Uploaded by Mikhail Tereshchenko

C++. Руководство для начинающих. Герберт Шилдт

advertisement
руководство ДЛЯ начинающих
Второе издание
А
Beginner's Guide
Second Edition
HERBERT SCHILDT
МсGга\\'-Нill/0sЬогпе
Cllicago SaIl r:raIlCISCO
1\1;JIJI'i(\ Мсхн;о Cil\' Мilап
;-\l'\" Dcll11 ~ап.lШJl SCOII\ Sll1gal"JI'(" SY(\IlC\'
J'\C\\'
\"OJ'k
Lj,IЮII
L,1'11I\01l
')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-е издание.
ский дом "Вильямс",
ISBN
2005. - 672 с. : ил. -
: Пер.
сангл.
-
М.
: Издатель-
Парал. тит. аш·л.
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оп
ЕПlеrprises lnlоmаliопаl.
published Ьу Williams РuЫishiпg House ассогdiпg 10 tbe АgreemеПI wilh R&l
Copyrighl © 2005
ISBN 5-8459-0840-Х (рус.)
JSBN 0-07-223215-3 (8.Нгл.)
© Издательскнli дом "Вильямс". 2005
© Тhc МсGrзw-Нill Соmралies. 2004
Оглавление
Об авторе
Введение
Модуль
1. Основы С++
Модуль 2. Типы данных и
операторы
Модуль З. Инструкции управления
Модуль
4. Массивы, СТрОI<И и указатели
Модуль 5. Введение в фУНI<ЦИИ
Модуль 6. О функциях подробнее
МОДУЛЬ 7. Еще о типах данных и операторах
Модуль 8. I<лассы и объекты
Модуль 9. О классах подробнее
Модуль 10. Наследование
МОДУЛЬ 11. С++-система ввода-вывода
Модуль 12. Исключения, шаблоны и кое-что еще
15
17
21
69
109
157
211
261
ЗОЗ
З49
З97
Приложение А. Препроцессор
465
521
571
639
Приложение Б. Использование устаревшего С++-компилятора
65з
Предметный указатель
656
Содержание
Об авторе
15
17
Введение
Как организована эта книга
Вопросы для текущего контроля
17
1.8
18
18
Спросим у опытного программиста
18
Практичсские навыки
Тсст для самоконтроля
Учсбные проекты
18
Никакого предыдущего опыта в области программирования не требуется
Требуемое программное оБССllсчение
Программный код
-
из Web-пространства
Для далЫlсйшего изучения программирования
Модуль
1. Основы С++
18
19
19
19
21
1.1. Из истории создания С++
Язык С: начало эры современного программирования
Предпосылки ВОЗIШКlЮВСНИЯ языка С++
22
23
24
25
Рождение С++
Эволюция С++
1.2. Связь С++ с языками Java и С#
1.3. Объектно-ориентированное программирование
Инкапсуляция
26
27
29
Ввод текста программы
30
31
32
32
34
Компилирование программы
34
Полиморфизм
Наследование
I
1.4. Создание, компиляция и выполнение С++-программ
Выполнение программЬ!
35
Построчныii "разбор полетов" первого ПРИl\fера IJporpaMMbl
36
Обработка синтаксических ошибок
38
1.5. Использование нсремснных
1.6. Использование операторов
1.7. Сt"пываllие данных с клавиатуры
1\0
Вариации на тему вывода данных
46
41
44
Познакомимся еше с одним ТIlIЮМ даJlНЫХ
1.8.
Использование IfНСТРУКЦИЙ управления
if и
(ог
47
52
С++: PYI<OBOACTBO для. начинающих
Инструкция
Цикл
1.9.
52
54
56
if
for
Использование блоков кода
Точки с запятой и расположение инструкций
Практика отступов
1.10.
Понятие о функциях
Ключевые слова С++
Идентификаторы
66
Модуль
2. Типы данных и операторы
Почему типы данных столь важны
2.1. Типы данНЫХ
Целочисленный тип
Типы данных с плавающей точкой
Тип данных Ьооl
Тип
void
2.2. Литералы
Шестнадцатеричные и восьмеРИЧllые литералы
Строковые литералы
Управляющие символьные последователыfстии
Со:щаиие инициализироваlfНЫХ переменных
Инициализация псременной
Динамическая инициалllзация переменных
Операторы
2.4.
75
77
78
80
83
84
84
85
87
87
88
Арифметические операторы
89
89
Инкремент и декремент
90
2.5. Операторы отношений и логические операторы
2.6. Оператор присваинания
2.7. Составные операторы приспаивания
2.8. П реобраэопание типов в операторах присваиваиия
Выражения
2.9.
69
70
70
72
С++
Символы
2.3.
58
59
62
64
65
Библиотеки С++
1.12.
1.13.
7
Прео6раэование типов в оыраЖСIlИЯ-Х
Пре06разооаIlИЯ, связанные с типом
bool
2.10.
Приведение тшlOВ
2.11.
Использование пробелов If КРУГЛЫХ скобок
92
98
99
100
101
101
1()2
102
104
8
Содержание
МОДУЛЬ
3. ИНСТРУКЦИИ управления
3.1. Инструкция if
Условное выражение
Вложенные if-инструкции
"Лестничная" конструкция
if-else-if
3.2. Инструкция switch
Вложенные инструкции
switcb
3.3. Цикл for
Вариации на тему цикла for
Отсутствие элементов в определении цикла
Бесконечный цикл
for
Циклы без тела
109
110
112
114
115
117
121
125
127
129
130
131
Объявление управляющей переменной цикла в заголовке
инструкции
for
3.4. Цикл while
3.б. Использование инструкции
3.7.
3.8.
3.9.
break для выхода из цикла
Использование инструкции сопtiпuе
Вложенные циклы
Инструкция
goto
МОДУЛЬ 4. Массивы, строки и ука3атели
4.1.
Одномерные массивы
На границах массивов без пограничников
4.2. Двумерные массивы
4.3. Многомерные массивы
4.4. Строки
Основы представления строк
Считывание строк с клавиатуры
4.5.
Некоторые библиотечные функции обработки строк
Функция
strcpyO
strcatO
Функция strcmp()
Функция strlenO
Функция
Пример использования строковых функций
Использование признака завершения строки
4.б. Инициализация массивов
Инициализация "безразмерных" массивов
4.7.
4.8.
Массивы строк
Указатели
132
134
143
145
151
152
157
158
162
163
165
169
169
170
173
173
173
173
174
174
175
177
180
182
183
С++: PYI<OBOACTBO для начинающих
Что представляют собой указатели
4.9. Операторы,
184
185
186
t 89
190
190
192
193
196
используемые с указателями
О важности базового типа указателя
Присваивание значений с помощью указателей
4.10.
Использование указателей в выражениях
Арифметические операции над указателями
Сравнение указателей.
4.11.
9
Указатели и массивы
Индексирование указателя
Строковые константы
198
~ассивыуказателей
203
205
206
Соглашение о нулевых указателях
4.12.
~ноroуровневая непрямая адресация
Модуль
5. Введение в функции
211
Основы использования функций
5.1. Общий формат С++-Функций
5.2. Создание функции
5.3. Использование аргументов
5.4. Использование инструкции return
Возврат значений
5.5.
Использование функций в выражениях
Правила действия областей видимости функций
5.6. Локальная область видимости
5.7. Глобальная область видимости
5.8. Передача указателей и массивов
в качестве аргументов функций
Передача функции указателя
Передача функции массива
Передача функциям строк
5.9.
Возвращение функциями указателей
Функция
5.10.
mainO
Передача аргументов командной строки функции
mainO
Передача числовых аргументов командной строки
5.11. Прототипы функций
Стандартные заголовки содержат прототипы функций
5.12.
Рекурсия
212
212
213
215
216
220
222
224
225
231
235
235
237
240
242
243
244
246
248
250
250
Модуль 6. О функциях подробнее
261
6.1. Два способа передачи аргументов
6.2. Как в С++ реализована передача аргументов
262
262
10
Содержание
6.3. Использование указателя ДЛЯ
6.4. Ссылочные параметры
6.5. Возврат ссылок
6.6. Независимые ссылки
обесПечения вызова по ссылке
Ограничения при использовании ссылок
6.7.
Пере грузка функций
Автоматическое пре06разование типов и п€регрузК2
6.8. Аргументы,
передаваемые функции 110 УМО.lчанИю
264
266
271
275
276
277
282
292
Сравнение возможности передачи аргументов по умолчаllИЮ
с перегрузкой функций
Об использовании аргументов, псредаваемых по умолчанию
6.9.
Перегрузка функций и неОДlIозначность
Модуль
303
7
Еще о типах данных и операторах
7.1.
294
296
298
Специфи каторы типа
const и volatile
const
Спецификатор типа уоl .. Ше
Спеl!ифик:атор типа
Специфю<аторы классов памяти
Спецификатор класса памяти
auto
класса памяти extern
7.2. Спецификатор
7.3. Статические переменные
7.4. Рсшстровые переменные
7.5. Перечисления
7.6. Ключевое слово typedef
7.7. Поразрядные операторы
ПоразряДныс операторы И, ИЛИ, исключающее ИЛИ и НЕ
7.8. Операторы сдвига
7.9. Оператор "знак вопроса"
7.10. Оператор "запятая"
7.11. Составные опсраторы I1РИСваивания
7.12. Использованис ключевого слова sjzcof
303
304
304
307
З08
ЗОВ
308
3\0
315
318
322
322
323
ЗЗО
ЗЗ9
С~ОДllая таблица приоритеТО8 С++-операторов
340
342
343
345
Модуль
349
8. Классы и объекты
В.1. Общи ii формат объявления класса
В.2. Определение класса и со:щание объектов
8.3. Добавление в класс функций-членов
8.4. Конструкторы и деструкторы
350
351
357
3б7
С++: РУКОВОДСТВО АЛ~ начинающих
8.5.
Параметризованные конструкторы
Добавление конструктора в класс
Vehicle
Альтернативный вариант инициализации объекта
8.6.
Встраиваемые функции
Создание встраиваемых функций R объявлении класса
8.7.
8.8.
8.9.
Массивы объектов
Инициализация массивов объектов
Ука:~атели на объекты
Модуль
9.1.
9.2.
9.3.
9. О классах подробнее
Переl"рузка КОIiСТРУКТОРОВ
370
373
374
376
378
388
389
391
397
Конструкторы. деструкторы и передача объектов
398
399
402
403
Передача объектов по ссылке
405
Потенциальные проблемы при передаче параметров
407
408
411
415
Присваивание объектов
Передача объектов функциям
9.4. Возвращение объектов функциями
9.5. Создание и использование конструктора копии
9.6.
9.7.
11
Функции-"друзья"
Структуры и объединения
Структуры
Объединения
9.8. Ключевое слово this
9.9. Персгрузка операторов
9.10. Перегрузка операторов
с использованием Функций-членов
О значении порядка операндов
421
-121
424
428
430
431
435
Использование функций-членов для Ilерегрузки
унарных операторов
9.11.
Перегрузка операторов с ИСПО-!lьзованисм функций-не членов класса
435
441
Испо~ьзование Функций-"друзей" для переrpузки
унарных операторов
Советы по реализащш перегрузки операторов
Модуль
10.1.
10. Наследование
ПОlIятие о наследовании
Доступ к членам класса и наследование
10.2.
Управление доступом к членам базового класса
10.3.
10.4.
Использование защищенных членов
Вызов конструкторов базового I(ласса
447
448
465
466
·170
4. 74.
477
-183
Содержание
12
10.5. Создание многоуровневой иерархии
10.6. Наследование нескольких базовых классов
10.7. Когда выполняются функции конструкторов и деструкторов
10.8. Указатели на производные типы
Понятие о виртуальной функции
493
497
498
501
5(12
502
502
Наследование виртуальных функций
50б
Зачем нужны виртуальные функции
.507
.509
514
Ссылки на производные типы
10.9. Ви.ртуальные функции и
полиморфизм
Применеtlие виртуальных функций
10.10. Ч исто виртуальные функции
Модуль
и абстрактн ые классы
11. С++-система ввода-вывода
521
Сравнение старой и новой С++-систем ввода-вывода
11.1.
522
523
524
524
526
527
Потоки С++
Встроенные С++-потоки
11.2. Классы потоков
11.3. Перегрузка операторов
ввода-вывода
Создание перегруженных операторов вывода
Использование функций-"друзей" для перегрузки
529
530
533
операторов вывода
Перегрузка операторов ввода
Форматированный ввод-вывод данных
11.4. Форматирование данных с использованием
функций-членов класса ios
11.5. Использование манипуляторов ввода-вывода
11.6. Создание co6cтвeНlfЫx манипуляторных функций
5З3
Файловый ввод-вывод данных
11.7. Как открыть и закрыть файл
11.8. Чтение и запись текстовых файлов
11.9. Неформатированный ввод-вывод данных n двоичном
Считывание и запись в файл блоков данных
11.10.
Использование других функций ввода-вывода
Версии функции
getO
Функция getlineO
Функция обнаружения конца файла
Функции
Функция
peekO и putbackO
flushO
режиме
540
543
545
546
549
551
554
556
557
558
559
559
559
С++: PYI<OBOACтeO дl\Я начинающих
13
11.11. Произвольный доступ
11.12. Получение информации об операциях ввода-вывода
565
568
Модуль
571
12. Исключения, wаблоны и кое-что еще
Обработка исключительных ситуаций
12.1.
Основы обработки исключительных ситуаций
Использование нескольких саtсh-инструкций
Перехват всех исключений
Определение исключений, генерируемых функциями
Повторное генерирование исключения
Шаблоны
12.2. Обобщенные функции
Функция с двумя обобщенными типами
, Явно заданная перегрузка обобщенной функции
12.3. Обобщенные классы
Явно задаваемые специализации классов
12.4. Динамическое распределение
памяти
Инициализация динамически выделенной памяти
60i
Выделение памяти для массивов
608
609
614
615
619
622
622
623
623
626
628
633
633
635
635
635
Выделение памяти для объектов
12.5.
Пространства имен
Понятие пространства имен
Инструкция usiпg
Неименованные пространства имен
Пространство имен
12.6.
572
572
578
581
583
585
587
587
590
591
594
597.
603
std
Статические члены класса
Статические члены данных класса
Статические функции-члены класса
12.7. Динамическая идентификация типов (RТГI)
12.8. Операторы приведения типов
Оператор dупarnic_сast
Оператор сопst_cast
Оператор
Оператор
static_cast
reinterpret_cast
Приложение А. Препроц&Ссор
639
Директива
640
642
644
#define
Макроопределения, действующие как функции
Директива
#error
14
Содержание
Директива
#include
Директивы
644
645
6,15
Директивы
МВ
ДиреКПIВы условной )(омпиляции
#if, #else, #еlif и #endif
#ifdef и #ifl1def
Директива #ulldef
ИСПОЛJ,ЗО8ание оператора defined
Директива #line
Директива #pragma
Операторы препроцессора "#" и "##"
Зарезероированные макроимена
Приложение Б. Использование
YCTapeBUlero C++-КОМПИI\IПОра
649
649
650
650
651
652
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ПЮГО здесь
11 области програММНРОВaJlI!Я НС
MaTeplI<1..I"IIIIK3KOrO предыдущего опыта
требуется. Поэтому, сели Г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адобится любой
(MicrosQft)
JI С++
1'13 таких современных
Builder (Borland).
Программный код -
компиляторов, как
Visual
С++
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ЫХ
них, которые
MOI')'T
It>p6epTu:\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-таН:
WWW:
info@williamspublishing.c:)m
http://www.williamspublishing.com
Информация для писем из:
России:
Украины:
115419, Москва, а/я 783
03150, Киев, а/я 152
МОДУЛЬ
1
ОСНОВЫ С++
1.1.
1.2.
1.3.
1.4.
1.5.
1.6.
1.7.
1.8.
1.9.
1.10.
1.12.
1.13.
Из истории создания С++
Связь С++ с языками Java и С#
Объектно-ориентированное программирование
Создание, компиляция и выполнение С++-программ
Использование переменных
Использование операторов
Считывание данных с клавиатуры
Использование инструкций управления
Использование блоков кода
Понятие о функциях
Ключевые слова С++
Идентификаторы
if и for
Глава 1. ОСНОВЫ С++
22
Если и существует один компьютерный язык, который определяет суп) со­
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
программи­
рования. Его влияние нельзя было переоцеtlить, поскольку он коренным образом
изменил подход ~ программированию и его восприятие. Его синтаксис и принци­
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лекательными, что в мире программирования непо-
+
u2i
aI
О
:I:
U
О
!Лава 1. Основы С++
24
стижимо быстро образовалась целая армия его приверженцев. С точки зрения
сегодняшнего ДНЯ этот феномен даже трудно понять, но, тем не менее, язык С
стал своеl'ородадолrожданным "свежим ветром". который вдохнул в программи­
рование новую жизнь. В результате С был общепризнан как самый популярный
структурированный язык 1980-х годов.
ПреДПОСblЛКИ возникновения языка С++
Приведенная выше характеристика языка С может вызвать справедливое не­
доумение: зачем же тогда, мол, был изобретен я;tык С++? Если С
-
такой успеш­
ный и полезный язык, то почему возникла необходимость в чем-то еще? Оказы­
вается, все дело в сложности. На протяжении всей истории программирования
усложнение программ заставляло программистов искать пути, которые бы позво­
лили справиться со сложностью. Язык С++ можно считать одним из способов ее
преодоления. Попробуем лучше раскрыть взаимосвязь между постоянно возрас­
тающей сложностью программ и путями развития языков программирования.
Отношение к программированию резко измен flЛОСЬ с момента изобретения КО.\{­
пьютера. Например, программирование для первых вычислительных машин состоя­
ло в переключении тумблеров на их передней панели таким образом, чтобы их поло­
жение соответствовало двоичным кодам машинных команд. Пока длины программ
не превышали нескольких сотен команд, такой метод еще имел право на существо­
вание. Но по мере их дальнейшего роста был изобретен язык ассемблер, чтобы про­
граммисты могли использовать символическое представление машинных команд.
Поскольку программы продолжали расти в размерах, желание справиться с более
высоким уровнем сложности ВЫЗВало появление ЯЗЫКОВ высокого уровня, разработ­
ка которых дала программистам больше инструментов (новых и разных).
Первым широко распространенным языком программирования был, конечно
же,
FORTRAN.
Несмотря на то что это был очень значительный шаг на пути про­
гресса в области программирования,
FORTRAN
все же трудно назвать языком,
который способствовал написанию ясных и простых для понимания программ.
Шестидесятые годы двадцатого столетия считаются· периодом появления струк­
турированного программирования. Именно такой метод программирования и
был реализован в языке С. С помощью структурированных языков программи­
рования можно было писать программы средней сложности, причем без особых
героических усилий со стороны программиста. Но если программный проект до­
стигал определенного размера, то даже с использованием упомянутых структури­
рованных методов его сложность резко возрастала и оказывалась непреодолимой
для возможностей программиста. К концу 70-х к "критической" точке подошло
довольно много проектов.
С++: PYI<080ACTBO ДМl начинающих
25
Решить эту проблему "DЗЯЛИСЬ" новые технолоmи программирования. Одна из
них получила название объектно-ориентированного nрограм.мuрованuя (ООП).
Вооружившись методами
00 П, программист мог справляться с программами го­
раздо большего размера, чем прежде. Но язык С не поддерживал методов ооп.
Стремление получить объектно-ориентированную версию языка С в конце кон­
Несмотря на то что язык С был одним из самых любимых и распространен­
возможности по написанию сложных программ достигли своего предела. Же­
лание преодолеть этот барьер и помочь программисту легко справляться с еще
более объемными и сложными программами
-
вот что стало основной причи­
ной создания С++.
Рождение С++
Язык С++ был создан Бьерном Страуструпом
Laboratories
(Bjame Stroustrup)
в
1979
году
(г. Муррей-Хилл, шт. Нью-Джерси). Сиачала но­
with Classes),
но в
1983
году он стал
называться С++.
Страуструп построил С++ на фундаменте языка С, включающем все его
средства, атрибуты и основные достоинства. Для него также остается в силе
принцип С, согласно которому программист, а не язык, несет ответственность
за результаты работы своей программы. Именно этот момент позволяет понять,
что изобретение С++ не было попыткой создать новый язык программирова­
ния. Это было скорее усовершенствование уже существующего (и при этом
весьма успешного) языка.
Большинство новшеств, которыми Страуструп обогатил язык С, было предна­
значено для поддержки объектно-ориентированного программирования. По сути,
С++ стал объектно-ориентированной версией языка С. Взяв язык С за основу,
Страуструп подготовил плавный переход к ооп. Теперь, вместо того, чтобы из­
учать совершенно новый язык, С-программисту достаточно было· освоить только
ряд новых средств, и он мог пожинать плоды использования 06ъектно-ориенти­
рованной технологии программироваиия.
Создавая С++, Страуструп понимал, иасколько важно, сохранив изначальную
суть языка С, Т.е. его зффективность, гибкость и принципы разработки, внести в
него поддержку объеКТlю-ориеllТИРОВaJlНОl"O орограммирования. К счастью, эта
цель была достигнута. С++ по-прежнему предоставляет программисту свободу
действий и власть над компьютером (которые были присущи языку С), значи­
тельно расширяя при этом его (программиста) возможности за счет использова­
ния объектов.
о
1:
О
ных профессиональных языков программирования, настало время, когда его
в компании ВеН
iЗ
()
цов и привело к созданию С++.
вый язык получил имя "С с классами" (С
:j:
u
Глава 1. Основы С++
26
Несмотря на то что С++ изначально был I-I.щелен на поддержку очень БО.1Ь­
ПlИX программ, этим, Конечно же, его использование не ограничивалось. И 8 са­
мом деле, объектно-ориентированные средства С++ можно эффективно приме­
пять практически к любой задаче программирования. Неудивительно, что С++
используется для создания компиляторов, peДёJKTOpOB, компьютерных игр и про­
грамм сетевого обслуживания. Поскольку С++ обладает эффективностью языка
С, то программиое обеспечение многих высокоэффективных систем построено с
использованием С++. Кроме того, С++
-
это язык, который чаще всего выбира­
ется для Wiпdоws-программирования.
Эволюция С++
С момента изобретения С++ претерпел т~,и крупные l1ереработки, причем
каждый раз язык как ДОПОJlllЯЛСЯ новыми средствами, так и в чем-то изменялся.
Первой ревизии он был подвергнут в
1985 году. второй -
в
1990, а третья произо­
шла в процессе стандартизации, который актюшзировался в начале 1990-х. Спе­
циально для этого был сформирован объединенный АNSI/ISО-комитет (я был
его членом), который
25 января 1994 года принял
первый проект предложенного
на рассмотрение стандарта. В этот проект БЫЛII включены все средства, впервые
определенные Страуструпом, и добавлены новые. Но в целом он отражал состоя­
ние С++ на тот момент времени.
Вскоре после завершения работы над первым проектом стандарта С++ про­
изошло событие, которое заставило значительно расширить существующий
стандарт. Речь идет о создании'Ллександром Степановым (Alexander Stepanov)
стандартной библиотеки шаблонов
вы узнаете позже,
STL -
(Standar<\ Template Library - 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·
С++: руководство для начинающих
(Borland).
27
Поэтому код программ, приведенных в этой Ю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ределенной операционной системы и в расчете на ЦП
конкретного типа. Например, если вы хотите написать высокоэффективную про­
грамму для выполнения на процессоре
онной системы
Языки
Windows, лучше
java и С#
Intel Pentium
под управлением операци­
всего использовать для этого язык С + +.
ра.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 и
С#.
С++: руководство ДЛ)) начинающих
и последнее. Языки
C++,java
29
и С# предназначены для решения различных
классов задач. Поэтому вопрос "Какой язык лучше?" поставлен некорректно.
Уместнее сформулировать вопрос иначе: "Какой язык наиболее подходит для ре­
шения данной задачи?".
:D
~ПРОСЫ ДI\'iI текущего l<онтрОл'il _ _ _ _ _ _ __
1.
От какого языка программирования произошел С++?
2.
Какой основной фактор обусловил создание С++?
3. Верно ли, что С++ является предком языков Java и С#?·
ВАЖНОI
58 0 бьектнn-nр и еЫZИРОВQ нн ое
программирование
Основополагающими для разработки С++ стали принципы объектно-ори­
ентированного программирования (ООП). Именно ООП явилось толчком для
создания С++. А раз так, то, прежде чем мы напишем самую простую С++-про­
грамму, важно понять, что собой представляют принципы ООП.
Объектно-ориентированное программирование объединило лучшие идеи
структурированного с рядом мощных концепций, которые способствуют более
эффективной организации программ. В самом общем смысле любую программу
можно организовать одним из двух способов: опираясь на код (действия) или на
данные (информация, на l<ОТОРУЮ направлены эти действия). При использова­
нии лишь методов структурированного программирования программы обычно
опираются на код. Такой подход можно выразить в использовании "кода, дей­
ствующего на данные".
Механизм объектно-ориентированного программирования основан на выборе
второго способа. В этом случае ключевым принципом организации программ яв­
ляется использование "данных, управляющих доступом
!(
коду". В любом 06ъек­
тно-ориентированном языке программирования определяются данные и процеду­
ры, которые разрешается применить к этим данным. Таким образом, тип данных в
точности определяет, операции какого вида можно примешпь к этим данным.
1. Язык С++ произошел от С.
2. Основной фактор создания С++ -
t
u
повышеIше сложности ПРОll>амм.
3. Верно: С++ действительно является родительским языком для Java и С#.
ID
О
I
U
О
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:
фуmщий (1I0Дl1рограмм). Функции и переменныс, СОС1'авляющнс класс, назьша­
ются его членами. Таким образом, переменная, объявленная в КЛi!ссе, называется
Ч: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перфеЙс.
+
+
u
1i
CD
О
1:
U
О
Глава 1. Основы С++
32
НаслеАование
Ншледован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 для от­
носительно не60лыllш npolpaJtlJtl иcnоль:юеtUШe
000 AIOжem
8Нести
неоnpавданкые затраты cuсте.мных ресурсов? И cnраеедлuео ли это
для С++?
Ответ.
Нет. Ключевым моментом для понимания применения ООП в С++ явля­
ется то, что оно позволяет писать объектно-ориентированные програм'
МЫ, но не явл.яется 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.
Выполнить.
+
u+
]5
ID
О
:J:
8
Глава
34
1. Основы С++
Прежде чем ПрИC1)'lIать к выполнению этих действий, необходимо определить
два термина: исходный код и объектный код. Исходный код
-
это версия программ ы,
которую может читать человек. Она сохраняется в текстовом файле. Приведенный
выше листинг - это пример исходного кода. Вьmолняемая версия программы (она
создается компилятором) называется 06ьeютmым или выnолн.яе.мым "одо.м.
ВВОД текста программы
Программы, представленные в этой книге, можно загрузить с Web-сзйта ком­
пании
Osborne
с адресом:
www.osborne.com. При желании вы можете ввести
текст программ вручную (это иногда даже полезно: при вводе кода вручную мы
запоминаете ключевые моменты). В этом случае необходимо использовать ка­
кой-нибудь текстовый редактор (например
Windows),
а не текстовой процессор
WordPad, если вы работаете
(word processor). Дело в том, что при
в ОС
8воде
текста программ должны быть созданы исключительно текстовые файлы, а не
файлы, в которых вместе с текстом (при использовании текстового процессо­
ра) сохраняется информация о его форматировании. Помните, что информация
о орматировании помешает работе С++-компилятора.
Имя файла, который будет содержать исходный код программы, формально
может быть любым. Но С++-программы обычно хранятся в файлах с расшиrе­
нием
. срр.
Поэтому называйте свои С++-программы любыми именами, но в ка­
честве расширения используйте
грамму
. срр.
Например, назовите нашу первую про­
Sample . срр (это имя будет употребляться в дальнейших инструкциях),
а для других программ (если не будет специаЛJ,НЫХ указаний) выбирайте имена
по своему усмотрению.
Компилирование программы
Способ компиляции программы
Sample. срр зависит от используемого ком­
пилятора и выбранных ОIЩИЙ. Более того, многие компиляторы, например ViStlal
С++ (Мiсrоsoft) и С++ Builder (Borland), предоставляют два различных способа
компиляции программ: с помощью компилятора командной строки и интегриро­
ванной среды разработки
(lntegrated Development Environment - IDE).
Поэтому
для компилирования С++-программ невозможно дать универсальные инструк­
ции, которые подойдут для всех компиляторов. Это значит, что вы должны сле­
довать инструкциям, приведенным в СОПРОВОДIfТельной документации, прилага­
емой к вашему компилятору.
Но, если вы используете такие популярные компиляторы, как
и С++
Builder, то проще всего в обоих случаях
Visual
С++
компилировать и выполнять про­
граммы, приведенные в этой книге, с использованием компиляторов командной
С++: PYI<OBOACTBO для начинающих
строки. Например, чтобы скомпилировать программу
35
Sample. срр, используя
Visua1 С++, введите следующую командную строку:
С:\
+
... >сl -GX Sample.cpp
Опция
-GX предназначена для повышения качества компиляции. Чтобы исполь­
Visual С++, необходимо выполнить пакет­
ный файл 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-прило­
жений. Однако ни одна из программ, представленных в этой книге, не использует
u+
]1
!3:I:
U
О
Глава 1. Основы С++
36
графический И1lТерфейс пользователя (graphics
что
Windows -
user interface - GUI). Дело в 1'ОМ,
довольно сложная среда для написания программ, включающая
множество второстепенных тем, не связанных напрямую с языком С++. ДЛЯ соз­
дания Wiпdоws-программ, которые демонстрируют возможности С++, потребо­
валось бы написать сотни строк кода. В то же время консольные приложения го­
раздо короче графических и лучше подходят для обучения программированию.
Освоив С++, вы сможете без ilроблем примешlТЬ свои знания в сфере создания
Wiпdоws-приложеНиЙ.
Построчный ··разбор полетов··
nepBoro примера
nporpaMMbI
Несмотря на то что программа
Sarnple. срр
довольно мала по размеру, она,
тем не менее, содержит ключевые средства, хара ктерные для всех С ++- програм м.
Поэтому мы подробно рассмотрим каждую ее строку. Итак, наша программа на­
чинается с таких строк.
/*
Это
простая
Назовите
С++-программа.
этот
файл
Sarnple.cpp.
*/
Это -
Kaм.мeнmapUЙ. Подобно большинству других языков программирования,
С++ позволяет вводить в исходный код программы комментарии, содержание ко­
торых компилятор игнорирует. С помощью комментариев описываются или разъ­
ясняются действия, выполняемые в программе, и эти разъяснения предназначают·
ся для тех, кто будет читать исходный код. В данном случае комментарий просто
идентифицирует программу. Конечно, в реальных приложениях комментарии ис­
пользуются для разъяснения особенностей работы отдельных частей программы
или конкретных действий программных средств. Другими словами, вы можете ис­
пользовать комментарии для детального описания всех (или некоторых) ее строк
В С++ поддерживается два типа комментариев. Первый, показанный в нача­
ле рассматриваемой программы, называется .м.НОlострочuым. Комментарий этого
типа должен начинаться символами
/*
(косая черта и "звездочка") и заканчи­
ваться ими же, но переставленными в обратном порядке
(* /). Все, что находит­
ся между этими парами символов, компилятор игнорирует. Комментарий этого
типа, как следует из его названия, может занимать несколько строк. Второй тнп
комментариев (однострочный) мы рассмотрим чуть ниже.
Приведем здесь следующую строку программы.
#include <iostream>
С++: PYI<OBOACTBO Mt;\ начинающих
37
торый представляет собой внешний исходный файл, помещаемый компилятором
•
в начало программы с помощью директивы
:0
в языке С++ определеtI ряд зшоловков
(header),
которые обычно содержат ин­
формацию, необходимую для программы. В нашу программу включен зaroловок
<iostream>
(он используется ДЛЯ поддержки С++-системы ввода-вывода), ко­
# incl ude. Ниже в этой
ближе познакомимся с заголовками и узнаем, почему они так важны.
книге мы
Эта строка означает, что компилятор должен использовать пространство имен
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 ().
'111
::1:
:и
using namespace stdi
-
::0
:0
Рассмотрим. следующую строку программы:
Пространства имен
: +
: +
:U
Выполнение С++-программы начинается и заканчивается
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е С++-инструкции завершаются точкой с запятой.
Сообщение "С++-программирование
-
это сила!" представляет собой
строку. В С++ под строкой понимается последовательность символов, заключен­
ная в двойные кавычки. как вы увидите, строка в С++
-
это один из часто ис­
пользуемых элементов языка.
А этой строкой завершается функция
return
main ():
О;
При ее выполнении функцJiЯ main О возвращает вызывающему пРощ.оссу
(в роли которого обычно выступает операционная система) значение О. ДЛЯ боль­
шинства операционных систем нулевое значение, которое возвращает эта функ­
ЦИЯ, свидетельствует о нормальном завершеНIIИ програмМЫ. Другие значения
MOtyT означать завершение программы в связи с какой-нибудь ошибкой. Слово
return
относится к числу ключевых и используется для возврата значения из
функции. При нормальном завершении (т.е. без ошибок) все ваши программы
должны возвращать значение О.
Закрывающая фиrypная скобка в конце програММbJ формально завершает ее.
Обработка синтаксических ошибок
Введите текст только что рассмотренной программы (если вы еще не сделали это),
скомпилируйте ее и выполните. Каждому програ.\tМИСТУ известно, насколько легко
С++: PYI<OBOACTBO ДI\~ начинающих
39
при вводе текста проrJ>аммы в компьютер вносятся случайные ошибки (опечатки). К
счастью, при попытке скомпилировать такую программу компилятор "просиmaлит"
сообщением о наличии cuнmaкt:UЧeCКUX ошибок. Большинство С++-компи.ляторов
поDыаются "увИдеть" смысл в исходном коде пJюгрзммы' неэависимо от того, что
вы ввели. Поэтому сообщение об ошибке не всегда отражает истинную причину про­
блемы. Например, если в предыдущей ПРОrJ>змме случайно опустить открывающую
фигурную скобку после имени фymщии та in
( ) , компилятор укажет в качестве ис­
точника ошибки инструкцию сои t. Поэтому при получении сообщения об ошибке
просмотрите две-три строки кода. непосредственно предшествующие строке с "об­
наруженной" ошибкой. Ведь иногда компилятор начинает "чуять недоброе" только
через несколько строк после реального местоположения ошибки.
Спросим у опытноrо проrраммиста
Вопрос. ПОJICWlCО сообщений
06 Оlllибках JlCOU
cmfJe резульmатОfJ CfJoeu работы и
C++-кО.JtmWUUrJОР 8ьЮаem
fJ
Кalfе­
nреiJррежiJенUR. Уем же npeiJy-
npежiJенu.я оmлwшютс. от оllltlбок и ка слеiJyem petullpOfJa",. на них?
Ответ.
действительно, мноrnе C++-КОМlIИЛЯТоры выдают В качестве результатов
своей работы не только сообщения о неисправиМhIX синтаксических ошиб­
ках, но и предупреждения
(waming)
различных типов. Если компилятор
"уверен" D некорректности программного кода (например, инструкция не
завершается точкой с запятой), он сиmа.лизирует об ошибке, а если у него
есть лишь "подозрение" на некорректностъ при видимой правильности с
точки зрения синтаксиса, то он выдает предупреждение. Тогда программист
сам должен оценить, насколько справедливы подозрения компилятора.
Предупреждения также можно использовать (по желанию программиста)
для информирования о применении в коде неэффекmвных конструкций
или устаревших средств. Компиляторы позволяют выбирать различные
опции, которые могут информировать об интересующих вас вещах. Про­
граммы, приведенные в этой книге, написаны в соответствии со стандар­
том С++ и при корректном вводе не должны генерировать никаких пред­
упреждающих сообщений.
Для примеров этой книги достаточно использовать обычную настрой­
ку компилятора. Но вам все же имеет смысл эаглянугь в прилагаемую к
компилятору документацию и поинтересоваться, какие возможности по
управлению процессом компиляции есть в вашем распоряжении. Многие
компиляторы довольно "интеллектуальны" и могут помочь в обнаруже­
нии неочевидных ошибок еще до того, как они перераС1)'Т в большие про­
блемы. Знание принципов, используемых компилятором при составлении
отчета об ошибках, .стоит затрат времени и усилий, которые потребуются
от программиста на их освоение.
+
u+
iR
о
:I:
U
О
Глава 1. Основы С++
40
~npocы МЯ текущего контроля - - - - - - - 1.
С чего начинается выполнение С++-программы?
2.
Что такое
cout?
З. Какое действие выполняет инструкция ~ include <iostream>?·
=
Возможно, самой важной конструкцией в любом языке программировання
является переменнcut. Пере.менная
-
это именованная область памяти, в которой
MOryт храниться различные значения. При этом значение переменной во время
выполнения программы можно изменить. Другими словами, содержимое пере­
менной изменяемо, а не фиксированно.
В следующей программе создается перемеиная с именем
присваивается значение
переменной
length
length,
7, а затем на экране отображается сообщение
7.
которой
Значение
равно
// Использование переменной.
#include <iostream>
using namespace std;
int main ()
{
int length;
//
length
Переиениой
cout «
cout «
=
7; / /
"Значение
~ Здесь об~.етCR переиеина•.
присваиааетс. чиcnо
lenqth
переменной
length
length; // ОТображаетCR
// lenqth, Т.е.
равно
7.
";
значение переиеиной
чиcnо
7.
1. Любая С++-программа начинает выполнение с функции тШпО.
2. Слово cout представляет собой встроенный идентификатор, который имеет отно­
шение к выводу данных на консоль.
З. Она включает в код программы заголовок
функционирование системы ввода-вывода.
<iostream>,
который lIоддерживает
С++: руководство ДI\.~ начинающих
return
41
О;
Как упоминалось выше, для С++-программ можно выбирать любые имена.
Тогда при вводе текста этой программы дадим ей, скажем, имя
VarDemo. срр.
Что же нового в этой программе? Во-первых, инструкция
+
+
u
2i
ID
О
I:
(J
int length; //
Здесь
объявляется
объявляет переменную с именем
О
переменная.
length целочисленного типа. В С++ все перемен­
ные ДОЛЖНЫ быть объявлены до их использования. В объявлении переменной по­
мимо ее имени необходимо указать, значения какого типа она может храниТЬ. Тем
самым объявляется тип переменной. В данном случае переменная length может
хранить цслочисленные значения, т.е. целые числа, лежащие в диапазоне
32767.
-32 768-
В С++ для объявления перемеllliОЙ целочисленного типа достаточно поста­
вить перед ее именем ключевое слово
int.
Ниже вы узнаете, что С++ поддерживает
широкий диапазон встроенных типов переменных. (Более того, С++ позволяет про­
граммисту определять co6cTBeHtrwe типы данных.)
Во-вторых. при выполнении следующей инструкции переменной присваива­
ется
KOHKpeTHQe
значение:
length = 7; //
Переменной
length
присваивается число
Как отмечено в комментарии, здесь переменной
length
7.
присваивается число
В С++ оператор nрисваuванuя представляется одиночным знаком равенства
7.
(=).
Его действие заключается в копировании значения, расположенного справа от
оператора, в переменную, указанную слева от него. После выполнения этой ин­
струкции присваивания переменная
length
будет содержать число
7.
Обратите внимание на использование следующей инструкции для вывода
значения переменной
cout «
length:
length; //
Отображается
число
7.
В общем случае для отображения значения переменной достаточно в инструк­
ции
cout
поместить ее имя спраоа от оператора
кретном случае переменная
"«".
Поскольку в данном кон­
length содержит ЧИCJЮ 7, то оно и будет отображено
на экране. Прежде чем переходить к следующему разделу, попробуйте присвоить
пере мен ной
length другие значения
(в исходном коде) и посмотрите на резуль­
таты выполнения этой программы после внесения изменений.
Подобно большинству других языков программирования, С++ поддерживает
полный диапазон арифметических операторов, которые позволяют выполнять
42
Глава
1. Основы С++
действия над числовыми значениями, используемыми в программе. Приведем
самые элементарные.
+
Сложение
Вычитание
*
/
Умножение
Деление
Действие этих операторов совпадает с действием аналогичных операторов в
алгебре.
В следующей программе оператор "*" исполъэуется для вычисления площади
прямоугольника, заданного его длиной и шириной.
//
Использование оператора.
#include <iostream>
using narnespace std;
int rnain ()
{
int length; //
int width; / /
int area;
//
Здесь
объявляется
Здесь
объявляется В'l'орая
переменная.
Здесь
объявляется
переменная.
length = 7; //
width = 5; //
Число
7
5
area
Число
п~ременная.
третья
присваивается переменной
присваивается
length * width; //
переменной
length.
width.
~ Здесь зычиcn.е~СR nnощад&
/ / DpJDIОУГОJIЪниха, а реЗУJIЪ~а~ Dpоизведевия:
// звачений aepeкeRRНX lenqth и vidth
// прксваиаае~~ аерекенной area.
cout «
cout «
return
"ПЛощадь
прямоугольника
area; //
Здесь
равна
отображается
";
число
35.
О;
в этой npoграмме сначала объявляются три перс~менные
length, width и area.
7, а переменной width - число 5.
После этого вычисляется произведение значений переменных length и width
Затем переменной
length
присваивается число
(т.е. вычисляется площадь прямоугольника), а результат умножения присваи­
вается переменной
area.
При выполнении программа отображает следующее.
С++: PYI<OBOACтaO для начинающих
ПЛощадь
прямоугольника
43
35.
равна
В этой программе в действительности нет никакой необходимости в исполь­
зовании переменной
area.
Поэтому предыдущую программу можно переписать
следующим образом.
11
11
Упрощенная версия
программы вычисления
площади
U
прямоугольника.
О
#include <iostream>
using namespace std;
int main ()
{
int length; 11
int width; /1
Здесь
объявляется переменная.
Здесь
объявляется
length = 7; /1
width = 5; /1
Число
7
5
cout «
cout «
Число
"Площадь
вторая
переменная.
присваивается
переменной
присваивается
переменной
прямоугольника
length * width; 1/
равна
length.
width.
";
Здесь о~обраааетс. чиcnо
11
11
11
return
35
(реsуnьта~ 8wпоnвеИКR
операции
lenqth
*
vidth
8JoI80,QИ'Ж'CJI на эхраи В&ПрJDlP)
•
О;
в этой версии площадь прямоугольника вычисляется в инструкции сои t пу­
тем умножения значений переменных
length и width с последующим выводом
результата на экран.
Хочу 06ратИ1:Ь ваше внимание на то, что с помощью одной и той же инструк­
ции можно объявить не одну, а сразу несколько перемеНIiЫХ. Для этого достаточ­
но разделить их имена запятыми. Например, переменные
length, width и area
можно было бы объявить таким образом.
int length, width, area; 11
//
Все переменные объявлены в
одной инструкции.
В профессиональных С++-npограммах объявление нескольких переменных в
одной инструкции
-
+
u+
J5
!3:I:
обычная практика.
Глава
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
int length: //
int width:
//
Здесь
объявляется переыеииая.
Здесь
объявляется вторая
11:1
О
переме~ная.
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). После выполнения
переменная length будет содержать значение длины прямоу­
же пользователь введет нечисловое значение, переменная length
cin
roльника. (Если
получит нулевое значение.) Инструкции, обеспечивающие ввод и считывание
значения ширины прямоугольника, работают аналогично.
:I:
8
46
Глава
1. Основы С++
Вариации на тему вывода данных
До сих пор мы использовали самые простые типы инструкций сои t. Однако в
С++ предусмотрены.и другие варианты вывода ;l:aнHblx. Рассмотрим два из них.
Во-первых, с помощью одной инструкции сои t можно выводить сразу несколь­
ко порций информации. Например, в программе вычисления площади для ото­
бражения результата использовались следующш: две строки кода.
cout «
cout «
"ПЛощадь
прямоугольника равна
";
length * width;
Эти две инструкции можно заменить одной.
cout «
"Площадь
прямоугольника равна
Здесь в одной инструкции
cout
" «
length * width;
используется два оператора
зволяют сначала вывести строку "Площадь
"«",
лрямоугольника
которые по­
равна
", затем
cout
значение площади прямоугольника. В общем случае в одной инструкции
можно соединять любое количество операторов зывода, предварив каждый эле­
мент вывода "своим" оператором
"«".
Во-вторых, до сих пор у нас не было необходимости в переносе выводимых
данных на следующую строку, Т.е. в выплненIIии
последовательности команд
"возврат каретки/перевод строки". Но такая необходимость может появиться до­
вольно скоро. В С++ упомянутая выше последовательность команд генерируется
с помощью CUМВOJllJ навой строки. Чтобы поместить этот символ в строку, исполь·
зуйте код \п (обратная косая черта и строчная буква "п"). Теперь нам остается на
практике убедиться, как работает последовательность \п.
/*
в этой программе демонстрируется и,::пользование кода
который
генерирует
*/
finclude <iostream>
using namespace std;
int main ()
{
cout
cout
cout
cout
«
«
«
«
return
"один\п";
"два\п";
"три";
"четыре";
О;
переход
на
новую строку.
\п,
С++: руководство ДЛЯ начинающих
47
При выполнении эта программа генерирует такие результаты.
один
+
u+
два
:i5
тричетыре
Символ новой строки можно размещать в любом месте строки, а не только в
конце. Вам стоит на практике опробовать различные варианты применения кода
\n, чтобы убедиться в том, что вам до конца понятно его назначение.
~ПРОСЫ A/IЯ теl<ущего l<онтрОл'i1 _ _ _ _ _ _ __
1. Какой оператор используется в С++ дЛЯ вводаДaJШЫХ?
2. С каким устройством по умолчанию связан идентификатор cin?
3. Что означает код \n?*
Познакомимся еще с ОДНИМ
ТИПОМ данных
В предыдущих программах использавались переменные типа
перемеlfные типа
in t
int.
Однако
могут содержать только целые числа. И поэтому их нельзя
использовать для представления чисел с дробной частью. Например, персмеЮ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ой строки.
~
:r
8
48
Глава 1. Основы С++
Чтобы лучше понять разницу между типами дaJmыx iлt и
double, рассмотрим
следующую npoграмму.
/*
Эта
лрограмма
данных
iлt и
иллюстрирует
различия
между
типами
double.
*/
<iostream>
using namespace std;
liлсludе
int mаiл () {
int ivar;
//
double dvar; //
ivar
Здесь объявляется
переменная типа
Здесь
переменная
Переменная
100; //
dvar = 100.0; //
cout «
cout «
объявляется
ivar
Переменная
"Исходное
"Исходное
значение
значение
получает значение
dvar
int.
double.
типа
100.
получает значение
переменной
ivar: " «
ivar
"\п";
перемеНЕОЙ
«
dvar: " «
dvar
«
cout «
"\п";
return
"Значение
"Значение
ivar
dvar
значения на
З.
nOC.:tIe деления:
после
деления:
" «
" «
ivar «
dvar «
О;
Вот как ВЫГЛЯДЯТ результаты выполнения этоii программы.
Исходное
значение
"\п";
ВМвОДИJI ПУС~ С!1'роху.
//
// А теперь делим оба
ivar
ivar / 3;
dvar = dvar / 3.0;
cout «
cout «
100.0.
переменной
Исходное значеНие переменной
ivar: 10~
dvar: 10)
"\п";
"\п";
С++: руководство ДЛ)! начинающих
Значение
Значение
ivar
dvar
после
деления:
33
после
деления:
33.3ЗЗЗ
Как видите, при делении значения переменной i
численное деление, в результате которого
делении на 3 значения переменной
(33)
var
на
3 выполняется
49
цело­
теряется дробная часть. Но при
dvar дробная часть сохраняется.
В этой ПРО'1>амме есть еще новая деталь. Обратите внимание на эту строку.
cout «
"\n"; //
:t
u
2i
са
О
:r
u
О
Выводим пустую строку.
При выполнении этой инструкции IlРОИСХОДИТ переход на новую строку. Ис­
пользуйте эту инструкцию в случае, когда нужно разделить выводиМые данн'ые
пустой строкой.
Спросим
V опытного программиста
Вопрос. Почему в С++ существgют различные типы аанных дм npeдcmавле­
НIIR целых IfUсел и ЗNQlfeHUU с плавающей точкой? ПочеJfCУ бы все чис­
ловые зна.,енllR "е представлять с nОJfCощью оОного типа данных?
Ответ.
В С++ предусмотрены различные типы даиных, чтобы программиеты
могли создавать эффективные про граммы. Например, вычисления с не­
ПОЛЬ."JО8aJ-lием Цt'ЛОЧИCJIенной арифметики выполняются гораздо быстрее,
чем вычислею1Я, производимые над значениями е плавающей точкой. Та­
ким образом, если оам не нужны значения с дробной частью, то вам и не
стоит зря расходовать системные ресурсы, соязанныс с обработкой таких
типов данных, как Ноа t или
double.
Кроме того, для хранения значе­
ний различных пmов требуются разные по размеру области пaмsrrи. Под­
держивая различные типы данных, С++ позволяет программисту выбрать
наилучший вариант использования системных ресурсов. Наконец, для не­
которых алгоритмов требуется использование данных конкретного nmа.
И потом, широкий диапазон встроенных С++-типов данных предоставля­
ет программисту проявнтъ максимальную гибкость при реализации реше­
ний разнообразиых задач программирования.
Проект
1.1.
, FtoM. срр
',l r4l:
~~, rl~ФJ.
:
\' ~-
'I'J,.V,
Несмотря на то что в предыдущих примерах программ были
продемонстрированы важные средства языка С++, они полезны
больше с точки зрения теории. Поэтому важно закрепить даже те немногие све­
ден ия, которые вы уже почерпну ли о С + + , при создании практических программ.
В этом проекте мы напишем про грамму перевода футов в метры. Программа
:;1
i•
!
1
Глава
50
1, Основы С++
должна предложить пользователю ввести значение в футах, а затем отобразить
значение, преобразованнос в метры.
Один метр равен приблизительно 3,28 футам. Следовательно, в программе мы
должны использовать представление данных с плавающей точкой. для выпол­
нения преобразования в программе необходимо объявить две переменные типа
double:
одну для хранения значения в футах, а другую для хранения значения,
преобразованного в метры.
Последовательность действий
1.
Создайте новый С++-фзйл с именем
Ftc.M. срр.
(Конечно, вы можете вы­
брать для этого файла любое другое имя.)
2.
Начните программу следующими строками, которые разъясняют назначе­
ние программы, включите заголовок iозtrеаm и укажите пространство
имен
std.
/*
Проект
Эта
1.1.
про грамма
Назовите
преобразует фУ'ГЫ в метры.
программу
FtoM.cpp.
*/
#include <iostream>
using namespace std;
3.
Начните определение функции main
int main () {
double f; / /
double т; //
4.
()
с объявления переменных f и!'.1.
содержит длину в футах
содержит результат
преобразоваНИR в метрах
добавьте код, предназначенный для ввода значения в футах.
cout « "Введите длину в фута>!: ";
cin » f; 1/ считывание значения, выраженного
5.
в
футах
добавьте код, которые выполняет преобраэование в метры и отображает
результат.
m = f 1 3.28; /1 преобразование в метры
cout « f « " футов равно n « m « " метрам.";
С++: PYI<OBOACTBO МЯ начинающих
6.
51
-
Завершите программу следующим образом.
return
О;
~
t
:U
7.
:~
'1:11
:0
Ваша законченная программа должна иметь такой ВИД.
::1:
:U
/*
Прое1(Т
Эта
:0
1.1.
программа
Назовите
преобразует
про грамму
футы в
метры.
FtoM.cpp.
*/
#include <iostrearn>
using narnespace std;
int rnain () {
double f; //
double т; //
содержит
длину в футах
содержит результат
преобразования
в ме­
трах
cout « "Введите длину в футах: ";
cin » f; // считывание значения, выраженного
в
футах
rn = f / 3.28; // преобразование в метры
cout « f « " футов равно" « rn « " метрам.";
return
8.
!
О;
СJ,(омпилируйте и выполните программу. Вот как выглядит один из воз­
можных результатов ее выполнения.
Введите
5
9.
длину
футов равно
t-
в
футах:
1.52439
5
метрам.
Попробуйте ввести другие значения. Л теперь поnытайтесь изменить про­
грамму. чтобы она преобразовывала метры в футы.
I
Глава 1. Основы С++
52
~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
t
10 не больше 11, поэтому инструкция cout не выполнит­
u
ся. Конечно же, операнды в инструкции i f не должны всегда быть константами.
111
Они могут быть переменными.
U
В этом случае число
ся в условном выражении. Перечислим ИХ.
Равно
Не равно
>
<
>=
<=
Больше
Меньше
Больше или равно
Меньше или равно
Обратите внимание на то, что для проверки на равенство используется двой­
ной знак "равно".
Теперь рассмотрим программу, в которой демонстрируется использование ин­
струкции Н.
//
Демонстрация
использования
инструкции
if.
#include <iostream>
using namespace stdi
int main ()
int а, Ь,
а
= 2;
Ь
З;
if
(а
Ь)
<
// Эта
if(a
а
"а меньше Ь\п";
инструкция ничего
Ь)
cout «
с =
С;
cout «
cout «
не
"Этого
/ / +-(--
отобразит.
вы не
увидите.\п";
"\п";
-
cout «
Ь;
//
Переменная
"Переменная
о
:J:
В С++ определен полныА набор операторов отношений, которые используют­
!=
:э
с
содержит
с содержит
-1.
-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 работает
кто уже не новичок в программировании, отмечу, что в С+ +
так же, как в языках С# или
формат использования цикла
fоr(инициализация;
Java.
Рассмотрим простейший
for.
условие;
инкременr)
инструкция;
Элемент инициализация обычно представляет собой инструкцию присваи­
вания, которая устанавливает управляющую nере.менную цuкла равной неко­
торому Ifачальному значению. Эта переменная действует в качестве счетчи­
ка, который управляет работой цикла. Элемент условие представляет собой
условное выражение, в котором тестируется значение управляющей пере­
мен ной цикла. По результату этого тестирования определяется, выполнится
цикл
for
еще раз или нет. Элемент инкремент -
это вцражение, которое
определяет, как изменяется значение управляющей переменной цикла после
каждой итерации (т.е. каждого повторения элемента инструкция). Обратите
С++: РУКОВОДСТВО для начинающих
внимание на то, что все эти элементы цикла
запятой. Цикл
for
for
55
должны отделяться точкой с
б~/:tет выполняться до тех пор, пока вычисление элемента
условие дает истинный результат. Как только это условное выражение ста­
нет ложным, цикл завершится, а выполнение программы продолжится с ин­
струкции, следующей за циклом
for.
1:
for демонстрируется в следующей программе.
водит на экран числа от 1 до 100.
11 Программа, иллюстрирующая ЦИКЛ for.
Она вы­
iinclude <iostream>
using hamespace std;
int main ()
{
int count:
for(count=l: count <= 100: count=count+1) //
cout « count « " ":
циxn ~Qr
О;
в этом цикле переменная
count инициализируется значением 1. При каждом
повторении тела цикла проверяется условие цикла.
count <= 100;
Если результат проверки истинен, на экран будет выведено значение переменной
count,
после чего управляющая переменная цикла увеличивается на единицу.
Когда значение переменной
count
превысит число
100,
проверяемое в цикле
условие станет ложным, и цикл остановится.
В ПРофСССИОllальных С++-программах вы не встретите инструкции
count=count+1,
или подобной ей, поскольку в С++ существует специальный оператор инкре­
мента, который выполняет операцию увеличения значения на единицу более эф­
фективно. Оператор инкремента обозначается в виде двух знаков "плюс"
Например, инструкцию
for
(+ +).
из приведенной выше программы с использованием
оператора "++" можно переписать так.
for(count=l; count <= 100; count++) //
cout « count « " ";
25.
m
О
Использование цикла
return
+
u+
ЦИКЛ
for
()
О
Глава 1. Основы С++
56
в остальной части книги для увеличения значения переменной на едиmщу
омы будем использовать оператор инкремента.
В С++ также реализован оператор декремента, который обозначается двумя
знаками "минус"
(--).
Он применяется для уменьшения значения переменной
на единицу.
~ПРОСЫ ДI\<;1 текущего коН!Рол<;1---------1.
Каково назначение инструкции i
2.
Для чего предназначена инструкция
f?
for?
З. Какие операторы отношений реализованы в С++?*
Одним из ключевых элементов С++ является бло" "ода. Блок
-
это логически
связанная группа программных инструкций (т.е. одна или несколько инс-rрук­
ций), которые обрабатываются как единое целое. В С++ программный блок соз­
дается путем размещения последоватеЛЬНОСТIf инструкций между фигурными
(открывающей и закрывающей) скобками, После создания блок кода становится
логической единицей, которую разрешено использовать везде, где можно приме­
нять одну инструкцию. Например, блок кода может составлять тело инструкций
if
или
i f (w
for.
<
Рассмотрим такую инструкцию
h)
v
w
w =
О;
if'.
{
*
h;
Здесь, если значение переменной
w меньше значения переменной h, будут вы­
полнены обе инструкции, заключенные в фИl'урные скобки. Эти две инструк­
ции (вместе с фигурными скобками) представляют блок кода. Они составля­
ют логически неделимую группу: ни одна из этих инструкций не может вы­
полниться без другой. Здесь важно понимап., что если вам нужно логически
1. Инструкция if - это инструкция условного выполнения части кода програмМЫ.
2. Инструкция {or - одна из инструкций цикла в С++, которая предназначена для
организации повторяющеroся выполнения некоторой последовательности ин­
струкций.
3. В С++
реализованы такие операторы отношеннй: =~,
1-, <, >, <-
И
>-.
С++: PYI<OBOACTBO для начинающих
57
связать несколько инструкций, это легко достигается путем создания блока.
Кроме того, с использованием блоков кода многие алгоритмы реализуются
более четко и эффективно.
Рассмотрим программу, в которой блок кода позволяет предотвратить деле­
ние на нуль.
Демонстрация использования блока
//
О
int main ()
double result, n, di
"Введите
делитель:
Здесь инструкция
if (d ! =
cout
";
ni
cout « "Введите
cin » di
//
делимое:
О)
";
управляет
if
целым блоком.
{
« "Звачение
« "\n".i
result = n I di
cout « n « " I "
d
не р_но
«
d
« "
О,
дenеlDlе осущеС'1'llJDIО."
Р".О
"
«
resulti
}
return
О;
Вот как Bblrлядят результаты выполнения этой программы.
Введите
делимое:
Введите
делитель:
Значение
10 / 2
d
не
равно
10
2
равно
О,
деление
21
111
О
1:
U
кода.
#include <iostream>
using namespace stdi
cout «
cin »
+
u+
осуществимо.
5
В этом случае инструкция i f управляет целым блоком кода, а не просто одной
ИliструкциеЙ. Если управляющее условие инструкции
if
истинно (как в нашем
примере), будут выполнены все три инструкции, составляющие блок. Поnробуй-
rлава
58
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«
х
«
" " «
у;
Более того. отдельные элементы любой инструкции можно располагать на от­
дельных строках. Например, следующий вариант записи инструкции абсолютно
приемлем.
С++: РУКОВОДСТВО ДЛЯ начинающих
cout «
«
"Это
а
+
длинная
Ь
+
с
строка.
+ d +
е
Сумма
равна
59
: "
+ 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 « " футов равно" «
counter++;
метры
//
метры
m«
" MeTpa.\n";
Посае Х&8Дой итерации циxnа
/ / ивкрекев'1'Ир"Уем сче~UDC С'1'рок.
//
После каждой
10-й CTPOK~ выводим пустую строку.
if(counter == 10) { //
//
cout «
"\n"; //
Еcnи сче~чик С'1'рок досчитал
до
выводим
10,
8W80ДИК пус~ С'1'року.
пустую строку
С++: руководство ДЛЯ начинающих
counter
О;
61
// обнуляем счетчик строк
+
u+
1i
return
ID
О;
О
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
футов
равно
12
13
14
15
16
17
18
19
20
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
21
22
23
футов
равно
футов
равно
футов
равно
3.35366 метра.
3.65854 метра.
3.96341 метра.
4.26829 метра.
4.57317 метра.
4.87805 метра.
5.18293 метра.
5.4878 метра.
5.79268 метра.
6.09756 метра.
6.40244 метра.
6.70732 метра.
7.0122 метра.
Глава 1. O~HOBЫ С++
62
5.
24
25
26
27
28
29
30
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
31
32
33
34
35
36
37
38
39
40
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов
равно
футов'равно
футов
равно
футов
равно
футов
равно
7.31707
7.62195
7.92683
8.23171
8.53659
8.84146
9.14634
метра.
метра.
метра.
метра.
метра.
метра.
метра.
9.45122 метра.
9.7561 метра.
10.061 метра.
10.3659 метра.
10.6,707 метра.
10.9756 метра.
11.2805 метра.
11.5854 метра.
11.8902 метра.
12.1951 метра.
Самостоятельно измените программу так, чтобы она выводила пустую
строку через каждые
25 строк результатов.
'ШОI
:1111 []ablSlIId" а ф~ЫКI ~ldSl;l
Любая С++-программа составляется из "строительных блоков", именуемых
функциями. И хотя более детально мы будем рассматривать функции в MOДY:le 5,
сделаем здесь краткий обзор понятий и терминов, связанных с этой темой. Функ­
ция
-
это подпрограмма, которая содержит одну или несколько С++-инструк­
ций и выполняет одну или несколько задач.
Каждая функция имеет имя, которое используется для ее вызова. Чтобы вы­
звать функцию, достаточно в исходном коде l1porpaмMbl указать ее имя с парой
круглых скобок. Своим функциям программист может давать любые имена, за
исключением имени mаiл
(), зарезервированного для функции, с которой
начи­
нается выполнение программы. Например, мы назвали функцию именем МуРи­
лс. Тогда для вызова функции MyFunc достаточно записать следующее.
МуFuлс();
При вызове функции ей передается управление программой, в результате чего
начинает выполняться код, составляющий тело функции. По завершении функ­
ции управление передается инициатору ее вы:юва.
С++: РУI<ОВОДСТВО ДЛЯ начинающих
63
функции между открывающей и закрывающей круглыми скобками. Например,
-
если функция MyFunc () принимает один целочисленный аргумент, то, исполь­
о
зуя следующую запись, можно вызвать функцию MyFunc () со значением
)
Функции можно передать одно или несколько значений. Значение, переда­
ваемое функции, называется аргументом. Таким образом, функции в С++ могут
принимать один или несколько аргументов. Аргументы указываются при вызове
2.
MyFunc(2);
Если функция принимает несколько аргументов, они разделяются запятыми.
В этой книге под термином список аргументов понимаются аргументы, разделен­
ные запятыми. Помните, что не все функции ПРИIIИМают аргументы. Если аргу­
менты не нужны, то круглые скобки после имеии функции остаются пустыми.
Функция может возвращать в вызывающий код значение. HelcoтopbIe фуик­
ции не возвращают никакого значения. Значение, возвращаемое функцией. мож­
но присвоить переменной в вызывающем коде, поместив обращение к функции
с праUОl1 стороны от оператора присваивaJШЯ. Например, если бы функция
MyFunc () возвращала значение, мы могли бы вызвать се таким образом.
х
= MyFunc(2);
Эта инструкция работает так. Сначала вызывается ФУIIКЦИЯ MyFunc (). По
ее завершении возвращаемое ею значение ПРИСВa.IfВается персменной х. Вызов
функции можно также использовать в выражении. Рас~мо/'рим пример.
х
= MyFunc(2)
+ 10;
В этом случае значение, возвращаемое функцией, суммируется с числом
1О,
а результат сложения присваивается переменной х. И вообще, если в какой-либо
инструкции встречается имя функции, эта функция автоматически вызывается,
чтобы можно было получить (а затем и использовать) возвращаемое ею значеЮiе.
Итак, вспомним: аргумент
емое функцией значение
-
-
это значение, передаваемое функции. Возвраща­
это данные, передаваемые назад в вызывающий код.
Рассмотрим короткую программу, которая демонстрирует использование функ­
ции. Здесь для отображения абсолютного значения числа используется стандарт­
ная библиотечная (т.е. встроенная) функция abs (). Эта функция принимает один
аргумент, преобразует его D абсолютное значение и возвращает результат.
//
Использование функции
#include <iostream>
#include <cstdlib>
using namespace std;
int main ()
abs().
:+
:+
:U
:а
CD
1:
'J
Глава 1. Основы С++
64
int result;
result
abs(-lO)j //
//
=
//
cout «
return
а
воsвращаекое 8JD sначевие
ПРИС8аивае~ск перемевной
result.
result;
О;
Здесь функции
abs () ,
Вызыаае~ск фунхциа аЬа(),
abs ()
в качестве аргумента передается число
-10.
Фушщия
приняв при вызове аргумент, возвращает
лученное значение присваивается переменной
бражается число
ero абсолютное значение. По­
resul t. Поэтому на экране ото­
10.
Обратите также ВtIИмание на то, что рассматриваемая программа включает за­
ГОЛОВОК
<cstdlib>. ЭТОТ заголовок необходим для обеспечения ВОЗМОЖНОСТИ
( ) . Каждый раз, когда вы используете библиотечную функ­
вызова функции аЬз
цию, в проrpамму необходимо включать соответствующий заголовок.
В общем случае в своих программах вы будеl е использовать функции двух ти пов.
К первому типу отнесем фушщии, нarшсанные программистом (т.е. вами), и в каче­
стве примера такой функции можно на.1вать функцию та i
n ( ) . как вы узнаете ниже,
реальные С ++-программы содержат множество пользовательских функций.
Функции второго типа предоставляются компилятором. К этой категории Функ­
ций принадлежит функция аЬs
( ) . ОбbIЧНо программы состоят как из функций, на­
писанных программистами, так и из функций, предоставленных компилятором.
При обозначении функций в тексте этоii КНИЛi используется соглашение
(обычно соблюдаемое в литературе, посвященной языку программирования
С++), согласно которому имя функции завершается парой круглых скобок. На­
пример, если функция имеет имя
ся как
getval ().
getval, то
ее упоминание в тексте обозначит­
Соблюдение этого соглашения позволит легко отличать имена
переменных от имен функций.
Библиотеки С++
как упоминалось выше, функция аЬs
()
не является частью языка С++. но ее
"знает" каждый С++-компилятор. Эта функция, как и множестводругих, входит
в состав стандартной 6u6лuотeICU. В примерах этой книги мы подробно рассмо­
трим использование многих библиотечных функций С++.
С++: руководство для начинающих
65
в С++ определен довольно большой набор функций, которые содержатся в
стандартной библиотеке. 3ти функции предназначены для выполнения часто
встречающихся
задач,
включая
операции
ввода-вывода,
математические
вы­
числения и обработку строк. При использовании программистом библиотечной
функции компилятор автоматически связывает объектный код этой функции с
объектным кодом программы.
Поскольку стандартная библиотека С++ довольно велика, в ней можно найти
много полезных ФУНКЦIIЙ, которыми действительно часто пользуются програм­
мисты. Библиотечные функции можно при менять подобно строительным бло­
кам, из которых возводится здание. Чтобы не "изобретать велосипед", ознакомь­
тесь с документацией на библиотеку используемого вами компилятора. Если вы
сами напишете функцию, которая будет "переходить" с вами из программы в про­
грамму, ее также можно поместить в библиотеку.
Помимо библиотеки функций, каждый С++-компилятор также содержит би­
блиотеку классов, которая является объеКТllо-ориеt}ТИРОВанной библиотекой.
Но, прежде чем мы сможем использовать библиотеку классов, нам нужно позна­
комиться с классами и объектами.
~просы для теl<ущего I<ОНТрОЛЯ
--------
1.
Что такое функция?
2.
Функция вызывается с помощью ее имени. Верно ли это?
3. Что понимается под стандартной библиотекой функций С++?·
ВАЖНОI
"'1 КАючевь'е САОва С++
В стандарте С++ определено
63
ключевых слова. Они показаны в табл.
1.1.
Эти ключевые слова (в сочетании с синтаксисом операторов и разделителей)
образуют определение языка С++. В ранних версиях С++ определено ключевое
слово
overload,
но теперь оно устарело. Следует иметь в виду, что в С++ раз­
личается строчное и прописное написание букв. Ключевые слова не являются ис­
ключением, Т.е. все они должны быть написаны строчными буквами.
1. Функция -
это подпрограмма, которая содержит одну или несколько С++­
инcrpукциЙ.
2. Верно. Чтобы вызвать функцию, дocrnточно В исходном коде программы указать ее имя.
З. Стандартная библиотека фyнюntЙ С++ - это коллекция функций, поддерживаемая
всеми С + +-компиляторами.
u+
~:J:
U
О
Глава 1. Основы С++
66
Таблица
1.1. Ключевые слова С++
азm
case
const
delete
else
extern
friend
int
new
public
short
st'atic cast
this,
typedef
unsigned
volatile
•
Ьооl
auto
catch
const class
do
enum
false
goto
long
operator
register
signed
struct
throw
typeid
using
wchar t
char
contint:e
double
explici t
float
if
mutable,
private
reinterpret_cast
sizeof
switch
true
typenanle
virtuaJ.
while
break
class
default
dinamic cast.
export
for
inline
namespace
protected
return
static
template
try
union
void
Идентификаторы
в С++ идентификатор представляет собой имя, которое присваивается функ­
ции, переменной или иному элементу, 'определенному пользователем. 'Иденти­
фикаторы могут состоять из одного или нескольких символов. Имена перемен­
ных должны начинаться с буквы или символа подчеркивания. Последующим
символом может быть буква, цифра и символ подчеркивания. Символ подчер­
кивания можно использовать для улучшения читабельноети имени перемеююй,
IJ;lIlример
line_count.
В С++ прописные и строчные буквы воспринимаются
t-.:ш, различные символы, Т.е.
myvar
И
MyVar -
это разные имена. В С++ нельзя
использовать в качестве идентификаторов ключевые слова, а также имена стан­
дартных функций (например,
abs).
Запрещено также использовать в качестве
пользовательских имен встроенные идентификаторы (например,
cout).
Вот несколько примеров допустимых идентификаторов.
Test
х
у2
ир
_top
my_var
Maxlncr
simplelnterest23
Помните, что идентификатор не должен начинаться с цифры. Так, 980К­
недопустимый идентификатор. Конечно, вы вольны называть переменные и
другие программные элементы по своему усмотрению, но обычно идентифика­
тор отражает назначение или смысловую характеристику элемента, которому
он принадлежит.
С++: PYI<OBOACTBO ДЛЯ начинающих
~ПРОСЫ мя теl<ущего контром
1.
u+
Какой из следующих вариантов представляет собой ключевое слово: f о r,
~
или
FOR?
Символы какого типа может содержать С++-идентификатор?
Слова
катор?
..
index21
и
Index21
представляют собой один и тот же идентифи­
ТеСТ,ДАЯ СQМОКQНТрООЯ по МОАJlt'Ю 1
•
{.
Выше отмечалось, что С++ занимает центральное место в области совре­
менного программирования. Объясните это утверждение.
2.
C++-КОМПИJlЯтор генерирует объектный код, который непосредственно ис­
пользуется компилятором. Верно ли это?
З. Каковы три основных принципа объектно-ориентированного программирования?
4.
С чего начинается выполнение С++-программы?
5.
6.
Что такое заголовок?
Что такое
<iostream>? Для чего служит следующий код?
#include <iostream>
7.
Что такое пространство имен?
8.
9.
Что такое переменная?
какое (какие) из следующих имен переменных недопустимо (недопустимы)?
А.
1.
+
--------
For
2.
3.
67
count
В.
count
С.
count27
D_
67count
Е.
if
Ключевым словом эдесь является вариант (от. В С++ все ключевые слова ПШlIутся
С ИСПОЛЪЗ0ванием СТРОЧНЫХ букв.
2. С++-идентификатор может содержать буквы, цифры И символ
подчеркивания.
З. Нет, в С++ прописные и строчные буквы воспринимаются как раэличные симвоJIы.
J!
:I:
8
68
10.
Глава 1. Основы С++
как создать однострочный комментарий? Как создать мноrocтpoчный ком­
ментарий?
11.
Представьте общий формат инструкции if. Представьте общий формат
инструкции
12.
13.
for.
Как создать блок кода?
Гравитация Луны составляет около
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.
2.11.
Типы данных С++
Литермы
Создание инициализированных переменных
Арифметические операторы
Операторы отношений и логические операторы
Оператор присваивания
Составные операТОрЫ присваивания
ПреобразОБание типов в операторах присваивания
Преобразование типов в выражениях
Приведеllие ТИПОlJ
Использование пробелов и круглых скобок
70
Модуль 2. Типы данных и операторы
Центральное место в описании языка ПРОГРJ.ммирования занимают типы дан­
ных и операторы. Эти элементы определяют ограничсния языка и виды задач, к
которым его можно примснить. Нетрудно предположить, что язык С++ поддер­
живает богатый ассортимент как типов данных, так и опсраторов, что позволяет
его использовать ДЛЯ решения широкого круга задач программирования.
Типы данных и операторы
-
это большая тема, которую мы начнем
pacc:vla-
тривать с основных типов данных и наиболее часто используемых операторов. В
этом модуле мы также обсудим подробнее такие элементы языка, как перемен­
ные и выражения.
Почему ТИПbl данных столь ваЖНbI
Тип переменных определяет операции, которые разрешены для выполнения
над ними, и диапазон значений, которые могут быть сохранены с их помощью.
В С++ определеио несколько типов данных, 11 осе они обладают уникальными
характеристиками. Поэтому все переменные ДОЛЖНЫ быть определены до их ис­
пользования, а объявление переменной должно включать спецификатор типа.
Без этой информации компилятор не сможет сгеuерировать корректный код.
В С++ не существует понятия "переменной ОС-\ типа".
Типы данных важНЫ для С++-программирования еще и потому, что несколько
базовых типов тесно связанЫ со "строительными блоками", которыми оперирует
компьютер: байтами и словами. Другими словами, С++ позволяет программисту
задействовать те же самые ТИПЫ данных, которые использует сам централl>НЫЙ
процессор. Именно поэтому с помощью С++ можно писать очень эффективные
\
программы системного уровня.
Язык С++ поддерживает встроенные типы данных, которые позволяют ис­
пользовать в программах целые числа, СИМВОJlЫ, значения с плавающей точкой
и булевы (логические) значения. Именно использование КОНКРетных типов дан­
ных позволяет сохранять и обрабатывать в программах различные виды инфор­
мации. как будет показано ниже D этой книге, С++ предоставляет программисту
возможность самому создавать типы данных, а не только использовать встроен­
ные. Вы научитесь создавать такие типы данных, как классы, структуры и перс­
числения, 110 при этом помните, что все (даже очень сложные) новые типы дан­
ных состоят из встроенных.
С++: РУI<ОВОДСТВО ДNI начинающих
71
В С++ определено семь основных типов данных. Их названия и ключевые
слова, которые используются для объявления переменных этих типов, приведе­
ны в следующей таблице.
Тип
Название
char
wchar t
int
float
double
bool
void
Символьный
Символьный двубайтовый
целочисленный
С плавающей ТОЧКОЙ
С плавающей точкой двойной точности
Лоrnческий (или булев)
Без значения
В С++ перед такими типами данных, как
char, int
и
double,
разрешается
использовать .модифU1Шmоры. Модификатор служит для изменения значения ба­
зовоro типа, чтобы он более точно соответствовал конкретной ситуации. Пере­
чиелим возможные модификаторы типов.
signed
unsigned
long
short
Модификаторы
signed, unsigned, long
и
short
можно примеitять к це­
лочисленным базовым типам. Кроме того, модификаторы
signed и unsigned
char, а модификатор long - с типом double. Все
допустимые комбинации базовых типов и модификаторов приведены в табл. 2.1.
можно использовать с типом
В этой таблице также указаны гарантированные минимальные диапазоны пред­
ставления для каждоro типа, соответствующие С++-стандарту
Минимальные диапазоны, представленные в табл.
2.1,
ANSI/ISO.
важно понимать именно
как .мUНUМШlЫtыe дuanаЭ()7fЫ и никак иначе. Дело в том, что С++-компилятор может
расширить один или несколько из этих минимумов (что и делается в большинстве
случаев). Это означает, что диапазоны представления С++-типов данных зависят от
конкретной реализации. Например, ДЛЯ компьютеров, которые используют арифме­
тику дополнительных кодов (т.е. почти все COBpeMeННhle компьютеры), целочислен­
НЫЙ тип будет иметь диапазоli представлеlШЯ чисел от
всех случаях диапазон представления типа
short
-32 768 до 32 767. Однако во
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
unsigned char
signed char
int
unsigned int
signed int
short int
unsigned short int
signed short int
long int
signed long int
unsigned long int
float
double
long double
8
8
8
32
32
32
16
16
16
32
32
32
-128-127
0-255
-128-127
-2 147483648-2 147483647
0-4 294 967 295
З2
1,8Е-З8-ЗАЕ +З8
64
64
2,2Е-308-1,8Е+308
16
0-65535
Ьооl
Аналогичен типу
int
-32 768-32 767
0-65535
-32 768-32 767
int
signed int
АналОПfчен типу unsigned int
Аналогичен типу
Аналогичен типу
2,2Е-308-1,8Е+308
ИСТИНА или ЛОЖЬ
w char t
Разли~ие между целочисленными значениями со знаком и без него заключа­
ется в иЙтерпретации старшего разряда. Если задано целочисленное значение
со знаком, С++-компилятор сгенерирует КОД с учетом того, что старший разряд
значения используется в качестве флazа знака. Если флаг знака равен О, число
считается положительным, а если он равен
1, -
отрицательным. Отрицатель­
ные числа почти всегда представляются в дополнительном коде. Для получения
дополнительного кода все разряды числа берутся 8 обратном коде, а затем по­
лученный результат увеличивается на единицу. Наконец, флаг знака устанав­
ливается равным
1.
Целочисленные значения со знаком используются во многих алгоритмах, но
максимальное число, которое можно представить со знаком, составляет только
74 Модуль 2. Типы данных и операторы
................................................................................................................................
половину от максимального числа, которое можно представить без знака. Рас­
смотрим, например, максимально возможное 16-разрядное целое число
(32 767):
0111111111111111
Если бы старший разряд этого значения со знаком был установлен равным
оно бы интерпретировалось как
его как
unsigned
-1
(в дополнительном коде). Но если объявить
iпt-значение, то после установки его старшего разряда в
получили бы число 65
1. то
1 мы
535.
Чтобы понять различие в С++-интерпрет.щии целочисленныx значений со
знаком и без него, выполним следующую короткую программу.
#include <iostream>
/*
Эта
про грамма демонстрирует
signed-
и
различие между
uпsigпеd-значениями
целочисленного
типа.
*/
using namespace std;
int main ()
{
short int i; // короткое
short unsigned int j; //
j
=
60000; //
Число
iпt-значение
короткое
попадает
60000
со
знаком
iпt-значение без знака
в
диапазон
// представления типа short unsigned int, но
// не попадает в диапазон представления типа
// short signed int.
i
j;
cout «
return
i «
//
Поэтому
//
//
переменной
как
" " «
после
i
присваивания
оно
числа
60000
будет интерпретироваться
отрицательное.
j;
О;
При выполнении программа выведет два числа:
-5536 60000
Дело в том, что битовая комбинация, которая представляет число 60000 как короткое
(short) целочисленное значение без знака, интерпретируется в качестве короткого
iпt-значения со знаком как число -5536 (при 16-разрядном представлении).
С++: РУКОВОДСТВО ДЛЯ начинающих
в С++ предусмотрен сокращенный способ объявления
75
unsigned-, short-
и
lопg-значений целочисленноro типа. Это значит, что при объявлении iпt-зна­
чений достаточно использовать слова
int,
Т.е. тип
int
unsigned, short
и
long, не указывая ~ип
подразумевается. Например, следующие две инструкции объ­
являют целочисленные переменные без знака.
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сп­
символов, но и для хранения числовых значений. Переменные типа
держать "небольшие" целые числа в диапазоне
пользовать вместо
i nt
-128-127 и
char могут со­
поэтому их можно ис­
-переменtlых, если вас устраивает такой диапазон представ­
ления чисел. Например, в следующей программе сhаr-переменная используется
для управления циклом, который выводит на экран алфавит английского языка.
,//
Эта про грамма
выводит алфавит.
tinclude <iostream>
76
МОДУЛЬ 2. Типы данных и операторы
using
паmезрасе
std;
int main ()
{
char letter; //
//
Перекеква_
letter
~a
char
for.
иcnоnьзуетса дn_ упрaanекия цихпок
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
для представления не­
больших целых чисел?·
char.wchar_t. in1:. float, double.bool
1.
Семьосновиыхтипов:
и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-раэрядных средах ДЛЯ хранения значений типа
ляться
16 бит, а
в 32-разрядиых
- 32.
i nt
должно выде­
При этом наименьший допустимый
размер для целочисленных значений в любой среде должен составлять
16
бит. Позтому, есЛи вы будете Ш1Сать программы, не "заступая" за пределы
минимальных диапазонов, то они (программы) будут переносимы в дру­
гие среды. Одно замечание: каждый С++-компнлятор указывает диапазон
базовых типов в заголовке
<cl imi t S>.
Типы данных с плавающей точкой
к переменным типа flоа t JI
double
обращаются либо для обработки чисел с
дробной частью, либо при необходимости выполнения операций над очень боль­
шими или очень малыми числами. ТИIIЫ
float
и
double различаются значением
наибольшего ( и наименьшего) числа, которые можно хранить с помощью пере­
менных этих типов. Обычно тип
double
в С++ позволяет хранить число, при­
близительно в десять раз превышающее значение типа float.
Чаще всего в профессиона.льных программах используется тип
IJ
double. Дело
том, что большинство математических функций из С++-библиотеки исполь­
зуют dоublе-значения. Например, функция
sqrt ()
возвращает dоublе-зна­
чение, которое равно квадратному корню из ее double-аргумента. Для примера
рассмотрим программу, в которой функция
sqrt ()
используется для вычисле­
ния длины гипотенузы по заданным длинам двух других сторон треугольника.
/*
Здесь
используется
длины
гипотенузы
сторон
*/
теорема
по
треугольника.
Пифагора
заданным
длинам
для
двух
вычисления
других
78 МОДУЛЬ 2. Типы данных и операторы
.................................................................................................................................
finclude <iostream>
#include <cmath>
//
Это~ saroловох аеобходим дn_
/ /
8101S08. ФУRJC.ЦИИ
8qrt () .
//
//
ФУВXЦИR
RВnRe~c_ час~
using namespace std;
int main () (
double х, у, z;
х
= 5;
У
4;
z
=
sqrt(x*x +
cout «
return
у*у);
"Гипотенуза
8qrt()
ма~ека~есхой С++-бибnио~ехи.
равна
" «
z;
О;
Вот как выглядят результаты выполнения згой программы.
Гипотенуза
равна
6.40312
Необходимо отметить, что поскольку ФунюпiЯ
sqrt () является частью стан­
дартной С++-библиотеки функций, то для ее вызова в программу нужно вкJlю­
читьзаroловок
Тип
<cmath>.
long double позволяет
работать с O~JeHb большими или очень малень­
кими числами. Он используется в программах научно-исследовательского харак­
тера, например, при анализе астрономических данных.
Тип данных Ьооl
Тип
bool
(относительно недавнее дополнение к С++) предназначен для хра­
нения булевых (т.е. ИСТИНNЛОЖЬ) значений. В С++ определеliЫ две бу.lевы
константы:
true
и
false,
являющиеся единственными значениями, которые
могут иметь переменные типа
bool.
Важно понимать, как значения ИСТИНNЛОЖЬ определяются в С++. Один
из фундаментальных принципов С++ состоит в том, что любое ненулевое значе­
ние интерпретируется как ИСТИНА, а нуль
ностью согласуется с типом данных
bool,
-
как ЛОЖЬ. Этот принцип пол­
поскольку любое ненулевое значение,
используемое в булевом выражении, автоматически преобразуется в значение
true, а нуль -
в значение
false.
Обратное утверждение также справедливо: при
С++: руководство для начинающих'
использовании в небулевом выражении значение
а значение
fa1se -
true
79
преобразуется в число
1,
в число О. Как будет показано в модуле З, конвертируемость
нулевых и ненулевы.х значений в их булевы эквиваленты особенно важна при ис­
пользовании инструкций управления.
Использование типа
//
bool демонстрируется
в следующей программе.
демонстрация использования boo1-значениЙ.
tinc1ude <iostream>
using nаmеэрасе std;
int main ()
Ьоо1
Ь;
= fa1se;
cout«
"Значение
ь
= true;
cout«
переменной Ь равно"
«
Ь
«
"\n";
"Значение переменной Ь равно"
«
Ь
«
"\п";
ь
// Одно Ьооl-sиачевие мо.ет упрaanкт.
if(b) cout«
"Это ВЫПОЛНИМО.\n";
fa1se;
if(b) cout«
ь
if-ивструхциеЙ.
=
"Это не выполнимо.\n";
// Результатом применения оператора отношения
// являеТСR true- или fа1sе-значение.
cout«
"10 > 9 равно" « (10 > 9) « "\п";
return
О;
Результаты выполнения этой программы таковы.
Значение
переменной Ь равно
О
Значение
переменной Ь равно
1
Это
выполнимо.
10 > 9
равно
1
В этой программе необходимо отметить три важных момента. Во-первых, как
вы могли убедиться, при выводе на экран Ьоо1-значения отображается число О
МОДУЛЬ 2. Типы данных и оперспоры
80
или
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.
2.
Каковы основные различия между типами
-
float
Какие значения может иметь переменная типа
чение преобраэуется нуль?
_______
_
и
double?
bool?
В какое булево зна­
.
................------------------_..------------------------..
3. Что такое void?·
1.
Основное различие между типами п0а t и
double заключается в величине значе­
НИЙ, которые они позволяют Xpaнm1>.
2.
Переменные типа Ьооl могут иметь значения tгue либо
ется в значение
3. void -
false.
Нуль преобразу­
false.
это ТШI данных, характеризующий ситуацию отсутствия значения.
С++: руководство ДЛ~ начинающих
Проеl<Т
-
81
2.1.
. - --1
Mars.cpp ,
В положении, когда Марс tlаходится к Земле ближе всего, рассто-
яние между ними составляет приблизительно
34000000 миль.
Предположим, что на Марсе есть некто, с кем вы хотели бы ПОI'()ворить. Тогда
возникает вопрос, какова будет задержка во времени между моментом отправ­
ки радиосигнала с Земли и моментом его прихода на Марс. Цель следующеro
проскта
-
создать программу, которая отвечает на этот вопрос. Для этоro вспом­
ним, что радиосигнал распространяется со скоростью света, т.е. приблизительно
186000 миль
в секунду. Таким образом, чтобы вычислить эту задержку, необхо­
димо расстояние между планетами разделить на скорость света. Полученный ре­
зультат следует отобразить в секундах и минутах.
Последовательность действий
1.
2.
Создайте новый файл с именем
Mars. срр.
Для вычисления змержки необходимо использовать значения с плавающей
. точкой.
Почему? Да просто потому, что искомый временной интервал будет
иметь дробную часть. Вот переМСliные, которые используются в программе.
double
double
double
double
3.
distancei
lightspeed;
delaYi
delay_in_min;
Присвоим перемеНIlЫМ
distance
и
lightspeed
начальные значения.
distance = 34000000.0; // 34 000 000 миль
ligl1tspeed = 18'6000.0; // 186 000 миль в секунду
4.
Для получения задержки во времени
(8 секундах) разделим значение
u
пере­
менtlОЙ di stалсе на значение
ное переменной
delay
cout«
=
1 igh tspeed. Присвоим вычисленное част­
delay и отобразим результат.
distance /
"Временная
lightspeedi
задержка
при
разговоре
с
Марсом:
"
«
delay «
5.
Для получения задержки
на
60
" секунд.\п";
n минутах разделим значение переменной delay
и отобразим результат.
delay_in_min
=
delay / 60.0;
1
1.
МОДУЛЬ 2. Типы данных и операторы
82
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
I
=
distance 1 lightspeed;
cout«
"Временная
задержка
ери разговоре
delay «
секунд.\п";
с
Марсом:
n
«
cout«
return
7.
"
"Это составляет"
«
delay_in_min «
"
минут.";
О;
Скомпилируйте и выполните программу. Вы должны получить такой ре­
зультат.
Временная задержка
при разговоре
с Марсом:
182.796
ceKYH.~.
Это
8.
составляет
3.04659
минут.
Самостоятельно отобразите временную задержку, которая будет иметь ме­
сто при ожидании ответа с Марса.
С++: руководство ДЛ~ начинающих
ЛитерШlЫ
83
- это фиксированные (предназначе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
long int
unsigned int
unsigned long
float
double
long double
1,123,21000,-234
35000L, -34L
10000U, 987U, 40000U
12323UL,900000UL
123.23F,4.34e-3F
23.23,123123.33,-0.9876324
1001.2L
Шестнадцатеричные и восьмеричные литералы
Иногда удобно вместо десятичной системы счисления использовать восьме­
ричную или шестнадцатеричную. В вось.мерuч//оЙ системе основанием служит
число
8,
а для выражения всех чисел используются цифры от О до
ричной системе число
1О
имеет то же значеНИI~. что число
стема счисления по основанию
16 называется
8
7.
В восьме­
в десятичной. Си­
шеcmнадu,aтеpuчной и использует
9 плюс буквы от А до F. означающие шестнадцатеричные "цифРhl"
10, 11, 12, 13, 14 и 15. Например, шестнадцатеричное число 10 равно числу 16
цифры от О до
в десятичной системе. Поскольку эти две системы счисления (шестнадцатерич­
ная и восьмеричная) используются в программах довольно часто, в языке C~
разрешено при желании задавать целочислеННI,rс литералы не в десятичной, а в
шестнадцатеричной или восьмеричной системе. Шестнадцатеричный литерал
должен начинаться с префикса Ох (нуль и буква х) или ох, а восьмеричный­
с нуля. Приведем два примера.
int hex
int oct
=
OxFFi 11 255
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
обходимо использовать префикс L. ВОТ пример.
w char wc;
~
-
char, не.
= 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 - это сама шестнадцатеричная константа)
\а
Модуль 2. Типы данных и операторы
86
ИСПО.1JЬэоваиие управляющих последовательностей демонстрируется на при­
мере следующей программы.
//
//
Демонстрация использования управляющих
последовательностей.
Jinclude <iostrearn>
using narnespace std;
int rnain ()
{
,
cout «
cout «
"one\ttwo\tthree\n";
"12З\Ь\Ь45"; // Последовательность \Ь\Ь
// обеспечит возврат на две позиции
// назад. Поэтому цифры 2 и 3 будут
// "стерты".
return
О;
Этапрограмма генерирует такие результаты.
two
опе
three
145
Здесь в первой соut-инструкции для позиционирования слов
ree"
"two" и "th-
используется символ горизонтальной табуляции. Вторая соut-инструк­
ция сначала отображает цифры
123,
затем после вывода двух управляющих
символов возврата на одну позицию цифры
дятся цифры
4
и
2
и
3
удаляются. Наконец, выво­
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
100L. ДЛЯ того чтобы константа
1 О О U.
int, необходимо записать
1 О() имела тип данных unsigned int, необ­
ходимо записать
З.
Запись \Ь представляет собой управляющую последовательность, которая означа­
ет возврат на одну позицию.
Модуль 2. Типы данных и операторы
88
и присваиваемое начальное значение. Общий формат инициализацuи nepeJll(!HHoU
имеет следующий вид:
тил имя_леременной =
значение;
Вот несколько примеров.
int count = 10; //
1/
//
//
//
char ch = 'х' ;
float f = 1.2F
Присваиваем
значение
переменной
начальное
ch
буквой х.
10.
Инициализируем
Переменная
значением
count
f
nE!ременную
инициализируется
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 «
return
"Объем цилиндра равен
О;
" «
volume;
С++: руководство ДЛ'il начинающих
89
Здесь объявляются три локальные переменные radius, height и volume.
Первые две
lume) -
(radius
и
height)
инициализируются константами, а третья
(vo-
результатом выражения, вычисляемым во время ВЫПОЛllения програм­
мы. Здесь важно то, что выражение инициализации может использовать любой
элемент, действительный на момент инициализации,
-
обращение к функции,
другие переменные или литералы.
Операторы
в С++ определен широкий набор операторов. Оператор
(operator) -
это
символ, который указывает компилятору на выполнение конкретных матема­
тических действий или логических манипуляций. В С++ имеется четыре общих
класса операторов: арифметические, поразрядные, логuческие и операторы отно­
шений. Помимо них определены другие операторы специального назначения. В
этом модуле мы уделим внимание арифметическим. логическими и операторам
отношений, а также оператору присваивания. Поразрядные и операторы специ­
ального назначения рассматриваются ниже в этой книге.
;В Арифмехические~ПQРnТОРЫ
в С++ определены следующие арифметические операторы.
Оператор
Действие
+
Сложение
-------_._-----_._----
Вычитание, а также унарный минуо
*
Умножение
/
Деление
%
деление по модулю
Декремент
++
Инкремент
Действие операторов +, -, ~ И / совпадает с действием аналогичных опера­
торов в алгебре. Их можно при менять к данным любого встроенного числового
типа, а также типа
char.
После применения оператора деления
(/) к целому числу остаток будет отбро­
шен. Например, результат целочислеиного деления 10/3 будет равен 3. Остаток
от деления можно получить с помощью оператора деления по .модулю (%). На­
пример, 10 % 3 равно 1. Это означает, что в С++ оператор "%" нельзя при менять к
МОДУЛЬ 2. Типы данных и операторы
90
типам с плавающей точкой
(float
или
double). Деление по модулю применимо
только к операндам целочисленного типа.
Использование оператора деления по модулю демонстрируется в следующей
программе.
Демонстрация использования операrора деления по модулю.
//
#include <iostrearn>
using narnespace std;
int rnain ()
(
int
х
У
х,
У;
=,10;
= 3;
cout «
"
х
с
= 1;
У = 2;
cout «
«
" / " «
остатком" «
«
у
х
%
"
равно
у
«
"
"
равно
" «
х
/
у
«
х
/
у
«
х
%
У;
"\п";
х
х
х
return
«
«
" / " «
" % " «
у
у
«
«
равно
" «
" «
"\п"
«
О;
Результаты выполнения этой программы таковы.
10 / з равно 3
1 / 2 равно О
1 % 2 равно 1
с остатком
1
Инкремент и декремент
Операторы инкремента
модуле
(++) и декремента (--). которые уже упоминались в
1. обладают очень интересными свойствами. Поэтому им следует уделить
особое внимание.
Вспомним: оператор инкремента выполняет сложение опер~нда с числом
оператор декремеftrа вычитает
х
=
х
+ 1;
1 из своего операнда.
1. а
Это значит. что инструкция
С++: руководство дI\'iI начинающих
91
аналогична такой инструкции:
++Х;
:а
а.
о
А инструкция
6
х=х...,/l;
а.
ф
с:
аналогична такой инструкции:
о
:s:
х
--Х;
:а
Операторы инкремента и декремента могут стоять как перед своим операн­
дом (nрефU1€СНая форма), так и после него (постфиксная форма). Например, ин­
=
Х
+ 1;
// Префиксная форма оператора инкремента.
или постфиксной формы:
Х++;
//
Постфиксная
форма
оператора инкремента.
В предыдущем примере не имело значения, в какой форме был применен опе­
ратор инкремента: префиксной или постфиксной. Но если оператор инкремента
или декремента используется как часть большего выражения, то форма его при­
менения очень важна. Если такой оператор применен в префиксной форме, то
С++ сначала выполнит эту операцию, чтобы операнд получил новое значение,
которое затем будет использовано остальной частью выражения. Если же опера­
тор применен в постфиксной форме, то С# использует в выражении его старое
значение, а затем выполнит операцию, в результате которой операнд обретет но­
вое значение. Рассмотрим следующий фрагмент кода:
Х
= 10;
У
=
++Х;
В этом случае переменная у будет установлена равной
11.
Но если в этом коде
прсфиксную форму записи заменить постфиксной, переменная у будет установ­
лена равuой
Х
У
J5
~
можно переllИсать в виде префиксной
++Х;
~
с:
струlЩИЮ
Х
1:
1:
10:
= 10;
= Х++;
В обоих случаях переменнэя Х получит значение
в какой момент она станет равной
11
11.
Разница состоит лишь в том,
(до присвоения ее значения перемеиной у
или после). Для программиста очень важно иметь возможность управлять време­
нем выполнения операции инкремента или декремеlПа.
МОДУЛЬ 2. Типы данных и операторы
92
Арифметические операторы ПОДЧИНЯЮТСЯ следующему порядку выполнения
действий.
Приоритет
Операторы
Наивысший
++ -- (унарный минус)
Низший
+ -
* /
%
Операторы одного уровня старшинства вычисляются компилятором
CJJeBa
направо. БезуCJJОВНО, ДЛЯ изменения порядка ВЫЧИCJJений можно использовать
круглые скобки, которые обрабатываются D С1-+ так же, как практичеСЮi во всех
других языках программирования. Операции IIЛИ набор операций, заключенных
в круглые скобки, приобретают более высокий приоритет по сравнению с други­
ми операциями выражения.
Спросим у опытного программиста
Вопрос. Не сtlRзан ли оператор инкремен",й
Ответ.
"++" с uмelleм ЯЗ6'ка
С++?
Даl как вы знаете, С++ построен на фу.даменте языка С, к которому добав­
лено множество усовершенствований, tiольшинство из которых предна;ша­
чено ДЛЯ поддержки объектно-ориеНТlfJЮВС1ННОro Iтрограммирования. Та-'
ким образом, С++ представляет собой '''''''крем.ентное усовершенствовани~
языка С, а результат добавления симвслов
"++"
(оператора инкремеНТd) к
имени С оказался вполне подходящим liМeHeM для нового языка.
Бьерн Страусгрyn сначала назвал свой язык иС С классами" (С
es), но позже он изменил это название на С++.
with Class-
и хотя успех нового языка
еще только преДIlОЛaraлся, принятие нового названия (С++) практически
гарантировало ему видное место в истории, поскольку это имя было узна­
ваемым для каждого С-программиста_
и логические операторы
Операторы отношений и логические (булевы) операторы, которые часто
идут ирука
06
руку", ИСПОЛЬЗУЮТСЯ для получения результатов в виде значений
ИСТИНА/ЛОЖЬ. Операторы отношений ОU,енивают по "двухбалльной систе­
ме" отношения Между ДВУМЯ значениями, а лоrnческие определяют различные
С++: РУКОВОДСТВО ДЛЯ начинающих
93
способы сочетания истинных и ложных значеtlИЙ. Поскольку операторы отно­
шений генерируют ИСТИНА/ЛОЖЬ-результаты, то они часто выполняются с
логическими операторами. Поэтому мы и рассматриваем их в одном разделе.
Операторы отношений и логические (булевы) операторы перечислены в
табл.
2.4. Обратите внимание на то, что в языке С++ в качестве оператора отноше­
=", а для оператора "равно" - двойной сим­
вол равенства (==). Согласно стандарту С++ результат выполнения операторов от­
ношений и логических операторов имеет тип bool, Т.е. при выполнении операций
отношений и логических операций получаются значения true или false.
ния "не равно" используется символ"!
-tc
Назаметку
n. . .
При использовании более старых компиляторов результаты выполнения этих операций имели тип
int (О или 1). Это различие в
интерпретации значений имеет в основном теоретическую осно­
ву, поскольку С ++,
разует значение
Kal< упоминал ось выше, автоматически преоб­
true в 1, а значение false - в О, и наоборот.
Операнды, участвующие в операциях "выяснения" отношений, могут иметь
практически любой тип, главное, чтобы их можно было сравнивать. Что касает­
ся логических операторов, то их операнды должны иметь тип
bool,
и результат
логической операции всегда будет иметь тип
uулевое чuсло оценивается как истишюе
bool. Поскольку в С++ любое не­
(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
if(i
if(i
if(i
if(i
if{i
< j) cout « "i < j\n";
<= j) cout « "i <= j\n":
!= j) cout « "i != j\n";
j) cout « "Это не вьmолняется!\п";
>= j) cout « "Это не выполняется!\п":
> j) cout « "Это не выполняется!\п":
= true:
Ь2 = false:
if(bl && Ь2) cout « "Это не выполняется!\п":
if(! (Ы && Ь2» cout «
" ! (Ы & & Ь2) равно значению ИСТИНА \п" ;
if (Ы I I Ь2) cout « "Ы I I Ь2 равно значению ИСТИНА \п";
Ы
return О:
При выполнении эта программа генерируе1 такие результаты.
i
i
< j
<= j
С++: PYI(OBOACтaO д.л~ начинающих
95
.................................................................................................................................
i != j
! (Ы && Ь2) равно значению ИСТИНА
Ы 1 1 Ь2 равно значению ИСТИНА
л
а.
Как операторы оmошений, так и логические операторы имеют более низкий
приоритет по сравнению с арифметическими операторами. Это означает, что та­
кое выражение, как
сано
n виде
10> 1+12, будет вычислено так, как ссли бы оно было запи­
10> (1+12). Результат этого выражения, КОJit.'ЧlJO же, равен значению
ложь.
С помощью логических операторов можно объединить в одном выражении
любое количество операций отношений. Например, в этом выражении объеДИllе­
но сразу три такие операции.
var > 15 11
! (10 < count)
о
>Q
at
[:
о
s
х
А
:r::
:r::
Q
~
А
:~
:""'
<= item
&& 3
Приоритет операторов отношений и логических операторов I1Ul,азан в следу­
ющей таблице.
-.-_._--_._----','-_._---------,
- - - - - -.. ---,-- ----=---
[7риор'-'-и~rе'_'_r_ ___=О'_'_п__=еLР__=а::..:...ТО.::.JрL:.Cы"-'---_ _ _ _ _ _ _ _ . _
________________ . __
Наивысший
> >= < <=
!=
&&
Низший
"
Проект
"исключающее ИЛИ"
...... ._",
•
2.2.
Q
g
(XOR)
_.. __ ...... - - .. ,
XOR. срр I В языке С++ не определен логический оператор "исключающее
ИЛИ"
(XOR),
хотя это
-
..
s
~
довольно популярный у программистов
бинарный оператор. Операция "исключающее ИЛИ" генерирует значение ис­
ТИНА, если это значение ИСТИНА имеет один и только один из его операндов.
Это утверждение можно выразить в виде следующей таблицы истинности.
q
р
ЛОЖЬ
ЛОЖЬ
ЛОЖЬ
ЛОЖЬ
ИСТИНА
ИСТИНА
ИСТИНА
ЛОЖЬ
ИСТИНА
ИСТИНА
ЛОЖЬ
ИСТИНА
Отсутствие реализации
логического
встроенного элемента языка одни
I
XOR q
р
.1
g
оператора
"исключающее ИЛИ" как
программисты считают досадным
изъяном
Модуль 2. Типы данных и опероторы
96
С++. Другие же придерживаются иного мнеьия: мол, это избавляет язык 01 из­
быточности, поскольку логический оператор "исключающее ИЛИ"
(XOR)
не­
ТРУДНО "создать" на основе встроенных операторов. И, ИЛИ и НЕ.
В настоящем проекте мы создадим операпию "исключающее ИЛИ", исполь­
II
зуя операторы &&,
и
!.
После этого вы са.\fИ рещите, как относиться к тому
факту, что этот оператор не является встроеЮIЫМ элементом языка С++.
Последовательность действий
1.
2.
Создайте новый файл с именем XOR. ср::).
Будем исходить из того, что логический оператор "исключающее ИЛИ"
(XOR),
(р
II
применяемый к двум значениям булевоro типа р и
q)
&&
!
(р
&&
q, строится
так.
q)
Рассмотрим это выражение. Сначала над операндами р и
q
выполняется
операция логического ИЛИ (первое подвыражеН1-\f), результат которой
будет равен значению ИСТИНА, если по крайней мере один из операн­
дов будет иметь значение ИСТИНА. Загем над операндами р и
q
ВЫПОЛ­
няется операЩIЯ логического И, результат которой будет равен значению
ИСТИНА, если оба операнда будут иметь значение ИСТИНА. Последний
результат затем инвертируется с помощью оператора НЕ. Таким обра­
зом, реэультат второго подвыражения !:р
&&
q) будет равен значению
ИСТИНА, если один из операндов (или оба сразу) будет иметь значение
ЛОЖЬ. Наконец, этот результат логичеСl:И умножается (с помощью опера­
цИИ И) на результат вычисления первого подвьrpажения (р
II
q). Таким
образом, полное выражение даст значение ИСТИНА, если один из операн­
дов (но не оба одновременно) имеет знач.~ние J:fСТИНА.
3.
Ниже приводится текст всей программы XOR. срр. При ее выполнении де­
монстрируется результат применения ош~рации "исключающее ИЛИ" дДЯ
всех четырех возможных комбинаций ИСТИНА/ЛОЖЬ-значений к двум
ее операндам.
1*
Проект
Соэдание
2.2.
оператора
"ИСКJ1ючаЮЩЕ!е ИЛИ"
встроенных логических
*/
С++-операторов.
(XOR)
на основе
С++: руководство ДЛЯ начинающих
97
using narnespace std:
•
int rnain ()
:0
#include <iostrearn>
#include <crnath>
:25
~g.
....
~8.
:ф
bool
р,
:~
:S
:)(
:25
.:1:
::1:
q:
:0
:<t
~
true:
true;
р
q
cout «
р
(р
II
(
~
«
" XOR " «
q)
&&
«
" XOR " «
!
(р
q «
&& q)
)
"
«
равно
" «
"\п":
false;
true;
р
q
cout «
(
(р
р
1 1 q)
&&
!
q «
(р && q)
)
"
«
равно
" «
"\п":
true:
false;
р
q
cout «
(
(р
р
11
«
" XOR " «
q)
&&
«
" XOR " «
q)
&&
!
(р
q «
&& q) )
"
«
равно
" «
"\n":
q
cout «
р
(р
11
return
О:
(
!
(р
q
&& q)
«
)
"
«
равно
"\п";
~
!
false:
false:
р
.§
" «
I
.~
I
J
98 МОДУЛЬ 2. Типы данных и операторы
................................................................................................................................
4.
Скомпилируйте и выполните программу. Вы должны получить такие ре­
зультаты.
1 XOR 1
О XOR 1
1 XOR О
О XOR О
равно
О
равно
равно
1
1
равно
О
Обратите внимание на внешние круглые скобки, в которые заключены вы­
5.
ражения о инструкции
операторы & & И
данных
II
cout.
Без них здесь обойтись нельзя, поскольку
имеют более низкий приоритет, чем оператор вывода
«<). Чтобы убедиться в этом, попробуйте убрать внешние круглые
скобки. При попытке скомпилировать программу вы получите сообщение
об ошибке.
~ПРОСЫ ДЛЯ текущего КоНтролЯ-------1.
Каково назначение оператора
"%"?
К значениям какого типа его можно
применять?
2.
Как объявить переменную
index типа int
с начальным значением
3.
Какой тип будет иметь результат логического выражения или выражения,
10?
U?
построенного с применением операторов отношении
Начиная с модуля
1, мы активно используем оператор присваивания.
Настало
время для "официального" с ним знакомства. В языке С++ оператором npuсва­
U6ШlUЯ служит одинарный знак равенства
(=). Его действие во многом подобно
действию аналогичных операторов в других языках программирования. Его об­
щий формат имеет следующий вид.
леременная
1.
Оператор
=
выражение;
"%" предназначен для выполнения операции деления по моДУJIЮ и воз­
вращает остаток от деления нацело. Его можно применять только к значениям
целочисленных типов.
2. int index
3.
=
10;
Результат логического выражения или выражения, построенного с применением
операторов отношений, будет иметь тип
bool.
С++: руководство для начинающих
99
Эта запись означает, что элементу леременная присваивается значение элемен­
та выражение.
Оператор присваивания обладает очень интересным свойством: он позволя­
ет создавать цепочку присваиваниЙ. Другими словами, присваивая "общее" зна­
чение сразу нескольким переменным, можно "связать воедино" сразу несколько
присваиваниЙ. Рассмотрим, например, следующий фрагмент кода.
int
х
=
~,
у,
у =
z
:s
х
25
100; 11
устанавливаем х,
у
и
z
равными
:r
:r
100
При выполнении этого фрагмента кода с помощью одной инструкции сразу
трем переменным х, у и
z
присваивается значение
100.
Почему возможна такая
форма записи? Дело в том, что результатом выполнения оператора
"="
является
значение выражения, стоящего от него с правой стороны. Таким образом. резуль­
татом выполнения операции
z
=
1О О
является число
100,
которое затем при­
сваивается переменной у и которое в свою очередь присваивается переменной
х. Использование цепочки присваиваний
-
самый простой способ установить
сразу несколько переменных равными "общему" значению.
присваивания
в С++ предусмотрены специальные cocmaвHыe операторы nрuсвauванu,Я. в
которых объединено присваивание с еще одной операцией. Начнем с примера и
рассмотрим следующую инструкцию.
х =
х
+ 10:
Используя составной оператор присваивания, ее можно переписать в таком виде.
х
+= 10;
Пара операторов += служит указанием компилятору присвоить переменной х
сумму текущего значения переменной х и числа
10.
А вот еще один пример. Инструкция
х =
х
- 100;
аналогична такой:
х
.
-= 100:
Обе эти инструкции присваивают переменной х ее прежнее значение, уменьшен­
ное на
100.
I
о
z;
=
!
~
25
с:
~
Модуль 2. Типы данных и операторы
100
................................................................................................................................
Составные версии операторов присваивания существуют для всех бинарных
операторов (т.е. для всех операторов, которые работают с двумя операндами). Та­
ким образом, при таком общем формате бинарных операторов присваивания
переменная
=
переменная
ор
выражение;
общая форма записи их составных версий выглядит так:
переменная
ор
=
выражение;
Здесь элемент ор означает конкретный арифметический или логический опера­
тор, объединяемый с оператором присваивания.
Поскольку составные операторы присваиваllИЯ короче своих "несоставных"
эквивалентов, составные версии иногда называются сокращеннbLМU операторами
присваивания.
Составные операторы присваивания обладают двумя достоинствами по срав­
нению со своими "полными формами". Во-первых, они компактнее. Во-вторых,
они позволяют компилятору сгенерировать более эффективный код (поскольку
операнд в этом случае вычисляется только один раз). Именно поэтому их можно
часто встретить в профессионально написанных С++-программах, а это значит,
что каждый С++-программист должен быть с ними на "ты".
ВАЖНОI
'1:8 "реобрnзовпние ТИEJав
воператорахприсваивания
Если переменные одного типа смешаны с переменными другого типа, про­
исходит nрео6разованuе тиnов. При выполнении инструкции присваивания
действует простое правило преобразования типов: значение, расположенное с
правой стороны от оператора присваивания, преобразуется в значение типа, со­
ответствующего переменной, указанной слева от оператора присваивания. Рас­
смотрим пример.
int Х;
char ch;
f10at f;
ch =
Х
=
Х;
f;
f
ch;
f
Х;
/*
/*
/*
/*
строка
1 */
строка
2 */
3 */
4 */
строка
строка
С++: PYI<OBOACтeO д,лfl начинающих
101
в строке
1 старшие биты целочисленной переменной х отбрасываются, остав­
ляя переменной ch младшие 8 бит. Если значение переменной х лежит в пре­
делах от -128 до 127, то оно будет идентично значению переменной ch. В про­
тивном случае значение переменной ch будет отражать только младшие биты
переменной х. При выполнении следующей инструкции (строка 2) переменная
х получит целую часть значения, хранимого впеременной
писанное в строке
3,
значения переменной
f.
Присвзивание, за­
обеспечит пре06разование 8-разрядного целочисленного
ch
в формат значения с плавающей точкой. Аналогичное
преобразование произойдет и при выполнении строки
4
за исключением того,
что в формат вещественного числа в этом случае будет пре06разовано не
char-,
а iпt-значение.
При пре06разовании целочисленных значений в символы и длинных
int-
в
обычные iпt-значения отбрасывается соответствующее количество старших би­
тов. Во многих 32-разрядных средах это означает, что при переходе от целочис­
ленного значения к символыюму будут утеряны
короткому iпt-значению
- 16
24 бит, а при переходе от int- к
бит. При пре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
расширяется в тип
int. Или, если хотя бы один из операндов имеет тип double, любой
double. Это означает, что такие преобразо­
вания, как из типа char в тип double, вполне допустимы. После преобразова­
long
другой операнд приводится к типу
ния оба операнда будут иметь один и тот же тип, а результат операции
-
тип,
совпадающий с типом операндов.
Прео6разования, связаННblе с типом
Как упоминалось выше. значения типа
bool
bool
при использовании в выражении
целочисленного типа автоматически преобразуются в целые числа О или
1.
При
преобразовании целочисленного результата в пrп
bool нуль превращается в fa1 se, а ненулевое значение - в true. И хотя тип bool относительно недавно был
добавлен в язык С++, выполнение автоматических преобразований, связанных с
типом
bool,
означает, что его введение в С++ не имеет негативных последствий
для кода, написанного для более ранних версий С++. Более того, автоматические
преобразования позволяют С++ поддерживать исходное определение значений
ЛОЖЬ и ИСТИНА в виде нуля и ненулевого значения.
ВАЖНОI
tll•1 ПРИВРАрние типов
L •
в С++ предусмотрена возможность установить для выражения заданный тип.
Для этого используется операция nриведенuя типов
(cast).
С++ определяет пять
видов таких операций. В этом разделе мы рассмотрим только один из них (самый
универсальный и единственный, который поддерживается старыми версиями
С + + ), а остальные четыре описаны ниже в этой книге (после темы создания объ­
ектов). Итак, общий формат операции приведеllИЯ типов таков:
(тип)
выражение
Здесь элемент тип означает тип, к которому необходимо привести выржение•.
Например, если вы хотите, чтобы выражение х /2 имело тип
написать следующее:
(float)
х
/ 2
floa t, необходимо
С++: РУКОВОДСТВО ДМ! начинающих
103
Приведение типов рассматривается как унарный оператор, и поэтому он име­
ет такой же приоритет, как и другие унарны.е операторы.
Иногда операция приведения типов оказывается очень полезной. Например,
в следующей программе для управления циклом используется некоторая цело­
численная переменная, входящая в состав выражения, результат вычисления ко­
торого необходимо получить с дробной частью.
11
Демонстрация операции приведения типа.
i1nclude <1ostream>
us1ng патеэрасе std;
1пt
та1п
() (
1nt i;
for(i=l; 1 <= 10; ++1 )
cout « 1 « "1 2 равно: " «
11
11
11
11
rеturп
(float) 1 1 2 «
'\п';
i
Приведение к
типу
fioat
обеспечит обязательный
вывод дробной части.
О;
Вот как выглядят результаты выполнения этой программы.
0.5
11 2
21 2
равно:
равно:
1
ЗI
равно:
1.5
2
2.5
2
41 2 равно:
51 2 равно:
61 2 равно: 3
71 2 равно: 3.5
81 2 равно: 4
91 2 равно: 4.5
101 2 равно: 5
Без оператора приведения типа
(floa t) выполнилось бы только целочислен­
ное деление. Приведение типов в данном случае гарatПирует, что на экране будет
отображена и дробная часть результата.
Модуль 2. Типы данных и операторы
104
ВАЖНО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 мы будем ис­
пользовать функцию
result
=
pow ( ) . Вот как выглядит формат ее выэова.
pow(base,
:о
а.
ехр):
g
Функция
pow () возвращает значение элемеита base, возведеиное в степень
pow () принимает аргументы типа double и возвращает значение
типа double.
ехр. Функция
~
с:
о
:s:
х
25
~
Последовательность действий
1.
2.
Создайте файл с именем
~
:о
RegPay. срр.
с:
~
Объявите следующие перемеНJiые.
double Principal:
double IntRate:
double
double
double
double
double
Pow ()
11
11
11
11
11
PayPerYear:
NumYears;
Payment;
1/
numer, denom; 1/
Ь,
е;
/1
исходная
сумма займа
процентная
числа
количество
срок
ставка
(например,
виде
выплат в
займа(в
размер
в
0.075)
год
годах)
регулярного
платежа
временные переменные
аргументы для
вызова
функции
Обратите внимание на то, что после объявления каждой переменной дан
комментарий, в котором описывается ее назначение. И хотя мы не снабжа­
ем такими подробными комментариями большинство коротких программ
в этой книге, я советую следовать этой практике по мере тоro, как ваши
программы будут становиться все больше и сложнее.
З. Добавьте следующие строки кода, которые обеспечивают ввод необходи­
мой для расчета информации.
cout « "Введите исходную
cin » Principal;
«
займа:
процентную
ставку
cout « "Введите количество
cin » PayPerYear;
выплат
cout
cin »
"Введите
сумму
":
(например,
IntRate;
cout « "Введите
cin » NumYears:
срок займа(в
в
годах):
год:
":
";
0.075):
";
106 МОДУЛЬ 2. Типы данных и операторы
.................................................................................................................................
4.
Внесите в программу строки, которые выполняют финансовые расчеты.
numer
е
=
=
IntRate
* NumYears);
(IntRate / PayPerYear) + 1;
denom
1 - pow(b,
Payment
е);
numer / denom;
Завершите программу выводом результата.
cout«
«
6.
Principal / PayPerYear;
-(РауРеrУеаr
Ь
5.
*
"Размер
платежа
по
займу
"
составляет
Payment;
Теперь приведем полный текст программы
RegPay. срр.
/*
Проект
2.3.
Вычисление
Назовите
размера
этот
файл
регулярных
платежей
по
займу.
RegPay.cpp.
*/
#include <iostream>
iinclude <cmath>
using namespace std;
int main () {
double Principal;
double IntRate;
double
double
double
double
double
//
//
//
PayPerYear;
//
//
NumYearsi
Payment;
//
numer, denom; //
Ь, е;
//
11
исходная сумма
займа
процентная ставка в
числа
(например,
количество
выплат
срок займа(в
cout « "Введите исходную
cin » Principali
сумму
год
платежа
переменные
аргументы для
функции
в
годах)
размер регулярного
временные
виде
0.075)
вызова
Pow ( )
займа:
";
С++: РУКОВОДСТВО ДЛЯ начинающих
107
................................................................................................................................
cout « "Введите
cin » IntRate;
процентную ставку
cout « "Введите количество
cin » PayPerYear;
cout « "Введите
cin » NumYears;
numer
е
=
=
(например,
выплат
в
срок займа (в годах):
год:
0.075): ";
";
";
IntRate * Principal / PayPerYeari
* NumYears);
(IntRate / PayPerYear) + 1;
-(РауРеrУеаr
Ь
denom = 1 - pow(b,
Payment
cout«
«
return
е);
numer / denom;
"Размер
платежа
по
займу
составляет
"
Payment;
О;
ВОТ как выглядит один из возможных результатов выполнения этой про­
граммы.
Введите
сумму займа:
ставку
Введите
выплат
Введите
Размер
7.
исходную
Введите процентную
количество
срок займа
платежа
по
(в
10000
'1
(например,
в
год:
годах):
0.075): 0.075
12
5
займу составляет
-1g
200.379
Самостоятельно внесите изменения в программу, чтобы она отображала
также общую сумму, выплаченную по npоцентам за весь период займа.
I
Ii
I
108 Модуль 2. Типы данных и операторы
.............................................................................................................................
- ..
"жеС Т АО9 самоконтроля по МОА)l'Ю 2
1.
Какие типы целочисленных значений поддерживаются в С++?
2. Какой тип по умолчанию будет иметь константа 12. 2?
3.
Какие значения может иметь переменная типа
bool?
4. Что представляет собой длинный целочисленный тип данных?
5.
Какая управляющая последовательность обеспечивает табуляцию? Какая
управляющая последовательность обеспечивает звуковой сю'пал?
6.
Строка должна быть заключена в двойные кавычки. Это верно?
7. Что представляют собой шестнадцатеРИЧlfые цифры?
8.
9.
Представьте общий формат инициализации переменной при ее объявлении
Какое действие выполняет оператор
"%"? Можно ли его применять к значе­
ниям с плавающей точкой?
10.
Поясните различие между префиксной и постфиксной формами оператора
инкремента.
11.
Какие из перечисленных ниже символов являются логическими операто-
рами в С++?
А.
12.
В.
&&
##
С.
11
о.
$$
Е.
!
•
Как по-другому можно переписать следующую инструкцию?
х
=
х
+ 12;
13.
Что такое приведение типов?
14.
Напишите программу. которая находит все простые числе в интервале
от
1 до 100.
-tc
На заметку
n. .
Ответы на эти вопросы можно найти на Web-странице данной
книги по адресу:
http://www . osborne. сот.
МОДУЛЬ3
ИНСТРУI<ЦИИ управления
3.1.
3.2.
3.3.
3.4.
3.6.
3.7.
3.8.
3.9.
Инструкция
if
Инструкция switch
Цикл
for
Цикл
while
ИСПОЛl>Зование инструкции
break для
Использование ИIIСТРУКЦИИ
continue
Вложенные llИКJIbl
Инструкция
goto
выхода ШJ цикла
110 Модуль З. ИНСТРУКЦИИ управления
..................................................................................................................................
В этом модуле вы узнаете, как управлять ходом выплнения С++-программы.
Существует три категории управляющих инструкций: инструкции 8ыбора
(if,
for-, while- и do-whileциклов) и инструкции перехода (break, continue, return и goto). За исклю­
чением return, все остальные перечисленные выше инструкции описаны в этом
swi tch),
итерационные инструкции (СОСТОЯЩИf' из
модуле.
Инструкция
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;
int guess;
magic
11
11
магическое число
вариант
rand(); 11
=
cout « "Введите
cin » guess;
пользователя
Получаем случайное число.
свой
вариант
if(guess == magic)
cout « "** Правильно **".,
else
cout « " ... Очень жаль, но
магического
11
вы ошиблись.";
11
return
числа:
";
Индикатор
неправильного ответа.
О;
Условное выражение
Иногда новичков в С++ сбивает с толку тот факт, что для управления if-ин­
струкцией можно использовать любое деЙствите.'IЪное С++-выражение. Другими
словами, тип выражения необязателыю ограничивать операторами отношений и
логическими операторами или операндами типа
bool.
Главное. чтобы результат
вычисления условного выражения можно было интерпреrnровать как значение
ИСТИНА или ЛОЖЬ. Как вы пьмните из предыдущего модуля, нуль автомати­
чески преобраэуется в
false,
а все ненулевые значения
-
в
true.
Это означает,
что любое выражение, которое дает в результате нулевое или ненулевое значе­
ние, можно использовать для управления if-ИlfструкциеЙ. Например, следую­
щая про грамма считывает с клавиатуры два целых числа и отображает частное
от деления первого на второе. Чтобы не допустить деления на нуль, в программе
используется if-инструкция.
11
Использование
//
if-инструкциеЙ.
iпt-значения для управления
#include <iostream>
using namespace std;
int main ()
С++: РУКОВОДСТВО ДМl начинающих
а,
int
cout «
cin »
cout «
cin »
Ь;
"Введите
";
числитель:
а;
"Введите
";
знаменатель:
Ь;
if(b)
cout «
"Результат:
" «
а
/
Ь
«'\п';
e1se
cout «
return
113
"На
нуль
делить
//
//
//
//
ДnR упрaanевик
этой if-ИВСтру1ЩИ8Й
достаточно ОД80Й
переllенвой Ь.
нельзя.\п";
О;
Возможные результаты выполнения этой программы выглядят так.
Введите числитель:
Введите
Результат:
Введите
12
6
числитель:
12
Введите
знаменатель:
На
делить
нуль
2
знаменатель:
О
нельзя.
Обратите внимание на то, что значение переменной Ь (делимое) сравнивается
с нулем с помощью инструкции 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 result
=
4; //
Эта else-ветвь
Эта else-ветвь
связана с
связана с
if(k).
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 « " Ваш вариант
else
cout « " Ваш вариант
return
но вы ошиблись.";
больше магического числа.\n";
меньше магического числа.\n";
О;
"Лестничная" конструкция if-else-if
Очень распространенной в программировании конструкцией, в основе кото­
рой лежит вложенная if-ИНСтрукция, является "лестница"
но представить
if
D следующем
if-else-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
Теперь познакомимся с еще одной инструкцией выбора
ция
switch обеспечивает
- swi tch.
Инструк­
многонаправленное ветвление. Она позволяет делать
выбор одной из множества альтернатив. Хотя многонаправленное тестирование
можно реализовать с помощью последовательности вложенных 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 «
cin »
s:
s:
с::
>-
"Введите
число
от
1
до
3: ";
::r
пит;
5.
е-
()
switch(num)
case 1:
cout «
break;
сазе 2:
cout «
break;
case 3:
cout «
.break;
default:
cout «
return
//
//
Значение переиенвой пum опредanRе~
awпоnниекую ca8e-.e~.ъ.
"Один пирог два раза
не съешь.\п";
"Двум головам на одних плечах
"Три раза прости,
TecHo.\n";
а в четвертый прихворости.\п";
"Вы должны ввести число
1, 2
или
3.\n";
О;
Вот как выглядят возможные варианты въmолнения этой программы.
Введите
Один
число
пирог
Введите
два
число
от
1
раза
от
1
дО
З:
не
дО
1
съешь.
З:
5
1, 2 или з.
break не06язательна,
Вы должны ввести число
Формально инструкция
хотя в большинстве случаев
использования switсh-КОНСТРУКЦИЙ она присутствует. Инструкция
break, сто­
ящая в последовательности инструкций любой саsе-ветви, приводит к выходу
из всей
5wi tсh-КОНСТРУКЦИИ
и передает управление инструкции, расположен­
ной сразу после нее. Но если инструкция
break в
саsе-ветви отсутствует, будут
выполнены все инструкции, связанные с данной саsе-ветвью, а также все после­
дующие инструкции, расположенные за ней, до тех пор, пока все-таки не встре-
:J:
: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:
cout «
сазе 2 :
cout «
сазе 3:
cout «
сазе 4 :
cout «
cout «
return
'\п'
{
"меньше
"меньше
"меньше
"меньше
"меньше
1 \п"; //
2\п"; //
З\п"; //
4\п"; //
5\п" ; //
ИRС'1'рYIЩИИ
break
нет
инструхции
break
вет
;
О;
При выполнении программа генерирует такие результаты.
меньше
меньше
меньше
1
2
3
меньше
4
меньше
5
меньше
2
меньше
меньше
3
4
меньше
5
меньше
3
С++: PYI<OBOACTBO ДЛЯ начинающих
меньше
4
меньше
5
меньше
4
меньше
5
меньше
5
Как ВИДНО по результатам, если инструкция
break в одной
121
саsе-ветви отсут­
ствует, выполняются инструкции, относящиеся к следующей саsе-ветви.
Как показано в следующем примере, в
swi tсh-КОНСТРУКЦИЮ можно включать
"пустые" саsе-ветви.
switch(i)
case 1:
саsе 2: // +саsе
Пустые саsе-ветаи.
З:
cout «
break;
саsе 4:
cout «
break;
"Значение
переменной
i
меньше
"Значение
переменной
i
равно
Если переменная
i
4.";
4.";
в этом фрагменте кода получает значение
1,2
или З, ото­
бражается одно сообщение.
Значение
переменной
i
меньше
Если же значение переменной
Значение
переменной
i
4.
i равно 4, отображается другое сообщение.
равно
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 « "Эта А-ветвь break;
case '8': // _.,
часть
внутренней
switch";
break;
саБе '8': // __ .
~npocы МЯ теl<ущеro контроля - - - - - - - 1.
2.
Какого типа должно быть выражение, управляющее инструкцией
Что происходит в случае, если результат вычисления
sw i tch?
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м значением. Рассмо­
трим, например, следующую
If(x < 10)
//
i f -е 1 5 е- i f
-последовательность.
_ ..
else Н(у > О) / / _ ••
else I f (! done) / / ' "
эту последовательность нельзя переllрограммировать с помощью инсгрук­
ции
swi tch,
поскольку все три условия вКJIIOчaIOТ различные перемеюwе
и различные пmы. какая переменнзя управляла бы инсгрукцией
switch?
Кроме тoro, if-е1sе-if-"лестницу" придется использовать и в случае, когда
нужно проверять значения с плавающей точкой или другие объекты, 'IИПbI
которых не подходят для ИСП01IЬЭ08aI!ИЯ В
1.
Выражение. управляющее инструкцией
swi1:ch,
swi tсh-вьrpaжении.
должно иметь целочисленный или
символьный тип.
2.
Если резулътзт вычисления
swi tсh-выражения
совпадает с саsе-константой, то вы­
полняется последователыlOСТЪ инструКЦИЙ, связанная с ЭТОЙ case-константоЙ.
З.
Если са 5 е-последовательность не завершается инструкцией
следующая case-последовательнос1Ъ инструкций.
break,
то ВЪП101ПlЯется
С++: руководство ДМ! начинающих
"'ЩеЙ"'II"
Г H;i~.~p;-
---
'_IIИМ·'.
123
_,мм t
Этот проект создает простую справочную систему, которая под-
. ....;
сказывает синтаксис управляющих С++-инструкций_ После за-
пуска наша программа отображает меню, содержащее инструкции управления, и
переходит в режим ожидания до тех пор, пока пользователь не сделает свой вы­
бор. Введенное пользователем значение используется в инструкции
swi tch для
отображения синтаксиса соответствующей инструкции_ В первой версии нашей
простой справочной системы можно получить "справку" только по
i f-
и
s wi t -
сh-Инструкциям. Описание синтаксиса других управляющих С++-инструкuий
будет добавлено в последующих проеКтах_
ПослеАовательность Аействий
1.
Создайте файл
2.
Выполнение программы должно начинаться с отображения следующего
Help. срр_
меню_
Справка
по
инструкциям:
1. if
2.switch
Выберите
вариант
справки:
Для реализации этого меню используйте следующую последователыюсть
инструкций.
cout
cout
cout
cout
3.
« "Справка по инс'!'рукциям:\п";
« " 1. if\n";
« " 2. switch\n";
« "Выберите вариант справки: ";
Затем программа должна принять вариант пользователя.
cin »
4.
choice;
Получив вариант пользователя, программа применяет инструкцию
ch для отображения синтаксиса выбранной инструкции_
switch(choice)
саsе '1':
cout « "Синтаксис инструкции if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "е1sе инструкция;\п";
break;
swi t-
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";
последовательность инструкций\n";
cout « "
break; \n" ;
cout « "
cout « " // ... \n";
cout « "}\n";
break;
default:
cout « "Такого варианта HeT.\n";
return
О;
Вот один ИЗ возможных вариантов вьmолнения этой программы.
Справка
по
инструкциям:
1. i f
2. switch
Выберите
вариант
справки:
Синтаксис инструкции
if(условие)
else
1
if:
инструкция;
инструкция;
ВАЖНО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
'\п';
О;
в этой программе использована стандартная С++-функция
разъяснялось в модуле
2,
sqrt ().
как
эта функция возвращает значение квадратного корня
С++: РУКОВОДСТВО М9. начинающих
127
из
CBoero аргумента. Аргумент должен иметь тип double, и именно поэтому при
sqrt () параметр пит приводится к типу double. Сама функ­
ция также возвращает значение типа double. Обратите внимание на то, что в
программу включен заголовок <ста th>, поскольку этот заголовочный файл обе­
спечивает подцержку функции sqrt ().
Управляющая переменнЗS\ цикла for может изменяться как с положитель­
вызове функции
ны,' так и с отрицательным приращением, причем величина этого приращения
также может быть любой. Например, следующая программа выводит числа в диа­
пазоне от
//
50 до -50 с декрементом, равным 10.
Использование в цикле
for
отрицательного приращеНИR.
#include <iostrearn>
using narnespace std;
int main()
int i;
for(i=50; i
return
i-10) cout «
>= -50; i
i «
I
1.
О;
При выполнении программа генерирует такие результаты.
50 40
за
20 10
О
-10 -20
-зо
-40 -50
Важно понимать, что условное выражение всегда тестируется в начале выпол­
нения цикла
for.
Это значит, что если первая же проверка условия даст значение
ЛОЖЬ, код тела ЦИJUIа не выполнится ни разу. Вот пример:
for(count=10; count < 5; count++)
cout « count; // Эта инструкция
не
выполнится.
Этот цикл Iiикогда не выполнится, поскольку уже при входе в
управляющей переменной
(count < 5) ложным
count
Hero значение его
больше пяти. Это делает условное выражение
с самого начала. Поэтому даже одна итерация этоro цикла
не будет выполнена.
Вариации на тему цикла
Цикл
for -
for
одна из наиболее гибких инструкций в С#, поскольку она по­
зволяет получить широкий диапазон вариантов ее применения. Например, для
г­
:s:
:r
Ф
.;;:
'"о
Q.
128
Модуль З. ИНСТРУКЦИИ управления
управления циклом
for
можно использовать несколько переменных. Рассмо­
трим следующий фрагмент кода.
for(x=O, y=lO;
х
у;
<=
++х,
--у)
// Сразу несколько
/ / упрааna:IЦИХ nepeкellJWX.
cout «
х
«
' , «
у
«
'\п';
Здесь запятыми отделяются две инструкции инициализации и два инкремеНТНbJХ
выражения. Это делается для того, чтобы компилятор "понимал", что существует
две инструкции инициализации и две инструкции инкремента (декремента). В
С++ запятая представляет собой оператор, I<ОТОрЫЙ, по сути, означает "сделай
это и то". Другие применения оператора "запятая" мы рассмотрим ниже в этой
книге, но чаще всего он используется в цикле
инкремента цикла
for.
В разделах инициалИЗ311ИИ и
for можно использовать любое количество инструкции, но
обычно их число не превышает двух.
Спросим у опытного программиста
Вопрос. Поддерживает ли С++, помимо !щrt(). другие мате..mтuческие функции?
Ответ.
Даl Помимо функции
sqrt (),
С++ поддерживает широкий набор мате­
матических функций, например
и
floor ( ).
sin () , cos (), tan (). 10g (), cei1 ()
Если вас интересует программирование в области матема1'И­
КИ, вам стоит ознакомиться с составом С++-библиотеки математических
функций. Это сделать нетрудно, поскольку все С++-компиляторы их под­
держивают, а их описание можно найти в документации, прилагаемой к
вашему компилятору. Помните, что все математичеСЮlе функции требуют
включения в программу заголовка <ста
th>.
Условным выражением, которое управляет циклом f о r, может быть любое допу­
стимое С++-выражение. При этом оно не06язательно должно включать управляю­
щую переменную ЦИЮIа. В следующем примере цикл будет выполняться до тех пор,
пока функция
rand ()
не сгенерирует значение, которое превышает число
20 000.
/*
Выполнение
случайное
цикла
число
продолжается
не
*/
tinclude <iostream>
tinclude <cstdlib>
using namespace std;
превысит
здесь
20000.
до
тех
пор,
пока
С++: PYI<OBOACTBO ДЛ91 начинающих
129
•
int main ()
10:
:s:
{
:1:
Ф
int i;
int r;
<
са
R
5-
:s:
~
r = rand () ;
~
>е-
for(i=O; r <= 20000; i++) //
В этом уcnо.вом
awp. . . вии
/ / уа:рaJШ-nцe- переllеlUl&Jl
// не ИCDоnъsуетс •.
ЦИIUI&
r = rand () ;
cout « "Число равно " « r «
" Оно сгенерировано при попытке " «
return
i
" . " ,.
«
О;
ВОТ ОДИН из возможных результатов выполнения этой программы.
Число равно
26500.
Оно
сгенерировано
при
попытке
При каждом проходе ЦИlUIа с помощью функции
случайное число. Когда оно превысит число
rand ()
20 000, условие
з.
генерируется новое
продолжения ЦИlUIа
станет ложным, и ЦИIUI прекратится.
Отсутствие элементов в определении цикла
в С++ разрешается опустить любой элемент заголовка цикла (инициализа­
ция, условное выражение, инкремент) или даже все сразу. Например, мы хотим
написать ЦИIUI, который должен выполняться до тех пор, пока с lUIавиатуры не
будет введено число
//
Цикл
for
123. Вот как выглядит такая программа.
без инкремента
#include <iostream>
using паmеsрасе std;
int main ()
в
заголовке.
.
u
.:1:
:S:
МОДУЛЬ З. ИНСТРУJ<ЦИИ управлени~
130
Х;
int
cout «
ci'n »
return
!= 123;
{ //
"Введите
число:
х
for(x=O;
~ Зl.zpахе_ JUUCPeKeR~a He~.
";
Х;
О;
Здесь в заголовке цикла
for
отсутствует выражение инкремента. Это означает,
что при каждом повторении цикла выполняется только оДJЮ действие: значение
переменной х сравнивается с числом
123. Но если ввести с клавиатуры число 123,
условное выражение, проверяемое в цикле, станет ложным, и цикл завершится.
Поскольку выражение инкремента в заголовке цикла
for
отсутствует, управля­
ющая переменная цикла не модифицируется.
Приведем еще один вариант никла
for,
в заголовке которого, как показано в
следующем фрагменте кода, отсутствует раздел инициализации.
О;
/ / +-
for ( ;
х<10;
х
=
cout «
х
Переиенная: х инициanизируе~CJI вне ЦИlCJlа.
«
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++) ; //
cout «
return
"Сумма
чисел равна
" «
~
"БеСlJ.lеnесRЫЙ" ЦИ1CJI.
sum;
О;
Результат выполнения этой программы выглядит так.
Сумма
чисел равна
55
Обратите внимание на то, что процесс суммирования полностью реализуется
в заголовке J:lНСТРУКЦИИ f О r, и поэтому в теле цикла нет никакой неоБХОДИМОСnt.
Обратите также в}lИмание на следующее выражение инкрементирования.
зит
+= i++
Модуль З. ИНСТРУI(ЦИИ управлени~
132-
Не стоит опасаться инструкций, подобных этой. Они часто встречаются в
профессиональных С++-программах. Если разБИТI> такую инструкцию на части,
то она окажется совсем несложной. Русским языком выполняемые ею действия
можно выразить так: "При6авим к значению переменной
ной
sum значение перемен­
и сохраним результат сложения в переменной эит, а затем инкремеllТИ­
i
руем переменную
i ". Другими словами, приведенную выше инструкцию можно
переписать так.
sum
=
эит
+ i;
i++;
Объявление управляющей переменной цикла
в заrоловке инструкции
for
Зачастую переменная, которая управляет циклом
for,
нужна только для это­
го цикла и ни для чего больше. В этом случае такую переменную можно оБЪЯflИТЬ
прямо В разделе инициализации заголовка инструкции
for.
ющей программе, которая вычисляет как сумму чисел от
риал, управляющая перемеtlНая цикла
ции
//
//
Например, в следу­
1 до 5,
так и их факто­
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
fact *= i;
//
но
cout «
здесь
/ /
Переменная
она уже не
"Сумма
чисел
i
изв(~стна
в
рамках цикла,
существует.
равна"
«
sum «
"\п";
С++: РУКОВОДСТВО дл9t начинающих
cout «
return
"Факториал
равен
" «
1ЗЗ
fact;
О;
При выполнении эта программа генерирует такие результаты.
Сумма
чисел
15
120
равна
Факториал равен
При объявлении переменной в заголовке цикла важно помнить следующее:
эта переменная известна только в рамках инструкции
В этом случае гово­
for.
рят, что область видимости такой переменной ограничивается циклом
for. Вне
его она прекращает свое существование. Следовательно, в предыдущем примере
переменная
i
недоступна вне цикла f о r. Если же вы собираетесь использовать
управляющую переменную цикла в другом месте программы, ее не следует объ­
являть в заголовке цикла
Нозаметку~
f
о r.
в ранних версиях С++ переменная, объявленная В разделе инициа­
лизации заголовка инструкции
fo r, была доступна и вне цикла fo r.
Но в результате процессастандартизации положение вещей измени-
лось. В настоящее время стандарт ANSI/ISO ограничивает область
ВИДИМОСТИ такой переменной рамками цикла
for.
Однако в неко­
торых КОМIШЛЯторах это требование не реализовано. Поэтому вам
имеет смысл прояснить этот момент в своей рабочей среде.
~просы ДЛЯ текущего КОНТРОЛЯ
t.
2.
--------
Могут ли некоторые разделы заголовка инструкции
for
быть пустыми?
Покажите, как на основе инструкции for можно создать бесконечный
цикл?
3.
Какова область видимости переменной, объявленной в заголовке ин­
струкции f о r?
1.
•
да. Пустыми MOryr быть все три раздела заголовка инструкции
for:
ииициалиэация,
выражение и инкремент.
2. for ( ; ; )
3. Область видимости переменной, объявленной в заголовке инструкции for, ограничи­
вается рамками цикла
for.
Вне его она неизвестна.
Модуль З. ИНСТРУI<ЦИИ управления
134
ВАЖНО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/
cout « ch;
ch++;
Начало цикла
while.
символы,
если
С++: руководство ДЛЯ начинающих
return
135
О;
Рассмотрим whilе-выражение из предыдущей программы. Возможно, вас уди­
вило, что оно состоит всего лишь из одной переменной
ch. Но "ларчик" открывается
просто. Поскольку переменная ch имеет эдесь тип unsigned char, она может со­
держать значения от О до 255. Если ее значение равно 255, то после инкрементирооа­
ния оно "сбрасывается" в нуль. Следовательно, факт равенства значения переменной
ch нулю служит удобным способом завершить whilе-цикл.
Подобно циклу for, условное выражение проверяется при входе в цикл while, а это значит, что тело цикла (при ложном результате вычис~ения УСЛОВJЮI'О
выражения) может не выполниться IШ разу. Это свойство цикла устраняет не­
обходимость отдельного тестирования до начала цикла. Следующая программа
выводит строку, состоящую из точек. Количество отображаемых точек равно зна­
чению, которое вводит пол ьзовате.IJ ь. Программа не позволяет "рисовать" строки,
если их длина превышает
80 символов.
Проверка на допустимость числа выводи­
мых точек вьmолняется внутри условного выражения ЦИlUlа, а не снаружи.
#include <iostream>
using namespace std;
int main ()
int len;
cout «
сiл
»
"Введите
длину
строки
(от
1
до
79): ";
lел;
while(len>O && len<80)
cout « '.';
len--;
rеturл
О;
}
Если значение переменной
(1-79),
то цикл
while
len
не "вписывается" в заданный диапазон
не выполнится даже один раз. В противном случае его
тело будет повторяться до тех пор, пока Зliачение переменной
равным нулю.
len
не станет
Модуль З. ИНСТРУI<ЦИИ управления
136
Тело whilе-цикла может вообще не содержать ни одной инструкции. Вот
пример:
while(rand()
!= 100)
Этот цикл выполняется до тех пор, пока случайное число, генерируемое функци­
ей
rand (),
не окажется равным числу
100.
ВАЖНОI
&. Ilиl(Adn-wЬjlе
•
Настало время рассмотреть последний из С++-циклов:
от циклов
ile
for
и
while,
do-while.
В отличие
do-whdo-while
в которых условие проверяется при входе, цикл
проверяет условие при выходе из цикла. Это значит, что цикл
всегда выполняется хотя бы один раз. Его общий формат имеет такой вид.
do {
инструкции;
} while
(выражение);
Несмотря на то что фигурные скобки необязательны, если элемент ИНСТР~IlЩИИ
состоит только из одной инструкции, они часто используются для улучшения
читабельности конструкции
клом
while.
Цикл
do-whi le, не допуская тем самым путаницы с ци­
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\Я начинающих
Используя цикл
do-while,
137
мы можем еще более усовершенствовать про­
грамму "Угадай магическое число". На этот раз программа "не выпустит" вас из
цикла угадывания, пока вы не угадаете это число.
//
//
Про грамма
"Угадай магическое число":
З-е усовершенствование.
#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(ЦИИ управлени~
Введите свой вариант магического числа:
... Очень
Введите
жаль,
жаль,
**
жаль,
но вы ошиблись.
но
** 41
чис~а:
100
Ваше число больше магического.
50
Ваше число
магического числа:
и есть
И еще. Подобно циклам
10
число меньше магического.
магического числа:
вы ошиблись.
свой вариант
Правильно
Ваше
магического
свой вариант
... Очень
Введите
вы ошиблись.
свой вариант
-... Очень
Введите
но
больше
магического.
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. срр
Этот проект
-
попытка усовершенствовать справочную систему
С++, которая была создаиа в проекте
лен синтаксис для циклов
1.
В цикле
for, while
и
do-while.
3.1. В этой версии добав­
Кроме того, эдесь проверя-
while условие проверяется при входе, а в цикле do-while - при выходе.
do-wh i 1 е всегда выполняется хотя бы ОДЮI раз.
Это значит, что цикл
2.
Да, тело цикла
while (как и любоl·О другого С++-цикла) может быть пустым.
С++: руководство ДЛ~ начинающих 139
................................................................................................................................
ется значение, вводимое пользователем при выборе uарианта меню, и с помощью
цикла
do-while
реализуется прием только допустимого варианта. Следует от­
метить, что для обеспечения функционирования меню цикл
do-wh i 1 е применя­
ется очень часто, поскольку именно при обработке меню необходимо, чтобы цикл
ВЫПОЛIiИЛСЯ хотя бы один раз.
Последовательность действий
1.
2.
Создайте файл
Help2 . срр.
Измените ту часть программы, которая отображает команды меню, причем
так, чтобы был использован цикл
do:....while.
do
cout
cout
cout
cout
cout
cout
cout
« "Справка по инструкциям:\п";
« " 1. if\n";
« " 2. switch\n";
« " З. for\n";
« " 4. whilе\л";
« " 5. dо-whilе\л";
« "Выберите вариант справки: ";
cin »
choice;
while ( choice < '1'
3.
Расширьте инструкцию
по циклам
I I choice > '5');
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;
Обратите внимание на то, что в этой версии
ствует ветвь
defaul t.
swi tсh-ИНСТРУКЦИИ
отсут­
Поскольку цикл по обеспечению работы меню ('а­
рантирует ввод допустимого варианта, то для обработки некорректных flа­
риантов инструкция
4.
defaul t
больше не нужна.
При ведем полный текст программы
Help2. срр.
/*
Проект
3.2.
Усовершенствованная
в
которой для
цикл
версия
обработки
справочной
системы С++,
вариантов меню используется
do-while.
*/
#include <iostream>
using namespace std;
int main ()
char choice;
do {
cout « "Справка по инструкциям:\п";
cout « " 1. if\n";
С++: PYI<OBOACTBO ДЛЯ начинающих
cout
cout
cout
cout
cout
«
«
«
«
«
cin »
"
"
"
"
2. switch\n";
З. for\n";
4 • while\n";
5. do-while\n";
"Выберите
вариант
" ,.
choice;
while ( choice < '1'
cout «
справки:
141
I I choice > '5');
"\п\п";
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 «
break;
"} while
(услоВи~);\п";
О;
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'аемой к вашему компилятору.
Большинство про грамм истов не объявляет переменные в управляющих
инструкциях, за исключением цикла
перемеНIIОЙ в дрynlХ инструкциях
-
[or.
И в самом деле, объявление
довольно спорный момент, и МНОЛfе
прогр,lммисты считают это "дурным 1'О/ЮМ· программирования.
С++: руководство ДМI начинающих
143
ВАЖНО!
ДМt выхода из цикла
с ПОМОЩЬЮ инструкции
brea k
можно организовать немедленный ВЫХОД из
цикла, опустив выполнение кода, оставшеrocя в его теле,
11
выражения. При обнаружении внутри цикла инструкции
break цикл заверша­
проверку условного
ется, а управление передается инструкции, следующей после цикла. Рассмотрим
простой пример.
iinclude <iostream>
using namespace std;
int main ()
int t i
// Цикл работает для значений t
for(t=O; t<100; t++)
if(t==10) break; 1/ При t = О
cout « t « I 1 ;
return
от
О
цихп
до
for
9,
а
не до
100!
npехращаетс •.
О;
Результаты выполнения этой программы таковы.
О
123 4 5
б
до
9, а не до 100, поскольку инструкция break при значении t, равном 10, обе­
7 8 9
как подтверждают эти результаты, программа выводит на экран числа от О
спечивает немедленный выход из цикла.
При использовании вложенных циклов (т.е. когда один цикл включает дру­
гой) инструкция
break обеспечивает выход только 113 наиболее глубоко вложеll­
ного цикла. Рассмотрим пример.
#include <iostream>
using namespace std;
int main ()
МОДУЛЬ З. ИНСТРУКЦИИ управления
144
int t, count;
t<10; t++) (
count = 1;
for(ii) (
cout « count « ' ';
count++i
if(count==10) break;
for(t~O;
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
1 2
1 2
1 2
1 2
3
3
3
3
3
4
4
4
4
4
5 6 7 8 9
5 6 7 8 9
5 б 7 8 9
5 б 7 8 9
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и
11
1/
cout «
return
х
«
I
значение х Rече~вое,
сразу перехоДJDC на cneД)'ltlQll)
итер8ЦIШ ЦИ1Ш&.
1;
О;
Здесь выводятся только четные числа, поскольку при обнаружении нечетного
числа происходит преждевременный переход к следующей итерации, и сои tинструкция опускается. (Вспомним, что с помощью оператора
"%"
мы получаем
остаток при выполнении целочисленного деления. Это значит, что при нечетном
х остаток от деления на два будет равен единице, которая (при оценке в качестве
результата условного выражения) соответствует истинному значению. В про­
тивном случае (при четном х) остаток от деления на доа будет равен нулю, т.е.
значению ложь.)
В циклах
while
и
do-while
инструкция
continue
передает управление
непосредственно инструкции, проверяющей условное выражение, после чего
циклический процесс продолжает "идти своим чередом". А в цикле
выполнения инструкции
жение, а затем
продолжен.
-
continue
for
после
сначала вычисляется инкрементное выра­
условное. И только после этого циклический процесс будет
М·одуль З. ИНСТРУI(ЦИИ управления
146
~ПРОСЫ ДIIЯ текущего КОНТРОЛЯ
--------
1.
Что происходит при выполнении инструкции
2.
В чем состоит назначение инструкции
З. Если цикл включает инструкцию
break в теле цикла?
continue?
switch, то
приведет ли к завершению
этого цикла инструкция break, принад.71ежащая инструкции sw i tch?·
н~iрЗ ~ срр -1, Этот проект завершает построение справочной системы С++ .
. _-_._- ..! Настоящая версия пополняется описанием синтаксиса инструк­
ций
break, continue
и
goto.
Здесь пользователь может делать запрос уже не
только по одной инструкции. Эта возможность реализована путем использова­
ния внешнего цикла, который работает до тех пор. пока пользователь не введет
для выхода из программы команду меню
q.
Последовательность действий
1.
2.
Скопируйте файл
Help2 . срр
в новый файл и назовите его НеlрЗ. срр.
Заключите весь программный код в бесконечный цикл
выход из этого цикла с помощью ИНСТРУ1ЩИИ
телем команды меню
q.
break
for.
Обеспечые
при вводе пользова­
Прерывание ЭТО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.
команды меню принимается вариант
4.
Расширьте инструкцию
по инструкциям
саэе
swi tch, включив реализацию
break, continue и goto.
саэе
вывода синтаксиса
'6':
cout «
cout «
break;
case '7':
cout «
cout «
break;
"Инструкция
break:\n\n"i
"break;\n";
"Инструкция continue:\n\n";
"continue;\n";
~
'8':
cout «
cout «
break;
5.
Кроме того, 8 качестве допустимой
q.
I
"Инструкция
goto:\n\n";
"goto MeTKai\n"i
Вот как выглядит полный текст программы
u
1
Help3. срр.
5
/*
Проект
3.3.
Окончательная
. позволяет
версия
справочной
обрабатывать
*/
#include <iostream>
using namespace stdi
несколько
системы С++,
запросов
которая
пользователя.
I
1
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 «
cout «
cout «
break;
сазе '4':
cout «
cout «
break;
case '5':
cout «
cout «
cout «
cout «
break;
сазе '6':
cout «
cout «
break;
сазе '7':
cout «
cout «
break;
case '8':
cout «
cout «
break;
"Инструкция
for:\n\n";
"fоr(инициализация;
"
условие;
инкремент)";
инструкция;\п";
"Инструкция
while:\n\n";
"whilе(условие)
"Инструкция
инструкция;\п";
do-while:\n\n";
"do {\n";
" инструкция;\п";
"} while (условие);\п";
"Инструкция
break:\n\n";
"break;\n";
"Инструкция
continue:\n\n";
"continue;\n";
"Инструкция
goto:\n\n";
~goto MeTKa;\n";
~
]1
I
u
cout «
'3:
"\n";
О
:о:
§
8.
с
u
return
6.
О;
Вот как выглядит один ИЗ возможных вариантов выполнения этой про­
с
!r
граммы.
Справка
I
:о:
CD
по
1. if
2. switch
з. for
инструкциям:
1
150
МОДУЛЬ З. ИНСТРУКЦИИ управления
4. while
5. do-while
б. break
7. continue
8. goto
Выберите
вариант
Инструкция
if(условие)
e1se
справки
(q
для
выхода):
1
(q
для
выхода):
б
(q
для
выхода):
q
if:
инструкция;
инструкция;
Справка
по
инструкциям:
1. if
2. switch
З. for
4. whi1e
5. do-while
б. break
7. continue
8. goto
Выберите
вариант
Инструкция
справки
break:
break;
Справка
по
инструкциям:
1. if
2. switch
З. for
4. while
5. do-while
б. break
7. continue
8. goto
Выберите
вариант
справки
С++: РУКОВОДСТВО ДМI начинающих
151
ВАЖНОI
G:
:s:
Как было продемонстрировано на примере одной из предыдущих программ,
один цикл можно вложить в другой. Вложенные циклы используются для реше­
ния задач самого разного профиля и часто используются в программировании.
Например, в следующей программе вложенный цикл
жители для чисел в диапазоне от
for
позволяет найти мно­
2 до 100.
множителей
вложенных
чисел
в
циклов
диапазоне
от
для
2
отыскания
до
100
*/
#include <iostream>
using namespace std;
int main ()
for(int i=2; i <= 100; i++)
cout « "Множители числа" «
for(int j = 2; j < i; j++)
if«i%j) == О) cout « j «
cout «
return
i «
". ";
" ";
"\n";
О;
Вот как выглядит часть результатов, генерируемых этой программоЙ.
2:
Множители
числа
Множители
числа
З:
Множители
числа
Множители
числа
Множители
числа
4: 2
5:
6: 2 3
Множители
числа
Множители
числа
Множители
числа
7:
8: 2 4
9: 3
<
ID
8.
~
:s:
~
~u
/*
Использование
:J:
ф
:J:
s:
Модуль З. ИНСТРУf<ЦИИ управления
152
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
Множители
числа
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
2 5
2 3 4 6
2 7
3 5
2 4 8
2 3 6 9
2 4 5 10
В этой программе внешн ий ~икл изменяет свою управляющую переменную
от
2 до 100.
мен ной
менной
Во внутреннем цикле тестируются все числа от
2 до
i
значения пере­
i и выводятся те из них, на которые без остатка делится значение пере­
i.
ВАЖНОI
",.• 14WCTPVK' ,ИЯ got-o
Инструкция
goto -
это С++-инструкция безусловного перехода. При ее вы­
полнении управление программой передается и НСТРУIЩИИ, указанной с помощью
метки. Долгие годы эта инструкция находилась в немилости у программистов,
поскольку способствовала, с их точки зрения, созданию "спагетти-кода". Однако
инструкция
goto
по-прежнему используется, 11 иногда даже очень эффективно.
В этой книге не делается попытка "реабилитаиии" законных прав этой инструк­
ции в качестве одной из форм управления программой. Более того, необходимо
отметить, что в любой ситуации (о области программирования) можно обойтись
без инструкции
goto,
поскольку она не является элементом, обеспечивающим
полноту описания языка программирования. Вместе с тем, в определенных ситу­
ациях ее использование может быть очень полезным. В этой книге было решено
ограничить использование инструкции
goto
рамками этого раздела, так как, по
мнению большинства программистов, она вносит в программу лишь беспорядок
и делает ее практически нечитабелыюЙ. Но, поскольку использование инструк­
иии
goto
в некоторых случаях может сделать намерение программиста яснее, ей
стоит уделить некоторое внимание.
Инструкция
goto требует наличия
в программе метки. Метка
-
это действи­
тельный в С-Н- идентификатор, за которым поставлено двоеточие. При выполне­
нии инструкции
goto
управление программой передается инструкции, указан­
ной с помощью метки. Метка должна находиться D одной функции С инструк-
С++: руководство ANil ночи'ноющих
цией
goto,
goto
и метки можно организовать следующий цикл на
х
=
153
которая ссылается на эту метку. Например, с помощью инструкции
100 итераций.
1;
loopl:
х++;
if(x < 100) goto loop1; //
//
Иногда инструкцию
goto
8wпопнение проrpaииw будет
ПРОД0П89но с метки
loopl.
стоит использовать для выхода из глубоко вложен­
ных инструкций цикла. Рассмотрим следующий фрагмент кода.
for ( ... ) {
for ( ... )
while ( ... )
if( ... ) goto stop;
stop:
cout «
"Ошибка
в
программе.\п";
Чтобы заменить инструкцию
goto,
пришлось бы выполнить ряд дополнитель­
ных про верок. В данном случае инструкция
goto существенно упрощает про­
break здесь не обошлось, по­
скольку она обеспечила бы выход лишь из наиболее глубоко вложенного цикла.
граммный код. Простым Ilрименением инструкции
. ..1еС Т А 4 Я C auOKOHrp04S
1.
ПО МОАУ'Ю 3
Напишите программу, которая считывает с клавиатуры символы до тех
пор, пока не будет введен символ "$". Организуйте в программе подсчет
количества введенных точек. Результаты подсчета должны выводиться по
окончании выполнения программы.
2.
Может ли кодовая последовательность одной саsе-ветви в инструкции
swi tch
переходить в последовательность инструкций следующей
ветви? Поясните свой ответ.
3.
Представьте общий формат if-еlsе-if-"лестницы".
4.
Рассмотрите следующий фрагмент кода.
case-
154
МОДУЛЬ З. Инструкции управления
if(x < 10)
i f (у > 100) (;
if (!done) х := Z;
else у = Z;
else cout «
"Ошибка!":
С 'какой if-инструкцией,
//
//
СВЯЗdна
эта
еlsе-ветвь?
Вопрос: с какой if-инструкцией связана последняя еlsе-ветвь?
5.
Напишите fоr-инструкцию для цикла, который считает от
rOM
6.
1000 до О с ша­
-2.
Допустим ли следующий фрагмент кода?
for(int i =
sum += i;
count
О;
i <
пит;
i++)
= i;
7.
Поясните назначение инструкции
8.
Что будет отображено на экране после выполнения инструкции
break.
break
в
следующем фрагменте кода?
for(i = О; i < 10; i++)
while (running) {
if (х < у) break;
//
...
cout «
cout «
9.
"После
инструкции whi~e.\n";
"После инструкции
for.\Il";
Что будет выведено на экран при выполнении следующего фрагмента кода?
for(int i = О; i < 10; i++) {
cout « i « " ";
if(! (1%2» continue;
cout « "\п";
10. Инкрементное
выражение в цикле
for
не обязательно должно изменять
управляющую переменную цикла на фиксированную величину. Псременная
цикла может изменяться произвольным образом. С учетом этого замечания
напишите программу, которая использует ЦИКЛ
отобразить прогрессию
1.2,4,8. 16,32 и Т.Д,
for, чтобы сгенерировать и
С++: руководство ДЛfil начинающих
11.
Код строчных букв
ASCII
отличается от кода прописных на
32.
155
Таким об­
разом, чтобы преобразовать строчную букву в прописную, необходимо вы­
честь из ее кода число
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.
4.7.
Многомерные массивы
Строки
Некоторые библиотечные функции обработки строк
ИIIИЦИ<U1И:J<lЦИЯ массивов
Массивы строк
4.8.
4.9.
Укааатсли
4.10.
4.11.
ИСI10JII)ЗО8aIlие указателей в выражениях
Операторы, используемые с УКa:Jателями
Указатели и массивы
4.12. Многоуровневая lIепрямая адресаl~ИЯ
158
МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели
в этом модуле мы рассматриваем массивы, строки и указатели. Эти темы
могут показаться совсем не связанными одна с другой, но это только на первый
взгляд. В С++ они тесно переплетены между собой, и, что важнее всего, если хо­
рошо разобраться в одиой из них, у вас не будет проблем и с остальными.
Массив .(апау)
-
это коллекция переменных одинакового типа, обращение
к которым происходит с применением общего для всех имени. В С++ массивы
могут быть одно- или мно,'омерными, хотя в основном используются одномер­
ные массивы. Массивы представJ1ЯЮТ собой удобное средство создания списков
(группирования) связанных переменных.
Чаще всего в "повседневном" программировании используются символьные
массивы, в которых хранятся строки. как упом:иналось выше, в С++ не опреде­
лен встроенный тип данных для хранения строк. Поэтому строки реализуются
как массивы символов. Такой подход к реализации строк дает С++-программи­
сту больше "рычагов" управления по сравнению с теми языками, в которых 11С­
пользуется отдельный строковый rnп данных.
Указатель
-
это объект, который хранит адрес памяти. Обычно указатель ис­
пользуется для доступа к значению другого объекта. Чаще всего таким объектом
является массив. В действительности указатели и массивы связаны друг с другом
сильнее, чем можно было бы ожидать.
ВАЖНОI
Одномерный массив
-
это список связанных переменных. Такие СПИСки до­
вольно популярны в программировании. Например, одномерный массив можно
использовать для хранения учетных номеров активных пользователей сети или
результатов игры любимой бейсбольной КОМ'шды. Если потом "ужно усреднить
какие-то данные (содержащиеся в массиве), 'Го это очень удобно сделать по­
средством обработки значений массива. Одним словом, массивы
-
незаменимое
средство современного программирования.
Для объявления одномерного массива ИСПОЛl>зуется следующая форма записи.
тип имя_массива[размерJ;
Здесь с помощью элемента записи тип объявляется базовый тип массива. Базо­
вый тип определяет тип данных каждого элемента, составляющего массив. Коли­
чество элементов, которые будут храниться в массиве, определяется элементом
размер. Например, при выполнении приведеиной ниже инструкции объявляет­
ся iпt-массив (состоящий из10 элементов) с именем
int sample[lOJ;
sample.
С++: РУКОВОДСТВО для начинающих
159
доступ к отдельному элементу массива осуществляется с помощью индекса.
Индекс описывает позицию элемента внутри массива. В С++ первый элемент мас­
сива имеет нулевой индекс. Поскольку массив
sample
содержит
10
элементов,
его индексы изменяютси 01'0 до 9. Чтобы получить доступ к элементу массива по
индексу. достаточно указать нужный номер элемента в квадратных скобках. Так,
первым элементом массива
ple [9).
sample
является
sample l О) • а
последним
Например, следующая программа помещает в массив
sample
-
заm­
числа от
Од09.
#iлсludе
usiлg
iлt
<iostream>
лаmеsрасе std;
mаiл
()
(
iлt
iлt
sample[10); //
//
t;
Эта инструкция резервирует область
памяти для
10
// Помещаем в массив значения.
for(t=O; t<10; ++t) 888ple[t]
элементов типа iлt.
ti
//
//
на
/ / Обратите внимание
то,
массив
// Отображаем содержимое массива.
for(t=Qi t<10i ++t)
cout « "Это элемент sample[" «
t «
как индексируется
8ample.
"]:
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[З]
2
з
nums[4]
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]
nums[l)
nums[2)
10;
18;
75;
пums[З)
О;
nums[4]
1;
минимального
эначенИЙ.
С++: руководство ДЛЯ начинающих
nums[S]
nums[6]
nums[7]
nums[8]
nums [9]
56;
100;
12;
-19;
88;
// Вычисление среднего значения.
avg = О;
for(i=O; i<10; i++)
avg += nums[i); // +- Суммируем
avg /= 10; /1 +co.ut «
161
"Среднее
звачеиих иаССИ8а
numa.
Вwчиcn_еи среднее значение.
значение равно
" «
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 «
cout «
return
"Минимальное
"Максимальное
значение:
значение:
.. «
"«
min val « '\n';
mах val «
'\п';
О;
При выполнении эта программа генерирует такие результаты.
34
Минимальное значение: -19
Максимальное значение: 100
Среднее
значение
равно
Обратите внимание на то, как в программе опрашиваются элементы массива
nums. Тот факт, что обрабатываемые здесь значения сохранены
в массиве, значи­
тельно упрощает процесс определения среднего, минимального и максимального
значений. Управляющая переменная цикла
for
используется в качестве индекса
массива при доступе·к очередному его элементу. Подобные циклы очень часто
применяются при работе с массивами.
162
МОДУЛЬ 4. Массивы, строки и УI<аэатели
Используя массивы, необходимо иметь в виду одно очень важное ограни­
чение. В С++ иельзя присвоить один массив другому. В следующем фрагменте
кода, например, присваивание а
int a[lO),
Ь[10]
=
Ь i недопустимо.
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
В данном случае цикл
сив
crash
for
выполнит
100
итераций, liесмотря на то, что мас­
предназначен для хранения лишь десяти элементов. При выполнении
этой программы возможна перезапись важной информации, что может привести
к аварийному останову программы.
Если "нарушение границы" массива происходит при выполнении инструкции
присваивания, могут быть изменены значения 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 «
return
массива
два
'\n';
О;
в этом примере элемент
[ 1] - значение
2,
numS [О] [О] получит значение 1, элемент nums I О]
n uтз [ О] [2] - значение 3 и т.д. Значение элемента
числу 12. Схематически этот массив можно предста­
элемент
nums [2] [3] будет равно
вить следующим образом.
о
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 байт.
А если каждую размерность увеличить в
то занимаемая массивом Ш:l.мяТl, возрастет до
21 600 000
10
раз,
байт. Как видите,
большие многомерные массивы способны "съесть" большой объем памяти, а
программа, которая их использует, может очень быстро столкнуться с пробле­
мой нехватки памяти.
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
I Поскольку одномерный
BUbble. ~~p
L
массив обеспечивает организацию дан­
ных в виде индексируемого линейного списка, то он представляет
собой просто идеальную структуру ланных для сортировки. В этом проекте вы по­
знакомитесь с одним из са\1ЫХ простых способоfl сортировки массивов. Существует
много различных алгоритмов сортировки. Широко применяется, например, сорти­
ровка перемешиванием и сортировка методом Шелла. Известен также алгоритм t)wстрой сортировки. Однако самым пpocrым счит ..ется алгоритм сортировки
ny3bl,CJb-
КOбbI.М методо,м. Несмотря на то что пузырьковая сортировка не отличается высокой
эффективностью (его производительность неприемлема для больших массивов), его
вполне успешно можно применять для сортировки массивов малого размера.
Последовательность действий
1.
2.
Создайте файл с именем ВиЬЫе. срр.
Алгоритм сортировки пузырьковым методом получил свое название от CI1O-
соба, используемого для упорядочивания элементов массива. Здесь выпол­
няются повторяющиеся операции сравнения и при необходимости меняются
местами смежные элементы. При этом элементы с меньшими значениями по­
степеllНО перемещаются к одному концу мзссива, а злсменты с большими зна­
чениями
1.
-
к дрyroму. этот npoпесс напОМIIНает поведение пузырьков воздуха
Верно: в С++ каждая размерность многомерного массива заключается в собствен­
ную пару квадратных скобок.
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.
Во внутреннем цикле организовано сравнение двух смежных элементов
массива. Если окажется, что они располагаются "не по порядку", т.е. не по
возрастанию, то их следуст поменять местами. При каждом проходе вну­
треннего цикла самый маленький элемеит помещается на "свое законное"
место. Внешний цикл обеспечивает повторение этого процесса до тех пор,
пока не будет отсортирован весь массив.
3.
Вот полный текст программы ВuЬЫе. срр.
/*
Проект
4.1.
Демонстрация
(ВuЬЫе
метода
sort).
*/
#include <iostre~m>
'include <cstdlib>
using namespace std;
int main ()
пузырьковой
сортировки
I
I
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]
return
" ,.
«
I
';
О;
Результаты выолнения этой программы таковы.
Исходный массив:
41 18467 6334 26500 19169 15724 11478 29358 26962 24464
Отсортированный массив:
41 6334 11478 15724 18467 19169 24464 26500 26962 29358
С++: руководство дl\t;J начинающих 169
.................................................................................................................................
4.
ХОТЯ алгоритм пузырьковой сортировки пригоден ДЛЯ не60ЛЬШи.х масси­
вов, для массивов большого размера он становится неэффективным. Более
универсальным считается алгоритм
Quicksort.
Однако этот алгоритм ис­
пользует средства С++, которые мы еще не изучили. Кроме того, в стан­
дартную библиотеку С++ включена Функция
qsort (), которая реализует
одну из версий этого алгоритма. Но, прежде чем использовать ее, вам не­
обходимо изучить больше средств С++.
I
!
1')
~
>:s:
:s:
~
о
е-
u
]j
ВАЖНОI
111
:s:
u
u
Чаще всего одномерные массивы используются для создания символьных строк.
В С++ поддерживается два типа строк. Первый (и наиболее популярный) пред­
полагdет, что строка определяется как символьный массив, который 3аВершается
нулевым символом
( , \ О ').
Таким образом. строка с завершающим нулем состоит
из символов и конечного нуль-символа. Такие строки широко используются в про­
граммировании, поскольку они позволяют программисту выполнять самые разные
операции над строками и тем самым обеспечивают высокий уровень эффективности
программирования. Поэтому, используя термин cmpoкa, С++-программист обычно
имеет в ВИДУ именно (."троку с завершающим нулем. Однако существует и ДРУГОЙ
тип представлеl"IИЯ строк в С++. Он заключается в применении объектов класса
string, который является частью библиотеки классов С++. Таким образом, класс
string - не встроенный тип. Он подразумевает объектно-ориентироВанный под­
ход к обработке строк, 110 используется не так IlIИJЮКО, как строки с завершающим
нулем. В этом разделе мы рассматриваем строки с завершающим нулем.
ОСНОВЫ преАставления СТРОК
Объявляя символьный массив, предназначенный для хранения строки с за­
вершающим нулем, необходимо учитывать признак ее завершения и задавать
длину массива на единицу больше длины самой большой строки из тех, которые
предполагается хранить в этом массиве. Например, при объявлении массива
s tr,
в который предполагается поместить 10-символьную строку, следует использо­
вать следующую инструкцию.
char str[l1];
Заданный здесь размер
вола в конце строки.
(11)
позволяет зарезервировать место для нулевого сим­
.0
:~
170 Модуль 4. Массивы, СТРОI(И и УI<азатели
................................................................................................................................
Как упоминалось выше в этой книге, С++ позволяет определять строковые
константы (литералы). Вспомним, что cmpOKoвblй лumepШl- это список симво­
лов, заключенный в двойные кавычки. Вот несколько примеров.
"Мне нравится с++"
"Привет"
Строка, приведенная последней
"Mars"
11.,
(" "), называется "улевОЙ. Она состоит только
из одного нулевого символа (признака завершения строки). Нулевые строки ис­
пользуются для представления пустых строк.
Вам не нужно вручную добавлять в конец строковых констант нулевые симво­
лы. С++-компилятор делает это автоматически. Следовательно, строка
"Mars"
в
памяти размещается так, как показано на этом рисунке:
м
r
8
s
о
I
Считывание строк с клавиатуры
Проще всего считать строку с клавиатуры, создав массив, который примет эту
строку с помощью инструкции
cin.
Считывание строки, введенной пользовате­
лем с клавиатуры, отображено в следующей программе.
Использование сiп-инструкции для считывания строки
11
11
с клавиатуры.
#include <iostream>
using namespace stdi
int main ()
{
char str[80]i
cout «
сi n
"Введите
>>
5t r ;
cout «
cout «
rеturп
11 +11
"Вот
ваша
строку:
";
СЧИ'nlВаек с'2!рОКУ с JUliUtиаlJ!УРIo1
с ПОМО~ инс'2!рУКЦИИ cin.
строка:
";
stri
О;
Вот возможный результат выполнения этой программы.
Введите
Вот
ваша
строку:
строка:
Тестирование
Тестирование
С++: PYI<OBOACTBO ANil начинающих
171
Несмотря на то что эта программа формально корректна, она не лишена недо­
статков. Рассмотрим следующий результат ее выполнения.
Введите
Вот
строку:
ваша
строка:
Это
проверка
Это
Как видите, при выводе строки, введенной с клавиатуры, программа отображает
только слово "Это", а не всю строку. Дело в том, что оператор
"»" (в инструкции
прекращает считывание строки, как только встречает символ пробела, табу­
cin)
ляции или новой строки (будем называть эти символы nробельным) •.
Для решения этой проблемы можно использовать еще одну библиотечную
функцию
gets () . Общий формат ее
вызова таков.
gеts(имя_массива);
Если в программе необходимо считать строку с клавиатуры, вызовите функ­
цию
gets () , а D качестве аргумента
передайте имя массива, не указывая индек­
са. После выполнения этой функции заданный массив будет содержать текст,
введенный с клавиатуры. Функция
gets ()
считывает вводимые пользователем
символы до тех пор, пока он не нажмет клавишу
<Enter>. Для вызова
gets () в программу необходимо включить заголовок <cstdio>.
функции
В следующей версии предыдущей программы демонстрируется использова­
ние функции
gets (),
которая позволяет ввести в массив строку символов, со­
держащую пробелы.
11
1/
Использование
с
функции
gets()
для
считывания
строки
клавиатуры.
#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;
I
172 МОДУЛЬ 4, Массивы, строки и УI<азатели
.................................................................................................................................
return
О;
На этот раз после запуска новой версии программы не! 8ыполнеtlие и ввода с
клавиатуры текста "Это простой тест" строка считывается полностью, а затем 1ак
же полностью и отображается.
Введите
Вот
ваша
строку:
Это
строка:
простой тест
Это простой тест
В этой программе следует обратить внимание на следующую инструкцию.
cout «
str;
Здесь (вместо привычного литерала) используется имя строкового массива. Эа­
помните: имя символьного массива, который содержит строку, можно испоЛ1,:Ю­
вать везде, где допустимо применение строкового литерала.
При этом имейте в DИДУ, что ни оператор
gets ()
"»"
(В инструкции
cin), нифункщ{я
не выполняют граничной проверки (на отсутствие нарушения границ
массива). Поэтому, если пользователь введет строку, длина которой преВЫШilСТ
размер массива, возможны неприятности, о которых упоминалось выше. Из
cl\a-
занного следует, что оба описанных здесь варианта считьшания строк с клавиату­
ры потенциально опасны. Однако ПOCJIе подробного рассмотрения С++-возмож­
ностей ввода-вывода мы узнаем способы, позволяющие обойти эту проблему.
~ПРОСЫ ДЛЯ текущего КОНТРОМ
1.
2.
Что представляет собой
--------
CTpOl<a с завершающим нулем?
Какой размер должен иметь символьный массив, предназначенный для
хранения строки, состоящей из
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щия
s2 не
strcat ()
присоеДИlJяет строку
s2 к
концу строки з1; при этом строка
изменяется. Обе строки должны завершаться нулевым символом. Резуль­
тат 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 «
cout «
"Длины
I
I
«
мощный
" «
строк:
5trlen
(з2)
if(!strcrnp(sl, 52»
cout « "Эти строки
язык.");
5tr1en(sl);
«
I
\п
1;
равны.\п";
ФУНКЦИЙ.
С++: Pyt<OBOACTBO ДЛЯ начинающих
else cout «
175
"Эти строки не равны.\п":
5trcat(51, 52):
cout « 51 « '\п':
5trcpY(52, 51):
cout « 51 « "
и
n
if(!5trcmp(51, 52»
cout « "Строки 51
return
«
52 «
и
52
"\п":
теперь
одинаковы.\п":
О:
ВОТ как выглядит результат выполнения этой программы.
Длины строк:
Эти
строки
С++
-
С++
Строки
3 19
не
равны.
это мощный
язык.
ЭТО мощный
ЯЗЫК.
51
и
52
теперь
и С++
-
это
мощный
ЯЗЫК.
одинаковы.
Использование признака завершения строки
Факт завершения нулевыми символами всех С++-строк можно использовать
для упрощения различных операций над ними. Следующий пример позволяет
убедиться в ТОМ, насколько простой код требуется для замены всех символов
строки их прописными эквивалентами.
// Преобразование символов строки
// в их прописные эквиваленты.
#include <io5tream>
#include <c5tring>
#inc1ude <cctype>
u5ing namespace 5td:
int main ()
char 5tr[BO]:
int i:
5trcpY(5tr, "abcdefg");
Модуль 4. Массивы, строки и указатели
176
/ / Э'l'О'r цmtn
// co"ep.a'1'lo
J.
//
saвe~CR,
признак
Kor"a
:шекек'r
заверше_
s tr [i]
бу"е'r
сo:rpоlCИ.
for(i=Oi str[i]i i++) str[i] = toupper(str[i])i
cout «
return
str;
О;
Эта проrpамма генерирует такой результат.
ABCDEFG
Здесь используется библиотечная функция
t.oupper (), которая возвращает
прописной эквивалент своего символьного аргумента. Для вызова функции t)-
upper ()
необходимо включить в проrpамму заголовок
<cctype>.
Обратите внимание на то, что в качестве УСЛQf:ИЯ завершения цикла
зуется массив
s tr,
индексируемый управляющей переменноil
for ИСПО."IЬ­
i (str [i ) ). Такой
способ управления циклuм вполне приемлем, IJOCKOJ1bKY за истинное значение в
С++ прюrимается любое ненулевое значение. Вспомните, что все печатные СЩf[-:Q­
лы представляются значениями, не равными нулю, Ii только символ, завершаЮIlНfЙ
строку, равен нулю. Следовательно, этот цикл работает до тех пор, пою\ Иlщекс не
укажет на нулевой признак конца строки, Т.е. пока значение
str [i )
не станет ну­
левым. Поскольку нулевой символ отмечает конец строки, цикл останавлинаеl::Я
в точности там, где нужно. В дальнеiiшем вы УВИ.НJТе множество примеров, в КО10ph1X нулевой признак КOllЦа строки используется подобным образом.
~просы ДЛЯ текущего I<ОНТРОЛЯ
1.
2.
Какое действие выполняет функция
~TO возвращает функция
strcmp ()
---------
strca t () ?
при сравнении двух эквивалентных
строк?
•
.........................................................
--.......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Х фуюсций обработки символов. Например, ФУНКЦИЮ
toupper ()
дополняет ФУНКЦИЯ
tolower (),
КОТQРая 80ЗВращает строч­
ный эквивалент своего символьного арryмеlПа. Например, perncтp буквы
(т.е. ПРОШIСНая она ИЮI строчная) можно определить с ПОМОЩЬЮ функции
isupper (),
которая возвра.щпет значение ИСТИНА, если исследуемая
буква является прописной, и функции
islower (),
которая возвращает
значение ИСТИНА, если исследуемая буква является строчной. Часто
используются такие функции, как
и
ispunct (),
isalpha (), isdigi t (), isspace ()
которые ПРИlшмаlOТ символьный аргумент и определяют,
принадлежит ли он к соответствующей категории. Например, функция
isalpha ()
во:шращает значение ИСТИНА, если ее аргумеlПОМ является
буква алфавита.
ВАЖНОI
а. ИНI4ЦI4ШИ3С1ЦkU1.,.м,QCСИВОВ
в С++ предусм()трена возможность инициализации массивов. Формат ини­
циализании массивов подобен формату ининиализации других переменных.
тип имя_массива [раэмер)
=
{список_значений};
Здесь элемент список_ эна чений представляет собой список значений ишщиа­
ЛИ:l3ЦИИ элемеНТОIJ массива, разделенных запятыми. Тип каждого значения ини­
циализаЩIII должен быть совместим с базовым типом массива (элементом тип).
Первое значение инициализации будет сохранено в первой позиции масс~ша,
IJторое значение
-
во второй 11 т.Д. ОбраТlIте внимание на то, что точка с запятоii
ставится после закрывающей фюурной скобки
( ) ).
Например, в следующем примере 10-элементнblЙ целочисленный массив ини­
циализируется числаМJI от
int i[lO)
=
1 до 10. '
{l, 2, 3, 4, 5,
б,
7, 8, 9, lO}i
После вьшолнения этой инструкции элемент
мент
i [9) -
значение
i
[О) получит значение
1, а эле­
10.
Для символьных маССИRОП, предназначенных для хранеllИЯ строк, предусмо­
трен сокращенный вариант Iшициализации, который имеет такую форму.
char
имя_массива[размер)
~
=
"строка";
м
~S
S
:..:
О
а.
u
iCII
s
u
u
о
~
178
МОДУЛЬ 4. Массивы, строки и указатели
Например, CJlедующий фрагмент кода инициализирует массив
str
фра.'ЮЙ
"привет".
char str[7] =
"привет";
Это равнозначно поэлементной инициализации.
char str[-7] =
{'п',
'р',
'и',
'в',
'е',
'т',
'\О'};
Поскольку в С++ строки должны завершаться нулевым символом, у6едитесь,
<по при объявлении массива его размер указан с учетом признака конца. Именно
поэтому в предыдущем примере массив
str
объявлен как 7-элемевтный, несмо­
тря на то, что в слове "привет" только шесть букв. При использовании строкового
литерала компилятор добавляет нулевой признак конЦа строки автоматически.
Многомерные массивы инициализируются по аналогии с одномерными. На­
пример, в следующем фрагменте программы массив
числами от
int
1,
2,
3.,
4,
5,
6,
sqrs
инициализируется
1 до 10 и квадратами этих чисел.
sqrs [10] [2] = {
1,
4,
9,
16,
25,
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,
{ 5,
{ 6,
{7,
16} ,
25} ,
36} ,
49} ,
180
Модуль 4. Массивы, СТРОI<И и УI<азатели
(8, 64},
{9, 81},
{10, 100}
};
int main ()
int i, j;
cout « "Введите
cin » i;
число
от
// Поиск значения i.
for(j=O; j<10; j++)
if(sqrs[j] [O]==i) break;
cout « "Квадрат числа" «
cout « sqrs[j] [1];
return
1
до
i «
10: ";
"
равен
";
О;
Вот один из возможных результаты ВЫПOJшения этой I1poгpaMMы.
Введите
число
от
Квадрат
числа
4
1
до
равен
10: 4
16
Инициализация "безразмерных" массивов
Объявляя инициализированный массив, можно позволить С++ автоматиче­
СЮi определять его длину. Для этого не нужно задавать размер массива. Компи­
лятор в этом случае определит его путем ПОl\счета количества инициализаторов,
после чего создаст массив, размер которого будет достаточным для хранения всех
значений инициализаторов. Например, при вы полнении этой инструкции
int nums[] = { 1, 2,
З,
4 };
будет создан 4-элементный массив
скольку здесь размер массива
nums, соД(·ржащиЙ значеШiЯ 1,2, 3 и 4. По­
nums не задан явным образом, его можно назвать
"безразмерным" .
Массивы "безразмерного" формата иногда бывают весьма полезными. Напри­
мер, предположим, что мы используем следующий вариант инициализации мас­
сивов для построения таблицы !пtегпеt-адресов.
С++: РУI<ОВQДСТВО Mt;I начинающих
char
char
char
е1[16]
181
"www.osborn.com";
"www.weather.com";
"www.amazon.com";
е2[16]
е3[15]
НетрудtlО предположить, что вручную неудобно подсчитывать символы в каж­
дом сообщении, чтобы определить корректный размер массива (при этом всегда
можно допустить ошибку в подсчетах). Но поскольку в С++ предусмотрена 80Э­
можность автоматического определения длины массивов путем испольэования
их "безразмерного" формата, то предыдущий вариант инициализации массивов
для построения таблицы Iпtегпеt-адресов можно переписать так.
char
char
char
е1[]
е2[]
е3
[]
"www.osborn.com";
"www.weather.com";
"www.amazon.com";
Помимо удобства в первоначалъном оnpеJ(елении массивов, метод "безраз­
мерной" инициализации позволяет изменить любое сообщение без пересчета его
длины. Тем самым устраняется возможность внесения ошибок, вызванных слу­
чайным просчетом.
"Безразмерная" инициализация не ограничивается одномерными массивами.
При инициализации многомерных массивов вам необходимо указать все данные,
за исключением крайней слева размерности, чтобы С++-компилятор мог долж­
ным образом индексировать массив. Используя "безразмерную" ииициализацию
массивов, можно создавать таблицы различной длины, позволяя компилятору
автоматически выделять область памяти, достаточную для их хранения.
В следующем примере массив
int sqrs[] [2]
1, 1,
2, 4,
3, 9,
4, 16,
=
sqrs объявляется как "безразмерный".
(
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 « "Телефонный номер: " «
break;
10) cout «
i f (i
return
"Отсутствует
numbers[i+1] «
"\п";
в каталоге.\п";
О;
Вот пример выполнения программы.
Введите
имя:
Телефонный
Джон
номер:
555-1037
Обратите внимание на то, как при каждом проходе цикла
переменная i инкрементируется на
2.
for
управляющая
Такой шаг инкремента объясняется тем,
что имена и телефонные номера чередуются в массиве.
ВАЖНОI
Указатели
-
один из самых важных и сложных аспектов С++. Вместе с тем
они зачастую являются источниками потенциальных пр06лем. В значительной
степени мощь многих средств С++ определяется использованием указателей. На­
пример, благодаря им обеспечивается поддержка связных списков и механизма
динамического вьщеления памяти, и имен~о они позволяют функциям изменять
содержимое своих аргументов. Однако об этом мы поroворим в последующих мо­
дулях, а пока (т.е. в этом модуле) мы рассмотрим основы применения указателей
и покажем, как с ними обращаться.
Модуль 4. Массивы, СТРОI(И и указатели
184
~просы ДЛ',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ые значения
с плавающей точкой типа
double -
- четыре. значения
floa t - четыре, а значения с плавающей точкой пша
восемь (эти 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.
total.
Выполнение этой инструкции uuкак не повлияло на значение переменной
Назначение оператора" &" можно "перевести" на русский язык как "адрес
переменной", перед которой он стоит. Следовательно, приведенную выше инструк­
цию присваивания можно выразить так: "перемеllная
ptr
получает адрес пере­
менной
total". Чтобы лучше понять суть этого присваивания, предположим, что
total расположена в области памяти с адресом 100. Следовательно,
после выполнения этой инструкции переменная ptr получит значение 100.
Второй оператор работы с указателями (*) служит дополнением к первому
(&). Это также унарный оператор, но он обращается к значению переменной, рас­
переменная
положенной по адресу, заданиому его операндом. Другими словами, он ссылается
на значение переменной, адресуемой заданным указателем. Если (продолжая ра­
боту с предыдущей инструкцией присваивания) перемеНl:lая
переменной
total, то при
ptr содержит адрес
выполнении инструкции
val = *ptri
переменной
val
будет присвоено значение переменной
зывает переменная
ptr.
total, на которую ука­
total содержит значение
инструкции переменная val будет содержать
Например, если переменная
3200, после выполнения последней
значение 3200, поскольку это как раз то значение, которое хранится по адресу
100. Назначение оператора "*" можно выразить словосочетанием "по адресу".
В данном случае предыдущую инструкцию можно прочитать так: "переменная
val
получает значение (расположенное) по адресу
ptr".
I
186
Модуль 4. Массивы, СТРОI<И и YI<аэатели
Последовательность только что описанных операций выполняется в следую­
щей программе.
#include <iostrearn>
using narnespace std;
int rnain ()
{
int total;
int *ptri
int val;
ptr
&totali //
Получаем адрес
*ptr;
Получаем значение,
=
cout «
return
//
//
по
total
присваиваем
=
val
3200; //
Переменной
total
переменной
3200.
total.
хранимое
этому адресу.
"Сумма равна:
"
« val «
'\n';
О;
к сожалению, знак умножения
(*) и оператор со значением "по адресу" обо­
значаются одинаковыми символами "звездочка", что иногда сбивает с то.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 генерирует указатель на
ublе-значение, ар
-
указатель на целочислеиtlый тип
int.
do-
Эти два типа несо­
вместимы, поэтому компилятор отметит эту инструкцию как ошибочную и не
скомпилирует программу.
1.
Указатель - это объект, который содержит адрес памяти некотороro другого 06ъекта.
2. long int *valPtr;
3.
Оператор" * "
позволяет получить значение, хранимое по адресу, заданному
ero
операндом. Онератор \\ &" возвращает адрес объекта. по которому расположен етО
операнд.
'МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели
188
Несмотря на то что, как было заявлено выше, при присваивании два указателя
должны быть совместимы по типу, это серьезное ограничение можно преодолеть
(правда, на свой страх и риск) с помощью операции приведения типов. Напри­
мер, следующий фрагмент кода теперь формально корректен.
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
Как видите, в этой программе переменной р (точнее. указателю на целочис­
ленное значение) присваивается адрес переменной х (которая имеет тип
bl е).
dou-
Следовательно, когда переменной у присваивается значение, адресуемое
указателем р, переменная у получает только 4 байт данных (а не все
ДЛЯ dоublе-значения), поскольку р
-
8, требуемых
указатель на целОЧl1сленный тип iлt. Та­
ким образом, при выполнении соut-Инструкции на экран будет выведено не чис­
ло
123 .23, а,
как говорят программисты, "мусор".
Присваивание значений с ПОМОЩЬЮ указателей
При присваивании значения области памяти, адресуемой указателем, сго
(указатель) можно использовать с левой стороны от оператора присваивания.
Например, при выполнении следующей инструкции (если р -
указатель на це­
лочисленный тип)
*р
= 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
:iS
са
:s:
u
u
·0
j~
Модуль 4. Массивы, СТРОI(И и указатели
190
через ухазатen. р.
//
cout «
(*р)
пит
++;
cout «
пит
(*р)--:
cout «
return
«
/ / t//
«
' ';
Ивхреиев'1'ИрУек значение переке_ой пшn
через ухазатenъ р.
' ':
// t- Дехрекеи'1'ИрУеи значение перекеиной num
//
через указатеnь р.
пит
«
'\n':
О:
Вот такие результаты генерирует эта программа.
100 101 100
ВАЖНОI
1'111 ИСПОАIИQВQМ'48 ука3Qr8 А ей
в выражениях
Указатели можно использовать в большинстве допустимых в С++ выражений,
Но при этом следует при менять некоторые снециальные правила и не забывать,
что некоторые части таких выражений необходимо заключать в круглые скобки,
чтобы гарантированно получить желаемый [)(':'Iультат.
АрИфметические операции над указателями
с указателями можно использовать только четыре арифметических операто­
ра: ++, --, + и
-.
Чтобы лучше понять, что ПРI)ИСХОДИТ при выполнении арифме­
тических действий С указателями, начнем с примера. Пусть
int-переменную с текущим значением
2000 (т.е. pl
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:
только один байт. Но при использовании любого дрyroго типа указателя при ИШЧJe­
ментировании или декрементировании значение переменноli-указателя будет уве­
личиваться или уменьшаться на величину, равную размеру его базового типа.
нием операторов инкремента и декремента. Со значениями ука.lателеЙ можно
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];
:.:
>:s:
:.:
О
...u
Q.
:а
Арифметические операции над указателями не ограничиваются использова­
выполнять операции сложения и вычитания, используя
м
са
s
u
u
о
~
192 Модуль 4. Массивы, СТРОI<И и УI(азатели
................................................................................................................................
double *f,
int Х;
i
j;
f
Ч;
for(x=O:
cout «
ч(10];
х<10:
х++)
i+x «
' , «
«
f+x
'\п':
//
//
Отобр. .аеи адреса,
/ /
CJIo*elUUl зВ&че_ х
,,/ /
return
попучеввые з рез~.та~е
с ха*дюс указатеnеи.
О:
Вот один из позможных резу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.
С каким типом связаны все арифметические действия над указателями?
На какую величину возрастет значение dоublе-указателя при его инкре­
2.
меlпировании, если предположить, что тип
double занимает 8 байт?
3. В каком случае может иметь смысл сравнение двух указателей?·
ВАЖНОI
111' Vказ nТМ l4.14ма ссивы
в С++ указатели и массивы тесно связаны между собой, причем настолько,
что зачастую понятия "указатель" и "массив" взаимозаменяемы. В этом разделе
мы попробуем проследить эту связь. Для начала рассмотрим следующий фраг­
мент программы.
char str [80];
char *рl;
рl
=
Здесь
str;
str
предстапляе·г соБОl1 имя массива, содержащего
УI<а:1атель на ТIШ
char.
80
символов, а рl -
Особы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) с числом
рl указывает на первый элемент массива
4,
ПОСКОЛЬКУ
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\Я начинающих
int i;
char 5tr[80] = "Thi5 I5
cout «
"Исходная
А
строка:
195
Test";
" «
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 «
"Строка
«
return
с инвертированным регистром букв:
"
str;
О;
Результаты выполнения этой программы таковы.
This Is
строка:
Строка
инвертированным регистром
с
А
Test
Исходная
букв:
tHIS iS
а
tEST
Обратите внимание на то, что в программе для определения регистра букв
используются библиотеЧllые функции
i supper ()
isupper ()
и
islower ().
Функция
возвращает значение ИСТИНА, если ее аргумент представляет собой
прописную букву, а функция
islower ()
возвращает значение ИСТИНА, если
ее аргумент представляет собой строчную букву. На каждом проходе цикла
помощью индексирования массива
5t
r
for с
осуществляется доступ к очередному его
элементу, который подвергается проверке регистра с последующим изменением
его на "противоположный". Цикл останавливается при доступе к завершающему
нулю массива
str,
поскольку нуль означает ложное значение.
А вот версия той же программы, пере писанной с использованием арифметики
указателей.
1/
Реверсирование регистра
//
арифметики указателей.
#include <iostrearn>
tinclude <cctype>
using narnespac'e std;
int main ()
букв
с
помощью
196
Модуль 4. Массивы, СТРОI<И и указатели
char *р;
char str[80]
cout «
р
=
"This I5
"Исходная
= str; //
А
строка:
" «
str «
"\п";
Присваиваем указателю р адрес
while (*р)
if (isupper (*р))
*р = tolower(*p);
else if(islower(*p)) //
//
*р
Test";
начала массива.
Дoc~ к массиву
_tr
через
указатeJП..
toupper(*p);
=
р++;
cout «
«
return
"Строка
с
инвертированным
регистром
букв:
"
str;
О;
в этой версии указатель р устанавливается на начало массива
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
cout «
р =
"Исходная
str; //
А
строка:
Test";
" «
return
"\п";
Присваиваем указателю р адрес
// now, index р
for(i = О; p(i); i++) (
if(isupper(p[i]))
p[i] = tolower(p[i)); / /
else if(islower(p[i]))
//
p[i) = toupper(p[i]);
cout «
«
str «
"Строка
с
начала массива.
f - Используем р !Сак еcnи бы
это было имя массива.
инвертированным регистром
букв:
"
str;
О;
При выполнении эта программа создает сhаr-указатель с именем р, а затем
присваивает ему адрес первого элемента массива
str.
В цикле
for
указатель
р индексируется с использованием обычного синтаксиса индексирования мас­
сивов, что а6солюrnо допустимо, поскольку в С++ выражение р
[ i]
по своему
действию идентично выражению'" (р+ 1) . Этот пример прекрасно иллюстрирует
тесную связь Между указателями и массивами.
198
МОДУЛЬ 4. Массивы, СТРОI(И и указатели
~просы МЯ текущего КоНтрОЛS1 _ _ _ _ _ _ __
1.
2.
Можно ли к массиву пол)"lить доступ через указатель?
Можно ли индексировать указатель подобно массиву?
3. Какое назначение имеет само имя массива, использованное без индекса?·
Строковые константы
Возможно, вас удивит способ обработки С++-компиляторами строковых KOIfстант, подобных следующей.
cout «
I
strlеп("С++-компилятор");
Если С++-компилятор обнаруживает строковый литерал, он сохраняет его
в таблице строк программы и генерирует указатель на нужную строку. Поэто­
му следующая программа совершенно корректна и при выполнении RЫВОДИТ на
экран фразу Работа с указателями
-
сплошное удовольствие!.
#include <iostream>
using namespace std;
int main()
char *ptr;
// Указатеn~ ptr присваиваетск ацрес строковой
// константы.
ptr = "Работа с указателями - СПЛО:l1ное удовольствие!\п";
cout «
return
ptr;
О;
1.
Да. к массиву можно получить доступ через указатель.
2.
Да, указатель можно индексировать подобно массиву.
З.
Само имя массива. использованное без индекса, генерирует указатель на первый
элемент этого массива.
С++: руководство ДМ) начинающих
199
Спросим у опытного программиста
Вопрос. Можно Jlи утверждать, что указатели и AlйCCU8Ы вэаWWЗtlAllШRe.AtЫ?
Ответ.
Указатели и массивы очень тесно связаны и во многих случаях вэаимо­
заменяемы. Например, с помощью указателя, который содержит адрес
начала массива, можно получить доступ к элементам этого массива либо
посредсгвом арифметических действий над указателем, либо посредс1'Вt)м
индексирования массива. Однако утверждать, что указатели и массивы
взаимозаменяемыми в общем случае нельзя. Рассмотрим, например, та­
lr:I
u
u
а
~
for (i=O; i<10; i++) {
*num = i; // Здесь
пит++;
все
в
порядке_
// ОШИБКА! Переменную num
//
модифицировать
нельзя.
Здесь используется масClШ целочислеНlIЫХ значений с именем
num.
как от­
мечено в комментарии, несмотря на то, что сопершеllllO приемлемо приме­
n um оператор" *" (который обычно ПРИМСllяется к указателям),
абсолюто недопуcntМо модифицировать значение
num. дело в том, что
это константа, которая указьmает на начало массиnа. И ее, следооа­
телыю, инкреМСН11rронать НlJЮlК НеЛЪ.1Я. Дрyrnмli словами, несмотря на то,
что имя массиna (без индекса) леЙС11ШТелыlO генерирует указатель на начало
массива, его значение IIЗменениlO не подлежlff. Хотя имя массива генерирует
констa.my-указатсль, его, тем не менее, можно (подобно указателям) вклю­
чать в выражения, если, конечно, оно при этом не модифицируется. Напри­
мер, следующая ИНСТРУIЩИЯ, при вьmолнении которой элемеmy пит
присвaиnaется значение
* (num+3)
=
:s:
:s:
int num[10];
int i;
num -
а
5-
:зi
кой фрагмент кода.
ИИ"IЪ к имени
I
I
100; //
//
[ З]
100, вполне допустима.
Здесь
все
поскольку
в
порядке,
num
не
изменяется.
При выполнении этой программы символы, образующие строковую констан­
ту, сохраняются в таблице строк, а персменной
ptr
присваивается указатель H~
соответствующую строку в этой таблице.
Поскольку указатель на таблицу строк конкретной программы при использо­
вании строкового литерала генерируется автоматически, то можно попытаться
использовать этот факт для модификации содержимоro данной таблицы. Однако
такое решение вряд ли можно назвать удачным. Дело в том, ЧТО С++-компиля-
200
МОДУЛЬ 4. Массивы, СТРОI<И и указатели
торы создают оптимизированные таблицы. в которых один строковый литерал
может использоваться
D двух (или больше) ра:JЛИЧНЫХ местах программы. По­
этому "насильственное" изменение строки мож('т вызвать нежелательные побоч­
ные эффекты.
1 Выше отмеча.lЮСЬ, что сравнение указателей имеет смысл,
если
06
:- s·"t-··R"- ..._""-
:
r ev.cpp
----
сравниваемые указатели ссылаются на один и тот же
ъект, на-
пример массив. Допустим, даны два указателя (с именами А и В), которые ссыла­
ются на один и тот же массив. Теперь, когда вы lIонимаете, как могут быть связа­
ны указатели и массивы, можно применить сравнение указателей для упрощения
некоторых алгоритмов. В этом проекте мы рассмотрим один такой пример.
Разрабатываемая здесь программа реверсирует содержимое строки, Т.е. меня­
ет на обратный порядок следования ее СИМВОЛОIl. Но вместо копирования СТРOJ~и
в обратном порядке (от конна к началу) о
Жll:\юе строю!
DHYTPIl
JlI<\СС!ШU,
l<UTopbli'(
ltpyroii
массиu, она реверсирует содер­
ее СОДСРЖIIТ. ДЛЯ реализации Dышеска­
заlllЮГО программа llспользует дnе переменныс типа у«азаТель. Одна указывает
на начало массива (еl"О пероыi1 элемент), а вторая
-
на еl"О конец (его последний
элемент). UИl<Л, используеl\(Ы(~1 в прогрuмме, продолжается до тех пор, пока ука­
затель на начало строки меньше указателя на ее конец. На каждой итерации ЦИК­
ла символы, на
I<OTOPbIC ссылаются
эти указатели, меняются местами, после че,'о
обеспечивается настройка указателей (путем инкрементирования и декрементн­
роваlШЯ) на слеДУЮЩllе СIfМnОЛЫ. Когда указатель на начало строки в результате
сравнения становится больше или
paUIIbIM
указателю на ее конец, строка OI<аЗlt\­
вается инвертированной.
Последовательность действий
1.
2.
Создайте файл с именем StrRev. срр.
Введите в этот файл следующие строки кода.
/*
Проект
4.2.
Реверсирование
содержимого
*/
tinclude <iostream>
tinclude <cstring>
using namespace stdi
строки
"по
месту".
С++: РУКОВОДСТВО дl\Я начинающих
201
int main ()
~
{
char str[]
"это простой
char *start, *end;
int len;
char t;
~
тест";
о
с')
о
5-
s
s
::.::
О
Строка, подлежащая реверсированию, содержится в массиве
ступа к нему используются указатели
3.
start
и
str.
Для до­
end.
J5
111
Введите в файл программы код, предназначенный для отображения исхо­
дной строки, получения ее длины и установки начальных значений указа­
телей
start
«
cout
len
и
end.
"Исходная
строка:
" «
str
«
~
S
U
U
.0
:~
"\п";
= 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 и декременти­
когда значение указателя start
цикла заканчивается ин крементированием указателя
рованием указателя
end.
В тот момент,
•
Модуль 4. Массивы, строки и указатели
202
станет больше или равным значению указателя
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
1/
Д-укерlDlЙ ка.ссив chаr-укаsатenей ДnK адресации
пар строк.
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>
даже
11
Ile
толыш соБСТВСIiИУЮ программу, но
операционную систему (отвратнтслыlйший,' надо сказатu, тип ошибки!).
Поскольку не существ уст гарантированного Сllособа избежать использования не­
I-!-нициализироваНIlОГО Уlшзателя, С++-программисты приняли процедуру, кото­
рая позволяет избегать таких ужасны·х ошибок. По соглашению, если указатель
содержит нулевое значение, считается, что он ни на что не ссылается. Это значит,
что, если всем неиспользуеМbIМ указателям присваивать нулевые значения и из­
бегать применения нулевых указателей, можно предотвратить случайное исполь­
зование неинициализированного указателя. Вам также следует придерживаться
этой практики программироваНIiЯ.
При объявлении указатель любого типа можно инициализировать нулевым
значением, например, как это делается в следующей инструкции.
float
*р =
О;
/1
р
теперь
нулевой указатель.
Модуль 4. Массивы, СТРОI(И и УI(аэатели
206
Для тестирования указателя используется инструкция
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-значения.
з.
Да, указатель, содержащий нулевое значение, считается неиспользуемым.
С++: руководство МЯ начинающих
свАоАТАЩ:"
1.%a[IA6
...--+1.1
«I*-AI~
207
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
х,
х
= 10;
р
&х;
*р,
**q;
/ / Приса . . . .ек перемвнной р адрес п&рек8КВОЙ х.
q = &р; 11 присванаавк пврекеикой q адрес перемеиной р.
cout «
**q; 11
11
Выводим
значение
переменной х.
(Доступ х
sваче~ перекенной х иw получаем через
208
МОДУЛЬ 4. Массивы, строки и указатели
//
//
return
ухазатen.
q.
Обра~. вникавие на
испо.Jlltsовавие дар .CPJМВOJIOB
"*".)
О;
Здесь переменная р объявлена как указатеЛ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.
3.
В С++ И14дексирование всех массивов начltиается с
_ _О
Напишите программу, которая находит и' отображает значения-дубликаты
в 10-элементном массиве целочисленных значений (если таковые в нем
присутствуют).
4.
5.
Что такое строка с завершающим нулем?
Напишите программу, которая предлагает польЗователю ввести две строки.
а затем сравнивает их, игнорируя "реПtстрооые" различия, Т.е. прописные и
строчные буквы ваша программа должна 80спринимать как одинаковые.
С++: PYI(OBOACTBO M~ начинающих 209
...............................................................................................................................
....
-
6.
Какой размер должен иметь массив-приемник при использовании ФУНКцИИ
strcat () ?
7.
Как задается каждый индекс в многомерном массиве?
8.
Покажите, КaJ( инициалИЗИРОВЗTh iлt-массив лuтs значениями
9.
Кш<Ое принципиальное преимущество имеет объявление безразмерного
5,66 и 887
массива?
10. Что
такое указатель? Какие вы знаете два оператора, используемые с ука­
зателями?
доступ к элементам массива посредством указателя?
Напишите программу, которая подсчитывает и отображает на экране KOJm-
чество ПРОПИСIJЫХ букв В cTpol<e.
13.
Как называется ситуация, когда используется указатель, который ссылает­
ся на другой?
14.
Что означает нулевой указатель в С++?
i\..
На заметку Л
...
OTU~Tbl Ш1 ЭПI ~UllpOC.bl МОЖllО lIаЙТll на Wcb-странице дашюй
ЮiИlИ по адресу.
>-
s
~
о
~
j
11. Можно ли индексировать указатель подобно массиву? Можно JЦI п(),лучить
12.
I
http./ /www.osborne.com.
111
S
U
U
о
~
Модуль5
Введение в фУНI<ЦИИ
5.1.
5.2.
5.3.
Общий формат С++-функций
Создание функции
Использование аргументов
5.4.
ИСПОЛЬЗ0Ианис инструкции
5.5.
Использование функций в выражениях
5.6.
5.7.
ЛокаЛl,Ная обласТl. видимости
5.8.
5.9.
5.10.
5.11.
5.12.
Передача указателей и массивов в качестве аргументов функций
return
Глобальная область видимости
Возвращение функциями указателей
Передача аргументов командной строки функции
Прототипы функций
Рекурсия
rnain ()
МОДУЛЬ 5. Введение в функции
212
в этом модуле мы приступаем к углубленному рассмотрению функций.
Функции
-
это строительные блоки С++, а потому без полного их понимания
невозможно стать успешным С++-программистом. Здесь вы узнаете, как создать
функцию и передать ей аргументы, а также о локальных и глобальных перемен­
ных,
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.
Функцию создать очень просто. Поскольку все функции исполъзуют один И
тот же общий формат, их структура подобна структуре фуикции
main (), с кото­
рой нам уже приходилось иметь дело. Итак, начнем с простого примера, который
main ()
и
myfunc ().
Еще до выполнения этой програм­
мы (или чтения послеДУlощего описания) внимательно изучите ее текст и попы­
тайтесь предугадать, что она должна отобра.lИТЬ иа экране.
/*
Эта
и
программа
содержит
две
Функции:
main()
myfunc().
*/
#include <iostream>
using namespace std;
void myfunc(); //
Прототип ФУНКЦИИ
myfunc().
int main ()
cout «
"В ФУНКЦИИ
myfunc(); //
cout «
return
/1
main(f.\n";
Вызываем ФУНКЦИЮ
"Снова в ФУНКЦИИ
О;
Опредеnение фУНКЦИИ
void myfunc () ,
myfunc().
main()
myfunc().
.\п";
ф
J
111 Создание фVНКII.kU4
содержит две фушщии:
са
214
Модуль 5. Введение в функции
cout «
..
В ФУНКЦИИ
myfunc () . \п";
Программа работает следующим образом. Вызывается функция
выполняется ее первая cout-инстрУ1<ЦИЯ. Затем из функции
ется функция
myfunc ( ) . Обратите
main () и
main () вызыва­
внимание на то, как этот вызов реализуется
в программе: указывается имя функции ту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.
8, в
Во время третьего вызова в параметр
параметр
width -
число
Использование Функции Ьох
//
()
6ив
length будет скопировано
параметр height - число 9.
демонстрируется в следующей программе.
Про грамма демонстрации использования ФУНКЦИИ Ьох().
#include <iostream>
using namespace std;
216
МОДУЛЬ 5. Введение в фУНI<ЦИИ
................................................................................................................................
void box(int length, int width, int height); // Прототип
// функции Ьох() .
int main ()
{
Ьох(7,
Ьох
Ьох
20, 4); //
(50, 3, 2);
(8, 6, 9);
return
Передаем apryмeH~ ФУНКЦИИ Ьох().
О;
// Вычисление объема параллелепипеда.
void box(int length, int width, int height) //
Параметры
11
npииимают значении аргумен-
1/
переданкых фУНКЦИИ Ьох().
'1'08,
cout «
"Объем
равен "
length * width * height «
параллелепипеда
«
При вьшолнении программа генерирует
Объем
параллелепипеда
равен
Объем
параллелепипеда
равен
Объем
параллелепипеда
равен
результаты.
560
300
432
•
Помните, что термин
n
пользуется ПРli вызове функции. Переменная, которая принимает
Узелок -""напамять
Tat..:I1C
"\п";
apZYJtfeum ОТIIОСИТСЯ !( значеllИIO, !<оторое ис­
значение аргумента, называется 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ТОРУ ее вызова. Любой код в функции, расположенный за ин­
струкцией
11
!
return,
игнорируется. Рассмотрим, например, эту программу.
Использование
инструкции
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
return: //
//
cout
функции
f () .
\п";
Beкeдneввoe 80S.р&Щ$ние х ивициа~ору .uзова
без ВlШоJUl8IIИJI cnедyDЦей соut-ииструхции.
«"Этот
текст не будет ОТОбражен.\п";.
Вот как выглядят результаты выполнения этой программы.
До
В
вызова
функции
После
функции.
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 main ()
(
power(10, 2):
power(10, -2);
int
ехр);
немед­
С++: PYI<OBOACTBO ДМ1 начинающих
219
О;
return
// Возводим целое число в положительную степень.
Ьаэе,
void power(int
int
ехр)
{
int i;
if(exp <
О)
return; /*
Отрицательные показатели степени
не обрабатываются.
i
=
*/
1;
for (
cout «
ехр;
ехр--)
i
"Ответ равен:
= base
" «
* i;
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); //
//
//
cout «
"Объем
Значение,
.os.p~eкoe
фувкцкей,
DpИС3&И8ае'J.'CJI
аереlсениоА
параллелепипеда
равен
&nswer.
"«
answer;
С++: РУКОВОДСТВО МЯ начинающих
return
221
О;
:s:
// Эта функция возвращает значение.
int box(int length, int width, int height)
(
1
ID
Ф
return length * width * height ; //
Bos.p~e~CR об~и.
Вот результат выполнения этой программы.
Объем параллелепипеда равен
330
В этом при мере функция Ьох
() с помощью инструкции return возвращает
length * width * height, которое затем присваивает­
ся переменной answer. Другими словами, значение, возвращаемое инструкцией
return, становlПСЯ значением выражения Ьох () в вызывающем коде.
Поскольку функция Ьох () теперь возвращает значение, ее прототип и опре­
деление не предваряется ключевым словом void. (Вспомните: ключевое слово
void используется только для функции, КОТQРая 1Ю возвращает значение.) На
этот раз функция Ьох () объявляется как возвращающая значение типа in t.
Безусловно, тип int - не единственный тип данных, которые может возвра­
значение выражения
щать функция. Как упоминалось выше, функция может возвращать данные лю­
бого допустимого в С++ типа, за исключеllием маССlша. Например, в следующей
программе мы переделаем функцию Ьох
типа
11
~
double
Возврат
11 возвращала значение
значения
типа
() так, чтобы она ПРИIIИМ<U1а параметры
типа double.
double.
#include <iostream>
using namespace stdi
// Использование параметров типа dovble.
double box(double length, double width, double height);
int main ()
{
double answer;
answer
cout «
=
Ьох(10.1,
"Объем
11.2, 3.3);' // Присваиваем значение,
// возвращаемое функцией.
параллелепипеда равен "«
answer;
:s:
J
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;) начинающих
//
//
.оs.ращаеиоrо функцией
непосредст••иво
Ьох(),
// •
return
223
инструхции
cout.
:s:
~
х
!
О;
са
ф
:s:
При выполнении этой инструкции
ция Ьох
()
cout
автоматически вызывается функ­
для получения возвращаемого ею значения, которое и выводится на
экран. Выходит, совершенно необязательно присваивать его сначала некоторой
переменной.
В общем случае 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) +
+
cout «
«
cout «
return
"Суммарный
зшn«
объем всех
Ьох(5.5,
Ьох
б.б,
7.7)
( 4 . О, 5. О, 8. О) ;
параллелепипедов
равен
"\П,";
"средний объем равен
" «
sum /
з. О
«
"\п";
О;
//
в этой версии функции Ьох()
//
типа
double.
используются параметры
"
I
со
a:I
224 МОДУЛЬ 5. Введение в функции
double box(double length, double width, double height)
{
return length
*
width * height
. При выполнении эта программа генерирует такие результаты.
Суммарный объем всех
Средний объем равен
параллелепипедов
равен
812.806
270.935
~просы для текущего контром ________
Покажите две формы применения инструкции
1.
2.
-
return.
Может ли 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.
3.
Иет, vоid·функция не может воэвращать эначение.
Выэов не VOid-функции можно испольэовагь в выражении любого типа. В <.ТОМ
случае функция, которая в нем содержится. автоматически выполняется с целью
получения воэвращаемого ею эначения.
С++: PYI<OBOACTBO
ДЛJ1 начинающих 225
..............................................................................
,. ................................................... .
ленные 8 локальной области, отличаются от переменных, объявленных в гло­
бальной, и как каждая из них связана с функциями.
L.
АQКQАьыая оБАQСТhВИДИМОСIИ
ID
Ф
s
Локальная область видимости создается блоком. (Вспомните, что блок на­
чинается с открывающей фигурной скобки и завершается закрывающей.) Таким
образом, каждый раз при создании нового блока программист создает новую об­
ласть видимости. Переменная, объявленная внутри любого блока кода, называет­
ся локалыюй (по отношению к этому блоку).
Локальную переменную могут ИСПО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
могут взаимодействовать с кодом
и данными, определенными в другой, поскольку две функции имеют различные
области видимости.
Так как каждая функция определяет собственную область видимости, пере­
менные, объявленные в одной функции, не оказывают никакого влияния на пере-
j
226
Модуль 5. Введение в функции
менные, объявленные в другой, причем даже в том случае, если эти переменные
имеют одинаковые имена. Рассмотрим, например, следующую программу.
tinclude <iostream>
using namespace std;
void f1 () ;
int main ()
{
int val
=
10; //
~a пsреке_. .
«
"Значение
val «
nОК8J'П8аа по
о~вошеВRD к функции ша1п().
//
cout «
val
переменной
val
J?,
функции
main(): "
val
Е
функции
main(): "
'\п';
f1 () ;
cout «
«
return
"Значение
val «
переменной
'\п';
О;
void fl ()
int val
cout «
«
88; 11
11
"Значение
val «
Э~а переИSКВ&R
о~ношеВRD К
переменной
val
val
nокanъна по
фУНКЦИИ
в
fl().
функции
f1(): "
"\п";
Вот результаты выполнения этой программы.
Значение
переменной
Значение
переыенной
Значение
переменной
val
va1
val
в
функции
в
функции
в
функции
main(): 10
f1(): 88
main(): 10
Целочислекная переменная
val объявляется здесь дважды: первый раз в
- в функции f1 (). При этом переменная val, объ­
явленная в функции main ( ) , не имеет никакого отношения к одноименной пере­
функции
main ()
и еще раз
менной из функции f 1 ( ) . как раэъяснялось выше, каждая переменная (в данном
случае
val) известна только блоку кода, в котором она объявлена. Чтобы убе-
С++: руководство ДЛЯ начинающих
227
диться в этом, достаточно выполнить приведенную выше программу. как видите,
несмотря на то, что при выполнении функции f1 () переменная
вается равной числу
равным
88,
значение переменной
val
в функции
val устанавли­
main () остается
10.
Поскольку локальные переменные создаются с каждым входом и разрушают­
ся с каждым выходом из программного блока, в котором они объявлены, они не
хранят своих значений между активизациями блоков. Это особенно важно пом­
нить в отношении функций. При вызове функции ее локальные переменные соз­
даются, а при выходе из нее
-
разрушаются. Это означает, что локальные пере­
менные не сохраняют своих значений между вызовами функций.
Если объявление локальной переменной включает инициализатор, то такая
переменная инициализируется при каждом входе в соответствующий блок. Рас­
смотрим пример.
/11
Локальная
переменная
при :каждом входе
в
инициализируется
"свой"
блок.
*/
#include <iostream>
using namespace std;
void f();
int main ()
for (int i=O; i < 3; i++)
return
f ();
О;
// Переменная num
/ / функции f () .
void f ()
инициализируется
при каждом вызове
(
int num
cout «
99;
пит
«
/ /
//
При JCаидом вызове ФУКIЩИИ
перекеинаR
"\п";
num
f ()
устаи8ll.JDlllаетCJI равной
99.
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
cout «
"х
+
х иэвестна
всему коду функции.
nepeкeRИa. у похan.ва дик if-бпоха.
у равно
" «
х
+
у
«
"\n";
С++: руководство ДЛЯ начинающих
11
100; 11
у =
return
Ошибка!
Переменная у здесь
229
неизвестна.
О;
s
~
!
са
Переменная х объявляется в начале области видимости функции
main () и
поэтому доступна ДЛЯ осеl"О последующего кода этой функции. В блоке if-ин­
струкции объявляется переменная у. Поскольку рамками блока и определяется
область видимости, переменная у видима только для кода внутри этого блока.
Поэтому строка
у
= 100;
(она находится за пределами этого блока) отмечена как комментарий. Если
убрать символ коммснтария
(/1) в началс этой строки, компилятор отреагирует
сообщением об ошибке, поскольку персменная у невидима вне этого блока. Вну­
три
i
f-блока вполне можно использовать переменную х, поскольку код в рамках
блока имеет доступ к переменным, объявленным во включающем блоке.
Несмотря на то что локальные ПСРСl\Iснные обычно объявляются в начале сво­
его блока, это не ЯАляется обязательным требованием. Локальная переменная
может быть объявлена в любом месте блока кода
-
главное, чтобы это произошло
до ее использования. Например, слеЛУЮIJЩЯ программа абсолютно допустима.
#include <iostrearn>
using namespace stdi
int rnain ()
{
cout « "Введите первое число: ";
int а; /1 Объявляем одну переменную.
cin » а;
cout « "Введите второе
int Ь; // Объявляем еще
cin » Ь;
cout «
return
число:
одну
"Проиэведение равно:
О;
";
переменную.
" «
а*Ь
«
'\п';
ф
j
230
Модуль 5. Введение в функции
в этом примере переменные а и Ь объявляЮТСЯ "по мере необходимости", т.е. не
объявляются до тех пор, пока в них нет нужды. Справедливости ради отмечу, что
БОльшинство программистов объявляют локальные переменные все-таки в начале
функции, в которой они используются, но этот вопрос
-
чисто стилистический.
Сокрытие имен
Если имя переменной, объявленной во внутреннем блоке, совпадает с именем
переменной, объявленной во внешнем блоке, то "внутренняя" переменная скры­
вает, или переопределяет, "внешнюю" в пределilX области видимости внутренне­
го блока. Рассмотрим пример.
linclude <iostream>
using namespace stdi
int main ()
{
int i, j;
10;
100;
i
j
if(j >
О)
{
int ii //
//
Эта
леременная
внешней
return
"Внешняя
отделена
леременной
i = j / 2;
cout « "Внутренняя
cout «
i
i.
леременная
переменная
i:
от
i: " «
" «
i
«
i «
'\п';
'\п';
О;
Вот как выглядят результаты выполнения этой программы.
Внутренняя
Внешняя
леременная
леременная
Здесь переменная
менную
i: 50
i: 10
i, объявленная внутри
if~лока, скрывает внешнюю пере­
i. Изменения, которым подверглась внутренняя переменная i, не ока-
С++: руководство МЯ начинающих
зывают никакого влияния на внешнюю
231
i. Более того, вне i f -блока виутреНВJUI
переменнCUI i больше не существует, и поэтому внешняя переменная
i
снова cтa~
новитCR видимой.
i
параметры функции
ID
Параметры функции существуют В пределах области ВИДИМОСТИ функции.
Таким образом, они локзльны по отношению к функции. Если не считать получе­
ния значений аргументов при вызове функции, то поведение параметров ничем не
отличается от поведения любых других локзль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ь­
ная область видимости
-
:s
это декларативная область, которая "заполняет" про­
странство вне всех функций. При объявлении переменной в глобальной области
видимости создается zл06шrьна.я nepeменная.
Глобальные переменные известны на протяжении всей программы, их можно
использовать в любом месте кода, и они поддерживают свои значения во время
Ф
:s
!
!
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: " «
cout «
'\п';
count; 11
11
11
Дос'l'YD х Ж'.nоб&Ju.НОЙ
перемеивой
count.
Вывод символа новой строки.
С++: руководство дl\Я начинающих
233
func2();
s
~
void func2()
int count; / /
//
//
!
са
Э~О JlOKaJIIoBёUI пер....IUI&JI.
ф
S
Здеса. ИСПOJlloSУ.~CII .покап.... пер....вва.
count.
for(count=O; count<3; count++) cout «
'.';
Результаты выполнения этой программы таковы.
count: О
... count:
... count:
... count:
... count:
· .. count:
· .. count:
· .. count:
... count:
· .. count:
~
са
CD
2
4
6
8
10
12
14
16
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.
3.
Можно ли локальную переменную объявить в любом месте внутри БЛОК.l?
Сохраняет ли локальная переменная спое значение между вызовами
функции, в которой она объявлена?·
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
11
Функция
объявляет параметр-ухазатель
f()
на iпt-значение.
int main ()
int ii
int
*р;
=
&i i
р
f(p)i
11
11
11
Указатель
11
теперь
Передаем ухазатель.
указателем на
cout « i j
return
р
1/
ссьшается
Функция
f()
на
переменную
вызывается
Переменная
i
теперь
содержит
число
О;
Функция
f()
void f(int *j)
принимает
с
iпt-значение.
указатель
на
iпt-значение.
100.
i.
236 МОДУЛЬ 5. Введение в фУНI<ЦИИ
............................................................................................................................
- ...
*j
Переменной,
100; //
//
адресуемой указателем
nрисваивается число
j,
100.
как видите, в этой программе функция f () принимает один параметр: указа­
тель на целочисленное значение. В функции
адрес переменной
i.
Затем из функции
main () указателю Р. присваиваетея
main () вызывается функция f (), а ука­
затель р передается ей в качестве аргумента. После 1'01'0 как параметр-указаТСJlЬ
j получит значение аргумента р, он (так же, как и р) будет указывать на пере­
менную i, определенную в функции main (). Таким образом, при 8ыполпенi!И
операции присваивания
*j == 100;
переменная
число
100
i получает значение 100. Поэтому программа отобразит на экране
100. В общем случае Ilрипеденная здесь функция f () присваипаеl' ЧИС.ilо
переменной, адрес «оторой был передан э10Й функции в качестве аргумента.
В предыдущем примере необяззтелыю было использовать переменную-ука­
затель р. Вместо нее при вызове функции f () ,'{остаточно взять переменную
i.
предварив ее оператором" &" (при этом, как вы знаете, генерируется адрес пе­
рсмешюй
i).
После внесения огопорешюго ИЗI\IСНfrшя предыдущая программа
приобретает такой вид.
// Передача указателя
#include <iostream>
using namespace std;
функции
(исправленная
версия).
void f(int *j);
int main ()
int i;
f(&i); //
в переиекной-ухазатеnе р нет необхоАИМОСТИ.
//
cout «
return
i;
О;
void f(int *j)
Тах напрRМ~ передаетск адрес перемениой
i.
С++: PYI(OBOACTBO AMI начинающих
*j
100; 11
//
Переменной,
адресуемой указателем
присваивается число
237
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)
display(t); /1
return
t [i] =i;
Передаем
функции
массив
t.
О;
1/ Функция выводит все элементы массива.
void display(int num[10]) //
Параие~ здесь
11
об~nев как
массив заданно~о размера_
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·
Важно помнить, что, если массив используется в качестве аргумента функции,
то функции передается адрес этого массива. Это означает, что код функции мо­
жет потенциально изменить реальное содержимое массива, используемого при
вызове функции. Например, в следующей программе функция сиЬе
()
прео6ра­
зует значение каждого элемента массива в куб этого значения. При вызове ФУНК­
цИИ сиЬе
в качестве первого аргумента необходимо передать адрес массива
()
значений, подлежащих пре06разоваliИЮ, а в качестве второго
//
Изменение содержимого массива
с
-
его размер.
помощью функции.
:s:
i
111
Ф
:s:
~
111
ID
#include <iostream>
using namespace std;
void cube(int ·n, int
пит);
int main ()
{
int i, nums[10];
for(i~O;
cout «
for(i=O;
cout «
//
i<10; i++) nums[i]
"Исходное
=
содержимое
i<10; 1++) cout «
1+1;
массива:
nums[i] «
Вычисляем
кубы значений.
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 начинающих
return
//
241
О;
1
Инвертируем регистр букв строки.
void strInvertCase(char *str)
(
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-Функцию с именем
мающую один параметр
Зllачение типа
2.
pt r,
count,
прини­
который представляет собой указатель на
long i n t.
Если функции перелается указатель, то может ли эта функция изменить
содержимое объекта, адресуемого этим указателем?
З. Можно ли функции 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. не найдена, возвращается нулевой указатель. Напри­
мер. если в строке "Я
ФУНlЩИЯ
11
люблю
С++" вьmолияется поиск подстроки "люблю", ТО
get_substr () возвраnп укаэатель на букву л в слове "люблю".
Возвращение
функцией указателя.
#include <iostream>
using namespace'stdi
char *get_substr(char
*зиЬ,
char *str)i
int main ()
(
char *substri
substr
cout «
return
gеt_SUЬstr("три",
=
"Заданная
О;
подстрока
"один
два
наЙден.а:
три
"
«
четыре");
substr;
С++: руководство ДМ1 начинающих
243
-
// Возвращаем указатель на подстроку или нуль,
// если таковая не найдена.
char *get_substr(char *эиЬ, char *str) //
Эта фyиxциR
//
возвращает
/ /
//
ужаза'l'enъ на
char-.наЧ8НИ8 .
111
•
ф
:s;
:~
int t;
char *р,
:<l
'ф
~~
*start;
*р2,
for(t=O; str[t); t++)
р = &str[t); // установка
start = р;
р2
=
указателей
suЬ;
while(*p2 &&
*р2==*р)
( //
проверка
на
наличие
подстроки
р++;
р2++;
/*
Если
обнаружен
искомая
i f (!
конец р2-строки
подстрока
(т.е.
подстроки),
то
*/
*р2)
return start; //
//,
return
найдена.
О;
//
Возвращаем указатель
на
начало
подстроки.
Совпадения
не
обнаружено.
Вот результаты выполнения этой программы,
Заданная
подстрока
найдена:
три
четыре
ФУНКЦИЯ main ( )
как вы уже знаете, ФУНlЩИЯ
I
main () -
специальная, поскольку это первая
функция, которая вызывается при выполнении программы. В отличие от некоторых других языков программирования, в которых выполнение всегда начинается
"сверху", Т.е. с первой строки кода, каждая С++-программа всегда начинается с
вызова функции
обычно функцию
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л
раметра,
argc
и
argv,
() опредеJlенодва встроенных, но JJсоuязательных на­
которые получают свои значения от аргументов командной
строки. В конкретной операционной среде могут гюддеРЖJlваться и ДРУПJе ар'"у­
менты (такую информацию необходимо уточнип, по ДОI<ументащш, прилагаемой
к вашему компилятору). Рассмотрим параметры а rgc и
На заметку
"*'-
n"
argv
более подробно.
Формально для имен парамеТрОll IсомаtJДJюi! СТРОКИ можно вы(iи-
рать любые идеНТИфИ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 и возвращает результат.
() Ilрсо6разует строку в значение типа long int и возвращает результат.
а to i () 11 реобразуст строку в значение Тlша i n t и возвращает результат.
а tal
Каждая из переЧИСЛСНН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. начинающих
double
а,
247
Ь;
if(argc!=3) {
cout « "Использование: add
return 1;
s
~
число число\п";
CII
ф
S
}
1/
Иcnonъзование функции
1/
строховых аргументов командной строки в чиcnовuе
1/
звачения: типа
= atof(argv[l]); //
Ь
//
atof(argv[2]); //
//
cout «
а
return
О;
+
atof() дпя: npеобразовaииR
double.
а
Преобразование второго аргумента
командной строки.
Преобраэование третьего аргумента
командной строки.
Ь;
Чтобы сложить два числа, используйте командную строку такого вида (пред­
полагая, что эта программа имеет имя
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иты командной строки принимаются в строковой форме.
~
CII
a::I
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 начинающих
void sqr_it(int *i); //
249
ПРОТОТИП фУНКЦИИ
int main ()
(
int
х
х;
= 10;
// Бnаrодари прототипу КОИПИnRТор вЫRВИТ иесоответствие
/ / типов. Ведъ функции sqr_ i t () дoпlCКa принимать
/ / yxasa'l'enь, а она IWSвана с ЦeJIОЧ:ИCneJUDal арryиен'1'ОК.
sqr_it(x); /1
return
void
*i
Несоответствие
типов!
О;
sqr~it(int
=
Ошибка!
*i)
*i '* '*i;
Определение функции может также служить в качеСТDе своего прототипа,
если оно размещено до первого ее использования в программе. Например, следу­
ющая прor-рамма абсолютно допустима.
//
Использование
определения функции в
качестве ее
прототипа.
#include <iostream>
using namespace std;
// Определяем, является ли заданное
bool isEven(int пит) ( /1 ПОСКО.JIьку
число
четным.
функция
isEven()
// опреДeJIиетси до ее испо.пьsоваиии,
// ее определение одновреиеико
// слукит и ее ПРО'l'отипок.
if(!
(пит
%2»
return false;
int main ()
return true; //
Значение пит
-
четное
число.
250
Модуль 5. Введение в функции
cout «
cout «
if(isEven(4»
if(isЕvеп(З»
return
4
четное.\п";
"Эта строка не будет выведена.";
О;
Здесь функция
main ( ).
"Число
isEven ()
определяется до ее использования в функции
Таким образом, ее определение служит и ее прототипом, поэтому нет
необходимости в отдельном В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 равен" «
cout « '\п';
factr(4)i
// Использование итеративной версии.
cout « "Факториал числа 4 равен" «
cout « '\п';
fact(4);
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
return(answer) ;
Нерекурсивная версия функции
answer*(t);
fact ()
довольно проста и не требует рас­
ширенных пояснеиий. В ней используется uикл, в котором организовано пере­
множение последовательных чисел, начиная с
1и
заканчивая числом, заЩlliНЫМ
в качестве параметра: на каждой итерации цикла текущее значение управляющей
переменной цикла умножается на текущее значение произведения, получеJflюе в
результате выполнения предыдущей итерации ЦИЮlа.
Рекурсивная функция
аргументом. равным
factr ()
несколько сложнее. Если она вызывается с
1. В пропшном случае она
factr (n-1) * п. Для вычисления этого выражения
вызывается функция factr () с apryMeHTOM п-1. Этот процесс повторяется до
тех пор, пока аргумент не станет равным 1, после чего вызванные ранее функции
1,
то сразу возвращает эначение
возвращает произведение
начнут возвращать значения. Например, при вычислении факториала числа
первое обращение к функции f
2
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, - лучший из известных ныне алгоритмоп
4.
сортировки общего назначения. Мы не рассматривали этот а.i1ГОРИТМ в модуле
поскольку самая эффективная его реализация опирается на использование ре­
курсии. Версия, которую мы будем разрабатывать, предназначена ДЛЯ сортироп·
ки символьного массива, но ее логику вполне можно адаптировать под сортиров­
ку объектов любого типа.
Ф
в вы1оре значения. именуемого КОм'парандом (операнд в операции cpanHCl-lllЯ). с
последующим разделением массива на ДB~ части. Все ЭЛ~\I~НТЫ. lшторые бо.'1ЬШ~
компаранда или равны ему, помещаем в одну часть, OCTaJIblThle (которые меш,шс
компаранда) - в другую. Этот процесс затем повторяется для ка.ждоЙ части до
тех пор, пока массив не станет полностью отсортированным. Например, воз.ьмем
fedacb
I{
используем значение
dn
качестве КОЫl1араl!да. Тогда в реЗУ"'lu'
тате первого прохода алгоритма QuickSогt lIаш массив Gудет реорганшюшш СЖ~­
дующим образом.
Исходный массив
Проход
1
fedacb
bcadef
Этот процесс затем повторяется отдельно для частей Ьса и
def.
К31\ I3ИДНТС.
этот процесс по своей природе рекурсивен, поэтому его проще всего реалИЗОВ<1ТiJ
в виде рекурсивной функции.
Значение компаранда можно выбрать двумя способами: либо случайным об­
разом, либо путем усреднения не60льшого набора значений, взятых из исходного
массива. для получения оmимальноro результата сортировки следует взять ЗШ1.че­
ние, расположенное в середине массива. Но для большmtства наборов данных это
сделать нелегко. В самом худшем случае выбранное вами значение будет находиться
на одном из концов массива. Но даже о этом случае алгоритм
QuickSort UЫПОЛIШТ­
ся корректно. В нашей версии в качестве компаранда мы выбираем среДlшii
элемент массива.
Следует отметить, что в библиотеке С++ содержится функция
торая также выполняет сортировку методом
qsort () , ко­
QlIickSort. Вам было бы И1lтересно
сравнить ее с версией, представленной в этом 1lроекте.
Последовательность действий
1.
Создайте файл QSDemo. срр.
2.
Алгоритм
ш
.::>:
Алгоритм QuickSoit основан на идее разделений. Общая ПРОПСДУР<l состоит
массив
-
QuickSort реализуется здесь в виде двух функций. Одна нз них,
quicksort () , обеспечивает удобный интерфейс для пользователя и под­
готавливает обращение к другой, qs (), которая, по сути, и выполняет со­
ртировку. Сначала создадим функцию quicksort () .
::1:
:Ф
:<i
:~
:CLJ
256
Модуль 5. Введение в функции
11
Функция обеспечивает вызов реальной функции сортировки.
void quicksort(char *items, int len)
(
qs(items,
Здесь параметр
раметр
len
len-l);
О,
i tems указывает на массив, подлежащий сортировке, а па­
задает количество элементов в массиве. Как показано в следу­
ющем действии (см. пункт
3),
ФУНКЦИЯ
qs () принимает область массива,
quicksort ( ) . Преимущества
предлагаемую ей для сортировки функцией
испаЛЬЗ0ваЮfЯ функции
звать, передав ей
n
quicksort ()
состоит D .ТОМ, что ее можно вы­
качестве параметров лишь указатель на сортируемый
массив и его длину. Из этих параметров затем формируются начальный и
конечный индексы сортируемой области.
З. Добавим в I1pограмму функцию реальной сортировки массива
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] < х)
while«k < items[j])
< right»
&&
(i
&&
(j > left»
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);
i++;
j--;
qs ()
о
сортирсвки
С++: PYI<OBOACтeO ДЛЯ начинающих
257
Эту функцию необходимо вызывать, передавая ей в качестве параметров
индексы сортируемой области. Параметр
left должен содержать начало
(левую границу) области, а параметр right - ее конец (правую границу).
При первом вызове сортируемая область представляет собой весь массив.
е каждым рекурсивным вызовом сортируется все меньшая область.
4.
Для использования алгоритма
quicksort () ,
QuickSort
достаточно вызвать функцию
передав ей имя сортируемого массива и
ero
длину. По за­
вершении этой функции заданный массив будет отсортирован. Помните,
эта версия подходит ТОЛ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[]
cout «
=
"jfmck1doe1az1kper";
"Массив
в
исходном порядке:
" «
str «
"\n";
quicksort(str, strlen(str));
cout «
return
"Отсортированный массив:
О;
" «
str «
"\п";
:s:
~
!
ID
Ф
:s:
~
ID
ID
МОДУЛЬ 5. Введение в функции
258
.................................................................................................................................
Функция обеспечивает вызов реальной функции сортиров­
11
ки.
void quicksort(char *items, int len}
(
qs(items,
len-l);
О,
Рекурсивная версия алгоритма Quicksort
1/ символов.
void qs(char *items, int left, int right)
1/
ДЛЯ
{
int i, j;
char х, У;
i
left; j
х
right;
=
= items [( left+right) / 2 ];
do (
while«items[i] < х) && (i < right»
while«x < items[j]) && (j > left))
i++;
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­
ма и возвращала результат типа
f()
1/
11
11
(
int.
По умолчанию для значения,
возвращаемого
тип
функцией,
подразумевается
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) ;
Использование функции
ка
<cmath>.
sqrt ()
требует включения в программу зarолов­
260 МОДУЛЬ 5. Введение в функции
................................................................................................................................
3.
Может ли функция возвращать указатель? Может ли функция возврawать
массив?
4. Создайте собственную версию стандартной библиотечной функции strlen (). 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.
6.4.
ИСlIоль:ювание укаэателя для обеспечения вызова по ссылке
ССЫЛОЧ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
.................................................................................................................................................................................................. ...................................................... .
~
предстзвленных до сих пор, использовался метод вызова по значению. Рассмо­
трим, например, функцию
//
11
reciprocal ()
в следующей программе.
Изменение параметра при вызове по функции по значению
не влияет на аргумент.
#include <iostream>
using namespace stdi
double reciprocal(double
Х);
int main ()
(
double
t
cout «
=
10.0;
"Обратная
«
величина
reciprocal(t) «
cout «
"Значение
«
t
return
О;
«
от
числа
10.0
равна»
"\n";
переменной
t
по-прежнему равно:
"
"\n"i
// Функция возвращает обратную
// заданного значения.
double reciprocal(double х)
величину
от
{
х =
х;
1 /
return
1/
11
ВЫЧИСЛReN обрат~ величину.
1/
из фунхции
(Это действие
иихах не отражается на значении перемекиой t
main().)
х;
При выполнении эта программа генерирует такие результаты.
Обратная величина
Значение
от
переменной
числа
t
10.0
равна
0.1
по-прежнему равно:
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 указателем у, помещается в объект, адресуемый
указателем х. Следовательно, по завершеиии этой Фуикции содержимое пере­
менных, используемых при се вызове, "поменяется местами".
значения которых иы хотите обменять. Корректный вызов этой функции проде­
монстрирован в следующей ПРОГJflмме.
//
Демонстрируется
указателей.
версия
ФУНКЦИИ
swap()
с
использованием
#include <iostream>
using namespace std:
// Объявляем ФУНКЦИЮ swap(),
void swap(int
*х,
int
которая использует указатели.
*у);
int main ()
{
int i, j;
10;
20;
cout «
cout «
"Исходные
i
swap(&j,
cout «
cout «
return
}
«
&i);
значения
' , «
О:
«
Вызываем
' ',«
переменных
j
«
i
и
j: ";
'\n';
swap() с
// переменных i и j.
//
"Значения
i «
j
переменных
i
'\п':
и
j
адресами
после обмена:
с:::
х
t;;
:::r
swap () ожидает получить два ука.lателя, вы должны
помнить, что функцию swap () необходимо вызьшзть с адресами переменных,
1/
g
S
Поскольку функция
i
j
ф
ф
":
:.:
:I:
~
О
266
Модуль 6. О ФУНJ(ЦИ~Х подрОбнее
// Обмен значениями двух
// параметрами х и у.
void swap(int *х, int *у)
переменных,
адресуемых
{
int tempi
// Менкем звачеНИR переиевиых, адресуемых
// паракетраии х и у, испоnьзук явно задаиные
// операции с ухазателкми.
temp = *х; // Временно сохраняем значение,
// расположенное по адресу х.
*х
*у;
// Помещаем значение, хранимое по адресу
// по адресу х.
*у
temp; // Помещаем значение, которое раньше
// хранилось по адресу х, по адресу у.
в функции main
менной j -
у,
() переменной i было ПРИСlюено начальное значение 10, а п('ре­
20. Затем была вызвана функция swap ()
с адресами переменныx i н j.
Для получеJiия адресов здесь используется унарный оператор "&". Следователt.tIО,
функции swap () при вызове были переданы адреса переменных i и j, а не их зна­
чения. После ВЫJJOлtlения функции swap () псрсменныс i и j ФбменЯJШСЪ своими
значения~и, что подтверждается результатами выполнения этой программы.
Исходные
значения
Значения
переменных
переменных
i
и
i
после
j
и
j: 10 20
обмена:
20 10
~npocы ДМ1 текущего контроля _______._
1.
2.
-
Поясните суть передачи функции пара.\tетров по значению.
Поясните суть персдачи фушщии парамстров по ссылке.
-
З. I<ц:ой механизм передачи параметров используется в С++ по умолчанию?·
..
ч
1.
При передаче функции параметров ПО значению (т.е. при вызове по значению) зна­
чения аргументов копируются
2.
8
параметры подпрограммы.
При передаче фующии параметров по ссылке (т.е. при вызове по ссылке) в naраме­
тры подпроrpаммы КОШiРУIOТСЯ адреса аргументов.
З.
По умолчанию в С++ используется вызов по значению.
С++: руководство ДМI начинающих
267
ф
ф
Несмотря на возможность "вручную" организовать вызов по ссылке с помощью
оператора получения адреса. такой подход не всегда удобен. Во-первых, он вынужда­
ет про~миста въmолнять все операции с использованием ука:iате..lеЙ. Во-вторых,
вызывая функцию, программист должен не забыть передать ей адреса
apryMcHTOB,
а не их значения. К счастью, в С++ МОЖIlО сориентировать l<ОМПЮl}lТОР на автома­
тическое использование вызова по ссылке (вместо вызова по значению) ДЛ}I ОДIIОГО
или нескольких параметров 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
cout «
=
1;
"Старое
значение
переменной "а1:
.. «
«
va1
'\п';
f(va1); 1/ Передаем адрес переменной va1 ФУНКЦИИ f().
cout « "Новое значение переменной val: " « val
«
return
О;
'\п';
:I:
\о
О
g
~
х
t;:
:s:
::r
'"
::r:
>-
-€т
О
МОДУЛЬ 6. О функциS1Х подробнее
268
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 () адрес аргумента, с которым вызывает­
ся эта функция. Таким образом, в фушщии та:..п
f(va1); //
Передаем
адрес
переменно;'1
передает функции f () адрес переменной
val
()
инструrщия
va1
функции
f().
(а не ее ЗШI.ЧСIIИС). Обратите 811И­
мание на то, что при вызове фуИIЩИИ f () не IfУЖНО предварять перемеюrую
vа 1
оператором "&". (Более того, это было бы ошибкой.) ПОСКОЛЬКУ функция f () по­
лучает адрес переменной
val
в форме ссылки, она может модифицировать зна­
чение этой переменной.
Чтобы проиллюстрировать реальное применение ссылочных параметров
(и тем самым продемонстрировать их ДОСТШlllства), перепишем нашу старую
знакомую функцию
swap ()
с использованием ссылок. В следующей программе
обратите внимание на то, как функция
swap () объявляется и вызывается.
С++: руководство ДМl начинающих
//
Использование ссылочных параметров в Функции
swap().
iinclude <iostream>
using namespace stdi
// Объявляем функцию swap()
// параметров.
void swap(int &х, int &У);
с использованием ссылочных
int main ()
{
il1t i, ji
i
j
10;
20;
cout «
cout «
"
Исходные
i «
cout «
cout «
"
Значения
i
«
return
О;
вызова
может
по
ссылке,
она
*/
void swap(int
«
j
i
и
j: ";
автоматически
а
i
и
не
обмен
j
после
i
и
обмена:
int
";
вызова
по
значениями
с использованием
значению.
двух
Поэтому
аргументов,
вызывается.
&х,
j.
'\n';
определяется
swap()
выполнить
которыми
swap()
переменных
' , «
функция
переменных
'\n';
передаатCR адреса перемевиwx
//
Здесь
«
j
Здесь фУНКЦИИ
swap(j, i); //
/*
значения
' , «
&у)
[
int tempi
// Для обмена значениями аргументов используем
// ссылки на них.
temp = х;
// Теперь обмен значениями происходит
х
У;
// автоматически посредствоы ссылок.
у
tempi
она
с
269
Модуль 6. О функци~х подробнее
270
Результаты выполнения этой программы совпадают с результатами запуска
предыдущей версии. Опять таки'. обратите внимание на то, что объявление х и у
ссылочными параметрами избавляет вас от необходимости использовать опера­
тор
*" при орraнизации обмена значениями. Как уже упоминалось, такая "навяз­
u
чивость" с вашей стороны стала бы причиной ошибки. Поэтому запомните, что
компилятор автоматичес.ки
вызове функции
генерирует адреса аргументов, используемых при
swap ( ) , .1 автоматически разыменовывает ссылки х и у.
Итак, подведем некоторые итоги. После создания ссылочный параметр авто­
матически ссылается (т.е. веявно указывает) ШJ аргумент. используемый при вы­
зове функции. Более того, при вызове функции не "ужно при менять к apГYM(~В:­
ту оператор" &". Кроме того, 13 теле функции ссылочный параметр используется
непосреДСТВС1lНО, Т.е. без оператора
".".
Все операции, включающие ссылочный
пара~етр, i1втоматнчrС:ЮI выполняются над apl)'MCHTOM, используемым при вы­
зоuс функции. Наконец, Ilрисванвё:lЯ некоторое значение параметру-ссылке, вы в
л:ействителыюсти присваиваете это значение переменной, на которую указывает
эта ссылка. Поэтому. IlримеllЯЯ ссылку в качестве параметра функции, при [1Ы­
зове функции вы в действительности ИСl10льзу~те переменную.
И еще одно замечание. В языке С ссылки н(' поддерживаются. Поэтому един­
ственный способ организаl11111 в С вызова 110 ссылке
-
использовать указатели. как
показано в первой версии функции swap(). При переводе С-программы в С++­
код нсООХОДИМО пре06разовать эти rnпМ параметров 8 ссылки, где это возможно.
~ПРОСЫ ДЛ'i1 теl<ущеro КоНтрОл'i1---_______
1.
2.
Как объявить парамстр-ссылку?
Нужно ли прсдварЯ1Ъ аргумент символом
11
&" при вызове ФУliКЦИИ, КОТО­
рая принимает ссылочный параметр?
З. Нужно ли Ilрел:варять параметр символами
"." или .. &" при выполнеЮ-il'i опе­
раций Над ними в nнкции, которая принимает ССbUlОЧНЫЙ параметр?*
1.
для объявления ССЫЛОЧНОГО парамща необходимо предварить его имя символом
"&".
2.
Нет, при использовании ссылочного параметра apryмeflТ автоматически передает­
СЯ по
cCbIJlI(e.
3. Нет. парамщ обрабатывается обычным способом. Но изменения, которым под­
верrnется параметр. влияют на арryмеtп. используемый при вызове функции.
С++: РУКОВОДСТВО дЛЯ начинающих
271
Спросим у ОПЫТНОГО программиста
Вопрос. В некоторых C++-nроZPIUUIQX Аlllе nрllXO()uлось .стречать C1tIUЛЬ
зы.иma с UJNeHeм типа
(1n t
&
~
1;), а не с UJNeнe.JН nеременно" (i n t
st
& i;). Есть ли разница между :lтими нумя 06uвлеНWlМи?
Ответ.
дит еще один способ записи прототипа функции
х,
int&
с::
swap () .
1nt, ане к имени
float*
переменной х. Более того, некоторые
11 указатсли, беJ пробела
"." с. ТJшом, а не С переМСlllюii, как в этом нрнмсре.
СВЯ:JЫ­
р;
Приведенные объявления отражают желание некоторых программистов
иметь в С++ отдельный тип ссылки или указаТСJ1J1. Но дело в том, 'lТo по­
добиое связывание символа 11&" или
"*" с т~шом (~ не с переменной) не
распространяется, в СООТВе1'СТВШI с формальным синтаксисом С++, на
весь список перемеНtlых, приведенных в объЯDлении, что может lJызвать
путаницу. Например, в следующсм объявлении создастся Ol1lНl ука~ате.7lЬ
(а не два) на целочисленную перемснную.
1nt*
а,
Ь;
Здесь Ь объявляется кaI( целочислеНII<lЯ перемеJlНая
(:1 не как ука:штель на
целочисленную псременную), посколысу, как опрrДСЛСIIО синтаксисом С++,
используемый в объявлешш СI{М1JОЛ "*" или "&" СВЯ:1ьшается с коНКреllЮЙ
переМelUiОЙ, которой он предшествует, ;t lIе с ТИПIЩ, за которым он следум'.
Важно ПОЮ1мать, что для C++-КОМЛI1ЛЯТОра абсолlOТ!1O uе:lра:iJIIIЧНО, IШК
именно B~ напишете объявление:
с;:
~
программисты опрсделяют в таком с.тилс
вая символ
х
:s:
:::r
:z:
I
у);
Нетрудно заметить, что в этом объявлении символ" &" ПРllлс("аf"Т вплот­
ную к имени ТJma
о
о
Если кратко ответить, то никакой разницы нет. Например, вот как выгля­
vo1d swap(int&
т
"&" сая­
06uгленUJI ссылок, • соотитст.ии с кomoptlAf оnераmoр
1n t
• рили 1 n t·
р. 1аким образuм,
если вы предпочитаете связывать символ
"*" или" &" с типом. а не пере­
менной, поступайте, как вам удобно. Но. чтобы юбсжать в д;lЛьнеiiшем
каких-либо недоразумений, в этой книге мы будем связывать символ" *"
или "&" с именем переменной, а не с именем типа.
Функция может возвращать ссылку. В программировании на С++ предусмо­
трено несколько применений для ссылочных значений, возвращаемых Функция-
О
272 Модуль 6. О ФУНI<ЦИSlХ подробнее
................................................................................................................................
ми. Сейчас мы продемонстрируем только некоторые из них, а другие рассмотрим
ниже в этой книге, когда познакомимся с перегрузкой операторов.
Если функция возвращает ссылку, это означает, что она возвращает неявный
указатель на значение, передаваемое ею в инструкции
return. Этот факт откры­
вает поразительные возможности: функцию, оказывается, можно использовать в
левой части Иltструкции присваивания\ Например, рассмотрим следующую про­
стую программу.
//
Возврат
ссылки.
#include <iostream>
using паmеsрасе std;
double
&f();
/ / ~ Эта ФУНКЦИЯ возвращает ССЫЛКУ
//
на
100.0;
double val
dоublе-9начеиие.
int main ()
(
double
cout «
Х;
f()
= [(); //
cout « х «
х
f()
=
cout «
return
«
'\п';
Отобр~жаем значение
Присваиваем значение
'\п';
//
99.1;
f()
//
'\п';
леременной
значение
леременной х.
Изменяем
значение
глобальной
1/
леременной
//
Отображаем новое
val.
значение
О;
// Эта фУНКЦИR
double &f ()
возвращает
ссылку на
dоublе-значение.
{
return val;
х.
Отображаем
//
«
va1
val.
//
ВОS8ращаем ccкnxy на rnоб~
//
переке~
val.
Вот как выглядят результаты выполнения Э1'ОЙ программы,
val.
...............................................~:~:P.Y.~?~?~.?:.~~.~~.~~.~~~.~~~~.... ~~~.
100
100
99.1
Рассмотрим эту программу подробнее. Судя по прототипу функции f ( ) , она
должна возвращать ссылку на dоuЫе-эначение. За объявлением функции f ()
следует объявление глобальной переменной
значением
100.
водится исходное значение переменной
cout «
f()
которая инициализируется
val,
~ри выполнении с;ледующей инструкции в функции
«
'\n'; 1/
llосле вызова функция
вы­
val.
Отображаем
значение
val.
f () при выполнении инструкции return возвращает
val.
ссылку на l1еременную
return val; 1/
rnain ()
Возвращаем ссылку
на
val.
TaKIIM оGразом, I1pll ВЫllOЛIIСIllШ ПРIIВСДСШlOii выше строки автоматически 80З­
вращается ССЫЛ1<а на глобальную переменную val. Эта ссылка затем использует­
ся инструкцией сои t для отображения значения
val.
При выполнеtlИИ строки
х
=
f();
I/Присваиваем значение
val
переменной х.
ссылка на перемеНIlУЮ vai, возвращенная функцией f () , используется для при­
своения значения
val
переменной х.
А вот самая интересная строка в программе.
f()
=
99.1; 11
Изменяем
значение
val.
глобальной переменной
При выполнении этой инструкции присваивания значение переменной
val
ста­
новится равным числу
99,1. И вот почему: поскольку функция f () возвращает
val, эта ссылка и является приемником инструкции при­
Таким образом, значение 99,1 присваивается переменной val косвен­
ссылку 113 l1еременную
сванвания.
но, через ССЫJlКУ (на иее), которую возвращает функция f () .
Приведем еще один пример программы, в которой в качестве значения, воз­
вращаемого функцией, используется ссылка (или значение ссылочного типа).
/1
Пример функции,
возвращающей ссылку
на
элемен~ массива.
#include <iostream>
using narnespace std;
double &change_it(int i); 11
double vals[J
=
Функция возвращает
(1.1, 2.2, 3.3,4.4,5.5);
ссылку.
274
МОДУЛЬ 6. О фУНI(ЦИ~Х подробнее
int main ()
(
int i;
cout « "Вот исходные
for(i=O; i<5; i++)
cout « vals[i] «
cout « '\n';
change_it (1)
change_it(3)
эначения:
";
' ';
5298.23; 11
-98.8;
11
Изменяем 2-й элемент.
Изменяем 4-й элемент.
cout « "Вот измененные значения: ";
for(i=O: i<S; i++)
cout « vals[i] « ' ';
cout « '\n';
return
О;
double &change_it(int i)
return vals[i]; 11
Возвращаем ссылку на
i-й элемент.
Эта программа изменяет значения второго и четвертого элементов массива
va1s.
Результаты ее выполнения таковы.
Вот исходные- значения:
Вот измененные
1.1 2.2 3. 3 4.4 5.5
1.1 5298.23 3.3 -98.8 5.5
значения:
Давайте разберемся, как они были получены.
Функция
change _ i t () объявлена как возвращающая ссылку на значение
типа double. Говоря более конкретно, она возвращает ссылку на элемент масси­
ва va1s, который задан ей в качестве параметра i. Таким образом, при выполне­
нии следующей инструкции функции main ()
change_it(1) = 5298.23; //
Изменяем
2-й элемент массива.
элементу
va15 [1] присваивается значение 5298,23. Поскольку функция
change _ i t (j возвращает CCЬUlKY на конкретный элемент массива v а 15, ее мож­
но ИСПОЛЬЗ0ватъ в левой части инструкции для присвоения нового значения со­
ответствующему элементу массива.
С++: PYI<OBOACTBO для начинающих
275
Однако, организуя возврат функцией ССЫЛI<И, необходимо позаботиться о том,
чтобы объект, на который она ссылается, не выходил за пределы действующей
области видимости. Например, рассмотрим такую функцию.
// Здесь ошибка: нельзя возвращать
// на локальную переменную.
int &f ()
ссылку
{
int i=lO;
return i;
При завершении функции f () локальная переменная
лаСТII видимости. СЖ'ltопательно, ссылка на переменную
i
выйдет за пределы об­
i, возвращаемая функ­
цией f ( ) , будет НСОПРСДСЛСlIноii. В дсiiствитеЛЬНОСПI некоторые компиляторы не
СКОМПИЛИРУЮТ ФУНЮJ.ию f () в таком Dиде, и именно по этой причине. Однако про­
блема ,'::IКОГО рода может быть создана опосредован но, поэтому нужно вниматель­
но отнестись к тому, на какой объект будет ВОЗDращатъ ссылку ваша функция.
Понятис ссылки В!UIЮЧСIIO В С++ главным образом для поддержки .способа
передачи П<lраметров "по ссылке" и для использования в качестве ССЪ1Лочного
типа значения, возвращаемого ФУШЩl1еЙ. Несмотря на это, можно объявить неза­
ВИСИМУЮ переменную ссылочного типа, которая JI называется uезйuuсu.мoЙ ссыл­
'Кой. Однако справедливости ради необходимо сказать, что эти независимые ссы­
лочные переменные используются довольно редко, поскольку они могут "сбить с
пути истинного" вашу программу. Сделав (для очистки совести) эти замечания,
мы исе же можем уделить нсзависимым ссылкам некоторое внимание.
I-lсзависимая ССЫ.l1ка должна YKalblBaTb на некоторый объект. Следовательно,
независимая ссылка должна быть ИllНцнализироваllа при ее объявлении. В общем
случае это означает., что ей будет присвоен адрес некоторой ранее объявленной
переменной. По~е этоro имя такOI':" ссылочной переменной можно применять
везде, где может быть использована переменная, на которую она ССЪ1Лается. И в
самом деле, между ссылкой и переменной, на которую она ссылается, практиче­
сю·f нет НИI<:акой разницы. Рассмотрим, например, следующую программу.
//
Использование независимой ссылки.
#include <iostream>
using namespace std;
Модуль 6. О ФУНI(ЦИSlХ подробнее
276
int main ()
{
int j, k;
int &i
j; //
j
веsавиCИК&R ccкnxa
10;
«
cout
k
i
«
j
" " «
i; /1
Выводится:
10 10
121;
k;
11
11
«
cout
return
Копирует
в
переменной
"\п"
«
j; 1/
переменную :j
k,
а
не
значение
aдp~c
Выводится:
переменной
k.
121
О;
При выполнении эта программа выводит следующие результаты.
10 10
121
Адрес, который содержит ссылочная перемснная, фиксирован и его измешrть
нельзя. Следовательно, при выполнении инструкции
i :
8
k;
переменную
j (адресуемую ССblЛкой i) копируется значение псременной k,
а не ее адрес.
Как бbIЛО отмечено выше, незаоисимые ссылки лучше не использовать, поско:[ь­
ку чаще всего ИМ можно найти замену. а их неаккуратное ПРИМСllение может ИСlCа­
зить ваш код. Согласитесь: наличие двух имен для одной и той же перемеююii. по
сути. уже создает ситуацию, потенциально порождшощую недора..1умения.
Оrраничения при использовании ССblЛОК
На применение ссылочных переменных налагается ряд следующих огра­
ничений\
•
Нельзя ссылаться на ссылочную переменную.
•
Нельзя создавать массивы ссылок.
•
Нельзя создавать указатель на ссылку, Т.е. нельзя к ссылке применять опе­
ратор
"&".
С++: РУКОВОДСТВО дl\9r начинающих
~просы для теl<ущеro I<ОНТрОЛЯ
277
--------
. 2. Что представляет собой независимая ссылка?
:r
g
t:
х
с:
Можно ли создать ссылку на ссылку? •
З.
ф
10
О
Может ли функция возвращать ссылку?
1.
ф
§
:..:
!
ВАЖНО!
о
1'8 Переtp~зк;а ф\шкцi4J4
в этом разделе мы узнае:\f
языка С++
-
06 ОДНОЙ из с.амых удивительных возможностей
нерегрузкс функций. В С++ несколько ФУНlЩIlЙ MoryT иметь оди­
наковые имена, но при условии, что их параметры будут различными. Такую
сптуаЦJlIО называют перегрузкой фУllКЦUЙ (fuпс.tiоп
торые в ней задействованы,
ций
-
-
tlереzружеllНЫ.мu
overloading), а функции, ко­
(overloaded). Перегрузка ФУНК­
ОДИН из способов реализации полиморфизма в С++.
Чтобы перегрузить функцию, достаточно объявить различные ее версии. Об
остальном же позаботится компилятор. При этом важно помнить, что тип и/или
количество параметров каждой перегруженной функции должны6ыть уникаль­
НЫМИ, поскольку именно эти факторы позволят КОМПИЛЯТОРУ определить, какую
версию перегруженной функции следует вызвать. Таким образом, перегружен­
ные ФУНКЦИИ должны отличаться типами и/или числом параметров. Несмотря
на то что перегружеНlIые функции .могут отличаться и типами возвращаемых
значений, этого вида информации недостаточно дЛЯ С++, чтобы во всех случаях
компилятор мог решить, l<аl<УЮ имеНJJО функцию нужно вызвать.
Рассмотрим простой ПРl1мер перегрузки функций.
//
"Трехкратная"
пере грузка
функции
f().
#include <iostream>
using namespace std;
1/
Для каждой версии переrpуженной фУНКЦИИ необходимо
//
ВICJШЧИ'По •
проrpакку отдe.nьНlolЙ протоТJШ.
1.
Да, функция может возвращать ссылку.
2.
Неэаписимая ссылка
3.
Нет, ссылку на ссылКу создать нельзя.
-
эквивалент, по сути, еще одно имя для объекта.
278 Модуль 6. О функциях подробнее
................................................................................................................................
void f(int i) ;
//
f(int
j)
;
void
i, int
//
void f(double k) ;
//
целочисленный
один
два
целочисленных
один параметр
параметр
параметра
типа
double
int main ()
(
f (10) ;
f(10,
//
вызов
функции
f (int)
20) ; //
вызов
функции
f(int,
вызов
функции
f(dot1ble)
f(12.23) ;
return
//
//
int)
О;
Перваи реanиsаци. фУНКЦИИ
f().
void f (int i)
cout «
//
"В
функции
f(int), i
Втора. реanиsации фУНКЦИИ
равно"
«
i «
'\n';
f().
void f(int i, int j)
(
cout «
cout «
//
"В функции
", j
f(int,
равно"
«
j
int), :..
«
равно"
«
i;
'\п';
Третъя реanиsации ФУНХЦИИ Е().
void f(double k)
(
cout «
"В
функции
f(double),
k
равно"
«
k «
'\п';
При выполнении эта программа генерирует следующие результаты.
В функции
В ФУНКЦИИ
В функции
f(int), i равно 10
f(int, int), i равно 10, j
f(double), k равно 12.23
равно
20
С++: PYI<OВOACTBO ДЛЯ начинающих 279
.................................................................................................................................
Как видите, функция f () перегружается три раза. Первая версия принимает
один целочисленный параметр, вторая
тья
-
два целочисленных параметра, а тре­
-
один dоublе-параметр. Поскольку списки параметров для всех трех вер­
сий различны, компилятор обладает достаточной информацией, чтобы вызвать
правильную версию каждой функции.
Чтобы лучше понять выигрыш от перегрузки функций, рассмотрим функцию
neg (),
которая возвращает результат отрицания (умножения на
гумента. Например, если функцию
вратит число
10. Л если
neg ()
-1) своего ар­
-10, то она воз­
возвратит число -9.
вызвать с аргумеlПОМ
при вызове ей передать число
9,
она
Если бы вам понадобились функции (и при этом вы не имели возможности их
перегружать) отрицания для данных типа
int, d01lble
и
long,
то вам бы при­
шлось создавать три различные функции с разными именами, например,
dneg ()
и
lneg ().
ineg ( ) ,
Но благодаря псрегрузке функций можно обойтись только
одним ШIСllем (lIапрш.IСР,
neg () ),
которое подоiiдет для всех функций, возвра­
щающих результат О'грицаl1l1Я аргумента. Таким образом, перегрузка функций
поддерживает ПрИНЦИП полиморфизма "один интерфейс
-
множество методов".
Этот принц!ш деМОlIстрируется на при мере следующей программы.
//
Создание
различных
версий функции
neg().
#include <iostream>
using namespace std;
int neg(int п);
double neg(double
long neg{long n)i
п);
// neg()
1/ печ()
/1 neg()
для
iпt-аргумеита.
ДЛЯ double-аргумента.
для
lопg-аргумента.
int main ()
cout «
cout «
cout «
return
"neg(-10): " « neg(-10) « "\n";
"neg(9L): " « neg(9L) « "\n";
"пеg(11.2З): " «
пеg(11.2З) «
"\n";
О;
1/ ФУНКЦИЯ neg()
int neg(int n)
"
ДЛЯ
iпt-аргумента.
280 Модуль 6. О функциях подробнее
..........................................................................
- .....................................................
return -n;
// Функция neg() для
double neg(double n)
double-аргумента.
(
return -ni
//
Функция
neg()
для
lопg-аргумента.
long neg(long n)
(
return -n;
Результаты выполнения этой программы таковы.
neg (-10):
10
neg(9L): -9
neg(11.23): -11.23
При выполнении эта программа создает три 110хожие, но все же различные функ­
ции, вызываемые с использованием "общего" (одного на всех) имени neg. Каждая
из них возвращает результат отрицания своего аргумента. Во всех ситуациях вызова
компилятор "знает'~, какую именно фуНlЩИЮ ему использовать. Для принятия реше­
ния ему достаточно "взглянуть" на тип аргумента, передаваемого функции.
Принципиальная значимость перегрузки состоит в том, что она позволяет об­
ращаться к связанным функциям посредством одного, общего для всех, имени.
Следовательно, имя neg представляет общее действие, которое выполняется во
всех случаях. Компилятору остается правильно выбрать "оu"рemн.ую версию при
конкретных обстоятельствах. Благодаря полиморфизму программисту нужно
помнить не три различных имени, а только одно. Несмотря на простоту приве­
денного примера, он позволяет понять, наскоЛ1,КО перегрузка способна упростить
процесс программирования.
Еще одно достоинство перегрузки состоит в том, что она позволяет определить
версии одной функции с небольшими разлИЧIIЯМИ, которые зависят от типа об­
рабатываемых этой функцией данных. Рассмотрим, например, функцию
min (),
которая определяет минимальное из двух значений. Можно создать версии этой
функции, поведение которых было бы различным для разных тИпов данных. Ilри
сравнении двух целых чисел функция
min ()
[JQзвращала бы наименьшее из llИХ,
•
С++: PYI(OBOACTBO д,ля начинающих
281
а при сравнении двух букв - ту, которая стоит по алфавиту раньше другой (неза­
висимо от ее реrn:стра). Согласно АSСП-кодам символов прописные буквы пред­
ставJUПOТCЯ значениями, которые на 32 меньше значений, которые имеют строчные
6уквы. Поэтому при упорядочении букв по алфавиту важно игнорировать способ
их начертания (регистр букв). Создавая версию функции
rnin () , принимающую в
качестве аргументов указатели, можно обеспечить сравнение значений, на которые
они указывают, и возврат указателя, адресующего меньшее из них. Теперь рассмо­
трим программу, которая реализует эти версии функции
//
Создание
различных
версий функции
rnin ( ) .
min().
#include <iostream>
using narnespace std;
int rnin(int а, int Ь) ;
// rnin ( )
char rnin(char а, char Ь) ; // rnin ()
int * min(int * а, int *Ь) ; // rnin ()
1/
на
для
int-значений
для
сhаr-значений
для
указателей
int-значения
int rnain ()
(
int i=10, j=22;
cout «
cout «
cout «
return
"rnin('X', 'а'): " « rnin('X', 'а') « "\n'!;
"rnin(9, 3): " « min(9, 3) « "\n";
"*rnin(&i, &j): " « *rnin(&i, &i) « "\n";
О;
// Функция rnin() для int-значенИЙ.
// наименьшее значение.
int rnin(int а, int Ь)
Возвращает
{
if(a < Ь) return
else return Ь;
а;
// Функция rnin() для сhаr-значений
char min(char а, char Ь)
(игнорирует
регистр).
282
Модуль 6. О функциS1Х подробнее
if (tolower (а) < tolower(b)) return
else return Ь;
а;
/*
Функция
rnin()
Сравнивает
указатель
*/
int * rnin(int
ДЛЯ
указателей на
адресуемые
на
ими
наименьшее
*а,
int
if(*a < *Ь) return
else return Ь;
int-значения.
значения
и
возвращает
значение.
*Ь)
а;
Вот как выглядят результаты выполнения этой программы.
rnin ( I Х 1 , I а I ) : а
rnin(9, З): 3
*rnin(&i, &j): 10
Каждая версия перегруженной функции может выполнять любые действия.
Другими словами, не существует правила, которое бы обязывало программиста
связывать перегруженные функции общими действиями. Однако с точки зрения
стилистики перегрузка функций все-таки подразумевает определенное "род­
ство" его версий. Таким образом. несмотря на то. что одно 1'1 то же имя можно
использовать для перегрузки не связанных общими действиями функций. этого
делать не стоит. Например, в принципе можно использовать имя
sqr
для ('оз­
дания функции, которая возвращает квадрат целого числа, и функции, котоrая
возвращает значение каадраm1l0г0 корня из вещественного числа (типа
doub1e).
Но, поскольку эти операции фундаментально rазличны, применеllие мсхаНlпма
перегрузки методов в этом случае сводит на нет его первоначальную цель. (Тшсой
стиль программирования, наверное, подошел бы лишь для того, чтобы ввести в
заблуждение конкурента.) На практике перегружать имеет смысл только Te'~HO
связанные операции.
Автоматическое преобраэование типо~ и перегруэка
Как упоминалось в модуле
2,
в С++ предусмотрено автоматическое преобра­
зование типов. Эти преобразования также ПРJfменяются к параметрам перегру­
женных функций. Рассмотрим, например, следующую программу.
С++: PYI<OBOACTBO для начинающих
/*
Автоматические
преобразования
выбор выполняемой функции из
могут
оказать
влияние
перегруженных ее
на
версий.
*/
iinclude <iostream>
using namespace std;
void f(int
х);
void f(double
х);
int main () (
int i = 10;
double d = 10.1;
short 5 = 99;
float r = 11.5Fi
f(i); //
f(d); //
вызов
версии
вызов
версии
f(int)
f(double)
//
При cnеду.ащкх вuзо.ах ФУНКЦИЙ происходит
//
автоматическое преобравоаавие типо•.
f(s); //
f(r); //
return
вызов
версии
вызов
версии
f(int) - преобразование типов
f(double) - преобразование типов
О;
void f (int х)
cout « "в
(
функции
f(int): " «
void f(double х) (
cout « "В функции f(double): "
х
«
«
"\п";
х
«
"\п";
При выполнении эта программа генерирует такие результаты.
в
функции
В
функции
f(int): 10
f(double): 10.1
283
284
Модуль 6. О фУНКЦИЯХ подробнее
f(int): 99
f(double): 11.5
в функции
В
функции
В этом примере определеиы две версии функции f (): с
метром. При этом функции f () можно передать
int- и double-napa5hort- или f10аt-значение. При
передаче 5hоrt-параметра С++ автоматически прео6разует его в iпt-значение, и
поэтому вызывается версия f (! n t) . А при передаче f10a t -параметра его значение
пре06разуется в dоublе-значение, и поэтому вызывается версия
f (double).
Однако важно понимать, что автоматичеСlсие пре06разования применяются
толы<О в случае, если
tleT
непосредственного совпадения п типах параметра
It
ар­
гумента. Рассмотрим предыдущую программу с добавлением версии ФУНlЩИИ
f (),
11
которая имеет short-параметр.
Предыдущая
лрограмма
с
добавлением
версии
f
(S)10rt) .
#include <iostream>
using namespace std; ,
void f(int х);
void f(5hort х); 11
void f(double х);
ДобaanRек Bep~
f(short).
int main () (
int i = 10;
double d = 10.1;
short s = 99;
float r = 11.5F;
f (i) ; 11
f (d) ; 11
f
(5) ; 11
1/
1/
f (r) ; 11
return
Вызов
версии
Вызов
версии
Теперь
вызвана
преобразовании типов
.ерс:ик
Вызов
О;
void f(int
будет
f (int) .
f (double) .
х)
{
версия
нет,
f(5hort) .
поскоnьку
З~есь
опре~enева
f (short) .
версии
f(double) -
преобразоsание
типов.
С++: руководство ДЛЯ начинающих
cout
«
"В ФУНКЦИИ
void f(short
cout « "В
х)
"
«
f(short):
"
f(int):
х
«
285
"\п";
{
функции
void f(double х) {
cout « "В функции f(double):
«
"
«
«
х
х
"\п";
«
"\п";
Теперь результаты 8ЬШОЛ\tСНИЯ этого варианта программы таковы.
В
функции
В
функции
В
функции
В
функции
f(int): 10
f(double): 10.1
f(short): 99
f(double): 11.5
~npocы для текущего контроля - - - - - - - 1.
2.
Какие условия должны соблюдаться при псрегрузке функции?
Почему перегруженные функции должны выполнять близкие по значе­
нию действия?
3.
Учитывается ли тип значения, возвращаемого функцией, в решении о вы­
боре нужной 8era:t~ ~Сl?е':еуженной функции?·
wщ·тЦ"МаЗДClНИfВIеPlШPv-eнн&JDфу.н_Й
вывода
Print. срр - В этом проекте
Mbl создадим коллекцию перегружснных функ­
ций, которые предназначены для вывода на экран данных раз­
личного типа. Несмотря на то что 80 многих случаях можно обойтись ИlIСТРУК-
1. Перегруженные функции должны иметь одинаковые имена, но объявление их па­
раметров должно быть различным.
2. Перегруженные функции должны выполнять близкие по значению действия, по­
скольку перегрузка функций подразумевает связь между ними.
З.
Нет, ТИПЫ значений. возвращаемых функциями, могут различаться, но это разли­
чие не влияет на решение о выборе нужной версии.
286
МОДУЛЬ 6. Офункци~х подробнее
цией сои t, программисты всегда предпочитают иметь альтернативу. Создавае­
мая здесь коллекция функций вывода и послужит такой альтернативой. (Вот в
языкахJаvа и С# как раз используются функции вывода данных, а не операторы
вывода.) Создав перегруженную функцию вывода, вы получите возможность
выбора и сможете воспользоваться более подходящим для вас вариантом. Более
того, средство перегрузки позволяет настроить функцию вывода под I<онкретные
требования. Например, при выводе результатов булевого типа вместо значений О
и
1 можно выводить слова "ложь"
и "истина" COOTBeTCTBeHIIO.
Мы создадим два набора функций с именами println () ирriпt (). Функция
println () выводит после задашюго аргумента символ новой строки, а функция
print () - только аргумент. Например, при выполнении этих строк кода
'print (1);
println ('Х');
рriпtlп("Перегрузка ФУНКЦИЙ - мощное средство! ");
print(18.22):
будут выведены такие данные.
lX
Пере грузка
ФУНКЦИЙ -
мощное
18.22
средство!
В этом проектефункции
Ьоо1,
print1n () и print () перегружаются для данных типа
char, int, 1ong, char* и double, но вы сами можете добавить ВОЗ~ОЖ­
насть вывода данных и друrnх типов.
Последовательность действий
1.
Создайте файл с именем Р r
i n t . срр.
2.
Начните проект с таких строк кода.
/*
Проект
6.1.
Создание
для
перегруженных
отображения
данных
ФУНКЦИЙ
print()
различного
и
print1n()
типа.
*/
#include <iostream>
using narnespace std:
3.
добавьте прототипы функций
//
//
println ()
и
print ().
Эти функции обеспечивают после вывода аргумента
переход на
новую строку.
С++: руководство МЯ начинающих
void
void
void
void
void
void
287
println(bool Ь);
println(int i);
println(long i);
println(char ch);
println(char *str);
println(double d);
ф
ф
:J:
10
О
Q.
g
с:
х
с;::
// Эти функции не обеспечивают
// переход на новую строку.
void print(bool Ь);
voie print(int i);
void print(long i);
void print(char ch);
void print(char *str);
void print(double d);
после
вывода
функций
{
if(b) cout «
else cout «
"истина\п";
"ложь\п";
void println(int i)
cout «
i «
"\п";
void println(long i)
cout «
i «
"\п";
void println(char ch)
cout «
ch «
"\п";
~
~.
о
4. Обеспечьте реализацию функций println ().
// Набор перегруж~нных
void println(bool Ь)
аргумента
:s:
::r
println().
Модуль 6. О функциях подробнее
288
..................................................................................................................................
void println(char *str)
{
cout «
str «
"\п";
void println(double d)
cout «
"\п";
d «
Обратите внимание на то, что каждая функция из этого набора после
i:lP-
гумента выводит си:\шол новой строки. Кроме того, отметьте, что версия
print 1п (bool)
при выводе значения булевого пша отображает слопа
"ложь" ИЛИ "истина". П[)и: жслашш эти слопа можно выводить ПРОПИСIIЫ­
ми бу/(вами. Это ДOI<азьшает, насколько легко можно настроить ФУНКЦИИ
вывода под конкретные требования.
5.
Реализуйте функции
1/
print ()
следующим образом.
Набор пере груженных функций
void print(bool
Ь)
{
if(b) cout «
e1se cout «
"истина";
"ложь";
void print(int i)
cout «
i;
void print(long i)
cout «
i;
void print(char ch)
cout «
ch;
print().
С++: PYI<OBOACTBO ДЛЯ начинающих
289
void print(char *str)
(
cout «
str;
void print(double d)
cout «
d;
Эти функции аНёtЛОШЧНbl функниям
println ()
за исключением того, что
они не ВЫВОДЯТ символа новой строки. Поэтому следующий ВЫВОДИМЫЙ
символ появится на той же строке.
6.
Вот как выглядит полный текст программы
Print. срр.
/*
Проект
6.1.
Создание
для
пере груженных
отображения
данных
функций
print()
различного
и
.
println()
типа.
*/
#include <iostream>
using namespace std;
// Эти функции обеспечивают
// переход на новую строку.
void println(bool Ь);
void println(int i ) ;
void println(long i ) ;
void println(char ch);
void println(char *str);
void println(double d);
после
// Эти функции не обеспечивают
// переход на новую строку.
void print(bool Ь);
void print(int i);
void print(long i ) ;
void print(char Ch)i
void print(char *str);
вывода
после
аргумента
вывода
аргумента
Модуль 6. О функциS1Х подробнее
290
................................................................................................................................
void print(double d);
int main ()
println(true);
println(lO);
рriпtlп("Это
простой тест");
println ( 'х' ) ;
println(99L);
рriпtlп(12З.2З);
print("BoT несколько
print(false);
print (' ');
print(88);
print(' ');
print(lOOOOOL):
print (' ');
print(lOO.Ol):
println("
return
значе~ий:
Выполнено!");
О;
// Набор перегруженных
void println(bool Ь)
функций
{
if(b) cout «
else cout «
"истина\п";
"ложь\п";
void println(int i)
cout «
");
i «
"\п";
void println(long i)
println().
С++:
cout «
i «
PYI<OBOACTBO
М9. начинающих
291
"\n";
ф
ф
:I:
void println(char ch)
cout «
ch «
10
о
g
"\п";
с:
х
СА
:s:
::r
:.:'
:I:
void println(char *str)
~
{
О
cout «
str «
"\n";
void println(double d)
cout «
d «
"\n";
// Набор перегруженных
void print(bool Ь)
функций
{
if(b) cout «
else cout «
"истина";
"ложь";
void print(int i)
cout «
i;
void print(long i)
cout «
i;
void print(char ch)
cout
« ch;
print().
Модуль 6. О фУНI<ЦИ~Х подробнее
292
void print(char *str)
(
cout «
str;
void print(double d)
cout «
d;
При выполнении этой программы получаем такие резулыаты.
ис'rина
10
Это
простой
тест
х
99
123.23
Вот
несколько
значений:
ложь
88 100000 100.01
ВыnолнеНJ!
ВАЖНОI
rf:8 Apгyмeыzы"..aapeдaвaaмыe
функции по умолчанию
в С++ мы може~ придап) параметру некоторое значение, которое будет ав­
томатически ИСПОЛЬЗOlЗ<lIЮ, если при lIbl30Be функции не задается аргумент, ':0ответствующий этому параметру. Аргументы, передаваемые функции по у.МОЛ­
чанию, можно использовать, чтобы упростить ()бращение к сложным ФУНКIlИЯМ,
а также в качестве "сокращенной формы" перегрузки ФУНКЦИЙ.
Задание аргументов, перелаваемых ФУНIЩIIИ по умолчанию, синтаксически
аналогично инициализаЦIIИ переменных. PacC-\ютрим следующий пример,
n ко­
myfunc (), приннмающая два аргумента типа int
по умолчанию значениями О 11 100.
тором объявляется функция
с дейстоующими
void myfunc(int
=
х
О,
int
у
~
После такого объявления ФУНКЦИЮ
100);
myfunc () можно вызвать одним из трех
следующих способов.
myfunc(l, 2); //
myfunc(10);
1/
11
Передаем явно
ПеРЕдаем для
а
для
заданные
значения.
параметра х
параметра
у
значение
позволяем
10,
применить
С++: руководство для начинающих
значение,
//
myfunc(); //
//
задаваемое
Для обоих
параметров
применить
значения,
по умолчанию
293
(100).
х и у позволяем
задаваемые по умолчанию.
ф
ф
:z::
1, а параметру у - число 2.
Во время второго вызова параметру х передается число 10, а параметр у по умол­
чанию устанавливается равным числу 100. Наконец, в результате третьего вы­
10
зова как параметр х, так и параметр у по умолчанию устанавливаются равными
1;;:
значениям, заданным в объявлении функции. Этот процесс демонстрируется в
:..:
При первом вызове параметру х передается число
Демонстрация
по
использова~ия
аргументов,
передаваемых
умолчанию.
#include <iostream>
using namespace std;
void myfunc(int
х
О,
int
у
100); 1/ в ПРО~ОТИП8 функции
/ /
З8ДaJW apZ'YК8Н'1'11 по
/ /
УНОJ1Ч&НИID ДJUI обоих
//
параке~ов.
«
"\п";
int main ()
(
myfunc(l, 2);
myfunc(10) ;
myfunc();
return
О;
void myfunc(int
cout «
"х:
х,
" «
int
х
«
у)
",
у:
" «
у
Результаты выполнения этой программы подтверждают принципы использо­
вания аргументов по умолчанию.
х:
х:
х:
1, у: 2
10, у: 100
О, у: 100
О
с::
х
:s:
~
:z::
~
следующей программе.
//
//
§
О
294
МОДУЛЬ 6. О функциS1Х подробнее
При создании функций, имеющих значения аргументов, передаваемых по
умолчанию, необходимо помнить, что эти значения должны быть заданы толь­
ко однажды и причем при первом объявлении функции в файле. В предыдущем
примере аргумент по умолчанию был задан в прототипе функции
myfunc ().
При попытке определить новые (или даже те же) передаваемые по умолчанию
значения аргументов в определении функции I1lyfunc () компилятор отобразит
сообщение об ошибке и не скомпилирует вашу программу.
Несмотря на то что передаваемые по умолчанию аргументы должны быть
определены только один раз, для каждой версии перегружснной функции для
передачи по умолчанию можно задавать разЮllltlые аргументы. Таким образом,
разные версии перегружеНIlОЙ функции могут иметь различные значения аргу­
ментов, действующие по умолчанию.
Важно помнить, что
ncc
парамстры. которыс принимают значения по умол­
'-JaНИЮ, ДОЛЖliЫ быть расположсны справа от остальных. Например, следующнй
прототип функции содержит ошибку.
.
// Неверно!
void f(int
а
= 1,
int
Ь);
Если вы начали определяrь параметры, которые принимают значения по умол­
чанию, нельзя после них указывать параметры, задаваемые при вызове функции
только явным образом. Поэтому следующее объявление также неверно и не бу­
дет скомпилировано.
int myfunc(iloat f,
char *str, 1nt 1=10, int j);
Поскольку для параметра i определено значение по умолчанию, для параме­
тра
j
также нужно задать значение по умолчаНIlЮ.
Включение в С++ возможности псрсдачи аргументов по умолчанию позволя­
ет программистам упрощать код программ. Чтобы предусмотреть максимально
возможное количество ситуаций и обеспечить их корректную обработку, ФУt/К­
ции часто объявляются с большим числом паrаметров, чем необходимо в наи­
более распространснных случаях. Поэтому благодаря применению аргументов
по умолчанию программисту нужно УI<азывать не все аргументы (используемые
в общем случае), а только тс, которые имеют смысл для определенной ситуации.
Сравнение возможности передачи аргументов
по умолчанию с перегрузкой функций
Одним из применений передачи аргументов по умолчанию является "сокращен­
ная форма" перегрузки функций. Чтобы понять ЭТО, представьте, что вам нужно соз­
дать две специальные версии стандартной ФУНКЦIIИ
s t rca t ( ) . Одна версия должна
С++: руководство ДМ1 начинающих
295
присоединять все содержимое одной crpoки к концу другой. Вторая же принимает
третий apryмeHT, который задает количество конкатенируемых (присоединяемых)
символов. Дрymми словами, эта версия должна конкатенировать только заданное
количество символов одной строки к концу другой. Допустим, что вы назвали свои
функции именем mY5trcat
() И предложили такой вариант их прототипов.
void mY5trcat(char *51, char *з2, int 1еп);
void mY5trcat(char *51, char *52);
Первая версия должна скопировать 1еп символов из строки
51.
Вторая версия копирует всю строку, адресуемую указатеж'м
адресуемой указателем
52
52,
в конец строки
в коиец строки,
51, Т.е. действует подобно стандартной функции 5 t r са t ( ) .
Несмотря на то что для достижения поставленной цеЛII можно реализовать
две версии функции тУ5 t
rca t ( ) , есть более простой способ решения этой зада­
чи. Используя ВО311ЮЖIЮСТЬ передачи аргументов по умолчанию, можно создать
только одну функцию
mY5trcat (),
которая заменит обе задуманные ее версии.
Реализация этой идеи продемонстрирована в следующей программе.
11
Создание
пользовательской версии функции
5trcat()_
#include <io5tream>
#inc1ude <cstring>
using namespace 5td;
void mY5trcat(char *51, char *52, int len = О); 11 Здесь
11 параиетр lеп по умоnчаиию
11 уставёUlnиваетс. pёUlВlolМ нуmo.
int main ()
(
char str1[80]
char 5tr2[80]
"Это
тест.";
"0123456789";
mystrcat(str1, 5tr2, 5); 11
cout « str1 « '\п';
Присoeдии5l8И TOnЪXO
5trcpy(str1,
11
"Это
тест.");
mystrcat.(str1, str2); 11
11
11
cout « 5trl « '\п';
Восстанавливаем
ПрисоеДИllJl8И
.CID
5
сиквonо•.
str1.
строку,
посхоnьху паранетр
len
умоnчаиию равен нуmo.
по
296
МОДУЛЬ 6. О функциS1Х подробнее
return
О;
Пользовательская
//
версия функции
void mY5trcat(char *51, char
*з2,
5trcat().
int 1en)
(
/i Находим конец строки 51.
while(*s1) 51++;
if(len
==
О)
len
while(*s2 && lcn)
*51 = *з2; //
51++;
52++;
len--;
*5 1 =
I \
о
1;
/
/
=
5trlen(52);
Копируем
Завершаем
символы.
строку
~; 1 нулевым символом.
Результаты l3ьшолнсния этой I1РОГРаммы выглядят так.
Это
теСТ.01234
Это
тест.О12345б789
Здесь функция
параметром
len
mY5trcat () присоединяет len символов с'!роки, адресуемой
52, J< J<OIlUY строки, адресуемой параметром 51. Но если значеtlИе
равно нулю, как и в случае разрешения передачи этого аргумента по умол­
чанию, функция
раметром
mY5trcat ()
присоединит к строке
51
всю строку, адресуемую па­
52. (Другими слооами, если значение len равно О, фУНI<ция mY5trc~t ()
5trcat ().) Используя для параметра
действуст подобно стандартной функции
len возможность передачи аргумента по умолчанию, обе операции можно объ­
единить о одной функции. Этот пример позволил продемонстрировать, как аргу­
менты, передаваемые ФУНКЦИИ по умолчанию, обеспечивают основу для сокра­
щенной формы объявления IIсрегружеНlIЫХ функций.
Об использовании аргументов. передаваемых
по умолчанию
Несмотря на то что аргументы, передаваемые фуикции по умолчанИЮ,
-
ОЧЕ'НЬ
мощное средство программирования (при их корректном использовании), с НИМИ
С++: РУКОВОДС:ТВО дl\Я начинающих
MOryт иногда возникать проблемы. Их назначение
297
- позволить функции эффек­
тивно выполнять свою работу, обеспечивая при всей простоте этого механизма
значительную гибкость. В этом смысле все передаваемые по умолчанию apry-
менты должны отражать способ наиболее общеro использования функции или
альтернативноro ее применения. Если не существует некоторого единоro значе­
ния, которое обычно присваивается тому или иному параметру, то и нет смысла
объявлять соответствующий apryMeHT по умолчанию. На самом деле объявление
аргументов, передаваемых функции по умолчанию, при недостаточном для это­
го основании приподит к деструктуризации кода, поскольку такие аргументы
способны сбить с толку любого, кому придется разбираться 8 такой программе.
Наконец,
n основе
ИСПОЛЬЗОВaJlИЯ аргументов по умолчанию должен лежать, как
у врачей, принцип "не навредить". Другими словами, случайное ltСIlOльзоваJlие
арГУМСJJта 110 умолчаНI1Ю lIе ДОЛЖIIО щmвссти к I1еобраТlIМЫI\I ОТРIщатеЛЫIЫМ
последствиям. Ведь такой <lpIYMCHT можно просто забыть указать при Dызове не­
которой функции, и, если это случится, подобный промах не должен вызвать, на­
пример, потерю важных данных!
~ПРОСЫ ДЛЯ текущего КОНТРОЛЯ
1.
--------
Покажите, как объявить vоid-функцию с именем
count (), которая при­
нимает два iпt-Ilараметра (а и Ь), устанавливая их по умолчанию равны­
минулю.
2.
МОЖIЮ ли устанавливаемые по умолчанию аргументы объявить как в
прототипе функции, так и в ее определении?
З. Корректно ли это объявление? Если нет, то почему?
int f(int х=10, double Ь);·
1. void count (int
2.
а=О,
int
Ь=О);
Нет, устанавливаемые по умолчанию аргументы должны быть объявлены лишь в
первом определении функции, которым обычно служит ее прототип.
3.
Нет, параметр, который не устанавливается по умолчанию, не может стоЯ1Ъ после
параметра, определяемого по умолчанию.
298
МОДУЛЬ 6. О фУНI<ЦИЯХ подробнее
H01
tl
СМ Оересрузка функ' '.иl4
и неоднозначность
Прежде чем завершить этот модуль, мы должны исследовать вид ошибок, уни­
кальный дЛЯ С++: неоднозначность. Возможны ситуации, в которых компилятор
не способен сделать выбор между двумя (или более) корректно перегруженными
функциями. Такие ситуации и называют lIеодUОЗUQЧllЫМU. Инструкции, создаю­
щие неоднозначность, являются ошибочными, а программы, которые их содер­
жат, скомпилированы не будут.
Основной причиной нсоднозначности в С++ является автоматическое преоб­
разопание типов. В С++ делается попытка автоматичеС1Ш преобразовать тип ар­
гумеllТОП, IIспользуемых для Dызоuа функции, 11 ТШl параметроп, определснных
функциеif. Рассмотрим пример.
int myfunc(double d);
//
...
cout «
myfunc('c'); //
//
Ошибки нет,
выполняется
преобразование
типов.
Как отмечено в комментарии, ошибки здесь нет, поскольку С++ автоматически
преобразует символ' с' в его dоublе-эквивалент. Вообще говоря, в С++ запре­
щено довольно мало видов преобразоnзний типов. Несмотря на то что автома­
тическое преобразование типов
-
это очень удобно, оно, тем не менее, является
главной причиной неоднозначности. Рассмотрим следующую программу.
//
Неоднозначность
вследствие лере грузки функций.
#include <iostream>
using namespace std;
myfunc(float i);
double myfunc(double i);
Поаt
int main ()
(
// Неоднозначности нет, вызывается
// функция myfunc(double).
cout « rnyfunc(10.1) « " ";
//
Здесь
лрисутствует
неоднозначность.
С++: руководство ANil начинающих
Ошибка!
cout «myfunc(10); //
//
return
шуfunс
()
299
(~версиа фУНКЦИИ
c.nедует адеа. JUlSaa'l'lo?)
О;
float myfunc(float i)
return i;
double myfunc(double i)
return -i;
Здесь благодаря перегрузке функция
либо типа Поа t, либо типа
cout «
double.
myfunc(lO.l) «
11
myfunc ()
может принимать аргументы
При выполнении строки кода
";
не возникает никакой неОДIlОЗl1ачности: компилятор "уверенно" обеспечивает вы­
зов функции
myfunc (double),
поскольку, ссли не задано явным образом иное,
все литералы (константы) с плавающей точкой в С++ автоматически получают
тип
double. Но при вызове функции myfunc () с аргументом, рапным целому
10, в программу вносится неоднозначность, поскольку компилятору неиз­
вестно, в какой тип ему следует преобразопать этот аргумент: Поа t или double.
числу
Оба преобразования допустимы. В такой неоднозначной ситуации будет выдано
сообщение об ошибке, и программа не скомпилируется.
На примере предыдущей программы хотелось бы подчеркнуть, что неодно­
значность в ней вызвана не перегрузкой функции
ды ДЛЯ приема
myfunc (), объявленной дваж­
double- и float-аргумента, а использованием
myfunc () аргумента нсопределенного для
вызове функции
при конкретном
прсо6разования
типа. Другими словами, ошибка состоит не в перегрузке функции
myfunc () ,
а в конкретном ее вызове.
А вот еще одии при мер неоднозначности, вызванной автоматическим прео6разованием типов в С++.
//
Еще
один лример
ОШИБКИ,
#include <iostream>
using namespace std;
вызванной
неодкозкачностью.
300
Модуль 6. О фУНI<ЦИ~Х подробнее
char myfunc(unsigned char ch);
char rnyfunc(char ch);
int rnain ()
(
cout «
cout «
return
rnyfunc('c'); // Здесь вызывается myfunc(char).
rnyfunc(88) « " "; // ~бка! Здесь вноситCJI
// Rеоднозначвос~ъ, поскольку нексно, в
//
значение
//
чиcnо
JCaKoro
88: char
типа cneдYe~ преобразова~ъ
или
unsiqned char?
О;
char myfunc(unsigned char ch)
return ch-l;
char myfunc(char ch)
return ch+l;
в С++ типы
unsigned char
и
char
не ЯВ.'lЯЮТСЯ сущестnешю неОДIЮЗШ~Ч­
lIЫМИ. (Это различные типы.) Но при вызове фушщии
ным аргументом
88
rnyfunc ()
с ЦС.i'IОЧИСЛLН­
компилятор "не знает", какую функцию ему выполнить, Т.С.
в значсние какого типа СМУ следует преоGраЗОВilТЬ число88: типа
char или Т1ша
unsigned char? Ведь оба преобразования здесь вполне допустимы!
Неоднозначность может быть также ВЫЗDаlla I1СIЮJlЬЗOIJallИем 8 перегруж(-н­
ных функциях аргументов, передаваемых по умолчанию. Для примера расо.lO­
трим следующую программу.
//
и еще
один
пример внесения неоднозначности.
#include <iostream>
using паmеэрасе std;
int myfunc(int i ) ;
int myfunc(int i, int j=l);
С++: руководство дl\Я начинающих
301
int main ()
cout «
cout «
myfunc(4, 5) «
myfunc(10); 11
" "; //
Веодвоsначвостъ,
1/ oro
return
неоднозначности нет
поскonьку.еRСВО,
.nи CЧJI1Татъ паракеlJ!P
/I
по УМОnЧaвJШ,
//
фунхции
j
зад_аемым
то .nи :вызwватъ верCИlO
myfunc ()
с одним паракетрок'?
О;
int myfunc(int i)
return i;
int myfunc(int i, int j)
return i*j;
Здесь в первом обращении к ФУНКЦIIИ
у компилятора не'г никаких сомнеllИЙ
с
( i n t i, i n t j), Т.е. никакой
myfunc () задастся jtпа аргумента, поэтому
[) выборе иужиой ФУНКЦИИ, а именио myfun-
неОДlIозначности в этом случае не при вносится. Но
при втором обращении к функции myflJnc
"lIe знает", то ли
один apIYMeHT, то ли
() мы СТМКlшаемся снеоднозначностью,
myfunc () ,
ПОСI<ОЛЫ<У компилятор
ему вызвать версию функции
которая принимает
использовать возможность пере;taчи аргу­
мента по умолчанию применительно к версии, которая пршшмаст два аргумента.
Программируя на языке С++, вам еще не pa:~ придется СТОЛ1шуты~я с uшибка­
ми неuднозначности, которые, к сожалению, очень легко "проникают" в програм­
мы, и только опыт и практика помогут вам избавиться от IШХ .
• .IAL'--%A 4
1.
2.
3.
9
СQМQкоыrpО4Я ПО.мод.)/АюD
Какие существуют два способа передачи аргумента в подпрограмму?
Что представляет собой ссылка в С++? как создается ССЬUЮЧНhIЙ параметр?
Как вызвать функцию
f () с аргументами ch и i в про грамме, содержащей
следующий фрагмент кода?
int f(char
/1 ...
&с,
int *i);
302
Модуль 6. О ФУНКЦИSlХ подробнее
char ch = 'х';
int i = 10;
4.
Создайте vоid-функцию
round (),
которая оКрУГЛЯет значение своего
dou-
Ыe-apryмeHтa до ближайшего целого числа Позаботьтесь о том, чтобы ФУНК­
цИЯ
round ( )
использовала ссьuючный параметр и возвращала результат
округления в этом параметре. Можете исходить из предположения, что все
округляемые значения положительны. Продемонстрируйте использование
функции
round ()
в программе. Для решения этой задачи используйте стан­
дартную библиотечную функцию
double modf(double
Функция
пит,
modf () . Вот как выглядит ее прототип.
double
~i);
modf () разбивает число пит на целую и дробную части. При
этом она возвращает дробную часть, а целую размещает впеременной,
адресусмой lIараМСТРОl\l
мо включить в
5.
i. ДЛЯ IIСПОJJЬ30ЩIIIИЯ функции modf () необходн­
программу заголовок <ста th>.
Модифицируйте ссылочную версию функции
swap ( ) , чтобы
она, помимо
обмена значениями двух своих аргументов, возвращала ссылку на мень­
ший из них. Назовите эту функцию
6.
7.
8.
min _ swap ( ) .
Почему функция не может возвращать ссылку на локальную переменную'!
Чем должны отличаться списки параметров двух перегруженных функций?
В проектс
6.1
мы создали
print () -
и
println () -коллекции
функций.
Добавьте в эти функции второй параметр, КОТОрЬП1 бы задавал уровень OTt;TYпа. Например, функцию р r i
nt
() можно было бы в этом случае вызвать Till<.
print("TecT", 18);
При выполнении этого вызова был бы сделан отступ шириной в
18 пробе.1ОВ,
а затем выведено слово "тест". Обеспечьте параметр отступа нулевым значени­
ем, устанавливаемым по умолчанию, если он не задан при вызове. Нетрудно
догадаться, что в этом случае отступ будет попросту отсутствовать.
9.
При заданном прототипе функции
bool myfunc(char ch, int
а=10,
int
Ь=20);
продемонстрируйте возможные способы вызова функции
tO.
rnyfunc ( ) .
Поясните кратко, как перегрузка функции может внести в программу не­
однозначность.
"" Ответы на эти вопросы можно найти на Web-странице данной
Назометку
n"
книги по адресу: http://www.osborne.com.
МОДУЛЬ 7
Еще о типах данных
и оператора~
7.1.
7.2.
Спецификаторы типа
7.3.
7.4.
7.5.
7.6.
Статические переменные
7.7.
7.8.
7.9.
7.10.
7.11.
7.12.
Поразрядные операторы
const
и
Спе1tификC:lТОР класса памяти
volatile
extern
Регистровые переменные
ПереЧИСЛСIlНЯ
Ключевое слово t
ypede f
Операторы сдвига
Оператор "знак вопроса"
Оператор "запятая"
Составные операторы присваивания
Использование ключевого слова
.
sizeof
304 Модуль 7. Еще о типах данных и операторах
................................................................................................................................
в этом модуле мы возвращаемся к теме типов данных и операторов. Кроме
уже рассмотренных нами типов данных, в С++ определены и другие. Одни ТИIlЫ
данных состоят из модификаторов, добавляемых к уже известным вам типам.
Друmе включают переЧl1сления, а третьи используют ключевое слово
type,jef.
С++ также поддерживает ряд операторов, которые значительно расширяют об­
ласть применепия языка
позволяют решать эадачи программировапия в весь­
If
ма широком диапазоне. Речь идет о пораЗРЯДIfЫХ операторах, операторах сдuига,
а также операторах
в
"?" и si zeof.
Иvоlаtilе
С ++ определено два спецификатора Т1!па,
Jюторые оказывают l3J1июше на )'0,
каким образом МОЖНО получить доступ" переМСIIIIЫМ или МОдllфИЦИРОВа1Ъ ИХ.
спсцпфикаторы
const и volatile.
::.tTO
Официально они именуются CV-С11ецuфuкаmо­
РaJ\ПL и должны предшесгвоваТI, базовому типу при объяnлешlИ псременноЙ.
СпецИфикатор типа
const
Перемснпые, объявленные с использованием спе!1ИфИI~атора
изменить свои значения
cons t, lIе Moryт
const.·Jlc-
время выполнеНIIЯ IIрограммы. Выходит,
/)0
ременную нельзя назвать "настоящей" перемеllllOИ. Тем не менее ей можно IlРИ­
своить некоторое начальное значение. Например, при выполнении инструюши
const int
тах
users
=
создается iпt-перемеНllая
9;
max_users,
которая содержит значение
9,
и это зна­
чение программа изменить .Уже не может. Но саму п~ремеНliУЮ можно исполюо­
ватъ в других выражениях.
Чаще всего спецификатор
const
используется для создания шtеIl0ваllНЫХ1((J1l­
сmаum. Иногда в программах многократно применяется одно и то же значение ДЛЯ
различных целей. Например, в программе необходимо объявить несколько раз­
личных массивов таким образом, чтобы все они имели одинаковый размер. Такой
общий ДЛЯ всех массивов размер имеет смысл Р(~illIизовать в виде сопst-пере~н~н­
ной. Затем вместо реалыюro значения можно ИСlJользовать имя этой переменной,
а сели это значение придется впоследствии изменить, вы измените его только в
одном месте программы (а не в объявлении каждого массива). Такой подход ПОЗIЮ­
ляет избежать ошибок и упрш:тить программу. Следующий пример предлагает вам
попробовать этот вид применения спецификатора
cons t
"на вкус".
С++: РУКОВОДСТВО дЛЯ начинающих
305
#include <iostream>
using namespace std;
const int num_employees = 100;
//
~ Соз~аек ИК8ВОВаввуа
//
XOBC~aвтy nuш_empl0уееs,
/1
coдe~ звачение
xo~op. .
100.
:i.nt main ()
{
int empNums[num_employees]i
// Имевоваивая хоис~~а
double salary [n1..1m_employees]; 11 num_employees З~8СЪ
char *names[num_employees]i
11 испоnъsуе~ся дnк задаИИR
11 размеров массивов.
/ /'
...
Если
n этом
при мере понадобится использовать новый размер для массивов,
вам потребустся И3МСИИТЬ только объявление переменной пит _ employees и
перекомпилировать программу. В результате все три массива автоматически по­
лучат новый размер.
Спецификатор
const
также используется для защиты объекта от модифика­
ции посредством указателя. Например, с помощью спецификатора
const
можно
11С ДОПУСТИТЬ, чтобы функция изменила значение объекта, адресуемого ее пара­
метром-указателем. Для этого достаточно объявить такой параметр-указатель с
использош:шием спеЦИфИЮlТора
тель предваряется КJIЮЧСIJЫМ
const. Другими словами, если параметр-указа­
словом cons t, пикаЮiЯ ИНСТРУIЩИЯ этой фушщии
не может модифицировать переменную, адресуемую этим параметром. Напри­
мер, функция
nega te ()
в следующей КОРОТКОЙ программе возвращает результат
отрицания значения, на которое указывает параметр. Испольэо"ание специфи­
катора
const
в объявлешlИ параметра не позволяет коду ФУIIКЦИИ модифициро­
вать объект, адресуемый этим параметром.
/1
Использование
сопst-параметра-указателя.
#include <iostream>
using namespace std;
int negate(const int *val);
306
Модуль
7. Еще о типах данных и' операторах
int main ()
(
int result;
int v = 10;
result
cout «
cout «
return
negate(&v);
=
"Результат
отрицания"
«
v «
"
равен
" «
result;
"\п";
О;
int negate(const int *val) 11 +//
Так параметр
val
объявnиетCR соnst-ухазатenек.
return - *val;
Поскольку параметр
val
является сопst-указателем, функция
negate () не
negate ()
может изменить значение, на которое он указывает. Так как функция
val, программа скомпи­
nega te () переписать так,
не пытается изменить значение, адресуемое параметром
лируется и выполнится корректно. Но если функцию
как показано в следующем при мере, компилятор выдаст ошибку.
// Этот вариант работать не
int negate(const int *val)
будет!
{
*val = - *val; /1
Ошибка:
1/
значение,
параметром
val,
адресуемое
изменять
нельзя.
return *val;
в этом случае программа попытается изменить значение переменной, на кото­
рую указывает параметр
val,
но это не будет Рt'ализовано, поскольку
val
объяв­
лен сопst-параметром.
Спецификатор
const
можно также использовать для ссылочных параметров,
чтобы не допустить в функции модификацию перемеНtIЫХ, на которые ссылают­
ся эти параметры. Например, следующая версия функции
negate ()
некоррек­
тна, поскольку в ней делается попытка модифицировать переменную, на кото­
рую ссылается параметр
val.
С+'+: руководство ДI\~ начинающих 3О7
// Этот вариант также не будет
int negate(const int &val)
работать!
(
val
-val; //
//
return val;
=
Ошибка:
параметр
значение,
val,
на которое ссылается
изменят. нельзя.
8.
~
ф
с:
О
s
х
2i
СпецИфикатор типа
:I:
:I:
vola tile
~
х
Спецификатор vo 1 а t
i 1 е сообщает комmшятору о том, что значение соответству­
ющей переменной может быть изменено в программе неявным 06ра.10М. Например,
адрес IlСIСОТОРОЙ глобальной персменной можст псредаваться управляемой преры­
ваниями подпрограмме тактиропшIИЯ, которая обнопляет эту переменную с прихо­
дом каждого импульса сигнала врсмеШI. В такой ситуации содержимое переменной
изменяется бе.э использопания явно заданных инструкций программы. Существуют
веские ОСНОDания для того, чтобы сообщить компилятору о внешних факторах из­
менения переменноЙ. Дело в том, что С++-компилятору разрешается автоматически
оптимизировать определенные выражения в предположении, что содержимое той
или иной переменной остается неизменным, если оно не нюшдится в левой части
ШiСТРУКЦИИ присваив.:"1Ния. Но ссли нскоторые факторы (внешние по отношению к
программе) изменят значение этого поля, такое предположение окажется неверным,
в результате чего MOryт uозникнуть проблемы. Для решения подобных про6лем не­
обходимо объявлять такие переменные с ключевым словом
volatile.
volatile int current_users;
Теперь значение переменной
current_users будет опрашиваться при каждом
ее использовании.
~ПРОСЫ для текущего I<ОНТРОЛЯ
1.
2.
_ _ _ _ _ _ __
Может ли значение сопst-переменнойбыть изменено программой?
Как следует объявить переменную, которая может изменить свое значе­
ние в результате воздействия внешних факторов?-
1.
2.
Нет, значение сопst-перемеlUlОЙ программа изменить не может.
Переменную, которая может измеmrrъ свое значение в результате воздействия
внешних факторов, нужно 06ъЯВИ1Ъ с использованием спецификатора volatile.
О
с:
~
О
~
ш
308
МОДУЛЬ 7. Еще о типах данных и операторах
Спецификаторы классов памяти
С++ поддерживает пять спецификаторов классов памяти:
auto
extern
register
static
mutable
С помощью этих ключевых слов компилятор получает информацию о том, как
должна ХРШlИться переМСllllая. Спецификатор классов памяти необходимо ука­
зьшать в начале объявления переменной.
Спецификатор
mutable применяется
TOJl),KO к объектам классов, о которых
I)(;'ЧI) вперСЮI. OCT,Ulbllble спенифшсаторы мы расоютрим в этом раЗД~J1С.
СпеЦИфикатор класса памяти
Спецификатор
auto
auto (достался языку С+-! от С "по наследству") объявляет
локальную перемеНliУЮ. Но 011 используется дuволыJO редко (возможно, Ba~1 НН­
когда и не доведется ПРИМСIlИТЬ его), ПОСКОJIЫ':У локальные lICpeMCJ-IlIbIе являют­
ся "автоматическими" по умолчанию. Вряд ли вам попадется это ключевое слово
и в чужих программах.
Все программы, которые мы рассмаТРИВaJlII ДО сих пор, имели довuлыlO CI<P,)l\1-
ный размер. Реальные же компьютерные программы гораздо больше. По мере уве­
личения размера файла, содержащего программу, время КОМШIJIЯЦИИ СТaIЮRИ rся
иногда раздражающе долгим. В этом случае следует разбить программу на не­
СКОЛЫ<О отдельных файлов. После этого Ilебол i>llIие изменения, вносимые D O;OIН
файл, не потребуют перекомпиляции осей программы. При разработке БОЛЫllИХ
проектов такой многофайловый подход может С:ЖОIJOМИТI) существенное премя.
Реализовать этот подход позволяет ключевое слово ехtеrп.
В программах, которые состоят из двух или более файлов, каждый файл должен
"знать n имена и типы глобальных переменных, используемых программой в целом.
Однако нельзя просто объявить копии глобальных переменных в каждом файле.
Дело в том, что в С++ программа может включать только одну копию каждой гло­
бальной переменной. Следователыю, если вы попытаетесь объявить необходимые
глобальные переменные в каждом файле, возникнут проблемы. Когда комлDtI08щик попытается объединить эти файлы, он обнаружит дублированные глобальные
С++: PYI<OBOACTBO Af\JI начинающих
309
переменные, и. компоновка npoграммы не состоится. Чтобы выйти из этоro затруд­
нительноro положения, достаточно оБЪЯВИ7Ь все глобальные переменные в одном
файле, а в других использовать ехtеrп-06ъявления, как показано на рис.
7.1.
~
а.
Файл :Е1
ФайnF2
int х, у:
char ch;
extern int х, у:
extern char ch;
ф
t:
О
S
х
::о
з:
з:
int main ()
void func22 ()
~
{
{
t:
...
If
О
х
}
х
=
~
у/ТО;
О
Ф
}
~
ш
void func1 ()
void func23 ()
{
{
х
=
123;
У
}
Рис.
= 10;
}
7.1. Использование глобй.льных nереме1IНЫХ в от­
дельно 1соАmuлируе.ыых АЮдулях
в файле
F 1 объявляются
и определяются переменные х, у и
пользуется скопированный из файла
ch.
В файле F2 ис­
Fl список глобальных переменных, к объ­
extern. Спецификатор extern
явлению которых добавлено ключевое слово
делает переменную известной для модуля, но в действительности не создает ее.
Друпп.ш словами, ключевое слово
extern
предоставляет компилятору инфор­
мацию о типе и имени глобальных nepeMetlНblX, повторно не выделяя для них
памяти. Во время компоновки этих двух модулей все ссылки на внешние пере­
менные будут определены.
До сих пор мы не уточняли, в чем состоит различие между объявлением и
опреltелением переменной. но здесь это очень важно. При объявлении переменной
приспаивается имя и тип, а посредством определения для переменной выделяется
па.мять. В большинстве случаев объявления переменных одновременно являют­
ся определениями. Предварив имя.переменноЙ спецификатором
extern,
можно
объявить переменную, не определяя ее.
Спецификатор компоновки
Спецификатор
extern
extern
позволяет также указать, как та ИJШ иная функция
связывается с программой (т.е. каким образом функция должна быть обработана
310
Модуль 7. Еще о типах данных и операторах
................................................................................................................................
КОМПОНОВЩИКОМ). По умолчанию функции компонуются как С++-функции. Но,
используя спецификацию компоновки (специальную инструКцию для компилято­
ра), можно обеспечить компоновку функций, написанных на других языках про­
граммирования. Общий формат спецификатора компоновки выглядит так:
extern
"язык"
прототип_функции
Здесь элемент язык означает нужный язык программирования. Например, эта
инструкция позволяет скомпоновать функцию
extern
rnyCfunc ()
как С-функцию.
void rnyCfunc();
"С"
Все С++-компиляторы подцерживают как С-, так и С++-компоновку. Неко­
торые компиляторы также 110ЗПОЛЯЮТ ИСПОЛЬЗОLJёlТЬ спецификаторы компоновки
для таких ЯЗЫIСОВ, как
мо уточнить
Fortran. Pascal или ВЛSIс. (Эту информапию необходи­
n ДокументаЦIIII, прилагасмой )( вашему IСОМПИJlЯТОРУ.) Испо.ll,ЗУЯ
слеДУЮЩll1i формат СПСЦНФlшаЦlШ КО:lIПОIIOВIШ. МОЖl10 задать не ОДIIУ. а сразу
несколько ФУНКЦИЙ.
extern
"язык"
(
ПротоТИПЫ_функций
Спецификации компоновки используются довольно редко, и вам, возможно,
никогда не I1ридется их при менять.
Переменные типа
s ta t i с -
это переменные "ДОЛI'овременного" хранения, т.е.
они хранят С80И значения в пределах своей функции или файла. От глобальных
они отличаются тем, что за рамками своей функции или файла они неизвестны.
Поскольку Сl1ецификатор
static
по-разному определяет "судьбу" локальных и
глобальных переменных, мы рассмотрим их в отдельности.
Локальные
s ta tiс~переменные
Если к локальной переменной применен мqдификатор
static, то для
нее вы­
деляется постоянная область памяти практически так же, как и для глобальной
переменной. Это позволяет статической переменной поддерживать ее значеlше
между вызовами функций. (Другими словами, в отличие от обычной локальной
переменной, значение stаtiс-переменной не теряется при выходе из функции.)
Ключевое различие между статической локальной и глобальной переменными
С++: руководство ДЛЯ начинающих
311
состоит в том, что статическая локальная переменная известна только блоку, в
котором она объявлена.
Чтобы объявить статическую переменную, достаточно предварить ее тип клю­
чевым словом
s ta t i с.
Например, при выполнении этой инструкции переменная
соип t объявляется статической.
static int count;
Статической пере мен ной можно присвоить некоторое начальное значение.
Например, в этой инструкции переменной соип t присваивается начальное зна­
чение
200:
static int count
= 200;
Локальные stаtiс-перемеиные инициализируются только однажды, в на­
чале выполнения программы, а не при каждом входе
n функцию,
IJ которой ОllИ
объявлены.
Возможность использопаШIЯ статических локальных перемеНlIЫХ важна для
создания функций, которые должны сохранять их значения между вызовами.
Если бы статические переменные не были предусмотрены в С++. пришлось бы
использовать вместо них глобальные, что открыло бы "лазейку" для всевозмож­
ных побочных эффектов.
Рассмотрим пример использования static-переменноЙ. Она служит для
хранения текущего среднего значения от чисел, вводимых пользователем.
/*
Вычисляем
текущее
среднее
значение
от
чисел,
вводимых
пользователем.
*/
#include <iostream>
using namespace std;
int running_avg(int i);
int main ()
int num;
do
cout « "Введите
cin » num;
if
(пит
числа
(-1
означает
выход):
!= -1)
cout «
"Текущее
среднее равно:
"
";
МОДУЛЬ 7. Еще о типах данных и операторах
312
« running_avg(num);
cout « '\п';
while(num> -1);
return
О;
// Вычисляем текущее среднее.
int running_avg(int i)
(
static int sum =
О,
count =
О;
//
Поскольку перемеккwе
/ / sшn и coun t JIВ.JUIIDТCK статическими,
// они сохраняют свои значеНИR между
// вызовами функции running_avg().
sum
=
sum + i;
count++;
return sum / count;
Здесь обе локальные переменные
sum и c:ount объявлены статическими и
инициализированы значением о. Помните. что лля статических переменных ини­
циализация выполняется только один раз (при первом DЬШОJ\нении ФУНКЦJlИ).
а не при каждом входе в функцию. В этой Ilрограмме функция
running aV'J ()
используется для вычисления текущего среДllего значеliИЯ от чис.ел, вводимых
пользователем. Поскольку обе переменные sшп и
count являются статическими,
running_avg ().
они поддерживают свои значения между вызовами функции
что позволяет нам получить правильный реЗУ:lьтат вычислений. Чтобы убедить­
ся в необходимости модификатора
static, попробуйте удалить его из програм­
мы. После этого программа не будет работать корректно, поскольку промежуточ­
ная сумма будет теряться при каждом выходе из функции
running_avg ().
I
Глобальные
s ta tic-перемеННbIе
Если модификатор
static применен к глобальной переменной, то компиля­
тор создаст глобальную переменную, которая будет известна только для файла,
в котором она объявлена. Это означает, что, хотя эта переменная является J ло­
бальной, другие функции в других файлах не имеют о ней "нн малейшего поня-
С++: руководство ДЛЯ начинающих
313
тия" и не MOryT изменить ее содержимое. Поэтому она и не может стать "жертвой"
несанкционированных изменений. Следовательно, для особых ситуаций, когда
локальная статичность оказывается бессильной, можно создать небольшой файл,
s
который будет содержать лишь функции, использующие глобальные
е
static-
а.
переменные, отдельно скомпилировать этот файл и работать с ним, не опасаясь
8.
вреда от побочных эффектов "qсеобщей глобальности".
1::
ф
О
Рассмотрим пример, который представляет собой переработанную версию
s:
лрограммы (из предыдущего подраздела), вычисляющей текущее среднее значе­
2i
нис. Эта версия СОСТОит И3 двух файлов и использует глобальные stаtiс-пе­
ременные для храJ:lения значсний промежуточной суммы и счетчика ВВОДИМblХ
чисел. В эту версию лрограммы добавлена функция
("сбрасывает") значения глобальных 5
// ---------------------
reset (),
ta ti с-леременных.
Первый файл
которая обнуляет
---------------------
int running_avg(int i);
void reset();
int main ()
{
пит;
do
cout «
"Введите
числа
(-1
для
выхода,
для
-2
cin » пит;
if (num==-2) . (
reset () ;
continue;
if(num != -1)
cout « "Среднее значение
« running_avg(nurn)i
cout « '\n':
while(num != -1);
return
О:
равно:
"
1:
1:
2с
S
1::
~
О
#inc1ude <iostream>
using namespace std;
int
х
сброса):
";
*
ш
314
Модуль 7, Еще о типах данных и операторах
// --------------------- Второй файл --------------------static int вит=О, count=O; // Эти перекеивuе иs_естнк
/ / ~OnЪ~O _ фaйnе, _ КОТОРОХ
они об'ЪJl8JlеНJ.r,
/ /
int running_avg(int i)
{
surn = surn
+ i;
count++;
return surn / count;
void reset()
surn =
О;
count
=
О;
в этой версии программы переменные 5ит и
count
являются глобально ста­
тическими, Т.е. их глобальность ограничена вторым файлом. Итак, они исполь­
зуются функциями
running avg ()
и
reset (),
причем обе ОJiИ расположены
во втором файле. Этот вариаtlТ программы позволяет сбрасывать накопленную
сумму (путем установки в исходное положение переменных 5ит н соип t), чтобы
можно было усреднить другой набор чисел. Но ни одна из функций, расположеtl­
ных вне второго файла, не может получить доступ к этим переменным. Работая с
данной программой, можно обнулить предыдущие накопления, введя число
В этом случае будет вызвана функция
reset ().
пытайтесь получить из первого файла доступ к любой из переменных
count.
-2.
Проверьте это. Кроме того, по­
s ит
или
(Вы получите сообщение ~ ошибке.)
Итак, имя локальной 5tаtiс-переменной и:шестно только фующии или блоку
кода, в котором она объявлена, а имя глобалыIйй stаtiс-переменной
файлу, в котором она "обитает". По сути, модифнкатор
- только
sta tic позволяет перемен­
ным существоватъ так, что о них знают только функции, использующие их, тем са­
мым "держа в узде" и ограничивая возможности негативных побочных эффектов.
Переменные типа
static
позволяют программисту "скрывать" одни части своей
программы от других частей. Это может оказаться просто супердостоинством, ког­
да вам придется разрабатывать очень большую и сложную программу.
С++: руководство д,лS1 начинающих
Спросим у
315
OnbITHoro программиста
Вопрос. Н слышал, что "екотор",е C~npOlptuCAlllCmw не исnмыуют lJI06ал"н",е
Ответ.
х
static-nеременнwе. Таили зто?
О
а.
о
Несмотря на то что глобальные static-перемеННblе по-прежнему допу­
а.
стимы и широко используются в С++-коде, стандарт С++ не предусма­
тривает их применения. Для управления доступом к глобальным перемен­
ным рекомендуется другой метод, который заключается в использовании
пространств имен. Этот метод описан ниже в данной книге. При этом гло­
бальные stаtiс-переменные широко используются С-npограммистами,
поскольку в С не поддерживаются пространства имен. Поэтому вам еще
долго придется встречаться с глобальными
s ta tiс-переменными.
о
ф
1::
О
:s;
х
J5
:I:
:I:
~
х
О
1::
....:s;
о
ф
~
ш
ВАЖНОI
M-ееr.иаравыe.nеpe.rJJ.eJiU;lblе
Возможно, чаще всего IIСПОЛI,ЗУСТСЯ спецификатор класса памяти
Для компилятора модификатор
register означает
register.
предписание обеспечить та­
кое хранение соответствующей переменной, чтобы доступ к ней можно было по­
лучить максимально быстро. Обычно переменная в этом случае будет храниться
либо в регистре цеитралыюго процессора (ЦП), либо в кэш-памяти (быстродей­
ствующей буферной памяти небольшой емкости). Вероятно, вы :~HaeTe, что до­
ступ к регистрам ЦП (или к кэш-памяти) принципиалыю быстрее, чем доступ
к основной памяти компьютера. Таким образом, l1еремеtшая, сохраняемая в ре­
гистре, будет обслужена гораздо быстрее, чем переменная, сохраняемая, напри­
мер, 8 оперативной памяти (ОЗУ). Поскольку скорость, с которой к переменным
можно получить доступ, определяет, по сути, скорость выполнения вашей про­
rpa.I\1MbI;
для
получения
удовлетворительных
важно разумно использовать спецификатор
Формальио спецификатор
register
результатов
программирования
register.
представляет собой лишь запрос, кото­
рый компилятор вправе проигнорировать. Это легко объяснить: ведь количество
регистров (или устройств памяти с малым времеием выборки) ограничено, причем
для разных сред оно может быть различиым. Поэтому, если компилятор исчерпает
память быстрого доступ<l, он будет хранить rеgistеr-переменные обычным спо­
собом. В общем случае неудовлетворенный rеgistеr-запрос не приносит вреда,
но, конечно же, и не дает никаких преимуществ хранения в регистровой памяти.
как правило, программист может рассчитывать на удовлетворение rеgistеr-за­
проса по крайией мере для двух переменных, обработка которых действительно
будет оптимизирована с точки зрения максиМально возможной скорости.
316
Модуль 7. Еще о типах данных и операторах
Поскольку быстрый доступ можно обеспечить на самом деле только для огра­
ниченного количества переменных, важно тщательно выбрать, к каким из них
применить модификатор
register.
(Лишь правильный выбор может повысить
быстродействие программы.) Как правило, чем чаще к перемеl"lНОЙ требуется до­
ступ, тем большая выгода будет получена в результате оптимизации кода с помо­
щью спецификатора
register.
Поэтому объявлять регистровыми имеет смысл
управляющие переменные цикла или перемснные, к которым выполняетс~ до­
ступ в теле цикла.
На примере следующей функции показано, как используются rеgistеr-пе­
ременные для повышения быстродействия функции
sumrnation (),
которая вы­
числяет сумму значеlrnй элементов массива. Здесь как раз и предполагается. что
реально для получеJПlЯ скоростного эффекта будут оптимизированы ТОЛЬК(I две
перемеllllые.
//
Демонстрация ИСПОЛЬЗО,вания
register-переменных.
#include <iostream>
using namespace std;
int sumrnation(int nums[], int
п);
int main ()
int vals[]
int result;
result
cout «
{ 1, 2,
З,
4, 5 };
sumrnation(vals, 5);
=
"Сумма
элементов массива равна
" «
result «
.,\
n" ;
return
О;
// Функция возвращает сумму iпt-элементов
int sumrnation(int питз[], int п)
массива.
{
register int i;
register int sum
О;
/ / Э'l'И перемеИВllе оп'ElDCИЗиров&вы ~
/ / ЩUI пonyчеНИJI иахcикa.m.ной СItОРОС'Х'И.
С++: PYI(OBOACтaO ДЛ'il начинающих
317
i < n; i++)
эит + nums[i];
О;
for(i
sum
return
R
о
зит;
6
а.
ф
Здесь переменная
i,
которая управляет циклом
for,
и переменная зит, к ко­
торой осуществляется доступ 6 теле цикла, определены с использованием спе­
цификатора
register.
Поскольку обе они используются внутри цикла, можно
рассчитывать на то, что их обработка будет оптимизирована для реализации бы­
строго к ним доступа. В этом примере предполагалось, что реально для получе­
ния скоростного эффекта будут оптимизированы только две переменные, поэто­
му
nums
и
n
ие были опрсделсны как
regis ter-пеРСМСl-lllые,
реализуется не столь часто, как к переменным
ведь доступ
!( ним
i и эит. Но если среда позволяет
оптимизацию более двух персменных, то имело бы смысл персмсш!ые питз и n
также объявить с использованием спецификатора
register, что еще больше по­
высило бы быстродействие программы.
~просы ДЛ'il текущего коНтрОлS1 _ _ _ _ _ _ __
1.
Локальная перемеllНая, объявленная с использованием модификатора
static,
2.
свое значение между вызовами функции.
Спецификатор extern используется для объявления переменной без ее
определения. Верно ли это?
З. Какой спецификатор служит для компилятора запросом на оптимизацию
обработки переменной с целью повышения быстродействия ПРОIJ>аммы?·
1.
Локальиая псременная, объявленная с использованием модификатора
sta tic,
сохраняет свое значение между вызовами функции.
2.
Верно, спецификатор
extern
действительно используегся для об'Ьявления пере­
меllНОЙ без ее определения.
З. Запросом на оптимизацию обработки переменной с целью повыmения'быстродей­
ствия проrpaммы служит спецификатор
reg i 5 te r.
с:
о
:s:
)(
:о
:I:
:I:
~
~
с:
~
О
~
ш
..
318
МОДУЛЬ
7. Еще О типах данных и операторах
Спросим у опытного программиста
Вопрос.
Когда 11 д06ишr fI npoгpaJНJtly сnецифuкаmoр
register, mo не :Jамеmuл ни­
КJlKиx изменений fI 6wcmpoдefu:mflUU. В чем причина?
Ответ.
Большинство компиляторов (благодаря прогрессивной технологии ,'х
разработки) автоматически оптимизируют программный код. Поэтому
во многих случаях внесение спецификатора
register
в объявление
переменной не ускорит выполнение программы, поскольку обработка
этой переменной уже оптимизирована. Но в некоторых случаях ИСПОЛI,­
зование спецификатора
register
оказывается весьма полезным, так
как он позволяет сообщить компилятору, какие именно переменные вы
считаете наиболее важными для ОПТlIмизации. Это особенно ценно ДJIЯ
функций, В которых используетси большое количество перемс!-\ных. и
ясно, что ВССХ их lIевозможно оптш,ш:mровать. Следовательно, несмотря
на прогрсссивиую технолоПfЮ раэраGотки компиляторов, спецификатор
register
по-прежнсму играет важную роль для эффективного про­
граммирования.
в С++ можно определить список именованных целочисленных констант. Та­
кой список называется nеречuсленuе.м
(enumeration). Эти константы можно З,lтем
использовать везде, где допустимы целочисленные значения (например, в цело­
численных выражениях). Перечисления определяются с помощью ключеJlОГО
слова
enum
enum, а формат их определения
имя_типа
{
имеет тикой вид:
список_перечисления
}
список_переменных;
Под элементом список_перечисления понимается список разделенных
запятыми имен, которые представляют значения перечисления. Элемент спи­
сок_ переменных необязателен, ПОСКОЛЬКУ псремениые можно объявить ПО~iже,
используя имя_ типа перечисления.
В следующем примере определяется перечисление
ные типа
епит
transport
с именами
tl
и
t ransport
и две перемен­
t2.
transport { car, truck, airplane, train, boat } tl, t2;
Определив перечисление, можно объявить другие переменные этого типа, ис­
пользуя имя типа перечисления. Например, с помощью следующей инструкции
объявляется одна переменная
transport how;
how
перечисления
transport.
С++: руководство ANi\ начинающих 319.
... ,...........................................................................................................................
эту инструкцию можно записать и так.
transport how;
епиm
х
Однако использование ключевого слова епиm здесь излишне. В языке С (ко­
торый также поддерживает перечисления) обязательной была вторая форма, по­
этому в некоторых программах вы можете встретить подобную запись.
С учетом предыдущих объявлений при выполнении следующей иистру/щии
переменной
how
присваивается значение
airplane.
&
§
Q.
Ф
1:
О
:s:
х
21
1:
1:
how = airplane;
Важно пони мать, что каждый символ списка перечисления означает целое
~
х
О
число, причем каждое следующее число (представленное идентификатором) I-/а
1:
единицу больше предыдущего. По УМОЛLJalШЮ значение перноl'O символа пере­
О
чпслспия равпо НУЛЮ, слеДОВ~lТелUllU, :шачснне второго-- СЛШIJЩС Н т.Л. ПnЭТО~[У
car «
' , «
train;
на экран будут выведены числа О и З.
Несмотря на то что переЧИСЛlfмые константы автоматичеСК/l преобразуются
в целочислениые, обратное преобразование автоматически не выполняется. На­
пример, следующая инструкция некорректна.
how
=
1; //
ошибка
Эта инструкция вызовет во время компиляции ошибку, поскольку автомати­
ческого преобразоваиия целочисленных значений в значения типа
transport
не существует. Откорректировать предыдущую инструкцию можно с помощью
операции приведения типов.
fruit
= (transport) 1; //
//
Теперь переменная
t r ап spo r t
how
Теперь
но
все
стиль
в
порядке,
не совершенен.
будет содержать значение
-константа связывается со значением
truck, поскольку эта
1. Как отмечеJЮ в комментарии,
несмотря на то, что эта инструкция стала корректной, ее стиль оставляет желать
лучшего, что простительно лишь в особых обстоятельствах.
Используя инициализатор, можно указать значение одной или нескольких
перечислимых констант. Это делается так: после соответствующего элемента
списка перечисления ставится знак равенства и нужное целое число. При исполь­
зовании инициализатора следующему (после инициализированного) элементу
списка присваивается значение, на единицу превышающее предыдущее значение
инициализатора. Например, при выполнении следующей инструкции константе
airplane
епит
Ф
::f
ш
при выполнении этой инструкции
cout «
~
присваивается значение
10.
transport { car, truck, airplane
=
10, train, boat l;
320
Модуль
7. Еще о типах данных и операторах
Теперь все символы перечисленияtrапsроrt имеют следующие значения.
car
truck
airplane
train
boat
О
1
10
11
12
Часто ошибочно предполагается, что символы перечисления можно вводить и
выводить как строки. Например, следующиii фрагмент кода выполнен не будет.
11 Слово "train"
how = train;
cout « how;
на
экран
Не забывайте, что символ
таким
tr ain -
образом не
попадет.
это прос то имя для некоторого целочис.;lеll­
ного значения, а не СТРOl<а. Следовательно, при выполнении ~редыдущего кода на
экране отобразится числовое значеШfе (<ОНСТШIТы
train,
а не строка
"traln".
Конечно, можно создать код ввода If lIьшода символов перечисления 8 виде строк,
но 01:1 lIЫХОДИТ несколько ГРОМОЗДЮIМ. Вот, например, KaI< МОЖl10 отобразить на
экране наЗВatIИЯ транспортных средств, связанных е переменной
how.
switch{how)
case car:
cout « "Automobile";
break;
case truck:
cout « "Truck";
break;
case airplane:
cout « "Airplane";
break;
case train:
cout « "Train";
break;
case boat:
cout « "Boat";
break;
I
Иногда для перевода значения перечисления в соответствующую строку
можно объявить массив строк и использовать значение перечисления в каче­
стве индекса. Например, следующая программа выводит названия трех видов
транспорта.
С++: руководство для начинающих
11
321
Демонстрация использования перечисления.
а
#include <iostream>
using namespace stdi
о
о
Q.
Q)
еnиm
transport ( car, truck, airplane, train, boat );
t:
О
S
~
char name[] [20]
"Automobile",
"Truck" ,
"Airplane",
"Train",
"Boat"
:r
:r
~
е
t:
~
О
Q)
3"
ш
};
int main ()
transport
hOWi
how = car;
cout « name[how] «
'\n';
how = airplane;
cout « name[how] «
'\n';
how = traini
cout « name[how] «
'\n';
return
О;
Вот результаты выполнения этой программы.
Automobile
Airplane
Train
Использованный в этой программе метод пре06разования значения пере­
числения в строку можно применить к перечислению любого типа, если оно не
содержит инициализаторов. Для надлежащего индексирования массива строк
перечислимые константы должны начинаться с нуля, быть строго упорядочен-
Модуль 7. Еще о типах данных и операторах
322
ными по возрастанию, и каждая следующая константа должна быть больше пред­
ыдущей точно на единицу.
Из-за того, что значения перечисления необходимо вручную пре06разовывать
в удобные для восприятия человеком СТРОЮ'I,ОIlИ, в ОСНОDlЮМ, используются там,
где такое преобразование не требуется. Для примера рассмотрите перечисление,
используемое для определения таблицы символов компилятора.
• КAIoиевое САnВО twsde;E
iJi
в с ++ разрешается определять новые имена типов дaHНblx с помощью ключе­
вого слова
t
уре de f. П rш IfспользоnаlПШ
t yperle f -имени не создается НОВЫЙ тип
Д<lШ/ЫХ, а ЛIIШЬ определяется "овое IIМЯ дЛЯ уже существующего типа. Благодаря
tуреdеf-имеllам можно сделать машинозависимые программы более переноси­
мыми: ДЛЯ этого иногда достаТОЧIlО изменить tуреdеf-ИIIСТРУКЦИИ. Это сред­
ство таюкс ПОЗDоляет улучшить читабельность кода, поскольку ДЛЯ стандартных
типо/З данных с его помощыо можно использовать описательные имена. Общий
формат записи инструкции
typedef
typedef таков.
тип ИМЯ;
Здесь элемент тип означает любой допустимый тип данных, а элемент ИМЯ -
но­
вое имя для этого типа. При этом заметьте: новое имя определяется вами в каче­
стве дополнения к существующему имени типа, а не для его замены.
Например, с llOМОЩЬЮ следующей инструкции можно создать новое имя для
типа
floa t.
typedef float balance;
Эта ИIIСТРУКЦИЯ является предписанием компилятору распознавать иденти­
фикатор
balance
как еще одно имя для типаflоаt. После этой инструкции мож­
но создanать float-перемеИllые с использованием имени
balance.
balance over due;
Здесь оБЪЯDлена переменная с плавающей точкой
over_due
типа
balance,
торый представляет собой стандартный тип floa t с другим иазванием .•
ко­
ВАЖНОI
ММ Dо,розрядNь1едаерnторы
Поскольку язык С++ сориентирован так, чтобы позволить полный доступ к
аппаратным средствам компьютера, важно, чтобы он имел возможность непо-
С++: РУКОВОДСТВО МЯ начинающих
323
средственно воздействовать на отдельные биты в рамках байта или машинного
слова. Именно поэтому С++ и содержит пораэрядные операторы. Поразрядные
oneраторы предназначены для тестирования, установки или сдвига реальных
битов в байтах или словах, которые соответствуют символьным или целочислен­
ным С++-типам. Поразрядные операторы не используются для операндов типа
х
&
е
о
а.
bool, f1oat, double, long double. void или других еще более сложных типов
~
данных. Поразрядныс операторы (табл.
используются для реше-
:s:
ния широкого круга задач программирования системного уровня, например, при
:I:
:I:
7.1) очень часто
опросе информации о состоянии устройства или ее формировании. Теперь рассмотрим каждый оператор этой группы в отдельности.
х
о
ПеречислеНIIС
-
...-_ _ _ _ _ _ _
.__
это список именованных
Какое ЦСЛОЧllСJIеlllЮС значение по умолчанию
Покажите, как объявить идентификатор
ним именем для типа
Таблица
~
О
IСОИСТант.
IfMceT первый символ
пере­
числения?
3.
О
~
Вопросы АЛ~ текущего I<ОНТРОЛ~
2.
х
Jj
с:
~
1.
о
7.1.
,.
1 ong 1 n t.
Biglnt, чтобы он стал еще од-
ПораЗРflдные операторы
- - - - . _ . _ - - - - - - - - _....---"._-
Значение
Оператор
Поразряпное И
&
(AND)
(OR)
Поразрядное ИЛИ
л
ПоразРЯДllое I1СIUIIOчающее ИЛИ
Дополнение до
»
1 (унарный
(XOR)
оператор НЕ)
Сдвиг оправо
«
влево
- - - - - - - - _....Сдвиг
_ _-----_._._._..
Пораэрядные операторы И, ИЛИ,
исключающее ИЛИ и НЕ
Поразрядные операторы И, ИЛИ, исключающее ИЛИ и НЕ (обозначаемые
символами &,
1,
л и
-
-
соотве1.:СТвенно) выполняют те же операции, что и их ло-
1.
Перечисление
2.
По умолчанию первый символ перечисления имеет значение О.
з.
typedef long int BigInt;.
это список именованных целочислеиньrх констант.
*
ш
324
МОДУЛЬ 7. Еще О типах данных и операторах
гические эквиваленты (т.е. они действуют согласно той же таблице истинности).
Различие состоит лишь в том, что поразрядныс операции работают на побитовой
oCIf;0Be. IB следующей таблице показан результат выполнения каждой поразряд­
ной операции для всех ВОЗМОЖtIЫХ сочетаний операндов (нулей и единиц).
р
q
р
о
о
О
о
О
&
q
р
I q
О
р
л
q
-р
О
1
О
О
о
1
о
о
1
Как видно-из таблицы, результат применения оператора
ИЛИ) будет равен значению ИСТИНА
(равен значению
1) ЛИШЬ О/ЩН
(1)
XOR
(исключающее
только в том случае, если испшен
113 операндов; !I противном случае результат при­
нимает значеllие ЛОЖЬ (О).
Ilоразрядный оператор И можно представить как способ подавления битовой
информации_ Это значит, что О D любом операнде обеспечит установку в О соот­
ветствующего бита результата. Вот при мер.
&
1101 0011
1010 1010
1000 0010
Использование оператора" &" демонстрируется в следующей программе. Она
преобразует любой строчный символ в его прописной эквивалент путем установ­
ки шестого бита равным значению О. Набор символов
ASCIl
определен так, что
строчные буквы имеют почти такой же код, что и прописные, за исключением
того, что код первых отличается от кода вторых ровно на
32.
Следовательно, как
показано в этой программе, чтобы из строчной буквы сделать прописную, доста­
точно обиулить ее шестой бит.
// Получение прописных букв С использованием поразрядного
// оператора ~И".
#inc1ude <iostream>
using namespace std;
int main ()
(
char ch;
for(int i
=
О
i
< 10;
Н+)
С++: РУ'<ОВОДСТВО дl\Я начинающих
325
ch = 'а' + i;
cout « ch;
~
//
ch
Эта инструкция Обнуляет
=
ch
В переменной
223; //
//
&
а.
6-й бит.
ch
о
6
теперь
а.
~
прописная буква.
о
:s:
cout «
ch «
х
" ";
2i
:z:
:z:
~
х
О
cout «
~
"\п";
~
О
return
~
О;
ш
ВОТ К(Ш ВЫГJШДЯТ рсзульта1Ъ1 выполнения этой uрограммы.
ад ЬВ
се
dD
Значение
еЕ
223,
fF gG hH iI jJ
используемое в ННСТРУIЩИИ поразрядного оператора "И", явля­
ется десятичным представлением ДDОИЧIЮГО числа
операция "И" оставляет все биты в переменной
шестого (он сбрасьшается в IIУЛЬ).
11 О 1 1111. Следовательно, эта
ch нетронутыми, за исключением
•
Оператор "И" также полезно использовать, если нужно определить, установ­
лен интересующий вас бит (т.е. равен ли он значению
1) или
нет. Например, при
выполнении следующей инструкции вы узнаете, установлен ли 4-й бит в пере­
менной
status.
if(status & 8)
cout
«
"Бит
4
установлен";
Чтобы понять, почему для тестирования четвертого бита используется чис­
ло
8,
вспомните, что в двоичной системе счисления число
8
представляется как
00001000, Т.е. в числе 8 установлен только четвертый разряд.
Поэтому условное
выражение инструкции i f даст значение ИСТИНА только в том случае, если
четвертый бит переменной
status также установлен (равен 1). Интересное ис­
disp_binary (). Она
пользование этого метода показано на примере функции
отображает в двоичном формате конфигурацию битов своего аргумента. Мы бу­
дем использовать функцию
show binary ()
ниже в этой главе для исследова­
ния возможностей других поразрядных операций.
//
Отображение
конфигурации битов
void show_binary(unsigned int
и)
в
байте.
326 Модуль 7. Еще о типах данных и операторах
.................................................................................................................................
int t;
for(t=128; t > О; t = t/2)
if(u & t) cout « "1 ";
e1se cout « "О ";
cout
«
Функция
"\п";
show_binary (),
используя поразрядный оператор "И", последова­
TeJlbHO тестирует каждый бит младшего байта переменной и, чтобы определить,
установлен он или сброшен. Если он установлен, отображается цифра
тпnном случае
-
1,
в про­
цифра О.
ПораЗРЯДllыii оператор "ИЛИ" (В ПРОТИDОПОJIOЖНОСТЬ поразрядному UИ")
удобно использовать для устаНО8lШ нужных r)итов равными единице. При JJbInOJlIlCIIIIII операции "ИЛИ" ШUПlчие в любом операнде бита, равного
1, означает,
что в реэультатс соответствующий бит также будет равен единице. Вот пример.
1101 0011
I 1010 1010
1111 1011
Оператор "ИЛИ" можно использовать для превращения рассмотренной выше
программы (которая преобразует строчные символы в их ПРОIIисные эквивален­
ты) в ее "противоположность", Т.е. теперь, как показано ниже, она будет преоб­
разовывать прописные буквы в строчные.
//
//
Получение
строчных
оператора
"ИЛИ".
букв
с использованием лоразрядного
#include <iostream>
using
nатезрасе
std;
int main ()
{
,
char ch;
for(int i = О ; i < 10; i++)
ch = I А I + i;
cout « ch;
С++: PYI<080ACT80 ДЛЯ начинающих
327
-
// Эта инструкция делает букву строчной,
// устанавливая ее 6-й бит.
ch = ch I 32; /1 в переменной ch теперь
/1
строчная
~~
:0-
буква.
~~
:0
:0-
coи~
«
ch «
:Ф
" ";
.1::
;0
:s
cout «
return
"\n";
О;
Вот результаты DЫПОЛJIСIIlIЯ этоii программы.
Аа
ВЬ
Сс
Dd
Ее
Ff Gg Hh Ii Jj
И так, вы убеДИJIНСЬ, что УСТaIЮlша шестого бита превращает прописн.ую букву
В ее СТРОЧIIЫЙ эквивалент.
Поразряднос l1сключающее "ИЛИ"
(XOR)
устанавливает бит результата рап·
ным единице "I:ОЛl)КО в том случае, если СООТllСТСТВУlOщие биты операндов отли·
чаются один от другого, т.е. не равны. Вот при мер:
л
0111 1111
1011 1001
1100 0110
Оператор "исключающее ИЛ И"
(XOR) Q6ладает интересным свойством,
кото­
рое дает нам простой способ кодирования сообщений. Если применить операцию
"исключающсе ИЛИ" к не которому значению Х и зарансе известному значению
У, а затем проделать то же самое с рсзультатом предыдущей операции и значени­
ем У, то мы снова получим значение х. Это означает, что после выполнения этих
операций
R1
=
Х
R2
=
Rl "
л
У;
У;
R2 будет иметь значение х. Таким образом, результат последовательного выпол­
нения двух операций "XOR" с использованием одного и того же значения дает
исходного значение. Этот принцип можно применить для создания простой шиф­
ровальной npoграммы, в которой некоторое целое число используется в качсстве
lUIюча для шифрования и дешифровки сообщения путем выполнения операции
XOR
Над символами этого сообщения. Первый раз мы используем операцию
XOR, чтобы закодировать сообщение, а
после второго ее применения мь! получа-
328
МОДУЛЬ 7. Еще о типах данных и операторах
ем исходное (декодированное) сообщение. Рассмотрим простой пример исполь­
зования этого метода для шифрования и дешифровки короткого сообщения.
//
//
Использование оператора
ДЛЯ кодирования и
XOR
декодирования сообщения.
#include <iostream>
using namespace std;
int main ()
(
char msg[) = "Это
char key := 88;
простой
cout «
сообщение:
"Исхо'дное
тест";
" «
msg «
"\п";
for(int i = О ; i < strlen(msg); i++)
msg[i) := msg[i) л key;
cout «
"Закодированное
for(int i
msg[i]
cout «
return
О
=
:=
;
i
msg[i]
сообщение:
" «
msg «
"\п";
msg «
"\п";
< strlen(msg); i++)
л key;
"декодированное
сообщение:
" «
О;
Эта программа генерирует такие результаты.
Исходное
сообщение:
Это
простой
тест
Закодированное сообщение:
+:Yxy~Y: :Уёх: а :
Декодированное
Это
соОбщение:
простой
тест
Унарный оператор "Н Е" (или оператор дополнения до
1) инвертирует состоя­
ние всех битов своего операнда. Например, если целочисленное значение (храни­
мое в переменной А) представляет собой двоичный код
операции -А получим двоичный код
1001 0110, то в результате
0110 1001.
В следующей программе демонстрируется использование оператора "НЕ" по­
средством отображения некоторого числа и его дополнения до
с помощью приведенной выше функции
show_binary ().
1 в двоичном коде
С++: руководство дl\Я начинающих
329
iinclude <iostrearn>
using патезрасе std;
)(
о
Q.
void
shоw_Ьiлаrу(uлsigпеd
и):
int
@
Q.
Ф
int main ()
с:
о
s
х
unsigned
cout «
cin »
j]
и:
:r
:r
"Введите
число
между
О
и
~
255: ";
~
с:
s
и;
~
о
cout « "Исходное
show_binary(u);
число
cout « "Его дополнение
show_binary(-u);
return
в
двоичном
до
единицы:
коде:
";
";
О;
// Отображение битов, составляющих
void show_binary(unsigned int и)
байт.
(
register int t;
t>O; t = t/2)
if(u & t) cout « "1 ";
else cout « "О ";
for(t~128;
cout «
"\п";
Вот как выглядят результаты выполнения этой программы.
Введите число между О и
255: 99
Исходное число
в двоичном
Его
до
дополнение
Операторы &,
I
и
-
единицы:
коде:
1
О
О
О
1 1 О О О 1 1
1 1 1 О О
примен.яются непосредственно к каждому биту значения
в отдельности. Поэтому поразрядные операторы нельзя использовать вместо их
*
ш
330 Модуль 7. Еще о типах данных и операторах
................................................................................................................................
логических эквивалентов в условных выражениях. Например, если значение х
равно 7, то выражение х & &
ние х &
8
8 имеет значение
ИСТИНА, в то время как выраже­
дае1· значение ложь.
ВАЖНОI
р;8 Оаераюры СДВl4,са
Операторы сдвига,
"»"
и
"«",
сдвигают нсе биты в значении псремеllНОЙ
вправо или влево. Общий формат использования оператора сдвига вщ>аво вы­
глядиттак.
переменная
»
ЧИСЛО_БИТОВ
А оператор с.ДПIfГ<1 плеlЮ lIспользуется та\(.
леременная
«
числО_битов
Здесь эле~fСllТ число_битов у,,:азывает, l1а сколько· позиций должно быть сдви­
нуто ~шачение элемента переменная. При каждом сдвиге влево все биты, с::о­
стаВЛЯЮЩllС 3llачеНllе, сдвигаются плево на одну позицию, а в младший ра.1РЯД
записывается нуль. При каждом СД8иге вправо все би'FЫ сдвигаются, соотпет­
ствешlO, вправо. Если СДll\lГУ вправо подвергаегся значение без знака, в старший
разряд записывается нуль. Если же сдвиry вправо подвергается значение со зна­
ком, значение ЗlIакового разряда сохраняется. Клк вы помните, отрицательные
LLелыc числа преДСТ<lВЛЯIOТСЯ установкой
cTapu;ero разряда
числа равным едини­
це. Т~ким образом, ссли сдвигаемое значение отрицательно, при каждом сдонге
вправо в старший разряд записывается еДИlllща, а если положительно
-
нуль. Не
забывайте: СДDИl·, выполняемый операторами сдоига, не является циклическим,
Т.е. при сдвиге как вправо, так и влево крайние биты теряются, и содержимое по­
терянного бита узнать невозможно.
Операторы сдвига работают только со значеНlIЯМИ целочислеНllЫХ типов, напри­
мер, символами, целыми числа.\1И и дЛинными целыми числами
int
или
short int).
(int, char, long
Они не применимы к знаЧ~IiИЯМ с плавающей точкой.
Побитовые операции сдвига могут оказаться весьма полезными для декоди­
рования входной информации, получаемой от внешних устройств (например,
LLифроаналоговых преобразователей), и обработки информации о состоянии
устройств. Поразрядные операторы сдвига можно также использовать для вы­
полнения ускоренных операций умножения и д.еления целых чисел. С помощью
сдвига влево можно эффективно умножать на два, сдвиг вправо позволяет не ме­
нее эффективно делить на два.
Следующая программа наглядно иллюстрирует результат использования опе­
раторов сдвига.
С++: руководство ДЛЯ начинающих
//
Демонстрация выполнения поразрядного
331
сдвига.
х
tinclude <iostream>
using namespace std;
О
Q.
о
~
&
ф
void show_binary(unsigned int
и);
1::
О
:s:
х
::а
int main ()
I
I
(
О
<t
int i=1, t;
// Сдвиг влево.
for(t=O; t < 8; t++)
show_binary(i) ;
i = i « 1; !! ~ Сдвиг
cout «
~
О
Ф
:!
ш
влево перекекной
вправо перемеиной
О;
// Отображение битов, составляющих
void show_binary(unsigned int и)
(
int t;
for(t=128; t>O; t=t/2)
if(u & t) cout « "1 ";
else cout « "О ";
cout «
на
i
1
позицию.
"\п";
// Сдвиг вправо.
for(t=O; t < 8; t++)
i = i >> 1; / / +- Сдвиг
show_binary(i) ;
return
S
1::
"\п";
байт.
i
на
1
позицшз.
МОДУЛЬ 7. Еще о типах данных и операторах
332
Результаты выполнения этой программы таковы.
О
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
1
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
О
О
О
О
О
О
О
О
1
~ПРОСЫ длЯ текущего контроля
-
Какими символами обозначаются пора:зрядные операторы И, ИЛИ, IIС-
1.
lUIючающее ИЛИ и НЕ'?
Поразрядный оператор работает на побитовой основе. Верно ли это?
2.
3.
Покажите, как выполнить сдвиг значения переменной х влево на два
еазряда.
Проект
•
-
7.1. eClA3nовивrфyiпщИЙПОРОЗРЯАИОг.а
циклического сдвига
i rota te . срр--! Несмотря на то что язык С++ обеспечивает два оператора сдвига,
в нем не определен оператор цuмuчecкozо Cдвuza. Оператор ци-
1.
Пораэрядные операторы обозначаются такими символами: &,
2.
Верно, пораэрядный оператор работает на побитовой основе.
З.
х
«
2
1,
А и-.
С++: PYI(OBOACTBO ДЛЯ начинающих
333
клическоro сдвига отличается от оператора обычноro сдвига тем, что бит, выдвигае­
мый с одноro конца, "вдвигается" в другой. Таким образом, выдвинутые биты не те­
ряются, а просто перемещаются "по кpyry". Возможен циклический сдвиг как вправо,
так и влево. Например, значение 1О 1О
разряд даст значение
0000 после циклическоro сдвига влево на один
0100 0001, а после сдвига вправо - число О 101 0000. В каждом
случае бит, выдвинутый с одного конца, "вдвигастся" в другой. ХОТЯ отсутствие опе­
раторов циклического сдвига может по казаться упущением, на са.\юм деле это не так,
В этом проекте предполагается создаtlИе двух функций:
rrotate ()
и
lro-
ет два параметра и возвращает результат. Первый параметр содержит значение,
-
количество сдвигаемых разрядов. Этот проект
щ,лючаст ряд действш':'( над битами 11 отображает ВОЗМОЖIЮСП, IIРНI\(СНСНИЯ по­
разрядных операторов.
Последовательность действий
1.
Создай:те файл с именем
2.
Определите функцию
rota te . срр.
lrota te () , предназначенную
для выполнения ци·
клического СДВИl'а влево.
ФУНКЦИЯ
//
unsig~ed
циклического сдвига влево байта
на
char lrotate(unsigned char val, int
n
разрядов.
п)
{
unsigned int t;
t
=
val;
for(1nt 1=0; 1 <
t = t « 1;
/*
п;
i++)
Если выдвигаемый
содержит единицу,
1f (t & 256)
t = t I 1; //
return t; //
бит
(8-й разряд
значения
t)
то устанавливаем младший бит.
Устанавливаем
1
на правом конце.
Возвращаем младшие
8
бит.
~
ф
с:
о
s
х
::I:
::I:
которые сдвигают байт впр,шо или влево. Каждая функция принима­
подлежащее сдвигу, второй
О
а.
о
Ji
поскольку их очень легко создать на основе других поразрядных операторов.
ta te ( ) ,
х
*/
~
х
О
с:
~
о
~
ш
334 Модуль 7. Еще о типах данных и операторах
Вот КёШ работает функция
lrotate (). Ей передается значение, которое
val, и количество разрядов, на которое нуж­
нужно сдвинуть, в параметре
но сдвинуть, в параметре
n. Функция присваивает значение параметра v,3.l
перемеНIIОЙ t, которая имеет тип unsigroed int. Необходимость присва­
ивания unsigned сhаr-значения перемснной типа unsigned int об­
условлена тем, что в этом случае мы не теряем биты, выдвинутые влево.
Дело в том, ЧТО бит, выдвинутый влево И:i байтового значения, просто ста­
новится восьмым битом целочислеиного :шачеиия (поскольку его длина
больше длины байта). Значение этого бита можно затем скопировать в ну­
левой бит байтового значения, тем самым ВЫПОЛНИВ циклический сдвиг.
В действительности циклический сдвиг выполняется следующим образом.
Настраивается ЦИКЛ 11<1 J(ОЛJIЧССТВО IIтераЦIIЙ, равное требуемому числу
СДIШГОВ. В теле цикла значеШlе I1СРСIIIСIШОЙ
t
сдвигается влево иа один
разряд. При этом справа будет "вдвинут" "уль. Но ссли значеtше восьмого
бита результата (т.е. 6l1та, который был выдвинут из байтового значения)
раDlIO единице, то и нулепоii бит нсоБХОДllМО установить равиым
t.
В про­
ТIIDIIОМ слууае нулевой бит остается равным О.
Значение восьмого бита тестируется с использованием if-инструкции:
i f (t
256)
&
Значение
256 представляет собой десятичное значение, в котором установ­
леll только 8-й бит. Таким образом, выражение t & 256 будет истинным
лишь в слу'ше, если в перемеlll:lОЙ t 8-й бит равен единице.
После выролнсння циклического сдвига функция
возвращает
lrotate ()
ЗllачеlJие
t. Но поскольку она объявлена l;aK возвращающая значение типа
uns igned cha r, то фаJC'ГИЧССКИ пернутся талыш младшие 8 бит значения t.
3.
Определите функцию
rrotate (),
предназначенную для выполнения ци­
клического сдвига вправо.
Функция
//
ЦИI<J1ического
сдвига
Е,право
байта
на
дов.
uлsigпеd
char rrotate(unsigned char val, int n)
{
unsigned int t;
t
//
t
=
val;
Сначала
= t
«
8;
сдвигаем значение
на
8
бит влево.
n
разря­
С++: Ру!<ОВОДСТВО ДЛЯ начинающих
for(int i=Q; i < n; i++)
t = t » 1; // Сдвиг вправо
335
)(
на
1
разряд.
на
1
разряд он
о
а.
о
...
/*
После
сдвига
бита
вправо
становит-
о
а.
ф
с:::
ся
О
седьмым
битом целочисленного
значени~
t.
Если
7-й
:s:
)(
2i
бит
равен
единице,
то
нужно
установить
крайний
слева
*/
бит.
1:
1:
~
)(
о
i f (t & 128)
t = t I 32768; 11 Устанавливаем "1" с левого кон-
с:::
~
о
ф
.:3"
ца.
: UJ
1*
t
Наконец,
помещаем результат назад в младшие
значения
t. *1
»
t
=
return
8
бит
8;
t;
\
Циклический сдвиг вправо немного сложнее ЦИКЛItческого сднш"а влево,
поскольку значение. переданное в параметре
чально сдвинуто во второй байт значения
val. должно быть пеРВОJlа·
t. чтобы "не упустить" биты,
сдвигаемые вправо. После завершения циклического сдпига значение не­
обходимо сдвинуть назад в младший байт значения t. подготовив его тем
самым для возврата из функции. Поскольку сдвиrасмый вправо бит стано­
вится седьмым битом, то для проверки его значения используется следую­
rчaя инструкция.
i f (t
&
128)
В десятичном значении
128 установлен ТОЛЬКО седьмой бит.
Если анализи­
руемый бит оказывается равным еДИНlще, то в значении t устанаJlливается
15-й бит путем применения к значению t операции "ИЛИ" с числом
32768.
В результате выполнения этой операции lS-й бит значения t устанавлива­
ется равным
4.
1, а все остальные не меняются.
Приведем ПWIНhlй текст программы, которая демонстрирует исполъзоваште
функций
rrotate ()
и
lrotate (). Для отображения
результатов в~олне­
кия каждого циклическоroсдвиra испольэуетсяФункция
show_binary ().
Модуль 7. Еще о типах данных и операторах
336
/*
Проект
7.1.
Функции циклического сдвига вправо и влево для байтовых
значений.
*/
*include <iostream>
using патезрасе std;
unsigned char rrotate (unsigned cl1ar val, int
unsigned char lrotate!unsigned char val, int
void shоw_Ьiпаrу(uпsigлеd int и);
iлt
п);
п);
main ()
{
char ch
'Т';
cout « "Исходное
show_binary(ch) ;
cout «
"Результат
значение в двоичном коде:\п";
8-кратного циклического
сдвига впра­
во:\п";
for(int i=O; i < 8; i++)
ch = rrotate(ch, 1);
show_binary(ch);
cout «
"Результат
8-кратного циклического сцвига вле­
во:\п";
for(int i=O; i < 8; i++)
ch = lrotate(ch, 1);
show_binary(ch);
return
//
О;
Функция циклического сдвига влево байта на
n
разрядов.
С++: pyt<OBOACTBO А.ЛЯ начинающих
unsigned char lrotate(unsigned char val, int
337
-
п)
{
)(
unsigned int t;
t
о
g....
о
val;
=
Q.
Ф
§
for (int 1=0; i <
t = t « 1;
/*
п;
if (t
t
:r
:r
единицу,
~
(8-й разряд значения t)
то устанавливаем младший бит.
)(
о
Устанавливаем
1
на правом конце.
Возвращаем младшие
8
бит.
// Функция циклического сдвига вправо байта на n
unsigned char rrotate(unsigned char val, int п)
разрядов.
{
uns1gned 1nt t i
= val;
/1
t
Сначала сдвигаем значение
=
t
«
на
бит
8
влево.
8;
for (int 1=0; i <
t = t » 1; 11
1*
~
О
I 1; 11
t
return ti 11
t
с::
*1
256)
&
=
)(
7i
Если выдвигаемый бит
содержит
:s:
i++) {
После
п;
i++)
(
Сдвиг
вправо на
1
разряд.
сдвига бита
вправо на
1
разряд он становится
седьмым битом
целочисленного
Если 7-й бит равен
единице,
крайний слева бит.
*/
if(t & 128)
t = t I 32768; 1/
значеНИR
то
Устанавливаем
нужно
"1"
t.
установить
с левого коица.
:
~
ш
Модуль 7. Еще о типах данных и операторах
338
/*
t
Наконец,
помещаем результат назад в младшие
значения
t. */
»
= t
8
8;
return ti
// Отображаем БИТЫ, которые составляют
void show_binary(unsigned int и)
байт.
{
int ti
for(t;128; t>Oi t = t/2)
if(u & t) cout « "1 ";
else cout « "О ";
cout «
5.
"\п";
ВОТ как выглядят результаты выполнения этой программы.
Исходное
О
1
О
1
значение
О
1
О
в
Результат
8-кратного
О
О
1
О
1
О
сдвига
вправо:
8-кратного циклического
сдвига
влево:
О
1
О
О
О
1
О
1
О
О
О
О
1
О
1
О
О
1
О
О
О
1
О
1
1
О
1
О
О
О
1
О
О
1
О
1
О
О
О
1
1
О
1
О
1
О
О
О
О
1
О
1
О
1
О
О
1
О
коде:
цикличес~ого
1
1
Результат
двоичном
О
1
О
1
О
О
О
1
О
1
О
1
О
О
О
1
О
1
О
О
О
1
О
О
1
О
О
О
1
О
1
1
О
О
1)
1
О
1
О
О
О
О
1
О
1
О
1
О
О
1
О
1
О
1
О
О
1
О
1
О
1
О
О
~ит
С++: PYl<080ACT80 дI\Я начинающих
339
rjo
·
аерnтар "Зl:lак вопроса"
ОДНИМ И3 самых замечательных операторов С++ является оператор
ратор
"?"
"?".
Опе­
можно ИСПОЛI,зовап.l D качестве замены if-еlsе-инструкций, употре­
бляемых в следующем общем формате.
i f
( условие)
выражение 1 ;
переменная
else
переменная =
выражение2;
Здесь значение, прнсваивасмое Ilсременной, зависит от результата вычисления
элемента условие, управляющего инструкцией
Оператор
"?"
i f.
lI<lзыпается mсрuарuы.М, поскольку он работает с тремя операн­
дами. Вот его общий формат записи:
Выражение1
?
Выражение2
:
Выражение 3;
Все элементы здесь являются Dыражениями. Обратите ВШfМание на использова­
ние J{ расположение двоеточия.
Значение ?-пыражения определяется следующим образом. Вычисляется Вы­
ражение 1. Если оно Ol<азьшается истиниым, вычисляется Выражение2, и ре­
зультат его вычислеш/Л становится значеtrнем всего ?-выражения. Если резуль­
тат вычисления элсмеwrа Выражение1 оказывается ложным, значением всего
?-выражения становится результат вычисления элемеита ВыражениеЗ. Рассмо­
трим следующий пример.
absval = val <
Здесь переменt10Й
менной
МСl1НОЙ
О
? -val : val;
//
//
Получение абсолютного
значения
переменной
val.
будет присвоено значение
если значение пере­
val больше или равно нулю. Если
absval будет присвоен результат отрицания
этого значения, Т.е. будет
absval
val,
значение val
отрицательно, пере­
получено положительное значение. Аналогичный КОД, но с использованием
if-
еlsе-инструкции, выглядел бы так
if(val < О) absval = -vali
else absval = vali
А вот еще один пример практического применен:ия оператора ? Следующая
программа делит два числа, но не допускает деления на нуль.
/*
Эта
программа
деления
использует
на нуль.
*/
оператор
"?"
для предотвращения
340
МОДУЛЬ 7. Еще о типах данных и операторах
#include <iostream>
using паmезрасе stdi
int main ()
int i, j, resul t;
cout « "Введите
cin » i » j;
делимое
и делитель:
";
// Эта инструхция не допустит возникновения ошибки
// деления на нуль.
result = j ? i/j : div_zero(); // Иcnonьзование ?-оператора
/ /
cout «
return
cout «
return
"Результат:
//
result;
" «
ДJJИ предотвращении
делении на нупь.
О;
"Нельзя делить
на
нуль.
\rl";
О;
Здесь, если значение переменной
j
не равно нулю, выполняется делеиие значе­
ния переменной
i на значение переменной j, а резулътат присваипается пере­
resul t. В противном случае вызывается обработчик ошибки деления на
нуль di v _ zero ( ) , и переменной resul t присваивается нулевое значение.
мен ной
"
Не менее интересным, чем описанные выше операторы, является такой оператор
С++, как "запятая". Вы уже видели несколько примеров его исполъэования D цикле
f о r, где с его помощью была организована инициализация сразу нескольких пере­
мещIых.. Но оператор "запятая" также может составлять часть любого выражения.
С++: руководство ДЛЯ начинающих
Его назначение в этом случае -
341
связать определенным образом несколько выра­
жений. Значение списка выражений, раэделенныx запятыми, определяется в этом
случае значением крайнего справа выражения. Значения других выражений отбра­
сываются. Следовательно, зна<lеиие выражения спрапа становится значением всего
выражения-списка. Например, при выполнении этой инструкции
ло
10,
count
count+1,
var
count
Q)
19,
персменtlой
incr -
чис­
прибавляется единица, после
присваивается значение крайнего справа выражения, т.е.
которое равно
20.
Круглые скобки здесь обязателЫibJ, 11ОСl(ОЛЬКУ опе­
ратор "запятая" имеет более низкий приоритет, чем оператор присваиваиия.
Чтобы понять наЗI1i.tчеШlе оператора "запятая", попробуем выполнить следу­
ющую программу.
int main ()
{
int i, j;
10;
i
(j++,
j+l00,
999+j); // Оператор "запятая" означает
//
"Сдf!лать
это,
и то,
и дру-
гое".
cout «
return
i;
О;
Эта программа выводит на экран число
ной j присваивается число
1О, затем
1010.
И вот почему: сначала перемеJl­
переменная j инкрементируется до
11.
По­
сле этого вычисляется выражение j
+ 100, которое нигде не при меняется. Нако­
нец, выполняется сложение значения переменной j (оно по-прежнему равно 11)
с числом
По
s
х
1i
:r
:r
~
х
О
r::
~
О
~
U.J
finclude <iostream>
using namespace stdj
j
6о­
о
сначала присваивается число
а затем к значению переменной
чего переменной
о­
о
r::
var = (count=19, incr=10, count+1)j
переменной
s
999, что в результате дает число 1010.
сути, назначение оператора "запятая" -
обеспечить выполнение задан­
ной последовательности операций. Если эта последовательность используется
в правой части инструкции присваивания, то переменной, указанной в ее левой
МОДУЛЬ 7. Еще о типах данных и операторах
342
части, присваивается значение последнего выражения И3 списка выражений, раз­
деленных запятыми. Оператор "запятая" по его функциональной нагрузке можно
сравнить со словом "и", используемым в-фразе: "сделай это,
u то, u другое ... ".
~ПРОСЫ ДЛ'il текущего коНтрОл'il-------Какое значение будет иметь перемеtшая х после вычисления этого выра­
1.
жения?
х
= 10 > 11 ? 1 :
О;
Оператор "?" называется mep1l.ap1l.ЬLM, поскольку он работает с
2.
_ __
операндами.
з. КШ<ОDО lIазначеllие оператора "запятая"'?*
Несколько ПРИСВО'ивоний "в одном"
Язык С++ позволяет IIрнмеНIIТЬ очень удобllЫЙ метод одновременного присва­
Iшания МJiOПJМ переменным одного 11 того же значения. Речь идет об объединении
сразу нсскольких присоаиваний в одной инструкции. Например, при выпmiениии
этой инструкции псременным
count = incr
=
index
=
count, incr и index будет присвоено число 10.
10;
Этот формат I1рисвоеllИЯ неС1ЮЛЬКИМ переменным общего значения можно
часто встретить в профессионально написанных программюс
ВАЖН<jI
t1'COCTQ8NbI8.oaepaTopbl
присваивания
в
С++
предусмотрены специальные состапные операторы присваивания,
D которых объединено ПРlIсваипаНl1е с еще одной операцией. Начнем с примера
и рассмотрим следующую инструкцию.
х
=
х
+ 10;
1.
Переменная
2.
Оператор "?" называется тернарным, посколы,:у он работает с тремя операндами.
З.
х после вычисления этого выражения получит значение О.
Оператор "запятая" обеспечивает выполнение заданной последовательности
операций.
С++: руководство ДЛЯ начинающих
343
Используя составной оператор присваивания, ее можно переписать в таком виде.
х
+= 10;
Пара операторов
+=
служит указанием компилятору присвоить переменной
х СУ:\IМУ текущего значения переменной х и числа
10.
Составные версии опера­
:а
:0.
je
:0
торов присваивания сущестnуют для всех БИllарных операторов (т.е. для всех
:0.
операторов, которые работают с двумя операндами). Таким образом, при таком
;0
общем формате бинарных операторов присваивания
::0
:Ф
.с::
::s:
:х
:1:
переменная
=
переменная
ор
выражение;
общая форма заllИСИ их составных версий выглядит так:
переменная
ор
=
выражение;
Здесь элемеlIТ ор ОЗllачает кош<реТIIЫЙ арифметический или лог~еский опера­
тор. 06ЬСДlllmемыli с Оllератор<щ ПРI1С13аиваШIЯ.
А вот еще один llример. ИJlСТРУJЩИЯ
х
=
;ос
100;
-
аналогична такой:
х
-=
100;
Обе эти rшструкцнн ПРIIСВ311В3ЮТ переменной х ее прежнее значение, уменьшен­
ное на
100.
Эти примеры служат иллюстрацией того, что составные операторы при св а­
ивания упрощают программирование определенных инструкций присваивания.
Кроме того, они позволяют компилятору сгенерировать более эффективный код.
Составные операторы ПРИСDaI·lDания можно очень часто встретить в професси­
онально написанных С++-программах, поэтому каждый С++-программист дол­
жеtl БЫТJ, с ними на "ты"
.
ВАЖНОI
tltА14СООАЬЗ0ваыир к ыnчево[о
слова
sizeof
Иногда полезно Зllать размер (в баЙта..х) одного из типов данных. Поскольку
раЗ:\fСРЫ BCTpoeHlIbIx С++-ТИПОВ данных в разных вычислителыlхx средах могут
быть различными, знать заранее размер переменной во всех ситуациях не пред­
ставляется возможным. Для решения этой проблемы в С++ включен оператор
sizeof
(действующий во время компиляции программы), который использует­
ся в двух следующих форматах.
sizeof (type)
sizeof имя_переменной
:6
;<t
:х
:0
.с::
j~
о
*
:ш
344
Модуль 7. Еще о типах данных и операторах
Первая версия возвращает размер заданного типа данных, а вторая
-.:
размер
заданной переменной. Если вам нужно узнать размер некоторого типа данных
(например,
int), заключите назваtш.е этого типа в круглые скобки. Если же вас
интересует размер области памяти, занимаемой конкретной переменной, можно
обойтись без круглых скобок, хотя при желании их можно использовать.
Чтобы понять, как работает оператор sizeof, испытайте следующую корот­
кую программу. Для многих 32-разрядных сред она должиа отобразить значения
1,4,4 и 8.
//
Демонстрация использования
оператора
sizeof.
#include <iostream>
using namespace stdi
int main ()
(
char Chi
int i;
cout
cout
cout
cout
, ,.
sizeof ch «
,
.
I
,.
sizeof i «
,
sizeof (Поа t) «
sizeof (double) «
,
«
«
«
«
return
/1
/1
,
1.
,
размер типа
char
int
// размер типа float
, ,. // размер типа double
размер типа
О;
Оператор 5
i zeof можно
применить к любому типу данных. Например, в слу­
чае применения к массиву он возвращает количество байтов, занимаемых масеи­
вом. Рассмотрим следующий фрагмент кода.
int nums[4Ji
cout « sizeof
пuтэ;
//
Будет
Для 4-байтных значений типа
экране отобразится число
на
int
выведено число
16.
при выполнении этого фрагмента кода на
16 (которое получается
в результате умножения
4 байт
4 элемента массива).
Как упоминалосъ выше, оператор
s i z ео f
действует во время компиляции
программы. Вся информация, необходимая для вычисления размера указанной
переменной или заданного типа данных, известна уже во время компиляции.
Оператор
sizeof главным образом используется при нarrnсании кода, который
зависит от размера С++-типов данных. Помните: поскольку размеры типов дан-
С++: PYI(OBOACTBO МЯ начинающих
345
ных О С++ определяются конкретной реализацией, не стоит полагаться на разме­
ры типов, определенные в реализации, в которой вы работ~ете в данный момент.
~ПРОСЫ ДЛЯ текущего IЮНТРОЛЯ
1.
Покажите, как присвоить переменным
о
5Q.
--------
tl, t2
t3
значение
10, используя
х
=
:s:
х
J:I
о
4
е
+ 100;
з. Оператор
с:
:r
:r
Как по-другому можно переписать эту инструкцию?
х
Ф
о
и
только одну инструкцию присваивания.
2.
х
R
с:
s i z ео f
•
возвращает размер заданной переменной или типа в
=
О
~
ш
CBoAHaSl таблица
приоритетов С++­
операторов
в табл.
7.2
показан ПрllOVJитет выполнения всех С++-операторов (от выс­
шего до самого НИЗl<ОГО). БОЛЫl1lll1СТВО операторов ассоциированы слева на­
право. Но унарные операторы, операторы присваивания и оператор
"7"
ассо­
циированы справа налево. Обратите внимание на то, что эта таблица включает
несколько операторов, которые мы пока не использовали в наших примерах,
поскольку они относятся к объектно-ориентированному программированию
(и описаны ниже).
1. t1
=
t2 =
2.
х +с
3.
Оператор
tЗ
=
10;
1ОО;
sizeof возвращает размер заданной переменной или типа D байтах.
346 Модуль 7. Еще о типах данных и операторах
................................................................................................................................
Таблица
7.2.
ПриоритетС++-операторов
НаивЫСШИЙ
() [] - > :: .
! - ++ -- - *
&
операторы приведения
sizeof new delete typeid
111/13
.* ->*
* /
%
+ « »
< <= > >=
!=
&
&&
11
?:
+=
*= 1= %= »= «=
&=
Л=
1=
Низший
ТеСТА49СnМQI<OнтролqММQДУАЮ
•
1.
Покажите, как объявить iпt-переменную с именем
test. I<ОТОРУЮ lIСIЮЗ­
можно изменить в программе. Присвойте ей начальное значение
2.
Z
100.
Спецификатор vola ti le сообщает компилятору о том, что значснис перс­
менной может быть изменено за прсделами программы. Так ли это?
З. Какой спецификатор примеllЯСТСЯ в одном из файлов Мllогофайловой JlРО­
граммы, чтобы сообщить об использовании глобальной псремешюй, объ­
явленной в другом файле?
4.
5.
Каково наиглавнейшее свойство статической локальной переменной?
Напишите программу, содержащую ФУJlКЦJIIО с именем
counter (),
1<01'0-
рая просто подсчитывает количество вы~IOВОВ. ПозаБОТI)тесь о том, чтобы
функция возвращала текущее значение счетчика ВhJЗОDОJl.
6.
Дан следующий фрагмент кода.
int myfunc ()
{
int Х;
int У;
int Z;
С++: РУКОВОДСТВО ДЛЯ начинающих 347
-
z = 10;
у
..
О;
х
а
Q.
о
1-
for(x=z;
у += х;
<15;
х
а
х++)
Q.
ф
с::
О
s:
х
rеturл
]j
У;
r
r
о
<1
Какую переменную следовало бы объявить с использованием специфика­
х
тора
~
register
с точки зрения получения наибольшей эффективности?
1.
Чем оператор" &" отличается от оператора" & &"?
8.
Какие действия выполняет эта инструкция?
х
9.
rrotate ()
н
*
:ш
1rotate ()
из ПРОСI<та
7.1,
можно за­
кодировать и ДСl\Одировать строку. Чтобы закодировать строку, выплtlитеe
циклический сдвиг влево каждого символа на некоторое количество раз­
рядов, которое задается I(ЛЮЧОМ. Чтобы декодировать строку, выполните
цю(лический сдвиг вправо каждого символа на то же самое количество раз­
рядов. Используйте ключ, который состоит из некоторой строки символов.
Существует множество способов для вычисления количества сдвигов по
ключу. Проявите свои способности к творчеству. Решение, представленное
в разделе ответов, является лишь одним из многих.
10. Самостоятельно
расширьте IЮЗМОЖНОСТИ функции Sl10W
бы она отображала все биты значения типа
binary (), что­
unsigned int, а не только
первые восемь.
Назометку
'*'n. . .
Ответы иа эти вопросы можно найти на Web-странице данной
книги по адресу:
с::
о
*= 10;
Используя функции
а
http://www.osborne.com.
Модуль 8
I<лассы и объеl<ТЫ
8.1.
8.2.
8.3.
8.4.
8.5.
8.6.
8.7.
8.8.
8.9.
Общий формат объявления класса
Определение класса 11 создание объектов
добавление в класс функций-членов
Конструкторы и деструкторы
Параметризованные конструкторы
Встраиваемые функции
Массивы объектов
Инициализация массивов объектов
Указатеди на объекты
350
МОДУЛЬ 8. Классы и объеl<ТЫ
До сих пор мы писали про граммы, в которых не использовались какие бы
то ни было С++-средства объектно-ориентированного программирования. Та­
ким образом, программы в предыдущих модулях отражали структурированное,
а не объектно-ориентированное
программирование. Для написания же объск­
тно-ориентированных программ необходимо использовать классы. Класс
- это
базовая С++-единица инкапсуляции. Классы используются для создания 06"ек­
тов. При этом классы и объекты настолько существенны для С++, что ОСТa.JIЬНая
часть книги в той или иной мере посвящеtta ИМ.
ОСНОВЫ ПОНЯТИЯ класса
Начнем с определения терминов класса и обl,екта. /(ласс можно представить как
некий шаблон, катаРЬН':'1 nпределяет формат обьскmа. Класс ВЮIJOчаст как Шllшые,
так и код, предназначенныii для ВblполнеllИЯ над :пими ДШIIIЫМII. В С++ сrн:UllфН­
кация класса используется для построения 061,ектов. Объекты
- это экзе.Мn"IJIРЫ
lIa60p [шанов, которые определяют, как
строить 06ъект. ВажllО понимать, '1ТО класс - это ЛОПNеская аБСТР<lКЦЮl, которая
класса. По сути, класс представляет собой
реально не существует до тех пор, пока не будет создан объект этого класса. Т.С. то,
что станет физическим представлением этого Юlaсса в паМЯТII компьютера.
Определяя класс, вы объявляете данные, которые он содержит, и код, который
выполняется над этими даННblМИ. Хотя очень простые клцссы могут содержать TO.'lЬ­
ко код ИЛИ только данныс, большинство реальных КJ1aCCOB содержат оба компонента.
В классе данные объявляются в пиде переМСIIНЫХ, а код оформляется в виде функ­
ЦИЙ. Функции и переменные, составляющие КJ1acc, называются его 'LЛенш.ш. Так им
образом, переменная, объявленная в классе, называется членам данных, а фушщия,
объявленная в классе, называется фу·ющueU-"Ч.Лf:1IO.М. Иногда вместо термина член
aaнubIX используется термин nереАte1l1tоя экземпляра (ШIИ nере.м.еuноя реШlUзацuu).
ВАЖНОI
1:1- Q6"'.ИЙ формоХ-ОБЪЯ8АеI:U4Sl
класса
Класс создается с помощью ключевого слова
ния класса имеет слеДУ10ЩИЙ вид.
claS5
имя_класса
закрытые
{
данные
и
ФУ],!КЦИИ
данные
и
ФУНКЦИИ
public:
открытые
список объектов;
class. Общий формат объяuле­
С++: руководство ДЛ';1 начинающих
351
фигурной скобкой объявлеиия класса (В качестве элемента СПИСОК_Объектов),
-
но это необязательно. После объявления класса его элем~нты ]'.Н)ЖНО создавзп,
:0
Здесь элемент имя_класса означает имя класса. Это имя становится именем
нового типа, которое можно использовать для создания объектов класса. Объ­
екты класса можно создать, указав их имена непосредственно за заКрЫВaIOщейся
по мере необходимости.
чанию все элементы, определенные в классе, являются закрытыми. Это означает,
что к ним могут получить доступ только другие члены их класса; никакие другие
части программы этого сделать не могут. В этом состоит одно И~'I llрОЯ13лений ин­
капсуляции: программист В полной мере может упраВЛЯТ1> ДОСТУПО\I к определен­
ным элементам данных.
Чтобы сдежlТЬ части класса ОТЩ)Ь!ТЫМlI (т.с. дОСТУПIlЫЮ'1 JIЛЛ l(РУПIХ частсii
программы), необходимо оБЪЯnИТ1> их 1I0CJJC J(J]\О'IСnОГО СЛОlJа PI.Jbl i с. Всс пере­
MeJ-llibJе или функции, определенные после спецификаТор<t риЬ2.
ic, ЛОСТУПШ..J
для всех других функций I1рограммы. Обычно в программе организуется достуr
к закрытым членам класса через его открытые функции. Обратите внимание
ic
1-1'
стоит двоеточие.
Хорошо разработанный класс должен определять одну и ТОЛЬКО одну логиче­
скую СУЩНОСТЬ, хотя т,шое синтаксическое правило отсутствует в "конституции"
С++. Например, клш;с, в котором хранятся имена лиц и их телефонные номера, не
стоит использовать для хранения информации о фондовой бирже, среднем количе­
стве осадков, циклах соmlеЧIЮЙ активности и прочих
l:Ie связанных с lCою<ретными
лицами данных. Основная мысль здесь состоит в том, что хорошо разра60ТШШЫЙ
класс должен включать логически связаlШУЮ информацию. Попытка поместить не­
связанные между собой данные в один класс быстро деструктуризнрует ваш код!
Подведем первые итоги: в С++ класс создает новый тип данных, который
можно использовать для создания объектов. В частности, класс создает логиче­
скую конструкцию, которая определяет отношения между ее членами. Объявляя
переменную класса, мы создаем объект. Объект характеризуется физическим су­
ществованием
11
.
v
:0>
.,.1)
:\0
::!;;
1i
Любой класс может содержать как закрытые, так и OTKPblTLIC 'IJlellbl. По умол­
то, что после ключевого слова риЫ
:• 1i
>-
является ко/шретным экземпляром класса. Другими словами,
объект занимает некоторую область памяти, а определение типа
- нет.
объектов
Для иллюстрации мы создадим класс, который инкапсулирует информацию о
транспортных средствах (автомобилях, автофургонах и грузовиках). В этом клас-
U
()
а
.<
:::::!
352
МОДУЛЬ 8. l<лассы и обьекlы
се (назовем его
Vehicle) будут храниться три элемента информации о транс­
портных средствах: количество пассажиров, которые могут там поместиться, об­
щая вместимость топливных резервуаров (в литрах) и среднее значение расхt>да
горючеro (в к#илометрах на литр).
Ниже представлена первая версия класса
перемеНllые экземпляра:
то, что класс
Vehicle
Vehicle. В нем определены три
passengers, fuelcap и mpg. Обратите внимание на
не содержит ни одной функции. Поэтому пока его можно
считать классом данных. (В следующих разделах мы дополним его функциями.)
class Vehicle (
public:
int passengers; 11
11
int fuelcapi
1/
1/
int rnpgi
количество
вместимость
пассажиров
топливных
резервуаров
в литрах
расход
горючего
в
километрах
на
литр
1.
J'
Переменны~эк,,~мпляра, определенные в классе Vehicle, ИJIJI10Cтрируют общий
формат их объявления. Итак, формат 06ъявлеН11Я переменной экземпляра таков:
тип имя_перемеННОЙi
Здесь элемент тип определяет тип переменной экземпляра, а элемент имя_ .'1е­
ременной - ее имя. Таким образом. переменная экземпляра объявляется так же,
как локальная перемеIiНая. В классе Vehicle все переменные экземпляра объ­
явлены с использованием модификатора доступа
public,
который, как упоми­
налось выше, позволяет получать к ним доступ СО стороны кода, расположенного
даже вне класса
Vehicle.
Определение class создает новый тип данных. В данном случае этот новый
тип данных называется Vehicle. Это имя можно использовать ДJIЯ объявления
объектов типа Vehicle. Помните, что объявление class - это лишь описание
типа; оно не создает реальных объектов. Таким образом, предыдущий код не озна­
чает существования объектов типа
Vehicle.
Чтобы реально соэдаТf, объект класса Vehicle, используйте, например, такую
инструкцию:
Vehicle
miлivал;
1/
Создаем объект
miлivап
типа
Building.
После выполнения этой инструкции объект mini van станет экземпляром класса
Vehicle, Т.е. обретет "физическую" реальность.
При каждом создании экземпляра масса создается объект, который содержит
собственную копию каждой переменной экземпляра, определенной этим мас­
сом. Таким образом, каждый объект класса Vehicle будет содержать собствен­
ные копии переменных экземпляра
passengers, fuelcap и mpg. Для доступа
С++:
PYI<OBOACTBO
ДЛ~ начинающих
к этим переменным используется оператор "точка"
(.).
353
Оператор "точка" свя­
зывает имя объекта с именем его члена. Общий формат этого оператора имеет
такой вид:
~
объект. член
1!3
ф
Кш< Lшдите, объект ука:iывается слепа от оператора "точка", а его член
Наllример, чтобы присuоить IIсреМСIilЮЙ
16; используйте следующую
s:
fuelcap объекта rnini van значение
U
U
инструкцию.
В общем случае оператор "точка" можно использовать для доступа как к псре­
меltным экземпляров, так и к функциям.
Теперь рассмотрим программу, 8 которой используется класс
Программа,
в
~оторой
используется
класс
Vehicle.
Vehic1e.
#inc1ude <iostream>
using narnespace std;
// Об~nение класса Vehicle.
class Vehicle
public:
int passengers; // количество пассажиров
int fuelcap;
i/ вместимость топливных резервуаров
// в литрах
int mpg;
// расход горючего в километрах на литр
};
int rnain ()
Vehicle rninivan; //
//
int range;
Соэдание объех~а
класса
minivan
(ЭХ8eкnn~а
Vehicle).
// Присваиваем значения полям в объекте rninivan.
minivan.passengers = 7; // Обра~ите вникакие на испоnьэо­
вавие
minivan.fuelcap = 16;
minivan.mpg = 21;
//
//
//
//
которое может
Вычисляем расстояние,
средство
после
оператора "~o.. кa" ДJUI дос~yua х
чn_uc о&иlJC~а.
заливки полного
2i
~
minivan.fuelcap = 16;
11
о
справа.
-
проехать
бака
транспортное
горючего.
354
МОДУЛЬ 8. l<лассы и объекты
..................................................................................................................................
range
=
cout «
«
* rninivan.rnpg;
rninivan.fuelcap
"Минифургон может перевез'1'И
«
"
"
return
О;
пассажиров
на
километров."
расстояние
«
"«
" «
rnini van. passengers
range
"\п";
Рассмотрим внимательно эту программу. В функции
rnain () мы сначала соз­
min i van. а затем осуществляем доступ
к переменным этого экземпляра mini van, ПРI1СВаивая им конкретные значения
и используя эти значения 8 вычислениях. ФУНlЩИЯ та i n () получает доступ к
Ч.'IСШШ l<ласса Vehicle, ПОСКОЛЬ1<У они объяl3ЛСНЫ открытыми, Т.е. рubliс-чле­
lIа~ш. ЕСЛIl бы 8 IIХ объявлсшш не было СIIСЦIlфllкатора доступа public, доступ
к ним огра~IИLJИВался бы рамками класса Veh2_cle, а функция main () не им.ела
даем экземпляр класса Vе hi с 1 е с именем
бы возможности использовать их.
При Бl,lПолнении этой программы получаем такие результаты:
Минифургон может
перевезти
7
пассажиров
на расстояние
ЗЗб
километров.
Прежде чем идти дальше, имеет смысл вспомнить основной принцип про­
граммнроваllИЯ классов: каждый объект класса имеет собственные копии пере­
менных экземпляра, определенных в этом классе. Таким образом, содержимое
переменных в одном объекте может отличаться от содержимого аналогичных
переменных о другом. Между двумя объектами нет связи. за исключением того,
что ОНИ являются объектами одного и того же типа. Например, если у вас есть
доа объекта типа
Vehicle и каждъп':i объект имеет свою копию переменных ра­
ssengers, fuelcap и mpg, то содержимое соответствующих (одноименных)
перемеНI-IЫХ этих двух экземпляров может бы'гь разным. Следующая программа
демонстрирует это.
//
Эта
программа
создает два
объекта
класса
Vehicle.
#include <iostream>
using namespace std;
// Объявление класса Vehicle.
class Vehicle
public:
int passengersi // количество пассажиров
int fuelcap;
// вместимость топливных
резервуаров
С++: руководство ДЛЯ начинающих
355
.......................................................................................................1' ....................... .
//
//
int mpg;
в литрах
расход
горючего
в
километрах
на литр
};
int main ()
Vehicle minivani
//
Vehicle sportscar; //
Создаем первый объект типа
Создаем второй объект типа
//
Теперь O~K~ шinivаn и
/ /
КОПИИ переиеlUDlX реamr8ации xnасса
sportscar
Vehicle.
Vehicle.
имеют собс~еaкwе
Vehi.cle.
int range1, range2;
// Присваиваем значения
minivan.passengers = 7;
minivan.fuelcap = 16;
minivan.mpg = 21;
полям объекта
minivan.
1/
полям объекта
sportscar.
Присваиваем значения
sportscar.passengers = 2;
sportscar.fuelcap = 14;
sportscar.mpg = 12;
// Вычисляем расстояние, которое может проехать каждое
/1 транспортное средство после заливки полного бака
/ / горючего. .
rangel
rninivan.fuelcap * minivan.mpg;
range2 = sportscar.fuelcap * sportscar.mpgi
cout «
«
«
cout «
«
«
«
return
"Минифургон может
"
,;
пассажиров
на
километров."
"Спортивный
перевезти
расстояние
«
,,«
" «
minivan.passengers
range1
"\п" i
автомобиль
может
перевезти
sportscar.passengers
"
"
О;
пассажира
на расстояние
километров."
«
"\п";
" «
range2
"
356
Модуль8.l<лассы и объеl<ТЫ
Эта программа генерирует "акие результаты.
Минифургон
может
перевезти
7
пассажиров
на
расстояние
ЗЗ6
километров.
Спортивный
яние
168
автомобиль
может
перевезти
2
пассажира
на рассто­
километров.
Как видите, даииые о мииифургоне (содержашиеся в объекте
minivan)
со­
вершеино не связаны с данными о спортивном автомобиле (содержащимися в
объекте
spor tscar).
Эта ситуация отображена на рис.
mlnivan
sportscar
Рис.
,'"
""
раsзengет
8.1.
7
fuclcap
16
mpg
21
passengers
2
fuelcap
14
mpg
12
8.1. Пере,J"енные одною эк.земnл.яра
не связаны с nере",.еuны.МII другого 3К­
зе,J,UL'lЯра
~ПРОСЫ М'iI текущего КоНтрОл'il-_ _ _ _ _ __
1.
2.
Какие два компонента может содержат) масс?
Какой оператор используется для доступа к членам класса посредством
объекта?
3.
Каждый объект имеет собственные копии
----
•
1.
Класс может содержать данные и код.
2.
Для доступа к членам класса посредством объекта используется оператор Uточ:ка".
З. Каждый объект имеет собственные копии переменны" экземпляра.
С++: РУI(ОВОДСТВО для начинающих
357
членов
Пока класс
Vehicle
содержит только данные и ни одной функции. И хотя
такие "чисто информационные" классы "полне допустимы, большинство классов
все-таки имеют фУlIкции-члены. Как правнло, функции-члены класса 06рабаты­
пают данные, определенные в этом классе, и во многих случаях обеспечивают до­
ступ к этим данным. Другие части программы обычно взаимодействуют с клас­
сом через его функции.
Чтобы ПРОНЛЛlOстрировап> ИСПОЛl>зооанне функций-членов, добавим функ­
цию-член D IUшсс
Vehicle.
ВСIIОМl!ите, что функция
main ()
вычисляет рас­
СТОЯl1ие, которое мож~т просхап, заданное транспортное средство после заливки
полного бака горючего, путем умножения вместимости топливных резервуаров
(в литрах) на расход горючего (в километрах на литр). Несмотря на формаль­
ную KoppeKTHoc·rb, эти вычисления выполнены не самым удачным образом. Вы­
числение этого расстояния лучше бы предоставить самому классу
Vehicle,
по­
скольку обе величины, используемые в вычислении, инкапсулированы классом
Vehicle.
Внеся в класс
Venicle
функцию, которая вычисляет произведение
члеJJОВ даJlНЫХ этого класса, мы значительно улучшим его объектно-ориентиро­
ванную структуру.
Чтобы добавить ФУJJКЦИЮ 8 класс
Vehicle, необходимо задать ее прототип
внутри объявления этого класса. Например, следующая версия класса Vehicle
содержит функцию с и менем r а n ge ( ) , которая возвращает значение максималь­
ного расстояния, которое может проехать заданное транспортное средство после
заливки полного бака I·орючего.
// Новое объявление класса Vehicle.
class Vehicle (
public:
int passengersi // количество пассажиров
int fuelcap;
// вместимость топливных резервуаров
// Е литрах
int mpg;
// расход горючего в километрах на литр
//
iлt
);
OCS~._
rалgе();
ФУВICЦИИ-чn_а
//
функция
ranqe () .
вычисления максимального пробега
358
МОДУЛЬ 8. Классы и объекты
Поскольку функции-члены обеспечены своими прототипами в определении
класса (в данном случае речь идет о функции-члене
их не нужно по­
range () ),
мещать больше ни в какое другое место ПРОГР;IММЫ.
Чтобы реализовать функцию, которая ЯВ.1lяется членом класса, необходимо
сообщить КОМПИЛЯТОРУ, какому классу она принадлежит, квалифицировав имя
этой ФУНКЦIIИ С именем класса. Например, вот как можно записать код функ­
range () .
ции
// Реализация функции-члена range().
int Vehicle::range() (
return mpg * fuelcap;
ОбраТlIте DJ-Нlмание на опеrатор, оБОЗllачаемый символом":
ляст IIlШl IИlасса
Vehicle
от IНIСIШ фушщии
range ().
: ",
который отде­
Этот оператор называется
071epamopO.~1 разрешеllllЯ 06ласти оидll..моcmu WIИ оиераmором раэрешеuuя коитек­
ста. Он связывает имя класса с именем его члена, чтобы сообщить компилятору,
Кa.lЮМУ классу принадлежит данная функция. В данном случае он связывает функ ..
цию
range ()
С классом
Vehicle. Другими словами, оператор": :" заявляет о том,
ЧТО функция range () находится в области видимости класса Vehicle. Это очень
важно, поскольку различные классы могут использовать одинаковые имеиа фуик­
циЙ. I<омшL'IЯТОР же определит, к какому классу принадлежит функция. именно с
помощью оператора разрешения области видимости и имени класса.
Тело фушщии
return mpg
range ()
СОС'roит из одной строки.
* fuelcap:
Эта инструкция возвращает значение расстояния как результат умножения
значений псременных
mpg и fuelcap. Поскольку каждый объект типа Veh:.cle
имеет собственную копию переменных mpg и fuelcap, при вьшолнении функ­
ции range () используются копии этих переменных, принадлежащих вызываю­
щему объекту.
В телс функции
range () обращсние к перемснным mpg И fuelcap проис"о­
дит напрямую, Т.е. без указания имени об"екта и оператора "точка". Если функ­
ЦШI-ЧJlСII использует переменвую экземпляра, которая определена в том же клас­
се. не нужно де;:rать явную ссылку на объект (с участием оператора "точка"). Ведь
компилятор в этом случае уже точно знает, какой объект подвергается обработке,
поскольку функция-член всегда вызывается относительно некоторого объекта
"ее" класса. Коль вызов функции произошел, :шачит, объект известен. Поэтому
в функции-члене нет необходимости указывать объект еще раз. Следовательно,
чле1'lЫ mpg и fuelcap в функции range () нсявно обращаются к копиям этих
переменных. относящихея к объекту, который вызвал функцию
range (). Одна-
С++: PYI<OBOACTBO ДЛ~ начинающих
1(0 КОД,
расположенный вне класса
mpg и fuelcap,
Vehicle, должен
359
обращаться к переменным
используя имя объекта и оператор "точка".
Функции-члены можно вызывать только относителыю заданного объск­
та. Это можно реализовать одним из двух способ08. Бо-первых, функцию-член
может вызвать код, расположенный вне класса. В этом случае (т.е. при вызове
функции-члена из части программы, которая находится вне класса) необходимо
1~
:Ф
:~
:0
::s:
::D
:~
:<
использовать имя объекта и оператор "точка". Например. при выполнении этого
:0
кода будет вызвана ФУНКЦИЯ
:~
rangel
range () ДЛЯ объекта mini van.
= minivan.range()i
Быражение
minivan. range ()
означает вызов Фуикции
будет обрабатывать копии переменных объекта
minivan.
range (),
которая
Поэтоr<IY фУНJ(ция
range () [ЮЭDратит ма"г.ИМ~ЫIУ\О дальность пробега для объекта mini van.
Во-вторых, ФУJJКЦНIO-ЧЛ('II МОЖI-IО вызвать \IЗ другой Функции-члеllа того же
класса. Б этом случае вызов также ПРОIIСХОДИТ напрямую, без оператора "точка",
ПОСIЮЛЬКУ КОМПИЛЯТОРУ уже известно, каl<ОЙ объект подвергается обработке.
И только тогда, когда Функция-член вызывается кодом. который не принадлежит
классу, необходимо использовать имя объекта и оператор "точка".
Использование класса
Vehicle
и функции
range ()
демонстрируется в при­
веденной ниже программе (для этого объединены все уже знакомые вам части
кода и добавлены недостающие детали).
1/
Про грамма
использования
класса
Vehicle.
#include <iostream>
using namespace stdi
// Объявление класса Vehicle.
class Vehicle
public:
int passengersi // количество пассажиров
int fuelcapi
// вместимость топливных резервуаров
/1 в литрах
int mpgi
// расход горючего в километрах на литр
//
Об~.RИе фунхции-чnена,ranqе().
int range()i
//
функция
вычисления
};
// Реалиэация функции-члена range().
int Vehicle::range() {
максимального
пробега
360
МОДУЛЬ 8. Классы и объекты
return rnpg * fuelcap:
int ·main ()
Vehicle minivan:
//
Vehicle sportscar; //
Создаем первый объект
типа
Создаем второй объект
типа
Vehicle.
Vehicle
int rangel, range2;
// Присваиваем значения
minivan.passengers = 7;
minivan.fuelcap = 16:
minivan.mpg = 21:
полям объекта
minivan.
11
полям объекта
sportscar.
Присваиваем значения
sportscar.passengers = 2:
sportscar.fuelcap = 14;
sportscar.mpg = 12;
//
Вычисляем расстояние,
/1
11
транспортное
средство после
cout «
«
«
cout «
«
«
«
minivan.range();
11
sportscar.range(): 11
=
может проехать
каждое
заливки полного бака
I
горючего.
range1
range2
которое
Вызов фу.хции
объек~о.
,.~
"Минифургон может перевезти
"«
"
"
"
пассажиров
на расстояние
километров."
"Спортивный
«
с::<
ranqe() AnR
xnасса Vehicle.
minivan.passengers
range1
"\п";
автомобиль
может
пере везти
"
sports~ar.passengers
"
"
пассажира
на
километров."
расстояние
«
" «
range2
"\п";
return.O:
При выполнении программа отображает такие результаты.
Минифургон может
километров.
перевезти
7
пассажиров
на расстояние
336
С++: руководство ДЛЯ начинающих
Спортивный автомо~иль
168
яние
может
перевезти
пассажира
2
361
на рассто­
километров.
:ti
~ПРОСЫ дl\Я текущего КОНТРОЛЯ
!;;:
ф
~
о
_ _ _ _ _ _ _ __
:s:
:ti
u
1.
Как называется оператор":
2.
Каково назначение оператора":
U
: "1
о
<
:::.::
:"?
З. Если функция-член вызывается извне ее класса, то обращение к ней
должно быть организовано с использованием имени объекта и оператора
"точка". Правда ли это?
•
00ЗД'tJНИ8JКf(Q.сса~справочноЙ
информации
HelpClass . срр
Если бы мы попытались резюмировать суть I(ласса одним
предложением, оно IIрнблизительно ПJlОЗВУЧало бы так:
класс инкапсулирует функциональность. Иноща главное
-
знаПJ, гдс заканчи­
вается одна "функциональность" и начинастся другая. Как правило, назначение
классов - бытъ CTPOl-IтеЛbJ-IЫМН блоками "большого" ПРИJlОЖСНИЯ. Для этого каж­
дый класс должен представлять одну функциональную "единицу", которая выпол­
няет четко очерченные деЙстпия. Таким образом, свои
IUIaCCbI имеет смысл делать
настолько мелкими, насколько это возможно. Другими словами, классы, которые
содержат постороннюю информацию, дезорганизуют и ДССТРУlCтуризируют код,
110
IUшссы, которые содержат недостаточную ФУIIКЦИОНальность, можно упрекнуть в
раздробленности. Истина, как известно, лежит посерсдине. Соблюсти баланс
- по­
сле решения именно этой сверхзадачи наука программирования становится искус­
ством програММИРОВalШЯ. К счастью, 60ЛЫl1lШСТАО программистоп считают, что с
опытом находить этот самый баланс становится все легче и легtJе.
Итак, начнсм прноб[1СтаlЪ ОПЫТ. ДЛЯ этою
ную систему I1З проекта
3.3 (СМ.
модуль
3)
IlOlIblTaCMCH
прсобраэопать справоч­
в ЮJасс Сl1раВОЧllоi:'lинформации. Почему
эта идея заслуживает внимания? Во-первых, справочная система определяет одну
1.
Оператор"::" называется оператором разрешения области видимости.
2.
Оператор и:
з.
Правда. При вызове функции-члена извне ее класса необходимо указывать имя
:" связывает имя
класса с именем его члена.
объекта и использовать оператор·"точка".
'S;
~
i
8
.s!
!
с:
u
u
J
362 Модуль 8. Классы и объекты
................................................................................................................................
лоrnческую "единицу". Она просто отображает синтаксис С++-инструкций управ­
леlШЯ. ТaJСИМ обра~ом, ее функциональность I\{)МПaJ\Тна и хорошо определена.
60-
вторых, оформление справочной системы в виде класса кажется уда</ным решением
даже с эстетической точки зрения. Ведь для того, чтобы предложить справочную
систему любому пользователю, достаточно реализовать конкретный объект этого
класса. Наконец, поскольку при "классовом" подходе к справочной системе мы со­
блюдаем принцип инкапсуляции, то любое ее усовершенствование или изменение
не вызовет нежелательных побочных эффектов в программе, которая ее использует.
Последовательность действий
1.
Создайте НОВЫЙ файл с именем
ПРОСI<та
2.
3.3, НеlрЗ . срр,
D файл
HelpClass. срр. Скопируйте фай]! из
HelpCl ass. срр.
Чтоuы IIреобраЗОП<lТl-, справочную с"стему D класс, Сllачала необходимо
ТО'lНО определить ее составляющие. Оапример, файл НеlрЗ
. срр содержит
код, который отображает меню, вводит команду от пользователя, ПрОБ('рЯ­
ет ее допустимость и отображает информацию о выбранной инструкции.
Кроме того, в программе создан цикл, выход из которого организован носле
ввода пользователем буквы
"q".
ОчеВИДIJО, что меню, проверка допустимо­
СПI пведенного запроса и отображение запрошенной информации явля­
ются неотьемлемыми частями спраВОЧIIОЙ системы, чего нельзя сказать о
приеме l<ОМШlДы от пользователя. Таким образом, можно уверенно создать
юшсс, который будет отображать справочtlУЮ информацию, меню и про­
верять допустимость введенноro запроса. Назовем функции, "отвечаюшие"
за эти действия,
helpon (), showmenu ()
и
isvalid ()
cootbetctbeJ-IНО.
З. Объявите класс Help таким образом.
// Класс, который инкапсулирует справочную систему.
class Help (
public:
void helpon(char what);
void showmenu();
bool isvalid(char ch);
};
Обратите внимание на то, что этот класс включает лишь функции. В нем
нет ни одной переменной реализ<щии. Как упоминалось выше, класс та­
кого вида (как и класс без функций) вполне допустим.
( В вопросе 9 раз­
дела "Тест для самоконтроля по модулю В" вам предлагается внести в класс
Help
переменную экземпляра.)
С++: РУКОВОДСТВО ДЛЯ начинающих
4.
Создайте ФУНКЦИЮ
363
helpon ().
// Отображение справочной информации.
void Help::helpon(char what)
switch (what) (
саве '1':
cout « "Инструкция if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п";
break;
сазе '2 ':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) (\п";
cout « " case константа:\п";
cout « "
последовательность инструкций\п";
cout « "
break;\n";
cout « " // ... \п";
cout « "}\п";
break;
case t З' :
cout « "Инструкция fоr:\п\л";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкция;\л";
break;
сазе '4':
cout « "Инструкция while:\n\n";
cout « "whilе(условие) ИНСТРУКЦИЯi\П";
break;
сазе '5':
cout « "Инструкция do-whil~:\n\n";
cout « "do {\п";
cout « " инструкция;\п";
cout « "} while (условие);\п";
break;
саsе '6':
cout « "Инструкция break:\n\n";
cout « "break;\n";
break;
саве '7':
cout « "Инструкция continue:\n\n";
~
ф
~
о
:s:
2i
()
()
~
1
:s
.~
i
D
U
~
J
~~.....~~.~X~~.~:..~~~~~I..~.~.?~.~~.I............................................................
cout « "солtiлuе;\л";
break;
сазе '8':
cout « "Инструкция goto:\n\n";
cout « "goto метка;\л";
break;
cout «
5.
"\л";
Создайте ФУНКЦИЮ shоwтелu
11
Отображение меню справочной системы.
void Help:
cout «
cout «
cout «
cout «
cout «
cout «
cout «
cout «
cout «
cout «
6.
:showmenu()
(
"Справка
инструкциям:\л";
"
"
"
"
"
"
"
"
по
1. it\n";
2 • switc!1\n";
З. tor\n";
4 • while\n";
dо-whilе\л";
5.
6. break\n";
7 . continue\n"i
8 . goto\n";
"Выберите
Создайте ФУНКЦИЮ
11
11
() .
вариант
справки
(q
для
выхода)
: " ,.
isvalid ().
Лроверка допустимости команды пользователя
возвращает значение ИСТИНА,
(функция
если команда допустима).
bool Help::isvalid(char ch) (
i f (ch < '1' I I ch > '8' && ch ! = 'q')
rеturл false;
else
return true;
7.
Перепишите функцию таiл
класс
Help.
Вот как
() из проекта 3.3 так, 'lтобы она использовала
выглядит полный Tel(CT файла HelpClass. срр.
1*
Проект
8.1.
Преобразование
справочной
системы из
проекта
з.з
в
С++: РУКОВОДСТВО МЯ начинающих
класс
365
Help.
*/
j
iinclude <iostream>
using namespace std;
// Класс, который инкапсулирует
class Help (
public:
void helpon(char what);
void showmenu();
bool isvalid(char ch);
о
справочную систему.
};
/1 Отображение справочной информации.
void Help::helpon(char what) (
switch(what) (
case '1':
cout « "Инструкция if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п";
break;
case '2':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) (\п";
cout « " case KOHcTaHTa:\n";
последовательность инструкций\п";
cout « "
break;\n";
cout « "
cout « " // ... \п";
cout « "}\п";
break;
case '3':
cout « "Инструкция for:\n\n";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкция;\п";
break;
case '4':
cout « "Инструкция while:\n\n";
cout « "whilе(усnовие) инструкция;\п";
break;
::s:
2i
u
U
~
366 МОДУЛЬ а.Классы и объеКТbI
сазе
'5':
cout « "ИНСТРУКЦИЯ do-whi1e:\n\n";
cout « "do {\n";
cout « " инструкция;\п":
cout « "} whi1e (условие);\п";
break;
сазе '6':'
cout « "ИНСТРУКЦИЯ break:\n\n":
cout « "break;\n";
break;
case '7':
cout « "Инструкция continue:\n\n":
cout « "continue:\n";
break;
case '8':
cout « "Инструкция goto:\n\n";
cout « "goto метка;\п";
break;
cout «
"\п";
// Отображение меню справочной системы.
void Help::showmenu() {
cout « "Справка по инструкциям:\п";
cout « " 1. if\n" ;
cout « " 2 . switch\n";
cout « " 3 . for\n";
cout « " 4 • whi1e\n":
cout « " 5. do-whi1e\n" ;
cout « " 6. break\n" ;
cout « " 7 . continue\n";
cout « " 8 . goto\n";
cout « "Выберите вариант справки (q
//
//
ДЛЯ
выхода)
Проверка допустимости команды пользователя
возвращает
значение ИСТИНА,
(функция
если команда допустима).
He1p::isvalid(char ch) {
i f (ch < '1' I I ch > '8' & & ch ! = 'q')
Ьоо1
.
: ",
С++: PYI(OBOACTBO д,лfl начинающих
367
return falsei
else
return truei
J5
!;;:
(1)
~:s:
int main ()
char choicei
Help hlpobi //
//
J5
u
U
Q
<
~
Создаем экземпляр класса
Используем объект
класса
Help
для
Help.
отображения ИН­
формации.
for(;i)
do (
hlpob.showmenu()i
cin » choicei
while(!hlpob.isvalid(choice))
i
if(choice == 'q') break;
cout « Н\п";
hlpob.helpon(choice);
return
О;
Опробовав эту программу, нетрудно убедиться, что она функционирует по­
добно той, которая представлена
D моnуле 3.
ПреимущеСТDО этого подхода со­
стоит в том, что теперь У вас есть компонент справочной системы, который при
необходимости можно использооать во многих других приложениях.
ВдЖi
';1
кокстру.к.т.а,рьL,иrдеару,кI.оры
в предыдущих при мерах перемеННblе каждого Vеhiсlе-объекта устанавли­
вались "вручнуюn С помощью следующей последооательности инструкций:
minivan.passengers = 7;
rninivan.fuelcap = 16;
minivan.mpg = 21;
368
МОДУЛЬ 8. l<Лассы и объекты
Профсссионзл никогда бы НС использовал Jlодобный ПОДХОД. И дело не СТОЛЬ­
ко в том, что таким образом можно попросту "забыть"
06 одном
или нескольких
данных, сколько в ТЩоf, что существует гораздо более удобный способ это сделать.
Этот способ
использопание конструктора.
-
Конструктор инициализирует объект при его создании. Он имеет такое же
имя, что и сам класс, и синтаксически подобеll функции. Однако в определении
конструкторов не указывается тип возвращаемого зtlачсния. Формат записи кон­
структора такой:
имя_класса
//
()
код конструктора
Оuычно конструктор IIспользуется, чтоu!>! l1рндать псреМСИl1ЫМ ЭI<ЗСМП.JlЯРi1,
опрсдеЛСllllЫМ
n ICлассе, lШЧ<lЛЫlые значения
Н.Ш UЫIIOЛНИТl. I1СХОДllЫС деЙСТUI1Я,
необходимые для создания полностью сформирооашюго 06ЬСl\та.
Дополнением
«
конструктору служит деструктор. ВО
MIIOI'J1X
случаяХ I1рИ
разрушении объекту необходимо выполнить Ii~KOTOPoe действие или Дi1же неко­
торую последовательность деikТВJiЙ. Локальные объекты со:щаются при входе
в блок, в котором они определены, 11 разрушаются при выходе из него. Глоба.1Ь­
ные объекты разрушаются IJРИ завершении программы. Существует множеС"DО
факторов. обусловливающих к{'обходимопъ ,,\ecT[JYKTopa. Например, объект
должен освободить ранее ОЫДt'ЛСI111УЮ для I1CJ'O lIамять или закрыть ранее откры­
тый для неl'О файл. В С++ ПМ.еюю ДССТРУJ<ТОру пору чается обработка проиесса
дезактивизации объекта. Имя деструктора С0811адает с именем
предваряется символом
"-".
KOHCTPYJ(TOpa,
но
llодобно конструкторам деструкторы не возвраща­
ют значений, а следоватеJJЬНО, в их объявлениях отсутствует тип возвращаемого
значения.
Рассмотрим простой при мер, в котором используется конструктор и деструк­
тор.
//
Использование
конструктора и дec~pYKTopa.
#include <iostream>
using патеsрасе stdi
class MyClass
public:
int Х;
//
Об~.ек XOBCТPYX~OP и дec~yктop ДnR класса МYClass.
С++: РУКОВОДСТВО ДI\~ начинающих
MyClass();
-МуСlаss();
//
//
369
конструктор
деструктор
};
// Реализация конструктора
MyClass ~ : MyClass () (
х
класса
MyClass.
= 10;
// Реализация деструктора класса MyClass.
MyClass: : -МуСlаss () {
cout « "Разрушение объекта ... \п";
int main ()
MyClass
MyClass
cout «
оЫ;
оЬ2;
оЫ.х
return
«
" " «
оЬ2.х
«
"\п";
О;
При выполнении этапрограмма генерирует такие результаты.
10 10
Разрушение
объекта
Разрушение
объекта
.. .
.. .
Б этом примере конструктор для класса МуС 1 а s s выглядит так.
// Реализация конструктора
MyC1ass::MyClass() (
х
=
класса
MyClass.
10;
Обратите внимание на рubliс-определение конструктора, которое позволяет
ВbIзывать его из кода, определенного вне класса
MyClass.
присваинает переменной экземпляра х значение
10.
вызывается при создании объекта класса
нии строки
MyClass
оЫ;
MyClass.
Этот конструктор
Конструктор
MyClass ()
Например, при выполне­
МОДУЛЬ 8. Классы и объеl<ТЫ
370
для объекта оЫ вызывается конструктор
MyClass (),
переменной экземпляра оЫ
То же самое справедливо и в отно­
.х
значение
10.
который присваивает
шении объекта оЬ2, Т.е. в результате создания объекта оЬ2 значение переменной
экземпляра оЬ2 . х также станет равным
Деструктор для класса
MyClass
10.
имеет такой вид.
// Реализация деструктора класса MyClass.
MyClass: : -МуСlаss () {
cout « "Разрушение объекта ... \n";
Этот деструктор попросту отображает сообщение, но В реальных программах
деструктор используется для освобождения одного или нескольких ресурсов
(файловых дескрипторов или памяти), используемых классом.
~просы дII5I текущего I<оНтрОл5l-------1.
Что представляет собой конструктор и в каком случае он выполняется?
2.
Имеет ли конструктор тип возвращаемого значения?
3.
В каком случае вызывается деструктор?
,..
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .d.UC. .8D. . . . . . . . . . . . . . . . . .Nk
. .. .
ВАЖНОI
1:.._ ПаРQмеtРИ30ВQNНhl8, КQI;ICTpYКZOPb l
в предыдущем примере использовался конструктор без параметров. Но чаще
приходится иметь дело с конструкторами, которые принимают один или несколь­
ко параметров. Параметры вносятся В конструктор точно так же, как в функцию:
для этого достаточно объявить их ВНУТРИ круглых скобок после имени KOHCТPYI(­
тора. Например, вот как мог бы выглядеть параметризованный l<ОИСТРУКТОР /U!Я
класса
MyClass.
MyClass: :MyClass (int i)
х = i;
1.
Конструктор
-
{
это функция, которая выполняется при создании oбъeкrd.
Конструктор используется для инициализации создаваемоro объекта.
2.
Нет, конструктор не имеет nmа возвращаемоro значения.
3. Деструктор вызывается при разрушении объекта.
С++: руководство ДЛЯ начинающих
371
Чтобы передать аргумент конструктору, необходимо связать этот аргумент с
объектом при объявлении объекта. С++ поддерживает два способа реализации
MyClass
оЫ
= MyClass(lOl)i
В этом объявлении создается объект класса
рому передается значение
1О 1.
MyClass
(с имеflем оЫ), кото­
Но эта форма (в таком контексте) используется
редко, поскольку второй способ имеет более короткую запись и удобнее для ис­
пользования. Во втором способе аргумеит (или аргументы) должен следовать
за именем объекта и заключаться в круглые скобки. Например, следующая ин­
струкция эквивалентна предыдущему объявлению.
MyClass obl(lOl)i
Это C<1J\fblil распростраJJС/ШЫН Сllособ объявления парзметризованных объек­
тов. Опираясь на этот метод, Ilриnедем общий формат передачи аргументов кон­
структорам.
тип класса
имя_переменной(список_аргумеНТОВ)i
Здесь элемент список_аргументов представляет собой список разделенных
запятыми аргументов, передаваемых конструктору.
•
На заметку "f"
Формально между двумя приведенными выше формами иници­
алИ:iации существует небольшое различие, которое вы поймете
при дальнейшем чтении этой книги. Но это различие не влияет на
результаты IJЫIIолнеliИЯ лрограмм, представленнЬ1Х в этом моду­
ле, и большинства других программ.
Рассмотрим полную программу, в которой демонстрируется использование
параметризованного конструктора класса
11
MyClass.
Использование параметризованного
1;~~
такого связывания. Вот как ВЫГЛЯДИТ'первый способ.
конструктора.
#include <iostream>
using namespace stdi
class MyClass
public:
int Х;
1/ Объявляем конструктор и деструктор.
MyClass(int i); // конструктор (добaanRеи
-МуСlаss()i 11 деструктор
параиетр)
;0
::!>:
;~
:()
.()
:0
~~
372
Модуль 8. Классы и объекты
};
// Реализуем параметризованный
MyClass::MyClass(int i) (
х = i;
конструктор.
// Реализуем деструктор класса MyClass.
MyClass: : -МуСlаss () {
cout « "Разрушение объекта, в котором
«
х
«
"
int main ()
MyClass tl(S); //
MyClass t2(19);
cout «
return
tl.x «
значение
х равно
"
\п";
Передаем арry.меиты в конструктор
" " «
t2.x «
NyClass().
"\п";
О;
Резулыаты выполнения этой программы таковы.
5 19
Разрушение
объекта,
в
котором
значение
х
равно
Разрушение
объекта,
в
котором
значение
х
равно
В этой версии программы конструктор
метр, именуемый
i,
MyClass ()
19
5
определяет один пара­
который используется для инициализации переменной реа­
лиз~щии х. Таким образом, при выполнении строки кода
MyClass
значение
оЫ(5);
5 передается
параметру i, а затем присваивается персменной реализа­
ции х.
В отличие от конструкторов, дес.ТРУК1·0РЫ не могут иметь параметров. При­
чину понять нетрудно: не существует средств передачи apryMeHTOB объекту, кото­
рый разрушается. Если же у вас возникнет та редкая ситуация, когда при вызове
деструктора вашему объекту необходимо передать некоторые данные, определя­
емые только во время выполнения программы, создайте для этой цели специаль­
ную переменную. Затем непосредственно перед разрушением объекта установи­
те эту переменную равной нужному значению.
С++: PYI<OBOACTBO ДЛЯ начинающих
373
Добавление конструктора в класс Vehicle
Мы можем улучшить класс
Vehicle, добавив в него конструктор, который
при создании объеlста автоматически инициализирует поля (т.е. переменные эк­
земпляра)
passengers, fuelcap 11 трч. Обратите особое внимание на то, как
создаются объекты класса Vehicle.
//
Добавление
конструктора
в
класс
~
:.::
Ф
~
о
:s;
1i
()
()
vehicle.
о
~
#include <iostream>
using патеsрасе stdi
// Объявление класса Vehicle.
class Vehicle
public:
int passengers; // количество пассажиров
int fuelcapi // вместимость топливных резервуаров в литрах
int трч;
// расход горючего в километрах на литр
// Объявление конструктора для
Vehlcle(lnt р, int f, int т);
int range () ;
//
функция
класса
Vehicle.
вь~исления максимального
пробега
} i
// Реализация конструктора для класса Vehicle.
Vehicle::Vehicle(int р, int f, int т) ( // Этот конструктор
passengers = р;
// ииициanизирует чnекw даниых
fuelcap = f;
// passenqers, fuelcap и щpq.
mpg = т;
// Реализация функции-члена range().
int Vehicle:: range () {
return mpg * fuelcapi
int main ()
// Передаем
дaнкue
о ~анспортвок средстве
1/ конструктору Vehicle().
Vehicle minivan(7, 16, 21);
374
Модуль 8. Классы и объекты
Vehicle sportscar(2, 14, 12);
int range1, range2;
// Вычисляем расстояние, которое может
// транспортное средство после заливки
// горючего.
range1
minivan.range();
range2 = sportscar.range();
cout «
«
«
«
cout «
«
«
«
return
"Минифургон может
проехать
полного
каждое
бака
"
перевезти
minivan.passengers
"
"
пассажиров
на
километров."
"Спортивный
расстояние
«
" «
range1
"\п";
автомобиль
может
перевезти
"
sportscar.passengers
"
"
пассажира
на
километров."
расстояние
«
" «
range2
"\п";
О;
Оба объекта, mini van и sportscar, в момент создания инициализируются в
программе конструктором
Vehicle ().
Каждый объект инициализируется в со­
ответствии с тем, как заданы параметры, передаваемые конструктору. Например,
при выполнении строки
Vehicle minivan(7, 16, 21);
Vehicle () передаются значения 7, 16 и 21. В результате это­
переменных passengers, fuelcap и mpg, принадлежащие объекту
конструктору
го копии
miпivал, будут содержать значения
7,16 и 21
соответственно.
АльтернаТИВНblЙ вариант инициализации объекта
Если конструктор принимает только один параметр, можно использовать аль­
тернативный способ инициализации членов объекта. Рассмотрим следующую
программу.
//
Альтернативный sариант инициализации объекта.
#include <iostream>
С++: PYl<OBOACтaO ДЛЯ начинающих
315
........................................................................................................................................
using narnespace std:
~
class MyClass
public:
int Х;
~
о
s:
2i
U
U
// Объявляем конструктор и деструктор.
MyClass(int i); // конструктор
-МуСlаss(); // деструктор
~
};
// Реализуем лараметризованный
MyClass: :MyClass (int i) (
Х
i;
=
Реализуем деструктор класса
//
конструктор.
MyClass::-МуСlаss()
cout «
"Разрушение
«"
«Х
rnain () {
MyClass оЬ
5;
cout «
«
MyClass.
(
объекта,
в
котором значение
Х равно
"
\п";
iлt
rеturл
оЬ.х
//
Вызывается
конструктор
MyClass(5).
"\л";
О;
Здесь конструктор ДЛЯ объектов класса
MyClass
принимает только один па­
раметр. Обратите внимание на то, как в функции main
()
объявляется объект оЬ.
Для этого используется такой формат объявления:
MyClass
оЬ
=
5;
В этой форме инициализации объекта число 5 автомаТически передается napа­
метру
i
при вызове конструктора MyClass
() . Другими словами, эта инструкция
объявления обрабатывается компилятором так, как если бы она была записана
следующим образом.
myclass
оЬ
(5);
Модуль 8. I<лассы и объеl<ТЫ
376
в общем случае, если у вас есть конструктор, который принимает только ОДИН
аргумент, для инициализации объекта вы можете использовать либо вариант
оЬ (х) , либо вариант оЬ
=
х. Дело в том, что при .создании конструктора с од­
НИМ аргументом неявно создается преобразование из типа этого аргумента в тип
этого класса.
Помните, что показанный здесь альтернативный способ инициализации объек­
тов применяется только к конструкторам, которые имеют лишь один параметр.
~просы Д/IЯ текущего коНтрОllя-----____
Покажите, как для I(ласса с именем
1.
принимает однн
i n t-l1араметр
Tes t
объявить конструктор, который
соuп t?
Как можно переписать эту инструкцию?
2.
Test
оЬ
=
Test(lO);
З. Как еще МОЖНО переписать объявление из второго вопроса?·
-
Спросим у опытного программиста
Вопрос.
Можно Лll oдllH масс оUъявить ,нутри друzоzо?
Ответ.
Да, можно. В этом случае создается аложеllНЫЙ класс. Поскольку объяв­
ление класса, по сути, определяет область uидимости, вложенный класс
действителен только в рамках области ШlДимости внешнего класса. Спра'
педливости ради хочу сказать, что благодаря богатству и гибкости других
средств С++ (например, наследованию), о которых речь еще впереди, не­
обходимости в соэдании вложенных классов пракrnчески не существует.
ВАЖНОI
1:1- ВС%pQивае.мые Ф\O:lIЩl4J4
Прежде чем мы ПРОДОЛЖИМ освоение класса, сделаем l1е60льшое, но важное от­
ступление. Оно не относится конкретно к объектно-ориенrnрованному програм­
мированию, но является очень полезным средством С++, которое довольно часто
1. Test:: Test (int count)
2. Test
оЬ
З.
оЬ =
Test
(10) ;
10;
{ ...
С++: руководство ДЛЯ начинающих
377
используется в определениях классов. Речь идет о вcmpauвae.мoЙ, или noдcmaв.л.яe­
.мой, функции (in1ine function). Встраиваемой называется функция, код которой лод­
ставляется в то место строки программы, из которого она вызывается, т.е. ВЫЗО8 та­
кой функции заменяется ее кодом. Существует два способа создания встраиваемой
фуНJЩИИ. Первый состоит в использовании модификатора
Например, что­
inline.
бы создать встраиваемую функцию f, которая возвращает iпt-значение и не при­
нимает ни одного параметра, достаточно объявить ее таким образом.
inline int f ()
//
...
Модификатор
inline должен предварять
все остальные аспекты оqъявлсtll1Н
функции.
Причиной существования встраиоаемых функций является эффективность.
Ведь при каждом вызове обычной функции должна быть выполнена целая по­
следовательность инструкций, связанных с обработкой самого выэова, включа­
ющего помещение ее аргументов в стек, и с возвратом из функции. В некоторых
случаях значительное количество циклов центрального процесс ара используется
именно для выполнения этих действий. Но если функция встраивается в строку
программы, подобные системные затраты попросту отсутствуют, и общая ско­
рость выполнения программы воэрастает. Если же встраиваемая функция ока­
зывается не такой уж маленькой, общий размер программы может существенно
увеличиться. Поэтому лучше всего в качестве встраиваемых использовать толь­
ко очень маленькие функции, а те, что побольше,
-
оформлять в виде обычных.
Продемонстрируем использование встраиваемой функции на примере следу­
ющей программы.
//
Демонстрация
использования
iпliпе-функции.
#include <iostream>
using namespace std;
class cl (
int i; //
Этот
член
данных
закрыт
(private)
по
умолчанию.
риЬНс:
int get_i () ;
void put_i(int j);
;
inline int cl::get_i() //
Эта фунkЦИR встраиваете. в строху.
.:~...,
:0;
:~
:0
::s:
:j5
u
u
~
Модуль 8. l<лассы и объекты
378
return i;
inline void cl::put_i(int j) 11
i
=
и эта фувкци. встраиваeкaR.
j;
int main ()
cl 5;
s.put_i(lO) ;
cout « s.get i();
return
О;
Важно понимать, что в действительности
использование
модификатора
inl ine является запросом, а не кома1lдой, по которой компилятор сгенерирует
(inline-) код. Существуют ра:~ЛИЧllые ситуации, которые могут
встраиваемый
не позволить компилятору удовлетворить наш запрос. Вот несколько примеров.
•
Hel<OTopbIe компиляторы
не генерируют встраиваемый КОД, если соответству­
ющая функция содержит цикл, конструкцию
sw i t ch
или инструкцию go t о.
•
Чаще всего встраиваемыми не MOryT быть РСКУРСИВliые функции.
•
Как правило, встраивание "не проходит" для функций, которые содержат ста­
тические
(static) переменные.
Помните. что ограничения на использопаliие встраиваемых функций зависят
от КОliкретной реализации системы, поэтому, чтобы узнать, какие ограничения
имеют место в вашем случае, обратитесь к документаЦlЩ, прилагаемой к вашему
компилятору.
Создание встраиваемых функций
в объявлении класса
Существует еще один способ создания встраиваемой функции. Он состоит в опре­
делении кода для функции-члена класса в самом объявлении класса. Любая ФУНК­
ЦИЯ, которая определяется в объявлении класса, автоматически становится встр:ш-
С++: руководство ДЛ~ начинающих
379
ваемоЙ. В этом случае необязательно предварять ее объявление КЛЮЧ«UJЫМ словом
inline.
Например, предыдущую программу можно переписать в таком виде.
#include <iostream>
using патеэрасе std;
class cl (
int i; //
public:
Этот член
данных
закрыт
(private)
по умолчанию.
/ / АвIJ!Оll&тичесUJ ВС'1'рaиJlа8lAlе фYlUЩИИ, посIcoJIысy
//
они оnpе~enеDl ВВУ'1'рИ cвoe:t'o
x.nacca.
int get_i () { return i; }
void put_i(int j) { i = j;
int main ()
cl 5;
s.put i(10);
cout « s.get_i():
return
О:
Обратите внимание на то, как выглядит код функций, определенных "внутри"
класса
cl. Для очень не60ЛЬШИХ по объему функций такое представление кода
отражает обычный стиль языка С++. Однако можно сформатировать эти функ­
ции и таким образом.
class cl {
int i; // закрытый член
public:
// встраиваемые функции
int get_i ()
{
return i;
}
void put_i(int j)
по умолчанию
380
МОДУЛЬ 8. Классы и объекты
i "" j;
};
Небольшие функции (как представлеиныс в этом примере) обычно опреде­
ляются в объявлеиии класса. ИСПОЛЪЗ0ваиие "внутриклассовых" встраиваемых
рubliс-функций
-
обычная практика в С++-программировании, поскольку с
ИХ помощью обеспечивается доступ к'закрытым
(private)
переменным. Такие
функции называются аксессорны.ми или фУIlКЦUЯМU доступа. Успех объектно­
ориентированного программирования зачастую определяется умением управ­
лять доступом к данным посредством функций-членов. Так как 6оЛЬШИliСТВО
С++-программистов определяют функции доступа внутри своих классов. это
соглашснис тттmменяется 11 l( осталЫIЫМ примерам данной книги. То же саМI)(' я
рекомендую делать и вам.
Итак, перепишем класс
range ()
fuelcap
Vehicle,
чтобы конструктор, деструктор и функция
опрелелялись внутри I<ласса. Кроме того, сделаем поля
и
mpg
passengers,
закрытыми, а для считывания их значений создадим спеЦИ;:L7IЬ­
ные функции доступа.
//
//
Определение
в качестве
конструктора,
деструктора
и функции
range()
встраиваемых функций.
#include <iostream>
using патезрасе std;
// Объявление класса Vehicle.
class Vehicle {
// Теперь эти переменные закрыты (private).
int passengersi // количество пассажиров
int fuelcapi // вместимость топливных резервуаров в литрах
int mpgi
// расход горючего в километрах на литр
public:
// Определение встраиваемого конструктора класса Vehicle.
Vehicle(int р, int f, int т)
passengers = р;
fuelcap = f;
mpg = т;
//
Функция вычисления максимального пробега
теперь
С++: РУКОВОДСТВО МЯ начинающих
381
// .~аиааеlCаSl.
int range()
{ return
* fuelcap; }
mрч
// Функции доступа к членам класса теперь встраиваеиые.
int get_passengers() { return passengers; }
int get_fuelcap() { return fuelcap; }
int get_mpg() { return mрч; }
};
int main () {
// Передаем значения в конструктор
Vehicle minivan(7, 16, 21);
Vehicle sportscar(2, 14, 12);
класса
Vehicle.
int rangel, range2;
//
//
Вычисляем расстояние,
которое
транспортное
после
1/
горючего:
rangel
range2
cout «
«
«
«
cout «
«
«
«
return
средство
может
проехать
заливки полного
каждое
бака
minivan.range();
sportscar.range();
=
"Минифургон
может
"
перевезти
minivan.get_passengers()
"
"
пассажиров
на
километров."
расстояние
«
" «
rangel
"\n";
"Спортивный автомобиль
может
перевезти
"
sportscar.get_passengers()
"
"
пассажира
на
километров."
расстояние
«
" «
range2
"\п";
О;
Поскольку члены ДЭJПIЫХ К1Iacca
Vehicle теперь объявлены закрытыми, то
для получения количества пассажиров, которых может перевезти то или иное
транспортное средство, в функции
ную функцию
rnain () необходимо использовать аксессор­
getyassengers ().
382
Модуль 8. Классы и объекты
~npocы мя текущего контроля
--------
1. Каково назначение модификатора inline?
2. Можно ли определить встраиваемую функцию в объявлении класса?
3. Что представляет собой функция доступа?·
.............................................................._Проект
8.2.
! Q~~ue-~~pp' Возможно, вы ЗН:::lсте, что под структурой данных ПОJlимается
! ...
ш
средство оргаШlЗ,ЩIШ nallHblX. В качестве llримсра caMoii
llPO-
стой структуры данных можно привести .массив (апау), предстаВЛЯЮЩllЙ собой
линейный список, в котором поддеР)Jj:ивается проиэвольный доступ К его элемен­
там. Массивы часто используются как UфУllдамент", на который опираются та­
кие более сложные структуры, как стеки или очереди. Стек
(stack) --:
это список,
доступ к элементам которого возможен только по принципу "первым прибыл,
последним обслужен"
(first in Last Out - FILO).
Очередь (чиеие)
-
это список,
доступ к элементам которого возможен толыю по ПРИllЦИПУ Uпервым прибыл,
первым обслужен"
(Ftrst in first Out - FIFO).
Вероятно, очередь не нуждается
в примерах: вспомните, как вам приходилось стоять к какому-нибудь окоше'-{ку
кассы. А стек можно представить в виде множества тарелок, поставленных О.1,на
на друГУIО (к первой, или самой нижней, можно добраться только в самом конце,
когда будут сняты все верхние).
В стеках и очередях интересно то, что в них объединяются как средства хра­
неиня информации, так и функции доступа к этой информации. Таким образом,
стеки и очереди представляют собой специаЛЫlые механизмы обслуживаllИЯ
данных, в которых хранение и доступ к ним обеспечивается самой структурой,
а не программой, в которой они используются.
Как правило, очереди поддерживают две основные операции: чтение и запись.
При каждой операции записи новый элсмент помещается в консц очереди, а при
каждой операции чтения считывается следующий элемент с начала очереди. При
1.
Модификатор
inline
обеспечивает замену инструкции выова функции В про­
грамме кодом этой функции.
2.
3.
да, встраиваемую функцию можно определить В объявлении класса.
Функция ДОС1)'ПЗ - ЭТО, как правило, короткая ФУНКЦИЯ, которая считывает значе­
ние закрытого члена дaнныx класса или устанавливает его.
С++: руководство дl\Я начинающих
383
этом считанный элемент снова сtlИтать нельзя. Очередь может быть заполненной
"до отказа", если для приема нового элемента нет свободного места. Кроме того,
очередь может быть пустой, если из нее удалены (считаны) все элементы.
Существует два основных типа очередей: циклические инециклические.
В циклической очереди при удалении очередного элемента повторно использу­
ется "ячейка" базового массива, на котором построена эта очередь. В нецикличе­
ской повторного использования элементов массива не предусмотрено, и потому
такая очередь в конце концов исчерпывается. В нашем проекте с целью упроще­
ния задачи рассматривается нециклическая очередь, но при не60льших дополни­
тельных усилиях ее легко превратить в циклическую.
Последовательность действий
1.
Создайте файл с именем
Queue. срр.
2.
В качестве фуtщамента для нашей очереди МbI берем массив (хотя возмож­
ны и другие варианты). Доступ к этому массиву реализуется с помощью двух
индексов: ИНдекса записи (рut-индекса) и индекса считывания (gеt-индекса).
ИНдекс записи определяет, где будет храниться следующий элемент данных,
а индекс считывания
-
с какой позиции будет считан следующий элемент
данных. Следует помнить о невоэможности считывания одного и того же эле­
мента дважды. Хотя очередь, кОО'орую мы создаем в проекте, предназначена
для хранения символов, аналогичную логику можно применить для хранения
объектов любого ТIJПа. Итак, начнем с создания класса
const int maxQsize = 100; //
//
//
class Queue {
char q[maxQsize)i
Queue.
Максимально возможное число
элементов,
храниться
//
Массив,
//
Реальный
на
которое может
в очереди.
котором строится
очередь.
int size;
(максимальный)
размер
очереди.
int putloc, getloc; // putКонстантная
(const)
переменная
и gеt-индексы
maxQsize
определяет размер самой
большой очереди, которую можно создать. Реальный размер очереди хранится В поле
s i ze.
З. Конструктор для класса
его код.
Queue
создает очередь заданного размера. Вот
:~
~ffi
:~
:0
::s:
:2j
:u
:u
:0
~:;2
МОДУЛЬ 8. Классы и объеКТbl
384
... ; .............................................................................................................................
//
Конструктор очереди
заданного размера.
Queue(int len) (
// Длина очереди должна быть положительной
// меньше заданного максимума.
if(len > rnaxQsize) 1еп = maxQsize;
else if(len <= О) len ; 1;
size
len;
putloc = get10c
и быть
= О;
Еыи указанный размер очереди превышает значение maxQsize, то ео.ща­
етея ОЧСJ1СДТ> макси:-.rалыюго размера. Если указанный размер очереди р:шен
НУЛЮ IIJlI1 IJыражСII отрицательным ЧI1CJЮI\[, то создается очередь ДJIИIЮЙ D
1 элемент.
Размер очереди хранится в поле
size.
ИI~декеы считывания и
записи первоначалыю устанавливаются равными нулю.
Создаем функцию записи элементов в очередь, ри t
4.
//
Функция внесения
элемента
void put(char ch)
if(putloc == size)
cout « " -- Очередь
returni
putlOC++i
q[putloc]
=
в
().
очередь.
~аполнена.\п";
ch;
Функция сначала проверяет возможность очереди принять новый элемент.
Если значение
putloc
равно максимальному размеру очереди, значит, в
ней больше нет места для приема новых элементов. В противном случае
переменная
putloc инкрементируется,
и ее новое значение определит по­
зицию для хранения нового элемента. Таким образом, переменная
putloc
всегда содержит Иliдекс элемента, запомненного в очереди последним.
Чтобы прочитать элемент из очереди, необходимо создать функцию считы­
5.
вания,
//
I
get ().
Функция
считывания
символа из
char get ()
if(getloc == putloc)
cout « " -- Очередь
очереди.
пуста.\п";
С++: PYI<OBOACTBO для начинающих
return
385
О;
getloc++;
return q[getloc);
Обраnпе внимание на то, что в этой функции сначала проверяется, не пуста
ли очередь. Очередь считается пустой, если индексы
getloc
И
putloc
ука­
зывают на один и тот же элемент. (Именно поэтому в конструкторе класса
Queue
оба индекса
getloc и putloc инициализируются нулевым значе­
getloc инкрементируется, и фУI{КЦИЯ возвращает
элемент. Таким образом, I1сременная getlac всегда содержит
нием.) Затем индекс
слеДУЮТЦllfj
1IIIДСКС элемента, считаl1НОГО из очеrеди 1IОСЛСДlШМ.
6.
Вот полный текст программы
Queue . срр.
/*
ПроеRТ
Класс
8.2.
очереди
для
хранения
символов.
*/
#include <iostream>
using namespace std;
const int maxQsize
100; //
//
//
Максимально
элементов,
храниться
возможное
число
которое может
в
очереди.
class Queue {
char q[maxQsize];
// Массив, на котором строится
// очередь.
int size;
// Реальный (максимальный) размер
// очереди.
int putloc, getloc; // put- и gеt-индексы
public:
// KOHCTpYRTOP очереди заданного размера.
(int len) {
// Длина оч~реди должна быть положительной
// меньше заданного маRсимума.
if(len > rnaxQsize) len = rnaxQsize;
Оиеие
и быть
386
МОДУЛЬ 8. Классы и объекты
................................................................................................................................
else if(len <=
О)
size
len;
putloc = getloc
//
len
1;
О;
ФУНКЦИЯ внесения элемента в очередь.
void put(char ch) {
if (putloc == size) (
cout « " -- очередь
return;
putloc++;
q [putloc]
//
Функция
заполнена.\п";
ch;
считывания
символа из
char get ()
if(getloc == putloc) {
cout « " -- Очередь
return О;
очереди.
пуста.\п";
getloc++;
return q[getloc);
};
//
Демонстрация использования класса
Queue.
int main () (
Queue bigQ(lOO);
Queue smal1Q(4);
char ch;
int i;
cout «
"Использование
очереди
bigQ
для
аЛфавита.\п";
//
Помещаем ряд чисел в очередь
bigQ.
хранения
С++: РУКОВОДСТВО для начинающих
387
for(i=O; i < 26; i++)
bigQ.put('A' + i);
//
Считываем и отображаем элементы из очереди
bigQ.
cout « "Содержимое очереди bigQ: ";
for(i=O; i < 26; i++)
ch = bigQ.get();
if(ch != О) cout « ch;
cout «
cout
"\n\n";
«
"Использование
очереди
srnallQ
для
генерирования ОШИБОК.\П";
/1
11
Теперь
используем
очередь
smallQ
для
генерирования
ошибок.
for(i=O; i
cout «
< 5; i++)
"Попытка
сохранить
(char)
"
«
(' Z' - i ) ;
smallQ.put('Z' - i);
cout «
cout
«
"\п";
"\п";
/1
Генерирование ошибок при считывании элементов из
11
очереди
smallQ.
cout « "Содержимое очереди smallQ: ";
for(i=O; i < 5; i++) {
ch = smallQ.get();
if(ch !=
cout
«
О)
"\n";
cout «
ch;
МОДУЛЬ 8. Классы и объекты
388
7.
Вот как выглядят результаты, сгенерированные этой программоЙ.
Использование
Содержимое
Использование
очереди
Попытка
сохранить
Z
Попытка
сохранить
У
Попытка
сохранить
Х
Попытка
сохранить
W
Попытка
сохранить
V
Содержимое
8.
bigQ для хранения алфавита.
bigQ: ABCDEFGHIJКLМNOPQRSTUVWXYZ
очереди
очереди
очереди
--
smallQ
для
очередь
генерирования
ошибск.
заполнена.
smallQ: ZYXW --
очередь
Самостоятельно попытайтесь модифицировать класс
мог хранить объекты других типов, например,
пуста.
Queue TaI<,
int или double.
чтобы он
ВАЖНОI
~!АмаССИ8ЬI 06ьеgD.8
Массивы объектов можно создавать точно так же, как создаются массивы зна­
чений других типов. Например, в следующей программе создается маССllВ для
хранения объеI<ТОВ типа
MyClass, а доступ
!(
объектам, которые являются эле­
ментами этого массива, осуществляется с по.\ющью обычной процедуры индек­
сирования массива.
//
Создание массива
объектов.
#include <iostream>
using namespace std;
class MyClass
int Х;
public:
void set_x(int i) { х = i;
int get_x() { return Х;
};
int main ()
С++: руководство ДЛr;! начинающих
MyClass obs[4]; //
int i;
389
~ Создание массива объектов.
for(i=O; i < 4; i++)
obs[i] .set_x(i);
for(i=O; i < 4; i++)
cout « "obs[" « i « "] .get_x(): " «
obs[i] .get_x() « "\n";
return
О;
Эта программа генерирует таЮ.Jе результаты.
.get_x(): О
obs[l] .get_x(): 1
obs[2] .get_x(): 2
оЬs(З] .get х(): 3
оЬs[О]
ВАЖНОI
1:1:"'~ЫL1ЦI4.аАk1~ацL4SLМaCC14ВОВ
объектов
Если класс включает llараметризоваШIЫЙ конструктор, то массив объектов
TaKol'O класса можно инициализировать, Например, в следующей программе ис­
полыуется параметризооанный класс
MyClass
и инициализируемый массив
оЬs объектов этого класса,
//
Инициализация
массива
#include <iostream>
using namespace std;
class MyClass
int Х;
public:
MyClass (int i) ( Х = i;
int get_x() { return Х;
};
объектов
класса.
Модуль 8. Классы и обьеl<ТЫ
390
int main ()
{
{ -1, -2, -3, -'1 };
MyClass obs[4]
int i;
for(i=O; i < 4; i++)
cout « "obs[" « i « "] .get_x(): " «
obs[i] .get_x() « "\п";
return
О;
в этом примере конструктору класса
до
-4.
MyClass
передаются значения от
-1
При выполнении эта прorрамма отображает следующие результаты.
.get_x():
.get_x():
obs[2] .get_x():
оЬs[З] .get_x():
оЬэ[О]
-1
оЬэ[l]
-2
-3
-4
В действительности синтаксис инициализации массива, выраженный строкой
MyClass
оЬэ[4]
= { -1, -2, -3, -4 \;,
представляет собой сокращенный вариант следующеro (более длинного) формата:
MyClass obs[4]
=
(
MyClass(-l), MyClass(-2),
// Еще ОДИ~
MyClass(-З), MyClass(-4)
}; // слоСо(5
// инициализации массива.
как разъяснялось выше, если конструктор принимает только один аргумент,
выполняется неявное преобразование из типа аргумеита в тип класса. При ис­
пользопаНIШ более ДЛИНlюгq формата ИНlЩИ<L71Изации массива конструктор вы­
зывается напрямую.
При инициализации массива объектов, конструкторы которых принимают
несколько аргументов, необходимо использовать более длинный формат ИНlIЦИ­
алнзации. Рассмотрим пример.
#include <iostream>
using namespace std;
class MyClass
int х, у;
public:
MyClass(int i, int j)
int get_x() { return
(
х;
х
=
i;
У
j;
С++: РУКОВОДСТВО ДЛJ\ начинающих 391
..................................................................................................................................
int get_y()
{ return
у;
}
};
~
int main ()
~
о
:s:
u
u
MyClass obs[4] [2] = (
/ / \' Дm«ннЧЙ" формаТ
MyClass(1, 2), MyClass(3, 4),
MyClass(5, 6), MyClass(7, 8),
/ / ИlUЩИ8JlИзации.
MyClass(9, 10), MyClass(11, 12),
MyClass(13, 14), MyClass(15, 16)
1i
~
};
int i;
for(i=O; i < 4 ; i++) {
cout « obs(i] (О] .get_x() «
cout « obs(i] [О] .get_y() «
cout « obs(i] [1] .get_x() «
cout « obs(iJ [1] .get_y() «
return
,
I
1.
11
\п";
I
1.
,
·'\n";
О;
в этом прИМСрС коt1струю'ор класса
MyC1ass
принимает два
apryMcHTa.
В функ­
ции main () объявляется If инициалиЗИРУется массив obs пугем непосредственных
ВЫЗОВОВ конструктора
MyClass (),
Ишщиализируя массивы, можно всегда исполь­
:ювать ДЛ1шный форм~т инициализации, даже если объект приюrмает ТОЛЬКО один
apryMellT (КОРОТКая форма
просто более удобна для применения). Нетрудно прове­
рить, что при выполнении эта программа aroбражает такие результаты.
1 2
3 4
5 6
7 8
9 10
11 12
13 14
15 16
392
МОДУЛЬ В.l<лассы и объекты
BAII
1:1% Vka-1атеАI4 на обыU<;ты
Доступ к объекту можно получить напрямую (как во всех предыдущих примерах)
или через указатель на этот объект. Чтобы получить доступ к отдельному элементу
(члену) объекта с помощью указателя tш этот объект, необходимо исполъзовать опе­
ратор "стрелка" (он состоит из знака "минус" и знака "больше":
"->").
Чтобы объявить указатель на объект, используется тот же синтакСИС,lсак и в
случае объявления указателей на значения других типов. В следующей npoграм­
ме создается простой класс Р _ example, определяется объект этого класса оЬ и
объявляется указатель на объект тина Р _ example с именем р. В этом при мере
показано, как можно напрямую получить доступ к объекту оЬ и как использовать
для этого указатель (В этом случае мы имеем дело с l<Освенным доступом).
//
Простой пример использования указателя
на объект.
#include <iostream>
using namespace std;
class P_example
int num;
public:
void set_num<int val) {пит = val;}
void show_num() { cout « num « "\n";
};
int main ()
P_example
оЬ,
*р;
ob.set_num(l); //
оЬ. show_num () ;
р =
&оЬ;
/1
О;
Объявляем объект и
указатель
Получаем
на него.
прямой доступ
к
Присваиваем указателю р адрес
p->set_num(20); 11
p->show_num();
11
return
//
//
объекту
оЬ.
объекта оЬ.
Вызываем функции с использованием
указателя
на
объект
оЬ.
С++: руководство ДМI начинающих
393
Обратите внимание на то, что адрес объекта оЬ получается путем использова­
ния оператора" &", что соответствует получению адреса для переменных любого
другого типа.
Как вы знаете, при инкрсментации или декрементации указателя он инкре­
ментируется или декрементируется так, чтобы всегда указывать lIа следующий
или предыдущий элемент базового типа. То же самое происходит и при иtlкре­
ментации или декрементации указателя на объект: он будет указывать на следу­
ющий или предыдущий объект. Чтобы проиллюстрировать этот механизм, мо­
дифицируем предыдущую программу. Теперь вместо одного объекта оЬ объявим
двухэлементный массив оЬ типа Р _ example. Обратите внимание на то, как ин­
крементируется и декрементируется указатель р для доступа к двум элементам
этого массива.
//
//
Инкрементация
на
и декрементация
указателя
объект.
#include <iostream>
латеsрасе std;
usiлg
class P_example
int лum;
public:
void sеt_пит(iлt val) {лиm = val;}
void show_num() { cout « пит « "\л";
};
iлt
main ()
P_example
оЬ[2],
*р;
оЬ[О] .sеt_лurn(lО);
ob[l)
р
=
1/
прямой доступ
к объектам
.sеt_лиm(20);
&оЬ[О];
1/
Получаем указатель
p->show_num () ; 11
//
Отображаем значение
//
p->show_num () ; 11
/1
Пере ходим к
р++;
на первый элемент.
элемента
оЬ[О]
с помощью указателя.
Отображаем
следующему объекту.
значение
с помощью указателя.
элемента
ob[l]
394
МОДУЛЬ 8. Классы и объекты
р--;
// Возвращаемся к предыдущему объекту.
p->show_num ();
/ / Снова отображаем значение
// элемента оЬ[О] .
О;
return
Вот как выглядят результаты выполнения :JТОЙ программы.
10
20
10
Как будет ПОJ«lЭШIO ниже в этоil книге, ука:JaТСЛИ на объекты играют главную
роль 8 реализации одного из важнейших прющипов С++: полиморфизма.
~ПРОСЫ ДflЯ текущего КОНТРОЛЯ
1.
2.
Можно ли массиву объектов I1РИСВОИТ). начальные зизчения?
Какой оператор используется для доступа к члену объекта. если опреде­
лен указатель на этот объект?*
.~
- - -_ _ _ __
__ ...
~~~:-.J.~:;г.i
;;iiJor._~.,
fC"
АР
.-
• .lеСТ..дАЯжСомок.он:z:p.oЛЯ.QQ.М.ОAVАю.8
1.
Каково различие между классом и объектом?
2.
Какое ключевое слово используется для объявления класса?
З. Собственную копию чего имеет каждый объект?
4.
Покажите, как объявить класс
переменные
coun t
Test,
который содержит две закрытые
int-
и тах.
5.
Какое имя носит конструктор? Как называется деструктор?
6.
Дано следующее объявление класса.
class Sample
int i;
public:
1.
Да. массиву объектов можно присвоить начальные значения.
2.
При доступе к члену объекта через указатель используется оператор Мстрелка".
С++: PYI<OBOACTBO ДМ! начинающих
Sample (int
//
х)
{i
395
х;
...
};
Покажите, l<aK объявить объект класса Sarnple, который инициализирует
перемен"ую i значением
7.
10.
Какая оптимизация происходит автоматически при определении функции­
члена в объявлении класса?
8.
Создайте класс
Triangle,
в котором для хранения длины основания и
высоты прямоугольного треугольника исполъзуются две переменные эк­
земпляра. Для установки этих значений включите в класс конструктор.
Определите две функции. Первая,
IЮТСIlУЗЫ, а вторая,
9.
area () , -
h ypot ()
,должна возвращать длину ПI­
его площадь.
Расширьте класс Не 1 р так, чтобы он хранил целочислеНIIыii идентифика­
ЦИОШlblЙ номер
(ID) ДЛЯ
идентификации каждого пользователя зтого клас­
са. Обеспечьте отображение этого номера при разрушении объекта спра­
вочной системы. Создайте функцию getID (), которая будет возвращать
Ш-номер.
На заметку
•
Ответы на эти вопросы можно найти на Wcb-страшще данной
'f- КНИПI по адресу:
http://www . osborne.
сот.
МОДУЛЬ 9
Оl~ассахподробнее
9.1.
9.2.
9.3.
Перегрузка конструкторов
9.4.
Возвращение объектов функциями
9.5.
9.6.
9.7.
9.8.
9.9.
9.10.
9.11.
Присваивание объектов
Передача объеI<ТОВ функциям
Создание и использование конструктора копии
ФУНКIlИИ-"друзья"
Структуры и объединения
Ключевое слово
this
Персгрузка операторов
Перегрузка операторов с использованием ФУИКIlИЙ-ЧЛСIlОВ
Перегрузка операторов с использованием фУНКЦИЙ-НС ЧJlСНОFl класса
398 .МОДУЛЬ 9. О классах подробнее
..................................................................................................................................
в этом модуле !\[Ь/ продолжим рассмотрение классов, начатое о модуле
8.
Здесь вы узнаете о перегрузке конструкторов, а также о возможности передачи
и возвращения объектов функциями. Вы познакомитесь с конструктором спе­
циального ТИ.па, именуемого конструкторо,М коn.ии, который используется, когда
ВО3ЩIкает необходимость в создании копии 06ъекта. Кроме того. здесь рассма­
тривается применение "дружественных" функций, структур, объединений и клю­
чевого слова
средств С++
this. Зaflершает этот модуль описание одного И3 самых интересных
- переГРУЗЮi операторов.
ВАЖНОI
tII!IJ]"ер.е.tPVа3ка.кOJ:iC.Т~к"Т..Q,рав
Несмотря на Dы!oлнениеe конструкторами уникальных деiiствий, они не СИЛЬ­
но отличаются от функций других типов и также могут подвергаться переГРУ:llre.
Чтобы перегрузить конструктор lU1acca. достаточно объявить его во всех нужных
форматах и определить )(аждое действие, связанное с соответствующим фОР\1а­
том. Например, в следующей программе определяются три конструктора.
//
Пере грузка
конструктора.
#include <iostream>
using namespace stdi
class Sample
public:
int Х;
int У;
//
Переrpу~ха конструктора:
// конструктор
Sample () { х =
по умолчанию
у
== О;
}
// конструктор с одним параметром
Sample(int i) { ~ = у = i; }
// конструктор с ДВУМЯ
Sarnple.(int i, int j) {
};
параметрами
х
=
i;
У
=
j;
С++: РУКОВОДСТВО ДЛЯ начинающих
int main ()
Sample t;
//
Sample t1(5);
//
Sample t2(9, 10); //
используем вариант
используем вариант
умолчанию
Sample(int)
Sample(int, int)
"t.x: " « t.x « " , t.y: " « t.y « "\n";
"tl.x: " « tl.x « " tl.y: " « tl.y « "'n";
"t2.x: " « t2.x « " , t2.y: " « t2.y « "\n"i
cout «
cout «
cout «
return
вызываем конструктор по
399
t.x: О, t.y: О
tl.x: 5, t1.y: 5
t2.x: 9, t2.y: 10
В этоii программе создается три КОНСТРУlCтора. Первый
-
- конструктор без па·
иtIJщиализирует члены данных х и у нулевым» значениями. Этот
КОIIСТРУКТОР играет роль конструктора по умолчанию, который (в случае его
ОТСУТСТОIIЯ) был бы пре!l<1ставлеll средстоами С++ автоматически. Второй кон­
cтpYJaop ГJflJlШJмает ОДИН ГJapa~I(~Tp, значение ((оторого прнсваl1вается обоим
'1ЛСJJЮ1 даНIIЫХ х п у.
TpCTJJii
КОIIСТРУКТОР принимает два параметра, которые IIС­
пользуются для индивидуальной инициа.JJИЗации переменных х и у.
Ilерегрузка конструкторов полезна по нескольким причинам. Во-первых,
она делает J\лаССh1, определяемые программистом, более гибкими, позволяя соз­
давать объекты различными способами. Во-вторых, перегрузка конструкторов
предоставляет удоБС'гва ДЛЯ пользователя класса, поскольку он может выбрать
самый подходящий способ построения объектов для каждой конкретной задачи.
В-третьих, определение конструктора как без napa.\ofeТPOB, так и параметри30Ван-
1101'0,
ф
~
~
с:
)(
О
U
U
~
О
О;
Результаты [)ыполнения этоii программы TaкOBbI.
раметров
ф
позволяет создавать как шшциализироваllные объекты. так и неиющиали­
зированные.
ВАЖНОI
11;- "РИСВQивомие объектов
Если два объекта имеют одинаковый тип (т.е. оба они
-
объекты одного клас­
са), то один объект можно присвоить друтому. Для присваивания недостаточно,
чтобы два класса бblЛИ физически подоБны; должны совпадать и имена классов,
объекты которых участвуют в операции присваивания. Если один объект присва-
Модуль 9. О классах подробнее
400
ивается другому, то по умолч;шию данные первого объекта поразрядtlO копиру­
ются во второй (т.е. второму объекту присваивается поразрядная копия данных
первого объекта). Таким образом, после выполнения присваивания IЮ умолча­
нию объекты будут идентичными, но отдельными. Присваивание объектов де­
монстрируется в следующей программе.
Демонстрация
//
присваивания
объектов.
#include <iostrearn>
using narnespace std;
class Test {
iлt
а,
Ь;
public:
void setab (int i, int j) ( а
void showab ()
cout « "а равно" « а «
cout « "Ь равно 11 « Ь «
i,
Ь
j;
'\п';
'\п';
};
iлt
rnain ()
Test
оЬI,
оЬ2;
obl.setab(lO, 20);
ob2.setab(0, О);
cout « "Объект оЬ!
оЬ! . showab () ;
cout « "Объект оЬ2
ob2.showab();
cout «
'\п';
оЬ2
=
оЫ;
//
до
присваивания:
\п";
до
присваивания:
\п";
Присваиааеи об~ек~ оЬ!
cout « "Объект
оЫ . showab () ;
cout « "Объект
ob2.showab();
cout « '\п';
об~к~у оЬ2.
оЬ!
после присваивания:
\п";
оЬ2
после присваивания:
\п";
С++: PYI<OBOACтeO ДЛ9t начинающих
obl.setab(-1, -1); //
cout « "Объект
оЫ . showab () ;
cout « "Объект
ob2.showab();
return
Из~еняем объект
401
оЫ.
\п";
оЫ
после
изменения
объекта
оЫ:
оЬ2
после
изменения
объекта
obl: \n";
О;
При выполнении эта программа генерирует такие результаты.
obl
10
равно 20
Объект
до
присваивания:
до
присваивания:
а равно
Ь
Объект
оЬ2
а равно
О
Ь
О
равно
obl
равно 10
равно 20
Объект
а
Ь
Объект
а
равно
Ь
равно
Объект
а
равно
Ь
равно
Объект
а
равно
Ь
равно
оЬ2
после
присваивания:
после
присваивания:
10
20
оЫ
после изменения
объекта
obl:
после
объекта
obl:
-1
-1
оЬ2
изменения
10
20
как подтверждают результаты выполнения этой программы' после присваи­
вавия одного объекта другому эти два объекта будут содержать одинаковые зна­
чения. Тем не менее эти объекты совершенно независимы. Поэтому последующая
модификация данных одного объекта не оказывает никакого влияния на другой.
Одиако при присваивании объектов возможны побочные эффеКТЫ. Например,
если объект А содержит указатель на некоторый другой объект В, то после копи­
рования объекта А его копия также будет содержать поле, которое указывает на
402
Модуль 9. О I<лассах подробнее
объект В. В этом случае НЗ~1енеtlие объекта В п()влияет на оба объекта. В такой
ситуации, как будет по!<а3ШЮ ниже
D
этом модуле, необходимо заблокировать
создание поразрядной КОПIIИ путем опред~лення "собственного" оператора при­
сваивания ДЛЯ данного класса.
Объект можно передать функции точно так же, как значение любого друго­
го типа данных. Объекты передаются функция~[ путем использования обычного
С++-соглзшения о передаче парщtетров по значению. Тюшм образом, ФУНКЦ(IИ
передастся не сам объект, а его кт/.llЯ. Следователыю, измеНСIIИЯ, внссенные в
объект при вып()лнении фУI\IЩИИ, не оказываю'l" Нlшакого ВЛИЯНИЯ на объект, IIС­
пользуемый в качестве аргумента для функции. 3тот механизм демонстрируется
в следующей программе.
//
Передача
объекта
функции.
#include <iostream>
using паmезрасе std;
class MyClass
int va).;
public:
MyClass (int i)
val = i;
(
int qetval () ( return val;
void setval(int i) ( val = i;
};
оЬ)
void display(MyClass
cout
«
ob.getval()
void change (MyClass
«
·оЬ)
//
//
//
Фувхциа
display()
привимает
в качестве параметра объект
кnacca МyClass.
'\п';
//
ФуВКЦИfl
chanqe О
также npинии.ает
С++: PYI<OBOACтe0 ДМ1 начинающих
//
403
в качестве параметра об~~
/ / JCJIacca МуСlавв.
Это никак
ob.setval(100): //
cout « "Значение
display(ob)j
не влияет
на
в
change(): ";
объекта
оЬ
функции
объекта
а
до
объекта
а
после
аргумент.
int main ()
MyClass
а(10):
cout « "Значение
display (а) ;
" ,.
change(a);
cout « "Значение
вызова
функции
аызова
change(): ":
функции сhалgе():
display(a);
return
О:
При ВЫПОЛНСJШИ эта программа генерирует следующие результаты.
Значение
объекта
Значение
объекта
Значение
объекта
change(): 10
оЬ в функции change(): 100
а после вызова функции change(): 10
а
до
вызова
функции
Судя по результатам, изменение значения оЬ в функции
вает влияния на объект а в функции
change ()
не оказы­
main ().
Конструкторы, деструкторы и передача объектов
1Iесмотря на то что передача ФушщИям простых объектов ~ качестве арryмеи­
- довольно несложная процедура, при этом мосут происходить непредвиден­
тов
ные события, имеющие ОТllошение к конструкторам и деструкторам. Чтобы разо·
браться в этом, рассмотрим следующую программу.
/1
Конструкторы,
деструкторы и
#include <iostream>
передача объектов.
Модуль 9. О классах подробнее
404
using namespace std;
class MyClass
int val;
public:
MyClass (int i) {
va1 = i;
cout « "Внутри
констружтора.\п";
~MyC1ass()
{ cout «
int getval()
"Разрушение объекта.\п";
//
//
Обратите внимание
на это сообщение.
{ return va1; }
};
void disp1ay(MyC1ass
cout «
оЬ)
ob.getva1() «
'\п';
int main ()
tv!yC1ass
а
(10) ;
cout « "До вызова функции disp1ay () . \п";
disp1ay(a);
cout « "После выполнения функции disp1ay()
return
.\п";
О;
Эта проrpамма генерирует ДОВОЛЬНО н~ржиданные резу.ilbтаты.
Внутри
До
конструктора.
вызова
функции
disp1ay() .
10
Разрушение
После
объекта.
выполнения
функции
Разрушение объекта.
disp1ay() .
С++: PYI<OBOACTBO д,л~ начинающих
405
Как видите, здесь выполняется одно обращение к функции конструктора (при
создании объекта а), но почему-то два обращения к функции деструктора. Да­
ф
вайте разбираться, в чем туг дело.
ф
При передаче объекта функции создается его копия (и эта КОllИЯ становится
параметром в функции). Создание копии означает "рождение" нового объекта.
Когда выполнение функции завершается, копия аргумента (Т.е. параметр ) разру­
шается. Здесь возникает сразу два вопроса. Во-первых, вызьшается ли конструк­
тор объекта при создании копии? Во-вторых, вызывается ли деструктор объекта
при разрушении копии? Ответы могут удивить вас.
Когда ПрИ вызове ФУНlЩИИ создается копия аргумента, обычный IЮНСТРУКТОР
не вызывается. Вместо этого выэывается K01lcmpYKmop коnии объекта. Конструк­
тор копии определяет, как должна быть создана копия объекта. (Kal( создать кон­
структор копии, будет покаЗaIЮ ниже в этой главе.) Но сели в классе явно не
определен конструктор копии, С++ предоставляет его по умолчанию. КОIlСТРУК­
тор копии по умолчанию создает побитовую (т.е. идентичную) копию объекта.
Поскольку для инициализации некоторых аспектов объекта используется обыч­
ный конструктор, он не должен вызываться для создания копии уже существу­
ющего объекта. ТaI<ОЙ вызов изменил бы его содержимое. При передаче объекта
функции имеет смысл использовать текущее состояние объекта, а не начальное.
Но когда функция завершается и разрушается копия объекта, используемая
в качестве аргумента, вызывается lteCTpYICTOP этого обьекта. Необходимость вы­
зовз деструктора связана с выходом объекта из области видимости. Именно по­
этому предыдущая программа имела два обращения к деструктору. Первое про­
изошло при lJыходе пз области ВIJДНМОСТИ параметра функции
рое
-
display () ,а вто­
при разрушении объекта а в функции main () по завершении программЪ1.
И так, когда объект передается функции в качестве аРl·умента. обычный кон­
структор не вызывается. Вместо него вызывается конструктор копии, который
по умолчанию создает побитовую (идентичную) копию этого объекта. Но когда
эта копия разрушается (обычно при выходе за пределы области видимости по за­
вершении функции), обязательно вызывается деструктор.
Передача объектов по ссылке
Объект можно передать функции не только по значению, но и по ссылке.
В этом случае функции передается ссылка, и функция будет непосредственно
обрабатывать объект. используемый в качестве аргумента. Это значит, что изме­
нения, вносимые в параметр, автоматически повлияют на аргумент. OltHaI<O пере­
дача объекта по ссылке, применима не во всех ситуациях. Если же это возможно,
мы выигрываем в двух аспектах. Во-первых, поскольку функции передается не
весь объект, а только его адрес, то можно говорить о более быстрой и эффектив-
::t
i
t:
~
U
U
~
О
406
МОАуЛЬ9.01~ассаХПОАРОбнее
ной передаче объекта по ссылке по сравнению с передачей объекта по значеНlIЮ.
Во-вторых, при передаче объекта по ссылке никакие новые объекты не создают­
ся, а, зна'lИТ, не тратится время и ресурсы системы на построение и разрушение
временного 06ъекта.
Теперь рассмотрим пример передачи объекта по ссылке.
//
Конструкторы,
деструкторы и передача
объектов
по
ссылке.
finclude <iostream>
using namespace std;
class MyClass
int val;
public:
MyClass (int i) {
val = i;
cout « "Внутри
конструктора.\п";
() { cout « "Разрушение
int getval () ( return val; )
void setval (int i) ( val = i; )
-МуСlаss
(,бъекта. \п";
)
};
&оЬ)
void display(MyClass
cout «
//
Здеqь объект ~иnа МyClass
//
передаеТСR по ссыпке.
ob.getval()
void change(MyClass
&оЬ)
//
//
ob.setval(100);
int main ()
MyClass a(lO);
Здесь
об~ект типа МYClass
передаетCR по ссыпке.
С++: РУКОВОДСТВО M~ начинающих
cout « "До вызова функции display() .\п";
display(a);
cout « "После возвращения из функции display()
407
:ф
.\п";
:ф
:!:
:10
·0
change(a) ;
cout « "После
display(a);
вызова
функции
change()
i~
:0
.\п";
:С::
:~
:о
:о
:0
return
~;2
О;
:0
Результаты выполнения этой программы таковы.
Внутри
До
конструктора.
8ызова
функции
display() .
10
После
возвращения
После
вызова
из
ФУНКЦИИ
display () ..
change().
функции
100
Разрушение
объекта.
В этой программе обе функции
display () и change () используют ссылоч­
ные параметры. ТаIШМ образом, каждой из этих функций передается не копия
арr)'Jllепта, а ее адрес, и ФУIIКЦИЯ 06раб;пывает сам apryмeHT. Например, когда
llЬШОЛНЯСТСЯ функция
crlangc (),
все изменения, вносимые ею D параметр оЬ,
непосредственно отражаются lIа объекте а, существующем в функции
main ().
Обратите также внимание на то, что :-\десь создастся и разрушается только один
объект а. В этой программе обошлось без создания временных объектов.
Потенциальные прОблемы при передаче параметров
Несмотря на то что в случае, когда объекты передаются функциям "по значе­
нию" (т.е. посредством обычного С++-механизма передачи параметров, который
теоретически эащищает аргумент и изолирует его от прииимаемого параметра),
здесь все-таки возможен побочный эффект или даже угроза ДЛЯ "жизни" объекта,
используемого в качестве аргумента. Например, сели объект, используемый как
apryMeHT, требует динамического выделения памяти и освобождает эту память
при разрушении, его локальная копия при вызове деструктора освободит ту же
самую область памяти, которая была выделена оригинальному объекту. И этот
факт становится уже целой проблемой, поскольку оригинальный объект все еще
использует эту (уже освобождениую) область памяти. Описанная ситуация дела­
ет исходный объект "ущербным" и, по сути, непригодным ДЛЯ использования.
408
МОАуль9.01~ассахподробнее
Одно из решений описанной проблемы
-
передача объекта по ссылке, кото­
рая была продемонстрирована в предыдущем разделе В этом случае никакие ко­
пии объектов не создаются, и, следовательно, ничего не разрушается при выходе
из функции. Как отмечал ось выше, передача по ссылке ускоряет вызов функции,
поскольку функции при этом передается только адрес объекта. Однако передача
объектов по ссылке применима далеко не ВО всех случаях. К счастью, есть более
общее решение: можно создать собственную версию конструктора копии. Это по­
зволит точно определить, как именно следует создавать копию объекта, чтобы
избежать описанных выше проблем. Но прежде чем мы займемся конструктором
I<ОПИИ, имеет смысл рассмотреть еще одну ситуацию, в обработке которой мы
также можем выиграть от создания KOJICTpYKTOra J<ОПИИ.
Если объекты можно передавать функциям, то "с таким же успехом" функции
могут возвращать объекты. Чтобы функция могла вернуть объект, во-первых, не­
обходимо объявить в качестве типа возвращаемого ею значения тип соответству­
ющего класса. Во-вторых, нужно обеспечить возврат объекта этого типа с ПОМQ­
щьюобычной ИIIСТРУКЦИИ return. Рассмотрим пример. в следующей программе
функция-члсн
mkBigger () возвращаст объект, в котором ero член данных 'Jal
содержит вдвое большее значение по сравнению с вызывающим объектом.
//
Возвращение
объектов функциями.
#include <iostream>
using namespace stdi
class MyClass
int vali
public:
// Обычный конструктор.
MyClass(int i)
val = i i
cout « "Внутри KOHCTpYKTopa.\n"i
()
cout «
-МуСlаss
"Разрушение
объекта.\П"i
С++: РУКОВОДСТВО для начинающих
int getval()
409
ф
ф
( return val;
];
10
О
// Возвращение объекта.
MyClass mkBigger() ( //
//
MyClass o(val * 2);
~
Функция mkВiqqer()
возвращает
об~хт типа МУСlазз.
О
1:
х
О
()
()
~
х
return
О
О;
}
};
void display(MyClass
cout «
оЬ)
ob.getval() «
'\n';
int main ()
cout « "Перед
MyClass a(lO);
cout « "После
созданием объекта
создания
объекта
a.\n";
a.\n\n";
cout « "Перед вызовом функции display() .\n";
display(a);
cout « "После возвращения из функции· display () . \n\n':;
cout « "Перед вызовом функции mkBigger() .\n";
а = a.mkBigger();
cout « "После возвращения из функции mkBigger () . \n\n";
cout « "Перед
display (а) ;
cout « "После
return
вторым вызовом функции
display() .\n";
возвращения из функции
display() .\n\n";
О;
Вот какие результаты генерирует эта программа.
410
МОДУЛЬ 9. О классах подробнее
Перед
созданием
Внутри
После
объекта
а.
конструктора.
создания
объекта
а.
display() •
Перед вызовом функции
10
Разрушение
объекта.
После возвращения из функции display().
Перед
Внутри
вызовом
mkBigger().
функции
конструктора.
Разрушение
объекта.
Разрушение об~хта.
После
возвращения
из
функции
mkBigger () .
Перед
вторым вызовом
функции
display().
функции
display().
20
Разруше~ие
После
объекта.
возвращения
Разрушение
из
объекта.
В этом примере функция
MyClass, в
котором его член
mkBigger () СОЗДает локальный объект о IUIзсса
данных val содержит вдвое большее значение, чем
соответствующий член дшшых вызывающего оОъекта. Этот объект затем возвра­
щается функцией и присваивается объекту а
11
функции main (). Затем 06ы'кт
о разрушается, о чем свидетельствует первое сообщеllие "Разрушение объекта.".
Но чем можно объяснить второй вызов деструктора?
Если функция возвращает объект, автоматически создается временный объ­
ект, который' хранит возвращаемое значение. Именно этот объект реально и воз­
врашается функuиеЙ. После возврата значения объект разрушается. Вот откуда
взялось второе сообщение" Разрушение объекта." как раз перед сообщением" По­
сле возвращения из функции
mkBiggerO.".
Это
-
вещественное доказательство
разрушения временного объекта.
Потенциальные проблемы. подобные тем, что возникают при передаче пара­
метров, возможны и при возвращении объектов из функций. Разрушение времен­
ного объекта в некоторых ситуациях может вызвать непредвиденные побочные
эффекты. Например. если объект, возвращаемый функцией. имеет деструктор,
который освобождает некоторый ресурс (например, динамически выделяемую
память или файловый дескриптор). этот ресурс будет осuобожден даже в том
С++: PYI<OBOACTBO ДЛЯ начинающих
411
случае, если объект, получающий возвращаемое функцией значение, все еще ее
использует. Решение проблемы такого рода включает применение I<онструктора
копии, которому посвящен следующий раздеJl.
И еще. Можно организовать функцию так, чтобы она возвращала объект по
ссыл ке. Но при этом необходимо позаботиться о том, чтобы адресуемый ссылкой
объект не выходил 1'13 области видимости по завершении функции.
~npocы текущего КонтрОлS1 ________
ANil
Копструкторы псрсгружать нельзя. Верно ли это утвеРЖДСНl1С?
1.
2.
Когда объект l1еРСДLlется ФУIIIЩJШ по значению, создается еl'О копия.
Разрушается ли эта копия, когда выполнение функции завершается'?
З. Когда объект возвращается функцией, создается временный объект, кото­
рыЙ СО~~РЖИ!..20Зn:L)ЗщаеМО~~;:fИе. веgио ли эт<;z;~:рждени.;?:
ВАЖНОI
Et1LС.Q.эдаНИe..иJ4CLI.QАЬ.за.ваwие.
конструктора копии
Как было показано в предыдущих llрю.Jер,ах, при передаче объекта фушщии
или возвратс оБЪСI(та 113 ФУIIКЦИИ создастся «опия объекта. По умолчанию копия
представляет соGой побнтовыi1 клон орипшального объекта. ВО МНОl11Х случаях
такое поведение "по УМОЛ'Iанию" вполне прнемлемо, но в ОСПL71I,НЫХ Сlпуациях
желательно бы уточнить, 1(3К ДОЛЖШlбыть с.оздана копия объекта. Это можно сде­
лать, определив явным образом конст[)у1<'ГО[) коПl-п-J ДЛЯ класса. К01lсmруктор "о­
пии представляет собоii специалЫlыii пш I1срегружешюго I<OIICТPYl<Topa. который
автоматически вызывается, когда в копии объекта В03НИ1<ает необходимость.
Для начала еще раз сфОРМУЛИ(1уем проблемы, ДЛЯ решения которых мы хо­
тим определиТl) конструктор копии. При передаче объекта функции создается
побитовая (т.е. точная) I(ОIlIlЯ этого объекта, которая передается параметру этой
функции. Однако возможны ситуации, когда такая идентичная копия нежела­
тельна. Например, ссли ОРИПfШlЛЫIЫЙ объект использует такой ресурс, как от-
1.
2.
Это lIеверно. Конструкторы можно перегружать.
Да. когда ВЫПОШIеЮIС функции завершается. эта копия разрушается.
З. Верно. Значение. возвращаемое функuией, обеспечивается за счет созданиЯ вре­
менного объекта.
412
Модуль 9. О классах подробнее
крытый файл, то копия объекта будет исполыовать тот же eшtый ресурс. Сле­
довательно, если копия внесет изменения в этот ресурс, то изменения коснyrся
также оригинального объектаl Более того, при завершении функции копия будет
разрушена (с вызовом деструктора). Это может освободить ресурс, который еще
нужен оригинальному объекту.
Аналогичная ситуация возникает при возврате объекта из функции. Компи­
лятор генерирует временный объект, который будет хранить копию значения,
возвращаемого функцией. (Это делается автоматически, и без нашего иа то со­
гласия.) Этот временный объект выходит за пределы области видимости сразу
же, как только инициатору вызова этой функции будет возвращено "обещанное"
значение, после чего ·незамедлительно вызывается деструктор временного объек­
та. Но если этот деструктор разрушит что-либо нужное для ВЫllOлняемого далее
кода, последствия будут печальны.
В центре рассматриваемых проблем лежит создание побитовой копии обl,ек­
та. Чтобы предотвратить их возникновение, необходимо точно определить, что
должно происходить, когда создается копия объекта, и тем самым избежать не­
желательных побочных эффектов. Этого можно добиться путем создания кон­
структора копии.
Прежде чем подробнее знакомиться с использованием конструктора копии,
важно пошtмать, что в С++ определено два ОТi1ельных вида ситуаций, D которых
значение одного объекта передается другому. Первой такой ситуацией является
присваивание, а второй
-
инициализация. Инициализация может выполняться
тремя способами, т.е. в случаях, когда:
• один объект яшю инициализирует другой объект, кak, например, в объявлении;
• копия объекта передается параметру функции;
• генерируется временный объект (чаще всего в качестве значения, возвращае­
мого функцией).
Конструктор копии применяется только к инициализациям. Он не применя­
ется к присваиваниям.
Вот как выглядит самый распространенный формат конструктора копии.
имя_класса(соnst
//
имя_класса·
&obj)
тело конструктора
ЗJ'есь элемент оЬ j означает ссылку на объект, которая используется для иници­
ализации другого объекта. Например, предположим, у нас есть класс MyClass
и объект у типа MyClass, тогда при выполнении следующих инструкций будет
вызван конструктор копии класса
MyClass.
С++: PYI<OBOACтe0 ДМI начинающих
MyClass Х = у; //
funcl (у) ;
//
у = func2 () ;
//
//
Объект
у явно инициализирует объект х.
Объект.у передается
Объект
413
у принимает
в качестве
параметра.
ф
объект,
ф
I
\о
возвращаемый функцией.
о
В первых двух случаях конструктору копии будет передана ссылка на объ­
ект у, а в третьем
-
ссылка на объект, возвращаемый функцией
func2 ().
Таким
образом, когда объект передается в качестве параметра, возвращается ФУIIIЩИ­
ей или используется в инициализации, для дублирования объекта и вызывается
конструктор копии.
Помните: конструктор (ОПИИ не вызывается в случае, когда один объект при­
сваивается другому. Например, при выполнеllИИ следующего кода КОНСТРУЮ'ОР
копии вызван не будет.
MyClass
MyClass
Х =
у;
Х;
у;
//
Конструктор копии здесь
не используется.
Итак, присваивания обрабатываются оператором присваивания, а не кон­
структором копии.
Использование конструктора копии демонстрируется в следующей программе.
/*
Конструктор копии вызывается при передаче
объекта
функции.
*/
#include <iostream>
using namespace stdi
class MyClass {
int val;
int copynumЬer;
public:
// Обычный конструктор.
MyClass(int i) t
val = ii
copynumber = О;
cout « "Внутри обычного KOHCTpYKTopa.\n";
// KOKCТPYK~OP копии.
MyClass(const MyClass
&0)
{
g
t:
Х
О
u
u
о
<
::<:
О
414 МОАуль9.01~ассаХПОАробнее
................................................................................................................................
val = o.val;
copynumber = o.copynumber + 1;
cout « "Внутри конструктора копии.\n";
()
-МуСlаss
if(copynumЬer
cout «
else
cout «
== О)
"Разрушение
оригинал~ного
"Разрушение
копии
copynumber «
int getval ()
{ return val;
объекта.
\n";
" «
"\n";
}
};
void display(MyClass
cout «
оЬ)
ob.getval() «
'\n';
int main ()
MyClass a(lO);
display(a); //
//
return
KOHCТPYX~OP хопии вызывае~СR,
ФУНХЦКИ
display()
хогда
передае~СR объех~ а.
О;
При выполнении программа генерирует такие результаты.
Внутри
обычного
Внутри
конструктора
конструктора.
копии.
10
1
Разрушение
копии
Разрушение
оригинального
объекта
..
При выполнении этой программы здесь происходит следующее: когда в функ­
ции
main () создается объект а, "стараниями" обычного конструктора значение
С++: PYI<OBOACTBO ДЛ9i начинающих
415
переменной сорупшnbеr устанавливается равным нулю. Затем объект а пере­
дается ФУНКЦИИ
display (), а имеНIIО -
ее параметру оЬ. В этом случае вызыва­
ется конструктор копии, который создает копию I)бъекта а. Конструктор копии
инкрементирует значение переменной
display ()
copynumber.
По завершении Функuии
объект оЬ выходит из области видимости. Этnт выход сопровождает­
ся вызовом его деструктора. Наконец, по завершении Функuип та i
n ()
ВЫХОДI1Т нз
области в~щимости объект а, что также сопровождается вызовом его деструктора.
Вам было бы полезно немного поэксперимеtlтировать с преllьщущей програм­
мой. Например, создайте Функuию, которая бы Dозвраща.lI<l объект класса МуС 1-
ass,
и понабmодайте, ,<:огда именно вызывается КОllСТРУКТОР копии.
~npocы ДЛЯ текущего I<ОНТРОЛЯ
_
..
~
__
Какая копня об"екта создается при ИСIIOJll>30ваНИlI I\UIICTpYI\.OYOpa копии
1.
но умолчанию?
Конструктор копии Rызывается в СЛУ~ilС, когда ОДИII
2.
oGucla
IIрисванвает­
ся ДРУI'OМУ. Верно ли это?
З. В каких случаях может 1I0надо6иться ЯlIllОС ОПрСДСЛСlше конструктора
копии для класса?
J1-"'W4 .•
_;
...
...
:.::..t<·..г.~~~r;..~--с1С.:....J-"""
_ _"",""""_"Е'%8·
... ;,Lo.,,.~'._Ч".
~..a..r
W==C--r"4~А':-J"D1"'.'"
ВАЖНОI
. , . <Il\lLllЩl4Иr"
А р,у3Ья.:
~r-"
••
в оБЩБ1 случае только 'Iлены класса имеют доступ к эш,рытым членам этого
I<Jlacca. ОДВШ<О п С++ сущсстnуст DО3~ЮЖИОСТЬ разрешить доступ к закрытым
членам класса ФУНlщиям, которые не нвляются членами этого класса. Для этого
достаточно объявить эти функции "дружественными" (или "дР!lЗЫШU") по от­
ношению к рассматриваемому Iслассу. Чтобы сделать функцию "другом" класса,
включите ее прототип в риЫ i.с-раздел объявления класса и предварите его клю­
чевымсловом
friend. Например, в этом фрагменте кода функция frnd () объяв­
ляется "другом" класса MyClass.
1.
2.
Коиструктор коmfИ по умолчанию создает поразрядную (т.е. идентичную) копию.
Не верно. Конструктор копии не вызывается, когда один объект присванвается
другому.
З. Явное определение коиструктора копии может поtraд06IПЬСЯ, когда копия объекта
не должна быть идентичной ориntиалу (чтобы не повреД}rrь его).
ф
ф
:I:
ID
О
g
с:
х
О
U
()
~
:0
Модуль 9. О классах подробнее
416
class MyClass
/ / ...
public:
friend void frnd(MyClass
/ / ...
оЬ);
};
Как видите, ключевое слово
friend
предваряет остальную часть ПРОТОТJmа
функции. ФуНlЩИЯ может быть "другом" нескольких классов.
Рассмотрим короткий пример, о котором функция-"друг" используется для
доступа к заI<РЫТЫМ члеНаМ Ю1асса
MyClass, чтобы определить, имеют ли OJШ
общий множитель.
//
Демонстрация использования функции-"друга".
#include <iostream>
using паrnезрасе std;
class MyClass
int а, Ь;
public:
MyClass(int i, int j) { a=ii b=ji }
friend int соmDеrюm (MyClass х); / / ФУИICЦИ_ comDenom() //
"друг"
lCJIacca MyClass.
};
//
//
Обратите внимание
на то,
что функция
соrnDепоrn
()
не является членом ни одного класса.
int comDenom(MyClass
х)
{
Поскольку функция
/*
она
имеет
данных
int
тах =
<
х.Ь
for(int 1=2; i <=
if«x.a%i)=~O
return
О;
"друг"
&&
?
тах;
класса
прямой доступ к его членам
*/
а и Ь.
Х.а
comDenom () --
право на
Х.а
: х.Ь;
i++)
(x.b%i)==O) return ii
MyClasS',
С++: руководство ДЛЯ начинающих
417
int main ()
MyClass n (18, 111);
if(comDenom(n»
cout «
"Общий
//
//
//
Функция
return
вызывается обычным
способом,
без использования
объекта и
оператора
множитель
соmОепоm(п)
else
cout «
соmОепоm()
«
равен
имени
"точка".
" «
"\п";
"Общего множителя
нет.\п";
О;
в ЭТОМ примерефункция сотОепот
() не является членом классаМуСlаss. Тем
pri vаtе-членам класса MyClass. В частно­
СПI, она может непосредственно исполь:ювать значения х . а их. Ь. Обратите так­
же внимание на то, что функция сотОепоm () вызывается 06ычны:м способом, Т.е.
не менее она имеет полный доступ к
без привязки к объекту (и без использования оператора "точка"). Поскольку она
не ФУНКЩ'lя-член, то при вызоuе ее не нужно квалифrщировать с указаиием имени
объекта. (Точнее, при ее вызове uель.JЯ задавать имя объекта.) ОБЫ'mо функции­
"другу" 8 качсствс параметра передастся один или несколько объектов юrасса, для
которого она является "другом", как в случае функции соmОепот
() .
Несмотря на то что в данном при мере мы не извлекаем никакой пользы из
объявления функции соmОепоm
()
"другом", а не членом класса
MyClass, суще­
CIвуют определенные обстоятельства, при которых статус функции-"друга" име­
ет большое значение. Во-первых, как вы узнаете ниже из этого модуля, функции­
"друзья" могут быть полезны для перегрузки операторов определенных ТИПОВ.
Во-вторых, функции-"друзья" упрощают создание некоторых функций ввода­
вывода
(06 этом
речь пойдет В модуле
11).
Третья причина использования функций-"друзей" заключается в том, что в не­
которых случаях два (или больше) класса могут содержать члены, которые нахо­
дятся во взаимной связи с другими частями программы. Например, у нас есть два
различных класса, CuЬe и
Cylinder, которые определяют характериcrики куба
и цилиндра (одной из этих характеристик является цвет объекта). Чтобы можно
было легко сравнивать цвет куба и цилиндра, определим функцию-"друга", ко­
торая, получив доступ к "цвету" каждого объекта, возвратит значение ИСТИНА,
418
МОДУЛЬ 9. О классах подробнее
если сравниваемые цвета совпадают, и значение ЛОЖЬ в противном случае. Эта
идея иллюстрируется па примере следующей программы.
11
11
ФУНКЦИRми-"ДРУЗЬR!-IИ" могут пользоваТЬСR
сразу несколько
классов.
#include <iostream>
using namespace stdi
class Cylinderi 11
епит
опережающее оБЪRвление
colors { red, green, yellow };
class СиЬе (
colors color;
public:
Cube(colors с) { color = с; }
friend bool sarneColor(Cube х, Cylinder
У);
11 ~-"дpyr"
11 1CJtacca сиье.
11
J;
class Cylinder
colors color;
public:
Cylinder(colors с) ( color= с; J
friend bool sarneColor(Cube х, Cylinder
У);
11
ФУНКЦИR-
//
"дpyr"
и
11 1CJt&cca
/1 Cylinder.
11
};
bool sarneColor(Cube
х,
Cylinder
у)
if(x.color == y.color) return truei
else return falsei
C++:·PYI<OBOACТВO МЯ начинающих
419
int main ()
(
cubel(red);
cube2(green);
Cylinder cyl(green);
СиЬе
СиЬе
if(sameColor(cubel, cyl) )
cout « "Объекты сиЬеl и
else
cout « "Объе:кты cubel и
if(sameColor(cube2, cyl) )
cout « "Объекты сuЬе2 и
else
cout « "Объекты сuЬе2 и
return
цвета.\п";
су!
одина:кового
су!
разного цвета.\п";
су!
одинакового
су!
разного
цвета.\п";
цвета.\п";
О;
При вьmолнении эта программа генерирует такие результаты.
Объекты
сиЬеl
и
су!
разного
цвета.
Объекты
cube2
и
су!
одина:кового
цвета.
Обратите внимание lIа то, что в этой программе используется опережающее
обмвленuе (таЮI\С именуемое опережающей ССЬUl1Cой) ДЛЯ lUшсса
необходимость обусловлена тем, что объявление функции
Cylinder. Его
sameColor () в клас­
се СиЬе исполь:зует ссылку на класс Су! inde r до его объявления. Чтобы создать
опережающее оБЪЯDление для класса, достаточно использовать формат. пред­
ставленный в этой программе.
"Друг" одного класса может быть членом другого класса. Перепишем преды­
дущую программу так. чтобы функция
sameColor ()
стала членом класса СиЬе.
Обратите внимание на использование оператора разрешения области видимости
(или оператора разрешения контекста) при объявлении фУНlщии
в качестве "друга" класса
1*
ФУНКЦИЯ может
"другом"
sameColor ()
Cylinder.
быть
другого.
tinclude <iostream>
using namespace std;
членом одного
*/
класса
И одновременно
420
Модуль 9. О классах подробнее
...............................................................................................................................
class Cylinder; //
епит
опережающее объявление
colors ( red, green, yellow };
class СиЬе (
colors color;
public:
Cube(colors с) { color=
Ьооl sameColor(Cylinder
с;
у);
}
//
Функция
//
R8ЛRетCR членом класса
sameColor()
теперь
Cube.
//
};
class Cylinder
colors colori
public:
Cylinder (colors с) { color '" с; }
friend Ьооl Cube::sameColor(Cylinder
у);
//
Функция
// Cube::sameColor() / / "дpyr" xnасса Cylinder.
//
};
bool Cube::sameColor(Cylinder у) (
if(color == y.color) return true;
else return false;
int main ()
cubel(red);
cube2(green);
Cylinder cyl(green);
СиЬе
СиЬе
if(cubel.sameColor(cyl»
cout « "Объекты сиЬеl
else
cout « "Объекты сиЬеl
и су!
одного цвета.\п";
и су!
разного цвета.\п";
С++: руководство ДЛЯ начинающих
if(cube2.sameColor(cyl))
cout « "Объекты сиЬе2
else
cout « "Объекты сиЬе2
return
и
су!
одного цвета.\п";
и
су!
разного цвета.\п";
421
О;
Поскольку функция
sameColor ()
является члеиом класса СиЬе, она должна
вызываться для объектов lUIacca СиЬе, Т.е. она имеет прямой доступ к перемен­
ной
color объектов типа СиЬе. Следовательно, в качестве параметра необходи­
sameColor () только объекты типа Cylinder.
мо передаваТh функции
~ПРОСЫ ДЛ'i1 текущего l<онтрОл'i1-_ _ _ _ _ __
1.
Что такое функuия-"друг"? Какое ключевое слово используется для ее
объявления?
2.
_.
Нужно ли использовать при вызове функции-"друга" (для объекта) опе­
ратор "точка"?
3. Может ли Функция-"друг" одного lUIacca быть членом другого класса?-
в дополнсние !( !(JIЮЧСIJОМУ слову
class
IJ С++ предусмотрена возможность
создания еще двух "классовых" типов: структуры и объединения.
Структур".
Структуры "достались в наследство" от языка С и объявляются с помощью
ключевого слова
struct
(struсt-о6ъявление синтаксически подобно
class-
объявлению). Оба объявлсния создают "классовый" тип. В языке С структура
1.
Функция-"друг"
-
это функция, которая не является членом класса, но имеет до­
ступ к закрытым членам этого класса. Функция-"друг" объявляется с использова­
нием ключевого слова
2.
friend
Нет, функция-"друг" вызывается как обычная функция, не имеющая "членства".
З. Да. функция-"друг" одного класса может быть членом другого класса.
422
Модуль 9. О классах подробнее
может содержать только члены данных, но это ограничение в С++ не действует.
В С++ структура, по сути, представляет собой лишь альтернативный способ
определения класса. Единственное различие 11 С++ между
struct-
и
class-
объявлениями состоит в том, что по умолчанию все члены открыты в структуре
(struct)
и закрыты в классе
(class).
Во всех остальных аспектах С++-струк­
туры и классы эквивалентны.
Рассмотрим при мер структуры.
#include <iostream>
using namespace stdi
struct Test {
int get_i () { return i;
void put_i (int j) ( i = j;
private:
int ii
// Эти члены открыты
// (public) по умолчанию.
};
int main()
Test S;
s.put_i(lO)i
cout « s.get i()
rеturп
i
О;
Эта простая программа определяет тип структуры
get_i ()
и
put_i ()
открыты, а член данных
использование ключевого слова
i
Test,
в которой функцни
закрыт. Обратите внимание Ila
private для объявления закрытых элементов
структуры.
В следующем примере показана эквивалентная npограмма, в которой вместо
5
t ruc t -объявлен ия используется с 1 а s s -объявление.
#include <iostream>
using паmеврасе std;
class Test {
int ii // закрытый
public:
член по
умолчанию
С++: РУКОВОДСТВО ДЛЯ начинающих
423
int get_i() ( return i;
void put_i(int j) { i = j;
m
};
6
о
g
int main ()
t::
)(
Test S;
О
U
U
~
s.put_i(lO);
cout « s.get_i();
return
О
О;
Спросим у опытного программиста
Вопрос. Если структура и класс так nОXIJЖlI, то почему в С++ существуют оба
BiqJllaHma оfiьявлеllll1l "классового" nlll1lа?
Ответ.
С ОДНОЙ стороны, действительно, D существовании структур и классов
присугствует определенная избьгго'!ность, и МИОП1е нач~rnающие про­
граммисты УДl18J1ЯЮТСЯ этому. Нередко можно даже услышать предложе­
ния mП<Видировать одно нз этих средств.
Однако не СТОIIТ забывать о том, что создатель С++ стремился обеспечить
совместимость С++ и С. Согласно современному определенкю С++ стан­
дартнаяС-структура является абсолютно доnyсгимой С++-стрyкryрой. Но
В С все члены структур oтl<pы1ы по умолчанию'(в С вообще нет ПОНЯПIЯ
открытых И закрытых членов). Вот почему члены С++-структур On<phITbl
(а не закрыты) 110 умолчанию. Поскольку lUlючевое СJlОВО
class
введено
для поддержки инкапсуляции, в ТОМ, что его члены заКРЫn,I по умолчанию,
есть определенный резон. Таким образом, чтобы избежать несовместимоCПI
с С в этом аспекте, C++-оБЪЯRление структуры по умолчанию не должно
отличаl'bCЯ от ее С-о6ъявления. Потому-то и бьшо добавлено новое клю­
чевое слово. Но в перспективе можно говорить о более веской причине для
отделения структур от классов. Поскольку тип
лен от типа
struct,
class
синтаксически отде­
определение класса вполне oткpьrro для эволюцион­
ных изменений, которые синтаксически могут оказаться нссовмеСТИМЫМlI с
С-структурами. Так как мы имеем дело с двумя отдельными типами, буду­
щее направление развития языка С++ не обременяется «моральными обя­
зательствами", связанными с совместимостью с C-струкryрамн.
424
МОДУЛЬ 9. О классах подробнее
С++-программисты тип
class
используют главным образом для определе­
ния формы объекта, который содержит функции-члены, а тип
struct -
для соз­
дания объектов, которые содержат только члены данных. Иногда для описания
структуры, которая не содержит функции-члены, используется аббревиатура
РОО
(Plain Old Оаtэ).
Объединения
Обьедuне1luе - это область памяти, которую разделяют несколько различных
перемеННbJХ. Объединение создается с помощью ключевого слопа uлiол. Его
объявление, как lIетрудно убедиться па следующем примере, подобио объявле­
нию структуры.
union utype {
short int i;
char Chi
Здесь объявляется объединение, в котором значение типа sho rt
типа
char
i nt
и значение
разделяют одну и ту же область памяти. Необходимо сразу же про­
яснить одии момент: невозможно сделать так, чтобы это объединение хранило и
целочисленное значение, и символ одновременно, поскольку переменные
i
и
ch
накладываются (в памяти) друг на друга. Но программа в любой момент может
обрабатывать информацию, содержащуюся в этом объединении, как цеЛОЧJiС­
ленное 3H:J.'1eHIIe или как сIIМВОЛ. Следовательно, объединение обеспечивает два
(или больше) способа представления одной и тоН же порции данных.
ПереМСIIНУЮ объединеНIIЯ можно объявить, разместив ее имя в конце его объ­
явлсния либо воспользоваВШИСI) отдельной Itнструкцией объявления. Чтобы
объявить персмеНIlУЮ объединения именем
u_ v а r
типа
ut
уре, достаточно за­
писать следующее.
utype u_vari
В переменной объединения
u _ var как переменная i типа short int, так и сим­
ch занимают одну и ту же область памяти. (Безусловно, пере­
менная i занимает два байта, а символьная переменная ch использует только один.)
Как переменные i и ch разделяют одну область памяти, показано на рис. 9.1.
вольная переменная
Согласно Uидеологии" С++ объединение, по сути, является классом, в котором
все элементы хранятся в одной облаcrи памяти. Поэтому объединение может вклю­
чать конструкторы и деструкторы, а таюке функции-члены. Поскольку объединение
унаследовано от языка С, его члены открыты (а не закрыты) по умолчанию.
Рассмотрим программу, в которой объединение используется для отображения
символов, составляющих младший и старший байты короткого целочисленного
значения (в предположении, что "размер" типа
short int составляет два байта).
С++: РУКОВОДСТВО ДЛЯ начинающих
~
I
m
:I:
10
I
I
~
ch
Рис.
9.1.
О
1:
Пере.менuые
i
и
Х
ch
О
U
U
вместе используют 06ьедине­
иие
425
~
u var
О
//
Демонстрация
использования
объединения.
#include <iostrearn>
using паrnезрасе stdi
union u_type (
u_type(short int а) { i = а; };
u_type(char х, char у) { ch[Q] =
void showchars() {
cout « ch[Q] «
cout « ch[l] «
short int ii / /
//
char ch[2]i
Х;
ch[l]
Yi}
" ".,
"\п";
Эти чnены даlUUalX обrъeдикеНИR
разделяют
одну и ту же область
naкRти.
} i
int rnain ()
u type u(lOOO)i
u_type и2('Х',
'У');
// Данные & об~кте типа u_type кокко использовать &
/ / качестве цenочиcnекноrо знаЧ811ИR и.nи двух СИКВOnО&.
cout « "Объединение u в качестве целого числа: ";
cout « u.i « "\п";
cout « "Объединение u в качестве двух СИМВОЛОВ: ";
и. showchars () ;
426
МОДУЛЬ 9. О классах подробнее
cout « "Объединение и2
cout « u2.i « "\n";
cout « "Объединение и2
u2.showchars();
return
в
качестве целого
В
качестве двух
числа:
";
символов:
";
О;
Результаты вьmолнения этой программы таковы.
Объединение
Объединение
u
u
в
качестве
целого
в
качестве
ДВУХ
1000
ш :
числа: 22872
числа:
символов:
Объединение
и2
в
качестве
целого
Объединение
и2
в
качестве
ДВУХ
СИМВОЛОВ:
Х
У
Как подтверждают эти результаты, используя объединение типа
u_type, на
одни и те же данные можно "смотреть" по-разному.
Подобно структуре, С++-объединение также произошло от CBoero С-пред­
шественника. Но в С++
0110
имеет более широкие "классовые" возможности
(ведь в С объединения могут включать тоЛl,КО члены даииых, а функции и
конструкторы пе разрешеIlЫ). Однако лишь то, 'lто С++ наделяет "свои" объ­
единения более мощными средстnами и большей степенью гибкости, не озна­
чает, что вы непремеНIIО д()лжны их ИСПОЛЬЗОЩIТЬ. Если пас Iшолпе устраивает
объединение с траДIIЦИО[IIIЫМ СТllлем поведения, ОЫ вольны именно таким его
и I1спользооать.
110 D
случаях, [Ш['Д<:\ можно [шкансулировать данные объс)(и­
пе1lИЯ вместе с ФУllКЦНЯМll, которые IIХ обра6аТblDают, все же стоит DОСПО.IЬ­
зоваться С+ + -DОЗМОЖНОСТЯМlI, что придаст пашей программе дополнительные
преимущества.
При использоваНIIИ C++-06ъеДИllений НС1)бходимо ПОМНIJТl) о некоторых
ограничениях, СПJlзmшых с их Прllмснснием. Большая их часть связана со сред­
ствами языка, которые рассматриваются lfИже в этой книге, но все же здесь стоит
их упомянуть. Прежде оссго, объединение не может наследовать класс и не мо­
жет быть базовым классом. Объединение не может иметь виртуальные функции­
члены. Стаrnческие переменные не могут быть членами объединения. В объеди­
нении нельзя использовать ссылки. Членом объединения не может быть объект,
который перегружает оператор
"=". Наконец, членом объединения не может быть
объект, в котором явно определен конструктор или деструктор.
Анонимные объединения
в С++ предусмотрен специалЬtlый тип объединения, который называется шю­
ншtныAI. Анонимное объединение не имеет наименования типа, и поэтому объект
С++: PYI<OBOACTBO ДМI начинающих
427
Taкoro объединения объявить невозможно. Но анонимное объединение сообщает
КОМПИЛЯТОРУ о том, что его члены разделяют одну и ту же область памяти. При
этом обращение к самим переменным объединения происходит непосредствен­
но, без использования оператора "точка".
о
Рассмотрим такой пример.
//
Демонстрация
использования
т
~
анонимного
объединения.
~
х
О
U
U
#inc1ude <io5tream>
#include <c5tring>
u5ing namespace std;
~
::.::
О
int main ()
{
/ / ОпредеJUl811 авОllИlOlое об'Идивеиие.
union (
10ng 1;
double d;
char 5[4];
//
к
//
обращение
членам анонимного
объединения
выполняется
напрямую.
1 = 100000;
cout « 1 « " ";
d = 123.2342;
cout « d « " ";
strcpy(s, "hi");
cout « 5;
return
О;
как видите, к элементам объединения можно получить доступ так же, как к обыч­
ным переменным. Несмотря на то что ОНИ объявлены как часть анонимного объ­
единения, их имена наХодятся на том же уровне области видимости, что и другие
локальные переменные ТОГО же блока Тахим образом, чтобы избежать конфликта
имен, член анонимного объединения не должен иметь ИМЯ, совпадающее с именем
любой другой переменной, объявленной в той же области видимости.
Все описанные выше ограничения, налагаемые на использование объедине­
ний, применимы и к анонимным объединениям. К ним необходимо добавить
428
Модуль9.01~ассахподробнее
следующие. АнОШlмные объединения должны содержать только члены дани ых
(функции-члены недопустимы). Анонимные объединения не могут ВJUIючать
private- или рrоtесtеd-элементы. (Спецификатор protected описан в мо­
дуле 10.) Наконец, глобальные анонимные 06ъединеJШЯ должны быть 06ъяв.Jе­
ны с использошшием спецификатора static.
ВАЖНОI
t.I:-----КАючевое САово thj.s
Прежде чем переходить
треть ключевое слово
this.
J(
теме пере1"РУ3КИ операторов, необходимо рассмо­
При каждом
ски передается указатель, именуемый
СЯ эта функция. Указатель
this -
obl30fle функции-члена ей автоматиче­
this, на объект, для которого оызьшзет­
это lIеявныu параметр, принимаемый всеми
функциями-членами. Следовательно, в любой функции-члене указатель t h i 5
можно использовать для ссылки на вызывающий объект.
Как вы знаете, функция-член может иметь прямой доступ к 3ЗI<рытым
ate)
(pr i '1-
членам дшшых своего класса.
Например, у нас определен такой класс.
c1ass Test {
int i;
void f ()
/ / .,.
. ..
};
};
в ФУНКЦИИ f () для присnзиnания члену
i
значения
10 можно использовать сле­
дующую инструкцию.
i
=
10;
В действительности предыдущая ИНСТРУКЦИЯ представляет собой сокращен­
ную форму следующей.
this->i
=
10;
Чтобы понять, как работает указатель
кую программу.
//
Использование
указателя
tinc1ude <iostream>
using narnespace std;
c1ass Test {
this.
this.
рассмотрим следующую корот­
С++: руководство д1\Я начинающих
int ii
public:
void load i(int val) {
this->i = val; //
int get i () (
return this->i; 11
то же
самое,
что
i
429
val
т:I:
ID
О
g
1:
то же самое,
что
return i
Х
О
U
U
о
<
~
о
int main ()
Test
о;
o.load_i(lOO);
cout « o.get_i();
return
О;
При выполнении эта программа отображает число
100. Безусловно, предыдущий
th is. Ско­
пример тривиален, но D нем показано, как можно использовать указатель
ро вы поймете, почему указатель
this так важен для программнрования на С++.
И еще. Функции-"друзья" не имеют указателя this, поскольку они не явля­
ются членами класса. Только функции-члены имеют указатель this.
~ПРОСЫ ДЛЯ текущего КОНТРОЛЯ
1.
2.
Может ли структура
--------
(struct) содержать функции-члены?
Какова основная характеристикао6ъединения
(union)?
3. На что указывает слово this?*
(struct) может содержать функции-члены.
1.
Да, структура
2.
Члены дaнных 06ъедЮlения разделяют одну и ту же область памяти.
3.
Слово
this указывает на объект, для которого была вызвана функция-член.
430
Модуль 9. О классах подробнее
~-
r.Y&I:IepeCpV3KQ оаераroров
Остальная часть модуля посвящена одному из самых интересных средств
-
nереzрузке операторов. В С++ операторы можно перегружать для "классовы.х"
типов, определяемых программистом. ПРИНЦИI1Иальный ВЫИL"рыш от перегруз­
ки операторов состоит в том, что она позволяет органично интегрировать нов ые
типы данных в среду программирования.
Перегружая оператор, программист опрсдеJlяет его назначение для I'Шfкрет­
ного l,ласса. Например, класс, Jшторыii определяет сuязный СПIJСОК, может не­
пользовать оператор
"+" для добавления
объеюз 1< списку. Класс, которые реали­
зует стек, может использовать оператор
нибудь другом классе тот же оператор
"+" длл записи объекта о стек. В каком"
"+" мог (iUI служить для совершенно иной
цели. При перегрузке оператора ни одно из оригинальных его значений не теря­
ется. Перегруженный оператор (о своем новом качестве) работает как совершен­
но новый оператор. Поэтому перегрузка оператора
"+" для
обработки, например,
связного С\lиска не приведет к изменению его функции (т.е. операции сложения)
по отношению к целочислеllllЫМ значениям.
Пеrегрузка операторов тесно связана с перегрузкой функций. Чтобы перегру­
зить оператор, необходимо Ullределить значение новой операции для класса. к
KOTOPO~IY она будет ПРИМСJJЯТЫ"Я. ДЛЯ этого создается ФУJlJЩIfЯ operator (але­
раТОРl1ая ФУIIКЦИН), которал определяет дсйстrне этого Оllсратора. Общий фор­
мат функции
operator
тип имя_класса::
таков.
operator#
(список_арr'Ументов)
{
Здесь персгружаемый оператор обозначается символом" #", а элемент тип пред­
ставляет собой тип значения, возвращаемого заданной операцией. И хотя он в
принципе может быть любым, rnп значения, возвращаемого функцией
tor,
opera-
часто совпадает с именем ктшсса, дЛЯ КОТОРО1·0 перегружается данный опе­
ратор. Тахая корреляlVUI облегчает использование перегруженного оператора в
составных выражениях. Как будет показано ниже, конкретное значение элемента
список_аргументов определяется несколькими факторами.
Операторная фУlUщия может БЫ1Ъ членом KlIaCCa или не быть им. Оператор­
ные функции, не являющиеrя членами класса, часто определяются как его "дру­
зья". Операторные функции-члены и функции-не члены класса различаются по
форме перегрузке. Каждый из вариантов мы рассмотрим в отделыюсrn.
С++: руководство ДМI начинающих
На заметку ~
431
Поскольку в С++ определено довольно много Оllсраторов, в этой
юшге невозможно описать все аспекты обширной темы перегруз­
ки операторов. Для получения более подробной информации об­
ратитесь к моей книге Полный справочник по С++, Издательский
дом "Вильяме".
ф
ф
ii
о
g
с::
х
О
ВАЖНОI
tlJl'
U
U
ПереГРV3КQ.оаераZQрав
о
;2
с использованием функций-членов
Начнем с простого примера. В следующей программе создается класс
ThreeD,
который обеспечивает поддержку координат объекта в трехмерном пространстве.
Для класса
Th reeD здесь реализована перегрузка операторов" +" 11 "=".
и TaI<, рас­
смотрим внимательно код этой программы.
//
Перегруэка
операторов
"+"
и
"="
для
класса
ThreeD.
У
z
#include <iostream>
using namespace std;
class ThrecD (
int х, у, z; // 3-мерные координаты
public:
ThreeD () ( х = у = z = О; )
ThreeD(int i, int j, int k)
х = i;
ThreeD operator+(ThreeD
ор2);
ThreeD operator=(ThreeD
ор2);
void show()
//
//
//
//
=
j;
передается
операнд
передается
ThreeD temp;
JtЛасса ТhreeD.
ор2)
неявно.
opl
};
{
k;
Операнд ор1
;
/ / ПереI'pуsха операlJ:lора "+" ДJISI
ThreeD ThreeD::operator+(ThreeD
=
неявно.
)
О
МОДУЛЬ 9. О классах подробнее
432
temp.x
х + ор2.х; //
temp.y
у + ор2.у; //
temp.z = z + op2.z; //
При выполнении операции сложения
целочисленг.ых значений сохраняется
оригинальный смысл оператора
Возвращае~CR новый об~х~.
return tempi //
//
Аргументы
функции не изменяются.
Перегрузха оператора присваивани.
//
"+".
ThreeD ThreeD::operator=(ThreeD
(=) Дn. xnасса ТhreeD.
ор2)
{
х
=
ор2.х;
у = ор2.у;
При выполнении операции присваивания
//
//
целочисленных значений сохраняется
оригинальный смысл оператора
z = Op2.Zi //
return *thisi //
"=".
Возвращае~СR NОДИФИЦИРО8авный об~х~.
// Отображение координат
void ThreeD::show()
Х,
У,
Z.
(
cout «
cout «
cout «
" " ,.
", ";
«
«
z «
х
у
"\n";
int main ()
ThreeD
а
(1, 2,
З),
cout « "Исходные
а. show () ;
cout « "Исходные
Ь. show () i
cout «
"\n";
с = а +
cout «
Ь;
//
Ь
(10, 10, 10),
С;
значения
координат
объекта
а:
" ,.
значения
координат
объекта
Ь:
" ,.
сложение объектов а и Ь
"Значения
координат
объекта
с
после
с
а
+
Ь:
";
С++: руководство дl\Я начинающих
433
c.show();
cout «
с = а
Q)
"\п" i
+
Ь
+
С;
ф
1/
cout « "Значения
с. show () ;
cout «
с =
Ь
координат
объекта
~
Ь и с
с после
с
=
а
+
Ь
+
с:
";
11
х
~
О
демонстрация множественного присваивания
координат
объекта
с
после
с
=
Ь
а:
";
координат
объекта Ь
после
с
=
Ь
а:
11;
О;
Эта программа генерирует такие результаты.
1, 2, 3
10, 10, 10
Исходные
значения
координат
объекта
а:
Исходные
значения
координат
объекта
Ь:
Знач~ния
координат объекта с после с =
а
+
Ь:
Значения
координат
объекта
с
после
с
=
а
+
Ь
Значения
координат
объекта
с
после
с
=
Ь
=
а:
Значения
координат
объекта Ь
после
с =
Ь
=
а:
11, 12, 13
+
с:
22, 24, 26
1, 2, 3
1, 2, 3
Исследуя КОД этой программы, вы, вероятно, удивились, увидев, что обе опера­
торные функции имеют только по одному параыетру, несмотря на то, что они пе­
реrpужают бинарные операции. Эro, на первый взгляд, "вопиющее" противоречие
можно легко 06ъяснить. Дело в том, что при перегрузке бинарного оператора с ис­
пользованием функции-члена ей передается явным образом только один apryмeкr.
Второй же неявно передается через указатель
=
х
+
с::
О
cout « "Значения
с. show ();
cout « "Значения
Ь. show () ;
temp.x
~
U
U
"\п";
= а;
return
сложение объектов а,
this. Таким образом, в строке
ор2.х;
под членом х подразумевается член
this->x, т.е. член х связывается с объектом,
который вызывает данную операторную функцию. Во всех случаях неявно пере­
дается объект, указываемый слева от символа операции, КОТОРЫЙ стал причиной
Модуль9.01~ассахподробнее
434
вызова операторной функции. Объект, располагаемый с правой стороны от СИМ~
вола операции, передается этоIi функции в качестве аргумента. В общем случае при
использовании функции-члена для переll>УЗКИ унарного оператора параметры не
используются вообще, а для пеРСll>УЗКИ бинарного
- только один параметр. (Тер­
нарный оператор "?" перегружать нельзя.) В любом случае объект, который вызы­
вает операторную функцию, неявно передается через указатель
this.
Чтобы понять, как работает механизм перегрузки операторов, рассмотрим
внимательно предыдущую ПРОll>амму, наЧИlIая с переll>ужешюго оператора
"+".
При обработке доух объеКТОD типа ThreeD оператором
жение значений соотвеТСТВУЮЩIlХ КООРДIIнат, как
а tar+
() .
"+" Dыполняется сло­
llOl<<l..laJlO n функции ар =r-
Но заметьте, что эта фуикция не модифицирует значение ни одного
операнда. В качестве результата операции эта функция возвращает объект типа
ThreeD, который содержит результаты попарного сложения координат двух объ­
ектов. Чтобы понять, почему операция
"+" не нзменяет содержимое
ни одного из
объектов-участников, рассмотрим стандартную арифметическую операцию сло­
жения, примененную, например, к числам
вен
22,
но при его получении ни
10 If 12. Результат операции 10+ L2 ра­
10, ни 12 не были изменены. Хотя не сущеСТlIует
правила, которое бы не позволяло перегруженному оператору измснять значсние
одного из его операндов, все же лучше, чтобы ОIlне противоречил общепринятым
нормам и оставался в соглаСJlИ со СIЮJlМ оригинальным назначением.
Обратите внимание на то, что функция аре ra to r+
()
возпращает объект т ИIlа
ThreeD. Несмотря lIа то что Оllа могла бы возвращаТl> значение любого допусти­
мого D С++ типа, тот фаICТ, что она возвращает объеl(Т типа ThreeD, ПОЗВОЛЯС"J ис­
пользовать оператор
"+"
в таЮ1Х составных выражениях,
выражения, а+Ь, генерирует результат типа
Kal<
а+Ь+с. Часть этогu
TrHeeD, который затем суммиру(~тся
с объеКТОI\'1 с. И если бы эта часть выражения ,·енерировала значсние иного 1 ипа
(а ис типа ThreeD), такое состашlOС выражеlше попросту не работало бы.
В отличие от оператора
"+", опсратор присваиваш1Я
приводит к модификации
UДlЮГО из своих аргументов. (Прежде всего, :)10 составляет саму суть приспаи­
вания.) Поскольку функция
operator= () вызывается объектом, который рас­
(=), IIменно этот объект и модифИЦИРУ­
положен слева от символа присваивания
ется в результате операции присвroшания. После выполнения этой операции зна­
чение, возвращаемое перегружеННblМ оператором, содержит объект, указанный
слева от символа присваивания. (Такое положение вещей вполне согласуется с
традиционным действием оператора
"=".) Например, чтобы можно было выпол­
нять инструкции, подобные следующей
а
=
Ь
=
с
= di,
необходимо, чтобы операторная функция
адресуемый указателем
operator= () возвращала объект,
this, и чтобы этот объект располагался слева от опера-
С++: PYI(OBOACTBO ДЛfl начинающих
435
тора "=". Это позволит выполнить любую цепочку присваиваНИЙ. Операция при­
сваивания
-
это одно из самых важных применений указателя
this.
В предыдущей программе не было насущной необходимости переrpужать опе­
ратор
"=",
поскольку оператор npисваива.ния, по умолчанию предоставляемый
языком С++, вполне соответствует требованиям класса ThreeD. (Как разъяс­
иялось выше в этом модуле, при выполнении операции ПРИСDаивания по умол­
Llанию создается побитовая КОПИЯ объекта.) Здесь OllepaTOp
"=" был перегружен
исключительно в демонстрационных целях. В общем случае оператор
"="
следу­
ет перегружать только тогда, когда побитовую копию использовать нельзя. По­
скольку применения оператора
"="
по умолчанию вполне достаточно для класса
ThreeD, в последующих примерах этого модуля мы перегружать его не будем.
о значении порядка операндов
Перегружая бинарные операторы, помните, что во многих случаях порядок
следования операндов имеет значение. Например, выражение А
но, а выражение А
-
В
-
нет. (Другими словами, А
-
+ В коммутатив­
В не то же самое, что В
-
А!)
Следовательно, реализуя пере!1>ужеliные версии неJCоммутативных операторов,
необходимо помнить, какой операнд стоит слева от символа операции, а какой
справа от
I/ero.
-
Например, l! следующем фрагменте кода демонстрируется пере­
грузка опера1'ора вычитания для класса
ThreeD.
/ / Пере грузка оператора вь.тчитания.
ThreeD ThreeD::operator-(ТhrееD ор2)
(
ThreeD tempi
- ор2.х;
- ор2.у;
temp.z = z - Op2.Zi
temp.x =
temp.y
х
у
return tempi
Помните, что именно левый операнд вызывает операторную функцию. Пра­
вый операнд' передается
n явном
виде.
Использование функций-членов ДЛSI перегрузки
унарных операторов
Можно также перегружать такие унарные операторы, как
ные
"-"
"++", "--", или унар­
и "+". Как УПОМИllалось выше, при перегрузке унарного оператора с помо-
$
~
g
t:
х
О
(J
(J
о
~
О
436
МОДУЛЬ 9, О I<ЛОССОХ подробнее
ЩЬЮ функции-члена операторной функции ни один объект не передается явhыM
образом. Операция же выпоmiЯется над объектом, который генерирует вызов этой
функции через неявно переданный указатель
грамму, в которой для объектоп типа
//
this.
Например, рассмотрим про­
ThreeD определяется операция
Пере грузка унарного оператора
инкремента.
"++".
#include <iostream>
using namespace std;
class ThreeD {
int х, у, Z; // З-мерные координаты
.public:
ThreeD () { х = у = Z = О; )
ThreeD(int i, int j, int k) {х = i;
ThreeD operator++()i //
У
=
ji
nрефиксная версия
Z
= k; }
оператора
"++"
void show() ;
/ / Переrpуsха префИХСRОЙ версии оператора
// xnасса ThreeD.
"++"
ДJlSI
ThreeD ThreeD::operator++()
х++;
//
инкремент
координат х,
у и
Z
у++;
z++;
return *this; //
Возвращаем инкрементированный объект.
// Отображение координат
void ThreeD::show()
{
cout «
cout «
cout «
int main ()
х
«
", ";
у
«
«
", ";
Z
"\n";
Х,
У,
z.
С++: РУКОВОДСТВО MfI начинающих
437
.ThreeDa(1,2,·3);
ф
ф
cout « "Исходные
a.show();
значения координат
объекта
а:
~
";
~
о
с
//
++а;
инкремент
cout « "Значения
а. show () ;
х
объе.кта а
координат
О
(J
после
++а:
";
U
~
О
return
О;
Результаты выполнения этой программы таковы.
Исходное
значение
Значение
координат после
координат
объекта
++а:
2,
З,
а:
1, 2, 3
4
Как видно по последней строке результатов программы, операторная: функ­
ция оре ra tor++
()
иикрементирует каждую координату объеК1'а и возвращает
модифицированный объект, что вполне согласуется с традиционным действием
оператора
"++".
Как ВЫ знаете, операторы
"++" и "--" имеют префиксную и
постфиксную фор­
мы. Например, оператор инкремента МОЖIЮ использовать 8 форме
++х;
или
х++;
.
operatThreeD. Но
Как отмечено в комментариях к предыдущей программе, функция
or++ ()
определяет префиксную форму оператора
"++"
ДЛЯ класса
нам ничего не мешает перегрузить и постфиксную форму. Прототип постфикс­
ной формы оператора
"++" для класса three_d
ThreeD operator++(int notused);
Параметр
notused не используется самой
имеет следующий ВИД.
функцией (игнорируется). Он слу­
жит индикатором для компилятора, позволяющим отличить I1рефиксную форму
оператора ИНl<ремента от постфиксной. (Этот параметр также используется в ка­
честве призиака постфиксной формы и ДЛЯ оператора декремента.) Ниже приво­
дится один из возможных способов реализации постфиксной версии оператора
"++" ДЛЯ класса ThreeD.
// Перегрузка постфиксной версии оператора "++".
ThreeD ThreeD:: operator++ (in·t notused) / / Обратите
внимание
438
Модуль 9. О классах подробнее
..................................................................................................................................
//
//
ThreeD temp
Х++;
//
=
*thisi //
инкремент
на
использование
параметра
notused.
сохранение исходного значения
координат х,
у и
Z
У++;
Z++i
return temp; //
возврат исходного
значения
Обратите внимание на то, что эта функция сохраняет текущее значение опе­
ранда путем выполнения такой инструкции.
ThreeD temp = *thiSi
Затем сохраненное значение операнда (в 06ъеl.-ге
инструкции
return.
temp) возвращается с помощью
Следует иметь в виду, что традиционный постфиксный опе­
ратор инкремента сначала получает значение операнда, а затем его инкремеНТIlРУ­
ет. Следовательно, прежде чем инкрементировать текущее значение операнда. его
нужно сохранить, а затем и возвратить (не забывайте, что постфиксный опер.пор
инкремента не должен возвращать МОДИфlщированное значение CBoero операнда).
В следующей версии исходной программы реализованы обе формы опера­
тора
//
//
"++".
Демонстрация пере грузки оператора
использованием его
~++"
с
префиксной и постфиксной форм.
iinclude <iostream>
using namespace stdi
class ThreeD {
int х, у, Zi // З-мерные координаты
public:
ThreeD()
х = у = z = О;
}
ThreeD(int i, int j, int k) {х = ii
ThreeD operator++()i // префиксная
ThreeD operator++(int notused)i //
1/
void show ()
};
У =
j; Z
версия
=
k;
}
оператора ~++"
постфиксная
оператора
версия
~++"
С++: PYI<OBOACTBO дl\Я начинающих
// Перегрузка префиксной версии
ThreeD ThreeD::operator++()
оператора
439
"++".
Q)
Q)
:I:
10
х++:
иикрементируем координаты х,
//
О
g
z
у и
у++:
t:
z++:
return *this; //
х
а
возвращаем измененное значение объекта
u
u
~
х
О
/ / Перегрузка постфиксной версии оператора "++" ..
ThreeD ThreeD::operator++(int notused)
{
ThreeD temp = *this: //
х++;
//
сохраняем исходное значение
инкрементируем координаты х,
у и
z
у++;
Z++;
return temp; //
возвращаем исходное
// Отображение координат
void ThreeD::show()
Х,
У,
значение
объекта
Z.
{
cout «
cout «
cout «
«
«
z «
", ";
", ";
х
у
"\п";
int main ()
ThreeD
ThreeD
а(l,
2,
З);
Ь:
cout « "Исходные
show ();
а.
cout «
"\п";
значения
координат
объекта
а:
":
440 Модуль 9. О I<ЛОССОХ подробнее
..................................................................................................................................
++а;
BloISыaae~c. ФУНКЦИJl префИJCСНОЙ форl&l инхремеll'1'а.
//
cout «
а.
"Значения
а++;
"Значения
=
после
а++:
";
Объект Ь получает значение
после
объекта а
инкремента.
"Значения
координат
объекта
а
после
Ь
++а:
";
"Значения
координат
объекта
Ь
после
Ь
++а:
";
а++:
";
а++:
";
show () ;
=
"\п";
а++;
cout «
а.
координат
show () ;
cout «
ь
1/
1/
++а;
cout «
Ь.
";
"\п";
cout «
а.
++а:
show () ;
cout «
ь
после
ВЫsывae~cJl фYRXЦИJ! постфИКСНОЙ фОPКU иихремеll'1'а.
//
cout «
а.
координат
show () ;
//
//
Объект
а
получает
значение
объекта
с
до инкремента.
"Значения
координат
объекта
а
после Ь
координат
объекта Ь после
=
show () ;
cout «
"Значения
Ь
b.show();
return
О;
При вьшолиении программа генерирует такие результаты.
Исходные
значения
объекта
координат
Значения
координат
после
++а:
2,
Значения
координат
после
а++:
З,
а:
1, 2, 3
З, 4
4, 5
Значения
координат
объекта
а
после
Ь
++а:
Значения
координат
объекта
Ь
после
Ь
++а:
Значения
координат
объекта
а
после
Ь
а++:
Значения
координат
объекта
Ь
после
1)
а++:
4, 5, 6
4, 5, 6
5, 6, 7
4, 5, 6
С++: руководство ДЛЯ начинающих
441
Помните, что если символ
"++" стоит перед операндом, вызывается операторная
operator++ (), а если после операнда - операторная фуmщия operator++ (int notused). Тотжеподходисполыуется и для переГРУЗЮ1 префиксной и
функция
постфиксной форм оператора декремента для любого класса. Б качестве упражне­
ния самостоятельно определите оператор декремента ДЛЯ класса
ThreeD.
Интересно отметить. что ранние версии языка С++ не содержали различий
между префиксной и постфиксной формами операторов инкремента и декремента.
Тогда в обоих случаях вызывалась префиксная форма операторной функции. Это
следует иметь в виду, если вам придется работать со старыми С++-программами.
~ПРОСЫ дли текущего коНтроли _ _ _ _ _ _ __
Перегрузка операторов должна осуществляться относительно класса.
1.
Верно ли это?
Сколько параметров имеет операторная функция-член ДЛЯ реализации
2.
бинарного оператора?
3. Левый операнд для операторной функции-члена передается через _ _..
ВАЖНi
'1'
Переrрvзка операторов
с использованием функций­
не членов класса
llерегрузку оператора для класса можно реализовать и с использованием
функции, не являющейсн члеl-JOМ этого класса. Такие функции часто определяют­
ся "друзьями" класса. Как упоминалось выше. функции-не члены (В том числе и
функции-"друзья") не имеют УЮlЗателя
this.
Следовательно. если для переГРУЗI<И
бинарного оператора исrюльзуется функция-"друг". явным образом передаются
оба операнда. Если же с помощью Функции-"друга" перегружается унарный опера­
тор, операторной функции передастся один оператор. С использованием функций­
не членов класса нелыя перегружать такие операторы:
1.
2.
=. (). []
и
->.
Верно. оператор перегружается ОТНОС1Пельно класса.
Бинарная операторная функция-член имеет один П:lраметр. который получает зна­
чение правого операнда.
З.
Левый операнд для uпсраторной функции-члена передается через указатель
this.
ф
ф
:r
10
о
g
с::
х
О
()
()
~
~
о
442
Модуль9.01~ассахподробнее
Например, в следующей программе ДЛЯ перегрузки оператора
ThreeD
//
"+"
ДЛЯ класса
вместо функции-члена используется Функция-"друг".
Пере грузка оператора
"+"
с помощью ФУНI<ции-"друга".
#include <iostream>
using паmезрасе std;
class ThreeD {
int х, у, Zi // 3-мерные координаты
public:
ThreeD ()
х = у = z = О; }
ThreeD(int i, int j, int k) { х = ii
/ /
//
//
ЗАесъ фyиJCЦИJI
ТhreeD.
operator+ ()
У
j; , = k;
об'bllВ.nека \\АРУХООМ"
}
:ac.nacca
Обра~е внимание на необходимость
испо.nъзовавия в этом c.nучае ~yx параметров.
friend ThreeD operator+(ThreeD opl, ThreeD
void show ()
ор2);
i
/ / Реализация операторной функции-"друга" operator+ () .
ThreeD operator+(ThreeD ор1, ThreeD ор2)
{
Thre~D
temp;
temp.x
opl.x + ор2.х;
temp.y
орl.у + ор2. у:
ternp.,
opl.z+ op2:z;
return tempi
// Отображение координат
void ThreeD: :show()
{
cout «
cout «
cout «
х
«
", ";
у
«
", ";
Z «
"\п";
Х,
У,
Z.
С++: PYI(OBOACTBO ДМI начинающих
443
int main ()
ф
ф
{
ThreeD
а(1,
2, 3),
cout « "Исходные
а. show () ;
cout « "Исходные
Ь. show () ;
Ь(10,
10, 10),
значения
~
с;
объекта
координат
а:
g
".
I
1:
".
U
U
х
значения
Ь:
объекта
координат
О
I
~
О
cout «
"\n";
а + Ь; // сложение объектов а и
cout « "Значения КООРдИНат объекта
с. show () ;
с =
cout «
после
объектов
а,
с
а
+
Ь:
+
Ь
+
";
Ь и с
объекта с после с
=
а
с:
";
"\n":
с = Ь = а:
// демонстрация
cout « "Значения координат
с. show () ;
cout « "Значения координат
Ь. show () ;
return
с
"\n";
с = а + Ь + с: // сложение
cout « "Значен·ия координат
с. show () ;
cout «
Ь
множественного присваивания
объекта
с
после
с
объекта Ь
после
с
=
Ь
Ь
=
а:
";
а:
";
О;
Результаты ВЬLl10Шlения этой программы таковы.
Исходные
значения
координа'l'
объекта
а:
Исходные
значения
координат
объекта
Ь:
Значения
координат
объекта с после
с
=
а
+
Ь:
Значения
координа'l'
объекта с
с
=
а
+
Ь
после
1, 2, 3
10, 10, 10
11, 12, 13
+
с:
22, 24, 26
444 Модуль 9. О классах подробнее
................................................................................................................................
Значения
координат
объекта
после
с
=
Ь
а:
Значения
координат
объекта Ь после
с
=
Ь
а:
с
Как видите, операторной функции
operator+ ()
1, 2, 3
1, 2, 3
теперь передаются два опе­
ранда. Левый операнд передается параметру ор1, а правый
-
параметру ор2.
ВО МНОПIХ случаях при перегрузке операторов с помощью Функций-"друзей"
нет никакого преимущества по сравнению с использованием Функций-чЛt~нов
класса. Однако возможна ситуация (когда нужно, чтобы слева от бинарного опе­
ратора стоял объект встроенного типа), в которой функция-"друг" оказывается
чрезвычайно полезной. Чтобы понять это, рассмотрим следующее. как вы знаете,
указатель на объект, который вызывает операторную функцию-член, передает­
ся с помощью ключевого слова
При использовании бинарного оператора
this.
функцию вызывает объект, расположенный слева от него. И это замечательно
при условии, что левый объект определяет заданную операцию. Например, пред­
положим, что у нас есть нскоторый объект Т, для которого определена операция
сложения с целочисленным значением, тогда следующая запись представляет со­
бой вполне допустимое выражение.
т =
т
+ 10; //
будет работать
Поскольку объект Т стоит слева от оператора" +", он вызывает перегруженкую
операторную функцию, которая (предположительно) способна выполнить опе­
рацию сложения целочисленкого значения с некоторым элементом объекта т Но
эта инструкция работать не будет.
т =
10 +
Т;
//
не
будет работать
Дело в том, что в этой инструкции объект, расположениый слева от оператора
"+", представляет собой
целое число, т.е. зиачение встроенного типа, для которого не
определена ни одна операция, включающая целое число и объект классового типа.
Решение описанной проблемы состоит в перегрузке оператора
"+"
с исполь­
зованием двух функциЙ-"друзеЙ". В этом случае оператор ной функции явным
образом передаются. оба аргумента, и она вызывается подобно любой другой пе­
регруженной фуикции., Т.С. на основе типов ее аргументов. Одна версия оператор­
ной функции
ние, адруга.я
-
operator+ ()
будет обрабатывать аргументы объект
аргументы int-значенuе
+ объект.
+
int-Зllаче­
Псрегрузка оператора "+" (или
любого другого бинарного оператора) с использованием функций-"друзей" по­
зволяет ставить значение встроенного типа как справа, так и слева от оператора.
РС:uJизация этого решения показана в следующей программе. Здесь опредслIOТ­
ся две версии операторной функции
operator+ () для объектов типа ThreeD.
Обе они выполняют сложение целочисленного значения с каждой И3 координат
С++: PYI(OBOACTBO дl\r. начинающих
445
ThreeD-06ъекта. При этом целочисленное значение может находиться как слева,
так и справа от оператора.
ф
//
//
Перегрузка операторной функции
наборов
операндов:
(int +
operator+()
объект)
и
ДЛЯ
(объект
i
таких
+ int).
1:
#include <iostream>
using namespace stdi
Х
О
U
U
class ThreeD (
int х, у, Z; // З-мерные координаты
public:
ThreeD () { х = у = Z = О; }
ThreeD (int i, int j, int k) { х = i;
friend ThreeD operator+(ThreeD
friend ThreeD operator+(int
орl,
орl,
~
х
О
У =
j; z
int
ор2);
ThreeD
оЬр);
= k; }
// об~х~
// int
// int +
// o&ъex~
void show ()
// Эта версия обрабатывает аргументы
// ThreeD-объект + iпt-значение.
ThreeD operator+(ThreeD орl, int ор2)
(
ThreeD tempi
temp.x
орl.х +
temp.y
орl.у +
opl.z +
temp.z
return tempi
ор2;
ор2;
ор2;
/1 Эта версия обрабатывает аргументы
// iпt-значение + ThreedD-объект
ThreeD operator+(int орl, ThreeD ор2)
{
ThreeD tempi
+
446
Модуль 9. О классах подробнее
temp.x
ор2.х +
temp.y == ор2.у +
op2.z +
temp.z
return temp;
ор1;
ор1;
ор1;
// Отображение координат
void ThreeD::show()
Х,
У,
Z.
{
cout «
cout «
cout «
«
у «
z «
", ";
", ";
х
"\п";
int main ()
а(1,
ThreeD
2,
З),
cout « "Исходные
a.show();
cout «
ь =
а
значения
координат
объекта
а:
";
"\п";
+ 10; /1
объект
cout « "Значения
b.show() ;
cout «
Ь;
+
iпt-значение
координат
объекта
Ь после
Ь
а
+ 10: ";
"\п";
//
Здес. cnева o~ опера~ора cnожени. находи~с. значение
//
.С'Х'роевноЖ'О omша.
= 10 + а; // iпt-значение + объект
cout « "Значения координат объекта Ь
b.show();
Ь
return
после
Ь
О;
Вот как выглядят результаты выполнения этой програмМЫ.
Исходные
значения
координат
объекта
а:
1, 2, 3
10 +
а:
";
С++: РУКОВОДСТВО Длf;\ начинающих
Значения
координат
объекта
Ь
после
Значения
координат
объекта
Ь
после Ь
Поскольку олераторная функция
Ь
а
447
+ 10: 11, 12, 13
10 +
а:
11, 12, 13
ПСрСl'ружается дважды, мы
operator+ ()
можем предусмотреть доа способа участия целого числа и объекта типа
ThreeD
в операции сложения.
ф
ф
z
ID
О
~
О
с:
х
Использование функций-1IАрузей" ДЛЯ перегрузки
унарных операторов
с помощью Функuий-"друзей" можно перегружать и
YHapllbIe
nператоры. Но
(++)
или декремента
(--),
то ТaJюjj операторной
функции необходимо передать объект по ссылке. ПОСКОЛhКУ ссылочный пара­
метр представляет собой неЯ8НЫЙ указатель на аргумент, то изменения, внесен­
ные в lJapaMeтp, повлияют и на аргумент. Таким образом, применение ССЫ.'10'lНОГО
пара метра позволяет функции успешно инкрементировать или деl<рементиро­
вать объект, используемый в I<ачестве операнда.
Если для перегрузки операторов инкремента или декремента ИСПОЛiJзуется
Функция-"друг", ее префиксная форма принимает один паРdметр (КОТОf>ЫЙ и
является операндом), а постфиксная форма
-
два параметра (вторым является
целочисленное значение, которое не используется).
Ниже приведен код перегрузки двух форм инкремента объекта трехмерных
координат. в котором используется операторная функция-"друг"
для класса
1*
с
operator++ ()
ThreeD.
Перегрузка
помощью
необходимо
префиксной
"++"
формы оператора
функции-"друга".
использование
Для
этого
ссылочного
лараметра.
~/
ThreeD operator++(ThreeD &opl)
{
opl.x++;
opl.y++;
opl.z++;
return opl;
1*
Перегрузка
с
помощью
~
О
это потребует от программиста ДОllОлнитеЛl,НЫХ усилий. Если вы хотите пере­
грузить операторы инкремента
О
()
()
о
постфиксной формы
функции-"друга".
оператора
Для
этого
необходимо использование ссылочного
"++"
также
параметра.
*/
448
Модуль 9. О классах подробнее
ThreeD operator++(ThreeD &opl, int notused)
(
ThreeD temp =
ор1;
ор1.х++;
ор1.у++;
opl.z++i
return temp;
Вопросы ДЛЯ текущего J<ОНТРОЛЯ
_ _ _ _ _ _ __
~колько параметров имеет бинарная операторная функция-не член класса?
\.. 2. ~ должен быть передан операнд для перегрузки оператора инкремента
(++)
при ИСПОЛЬ30нании операторной функции-не члена?
з. Важным достоинством применения операторных функций-"друзей" яв­
ляется тот факт. что они позволяют в Iсачестве левого операнда использо­
•
..................................................................
8ать значение встроенного тиnа. Верно ли это?
Советы по реализации перегрузки операторов
Действие перегруженного оператора применительно к классу, для которого
он определяется, не обязательно должно иметь отношение к стандартному дей­
ствию этого оператора применительно ко встроенным С++-типам. Например,
операторы
"«"
и"»", применяемые к объектам
cout
и
cin, имеют мало общего
с аналогичными операторами, при меняемыми к значениям целочисленного типа.
Но для улучшения структурированности и читабелыюсти программного кода
создаваемый перегружеНIlЫЙ оператор должен по возможности отражать исхо­
дное назначение того или иного оператора. Например, оператор
вый для класса
ThreeD,
концептуально подобен оператору
"+", переГРУЖ.ен­
"+", определенному
для целочисленных типов. Ведь вряд ли ecТl, лоrnка в определении для класса,
1.
Бинарная операторная функция-не член имеет два параметра.
2. Для перегрузки оператора инкремента (+ +) при использовании операторной Функ­
ции·не члена операнд должен быть передан по ссылке.
З.
Верно. Применение операторных функций-"друзей" позволяет в качестве левого
операнда использовать значение встроенного типа (например,
int).
С++: PYI<OBOACTBO д/'JI начинающих
например, оператора
ратор деления
ратора
-
"+",
449
который по своему действию больше напоминает опе­
(1). Таким образом, основная идея создания пере груженного опе­
наделить его новыми (нужными для вас) возможиостями, которые, тем
не менее, связаны с его первоиачальиым назначением.
ф
ф
~
о
~
с:
Спросим у опытного программиста
х
О
()
Вопрос.
Существуют ли особенности nерегРУ1ки операторов оmношенlUI?
ОтвеТ.
Операторы отношений (например,
"=="
или
"<") также
()
можно перегружать,
причем делаn, это совсем нетрудно. как правило, перегружеЮlЗЯ операторная
функция возвращает объект класса, для которого она перегружается. Однако
iIерегружешrый оператор отношения возвращает одно из двух возможных
значений:
true или false. Это соответствует обычному применеиию этих
операторов и позволяет использовать их R условных выражениях. ВЬПlIеска­
занное справедливо и в отношении логических операторов.
Чтобы показать, как можно реализовать перегруженный оператор отно·
шения, рассмотрим пример перегрузки оператора
нам класса
/ /
"==" для уже знакомого
ThreeD.
Пере грузка
Ьооl"
оператора
"==".
ThreeD: :operator== ( ThreeD
ор2)
(
if((x == ор2.х) &&
return true;
else
return false;
(у
==
Если счи·гаn., '11"0 операторная ФУНКЦИЯ
ор2.у)
&&
oper а tor== ()
(z
ор2.
z))
уже реализована,
следующий фрагмент кода совершенно корректен.
ThreeD
а(l,
//
1, 1),
Ь(2,
2,2);
...
if(a == Ь)
else cout
cout
«
"а
«
"а равно Ь\n";
ие равно Ь\n";
На переГРУЗl<У операторов налагается ряд ограничений. Во-первых, нельзя из­
менять приоритет оператора. Во-вторых, нельзя изменять количество операндов,
принимаемых оператором, хотя операторная функция могла бы илюрироватъ
любой операнд. Наконец, за исключением оператора вызова функции (о нем речь
впереди), операториые функции не могут иметь аргументов по умолчанию.
~
О
450
МОДУЛЬ 9. О I<лассах подрОбнее
Почти все С++-операторы можно перегружать (в том числе и оператор индек­
сирования массива"
[] ",
вызова фушщии"
() ", а также оператор "->").
Операто­
ры, перегрузка которых запрещена, перечислены ниже.
*
Оператор". *" -
?
это оператор специального назначения, рассмотрение кото­
рого выходит за рамки этой книги.
Проект
шr
...,и...... ·" п~.~WШI
•• ,~",g
9.1 . "'""'-"I1а;:а
,,-r.!'o'-·Н--"­
Используя перегрузку операторов, МОЖIIО создавать классы, которые полностью
интегрируются в среду программирования С++. Опредешш необходимый набор
операций, мы разрешаем использовать в программе некоторый классовый тип по­
добно тому, как мы используем любые встроенные типы. Объекты такого
K.lacca
мы сможем тогда обрабатьшать с помощью известных операторов и включать их
Set.cpp
в выражения. Чтобы продемонстрировать создание и интеграuию
нового класса в среду С++, в этом проекте мы создадим класс
Set,
который определяет тип множества.
Но сначала уточним, что мы понимаем под множеством. Для данного ПРОСК­
та ограничимся таким определением: множество
-
это коллекция уникальных
элементов. Другими словами, множество не может содержать двух одинаковых
элементов. Порядок следования элементов в множестве несущественеll. Это зна­
чит, что множество
{А,В,С)
совпадает со множеством
{А, С, В}
Множество может быть пустым.
М ножества поддерживают некоторый набор операций. В нашем проекте мы
реализуем такие:
•
включение элемента в множество;
•
удаление элемента из множества;
• объединение множеств;
•
получение разности множеств.
Такие операции, как внесение элемента в множество и удаление элемента из
него него, не требуют дополнительных разъяснений. О других же необходимо
сказать несколько слов.
06ьедИНe1I.ие двух множеств представляет собой множество, которое содержит
все элементы из объединяемых множеств. (При этом дуБЛИРОDание элементов не
С++: руководство ДМI начинающих
451
разрешено.) Для выполнения операции объединения множеств будем использо­
вать оператор" +".
Разность множеств
-
это множество, содержащее те элементы И3 первого
множества, которые не являются частью второго. дЛЯ выполнения операции вы­
читания множеств будем ИСПОЛЬЗОDать оператор
жества
S2
S 1 и S2, то при
будут удалены из множества
S3
=
"-".
Например, если даны мно­
выполнении следующей инструкции элементы множества
Sl,
а результат помещен 8
S3.
SI - S2
Если множества
Класс
S 1 и S2 одинаковы, то S3
Set также будет
будет пустым множеством.
В!(ЛlOчатъ функцию
isMember (),
которая определяет,
присутствует ли заданный элемент в данном множестве.
Над множествами можно выполнять и другие операции. Некоторые из них
предложены для разработки в разделе "Тест для самоконтроля по модулю
9".
Кро­
ме того, вы можете самостоятельно расширить набор ощ~раций над множествами.
Для простотw разрабатываемый здесь класс
символов,
110
Set предназначен ДЛЯ хранения
те же самые Принципы можно использовать для создания класса
множества, способного хранить элементы и ДРУПIХ типов.
Последовательность действий
1.
Создайте новый файл с именем
2.
Начните создание типа множества с объявления класса
const int MaxSize
=
Set. срр.
100;
class Set
int len; // количество членов
char members[MaxSize); 1/ На этом
//
/*
Функция
find ()
Set.
закрыта,
массиве и будет
построено множество.
поскольку она
вне класса Set. */
int find(char ch); /1 выполняет
поиск
не используется
элемента
public:
11
Построение
Set()
//
nycTqro
{ len = О; )
Получение
множества.
количества
элементов
в
множестве.
452
Модуль 9. О классах ПОАробнее
int getLength() { return len; }
void showset()i // отображение множества
Ьооl isMember(char ch);
// проверка членства
Set operator + (char ch); // добавление элемента
Set operator -(char ch); // удаление элемента
Set operator +(Set
Set operator -(Set
оЬ2);
оЬ2);
..
// объединение множеств
// получение раэности множеств
};
Каждое множество хранится в сhаr-массиве
members. Реальное количе­
len. Максимальный
размер множества определяется константой MaxSize, которая устанОВ.'1ена
ство членов в множестве содержится в переменной
равной числу
100. (При
Конструктор
Set ()
необходимости это значение можно увеличить.)
создает пустое множество, которое не содержит Ч.1е­
нов. Создавать ДРУП1е конструкторы или определять конструктор копии
для класса
Set
нет необходимости, поскольку для целей данного проепа
вполне достаточно побитовой копии. Функция
значение переменной
getLength ()
возвращает
len, в которой хранится текущее количество элемен­
тов в множестве.
З. Определение функций-членов начните с закрытой функции
/*
Функция
воэвращает
индекс
параметром ch, или -1,
int Set: :find(char ch)
int i;
элемента,
если
find ( ) .
эаданного
таковой не
найден.
*/
for(i=O; i < len; i++)
if(memЬers[i] == ch) return i;
return -1;
Эта функция определяет, является ли элемент, переданный параметру
ch,
членом данного множества. Если заданный элемент найден, функция воз­
вращает его индекс, в противном случае
-
число
-1. Функция find () опре­
Set. Закрытая
делена закрытой, поскольку она не используется вне класса
функция-член может быть вызвана только другими функциями-членами
того же класса.
С++: PYI<OBOACTBO д,л91 начинающих
4.
В1UIючите в состав членов класса ФУНКЦИЮ
453
showset () , которая отобража­
ет содержимое множества.
ф
ф
// Отображение содержимого множества.
void Set:: showset () (
cout « "( ";
for(int i=O; i<leni i++)
cout « members[i] « " ";
cout «
5.
:J:
\D
О
~
1:
)(
О
U
U
~
"}\п";
Добавьте функцию
О
isMember (),
которая определяет, является ли задан­
ный символ членом данного множества.
/*
Функция
является
В
возвращает
членом
противном
значение
true,
если
символ
ch
множества.
случае
возвращается
значение
fa1se. */
Set::isMember(char ch) (
if(find(ch) != -1) return truei
return fa1se;
Ьоо1
Эта ФУНКЦИЯ, чтобы определить, является ли символ
ch членом данного
find () . Если такой символ был найден в
множестве, функция isMember () возвращает значение true, в против­
ном случае - значение fa1se.
множества, вызывает функцию
6.
Теперь перейдите к определению операторов множества. Начните с опера­
тора
"+".
Перегрузите оператор
"+"
для объектов типа
Set
так, чтобы он
позволял включать в множество новый элемент.
// Добавление нового элемента
Set Set::operator +(char ~h)
Set newset;
if(len == MaxSize)
cout « "Множество залолнеНО.\Л"i
return *this; // возвращает существующее
newset
*this; //
дублирует
I
а
в множество.
~
I
«3
множество
существующее множество
454
Модуль 9. О «лассах подробнее
// Проверяем, существует ли уже в множестве такой
// элемент.
if(fmd(ch) == -1) { // Если элемент не найден, его
// можно добавлять в множество.
// Включаем новый элемент в новое множество.
newset.members[newset.1en] = Chi
newset.1en++i
return newseti //
возвращает модифицированное множество
Эта операторная функция требует некоторых пояснений. Прежде
Bcero,
она создает новое множество, в котором будет храниться содержимое ис­
ходного, а также символ·, заданный параметром ch. Но перед добавлением
нового символа проверяется, достаТОЧIfО ли свободного места в множестве
для принятия нового члена. Если достаточно, то содержимое исходно­
го множества дублируется в объект newset типа Set. Затем вызывается
фУIIЮ{ИЯ
find ( ) , чтобы
определить, не является ли кандидат в члены уже
частью данного множества. Если нет, символ
newset
и модифицируется переменная
len.
ch
добавляется в множество
В любом случае в результате
выполнения операции сложения возвращается новое множество
newset;
а исходное множество не изменяется.
7~ Перегружаем оператор
"-" так,
чтобы с его помощью можно было удалить
элемент из множества.
// Удаляем элемент из множества.
Set Set::operator -(char ch)
Set newset;
int i = find(ch); // Переменная i получит значение -1,
// если заданный элемент не будет
// найден в множестве.
// Копируем и уплотняем оставшиеся элементы.
for(int j=O; j < leni j++)
if(j != i) newset = newset + members[j]i
return newset;
Выполнение этой операторной функции начинается с создания нового пусто­
го множества. Затем вызывается функция
find ( ) , чтобы
определить индекс
С++: РУ'<ОВОДСТВО ДЛЯ начинающих
455
заданного символа ch в исходном множестве. Вспомните, что ФУНlщия fin d
возврашает значение
()
-1, если СИМВОЛ ch не является членом множества. Затем
в новое множество помещаются элеменТhI исходного множества за исключе­
нием элемента, индекс которого совпадает с полученным в результате выпол­
нення функции fiлd
элементы
( ) . Таким образом, новое множество будет содержать все
исходного, но без символа ch. Если символ ch не является частью
исходного множеСТDа, нопое множество будет эквивалеН11-Ю исходному.
8.
Clюва перегружаем операторы
"+"
и
"-", но теперь для реализации объеди- .
неиня и разности множеств.
Добавляем
1/
из второго.
в
~
g
t:
х
О
()
()
~
о
// Объединение множеств.
Set Set::operator +(Set оЬ2)
Set newset = *this; 1/ Копируем
//
$
первое
множество
первое множество.
уникальные
элементы
for(int i=O; i < ob2.1en; i++)
newset = newset + ob2.memЬers[i);
return newset; /1
Возвращаем модифицированное множество.
// Разность множеств.
Set Set::operator -(Set оЬ2)
Set newset = *this; // Копируем
первое множество.
// "Вычитаем" элементы из второго множества.
for(int i=O; i < ob2.1en; i++)
newset = newset - ob2.memЬers[i);
return newset; //
Возвращаем модифицированное множество.
Как видите, эти операторные функции используют ранее определенные
версии операторов
"+" и "-".
При выполнении операции объединения сна­
чала создается новое множество, которое содержит элементы первого мно­
жества. Затем в него добавляются элементы второго множества. Поскольку
оператор
"+" добавит элемент только в том случае, если
он еще не является
частью множества, то в результате получим объединение (без элементов­
дубликатов) двух множеств. При получении разности множеств с помощью
I
i
~
J
456
МОДУЛЬ 9. О I<лассах подробнее
оператора
9.
"-" из
первого множества удаляются совпадающие элементы.
Ниже приводится полный код программы, включающей определение клас­
са
Set
и функции
main (),
которая деМОliстрируетего использование.
/*
Проект
Класс
9.1.
множества
для
символов.
*/
#include <iostream>
using namespace std;
const int MaxSize
=
100;
class Set
int len: // количество членов
char members[MaxSize]: // На этом массиве и будет
// построено множество.
/*
Функция fщd()
закрыта,
поскольку
она
не используется
вне класса
Set. */
int find (char c11); / / выполняет
поиск злемента
public:
// Построение
Set{) { len =
пустого множества.
О;
}
// Получение количества элементов
int getLength() { return len; }
в
множестве.
void showset(); // отображение множества
bool isMember{char ch);
// проверка членства
Set operator +(char ch); //
Set operator -(char ch); //
Set operator +(Set
оЬ2);
//
добавление элемента
удаление элемента
объединение множеств
С++: pyr<OBOACTBO для начинающих
Set operator -(Set
оЬ2);
//
457
получение разности множеств
};
ф
ф
/* /*
Функция возвращает индекс элемента,
параметром
ch, или -1,
int Set: :find (char ch)
int i;
если
таковой не
заданного
найден.
*/
~
о
~
с
)(
О
()
()
for(i=Oi i <
~
i++)
== ch) return i;
1еп;
if(meтbers[i]
О
return -1;
// Отображение содержИмого множества.
void Set::showset() {
cout « "{ ";
for(int i=O; i<len; i++)
cout « memЬers[i] « " ";
cout «
/*
"}\п";
Функция
является
возвращает
членом
значение
true,
если символ
ch
множества.
В противном случае
возвращается
значение
fa1se. */
bool Set::isMember(char ch)
if(find(ch) != -1) return true;
return false;
// Добавление нового элемента
Set Set::operator +(char ch)
Set newset;
в
множество.
if(len == MaxSize)
cout « "Множество заполнено.\п";
return *this; // возвращает существующее
множество
458
МОДУЛЬ 9. О классах подробнее
newset = *this; 11
// Проверяем,
11
дублирует
существующее
существует ли уже
множество
в множестве
такой
элемент.
i f (find (.ch) == -1)
{ / / Если элемент не найден,
1/
можно добавлять
его
в множество.
// Включаем HOBbrn элемент в новое множество.
newset.members[newset.len1 = Chi
newset.len++i
return newseti 11
возвращает модифицированное множеств"
// Удаляем элемент из множества.
Set Set::operator -(char сЬ)
Set newseti
int i = find(ch); 1/ Переменнаq i
получит
значение
.-1,
// если заданньrn элемент не будет
11
1/
найден
в
множестве.
Копируем и уплотняем остаВJUиеся элементы.
for(int j=Oi j < leni j++)
if(j != i) newset = newset + members[j];
return newseti
// Объединение множеств.
Set Set::operator +(Set оЬ2)
Set newset = *thisi 1/ Копируем
первое множество.
/1 Добавляем в первое множество уникальные
/1 из второго.
for(int i=O; i < ob2.1eni i++)
newset = newset + ob2.members[i]i
return newseti //
элементы
Возвращаем модифицированное множество.
С++: РУI<ОВОДСТВО МЯ начинающих
// Разность множеств.
Set Set::operator -(Set оЬ2) {
Set new5et = *thi5i // Копируем
459
ф
ф
первое множество.
Н
// "Вычитаем элементы из второго множества.
for(int i=O; i < оЬ2.1еп; i++)
new5et = new5et - ob2.member5[i]i
:I:
\о
О
g
с::
х
О
U
U
о
~
return new5et; //
Возвращаем модифицированное множество.
// Демонстрация использования класса Set.
int main ()
// Создаем пустое множество.
Set 51;
Set 52;
Set 53;
51
sl
51
;
51 + 'В' ;
51 + 'С' ;
sl +
cout «
'А'
"Множество
51
СИМВОЛОВ А В
sl.5howset()
после
С:
добавления
";
i
cout «
"\п";
cout «
"Тестирование
членства
с
помощью
isMember () . \п" i
if(sl.isMember('B'»
cout « "в - член множества sl.\n"i
else
cout « "в - не член множества sl.\n";
if(sl.isMember('T') )
cout « "т - член множества sl.\n"i
else
функции
О
460
Модуль 9. О классах подробнее
cout «
cout
«
"Т
-
не
член множества
51.\п";
"\п";
51 = 51 - 'В';
cout « "Множество 51
51.5how5et();
после
51
51-
'В':";
51 = 51 - 'А';
cout « "Множество 51
51.5how5et() ;
после
sl
51 -
'А':
51 = 51 - 'С';
cout « "Множество
51.5how5et();
после
51
51-
'С':";
после
добавления
А В С:
";
после
добавления
А
";
после
53
cout «
~1
"\п";
51
51 + 'А';
51
51 + 'В';
51
51 + 'С';
cout « "Множество 51
51. 5how5et () ;
cout
«
52
52
52
52 + 'А';
52 + 'Х';
52 + 'W';
"\п";
cout « "Множество 52
52.5how5et();
cout «
";
Х
W:
"\п";
53 = 51 + 52;
cout « "Множество 53
53.5hOW5et();
51 + 52: ";
С++: PYI<OBOACTBO ДЛ91 начинающих
53 = 53 - 51;
cout « "Множество 53
после
53
53 -
51:
461
-
";
:Ф
5З.5hОW5еt();
:Ф
:1:
:10
cout
«
cout
«
"\п";
1§
:0
"Множество
:0
52 = 52 - 52;
52.5how5et();
cout
«
"\п";
52
52
+
'С';
52
:с:
:х
после
//·очищаем
52 - 52: ";
52
:u
:u
52
:0
~~
:0
// Помещаем символы АВС в обратном
// порядке.
52
52 +
52
52 + 'А';
'В';
cout «
"Множество 52
52.showset();
после
добавления
С В
А:
";
О;
return
Результаты выполнения этой программы таковы.
Множество
51
Тестирование
в
Т
-
член
не
после
добавления
членства
с
символов А
помощью
функции
В С:
i
5Меmbе r
, 51.
множества
член
Множество
множества
51.
'в'
:
А
С
'А'
:
С
}
• С' :
}
после
Множество
после
51
51
51
Множество
51
после
добавления
А В
С:
~
В
С
Множество
52
после
добавления
А
Х
W:
А
Х
W
Множество
53
53
после
53
С
Х
W
после
53
Множество
после
51
51
51
-
51
51
51
Множество
51
53
+ 52:
А
В
51:
Х
W }
{
А В
() •
С
}
462
МОДУЛЬ 9. О I<ЛОССОХ подробнее
=
Множество
з2
после
s2
Множество
s2
после
добавления
52 -
s2:
С
{
В
}
А:
{
С
В
А
}
JQСТААЯ самоКОНТРОASLдА.МOAV~
•
1.
Что представляет собой кон.структор копи И И В каких случаях он вызы вает­
ся? Представьте общий формат конструктора к~пии.
2.
Поясните, что происходит при возвращении объекта функцией? В частно­
сти, в каких случаях l1ызьшается его деструктор?
3.
Дано следующее определение класса.
class
Т
(
int i, j;
public:
int sum()
return i + j;
};
Покажите, как следует переписать фушщиlО sum () ,чтобы она ИСПОЛЬЗОВit­
ла указатель
this.
4.
Что такое структура? Что такое объединение?
5.
На что ссылается
6.
Что представляет собой friепd-функция')
7.
Представьте общий формат перегруженной бинарной операторной фУНК­
* this
в коде функции-члена?
ции-члена.
8.
Что необходимо сделать, ~побы разрешить выполнение операЦIIЙ Над объ­
ектом класса и зиачением встроенного типа?
9.
Можно ли перегрузить оператор
"?"?
Можно ли менять приоритет опера­
тора?
10. Для
">",
класса
Set,
определенного в проекте
!).1,
определите операторы
"<"
и
чтобы с их помощью можно было узнать, является ли одно множеств()
подмножеством или супермножеством другого. Пусть оператор
"<"
DОЗ­
true, если левое МНОЖI~(ТRО является подмножеством
false 8 противном случае. ПУС,ть оператuр ">" 130:1вращает значение true, если левое множество является супермножеством
правого, и значение false в противном случае.
вращает значение
правого, и значение
С++: PYI<OBOACTBO ДЛЯ начинаюЩих
463
11. Для класса Set определите оператор "&", чтобы с его помощью можно было
получить пересечение двух множеств.
12.
Самостоятельно попытайтесь добавить в класс
Set
Например, попробуйте определить оператор
для получеliИЯ строгой
"1"
другие операторы.
дuзьюнкцuu двух множеств. Результат примеliения строгой дизъюнкции
должен включать элемеJlТЫ, которых нет ии в одном из рассматриваемых
НазаметlW
n. .
О
~
О
с:
)(
U
()
о
Ответы на эти вопросы можно найти на Web-странице данной
книги по адресу:
:t:
ID
О
множеств.
-tc
ф
ф
http://www . osborne.
сот.
<
::.:
О
МОДУЛЬ
10
Наследование
10.1. Понятие о наследовании
10.2. Управление доступом к членам базового класса
10.3. Использование защищенных членов
10.4. Вызов конструкторов базового класса
10.5. Создание многоуровневой иерархии
10.6. Наследование неСКОЛЬК\fХ базовых классов
10.7. Когда выполняются функции конструкторов и деструкторов
10.8. Указатели на производные типы
10.9. Виртуальные функции и полиморфизм
10.10. Чисто виртуальные функции и абстрактные классы
466
Модуль10.Наследование
в этом модуле рассматрнваются три средства С++, которые непосредствен­
но связаны с объектно-ориентированным программированием: наследование,
виртуальные функции и полиморфизм. Наследование
позволяет одному классу наследовать характеристики
-
это свойство, которое
npyroro.
Используя насле­
дование, можно создать общий класс, который определяет черты, присущие мно­
жеству связанных элементов. Этот класс затем может быть унаследован ДРУПfми,
узкоспециализированными I<лассами с добаВЛf'нием в каждый из tlИХ своих, уни­
кальных особенностеj;'l. На основе наследования построено понятие виртуальной
функции, причем виртуальная функция Я8Ляется одним из факторов поддержки
полиморфизма, который реализует принцип объектно-ориентированного про­
граммирования "один интерфейс
-
множество методов".
ВАЖНОI
1("'Поыяж ие О наСАААО'ОЫИИ
..
в стандартной терминологии языка С++ класс, который наследуется, называ­
ется базовым. Кnacc, который наследует 6азовый класс, называется nроuзводным.
Следовательно, производный класс предстаВ.'lЯет собой специализированную
версию 6азового класса. Производный класс наследует все члены, определенные
. в базовом, и добавляет к ним собственные уникальные элементы.
Язык С++ поддерживает механизм наследоваllИЯ, который предусматривает
возможность в объявление одного класса встраивать другой класс. Для этого
достаточно при объявлении пронзводного класса указать базовый. Лучше вее­
го это пояснить на примере. В следующей ПР(lграмме создается базовый класс
TwoDShape,
в котором хранятся основные параметры (ширина и высота) дву­
мерного объекта, и ПРОИЗВОДНЫЙ класс Triangle, создаваемый на основе класса
Two DShape. Обратите особое внимание на то, как оБЪШ1ляется класс т r iang 1 е.
11
Простая иерархия классов.
#include <iostream>
#include <cstring>
using namespace stdi
// Кла~с определения
class TwoDShape {
public:
double widthi
double height;
двумерных
объектов.
С++: PYI<OBOACTBO AMI начинающих
467
void showDim ()
cout « "Ширина и высота равны " «
width « " и " « height <~ "\п";
ф
:s:
:I:
о
a::I
~
};
U
о
::r:
// ,Класс Trianqle ВЫВОДИТСR из класса
class Triangle : public TwoDShape ( //
//
public:
char style[20);
double area() {
return width * height / 2; //
ТWoDShape.
Обратите внимание на
синтаксис объявления.
,
Класс
может
Trianqle
// обращаТЬСR к членам xnасса
// TwoDShape
//
void showStyle ()
cout « "Этот
так,
как если бы они
были частью класса
Trianqle.
{
треугольник"
«
style «
"\п";
};
int main ()
Triangle tl;
Triangle t2;
tl.width = 4.0;
//
tl.height = 4.0; //
//
Shape.
strcpy(tl.style,
t 2•• wi d t h = В. О ;
t2.height = 12.0;
strcpy(t2.style,
Все члены класса
объектам xnасса
Trianqle
Trianqle,
которые унасле~овавы от
"равнобедренный");
"лряыIугольны'');;
~ocтynвы
~аже те,
класса ТWoD-
468
Модуль 10. Наследование
cout « "Информация о треугольнике tl:\n";
tl.showStyle();
t1.showDim();
cout « "Площадь равна" « tl.area() « "\n";
cout « "\n";
cout « "Информация о треугольнике t2:\n";
t2.showStyle();
t2 . showDim () ;
cout « "Площадь равна" « t2.area() « "\n";
return
О;
Вот как выглядят результаты выполнения этой программы.
ИНформация
Этот
треугольнике
t1:
треугольник равнобедренный
Ширина
Площадь
и высота равны
равна
Информация
Этот
о
о
Площадь
треугольнике
и высота
равна
Здесь класс
и
4
8
треугольник
Ширина
4
t2:
прямоугольный
равны
8
и
12
48
TwoDShape определяет атрибуты "обобщенной" двумерной гео­
ме-грической фИIуры (например, квадрата, прямоуroльника, треугольника и т.д.).
В классе
Triangle создается специфический тип объекта класса TwoDShape, в
данном случае треугольник. Класс Triangle содержит все элементы клас­
са TwoDShape и, кроме того, поле style. функцию area () и ФУНКЦИЮ
showStyle (). в переменной style хранится описание типа треугольника,
ФУНКЦИЯ area () выч.исля~т И возвращает его площадь, а ФУНКЦИЯ showSty1 е () отобр~жает данные о типе треугольника.
Ниже приведен синтаксис, который I·iспользуется в объявлеиии
Triangle, чтобы
сделать его ПРОИЗВОДНЪ1м от класса
класса
TwoDShape.
class Triangle : public TwoDShape {
Этот синтаксис довольно прост для запоминания и использованИЯ. Если.один
класс наследует другой, то имя базового класса (В данном случае это масс
TwoDShape) указывается после имени ПРОИЗ80ДНОГО (В данном
Triangle), причем имена классов разделяются двоеточием.
случае это масс
С++: PYI(OBOACTBO ДЛЯ начинающих
469
Поскольку класс Triangle включает все члены базового класса,
TwoDShape, он может обращаться к членам width и height внутри метода
area (). Кроме того, внутри метода main () объекты tl и t2 могут непосред­
ственно ссылаться на члеllЫ width и height, как если бы они были частью
класса Triangle. Включение класса TwoDShape в класс Triangle схемати­
чески показано на рис. 10.1.
ф
s:
:r
О
ш
~
U
о
I
~Sh~{
width
helght
showDimO
Trlangle
styte
areaO
showSlyleO
Рис.
10.1. Схе.матuчес"оеnредстав­
ленuе класса
Triangle
Несмотря на то что класс TwoDShape является базовым для класса Triangle,
он совершенно независим и автономен. То, что его в качестве базового использу­
ет производный класс, не означает невозможность применения его самого.
Общий формат объявления класса, который наследует базовый класс, имеет
такой вид:
class имя_производного_класса
// тело производного класса
доступ имя
базового
класса
Здесь элемент доступ необязателен. При необходимости его можно выразить
одним из спецификаторов доступа:
public,.private
или
protected
(подроб­
нее о них вы узнаете ниже в этом модуле). Пока (для простоты изложения) при
объявлении производного класса будем использовать спецификатор
Если базовый класс наследуется как риЬ 1 i с-класс, все его
public.
publ i с-члены стано­
вятся рubliс-членами производного класса.
Основное достоинство наследования состоит в том, что, создав базовый класс,
который определяет общие атрибуты для множества объектов, его можно ис­
пользовать для создания любого числа более специализированных производных
классов. В каждом производном классе можно затем точно "настроить" собствен­
ную классификацию. Вот, например, как из базового класса
TwoDShape
вывести ПРОИЗВОДIiЫЙ класс, который ИНКЩICулирует прямоугольники.
можно
МОДУЛЬ 10. Наследование
470
// Класс прямоугольников Rectangle, производный
// от класса TwoDShape.
class Rectangle : public TwoDShape (
public:
// Функция возвращает значение true, если
// прямоугольник является квадратом.
bool isSquare () (
if(width == height) return truei
return falsei
// Функция возвращает значение
double area ()
return width * heighti
площади
прямоугольиика.
Класс
Rectangle включает класс TwoDShape и добавляет к нему функцию
isSquare (), которая определяет, является ли прямоугольник квадратом, и
ФУllКЦИЮ area () , вычисляющую площадь прямоугольника.
Доступ к членам класса и наслеАование
Как разъяснялось в модуле
8,
члены класса часто объявляются закрытыми,
чтобы предотвратить их несанкционированнос использование и внесение
,,3-
менениЙ. Наслсдование класса не отменяет ограничения, связанные с закрыт:.!м
дос.тупом. Таким образом, несмотря на то, что производный класс включает IJce
члены базового класса, он не может получить доступ к тем И3 них, которые объ­
явлены закрытыми. Например, как показано в следующем коде, если перемен­
width и height являются рrivаtе-членами в классе TwoDShape, то класс
Triangle не сможет получить к ним доступ.
ные
//
//
Доступ
к рrivаtе-членам не
производным классам.
class TwoDShape
// Теперь это рrivаtе-чnены.
double width;
double height;
public:
void showDim ( )
передается
по
наследству
С++: руководство ДМ! начинающих
cout «
"Ширина
width «
и
высота
"
и
" «
height «
471
-
равны
" «
"\п";
:Ф
:::;;:
:::t
:~
};
:0
:<[
:Ф
.<
:u
// Класс Triangle выводится из класса TwoDShape.
class Triangle : public TwoDShape {
public:
char style [20] ;
double area () {
return width * height / 2; //
void showStyle ()
cout « "Этот
треугольник"
Ошибка:
:0
::!:
кenъЗR поn~
/ /
ПРIDIОЙ дос'l'УП IC за!cpы'l'tolн
//
чnевак базового xnасса.
«
style «
"\п";
};
Класс
Triangle
не скомпилируется, поскольку ссылка на члены
width
и
height внутри функции area () приведет I< нарушению прав доступа. Посколь­
ку width и height - закрытые члены, они ДоступнЪJ только для членов их соб­
ствеююго класса. На производные классы эта доступность не распространяется.
На первый взгляд может по казаться. что невозможность доступа к закрытым
членам базового ЮJaсса со стороны производного
нако это не так, поскольку в С++
проблемы. Одна из них
-
-
серьезное ограничение. од­
Ilpe/,tycMoTpeHbJ возможности решения этой
рrоtесtеd-члены, о которых пойдет речь в следую­
щем разделе. Вторая возможность - использование открытых свойств и функ­
ЦИЙ, позволяющих получить доступ к За!<РЫТЫМ данным. Как бьuIO показано в
предыдущих модулях, С++-программисты обычно предоставляют доступ к за­
крытым членам класса посредством открытых функций. Функции, которые обе­
спечивают доступ к зшсрытым данным, называются ФУllКЦuя..мu доступа (иногда
аксессорнымu фУНКЦllЯAШ). Перед вами
-
новая версия класса
торую как раз и добавлены функции доступа к
//
Доступ
к
закрытым данным
#include <iostream>
с
TwoDShape.
членам width и height.
помощью функций доступа.
в ко­
472
Модуль 10. Наследование
#include <cstring>
using namespace std:
// Класс двумерных объектов.
class TwoDShape {
// Закрытые члены класса.
double width:
double heighti
public:
void showDim ( )
cout « "Ширина и высота равны " «
width « " и " « height «
"\n":
// Функции ~ocтyna к членах width и heiqht.
double getWidth() { return width: }
double getHeight() { return height:
void setWidth(double w) { width = w: }
void setHeight(double h) { height = h:
};
// Класс Triangle выводится из класса TwoDShape.
class Triangle : public TwoDShape {
public:
char style[20]:
double area()
{ //
//
Дпк полученик значений ширины и высоты
з~есь ИСПОnЪЗУЮТСR функции ~оступа.
return getWidth() * getHeight()
void showStyle() {
cout « "Этот треугольник" «
};
int main ()
Triangle tl:
/ 2;
style «
"\n":
С++: руководство ДЛЯ начинающих
473
Triangle t2;
tl.setWidth(4.0);
tl.setHeight(4.0) ;
strcpy(tl.style, "равнобедренный");
t2.setWidth(8.0);
t2.setHeight(12.0) ;
strcpy(t2.style, "прямоугольный") ;
cout « "Информация о треугольнике tl:\n";
tl.showStyle();
tl . showDirn () ;
cout « "Площадь равна" « tl.area() « "\п";
cout « "\п";
cout « "Информация о треугольнике t2:\n";
t2.showStyle();
t2.showDirn();
cout « "Площадь равна" « t2.area() « "\п";
return
О;
}
~npocы ДЛЯ текущего l<онтролЯ ________
1.
Как в программе указать, что базовый класс наследуется производным
классом?
2.
Включает ли производный класс члены своего базового класса?
З. Имеет ли производиый класс доступ к закрытым членам базового класса?·
1.
Имя базового класса указывается после имени производного. Имена этих классов
должны разделяться двоеточием.
2.
Да, производный класс включает члены своего базового класса.
З.
Нет, производный класс не имеет доступа L< закрытым членам базового класса.
ф
:s:
:r:
О
ID
~
u<
о
:I:
МОДУЛЬ 10\ Наследование
474
Спросим у опытного программиста
Вопрос.
Я СЛ6lшtlll, что "pozpa.wнllcmw 11" Jr:JЫI(J! Java IIСnOllьзуюm mepмШI.' "cynер­
класс" u "nодкласс". Как зmu ItU!pAfШl.' сооmносяmCJI с C++-термUНOIIozuеu?
Ответ.
То, что jаvа-программист назbIВ~ет суперклассом, С++-программист на­
зывает базовым классом. Надо ска.10iТ1., что оба набора терминов приме­
няются во многих ЯЭblJ<ах. Однако 1:1 этой книге мы будем придерживаться
стзндартнойтермююлопш С++. (Замечу. что в С# применительно к клас­
сам также исполыуют термины "базовый" и "ПРОИЗВОДIIЫЙ".)
ВАЖНОI
..
1[1'1 Xnравдеыие А OCтyaQМК ЧАенам
базового класса
Как разъяснялось выше, если один класс наследует другой, <!Лены базового
класса становятся членами производиого. Однако статус доступа членов базо­
вого класса в производном классе определяется спецификатором доступа, ис­
пользуемым для наследования базового класса. Спецификатор доступа базового
класса выражается одним из ключевых слов:
public, private или pr;otectl~d.
Если спецификатор доступа не указан, то по умолчанию используется специфи­
катор
pri vate, если речь идет о наследовании типа class. Если же наслсдуе7СЯ
тип s t ruct, то при отсутствии явно заданного с.пецифш\атора доступа по умол­
чанию используется спецификатор public. Рассмотрим рамификацию (ра.lвет­
вление) использования СI1СЦИфИl(аторов publ ic или pri vate. (Спецификатор
protected описан J) следующем разделе.)
Если базовый класс наследуется как рuЫ i с- класс, все его publ i с-члены с raНО8ЯТСЯ publ ic-членаМI1 производного класса. Во всех случаях pr i va tе-члены
базового класса остаются закрытыми в рамка,'{ этого класса и не доступны ДЛЯ
членов производного. Например, н следующей программе рuЫ i с-члены класса
В становятся рubliс-членами класса
D.
Следовательно, они будут доступны и
для других частей программы.
//
Демонстрация
рubliс-наследования.
#include <iostream>
using namespace stdi
class В {
int i, ji
С++: руководство д,л>1 начинающих
public:
void set(int а, int Ь) ( i
а; j
void show() { cout « i « " " «
=
475
-
Ь;
j «
"\п" i
:ф
}.
:s
.::t:
:0
};
·ID
:0
class D : public
В
Х;
//
:I
}
cout «
10; //
// i
:~
OTкpЫТWN способом.
//
int ki
public:
D(int х) ( k
void showk ()
:~
:Ф
:<
~ Здесь класс В ваcnедуетс.
( //
k «
Ошибка!
Чиев
"\п";
i
}
закрыт 8
раихах класса В и
ДОСТУП к веку не разрешен.
)i
int rnain ()
оЬ
D
(3) ;
Получаем доступ к членам базового класса.
ob.set(1, 2); //
ob.shoW()i
//
оЬ
. showk ()
Получаем доступ
//
i
Здесь
к членам базового класса.
используется член
производного
класса.
return
О;
Поскольку функции
set ()
и
show ()
являются открытыми
нами класса,В, их можно вызывать для объекта типа
поскольку переменные
i
и
j
Dв
(public) чле­
rnain (). Но
функции
определены как рrivаtе-члены, они остаются за­
крытыми в рамках своего класса В. Поэтому следующая строка кода превращена
в комментарий.
// i
Класс
=
10; //
//
Ошибка!
доступ
D не может
Член
к
i
нему
(private).
не
в
рамках
класса
В и
разрешен.
получить доступ к закрытому члену ЮIасса В.
Противоположностью открытому
тое
закрыт
(public)
наследованию является закры­
Если базовый класс наследуется как рrivаtе-класс, все его
Модуль 10. Наследование
476
рubliс-члены становятся рrivаtе-членами производноro класса. Например,
следующая программа не скомпилируется, поскольку обе функции
show ()
теперь стали рrivаtе-членами класса
вызывать из функции
main ().
//
//
закрытого
Использование
(Эта
программа
не
способа
derived,
set ()
и
и поэтому ИХ.иельзя
наследования.
скомпилируется.)
#include <iostream>
using namespace std;
class В {
int i, j;
public:
void set (int а, int Ь) {i
а; j
Ь;
void show () {cout « i « " " « j «
"\п";
};
// Открытые элементы
class D : private В
int к;
public:
D(int х) ( k
void showk ()
х;
класса
/1
11
В
становятся
закрытыми
в
классе
D.
~ Тепер~ &пасс В наcnеАуетс.
закpwтым способом.
}
cout «
k «
"\n"; }
};
int main ()
D
//
//
оЬ(3);
Тепер~ ФУВJЩIOI
set ()
и аЬо.
()
не АОСТУПlUol М"
частей проrpамкы вне &пасса D.
ob.set(l, 2); //
ob.show();
//
return
Ошибка,
доступ
к функции
Ошибка,
доступ к функции
set()
show()
невозможен.
невозможен.
О;
Итак. если базовый класс наследуется как рrivаtе-класс,
ны становятся закрытыми (р r
i v а t е)
ero открытые чле­
членам и производного класса. Это означа-
С++: PYI<OBOACTBO ДЛ)! начинающих
477
ет, что они доступны для членов производного класса, но не доступны для других
частей программы.
ф
:s::
:J:
о
ID
О
ВАЖНОI
af
I-ИсI:lОАЬ308aWИе защI4IJ~"hI"ых
<
u
·0
::!:
членов
Как упоминалось выше, ~акрытый член базового класса недоступен для про­
изводного класса. Казалось бы, это означает, что, если производному классу ну­
жен доступ к члену базового класса, его следует сделать открытым. При этом
придется смириться с тем, что открытый член будет доступным для любого дру­
гого кода, что иногда нежелательно. К счастью, таких ситуаций можно избежать,
поскольку С++ позволяет создавать защuще1/1/ые 1iле1/Ы. Защищенным является
член, который открыт для своей иерархии классов, но закрыт вне этой иерархии.
з.iщищенныЙ член создается с помощью модификатора доступа
protected.
При объявлении protected-члеН(l он, по сути, является закрытым, но с одним
исключением. Это исключение вступает в силу, когда защищенный член наследу­
ется. В этом случае защищенный член базового класса становится защищенным
членом производного класса, а следовательно, и доступным дЛЯ ПРОИЗDОДНОГО
класса. Таким образом, используя модификатор доступа protected, можно соз­
давать закрытые (для "внешнего мира") члены класса, но вместе с тем они будут
наследоваться с возможностью доступа со стороны производных классов. Специ­
фикатор
protected можно также использовать со структурами.
Рассмотрим простой при мер использования защищенных членов класса.
//
Демонстрация
защищенных членов
класса.
#include <iostream>
using namespace std;
class В {
protected: / / ЗдеClt перекеННlolе i и j - защищенные чnевы.
int i, j; // Хотя эти члены закрыты в рамках класса В,
// тем не менее они доступны для класса D.
public:
Ь;
void set (int а, int Ь) { i
а; j
void show() { cout « i « " " « j « "\п";
};
Модуль 10, Наследование
478
class D : public В {
int k;
public:
// Класс D ножет обращатьCR х
// посхоnъху ОНИ не private-,
void setk () { k = i *j; }
void showk () { cout «
k «
членах
i
и
хпасса В,
j
\
а рrоteсtеd-чnеиы.
"\п";
}
);
int main ()
D
оЬ;
ob.set(2, 3); / /
оЬ. show () ;
//
ob.setk();
оЬ. showk ()
return
ОК,
ФУНКЦИЯ
ОК,
ФУНКЦИЯ
set()
sho\oJ ()
открыта
открыта
в
классе В.
в
классе В.
i
О;
Поскольку класс В унаследован классом D открытым способом (т.е. как pub1 iC-!Ulас<;), и поскольку члены i и j объявлены зашищеНlibtми в классе В, ФУНК­
Ц'IlЯ setk'() (член класса о) имеет полное право на доступ к ним. Если бы члены
i и j были объявлены u классе В закрытыми, то класс D не'мог бы обращатъся к
ним, и эта программа не скомпилиропалась бы.
Если базовый класс наследуется как рubliс-класс, защищенные
ted)
(protec-
члены базового класса становятся защищенными членами производного
класса, Т.е. доступными для производного класса. Если же базовый класс насле­
дуется закрытым способом (т.е. с использованием слецификатора
щищенные члены этого базового класса становятся закрытыми
private), за­
(private) чле­
нами производноro класса.
Спецификатор защищенного доступа может стоять в любом месте объявления
класса, но, как правило, рrоtесtеd-члены объявляются после (объявляемых по
умолчанию)
pri vаtе-члснов
и перед рubliс-членами. Таким образом, самый
обший формат объявления класса обычно ВЫI'JlЯДИТ так.
class
//
имя_класса
{
рrivаtе-члены по умолчанию
С++: PYI<OBOACTBO д,ля начинающих 479
-
protected:
// рrоtесtеd-члены
public:
/ / рubliс-члены
Ф
:s:
1:
о
ID
~
};
.Ф
Напомню, что раздел защищенных членов необязателен.
Ключевое слово protected можно использовать не только для придания членам
класса статуса "защищенности", но и в (ачестве специфИJ<атора доступа при насле­
довании базового класса. Если базовый класс наследуется как защищенный, все его
открытые и защшценные члены становятся защищенными членами производнаго
класса. Например, если (<Ласс D наследует класс В таким способом
class D : protected
В
{,
то все незакрытые члены класса В станут защищенными членами класса о.
~ПРОСЫ ДЛ'i1 текущего коНтрОЛS1----___.__
1. Если базовый класс наследуется закрытым способом, то его открытые чле­
ны становятся закрытыми членами производного класса. Верно ли это?
2.
Может ли закрытый член базового класса стать открытым благодаря ме­
ханизму наследования?
3.
Какой спецификатор доступа нужно использовать, чтобы сделать член
класса доступым в рамках иерархии классов, но закрытым для осталь­
ного "мира"?·
1.
Верно: если базовый класс наследуется закрытым способом, его открытые члены
стаНОВЯТСЯ закрытыми членами производного класса.
2.
Нет, закрытый член всегда остается закрытым в рамках своего класса.
з.
Чтобы сделать член класса доступым в рамках иерархии классов, но закрbпым для
остального "мира", нужно использовать спецификатор
protected.
.<
:u
:0
:I
480
Модуль10.НаследоваНие
Спросим у опытного программиста
Вопрос. Хотелосъ бы обобщитъ сведения об иСnОЛЪЗОВflIlии спецификаторов pt.:.b-
lic, protected и private.
Ответ.
При объявлении члена класса открытым (с использованием ключевого
слова
public)
к нему можно получить доступ из любой другой часп
программы. Если член класса объявляется закрьггым (с помощью специ­
фИI<a1'ора
private), к нему могут получать доступ только члены того же
)( закрытым членам базового класса не имеют доступа
класса. Болсе того,
даже производные классы. Если же ЧЛf'н класса объявляется защищенным
(рrоtесtеd-членом), к нему
MOryr
получа'ГЬ доступ только члены того
же или прои:шодных классов. Таким образом. спецификатор
позволяет наследовать члены, но остаRляет их закрытыми
protecte j
Rрамках иерар­
хии классов.
Если базовый класс наследуется с использованием ключсвого слова put,l-
ic, его publ iс-членw стаllОВЯТСЯ риЫ i с-членами производного класса, а
- рrоtесtеd-членами ПРОИЭRОДНОГО класса. ЕСЛl1
базовый класс наследуется с использованием спецификатора protectec!,
его public- и рrоtесtеd-члены становятся рrоtесtеd-членами пр('­
его рrоtесtеd-члены
изводного класса. Если же базовый класс наследуется с использование:\{
ключевого слова private, его publicpr i va tе-членами производноro класса.
и рrоtесtеd-члены становятся
Во всех случаях
pr i va tе-члены
базового класса остаются закрытыми в рамках этого класса.
Конструкторы и наследование
в иерархии классов как базовые, так и производные классы могут иметь
собственные конструкторы. При этом возникает важный вопрос: какой КОН­
структор отвечает за создание объекта производного класса? Конструктор ба­
зового, производного класса, или оба одновременно? Ответ таков: конструк­
тор базового класса создает часть объекта, соответствующую базовому клас­
су, а конструктор производного класса
-
часть объекта, соответствующую
производному классу. И это вполне логично, потому что базовый класс "не
видит" элементы производного класса или не имеет доступа к ним. Поэтому
их "конструкции" должны быть раздельными. В предыдущих примерах
сы
опирались на конструкторы
K.lac-
по умолчанию, создаваемые автоматически
средствами С++, и поэтому мы не сталкивались с подобной проблемоЙ. Но на
практике большинство классов имеет конструкторы, и ВЫ должны знать, как
выходить из этой ситуации.
С++: PYI<OBOACTBO ДМI начинающих
481
Если конструктор определяется только в ПРОИЗ80ДНОМ классе. процесс создания
объекта несложен: просто создается объект производного класса. Часть 06ъекта, со­
ответствующая базовому классу. создается автоматически с помощью конструктора
по умолчанию. Например, рассмотрим перера60танную версию класса
8 которой явно определяется конструктор. Здесь членstуlе
Triangle,
объявлен private-
члеliОМ, поскольку теперь он устанавливается КШIСТРУКТОРОМ.
//
Добавление
конструктора
в
класс
Класс двумерных
U
о
Triangle.
объектов.
class TwoDShape {
// Теперь эти члены закрытые.
double width;
double height;
public:
void showDim ()
cout « "Ширина и высота равны " «
width « " и " « height «
1/
Функции доступа
к
"\п";
закрытым членам класса.
double getWidth() { return width; }
double getHeight() { return height; }
void setWidth(double w) { width = w; }
void setHeight(double h) { height = h;
};
// Класс Triangle - производный от класса TwoDShape.
class Triangle : public TwoDShape {
char style(20]; // Теперь это рrivаtе-член.
public:
// Конструктор класса Triangle.
Triangle(char *str, double w, double h)
//
Инициализируем "ба.зовYJD"
часть
{
lC.JIacca,
О
111
:I:
#include <iostream>
#include <cstring>
using паmеэрасе std;
11
,
ф
s
:I:
'3!. е.
час~,
482
Модуль10.Наследование
// оnpедenR~
setWidth(w);
setHeight(h);
11
11
1/
в
Тrianqlе-об~~те
классом ТvoDShape.
Инициa.nиэируеи "ПРОИ8ВоДИ}'JD" час'1'Ъ· xnacca,
'.1'. е . час'1'Ъ ,
оnpедеnя~ в Тrianqlе-об~~'.1'е ~оикре'.1'КWК '.1'ИПОХ
треу:rозп.RИ~а.
strcpy(style, str);
double area ()
return getWidth() * getHeight() / 2;
void showStyle()
cout « "Этот треугольник" «
style «
"\п";
);
int main ()
Triangle tl ("равнобедренный", 4.0, 4.0);
Triangle t2("прямоугольный', 8.0, 12.0);
cout « "ИНформация
t1.showStyle();
tl.showDim();
coи~
«
"Площадь
о
треугольнике
равна"
«
tl:\n";
t1.area() «
"\п";
cout « "\п";
cout « "Информация о треугольнике t2:\n"i
t2.showStyle();
t2.showDim();
cout « "Площадь равна" « t2.area() « "\п";
return
О;
Здесь конструктор класса
Triangle
инициализирует наследуемые им члены
класса TwoDShape, а также собственное поле style.
С++: руководство ДМI начинающих
,
483
Если КОПСТРУЮ'ОРЫ определены и в 6а.10ВОМ, и в пронзводиом классе, процесс
создания объектов несколько усложняется, ПОСКОЛl)ку
Dэтом случае должны вы­
полниться конструкторы обоих классов.
:Ф
:s
::1:
:~
:0
:<[
:Ф
ВАЖНОI
111'1 ВЫ308..к.owc;rР~la.QроВJ)азоа.Qr..Q
класса
Если ба.10ВЫЙ класс имеет конструктор, то ПРОИЗDОДИЫЙ класс должен яшю
вызваТI) его для инициализации той части "своего" объекта, которая соответству­
ет ба.10ВОМУ классу. ПРОИЗВОдИЫЙ класс может пызывать конструктор, опреде­
ленный в его базовом классе, используя расширенную форму объявления кон­
структора ПрОИ380ДНОГО класса. Формат такого расширенного объявления имеет
следующий вид.
конструктор производного
-
//
тело
-
класса (список аргументов)
-
.
конструктор_базов ого_класса (список_аргументов)
конструктора
лроизводного класса
Здесь элемент КОНСТруКТОр_базового_ кла сса представляет собой имя базо­
вого класса, наследуемого ПРОИЗ80ДНЫМ классом. Обратите внимание на IfСПОЛЬ­
З0вание двоеточия, которое разделяет объявление конструктора производного
класса от конструктора ба3080ГО. (Если класс наследует несколько баЗ0ВЫХ клас­
сов, то имена их KOHCTPYI(TOPOB l'Iеобходимо перечислить через запятую в виде
списка.) С помощью элемента список_аргументов задаются аргументы, необ­
ходимые конструктору в соответствующем классе.
В следующей программе показано, как передаются аргументы KOHCTpYKTory
баЗ0ВОГО класса. Здесь определяется КОНСТРУI<торкласса
инициализирует члены данных
//
Добавление
width
конструктора
в
#include <iostream>
#include <cstring>
using патезрасе stdi
// Класс двумерных объеКТОБ.
class TwoDShape {
// Закрытые члены класса.
dCiuble widthi
и
TwoDShape, который
height.
класс
TwoDShape.
:<
,()
:0
:I
484
МОДУЛЬ 10. Наследование
double heighti
public:
// Конструктор класса TwoDShape.
TwoDShape(double w, double h) {
width = Wi
height = hi
void showDim ( ) {
cout « "Ширина и
width «
//
высота
"и
., «
height «
равны
" «
"\п";
Функции доступа.
doub~e getWidth() { return widthi }
double getHeight() ( return height;
void setWidth(double w) { width = Wi }
void setHeight(double h) { height = hi
}i
// Класс Triangle выведен из класса TwoDShape.
class Triangle : public TwoDShape (
char style[20]i // Это рrivаtе-член.
public:
// Конструктор класса Triangle.
Triangle(char *str, double w,
double h) : TwoDShape(w, h)
//
( //
З~есъ .МSU8ae~c.
XOKCТPYК~OP класса ТvoDShape.
strcpy(style, str)i
double area() (
return getWidth()
void showStyle()
cout « "Этот
*
getHeight() / 2;
{
треугольник
" «
style «
"\п";
С++: PYI<OBOACTBO дl\fI начинающих
485
};
ф
:s;
:I:
int main ()
Triangle
Triangle
а
CII
tl("равнобедренный",
t2("прямоугольный",
i
4.0, 4.0);
8.0, 12.0);
u
а
I
cout « "Информация о треугольнике t1:\n";
t1. showStyle () ;
tl.showDim();
cout « "Площадь равна" « tl.area() « "\п";
cout « "\п";
cout « "Информация о треугольнике t2:\n";
t2. showStyle () ;
t2.showDim();
cout « "Площадь равна" « t2.area() « "\п";
return
О;
Здесь конструктор
трами
w и h,
Triangle ()
вызывает конструктор
который инициализирует свойства
ющими значениями. Класс
Triangle
TwoDShape () с параме­
width и height соответству­
больше не инициализирует эти значения
сам. Ему остается УСТaJЮВИТЬ только одно значение, уникальное для класса тре­
угольников, а именно члеи
TwoDShape
style
(тип треугольника). Такой подход дает классу
свободу иы60ра среди IЮЗМОЖllЫХ способов построения подобъек­
тов. Более того, со временем мы можем расширить функции класса
TwoDShape,
но об этом расширении ранее созданные ~роизводные классы не будут "знать",
что спасет существующий'код от разрушения.
Конструктор ПРОИЗВОДIIОГО класса может вызвать конструктор любого фор­
мата, определенный 8 базовом классе. Реально же выполнится тот конструктор,
параметры которого будут COOTBeTC~BOBaTЬ переданным при выэове аргументам.
Например, вот как выглядят расширенные версии классов
gle,
11
TwoDShape и Tr ian-
которые включают дополнительные конструкторы.
КЛасс
TwoDShape
iinclude <iostream>
#include <cstring>
с
дополнительным
конструктором.
МОДУЛЬ 10. Наследование
486
usiлg
11
лаmеsрасе
st.di
Класс двумерных
объектов.
class TwoDShape (
11
Это рrivаtе-члены.
double widthi
double height;
public:
11
Конструктор класса ТwoDShape по уиоnчавию.
TwoDShape() {
width = height = 0.0;
11
Конструктор класса ТwoDShape с параиетрани.
TwoDShape(double w, double h)
width = w;
height = h;
11
Конструктор об~кта,
TwoDShape(double
width = heigHt
х)
=
{
у KOTOPO~O ширина равна высоте.
{
Х;
void showDim() (
cout « "Ширина и высота равны " «
width « " и " « height «
11
Функции доступа
"\л";
к закрытым членам класса.
double getWidth() { rеturл width; }
double getHeight () { rеturл height; }
void setWidth(double w) { width = w; }
void setHeight(double h) { height = h;
};
11
~,acc треугольников,
class
Тriалglе
производный от класса
: public TwoDShape (
TwoDShape.
С++: РУI<ОВОДСТВО ДЛЯ начинающих
char style[20]; //
public:
Теперь
487
это рrivаtе-член.
ф
s
/*
KOHCТPY~TOP по укоnчаиио.
Он автоматически вызывает
~оиструхтор по укоnчаниа xnасса ТwoDShape.
Triangle () (
strcpy(style,
//
*/
~
о
"неизвестный"):
I
Кокструхтор С трeIOl паракетрами.
КОНСТРУХТ6Р ра.аRобедре_оrо треyrоnьниха.
Triangle(double х) : TwoDShape(x) (
strcpy(style, "равнобедренный");
double area ()
return getWidth() * getHeight()
void showStyle()
cout « "Этот
со
U
Triangle(char *str, double w,
double h) : TwoDShape(w, h)
strcpy(style, str):
/ /
:I:
О
/ 2;
{
треугольник"
«
style «
"\п":
}:
int main ()
Triangle t1:
Triangle t2("прямоугольный", 8.0, 12.0):
Triangle tЗ(4.0):
tl = t2:
cout « "Информация
t1. showStyle () :
t1. showDim () :
о треугольнике
tl:
\п":
488
МОДУЛЬ
10. Наследование
cout «
"Площадь
cout «
"\п";
" «
равна
t1. area () «
"\п";
cout « "Информация о треугольнике t2: \п";
t2.showSty1e();
t2.showDim();
cout « "Площадь равна" « t2.area() « "\п";
cout «
"\п";
cout « "Информация о треугольнике t3: \п";
t3.showStyle();
tЗ.shоwDim() ;
cout « "Площадь равна" « tЗ.~rеа() « "\п";
cout «
return
"\п";
О;
Вот как выглядят результаты ВhIПОШIСНИЯ этой версии программы.
Информация
Этот
и
Площадь
равна
о
равна
Информация
о
Площадь
и
t2:
прямоугольный
равны
8
и
треугольнике
высота
равна
12
12
48
треугольник
Ширина
и
треугольнике
и высота
Площадь
8
48
треугольник
Ширина
t1:
прямоугольный
высота равны
ИнФормация
Этот
треугольнике
треугольник
Ширина
Этот
о
8
tЗ:
равнобедренный
равны
4
и
4
С++: РУКОВОДСТВО ДМ1 начинающих
489
~просы ДЛЯ текущего КоНтроля ________
ф
:s:
:I:
а
CD
1.
2.
Каким образом производный класс выполняет конструr<Тор базового
<
u
Можно ли передать параметры конструктору базового класса?
I
а
з. Какой конструктор отвечает за инициализацию "базовой" части объ­
екта производного класса: опрелеленный ПРОИЗВОДIiЫМ или базовым
__. u____________
~__________________________________________
классом?*
Проект
10.1.
:Rосwирение~ВО3МО"081rей:lМасеа
Vehicle
I TruckDemo. срр
ию, что класс
Цель этого проекта
-
создать полкласс класса
к разработке которого мы приступили 11 модуле
Vehicle
Vehicle,
8.
Напом-
ИlIкапсулирует основные данные о транспортных сред­
ствах (возможное количество переВОЗИМblХ пассажиров, общая вместимость то­
пливны?, резервуаров и расход топлива). Теперь мы можем использовать класс
Vehicle в качестве отправной точки для разработки более спеЦllализированных
классов. Например, uозьмем такой ТIIП траНСПОРТIIОГО сред.ства, как грузовик.
Одной из важнейших его технических характеристик является грузовмести­
МОС1Ъ. Итак, для создания класса
Truck используем u l<ачестве базового класс
Vehicle и добавим к его содержимому переменную для хранения грузовмести­
мости. Таким образом, в этом проекте мы созд.адим класс Truck на основе класса
Vehicle, сделав перемеНllые базопого I<ласса закрытыми, но позаботившись о
доступе к ним посредстnом ш<сессорных функций.
Последовательность действий
1.
Создайте файл
цию класса
TruckDemo. срр и
Vehicle из модуля 8.
скопируйте в него rlOслещrюю реализа­
1.
При определении конструктора производного класса указывается имя конструкто­
2.
Да, конструктору базового класса можно передать параметры.
ра базового класса.
з.
~
класса?
За шшциализаЦI1Ю "базовой" части объекта ПРОИЗ80ДНОГО клас.са отвечает кон­
структор, определенный базовым классом.
490 , МОДУЛЬ 10. Наследование
2.
Создайте класс
Truck.
// Используем класс Vehicle для создания
// специализированного класса Truck.
class Truck : public Vehicle {
int cargocapi // грузовместимость в фунтах
public:
// Конструктор класса Truck.
Truck(int р, int f, int m, int
с)
Vehicle(p, f, m)
{
cargocap
С;
// Функция доступа
int get_cargocap()
cargocap.
cargocapi }
к члену данных
retu~
} i
Здесь класс
Truck наследует класс Vehicle с добавлением члена cargoсар. Таким образом, класс Truck включает все общие атрибугы транс­
портных средств, определенные в классе Vehicle, и нам остается люпь
добавить элементы, которые уникальны ДЛЯ данного Iсласса.
з. Приведем полный текст программы демонстрации класса
//
//
Создание подкласса
из класса
Truck,
Truck.
эызеденного
Vehicle.
#include <iostream>
using паmезрасе stdi
11 Объявление класса Vehicle.
class Vehicle (
11 Это рrivаtе-члены.
int passengersi // количество пассажиров
int fuelcapi // вместимость топливных резервуаров
//в литрах
int mpg;
// расход горючего
public:
// Конструктор класса Vehicle.
Vehicle(int р, int f, int m)
passengers = р;
в
километрах на литр
С++: РУI(ОВОДСТВО ДЛЯ начинающих
491
fuelcap = f;
mpg = m;
ф
s
:t
~
// Функция
int range ()
вычисления
максимального
~<
пробега.
{ return mpg * fuelcap;
}
u
о
::r:
// Функции доступа к членам класса.
int getyassengers () ( return passengers;
int get_fuelcap() ( return fuelcap;
int get_mpg() { return mpg; }
};
// Использование класса Vehicle
// специализированного класса Truck.
class Truck : public Vehicle {
int cargocap; // грузовместимость в
public:
// Конструктор класса Truck.
Truck(int р, int f, int т, int
фунтах
Vehicle(p, f, m)
с)
(
cargocap
//
Функция
с;
доступа
int get_cargocap()
к члену данных
cargocap.
return cargocap; }
};
int main ()
// Создаем несколь~о "грузовых"
Truck semi(2, 200, 7, 44000);
Truck pickup (3, 28, 15, 2000);
int dist = 252;
cout «
«
cout «
"Полуторка может
перевезти
semi.get_cargocap() «
"После
заправки
объектов.
она
"
может
"
фунтов
груза.\п";
проехать
максимум
"
492 Модуль10.Наследование
................................................................................................................................
«
semi.range() «
cout «
«
" километров.\п";
" « dist
"Чтобы проехать
"
километра,
полуторке
неОбходимо
" «
dist I semi.get_mpg() «
.. литров топлива.\п\п";
cout « "Пикап может перевезти " « pickup.
get_cargocap() «
" фунтов груза.\п";
cout « "После заправки он может проехать максимум ..
«
cout «
«
pickup.range() « " километров.\п";
"Чтобы проехать " «
dist
" километра, nикапу необходимо " «
dist 1 pickup.get_mpg() «
литров
11
return
4.
топлива.\п";
О;
Результаты выполнения этой программы таковы.
Полуторка может
После
44000
перевезти
заправки
она
может
Чтобы проехать
252
километра,
фунтов
проехать
груза.
максимум
1400
киломе­
тров.
литров
необходимо
З6
топлива.
Пикап может
После
полуторке
перевезти
заправки
он
2000
может
фунтов
проехать
груза.
максимум
420
киломе­
тров.
Чтобы проехать
тров
5.
252
километра,
пикапу
необходимо
16
ли­
топлива.
Из класса
Vehic1e
можно вывести множество друrnх типов классов.
Например, при использовании следующеН схемы можно создать класс 8не­
дорожных транспортных средств, в котором предусмотрено хранение тако­
го атрибута, как высота дорожного просвста.
11
Создание класса внедорожных транспортных средств.
class OffRoad : public Vehicle {
С++: PYI(OBOACTBO ДМ) начинающих
int groundClearancei //
//
public:
//
высота дорожного
493
-
просвета
в дюймах
.ф
:s
.:х:
:0
:ш
:0
} i
:4:
:Ф
.<
:u
Главное
-
понимать, что, создап базовый класс, определяющий общие па­
раметры объекта, можно сформировать множество специализированных
классоп путем Iщследования базового. В каждый производный класс доста­
точно добавить уникальные атрибуты. В этом и состоит суть наследования.
ВАЖНi
D
СоздаWИ~OJ:OVровweaой
иерархии
До сих пор мы исполЬ..,опали простые иерархии. состоящие только из базо­
вого и ПРОНЗВОДного классов. Но можно построить иерархии, которые содержат
любое количество уровней наслсдоuаНI1Я. Как упоминалось выше, один произ­
водный класс ВlIолне допустимо использовать в качестве базового для другого.
Например, из трех классов (А, В I1 С) С может быть производным от В, который.
в свою очередь, может быть проIJзводныM от А. В подобной ситуации каждый
производныif класс наследует содержимое всех своих баЗОRЫХ классов. В данном
случае класс С наследует ВСС члены классов В и А.
Чтобы понять, какую ПОJlЬ;lУ можно получить от МlJогоуровневой иерархии,
рассмотрим следуlOШУЮ программу. В ней .ПРОИ3ВОДlIЫЙ (от
тr i
.
ang le
TwoDShape)
используется в качеств\: базового для создания еще одного npоизво-
Дного класса С именем
Класс
члены классов
11 добавляет собственное поле
ColorTr iangle.
Triangle и TwoDShape
которое содержит ЦВСТ треуголышка.
//
класс
Многоуровневая
иерархия.
#include <iostream>
#include <cstring>
usiлg лаrnеsрасе stdi
// Класс двумерных объектов.
class TwoDShape {
// Это private-чЛены.
ColorTriangle
наследует все
color,
:0
;::1:
494
МодуЛЬ10.Наследование
double width;
double height;
public:
//
KOHCTPY~TOP по умолчанию.
TwoDShape() (
width = height = 0.0;
1/, KOHCTpY~TOP класса TwoDShape
TwoDShape(double w, double h) (
width = w;
height = h;
// Конструктор объекта,
TwoDShape(double х) (
width = height = Х;
~
параметрами.
у которого ширина равна высоте.
void showDirn() (
cout « "Ширина и высота равны " «
width « " и " « height «
"\n";
// Функции доступа к членам класса.
double getWidth() { return width; J
double getHeight() ( return height;
void setWidth(double w) { width = W; }
void setHeight(double h) { height = h;
};
// Класс Triangle - производный от
class Triangle : public TwoDShape
char style[20]; // рrivаtе-член
public:
/*
Конструктор
по
~OHCTPY~TOP по
Triangle ()
{
умолчанию.
Он
класса
TwoDShape.
автоматически вызывает
умолчанию класса
TwoDS.hape.
*/
С++: РУКОВОДСТВО ДЛЯ начинающих
strcpy(style,
495
"неизвестный"):
// Конструктор с тремя параметрами.
Triangle(char *str, double w,
double h) : TwoDShape(w, h)
strcpy(style, str):
11
Конструктор
Triangle(double
strcpy(style,
раВНОбедренных
х)
: TwoDShape(x)
(
"равнобедренный"):
double area ()
return getWidth()
void showStyle()
cout « "Этот
треугольников.
*
getHeight() / 2;
{
треугольник"
«
style «
"\n"i
}i
11
Класс цветных треугольников.
class ColorTriangle : public Triangle ( // Класс ColorТrianqle
1 / Hacпeдye~ JШ&сс Trianqle, хо~PJoIЙ :в CВOlD
11
очереДlt Hacneдye'l' IUI&CC Т1foDShape.
char color[20]:
public:
ColorTriangle(char *clr, char *style, double w,
double h) : Triangle(style, w, h)
strcpy(color, clr)i
// Функция отображения цвета.
void showColor() {
cout « "цвет" « color «
}i
"\п":
496
Модуль 10. Наследование
int main ()
ColorTriangle
ColorTriangle
tl("синий",
"прямоугольный",
t2("красный",
8.0, 12.0);
2.0, 2.0);
"равнобедренный",
cout « "Информация о треугольнике t1:\n";
t1.showStyle();
t1.showDim();
t1.showColor();
cout « "Площадь равна" « t1.area() « "\n";
cout «
"\n";
cout « "Информация о треугольнике t2:\n";
t2.showStyle(); // Объех~ xnасса ColorTriangle Mo.e~
t2 . showDim () ;
/ / вызывать функции, опреде.nеJUUlе
// в ием саком,
t2.showColor(); // а тахже фуихции, опреде.nеииые в базовых
// x.naccax.
cout «
return
"Площадь
равна"
«
t2.area() «
"\n";
О;
Эта программа генерирует такие результаты.
Информация
Этот
о
треугольнике
треугольник
Ширина и высота
Цзет
равна
Информация
8
и
12
о
48
треугольнике
треугольник
Ширина
Цвет
равны
синий
Площадь
Этот
t1:
прямоугольный
и
высота
t2:
равнобедренный
равны
2
и
2
красный
Площадь
Благодаря
равна
2
наслеДОВанию класс
определенные классы
Triangle
ColorTriangle может
и TwoDShape, добавляя
использовать
paHt'e
только ту инфор.\lа­
ЦИЮ, которая необходима для собственноro (специального) применения. В этом
и состоит ценность наследования: оно позволяет многократно I1СПОЛl,зовать од­
нажды СОЗ;J,анный код.
С++: руководство ANil начинающих
497
................................................................................... - .......................................... .
Этот пример иллюстрирует еш.е ОДИII важНblЙ МОМСНТ: ссли конструктору ба­
зового класса требуются параметры, все произnодныс КЛаССЫ должны передавать
эти параметры "по иерархии", нсзависимо оттого, нужны ЛИ эти парамстръr само­
О
му производному классу.
i
u
ВАЖНОI
51·И Н,QСАеДОВQНие нескОА. ЬКИХ
базовых классов
в С++ произподный класс одновременно может Н:1СJ1СДОШLТЬ два или больше
базовых классов. Например,
класса
81
и
n
этой корuткой программс класс О t!аследует 06а
82.
// Пример использования нескольких базовых клаССОЕ.
#include <iostream>
using патеsрасе stdi
c1ass 81 (
protected:
int Х;
public:
void showx()
{ cout «
х
«
"\л";
}
{ cout «
у
«
"\п";
}
} i
c1ass 82
{
protected:
int У;
public:
void showy ()
};
11
Наследование двух
с1азз
В1,
D: public
public:
/* Члены
а
х
и
у
защищены в
базовых
классов.
{ 11 Класс D наследует
// одновремевно и класс В1 и класс В2.
public
доступны,
классах
yoid set(int i, int j)
} i
Q)
sх
{
В2
пос~ольку
В1
и
82.
х
=
i;
они
*/
У
== j i
не
закрыты,
о
I
Модуль10.Наследование
498
int main ()
D
оЬ;
ob.set(10, 20) ;
ob.showx() ;
оЬ. showy () ;
return
11
1/
11
член
класса
D
член
класса
вl
член
класса
В2
О;
}
Как ВИДНО IIЗ этого при мера, чтобы обеспечить наследование несколышх
базовых клаССО8, необходимо через запятую l1ере'lНСJlJП'l> их имена в виде СI!И­
ска. При этом нужно указать спецификатор ДОСТУllLl для каждого наследуемо­
го баЗ080ГО класса.
ВАЖНОI
ПР Кадо ВЫI1QAblsuo:rС8.ф~ЦI4И
конструкторов и деструкторов
Базовый или ПРОИ:'\ВОДIJЫЙ кnасс (или оба ОДНОlJрсмешю) :'ЮГУТ содержать
конструктор и/или деструктор. Важно rlOIIИМi\'IЪ IIОрЯДОК, В котором ВЫlIOЛIIЯЮТ­
ел эти ФуН1ЩИИ. При использовании
MCXaIll13i1la
ШIСJlедования оБЫЧIIО ВОЗНlll<а­
ет два важных вопроса, связанных с l(онструкторами и деструкторами. Перrщй:
в каком ПОрЯДI<С вызываются конструкторы базового и IIРОIlЗВUДIЮГО КЛ<1ссе,в?
Второй: в каком IIорядке 13ызьшаются деструкторы базuвого и IlРОИЗВОДllОГО
классов? Чтобы ответить IШ эти вопросы, рассмотрим простую программу.
#include <iostream>
bsing namespace std;
class В
public:
В()
( cout «
-В()
( cout «
"Построение
"Разрушение
базовой
базовой
части
части
объекта.\п";
объекта.\п";
)
)
);
class D: public
public:
D () { cout «
В
(
"Построение
произво.IlНОЙ
части
объекта. \п";
}
С++: PYI<OBOACTBO для начинающих
{ cout «
-о()
"Разрушение
лроизводной
части
499
объекта.\п";
ф
};
:s;
:I:
О
ID
i
int main ()
о
u
оЬ;
//
//
о
:I:
Никаких действий,
кроме
создания и
разрушения объекта оЬ.
return
О;
Как отмечено в комментариях функции
main (),
эта программа лишь создает
и тут же разрушает объект оЬ, который имеет тип о. При выполнении программа
отображает такие результаты.
Построение
базовой части объекта.
Построение
производной
части
объекта.
Разрушение
производной части
объекта.
Разрушение
базовой
части
объекта.
Судя по результатам, сначала выполняется конструктор класса в, а за
ним
-
конструктор класса о. Затем (по причине немедленного разрушения
объеl<та оЬ в этой программе) вызывается деструктор класса О, а за ним
-
де­
структор класса В.
Результаты вышеописанного эксперимента можно обобщить следующим обра­
зом. При создании объекта производноro )(]lacca сначала вызывается конструктор
базового класса, аза ним
-
конструктор производноro класса. При разрушении объ­
екта производного класса сначала вызывается его "родной" деструктор, а за ним
-
деструктор базового класса. Другими словами, конструкторы вызываются в порядке
происхождения классов, а деструкторы
-
в обратном порядке.
При многоуровневой иерархии классов (т.е. в ситуации, когда производный
масс становится базовым классом для еще одного производного) при меняет­
ся то же общее правило: конструкторы вызываются в порядке происхождения
классов, а деструкторы
-
в обратном порядке. Если некоторый класс наследует
несколько базовых классов одиоuременно, вызов конструкторов, заданных в спи­
ске наследования этого ПРОIIЗВОДIIOГО класса, IIРОИСХОДИТ в направлении слева
направо, а вызов деструкторов
-
справа налево.
500
МОДУЛЬ 10. Наследование
~ПРОСЫ ДIIЯ Teкy~ero КОНТРОЛЯ
1.
--------
Можно ли производный класс использовать в качестве базового для дру­
гого производного класса?
2. 13
каком порядке (е точки зрения иерархии массов) вызываются КОН­
структоры при создании объекта?
З. В каком порядке (с точки зрения иерархии классов) вызываются деструк­
•
...
n .______________________________
.-. _______________________ ___
торы при разрушении объекта?
Спросим у опытного программиста
Вопрос. Почему конструкторы гыз •• гаются 8 порядне nроuсхождеНIlJI классов,
а деструктор ••
Ответ.
-
8
06pamHOAf порядке?
Вполне· JIОГИЧIЮ, что функции коtlС1РУКТОров выполняются В порядке
происхождешш их классов. Поскольку базовый класс "ничего не знает" ЮI
n
KaJ(OM
ПРОИЗDОJ\НОМ классе, операции по иницимизаЦЮ1, которые e~y
нужно ВЬLПолН/пь, не зависят от операций ИНИЩiализации, выполняемых
ПРОИЗIЮДПЫМ I(лассuм, но, возможно, создают предварительные условия
Д1IЯ последуlOЩr.Й работы. Поэ"гuму конструктор базового класса должеч
выллнятъсяя первым.
Аналогичная логика I1РИСУТСТВУет и в том, что деструкторы вьmолняются
11 поряД](е, обратном порядку происхождения. классов. ПОСI<ОЛЬКУ базовый
класс лежит в основе производного класса, разрушение базового класс,а
подразумевает
разрушение
производного.
Следовательно, деструктор
производного класса имеет смысл вызвать до того, как объект будет пол­
ностыо разрушен.
1.
Да, производный
Krracc можно ИСllОJl,ьзоватъ в качестве базового для дрyroго произ­
водного класс.а.
2.
Конструкторы выыыаютсяя в порядке происхождения классов.
З. Деструкторы вызываю'гся в порядке, обратном порядку ПроисхождеюfЯ классов.
С++: РУКОВОДСТВО ДМ1 начинающих
501
i- ""озаТ8 А '4"0 "роизводНЫ&-tИ,аы
Прежде чем переходить к виртуальным функциям и понятиlO полиморфизма,
необходимо рассмотреть один важный аспект использования указателей. Указа­
тели на базовые и производные ~accы связаны такими OTI-юпrеIlИЯМИ, которые
не свойственны указателям других типов. КЗJ< правило, указатель одного типа не
может указывать на объект друтого типа. ОД.liако указатели на базрвые классы и
объекты производных классов
исключения из этого правила. В С++ указатель
-
на базовый класс такжс можно использовать для ссылки на объект любого клас­
са, выведенного из базового. Например, предположим, что у нас есть базовый
класс В и класс О, который выведен I1З класса В. Любой указатель, объявленный
как указатель на класс В, можно использовать также для ССЫJШИ на объект типа о.
Следовательно, после этих объявлений
в
*р;
В В_оЬ;
D о_оь;
//
//
//
указатель
на
объект типа
объект
типа
объект
типа D
В
В
обе следующие инструкции абсолютно заКОНIIЫ:
р
&В_оЬ;
//
р указывает
р
&О_оЬ;
/*
Р указывает на объект
который
на объект
является
выведенным из
типа В
типа
О,
объектом,
класса В.
*/
Любой указатель на базовый класс (В данном примере это указатель р) мож­
но использовать для доступа только к тем частям объекта производноro класса,
которые были унаследованы от базового класса. Так, в этом примере указатель р
можно использовать для доступа ко вссм элементам объекта
из объекта в _оЬ. Однако
0_ оЬ,
выведенным
« элементам, которые составляют СПСl1ИФИ4ССКУЮ "над­
стройку" (Над базой, Т.е. над базовым классом В) объекта О_оЬ, доступ с помо­
щью указателя р получить нельзя.
Кроме того, необходимо понимать, что хотя "базовый" указатель можно ис­
пользовать для доступа к объектам любого производного тина, обратное утверж­
дение нсверно. Другими словами, ИСПОЛl>зуя указатель на ПРОll3ВОДНЫЙ класс,
нельзя получить доступ к объекту базового типа.
Как вы уже Зllаете, указатель инкрементируется и декремснтируется от­
носителыiO своего базового типа. Следовательно, если указат(>ЛI) на базовый
класс используется для доступа
I(
объекту производtlОГО тина, иш<рементиро­
вание или декрементирование I-rС заставит его ссылаться на слсдующий объект
производного класса. Вместо этого он будет указывап> ("по его мнению") на
ф
:s:
:I:
О
tD
О
~
U
О
:::r::
502
МОДУЛЬ 10. Наследование
следующий объект базоного класса. Таким .)6разом, ннкремеllтирование или
декрементнрование указателя на базовый класс следует расценивать как не­
корректную операцию, ссли этот указатель IJСlJользуется для ссылки на объ­
ект произподного класса.
Тот факт, что указатель на базовый тип можно ИСПОЛЬ30lJап, для ССЫЛКI1 на
люБОI';'! объект, выведеНI1Ыli из базового. чрезвычайно важен
11
JlРИllЦипиаПСIf для
С++. Как будет lIоказано ниже, эта гибкость Яl3лястся ключевым
способа реализации динамического ПОЛИМОРфllзма
lJ
MOMetlTO:l1
ДЛЯ
С++.
Ссылки на производные типы
Подобно указателям, ссылку на базовый К.IIасс также можно ИСПОЛЬЗ0ВЗП, для
доступа к оБЪСl\ТУ произподного ТIlПа. Эта возможность особенно часто IIрименя­
ется при l1ередаче аргументов функциям. Параметр, IЮТОрЫЙ имеет тип ссы rlКИ
на базовый класс, может пршшмать объекты 6азового JUlacca, а также объекты
любого другого типа, выведенного из него.
ВАЖНОI
IIIа.ВИрЦ(.Q АЬИЬte..ф.У--NКЦИИ
и полиморфизм
ФуtlJt<lмеllТ, lIа котором u С++ строится поддержка ПОЛII!l.~ОРфИЗМ3, СОСТОIIТ IIЗ
механизма наследования н указателей на базовый JUlacc. Конкретным средством,
котоrюе в действитеЛhНОСТИ 110зволяет реализовап) полиморфизм, является DIlPтуалЫlая функция. (Оставшаяся часТl) этого модуля как раз и посвящеl·lа расс\ю­
трению этого важного средства.)
Понятие о виртуальной функции
ВuрmуаЛЫ/аЯ фуuкцuя -
это функция, которая объявляется н базовом I(лас­
се с I1спользоваllием КЛЮLIСВОГО слова
vi r:tual
11 псреопрс/(слястся в ОДIJОМ IJЛИ
неСК())JЫШХ ПРОИ:ШОДIiЫХ классах. ТаКИ:\1 образом, каждый IIрои,зводtlый класс
может IIMl'ТI> собственную пеРС1l1O 1I1'lрТУалыюй Функшщ. ИlIтересно рассмо­
треть СlIтуацию, "огда виртуалыlяя ФУIfЮJ.I1Я 8Ызынается чеrез указатсЛl) (IIЛИ
ссылку) на базовыti класс. В ЭТО:\I случае С++ OIlределя('т, каl\:УЮ IIмеНIfО версию
виртуалыюй ФУНКI\I1.И необходимо вызвать, по nllmy объекта, адресуемого Э1 им
указателем. Првчеы следует иметь в ШЩУ, что это решение IlрИШlмается во вре­
.МЯ оыl/лнеlluяя IlрОгрЗММЫ. СледователbtlO, 11\)\\ указаlllШ II'a различные объекты
будут вызываться JI различные версии виртуальной ФУНКЦИИ. Другими словами,
С++: PYI<OBOACTBO ДМl начинающих
503
именно по типу адресуемою обьекmu (а не по типу самого указателя) определя­
ется, какая версия виртуальной функции будет выполнена. Таким образом, если
ф
базовый класс содержит виртуальную функцию и если из этого базового класса
:s:
выведено два (или БОЛhше) других класса, то при адресации раз.1ИЧНbIX типов
О
ID
объектов через указатель на базовый класс будут выполняться и различные вер­
r
~
сии этой виртуалhНОЙ функции. Аналогичный механизм работает If при исполь­
U
зовании ссылки на базовый класс.
:I:
о
Функция объявляется виртуальной в базовом классе с помощью ключевого
слопа
virtual.
При переопределении виртуальной функции в производном клас­
се ключевое слово virtuаl110ВТОРЯТЬ не нужно (хотя это не будет ошибкой).
Класс, который включает виртуальную функцию, называется nолшlорф1lым
классом. Этот термин также примеllяется к классу, который наследует базовый
класс, содержащий виртуалЬНУЮ ФУНКЦИЮ.
Рассмотрим следующую короткую программу, в которой демонстрируется ис­
пользование виртуальной функции.
//
Пример
использования
виртуальной функции.
#include <iostream>
using namespace stdi
class В (
public:
virtual void who() { // +- Об~nеRИе
cout « "Базовый клаСС.\n"i
.ир~уanъвой фуихции.
};
class О1 : public В {
public:
void who () { / / Переопредenевие фуJUЩИИ "Ьо ()
// .пасса Dl.
cout « "Первый производный класс.\п";
.ЦJU[
} i
class О2 : public
public :.
void who() { //
//
В
{
Ещ$ ОДНО переопредеnение фувхции "Ьо()
ДnR .пасса
D2.
МОДУЛЬ 10. Наследование
504
cout «
"Второй
производный класс.\п";
};
int main ()
В
base_obj;
В
*р;
О1
D1_obj;
О2 D2_obj;
р
=
&base_obj;
/ / Вызываем ВИР'Х'УanьнyJD ф)'JI1ЩИJD who ()
// базовый класс.
p->who();
// доступ к функции who()
р
черва ухааа'Х'ет. на
класса
В
=
&D1_obj;
p->who(); // доступ к функции who()
р = &D2_Obj;
p->who () ;
/ /
return
доступ
к функции
wrlO ()
класса
класса
О1
О2
О;
Эта программа генерирует такие результаты.
Базовый
Первый
класс.
производный
класс.
Второй производный
класс.
Теперь рассмотрим код этой программы подробно, чтобы понять, как она ра­
ботает.
В классе В функции who () объявлена виртуальной. Это означает, что ее
можно переопределить в производном классе (в классе, выведенном из В). И
она действительно переопределяется в обоих ПРОI1ЗВОДНЫХ классах О1 и О2. В
функции main () объявляются четыре переменные: base _ obj (объект типа В),
р (указатель на объект класса В), а также два объекта D1_obj и D2_obj двух
производных классов О1 If О2 соответственно. Затем указателю р присваивает­
ся адрес объекта base _ obj, и вызывается функция who () . Поскольку !рункция
who () объявлена виртуальной, С++ во время выполнения программы опреде-
С++: руководство ДМ! начинающих
ляет, к какой именно версии функции
who ()
505
здесь нужно обратиться, причем
решение принимается путем анализа типа объекта, адресуемого указателем р.
В данном случае р указывает на объект типа В, поэтому сначаJlа ВЫllолнястся
та версия функции
who ( ) ,
которая объявлена в классе в. Затем указателю р
присваивается адрес объекта D 1_ оЬ j. Вспомните, что с IЮМUЩЬЮ указателя на
базовый класс можно обращаться к объекту любого его производного класса.
Поэтому, когда фуикция
who ()
вызывается во второй раз, С++ снова выясня­
ет тип объекта, адресуемого ука:~ателем р, Н, исходя ИЗ этого типа, определяет,
какую версию функции
who ()
нужно вызвать. Поскольку р здесь указывает на
объект типа
01, то выполняется версия функции who ( ) , онрсделешшя 8 классе
01. Аналогично после присвоения р адреса объекта 02_ ol"Jj вызывается версия
функции
who ( ) , объявленная n классе
О2.
Итак, запомните: то, какая версия виртуальной функции Jtействительно будет
вызвана, определяется во время выполнения программы. РеШСI-lИС основывается
исключительно на типе объекта, адресуемого указателем (используемым при вы­
зове рассматриваемой функции) на базовый класс.
Виртуальную функцию можно вызывать обычным способом (не через указа­
тель), используя оператор "точка" и задавая имя выэьшающего объекта. Этоозна­
чает, что в предыдущем примере было бы синтаксически корректно обратиться к
функции
who ()
с помощью следующей инструкции:
01 obj. who () ;
Однако при вызове виртуальной функции таким способом игнорируются ее
полиморфные атрибуты. И только при обращении к виртуальной функции через
указатель на базовый класс достигается динамический полиморфизм.
Поначanу может 110казаться, что Ilt:'рсопределение виртуальной функции в
производном классе представляет собоii специальную форму перегрузки фуш(­
ций. Но это не так. В действительности мы имеем дело с двумя принципиально
разными процессами. Прежде всего, версии перегружеНIIОЙ ФУНКЦИИ должны
отличаться друг от друга типом и/или количеством параметров, в то время как
тип и количество параметров у версий переОllределенной виртуальной функ­
ции должны в точности совпадать. И в самом деле, прототипы виртуальной
функции и ее переопрсделений должны быть абсолютно одинаковыми. Если
прототипы будут различными, то такая ФУНКЦIIЯ будет попросту СЧllтаться пс­
регруженноj:j, и ее "ВИРТУ,Ulьная сущность" утратится. Кроме того, виртуальная
функция должна быть членом класса, для которого она определяется, а не его
"другом". Но в то же время виртуальная функция может быть "другом" ино­
го класса. И еще: функциям деструкторов разрешается быть виртуальными, а
функциям конструкторов
-
нст.
ф
:s:
:r
О
m
~
Ф
<
U
.0
:I
506
Модуль 10. Наследование
Наследование виртуальных функций
Если функция объявляется как виртуальная, она остается такой независимо от
того, через сколько уровней производных классов она может пройти. Напри мер, если
бы класс
D2
бьVI выведен из класса D1, а не из класса В, I(Ш( показано в следующем
примере, то функция
who ()
по-прежнему оставалась бы виртуальной, и механизм
выбора соответствующей версии по-прежнему работал бы корректно.
// Этот класс выведен из класса О1, а не из В.
class D2 : public О1 {
public:
void who ( ) { / / Переопределение функции who ()
cout « "Второй производный класс.\л";
для класса [12.
} ;
Если производный класс не переопределяет виртуальную функцию, то ис­
пользуется функция, определенная в базовом классе. Например; проверим, как
поведет себя версия предыдущей программы, если в классе
делена функция
D2
не будет переОl1ре­
who ().
#include <iostream>
using narnespace std;
class В {
public:
virtual void who()
cout « "Базовый
класс.\п";
}
};
class О1 : public В {
public:
void who() {
cout « "Первый производный
КЛilСС. \п";
}
};
class
//
};
О2
: public
Функция
who()
В
не
( //
//
Кnacc
D2 не переоnpедеnRет
функцию
определена.
who().
С++: РУI<ОВОДСТВО ДМ\ начинающих
507
int main ()
ф
в
base_obj;
В
*р;
sз:
О
ID
О
~
01 01 obj;
02 02 obj;
р
<
u
о
::r:
&base_obj;
p->who();
// Обращаемся
р
=
who()
:класса
В.
= &Ol_obj;
p->who(); //
р
:к ФУНКЦИИ
Обращаемся
к ФУНКЦИИ
who()
класса
01.
= &02 obj;
p->who(); /*
ОбращаемСR к фуккции _Ьо()
в классе
return
D2
класса В,
она не переопредenена.
поскольку
*/
О:
При выполнении эта программа генерирует такие результаты.
Базовый
Первый
Базовый
класс.
производный
класс.
класс.
Как подтверждают результаты выполнения этой программы, IЮСКОJlЫ<У ФУНК­
ция
who () не переопределена классом 02, то при ее вызове с помощью ннструк­
p->who () (когда член р указывает на объект D2_obj) ВЫllOJlШlется та вер­
сия ФУНКЦIIИ who () , которая определена в классе В.
Следует иметь о виду, что наследуемые свойства спецификатора virtual
ЦШI
являются иерархическими. Поэтому, ссли IIредыдущий пример изменить так,
чтобы класс О2 был выведен из класса О1, а не И3 класса В, то при обраЩСIЩИ к
фушщии who () через 06ъеl<Т типа О2 будет Вblзвана та ее версия, которая объ­
явлена в классе О1, поскольку этот класс является "ближайшим" (по иерархиче­
ским "меркам") к классу О2, а не функция
who ()
из класса В.
Зачем нужны виртуальные функции
Как отмечал ось выше, виртуальные функции в сочетании с прОИ3ВОдНЫМИ
ТИП"~Ш позволяют С++ поддерживать ДИШ.'IмпчсскиЙ полиморфнзм. Полимор-
508
Модуль10.Наследование
физм существен для объектно-ориентированного программирования по ОДНОЙ
важной ПРl1чине: он обе~печиuает возможность некоторому обобщенному клас­
су определять ФУНКЦИИ, которые будут использовать все l1роизводные от него
классы, причем l1РОИЗВОДИЫЙ класс может определить собственную реализаuию
некоторых или всех этих функций. Иногда эта идея выражается следующим об­
разом: базовый класс диктует общий интерфейс, который будет иметь любой
объект, выведенный из этого класса, 110 позволяет при этом производному fl.лас­
су определить ",етод. используемый для реализации этого интерфейса. ВОТ по­
чему для описания полиморфизма часто используется фраза "один интерфейс,
множество методов".
Для успешного применения полиморфизма необходимо понимать, что базо­
вый и производный I<лассы образуют иерархию, развитие которой направлено от
большей степени обобщения к меньшей (т.е. от базового класса к производному).
При корректной разработке базовый класс обеспечивает все элементы, которые
производный класс может использовать налрямую. Он также определяет функ­
ции, которые производный класс должен реализовать самостоятельно. Это дает
производtЮМУ классу гибкость в определении собствеНIIЫХ методов, но в то же
нремя обязывает использовать общий интерфейс. Другими словами, поскольку
формат интерфейса определяется базовым классом, любой производный I\ласс
должен разделять этот общий интерфейс. Таким образом, ИСПОЛЬЭОВШiие нирту­
алЬНЫХ функций позволяет базовому l(лассу определять обобщенный интерфейс,
который'будет использован всеми производными класса.\1И.
Теперь у нас может 1:Ю31IИКИУТЬ DОПрОС: почему же так важен общий интерфейс
со множеством реализаций? Ответ снова возвращает нас к основной побудитель­
ной причине возникновения объектно-ориентироваююго программирования: та­
кой интерфейс позволяет программисту справляться со все возрастающей слож­
ностью программ. Например, если корректно разработать программу, то можно
быть уверенным в том, что ко всем объектам, выведенным из базового класса,
можно будет получить доступ единым (общим для всех) способом, несмотря на
то, что конкреТЮ)Iе действия у одного производного класса могут отличаться от
действий у другого. Это означает, что программисту придется иметь дело только
с одним интерфейсом, а не с великим их множеством. Кроме того, производный
класс волен использовать любые или все фуНIЩИИ, предоставленные базовым
классом. Другими слонами, разработчику производноro класса не нужно заново
изобретать элементы, уже имеющиеся в базовом классе.
Отделение интерфейса от реализации позволяет создавать библиотеки классов,
tJапис,шием которых МOJуг заниматься сторонние организащш. Корректно реа.тIl1ЗD­
ванные библиотеки должны предоставлять общий интерфейс, который программист
мож~ ИСПОЛЬ:ЮВi:l.ТЬ для выведеliИЯ классов в соответствии со своими конкретными
С++: PYI<OBOACTBO
ДЛЯ начинающих 509.
..............................................................
................................................................
,)
потребностями. Например.
библиотека бaзuвых классов Мicrоsоft
(Microsoft
Foundation Classcs - MFC), так и более новая библиотека классов .NET Framework
Windows Fonns поддерживают ра.зработку пршюжений для Мiсrоsоft Windows. Ис­
NaK
пользование этих классов позволяет писать программы, которые
MOIyr
унаследовать
множество функций, нужных любой Wiпdоws-программс. Вам понадобlrrcя лишь
добавить в нес средства, уникальные ДЛЯ вашего приложения. Это - большое под­
Чтобы лучше почувствовать силу виртуальных функций, применим их I( клас­
су
TwoDShape. В предыдущих примерах каждый класс, вьшеденныii из класса
TwoDShape, определяет функцию с именем а rea ( ) . Это наводи,' нас на мысль о
том, не лучше ли сделать функцию вычислеНIIЯ площади фигуры area () вирту­
альной в классе TwoDShape. Это даст нам возможность переопределить ее в про­
изводных классах таким образом, чтобы она вычисляла площадь согласно типу
конкретной геометрической фигуры, которую инкапсулирует данный класс. Эта
МЫCJII> И реалИ3Qвана в следующей ПРОl'рамме. Для удобства в класс
TwoDShape
вводится поле пате, которое упрощает демонстрацию Эn1Х классов.
виртуальных
функций и механизма
полиморфизма.
#include <iostream>
#include <cstring>
using namespace stdi
// КЛасс двумерных объектов.
class TwoDShape {
// Это рrivаtе-члены.
double widthi
double height;
// Добавляем поле
char пате[20];
public:
пате.
// Конструктор по умолчанию (без
TwoDShape() (
width = height = 0.0;
strcpy(name, "неизвестный");
ID
~<
U·
I
Применение виртуальных функций
Использование
:s:
1:
О
о
спорье при программироваlfИИ сложных систем.
//
ф
параметров)
.
510
Модуль 10. Наследование
// Конструктор объектов с тремя параметрами.
TwoDShape(double w, double h, char *n) {
width = w;
height = h;
strcpy(name, n);
// Конструктор объектов, у КОТОРЫХ
TwoDShape(double х, char *n) {
width = height = Х;
strcpy(name, n);
ширина равна высоте.
void showDim() (
cout « "Ширина и высота равны " «
width « " и " « height «
"\n";
// Функции доступа к членам класса.
double getWidth() { return width; }
double getHeight() { return height;
void setWidth(double w) { width = w; }
void setHeight(double h) { height = h;
char *getName() { return пате; }
// ДОбавляем в класс TwoDShape функцию area()
// и делаем ее виртуальной.
virtual double area() ( // Теперь это виртуanьВaR фув~.
cout « "\ПОШИбка: функцию area() нужно переопределить.\п";
return 0.0;
};
// Класс Triangle - производный от класса TwoDShape
// (класс треугольников).
class Triangle : public TwoDShape {
С++: руководство {:::./\Я начинающих
511
рrivаtе-член
char style(20): //
public:
ф
:::s:
/*
Конструктор по умолчанию.
действующий
по
Он автоматически вызывает
умолчанию конструктор
класса
TwoDShape.
j./
::I:
:0
·со
:0
:«
:Ф
:<
Triangle () {
strcpy(style,
:~
:I
"неиэвестный");
// Конструктор с тремя параметрами.
Triangle(char *str, double w,
double h) : TwoDShape(w, h,
strcpy(style, str);
"треугольник")
// Конструктор равнобедренных треугольников.
Triangle(double х) : TwoDShape(x, "треугольник")
strcpy (style, "равнобедренный") i
//
ПереОпр8J(8Jlеиие фунхции
// 8
хааее. ТwoDShape,
double area () {
return getWidth()
*
8
area (), об'loJDJlеRВОЙ
кnacce Triangl •.
getReight() / 2;
void showStyle() {
cout « "Этот треугольник" «
style «
"\п";
}
};
// Класс Rectangle - проиэвоДный от класса TwoDShape
// (класс прямоугольников) .
class Rectangle : public TwoDShape {
риЬНс:
// Конструктор npямоугольника.
Rectangle(double w, double h)
(
512
МОДУЛЬ 10. Наследование
TwoDShape(w,
11
Конструктор
Ь,
квадратов.
Rectangle(double х) :
TwoDShape(x, "прямоугольник")
bool isSqua re ()
i f (getWidth ()
return false;
//
//
{ }
"прямоугольник")
{ }
getHeight()) return true;
Еще одно переопреДелеlDfе ФУИIЩИИ
а классе ТwoDShape,
double area() (
return getWidth()
а xnассе
area (), обoъJlвлеRИОЙ'
Rectanqle.
* getHeight();
};
int main ()
// Объявляем массив указателей
TwoDShape *shapes[5];
shapes[O]
shapes[l]
shapes[2]
shареs[З]
shapes[4]
&Тriапglе("прямоугольНblЙ",
&Rectangle (10);
&Rectangle(10, 4);
&Triangle (7. О) ;
&TwoDShape (10, 20,
for(int i=O; i < 5; i++)
cout « "Этот объект "
«
TwoDShape.
8.0, 12.0);
"общий");
«
shapes[i)->gеtNarnе()
cout
на объекты типа
"Площадь равна " «
shapes[i]->area() «
«
"\п";
"\п";
// ДmI xUt,Цo:ro o&J.eJC'2.'a
11
'2.'еперъ 8blsыaaelJ.lC.
, / / JIPIUUI :верClOl
// фунхции area().
cout «
"\п";
С++: руководство ДЛЯ начинающих
return
513
О;
ф
:s:
1:
О
Результаты выполнения этой программы таковы.
Этот
объект
Площадь
1:11
~
треугольник
равна
<
u
48
о
::r:
Этот
объект
ПЛощадь
Этот
объект
Площадь
Этот
Этот
40
треугольник
равна
объект
100
прямоугольник
равна
объект
Площадь
прямаугольник
равна
24.5
общий
ПЛощадь
равна
Ошибка:
функцию
area()
нужно
переопределить.
О
Рассмотрим эту программу подробнее. Во-первых, функция
ляется в классе
определяется в
area () объяв­
TwoDShape с использованием ключевого слова virtual и пере­
классах Triangle и Rectangle. В классе TwoDShape функция
area () представляет собой своего рода "заглушку", которая просто информи­
рует пользователя о том, что в производном классе эту функцию необходимо
переопределить. Каждое переопределение ФУНКЦИИ
area ()
реализует вариант
вычисления площади, соответствующий типу объекта, инкапсулируемому про­
изводным классом. Таким образом, если бы вы реализовали ЮIасс эллипсов, то
функция
area () в этом классе вычисляла бы площадь эллипса.
В предыдущей програ...\fме проиллюстрирован еще один важный момент. Об­
ратите внимание на то, что в функции main () переменная shapes объявляет­
ся как массив у"азателей на объекты типа
TwoDShape. Одиако элементам этого
массива прис,Ваившотся у"азатеЛI1 на объекты классов Triangle, Rectangle и
TwoDShape. Это вполне допустимо, поскольку указатель на базовый класс может
ссылаться на объект производного класса. Затем программа в цикле опрашивает
массив
shapes, отображая ииформацию о
каждом объекте. Несмотря иа просто­
ту, этот цикл иллюстрирует силу как наследования, так и виртуалЫIЫХ функций.
Конкретный тип объекта, адресуемый указателем на базовый класс, определя-
514
МОДУЛЬ 10. Наследование
ется во время ВЫПОJJНСIШЯ I1рограммы,
'11'0
ПОЗlюляет принять соответствующие
меры, Т.е. оыполнить действня, соответствующие объекту дашюго типа. Если
объект (В конечном счете) выведен из
ICJJacca TwoDShape,
е,'О площадь можно
узнать посредством вызова ФУНКЦИИ area () . Интерфейс для выполнения этой
Оllерации одишн<ов ДЛЯ всех ПРОИЗ'!lодных (от TwoDShape) классов, незаВИСl1МО
от конкретного типа используемой фигуры.
~ПРОСЫ ДЛя текущего КоНтрОля _______Ul_
1.
Что I1редставляет собой виртуальная функция?
2.
Поче~,у виртуальные функции столь важJlЫ ДЛЯ С++-программирования?
3.
Какая версия персопределенной вирту;шыюй функции 8ЬШОJшяется "ри
___
ее вызове через указатель l-Iа базовый класс?·
"._'""'-~#г
"''IIК:4.~ ~.t-~......
mf
Вi'
-
. . , . JII
а.м
ВАЖНОI
DRI.~ИСJ.о...вИРt"УаАЫlЫе...ф.y,и1ЩI4J4
и абстрактные классы
Иногда полезно со;щаТI> базовый класс, опредr JlЯЮЩИЙ ТОЛЫШ своего рода "пустой
бл;urк", J<ОТОРЫЙ унаследуют все ПРОИЗВОДНЫС классы, ПРllчем каждый И3 них запал­
и ит этот "БЛaJ 11(" с06ствешюй ишtюрмациеЙ. Такой ~шсс определяет "сугь" функций,
которые производные классы должны ре3JIИЗОВ<1ТЪ, но сам при этом J-Ie обесJlечивает
реaJJИ3iЩЮ1 одной ИЛИ НI'СКОЛЫ<ИХ фуш(циЙ. Подобная ситуация может IЮ.ЗНИКIIУТЬ,
когда базовый класс попросту не
n
СОСТОЯНИИ рсапизоватъ фУНlщию. Этот случай
был пронлm()стрирооаll веrсией класса TwoDShape (и:) предыдущей программы), в
которой определеиие функции area () представляло собой "заглушку", ПОСI<ОЛЫ,У в
ней ПЛОЩ3Дl, фИI)'РЫ JiC ВЫЧИCJlЯлась Н, естествешlO, не от06раЖ3JIась.
В будущем, создавая собствеииыс библиотеки классов, вы убсдитесь, что от­
сутствие у ФУНl(цИИ 'Iеткого определения в контексте своего (базового) класса,
1.
Виртуальная ФУНКI1Ш/ - это ФУНКЦИЯ, которая объявляется в базовом клаССе с по­
МОЩI,Ю I{JlЮЧ~DО/'О C.IIOlJ(\
virtual
и п~реопредеJlяется в пронз~()дном I<ласе\.'.
2. ВИРТУ<LIIЫlые ФУНКЦИИ предстanляют собой одiш из способов поддержки ПОЛIIМОр·
физма D С++.
3.
КонкреТ1Iая
nереия
выполняемой
виртуа.тп,ноЙ ФУНКЦИИ определяется типом
объекта, IlСIюльзуемого IJ момент ее IJЫЗОВU. ТаЮIМ обрuзом, определение нужноЙ
nepClI1I происходит по время nылолнеШIЯ программы.
С++: PYI<OBOACTBO ДМ1 начинающих
515
не является чем-то необычным. Описаl\НУЮ ситуацию :'I-южно обработать двумя
способами. Один из них, который продеМОНСТРI!рован в прсдыдущем Ilримере,
состоит в выводе предупреждающего сообщения. И хотя ТaJ<ОЙ подход может
быть полезным в определенных обстоятельствах (наприМt:р, при отладке про­
граммы), все же он не соответствует уровню профеССИОНaJ1ЫЮГО I1рограммиро­
вания. Существует и другой способ. Наша цель
-
застаВlI1Ъ производные классы
пере определить ФУ1iКЦИИ, которые в базовом классе не имеют никакого смысла.
Рассмотрим класс
ция
Triangle. Им нельзя пользоваться, если не Оl\редслсна функ­
area () . в этом случае нам не обойтись без "сильнодействующего" средства,
благодаря КОТОРОМУ производный класс обязательно переопределит все необхо­
димые Функuии. Этим средством в С++ является чucrnо вuртуальная фУ"LКЦUЯ.
Чисто виртуальиая функция
- это функция, объявленная в базовом классе,
tlO не имеющая в нем никакого определения. Поэтому любой произnодный класс
должен определить собственную версию этой функции, ведь у него просто нет
никакой возможности использовать версию из базового КJШСС<1 (по ПрИ<IЮlе ее
отсутствия). Чтобы объявить чисто виртуальную функцию, IIспользуйте следу­
ющий общий формат:
virtual
тип имя_функции (слисок_лараметров)
=
О;
Здесь под элементом тип подразумевается тип значения, возвращаемого функ­
цией, а под элементом имя_ функции - ее имя. Обозначение =
о я'вляется при­
знаком того, что функция здесь оБЪЯВЛЯС1·СЯ как чисто виртуаЛЫIaЯ.
Используя возможность создания чисто виртуальных функций, можно усо­
. вершенствdвать класс TwoDShape. Например, в следующей версии определения
этого класса (см. предыдущую программу) функция area () уже представле­
на как чисто виртуальная. Это означает, что все классы, вывел.енные из клас~а
TwoDShape,
/1
обязательно должны переОllределить функцию
Использование чисто
виртуальной
#include <iostream>
#include <cstring>
using namespace stdi
/1
КЛасс
двумерных
объеКТ0В.
class TwoDShape {
1/ Это рrivаtе-члены.
double width;
double heighti
функции.
area ().
516
Модуль 10. Наследование
char пате [20] ;
public:
11
Конструктор
по
умолчанию.
TwoDShape() (
width = height = 0.0;
strcpy(name, "неизвестный");
11
Конструктор
объектов
с
тремя
параметрами.
TwoDShape(double w, double h, char *n)
width = w;
height = h;
strcpy(name, п);
11
Конструктор объектов,
TwoDShape(double х, char
width = height = х;
strcpy(name, п);
у которых ширина равна высоте.
*п)
{
void showDim ()
cout « "Ширина и высота равны " «
width « " и " « height «
11
Функции доступа
(
"\п";
к членам класса.
double getWidth() { return width; }
double getHeight() { return height;
void setWidth(double w) { width = w; }
void setHeight(double h) ( height = h;
char *getName() { return пате; }
11 Фуихци. area ( ) теперь чисто
virtual double area() = о;
};
виртуa..nъва•.
С++: РУI<ОВОДСТВО ДЛЯ начинающих
// Класс треугольников Triangle (производный
// класса TwoDShape) .
class Triangle : public TwoDShape
char style[20]; // now private
public:
517
от
ф
s
:I:
О
ID
~
U
/*
Конструктор ,по
конструктор
умолчанию.
класса
Он
автоматически
TwoDShape,
действующий
вызывает
по
умолчанию.
*/
Triangle () {
strcpy(style,
"неизвестный");
// Конструктор треугольников с тремя
Triangle(char *str, double w,
double h) : TwoDShape(w, h,
strcpy(style, str);
//
Конструктор равнобедренных
Tri~ngle(double
strcpy(style,
х)
параметрами.
"треугольник")
треугольникев.
: TwoDShape(x,
"треугольник")
"равнобедренный");
// Здесь переопределяется функция area(),
// в классе TwoDShape.
double area() {
return getWidth() * getHeight() / 2;
void showStyle()
cout « "Этот
треугольник"
«
style
«
объявленная
"\п";
};
// Класс прямоугольников Rectangle
// класса TwoDShape) .
class Rectangle : public TwoDShape
public:
(производный
от
О
:I:
518
Модуль 10. Наследование
• • • • • • • • • • • • • • • • • • о • • • • • • • • • • • • • • о . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -. . . . . . . . . . . . . . . . . . . . . . . . . . . .
// Конструхтор прямоугольников.
Rectangle(double w, double h) :
TwoDShape(w, h, "прямоугоnьник")
// Конструхтор квадратов.
Rectangle(double х) :
TwoDShape(x, "прямоугольник")
bool isSquare ()
i f (getWidth ()
return fa1se;
getHeight())
0"0 . . . . . . . . . . . . . . . . . . . . . . . . .
о ••••• о ••
{ }
{ }
rеturп
true;
// Это еще одно переопределецие функции area().
double area() {
return getWidtht) * getHeight();
};
int main () { .
// Объявляем массив указателей
TwoDShape *shapes[4J;
на объекты типа
TwoDShape.
shapes[O]
'&Тriапg1е("прямоугоnьнbIЙ", 8.0, 12.0);
shapes[1J
&Rectang1e(10);
shapes[2) = &Rectang1e(10, 4);
shареs[З] = &Triang1e(7.0);
for(int i=O; i < 4; i++) {
cout « "Этот объект " «
зhареs[i]->gеtNamе()
cout «
"Площадь
равна
"
shapes[iJ->аrеа()
cout «
"\п";
«
"\п";
«
«
"\п";
С++: PYI<OBOACTBO M~ начинающих
519
абсrпрактuы.At. Абстрактный класс характеризуется одной важной особенностью:
-
у такого класса не может быть объектов. Чтобы убедиться в этом, попробуйте
:<i
rеturл
О;
Если класс имеет хотя бы одну чисто виртуальную функцию,
el'o называют
создать производнъrй класс, который не персопределяет функцию
этого достаточно удалить переопределение этой ФУНКЦИII из
area () (для
класса Triangle).
Вы тут же (т.е. во время КОМПИЛЯЦИИ) получите сообщCIIИС об ошибке. АБСТР<lКТ­
НЫЙ класс можно использовать только в качестве базового. IIЗ которого будут вы­
водиться другие классы. ПРИ\fЮlа того, что абстрактный
KJ1acc нельзя использо­
вать ДЛЯ создания объектов, лежит, безус.ловно, D отсутствии определения для его
одной или нескольких функций. Поэтому в ФУНКЦИИ
main () IIредыдущей про­
граммы размер массива shapes сокращен до 4 элементов, 11 больше не создается
"заготовка для фигуры" в Dиде "об06щсннnго" объекта КЛiкса TwoDShape. Но,
как доказывает эта программа, даже ссл" базовый класс ЯВJlяется абстрактным,
мы все равно можем объявлять указатели lIа его тип, ,<оторые затем будем ис­
пользовать для указания на объекты производных классов.
1.
Класс, который ~аследуется, называется
классом. А класс, кото-
рый является наследником (потомком), на:iывается
2.
классом.
Имеет ли базовый класс доступ к членам ПРОИЗfЮДНОГО'? Имеет ли произ­
водный класс доступ к членам базового?
3.
Создайте класс
него ФУНКЦИЮ
Circle. прои:шодный от класса TWQDShape.
а rea () для вычисления площади '<руга.
Включите в
4. КаКИl','1 образом производному J<лассу можно не разрешить доступ к членам
базового класса?
5.
Представьте общий формат конструктора, который ВЫ:JЫШIСТ конструктор
базового класса.
6.
Дана следующая иерархия классов.
class Alpha { ...
class Beta : public Alpha { .. .
classGamma: public Beta { .. .
в каком порядке вызываются конструкторы ЭТlfХ K'1(lCCOn прн создании
объекта класса
7.
Gamma?
Как можно получить доступ IC рrоtесtеd-членам?
:Ф
::s::
:]:
:0
.11:1
:0
:Ф
.<
:u
': О
:I
520
8.
Модуль 10. Наследование
С помощью указателя на базовый класс можно ссылаться на объект про­
изводного класса. Поясните, почему "то важно для переопределения
функций.
9.
Что представляет собой чисто виртуальная функция? Что такое абстракт­
ный класс?
10. Можно ли создать объект абстрактного
11.
класса?
Поясните, каким образом чисто виртуальная функция способствует реа­
лизации такого аспекта полиморфизма, как "один интерфейс, множество
методов".
"'"
Ответы на эти вопросы можно найти на Web-стрзtlице данной
Ноэометку""' книги по адресу:
http://www.osborne.com.
МОДУЛЬ
11
С++-система ввода­
вывода
11.1.
11.2.
11.3.
11.4.
Потоки С++
КлаССЫIIОТОКО8
Перегрузка операторов ввода-вывода
Форматирование данных с использованием функций-членов
К1Iacca
ios
11.5.
11.6.
11.7.
11.8.
Использование манипуляторов ввода-вывода
11.9.
Нсформатировзнный ввод-вывод данных в двоичном режиме
Создание собственных манипуляторных функций
Как открыть и закрыть файл
Чтение и запись текстовых файлов
11.10.
Использование других функций ввода-вывода
11.11.
произволыlйй ДОСТУII
11.12.
Получение информации об операциях ввода-вывода
МОДУЛЬ 11. С++-система ввода-вывода
522
С самого lIаЧ<L71<l КIШПI мы использовапи С+ +-систему ввода-выпода, но прак­
тически не давали ниюжих пояснений по этому поводу. Поскольку С++-систt'ма
пвода-выиода ностроена на иерархии классов. ее теорию и дет<U1И непозможно
освоить, не рассмотрев сначала классы и механизм наследования. Теперь HaCT.UlO
время для подробного, изучения С+ .J..-средств ввода-выпода.
Необхuдимо сразу отметить, что С++-систсма ввода-вывода
- довольно об­
ширная тема, и здесь оllисаныl ЛllШЬ самые ш\жI-Iыle и часто ПРИМСllяемые сред­
ства. В частности, вы узнаете, как псреГРУЗI1ТI> операторы
"«"
и"»" ДJlЯ пвода
и вывода объектов создаваемых вами классов, а также КaI< отформатировать вво­
димые ИЛI1 пьшодимые данные 11 IfСl10льзовать манипуляторы ввода-вывода. За­
вершает этот модуль рассмотрение средств файлового ввода-вывода.
Сравнение старой и новой
С++-систем ввода-вывода
в настоящсе время существуют две версии библиотеки объектно-ориснти­
ропзнного вво1tа-выпода, JlрН'lем обе ШИРОКО используются программиста\1И:
бuлее старая. основаНllая 11<1 оригннаЛЫIЫХ спецификациях языка С++, и tiOван, определенная стандартом языка С++. Старая библиотека ввода-пывода
пuддерживается за счет эаГОЛОJЮ'IIIOГО файла
средством заГОЛОВlса
<iostream. h>,
а 110Вая
- :10-
<ios trearr.>. Новая библиотека ввода-вывопа, по сути,
Ilредставляет собой обновленную и усовершенствованную версию старой.
\
ОСНОlllюе раЗЛ1lчие между lJliМИ состоит В реализации, а не в том, как их нужно ИСlIOЛЬЗОВ<lТЬ.
С точки зрения ПРOJ'раммиста, есп, два существеtIНЫХ различия между ста­
рuй
11
новой С++-библиоте.ками ввода-вывода. Во-первых, нопая библиотека
содержит ряд ДОПОЛlнпеЛЫiЫХ средств и определяет несколы(о новых типов
данных. Таким образом, новую библиотеку ввода-вывопа можно считать су­
пермножеством старой. Практически все Ilрограммы, написанные для старой
бибЛlIотеки, успешно КОМlIИлируются
npll I1СПОJlI>зоваШIII новой, не требуя
ВllесеllИЯ Ю\lшх-либо зш1'lllтелыllхx I1зменеНIIЙ. Во-вторых, старая библиоте­
ка ппода-вывода была определена в глобальном пространстве имен, а новая
использует пространство ((мен
std,
(Вспомните, что пространство имен
std
используется всеми бибЛIIuтекамп стандарта С++.) Поскольку старая библи­
отека ввода-вывода уже устарела, в этой книге описывается только новая, но
большая часть инфuрмации IIРИМСllима и
« старой.
С++: руководство ДА>! начинающих
523
llРИНЦИПИaJlЫIЫМ для 110нимания С++-системы ввода-вывода является то,
что она опирается на повитве потока. Поток
(stream) -
рая либо синтезирует LIНформацию, либо потребляет ее
::>то абстракция, кото­
11 связывается с любым
физическим устройством с помощью С++-системы ввода-нывода. Характер НО­
ведения всех потоков одинаков, несмотря на различные физические устройства,
с которыми ОIlИ связываются. ПоскоЛl>КУ ПОТОКИ действуют одинаково, то прак­
ПIЧССКИ ко всем типа...... устройств можно I1р"менить ОДНII
11
те же ФУНКЦIIИ
11
операторы ввода-вывода. llапример, методы, используемые ДЛЯ записи данных
lIа экран, такжс можно IIс/юльзовать для вывода их на IlрЮIТСр или ДЛЯ записи
в дисковый файл.
в самой общей форме поток можно назвать логическим шперфейсом с фай­
лом. С++-определение термина "файл" можно отнссти К дисковому файлу, экра­
ну, клавиатуре, порту, файлу на магннтной ленте и пр. Хотя файлы отличаются
по форме и возможностям,
J;Jce
потоки одинаковы. ДОСТOIШСТ1Ю этого подхода
(с точки зрения программиста) состоит в том, что ОДI-lO устройство компьютера
может "выглядеть" подобно любому другому. Это значит. что поток обеспечиваст
интерфейс, согласующийся со всеми ycтpoikToaMII.
Поток связывастся с файлом при ВЫПОЛllеtllш операUlIII открытия файла,
а отсоеДИliЯСТГЯ от него с 11O~\ОЩЬЮ оперании ~акрытия.
Существуст два типа потоков: текстовый
11 двоичный. Текстовый поток ис­
пользуется для ввода-вывода символов. При этом могут пронсходить некоторые
преобразования символов. Например, при вьшоде символ
Houoil
строки может
быть преобразован в 11Оследовательность символов: возврата каретки и перехо­
да на новую строку. Поэтому можст не быть взаИМНО-ОДНОЗllачного соотвеТСТRИЯ
междr тем, что посылается
8
поток, И тем, что в действитеЛЫЮСТlI записывается
в фаifл. Двоичный поток можно использовать с данными Jlюбого пша, причем в
этом случае lIикакого преобразоваtШЯ символов не выполняется, и между тем,
что посылается в поток, и тем, что потом рсалыю содержится IJ файлс, сушестпу­
ет взаимно-однозначное
COOTBCTCTBlle.
Говоря о потока~, необходимо 1100шмаТI>, что вкладьшастl'Я в ПОllятие "теку·
шей позиции". Текущая llOЗUl~UЯ -
это место в файле, с которого будет ВЫПQJ1·­
няться следующая операция доступа к файлу. Например, если длина файла равна
100 байт, и известно, что уже прочитана половина этого файла, то следующая
операция чтения прошюйдет на байте
теКУII.tеЙ позицией.
.50,
"оторый В данном случае и является
g
ltI
:Li
ltI
~
ltI
ltI
О
:i
~
U
:s:
,
U
+
+
u
524
Модуль 11, С++-система ввода-вывода
Итак, в языке С++ механизм ввода-вьтода функционирует с ИСIЮЛЬЗОl)знием
логического интерфейса, именуемого nотокол/. Все потоки имеют аналогичные
свойства, которые позволяют I)ЬШОЛНЯТЬ ОДИllаковые функции ввода-вывода,
независимо от того, с файлом какого типа установлена связь. Под файлом 110нимается реальное физическое устройство, которое содержит данные. Если фай­
лы различаются между собой, то потоки
нет. (Конечно, некоторые устройства
-
могут не поддерживать все операции ввода-вывода, например операции с пронз­
вольной выборкой, поэтому и связанныс с ними потоки тоже не будут поддержи­
вать эти операции.)
Встроенные С++-потоки
в С++ содержится ряд встроенных потоков
(cin, cout, cerr
и
clog),
кого­
рые автоматически открываются, как только программа начинает I)ЫПОЛНЯТЬСЯ.
Как вы знаете,
cin - это стандартный входной, а cout - стандартный выхIщ­
cerr и clo,:! (они преднаЗllачены для I)ывода информации об
ной поток. Потоки
ошибках) также связаны со стандартным выводом данных. Разница между ними
состоит в том, что поток
clog
буферизован, а поток
что любые выходные даНlIые, посланные
дены, а при использовании потока
1)
cerr - нет. Это означает,
поток cerr, будут немедленно вьше­
clog данныс
сначала записываются в буфср,
и реальный их вывод происходит только тогда, l(Огда буфер llOЛllОСТЫО заlЮЛ­
няется. Обычно потоки
cerr и clog используются для записи информации об
отладке или ошибках.
В С++ также предусмотрены д·вухбаЙтовые (16-битовые) символьные Dерсии
стандартных потоков, именуемые
wcin, wcout, wcerr
и
wclog.
Они предназна­
чены для поддержки таких языков, как китайский, для представления которых
требуются большие символьныс наборы. В этой книге двухбайтовые стаНД<Jрт­
ные потоки не используются.
По умолчанию стандартные С++-потоки связываются с консолью, но про­
граммным способом их можно !lереl1аправить на другие устройства или файлы.
Перенаправление может также выполнить операционная система.
ВАЖНОI
""
KAQCChLOOIOКQB
Как вы узнали в модуле
<iostream>,
1,
С++-система ВlIода-вывода использует заголовок
в котором для помержки операций ввода-вывода определена до­
вольно сложная иерархия классов. Эта иерархия начинается с системы шаблон­
ных классов. Как будет отмечено в модуле
12, шаблонный
класс определяет фор-
С++: РУКОВОДСТВО ДЛ9i начинающих
525
му, ве задавая в полном объеме данные, которые он должен обрабатывать. Имея
шаблонный класс, можно создавать его конкретные экземпляры. Для библиотеки
ввода-вывода стандарт С++ создаст две специальные версии шаблонных
одну для
lUJaCcoB:
8-, а другую для 16-битовых ("широких") символов. Эти версии дей­
ствуют подобно другим классам, и для использования С++-системы ввода-выво­
да никакого предварительного знакомства с шаблонами не требуется.
•
:0
:<{
:0
·10
~~
:6
:<{
:0
С ++-система ввода-вывода построена на двух связанных, 110 различных иерархи­
·10
:ш
:0
lUJacca liизкоуровневого ввода-вывода
::::t
basic_streambuf. Этот класс ПО,lIДеРЖJoшает базовые низкоуровневые операции
:§1
ях шаБЛОЮIЫХ классов. Первая выведена из
ввода и вывода и обеспечивает поддержку для всей С++-системы ввода-вывода.
Если вы не собираетесь заниматься программированием специализированных опе­
раций ввода-вывода, то вам вряд ли придется использовать напрямую класс Ьа 5
i с_
streambuf. Иерархия lUJaCCOB, с которой С++-программистам наверняка предстоит
работать вwIOТНУЮ, выведена из класса basic_ios. Это - класс высокоуровневого
ввода-вывода, который обеспечивает форматирование, КОНТРОЛI) ошибок и предостав­
ляет статусную информацию, связанную с потоками ввода-вывода. (Класс
basic_
ios_base, который определяет ряд СВОЙСТН, используемых
basic_ios.) Класс basic_ios IIсполь.зуется в качестве базовоm для не­
скольких производных классов, ВКlIЮЧая классы basic_istream, basic_ostreаmи basic_ iostream Эти lUJаССЫllри·меняются для создания потоков, предназна­
ios
выведен из класса
классом
ченных для ввода данных, вывода и ввода-вывода соотncтствешю.
Как упоминал ось выше, библиотека ввода-вывода создает две специализи­
ровпнныс иерархии шаблонных массов: одну для
8-, а другую для 16-битовых
символов. Б этой I<ниге описываются классы только для 8-битовых символов, по­
CI(OJlbKY они используются гораздо чаще. Ниже приводится список имен шаблон­
ных классов и соответствующих им "СИМВОЛЫfЫХ" версий.
Шаблонные классы
Символьные классы
basic
basic
basic
basic
basic
basic
basic
basic
streambuf
ios
istream
ostream
iostream
fstream
ifstream
ofstream
streambuf
ios
istream
ostream
iostream
fstream
ifstream
ofstream
Б ОСПUlьной части этой книги ИСIЮЛ!>3УЮТСЯ имеиа символьных массов, по­
скольку имснно ОНИ применяются в программа.х. Те же имена используются и
старой библиотекой ввода-вывода. Бот поэтому старая и новая библиотеки со­
вмсстимы иа уровне исходного кода.
~~
~~
:+
:U
526
МОДУЛЬ 11. С++-система ввода-вывода
и еще: кл~сс iоsсодержит множество фуmщий-членов и переменных, кото­
рые управляют основными Оllерациями над 'потоками или отслеживают резуль­
таты их выполнения. Поэтому имя класса
ios
будет употребляться в этой КЮ1ге
довольно часто. И помните: если включить в программу зarолонок
<iostream>,
она будет иметь доступ К этому важному классу.
~ПРОСЫ ДЛЯ теI<УЩ~ГО I<ОНТРОЛЯ
----------
1.
Что такое поток? Что такое файл?
2.
3.
Какой объект связан со стандартным выходным потоком?
C++-СlIстема ввода-Rьшода поддерживается сложной исрархией классов.
Верно ли это?·
8 8 n. .~88~аН~nm~8D"ww8П~.w'~ИW&W
..
_ _ПМ»QП&~&иWL8DВn_ _. . . . . . . ~_ _. .. . . . . . . . . . . . . . . . . . . . ..
' .
в ПРИМСР(L\: из прсдыл.УII!ИХ модулей при IIt'о6ходимости выполнить опсра­
ЦlIЮ ввода
или вывода
JlaIlHbIX.
связанных с
IICI\OTOPbIM
классом. создава.1ИСЬ
ФУIIКltIш-члены, lIазнаЧСllие I<ОТОРЫХ и состояло ЛИШЬ в том, чтобы ввести или
пьшеСТlt ЭТИ данные. Несмотря на то что в самом этом решении нет ничего не­
праВI1J1ЫIOГО, в С·Н предусмотрен более удачный способ выполнсния операций
ввода-вывода "KilГlCCOBbJX" ДШIН1>IХ: путем псрегрузки опсраторо~ ввода-вывода
i~«11 и"»",
в ЯЭЫI<(' С++ оператор
"«"
называется оператором. вывода ИЛИ вставки. по­
СКОЛЬКУ он I3ставляст символы в поток. Ar-rаЛОГIIЧflО оператор
"»"
называется
операmоро.м ввода или u~шлеч.еIlUЯ, IIOСКОЛЬКУ OIJ извлекает данные из потока.
Операторы ввода-выМда уже прегружеf-IЫ (В заГОЛОВl(е
<iostream»,
'11'0-
бы они МОГЛИ ВЫIЮJlНЯТЬ операции потокового ввода или вьшода данных любых
BcтpOelJHblX C++-тиrЮВ. Здесь вы узнаете: как определить эти операторы для сЬб­
CTBCll1lbJX
!.
классов.
ПОТОК
-
это J!UПlчеСJ(;iЯ абстракция. которая либо синтезирует, либо потребляет
IНlфОРМ'ЩИЮ. Файл
-
это фнзическиf:1 объект, I<ОТОРЫЙ содержит данные.
cout.
2.
Со СТ;Jндартным ВЫХОДНЫМ потоком саЯЗЮI 061оеl<Т
з.
Верно, С++-система IИЮJl<l-ОЫIlОД" ПОllдержипастся сложной lI('рархией классов.
С++: PYI(OBOACTBO ДМ, начинающих
527
Создание перегруженныx операторов вывода
в качестве простого ПРl1мера рассмотрим создание uператора Пblвода ДЛЯ сле­
дующей версии 100acca
ThreeD.
class ThreeD
public:
int х, у, z; // 3-мерные
ThreeD(int а, int Ь, int
координаты
с)
{
х
=
а;
у
=
Ь;
z =
о
1
С;
~s
};
Чтобы создать операторную функцию вывода для 06ъс!{тов типа
обходимо переrpузить оператор "«" для класса
способов.
ThreeD, не­
~
ThreeD. ВОТ ОДIШ из возможных ;+
:U
// Отображение координат Х, У, Z (оператор вывода
1/ для класса ThreeD).
ostream &operator«(ostream &stream, ThreeD obj)
{
stream
stream
stream
return
« obj .Х «
« obj.y «
« obj.z «
strearn; //
" , " ,.
" , " ,.
"\n";
возвращает
параметр
strеаш
Рассмотрим внимательно эту ФУНКllИЮ, поскольку ее СОJ1.сржююе характерно
для многих ФУНКЦИЙ вывода данных. Во-первых, отметьте, что согласно оG1>явле­
нию она возвращает ссылку на объект типа
ostream. Это ПОЗIlоляет I-IССIЮЛЬКО
операторов вывода объединить в одном составном выражсНl'-И. Затем обратитс
IНlИмание на то, что эта ФУНКЦИЯ имеет два параметра. Первый представляет со­
бой ссылку на лоток, кuторый используется 8 левой части оператора
"«".
Вто­
рым является объект, который С1'оит в правой части этого оператора. (При необ­
ходимости второй параметр также может иметь тип ссылки на о6ъс!,т.) Само тело
ФУНlЩИИ состоит из инструкций вывода ТрСХ значений координат, содержащихся
в объекте типа
ThreeD, и
инструкции возврата потока
strGarn.
Перед ва~ш короткая I1рограмма, в КОТОРОЙ демонстрируется ИСllользование
оператора вывода.
//
Д~монстрация
использования
да.
iinclude <iostream>
using namespace std;
-
перегруженного
оператора
выво­
528 МОДУЛЬ 11. С++-система ввода-вывода
...........................................................................................................................
- ....
class ThreeD
public:
int х, у, Zi / / з-о coordinates
ThreeD (int а, int Ь, int с) { х
а;
Ь;
у
z
с;
}
};
//
Отображение
координат Х,
У,
Z
(оператор вывода
/ / ДЛЯ класса ThreeD).
ostream &operator«(ostream &stream, ThreeD obj) // Оператор
/ / вывода .ц.п. lCJIасса ТhreeD.
obj.x «
obj.y «
« obj.z «
stream; //
" " ,.
" , " ,.
«
stream
stream
stream
return
«
"'п";
возвращает
параметр
stream
int main ()
ThreeD
а(l,
cout «
а
«
2, 3),
Ь
«
Ь(3,
С;
4, 5),
с(5,
б,
7);
// Испonьзование оператора вывода,
//
переrpу-.ениоrо .ц.п. lCJIасса ТhreeD,
// для вывода координат.
return
О;
Эта программа генерирует такие результаты.
1, 2, 3
3, 4, 5
5, б, 7
Если удалить код, относящийся конкретно к классу
ThreeD,
останется "ске­
лет", подходящий для любой функции вывода данных.
ostream &operator«(ostream &stream, class_type obj)
{
//
КОД,
ОТНОСЯЩИЙСЯ
return stream; //
к конкретному классу
возвращает
параметр
stream
С++: РУI<ОВОДСТВО ДЛ9t начинающих
Как уже отмечалuсь, для параметра
obj
529
разрешается использовать передачу
по ссылке.
В широком смысле конкретные действия функции вывода определяются про­
граммистом. Но если вы хотите следовать профессиона.льному стилю програм­
мированИII, то ваша функция вывода должна все-таки выводить информацию.
И потом, всегда-нелишне убедиться в том, что она возвращает параметр
stream.
~
о
ID
J5
ID
~
ID
ID
О
Использование функций- "друзей·· ДЛЯ перегрузки
операторов вывода
Three D.
В действительности ни фУIIКПИЯ вывода, ни функция
ввода не могут быть членами класса. Дело здесь вот в чем. Если ореrаtоr-функ­
ция является членом класса, левый операнд (неявно передаваемый с помощью
указателя
this) должен быть объектом класса, I<ОТОРЫЙ сгенерировал обраще­
ние к этой оператор ной функции. И это изменить нельзя. Однако при перегрузке
операторов вывода левый операнд должен быть потоком, а праоый
-
объектом
класса, данные которого подлежат выводу. Следователыro, перегруженные опе­
раторы вывода не могут быть функциями-членами.
В связи с тем, что операторные функции вывода не должны быть членами
класса, для которого они определяются, возникает серьезный вопрос: как пере­
груженный оператор вывода может получить доступ к ЗaJ<РЫТЫМ элементам клас­
са? В предыдущей программе перемеНllые Х, у Z были определены как открытые,
и поэтому оператор вывода без пр06лем мог получить к ним л.оступ. Но ведь со­
крытие данных
-
важная часть объеl<тно-ориентироuашIOГО IIрограммироваиия,
и требовать, чтобы все j{<lHHbIe были открытыми, попросту нелоrnчно. Однако
существует решение и для этой проблемы: оператор I3Ыlюда можно сделать "дру­
гом" класса. Если фУНКIlИЯ является "другом" lIекоторого к.nасса, то она получает
легалыIйй доступ к его рrivаtе-данным. Как можно объявить "другом" lUIacca
перегружеНliУЮ функцию вывода, покажем на при мере класса
//
//
Использование
оператора
friепd-функции
вывода
для
ThreeD.
перегрузки
«<).
tinclude <iostream>
using namespace stdi
class ThreeD (
int х, у, z; //
/1
3-мерные
~
u
s
в предыдущей l1рограмме l1ерегружеJ-Jная функция вывода не была определе­
на как член класса
::1:
координаты
рrivаtе-члены)
(теперь
это
'i
~
u
530 МОДУЛЬ
11. С++-система ввода-вывода
....................
-............................................................................................-............ .
public:
ThreeD(int а, int Ь, int с) { х = а; у = Ь; z = с; }
// Опера~ор вывода, определ.еКЫЙ Дn. xnасса ThreeD, ~еперь
/ / _nlle~clI ФУНJCцией-" дру:rои" и поэтому имеет ДОСТУП JC e:ro
/ / sахры'пDI д8иRым.
•
friend ostream &operator«(ostream &stream, ThreeD obj);
};
// Отображение координат Х, У, Z (определение оператора
// вывода данных для класса ThreeD).
ostream &operator«(ostream &strеаш, ThreeD obj)
{
stream
stream
stream
return
« " , " ,.
« " , " ,.
« "'п";
// Функция
« obj.x
« obj.y
« obj.z
stream;
возвращает
поток.
int main ()
ThreeD a(l, 2,3),
cout «
а
return
О;
«
Ь
«
Ь(З,
4, 5),
с(5,
6, 7);
с;
Обратите внимание lIа то, что переменныс х, у 11 Z В этой версии ПРОГРсL\lМЫ
являются закрытыми в классе
ThreeD,
тем нс :Io1CJ-Iее, операторная ФУIIКЦИЯ вы­
вода обращается к ним напрямую. Вот где проявляется велИl(ая СIIЛЗ "друж()ы":
объявляя операторныс ФУНlЩИИ ввода И вывода "друзьями" класса, для КОТО\ЮI'О
они опредеJlЯЮТСЯ, мы тем самым поддерживаем принцип инкапсуляции об1,ек­
тно-ориентированного программирования.
Перегрузка операторов ввода
Для переГРУЗКI1 опсраторов ввода используi:'lТе то г же метод, который ~Ibl IIР"-­
меняли при перегрузке оператора вывода. Например, следующий оператuр ввода
обеспечивает ввоД трехмер"ых координат. Обратите внимание на то, что он так­
же ВblВОДИТ соответствующее сообщение ДЛЯ ПОЛI>зователя.
С++: PYI(OBOACTBO ANI начинающих
1/
Прием трехмерных координат
531
(определение оператора ввода
1/ данных ДЛЯ класса ThreeD) .
istream &operator»(istream &stream, ThreeD &obj)
(
cout « "Введите ЗНqчения координат X,Y,Z: ";
stream » obj.x » obj.y » obj.Zi
rеturл streami
Оператор ввода должен возвращать ссылку на объект типа
istream.
Кроме
того, первый параметр должен представлять собой ссылку на объект типа
ream.
Этим типом определяется поток, указанный слева от оператора
ist-
"»". Вто­
рой параметр является ссылкой на переменную, которая принимает вводимое
значение. Поскольку второй параметр
-
ССЫJlКа, он может быть модифицирован
при В80де информации.
Общий формат оператора ввода имеет следующий вид.
istream &operator»(istream &stream, object_type &obj)
{
11
код операторной функции ввода данных
return streami
Использование функции ввода данных для объектов типа
Three Dдемонстри­
руется D следующей программе.
1/
Использование
перегруженного
оператора
ввода.
#include <iostream>
using namespace stdi
class ThreeD (
int х, у, Zi / / З-мерные координаты
public:
ThreeD(int а, int Ь, int с) ( х = а; У = Ь; z = С;
friend ostream &operator«(ostream &stream, ThreeD obj)i
friend istream &operator»(istream &stream, ThreeD &Obj)i
,
11 Отображение координат Х, У, Z (определение оператора
/1 вывода ДЛЯ класса ThreeD) .
ostream &operator«(ostream &stream, ThreeD obj)
532 Модуль 11. С++-система ввода-вывода
................................................................................................................................
stream
stream
stream
return
«
«
«
obj.x «
obj.y «
obj.z «
stream; //
" , " ,.
" , " ,.
"\п"
;
Функция
возвращает
поток
.
.
//
прием трехмерных хоордиват
(опредenеиие оператора ввода
/ / данных Дnll x.nасса ТhreeD) •
istrearn &operator»(istrearn &strearn, ThreeD &obj)
(
cout « "Введите значения координат X,Y.,Z: ";
strearn » obj.x » obj.y » obj.z;
return strearn;
int main()
ThreeD a(l, 2, 3);
cout «
а;
cin »
cout «
а;
return
О;
а;
Один из возможных вариаlJТОВ выполнения этой программы выглядит так.
1, 2, 3
Введите
значения
координат
X,Y,Z: 5 6 7
5, 6, 7
Подобно функциям выпода, функции ввода не могут быть членами класса, для
обработки. данных которого они предназначены. Их можно· объявить "друзьями"
этого класса или просто независимыми фУНКltиями.
За исключением того, что функция ввода ДОЛЖllа возвращать ссьшку на объект
типа
i s t rearn, тело этой функции может содержать все, что вы
считаете нужным
в нее ВКЛЮЧIПЬ. НО логичнее использовать операторы ввода все же по прямому
назначению, Т.е. ДЛЯ выполнения операций ввода.
С++: руководство для начинающих
533
~ПРОСЫ ДМI теJ<ущего J<ОНТРОЛЯ -=--------
:0
:~
:0
:~
:со
1.
Какие действия выполняет оператор вывода i1ЛЯ класса?
:0
2.
Какие действия выполняет оператор ввода для КЛ<1сса?
:0
:~
·СО
:со
з. Почему в качестве Оl1ераторных функций ввода н вывода данных некото­
рого класса часто используются функции-"ДГУ3hЯ"?'"
8-
действовали параметры форматирования, которыс С++-система ввода-выво­
да использует по умолчанию. Но программист может сам управлять форматом
представления данных, причем двумя способами. Первый способ предполагает
использование функций-членов класса
специального
типа, именуемых манипуляторами
освоение воз­
можностей форматирования с
ВАЖНОI
!'М' Форматирдвание доыI:lых
с использованием функций­
членов класса
ios
в системе ввода-вывода С++ каждый поток связан с набором флагов фор­
матирования, управляющих процессом форматирования информации. В клас­
ios
объявляется перечисление
fmtflags, u
котором определены следующие
значения. (Точнее, эти значения определены в классе
упоминалось выше, является базовым для КЛ<1сса
1.
2.
з.
ios_base,
который, как
ios.)
Оператор вывода помещает даныые в поток.
Оператор ввода извлекает данные из потока.
Функции-"друзья" часто используются в качестве операторных Функцю1 ввода и
вывода данных некоторого класса, поскольку они ПОЛУЧаЮТ доступ к закрытыM
данным этого класса.
:~
:~
До сих пор при вводе или uьшоде ИJlфОРМации в наших примерах программ
ios, а второй - функций
(manipulator). Мы жс начнем
функций-членов класса ios.
:0
:~
i!
Форматированный ВВОД-ВЫВОД данных
се
:У
:+
:+
:U
МОДУЛЬ 11. С++-система ввода-вывода
534
................................................................................................................................
adjustfield
basefield
boolalpha
dec
fixed
floatfield
hex
internal
left
oct
rigtt
scientific
showbase
showpoint
showpos
skipws
unitbuf
uppercase
Эти значения ИС'nОЛhЗУЮТСЯ для установки или очистки флагов форматирова­
ния. При использовании старого компилятора может оказаться. '!то он не опреде­
ляет тип перечисления
fmtf1ags. В этом случае фЛaIrl форматирования будут
кодироваться как цслочисленtlые lопg-значения.
Если флаг
skipws установлен. то при потоковом 680дедa.JШЫХ ведущие "lIРО­
бслъные" символы. или символы пропуска (т.е. пробелы, символы табуляuии и
новой строки), отбрасываются. Если же флаг :зkiрws не YCT3HoB.rieH. пробельные
символы не отбрасываются.
Если установлен. флаг
1 е f t. выводимые данные выравниваются по левому краю.
а если установлен флаг right - по правому. Еслп установлси флаг internal. '{ИС­
ловое значение ДОIЮJшяется n роGcлам и, }юторыми заполняется поле меЖil,У IIIIM и
знаком числа или символом основания системы счисления. Если IIИ ОДИII И:i :IТHX
флагов не установлен, реЗУЛI)Тат выравнивается по правому краю по умолчанию.
По умолчанию числовые аначения выводятся в десятичной системе СЧН'~Jlе­
)шя. Однако основание системы СЧllсления можнu 1131'\1еIl1ПLo.
oct
YCTalloBKa
ф.lага
приведет к 8ЫВОДУ результата в восьмеричном представлении. а установка
флага
ся
hex - в шестнадцатеричtlом. Чтобы при отображении результата вернуть­
к десятичной системе СЧllсления. достаточно установить флаг dec.
Установка флага showbase приводит '( отображению обозначения ОСНОШ!1Н1Я
системы счисления, в которой представляlOТСЯ числовые зиачения. Например.
если используется llIестнадuатеРИЧRое представление, то значение
бражено как
1F будет ото­
Ox1F.
По умолчанию при IIспользовании экспоненциального представлсния Чllсел
отображается строчный вариант буквы "е". Кроме того. при отображении шест­
надцатеричного значения используется также строчная буква "х". После ycтalloB-
1<If
флага
uppercase отображается ПРОПИСl-lоi,j вариант этих СIIМВОЛОВ.
showpos вызывает отображение ведущего :шака "плюс"
УстаНОDl.(а флага
пс­
ред ПОЛОЖJlТельными значениями.
УстаНО8ка флю'з
showpoint приводит к отображению десятичной тоtlКИ И
- IiУЖНЫ они или нет.
После установки флага scientific числовые 3liачения с плавающей точкой
отображаются в ЭКСllонеllЦИaJlЫJOМ представле1lИИ. Если уста1l0влен флаг fi:.:ed.
вещественные значения отображаются в обычиом предстаnлеШIII. ЕСЛII 11(' устахвостовых нулей ДЛЯ всех чисел с плавающей ТОЧI<ОЙ
С++: PYI(OBOACTBO ДМ! начинающих
535
новлен ни один ИЗ этих флагов, компилятор сам выбирает соответствующий ме­
тод представления.
При установленном флаге
uni tbuf содержимое буфера сбрасывается надиск
после каждой операции вывода данных.
g
111
Jj
Если установлен флаг
boolalpha, значения булева типа можно вводить или
выводить, используя ключевые словаtruе и false.
Поскольку часто прrfXОДИТСЯ обращаться к полям oct, dec и hex, на них l{О­
пускается коллективная ссылкаЬаsеfiеld. Аналогично поля left, right и internal можно собирателыю назвать adj ustfield. Наконец. поля scientific и
fixed можно назвать floa tfie ld.
111
~
О
111
111
О
:е
ф
....
(J
:s:
~+
u
Установка и сброс флагов форматирования
Для установки любого флага используется функция
setf (), которая являет­
ся члеllОМ класса ios. В самом общем виде ее формат выглядит так.
fmtflags setf(fmtilags flags);
Эта функция возвращает значение предыдущих установок флагов форматирования
и устанавливает их в соответствии со значением, заданным парамеТРОl\l f1ags. Напри­
мер, чтобы установить флаг
showbase, можно использовать такую инструкцию.
stream.setf(ios: :showbase);
Здесь элемент stream 0значает поток, параметрhl форматирования которого вы
хотите изменить. Обратите внимание на использование префикса
ios::
для
уточнения "ЮJaССОПОЙ" принадлежности параметра shоwЬаsе.Лоскольку пара­
метр
showbase представляет собой перечислимую константу, определенную в
ios, то при обращении к ней необходимо указывать имя класса ios. Этот
принцип относится ко всем флагам форматирования.
.
В следующей программе функция setf () используется для установки фла­
гов showpos и scientific.
// Использование функции setf().
классе
#include <iostream>
using namespace std;
int main ()
.{
//
Установха
фnаrов форматирования
11 с помо~ю функции setf().
cout.setf(ios: :showpos);
cout.setf(ios: :scientific);
showpos
и scient~c
536
Модуль 11. С++-система ввода-вывода
cout «
return
123 «
" " «
123.23 «
" ";
О;
При выполнении эта программа генерирует такие результаты.
+123
+1.232300е+ОО2
С помощью операции ИЛИ можно установить с.разу несколько нужных
флагов форматирования в одном вызове функции
Например, пред­
set f ( ).
ыдущую программу можно сократить, объединив по ИЛИ флаги
и
showpos. поскольку
set f () .
scient::.fic
в этом случае выполняется только одно обращение к
функции
cout.setf(ios::scientific I ios::showpos);
Чтобы сбросить флаг, используйте функцию uпsеtf
(),
прототип которой
выглядит так
void
uпsеtf(fmtfiаgs
flags);
В этом случае будут обнулены флаги, заданные параметром f1ags. (При этом
все другие флаги остаются в прежнем состояltии.)
Для того 'lТобы узнать текущие устаltОВКИ флагов форматирования, восполь­
зуйтесь функцией
flags (),
прототип которой имеет следующий вид.
fmtflags flags();
Эта функция возвращает текущее значение флагов форматирования для вызы­
вающего потока.
При использовании следующего формата вызова функции
flags ()
устанав­
ливаются значения флагов форматирования 11 соответствии с содержимым пара­
метРа flags и возвращаются их предыдущие значения.
fmtfiags flags(fmtflags f1ags);
Чтобы понять, как работают фушщии
flags () и uпsеtf (), раССМОТРИ~f с.ле­
дующую IIрограмму.
1/
Демонстрация использования функций
<iostream>
using namespace std;
#iпсludе
iпt
mаiп
()
flags()
и
unsetf().
С++: PYI<OBOACTBO ДЛfl начинающих 537
ios::fmtflags [;
= cout.flags(); //
f
С~вие фn~ов ФоркатировaвиR.
i f (f & ios::showpos)
cout «
else
cout «
"Флаг
showpos
установлен
"Флаг
showpos
сброшен
для
для
потока
потока
cout.\n";
cout.\n";
cout « "\пУстановка флага showpos для потока cout.\n";
cout.setf(ios::showpos); // Установка фnarо. форма~ов&RИR.
f
= cout.flags():
i f (f & ios: : showpos)
cout «
else
cout «
"Флаг
showpos
установлен
"Флаг
showpos
сброшен
для
для
потока
потока
cout.\n":
cout.\n":
cout « "\пСброс флага showpos ДЛЯ потока cout.\n":
cout.unsetf(ios::showpos); 11 Сброс фnarо. фор••~о.aaиR.
f = cout .flags () :
if(f & ios::showpos)
cout « "Флаг showpos
else
cout « "Флаг showpos
return
установлен для потока
сброшен ДЛЯ потока
О:
}
реэ~ьтаты выполнения этой программы таковы.
Флаг
showpos
Установка
Флаг
сброшен ДЛЯ
cout.
showpos
~Я
потока
установлен
ДЛЯ
потока
флага
showpos
потока
cout.
cout.
cout.\n":
cout.\n":
538
Модуль 11. С++-система ввода-вывода
Сброс
флага
Флаг
showpos
showpos
для
сброшен
для
cout.
потока cout.
потока
В этой программе обратите внимание на то, что тип
fmtflags указан с уточ­
ios: :. Необходимость этого уточнения вызвана тем, что
тип fmtfags определен в классе ios. В общем случае при использовании
няющим префиксом
имени типа или перечислимой константы, определенной в некотором классе,
соответствующее имя обязательно нужно указывать вместе с именем класса.
Установка ширины пом, точности и символов заполнения
Помимо флагов форматирования можно также устанавливать ширину поля,
символ заполнения и количеСТJJО цифр после десятичной точки (точность). Для
этого достаточно использовать следующие три фуикции, определенные в классе
ios: width (). fill ()
и
precision ().
По умолчанию выводимое значение имеет такую ширину поля (т.е. количество
занимаемых позиций), которая определяется количеством символов, необходи­
мых для его отображения. Но с помощью ФУJlКЦИИ width () можно установить
минимальную ширину этого поля. Вот как выглядит прототип ;>той функции.
streamsize width(streamsize w);
Функция
width () возвращает текущую ширину поля и устанавливает tJOIJУЮ
w. Некоторые реализации С++-компилятора требу­
равной значению параметра
ют, чтобы ширина поля устанавливалась перед каждой операцией вывода iщн­
ных. В противном случае ширина поля DЫDОДИМОГО значения будет установлена
по умолчанию. Если количество символов, из которых состоит выводимое значе­
ние, превышает заданную ширину поля, f1>аницы этого поля будут "нарушены".
Значения при выводе не усе каются. Тип
streamsize
определен как разно[:ид­
ность целочисленного типа.
После устаtЮВКИ минимального размера ширины поля оставшиеся (после
вывода значения) позиции (если таковые имеются) заполняются текущим СИМ­
волом заполнения (по умолчанию. используется пробел). Функция
fill ()
J)ОЗ­
вращает текущий символ заполнения и устанавливает новый, заданный пара­
метром ch. Этот символ используется для дополнения результата символами,
недостающими для достижения задаtlНОЙ ширины поля. Прототип этой функ­
ции выглядит так.
char fill(char ch);
Функция precision
() возвращает текущее количество цифр, отображаемых
после деСЯТИЧIIОЙ точки, и устаliаnливает новое текущее значение точности рав­
HI;>IM содержимому параметра р. (По у~юлчан"ю после десятичной точки отобра-
С++: PYI(OBOACTBO МЯ начинающих
539
жается шесть цифр.) Некоторые реализации C++-КОМПИЛЯiора требуют, чтобы
значение точности УС1·анавлипалось перед каждой операцией вывода значений с
плавающей точкой. В противном случае значение точности будет использовано
streamsize precision(streamsize
:<1
:0
~~
•
р);
:
I
О
:<1
Рассмотрим программу, которая демонстрирует flСПОЛЬЗОВaJil1е этих трех
ФУНКItиЙ.
:
0
.0)
: ш
:0
:~
ДемонстраЦИR использования
и
:0
·Ш
по умолчанию.
11
11
-
ФУНКЦИЙ
width(), precision()
fill () .
~~
:U
.s:
~~
:+
;U
<iostream>
tlsing namespace std;
#iлс1udе
:.лt
П1аiл
()
(
r
cout.setf(ios::showpos);
cout.setf(ios::scientific);
cout « 123 « " " « 123.23 «
/1
Уставовка точности.
cout.precision(2); 11
11
Две
цифры после десятичной точки.
Установка ширииы полн.
cout.width(10);
cout « 123 « n
сои t. width (10) ;
cout « 123.23 «
//
"\п";
Установка
//
Все
поле
//
Установка
состоит из
10
символов.
";
ширины поля равной
10.
"\п";
CJOIIIOJIII
ЗIIпоnнеНИJl:.
cout.fi11("');
// ДЛR заполнителя возьмем символ ",и
cout.width(10); 1/ Установка ширины поля равной 10.
cout « 123 « " ";
cout.width(10);
// Установка ширины поля равной 10.
cout « 123.23;
return
О:
Проrpамма генерирует такие результаты.
Модуль 11. С++-система ввода-вывода
540
+123.
+1.232300е+ОО2
+123
#*####+123
+1.23е+ОО2
+1.23е+ОО2
Как упоминалось выше,
n
некоторых реализациях необходимо переустанав­
ливать значение ШИрИНЫ поля перед выполнением каждой операции ВБIВОДа. По­
этому функция
width () в предыдущей программе ВЫЗblвалась несколько раз.
В системе ввода-вывода С++ определены и перегруженные версии ФУ}llЩИЙ
width (), precision () и fill (), которые не изменяют текущие Зliачения со­
ответствующих парамеТрОD форматирования и используются только для их по­
лучения. Вот как выглядят их прототипы.
char fill () ;
streamsize width();
streamsize precision();
~просы ДЛЯ текущего контроля ________
1. Каково назначение флага boolalpha?
2. Какие действия выполняет функция set f ()?
3. Какая функция используется для установки символа заполнения поля?·
--
.... 'IIURТW
ВАЖ~i
1Ft
ИСПQАьзо;ваw и е маНJ4D~АЯТОРОВ
ввода-вывода
в С++-системе ввода-вывода предусмотрен и второй способ изменения пара­
метров форматирования, связанных с потоком. Он реализуется с помощью спе­
циальных функций, ваэываемых Ма1LUnул.яmорамu, которые можно включать в
выражение ввода-вывода. Стандартные манипуляторы описаны в табл.
1.
Если флаг
boo1alpha установлен, булевы
true и false.
11.1.
значения вводятся и выводятся с ис­
пользованием слов
2.
Функция 5 е t f () устанавливает один или несколько флагов форматирования.
З.
ДЛЯ устаноВlШ символа заполнения поля используется функция
fill ().
С++: PYI<OBOACTBO AMI начинающих
Таблица
541
11.1. Стандартные С++-манипуляторы ввода-вывода
маНИ[1ШRТОQ
Назночение
Ф~нкуия
boolalpha
dec
endl
Устанавливает флаг
boolalpha
Устанавливает флаг dec
ВВОД-ВЫВОД
ВЫВОДИТ символ новой строки и "сбрасывает" по-
Вывод
Ввод-вывод
Устанавливает флаг
(,\
о
')
fixed
са
са
Вывод
ВЫВОД
«Сбрасывает" поток
ВЫВОД
Устанавливает флаг
hex
Устанавливает флаг internal
Устанавливает флаl' 1 е f t
Обнуляет флаг boolalpha
Обнуляет флаг showbase
Обнуляет флаг showpoint
Обнуляет флаг showpos
Обнуляет флаг skipws
Обнуляет флаг uni tbuf
Обнуляет флаг uppercase
Устанавливает флаг oct
Ввод-вывод
Обнуляет флаги, заданные в параметре f
Ввод-вывод
Устанавливает флаг
Вывод
Устанавливает флаг
r igh t
s cie n t ific
Устанавливает основание системы счисления
равкой значеюпо
Вывод
Вывод
Ввод-вывод
Вывод
Вывод
Вывод
Ввод
ВЫВОД
Вывод
Ввод-вывод
Вывод
Вывод
base
Устанавливает символ-заполнитель равным
Вывод
значению параметра сЬ
Устанавливает флаги, заданные R параметре f
Ввод-вывод
Устанавливает количество цифр точности
Вывод
(после десятичной точки)
Устанавливает ширину поля равной значению
параметра
са
6
О
наго с потоком, на соответствующее устройство
Вставляет в поток ну левой символ
са
J!
<i
ТОК, Т.е. переписывает содержимое буфера, свя~ан-
ends
fixed
flush
hex
internal
left
noboolalpha
noshowbase
noshowpoint
noshowpos
noskipws
nounitbuf
nouppercase
oct
resetiosflags(
fmtflags f)
right
scientific
setbase(
int bdse)
setfill (
int сЬ)
setiosflags(
frntflags f)
setprecision(
int р)
setw(int w)
о
g
Вывод
w
showbase
showpoint
showpos
skipws
unitbuf
uppercase
Устанавливает флаг
showbase
showpoint
Устанавлиnaет флаг showpos
УСТaJLавливаетфлаг skipws
Устанавливает флаг uni tbuf
Устанавливает флаг и рре rca s е
Вывод
Устанавливает флаг
Вывод
ws
llgonycкaeт ведущие "ПQQБСJlьные" СИМВОЛЫ
Ввоn
Вывод
Ввод
Вывод
Вывод
О
~
...
ф
u
:s:
у
+
+
U
542
МОДУЛЬ 11, С++-система ввода-вывода
Манипулятор используется как часть выражения ввода-вывода. Вот пример
ПРOl"раммы, в которой показано, как с помощью манипуляторов можно управ­
лять форматированием выводимых данных.
1/
Демонстрация использования манипуляторов ввода-вывода.
#include <iostream>
#include <iomanip>
using паmеврасе std;
int main ()
(
setprecision(2) « 1000.243 «
setw(20) « "Привет всем!";
cout «
cout «
return
endli
О;
При выполнении программа генерирует такие результаты.
1е+ООЗ
Привет
всем!
Обратите внимание на то, как используются манипуляторы в цепочке tJllepaций ввода-вывода. Кроме того, отметьте, что, если манипулятор вызывается без
аргументов (как, например, манипулятор
endl
в нашей программе), то его имя
указывается без пары круглых скобок.
В следующей программс используется манипулятор
новки флагов
/1
scientific
и
Использование манипулятора
setiosflags(),
#include <iostream>
#include <iomanip>
using namespace stdi
int main ()
{
cout «
setiosfiaqs(ios::showpos) «
setiosfiaqs(ios: :scient~c) «
123 «
return
О;
setiosflag,s ()
showpos.
" " «
123.23;
для уста­
С++: PYI<OBOACTBO ДМl начинающих
543
А в этоИ программе демонстрируется использованис ~/lIIlш!улятора W5, I<UTO-
рый пропускает ведущие "пробеJlьные" символы IlрИ ВВО/1:С строки 13 массив
11
Пропуск·начальных "пробельных"
s:
символов.
-
~include <iostream>
using namespace 5td;
о
~
int main ()
~
u
:s:
u
+
char 5[80J;
:+
:u
cil1
»
cout «
ВАЖНОI
_
»
ws
S; 11 ИСПОJIЬзование мa.виnУnЯ'rора ws.
5;
СО.здаыме,J:оБСIвеыwbIX
манипуЛSlТОРНЫХ функций
Программист может создават/) соБСТJlt'lIllые маНИПУЛЯТОРlfЫС функции. Су­
ществует два Тlша маШШУРЯТОРНblХ ФУНКЦИЙ: ПРЮJИмающие и не l1рИl1имающие
аргументы. Для создания парамеТРИЗ0ваННblХ манипулЯ1'ОРОВ используются ме­
тоды. рассмотрение которых ВЫХОДИТ::Iа рамки этой кннПf. Однако создание ма­
Нlшуляторов, которые не IfМСЮТ параМСТРОIJ, не вызывает особых трудностей.
Все ма'l-/unулятор"ые фУIl'КЦUU IJыо()да данных без парамt:'ТРОП имеют следую­
щую
CTPYJ<TYPY.
ostream
&mалiр_лаmе(оstrеаm
&streaт)
{
11
код манипуляторной
ФУНRЦИИ
return stream;
Здесь элемент
manip_ пате
означает имя маНИПУЛЯТОРii. В(lЖНО понимать, что,
несмотря на то, что 1\·lаНИПУJJЯТОР ПРИlIим.ает в качестве единственного аргумента
указатель на пото!(, КОТОРblЙ он обрабаТblвает, при использовании манипулятора
в реЗУJI},тнрующем Вblражении ввода-вывода аргументы не указываются вообще.
544
МОДУЛЬ 11. С++-система ввода-вывода
в следующей программе создастся манипулятор
setup (),
который устанав­
ливает флаг выравнивания по левому краю, ширину поля равной
10
и задает в
I.ачестве заполняющею символа знак доллара.
11
11
Соэдание
манипулятора
для
форма'J'ирования
ВЫВОДИМОЙ
информации.
#inc1ude <iostream>
#inc1ude <iomanip>
using namespace std;
ostream &setup(ostream &stream) 1/
Оnpe~enевие канипу~кой
(
фУВХЦИИ.
/1
stream.setf(ios: :left);
stream « setw(lO) « setfi11('$');
return stream;
int main ()
cout «
return
10 «
"11
«
setup «
10;
О;
Собствешrые манипуляторы полезны по двум причинам. Во-первых, иногда
возникает необходимость выполиять операции ввода-вывода с использованием
устройства, к которому НИ один из встроенных манипуляторов не применяется
(например, плоттер). В этом случае создание собственных манипуляторов сдела­
ет вывод данных на это устройство более удобным. Во-вторых, может оказа1'ЬСЯ,
что у вас в программе не которая последоватеЛьность инструкций повторяется
несколько раз. И тогда, как показано в предыдущей програМме, вы можете объ­
единить эти операции в один манипулятор.
Все Ма1щnуляторuые фуuкцuu ввода данных без параметров имеют следую­
щийформат.
istream
&mапiр_лаmе(istrеаm
&stream)
{
/1 КОД манипуляторной
return stream;
функции
С++: PYI<OBOACTBO ДМ! начинающих
Например, в следуюшей I1РOl"рамме создается манипулятор
545
prompt ( ) . Он на­
стривает входной поток на прием данных в шестнадцатеричном представлении и
о
отображает для пользователя наводяшее сообшение.
//
//
Создание
манипулятора
для форматирования
g
ID
вводимой
~
информации.
~
#include <iostream>
#include <iomanip>
using паmеэрасе std;
о
::!:
Ф
t;
S
U
.+.
u+
istream &prompt(istrearn &stream)
cin » hex;
cout « "Введите
число
в
шестнадцатеричном
формате:
";
return streami
int main ()
int ii
cin » prornpt »
cout « ii
return
i;
О;
Помните: очень важно, чтобы ваш манипулятор возвращал потоковый объект
(элемент
stream). В противном случае этот манипулятор нельзя будет исполь­
зовать в цепочке операций ввода или вывода.
ФайАовый ВВОД-ВЫВОД данных
в С++-систсме ввода-вывода также предусмотрены средства ДЛЯ выполнения
операций с фаЙла:-'1II. Файловые операции Dвода-вывода можно реализовать по­
сле включения 1) программу заrОЛО8ка
< f s t rearn>, В котором определены все не­
обходимые для этого классы и значения.
546
Модуль 11, С++-система ввода-вывода
.•••••...•........•..••••..•..•••.•••••.•••..•.•.•.••..••.•.•••.• ............................... ' ••••••••.•.•
~
о.
• •• . . •• . •••• ••••
~просы для текущего I<оНтрОля ______~I1.
Какие действия выполняет манипулятор
endl?
2.
Какие действия выrlOлняет манипулятор
ws?
3.
Можно ли использовать манипулятор ввода-вывода как часть выражения
ввода-вывода?·
__====,_==_____
.-~.~==т
. .- -. . . . . .
~~~а~_~·~·-.s8n
--ТnТnа8
_ _. . . . . ._ _. .~_ _ _ _. ._ _. . .
в С++ файл открывается путем связывания его с потоком.
KaI<
вы знаете, су­
ществуют ПОТОI<И трех ТI1110В: ввода, вывода и ввода-вывода. Чтобы открыть вход­
НОЙ 110ТОК, необходимо объявить 110ТОКОnЫЙ объект типа
тия ПЫХОI1НОГО потока IIУЖНО объявить поток класса
ifstream. Для откры­
ofstream. ПОТОК, который
предполагается ИСIlОЛI,:юuать для операций как ввода, так и вывода, должен быть
объявлен как объект класса
fstream.
Например, при выполнении следуюшего
фрагмента кода будет создан ВХОДIIОЙ поток, выходной и поток, позволяющий
выполнять операции в обоих направлениях.
ifstream in;
//
ofstream out; //
fstream both; //
входной
поток
ВЫХОДНОЙ поток
лоток
ввода-вывода
Создав ПОТО1<, его нужно связать с файлом. Это можно сделать с помощью
ФУNКЦИИ ореп
ция-член ор~п
( ) , приче~ в каждом из трех потокооых
( ) . Представим их прототипы.
классов есть своя фУНК­
void ifstream::open(const char *fllename,
ios::openmode mode = ios::in);
void ofstream::open(
const char *fllename,
ios::openmode mode = ios::out I ios::trunC)i
1.
2.
Манипулятор
endl
обеспечивает переход на новую строку.
Манипулятор ws обеСl1е'fИШlет пропуск ведущих пробельных символов при вво­
де данных.
3.
да, манипулятор ввода-вывода можно использовать как часть выражения ввода­
вывода.
С++: PYI<OBOACTBO ДЛ~ начинающих
547
void fstream::open(
const char *fllелаmе,
ios::openmode тode = ios::in I ios::out)i
Здесь элемент Лlепаmе оэrtачает имя файла, которое может включать специфи­
катор пути. Элемеrп
mode
определяет способ открытия файла. Он должен при­
нимать одно ИЛИ несколько ЗI·raчениЙ переч.исления
лено в классе
ios. Ниже
openmode,
которое опреде­
о
+
+
u
openmode можно объединять посредством
логического сложения (ИЛИ).
ios: : арр
в параметр тode обеспечит присоединение
к концу файла псехвыводимых данных. Это значение можно применять только
к фаЙла.\f, открытым для вывода данных. При открытии файла с использовани­
ios: : а te поиск будет начинаться с конца файла. Несмотря на это,
операции ввода-вывода могут по'-прежнему вып.11Iiятьсяя по всему файлу.
.
Значение
ios: : in говорит о том, что данный файл ОТКРЫ8ается для ввода
данных, а значение ios: : out обеспеЧИDает открытие файла для их вывода.
ЗначеНllе ios: : binary ПОЭlюляет открыть файл в двоичном режиме. По
умолчанию все файлы открываются
() TCJ(CTOBOM режиме.
Как УIlОМИНалось выше,
n текстовом режиме могут ПрОИСХОДllТh некоторые преобразования символов
(например, послсдовательнос~ь, состоящая из символов возврата каретки и пере­
хода на новую строку, может быть преобразоnзва в символ новой строки). Но при
ОТJ<PlхТИИ файла в двоичном режиме Jlикакого преобразования символов не
Dbl-
полняется. Следует иметь 8 виду, что любой файл, содержащий форматирован­
ный текст или еще нео6работанные данные, можно открыть [(ак в двоичном, Ta~
и в текстовом режиме. Единственное различие между этими режимами состоит в
npеобразовании (или нет) символов.
Использование значения
ios: : truпс
приводит к разрушению содержимого
файла, имя которого совпадает с параметром filename, а сам этот файл усекается
до Ilулевой длины. При создании 8ЫХОДНОГО потока типа
ofstream любой суще­
ствующий файл с ИМенем filename автоматически усекается до нулевоii длины.
При выполнении следующего фрагмента кода открывается обычный выход­
ной файл.
~'"
~
u
:s:
u
Несколько значений пере'lИсления
ем значения
'":а
'"
'"::;
приuедены значения это(·о l1ерсчисления.
ios: :арр
ios::ate
ios: : binary
ios: : in
ios: : out
'ios: : trunc
Включение значения
g
548 МОДУЛЬ 11. С++-система ввода-вывода
................................................................................................................................
ofstream out;
out.open("TecT") ;
Поскольку параметр
mode
функции ореп
()
по умолчанию устанавливается
равным значению, соответствующему типу открываемого потока, впредыдушем
примере вообще нет необходимости задавать его значение.
Не открытый в результате Heyдa'lНoгo выполнения функции ореп () поток
при использовании в булевом выражении устанавливается равным значению
ЛОЖЬ. Этот факт может служить для подтверждения успешного открытия фай­
ла, например, с помощью такой if-ИIlСТРУКЦlШ.
if (!mystream)
cout « "Не удается
// обработка ошибки
открыть
фаЙл.\п";
Прежде чем делать попытку получить доступ к файлу, следует всегда прове­
рять результат вызова функции ореп
().
Можно также проверить факт успешного открытия файла с помощью функ­
is_open (), которая
tream. Вот ее прототип.
ции
является членом КЛi\ССОВ
fstream, ifstream 11 01's-
bool is_open () ;
Эта функция возвращает значение ИСТИНА. если поток связан с открытым
файлом, и ЛОЖЬ - в противном случае. Например; используя следующий код,
можно узнать, открыт ли в данный момент потоковый объект
mystream.
if(!mystream.is_open(»
cout « "Файл не OTKpblT.\n";
11 .. Хотя вполне корректно использовать функцию ореп
()
для открытия файла,
в большинстве случаев это делается по-другому, поскольку классы
ofstream
и
fstream
ifstream,
включают КОНСТРУI<ТОРЫ,которые автоматически откры­
вают заданный файл. Параметры у этих
KOHCTrYKTOpOB и
их значения (действую­
щие по умолчанию) совпадают с параметрами и соответствующими значениями
функции ореп
( ) . ПОЭТО:\fУ
чаще всего файл открывается тш(, [(ак показано в сле­
дующем при мере.
ifstream mystream("myfile"); 11
файл открывается
для
ввода
Если по какой-то причине файл OTKpыТI) невозможно, потоковая переменная,
связываемая с этим файлом, устанавлиuается равной значению ЛОЖЬ.
С++: PYI<OBOACTBO ДМI начинающих
549.
...........................................................................................
...................................
~
Чтобы закрыть файл, используйте функцию-член
закрыть файл, связанный с потоковым объектом
close (). Например, чтобы
mystream, -используйте такую
~
ивструкuию.
mystream.close():
Функuия
]j
ер
close () не имеет парамеТрОD
11 не возвращает IIИКaI<ОГО значения.
g
ID
ID
О
~
ВАЖНОI
11';' Ч:r.eыJ4eJL30,I:II4Cb..lsк.c:rо вых фQЙAов
()
:s;
()
I
Проще всего считывать данные из текстового файла или записывать их в Hero
с помощью операторов
етен :запись В файл
//
Запись
"«" 11 "»".
test
данных
в
Например, в следующей программе выполня­
целого числа, зtiaЧel-lИЯ с плавающей точкой и строки.
файл.
#include <iostream>
#include <fstream>
using namespace std;
int main ()
ofstream out("test"); //
Создаем файл с именем
test и
// открываем его ДnR Bwвoдa даннкх.
i f (!out)
cout « "Не
return 1;
out «
10 «
удается
" " «
открыть
123.23 «
фаЙл.\n";
"\n"; //
//
out «
"Это короткий
out.close();
return
Выводим д&lUUo18
В файл.
текстовый Файл.";
//,Захрwваем файл.
О:
Следующая программа считывает целое число,
floa t -значение, символ и
ку из файла, созданного при выполнении предыдущей программы.
стро­
+
+
u
550
//
Модуль 11. С++-система ввода-вывода
Считывание данных из
файла.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
char ch:
int i:
float f;
char str[80];
ifstrearn in("test"); // О'1'!СрlolВаеи фaйn AnК
i f (! in)
cout« "Не удается открыть ФаV..л.\п";
return 1:
in
in
in
in
»
»
»
»
cout «
cout «
i; //
ввода даlUAlX.
СЧИ'1'ЫВаек даккые из фВЙnа.
f:
ch;
str;
« " " «
str;
i
in.close(); //
return О:
f
«
" "
«
ch «
"\n";
ЗаКРlolВаек фвйn.
Следует иметь в виду. что при использовании Ol1epaTOpa "»" ДЛЯ считывания
данных И3 текстовых файлов происходит прео(JразоваJiие некоторых символов.
Например. "пробeJIЫlые" символы опускаются. Если необходимо предотвра­
тить каЮfе бы то ни было преобразования символов, откройте файл в ДВШIЧНОМ
реЖlIме доступа. Кроме того, помните. что при использовании оператора
"»"
ДЛ>I СЧI1ТЬШ~НИЯ строк ... ввод прекращается при обнаружении первого "пробель­
ного" символа.
С++: PYI<OBOACTBO ДМ] начинающих
551
Спросим у опытного программиста
Вопрос.
какрa:rыlнялосьь в модуле
1,
g
С++ является cynep.',f1IOJlreCnfBlW языка С Мне
Il3вестно, .,то в С оnределе"а
,·o6cmBelllllIR CllcmeAla 8водll-'ЫВода. То/да ВО'1-
ш
::о
НlUШem вопрос, ...ожет ли C++-lJрогрtlJlfAII,сm использовать C-систeJl.У ввода­
ш
~т
выtюдa? Если да. то имеет ЛJl СА'Ь'СlI ее nримеllеlluе в C++-IIРОграм.мох?
Ответ.
На первый вопрос ответ ПОJlОЖНТСЛЫIЫЙ: C-СlIстсма ввода-вывода ДОСТУII­
о
~
наДJlЯ С++-программистов. Но на второй вопрос OTBe'IY: "Нет". C-сНLтсма
@
u
:s:
впода-вывода не является объектно-ориентировашюЙ. Поэтому для при­
менеиия в С++-программах больше подходят C++-среДfтва ввода-выво­
~
да. Однако С-система ввода-вывода по-прежнему широко используется
+
+
u
благодаря tIpOCTOTe 11 иебольшим расходам системных ресурсов. Таким
образом, ДЛЯ ряда узкоспециаJшзированных ЩЮГРLlММ она может оказать­
ся ВПОЛJiе удачным выбором. Подробную инфuрмацшо о С-системе ввода­
вывода можно найти в моей книге Полный справочник по С+ + (М.: Изда­
тельский дом "Вильямс").
~просы ДЛЯ текущего контролSl-__. --=----1.
Какой класс используется для создания ВХОДНОЮ файла?
2.
С помощью какой функции можно открыть файл?
З. Можно ли считывать информацию из файла и записывать о него, исполь­
..
зуя операторы
"«" и "»"7'"
1
_
:w
ы___
~.
...
ВАЖНОI
lIdJ::IеформаJИРQваИ~ВВОД";,ВЫВQД
данных в двоичном режиме
Несмотря на простоту чтения
(11
записи) форматироuанных Tel(CTOnWX фаi:j­
лов (подобных тем, которые ИСПОJ1l,:Юl3аЛI1СЬ
13
предыдущих примерах), они не
1.
Чтобы создзiЪ входной файл, IIсполь;!yiiте класс
2.
Чтобы открыть файл, используйте KOH(11»)'KTOP соотпеТСТВУlOщего класса или
ФУНКЦIIЮ ореn
з.
Д;J.
() .
onepaTopL,1 '.«" 11 "»"
фаiiла
I1
:1;JПИСJl
ifstream.
n IH'rO.
МОЖНО J.lСI[()ЛI,ЗО"3ТI, для считывания I111формации 113
552
МОДУЛЬ 11. С++-система ввода-вывода
являются наибол~е эффективным способом обработки файлов. Иногда прOt:ТО
необходимо сохранять неформаТllроваllные двоичные ланные, а не текст. Поэто­
му С++ поддерживает ряд функций файловOI"О ввода-вывола в двоичном режи­
ме, которые могут выполнять операции без форматирования данных.
Для выполнения двоичных операций файлового B80дa~BЫBoдa неоБХОДIIМО
открыть файл с исrЮЛЬЗОВalшем спецификатора режима
ios: : binary. Необ­
ходимо отметить, что функции обработки неформатированных файлов могут
работать с файлами, открытыми в текстовом режиме доступа, но при этом могут
иметь место преобразования символов, которые сводят на нет основную цель вы­
полнения двоичных файловых операций.
В общем случае сущестоует дна способа записи неформатированных двоичных
данных в файл и считывания их из фаЙла. Первый состоит D использовании функ­
ции-члена
put ()
(для записи байта в файл) и функции-члена
get ()
(для считыва­
ния байта из файла). Второй способ предполагает применение ~блочных" С++-функ­
ций ввода-вывода
read () и wr i te ( ) . Рассмотрим каждый способ в отдельности.
Использование функций
Функции
get ()
и ри t
()
ge t () и ри t () имеют множество форматов, но чаще всего исполь­
зуются следующие их версии:
istream &get(char &ch);
ostream &put(char Ch)i
Функция
get () считывает оJJ,ин символ из соответствующего потока и помеща­
ет его значение в перемевную ch. 0иа возвращает ссылку на поток, связанный с
предварительно открытым файлом. При достижении конца этого файла значе­
ние ссылки станет равным нулю. Функция
put () эаl1исьшает символ ch в поток
и возвращает ссылку на этот поток.
При ВbJПОЛliении следующей I1рограммы на экраи будет выведено содержи­
мое любого заданного файла. Здесь ИСПОЛl>зуrтся ФУIIКЦИЯ
//
Отображение
содержимого
файла
tinclude <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[J)
char Chi
с
get ( ) .
помощью ФУНКЦИИ
get().
С++: руководство для начинающих
if(argc!=2) {
cout « "Применение:
return 1;
553
имя_про граммы <имя_Файла>\п";
g
ID
::iI
ID
ifstream in(argv[l}, ios::in
if(!in) {
cout « "Не
return 1;
удается
открыть
ios::binary); 11 OтKp~aeм
11 ф&йn в двоичном режиме.
~
CII
CII
О
~
~
u
:s:
ФаЙЛ.\п";
Ч
+
+
u
11 C~aeM ~aнкыe ~o тех пор, пока не бу~ет ~остигнут
1 1 конец ф&ЙJlа-.
while(in) ( 11 При достижении конца файла потоковый
1/ объект in примет значение false.
in.get(ch);
if(in) cout « ch;
in . close () ;
return
О;
Рассмотрим цикл
while.
При достижении конца файла потоковый объект
in
примет значение ЛОЖЬ, которое и остановит выполнение этого цикла.
Существует более короткий вариант цикла, предназначенного для считыва­
ния и отображения содержимого файла.
while(in.get(ch»
cout « ch;
Этот вариант также имеет право на существование, поскольку функция
возвращает потоковый объект
значение
in,
get ()
который при достижении КOIща файла примет
false.
В следующей программе для записи строки в файл используется функция
put ().
11 Использование
#iлсludе
функции
<iostream>
put()
для
записи
строки в
файл.
554
Модуль 11. С++-система ввода-вывода
#inc1ude <fstream>
using namespace std;
int main ()
{
char
*р =
"Всем привет!\п";
·ofstream out("test", ios::out I ios::binary);
i f (!out)
cout « "Не удается открыть фаЙл.\п";
return 1;
// Записываем символы до тех пор, пока
// символ завершения строки.
whi1e(*p) out.put(*p++); // Записываем
/ /
не
встретится
С'1'року 8
ПОМОЩЬЮ ФУИICЦИИ
// каких-либо
// сииволов.
фaйJ1 с
put ()
без
npеобразовавий
out. close () ;
retur.n
О;
После выполнения этой программы файл
test
будет содержать строку Всем
привет! ,за которой будет следовать символ новой строки. Никакого пре06разо­
вания СИМВО)ЮIJ здесь не происходит.
Считывание и запись в файл блоков данных
Чтобы считывать и :iаГlисьшаТI, в файл БЛОКII ДПOliЧНЫХ ДaJЩЫХ, исполь~уйте
функции-члены
read ()
11
wr i te () . Их
прототипы имеют следующий вид.
istream &read(char *buf, streamsize лит);
ostream &write(const char *buf, streamsize
Функция
read ()
лит);
считывает л ит байт ДaJlIIЫХ из связанного с файлом потока
и помещает их в буфер, адресуемый параметром buf. Функция
wri te ()
запи­
сЬ/вает л ит байт даlШЫХ в связаниы й с фаЙJlО~' поток из буфера, адресуемого па­
pa.~eТPOM buf. Как УПОМIIНалось выше, тип
s t reams i ze определен б"блиотекой
01-1 позволяет хранить
С++ как некоторая разновидность ЦС.lJOЧI1СЛСIiНОГО типа.
С++: руководство ДЛЯ начинающих
555
самое большое количество байтоп, которое может быТl) передано в. процессе лю­
бой операции ввода-вывода.
При выполнении следующей программы сначала в файл записывается массив
целых чисел, а затем он же считывается из файла.
о
g
са
Ji
са
//
Использование функций
read()
и
~
write() .
•
са
са
*inc1ude <iostream>
finclude <fstream>
using паmезрасе stdi
О
:::1:
...
Ф
U
s:
u,
+
+
int main ()
u
{
int п[5] = (1, 2, 3, 4, 5};
register int i;
ofstream out("test", ios::out I ios::binary);
i f (! out)
cout « "Не удается открыть фаЙл.\п";
return 1;
out.write«char *) &п( sizeof п); // Зanиcwваеи бnох данных.
out_c1ose ();
for(i=O; i<5; i++) //
n[i) = О;
очищаем массив
i05: :binary)
ifstream in("test", ios::in
i f ( ! in)
cout « "Не удается открыть фаЙЛ.\П"i
return 1;
in_read«char
*)
&п,
for(i=O; i<5; i++)
cout «
n(iJ «
.
sizeof
//
//
".,
п);
//
Считываем бпох дaквux.
Отображаем значения,
из
файла.
i
считанные
556
МОДУЛЬ 11. С++-система ввода-вывода
in. close () ;
return
О;
Обратите внимание на то, что в инструкциях обращеНIIЯ к <4Ункциям
и
wr i te ()
read ()
выполняются Оllерации приведения типа, которые обязательны при
использовании буфера, определенного не в виде СИМllОЛblЮГО массива.
Если конец файла будет достигнут до того, как будет считано пит СИМВОЛ:JВ,
ФУНКЦИЯ
read () просто прекратит выполнение, а буфер будет содержать СТО.1Ь­
ко символов, сколько удалось считать до этого момента. Точное количество счи­
танных символов можно узнать с помощью еще одной функции-члена
gcoun t ( ) ,
которая имеет такой прототип.
streamsize gcount();
Функция
gcoun t ()
возвращает количество символов, считанных
n IIроцессе IIЫ­
полнения последней операции ввода данных.
~просы для текущего контроля _______,._
1.
Какой спецификатор реЖlIма ИСПОЛl>зустся при ОТКРЫТИI1 файла для счи­
тывания или записи ДВОИЧJJЫХ данных?
2. Каково назначение ФУНКЦИИ get ()? Каково назначение функции put ()?
3. С помощью какой функции можно считать блок данных?·
-
.-
ввода-вывода
С++-система ввода-вывода содержит множество других полезных ФУНlшиЙ.
Рассмотрим некоторые из них.
1.
Чтобы считать или записать двоичные данные, используйте спецификатор реж ItMa
ios: : binary.
2.
Функция
З.
Чтобы считать блок данных, Itсполь;~уйте ФУНКIIИЮ
get ()
считывает символ, а ФУНКЦIIЯ
put ()
записывает символ.
read () .
С++: PYI<OBOACTBO Дл~ начинающих
557
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• •••••••••••••••• J •••
Версии функции
get ()
ПОМИМО приведеНIIОГО выше формата использования фушщии
суще­
стпуют и другие ее персгруженные версии. ПРlIведем прототипы для трех из них,
g
используемых чаще всего.
ID
get ()
2i
istream &get(char *buf, streamsize
istream &get(char *buf, streamsize
int get () ;
~
пит);
пит,
ID
ID
char delim);
О
~
Первая версия позволяет считывать символы в массив, заданный параметром
buf, ДО тех ПОР, пока либо не будет считано num-1 символов, либо не встретится
символ новой строки, либо не будет достигнут конен файла. После выполнения
функции
get ()
массив, адресуемый параметром buf, будет иметь завершающий
нуль-символ. Символ новой строки, если таковой обнаружится во входном по­
токе, не извлекается. О" остается там до тех пор, пока II~ ВЫIlОЛIlИТСЯ следующая
операция ввода-вывода.
Вторая версия предназначена для СЧИТЫВ<l.ния СИМВОЛОВ В массив, адресуе­
мый параметром buf, до тех пор, пока либо Ilе будет считано num-1 символов,
либо не обнаружится символ, зад;шный парам~тром
СТИГIlУТ конец фаЙЛ<l. После выполнеlШЯ ФУ"КЦИII
delim, либо не будет до­
geL () массив, адресуемый
параметром buf, будет иметь завершающий нуль-символ. Символ-разделитель
(заданный параметром
delim),
если таКОJ]ОЙ обнаружится во входном потоке,
не извлекается. Он остается там до тех пор, пока не I:IЫ110ЛНИТСЯ С.llеДУЮЩ:-IЯ
операция ввода-вывода.
Третья перегруженная J]ерсия функнии
щий символ.
011 содержится
ge t ()
возвращает I1З потока следую­
в младшем БЮ1ТС значсиия, возвращаемого функци­
ей. Следовательно, значение, возврашаемое ФУlIкцией
переменной ТИПёi
значение
EOF,
Функцию
char.
get () , можно
присвоить
При достижсшш конца файла эта функция возвращает
которое определено в заголовке
<iostream>.
get () полезно использовать ДЛЯ считывания строк, содержащих
пробелы. Как вы знаете, ссли для считываJlИЯ строки используется оператор
"»",
процесс ввода останавливается при обнаружении первого же пробельного
символа. Это делает оператор
"»"
бесполезным ДЛЯ считывания строк, содер­
жащих пробелы. Но эту проблему, как показано в следующей I1рограмме, можно
обойти с помощью функции
11
11
Использование
get (buf,
функции
содержащих пробелы.
#include <iostream>
#include <fstream>
ID
get()
пит).
для
считывания
строк,
~
u
s
u
.j.
+
u
·
~~.~.....~9.~Y~~.~.~:..~~~~.~~.:~~.<?~~9.~9~~~~~?~9...................................... .
using namespace std;
int main ()
char str[80];
cout « "Введите имя: ";
cin.get(str, 79); // Иcnonъзуен
//
cout «
return
str «
С'2!роJCИ,
фун~qеt()
ДnR счит.ыаании
содержащей аробе.nы.
'\п';
О;
Здесь в качестве символа-разделитсля при СЧf.lТЫDании СТРОI<И с помощью функ­
ции
get ()
используется симnол новой СТрОЮI. Это делает функцию
раздо6сзопаенее функции
get ()
го­
gets ().
ФУНКЦИЯ getline ( )
РаеС:\ЮТРIlМ еще одну ФУНКЦИЮ. которая по;~rюляет вводить даЮIЫС. Речь и;сет
о ФУШ(UI1If getline (). которая является членом каждuго lIйТОКОВОГО класса.
пr~JtнаЗllаченного для ввода IIНфuрмзции. Вот
1(;)((
выглядят ПРОТОТIIПЫ версий
этой функции.
istream &getline (char ·I<buf, streamsize
istream &getline(char *buf, strearnsize
лит) i
лит,
char delim)j
При "спользовании первоii версии символы считываются в массив. адресу­
емый указателем buf. до тех пор. пока либо не будет считано лит-l символов,
либо НС встретится символ lЮПОЙ строки, либо не будет ДОСТИПiут конец фаЙла.
После выполнения функции
getline ()
массив, адресуемый пара метром
buf,
будет иметь завершаюший нуль-символ. Символ IIОВОЙ строки, если таковой об­
наружится во входном потоке, при этом ИЗВЛСJ~ается, но не помещается в масСИВ
buf.
Вторая версия ПРСдJJa31шчеlla для с'!ИтываJШЯ символов в массив, адресуемый
параметром buf, до тех пор, пока либо не будет считаl10 nит-l символов, лпбо
не обнаружится символ. ~jадаШIЫj,j парамеТРОl\J
delim, либо не будет достипryт
конец файла. После ВЫПОЛllеllИЯ функции get 1 ine () :\1аССI1В, адресуемый пара­
метром buf, будет иметь завЬршаЮЩIIЙ нуль-ClIМПОЛ. СИМВОЛ-Р<IЗделитель (за-
С++: pYt<OBOACTBO ДМ, начинающих
данный параметром
delim), если таковой обнаРУЖI1ТСЯ во ВХОЛJюм потоке, из­
влекается, но не помещается в массив
buf.
Как видите, эти две версии ФУНКЦIIИ
версиям
get (buf,
559
пит) и
get (buf,
getl ine () практически пдентичны
пит,
delirn) функции get (). Обе
считывают символы из входного потока и помещают их в массив, адресуемый
параметром buf, до тех пор, покали60 не будет считано nиm-1 символов, Лll60 не
обнаружится символ, заданный параметром
delim. Различие между фУНКlllIЯМИ
get () и getline () состоит в том, что функция getline () СЧlПывзer и у;tаля­
ет символ-разделитель из входного потока, а функция get () этого не делает.
~
о
aI
:о
ID
~
ID
О
~
~
()
s
,
+
+
()
ФУНКЦИЯ обнаружения конца файла
u
Обнаружить конец файла можно с помощью ФУlfкции-члена ео!
() ,
которая
имеет такой прототип.
bool eof () ;
Эта функция возвращает значение true при достижснии КОllца файла; в против­
ном случае она возвращает значение false.
Функции peek () и putback ( )
Слсдующий СI1МВОЛ из входного потока можно ПOJIУЧИТI, Н lIе удuлять его от­
туда с ПОМОЩI)Ю функции
peek ().
ВОТ как ВЫГЛЯДИТ ее ЩЮППJlJl.
int peek () ;
Функция рее k
EOF, если
ДОСПlrnу·г конец файла. С"итанный символ возвращается·в МЛадшем байте 311(1()
возвращает следующий СИМDОЛ потока, или значеIiне
чения, возвращаемого функцией.
Последний символ, считанный из потока, моЖItО вернут!>
функцию
putback ().
n
потOI<, IIСГЮЛЬЗУЯ
Ее прототип выгющит так.
istrearn &putback(char
с);
Здесь парзметр с содержит символ, СLlИТЗllllЫЙ И3 потока последним.
Функция flush
()
При выводе данных немедлеliIiОЙ 11Х записи на фJJзическое устройство, свя­
занное с потоком, не происходит. Подлежащая ВЫВОДУ I1IнjЮР:'fflllIlЯ накаплива­
ется во внутреннем буфере до тех
110\"1, пока этот буфер не заПОЛIIИТСЯ целиком.
И только тогда CI'O содержимое IIсреl1нсЫВ<t.ется на диск. О;l.Н;)I(О сvществует воз­
можность немедлеНIiОЙ lIерсзашюп!> на диск храllИМУЮ в 6уфер(" ~шФО\"1~lаJtIfЮ.
560
МОДУЛЬ 11, С++-система ввода-вывода
не дожидаясь его заполнения. Для этого следует вызвать функцию
ilush (). Ее
прототип имеет такой вид.
ostream &ilush();
К вызовам функции
flush () следует I1риGсгать в случае, ссли прогрй.\1ма I1Р('Д­
назначена для выполнения в неблагоприятных средах (для которых xapaKTep~ы,
например, частые отключения электричсства).
~
На юметку
n"
Содержимое всех буфер. ов псреписывается на диск также при :ш­
(срытии файла или завершении lIрограммы.
CompFiles. срр
В
<JTUM IIроекте разрабаТЫlJается дuволыю IlpOCToe, IЮ
11U-
леЗIIОС срсдстио СJJaВIIСIII1Я файлов. МсхаllИЗМ СJ1аВII(~IIIIЯ
состоит в CJlсдующем. После ОТКРЫТИЯ обоих файлов из каждого из них счн, 1>1паются порции байтов и сравниваются между собой. При обнаружении
дения делается ВЫВОД о том. <по сравниваемые файлы различны. Если
HeCOBI1L1же I(Olett
обоих файлов достигается одновременно и IIрИ этом никакого совпадеЮ1Я не
наруживается, ЭТII фаiJлы СЧllтаются ОДИНaJ<QlJЫМИ.
Последовательность действий
1.
Создайте файл с ИМСIIСМ
2.
Этот файл должен начшшться такими строками кода.
CompFiles. Cl=p.
/'*
Проект
11.1
Создание
средства
сравнения
файлов.
'*/
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[])
register int i;
int numread;
06-
С++: руководство ДЛ)\ начинающих
561
unsigned char buf1(1024), buf2[1024];
if(argc!=3)
cout « "Применение: compfi1es <filel> <file2>\n";
return 1;
g
g
111
J5
ер
111
111
О
Обратите внимание на то, что имена сравниваемых файлов указываются в
командной строке.
::!:
~
s
()
3.
Добавьте код, который обеспечивает открытие файлов для выполнения
операций ввода данных в двоичном режиме.
ifstream f1(argv[1], ios::in I ios::binary);
if (!П) (
cout « "Не удается открыть первый фаЙл.\п";
return 1;
ifstream f2(argv[2], ios::in
ios: :binary) ;
if(!f2) {
cout « "Не удается открыть второй фаЙл.\п";
return 1;
Файлы здесь открываются для выполнения операций ввода в двоичном ре­
жиме, чтобы не допустить npеобразований символов, I<oTopbIe характерны
для текстового режима.
4.
Добавьте код, который, собственно, и реализует сравнение файлов.
cout «
"Сравнение
файлов
...
\п";
do (
fl.read((char *) bufl, sizeof bufl);
f2.read((char *) buf2, sizeof buf2);
if(П.gсоuпt()
!= f2.gcount(»)
cout « "Файлы
f1 . close () ;
f2 : close () ;
return О;
имеют различный размер.\п";
. +
+
;U
562
Модуль 11. С++-система ввода-вывода
11
Сравниваем
содержимое
буферов.
for(i=O; i<f1.gcount(); i++)
if(bufl[i] != buf2[i)
(
cout « "Файлы различны.\п";
П . close () ;
f2 . close () ;
return О;
while (!
cout «
П.
eof () && ! f2. eof () ) ;
"Файлы одинаковы.\п";
При выполнеНЮ1 этого кода с помощью функции read () считывается по
одному буферу из каждого файла. Затем сравнивается содержимое этих
буферов. Если окажется, что буферы имеют различное содержимое, фай­
лы будут закрыты, на экране появится сообщение" Файлы
различг.ы.",
и программа завершится. В противном случае в буферы ЬиП 11
buf2 ;Lnя
сравнения будут считываться новые порнии байтов до тех пор, пока не об­
наружится конец одного (или обоих) фdЙлов. Поскольку В конце люб.JI"О
файла возможно считывание неполного 6уфера, в ПРОI"рамме используется
функция
gcount () . I(СТОРая
позволяет точно определить I(OJIIIЧССТlЮ CII:l1-
волов В каждом буфере. Если окажется. что один из фаiiлоu короче друго­
го, ЗllачеНIIЯ, возвращаемые функцией
gcoun t
() для файлов, будут раз­
личными при достижении конца одного из файлов. В этом случае на экран
выводится сообщение "Файлы имеют различный размер.". Наконец,
если файлы одинаковы, то при обнаружении конца одного из файлов т;шже
должен быть обнаружен конеЦ и другого фаЙJlа. Это подтверждается вы­
зовом функции
eof ()
для каждого ПОТОКi:l. И только в том С.'1учае, если ~a­
венство фаЙJlОВ будет безоговорочным во всех отношениях, на экран будет
выведено соответствующее сообщение.
5.
Завершая программу. не забудьте закрыть файлы.
f1. close () ;
f2. close () ;
return
6.
О;
Вот как выглядит полный текст программы
1*
Проект
11.1
cornpFiles. срр.
С++: РУКОВОДСТВО M~ начинающих
Создание
средства
сравнения
файлов.
*/
563
g
CD
2i
ер
*include <iostream>
*include <fstream>
using патеэрасе std;
~
CD
О
~
~
int main(int argc, char *argv[])
(J
s
~
+
register int i;
int numread;
u
unsigned char bufl[1024], buf2[1024];
if (argc! =3) (
cout « "Применение : compfiles <file1> <file2>\n";
return 1;
ifstream f1(argv[1],
i f (! f1)
ios::in I ios::binary):
(
cout « "Не
return 1:
у.цается
открыть
ifstream f2(argv[2], ios::in
if(!f2) (
cout « "Не удается открыть
return 1;
cout «
"Сравнение
файлов
первый фаЙл.\п":
ios: :binary) :
ВТОРОЙ ФаЙЛ.\п";
... \п";
do (
f1.read«char *) buf1, sizeof bufl);
f2.read«char *) buf2, sizeof buf2);
if (f1.gcount () != f2 .gcount ()
{
cout « "Файлы имеют различнЫЙ'размер.\п";
564 Модуль 11. С++-система ввода-вывода
.................................................................................................................................
f1 . close () :
f2 . close () :
return О:
11
Сравниваем
содержимое
буферов.
for(i=O: i<fl.gcount(); i++)
if(bufl[i] != buf2[i]) {
cout « "Файлы раэличны.\п":
fl . close () :
f2. close () ;
return О:
while(!fl.eof()
cout «
&&
!f2.eof()):
"Файлы одинаI<:ОВЫ.\п";
fl . close () ;
f2 . close () ;
return
7.
О;
Чтобы испытать I1рограмму CompFiles. срр, сначала скопируйте файл
CompFiles.cpp в другой файл, КОТОРЫЙ назовите, скажем, temp.txt.
Затем (после КОМПИЛЯl1ИИ программы C:ompFiles. срр) введите следую­
щую командную строку.
CompFiles CompFiles.cpp temp.txt
При выl1лн~ниии ЭТОЙ програ.~мы вы должны получить сообщение о том, что
файлы одинаковы. Затем сравните файл
CompFiles. срр
с каким-нибудь
другим файлом (например, с одним из программных файлов этого модуля). В
этом случае ВЫ ДОЛЖНbI получИTh сообщение о том, что файлы различны.
8.
Самостоятельно попробуйте усовершенствовать програ.'dму
CompFiles.
срр. Например, добавьте средство, которое позволяет игнорировать ре­
гистр букв, в результате чего строчные и ПрОШIсные буквы не должны рзз­
личаТhСЯ программой сраВliеlШЯ файлов. Можно также позаботиться о
том, чтоБЫ новая версия программы
COInpFiles. срр
сообщала ПО3Иl1ИЮ,
в которой обнаружено несовпадение в содержимом файлов.
С++: PYI(OBOACTBO ДМI начинающих
ВАЖНОI
565
..........-
"~а"~ ПРОИ2ВОАЬWЫ Й AQI'ТVn
До сих пор мы использовали файлы, доступ к содсржимому которых был ор­
ганизован строго последовательно, байт за байтом. Но в С++ также можно полу­
чать доступ к файлу в ПРОИ:JВОJlЬНОМ порядке. В этом случае необходимо исполь­
зовать функции
seekg () и seekp (). Вот как выглядят прототипы их самых рас­
пространснных форматов.
istream &seekg(off_type offset, seekdir
ostream &seekp(off_type offset, seekdir
Используемый здесь целочисленный тип
оrigiл);
оrigiл);
off type (он
определен 8 классе
ios)
позволяет хранить самое большое допустимое значение, которое может иметь па­
раметр
offset. Тип seekdi r определен как перечисление, которое имеет следу­
ющие значения.
Значение
Описание
ios: : beg
Начало файла
ios: : cur
Текущая ПОЗJЩИЯ
_i_о_s_:_:_е_П_d
______К~о_н_е_ц~файл~______________________________________________
в С++-системе ввода-вывода предусмотрена возможность управления двумя
указателями, связанными с файлом. Эти так называемые
get-
и рut-указаmелu
определяют, в каком месте файла должна выполниться следующая операция вво­
да и вывода соответственно. При каждом выполнении операции мода или выво­
да соответствующий указатель автоматичеСlСИ изменяется. Используя функции
seekg ()
и
seekp ()
,можно "перемещать" нужный указатель и получать тем са­
мым доступ к файлу в ПРОИЗВОЛЫlOм порядке.
Функция see~g
()
персмещает ТСl<ущиi:"1 gеt-указатель соответствующего
файла на offset байт относительно позиции, задашюй параметром оrigiл.
Функция
seekp () перемещает текущий рut-указатель соответствующего файла
на offset байт относительно позиции, заданной параметром оz·igiл.
В общем случае произвольный доступ для операций ввода-вывода должен вы­
полняться только для файлов, открытых в двоичном режиме. Преобразования
символов, происходящие в текстовых файлах, могут привести к тому, что запра­
шиваемая позиция файла не будет соответствовать его реальному содержимому.
В следующей программе демонстрируется использование функции
Она позполяет задать имя файла 8 командной строке, а за ним
-
seekp ().
конкретный
байт, который нужно в нем изменить. Программа затем записьшает в указанную
позицию символ "х". Обратите внимание на то, что обрабатываемый файл дол­
жен быть открыт для выполнения операций чтения-записи.
-
~.....~~~Y~~..~.~:..~~~.~~.~~.~~~.<?~~?~~~~~~~?~? ......................................
//
Демонстрация
ПРОИЗБОЛЬНОГО
доступа
к файлу.
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main(int argc, char *argv(])
(
if(argc!=3) {
cout « "Применение:
имя ПРОГРАММЫ <имя_файла>
<байт>\
п";
return 1;
fstream out(argv[l], ios::in I ios::out I ios::binary);
if ( ! out) {
cout « "Не удается открыть ФаЙл.\п";
return 1;
//
Функция
1/ •
1/ с
seekp()
ус~анaanивае~ рut-укаsа~enъ
соо~.е~сТ8ИИ
SaдaHR.... байтом:
out.seekp(atoi(argv[2]), ios::beg);
out.put ('Х');
out.close();
return
О;
Теперь продемонстрируем использование функции
seekg ().
При выполне­
шш следующей программы отображается содержимое файла, начиная с задан­
ной позиции (имя файла и позиция указываются в командной строке).
//
//
Отображение
содержимого
стартовой позиции.
#include <iostream>
файла
с
:заданной
С++: PYI<OBOACTBO ДМ) начинающих
567
#inc1ude <fstream>
#inc1ude <cstd1ib>
using namespace std;
int main(int argc, char *argv[])
char ch;
if(аrgс!=З)
cout «
«
"Применение:
имя ПРОГРАММЫ
"
"<имя_файла> <стартовз.Я_ПОЗИЦИR>\П";
return 1;
ifstream in(argv[l], ios::in I ios::binary);
if(!in) (
cout « "Не удается открыть ФаЙЛ.\п";
return 1;
in.seekg(atoi(argv[2]), ios::beg);
whi1e(in.get(ch»
cout « ch;
return
О;
Текущую позицию каждого файлового указателя можно определить с помо­
щью этих двух функций.
type tellg();
pos type tellp();
рО5
Здесь используется тип ро 5
t
уре (он определен в классе
i05), позволяющий хра­
нить самое большое значение, которое может возвратить любая из этих функций.
Сущестоуют перегруженные версии функций
5eekg ()
и
seekp (),
которые пе­
ремещают файловые указатели в позиции. заданные значениями, возвращаемыми
функциями
tellg ()
и
tellp ()
соответственно. Вот как выглядят ИХ прототипы.
istream &seekg(pos_type position);
ostream &seekp(pos_type position);
~~.~ .....~~.~y~..~.~:..~:+:~.~~.~~.:~~.?~~~.~?~~~~~?~~.......................................
~просы ДМ! текущего контроля - -_______
,
1.
2.
С ПОМОЩЬЮ какой функции можно оБН:lРУЖИТЬ конец файла?
КаI<ие действия nыlo.lнlяетT функция
getline ()?
з. Какие функции 110:ШОЛЯЮТ получить доступ к произвольно заданной по­
~FЫ~
зиции в файле?· Emu.,. . . .____. .___..
=== ____..______..______..___...
_ _. . . .М8_ _~_ _. . . .
~
об операциях ввода-вывода
С++-систсма ввода-въшода поддерживает информацию о результатах выпол­
нения каждой операции Iшода-вьшода. Текущее состояние потока ввода-вывода
ОПИСblвается в объекте ПIlJa
иие (оно определено n КЛ(1ссе
................ _ - -.-, ...
... .. ~---_
~
iostate, который представляет собой перечисле­
ios), включаюшес.слсдующие члены .
., ... " ..
,,----,-,-,---_._-,-~-----_
Имя
Значение
ios: : goodbi t
ios: : eofbi t
ios: : failbi t
ОШl\БКJI отсутствуют
..................... _-
~-
... _._-_.--
.~~~-------------
1 при обнаружении конца файла;
1 при
О в пропшном случае
[ю:mикновении испраВIJМОЙ ошибки ввода-вывода; О в IlРО­
тиВlЮМ случае
ios: : badbi t
1 при
Iю:шикновении неисправимой ошибки ввода-вывода; О в про­
тивном случае
СтаТУСIIУIO Иllформацию о результатах выполнения операций ввода-вывода
можно получать двумя способами. Во-первых, можно вызвать функцию
te (),
которая ЯWlяется членом класса
ios.
rdst.:a-
Оllа имеет такой прототип.
iostate rdstate();
Функция rdstate ()
возвращает текущий статус флагоп ошибок. Нетрудно
догадаТhСЯ, что, судя по lIриведешlOМУ выше списку флагов, фуикция
te ()
возвратит значение
goodbi t
rdsta-
при отсутствии каких бы то ШI было ошибок.
В противном случае она возвращает соответствующий флаг ошибки.
1.
Конец файла можно обнаружить с помощью функции
eof ().
2. Функция get 1 i ne () ('пIтыаетT строку текста.
з.
для обработки заllрОШll, прещюлагающих доступ к произвольно заданной пози­
ции в фаiiле, ИСПОЛl,ауются функции
seekg () и seekp () .
С++: РУI<ОВОДСТВО ДМ! начинающих 569
................................................................................................................................
Во-вторых, о наличии ошибки можно узнать с помощью одной или несколь­
ких следующих функцю';i-членов класса
ios.
о
bool
bool
bool
bool
<1
bad();
eof () ;
fail () ;
good();
О
m
JS
m
~
m
m
Функция eof () рассматривалась выше. Функция bad () возвращает значение
ИСТИНА, если в результате выполнения операции ввода-вывода был установ­
лен флаг badbi t. ФУНКЦИЯ fail () возвращает З~laченне ИСТИНА, если в ре­
зультате выполнения операции ввода-вывода был устаlJовлен флаг f а i
lb i t.
Функция good () возвращает значение ИСТИНА, если при выполнении опера­
ции ввода-вывода ошибок не произошло. В противном случае эти функции воз­
вращают значение ЛОЖЬ.
Если при выполнении операции ввода-вывода произошла ошибка, то, возмож­
но, прежде чем продолжаТh выполнение программы, имеет смысл сбросить флаги
ошибок. Для этого используйте ФУНКЦИЮ clear () (член класса ios), ПРОТОТИП
которой выглядит так.
void clear{iostate flags = ios::goodbit);
Если параметр flags равен значению
goodbi t (оно устанавливается по умолча­
нию), все флаги ошибок очищаются. В противном случае флаги устанавливаются
в соответствии с заданным вами значением.
Прежде чем переХОДИТh к следующему модулю, стоит опроБОRать функции,
которые сообщают данные о состоянии флагов ошибок, внеся в предыдущие при­
меры программ код проверки ошибок.
1.
Назовите четыре встроенных потока.
2.
Предусмотрены ли в С++ (помимо В-разрядных) ДвухбаЙТОDые (1б-раз­
рядные) версии стандартных потоков?
3.
Представьте общиii формат переl"рУЗКИ оператора вывода (вставки) данных в поток.
4.
Какие действия выполняет манипулятор
5.
Каково назначение ФУIiI<ЦИII
6.
В выраженпя.х ввода-вывода можно ИСIlОJll,ЗОJЗать любой манипулятор вво­
да-вывода. Верно ли это?
ios: : scient ific?
w idth () ?
О
:!
Ф
>-
U
:s:
u
+
+
u
570
7.
8.
9.
10.
Модуль 11. С++-система ввода-вывода
Покажите, как открыть файл для СЧИТЫВШIИЯ данных в текстовом реЖl1ме.
Покажите, как открыть файл для записи данных в текстовом режиме.
Каково назначение манипулятора
ios : : binary?
При достижении I(OJ-JЦа файла перемеlШая потока принимает значение
ЛОЖЬ. Верно ли это?
11.
Предположим, что файл связан с ВХОДIIЫМ потоком
strm.
Покажите. как
считать все содержимое файла?_
12.
Напишите программу копирования файла. Обеспечьте возможность ДЛЯ
пользователя задавать входной и ВЫХОДJlO~i файлы в командtJой строке.
Убедитесь, что ваша программа может копировать как текстовые, так и дво­
ичные файлы.
13.
Напишите программу объединения двух текстовых файлов. Обеспе'lьте
возможность для пользователя задавать имена объединяемых файлuв
и файл-результата в командной строке. Например, если ваша программа
будет называться merge, то для объединения файлов MyFilel. txt и
MyFile2. txt в файл Target. txt необходимо использовать следующую
командную строку.
Merge MyFilel.txt MyFile2.txt Target.txt
14.
Покажите, как с помощью функции
токе
-tc
На заметку
seekg () перейти к 300-му байту о по­
MyStrm.
n. .
Ответы на эти вопросы можно найти на Wcb-страюще данной
книги по адресу: httр:/lwww.оsЬоrле.соm.
МОДУЛЬ
12
ИСI<лючения/ шаблоны
и I<ое-что еще
12.1.
12.2.
12.3.
12.4.
12.5.
12.6.
12.7.
12.8.
Обработка исключительных ситуаций
Обобщснныс функции
Обобщенные классы
Динамическое распределение памяти
Пространства имсн
Статические члены класса
Динамическая идснrnфикация rnпов (RTТI)
Операторы приведсния типов
572
МОДУЛЬ 12. ИСl<лючени~, шаблоны и I<ое-что еще
С начала этой книги мы с вами прошли ОГРОМIiЫЙ путь. И в этом последнем
модуле мы рассмотрим ряд таких важных TC~I, ЮIК обработка исключительных
ситуаций, использование шаблонов, динамическое распределение памяти /1
IIPO-
странства имен. Очевидно, что в руководстве для начинающих невозможно охва­
тить все средства и нюансы програмМИРОВaJiИЯ, ведь С++
-
довольно сложный и
объемный языl<' предназначенный для создания высокоэффективных программ,
которые требуют профессионалыlOГО подхода. Тем не менее, освоив материал
этого модуля, вы овладеете основными элементами языка С++ и сможете при­
ступить к написанию ПРИI<ладных программ.
ВАЖНОI
Itl' Обра6Q1КQ ИСКАЮNитеАhNhlX
ситуаций
Исключитель1/.ая ситуация (или исключение)
-
это ошибка, которая во:'ни­
кает во время выполнения программы. Используя С++-подсистему обработки
исключительных ситуаций, с такими ошибками [)полие можно справляться. При
их ВОЗlшюювении во время работы програМ:\1Ы автоматически вызывается так
называемый обработчик UСКЛЮ'lеllиЙ. В этом-то и состоит принципиальное пре­
имущества системы обработки ИСl<Лючений, поскольку именно она Uотвечае1'" за
код обработки ошибок, который прежде приходилось "вручную'" вводить в и без
того объемные программы.
ОСНОВЫ обработки исключительных ситуаций
Управление С++-мсханизмом обработки ИСl<Лючений зиждется на трех клю­
чевых словах:
try, catch
и
throw.
В самых общих чертах их работа состоит в
следующем. Программные инструкции, которые вы считаете нужным про КОН­
тролировать на предмет исключений, помещаются в trу-блок. Если ИСКЛЮ'-l{'ние
(т.е. ошибl{а) таки возникает в этом блоке, оно дает знать о себе выбросом опреде­
ленного рода информаНIIИ (с помощью КЛIOЧ('IЮГО слова
throw).
Это выброше1/.-
1l0е исключение может быть перехвачено программным путем с помощью са tch-
блока и обработаliО соответствующим образом. А теперь подробнее.
Код, в котором возможно возникновение ИСКЛЮ'-lительных ситуаций, должен
выполняться в рамках trу-блока. (Любая функция, вызываемая из этого
try-
блока, также подвергается контролю.) ИСI<лючения, которые :\10ГУТ быть "вы­
брошены" контролируемым кодом, перехватываются catch-инструкциеЙ. непо­
средственно следующей за trу-блоком, в котором фИl(сируются ЭТИ "выбросы"
исключений. Общий формат
try-
и саtсh-блоков выглядит так
С++: РУКОВОДСТВО ДМI начинающих
try {
//
/I
trу-блок
(блок кода,
573
-
подлежащий проверке
:ф
на налич.ие ошибок)
:3:
.ф
~ о
catch ( t;lpe 1 arg)
// саtсh-блок
catch (type2 arg)
// саtсh-блок
.....
{
(обрабОТЧик
: ... J
исключения
типа
typel)
:Ф
~~
Ji
:I:
О
(обработчик
исключения
типа
type2)
.<
:\0
:0
:3
catch
//
(tуреЗ
arg)
саtсh-блок
(обработчик
исключения
типа
type3)
//
catch (typeN arg)
// саtсh-блок ( обработчик
исключ.ения
типа
typeN)
}
Блок
try
должен содержать код, КОТОРЫЙ, по вашему мнению, следует про­
верять на предмет возникновения ошибок. Этот блок может включать лишь
несколько ИНСТРУIЩИЙ некоторой функции либо охватывать весь код функции
main ()
(в этом случае, по сути, "под колпаком" системы обработки исключений
будет находиться вся программа).
После "выброса" ИСКЛIO'Jение перехватывается соответствующей инструкци­
ей
catch,
которая выполняет его обработку. С одним trу-блоком может быть
связана не одна, а неСIЮJJЬКО са tch-инструкциЙ. Какая именно из них будет ВЫ­
полнена, определяется типом ·исключения. Другими словами, будет вьшолне­
на та саtсh-инструкция, тип исключения котороЙ (т.е. тип данных, заданный
в
са tсh-инструкции) совпадает с типом сгенерированного исключения (а все
остальные будут проигнорированы). После перехвата исключения параметр а rg
примет его значение. Таким путем могут перехватываться данные любого типа,
включая объекты классов, созданных программистом.
Общий формат инструкции throw ВЫГЛЯДlIТ так:
throw exception;
Здесь с помощью элемента exception задается исключение, сгенерированное
инструкцией
throw.
throw должна быть
Если это исключение подлежит перехвату. то инструкция
выполнена либо в самом блоке
мой из него функции (т.е. прямо или косвенно)_
try, либо
в любой вызывае­
574
Модуль 12; ИСI<лючениS1, шаблоны и I<ое-что еще
Если в программе обеспечивается "выброс" иск.лючения, для которого не
предусмотрена соответствующая саtсh-ИНСТРУI<ЦИЯ, произойдет аваРИЙl-IOr за­
вершение программы. Поэтому программист должен предусмотреть перехват
всех возможных иск.лючениЙ в программе.
Рассмотрим простой при мер обработки исключений средствами я~п<а С++.
//
Простой пример обработки исключений.
#include <iostream>
using namespace std;
int main ()
{
cout «
"НАЧАЛО\п";
try { // начало try-бnока
cout « "В trу-блоке\п";
throw 99; / / rенерирование
cout « "Эта инструкция не
ошибки
будет
catch (int i) { // перехва~ ошибки
cout « "Перехват исключения. Его
cout « i « "\п";
cout «
return
("ВloIброс" ИСlCJDDЧеНИR)
выполнена.";
значение
равно:
";
"КОНЕЦ";
О;
При выполнении эта программа генерирует такие результаты.
НАЧАЛО
В
trу-блоке
Перехват
исключения.
Его
значение
равно:
99
КОНЕЦ
Рассмотрим внимательно код этой программы. как видите, здесь try-БЛОI{ со­
держит три инструкции, а инструкция
ca.tch (int i) предназначена для обра­
t rу-блоке выполняются только
ботки иск.лючения целочисленного типа. В этом
две из трех инструкций:
cout
и
throw.
После генерирования иск.лючения управ­
ление передается саtсh-выражению, при этом выполнение trу-блока прекра-
С++: PYI<OBOACTBO ДЛfl начинающих
575
"
щается. Необходимо нонимать, что catch-ИНСТрукция не вызывается, а просто
с 11ее продолжается ВЫПОЛllение программы после "выброса" ИСК.lllоченЮL (Стек
программы автоматически настраивается в соответствии с ('оздавшейся ситуаци­
ей.) Поэтому соut-инструкция, СJlедующая после thrО~I-1\J-IСТРУКЦИИ, никогда
не вы полнится.
~
ф
...;r
о
ф
Обычно при выполнении саtсh-блока делает с}! 110flЫТЮl исправить ()Шllбку
о
путем пыплIlеlшяя соответствующих действий. Если ошибку можно исправить,
:s;
1i
то после Вblполнения саtсh-блока управление программой передается ИНСТРУК·
'"
:I:
О
.<
ции, следующей за этим блоком.· В противном случае ПI?ОГР;)~fма должна БЫТl,
:10
прекращена.
:3
Как упоминалосъ выше, тип исключения должен СОВШI.l:J.тr, с типом, заданным
в саtсh-инструкции. Например, если в предыдушей ПРОГР<J.\lме тип
ный
\)
саtсh-выражении, заменить типом
double,
ln t. указан­
то исключение перехвачсно
не будет. и произойдет аварийное завершение программы. ВОТ как ВЫfлядят 110СЛСДСТnIlЯ внесения такого изменения.
//
Этот
пример работать
не
будет.
#include <iostream>
using namespace std;
int main ()
{
cout «
"НАЧАЛО\п";
try { // начало trу-блока
cout « "В trу-блоке\п";
throw 99; /1 генерирование
cout « "Эта инструкци!! не
catch (double
cout «
cout «
cout «
return
i)
i
«
будет
выполнена.";
/1
Перехват .искnaчеВИR типа
//
не состоитс..
"Перехват
"КОНЕЦ";
О;
{
ошибки
"\п";
исkлючеНИR.
Его
значение
int
равно:
";
:0
ri.
:s;
ai:r
:2
:<
.~
;()
:S
576
Модуль 12. ИСl<лючени~, шаблоны и I<ое-что еще
Такие результаты выriолнеюlЯ этой программы объясняются тем, что исключе­
ние целочисленного типа не перехватывается инструкцией са t
ch (doub 1е i).
НАЧАЛО
В
trу-блоке
Abnormal program termination
Последнее сообщение об аварийном завершении программы
program termination)
(Abnormal
может отличаться от приведенного в результатах выпол­
нения предыдущего примера. Это зависит от используемого вами компилятора.
Исключен не. сгенерированное функцией. вызванной из trу-блока, может
быть 06работаIЮ этим же try-БЛОI<ОМ. Рассмотрим, например, следующую впол­
не корректную программу.
/*
Генерирование исключения из
из
функции,
вызываемой
значение
test
trу-блока.
*/
#include <iostream>
using namespace stdi
void Xtest(int test)
(
cout «
"В функции Xtest(),
test « "\п";
if(test) throw testi // Это
равно:
"
«
ис~ение переХ8аТК8ае~CR
// саtсh-инс~укцией 8 фУНКЦИИ
main() .
}
int main ()
cout «
"НАЧАЛО\П"i
try ( // начало trу-блока
cout « "В try-блоке\П"i
Xtest (О) i / / ПОСКОJlЬКУ Xtest() в.ызwваетCJI иs try-бnока,
Xtest(l)i // ее КОД так.е подверrае~с. Ko~oma на
Xtest(2)i // ошибки.
catch (int i)
// перехват ошибки
cout « "Перехват исключения. Его
значение
равно:
";
С++: PYI<OBOACTBO ДЛЯ начинающих
cout «
cout «
i «
577
"\п";
~
ф
"КОНЕЦ";
....о
'J
return
сь
О;
~
:s:
:о
Эта программа генерирует такие результаты.
:с
~
НАЧАЛО
о
В
trу-блоке
В
функции
В
функции
Xtest(),
Xtest(),
значение
значение
Пере хват исключения.
Его
test
test
равно:
О
равно:
1
значение
равно:
3
s:
r:i.
:с
ф
~u
1
КОНЕЦ
S
Как подтверждают результаты выполнения этой программы, исключение, сге­
нерированное в функции
Xtest (j,
было перехвачено обработчиком в функции
main ().
Блок
try может быть локализован в рамках функции.
В этом случае при каж­
дом ее выполнении запускается и обработка исключений, связанная с этой функ­
цией. Рассмотрим следующую простую программу.
//
Блок
try
может быть
локализован
в рамках функции.
#include <iostream>
using namespace std;
// Функционирование блоков try/catch
// при каждом входе в функцию.
void Xhandler(int test)
возобновляется
{
try{ // ~OT try-бnох nохanен
/ / х фувхции Xhandler () .
if(test) throw test;
catch(int
cout «
int main ()
по отношеиио
i)
"Перехват!
Исключение ~:
" «
i «
'\п';
578
Модуль
cout «
12. ИСl<лючения, шаблоны и I<ое-что еще
"НАЧАЛО\п";
Xhandler(l) ;
Xhandler(2);
Xhandler(O) ;
Xhandler(3);
cout «
return
"КОНЕЦ";
О;
При выполнении этой программы отображаются такие результаты.
НАЧАЛО
Перехват!
Исключение
Н' :
Перехват!
Исключение
Перехват!
Исключение
'. 2
N!: 3
1
Н"
КОНЕЦ
Как видите, программа сгенерировала три исключения. После каждого иск.."IЮ­
чсния функция
Xhandler ()
передавала управление в ФУНКЦИЮ
rnain (). Когда
она снова вызывалась, возоБНОМЯJJась и обработка исключения.
В общем случае trу-блок Dозобиовляет свое функционирование при каждом
входе в него. Поэтому если trу-блок является частью цикла, то он будет запу­
скаТl,СЯ при кажДОМ повторении этого цикла.
~npocы ДЛ'i1 текущего lюНтрОл'i1 ________
1.
2.
-
Что понимается ПОД исключением в языке С++?
На каких трех ключевых словах основана обработка исключений?
З. При перехвате исключения uажен его тип. Верно ли это?·
1.
2.
З.
Исключение
-
это ошибка, которая возникает во ВреМя выполнения ПрОIJ>ЗММЫ.
Обработка исключений оснонана на трех ключевых словах:
try, catch и throw.
Верно. ИсключеНllе будет перехвачено только в том случае, если его nш совпадет с
типом, заданным
u саtсll-ИНСТРУКЦИИ.
С++: РУКОВОДСТВО дЛЯ нач.инающих 579
Использование нескольких саtсh-ИНСТРУКЦИЙ
Как упоминалось выше, с
t rу-блоком можно спязыпаТI> не одну, а несколь­
ко catch-инструкuиЙ. В действительности именно такая практика и ·является
обычной. Но при этом все саtсh-ИНСТРУКUИИ должны псрехватыатъ исключс­
ния различных типов. Например, 8 привсденной НJlже программс обеспечивает­
: 0)
:3"
:0)
:0
.:!Т,
:Ф
:0
.:.!
ся перехват как целых чисел, так и указатслей на СИМFlОЛЫ.
:s
//
:0
Использование
нескольких
:~
catch-инструкциЙ.
:1:
:<
:10
:0
#include <iostream>
using narnespace std;
;3
I
:ti
:s
~ffi
// Здесь возможен перехват
void Xhandler(int test)
исключений различных
j§
типов.
:()
;S
try{
if(test) throw test; // "Выброс" исключений типа int.
else throw "Значение равно нулю."; 11 "Выброс"
11 исключений типа char *
catch(int i) { // Перехват искпачевий типа int.
cout « "Перехват! Исключение N!: " « i « '\п';
catch(char *str) ( // Перехват исхпючеsий
cout « "Перехват строки: ";
cout « str « '\п';
int rnain ()
cout «
"НАЧАЛО\п";
Xhandler(l);
Xhandler(2);
Xhandler(O);
Xhandler(3) ;
cout «
"КОНЕЦ";
типа
char
*
580
Модуль 12. ИСl<лючения, шаблоны и I<ое-что еще
О;
return
Эта программа генерирует такие результаты.
НАЧАЛО
Перехват
Исключение
!
Перехват !
Перехват
Исключение
строки:
Перехват!
N': 1
N': 2
Значение
Исключение
~:
равно
нулю.
3
КОНЕЦ
Как видите, каждая саtсh-инструкция отвечает только за исключение "сво­
его" типа.
В общем случае саtсh-выражения проверяются в порядке следования, и вы11ОЛЮlется только тот саtсh-6ЛОI<, в котором тип З<1даИIiОГО исключения совпа­
дает с типом сгенерированного исключения. Все остальные саtсh-блоки игно­
рируются.
Перехват исключений базового класса
Важно поиимать, как ВЫПОЛНЯЮТСЯ саtсh-ЮIСТРУКЦИИ, связанные с про из­
водными классами. Дело в том, что саtсh-выражение для базового класса "от­
реагирует совпадением" на исключение любого производного типа (т.е. типа,
выведенного из этого базопого класса). Следовательно, если нужно отдельно
перехватывать исключения как базового, так и производного типов, в
catch-no-
следователЫЮС1'И саtсh-инструкцию ДЛЯ производного типа необходимо по­
местить перед са tсh-И/fструкцией для базового типа. В противном случае са­
tсh-выражение для базового класса будет перехватывать (помимо "своих'") и
исключения всех производных классоп. Рассмотрим, например, следующую про­
грамму:
// Перехват исключений производных типов.
// Эта программа не корректна!
#include <iostream>
using namespace std;
class
В
{
};
class D: public
В
{
С++: руководство ДЛ~ начинающих
581
-
}i
int main ()
D derivedi
try (
throw derivedi
catch(B
// Эти саtсh-ивструкции расположекы
// корректном пор.~ке! Искnючени.
Ь)
//
//
cout «
catch(D d)
cout «
return
не а
проиsво~ных типов &у8Ио перехва~атъ
~o исключений базовых ТИПОВ.
"Перехват
"Этот
исключения
перехват
базового класса.\п";
никогда
не
произоЙдет.\п";
О;
ПОСКОЛЬКУ здесь объект
ба;ювого класса В, то
derived - это объект класса D, который выведен И3
исключение типа derived будет всегда перехватываться
первым саtсh-выражением; вторая же саtсh-инструкция при этом никогда не
выполнится. Одни компиляторы отреагируют на такое положение вещей преду­
преждающим сообщением. Другие могут выдать сообщение об ошибке. В любом
случае, чтобы исправить ситуаuию, достаточно поменять порядок следования
этих са tch-ИНСТрукций на ПРОТИDОIIОЛОЖIIЫЙ.
Перехват всех исключений
Иногда имеет смысл создать обработчик для перехвата всех исключений, а не
исключений только определеНIiОГО типа. Для этого достаточно использовать та­
кой формат саtсh-блока.
catch( ... )
11 Обработка
всех исключений.
Здесь заключенное в круглые скобки многоточие обеспечивает совпадение с
любым типом данных.
582
Модуль
1'2. ИСI<лючения. шаблоны и I<ое-что еще
Использование формата
catch ( ... )
илл/ОСтрируется в следующей про­
грамме.
// В этой про грамме перехватываются исключения всех ТИПОВ.
#include <iostream>
using паmеsрасе std;
void Xhandler(int test)
{
try{
if(test==O) throw test; // генерирует iпt-исключение
if(test==l) throw 'а'; // генерирует сhаr-исключение
if(test==2) throw 123.23; // генерирует
// dоublе-исключение
catch( ... ) { /1 перехва~ всех
cout « "Перехват!\п";
искnючеRИЙ
int main ()
cout «
"НАЧ!U10\п";
Xhandler(O);
Xhandler(1);
-Xhandler (2);
cout «
rеturл
"КОНЕЦ";
О:
Эта программа генерирует такие результаты.
НАЧАЛО
Перехват!
Перехват!
Перехват!
КОНЕЦ
С++: PYI<OBOACTBO дl\9I начинающих
Как видите, функция
char
11
Xhandler ()
double. И ВСС ОШI
catch ( ... ).
генерирует исключения трех типов:
583
int,
перехватываются с помощью одной-еДIlнственной ИII­
струкции
Зачастую имеет смысл использовать инструкцию са tch
( ... )
в качестве по­
следнего "рубежа" саtсh-послсдовательности. В этом случае она обеспечиuае"I'
перехват исключений "всех остальных" типов (т.е. не I1рс!дусмотрсrшых предыду­
щими саtсh-выражениями). Это
-
удобный способ перехватить все исключе­
ния, которые вам не хочется обрС\батывать в явном виде. Кроме того, перехва­
тывая абсолютно все исключения, вы предотвращаете возможность аварийного
завершения программы, которое может быть вызвано каким-то lIепредусмотрен­
ным (а значит, необработанным) исключением.
Определение исключений, генерируемых
функциями
Программист может определить тип "Вllешних" исключений, генерируемых
ФУНlщиеЙ. Можно также оградить функцию от l'еllерирования каких бы то нн
было исключений вообще. Для формироваНIIЯ этих ограничешrй необходимо
внести в определеtlие функции throw-выражеНllе. Общий формат определения
функции с использованием thrоw-выражения выглядит так
тип
имя_функции (список_зргументов)
thrоw(список_имен_типов)
1/ ...
Здесь элемент список_имен_ ТИПОВ должен включать толr,ко те имена типов дан­
НЫХ, которые разрешается генеРИРОllать функции (элементы списка разделяются за­
пятыми). Генерирование исключения любого друтого типа приведет к аварийному
окончанию програММhl. Если нужно, чтобы ФУНЮЩЯ вообще не МОГ.lа генерировать
исключения, используйте в качестве этого элемента пустой список.
~
На заметку
n. .
На момент написаиия этой книги среда Visual С++ не обеСllечивала ДЛЯ функции запрет генерировать исключения, тип которых
не задан в thrоw-выражении. Это говорит о lJt~стан!{арпюм пове­
дении данной среды. Тем не меиее вы нее раВIIО можете задавать
"ограничивающее" thrсw-выражение, 110 оно D ЭТОМ случае будет
"грС\ть лишь "УНСДОМI1ТСЛЬНУЮ" роль.
На примере следующей lIрограММbl IIоказано, как можно указать типы исклю­
чений, которые способна генерировать функция.
584
//
Модуль 12. ИС1<лючени~, шаблоны и 1<ое-что еще
Ограничение
типов
исключений,
генерируемых функцией.
#include <iostream>
using namespace
stdi
,
// Эта функция мож~т генерировать
/1 исключения только типа int, char и double.
void Xhandler(int test) throw(int, char, double) /1 Здесь
1/ указаны типы ис~чений, которые
// KorYT быть сrенерировавы функцией
// Xhandler().
if(test==O) throw testi // генерирует iпt-исключение
if(test==l) throw 'а'; // генерирует сhаr-исключение
if(test==2) throw 123.23; 1/ генерирует
11 dоublе-исключение
int main ()
cout «
"НАЧАЛО\п";
try{
Xhandler(O); /1
/1
Попробуйте
функции
также
передать
Xhandler()
аргументы
catch (int i) {
cout « "Перехват
iпt-исключения.\п";
catch (char
cout «
сhаr-исключения.\п";
с)
(
"Перехват
catch(double d) {
cout « "Перехват
cout «
return
"КОНЕЦ";
О;
dоublе-исключения.\п";
1
и
2.
С++: руководство ДМI начинающих
585
в этой программе функция
только типа
int, char
и
Xhandler () может генерировать исключения
double. При попытке сгенерировать исключение любо­
го другого типа про изойдет аварийное завершение программы. Чтобы убедиться
в этом, удалите из thrоw-списка, например, тип
(Как упоминалось выше, среда
Visua)
int
и перезапустите программу.
С++ пока не ограничивает ИСКЛЮ'lения. ко­
*
(J)
о
!т
ф
о
торые может генерировать функция.)
~
Важно пони мать, что диапазон исключений, разрешенных для генерирования
:s:
функции, можно ограничивать только типами, генерируемыми ею в trу-блоке,
:J:
из которого она была вызвана. Другими словами, любой trу-блок, расположен­
~
ный в теле самой функции, может генерировать исключения любого типа, если
3
они перехватываются в теле тоН же функции. Ограничение применяется только
Ii
для ситуаций, когда "выброс" исключений происходит за пределами функции.
ключение будет передано во внешнюю trу/саtсh-последовательность. Чаще
служит стремление
позволить доступ к одному исключению нескольким обработчикам. Например,
первый обработчик будет сообщать об одном аспе.,те исключения, а второй
-
о другом. Исключение можно повторно сгенерировать только R саtсh-блоке
(или в любой функции, вызываемой иэ этого блока). При повторном генериро­
вании исключение не будет lIерехватываться той же catch-IШСТРУКЦl1еЙ. 0110
распространится на следующий са tсh-блок. Повторное генерирование исклю­
чения р.емонстрируется в следующей IIрограмме (в даНIIОМ случае повторно гене­
//
Пример
char *).
"повторного
генерирования"
исключения_
#include <iostream>
using namespace std;
void Xhandler ()
{
try {
throw
"Привет";
11
генерирует
:s:
:J:
~u
вторно сгенерировать исключение в его обработчике. В этом случае текущее ис­
рируется исключение типа
о
:s:
Используя thrоw-инструкцию без указания типа исключения, можно по­
throw
о
(J)
Повторное генерирование исключения
всего причиной для такого выполнения инструкции
:о
исключение
типа
char *
catch(char *) { 11 лерехватывает исключение типа char *
cout « "Перехват исключения в функции Xhantiler. \п";
throw ; 11 Повторное генерирование исxnючеНИR
586
Модуль
12. ИСl<лючения, шаблоны и I<ое-что еще
/ /
'1'JШ8
//
аве фVВJЩИИ
хоторое буд8'1' перехаа.ево
char "',
Xhandler.
int main ()
cout
«
"НАЧАЛО\п";
try{
Xhandler () ;
catch (char *)
cout « "Перехват исключения з функции main () . \п";
cout
«
return
"КОНЕЦ";
О;
При пыпоmlСНИИ эта программа генерирует такие результаты.
НАЧАЛО
Перехват
исключения
в
функции
Перехват исключения
в
функции
Xhandler.
main().
КОНЕЦ
~просы ДЛЯ теl<ущего I<ОНТРО~Я
- - -_ _ _ __
1.
Покажите, как обеспечить перехват всех исключений.
2.
Как указать тип исключений, которые MOryT быть сгенерированы за пре­
делами тела функции?
З. Как можно повторно сгенерировать исключение?
li
-=--«
гт
п.
•
1.
•
Чтобы обеспечить перехват всех ИСКJJюченнй, достаточно использовать формат
catch( ... ).
2.
Чтобы указать тип исключений, которые могут быть сгенерированы за пределами
теJlа функции, необходимо включить в ее определение thrоw-выражение.
З.
Чтобы повторно сгенерировать исключение. достаточно использовать КJlЮчевое
слово
throw без знзчеЮiЯ.
С++: руководство ДЛЯ начинающих
587
Спросим у опытного программиста
Вопрос.
Из всего В6Iшесказаl"'ОZО следует, что функция MWlcem сооБЩllть 06 ошиб­
ке двумя сnособtlAlU: путем zeHepupO'UHIUI "СКJlючеIlUR или 80зврата кода
ошu6КIL В каких случаях следует UСllолыоtlаmь зm" способ.. ?
ОтвеТ.
ф
о
1-
:r
В настоящее время пpuфессионалы рекомендуют использовать исключс­
Ф
О
~
liИЯ, а не коды ошибок. В языкахjаvа и С# о распространенных ошибках
:s:
(например, возникающих при открытии файла I!ЛИ l1ереl10лнеН1IИ) сооб­
1i
щается только с помOlЦJ>Ю исключений. ПОСКОЛI>КУ С++ произошел от С,
дЛЯ отчета об ошибках в нем предусмотрена возможность IICПОJJI,;ювания
как кодов ошибок, так
11
исключений. Поэтому индикация многих оши­
бочных ситуаций, связаных с библиотечными функциями, обеспе<щвзl'Т­
ел за счет кодов ошибок. Но в новых программах согласно современным
тенденциям в программироваНI1И вам следует для этих целей использо­
в исходную спецификацию С++, 11 лишь несколько лет нао1М стал неотъемлемоj:,!
частью программирования на С++. Шаблоны позволяют достичь одну из самых
Используя
шаблоны,
О
3
ri
:s:
::t
Ф
:r
Q
<
:s:
это одно из самых сложных и мощиых средст13 8 С++. Он не пошел
трудных целей 8 програММИРОDаllИИ
2
10
u
Шаблоны
-
::t
~
вать исключе1lИЯ.
Шаблон
~
-
создать многократно используеМЫII !<од.
можно создавать обобщеtlllые функции
Jf
классы.
В обобщенной функции (или классе) обрабатываемый ею (им) тип данных зада­
ется как параметр. Таким образом, одну функцию или класс можно использовать
для раэных типов даюlых' не предоставляя явным обраЗОI\'1 конкретные версии
для каждого типа данных.
ВАЖНО\
"'JОБОБ'А,емные ФVrНК1 tии
Обобщенная функция определяет общий набор операций, которые преднаЗllа­
чены для применения
1< данным различных типов. Тип данных, обрабатываемых
функцией, передается ей как параметр. 'Используя обобщенную ФУНКЦИЮ, к ши­
рокому диапаэону данных можно применить единую общую процедуру. Возмож­
но, вам известно, что Мlюгие алгоритмы Itмеют одинаковую логику для разных
типов даиных. Например. один и тот же алгоритм сортировки
Quicksort
приме­
няетс.я If к массиву целых чисел, и к массиву значений с плавающей точкой. Раз­
личие здесь состоит только в типе сортируемых данных. Создавая обобщенную
588
МОДУЛЬ 12. ИСI<лючени~, шаблоны и кое-что еще
функцию, можно определить природу алгоритма нсзависимо от типа данных.
После этого I(ОМПИЛЯТОР автоматически сгенерирует корректный код для пша
данных, который в действительности используется при выполнении этой функ­
ции. По сути, создавая обобщенную функцию. вы создаетс ФУНlЩИЮ, которая ав­
томатически перегружает себя саму.
Обобщенная функция создается с помощью ключевого слова
Обычное значение слова
templa ;:е.
"tel1lplatc" точно отражает целъ его примснения в С++.
Это ключевое слово используется ДЛЯ создаllИЯ шаблона, который описывает
деЙСТ8ИЯ, выполняемые функцией. Компилятору же остается "дополнить недо­
стающие детали" в соотвеТСТ8ИИ с заданным значением парамстра. Общий фор­
мат определения шаблонной функции имеет СЛСДУЮЩИl';'1 вид.
template <class Ttype>
тип имЯ_функции(список_параметров)
{
//
тело функции
Здесь элемент Ttype представляет собой "заполнитель" для типа данных, об­
рабатываемых функцией. Это имя может быть использовано в теле функции.
Но оно означает всего лишь "заглушку", вместо которой компилятор автоматиче­
ски подставит реaJlЫIЫЙ тип данных при создаНlI1I конкретной версии функции.
И хотя для задания обобщеюlОГО типа в tеи.рlаtе-объяплснии по традиции
применяется ключевое слово
class,
можно также использовать ключевое слово
typename.
В следующем при мере создается обобщенная фУI-IIЩИЯ, которая меняет места­
ми значения двух llеремеШIЫХ, используемых при ее ВЫЗОВС. Поскольку общий
процесс обмена значениями переМСIII-lЫХ НС зависит от их типа, 011 является пре­
красным кандидатом для создания обобщенной функции.
//
Пример
шаблонной
функции.
#include <iostream>
using namespace std;
//
//
Определение шаблонной фуихции,
хоторан меннет местами
значения своих аргументов.
template <class
Х>
void swapargs(X
&а,
/ /
Х
temp;
Х
&Ь)
//
Х
-
это
обобщеННJ.IЙ '1'ИП данных.
С++: PYI<OBOACTBO ДЛt;l начинающих
temp =
а
Ь
589
а;
Ь;
= temp;
int main ()
int i=10, j""2Q;
double х=10.1, у=23.3;
char а='х', b='z';
cout «
"Исходные
значения
i, j : " «
cout «
"Исходные
значения
х,
у:
cout «
"Исходные
значения
а,
Ь:
«
" «
«
" «
«
,
i «
«
«
«
«
«
j
х
у
а
Ь
' \п' ;
,
' \п' ;
,
' \п' ;
// КОNПИnRтор автоматически соз~ает версии функции
// swaparqs(), которые используют различные типы давиых,
/ / задаикwе ее аргументами.
swapargs(i, j); 1/
swapargs(x, у); 11
swapargs(a,
Ь);
1/
1/
перестановка
целых чисел
перестановка
значений
с
плавающей точкой
перестановка
символов
cout «
"После
перестановки
i,
j:
cout «
"После
перестановки
х,
у:
cout «
"После
перестановки
а,
Ь:
return
" «
«
«
"
«
" «
«
i «
j
х
у
а
Ь
«
«
«
«
«
О;
ВОТ как выглядят результаты ВЫПОЛllения этой программы.
Исходные
значения
Исходные
значения
i, j: 10 20
х, у: 10.1 23.3
,
' \п' ;
,
' \п';
,
'
\п'
;
590 МОДУЛЬ 12. ИСl<лючени~, шаБЛОНbI
и I<ое-что еще
................................................................
...............................................................
.
~
Исходные
После
значения
Ь: х z
i, j: 20 10
х, у: 23.3 10.1
а, Ь: z х
а,
перестанов~и
После
перестанов~и
После
перес~ансв~и
И'гак, рассмотрим внимательно код программы. Строка
template <class
Х>
void swapargs(X
&а,
Х
&Ь)
сообщает компилятору, во-первых, о том, что создастся шаблон, и, во-вторых, что
здесь начинается обобщенное Оl1ределение. Обозначение х представляет собой
обобщенный тип, который используется В качестве "заполнителя". За
tе-заголовком следует объявлеНИt: функции
swapargs () , в
templa-
котором символ Х
означает тип данных для значеttий, которые будут меняться местами. В функции
main ()
демонстрируется НЫЗОD функции
ра.l.'1ИЧНЫХ типов данных:
in t,
Поа t 11
swapargs () с использованием трех
char. Поскольку функция swapargs ()
является обобщенной, компилятор автоматически создает три версии ФУJiК­
Цl1И
swapargs () : одну
для обмена целых чисел, вторую для обмена значениJ'i с
плавающей точкой и третью для обмена СИМВО.lОв. Таким образом, одну и ту же
обобщенную ФУНКЦИЮ
swapargs () можно использоватъдля обмена аргументов
любого типа данных.
Здесь необходимо уточнить некоторыс важные термины, связанные с шабло­
нами. Во-первых, обобЩС:НН<UI функция (т.е. функция, 06ЪЯDJlение которой пред­
варяется tеmрlаtе-инструкциеi1) также называется шаБЛО1l11ОU функцией. Оба
термина используются 11 этоН книгс взаимозаМСliяемо. Когда компилятор создаст
КОН1сретную версию этой ФУНКflИИ, то говорят, что создается ее С1lецuanuзацll.R
(или коuкретuзацuя). СllеUИaJIизация также называется nорождае.моЙ фуюсцuей
(generated functioll).
Действие норождения функции определяют как ее реализа­
ЦИЮ (iпsttшtiаtil1g). ДРУПIМI1 СJJOВ<lМИ, порождаемая функция является конкрет­
ным экземпляром шаблонной фунrщии.
Функция с ДВУМЯ обобщенными типами
в tсmрlаtе-инструкции можно опредеЛI11Ь несколько обобщеJJНЫХ типов
данных, используя список элементов, разделенных заIlЯТОЙ. Например, в следу­
ющ~й Ilрограмме соэдается шаблонная ФуtlКЦЮI с двумя обобщенными типами.
#include <iostream>
using namespace std;
template <class Туре1, class
void myfunc(Type1 х, Туре2 у)
Туре2>
//
Два обоб~RВblX типа.
С++: pYt<OBOACTBO ДМ1 начинающих
cout «
х
«
' , «
у
«
591
'\п';
:Ф
::j
;Ф
int main ()
:0
:5=
myfunc(lO,
:Ф
"Привет");
:0
:~
:s;
Ji
:r
myfunc(0.23, lOL);
:0
:<
return
:\0
:0
;3
О;
:..;
::s;
::r
в этом примере при выполнении функции
main () , когда КОМПИЛЯТОР гене­
рирует конкретные экземпляры функции myfunc (). заполнители типов typel
и type2 заменяются сначала парой типов данных int и char ... а затем парой
double и long соответственно.
Явно заданная переrрузка обобщенной функции
Несмотря на то что обобщенная функция сама l1срегружается по мере необ­
ходимости, это МОЖНО делать и явным образом. ФормалЬНО этот процесс НаЗЫ­
вается явноii сnеЦUШlU.1m~uР.й. ПРИ пере грузке обобщеJfНая фушшия l1ереОllрсде­
ляется "в пользу" этой конкретной версии. Рассмотрим, например. следующую
программу, которая представляет собой переработанную версию ПРИВСДСI\lЮГО
выше примера программы обмена значений аргументов.
1/
Специализация
шаблонной
функции.
#include <iostream>
using narnespace std;
template <class
Х>
void swapargs(X
&а,
Х
&Ь)
{
х
ternp;
temp =
=
Ь;
Ь
=
temp;
ccut «
11
а;
а
"Выnолняе;гся
шаблонная
функция
swapargs()
.\л";
~a ф)'RКЦИ. ..но переопредen.ет обобщекиYID версИJD фунJЩИИ
:Ф
1~
:u
;S
592
МОДУЛЬ 12. ИСI<лючениS1, шаблоны и I<ое-что еще
// 8vaparqs() AПR
void swapargs(int
int-параме~о•.
&а,
int
&Ь)
{
int temp;
temp =
а
=
а;
Ь;
= temp;
cout « "Это
Ь
iпt-специализация
ФУНКЦИИ
swapargs()
int main ()
int i=lO, j=20;
double x=lO.l, у=2З.З;
char а=' х', b='z';
cout «
"Исходные
значения
i, j: " «
cout «
"Исходные
значения
х,
у:
cout «
"Исходные
значения
а,
Ь:
swapargs(i, j) ; //
//
swapargs(x, у) ; //
swapargs(a,
cout «
Ь)
"После
фуккцик
«
«
«
«
«
j
х
у
а
Ь
'\п'
;
'\п'
;
'\п'
;
, ,
, ,
svaparqs().
Обобщенная
функция
// swapargs() .
; // Вызывается
обобщенная
,
// swapargs() .
функция
Вызывается
перестановки
i,' j:
"После
перестановки
х,
cout «
"После
перестановки
а,
О;
«
" «
«
" «
«
вызыве'.1'cJI _но пережорреННaJI
cout «
return
, ,
i «
у:
Ь:
" «
«
" «
«
" «
х
«
Ь
i «
j
у
а
«
«
«
«
«
, ,
'\п' ;
, ,
'\п'
;
'\п'
;
, ,
.\п";
С++: РУI<ОВОДСТВО AMl начинающих
593
При выполнении эта программа генерирует такие результаты.
i, j: 10 20
у: 10.1 23.3
Исходные значения а, Ь: х z
Это iпt-специализация функции swapargs().
Выполняется шаблонная функция swapargs() .
Выполняется шаблонная функция swapargs() .
После перестановки i,
j : 20 10
После перестановки х,
у:
23.3 10.1
Исходные
значения
Исходные
значения
После
перестановки
х,
а,
Ь:
z
х
Как отмечено в комментариях, при вызове функции
полняется явно лерегруженная версия ФУНКIIИИ
swapargs (i, j) вы­
swapargs (), определенная
в программе. Компилятор в этом случае не генерирует эту версию обобщенной
функции
swapargs () , поскольку
обобщенная фующия переопределяется явно
заданным вариантом перегруженной функции.
Для обозtlачения явной специализации функции можно использовать IIОВЫЙ
альтернативный синтаксис, содержащий ключевое слово
template.
Например,
если задать специализацию с использованием этого альтернативного синтаксиса,
перегруженная версия функции
swapargs ()
из предыдущей программы будет
выглядеть так.
11 Использование нового синтаксиса задания специализации.
template<> void swapargs<int>(int &а, int &Ь)
{
int temp;
temp ==
а;
а
=
Ь;
Ь
=
temp;
cout «
"Это
iпt-специализация функции
swapargs.\n";
Как видите, в новом синтаксисе ДЛЯ обозначения специализации использует­
ся конструкция
ternplate<>.
Тип данных, ДЛЯ которых создается эта специали­
зация, указывается в угловых скобках после имени функции. Для задания любо­
го типа обобщенной функции используется один и тот же синтаксис. На данный
момент ни один из синтаксических способов задания специализации не имеет
никаких преимуществ перед ДРУШМ, но С точки зрения перспективы развития·
языка, возможно, все же лучше использовать новый стиль.
594
Модуль 12. ИСl<лючениS1, шаблоны и I<ое-что еще
Явная специализация шаблона позволяет спроектировать версию обо6щеЮ1 ой
функции в расчете на некоторую уникальную ситуацию, чтобы, возможно, в,х­
пользоваться преимуществами повышенного быстродействия программы то.'1ь­
ко для одного типа данных. Но, как правило, если вам нужно иметь различные
версии функции для разных типов данных, лучшс использовать перегружснные
функции, а не шаблоны .
•
Обоб""еиwbl.& КАассы
Помимо обобщенных функций, можно также определить обобщенный класс.
Для этого создается класс, в котором определяются все используемые им Cl.Лго­
ритмы; при этом реальный тип обрабатываемых в нем данных будет задан как
параметр при создании объсктов этого класса.
Обобщенныс классы особенно полезны в случае, когда в них используется
ЛOl'ика, которую можно обобщить. Например, алгоритмы, которые поддержи­
вают функционирование очереди целочисленных значений, также подходят и
для очереди символов. Механизм, который обеспечивает поддержку связно­
го списка почтовых адресов, также ГОДlпся для поддержки связного списка,
преднаЗllачешlOГО для хранения данных о запчастях к автомобилям. 110(ле
создания обобщенный класс сможет ВЫIIOЛIJЯТЬ определенную программи­
стом операцию (например, поддержку'очереди или связного CI1l1c((a) для лю­
бого типа данных. Компилятор <lвтоматичсски сгенерирует корректный тип
объекта на основе типа, заданного при создании объекта.
Общий формат объявления обобщенного класса имеет следующий вид:
template <class Ttype> class
// тело класса
имя класса
Здесь элемент Ttype представляет собой "заполнитель" для имени типа, кото­
рый будет задан при реализации класоа. При необходимости МОЖНО определить
несколько обобщенных типов данных, используя список элементов, разделенных
запятыми.
Сформировав обобщенный класс, можно создать его конкретный экземпляр,
используя следующий общий формат.
имя_классС'
<тип>
имя_объекта;
Здесь элемент тип означает имя типа данных, которые будут обрабатываться :ж­
зсмпляром обобщенного класса. Функции-члены обобщенного класса автомати-
С++: РУКОВОДСТВО ДЛЯ начинающих
595
чески являются обобщенными. Поэтому вам не нужно использовать ключевое
слово
ternplate для явного определения
их таковыми.
Рассмотрим простой пример обобщенного класса.
11
Пример простого
оБОБщенного
класса.
*include <iostrearn>
using narnespace std;
clas5 MyClas5 ( 11
11
Т>
ternplate <class
11
т
Х,
xnасса.
т
-
З~еClo
обобщеllJDlЙ '!'ИП.
у;
public:
MyClass(T
т
О~еRИ. обо~вво~о
х
=
а;
у
;
Ь;
а,
Т
ь)
(
{ return x/y; }
div()
};
intrnain()
1/ Создаем версию класса MyClass для doubles-эначенИЙ.
MyClass<double> d_ob(10.0, 3.0); 11 СО8~ание KoaкpeTBO~O
11
cout «
«
"Результат
деления
d_ob.div() «
экземпnRp& обобщеВRО~О xnасса.
dоublе-значений:
"
"\n";
1/ Создаем версию класса MyClass для iпt-эначенИЙ.
MyClass<int> i оЬ(10, 3);
cout « "Результат деления iпt-экачений: "
« i_ob.diV() « "\п";
return
О;
Ре:~ультаты выполнения этой программы таковы.
Результат деления
dоublе-значений:
Результат деления
iпt-значений:
3
З.З3333
596
Модуль 12. Исключени~, шаблоны и J(ое-что еще
Как видно по результатам, объект типа
плавающей точкой, а объект типа
int -
вьшолнил деление чисел с
double
целочисленное деление.
После объявления KOНl<peTHoгo экземпляра класса
томатически генерирует все версии функции
di v ()
MyClass
компилятор ав­
и переменные х и у, необ­
ходимые для обработки реальных данных. В данном при мере объявляются ;J,Ba
объеI<та разного типа. Первый, d _ оЬ, обрабатывает данные типа
double.
Это
означает, что перемеНllые х и у, а также резУЛl)тат деления (как и значение, воз­
вращаемое функuией
di v ( ) )
имеют тип
doubl е.
Второй объект,
i _ оЬ,
обраба­
тывает целочисленные данные. Таким образом, переменные х и у, а также значе­
ние, возвращаемое функцией
di v () , имеют тип int.
Обратите особое внимание
на эти объявлеliИЯ:
MyClass<double> d_ob(10.0, 3.0);
MyClass<int> i_ob(10, 3);
3ам:етьте, как указывается нужный тип данных: он заключается в угловые скоб­
ки. Изменяя тип данных при создании объектов класса
MyClass,
можно изме­
ю~ть тип данных, обрабатываемых этим классом.
lUаблонный класс может иметь несколько обобщенных типов данных. Для
этого достаточно объявить все нужные типы данных в
templa tе-спецификации
8 виде элементов списка, разделяемых запятыми. Например, в CJJедующей про­
грамме создается класс, который использует доа обобщенных типа данных.
/*
Здесь
в
используется
определении
два
обобщенных типа данных
класса.
*/
ffinclude <iostream>
using namespace std;
template <class
Тl,
class
Т2>
class MyClass
(
Тl
Т2
i;"
j;
public:
myclass(Tl
void show ()
а,
Т2
Ь)
cout «
i
=
i «
j =
' , «
а;
Ь;
j
};
int main ()
MyClass<int, double>
оЫ(10,
0.23);
«
'\п';
}
С++: PYI<OBOACTBO дl\>! начинающих
MyClass <char, char *>
obl.show(); //
ob2.show(); //
return
оЬ2('Х',
"Это
отображение
int-
отображение
значений
591
тест.");
и dоublе-значе~ий
типа
char
и
char *
О;
Эта программа генерирует такие результаты.
10 0.23
Х
Это
тест.
В данной программе объявляется два вида объектов. Объект оЫ использует
данные типа
и
int
double.
а объект оЬ2
-
символ и указатель на символ. Для
этих ситуаций компилятор автоматичеСЮi генерирует данные и функции. соот­
ветствующие конкретному способу создания объектов.
Явно задаваемые специализации классов
Подобно шаблонным функциям можно создавать и специализации обоб­
щенных классов. Для этого используется конструкция
templa te<>.
которая
работает 110 аналогии с явно задаваемыми специализациями функций. Рас­
смотрим пример.
//
Демонстрация
специализации
класса.
iinclude <iostream>
using namespace std;
template <class
т
class MyClass (
Х;
public:
MyClass(T
cout «
Х
т
Т>
=
а)
{
"В теле оБОБщенного класса
MyClass.\n"i
д;
getx() { return
х;
}
};
//
Я:вкак специa.nизации
lUIacca
МyClass МИ 'X'ИI1а
template <> class MyClass <int> {
int.
598
Модуль
12. ИСI<лючени~, шаблоны и I<ое-что еще
int Х;
public:
MyClass(int а)
cout « "В
х = а * а;
int getx ()
теле
специализации
( return
Х;
MyClass<int>.\n"i
I
);
int main ()
MyClass<double> d(lO.l);
cout « "double: " « d.getx() «
MyClass <int> i(5); //
//
cout «
return
"int: " «
"\п\п";
Здесь используется RВBaR
специa.nиэаЦИR
i.getx() «
lU1acca
МyClass.
"\п";
О;
При выполнении данная программа отображает такие результаты.
В
теле
о~общенного
класса
MyClass.
doubl е: 1 О • 1
в
теле
специализации
MyClass<int>.
int: 25
В этой программе обратите особое внимание на следующую строку.
template <> class MyClass<int> {
Она уведомляет компилятор о том, что создается явная
са
MyClass.
in t-специализация кла<:·
ТОТ же синтаl<СИС используетс}! и для любого другого типа специа­
лизации класса.
Явная специализаЦJlЯ )<лассов расширяет диапазон ПРlfменения обобщен­
ных классов, поскольку она позволяет легко обрабатывать одну или две спе­
циальные ситуации, оставляя все остальные варианты для автоматической
обработки компилятором. Но если вы заметите, что у вас создается слишком
много специализаций, то тогда, возможно, лучше вообще отказаться от созда­
ния шаблонного класса.
С++: РУI<ОВОДСТВО ДЛ91 начинающих
599
~npocы ДЛЯ текущего контроля ________
1.
Какое ключевое слово используется ДЛЯ объявления обобщенной функ­
3.
Будут ли в обобщенном классе все его функции-члены обобщенными ав­
. . . . . .______. . . . . .
о
7
Ф
х
Можно ли явным образо~ перегружать обобщенную функцию?
томатически?·
ф
...
О
ции J1ЛJ1 I(ласса?
2.
~
S
2i
:J:
~
ID
О
. . . . . .____. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. .
mв
3
с;;
s
:J:
Проект
12.1. r.So.ctдa~_
С\,
1: 1, "
• It • С - "'~\, 1'11
очереди
GenericQ. срр
:s:
В ПРОСКТС 8.2 мы создали класс Оиеие, предназначенный
преnбразуем его в обобщенный класс. Класс Оиеие
-
прекрасный кандидат
для преобразования в обобщснный класс, поскольку его логика не зависит от
типа данных, которыми 011 оперирует. другими словами, один и тот же меха­
в этом случае можно использовать для организации очереди перемен­
ных любого Тlша (например, Ilелых чисел, значений с плавающей точкой или
даже объеКТОR классов, создаваемых программистом). Определив обобщен­
ный класс
Queue,
мы сможем затем использовать его везде, где нам нужно
организовать очередь объектов.
Последовательность действий
1.
Скопируйте класс
2.
Прео6разуйте прсжнее объявление класса
Queue
из проекта
8.2
в файл с именем
Queue
GenericQ. срр.
в шаблонное.
tcmplate <class QType> class Queue {
Здесь для обозначения обобщенного типа данных используется идентифи­
катор
1.
QType.
Для объявления обобщенной функции ИЛII класса используется ключевое слово
template.
2.
~
U
ДЛЯ организации очереди символов. В этом проекте мы
низм
Ф
7
Да, обобщенную функцию можно явным образом перегружать.
з. Да. в обобщенном классе все ero функции-члены автоматически 6yдyr обобщенными.
600 Модуль 12. ИСI<лючениS1, шаблоны и кое-что еще
..............................................................................,.................................................. .
3.
Замените прежний тип данных массива
QType q[maxQsize]; 1/
Поскольку массив
q
q
идентификатором
QType.
Этот массив будет содержать
очере~ь.
теперь имеет обобщенный тип, его можно ИСПО.Jlьэо­
вать для храиения объектов любого типа, который будет объявлен при соз­
дании экземпляра класса
4.
Queue.
Замените преЖJiИЙ тип данных параметра функции
put ()
идентификато­
ром аТуре.
/1
Помещаем данные в
очередь.
void put(QType data) (
if(putloc == size) {
cout « " -- Очередь
return;
заполнена.\п";
putlOC++i
q[putloc] = data;
5.
Замените прежний тип данных параметра функции
get ()
идентификато­
ром ОТуре.
1/
Извлекаем данные из очереди.
get() (
if(getloc == putloc) (
cout « " -- Очередь
return О;
аТуре
пуста.\п";
getloc++;
return q[getloc);
6.
Ниже приводится полный код определения класса аиеие вместе с функ­
цией
main ()
,которая демонстрирует его использование.
/*
Проект
12.1
Шаблонный класс
*/
#include <iostream>
очере~и.
С++:
PYI<OBOACTBO
для начинающих
601
-
using namespace std;
ф
const int maxQsize = 100;
:j
ф
...:r
о
// Создание обобщенного класса очереди.
template <class QType> class Queue (
QType q[maxQsize];
// Этот массив будет содержать
// очередь.
int size;
// Максимальное количество
1/
1/
int putloc, getloc; 1/
//
элементов,
хранить
в
которое можно
используемые
функциях
put()
и
:0
:3
:s;
1:
Ф
get(}.
Q
;r
S
:S
Создание очереди конкретной длины.
(
Длина очереди должна не
превыwать
значение
// и выражаться положительным числом.
if(len > maxQsize) len = maxQsize;
else if(len <= О) len = 1;
size
len;
putloc = getloc
1/
О
.<
:\0
.u
Queue(int len)
/1
:s;
1i
:1:
в
public:
1/
~
ri
этой очереди.
Индексы,
I
Ф
О
О;
Помещаем данные в очередь.
void put(QType data) {
if (putloc == size) (
cout « " -- очередь
return;
putloc++;
q[putloc]
эаnолнена.\n";
data;
// Извлекаем данные
QType get() {
из очереди.
maxQsize
МОДУЛЬ
602
12. Исключения,. шаблоны и I<ое-что еще
if(getloc == putloc) I
cout « " -- Очередь
return О;
пуста.\п";
getloc++;
return q[getloc];
};
// Демонстрируем
int rnain ()
использование
обобщенного
класса
Queue.
{
Queue<int> iQa(lO), iQb(lO);
//
//
Создание двух
iпt-очередеЙ.
iQa.put(l);
iQa .put (2) ;
iQa.put(3);
iQb.put(10);
iQb.put(20)i
iQb.put(30);
cout « "Содержимое iпt-очереди iQa: ";
for(int i=O; i < 3; i++)
cout « iQa.get() « " ";
cout « endl;
cout « "Содержимое iпt-очереди iQb: ";
for(int i=O; i < З; i++) .
cout « iQb.get() « " ";
cout « endl;
Queue<double> dQa(lO), dQb(lO);
dQa.put(l.Ol);
dQa.put(2.02);
//
//
Создание двух
double-очередеЙ.
С++: PYI(OBOACTBO ДМ! начинающих
603
-
dQa.put(3.03);
: (1)
dQb.put(10.01)i
dQb.put (20.02) ;
:~
;(1)
:0
:~
dQЬ.рut(ЗО.О3);
:Ф
:0
cout « "Содержимое dоublе-очереди dQa: ";
for(int i=Oi i < 3; i++)
cout « dQa.get() « " ";
сои t «
end1;
cout « "Содержимое dоиblе-очереди dQb: ";
for(int i=O; i < 3; i++)
cout « dQb.get () « " ";
cout « endl;
return
О;
Результаты llыполнения этой программы таковы.
Содержимое
Содержимое
содержимое
Содержимое
7.
iпt-очереди
1 2 3
iQb: 10 20 30
dоublе-очереди dQa: 1.01 2.02 3.03
dоиblе-очереди dQb: 10.01 20.02 30.03
iQa:
iпt-очереди
На примере класса Оиеие иетрудно убедиться, что обобщенные ФУНКЦИИ и
классы представляют собой мощные средства, которые помогут увеличить
эффективность работы программиста. Они позволяют определить общий
формат объекта, который можно затем использовать с любым типом дан­
ных. Обобщенные ФУНКЦИИ и классы избавляют вас от утомительного тру­
да по созданию отдельных реализаций для каждого типа данных, подлежа­
ЩIIХ обработке единым алгоритмом. Эту работу сделает за вас компилятор:
он автоматически создаст конкретные версии определеН/юга вам и класса.
~
..
lИ.LДиwомиwес"ое расаР8Д8А81::1148
памяти
Для С++-программы существует два основных способа хранения информа­
шш в ппераТИШlO1i па~I.НТИ компьютера. Первый состоит в использовании пере-
:~
:s
:J5
::1:
:0
:<
:10
:0
:3
~~
~ffi
~~
:$
:(j
:S
604
Модуль 12. Исключени~, шаблоны и I(ое-что еще
менных. Область памяти, предоставляемая переменным, закрепляется за ними
I
во время компиляции и не может быть изменена при выполнении программы.
Второй способ заключаеТся в использовании C++-cucmeмы динамическоzо рас­
пределения nам.яmи. В этом случае память для данных выделяется по мере не­
обходимости из раздела свободной памяти, который расположен между вашей
программой (и ее постоянной областью хран('ния) и стеком. Этот раздел tta;Jbl-
вается "кучей" (heap). (Расположение программы в памяти схематично показано
на рис.
12.1.)
Высокая
обnасть
адресов
Нижняя
oбnасть
адресов
Рис.
12.1.
Исnользооаllue
nам.яти
в
С + + -nрогРа.м.А,е
Динамическое выделение памяти
-
это получение проrpаммой памяти во вре­
мя ее выполнения. Другими словами, благодаря этой системе программа может
создавать переменные во время выполнеЮIЯ, причем в нужном (в зависимости от
СI'ТУации) количестве. Эта система динамического распределения памяти осо­
бенно ценна для поддержки таких структур данных, как связные списки и двоич­
ные деревья, которые изменяют свой размер по мере их использования. Динами-
С++: руководство ДМ1 начинающих
605
зуется память из раздела, называемого "кучей". Нетрудно предположить, что JЗ
-
некоторых чрезвычайных ситуациях свободная память "кучи" может исчерпать­
5=
ся. Следовательно, несмотря на то, что динамическое распределение памяти (по
:.:
ческое вьщеление памяти для тех или иных целей
-
важная составляющая почти
всех реальных программ.
Чтобы удовлетворить запрос на динамическое выделение памяти, исполь­
сравнению с фиксированным) обеспечивает большую гибкость, оно и в этом слу­
new и delete, которые выполняют функ­
ции по выделению и освобождению памяти. Оператор new позволяет динами­
чески выделить область памяти, а оператор delete освобождает динаМИLJескую
память, ранее выделенную оператором new. Приводим их общий формат.
new
тип_переменной;
чение, тип которого задан элементом тип_ переменной. Оператор
new выделяет
область памяти, достаточную для хранения значения задаюJOГО типа, 11 возвраща­
ет указатель на эту область памяти. С помощью оператора
память для значений любого допустимого типа. Оператор
new можно выделить
delete освобождает
область памяти, адресуемую заданным указателем. После освобождения эта па­
мять может быть снова выделена в других целях при последующем пеw-запросе
на выделение памяти.
Поскольку объем "кучи" конечен, она может когда-нибудь исчерпаться. Если
для удовлетворения очередного запроса на выделение памяти не существует до­
статочно своБОдliОЙ памяти, оператор
bad_alloc
new
потерпит фиаско, и будет сгенериро­
(оно определено в заголовке
<new».
Ваша
программадолжна обработать это исключение и по возможности выl1лJ-lиTh дей­
ствие, соответствующее конкретной ситуации. Если исключеlIие не будет обра­
ботано вашей программой, ее выполнение будет прекращено.
Такое поведение оператора
new
в сл~чае не возможности удовлетворить за­
прос на выделение памяти определено стандартом С++. На таl<УЮ реализацию на­
строены также все современные компиляторы, включая последние версии
B\1ilder.
о
:s:
J5
.<
:10
:0
:3
ti
:s:
:J:
Ф
§
:S
Здесь элемент переменная-указатель представляет собой указатель на зна­
С++ и С++
ф
.()
переменная-указатель;
вано исключение типа
о
О
Язык С++ содержит два оператора,
delete
ф
:J:
чае имеет свои пределы.
переменная-указатель =
ф
3"
Visual
Однако некоторые более ранние компиляторы обрабатывают
пеw-инструкции по-другому. Сразу после изобретения языка С++ оператор леw
при неудачном выполнении возвращал нулевой указатель. Позже его реа.1Iиза­
ция была изменена так, чтобы в случае неудачи генерировал ось исключение, как
было описано выше. Если вы используете более старый компилятор, обратитесь
к прилагаемой к нему документации и УТОLJIIИТС, как реализован оператор
new.
Модуль 12. ИСl<лючения, шаблоны и ,<ое-что еще
606
Поскольку в этой книге мы придерживаемся стандарта С++, то во всех пред­
ставленных здесь примерах предполагается именно генерирование исключения.
Если ваш компилятор обрабатывает неудачи с выделением памяти по-другому,
то вам придется внести в примеры соответствующие изменения.
Рассмотрим пример программы, которая иллюстрирует использование опера­
торов
new
и
delete
(а именно выделяет память для хранения целочислеНIIОГО
значения).
//
Демонстрация
использования
операторов
new
и
delete.
#include <iostream>
#inc1ude <new>
using namespace std;
int main ()
{
int
*р;
try
р
new int; // Выдел.ем паккть Дn. int-зиаченик.
catch (bad_a110c ха) { // Проверка факта иеудачного
// выnоnнеИИR оператора new.
cout « "Не удалось вьщелить память.\п";
return 1;
*р
100;
cout «
cout «
"По
адресу
"хранится
" «
р
значение
«
"
".,
" «
*р
«
"\n";
delete р; // Освобо*даем п.м.ть.
return
О;
Эта программа присваивает указателю р адрес (взятой из "кучи") области па­
мяти. которая будет иметь размер, достаточный для хранения целочисленноro
значения. Затем в эту область памяти помещается число
100. после чего на экране
отображается ее содержимое. Наконец, динамически выделенная память ОС.ВО­
бождается.
С++: PYI<OВOACTBO ДМI начинающих
Оператор
delete
607
необходимо использовать только с тем указателем на па­
мять, который был возвращен в результате пеw-запроса на выделение памяти.
Использование оператора
delete с другим ТИПОм
адрес.} может вызвать сеРl,ез­
~
ф
ныe проблемы (даже ПОЛНЫЙ отказ системы).
...
о
:т
сь
Спросим у опыноrоo проrраммиста
Вопрос.
О
~
s:
Мне nРlIXD(}шrос • • идет. C++-nроzpIlJlUlbl, • которых с целью д.lНllJIfuче­
СIШZО раcnptЮеленlUI nllJlUJmu иC1UJЛыуюmCJI ФУIIКЧ"U та 11 ос
() " f ree ( ).
Язык С не поддерживает операторы пе~' и
памяти, а
delete.
Вместо них ДЛЯ вы­
и
free (). Фующия malloc () предназначена J!..1Я выделеНJlЯ
функция free () - для ее осnoбождения. С++ по-прежнему
подцерживает С-систему динамическоro распределения памяти. Поэтому
эти функции
MOryr встречаться В
С++-программах.
oco(jeHIIO
в тех, кото­
рые были переведсны с языка с. в новых С++-программах для выделе­
ния и освобождения памяти все же следует ИСПО.'1ьэооатъ операторы
de lete
new
И
(не только потому, ЧТО ОНИ более удобны, но и потому, ЧТО они не
позволят вам допустить некоторые ошибки, которые легко "проникают"
в программу при работе с функциями
malloc ()
и
free
(». и
ществует ОДНО "неписанное" правllЛО: не смешивайте ФУНКЦIfИ
и
free ()
с операторами
new
И
delete
еще. Су­
malloc ()
в одной программе. Нет никакой
Гdрантии, что они взаимно совместимы.
Инициализация динамически ВЫАеленной памяти
Используя оператор
new, динамически выделяемую память можно иющиали­
зировать. Для этого после имени типа задайте начальное значение (инициализа­
тор), заКJIЮЧИВ его в круглые скобки. Вот как выглядит общий формат операции
выделения памяти с использованием инициалиэатора:
переменная-укаэателъ
=
new
тип(инициалиэаТОР)i
Безусловно, тип инициалиэатора должен быть совместим с типом объекта, для
которого выделяется память.
Например, в следующей программе область памяти, адресуемая указателем р,
инициализируется значением
11
Инициализация
87.
памяти.
#include <iostream>
~
3
деления и осво60ЖДСIWЯ памяти ИСПО,11kIУЮТСЯ библиотечные ФУНКЦИИ
malloc ()
:I:
а
Что это ,а фуНКЦIlII?
Ответ.
·21
~
:I:
Ф
:т
Q
~
u
S
Модуль 12. ИСI<лючения, шаблоны и I<ое-что еще
608
tinclude <new>
using narnespace std;
int rnain ()
{
int
*р;
try
р
new int (87); // Инициanизируек П~.
catch (bad_alloc ха) {
cout « "Не удалось выделить память.\п";
return 1;
cout «
cout «
"По
адресу
"хранится
delete
р;
return
О:
" «
р
«
значение"
.
«
чиcnои
87.
".,
*р
«
"\п";
Выделение памяти для массивов
с помощью оператора леw можно выделять память и для массивов. Вот как
выглядит общий формат операции выделения памяти для одномерного массива:
переменная-указатель
=
new
тип_массива
[размер];
Здесь элемент размер задает количество элементов' в массиве.
Чтобы освободить цамять, выделенную для динамически созданного массива,
используйте такой формат оператора
delete []
delete:
переменная-указатель;
Здесь квадратные скобки означают, что динамически созданный массив удаляет­
ся, а вся область паМЯТlf, выделенная для него, автоматически освобождается.
Например, при выполнении следующей программы выделяется память для
10-элементного целочисленного массива.
//
Выделение
памяти для массива.
#include <iostream>
С++: PYI(OBOACTBO д,лfl начинающих
609
#include <new>
using namespace std;
int main ()
int
*р,
i;
try
р
new int [10]; //
//
catch (bad_alloc ха)
cout « "Не удалось
return 1;
Вwдen_eм П~ дn_ 10-эnеме.,.о~о
t\еJlОЧИc.n.ано~ каССИ8а.
выделить
память.\п";
for(i=O; i<10i i++ )
p[i] = i;
for(i=O; i<10i i++)
cout « p[i] « " ";
delete [)
return
р;
//
ОсаоБQ8Даек ПaкRТЪ,
заника~ каССИ80К.
О;
Обратите внимание на инструкцию
delete.
как упоминалось выше, при
освобождении памяти, занимаемой массивом, с помощью оператора
обходимо использовать квадратные скобки
( [ J).
delete не­
как будет по казан о в следую­
щем разделе, это особенно важно при выделении памяти для массивов объектов.
При динамическом выделении памяти для массива важно помнить, что его
нельзя одновременно и инициализировать.
Выделение памяти для объектов
Используя оператор
оператор
new, можно DbJДелять память и для объектов. При этом
new возвращает указатель на созданный объект. Объекты, созданные
дина~fИчески, действуют подобно любым другим объектам. При создании объ­
екта Оblзывается его конструктор, а при разрушении
-
его деструктор.
610 МОДУЛЬ 12. ИсключениSl, шаблоны и J<ое-что еще .
.................................................................................................................................
Рассмотрим программу создания класс,з
Rectangle, в котором инкапсулиру­
ются ширина и пысота лрямоугольника. В ФУI:IКЦИИ main () объект типа Rectangle создастся динамически. Разрушение объекта происходит по завершении
программы.
1/
Выделение
памяти для
объекта.
iincludc <iostream>
4tinclude <new>
using namespace stdi
class Rectangle
int width;
int heighti
public:
Rectangle (int w, int h) (
width = W;
height = h;
cout « "Создание прямоугольника
" и высотой" « height «
шириной
" «
width «
".\п";
~Rectangle()
cout «
"
"Разрушение прямоугольника шириной
и высотой"
«
height «
" «
width «
".\п";
iлt
area () {
return width * height;
} ;
int main ()
Rectangle
*р;
try (
р
= new Rectangle(10, 8); //
//
//
~enRеи Пам8~. ~ об~к~а
~ипа
Rectanqle.
Э~о вызо.е~
конструктор xnасса
Rectanqle.
С++: PYI(OBOACTBO Дl\9. начинающих
catch (bad_alloc ха)
cout « "Не удалось
return 1;
выделить
611
память.\п";
ф
3"
ф
...:r
о
cout «
"ПЛощадь
прямоугольника равна"
«
«
сь
p->area()
О
~
:s:
".\п";
:о
:r
о
cout «
~
"\п";
о
3
delete
return
р;
//
//
Освобождение пами~и,
Ii
занимаемой объек~ом.
Это вызоае~ дeCТPYK~OP xnасса
:s:
ffi
Rectanqle.
§
О;
(J
s:
Результаты выполнения этой программЪ1 таковы.
Создание
Площадь
прямоугольника
шириной
прямоугольника равна
Разрушение
10
и
ВЫСОТОЙ
8.
80.
прямоугольника шириной
10
И ВЫСОТОЙ
8.
Обратите внимание на то, что аргумснты ДЛЯ конструктора объекта задаются
после имени ·гипа. Кроме того, 11ОСI<ОЛЬКУ переменная р содержит указатель на
объеJ<Т, то при вызове функции
area ()
используется оператор "стрелка", а не
оператор "точка".
Мvжно также динамически создапать целый массив объектов, но с одной "ого­
воркой". Поскольку массив объектов, создавасмый с помощью оператора
new,
не
может иметь I1l1ициализатора, то вы должны быть уверены в том, что если класс
определяет конструкторы, то один ИЗ них должен быть без параметров. В против110М случае С++-компилятор при попытке выделить па.",ять для этого массива не
найдет соответствующий конструктор и не скомпилирует программу.
Новая версия предыдущей ПРОIl'аммы отличается от старой добавлеllием KOIIструктора, не имеющего параметрон, что позволит динамически создать массив
объектов типа
Rectangle. Кроме того, новое определение класса Rectangle
set (), которая устанавливает размеры прямоугольникз.
содержит функцию
//
Выделение памяти для массива
#include <iostream>
#include <new>
using namespace std;
объектов.
Модуль 12. Исключения, шаблоны и I<ое-что еще
612
class Rectangle
int width:
int heighti
public:
Rectangle ()
// Добавnяем конструктор беs паракетров.
width = height = О:
cout « "Создание прямоугольника ШИРИНОЙ " « width «
" и ВЫСОТОЙ" « height « ".\п";
Rectangle(int w, int h)" (
width = w;
height = h;
cout « "Создание прямоугольника
" и ВЫСОТОЙ" « height «
ШИРИНОЙ
"
int area () {
return width
*
( //
heighti
};
int main ()
Rectangle
*р;
try
р
new Rectangle [3]:
width «
".\п":
-Rectangle ()
cout « "Разрушение прямоугольника ШИРИНОЙ
" и ВЫСОТОЙ" « height « ".\п";
void set(int w, int h)
width = w:
height = h;
«
"
добааn.ем ф~
«
width «
set().
С++: PYI<OBOACтeO для начинающих
catch (bad_alloc ха)
cout « "Не удалось
return 1;
выделить
613
-
память.\л";
~
(1)
о
!т
cout «
сь
"\п";
о
:.о:
:s;
11
р[О).sеt(З,4);
:I:
·0
:<
:10
:0
.set (10, 8);
(2) . set (5, б);
р[l)
р
:3
:ri
::s;
for(int i=O; i < 3; i++)
cout « "Площадь равна" «
:х
:(1)
p[i] .area() «
\ ()~
endl;
:
cout «
:S
"\п";
delete [)
rеturл
р;
//
Здесь .~Bывae~c_ дec~yx~op дn_ xa.дoro
//
ocnex~a иаСCID8.
О;
Вот как выглядят результаты выполнения этой программы.
Создание
прямоугольника
шириной
О
и
высотой
О.
Создание
прямоугольника
шириной
О
и высотой
О.
Создание
прямоугольника шириной
О
и высотой
О.
Площадь
равна
12
80
Площадь
равна
ЗА
ПЛощадь
равна
Разрушение прямоугольника шириной
Разрушение
прямоугольника шириной
Разрушение
прямоугольника шириной
5 и высотой б.
10 и высотой 8.
З и высотой 4.
Поскольку указатель р освобождается с использованием оператора
[ ] , то, как
delete
подтверждают результаты выполнения этой программы, для каждого
объекта в массиве выполняется деструктор. Обратите также внимание на то, что,
поскольку указатель р индексируется подобно имени массива, для доступа к чле­
нам класса
Rectangle
используется
OllepaTOp "точка".
МОДУЛЬ 12. ИСI<лючения, шаблоны и I<ое-что еще
614
~просы ДЛЯ текущего КОНТРОЛЯ
_=_ _ _ _ _ _ __
Какой оператор позволяет вьщелить память для переменных? Какой опе­
1.
ратор освобождает выделенную память?
2. Что происходит в случае, ссли не может быть выполнен запрос на выделе­
ние памяти?
Можно ли инициализировать память при ее динамическом распреде­
3.
ЛNШI1?·
_
:ан
ВАЖНIi
Itlt Dростраыаво I4МАИ
Пространства имен были кратко описаны в модуле
1.
Здесь мы рассмотрим
,а более детально. ОНИ ПОЗDОЛЯЮТ локализовать имена идентификаторов, чтобы
избежать конфликтных ситуаций с н·ими. В С++-среде программирования ис­
пользуется огромное количество имен переменных, функций и имен классов. До
введения Ilространств ИМСJ-IlIсе эти имена конкурировали за память в глобальном
I1ространстве имен, что и было причиной DОЗlfИкновения многих конфликтов.
Например, если бы в вашеi{ программе была определена функция
toupper {J,
она могла бы (в зависимости от списка параметров) переопределить стандарт­
ную библиотечную функцию
toupper () ,поскольку оба имени должны были бы
храниться R глобальном пространстве имен. Конфликты с именами возникали
также при использовании в одной программе нескольких библиотек СТОрОJ::iНИХ
ПРОИЗ80дителеЙ. В этом случае имя, определенное в ОДJЮЙ библиотеке, конф.1RК­
товало с таким же именем из другой библиотеки. Подобная ситуация ос06енко
неприятна IlрИ использоваllИИ одноименных классов. Например, если в вашей
программе определен класс
Videot-1ode.
и в библиотеке, используемой вашей
I1рогrаммоЙ. определен класс с таким же имене~, конфликта не избежать.
Для решения ОШlсанной проблемы было создано ключевое слово
namespace.
IIОСКОЛhКУ 0110 локализует видимость объявленных в нем имен, это значит, что
ПРОСJрансТlЮ имен позволяет использовать одно и то же имя в различных IЮН-
1. Выделить память для переменнъrх можно с помощью оператора·nеw. а освободить
эту память - с помощью оператора delete.
2. Ec..'/I! защ.юс на выделение паМЯТII не может быть выполнен, генерируется исключе­
ние ППl<l
З.
bad alloc.
да, ПРJJ ДI!Шl.мическом распределении память :l.IОЖНО IШIШИализировать.
С++: руководство дl\9i начинающих
615
текстах, не вызывая при этом КОlJфликта имен. Возможно, больше всего от но­
вовведения "повезло" С++-бнблиотеке стандартных функций. До появления
ключевого слова
namespace вся С++-библиотека была определена в гл06алыюм
ПРОС1"ранстве имен (которое было, конечно же, единствеIlНЫМ). С наступлением
паmеsрасе-"эры" С++-библиотека определяется D собственном простраlJСТВС
:ф
:~
:Ф
:
0
• >:7
:Ф
которое значительно понизило вероятность В031111101O­
:0
вения конфликтов имен. В своей Ilрограмме програММJlСТ оолен создавать соб­
:s:
имен, именуемом
std,
ственные пространства имен, чтобы локализовать видимость тех Ifмен, которые,
по его мнению, могут стать причиной конфликта. Это особеНIIО важно, если вы
:~
J5
1:
:~
:10
:0
:3
занимаетесь созданием библиотек классов или функций.
ri
:s
1:
Ф
Понятие пространства имен
КlIючевое слово
namespace
позволяет программисту "отделиться" от гло­
бального пространства имен llYTeM создания некоторой декларативной области.
По сути, пространство имен определяет область видимости. ОбщиН формат за­
дания пространства имен таков.
namespace пате (
// объявления
Все, что определено о границах инструкции
namespace, находится в области ви­
димости этого пространства имен.
В следующей программе приоеден пример использования паmеsрасе-ни­
струкции. Она локализует имена, используемые для реализации простого класса
счета в обратном направлении. В созданном здесь пространстве имен определя­
ется класс counter, )юторый реализует счетчик, и nepeMCHJlble upperb~und и
lowerbound, содержащие значеllИЯ верхней и нижней границ, примеияемых для
всех счетчиков.
1/
Демонстрация
использования
namespace CounterNameSpace
int upperbound;
int lowerbound;
class counter
int c9unt;
public:
counter(int
п)
пространства
имен.
/I
Соэдание простраиС'1'llа
1/
имен
CounterNameSpace.
§
U
:S
Модуль
616
12. ИСl<лючени~, шаблоны и I(ое-чтоеще
if(n <= upperbound) count
else count = upperboundi
п;
void reset(int п)
if(n <= upperbound) count
п;
int run ()
if(count > lowerbound) return count--i
else return lowerbound;
} i
Здесь переменные
upperbound
и
lowerbound, а также
класс
ся частью области видимости, определенной пространством
counter ЯВЛЯЮТ­
имен CounterNam-
eSpace.
В любом пространстве имен к идентификаторам, которые в нем объявлены,
можно обращаться напрямую, т.е. без указания этого пространства имен. Напри­
мер, в функции
run () , которая
находится в пространстве имен Соип terNamE-S-
расе, можно напрямую обращаться к переменной
lowerbound:
if(count > lowerbound) return count--i
Но поскольку инструкция паmезрасе определяет область видимости, то при
обращении к объектам, объявленным в пространстве имен, извне этого простран­
ства необходимо использовать оператор разрешения области видимости. Напри­
мер, чтобы присвоить значение
10
переменной
upperbound из кода, который
CounterNameSpa':e,
является внешним по отношению к пространству имен
нужно использовать такую инструкцию.
CounterNameSpace::upperbound = 10;
Чтобы объявить объект типа
NameSpace, используйте
counter
вне пространства имен
Counter-
инструкцию, подобную следующей.
CounterNameSpace::counter
оЬ;
В общем случае, чтобы получить доступ к некоторому ~лену пространства
имен извне этого пространства, необходимо нредварить имя этого члена име­
нем пространства и отделить имена оператором разрешения области видимо­
сти (или оператора разрешения контекста, который также называется опера­
тором яв1l0го задания).
С++: PYI<OBOACTBO ДМ! начинающих
617
Рассмотрим программу, в которой демонстрируется использование простран­
ства имен
//
//
CounterNamespace.
Демонстрация использования
имен
пространства
CounterNameSpace.
#include <iostream>
using namespace std;
namespace CounterNameSpace
int upperbound;
int lowerbound:
class counter
int count;
public:
counter (int п) (
if(n <= upperbound) count
else count = upperbound;
п;
void reset(int п)
if(n <= upperbound) count
п;
int run () (
if(count > lowerboupd) return count--;
else return lowerbound;
};
int main ()
Co~nterNameSpace::upperbound
100; / /
CounterNameSpace::lowerbound
О;
CounterNameSpace::counter obl(lO);
Явное обращевие IC
/ / ч.nенах прос~аасomaа
/ / имек
// СоuntеrNашеSрасе.
/ / Обра'1'ИТ8 внима.вие на
// испоnъэоваиие
618
МОДУЛЬ
12. ИСI<лючени~, шаблоны и I<ое-что еще
// опера'1'ора
// рaspешеRИR ХОВ'1'еХС'1'&.
int i;
do (
i = оЫ. run () ;
cout « i « " ";
while(i > CounterNameSpace::lowerbound);
cout « endl;
CounterNameSpace::counter
оЬ2(20);
do (
i = оЬ2. run () ;
cout « i « " ";
while(i > CounterNameSpace::lowerbound);
cout « endl;
ob2.reset(lOO);
CounterNameSpace::lowerbound
90;
do (
i = ob2.run();
cout « i « " ";
while(i > CounterNarneSpace::low~rbound);
return
О;
Обратите внимание на то, что при объявлении объекта класса counte)~ и
обращении ,к перемеННblМ
странства имен
ter
upperbound и lO"'lerbound используется имя про­
CounterNameSpace. Но после объявления объекта типа coun-
уже нет необходимости в полной квалификации его самого или его членов.
ПОСКОЛl,ку пространство имен однозначно определено, функцию run () объек­
та оЫ можно вызывать напрямую, Т.е. без указания (в качестве лрефикса) про­
странства имен (оЫ.
run (».
Программа может содержать несколько объявлений лространств имен с оди­
наКОВblМИ именами. Это означает, что npocтpallcTBO имен можно разбить на не­
сколько файлов или на несколько частей в рамках одного файла. Вот npимер.
namespace NS {
int i;
С++: руководство ANi) начинающих
619
ф
//
::f
ф
о
5=
namespace NS (
int j;
сь
о
~
:s:
:iS
Здесь пространство имен NS разделено на две части. Однако содержимое каждой
части относится к ОДllаму и тому же пространству имен
1:
2
\о
а
NS.
OCTMbllblX обла­
3
стей видимости. Это означает, что. нельзя объявлять пространства имен, которые
:s:
локализованы, например, в рамках функции. При этом одно пространство. ю.IСН
Ф
J
Любое пространство имен должно быТI> объявлено вне всех
может быть вложено в другое.
ti
1:
Q
<
:.:
U
S
ИНСТРУКЦИЯ
usinq
Если программа включает множества ссылок на члены некатарого пространства
имен, то нетрудно представить, что ие06ХОДИМОСTh указываТ1. имя ,утога простран­
ства имен при каждом к нему обращеlШИ, ачень
Cl<apa угомит вас.
Эту Jlроблему па­
зволяет решить инструкция us i ng, которая применяется в двух форматах.
using namespace ИМЯ;
using
паmе::член;
В первой форме элемент ИМЯ задаст название пространства имен, к катора­
му вы хотите получить доступ. Все члены, апределенные ВНУТР" задаНJlОГО про­
странства имен, попадают п "поле виДимасти", т.е. станавятся частью текущего
прастранства имен, и их мажно затем использовать без дополнитеЛЫЮl1 кuа..rш­
фикации (утачнения пространства имен). Во второй форме делается "ПИДJlМЫМ"
только указанный член прастранства имен. Например, если I1редпаложить, что.
пространство имеl1 CounterNameSpace определено (как показзно выше), сле­
дующие инструкции using и присваив~ния будут вполне заl(OJН1ЬШИ.
using CounterNameSpace: :1owerbound;
1owerbound = 10;
// Видимым стал только
// член 1owerbound.
// Все в порядке, поскольку ч~ен
// 1owerbound находится в об~асти
//
видимости.
using namespace CounterNameSpace;
// Все члены видимы.
620
Модуль 12. ИсключениSl, шаблоны и I<ое-что еще
upperbound = 100; 11
//
Все в
порядке,
поскольку все члены
видимы.
Использование ИНСТРУКЦИИ
using демонстрируется
в следующей программе
(которая представляет собой новый вариант счетчика из предыдущего раздела).
//
Демонстрация
использования инструкции
using.
#include <iostream>
using паmезрасе std;
CounterNameSpace
int upperbound;
int lowerbound;
паmезрасе
class counter
int count;
public:
counter (int п) (
if(n <= upperbound) count
else count = upperbound;
void reset(int п) (
if(n <= upperbound) count
п;
п;
int run () {
if(count> lowerbound) return count--;
else return lowerbound;
};
int main ()
// Делается "видимым" только член upperbound из
// пространства имен CounterNameSpace.
using CounterNameSpace::upperbound; // Испоnьзоваиие
//
KOHкpe~HO~O члена
С++: РУI<ОВОДСТВО ДЛ'i! начинающих
621
/ / прос~ааСТJIа икеа.
// Теперь для установки значения переменной upperbound
// не нужно указывать пространство имен.
upperbound = 100;
~
ф
...
о
;г
// Но при обращении к переменной lowerbound и
// объектам по-прежнему необходимо указывать
// пространство имен.
CounterNameSpace::lowerbound = О;
CounterNameSpace::counter оЫ(10):
int i;
другим
ф
о
~
s
2i
:J:
О
~
О
3
ti
s
:J:
Ф
do
i = оЫ. run ( ) :
cout « i « " ":
while(i > CounterNameSpace::lowerbound):
cout « endl:
//
//
Теперъ используем все прос~авСТJIо
имев
usi~g
CounterNameSpace.
CounterNarneSpace:
паrnезрасе
counter
оЬ2(20):
do
i = ob2.run():
cout « i « " ";
while(i> lowerbound)i
cout « endl:
ob2.reset(100);
lowerbound = 90;
do (
i = оЬ2. run () :
cout « i « " ";
while(i > lowerbound):
return
О:
§
U
s:
622
Модуль
12. ИСl<лючения, шаблоны и I<ое-что еще
Эта программа иллюстр"рует еще один нажный момент. Использование одно­
го пространства имен не переопределяет другое. Если некоторое пространство
имеJl становится "видимым", это значит, что оно просто добавляет свои имеI-lа к
именам других, уже действующих пространств. Поэтому
мык глобальному пространству имен добавились и
std,
'( концу этой програм­
CounterNameSpace.
и
Неименованные пространства имен
Существует lleWle1l0aa1l11Oe пространство I1мен специального типа, которое
позволяет создавать идентификаторы, уникальные для данного файла. Общий
формат его объявления выглядит так.
namespace {
// объявления
НеименопаШ-lые пространства имен позволяют устанавливать уникальные
идентификаторы, которые известны ТОЛl)ко в области видимости одного Фl1i,lла.
Другими словами. члены файла, КОТОРЫЙ содержит неименованное пространство
имен, можно использовать напрямую, без уточняющего префикса. Но вне файла
эти идснтифш(зторы неизвестны.
Как УIIОМИllалОСЬ выше в этой книге, использование модификатора типа
sta.tic также позволяет ограничить область ыщимости глобального простр,u-r­
пва имен ФаrIЛОМ, в котором он объявлен. Несмотря на то что ИСПОЛ1,зование
глобальных stаtiс-о6ъявлений исе еще разрешено в С++, дЛЯ локализашlИ
идентификатора 8 рамках одного файла лучше использовать lIеименованное
пространство имен.
Пространство имен
s td
Стандарт С + + определяет всю свою библиотеку в собственном пространстве
имен, именуемом
std.
Именно поэтому большинство программ в этой книге
включает следующую инструкцию:
using namespace std;
При выпuлнении данной инструкции IlpoCTpaHcTBo имен
std
становится те­
кущим, что открывает прямой доступ к именам функций и классов, определен­
ных в этой библиотеке, Т.е. прн обращении к HIIM отпадает необходимость в ис­
поJltJзоuании префикса
std: :.
КОН~'lI-IО, lIрИ желаНИII можно явным образом квалифицировать каждое би­
UЛНl)теЧliое имя rIРСфIIКСО~1 ~ td: :. Например, :\tОЖI\О было бы явно квалифllllИ­
ровать объект cout следующим образом.
С++: РУ'<ОВОДСТВО ДМ! начинающих
std: :cout «
std." ;
"Явно квалифицируем объект
cout
префиксом
ограни­
~
n глобальноf' пространсТIЮ
о
Если ваша программа использует стандартную библиотеку только
ченных пределах, то, возможно, ее и не стоит вносить
623
J)
имен. Но если ваша программа содержит сотни ссылок на библиотечные И~{('llа,
то гораздо проще сделать пространство имеll
std
текущим, чем полностью ква­
ф
....
;т
ф
о
~
:s:
лифицировать каждое имя в отдельности.
Ji
1:
~просы ДЛЯ текущего I<ОНТРОЛЯ
О
<
\()
о
_ _ _ _ _ _ _ __
создается?
Являются ли пространства имен аддитивными?
2.
'$
-
It*' Статические WAeNbIJЦ\occa
ВАЖНОI
static
в модуле
7,
Г;1.е 01lИСLll:laJlUСI,
его использование для модификации объявлениii локальных 11 глобалыlхх I1('ре­
менных. Помимо этого, ключевое слово
sta tic
можно применять Ii К члеJJ;НI
lUIacca: как переменным, так и функциям.
Статические члены данных класса
Объявляя член данных (псременную) класса статическим. мы сообщаем
компилятору, что независимо от того, сколько объектов этого l<Ласса будет ("0::\дано, существует только одна копия данного stаtiс-членз. Другими словаМIl,
sta tiс-член разделяется
1.
Пространство имен
-
всеми объектами класса. Все статические перемеtllfые
это декларативная область, которая определяет диаIla:lОН IIН­
ДI1МОСТИ объявлеНIIЫХ в ней имен. Пространство им('н создается с помощыо K,l)()'1L~
вого слова
2.
namespace.
да, пространства имен яnляются аДДlmiВНЫМ1I. Это значит, что имена n.'llЮГО про­
. странства можно добавить к имена:'1 ДРУПJХ,
З.
уже деЙСТВУЮЩIIХ npOCTpallCTn.
Инструкция usiлg позволяет сде,13ТЬ "видимыми" члены 3д_l:1IН(()Р,) простран·
ства имен.
ffi
;т
Q
<
~
u
S
з. Каково назначение инструкции using?·
Вы познакомились с I<лючевым CJlOBOM
r;j
:s:
Что такое ПРОСТРЭJlСТВО имен? С помощью Kaкol'o ключевого слова оно
1.
3
624
Модуль 12. ИСI<лючениS1, шаблоны и I<ое-что еще
\
при первом создании объекта инициализируются нулевыми значениями, если не
задано других значений инициализации.
При объявлении статического члена данных в классе программист его не
определяет. Вместо этого он должен обеспечить его глобальное оnределеuШ! вне
такого l<ласса. Это реализуется путем повторного объявления этой статической
переменной с помощью оператора разрешения области видимости, который 110зволяет идентифицировать, к какому классу она принадлежит. И только ТОj'да
для этой статической переменной будет выделена память.
Рассмотрим пример использования stаtiс-члена класса.
//
Использование
статических переменных реализации.
#include <iostream>
using namespace std;
class ShareVar {
static int num; //
//
//
~ ОБЪ«ВЛRем stаtiс-чnев д&квwx.
Ero
будут совместно использовать все
эхземпnRpЫ xnасс&
ShareVar.
public:
void setnum (int i)
пит = i;
};
void shownum() ( cout « num « " ";
};
int ShareVar::num; //
~ ОпредenReN statiс-чnеи давиwx
num.
int main ()
ShareVar
а,
Ь;
a.shownum(); //
b.shownum(); //
a.setnum(10); //
a.shownum(); //
b.shownum(); //
return
О;
Выводится
значение
О.
Выводится
значение
О.
Устанавливаем stаtiс-член пит равным
Выводится
значение
Также выводится
10.
значение
10.
10.
С++: руководство для начинающих 625
................................................................................................................................
Результаты выполнения этой программы таковы.
О
О
10 10
в этой программе обратите внимание на то, Ч'го статический uеЛО'lисленный
член пит и объявлен в классе
ShareVar,
и определен в Kat/ecTBe глобальной пе­
ременной. Как было заявлено выше, необходимость такого двойного объявления
вызвана тем, что при объявлении члена
num в классе ShareVar память для него
не выделяется. С++ инициализирует переменную пит значением О, поскольку
никакой другой инициализации в ПРOl-рамме нет. Поэтому в результате двух пер­
вых вызовов функции
shownum ()
для объектов а и Ь отображается значение О.
Затем для объекта а член пит устанавливается равным
объектов а и Ь с помощью функции
shownum ()
10,
после чего для обоих
выводится на экран значение
члена пит. Но так как существует только одна копия переменной пит, разделяе­
мая объектами а и Ь, при вызове функции
пьшедено одно н то же значение
Если
stаtiс-переменная
shownum ()
для обоих объектов будет
10.
является
открытой
(т.е.
риbliс-переменной),
к ней можно обращаться напрямую через имя ее класса, без ССЬUlки на какой­
либо конкретный объект. (Безусловно, обращаться можно также 11 через имя объ­
екта.) Рассмотрим пример.
11
Обращение
к
stаtiс-переменной через
имя ее
класса.
#include <iostream>
using namespace std;
class Test {
public:
static int пит;
void shownum() { cout «
пит
«
endli }
};
int Test::numi 11
Определяем
stаtiс-переменную пит.
int main ()
Test
11
а,
Ь;
Устанавливаем
Test::num
=
переменную пит,
100; 11 +-
11
ИСПОЛЬЗУЯ
имя
класса.
ОбращаеИСR х члену nuш через ИИR
его класса
Test.
Модуль 12. ИСl<лючени~, шаблоны и I<ое-что еще
626
a.shownum(): //
b.shownum(): //
//
ВЫВОДИТСЯ
значение
ВЫВОДИТСЯ
значение
Устанавливаем переменную пит,
а.пит =
200: // +-
return
ИСПОЛЬЗУЯ ИМЯ объекта.
Обращаемс. Х члену
num
через ИИR
об'bl!ХТа. а.
//
a.shownum(); //
b.shownurn(}; //
100.
100.
ВЫВОДИТСЯ
значение
ВЫВОДИТСЯ
значение
200.
200.
О:
Обратите внимание иа то, как устанавливается значение переменной пит с
использованием имени ее класса:
Test::num = 100:
Вполне допустимо устанавливать значение открытой stаtiс-переменной и
через объект:
а.пит
=
200;
Любой из этих способов установки псреМСНllо!1 совершенно легален.
Статические функции-члены класса
Можно также объявить статической и фУНlщию-член, но это
-
нераспростра­
ненная практика. К статической ФУl1Jщии-члеllУ могут получить доступ только
другие stаtiс-члены этого класса. (Конечно же, статическая функция-член мо­
жет получать доступ к нестатическим глобальным данным и функциям.) Стати-.
ческая функция-член не имеет указателя
this.
Создание виртуальных статиче­
ских функций-членов не разрешено. Кроме того, их нельзя оБЪЯ-В.1\ять с модифи­
каторами
const
или
volatile.
Статическую функцию-член можно вызвать для объекта ее класса или иезави­
симо от какого бы то ии было объекта, а для обращения к ней достаточно исполь­
З0вать имя класса и оператор разрешения области ВНДJ1МUСТИ. Рассмотрим, наJJРИ­
мер, следующую программу. В ней определяется
s t а ti с-переменная соип t, кото­
рая служит для хранения количества существующих в дашп"й момент объектов.
//
Демонстрация использования
#include <iostream>
using namespace std:
статических функций-членов.
С++: PYI<OBOACTBO ДЛЯ начинающих
627
class Test (
static int count;
public':
i
~
I
Test ()
count++;
cout «
Ф
О
~
:s:
"Создание
count «
o~ъeKTa
::а
" «
:I:
о
~
endli
о
3
r:i.
:s:
:I:
-Test ()
cout «
ф
"Разрушение
count «
объекта
~
" «
endli
:s:L'
count--i
static int numObjects()
{ return counti } //
/ /
C~a~8CX"
фУJUCЦИJl-чnев.
};
int Test::count;
int main ()
Test а, Ь,
cout «
"В
с;
данный
момент
существует
11
«
Test : ': numObj ects () «
" объекта. \п \п" ;
Test
*р
cout «
=
new Test();
"Поtле создания объекта класса
"насчитывается
" «
T8Bt::numObjects()
"
delete
р;
Test " «
Объекта.\п\п";
«
//
ИСП~8У~CR ИИR киасса.
628
МОДУЛЬ
cout «
12.
"После
r
ИСI<лючеНИ>l, шаблоны и кое-что еще
удаления
"насчитывается
объекта
a.numObjects() «
"
return
" «
" «
//
Используете. ИИR об~екта и
//
оператор
"точка".
Объекта.\п\п";
О;
При Dыполнешш эта программа генерирует такие результаты.
Создание
объекта
Создание
объекта
Создание
объекта
В
момент
данный
Создание
После
Разрушение
После
существует
объекта
создания
1
2
3
объекта.
4
объекта
объекта
удаления
3
объекта
Разрушение
объекта
Разрушение
объекта
Test
насчитывается
4
объекта.
4
объекта
Разрушение
класса
насчит~вается
3
объекта.
3
2
1
В Э'Рой программе обратите внимание на то, как вызывается статическая фУНК­
ция
numObj ects () . в
первых двух ее вызовах испuльзуется имя класса, членом
которого она является.
Test: :numObjects()
В третьем вызuве используется обычный СИlП<lКСИС. т.е. имя объекта и опера­
тор "точка".
а.
numObjects ()
ВАЖНОI
Кll •ЛИИQмическая.идеwт.ифико,
~WI
•
•
типов (RПI)
с динамической идентификацией типов ВЫ. IЮЗМОЖНО, незнакомы, посколы<у
это средство отсутствует в тшшх tlеПОЛИМОРфНlrIХ языка..х. как С или традишroн-
С++: руководство д!\)! начинающих
ный
629
BASIC. В неполиморфных языках попросту нет необходимости в получении
информации о типе во время выполнения программы' так как тип каждого объекта
известен во время компиляции (т.е. еще при написании программы). Но в таких
полиморфных языках, как С++, возможны ситуации, в которых тип объекта не­
известен в период компиляции, поскольку точная природа этого объекта не будет
определена до тех пор, пока программа не начнет ВЫПОЛНЯТЬСЯ. Как вы знаете, С + +
реализует полиморфизм посредством использования иерархии классов, вирту~ь­
ных функций И указателей на базовые классы. Указатель на базовый класс можно
использоuать Д1IЯ ссылки на объекты этого базового класса или на 06ьeкmы любых
ЮlаССО6, выведенных из него. Следовательно, не всегда заранее известно, на объект
какого типа будет ССЬUIаться указатель Ш1 базовый класс в ПРОИЗВUЛЬНhlй момент
времени. Это выяснится только при выполнении программы
-
при использовании
одного из средств динамической идентификации типов.
Чтобы получить тип объекта во время выполнения программы, используй­
те оператор
typeid.
<typeinfo>. Самый
peid таков.
Для этого необходимо включить в программу заголовок
распространенный формат использования оператора
ty-
typeid (object)
Здесь элемент object означает объект, тип которого нужно получить. Можно
запрашивать не только встроенный тип, но и тип класса, созданного программи­
стом. Оператор
t
уре id возвращает ссылку на объект типа
t
уре _ i
n f о,
который
описывает тип объекта оЬ j е с t.
В классе
type_info
определены следующие рubliс-члены.
bool operator==(co~st type_info &оЬ);
bool operator!=(const type_info &оЬ);
bool before(const type_info &оЬ);
const char *пате();
Перегруженные операторы
ция
before ()
возвращает
"==" и"! =" служат для сравнения типов. Функ­
значение true, если вызывающий объект в порядке
сопоставления стоит перед объектом (элементом оЬ), ИСПОЛI)зуемым в качестве
пара метра. (Эта функция предназначена в основном для внутреннего примене­
ния. Возвращаемый ею результат не имеет ничего общего с наследованием или
иерархией классов.) Функция пате
()
возвращает указатель на имя типа.
Рассмотрим простой пример использования оператора
//
Пример
использования
#include <iostrearn>
#include <typeinfo>
оператора
typeid.
typeid.
Модуль 12. Исключения, шаблоны и кое-что еще
630
using narnespace std:
class MyClass
//
}:
int rnain ()
int i, j;
float f;
MyClass оЬ;
//
Испоnьзоваиие
/1
об'loеJC~а.
cout
cout
cout
cout
cout
cout
«
«
«
«
«
«
"Тип
опера~ора
typeid
переменной
i:
" «
typeid(i) .narne();
переменной
f: " «
typeid(f) .narne ();
переменной
оЬ:
endl:
"Тип
endl;
"Тип
" «
typeid(ob) .narne();
"\n\n":
if(typeid(i) == typeid(j»
cout « "Типы переменных i
i f (typeid (i)
cout «
return
дnя поnучевия ~ипа
и
j
одинаКОБЫ.\П":
и
f
неодинаковы.\п";
! = typeid (f) )
"Типы переменных
i
О;
При выполнении этой программы получены такие результаты.
Тип
переменной
i: int
Тип
переменной
f: float
Тип
переменной оЬ:
Типы
переменных
Типы переменных
Если оператор
i
i
class MyClass
и
и
typeid
j
f
одинаковы.
неодинаковы.
при меняется к указателю на полиморфный баЗО8ЫЙ
класс (вспомните: полиморфный класс
-
это класс, который содержит хотя бы
С++: PYI<OBOACтe0 ДМI начинающих
631
одну виртуальную функцию), он автоматически возвращает тип реального объ­
екта, адресуемого этим указателем: будь то объект базового класса или объект
класса, выведенного из ба.-зового. Сл.едовательно, оператор
typeid
можно ис­
пользовать для динамического определения типа объекта, адресуемого указате­
лем на базовый класс. Применеllие этой возможности демонстрируется в следу­
ющей программе.
1/
11
Пример
применения
полиморфных
оператора
typeid
к иерархии
классов.
#include <iostream>
#include <typeinfo>
using паmезрасе std;
class Base
virtual void f()
1/ ...
(); 1/
делаем класс Вазе полиморфным
);
class Derivedl: public Base {
11
);
class Derived2: public
11
Вазе
\
{
\
};
int main ()
Вазе
*р,
Derivedl
Derived2
р
=
ЬазеоЬ;
оЫ;
оЬ2;
&ЬаsеоЬ;
cout «
cout «
"Переменная
typeid(*p)
р
указывает
.пате()
«
на
&obli
cout « "Переменная р указывает на
cout « typeid(*p) .пате(} « endl;
р
объект
типа
";
объект
типа
";
endl;
=
632
р
Модуль 12. ИСJ<лючениSl, шаблоны и I<ое-что еще
=
&оЬ2;
cout «
cout «
"Переменная
typeid(*p)
return
р укаЗbIБает
.паmе()
«
на
объект
типа
";
endl;
О;
Вот как выглядят результаты выполнения этой программы:
Переменная
р
указывает
на
объект
типа
Переменная
р
указывает
на
объект
типа
Переменная
р
указывает
на
объект
типа
Если оператор
Base
Derivedl
Derived2
typeid применяется к указателю на базовый
класс полиморф­
ного типа, тип реально адресуемого объекта, как подтверждают эти результаты,
будет определен во время выполнения ПРОI1>3ММЫ.
Во всех случаях применеllИЯ оператора
typei.d к указателю
на неПОЛИМОРФIlУЮ
иерархию классов будет получен указатель на базооый 111П, т.е. то, на что этот указа­
тель реально указывает, определить нельзя. В качестве эксперимента превратите в
комментарий виртуam)ную функцию
f () в классе Base и посмотрите на результат.
Вы УDидите, что тип каждого объекта после внесения в программу этого изменения
будет определен как
Base, поскольку именно этот тип имеет указатель р.
typeid обычно примеllяется к разЫМСlIованному указа­
указателю, к которому уже примеllен оператор "*"), для обработки
Поскольку оператор
телю (т.е. к
ситуации, когда этот разыменованный YIсазаТeJll) оказывается нулевым, создано
специальное исключение. В этом случае оператор
ние типа
typeid
генерирует исключе­
bad_typeid.
Ссылки на объекты иерархии полиморфных классов работают подобно ука­
зателям. Если оператор
typeid
применяется к ССЫЛl(е на полиморфный класс,
оп возвращает тип объекта. на который она реально ссылается, и это может быть
объект не базового, а производного типа. Описанное средство чаще всего исполь­
зуется при передаче объектов функциям по ссылке.
Существует еще один формат применения оператора
typeid, который в каче­
стве аргумента принимает имя типа. Этот формат выглядит так.
typeid (имя_типа)
Например, следующая инструкция совершенно допустима.
cout «
typeid(int)
.паmе();
С++: PYI<OBOACTBO ДМ! начинающих
Назначение этой версии оператора
info
typeid -
получить объект типа
633
type_
(который описывает заданный тип данных), чтобы СГО можно было затем
использовать в инструкции сравнения типов.
~
ф
~ПРОСЫ ДЛII текущего КОНТРОЛЯ
о
!т
ф
______
••____....
9 __
~
:s:
:в
t.
В чем состоит уникальность статических членов данных?
2.
Каково назначение оператора
typeid?
I
о
.!§
о
З. Объект каlШГО типа возвращает оператор typeid?*
3
ti
:s:
I
Ф
'3'
81°1 Q пераZОРhJl1Pl488дel:t и q ТI4ПОВ
t?1
в С++ определено ПЯТh операторов ПРИDедеl1ИЯ типов. Первый оператор (он
описан выше в этой книге), применяемый в обычном (традиционном) стиле, был
с ·самого начала встроен в С++. Остальныс четыре
cast, reinterpret_cast
и
static_cast)
(dynarnic_cast, const_
были добавлены в язык всего не­
сколько лет назад. Эти опсраторы служат дополнительными "рычагами управле­
ния" характером выполнения операций приведения типа. Рассмотрим каждый из
них в отдельности.
Оператор
dynamic_ саз t
Возможно, самым важным из новых операторов является оператор динами·
ческого приведения типов
dynamic_cast.
Во время выполнения программы он
проверяет обосноваНIIОСТЬ предлагаемой операции. Если в момент его вызова за­
данная операция оказывается недопустимой, приведение типов не реализуется.
Общий формат применения оператора
dynamic_cast
таков.
dynarnic_cast<type> (expr)
1.
Пространство имен
-
это декларапшная обласТь, которая определяет диапазон ви'
димости объявленных в I!ей имен. Ilространство имен создается с помощью ключе­
вого слова
2.
namespace.
Да. пространства имен ЯБЛЯЮТСЯ aддIillfВНЫМИ. Это значит, что имена одного про­
странства можно 110б;ШIlТЬ I( именам друrnх, уже действующих пространств.
з.
Инструкция
ства имен.
using
позволяет сделать "ВИДИМЫМИ" члены заданного простран­
~
U
s
МОДУЛЬ
634
Здесь элемент
12. ИСI<лючени~. шаблоны и I<ое-что еще
type
означает новый тип, который яuляется целью выполнения
этой операции, а элемент
Тип
type должен быть
expr -
выражение, при водимое к этому новому типу.
представлен указателем или ссылкой, а выражение
expr
должно приводиться К указателю или ссылке. Таким образом. оператор dynami с_
cast можно использовать для преобразования указателя одного типа в указа­
тель другого или ссылки одного типа в ссылку другого.
Этот оператор в основном используется для динамического выполнения опе­
раций приведения типа среди полиморфных типов. Налример, если даны пол.и­
морфные классы Б и О, причем
lUIacc О выведен из класса Б, то с помощью olledynamic_cast всегда можно преобразопать указатель 0* в указатель В*,
ратора
поскольку указатель на базовый класс всегда можно использовать для yka.1aI-ШЯ
на объект класса, выведенного из базового. Однако оператор dynamic_ cast мо­
жет преобразовать указатель Б* в указатель
0*
только в том случае, если адре­
суемым объектом действительно является объект класса
dynamic_cast
D. И, вообще, оператор
будет успешно выполнен только при условии. если разрешено
полиморфное приведение типов, Т.е. если указатель (или ссылка), приводимый
к новому типу, может указывать (или ссылаться) на объект этого нового типа
или объект, выведенный из него. В противном случае, Т.е. если заданную опера­
цию приведения типов выполнить нельзя, результат действия оператора
mic_cast
dyna-
оценивается как нулевой, если в этой операции участвуют указатели.
(Если же попытка выполнить эту операцию оказалась неудачной при участии в
ней ссылок, генерируется исключение типа
bad _ cast.)
Рассмотрим простой пример. Предположим. что класс Баsе
а класс
Баsе
Oerived выведен ИЗlUIасса
*Ьр,
-
полиморфный,
Баsе.
Ь_оЬ;
Oerived *dp, d_ob;
Ьр
&d_ob; // Указатель на базовый класс ссылается
// на объект класса Oerived.
dp
dynamic_cast<Oerived *> (Ьр); // Приведение к
// указателю на
// производный класс
// разрешено.
if(dp) cout « "Приведение типа прошло успешно!";
Здесь приведение указателя Ьр (на базовый класс) к указателю
дный
dp
(на npоизво­
lUIacc) выполняется успешно, поскольку Ьр действительно указывает на
Oerived. Поэтому при выполнении этого фрагмента кода будет
объект класса
выведено сообщение Приведение
типа
прошло успешно!. Но в следующем
С++: PYI<OBOACTBO для начинающих
635
фрагменте кода попытка совершить Оllерацию приведения типа будет неудачной,
поскольку Ьр в действительности указывает на объект класса Баsе, а приводить
указатель на базовый класс к типу указателя на производный неправомерно, если
адресуемый им объект не является на CQ.JНOA' деле объектом производного класса.
&Ь_оЬ;
Ьр
//
//
dp =
if(!dp) cout «
Указатель
на базовый класс ссылается
на объект класса
dynamic~cast<Derived
*>
"Приведение
Base.
//
(Ьр);
типа
ошибка!
выполнить
не
I
удалось";
Поскольку ПОПЫТl(а выполнить операцию приведения типа оказалась неудачной,
при выполнении этого фрагмента кода будет выведено сообщение Приведение
типа
выполнить
Оператор
не
удалось.
const_cast
Оператор
const_cast используется для явного переопределения модифи­
const и/или volatile. Новый тип должен совпадать с исходным, за
исключением его атрибутов const или volatile. Чаще всего оператор const_
cast используется для удаления признака постоянства (атрибута const). Его
каторов
общий формат имеет следующий вид.
const_cast<type> (expr)
Здесь элемент type задает новый тип операции приведения, а элемент expr
означает выражение, которое приводится к новому типу. Необходимо подчер­
кнуть, что использование оператора
const_cast для удаления
сопst-атрибута
является потенциально опасным средством. Поэтому 06ращайтесь с ним очень
осторожно.
И еще. Удалять сопst-атрибут способен только оператор
гими словами, ни
dynamic_cast,
ии
static_cast,
ни
const_cast. Дру­
reinterpret_cast
нельзя использовать для измеIieJ-IИЯ сопst-атрибута объекта.
Оператор
Оператор
static_cast
static_cast
выполняет операцию неполиморфного приведения
пшов. Его можно использовать для любого стандартного преобразования. При
этом во время раБОТbI программы юшаких проверок на допустимость ие выпол­
няется. По сути,
static_cast
может заменить "традиционный" оператор при­
ведения типов.
Оператор
static cast имеет следующий общий
static_cast<type> (expr)
формат записи.
636
Модуль 12. ИСl<лючени~, шаблоны и I<ое-что еще
Здесь элемент
type
задает новый тип операции приведения, а элемент
expr
означает выражение, которое приводится к этому новому типу.
Оператор
Оператор
reinterpret_cast
reinterpret_cast
прсобразует ОДИН тип в принципиалыю дру­
гой. Напримср, его МОЖIlQ. использовать для преобразования указателя в целое
значение и целого значения
в указатель. Его также можно использовап. для
-
приведения наследственно несовместимых типов указателей. Этот оператор имс­
ет следующий общий формат записи.
reinterpret_cast<type> (expr)
Здесь элемент
type
задает IЮUЫЙ тип операции привсдения, а элемент
expr
означает выражение, которое приводится к этому новому типу.
Что же дальше?
Цель этой книги
-
преподать начинающему программисту основные элемен­
ты языка С++, т.С. те средства и методы программирования, которые используют­
ся в обычной "еЖСДIiСВНОЙ" пракТИlсе. Освоив материал этой книги, вы сможете
смело приступать к созданию реалЫiЫХ программ. Однако помните, что С-о;
-
очень богатый язык, который содержит множество средств высокого уровня, без
изучения которых невозможно стать профессиональным программистом. К ним
относятся следующие:
• стандартная библиотека шаблонов (Standard Template Libraгy - STL);
•
"неконвертирующие", или
expl ici t-коНСТРУКТОРbI;
•
функции пре06разования (типа класса в другой тип);
•
константные
•
•
ключевое слово
(const) функции-члены и ключевое слово mutablej
asm;
перегруженныс операторы индексации массивов
динамичсского распределения памяти
new
и
( ( ] ), вызова функции ( ( ) )
delete.
и
Из перечислеНllblХ здесь средств самым важным для становления програМl\Ш­
ста я считаю стандартную библиотеку шаблонов
(STL).
Эта библиотека шаблон­
ных I<лассов предоставляет готовые решения для самых разных задач хранения
и :1рганизации данных. Например,
STL определяет такие обобщенные структуры
данных, как очереди, стеки и списки, которые вы можете эффективно ИСПОЛЬ.10вать в своих программах.
С++: РУI<ОВОДСТВО AMl начинающих
637
Полезно также изучить библиотеку функций С++. Она содержит широкий
выбор готовых ФунКI\ИЙ, примеllение которых позволит вам сушественно упро­
Для продолжения теоретического освоения С++ предлагаю обратиться к моей
книге Полный справочник по С++, М.
~
стить и ускорить создание собственных программ.
: Издательский
дом "Вильямс". Она содер­
жит подробное описание элементов языка С++ и библиотек. Ваш нынешний ба­
гаж знаний вполне позволит оам ОСИЛIIТЬ материал этого справочника.
ф
о
от
ф
о
'"
:s:
JS
1:
О
.<
:10
:0
;3
1.
Поясните, как использовать ИНСТРУКЦIIИ
try, catch
и
throw
для под­
Ф
7
держки обработки исключитеЛЫIЫХ ситуаций.
2. Как должен быть организован список catch-ИIlСТРУКЦИЙ при перехвате
Как указать, что исключение типа
функции
4.
func (),
MyExept
может быТ!) сгеIJерировано вне
которая возвращает значение типа
Определите исключение для обобщенного класса
Класс
Queue
void?
Queue
(СМ. проект
12.1).
должен генерировать это исключение при переполнеНИI1 или
потере значимости. Продемонстрируйте его использованиС'.
5.
Что представляет собой обобщенная функция и какое ключевое слово ис­
пользуется для ее создания?
6.
Создайте обобщенные версии функций
ленных в проею·е
7.
5.1.
quicksort ()
и
qs (),
представ­
Продемонстрируйте их использование.
Используя привеДСНIIЫЙ ниже класс
Sampl,
создайте очередь, состоящую
из трех Sаmрl-объектов. Для этого воспользуйтесь обобщенным классом
Queue
из проекта
12.1.
class Sarnpl
int id;
public:
Sarnpl () { id = О; }
Sampl(int х) ( id = х;
void show() ( cout « id «
endl; }
};
8. Переделайте ответ на BOllPOC 7 так, 'побы хранимые в очереди объекты
класса Sarnpl создаВaJlИСЬ динамически.
9.
10.
§
U
;S
исключений как базового, ТaI< и IIРОIIЗnОДIIЫХ классов?
3.
ri.
:s:
1:
Покажите, как объявить пространство имен RоЬоtМоtiоп.
Какое пространство имен содержит стандартную библиотеку С++?
638
11.
МОДУЛЬ 12. ИСl<лючени~, шаблоны и I<ое-что еще
Может ли статическая
(static)
функция-член получить доступ к
Hccra-
тическим членам данных класса?
12.
С помощью какого оператора можно получить тип объекта при выполне­
нии программы?
13.
Какой оператор приведения типов следует использовать. чтобы определить
допустимость операции приведения полиморфных типов при выполнении
программы?
14.
Какие действия выполняет оператор соп 5 t
15.
Попытайтесь самостоятельно внести класс Оиеие (из проекта
_ cas t?
12.1) в соб­
Queue . срр.
ственное пространство имен
QueueCode, а также в свой файл
main (). включив в нее ШIСТРУКЦИЮ
QueueCode стало "видимым".
Затем переделайте функцию
чтобы пространство имеll
16.
Продолжайте изучать средства С++. Это
-
usiпg.
самый мощный язык програм­
мирования на данный момент. Освоение его возможностей на практике
позволит вам считать себя "принятым" в высшую лигу программистов.
Успеха вам!
""
На заметку"
"
Ответы на эти вопросы можно найти на Web-странице данной
книги по адресу:
http://www . osborne . сот.
Прило)кение А
Препроцессор
640
Приложение А. Препроцессор
Препроцессор
это часть компилятора, которая подвергает вашу програ~'му
-
различным текстовым преобразованиям до реальной трансляции исходного кода
в объектный. Программист может давать препроцессору команды, называемые
дuректUВа.Аси nреnроцессора
(preprocessor directives),
которые, не являясь фJР­
мальной частью языка С++, способны расширить область действия его среды
программирования.
Препроцессор С++
-
прямой потомок препроцессора С, и некоторые его сред­
ства оказались изБЫТОll\ibl~Нi после введения в С++ новых элементов. Однако он
по-прежнему является важной частью С++-среды программирования, и МНОi'ие
программисты все еще активно его применяют.
Препроцессор С++ включает следующие директивы.
#define
Hf
#endif
#undef
#include
#elif
Hfndef
#pragma
#Efrror
#else
#ifdef
#Нпе
Как видите, все директивы препроцессора начинаются с символа
"#".
Теперь
рассмотрим каждую И:J них в отдельности.
Директива #define
ДиреКтива
# define используется для определения идентификатора и символь­
ной последовательности, которая будет подставлена вместо идентификатора везде,
где он встречается в исходном коде программы. Этот идентификатор называется
Ашкp0w.4ене.м.., а процесс замены
- .макроnодcmано8КОЙ (реализацией макрорасшире­
ния). Общий формат использования этой директивы имеет следующий вид.
#define
макроимя последовательность_символов
Обратите внимание на то, что здесь нет точки с запятой. Заданная по следов а тельность_символов завершается только символом конца строки. Между Э.'1е­
ментами имя_макроса и последовательность_символов может быть любое
количество пробелов.
Например, если вы хотите использовать слово
во
DOWN в
UP в
качестве значения
качестве значения О, объявите такие две директивы
#define UP 1
#dеfiле DOWN
Данные директивы вынудят компилятор подставлять
#define.
I
I
«
DOWN «
I
I
«
1 или О каждый раз,
UP или DOWN соответственно.
пример, при выполнении инструкции
UP «
сло­
О
когда в файле исходного кода встретится слово
cout «
1и
UP + UPi
На­
С++: PYI<080ACTBO для начинающих
641
Ifa экран будет выведено следующее:
1
О
2
Q.
Важно пониматъ, что макроподстановка - это просто замена идентификатора
соответствующей строкой. Следовзтелыю, есm1 вам нужно определить стандарт­
О
(J
(J
~
о
ное сообщение, используйте КОД, подобный этому.
Q.
с
cout «
Q.
С
Препроцессор заменит строкой "Введите
идентификатора
Ф
"Введите имя файла"
#define GETFILE
/ / ...
имя
файла" каждое вхождсние
GETFILE. ДЛЯ компилятора эта cout-ИИСТрукция
GETFILE;
в действительности выглядит так
cout «
"Введите
имя
файла";
Никакой текстовой замены не произойдет, если идентификатор находится
n
строке, заключенной в кавычки. Например, при выполнении следующего кода
#define GETFILE "Введите имя файла"
/ / ...
cout « "GETFILE - это макроимя\n";
на экране будет отображена эта информация
GETFILE -
это
макроимя,
а не эта:
Введите
имя
файла
-
э~о макроимя
Если текстовая последователыlOСТЬ не помещается в строке, ее можно про­
должить на следующей, поставив обратную косую черту в конце строки, как по­
казаtlO в этом примере.
#define LONG_STRING
которая
"Это очень длинная последовательность,
используется
в
качестве
\
примера."
Среди C++-програММI1СТОВ принято использовать для макроимен прописные
буквы. Это соглашение позволяет с первого взгляда понять, что здесь использу­
ется макроподстановка. Кроме того, лучше всего поместить все директивы
ine
*de
f-
в начало файла или включить ИХ в отдельный файл, чтобы не искать потом
по всей программс.
Важно помнить, что в С++ предусмотрен еще один способ определения кон­
стант, который заключается в использовании спецификатора
const.
Однако
мнЬгие программисты "пришли" в С++ из С-среды, где дЛЯ ЭТИХ целей обычно
642
Приложение А. Препроцессор
использовалась директива
idefine.
Поэтому вам еще часто IIридется с ней стал­
киваться в С++-коде.
Макроопределения. действующие как функции
директиву
#define
можно использовать и по-друтому: се макроимя может
иметь аргументы. В зтом случае при каждом вхождении макроимеНII связаIlные с
ним
apryMeHTbI
заменяются реальными аргументами, указанными
n
коде програм­
мы. Такие макроопределения действуют подобно функциям. Рассмотрим ПрНМL'р.
1/
Использование
"функциональных"
макроопределений.
#include <iostream>
using namespace std;
#define MIN
(а, Ь)
((
(а)
< (Ь»
?
а
"
«
Ь)
int main ()
{
int
х,
х
= 10;
У
= 20;
cout
«
return
У;
"МиНИМУМ равен:
MIN(x,
у);
О;
При компиляции этой программы выражение, определенн.ое идентификато­
ром MIN (а, Ь) • будет заменено. но х и У будут рассматриваться как операнды.
Это значит, что соut-Инструкция после компиляции будет выглядеть так
cout «
"МИНИМУМ
равен:
" «
(((х)«у»
?
х
:
у);
По сути. такое макроопределение представляет собой способ опредеJШТЬ
функцию. которая вместо вызова позволяет раскрыть свой код в строке.
Кажущиеся избыточными круглые скобки. в которые заключено макроопре­
деление MIN. необходимы, чтобы гарантироваТJ> праВlfлыюе восприятие комr1и­
лятором заменяемого выражения. На самом деле дополнительные круглые скоб­
ки должны применяться практически ко всем макроопределениям, действующим
подобно функциям. Нужно всеl'да очень внимателыlO ОТIIОСИТЬСЯ к определению
таких макросов; в противном случае возможно получение неожиданных резуль­
татов. Рассмотрим, например, эту короткую программу, которая исполr)зует ма­
крос для определеНIIЯ четности значения.
С++: РУI<ОВОДСТВО ДМ) начинающих
//
Эта
про грамма дает
643
•
неверный ответ.
~
#include <iostrearn>
using namespace std;
#define EVEN(a)
а%2==0
u
~
о
а.
О
? 1
t:.
ф
.а.
:С
int rnain ()
(
if(EVEN(9+1)) cout <~ "четное число";
else cout « "нечетное число ";
return
О;
Эта программа не будет работать корректно, поскольку в ней не обеспечена
правильная подстановка значений. При компиляции выражение
EVEN ( 9+ 1)
бу­
дет заменено следующим образом.
9+1%2==0 ? 1 :
О
Напомню. что оператор "%" имеет более высокий приоритет, чем оператор
Это значит, что Сllачала выполнится операция деления по модулю
числа
1.
а затем' ее результат будет сложен с числом
9,
(%)
"+".
для
что (конечно же) не
равно о. Чтобы исправить ошибку, достаточно заключить в круглые скобки
аргумент а в МaI<рnопределении
EVEN,
как показано в следующей (исправлен­
ной) версии той же программы.
//
Эта
про грамма работает корректно.
#include <iostream>
using narnespace std;
#define EVEN (а)
(а)
%2==0 ? 1
О
int rnain ()
(
if(EVEN(9+1»
else cout «
return
О;
cout «
"нечетное
"четное
число";
число";
644
Приложение А. Препроцессор
Теперь сумма
9+1
вычисляется до выполнения операции делеюlЯ по модулю.
В общем случае лучше всегда заключать параметры макроопределения в круглые
скобки, чтобы Ifзбежать непредвиденных ре.эультатов, подобных описанному выше.
Использование макроопределений вместо настоящих функций имеет одно (у­
ществешюе достоинство: поскольку код макроопределения расширяется в счю­
ке, и нет никаких затрат системных ресурсов на вызов функции, скорость работы
вашей программы будет выше по сравнению с I1рименением обычной функции.
Но повышение скорости является платой за увеличение размера программы (I<:З­
за дублирования кода функции).
Несмотря на то что маКрООllредсления все еще встречаются в С++-коде, ~!a­
красы, действующие подобно функциям, МОЖIIО заменить спецификатором il1-
line,
который справляется с той же ролью лучше и безопаснее. (Вспомните:
спецификатор
inl ine обеспечивает вместо вызова функции расширение ее тела
в строке.) Кроме того, inlinе-функции не требуют дополнительных круглых
скобок, без которых не могут обойтись макроопределения. Однако макросы, дей­
ствующие подобно функциям, все еще остаются частью С++-программ, посколь­
ку многие С/С++-программисты продолжают использовать их по привычке.
Директива
Директива
#error
#error
представляет собой указание компилятору остановить
компиляцию. Она используется в основном для отладки. Общий формат ее за­
писи таков.
#error
сообщение
Обратите внимание "а то, что элемент сообщение не заключен в двойные ка­
ВЫ'IКИ. При встрече с директивой
#error
отображается заданное сообщение и
другая информация (она зависит от конкретной реализации рабочей среды), по­
сле чего компиляция прекращается. Чтобы узнать, какую информацию отобра­
жает в этом случае компилятор, достаточно провести эксперимент.
Директива
#include
Директива препроцессора
# incl ude обязывает компилятор включить в файл,
который ее содержит, либо стандартный заголовок, либо другой исходный файл,
задаНIIЫЙ в директиве
#include.
Имя стандартного заголовка заключается в
угловые скобки, как показано в примерах, приведенных в этой книге. Например,
эта директива
#include <fstrearn>
С++: PYI<OBOACTBO ДЛfl начинающих
645
включает стандартный заголовок для реализации файловых операций ввода­
вывода.
При включении другого исходного файла его имя может быть указано в двой­
ных кавычках или угловых скобках. Например, следующие две директивы обя­
зывают С++ прочитать и скомпилировать файл с именем
sample. h:
ной реализацией. Если же имя файла заключено в кавычки, поиск файла выпол­
няется, как правило, в текущем каталоге (что также определено конкретной ре­
ализацией). Во многих случаях это означает поиск текущего рабочего каталога.
Если заданный файл не найден, поиск повторяется с использованием первого
способа (как если бы имя файла было заключено в угловые скобки). Поскольку
маршрут поиска файлов определяется конкретной реализацией, то за детальным
описанием этого процесса лучше обратиться к руководству пользователя, при­
лагаемому к вашему компилятору.
Директивы условной компиляции
Существуют директивы, которые позволяют избирателыlO компилировать
части ИСХОДllоrD кода. Этот процесс, именуемый УСЛО8Н.оЙ КОАтШUlЦueй, широко
используется коммерческими фирмами по разработке програ~IМНОГО обеспече­
ния, которые создают и поддерживают множество различных версий одной про­
граммы.
#if, #else, #elif и #endif
Главная идея директивы
#i f
состоит в том, что если стоящее после нее вы­
ражение оказывается истинным, то будет скомпилирован код, расположенный
#endif
#endi f;
В противном случае данный код будет опущен.
используется для обозначения конца блока
Общая форма записи директивы
#if
о
а.
~
Если имя файла заключено в угловые скобки, то поиск файла будет осущест­
Директива
~
С
вляться в одном или нескольких специальных каталогах, определенных конкрет­
между нею и дирек-rивой
о
t.)
t.)
с:::
#include <sample.h>
#include "sample.h"
Директивы
а.
#i f
# i f.
выглядит так.
константное_выражение
последовательность
инструкций
#endif
Если константное_выражение Яl3ляется истинным, код, расположенный непо­
средственно за этой директивой, будет СКОМШfЛИРОван. Рассмотрим 1lрlfмер.
646
Приложение д. Препроцессор
Простой пример использования
//
директивы
#if.
#iпс1udе <iostream>
using namespace std;
#define
МАХ
100
int main ()
#if
МAX>lO
cout «
#endif
/ / '"
return
"Требуется
дополнительная
память\п";
О:
При выполнении эта программа отобразит на экране сообщение Требуется
дополнительная
память, посколысу, как определено в программе, значение
константы МАХ больше
10.
Этот пример иллюстрирует важный момент: выра­
жение, которое стоит после директивы
#if,
ВЫ'lИсляется во время КОМПИЛЯЦИИ.
Следовательно, оно должно содержать только идентификаторы, которые были
предварительно определены, или константы. Использование же перемеНIIЫХ
здесь исключено.
Поведенис директивы
# е 1 s е во многом подобно поведснию и НСТРУJ(:ЦИИ e.i. s е,
которая является частью языка С++: она определяет альтернативу на случай не­
выполнения директивы
#if.
Чтобы показать, как работает директива
воспользуемся предыдущим примером, немного его расширив.
// Пример использования
#inc1ude <iostream>
using namespace std;
#define
МАХ
директив
#if/#e1se.
6
int main ()
#if
МAX>lO
cout «
#else
cout «
"Требуется
"Достаточно
дополнительная
имеющейся
память.\п");
паМRТИ.\п";
#else,
С++: руководство ДМ! начинающих 6А7
........................................................................................................
-....................... .
iendif
Q.
//
О
"""
U
return
~
О;
Q.
в этой программе для имени МАХ определено значение, которое меньше
поэтому
fI i
f-веТIJЬ кода не скомпилируется, но зато скомпилируется альтерна­
тивная #else-вствь. В результате отобразится сообщение Достаточно
щейся
10,
имею­
памяти".
Обратите внимание на то, что директива
#else используется для индикации
одновременно как конца iif-блOl(з' так и начала fеlsе-блока. В этом есть ло­
mческая необходимость, поскольку только одна директива
связана с директивой
Директива
#elif
#endif может быть
# i f.
эквивалентна связке инструкций
else-if и использует­
if-else-if, представляющей
После директивы #elif должно стоять кон­
ся для формирования многозвенной "лестницы"
несколько вариантов компиляции.
стантное выражение. ЕСЛII это выражение истинно, скомпилируется расположен­
ный за ним блок кода, и уже никакие ДРУl"ие #еlif-выражения не будут тести­
роваться или компилироваться. В противном случае будет проверсно следующее
по очереди
директивы
#i f
# е 1 i f -выражение.
# е 1 i f.
Вот как выглядит общий формат использования
выражение·
последовательность
#еlifвыражение
1
последовательность
#elif
выражение
вцражение
инструкций
2
последовательность
#elif
инструкций
инструкций
3
последовательность
инструкций
/ / .""
ielif выражение N
последовательность
инструкций
#endif
Например, в этом фрагменте кода используется идеНТИфИl(атор
ВУ, который позволяет определить, кем компилируется программа.
fldE'fine JOHN О
#define ВОВ 1
COMPILED_
!Б
Q.
с::
648
Приложение А. Препроцессор
ТОМ
!tdefine
2
ВУ
!tdefine COMPILED
JOHN
#if COMPILED ВУ == JOHN
char who[] = "John";
!telif COMPILED ВУ == ВОВ
char who []
"ВоЬ";
#else
char who[] = "Тот";
#endif
Директивы #if и #elif могут быть вложенными. В этом случае диреК1'И­
ва !tendif, ielse или !telif связывается с ближайшей директивой !tif или
tte 1 i f.
Например, следующий фрагмент кода совершенно допустим.
!tif COMPILED ВУ == ВОВ
#if'DEBUG == FULL
int port = 198;
#elif DEBUG == PARTIAL
int port = 200;
#endif
ielse
cout «
"Боб
должен
«
"для
отлад:ки
скомпилировать
вывода
:код
"
данных.\П";
#endif
Директивы
Директивы
#i:fdef и #ifnde:f
#ifdef
и
# ifndef
предлагают еще два варианта условной ком­
пиляции, которые можно nыразить как "если определено" и "если не определено"
соответственно.
Общий формат использования директивы
#ifdef
# ifdef таков.
макроимя
последовательность инструкций
#endif
Если макроимя предварительно определено с помощыо какой-нибудь директи­
вы
#define,
рективами
то последовательность
# i fdef
инструкций, расположенная между ди­
и iendi f, будет скомпилирована.
С++: PYI<OBOACTBO ДМ] начинающих
649
Общий формат ИСПОЛЬЗ0вания директивы # i fnde f таков.
#ifndef
макроимя
Q.
последовательность
О
инструкций
U
U
#endif
Если макроимя ис определено с помощью [(акой-нибудь диреКТl1ВЫ
то последовательность
~
#define,
инструкций, расположенная между директивами
#ifdef и #endif, будет скомпилирована. Как директива #ifdef, так И дирек­
ТJoтa # ifndef может иметьдирекпшу #else ИЛИ #elif. Кроме того, директивы
#ifdef и #ifndef можно вкладывать точно так же, как и директивы #if.
Директива
Дирекrnва
#undef
#undef
используется для удалеtIИЯ предыдущего определения не­
которого макроимени. Ее общий формат таков.
#undef
макроимя
Рассмотрим пример.
#define TIMEOUT 100
4Idefine WAIT О
11 ...
#undef TlМEOUT
#undef WAIT
Здесь имена TlМEOUT и
ответствующая директива
WAIT определены до тех пор, пока не выполнится со­
#undef. Основное назначение ДИРСI<'ПШbl #undef -
разрешить локализацию макроимен для тех частей кода, в которых они нужны.
Использование оператора
defined
Помимо директивы #ifdef существует еще один способ выяснить, определе­
но ли в программе некоторое макроимя. Для этого можно ИСIЮJlьзовать директи­
ву
#i f
в сочетании с оператором времени компиляции
бы узнать, определено ли макроимя
препроцессорной обработки.
#if defined MYFILE
или
#ifdef MYFILE
defined. Например, что­
MYFI LE, можно использовать такие команды
о
Q.
с
~
С
650
Приложение А Препроцессор
При необходимости, чтобы реверсировать условие проверки, можно пред­
варить оператор
defined
символом"!
11.
lIanpl/Mep,
скомпилируется только в том случае, если макронмя
#if !defined DEBUG
cout « "Окончательная
t.endif
Директива
следующий фрагмент lсода
DEBUG
не определено.
версия!\n";
#line
Директива #liле используется для изменения содержимого IlсевдопереМСII­
иых
__ LINE __
и
__ FILE __ '
которые являются зарезервированными иденти­
фикаторами (макроименами). ПссвдопереМСНllая
__ LINE __ содержит номер
скомпилированной строки, а псеВДОllеременная __ FI LE __ - имя компилируе­
мого файла. Базовая форма записи директивы 411 ine имеет следующий вид.
#1ine
номер
"имя_файла"
Здесь номер - это любое положительное целое число, а имя_Файла - любой до­
пустимый идентификатор файла. Значение элемента номер становится номером
текущей строки, а значение элемента имя файла Имя файла
-
элемент необязаТСJJЬНЫЙ. ДиреКТlша
именем исходного файла.
# 1 ine используется, глав­
ным обра.10М, в целях отладки и в специальных приложениях.
Директива
Работа директивы
#pragma
#pragrr.a зависит от конкретной
реализации компилятора.
Она позволяет выдавать компилятору различные инструкции, предусмотренные
создателем компилятора. Общий формат ее использоваиия таков.
#pragma
имя
Здесь элемент имя представляет имя желаемой #рrаgmа-инструкции. Если ука­
за.ШlOе имя не распознается компилятором, директива
#pragma
попросту Ю·НО­
рируется без сообщения об ошибке.
Для получения подробной инФормации о ВОЗМОЖIJЫХ вариантах использова­
ния директивы #pragma стонт обратиться к систсмноii документации по ИСПОЛI)~
зуемому ваМII компилятору. Вы можете найти для себя
oLJelll> полеЗlfУЮ информа­
uию. Обычно #рrаgmа-инструкuии позволяют определить, какие предупрежда­
ющие сообщения выдает компилятор, как генерируется код и кaJ<lfe библиотеки
компонуются с вашими IIрограммами.
С++: руководство MfI начинающих
Операторы препроцессора
651
"#" и "##"
в С++ предусмотрена поддержка двух операторов препроцессора:
Эти операторы используются совместно с директивой
IIdefine.
"#"
и
"##".
"#"
Оператор
прео6разует следующий за ним аргумент n строку, заключеllНУЮ 8 кавычки. Рас­
~
u
u
~
а.
смотрим, например, следующую программу.
r::
#include <iostream>
using namespace std;
а.
с::
Q)
#define rnkstr(s)
11 s
/
int main ()
{
cout «
return
mkstr(Я
D
восторге
от
С++);
О;
11 репроцессор
cout «
в
С ++ нрео6раэует строку
mkstr(Я
в
восторге
от
С++);
строку
cout «
"я
з
восторге
от
с++"·;
Оператор" 11 #.. используется для конкатенации двух лексем. Рассмотрим при­
мер.
#include <iostream>
using namespace std;
#define concat(a,
Ь)
а
##
int rnain ()
int
ху
cout «
return
10;
=
concat(x,
О;
у);
Ь
652
Приложение А. Препроцессор
Препроцессор преобразует строку
cout «
concat(x,
у);
в строку
cout «
ХУ;
Если эти операторы вам кажутся странными, помните, что они не являются
операторами "повседневного спроса" и редко используются в программах. Их
OCllODHoe назначение
-
позволить препроцессору обрабатывать некоторые спе­
циальныс ситуации.
ЗарезервироваННblе макроимена
в языке С++ определено шесть встроенных макроимен.
LINE
FILE
ОАТЕ
ТIME
STDC
__ cplusplus
Макросы
4t 1 i
__ LINE __
и
__ FILE __
описаны при рассмотрении директивы
пе выше в этом приложении. ОНИ содержат llOMep текущей строки и имя фай­
ла компилируемой программы.
МаI(РОС
__ ОАТЕ __ представляет собой
строку в формате месяц/день/год
которая означает дату трансляции исходного файла в объектный код.
Врсмя трансляции исходного файла в объектный код содержится п виде СТРОКИ
в макросе __ Т IME __. Формат этой строки следующий: часьг.минуты. секунды.
Точное lIазначение макроса
__ STDC __ заВИСИТ от конкретной
__ STDC __определен, то
компилятора. Как правило, если макрос
реализации
компилятор
примет ТОЛl,ко стандартный С/С++-код, КОТОРЫЙ не содержит никаких нестаrl­
дартных расширений.
Компилятор, соответствующий ANSI/ISO-стаНIlарту С++, определяет макрос
__ cplusplus
как значение, содержащее по крайней мере шесть цифр. "Нестан­
даРТllые" КОМПИЛЯТОрЫ ДОЛЖНЫ использовать эначеliИС, содержащее пять (или
даже меньше) цифр.
Прило)кение Б
Использование
устаревшего С++­
I<омпилятора
654
Приложение Б. Использование устаревшего ...
При использовании современного компилятора у вас не должно быть пр06лем
с компиляцией щЮl-рамм и;э этой книги. В этом случае вам вообще не понадобlПСЯ
информация, представленная в настоящем прнложении. Как раЗЪЯСIfЯЛОСЬ выше.
программы, приведенные в этой книге. полностыo соответствуют стандарту ANSI/
IS0
дЛЯ С++ и могут быть СКОМШIЛИРОВaRЫ практически любым современным
С++-компилятором, включая
Visual С++ (Мiсгшоft) и С++ Builder (Borland).
Но если вы используете компилятор, созданный несколько лет назад, то при
попытке скомпилировать наши примеры он может пыдать целый список ошибок,
не распознав ряд новых С++-средств. И в этом СJlучае не стоит волноna.ться. Для
того чтобы эти программы заработали со старыми компиляторами, нужно внесП1
в них иебольшие изменеиия. Чаще всего старые н новые С++-программы отлича­
ются использованием двух средств: заголовков и npoстранств имеа. Вот об этом
и пойдст здесь речь.
Как упоминал ось в модуле
1,
инструкция
# include
включает в программу
задаииый заголовок Для более ранних версий С++ подзаголовками понимались
файлы с расширением
заголовка
. h.
Например. в старой C++-ПРОГРi;1мме для включеJlИЯ
i05tream была бы использована следующая ИНСТРУКЦИЯ.
finclude <io5tream.h>
В этом случае R ПРО"'Рамму uыл иы ВКЛЮ'Jеll ЗJГОЛОIЮЧНЫЙ файл
i05 t ream. :1.
Таким образом. ДЛЯ того. чтобы IJКЛЮЧИТЬ В С++-IIРОI1>ЗММУ заголOlЮК 1:1 расчете
более "древний" компилятор, необходимо задать 11МЯ фШ1ла с расширс~и('м .1;.
В новых С-!'+-программах
J'<I
IJ СUОТIJt'ТСТВIIИ со CT;J.HnapTOM ЛNSI/ISО ДЛЯ С++ ис­
пользуются заголовки другого типа. Современные заГОЛОВЮ1 uпредеЮIIОТ не имена
файлов. а стандартные идентификаторы. которы(' могут совпадать с тщювыми, но
не всегда. Современные C++-загОЛОВЮi представляют соБО~1 абстракцию, которая
попросту гарантирует включение в npoq>aMМY требуемой информаЦШI.
Поскольку современные ЗЮ'ОЛОDКН необязательно являются именами файло)).
они не должны иметь расширение. h. Они определяют имя заюловка. заключен­
ного в угловые скобки. Вот, например. КШ< НЫl·ЛЯI1.ЯТ два современных эш·олавка.
поддерживаемых стандартом С++.
<iostream>
<fstream>
Чтобы преобразовать эти "новые" загOJlовки в "старые" заголовочные фаЙл.ы,
достаточно добавить расширение
. h.
ВJСЛЮЧая современнъrй заголовок 1:1
nPO"'PCl.'fMY. необходимо помнить, что его со­
s td. Кll, упоминалось выше. простр.ш­
СТ80 имен - это просто дскларапlВНая 06.1IacТl •. Ее tlaЗllзчеJlие - ЛОК3JНlзоваТh имена
держимое относится к пространств}' IIМСII
идентификаторов во избежанис коллl13ИЙ с IIМСllа,\m. В старых версиях СТ+ имена
библиотечных функций помещаются в глобаJlьное I1РОСТРанство имен,
;)
не в про-
С++: PYl<OBOACтaO M~ начинающих
..:транство имен
655
std, используемое совремеlпlыми компиляторами. Таким образом,
работая со старым компилятором, не нужно испол.ьзовать эту инструкцию:
using namespace std;
В действительности большинство старых компиляторов вообще не воспримут
инструкцию
1: 08.
:~
=~
:~
using namespace.
:::::Е
О
:
.~
:=++
:U
Два простых изменения
Есл 11 ваш компилятор не поддерживает пространства имен и новые заголовки,
:0
:ф
он выдаст одно ИЛII несколько сообщений об ошибках при попытке скомпили­
:3
ровать первые неСКОЛhКО строк программ, приведенных в данной книге. В этом
~~
c:I)''1ae в "рограммы lIеобходимо внести только два простых изменения: исполь­
зов;пь заголовок старого типа и удалить паmеsрасе-инструкцию. Например, за­
.а;а
....
:0
~~
Q)
s
r
мен IПС ЭТII инструкции
~
О
<iostream>
using namespace std;
~i~clude
~
g
u
такой.
s:
#include <iostream.h>
Это изменение преобра:зует "новую" программу в "старую". Поскольку при ис­
пользовании "старого" заголовка в гл06альное пространство имен считывается
все содержимое заголовочного файла. необходимость в исполъэовании пате 5pa::e-I1I1СТРУКJlИИ отпадает. После внесения этих изменений программу можно
СКОМПИJIlI[ЮВёПЬ с помощью старого компилятора.
Иногда приходится вносить и другие изменения. С++ наследует ряд заголов­
ков из языка С. Язык С не поддерживает современный стиль С++-заголовков,
используя B~lecTo них заголовочные. h-фаЙлы. Для разрешения обратной со­
вместимости стандарт С++ по-прежнему поддерживает заголовочные С-файлы.
Oдli<IKO стандарт С++ также определяет современные заГОЛ08КИ, которые мож­
но IIспользовать вместо заголовочных С-файлов. В С++-версиях стандартных
С-:13ГUЛОВICОВ К имени С-файла просто добавляется префИКС 'с' И опускается рас­
ширение
. h.
<cmath>.
а Д.1Я файла
I1апрнщ~р, С++-заголовком для фаЙJIа
string. h -
заголовок
math. h служит заголовок
<cstring>. Несмотря tla то что в
С++-программу разрешено включать заголовочный С·ФаЙл, против такого под­
хода у разработчиков стандарта есть существенные возражения (другими слова­
ми, это не рекомендовано). Поэтому в настоящей книге используются современ­
ные С ++-заголовки
80 всех ИНСТРУКЦИЯХ IHnclude. Но если ваш
компилятор не
поддерживает С++-эквиваленты ДЛЯ С-заголовков, просто замените их "стары­
МII" заголовочными файлами.
656
Предметный УI(озотель
Предметный указатель
д
Символы
#define, дирекпша 640
#elif, директива 647
#elldif, директива 645
#error, диреКТ1lва 644
#if, диреКТlша 645
#ifdef, директива 648
#ifndef, директива 648
#include, директива 644,654
#pragma, директива 650
#undef, директива 649
_cplusplu~. макрос 652
_DATE_. макрос 652
_FILE_. макрос 652
_LINE_, макрос 652
_STDC_, макрос 652
_TIME_, макрос 652
Fll.E
650
LINE __ 650
Ap~{eнт
518
ApГY~\1ellТЫ функции
292
215
Аргуменш функций
Б
Байт-код
28
Библиотеки С++
Блок
Директива
#define 640
#eljf 647
#епdif 645
#епог 644
#if 645
#ifdef 648
#ifпdеf 648
#include 644,654
#li11c 650
#ргаgmз 650
#tll1def 649
Директивы прелроцессора
ДополнителЫIЫЙ код
250
<cctype> 176
<сшаth> 78, 127
<cstdio> 171
<cstdlib> 64, 111
<cslril1g> 173
<fsl:ream> 545
<iostream> 37,522,524,557
<typeinfo> 629
<iostгeam.l1>
522
56
и
в
ИдеНТl\фИкатор
llиртуальная машина Java
Виртуальные функции
Вложенный класс
Выражение
условное
Выражеиия
112
101
376
640
73
3
Заголовочный файл
64
225
Блок кода
90
368
Зarоловок
G3
по умолчанию
Деструктор
ЗаГОЛ(1DКИ
А
Абстрактный класс
Декремент
28
502
Индекс
66
159
Инициализация
массивов
177
87
Инкапсуляция 30
Инкремент 90
персменных
Инструкция
cin 45
С++: руководство ДЛr;J начинающих 657
.................................................................................................................................
л
continue 145
сщ,t 45
do-wl,ile 136
fOT 54, 125
goto 152
if 52,110
s\\'itch 117
while 134
Искл ючение 572
bad cast 634
bad=typeid 632
Литерал
вешественный
восьмеричный
символьный
строковый
572
=ПМЕ= 652
466
376
503, 630
лроизводный 466
Классы
594
66
Комментарий
36
255
Ком I1ИЛЯТОР
С++ Builder 34
Visua[ С++ 34
КОliСтанты 83
KOHcТPYRToP 368,480
копии 405,411
параметрИЗ0ванньп1
Кэш
640
Манипулятор
полиморфный
605
315
652
640
_cplusplus 652
DЛТЕ
652
-FILE -652
-LINE- 652
-STDC 652
basic- istr('.am 525
Куча
Макроимя
Макроподстановк.,
basic-ostream 525
basic=strearnbuf 525
fstream 546
ifst,"eam 546
ios 533,547
ios_base 525
ofstream 546
string 84, 169
type_info 629
абстрактный 518
Ключевые CJl0B3 С++
Макроимена
Макрос
30,350
basic ios 525
basic-iostream 525
Компаранд
83
м
Класс
обобщенные
83
84,170
шестнадцатеричный
к
вложенный
83
84
целочислеНIiЫЙ
исключителыlя ситуация
базовый
83
370
boolal рЬа 541
dec 541
cndl 541
ends 541
fixed 541
flush 541
Ьех 541
internal 541
left 54 t
noboolalpba 541
noshowbase 541
nosl1Owpoint 541
noshowpos 541
noskipws 541
nounitbuf 541
nouppeTcase 541
oct 541
resetiosflagsO 541
right 541
scientific 541
setbaseO 541
setfi110 541
setiosflagsO 541, 542
setprecisionO 541
setwO 541
84
658
Предметный указатель
Объявление
showbase 541
showpoint 541
showpos 541
skipws 541
un.itbuf 541
uppercase 541
ws 541,543
класса
оап
543
Манипуляторы
540
Массив 158,193
двумерный 163
1= 629
& 185
• 185
== 629
"It#" 651
"11" 651
"запятая"
177
128.340
339
многомерный 165
одномеркый 158
"знак вопроса"
строк
"точка"
"IIСКЛlOчающее ИЛИ"
182
Массивы
объектов
388
203
указателей
Метка
Метод
152
30
Многоуровневая непрямая адресация
206
Модификатор
const 635
inline 377
long 71
short 71
signed 71
static 310,312
unsigned 71
volatile 635
56
И.
89
1 328
поразрядный 324
исключающее ИЛИ
506
468
НЕ
324.327
328
присваивания
41
ра:lрешсния контекста
41!)
Обобщенные
Операторы
классы
594
функции 587
89
арнфметические
424
426
Объект 30
Объектно-ориеитированное
программирование
358.419
раарешения облаСТII ВIIДИМОСТИ
о
анОНИМfrые
526
ИЛИ. поразрядный 326
IIHKpeMeHTa 55.436
32,465
Объединения
вывода 3В,
декремента
дополнеllИЯ до
виртуалькых функций
синтаксис
(XOR) 96
353
cconst_cast 635
defined 649
dt'lcte 605
dynamic_cast 633
J1(~\У 605
reinterpret_cast 635
sizcof 343
static _ cast 635
typeid 629
XOR 324.327
ввода 44.526
деления по модулю
н
Наследование
419
25
Оператор
Маниnyляторные функции
иmщиализация
478
опережаlOщее
25,29
декремента
90
иш<ременrd
90
логическис
92
отношений 92
поразрядные 322
89
358,
С++: руководство ДЛЯ начинающих 659
.................................................................................................................................
лриведения типов 6З3
Пространство имен
rrрисваивания, составные
сдвига
99,342
неименованное
330
622
248
Прототип функции 214
Псевдокодом 28
Прототипы функций
Операция
приведения типов
102
Опережающее обышление
419
Очередь ЗВ2
ПсевдоперемеН1iые
_FILE_ 650
LINE_ 650
п
Параметры
ссылочные
р
266
Перегрузка
Расширение типа
конструкторов
операторов
Реализация
398
Рекурсия
430
операторов ввода-вывода
Перегрузка функций
Переменная
614
std 37,522,622
250
Р~ггчи, Дэнис
526
102
590
23
277
с
40
Переменные
IIЮЩНализация
Символ
87
новой строки
Перечислсние
fn1tflags 533
восьмеричная
iostate 568
openmod~ 547
scekdir 565
ПереЧИ~lения 318
84
шсстнадцатеричная
CoprnpoBкa массива
Спагетти-коД
84
166
23
Специализация
Поле
класса:явная
сборНое:аdju~tfiеld
535
c6opHoe:lJascfield 534
c60pHoe:floatfield 535
Полиморфизм 31,502
ПОJlltморфный класс 503,630
ПорождеПllая функция 590
По"roк 523
ccrr 524
cin 524
clog 524
cout 524
wccrr 524
wcin 524
\vclog 524
\\'cout 524
двоичный 523
текстовый 523
П редупреждення 39
Преnpoцессор 639
Промежуточный язык Мicrоsoft
46
Система СЧИCJIения
ФУНJЩИИ
597
590
функции:явная
591
Спецификатор
inline 644
private 474
pubIic 351,469,474
lШМnОНОВКИ 310
Спецификатор класса п3.МJIТИ
auto 308
extern 308
register 315
Спецификатор rnna
COl1st 304
volatile 307
Ссылки
на пронэводные nШЪ1
независим:ые
CTI.'K
28
275
382
Страустрyn, Бьерн
Строка
38, 84
25, 92
502
660
Предметный указатель
Строки
169
Строковый литерал
170
Структурированное программирование
23
Струю'УРЫ
421
showpos 534
skipws 534
tlnitbuf 534
t1ppercase 534
Флаг знака 73
Функции
т
"друзья"
Тип
встраиваемые
Ьооl 102
доступа
ofCtype 565
pos_type 567
streamsize 538
Тип данных 41
Ьооl 78
сЬаг 75
doubIe 77
float 77
int 72
long doubIe 7В
void ВО
wchar_t 76
перегрузка
391
50 I
Указатель
158,183
this 428
Управляющие последовательности
Условное выражение
112
ф
523
Флаг
hoolalpha 534
dec 534
fixed 534
Ьех 534
internal 534
left 534
oct 534
right 534
scientific 534
showbase 534
showpoint 534
543
587
277
чисто виртуальные
Функция
на производные типы
Факториал числа
471
обобщенные
Указатели
на объекты
502
376
манипуляторные
у
Файл
415
виртуальные
251
85
37,62,212
absO 63
atof() 246
badO 569
beforeO 629
clearO 569
closeO 549
eofO 559
failO 569
fil10 538
flagsO 536
flushO 559
freeO 607
getO 552. 557
getlineO 558
getsO 171
goodO 569
isalphaO 177
islowerO 177. 195
isupperO 177
mainO 37. 243
mallocO 607
паmсО 629
орепО 546
operator 430
pt~ekO 559
powO 104
precision() 5ЭВ
ptltO 552
ptltbackO 559
qsoгtO 169,255
гandO 110
515
С++: PYI<OBOACTBO ДЛЯ начинающих 661
.................................................................................................................................
basic_iostream, класс 525
basic_istream, к.,1асс 525
basic_ostream, класс 525
basic_strearnbuf, класс 525
BCPL 23
beforeO 629
Ьооl 71
boolalpha, флаг 534
break 143
Bytecode 2В
rdstateO 568
readO 554
seekgO 565, 567
seekpO 565, 567
setf() 535
sчrtО 126
strcatO 173
strcmpO 173
strcpyO 173
strlenO 174
tolowerO 177
соиррегО 176
tшsеtf() 536
widthO 538
writeO 554
операторная 430
порожденная 590
шаблонная 590
с
С
ц
цикл
do-wbile 136
54,125
while 134
(ог
бесконечНhlЙ
вложенный
130
151
ч
Чисто виртуальная функция
ш
Ша6ЛОli
587
ШаБЛОIiНая функция
22
27
С++ Bui1der 26, З4
Call-by-reference 262
Call-by-value 262
Cast 102
catch 572
сегг 524
char 71,72,73
cin 44,524
class 350
clearO 569
clog 524
closeO 549
CLR (Comrnon Language Runtime) 28
Common Language Runtirne 28
const, спецификатор nша 304
соnst _ cast, оператор 635
continue 145
cout 3В,524
С#
515
590
А
Лrraу 158
atof() 246
auto 231
auto, спецификатор 308
D
dec, флаг 534
delete 605
delete, оператор 605
do~whi1e 136
double 71
dynamic_cast, оператор
в
в
23
badO 569
bad_cast, ИСКJIЮчение 634
basic_ios, КJIaCC 525
Е
enum 318
eofO 559
extem ЗОВ
БЗ3
662 Предметный указатель
...............................................................................................................................
iпtеГП<.lI. флаг 534
ios, класс 5ЗЗ
ios_b<.lSC, класс 525
iostate, ле[lеЧJ.icлсние 568
isalpl1aO 177
islо\\'(:гО 177,195
isupperO 177
F
fail() 569
{a1sf, константа 78
FIFO 382
fill0 538
FILO 3В2
First in First Out 382
First il1 Last Out 382
fixed, флаг 534
flagsO 536
float 71
flusl10 559
fmtflаgs,леречисление
{ОС, инструкция 54
for, цикл 125
J
Java 27
Java \'irtual Machine 28
JVM U<1va Virtual Machine) 28
533
FORTRAN 24
freeO 607
friend 415
Function overloading 277
G
Generated function 590
getO 552, 557
getlineO 558
getsO 171
goodO 569
goto 152
GUl 36
н
Неар
L
left, фЛaJ О 534
1011g, модификаоroр 71
1опв (10иЫе 72, 73
10ng il1t 72,73
м
mainO 37,243
таllосО 607
Manipu1ator 533
МРС 509
Мiсrоsоft Foundation Classcs 509
Microsot"t 111tennedi"te Languagc 28
МSП. (Мiсrоsоft lntenncdiate Language)
28
Multiple indircction 206
N
604
Ьех, флаг
534
nameO 629
1
IDE (Integrated Development
Environment) 34
if 52, 110
if-e1se-if 115
in1ine 644
inline. МQДИфИК<'lТОР 377
Iпliпе ftlлсtiол 377
Il1stantiating 590
int 41, 71, 72, 73
Iotegral РГОJПоtiоп 102
Iлtеgгаtеd Development Environment 34
Nашсsрасе
37
namespace 614
l1CW, оператор
605
о
oct, флаг 534
546
openmode, лереЧНСЛСНllе 547
Oper<.llor 89
operator 430
overlo<1(\ 65
ореп()
С++: PYI<OBOACTBO ДЛЯ начинающих
•••• 0
................................. •
р
Pascal 23
peckO 559
Plain 01d Data 424
РОО 424
pow() 104
precisionO 538
Preprocessor 640
privatc 1174
protected, модификатор доступа 477
pubIic 469,474
public. спецификатор 351
put() 552
putbackO 559
Q
qsortO 169, 255
Queue 382
QuickSort, алгоритм сортировки 254
R
randO 110
rdsta teO 568
readO 554
Refel'ence parameter 267
register, спецификатор 315
reinterpret_cast, unepaTOp 635
retuгn 38
riglJt, флаг 534
RTТI 628
signed 10ng int 72, 73
signed shOlt int 72, 73
sizeof 343
skipws, флаг 534
sqrtO 126
Stack 382
Standard С++ 26
Stal1dard Template Library 26
static 623
static, модификатор 310,312
static_cast, оператор 635
std 522
std, пространство имен 37
STl. 26
StrC<ltO 173
strcmpO 173
strcpyO 173
Strcam 523
streamsize, ТlШ 538
strlenO 174
struct 421
switch 117
т
seckpO 565,567
setf() 535
template 588,595
tСПlрlаtе<> 593, 597
this 428
throw 572
thrоw-nыражение 583
tolower() 177
toupperO 176
tгue,KoHcTaнтa 78
try 572
type_info, класс 629
typedef 322
typeid 629
typename 588
Туре promotion 102
slJoГt:, модификатор 71
short int 72,73
showbase, флат 534
showpoint, флаг 534
shоwfЮS, флаг 534
signed, модификатор 71
signed сnат 72. 73
signcd int 72, 73
union 424
unitbuf, флаг 5З4
L!NIX 23
unsetf() 536
unsigned, модификатор 71
unsigned char 72, 73
s
scientific, флаг 534
sееkdiг,перечисление
565
seck~O 565, 567
663
••••••••••• •• •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
u
.~~.....~P~~~.~!~~~~.Y!~?~?:~~~ ......................................................................
unsigned int 72.73
unsigned long int 72. 73
unsigned short int 72.73
uppercase. флаг 534
L1sing 37.619
v
virtuaI 502
Visua\ С++ 26.34
VisuaI Studio .NEт, среда разработки
программ
35
void 71.212.214
volati1e. специфи.катор 307
volatile. спецификатор типа 307
w
wcerr 524
wchar_t 71
wcin 524
wclog 524
\vcout 524
w!1i]e 134
widthO 538
wnteO 554.
НаучНО-nОnУЛЯР"0е uздаllliе
Герберт Шилдт
С++:РУКОВОДСТВО ДЛЯ начинающих
2-е издание
Литературный редактор
Верстка
Художественный редактор
Корректоры
О.Ю. Бе~озовская
О.В. ЛIIННIlК
вт ПавлюmllН
з.в. Александрова. ПА. Гордllеющ
о.в. Мurиуm"на. пв. Чернокозuнская
ИздатсльскнА дом "вилыlс"·
101509.г.~осква.ул.Лесная.д.43.сrp.
Подпнсано в печать с готовых днапозитивов
Формат
14.06.05.
70)( 100'/, •. Печать офсетнаJl.
Гарнитура
Уч.-И1д. л.
1
23,S.
Times.
Тираж
Уел. печ. л.
3 000
54, 18.
ЭК1. Зака1 H~
201.
ОДА «Cbhkt-ПстербургекаJl типографНI Н!
19! 144,
Санкт-Петербург, ул. ~ollceeHKo,
Т i:ЛСфОII ~'Т;II:ла марксНlНГЗ
271-35-42.
6».
10.
С ++: базовый курс,
третье издание
Герберт Шилдт
8
этой книге описаны все ОСНООI1ые средства языка С++: от
элементарных
понятий
до
супервозможностеЙ. После рассмотрения
основ
програМl\Iирования
на С++ (переменнЬ!х, операторов,
инструкций управления, функций,
классов и объектов) читатель освоит такие более сложные средства
языка, как механизм обработки
ИСКJlЮ4ительных ситуаций (исключений),
шаблоны,
пространства
имен, динамическая идентифика-
ция типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором
ключевых
слов,
используемым
в
программировашшдля .NЕт. Автор
спраВ04ника - общепризнанный
авторитет в области программирования на языках С и С++, Java и
С# - оключил В текст своей книги
и советы программиста~f, которые
по:зволят ПОВЫСИТЬ эффективность
их работы.
ISBN 5-8459-0768-3
в продаже
v
ПОЛНЫИ
СПРАВОЧНИК
по
(++,
4-Е ИЗДАНИЕ
Герберт Шилдт
il
чствертом нэдашш 'ПОЛIIОЮ
cnpaBollllUKfl 1'/0 С +
+ .. полносты()
OIIII(illIbI и ПРUИЛЛIOСТРНРО";JНЫ
вес ключевf,J(' (лова, ФУIIЮЩII,
классы 11 с"оiiспы ЯЭЫ/Gl С + ~,
(оотuеТСТОУЮЩIIС стандарту
ANSI/ISO.
Иllф()Р~!<ЩИЮ.
lt:Jложенную
11
I(IIIIГ('. можно
ИСПОЛЬЗОIJап, ВО
IJCCX
современных
средах прuграммнроrзаНIIЯ.
Освещены uce аспекты языка С++,
включая его осшшу - язык С.
Справочник состоит из пяп!
частей:
1) подмножество С;
2) Я:JЫК С++;
3) 611БЛlютека стандартных
функциii;
www.williamspubIishing.com
.1)
6нблноТ<.'к;J. стандаРll{ЫХ
к.лассоu;
5)
IIрllложения IШ языкr' С+-,.
ДЛЯ Ulирокого
)<pYI'a
IljJUI-раММIIСТОВ.
..,
ПОЛНЫИ
СПРАВОЧНИК
по С,
4-Е ИЗДАНИЕ
в данной КlШГС, задумаllНОЙ как
справочник для всех програММIIС­
ТОП, ра60таЮЩIIХ иа языке С, ис.за­
llIlСИМ() от их уровня подготовки,
подробно ОПllсаны псе аспскты ЯЗЫ­
ка С и его бибЛНОТ(:'КII стшщартных
функциil. Главныi'1 акцент сдслаJl
на стандарте ЛNSI/ISО Я;J(,(ка С
Приведево ОПllсаНlfе I(ак стандарта
С89, 1'(IК н С99. В кнше oco(ioe
nНlIмание уделяется
Y'ICTY
характе­
РИСТIIК трансляторов, среды про­
Герберт Шилдт
граММИРОIШIIШ1
11
Olll'paLIllUIIIIbIX
систем, IIСllOЛЬЗУЮЩllХСЯ в иастоя­
щее время. Уже -н самом начале по­
дробно предстаlJлены все средства
Полный
ЯЗЫК,1 С, таЮIС как Ю1Ю'lСl3ые сло­
ва, IIНСТРУКЦИИ прспроцессора 11
другие. Вначале описывается глав­
ным образом С89, а затем ПРИ ВО­
дится подробное ОПlfсаНIIС IIОВЫХ
НО:IМОЖlIостей я:н.Iка, вж'ленных
CTallJIapTOM С99. Такая IlOследова­
справоЩlliК
облегчить практическое "рограм.\ш­
тельность изложеНIIЯ !lОJв\)ляет
роваНllе на языке С, так как
u на­
стоящее время имснно эта версия
для большинства программистов
предсr.шляется как "собственно С",
к тому же это самыИ РLlспростра­
веННblЙ D мире ЯЗblК програММIlРО­
ВaIIШI. Кроме того, эта последова­
ТС.1ьностъ l1;JложеНllЯ облегчает ос­
воение С++, КОТОрblЙ является над­
МllожесТlЮМ С89. В ОПНС<l.JIIШ биб­
.,,': .
• I
"
.;.~,
www.williamspubIishing.com
лиотеки стандартных ФУНКЦIIII С
нриuедеНbI как фУНКЦИII стаlщарrd
С89, так и С99, причем функции,
введенные стандартом С99, отм('­
ЧСI!Ы спеЦllально. Все ЮПfГИ
Шилдта HPOllJaMMHCTbl всегда го­
рячо любили за наЛИЧllе с[).1ержа­
телыIх,' IIСТРИВИ;UIЫIЫХ I1РIfМС­
роп. В «(Iиге рассматриваются наи­
более ваЖНblе 11 раПlространеНlIЫС
алro[штмы 11 прнлпжеНIIЯ, необхо­
димые для К;lЖДОП) ПРОГР'НIМl!ста.
а также ПРllменеlilН' методов искус­
ственного IIнтеллскта н програм­
в продаже
МllроваНIIС для
Windo\\'s 2000.
ПОЛНЫЙ СПРАВОЧНИК
ПОС#
Герберт Шилдт
СО:Щi1нныii K()~lllalllJl'ii 1\!luos,)ft
для ш)ltiН~jJЖI(lj срtды
.N 1:Т
I:Г3Пlс\vnгk, Я:JЫК С# UlllIP<1tTC;1
lla (югатос lIасле.'IIIС 11 Оl1лаСТII
I1рограМ~I11РОllаНllЯ.
CIi -
потомок дJlУХ
успеIIIIlЫ;, в
C<lMI.IX
11РЯ~lI)i\
Мllре KOMlll.IOTCIHlbIX }(:H,IKlJlJ: С++
П
v
олныи
~
~~'­
~.~,
справочник
и.J ,1\'<1. llpll Н:iJlожеllllll М;.1Тt:'РI1 а .1<1
11 язык(' С# '-РУiIНl't: lJl'l'П) IIOIHIТl"
IЮI'}I<I ('ЛСiIУl'Т Iюста Г"IП, ПР/ ку.
Сам /10 се()!' Я:IЫ)( С;; 04l'lIl,
(jОЛЫIIОЙ, а 6IJблгютска классов
С# еще (JО.ilI,ше. LJTo(ih] Чllтате:IIО
()J,IЛО лсгче СОIl.llа,~а,-ь с Tal(llM
ОГIЮМIIЫ~I ()бl,е~\Ом маТСРll<lла,
:па КllllПl ()J,IЛ(l разделена
lIа
TPIJ
'{асп/, llOl'LlЯШСННЫХ
>I:JJ,II\Y С#, СI"O (JlIU;IIЮТСКС IJ
Г1[11IMClleIlIIIO :пого языка,
Для работы е KHlJfOIr HI1K;IKOfO
преДЫI\ущеl'О 011 ЫТiI n облаеТII
прor'раММIIJ10ваНIIН не трсбуеlСЯ.
Ес!1И вы уже Зliаl(О~IЫ с С-;,,;
llJlI/Java, то с ОСВОСНllеи СИ у
вас не будет н llK<lKIIX ll[Jo(iJJ('M.
Ct. ,'/НОС'О о(jщего
Гf()Скольку У
с ЭПI~I" языкаМlI, Г::(,!l1I IiI,J
не С'llIтаетс себя опытным
www.williamspublishing.com
I1рограММIIСТОМ,
Kltlll'a
поможет IIЗУ'JIПЬ С#, НО !lJlЯ
этого вам придется тщаl елы/О
разобраться 13 ПРlIмерах,
ПРlIlJедснных в каждой главе,
ISBN
5-8459-05БЗ-Х
в продаже
Искусство
программирования
на
Java
Герберт Шилдт, Джейме Холме
Э
та книга отличается от множестоа
IS8N 5-8459-0786-1
других книг по языку
8
]ava.
В то
продаже
время как другие КIfИГИ обучают
основам языка, эта l<инга показывает,
как использовать язык наиболее эффек­
ТИВllO, с большей пользой и отдачей ДЛЯ
решения
запутанных
задач
програм­
мирования. На страницах КНИПi посте­
пенно раскрывается мощь. универсаль­
ность и элегантность языка]аvа.
Как и можно ожидать, несколько опи­
санных приложений связаны непосред­
ственно с Internet. Многие главы посвя­
щены анализу кода. В них покаэаны
потрясающие возможности языка Java
I1РИ создании приложений для Internet
Легкость. с KOTOPO~! эти 11 POl-раммы могут
быть написаны на ЯЗblке ]ava. подтверж­
дает гибкость и элегантность языка.
В каждой главе рассматриваются фраг­
менты кода, которые без изменений вы
сможете использовать в своих програм­
мах. Например. синтаксн чеСКII й ана­
лизатор может послужиТl) отличным
дополнением для многих разработок.
Однако наибольшую rЮJIЬЗУ от этих
программ можно получить. если на IIХ
основе создавать собственные IlРШЮ­
жен ия. Например, Web-чеfШЬ, пО!\рn6·
ное описание которого IlРИВОДIIТСЯ
D
книге, может ПОСJlУЖIПЬ ОСIIОВОЙ для
разработки архиватора Web-сайта или
детектора разрыва связи.
Квита рассчитана на студентов, пре­
подавателей и специалистов D области
компьютеРНblХ технологий.
http://www.williamspublishing.com
Параллельное
и распределенное
программирование
с использованием С ++
Камерон Хьюз. Трейси Хьюз
В
этоii книге представл('rr аРХlIтектурный подход к распрс.1С.1СН­
нам!' н параЛЛСЛЬJlОМУ програММllроваНIIЮ с иrПОЛЪЗОlJаНllем
языка С++. Здесь OIllIC;]llbl простые методы програММIlРООЗН1:Н
параллеЛЫIЫХ ВIIРТУ:JЛЫIЫХ МilПlIЩ И основы Р;t:JраGотки кластrр·
ISBN 5-8459-0686-5
в продаже
пых IlРIIЛОЖ('lIиii.
МиоголеТllllii ОПЫТ анторов ЮlllГИ в области разра(ЮТКIIНРnГРi1ММ·
110\"0 О()ССIIС'IСНШ1 убедил IIХ в ТОМ, чтодля успешного ПРnСJ'Т11РUl';J·
ПIIЯ програюIныx средств 11 эффеКТИВIIО!1 IIХ рсализаUИII без УIIН­
нерсаЛЬНОСТII ПРlIменж'мых средств уже не оБОЙТIIСЬ. Чтобы :Iффек­
Т11НI\О rешать задаЧJl, стоящпс I1ер!'д СОВрСМL'I1ПЫМ программистом,
lIеоuхо;шмо сочетать различные программныс JI 1II1жеllерные под­
ХО.11,I 11(1 IlрllМСр, для РСlUеllШI проблем "гонки' данных 11 CJ!HXPUlIIIза!! 1111 !lОl'тупа к ПIIМ МОЖJlО нспnльзовать методы О()ЪСКТНО-ОРIII:IIТН­
POR<lHIIOI'(J програММI\РОВi1JI1Нl. ПРIJ МllогозадаЧIIО~{ 11 MlIornnOTO'IIJf)M
упраllлеНlIИ ilВТnРЫ СЧlIтают шш()олее псрспеПlJ!IIЮll агеllТН()-ПРИ­
l'IIТIlРUllанную архитектуру, а для МI1НИМllзаШIII затrат lIа обеСI1С'lе­
!1Ш' Сllязей МСЖДУ оuъсктаЮI предлагают IIрИМСИЯТЬ меТОДОЛОГIIЮ
·классноll доски". ПОМНМО объектно-ориентированного. агеllТНО­
ОjJllСНПIРОВ<llIlJОГО 11 AI-ОРIIСНТНРОВiIIIIIОГО J1рограММllрокаНШI, ОIlИ
ИСПОЛЬЗУЮТ парамr.ТРlIзоваllНОС ПРОГР;JММllроваШIС для реализаUИII
обобщенных C++-аЛГОРIIТМОR, KOTOPЫ~ ПРll~lеняютr.я IIмеНlЮ там,
где lIужен параллеЛlI:iМ.
ЭтО! КННГilllС только нау'lИТ П1lсать програММIIЫС компоненты, пред­
назначенные для совместноН работы в с.етевоЙ среде, но и ПОСЛУЖИТ
надежным "ЛУТСВОДIПСЛ('М" по стандартам дЛЯ ПРОГРЗММIIСТОВ,
которые заllllМilЮТСЯ МНОI'(J:JадаЧIJЫМИ и многопоточными прило­
ЖСIlШI~НI.
Основные темы КIIНГИ:
ВО;JМОЖJlОСТЬ IIспользоваlJИЯ агентов 11 ,·еХIЮЛОГIIИ "клаССIIОЙ
досю!"' лля упрощеНIIЯ параллсльного программироваllИЯ;
о(iъсктно-орнеНТllрОlJаНlfые подходы к многозадачности и МИOJ·о­
ПОТО'1II0СТII:
JlСIIОЛl>ЗоваШIС средств языка
UML в
ЮЩIIХ ПРИ~lеl!еIlИЯ параллслъного
11
разработке просктов, требу­
рас.предслениого программи­
ронаНIIЯ;
1I01lЫЙ стандарт POSIXjUNIX 'ЕЕЕ дЛЯ библиотеки
Эта КlIIII·a
aJJpecolJ<lJ[(I
Ptl1rcads.
программнетам, проектировщикам
11
разра­
БОТ'IJJКiJМ программных продуктов, а также научным работникам,
прсподзuатслям
JJелыюе
11
11 СТУДСlIтам, которых IIHTepecyeT Dведеl1ие
о парал­
раслределенно~ Пj10ГР<lММИJ10вание с НСIlОльзоваНlIСМ
SI:lblK3 С++.
http / jwww.williamspublishing.com
С++
ДЛЯ ··чаЙников"
5-еиэдание
с. ДЭВИС
К
НИ\'U представляет собой введение
в ЯЭblК программирования С++.
Основное отличие даНlIОЙ книги от
ISBN 5-8459-0723-3
В продаже
предыдущих изданий С++ для "цаЙl/uков"
R том, что это издание не требует от чита-
теля каких-либо дополнительных знаний, в ТО время как предыдущие издания
опиралис\" на знание читателем языка
ПРОГРLlммнрования С. Книга отличается также тем, что несмотря на простоту
изложения материала, он подаli
J!OCTa-
ТОЧliО строго. Поэтому, изучив основы
программироваllИЯ на С++, 'штателю не
придется пересматривать свои знания
при даЛЫiейшем изучении языка.
Книга отличается широким охватом
тем - от простейших объявлений пере­
менных до конuепuий объеКТlю-ориенти­
рованного программирования, вопросов
перегрузки операторов и множественно­
го liаследования, причем весь материал
снабжен множеством практических при­
меров.
Эта книга не учит программированию в
Windo\~'s или созданию красивого интер­
фейса двумя движениями мыш\.,\о; изло­
женный в ней материал не ПРИDязан 1<
какому-то определенному компилятору
или операционной системе. Она вряд ли
будет полезна профессионаJIЬНОМУ про­
граммисту, но если ваша uель - глубокое
знание ЯЗblI<а программирования и вы не
знаете, с чего начать
-
эта книга ДЛЯ вас.
http jjwww.dialektika.com
Download