Загрузил Mikhail Tereshchenko

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

Реклама
руководство ДЛЯ начинающих
Второе издание
А
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 с именем р. В этом при мере
показано, как можно напрямую получить доступ к объекту оЬ и как использовать
для этого указатель (В этом случае мы им