Автор: Дейтел Х.М. Дейтел П.Дж.
Теги: языки программирования компьютерные технологии программирование задачи по программированию переводная литература издательство бином язык программирования c
ISBN: 5-7989-0170-X
Год: 2000
X.M. Дейтел
П.Дж. Дейтел
БИНОМ
КАК ПРОГРАММИРОВАТЬ
НА
С
HOW то
PROGRAMM
S E С О N D
H. М. Deiftel
Nova
University
Deitel and Associates
P. J. Deitel
Deitel and Associates
Prentice-Hall
International, Inc.
E D I T I О N
X. М. Дейтел
П. Дж. Дейтел
КАК ПРОГРАММИРОВАТЬ
НА
Перевод
с английского
под редакцией
В. Тимофеева
ЗАО Издательство
БИНОМ
Москва 2000
УДК 004.43
ББК 32.973.26-018.1
Д27
Перевод с английского
Бреховских В. B., Карташова B.M., Козлова A.B., Череховского В.И.
Харви Дейтел, Пол Дейтел
Как программировать
БИНОМ», 2000
г.
-
1008
на
С:
Пер.
с
М.:
англ.
Книга предлагает читателю курс программирования,
С/С++,
«Издательство
ориентированный
мирования,
так
и
на
опытных
весьма
программистов,
Помимо достаточно
серьезное
введение
в
С++,
которые
могут
просто
полного и
глубокого
из
наиболее
одного
пропустить
не
изложения языка
С
перспективных
на
настоящий момент языков; ему посвящена значительная часть книги.
ние
на языки
и рассчитана как на начинающих, не владеющих никакими языками програм¬
интересующие их главы.
дается
ЗАО
c.: ил.
Особое внима¬
объектно-ориентированного программиро¬
вания больших программных систем. Примеры и многочисленные упражнения знако¬
мят читателя с часто применяемыми алгоритмами и фундаментальными структурами
данных, показывая технические приемы их реализации. Приводится также масса
уделяется
методикам
структурного
и
полезных советов.
Книга адресована широкому кругу читателей,
от новичков до студентов,
изучаю¬
щих программирование в рамках своей специальности.
Все
права
защищены.
Никакая
часть
этой
книги
не
форме или любыми средствами, электронными
фотографирование, магнитную запись или иные средства
информации без письменного разрешения издательства.
любой
может
или
быть
воспроизведена
механическими,
копирования
или
сохранения
Authorized translation from the
English
language edition
ISBN 5-7989-0170-X
(pyc.)
ISBN 0-13-288333-3 (англ.)
в
включая
© Original copyright. Prentice-Hall
© Издание на русском языке.
ЗАО «Издательство БИНОМ», 2000
Содержание
17
Предисловие
Об этой
Обзор
18
книге
20
книги
Глава 1.
Принципы машинной обработки
данных
29
1.1. Введение
30
1.2. Что такое
32
компьютер?
1.3. Внутренняя организация
33
компьютера
1.4. Пакетная обработка, мультипрограммирование
и разделение времени
1.5.
Персональные
вычисления, распределенные вычисления и
вычисления в модели
1.6. Машинные
1.7. История
34
языка
34
клиент/сервер
языки, языки
ассемблера
и языки высокого уровня
35
36
С
1.8. Стандартная библиотека С
37
1.9. Другие
38
языки высокого уровня
38
1.10. Структурное программирование
39
1.11. Основные принципы среды С
1.12. Общие
замечания о
С
и
этой
41
книге
42
1.13. Concurrent С (Параллельный С)
1.14. Объектно-ориентированное программирование
Резюме
Глава 2.
стиль
Упражнения
Введение
в
Рекомендуемая литература
программирование
2.1. Введение
2.2. Простая программа
на
С:
в
С
С
печать строки текста
на
С:
сложение двух целых чисел
понятия о памяти компьютера
2.5. Арифметика
на
53
54
2.3. Еще одна простая программа
2.4. Общие
43
программ
Упражнения для
самоконтроля
С++
Советы по
программирования
Советы по повышению эффективности
Ответы к упражнениям для
самоконтроля
Хороший
переносимости
и
54
58
63
64
6
Как программировать на С
2.6.
Принятие решений:
операции равенства
и отношения
67
Резюме
Распространенные ошибки программирования
Хороший
Советы по переносимости программ
программирования
Ответы на упражнения для
Упражнения для самоконтроля
самоконтроля
Упражнения
стиль
Глава 3.
87
Структурная разработка программ
3.1. Введение
3.2. Алгоритмы
88
88
89
3.3. Псевдокод
3.4. Управляющие структуры
3.5. Структура выбора if
3.6. Структура выбора if/else
90
92
94
3.7. Структура повторения while
3.8. Формулирование алгоритмов: пример 1
(повторение, управляемое счетчиком)
3.9. Формулирование алгоритмов на основе нисходящего
98
99
2
пошагового уточнения: пример
102
(повторение, управляемое контрольным значением)
ЗЛО. Формулирование алгоритмов
на основе нисходящего
пошагового уточнения: пример
3
108
(вложенные управляющие структуры)
113
3.11. Операции присваивания
3.12. Операции инкремента
Резюме
стиль
114
декремента
ошибки программирования
Хороший
Советы по повышению эффективности
Распространенные
программирования
Общие методические
Ответы
Глава 4.
и
к
замечания
Упражнения для самоконтроля
Упражнения
упражнениям для самоконтроля
137
Управление программой
4.1.Введени е
138
4.2. Основы структур повторения
139
4.3. Повторение, управляемое
139
счетчиком
4.4. Структура повторения for
141
4.5. Структура for:
144
замечания и рекомендации
145
4.6. Примеры структур for
4.7. Структура
со множественным
4.8. Структура
повторения
4.9. Операторы break
и
148
выбором switch
154
do/while
156
continue
4.10. Логические операции
4.11. Смешивание операций равенства ( )
4.12. Краткая сводка
158
и присваивания
по структурному программированию
(=)
161
163
7
Содержание
Хороший
Распространенные ошибки программирования
Советы по повышению эффективности
Советы по переносимости программ
Общие методические
Ответы к
замечания
Упражнения для самоконтроля
Упражнения
упражнениям для самоконтроля
Резюме
стиль
программирования
Глава 5.
185
Функции
5.1. Введение
186
*
5.2. Программные модули
в
187
С
5.3. Функции математической библиотеки
188
5.4. Функции
189
5.5. Определения функций
190
5.6. Прототипы функций
194
5.7. Заголовочные файлы
197
5.8. Вызов функций:
198
вызов по значению и по ссылке
5.9. Генерация случайных
5.10. Пример:
5.11. Классы
199
чисел
203
стохастическая игра
206
памяти
5.12. Правила области действия
209
5.13. Рекурсия
210
5.14. Пример применения рекурсии:
5.15. Рекурсия
числа
215
Фибоначчи
219
итерацией
Распространенные ошибки программирования
Резюме
стиль
в сравнении с
Советы
программирования
Советы
по
по
переносимости
Хороший
программ
Общие методические
эффективности
Ответы на
Упражнения для самоконтроля
для самоконтроля
Упражнения
повышению
замечания
упражнения
247
Массивы
Глава 6.
6.1.Введени е
248
6.2. Массивы
249
6.3. Объявление
6.5. Передача
6.7. Пример:
и
с массивами
массивов в
6.6. Сортировка
251
массивов
6.4. Примеры работы
функции
вычисление среднего значения, медианы
значения с использованием массивов
в массивах
6.9. Многомерные
массивы
Хорошкй
Распространенные ошибки программирования
Советы по повышению эффективности
Общие методические замечания
Упражнения для самоконтроля
Ответы к упражнениям для самоконтроля
Упражнения
Упражнения на рекурсию
Резюме
стиль
263
267
массивов
наиболее вероятного
6.8. Поиск
251
программирования
269
273
277
Как программировать на С
8
Глава 7.
307
Указатели
308
7.1. Введение
7.2. Объявление и инициализация
7.3. Операции
7.4.
Передача
309
переменной-указателя
310
над указателями
312
параметра по ссылке
7.5. Использование модификатора const с указателями
7.6.
Программа пузырьковой
316
сортировки, использующая
322
вызов по ссылке
7.7.
Выражения
и
арифметические операции
7.8. Связь между указателями
с указателями
329
и массивами
333
7.9. Массивы указателей
7.10. Пример: программа
7.11. Указатели
Резюме
на
326
334
тасовки и сдачи колоды карт
339
функции
ошибки программирования
Хороший
Советы по повышению эффективности
Распространенные
стиль
программирования
Советы по переносимости
Общие методические
программ
Ответы на
Упражнения для самоконтроля
Специальный
Упражнения
упражнения для самоконтроля
раздел: как самому построить компьютер
замечания
Глава 8.
Символы
и
369
строки
370
8.1. Введение
8.2. Строки
370
и символы
8.3. Библиотека обработки
372
символов
377
8.4. Функции преобразования строк
382
8.5. Функции стандартной библиотеки ввода/вывода
8.6. Функции операций над строками
из
библиотеки
385
обработки строк
8.7. Функции сравнения
8.8. Функции
поиска из
8.9. Функции
памяти
8.10. Другие функции
Резюме
стиль
388
библиотеки обработки строк
390
библиотеки обработки строк
395
библиотеки обработки строк
из
399
библиотеки обработки строк
ошибки программирования
Хороший
Советы по переносимости программ
Ответы к упражнениям для
самоконтроля
Распространенные
программирования
Упражнения для
самоконтроля
упражнения
Глава 9.
из
по
Специальный раздел: более
Упражнения
работе со строками
419
Форматированный ввод/вывод
420
9.1. Введение
421
9.2. Потоки
9.3. Форматированный вывод
9.4. Печать
сложные
целых чисел
с применением
printf
421
422
9
Содержание
плавающей точкой
9.5. Печать чисел с
9.6. Печать строк
423
425
и символов
9.7. Другие спецификаторы преобразования
426
9.8. Печать
428
с заданием ширины поля и точности представления
9.9. Использование флагов
9.10. Печать литералов
Резюме
стиль
printf
строке управления форматом
430
Еэс-последовательностей
433
ввод с применением scanf
434
и
9.11. Форматированный
в
ошибки программирования
Хороший
Советы по переносимости программ
Ответы на упражнения для
самоконтроля
Распространенные
программирования
Упражнения для
Упражнения
самоконтроля
Глава 10. Структуры, объединения, операции
и
с
битами
451
перечисления
10.1. Введение
452
10.2. Описания структур
453
10.3. Инициализация структур
455
10.4. Доступ
455
к элементам
структур
10.5. Использование структур с функциями
457
10.6.
458
Typedef
10.7. Пример:
моделирование
высокоэффективной
тасовки
459
и раздачи карт
10.8. Объединения
461
10.9. Поразрядные операции
463
10.10. Битовые
471
поля
10.11. Перечислимые
474
константы
Резюме
Хороший
Распространенные ошибки программирования
Советы по переносимости программ
программирования
Советы по повышению эффективности
Общие методические
Ответы на
замечания
Упражнения для самоконтроля
упражнения для самоконтроля
Упражнения
стиль
Глава 11. Работа
с
489
файлами
11.1. Введение
490
11.2. Иерархия данных
490
11.3. Файлы
493
и потоки
11.4. Создание файла последовательного доступа
493
11.5. Чтение данных
499
из
файла
последовательного доступа
503
11.6. Файлы произвольного доступа
11.7.
504
Создание файла произвольного доступа
11.8. Произвольная
запись данных в
11.9. Последовательное
доступа
файл произвольного
чтение данных из
файла
доступа
506
произвольного
508
ТО
Как программировать на С
11.10.
Резюме
стиль
508
Пример: программ^обработкитранзакций
Распространенные
программирования
Советы
по
повышению
самоконтроля
Упражнения
ошибки программирования
Советы
по
переносимости
Упражнения для
эффективности
Ответы
на
Хороший
программ
упражнения для самоконтроля
Глава 12. Структуры данных
12.1. Введение
12.2. Структуры,
12.3.
527
528
ссылающиеся на себя
Динамическое распределение
12.4. Связанные
529
530
памяти
531
списки
12.5.Стек и
539
12.6. Очереди
545
12.7. Деревья
550
Резюме
стиль
Распространенные
программирования
ошибки программирования
Советы
по
повышению
Хороший
эффективности
Советы по переносимости программ
Упражнения для самоконтроля
Ответы на упражнения для самоконтроля
Упражнения
Глава 13. Препроцессор
587
13.1. Введение
13.2. Директива
препроцессора #include
588
13.3.
препроцессора #define: символические константы
589
Директива
588
13.4. Директива препроцессора #define: макросы
590
13.5. Условная компиляция
592
13.6. Директивы препроцессора #error
13.7. Операции #
и
и
593
#pragma
593
##
13.8. Нумерация строк
13.9. Предопределенные
594
594
символические константы
13.10. Макрос подтверждения
Резюме
стиль
594
ошибки программирования
Хороший
Советы по повышению эффективности
Ответы на упражнения для
самоконтроля
Распространенные
программирования
Упражнения для
самоконтроля
Упражнения
Глава 14. Специальные вопросы
14.1. Введение
14.2. Переадресация ввода/вывода
601
602
в системах
UNIX
и
DOS
602
14.3. Списки аргументов переменной длины
603
14.4. Аргументы командной строки
606
14.5. Замечания
относительно компиляции программ
из нескольких исходных
файлов
607
11
Содержание
14.6. Выход
из программы с помощью
14.7. Модификатор
типа
exit
и
atexit
609
volatile
14.8. Суффиксы для целых
609
констант и констант
плавающей точкой
610
14.9. Еще раз о файлах
14.10. Обработка сигналов
611
с
613
14.11. Динамическое выделение
памяти:
функции calloc
и
realloc
14.12. Безусловный переход: goto
615
616
Советы по
Распространенные ошибки программирования
Советы по повышению эффективности
программ
Общие методические замечания
Упражнения для самоконтроля
Ответы на упражнения для самоконтроля
Упражнения
Резюме
переносимости
Глава 15. С++
«улучшенный» С
как
625
15.1. Введение
626
15.2. Однострочные комментарии С++
627
15.3. Потоковый ввод/вывод С++
15.4. Объявления
15.5. Создание
в
628
С++
630
новых типов данных в
15.6. Прототипы функций
и
С++
630
контроль соответствия типов
631
15.7. Встроенные функции
633
15.8.
637
Параметры-ссылки
15.9. Модификатор const
641
15.10. Динамическое распределение памяти
delete
643
15.11. Параметры, используемые по умолчанию
15.12. Унарная операция разрешения области действия
644
с помощью new и
646
647
15.13. Перегрузка функций
15.14. Спецификации внешней
649
связи
15.15. Шаблоны функций
Резюме
стиль
649
Распространенные
программирования
ошибки программирования
Советы
по
переносимости
Хороший
программ
Общие методические
эффективности
Ответы к
Упражнения для самоконтроля
упражнениям для самоконтроля
Упражнения
Рекомендуемая
Приложение: ресурсы С++
литература
Советы
по
повышению
замечания
Глава16.
Классы
и
абстракция
данных
663
16.1. Введение
664
16.2.
666
16.3.
Определение структур
Доступ к элементам структур
16.4. Реализация
пользовательского типа
с помощью структуры
667
Time
668
12
Как программировать на С
16.5. Реализация
абстрактного
типа данных
Time
670
с помощью класса
16.6. Область действия
16.8.
676
класса и доступ к элементам класса
16.7. Отделение интерфейса
677
от реализации
доступом к элементам класса
Управление
681
Функции доступа и сервисные функции
16.10. Инициализация объектов класса: конструкторы
684
16.11. Использование
688
16.9.
687
с конструкторами аргументов по умолчанию
16.12. Деструкторы
691
16.13. Когда
691
вызываются конструкторы и деструкторы
16.14. Использование
элементов данных и
16.15. Скрытая ловушка: возвращенце
закрытый
на
694
элементов-функций-
ссылки
699
элемент данных
16.16. Присваивание
по умолчанию путем поэлементного
701
копирования
16.17. Повторное
использование программного
701
обеспечения
Распространенные ошибки программирования
Хороший
Советы по повышению эффективности
Общие методические замечания
Упражнения для самоконтроля
Ответы к упражнениям для самоконтроля
Упражнения
Резюме
стиль
программирования
Глава 17. Классы:
часть
II
715
17.1. Введение
716
17.2. Константные объекты
17.3. Композиция:
и константные
элементы-функции*
классы в качестве элементов других классов
17.4. Дружественные функции
и дружественные классы
17.6. Динамическое распределение
new и delete
17.8. Абстракция данных
17.10. Шаблоны
Резюме
памяти с помощью
723
727
операций
734
735
элементы класса
17.9. Контейнерные
717
729
17.5. Указатель this
17.7. Статические
*
и сокрытие
739
информации
743
классы и итераторы
743
классов
ошибки программирования
Хороший
Советы по повышению эффективности
Распространенные
стиль
программирования
Советы по переносимости
программ
Общие методические
Упражнения для
Ответы
самоконтроля
Упражнения
упражнениям для самоконтроля
замечания
Глава 18. Перегрузка операций
18.1. Введение
18.2. Основные принципы перегрузки операций
к
755
756
757
13
Содержание
18.3. Запреты на перегрузку операций
18.4. Функции-операции как элементы
и как дружественные функции
18.5. Перегрузка операций передачи
759
класса
760
в поток
762
и извлечения из потока
18.6. Перегрузка одноместных операций
764
18.7. Перегрузка двухместных операций
765
18.8. Пример:
Array
18.9. Преобразование типов
765
18.10. Пример:
777
класс
18.11. Перегрузка
18.12. Пример:
Резюме
стиль
++ и
786
-
Date
класс
788
Распространенные
Ответы
к
ошибки программирования
Советы
программирования
Общие методические
776
String
класс
по
повышению
Хороший
эффективности
Упражнения для самоконтроля
Упражнения
замечания
упражнениям для самоконтроля
Глава 19. Наследование
19.1. Введение
19.2. Базовые и
805
806
808
производные классы
19.3. Защищенные
810
элементы
19.4. Приведение указателей базового
к указателям на
производный
класса
811
класс
815
19.5. Применение функций-элементов
19.6. Переопределение
базового
элементов
класса
816
в производном классе
19.7. Открытые, защищенные
19.8. Непосредственные
и закрытые
и косвенные
19.9. Применение конструкторов
базовые
базовые
классы
и деструкторов
821
в производном классе
19.10. Неявное преобразование объектов производного
к базовому
19.11. Наследование
19.12. Композиция
820
820
классы
класса
824
в
конструировании программного обеспечения
в сравнении с наследованием
19.13. Отношения «использует»
и
«знает»
825
827
828
19.14. Пример: Point, Circle, Cylinder
828
19.15. Сложное наследование
833
Резюме
стиль
ошибки программирования
Хороший
Совет по повышению эффективности
Распространенные
программирования
Общие методические
замечания
Упражнения для самоконтроля
Упражнения
Ответы на упражнения для самоконтроля
Как программировать на С
14
Глава 20.
Виртуальные функции
и
847
полиморфизм
848
20.1. Введение
20.2. Обработка различных
типов данных при помощи
849
операторов switch
849
20.3. Виртуальные функции
20.4. Абстрактные базовые классы
850
и конкретные классы
851
20.5. Полиморфизм
20.6. Пример: программа
начисления
использующая возможности
20.7. Новые
заработной
платы,
853
полиморфизма
862
классы и динамическое связывание
864
20.8. Виртуальные деструкторы
20.9. Пример: наследование интерфейса
864
и реализации
Резюме
стиль
ошибки программирования
Хороший
Советы по повышению эффективности
Распространенные
программирования
Общие методические
Ответы
на
Упражнения для самоконтроля
Упражнения
замечания
упражнения
для самоконтроля
Глава 21. Потоки ввода/вывода
в
877
С++
21.1. Введение
879
21.2. Потоки
879
21.3. Потоковый вывод
882
21.4. Потоковый ввод
886
21.5. Функции неформатируемого ввода/вывода read,
gcount и write
892
21.6. Манипуляторы
893
потоков
21.7. Флаги форматирования
21.8. Состояния ошибки
21.9. Ввод/вывод определяемых
21.10. Привязка
896
потока
906
потоков
907
пользователем типов
910
потока вывода к потоку ввода
Хороший
Распространенные ошибки программирования
Советы по повышению эффективности
Общие методические замечания
Упражнения для самоконтроля
Ответы на упражнения для самоконтроля
Упражнения
Резюме
стиль
программирования
Приложение
929
А. Синтаксис С
941
Приложение Б. Стандартная библиотека
Приложение В.
Таблица приоритета операций
Приложение Г.
Набор
символов
ASCII
.
978
980
15
Содержание
Приложение Д.
Системы
Д.1. Введение
Д.2. Запись двоичных
981
счисления
982
чисел в виде восьмеричных
и шестнадцатеричных
985
Д.З. Преобразование
восьмеричных
и шестнадцатеричных чисел в двоичные
987
двоичной, восьмеричной
Д.4. Преобразование
или шестнадцатеричной форм в десятичную
числа из
Д.5. Преобразование десятичного
числа в двоичное, восьмеричное
988
или шестнадцатеричное
Д.6. Отрицательные двоичные
Резюме
числа: дополнение до двух
Упражнения для самоконтроля
Упражнения
для самоконтроля
987
Ответы
на
упражнения
990
Предисловие
книгу написали два человека
Эту
мирует
и
учит
старый
программировать других
и
молодой. Старик програм¬
лет. Молодой человек
больше 30-ти
работал программистом около десяти лет, йока им не овладела страсть к учи¬
тельству и писательству. Старик программирует и учит благодаря своему опы¬
ту. Молодой человек
благодаря неистощимому запасу энергии. Старик стре¬
мится к ясности; молодой человек
к наивысшей эффективности. Старику
и элегантность. Молодому человеку хочется результатов.
Мы объединились, чтобы на свет появилась книга, которую, как мы надеемся,
вы найдете полезной, интересной и занимательной.
нравится красота
В большинстве учебных курсов языку С учат людей, которые знают, как
программировать. Многие преподаватели думают, что сложность С и ряд дру¬
гих
трудностей
делают этот язык непригодным для начального
граммированию
как раз для того, на что нацелена эта книга.
написали?
Язык С занял особое
обучения
про¬
Так почему
же
мы ее
ной индустрии,
место среди языков реализации систем в компьютер¬
объектно-ориентирован¬
ный вариант С++ станет ведущим языком середины
конца 90-х годов. Хар¬
ви Дейтел в течение 13-ти лет преподавал в университетах Паскаль, делая ак¬
цент на разработке ясно написанных, хорошо структурированных программ.
Большинство
и есть все основания верить, что его
риал в точности так, как это делал
Тут
курсах.
старший
это основ¬
этот мате¬
из нас в своих университетских
имеются определенные ловушки, но там, где они встречаются, мы
указываем на них и
можем
Паскалю
Мы представили
из того, чему учат во вводных курсах по
ные принципы структурного программирования.
сказать,
что
объясняем, как можно их избежать. Исходя из опыта мы
обучающиеся справляются с данным курсом ничуть не
хуже, чем с аналогичным, но ориентированным на Паскаль. Однако здесь есть
одно существенное отличие: у студентов появляется очень сильный стимул
благодаря тому, что они учат язык, который немедленно окажется им необхо¬
дим, как только они покинут стены университета. Они с энтузиазмом относят¬
ся к работе с материалом
что немаловажно, учитывая повышенную труд¬
С.
Наша цель была ясна: написать учебник по программированию на С для
вводных курсов университетского уровня с ориентацией на студентов, имею¬
щих небольшой или вообще нулевой опыт программирования, но одновремен¬
ность изучения
но и книгу, предлагающую достаточно строгое изложение теории и практики,
С. Чтобы достичь этих целей,
обычные учебники,
потому что
характерное для традиционных курсов по языку
мы написали книгу
более объемистую,
чем
наш текст, помимо всего прочего, постепенно приучает читателя к принципам
структурного программирования. Около 1000 студентов изучали предлагае¬
мый здесь материал на наших курсах.
изучали
С
по первому изданию
этой
Десятки
тысяч учащихся по всему миру
книги.
Книга содержит большой набор примеров, упражнений
личных
областей, чтобы
и проектов из раз¬
дать читателю возможность решать интересные зада¬
чи из реального мира.
Книга сосредоточивает внимание на принципах качественного конструи¬
рования программного обеспечения и делает акцент на ясности программ, на¬
писанных с использованием методологии структурного программирования.
Мы пытаемся избегать
сиса в пользу
обучения
эзотерической терминологии
на примерах.
и
спецификаций
синтак¬
18
Как программировать на С
Среди
педагогических элементов, применяемых в тексте
законченные
демонстрирующие обсуждаемые концеп¬
ции; краткое содержание, приведенное в начале каждой главы; распростра¬
ненные ошибки и правила хорошего стиля программирования, сообщаемые по
программы
и
примеры
их
вывода,
отдельный раздел в конце главы; резюме и кон¬
трольные вопросы с ответами к ним; и наконец, богатейший набор упражне¬
ний, какой вы вряд ли найдете в других книгах по С. Упражнения варьируют¬
ся от простых вопросов на повторение до больших программных задач и насто¬
ходу изложения и сведенные в
ящих полноценных проектов.
Преподавателям, которым требуются достаточ¬
работ для студентов, найдут много
но сложные проекты в качестве курсовых
подходящих
приложили
курс
задач,
приведенных
в
упражнениях
приобрел
для студента
в
главах
5-й
по
21-ю. Мы
благодаря упражнениям данный
большую ценность. Программы в тексте книги те¬
немалые усилия к тому,
чтобы
ANSI компиляторов на Sun SPARCstation, Apple Macintosh (Think С), IBM PC (Turbo С, Turbo С++ и Borland С++)
и DEC VAX VMS (VAX С).
Данная книга следует стандарту ANSI С. Многие особенности ANSI С не
реализуются компиляторами для более ранних версий С. Обратитесь к руко¬
водствам по вашей конкретной системе, чтобы выяснить больше подробностей
относительно языка, либо достаньте документ ANSI/ISO 9899: 1990, «Ameri¬
can National Standard for Information Systems
С».
Programming Language
стировались при помощи совместимых с
Об этой
Все
то,
компоненты и отличительные
чтобы помочь студентам
книге
особенности этой
книги направлены на
в изучении языка.
Цели
урока
Каждая глава
начинается
с
формулирования учебных
задач.
Благодаря
этому студент получает представление о том, к чему следует подготовиться, а
также, по прочтении главы, он может оценить, насколько ему удалось выпол¬
нить данные задачи.
Содержание глав
Содержание в начале каждой
главы помогает студенту
подойти
к изуче¬
«сверху вниз». Тем самым он может видеть, что
спланировать свой темп работы.
нию материала, так сказать,
ждет его впереди,
Разделы
Каждая глава
и
организована
ключевым моментам темы.
в
виде
Каждый
небольших разделов, посвященных
С представлен в контексте закон¬
аспект
ченной
работающей программы. После каждой программы приводится
«окно», содержащее вывод, полученный при ее запуске. Это позволит студен¬
ту убедиться, что программа работает, как ожидалось. Соотнесение вывода с
операторами программы, которые его производят, является превосходным
подспорьем в изучении и закреплении принципиального материала. Наши
разработаны
С. Внимательное чтение
программы
мы
действительно
для того, чтобы испытать
разнообразие возможностей
будто эти програм¬
книги должно выглядеть так, как
вводятся, компилируются и запускаются на компьютере.
19
Предисловие
Иллюстрации
Книга содержит разнообразные рисунки
и диаграммы. Обсуждение струк¬
турных блок-схем, помогающее оценить значение управляющих структур и
структурного программирования, сопровождается ясными диаграммами. Гла¬
ва о структурах данных содержит массу рисунков, иллюстрирующих создание
и поддержку связанных списков, очередей, стеков и двоичных деревьев.
Вспомогательные
Мы используем
оформления
оформления, чтобы помочь студентам
важных аспектах разработки программ, их те¬
элементы
пять
элементов
сконцентрировать внимание на
стирования и отладки, вопросах эффективности и переносимости. Мы выделя¬
ем соответствующую информацию под рубриками Хороший стиль программи¬
рования,
нию
Распространенные
ошибки программирования, Советы по повыше¬
по переносимости кода и Общие методические
эффективности, Советы
замечания.
Хороший
В
тексте
стиль программирования
советы
выделяются
относительно
Они привлекают внимание
лучшие программы. Эти советы
граммирования.
щим писать
что мы вынесли из своего сорокалетнего
Распространенные
язык
ния к этим распространенным
Лучше
стиля
приемам,
сообщают самое
(в сумме) опыта.
дсобенно
про¬
помогаю¬
ценное из того,
те, кто проходит начальный курс
склонны совершать одни и те же
отношении.
к
ошибки программирования
Студенты, изучающие
обучения,
целесообразного
читателя
ошибки. Привлечение
ошибкам программистов
внима¬
очень помогает в этом
учиться на чужих ошибках, чем на своих
это позволяет
избежать напрасной потери времени.
Советы
Мы
по повышению
эффективности
считаем, что написание ясных и понятных программ
целей
начального курса по программированию.
самые
быстрые,
ком-либо
из
хотят писать
самые компактные, самые экономные, либо выдающиеся в ка¬
ином отношении программы.
Им действительно небезразлична эф¬
фективность программ. Они хотят знать,
мы
важнейшая
Однако студенты
как можно оснастить свои програм¬
каким-нибудь «усилителем». Поэтому мы включили в текст Советы по по¬
эффективности, чтобы подчеркнуть возможные пути улучшения
вышению
программ.
Советы
по переносимости программ
Разработка программного обеспечения
сложное и чрезвычайно дорогое
Организации, разрабатывающие программы, часто вынуждены про¬
изводить различные их версии, приспособленные к разнообразным существу¬
ющим компьютерам и операционным системам. Поэтому сегодня делается си¬
занятие.
льный акцент на переносимости, т.е. на производстве такого программного
обеспечения, которое будет работать без изменений на многих компьютерных
системах. Многие утверждают, что С
лучший язык для разработки перено¬
симого программного обеспечения.
зуют свою программу на
сту ошибочно.
льного
С,
Некоторые
полагают, что если они реали¬
она автоматически
будет переносимой. Это попро¬
Достижение переносимости требует тщательного и осмотрите¬
проектирования. Здесь масса ловушек. В самом документе ANSI по
20
Как программировать на С
Стандартному С
перечень потенциальных трудностей занимает 11 страниц.
Мы включили в текст многочисленные Советы по переносимости кода, в ко¬
торых обобщили свой собственный опыт разработки переносимого програм¬
много обеспечения, дополнив его внимательным изучением раздела стандарта
ANSI, посвященного переносимости.
Общие
Этот
под этой
методические
элемент
замечания
новый
оформления
рубрикой
во втором издании.
Мы суммировали
замечания, касающиеся архитектуры и конструирования
программных систем, особенно больших систем.
Резюме
Каждая из глав оканчивается набором дополнительных разделов, при¬
званных служить определенным педагогическим целям. Мы приводим деталь¬
ное резюме главы в форме перечня ключевых пунктов. Это помогает студенту
пересмотреть и закрепить в памяти
важнейшие
моменты материала.
Терминология
Раздел Терминология содержит список важнейших
ном порядке), определяемых в данной главе. Цель его
терминов
(в алфавит¬
та же
закрепление
пройденного материала. Затем мы приводим все имеющиеся в главе вставки,
собранные по рубрикам
Хороший стиль программирования, Распростра¬
ненные ошибки программирования,
Советы
по
переносимости кода
Упражнения
Эти
и
Советы
по повышению
Общие методические
эффективности,
замечания.
для самоконтроля
многочисленные упражнения,
с исчерпывающими ответами на них,
включены в книгу для самостоятельного изучения.
приобрести уверенность
чтобы попробовать
в
применении
изученного
Они
и
позволяют студенту
подготовиться
к
тому,
свои силы в решении настоящих задач.
Упражнения
Завершает каждую
главу объемистый набор упражнений, диапазон кото¬
рых меняется от простого повторения важнейших терминов и понятий, через
написание одиночных операторов C,, небольших фрагментов функций, закон¬
ченных
функций
и программ
Такое большое число
свои курсы к
до написания больших курсовых проектов.
упражнений
позволяет
преподавателям
специфическим потребностям аудитории
и менять
приспособить
учебные зада¬
ния от семестра к семестру.
Они могут использовать эти упражнения в качестве домашних
заданий,
для коротких опросов и для экзаменационных сессий.
Обзор
книги
Первая из них, охватыва¬
подробное изложение языка С,
включая
формальное введение в структурное программирование. Вторая
главы с 15-й по
часть
что отличает нашу книгу от других учебников по С,
21-ю, представляет собой серьезное введение в язык С++ и объектно-ориенти¬
рованное программирование, пригодное в качестве учебного пособия для сту¬
Книгу
ющая
можно разделить на три основных части.
главы
с
1-й
по
14-ю, содержит
очень
21
Предисловие
Третья
часть, приложения А
Д, содержат
материал, дополняющий основной текст.
Глава 1, «Введение», рассказывает о том, что такое компьютеры, как они
дентов старших курсов колледжа-
разнообразный справочный
работают
и
как
их
программирования
программируют.
и
В ней вводится
объясняется, почему переход
понятие
структурного
к
структурным методи¬
написания программ. В главе
кам явился своего рода революцией в практике
дается краткая история развития языков программирования от машинных
языков через языки ассемблера
к языкам высокого уровня. Рассказано об
истоках языка
вания
С. В
главе также представлено введение в среду программиро¬
С.
Глава 2, «Введение в программирование на С», дает общее представление о
том, как пишутся программы на С. Приводится подробное описание принятия
решений
и
арифметических операций. После прочтения
главы
учащийся бу¬
дет понимать, как написать простую, но тем не менее законченную программу
на С.
Глава
3, «Структурное программирование»,
является, возможно, наибо¬
лее важной во всей книге,
особенно для студента, всерьез занимающегося
компьютерными дисциплинами. В ней вводится понятие алгоритмов (проце-
решения задач. Объясняется важность структурных методов для написа¬
ния понятных, простых в отладке и сопровождении программ, которые с боль¬
шей вероятностью могут правильно заработать с первой попытки. Вводятся
ДУР)
фундаментальные управляющие конструкции структурного программирова¬
ния, а именно последовательная, выбора (if и if/else) и повторения (while).
Объясняется
методика нисходящего пошагового уточнения, критическая для
написания хорошо структурированных программ. Обсуждается популярный
инструмент проектирования программ
структурный псевдокод. Методы и
подходы, изложенные в 3-й главе, приложимы к структурному программиро¬
ванию на любом языке, не только на С. Глава помогает учащемуся выработать
«хорошие привычки» в программировании, заранее подготавливая его к более
серьезным программным задачам в оставшейся части текста.
Глава 4, «Управление программой», уточняет понятия структурного про¬
граммирования и представляет дополнительные управляющие структуры. Де¬
тально исследуется повторение и сравниваются альтернативы циклов,
ляемых счетчиком, и циклов, управляемых контрольным значением.
управ¬
Вводит¬
for как удобное средство реализации циклов с управлением счет¬
Представляются структура выбора switch и структура повторения
do/while. Заканчивает главу обсуждение логических операций.
Глава 5, «Функции», обсуждает проектирование и построение програм¬
ся структура
чиком.
мных модулей. В С имеются стандартные библиотечные функции и функции,
определяемые программистом, возможности рекурсии и вызова по значению.
Методики, представленные
в
5-й главе, существенны для
написания и пони¬
мания преимуществ правильно структурированных программ,
ших программ и
вообще
того рода программного
ным программистам скорее всего придется
особенно боль¬
обеспечения, которое
разрабатывать
систем¬
приложений
ре¬
ального мира. В качестве эффективного средства для решения сложных задач
предлагается стратегия
«разделяй
и
властвуй»; функции
для
позволяют програм¬
мисту разбить сложную программу на более простые взаимодействующие ком¬
поненты. Студентам нравится заниматься случайными числами и моделирова¬
поэтому их увлекает раздел главы, обсуждающий игру в кости, где
изящно используются управляющие структуры. Глава также предлагает осно¬
вательное введение в рекурсию; приводится таблица, где перечислены приме¬
нием,
22
Как программировать на С
ры и упражнения на рекурсию (всего их 31), встречающиеся на протяжении
остальной части книги. В некоторых учебниках рекурсия вводится в ка¬
кой-либо из последних глав; но мы считаем, что эту тему лучше всего охваты¬
вать
постепенно,
на
в конце
упражнений
всем
5-й
ные задачи, например,
текста.
протяжении
В
объемистый
набор
из
39
главы включены некоторые классические рекурсив¬
Ханойская башня.
Глава 6, «Массивы», обсуждает структурирование данных в виде масси¬
вов, т.е. групп логически связанных элементов одного типа. В главе представ¬
лены многочисленные примеры как одномерных, так и двумерных массивов.
Структурирование данных повсеместно признается не менее важным момен¬
том разработки структурных программ, чем применение управляющих струк¬
тур. Примеры в главе исследуют различные распространенные операции над
массивами, печать гистограмм, сортировку данных, передачу массивов функ¬
циям и дают представление об анализе наблюдаемых данных. Следует отме¬
подробное рассмотрение
тить
метода двоичного поиска, как важнейшего шага
вперед по сравнению с линейным поиском.
чают
себя особенно много интересных
в
улучшение
методов
авиабилетов,
сортировки,
LOGO),
трудных
проектирование
введение в концепцию
нитой в качестве языка
Упражнения
и
в конце главы вклю¬
задач.
системы
Сюда
относятся
резервирования
«черепаховой графики» (ставшую
а также задачи
об обходе доски
знаме¬
конем и о вось¬
ферзях, где вводятся понятия эвристического программирования, столь
широко используемые в области исследования искусственного интеллекта.
Глава 7, «Указатели», представляет одну из самых мощных особенностей
ми
языка
С. Глава
обсуждает операции, применяемые с указателями,
арифметику указателей, взаимо¬
массивов, массивы указателей и указатели на функции. В
в деталях
вызов по ссылке, выражения с указателями,
указателей и
упражнений главы входит моделирование классического соревнования
между зайцем и черепахой, а также алгоритмы тасования и сдачи карт. Име¬
связь
число
специальный раздел, озаглавленный «Как построить свой собственный
компьютер». В нем объясняются принципы программирования на машинном
языке и представлен проект, включающий в себя проектирование и реализа¬
ется
цию компьютерного симулятора, позволяющего читателю писать и запускать
программы на
«машинном»
языке.
Этот раздел будет особенно интересен для
работает компьютер. Нашим студен¬
там очень нравится этот проект, и они часто предлагают и реализуют сущест¬
венные его усовершенствования; некоторые из них предлагаются читателю в
качестве упражнений. Другой специальный раздел имеется в главе 12, где чи¬
тех,
кто хочет понять, как на самом деле
тателю демонстрируется создание компилятора; машинный код, генерируе¬
мый компилятором, затем исполняется на симуляторе, разработанном в 7-й
главе.
В главе
8, «Символы
и строки»,
речь идет
ботки
нечисловых данных. В главу включен
ботки
символов
мые здесь,
и строк,
имеющихся
в
об основных принципах обра¬
подробный обзор функций обра¬
библиотеках С. Методики, обсуждае¬
широко используются при создании текстовых редакторов,
графских программ макетирования
и
приложений обработки
текстов.
типо¬
Глава
завершается интересным набором из 33 упражнений, исследующих обработку
Читателю понравятся задачи, где требуется писать лимерики и стоха¬
стические стихотворения, переводить английский текст на «свинячью ла¬
текстов.
семибуквенные слова, эквивалентные заданному номеру
осуществлять выравнивание текста, защищать чеки, записывать
сумму чека прописью, генерировать код Морзе, делать метрические преобра¬
тынь», генерировать
телефона,
23
Предисловие
зования и отправлять угрожающие письма с напоминаниями об уплате. Нако¬
нец, последнее упражнение предлагает даже использовать компьютерный сло¬
кроссворды!
Глава 9, «Форматированный ввод/вывод», рассатривает мощные возмож¬
ности функций printf и scanf. Мы обсуждаем различные детали форматирова¬
ния вывода с помощью printf, такие как округление значений с плавающей
точкой до заданного количества значащих цифр, выравнивание столбцов чи¬
сел, левое и правое выравнивание в поле, вставка литеральной информации,
принудительный вывод знака «плюс», печать начальных нулей, использова¬
варь для того, чтобы генерировать
ние
экспоненциальной
нотации, восьмеричных и шестнадцатеричных чисел и
шириной поля и точностью представления. Приводятся все esc-noследовательности функции printf для перемещения курсора, вывода специа¬
управление
льных символов и подачи звукового сигнала. Затем исследуются все возмож¬
ности
функции scanf,
ввода с помощью
форматного
включая ввод
ских типов данных и пропуск символов во входном потоке.
спецификаторы формата scanf для
специфиче¬
Мы обсуждаем все
чтения десятичных, восьмеричных, шест¬
плавающей точкой, символов и строк, а так¬
же для сканирования ввода на предмет поиска совпадений (или несовпадений)
с заданным набором символов. Упражнения главы демонстрируют практиче¬
ски все возможности форматирования в С.
надцатеричных
значений,
чисел с
Глава 10, «Структуры, объединения, операции
представляет широкое
поминают
«записи»
в
разнообразие
Паскале
и
других
языках
группируют элементы данных различных типов.
льзуются для
В
главе
лением
формирования файлов, состоящих
12 структуры
памяти
с
битами
из
область
для
Такое разделение
ричного хранения
удобных
создания
динамических
структур данных,
Объединения
памяти позволяет сократить
объему
данных. Перечисления
памяти или к пространству на
позволя¬
требования про¬
устройствах вто¬
представляют собой средство опре¬
в использовании символических констант; такие константы
помогают сделать программу
С
испо¬
памяти в разные моменты времени для данных раз¬
граммы к доступному
деления
на¬
они
информационных записей.
таких как связанные списки, очереди, стеки и деревья.
личных типов.
Структуры
программирования
В главе 11 структуры
в соединении с указателями и динамическим распреде¬
применяются
ют использовать
и перечисления»
важных элементов языка.
самодокументированной. Мощные
в плане манипуляции отдельными
битами
возможности
позволяют программистам писать
использующий низкоуровневые особенности оборудования. Программы
обрабатывать битовые последовательности, устанавливать или сбрасы¬
вать отдельные биты и хранить информацию более компактно. Эти черты С,
свойственные обычно только языкам ассемблера, чрезвычайно ценятся про¬
код,
могут
граммистами, пишущими системное обеспечение, например, операционные
системы или сетевое программное обеспечение. Главным примером этой главы
является
пересмотренная,
высокоэффективная
модель
тасования
и
сдачи
карт. Он дает преподавателю прекрасную возможность продемонстрировать,
что такое качество алгоритма.
Глава
11, «Обработка файлов», обсуждает
методы
обработки
текстовых
произвольном режимах доступа. Глава начина¬
ется с представления иерархии данных от отдельных битов к байтам, к полям,
к записям и, наконец, к файлам. Затем показан простой подход языка С к
файлов
в последовательном и
файлам
и потокам.
файлам обсуждается в контек¬
файлы открываются и закрыва¬
последовательный файл и затем чтение из
Последовательный доступ
сте трех программ, которые показывают, как
ются, как производится запись в
к
24
Как программировать на С
него.
Произвольный
грамм,
файл
доступ к
как,
файлам обсуждается йа примере четырех про¬
используя последовательный доступ, создать
дальнейшей работы
с произвольным доступом к нему, и как произ¬
показывающих,
для
водится последовательное чтение
файла
Четвертый
с произвольным доступом.
пример для демонстрации произвольного доступа комбинирует различные
приемы как последовательного, так и произвольного доступа к файлам, явля¬
ясь
законченной программой обработки транзакций. Студенты
на наших про¬
мышленных семинарах говорили, что после изучения материала по
файлов
обработке
они смогли писать серьезные программы, сразу оказывавшиеся полез¬
работе их организаций.
Глава 12, «Структуры данных»,
ными в
рассматривает создание динамических
структур. Глава начинается с представления структур, ссылающихся на себя,
и динамического
распределения памяти. Затем обсуждается, как создавать
поддерживать различные динамические структуры данных,
и
включая связан¬
Для каждого типа структур мы приво¬
работающие программы и показываем, что выводится в ре¬
зультате их работы. Глава 12 помогает студенту в совершенстве овладеть прие¬
мами работы с указателями. В главе имеется масса примеров с косвенной ад¬
ные списки, очереди, стеки и деревья.
дим законченные
и
ресацией
Одной из
двойной косвенной адресацией
проблем работы
зуально представить
Поэтому
особенно трудной конструкцией.
с указателями является то, что студентам трудно ви¬
себе структуры данных
и связи их узлов
друг с другом.
мы приводим иллюстрации, показывающие эти связи и последовате¬
которой они создаются. Пример с двоичным деревом завершает изу¬
указателей и динамических структур данных. Этот пример создает дво¬
ичное дерево, исключая при этом дублирование данных, и производит рекур¬
сивные обходы дерева с предварительной, порядковой и отложенной выбор¬
кой. Студенты получают настоящее удовольствие от изучения и реализации
этого примера. Особенное впечатление на них производит тот факт, что обход
дерева с порядковой выборкой печатает значения узлов в сортированном виде.
Завершает главу большое собрание упражнений. Важнейшие из них объеди¬
льность, в
чение
няются
в
специальном
разделе
«Как построить свой собственный
компиля¬
Упражнения проводят студента через процесс разработки программы
преобразования инфиксной нотации в постфиксную и программы постфикс¬
ной оценки. Затем мы модифицируем алгоритм постфиксной оценки так, что¬
бы он генерировал код машинного языка. Компилятор помещает этот код в
файл (используя методы главы 11). Затем студенты запускают машинный код,
генерируемый их компилятором, на программном симуляторе, построенном в
тор».
упражнениях
7-й
главы.
Глава 13, «Препроцессор», дает подробные описания директив препроцес¬
сора. Включена более полная информация по директиве #include, которая
приводит к включению перед компиляцией копии указанного
файла вместо
#define, создающей символические константы и мак¬
обсуждается условная компиляция, позволяющая программи¬
директивы, и директиве
росы.
В
главе
сту управлять исполнением препроцессорных директив и компиляцией кода
программы. Также описывается операция #, преобразующая свой операнд в
строку, и операция ##, выполняющая конкатенацию двух лексем. Представ¬
FILE
( LINE
). Наконец, обсуждается макрос assert
лены пять предопределенных символических констант
DATE
,
TIME
и
STDC
,
,
из
файла assert.h. Этот макрос является ценным инструментом те¬
стирования, отладки и верификации программ.
заголовочного
25
Предисловие
Глава 14, «Специальные вопросы», посвящена
довольно сложной темати¬
Раздел 14.2 по¬
файл и направить в
вход другой (конвейер),
ке, которая обычно не затрагивается во вводных курсах по С.
казывает, как можно переадресовать ввод программы на
файл
ее вывод, направить вывод
одной программы
на
присоединить вывод программы в конец существующего файла. Раз¬
14.3
описывает разработку функций со списками аргументов переменной
дел
длины. Раздел 14.4 показывает, как передать функции main аргументы
а также
командной строки. Раздел 14.5 говорит о компиляции программ, компоненты
которых распределены по различным файлам. Раздел 14.6 обсуждает регист¬
рацию с помощью atexit функций, которые должны вызываться при заверше¬
завершение исполнения программ вызовом функции
exit. В разделе 14.7 описываются модификаторы типа const и volatile. Раздел
14.8 показывает, как специфицируетСя тип числовых констант m средством
нии программы, а также
целых и с
суффиксов
файлы
и использование
пользовать
плавающей запятой. Раздел 14.9 обсуждает двоичные
файлов. В разделе 14.10 показано, как ис¬
временных
библиотеку обработки
Наконец, раздел
денные события.
сигналов, чтобы перехватывать непредви¬
14.11 посвящен созданию и использованию
функций calloc и realloc.
В первое издание книги мы включили одну главу, являвшуюся введением
С++ и объектно-ориентированное программирование. За время, прошедшее с
динамических массивов с помощью
в
тех пор, многие университеты решили дополнить свои курсы по С введением в
С++. Поэтому в новом издании мы расширили наше изложение С++ и объект¬
но-ориентированного программирования до семи глав; их материала достаточ¬
но для курса продолжительностью в один семестр.
Глава 15, «С++ как улучшенный С», представляет те новые элементы язы¬
ка, которые не являются
объектно-ориентированными. Они
направлены на со¬
написания
обычных,
вершенствование процесса
процедурно-ориентированных программ. В главе обсуждаются однострочные комментарии, потоковый
ввод/вывод, объявления, создание новых типов данных, прототипы функций
и проверки типа,
встроенные функции
(как
альтернатива
макросам),
парамет-
ры-ссылки, модификатор const, динамическое распределение памяти, аргу¬
менты по умолчанию, унарная операция разрешения области действия, пере¬
грузка функций, спецификации внешней связи и шаблоны функций.
Глава 16, «Классы
абстракция данных», дает читателю превосходную
абстракцию данных «правильно»
при посредстве язы¬
ка (С++), специально предназначенного для реализации абстрактных типов
данных (ADT). В последние годы абстракция данных стала главной темой
и
возможность изучить
вводных курсов
по программированию, использующих
Паскаль. Когда
мы пи¬
абстракции данных на
базе языка С, но вместо этого решили включить в нее подробное введение в
С++. Главы 16, 17 и 18 содержат серьезный материал по абстракции данных.
В главе 16 обсуждается реализация ADT как struct и как классов в стиле С++,
сали книгу, то рассматривали возможность изложения
доступ к элементам класса, отделение
ние
функций
доступа
и
интерфейса
от реализации, использова¬
сервисных функций, инициализация объектов с помо¬
щью конструкторов и их уничтожение с помощью деструкторов, присваива¬
ние путем поэлементного копирования, а также повторное использование про¬
граммного обеспечения.
Глава 17, «Классы: часть II» продолжает изучение классов
и абстракции
обсуждается создание и использование константных объектов,
функций-элементов, композиция
построение классов, фслю-
данных. В ней
константных
чающих
в качестве элементов
объекты других классов, дружественные функ¬
26
Как программировать на С
ции и классы, имеющие специальные права доступа к закрытым элементам
классов, указатель
this, позволяющий объекту
динамическое распределение памяти,
щие единую для всего класса
в
эволюции
результате
выразить самую сущность
ва или
очереди)
float
С++. Они
языка
абстрактного
и затем создавать
классы, итераторы,
одно из недавних
дополнений,
поя¬
позволяют программисту
типа данных
(например,
стека, масси¬
с минимальным количеством дополните¬
версии этого ADT для конкретных типов (таких, как стек int,
int и т.д.). По этой причине шаблоны классов часто на¬
льного кода
стек
свой собственный адрес,
информацию, контейнерные
и шаблоны классов. Шаблоны классов
вившихся
знать
статические элементы класса, содержа¬
или очередь
зывают параметризованными типами.
Глава 18, «Перегрузка операций», посвящена одному из наиболее попу¬
лярных разделов в курсах по С++. Студенты изучают ее с удовольствием. Они
находят, что материал главы прекрасно соответствует изложению
ных типов данных в главах
16
и
сообщить компилятору,
возможность
объектами
щие операции с
абстракт¬
17. Перегрузка операций дает программисту
как
новых типов.
следует
использовать
С++ заранее знает,
существую¬
как эти операции
применяются к объектам встроенных типов, таких, как целые, числа с плава¬
ющей точкой
для
него
и символы.
должен
Но допустим, что мы создаем новый класс строк. Что
знак «плюс»? Многие программисты используют
означать
плюс со строками, подразумевая их конкатенацию. В
нает, как «перегрузить»
данный
между двумя
в
строками
ции-операции», которая
знак таким
выражении,
образом,
этой
главе читатель уз¬
что если он
будет
компилятор генерирует вызов
выполнит конкатенацию этих строк.
В
стоять
«функ¬
главе рассмат¬
операций, ограничения на перегрузку, перегруз¬
ка операций функциями-элементами класса и дружественными функциями,
перегрузка одноместных и двуместных операций и операции преобразования
одного типа в другой. Особенностью главы является большое число разверну¬
риваются основы перегрузки
тых примеров, а именно примеров класса массивов, класса строк, класса даты,
(последние два отно¬
упражнениям).
Глава 19, «Наследование», имеет дело с одной из фундаментальных черт
всех объектно-ориентированных языков. Наследование является формой по¬
класса сверхдлинных целых и класса комплексных чисел
сятся к
вторного использования программного кода, когда новые классы можно
ми новыми
быст¬
свойства существующего класса и дополнив их необходимы¬
возможностями. В главе рассматриваются понятия базовых и про¬
ро получить,
взяв
изводных классов, защищенные элементы, открытое, защищенное и закрытое
наследование, непосредственные и косвенные
и деструкторов
конструкторов
наследования
в
в
базовых
конструировании
и
базовые классы, использование
производных классах
программного
и значение
обеспечения. Наследование
(отношение «является») сравнивается с композицией (отношение «имеет»), а
вводятся понятия отношений «использует» и «знает». В главе приво¬
также
дится несколько развернутых примеров.
В частности, один из них реализует
Заключает главу пример слож¬
иерархию классов точек, кругов и цилиндров.
ного
наследования
производный
особенности
языка
класс путем наследования
С++, позволяющей сформировать
атрибутов
и поведения нескольких
ба¬
зовых классов.
Глава 20, «Виртуальные функции и полиморфизм», посвящена другому
фундаментальному аспекту объектно-ориентированного программирования,
а именно полиморфному поведению. Когда многие классы связаны друг с
другом через наследование одному и тому же базовому классу, объект любого
27
Предисловие
из производных классов может рассматриваться как объект базового класса.
Это позволяет писать программы в довольно общем виде, не зависящем от
конкретного типа объектов производных классов. Новые виды объектов мо¬
обрабатываться той же самой программой, что делает программные сис¬
Полиморфизм позволяет устранить из программ слож¬
ную логику переключателей и сделать код более «прямолинейным». Менед¬
жер экрана игровой программы может, например, просто послать сообщение
типа «нарисовать» каждому объекту в связанном списке визуальных объек¬
гут
темы расширяемыми.
тов.
Каждый объект знает, как
объект, не модифицируя
новый
себя нарисовать. В программу можно ввести
ее, если новый объект тоже умеет нарисовать
разработ¬
интерфейсов пользовате¬
ля. В главе рассматривается механизм реализации полиморфного поведения
посредством виртуальных функций. Проводится различение абстрактных
классов (которые не могут иметь представителей) и конкретных классов
(объекты-представители которых можно создавать). Абстрактные классы по¬
лезны при создании наследуемого интерфейса для всей иерархии классов. В
система начис¬
главе разбираются два больших примера на полиморфизм
ления заработной платы и вторая версия иерархии точек, кругов и цилинд¬
ров, рассмотренной в главе 19.
себя. Такой способ программирования чаще всего используется при
ке
чрезвычайно
популярных сегодня графических
Глава 21, «Потоки ввода/вывода
ное описание нового,
Многие
в
в С++», содержит чрезвычайно подроб¬
объектно-ориентированном стиле, ввода/вывода С++.
курсы по С проводятся с использованием компиляторов С++,
и препо¬
предпочитают учить вводу/выводу в новом стиле, не пользуясь
старыми функциями printf/scanf. В главе рассматриваются различные аспек¬
ты ввода/вывода С++, куда входит вывод с применением операции помещения
даватели часто
в поток и ввод с применением операции извлечения из потока, безопасный по
ввод/вывод (большое улучшение по сравнению с С), форматируемый и не
типу
форматируемый (в целях эффективности) ввод/вывод, манипуляторы потока
для управления основанием (десятичное, восьмеричное или шестнадцатерич¬
ное основание), представлением чисел с плавающей точкой и шириной поля
вывода; определяемые пользователем манипуляторы, состояния
ошибки пото¬
ка, ввод/вывод объектов определяемого пользователем
привязка
типа
и
вы¬
действительно появля¬
лись на экране перед тем, как пользователь должен дать на них ответ).
В нескольких приложениях приводится ценный справочный материал. В
частности, в приложении А мы представляем сводку синтаксиса С; сводка
всех функций стандартных библиотек С приводится в приложении Б; таблица
приоритетов и ассоциации операций дана в приложении В; приложение Г со¬
держит таблицу ASCII-кодов символов; и, наконец, в приложении Д проводит¬
ся обсуждение двоичной, десятичной, восьмеричной и шестнадцатеричной си¬
ходных потоков ко входным
(для
того, чтобы запросы
В основу приложения Б положен, с письменного разрешения
института национальных стандартов, документ стандарта
ANSI; приложение является детальным, очень ценным для практического
стем счисления.
Американского
программиста материалом.
Приложение Д
является
учебным пособием
по сис¬
темам счисления, в которое входят контрольные вопросы и упражнения.
Мы
в
полной мере берем
на
себя
ответственность за недостатки и неточно¬
книге. Мы с благодарностью примем
ваши замечания, критику, исправления и предложения по улучшению текста.
сти, которые могут встретиться в
нашей
Пожалуйста, шлите нам свои предложения и дополнения к рубрикам Хоро¬
ший стиль программирования, Распространенные ошибки программирова¬
Как программировать на С
28
эффективности, Советы по переносимости кода
замечания.
методические
Общие
Направляйте всю корреспонденцию на наш адрес электронной почты:
ния9 Советы по повышению
и
deitel0world.std.com
либо
пишите нам:
Harvey М. Deitel (author)
Paul J. Deitel (author)
с/о Computer Science Editor
College Book Editorial
Prentice Hall
Englewood Cliffs, New Jersey 07632
Мы немедленно вам ответим.
Харви М. Дейтел
Пол Дж. Дейтел
г
л
в
а
а
i
Принципы машинной
обработки
данных
Цели
Понять основные принципы
организации
компьюте¬
ров.
Познакомиться с различными типами языков програм¬
мирования.
Познакомиться с
историей
Получить представление
языка
С.
стандартной библиотеке
о
Понять принципы среды
разработки
Оценить
как языка для
обучения
возможности
С
программ
С.
на
С.
начального
программированию.
Оценить преимущества С
как базы для дальнейшего
изучения программирования вообще и языка С++
в
частности.
Глава 1
30
Содержание
1.1.
Введение
1.2.
Что такое компьютер?
1.3.
Внутренняя организация компьютера.
1.4.
Пакетная
1.5.
Персональные вычисления, распределенные
ния
1.6.
в
обработка,
модели
мультипрограммирование
клиент/сервер
ассемблера
Машинные языки, языки
1.7.
История
1.8.
Стандартная библиотека
1.9.
Другие
языка
языки
и
и
разделение времени
вычисления
языки
высокого
и
вычисле¬
уровня.
С
высокого
С
уровня
1.10. Структурное программирование
1.11. Основные принципы среды С
1.12. Общие
замечания
1.13. Concurrent
1.14.
С
о С
и
об этой
книге
(Параллельный С)
Объектно-ориентированное
программирование
и
С++
стиль программирования
Советы по переносимости
Советы по повышению эффективности
Упражнения для само¬
Ответы к упражнениям для самоконтроля
контроля
Упражнения Ре¬
комендуемая литература
Хороший
Резюме
программ
1.1. Введение
Итак, уважаемый
читатель,
С! Авторы потратили
на языке
добро
пожаловать
в мир программирования
на эту книгу немало труда и надеются, что чте¬
ние ее окажется для вас не только полезным, но и занимательным.
Поэтому
С
является
который обычно изучают только опытные программисты.
книга в ряду учебников по С является уникальной:
языком,
трудным
данная
Она подойдет техническим специалистам, которые
либо имеют небольшой опыт программирования.
Она пригодится
кое
и
строгое
и
опытным программистам,
изложение
языка.
не
которым
имеют
вообще,
требуется глубо¬
Принципы машинной обработки
31
данных
Возникает вполне естественный вопрос: каким
на удовлетворить
потребности
образом одна книга способ¬
групп'читателей? Ответ заклю¬
двух различных
чается в том, что в книге сделан упор на достижение ясности написания про¬
апробированного метода «структурного программирова¬
Непрограммисты получают возможность изучать программирование
граммы посредством
ния».
«правильным» образом, т.е. начиная с азов. Мы попытались написать книгу
в
иллюстраций. И что, воз¬
можно, наиболее важно, книга снабжена большим количеством работающих
С-программ с образцами выдачи при запуске их на компьютере. Первые четы¬
ре главы вводят фундаментальные принципы процесса вычислений, компью¬
ясной манере
со множеством
и
С. Введение
простой
и достаточно
терного
программирования
языка
в
программирование
пред¬
структурного подхода. Новички, пользовавшиеся нашим
курсом, высказывают мнение, что материал этих глав представляет собой со¬
лидную фундаментальную базу для углубленного изучения С в последующих
главах с 5 по 14. Опытные программисты обычно быстро прочитывают первые
ставлено
в
аспекте
четыре главы и находят, что изложение материала в главах с 5 по 14 достаточ¬
но строгое и в то же время интересное. Они особенно отмечают детальное опи¬
сание в этих главах указателей, строк, файлов и структур.
Многие
опытные программисты оценили наше изложение
структурному
программированию.
структурном языке типа
Pascal,
В
основном
они
а поскольку они никогда
ли структурное программирование, то соответственно и
был
После изучения С по нашей
свой стиль программирования.
не самым лучшим.
можность улучшить
лезной
интересной информации.
Большинство людей так или иначе
формально
на
не изуча¬
написанный
ими код
книге они получили воз¬
Поэтому
здесь он
татель новичком, либо опытным программистом,
материала по
программировали
является ли чи¬
найдет
много по¬
и
знакомы с теми удивительными веща¬
ми, которые могут делать компьютеры.
В данном курсе
читатель узнает, как
Компьютерами, т.е. аппа¬
ратной частью (hardware) управляет программное обеспечение (software), а
одним из наиболее популярных на сегодняшний день языков для разработки
программного обеспечения является С. Данная книга описывает стандартизо¬
подавать компьютеру команды для их выполнения.
ванную версию языка С, принятую
в качестве стандарта в
1989 году Амери¬
(American National Standards
Instutute
ANSI) и Международной организацией стандартов (International
Standards Organization
ISO).
канским институтом национальных стандартов
В настоящее время
почти во всех отраслях
человеческой деятельности
более расширяется. На фоне
вычислительной техники драматически
пользование компьютеров все
постоянно
щих цен стоимость
падает
ис¬
расту¬
благодаря
значительным успехам как в
и в развитии
программного обеспечения.
занимали не¬
разработке аппаратной части, так
Компьютеры, которые 25 лет назад
сколько комнат и стоили миллионы долларов, сегодня помещаются в несколь¬
ких кремниевых микросхемах стоимостью по несколько долларов каждая. По
иронии судьбы
Земле
кремний
это ингредиент
является
наиболее распространенным материалом на
песка. Технология производства кремние¬
обычного
вых
микросхем
ной,
что в настоящее время в мире насчитывается
онов
сделала
компьютеров
промышленности,
компьютеризацию
общего
экономически
выгод¬
приблизительно 150
милли¬
назначения, которые помогают людям в
политике и в
число вполне может
настолько
повседневной
быть удвоено.
жизни.
Через
бизнесе,
несколько лет это
32
Глава 1
Можно ли изучать С как
первый
язык программирования, пользуясь, на¬
данной книгой? Мы полагаем,
что да. Два года назад, когда в качестве
Pascal, мы решилипопробовать. Была написана
«Как программировать на С», первое издание текста, который вы сей¬
пример,
первого языка люди изучали
книга
час
читаете.
Сотни университетов
Было доказано,
по
всему
миру
использовали
эту
книгу.
курсы на ее основе одинаково эффективны с пред¬
шествующими курсами, ориентированными на Паскаль. Не было замечено
значительной разницы за исключением того, что у студентов появилась боль¬
что
учебные
шая мотивация для изучения языка, потому что они знают, что вероятнее все¬
го в своей профессиональной деятельности им придется программировать на
С,
Паскале. Студенты, изучающие С,
а не на
также великолепно понимают,
будут более
подготовлены для последующего изучения С++. С++ является
«надмножеством» языка С, предназначенным для программистов, пишущих
что
объектно-ориентированные
в разделе
программы. Более
подробно
о
С++
мы расскажем
1.14.
Действительно, сегодня С++ привлекает настолько большое внимание,
15 (и далее) мы даем подробное введение в С++ и объектно-ориен¬
что в главе
Интересным явлением на рынке языков про¬
факт, что ведущие производители чаще постав¬
объединенный продукт С/С++, нежели эти языки по отдельности. Это
тированное программирование.
граммирования является тот
ляют
дает
при
возможность пользователям продолжать программирование на
необходимости,
Итак, перед
постепенно
на
itel@world.std.com. Мы,
в свою
электронной
С,
а затем,
С++.
вами книга по программированию на
тель может связаться с нами по
тить на каждое
перейти
С. При желании чита¬
Internet по адресу de-
почте через
очередь, сделаем все возможное, чтобы отве¬
сообщение.
1.2. Что такое
компьютер?
это устройство, способное производить вычисления, а так¬
Компьютер
же выполнять действия по принятию логических решений со скоростью в
миллионы или даже миллиарды раз
быстрее,
чем человек.
Например, боль¬
шинство современных персональных компьютеров могут выполнять десятки
арифметических операций в секунду. Человеку с калькулятором в
потребуется несколько месяцев, чтобы выполнить такой же объем рабо¬
ты, какой персональный компьютер производит за одну секунду. (Вопрос: как
вы узнаете, правильно ли человек складывает цифры? Как вы узнаете, прави¬
льно ли компьютер складывает цифры?) Сегодняшние самые быстрые супер¬
то
компьютеры могут выполнять сотни миллиардов сложений в секунду
есть такое количество арифметических операций под силу выполнить лишь
сотням тысяч человек в течение года! А в исследовательских лабораториях
уже работают компьютеры с быстродействием триллион операций в секунду.
Компьютеры обрабатывают данные, используя наборы инструкций, назы¬
миллионов
руках
ваемые
компьютерными программами, которые
заставляют
компьютер
вы¬
действия
заранее установленном порядке. Действия
определяются людьми, которых называют программистами.
Различные устройства (такие как клавиатура, экран, диски, память и
полнять определенные
в
микропроцессор), составляющие компьютерную систему, называются аппа¬
ратной частью. Программы, выполняющиеся на компьютере, называются
программным обеспечением. Стоимость аппаратуры в последние годы значите¬
Принципы машинной обработки
льно упала,
ления.
благодаря
К сожалению,
33
данных
чему компьютеры стали доступным предметом
обеспечения
стоимость программного
разрабатывают
поскольку программисты
При
кладные программы.
этом в
самой
все
потреб¬
постоянно растет,
более мощные
и
сложные
при¬
разработки программного
улучшений. В данной книге рассмат¬
технологии
обеспечения не наблюдается адекватных
риваются методы разработки программ, которые могут существенно сокра¬
тить время и затраты на создание современных программных продуктов высо¬
кого качества. Эти методы включают в себя структурное программирование,
пошаговое
нисходящее уточнение, разбиение на функции и описанное
объектно-ориентированное программирование.
в
по¬
следнем разделе
1.3.
Внутренняя организация компьютера
Фактически каждый компьютер,
вне зависимости от того,
как он выгля¬
дит, можно представить в виде совокупности шести логических
1. Входной блок. Это «принимающая»
формацию (данные
устройств ввода
информация
часть компьютера.
компьютерные
Он получает
программы)
от
ин¬
различных
блоки, так что
дальнейшем передана на обработку. Часто
информации поступает в современные компьютеры че¬
и помещает ее для хранения в другие
может
основная масса
рез
и
блоков. Это:
клавиатуру,
быть
в
напоминающую
пишущую
машинку.
2. Блок вывода. Он воспринимает обработанную компьютером информа¬
цию и помещает ее на различные устройства вывода так, чтобы сде¬
лать ее
доступной для использования вне компьютера. На сегодняшний
информация обычно выводится на экран либо распечатывается на
бумаге.
день
3. Блок
памяти.
Это хранилище информации, характеризуемое высокой
и относительно низкой емкостью. Блок памяти хра¬
скоростью доступа
нит
информацию, полученную
медленно предоставляет ее для
от
блока ввода,
и при
обработки. Здесь
необходимости
не¬
также содержится ин¬
в результате вычислений, до того момента, пока
она не будет направлена блоком вывода на устройства вывода. Блок па¬
формация, полученная
мяти называют просто памятью, или
первичной
4.Арифметико-логическое устройство (АЛУ).
часть компьютера.
ческих
операций,
Она
памятью.
Это «перерабатывающая»
несет ответственность за выполнение
таких как сложение,
арифмети¬
вычитание, умножение и деле¬
ние. В ней также содержатся механизмы принятия решений, которые
позволяют компьютеру, например, сравнить два числа из блока памяти
и определить, равны они или нет.
5.Центральное
процессорное устройство
(ЦПУ). Это «административ¬
работы остальных
ная» часть компьютера, являющаяся координатором
блоков. Устройство центрального процессора направляет команду вход¬
ному устройству поместить информацию в блок памяти, устройству
арифметики
блоку
вода.
2
Зак 801
и
вывода
логики
использовать
информацию
вычислений,
устройство вы¬
для
отправить информацию на конкретное
34
Глава 1
6. Блок
вторичного
компьютера.
но другими
хранения данных.
Программы
Это долговременное
блоками, обычно помещаются
в
устройства
вторичного хра¬
нения
(например, диски),
снова,
возможно, через час, день, месяц или даже год.
1.4. Пакетная
хранилище
или данные, которые не используются актив¬
понадобятся
до того момента, пока они не
обработка,
и
мультипрограммирование
разделение времени
Раньше компьютеры
могли решать в каждый момент времени только одну
Подобная форма работы называется однопользовательской пакетной
обработкой. Компьютер обрабатывает данные группами, или пакетами, при¬
чем в каждый момент времени выполняется только одна программа из
задачу.
группы.
В
то время пользователи представляли свои программы в вычислите¬
льные центры в виде
ной выдачи
набора перфокарт
и вынуждены
часами или даже в течение нескольких
были ждать
окончатель¬
дней.
По мере совершенствования компьютеров становилось
обработка редко использует
очевидным, что од¬
ресурсы компьюте¬
ра эффективно. Возникла идея, что несколько программ, или задач, могут раз¬
делять между собой ресурсы компьютера, чтобы его загрузка была более ра¬
нопользовательская пакетная
циональной. Это называется мультипрограммированием. Мультипрограмми¬
рование подразумевает «одновременное» выполнение на компьютере несколь¬
ких задач; компьютер разделяет свои ресурсы между задачами, на них претен¬
дующими. Но как и прежде, пользователи представляли свои программы на
перфокартах и ждали свои результаты по несколько часов.
И вот в шестидесятых годах в нескольких промышленных центрах и уни¬
верситетах возникла новаторская концепция
это
времени
разделения
ватели получают доступ к компьютеру через
терминалы.
В типичной компьютерной
ходиться несколько сотен
но.
Процессор
выполняет
пользователей, решающих свои
часть одной задачи,
небольшую
компьютер возвращается
с точки
1.5.
к
одной
и
зрения пользователей
той
когда пользо¬
устройства ввода/вывода,
или
системе разделения времени может на¬
ся на другую. Это происходит настолько
зом,
времени. Разделение
специальный случай мультипрограммирования,
быстро,
задачи одновремен¬
затем переключает¬
что в течение
одной секунды
же задаче несколько раз.
их задачи
Таким обра¬
решаются одновременно.
Персональные
вычисления, распределенные
и
вычисления
вычисления в модели
клиент/сервер
В 1977 году благодаря компании Apple Computer родился феномен персо¬
нальных вычислений. Вначале это было лишь мечтой одержимых программистов-любителей. Компьютеры становились экономически доступными насто¬
лько,
чтобы
1981 году
люди
покупали
компания
ла на рынке
их
для
решения
IBM, мировой лидер
своих
персональных
задач.
В
по продаже компьютеров, представи¬
Персональный Компьютер IBM. Буквально за одну ночь персона¬
приобрели законное право на свое применение в бизнесе,
льные компьютеры
промышленности
и
государственных учреждениях.
Принципы машинной обработки
35
данных
были изолированы друг от друга, т.е. были
компьютерами-одиночками. Люди выполняли работу на своих собственных
машинах, а затем передавали друг другу диски для обмена информацией. Не¬
Однако
смотря
эти
компьютеры
на то, что ранние персональные компьютеры
были
не настолько мощ¬
чтобы обеспечивать разделение времени для нескольких пользовате¬
эти
машины могли быть объединены в компьютерные сети посредством
лей,
ными,
в локальные сети в пределах одной организации. Это
привело к появлению феномена распределенных вычислений, когда работа
всей организации, вместо того, чтобы быть выполненной непосредственно на
телефонных линий или
центральном компьютере, распределялась по сети на
рабочие
места, на кото¬
Персональные компьютеры были достаточ¬
но мощными, чтобы удовлетворить требования индивидуальных пользовате¬
лей, а также справляться с задачами по электронному обмену информацией.
рых и производились все расчеты.
Современные персональные компьютеры обладают такой же вычислитель¬
ной мощностью, как и машины стоимостью в миллион долларов десятилетней
обеспечивают ин¬
давности. Наиболее мощные машины
рабочие станции
дивидуальных
пользователей
невиданными
возможностями.
Информация
легко распределяется по компьютерным сетям, в которых отдельные компью¬
теры,
серверами, хранят общий для
называемые
всех
набор
ных, которые могут быть использованы компьютерами
щими в состав сети.
С++ стали
программ и дан¬
клиентами, входя¬
Отсюда происходит термин клиент/сервер. Языки С
и
теми языками программирования, на которых пишут программное
обеспечение для операционных систем, компьютерных сетей
ных программ модели
и для
приклад¬
клиент/сервер.
1.6. Машинные языки, языки
высокого
ассемблера
и языки
уровня
Программисты
пишут программы на различных языках программирова¬
ния, некоторые из которых непосредственно понятны компьютеру, а другие
нуждаются в промежуточной стадии трансляции. Сотни имеющихся языков
могут быть подразделены на три
1. Машинные
языки
высокого
Каждый компьютер
рый
типа:
языки
2. Ассемблерные
3. Языки
общих
уровня
может понимать только
свой машинный язык, кото¬
Он тесно связан
является естественным языком конкретного компьютера.
с его аппаратной частью. Машинные языки в общем случае состоят из после¬
довательностей чисел (обычно нулей и единиц), которые являются командами
операций. Машинные языки явля¬
конкретный машинный язык может быть ис¬
на выполнение одиночных элементарных
ются машинно-зависимыми, т.е.
пользован только с определенным типом компьютера.
добны
Машинные
языки неу¬
для восприятия человеком, что можно проиллюстрировать небольшой
программой, в которой к тарифной ставке прибавляются выплаты за сверх¬
урочную работу, а результат сохраняется в переменной общей выплаты.
2*
36
Глава 1
+
1300042774
+
1400593419
+
1200274027
По мере распространения компьютеров становилось очевидным,
граммирование на машинных языках тормозит развитие
является
ники,
непосильным
очень
тех¬
большинства
программистов
Вместо последовательностей чисел, непосредственно
медленным
занятием.
понятных компьютеру,
и
что про¬
компьютерной
для
программисты для представления элементарных опе¬
аббревиатуры, которые и сформирова¬
ли основу языков ассемблера. Для преобразования программ, написанных на
таких языках, в машинный язык были разработаны программы-транслято¬
раций
стали применять англоязычные
называемые ассемблерами. Преобразование происходило со скоростью,
равной быстродействию компьютера. Нижеприведенный фрагмент программы
на языке ассемблера также вычисляет основную стоимость в виде суммы та¬
рифной ставки и сверхурочных. При этом программа становится более понят¬
ры,
ной,
чем аналогичная на машинном языке:
LOAD
BASEPAY
ADD
OVERPAY
STORE
GROSSPAY
С
ассемблера использование компьютеров значитель¬
требовалось написание большого количества
инструкций даже для реализации решения простейших задач. Для ускорения
процесса программирования были разработаны языки высокого уровня, в ко¬
торых для выполнения сложных действий достаточно написать один опера¬
тор. Программы для преобразования последовательности операторов на языке
появлением языков
но расширилось, однако все еще
высокого уровня в машинный язык называются компиляторами. В языках
высокого уровня инструкции, написанные программистами, зачастую выгля¬
дят как обычный текст на английском языке с применением общепринятых
Уже
математических знаков.
знакомое нам вычисление
суммарной
выплаты
выглядит так:
grossPay
=
+
basePay
overPay
Совершенно очевидно, что с
уровня более предпочтительны,
точки зрения программистов языки высокого
чем
любые
машинные или
ассемблерные
язы¬
ки.
1.7.
История
языка
С
Язык С берет свое начало от двух языков, BCPL и В. В 1967 году Мартин
Ричардс разработал BCPL как язык для написания системного программного
обеспечения и компиляторов. В 1970 году Кен Томпсон использовал В для со¬
здания
версий операционной системы UNIX на компьютере DEC
BCPL, так и в В переменные не разделялись на типы
каждое
ранних
PDP-7. Как
в
значение данных занимало одно слово в памяти и ответственность за различе¬
ние, например, целых и
действительных
чисел
целиком ложилась на плечи
программиста.
Язык С был разработан (на основе В) Деннисом Ричи из Bell Laboratories и
был реализован в 1972 году на компьютере DEC PDP-11. Известность
впервые
С получил
в качестве языка
операционной
системы
UNIX. Сегодня практиче¬
С и/или С++. По проше¬
ски все основные операционные системы написаны на
Принципы машинной обработки
ствии двух
Язык С
десятилетий С
не зависит от
37
данных
имеется в наличии на
аппаратной
большинстве компьютеров.
части, и программы, написанные на нем, мо¬
гут быть перенесены на многие системы. С сочетает в себе основные принципы
языков BCPL и В; кроме того, в нем введена типизация переменных и некото¬
рые другие важные моменты.
В конце 70-х годов С превратился в то, что мы называем
С». Публикация
в
1978 году
С» привлекла широкое
ния
мых удачных среди
изданий
книги
Кернигана
и
внимание к языку.
«традиционный
Ричи «Язык программирова¬
Эта книга стала одной из са¬
по компьютерным дисциплинам, выпущенных в
свет.
Применение С
для разных типов компьютеров
ратными платформами) привело
которые,
несмотря
на
свою
(иногда
называемых аппа¬
к появлению различных вариантов языка,
схожесть,
были
зачастую
несовместимыми.
Это
серьезной проблемой для разработчиков программных продуктов, ко¬
торым требовалось разрабатывать коды, способные работать на нескольких
платформах. Становилось ясно, что нужна стандартная версия С. В 1983 году
Американским комитетом национальных стандартов в области компьютеров и
обработке информации (X3) был учрежден технический комитет X3J11, перед
которым ставилось целью выработать «однозначное и машинно-независимое
определение языка С». В 1989 году стандарт был одобрен. Документ известен
как ANSI/ISO 9899:1990. Копии могут быть заказаны через Американский
явилось
институт национальных стандартов,
Второе
издание книги
эту версию
Кернигана
и
С, называемую ANSI С
адрес которого указан в предисловии.
Ричи выпущено
в
1988 году
и широко используемую в
и описывает
мире
(Ke88).
Совет по переносимости программ 1.1
что С является машинно-независимым и широко доступным языком,
прикладные программы, написанные на С, могут работать практически без измене¬
ний на большинстве компьютерных систем.
Благодаря тому,
1.8.
Как
лей,
сать
Стандартная библиотека
С
5, программы, написанные на С, состоят из моду¬
функциями. Пользователь сам может напи¬
создания программы функции, однако большинство
вы узнаете в главе
фрагментов,
необходимые для
называемых
или
программистов предпочитают применять уже существующие, которые нахо¬
стандартной библиотеке С. Таким образом, есть два аспекта изуче¬
умение пользо¬
Первый
изучение непосредственно С и второй
ваться стандартной библиотекой. В данной книге рассматриваются многие
функции из стандартной библиотеки, все они приводятся в приложении Б
дятся в
ния языка.
(так,
как
их
граммистам,
определяет
желающим
ANSI С). Книга Плогера (P192) будет
приобрести глубокие
знания
и
полезна про¬
понимание
работы
стандартных функций, их применения при написании компактных кодов.
В данном курсе читателю будет предложен блочный метод построения
программы. Не следует изобретать велосипед. Применение готовых частей на¬
зывается
повторным
использованием
кода
это
ключевой
момент в развива¬
ющейся сейчас области объектно-ориентированного программирования (гла¬
ва 15). В языке С при формировании программы вы будете использовать сле¬
дующие
«строительные
блоки»:
38
Глава 1
Функции стандартной библиотеки С
Функции,
которые вы создадите сами
Функции,
написанные
Преимущество
другими
людьми
создания своих собственных
отлично знаете и понимаете, как они
кода С.
функций состоит
работают. У вас появится
работой
Ну
потребуются для создания новых функций.
При использовании существующих функций
проследить за
а недостаток
в том, что вы
возможность
временные затраты, которые
не приходится
изобретать
ве¬
В случае применения стандартных функций ANSI, которые, как вы
очень тщательно написаны, ваши программы с большей вероятностью
лосипед.
знаете,
могут быть перенесены
на другие типы компьютеров.
Совет по повышению
эффективности 1.1
Использование функций стандартной библиотеки ANSI вместо написания собствен¬
ных аналогичных функций может улучшить характеристики программы, т к стандар¬
тные функции написаны весьма тщательно и работают очень эффективно
Совет
по
переносимости программ 1.2
Использование функций стандартной библиотеки ANSI вместо написания собствен¬
ных аналогичных функций может способствовать переносимости программ, т к стан¬
дартные функции реализованы практически во всех существующих вариантах С
1.9.
Несмотря
лько
Другие
на то, что
некоторые
из
языки высокого
были разработаны
них
нашли
широкое
уровня
сотни языков высокого уровня, то¬
применение.
TRANslator) разработан фирмой IBM между 1954
вания в научных и инженерных программах,
и
FORTRAN (FORmula
1957 годами для
требующих
использо¬
сложных математи¬
вычислений. FORTRAN широко применяется и до сих пор.
COBOL (COmmon Business Oriented Language) разработан в 1959 году груп¬
пой, в которую входили производители и пользователи компьютеров. COBOL
ческих
главным образом используется в коммерческих прикладных программах, ког¬
да необходима высокая точность при обработке большого количества данных.
Сегодня больше
половины программного
использования в
бизнесе,
человек в настоящее время
Pascal был создан
лее
зарабатывают
1.10.
COBOL. Свыше
для
миллиона
на жизнь, программируя на
COBOL.
С. Он предназначен для ака¬
остановимся на языке Pascal бо¬
почти в то же время, что и
целей. В следующем разделе
подробно.
демических
обеспечения, предназначенного
все еще написано на языке
мы
Структурное программирование
В шестидесятых годах при разработке программного обеспечения про¬
граммисты испытывали серьезные затруднения. Время разработки обычно за¬
тягивалось свыше установленных сроков, стоимость
шала
бюджет,
а
окончательный продукт был
работ
значительно превы¬
весьма ненадежен.
Люди
начали
Принципы машинной обработки
39
данных
разработка программного продукта является делом более слож¬
ным, чем они себе представляли. В результате научных исследований, прове¬
денных в шестидесятых годах, возникла концепция структурного програм¬
мирования> т.е. такого подхода к написанию программ, при котором они ста¬
понимать, что
новятся более ясными для понимания, более корректными и лучше приспо¬
собленными для модификации в дальнейшем. В главах 3 и 4 приводится обзор
принципов структурного программирования. Ниже мы остановимся лишь на
развития структурного программирования на С.
Одним из знаменательных результатов научных исследований в области про¬
граммирования явилось создание в 1971 году профессором Никласом Виртом
обсуждении
языка программирования
Pascal. Pascal, названный
по имени математика и
фи¬
семнадцатого века Блеза Паскаля, был разработан для обучения струк¬
турному программированию в академических кругах и быстро распространился
в университетах для изучения в качестве первого языка программирования. К
лософа
сожалению, в нем отсутствуют свойства, благодаря которым его можно было бы
использовать в коммерческой и индустриальной сферах деятельности и, как
следствие, в этих областях он не получил широкого распространения.
Однако
можно отметить, что реальное значение этого языка в том, что он лег в основу
Ada. Разработка языка Ada финансировалась Мини¬
начале 80-х годов. Для обслуживания
стерством обороны США в конце 70-х
огромного комплекса МО использовались сотни различных языков. Для их заме¬
ны было принято решение создать один язык программирования, который бы
языка программирования
удовлетворял всем требованиям. Для этой цели был выбран Pascal, однако конеч¬
ный продукт
Ada
значительно от него отличается. Новый язык программи¬
был назван
по имени леди Ады Лавлейс, дочери поэта лорда Байрона.
Ада Лавлейс в начале 1800-х написала первую в мире компьютер¬
ную программу. Одной из основных черт языка Ada является многозадачность,
которая позволяет нескольким процессам протекать параллельно. Остальные
языки высокого уровня, которые мы обсуждали, включая С и С++, допускают
выполнение в данный момент только одной задачи.
рования
Считается,
что
1.11.
Все С-системы
Основные принципы среды С
общем состоят из трех частей: среды программирования,
стандартной библиотеки С. В процессе подготовки С-программы проходят через шесть стадий (рис. 1.1). Это: редактирование, npenpoцессорная обработка, компиляция, компоновка, загрузка и исполнение. Здесь
мы имеем в виду типичную UNIX-систему С. В случае если читатель использу¬
ет другую операционную систему, ему следует обратиться к соответствующему
собственно
в
языка и
справочному
руководству
или к
более опытному коллеге
за разъяснениями,
как эти задачи выполняются в его среде.
Первая фаза состоит из редактирования файла при помощи программы-рев которой программист набирает программу и производит необходи¬
дактора,
мые исправления. Затем программа сохраняется на вторичном запоминающем
например, диске. Имена файлов, содержащих программы на С,
устройстве,
должны иметь расширение .c. В системах UNIX широко используются два ре¬
дактора
vi
и emacs.
Пакеты С/С++, такие, как Borland С++ для IBM и со¬
компьютеров или Symantec С++ для Apple Macin¬
вместимых персональных
tosh, обладают
встроенными редакторами, интегрированными в среду про¬
граммирования. Мы предполагаем, что читатель знает, как отредактировать
программу.
Глава 1
40
Этап 1:
Программа создается
редактором
Редактор
и запоминается
на диске
Этап 2:
Этап 3:
Программа
предварительной
обработки
преобразовывает
Препроцессор
Компилятор создает
объектный код
и сохраняет его
на диске
Компилятор
Диск
Этап 4:
код
Компоновщик связывает
объектный код
с библиотеками,
создает a.out
и сохраняет его
на диске
Компоновщик
Первичная
память
Этап 5:
Загрузчик
Загрузчик размещает
программу в памяти
Диск
Первичная
память
ЦПУ
Этап 6:
ЦПУ выбирает каждую
инструкцию и выполняет
ее, возможно сохраняя
новые значения данных
по мере выполнения
программы
Рис. 1.1. Типичная
Далее,
среда С
программист дает команду на компиляцию программы. Компиля¬
С-программу в код
кодом). В С-системе
(который
также называ¬
тор транслирует
машинного языка
ют объектным
перед началом фазы трансляции автомати¬
чески выполняется программа препроцессора.
Препроцессор С
подчиняется
Принципы машинной обработки
41
данных
директивам препроцессора, которые определяют,
какие действия должны быть выполнены перед компиляцией программы.
Обычно это добавление других файлов к компилируемому и замена специаль¬
специальным командам
ных символов в тексте программы.
описываются в
13. Препроцессор
глава
священа
В общих чертах директивы препроцессора
начальных главах книги,
перед тем, как начнется
а детальному их рассмотрению по¬
автоматически
преобразование
запускается
программы
в
компилятором
машинный
язык.
Четвертая фаза называется компоновкой. Типичные С-программы содер¬
жат обращения к функциям, чьи определения находятся либо в стандартных
библиотеках, либо в библиотеках, созданных группой пользователей в процес¬
се работы над конкретным проектом. То есть объектный код, получившийся в
результате работы компилятора, содержит «дыры», обусловленные отсутстви¬
ем определений функций, к которым происходит обращение. Компоновщик
связывает объектный код с определениями отсутствующих функций, т.е. вы¬
рабатывает исполняемый образ (в котором уже нет отсутствующих частей).
Например, для компиляции и компоновки программы с именем welcome.c
следует после подсказки UNIX напечатать
cc
welcome.c
и нажать
вится
клавишу Enter. В случае корректной компиляции и компоновки поя¬
являющимся исполняемым кодом программы welcome.c.
файл a.out,
Пятая фаза называется
быть размещена
загрузкой. Перед выполнением программа должна
Эту задачу выполняет загрузчик, который помеща¬
в памяти.
ет в память исполняемый код с диска.
И наконец, компьютер, под контролем
ЦПУ,
следовательно прочитывая ее инструкции одну за
полнения программы
в системе
UNIX
выполняет программу,
другой. Для
по¬
загрузки и вы¬
после приглашения мы печатаем a.out и
Enter.
нажимаем
Большинство С-программ получают
и (или) выводят данные. Конкретные
С
от
stdin (standard input device
стан¬
получают входные данные
функции
дартное устройство ввода), которое, как правило, ассоциировано с клавиату¬
рой; однако, stdin может быть подключено и к другому устройству. Вывод
данных осуществляется на stdout (standard output device
стандартное
устройство вывода), которым обычно является экран компьютера, однако так¬
же может быть подключено к другому устройству. Когда мы говорим, что про¬
грамма печатает результат,
на
экране.
или
Данные
принтер.
мы подразумеваем,
что результат высвечивается
могут выводиться на другие
Имеется
также
stderr (standard
устройства,
error
устройство ошибок). Устройство stderr (обычно
такие
как
диск
device
стандартное
ассоциированное с экраном)
используется для вывода сообщений об ошибках. Часто для вывода данных,
т.е. для stdout, вместо экрана применяют другие устройства, а экран исполь¬
зуют для вывода
сообщений об ошибках, т.е. в качестве stderr, чтобы пользо¬
быстрее был информирован о возникающих ошибках.
ватель как можно
1.12.
С
это
Общие
трудный
язык.
замечания о
С
и
этой
книге
Опытные программисты зачастую гордятся
своим
умением писать запутанные и загадочные тексты программ.
Однако
плохой
становится неудо¬
стиль программирования.
В результате программа
бочитаемой, увеличивается вероятность сбоев
и
затрудняется
ее
это просто
отладка
и
Глава 1
42
проверка.
Данная
книга предназначена для начинающих программистов, поэ¬
тому мы делаем упор на написание ясных,
грамм.
Одной
из ключевых
написании
при
целей этой
программы
через
структурного программирования и
хорошо структурированных про¬
книги является достижение четкости
применение
благодаря
апробированной
методики
связанному с ней хорошему сти¬
лю программирования.
Хороший
стиль программирования 1.1
Пишите программы в
(«keep
it
simple»
Возможно,
простой, четкой манере Подобный
стиль иногда называют KIS
будьте проще)
-
читатель слышал, что
ные на нем программы могут
работать
С
переносимый
язык и что написан¬
на разных компьютерах.
носимость является весьма труднодостижимым
Однако пере¬
свойством. Стандартный доку¬
ANSI насчитывает 11 страниц, посвященным проблеме переносимости.
На тему переносимости С написаны целые книги (Ja89), (Ra90).
мент
Совет по переносимости программ 1.3
Несмотря на возможность написания переносимых программ существует много
проб¬
лем совместимости между различными версиями С и разными компьютерными сис¬
темами,
грамм
что
на
С
затрудняет достижение
не
обеспечивает
их
переносимости
Само
по
себе
написание
про¬
переносимости
Мы очень тщательно проработали стандартный документ ANSI С и в соот¬
очень богатый язык и
ветствии с ним подготовили нашу книгу. Однако С
некоторые его тонкости в книге не рассматриваются.
бится дополнительное
техническое описание
ANSI С,
Если
читателю понадо¬
то мы рекомендуем ему
ANSI С или книгу Кернигана и Ричи (Ke88). Мы
обсуждение ANSI С, который по многим свойствам несо¬
самому прочитать стандарт
ограничиваем наше
вместим с ранними версиями языка, и в
которые программы,
Хороший
стиль
не
работающие
этой
книге читатель может
со старыми
найти
не¬
С-компиляторами.
программирования 1.2
Читайте руководства пользователя по версиям С, с которыми вы работаете Частое
обращение к ним позволит вам узнать много полезного об особенностях языка С и
поможет их корректно использовать
Хороший стиль программирования 1.3
Хорошими учителями являются компьютер и компилятор. Если вы не уверены в том,
как работает та или иная конструкция С, то следует написать пробную программу, от¬
компилировать,
запустить
ее
и
посмотреть,
1.13. Concurrent С
что
получится
(Параллельный С)
В результате продолжающейся исследовательской работы в Bell Laborato¬
ries были разработаны другие версии С. Гехани (Ge89) разработал Concurrent С
надмножество, обладающее возможностями для парал¬
(Параллельный С)
Принципы машинной обработки
43
данных
лельного запуска нескольких задач. Языки типа Concurrent
ционные системы, поддерживающие параллельную
работу,
С,
а также опера¬
станут более попу¬
лярны в следующем десятилетии с ростом использования мультипроцессоров
(т.е. компьютеров с несколькими ЦПУ). В настоящее время Concurrent С не
более чем исследовательский язык. Книги по операционным системам
(De90),
как правило, уделяют значительное внимание параллельному программирова¬
нию.
1.14.
Объектно-ориентированное
и
программирование
С++
В лаборатории Bell Бьерном Страуструпом (St86) было разработано еще
одно надмножество С, а именно С++. Язык С++ имеет целый ряд качеств,
«украшающих» обычный С. Но
чцля
самое главное, он предоставляет возможности
объектно-ориентированного программирования.
Объекты
это в принципе многократно используемые
компоненты про¬
обеспечения, которые моделируют реальные сущности. В мире про¬
граммных продуктов произошла революция. Быстрое, корректное и экономи¬
граммного
чески выгодное построение программного
нодостижимой целью,
вом,
мощном
обеспечения
все еще остается труд¬
а время таково, что воздух пронизан
потребностью
В настоящее время
разработчики
сознают, что применение
ентированного подхода способно повысить производительность
лективов в
в но¬
программном обеспечении.
10-100 раз
по сравнению со
объектно-орирабочих кол¬
стандартной методикой программиро¬
вания.
ет
Разработано много объектно-ориентированных языков. Однако существу¬
убежденность, что именно С++ станет доминантным системным языком се¬
конца 90-х годов.
редины
людей
Большинство
гией обучения
главы с
15
мирование
склоняется
к
тому,
что
сегодня
наилучшей страте¬
С++. Поэтому
21 посвящены введению в объектно-ориентированное програм¬
С++. Мы надеемся, что они будут оценены читателем, и что после
является
первоначальное
изучение
С,
а
затем
по
и
прочтения книги он продолжит изучение С++.
Резюме
Аппаратной
обеспечение
ANSI-C
компьютера
(hardware)
управляет
это версия языка программирования
стандарта в
дартов
частью
программное
(software).
С, принятая
в качестве
1989 году Американским институтом национальных
(ANSI)
и
Международной организацией
стандартов
стан¬
(ISO).
которые 25 лет назад занимали несколько комнат и стои¬
ли миллионы долларов, сегодня помещаются в нескольких кремниевых
микросхем стоимостью по несколько долларов каждая.
Компьютеры,
В настоящее время
в
мире насчитывается
приблизительно 150
миллио¬
назначения, которые помогают людям в биз¬
несе, промышленности, политике и в повседневной жизни. Через не¬
нов компьютеров
сколько
лет
общего
это'число
может
быть удвоено.
Глава 1
44
Компьютер
устройство, способное производить вычисления, а
действия по принятию логических решений со скоро¬
это
также выполнять
в
стью
миллионы
или
даже
Компьютеры обрабатыБают
данные
чем
раз быстрее,
миллиарды
под
управлением
человек.
компьютерных
программ.
Различные устройства (такие,
как клавиатура, экран, диски, память и
устройства), составляющие
аппаратной частью.
процессорные
зываются ее
компьютерную систему, на¬
Компьютерные
программы, выполняющиеся
ся программным обеспечением.
Входной блок
информации
это
Блок вывода
ции. На
«принимающая»
в современные
или
компьютере, называют¬
часть компьютера.
это часть компьютера, отвечающая за выдачу
сегодняшний
памяти
это
первичной
день большинство
информации
информа¬
выводится на эк¬
бумаге.
«хранилище»
компьютера,
памятью,
именуемое
памятью.
Арифметико-логическое устройство (АЛУ) выполняет
ветственно за процесс принятия решений.
Центральное
Большинство
компьютеры поступает через клавиатуру.
ран либо распечатывается на
Блок
в
процессорное устройство
(ЦПУ)
это
вычисления и от¬
«административ¬
работы
ная» часть компьютера, являющаяся координатором
остальных
блоков.
Программы
или
данные,
которые
не
активно
используются
другими
блоками, обычно помещаются на устройства вторичного хранения (на¬
пример, диски), пока они не понадобятся снова.
При однозадачной пакетной обработке
ми,
или
времени
пакетами,
причем
одну-единственную
данные
обрабатываются
компьютер выполняет
в
каждый
группа¬
момент
программу.
подразумевает одновременное выполнение
на компьютере нескольких задач
компьютер распределяет свои ре¬
ними.
сурсы между
Мультипрограммирование
Разделение времени
специальный случай мультипрограммирова¬
это
ния, когда пользователи получают доступ к компьютеру через
ства
ввода/вывода,
задачи
При
решаются
или
терминалы.
точки
зрения
устрой¬
пользователей
их
одновременно.
распределенных вычислениях
ляется по сети на
С
рабочие
работа всей организации распреде¬
места, на которых и производятся все расче¬
ты.
хранят общий для всех пользователей набор программ и дан¬
клиентами,
ных, которые могут быть использованы компьютерами
Серверы
входящими
в
состав
сети.
Отсюда происходит термин
«модель
кли¬
ент/сервер».
Каждый компьютер
шинный
язык.
может непосредственно понимать только свой ма¬
Принципы машинной обработки
Машинные
сел
(в
языки
в
45
данных
общем
случае состоят из последовательностей чи¬
единицам), которые явля¬
конечном счете сводящихся к нулям и
ются командами на выполнение одиночных элементарных операций.
Машинные языки являются машинно-зависимыми.
Основу языков ассемблера образуют англоязычные аббревиатуры. Ас¬
семблеры транслируют программы, написанные на ассемблерных язы¬
машинный
в
ках,
язык.
для преобразования последовательности операторов на
языке высокого уровня в машинный язык называются компиляторами.
Программы
С
известен
как
Программы,
язык
разработки операционной
написанные на
С, могут быть перенесены
одобрен
на
UNIX.
большинство
систем.
существующих компьютерных
В 1989 году был
системы
стандарт ANSI С.
FORTRAN (FORmula TRANslator) используется
в научных и
инженер¬
ных программах.
COBOL (COmmon Business Oriented Language)
главным
зуется в коммерческих прикладных программах, когда
сокая
точность
Структурное
грамм,
лее
при
обработке большого
программирование
и
лучше
данных.
это такой подход к написанию про¬
при котором они становятся
корректными
количества
образом исполь¬
необходима вы¬
более
подходящими
ясными для понимания,
для
бо¬
дальнейших модифика¬
ций.
Pascal был разработан для обучения структурному программированию
в
кругах.
академических
Ada финансировалась Министерством обороны США.
для его создания был выбран Pascal.
Разработка
языка
В
основы
качестве
Одной
из основных черт языка
позволяет
нескольким
Ada
процессам
является многозадачность, которая
протекать
параллельно.
общем состоят из трех частей: среды, языка програм¬
мирования и стандартной библиотеки С. Библиотечные функции не яв¬
ляются собственно частью языка С; они выполняют такие операции,
как ввод/вывод данных, а также математические вычисления.
Все С-системы
в
В процессе подготовки
С-программы проходят через шесть стадий: ре¬
обработка, компиляция, компоновка,
дактирование, препроцессорная
загрузка и выполнение.
Программист набирает
программу и производит необходимые исправ¬
ления при помощи программы-редактора.
Компилятор транслирует программу на С в
торый также называют объектным кодом).
Препроцессор С
(ко¬
специальным командам
директивам
обычно указывают, что к компилируемому
файлу следует добавить другие файлы и должна быть произведена за¬
мена специальных символов в тексте программы.
препроцессора,
подчиняется
код машинного языка
которые
46
Глава 1
Компоновщик
щих функций,
связывает объектный код с определениями отсутствую¬
вырабатывает
частей).
т.е.
отсутствующих
нет
помещает в память исполняемый код с диска.
Загрузчик
Компьютер,
льно
исполняемый код (в котором уже
под контролем
ее
прочитывая
ЦПУ,
инструкции
выполняет
одну
за
программу,
последовтге-
другой.
Некоторые функции С (например, scanf) получают входные данные от
stdin (стандартного устройства ввода), которое, как правило, ассоции¬
ровано с клавиатурой.
Вывод
да),
данных осуществляется на stdout
(стандартное устройство
выво¬
которым обычно является экран компьютера.
Имеется
также stderr (стандартное устройство ошибок).
Устройство
stderr (обычно ассоциированное с экраном) используется для вывода
сробщений об ошибках.
Несмотря
вует
на возможность написания переносимых
много
проблем
совместимости
между
программ,
различными
сущест¬
версиями
С
и
разными компьютерными системами, что затрудняет достижение пере¬
носимости.
Concurrent С
это
параллельного
надмножество
запуска
С++ предоставляет
нескольких
возможности
С, обладающее
возможностями для
задач.
для
объектно-ориентированного
про¬
граммирования.
Объекты
это
программного
в
принципе
многократно
используемые
компоненты
обеспечения, которые моделируют реальные сущности.
Существует общепринятое
мнение, что именно С++ станет ведущим си¬
стемным языком середины
конца 90-х годов.
Терминология
С
запуск программы
с++
исполнение программы
COBOL
исполняемый
Concurrent С
клиент
CPU
компилятор
FORTRAN
компоновщик
Pascal
компьютер
UNIX
компьютерная
аппаратная платформа
логические
аппаратная
блок
часть
метод
программа
блоки
машинно-зависимый
машинно-независимый
памяти
блочный
образ
построения
программ
ввод/вывод (I/O)
входной блок
выходной блок
машинный
язык
многозадачность
данные
клиент/сервер
мультипрограммирование
мультипроцессор
нисходящее пошаговое уточнение
естественный язык компьютера
объект
загрузчик
объектно ориентированное
задача
модель
программирование
Принципы машинной обработки
47
данных
объектный код
стандартная библиотека С
стандартная ошибка (stderr)
окружение (рабочая среда)
память
первичная память
стандартный ввод (stdin)
стандартный вывод (stdout)
переносимость
структурное программирование
персональный компьютер
повторное использование
терминал
программного кода
препроцессор С
устройство вывода
программа-транслятор
файловый сервер
программист
функция
программное обеспечение
центральное процессорное
суперкомпьютер
устройство ввода
рабочая станция
разбиение на функции
устройство (CPU)
разделение времени
распределенные вычисления
экран
язык высокого уровня
язык программирования
редактор
ясность
сохранение программы
Хороший
1.1.
простой, четкой манере. Подобный
будьте проще).
в
стиль ино¬
KIS («keep it simple»
называют
Читайте
руководства пользователя по версиям С, с которыми вы ра¬
ботаете. Частое обращение к ним позволит вам узнать много полезно¬
го
1.3.
программирования
Пишите программы
гда
1.2.
стиль
об особенностях
языка
Хорошими учителями
'
С
и поможет их корректно использовать.
являются компьютер и компилятор.
Если
вы
работает та или иная конструкция С, то следу¬
пробную программу, откомпилировать, запустить ее и
не уверены в том, как
ет
написать
посмотреть, что получится.
Советы по переносимости программ
1.1.
тому, что С является машинно-независимым и широко
доступным языком, прикладные программы, написанные на С, мо¬
гут работать практически без изменений на большинстве компью¬
Благодаря
терных
1.2.
систем.
Использование функций стандартной библиотеки ANSI вместо напи¬
сания собственных аналогичных функций может улучшить перено¬
симость программы, т.к.
чески
1.3.
во
Несмотря
всех
проблем
вает
С.
совместимости между различными версиями
разными компьютерными системами,
переносимости.
Само
по
себе
С
что затрудняет достижение
написание программ на
С
не
обеспечи¬
переносимости.
Советы по повышению
1.1.
функции реализованы факти¬
вариантах
на возможность написания переносимых программ, суще¬
ствует много
и
стандартные
существующих
эффективносги
Использование функций стандартной библиотеки ANSI вместо напи¬
сания собственных аналогичных функций может улучшить характе¬
ристики
программы,
тщательно
и
т.к.
работают
функции
эффективно.
стандартные
очень
написаны
весьма
48
Глава 1
Упражнения
1.1.
для самоконтроля
Заполните
пробелы
в
каждом
а) Компания, которая
ний,
из
следующих
феномен персональных
ввела в мир
называется
предложений:
вычисле¬
.
которому персональные вычисления были
промышленности и бизнеса, называется
б) Компьютер, благодаря
введены
в
практику
в) Компьютеры обрабатывают
называемым
г) Шестью
данные, следуя
компьютерными
ключевыми логическими
д)
это
наборам инструкций,
.
блоками компьютера
являются:
специальный случай мультипрограммирова¬
ния, при котором пользователи получают доступ к компьютеру по¬
средством
е) В
главе
под названием
устройств
обсуждаются
«терминалы».
три класса языков:
и
,
которые транслируют код с языков высокого уровня
на машинный язык, называются
ж) Программы,
.
з) С
широко известен как язык, на котором написана операционная
система
.
и) Книга
и
С
рассматривает версию С, которая называется
была стандартизирована Американским национальным институ¬
том
стандартов.
к) Язык
был разработан Виртом
ному программированию
в
обучения
для
структур¬
университетах.
л) В Министерстве обороны был разработан язык Ada, который ха¬
позволяющей программистам запускать
рактеризуется
,
несколько
1.2.
задач
параллельно.
Заполните пробелы
в
предложениях о среде С.
следующих
а) С-программы обычно набирают
грамму
б)
на
компьютере,
используя
про¬
.
В С-системе перед началом фазы трансляции автоматически
полняется
программа
в) Двумя наиболее частыми
обработки являются
г) Программа
объединяет выходные
течными функциями.
д) Программа
вы¬
.
операциями во время
и
для
препроцессорной
.
формирования
исполняемого
кода
данные компилятора с различными библио¬
перемещает исполняемый
код с диска в
память.
е) Для
того, чтобы загрузить и выполнить только что откомпилиро¬
ванную программу в системе
UNIX, следует
ввести
.
Принципы машинной обработки
49
данных
Ответы к упражнениям для самоконтроля
1.1.
а) Apple, б) IBM PC, в)
программами,
г) блок
ввода, блок вывода,
арифметико-логическое устройство (АЛУ), централь¬
ное процессорное устройство, блок вторичного хранения данных, д)
разделение времени, e) машинные языки, языки ассемблера и язы¬
ки высокого уровня, ж) компиляторами, з) UNIX, и) ANSI, к) Pas¬
cal, л) многозадачностью.
блок памяти,
1.2.
а) редактора, б) препроцессора, в) включение в компилируемый
файл других файлов, замена специальных символов в тексте про¬
граммы, г) компоновщика, д) загрузчика, e) a.out.
Упражнения
1.3.
Ответьте, к какой группе
относятся
обеспечению
аппаратной
следующие
или
части
программному
объекты:
а) ЦПУ
б) Компилятор С
в) АЛУ
г) Препроцессор С
д) Блок ввода
текстового
е) Программа
1.4.
Для
чего вам может
редактора
понадобиться
зависимом языке вместо того,
писать программу на машинно-не-
чтобы
писать ее на машинно-зависи¬
мом? Почему машинно-зависимый язык
ходящим для написания определенных
может оказаться
типов
более под¬
программ?
1.5. Программы-трансляторы, такие как ассемблеры и компиляторы,
преобразуют программы с одного языка (называемого исходным) на
другой (называемый объектным языком). Определите, какие из сле¬
дующих утверждений справедливы, а какие нет:
а) Компилятор
кого уровня,
б) Ассемблер
ный
транслирует программы, написанные на языке высо¬
в
объектный
язык.
транслирует программы с исходного языка
на машин¬
язык.
в) Компилятор преобразует
граммы
на
г) Языки
объектном
высокого
программы на исходном языке
в
про¬
языке.
уровня,
как
правило,
машинно-зависимы.
запуском программы на компьютере программа, написан¬
ная на машинном языке, нуждается в трансляции.
д) Перед
1.6.
Заполните
пробелы
в
каждом
из
следующих
предложений:
а) Устройства, посредством которых пользователи получают доступ
к системам с разделением времени, называются
.
б) Компьютерная
ных
программ
в
программа, которая преобразует коды ассемблер¬
программы на машинном языке, называется
Глава 1
50
в) Логический блок
компьютера, который получает информацию из¬
вне для использования внутри компьютера, называется
.
г) Процесс
д) Язык
какого
типа
.
англоязычные
использует
команд на машинном языке?
е) Какие
шесть
имеются
быть
з) Общим
написанные
язык,
вне
названием
на
программ,
конкретном
компьютера?
обработанную
информация может
так что
.
которые
преобразуют
компьютерном
в
языке,
программы,
машинный
.
и) Какой логический блок
к) Какой
для
посылает уже
устройства,
компьютера?
является
блоков
логических
на различные
использована
аббревиатуры
.
ж) Какой логический блок компьютера
информацию
для решения конк¬
инструкций компьютеру
написания
ретных задач называется
логический
блок
компьютера
компьютера
хранит
информацию?
выполняет
л) Какой логический блок компьютера принимает
ния?
вычисления?
логические реше¬
.
м) Какая общепринятая аббревиатура
управляющего блока
н) Какой
сту для
используется для обозначения
компьютера?
.
уровень компьютерного языка наиболее
быстрого
и
легкого
написания
о) Общепризнанный ориентированный
это
программирования
п) Единственный
Определите,
язык
компьютера координирует деятельность
.
какие из следующих
нет.
а) Машинные
б) При
бизнес современный
.
всех остальных блоков?
какие
.
компьютера.
p) Какой логический блок
а
на
программи¬
язык, непосредственно понятный компьютеру, на¬
зывается
1.7.
удобен
программ?
Объясните
языки,
свои
утверждений
являются верными,
ответы:
как правило,
машинно-зависимы.
разделении времени несколько пользователей действительно
одновременно используют компьютер.
в) Как
и другие языки высокого уровня,
шинно-независимый
1.8.
Обсудите
значение
С рассматривается
как ма¬
язык.
каждого
из
следующих
имен
в
среде
UNIX:
а) stdin
б) stdout
в) stderr
1.9.
Какой ключевой особенностью обладает Concurrent С, которой
ANSI С?
нет в
Принципы машинной обработки
1.10.Почему
51
данных
сегодня уделяется большое внимание
вообще
ванному программированию
и
языку
объектно-ориентироС++
в
частности?
Рекомендуемая литература
(An90)
ANSI,
American National Standard for Information Systems-Programming Language C (ANSI Document ANSI/ISO 9899: 1990),
New York. NY: American National Standards Institute. 1990.
no ANSI С. Распространение документа осу¬
ществляется Американским институтом национальных стандар¬
тов, 1430 Broadway, New York 10018.
Это первоисточник
(De90)
Deitel, H. М., Operating Systems (Second Edition), Reading. MA:
Addison-Wesley Publishing Company, 1990.
Книга
тем.
о
традиционном
В главах
4
и
программировании
операционных
сис¬
5 широко обсуждается параллельное програм¬
мирование.
(Ge89)
Gehani, N., and W. D. Roome, The Concurrent С Programming
Language. Summit, NJ: Silicon Press. 1989.
Первоисточник
no
Concurrent С
льких
(Ja89)
задач.
Jaeschke. R., Portability and the С Language, Indianapolis, IN: Ha¬
yden Books, 1989.
Обсуждается
написание
переносимых
в
Джэшке принимал участие
ISO С.
(Ke88)
С, которое обес¬
параллельной работы неско¬
надмножества
печивает возможность организации
работе
программ
комитетов
по
на
языке
ANSI С
и
С.
по
Kernighan, В. W., and D. М. Ritchie, The С Programming Langua¬
ge (Second Edition). Englewood Cliffs. NJ: Prentice Hall. 1988.
Книга,
ставшая в своем роде классической.
С
Интенсивно
исполь¬
семинарах по подготовке про¬
граммистов, содержит богатый справочный материал. Ричи яв¬
ляется автором языка С и одним из создателей операционной
(P192)
зовалась
на курсах
системы
UNIX.
языка
и
на
Plauger, P. J., The Standard С Library. Englewood Cliffs. NJ: Prentice.Hall. 1992.
с использованием функций стандартной
библиотеки С. Плогер работал начальником подкомитета при ко¬
митете, который разрабатывал стандарт ANSI С. Сейчас является
главой комитета ISO, занимающегося проблемами С.
Приводятся примеры
(Ra90)
Rabinowitz, H., and С. Schaap. Portable С, Englewood Cliffs. NJ:
Prentice Hall. 1990.
Книга
написана в качестве
носимости, читаемого
тает в
в
учебника
по курсу о
проблемах
пере¬
AT&T Bell Laboratories. Рабинович рабо¬
лаборатории разработки
порации NYEEX, а Шаап
искусственного интеллекта в кор¬
глава корпорации
Delft Consulting.
52
Глава 1
(Ri78)
Ritchie. D. М.; S. С. Johnson; М. E. Lesk; and В. W. Kernighan,
«UNIX Time-Sharing System: The C Programming Language, The
Bell System Technical Journal, Vol. 57, No. 6. Part 2. July-August
1978. pp. 1991-2019.
из классических статей, описывающих язык С. Впервые
опубликована в специальном выпуске Bell System Technical Jour¬
nal, посвященном операционной системе UNIX: «UNIX Ti¬
Одна
me-Sharing System».
(Ri84)
Ritchie. D. М., «The UNIX System: The Evolution of the UNIX Ti¬
me-Sharing System,» AT&T Bell Laboratories Technical Journal,
Vol. 63, No. 8, Part 2, October 1984. pp. 1577-1593.
Классическая
статья
по
системе UNIX. Впервые
Bell System Technical Jour¬
операционной системе UNIX: «The
операционной
в специальном издании
опубликована
nal, полностью
UNIX System.»
(Ro84)
посвященном
Past and Fu¬
Rosier, L., «The UNIX System: The Evolution of C
ture.» AT&T Bell Laboratories Technical Journal, Vol: 63, No. 8,
Part 2, October 1984. pp. 1685-1699.
Отлично дополняет (Ri78)
и будет полезна читателям, интересу¬
историей языка С и проблемами его стандартизации.
Впервые опубликована в специальном издании Bell System Tech¬
nical Journal, полностью посвященном операционной системе
ющимся
UNIX:
(St84)
«The UNIX System.»
Stroustrup, B., «The UNIX System: Data Abstraction in C,» AT&T
Bell Laboratories Technical Journal, Vol. 63. No. 8. Part 2, October
1984. pp. 1701-1732.
статья по С++. Впервые опубликована в специаль¬
Bell System Technical Journal, полностью посвящен¬
операционной системе UNIX: «The UNIX System.»
Классическая
ном издании
ном
(St91)
Stroustrup, В. The С++ Programming Language (Second Edition),
Reading, MA: Addison-Wesley Series in Computer Science, 1991.
Первоисточник по С++, надмножества С, включающего в себя
специальные возможности для объектно-ориентированного про¬
граммирования. Страуструп разработал С++
AT&T Bell Laboratories.
(To89)
в
лаборатории
Tondo, С. L., and S. E. Gimpel, The С Answer Book, Englewood
Cliffs, NJ: Prentice Hall.l989.
Книга содержит ответы к упражнениям, приведенным в книге
Кернигана и Ричи (Ke88). Авторы демонстрируют образцовый
стиль программирования, а также обсуждают проблемы выбора
схемы
написания
программы.
г
л
в
а
а
2
Введение
в
программирование
на
С
Цели
Научиться
Научиться
писать
писать
простейшие
программы
простые операторы ввода
на
С.
и вывода.
Познакомиться с базовыми типами данных.
Понять принципы организации
Научиться
использованию
компьютерной
памяти.
арифметических операций.
Изучить приоритеты арифметических операций.
Научиться
решений.
писать
простейшие
операторы принятия
Глава 2
54
Содержание
2.1.
Введение
2.2.
Простая программа
2.3.
Еще одна простая программа
2.4.
Общие
2.5. Арифметика
2.6.
о
понятия
в
на
С:
памяти
печать
на
строки
С:
текста
сложение
чисел
компьютера
С
Принятие решений: операции равенства
Резюме
двух целых
и
отношения
Распространенные ошибки программирования
Хороший
стиль
Советы по переносимости программ
Упражнения для
Ответы на упражнения для самоконтроля Упражнения
программирования
самоконтроля
2.1.
Введение
Язык С способствует структурному и дисциплинированному подходу к
программ. В этой главе даны вводные замечания,
писанию компьютерных
на¬
ка¬
программирования на С, и представлено несколько примеров, ил¬
люстрирующих многие важные особенности языка. Каждый пример посвящен
тщательному анализу какого-либо отдельного оператора. Главы 3 и 4 пред¬
ставляют собой введение в структурное программирование на С. Далее струк¬
сающиеся
турный подход будет
2.2.
использоваться до конца книги.
Простая программа
на
Язык С использует нотацию, которая
С:
печать
строки
может показаться
текста
странной
людям,
никогда не программировавшим компьютеры. Мы начнем с рассмотрения до¬
печать строки текста. Про¬
вольно простой программы на С. Этот пример
грамма
/*
и ее
результат, выведенный на экран, представлены на рис. 2.1.
Первая программа
на
С
*/
main()
{
prmtf("Welcome
to C!\n");
Welcome to С!
Рис. 2.1. Текст программы
печати
Введение
программирование на С
в
Несмотря
55
на простоту программы,
вать некоторые важные
особенности
с ее помощью можно проиллюстриро¬
С. Рассмотрим подробно каждую
языка
строчку программы. Строка
/* Первая программа
начинается символами
/*
на
*/
С
и заканчивается символами
строка является комментарием.
Программисты
*/,
означающими, что эта
вставляют в код комментарии
чтобы сделать
для документирования программ и для того,
их
более удобочитае¬
компьютера во
время исполнения программы. Компилятор С просто игнорирует комментарии,
не создавая для них никакого машинного объектного кода. Комментарий Пер¬
мыми.
вая
Комментарии
программа на
не оказывают никакого влияния на
С просто объясняет
также помогают другим
назначение
и
людям прочитать
слишком многословные комментарии могут,
работу
программы.
понять
наоборот,
Комментарии
вашу программу,
однако
затруднить ее прочтение.
Распространенная ошибка программирования 2.1
Часто забывают закончить комментарий символами */
Распространенная ошибка программирования 2.2
Часто начинают комментарий символами */,
а
заканчивают
/*
символами
Строка
main ()
должна обязательно присутствовать в
означают,
что
main
каждой программе. Скобки
«строительным
мым функцией. Программа С может
функций, однако одна из функций обязательно
Хороший
после main
блоком» программы, называе¬
содержать одну или большее количество
является
должна быть main.
стиль программирования 2.1
Каждая функция должна быть предварена комментарием, объясняющим
ее назначе¬
ние.
Левая фигурная скобка
ответственно
ции.
правая
({)
должна предварять тело каждой функции. Со¬
должна стоять в конце каждой функ¬
фигурная скобка
Эта пара скобок и часть программы между ними называется блоком.
Блок
С.
важная программная единица в
Строка
printf("Welcome
to
C!\n");
дает компьютеру команду выполнить
действие,
строку символов, находящуюся внутри
вольной
строкой, сообщением
гументы
внутри
оператором.
круглых
кавычек.
а именно
вывести
Такую строку
на
экран
называют сим¬
или литералом. Вся строка, включая printf, ар¬
скобок и точку с запятой (;), называется
Каждый оператор
должен заканчиваться точкой с запятой
(ино¬
оператора). Результатом выполнения опера¬
сообщения Welcome to С! на экран. Символы
гда называемой символом конца
тора
printf
обычно
является
вывод
печатаются именно так, как они записаны внутри
операторе printf. Заметьте,
косая черта (\) называется
что символы
\n
esc-символом.
двойных
кавычек в
не появились на экране.
Он указывает,
что
printf
Обратная
предстоит
56
Глава 2
выполнить
нечто нестандартное. Когда встречается обратная косая черта,
printf считывает следующий за ним символ и, объединяя его с обратной косой
четой, создает esc-код. Esc-код \n означает новую строку, результатом являет¬
ся перевод курсора на начало следующей строки на экране. Некоторые другие
esc-коды представлены на рис. 2.2. Функция printf
одна из многих функ¬
в
С
ций, входящих
(список функций стандартной
стандартную библиотеку
библиотеки дан в Приложении Б).
Esc-код
Описание
VT
Новая строка
Перемещает курсор
в
начало новой строки
Горизонтальная табуляция Перемещает курсор
\t
в следующую позицию
табуляции
V
Возврат каретки Перемещение курсора в начало текущей строки без
перемещения на следующую строку
v>
Звуковой системный сигнал оповещения
Обратная косая черта
\\
Вывод на экран символа \ при помощи
оператора printf
Двойные кавычки
V
Вывод на экран символа двойных кавычек
посредством оператора printf
Рис. 2.2. Некоторые распространенные esc-коды
последних esc-кода на рис. 2.2. могут показаться странными.
Два
Дело в
обратная косая черта воспринимается printf не так, как все осталь¬
ные символы; printf распознает его как ese-символ, а не как символ, который
необходимо отобразить, поэтому используется двойная черта (\\), чтобы ука¬
зать на необходимость отобразить на экране одну обратную косую черту. Пе¬
чать двойных кавычек также представляет определенную проблему для
printf, поскольку обычно подразумевается, что двойные кавычки обозначают
том,
что
границы строки, и по этой причине сами по себе двойные кавычки не должны
выводиться на печать. Используя esc-код \, мы сообщаем оператору printf, что
необходимо вывести на печать двойную кавычку.
Правая фигурная скобка (}) означает, что выполнение функции main
окончено.
Распространенная ошибка программирования 2.3
Вместо
имени
функции
вывода
printf
в
тексте
программы
набирают print
Мы говорили, что printf вынуждает компьютер выполнить определенные
действия. Выполнение любой программы представляет собой набор разнооб¬
разных действий, а также принятие решений. Конец этой главы посвящен об¬
суждению принятия решений. В третьей
главе
будут
даны дальнейшие пояс¬
нения модели программирования действие/решение.
Необходимо отметить, что стандартные библиотечные
printf
и
scanf,
пилятор не в состоянии
да во время
вирует
работы
место
в
функции,
не являются частью языка программирования
обнаружить ошибки
в
компилятору встречается
объектной программе для
такие как
С. Поэтому
ком¬
printf или, например, scanf. Ког¬
оператор printf, он просто резер¬
вызова
библиотечной функции. Но
Введение
в
программирование на С
компилятор
знает.
57
не знает, где находится
Поэтому,
библиотечной
библиотечная функция. Зато компоновщик
когда запускается компоновщик, он находит местоположение
функции
и вставляет
соответствующий
вызов этой
библиотечной
функции в объектную программу. Теперь объектная программа имеет «закон¬
ченный вид» и готова к выполнению. Часто скомпонованную программу так и
называют: исполняемой. Если имя функции содержит ошибку, именно компо¬
новщик ее
С
имени
обнаружит,
какой-либо
Хороший
стиль
так как не сумеет сопоставить имя в программе на языке
из существующих в
библиотеке функций.
программирования 2.2
какую-либо печать, последний выводимый символ дол¬
(\n). Это гарантирует, что функция оставит экран¬
Для функции, производящей
жен быть символом новой строки
ный
курсор расположенным
в начале новой строки
Хороший
рода облегча¬
основной задачи в
Соглашения такого
программного кода
процессе усовершенствования программного обеспечения
ют возможность повторного использования
-
стиль программирования 2.3
Сдвигайте тело каждой функции на один абзацный отступ (три пробела) внутрь отно¬
скобок, ограничивающих тело функции Это придает выразительность функ¬
сительно
циональной структуре программ
Хороший
и
облегчает восприятие при
чтении
стиль программирования 2.4
Установив соглашение на величину отступа, вы предпочтете и в
дальнейшем придер¬
программ Для создания отступов можно
зоваться клавишей табуляции, но позиции табуляции могут изменяться, и
мендуем или использовать позиции табуляции в полсантиметра, или
вводить три пробела на каждый уровень отступа
живаться такого стиля
написания
восполь¬
мы
реко¬
вручную
Функция printf может напечатать Welcome to С! несколькими различны¬
способами. Например, результат выполнения программы на рис. 2.3. такой
же, что и программы на рис. 2.1. Дело в том, что каждая последующая функ¬
ми
ция
printf возобновляет
предыдущая
печать с того самого места, на котором остановилась
функция printf. Первая функция printf
печатает
Welcome
и сле¬
дующий за ним пробел, вторая функция printf начинает печатать в позиции,
следующей сразу за пробелом.
Один оператор printf может напечатать несколько строк, если использо¬
вать символы перехода на новую
строку,
как показано
на рис.
раз, когда встречается esc-код \n (новая строка), функция
2.4. Каждый
printf
курсор на начало следующей строки.
/*
Печать
в
одну
строку
двумя
вызовами
printf */
main()
prmtf("Welcome ");
prmtf("to C!\n");
Welcome to С!
Рис. 2.3. Печать
в одну строку несколькими операторами
printf
переводит
58
Глава 2
/*
Печать
main
нескольких
строк
одним
вызовом
printf */
()
{
printf("Welcome\nto\nC!\n");
}
Welcome
Рис. 2.4. Печать нескольких строк одним оператором printf
2.3. Еще одна простая программа
двух целых чисел
Следующая программа, которую
мы
С:
сложение
сейчас рассмотрим, использует стан¬
библиотечную функцию scanf, чтобы
дартную
на
считать два целых числа, вве¬
денные пользователем с клавиатуры, вычислить сумму их значений и напеча¬
тать результат, используя
функцию printf. Текст программы
и
образец
вывода
представлены на рис. 2.5.
/* Программа сложения
#include <stdio.h>
*/
main ()
int
mtegerl,
mteger2,
/* объявление */
sum;
/*
подсказка
*/
&integerl);
printf("Enter second integer\n");
/*
прочитать
целое
/*
подсказка
*/
scanf("%d",
/*
прочитать
целое
*/
/*
присвоить
сумму
*/
/*
напечатать
first
printf("Enter
integer\n");
scanf("%d",
&integer2);
integerl + integer2;
printf("Sum is %d\n", sum);
=
sum
return
Enter
0;
first
/*
показывает
успешное
завершение
*/
сумму
*/
программы
*/
integer
45
Enter
second
integer
72
Sum
is
117
Рис. 2.5.
Программа суммирования
Комментарий /* Программа
мы.
сложения
*/ объясняет
назначение програм¬
Строка
#include
является
<stdio.h>
директивой для препроцессора С. Строка, начинающаяся символом #,
выполняется препроцессором до того, как программа начнет компилировать¬
Введение
в
программирование на С
59
ся. Эта специфическая строка сообщает препроцессору, что необходимо вклю¬
чить
в
программу
содержание стандартного заголовочного файла
ввода/вывода (stdio.h). Этот заголовочный файл содержит информацию и объ¬
явления, используемые компилятором
во время компиляции вызовов стандар¬
функций ввода/вывода, таких, как printf. Кроме того, заголовочный
файл содержит информацию, которая помогает компилятору определить, кор¬
ректно ли написаны обращения к библиотечным функциям. Более детально со¬
держимое заголовочных файлов будет рассмотрено в главе 5.
тных
Хороший
стиль программирования 2.5
Хотя включение в текст программы <stdio.h> не является обязательным, это необхо¬
димо делать для каждой программы С, использующей любые стандартные функции
ввода/вывода При этом компилятор поможет вам обнаружить ошибки еще на ста¬
дии компиляции, а не на стадии исполнения
(когда исправить ошибки
значительно
труднее)
Как было
отмечено выше, каждая программа начинает исполняться с
кции main. Левая
фигурная скобка
ветственно конец.
Строка
int
integer2,
mtegerl,
является объявлением.
менных.
ние,
Группы
Переменная
то есть такие числа, как
тип данных.
ся
sum;
символов
integerl, integer2
Кроме
объявление
имена пере¬
и sum
использования
переменные
использованы в программе, сразу же после
начинается тело
фун¬
а правая соот¬
программой. Приведенное выше
integerl, integer2 и sum принадлежат к
в этих переменных будут храниться целые величины,
7, 11, 0, 31914 и им подобные. Прежде чем они смогут
для
объявление сообщает, что
типу int и, следовательно,
рой
main,
это ячейка памяти, в которую можно записывать значе¬
предназначенное
быть
отмечает начало тела
main,
int
левой фигурной скобки,
с кото¬
для всех переменных должны быть объявлены имя и
в языке
С существуют
и
другие типы данных.
Допускает¬
нескольких переменных одного и того же типа в одном операторе.
Конечно, мы могли написать три отдельных объявления для каждой перемен¬
ной, но представленный выше вариант более выразителен.
Хороший
стиль
программирования 2.6
Вставляйте пробел
после
каждой запятой (,),
это
облегчит восприятие
текста
програм¬
мы
Именем переменной в С может служить любой допустимый идентифика¬
это последовательность символов, включающая в себя
тор. Идентификатор
и
символ
буквы, цифры
). При этом она не должна начина¬
подчеркивания (
_
ться с
цифры. На длину идентификатора
не существует никаких
ограничений,
установленным для языка С стандартом ANSI,
лишь 31 символ распознается компилятором. Язык С различает регистр, то
есть буквы верхнего и нижнего регистра воспринимаются в С по-разному, поэ¬
однако согласно
тому
а1
и
A1
требованиям,
являются различными
идентификаторами.
Распространенная ошибка программирования 2.4
Использование
заглавных
букв
там, где
мер, вводят Main вместо main)
необходимо
использовать строчные
(напри¬
60
Глава 2
Совет по переносимости программ 2.1
идентификаторы длиной не больше 31 символа Это га¬
рантирует переносимость и даст возможность избежать некоторых трудно обнаружимых ошибок в процессе программирования
Рекомендуется использовать
Хороший стиль программирования 2.7
Осмысленный
рованной,
Хороший
то
выбор
есть
имен
переменных поможет сделать программу самодокументи-
необходимых комментариев
количество
уменьшить
стиль программирования 2.8
Первая буква идентификатора, представляющего простую переменную,
должна быть
будет придаваться особый смысл
идентификаторам, которые начинаются с заглавной буквы, и идентификаторам, в ко¬
торых используются исключительно заглавные буквы
набрана
нижнем
в
Хороший
регистре
Кроме того,
в
книге
стиль программирования 2.9
Имена переменных, состоящие из нескольких слов, помогут сделать программу
легкой для восприятия Однако следует избегать слитного
как например totalcommissions Лучше разделять слова
черкивания
total_commissions,
более
написания отдельных слов,
при
помощи символа
под¬
или, если вы все же хотите писать слова слитно, на¬
чинайте каждое слово после первого с заглавной
буквы,
например, totalCommissH
ons
Объявления должны располагаться после левой фигурной скобки функ¬
ции и перед первым исполняемым оператором. Например, в программе на
рис. 2.5 объявление, помещенное после первого printf, должно вызвать появ¬
ление сообщения о синтаксической ошибке. Сообщение о синтаксической
ошибке появляется, когда компилятор не может распознать оператор.
лятор
Компи¬
сообщение, чтобы помочь программисту, найти и
это нарушения
неправильный оператор. Синтаксические ошибки
в этом случае выдает
исправить
правил языка.
ции или
Часто
ошибки
синтаксические
называют ошибками
компиля¬
ошибками времени компиляции.
Распространенная ошибка программирования 2.5
Объявление переменных между двумя
Хороший
образом,
и исполняемые
где кончаются
Оператор
printf("Enter
first
операторы одной пустой строкой, отмечая, та¬
а где начинаются исполняемые операторы
объявления,
integer\n");
печатает на экране символы
Enter first integer
следующей строки. Такое сообщение
кой,
операторами.
стиль программирования 2.10
Разделяйте объявления
ким
исполняемыми
и переводит курсор на начало
называется приглашением или
так как предлагает пользователю произвести определенные
подсказ¬
действия.
Введение
в
программирование на С
61
Оператор
scanf("%d",
&mtegerl);
вызывает scanf, чтобы получить от пользователя некое значение. Функция
scanf считывает данные со стандартного устройства ввода, которым обычно яв¬
ляется клавиатура. В нашем случае функция scanf имеет два аргумента, % d и
&integerl. Первый аргумент
управляющая строка, задает формат считыва¬
ния,
лю.
тем самым определяется тип данных, которые предстоит ввести пользовате¬
Так,
в частности,
%d
спецификация преобразования,
вводимые данные должны быть целым числом
тичных
целых»). Знак %
мы увидим, что это в
комбинация %d
(буква d
в данном контексте трактуется
полной мере
означающая, что
используется для «деся¬
scanf (в дальнейшем
printf),
(подобно \n). Второй
относится и к
как esc-код
(подобно \),
а
аргумент scanf начи¬
которым в С задается операция взятия адреса
является esc-кодом
нается со знака амперсанда
(&),
следующей за ним переменной. Амперсанд, когда он используется совместно с
именем переменной, сообщает scanf ячейку памяти, в которой хранится пере¬
менная integerl. В последующем компьютер будет хранить величину для inte¬
gerl в этой ячейке. Использование амперсанда очень часто смущает
начинающих программистов,
гих языках. На
димо
имя
или
людей, программировавших
данный
каждой переменной предварять
исключения из
до этого на дру¬
момент просто запомните, что в операторе
этого правила
знаком
будут обсуждаться
в
главах
смысл амперсанда станет ясным после того, как в главе
scanf необхо¬
амперсанда.
7
6
и
Некоторые
7. Настоящий
мы изучим указатели.
вышеприведенный оператор scanf, он ждет,
переменной integerl. Пользователь
после чего нажимает клавишу Return (иногда она называет¬
таким образом, число в компьютер. После этого компьютер
число, или значение, переменной integerl. Любая последу¬
Когда компьютер
выполняет
пока пользователь не введет значение для
вводит целое число,
ся
Enter)y
посылая,
присваивает данное
integerl будет представлять именно это значение.
printf и scanf облегчают взаимодействие между пользователем и
компьютером. Ввиду того, что подобное взаимодействие походит на диалог, час¬
то такой режим работы называют диалоговым или интерактивным.
ющая ссылка в программе на
Функции
Оператор
prmtf("Enter
second mteger\n");
сообщение Enter second integer, после чего переводит кур¬
следующей строки. Он также предлагает пользователю произве¬
печатает на экране
сор на начало
сти определенные действия.
Оператор
scanf("%d",
получает
полнения
sum
=
&mteger2);
от пользователя значение для
переменной integer2. В результате
вы¬
оператора присваивания
mtegerl
+
integer2;
вычисляется сумма переменных
integerl
и
integer2,
и ее значение
ется переменной sum посредством операции присваивания
присваива¬
Оператор читает¬
как
«sum
ся
получает значение integerl + integer2». Большинство
и
вычислений выполняются при помощи операции присваивания. Операция
имеет
+
что
называются
каждая
операция
двухместными операциями потому,
по два операнда. В случае операции + двумя операндами являются integerl и
двумя операндами являются sum и значение
integer2. А в случае операции
выражения integerl + integer2.
=.
=
=
62
Глава 2
Хороший
стиль программирования 2.11
Оставлять
пробелы
и
с
каждой стороны двухместной операции. Это выделит операции
придаст программе более ясный вид.
Распространенная ошибка программирования 2.6
Вычисления
операторе присваивания должны находиться с правой стороны опера¬
с левой стороны операции присваивания, считаются
синтаксической ошибкой
=
ции
в
Вычисления, помещенные
Оператор
prmtf("Sum
вызывает
is
%d\n",
sum);
функцию printf, чтобы
напечатать на экране текст
Sum is,
после ко¬
переменной sum. В данном случае printf
"Sum is % d\n" и sum. Первый аргумент
управляющая
торого следует численное значение
имеет два аргумента,
строка, определяет формат вывода. Он содержит несколько символов, которые
будут отображены, и спецификатор преобразования %d, определяющий, что
будет напечатано целое число. Второй аргумент задает значение, которое будет
напечатано. Отметим, что спецификатор преобразования для целого одинаков
как для printf, так и для scanf. Это справедливо для большинства типов дан¬
ных в
С.
Вычисления
printf.
Мы
могли
могут выполняться и непосредственно внутри
бы скомбинировать два предыдущих оператора в
printf("Sum is %d\n",
integerl
+
оператора
один:
integer2);
Оператор
return
0;
операционной системы, в которой исполнялась про¬
Для операционной системы это означает, что программа завершена
передает значение 0 среде
грамма.
успешно. Чтобы узнать, как сообщить о каких-либо сбоях в программе, изучи¬
те руководство по используемой вами операционной системе.
Правая фигурная скобка
означает, что
функция main
окончена.
Распространенная ошибка программирования 2.7
Иногда забывают одну
ляющую строку
в
или
printf
обе двойные кавычки,
scanf
в
которые следует помещать управ¬
или
Распространенная ошибка программирования 2.8
Забывают
scanf
знак %
в
спецификации преобразования
в
управляющей строке printf
или
Распространенная ошибка программирования 2.9
Помещают esc-код, например \n, снаружи управляющей строки printf
или
scanf
Распространенная ошибка программирования 2.10
Задают
в
ражения,
printf спецификации преобразования
значения
которых должны
быть
и
забывают
напечатаны
включить в оператор вы¬
Введение
в
программирование на С
63
Распространенная ошибка программирования 2.11
Не включают в управляющую строку
обходимо
напечатать
printf спецификацию преобразования,
когда не¬
выражение
Распространенная ошибка программирования 2.12
Помещают внутрь управляющей строки формата запятую, которая
управляющую строку и выражения, которые должны быть
Распространенная ошибка программирования
Забывают поставить перед
переменной
в
должна разделять
напечатаны
2.13
scanf
операторе
знак
амперсанда
На многих системах эта ошибка в процессе исполнения программы назы¬
вается «ошибкой сегментации» или «нарушением доступа». Подобная ошибка
происходит, когда программа пытается получить доступ к той части памяти
компьютера, к
которой
пользовательские программы доступа не имеют.
подобной ошибки будет объяснена
точно причина
в главе
Более
7.
Распространенная ошибка программирования 2.14
Перед
мом
В
именем
деле
главе
переменной включенной
переменная
7
в
ставится амперсанд, тогда как на са¬
этим
знаком
изучать указатели, и рассмотрим случаи, в которых
предварять имя переменной знаком амперсанда, чтобы на¬
этой переменной. Однако
печатать адрес
printf
предваряться
будем
мы
будет необходимо
ющих глав операторы
2.4.
не должна
printf
Общие
Имена переменных,
на протяжении нескольких последу¬
не должны содержать знаки амперсанда.
понятия о памяти
такие как
компьютера
и sum, в действительно¬
Каждая переменная имеет
integerl, integer2
сти соответствуют ячейкам в памяти компьютера.
имя, тип и значение.
В программе суммирования
scanf("%d",
на рис.
2.5,
во время исполнения оператора
&integerl);
в ячейку памяти, которой
integerl. Предположим, что пользователь вводит в качестве
integerl число 45. Компьютер помещает 45 в ячейку integerl, как
значение, введенное пользователем, помещается
присвоено имя
значения
показано на рис.
2.6.
Всякий раз, когда
значение помещается в
ячейку
памяти, оно переписы¬
вает предыдущее значение, находившееся в этой ячейке. Так как предыдущая
информация уничтожается, процесс
ти называется
разрушающим
integerl
информации
ячейку.
считывания
считыванием в
в
ячейку
45
Рис. 2.6. Ячейка памяти, указывающая имя и значение переменной
памя¬
64
Глава 2
Еще раз вернемся
scanf("%d",
к программе сложения, когда выполняется оператор
&integer2);
Предположим,
пользователь вводит значение
72. Оно помещается
в
ячей¬
ку integer2, и как теперь выглядит память, показано на рис. 2.7. Заметим, что
ячейки памяти совсем не обязательно окажутся соседними.
Рис. 2.7. Ячейки
integerl
45
integer2
72
памяти после ввода значений для
обеих переменных
После того, как программа получила значения для
integerl
и
integer2,
она складывает эти величины и помещает суммарное значение в переменную
sum.
Оператор, выполняющий
=
sum
+
integerl
сложение
integer2;
считывание в ячейку. Это происходит, ког¬
да вычисленная сумма integerl и integer2 помещается в ячейку sum (стирая
при этом значение, которое могло уже находиться в sum). После того как sum
тоже влечет за
собой разрушающее
вычислена, память выглядит, как показано на рис.
integerl
лись.
и
Эти
integer2
2.8. Отметим,
что значения
после использования в вычислении sum никак не измени¬
были уничтожены во время выпол¬
Таким образом, когда значение считывается
величины использовались, но не
нения вычислений компьютером.
из ячейки памяти, то такой процесс называется
неразрушающим
считывани¬
ем из ячейки.
integerl
45
integer2
72
117
sum
Рис. 2.8. Ячейки памяти после вычисления
2.5.
Большинство
вычисления.
на рис.
написанных на языке
в
С программ
С
выполняют
Сводная таблица арифметических операций
2.9. Отметим
льзуемых в
Арифметика
арифметические
языка
С представлена
использование различных специальных символов, не испо¬
алгебре. Так, звездочка (*)
означает операцию взятия по
модулю,
означает умножение, а знак
процента
(%)
которая будет объяснена ниже. В алгебре,
мы хотим умножить а на b,
мы можем просто написать переменные,
обозначенные одной буквой, рядом, то есть ab. Однако если мы напишем нечто
если
подобное
в
С,
то конструкция
ab будет воспринята языком,
как одна переменная
Введение
программирование на С
в
65
(или идентификатор) из двух букв. Таким образом, С (как, впрочем, и
другие языки программирования) требует, чтобы умножение было явно
чено с использованием знака звездочки следующим образом: а * b.
многие
обозна¬
~------
Действие
в С
Сложение
Алгебраическое
выражение
Выражение
+
f + 7
f
Р-7
P
-
Вычитание
*
Умножение
Деление
Взятие
Арифметическая
операция
x
/
х/у
%
по модулю
b
bm
-
или
x
или Хт-у
Рис. 2.9.
+ 7
~
*
с
m
/ у
r %
r mod s
на С
s
Арифметические операции
Все арифметические операции являются двухместными. Например, выра¬
жение 3 + 7 содержит двухместную операцию + и операнды 3 и 7.
Результатом деления двух целых чисел также будет целое число. Например,
значение выражения
7 / 4 будет 1,
а
17 / 5 равно 3. В С существует операция
взя¬
дает значение остатка после деления нацело. Опера¬
ция взятия по модулю может использоваться только с целыми операндами. Вы¬
ражение x % у означает остаток от деления x на у. Таким образом, результатом 7
тия по модулю,
%, которая
% 4 является 3, а 17 % 5 соответственно 2. В дальнейшем будет рассмотрено
много интересных вариантов применения операции взятия по модулю.
Распространенная ошибка программирования 2.15
Деление на ноль обычно не определено в компьютерных системах и, как правило,
приводит к фатальной ошибке, то есть такой ошибке, в результате которой выполне¬
ние программы немедленно прерывается. В случае не фатальной ошибки программа
выполнится до конца, однако результат выполнения программы, как правило, неве¬
рен
Арифметические выражения в С должны записываться в строчку, в таком
виде легче ввести программу в компьютер. Таким образом, выражения типа
«а деленное наЬ» следует записывать как a/b, чтобы все операции и операнды
располагались на
одной строке. Алгебраическая
нотация
а
b
неприемлема, как правило, для компиляторов, хотя существуют созданные
целей пакеты программного обеспечения, поддерживающие
более привычную нотацию сложных математических выражений.
для специальных
Круглые скобки используются в выражениях на языке С точно так же,
в алгебраических выражениях. Например, чтобы умножить а на b+c мы
как и
пишем:
а
*
(b
+
с)
С оценивает арифметические выражения
значение)
раций, которые
(т.е. вычисляет их численное
определяемой правилами старшинства опе¬
же, как и в алгебре:
в последовательности,
3
Зак 801
в
общем
такие
66
Глава 2
1.
Выражения
в
ваются
выражений, находящиеся внутри скобок, оцени¬
Другими словами, могут использоваться
поменять порядок вычислений на тот, что требуется
или части
первую
скобки, чтобы
очередь.
программисту. Говорят, что скобки обладают «наивысшим приорите¬
В случае вложенных скобок выражение, находящееся во внут¬
том».
ренней
2.
паре скобок, оценивается первым.
Следующими
выполняются операции умножения, деления и взятия по
Если
в каком-нибудь выражении содержится несколько опера¬
ций умножения, деления или взятия по модулю, оценка производится
слева направо. Говорят, что умножение, деление и взятие по модулю
модулю.
операции одинакового приоритета.
3. Последними выполняются операции сложения
ражение содержит несколько
производится
слева
операций
Сложение
направо.
Если
и вычитания.
вы¬
сложения и вычитания, оценка
и
вычитание
также
операции
одного приоритета.
Правила,
операций,
определяющие приоритет
щим принципом,
который
да оценка происходит
являются
тем
руководя¬
С правильно оценивать выражения. Ког¬
направо, говорят об ассоциативности слева на¬
позволяет
слева
На
право. Мы увидим, что некоторые операции ассоциативны справа налево.
рис. 2.10 представлена сводная таблица старшинства операций
в порядке
убы¬
вания приоритета.
Теперь
рассмотрим несколько
старшинства
операций. Для
равнозначная запись на
Операция
Действие
0
Скобки
выражений
Выражения
+
или
-
первую
первую
вычисляется значение во внутренних
скобках
А в случае нескольких пар
есть
не
вложенных)
скобок «одного
они
вычисляются
направо
Оцениваются в последнюю очередь Если операций
несколько, то вычисления ведутся слева направо
Сложение или вычитание
Следующий пример
в
за скобками
Если операций
несколько, то вычисления ведутся слева направо
модулю
Рис. 2.10.
в скобках оцениваются
случае вложенных скобок в
В
Оцениваются вслед
Умножение, деление,
взятие по
правил
запись и
очередь
слева
%
применения
алгебраическая
Порядок оценки (приоритет)
уровня» (то
или
плане
С.
очередь
*, /
в
каждого примера дается
Старшинство арифметических операций
вычисляет среднее
арифметическое
для пяти элемен¬
тов:
а + Ъ + с+d + e
Алгебра:
С:
m
=
5
m
=
(а
+
b
+
с
+
d
+
e)
/
5;
Скобки необходимы по той причине, что операция деления обладает более вы¬
соким приоритетом, чем сложение. Необходимо поделить сумму (а + b + с + d +
e)
на
5. Если скобки
по
ошибке пропущены,
мы получим а +
что приведет к вычислению совсем другого выражения
b +
с +
d +
e
/ 5,
Введение
+
а
в
программирование
+
b
+
с
на С
67
+
d
5
Еще один
Алгебра:
у
С:
у
уравнение прямой линии:
пример
=
mx +
b
*
=
m
x
b;
+
требуются. Умножение
В данном случае скобки не
выполняется первым, так
более высокий приоритет,
как операция умножения имеет
чем операция сло¬
жения.
Следующий пример содержит операции взятия по модулю
ния, деления, сложения и вычитания:
Алгебра:
z
С:
z
-
pr mod q +
w
*
=
%
r
p
/
x
(%),
умноже¬
-
у
+
q
w
/
-
x
у;
числа, расположенные под оператором, показыва¬
Обведенные кружками
ют порядок, в котором С производит вычисления.
Умножение,
взятие по моду¬
лю и деление оцениваются в первую очередь в порядке слева направо
направо), так как имеют более высокий приоритет,
вычитание. Следующими выполняются операции сложения
социативны слева
жение и
(они
ас¬
чем сло¬
и вычи¬
тания, они также оцениваются слева направо.
Не
все выражения,
женные
а
*
в которых есть несколько пар
(b
+
+
с)
*
с
+
(d
ся на «одном уровне».
В
Говорят,
что в данном случае
Чтобы лучше
а
*
слева направо.
понять правила старшинства
вычисляет полином
x
*
второй
x
операций, рассмотрим,
как
С
степени.
+
Обведенные кружками
скобки находят¬
в скобки выра¬
С вычисляет заключенные
этом случае
жения в первую очередь, порядок вычисления
=
вло¬
e)
не содержит вложенных скобок.
z
скобок, содержат
скобки. Выражение
*
b
4
x
с ;
числа, расположенные под оператором, показыва¬
ют порядок, в котором С производит оценку. В языке С нет
операции возведения в степень.
Поэтому
мы
арифметической
представили x2 как
x
*
x.
Стан¬
дартная библиотека С включает в себя функцию pow возведения в степень. По
причине того, что существуют некоторые тонкости применения pow, связан¬
ные с типом
требуемых функцией
данных, мы отложим детальное объяснение
ее до главы 4.
Предположим,
будет вычисляться
2.6.
что а
=
2, b
=
3,
с
=
7
и x
=
5. Рис. 2.11 иллюстрирует,
значение представленного выше полинома
второй
как
степени.
Принятие решений: операции равенства
и отношения
Исполняемые операторы С
выполняют действия (такие, например,
данных), или принимают решения (скоро мы
этого). Мы можем принять решение в программе,
как вычисления или ввод-вывод
увидим несколько
3*
примеров
или
68
Глава 2
Шаг 1.
у
2
=
5
*
2
*
5 + 3
5 + 7;
Qo)
5 равно
*
*
(Первая операция
-
самое левое
умножение)
r^
Шаг 2.
у
10
=
10
*
5 + 3
*
5 + 7;
5 равно
*
^o)
(Самое
левое
умножение)
г
Шаг 3.
у
50 + 3
=
3
*
5 + 7
5 равно
*
^j)
(Умножение перед сложением)
г
Шаг 4.
у
50 + 15 + 7
=
50 + 15
равно^Й)
(Самое
левое
сложение)
г
Шаг 5. y
65 + 7
=
65 + 7 равно
^2)
(Последняя операция)
Рис. 2.11. Вычисление
полинома
второй
степени
например: определить отметку некоторого человека на экзамене,
окажется выше или равна
шли».
ры if
60,
напечатать
В этом разделе приведен упрощенный вариант управляющей структу¬
языка
С, который
позволяет программе принимать решения исходя из
того, истинно или ложно некое утверждение, называемое
вие выполняется
(т.е.
условием. Если усло¬
истинно), тело структуры if исполняется. Если же
(т.е. оно ложно), то тело оператора не исполняется.
оно
условие не выполняется
Вне
и если она
сообщение «Поздравляем! Вы про¬
зависимости от того, исполнилось тело оператора или нет, после заверше¬
ния выполнения оператора
Условия
в
if
выполняется
следующий
за ним оператор.
операторе if задаются с использованием операций равенства и
отношения, сводная таблица которых представлена
ношения имеют
одинаковый приоритет
на рис.
2.12. Операции
и ассоциативны слева направо.
от¬
Опера¬
ции равенства имеют более низкий приоритет и также ассоциативны слева на¬
(Замечание: в С условием может какое-либо выражение, значение кото¬
либо равно нулю (false), либо нет (true). На всем протяжении книги нам
право.
рого
будут встречаться многочисленные
вие в операторе if.)
примеры
подобного способа
Распространенная ошибка программирования
Разделение символов в любой из
синтаксической
ошибкой.
операций
задавать усло¬
2.16
==, !=, >= или <=
пробелами
является
Введение
в
программирование
Стандартная
алгебраическая операция
равенства или отношения
на С
69
Операцияравенст-
Пример
условия
ва или отношения в
Смысл
в C
условия
в С
C
Операции равенства
=
==
X
==
=
x равен у
У
i=
X
>
>
X > у
x больше у
<
<
X < у
x меньше у
>
>=
X
>=
у
x больше или равен у
<
<=
X
<=
у
x меньше или равен у
*
x не равен у
у
Операции отношения
Рис. 2.12. Операции равенства и отношения
Распространенная ошибка программирования 2.17
Два
символа
в
любой
из
операций !=,
>=
или <=,
набранные
в
обратном
порядке
(соответственно =!, => и =<), также приведут к синтаксической ошибке.
Распространенная ошибка программирования 2.18
Часто путают операцию
==
с
операцией
присваивания
Чтобы избежать этой ошибки, следует
как
«двойное равенство»,
чение».
Как
мы увидим в
читать
=.
знак
операции
а знак операции присваивания как
равенства
«присвоить зна¬
дальнейшем, путаница
в применении этих операций
распознаваемой синтаксической ошиб¬
причиной чрезвычайно трудно обнаружимых ло¬
не всегда приводит к появлению легко
ки, а, напротив, может быть
гических
ошибок.
Распространенная ошибка программирования 2.19
Иногда
ставят точку с запятой непосредственно за
правой скобкой, завершающей
вы¬
ражение условия структуры if.
Пример
на рис.
2.13 использует
шесть операторов
if, чтобы сравнить
два
введенных пользователем числа. Если условие в любом из операторов if истин¬
но, то выполняется оператор
printf,
связанный
с данным
if. Программа
и три
варианта вывода в результате ее выполнения показаны на рисунке.
Заметим, что программа на рис. 2.13 использует scanf, чтобы ввести два
Каждой спецификации преобразования соответствует аргумент, в кото¬
числа.
Первая спецификация %d преоб¬
в переменной numl, а вторая
которое будет сохраняться в пере¬
ром будет сохраняться введенное значение.
разует значение, которое должно сохраняться
спецификация %d преобразует
значение,
менной num2. Смещая вправо тело каждого оператора if и помещая пустые
строки сверху и снизу от него, мы облегчаем восприятие программы. Заметим
также,
что
каждый оператор if
на рис.
2.13. содержит
в своем теле единствен-
Глава 2
70
ный оператор. В главе 3
будет
показано, как записать оператор
if,
тело которо¬
го содержит несколько операторов.
Применение операторов if,
операций отношения и операций равенства
#include <stdio.h>
/*
main ()
{
int numl,
*/
num2;
two
pnntf("Enter
I
and
integers,
will
if
if
if
num2)
(numl
pnntf("%d is equal
if
to %d\n",
(numl != num2)
printf("%d is not equal
(numl
<
целых
is
less
(numl > num2)
printf("%d is
(numl
to
%d\n",
<=
than
%d\n",
greater than
num2);
numl,
num2);
numl,
num2);
num2)
/*
0;
num2);
numl,
%d\n",
is
less
than
equal
or
(numl >= num2)
pnntf("%d is greater than
return
numl,
показывает
or
to
equal
успешное
%d\n",
to
numl,
%d\n",
завершение
3
two integers, and I will
relationships they satisfy:
is not equal to 7
3
is
less
than
7
3
is
less
than
or
Enter
the
equal
to
программы
two integers, and I will
relationships they satisfy:
22 is not equal to 12
the
is
greater
than
12
22
is
greater
than
or
two integers, and I will
relationships they satisfy:
is
7
is
equal to 7
less than or
7
is
greater
than
equal to
or equal
you
7
tell
you
22 12
12
to
equal
Enter
7
3
7
Enter
22
tell
tell
7
you
7
7
to
Рис. 2.13. Использование
7
операций равенства
num2);
numl,
}
the
*/
num2)
pnntf("%d
if
два
==
pnntf("%d
if
you\n");
tell
printf("the relationships they satisfy: ");
/* прочитать
scanf("%d%d", &numl, &num2);
и отношения
num2);
*/
Введение
в
программирование на С
71
Хороший стиль программирования 2.12
Смещать вправо операторы
в
теле
if
Хороший стиль программирования 2.13
Помещать
ния
Хороший
В
в программе пустую строку над и под каждым оператором
восприятия
if для облегче¬
программы
стиль программирования 2.14
каждой строке программы должно быть
не
более одного оператора
Распространенная ошибка программирования 2.20
Ставят
запятые
(хотя
они
не
управляющей строке формата
нужны)
между спецификациями
оператора scanf
преобразования
в
считывания
Комментарий к программе на рис. 2.13 разбит на две строки. В програм¬
С пробельные символы, такие как табуляции, новые строки, про¬
мах на языке
белы обычно игнорируются. Поэтому
разбивать операторы и коммента¬
разбивать нельзя.
Таблица на рис. 2.14 показывает старшинство операций, введенных в этой
главе. Операции расположены сверху вниз в порядке убывания приоритета.
Заметим, что знак равенства тоже является операцией. Все эти операции за
исключением операции присваивания
ассоциативны слева направо. Опера¬
можно
рии на несколько строк. Однако идентификаторы
=
ция присваивания ассоциативна справа налево.
Хороший
стиль программирования 2.15
Длинные операторы можно записывать в несколько строк Если оператор необходимо
разбить на несколько строк, старайтесь делать это осмысленно (скажем, после запя¬
той в разделенном запятыми списке) Если оператор разбит на две или большее чис¬
ло строк, смещайте вправо все следующие за первой строки
Хороший
стиль программирования 2.16
При написании выражений, содержащих много операций, пользуйтесь таблицей
старшинства операций. Следите за тем, чтобы операции в выражении выполнялись в
правильном порядке Если вы не уверены в порядке выполнения операций в сложном
выражении, используйте скобки для принудительного задания нужного порядка, так
же как делаете это в алгебраических выражениях Не следует также забывать, что не¬
которые операции в С, например операция присваивания (=), ассоциативны справа
налево, а не наоборот.
Некоторые
С
в этом
из слов, которые мы использовали при написании программ на
параграфе
в частности
int, return
и
if
являются ключевыми или
зарезервированными словами языка. Полный набор ключевых слов языка С
приведен на рис. 2.15. Эти слова имеют специальное значение для компилято¬
ра
С, поэтому программист
идентификаторы имен
как
чевые слова С.
должен быть внимателен и не применять эти слова
переменных.
В
книге
будут
рассмотрены все клю¬
72
Глава 2
Ассоциативность
Операция
0
%
/
+
V
V II
-
!
л
л
и
слева
направо
слева
направо
слева
направо
слева
направо
слева направо
-
справа налево
-
Рис. 2.14. Старшинство и ассоциативность рассмотренных на данный момент
В данной
главе
программирования
мы
С,
основываясь на
пройденном
турного программирования.
менения отступов;
с
целым
рядом
возможностей
узнаете,
в
и
этой
принятие
решений. В следующей
главе,
главе материале, мы введем понятие струк¬
Вы подробнее познакомитесь
как задавать порядок,
с принципами при¬
в котором должны выпол¬
т.е. контролировать то, что называется потоком
няться операторы
языка
включая вывод данных на экран, ввод данных пользова¬
вычислений
выполнение
телем,
познакомились
операций
управле¬
ния.
Ключевые слова С
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
Рис. 2.15. Зарезервированные
слова
Резюме
Комментарии
начинаются
с
/*
и
заканчиваются
*/. Программисты
вставляют комментарии для документирования программ и
их восприятия.
Комментарии
облегчения
не оказывают никакого влияния на
рабо¬
ту компьютера во время исполнения программы.
Директива
что
препроцессора #include <stdio.h> сообщает компилятору,
включить в программу стандартный заголовочный
необходимо
файл ввода/вывода. Этот файл
содержит информацию, которая помога¬
ет компилятору определить, корректно ли написаны обращения к фун¬
кциям ввода-вывода, например, scanf и
printf.
из
С
состоит
из
которых обязательно должна
Программа
функций, одна
быть main. Каждая программа начинает выполняться с функции main.
Введение
в
программирование на С
Функция printf
ной
может использоваться для вывода на печать заключен¬
в кавычки строки и
чения
первый
73
значений выражений. При
аргумент функции
printf
печати целого зна¬
управляющая строка, задаю¬
преобразования
щая формат вывода, должна содержать спецификатор
%d
и, кроме того, может содержать любые другие символы, которые
второй аргумент
выражение, значение ко¬
торого необходимо напечатать. Если на печать будет выводиться более
должны быть напечатаны;
одного целого числа, управляющая строка формата должна содержать
%d для каждого целого, а разделенные запятой аргументы, следующие
за
управляющей строкой,
содержать выражения,
значения
которых
должны быть напечатаны.
Функция scanf получает значения, которые пользователь обычно вво¬
дит с клавиатуры. Ее первый аргумент
управляющая строка, задаю¬
щая формат считывания, которая сообщает компьютеру, какой тип
данных должен быть введен пользователем.
вания
% d определяет,
Спецификация преобразо¬
что эти данные должны
быть целыми. Каждому
спецификация пре¬
образования в управляющей строке формата считывания. Имя каждой
переменной обычно предваряется амперсандом (&), называемым в С
операцией взятия адреса. Амперсанд при объединении с именем пере¬
менной сообщает компьютеру ячейку памяти, в которой будет хранить¬
ся значение. Затем компьютер записывает значение в эту ячейку.
из оставшихся аргументов должна соответствовать
в программе на С должны быть объявлены, прежде чем
они будут использованы в программе.
Все переменные
в С может быть любой допустимый идентификатор.
Идентификатор представляет собой последовательность символов, состо¬
Именем переменной
ящую из
букв, цифр
могут начинаться с
однако
согласно
и символа подчеркивания
(
_
). Идентификаторы
не
цифры. Идентификаторы могут быть любой длины;
ANSI
стандарту
значим
лишь
тридцать
один
символ.
Язык С различает регистр.
Большинство вычислений
выполняются
при
помощи
операторов
при¬
сваивания.
Каждая переменная, хранящаяся
значение
и
в
памяти
компьютера,
имеет
имя,
тип.
Всякий раз, когда
новое значение помещается в
ячейку
памяти, оно за¬
меняет предыдущее значение, находившееся в этой ячейке. Так как эта
информация уничтожается, процесс помещения информа¬
ячейку памяти назван разрушающим считыванием в ячейку.
предыдущая
ции в
Процесс
считывания
считыванием
из
значения
из
памяти
Чтобы облегчить введение программы
выражения в С должны записываться
С
вычисляет
вилами,
называют
неразрушающим
ячейки.
в
компьютер,
в
строчку.
арифметические выражения
определяющими
приоритет
и
арифметические
в точном соответствии с пра¬
ассоциативность
операций.
Оператор if позволяет программисту вводить в программу возможность
выбора, в зависимости от выполнения некоторого условия. Формат опе¬
ратора
if следующий:
74
Глава 2
if
(условие)
оператор
Если условие
истинно, оператор в теле if выполняется. Если условие
ложно, тело оператора пропускается.
Условия
в операторе if обычно задаются с использованием операций ра¬
венства и отношения. Результат этих операций всегда «истина» или
«ложь».
Заметим,
что условием может
порождающее нулевое (false)
или
быть
ненулевое
также
(true)
любое выражение,
значение.
Терминология
С
неразрушающее
esc-код
нуль (false)
обратная косая черта \ (ese-символ)
объявление
ese-символ
false
int
чтение
ячейки
из
окончание оператора (;)
main
операнд
stdio.h
оператор
true
оператор присваивания
амперсанд (&)
аргумент
операции
<
арифметические операции
ассоциативность операций
ассоциативность слева направо
ассоциативность
справа
налево
блок
вложенные
скобки
двухместные
операции
<=
>
отношения
«меньше»
«меньше
или
равно»
«больше»
>= «больше или равно»
операции равенства
!= «не равно»
==
«равно»
операция
действие
операция
взятия
адреса
деление на 0
операция
взятия
по
деление нацело
операция присваивания
диалоговое вычисление
операция умножения
запись в строчку
отступ
ошибка времени компиляции
ошибка при трансляции
зарезервированные слова
зарезервированные слова языка С
звездочка
процента
знак
равенства
%
(ese-символ)
=
(операция присваивания)
значение
переменной
идентификатор
значение
имя
интерактивное
вычисление
переменная
правила старшинства
различение
регистра
разрушающее
клавиша
решение
enter
return
ключевые
слова
операций
препроцессор С
приглашение
принятие решения
приоритет операций
пробельные символы
истинность
клавиша
считывание
новой строки (\n)
символ подчеркивания (
ложность
действие/решение
нулю (true)
не
равное
не
фатальная ошибка
в
ячейку
символ
__
комментарий
круглые скобки ()
литерал
модель
(%)
(=)
(*)
память
(*)
знак
модулю
синтаксическая
)
ошибка
сообщение
спецификация преобразования
спецификация преобразования %d
стандартная библиотека С
стандартный заголовочный файл
ввода/вывода
Введение
в
программирование на С
75
управляющая структура
строка
строка
символов
'
структурное
тело
тип
фатальная ошибка
фигурные скобки {}
функция
функция printf
функция scanf
программирование
функции
переменной
точка
с
if
условие
запятой
;
(окончание оператора)
управление, поток управления
управляющая
строка
управляющая
строка
целое
ячейка
ячейка
формата
памяти
Распространенные ошибки программирования
2.1.
2.2.
Часто забывают
закончить
Часто
комментарий
лами
2.3.
начинают
комментарий
*/.
символами
символами
*/,
а заканчивают симво¬
/*.
Вместо
имени
функции
вывода
printf
в тексте
программы
набирают
print.
2.4.
Использование заглавных букв там, где должны быть
строчные (например, вводят Main вместо main).
2.5.
Объявление
2.6.
Вычисления
стороны
2.7.
2.8.
переменных между двумя исполняемыми операторами.
в
=.
Вычисления, помещенные
присваивания,
Иногда забывают одну
Забывают
знак
%
в
2.10.3адают
в
стороны
спецификации преобразования
в
следует
управляющей
scanf.
Помещают esc-код,
формата printf
левой
обе двойные кавычки, в которые
в printf или scanf.
или
управляющую строку
или
с
синтаксической ошибкой.
считаются
помещать
строке printf
2.9.
операторе присваивания должны находиться с правой
операции
операции
использованы
или
например \n,
scanf.
снаружи управляющей строки
printf спецификации преобразования
чить в оператор выражения,
и
забывают
значения которых должны
вклю¬
быть
напе¬
чатаны.
2.11.He
включают в управляющую строку
зования,
когда
необходимо
printf спецификацию преобра¬
напечатать
выражение.
2.12.Помещают
внутрь управляющей строки формата запятую, которая
должна разделять управляющую строку и выражения, которые дол¬
жны быть напечатаны.
2.13. Забывают поставить перед
переменной
в операторе
scanf
знак ампер¬
санда.
2.14.Перед
именем
тогда как
переменной включенной
на самом деле
переменная
в
printf
ставится амперсанд,
не должна предваряться
этим
знаком.
2.15.Деление
на ноль
обычно
как правило, приводит к
не определено в компьютерных системах и,
фатальной ошибке,
то есть такой
ошибке,
в
76
Глава 2
результате
которой выполнение программы немедленно прерывает¬
фатальной ошибки программа выполнится до конца,
ся. В случае не
однако результат выполнения программы, как правило, неверен.
2.16. Разделение символов
лами
2.17. Два
любой
из
операций
!=,
==,
>= или <=
любой
операций !=, >= или <=, набранные
(соответственно =!, => и =<), также являются
символа в
ном порядке
в
пробе¬
синтаксической ошибкой.
является
из
в
обрат¬
синтак¬
сической ошибкой.
2.18.Часто путают операцию
2.19.Иногда ставят
завершающей
точку с
==
с
операцией
присваивания
запятой непосредственно
за
=.
правой скобкой,
выражение условия структуры if.
2.20. Ставят запятые
разования
scanf.
Хороший
стиль
в
(хотя они не нужны) между спецификациями преоб¬
управляющей строке формата считывания оператора
программирования
2.1.
Каждая функция должна быть предварена комментарием, объясня¬
ющим ее назначение.
2.2.
Для функции, производящей какую-либо печать, последний выво¬
димый символ должен быть символом новой строки (\rt). Это гаран¬
что функция оставит экранный курсор расположенным в на¬
чале новой строки. Соглашения такого рода облегчают возможность
основной задачи в
повторного использования программного кода
обеспечения.
процессе усовершенствования программного
тирует,
2.3.
Сдвигайте тело каждой функции на один абзацный отступ (три про¬
бела) внутрь относительно скобок, ограничивающих тело функции.
Это придает выразительность функциональной структуре программ
и
2.4.
облегчает восприятие при
Установив
чтении.
соглашение на величину отступа, вы
предпочтёте
и в да¬
льнейшем придерживаться такого стиля написания программ. Для
создания отступов можно воспользоваться клавишей табуляции, но
табуляции могут изменяться, и мы рекомендуем или испо¬
табуляции в полсантиметра, или вручную вводить
на
каждый уровень отступа.
пробела
позиции
льзовать позиции
три
2.5.
Хотя
включение в текст программы
<stdio.h>
не является
обязате¬
каждой программы С, использую¬
щей любую из стандартных функций ввода/вывода. При этом ком¬
вам
поможет
пилятор
обнаружить ошибки еще на стадии
льным,
это
необходимо
делать для
компиляции, а не на стадии исполнения
значительно
2.7.
текста
после
каждой запятой (
,
),
это
облегчит восприя¬
программы.
Осмысленный выбор
имен переменных поможет сделать программу
самодокументированной,
мых
исправить ошибки
труднее).
2.6. Вставляйте пробел
тие
(когда
комментариев.
то
есть
уменьшить
количество
необходи¬
Введение
2.8.
в
программирование
на С
77
Первая буква идентификатора, представляющего простую пере¬
менную, должна быть набрана в нижнем регистре. Кроме того, в
будет придаваться особый смысл идентификаторам, которые
начинаются с заглавной буквы, и идентификаторам, в которых ис¬
книге
пользуются исключительно заглавные буквы.
2.9.
Имена переменных, состоящие из нескольких слов, помогут сделать
программу более легкой для восприятия. Однако следует избегать
слитного написания отдельных слов, как например totalcommissions. Лучше разделять слова при помощи символа подчеркивания to-
tal_commissions,
или, если вы все же хотите писать слова слитно,
начинайте каждое слово после первого с заглавной
буквы,
напри¬
мер, totalCommissions.
2.10.Разделяйте объявления и исполняемые операторы одной пустой
строкой, отмечая, таким образом, где кончаются объявления, а где
начинаются
исполняемые
2.11.0ставлять пробелы
с
операторы.
каждой стороны двухместной операции. Это
выделит операции и придаст программе более ясный вид.
2.12.
2.13.
Смещать
вправо операторы в теле if.
Помещать
в программе пустую строку над и под каждым
оператором
if для облегчения восприятия программы.
2.14.B каждой строке программы должно быть не более одного операто¬
ра.
2.15.Длинные
операторы можно записывать
необходимо разбить
в несколько строк.
Если
опе¬
старайтесь делать это
(скажем,после запятой в разделенном запятыми спис¬
ке). Если оператор разбит на две или большее число строк, смещайте
вправо все следующие за первой строки.
ратор
на несколько строк,
осмысленно
2.16.При написании выражений, содержащих много операций,
пользуйтесь таблицей старшинства операций. Следите за тем, чтобы
операции
в
выражении
выполнялись
вы не уверены в порядке выполнения
нии,
в
правильном
операций
порядке.
Если
в сложном выраже¬
используйте скобки
рядка,
для принудительного задания нужного по¬
так же как делаете это в алгебраических выражениях. Не
следует также забывать, что некоторые операции в С, например опе¬
рация присваивания
(=),
ассоциативны справа налево, а не наобо¬
рот.
Советы по переносимости программ
2.1.
Рекомендуется
использовать
возможность
процессе
идентификаторы
с количеством симво¬
31. Это гарантирует переносимость и даст
избежать некоторых трудно обнаружимых ошибок в
лов, равным или меньшим
программирования.
78
Глава 2
Упражнения
2.1.
для самоконтроля
Заполните пустые места
а) Каждая С
в
d) Для
из
следующих
и заканчивается
начинается с
оператор заканчивается
информации
функция
вывода
лиотечная
утверждений,
программа начинает выполняться с функции
b) Тело каждой функции
c) Каждый
каждом
e) Esc-код \n
.
на экран используется стандартная
биб¬
.
представляет символ
вод курсора в начальную позицию
,
следующей
вызывающий пере¬
строки экрана.
f) Для приема данных с клавиатуры используется стандартная биб¬
лиотечная функция
.
помещается в управ¬
чтобы
scanf
показать, что будет
того,
для
ляющей строке формата
g) Спецификация преобразования
вводиться целое число, и в
бы показать,
h) Всякий
что
будет
управляющей строке формата printf,
что¬
выводиться целое число.
раз, когда новое значение помещается в
ячейку
памяти,
это значение переписывает предыдущее значение, находившееся в
ячейке. Этот процесс известен как
считывание в ячей¬
ку.
i) Когда
этой
значение считывается из ячейки памяти и величина в
ячейке сохраняется,
ячейки.
считыванием из
то это называется
используется для того, чтобы принять реше¬
j) Оператор
ние.
2.2.
Установите,
верными;
a) Когда
начала
являются ли следующие утверждения верными или не¬
если
утверждение
неверно,
функция printf,
вызывается
объясните, почему.
она всегда начинает печатать с
новой строки.
b) Комментарии
выводить
на
заставляют компьютер при выполнении программы
экран
текст,
заключенный между /*
и
*/.
c) Esc-код \n при использовании в управляющей строке формата
функции printf перемещает курсор в начальную позицию следую¬
щей строки экрана.
d) Все
переменные должны быть объявлены, прежде чем
будут
испо¬
льзоваться.
e) При объявлении переменной необходимо указать ее
f) Язык С рассматривает переменные number и NuMbEr
тип.
как тожде¬
ственные.
g) Объявление можно поместить в любом месте тела функции.
h) Все аргументы функции printf, следующие за управляющей стро¬
кой формата, должны предваряться амперсандом (&).
i) Операция взятия по модулю (%) может использоваться только с
целыми
операндами.
Введение
в
79
программирование на С
j) Арифметические
/, %
операции *,
,
+,
и
имеют
-
одинаковый прио¬
ритет.
k) Верно
ли следующее утверждение: следующие имена переменных
идентичны
всех
для
С, удовлетворяющих стандарту ANSI
систем
thisisasuperduperlongnamel2 34 5 67
thisisasuperduperlongnamel234 5 68
1) Верно
необходимо
использовать три опера¬
printf.
тора
2.3. Как
С, чтобы
ли следующее утверждение: в программе на языке
вывести на печать три строки,
с
щих
одного
помощью
оператора
С
выполнить
из
каждое
следую¬
действий:
a) Объявить переменные
int: с, thisVariable, q766354
типа
и
num¬
Закончите
ваше
ber.
b) Предложить
приглашающее
вать
пробел,
пользователю
сообщение
и оставьте
ввести
целое
(:),
в позиции
непосредственно за пробе¬
двоеточием
курсор
число.
за которым должен следо¬
лом.
c) Считать
целое число, введенное с клавиатуры,
значение в
переменной
а
d) Если переменная number
и сохранить его
int.
типа
не равна
able number is not equal to 7.».
e) Напечатать сообщение «This is
f) Напечатать сообщение «This is
7,
сообщение «The vari¬
C program.»
a
а
выдать
С program.»
в
в
одну
две
строку.
строки так,
чтобы первая строка заканчивалась на С.
g) Напечатать сообщение «This is а С program.» так, чтобы каждое
слово располагалось на отдельной строке.
h) Напечатать сообщение «This is а С program.» так, чтобы каждое
слово
2.4.
располагалось
в
новой позиции табуляции.
Напишите оператор (или комментарий), содержащий:
a) Утверждение,
целых
b) Объявление,
жать
к
будет
что программа
вычислять произведение трех
чисел.
типу
что
переменные
x,
у,
z
c) Приглашение
пользователю
ввести
три
d) Считать
три целых числа с клавиатуры
ных x, у и z.
e) Вычислить
result должны принадле¬
и
int.
и
целых
числа.
сохранить их в перемен¬
произведение трех целых чисел, значения которых со¬
x, у, z и присвоить результирующее значе¬
держаться в переменных
ние переменной result.
f) Вывести на печать сообщение «The product is»
значение переменной result.
и
следом
за
ним
2.5. Используя
операторы, написанные в упражнении 2.4, напишите
полную программу, вычисляющую произведение трех целых чисел.
2.6.
Найдите и исправьте ошибки
в каждом из
следующих операторов:
8Q
Глава 2
a) printf("The
b)
value
(c
7) ;
("C is
<
less
printf
if
(c
=>
Ответы на
is
to
equal
or
less
than
7\n");
упражнения для самоконтроля
а) main, b) левая фигурная скобка ({), правая фигурная скобка (}),
с) точка с запятой, d) printf, e) новая строка, f) scanf, g) %d, h) раз¬
рушающее,
2.2.
than 7\n";
7)
printf("C
2.1.
&number);
%d\n",
scanf("%d%d",&numberl,number2);
c) if
d)
is
i)
неразрушающее,
а) Неверно. Функция printf
находится
курсор,
а
j) if.
всегда начинает печать с того места, где
курсор
может
находиться
в
любой
позиции
строки.
b) Неверно. Комментарии
вий
тирования программ
каких-либо дейст¬
Они используются для докумен¬
не вызывают выполнения
во время выполнения программы.
и для того,
чтобы
повысить их
удобочитаемость.
c) Верно.
d) Верно.
e) Верно.
f) Неверно. С
различает регистр, поэтому данные переменные воспримутся компилятором как две различные
g) Неверно. Объявления должны размещаться после левой фигурной
скобки тела функции и до любого из исполняемых операторов.
h) Неверно. Аргументы функции printf обычно не предваряются ам¬
персандом. Аргументы, следующие за управляющей строкой форма¬
функции scanf, напротив, обычно предваряются амперсандом.
Исключения из данного правила будут обсуждены в главах 6 и 7.
та
i) Верно.
j) Неверно. Операции *, / и % имеют одинаковый приоритет,
более низкий приоритет.
рации + и
k) Неверно. Некоторые
длиной более 31
2.3.
напечатать
а) int с,
оператор
printf
несколько
строк.
thisVariable,
b) printf("Enter
c) scanf("%d",
d) if
могут
различать
идентификаторы
символа.
1) Неверно. Один
жет
системы
а опе¬
q76354,
integer:
an
с
несколькими
esc-кодами
\n
number;
");
&a);
(number != 7)
printf("The variable number is
e) printf("This
is
a
C
f) printf("This
is
a
C\nprogram.\n");
not
program.\n");
g) printf("This\nis\na\nC\nprogram.\n");
h) printf("This\tis\ta\tC\tprogram.\n")
;
equal
to
7.\n");
мо¬
Введение
2.4.
в
на С
программирование
а) /* Calculate the product of three integers */
b)
int
x,
result;
z,
y,
c) printf("Enter
d)
scanf
e)
result
three
=
x
*
*
/*
Calculate
");
&z);
&y,
z;
y
the
integers:
&x,
("%d%d%d",
f) printf("The product
2.5.
81
is
%d\n",
of
product
result);
three
integers
*/
<stdio.h>
#include
main ()
{
int
x,
z,
y,
printf("Enter
scanf
result;
three
result
=
x
printf("The
return
integers:
&x,
("%d%d%d",
*
*
");
&z);
&y,
z;
y
is
product
%d\n",
result);
0;
}
2.6.
а) Ошибка: &number. Исправление: убрать значок &. Позже
будут рассмотрены исключения их этого правила.
b) Ошибка: перед number2 нет амперсанда. Исправление:
number2 необходимо
написать
&number2. Позже
в
в книге
вместо
книге
будут
рассмотрены исключения их этого правила.
c) Ошибка: точка с запятой после правой скобки в условии операто¬
ра if. Исправление: Убрать точку с запятой после правой скобки. За¬
мечание:
вие в
является
то,
что
оператор
выполнен независимо от того, истинно или ложно усло¬
if. Точка
с
запятой
правой скобки трактуется компиля¬
оператор, который ничего не делает.
после
тором как пустой оператор
d) Ошибка:
ошибки
такой
результатом
printf будет
операцию отношения => следует заменить на >=.
Упражнения
2.7.
Найдите
и
(Замечание:
ошибок):
a)
исправьте
ошибки
в некоторых
scanf("d",
value);
b) printf("The product
c)
firstNumber
d)
if
(number
largest
e)
*/
в каждом из следующих операторов
операторах ошибок может быть несколько
+
=>
==
of
%d
secondNumber
and
=
%d
is
%d"\n,
x,y);
sumOfNumbers
largest)
number;
Program to determine the largest of three integers
/*
f) Scanf("%d", anInteger);
g) printf("Remainder
of
%d
divided'by
%d
is\n",
x,
y,
x
%
y);
82
Глава 2
2.8.
h)
if
i)
print("The
j)
Printf("The value you enterd is:
=
(x
у);
printf(%d is equal
sum
Заполните пробелы
a)
b) Для
%d\n,"
x
каждом
+
из
x,
у);
у);
%d\n,
&value);
следующих утверждений:
удобочитаемости.
информации
вывода
c) Оператор
языка
это
d)
в
%d\n",
to
используется для документирования программ и по¬
вышения их
на
экран
С, предназначенный
функция
используется
для принятия
решений
.
Вычисления
e) Функция,
2.9.
is
обычно
выполняются
при
помощи
операторов
предназначенная для ввода данных с клавиатуры
Напишите оператор С
или строку,
выполняющие следующие
это
дейст¬
вия:
a) Печатает сообщение «Enter two numbers».
b) Присваивает
ной
значение произведения переменных
b
и
с
перемен¬
а.
c) Констатируйте, что программа представляет собой пример вычис¬
ления заработной платы (т.е. напишите текст, который помог бы до¬
кументировать программу).
d) Введите
три целых числа с клавиатуры и поместите их значения в
целые переменные а, b и с.
2.10.Установите,
какие из следующих
утверждений
верны, а какие нет.
Объясните свои ответы.
a) Операции в С оцениваются
b) Ниже перечисленные имена
слева
направо.
переменных являются допустимыми:
_under_bar_, m928134, t5, j7, her_sales, his__account_total,
z,
а,
b,
с,
z2.
c) Оператор printf(<<a
=
5;»);
типичный пример оператора присва¬
ивания.
d) Допустимое арифметическое выражение на С, не содержащее ско¬
бок, будет вычисляться слева направо.
e) Все ниже перечисленные имена переменных являются недопусти¬
мыми: 3g, 87, 67h2, h22, 2h.
2.11.Впишите
вместо
пробелов правильный
a) Какие арифметические операции
умножением?
ответ.
имеют приоритет,
одинаковый
с
.
b)
В случае вложенных скобок выражение какой пары скобок будет
вычисляться
в
первую
очередь
в
арифметическом выражении?
Введение
в
на С
программирование
с) Ячейка
памяти компьютера, которая в процессе выполнения про¬
может
граммы
83
в
содержать
называется
разные
моменты
различные
значения,
.
2.12.Напечатается
ли что-нибудь при исполнении каждого из нижеследу¬
ющих операторов С? Если ничего не напечатается, ответьте «ниче¬
го». Пусть x
2 и у
3.
=
=
a) printf("%d",
x);
b) printf("%d",
x
x);
+
c) printf("x=");
d) printf("x=%d",
e) printf("%d
f)
z
=
+
x
+
x
g) scanf("%d%d",
/* pnntf("x
i)
printf ("\n" )
из
&x,
+
%d",
p
+
i
в
k
&b,
*/
С
содержат
считывании
&c,
&d,
&e,
в
переменные,
ячейку?
&f);
+7;
c) printf("Destructive
d) printf("а
у);
операторов
разрушающем
+
j
+
x
;
a) scanf("%d%d%d%d%d",
b)
x);
+
у
&y);
=
у
следующих
участвующие
=
у,
у;
h)
2.13.Какие
x);
%d",
=
read-in");
5");
=
о
2.14.Дано
уравнение у
=
b)
=
c)
d)
у
у
у
e)
у
f)
у
=
=
*
а
*
а
*
(а
*
а
=
а
*
*
x)
*
*
*
x
2.15.0пределите
x
x*
(x*
+
7;
+
7) ;
+
(x
x*
x*
+
x
+
a)
x
=
b)
x
=
c)
x
=
x
2
(3
+
7;
+
2
%
*
9
будут
выполняться операции в каж¬
определите, какое значение будет
завершения вычислений.
*
3
из
7) ;
порядок, в котором
после
7
какой-нибудь
7) ;
дом из следующих операторов
иметь
ли ему
С?
7;
+
x)
x
7; соответствует
операторов
(x
x*
x)
(x
*
*
x
*
x
(а
=
*
x
+
ах
ниже
перечисленных
a) у
=
*
6
/
2
2
*
2
(3
+
-
-
(9
С,
и
1;
2
/
*
2;
3
/
(3) ) ) ) ;
2.16. Напишите программу, предлагающую пользователю ввести два чис¬
ла, затем принимающую два числа, и выводящую на печать сумму,
произведение, разность, частное и результат взятия по модулю этих
чисел.
2.17. Напишите программу, выводящую на печать числа от одного до че¬
тырех в одну строку.
Сделайте
это следующими тремя
способами:
84
Глава 2
a) Применив
один оператор
без спецификаций преобразова¬
printf
ния.
b) Применив
один оператор
printf
спецификациями пре¬
с четырьмя
образования.
c) Применив четыре оператора printf.
2.18.Напишите программу, которая предлагает
целых числа,
большее
получает
эти числа и
из чисел со словами
жно печататься
изученный
в
«is
после
пользователю ввести два
этого выводит на печать
larger». Если
сообщение «These numbers
этой
главе
оператора
вариант
же числа равны, дол¬
are
equal». Используйте
if.
2.19.Напишите программу
на С, которая принимает три различных чис¬
ла с клавиатуры и находит их сумму, среднее, произведение, наи¬
меньшее и наибольшее. Используйте изученный в этой главе вари¬
ант
оператора
if. Диалог
на
должен
экране
выглядеть
следующим
образом:
Input
three different
Sum is
integers:
13
27
34
54
Average
Product
is
18
is
4914
Smallest
is
is
Largest
13
27
считывающую радиус и выводящую на пе¬
2.20.Напишите программу,
чать диаметр окружности, ее периметр и площадь.
Используйте
для
величину 3.14159. Выполните каждое из этих вычислений внутри
printf и используйте спецификацию преобразования %f.
(Замечание: в этой главе мы рассматривали только целые константы
оператора
и переменные.
то
есть
В
числа,
3
главе
имеющие
плавающей точкой,
дробную десятичную часть.)
мы рассмотрим числа с
2.21.Напишите программу, печатающую
ромб, как показано на рисунке:
2.22. Что будет
прямоугольник, овал, стрелку
напечатано в результате выполнения данного
и
оператора?
Printf ("*\n**\n***\n****\n*****\n");
2.23. Напишите программу, которая считывает пять целых чисел, и затем
определяет наибольшее и наименьшее из них. При написании поль¬
зуйтесь
изучены
только
в
теми
методами
программирования,
которые
были
этой главе.
2.24. Напишите программу, считывающую целое число,
а затем определя¬
ющую, четное оно или нечетное, и выводящую эту
информацию
на
Введение
в
программирование на С
печать.
Четное
дает
в
(Подсказка: воспользуйтесь операцией
число должно
остатке
2.25.Напечатайте
на
ноль
свои
дую прописную
но
85
взятия
по
модулю.
быть кратно двум. Любое кратное двум
при
делении
на
буквами. Составьте
инициалы прописными
букву
число
два.)
ей символов,
из соответствующих
каж¬
как показа¬
рисунке:
2.26.Напишите программу, которая
считывает
два
целых
числа,
чего определяет, кратно ли первое второму, и выводит эту
цию на печать.
(Подсказка: воспользуйтесь операцией
после
информа¬
взятия по мо-
дулю.)
2.27. Изобразите модель шахматной
printf, а затем нарисуйте ту же
доски, используя восемь операторов
самую картинку, используя минима¬
льно возможное количество операторов
2.28. В
чем разница между понятиями
ошибка? Почему сообщение
лее
2.29.B
предпочтительным,
этом упражнении мы
printf.
фатальная ошибка
чем
заглянем немного вперед.
узнали о целых числах типа
и не
фатальная
фатальной ошибке можно считать бо¬
сообщение о не фатальной ошибке?
о
int. Однако С
со строчными и прописными
буквами,
В этой
главе вы
может также оперировать
и значительным количеством
Для внутреннего представления этих симво¬
лов С использует небольшие целые числа. Набор символов, исполь¬
специальных символов.
зуемых компьютером, с соответствующим каждому символу целым
числом называется
печатать
выполнив
целое
таблицей
число,
следующий
символов компьютера.
соответствующее,
оператор:
например,
Вы можете
на¬
прописной А,
Глава 2
86
printf("%d",
А);
Напишите программу на С, которая печатает целые числа, соответ¬
ствующие некоторым прописным и строчным буквам, цифрам и спе¬
Определите по крайней мере числа, соответст¬
*
+ / и символу
вующие следующим символам: А В С а b с 0 1 2 $
циальным символам.
пробела.
2.30. Напишите
программу, которая считывает число из пяти цифр, раз¬
деляет это число на отдельные цифры и выводит эти цифры на пе¬
чать,
отделяя одну от
льзователь
4
2.31.
2
3
Используя
ввел
3
другой тремя пробелами. Например, если
42339, программа должна напечатать.
9
только материал,
изложенный
используя табуляцию, выведите
чений:
square
0
1
4
9
16
25
36
49
64
81
100
cube
0
1
8
27
64
125
216
343
512
729
1000
данной главе, напишите
кубы чисел от 0 до 10 и,
следующую таблицу зна-
в
программу, которая вычисляет квадраты и
number
0
1
2
3
4
5
6
7
8
9
10
по¬
число
на печать
г
л
в
а
а
з
Структурная разработка
программ
Цели
Изучить
основные
методики
решения задач.
Научиться разработке алгоритмов
методом
нисходящего последовательного уточнения.
Научиться
использованию
для принятия
Научиться
структур
выбора
if
и
if/else
решений.
использованию
для многократного
структуры повторения while
операторов програм¬
исполнения
мы.
Изучить методики повторения, управляемого
счетчиком, и повторения, управляемого контрольным
значением.
Понять принципы структурного программирования.
Изучить операции инкремента, декремента
и
присваивания.
88
Глава 3
Содержание
3.1.
Введение
3.2.
Алгоритмы
3.3. Псевдокод
3.4.
Управляющие структуры
3.5. Структура выбора if
3.6.
Структура выбора if/else
3.7.
Структура
3.8.
Формулирование
счетчиком)
3.9.
Формулирование алгоритмов на основе нисходящего пошагового
уточнения: пример 2 (повторение, управляемое контрольным
значением)
повторения while
алгоритмов:
пример 1 (повторение, управляемое
З.Ю.Формулирование
алгоритмов на основе нисходящего пошагового
уточнения: пример 3 (вложенные управляющие структуры)
3.11. Операции присваивания
З.12.0перации инкремента
Резюме
и
декремента
Распространенные ошибки
программирования
Советы
эффективности
дические замечания
Упражнения для самоконтроля
ниям для самоконтроля
Упражнения
3.1.
Перед
Хороший стиль
Общие мето¬
программирования
по повышению
Ответы
к упражне¬
Введение
конкретной
написанием программы для решения
ставить себе полное представление о
проблеме
задачи важно со¬
и иметь тщательно
спланиро¬
решению. В следующих двух главах обсуждаются методы,
облегчающие разработку структурированных компьютерных программ. В раз¬
деле 4.11 мы представим краткую сводку структурного программирования,
ванный подход
к ее
которая свяжет воедино методы,
3.2.
разработанные
Алгоритмы
Решение любой задачи, связанной
полнение ряда
действий
здесь и в главе 4.
с вычислениями,
в определенном порядке.
включает в
Процедура
в виде
1. действий, которые надлежит выполнить, и
2.
порядка,
в
себя
вы¬
решения задачи
котором эти действия должны быть выполнены,
89
Структурная разработка программ
называется
алгоритмом. Следующий пример показывает
важность правильно¬
го определения порядка, в котором должны выполняться
Рассмотрим «алгоритм
активного
действия.
которому следует некий
отправиться на работу:
пробуждения»,
клерк для того, чтобы встать с постели
и
Встать с постели.
Снять пижаму.
Принять душ.
Одеться.
Позавтракать.
Отправиться на работу.
В результате этой последовательности
действий клерк приходцт на
работу
хорошо подготовленным для принятия критических решений. Предположим,
однако, что те же самые шаги выполняются в несколько ином порядке:
Встать с постели.
Снять пижаму.
Одеться.
Принять душ.
Позавтракать.
Отправиться на работу.
В этом случае наш служащий
появится на работе мокрым до нитки. Зада¬
ние порядка, в котором должны выполняться операторы, называется управле¬
нием (программой). В этой и следующей главах мы исследуем возможности
управления программами в языке
С.
3.3. Псевдокод
Псевдокод
это искусственный неформальный язык, который помогает
разрабатывать алгоритмы. Псевдокод, который мы здесь пред¬
особенно полезен для разработки алгоритмов, которые преобразуют¬
программистам
ставляем,
ся затем в структурные программы на языке
невный язык; он
языком
удобен
напоминает повсед¬
хотя и не является подлинным
программирования для компьютера.
Программы
рах. Скорее они
на псевдокоде на самом деле не выполняются на компьюте¬
просто помогают программисту «продумывать» программу пе¬
попыткой написать ее
С. В этой главе мы даем
ред
псевдокода при
Псевдокод
удобно
С. Псевдокод
и достаточно прост,
на языке программирования, таком, например, как
примеров эффективного использования
разработке структурированных программ на языке С.
несколько
состоит исключительно из символов,
поэтому программистам
вводить программы на псевдокоде в компьютер, используя для этого
редактор.
Компьютер
отображать
требова¬
Тщательно подготовленная
на псевдокоде может быть легко преобразована в соответствующую
на языке С. Во многих случаях для этого достаточно простой заме¬
может
на экране или печатать по
нию последнюю копию программы на псевдокоде.
программа
программу
ны операторов псевдокода их эквивалентами в
Псевдокод
рые
будут
выполняться после
С.
тех операторов,
действия
преобразования программы из псевдокода
состоит только из операторов
кото¬
в С
и
запуска. Объявления не являются исполняемыми операторами. Они пред¬
ставляют собой сообщения компилятору. Например, объявление
ее
Глава 3
90
int
просто
i;
сообщает компилятору
какого-либо действия при
вод или вычисление.
переменной i и дает ему указание зарезер¬
переменной. Но это объявление не вызывает
о типе
вировать место в памяти для этой
выполнении программы, как, например, ввод, вы¬
Некоторые
их назначении.
ным средством
Повторяем, псевдокод
разработки программ.
3.4.
в
программисты предпочитают
граммы на псевдокоде перечислять все переменные с
является
начале
про¬
кратким упоминанием об
неформальным
вспомогатель¬
Управляющие структуры
Обычно операторы в программе выполняются один за другим в порядке их
Это называется последовательным выполнением. Различные операто¬
записи.
ры языка
С, которые
ность указать, что
личаться
от
мы скоро
следующий
в
очередного
будем обсуждать,
дают программисту возмож¬
оператор, подлежащий выполнению, может от¬
последовательности.
Это
называется
передачей
управления.
В 60-е годы
мых группами
программного обеспечения, лежало
использование передачи управления.
который
трудностей, испытывае¬
бесконтрольное
Вина была возложена на оператор goto,
стало ясно, что в основе большинства
разработки
позволяет программисту передавать управление в программе по одно¬
му из возможных адресов в очень широком диапазоне.
мого
структурного программирования
goto».
Исследование Бома
Понятие
так называе¬
стало почти синонимичным «устране¬
нию оператора
и
Якопини1 показало,
но и при полном отсутствии операторов
что программирование возмож¬
goto. Смена
стиля программирования
«программирование без goto» стала эпохальным девизом для программи¬
стов. Но только в 70-е годы широкие круги профессиональных программистов
на
Результаты оказа¬
разработки программного обеспече¬
разработки, более частой поставке сис¬
начали принимать структурное программирование всерьез.
лись впечатляющими, поскольку группы
ния сообщали об уменьшении времени
тем в срок и завершении проектов в рамках
бюджета. Ключом
к успеху явля¬
ется попросту то, что программы, созданные на основе методов
программирования, более понятны, их проще отлаживать
и
структурного
модифицировать
и, самое главное, более вероятно, что они написаны без ошибок.
Работа Бома и Якопини показала, что все программы могут быть написа¬
ны с использованием всего трех управляющих структур, а именно последова¬
тельной структуры, структуры
выбора
и
вательная структура, по существу, является
структуры повторения. Последо¬
встроенной в язык С. Если не ука¬
зано иначе, компьютер автоматически выполняет операторы
в порядке их записи.
Фрагмент
С один
за другим
блок-схемы на рис. 3.1 иллюстрирует последо¬
вательную структуру языка С.
Блок-схема является графическим представлением алгоритма или части
алгоритма. При рисовании блок-схем используются некоторые символы спе¬
циального назначения, такие как прямоугольники,
ромбы,
овалы и кружки;
эти символы соединяются стрелками, называемыми линиями
1
перехода.
Bohm, C., and G. Jacopini, «Flow Diagrams, Turing Machines, and Languages with Only Two
Formation Rules,» Communications of theACM, Vol. 9, No. 5, May 1966, pp. 336-371.
91
Структурная разработка программ
добавить grade
x
total
total
=
counter
total + grade
=
counter
+
1
добавить 1
x
counter
Рис. 3.1. Блок-схема
последовательной структуры
Подобно псевдокоду, блок-схемы
полезны для
языка С
разработки
и
представле¬
алгоритмов, хотя большинство программистов предпочитает псевдокод.
На блок-схемах ясно видно, как действуют управляющие структуры; собст¬
ния
этой книге.
блок-схемы для последовательной структуры на
рис. 3.1. Для указания на любой тип действия, включая вычисление или опе¬
рацию ввода/вывода, мы используем символ прямоугольника, называемый
также символом действия. Линии перехода на рисунке указывают на поря¬
венно, ради этого
Рассмотрим
мы и используем их в
часть
док, в котором должны выполняться
ной
grade
жна быть
сначала значение перемен¬
действия
переменной total, а затем 1 дол¬
должно быть добавлено к значению
добавлена
к
переменной
counter. Язык С позволяет нам включать
в
структуру произвольное число действий. Как мы скоро уви¬
может быть выполнено одно действие, можно поместить после¬
последовательную
дим, везде, где
действий.
При рисовании блок-схемы, представляющей полный алгоритм, первым
символом, используемым в блок-схеме, является символ овала, содержащий
довательность из нескольких
слово
«Begin» («Начало»);
символ овала,
содержащий
слово
«End» («Конец»)
рисовании только части алгоритма, как
на рис. 3.1, символы овалов опускаются и вместо них используются символы
кружков, также называемые символами соединения.
является последним символом.
При
Возможно, наиболее важным символом при составлении блок-схем явля¬
ется символ ромба, также называемый символом принятия решения, который
указывает на необходимость
выбора. Мы обсудим
символ
ромба
в
следующем
разделе.
Язык С предоставляет программисту три типа структур выбора. В струк¬
туре выбора if (раздел 3.5) некоторое действие либо выполняется (выбирает¬
ся),
если
условие
истинно,
либо пропускается,
если
это
условие
ложно.
В
структуре выбора if/else (раздел 3.6) некоторое действие выполняется, если
условие истинно, и выполняется другое действие, если это условие ложно. В
структуре
выбора switch (обсуждаемой
в главе
4)
выполняется одно из
набора
различных действий в зависимости от значения некоторого выражения.
Структура if
называется структурой с единичным выбором, поскольку в
выбирается или игнорируется одно действие. Структура if/else называет¬
структурой с двойным выбором, поскольку в ней выбор происходит между
ней
ся
двумя альтернативными действиями. Структура switch
рой
со множественным
выбором,
льких различных действий.
называется
структу¬
поскольку в ней выбор происходит из неско¬
92
Глава 3
Язык С предусматривает три типа структур повторения, а именно цикл
(раздел 3.7) и циклы do/while и for (оба обсуждаются в главе 4).
Вот и все. В языке С имеется только семь управляющих структур: после¬
довательная, три типа выбора и три типа повторения. Любая программа на
while
языке С строится путем объединения такого количества управляющих струк¬
тур каждого типа, которое соответствует алгоритму, реализуемому програм¬
мой. Как и в случае последовательной структуры на рис. 3.1, мы увидим, что в
каждой
точке
управляющей структуре
входа
в
управляющую
с
управляющие структуры
имеются два символа в виде кружков, один в
структуру
и
одним входом
и
один
в
точке
выхода
из
одним выходом делают
нее.
Эти
написание
программ довольно простым. Управляющие структуры могут присоединяться
друг к другу путем соединения точки выхода одной управляющей структуры с
точкой входа последующей. Это очень похоже на то, как ребенок ставит куби¬
поэтому мы будем называть это суперпозицией управляю¬
Мы
щих структур.
узнаем, что существует еще только один способ их соеди¬
нения
метод, называемый вложением управляющих структур. Таким об¬
ки один на
другой,
разом, любая программа на языке
писать, может
щих структур,
быть построена
объединенных одним
Структура выбора
нам
когда-либо потребуется
на¬
из двух возможных
способов.
Структура выбора if
3.5.
используется для
направлений действий. Например,
мене равен
С, которую
всего лишь из семи различных типов управляю¬
избрания
одного из альтернативных
предположим, что
проходной балл
на экза¬
бО. Оператор псевдокода
Если оценка студента больше или равна 60
Вывести на экран «Экзамен сдан»
определяет,
является ли условие «оценка студента
тинным или ложным.
Если условие истинно,
больше
60» ис¬
«Экзамен
или равна
на экран выводится
и «выполняется» следующий по порядку оператор псевдокода (не забы¬
вайте, что псевдокод не является настоящим языком программирования).
Если условие ложно, вывод на экран игнорируется и выполняется следующий
по порядку оператор псевдокода. Обратите внимание, что вторая строка струк¬
туры выбора записана с отступом. Использование отступов не обязательно, но
сдан»
рекомендуем, поскольку это помогает акцентировать вни¬
существенных особенностях структуры программ. Мы будем уделять
особое внимание применению соглашений об отступах на протяжении всей
мы его настоятельно
мание на
книги.
Компилятор
белы, табуляции
ния
в
текст
языка
С игнорирует пробельные символы,
такие как про¬
и символы перевода строки, которые используются для введе¬
программы
отступов
и
пустых
строк.
Предыдущий условный
оператор псевдокода может быть написан на С как
if
(grade >= 60)
printf("Passed\n");
Обратите внимание, что код на С близко соответствует псевдокоду. Это
ляется одним из
ментом
свойств псевдокода, делающих
разработки
программ.
яв¬
его таким полезным инстру¬
93
Структурная разработка программ
Хороший
стиль
программирования 3.1
Единообразное применение разумных соглашений об отступах значительно повышает
удобочитаемость программы. Мы предлагаем использовать для отступа табуляцию
фиксированного размера приблизительно в 1/4 дюйма или три пробела.1
Хороший
стиль программирования 3.2
Псевдокод
часто используется для
тирования. Затем программа
Блок-схема
на рис.
на
«продумывания» программы в процессе
псевдокоде
преобразуется
3.2 иллюстрирует структуру if
в
программу
с единичным
ее
на
проек¬
С.
выбором.
Эта блок-схема
содержит, возможно, наиболее важный из символов, использу¬
емых при составлении блок-схем
символ ромба, также называемый симво¬
лом принятия решения, который указывает на необходимость принятия реше¬
ния. Символ принятия решения содержит выражение, например, некоторое
условие, которое может быть истинным или ложным. Символ принятия реше¬
ния содержит две исходящие из него линии перехода. Одна из них показывает
направление, которое должно быть выбрано, если соответствующее выраже¬
ние истинно; другая показывает направление,
оно ложно.
В
главе
2
выбираемое
в том случае, если
мы узнали, что решения могут приниматься на основа¬
условий, содержащих операции отношения или равенства. На самом деле,
если выражение
решение может быть принято на основе любого выражения
нии
оценивается как нулевое, то оно интерпретируется как ложное, а если оно оце¬
нивается как ненулевое, то интерпретируется как истинное.
напечатать
grade >=60
"Зачет"
выбором
Рис. 3.2. Блок-схема структуры с единичным
Обратите
внимание, что структура if является
и одним выходом.
Скоро
мы узнаем, что
ющих структур также содержат
рехода)
(кроме
языка С
структурой
блок-схемы для
с одним входом
оставшихся управля¬
символов в виде кружков и
только символы прямоугольников для указания на
линий
пе¬
действия, которые
выполнить, и символы ромбов для указания на решения, которые нуж¬
но принять. Это является моделью программирования действие/решение, на
нужно
которой
мы и акцентировали свое внимание.
Можно представить себе
как бы семь ящиков, в каждом из которых содер¬
жатся управляющие структуры только одного из семи типов.
щие структуры пусты. В
их прямоугольниках и
ромбах
Эти управляю¬
ничего не написано.
94
Глава 3
Задачей программиста в таком случае является сборка программы из такого
количества управляющих структур каждого типа, которого требует алгоритм,
объединение этих управляющих структур только одним из двух возможных
способов
(суперпозиция или вложение) а затем заполнение символов действий
решений соответствующим алгоритму образом. Нам еще предстоит обсудить
разнообразие способов написания действий и решений.
и
3.6.
Структура выбора if/else
В структуре выбора if указанное действие
да
условие
в
истинно;
противном
случае
выполняется только тогда, ког¬
действие пропускается. Структура
дает программисту возможность указать, что в зависимости от
того, является ли условие истинным или ложным, должны выполняться раз¬
личные действия. Например, оператор псевдокода
выбора if/else
студента больше или равна 60
Вывести на экран «Экзамен сдан»
Если оценка
иначе
Вывести
выводит
на
на экран
экран «Экзамен
«Экзамен сдан»,
либо выводит «Экзамен
случае
не сдан»,
сдан»
не
если оценка студента
после вывода на экран «выполняется»
псевдокода.
Обратите
больше
или
если оценка студента меньше
следующий
равна 60,
60. В любом
по порядку оператор
внимание, что тело для ветви else также записано с отсту¬
пом.
Хороший
стиль программирования 3.3
Делайте отступы для обеих групп операторов структуры if/else.
Какое бы
соглашение
об отступах
вы не предпочитали, оно должно после¬
Чтение программы, кото¬
об однородности интервалов, вызывает за¬
довательно применяться во всех ваших программах.
рая не удовлетворяет соглашению
труднения.
Хороший
При
стиль программирования 3.4
наличии
нескольких
уровней отступов отступы
включать одно и то же количество дополнительных
Предыдущая
структура псевдокода
if/else
для
каждого
уровня должны
пробелов
может
быть
написана на
С
как
60)
(grade
printf ("Passed\n");
else
>=
if
printf("Failed\n");
Блок-схема
if/else. Еще
раз
на
рис.
обратите
3.3
иллюстрирует
внимание, что
поток
ными символами в блок-схеме являются
управления
в
структуре
стрелок) единствен¬
прямоугольники (для действий) и
(кроме
кружков
и
ромб (для решения). Мы продолжаем акцентировать внимание на этой модели
вычислений действие/решение. Снова вообразите большой ящик, содержа¬
щий такое количество пустых структур с двойным выбором, какое может по-
95
Структурная разработка программ
grade >=60
напечатать
напечатать
"Незачет"
"Зачет"
Рис. 3.3. Блок-схема структуры
if/else
с
двойным выбором
языка С
надобиться
для написания любой программы на С. Работа программиста,
опять же, состоит в компоновке этих структур выбора (путем их суперпозиции
и
вложения)
с
ет алгоритм,
любыми другими управляющими структурами, которых требу¬
и заполнении пустых прямоугольников и
ромбов действиями
и
решениями, соответствующими реализуемому алгоритму.
В
языке
С предусмотрена условная операция (?:), которая
тесно связана со
структурой if/else. Условная операция является единственной тернарной
(трехместной) операцией языка С, поскольку для нее требуется три операнда.
Операнды вместе с условной операцией образуют условное выражение. Пер¬
вый операнд является условием, второй операнд значением для всего условно¬
го выражения, если условие истинно, и третий операнд значением для всего
условного выражения, если условие ложно. Например, оператор printf
pnntf("%s\n", grade >= 60 ? "Passed" : "Failed");
содержит условное выражение, значением которого является
рал
«Passed» («Экзамен сдан»),
вый литерал «Failed»
управления форматом
ния
%s для
печати
printf
Значениями
>=
строковый
60 истинно,
лите¬
строко¬
grade
(«Экзамен не сдан»), если это условие ложно. Строка
функции printf содержит спецификацию преобразова¬
если условие
и
символьной строки. Таким образом, предыдущий оператор
выполняется, по существу, аналогично предыдущему оператору
в условном
if/else.
выражении могут быть также действия, которые
надлежит выполнить. Например, условное выражение
grade
>=
читается так:
sed\n»),
60
?
printf("Passed\n")
«Если оценка больше
иначе выполнить
:
printf("Failed\n");
или равна
printf(«Failed\n»)».
щей структурой if/else. Мы увидим,
что
в
60,
Это
то выполнить
printf(«Pas-
также сравнимо с предыду¬
некоторых
операции могут быть использованы там, где операторы
случаях
if/else
условные
использовать не¬
льзя.
Вложенные структуры
if/else служат для проверки составных условий,
if/else помещаются внутри других структур if/else.
Например, следующий оператор псевдокода будет печатать А для экзаменаци¬
при этом одни структуры
96
Глава 4
онных оценок, больших или равных 90, В для экзаменационных оценок, боль¬
ших или равных 80, С для экзаменационных оценок, больших или равных 70,
D для экзаменационных оценок, больших или равных 60, и F для всех других
оценок.
Если оценка
больше
студента
или
равна 90
Вывести «А»
иначе
Если оценка студента больше
Вывести «В»
или
равна 80
иначе
Если оценка студента больше
Вывести «С»
или
равна 70
иначе
Если оценка студента больше
Вывести «D»
или
равна 60
иначе
Вывести «F»
Этот псевдокод
if
(grade
>=
может
быть
написан на
С
в виде
90)
printf("A\n");
else
if (grade >= 80)
printf("B\n");
else
if (grade >= 70)
printf("C\n");
else
if (grade >= 60)
printf("D\n");
else
printf("F\n");
Если переменная
grade больше
или равна
дут истинными, однако выполняться
будет
90, первые четыре условия бу¬
только оператор
printf
после пер¬
вой проверки. После выполнения этого оператора printf ветвь else «внешнего»
оператора if/etee игнорируется. Многие программисты на С предпочитают за¬
писывать предыдущую структуру
if
(grade
>=
if
в виде
90)
printf("A\n");
else if (grade >= 80)
printf("B\n");
else if (grade >= 70)
printf("C\n");
else if (grade >= 60)
printf("D\n");
else
printf("F\n");
С
точки зрения компилятора
няя
С обе формы
являются эквивалентными.
форма широко распространена, поскольку
льных отступов вправо при написании кода.
она позволяет
Послед¬
значите¬
При наличии таких отступов в
разбиение строк и ухудшает
строке часто остается мало места, что вызывает
читаемость программы.
избегать
97
Структурная разработка программ
В теле структуры выбора if предполагается наличие только одного операто¬
Чтобы
включить в тело структуры if несколько операторов, заключите этот
ра.
операторов в фигурные скобки ({ и }). Совокупность операторов, содержа¬
щихся внутри пары фигурных скобок, называется составным оператором.
набор
Общее методическое замечание 3.1
Составной оператор может быть помещен в любое место программы, где может сто¬
ять простой оператор.
Следующий
if/else.
if
пример содержит составной оператор в ветви else структуры
>=
(grade
60)
printf("Passed.\n");
else {
printf("Failed.\n");
pnntf("You must take
}
В
этом случае,
again.\n");
course
если переменная
printf
оба оператора
this
grade
меньше
60, программа
выполняет
внутри ветви else и выводит
Failed.
You must take
в
again.
course
в которые заключены оба опера¬
предложении else. Эти фигурные скобки важны. Без фигурных скобок
Обратите
тора
this
внимание на
фигурные скобки,
оператор
printf("You must
оказался бы вне тела
от того, меньше
60
take
this
course
again.\n");
else-ветви структуры if
значение
grade
и выполнялся
Распространенная ошибка программирования
Пропуск одной
или
бы
вне зависимости
или нет.
3.1
обеих фигурных скобок, ограничивающих составной оператор
Синтаксические
ошибки проявляются
ошибки
во время
Логические
компилятором.
Фатальные логические ошибки
вылавливаются
выполнения.
причиной отказа в работе программы и ее преждевременного завер¬
шения. Логическая ошибка, не являющаяся фатальной, допускает дальней¬
шее выполнение программы, но при этом программа дает неправильные резу¬
льтаты.
являются
Распространенная ошибка программирования 3.2
Помещение точки с запятой после условия в условной структуре приводит
ошибке в структурах if с одним выбором и к синтаксической ошибке
ской
рах if
с
стиль программирования 3.5
Некоторые программисты предпочитают
скобки
скобок
4
Зак 801
структу¬
двойным выбором
Хороший
ные
к логиче¬
в
вводить начальную и завершающую
фигур¬
операторах до начала ввода операторов внутри фигурных
Это помогает избежать пропусков одной или обеих фигурных скобок.
в
составных
98
Глава 3
Общее методическое замечание 3.2
Подобно тому, как составной оператор можно помещать всюду, где может быть по¬
мещен
обще,
с
простой оператор,
так же возможно не помещать там никакого
то есть использовать
запятой
(;)
на
месте,
оператора
во¬
пустой оператор Пустой оператор представляется точкой
обычно
занимаемом
некоторым
операюром
В этом разделе мы ввели понятие составного оператора. Составной опера¬
тор может содержать объявления (как это происходит, например, в теле функ¬
ции main). В этом случае составной оператор называется блоком. Объявления
в
блоке должны помещаться в начало блока перед любыми операторами дейст¬
Мы обсудим использование блоков в главе 5. До этого времени читателю
вия.
следует
кции
избегать использования блоков (конечно,
это не относится к телу
фун¬
main).
3.7.
Структура
Структура повторения while
повторения
гократное выполнение
истинным.
Оператор
позволяет
действия
программисту
специфицировать
мно¬
до тех пор, пока некоторое условие остается
псевдокода
Пока в моем списке покупок еще остаются пункты
Сделать следующую покупку и вычеркнуть соответствующий пункт
описывает повторяющиеся
купками.
Условие
дейстзия, происходящие
во время поездки за по¬
«в моем списке покупок еще остаются пункты» может
истинным или ложным.
Если оно истинно,
то выполняется
быть
действие «Сделать
следующую покупку и вычеркнуть соответствующий пункт из списка». Это
действие будет многократно выполняться до тех пор, пока условие будет оста¬
ваться истинным. Оператор(ы), содержащийся в структуре повторения while,
составляет тело
этой структуры. Тело структуры while
может
быть простым
или составным оператором.
В конце концов условие становится ложным (когда последний предмет
списке покупок
завершается
и
приобретен
и вычеркнут из
выполняется
первый
списка). В
оператор
в
этот момент повторение
псевдокода,
следующий
за
структурой повторения.
Распространенная ошибка программирования 3.3
Отсутствие в теле структуры while действия, которое в конечном итоге приводит к
тому, что условие в структуре while становится ложным Обычно такая структура по¬
возникает ошибка, называемая «бесконеч¬
вторения никогда не будет завершена
-
ным
циклом»
Распространенная ошибка программирования 3.4
Написание
ключевого слова while с W в верхнем регистре в виде While
(не забывай¬
регистр) Все зарезерви¬
рованные ключевые слова языка С, такие как while, if и else, содержаттолько симво¬
лы нижнего регистра
те, что С является языком программирования, различающим
99
Структурная разработка программ
В
качестве примера реальной структуры while рассмотрим фрагмент про¬
граммы, разработанной для нахождения первой степени 2, превосходящей
1000. Предположим, что целочисленная переменная product была инициали¬
зирована значением 2. После завершения
повторения while
=
product
выполнения следующей структуры
переменной product будет содержаться желаемый ответ:
в
2;
while
<=
(product
2
product
*
=
1000)
product;
Блок-схема на рис. 3.4 иллюстрирует поток управления в структуре повто¬
while. Еще раз обратите внимание, что (кроме кружков и стрелок)
рения
блок-схема
содержит только символ прямоугольника и символ ромба. Снова во¬
образите себе большой ящик пустых структур while, которые путем суперпози¬
ции или вложения в другие управляющие структуры могут образовывать
структурную реализацию потока управления алгоритма. Пустые прямоуголь¬
ники
и
ромбы
Блок-схема
затем заполняются соответствующими
отчетливо демонстрирует повторение.
действиями
и решениями.
Линия перехода, выходящая
снова возвращается к блоку принятия решения, условие
которого проверяется на каждом проходе цикла до тех пор, пока оно в конечном
итоге не становится ложным. В этот момент происходит выход из структуры
из прямоугольника,
while, и
При
управление переходит к следующему
входе в структуру while значение
оператору программы.
переменной product равно 2. Пере¬
product многократно умножается на 2, последовательно принимая
значения 4, 8, 16, 32, 64, 128, 256, 512 и 1024. Когда значение переменной
product становится равным 1024, условие в структуре while, product <= 1000,
менная
становится ложным.
ной
Это завершает повторение,
и конечное значение перемен¬
равно 1024. Выполнение программы продолжается с оператора,
следующего за структурой while.
product
Рис. 3.4
Блок-схема структура повторения while
3.8.
Формулирование алгоритмов: пример
(повторение, управляемое счетчиком)
Чтобы проиллюстрировать,
как
разрабатываются алгоритмы, мы решим в
средней оценки в группе. Рассмотрим
нескольких вариантах задачу о подсчете
задачу в следующей постановке:
4*
1
100
Глава 3
Группа
из
десяти студентов сдала экзамен. В вашем распоряжении име¬
(целые числа в диапазоне от 0 до 100), полученные студен¬
этом экзамене. Определите среднюю оценку за экзамен для
ются оценки
тами
на
группы.
Средняя оценка равна сумме оценок, деленной
на число студентов.
Алгоритм
для решения этой задачи на компьютере должен предусматривать ввод
каждой
оценки, расчет среднего и вывод результата.
Давайте воспользуемся псевдокодом и составим перечень действий, подле¬
жащих выполнению, и определим порядок, в котором эти действия должны
быть выполнены. Для ввода оценок по одной за раз мы используем повторение,
управляемое счетчиком. Этот метод использует переменную, называемую счет¬
чиком, определяющую, сколько раз должна выполняться последовательность
операторов. В этом примере повторение завершается, когда значение счетчика
становится больше 10. В этом разделе мы просто представим алгоритм на псев¬
(рис. 3.5) и соответствующую программу на С (рис. 3.6). В следующем
разделе мы покажем, как разрабатываются алгоритмы на псевдокоде. Повторе¬
докоде
ние, управляемое счетчиком, часто называют
скольку число
Обратите
повторений
определенным
повторением,
по¬
известно до начала выполнения цикла.
внимание на ссылки в алгоритме на итоговую сумму и счетчик.
Итоговая сумма представляет собой переменную, которая используется для
накопления суммы ряда значений. Счетчик
это переменная, которая испо¬
льзуется для подсчета, в данном случае для подсчета числа введенных оценок.
используемые для хранения итоговых сумм, обычно должны
инициализироваться нулем перед суммированием; иначе сумма включала бы
предыдущее значение, которое хранилось в ячейке памяти итоговой суммы.
Переменные,
Переменные-счетчики обычно инициализируются нулем
или
единицей
в зави¬
конкретного алгоритма (мы представим примеры, иллюстрирую¬
щие каждый из вариантов). Неинициализированная переменная содержит в
качестве значения «мусор»
значение, которое последним хранилось в заре¬
симости
от
зервированной
для нее ячейке памяти.
Распространенная ошибка программирования 3.5
Если
счетчик
или
сумма не инициализированы, результаты работы вашей
вероятности, некорректными Это является примером логи¬
итоговая
программы будут,
ческой ошибки
по всей
Установить итоговую сумму
Установить счетчик оценок
Пока
счетчик
оценок
меньше
в
в
ноль.
единицу.
или равен
десяти
следующую оценку
Прибавить эту оценку к итоговой
Ввести
Прибавить единицу
к
сумме
счетчику оценок
оценке в группе значение
на
деленной
десять
суммыу
Вывести среднюю оценку в группе
Присвоить средней
Рис. 3.5. Алгоритм
на псевдокоде,
использующий повторение, управляемое
средней оценки в группе
для решения задачи о подсчете
счетчиком,
101
Структурная разработка программ
/*
подсчета
Программа
с
повторением,
средней оценки
управляемым
в
счетчиком
группе
*/
#include <stdio.h>
main ()
{
int counter,
total,
grade,
average;
/* этап инициализации */
total
0;
counter
1;
=
=
/*
этап
while
(
обработки */
counter <= 10
)
{
printf("Enter grade: ");
scanf("%d", &grade);
total
total + grade;
=
counter
-
+
counter
1;
}
/*
этап
average
завершения
=
total
printf("Class
return
0;
/*
*/
/ 10;
average
is
показывает,
%d\n",
что
average);
программа
успешно
завершена
*/
}
Enter
grade:
98
Enter
grade:
76
Enter
grade:
71
Enter
grade:
87
Enter
grade:
83
Enter
grade:
90
Enter
grade:
57
Enter
grade:
79
Enter
grade:
82
Enter
grade:
94
Class
average
is
Рис. 3.6. Написанная на С программа подсчета средней оценки в группе с повторением,
управляемым счетчиком, и пример ее выполнения
Хороший
стиль программирования 3.6
Инициализируйте
счетчики
и
итоговые
суммы
Обратите внимание, что расчет среднего в программе дал целочисленный
результат. На самом деле сумма оценок в этом примере равна 817, которая, бу¬
дучи поделенной на 10, должна дать 81.7, т.е. число с десятичной дробью. В
следующем разделе мы увидим, как обращаться с такими числами (называе¬
мыми числами с плавающей точкой).
102
Глава 3
3.9.
Формулирование
алгоритмов на основе
уточнения: пример 2
нисходящего пошагового
(повторение, управляемое контрольным значением)
Давайте обобщим
задачу о подсчете
средней
оценки в группе.
Рассмотрим
следующую задачу:
Разработать программу для подсчета средней оценки в группе, которая
при каждом своем запуске будет обрабатывать произвольное число оце¬
нок.
В первом примере для подсчета средней оценки
в группе число оценок
известно заранее. В настоящем примере не дается никаких
указаний
(10) было
относите¬
быть введено оценок. Программа должна обрабаты¬
число. Каким образом программа сможет определить,
льно того, сколько должно
вать
произвольное
их
когда ввод оценок должен быть
должна
быть вычислена
Один
из
прекращен? Каким образом
она узнает,
и выведена на экран средняя оценка в
способов решения этой проблемы
состоит в том,
когда
группе?
чтобы
использо¬
вать для указания на «конец ввода данных» специальное значение, называе¬
(другие названия: сигнальное
флаговое значение). Пользователь вводит
мое контрольным значением
значение,
фиктив¬
оценки до тех пор,
пока не будут введены все «правильные» оценки. После этого пользователь
вводит контрольное значение, чтобы показать, что была введена последняя
ное значение или
Повторение, управляемое контрольным значением, часто называют
неопределенным повторением, поскольку число повторений неизвестно до на¬
оценка.
чала выполнения цикла.
Очевидно, что контрольное значение должно выбираться таким образом,
чтобы его нельзя было спутать с допустимым входным значением. Поскольку
экзаменационные оценки обычно являются неотрицательными целыми числа¬
ми, приемлемым контрольным значением для этой задачи
будет -1. Таким об¬
разом, при запуске программы подсчета средней оценки в группе может быть
обработан
поток входных
значений, например, 95, 96, 75, 74, 89
и
-1. После
этого программа вычислит и выведет на экран среднее значение по группе для
оценок
95, 96, 75, 74
и
89 (-1
является контрольным значением, поэтому оно
не должно учитываться при расчете
среднего).
Распространенная ошибка программирования
Выбор
нием
в качестве
3.6
контрольного такого значения, которое является допустимым
значе¬
данных
Наш подход к написанию программы для подсчета средней оценки в груп¬
будет опираться на метод, называемый нисходящим пошаговым уточнени¬
ем, и который является неотъемлемой частью разработки хорошо структури¬
пе
рованных программ. Мы начнем с представления псевдокода для верхнего
уровня нисходящего процесса:
Определить среднюю
оценку
в
группе
за
экзамен
уровень представляет собой одно предложение, выражающее общее
назначение программы. Как таковой, верхний уровень фактически полностью
представляет программу. К сожалению, верхний уровень (как в этом случае)
Верхний
103
Структурная разработка программ
редко несет в себе достаточное количество
подробностей, необходимых
для на¬
писания программы на С.
Итак, теперь
уровень
на ряд
мы начинаем процесс уточнения.
более
Мы подразделяем верхний
мелких задач и перечисляем их в том порядке, в котором
они должны выполняться.
Это приводит
к следующему первому уточнению.
Инициализировать
переменные
Ввести, просуммировать и подсчитать
Вычислить
и
вывести
на
экран
количество
оценок
оценку для группы
среднюю
Здесь имеет место последовательная структура
должны выполняться по порядку, один за другим.
перечисленные шаги
Общее методическое замечание 3.3
Каждое уточнение, так же как и сам верхний уровень, представляет собой полную
спецификацию алгоритма, меняется только уровень детализации
Для перехода к следующему уровню детализации, то есть ко второму уточ¬
нению, мы вводим конкретные переменные. Нам нужна текущая сумма чисел,
счетчик количества обработанных чисел, переменная для приема значения
очередной вводимой оценки и переменная, в
ное среднее значение. Оператор псевдокода
Инициализировать
которой содержится
рассчитан¬
переменные
может быть уточнен следующим образом:
Инициализировать итоговую сумму
Инициализировать счетчик нулем
нулем
внимание, что нужно инициализировать только итоговую сумму и
счетчик; переменные average и grade (соответственно для рассчитанного сред¬
него значения и ввода пользователя) инициализировать не обязательно, по¬
Обратите
скольку
их
значения
перезаписываются
разрушением информации,
обсуждаемого
в
результате
в главе
процесса
ввода
с
2. Для оператора псевдоко¬
да
Ввести, просуммировать
потребуется
подсчитать
структура повторения
дится каждая оценка.
торое
и
должно
быть
Поскольку
(т.е. цикл),
мы
значением.
допустимые оценки. После ввода
в
которой
оценок
последовательно вво¬
мы не знаем заранее количества оценок, ко¬
обработано,
управляемое контрольным
количество
будем
использовать
Пользователь
по
последней допустимой
повторение,
одной будет
вводить
оценки пользователь
Программа будет осуществлять проверку после
будет введено контрольное значе¬
предыдущего оператора псевдокода тогда будет
введет контрольное значение.
ввода каждой оценки и завершит цикл, когда
ние. Уточнением
Ввести первую оценку
Пока пользователь не
ввел
контрольного
значения
Прибавить эту оценку к текущему итогу
Прибавить единицу к счетчику оценок
Ввести следующую оценку (возможно, контрольное
значение)
104
Глава 3
Обратите
с
тело
операторов, образующей
все эти операторы под
просто выравниваем
принадлежат этой структуре while.
формальное вспомогательное
Оператор псевдокода
while, чтобы
Повторим, что
разработки
средство
Вычислить и вывести на экран
может
фигурные скобки
внимание, что в псевдокоде мы не используем
последовательностью
среднюю
структуры
while. Мы
показать, что все они
это только не¬
псевдокод
программ.
оценку для группы
быть уточнен следующим образом:
Если
счетчик
не равен
нулю
Присвоить переменной
Вывести
average сумму,
деленную
на
экран average
на
экран «Не было ввода оценок»
на
счетчик
иначе
Вывести
Обратите
внимание, что мы
действуем
здесь достаточно аккуратно и
проверяем возможность деления на ноль
фатальной
оставшись невыявленной, способна привести к отказу в
(часто
называемому
рис. 3.7.
«крахом»). Полностью
ошибки, которая,
работе программы
второе уточнение показано на
Распространенная ошибка программирования 3.7
Попытка деления на ноль, являющаяся причиной фатальной ошибки
Инициализировать
Инициализировать
итоговую сумму нулем
счетчик
Ввести первую оценку
Пока пользователь
Прибавить
не
нулем
ввел
эту оценку
контрольного
значения
к
текущему итогу
счетчику оценок
Прибавить единицу к
Ввести следующую оценку (возможно, контрольное значение)
Если
счетчик
не равен
нулю
Присвоить переменной
Вывести
average сумму,
деленную
на
экран average
на
экран «Не было ввода оценок»
на
счетчик
иначе
Вывести
Рис. 3.7. Алгоритм
на псевдокоде,
использующий повторение, управляемое контрольным
средней оценки в группе
значением, для решения задачи о подсчете
Хороший
стиль программирования 3.7
При выполнении деления на выражение, значение которого может быть нулем, явно
осуществляйте проверку этого случая и обрабатывайте его соответствующим образом
(например, выводите сообщение об ошибке), и не допускайте возникновения фата¬
льной ошибки
105
Структурная разработка программ
На рис. 3.5
и
3.7
мы включаем в псевдокод несколько пустых строк для
удобочитаемости. Фактически
пустые строки разделяют эти
программы на различные этапы их выполнения.
повышения
его
Общее методическое замечание 3.4
Многие программы логически могут быть разделены на три этапа' этап инициализа¬
ции, на котором происходит инициализация переменных программы; этап обработ¬
ки, на котором происходит ввод данных и соответствующая
программы,
и
ние
окончательных
вывод
Алгоритм
средней
счете
разработан
на псевдокоде,
настройка переменных
программы, на котором происходит вычисле¬
работы
и этап завершения
результатов
приведенный на рис. 3.7, решает задачу о под¬
общей постановке. Этот алгоритм был
двух этапов уточнения. Иногда требуется боль¬
оценки в группе в более
после всего лишь
шее количество этапов.
Общее
методическое замечание 3.5
Программист завершает процесс нисходящего пошагового уточнения в тот момент,
когда алгоритм на псевдокоде специфицирован достаточно подробно для того, чтобы
программист мог преобразовать псевдокод в программу на С
на С в этом случае обычно не вызывает затруднений
Реализация программы
На рис. 3.8 показана программа на С и пример ее выполнения. Хотя вво¬
дятся только целочисленные оценки, при расчете среднего может получиться
дробное
число.
Типом int
такое число представлено
ме применяется тип данных
float для обработки
быть
не может.
чисел с
В програм¬
десятичной дробью
(называемых числами с плавающей точкой) и специальная операция, называ¬
емая операцией приведения типа, для управления расчетом среднего значе¬
ния. Более подробно эти понятия объясняются после представления програм¬
мы.
Обратите внимание на составной оператор в цикле while на рис. 3.8. По¬
вторим, что фигурные скобки необходимы для того, чтобы выполнялись все
четыре оператора внутри цикла. Без фигурных скобок последние три операто¬
ра в теле цикла вышли бы за его пределы, приводя к неправильной интерпре¬
тации этого кода:
while
(grade != -1)
total
total + grade;
=
counter
=
counter
+1;
-1
printf("Enter grade,
scanf("%d",
&grade);
to
end:
Получается бесконечный цикл,
первой
");
если пользователь не вводит
-1
в качестве
оценки.
Хороший
стиль программирования 3.8
В цикле, управляемом контрольным значением, подсказки для запроса ввода данных
должны
явно
Средние
напоминать
пользователю,
равно это
чему
значение
величины не всегда выражаются целочисленными значениями.
Часто среднее
является
значением
типа
7.2
или
-93.5, которое содержит
106
Глава 3
дробную
часть.
Эти
значения
называются
представляются типом данных
at, чтобы
^н
дробную
не потерять
/* Программа
с
#include
mam
с
плавающей точкой
как
оценки
управляемым
в
группе
контрольным
значением
*/
<stdio.h>
()
{
float
int
/*
этап
total
/*
average;
=
counter
новый
тип
данных
*/
total;
grade,
counter,
*/
инициализации
0;
0;
=
/* этап обработки */
printf("Enter grade,
scanf("%d",
&grade);
-1
to
end:
");
while ( grade != -1) {
total
total + grade;
=
counter
counter
=
+
pnntf("Enter grade,
scanf("%d",
&grade);
1;
-1
to
end:
");
}
/*
if
этап
завершения
!=
(counter
0
*/
{
(float) total / counter;
average
pnntf ("Class average is %.2f", average)
)
=
;
}
else
printf("No grades
return
0;
/*
were
показывает,
entered\n");
что
программа
успешно
завершена
*/
}
Enter
Enter
grade,
grade,
grade,
grade,
grade,
grade,
grade,
grade,
grade,
Class
average
Enter
Enter
Enter
Enter
Enter
Enter
Enter
-1
-1
-1
-1
-1
-1
-1
-1
-1
is
to
end:
75
to
end:
94
to
end:
97
to
end:
88
to
end:
70
to
end:
64
to
end:
83
to
end:
89
to
end:
-1
82.50
средней оценки в группе с повторением,
управляемым контрольным значением, и пример ее выполнения
Рис. 3.8. Написанная на С программа подсчета
и
flo¬
часть результата нашего вычисления.
средней
подсчета
повторением,
числами
float. Переменная average объявлена
107
Структурная разработка программ
Однако результат вычисления total
скольку
обе переменных total
и
/ counter
counter
является целым числом, по¬
являются целочисленными.
двух целых чисел приводит к целочисленному делению, при котором
часть результата теряется (т.е. происходит его усечение). Поскольку
Деление
дробная
сначала
дробная часть теряется до присваивания результата пе¬
ременной average. Для деления с плавающей точкой целочисленных значений
выполняется деление,
мы должны создать для них временные значения, которые являются числами
с плавающей точкой.
В
С Для решения этой задачи предусмотрена одноместная операция
языке
типа.
приведения
включает
Оператор
(float)
=
average
total
/
counter;
себя операцию приведения
типа (float), которая создает для своего
total
копию
с
плавающей точкой. Использование опера¬
операнда
временную
в
ции приведения типа подобным образом называется явным преобразованием.
Значение, хранимое в переменной total, по-прежнему является целым числом.
Вычисление теперь состоит в делении значения с плавающей точкой
ная «копия»
ной counter.
(времен¬
float) на целочисленное значение, хранимое в перемен¬
Компилятор С умеет оценивать лишь выражения с идентичными
total
типа
типами данных операндов. Чтобы гарантировать совпадение типа операндов,
компилятор выполняет над
цию возведения типа
(или
выбранными операндами так называемую опера¬
преобразование). Например, в выражении,
неявное
содержащем типы данных int и float, согласно стандарту ANSI, для операндов
типа int делаются копии, которые возводятся до типа float. В нашем примере
после создания копии переменной counter и ее возведения до типа float произ¬
плавающей точкой присваивается
предусматривается набор правил возве¬
дения для операндов различных типов. В главе 5 представлено обсуждение
водится вычисление,
переменной average.
и результат деления с
Стандартом ANSI
всех стандартных типов данных и порядка их возведения.
Операции
приведения типа существуют для любого типа данных.
Опера¬
образуется путем помещения имени типа данных в круг¬
лые скобки. Операция приведения типа является одноместной операцией, т.е.
операцией, принимающей только один операнд. В главе 2 мы изучали двухме¬
ция приведения типа
стные арифметические операции. В С также поддерживаются одноместные
варианты операций плюс (+) и минус (-), поэтому программист может напи¬
сать выражения вроде -7 или +5. Операции приведения типа ассоциируются
справа налево и имеют тот же приоритет, что и другие одноместные
операции, как, например,
уровень выше приоритета
(унарные)
Этот приоритет на один
унарный
мультипликативных операций *, / и % и на один
унарный
4- и
-.
круглых скобок.
В программе на рис. 3.8 для вывода значения переменной average исполь¬
порядок ниже приоритета
зуется спецификатор преобразования % .2f функции printf. Символ f указыва¬
ет, что будет выведено значение с плавающей точкой. Символ .2 представляет
собой
что
чной
ния
которой будет отображено это значение. Она устанавливает,
будет отображено с 2 десятичными знаками справа от десяти¬
точность, с
значение
точки.
Если используется спецификатор преобразования %f (без указа¬
то по умолчанию используется точность 6
как если Ьы был
точности),
указан спецификатор преобразования % .6f. Когда значения с
кой выводятся с указанием точности, выводимое значение
плавающей
точ¬
округляется
до
108
Глава 3
заданного числа десятичных знаков. Значение в памяти остается неизменным.
При
выполнении следующих операторов выводятся значения
"
%
2f\n"
,
3.446);
/*
выводит
3.45
*/
pnntf("%.lf\n",
3.446);
/*
выводит
3.4
*/
pnntf (
.
3.45
и
3.4.
Распространенная ошибка программирования 3.8
Указание точности для спецификации преобразования в строке управления форма¬
функции scanf является ошибочным Точность используется только в специфика¬
том
циях
преобразования функции printf.
Распространенная ошибка
программирования 3.9
Предположение, что числа с плавающей точкой
ютера с абсолютной точностью, может привести
льшинстве компьютеров числа с
будут
представлены
в памяти компь¬
к неправильным результатам
плавающей точкой представляются
только
На
бо¬
приблизи¬
тельно
Хороший
Не
стиль программирования 3.9
проверяйте
Несмотря
на
равенство
значения
на то, что числа с
плавающей точкой
с
плавающей точкой не всегда «на 100% точ¬
Например, когда мы говорим о
ны», они имеют многочисленные приложения.
«нормальной» температуре
тела в
на точность, представляемая
зывает нам температуру в
98.6 градусов (по Фаренгейту),
большим
числом знаков.
98.6 градусов,
нам не нуж¬
Когда термометр
на самом деле она может
пока¬
быть равна
просто 98.6
98.5999473210643. Суть в том, что принятие этого числа равным
прекрасно подходит для большинства приложений. Позже мы обсудим
вопрос более подробно.
Другой причиной,
по
ляется операция деления.
ло
3.3333333...
ютер выделяет
с
которой возникают
Когда мы делим 10
этот
плавающей точкой, яв¬
3, результатом является чис¬
числа с
на
бесконечной повторяющейся последовательностью 3. Компь¬
область памяти для его хранения, поэтому
лишь ограниченную
очевидно, что значение с
плавающей точкой
может
быть
только
приближен¬
ным.
3.10.
Формулирование
алгоритмов
на основе
уточнения: пример 3
управляющие структуры)
нисходящего пошагового
(вложенные
Давайте найдем
полное решение еще
одной
задачи. Мы еще раз
сформули¬
для этого псевдокод и процесс нисходящего пошаго¬
вого уточнения, и напишем соответствующую программу на С. Мы уже виде¬
руем алгоритм, используя
ли, что управляющие структуры могут
ДРУГУЮ, подобно тому, как
увидим
ребенок
единственный отличный
от предыдущего
соединяться управляющие структуры,
щей структуры
в другую.
(последовательно) помещаться одна на
кубики. В этом примере мы
укладывает
а
именно
способ, которым в С могут
одной управляю¬
вложение
109
Структурная разработка программ
Рассмотрим следующую постановку
задачи:
предлагает
студентов к сдаче
на
диплома
экзамена
государственного
получение
брокера по недвижимо¬
сти. В прошлом году несколько студентов из прослушавших этот курс
платный курс для подготовки
Колледж
экзамен на получение диплома. Естественно, что колледж хо¬
бы знать, насколько успешно его студенты сдали этот экзамен.
Вас попросили написать программу для обобщения результатов экзаме¬
сдавали
тел
на.
Вам дали
студента
мен
список
10
этих
проставлена i, если
каждого
студентов. Напротив фамилии
студент сдал экзамен, и 2, если
он экза¬
сдал.
не
Ваша программа должна анализировать результаты
экзамена следующим
об¬
разом:
1. Ввести результат каждого
ном
запросе
«Введите
экзамена
экзамена
результата
(т.е. 1 или 2). При каждом очеред¬
отображать на экране сообщение
результат».
2. Подсчитать количество результатов каждого типа.
3.
краткую сводку результатов экзамена, указав количество
студентов, сдавших экзамен, и количество студентов, его не сдавших.
Отобразить
4. Если экзамен сдали более 8 студентов, вывести сообщение «Повысить
плату за курс».
После тщательного изучения постановки задачи мы можем сделать следу¬
ющие наблюдения:
1.
Программа
зован
обработать 10 результатов
управляемый счетчиком.
должна
цикл,
экзамена.
Будет
исполь¬
2. Каждый результат экзамена представляет собой число, 1 или 2. Всякий
раз, когда программа считывает результат экзамена, она должна опре¬
делять, является ли это число 1 или 2. В нашем алгоритме мы проверя¬
ем на
нении
1. Если
в
число не равно
этой
конце
1,
главы
мы предполагаем, что это
рассматриваются
2. (В упраж¬
последствия
такого
предположения.)
3. Используются
два
счетчика
сдавших экзамен, и
другой
один
для
подсчета
числа
студентов,
для подсчета числа студентов, его не сдав¬
ших.
4. После того, как программа
шить,
сдали
ли
Давайте перейдем
экзамен
обработает
все результаты,
она должна ре¬
более 8 студентов.
Мы
к нисходящему пошаговому уточнению алгоритма.
начинаем с представления на псевдокоде верхнего уровня:
Проанализировать результаты
вышена
Повторим
плата
за
экзамена и
важно акцентировать внимание на том, что
ностью представляет программу, однако, по
уточнений, прежде
преобразовать в программу
сколько
решить, должна
ли
быть
по¬
курс
чем псевдокод можно
на
верхний уровень
пол¬
всей вероятности, потребуется
будет
естественным
С. Нашим первым уточнением
не¬
образом
является
110
Глава 3
Инициализировать переменные
Ввести десять экзаменационных
оценок и подсчитать число сданных
и несданных экзаменов
Вывести краткую
ли
быть
Хотя здесь
сводку
повышена
результатов
плата
за
экзамена
и решить,
должна
курс
мы также имеем полное представление
всей программы, необходи¬
дальнейшее уточнение. Теперь мы введем конкретные переменные. Нам по¬
требуются счетчики для регистрации количества сданных и несданных
мо
экзаменов, счетчик для управления выполнением цикла и переменная для хра¬
нения пользовательского ввода.
Инициализировать
может
Оператор
псевдокода
переменные
быть уточнен следующим образом:
Инициализировать нулем количество сданных экзаменов
Инициализировать нулем количество несданных экзаменов
Инициализировать
счетчик
студентов единицей
Обратите внимание, что инициализируются
мы. Оператор псевдокода
только счетчики и итоговые сум¬
Ввести десять экзаменационных оценок и подсчитать число сданных и
несданных экзаменов
требует цикла
Здесь известно
для последовательного ввода результатов каждого экзамена.
заранее, что имеется в точности десять результатов экзамена,
подойдет цикл, управляемый счетчиком. Структура с двойным
выбором внутри цикла (т.е. вложенная в цикл) будет определять для каждого
следовательно,
экзамена, является ли он в результате сданным или несданным, и исходя из
этого увеличивать соответствующий счетчик. Уточнением предыдущего опера¬
тора псевдокода тогда будет
Пока
студентов меньше или равен
следующий результат экзамена
счетчик
Ввести
студент сдал экзамен
Прибавить единицу к счетчику
десяти
Если
сданных экзаменов
иначе
Прибавить единицу
Прибавить единицу
Обратите
if/else,
к
к
счетчику несданных
счетчику
экзаменов
студентов
управляющей
удобочитаемой. Оператор псев¬
внимание на вставку пустых строк для выделения
структуры
делающих программу более
докода
Вывести краткую сводку результатов экзамена
ли быть повышена плата за курс
может
быть уточнен следующим образом:
Вывести
количество
сданных
экзаменов
Вывести количество несданных
экзаменов
сдали более восьми студентов
Вывести «Повысить плату за курс»
Если
экзамен
и
решить, должна
111
Структурная разработка программ
Полностью второе уточнение
на
показано
рис.
что и в данном случае для выделения структуры
ки, что повышает
удобочитаемость
3.9. Обратите внимание,
while
вставлены пустые стро¬
программы.
Инициализировать нулем количество сданных экзаменов
Инициализировать нулем количество несданных экзаменов
Инициализируйте счетчик студентов единицей
Пока
студентов меньше или равен
следующий результат экзамена
счетчик
Ввести
студент сдал экзамен
Прибавить единицу к счетчику
десяти
Если
сданных
экзаменов
иначе
Прибавить единицу
Прибавить единицу
Вывести
количество
к
к
счетчику несданных
счетчику
сданных
экзаменов
студентов
экзаменов
несданных экзаменов
Если экзамен сдали более восьми студентов
Вывести «Повысить плату за курс»
Вывести
количество
Рис. 3.9. Псевдокод для задачи
псевдокод является достаточно детализированным для
Теперь
С. Программа
3.10. Обратите внимание,
вания его в код
рис.
о результатах экзаменов
зволяющим
инициализацию
инициализация происходит
Совет по повышению
на
С
преобразо¬
и два примера ее выполнения показаны на
что мы воспользовались
объединять
с
свойством языка С,
объявлениями.
Подобного
по¬
рода
во время компиляции.
эффективности 3.1
Инициализация переменных при
их
объявлении уменьшает время
выполнения
про¬
граммы
Общее
методическое замечание 3.6
Опыт показывает, что наиболее
трудной
частью решения задачи на компьютере явля¬
алгоритма ее решения Как только соответствующий алгоритм по¬
строен, процесс написания работающей программы на С обычно не вызывает затруд¬
нений
ется
разработка
Общее методическое замечание 3.7
Многие программисты пишут программы, никогда не пользуясь никакими инстру¬
разработки программ типа псевдокода Они полагают, что поскольку их ко¬
ментами
нечной целью является решение задачи на компьютере, написание псевдокода только
задерживает
производство
конечного
продукта
112
Глава 3
/*
Анализ результатов
экзамена
*/
#include <stdio.h>
mam
()
{
/* инициализация переменных при их объявлении */
int passes
0, failures
0, student
1, result;
=
=
=
/* обрабатывает 10 студентов; цикл, управляемый
while (student <= 10) {
printf("Enter result (l=pass, 2=fail): ");
scanf("%d",
if
(result
passes
счетчиком
*/
&result);
==
=
/*
1)
+
passes
if/else
вложенная
в
while
*/
1;
else
failures
student
=
=
+
failures
student
+
1;
1;
}
printf("Passed
%d\n",
passes);
pnntf("Failed
%d\n",
failures);
if
(passes
>
8)
printf("Raise
return
0;
/*
tuition\n");
успешное
завершение
*/
}
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
2
Enter
result
l=pass,
2=fail)
2
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
2
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
2
Passed
6
Failed
4
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass, 2=fail)
1
Enter
result
l=pass,
2=fail)
1
Enter
result
l=pass,
2=fail)
1
Passed
Failed
Raise
Рис. 3.10.
9
1
tuition
Программа
на С и пример ее
выполнения для задачи
о
результатах экзаменов
113
Структурная разработка программ
Операции
3.11.
В
языке
роткой
с
может
с
С предусмотрено
несколько операций
выражений. Например, оператор
записи
=
+
с
присваивания для более ко¬
3 ;
быть сокращен
+=
присваивания
с помощью операции сложения с присваиванием += до
3;
Операция += прибавляет значение выражения справа от операции к зна¬
переменной слева от нее и сохраняет результат в переменной слева от
чению
Любой оператор вида
операции.
=
переменная
переменная операция выражение;
где операция
является
гих, которые
мы
одной
обсудим
операций +,
из двухместных
в главе
10),
может
быть
*, /
-,
или
% (дру¬
записан в виде
переменная операция= выражение;
Таким образом, присваивание с += 3 прибавляет 3 к значению перемен¬
ной с. На рис. 3.11 показаны арифметические операции присваивания, приме¬
в которых они используются,
ры выражений,
Операция при¬
Пример выра¬
сваивания
жения
int
Предположим
+=
-=
* =
/
с
с
+=
7
d
-=
*=
f
g
e
=
%=
d
3,
=
iв
=
4,
с
с
4
d
=
d
5
e
=
e
/=
3
f
=
f
%
9
g
=
g
=
по повышению
Выражение
5,
Присваивает
Пояснение
=
Рис. 3.11.
Совет
=
f
=
6,
12;
переменной
с
4
1
переменной
d
5
20
/
3
2
переменной
f
%
9
3
переменной
g
*
переменной
e
Арифметические операции присваивания
эффективности 3.2
операцией присваивания (например, с += 3) компилируется быстрее,
с + 3), поскольку переменная с в пер¬
раскрытое выражение (с
с
=
выражении оценивается только один раз,
она оценивается дважды
по повышению
из
:=
10
-
вом
Многие
g
7
+
чем эквивалентное
Совет
и соответствующие пояснения.
советов
по
в то
время как во втором выражении
эффективности 3.3
эффективности, которые мы даем в этой
первый взгляд улучшениям, поэтому у читателя
повышению
приводят к незначительным на
книге,
может
возникнуть искушение игнорировать их Дело в том, что заставить программу выпол¬
няться значительно быстрее может только совокупный эффект от всех этих мер по по¬
вышению
сении
того, значительное улучшение достигается при вне¬
незначительного усовершенствования в цикл, который может
эффективности Кроме
вроде
повторяться
бы
большое
количество
раз.
114
Глава 3
3.12.
В
языке
Операции инкремента
С предусмотрены
и
декремента
также унарная операция
инкремента ++
и уна¬
краткая информация о которых приведена на
операция декремента
с + 1 или
рис. 3.12. При увеличении переменной с на 1 вместо выражений с
--,
рная
=
1
с +=
может использоваться операция инкремента ++.
Если операции инкре¬
переменной, они называются соот¬
ветственно преинкрементными или предекрементными операциями. Если
операции инкремента или декремента помещаются после переменной, они на¬
зываются
соответственно
постинкрементными или постдекрементными
операциями. Операция преинкремента (предекремента) над переменной вызы¬
вает увеличение (уменьшение) этой переменной на 1, затем новое значение пе¬
ременной используется в выражении, в котором она появляется. Операция по¬
стинкремента (постдекремента) над переменной вызывает использование те¬
кущего значения этой переменной в выражении, вкотором она появляется, за¬
тем значение переменной увеличивается (уменьшается) на 1.
мента или декремента помещаются перед
Операция
Пример выражения
Пояснение
++
++а
Увеличивает
а в
++
а
на
выражении,
в
которое входит а,
значение а
в
выражении,
затем увеличивает а
на
в
1
Уменьшает b на 1, затем использует новое значение b
в выражении, в которое входит b
--b
b--
--
затем использует новое значение
которое входит а
Использует текущее
а++
--
1,
Использует текущее
которое
Рис. 3.12.
входит b,
Операции инкремента
значение b
затем
в
выражении,
уменьшает b
на
в
1
и декремента
В программе, приведенной на рис. 3.13, демонстрируется различие между
преинкрементной и постинкрементной версиями операции ++. Постинкре¬
ментирование переменной с вызывает ее увеличение после ее использования в
функции printf. Преинкрементирование переменной с вызывает ее увеличе¬
ние до ее использования в функции printf.
Программа отображает значение переменной с до и после использования
операции ++. Операция декремента ( ) действует аналогично.
Хороший
стиль программирования 3.10
Унарные операции следует
разделительных
Три
помещать непосредственно рядом с их операндами без
пробелов
оператора присваивания на рис. 3.10
passes
passes + 1;
failures +
=
failures
student
=
=
student
+
1;
1;
могут быть записаны более кратко с использованием
виде
операций
присваивания в
115
Структурная разработка программ
/*
Операции преинкремента
и
постинкремента
*/
<stdio.h>
#include
main ()
{
int
с;
с
5;
=
pnntf("%d\n",
pnntf("%d\n",
с);
с + + );
printf("%d\n\n",
с
/*
постинкремент
/*
преинкремент
*/
с);
5;
=
printf("%d\n",
с);
printf("%d\n",
++с);
printf("%d\n",
с);
return
/*
0;
успешное
завершение
*/
*/
}
5
5
6
5
6
6
Рис. 3.13.
Демонстрация различия между операциями преинкремента
1;
+=
passes
failures
student
и постинкремента
1;
+=
+=
1;
с использованием
операций преинкремента
в виде
++passes;
++failures;
++student;
или с операциями постинкремента
passes++;
failures++;
student++;
Здесь
обратить внимание на то, что при инкрементировании или
переменной в операторе, в который входит только эта пе¬
ременная, преинкрементная и постинкрементная формы дают один и тот же
эффект. Только при появлении переменной в контексте большего выражения
преинкрементная и постинкрементная формы имеют различный смысл (это
же верно и для операций предекремента и постдекремента).
В качестве операнда операций инкремента или декремента может быть ис¬
пользовано только простое имя переменной.
важно
декрементировании
Распространенная ошибка программирования
3.10
Попытка использовать операцию инкремента или декремента с выражением, отлич¬
ным от простого имени переменной, вроде ++(x + 1), является синтаксической ошиб¬
кой
116
Глава 3
Хороший
стиль программирования 3.11
Стандартом ANSI
в общем случае не специфицируется порядок, в котором будут оце¬
ниваться операнды той или иной операции (хотя в главе 4 мы увидим, что для неко¬
операций
правила существуют исключения) Поэтому программист
с операциями инкремента или декремента,
в которых
некоторая переменная подвергается их воздействию более одного раза.
торых
должен
из
избегать
В таблице
этого
команд
3.14
на рис.
показаны сверху вниз в порядке уме¬
Во втором столбце
ньшения их приоритета.
раций
показаны приоритет и ассоциативность представ¬
операций. Операции
ленных до сих пор
описывается ассоциативность опе¬
Обратите внимание, что
инкремента (++), декремента ( ),
для каждого уровня приоритетов.
операция
(-)
минус
(?:),
унарные операции
и приведения типа, и операции присваивания =,
+=,
-=,
*
условная
плюс
=,
/=
и
(+),
%
=
ассоциированы справа налево. В третьем столбце указаны названия различ¬
ных групп операций. Все другие операции на рис. 3.14 ассоциированы слева
направо.
Операции
0
++
--
(тип)
%
/
+
-
V
V
!
+
-
II
Л
Л II
=
9 ;
+
=
-=
*=
/
=
%
=
Рис. 3.14. Приоритет
Ассоциативность
Тип
слева
круглые скобки
направо
справа налево
унарные
слева направо
мультипликативные
слева
направо
аддитивные
слева
направо
отношения
слева
направо
равенства
справа налево
условные
справа налево
присваивания
операций, упомянутых
в книге до настоящего момента
Резюме
Решение любой задачи, связанной
выполнение
ряда
действий
в
с
вычислениями,
определенном
порядке.
включает
Процедура
в
себя
реше¬
ния задачи в виде действий, которые надлежит выполнить, и порядка,
в котором эти действия должны быть выполнены, называется алгорит¬
мом.
в котором в компьютерной программе должны вы¬
полняться операторы, называется программным управлением.
Задание порядка,
Псевдокод
искусственный неформальный язык, который помога¬
разрабатывать алгоритмы. Он напоминает повседнев¬
ный язык. Программы на псевдокоде на самом деле не выполняются на
компьютерах. Скорее они просто помогают программисту «продумы¬
это
ет программистам
вать» программу перед
ния,
таком,
например,
попыткой
как
С.
написать ее на языке программирова¬
117
Структурная разработка программ
Псевдокод
состоит исключительно из символов, поэтому программисты
могут вводить программы на псевдокоде в компьютер, редактировать и
сохранять
их.
Псевдокод
состоит
только
из
исполняемых
операторов.
Объявления
представляют собой сообщения компилятору, которые передают ему
ат¬
переменных и дают ему указание зарезервировать для них мес¬
то в памяти.
рибуты
Структура выбора используется
ных направлений действия.
для избрания одного из альтернатив¬
В структуре выбора if указанное действие
когда
условие
выполняется
только
тогда,
истинно.
указывает, что в зависимости от истинности
условия должны выполняться различные действия.
Структура выбора if/else
или
ложности
Во вложенной структуре выбора if/else может проверяться несколько
различных случаев. Если истинным оказывается более одного условия,
выполнены только операторы после первого истинного условия.
будут
Всякий раз, когда должно быть выполнено более одного оператора в
том месте программы, где обычно ожидается появление только одного
быть заключены в фигурные скобки,
образующие составной оператор. Составной оператор может быть поме¬
щен в любое место программы, где может стоять простой оператор.
оператора, эти операторы должны
пустого оператора, показывающего, что не должно выпол¬
няться никакое действие, служит точка с запятой (;) на месте, обычно
Признаком
занимаемом
некоторым
оператором.
повторения требует многократного выполнения действия до
тех пор, пока некоторое условие остается истинным.
Структура
структуры повторения while:
Формат
while
(условие)
оператор
также составной оператор или блок),
содержащийся
структуре повторения while, составляет тело этой структуры.
Оператор (а
Обычно некоторое действие,
выполняемое в теле структуры
while,
в
дол¬
жно в конечном итоге приводить к тому, что условие становится лож¬
ным. В противном случае цикл никогда не будет завершен
возникает
ошибка,
«бесконечным циклом».
называемая
В цикле, управляемом счетчиком, для определения
момента
заверше¬
ния
счетчик
проходов
цикла
используется
некоторая
переменная
цикла.
Итоговая сумма представляет собой переменную, в которой накаплива¬
ется сумма последовательности чисел. Итоговые суммы обычно должны
быть инициализированы нулем до
Блок-схема
совании
является
цикла.
графическим представлением алгоритма. При ри¬
блок-схем используются некоторые специальные символы,
кие как овалы, прямоугольники,
ками,
выполнения
называемыми
линиями
ромбы
та¬
и кружки, соединяемые стрел¬
перехода.
Символы
указывают
на
Глава 3
118
действия, которые подлежат выполнению. Линии перехода указывают
на порядок, в котором должны выполняться действия.
Символ овала, называемый
начало
и
всякого
конец
также
конечным
символом,
указывает
на
алгоритма.
Символ прямоугольника, называемый также символом действия, ука¬
зывает на вычисления любого типа или операцию ввода/вывода. Сим¬
волы
соответствуют
прямоугольников
полняются
с
помощью
операторов
действиям, которые обычно
присваивания,
или
вы¬
операторам
ввода/вывода, которые реализуются при посредстве стандартных биб¬
лиотечных функций, подобных printf и scanf.
Символ ромба,
зывает
также
называемый
жит выражение, которое может
исходят две линии перехода.
ление,
символом принятия решения, ука¬
необходимость выбора. Символ принятия решения содер¬
на
которое
показывает
должно
быть
Одна
истинным или ложным.
быть выбрано,
направление,
Из
него
линия перехода показывает направ¬
выбираемое
если
в
условие
том
истинно;
случае,
другая
когда условие
ложно.
Значение, которое содержит дробную часть, называется
вающей точкой и представляется типом данных float.
числом с пла¬
Деление
двух целых чисел приводит к целочисленному делению, при
котором
дробная
часть вычисления теряется
(т.е. происходит усечение
числа).
В
языке
С предусмотрена одноместная операция приведения
типа
(flo¬
которая создает для своего операнда временную копию с плаваю¬
щей точкой. Использование операции приведения типа подобным обра¬
зом называется явным преобразованием. Операции приведения типа
at),
существуют для любого типа данных.
Компилятор С
умеет оценивать лишь выражения с идентичными ти¬
пами данных операндов. Чтобы гарантировать совпадение типа опе¬
рандов, компилятор выполняет над выбранными операндами так на¬
зываемую операцию возведения типа
Стандартом ANSI специфицируется,
(или
неявное
преобразование).
int дела¬
float. Стандарт ANSI преду¬
что для операндов типа
ются копии, которые возводятся до типа
сматривает набор правил возведения для операндов различных
ти¬
пов.
плавающей точкой выводятся с определенным числом зна¬
десятичной точки; оно задается спецификатором преобразо¬
вания %f в операторе printf. Значение 3.456, выведенное со специфи¬
катором преобразования %.2f, отображается на экране как 3.46. Если
Значения
с
ков после
используется спецификатор преобразования %f (без указания
сти), то по умолчанию принимается точность, равная 6.
В
языке
точно¬
С предусмотрены разнообразные операции присваивания, по¬
записывать более коротко некоторые распространенные
могающие
типы
вы:
арифметических выражений присваивания. Эти операции
+=, -=, *=, /=
переменная
=
и
%=. Вообще любой оператор вида
переменная
операция выражение;
тако¬
119
Структурная разработка программ
где операция является
быть
может
записан
переменная
одной
в
из двухместных
операций +,
операция=
или
%
и
операция
декре¬
переменной на 1.
переменным в префиксной или по¬
используется в префиксной форме,
для увеличения или уменьшения значения
мента
Эти операции могут применяться
стфиксной формах. Если
переменная
сначала
к
операция
увеличивается или уменьшается на 1 и затем
Если операция применяется в постфик¬
в оценке выражения.
участвует
сной форме,
или
*, /
выражение;
В языке С предусмотрена операция инкремента ++
то
-,
виде
то переменная участвует в
на
уменьшается
оценке и
затем
увеличивается
1.
Терминология
float
первое уточнение
алгоритм
арифметические операции
присваивания: +=, -=, *=, /=
бесконечный цикл
передача управления
повторение
и
%
=
блок
повторение, управляемое счетчиком
порядок действий
блок-схема
последовательная структура
последовательное выполнение
вложенные структуры if/else
вложенные управляющие
пошаговое уточнение
пробельные символы
структуры
возведение
псевдокод
второе уточнение
решение
сигнальное значение
пустой оператор (;)
выбор
действие
деление
символ действия
на
символ завершения
символ овала
ноль
инициализация
итоговая
«конец
сумма
ввода
конечный
данных»
символ
контрольное
крах
символ прямоугольника
символ решения
goto
исключение
символы блок-схемы
значение
синтаксическая ошибка
программы
линии
составной оператор
перехода
логическая
ошибка
мультипликативные
выбора if
структура выбора if/else
структура повторения while
структура
операции
«мусор»
неопределенное
символ ромба
символ стрелки
повторение
нефатальная ошибка
неявное преобразование
структура с двойным выбором
структура с единичным выбором
структура со множественным выбором
нисходящее пошаговое уточнение
структурное программирование
округление
структуры выбора
структуры повторения
оператор goto
операция декремента
операция инкремента
( )
(++)
суперпозиция управляющих структур
счетчик
операция постдекремента
тело цикла
операция постинкремента
операция предекремента
тернарная
точность
операция преинкремента
точность по умолчанию
операция приведения
управление программой
определенное повторение
отказ программы
управляющая структура
усечение
(трехместная)
операция
120
Глава 4
управляющие структуры с одним
входом / одним выходом
фиктивное значение
флаговое значение
условие завершения
целочисленное деление
условная операция (?:)
цикл
число с плавающей точкой
шаг
фаза завершения
фаза инициализации
явное преобразование
фаза обработки
фатальная ошибка
Распространенные ошибки программирования
3.1.
Пропуск одной или обеих фигурных скобок, ограничивающих
ставной оператор.
3.2.
Помещение точки с запятой после условия в условной структуре
приводит к логической ошибке в структурах if с одним выбором и к
синтаксической ошибке
3.3.
Отсутствие
в
теле
в
структурах
if
двойным выбором.
с
while действия, которое
структуры
со¬
в
конечном
приводит к тому, что условие в структуре while становится
ложным. Обычно такая структура повторения никогда не будет за¬
итоге
3.4.
ошибка,
вершена
возникает
Написание
ключевого
While (не забывайте,
различающим
слова
что
С
называемая
while
с
W
является
в
«бесконечным циклом».
верхнем
языком
регистре
в
виде
программирования,
регистр). Все зарезервированные ключевые слова
while, if и else, содержат только символы ниж¬
языка С, такие как
него
3.5.
Если
ты
регистра.
счетчик или итоговая сумма не инициализированы,
работы вашей
Это
ными.
программы
является
примером
по
3.6.
Выбор
контрольного такого
допустимым значением данных.
3.7.
Попытка деления на ноль, являющаяся
в
результа¬
всей вероятности, некоррект¬
логической ошибки.
будут,
качестве
значения,
которое
является
причиной фатальной ошиб¬
ки.
3.8.
Указание
точности
для
спецификации преобразования
в
строке
управления форматом функции scanf является ошибочным. Точ¬
ность используется только в спецификациях преобразования функ¬
ции
printf.
3.9. Предположение,
что числа с
плавающей точкой будут представлены
абсолютной точностью, может привести к не¬
На большинстве компьютеров числа с
результатам.
в памяти компьютера с
правильным
плавающей точкой представляются
З.Ю.Попытка
Хороший
3.1.
4-
приблизительно.
использовать операцию инкремента или декремента с вы¬
ражением,
4-+(x
только
1),
стиль
отличным
является
от
простого
имени
переменной,
вроде
синтаксической ошибкой.
программирования
Единообразное применение разумных соглашений об отступах
чительно повышает
удобочитаемость
зна¬
программы. Мы предлагаем ис¬
121
Структурная разработка программ
пользовать для отступа
1/4 дюйма
в
зительно
3.2. Псевдокод
часто
табуляцию фиксированного
или три пробела.
используется
процессе ее проектирования.
в
разуется
программу
на
для
размера
«продумывания»
Затем программа
прибли¬
программы
на псевдокоде
в
преоб¬
С.
3.3.
Делайте отступы для обеих групп операторов структуры if/else.
3.4.
нескольких уровней отступов отступы для каждого
должны
уровня
содержать одно и то же количество дополнительных
При
наличии
пробелов.
3.5. Некоторые
программисты предпочитают вводить начальную и завер¬
шающую фигурные скобки в составных операторах до начала ввода
операторов внутри фигурных скобок. Это помогает избежать пропус¬
ков одной или обеих фигурных скобок.
3.6.
Инициализируйте
3.7.
При выполнении деления на выражение, значение которого может
быть нулем, явно осуществляйте проверку этого случая и обраба¬
тывайте
щение
его
об
и
счетчики
итоговые
суммы.
образом (например, выводите сооб¬
допускайте возникновения фатальной
соответствующим
ошибке),
и
не
ошибки.
3.8. В
цикле, управляемом контрольным значением, подсказки для за¬
проса ввода данных должны явно напоминать пользователю, чему
равно это значение.
3.9.
Не
проверяйте
на
равенство
З.Ю.Унарные операции следует
значения
с
плавающей точкой.
помещать непосредственно рядом с их
операндами без разделительных
пробелов.
З.Н.Стандартом ANSI в общем случае не специфицируется порядок, в
котором будут оцениваться операнды той или иной операции
(хотя в главе 4 мы увидим, что для некоторых операций из этого
правила существуют исключения). Поэтому программист должен
избегать команд с операциями инкремента или декремента,
торых
некоторая
переменная
подвергается
Советы по повышению
3.2.
ко¬
эффективности
Инициализация переменных при
выполнения
в
воздействию более
*
одного раза.
3.1.
их
их
объявлении уменьшает время
программы.
операцией присваивания (например, с += 3) компили¬
с + 3),
быстрее, чем эквивалентное раскрытое выражение (с
Выражение
с
=
руется
поскольку переменная с
один
раз,
в
то
время
в
как
первом
во
выражении
втором
оценивается
только
выражении она оценивается
дважды.
3.3.
Многие из советов по повышению эффективности, которые мы даем
в этой книге, приводят к незначительным на первый взгляд улучше¬
ниям, поэтому у
читателя может возникнуть
искушение
игнориро¬
122
Глава 3
вать их.
льно
Дело в том,
быстрее может
повышению
что заставить программу выполняться значите¬
совокупный эффект от всех этих мер по
эффективности. Кроме того, значительное улучшение
только
достигается при внесении вроде бы незначительного усовершенство¬
вания в цикл, который может повторяться большое количество раз.
Общие
3.1.
методические замечания
Составной оператор может быть помещен
где
3.2.
может
простой
стоять
любое
место программы,
Подобно тому, как составной оператор можно помещать всюду, где
может быть помещен простой оператор, так же возможно не поме¬
щать там никакого оператора
Пустой
оператор.
пятой (;)
3.3.
в
оператор.
на
вообще,
то
есть
использовать
пустой
оператор представляется помещением точки с за¬
место,
обычно
занимаемое
некоторым
оператором.
так же как и сам верхний уровень, представляет
собой полную спецификацию алгоритма; меняется только уровень
Каждое уточнение,
детализации.
3.4.
Многие программы логически могут быть разделены на три этапа:
этап инициализации, на котором происходит инициализация пере¬
менных программы;
и
данных
этап завершения
ление
3.5.
и
этап
обработки, на котором происходит ввод
настройка переменных программы; и
соответствующая
вывод
работы
программы, на котором происходит вычис¬
окончательных
результатов.
Программист завершает процесс нисходящего
пошагового
уточне¬
специфицирован до¬
того, чтобы программист мог преобразовать
на С. Реализация программы на С в этом слу¬
ния в тот момент, когда алгоритм на псевдокоде
статочно
подробно
для
псевдокод в программу
чае
обычно
3.6. Опыт
не
вызывает
затруднений.
показывает, что наиболее
трудной частью решения задачи на
разработка алгоритма ее решения. Как только
соответствующий алгоритм построен, процесс написания работаю¬
щей программы на С обычно не вызывает затруднений.
компьютере является
3.7. Многие
программисты пишут программы, никогда не пользуясь ни¬
какими инструментами разработки программ типа псевдокода. Они
полагают, что поскольку их конечной целью является решение зада¬
чи на компьютере, написание псевдокода только задерживает произ¬
водство конечного продукта.
Упражнения
3.1.
для самоконтроля
Ответьте на каждый из следующих вопросов.
в
a) Процедура решения задачи
виде
выполнить, и порядка, в котором эти
нены, называется
действий, которые надлежит
действия должны быть выпол¬
.
b) Задание
зывается
порядка, в котором компьютер выполняет операторы, на¬
.
123
Структурная разработка программ
c) Все
программы
могут
быть
написаны
управляющих структур:
с
использованием
и
,
трех
.
d) Структура выбора
используется для выполнения не¬
которого действия в случае истинности условия и для выполнения
другого действия в случае его ложности.
e) Несколько
операторов, сгруппированных вместе в фигурных скоб¬
ках
называются
({
}),
и
.
f) Структура
повторения
специфицирует многократное
выполнение оператора или группы операторов, пока некоторое усло¬
вие остается истинным.
g) Повторение набора
команд определенное число раз называется по¬
вторением,
.
h) Если заранее неизвестно, сколько раз будет повторено некоторый
набор операторов, то для завершения повторения может быть испо¬
льзовано
значение.
3.2.
Напишите четыре различных оператора С, каждый из которых при¬
бавляет 1 к целочисленной переменной x.
3.3.
Напишите одиночный оператор С для
ющих
выполнения
каждой
из следу¬
задач:
a) Присвойте
сумму x и у
менной
1
x
на
b) Умножьте
после
переменной
проведения
z
и увеличьте значение
переменной product
значение
пере¬
вычисления.
на
2, используя опера¬
цию *=.
c) Умножьте
=
и
переменной product
значение
на
2, используя операции
*.
d) Проверьте, является ли значение переменной count больше 10.
Если является, выведите на экран «Значение переменной count бо¬
льше
10».
e) Уменьшите
значение
переменной
x на
1,
затем вычтите его из пе¬
ременной total.
f) Прибавьте
ньшите
x
на
g) Вычислите
значение
переменной
x к
переменной total,
затем уме¬
1.
переменной q на значение перемен¬
результат переменной q. Напишите этот опе¬
ратор двумя различными способами.
ной divisor
и
остаток от деления
присвойте
h) Выведите значение 123.4567 с точностью до 2 знаков. Какое зна¬
будет выведено?
i) Выведите значение с плавающей точкой, равное 3.14159, с тремя
знаками после десятичной точки. Какое значение будет выведено?
чение
3.4. Напишите
оператор С для выполнения каждой из следующих задач.
a) Объявите переменные sum и x с
b) Инициализируйте переменную x
c) Инициализируйте
типом int.
единицей.
переменную sum нулем.
d) Прибавьте переменную
тат переменной sum.
x к
переменной
sum и
присвойте
резуль¬
124
Глава 3
e) Выведите
«Сумма равна:
на экран строку
»,
за
которой
следует
значением переменной sum.
3.5.
Объедините операторы, которые вы написали в упражнении 3.4, в
программу, которая вычисляет сумму целых чисел от 1 до 10. Испо¬
льзуйте
структуру while для выполнения
ления и
инкрементных
значение
3.6.
переменной
Определите
станет
каждой
числения.
Предположите,
оператора
все
b)
result
11.
перед
равны
началом
выполнения
каждого
5.
x+ + ;
+
++x
=
равным
в цикле указанного вычис¬
должен завершиться, когда
из переменных после выполнения вы¬
что
переменные
*=
a) product
3.7.
значение
x
операций. Цикл
x;
Напишите одиночный оператор С, который
a) Вводит целочисленную переменную
x
с
b) Вводит целочисленную переменную у
с
помощью
scanf.
помощью
scanf.
c) Инициализирует целочисленную переменную i единицей.
d) Инициализирует целочисленную переменную power единицей.
e) Умножает переменную power
на x и присваивает результат пере¬
менной power.
f) Увеличивает
переменную у на 1.
g) Проверяет переменную
шей или равной x.
h) Выводит
у, чтобы выяснить, является ли она мень¬
целочисленную переменную power с помощью
printf.
3.8. Напишите
ния
программу на С, которая использует операторы упражне¬
3.7 для расчета значения x в степени у. Программа должна
включать управляющую структуру повторения while.
3.9.
Найдите
a)
и
while
исправьте
(с
<=
5)
*=
product
ошибки
в
каждом
из
следующих
случаев:
{
с;
++с;
b)
scanf("%.4f",
&value);
c)
if
1)
==
(gender
printf("Woman\n");
else;
printf("Man\n") ;
З.Ю.Что неправильно в
while
sum
(z
+=
>=
следующей
структуре повторения while?
0)
z;
Ответы на упражнения для самоконтроля
3.1.
а) Алгоритмом, b) Программным управлением, с) Последователь¬
ность, выбор, повторение, d) if/else. e) Составным оператором, f)
while. g) Управляемым счетчиком, h) Контрольное.
125
Структурная разработка программ
3.2.
x
=
x
+=
1;
+
x
1 ;
++x;
x++;
3.3.
a)
+
x++
=
z
у;
b) product
*=
c) product
=
d)
if
>
e)
-=
f)
total
+=
g)
q
%=
=
2;
10)
("Count
total
*
product
(count
printf
q
2;
is
than
greater
10.\n");
--x;
x
;
divisor;
q
%=
divisor;
h) printf("%.2f",
123.4567);
Наэкранвыводится 123.46.
i)
3.14159);
("%.3f\n",
printf
На экран выводится 3.142.
3.4.
a) int
b)
x
=
sum,
x;
1;
c)
sum
=
d)
sum
+=
0;
or
x;
e) printf("The
3.5.
=
sum
sum
is :
sum
x;
%d\n",
/* Вычисляет сумму целых
#include <stdio.h>
mam
+
sum);
чисел
от
1
()
{
int
x
=
sum
x;
sum,
1;
0;
=
while
<=
(x
sum
+=
10)
{
x;
++x;
}
printf
("The
sum
is:
}
3.6.
b)
3.7.
result
25,
=
a) product
=
12,
a) scanf("%d",
=
x
x
=
&x);
b) scanf("%d", &y);
c)
i
d)
power
=
1;
=
1;
6;
б;
%d\n",
sUm);
до
10
*/
126
Глава 3
e)
power
f)
у++;
g) if(у
*=
<=
x)
h) printf
3.8.
x;
("%d",
power);
/* возводит x в степень
#include <stdio.h>
*/
у
main()
int x,y,i,power;
i
1;
=
1;
power
scanf("%d",
&x);
scanf(%"d",
&у);
=
while
<=
(i
++
{
у)
*=
power
x;
i;
printf
return
("%d",power);
0;
}
3.9.
а) Ошибка:
отсутствие
структуры
while.
завершающей правой фигурной скобки
в теле
Исправление: добавьте завершающую правую фигурную скобку
сле оператора ++c;.
b) Ошибка: точность, используемая
в
по¬
спецификации преобразования
функции scanf.
Исправление: удалите .4 из спецификации преобразования.
c) Ошибка: помещение точки с запятой после ветви else структуры
if/else приводит
выполняться
Исправление:
З.Ю.Значение
к
логической ошибке. Второй оператор
printf будет
всегда.
удалите точку с запятой после else.
переменной
Следовательно,
z
ни
возникает
разу
не
изменяется
в
структуре
while.
бесконечный цикл. Для недопущения бес¬
конечного цикла переменная z должна уменьшаться, так чтобы в ко¬
нечном
итоге
ее
значение
стало
равно
0.
Упражнения
З.Н.Найдите
мечание:
и
в
исправьте
каждом
ошибки
в
фрагменте
каждом
из следующих случаев
кода может быть более
ки):
а)
if
(age
>=
65);
printf
else
("Аде
is
greater
pnntf
("Age
is
less
than
than
or
equal
65\n") ;
to
(За¬
одной ошиб¬
65\n");
127
Структурная разработка программ
b)
int x
1, total;
while (x <=10) {
total += x;
=
++x;
}
c)
(x
<=
100)
total
=+
x;
While
++x;
d)
while
>
(у
0)
{
printf("%d\n"
у)
,
++у;
}
3.12.3аполните пропуски
a) Решение любой
в
следующих
включает
задачи
определенном
b)
из
каждом
утверждений:
выполнение
действий
ряда
в
.
Синонимом для процедуры
c) Переменная,
в
которой
называется
является
накапливается
.
сумма
нескольких
чисел,
.
d) Процедура
присвоения некоторым переменным
значений в начале программы называется
e) Специальное
определенных
.
значение, используемое для указания на «конец вво¬
да данных», называется
,
или
,
значением.
f)
графическим представлением алгоритма.
является
g) В блок-схеме порядок,
обозначается
ритма,
h) Конечный
в котором должны выполняться шаги алго¬
символами
символ указывает на
.
любого
и
алгоритма.
i) Символы прямоугольников соответствуют вычислениям, которые
обычно выполняются с помощью операторов
операци¬
,
ям
ввода/вывода,
стандартным
которые обычно выполняются путем
библиотечным функциям
j) Элемент, который
.
3.13.Что выводит следующая программа?
()
{
int
у
1,
=
x
while
(x
=
x
<=
*
total
10)
=
0,
у;
{
x ;
printf( "%d\n",
total += у;
у);
++x;
}
printf
return
("Total
0;
к
.
помещается внутри символа принятия реше¬
ния, называется
main
и
и
обращений
is
%d\n",
total);
128
Глава 3
3.14. Напишите одиночные операторы псевдокода, которые соответствуют
каждому из следующих действий:
a) Отобразите
b) Присвойте
на
экране
сообщение «Введите
сумму переменных x, у
и
z
два числа».
перем.енной
р.
c) Следующее условие должно быть проверено в структуре выбора
if/else: текущее значение переменной m больше удвоенного текуще¬
го значения переменной v.
d) Введите
значения
3.15.Сформулируйте
для
переменных
r
s,
и
t
с
клавиатуры.
алгоритм на псевдокоде для каждого из следующих
действий:
a) Введите
те
с клавиатуры два числа, вычислите их сумму и
b) Введите
какое
не,
отобразите
с клавиатуры два числа, определите и
(если какое-то)
c) Введите
отобрази¬
вычислений.
результат
этих
из
двух
чисел
на экра¬
больше.
с клавиатуры последовательность положительных чисел,
определите и
отобразите
на экране сумму этих чисел.
Предположи¬
те, что пользователь вводит контрольное значение -1 для указания
на
«конец ввода данных».
3.16.Установите,
какие из следующих
ми и какие
ясните
b)
показывает, что наиболее
компьютере
В
рое
высказываний
Если утверждение
являются истинны¬
объ¬
является ложным,
почему.
a) Опыт
на
ложными.
является
создание
трудной частью решения задачи
работающей программы на С.
качестве контрольного должно использоваться значение,
нельзя
c) Линии
спутать
с
допустимым
значением
кото¬
данных.
перехода указывают на действия, подлежащие выполне¬
нию.
d) Условия,
гда
записываемые внутри символов принятия решения, все¬
содержат
арифметические операции (т.е. +,
-,
*, /
и
%).
нисходящем пошаговом уточнении каждое уточнение являет¬
ся полным представлением алгоритма.
e) При
Для упражнений
с
3.17
по
3.21
выполните
каждый
из следующих шагов:
1. Прочитайте постановку задачи.
2. Сформулируйте алгоритм, используя для
нисходящее
пошаговое
3. Напишите программу
этого
псевдокод
на
4. Протестируйте, отладьте
С.
и
выполните
программу на С.
3.17.Из-за высокой цены на бензин водители интересуются
их
автомобилей. Некий водитель отследил
го
автомобиля,
записывая
каждой заправки
и
уточнение.
пробегом
пройденное расстояние в милях
Разработайте программу на С
в галлонах.
сво¬
несколько заправок свое¬
и
объем
для вво¬
пройденного расстояния в милях и объема каждой заправки в
Программа должна вычислять и отображать на экране ко¬
личество пройденных миль на галлон для каждой заправки автомо¬
да
галлонах.
биля. После обработки всей входной информациипрограмма должна
129
Структурная разработка программ
вычислить и вывести
по
всем
общее
пройденных
миль на галлон
заправкам.
Введите расход бензина
Введите пройденный
Для
количество
этой
заправки
(-1,
путь:
если
ввод
закончен):
12.8
287
получено
миль/галлон 22.421875
Введите расход бензина (-1, если ввод закончен): 10.3
Введите пройденный путь: 200
Для этой заправки получено миль/галлон 19.417475
Введите расход бензина
пройденный
введите
Для
этой
заправки
если
ввод
закончен):
5
120
получено
Введите расход бензина
миль/галлон 24.000000
(-1,
если
ввод
закончен):
-1
миль/галлон 21.601423
число
Среднее
(-1,
путь:
3.18. Разработайте программу на С, которая будет определять, превысил
ли тот или иной покупатель универмага предельный размер кредита
на своем расчетном счете.
1. Номер
на
начало
3. Общее сумма
месяца
по всем статьям расхода для данного покупателя в
месяце
4. Общее сумма
теля
каждого покупателя доступна следу¬
счета
2. Баланс
этом
Для
информация:
ющая
в
этом
всех кредитов,
отнесенный
на счет данного покупа¬
месяце
5. Допустимый предельный размер кредита
Программа должна вводить все эти данные, вычислять новый баланс
кредит) и определять, превышает
(= начальный баланс + расходы
-
предельный размер кредита покупателя. Для тех
покупателей, чей предельный размер кредита превышен, программа
должна отображать на экране номер счета покупателя, предельный
размер кредита, новый баланс и сообщение «Предельный размер
ли новый баланс
кредита превышен».
счета
(-1, если ввод закончен):
Введите начальный баланс: 5394.78
Введите общую сумму расходов: 1000.00
Введите номер
500.00
Введите
общую
Введите
предельный размер кредита:
Счет:
сумму
кредита:
100
5500.00
100
Предельный размер кредита:
Баланс:
5500.00
5894.78
Предельный размер кредита превышен.
Введите номер
счета
(-1, если ввод закончен):
Введите начальный баланс: 1000.00
Введите общую сумму расходов: 123.45
Введите общую сумму кредита: 321.00
Введите
5
Зак 801
предельный размер кредита:
1500.00
200
Глава 3
130
300
счета
(-1, если ввод закончен):
Введите начальный баланс: 500.00
Введите общую сумму расходов: 274.73
Введите номер
100.00
Введите
общую
Введите
предельный размер кредита:
сумму
Введите номер
кредита:
(-1,
счета
3.19.Некая крупная
химическая
основе комиссионных
неделю плюс
если
80
-1
закончен):
ввод
компания
платит
своим
вознаграждений. Продавцы
9 процентов
продавцам
получают
на
$200
от их валовых продаж за эту неделю.
в
На¬
пример, продавец, реализующий за неделю химических препаратов
на $5000, получает $200 плюс 9 процентов от $5000, или в сумме
$650. Разработайте
программу на С для ввода валовых продаж для
за
последнюю неделю и расчета и отображения на
каждого продавца
экране
для
заработка
одного
Введите
этого продавца.
Обрабатывайте
за один раз данные
продавца.
сумму
продаж
закончен):
в
долларах
(-1,
если
в
долларах
(-1,
если ввод закончен):
в
долларах
(-1,
если ввод закончен):
в
долларах
(-1,
если ввод
ввод
5000.00
$650.00
Зарплата:
Введите сумму продаж
1234.56
$311.11
Зарплата:
Введите сумму продаж
1088.89
$298.00
Зарплата:
Введите
3.20.Простые
interest
сумму продаж
-1
проценты по ссуде рассчитываются по формуле
=
principal
*
*
rate
days /
В предыдущей формуле принимается,
ставкой
закончен):
365
что
rate
за год и поэтому включает деление на
тайте программу
является
процентной
365 (дней). Разрабо¬
С, которая будет вводить значения principal,
будет вычислять и отображать на
экране простые проценты по каждой ссуде, используя для этого пре¬
дыдущую формулу.
rate
и
days
на
для нескольких ссуд и
Введите основную сумму ссуды
(-1,
Введите процентную
ставку:
.1
Введите
срок
в
365
Выплаты
по
Введите
основную
ссуды
простым
днях:
процентам
сумму
(-1,
.08375
если
закончен):
1000.00
$100.00
ввод
закончен):
1000.00
224
простым процентам составляют
Выплаты
по
Введите
основную сумму ссуды (-1,
.0 9
процентную ставку:
Введите
ввод
составляют
ссуды
Введите процентную ставку:
Введите срок ссуды в днях:
если
Введите
срок
Выплаты
по
Введите
основную сумму ссуды
если ввод
ссуды в днях: 1460
простым процентам составляют
(-1,
$51.40
закончен):
10000.00
$3600.00
если ввод закончен):
-1
131
Структурная разработка программ
программу на С для определения общей зарплаты
каждого из нескольких служащих. Компания платит «обычную
плату» за первые 40 часов, отработанных каждым служащим, и
луторную зарплату» за все время, отработанное сверх 40 часов.
3.21.Разработайте
предоставлены список служащих компании, число часов,
ных
каждым
за последнюю
служащим
служащего
его
общую
эту
информацию
и
и
неделю,
каждого служащего. Ваша программа должна
определить
Вам
отработан¬
почасовой тариф
ввести
для
отобразить
и
для
зар¬
«по¬
каждого
на
экране
зарплату.
Введите # отработанных
часов
(-1,
Введите почасовой тариф работника
Зарплата составляет $390.00
если
Введите # отработанных часов (-1,
Введите почасовой тариф работника
Зарплата составляет $400.00
если
Введите # отработанных часов (-1,
Введите почасовой тариф работника
Зарплата составляет $415.00
если
Введите # отработанных
если
3.22. Напишите программу
часов
на
(-1,
ввод
($00.00):
ввод
($00.00):
ввод
($00.00):
ввод
закончен):
10.00
39
закончен):
10.00
40
закончен):
10.00
41
закончен):
-1
С, показывающую различие между опера¬
циями предекремента и постдекремента, используя для этого опера¬
цию декремента
--.
3.23. Напишите программу
10, располагая их бок
мя
на
о
С, которая выводит
в одной и той же
бок
в
цикле числа от
1 до
строке и разделдя тре¬
пробелами.
3.24.Процесс нахождения наибольшего числа (т.е. максимума группы
чисел) часто используется в компьютерных приложениях. Напри¬
мер, программа для определения победителя конкурса продаж вво¬
дит количество единиц товара, проданных каждым продавцом. Про¬
реализовавший наибольшее количество единиц
побеждает в конкурсе. Напишите программу на псевдокоде,
давец,
С для ввода серии
наибольшего из этих
программу на
на печать
должна
counter;
использовать
счетчик
до
такие
из
10
чисел.
Подсказка:
отслеживания
number; текущее число, введенное
С, которая
таблицу значений:
N
5*
в
10*N
100*N
1000*N
1
10
100
1000
2
20
200
2000
3
30
300
3000
4
40
400
4000
количества
обработаны
все
введен¬
10 чисел).
программу.
наибольшее число, найденное до
на
ваша программа
переменные:
10 (т.е. для
3.25. Напишите программу
а затем
чисел и определения и вывода
ных чисел и определения момента, когда
largest;
товара,
сих
пор.
выводит в цикле следующую
132
Глава 3
5
50
500
6
60
600
6000
7
70
700
7000
8
80
800
8000
9
90
900
9000
10
100
1000
10000
Для
столбцов табуляциями
табуляции \t.
отделения
использован
5000
3.26.Напишите программу
таблицу значений:
3.27.
printf
может
быть
на С, которая порождает в цикле следующую
А
А+2
А+4
3
5
7
9
6
8
10
12
15
А+6
9
11
13
12
14
16
18
15
17
19
21
аналогичный
Применяя подход,
3.24, найдите из 10
можете
в операторе
символ
вводить
чисел два
каждое
применявшемуся
наибольших
число
только
значения.
один
в
упражнении
Замечание:
вы
раз.
3.28. Измените программу на рис. 3.10 так, чтобы она проверяла правиль¬
ность своих входных данных. После ввода любого значения, отлич¬
ного от 1 или 2, продолжайте выполнение цикла до тех пор, пока по¬
льзователь
не
введет
значения.
правильного
3.29. Что выводит следующая программа?
#include <stdio.h>
main ()
{
int
count
while
1;
=
<=
(count
10) {
count
printf("%s\n",
%
2
?
"****"
:"++++++++");
++count;
}
return
0;
}
3.30. Что выводит следующая
#include
программа?
<stdio.h>
main ()
{
int
row
while
(row
column
while
column;
10,
=
>=
=
1)
(column
printf
{
1;
<=
("%s",
++column;
10)
{
row
%
2
?
"<"
:
">");
133
Структурная разработка программ
}
--row;
printf"\n");
}
return
0;
}
3.31.(Проблема
висящего else) Определите результаты вычислений для
каждого из следующих фрагментов кода, когда x равен 9 и у равен
11, и когда x равен 11 и у равен 9. Обратите внимание, что компиля¬
тор игнорирует отступы в программе на С.
Кроме
того, компилятор
if, если
фигурных скобок {}. Поскольку на
С всегда ассоциирует else с последним предшествующим ему
не
иначе
указано
первый
посредством
взгляд программист может и не быть уверенным, какому if
соответствует
данный else,
льше
усложнить
b)
if
(x
<
if
(у
>
(Подсказка:
изучили.)
«вися¬
чтобы еще бо¬
примените соглашения об от¬
10)
10)
printf
else
("*****\n");
printf
("ff#ll\n");
pnntf
( "$$$$$\n" ) ;
if
(x
<
10)
if
(у
>
10)
printf
проблемы
из следующего кода,
задачу.
ступах, которые вы
a)
это получило название
else». Мы удалили отступы
щего
{
("*****\n");
}
else
{
printf
("lfllf\n");
pnntf
("$$$$$\n") ;
}
3.32.(Еще одна проблема
висящего
получения показанного вывода.
else) Измените следующий код для
Используйте соответствующие мето¬
ды выравнивания. Возможно, вам не придется делать никаких изме¬
нений, кроме вставки фигурных скобок. Компилятор игнорирует
выравнивание в программах на С. Мы удалили из кода отступы, что¬
бы еще больше усложнить задачу. Замечание: возможно, не
ется
никаких
if
if
(у
(x
printf
else
printf
printf
printf
8)
5)
("00000\n")
==
==
("#####\n");
("$$$$$\п");
("&&&&&\n");
а) В предположении,
00000
$$$$$
&&&&&
потребу¬
изменений.
что x
=
5
и у =
8, программа
должна вывести:
134
Глава 3
b)
В предположении,
что x
=
5
и у
предположении, что x
=
5
и у =
=
8, программа
должна вывести:
@@@@@
c) В
8, программа должна
вывести:
@@@@@
&&&&&
В предположении,
d)
ющий
ются
что x
=
5
и у
=
7,
должен быть получен следу¬
вывод. Замечание: все три последних оператора
частью
составного
printf
явля¬
оператора.
#####
$$$$$
&&&&&
3.33. Напишите программу, которая
и
затем
выводит
этот
квадрат
должна работать для всех
20. Например, если ваша
она
должна
считывает размер стороны квадрата
в
виде
Ваша программа
1 и
звездочек.
квадратов с размерами сторон между
считывает
программа
размер,
равный 4,
вывести
****
****
****
****
3.34. Измените программу, которую вы написали в упражнении 3.33 так,
чтобы она выводила полый квадрат. Например, если ваша програм¬
ма
считывает
размер,
равный 5,
она
должна
вывести
*****
*
*
*
*
*
*
*****
3.35. Палиндромом
называется число или
фраза
одинаково как слева направо, так и в
текста, которая читается
обратном
порядке.
Например,
каждое из следующих пятизначных целых чисел является палинд¬
ромом:
12321, 55555, 45554
и
11611. Напишите программу, которая
считывает пятизначное целое число и
определяет,
является ли оно
примените операции деления и взятия по
модулю для разложения числа на отдельные цифры.)
палиндромом.
(Подсказка:
3.36. Введите целое число, содержащее только 0 и 1 (т.е. «двоичное» це¬
лое число) и выведите его десятичный эквивалент. (Подсказка: при¬
мените операции деления и взятия по модулю для отделения справа
налево одного за другим разрядов «двоичного» числа. Подобно тому,
как в десятичной системе счисления цифра самого правого разряда
имеет позиционное значение 1, следующая из оставшихся цифр име¬
ет позиционное значение 10, потом 100, потом 1000 и т.д., в системе
двоичного счисления цифра самого правого разряда имеет позицион¬
ное значение 1, следующая из оставшихся цифр имеет позиционное
значение 2, потом 4, потом 8 и т.д. Таким образом, десятичное число
234 может быть интерпретировано как 4 * 1 + 3 * 10 + 2 * 100. Деся¬
тичным эквивалентом двоичного
+ 1
*
8
или
1 + 0 + 4 + 8,
1101
или
13.)
является
1
*
1 + 0
*
2 + 1
*
4
135
Структурная разработка программ
3.37. Мы все время слышим о том, как
зом
вы
можете
функционирует
циклом
определить,
ваша
собственная машина? Напишите программу с
считает от 1 до 3 000 000 с шагом 1. Всякий
while, который
раз, когда результат
это значение
мое
быстры компьютеры. Каким обра¬
быстро в действительности
насколько
для
счета становится кратным
на экран.
выполнения
По
1 000 000, выводите
рассчитайте время, требуе¬
миллиона повторений цикла.
своим часам
каждого
3.38.Напишите программу, которая выводит 100 звездочек, по одной за
один раз. После каждой десятой звездочки ваша программа должна
выводить символ новой строки.
Примените операцию
чаев,
счетчик
когда
становится
3.39. Напишите программу, которая
(выводя
результат на
(Подсказка: считайте
от
1 до 100.
взятия по модулю для распознавания всех слу¬
печать),
кратным
10.
считывает целое число и определяет
сколько
цифр
3.40. Напишите программу, которая отображает
в этом числе равно
на экране
7.
следующий
ри¬
сунок шахматной доски:
Ваша программа может использовать только три оператора
printf,
один в виде
"*
printf(
Другой
printf("
и
");
");
третий
printf ("\n");
3.41. Напишите
программу, которая последовательно выводит числа,
кратные целому числу 2, а именно 2, 4, 8, 16, 32, 64 и т.д. Ваш цикл
не должен завершаться (т. e. вы должны создать бесконечный цикл).
Что произойдет, когда
3.42. Напишите
и
будете
выполнять
эту
программу?
программу, которая считывает радиус круга
чения типа
риметра
вы
float),
(в
виде зна¬
вычисляет и выводит значения его диаметра, пе¬
площади.
Примите
для n значение 3.14159.
3.43. Что
неправильно в следующем операторе? Перепишите оператор,
чтобы он выполнял то, что, возможно, хотел сделать программист.
printf
("%d",
++(x
+
у));
3.44. Напишите программу, которая считывает три ненулевых значения
типа float и определяет, могут ли они представлять стороны треуго¬
льника,
выводя
результат на экран.
136
Глава 3
3.45. Напишите программу, которая считывает три ненулевых целых чис¬
ла и определяет, могут ли они быть сторонами прямоугольного треу¬
гольника, выводя результат на экран.
3.46. Компании необходимо передавать данные по телефону, но есть опа¬
сение, что телефоны могут прослушиваться. Все данные передаются
в виде четырехзначных целых чисел.
Представители
просили вас
шифровки
сделать
их
написать программу для
передачу
шифровать
разом: «Заменить каждую цифру значением
по
модулю 10. Затем
ванное
чтобы
счи¬
его следующим
(суммы
об¬
этой цифры и
цифру и третью,
зашифрованное целое
Напишите отдельную программу, которая вводит зашифро¬
четырехзначное целое число и дешифрует его, воссоздавая
вторую цифру
число.
компании по¬
данных,
более надежной. Ваша программа должна
тывать четырехзначное целое число и
7)
их
и
поменять местами первую
четвертую».
Затем
вывести
первоначальное число.
3.47.
Факториал неотрицательного целого числа n записывается в виде n\
(произносится «п факториал») и определяется следующим образом:
п1 п(п-1)(п-2)...(для значений n> больших или равных 1)
=
и
n\
1 (для
ra=0).
Например, 5! 5
=
4
=
3
2
1,
что
равняется
a) Напишите программу, которая
число,
вычисляет
и
выводит
его
b) Напишите программу, которая
ской константы e по формуле:
1
1
1
120.
считывает неотрицательное целое
факториал.
оценивает значение математиче¬
+
е_1+1! 2!+3!4'c) Напишите программу,
x
x2
x3
=
1+
+
й ^
+
^Г+-
которая вычисляет значение
ех
по
формуле
г
л
а
в
а
4
Управление программой
т
Цели
Научиться применению структур повторения for
и
do/while.
Изучить структуру
множественного
выбора
switch.
Научиться применению операторов управления break
и
continue.
Освоить использование логических
операций.
138
Глава 4
Содержание
4.1.
Введение
4.2. Основы структур повторения
4.3.
Повторение, управляемое
4.4.
Структура повторения for
Структура for: замечания
4.5.
4.6. Примеры
использования
4.7. Структура
со
Операторы break
4.10.Логические
рекомендации
структуры for
и
switch
выбором
do/while
continue
операции
4.11. Смешивание
операций
4.12.Краткая сводка
Резюме
и
множественным
4.8. Структура повторения
4.9.
счетчиком
по
равенства (==)
и
присваивания (=)
структурному программированию
Распространенные ошибки программирования
программирования
Советы
реносимости программ
самоконтроля
Ответы
к
упражнениям для самоконтроля
4.1.
К настоящему моменту
Хороший
стиль
эффективности Советы по пе¬
Общие методические замечания Упражнения для
по повышению
Упражнения
Введение
читатель должен чувствовать
себя уверенно в на¬
С. В этой
писании простых, но вместе с тем законченных программ на языке
главе
будет более подробно
ставлены
дополнительные
структура for и структура
ным
рассмотрено повторение, в частности,
управляющие
do/while. Будет
выбором switch. Мы обсудим оператор
структуры
будут
повторения,
а
пред¬
именно
введена структура со множествен¬
break для безотлагательного и бы¬
строго выхода из некоторых управляющих структур и оператор continue для
пропуска
дующей
оставшихся операторов тела структуры повторения и перехода к сле¬
итерации цикла. В этой главе
пользуемые для
объединения условий,
обсуждаются
логические операции, ис¬
а заканчивается она кратким
обзором
принципов структурного программирования, представленных в главах 3 и 4.
139
Управление программой
4.2. Основы структур повторения
Большинство программ содержит повторение,
группа
команд,
выполняемых
многократно
условие
продолжения
способа
повторений:
т.е.
циклы.
компьютером,
цикла остается истинным.
До
сих пор мы
1. Повторение,
управляемое счетчиком
2.
управляемое контрольным значением
Повторение,
Повторение,
управляемое счетчикфм, иногда называют
вторением, поскольку
Цикл
пока
это
некоторое
обсудили
два
определенным по¬
будет выпол¬
мы в точности знаем заранее, сколько раз
Повторение, управляемое контрольным значением, иногда называ¬
неопределенным повторением, поскольку заранее неизвестно, сколько раз
нен цикл.
ют
потребуется
выполнить цикл.
В повторении, управляемом счетчиком, для подсчета
числа
повторений
используется управляющая переменная. Управляющая переменная увеличи¬
вается (обычно на 1) всякий раз, когда выполняется тело цикла. Когда значе¬
ние
управляющей переменной показывает, что было выполнено соответствую¬
повторений, цикл завершается, и компьютер продолжает выполне¬
программы с оператора, следующего за структурой повторения.
щее число
ние
Контрольные
значения используются для управления
повторением в тех
случаях, когда:
1. Заранее
неизвестно
точное
число
повторений,
и
2. Цикл содержит операторы, которые получают данные при каждом
полнении
Контрольное
после того,
как
Контрольные
значение служит признаком
в программу
были переданы
«конца данных».
все
обычные
значения должны отличаться от возможных
4.3.
вы¬
цикла.
Повторение, управляемое
Оно вводится
элементы данных.
значений данных.
счетчиком
Для повторения, управляемого счетчиком, требуется:
1. Имя управляющей переменной (или
2. Начальное
значение
счетчика
цикла).
управляющей переменной.
3. Значение инкремента (приращения со знаком плюс) или
(приращения со знаком минус), на которое управляющая
изменяет
4. Условие,
свое
значение
после
каждого
выполнения
декремента
переменная
цикла.
в котором происходит проверка на конечное значение управ¬
ляющей переменной (т.е.
должно ли продолжаться выполнение
цикла).
Рассмотрим простую программу, показанную на рис. 4.1, которая выводит
числа от 1 до 10. Объявление
int
counter
=
1;
именует управляющую переменную
(counter), объявляет
зервирует для нее память и присваивает
объявление
ей
ее целым числом, ре¬
начальное значение, равное
не является исполняемым оператором.
1. Это
140
Глава 4
/*
Повторение,
управляемое
счетчиком
*/
#include <stdio.h>
main
()
int
counter
while
1;
=
(counter
<=
printf("%d\n",
++counter;
return
10)
{
/*
инициализация
/*
условие
/*
приращение
*/
повторения
*/
counter);
*/
0;
}
1
2
3
4
5
6
7
8
9
10
Рис. 4.1. Повторение, управляемое
счетчиком
Объявление и инициализация счетчика могли бы быть выполнены с помо¬
щью операторов
int counter;
counter
1;
=
Объявление не является исполняемым оператором, но присваивание явля¬
ется таковым. Мы используем оба метода инициализации переменных.
Оператор
++counter;
увеличивает счетчик цикла на 1 при каждом выполнении цикла. Условие про¬
должения цикла структуры while проверяет, является ли значение управляю¬
меньшим
или
10 (это последнее значение, для
равным
которого условие истинно). Обратите внимание, что тело этого цикла while вы¬
полняется и в том случае, когда управляющая переменная равна 10. Цикл за¬
щей переменной
вершается,
когда
управляющая
переменная counter
становится
переменная
становится
(++counter
<=
10
(т.е.
равной 11).
Программирующие на С сделали бы программу
инициализировав переменную counter значением 0 и
while
больше
на рис.
4.1 более краткой,
while на
заменив структуру
10)
printf("%d\n",counter);
Этот код
экономит
один
оператор,
непосредственно в условии структуры
здесь устранены
поскольку
while перед
фигурные скобки вокруг
тела
приращение
выполняется
проверкой. Кроме того,
структуры while, поскольку
его
while теперь содержит только один оператор. Написание кода в такой сжатой
манере
требует некоторой
практики.
141
Управление программой
Распространенная ошибка программирования 4.1
Ввиду того, что значения с плавающей точкой могут быть неточными, управление
циклами со счетчиком при помощи переменных с плавающей точкой может привести
к неточным значениям счетчика и ошибочным результатам проверки на окончание
цикла
Хороший
стиль
программирования 4.1
Управляйте циклами
Хороший
счетчиком
с
помощью
целочисленных
значений
стиль программирования 4.2
Делайте отступы
Хороший
со
для операторов в теле каждой
управляющей структуры
стиль программирования 4.3
Помещайте пустую строку до и после каждой управляющей структуры большого раз¬
мера для выделения ее в тексте программы
Хороший
Слишком
стиль программирования 4.4
большое
число
уровней
вложенности
может сделать
для понимания. Пытайтесь избегать использования более трех
Хороший
программу
уровней
трудной
вложенности.
стиль программирования 4.5
Сочетание междустрочного интервала до и после управляющих структур и отступов в
теле управляющих структур в
ный вид, который
пределах их заголовков
значительно
4.4.
повышает
их
придает программам двумер¬
удобочитаемость
Структура повторения for
Структура
повторения for автоматически контролирует все детали повто¬
рения, управляемого счетчиком. Для иллюстрации всей мощи цикла for да¬
вайте перепишем программу на рис. 4.1. Результат показан на рис. 4.2.
Программа работает следующим образом. Когда структура for
начинает
выполняться, управляющая переменная counter инициализируется значени¬
ем
1. Затем проверяется условие продолжения цикла counter
ку
начальное значение
и
оператор
Затем
<=
10. Посколь¬
переменной counter равно 1, условие удовлетворяется
printf выводит значение переменной counter, а именно 1.
в выражении
counter++ происходит приращение управляющей
пе¬
ременной counter
и снова начинается выполнение цикла с проверки условия
его продолжения.
Поскольку
ное значение не превышено,
управляющая переменная теперь равна 2, конеч¬
и поэтому программа снова выполняет оператор
printf. Этот
процесс продолжается до тех пор, пока управляющая переменная
это приводит к неу¬
counter не увеличивается до ее конечного значения 11
даче при проверке условия
продолжения
Выполнение программы продолжается
for (в нашем случае с оператора return
с
цикла,
и
повторение
завершается.
первого оператора после структуры
в конце
программы).
Глава 4
142
/*
счетчиком
Повторение,
управляемое
*/
<stdio.h>
структуры
#include
mam
с
использованием
for
()
{
int
/*
counter
1;
=
инициализация,
условие
повторения
/* все они включены в заголовок
for (counter
1; counter <= 10;
=
приращение
for
*/
*/
counter++)
counter);
pnntf("%d\n",
return
и
структуры
0;
Рис. 4.2. Повторение, управляемое счетчиком с использованием структуры for
На рис. 4.3 более подробно рассмотрена структура for, представленная на
в ней
рис. 4.2. Обратите внимание, что структура for «делает всю работу»
необходимые для повторения, управляемого
управляющей переменной. Если в теле цикла for
содержится более одного оператора, для определения тела цикла требуются
фигурные скобки.
Обратите внимание, что на рис. 4.2 используется условие продолжение
цикла counter <= 10. Если бы программист по ошибке написал counter < 10,
то цикл был бы выполнен только 9 раз. Это является распространенной логи¬
специфицированы
все элементы,
счетчиком с использованием
ческой ошибкой, называемой ошибкой смещения счетчика.
Распространенная ошибка программирования 4.2
Использование неправильной операции отношения или неправильного конечного
структур while или for может привести к ошибке
смещения счетчика
значения счетчика цикла в условии
Хороший
стиль программирования 4.6
Использование конечного значения в условии структур while или for и операции отно¬
шения <= помогает избежать ошибок сдвига счетчика Например, для цикла, использу¬
емого для вывода значений от 1 до 10, условием продолжения цикла должно быть co¬
unter <= 10, а не counter < 11 или counter < 10
Конечное значение
управляющей
переменной
Имя
управляющей
переменной
i
for
(int
I
counter
т
=
1;
counter <= 10;
т
counter++)
т
Ключевое
Начальное
Приращение
слово for
значение
управляющей
переменной
управляющей
переменной
Рис. 4.3. Компоненты
типичного заголовка структуры
for
143
Управление программой
Общий формат структуры for
for
(выражение1;
выражение2;
выражениеЗ)
оператор
выражение1 инициализирует переменную управления циклом, выражение2 является условием продолжения цикла и выражениеЗ служит для прира¬
щения управляющей переменной. В большинстве случаев структура for может
где
быть представлена эквивалентной
выражение!
структурой while
следующим
образом:
;
{
(выражение2)
оператор
выражениеЗ;
while
}
Из
этого правила есть исключение, которое мы
Часто
выражение1
и
выражениеЗ
обсудим в разделе 4.9.
выражений, разде¬
являются списками
ленных запятыми. Запятые здесь на самом деле являются операциями-запятыми, которые гарантируют, что списки выражений будут оцениваться слева
направо. Значением и типом списка
выражений, разделенных запятыми, яв¬
Операция-запятая
ляются значение и тип самого правого выражения в списке.
в структуре for. Ее основным назначением является
множественной инициализации и/или нескольких выражений
чаще всего используется
реализация
приращения.
Например,
одной структуре for могут быть
в
две управляющих
переменных, которые должны инициализироваться и получать приращение.
Хороший
стиль
программирования 4.7
Помещайте в разделы инициализации и приращения структуры for только выраже¬
ния, включающие управляющие переменные. Манипуляции с другими переменными
должны производиться либо перед циклом (если они выполняются только один раз,
как операторы инициализации), либо в теле цикла (если они выполняются по одному
разу
за
повторение,
как
операторы
инкремента
или
декремента).
Все три выражения в структуре for являются необязательными. Если опу¬
щено выражение2у С предполагает, что условие истинно, и возникает беско¬
нечный цикл. Выражение1 может быть опущено, если управляющая перемен¬
ная инициализируется в другом месте программы.
ВыражениеЗ
каться, если операторы приращения находятся в теле структуры
может опус¬
for
или если
вообще не требуется. Выражение приращения в структуре for
действует подобно автономному оператору С в конце тела цикла for. Поэтому
приращение
все нижеследующие
counter
=
counter
+=
выражения
counter
+
1
1
++counter
counter++
в блоке приращения структуры
граммирующие
на
for
являются эквивалентными.
С предпочитают форму counter++,
Многие про¬
так как приращение про¬
Поэтому постинкрементная форма
Поскольку переменная, получающая здесь
приращение в преинкрементной или постинкрементной форме, не входит в вы¬
ражение, обе формы приращения приводят к одному и тому же результату. Две
исходит
после
выполнения
тела
цикла.
представляется более естественной.
точки с
запятой
в
структуре for являются обязательными.
144
Глава 4
Распространенная ошибка программирования
4.3
Использование запятых вместо точек с запятой в заголовке структуры for.
Распространенная ошибка программирования
4.4
Помещение точки с запятой сразу после заголовка структуры for делает тело этой
структуры пустым оператором. Обычно это является логической ошибкой
4.5.
Структура for:
замечания и
рекомендации
1. Блоки инициализации, условия продолжения цикла
арифметические выражения. Например,
и у
10, оператор
содержать
гут
нии, что x
for
(j
=
=
2
в
предположе¬
=
<=
j
x;
и приращения мо¬
*
4
x
*
у;
j
+=
у
/
x)
эквивалентен оператору
for
2.
(j
=
2;
80;
<=
j
«Приращение»
может
j
+=
5)
быть отрицательным (в
вительности является декрементом,
мом
деле
и
этом случае оно в
дейст¬
значение счетчика цикла на са¬
уменьшается).
3. Если
условие продолжения цикла является ложным изначально, то
тело цикла не выполняется. Вместо этого выполнение продолжается с
оператора, следующего за структурой for.
4. Управляющая
переменная часто выводится на печать или используется
при вычислениях в теле цикла, но это не является обязательным. Ис¬
переменной
пользование
для управления повторением при отсутствии
ссылок на нее в теле цикла
5. Блок-схемы структур for
и
довольно
while
распространенный случай.
очень похожи.
Например,
на рис.
4.4
показана блок-схема оператора
for
(counter
=
printf("%d",
counter <=10;
1;
counter++)
counter);
Из этой блок-схемы становится понятно, что инициализация происхо¬
дит только один раз и что приращение происходит после выполнения
оператора тела цикла. Обратите внимание, что (кроме кружков и стре¬
лок) блок-схема содержит только
ромба. Снова вообразите себе, что
символы прямоугольников и символ
программист имеет доступ к
большо¬
их столько, сколько может потребо¬
му ящику пустых структур for
ваться программисту, чтобы путем их суперпозиции и вложения в дру¬
гие
управляющие
сформировать структурную реализацию
Затем, опять же, прямоугольники и
соответствующими алгоритму действиями и реше¬
структуры
потока
управления
ромбы
заполняются
алгоритма.
ниями.
Хороший
стиль
программирования 4.8
Хотя значение управляющей переменной может модифицироваться в теле цикла for,
это может привести к труднообнаружимым ошибкам. Лучше его не изменять.
145
Управление программой
Задание
counter
начального
=
1
значения
управляющей
переменной
Проверка,
не достигнуто
ли конечное
значение
counter <
<
printf("%d",
10
=
counter ++
counter);
управляющей
переменной
Изменение
Тело цикла
(это может быть
несколько операторов)
управляющей
переменной
Рис. 4.4. Блок-схема типичной структуры for
Примеры структур for
4.6.
Следующие
переменной
в
примеры демонстрируют способы изменения
структуре for.
a)Вариация управляющей переменной
for i
1;
=
100;
<=
i
i +
от
1 до 100
с
управляющей
приращением
1.
+)
b) Вариация управляющей переменной
от
100 до 1
с
приращением
-1
(декрементом 1).
for
(i
=
i
100;
>=
i--)
1;
c) Вариация управляющей переменной
for
(i
=
7;
<=
i
77;
i
+=
for
(i
20;
1
>=
2;
i
-=
7 до 77
с шагом, равным
7.
от
20 до 2
с шагом, равным
-2.
7)
d) Вариация управляющей переменной
=
от
2)
e) Вариация управляющей переменной
следующей
последовательности
f) Вариация управляющей переменной по следующей
значений: 99, 88, 77, 66, 55, 44, 33, 22, 11, 0.
последовательности
значений:
for
for
(j
(j
=
=
по
2, 5, 8, 11, 14, 17, 20.
2;
<=
з
99;
j
>=
20;
0;
j
j
+=3)
-=11)
В следующих двух примерах даны простые приложения структуры for.
Программа
на рис.
4.5 использует структуру for для суммирования
2 до 100.
всех чет¬
ных целых чисел от
но
Обратите внимание, что тело структуры for на рис. 4.5 на самом деле мож¬
было бы соединить с правой частью заголовка структуры for, используя для
этого операцию-запятую следующим
образом:
146
Глава 4
for
(number
number
2;
=
0
Инициализацию sum
структуры for.
=
100;
<=
+=
sum
также можно
number
number,
было бы объединить
+=
с
2)
блоком
ини¬
циализации
/* Суммирование с помощью
#include <stdio.h>
main ()
{
int
for
(number
=
2;
number
return
<=
100;
number
+=
2)
+=
number;
printf("Sum is %d\n",
sum
*/
for
number;
0,
=
sum
структуры
sum);
0;
Sum is 2550
Рис. 4.5. Суммирование
Хороший
Хотя
операторы, предшествующие структуре for,
это делает
Хороший
структуры for
стиль программирования 4.9
теле, часто могут
льку
с помощью
и
операторы, содержащиеся
быть объединены в заголовке структуры for, избегайте этого,
программу более трудной для чтения
в
ее
поско¬
стиль программирования 4.10
Ограничивайте,
если это возможно, размер заголовков управляющих структур
одной
строкой
Во втором примере
со
структурой for
вычисляются
сложные
проценты.
Рассмотрим
следующую постановку задачи:
Некий клиент открыл в банке 5-процентный
$1000.00. Предполагая,
считайте
и
что вся
прибыль
выведите сумму денег
сберегательный
счет
на
депозите счета, рас¬
каждого года за 10 лет.
остается на
на счете на конец
Для определения этих сумм используйте следующую формулу:
а
p( 1 + г)п
где
p
первоначально вложенная сумма (т.е. сумма, на которую
ются проценты)
=
r
n
годовая процентная
начисля¬
ставка
число лет
сумма на депозите на конец п-го года.
Эта задача требует цикла, в котором выполняется указанный расчет ба¬
ланса счета для каждого года 10-летнего периода. Решение задачи показано на
рис. 4.6.
а
147
Управление программой
/
*
Вычисление
сложных
*
/
процентов
<stdio.h>
<math.h>
#include
#include
main ()
{
mt year;
double amount,
=
principal
pnntf("%4s%21s\n",
"Year",
for
<=
(year
amount
1;
=
=
year
10;
*
principal
"Amount
year++)
+
pow(1.0
printf("%4d%21.2f\n",
rate
1000.0,
year,
=
.05;
deposit");
on
{
rate,
year);
amount);
}
return
Year
0;
Amount
1
2
3
4
5
6
deposit
1050.00
1102.50
1157.62
1215.51
1276.28
1340.10
7
1407.10
8
1477.46
9
1551.33
10
1628.89
on
Рис. 4.6. Вычисление сложного процента с помощью for
Структура for
выполняет
щую переменную от
1 до 10
10 раз
тело цикла, изменяя при этом управляю¬
с приращением
1. Хотя
в язык
С
не включена опе¬
рация возведения в степень, мы можем, тем не менее, вызвать для
этой цели
стандартную библиотечную функцию pow. Функция pow(x, у) вычисляет зна¬
чение x в степени у. Она принимает два параметра типа double и возвращает
значение типа double. Тип double представляет собой тип с плавающей точ¬
кой, очень похожий на float, но в переменной типа double может храниться
большее значение и с большей точностью, чем в переменной типа flo¬
at. Обратите внимание, что всякий раз, когда используется математическая
функция типа pow, должен быть включен заголовочный файл math.h. На са¬
мом деле без включения файла math.h эта программа работала бы неправиль¬
гораздо
но.
что
Функция
включает
ции pow
типа
требует двух параметров типа double. Обратите внимание,
year является целым числом. Заголовочный файл math.h
pow
переменная
информацию, которая
преобразовать
значение
заставляет компилятор перед вызовом
переменной year
double. Эта информация содержится
Прототипы функций
обсуждаются в главе 5. Там
в так называемом
прототипе функ¬
ции pow.
являются важным нововведением языка
С
же мы дадим краткое описание
и
других
библиотечных
Обратите
математических
функ¬
во временное представление
ANSI
функции pow
и
функций.
внимание, что мы объявили переменные amount,
principal
и
rate с типом double. Мы сделали это для простоты, поскольку имеем здесь
дело с дробными долями доллара.
148
Глава 4
Хороший
Не
стиль программирования 4.11
используйте переменных
типа
занных с денежными суммами
double
или
float для проведения вычислений,
Неточность чисел с
плавающей точкой
свя¬
может вызвать
к неправильным значениям денежных сумм. В упражнени¬
изучим возможности использования целых чисел для проведения вычислений,
ошибки, которые приведут
ях мы
связанных
с
денежными
суммами
Вот простое пояснение того, что может происходить при использовании
float или double для представления долларовых значений.
Две денежные суммы, хранящиеся в машине в переменных типа float, мо¬
гут быть равны 14.234 (выводится в виде 14.23 со спецификацией преобразо¬
вания %.2f) и 18.673 (со спецификацией преобразования %.2f выводится в
типов
18.67). При сложении этих значений получается сумма 32.907, которая
спецификацией преобразования %.2f выводится в виде 32.91. Таким обра¬
виде
со
зом, ваша распечатка может выглядеть как
14.23
+
18.67
32.91
но очевидно, что сумма отдельных чисел, в том виде, как они напечатаны, дол¬
жна быть 32.90!
переменной amount в программе используется спе¬
преобразования %21.2f. Число 21 в спецификаторе обозначает ши¬
рину поля, в которое будет выводиться значение. Ширина поля 21 указывает
Для
вывода значения
цификатор
на то, что выводимое значение
во
займет 21 позицию вывода. Число 2 специфи¬
(т.е. число позиций после десятичной точки). Если количест¬
отображаемых символов меньше ширины поля, то значение будет автома¬
точность
цирует
выровнено по правому краю. Это особенно
значений с десятичной точкой, выводимых с одной
тически
выровнять
ду
%
и
значение по
шириной
левому краю,
поля.
Обратите
полезно для выравнивания
и
той
поместите символ
внимание,
же точностью.
«-»
Чтобы
(знак минус)
что знак минус также
меж¬
может
применяться для выравнивания по левому краю целых чисел
в
%-6d)
и символьных строк
эти мощные средства
4.7.
(как,
например,
в
%-8s).
форматирования функций printf
Структура
(как, например,
Мы подробно обсудим
и scanf в главе 9.
выбором
со множественным
switch
обсуждали структуру с одним выбором if и структуру с двой¬
if/else. Иногда алгоритм содержит последовательность принятия
решений, когда происходит независимая проверка переменной или выражения
В главе 3
мы
ным выбором
на равенство каждому из константных целочисленных
значений, которые они
дейст¬
могут принимать, и в зависимости от этого предпринимаются различные
вия.
В
языке
С для обработки
такого рода
шений, предусмотрена структура
Структура switch
fault. В программе
состоит из ряда меток case и
на рис.
личества различных
ситуаций,
связанных с принятием ре¬
со множественным
выбором switch.
необязательного блока de¬
4.7 структура switch используется для подсчета
буквенных
ко¬
оценок, полученных студентами на экзамене.
149
Управление программой
/* Подсчет буквенных
#include <stdio.h>
mam
оценок
*/
()
{
int grade;
int aCount
dCount
=
0,
=
0, bCount
fCount
0;
=
0,
cCount
0,
=
=
printf("Enter the letter grades, \n");
pnntf("Enter the EOF character to end mput.
while
(
(
grade
switch
=
getchar()
{
(grade)
case
'A':
case
'a':
++aCount;
break;
case
'В':
case
'b'
++bCount;
break;
case
'С':
case
'с'
++cCount;
)
!= EOF)
/*
switch,
/*
/*
или
{
вложенная
grade равна А
а
в
grade равна
/*
или
/*
/*
или
/*
/*
или
/*
/*
или
/*
этот
в
В
в
в
while
верхнем
регистре
в
нижнем
grade равна С
с
в
нижнем
/*
b
\n");
верхнем
регистре
в
нижнем
верхнем
регистре
*/
*/
*/
*/
*/
*/
*/
break;
case
'D':
case
'd'
++dCount;
break;
case
'F':
case
++fCount;
break;
case
'\n':
case
'f'
grade равна D
d
в
grade равна
f
в
в
нижнем
F
нижнем
ввод
верхнем
регистре
в
верхнем
регистре
игнорируется
*/
*/
*/
*/
*/
break;
default:
/* ловит все другие символы */
pnntf("Incorrect letter grade entered.");
printf(" Enter а new grade, \n");
break;
printf("\nTotals for each letter grade are:\n")
%d\n", aCount)
printf("А
%d\n", bCount)
printf("В
%d\n", cCount)
pnntf ("С
%d\n", dCount)
printf("D
%d\n", eCount)
pnntf("E
%d\n", fCount)
pnntf("F
return
0;
Рис. 4.7.
Пример
использования структуры switch
(часть 1
из
2)
150
Глава 4
Enter the letter grades.
Enter the EOF character to end input.
A
B
C
c
A
D
F
C
E
Incorrect letter grade entered.
Enter
a
grade.
new
D
A
B
Totals
A:
3
B:
2
C:
3
D:
2
F:
1
for
each
Рис. 4.7.
В программе
Пример
(
grade
are:
использования структуры switch
пользователь вводит
заголовка структуры
while
letter
буквенные
(часть 2
из
2)
оценки для группы.
,
Внутри
while
( grade
=
getchar()
)
!= EOF)
сначала выполняется заключенное в круглые
getchar() ). Функция getchar (из стандартной
скобки присваивание ( grade
библиотеки ввода/вывода) счи¬
=
тывает один символ с клавиатуры и сохраняет этот символ в
ременной grade. Символы обычно хранятся
важной особенностью
языка
С
целочисленной
в переменных типа
пе¬
char. Однако
является то, что символы могут храниться в лю¬
бом целочисленном типе данных, поскольку они представляются в компьютере
в виде 1-байтовых целых чисел. Таким образом, мы можем обрабатывать сим¬
вол либо как целое число, либо как символ в зависимости от его смысла. На¬
пример, оператор
printf("The character
has the value %d.\n",
(%c)
использует спецификаторы преобразования %
да символа а и его целочисленного значения.
The
character
Целое
число
97
(а)
has
the
value
с и
%d
'a',
'a');
соответственно для выво¬
Результатом будет
97.
является численным представлением символа в компьюте¬
Многие компьютеры в настоящее время используют набор символов ASCII
(American Standard Code for Information Interchange), в котором число 97
ре.
представляет символ строчной буквы a'. Список символов ASCII и их десяти¬
значений представлен в приложении Г. Символы можно считывать с по¬
чных
мощью функции scanf со спецификатором преобразования %c.
Операторы присваивания, взятые целиком, на самом деле обладают значе¬
нием. Это
в
точности
слева от знака =.
то
самое
переменной
getchar() является символ,
присваиваемый переменной grade.
значение,
Значением присваивания
возвращаемый функцией getchar
и
которое присваивается
grade
=
151
Управление программой
Тот факт,
что операторы присваивания
обладают
значением, может оказа¬
ться полезным для инициализации нескольких переменных одним и тем же
значением. Например, в операторе
=
а
b
=
0;
=
с
сначала оценивается присваивание с
справа
=
0 (поскольку операция
=
ассоциируется
0
присваивается значение присваивания с
налево). Затем переменной b
=
(которое равно 0). Затем переменной а присваивается значение присваивания
b
(с 0) (которое также равно 0). В программе значение присваивания grade
=
=
=
сравнивается со значением EOF
getchar()
«end of file»
чает
ет
значение
«конец
-1)
в
(символом,
мнемоника которого озна¬
принимаем EOF (который обычно име¬
контрольного значения. Пользователь вводит
файла»). Мы
качестве
системно-зависимую комбинацию клавиш, означающую «конец
меня нет
файла»,
т.е.
«У
больше входных данных». EOF представляет собой символическую це¬
лочисленную константу, определенную
в
заголовочном
файле <stdio.h> (мы
6). Если значение,
увидим, как определяются символические константы, в главе
присвоенное
EOF
равно EOF, программа завершается. Мы реши¬
переменной grade,
ли представлять символы
в
этой программе
(как
имеет целочисленное значение
Совет
в виде
значений
типа
мы уже говорили,
int, поскольку
обычно -1).
по переносимости программ 4.1
Комбинации
клавиш для ввода EOF
Совет по переносимости
(конца файла)
являются системно-зависимыми.
программ 4.2
Проверка значения на равенство символической константе EOF, а не числу -1 делает
программы более переносимыми Стандарт ANSI требует, чтобы EOF являлась отри¬
цательным целым числом (но не обязательно -1) Таким образом, на различных вы¬
числительных
На
системах
системах
UNIX
EOF
может
иметь
и многих других
различные
значения
индикатор EOF вводится нажатием
<return> <ctrl-d>
Эта запись означает нажатие клавиши возврата каретки
менное
нажатие
корпорации DEC
или
и затем одновре¬
d. На других системах, например, VAX VMS
MS-DOS корпорации Microsoft, индикатор EOF можно
клавиш
ctrl
и
ввести при помощи
<ctrl-z>
Пользователь вводит
врата
с клавиатуры оценки.
(Enter) функция getchar
считывает
После
одиночный
нажатия клавиши воз¬
Если
символ.
EOF, происходит вход в структуру switch. За ключевым словом
имя переменной grade в круглых скобках. Она является здесь
он не равен
switch следует
управляющим
выражением. Значение этого выражения сравнивается с каждой из меток
case. Предположим, пользователь ввел в качестве оценки символ С. Символ С
автоматически
сравнивается
имеет место соответствие
с
каждой меткой
(case С :),
case
структуры
случае символа С переменная cCount увеличивается на 1
происходит немедленный выход из структуры switch.
Оператор break
структуры
него метки
операторе
switch. Если
В
выполняются операторы для этого case.
и по
оператору break
вызывает передачу управления первому оператору после
switch. Необходимость оператора break обусловлена тем, что без
case оператора switch могли бы выполняться подряд. Если break в
switch вообще отсутствует,
то
всякий раз, когда
в
структуре switch
152
Глава 4
имеет место соответствие,
будут выполняться операторы для всех последую¬
особенность
(Эта
редко используется, хотя она идеально под¬
ходит для программирования итеративного стишка «Дом, который построил
Джек».) Если не имеется ни одного соответствия, то выполняется блок default
и выводится сообщение об ошибке.
Каждый блок case может включать одно или более действий. Структура
щих меток case.
switch
отличается от всех других структур тем, что в
switch последовательность операторов
требуется
блоке
case структуры
в фигурные
скобки. Общая блок-схема структуры со множественным выбором switch (с
операторами break в каждом блоке case) приводится на рис. 4.8.
случай
не
действия
а
в случае
случай
в
случай
break
а
действия
ь
break
случае ь
деиствия
z
в
заключать
случае
break
z
действия
по умолчанию
Рис. 4.8. Структура
со множественным
switch
выбором
Из этой блок-схемы становится понятно, что каждый оператор break в
конце блока case приводит к немедленной передаче управления за пределы
структуры switch. Снова представьте себе, что программист имеет доступ к бо¬
льшому ящику пустых структур switch
ваться
программисту,
чтобы путем
их
управляющие
и
структуры сформировать структурную
управления алгоритма. Затем прямоугольники и ромбы
ствующими алгоритму
действиями
потребо¬
их столько, сколько может
суперпозиции
и решениями.
вложения
в
реализацию
другие
потока
заполняются соответ¬
153
Управление программой
Распространенная ошибка программирования 4.5
Пропуск оператора break
структуре switch, когда он необходим
в
Хороший стиль программирования 4.12
Не забывайте о блоке default в операторах switch Случаи, явно не проверяемые в
структуре switch, игнорируются Блок default помогает избежать этого, фокусируя
внимание программиста на необходимости обрабатывать необычные условия Суще¬
ствуют ситуации, когда никакой обработки по умолчанию (в блоке default) не требу¬
ется
Хороший
стиль программирования 4.13
предложение default в структуре switch могут появляться в
порядке, хорошим стилем программирования считается размещение
предложения default в конце структуры
Хотя предложения case и
произвольном
стиль
Хороший
программирования 4.14
структуре switch предложение default указано последним, опе¬
break для него не требуется Однако некоторые программисты вводят этот
break для ясности и единообразия с метками case
В
том случае, когда в
ратора
В структуре switch
case
'\n':
case
на рис.
'
4.7 строки
':
break;
пробелы. Чтение
проблемы. Чтобы заставить
заставляют программу пропускать символы новой строки и
символов
по
одному
может
вызывать
некоторые
программу считывать символы, их нужно отправить компьютеру путем нажа¬
тия на клавиатуре клавиши возврата.
ток символа
чтобы
новой строки
работала
программа
тываться
Это приводит
после символа,
который
по¬
правильно, этот символ новой
в нашу структуру switch вышеприведенную
сообщения об ошибке
да во входном потоке встречаются символы
Распространенная ошибка
Отсутствие
по
обработки
одному
Хороший
Не
входной
обработать. Часто,
строки должен обраба¬
особым образом. Включив
метку case, мы предотвращаем вывод
лов
к помещению во
мы хотим
о
потоке
Обратите
в
блоке default,
или
ког¬
пробелы.
программирования 4.6
символов новой строки во входном потоке при чтении симво¬
может
приводить
к логическим
ошибкам.
стиль программирования 4.15
забывайте
входном
новой строки
возможной
при
необходимости обработки
считывании
символов
по
символов
новой
строки
во
одному
внимание, что перечисление вместе нескольких меток case
(как,
например, case 'D': case *d*: на рис. 4.7) просто означает, что для каждой из
этих меток должна быть выполнена одна и та же последовательность действий.
Применяя структуру switch, не забывайте, что она может быть использо¬
вана только для проверки константного целочисленного выражения, т.е. лю¬
Глава 4
154
бой комбинации символьных и целочисленных констант, которая раскрывает¬
ся в константное целое значение. Символьная константа представляется в
виде отдельного символа в одиночных кавычках, как, например 'A'. Чтобы
быть распознанными в качестве символьных констант, символы должны быть
заключены в одиночные кавычки.
лые значения.
забывайте,
В
что
Целочисленные
константы
это просто це¬
нашем примере мы использовали символьные константы.
символы
фактически представляют собой небольшие
Не
целые
значения.
Переносимые
языки программирования,
подобные С,
должны иметь гибкие
размеры типов данных. Различным приложениям могут понадобиться целые
числа различных размеров. Язык С предусматривает несколько типов данных
для представления целых чисел. Диапазон целочисленных значений для каж¬
дого типа зависит от аппаратных средств конкретного компьютера. В дополне¬
ние к типам int и char в С предусмотрены типы short (сокращение от short int)
и
long (сокращение
от
long int). Стандарт ANSI
определяет, что минимальный
диапазон значений для целых чисел типа short равен ±32767. Целых чисел
типа long достаточно для подавляющего большинства целочисленных расчетов.
Стандарт определяет, что минимальный диапазон значений для целых чисел
типа long равен ±2147483647. На большинстве компьютеров тип int эквивален¬
тен типам short или long. Стандарт устанавливает, что диапазон значений для
числа типа int по крайней мере совпадает с диапазоном значений целых чисел
типа short и не превосходит диапазона значений целых чисел типа long. Тип
данных char может применяться для представления целых
±127
или
любого
символа из
набора
чисел в диапазоне
символов компьютера.
Совет по переносимости программ 4.3
Поскольку
long,
тип
int различается
по
размеру от системы
к системе,
пользуйтесь
целыми
целые числа вне диапазона ±32767 и
хотели бы иметь возможность выполнять программу на различных компьютерных си¬
типа
если вы рассчитываете
обрабатывать
стемах
Совет по повышению
эффективности
4.1
В случаях, ориентированных на
память или
льзование
эффективность выполнения, когда нужно экономить
необходимо быстродействие, может оказаться предпочтительным испо¬
целых
4.8.
чисел
меньших
размеров
Структура повторения do/while
Структура повторения do/while подобна структуре while. В структуре whi¬
le условие продолжения цикла проверяется в начале цикла до выполнения его
тела. Структура do/while проверяет условие продолжения цикла после выпол¬
нения тела цикла, поэтому тело цикла будет выполнено по крайней мере один
раз. После завершения цикла do/while выполнение программы продолжается
с оператора, следующего за предложением
while. Обратите внимание,
наличии только одного оператора в теле цикла в структуре
ходимости
использовать
включаются
Например,
во
do/while
что при
нет
необ¬
фигурные скобки. Однако обычно фигурные скобки
избежание путаницы между структурами do/while
и
while.
155
Управление программой
while
(условие)
обычно рассматривается
в
заголовка структуры while.
Структура
вокруг ее тела, состоящего из одного оператора,
качестве
do/while без фигурных скобок
выглядит как
do
оператор
while
(условие);
что может приводить к путанице. Последняя строка
быть неправильно
while (условие);
истолкована читателем как структура
пустой оператор. Поэтому структура do/while
может
while, содержащая
с одним оператором часто запи¬
сывается так:
do
{
оператор
(условие)
while
}
Хороший
;
стиль программирования 4.16
Некоторые программисты
le,
фигурные скобки в структуру do/while,
необходимыми Это помогает отличить структуру do/whi¬
всегда включают
даже если они не являются
содержащую один оператор,
от
структуры while
Распространенная ошибка программирования 4.7
Если условие продолжения цикла
в
структурах while, for или
do/while
становится ложным, возникают бесконечные циклы. Во избежание этого
что
после
сразу
заголовка
структуры
for
или
while нет точки
с
никогда не
убедитесь,
запятой.
В
цикле,
управляемом счетчиком, убедитесь, что управляющая переменная в теле цикла уве¬
личивается (или уменьшается) В цикле, управляемом контрольным значением, убе¬
дитесь в том, что оно рано или поздно вводится
В программе на рис. 4.9 структура
сел от 1 до 10.
Обратите
do/while
внимание, что к
используется для вывода чи¬
управляющей переменной counter
при проверке условия продолжения цикла применяется операция преинкре¬
мента. Обратите также внимание на фигурные скобки, заключающие тело
структуры
/*
#
do/while (состоящее
Использование
include
из одного
структуры
оператора).
повторения
do/while
*/
<stdio.h>
main ()
{
int
do
counter
1;
=
{
", counter);
printf("%d
while (+ + counter <= 10) ;
}
return
0;
}
1
2
3
4
5
6
7
8
9
10
Рис. 4.9. Использование структуры
do/while
156
Глава 4
На рис. 4.10 представлена блок-схема структуры
вится
понятно,
что
условие
действие будет
продолжения
цикла
do/while.
проверяется
Из
нее стано¬
только
после
крайней мере один раз. Снова обратите
внимание, что (кроме кружков и стрелок) блок-схема содержит только символ
прямоугольника и символ ромба. Снова представьте себе, что программист
имеет доступ к большому ящику пустых структур do/while
их столько, ско¬
лько может потребоваться программисту, чтобы путем их суперпозиции и вло¬
того, как
выполнено по
жения в другие управляющие структуры сформировать структурную реализа¬
цию потока управления алгоритма. Затем прямоугольники и ромбы заполня¬
ются соответствующими алгоритму
действиями
и решениями.
действия
true
условие
false
Рис. 4.10.
4.9.
Структура повторения do/while
Операторы break
и
continue
Операторы
break и continue предназначены для изменения потока
Исполнение оператора break в структурах while, for, do/while
switch приводит к немедленному выходу из структуры. Выполнение про¬
управления.
или
граммы продолжается с первого оператора, следующего за ней. Обычным
применением оператора break является досрочный выход из цикла или про¬
пуск оставшихся операторов в структуре switch (как на рис. 4.7). Рис. 4.11
демонстрирует оператор break
if
обнаруживает,
в
ak. Это завершает оператор for,
ратора
printf,
структуре повторения for. Когда структура
что значение x стало равным
следующего за
5,
выполняется оператор
bre¬
а программа продолжает выполняться с опе¬
структурой for. Цикл
выполняется полностью
только четыре раза.
Выполнение оператора continue в структурах while, for или
приводит к пропуску оставшихся операторов в теле этой структуры
do/while
и выпол¬
цикла. В структурах while и do/while проверка
цикла
условия продолжения
проводится сразу же после выполнения опера¬
тора continue. В структуре for выполняется выражение приращения, а затем
проверяется условие продолжения цикла. Ранее мы установили, что в боль¬
нению
следующей итерации
шинстве случаев структура
Одним
из
исключений
for
может
является
быть представлена структурой while.
случай,
когда выражение
приращения
в
структуре while следует за оператором continue. В этом случае приращение
157
Управление программой
проверкой условия продолжения цикла не выполняется, и структура
работает не так, как структура for. На рис. 4.12 оператор continue
служит в структуре for для пропуска оператора printf и начала следующей
итерации цикла.
перед
while
|
/* Использование оператора break
#include <stdio.h>
I
main()
{
int x;
for
(
if
1;
=
x
(x
5
==
10;
<=
x
x++)
в
структуре
*/
for
{
)
/*
break;
printf ("%d
цикл
прерывает
только
если
x
==
5
*/
x) ;
11,
}
out
printf("\nBroke
return
of
loop
==
5
at
x
==
%d\n",
x);
0;
}
1
2
4
3
Broke
out
of
loop
at
x
Рис. 4.11. Использование оператора break в структуре for
Использование
/*
оператора
<stdio.h>
#include
mam
continue
в
структуре
for
*/
()
{
int
x;
for
(
x
if
1;
=
<=
x
( x == 5 )
continue;
pnntf("%d ",
10;
/*
x)
x++)
{
пропускает оставшийся
==
x
если
5
*/
код
в
цикле
только
;
}
pnntf("\nUsed continue
return
1
2
Used
3
4
6
to
skip printing the value 5\n");
0;
7
8
continue
9
to
10
skip printing
the
value
5
Рис. 4.12. Использование оператора continue в структуре for
158
Глава 4
Хороший
программирования 4.17
стиль
Некоторым программистам кажется, что операторы break и continue нарушают нор¬
мы структурного программирования Поскольку результаты действия этих операторов
могут быть достигнуты структурными средствами (о которых мы скоро узнаем), эти
программисты не пользуются операторами break и continue
эффективности 4.2
Совет по повышению
Операторы break
ются
быстрее
и
continue, при условии
их надлежащего использования, выполня¬
соответствующих структурных методов,
о которых мы
скоро узнаем
Общее методическое замечание 4.1
Существует определенное противоречие между разработкой качественного програм¬
много обеспечения и разработкой программного обеспечения, работающего наибо¬
лее
эффективно Часто
одна из этих целей достигается за счет
другой
4.10. Логические операции
До
сих пор мы изучали только простые условия, вроде
tal > 1000
и
number != sentinelValue. Мы выражали
counter <= 10, to¬
эти условия с помощью
операций отношений >, <,
>= и <= и операций равенства
и !=. При каж¬
дом принятии решения проверялось в точности одно условие. Если бы в про¬
цессе принятия решения нам понадобилось проверить много условий, мы дол¬
жны
==
были бы делать эти проверки
if или if/else.
в отдельных операторах или во вложенных
структурах
В языке С предусмотрены логические операции,
можно
формировать
сложные условия путем
гическими операциями являются
посредством которых
объединения более простых. Ло¬
&& {логическое И),
11 (логическое ИЛИ)
и
!
(логическое НЕ, также называемое отрицанием). Мы на примерах рассмотрим
каждую из этих операций.
Предположим, что в некотором месте программы мы хотим обеспечить ис¬
тинность двух условий одновременно, прежде чем выберем определенную
ветвь ее выполнения. В этом случае мы можем применить логическую опера¬
цию && следующим образом:
if
==
(gender
1
&&
>=
age
65)
++seniorFemales;
Этот оператор if содержит два простых условия. Условие
gender
==
1
мо¬
жет оцениваться, например, для определения принадлежности определенного
Условие age >= 65 оценивается для определения его
принадлежности
гражданам. Сначала оцениваются два простых
и >= выше, чем приоритет
условия, поскольку приоритет обеих операций
операции &&. Затем в операторе if рассматривается объединенное условие
лица к женскому полу.
к
пожилым
==
gender
==
1
&&
age
>=
65
Это условие является истинным тогда и только тогда, когда оба простых
условия истинны. И наконец, если это
объединенное условие действительно
seniorFemales увеличивается на 1. Если
одно или оба простых условия ложны, то программа игнорирует приращение
и переходит к оператору, следующему за if.
становится
истинным,
то
счетчик
159
Управление программой
Таблица
на рис.
4.13 сводит воедино свойства операции &&. В таблице
по¬
комбинации нулевых (false) и ненулевых (true)
значений для выражений expressionl и expression2. Такие таблицы часто на¬
зывают таблицами истинности. В С все выражения, включающие операции от¬
ношений, операции равенства и/или логические операции, оцениваются как 0
казаны все четыре возможных
1. Хотя С устанавливает для выражений значение true, равное 1, любое
ненулевое значение воспринимается как истинное.
или
expression1
expression2
expression1 && expression2
0
0
0
0
ненулевое
0
ненулевое
0
0
ненулевое
ненулевое
1
Таблица
Рис. 4.13.
истинности операции &&
Теперь давайте рассмотрим операцию
JJ
(логического И)
(логическое ИЛИ). Предполо¬
жим, что в некотором месте программы мы хотим обеспечить истинность хотя
бы одного из двух условий, чтобы перейти к некоторой ее ветви.
В
этом
случае
мы
применим
операцию
||
в
как
,
следующем
фрагменте
программы:
if
(semesterAverage
printf("Student
>=
90
grade
fmalExam
| |
is
>=
90)
A\n");
Этот оператор также содержит два простых условия.
90 определяется, заслуживает
mesterAverage
>=
свои
занятия
усердные
xam >=
чине
протяжении
90 определяется, заслуживает
выдающихся
операторе
результатов
на
семестра.
Оценкой условия
se-
«А»
за
студент
оценки
Оценкой условия finalE-
ли студент оценки
заключительном
«А»
экзамене.
за курс по при¬
После
этого
в
if рассматривается объединенное условие
semesterAverage
и
на
ли
>=
90
| |
fmalExam
>=
90
студент получает оценку «А», если одно или оба простых условия истинны.
Обратите внимание, что сообщение «Student grade is А (Оценка студента:
А)» не выводится только тогда, когда оба простых условия ложны (равны
нулю). Рис. 4.14 показывает таблицу истинности для логической операции
ИЛИ(Ц).
expression1
expression2
expression1
0
0
0
0
ненулевое
0
ненулевое
0
0
ненулевое
ненулевое
1
Рис. 4.14. Таблица
истинности операции
^(логического
expression2
ИЛИ)
160
Глава 4
Операция &&
рации
И
или
имеет
более высокий приоритет, чем операция
слева направо.
ассоциируются
II
.
Обе
опе¬
содержащее операции &&
Выражение,
оценивается только до тех пор, пока не установлена его истинность
,
или ложность. Таким
gender
1
==
будет
и
образом,
age
>=
оценка условия
65
если переменная
прекращается,
ложно),
&&
gender
не
равна
1 (т.е. выражение
продолжена, если переменная
целом все еще может
быть истинным,
если
gender равна
age >= 65).
1
(т.е.
в
целом
выражение
в
Совет по повышению эффективности 4.3
В выражениях, включающих операцию &&, делайте условие, ложность которого наи¬
более вероятна, крайним слева В выражениях, включающих операцию II
делайте
,
крайним
шить
условие, истинность которого наиболее вероятна
слева
время
выполнения
Это
может умень¬
программы
В С предусмотрена операция ! (логическое отрицание), позволяющая про¬
смысл условия. В отличие от операций && и II
кото¬
объединяют два условия (и, следовательно, являются бинарными опера¬
циями), операция отрицания имеет в качестве операнда только одно условие
(и, следовательно, является унарной). Логическая операция отрицания поме¬
щается перед условием, когда мы хотим выбрать ветвь выполнения програм¬
граммисту «обратить»
,
рые
мы с ложным
дующем
if
(до отрицания)
первоначальным условием, как, например,
в сле¬
фрагменте программы:
( ! (grade
sentmelValue) )
pnntf("The next grade is %f\n",
==
grade);
Заключение условия grade
sentinelValue в круглые скобки необходи¬
мо, поскольку логическая операция отрицания имеет более высокий приори¬
тет, чем операция равенства. Рис. 4.15 показывает таблицу истинности для
==
логической операции отрицания.
expression
!expression
0
1
ненулевое
0
Рис. 4.15. Таблица
истинности операции !
В большинстве случаев программист
(логического отрицания)
может
избежать
использования логи¬
ческого отрицания, по-другому выражая условие с помощью
операции
отношения.
Например, предыдущий
соответствующей
оператор может быть записан и
так:
if
(grade
!=
sentinelValue)
next grade is
pnntf("The
В таблице
операций
на рис.
языка
4.16
%f\n",
grade);
показаны приоритет и ассоциативность различных
С, рассмотренных к настоящему моменту. Операции
убывания их приоритета.
ны сверху вниз в порядке
показа¬
161
Управление программой
Операции
О
++
+
--
!
-
(тип)
%
/
+
V
V
==
!
л
II
Л ii
=
Ассоциативность
Тип
слева направо
круглые скобки
справа налево
унарные
слева направо
мультипликативные
слева направо
адитивные
слева
направо
отношения
слева
направо
равенства
&&
слева
направо
логическое
И
II
слева
направо
логическое
ИЛИ
? ;
+=
-=
/
*=
%
=
=
справа налево
условные
справа налево
присваивания
слева направо
запятая
Рис. 4.16. Приоритет и ассоциативность операций
4.11. Смешивание
и
Есть одна
типичная
операций равенства (==)
присваивания (=)
ошибка, которую программирующие
С,
на
вне зависи¬
мости от их опыта, склонны делать настолько часто, что мы решили посвятить
ей специальный раздел. Эта ошибка
(равенства)
на
(присваивания)
=
тив, операторы
с
этими
выполняются
граммы
или
обычно
мен связан с тем, что они
случайной замене операции
(реже) наоборот. Особый ущерб от этих
состоит в
не вызывают синтаксических
нормального
завершения,
этом неправильные результаты из-за логических
Можно выделить два аспекта
Во-первых, любое
выражение
в
возможно,
и
порождая
ошибок времени
про¬
при
выполнения.
С, с которыми связаны эти проблемы.
С, вырабатывающее значение, может
решения любой управляющей структу¬
языка
языке
быть использовано в блоке принятия
ры. Если это значение равно 0, оно
лично от нуля, то
за¬
ошибок. Напро¬
ошибками обычно компилируются правильно
до
==
обрабатывается
обрабатывается как ложное, а если оно от¬
Второй аспект связан с тем,
как истинное.
С присваивание возвращает значение, а именно то значение, кото¬
присваивается переменной слева от знака =. Например, предположим, что
что в языке
рое
мы намереваемся написать
if
(payCode
4)
==
pnntf("You get
но случайно
if
а
bonus!");
а
bonus!");
мы пишем
(payCode
=
4)
pnntf("You
get
оператор if, как и положено, выдает премию сотруднику, платеж¬
ный код которого равен 4. Второй оператор if
тот, что содержит ошибку,
Первый
вычисляет
значение
выражения
присваивания
в
условии
выражение представляет собой простое присваивание,
6
Зак 801
структуры
if. Это
значением которого яв-
162
Глава 4
Поскольку любое ненулевое значение интерпретируется
как «true», условие в этом операторе if всегда истинно и сотрудник всегда по¬
лучает премию, независимо от его фактического платежного кода!
ляется константа 4.
Распространенная ошибка программирования 4.8
Использование операции
честве
знака
==
для присваивания или использование операции
=
в ка¬
равенства
7 имя переменной сле¬
так, чтобы
При обратном порядке записи
константа была слева, а имя переменной справа, как в выражении 7
x,
на операцию =, будет под¬
программист, случайно заменивший операцию
страхован компилятором. Компилятор обработает это как синтаксическую
ошибку, поскольку слева от операции присваивания может помещаться толь¬
ко имя переменной. По крайней мере, это предотвратит возможные разруши¬
Программисты обычно пишут
в
условиях типа x
==
ва и константу справа от знака =.
==
==
тельные последствия логической ошибки времени выполнения.
Говорят,
слева»),
что имена переменных являются
lvalue (от «left value»,
«значе¬
поскольку они могут стоять слева от операции присваивания.
Константы, напротив, являются rvalue (от «right value», «значение справа»),
ние
поскольку они могут находиться только в
тите внимание, что
не
lvalue
правой
Обра¬
rvalue, но
части присваивания.
может использоваться также в качестве
наоборот.
Хороший
Когда
в
стиль программирования 4.18
выражение равенства входят переменная
и константа, как в
случае x
==
1,
не¬
которые программисты предпочитают записывать в этом выражении константу слева,
а имя
переменной справа
от знака
ки, возникающей при случайной
Есть
и другая сторона медали,
неприятной. Предположим,
=
в качестве
замене
защитной меры
от логической
программистом операции
которая может оказаться
==
в
ошиб¬
операцией
равной
=
степени
что программист хочет присвоить значение пере¬
менной посредством простого оператора вроде
x
=
1;
но вместо этого пишет
x
==
1;
Здесь это также не является синтаксической ошибкой. Компилятор про¬
сто оценивает выражение как условие. Если переменная x равна 1, условие ис¬
тинно и выражение возвращает значение 1. Если переменная x не равна 1,
условие ложно и выражение возвращает значение 0. Вне зависимости от воз¬
вращаемого значения здесь отсутствует какая бы то ни было операция присва¬
ивания, поэтому это значение будет просто потеряно и значение x останется
без изменений, вызывая, возможно, тем самым логическую ошибку времени
выполнения. К сожалению, не существует удобного приема, чтобы помочь вам
в
разрешении этой
проблемы!
163
Управление программой
4.12.
сводка по структурному
Краткая
программированию
Подобно
тому, как архитекторы проектируют различные сооружения, ис¬
пользуя при этом коллективную мудрость представителей своей профессии,
так
же
должны
Наша профессия
и
поступать
программисты
беднее. Мы многому научились
Возможно, наиболее важным из того, что
тельно
мощи
при
проектировании
программ.
моложе архитектуры и наша коллективная мудрость значи¬
структурного
программирования
проще для понимания
(чем
всего
можно
строить
неструктурированные
но, проще для тестирования, отладки,
лишь
за
пять
десятилетий.
мы узнали, является то, что при по¬
программы,
программы)
модификации
которые
и, следователь¬
и даже для проверки их
корректности с точки зрения математики.
В главах 3
и
4
Каждая структура
мой и обсуждалась
представим
мы сосредоточились на управляющих структурах языка
в отдельности
на примерах.
С.
была представлена вместе со своей блок-схе¬
Сейчас мы обобщим результаты глав 3 и 4 и
правил для составления структурных программ и
простой набор
определения их свойств.
На рис. 4.17 приводится
суждавшимся в главах 3 и 4.
общая сводка по управляющим структурам, об¬
Для указания единственной точки входа в каж¬
дую структуру и единственной точки выхода из нее на рисунке используются
символы кружков. Соединение отдельных символов блок-схемы в произволь¬
ном порядке может привести к неструктурированным программам.
Поэтому
программисты решили, объединяя символы блок-схемы, сформировать огра¬
ниченный набор управляющих структур и создавать только структурные
программы путем надлежащего объединения управляющих структур только
двумя простыми способами.
одним
входом/одним
Для
выходом
простоты используются только структуры с
существует только один способ входа в каж¬
дую управляющую структуру и только один способ выхода из нее. Последо¬
вательное соединение управляющих структур для создания структурных
точка выхода одной управляющей структуры непо¬
программ несложно
средственно соединяется
т.е.
ры,
грамме;
с
точкой входа следующей управляющей структу¬
управляющие структуры просто помещаются одна за
мы
назвали
это
«суперпозицией
другой
управляющих структур».
в
про¬
Кроме
того, правила построения структурированных программ допускают вложе¬
ние управляющих структур.
На рис. 4.18 показаны правила построения правильно структурированных
Правила подразумевают, что символ прямоугольника может
означать в блок-схеме любое действие, включая операции ввода/вывода.
программ.
Применение
правил из рис. 4.18 всегда приводит к
ме с отчетливыми компоновочными
структурной блок-схе¬
блоками. Например, многократное приме¬
2 к простейшей блок-схеме приводит к структурной блок-схесодержащей набор последовательных прямоугольников (рис. 4.20). Обра¬
нение правила
ме,
тите
внимание,
что
по
правилу
другом управляющих структур;
зиции.
6*
2 генерируется ряд расположенных друг за
назовем правило 2 правилом суперпо¬
давайте
164
Глава 4
Рис. 4.17. Структуры языка С (последовательная, выбора и повторения) с одним
входом/одним выходом
165
Управление программой
Правила
построения структурированных программ
1) Начинайте с «простейшей блок-схемы»
2) Любой прямоугольник (действие)
(рис 419)
может
быть заменен двуя последовательными
прямоугольниками (действиями)
3) Любой прямоугольник (действие) может быть заменен любой управляющей структурой
(последовательной, if, if/else, switch, while, do/while или for
4) Правила 2
и 3
могут применяться неограниченное количество раз в произвольном порядке
Рис. 4.18. Правила построения структурных программ
Рис. 4.19.
Простейшая блок-схема
Правило 3 называется правилом вложения. Многократное применение
правила 3 к простейшей блок-схеме приводит к блок-схеме с отчетливой вло¬
женностью управляющих структур. Например, на рис. 4.21 прямоугольник в
простейшей блок-схеме сначала заменяется структурой с двойным выбором
(if/else). Затем правило 3 снова применяется к обоим прямоугольникам в
структуре с двойным выбором и происходит замена каждого прямоугольника
своей структурой с двойным выбором. Изображенные штриховой линией пря¬
моугольники вокруг каждой из структур с двойным выбором представляют
исходный прямоугольник.
Рис. 4.20. Многократное применение правила 2 рис. 4.18 к
простейшей блок-схеме
Глава 4
166
Рис. 4.21. Применение правила 3 рис. 4.18 к простейшей блок-схеме
Правило 4 порождает большие по размеру, более сложные структуры с бо¬
глубоким вложением. Блок-схемы, которые возникают в результате при¬
менения правил на рис. 4.18, составляют в совокупности набор всех возмож¬
лее
ных структурных
блок-схем и, следовательно, набор
всех возможных струк¬
турированных программ.
То,
что
их
компоновочные
блоки никогда
не
перекрываются,
является
структурного подхода состо¬
ит в том, что мы ограничиваемся небольшим числом простых фрагментов с од¬
ним входом/одним выходом и компонуем их только одним из двух простых
следствием исключения оператора
goto. Красота
способов. На рис. 4.22 показаны виды суперпозиции компоновочных блоков,
возникающих в результате применения правила 2, и виды вложенных компо¬
новочных
рисунке
блоков, возникающих
также
в результате применения правила
показаны перекрывающиеся компоновочные
не могут появляться в структурированных
нию оператора
goto).
3. На
этом
блоки, которые
блок-схемах (благодаря
исключе¬
167
Управление программой
Пакетированные
стандартные блоки
Вложенные стандартные блоки
Частично перекрывающиеся стандартные блоки
(недопустимые в структурированных программах)
Рис. 4.22. Суперпозиция компоновочных блоков, вложенные компоновочные блоки и
перекрывающиеся компоновочные блоки
При условии соблюдения правил на рис. 4.18 неструктурированная
блок-схема (как, например, на рис. 4.23) получена быть не может. Если у вас
есть сомнения в том, является ли данная блок-схема структурированной, при¬
меняйте правила рис. 4.18 в
обратном
порядке
стейшей блок-схеме. Если блок-схему
ная блок-схема является
и
попытайтесь
можно привести к
структурированной;
свести ее к про¬
простейшей,
в противном случае она
исход¬
таковой
не является.
Рис. 4.23.
Структурное
ваний Бома
и
Неструктурная блок-схема
программирование поощряет простоту. В результате исследо¬
Якопини
стало ясно, что
программного управления:
необходимы
только три разновидности
Глава 4
168
Последовательное
Выбор
Повторение
Последовательное
управление тривиально.
Выбор
реализуется одним из
трех способов:
Структура if (один выбор)
Структура if/else (двойной выбор)
Структура switch (множественный выбор)
На самом деле несложно показать, что простой структуры if достаточно
все, что можно сделать с
для обеспечения любой разновидности выбора
помощью структур if/else и switch, может быть реализовано структурами if.
Повторение реализуется одним
из трех
способов:
Структура while
Структура do/while
Структура for
Несложно показать, что структуры while достаточно для реализации лю¬
бой разновидности повторения. Все, что можно сделать с помощью структур
do/while
и
for,
может
Объединение
быть сделано посредством структуры while.
любая разновидность про¬
этих результатов показывает, что
граммного управления, которая когда-либо может понадобиться в программе
на С, может быть выражена при помощи всего лишь трех видов управления:
последовательного
структуры
if (выбора)
структуры while
И
(повторения)
объединяться только двумя способа¬
суперпозицией и вложением. Воистину структурное программирование
поощряет простоту.
В главах 3 и 4 мы обсуждали способы создания программ из управляющих
структур, содержащих действия и решения. В главе 5 мы представим еще
один элемент структурирования программ, называемый функцией. Мы нау¬
чимся создавать большие программы, комбинируя функции, которые в свою
очередь состоят из управляющих структур. Мы обсудим также, каким обра¬
зом применение функций способствует многократному использованию про¬
эти управляющие структуры могут
ми
граммного кода.
Резюме
Циклом
называется группа команд, повоторяемая компьютером много¬
кратно,
пока
будет удовлетворено некоторое условие завершения.
повторения являются: повторение, управляемое счет¬
повторение, управляемое контрольным значением.
не
Двумя формами
чиком,
и
Счетчик цикла служит для подсчета
команд. Он увеличивается
ся группа команд.
Контрольные
чаях,
(обычно
на
количества повторений группы
1) всякий раз, когда выполняет¬
значения обычно применяются для управления в тех слу¬
когда точное число
повторений
заранее не известно, и цикл со-
169
Управление программой
держит операторы, которые получают данные при каждом выполнении
цикла.
Контрольное
даны
значение вводится после того, как в программу
все значимые элементы данных.
выбираться
с
Контрольные
были пере¬
значения должны
так, чтобы их нельзя было спутать с
осторожностью,
допустимыми элементами данных.
повторения for автоматически контролирует все детали по¬
вторения, управляемого счетчиком. Общий формат структуры for
Структура
for
(выражение1;
выражение2;
выражениеЗ)
оператор
выражение1 инициализирует переменную управления циклом, выражение2 является условием продолжения цикла и выражениеЗ слу¬
жит для приращения управляющей переменной.
где
Структура
однако в
условие продолжения цикла проверяется в конце
повторения
do/while
структуре
do/while подобна
цикла, поэтому тело цикла
Формат
для оператора
будет
do/while
структуре
выполнено по
while,
крайней
мере один раз.
do
оператор
while (условие) ;
Исполнение оператора break в какой-либо из структур повторения
while
(for,
приводит к немедленному выходу из структуры. Вы¬
полнение продолжается с первого оператора после цикла.
и
do/while)
Исполнение оператора continue
(for, while
в
какой-либо
из
структур
повторения
приводит к пропуску всех оставшихся операто¬
в
и
теле
ров
структуры
переходу к следующей итерации цикла.
do/while)
и
Оператор switch обрабатывает последовательность решений, когда про¬
исходит проверка конкретной переменной или выражения на равенство
каждому из
значений, которые они могут принимать, и предпринима¬
действия. Каждая метка case в операторе switch может
ются различные
вызывать
грамм
выполнение
нескольких
после операторов каждого
операторов.
блока
case
В
большинстве
про¬
необходим оператор break,
случае программа будет выполнять операторы всех блоков
case до тех пор, пока не будет встречен оператор break или достигнут
конец оператора switch. В нескольких блоках case могут выполняться
в противном
одни и те же операторы, если метки этих case будут перечислены вмес¬
те перед этими операторами. В структуре switch могут проверяться то¬
лько константные целочисленные выражения.
Функция getchar
возвращает с клавиатуры
(стандартный ввод)
один
символ в виде целого числа.
На системах UNIX
и многих других символ
EOF вводится последовате¬
льностью
<return>
На
<ctrl-d>
системах
VMS
и
DOS
символ
EOF вводится посредством
<ctrl-z>
Логические операции
можно использовать для
условий. Логическими операциями
являются
формирования сложных
и !, означающие
&&,
170
Глава 4
соответственно логическое И, логическое ИЛИ и логическое НЕТ
(отри
цание).
Истинным
Ложным
является
значением
значением
является
0
любое ненулевое
значение.
(нуль).
Терминология
break
множественный
выбор
char
набор
ASCII
continue
<ctrl-z>
начальное
значение управляющей
переменной
неопределенное повторение
определенное повторение
ошибка смещения счетчика
double
long
lvalue («значение слева»)
<return><ctrl-d>
rvalue («значение справа»)
short
бесконечный цикл
блок default в структуре switch
вложенные управляющие
структуры
выравнивание
по
левому
выравнивание
по
правому
краю
краю
декремент
знак
по
для
минуса
левому
конец
выравнивания
краю
файла
конечное
значение
переменной
контрольное
управляющей
циклом
переменная
управления
повторение,
управляемое
правило
вложения
правило
суперпозиции
счетчиком
управляющей переменной
простое условие
структура выбора switch
структура повторения do/while
структура повторения for
структура повторения while
структуры повторения
приращение
счетчик
цикла
таблица
истинности
тело
цикла
(одноместная) операция
управляющие структуры с одним
унарная
значение
логические
операции
логическое
И
логическое
ИЛИ
логическое
отрицание
метка
символов
входом / одним выходом
(&&)
условие продолжения цикла
(||)
функция
(!)
case
getchar
функция
pow
ширина поля
Распространенные ошибки программирования
что значения с плавающей точкой могут быть неточны¬
ми, управление циклами со счетчиком при помощи переменных с
плавающей точкой может привести к неточным значениям счетчика
и ошибочным проверкам на окончание цикла.
4.1.
Ввиду того,
4.2.
Использование
4.3.
Использование запятых вместо точек с запятой в заголовке структу¬
ры for.
4.4.
Помещение
неправильной операции отношения или неправиль¬
ного конечного значения счетчика цикла в условии структур while
или for может привести к ошибке смещения счетчика.
точки с запятой сразу после заголовка структуры for де¬
этой структуры for пустым оператором. Обычно это явля¬
логической ошибкой.
лает тело
ется
4.5.
Пропуск оператора break
в структуре
switch,
когда он
необходим.
171
Управление программой
4.6.
Отсутствие обработки
чтении
символов
по
символов
одному
новой строки
может
во входном потоке при
приводить
к
ошиб¬
логическим
кам.
4.7.
Если условие
do/while
циклы.
продолжения цикла
в
Во избежание
for
структуры
или
этого
while
счетчиком, убедитесь,
убедитесь,
нет точки с
что
while, for
структурах
никогда не становится ложным,
или
возникают бесконечные
что
сразу
после
заголовка
запятой. В цикле, управляемом
управляющая
в
переменная
теле
цикла
(или уменьшается). В цикле, управляемом контроль¬
значением, убедитесь в том, что оно рано или поздно вводится.
увеличивается
ным
4.8.
Использование операции
=
операции
Хороший
стиль
в
качестве
==
для присваивания или использование
знака
равенства.
программирования
4.1.
Управляйте циклами
чений.
4.2.
Делайте отступы для операторов
со счетчиком с помощью целочисленных
в теле
зна¬
каждой управляющей струк¬
туры.
4.3.
Помещайте пустую строку до и после каждой управляющей структу¬
ры большого размера для выделения ее в тексте программы.
4.4.
Слишком большое число
4.5.
уровней
вложенности может сделать про¬
грамму
понимания. Пытайтесь избегать использования
более
вложенности.
трудной для
трех уровней
Сочетание
междустрочного интервала до
структур и отступов в теле
двумерный
удобочитаемость.
головков придает программам
повышает
их
4.6. Использование
и
после
управляющих структур
вид,
управляющих
в пределах их за¬
который
конечного значения в условии структур
значительно
while
или
for
операции отношения <= помогает избежать ошибок сдвига
счетчика. Например, для цикла, используемого для вывода значений
и
от 1 до
а
4.7.
не
должно быть counter <=
10,
в разделы инициализации и приращения структуры
for
10, условием продолжения цикла
counter
Помещайте
11
<
только выражения,
пуляции
или
counter
<
10.
включающие управляющие переменные.
с другими переменными должны производиться
циклом (если
они
выполняются
инициализации), либо
только
в теле цикла
(если
один
раз,
как
теле
кам;
4.9.
значение
цикла
for,
либо перед
операторы
они выполняются по одно¬
му разу за повторение, как операторы инкремента или
4.8. Хотя
Мани¬
декремента).
управляющей переменной может быть изменено в
это может привести к труднообнаружимым ошиб¬
лучше его не изменять.
for, и операторы, со¬
в заголовке
в
ее
часто
быть
теле,
объединены
могут
держащиеся
структуры for, избегайте этого, поскольку это делает программу бо¬
лее трудной для чтения.
Хотя операторы, предшествующие структуре
172
Глава 4
4.10.0граничивайте,
щих
если
это
возможно,
4.11.He используйте переменных
вычислений,
размер
управляю¬
типа
double
или
float для проведения
Неточность чисел с
связанных с денежными суммами.
плавающей точкой
может вызвать
ошибки, которые приведут
В упражнениях
правильным значениям денежных сумм.
возможности использования
ний,
заголовков
одной строкой.
структур
связанных
4.12.He забывайте
с
целых чисел
денежными
к не¬
мы изучим
для проведения вычисле¬
суммами.
блоке default
в операторах switch. Случаи, явно не
switch, игнорируется. Блок default помога¬
ет избежать этого, фокусируя внимание программиста на необходи¬
мости обрабатывать необычные условия. Существуют ситуации, ког¬
да никакой обработки по умолчанию (в блоке default) не требуется.
о
проверяемые в структуре
4.13.Хотя
предложения case и предложение default в структуре switch
могут появляться в произвольном порядке, хорошим стилем про¬
граммирования считается размещение предложения default в конце
структуры.
4.14. В том случае, когда в структуре switch предложение default указано
последним, оператора break для него не требуется. Однако некото¬
рые программисты вводят этот
метками
break для
ясности и
единообразия
с
case.
4.15.He забывайте
вой строки во
о
возможной необходимости обработки
символов
но¬
входном потоке при считывании символов по одному.
4.16.Некоторые программисты всегда включают фигурные скобки в
структуру do/while, даже если они не являются необходимыми. Это
помогает отличить структуру do/while, содержащую один оператор,
от структуры while.
4.17.Некоторым
nue
программистам кажется, что операторы break
нарушают
нормы
структурного
программирования.
и
conti¬
Поскольку
результаты действия этих операторов могут быть достигнуты струк¬
турными средствами (о которых мы скоро узнаем), эти программи¬
сты
не
4.18. Когда
пользуются
операторами
break
и
continue.
в выражение равенства входят переменная и константа, как в
1, некоторые программисты предпочитают записывать в
переменной справа от знака
в качестве защитной меры от логической ошибки, возникающей
при случайной замене программистом операции
операцией =.
случае x
==
этом выражении константу слева, а имя
=
==
Советы по повышению
4.1.
эффективности
В случаях, ориентированных
нужно
оказаться
предпочтительным
эффективность выполнения, когда
необходимо быстродействие, может
на
экономить память или
использование
целых чисел
меньших
размеров.
4.2.
Операторы break
вания,
и
continue, при условии их надлежащего использо¬
быстрее соответствующих структурных мето¬
выполняются
дов, о которых мы скоро узнаем.
173
Управление программой
4.3.
В выражениях, включающих операцию
ность
которого
включающих операцию
ность которого
полнения
&&, делайте условие,
наиболее вероятна, крайним
I
слева.
В
выражениях,
I, делайте условие крайним слева,
наиболее вероятна. Это
лож¬
истин¬
может уменьшить время вы¬
программы.
Советы по переносимости программ
4.1.
Комбинации
клавиш для ввода
EOF (конца файла)
являются систем¬
но-зависимыми.
4.2.
Проверка значения на равенство символической константе EOF, а не
-1 делает программы более переносимыми. Стандарт ANSI
требует, чтобы EOF являлась отрицательным целым числом (но не
числу
обязательно -1). Таким образом, на различных вычислительных
темах EOF может иметь различные значения.
4.3.
Поскольку
тип
int различается
целыми типа
льзуйтесь
long,
сис¬
по размеру от системы к системе, по¬
если
вы
рассчитываете
обрабатывать
целые числа вне диапазона ±32767 и хотели бы иметь возможность
выполнять программу на нескольких различных компьютерных сис¬
темах.
Общие
4.1.
методические замечания
разработкой качест¬
разработкой программного
Существует определенное противоречие между
венного
программного
обеспечения
и
обеспечения, работающего наиболее эффективно. Часто одна
целей
достигается за счет
Упражнения
4.1.
из этих
другой.
для самоконтроля
Заполните
пробелы
в
каждом
из
a) Повторение, управляемое
следующих
счетчиком,
утверждений.
также
называется
повторением, поскольку заранее известно, сколько раз
будет
выполнен
b) Повторение,
ется
лько раз
будет
цикл.
управляемое контрольным значением, также называ¬
повторением, поскольку заранее не известно, ско¬
выполнен
цикл.
c) В повторении, управляемом счетчиком,
рений группы команд используется
для подсчета числа повто¬
.
d) Оператор
при его исполнении в структуре повторения
вызывает немедленное выполнение следующей итерации цикла.
при его исполнении в структуре повторения
или структуре switch вызывает немедленный выход из структуры.
e) Оператор
f)
выражения
используется для проверки данной
на
равенство
значений, которые
4.2.
Установите,
неверными.
они
являются
каждому
могут
ли
из
постоянных
переменной
или
целочисленных
принимать.
следующие
высказывания
верными
Если высказывание неверно, объясните почему.
или
Глава 4
174
a) В структуре выбора switch необходим блок default.
b) В блоке default структуры выбора switch необходим оператор
&&
>
break.
c) Выражение (x
b) истинно, если истинно либо выраже¬
у
ние x > у, либо выражение а < b.
d) Выражение,
а <
содержащее
операцию
I I
если
истинно,
,
истинен
один из или оба его операнда.
4.3.
Напишите оператор С или последовательность операторов для реше¬
ния
каждой
из
следующих
a) Просуммируйте
этого структуру
sum
ременные
b) Выведите
for. Предположите,
и
что
1 до 99, используя для
были объявлены целые пе¬
count.
333.546372
значение
1, 2, 3, 4
шириной
с
поля
15
символов
5. Выровняйте выводимые значения
Какими будут эти пять выводимых значений?
точностью
краю.
задач:
нечетные целые числа от
c) Вычислите
и
значение
2.5
в
степени
Выведите результат
цию pow.
с
с
по левому
3, используя для этого функ¬
2 и с шириной поля 10
точностью
позиций. Каким будет выводимое значение?
d) Выведите целые
и
числа от
переменную-счетчик
объявлена,
x.
1 до 20, используя для
Предположите,
но не инициализирована.
что
Выводите
Подсказка: используйте вычисление
чение равно 0, выводите символ новой строки,
выводите символ табуляции.
лых чисел.
e) Повторите
4.4.
упражнение 4.3
Найдите ошибку
(d)
этого цикл
while
x
была
переменная
в строке только
x
% 5. Если
в противном случае
с использованием структуры
в каждом из следующих
фрагментов
x
1;
while (x
=
<=
10);
x++;
}
.1; у != 1.0;
printf("%f\n", у);
b)
for
c)
switch
у
+=
.1)
printf("The
case 2:
number
is
l/n");
pnntf("The
number
is
2/n");
number
is
not
=
(у
(n)
case
{
1:
break;
default:
printf("The
1
or
2\n");
break;
}
d) Следующий
n
=
код должен выводить значения от 1 до 10.
1 ;
while
(n
<
10)
printf("%d
",
n++);
for.
кода и объяс¬
ните, как ее можно исправить.
a)
5 це¬
это зна¬
175
Управление программой
Ответы
4.1.
к
упражнениям для самоконтроля
а) определенным, b) неопределенным, с) контрольная переменная
или
d) continue. e) break. f)
счетчик,
4.2. а) Неверно. Блок default
ется
действия
никакого
не является
по
структура
выбора switch.
обязательным. Если
то
умолчанию,
нет
не требу¬
необходимости и в
блоке default.
b) Неверно. Оператор break используется для выхода из структуры
switch. Оператор break не является необходимым, если блок default
является
блоком структуры.
последним
c) Неверно. При использовании операции && для того, чтобы истин¬
ным было все выражение, должны быть истинными оба выражения
отношений.
d) Верно.
4.3.
а)
=
sum
for
0;
(count
sum
+=
1;
=
count
<=
99;
2)
333.546372);
333.546372);
333.546372);
/*
/*
/*
выводит
333.5
*/
выводит
333.55
*/
выводит
333.546
printf( "%-15.4f\n",
printf( "%-15.5f\n",
333.546372);
333.546372);
/*
/*
выводит 333.5464
выводит 333.54637
V
*/
*/
=
x
1;
while (x
{
x) ;
if
(x % 5
0)
printf ("\n");
else
<=
20)
printf("%d",
==
printf("\t");
x++;
или
x
=
1 ;
while
(x
<=
if
(x
%
20)
5
==
0)
printf("%d\n",
else
x++)j
printf("%d\t",
x++)i
или
x
=
0 ;
while
if
e)
+=
pnntf ( "%-15.lf\n",
printf( "%-15.2f\n",
printf( "%-15.3f\n",
c) printf("%10.2f\n", pow(2.5,
d)
count
count;
for
(++x
(x %
<=
5
20)
==
0)
printf("%d\n",
else
x);
printf("%d\t",
x);
(x
=
1;
x
<=
printf("%d",
if (x % 5
==
20;
x);
0)
printf("%d\n");
x++)
3)
/*
выводит
15.63
*/
176
Глава 4
else
printf("\t");
}
или
for
4.4.
1; x
% 5
(x
if (x
=
x+ + )
20;
<=
==
0)
printf("%d\n",
else
x);
printf("%d\t",
x);
а) Ошибка: помещение точки с запятой после заголовка структуры
while приводит к бесконечному циклу.
Исправление:
b)
Ошибка:
ния
замените точку с
запятой
структурой
1;
=
у
!=
}.
для управле¬
требуемые
значения.
10;
pnntf("%f\n",
c) Ошибка:
или удалите ; и
целое число и выполните соответствую¬
щее вычисление, чтобы получить
(у
{
повторения for.
Исправление: используйте
for
символом
плавающей точкой
использование числа с
у++)
(float)
у
/
10);
отсутствие оператора break в первом блоке case.
Исправление: добавьте оператор break в конец первого блока case.
Обратите внимание, что это не обязательно является ошибкой, если
программист хочет,
кий
раз,
когда
чтобы оператор блока case 2: выполнялся
оператор блока case 1:.
d) Ошибка: используется неправильная операция
вии
вся¬
выполняется
продолжения
повторения
структуры
Исправление: используйте операцию
<=
отношения в усло¬
while.
вместо
<.
Упражнения
4.5.
Найдите ошибку в каждом из следующих фрагментов кода (Приме¬
может быть более одной ошибки):
чание:
a) for
(x
100, x >= 1,
printf("%d\n", x);
=
x++)
b) Следующий
код должен выводить, является ли данное целое чис¬
ло нечетным или нечетным:
switch
case
(value
0:
%
2)
{
pnntf("Even
case
integer\n");
1:
pnntf("Odd mteger\n");
}
c) Следующий код должен вводить целое
их.
Предположите,
что
пользователь
scanf("%d", &mtVal);
charval
getchar();
printf("Integer: %d\nCharacter:
число и символ и выводить
вводит
с
клавиатуры
100 А.
=
d) for
(x
=
.000001;
x
pnntf("%.7f\n",
<=
x);
.0001;
x
%c\n",
+=
intVal,
.000001)
charVal);
Управление программой
177
e) Следующий
код должен выводить нечетные целые числа от 999 до 1:
for
(x
f) Следующий
Do
}
{
if
код должен выводить четные целые числа от 2 до 100:
(counter % 2
printf("%d\n",
==
g) Следующий код
(предположите, что
total
4.6.
Установите,
каждым
100;
=
(x
из
а) for(x
+=
for(x
for
(x
for
(x
=
=
for
(x
=
13;
<=
x
<=
x
3;
1;
управляющей переменной
0):
x
x выводятся
x
+=
2)
+=
7)
x) ;
,
15;
,
x)
5;
,
>=
,
x
+=
3)
r
7)
+=
x
x)
2;
x)
x
i
и
U)
;
Напишите операторы for, которые выводят следующие последовате¬
льности значений:
a) 1, 2, 3, 4, 5, 6, 7
b) 3, 8, 13, 18, 23
c) 20, 14, 8, 2, -4, -10
d) 19, 27, 35, 43, 51
4.8.
значением
операторов for:
22; x
x) ;
<=
x
12;
,
<=
x
printf("%d\n"
4.7.
total инициализирована
x++);
x;
printf ("%d\n"
e)
150;
<=
printf("%d\n"
d)
;
переменная
x
printf("%d\n"
с)
100)
какие значения
5;
=
0)
counter);
должен суммировать целые числа от 100 до 150
printf("%d\n"
b)
<
следующих
2;
=
2)
2;
=
counter += 2;
While (counter
for
+=
x
1;
x);
("%d\n",
printf
counter
>=
x
9 99;
=
Что делает следующая программа?
#include <stdio.h>
main ()
{
int i, j, x, у;
pnntf("Enter integers in the range 1-20:
scanf("%d%d", &x, &y);
for (i
1; i <= у; i++) {
=
for
(j
=
1;
j
<= x;
printf("0");
printf ("\n");
}
return
0;
j++)
")
178
Глава 4
4.9.
Напишите
Предположите,
что
scanf, определяет
помощью
ввести.
которая суммирует последовательность це¬
программу,
лых чисел.
целое
первое
количество
число,
считываемое
значений, которое
с
осталось
Ваша программа должна считывать только одно значение
scanf. Типичной входной последовательно¬
при каждом выполнении
стью
могло
5
бы быть
200
100
300
где 5 указывает,
ний.
400
500
что должны суммироваться последующие
4.10.Напишите программу, которая
ние
нескольких
для
значением,
считываемым
с
Предположите, что последнем
scanf, является контрольное
помощью
9999. Типичной входной последовательностью
значение
значе¬
вычисляет и выводит среднее значе¬
чисел.
целых
5
могла
бы
быть последовательность
10
8
11
7
9
указывающая,
9999
что должно
быть
вычислено среднее для всех значе¬
ний, предшествующих 9999.
4.11.Напишите программу, которая
целых чисел.
ляет
Предположите,
количество
оставшихся
находит наименьшее из нескольких
что первое считанное значение опреде¬
значений.
4.12.Напишите программу, которая
целых чисел от 2 до 30.
вычисляет и выводит сумму четных
4.13.Напишите программу, которая вычисляет
нечетных целых чисел от 1 до 15.
4.14.Функция факториала
ятности.
n!
Факториал
и произносится
ет
факториалы
лений
выводит
произведение
часто используется в задачах по теории веро¬
положительного целого числа n
«п
ных целых чисел от
и
факториал»)
1 до
n.
(записывается
равен произведению положитель¬
Напишите программу, которая
целых чисел от 1 до 5.
вычисля¬
Выведите результаты
табличной форме. Какие сложности
вычислить факториал числа 20?
в
могли
бы
вычис¬
помешать вам
4.15.Измените программу вычисления сложного процента из раздела 4.6
так, чтобы она повторяла свои шаги для процентных ставок 5 про¬
центов, 6 процентов, 7 процентов, 8 процентов, 9 процентов
процентов. Для варьирования процентной
ставки
используйте
и
10
цикл
for.
4.16.Напишите
программу,
которая
другим следующие рисунки.
по
Для
отдельности
выводит
один под
генерации рисунков используйте
циклы for. Все звездочки
(*) должны выводиться единственным опе¬
printf в виде printf("*"); (это вызывает вывод звездочек ря¬
друг с другом). Подсказка: для последних двух рисунков необ¬
ратором
дом
ходимо, чтобы каждая строка начиналась с соответствующего числа
пробелов.
179
Управление программой
(А)
(В)
(С)
(D)
*
*
**
*********
***
********
****
*******
*****
* * * ** *
******
*****
*******
****
********
***
*********
**
**********
*
4.17. В период
**
*********
********
***
** *****
****
******
*****
*****
****
*******
***
********
**
*********
*
**********
востребование
экономического спада
денежного долга зна¬
чительно затрудняется, поэтому компании могут ограничивать пре¬
дельные размеры кредитов своих клиентов, чтобы не допустить раз¬
растания их годных к
должны
(денег, которые клиенты
длительный спад некая компания
принятию счетов
компании). В
ответ
на
урезала предельные размеры кредитов своих клиентов наполовину.
Таким образом, если данный клиент имел предельный размер кре¬
дита в $2000, теперь предельный размер кредита этого клиента ра¬
вен $1000. Если клиент имел предельный размер кредита в $5000,
теперь
предельный размер кредита
пишите
программу,
клиентов
этой
1.
счета
Номер
которая
компании.
этого клиента равен
состояние
анализирует
Для
$2500. На¬
кредита
трех
каждого клиента вам известны:
клиента
2. Предельный размер кредита до спада
3. Текущий баланс
(т.е.
клиента
сумма,
которую клиент должен
компании).
Ваша программа
должна вычислить и вывести на печать новое зна¬
предельного размера кредита для каждого клиента и опреде¬
чение
лить
(и распечатать)
список
тех
клиентов,
текущие
балансы
кото¬
рых превышают их новое значение предельного размера кредита.
4.18.0дним интересным приложением компьютеров является рисование
графиков и столбцовых диаграмм (иногда называемых «гистограм¬
мами»). Напишите программу, которая считывает пять чисел (каж¬
дое между 1 и 30). Для каждого считанного числа ваша программа
должна вывести строку из равного этому числу количества смежных
звездочек.
она
Например,
должна
вывести
если
4.19.Фирма, занимающаяся
изделия
число
семь,
заказами по почте, продает пять различных
изделий, розничные
таблице:
видов
Номер
ваша программа считывает
*******.
цены на которые показаны в
Розничная цена
1
$2 98
2
$4 50
3
$9 98
4
$4 49
5
$6 87
следующей
180
Глава 4
считывает последовательность пар
Напишите программу, которая
чисел следующим образом:
1. Номер
изделия
2. Количество, проданное за один день
Ваша программа должна использовать оператор switch, чтобы опре¬
делить розничную цену за каждое изделие. Программа должна вы¬
числить и отобразить на экране итоговую сумму по розничной про¬
даже всех изделий за последнюю неделю.
4.20. Закончите следующие таблицы истинности, заполняя каждый про¬
бел 0 или 1.
Условие1
Условие2
Условие1 && Условие2
0
0
0
0
ненулевое
0
ненулевое
0
ненулевое
ненулевое
Условие1
Условие2
Условие1
0
0
0
0
ненулевое
1
ненулевое
0
ненулевое
ненулевое
Условие1
!Условие1
0
1
||
Условие2
ненулевое
программу на рис. 4.2 так, чтобы инициализация пере¬
менной counter выполнялась при объявлении, а не в структуре for.
4.21.Перепишите
4.22. Измените программу на рис. 4.7 так, чтобы она вычисляла среднюю
оценку для группы.
4.23.Измените программу на рис. 4.6 так, чтобы для вычисления слож¬
ных
она
процентов
обрабатывайте
использовала
только
центов. Затем «разбейте» результат
для этого
вьте
десятичную
"
%d"
i
2, k
1, j
операторов?
=
=
==
1)
;
b) printf("%d", j
==
3)
;
c) printf("%d",
i
>=
1
&&
d) printf("%d",
m
<=
99
e) printf("%d",
j
>=
(
(Подсказка:
на доллары и центы,
,
значений
используя
модуля.
Вста¬
точку.)
что
следующих
a) pnntf
числа.
соответственно операции деления и взятия
4.24.Предположим,
из
целые
все денежные суммы в виде целочисленных
i
i
k
&&
||
<
j
k
=
3
4);
<
m);
==
m);
и m
=
2. Что выводит каждый
181
Управление программой
+
f) printf("%d",
к
g) pnntf("%d",
!m)
h) printf("%d",
!(j
i)
j)
<
m
j
-
>=
к)
;
;
-
m))
printf("%d",
!(к
>
m))
printf ( "%d"
! (з
>
к) )
,
3
| |
j
.
4.25. Выведите таблицу, состоящую
из
десятичного,
ричного и шестнадцатеричного эквивалентов.
этими
системами
упражнение,
4.26.Вычислите
4
71
=
значение
4
+
4_
з
счисления,
4
4
+
~
7
5
но
хотите
прочитайте
сначала
n
с
Сколько
это
бесконечного ряда
4
Ц+К
показывает
ванное одним членом этого ряда,
впервые
выполнить
Д.
~
9
Выведите таблицу, которая
т.д.
восьме¬
вы не знакомы с
попытаться
приложение
помощью
двоичного,
Если
значение тг,
двумя членами,
аппроксимиро¬
тремя членами и
членов этого ряда нужно использовать, прежде чем вы
получите
3.14? 3.141? 3.1415? 3.14159?
треугольник может иметь
стороны, каждая из которых является целым числом. Набор трех
целочисленных значений для сторон прямоугольного треугольника
называется пифагоровой тройкой. Эти три стороны должны удовлет¬
4.27.(Пифагоровы тройки.) Прямоугольный
сумма квадратов двух катетов
равна квадрату гипотенузы. Найдите все пифагоровы тройки для ка¬
тетов sidel, side2 и гипотенузы, не превосходящих 500. Используйте
ворять следующему соотношению:
цикл for с
все
тройной
возможности.
грубой
ния.
силы».
Но
вложенностью, в котором просто
Это
является
Многим
оно не
примером
перебираются
вычисления
«с
помощью
приносит эстетического удовлетворе¬
есть много причин, по которым эти методы важны.
Во-пер-
возрастающей
такими
вых, при мощности вычислительной техники,
необыкновенными темпами, решения, для получения которых
пона¬
добились бы
годы или даже столетия компьютерного времени при
использовании технологий, применявшихся всего лишь несколько
лет тому назад, теперь могут быть получены за часы, минуты или
Современные микропроцессорные схемы могут обра¬
батывать более 100 миллионов операций в секунду! И в 90-е годы, по
даже секунды.
всей
вероятности, должны появиться микропроцессорные схемы,
способные обрабатывать миллиард операций в секунду. Во-вторых,
как вы узнаете из курсов по
информатике
для продолжающих
обу¬
чение, существует большое число интересных задач, для которых не
известны алгоритмические подходы, отличные от решения «с помо¬
грубой силы». В этой книге мы исследуем многие разновидно¬
методологий решения задач. Мы рассмотрим многие подходы к
различным интересным проблемам, связанные с решением «с помо¬
щью грубой силы».
щью
сти
4.28. Компания
как администраторам (это те,
фиксированную зарплату), работникам с
почасовой оплатой (те, кто получает фиксированную почасовую
оплату за первые 40 отработанных часов и «полуторную», т.е. пре¬
платит
своим
служащим
кто получает еженедельно
182
Глава 4
вышающую в 1.5 раз их почасовую, оплату за сверхурочные часы
работы), работникам с комиссионным вознаграждением (те, кто по¬
лучает $250 плюс 5.7% от их валовых еженедельных продаж) и ра¬
ботникам со сдельной оплатой труда (те, кто получает фиксирован¬
ную сумму денег за каждую единицу произведенной ими
каждый сдельщик в этой компании работает только с
одной разновидностью продукции). Напишите программу для расче¬
та еженедельной зарплаты каждого служащего. Вам неизвестно за¬
продукции
ранее количество служащих.
Служащий каждой
из групп имеет
соб¬
ственный
код оплаты: администраторы имеют код оплаты 1,
работники с почасовой оплатой имеют код 2, работники с комисси¬
онным вознаграждением имеют код 3 и работники со сдельной опла¬
той труда
имеют
4. Используйте оператор switch для расчета
код
в зависимости от его кода. Внутри
оператора switch попросите пользователя (т.е. клерка, рассчитываю¬
щего зарплату) ввести соответствующие данные, которые потребу¬
зарплаты каждого служащего
вашей программе для расчета зарплаты каждого служащего,
исходя из его кода оплаты.
ются
4.29. (Законы де Моргана.) В этой
главе мы
обсуждали
логические опера¬
!. Законы де Моргана иногда могут помочь нам запи¬
ции &&,
сать логическое выражение в более удобной форме. Эти законы уста¬
и
\(условие1 && условие2) логически
(\условие1
\условие2). Также выраже¬
ние \(условие1
логически
эквивалентно выражению
условие2)
&&
законы
де Моргана для напи¬
(\условие1
\условие2). Используйте
что
навливают,
выражение
эквивалентно выражению
сания эквивалентного выражения для каждого из следующих выра¬
жений, а затем напишите программу, показывающую, что исходное
и новое выражения в каждом случае являются эквивалентными:
a)
! (x
<
b)
! (а
F=
c)
! ( (x
d)
! (i
8)
| |
4)
>=
! (у
! (g
| |
b)
<=
>
&&
5)
&&
(Y
(j
7)
!= 5)
>
<=
4) )
б))
программу на рис. 4.7, заменив оператор switch вло¬
женным оператором if/else; будьте внимательны и надлежащим об¬
4.30.Перепишите
разом рассмотрите блок default. Затем перепишите эту новую вер¬
сию, заменяя вложенные операторы if/else серией операторов if;
здесь также
будьте
внимательны
default (это сделать труднее,
чем
и
рассмотрите
в
варианте
Упражнение показывает, что оператор switch
удобства и что любой такой оператор может
помощью операторов с одним выбором.
4.31.Напишите
ромба. Вы
с
реализацию
вложенным
блока
if/else).
введен в сущности для
быть
записан только с
программу, которая выводит следующую фигуру в виде
printf, которые выводят
(*), либо один пробел. Максимизируйте количе¬
ство используемых вами повторений (посредством вложенных
структур for) и минимизируйте число операторов printf.
можете использовать операторы
либо одну звездочку
183
Управление программой
*
*
*****
*******
*********
*******
*****
***
*
4.32.Измените программу, которую вы написали в упражнении 4.31 так,
чтобы она считывала нечетное число в диапазоне от 1 до 19 для зада¬
ния
количества
бражать
4.33. Если
на
вы знакомы с
ромб
ваша программа должна ото¬
соответствующего размера.
римскими цифрами, напишите программу, кото¬
рая выводит
таблицу
в
от
диапазоне
ромбе. Затем
в
строк
экране
всех римских эквивалентов десятичных чисел
1 до 100.
4.34. Напишите программу, которая выводит таблицу двоичных,
и
ричных
эквивалентов
шестнадцатеричных
десятичных
восьме¬
чисел
в
1 до 256. Если вы не знакомы с этими системами счис¬
хотели бы попытаться сделать это упражнение, сначала про¬
диапазоне от
ления и
читайте приложение Д.
4.35.0пишите
do/while
процесс,
который потребовался бы для замены цикла
while. Какая возникает проб¬
эквивалентным ему циклом
лема, когда вы пытаетесь заменить цикл while эквивалентным цик¬
лом
do/while? Предположим,
что
цикл while и заменить его циклом
вам
бы
ее использовали,
ведет
программа
себя
так
же,
как
нужно
убрать
дополнительная
понадобиться
чтобы гарантировать,
точно
что
do/while. Какая
управляющая структура могла бы вам
зом вы
сказали,
и
каким
обра¬
что получившаяся
оригинал.
4.36.Напишите программу, которая вводит год в диапазоне от 1994 до
1999 и использует повторение с циклом for для печати сжатого, ак¬
куратного
календаря.
Обратите
внимание
на
високосные
годы.
4.37.0ператоры break
является
всегда
и continue критикуют за то, что каждый из них
неструктурным. В самом деле, операторы break и continue
можно заменить структурными операторами, хотя выглядеть
это может неуклюже.
могли
бы убрать
Опишите
из цикла
в
общем
случае, каким
любой оператор break
образом
вы
и заменить его не¬
которым структурным эквивалентом. (Подсказка: при помощи опе¬
ратора break мы выходим из цикла, находясь внутри тела цикла.
Другим способом
выхода является
отрицательный результат провер¬
Рассмотрите использование при проверке
второй проверки, которая указывает на «до¬
ки на продолжение цикла.
на продолжение цикла
срочный выход из-за условия
*break ».) Примените разработанный
здесь
оператора
рис.
метод
для
устранения
break
4.11.
4.38.Что делает
следующий фрагмент программы?
из
программы
на
184
Глава 4
for
(i
for
1;
=
(j
for
=
j <= 5;
1; j <=
i++)
{
3; j++)
1, к <=
printf ("*");
printf ("\n");
(к
=
4;
{
к++)
}
printf
("\n");
}
4.39. Опишите
цикла
ным
в
общем
случае, каким
любой оператор continue
эквивалентом.
образом
вы
и заменить его
могли
бы убрать
из
некоторым структур¬
Примените разработанный
здесь
метод
для
устранения оператора continue из программы на рис. 4.12.
4.40. Опишите в
общем
случае, каким
образом
торы break
из структуры
валентами.
Примените разработанный
люжий)
switch
вы могли
и заменить их
бы убрать опера¬
структурными экви¬
здесь метод
(возможно
неук¬
для устранения операторов break из программы на рис. 4.7.
г
л
в
а
а
5
Функции
Цели
Понять принципы модульного построения программ
из небольших блоков, называемых функциями.
Познакомиться с часто используемыми математиче¬
скими функциями стандартной библиотеки С.
Научиться
созданию новых
Понять механизмы обмена
функций.
информацией между
функциями.
Познакомиться
ванными
Понять
ций,
на
с
принципы
моделирования,
методиками
генерации
случайных
написания
и
вызывающих самих себя.
осно¬
чисел.
использования
функ¬
186
Глава 5
Содержание
5.1.
Введение
5.2.
Программные модули
5.3.
Функции
5.4.
Функции
5.5.
Определения функций
Прототипы функций
5.7.
Заголовочные
5.9.
файлы
функций:
вызов
Генерация случайных
5.10.Пример:
5.11. Классы
С
математической библиотеки
5.6.
5.8. Вызов
в
по значению
и
по
ссылке
чисел
стохастическая
игра
памяти
5.12.Правила области действия
5.13.
Рекурсия
5.14.Пример применения рекурсии:
5.15.Рекурсия
Резюме
в
сравнении
ошибки программирования
Советы по переносимости программ
эффективности
самоконтроля
Фибоначчи
итерацией
Распространенные
программирования
шению
с
числа
Ответы
Общие методические
на упражнения
5.1.
замечания
для самоконтроля
Хороший
Советы
стиль
по повы¬
Упражнения для
Упражнения
Введение
Большинство компьютерных программ, которые решают задачи реально¬
больше тех, что представлены в первых главах этой книги.
Как показывает практика, наилучшим способом разработки и поддержки бо¬
го мира, намного
льших программ является конструирование программы из небольших частей,
или модулей, с каждым из которых обращаться проще, чем с первоначальной
программой. Эта
реализацию,
«разделяй и властвуй». Данная
С, которые упрощают проектирование,
методика следует принципу
глава описывает возможности языка
использование и сопровождение
больших программ.
Функции
187
5.2.
Модули
в
С
Программные модули
в
С
функциями. Программы на С обычно пишутся пу¬
функций, созданных программистом, с функциями, ко¬
называются
тем соединения новых
стандартной библиотеки С. В этой главе мы
обсудим оба вида функций. Стандартная библиотека С предоставляет широ¬
кий набор функций для выполнения общих математических вычислений, об¬
работки строк и символов, ввода/вывода и многих других полезных операций.
Стандартные функции упрощают работу программиста, поскольку удовлетво¬
ряют многим из его потребностей.
торые поставляются в составе
Хороший
Освойтесь
стиль программирования 5.1
с
широким
набором функций стандартной библиотеки
ANSI С.
Общее методическое замечание 5.1
Не изобретайте колесо. Если есть возможность, используйте функции стандартной
библиотеки ANSI С вместо того, чтобы создавать новые функции Это уменьшает
время разработки программы
Совет
по переносимости программ 5.1
Использование
функций стандартной библиотеки ANSI С
делает программы более пе¬
реносимыми.
Хотя
языка
scanf
С,
функции стандартной библиотеки
технически не являются частью
они неизменно поставляются с системами:
и pow,
которые
ANSI С. Функции printf,
мы использовали в предыдущих главах, являются
функ¬
стандартной библиотеки.
Программист может написать функции
циями
для выделения конкретных задач,
которые могут вызываться из многих точек программы. Эти функции иногда
упоминаются как функции, определенные программистом. Операторы, опре¬
деляющие функцию, пишутся только один раз, и эти операторы скрыты от
функций.
Обращение к функции осуществляется посредством вызова функции. В
вызове функции указывается ее имя и передается информация (в качестве ар¬
гументов), необходимая функции для выполнения своей задачи. Аналогом та¬
других
форма управления. Начальник (вызы¬
вающая функция) просит работника (вызываемую функцию) выполнить зада¬
кой процедуры служит иерархическая
будет выполнено, сообщить об этом. Например, функция, ко¬
торой требуется вывести информацию на экран, вызывает для выполнения за¬
дачи рабочую функцию printf; printf выводит информацию и передает (т.е.
возвращает) вызывающей функции сообщение о выполнении своей задачи.
Функция-начальник не знает, каким образом функция-работник выполняет
поставленную задачу. Работник может вызывать другие рабочие функции, а
начальник не будет об этом знать. Мы скоро увидим, как это «сокрытие» по¬
дробностей выполнения задачи способствует разработке хороших программ.
ние и, когда оно
На рис. 5.1 показана функция main, поддерживающая связь с несколькими
рабочими функциями согласно законам иерархии. Обратите внимание, что
действует как функция-начальник по отношению к worker4 и wor-
workerl
ker5. Отношения между функциями могут
ную от
иерархической, показанной
иметь и другую структуру, отлич¬
на этом рисунке.
Глава 5
188
main
workerl
worker2
worker4
worker3
worker5
Рис. 5.1. Иерархия отношений функции-начальника
и
функции-работника
Функции математической библиотеки
5.3.
Функции математической библиотеки позволяют программисту выпол¬
общие математические вычисления. Мы воспользуемся раз¬
личными функциями математической библиотеки, чтобы познакомиться с
концепцией функций. Позже в книге мы обсудим многие другие функции
стандартной библиотеки С. Полный список функций стандартной библиотеки
приведен в приложении Б.
Обычно к функции обращаются, записывая имя с последующими левой
нять некоторые
круглой скобкой, аргументом функции (или списком аргументов, отделяе¬
мых друг от друга
запятыми)
и
желающий вычислить
900.0, мог бы написать
мист,
pnntf("%.2f,
правой круглой скобкой. Например,
и
напечатать
значение
програм¬
квадратного корня из
sqrt(900.0));
Когда этот оператор выполняется, для вычисления значения квадратного
корня из числа в скобках (900.0) производится вызов функции
тической библиотеки. Число 900.0 является аргументом
Показанный оператор
типа
double
и
напечатал
возвращает
бы 30.00. Функция sqrt получает аргумент
типа double. Все функции математиче¬
результат
ской библиотеки возвращают данные
При
типа
double.
стиль программирования 5.2
Хороший
использовании
ловочный
sqrt из матема¬
функции sqrt.
файл
функций математической библиотеки
включите в программу заго¬
математики с помощью директивы препроцессора #include <math.h>
Распространенная ошибка программирования 5.1
При отсутствии в программе заголовочного файла математики обращение программы
функциям математической библиотеки может приводить к странным результатам
к
Аргументы функций
ниями. Если cl
=
13.0, d
printf("%.2f",
вычисляет
т.е. 5.00.
и
могут быть константами, переменными или выраже¬
=
sqrt(cl
печатае^
'
3.0
+
и
d
значение
f
*
=
4.0,
f) )
то оператор
;
квадратного
кОрня
из
13.0+3.0*4.0=25.0,
189
Функции
Некоторые функции математической библиотеки С
Переменные x
и у имеют тип
Функция
Назначение
sqrt(x)
квадратный корень
exp(x)
экспоненциальная
log(x)
натуральный логарифм
log10(x)
логарифм
fabs(x)
абсолютное
x
Пример
sqrt(900.0) равен 30
sqrt(9.0) равен 3.0
из x
функция ех
x
(основание e)
log(2 718282) равен 1 0
log(7 389056) равен 2 0
(основание 10)
log10(1 0) равен 0 0
log10(10 0) равен 1 0
log10(100 0) равен 2 0
значение x
если x >
если
округляет
x до
округляет
x до
не
ceil(9 2) равно 10 0
ceil(-9.8) равно -9.0
ближайшего целого,
не
floor(9.2) равно 9 0
floor(-9.8) равно -10 0
превосходящего x
pow(x, у)
fmod(x,
x возводится в степень у
у)
остаток
х/у
как число с
(ху)
pow(2, 7) равно 128 0
pow(9, 5) равно 3 0
плавающей точкой
(x
sin(x)
тригонометрический синус
cos(x)
тригонометрический косинус x (x в
tan(x)
тригонометрический тангенс x (x в
Рис. 5.2.
Функции общего
x
они
шинство функций
ям
sin(0 0)
равен 0 0
радианах)
cos(0.0)
равен 1 0
радианах)
tan(0 0)
равен 0.0
радианах)
библиотеки
Функции
разбить программу на модули. Все пе¬
функций, являются локальными пе¬
известны только той функции, в которой определены. Боль¬
имеют список параметров. Параметры позволяют функци¬
позволяют программисту
ременные, объявленные
ременными
в
fmod(13 657, 2 333) равен 1 992
назначения математической
5.4.
Функции
0, то fabs(x) равно x
x=0, то fabs(x) равно 0.0
x < 0, то fabs(x) равно -x
ближайшего целого,
меньшего x
floor(x)
0
exp(1 0) равна 2 718282
exp(2 0) равна 7.389056
если
ceil(x)
приведены на рис. 5.2.
double.
в определениях
обмениваться информацией. Параметры функции
это также локальные
переменные.
Общее
методическое замечание 5.2
В программах, содержащих большое число
как группа
обращений
Существует
несколько
Другой
функциям,
функций,
main должна быть реализована
выполняющим основную часть
оснований для разбиения программы
работы.
на
функции.
властвуй»
разработку программы более контроли¬
мотив
это повторное использование кода, т.е. использова¬
Подход «разделяй
руемой.
к
и
делает
190
Глава 5
ние однажды написанных
функций в качестве конструктивных блоков для со¬
Многократное использование кода является основ¬
фактором при переходе к объектно-ориентированному
здания новых программ.
ным
определяющим
программированию. Имея хорошо продуманные имена и определения функ¬
ций, можно создавать программы из стандартизованных модулей, не создавая
специализированного
ракция.
программного
Мы прибегаем
вызывающую функции
кода.
Эта методика
известна
как
абст¬
абстракции всякий раз, когда пишем программу,
стандартной библиотеки, например, printf, scanf и
к
Третьей причиной является правило избегать дублирования кода в про¬
Оформление кода в виде функции позволяет выполнять его в разных
частях программы посредством простого вызова функции.
pow.
грамме.
Общее
методическое замечание 5.3
Каждая функция должна ограничиваться выполнением одной, точно определенной
задачи, а имя функции должно отражать смысл данной задачи Это облегчает абст¬
ракцию и способствует многократному использованию программного кода
Общее методическое замечание 5.4
Если вы не можете выбрать краткое имя, которое выражает назначение функции, воз¬
можно, что ваша функция пытается выполнить слишком много задач. Лучше разбить
такую функцию на несколько меньших функций.
5.5.
Определения функций
Каждая из приведенных ранее программ состояла из функции с именем
main, которая для выполнения своей задачи вызывала функции стандартной
библиотеки. Теперь мы рассмотрим, как программисты пишут свои собствен¬
ные специализированные функции.
Рассмотрим программу, которая использует функцию square для вычис¬
ления квадрата целых чисел от 1 до 10 (рис. 5.3).
Функция square активируется, или вызывается в main внутри операто¬
ра printf:
pnntf("%d ",
square(x));
Функция square получает
копию значения x в
параметре у. Затем square
y*y. Результат передается назад
square, и printf выводит результат.
вычисляет значение произведения
printf
в
main,
где была вызвана
функции
Этот про¬
цесс повторяется десять раз в цикле for.
Определение функции square показывает, что square предполагает пере¬
дачу в параметре у целого значения. Ключевое слово int, предшествующее
имени функции, показывает, что square возвращает результат целого типа.
Оператор return в square передает результат вычислений функции, которая
вызвала square.
Строка
int
программы
square(mt);
является прототипом функции. Ключевое слово int
в
скобках информирует
вызывающей функции
целое значение. Слово int слева от имени функции square информирует компи¬
лятор о том, что square возвращает вызывающей функции результат целого
компилятор
о том, что square предполагает получить от
191
Функции
Компилятор использует прототип функции для проверки того, что вызо¬
корректный тип возвращаемых данных, корректное число ар¬
гументов, корректный тип аргументов и что аргументы следуют в правильном
порядке. Прототипы функций детально рассматриваются в разделе 5.6.
типа.
вы square имеют
/* Функция square,
#include <stdio.h>
определенная
int
/*
sguare(mt);
прототип
программистом */
функции
*/
main ()
{
int
x;
for
(x
1;
=
x
10;
<=
",
printf("%d
x++)
square(x));
printf("\n");
return 0;
}
/* Определение функции */
int square(mt у)
{
return у * у;
}
4
1
16
9
25
36
49
Рис. 5.3. Использование
Хороший
стиль
64
81
100
функции, определенной программистом
программирования 5.3
Вставляйте пустую строку между определениями функций, чтобы отделить функции
друг
от друга
и
улучшить
Определение функции
тем
самым
имеет
читаемость
программы
следующий формат:
тип_возвращаемого_значения имя_функции(список_параметров)
{
объявления
операторы
}
В качестве имени функции
(имя_функции)
может использоваться
любой
допустимый идентификатор. Типом результата, возвращаемого вызывающей
функции, является тип_возвращаемого_значения. Если тип_возвращаемого_значения задан ключевым словом void, то это означает, что функция ниче¬
го не возвращает. Если тип_возвращаемого_значения не указан, то компиля¬
тор
будет
предполагать тип int.
Распространенная ошибка программирования 5.2
Если тип_возвращаемого_значения в определении функции опущен, то это вызовет
синтаксическую ошибку в том случае, если прототип функции определяет, что возвра¬
щаемый
тип
не
является
целым
(int)
192
Глава 5
Распространенная ошибка программирования 5.3
Если функция должна возвращать некоторое значение, а в функции опущен оператор
возврата, то это может привести к непредвиденным ошибкам Стандарт ANSI гласит,
что в этом случае результат не определен
Распространенная ошибка программирования 5.4
Возврат значения из функции, для которой
void,
вызывает
Хороший
Хотя
синтаксическую
возвращаемый
тип
был объявлен
как
ошибку.
стиль программирования 5.4
опущенный возвращаемый
int, всегда за¬
функции main возвращае¬
тип по умолчанию расценивается как
давайте возвращаемый тип явным образом Однако
мый тип обычно опускается
для
это список объявлений параметров (отделенных
Список_параметров
запятыми), получаемых функцией при ее вызове. Если функция не получает
значения, список_параметров обозначается ключевым словом void. Тип каж¬
дого
параметра должен
int. Если
быть указан
явно
за исключением
параметров
тип параметра не указан, то по умолчанию принимается тип
типа
int.
Распространенная ошибка программирования 5.5
Запись float x, у
вместо
щихся к одному типу
ло
бы параметру у
float x, float у при объявлении параметров функции, относя¬
объявление параметров, как float x, у фактически присвои¬
Такое
тип
int, поскольку int принимается
по
умолчанию
Распространенная ошибка программирования 5.6
Символ точки
ров
в
с запятой после
определении
функции,
правой круглой скобки, закрывающей
является
синтаксической
список парамет¬
ошибкой
Распространенная ошибка программирования 5.7
Повторное определение параметра функции
синтаксическая ошибка
ции
как
локальной переменной внутри функ¬
-
Хороший
стиль программирования 5.5
Включайте тип каждого параметра в список параметров функции даже в том случае,
если данный параметр относится к типу int, принятому по умолчанию
Хороший
стиль программирования 5.6
ошибкой, не используйте одни и те
функции аргументов и соответствующих параметров
Хотя это и не является
же имена для передавае¬
мых
в
Это
поможет
Объявления
ции. Тело
определении
функции.
избежать неоднозначности
операторы внутри фигурных скобок
это
функции также называют блоком. Блок
и
образуют
просто
тело
функ¬
составной
опе¬
193
Функции
который включает в себя объявления. Переменные могут быть объявле¬
любом блоке, а блоки могут быть вложены. В любом случае функция не
может быть определена внутри
другой функции.
ратор,
ны в
Распространенная ошибка
программирования 5.8
-
Определение функции внутри другой функции
Хороший
Выбор
Общее
ошибка
стиль программирования 5.7
осмысленных имен
таемыми
синтаксическая
и
помогает
функций
и параметров делает программы
избежать чрезмерного
использования
более
удобочи¬
комментариев.
методическое замечание 5.5
функции не должна превышать одной страницы. Еще лучше, чтобы функция не
превышала по длине половины страницы. Небольшие функции способствуют повтор¬
ному использованию программного кода.
Длина
Общее методическое замечание 5.6
Программы должны составляться в виде совокупности небольших функций.
упрощает создание программ, их отладку, поддержку и модификацию.
Это
Общее методическое замечание 5.7
Функция, требующая большого количества параметров, может выполнять слишком
много задач. Рассмотрите возможность ее разделения на меньшие функции, которые
выполняют выделенные задачи. Заголовок функции должен, по возможности, умеща¬
ться на одной строке.
Общее
методическое замечание 5.8
Прототип функции, заголовок функции и вызов функции должны иметь взаимное со¬
ответствие по числу, типу и порядку следования аргументов и параметров, а также по
типу возвращаемого значения.
Существует три способа возвращения управления в ту точку программы, в
которой была вызвана функция. Если функция не возвращает результат,
управление возвращается просто при достижении правой фигурной скобки,
завершающей функцию, или при исполнении оператора
return;
Если функция возвращает результат, оператор
return выражение;
возвращает вызывающей функции значение выражения.
Наш второй пример демонстрирует определенную программистом функ¬
цию maximum для определения и возврата наибольшего из трех целых значе¬
ний (рис. 5.4). С помощью функции scanf три целых числа вводятся. После
этого числа передаются функции maximum, которая находит наибольшее це¬
лое число. Это значение возвращается в main оператором return в функции
maximum. Возвращенное
7
Зак 801
значение выводится на печать оператором
printf.
194
Глава 5
/*
Поиск
максимального
int
из
целых
трех
значений
maximum(int,
main ()
{
int а,
b,
int,
int);
/* прототип функции */
с;
printf("Enter three integers: ");
scanf("%d%d%d", &a, &b, &c);
printf("Maximum is: %d\n", maximum(a,
return
/*
int
*/
<stdio.h>
#include
b,
c));
0;
функции
Определение
maximum(int
x,
int
maximum
у,
int
*/
z)
{
int
if
max
>
(у
>
(z
max
return
x;
max)
=
max
if
=
у;
max)
=
z;
max;
integers:
85
22 85 17
integers:
85
85 22 17
Enter three integers:
Maximum is: 85
22 17 85
Enter
three
Maximum is:
Enter
three
Maximum is:
Рис. 5.4. Определенная программистом функция maximum
5.6.
Одной
наиболее
из
функций. Они были
Прототипы функций
важных
особенностей ANSI С
позаимствованы комитетом
Прототип функции сообщает компилятору
являются прототипы
ANSI С у разработчиков С++.
тип данных,
возвращаемых функ¬
цией, число параметров, получаемых
функцией, тип и порядок следования па¬
раметров. Компилятор использует прототипы функций для проверки коррект¬
ности обращений к функции. Предыдущие версии С не выполняли этот вид
проверки, так что существовала возможность неправильного вызова функции,
при котором компилятор не регистрировал ошибки. Такие обращения могли
приводить к
фатальным ошибкам при
кам, которые,
выполнении программы,
или к
ошиб¬
не вызывая краха программы, приводили тем не менее к труд-
нообнаружимым
логическим
ляют такое положение дел.
ошибкам. Прототипы функций
в
ANSI С исправ¬
195
Функции
Хороший
стиль программирования 5.8
Включайте в программу прототипы для всех функций, чтобы воспользоваться преи¬
муществами проверки типов С Используйте директивы препроцессора #include, что¬
бы получить прототипы функций стандартной библиотеки m заголовочных файлов
соответствующих библиотек
Также используйте #include, чтобы включить заголовоч¬
ные файлы, содержащие ваши собственные прототипы функций или прототипы, ис¬
пользуемые членами вашей рабочей группы
Прототипом функции maximum, приведенной
mt
maximum(mt,
что
возвращает результат типа int.
имеет
тот
же
вид,
что
и
5.4,
первая
maximum получает три параметра
Обратите
строка
Имена
ния
типа
int
внимание, что прототип
определения
функции
функции maximum, за
исключением того, что в него не включены имена параметров
Хороший
является
int);
int,
Этот прототип объявляет,
и
на рис.
(x,
у и
z).
стиль программирования 5.9
параметров иногда
включаются
Компилятор игнорирует
эти
в
прототип
функции
с целью документирова¬
имена
Распространенная ошибка программирования 5.9
Пропуск
точки с запятой в конце прототипа
функции
вызывает синтаксическую
ошибку
Вызов функции, который не соответствует ее прототипу, вызывает син¬
ошибку. Ошибка генерируется также в том случае, если не согла¬
сованы прототип функции и ее функции. Например, если прототип функции
записать в виде (рис. 5.4)
таксическую
void
maximum(mt,
mt,
int) ;
ошибку, поскольку возврат типа void в про¬
тотипе функции отличался бы от возврата типа int заголовка функции.
то компилятор зарегистрировал бы
автомати¬
Другое важное следствие применения прототипов функций
приведение аргументов, т.е. принудительное преобразование аргумен¬
тов функции к соответствующему типу. Например, функция математической
библиотеки sqrt может вызываться с целочисленным аргументом, и функция
будет работать правильно, хотя прототип ее в math.h определяет аргумент как
ческое
double. Оператор
pnntf("%.3f\n",
sqrt(4));
правильно вычисляет sqrt(4) и выводит на печать значение 2.000. Прототип
функции заставляет компилятор преобразовать целое значение 4 в значение
типа double
4.0
перед тем, как значение будет передано sqrt. В общем слу¬
чае значения аргументов, которые не соответствуют в точности типам парамет¬
ров в прототипе функции, перед вызовом преобразуются в соответствующий
тип.
Эти преобразования могут привести
учитывать правила возведения типов С.
к неправильным
результатам, если не
Правила возведения определяют, ка¬
ким образом одни типы могут быть преобразованы в другие типы без потери
данных. В нашем примере с sqrt тип int автоматически был преобразован в do¬
uble без изменения значения. Однако преобразование типа double в тип int от¬
брасывает дробную часть значения double. Преобразование типа больших
целых к типу малых целых (например, long в short) может также приводить к
изменению
7*
значений.
Глава 5
196
Правила возведения автоматически применяются к выражениям, содер¬
жащим значения двух или более типов данных (называемым выражениями
смешанного
типа). Каждое
значение в выражении смешанного типа автома¬
тически возводится к наивысшему типу
выражения1 (фактически
в выраже¬
временная версия каждого значения, а первона¬
чальные значения остаются неизменными). На рис. 5.5 перечислены типы
нии создается и используется
данных в порядке от наивысшего к низшему и приведены спецификации пре¬
образования
для операторов
printf
и
scanf.
Тип данных
спецификация преобразования
для printf
спецификация преобразования
для scanf
long double
%Lf
%Lf
double
%f
%lf
float
%f
%f
unsigned long int
%lu
%lu
long int
%ld
%ld
unsigned int
%u
%u
int
%d
%d
short
%hd
%hd
char
%c
%c
Рис. 5.5. Иерархия возведения
Преобразование значений
ному результату.
низкий
к
более
Следовательно,
тип только посредством явного присваивания
преобразуются
они
обычно приводит к невер¬
быть преобразовано в более
низким типам
значение может
го типа или с помощью операции приведения.
то
типов данных
к типу параметров прототипа
непосредственно
присваиваются
переменной более
низко¬
Значения аргументов функции
функции
переменным
таким
этого
образом,
типа.
как
Если
буд¬
наша
функция square, которая имеет параметр целого типа (рис. 5.3), вызывается с
аргументом типа float, то аргумент преобразуется в тип int (более низкий
тип). В
этом случае square может вернуть неверное значение.
re(4.5)
дало бы значение
16,
а не
Например,
squa-
более
низко¬
20.25.
Распространенная ошибка программирования 5.10
Преобразование более
му типу
может
высокого типа данных в иерархии возведения к
изменить
значение данных.
Если прототип для данной функции
лятор
дение
1
не
был
включен в программу, компи¬
формирует собственный прототип функции, используя ее первое вхож-'
в программу: или определение, или обращение к функции. По умолча¬
Точнее говоря, процедура возведения типа применяется последовательно к каждой паре
операндов текущей операции по ходу оценки выражения. Например, оценка выражения
3/2*3.14 даст врезультате 3.14, так как для первой операции (/) никакого возведения не
производится (оба операнда целые) и результатом ее будет целое 1. Только на следующем
шаге этот промежуточный результат будет возведен до 1.0, поскольку второй операнд умно¬
жения
число с плавающей точкой.
Прим. ред.
197
Функции
нию компилятор предполагает, что
функция возвращает тип int, ничего не
Следовательно, если аргументы,
функции, некорректны, то компилятор не обнаружит ошибку.
предполагая относительно типа аргументов.
переданные
Распространенная ошибка
программирования 5.11
Пропуск прототипа функции вызывает синтаксическую
ошибку,
щает тип, отличный от int, а определение
зова
во
функции появляется
функции. В других случаях пропуск прототипа функции
время
выполнения
программы или привести
к
если
функция возвра¬
в программе после вы¬
может вызвать
ошибку
непредвиденному результату
Общее методическое замечание 5.9
Прототип функции, размещенный вне любого определения функции, применяется ко
всем вызовам, появляющимся в файле после прототипа функции. Прототип функции,
помещенный в функцию, применяется
функции.
только
к тем
вызовам,
которые делаются
из
этой
5.7. Заголовочные
файлы
Каждая стандартная библиотека имеет свой заголовочный файл, содержа¬
всех функций данной библиотеки, а также определения
и констант,
необходимых этим функциям. На
различных типов данных
5.6
список
заголовочных
рис.
приведен алфавитный
файлов стандартной биб¬
лиотеки, которые могут быть включены в программу. Термин «макрос», кото¬
рый несколько раз используется в рис. 5.6, подробно обсуждается в главе 13,
щий прототипы для
«
Препроцессор ».
Заголовочный
файл
стандартной библиотеки
Содержимое
<assert.h>
Содержит
заголовочного
файла
диагностические макросы и
информацию, которая
помогает при отладке программы
<ctype.h>
Содержит прототипы для функций, которые проверяют определенные
характеристики символов и прототипы для функций, которые могут
использоваться для
преобразования символов
и наоборот
нижнего регистра
в
символы верхнего регистра
сообщений
ошибки
<errno.h>
Определяет макросы, которые
<float.h>
Содержит пределы значений для чисел
<limits.h>
Содержит пределы значений
<locale.h>
функций и другую информацию, которая
позволяет изменять работу программы согласно правилам текущего
локала, в котором она выполняется Понятие локала дает
компьютерной системе возможность обрабатывать соглашения,
полезны для
с
о типе
плавающей точкой
в системе
для целых чисел в системе.
Содержит прототипы
принятые в различных странах по всему миру для выражения таких
данных, как дата, время, денежные единицы и способ записи
больших чисел.
<math.h>
<setjmy.h>
Содержит прототипы
для
функций математической библиотеки
Содержит прототипы для функций, которые позволяют обойти
вызов функции и последовательность возврата
обычный
Глава 5
198
Заголовочный файл
стандартной библиотеки
Содержимое
<signal.h>
Содержит прототипы функций и макросы для обработки различных
условий, которые могут возникать во время выполнения программы
<stdarg.h>
Определяет макросы для обработки списка параметров функции, для
которой неизвестно число параметров и их тип
<stddef.h>
Содержит общие определения типов, используемых С для
выполнения некоторых вычислений
<stdio.h>
Содержит прототипы для функций ввода/вывода стандартной
библиотеки и информацию, используемую ими
<stdlib.h>
Содержит прототипы функций для преобразования чисел в текст и
текста в числа, прототипы функций размещения памяти, генерации
случайных чисел и других сервисных функций
<stnng. h>
Содержит прототипы для функций
<time.h>
Содержит прототипы функций и типы для функций управления
временем и датой
заголовочного
Рис. 5.6. Заголовочные
файла
обработки
строки
файлы стандартной библиотеки
специализированный заголовочный файл.
файлы также должны заканчива¬
ться .h. Определенный программистом файл может быть включен директивой
препроцессора #include. Например, заголовочный файл square.h может быть
Программист
Определенные
может
создать
программистом заголовочные
включен в нашу программу с помощью директивы
#include
размещенной
нительная
"square.h"
в
5.8. Вызов
Во
верхней
информация
части программы. В разделе
13.2 представлена допол¬
по включению в программу заголовочных
функций:
вызов по значению и по ссылке
вызов по значению и вызов по ссылке.
вызове по значению,
то
способа
фун¬
Когда аргумент используется в
многих языках программирования существует два
кций
файлов.
вызываемой функции передается
вызова
копия значения ар¬
Изменения, происходящие с копией, не отражаются на значении ис¬
ходной переменной в вызывающей функции. Когда аргумент функции переда¬
ется по ссылке, вызывающая функция фактически позволяет вызываемой
функции изменять значение исходной переменной.
Передача аргумента по значению должна использоваться, когда вызывае¬
мой функции не нужно менять значение исходной переменной в вызывающей
функции. Это предотвращает случайные побочные эффекты, которые мешают
разработке правильных и надежных систем программного обеспечения. Пере¬
гумента.
дача аргумента по ссылке должна применяться только с такими вызываемыми
функциями, которым необходимо
менять первоначальную переменную и ко¬
торым можно
«доверять».
В С все вызовы передают аргументы по значению. Как
ве
7,
льзуя операции взятия
мы увидим в гла¬
имитировать передачу аргумента по ссылке, испо¬
адреса и косвенные операции. В главе 6 мы увидим,
имеется возможность
199
Функции
что массивы автоматически передаются посредством имитации передачи аргу¬
мента по ссылке. Мы должны подождать до главы 7, чтобы полностью разо¬
Пока
браться в этом довольно сложном вопросе.
передачей аргументов по значению.
5.9.
Теперь
Генерация случайных
мы сделаем
небольшой перерыв
В
этом и следующем разделах мы
а именно с моделированием и
разработаем
ную игровую программу, которая включает
демонстрирует большую
чисел
и немного развлечемся с интересным
и популярным приложением программирования,
играми.
мы ограничимся вызовами с
в
себя
хорошо структурирован¬
много
функций. Программа
часть изученных нами управляющих структур.
В азартной атмосфере казино есть что-то, что определяет поведение любого
оказавшегося там человека: от игрока, делающего крупные ставки за дорогим
столом для игры в кости,
случайности,
до игрока за игральным автоматом.
возможность того, что
гору денег. Элемент случайности может быть введен
жения, если использовать
функцию rand
Рассмотрим следующий
i
=
Это
элемент
случай превратит просто полный карман
из
в
и в компьютерные прило¬
стандартной библиотеки С.
оператор:
rand();
rand
Функция
генерирует
целое
значение
в
RAND_MAX (символическая константа, определенная
диапазоне
от
в заголовочном
0
до
файле
<stdlib.h>). Стандарт ANSI объявляет, что значение RAND_MAX должно
32767, т.е. максимальному значению 2-байтового
целого (или 16-битового, что то же самое). Программы этого раздела были про¬
быть равно по меньшей мере
верены на системе С с максимальным значением 32767 для RAND_MAX.
Если rand действительно генерирует целые числа случайным образом, то каж¬
между 0 и RAND_MAX имеет равные шансы (или вероятность)
быть выбранным при каждом вызове функции rand.
Диапазон значений, непосредственно генерируемых rand, часто отличает¬
ся от того, который необходим в данном приложении. Например, программе,
которая имитирует подбрасывание монеты, требуется только два значения: 0
дое число
для «орла» и 1 для «решки». Программе, которая моделирует выбрасывание
игральной кости с шестью сторонами, потребовались бы случайные целые чис¬
ла в диапазоне от 1 до 6.
Чтобы продемонстрировать функцию rand, давайте разработаем програм¬
му, которая имитирует 20 бросков шестигранной игральной кости и печатает
значение,
быть
выпавшее при каждом
четании с
rand()
броске. Прототип функции
<stdlib.h>. Мы применим операцию
rand:
найден
%
в
для rand может
взятия по модулю
(%)
в со¬
6
чтобы генерировать целые в диапазоне от 0 до 5. Это называется масштабиро¬
ванием. Число 6 называется коэффициентом масштабирования. После этого
мы
сдвигаем диапазон генерируемых чисел, добавляя 1 к нашему предыдуще¬
Рис. 5.7 подтверждает, что результаты находятся в диапазоне
1 до 6.
Чтобы показать, что эти числа появляются приблизительно с равной веро¬
му результату.
от
ятностью, давайте смоделируем 6000 бросков игральной кости, используя про¬
грамму, приведенную на рис. 5.8. Каждое целое число от 1 до 6 должно «вы¬
пасть» приблизительно 1000 раз.
200
Глава 5
/* Генерация целых 1
#include <stdio.h>
#include <stdlib.h>
main ()
{
int
+
rand()
%
6
в
масштабе
и
со
сдвигом
*/
i;
for
(i
1;
=
<=
i
pnntf("%10d",
if
20;
1
+
i++) {
(rand()
%
6));
(i % 5
0)
printf("\n");
==
}
return
5
2
5
5
0;
5
4
3
1
3
2
2
4
5
5
2
6
Рис. 5.7. Масштабированные
/*
Бросание
#include
#include
main
и
5
5
1
4
сдвинутые целые, генерируемые выражением 1 + rand() % 6
шестигранной игральной
кости
6000
раз
*/
<stdio.h>
<stdlib.h>
()
{
int
face,
roll,
frequency3
frequency5
for
(roll
face
=
switch
=
1;
1
+
frequencyl
0,
0,
=
=
frequency4
frequency6
roll
rand()
(face)
case
=
<=
%
6000;
0,
=
=
frequency2
0,
=
0,
0;
roll++)
{
6;
{
1:
++frequencyl;
break;
case
2:
++frequency2;
break;
case
3:
++frequency3;
break;
case
4:
++frequency4;
break;
case
5:
++frequency5;
break;
Рис. 5.8.
Бросание шестигранной игральной
кости 6000 раз
(часть 1
из
2)
201
Функции
case
6:
++frequency6;
break;
pnntf("%s%13s\n", "Face , "Frequency");
l%13d\n", frequencyl);
pnntf("
2%13d\n", frequency2);
pnntf("
3%13d\n", frequency3);
printf("
4%13d\n", frequency4);
pnntf("
5%13d\n", frequency5);
pnntf("
6%13d\n", frequency6);
pnntf("
return
Face
0;
Frequency
987
984
1029
974
1004
1022
1
2
3
4
5
6
Рис. 5.8. Бросание шестигранной игральной
Как
кости 6000 раз
(часть 2
из
2)
выведенный программой, масштабирование и
функцией rand позволяет реалистично моделировать вра¬
щение шестигранной игральной кости. Обратите внимание, что в структуре
switch нет варианта default. Также обратите внимание на использование спе¬
цификации преобразования %s для печати символьных строк «Face» и «Fre¬
quency» в качестве заголовков столбцов. После того как мы исследуем масси¬
вы в главе 6, мы покажем, как заменить всю структуру switch на оператор,
умещающийся в одной строке.
Повторное выполнение программы рис. 5.7 дает результат
показывает результат,
сдвиг в сочетании с
5
2
5
5
5
4
3
1
3
2
2
4
5
5
2
6
5
5
1
4
Обратите внимание, что была напечатана та же самая последователь¬
значений. Как же это может быть случайными числами? Как ни стран¬
эта повторяемость является важной характеристикой функции rand. При
ность
но,
отладке программы
эта повторяемость является существенным
доказательства того,
что
исправления,
внесенные
в
фактором
программу,
для
работают
правильно.
Функция rand
зове
генерирует на самом деле
псевдослучайные
числа.
При
вы¬
rand генерируется последовательность чисел, которые выглядят случай¬
ными.
Однако
эта последовательность повторяется при каждом новом выпол¬
нении программы.
фицировать
Когда программа будет тщательно отлажена, можно моди¬
последовательностей случайных чи¬
ее для генерации различных
сел для каждого выполнения.
Это
называется
рандомизацией
и
обеспечивает¬
функцией стандартной библиотеки srand. Функция srand получает в каче¬
стве аргумента целое без знака (тип unsigned), называемое семенем, которое
ся
Глава 5
202
позволяет получать от rand различные последовательности
случайных
чисел
при каждом исполнении программы.
Применение функции srand показано на рис. 5.9. В программе мы исполь¬
тип данных unsigned, что является сокращением записи unsigned int.
зуем
Тип int хранится
по меньшей мере в двух байтах памяти и может принимать
положительные и отрицательные значения.
Переменная
типа
unsigned
также
меньшей мере в двух байтах памяти. Двухбайтовое целое типа un¬
int может принимать только положительные значения в диапазоне от 0
хранится по
signed
до 65535. Четырехбайтовое целое
типа
жительные значения в диапазоне от
unsigned int
может принимать поло¬
0 до 4294967295. Функция srand получа¬
unsigned. Для чтения данных типа un¬
signed функцией scanf необходима спецификация преобразования % u. Прото¬
ет в качестве аргумента значение типа
тип
функции
для srand находится в <stdlib.h>.
Давайте выполним программу несколько раз и посмотрим на результаты.
Обратите внимание, что при каждом выполнении программы получаются раз¬
личные последовательности случайных чисел, что обеспечивается вводом раз¬
личных значений для семени.
/*
Рандомизация
#include
I #include
программы бросания
<stdlib.h>
игральной
кости
*/
<stdio.h>
I main()
{
int
I;
unsigned
seed;
printf("Enter seed:
scanf("%u", &seed);
srand(seed) ;
for
(i
=
1;
i
<=
pnntf("%10d",
if
(i
%
5
==
10;
1
");
i++)
+
{
(rand()
%
6));
0)
printf("\n");
return
0;
Enter seed:67
1
5
6
6
5
3
1
1
4
2
Enter seed:
4
2
432
2
5
5
1
4
4
3
4
Enter seed:
1
5
67
6
6
5
3
1
1
4
2
Рис. 5.9. Рандомизация программы бросания игральной
кости
(окончание)
203
Функции
Если мы хотим проводить рандомизацию без ввода семени, мы можем
написать оператор вроде
srand(time(NULL));
здесь считывает показания внутренних часов, чтобы автомати¬
Компьютер
Функция time возвращает
преобразуется в целое без знака
случайных чисел. Функция time по¬
чески получить не определенное заранее значение.
текущее время дня
секундах1.
в
Это
значение
и используется как семя для генератора
NULL (функция time может формировать строку,
представляющую время дня2; аргумент NULL запрещает этот вариант в дан¬
лучает в качестве аргумента
ном вызове
time). Прототип функции time
находится в <time.h>.
Значения, непосредственно генерируемые rand,
всегда находятся в диапа¬
зоне
<=
0
rand()
<=
RAND_MAX
Предварительно
мы
показали,
как
единственный оператор С,
написать
имитирующий вращение шестигранной игральной
face
+
1
=
%
rand.()
кости:
6;
Этот оператор всегда присваивает целое значение
ременной face
диапазона (т.е.
ным
числом
в
диапазоне
от
(случайным образом)
1 до 6. Обратите внимание,
число последовательных целых чисел в
этого
диапазона является
1.
Что
что
нем)
пе¬
ширина этого
равна 6 и началь¬
касается
предшествующего
утверждения, мы видим, что ширина диапазона определяется числом, исполь¬
зуемым для масштабирования функции rand операцией деления по модулю
(т.е. 6),
а
начальное
число
rand % 6 (т.е. 1). Мы
=
n
а
+
rand()
Здесь а
довательных
равен ширине
конце
равно
обобщить
числу,
которое
прибавляется
образом:
к
этот результат следующим
% b;
значение
целых
диапазона
можем
сдвига (которое равно первому числу диапазона после¬
и b
коэффициент масштабирования (который
чисел),
чисел). В упражнениях в
выбирать случайные целые числа из на¬
диапазона последовательных целых
раздела мы увидим, что можно
боров значений,
не являющихся последовательными.
Распространенная ошибка программирования 5.12
Использование srand
5.10.
вместо
rand для генерации случайных
Пример:
стохастическая
чисел.
игра
Одной из наиболее популярных стохастических игр
который играют в казино и в темных закоулках по всему
является
миру.
«крепс»,
Правила
в
игры
просты:
Игрок выбрасывает две кости. Каждая кость имеет шесть граней. Эти
грани содержат 1, 2, 3, 4, 5 и 6 точек. После того как кости остановятся, вы¬
числяется сумма точек на двух гранях, повернутых вверх. Если выпавшая
сумма при первом броске оказалась равной 7
1
Хоть
или
11,
то
победил игрок. Если
функция time возвращает
не текущее
в данном примере это и не важно, заметим, что
с момента 00:00:00 GMT 1-го января 1970 г.
время дня, а время
(в секундах), прошедшее
2
прим. ред.
Функция time этого не может. Ее аргумент (если он не NULL)
ременную,
прим. ред.
в
которую
записывается
то
же
самое
значение,
это просто указатель на пе¬
что
функция возвращает.
204
Глава 5
броске составила 2, 3 или 12, то игрок проигрывает (выиг¬
Если
рывает казино).
сумма первого броска равна 4, 5, 6, 8, 9 или 10, то эта
становится
очками
сумма
игрока. Чтобы выиграть, вы должны продолжить
бросать кости до тех пор, пока не наберете свои очки еще раз. Игрок проиг¬
сумма при первом
если при очередном броске выпадает 7.
Программа, приведенная на рис. 5.10, моделирует
рывает,
рис. 5.11
данную игру. На
показано несколько примеров выполнения программы.
/* Игра в крепс */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int
rollDice(void);
main ()
{
int gameStatus,
sum,
myPoint;
srand(time(NULL)) ;
sum
rollDice();
=
switch(sum)
case
7:
2:
первый выброс костей */
/*
выигрыш при первом броске
/*
проигрыш при первом броске
/*
запомнить
{
case
11:
case
3:
case
12:
*/
2;
=
gameStatus
break;
default:
*/
1;
=
gameStatus
break;
case
/*
очки
*/
0;
gameStatus
sum;
myPoint
=
=
pnntf("Point is %d\n",
break;
}
while
==
sum
(gameStatus
rollDice();
if
(sum
0)
{
myPoint);
/*
*/
продолжать бросать
=
==
myPoint)/* выигрыш повторным выбросом
gameStatus
else
if (sum
=
gameStatus
=
}
if
/*
7)
==
проигрыш при выбросе
2;
1)
(gameStatus
printf("Player wins\n");
else
==
pnntf("Player loses\n");
return 0;
}
int
rollDice(void)
{
int diel,
die2,
очков
1;
workSum;
Рис. 5.10. Программа, моделирующая игру
в крепс
(часть 1
из
2)
7
*/
*/
205
Функции
die2
=
1
+
(rand()
%
б);
=
1
+
(rand()
%
б);
|diel
workSum
=
+
diel
rolled
pnntf("Player
return
die2;
%d
+
%d
=
%d\n",
diel,
die2,
workSum);
workSum;
1
Рис. 5.10.
Программа, моделирующая игру
Player rolled 6
+
5
=
11
Player wins
Player rolled
6
+
б
=
12
4
+
б
=
10
Player
rolled 2
+
4
=
б
Player
rolled
б
+
5
=
11
Player
rolled 3
+
3
=
б
Player
rolled
б
+
4
=
10
rolled 1
+
3
=
4
5
Player
в крепс
(часть 2
из
2)
loses
Player rolled
Point is 10
Player wins
Player
Point
is
4
Player
rolled
1
+
4
=
Player
rolled
5
+
4
=
9
Player
rolled 4
+
б
=
10
Player
rolled
б
+
3
=
9
Player
rolled 1
+
2
=
3
Player
rolled
+
2
=
7
Player
loses
5
Рис. 5.11.
Пример
выполнения программы игры в кости
Обратите внимание, что и в первом броске и во всех последующих игрок вы¬
брасывает две кости. Мы определяем функцию rollDice таким образом, что она
выполняет броски игровых костей, вычисляет и печатает сумму. Функция rol¬
lDice определена один раз, но вызывается в двух местах программы. Интересно,
что
rollDice
стоит
не
получает никаких
аргументов,
поскольку
void. Функция rollDice возвращает сумму, выпавшую
тях, так что в заголовке
Начинается игра.
броске,
функции
Игрок
или может выиграть
в качестве
в
списке
параметров
на двух игровых кос¬
возвращаемого типа указан тип int.
либо проиграть уже при первом
либо проиграть при любом последующем. Для от¬
может выиграть
gameStatus.
броске, либо на любом из по¬
следующих, переменная gameStatus устанавливается равной 1. Когда про¬
грамма проиграла на первом броске либо на одном из последующих, gameSta¬
tus устанавливается равной 2. В других случаях gameStatus равна нулю и
слеживания ситуации используется переменная
Когда
программа выиграла либо на первом
игра должна продолжаться.
Если после первого броска игра заканчивается, программа обходит струк¬
gameStatus не равна нулю. Программа пе¬
if/else, которая выводит на печать строку «Player wins»
(Игрок выиграл), если gameStatus равна 1, и «Player loses» (Игрок проиг¬
рал), если gameStatus равна 2.
туру while, поскольку переменная
реходит к структуре
Глава 5
206
броска игра не заканчивается, то значение sum сохра¬
переменной myPoint. Выполнение программы продолжается с учас¬
структуры while, поскольку gameStatus равна 0. Каждый раз при про¬
Если после первого
няется в
тием
хождении структуры while вызывается функция rollDice, которая генерирует
новую сумму. Если значение sum совпадает со значением myPoint, gameSta¬
tus устанавливается
на
1, чтобы показать, что игрок выиграл. После этого сле¬
while, поскольку ее условие перестает выполняться,
дует выход из структуры
if/else
структура
Если
граммы прекращается.
2, чтобы
ется на
«Player
выводит на печать строку
значение sum равно
wins»
и выполнение про¬
7, gameStatus устанавлива¬
Далее следует выход из струк¬
«Player loses» и выполнение про¬
показать, что игрок проиграл.
туры while, оператор
if/else
печатает строку
граммы прекращается.
Обратите
Мы
внимание на интересную управляющую
использовали две
if/else
и
вложенную
интересные
main
функции
структуру
if. В упражнениях
характеристики игры
в
структуру программы.
rollDice, структуры switch, while,
и
мы
исследуем
различные
крепс.
5.11. Классы
памяти
В главах 2-4 для имен переменных мы использовали идентификаторы.
Атрибуты переменных включают в себя имя, тип и значение. В этой главе мы
также используем идентификаторы в качестве имен функций, определяемых
пользователем. На самом деле каждый идентификатор в программе имеет и
атрибуты,
другие
вия
и
тип
включая
класс
памяти,
период
хранения, область дейст¬
компоновки.
Язык С поддерживает четыре класса памяти, обозначаемые спецификато¬
рами класса памяти: auto, register, extern и static. Класс памяти идентифи¬
катора
помогает определить его период хранения,
поновки.
Период
данный идентификатор существует
область действия
это время,
хранения идентификатора
в памяти.
и тип ком¬
в течение которого
Некоторые идентификаторы
су¬
ществуют короткое время, некоторые неоднократно создаются и разрушают¬
ся, другие существуют в течение всего времени выполнения программы. Об¬
ласть действия идентификатора характеризует возможность обращения к
нему из различных
частей программы. Некоторые идентификаторы доступны
только в отдельных ее частях. Тип компоновки
во всей программе, другие
идентификатора определяется для программ, состоящих из нескольких исход¬
ных файлов, объединяемых на этапе компоновки (предмет, который мы иссле¬
14). Эта характеристика показывает, известен ли идентификатор
только в текущем исходном файле или в любом исходном файле с соответству¬
дуем в главе
ющими
объявлениями. В
и период хранения.
торов. В
главе
этом разделе рассматриваются четыре класса памяти
В разделе 5.12 обсуждается область действия идентифика¬
14 обсуждается
тип компоновки
рование с несколькими исходными
Четыре спецификатора
периоду хранения:
идентификатора
и программи¬
файлами.
быть разбиты на два типа по
хранения и статический период
класса памяти могут
автоматический
период
хранения. Для объявления переменных
с автоматическим периодом хранения
register. Переменные с автоматическим хране¬
управление получает программный блок, в котором они
служат ключевые слова auto и
нием создаются, когда
объявлены. Переменные
жаются,
этого типа существуют, noi
когда происходит выход из
блока.
x
блок активен,
и уничто¬
207
Функции
Автоматический период хранения могут иметь только переменные. Лока¬
льные переменные функции (объявленные в списке параметров или в теле
функции) обычно
имеют
автоматический период хранения. Ключевое
auto объявляет переменные
с автоматическим хранением явным
слово
образом. На¬
пример, следующее объявление показывает, что переменные x и у типа float
являются автоматическими локальными переменными и существуют только в
теле функции, в
auto
float
которой
x,
находится данное объявление:
у;
Локальные переменные имеют автоматический период хранения по умол¬
чанию, так что ключевое слово auto используется редко. Во всем последую¬
щем тексте мы будем называть переменные с автоматическим периодом хране¬
ния просто автоматическими переменными.
Совет по повышению
5.1
эффективности
Автоматическое хранение
способствует
экономии памяти,
переменные существуюттолько тогда, когда они
функции,
функции
пуске
из
Общее
в
которой
они
объявлены,
и
поскольку автоматические
необходимы Они
создаются при за¬
уничтожаются, когда происходит выход
методическое замечание 5.10
Автоматическое хранение является примером реализации принципа минимальных
привилегий. Почему переменные должны храниться в памяти и быть доступными,
когда в данный момент в них нет
необходимости?
Данные в программе на машинном
другой обработки обычно загружаются
языке для выполнения вычислений
в регистры.
и
Совет по повышению эффективности 5.2
Перед объявлением автоматической переменной может быть помещен спецификатор
класса памяти register, чтобы рекомендовать компилятору разместить ее в одном из
аппаратных регистров компьютера. Если интенсивно используе¬
мые переменные типа счетчиков или сумм будут реализованы в аппаратных регист¬
рах, то можно исключить непроизводительные затраты на неоднократную загрузку
переменных из памяти в регистры и обратно
быстродействующих
Компилятор
распоряжении
может
проигнорировать
компилятора
может
не
объявление register. Например,
оказаться
достаточного
числа
в
регист¬
Следующее объявление предлагает, чтобы целая переменная counter
была размещена в одном из регистров компьютера и инициализирована значе¬
нием 1:
ров.
register
int
counter
Ключевое слово
имеющими
Совет
=
register
1;
может
использоваться
только
с
переменными,
автоматический период хранения.
по повышению
Объявления register
эффективности 5.3
часто
бывают
не нужны
Современные оптимизирующие
компи¬
ляторы способны распознать часто используемые переменные и могут размещать их в
регистрах самостоятельно, не требуя от программиста объявления register
Глава 5
208
Ключевые слова extern и static используются для объявления идентифика¬
торов переменных и функций со статическим периодом хранения. Идентифика¬
торы статического периода хранения существуют с того момента, как програм¬
ма начинает выполняться. Для переменных память распределяется и инициа¬
один
лизируется
когда
раз,
программа запускается.
Для функций
начинает существовать с момента начала выполнения программы.
при том, что переменные и имена
полнения
программы,
это
не
означает,
всей программе. Период хранения
но)
функций
и
являются разными вещами, как
что
имя также
Однако
даже
существуют с момента начала
вы¬
идентификаторы доступны
эти
область действия (область, где
мы и увидим в разделе 5.12.
во
имя доступ¬
типа идентификаторов со статическим периодом хране¬
идентификаторы (вроде глобальных переменных и имен функ¬
Существует два
ния: внешние
ций)
и локальные
переменные, объявленные со спецификатором класса памя¬
static. Глобальные переменные и имена функций имеют по умолчанию
класс памяти extern. Глобальные переменные создаются при помещении их
объявлений вне любого определения функции, и они сохраняют свои значения
ти
времени выполнения программы.
в течение всего
ременным
их
и
функциям
возможно из
Обращение
глобальным
к
любой функции, которая следует
пе¬
после
файле. Это является одной из причин испо¬
функций. Когда мы включаем stdio.h в программу, ко¬
вызывает printf, прототип функции помещается в начало нашего файла,
имя printf известным для остальной части файла.
объявления
или определения в
льзования прототипов
торая
делая
Общее
методическое замечание 5.11
Объявление
ным
переменной
как
глобальной,
а не
локальной,
побочным эффектам, когда функция, которой
может приводить к
случай¬
доступ к этой перемен¬
ной, случайно или преднамеренно ее изменяет. В целом нужно избегать использова¬
ния глобальных переменных, за исключением некоторых ситуаций с уникальными
требованиями к производительности (рассматривается в главе 14).
Хороший
не нужен
стиль программирования 5.10
Переменные, использующиеся
только в
лены локальными переменными этой
определенной функции, должны быть объяв¬
функции, а не внешними переменными
Локальные переменные, объявленные
известными только той
функции,
в
с ключевым словом
которой
static,
остаются
они определены, но в отличие от
автоматических статические локальные переменные сохраняют свое значение
и после выхода из
переменная
будет
функции. При следующем
выходе из функции.
ная
Следующий оператор объявляет,
static будет статической,
static
вызове статическая локальная
содержать то значение, которое она имела при последнем
int count
=
что локальная перемен¬
и инициализирует ее значением
1.
1;
Все числовые переменные со статическим хранением инициализируются
если они явно не инициализированы программистом. (Перемен¬
нулем,
ные-указатели,
обсуждаемые
в главе
7, инициализируются
Распространенная ошибка программирования
значением
NULL.)
5.13
Использование нескольких спецификаторов класса памяти для идентификатора. К
данному идентификатору может применяться только один спецификатор класса па¬
мяти.
209
Функции
Ключевые слова extern и static имеют специальное значение, когда при¬
меняются к внешним идентификаторам явным образом. В главе 14 мы увидим
программы,
явно использующие
5.12.
extern
и
Правила области действия
Областью действия идентификатора
торой
возможно
static.
обращение
к нему.
является та часть программы, в ко¬
Например,
когда мы объявляем в некото¬
ром блоке локальную переменную, к ней можно
обратиться только из этого
блоков, вложенных в данный блок. Область действия идентифи¬
катора делится на четыре вида: область действия функции, область дейст¬
вия файла, область действия блока и область действия прототипа функ¬
блока
или из
ции.
Метки (идентификаторы,
являются единственными
сопровождающиеся двоеточием,
вроде start:)
идентификаторами, принадлежащими области дей¬
функции. Метки могут
которой они появляются, но
ствия
использоваться в произвольном месте функции,
в
на них нельзя сослаться вне тела
ки используются в структурах
goto
(см.
14). Метки
switch (в
относятся
к
качестве меток
case)
функции. Мет¬
и в
операторах
подробностям
реализации, которые
функции скрывают от друг друга. Эта скрытность, более формально называе¬
мая сокрытием информации, является одним из наиболее фундаментальных
главу
принципов хорошего стиля программирования.
Идентификатор, объявленный вне любой функции, имеет область дейст¬
файла. Такой идентификатор «известен» всем функциям начиная с того
места, где он объявлен, и до конца файла. Глобальные переменные, определе¬
ния функций и прототипы функций, помещенные вне функции,
все они
вия
область действия файла.
Идентификаторы, объявленные внутри блока, имеют область действия
блока. Область действия блока заканчивается завершающей правой фигурной
имеют
скобкой (}) блока. Локальные
имеют
переменные, объявленные
область действия блока,
рассматриваются
так
же
как
и
как ее локальные переменные.
начале функции,
функции, которые
в
параметры
Любой блок
может содержать
объявления переменных. Когда блоки вложены, а идентификатор во внешнем
блоке имеет то же самое имя, что и идентификатор во внутреннем блоке, иден¬
тификатор во внешнем блоке «скрывается», пока внутренний блок не завер¬
шит работу. Это означает, что пока выполняется внутренний блок, он видит
значение собственного локального идентификатора, а не значение идентифи¬
катора с тем же именем, находящегося в объемлющем блоке. Локальные пере¬
менные, объявленные как static, имеют область действия блока несмотря на
Таким образом,
область действия идентификатора.
идентификаторами с областью действия прототипа
то, что существуют с момента начала выполнения программы.
период хранения
не влияет на
Единственными
функции
идентификаторы, которые используются в списке парамет¬
функций не требу¬
списке параметров стояли имена идентификаторов; требуется то¬
являются
ров прототипа функции. Как отмечалось выше, прототипы
ют, чтобы в
Если
параметров прототипа используется имя, то ком¬
указанные в прототипе функции,
могут неоднократно встречаться в других местах программы, и здесь не возни¬
кает никакой неоднозначности.
лько их тип.
в списке
пилятор его игнорирует.
Идентификаторы,
210
Глава 5
Распространенная ошибка программирования 5.14
Случайное объявление во внутреннем блоке имени идентификатора, которое уже ис¬
пользуется во внешнем блоке, в то время как программист на самом деле хотел, что¬
бы идентификатор внешнего блока был доступен для внутреннего блока
Хороший
Не
стиль программирования 5.11
объявляйте
ствия. Можно
имен переменных, которые скрывают имена во внешних областях дей¬
просто избегать любого дублирования идентификаторов в программе
Программа, приведенная на рис. 5.12, иллюстрирует правила применения
глобальных, автоматических локальных и статических локальных перемен¬
ных. Глобальная переменная x объявляется и инициализируется значением 1.
Эта глобальная переменная скрыта в любом блоке
(или функции),
в
котором
объявлена переменная с именем x. В main объявляется и инициализируется
значением 5 локальная переменная x. Эта переменная сразу выводится на пе¬
чтобы показать, что глобальная переменная x скрыта от main. После этого
main определяется новый блок с другой локальной переменной x, инициали¬
зированной значением 7. Эта переменная выводится на печать, чтобы показать,
что переменная x нового блока скрывает переменную внешнего блока main. Пе¬
ременная x со значением 7 автоматически разрушается, когда блок прекращает
чать,
в
работу, и локальная переменная x внешнего блока main печатается еще раз,
чтобы показать, что она уже не скрыта. В программе определены три функции
таким образом, что каждая из них не получает никаких аргументов и ничего не
возвращает.
Функция
а определяет автоматическую локальную переменную x и
25. При
инициализирует ее значением
вызове
функции
а эта переменная выво¬
дится на печать, увеличивается и печатается еще раз перед выходом из
функ¬
этой функции автоматическая локальная пере¬
менная x заново инициализируется значением 25. Функция b объявляет стати¬
ческую переменную x и инициализирует ее значением 50. Локальные перемен¬
ные, объявленные как static, сохраняют свои значения даже вне обычной обла¬
ции.
сти
Каждый раз при
вызове
действия локальной переменной. При
вызове
функции b
печатается значе¬
функции.
При следующем обращении к этой функции статическая локальная переменная
x будет содержать значение 51. Функция с не объявляет никаких переменных.
Следовательно, когда она обращается к переменной x, используется глобальная
ние x, затем x увеличивается и печатается еще раз перед выходом из
При следующем вызове функции с печатается значение глобаль¬
переменной, она умножается на 10 и печатается еще раз перед выходом из
функции. При последующем вызове функции с глобальная переменная все еще
переменная x.
ной
имеет модифицированное значение
значение
локальной переменной
из вызовов
функций
x в
10. В заключение программа печатает
main еще раз, чтобы показать, что ни один
не изменил значение x, поскольку
переменные в других
функции
ссылались на
областях действия.
5.13.
Рекурсия
Программы, которые мы обсуждали,
ций, которые вызывают другие функции,
имели
в
основном
структуру
функ¬
иерархии. Для
некоторых типов задач полезно иметь функции, которые вызывают сами себя.
Рекурсивная функция
это
подчиняясь
строгой
функция, которая вызывает саму себя или непо¬
сложный пред¬
функцию. Рекурсия
средственно, или косвенно через другую
211
Функции
мет, изучаемый лишь в некоторых курсах по компьютерным дисциплинам. В
этом и в следующих разделах приведены простые примеры рекурсии. Эта книга
уделяет большое внимание рекурсии, которая так или иначе применяется начи¬
ная с главы 5
12. Рис. 5.17 в конце раздела 5.15 содержит сводку по
упражнениям на рекурсию, приведенным в книге.
и до главы
всем примерам
и
/* Пример по областям действия
#include <stdio.h>
void a(void);
void b(void);
void c(void);
int
=
x
main()
{
int
5;
=
прототип функции
прототип функции
*/
/*
прототип
функции
*/
/*
локальная
("local
pnntf
/*
{
int
=
x
*/
/*
/*
глобальная переменная
1;/*
x
*/
x
переменная
outer
начало
scope
новой
of
области
*/
main
для
main
x);
%d\n",
is
*/
действия
7;
printf("local
/*
}
in
*/
x
x
pnntf("local
in
inner
in
scope
обл'асти
новой
конец
outer
of
scope
of
main
is
*/
main
%d\n",
is
x);
%d\n",
действия
x);
а содержит автоматическую локальную x */
/* b содержит статическую локальную x */
/* с использует глобальную x */
/* а заново инициализирует автоматическую
*/
локальную x
/* статическая локальная x сохраняет предыдущее
значение
*/
/* глобальная x также сохраняет свое значение */
/*
a();
b();
c();
a();
b();
c();
printf("local
return
x
in
main
is
%d\n",
x);
0;
}
void
а(void)
{
int
x
=
/*
25;
инициализируется при каждом
printf("\nlocal
x
in
is
а
%d
after
entering
*/
а
вызове
a\n",
x);
++x;
printf("local
x
in
а
is
%d
before
x);
exiting a\n",
}
void b(void)
{
static int
x
=
printf("\nlocal
50;
static
/*
/*
x
как
static
*/
первом
вызове
b
инициализация
только
is
%d
при
on
entering b\n",
*/
x);
Рис 5.12. Программа, иллюстрирующая области действия (часть 1 из 2)
212
Глава 5
++x;
static
printf("local
x
is
%d
exiting b\n",
on
x);
}
void c(void)
{
printf("\nglobal
*=
x
is
x
%d
on
entering
c\n",
x);
10;
x
printf("global
is
%d
on
exiting c\n",
x);
}
local
x
in
outer
scope
of main
is
5
local
x
in
inner
scope
of main
is
7
local
x
in
outer
scope
of main
is
5
local
x
in
a
is
25
after
local
x
in
a
is
26
before
local
static
x
is
50
on
local static
x
is
51
on exiting b
global
x
is
1
global
x
is
10
on
on
entering
exiting
entering b
entering
exiting
c
c
entering
exiting
local
x
in
a
is
25
after
local
x
in
a
is
26
before
local
static
x
is
51
on
local static
x
is
52
on
on
entering
exiting
global
global
local
x
x
is
10
x
is
100
on
main
is
in
Рис. 5.12.
a
a
a
a
entering b
exiting b
c
c
5
Программа, иллюстрирующая области действия (часть 2
из
2)
Сначала мы рассмотрим рекурсию обобщенно, а затем исследуем несколь¬
ко программ, содержащих рекурсивные функции. Рекурсивные подходы к ре¬
шению задач имеют ряд общих элементов. Для решения задачи вызывается
рекурсивная функция. Фактически функция знает решение только
шего случая
(случаев),
или так называемого основного
простей¬
случая. Если функция
вызывается для решения основного случая, то она просто возвращает резуль¬
тат.
Если функция
вызывается для решения
обобщенных
которой функция имеет
которой функция решения не имеет. Чтобы сде¬
возможной, последняя часть должна быть похожа на первона¬
ция делит задачу
способ решения, и
лать рекурсию
часть, для
чальную задачу, но она должна
вариантом.
более сложной проблемы, функ¬
части: часть, для
на две
Поскольку
быть более простым,
или редуцированным ее
эта новая задача похожа на первоначальную,
функция
чтобы продолжить решение мень¬
шей задачи. Этот процесс называется рекурсивным вызовом или шагом рекур¬
сии. Шаг рекурсии также включает ключевое слово return, поскольку ее резу¬
запускает (вызывает)
льтат
свою новую
будет объединен
с
копию,
предыдущей
частью
задачи,
для
которой функция
чтобы сформировать окончательный результат, который будет
передан первоначальной вызывающей функции, возможно main.
знала решение,
213
Функции
В то время как выполняется шаг рекурсии, первоначальное
функции
обращение
остается открытым, т.е. его выполнение не завершается.
к
Шаг рекур¬
большему числу таких рекурсивных вызовов,
которые продолжаются до тех пор, пока функция продолжает делить каждую
сии может приводить к намного
последующую задачу, для решения
ные
Чтобы рекурсия
части.
в
которой
конце
концов
обобщен¬
функция каждый
она вызывалась, на две
завершилась,
должна вызываться для решения все более простого варианта первонача¬
льной задачи, и эта последовательность все меньших и меньших задач должна
раз
в конце концов, свестись к основному случаю. В этой точке
ет
основной случай, возвращает результат предыдущей
лее следует последовательность
первоначальный
возвратов по
функции
вызов
не вернет
функция распозна¬
функции, и да¬
копии
обратному
пути до тех пор, пока
конечный результат
в
звучит совершенно экзотически по сравнению со стандартными
main. Все
это
способами ре¬
Действительно, для написа¬
требуется большая практика, прежде чем рекур¬
покажется чем-то естественным. Для иллюстрации работы этой концеп¬
мы создадим рекурсивную программу, выполняющую распространенный
шения задач, которые мы применяли до сих пор.
ния рекурсивных программ
сия
ции
вид математических вычислений.
Факториал целого неотрицательного числа n записывается
«п факториал») и является произведением
как n\
(произ¬
носится
n\
=
так что
дением
(n
n
-
1) (n
-
2)-Kl
1! равен 1, а 0! равен 1 по определению. Например, 5!
5 * 4 * 3 * 2 * 1, которое равно 120.
Факториал
целого
number, большего
итеративно (не рекурсивно)
factorial
for
или равного
с помощью цикла
0,
является произве¬
может
быть
вычислен
for:
1;
=
(counter
factorial
number;
=
*=
counter >=
1;
counter )
counter;
Рекурсивное определение функции для вычисления факториала основано
на следующем соотношении:
n!
=
(n
n
-
1)!
Например,
*
очевидно, 5! равен 5
5!
2
=
4
5
3
(4 3 2 -1)
5!
5 (4!)
Вычисление 5! могло
5!
=
5
-
-
4!,
как показано ниже:
1
-
=
Рис. 5.13а показывает,
бы
зовов до тех пор, пока не
канчивается рекурсия.
зывающей функции
будет
вычислено значение
На рис. 5.13b
приведенной
и вывода на печать
ных
long
будет
1;
Если
Возврат значений
целых чисел от 0 до 10
(выбор типа
сначала
factorial
ниже). Рекурсивная функция
факториалов
поясняется чуть
1. Если number действительно
меньше или равна
дальнейшей рекурсии необходимости
number больше 1, оператор
в
return
за¬
возвращаемые вы¬
про¬
вычислено и возвращено конечное значение.
веряет, выполняется ли условие завершения: переменная
ет
5.13.
приведенная на рис. 5.14, использует рекурсию для вычисле¬
ния
равна
на рис.
1!, равное 1, которым
показаны значения,
из каждого рекурсивного вызова.
исходит до тех пор, пока не
Программа,
выполняться по схеме,
как происходит последовательность рекурсивных вы¬
number
*
factorial(number
-
1);
number
дан¬
про¬
меньше или
1, factorial возвраща¬
нет и программа завершается.
214
Глава 5
возвращается 5!
=
5
возвращается 4!
*
24
=
4
возвращается 3!
=
*
=
120
6
3
возвращается 2!
=
*
=
24
2
=
2
*
6
1
=
2
возвращено 1
а) Процесс рекурсивных
6) Значения, возвращаемые
вызовов
после каждого
рекурсивного вызова
Рис. 5.13.
Рекурсивное
вычисление 5'
задачу как вычисление произведения number на рекурсивный
factorial, возвращающий факториал от number 1. Обратите внимание,
немного более простая задача, чем исходная
факториал от (number 1)
представляет
вызов
что
-
-
задача
factorial(number).
В объявлении функции factorial указано, что она получает параметр типа
long и возвращает результат типа long. Эта запись является сокращением от
int. Стайдарт ANSI определяет, что переменная типа long int хранится по
мере в 4-х байтах и, следовательно, может иметь значения до
+2147483647. Как можно видеть из рис. 5.14, значения факториала быстро
long
меньшей
возрастают. Мы выбрали тип данных long для того, чтобы программа могла
вычислять значения факториалов больших 7! на компьютерах с малой разряд¬
(например, 2-байтовых). Для печати значений типа long
преобразования %ld. К сожалению, функция fac¬
torial приобретает большие значения настолько быстро, что даже тип long int
не позволяет напечатать большое число факториалов без превышения размера
переменной типа long int.
Как мы еще увидим в упражнениях, пользователям, которым необходимо
вычислять факториалы больших чисел, придется использовать типы float и
double. Это указывает на слабость С (и большинства других языков програм¬
мирования), а именно на то, что язык не так легко расширить, чтобы
ностью целых чисел
используется спецификация
удовлетворить уникальные требования различных приложений. Как мы уви¬
С++ является расширяемым языком, который, если мы того жела¬
дим позже,
ем, позволяет создавать произвольно
большие целые
числа.
Распространенная ошибка программирования 5.15
Отсутствие возврата
значения
из
рекурсивной функции,
когда он
необходим
215
Функции
factorial
/* Рекурсивная функция
#include <stdio.h>
*/
factorial(long);
long
main ()
{
int
i;
for
(i
1;
=
i
printf("%2d
return
<=
10;
!=
i++)
i,
%ld\n",
factorial(i));
0;
/* Рекурсивное определение функции
long factorial(long number)
{
if (number <= 1)
return 1;
else
return
*
(number
factorial
factorial(number
-
*/
1));
}
1!
=
1
2!
=
2
3!
=
6
4!
=
24
5!
=
120
6!
=
720
7!
=
5040
8!
=
40320
9!
=
362880
10!
=
3628800
Рис. 5.14. Вычисление факториалов
рекурсивной функцией
Распространенная ошибка программирования 5.16
Пропуск
основного случая или некорректная запись шага рекурсии приводит к тому,
бесконечную рекурсию и
Аналогией этому является
проблема бесконечного цикла в итеративном (нерекурсивном) решении Бесконечная
рекурсия может быть также вызвана неправильным вводом значения
что рекурсия не сходится к основному случаю
приводит,
5.14.
в
конечном счете,
Это вызывает
к переполнению памяти
Пример применения рекурсии:
числа
Фибоначчи
Числа Фибоначчи
0,1/1,2,3,5,8,13,21,
начинаются с
0
и
1
.
.
.
и имеют то
свойство,
что каждое последующее число
Фибо¬
наччи равно сумме двух предыдущих.
Числа Фибоначчи встречаются
му спирали. Отношение
в
природе и,
в частности,
описывают
фор¬
последовательных чисел Фибоначчи сходится к значе¬
нию константы 1.618.... Это число также неоднократно встречается в природе
216
Глава 5
средним. Люди стремятся най¬
Архитекторы часто разрабатывают
и называется золотым сечением или золотым
эстетический аспект.
ти в золотом сечении
окна, комнаты и здания, у которых отношение длины и ширины соответствует
золотому сечению. Почтовые открытки также делаются с отношением сторон,
близким к значению золотого сечения.
Числа Фибоначчи могут быть выражены при помощи рекурсии следую¬
образом:
fibonacci(0)
щим
=
0
fibonacci(1)
=
1
fibonacci(n)
=
fibonacci(n
-
1)
+
fibonacci(n
-
2)
Программа, приведенная на рис. 5.15, вычисляет i-oe число Фибоначчи,
вызывая рекурсивную функцию fibonacci. Обратите внимание, что числа Фи¬
боначчи имеют тенденцию
значения
щаемого
быстрого
роста.
функции fibonacci
мы
Поэтому для параметра и возвра¬
выбрали тип данных long. На
рис. 5.15 каждая пара строк вывода показывает результат независимого запу¬
ска программы.
Вызов fibonacci из main не является рекурсивным вызовом, но все после¬
дующие вызовы fibonacci выполняются рекурсивно. Каждый раз когда вызы¬
вается fibonacci, следует немедленная проверка на основной случай, т.е. на ра¬
венство n
0
или
1. Если
но, что если n больше
каждый
ный
бы
это условие выполнено, то следует возврат n.
1,
Интерес¬
шаг рекурсии генерирует два рекурсивных вызова,
более простую задачу, чем первоначаль¬
fibonacci. На рис. 5.16 показано, как функция fibonacci вычислила
из которых решает немного
вызов
значение
сунок более
fibonacci(3) (мы
ясным).
сократили имя fibonacci до
f, чтобы
сделать ри¬
Этот
рисунок поднимает некоторые интересные вопросы, касающиеся по¬
рядка, в котором компиляторы С вычисляют значения операндов в операци¬
ях. Он не имеет отношения к тому порядку, в котором операции применяются
к их операндам, т.е. от порядка, диктуемого правилами приоритета операций.
Из рис. 5.16 видно, что при вычислении значения f(3) будет сделано два ре¬
курсивных вызова: f(2) и f(l). Но в каком порядке будут сделаны эти вызовы?
Большинство программистов просто полагает, что значения операндов
вычисляться слева направо.
Странно,
но стандарт
ANSI
будут
не определяет поря¬
большинства опера¬
ций (включая +). Следовательно, программист не может делать никаких пред¬
положений относительно порядка, в котором будут выполняться эти вызовы.
док, в котором должны вычисляться значения операндов
Действительно,
могли
бы
сначала мог
в
выполняться
бы следовать
вызов
обратном порядке
f(2), а затем f(l), или вызовы
f(l), а затем f(2). В этой про¬
грамме и в большинстве других программ окажется, что конечный результат
для обоих случаев был бы тем же самым. Но в некоторых программах вычис¬
ление значения операнда может иметь побочные эффекты, которые могли бы
воздействовать на конечный результат выражения. Из большого числа опера¬
ций С стандарт ANSI определяет порядок вычисления операндов только в че¬
тырех операциях: &&, |
них
это двухместные
|, операции-запятой (,)
дут вычисляться слева направо.
трехместная операция.
вую очередь; если
и
операции ?:.
Последняя операция
крайний левый
если
три из
бу¬
единственная
в
пер¬
отличающийся от
последний операнд игно¬
операнд дает результат,
крайний левый операнд дает
средний игнорируется.
третьего операнда, а
это
Ее крайний левый операнд всегда вычисляется
нуля, то вычисляется значение среднего операнда, а
рируется;
Первые
операции, два операнда которых гарантированно
нуль, то вычисляется значение
217
Функции
/* Рекурсивная функция
#include <stdio.h>
long
fibonacci
*/
fibonacci(long);
main ()
{
long
number;
result,
an
integer: ");
scanf("%ld", &number);
result
fibonacci(number);
%ld\n",
printf("Fibonacci(%ld)
printf("Enter
=
=
return
/*
number,
result);
0;
Рекурсивное
определение
функции
fibonacci
*/
long fibonacci(long n)
{
if (n
0 || n
1)
==
==
return n;
else
return fibonacci(n
-
1)
+
fibonacci(n
-
2);
}
an integer:
0
Fibonacci(0)
0
Enter an
integer:
Fibonacci(l) = 1
1
Enter an
integer:
Fibonacci(2) = 1
2
Enter an
3
Enter
=
integer:
Fibonacci(3) = 2
Enter
an
integer:
=
Fibonacci(4)
4
3
Enter an
integer:
Fibonacci(5) = 5
5
an integer:
Fibonacci(6) = 8
6
Enter
Enter an
integer: 10
=
55
Fibonacci(10)
Enter
an
integer: 20
=
6765
Fibonacci(20)
integer: 30
Fibonacci(30) = 832040
Enter an
Enter an
integer: 35
Fibonacci(35) = 9227465
Рис. 5.15. Рекурсивная генерация чисел Фибоначчи
218
Глава 5
Рис. 5.16. Последовательность рекурсивных вызовов функции fibonacci
Распространенная ошибка программирования 5.17
Создание программы, которая зависит от порядка вычисления значений операндов
операций,
не являющихся
&&, 11, ?:
кам, поскольку компиляторы могут
который
и
операцией-запятой (,),
может привести к
ошиб¬
вычислять значения операндов не в том порядке,
ожидал программист.
Советы по переносимости программ 5.2
Программы, которые
не являющихся
му
на
системах
Следует
с
и
различными компиляторами
сказать
сивных программ,
операций,
операцией-запятой (,), могут функционировать по-разно-
зависят от порядка вычисления значений операндов
&&, | |, ?:
несколько
предостерегающих
подобных той,
слов
относительно
рекур¬
что мы написали здесь для генерации чисел
Фибоначчи. Каждый уровень рекурсии в функции fibonacci удваивает число
вызовов, т.е. число рекурсивных вызовов, которые будут выполнены для вы¬
числения числа Фибоначчи с номером n, растет как 2П. Этот процесс быстро
выходит из-под контроля. Вычисление только 20-го числа Фибоначчи потребо¬
вало бы порядка 220, или около миллиона вызовов; вычисление 30-го числа
Фибоначчи требует порядка 230, или около миллиарда вызовов, и так далее.
Ученые, работающие с компьютерами,
ненциальной сложностью. Эти задачи
называют эти задачи задачами с экспо¬
сложны даже для наиболее производи¬
настоящий момент! Проблемы сложно¬
экспоненциальной сложности в частности, подробно об¬
тельных компьютеров, имеющихся на
сти задачи
вообще,
и
суждаются в курсе по компьютерным дисциплинам,
который
называется
«Ал¬
горитмы ».
Совет по повышению эффективности 5.4
Избегайте рекурсивных программ, подобных вычислению чисел Фибоначчи, которые
приводят к экспоненциальному «взрыву» рекурсивных вызовов
219
Функции
5.15.
гут
Рекурсия
в
сравнении
итерацией
с
В предыдущих разделах мы исследовали две функции, которые легко мо¬
быть реализованы посредством как рекурсии, так и итерации. В этом раз¬
деле мы сравним эти два подхода и обсудим, почему программист может пред¬
почесть один подход другому в конкретном приложении.
И итерация и рекурсия основаны на управляющих структурах: итерация
использует структуру повторения; рекурсия использует структуру выбора. И
и рекурсия подразумевают повторение: итерация использует струк¬
посредством повторных вызовов
туру повторения явным образом; рекурсия
функции. Итерация и рекурсия включают проверку на завершение: итерация
завершается, когда перестает выполняться условие продолжения цикла; ре¬
итерация
курсия завершается, когда распознается основной случай. Как итерация, с ее
проверкой повторения по состоянию счетчика, так и рекурсия приближаются
к завершению постепенно: итерация продолжает изменять счетчик, пока счет¬
чик не примет значение, которое перестает удовлетворять условию продолже¬
ния цикла; рекурсия продолжает производить более простые варианты перво¬
начальной задачи, пока не будет достигнут основной случай. И итерация и ре¬
курсия может происходить бесконечно:
цикл,
курсия продолжается
ким
образом,
Рекурсия
рует
итерация попадает
в
если условие продолжения цикла никогда не становится
бесконечно,
бесконечный
ложным;
ре¬
если шаг рекурсии не редуцирует задачу та¬
что задача сходится к основному случаю.
Она многократно иниции¬
имеет много отрицательных сторон.
механизм
вызова
функций
и, соответственно, увеличивает связанные с
ним накладные расходы. Это может дорого стоить как в плане процессорного
времени, так и в плане расхода памяти.
Каждое рекурсивное обращение созда¬
функции (фактически только копию ее переменных), и это
ет другую копию
может расходовать значительные ресурсы памяти. Итерация обычно происхо¬
дит в пределах функции, так что здесь нет накладных расходов на повторные
вызовы функций и дополнительное выделение памяти. Так по какой причине
для решения задачи
Общее
можно
выбрать рекурсию?
методическое замечание 5.12
проблема, которая может быть решена рекурсивно, может также быть решена
итеративно (не рекурсивно). Рекурсивный подход обычно предпочитается итератив¬
ному в тех случаях, когда рекурсия более естественно отражает математическую сто¬
Любая
рону задачи и
решение
Совет
которая проще для понимания и отладки Дру¬
рекурсивного решения является то, что итеративное
быть очевидным
приводит
гой причиной
может
для
не
к программе,
выбора
по повышению
эффективности
5.5
Избегайте использовать рекурсию в ситуациях,
граммы
мять
Рекурсивные
обращения требуют
требующих
времени
и
производительности про¬
расходуют дополнительную па¬
Распространенная ошибка программирования 5.18
Случайный вызов нерекурсивной функцией самой себя или непосредственно,
косвенно через другую функцию.
или
220
Глава 5
Большинство
учебников
по
программированию
Мы считаем, что
сложный предмет, который лучше вводить раньше
намного позже, чем сделано здесь.
представляет
рекурсию
это
рекурсия
ценный
и
и
рассматривать примеры
рекурсии по всему остальному тексту. На рис. 5.17 перечислен 31 пример ре¬
курсии, представленный в различных главах книги.
Позвольте нам закончить эту главу некоторыми замечаниями, которые
неоднократно делаем во всей книге. Систематизация разработки програм¬
много обеспечения важна. Высокая производительность программ также важ¬
на. К сожалению, эти цели часто противоречат друг другу. Систематическое
мы
конструирование программного обеспечения является ключом к более эффек¬
тивному управлению процессом создания еще больших и более сложных сис¬
тем.
Высокая производительность
дущего, которые
ным средствам.
является ключом к реализации систем
еще большие
когда-нибудь предъявят
Какое
же отношение все это имеет к
Глава
Примеры и упражнения по рекурсии
Гпава 5
Функция factorial
Функция fibonacci
Наибольший общий делитель
Сумма двух целых
Произведение двух целых
Возведение целого в целую степень
требования
функциям?
бу¬
к аппарат¬
Ханойская башня
Рекурсия main
Печать ввода с клавиатуры
Визуализация рекурсии
Гпава 6
в
обратном порядке
Сумма элементов массива
Печать массива
Печать массива в
Печать строки в
Проверка,
обратном порядке
обратном порядке
является ли строка
Минимальное значение в
палиндромом
массиве
Сортировка выбором
Быстрая сортировка
Линейный поиск
Двоичный поиск
Глава 7
Восемь ферзей
Обход лабиринта
Гпава 8
Печать строки, введенной
Глава 12
Вставка в связанный список
Удаление из связанного списка
Поиск в связанном списке
Печать связанного списка в обратном порядке
Вставка в двоичное дерево
Обход двоичного дерева с предварительной выборкой
Обход двоичного дерева с порядковой выборкой
Обход двтжчного дерева с отложенной выборкой
Рис. 5.17. Список примеров
Общее
испражнений
с клавиатуры,
в
обратном
порядке
по рекурсии, приведенных в тексте
методическое замечание 5.13
Разбиение программы на функции аккуратным, иерархическим образом способствует
систематизации конструирования программного обеспечения. Но за это приходится
платить.
221
Функции
Совет по повышению
эффективности 5.6
Программа, интенсивно использующая функции, по сравнению с монолитной (те
состоящей из одной части) программой без функций имеет потенциально большое
число вызовов, а это увеличивает процессорное время программы. Но монолитные
программы трудны для написания, тестирования, отладки, сопровождения и разви¬
тия
Таким образом, создание вашей программы
разумным
решением,
производительностью
программного
но
и
функций
из многих
является
всегда нужно учитывать шаткое равновесие
стремлением
к
систематическому
между
конструированию
обеспечения.
Резюме
Наилучшим способом разработки
и сопровождения больших программ
является создание программы из небольших программных модулей,
каждый из которых проще в обращении, чем исходная программа. В С
модули реализуются
как
функции.
функции осуществляется посредством вызова. В вызове
функции указывается ее имя и передается информация (в виде аргу¬
ментов), необходимая функции для выполнения своей задачи.
Обращение
к
сокрытия информации является стремление к тому, чтобы фун¬
кции имели доступ только к той информации, которая им необходима
для выполнения своих задач. Смысл этого состоит в применении прин¬
Целью
ципа минимизации
привилегий
разработки
пов систематической
Функции обычно
одного из наиболее важных принци¬
программного обеспечения.
вызываются в программе посредством записи имени
функции
с
ции (или
списком
последующими
левой круглой скобкой, аргументом функ¬
аргументов,
отделяемых
запятыми)
и
правой
круг¬
лой скобкой.
типа double являются числами с плавающей точкой, аналогич¬
float. Переменные типа double могут хранить значения намного
большей величины и точности, чем float.
Данные
ными
Любой аргумент функции
может
быть константой, переменной
или вы¬
ражением.
Локальная переменная
на.
Другим функциям
ных
известна только
данной функции, равно
подробности
как и
реализации других
Общий формат
той функции, где
она определе¬
не разрешается знать имена локальных перемен¬
любой функции
не разрешено знать
функций.
определения функции имеет
следующий
вид:
тип_возвращаемого_значения имя_функции(список_параметров)
{
объявления
операторы
}
Тип_возвращаемого_значения
объявляет
вызывающей функции. Если функция
тип значения, возвращаемого
не
тип_возвращаемого_значения объявляется
возвращает
как
значения,
void. Имя_функции
то
222
Глава 5
это любой
допустимый идентификатор. Список_параметров
это спи¬
объявлений параметров (разделенных запятыми), получаемых фун¬
кцией при ее вызове. Если функция не получает значений, список па¬
раметров объявляется как void. Тело функции
набор объявлений и
сок
операторов, которые составляют собственно функцию.
переданные функции, должны соответствовать по числу,
Аргументы,
типу
и
порядку
следования
параметрам,
указанным
в
определении
функции.
Когда программа встречает
зова передается
ваемой
имя
функции,
вызываемой функции,
функции
и
управление
то управление из точки вы¬
выполняются операторы
возвращается
вызы¬
вызвавшей
структуре,
функцию.
Вызываемая функция может вернуть управление вызывающему опера¬
одним из трех способов. Если функция не возвращает значения,
тору
управление возвращается,
завершающая
тело
когда достигается
функции,
или
правая
фигурная скобка,
посредством оператора
return;
Если функция возвращает значение, оператор
return expression;
возвращает значение выражения
Прототип функции объявляет
expression.
тип,
возвращаемый функцией,
а также
число, типы и порядок следования параметров, которые она должна по¬
лучить.
дают возможность компилятору проверить,
Прототипы
вызвана
что
функция
правильно.
Компилятор
игнорирует имена переменных,
в
указанных
прототипе
функции.
Каждая стандартная библиотека
файл, содержащий
также
имеет
соответствующий заголовочный
функций этой библиотеки, а
прототипы для всех
содержит определения различных символических констант,
обходимых
этим
не¬
функциям.
могут создавать и включать в программу свои собствен¬
ные заголовочные файлы.
Программисты
Когда аргумент передается
ется и копия передается
зываемой функции
не
по значению,
значение
переменной копиру¬
вызываемой функции. Изменения
отражаются
на
копии в вы¬
первоначальной
значении
пере¬
менной.
Все
вызовы
в
С передают аргументы
Функция rand
генерирует
целое
по
значению.
значение
в
диапазоне
от
0
до
RAND__MAX. Стандарт ANSI устанавливает, что значение
RAND_MAX должно быть равно по крайней мере 32767.
Прототипы функций rand и srand содержатся в <stdlib.h>.
Значения, генерируемые rand, могут масштабироваться
достижения необходимого диапазона значений.
для
и
сдвигаться
223
Функции
Для
рандомизации программы
используйте функцию srand
из стандар¬
тной библиотеки С.
Оператор
функцией srand обычно
с
вставляется в программу только по¬
При отладке лучше всего
Это гарантирует повторяемость результатов, которая
существенна для доказательства того, что исправления, внесенные в
программу с генератором случайных чисел работают правильно.
сле того, как программа полностью отлажена.
опустить srand.
Чтобы рандомизировать программу, не вводя каждый раз новые значе¬
ния семени, можно использовать srand (time (NULL)). Функция time
возвращает
time
Прототип функции
число секунд, прошедшее с начала дня.
находится в заголовочном
Общей формулой
для
файле <time.h>.
масштабирования
и
сдвига
случайных
чисел яв¬
ляется
n
=
+
а
где а
rand()
% b;
значение смещения
(которое
равно первому числу задаваемого
диапазона последовательных целых чисел),
табирования (который равен
тельных целых чисел).
Каждый идентификатор
период
хранения,
класса
Период
программе
область действия
С поддерживает четыре
ми
в
памяти:
и
b
коэффициент
масш¬
ширине задаваемого диапазона последова¬
и
имеет
тип
класса памяти,
атрибуты
памяти:
класса
компоновки.
обозначаемые спецификатора¬
и static.
auto, register, extern
это
хранения идентификатора
данный идентификатор существует
в
время,
в
течение
которого
памяти.
Область действия идентификатора характеризует
ния к нему из различных частей программы.
возможность
обраще¬
Тип компоновки идентификатора определяется для программ, состоя¬
щих
из нескольких исходных
известен ли
идентификатор
любом исходном файле
с
файлов. Эта характеристика
только
в
текущем
соответствующими
исходном
показывает,
файле
или
в
объявлениями.
Переменные с автоматическим периодом хранения создаются при запу¬
ске блока, в котором они объявлены, существуют, пока блок активен, и
разрушаются, когда блок завершает работу. Локальные переменные
функции обычно имеют автоматический период хранения.
класса памяти register может быть помещен перед объ¬
автоматической локальной переменной, чтобы рекомендо¬
Спецификатор
явлением
игнорировать
из быстродейству¬
Компилятор может
объявление register. Ключевое слово register может ис¬
пользоваться
только
вать компилятору разместить
ющих
риод
аппаратных
переменную
регистров
с
в
одном
процессора.
переменными,
имеющими
автоматический
пе¬
хранения.
Ключевые
слова extern и
фикаторов переменных
Переменные
ализируются
и
static используются при объявлении иденти¬
функций
со статическим периодом хранения.
со статическим периодом хранения размещаются и иници¬
один
раз,
когда
программа
начинает
выполняться.
224
Глава 5
Существует
ния:
два типа
идентификаторов со статическим периодом хране¬
идентификаторы (такие как глобальные переменные и
функций) и локальные переменные, объявляемые со специфика¬
внешние
имена
тором класса памяти static.
Глобальные переменные создаются при объявлении переменных вне
любого определения функции, и они сохраняют свои значения в тече¬
ние
всего
времени
выполнения
программы.
Локальные переменные, объявленные
чения и после того, как
ет
функция,
в
static, сохраняют свои зна¬
которой они объявляются, заверша¬
как
работу.
Все
числовые переменные со статическим периодом хранения инициа¬
обнулением, если
образом.
Четырьмя областями действия
они
лизируются
стом
не
инициализированы
программи¬
явным
для
идентификаторов
являются:
область
действия функции, область действия файла, область действия блока
область действия прототипа функции.
Метки
это единственные
кции. На
метки
они
появляются,
идентификаторы
можно ссылаться в
но
вне
тела
любом
функции
на
с
и
областью действия фун¬
месте
них
функции,
ссылаться
в
которой
нельзя.
Идентификатор, объявленный вне любой функции, имеет область дей¬
ствия файла. Такой идентификатор «известен» во всех функциях начи¬
ная с места, в котором идентификатор был объявлен, и до конца файла.
Идентификаторы, объявленные внутри блока, имеют область действия
блока. Область действия блока заканчивается правой фигурной скоб¬
кой (}), завершающей блок.
в начале функции, имеют об¬
действия блока, так же как и параметры функции, которые рас¬
сматриваются в качестве ее локальных переменных.
Локальные переменные, объявленные
ласть
Любой блок
блоке
содержать
объявления переменных. Когда блоки
блоке имеет то же самое имя,
идентификатор
идентификатор во внутреннем блоке, идентификатор
вложены,
что и
может
во внешнем
а
оказывается
«скрытым»,
пока
внутренний блок
во внешнем
не завершит ра¬
боту.
Единственными идентификаторами с областью действия прототипа
функции являются идентификаторы, которые встречаются в списке па¬
раметров прототипа. Идентификаторы, указанные в прототипе функ¬
ции, могут неоднократно использоваться в других местах программы,
при этом не возникает никакой неоднозначности.
Рекурсивная функция
непосредственно,
Если функция
или
это
функция, которая
вызывает саму
себя
или
косвенно.
вызывается для решения основного случая,
то она про¬
Если функция вызывается для решения бо¬
лее сложной задачи, функция делит задачу на две обобщенных части:
часть, для которой функция имеет способ решения, и часть, для кото¬
сто возвращает результат.
рой функция решения не имеет. Поскольку
исходную, функция запускает (вызывает)
эта новая задача похожа на
свою
продолжить решение редуцированной задачи.
новую
копию,
чтобы
225
Функции
Чтобы рекурсия завершилась, функция каждый раз должна вызывать¬
ся для решения немного более простого варианта исходной задачи, и
эта последовательность все меньших и меньших задач должна, в конеч¬
ном
свестись
счете,
основной случай,
к
основному
Когда функция распознает
предыдущей копии функ¬
случаю.
она возвращает результат
обратному
ции и далее следует последовательность возвратов по
до тех пор, пока
первоначальный
вызов
функции
не
вернет
пути
конечный
результат.
Стандарт ANSI
не определяет порядок, в котором должны вычисляться
значения операндов
числа
операций С
рандов только
рации
в
большинства операций (включая +). Из большого
стандарт ANSI определяет порядок вычисления опе¬
из них
которых гарантированно
операция
| |, операции-запятой (,)
четырех операциях: &&,
?:. Первые три
будут
операнд всегда вычисляется в первую очередь;
ние среднего операнда,
Последняя
крайний левый
крайний левый
вычисляться слева направо.
единственная трехместная операция.
операнд дает результат,
и опе¬
это двуместные операции, два операнда
Ее
если
отличающийся от нуля, то вычисляется значе¬
а последний операнд игнорируется; если край¬
ний левый операнд дает нуль, то вычисляется
ранда, а средний операнд игнорируется.
значение третьего опе¬
И итерация и рекурсия основаны на управляющих структурах: итера¬
ция использует структуру повторения; рекурсия использует структуру
выбора.
И итерация
и рекурсия подразумевают повторение: итерация использу¬
ет структуру повторения явным
образом;
посредством по¬
рекурсия
вторных вызовов функции.
и рекурсия включают проверку на завершение: итерация за¬
вершается, когда перестает выполняться условие продолжения цикла;
рекурсия завершается, когда распознается основной случай.
Итерация
И итерация и рекурсия может происходить бесконечно: итерация попа¬
дает в бесконечный цикл, если условие продолжения цикла никогда не
становится ложным;
рекурсия продолжается
курсии не редуцирует задачу таким
ному
образом,
бесконечно,
если шаг ре¬
что она сходится к основ¬
случаю.
Рекурсия
многократно инициирует механизм вызова
функций
и, соот¬
ветственно, увеличивает связанные с ним накладные расходы. Это мо¬
жет дорого стоить как в плане процессорного времени, так и в плане
расхода памяти.
Терминология
clock
автоматическая
rand
автоматический
RAND_MAX
автоматическое
return
класс
аргументов
функции
вызове функции
активация
time
аргумент
блок
в
вызов
по
значению
вызов
по
ссылке
абстракция
Зак 801
памяти
приведение
srand
unsigned
void
8
переменная
226
Глава 5
функции
функция
вызывающая функция
выражение со смешанными типами
генерация случайных чисел
повторное
вызов
вызываемая
глобальная
переменная
файлы стандартной
библиотеки
заголовочный файл
иерархия возведения
минимума
рекурсия
итерация
сдвиг
классы памяти
сокрытие
информации
спецификатор класса памяти
спецификатор класса памяти
спецификатор класса памяти
конструирование программного
обеспечения
копия значения
локальная переменная
auto
extern
масштабирование
моделирование
модульная программа
область действия
область действия блока
область действия прототипа
область действия файла
область действия функции
объявление функции
определение функции
оптимизирующий компилятор
основной случай рекурсии
параметр в определении функции
переменная класса static
спецификатор класса памяти
register
спецификатор класса памяти static
спецификатор преобразования %s
стандартная библиотека С
статический период хранения
тип
возвращаемого
тип
компоновки
значения
функции математической
библиотеки
функция
функция факториала
функция, определяемая
программистом
элемент случайности
хранения
побочные
кода
привилегий
прототип функции
псевдослучайные числа
«разделяй и властвуй»
рандомизация
рекурсивная функция
рекурсивный вызов
принцип
заголовочные
период
использование
программного
эффекты
Распространенные ошибки программирования
5.1.
При отсутствии
5.2.
Если
в программе заголовочного файла математики обра¬
щение программы к функциям математической библиотеки может
приводить к странным результатам.
тип_возвращаемого_значения в определении функции опу¬
щен, то это вызовет синтаксическую ошибку в том случае, если про¬
тотип функции определяет, что возвращаемый тип не является це¬
лым
(int).
5.3. Если функция
должна возвращать некоторое значение, а в
опущен оператор возврата,
ным
то
это
может
ошибкам. Стандарт ANSI гласит,
привести
к
функции
непредвиден¬
что в этом случае результат не
определен.
5.4.
Возврат значения из функции, для которой возвращаемый
объявлен как void, вызывает синтаксическую ошибку.
5.5.
Запись float x, у вместо float x, float у при объявлении параметров
функции, относящихся к одному типу. Такое объявление парамет¬
ров, как float x, у фактически присвоило бы параметру у тип int, по¬
скольку
int принимается
по
умолчанию.
тип
был
227
Функции
5.6.
Символ точки с запятой после
щей
правой круглой скобки, закрываю¬
функции, является синтакси¬
список параметров в определении
ческой ошибкой.
5.7.
Повторное определение параметра функции как локальной
ной внутри функции
синтаксическая ошибка.
5.8.
Определение функции внутри другой функции
ошибка.
5.9.
Пропуск
точки с
запятой
в конце
перемен¬
синтаксическая
прототипа функции вызывает син¬
таксическую ошибку.
5.10.
Преобразование более
к
высокого типа данных в иерархии возведения
более низкому типу
может
изменить
значение
данных.
прототипа функции вызывает синтаксическую ошибку, если
функция возвращает тип, отличный от int, а определение функции
появляется в программе после вызова функции. В других случаях
пропуск прототипа функции может вызвать ошибку во время выпол¬
5.11.Пропуск
нения программы
или
привести к непредвиденному результату.
5.12. Использование srand вместо rand для генерации случайных чисел.
спецификаторов класса памяти для
К
данному идентификатору может применяться
идентификатора.
только один спецификатор класса памяти.
5.13.Использование
5.14.
нескольких
Случайное объявление
во внутреннем
блоке
имени
идентификатора,
время как програм¬
мист на самом деле хотел, чтобы идентификатор внешнего блока был
доступен для внутреннего блока.
которое уже используется
5.15.
Отсутствие
во внешнем
возврата значения из
блоке,
в то
рекурсивной функции,
когда он не¬
обходим.
5.16.Пропуск
основного случая или некорректная запись шага рекурсии
приводит к тому, что рекурсия не сходится на основном случае.
вызывает
Это
бесконечную
рекурсию и приводит, в конечном счете, к
памяти.
Аналогией этому является проблема беско¬
переполнению
нечного цикла в итеративном (не рекурсивном) решении. Бесконеч¬
ная рекурсия может быть также вызвана неправильным вводом зна¬
чения.
5.17.Создание программы, которая зависит от порядка вычисления зна¬
чений операндов
цией-запятой
(,),
операций,
не
?: и опера¬
&&,
ошибкам, поскольку компилято¬
являющихся
может привести к
,
ры могут вычислять значения операндов не в том порядке,
который
ожидал программист.
5.18.Случайный
вызов
средственно,
Хороший
5.1.
стиль
Освойтесь
ANSI С.
8*
или
нерекурсивной функцией самой себя
косвенно
или
непо¬
через другую функцию.
программирования
с
широким
набором функций стандартной библиотеки
Глава 5
228
5.2.
При
использовании
функций математической библиотеки
заголовочный файл математики
препроцессора #include <math.h>.
в программу
включите
с помощью директивы
5.3.
Вставляйте пустую строку между определениями функций, чтобы
отделить функции и улучшить тем самым читаемость программы.
5.4.
Хотя опущенный возвращаемый тип по умолчанию рассматривается
как int, всегда задавайте возвращаемый тип явным образом. Однако
для
5.5.
функции main возвращаемый
Включайте
тип
каждого
тип
параметра
в
обычно опускается.
список
параметров
даже в том случае, если данный параметр относится к типу
нятому
5.6.
Хотя
по
функции
int, при¬
умолчанию.
ошибкой, не используйте одни и те же имена
функции, и соответствующих парамет¬
функции. Это поможет избежать неоднозначно¬
это и не является
для аргументов, переданных
ров
в
определении
сти.
5.7.
Выбор
вания
5.8.
функций
осмысленных имен
более удобочитаемыми
и
помогает
и параметров делает программы
избежать чрезмерного использо¬
комментариев.
Включите в программу прототипы функций для всех функций, что¬
бы воспользоваться преимуществами проверки типов С. Используй¬
те
препроцессора #include, чтобы получить прототипы
стандартной библиотеки из заголовочных файлов соответ¬
директивы
функций
ствующих
библиотек. Также используйте #include, чтобы включить
файлы, содержащие ваши собственные прототипы
заголовочные
функций
или
прототипы,
используемые
членами
вашей
рабочей
группы.
5.9.
Имена параметров иногда включаются в прототип функции с целью
Компилятор игнорирует эти имена.
документирования.
5.10.
использующиеся только в определенной функции, дол¬
жны быть объявлены локальными переменными этой функции, а не
внешними переменными.
Переменные,
5.11. Не объявляйте имен переменных, которые скрывают имена во внеш¬
них областях действия. Можно просто избегать любого дублирова¬
ния
имен
идентификаторов
в
программе.
Советы по переносимости программ
5.1.
Использование
функций стандартной библиотеки ANSI С
делает
программы более переносимыми.
5.2.
Программы,
которые зависят от порядка вычисления значений опе¬
рандов операций, не являющихся &&,
(,),
,
?:
и
операцией-запятой
могут функционировать по-разному на системах с различными
компиляторами.
229
Функции
Советы по повышению
5.1.
эффективности
Автоматическое хранение способствует
экономии памяти, поскольку
автоматические переменные существуют только тогда, когда они не¬
обходимы. Они
явлены,
5.2.
и
функции,
создаются при запуске
уничтожаются,
когда
в
которой
они
объ¬
происходит выход из функции.
Перед объявлением автоматической переменной может быть поме¬
щен спецификатор класса памяти register, чтобы рекомендовать
компилятору разместить ее в одном из быстродействующих аппа¬
Если
ратных регистров компьютера.
интенсивно используемые пе¬
реализованы в аппарат¬
ных регистрах, то можно исключить непроизводительные затраты
на неоднократную загрузку переменных из памяти в регистры и об¬
ременные типа счетчиков или сумм
будут
ратно.
5.3.
Объявления
зирующие
переменные
требуя
5.4.
от
register
часто
компиляторы
и
могут
бывают
размещать
программиста
боначчи, которые приводят
5.5.
их
в
часто
регистрах
оптими¬
используемые
самостоятельно,
не
объявления register.
Избегайте рекурсивных программ, подобных
сивных
Современные
не нужны.
способны распознать
к
вычислению чисел Фи¬
экспоненциальному
«взрыву»
рекур¬
вызовов.
Избегайте
требующих произво¬
Рекурсивные обращения требуют времени
использовать рекурсию в ситуациях,
дительности программы.
и расходуют дополнительную память.
5.6.
Программа,
интенсивно
использующая
функции,
по
сравнению
с
монолитной (т.е. состоящей из одной части) программой без функ¬
ций имеет потенциально большое число вызовов, а это увеличивает
процессорное время программы.
Но
монолитные программы трудны
для написания, тестирования, отладки, сопровождения и развития.
Общие
5.1.
методические замечания
изобретайте колесо. Если есть возможность, используйте функ¬
ции стандартной библиотеки ANSI С вместо того, чтобы создавать
Не
новые
5.2.
функции. Это уменьшает время разработки программы.
В программах, содержащих большое
быть реализована как группа
щим
основную
часть
5.3. Каждая функция
но определенной
число
обращений
к
функций, main
функциям,
должна
выполняю¬
работы.
должна ограничиваться выполнением одной, точ¬
а имя функции должно отражать смысл
данной задачи. Это облегчает абстракцию и поддерживает способст¬
задачи,
вует многократному
5.4.
Если
использованию
программного
кода.
вы не можете выбрать краткое имя, которое выражает назначе¬
ние функции, возможно, что ваша функция пытается выполнить
слишком много задач. Лучше разбить такую функцию на несколько
меньших функций.
230
Глава 5
5.5.
Длина функции
не должна превышать одной страницы. Еще лучше,
чтобы функция не превышала по длине половины страницы. Небо¬
льшие функции способствуют повторному использованию програм¬
много
5.6.
Программы должны составляться в виде совокупности небольших
функций. Это упрощает создание программ, их отладку, поддержку
и
5.7.
кода.
модификацию.
Функция, требующая большого
количества параметров,
полнять
Рассмотрите
слишком
функции
много
на меньшие
задач.
функции, которые
Заголовок функции должен,
дачи.
по
может вы¬
возможность
деления
выполняют выделенные за¬
возможности,
умещаться
на
одной строке.
5.8.
Прототип функции,
заголовок
функции
и
вызов
функции
должны
иметь взаимное соответствие по числу, типу и порядку следования
аргументов и параметров, а также по типу возвращаемого значения.
5.9.
Прототип функции, размещенный
типа
вне
любого определения функции,
файле после прото¬
функции. Прототип функции, помещенный в функцию, приме¬
применяется
ко всем вызовам,
появляющимся в
няется только к тем вызовам, которые делаются из
5.10 Автоматическая хранение
минимальных
в памяти и
этой функций.
является примером реализации принципа
привилегий. Почему
быть доступными, когда
переменные должны храниться
в
данный
момент в них нет не¬
обходимости?
5.11.0бъявление переменной как глобальной, а не локальной, может
приводить к случайным побочным эффектам, когда функция, кото¬
рой не нужен доступ к этой переменной, случайно или преднамерен¬
но ее изменяет. В целом нужно избегать использования глобальных
переменных, за исключением некоторых ситуаций с уникальными
требованиями
к производительности
(рассматривается
в главе
14).
5.12.Любая проблема,
которая может быть решена рекурсивно, может
также быть решена итеративно (не рекурсивно). Рекурсивный под¬
ход обычно предпочитается итеративному в тех случаях, когда ре¬
курсия более естественно отражает математическую сторону задачи
и приводит к программе, которая проще для понимания и отладки.
для выбора рекурсивного решения является то,
что итеративное решение может не быть очевидным.
Другой причиной
5.13.Разбиение программы на функции аккуратным, иерархическим об¬
способствует систематизации конструирования программного
обеспечения. Но за все приходится платить.
разом
Упражнения
5.1.
для самоконтроля
Ответьте на следующие вопросы:
a) Модуль программы
b) Обращение
к
c) Переменная,
она
на
С
называется
.
функция осуществляется посредством
которая известна только внутри функции,
определена,
называется
.
.
в
которой
231
Функции
d) Оператор
передачи
вызываемой функции используется для
в
значения
жит
никаких
f)
вызывающей функции.
выражения
e) Ключевое слово
чтобы показать, что функция
используется
в
заголовке
функции,
не возвращает значения или не содер¬
параметров.
идентификатора является частью
может быть использован.
программы, в кото¬
рой идентификатор
g) Тремя способами
возврата управления от вызываемой функции к
и
вызывающей являются
.
,
h)
позволяет компилятору проверять число, типы и по¬
рядок
следования
аргументов,
i) Функция
переданных
используется
функции.
для генерации
случайных
чи¬
сел.
устанавливает семя генератора случайных
чисел для рандомизации программы.
j) Функция
k) Спецификаторами
памяти
класса
и
,
1) Предполагается,
являются
что переменные,
объявленные
тору
разместить
класса памяти
переменную
в
в
блоке
или в спис¬
класс памяти, если не
параметров функции, имеют
указано иначе.
ке
m) Спецификатор
,
.
рекомендует компиля-
одном
из
регистров
компьютера.
n) Переменная, объявленная вне любого блока или функции, явля¬
ется
переменной.
о) Чтобы локальная переменная в функции сохраняла свое значение
функции,
между вызовами
катором
класса
p) Четырьмя
она должна
памяти
возможными
ляются
со
специфи¬
областями действия идентификатора
,
q) Функция, которая
быть объявлена
.
и
,
вызывает саму
косвенно является
себя
или
яв¬
.
непосредственно или
функцией.
r) Рекурсивная функция обычно
имеет два компонента: один компо¬
нент задает условие завершения рекурсии, проверяя текущую зада¬
случай,
чу на
выполнения
5.2.
очередного
а
второй
компонент упрощает задачу для
рекурсивного
вызова.
Для программы, приведенной ниже, установите область действия
каждого из перечисленных элементов (область действия функции,
область действия файла, область действия блока или область дейст¬
вия
прототипа
a) Переменная
функции).
x
в
b) Переменная у в
c) Функция cube.
main.
cube.
d) Функция main.
e) Прототип функции
для cube.
232
Глава 5
у в прототипе функции cube.
f) Идентификатор
<stdio.h>
#include
int
cube(int
main
у);
()
{
int
x;
for
(x
1;
=
10;
x++)
cube(x));
<=
x
pnntf("%d\n",
}
int
cube(int
у)
{
return
у
=
*
у
у;
}
5.3.
вызова
ры
рис.
5.4.
действительно ли приме¬
функций математической библиотеки, показанные на
Напишите программу, которая проверяет,
5.2,
дают приведенные на рисунке результаты.
Приведите
заголовок
функции
функций.
для следующих
a) Функция hypotenuse (гипотенуза),
которая получает два аргумен¬
та sidel и side2, имеющих тип с плавающей точкой удвоенной точ¬
ности,
и
которая возвращает результат
щей точкой удвоенной
b) Функция smallest (наименьшее),
у,
z
и
целое
возвращает
в
виде
значения
с
плаваю¬
точности.
которая получает три целых x,
значение.
c) Функция instructions (инструкции), которая не получает аргу¬
ментов и ничего не возвращает (Примечание: такие функции часто
используются для вывода на экран инструкций для пользователя.)
d) Функция intToFloat,
которая получает целый аргумент number и
возвращает результат типа с плавающей точкой.
5.5.
5.6.
Приведите
прототипы следующих
функций:
5.4a.
a) Функции, рассмотренной
в
упражнении
b) Функции, рассмотренной
в
упражнении 5.4b.
c) Функции, рассмотренной
в
упражнении 5.4c.
d) Функции, рассмотренной
в
упражнении
5.4d.
Напишите следующие объявления:
a) Целого count, которое должно обрабатываться
ализируйте count значением 0.
b) Переменной
значение
с
между
c) Внешнего
целого
Найдите ошибку
ее
исправить
регистре. Иници¬
плавающей точкой lastVal, которая ^охраняет свое
вызовами функции, в которой она определена.
ваться оставшейся
5.7.
в
number, чья область действия должна ограничи¬
файла, в котором определена переменная.
частью
фрагменте программы
упражнение 5.50):
в каждом
(см.
также
и
объясните,
как
233
Функции
a)
int
g(void) {
pnntf("Inside function g\n");
int
{
h(void)
pnntf("Inside function h\n");
}
}
b) int sum(int
int
int
x,
у)
{
result;
result
=
+
x
у;
}
c)
int
sum(int
if
n)
{
0)
return 0;
else
==
(n
n
sum(n
+
1);
-
}
d)
void
f(float
float
а);
{
а;
prmtf("%f",
а);
}
e)
void
{
product(void)
int а, b, с, result;
pnntf("Enter three integers:
scanf
&c);
("%d%d%d",
&a,
&b,
result
=
a
*
*
b
c;
pnntf("Result is %d",
return
")
result);
result;
}
Ответы на
5.1.
упражнения для самоконтроля
а) функцией, b)
turn. e) void.
вызова
функции, с) локальной переменной, d)
f) Область действия, g) return;
или return
re¬
expression;
правой фигурной скобки, завершающей функцию,
h) Прототип функции, i) rand. j) srand. k) auto, register, extern, sta¬
tic. 1) автоматический m) register. n) внешней, глобальной, о) static.
p) область действия функции, область действия файла, область дей¬
ствия блока, область действия прототипа, q) рекурсивной, r) основ¬
или достижение
ной.
5.2.
а) область действия блока,
b)
область действия блока, с) область дей¬
файла, d) область действия файла, e) область действия файла.
f) область действия прототипа.
ствия
5.3.
/* Проверка функций
#include
<stdio.h>
#include
<math.h>
main(
математической
библиотеки
*/
)
{
printf("sqrt(%.If)
pnntf ( "sqrt (%
.
If)
=
%.lf\n",
=%.lf\n",
900.0,
9.0,
sqrt(900.0));
sqrt(9.0));
234
Глава 5
%f\n", 1.0, exp(1.0));
%f\n", 2.0, exp(2.0));
pnntf("exp(%.lf)
printf("log (%f) =%.lf\n", 2.718282, log(2.718282));
printf("log(%f) =%.lf\n", 7.389056, log(7.389056));
%.lf\n", 1.0, logl0(1.0));
printf("logl0 (%.If)
printf("logl0 (%.If) =%.lf\n", 10.0, logl0 (10.0));
printf("logl0 (%.If) =%.lf\n", 100.0, logl0 (100.0));
%.lf\n", 13.5, fabs(13.5));
printf("fabs (%.If)
%.lf\n", 0.0, fabs(0.0));
printf("fabs(%.lf)
printf("fabs(%.If) =%.lf\n", -13.5, fabs(-13.5));
%.lf\n", 9.2, ceil(9.2));
printf("ceil(%.lf)
%.lf\n", -9.8, ceil(-9.8));
printf("ceil(%.lf)
printf("floor ( %.If) =%.lf\n", 9.2, floor(9.2));
%.lf\n", -9.8, floor(-9.8));
printf("floor(%.lf)
%.lf\n",
printf("pow(%.lf, %.lf)
2.0, 7.0, pow(2.0, 7.0));
%.lf\n",
printf("pow(%.If, %.lf)
=
printf("exp(%.lf)
=
=
=
=
=
=
=
=
=
9.0, 0.5, pow(9.0, 0.5));
%.3f\n",
prmtf("fmod(%.3f/%.3f)
13.675, 2.333, fmod(13.675, 2.333));
%.lf\n", 0.0, sin(0.0));
printf("sin(%.lf)
%.lf\n", 0.0, cos(0.0));
printf("cos(%.lf)
%.lf\n", 0.0, tan(0.0));
pnntf("tan(%.lf)
=
=
=
=
3.0
=
sqrt(9.0)
30.0
=
sqrt(900.0)
exp(1.0)
=
2.718282
exp(2.0)
=
7.389056
log(2.718282)
=
1.0
log(7.389056)
=
2.0
0.0
=
logl0(1.0)
1.0
=
logl0(10.0)
13.5
=
fabs(13.5)
0.0
=
fabs(0.0)
10.0
=
ceil(-9.8)
=
floor(9.2)
=
floor(-9.8)
13.5
=
fabs(-13.5)
ceil(9.2)
2.0
=
logl0(100.0)
-9.0
9.0
=
-10.0
pow(2.0,
7.0)
=
128.0
pow(9.0,
0.5)
=
3.0
=
fmod(13.675/2.333)
sin(0.0) = 0.0
5.4.
5.5.
cos (0.0)
=
1.0
tan(0.0)
=
0.0
a)
double
b)
int
c)
void
d)
float
a)
double
b)
int
2.010
sidel,
hypotenuse(double
smallest(int
x,
int
y,
int
double
z)
instructions(void)
mtToFloat (mt
number)
hypotenuse(double,
smallest(int,
int,
double);
int);
side2)
235
Функции
5.6.
c)
void
d)
float
instructions(void);
intToFloat(int);
а) register
int
b)
static
float
c)
static
int
Примечание:
count
0;
=
lastVal;
number;
эти
объявления должны находиться
вне
любого опре¬
деления функции.
5.7.
а) Ошибка: функция h определена
Исправление:
b) Ошибка:
ло,
но
поместите
функция,
эта
этого
x
определение
+
Исправление:
n
h
вне
определения
g.
как предполагается, возвращает целое чис¬
переменную result и вставьте в функцию сле¬
у;
c) Ошибка: не возвращается
неверный результат.
return
функции g.
нет.
Исправление: удалите
дующий оператор:
return
в
результат n +
перепишите оператор
+
sum
в
sum(n-l);
условии
else
sum возвращает
как
1);
-
(n
d) Ошибка:
точка с запятой после правой круглой скобки, которая
включает список параметров, и переопределение параметра а в опре¬
делении функции.
Исправление:
списка
удалите точку с запятой после
параметров
и
удалите
правой круглой скобки
объявление float a;.
e) Ошибка: функция возвращает значение, когда
этого не предпола¬
гается.
Исправление:
удалите оператор return.
Упражнения
5.8.
5.9.
Приведите
=
значение
a)
x
b)
x
c)
x
=
fabs
(0.0)
d)
x
=
ceil
(0.0)
=
e)
x
=
f)
x
=
g)
x
=
fabs
x
после
выполнения
каждого
(7.5)
floor(7.5)
fabs(-6.4)
ceil (-6
.
4)
ceil(-fabs(-8 + floor (-5.5)))
Гараж требует
оплатить
минимальный
взнос в
машины на время, не большее трех часов. За
(или
в
за
час.
$2.00
для парковки
каждый
час
времени
неполный час) сверх 3-х часов гараж требует доплаты $0.50
Максимальная плата за любые 24 часа составляет $10.00.
Предположим,
время,
оператора
что здесь нет
большее 24-х
часов.
автомобилей, запаркованных сразу
Напишите программу, которая будет
на
вы¬
Глава 5
236
числять и печатать плату за парковку для 3-х водителей, которые
вчера запарковали свои автомобили в этом гараже. Вы должны буде¬
Ваша про¬
табличном фор¬
те ввести время начала парковки для каждого заказчика.
грамма должна печатать результаты
в
аккуратном
печатать общую сумму вчерашних
использовать
функцию calculateCharПрограмма должна
ges для определения платы от каждого клиента. Программа должна
выводить данные в следующем формате:
и
мате
вычислять
должна
и
платежей.
Car
5.10.
Hours
1
2
3
1.5
4.0
24.0
Charge
2.00
2.50
10.00
TOTAL
29.5
14.50
Одним
до
из
применений функции floor
является округление значения
ближайшего целого. Оператор
у
=
floor(x
+
.5);
округлит число x до ближайшего целого и присвоит результат у. На¬
пишите программу, которая считывает несколько чисел и использу¬
ет данный оператор для округления каждого из этих чисел до бли¬
жайшего
целого.
5.11.Функция floor
=
может
десятичного
данного
у
Для
каждого
floor(x
*
10
=
floor(x
округляет
x до
*
100
+
.5)
+
напечатайте
округления числа до
за¬
Оператор
/
10;
Оператор
/
.5)
100;
Напишите
функции
a) roundToInteger(number)
программу, которая
для округления числа x:
/*
округлить
b) roundToTenths(number)/* округлить до
c) roundToHundreths(number)/* округлить
d) roundToThousandths(number)/*
Для
числа
число.
использоваться для
сотых.
следующие четыре
обработанного
округленное
знака.
округляет x до десятых.
у
и
значение
первоначальное
до
целого
десятых
до
округлить
сотых
до
определяет
*/
*/
*/
тысячных
*/
каждого считанного значения ваша программа должна печатать
первоначальное значение, число, округленное до ближайшего цело¬
го, число, округленное до десятых, число, округленное до сотых и
число,
округленное до тысячных.
5.12.0тветьте на следующие вопросы.
a) Что
означает
«выбирать
числа
случайным образом»?
b) Почему для моделирования стохастических игр шанс необходима
функция rand?
c) По какой причине вы рандомизировали бы программу с помощью
функции srand? При каких обстоятельствах рандомизация нежела¬
тельна?
d) Почему
часто
возникает
необходимость масштабировать и/или
функцией rand?
сдвигать значения, генерируемые
237
Функции
e) Чем
полезен метод компьютерных
имитаций ситуаций реального
мира?
5.13.Напишите операторы, которые присваивают целые случайные
переменной n в следующих диапазонах:
a)
l
<n
<
b)
1
<n
<100
0
< n
< 9
c)
5.14.
2
d)
1000
e)
-1
<n
< 1
f)
-3
<n
<11
Для
числа
<n
<1112
наборов целых чисел напишите одиноч¬
который будет печатать число из набора случайным
каждого из следующих
ный оператор,
образом.
a)
2,
4,
б,
8,
10.
b)
3,
5,
7,
9,
11.
c)
6,
10,
14,
18,
22.
hypotenuse, которая вычисляет длину гипоте¬
нузы прямоугольного треугольника по двум другим сторонам. Испо¬
льзуйте эту функцию в программе для определения длины гипотену¬
5.15.0пределите функцию
зы треугольников, приведенных ниже. Функция должна получать
два аргумента типа double и возвращать значение гипотенузы также
типа double.
Треугольник
Сторона 1
Сторона 2
1
3 0
40
2
5 0
12 0
3
8 0
15 0
5.16.Напишите функцию
щает значение
integerPower(base, exponent),
которая возвра¬
baseexponent
Например, integerPower (3, 4)
ponent
Для
управления
3
*
3
*
3
*
3. Предположим, что ex¬
а base целым.
функция integerPower должна
используйте никаких функций математиче¬
вычислением
применять цикл for. Не
ской
=
является положительным ненулевым целым,
библиотеки.
5.17. Напишите функцию
multiple
для двух целых, которая определяет,
кратно ли второе число первому. Функция должна получать два це¬
лых аргумента и возвращать 1 (true), если второе число кратно пер¬
вому, и 0 (false) в противном случае. Используйте эту функцию в
программе, которая вводит серию пар целых чисел.
5.18.Напишите
программу, которая вводит последовательность целых
чисел и передает их по одному функции even, которая использует
238
Глава 5
операцию деления по модулю, чтобы определить, является ли целое
четным. Функция должна получать один аргумент и возвращать 1,
если
целое
четно
0
и
в
противном
случае.
5.19.Напишите функцию, которая выводит у левой границы экрана
сплошной квадрат из символов *, сторона которого определяется це¬
лым параметром side.
дет
следующее
Например,
изображение:
5.20.Измените функцию, созданную
чтобы
в
в символьном
side равно 4, функция
упражнении
5.19
таким
выве¬
образом,
любых символов, содержащихся
параметре fillCharacter. Например, если side равно 5,
формировала квадрат
она
если
а символ равен
«#»,
то
функция
из
выведет на экран следующее изоб¬
ражение:
#####
#####
#####
#####
#####
методику, аналогичную развитой в упражнениях 5.19
и 5.20 для создания программы, которая рисует разнообразные фи¬
5.21.Используйте
гуры.
5.22. Напишите
фрагменты
программ,
которые
выполняют
следующие
действия:
a) Вычисляет
целую часть от деления целого числа а на целое чис¬
ло b.
b)
ло
Вычисляет целый
b.
c) Используйте
для создания
части программы,
разработанные
функции, которая вводит целое
и печатает его как
мя
остаток от деления целого числа а на целое чис¬
в
пунктах
число между
1
а)
и
и
b),
32767
ряд цифр, каждая пара которых отделяется дву¬
пробелами. Например,
целое число 4562 должно быть напечата¬
но как
4
5
6
2
5.23. Напишите функцию, которая получает время в качестве трех целых
аргументов (часы, минуты и секунды) и возвращает число секунд с
«пробили 12». Используйте эту функцию для
вычисления промежутка времени в секундах между двумя момента¬
ми, которые лежат внутри одного и того же 12-ти часового круга.
момента, когда часы
5.24.
Реализуйте
следующие целые функции:
a) Функция celsius возвращает эквивалент по шкале Цельсия темпе¬
ратуры, заданной по шкале Фаренгейта.
b) Функция fahrenheit возвращает эквивалент по шкале Фаренгейта
температуры, заданной по шкале Цельсия.
239
Функции
с) Используйте эти функции для создания программы, которая печа¬
тает таблицы, показывающие температурные эквиваленты шкалы
Фаренгейта для всех значений температуры по шкале Цельсия в
диапазоне от 0 до 100 градусов, и эквиваленты шкалы Цельсия для
всех температур шкалы Фаренгейта в диапазоне от 32 до 212 граду¬
сов.
Напечатайте аккуратную таблицу
зирует
число
выводимых
строк
при
в
формате, который
сохранении
5.25.Напишите функцию, которая возвращает
с плавающей точкой.
5.26. Целое
лей,
число называют
включая
1 (но
миними¬
таблицы.
ясности
наименьшее из трех чисел
совершенным числом, если сумма его делите¬
не само
число),
равна этому числу. Например, 6
1 + 2 + 3. Напишите
6
является совершенным числом, поскольку
=
которая определяет, является ли ее параметр со¬
вершенным числом. Используйте эту функцию в программе, кото¬
рая находит и печатает все совершенные числа в диапазоне от 1 до
функцию
perfect,
1000. Напечатайте все делители для каждого совершенного числа,
чтобы убедиться, что число действительно является совершенным.
Бросьте вызов своему компьютеру, попробовав проверить числа, на¬
много большие 1000.
5.27.
Целое
1 и на
само
и
число называют простыму если оно делится только на
себя. Например, 2, 3, 5 и 7 являются простыми, а 4, 6, 8
9
нет.
a) Напишите функцию,
которая определяет, является
ли число
про¬
стым.
эту функцию в программе, которая находит и печа¬
тает все простые числа в диапазоне от 1 до 10000. Сколько чисел из
этих 10000 вы действительно должны проверить, чтобы быть уве¬
b) Используйте
ренным в том, что вы нашли все простые числа?
c) Первоначально вы могли подумать, что верхним пределом для де¬
лителей, с которыми нужно проверить простое число, является n/2,
но в
действительности
ного
корня
двумя
из
n.
вам нужно дойти только до значения квадрат¬
Почему? Перепишите программу и запустите ее
способами. Оцените увеличение производительности.
5.28. Напишите функцию, которая получает целое
число
с
обращенным
порядком цифр.
функция должна вернуть
значение
значение и возвращает
Например,
для числа
7631
1367.
5.29. Наибольший общий делитель (НОД) двух целых чисел является са¬
мым большим целым числом, на которое делится каждое из двух чи¬
сел. Напишите функцию gcd, которая возвращает наибольший об¬
щий делитель двух целых чисел.
5.30. Напишите функцию
gualityPoints,
которая вводит средний бал сту¬
дента и возвращает 4, если средний бал студента составляет 90-100,
3, если 80-89, 2, если 70-79, 1, если 60-69, и 0, если средний бал
ниже
чем
60.
5.31.Напишите программу, которая моделирует подбрасывание
Для
каждого
подбрасывания
Heads
Tails (орел
слова
или
монеты
или
программа
решка). Пусть
должна
программа
монеты.
печатать
подбросит
Глава 5
240
монету 100 раз и сосчитает число выпадений для каждой стороны
монеты. Напечатайте результаты. Программа должна вызывать от¬
дельную функцию flip, которая не получает никаких аргументов и
возвращает 0 для решки и 1 для орла.
подбрасывание
моделирует
Примечание:
если программа
монеты реалистично, то каждая сторона
монеты должна выпасть примерно равное число раз
50 орлов
5.32. Компьютеры играют
все
возрастающую
роль
шите программу, которая поможет ученику
чить
(т.е.
примерно
50 решек при 100 подбрасываниях).
и
таблицу
умножения.
в
образовании. Напи¬
начальной
школы выу¬
Используйте функцию rand
для генера¬
ции двух положительных одноразрядных целых чисел.
Программа
должна выводить вопрос вроде:
How much is
6
times
7?
(Сколько будет
шестью
семь?)
Школьник должен напечатать ответ. Ваша программа проверяет от¬
вет. Если он правильный, напечатайте фразу «Очень хорошо!». По¬
сле этого задавайте следующий вопрос по умножению. Если ответ
неправильный,
напечатайте
«Неверно. Пожалуйста попробуйте
снова.» и разрешите школьнику отвечать на вопрос до тех пор, пока
он
не
даст
правильный
ответ.
образовании относится к области ис¬
следований, называющейся
обучением. Одной из проб¬
лем, разрабатываемой в области машинного обучения, является
проблема усталости ученика. Усталость может быть уменьшена пу¬
тем внесения разнообразия в диалог с компьютером, что помогает
5.33. Использование компьютеров
в
машинным
удержать внимание ученика.
так,
что
она
будет
печатать
Измените программу упражнения 5.32
различные
комментарии
правильного ответа и каждого неправильного ответа.
для
каждого
Например,
сле¬
дующие:
Реакция
на
правильный
ответ
Very good!
Excellent!
Nice work!
Keep up the
(Очень хорошо!)
(Превосходно!)
(Отличная работа!)
good work! (Продолжайте
Реакция
неправильный
на
в
том
же
духе!)
ответ
try again. (Нет. Пожалуйста пробуйте снова.)
Try once more. (Неправильно. Попытайтесь еще раз.)
Don't give up!
(He сдавайтесь!)
No. Keep trying.
(Нет. Продолжайте пробовать.)
No.
Please
Wrong.
Используйте генератор случайных чисел в диапазоне от 1 до 4 для
выбора одного из комментариев к каждОму ответу. Используйте для
вывода фраз структуру switch совместно с оператором printf.
5.34.
Более
обучения контролируют эффек¬
ученика в течение определенного периода времени.
Решение начать новый раздел часто основано на успехах ученика по
предыдущим разделам. Измените программу упражнения 5.33 так,
сложные системы машинного
тивность
работы
чтобы считать число правильных и неправильных ответов, вводи¬
мых учеником. После того как ученик введет 10 ответов, ваша про¬
грамма должна вычислить процент правильных ответов. Если про¬
241
Функции
цент ниже чем
и
щи»
75,
ваша программа должна напечатать
фразу
типа
попросите вашего учителя о дополнительной помо¬
«Пожалуйста,
работу.
завершить
5.35.Напишите программу С,
ваша
дующим образом:
которая играет
в игру
«угадай
число»
сле¬
программа «задумывает» число, которое
нужно угадать, выбирая целое число в диапазоне от 1 до 1000 слу¬
чайным образом. После этого программа печатает:
У
меня
Вы
есть
можете
число
из
этого игрок
1
и
1000.
число?
ваше
первое
предположение.
вводит первое число.
Программа
отвечает
одной
фраз:
следующих
1.Превосходно!
Не
мое
напечатайте
Пожалуйста,
После
между
отгадать
Вы
угадали
число!
желаете
попробовать еще раз?
2.Слишком маленькое. Попробуйте еще раз.
3.Слишком большое. Попробуйте еще раз.
Если предположение игрока неправильно,
ваша программа должна
выполнять цикл, пока игрок не введет правильное число. Ваша про¬
сообщения игроку «Слишком
большое» или «Слишком маленькое», чтобы помочь ему попасть в
правильный ответ. Примечание: методика поиска, использованная в
этой задаче, называется двоичным поиском. В следующей задаче мы
поговорим об этом более подробно.
грамма должна продолжать печатать
5.36.Измените
10
образом, чтобы
программу упражнения 5.35 таким
тать число
предположений, которое
напечатайте фразу «Либо
Если игрок угадывает число
или меньше, то
вам повезло!».
счи¬
делает игрок. Если это число
вы знаете секрет,
с
10-ти попыток,
либо
то на¬
знаете секрет!». Если игрок делает более 10-ти
предположений, то напечатайте фразу «У вас есть возможность уга¬
дывать быстрее!». Почему этот процесс должен длиться не больше
печатайте фразу «Вы
10-ти предположений? С каждым «хорошим предположением» иг¬
рок может устранить половину чисел. Покажите, почему любое чис¬
ло от 1 до 1000 можно угадать за 10 или за меньшее число попыток.
5.37.Напишите рекурсивную функцию power
возвращает значение
(base, exponent),
которая
baseexponent
Например, power(3, 4)
является целым,
мог
бы
использовать
baseexponent
а
=
base
завершающим
станет
base1
=
равным
=
3
большим
*
*
3
*
3
*
3. Предположим, что exponent
1. Подсказка: шаг рекурсии
или равным
соотношение
baseexponent l
условием
будет случай,
1, поскольку
base
5.38.Последовательность
чисел
0, 1, 1, 2, 3, 5, 8, 13, 21,
Фибоначчи
...
когда значение
exponent
242
Глава 5
начинается с числа 0 и 1 и имеет то свойство, что каждое последую¬
щее число является суммой двух предыдущих, а) Напишите нере¬
курсивную функцию fibonacci(n), которая вычисляет n-oe число Фи¬
боначчи. b) Определите наибольшее число Фибоначчи, которое
можно
ти
а)
найти
таким
на
вашей
системе.
образом, чтобы
Для
этого измените программу час¬
для вычисления и возврата чисел Фибо¬
наччи использовать тип double вместо int.
пока
циклиться,
большого
система
не
выдаст
Разрешите программе за¬
ошибку из-за чрезмерно
значения.
5.39. (Ханойская башня) Каждый подающий надежды ученый должен по¬
знакомиться с некоторыми классическими задачами, а Ханойская
башня (см. рис.
5.18)
это
из
одна
наиболее
известных
классиче¬
Дальнем Востоке священ¬
ники пытаются переместить башню из дисков с одной стойки на дру¬
гую. Начальная башня имела 64 диска, собранных на одной стойке и
упорядоченных от нижней части до верхней по уменьшению размера.
Священники пытаются переместить башню с этой стойки на вторую
ских задач.
Легенда
гласит, что в храме на
при следующих ограничениях: за один раз перемещается только один
диск и ни при каких условиях диск
большего размера
не может нахо¬
Третья стойка
может исполь¬
диться выше диска меньшего размера.
зоваться
для
временного
хранения
придет конец, когда священники
нас
не
так
уж
много
дисков.
выполнят
оснований, чтобы
Согласно легенде миру
свою задачу. Так что у
попытаться
им
помочь.
Рис. 5.18. Ханойская башня для случая четырех дисков
примем, что священники пытаются перемещать диски со
стойки 1 на стойку 3. Мы хотим разработать алгоритм, который бу¬
Давайте
дет печатать точную последовательность перемещения дисков.
попробуем подойти
быстро и безнадежно
Если мы
ми, мы
к
этой задаче
со
стандартными метода¬
завязнем в перемещениях дисков.
проблеме, помня о рекурсии, то
послушной. Перемещение n дисков
то этого, если мы приступим к
незамедлительно
станет
рассматриваться с
следовательно,
точки
зрения
перемещения только
это имеет отношение к
рекурсии)
n
-
Вмес¬
задача
может
1 диска (а
следующим
образом:
243
Функции
1. Переместить n
1 диск со стойки 1 на стойку 2, используя стой¬
ку 3 как временное хранилище.
-
2. Переместить последний диск
ку 3.
(наибольший)
стойки 1
со
на
стой¬
1 диск со стойки 2 на стойку 3, используя стой¬
3. Переместить n
ку 1 как временное хранилище.
-
Этот процесс заканчивается, когда последняя задача потребует пере¬
1 диска, т.е. достигается основной случай. Это выпол¬
мещения n
няется обычным перемещением диска без необходимости использо¬
=
вать временное хранилище.
Напишите программу для решения задачи Ханойской башни. Испо¬
льзуйте рекурсивную функцию
с
четырьмя
параметрами:
1.
Число дисков, которое нужно переместить
2.
Стойка, на которой первоначально находятся
3.
Стойка,
4.
Стойка, которую нужно
на
которую
должна
эти
диски
башня дисков
переместиться
использовать как временное хранилище
Ваша программа должна выводить
точные команды, которые она
бу¬
дет выполнять при перемещении дисков с начальной стойки на ко¬
стойку. Например, чтобы переместить башню из трех дисков
на стойку 3, ваша программа должна напечатать следу¬
ющий ряд шагов:
нечную
со
стойки 1
1 ^
3 (Это
2
1
^>
3
^>
2
1
^>
3
2
^>
1
2
^>
3
1
^>
3
означает перемещение диска со стойки 1 на
стойку 3)
5.40. Любая программа, которая может быть реализована рекурсивно, мо¬
жет быть реализована итеративно, хотя иногда со значительно боль¬
шими трудностями и со значительно
те
создать
Если
итеративную
версию
вы сделали это успешно,
меньшей
программы
ясностью.
для
Попробуй¬
Ханойской башни.
сравните вашу итеративную версию с
рекурсивной версией, которую вы разрабатывали в упражне¬
нии 5.39. Исследуйте вопросы эффективности, ясности, и ваши воз¬
можности продемонстрировать правильность программ.
5.41.
(Визуализация рекурсии) Интересно понаблюдать рекурсию «в дей¬
ствии». Измените функцию факториала рис. 5.14 так, чтобы она пе¬
чатала значения своей локальной переменной и параметр рекурсив¬
ного вызова. Для каждого рекурсивного вызова выведите значения
отдельной
строкой
и
добавьте отступы. Сделайте
все возможное, что¬
бы результат вывода выглядел ясно, интересно и осмысленно. Ваша
цель состоит в том, чтобы разработать такой выходной формат дан¬
ных, который
помогает
вить такие же методы
лучше
понять
рекурсию.
отображения информации
гих примеров рекурсии, приведенных в книге.
к
Вы можете доба¬
большинству дру¬
244
Глава 5
5.42. Наибольший
общий делитель целых чисел x и у является наиболь¬
шим целым числом, на которое делятся и x и у. Напишите рекур¬
функцию gcd, которая возвращает наибольший общий
сивную
тель
x
и
дели¬
Рекурсивное определение функции gcd выглядит
следующим образом: если у равен 0, то gcd(x, у) есть x, иначе gcd(x,
у) есть gcd(y, x % у), где %
операция деления по модулю.
у.
рекурсивно? Напишите программу, со¬
держащую функцию main. Включите статическую локальную пере¬
менную count, инициализируемую значением 1. Выполняйте по¬
5.43. Может ли main быть вызвана
стдекремент и печатайте значение count каждый раз,
вызывается main. Запустите программу. Что происходит?
когда
5.44.B
упражнениях с 5.32 по 5.34 была разработана программа машин¬
ного обучения, которая учит школьников начальных классов умно¬
жению. Это упражнение предлагает ввести в программу расшире¬
ния.
a) Измените программу, чтобы разрешить пользователю вводить
уровень сложности задач. Уровень сложности 1 означает, что в зада¬
че используются только одноразрядные числа, уровень сложности 2
означает
использование
чисел
с
двумя
десятичными
разрядами
и
т.д.
b) Измените программу таким образом, чтобы позволить пользовате¬
лю выбирать тип арифметических задач, который он хочет исследо¬
вать. Опция 1 означает, что будут использованы только задачи на
сложение,
2
только задачи на вычитание,
4
умножение,
зование
всех
только задачи на деление и
3
5
только задачи на
смешанное исполь¬
арифметических действий.
5.45. Напишите функцию distance, которая вычисляет расстояние между
двумя точками с координатами (xl, yl) и (x2, y2). Все числа и воз¬
вращаемые
значения
должны
тип
иметь
5.46.Что делает следующая программа?
mam
()
{
int
if
с;
((c
=
getchar())
();
printf ("%c",
!= EOF)
{
main
с);
}
return 0;
}
5.47. Что делает следующая
int
mystery(mt,
программа?
int);
main()
{
int
x,
у;
prmtf("Enter
two
integers:
");
float.
245
Функции
printf("The
return 0;
scanf("%d%d", &x, &y);
result is
%d\n",
Параметр b должен быть
положительным
mystery(x,
y));
}
/*
чтобы
целым,
бесконечную рекурсию */
предотвратить
int mystery(mt а, int b)
{
if (b
1)
return а;
else
return а + mystery(a,
==
b
-
1);
}
5.48. После того, как вы определили, что делает программа упражне¬
ния 5.47, измените программу так, чтобы она правильно работала
после
удаления
ограничения
на
второй
аргумент,
который
теперь
может быть не только положительным.
5.49.Напишите программу, которая проверяет как можно больше функ¬
ций математической библиотеки (рис. 5.2). Поупражняйтесь с каж¬
из этих функций, распечатав таблицы возвращаемых значений
для различных значений параметров.
дой
5.50.Найдите
ошибку
объясните,
a)
float
как
в
ее
каждом
cube(float);
cube(float
из
следующих
number)
/*
прототип
/*
определение
функции
number
*
*
number
}
b) register
c)
int
d)
float
auto
randomNumber
=
у
x
int
=
7;
=
srand();
123.45678;
int x;
x
=
у;
pnntf("%f\n",
e)
double
(float)
square(double
x)
;
number)
{
douhle
number;
return
number
*
number;
}
f)
int
sum(int
n)
{
if
(n
==
return
else
return
0)
0;
n
+
sum(n);
*/
функции
{
return
фрагментов программ
исправить:
numbers
*/
и
246
Глава 5
5.51. Измените программу игры в крепс на рис. 5.10, чтобы игрок мог де¬
лать ставки. Реализуйте как функцию ту часть программы, которая
моделирует один розыгрыш. Инициализируйте переменную bankBalance значением 1000 долларов. Попросите игрока ввести ставку.
Используйте цикл while для проверки того, что ставка меньше или
равна значению bankBalance и, если это не так,
зователя
до
тех
пор,
пока
он
не
введет
запрашивайте
корректную
ставку.
поль¬
После
ввода корректной ставки программа должна провести один розыг¬
рыш. Если игрок выиграл, увеличьте значение bankBalance на ве¬
личину ставки и напечатайте это новое значение. Если игрок проиг¬
рал, уменьшите значение bankBalance на величину ставки,
его, проверьте, не равно ли значение bankBalance
нулю. Если деньги кончились, напечатайте соответствующее сооб¬
щение, например, «Извините. Вы проигрались!». По ходу игры вы¬
напечатайте
сообщения, создающие впечатление обычной «бол¬
Например, «О, идете ва-банк, да?», «Ну-ну, рискните!»,
«Не пора ли вам обменять фишки на наличные?».
водите на печать
товни».
г
л
в
а
а
6
Массивы
Цели
Познакомиться со
данных, называемой
структурой
массивом.
Изучить применение
ровки данных
массивов
для
и поиска в списках и
хранения, сорти¬
таблицах значений.
Научиться объявлять массив, инициализировать его
и ссылаться на отдельные элементы массива.
Научиться передавать
Изучить
основные
методики
Научиться объявлять
ми
и
работать
с
массив
в
функцию
сортировки.
массивы с несколькими индекса¬
ними.
248
Глава 6
Содержание
6.1.
Введение
6.2. Массивы
6.3. Объявление массивов
6.4.
Примеры
использования
6.5.
Передача
массивов
6.6.
Сортировка
6.7.
Пример: определение среднего, медианы и наиболее вероятного зна¬
чения с использованием массивов
в
массивов
функции
массивов
6.8. Поиск в массивах
6.9.
Многомерные
массивы
Распространенные ошибки программирования
Хороший стиль
Советы по повышению эффективности
Общие мето¬
дические замечания
Упражнения для самоконтроля Ответы к упражне¬
ниям для самоконтроля
Упражнения
Упражнения на рекурсию
Резюме
программирования
6.1. Введение
Эта
глава служит введением в важную тему о структурах данных.
Масси¬
представляют собой структуры данных, состоящие из логически связан¬
ных элементов данных одного и того же типа. В главе 10 мы обсудим понятие
вы
структуру данных, состоящую из логически
(структуры) языка С
связанных элементов данных, возможно, различных типов. Массивы и струк¬
туры являются «статическими» объектами в том смысле, что их размер оста¬
struct
ется одним и тем же в процессе выполнения программы
(конечно,
образом,
принадлежать к автоматическому классу памяти и, таким
они могут
создавать¬
ся и уничтожаться всякий раз при входе и выходе из блоков, в которых они
определяются).
В
главе
12
мы представим динамические структуры данных,
такие как списки, очереди, стеки и деревья, которые могут увеличивать и уме¬
ньшать
свой размер
в процессе выполнения программы.
Массивы
249
6.2. Массивы
Массив
является
группой
ячеек памяти, логически связанных в том отно¬
шении, что все они имеют одно и то же имя и один и тот же тип.
ния к
и
конкретной области,
номер позиции этого элемента в массиве.
На рис. 6.1 показан целочисленный массив
надцать элементов.
имя
массива
([]). Первый
и
На каждый
номер
элемент в
Для обраще¬
или элементу массива, мы указываем имя массива
позиции
любом
с.
Этот
массив содержит две¬
из этих элементов можно ссылаться, задавая
элемента
конкретного
в
квадратных
скобках
нулевой порядковый номер. Та¬
с обозначается как c[0], второй элемент
в общем случае, i-й элемент массива с
массиве имеет
ким
образом, первый элемент массива
c[l], седьмой элемент как c[6] и,
обозначается как c[i-l]. Для имен массивов
как
справедливы
те
же
соглашения,
что и для имен других переменных.
Имя массива
(заметьте,
что все
элементы этого массива имеют
одно и то же имя,
с)
\
c[0]
-45
c[l]
6
c[2]
о
c[3]
72
c[4]
1543
c[5]
-89
c[6]
0
c[7]
62
c[8]
-3
c[9]
1
6453
c[10]
c[ll]
t
Позиция номера
элемента внутри
массива с
Рис. 6.1.
Массив из 12 элементов
Номер позиции элемента, содержащийся внутри квадратных скобок, бо¬
лее формально называют индексом. Индекс должен быть целым числом или
целочисленным выражением. Если в программе в качестве индекса использу¬
ется выражение, то для определения индекса происходит оценка этого выра¬
жения. Например, если а
5 и b
6, то оператор
=
=
250
Глава 7
c[a
+
+=
b]
2;
прибавляет 2 к элементу массива
c[ll]. Обратите
индексом имя массива является lvalue
внимание, что снабженное
оно может стоять в
левой
части опера¬
ции присваивания.
но.
6.1, более подроб¬
Давайте
рассмотрим массив с,
Имя
как c[0],
c[ll]. Значение c[0] равно -45, значение c[l] равно 6, значение
значение c[7] равно 62 и значение c[ll] равно 78. Для того, чтобы
этого
c[l], c[2],
c[2] равно
массива
с.
представленный
на рис.
Его двенадцать элементов обозначаются
...,
0,
вывести сумму
значений, содержащихся
в первых трех элементах массива с,
мы можем написать
c[0]
pnntf("%d",
Для
присвоить результат
=
x
+
c[l]
+
c[2]);
того, чтобы разделить значение седьмого элемента массива с на 2 и
/
c[6]
переменной
x, можно написать
2;
Распространенная ошибка программирования 6.1
Важно
понимать различие между «седьмым элементом массива» и «элементом мас¬
семь»
Поскольку индексы массива начинаются с 0, «седьмой элемент
массива» имеет индекс 6, в то время как «элемент массива номер семь» имеет индекс
7 и фактически является восьмым элементом массива Это источник так называемых
сива
номер
ошибок смещения индекса
Скобки,
в которые заключается индекс массива, на самом деле рассматри¬
ваются в качестве операции языка
и
круглые
скобки. В таблице
операций,
ность
С. Они
на рис.
6.2
имеют тот же
самый приоритет,
уже представленных в книге.
Операции
показаны
вниз в порядке уменьшения их приоритета.
Операции
О
++
[]
--
!
(тип)
%
/
+
V
V
II
л
Л
II
| =
Ассоциативность
Тип
слева
наивысший
направо
справа налево
унарные
слева направо
мультипликативные
слева направо
аддитивные
слева
направо
отношения
слева
направо
равенства
&&
слева направо
логическое И
11
слева направо
логическое ИЯИ
справа налево
условные
справа налево
присваивания
слева направо
запятая
7
;
+=
_^
-=
*=
/=
%
=
что
показаны приоритет и ассоциатив¬
Рис. 6.2. Приоритет
операций
сверху
Массивы
251
6.3. Объявление
Массивы
занимают
определенное
массивов
в
место
Программистом уста¬
требуемых для каждого
соответствующий объем памя¬
памяти.
навливается тип каждого элемента и число элементов,
массива, чтобы компьютер мог зарезервировать
Для указания компьютеру, чтобы он зарезервировал 12 элементов для це¬
лочисленного массива с, нужно написать
ти.
int
с[12] ;
помощи одного объявления можно зарезервировать память для не¬
скольких массивов. Чтобы зарезервировать 100 элементов для целочисленного
массива b и 27 элементов для целочисленного массива x, нужно написать:
При
int
b[100],
x[27];
Массивы могут объявляться также и для хранения других типов данных.
для символьной строки может использоваться массив типа char.
Символьные строки и их сходство с массивами рассматриваются в главе 8.
Связь между указателями и массивами обсуждается в главе 7.
Например,
6.4.
Примеры работы
Программа, представленная
на рис.
с массивами
6.3, использует структуру повторения
for
для инициализации нулями элементов целочисленного массива n из деся¬
ти элементов и выводит его в табличной форме.
I /*
инициализация
#mcluide
массива
*/
<stdio.h>
main ()
{
int
n[10],
for
(i
-
n[i]
=
i;
0; i
0;
<=
9;
i++)
/*
инициализация
массива
pnntf("%s%13s\n", "Element", "Value");
for (i
/* вывод элементов
0; i <= 9; i++)
printf("%7d%13d\n", i, n[i]);
=
return
Element
0
1
2
3
4
5
6
7
8
9
0;
Value
0
0
0
0
0
0
0
0
0
0
Рис. 6.3. Инициализация
элементов массива нулями
*/
массива
*/
252
Глава 6
Обратите
внимание, что мы решили не помещать пустую строку между
первым оператором
printf
структурой for
и
на рис.
ры тесно связаны. В данном случае оператор
столбцов, выводимых
двух
Элементы
эти операто¬
заголовки для
for. Программисты часто опускают пу¬
ней оператором printf.
инициализироваться при объявлении мас¬
в структуре
структурой for
стую строку между
6.3, поскольку
printf отображает
и тесно связанным с
массива также могут
сива путем помещения вслед за его
объявлением
знака равенства и списка
(за¬
фигурные скобки) инициализирующих значений, разделенных
Программа, представленная на рис. 6.4, инициализирует целочис¬
ключенного в
запятыми.
ленный массив десятью значениями и выводит его в табличной форме.
/ * Инициализация массива
#include <stdio.h>
I
main
объявлении
при
*
/
()
{
int
i,
=
n[10]
{32,
printf("%s%13s\n",
for
0;
=
(i
i
<=
64,
27,
18,
"Element",
9;
95,
14,
90,
70,
60,
37}
;
"Value");
1++)
printf("%7d%13d\n",
i,
n[i]);
i
0;
return
}
Lt
Value
0
1
27
32
2
64
3
18
4
95
14
5
6
90
7
70
8
60
9
37
Рис. 6.4. Инициализация элементов массива при объявлении
Если инициализирующих значений меньше, чем элементов массива, оста¬
ющиеся элементы автоматически инициализируются нулями. Например, эле¬
менты массива n
средством
на рис.
6.3
int n[10]
=
щих
по¬
{0};
которое явно инициализирует нулем
лизирует
было бы инициализировать нулями
можно
объявления
нулями
оставшиеся
значений меньше,
первый
девять
элемент и автоматически инициа¬
элементов,
чем элементов в массиве.
не инициализируются нулями автоматически.
поскольку
инициализирую¬
Важно помнить,
Программист
что массивы
должен инициа¬
лизировать по крайней мере первый элемент, чтобы оставшиеся элементы
были автоматически заполнены нулями. Этот метод инициализации элементов
массива нулями реализуется во время компиляции.
рис.
6.3,
Метод, используемый
на
может применяться многократно в процессе выполнения программы.
Массивы
253
Распространенная ошибка программирования 6.2
Отсутствие инициализации массива, элементы которого должны быть инициализиро¬
ваны
Следующее объявление
mt
{32,
=
n[5]
27,
массива
64,
18,
95,
14};
бы синтаксическую ошибку, поскольку имеется 6 инициализирую¬
щих значений и только 5 элементов массива.
вызвало
Распространенная ошибка программирования
6.3
Указание в списке инициализации большего числа значений, чем количество элемен¬
тов в массиве, является синтаксической ошибкой
Если размер массива не включается в его объявление со списком инициа¬
лизирующих значений, то число элементов в массиве будет равно числу эле¬
ментов в списке инициализации. Например, объявлением
mt
n[]
был бы создан
2,
{1,
=
3,
4,
5}
;
массив из пяти элементов.
Программа
на рис.
сива s значениями
6.5 инициализирует элементы десятиэлементного мас¬
20 и выводит массив в табличной форме. Эти
2, 4, 6,
...,
значения генерируются путем умножения
ния 2.
счетчика
цикла
на
2
и
прибавле¬
В этой программе вводится директива препроцессора #define. Строка
#define SIZE 10
значение которой равно 10. Сим¬
волическая константа является идентификатором, вместо которого препроцес¬
сор С до компиляции программы подставляет заменяющий текст. Во время
определяет символическую константу SIZE,
препроцессорной обработки программы
ской
ских
константы
SIZE
констант
масштабируемой.
полнить массив из
#define
с
10
на
для
задания
6.5 все вхождения
«10». Использование
на рис.
заменяются на текст
размеров
массива
делает
символиче¬
символиче¬
программу
На рис. 6.5 с помощью первого цикла for можно было бы за¬
1000 элементов, просто изменив значение SIZE в директиве
1000. Если бы
символическая константа
SIZE
не использова¬
чтобы масштабировать программу для обработки 1000 элементов мас¬
нам пришлось бы изменить программу в трех различных местах. По мере
лась, то,
сива,
увеличения размера кода эта методика становится
ствуя написанию более ясных программ.
еще
более полезной, способ¬
Распространенная ошибка программирования 6.4
Завершение директив препроцессора #define или #include точкой
бывайте, что директивы препроцессора не являются операторами
с
запятой
языка
Не
за¬
С
Если предыдущая директива препроцессора #define будет оканчиваться
с запятой, то все вхождения символической константы SIZE в програм¬
точкой
ме
будут
заменены препроцессором на текст
«10;». Это
может привести к син¬
ошибкам времени компиляции или логическим ошибкам во вре¬
мя выполнения. Не забывайте, что препроцессор не является компилятором С,
это всего лишь текстовый манипулятор.
таксическим
254
Глава 6
/*
Инициализирует
элементы
четными
числами
целыми
массива
от
2
s
2 0
до
*/
#include <stdio.h>
#define SIZE 10
main
()
{
int
s[SIZE],
for
s
(3
[з ]
=
-
j;
0;
j
<=
2
+
2
SIZE
*
printf("%s%13s\n",
for
(j
0;
=
<=
j
]++)
"Element",
SIZE
1;
-
printf("%7d%13d\n",
return
1;
-
/*
устанавливает
значения
*/
з ;
j,
"Value");
j++)
s
/*
выводит
значения
*/
[j]);
0;
Lt
Value
0
2
1
4
2
6
3
8
4
10
5
12
6
14
7
16
8
18
9
20
Рис. 6.5.
Генерация значений
для размещения в элементах массива
Распространенная ошибка программирования 6.5
Присвоение
значения
синтаксической
символической
константе
ошибкой Символическая
резервирует для нее места в памяти, как в случае
во
время
Общее
выполнения
в
константа
исполняемом
-
операторе
не переменная
является
Компилятор
не
переменных, содержащих значения
программы
методическое замечание 6.1
Определение размера всех массивов с помощью символических
общность программ (делает их масштабируемыми)
Хороший
констант повышает
стиль программирования 6.1
Используйте для имен символических констант только символы верхнего регистра
Это выделяет эти константы в тексте программы и напоминает программисту, что
символические константы не являются переменными
Программа
на рис.
6.6 суммирует значения, содержащиеся
ном массиве а из двенадцати элементов.
итоговую сумму.
Оператор
в теле цикла
в целочислен¬
for
вычисляет
Массивы
255
/ * Вычисляет сумму элементов массива
#include <stdio.h>
#define SIZE 12
main
*
/
()
mt
for
a[SIZE]
i, total
(i
total
=
return
of
3,
5,
4,
7,
1;
i++)
2,
99,
16,
45,
67,
89,
45},
0;
=
0;
1
+=
а[i];
<=
SIZE
-
("Total of array element values
printf
Total
{1,
=
is
total);
%d\n",
0;
array
element
is
values
383
Рис. б.б. Вычисление суммы элементов массива
В нашем следующем примере массивы используются для подведения ито¬
Рассмотрим постановку задачи.
Сорок студентов попросили оценить качество питания в студенческой
столовой по шкале от 1 до 10 (1 означает ужасное качество, а 10 означает
превосходное). Поместите сорок ответов в целочисленный массив и опреде¬
гов опроса.
лите итоговый
Это
результат опроса.
(см. рис. 6.7). Мы
типичное применение массива
количество
ответов
каждого
40-элементным массивом
ответов
хотим просуммировать
1 до 10). Массив responses является
студентов. Мы используем массив из одиннад¬
типа
(т.е.
от
frequency для подсчета числа появлений каждого ответа. Мы иг¬
первый элемент frequency[0], поскольку более логично, чтобы ответ 1
увеличивал frequency[l], а не frequency[0]. Это позволяет нам использовать каж¬
дый тип ответа непосредственно в качестве индекса в массиве frequency.
цати элементов
норируем
Хороший
стиль программирования 6.2
Стремитесь
к
ясности
программы
фективного использования
более понятного кода
Совет по повышению
памяти
Иногда
или
имеет смысл
отказаться
процессорного времени
в
от
наиболее эф¬
пользу написания
эффективности 6.1
Иногда соображения эффективности
намного
перевешивают
соображения
понятно¬
сти.
for из массива responses по одному выбираются ответы и в
frequency увеличивается один из десяти счетчиков (от frequency[l] до
frequency[10]). Ключевым оператором цикла является
В первом цикле
массиве
++frequency[responses[answer]];
Этот оператор увеличивает
мости
от
answer равна 0,
swer]];
соответствующий счетчик frequency в зависи¬
responses[answer]. Например, когда переменная-счетчик
responses[answer] равно 1, так что ++frequency[responses[an-
значения
на самом деле интерпретируется как
Глава 6
256
++frequency[l];
что увеличивает элемент массива номер один.
/ * Программа опроса
#include <stdio.h>
I
/
40
#define
RESPONSE_SIZE
#define
FREQUENCY_SIZE
main
*
студентов
11
()
{
int
answer,
int
responses[RESP0NSE_SI2E]
10, 1, 6, 3, 8, 6, 10, 3,
rating;
{1, 2, 6, 4, 8, 5, 9, 7, 8,
8, 2, 7, 6, 5, 7, 6, 8, 6, 7,
5, 6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10};
{0};
frequency[FREQUENCY_SIZE]
int
=
=
for
(answer
=
0;
<=
answer
RESPONSE_SIZE
1;
-
answer++)
++frequency[responses[answer]];
printf("%s%17s\n",
for
(rating
=
1;
"Rating",
rating
printf("%6d%17d\n",
return
"Frequency");
<=
1; rating++)
FREQUENCY_SIZE
rating, frequency[rating]);
-
0;
Rating
1
Frequency
2
2
2
3
4
2
5
6
5
11
7
5
7
2
8
9
1
10
3
Рис. 6.7. Простая программа анализа опроса студентов
Когда переменная
answer
равна
++frequency[responses[answer]];
1, responses[answer] равно 2,
так
что
интерпретируется как
++frequency[2];
что увеличивает элемент массива номер два. Когда переменная answer равна 2,
responses[answer] равно 6, так что ++frequency[responses[answer]]; интерпре¬
тируется как
++frequency[6];
что увеличивает элемент массива номер шесть, и так далее.
Обратите внима¬
обработанных во время обследова¬
ния, для суммирования результатов требуется массив, содержащий только
одиннадцать элементов (мы игнорируем элемент с номером нуль). Если бы дан¬
ные содержали недопустимые значения, как, например 13, программа попыта¬
лась бы прибавить 1 к frequency[13]. Это было бы выходом за пределы массива.
ние, что вне зависимости от числа ответов,
Массивы
257
В языке С отсутствует проверка на выход за пределы массива, предотвраща¬
ющая ссылку на несуществующий элемент. Таким образом, выполняющаяся
программа
может
выйти за пределы
без предупреждения. Програм¬
массива
мист должен гарантировать, что все ссылки на массив не выходят за его преде¬
лы.
Распространенная ошибка программирования 6.6
Ссылка
на
Хороший
элемент за
стиль
пределами
массива
программирования 6.3
При выполнении цикла над элементами массива его индекс никогда
меньше 0 и должен
быть строго
(размер массива
обращения к элементам
ше,
чем
Хороший
-
меньше
1). Убедитесь,
вне
этого
общего
стиль программирования 6.4
массива,
способствуя
тем
цикла
программирования 6.5
стиль
Программы должны проверять правильность
тить
быть
боль¬
что условие завершения цикла не допускает
for наибольшее значение индекса
устранению ошибок преждевременного выхода из
Хороший
не
диапазона.
Указывайте в структуре
самым
не должен
количества элементов, т e
воздействия ошибочной информации
Совет по повышению
всех входных
на
значений, чтобы
вычисления
не допус¬
программы
эффективности 6.2
Последствия (обычно серьезные)
ссылок на элементы за пределами массива являются
системно-зависимыми.
В
(рис. 6.8) происходит считывание чисел из
отображение информации в виде столбцовой диаграм¬
нашем следующем примере
массива и графическое
мы, или гистограммы; выводится каждое число и, кроме того, рядом с ним вы¬
водится полоса, состоящая из такого же количества звездочек. Полосы выво¬
вложенным
дятся
printf("\n")
циклом
for.
Обратите
внимание
на
использование
для завершения полосы гистограммы.
обещали, что покажем более изящный метод написания про¬
бросания кости, приведенной на рис. 5.10. Задача состояла в бро¬
В главе 5 мы
граммы для
сании
6000 раз шестигранной
ратор
случайных
чисел
кости для проверки того, на самом ли деле гене¬
вырабатывает случайные
числа.
Вариант этой
про¬
граммы с массивом показан на рис. 6.9.
До
сих пор мы
обсуждали
только целочисленные массивы.
Однако
в мас¬
содержаться данные любого типа. Теперь мы обсудим хранение
строк в символьных массивах. Пока единственной возможностью обработки
сивах
могут
строк, которой мы располагаем, является вывод строки с помощью функции
printf. В языке С строка типа "hello" на самом деле является массивом отдель¬
ных символов.
9
Зак 801
258
Глава 6
*
/ * Программа печати
#include <stdio.h>
#define SIZE 10
/
гистограммы
main()
int
n[SIZE]
int
i,
{19,
=
3,
15,
(i
9,
"Element",
printf("%s%13s%17s\n",
for
11,
7,
13,
17,
5,
1};
j;
0;
=
<=
i
SIZE
1;
-
pnntf("%7d%13d
",
<
n[i];
printf("%c",
'*');
for
1;
=
(j
j
i + +)
i,
"Value",
"Histogram");
{
n[i]);
/*
j++)
выводит
отдельный столбец
*/
printf ( "\n");
}
return 0;
t
Value
0
19
1
3
2
15
3
7
4
11
5
9
6
13
7
5
8
17
9
1
Histogram
*******************
***
***************
*******
***********
*********
*************
*****
*****************
*
Рис. 6.8. Программа вывода гистограмм
Символьные массивы имеют несколько уникальных особенностей. Симво¬
льный массив может быть инициализирован строковым литералом. Напри¬
мер, объявление
char
=
stringl[]
"first";
инициализирует элементы массива
литерала "first".
Размер
массива
stringl отдельными символами строкового
stringl в предыдущем объявлении определя¬
ется компилятором исходя из длины строки.
Важно обратить
лов
и
специальный
внимание на то, что строка
символ
окончания
строки,
"first" содержит
пять симво¬
называемый нулевым
симво¬
образом,
stringl фактически содержит шесть элементов.
Представлением нулевого символа в виде символьной константы является
лом.
Таким
массив
'\0'.
Все строки
сив,
представляющий
в
языке
С
заканчиваются этим символом.
Символьный
мас¬
строку, всегда следует объявлять достаточно большим
всех символов строки и завершающего нулевого символа.
для хранения в нем
Символьные массивы могут также инициализироваться отдельными сим¬
вольными константами в инициализирующем списке. Предыдущее объявле¬
ние эквивалентно
char
stringl
=
{'f'/
'i',
'r',
's',
't',
'
\0'};
Массивы
259
/ * Моделирует бросание шестигранной
#include <stdio.h>
#include <stdlib.h>
кости
6000
*
раз
/
#include <time.h>
#define SIZE 7
main ()
{
int
face,
roll,
frequency[SIZE]
{0};
=
srand(time(NULL));
for
(roll
face
=
1; roll <=
rand() % 6 +
=
++frequency[face];
}
printf("%s%17s\n",
for
(face
1;
=
face
<=
Face
1
roll++)
/*
заменяет
/*
на
"Face",
pnntf("%4d%17d\n",
return
6000;
{
1;
рис.
20-строчный switch */
5.8
*/
"Freduency");
SIZE
face,
-
1;
face++)
frequency[face]);
0;
Frequency
1037
2
987
3
4
1013
1028
5
952
6
983
Рис. 6.9. Программа бросания кости, использующая массивы вместо оператора switch
Поскольку строка в действительности является символьным массивом,
мы можем непосредственно обращаться к отдельным символам строки, поль¬
индексной нотацией. Например, stringl[0] представляет собой
зуясь
T,
а
stringl[3]
Мы
также можем вводить строку в
клавиатуры, пользуясь для этого
вания
символ
V.
символьный
функцией scanf
и
массив непосредственно с
спецификацией преобразо¬
%s. Например, объявление
char
stnng2[20];
создает символьный массив, в котором могут храниться строка из 19 символов
и
завершающий нулевой символ. Оператор
scanf("%s", stnng2);
считывает строку с клавиатуры в
передается в
string2. Обратите
внимание, что имя массива
функцию scanf без предшествующего &, необходимого
с другими
переменными. Символ & обычно используется для того, чтобы снабдить scanf
информацией о местоположении переменной в памяти, с тем чтобы там можно
в ней значение. В разделе 6.5 мы обсудим передачу массивов
функциям. Мы увидим, что имя массива является адресом его начала; поэтому
символ & не является необходимым.
было сохранить
9*
260
Глава 6
Ответственность за то, чтобы в массиве, куда считываются символы, могла
быть сохранена любая вводимая пользователем строка, лежит на программи¬
сте. Приведенный выше вызов функции scanf считывает символы с клавиату¬
пока не будет встречен первый пробельный символ
размер
массива ее не интересует. Таким образом, функция scanf может записывать
ры до тех пор,
информацию
за пределы массива.
Распространенная ошибка программирования 6.7
Передача функции scanf
символьного массива, недостаточно
вводимой с клавиатуры строки, может привести
гим ошибкам времени выполнения
к
Символьный массив, представляющий строку,
мощью
функции printf
и
большого для хранения
потере данных
может
в программе и дру¬
быть выведен
с по¬
спецификатора преобразования %s. Массив string2
выводится при помощи оператора
pnntf("%s\n",
вольного массива.
чен
stnng2) ;
внимание, что
Обратите
printf,
как и
scanf,
Символы строки выводятся до
завершающий нулевой
не
заботится
о размерах сим¬
тех пор, пока не
будет
встре¬
символ.
Рис. 6.10 демонстрирует инициализацию символьного массива строковым
символьный массив, вывод символьного массива в
литералом, чтение строки в
виде строки и доступ к отдельным символам строки.
I
/
I
mam
*
Интерпретация символьных
#include <stdio.h>
массивов
в
виде
*
строк
/
()
{
char
int
stringl[20],
i;
string2[]
printf("Enter а stnng:
scanf ("%s", stnngl) ;
pnntf("stnngl
"stringl
stringl,
for
(i
=
0;
=
"string literal";
");
is
%s\n"
spaces between characters
string2);
is:
%s\nstnng2:
with
!= '\0';
stringl[i]
", strmgl[i]);
is:\n",
i++)
pnntf("%c
prmtf ("\n") ;
return 0;
}
a
string: Hello there
stringl is: Hello
string2 is: String literal
stringl with spaces between characters is:
Enter
H
e
1
1
o
Рис. 6.10.
Интерпретация
символьных массивов в качестве строк
На рис. 6.10 структура for используется для прохода
stringl
и вывода отдельных символов,
разделенных
в цикле по массиву
пробелами,
при помощи
261
Массивы
спецификации
преобразования %c. Условие в структуре for, stringl[i] != '\0',
пока в строке не будет встречен завершающий нулевой
остается истинным,
символ.
5 обсуждался спецификатор
В главе
класса памяти
static. Статическая
ло¬
функции существует на всем протяжении
выполнения программы, но видима только в теле функции. Мы можем приме¬
нять static к объявлению локального массива, при этом во время работы про¬
граммы массив не будет создаваться и инициализироваться всякий раз при
вызове функции и уничтожаться при выходе из нее. Это уменьшает время вы¬
полнения программ, особенно программ с частым вызовом функций, содержа¬
кальная переменная в определении
щих большие массивы.
Совет по повышению эффективности 6.3
В функциях, содержащих локальные массивы и часто входящих и выходящих из об¬
ласти действия, делайте массивы статическими, чтобы они не создавались заново при
каждом вызове функции.
Массивы, объявленные статическими, автоматически инициализируются
один раз во время компиляции. Если статический массив явно не инициализи¬
рован программистом, компилятор инициализирует его нулями.
На рис. 6.11 показаны функция
объявленным static,
staticArrayInit
с
локальным
массивом,
функция automaticArrayInit с автоматическим локаль¬
ным массивом. Функция staticArrayInit вызывается дважды. Статический
локальный массив, содержащийся в функции, инициализируется компилято¬
ром нулями. Функция выводит массив, прибавляет 5 к каждому его элементу
и снова
вцводит
и
При втором вызове функции статический массив со¬
сохранившиеся со времени ее первого вызова. Функция
массив.
держит значения,
automaticArrayInit
также
вызывается
локального массива, содержащегося в
ями
1, 2
и
дважды.
Элементы
автоматического
функции, инициализируются
3. Функция выводит массив, прибавляет 5
значени¬
к каждому его элементу
При втором вызове функции элементы массива
снова инициализируются как 1, 2 и 3, поскольку массив имеет автоматиче¬
ский период хранения.
и снова выводит этот массив.
*
Статические
#include
массивы
инициализируются
нулями
*
/
<stdio.h>
void staticArrayInit(void);
void automaticArrayInit(void);
I/
main()
f
printf("First call to each function:\n");
staticArrayInit ();
automaticArrayInit ();
printf(\n\nSecond call to each function:\n");
staticArrayInit ();
automaticArrayInit ();
return 0;
}
Рис. 6.11. Статические
массивы автоматически инициализируются нулями, если они явно
не инициализированы
программистом (часть 1
из
2)
262
Глава 6
/* функция, иллюстрирующая статический локальный
void staticArrayInit(void)
{
static int а[3];
int i;
массив
pnntf("\nValues
on
entering staticArrayInit:\n");
for
2;
i++)
(i
=
0;
i
<=
printf("arrayl[%d]
=
%d
",
i,
a[i]);
printf["\nValues
on
exiting staticArrayInit:\n");
for
2;
I++)
(i
=
0;
i
<=
printf("arrayl[%d]
=
%d
",
i,
*/
a[i]
+=
5);
}
/* функция, иллюстрирующая автоматический локальный массив
void automaticArrayInit(void)
*/
{
int
a[3]
int
i;
=
{1,
2,
3};
printf("\n\nValues
for
(i
=
0;
i
<=
2;
on
entering automaticArrayInit:\n");
i++)
printf("arrayl[%d]
=
%d
",
i,
a[i]);
printf("\nValues
on
exiting automaticArrayInit:\n");
for
2;
i++)
(i
=
0;
i
<=
printf("arrayl[%d]
=
%d
",
i,
a[i]
+=
5);
}
First
Values
call
on
arrayl[0]
Values on
arrayl[0]
Values on
arrayl[0]
Values
on
arrayl[0]
to
on
function:
entering staticArrayInit:
0
arrayl[l] = 0 arrayl[2]
=
exiting staticArrayInit:
=
=
5
5
arrayl[l]
arrayl[2]
entering automaticArrayInit:
=
=
2
1
arrayl[l]
arrayl[2]
exiting automaticArrayInit:
=
6
arrayl[l] = 7 arrayl[2]
Second call
Values
each
to
each
=
arrayl[0]
=
exiting
10
0
=
5
=
3
=
8
=
5
=
10
=
3
=
8
function
entering staticArrayInit:
5
arrayl[l] = 5 arrayl[2]
arrayl[0]
Values on
=
staticArrayInit:
arrayl[l]
=
10
arrayl[2]
Values
on entering automaticArrayInit:
=
=
2
1
arrayl[l]
arrayl[2]
arrayl[0]
Values on exiting automaticArrayInit:
=
=
7
6
arrayl[2]
arrayl[0]
arrayl[l]
Рис. 6.11. Статические массивы автоматически инициализируются нулями, если они явно
не инициализированы программистом (часть 2 из 2)
Массивы
263
Распространенная ошибка программирования 6.8
Предположение, что элементы локального массива, объявленного как static, инициа¬
лизируются нулями всякий раз при вызове функции, где этот массив объявлен.
6.5.
Передача
массивов в
функции
Для передачи массива в качестве параметра функции укажите его имя без
всяких скобок. Например, если массив hourlyTemperatures был объявлен как
int
hourlyTemeratures[24];
оператор
вызова
функции
modifyArray(hourlyTemeratures, 24);
передает массив hourlyTemperatures и его размер в функцию modifyArray.
При передаче массива в функцию часто передается и размер массива, чтобы
функция смогла обработать определенное количество его элементов.
С автоматически передает массив в функцию путем имитации передачи
параметра по ссылке
при этом вызываемые функции могут изменять значе¬
ния элементов в исходных массивах вызывающих функций. Ведь имя массива
фактически является адресом его первого элемента! Поскольку передается на¬
чальный адрес массива, вызываемая функция точно знает, где хранится этот
массив. Таким образом, когда вызываемая функция изменяет в своем теле эле¬
менты
массива,
ячейках
она
изменяет
подлинные
массива
элементы
в
их
исходных
памяти.
Рис. 6.12 демонстрирует,
сом его первого элемента,
что имя массива
действительно
выводя значения array и
цией преобразования %p
&array[0]
специальной спецификацией
является адре¬
со
специфика¬
для вывода адресов.
Спецификация преобразования % p обычно выводит адреса виде шестнадцате¬
ричных чисел. Шестнадцатеричные (основание 16) числа состоят из цифр от 0
до 9 и букв от А до F. Они часто используются для сокращенной записи боль¬
целочисленных значений. В приложении Д дано подробное описание
взаимоотношений между двоичными (основание 2), восьмеричными (основа¬
ших
ние
8),
десятичными
(основание 10;
ричными целыми числами.
и
&array[0]
граммы
стандартные целые числа)
Выходные
имеют одно и то же
значение,
а именно
FFF0. Вывод этой про¬
является системно-зависимым, но адреса всегда
Совет по повышению
и шестнадцате¬
данные показывают, что как array, так
будут
совпадать.
эффективности 6.4
массивов посредством имитации передачи по ссылке имеет смысл по сооб¬
ражениям эффективности. Если бы массивы передавались по значению, передава¬
лась бы копия каждого элемента. Для больших часто передаваемых массивов это
было бы расточительно по времени и приводило бы к расходу значительного объема
Передача
памяти
для
Общее
методическое замечание 6.2
хранения
их
копий
Возможна передача массива по значению с помощью простого приема,
сывается
в
главе
10.
который
опи¬
264
Глава 6
/ * Имя массива это
#include <stdio.h>
mam
то
же
что
самое,
&array[0]
*
/
()
{
char
array[5];
pnntf("
return
array
&array[0]
%p\n",
=
&array[0]);
0;
=
FFF0
=
FFF0
Рис. 6.12. Имя массива
Хотя
%p\n&array[0]
=
array
array,
это то же самое, что и адрес его первого элемента
в целом массивы передаются посредством имитации передачи пара¬
метра по ссылке, отдельные элементы массива передаются по значению, точно
так
же,
как
называются
простые
переменные.
скалярами
цию элемента массива
главе
7
мы
скаляров
используйте
массива.
передаче в функ¬
При
в качестве параметра его имя и индекс.
способы имитации передачи параметра
отдельных переменных и элементов
параметров
передача
быть
(т.е.
того, чтобы
Для
списке
покажем
Такие простые одиночные порции данных
скалярными величинами.
или
функция при обращении
этой
Например,
быть
должно
функции
заголовок
ссылке
могла принять массив,
указано,
что
в
ожидается
функции modifyArray
для
В
для
массива).
ней
к
по
мог
бы
записан как
void
modifyArray(int b[],
int
Список параметров показывает,
size)
что
функция modifyArray
ожидает прие¬
ма массива целых чисел в параметре b и числа элементов массива в параметре
size.
Размер
компилятор
массива
его
в
скобках
игнорирует.
в
заголовке
Поскольку
требуется. При
не
массивы
его
автоматически
указании
передаются
функ¬
b, она на самом деле ссылается на фактически су¬
ществующий массив в вызывающей функции (массив hourlyTemperatures в
предыдущем вызове). В главе 7 мы введем другие обозначения для указания
путем имитации передачи параметра по ссылке, то, когда вызываемая
ция использует имя массива
того,
что
функция принимает
близкой
ваны на
Обратите
массив.
Мы увидим,
что эти
обозначения
связи между массивами и указателями в языке
осно¬
С.
необычный внешний вид прототипа функции modi¬
внимание на
fyArray
void modifyArray(mt
Этот прототип
void
однако,
можно
[],
int);
было бы
записать в виде
modifyArray(mt anyArrayName[],
как мы узнали в главе
int
5, компилятор
anyVanableName);
языка
С игнорирует
имена пере¬
менных в прототипах.
Хороший
стиль программирования 6.6
Некоторые программисты
включают
имена
в прототипы функций для
Компилятор игнорируетэти имена
переменных
того, чтобы сделать программы более понятными
Массивы
265
Не забывайте, что прототип
ров
и типе каждого из них
редаваться).
В программе
(в
сообщает
компилятору о количестве парамет¬
том порядке, в котором эти параметры
на рис. 6.13 показано различие между
передачей
будут
пе¬
целого мас¬
сива и элемента массива. Сначала программа выводит пять элементов цело¬
численного массива а. Затем массив а и его размер передаются в
difyArray,
которой каждый
в
main происходит повторный вывод
в программе
выводимые данные,
функцию
mo-
2. После этого
Как показывают
элемент массива а умножается на
массива а.
функция modifyArray действительно
изменила элементы
Теперь программа выводит значение a[3] и передает его в функцию
modifyElement. Функция modifyElement умножает свой параметр на 2 и вы¬
водит новое значение. Обратите внимание, что когда элемент a[3] повторно
массива а.
выводится в функции main, его значение остается прежним, поскольку отде¬
льные элементы массива передаются по значению.
В ваших программах могут возникать ситуации, когда функция не дол¬
жна изменять элементы массива. Поскольку массивы всегда передаются пу¬
ве с трудом поддается контролю.
модификация значений в масси¬
Для предотвращения изменения в функции
значений
в
тем имитации передачи параметра по ссылке,
элементов
массива
С
языке
специальный
предусмотрен
Когда параметру-массиву предшествует const, эле¬
становятся константами в теле функции и любая ее попытка
типа const.
модификатор
менты массива
изменить элемент массива приводит к
ошибке времени компиляции. Это дает
возможность программисту исправить программу таким
образом, чтобы
она не
Хотя модификатор const строго опреде¬
системах С способы его поддержки различ-
пыталась изменять элементы массива.
лен в стандарте
I
/
*
ANSI,
в различных
в
функцию
<stdio.h>
SIZE 5
Передача
#include
#define
массива
void modifyArray(mt [],
void modifyElement(mt);
mam
и
отдельного
/*
int);
элемента
выглядит
массива
необычно
*/
()
{
int a[SIZE]
mt i;
=
1,
{0,
2,
3,
4};
of passing entire array call
"by reference: \n\nThe values of the
"original array are:\n");
"
pnntf("Effects
for
0; i <= SIZE
(i
printf("%3d", a[i]);
=
-
pnntf ("\n") ;
modifyArray(a,
SIZE);
pnntf("The values
for
(i
=
0;
i
<=
pnntf("%3d",
Рис. 6.13. Передача
в
of
SIZE
/*
1;
i++)
массив
.
а
передается
the modified array
-
1;
"
are:
по
ссылке
*/
\n");
i++)
a[i]);
функцию
массива и его отдельного элемента
(часть 1
из
2)
*
/
266
Глава 6
of passing
printf("\n\n\nEffects
call
array element
"by value:\n\nThe value of a[3] is %d\n",
modifyElement(a[3]);
printf("The value of a[3] is %d\n", a[3]);
return
void
"
a[3]);
0;
int size)
modifyArray(int b[],
{
int j;
for
(j
0;
=
b[}l
*=
<=
j
2;
-
size
1;
j++)
}
void modifyElement(int e)
{
printf("Value in modifyElement
is
%d\n",
e
*
=
2);
}
Effects of passing entire array call by reference:
The values of the original array
0
1
The
2
values
0
2
Effects
The
3
4
of
the
4
6
8
modified
of
passing array
of a[3] is 6
in modifyElement
The value of a[3] is 6
array
element
are:
are:
call
by value:
value
Value
Рис. 6.13.
Передача
в
функцию
6.14 демонстрирует
Рис.
is
12
массива и его отдельного элемента
описатель
const.
(часть 2
из
2)
Функция tryToModifyArray
определена с параметром const intb[], который указывает, что массив b явля¬
ется константрым и не может быть изменен. Результатом работы программы
сообщения об ошибках (компилятор Borland С++). Каждая из трех
функции изменить элементы массива приводит к ошибке компилято¬
являются
попыток
ра
«Невозможно
изменить константный
будет обсуждаться
в главе
Общее методическое
замечание 6.3
К
в
параметру-массиву
const, чтобы
не
объект». Модификатор const
определении
допустить изменения
функции может применяться модификатор
функции исходного массива Это являет¬
в теле
ся еще одним примером принципа наименьших
привилегий. Функциям
давать возможности изменять массивы, если только это
обходимым.
снова
7.
не является
не следует
совершенно не¬
Массивы
/
267
*
Демонстрация модификатора
#include <stdio.h>
void tryToModifyArray(conat
типа
int
const
*
/
[]);
main ()
int a[]
20,
{10,
=
30};
tryToModifyArray(a);
printf("%d %d %d\n",
return
a[0],
a[l],
a[2]);
0;
tryToModifyArray(conat int b[])
void
{
b[0]
/
b[l]
b[2]
/
=
/
=
=
ошибка
ошибка
/*
/*
/*
2;
2;
2;
ошибка
*/
*/
*/
}
FIG6_14.С:
FIG6_14.C 16: Cannot modify а
Error FIG6_14.C 17: Cannot modify а
Error FIG6_14.C 18: Cannot modify а
Warning FIG6_14.C 19: Parameter 'b'
Compiling
Error
const object
const object
const object
is never used
Рис. 6.14. Демонстрация модификатора типа const
6.6.
Сортировка
массивов
Сортировка данных (т.е. расположение данных в некотором специальном
порядке, например, по возрастанию или убыванию) является одним из наибо¬
лее важных применений компьютера. Банк сортирует все чеки по номерам
счетов, чтобы в конце каждого месяца можно было подготовить личные бан¬
ковские декларации.
Телефонные компании сортируют списки своих счетов
фамилий, по именам, чтобы облегчить поиск телефон¬
ных номеров. Практически каждой организации приходится сортировать ка¬
большие массивы данных). Сорти¬
кие-нибудь данные (и во многих случаях
ровка данных представляет собой захватывающую проблему, требующую
самых интенсивных усилий исследователей в области информатики. В этой
главе мы обсудим, возможно, самую простую из известных схем сортировки. В
по
фамилиям
и, внутри
упражнениях и в главе 12 мы исследуем более сложные схемы, которые явля¬
ются намного более эффективными.
Совет
по повышению
эффективности
Часто наиболее простые алгоритмы
стота написания,
тестирования
работают
и отладки.
требуются более
фективности
часто
Программа
на рис.
6.5
сложные
6.15 сортирует
массива а в порядке их возрастания.
плохо.
Их достоинством является про¬
Однако для достижения максимальной эф¬
алгоритмы
значения элементов десятиэлементного
Используемая
нами методика называется
268
Глава 6
пузырьковой сортировкой или сортировкой погружением, поскольку меньшие
значения, подобно воздушным пузырькам в воде, постепенно «всплывают» в
верхнюю часть массива, в то время как большие значения опускаются вниз.
требует
Метод
стающем
Если
На каждом проходе сравнива¬
нескольких проходов по массиву.
Если
ются последовательные пары элементов.
(или
порядке
если
их
элементы расположены в возра¬
совпадают), мы
убывающем порядке,
ничего
значения
элементы пары расположены в
не
меняем.
их значения в мас¬
сиве меняются местами.
I
/* Программа сортирует
#include <stdio.h>
#define SIZE 10
main()
{
int
a[SIZE]
int i,
=
pass,
{2, б,
hold;
printf("Data items
for
0; i <=
(i
pnntf ( "%4d"
=
,
for
(pass
for
(i
if
значения
4,
in
SIZE
a[i] )
8,
1;
-
pass
<=
=
0;
i
SIZE
=
a[i]
a[i
=
+
89,
возрастания
68,
37};
45,
*/
i++)
-
SIZE
-
>
(a[i]
12,
порядке
;
1;
hold
10,
в
original order\n");
=
<=
массива
a[i + 1])
a[i];
а[i + 1];
hold;
1]
2;
1;
pass++)
I++)
/*
/*
{
/*
проходы */
/*
один проход */
*/
одно
сравнение
одна
перестановка
*/
=
}
\nData
printf("
for
items
0; i <= SIZE
(i
pnntf ( "%4d" a[i] ) ;
=
in
-
ascending order\n");
1;
i++)
printf("\n");
return 0;
}
Data
2
Data
2
items
6
items
4
in original order
4
8
10
12
89
68
45
37
in ascending order
10
12
6
8
37
45
68
89
Рис. 6.15. Сортировка массива пузырьковым методом
a[0] и a[l], потом a[l] и a[2], потом a[2] и
a[8] и a[9] не завершит проход. Обратите вни¬
Сначала программа сравнивает
a[3]
и так далее, пока
сравнение
10 элементов, выполняется только девять сравне¬
ний. Из-за способа проведения последовательных сравнений большое значе¬
мание, что хотя существует
ние за один проход может переместиться в массиве на много
позиций
вниз, а
небольшое значение может переместиться только на одну позицию вверх. За
Массивы
269
проход самое большое значение гарантированно опускается в нижний
элемент массива a[9]. За второй проход второе по величине значение гаранти¬
рованно опускается в a[8]. За девятый проход девятое по величине значение
оказывается в a[l]. В результате этого самое маленькое значение остается в
первый
a[0],
так
что
для
сортировки
массива
необходимо
только
девять
проходов,
даже если он состоит из десяти элементов.
Сортировка
выполняется посредством вложенного цикла
обходимости перестановки
hold
=
[i]
a[i
=
а
+
она выполняется путем трех
for. В случае
не¬
присваиваний
а[i];
+
а [ i
1 ] ;
hold;
1]
=
где в дополнительной
ниваемых значений.
переменной hold
Перестановка не
временно хранится одно из двух обме¬
может
быть
выполнена
только
двумя
присваиваниями
а [ i ]
а
[
i
=
+
а
1
[
]
i
+
=
а
1
[
i
]
]
;
;
Если, например, a[i] равно 7
вания оба значения
будут
и a[i + 1] равно 5, то после первого присваи¬
равны 5 и значение 7 будет потеряно. Отсюда необ¬
переменной hold.
пузырьковой сортировки
ходимость в дополнительной
Главным
является то, что ее легко
запрограммировать. Однако пузырьковая сортировка выполняется медленно.
Это становится очевидным при сортировке больших массивов. В упражнениях
мы разработаем более совершенные версии пузырьковой сортировки. В насто¬
достоинством
ящее время уже разработаны гораздо более эффективные методы сортировки,
чем пузырьковая. Позже мы исследуем некоторые из них. В углубленных кур¬
сах обучения программированию методы сортировки и поиска рассматривают¬
ся более полно.
6.7.
Пример:
медианы
и
среднего значения,
наиболее вероятного значения с
вычисление
использованием массивов
Теперь рассмотрим более значительный пример. Компьютеры
часто при¬
сбора
результатов различных исследований и опросов
общественного мнения. Программа на рис. 6.16 использует массив response,
инициализированный 99-ю (см. символическую константу SIZE) ответами
опроса. Каждый из ответов представлен числом от 1 до 9. Программа вычисля¬
меняются для
и анализа
ет среднее, медиану и
Средним
Функция
наиболее вероятное
значением называется
из
99 значений.
среднеарифметическое
этих
99 значений.
mean вычисляет среднее значение путем суммирования
тов и деления результата на
99
элемен¬
99.
Медианой называется «серединное значение». Функция median определя¬
ет медиану путем вызова функции bubbleSort для сортировки массива ответов
в порядке возрастания и выбора среднего элемента answer[SIZE / 2] отсорти¬
рованного массива. Имейте в виду, что в случае четного числа элементов меди¬
ана должна вычисляться как среднее значение двух «серединных» элементов.
Функция median в настоящее время не обеспечивает такой возможности.
вывода массива response вызывается
функция printArray.
Для
270
Глава 6
/*
Эта
программа производит
Она
вычисляет
анализ
значение,
среднее
данных
опроса.
и
медиану
наиболее
вероятное
*/
#include <stdio.h>
#define SIZE 99
значение
void mean(int []);
void median(int []);
void mode(int [], int
[]);
[]);
void bubbleSort(int
void printArray(int
main ()
{
int
[]);
frequency[10]
=
response[SIZE
=
{0},
{6,
7,
6,
7,
6,
7,
5,
7,
7,
4,
7,
8,
7,
8,
7,
8,
6,
8,
4,
5,
9
8
8,
9,
8,
9,
5,
9,
8,
9,
3,
7,
8,
9,
8,
7,
8,
9,
8,
7,
8,
7,
8,
9,
8,
9,
8,
9,
7,
8,
9
8,
7,
8,
7,
9,
8,
9,
2
7
9,
8,
9,
8,
9,
7,
5,
3
7,
2,
5,
3,
9,
4,
6,
4
9,
б,
8,
7,
8,
9,
7,
8
4,
2,
5,
3,
8,
7,
5,
6,
1,
б,
5,
7,
8,
7 }
б
,
mean(response);
median(response);
mode(frequency,response);
return 0;
void mean(int answer[])
{
0 ;
int j, total
=
"
printf ("%s\n%s\n%s\n",
for
"********'',
Mean",
"******
(j=0; j<=SIZE-l; j++)
total += answer[j];
printf("The mean is the
"items. The mean
"all
"of
the
data
data
items
items
"this
run
SIZE,
total,
is
average value of the data\n"
is equal to the total of\n"
:
%d
SIZE,
/
by the number \n"
mean value for \n"
devided
(%d).
The
%d
%.4f\n\n",
(float) total / SIZE);
=
}
void median(int answer[])
{
printf("\n%s\n%s\n%s\n%s",
"********>i^
Mean",
ч
n********»»^
"The unsorted array of responses is");
printArray(answer);
Рис. 6.16. Программа
анализа данных опроса
(часть 1
из
3)
Массивы
271
bubbleSort(answer);
printf("\n\nThe sorted array
is");
printArray(answer);
pnntf("\n\nThe median
"the
"For
is element %d of \n"
sorted %d element array.\n"
this run the median is %d\n\n",
SIZE / 2,
SIZE,
void mode(int freq[],
int
answer[SIZE / 2]);
answer[])
{
largest=0,
modeValue=O;
printf("\n%s\n%s\n%s\n",
"********»1^
Mode",
"********");
int
rating,
h,
j,
П
for
(rating=l;
freq[rating]
for
(j=0;
j
<=
9;
<=
rating
0;
rating++)
=
SIZE-1;
}++)
++freq[answer[j]];
printf("%s%lls%19s\n\n%54s\n%54s\n\n",
"Response","Frequency","Histogram",
"1
2
1
0
5
0
5");
2", "5
for
(ratmg=l;
rating
if
9;
<=
",
printf("%8d%lld
rating++) {
rating, freq[rating]);
(freq(rating) > largest)
largest
freq[rating];
modeValue
rating;
{
=
=
}
for
h
(h =1;
<=
freq[rating];
h++)
printf ("*");
printf ("\n");
}
printf("The mode is the most frequent value.\n"
"For this run the mode is %d which occured"
"
%d
times.\n", modeValue, largest);
}
void bubbleSort(int a[])
{
int pass,
j, hold;
for
(pass=l;
for
(j=0;
if
pass
j
(a[ ]
<=
>
<=
SIZE-1;
j++)
SIZE-2;
a[j+l])
Рис. 6.16. Программа
pass++)
{
анализа данных опроса
(часть 2
из
3)
272
Глава 6
hold
=
a[j]
а
a[j];
a[j+l]
=
[ j +1]
void pnntArray(int
j
int
for
;
hold;
=
a[])
;
(j=0;
if
j
<=
{
]++)
SIZE-1;
0)
(j % 20
pnntf ( "\n" ) ;
==
pnntf("%2d",
a[j]);
Рис. 6.16. Программа анализа данных опроса (часть 3 из 3)
Наиболее вероятным значением называется значение, которое наиболее
часто встречается среди 99 ответов. Функция mode определяет наиболее веро¬
ятное значение путем подсчета числа ответов каждого типа и выбора из них
значения
наибольшей суммой. Эта версия функции mode
с
не
обрабатывает
упражнение 6.14). Функция строит так¬
же гистограмму для помощи в определении наиболее вероятного значения гра¬
фически. На рис. 6.17 содержится пробный прогон этой программы. Этот при¬
случай
нескольких равных сумм
(см.
мер включает в себя большинство стандартных методик, обычно
требующихся^
в задачах, связанных с массивами, в том числе передачу массива в
функцию.
Mean
********
The
mean
items.
all
is
The
the
the
data
items
of data items
for
this
run
is
equal
to
the
681
the
data
total
of
byy the number
divided
(99).
is:
of
value
average
Mean
The mean value
6.8788
/ 99
=
********
I* *
Median
* * * * * * * *
The
unsorted array
8 9 8 7 8 9 8
of
7
8
7
6
7
8
9
3
9
8
7
8
7, 7
8
9
8
9
8
9
7
8
9
6
7
8
7
8
7
9
8
9
2
7
8
9
8
9
8
9
7
5
3
5
6
7
2
5
3
9
4
6
4
7
8
9
6
8
7
8
9
7
8
7
4
4
2
5
3
8
7
5
6
4
5
6
1
6
5
7
8
7
The
sorted
9
7
responses is
8 9 5 9 8 7
6
8
2
2
2
3
array
3 3 3
is
1
4
4
4
4
4
5
5
5
5
5
5
5
5
6
6
6
6
6
6
6
6
6
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
7
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
8
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
9
Рис. 6.17. Пример выполнения программы анализа данных опроса (часть 1 из 2)
Массивы
273
The median is element 49 of
the sorted 99 element array.
For this run the median is 7
*******
Mode
********
Response Frequency
Histogram
1
0
5
2
0
1
5
2
5
1
1
2
3
3
4
* * * *
4
5
* * ** *
5
8
* * ** ** * *
6
9
*********
7
23
* ** * * * * * * * * ** * * * * * * * * * * *
8
27
****************************
9
19
* ** * * * * * * ** * * * ** * * **
The mode
is
For
run
this
*
***
the most
the mode
frequent value.
is 8 which occurred
27
times.
Рис. 6.17. Пример выполнения программы анализа данных опроса (часть 2 из 2)
6.8. Поиск
Программисту
часто приходится
хранимых
в массивах.
ли массив
элемент,
Может
в массивах
работать
оказаться
соответствующий
с
большими объемами данных,
необходимым определить, содержит
некоторому ключевому значению.
Про¬
цесс нахождения определенного элемента массива называется поиском. В этом
разделе мы
обсудим два метода поиска
простой метод линейного поиска
(или последовательного перебора) и более эффективный метод двоичного по¬
иска. В упражнениях 6.34 и 6.35 в конце этой главы вам будет предложено ре¬
ализовать версии линейного
При линейном
массива с ключом
и двоичного поиска с помощью
рекурсии.
(рис. 6.18) происходит сравнение каждого элемента
поиска. Поскольку массив не упорядочен особым образом,
поиске
вероятность нахождения требуемого значения в первом и в последнем элемен¬
тах массива одинакова. Таким образом, в среднем программа должна будет
сравнить ключ поиска с половиной элементов массива.
Метод линейного поиска хорошо работает для небольших или несортиро¬
ванных массивов.
сортированного
Однако
массива
для больших массивов он
может
быть
использован
неэффективен. В случае
высокоскоростной
метод
двоичного поиска.
При
двоичном алгоритме поиска после каждого сравнения исключается по¬
ловина элементов массива, в котором проводится поиск.
Алгоритм
находит сред¬
ний элемент массива и сравнивает его с ключом поиска. Если они равны, ключ
возвращается индекс этого элемента. Если они не
поиска
в одной половине массива.
равны, задача упрощается до
Если ключ поиска меньше среднего элемента массива, поиск производит¬
поиска считается
найденным
и
ся в
первой
рой
половине. Если ключ поиска в указанном подмассиве
половине массива, в противном случае поиск производится во вто¬
(части
первоначаль-
274
ного
Глава 6
массива)
найден, алгоритм повторяется
не
для четверти массива. Поиск
продолжается до тех пор, пока ключ не окажется равен среднему элементу
подмассива или пока подмассив не
ного ключу
(это
будет
^н /*
Последовательный перебор
#include <stdio.h>
#define SlZE 100
int
lmearSearch(int
main()
{
int
a[SIZE],
for
а
(x
[x]
состоять из одного элемента, не рав¬
означает, что ключ поиска не
0;
=
=
x,
*
int,
-
SIZE
*/
массива
searchKey,
<=
x
2
[],
1;
найден).
int);
element;
/*
x++)
порождает
pnntf("Enter integer search key:\n");
scanf("%d", &searchKey);
element
mt linearSearch(a,
searchKey,
=
if
!=
(element
данные
*/
SIZE);
-1)
pnntf("Found value
else
not
pnntf("Value
return
какие-то
x ;
m
element
%d\n",
element);
found\n");
0;
}
int
array[],
lmearSearch(mt
mt
key,
int size)
{
int
for
n;
(n
if
=
0;
n
return
return
<=
(array[n]
size
==
-
1;
n++)
key)
n;
-1
}
integer search key:
36
Found value in element 18
Enter
Enter
integer
search
key:
37
Value
not
found
Рис. 6.18. Последовательный
В наихудшем случае двоичный
перебор
массива
поиск в массиве из
1024
элементов потре¬
бует только 10 сравнений. Последовательное деление 1024 на 2 дает значения
512, 256, 128, 64, 32, 16, 8, 4, 2 и 1. Число 1024 (210) до получения значения,
равного 1, делится на 2 только десять раз. Деление на 2 соответствует одному
сравнению в алгоритме двоичного поиска. Чтобы найти ключ
1048576 (220) элементов, потребуется максимум 20 сравнений.
в
массиве
из
В
массиве
из
275
Массивы
элементов
миллиарда
потребуется
максимум
сравнений. Это
30
является
в сравнении с последовательным пере¬
огромным увеличением эффективности
бором, который требует в среднем сравнения ключа поиска с половиной эле¬
ментов массива. Для массива из миллиарда элементов разница между 500
миллионами сравнений (в среднем) и максимум 30 сравнениями очевидна!
Максимальное число сравнений для любого массива можно определить путем
нахождения первой степени 2, превосходящей число элементов в массиве.
На рис. 6.19 представлена итеративная версия функции binarySearch.
целочисленный массив b, целочис¬
Функция принимает четыре параметра
ленную переменную searchKey, индекс массива low и индекс массива high.
Если
ключ поиска не соответствует среднему элементу подмассива, один из ин¬
дексов
low
водить
в меньшем подмассиве.
или
high
изменяется таким
Если
образом, чтобы
поиск можно
было про¬
ключ меньше среднего элемента, индексу
1 и поиск продолжается для элементов от
присваивается значение middle
low до middle -1. Если ключ больше среднего элемента, индексу low присваива¬
ется значение middle + 1 и поиск продолжается для элементов от middle + 1 до
high
high.
-
В программе используется
восходящая
массив из
15
элементов.
число элементов в этом массиве, равна
16
Первая
(24),
степень
2, пре¬
поэтому для нахож¬
потребуется максимум 4 сравнения. В программе используются
printHeader для вывода индексов массива и printRow для вывода
подмассива, возникающего в процессе двоичного поиска. Средний эле¬
дения ключа
функции
каждого
мент в каждом подмассиве отмечен
звездочкой (*), указывающей
на элемент, с
которым сравнивается ключ поиска.
I
/* Двоичный
в
поиск
массиве
*/
#include <stdio.h>
#define SIZE 15
int binarySearch(int [], int, int, int);
void printHeader(void);
void printRow(int [], int, int, int);
main()
{
int
a[SIZE],
for
а
(i
[i]
0;
=
=
2
i,
i
<=
*
i;
prmtf("Enter
scanf("%d",
а
key,
result;
SIZE
number
1;
-
i++)
between
0
and
28:
");
&key);
printHeader();
result
binarySearch(a,
=
key,
0,
SIZE
-
1);
if
(result != -1)
pnntf("\n%d found in array element
else
printf("\n%d not found\n", key);
%d\n",
key,
result);
return 0;
}
Рис. 6.19. Двоичный
поиск в сортированном массиве
(часть 1
из
3)
Глава 6
276
I
int
int
binarySearch(int b[],
searchKey,
int
low,
int
high)
{
int middle;
while
<=
(low
middle
{
high)
high)
+
(low
=
middle,
low,
printRow(b,
if
/ 2;
high);
b[middle])
(searchKey
return middle;
else if (searchKey < b[middle])
middle
1;
high
==
=
-
else
low
=
+
middle
1;
}
return
ключ
/*
-1;
не
найден
*/
выводимых
данных
*/
поиска
}
I
/*
Печатает
void
заголовок
для
printHeader(void)
{
int
i;
"
printf (
for
(i
=
\nSubscnpts : \n" ) ;
0;
i
<=
-
SIZE
1;
i + +
)
i);
",
printf("%3d
printf("\n");
for
(i
=
1;
i
<=
*
4
SIZE;
i+
+)
printf("-");
printf("\n");
}
/*
Выводит
в
(i
if (i
=
0;
<
i
low
printf ("
else if (i
<=
SIZE
i
||
==
массива,
настоящее
void printRow(int b[],
{
int i;
for
показывающих часть
*/
время.
int low, int mid, int
строку данных,
обрабатываемую
>
-
1;
high)
i++)
high)
");
mid)
printf("%3d*",
else
b[i]);
printf("%3d ",
b[i]);
/*
отмечает
среднее
значение
printf("\n");
}
Рис. 6.19. Двоичный
поиск в сортированном массиве
(часть 2
из
3)
*/
Массивы
277
Enter
а
number
between
0
and
28:
7
25
Subscripts:
0
1
2
3
4
5
6
0
2
4
6
8
10
12
14*
8
9
10
11
12
13
14
16
18
20
22
24
26
28
16
18
20
22*
24
26
28
24
26*
28
24*
25
not
Enter
found
а
number
between
0
and
28:
7
8
Subscripts:
0
1
2
3
4
5
6
0
2
4
6
8
10
12
0
2
4
6*
8
10
12
8
10*
12
8
9
10
11
12
13
14
14*
16
18
20
22
24
26
28
and
28:
6
7
8
9
10
11
12
13
14
16
18
20
22
24
26
28
8*
8
found
Enter
a
in
element
array
number
between
0
4
Subscripts:
0
6
1
2
3
4
5
6
0
2
4
6
8
10
12
0
2
4
6*
8
10
12
found
in
element
array
Рис. 6.19.
Двоичный
6.9.
Массивы
в
С могут
14*
3
поиск в сортированном массиве
Многомерные
(часть 3
из
3)
массивы
иметь несколько индексов.
Многомерные
массивы час¬
состоящих из значений, упорядо¬
таблиц,
столбцам. Для идентификации конкретного элемента
таблицы мы должны указать два индекса: первый, идентифицирующий (по
соглашению) строку элемента, и второй, идентифицирующий столбец. Табли¬
цы, или массивы, требующие двух индексов для идентификации данного эле¬
мента, называются двумерными массивами. Обратите внимание, что много¬
мерные массивы могут иметь более двух индексов. Стандартом ANSI установ¬
лено, что система ANSI С должна поддерживать по крайней мере 12 индексов
то применяются для представления
ченных по строкам и
массива.
Рис. 6.20 иллюстрирует
двумерный массив а. Массив содержит три строки
столбца, поэтому говорят, что это массив 3 на 4. В общем случае, мас¬
сив с m строками и n столбцами называется массивом m на п.
На рис. 6.20 каждый элемент массива а идентифицируется как a[i][j];
здесь а имя массива, i и j его индексы, которые однозначно определяют каж¬
дый элемент в массиве а. Обратите внимание, что у всех элементов первой
строки массива первый индекс равен 0; у всех элементов четвертого столбца
массива второй индекс равен 3.
и четыре
278
Глава 6
Столбец
Столбец
Столбец
Столбец
1
0
2
3
Строка 0
a[0] [0]
a[0][1]
a[0] [2]
a[0][3]
Строка 1
a[l][0]
a[l][1]
a[l] [2]
a[l] [3]
Строка 2
a[2] [0]
a[2][1]
a[2][2]
a[2][3]
Индекс столбца
Индекс строки
Имя
Двумерный
Рис. 6.20.
массива
массив с тремя строками и четырьмя
столбцами
Распространенная ошибка программирования 6.9
Неправильная
ссылка
Многомерный
ван при его
на
массив,
элемент двумерного
массива
подобно одномерному,
объявлении. Например, двумерный
a[x][y]
может
массив
в
виде
a[x,y]
быть инициализиро¬
может быть объ¬
b[2][2]
явлен и инициализирован посредством
mt
1
и
b[2] [2]
{{1,
-
2},
{3,
4}};
Значения группируются в фигурных скобках по строкам. Таким образом,
2 инициализируют b[0][0] и b[0][l], 3 и 4 инициализируют b[l][0] и
b[l][l]. Если
для
данной строки
недостаточно инициализирующих
оставшиеся ее элементы инициализируются нулями. Таким
значений,
образом, объявле¬
ние
mt
b[2] [2]
=
{{1},
{3,4H;
инициализировало бы b[0][0] единицей,
b[0][l]
нулем,
b[l][0] тройкой
и
b[l][l]
четверкой.
Рис. 6.21
явлении.
демонстрирует инициализацию двумерных массивов при их объ¬
В программе объявляются три массива с двумя строками и тремя
столбцами (каждый
предусматривает
Первый
и
5
элементов). Объявление
инициализирующих
значений
в
массива
двух
arrayl
подсписках.
1, 2
4,
arrayl убрать фи¬
подсписок инициализирует первую строку массива значениями
3; второй
и
состоит из шести
шесть
6. Если
подсписок инициализирует вторую строку массива значениями
из списка инициализирующих
значений
массива
гурные скобки, в которые заключен каждый подсписок, компилятор автома¬
тически проинициализирует элементы первой строки, а затем элементы вто¬
строки. Объявление массива array2 предусматривает пять инициализиру¬
ющих значений. Инициализирующие значения присваиваются сначала пер¬
вой строке, затем второй строке. Все элементы, для которых нет явного ини¬
рой
циализирующего значения, автоматически инициализируются нулями, поэто¬
му array2[l][2] инициализирован 0. Объявление массива array3 предусматри¬
вает три инициализирующих значения в двух подсписках. Подсписок для пер¬
вой строки явно инициализирует первые два элемента первой строки едини¬
цей
и
двойкой. Третий
элемент автоматически инициализируется нулем.
Под¬
Массивы
279
список для второй строки явно инициализирует первый элемент четверкой.
Последние два элемента автоматически инициализируются нулями.
/* Инициализация многомерного
#include <stdio.h>
void printArray(int
mam
массива
*/
[][3]);
()
{
int arrayl[2][3]
{
array2[2][3]
{
array3[2][3]
=
=
{ {1, 2, 3}, {4,
1, 2, 3, 4, 5 },
{1, 2}, {4} };
=
in arrayl
5,
6}
by
row
are:\n");
printf("Values in array2 by
printArray(array2);
row
are:\n");
printf("Values in array3 by
printArray(array3);
row
are:\n");
printf("Values
},
printArray(arrayl);
return 0;
void printArray(int a[][3])
{
int i,j;
for
(i
for
=
0;
i
<=
0; j
(j
printf("%d
=
1;
i+
<=
2;
",
a[i][j]);
+)
{
j++)
printf("\n");
}
Values in arrayl by
1
2
3
4
5
6
Values
1
2
3
4
5
0
Values
1
2
0
4
0
0
are:
in
array2 by
row
are:
in
array3 by
row
are:
Рис. 6.21.
Для
row
Инициализация многомерных
массивов
вывода элементов каждого массива в программе вызывается
функция
printArray. Обратите внимание, что в определении функции параметр-массив
специфицирован как int a[][3]. Когда функция принимает в качестве парамет¬
ра одномерный массив, в списке параметров функции скобки, относящиеся к
массиву,
остаются
незаполненными.
Первый
индекс многомерного
массива
280
Глава 7
требуется, однако все последующие индексы необходимы. Компиля¬
тор использует эти индексы для определения местонахождения в памяти эле¬
также не
ментов многомерных массивов.
Все
элементы массива хранятся в памяти по¬
независимо от количества индексов.
следовательно,
первой строкой
В двумерном
массиве за
в памяти следует вторая строка.
Указание значений индексов при объявлении параметров функции дает
сообщить ей о способе отыскания элементов в мас¬
сиве. В двумерных массивах каждая строка по существу является одномер¬
возможность компилятору
определения местонахождения элемента в конкретной
должен
знать, сколько в точности элементов находится в
строке компилятор
каждой строке, чтобы при обращении к этому массиву он мог пропустить соот¬
ным
Для
массивом.
ветствующее число
a[X][2]
блоков
памяти. Таким
образом,
при
обращении
в нашем примере компилятор знает, что для того,
второй строки (строка 1), ему нужно пропустить
строки.
После этого компилятор
обращается
к элементу
чтобы добраться до
в памяти три элемента
к третьему элементу
первой
этой строки (эле¬
2).
Во многих
мент
манипуляциях с массивами обычно используется структура по¬
for. Например, следующий оператор устанавливает все элементы в
вторения
третьей строке
for
массива а,
0;
(column
а[2][column]
изображенного
column
=
=
<=
3;
на рис.
6.20,
в нуль:
column++)
0;
Мы устанавливаем третью строку, следовательно, мы знаем, что первый
индекс всегда равен 2 (0 это первая строка, 1
вторая строка). В цикле for из¬
меняется только
второй
индекс
(т.е.
индекс
столбца). Предыдущая
структура
for эквивалентна четырем операторам присваивания:
a[2][0]
=
0;
a[2][1]
=
a[2][2]
a[2][3]
=
0;
0;
0;
=
Следующая
вложенная структура
for подсчитывает сумму
всех элементов
массива а.
total
for
=
(row
for
0;
=
0;
row
<=
2;
row++)
0; column <= 3;
(column
total += a[row][column];
=
column++)
В этой структуре for элементы массива суммируются построчно. Выполне¬
внешней структуры for начинается с установки row (т.е. индекса строки) в
так что при помощи внутренней структуры for могут быть просуммированы
ние
0,
элементы
первой строки. Во внешней структуре for переменная row увеличи¬
вается до 1, так что могут быть просуммированы элементы второй строки. За¬
тем во внешней структуре for переменная row увеличивается до 2, и суммиру¬
ются элементы третьей строки.
В программе на рис. 6.22 с использованием структур for над массивом studentGrades размером 3 на 4 выполняются некоторые другие манипуляции,
обычные для массивов. Каждая строка массива представляет студента, а каж¬
дый столбец представляет оценку за один из четырех экзаменов, которые сту¬
денты сдавали в семестре. Манипуляции с массивами выполняются посредст-
Массивы
281
вом четырех функций. Функция minimum определяет самую низкую оценку
среди всех студентов за семестр. Функция maximum определяет самую высо¬
кую. Функция average определяет для данного студента его среднюю оценку
за семестр.
Функция printArray
выводит
двумерный
массив в
наглядной таб¬
личной форме.
/* Пример
#include
массива
двумерного
<stdio.h>
#define
STUDENTS
#define
EXAMS
*/
3
4
int
minimum(int
[][EXAMS],
int,
int);
int
maximum(int
[][EXAMS],
int,
int);
float
average(int
[],
void printArray(int
int);
[][EXAMS],
int,
int);
main ()
{
int
student,
studentGrades[STUDENTS][EXAMS]
=
68,
87,
90,
{{77,
{ 96,
{70,
86,
89,
86,
printf("The array is:\n");
printArray(studentGrades, STUDENTS, EXAMS);
printf("\n\nLowest grade: %d\nHighest grade:
minimum(studentGrades, STUDENTS, EXAMS),
maximum(studentGrades, STUDENTS, EXAMS));
for
(student
=
printf("The
student
0;
average
student,
<=
STUDENTS
for
grade
-
student
73},
78 }
81}};
,
%d\n",
1; student++)
%d is %.2f\n",
average(studentGrades[student],
EXAMS));
return 0;
}
/* Находит минимальную оценку */
minimum(int grades[][EXAMS],
{
int i,
100;
j, lowGrade
int
int pupils,
int tests)
=
for
(i
for
0;
i
<=
1; i++)
1; j++)
(j
j
if (grades[i][j] < lowGrade)
lowGrade
grades[i][j];
=
=
0;
pupils
<=
-
tests
-
=
return
lowGrade;
}
/*
int
Находит
максимальную
maximum(mt
оценку
*/
grades[] [EXAMS],
int
oupils,
int tests)
{
int i,
j,
highGrade
=
0;
Рис. 6.22. Пример использования двумерных
массивов
(часть 1
из
2)
282
Глава 6
for
(i
for
0;
=
<=
1; i++)
tests
0; j
1; }++)
(j
if (grades[i][j] < highGrade)
i
pupils
highGrade
return
-
<=
=
-
=
grades[i][j];
highGrade;
}
/* Определяет среднюю оценку для
average(int setOfGrades[],
данного
float
int
*/
студента
tests)
{
int
for
total
i,
(i
total
=
return
=
0;
0;
i
+=
setOfGrades[i];
(float)
<=
tests
total
/
1;
-
i++)
tests;
/* Выводит массив */
void printArray(int grades[][EXAMS],
{
int i, j;
Pnntf("
for
[0]
0;
=
(i
i
<=
pupils
[1]
[2]
1;
i++)
-
int pupils,
[ 3 ] ")
",
for
j++)
0;
j <=
printf("%-5d",
=
-
tests
1;
;
{
printf("\nstudentGrades[%d]
(j
int tests)
i);
grades[i][j]);
}
The array is:
studentGrades[0]
studentGrades[1]
studentGrades[2]
[0]
77
96
70
[1]
68
87
[2]
86
[3]
73
89
90
86
78
81
Lowest grade: 68
Highest grade: 96
The average grade for student 0
The average grade for student 0
The average grade for student 0
is
76.00
is
87.50
is
81.75
Рис. 6.22. Пример использования двумерных
Каждая
раметра
из
функций minimum, maximum
массив
и
studentGrades (называемый
массивов
(часть 2
printArray
каждой
из
2)
принимает три па¬
функций grades),
(строк массива) и количество экзаменов (столбцов мас¬
функций по массиву grades выполняется цикл с вложен¬
в
из
количество студентов
сива). В каждой
из
ными структурами
том определения
for. Следующая
вложенная структура является
функции minimum:
фрагмен¬
Массивы
for
283
(i
for
0; i <= pupils
1; i++)
0; j <= tests
1; j++)
(j
if (grades[i][j] < lowGrade)
lowGrade
grades[i][j];
=
-
=
-
=
Выполнение внешней структуры for начинается с установки переменной i
(т.е. индекса строки) в 0, так что в теле внутренней структуры for элементы
первой строки могут сравниваться с переменной lowGrade. Во внутренней
структуре происходит выполнение цикла для четырех оценок данной строки и
сравнение каждой оценки с lowGrade. Если оценка меньше, чем lowGrade, пе¬
ременной lowGrade присваивается значение этой оценки. Затем во внешней
структуре индекс строки увеличивается до 1. С переменной lowGrade сравни¬
ваются элементы второй строки. Затем индекс строки увеличивается до 2, и с
переменной lowGrade сравниваются элементы третьей строки. Когда выполне¬
ние всей структуры заканчивается, в lowGrade содержится наименьшая оцен¬
ка
в
двумерном
массиве.
Функция maximum работает
аналогично
функции
minimum.
одномерный
Функция average принимает два аргумента
нок данного студента с именем
setOfGrades
и число
массив
оце¬
(экзаменов),
элементов
содержащихся в этом массиве.
При вызове функции average первым переда¬
studentGrades[student]. Это приводит к тому,
что в функцию передается адрес одной строки двумерного массива. Параметр
studentGrades[l] представляет собой начальный адрес второй строки масси¬
ва. Не забывайте, что двумерный массив по существу является массивом од¬
ваемым параметром является
номерных массивов и что имя одномерного массива является
Функция
массива в памяти.
щий
адресом
этого
вычисляет сумму элементов массива, делит
об¬
итог на число экзаменов и возвращает результат в виде числа с плаваю¬
щей точкой.
Резюме
В
С
языке
группой
списки
значений хранятся
в
массивах.
логически связанных ячеек памяти.
Их
они имеют одно и то же имя и один и тот же тип.
кретной ячейке,
или
элементу
массива,
Массив
является
связывает то,
что все
Для обращения
мы указываем
имя
к кон¬
массива и
индекс.
Индекс
Если
может
быть целым
или
числом
целочисленным
в качестве индекса используется выражение,
конкретного
элемента
Важно обратить
массива
происходит
оценка
внимание на различие между
индекс равен
6,
в то время как
этого
ссылкой
мент массива и на элемент массива номер семь.
Для
Массивы
тов для
элемент массива номер
массива
int
занимают место в памяти.
целочисленного массива
x,
программист
b[100],
Массив
строк.
типа
b
на
выражения.
седьмой
эле¬
седьмого элемента
7 (фактически это восьмой элемент массива). Это
ником ошибок преждевременного выхода из цикла.
декс
выражением.
то для определения
семь имеет ин¬
является источ¬
Чтобы зарезервировать 100 элемен¬
27 элементов для целочисленного
и
пишет
x[27];
char
может
использоваться
для
хранения
символьных
284
Глава 6
Элементы
быть инициализированы тремя способами:
массива могут
объявления, присваивания
средством
и
по¬
ввода.
Если инициализирующих значений в объявлении меньше, чем элемен¬
тов в массиве, С автоматически инициализирует оставшиеся элементы
нулями.
С допускает
ссылки
на
Для инициализации
строковый литерал.
Все строки
нием
в
языке
Символьные
за
символьного
С
в
может
быть
константы
могут
константами
в
К отдельным
символам строки,
списке
хранимой
Представле¬
'\0'.
является
быть инициализированы
инициализирующих значений.
массивы
использован
символом.
нулевым
символьной
виде
массива.
пределами
массива
заканчиваются
символа
нулевого
элементы
символьными
в массиве, можно
обращаться
непосредственно, пользуясь для этого индексной нотацией.
Строка
быть непосредственно введена
может
клавиатуры при помощи
ния
функции scanf
и
символьный
в
массив
с
спецификации преобразова¬
%s.
Символьный массив, представляющий строку,
помощью
функции printf
может
быть выведен
с
спецификации преобразования %s.
и
Применяйте при локальном объявлении массива ключевое слово static,
с тем чтобы массив не создавался при каждом вызове функции и не раз¬
рушался при каждом выходе из нее.
Массивы, объявленные
циализируются
инициализирует
пилятором
один
с ключевым словом
раз
во
время
статический
static,
компиляции.
массив
явно,
он
автоматически ини¬
Если программист
инициализируется
не
ком¬
нулями.
передачи массива в функцию передается его имя. Для передачи в
функцию одного элемента массива просто передайте имя массива с по¬
Для
следующим
В
индексом
С передача
языке
данного
массива
передачи параметра по ссылке
значения
Имя
элементов
массива
в
является
Поскольку происходит передача
мая
функция
Для
того, чтобы
точно
знает,
где
массивах
ся передача массива.
скобках).
вызывающих
адресом первого
начального
адреса
программ.
массива!
элемента
массива,
вызывае¬
массив.
хранится
функция при обращении
в списке параметров
квадратных
функцию происходит путем имитации
вызываемые функции могут изменять
подлинных
фактически
(в
элемента
в
к
ней
могла принять массив,
этой функции должно быть указано, что ожидает¬
Размер массива в скобках при этом не требуется.
Спецификация преобразования %p обычно
служит для вывода адресов
в виде шестнадцатеричных чисел.
Для
в
предотвращения изменения
языке
функцией значений
С предусмотрен специальный модификатор
параметру-массиву предшествует const,
константами в теле функции и любая
массива
приводит
к
элементы
ее
элементов массива
типа const.
массива
попытка
ошибке времени компиляции.
Когда
становятся
изменить
элемент
Массивы
285
Массив
быть сортирован
может
сортировки.
Делается
с
несколько
метода
применением
проходов
по
массиву.
пузырьковой
При каждом
проходе сравниваются последовательные пары элементов. Если элемен¬
ты расположены по порядку (или если их значения совпадают), ничего
Если
не меняется.
элементы пары расположены не по порядку, их зна¬
чения меняются местами.
нении
с
более
другими,
Для небольших
больших
ровка приемлема, но для
массивов пузырьковая сорти¬
она является
сложными
неэффективной
алгоритмами
в срав¬
сортировки.
При линейном
поиске (последовательном переборе) происходит сравне¬
ние каждого элемента массива с ключевым значением. Поскольку мас¬
сив не упорядочен
го
в
значения
особым образом, вероятность нахождения требуемо¬
первом
Таким образом,
и
в
последнем
элементах
в среднем программа должна
массива
будет
одинакова.
сравнить ключ по¬
иска с половиной элементов массива. Метод последовательного
ра хорошо
работает
перебо¬
для небольших или несортированных массивов.
двоичном алгоритме поиска после каждого сравнения исключается
половина элементов массива, в котором проводится поиск. Алгоритм
При
находит
Если
средний
элемент
массива
декс этого элемента в массиве.
до
и
сравнивает
поиска
в
одной
половине
Если
его
с
ключом
поиска.
найденным
и возвращается ин¬
они не равны,
задача упрощается
они равны, ключ поиска считается
массива.
В наихудшем случае двоичный поиск в массиве из 1024 элементов по¬
требует только 10 сравнений. Чтобы найти ключевое значение в масси¬
ве
В
из
(220)
1048576
массиве
из
элементов,
миллиарда
потребуется максимум 20 сравнений.
потребуется максимум 30 сравне¬
элементов
ний.
Массивы могут
использоваться для представления таблиц, состоящих
значений, упорядоченных по строкам и столбцам. Для указания
конкретного элемента таблицы определяются два индекса: первый,
идентифицирующий (по соглашению) строку, в которой содержится
элемент, и второй, идентифицирующий столбец. Таблицы, или масси¬
вы, требующие для идентификации элемента двух индексов, называют¬
из
ся
двумерными
массивами.
Стандартом установлено, что система
крайней мере 12 индексов массива.
ANSI С должна поддерживать по
его
объявле¬
Когда функция принимает в качестве параметра одномерный
списке параметров функции скобки, относящиеся к массиву,
массив, в
Многомерный
нии
списком
массив
может
быть инициализирован при
значений.
инициализирующих
остаются
незаполненными.
Первый
индекс многомерного массива также не
требуется,
однако все
необходимы. Компилятор использует эти индек¬
сы для определения местонахождения в памяти элементов многомер¬
ных массивов.
последующие индексы
Для
передачи одной строки двумерного массива в функцию, принимаю¬
щую
одномерный
щим
первым
массив,
индексом.
просто
передайте
имя
массива
с
последую¬
286
Глава 6
Терминология
a[i]
a[i][j]
нулевой
анализ
данных
временная
*\0'
общая
сумма элементов массива
объявление массива
опроса
область для обмена
значений
выражение
за
выход
в
качестве
пределы
одномерный массив
ошибка смещения индекса
передача массивов в функции
индекса
массива
передача по ссылке
поиск в массиве
гистограмма
двойная
точность
двумерный
директива
массив
препроцессора
заменяющий
значение
имя
элемент
нуль-символ
#define
текст
проход сортировки
элемента
пузырьковая сортировка
символическая константа
массива
скаляр
индекс
индекс
проверка на выход за пределы
массива
столбца
скалярная величина
индекс строки
сортировка
инициализация массива
квадратные скобки
сортировка погружением
сортировка элементов массива
ключ поиска
спецификатор преобразования
линейный поиск
список
массив
среднее
массив n на m
столбцовая диаграмма
строка
таблица значений
табличная форма
масштабируемость
медиана
многомерный
наиболее
номер
массив
трехмерный
значение
вероятное
инициализации
элемент
позиции
%p
массива
массив
массива
Распространенные ошибки программирования
6.1.
Важно обратить
массива»
и
внимание на различие между
«элементом
массива начинаются с
в то время как
массива
номер
0, «седьмой
семь».
«седьмым элементом
Поскольку
индексы
6,
фак¬
элемент массива» имеет индекс
«элемент массива номер семь»
имеет индекс
тически является восьмым элементом массива.
Это
7
и
источник так на¬
ошибок смещения индекса.
зываемых
6.2.
Отсутствие инициализации массива,
быть инициализированы.
6.3.
Указание
в
списке
инициализации
элементы
большего
количество элементов в массиве, является
которого
числа
должны
значений,
чем
синтаксической ошибкой.
6.4.
Завершение директив препроцессора #define или #include точкой с
запятой. Не забывайте, что директивы препроцессора не являются
операторами языка С.
6.5.
Присвоение
значения
раторе является
та не является
та
в
памяти,
время
6.6.
на
константе в выполняемом опе¬
переменной. Компилятор
как
в
выполнения
Ссылка
символической
синтаксической ошибкой. Символическая
элемент
случае
переменных,
содержащих
программы.
за
пределами
констан¬
не резервирует для нее мес¬
массива.
значения
во
Массивы
6.7.
287
Передача функции scanf
го
для
символьного массива, недостаточно
вводимой
хранения
с
клавиатуры
что
Предположение,
как
элементы
строки,
ошибкам времени
потере данных в программе и другим
6.8.
может
локального
большо¬
привести
к
выполнения.
объявленного
массива,
static, инициализируются нулями всякий раз при
вызове
функ¬
ции, где этот массив объявлен.
6.9.
Неправильная
a[x,y].
Хороший
6.1.
стиль
регистра.
напоминает
ются
в виде
программирования
Используйте для
хнего
a[x][y]
ссылка на элемент двумерного массива
имен символических констант только символы вер¬
Это выделяет
программисту,
эти константы в
что
тексте программы и
символические
константы
не
явля¬
переменными.
6.2.
Стремитесь к ясности программы. Иногда имеет смысл отказаться
от наиболее эффективного использования памяти или процессорного
времени в пользу написания более понятного кода.
6.3.
При
выполнении цикла над элементами массива его индекс никогда
быть
не должен
меньше
0
быть строго
и должен
личества элементов, т.е. не
больше,
чем
(размер
тесь, что условие завершения цикла не допускает
вне
ментам
6.4.
в структуре
способствуя
тем
из
самым
не допустить
вы¬
значе¬
входных
на вы¬
программы.
функций
Компилятор
включают
имена
переменных
в
прототи¬
для того, чтобы сделать программы более понятными.
игнорирует эти имена.
Советы по повышению
эффективности
Иногда соображения эффективности
жения
всех
воздействия ошибочной информации
Некоторые программисты
пы
6.2.
значение индекса массива,
ошибок преждевременного
Программы должны проверять правильность
числения
6.1.
for наибольшее
устранению
цикла.
ний, чтобы
6.6.
-
диапазона.
Указывайте
хода
6.5.
этого
общего ко¬
1). Убеди¬
обращения к эле¬
меньше
массива
намного перевешивают
сообра¬
понятности.
Последствия (обычно серьезные)
массива
являются
ссылок
на
элементы
за пределами
системно-зависимыми.
6.3.
В функциях, содержащих локальные массивы и часто входящих и
выходящих из области действия, делайте массивы статическими,
чтобы они не создавались заново при каждом вызове функции.
6.4.
Передача
массивов посредством имитации передачи по ссылке имеет
смысл из
соображениям эффективности. Если бы
лись
по
больших
значению,
часто
передавалась
передаваемых
бы
копия
массивов
это
массивы передава¬
каждого
элемента.
Для
было бы расточительно
288
Глава 6
по времени и приводило бы к расходу значительного объема памяти
для хранения их копий.
6.5.
Часто наиболее простые алгоритмы
вом является простота написания,
для достижения максимальной
лее
Общие
6.1.
сложные
6.3.
Их достоинст¬
плохо.
эффективности
часто
Однако
требуются бо¬
алгоритмы.
методические замечания
всех массивов с помощью символических кон¬
Определение размера
стант повышает
6.2.
работают
тестирования и отладки.
общность
программ
Возможна передача массива
ма, который описывается в
К параметру-массиву
в
массива.
наименьших
Это
масштабируемыми).
их
по значению с помощью простого прие¬
главе
10.
функции
определении
модификатор const, чтобы
исходного
(делает
может
не допустить изменения
является
одним
еще
привилегий. Функциям
применяться
в теле
примером
функции
принципа
не следует давать возможности
изменять массивы, если только это не является совершенно
необхо¬
димым.
Упражнения
6.1.
для самоконтроля
Заполните пропуски
в
каждом
из
следующих
a) Списки и таблицы значений хранятся в
b) Элементы массива связаны в том отношении,
и
те
же
и
c) Число,
сива,
утверждений:
.
что они имеют одни
.
используемое для
обращения
называется
к конкретному элементу мас¬
.
d) Для объявления
,
размера массива должна использоваться
поскольку в этом случае программа становится более
общей.
e) Процесс
называется
размещения элементов массива в определенном порядке
массива.
f) Процесс
определения,
значение, называется
g) Массив, который
содержит ли массив некоторое ключевое
в массиве.
имеет два индекса, называется
мас¬
сивом.
6.2.
Установите,
являются
невнрными.
Если
a) В
массиве
b) Индексом
c) Если
может
следующие
храниться
массива
число
ли
высказывание
может
много
быть
инициализирующих
оставшиеся
лизации.
элементы
типа
значений
С
верными
или
объясните почему.
различных
число
ции меньше числа элементов массива,
рует
высказывания
неверно,
в
типов
значений.
float.
списке
инициализа¬
автоматически инициализи¬
последним значением
в
списке
инициа¬
Массивы
289
d)
Если
список
щих значений,
кой.
инициализации
больше инициализирую¬
ошиб¬
содержит
чем имеется элементов в массиве, это является
элемент массива, который передается в функцию и ко¬
торый изменяется в вызываемой функции, в вызывающей функции
будет содержать измененное значение.
e) Отдельный
6.3.
Выполните следующие действия для
массива
с
именем
fractions.
a) Определите символическую константу SIZE, которая будет
на текст 10.
заме¬
щена
b) Объявите
те
SIZE
массив из
элементы
float
элементов типа
c) Назовите четвертый элемент от
d) Обратитесь к элементу массива
начала
массива.
4.
номер
1.667 элементу
значение
e) Присвойте
массива
номер
f) Присвойте значение 3.333 седьмому элементу
g) Выведите элементы массива с номерами 6 и 9 с
знаков
ния,
справа
которые
h) Выведите
девять.
массива.
точностью до двух
десятичной точки и покажите выводимые
фактически отображаются на экране.
от
значе¬
все элементы массива, используя структуру повторения
for. Сделайте при
переменной
инициализируй¬
и
нулями.
этом предположение, что в качестве
управляющей
цикла была определена целая переменная x. Покажите
выводимые значения.
6.4.
действия
Выполните следующие
a) Объявите целочисленный
Предположите,
для массива с именем table.
3 строками
с
массив
была определена
что
SlZE, равная 3.
b) Сколько элементов
3 столбцами.
константа
массиве?
в
содержится
и
символическая
c) Используйте структуру повторения for для инициализации каждо¬
го элемента массива
суммой
его индексов.
стве управляющих переменных
d) Выведите
жите,
что
значения
массив
каждого
и целые переменные x и у
Найдите ошибку
исправьте
a)
#define
b)
SIZE
=
SIZE
(i
=
=
#indlude
0; i
1;
10
Зак 801
4,
{2,
6),
{5}};
в качестве управляющих пере¬
значения.
фрагментов программы
100;
=
<=
int
10;
b[10]
5;
что
int
x и у.
table. Предполо¬
объявления
=
{0},
i;
I++)
<stdio.h>;
e) Предположим,
a[l,l]
8},
в каждом из следующих
что
что в каче¬
помощью
10;
b[i]
d)
{{1,
объявлены
массива
с
ее.
c) Предположим,
for
=
Покажите выводимые
менных.
элемента
был инициализирован
int table[SIZE][SIZE]
6.5.
Предположите,
объявлены целые переменные
a[2][2]
=
{{1,
2},
{3,
4}};
и
Глава 6
290
Ответы на упражнения для самоконтроля
6.1.
а) массивах, b) имя, тип. с) индексом, d) символическая
e) сортировкой, f) поиском, g) двумерным.
6.2.
а) Неверно. В
того
же
массиве
могут
храниться
только
константа,
значения
одного
и
типа.
b) Неверно. Индексом
численное
массива должно
быть целое
число или цело¬
выражение.
c) Неверно. С
автоматически инициализирует оставшиеся элементы
нулями.
d) Верно.
e) Неверно. Отдельные
Если
же в
элементы
функцию передается
массива
передаются
по
значению.
весь массив, то все изменения
будут
отражены в оригинале.
6.3.
10
SIZE
а)
#define
b)
float
c)
fractions
[3]
d)
fractions
[4]
e)
fractions
[9]
=
1.667;
f)
fractions
[6]
=
3.333;
fractions
=
[SIZE]
{0};
g) printf("%.2f %.2f\n", fractions[6], fractions[9]);
Вывод:
h)
for
3.33
=
(x
1.67.
0;
(
printf
x
Вывод:
fractions[0
fractions[1
fractions[2
fractions[3
fractions[4
fractions[5
fractions[6
fractions[7
fractions[8
fractions[9
6.4.
a)
mt
<=
c)
=
(x
for
(у
0;
=
x
0;
<=
у
table[x][у]
for
1;
x++)
%f\n",
=
x,
fractions[x]);
table[SIZE] [SIZE];
элементов.
d)
-
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
3.333000
0.000000
0.000000
1.667000
b) Девять
for
SIZE
"fractions[%d]
(x
for
=
(у
0;
=
x
0;
<=
SIZE
<=
=
x
SIZE
у <=
-
SIZE
+
-
SIZE
1;
-
table[0][1]
=
1
=
8
1;
у++)
у;
1;
-
printf("table[%d][%d]
Вывод:
table[0][0]
x++)
x++)
1;
=
у++)
%d\n",
x,
у,
table[x]ly]);
Массивы
6.5.
291
table[0] [2]
=
0
table [1] [0]
=
2
4
table[l][1]
=
table[1] [2]
=
6
table[2][0]
=
5
table[2] [1]
=
0
table[2] [2]
=
0
а) Ошибка:
точка с
запятой
в конце директивы препроцессора
#defi¬
ne.
Исправление: уберите точку с запятой.
b) Ошибка: присвоение значения символической
константе с исполь¬
зованием операции присваивания.
Исправление: присвойте
значение
символической константе
в
ди¬
рективе препроцессора #define без использования операции присва¬
ивания, как в #define SIZE 10.
c) Ошибка:
ссылка
Исправление:
на
на
элемент
массива
за
его
пределами
(b[10]).
управляющей переменной
измените верхнее значение
9.
d) Ошибка:
lude.
точка с
запятой
в конце директивы препроцессора
Исправление: уберите точку
e) Ошибка: некорректное
Исправление:
замените
с
#inc¬
запятой.
использование
на
оператор
индексов
a[l][l]
массива.
5;
=
Упражнения
6.6.
Заполните пробелы
в
каждом
из
следующих
предложений:
a) В языке С списки значений хранятся в
b) Элементы массива связаны в том отношении,
c) При
ссылке на элемент массива номер его позиции,
скобках,
ся
в
d)
Именами
называется
пяти
массива
e) Содержимое
конкретного
этого элемента.
элементов
содержащий¬
являются
p
и
,
f) Присвоение
.
.
элементов
,
его
.
что они
,
.
элемента
массива
называется
имени массиву, задание его типа и определения числа
называется
массива.
g) Процесс размещения элементов массива в возрастающем или
вающем порядке называется
убы¬
.
h) В двумерном массиве первый индекс идентифицирует (по согла¬
его
элемента, а второй индекс
шению)
m
n
Массив
на
столбцов
i)
содержит
строк,
.
и
j) Именем
10*
элементов.
элемента
массива
d
в
строке
3
и
столбце 5
является
Глава 6
292
6.7.
Установите, какие из следующих высказываний являются верными,
а какие неверными; для неверных высказываний объясните, почему.
a) Для обращения
к
или
памяти,
данному участку
элементу,
внутри
массива мы указываем имя массива и значение конкретного элемента.
b) При объявлении массива для него резервируется память.
c) Чтобы указать, что для целочисленного массива p должно
быть
зарезервировано 100 участков памяти, программист пишет объявле¬
ние
p[100];
d) Программа
массива
e) Программа
должна
С, инициализирующая
на
должна
нулями,
С, суммирующая
на
вложенные
содержать
f) Средним, медианой
го
6.8.
содержать
набора значений
выполнения
массива,
двумерного
for.
являются соответственно
Напишите операторы С для
for.
оператор
элементы
операторы
наиболее вероятным
и
15-элементного
элементы
один
значением для следующе¬
5, 6
и
каждой
7: 1, 2, 5, 6, 7, 7, 7.
из
за-
следующих
дач:
a) Отобразите
массива
b) Введите
ящего
на
значение
экране
из
значение в элемент номер
элементов
массива
4 одномерного
массива
b,
состо¬
из
5
элементов
одномерного целочис¬
8.
числом
g
d) Просуммируйте
с
символьного
плавающей точкой.
с
c) Инициализируйте каждый
ленного
элемента
седьмого
f.
элементы массива с, состоящего из
100
элементов
плавающей точкой.
e) Скопируйте
массив а в начало массива
b. Предположите,
что
float
a[ll], b[34];
f) Определите
ние,
и
самое
выведите
маленькое
содержащиеся в массиве w из
99
и
самое
элементов с
большое
значе¬
плавающей
точ¬
кой.
6.9.
Рассмотрим целочисленный
массив
t
размером
2
на
5.
a) Напишите объявление для t.
b) Сколько строк
t?
в
c) Сколько столбцов
d) Сколько
элементов
e) Напишите
f)
Напишите
g) Напишите
массива t
в
t?
в
t?
в
строки массива t.
имена
всех
элементов
второй
имена
всех
элементов
третьего
оператор С,
строке
1
и
который
столбца
устанавливает
в
массива
нуль элемент
столбце 2.
h) Напишите ряд операторов С, инициализирующих каждый
мент массива t нулем. Не используйте структуру повторения.
i) Напишите
t.
эле¬
вложенную структуру for, инициализирующую каж¬
элемент
массива
t нулем.
дый
Массивы
293
j) Напишите
оператор С для ввода значений
в элементы массива
t с
терминала.
к)
Напишите ряд операторов С для определения
шего
1)
значения
в
массиве
Напишите оператор С для отображения
вой строки
массива
и выводанаимень-
t.
на экране элементов пер¬
t.
m) Напишите
оператор С для суммирования элементов четвертого
столбца массива t.
n) Напишите
ряд операторов С, которые выводят элементы массива t
в наглядной табличной форме. Перечислите индексы столбцов в ка¬
честве заголовков в
верхней
таблицы
части
и индексы строк слева от
каждой строки.
6.10. Используйте одномерный массив для решения следующей задачи.
Компания оплачивает работу своих продавцов с учетом комиссион¬
ных. Продавцы получают $200 в неделю плюс 9% от их валового
сбыта.
Например,
продавец,
валовой
сбыт
которого
составляет
$3000 в неделю, получит $200 плюс 9% от $3000, или в сумме
$470. Напишите программу на С (с массивом счетчиков) для опреде¬
ления количества продавцов, заработок которых попал в каждый из
следующих диапазонов (предположите, что заработок каждого про¬
давца округлен до целочисленного значения):
1. $201-$299
2. $300-$399
3. $400-$499
4. $500-$599
5. $600-$699
6. $700-$799
7. $800-$899
8. $900-$999
9. $1000 и более
на рис. 6.15, явля¬
Сделайте следующие
эффективности пузырьковой
б.Н.Метод пузырьковой сортировки, представленный
для больших массивов.
неэффективным
простые изменения для
сортировки.
a) После первого прохода
ется
повышения
самое большое число гарантированно нахо¬
дится в элементе массива с наибольшим индексом; после второго
прохода «на место» попадают два самых больших числа и так далее.
Измените пузырьковую сортировку так, чтобы вместо проведения
сравнений на
сравнений на втором
девяти
каждом
проходе,
проходе
семь
на
нужно
было делать
третьем
и
восемь
т.д.
b) Данные
в массиве могут уже находиться в надлежащем порядке
или порядке, близком к нему, так зачем делать девять проходов,
если будет достаточно меньшего количества? Измените сортировку
так, чтобы в конце каждого прохода проверялось, делались ли пере¬
становки. Если перестановок не
было,
то
данные
уже
находятся
нужном порядке, так что программа должна завершиться.
рестановки
были,
то
необходим
по
крайней
Если
в
пе¬
мере еще один проход.
Глава 7
294
6.12.Напишите одиночный оператор для выполнения каждой из следую¬
щих
операций
над одномерным массивом:
a) Инициализируйте 10
элементов
целочисленного
массива
counts
нулями.
b) Прибавьте 1
15
к каждому из
элементов целочисленного массива
bonus.
c) Считайте
12 значений
с клавиатуры
состоящего из элементов с
res,
d) Выведите 5 значений
столбца.
6.13.Найдите ошибку(и)
в
массива
целочисленного массива bestScores в виде
из
каждом
следующих
a) Предположим: char str[5];
scanf("%s", str); /* Пользователь
b) Предположим: int a[3];
printf ( "$d %d %d\n", a[l], a[2],
c)
float
10.01,
{1.1,
=
f[3]
d) Предположим: double
d[l,
9]
monthlyTemperatu-
плавающей точкой.
операторов:
вводит
слово
*/
Hello
a[3]);
1000.0001};
100.001,
d[2][10];
2.345;
=
6.14 Измените программу на рис. 6.16 так, чтобы функция mode могла
обрабатывать случай
нескольких равных сумм при расчете
значения.
вероятного
Также
измените
функцию median
наиболее
так, чтобы в
массиве с четным числом элементов усреднялись значения двух эле¬
ментов в середине.
6.15.
Используйте одномерный массив для решения следующей задачи.
Прочитайте 20 чисел, каждое из которых находится между 10 и
100,
включая
эти
значения.
В процессе
считывания
выводите
это
дублирует одного из уже счи¬
Предусмотрите «наихудший случай», когда все 20
число только в том случае, если оно не
танных чисел.
чисел
Объявите для
различны.
решения
наи¬
этой задачи массив
меньшего возможного размера.
6.16.
Промаркируйте
элементы двумерного массива
чтобы
порядок,
показать
следующем
for
котором
sales размером 3
они устанавливаются
в
на
нуль
фрагменте программы:
2; row++)
column <= 4,;
0;
sales[row][column]
(row
for
в
=
0;
<=
row
(column
=
0;
column++)
=
6.17.Что делает следующая
#include
#define
int
программа?
<stdio.h>
SIZE
10
WhatIsThis(int
int);
[],
main()
{
int
total,
a[SIZE]
=
{1,
2,
3,
4,
5,
6,
7,
8,
9,
10};
5,
в
295
Массивы
total
WhatIsThis(а, SIZE);
printf("Total of array element values id %d\n",
return 0;
=
int WhatIsThis(int b[],
{
if (size
1)
total);
int size)
==
return b[0];
else
return b[size
-
+
1]
WhatIsThis(b,
size
-
1);
}
6.18.Что делает следующая
#include
#define
void
программа?
<stdio.h>
SIZE
10
someFunction(int
[],
int);
21,
64,
main()
{
int
a[SIZE]
=
{32,
18,
95,
14,
90,
printf("The values in the array are:\n",
someFunction(a, SIZE);
70,
60,
37};
total);
printf("\n");
return 0;
void someFunction(int b[], int size)
{
if (size > 0) {
1);
someFunction(&b[l], size
-
printf("%d
",
b[0]);
}
}
6.19.Напишите программу на С, которая моделирует
льных костей. Программа должна вызывать
«бросания» первой кости и
второй. Затем должна быть
чание: поскольку на
затем
эту же
бросание двух игра¬
функцию rand для
функцию для «бросания»
вычислена сумма двух значений. Заме¬
каждой
кости может выпадать целое значение
1 до 6, то сумма двух значений может изменяться от 2 до 12, при
этом 7 будет наиболее часто встречающейся суммой, а 2 и 12
от
встречающимися наименее часто. На рис. 6.23 показаны 36 возмож¬
ных комбинаций для двух костей. Ваша программа должна бросить
две кости
в
36,000 раз. Используйте одномерный массив для подсчета
сумм. Выведите результаты
появлений каждой из возможных
табличной форме. Также определите,
числа
являются ли итоговые сум¬
выбрасывания 7 существу¬
ет шесть способов, то все выбрасывания 7 должны составить прибли¬
мы
правдоподобными,
зительно
одну
т.е. поскольку для
шестую.
296
Глава 6
1
2
3
4
5
6
1
2
3
4
5
6
7
2
3
4
5
6
7
8
3
4
5
6
7
8
9
4
5
6
7
8
9
10
5
6
7
8
9
10
11
6
7
8
9
10
11
12
Рис. 6.23. 36 возможных исходов при бросании двух костей
6.20. Напишите
и
крепс
программу, в которой разыгрываются 1000 партий
которая отвечает на каждый из следующих вопросов:
a) Сколько игр выигрывается при первом бросании, втором
нии,
...,
двадцатом
b) Сколько
нии,
...,
бросании
Как
d)
после
и
после
вероятность выигрыша в
знать, что крепс является
но.
и
двадцатого
игр проигрывается при первом
двадцатом
c) Какова
бросании
вы
полагаете,
одной
что
это
броса¬
бросания?
бросании, втором броса¬
бросания?
двадцатого
крепс? (Замечание:
вы
должны
из самых справедливых игр в кази¬
означает?)
Какова средняя продолжительность игры
e) Увеличиваются
льности игры?
в
ли шансы на выигрыш при
крепс?
в
большей продолжите¬
6.21. (Система предварительной продажи билетов) Небольшая авиаком¬
пания недавно приобрела компьютер для новой автоматизированной
системы предварительной продажи билетов.
Президент компании
попросил вас написать на
темы.
Вы должны
каждом
мость:
рейсе
С программное обеспечение для новой
составить
бронирования
авиакомпании (его
программу для
единственного самолета
сис¬
мест на
вмести¬
10 мест).
Ваша программа должна отображать
альтернативных возможностей:
Пожалуйста,
Пожалуйста,
на
экране
следующее
меню
введите 1 для "курящих"
введите 2 для "некурящих"
Если оператор вводит 1,
то
ваша программа должна
(места 1-5). Если
забронировать место
забронировать
место в отсеке для курящих
оператор вводит 2, то
ваша программа должна
в
щих
(места 6-10). После
посадочный
отсеке для
некуря¬
этого ваша программа должна напечатать
талон с указанием номера места пассажира и
информа¬
ции о том, находится ли оно в отсеке для курящих или в отсеке для
некурящих пассажиров самолета.
представления схемы мест для пассажиров самолета используй¬
те одномерный массив. Инициализируйте все элементы массива ну¬
Для
лями,
чтобы показать,
что
все
места
свободны. По мере бронирова-
297
Массивы
ни я
места
каждого
устанавливайте соответствующие
1 для указания
массива в
на то,
что
это
больше
место
элементы
не является
свободным.
Конечно, ваша
бронированное
программа никогда не должна
Если
место.
бронировать
уже за¬
отсек для курящих заполнен, ваша про¬
допустимо ли бронирование
(и наоборот). В случае положительно¬
го ответа произведите соответствующее бронирование места. В случае,отрицательного ответа выведите сообщение «Следующий рейс
через 3 часа.»
грамма
должна
запросить
оператора,
места в отсеке для некурящих
6.22. Используйте двумерный
компании
работают
следующей задачи. В
(с 1 по 4), которые продают
(с 1 по 5). Один раз в день каждый
массив для решения
четыре продавца
пять различных видов изделий
продавец передает в компанию карточку сбыта по каждой разновид¬
ности проданного товара. Каждая карточка содержит:
1.
2.
3.
Номер
Номер
Общую
продавца
изделия
сумму в долларах за данный товар,
проданный
Таким образом, каждый продавец передает
сбыта. Предположим, что доступна информация
в день от
чек
в этот день
0 до 5 карто¬
по
всем
кар¬
последний месяц. Напишите программу, которая будет
считывать всю эту информацию о сбыте за последний месяц и под¬
водить общий итог о сбыте каждым продавцом каждой разновидно¬
точкам
сти
за
Все итоговые суммы должны храниться в двумерном
sales. После обработки всей информации за последний ме¬
товара.
массиве
таблицы,
бец представляет конкретного продавца и
сяц выведите результаты в виде
в
которой каждый
стол¬
каждая строка представ¬
конкретную разновидность товара.
Для получения общего
сбыта каждой разновидности товара за последний месяц просумми¬
руйте каждую строку; для получения общего сбыта для каждого
ляет
продавца за последний месяц проведите суммирование каждого
столбца. Распечатка вашей таблицы должна включать эти перекре¬
стные итоговые суммы справа от итоговых строк и в нижней части
итоговых столбцов.
6.23.
(Черепашья графика) Язык Лого, особенно популярный
среди поль¬
зователей персональных компьютеров, сделал знаменитым принцип
черепашьей графики (turtle graphics). Вообразите себе
механиче¬
скую черепаху, которая передвигается по комнате под управлением
программы
верхней
С. Черепаха держит перо в одной из двух позиций,
нижней. Когда перо находится в нижней позиции, че¬
на
или
фигуры, а ког¬
свободно перемеща¬
При решении этой
репаха при своем движении вычерчивает различные
да перо находится в
ется,
не
оставляя
верхней
при
позиции, черепаха
этом
никаких
следов.
задачи вы смоделируете действия черепахи, а также создадите авто¬
матизированный блокнот
Используйте
массив
для
набросков.
floor размером 50
нулями. Считывайте
на
50, инициализированный
содержащего их массива. Все
время отслеживайте текущее местоположение черепахи и то, нахо¬
дится ли перо в настоящее время в верхней или нижней позиции.
команды
из
298
Глава 6
Предположим, что черепаха всегда начинает в точке пола с коорди¬
натами 0,0 с поднятым пером. Множество команд черепахи, кото¬
рые должна обрабатывать ваша программа, таково:
Команда
Смысл
1
Перо вверх
2
Перо
3
Повернуть направо
4
Повернуть
5, 10
вниз
налево
Переместиться вперед
на 10
(или другое число)
интервалов
б
Вывести
9
Конец данных (контрольное значение)
на экран
массив
Предположим, что черепаха находится
пола. Следующая «программа» рисует и
размером
где-то
50x50
вблизи
от
центра
выводит на экран квадрат
размером 12 x 12:
2
5, 12
3
5, 12
3
5, 12
3
5, 12
1
6
9
Когда черепаха перемещается с опущенным пером, устанавливайте
соответствующие элементы массива floor в 1. При выдаче команды 6
(вывести
ся
на
экран)
1, отобразите
для всех элементов массива, в которых находит¬
на экране звездочку или
любой другой символ,
ко¬
Для всех элементов массива, в которых нахо¬
дится нуль, отобразите на экране пробел. Напишите программу на
С, реализующую возможности черепашьей графики, которые мы
сейчас обсудили. Напишите несколько программ черепашьей графи¬
торый
вам нравится.
ки, которые рисуют интересные фигуры. Добавьте другие команды
для увеличения мощности вашего «черепашьего» языка.
6.24.
(Обход конем) Одной
бителей
из
наиболее интересных головоломок для лю¬
об обходе конем шахматной доски,
шахмат является задача
Эйлером. Проблема со¬
фигура, называемая ко¬
нем, обойти пустую шахматную доску, при этом побывав в каждой
из 64 клеток один и только один раз? Здесь мы глубоко исследуем
эту захватывающую проблему.
первоначально предложенная
стоит
в
следующем:
может
ли
математиком
шахматная
Массивы
299
Конь ходит в виде
буквы L (на
две клетки в одном направлении и за¬
тем на одну клетку в перпендикулярном
квадрата в центре
пустой шахматной
ему). Таким образом,
семь различных ходов, показанных на рис. 6.24
ны от 0 до
из
доски конь может сделать во¬
(они
пронумерова¬
7).
0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
Рис. 6.24. Восемь возможных ходов конем
a) Нарисуйте на листе бумаги шахматную доску размером 8 на 8 и
попытайтесь обойти ее конем самостоятельно. Поместите 1 в первую
клетку, в которую вы перемещаетесь, 2 во вторую клетку, 3 в тре¬
тью и т.д. Перед началом обхода оцените, насколько далеко, по ва¬
шему мнению, вы сможете продвинуться, помня о том, что полно¬
стью
обход
Близко
ли
доски состоит из 64 ходов. Как далеко вы
вы
подошли
к
продвинулись?
своей оценке?
b) Теперь давайте разработаем
программу, которая будет переме¬
щать коня по шахматной доске. Сама доска представлена двумер¬
ным массивом board размером 8 на 8. Каждая клетка инициализи¬
рована нулем. Мы описываем каждый из восьми возможных ходов
его
горизонтальной и вертикальной
0, как показано на рис. 6.24,
типа
составляющими.
состоит
из
Например,
перемещения
ход
на две
клетки вправо по горизонтали и на одну клетку вверх по вертикали.
Ход 2
состоит из перемещения на одну клетку влево по горизонтали
и на две клетки вверх по вертикали.
влево
и
вертикальные
ными числами.
щью двух
образом:
перемещения
Указанные
Горизонтальные перемещения
вверх
обозначены отрицатель¬
быть описаны с помо¬
восемь ходов могут
одномерных массивов,
horizontal
и
vertical, следующим
300
Глава 6
horizontal[0]
=
2
horizontal[l]
=
1
horizontal[2]
=
-1
horizontal[3]
=
-2
horizontal[4]
=
-2
horizontal[5]
=
-1
horizontal[6]
=
1
horizontal[7]
=
2
vertical[0]
=
-1
vertical[l]
=
-2
vertical[2]
=
-2
vertical[3]
=
-1
vertical[4]
=
1
vertical[5]
=
2
vertical[6]
=
2
vertical[7]
=
1
Пусть
и
ку
типа
7,
переменные currentRow
и
currentColumn указывают
на стро¬
столбец текущего местоположения коня. Чтобы сделать ход
moveNumber, где значение moveNumber находится между 0 и
ваша
программа
использует
операторы
+=
vertical[moveNumber];
currentColumn += horizontal[moveNumber];
currentRow
Организуйте
счетчик,
который
изменяется от 1 до
64. Записывайте
каждой клетке, в которую перемеща¬
ется конь. Не забывайте проверять каждый возможный ход, чтобы
определить, не был ли уже конь в этой клетке. И, конечно, прове¬
ряйте каждый возможный ход, чтобы убедиться, что конь не пойдет
последнее
значение счетчика в
А теперь напишите программу для перемещения
шахматной доске. Выполните программу. Сколько ходов
сделал конь?
за пределы доски.
коня
по
с) Пытаясь написать и выполнить программу обхода конем шахмат¬
ной доски, вы, по всей вероятности, развили в себе некие ценные ин¬
туитивные качества. Мы воспользуемся ими для разработки эври¬
стики
(или стратегии)
гарантируют
успеха,
для
однако
перемещений коня. Эвристики не
разработанная эвристика
тщательно
значительно увеличивает шансы на успех. Вы, возможно, уже заме¬
тили, что внешние клетки в некотором смысле вызывают большие
проблемы,
мом деле,
угловых
чем клетки,
находящиеся
наиболее трудными,
ближе
к центру доски.
На
са¬
или недоступными, являются четыре
клетки.
Интуиция
подсказывает, что сначала следует попытаться помещать
коня в наиболее труднодоступные клетки и оставить напоследок те
из них, попасть в которые наиболее просто, с тем, чтобы имелся бо¬
льший шанс на успех, когда ближе к концу
обхода
доска
будет
пере¬
полнена.
Мы можем
разработать «эвристику доступности», классифицировав
каждую из клеток в соответствии с тем, насколько доступной она
всегда перемещать коня в клетку (разуме¬
L-образных ходов коня), которая является наиме¬
доступной. Мы размечаем двумерный массив accessibility числа-
является, и в
дальнейшем
ется, в пределах
нее
301
Массивы
ми, указывающими число клеток, из которых каждая конкретная
клетка является доступной. Для пустой шахматной доски централь¬
ные клетки, следовательно, оцениваются
2-мя
ниваются
или
6,
2
3
4
4
4
4
3
2
3
4
6
6
6
6
4
3
4
6
8
8
8
8
6
4
4
6
8
8
8
8
6
4
4
6
8
8
8
8
6
4
4
6
8
8
8
8
6
4
3
4
б
б
б
б
4
3
2
3
4
4
4
4
3
2
как
8-ю, угловые
клетки оце¬
и другие клетки имеют показатели доступности
3, 4
ниже:
указано
'
Теперь
напишите вариант программы
обхода
В любой
В случае
нескольких
может перемещаться в
может начинаться в
по
мере
момент конь
в клетку с наименьшим показателем доступ¬
должен перемещаться
ности.
шахматной до¬
конем
ски с использованием эвристики доступности.
любой
показателями
равными
по
шахматной доске
(Замечание:
ваша
программа
должна уменьшать показатели доступности, поскольку все
больше
момент времени в
доступной
до
торых
В
клеток становятся занятыми.
граммы.
процессе
обхода
конь
Таким образом, обход
из этих клеток.
из четырех угловых клеток.
коня
перемещения
с
клеток
любую
этом случае в
больше
и
любой данный
каждой
показатель доступности
клетки остается равным в точности числу клеток, из ко¬
нее
можно
Получился
добраться.) Выполните
ли
у
вас
эту версию своей про¬
полный обход? Теперь
измените
про¬
64 обходов, по одному из каждой клетки
шахматной доски. Сколько у вас получилось полных обходов?
грамму
для
выполнения
d) Напишите версию программы обхода
торая,
встретившись
с
равными
конем
шахматной доски, ко¬
для двух или более
показателями
клеток, решает, какую клетку выбрать
на
просмотра тех клеток, до которых можно
добраться
ными
показателями.
основании
результатов
из клеток с рав¬
Ваша программа должна перемещаться
в
ту
клетку, следующее перемещение из которой пришлось бы на клетку
с наименьшим показателем доступности.
6.25.
конем: подходы «с позиции грубой силы») В упражнении 6.24
мы нашли решение проблемы, связанной с обходом конем шахмат¬
ной доски. Использованный подход, названный нами «эвристикой
(Обход
доступности», порождает множество
решений
эффективно
и
выпол¬
няется.
мощность компьютеров продолжает возрастать, мы смо¬
жем решать многие проблемы исключительно за счет производите¬
Поскольку
льности компьютеров,
Давайте назовем
грубой силы».
мы.
применяя
этот
относительно
подход
решением
несложные
задачи
«с
алгорит¬
позиции
генерацию случайных чисел для предоставления
коню возможности обхода шахматной доски (разумеется, в рамках
а) Используйте
его
правильных
L-образных ходов)
в
произвольном
порядке.
Ваша
302
Глава 6
программа должна выполнить один обход и вывести конечное состо¬
яние шахматной доски. Как далеко забрался ваш конь?
b) По всей вероятности, результатом предыдущей программы
явился
короткий обход. Теперь измените вашу программу,
чтобы она ЮООраз попыталась сделать обход. Объявите одномер¬
ный массив для отслеживания числа обходов каждой продолжитель¬
относительно
После того,
ности.
как ваша программа закончит
1000
попыток
об¬
информацию в удобной табличной
Каков
был
форме.
наилучший результат?
c) По всей вероятности, в результате работы предыдущей программы
ходов,
она
вывести
должна
эту
у вас появились какие-то «приличные» обходы, но не было полных.
Теперь
ме
и просто
«снимите все ограничители»
выполняться
тех
до
(Предостережение:
пока
пор,
она
не
дайте вашей програм¬
полного обхода.
выдаст
эта версия программы может часами выполнять¬
компьютере.) Снова организуйте таблицу числа обхо¬
продолжительности и выведите эту таблицу после на¬
ся на мощном
дов каждой
хождения
сделать
полного
первого
обхода. Сколько обходов
ваша программа перед
Сколько для
этого
тем,
как
попыталась
был выдан полный обход?
потребовалось времени?
решение задачи об обходе конем шахматной доски «с
позиции грубой силы» с вариантом, основанным на эвристике до¬
ступности. Какое решение требует более тщательного анализа проб¬
d) Сравните
лемы? Какой алгоритм было труднее разработать? Для какого
них требуется большая производительность компьютера? Можем
мы
быть уверенными (заранее)
пользовании эвристики
ли мы
обхода при
ис¬
быть уверенны¬
обхода при использовании подхо¬
грубой силы»? Обсудите все за и против решения
позиции грубой силы» в общем случае.
ми
(заранее)
да
«с
задач
в получении полного
доступности? Можем
из
ли
в получении полного
позиции
«с
6.26. (Восемь ферзей) Другой головоломкой для любителей шахмат явля¬
ется задача о восьми ферзях. В простой постановке она выглядит
так: можно ли разместить восемь ферзей на пустой шахматной доске
таким образом, чтобы ни один из них не «нападал» на любого друго¬
го? Это значит, что никакие два ферзя не расположены в одном и
том же горизонтальном ряду,
в одном и том же вертикальном
ряду
или на одной диагонали.
Используйте подход, применявшийся в
упражнении 6.24, чтобы сформулировать эвристику для решения
задачи о восьми ферзях. Выполните свою программу. (Подсказка:
можно присвоить
каждой
клетке
шахматной доски
числовое значе¬
пустой шахматной доски «иск¬
помещении ферзя в эту клетку. На¬
ние, показывающее, сколько клеток
лючаются из рассмотрения» при
пример, каждой из четырех угловых клеток было бы присвоено
значение 22, как показано на рис. 6.25.)
Если
разместить эти «показатели исключений» во всех 64 клетках
эвристикой могла бы стать следующая: по¬
ферзя в клетку с наименьшим показателем иск¬
доски, соответствующей
местить следующего
лючений. Почему
ной?
эта стратегия
является
интуитивно
притягатель¬
Массивы
303
**
*
*
*
*
*
*
*
*
Рис. 6.25. 22 клетки, исключаемые при помещении ферзя в верхний левый угол
6.27.(Восемь ферзей: подходы
разработаете
нию задачи о восьми
a) Решите задачу
ных
числах
«с позиции
несколько подходов
ферзях, представленной
этой задаче
силы»
решения
к
вы
реше¬
6.26.
в упражнении
ферзях, используя основанный
«с позиции грубой силы»,
о восьми
метод
грубой силы») В
грубой
«с позиции
на
случай¬
применяв¬
шийся в упражнении 6.25.
b) Используйте
ные
метод прямого перебора, т.е. попробуйте все возмож¬
комбинации расположения восьми ферзей на шахматной доске.
c) Как
вы думаете,
ванный на
щим
для
переборе
решения
d) Сравните
всех
переборе
всех
грубой
позиции
силы», осно¬
возможностей,
может оказаться неподходя¬
об обходе
шахматной доски?
задачи
общем
случайных
и сопоставьте в
силы», основанный на
на
«с
почему подход
конем
случае подход «с позиции
грубой
числах, с подходом, основанным
возможностей.
6.2&.(Исключение дублирующих значений)
туру данных двоичного дерева поиска.
В
главе
Одной
12
из
мы изучим струк-
особенностей двоич¬
ного дерева поиска является то, что при проведении вставок в это де¬
дублирующие значения отбрасываются. Это называется
дублирующих значений. Напишите программу, кото¬
рая выдает 20 случайных чисел между 1 и 20. Программа должна
сохранять все недублированные значения в массиве. Используйте
рево
исключением
для решения этой задачи наименьший из возможных массивов.
6.29.(Обход
случай замкнутого обхода) В
конем:
шахматной доски обход
хода, побывав в каждой
один раз.
считается
клетке
Замкнутый обход
мещение удалено
задаче об
полным,
если
обходе
конь
шахматной доски один
возникает в том случае,
на один ход от того места,
если
конем
делает
и
64
только
64-е пере¬
с которого конь
начал
обход. Измените программу обхода конем шахматной доски, напи¬
санную вами в упражнении 6.24, для проверки наличия замкнутого
обхода в том случае, если имеет место полный обход.
6.30. (Решето
Эратосфена) Простым
торое можно разделить
то
работает
называется
любое целое число, ко¬
1. Реше¬
остатка только на него само и на
представляет собой метод нахождения простых чисел.
Эратосфена
Оно
без
следующим
образом:
1) Создайте массив и инициализируйте все его элементы единицей
(TRUE). Элементы массива с простыми индексами останутся равны¬
ми 1. Все другие элементы массива будут по завершении алгоритма
установлены
в
нуль.
Глава 6
304
2) Начните
с индекса массива, равного
стым); всякий раз при нахождении
выполните цикл
по
2 (индекс 1 должен быть про¬
1
элемента массива со значением
элементам массива и
оставшимся
установите
в
нуль те из них, индекс которых является кратным индексу элемента
1. Для индекса массива, равного 2,
со значением
все элементы мас¬
2, которые кратны 2, будут установлены в нуль (индексы
4, 6, 8, 10 и т.д.). Для индекса массива, равного 3, все элементы мас¬
сива после 3, кратные 3, будут установлены в нуль (индексы 6, 9,
12, 15 и т.д.).
После завершения этого процесса те элементы массива, которые все
сива после
еще установлены в единицу, указывают на то, что их индекс являет¬
ся простым числом. Эти индексы могут быть затем выведены на пе¬
чать. Напишите программу, которая объявляет массив из 1000 эле¬
ментов для определения и вывода на печать простых чисел между 1
и 999. Исключите из рассмотрения элемент массива с номером 0.
6.31.
(Блочная сортировка) Для блочной сортировки требуется одномер¬
ный массив положительных целых чисел, подлежащий сортировке,
и двумерный массив целых чисел с индексами строк от 0 до 9 и ин¬
число значений в сортируе¬
столбцов от 0 до n 1, где n
мом массиве. Каждая строка двумерного массива называется бло¬
ком. Напишите функцию bucketSort, которая принимает в качестве
параметров целочисленный массив и его размер.
-
дексами
Алгоритм выглядит так:
1) Выполните цикл по одномерному
массиву и поместите каждое из
его значений в некоторую строку блочного массива в зависимости от
значения его разряда, представляющего единицы.
7, 3 помещается
мещается в строку
в строку
3
и
Например, 97
по¬
100 помещается
в
0.
строку
2) Выполните цикл
обратно
в
значений
по
исходный
в
одномерном
3) Повторите
этот
массиву и
блочному
скопируйте
его значения
Новый порядок для приведенных
массиве будет 100, 3 и 97.
массив.
процесс
для
каждого
из
последующих
выше
разрядов
(десятков, сотен, тысяч и т.д.) и прекратите его выполнение
обработки крайнего слева разряда самого большого числа.
после
втором проходе по массиву 100 помещается в строку 0, 3 поме¬
щается в строку 0 (у 3 только один разряд) и 97 помещается в строку
При
9. Порядок значений в одномерном массиве будет 100, 3 и 97. При
третьем проходе 100 помещается в строку 1, 3 помещается в строку
нуль и 97 помещается в строку нуль (после 3). Гарантируется, что
при
блочной сортировке
все значения будут надлежащим образом
обработки крайнего слева разряда самого боль¬
отсортированы после
шого числа.
будут
Блочная сортировка заканчивается, когда
скопированы
в
Обратите внимание,
блоки,
строку
что
двумерного
размер
массива
двумерного
с
все значения
номером
массива,
0.
содержащего
превосходит размер целочисленного массива,
подлежащего сортировке. Этот метод сортировки обеспечивает боль¬
шую эффективность в сравнении с пузырьковым методом, но требу¬
ет
в
десять
значительно
раз
большего объема
памяти.
При пузырьковой
сорти¬
Массивы
305
ровке
требуется
только
сортируемого
уступки в отношении
ячейка памяти для
дополнительная
Блочная сортировка
требуемой
является примером
памяти ради уменьшения времени
Она использует больший объем памяти, но
выполнения.
ся
одна
типа данных.
быстрее. Данный
вариант блочной сортировки
шаге копирования всех данных
обратно
в
выполняет¬
требует
исходный
возможность состоит в создании второго двумерного
на каждом
Другая
массив.
блочного
масси¬
ва и многократном перемещении данных между этими двумя блоч¬
ными массивами до тех пор, пока все данные не
в строку с номером нуль одного из массивов.
нуль
будет
Упражнения
на
содержать
отсортированный
будут
скопированы
Тогда строка
с номером
массив.
рекурсию
6.32.(Выборочная сортировка) При выборочной сортировке происходит
поиск наименьшего элемента в массиве.
найден,
его
меняют
местами
с
Когда наименьший
элементом
первым
Каждый проход
по массиву приводит к помещению
Эта сортировка при
одного элемента на подходящее для него место.
обработке данных
методом
проходов
по
имеет
массиву
сходные
характеристики,
из
n
Затем
со второго эле¬
процесс повторяется для подмассива, начинающегося
мента массива.
элемент
массива.
элементов
должно
с
пузырьковым
быть сделано
n
-
1
и в каждом подмассиве для нахождения наименьшего зна¬
быть сделано n
1 сравнений. Если обрабатываемый
подмассив содержит один элемент, массив отсортирован. Напишите
рекурсивную функцию selectionSort для выполнения этого алгорит¬
чения должно
-
ма.
6.33. (Палиндромы) Палиндромом
называется
строка,
которая
читается
направлении. Неко¬
торыми примерами палиндромов являются: «radar», «аЫе was i ere i
saw elba» и «а man a plan a canal Panama». Напишите рекурсивную
одинаково как слева направо, так и в
обратном
функцию testPalindrome, которая возвращает 1, если строка, хра¬
нящаяся в массиве, является палиндромом, и 0 в противном случае.
Функция
должна игнорировать
пробелы
и
знаки
пунктуации.
программу на рис. 6.18, чтобы
6.34.(Линейный поиск) Модифицируйте
для выполнения последовательного
перебора
массива
можно
было
Эта функция
рекурсивную функцию linearSearch.
должна принимать в качестве параметров целочисленный массив и
его размер. Если ключ поиска найден, возвратите индекс массива; в
использовать
противном случае возвратите -1.
6.35.Щвоичный поиск) Модифицируйте
для выполнения двоичного поиска
программу на рис. 6.19, чтобы
в массиве можно
было
использо¬
функцию binarySearch. Эта функция должна
в
качестве
параметров целочисленный массив и началь¬
принимать
ный и конечный индексы. Если ключ поиска найден, возвратите ин¬
вать
рекурсивную
декс
массива;
в
противном
случае
возвратите
-1.
306
Глава 6
6.36.(Восемь ферзей) Модифицируйте
зданную
вами в упражнении
6.26,
программу о восьми ферзях, со¬
для рекурсивного решения зада¬
чи.
6.37.(Вывод массива)
Напишите рекурсивную функцию printArray,
торая принимает в качестве параметров
размер и ничего не возвращает.
ботку
целочисленный
Функция
и завершить свое выполнение
ко¬
массив и его
должна прекратить
обра¬
при получении массива нулево¬
го размера.
6.38.
(Вывод строки в обратном порядке) Напишите рекурсивную функ¬
цию stringReverse, которая принимает в качестве параметра симво¬
льный массив и ничего не возвращает. Функция должна прекратить
обработку и завершить свое выполнение, если будет встречен завер¬
шающий строку нулевой символ.
6.39. (Нахождение
минимального значения в
массиве) Напишите
рекур¬
сивную функцию recursiveMinimum, которая принимает в качестве
параметров целочисленный массив и его размер и возвращает наи¬
меньший элемент массива. Функция должна прекратить обработку
и завершить свое выполнение при получении массива из
1
элемента.
г
л
а
в
а
7
Указатели
Цели
Понять концепцию
указателей.
Изучить применение указателей для передачи
функции по ссылке.
аргументов в вызове
Понять
связь
между указателями,
массивами
и
указателей
на
строками.
Познакомиться
с
использованием
функции.
Научиться объявлять
и
использовать
массивы
строк.
308
Глава 7
Содержание
7.1.
Введение
7.2. Объявление и инициализация
переменной-указателя
7.3. Операции над указателями
7.4.
Передача параметра
по ссылке
7.5. Использование модификатора const
7.6. Программа
7.7.
с
указателями
пузырьковой сортировки, использующая
Выражения
и
арифметические операции
7.8. Связь между указателями
и
с
вызов по ссылке
указателями
массивами
указателей
7.9.
Массивы
7.10.
Пример: программа
7.11. Указатели
на
тасовки
и сдачи колоды
карт
функции
Распространенные ошибки программирования
Хороший стиль
Советы по повышению эффективности Советы по пе¬
реносимости программ
Общие методические замечания Упражнения для
самоконтроля Ответы на упражнения для самоконтроля Упражнения
Специальный раздел: как самому построить компьютер
Резюме
программирования
7.1. Введение
В этой
главе мы
обсудим
один из наиболее мощных элементов языка С
указатели. Овладение техникой работы
трудное.
Указатели дают
с
указателями
возможность программам
дело достаточно
генерировать
вызов
по
ссылке, создавать и управлять динамическими структурами данных, которые
это могут быть, например,
могут увеличиваться и уменьшаться в размере,
связанные списки, очереди, стеки и деревья.
боты
с указателями.
структурами.
В
В
главе
Эта
глава посвящена основам ра¬
10 будет рассказано о роли указателей в работе со
12 будут представлены динамические методы управле¬
главе
ния памятью с примерами создания и использования динамических структур
данных.
309
Указатели
7.2. Объявление
и
инициализация
переменной-указателя
Указатели представляют собой переменные, значениями которых являют¬
«обычной» переменной непосредственно содержится неко¬
торое значение. Указатель же содержит адрес переменной, в которой находит¬
ся адреса памяти. В
ся конкретное значение.
Говорят,
что переменная
непосредственно ссылается
(рис. 7.1). Ссылка на
косвенной адресацией.
на значение, а указатель косвенно ссылается на значение
значение через посредство указателя называется
count
count прямо
ссьшается на переменную
со
7
countPtr
значением 7
count
countPtr косвенно
ссьшается на
переменную
со значением 7
Рис. 7.1.
Указатели,
прежде
int
чем они
как
и
будут
Прямая
и косвенная ссылка на переменную
любые другие переменные, должны быть объявлены,
В операторе
использоваться.
*countPtr, *count;
объявляется переменная countPtr типа int * (указатель на целочисленное зна¬
чение), что читается как «countPtr
указатель на целое» или «countPtr ука¬
зывает на объект типа целое». Кроме того, объявляется переменная count
* в объявлении
целого типа. Символ
распространяется только на countPtr.
Этот символ означает, что объявляемая переменная является указателем.
Можно объявлять указатели, ссылающиеся на объекты любого типа.
Распространенная ошибка программирования 7.1
Знак операции косвенной адресации
строке объявления. Символ
Хороший
стиль
*
*
не
распространяется на все переменные в
должен предшествовать имени каждого указателя.
программирования 7.1
Включайте в имена переменных-указателей префикс (или
суффикс) ptr; такие имена
обрабатываться
подскажут вам, что эти переменные являются указателями и должны
соответствующим
образом.
Указатели
при
должны быть инициализированы либо при объявлении, либо
помощи оператора присваивания. Указатель может быть инициализиро¬
ван нулем, макросом NULL или значением адреса. Указатель со значением
NULL не указывает ни на что. Символическая константа NULL определяется
в файле заголовка <stdio.h> (и в некоторых других заголовочных файлах).
Инициализация
указателя значением 0 эквивалентна инициализации указа¬
теля константой NULL, однако использование NULL предпочтительнее. Ког¬
да присваивается значение 0, то происходит его преобразование к указателю
310
Глава 7
соответствующего типа. Значение 0 является единственным целым числом,
которое может быть присвоено переменной-указателю непосредственно. При¬
своение указателю адреса
Хороший
обсуждается
в разделе
7.3.
стиль программирования 7.2
Чтобы избежать неожиданных результатов, всегда инициализируйте указатели.
7.3.
Операция
Операции
взятия
над указателями
адреса & является унарной операцией, которая возвра¬
Например, если мы объявим переменные
щает адрес своего операнда.
int у
int
то
=
*
5;
yPtr;
следующий оператор
yPtr
&y;
=
переменной-указателю yPtr адрес переменной у. После этого можно
говорить, что переменная yPtr «указывает на» у. На рис. 7.2 схематически
представлены значения переменных после выполнения приведенного операто¬
присвоит
ра присваивания.
yPtr
Рис. 7.2. Графическое представление указателя на переменную целого типа
На рис. 7.3 показано расположение переменных в памяти, в предположе¬
нии, что целая переменная у находится по адресу 600000, а переменная-указа¬
тель yPtr
по адресу 500000. Операндом операции взятия адреса должна
быть переменная;
эта операция не может применяться к константам, выраже¬
ниям или к переменным,
объявленным
с
модификатором register.
yPtr
500000
600000
600000
Рис. 7.3. Размещение и содержимое у и yPtr в памяти
Операция *, обычно называемая операцией косвенной адресации или ра¬
зыменования, возвращает значение объекта, на который операнд (то есть ука¬
затель)
ссылается.
printf
("%d",
выводит значение
ции
*
называется
Например,
оператор
*yPtr);
переменной
у, а именно число 5. Такое использование опера¬
разыменованием указателя.
311
Указатели
Распространенная ошибка программирования 7.2
Разыменование указателя, который не был инициализирован или которому не при¬
своили необходимое значение адреса Приводит к фатальной ошибке во время вы¬
полнения программы или случайной порче данных, в результате чего выполнение
программы завершается с неверным результатом.
В программе йа рис. 7.4 показано выполнение
Спецификатор формата % p
функции
printf
операций над указателями.
выводит содержимое элемента па¬
(подробнее относительно шестнад¬
Д, «Системы счисления»). Обратите
мяти как шестнадцатеричное целое число
цатеричных целых чисел см. приложение
внимание на то, что адрес
переменной
aPtr при выводе совпада¬
а и значение
переменной а действительно присвоен
*
и
aPtr.
взаимно дополняют друг друга, и
&
переменной-указателю
Операции
ют,
из чего можно заключить, что адрес
когда они
обе применяются последовательно к aPtr, то
результат будет тем же. В таблице на
роли не играет
стные нам на
/*
данный
показаны изве¬
операций
&
*
и
*/
<stdio.h>
main()
{
int а;
/*
а
int
*aPtr;
/*
aPtr
а
7;
/*
Указателю aPtr
=
7.5
момент операции, их приоритет и ассоциативность.
Использование
#include
aPtr
порядок их следования
рис.
=
&a;
printf("The
"The
printf("The
"The
переменная
на
-
указатель
of
address
а
is
%p\n"
%p\n\n",
aPtr
is
value
of
а
%d\n"
value
of
*aPtr
is
*
&
that
"\n&*aPtr
is
and
%d\n\n",
are
%p\n*&aPtr
*&aPtr);
&*aPtr,
=
типа
целое
*/
*/
присваивается
of
value
printf("Proving
return
целого
-
=
&a,
а,
адрес
*aPtr);
complements
of
%p\n",
0;
address
The
value
of
aPtr
is
The
value
of
a
7
The
value
of
*aPtr
is
*
&
of
Proving that
&*aPtr
=
FFF4
*&aPtr
=
FFF4
а
FFF4
is
is
and
FFF4
7
are
Рис. 7.4.
complements
Операции &
и * над
а
aPtr);
}
The
переменной
of each oter.
указателем
each
oter.
"
*/
312
Глава 7
Операции
О
[]
+
*
*
!
++
-
(тип)
&
%
/
+
V
V II
|==
!
л
Ли
=
Ассоциативность
Описание
слева направо
высший
справа налево
унарные
слева направо
мультипликативные
слева
направо
аддитивные
слева
направо
отношения
приоритет
слева направо
сравнения
&&
слева направо
логическое И
l l
слева направо
логическое ИЛИ
?:
справа
+=
-=
%=
/=
*=
налево
условная
справа налево
присваивания
слева направо
запятая
~~
\'
Рис. 7.5.
7.4.
Старшинство операций
Передача параметра
по ссылке
Существуют два способа передачи параметров функции
Все
по ссылке.
вызовы
функций
в
С
по значению и
являются вызовами по значению.
Как
мы
5, при помощи оператора return можно возвратить единствен¬
ное значение из вызываемой функции в вызывающую (или вернуть управле¬
видели в главе
ние
в вызывающую
функцию без передачи значения). Однако
во многих слу¬
чаях нужно иметь возможность изменять не одну, а несколько переменных в
вызывающей функции,
или передавать
данных, чтобы избежать издержек,
чению
Для
(так как
целей
этих
функции указатель
на
передачей
связанных с
большой объект
параметра по зна¬
в этом случае создается и потом передается копия
в
С существует
возможность вызова
функции
объекта).
по ссылке.
В С
для организации
программисты используют указатели и
операцию косвенной адресации. Если вызывается функция, аргументы кото¬
вызова
рой
по
ссылке
должны изменяться, то в этом случае ей передаются адреса аргументов.
значение
которой будет
стве аргумента в
изменяться.
Как
функцию передается
чала массива и использовать операцию
ние
имени
массива
эквивалентно
(&)
переменной,
6, если в каче¬
функция получает адрес на¬
Обычно для этой цели применяется операция взятия адреса
к
мы уже видели в главе
массив, то
&
в этом
заданию
в
случае не нужно
качестве
(использова¬
&arrayNa-
аргумента
me[0]). Когда
адрес переменной передан функции, то для изменения ее значе¬
ния может быть использована операция косвенной адресации (*).
Программы
на рис.
7.6
и рис.
ции, вычисляющей куб целого
7.7 представляют собой два варианта функ¬
числа
cubeByValue
и
cubeByReference. Про¬
грамма на рис. 7.6 передает переменную number функции cubeByValue по
значению. В функции cubeByValue вычисляется третья степень аргумента
функции,
и полученное значение возвращается в
функцию main при
оператора return. Затем это новое значение присваивается
в
функции main.
помощи
переменной number
Указатели
313
/ * Вычисление куба числа с использованием вызова по значению
#include <stdio.h>
int cubeByValue
(int);
main ()
{
int number
5;
=
printf("The original value of number
number
cubeByValue(number);
is
%d\n",
*
/
number);
=
printf("The
return
new
value
of
number
is
%d\n",
number);
0;
cubeByValue(int n)
int
{
*
n
return
n
*
n;
/*
возводим
в
куб локальную переменную
n
*/
}
The original value of number is 5
The new value of number is 125
Рис. 7.6. Вычисление
куба переменной
/* Вычисление куба переменной при
#include <stdio.h>
void cubeByReference(int
mam
с вызовом по значению
помощи
вызова
по
ссьшке
*/
*);
()
{
int number
5;
printf ("The original value of number is %d\n", number);
cubeByReference(&number);
printf("The new value of number is %d\n", number);
=
return 0;
void cubeByReference(int *nPtr)
{
*nPtr
*nPtr * *nPtr * *nPtr;
=
/*
куба переменной
number функции main */
вычисление
}
The original value of number is 5
The new value of number is 125
Рис. 7.7. Вычисление
В программе
на рис.
куба переменной
с вызовом по ссылке
7.7 переменная number передается
по ссылке,
т.е.
функция cubeByReference получает адрес number. Функция cubeByReference
помещает этот адрес в свой параметр nPtr, переменную-указатель на целое.
Далее функция
разыменовывает указатель и вычисляет
числа, на которое указывает nPtr. В результате значение
функции main
изменяется.
На рис. 7.8
и
кубическое значение
переменной number в
7.9 графически изображены этапы
7.6 и рис. 7.7.
выполнения программ, приведенных соответственно на рис.
314
Глава 7
Перед тем,
main О
как main
вызывает
int cubeByValue(int n)
number
{
{
5
int number
number
=
=
cubeByValue:
125
return (n ♦ n ♦ n;)
5;
cubeByValue (number);
}
J
Послетого,как cubeByValue возвращаетуправлениев main:
main ()
number
int cubeByValue(int n)
{
{
int number
=
5;
return n
*
*
n
n;
125
number
=
(cubeByValue (number);)
}
n
^
не
определена)
После того, как main завершает присваивание number:
main
{
О
number
int
^
125
int number
number
=
=
5;
cubeByValue (number)
;
cubeByValue(int n)
return n
}
*
*
n
n;
n
не
}
определена
Рис. 7.8. Последовательность выполнения обычного вызова по значению
Распространенная ошибка программирования 7.3
Не разыменовывайте указатель, кроме случая,
на которое он ссылается.
когда вам нужно получить значение,
Указатели
315
Перед
вызовом
cubeByReference
void
main()
number
{
int
number
=
по ссылке:
cubeByRe£erence(int *nPtr)
<
*nPtr
5;
*nPtr
*
*nPtr
*
nPtr
cubeByReference(&number);
*nPtr;
|не определена)
}
I
После вызова cubeByRe£erence по ссылке перед возведением *nPtr в
main()
void
number
j
=
cubeByRe£erence(int *nPtr)
{
fi
int number
куб:
^
5;
L
*nPtr
«
*nPtr
*
*nPtr
J
cubeByReference(&number);
}
*
nPtr
*nPtr;
J>
i
После возведения *nPtr в
main()
куб:
void cubeByRe£erence(int
number
{
*nPtr)
{
125
int number
=
at
5;
frnPtr
g
*nPtr
*
*nPtr
J
cubeByReference(&number);
*
nPtr
*nPtr;]I
<Ь
_J
Рис. 7.9. Анализа типичного вызова по ссылке
Функция, получающая в качестве аргумента адрес, должна иметь соответ¬
ствующий параметр-указатель для размещения адреса. Например, заголовок
функции cubeByReference
void
имеет вид
cubeByReference(int *nPtr)
Заголовок функции показывает, что cubeByReference получает в качестве
аргумента адрес целой переменной, помещает его в локальную переменную
nPtr и не возвращает значения.
Прототип функции cubeByReference содержит в круглых скобках int *.
указателей, как и имена переменных других типов, в прото¬
типы функции необязательно. Имена включаются как комментарии и игнори¬
Включать имена
руются компилятором.
Если функция
то в
может получить в качестве аргумента
функциональном
лучает
быть определен
одномерный
массив,
функции соответствующий
па¬
Компилятор не делает различия
функцией, имеющей параметром указатель, и функцией, которая по¬
в качестве аргумента одномерный массив. Однако понятно, что функ¬
раметр может
между
заголовке и в прототипе
как указатель.
ция должна «знать», когда она получает массив и когда ссылку на одиночную
Когда компилятор встречает одномерный массив в качестве
функции, заданный в форме int b[], компилятор преобразует этот
переменную.
раметра
раметр к виду
int *b. Эти две формы
являются взаимозаменяемыми.
па¬
па¬
316
Глава 7
Хороший
стиль программирования 7.3
Используйте
вызов по ссылке только в том случае, когда вызываемая
жна изменять
передаваемый ей аргумент. (Еще
функция
дол¬
один пример принципа минимума
привилегий.)
7.5. Использование
модификатора
const
с указателями
Модификатор
тору
const дает возможность программисту сообщить компиля¬
указанной переменной не должно изменяться. В пер¬
о том, что значение
версиях С модификатор const отсутствовал, он был добавлен к языку С
комитетом ANSI.
вых
Общее методическое замечание 7.1
Употребление модификатора const можно рассматривать в рамках соблюдения
принципа минимальных привилегий. Следование этому принципу позволяет сущест¬
венно сократить
эффектов,
Совет
по
а
время
отладки
также делает
и
программы
количество
За прошедшие годы большое
них версиях языка
С,
в
нежелательных
сопровождения.
стандарте ANSI С, некоторые системы
количество программ
в которых не использовался
что его просто не существовало.
По этой причине
ности
программного
«старом»
и
переносимости программ 7.1
Хотя модификатор const определен
поддерживают.
для
побочных
программу проще для понимания
усовершенствования
С. Хотя
и
сейчас еще
многие
было
его не
написано на ран¬
модификатор const, потому
имеются огромные возмож¬
обеспечения, написанного на
программисты, работающие с ANSI С,
модификатор const
в своих программах, потому что они при¬
выкли к старому варианту языка С, и игнорируют многие другие возможности
хорошего стиля в разработке программ.
не используют
Существует
фикатора const
шесть вариантов использования
с параметрами
функции
(или неиспользования)
моди¬
две при передаче параметра по зна¬
чению и четыре при передаче параметра по ссылке. Как выбрать один вариант
из шести? Руководствуйтесь принципом минимальных привилегий. Всегда
предоставляйте функции столько прав доступа к данным, передаваемым через
параметры, сколько нужно для того, чтобы функция выполнила свою задачу,
но не более того.
Из главы 5 мы знаем, что все вызовы
в
С
это вызовы по значению; при
функции ей передается копия аргумента. Если копия изменяется в
функции, первоначальное значение аргумента в вызвавшей функции остается
вызове
без
изменения.
Во
многих случаях переданное значение изменяется в процессе
функцией своей
задачи. Однако в некоторых случаях переданное
значение не должно изменяться, несмотря на то, что вызываемая функция
оперирует всего лишь копией первоначального значения.
выполнения
Рассмотрим функцию,
которая принимает
одномерный
массив
массива как m раметры и выводит содержимое этого массива.
и
размер
Такая функция
317
Указатели
должна содержать цикл по элементам массива и выводить каждый элемент
массива в цикле. Размер массива используется для того, чтобы определить по¬
следний
элемент массива, завершить цикл и вывод элементов массива.
Общее
методическое замечание 7.2
Если передаваемое
функции,
в теле
вать
Размер
функции.
массива не должен изменяться в теле
функции
оно должно
невозможность
Если происходит
даже
(или не должно быть изменено)
модификатором const, чтобы гарантиро¬
значение не изменяется
объявляться
случайного
с
изменения.
попытка изменить значение,
которое
было объявлено
модификатором const, компилятор перехватывает эту попытку
предупреждение, либо сообщение об ошибке,
компилятор вы используете.
в
зависимости
с
либо
какой
и выдает
от
того,
Общее методическое замечание 7.3
В результате вызова по значению в вызывающей функции может быть изменена толь¬
ко одна величина Этой величине должно быть присвоено возвращаемое вызванной
функцией значение. Для изменения сразу нескольких значений в вызывающей функ¬
ции необходимо использовать вызов по ссылке.
Хороший
стиль программирования 7.4
Прежде чем
функция
ли
использовать
изменять
функцию, посмотрите
переданные ей
на ее
прототип, чтобы узнать, может
значения.
Распространенная ошибка программирования 7.4
Функции ошибочно передается параметр
ях
некоторые
компиляторы
поступят с
этого
по значению, а не по ссылке. В таких
полученным
произведут
разыменование
«указателя».
программы
произойдет ошибка обращения
пиляторы, проверяющие аргументы
общение об ошибке.
В
аргументом
результате
к памяти или
во
как
с
случа¬
указателем
время
и
выполнения
ошибка сегментации. Ком¬
выдадут со¬
и параметры на соответствие типов,
Существует четыре способа передать функции указатель: изменяемый
указатель на изменяемые данные, неизменяемый указатель на изменяемые
данные,
изменяемый указатель
указатель
на
на неизменяемые данные.
неизменяемые
Каждая
данные
из четырех
и
неизменяемый
комбинаций обеспе¬
различный уровень привилегий доступа к данным.
Самый высокий уровень доступа предоставляется не-константным указа¬
телям на не-константные данные. В этом случае данные могут изменяться че¬
чивает
рез разыменование указателя, а сам указатель может изменяться и ссылаться
на другие элементы данных. Объявление указателя
не константы на не-кон¬
const. Такой указатель мог бы
использоваться, например, при передаче строки как параметра функции, в ко¬
торой используются арифметические операции над указателем при обработке
стантные данные не содержит ключевого слова
(и, возможно, изменении) каждого отдельного
convertToUppercase, приведенная на рис. 7.10,
тель
Функция
*
(char s). Функция обрабаты¬
арифметические операции над указате¬
не константу на не-константные данные
вает строку s посимвольно, используя
символа в строке.
объявляет параметром указа¬
Глава 7
318
лем. Если символ находится в диапазоне от а до z, то он
ветствующий
символ
основанных на
верхнего
ASCII-кодах
от
регистра
А до Z
с
преобразуется
помощью
в соот¬
вычислений,
символов; в противном случае символ не изменя¬
обрабатывается следующий символ в строке. Заметим, что все символы
верхнего регистра имеют ASCII-код, равный ASCII-коду соответствующего
символа нижнего регистра минус 32 (см. ASCII-таблицу в приложении Г). В
главе 8 мы представим вам функцию toupper из стандартной библиотеки С,
используемую для преобразования символов в символы верхнего регистра.
ется и
/*
Преобразование
символов
нижнего
в
регистра
/* регистра с использованием указателя-не
/* которые могут модифицироваться */
#include <stdio.h>
символы
константы
на
верхнего
данные,
*/
*/
void convertToUppercase(char *);
main()
{
char string[]
=
"characters";
string before
printf("The
return
is:
conversion
convertToUppercase(string);
printf("The string after conversion
is:
%s\n",
string);
%s\n",
string);
0;
}
void
convertToUppercase(char
*s)
{
while
(*s
if
(*s
*s
!
>=
-=
'\0'
=
'a'
32;
)
{
'z')
*s
преобразование
/* увеличение
+ + s;
<=
&,&
/*
s
для
к
перехода
верхнему
к
регистру
следующему
*/
символу
*/
}
}
The string before conversion is:
The string after conversion is:
Рис. 7.10. Преобразование строки
characters
CHARACTERS
к верхнему регистру с использованием не константного
указателя на не константные данные
Указатель
рый
не
константа на данные-константы
может изменяться и ссылаться на
это указатель,
кото¬
любой объект данных соответствующе¬
го типа, но элемент данных, на которые он указывает, не может изменяться.
Такой указатель
мента
мог
бы
использоваться для передачи массива в качестве аргу¬
функции, которая обрабатывает каждый
элемент массива и при этом не
приведенная на рис. 7.11 функция printCharacters объявляет параметр s, типа const char *. Объявление читается справа на¬
лево, и звучит как «s
указатель на символ-константу». В теле функции для
изменяет данные.
Например,
вывода символов строки используется цикл for, который завершается при до¬
стижении конца строки. После вывода очередного символа производится уве¬
личение указателя для перехода к следующему символу в строке.
Указатели
/
*
/
*
319
Посимвольный
вывод строки, использующий */
не-константный указатель на константные данные
*
/
#include <stdio.h>
void printCharacters(const char *);
main ()
{
char string[]
=
"print characters of
а
string";
printf("The string is:\n");
printCharacters(string);
putchar(1\n1);
return
void
0;
printCharacters(const char *s)
{
for
( ; *s != '\0';
putchar(*s);
/*
s++)
цикл
не
инициализируется
*/
}
The string is:
print characters of
а
string
Рис. 7.11. Посимвольный вывод строки
с использованием указателя
не константы на
константные данные
тор
На рис. 7.12 приведены сообщения об ошибках, которые выдает компиля¬
Borland С ++ при попытке компилировать функцию, получающую указа¬
не константу на данные-константы и пытающуюся при помощи указа¬
тель
теля изменить эти данные.
Как
мы знаем, массив является составным типом данных,
объединяющим
под одним именем логически связанные данные одного типа. В главе 10 мы об¬
структурой (или за¬
программирования). Структура объединяет связанные
элементы данных различных типов под одним именем (например, информацию
относительно каждого служащего компании). Когда в качестве параметра фун¬
судим другую
форму
составного типа данных, называемую
писью в других языках
кции используется массив, то автоматически генерируется вызов по ссылке.
Однако структуры всегда передаются по значению, т.е. передается копия всей
структуры. Во время выполнения программы это приводит к дополнительным
накладным расходам для создания копии каждого элемента данных в структу¬
ре и сохранении копии структуры в стеке компьютера. При передаче структуры
в функцию мы можем использовать указатель на константные данные, чтобы
сочетать эффективность вызова по ссылке с уровнем защиты данных, соответст¬
вующим вызову по значению. При передаче указателя на структуру функция
будет получать только копию адреса структуры. При этом на машине с адреса¬
ми в
4 байта копирование 4 байт адреса будет выполняться быстрее,
байт элементов структуры.
чем копи¬
рование, возможно, сотен или тысяч
Совет по повышению
эффективности 7.1
При передаче больших объектов
данных, например, структур,
используйте указатель
на
константные данные. Этот подход позволит вам совместить производительность переда¬
чи параметра по ссылке и защиту данных как
прй передаче параметра
по значению.
Глава 7
320
Попытка изменения данных при помощи */
/*
/* неконстантного указателя на
данные-константы */
#include <stdio.h>
void f(const
main
int
*);
)
(
{
int
у;
f(&y);
return 0;
/*
void f(const
{
*x
100;
int
=
/*
функция
f
пытается
выполнить
изменение
*/
данных
*x)
нельз
модифицировать объект
данных
типа
const
*/
Compiling FIG7_12.С:
Error
FIG7_12.C 17: Cannot modify а
FIG7__12.C 18: Parameter 'x'
Warning
const object
is never used
Рис. 7.12. Попытка изменить данные, используя неконстантный указатель на константные
данные
очередной при¬
указателей на константные данные
выполнения/требуемая память. Если памяти недоста¬
точно и эффективность выполнения программы является важным факто¬
используйте указатели. Если памяти достаточное количество, а бороть¬
ром
ся за эффективность не нужно, то, в соответствии с принципом минимума при¬
вилегий, данные должны передаваться по значению. Следует заметить, что не¬
которые системы недостаточно строго поддерживают модификатор const, так
Такое применение
мер компромисса время
что передача параметра по значению остается самым надежным
способом
за¬
щиты данных от изменения.
Указатель-константа на не-константные данные
указатель, который
всегда указывает на одно и то же место в памяти, а расположенные там дан¬
ные могут изменяться. Такой указатель назначается по умолчанию для имени
массива. Имя массива является указателем-константой на начало массива. Ко
всем
элементам
массива
массива и индекс.
можно
обращаться
Константный указатель
и
изменять
использоваться для передачи массива как параметра
щается
их,
используя
имя
на не-константные данные может
функции, которая обра¬
Указатели, кото¬
к его элементам, используя только индекс массива.
объявляются
рые
с модификатором const, должны быть инициализированы
(если такой указатель является параметром функции, он ини¬
переданным функции значением).
при объявлении
циализируется
Программа
на
рис.
Указатель ptr объявлен
7.13 делает попытку изменить указатель-константу.
*
const. Объявление должно читаться справа
как int
указатель-константа на це¬
будет звучать как «ptr
лое число». Указатель инициализирован адресом переменной x целого типа.
Программа пытается присвоить указателю ptr адрес у, в результате чего выда¬
налево и в данном случае
ется
сообщение об ошибке.
Указатели
321
/* Попытка изменения указателя-константы
/* не-константные данные
*/
#include <stdio.h>
main ()
{
int x,
int *
*/
у;
const
=
ptr
на
return
=
ptr
&x;
&y;
0;
}
FIG7_13.C:
FIG7_13.C 10: Cannot modify a const object
Warning FIG7__13.C 12:
'ptr' is assigned a value
Compiling
Error
that is
never used
FIG7__13.C
Warning
12:
'y'
is declared but never used
Рис. 7.13. Попытка изменения указателя-константы, ссылающегося на не-константные данные
Минимальные права доступа предоставляет указатель-константа на кон¬
стантные данные. Такой указатель всегда указывает на то же самое место в па¬
мяти, а расположенные по этому адресу данные не могут
Этому варианту соответствует передача
массива
модифицироваться.
функции, которая только про¬
В про¬
const int *
сматривает массив при помощи индекса и не изменяет его элементы.
грамме на рис. 7.14 объявляется переменная-указатель ptr
const. Объявление читается справа налево и звучит так: «ptr
типа
указатель-кон¬
станта на целую константу». На рисунке показаны сообщения об ошибках, по¬
лученных в результате попытки изменить данные, на которые указывает ptr,
и изменить
присвоенный
указателю адрес.
Попытка модификации указателя-константы
/* данные-константы
*/
#include <stdio.h>
/*
main ()
{
int x = 5, у;
const int *const ptr
=
*ptr
=
на
*/
&x;
7;
=
ptr
&y;
return 0;
Compiling FIG7_14.С:
Error FIG7__14.C 10: Cannot modify а const object
Error
FIG7__14.C
11:
Cannot modify а const object
'ptr' is assigned а value that is
Warning FIG7__14.C
never used
13:
FIG7__14.C
13:
Warning
'y'
is declared but never used
Рис. 7.14. Попытка изменить константный указатель на константные данные
11
Зак 801
Глава 8
322
7.6.
Программа пузырьковой сортировки,
использующая
Давайте
изменим
программу
ки элементов массива
Как
известно,
array[j]
функции
в
и swap. Функция bubbleSort
функцию swap для перестанов¬
bubbleSort
Она
и
сортировки, приведенную на
пузырьковой
рис. 6.15, и введем две функции
выполняет сортировку массива.
вызов по ссылке
вызывает
array[j
+
С скрывают
1] (см.
рис.
7.15).
свои данные друг от друга, поэтому
swap не имеет доступа к элементам массива в
функции bubbleSort. Но
поско¬
bubbleSort заинтересована в том, чтобы функция swap имела доступ к
массиву, bubbleSort передает ей нужные элементы по ссылке, т.е. явно пере¬
льку
дает адреса элементов массива.
аргумента
передается
по
При
ссылке
использовании имени массива в качестве
весь
массив
целиком,
индивидуальные
элементы массива являются скалярами и передаются по значению.
bubbleSort при обращении к функции swap использует операцию
са (&) каждого из элементов:
swap
(&array[j],
&array[j
+
же
Поэтому
взятия адре¬
1]);
чтобы передать их по ссылке.
Функция swap помещает адрес &array[j] в пере¬
менную-указатель elementlPtr. И теперь, хотя swap и не имеет доступа к эле¬
ментам массива array[j], по причине сокрытия информации, swap может
разыменованный указатель *elementlPtr как синоним array[j].
Следовательно, когда swap обращается к *elementlPtr, она получает элемент
array[j] из bubbleSort. Аналогично, когда swap ссылается на *element2Ptr, то
получает array[j + 1] из bubbleSort.
использовать
И
хотя swap rie может выполнить следующие операторы,
temp
array[j];
array[j]
array[j
=
=
+
array[j
1]
=
+
1];
temp;
она получит тот же самый результат при помощи операторов
*elementlPtr;
*elementlPtr
*element2Ptr;
*element2Ptr
temp;
temp
=
=
=
7.15.
особенностей функции bubbleSort. Ее заголовок объ¬
являет массив array как int *array, а не как int array[], поскольку эти спосо¬
бы записи параметра-массива взаимозаменяемы. В соответствии с принципом
в своем теле
Отметим
см. рис.
несколько
привилегий, параметр size объявляется с модификатором const. И
хотя параметр size содержит копию значения из функции main, изменение ко¬
минимума
торой
никак не может изменить значение
bleSort
переменной
main, функции bub¬
в
size для выполнения своей задачи.
во время выполнения bubble¬
массива
остается
постоянной
величиной,
Размер
Sort. Поэтому параметр size и объявлен константным, чтобы гарантировать
его неизменность. Если бы в процессе сортировки размер массива изменился,
не
требуется
изменять
величину
то, возможно, алгоритм выполнялся бы неправильно.
Прототип функции swap включен в тело функции,
Sort
единственная
функция, которая
вызывает swap.
потому
что
bubble¬
Размещение
прототи¬
па в bubbleSort ограничивает правильные вызовы swap только теми, что были
сделаны из bubbleSort. Другие функции, которые, не имея доступа к соответ¬
ствующему функциональному прототипу, попытаются вызвать swap,
использовать прототип по умолчанию.
Обычно
будут
этот прототип не соответствует
Указатели
323
(что
заголовку функции
приводит к ошибке
тор по умолчанию предполагает тип
метров
/*
компиляции),
так как компиля¬
int для возвращаемого
значения и пара¬
функции.
Эта
заполняет
программа
возрастания и выводит
#include <stdio.h>
#define
SIZE
сортирует значения
массив */
массив,
в
порядке
отсортированный
10
void bubbleSort(int
int);
*,
main ()
{
int
i,
a[SIZE]
=
{2,
printf("Data items
for
(i
=
0;
i
<=
printf("%4d",
6,
(i
=
0;
<=
i
printf("%4d",
8,
10,
in original
SIZE
-
1;
12,
89,
68,
45,
order \n");
i++)
SIZE
/* сортировка
in ascending order.\n");
-
1;
i++)
a[i]);
printf("\n");
return 0;
}
void bubbleSort(int
{
int pass, j;
*array,
int
void swap(int *, int *);
for (pass
1; pass <= size
=
for
=
(j
if
0;
j
<=
(array[j]
size
>
-
size)
1;
-
2;
pass++)
j++)
+
1])
&array[j
array[j
swap(&array[j],
+
1]);
}
void swap(int
{
int temp;
*elementlPtr,
int
*element2Ptr)
*elementlPtr;
*elementlPtr
*element2Ptr;
*element2Ptr
temp;
temp
=
=
=
}
Data items in original order
2
4
8
12
6
10
89
68
Data items in ascending order
2
4
12
6
8
10
37
45
45
37
68
89
Рис. 7.15. Пузырьковая сортировка
11*
37};
a[i]);
bubbleSort(a, SIZE);
printf("\n Data items
for
4,
и вызов no ссылке
массива
*/
324
Глава 7
Общее методическое замечание 7.4
Размещение прототипа функции в теле
зовы
первой функции
другой функции ограничивает корректные вы¬
будут сделаны из функции, где объявлен про¬
привилегий.
только теми, что
тотип. Это опять принцип минимума
Отметим еще раз,
размер массива.
Для
что
функция bubbleSort получает в качестве параметра
функция должна знать
передается в функцию, функция получает адрес пер¬
того, чтобы сортировать массив,
Когда массив
Адрес не несет в себе информации относительно числа элемен¬
тов в массиве. Следовательно, программист должен каким-либо образом сооб¬
щить функции размер массива.
Передача функции размера массива в качестве параметра имеет два преи¬
это хороший стиль программирования, во-вторых
мущества: во-первых
такую функцию можно использовать многократно. Функция, которая получа¬
ет размер массива как параметр, может быть использована любой другой про¬
граммой, которая сортирует одномерные целочисленные массивы произволь¬
его размер.
вого элемента.
ного размера.
Общее
методическое замечание 7.5
При передаче в функцию массива следует передавать и его
кцию более универсальной. Универсальные функции могут
количеством
размер. Это сделает
использоваться
фун¬
большим
программ.
Мы
могли бы поместить размер массива в глобальную переменную, кото¬
была бы доступна для всей программы. Такой способ более эффективен,
потому что не создается копия size для передачи в функцию. Но не все про¬
рая
требуется сортировать целочисленный массив, могут иметь
переменную с нужным именем, и в таких программах функция не
могла бы использоваться.
граммы, которым
глобальную
Общее методическое замечание 7.6
Глобальные переменные нарушают принцип минимальных привилегий и являются
примером плохого стиля программирования.
Совет по повышению эффективности 7.2
Передача параметра
мой в
сов,
требует
времени
и места в стеке для создания копии, передавае¬
функцию Глобальным переменным
так
Размер
как
они
доступны
массива можно
в
любом
было бы
месте
не
требуется
«зашить»
цию. Но это ограничило бы применение
этих дополнительных ресур¬
программы
функ¬
специфи¬
непосредственно в саму
функции
только массивами
уменьшило бы возможность использования
Эта
функции другими программами.
функция годилась бы только для про¬
грамм, обрабатывающих одномерные целочисленные массивы специфическо¬
ческого размера, т.е.
чрезвычайно
го размера, запрограммированного
можно определить размер массива
функции.
sizeof, при помощи которой
(или любого другого типа данных) в байтах
во время компиляции программы.
Если эту операцию применить
В С
в теле
имеется специальная унарная операция
к имени мас¬
Указатели
325
сива, как это показано на рис. 7.16, то sizeof возвратит
занимаемое массивом, в виде целого числа.
Например,
общее
количество
байт,
переменная типа float
обычно занимает 4 байта памяти, а массив объявлен двадцатиэлементным.
Следовательно, общее количество байт в массиве равно 80.
/* применение операции sizeof к массиву */
/* возвращается число байт в массиве */
#include <stdio.h>
main()
{
float array[20];
printf("The number of bytes
sizeof(array));
return
in the array is
%d\n",
0;
}
The number of bytes in the array is 80
Рис. 7.16. Операция sizeof, применяемая
Число элементов
ляции.
Например,
к имени массива, возвращает его размер в
в массиве также может
быть определено
байтах
во время компи¬
рассмотрим следующее объявление массива:
double real[22j;
типа double обычно занимают 8 байт памяти. Таким образом,
real содержит 176 байт. Для того, чтобы определить число элементов в
Переменные
массив
массиве, можно использовать следующее выражение:
sizeof(real)
/
sizeof(double)
В выражении определяется
ние делится затем на число
число
байт
в массиве
байт, используемых
real
и полученное значе¬
для представления типа doub¬
le.
Программа
на рис.
7.17
вычисляет число
байт, используемых для пред¬
ставления стандартных типов данных совместимого с
Совет
PC компьютера.
по переносимости программ 7.2
Размер в байтах одного и того же типа данных может быть разным на различных сис¬
темах. При написании программ, которые будут работать на различных платформах и
при этом небезразличны к представлению данных, используйте операцию sizeof для
определения размера типа данных.
Операция sizeof может применяться к любой переменной, типу данных
При определении размера переменной или константы (но не
массива) возвращается число байт, отводимых под тип переменной или кон¬
станты. Обратите внимание, что скобки с sizeof обязательны, если в качестве
операнда используется имя типа. Отсутствие скобок в этом случае приводит к
сообщению об ошибке. Скобки не требуются, если операнд является именем
переменной.
или константе.
Глава 7
326
/* Использование операции sizeof
#include <stdio.h>
*
/
main ()
{
sizeof(char)
=
%d\n"
sizeof(short)
sizeof (int)
=
sizeof(long)
sizeof(float)
sizeof(double)
sizeof(long double)
=
%d\n"
%d\n"
%d\n"
%d\n"
%d\n"
=
=
=
%d\n",
sizeof(char), sizeof(short), sizeof(int),
sizeof(long), sizeof(float), sizeof(double),
sizeof(long double));
return
=
0;
sizeof(char)
=
1
sizeof(short)
=
2
sizeof(int)
=
2
sizeof(long)
=
4
sizeof(float)
=
4
sizeof(double)
=
8
sizeof(long double)
=
10
Рис. 7.17. Использование операции sizeof для определения размера стандартных типов
данных
7.7.
Выражения
с
Указатели
жениях,
арифметические операции
и
указателями
арифметических выра¬
Однако не все операции,
верный результат для пере¬
являются допустимыми операндами в
выражениях присваивания и сравнения.
обычно используемые
в
этих выражениях,
дают
менных-указателей. Этот
раздел описывает операции, которые могут приме¬
няться к указателям, и как эти операции должны использоваться.
К указателям может применяться ограниченный набор арифметических
операций. Указатель
или уменьшен
лю может
+=),
может быть увеличен (++)
быть прибавлено целое число (+ или
честь целое число
В
будет
иметь адрес
-=)
в
и можно вычислить разность двух
памяти,
v[0], т. e.
изображен графически
для того,
бым
или
качестве примера определим массив
значением адреса
мер
(-
( ),
int v[10], первый
указатель vPtr
vPtr равно 3000. На рис. 7.18
для машины с 4-байтными целыми.
чтобы vPtr указывал
указателей.
элемент которого
равный 3000. Инициализируем
значение
к указате¬
из указателя можно вы¬
этот при¬
Заметим,
что
на массив v, его можно инициализировать лю¬
из следующих операторов:
vPtr
=
vPtr
=
v;
&v[0];
В обычной арифметике результатом сложения 3000 + 2 будет значение
3002. В арифметике указателей такой результат обычно будет неверным. При
прибавлении
или вычитании из указателя целого числа значение его увеличи¬
327
Указатели
вается или уменьшается не на это число, а на произведение числа на размер
который указатель ссылается.
объекта, на
Совет по переносимости программ 7.3
Большинство компьютеров сегодня поддерживают 2-байтовые и 4-байтовые целые.
Некоторые из последних машин имеют еще и 8-байтовые целые Поскольку результат
арифметики указателей зависит от размера объектов, на которые ссылается указатель,
то результат арифметических выражений с указателями является машинно-зависимым.
адреса
3000
3004
3008
переменная-указатель
Рис. 7.18. Массив
Размер объекта
vPtr
+=
в
байтах
3012
3016
vPtx
указатель vPtr, ссылающийся
v и
зависит от типа
на v
объекта. Например, оператор
2;
даст результат 3008
(3000
+ 2 *
4),
если для целого числа отводится в памяти
4
байта. Теперь vPtr будет ссылаться на элемент v[2] (см рис. 7.19). Если бы це¬
лое число занимало 2 байта, то предыдущий расчет дал бы адрес в памяти 3004
(3000 + 2
*
2). Если бы
предыдущий
удвоенный размер объекта данного типа.
При выполнении арифметических операций с указателем на символьный мас¬
сив результаты будут совпадать с арифметическими, потому что под каждый
массив состоял из данных другого типа,
оператор увеличил бы указатель на
символ отводится в памяти один байт.
Если бы vPtr был увеличен до
су
элемента массива
vPtr
-=
v[4],
значения
3016, которое соответствует адре¬
то оператор
4;
вернул бы vPtr к значению 3000, соответствующему началу массива.
При уве¬
личении или уменьшении указателя на единицу можно использовать операции
инкремента
(++)
и декремента
( ) Каждый
из следующих операторов
++vPtr;
vPtr++;
увеличивает значение указателя,
мент массива.
Любой
который будет
ссылаться на
следующий
эле¬
из следующих операторов
vPtr;
vPtr
;
уменьшает значение указателя,
щему элементу массива.
который получает
при этом доступ к предыду¬
328
Глава 7
адреса
3000
3004
Рис. 7.19. Указатель vPtr после
Переменные-указатели
значение
vPtr равно 3000,
3008
3012
3016
арифметического преобразования
могут вычитаться друг из друга.
а
v2Ptr содержит адрес 3008,
Например,
если
то в результате вы¬
полнения оператора
x
=
v2Ptr
переменной
x
-
vPtr;
будет присвоено число элементов массива, расположенных начи¬
и до v2Ptr; в нашем случае это будет значение 2. Обычно
vPtr
ная с адреса
арифметические операции с указателями используются при работе с массива¬
ми. Элементы массива хранятся последовательно, друг за другом, а две пере¬
менные одного и того же типа не обязательно находятся в памяти рядом.
Распространенная ошибка программирования
Использование арифметических
ты массива.
операций
с
7.5
указателем, ссылающимся не на элемен¬
Распространенная ошибка программирования 7.6
Вычитание или сравнение двух указателей, ссылающихся не на один и тот же массив.
Распространенная ошибка программирования 7.7
Выход за начало или конец массива при арифметических операциях
с
указателем.
Указатель может быть присвоен другому указателю, если оба указателя
имеют один и тот же тип. В противном случае нужно использовать операцию
приведения типа указателя в правой части оператора присваивания к типу
указателя в
на
void (т.е.
левой
части.
Исключением
из этого правила является указатель
void *), который является обобщенным указателем и может
любой тип указателя. Указатель любого типа может быть при¬
типа
представлять
своен указателю на void, и void-указатель может быть присвоен указателю лю¬
бого типа. В обоих случаях применение операции приведения типа не требует¬
ся.
Указатель
на
void
не может
быть разыменован. Например, при разымено¬
бай¬
вании указателя на целое компилятор знает, что тот ссылается на четыре
та памяти
(на
машине с целыми числами размером в
4 байта),
тель содержит адрес памяти для неизвестного типа данных,
но
void-указа¬
размер которого
Указатели
329
не известен компилятору.
Компилятор
мым, размер элемента данных в
тель. В случае указателя
делен компилятором.
void размер
на
должен знать тип данных и, тем са¬
байтах, чтобы правильно разыменовать указа¬
элемента в
байтах
не может
быть опре¬
Распространенная ошибка программирования 7.8
Присвоение
из
них
не
значения указателя одного типа указателю другого типа,
является
указателем
типа
void
*,
когда ни один
к синтаксической
приводит
ошибке.
Распространенная ошибка программирования 7.9
Разыменование void-указателя.
Указатели могут сравниваться друг
ния и отношения, но сравнение
с другом при помощи
указателей обычно
операций
сравне¬
не имеет смысла, если они
При
не ссылаются на элементы одного и того же массива.
сравнении
указателей
являющиеся значениями указателей. Сравнение двух
ссылающихся на элементы одного и того же массива, могло бы по¬
сравниваются адреса,
указателей,
казать, например, что один указатель ссылается на элемент с большим значени¬
ем индекса, чем другой указатель. Другая часто используемая операция сравне¬
ния указателя
это проверка, не равно ли его значение
7.8. Связь между указателями
Массивы
ются
и указатели в
С
взаимозаменяемыми.
NULL.
и массивами
тесно связаны друг с другом и практически явля¬
Имя
массива
можно
рассматривать
как
указа¬
тель-константу. А над указателями можно выполнять различные операции, в
том числе использовать с указателем индексные выражения.
Совет по повышению
Ссылка
эффективности 7.3
на элемент массива
при помощи индекса
в выражение с указателем, поэтому, если вы сами
сива
преобразуется во время компиляции
будете обращаться к элементам мас¬
при помощи указателей, то сократите время компиляции вашей программы.
Хороший
стиль программирования 7.5
Ссылайтесь на элементы массива при помощи индексов
Пусть
компилироваться немного дольше, но зато текст программы
В
качестве
ную-указатель
декс)
примера
bPtr
объявим
на целое.
является указателем на
указателю
bPtr адрес первого
Так
целочисленный
как имя массива
первый
ваша
массив
(если
программа
будет более
b[5]
и
будет
понятен.
перемен¬
с ним не указан ин¬
элемент массива, мы можем присвоить
элемента массива
b при помощи оператора при¬
сваивания
bPtr
=
b;
Этот оператор эквивалентен следующему оператору, в котором использу¬
ется операция взятия адреса первого элемента массива:
bPtr
=
&b[0];
330
Глава 7
Альтернативный способ
ссылки на элемент массива
выражение с указателем, представлен
*
(bPtr
в следующем
b[3], использующий
операторе:
3)
+
Константа 3
в приведенном выражении называется смещением. Когда
указатель ссылается на начало массива, величина смещения указывает, на ка¬
кой элемент массива производится ссылка; значение смещения равно значе¬
нию индекса массива. Приведенный способ записи носит название нотации
указатель/смещение. В
этом выражении использованы круглые
*
имеет
му что операция
больший приоритет,
чем
скобки,
операция +.
пото¬
Без круглых
скобок в вышеупомянутом выражении значение 3 было бы прибавлено к зна¬
чению выражения *bPtr (т.е. число 3 будет прибавлено к элементу b[0], так
bPtr указывает
как
на начало
массива). Поскольку
на значение элемента мас¬
сива можно сослаться при помощи выражения с указателем,
&b[3]
bPtr
Имя
+
3
массива может рассматриваться как указатель, так что его можно ис¬
пользовать в выражениях
*
адрес элемента
представляется выражением
(b
+
арифметики указателей. Например, выражение
3)
будет ссылаться на элемент массива b[3]. Вообще говоря, все выражения с ин¬
дексами могут быть преобразованы в выражения с указателем и смещением. В
этом случае в качестве указателя можно использовать имя массива. Обратим
внимание на то, что в предыдущем значение указателя выражении не изменя¬
b, он по-прежнему указывает на первый элемент в массиве.
Указатели, в свою очередь, могут быть использованы вместо имен масси¬
ется
вов
в индексных выражениях.
Например,
выражение
bPtr[l]
представляет собой ссылку на элемент массива
но назвать методом
Не забудьте,
что имя массива
зывает на начало массива.
b
+=
является
b[l]. Такой способ
записи мож¬
указатель/индекс.
это указатель-константа и он всегда ука¬
Поэтому
выражение
3
недопустимым, так как
адреса массива.
в нем делается попытка изменить значение
начального
Распространенная ошибка программирования
7.10
Попытка изменения имени массива, являющегося указателем-константой, приводит
синтаксической ошибке
к
В программе на рис. 7.20 используются четыре метода ссылки на элемен¬
ты массива, которые мы
обсудили
указатель/смеще¬
для выво¬
указатель/смещение,
имя массива и индекс,
ние с именем массива, указатель и индекс и
да четырех элементов целочисленного массива b.
В качестве еще одного примера взаимозаменяемости массивов и указате¬
в про¬
лей рассмотрим две функции копирования строк
copyl и copy2
грамме на рис. 7.21. Обе функции копируют строку символов
вольный массив)
в массив символов.
лютно идентичны. И
действительно,
делают это по-разному.
(возможно, сим¬
Прототипы функций copyl и copy2 абсо¬
они выполняют одну и ту же задачу, но
331
Указатели
/* Использование индексов
#include <stdio.h>
main
(
указателей при работе
с
массивами
указателю bPtr присваивается
адрес массива b */
начальный
и
*/
)
{
int
i,
offset,
b;
int *bPtr
=
b[]
/*
{10,
=
20,
30,
40)};
printf("Array b is printed with:\n"
"Array subscript notation\n");
for
(i
0;
=
for
(offset
=
pnntf("*(b
(i
i + +)
3;
%d\n",
=
0;
+
offset
%d)
i,
b[i]
);
0;
=
pnntf(
i
<=
=
<=
3;
%d\n",
offset++)
offset,
*(b
+
offset));
subscript notation\n");
("\nPointer
printf
for
<=
"\nPointer/offset notation where \n"
"the pointer is the array name\n");
(
printf
i
("b[%d]
printf
3;
bPtr[%d]
i++)
%d\n
=
,
i,
bPtr[i]);
printf("\nPointer/offset notation\n");
for
(offset
=
0;
pnntf("*(bPtr
offset
+
%d)
<=
=
3;
offset++)
%d\n",
offset,
*(bPtr
+
offset));
return 0;
Array b is printed with:
Array subscript notation
10
b[0]
=
b[l]
=
20
b[2]
=
30
b[3]
=
40
Pointer/offset notation where
the pointer is the array name
*(b + 0) = 10
*(b +
*(b +
1)
=
20
2)
=
30
*(b +
3)
=
40
Pointer
subscript notation
10
bPtr[0]
=
bPtr[l]
=
20
bPtr[2]
=
30
bPtr[3]
=
40
Рис. 7.20. Использование четырех методов ссылки на элементы массива (часть 1 из 2)
Глава 7
332
notation
+
10
0)
=
+ 1)
*(bPtr + 2)
*(bPtr + 3)
=
20
=
30
=
40
*(bPtr
IPointer/offset
*(bPtr
Рис. 7.20. Использование четырех методов ссылки на элементы массива (часть 2 из 2)
Копирование строки с использованием индексов
и арифметики указателей */
#include <stdio.h>
/*
void
copyl(char
*,
const
char
*);
void
copy2(char
*,
const
char
*);
с
именем массива
main ()
char
*string2
string4[]
copyl(stringl,
"Hello",
=
stringl[10J,
string3[10},
"Good bye";
=
string2);
%s\n",
stringl);
copy2(string3, string4);
%s\n",
printf("string3
string3);
=
printf("stringl
=
return
0;
}
/*
s2
копирование
void
в
sl
copyl(char *sl,
с
помощью
const
char
индексной
нотации
*/
*s2)
{
int
i;
for
(i
=
0;
sl[i]
/*
s2
копирование
в
void copy2(char *sl,
for
(;
*sl
;
stringl
string3
=
=
=
=
s2[i];
тело
/*
;
sl
с
i++)
цикла
пусто
использованием
const
char
*s2; sl++, s2++)
/* тело цикла
*/
арифметики указателей */
*s2)
пусто
*/
"Hello",
"Good bye";
Рис. 7.21.
Копирование строки
с помощью индексов и
указателей
Функция copyl применяет для копирования строки s2
в массив sl индекс¬
объявляет
ную нотацию. Функция
целую переменную-счетчик i, используе¬
мую как индекс массива. В заголовке цикла for выполняется вся работа по со¬
зданию копии, при этом тело цикла пусто. В заголовке цикла i инициализиру¬
ется нулем и увеличивается на единицу на каждом шаге цикла.
Условие цик¬
Указатели
ла for,
да
333
sl[i]
=
s2[i],
выполняет посимвольное копирование строки
s2 встречается нуль-символ,
в
он присваивается
sl,
тому что целочисленное значение нуль-символа равно нулю
false). Напомним,
жения
s2
в
sl. Ког¬
и цикл завершается, по¬
(а
значение выра¬
что значением оператора присваивания является
значение, получаемое левым операндом.
Функция copy2 при копировании строки s2 в символьный массив sl испо¬
льзует указатели и арифметику указателей. Здесь также все операции копиро¬
вания производятся в заголовке цикла for. В цикле не используется управля¬
ющая переменная. Как и в функции copyl, копирование символов строки вы¬
(*sl
*s2). Указатель s2 разыменовы¬
вается и полученный символ присваивается разыменованному указателю sl.
После выполнения присваивания в выражении условия цикла указатели уве¬
личиваются для перехода соответственно к следующему элементу массива sl и
=
полняется в выражении условия цикла
следующему символу строки s2. Когда в строке s2 встречается нуль-символ,
он присваивается разыменованному указателю sl и цикл завершается.
Обратите
внимание
на то,
что
первый
параметр
функций copyl
и
copy2
должен быть массивом достаточно большого размера, чтобы вместить строку,
передаваемую вторым параметром. В противном случае
произойдет ошибка
при записи в область памяти, которая не является частью массива. Заметьте
*
также, что второй параметр каждой функции объявлен как const char
(стро¬
ка-константа). В обеих функциях второй
аргумент копируется в первый аргу¬
мент, при этом символы из второго аргумента читаются по одному и не изме¬
няются. Поэтому, в соответствии с принципом минимума привилегий, второй
параметр объявляется указателем на значение-константу.
Ни одной из функций не нужно изменять второй аргумент, поэтому ни од¬
ной из них и не предоставляется такое право.
7.9. Массивы
Массивы могут
ва
состоять
это массив строк,
из
который
указателей. Обычный случай
так и называется массив
кого массива является строка, а строки в
ми на
первый
символ строки.
указателей
Значит,
С являются,
такого
элементами строкового массива являют¬
suit,
ко¬
может пригодится для описания игральных карт.
char
*suit[4]
Выражение
При
та¬
по существу, указателя¬
ся указатели на начала строк. В качестве примера рассмотрим массив
торый
масси¬
строк. Элементом
=
{"Hearts",
suit[4]
в
"Diamonds",
объявлении
"Clubs",
"Spades"};
означает массив из четырех элементов.
* этот массив объявляется
помощи char
состоящим из
указателей на тип
«Hearts», «Diamonds», «Clubs»
и «Spades» («Червы», «Бубны», «Трефы» и «Пики»). Каждое из этих значе¬
ний хранится в памяти как строка символов с конечным нулем, длиной на
один символ больше, чем количество символов, заключенных в кавычки.
Строки эти занимают в памяти соответственно 7, 9, 6 и 7 байт. И хотя кажет¬
char. В
массив помещаются четыре значения
ся, что в массив помещаются сами строки, элементами массива являются ука¬
(см. рис 7.22).
Каждый указатель ссылается на первый символ соответствующей строки.
Таким образом, хотя массив suit имеет фиксированный размер, он может
«хранить» символьные строки произвольной длины. Это еще один пример
затели
мощных возможностей структурирования данных в С.
Глава 7
334
+
suit[0]
'\0'
'
4
suit[l]
'D'
i'
suit[2]
'C'
'1'
euit[3]
'S'
Рассматриваемый
имя
помещалось бы
4
'a
'm'
'o'
u'
'b'
s'
a'
'd'
'e'
%\оч
s'
Графическое представление
масти занимало
\0^
массива suit
мастей
можно
бы одну строку,
по одному символу имени масти.
ки массива должны
\0*
'
нами массив карточных
каждой
s'
'
'
'P'
'd'
'n'
'
Рис. 7.22.
двумерным:
'H'
При
было бы сделать
а в каждом
столбце
таком подходе все стро¬
быть одинаковой длины, равной размеру самой длинной
строки символов. Это привело бы к неоправданному
когда большинство сохраняемых строк короче, чем
следующем разделе мы
будем
расходу памяти в случае,
самая длинная строка. В
использовать массивы строк для представления
колоды карт.
7.10.
Пример: программа
тасовки и
сдачи колоды
карт
В
этом разделе, используя генератор
случайных чисел, мы составим про¬
Эту программу можно будет по¬
грамму, моделирующую тасовку и сдачу карт.
том использовать при написании игровых карточных программ.
но использовали неоптимальные алгоритмы тасования и сдачи,
Мы намерен¬
чтобы таким
образом познакомить вас с некоторыми тонкими проблемами эффективности.
В упражнениях и в главе 10 мы разработаем более эффективные алгоритмы.
Действуя методом нисходящего последовательного уточнения, мы разра¬
ботаем программу, которая перетасует колоду из 52 игральных карт и затем
все их раздаст. Нисходящий метод особенно полезен при решении больших и
сложных
проблем,
а не таких простых,
как те, с которыми мы имели дело в
предыдущих главах.
deck разме¬
0
червы, строка 1
бубны, строка 2
трефы, и строка 3 представляет пики. По
столбцам записаны номиналы карт, столбцы с индексом от 0 до 9 соответству¬
ют картам соответственно от туза до десятки, а столбцы с 10-го по 12-й соот¬
име¬
ветствуют вальту, даме и королю. Мы заполним массив suit строками
нами четырех мастей, а массив face
строками, соответствующими тринад¬
Для
ром 4
представления колоды мы используем
на
13 (см. рис. 7.23). Строки
двумерный
массив
массива соответствуют мастям; строка
цати карточным номиналам.
Эта воображаемая колода карт может быть перетасована следующим
зом.
Сначала
бирается
массив
deck
заполняется нулями.
обра¬
Затем случайным образом
вы¬
(число от 0 до 3) и номер столбца column (число в
0-12). В выбранный таким образом элемент массива deck[row][co-
номер строки row
диапазоне
помещается число 1, которое означает, что эта карта из «перетасован¬
ной» колоды будет сдана первой. Этот процесс продолжается далее для чисел
lumn]
2, 3,
52, вставляемых случайным образом в массив deck и обозначающих
карты, которые должны быть сданы второй, третьей и т.д. до пятьдесят вто¬
рой. Как только массив deck начинает заполняться числами, возникает веро...,
335
Указатели
<Q
*
S
о
ffi
<Q
*
а
Ф
ffi
H
Ф
T
<Q
*
>S
О
а
cE
<Q
tt
<Q
tt
а
Ф
2t
Ф
<Q
*
Q.
Ф
2
ф
и
В
<Q
ъс
Q.
Ф
2
А
U
О
0Q
S
<Q
tt
Л
К
ffi
Ф
Ф
С
<Q
0Q
cE
10
Черви
0
Бубны
1
Трефы
2
Пики
3
§
а
<Q
2
<Q
о
*
t*
11
12
f
deck[2] [12] представляет короля треф
Трефы
Рис. 7.23.
^^Король
^
'
Представление колоды карт
в виде двумерного массива
будут выбраны дважды, т.е. при выборе некото¬
deck[row][column] его значение будет отлично от нуля. В этом
случае случайный выбор row и column повторяется до тех пор, пока не будет
найдена карта, которая еще не выбиралась. В конечном счете числа от 1 до 52
займут все 52 элемента массива deck. Тем самым колода будет полностью пе¬
ятность, что
некоторые карты
рого элемента
ретасована.
Этот алгоритм перетасовывания может выполняться неопределенно долго,
были сданы, продолжают выпадать в случайном вы¬
боре. Это явление известно и носит название бесконечной отсрочки. В упраж¬
если карты, которые уже
обсудим улучшенный алгоритм тасования
возможность
бесконечной отсрочки.
устранена
нениях
мы
колоды,
в
котором
Совет по повышению эффективности 7.4
Часто «естественный» алгоритм может столкнуться с неожиданными препятствиями
при своем выполнении, как, например, бесконечная отсрочка. Ищите алгоритмы, ко¬
торые
не
Чтобы
содержат
начать
deck[row][column],
в
себе
сдачу
таких
«бесконечностей»
колоды,
мы
должны
значение которого равно
найти
1. Эту задачу
элемент
массива
выполняют вложен¬
от 0 до 12. Как
for, в которых row изменяется от 0 до 3, а column
узнать, какая карта соответствует данному элементу массива? Так как массив
мастей suit был уже нами определен, для того, чтобы получить название мас¬
ные циклы
ти,
нужно
вывести
строку
suit[row]. Аналогично, чтобы получить название
face[column]. Кроме того, мы вы¬
номинала карты, нам нужно вывести строку
"
водим строку
в
of ". Информация
относительно всех сданных карт выводится
форме "King of Clubs", "Асе of Diamonds"
и т.д.
приступим к процессу нисходящего проектирования. Сначала за¬
пишем нашу задачу в общем виде
Давайте
Тасование и
раздача 52
карт
На первом шаге детализации алгоритма его можно записать так:
Инициализация массива suit
336
Глава 7
Инициализация
Инициализация
массива
face
массива
deck
Тасование колоды
Раздача 52 карт
Пункт «Тасование колоды»
может быть детализирован следующим
обра¬
зом:
каждой из 52 карт
Записать номер карты
элемент массива deck
Для
Пункт «Раздача 52 карт»
Для
каждой
из
выбранный случайным образом свободный
может
быть детализирован следующим образом:
52 карт
Найти номер карты
и масти
в
и вывести название номинала
карты
С учетом сделанных расширений
будет
deck
в массиве
на втором
шаге детализации алгоритм
выглядеть так:
Инициализация
Инициализация
Инициализация
массива suit
массива
face
массива
deck
Для каждой из 52 карт
Записать номер карты
элемент массива
Для каждой из 52 карт
Найти номер карты
и масти
в
выбранный случайным образом свободный
deck
в массиве
deck
и вывести название номинала
карты
Пункт «Записать номер карты в выбранный случайным образом свобод¬
ный элемент массива deck» может быть расширен следующим образом:
Выбрать случайным образом
Пока выбранный
элемент массива
deck
выбирался ранее
Выбрать случайным образом элемент массива deck
Записать номер карты в выбранный элемент массива deck
элемент уже
Пункт «Найти номер карты
масти карты»
Для
можно
в массиве
deck
и вывести название номинала и
детализировать как:
каждого элемента массива
Если
элемент содержит искомый номер карты
Вывести название номинала карты и ее масть
В результате
на третьем шаге детализации алгоритм примет вид:
Инициализация
Инициализация
Инициализация
массива suit
массива
face
массива
deck
Для каждой
из 52 карт
Выбрать случайным образом элемент массива deck
Пока выбранный элемент уже выбирался ранее
Выбрать случайным образом элемент массива deck
в выбранный элемент массива
Записать номер карты
deck
337
Указатели
каждой из 52 карт
Для каждого элемента массива
Если элемент содержит искомый номер карты
Вывести название номинала карты и ее масть
Для
На
этом процесс детализации алгоритма можно завершить.
мание на то, что эту программу можно сделать
ритмы тасования и раздачи
карту.
объединить
Мы выбрали раздельный вариант
к реальности:
карты сначала тасуются,
Программа
и
Обратите
более эффективной, если
сразу сдавать помещенную
на рис.
в колоду
этих алгоритмов, поскольку он
ближе
а потом сдаются.
тасования и сдачи колоды карт приведена на рис.
мер ее исполнения
вни¬
алго¬
7.25. Обратите
7.24,
а при¬
внимание на использование спе¬
цификатора формата %s функции printf для вывода строки символов.
ветствующий этому спецификатору аргумент функции printf должен
указателем на тип char (или символьным массивом).
кация "%5s of %-8s" выводит символьную строку,
Соот¬
быть
В функции deal специфи¬
выровненную
по правому
"
краю, в поле из пяти символов, за
которой
волов, выровненную по левому краю,
спецификации %-8s
шириной 8 символов.
нус» в
поле
/*
#include
<stdio.h>
#include
<stdlib.h>
#include
<time.h>
of ",
и
строку сим¬
в поле из восьми символов.
Знак
колоду
shuffle(int
[][13]);
void deal(const int [][13],
карт
*/
void
main ()
{
char *suit [4]
char *face[13]
=
=
int deck[4][13]
=
const char *[],
const char *[]);
{"Hearts", "Diamonds", "Clubs", "Spades"};
{"Ace", "Deuce", "Three", "Four",
"Five", "Six", "Seven", "Eight",
"Nine", "Ten", "Jack", "Queen", "King"};
{0};
srand(time(NULL));
shuffle(deck);
deal(deck, face,
suit);
return 0;
}
void shuffle(int wDeck[][13])
{
int card, row, column;
for
1; card <= 52;
rand() % 4;
column
rand() % 13;
(card
row
=
«ми¬
как раз и означает выравнивание по левому краю в
сдающая
Программа,
следует строка
card++)
{
=
=
Рис. 7.24. Программа раздачи колоды карт (часть 1
из
2)
Глава 7
338
while
!=
(wDeck[row][column}
rand() % 4;
column
rand() %
row
0)
{
=
13;
=
}
card;
=
wDeck[row][column]
}
}
void deal(const int wDeck[
const char *wSuit[ ])
const
][13],
char
*wFace[
],
{
int
card,
for
(card
for
row,
column;
1;
card <=
=
(row
for
=
0;
(column
if
row
=
52;
3;
<=
0;
card++)
row++)
column
12;
<=
column++)
card)
printf("%5s of %-8s%c",
wFace[column], wSuit[row],
card % 2
0 ? '\n'
:
'\t');
(wDeck[row][column]
==
==
Рис. 7.24. Программа раздачи колоды карт (часть 2 из 2)
Six of Clubs
Асе
Асе
Queen
Ten
of
Spades
of Hearts
of Hearts
Deuce
of Clubs
Three
of
Four
of
Ten
Four
of Diamonds
Six of Diamonds
Eight of Hearts
Nine of Hearts
Five
of Diamonds
of Clubs
Spades
of Diamonds
Deuce
Ace
Queen of Diamonds
Seven of Hearts
Ten of
Deuce
Seven of Diamonds
of
Spades
of Clubs
of Diamonds
Five of Spades
King of Diamonds
Deuce of Hearts
Асе
of Clubs
Three
of .Clubs
Nine
of Clubs
Four
of Hearts
Eight of Diamonds
Jack of Diamonds
Spades
Spades
Ten of Clubs
Six of
Three
Spades
of Diamonds
Three
of Hearts
Six of Hearts
Eight of Clubs
Eight of Spades
King of Clubs
Jack of Spades
Queen of Hearts
King of Spades
King of Hearts
Nine of Spades
Queen of Spades
Nine of Diamonds
Seven of Clubs
Five
of Hearts
Five
of Diamonds
Four
of Clubs
Jack
of Hearts
Jack
of Clubs
Seven of
Spades
Рис. 7.25. Пример выполнения программы сдачи колоды карт
339
Указатели
У нашего алгоритма сдачи карт есть один недостаток. Когда найдена нуж¬
ная карта, иногда с первой попытки, два внутренних цикла for продолжают
поиск в оставшейся части массива deck. В упражнениях и в главе 10 мы исп¬
равим этот
дефект.
7.11. Указатели на
Указатель
функции
это переменная, содержащая адрес в памяти, по
функцию
функция. Из главы 6 мы знаем, что имя массива являет¬
ся адресом первого элемента массива. Аналогичным образом имя функции
это адрес начала программного кода функции. Указатели на функции могут
быть переданы функциям в качестве аргументов, могут возвращаться функция¬
на
которому расположена
ми, сохраняться в массивах и присваиваться другим указателям на функции.
Чтобы проиллюстрировать использование указателей на функции, мы
возьмем программу пузырьковой сортировки, приведенную на рис. 7.15,
здадим
новый
ее вариант, код которого приведен на рис.
7.26. Наша
и со¬
новая про¬
функций main, bubble, swap, ascending и descending. Функ¬
это может быть функция ascen¬
функцию
в
к
дополнение
двум другим параметрам: цело¬
функция descending
грамма состоит из
ция bubble получает указатель на
ding
или
численному массиву и размеру массива. Во время исполнения программа запра¬
шивает у пользователя способ сортировки
в порядке возрастания или убыва¬
Если пользователь вводит
ния.
на
функцию ascending
Если пользователь вводит
растанию.
указатель
ванию.
функцию descending и
Пример двух сеансов работы
на
В заголовке функции bubble
int
(*
1, функции bubble передается указатель
число
и производится сортировка переданного массива по воз¬
compare)(int,
число
2,
то в
функцию bubble передается
убы¬
производится сортировка массива по
с
программой показан на рис. 7.27.
следующий параметр:
имеется
int)
Это значит, что функция bubble получает аргумент,
являющийся указате¬
функцию, которая имеет два целочисленных параметру и возвращает
результат целого типа. Вокруг *compare необходимы круглые скобки, потому
что операция * имеет более низкий приоритет, чем скобки, в которые заклю¬
чены параметры функции. Если мы уберем круглые скобки, то объявление бу¬
лем на
дет иметь вид
int
и
*
compare
(int,
int)
будет обозначать функцию, которая
получает два целых числа как парамет¬
ры и возвращает указатель на целое число.
Прототип функции bubble
int
(*)
(int,
может
быть объявлен
и в таком виде:
int)
Обратите внимание на то, что обязательны только объявления типа дан¬
Программист может включать в прототип имена, чтобы текст программы
был более ясным. Компилятор будет их игнорировать.
Функция, переданная функции bubble, вызывается в операторе if следую¬
ных.
щим образом:
if ((*compare)
Подобно тому,
(work
[count],
лучить ее значение, указатель на
сти вызов
work
[count
+
1]))
как разыменовывают указатель на переменную,
этой функции.
чтобы
по¬
функцию разыменовывают, чтобы произве¬
Глава 7
340
/*
Многоцелевая программа
использующая указатели
сортировки,
функцию */
#include <stdio.h>
#define SIZE 10
на
void bubble(int *, const
int ascending(const int,
int descending(const int,
int
int,
const
int));
(*)(int,
int);
const
int);
main ()
{
int
=
а[SIZE}
int counter,
{2, 6,
order;
8,
4,
10,
89,
12,
68,
45,
37};
printf("Enter 1 to sort in ascending order,\n");
printf("Enter 2 to sort in descending order: '*);
scanf("%d", &order);
printf("\nData items in original order\n");
for
(counter
=
printf("%4d",
if
(order
==
bubble(a,
counter
0;
<=
SIZE
-
1;
counter++)
a[counter]);
1) {
SIZE,
printf("\nData
ascending);
items in ascending order\n");
}
else
{
bubble(a,
SIZE,
printf("\nData
for
(counter
=
printf
("\n"
return
0;
void bubble(int
counter
0;
printf("%4d",
descending);
items in descending order\n");
<=
SIZE
-
1;
counter++)
a[counter]);
);
const
*work,
int
size,
int
(*compare)(int,
int))
{
int pass, count;
void swap(int *,
for
(pass
for
=
1;
(count
if
int
*);
pass
=
0;
<=
size
count
<=
-
1;
size
pass++)
-
2;
count++)
((*compare)(work[count], work[count +
swap(&work[count], &work[count + 1]);
Рис. 7.26. Многоцелевая программа сортировки, использующая указатели
(часть 1
из
2)
1]))
на
функцию
341
Указатели
void swap(int
{
mt temp;
int
*elementlPtr,
*element2Ptr)
*elementlPtr;
temp
*elementlPtr
*element2Ptr;
*element2Ptr
temp;
=
=
=
int ascending(const
{
return b < a;
int a,
const
descendmg(const int a,
int
int b)
const
int
b)
{
return
>
b
a;
Рис. 7.26. Многоцелевая программа сортировки, использующая указатели на
(часть 2
Enter
1
to
sort
in
Enter
2
to
sort
in
Data
2
items
6
Data
2
items
4
8
in
6
8
45
37
68
89
1
to
sort
in
to
sort
in
items
6
Data
89
68
Рис. 7.27.
in
4
items
45
37
ascending order,
descending order:
10
12
89
68
descending order
12
6
10
8
2
45
37
4
2
Результаты работы программы пузырьковой сортировки, приведенной
Функцию можно
следующей строке:
if
89
original order
8
in
order
68
2
2
1
ascending order
12
37
45
10
Enter
функцию
2)
ascending order,
descending order:
original
12
10
Enter
Data
в
in
4
из
на рис. 7 26
вызвать и без разыменования указателя, как это сделано
(compare(work[count],
work[count
+
1]))
Здесь указатель используется непосредственно, как имя функции. Мы
с разыменованием указателя, потому
предпочитаем первый метод вызова
что в этом варианте явно видно, что compare является указателем на функ¬
При втором методе вызова функции через указатель может создаться
это имя функции. В результате пользователь про¬
впечатление, что compare
цию.
граммы может запутаться, если попытается
найти определение функции com¬
обнаружит, что она нигде в файле не определена.
Указатели на функции часто используются в системах, управляемых посред¬
ством меню. Пользователь выбирает команду меню (например, одну из пяти).
Каждая команда обслуживается своей функцией. Указатели на каждую функ¬
цию находятся в массиве указателей. Выбор пользователя служит индексом, по
которому из массива выбирается указатель на нужную функцию.
pare
и
342
Глава 7
Программа
на рис.
зования массива
tionl, function2
7.28
типовой пример объявления
показывает
указателей
function3,
и исполь¬
функцию. Определяются три функции
на
func-
каждая из которых имеет целочисленный па¬
раметр и не возвращает значения. Указатели на эти три функции находятся в
массиве f, который объявляется так:
(*f[3]
void
и
)
(int)
Объявление читается,
массив из трех
указателей
не возвращает значения».
Когда
(functionl,
=
на
function3);
function2,
самой левой скобки
начиная с
функцию, которая
и звучит как:
«f
имеет параметр целого типа и
Массив инициализируется именами трех функций.
0 до 2, это значение используется в ка¬
пользователь вводит значение от
указателей
образом:
(*f[choice])(choice);
честве индекса массива
на
функцию. Вызов функции
выполняется
следующим
/*Пример
использования
массива
указателей
на
функцию */
#include <stdio.h>
void functionl(int);
void function2(int);
void function3(int);
main ()
{
void (*f[3])(int)
int choice;
printf("Enter
scanf("%d",
while
а
=
(functionl,
number
between
function2,
0
and
2,
3
function3);
to
end:
");
&choice);
(choice
>=
0
&&
choice
<
3)
{
(*f[choice])(choice);
printf("Enter a number
scanf("%d", &choice);
between
0
and
2,
3 to end:
");
}
printf("You entered 3 to end\n");
return 0;
void functionl(int a)
{
printf("You entered %d
so
functionl
was
called\n\n",
a);
void function2(int b)
{
printf("You entered %d
so
function2
was
called\n\n",
b);
void function3(int c)
{
printf("You entered %d
so
function3
was
called\n\n",
c);
Рис. 7.28. Пример
массива
указателей
на
функцию (часть 1
из
2)
343
Указатели
number
а
You entered 0
Enter
number
a
IEnter
You entered 1
Enter
a
so
number
0
0
between
0
was
was
was
2
3 to end:
called
2,
and
1
3 to end:
called
2,
and
0
called
2,
and
function3
3 to end:
2,
and
function2
between
number
a
0
functionl
between
so
You entered 2
Enter
between
so
3 to end:
3
You entered 3 to end
В
Рис. 7.28. Пример
массива
указателей
f[choice] выбирает
этом вызове
на
функцию (часть 2
из
2)
указатель по индексу choice.
функции указатель разыменовывается,
а переменная
Для
вызова
choice передается функ¬
в качестве аргумента. Каждая функция выводит значение полученного
аргумента и свое имя, что позволяет убедиться в правильности вызова функ¬
ции. В упражнениях вы разработаете программу, управляемую с помощью
меню.
ции
Резюме
Указатели
жат
являются переменными, которые в качестве значения содер¬
адреса
других
переменных.
Указатели должны быть объявлены, прежде
чем они
будут
использова¬
ться.
В строке
int *ptr;
переменная ptr объявляется указателем
на
объект целого типа,
что чи¬
*
используется здесь
указатель на целое». Символ
как знак того, что переменная является указателем.
тается
как
«ptr
Имеются три значения, которые могут
использоваться для инициализа¬
0 (ноль), макрос NULL или адрес. Инициализации ука¬
значением 0 и NULL
идентичны.
ции указателя:
зателя
Единственное
это
целое число, которое может быть присвоено указателю
0.
Операция
взятия
адреса
(&)
возвращает адрес своего операнда.
операции взятия адреса должна быть переменная; операция
& не может применяться к константам, выражениям или к перемен¬
Операндом
ным,
объявленным
с
модификатором register.
операцией косвенной адресации или разыме¬
нования, возвращает значение объекта, на который ссылается ее опе¬
ранд. Это называется разыменованием указателя.
Операция *,
называемая
Когда функции передается аргумент, который
мента в
вызывающей
она
должна
изменить,
Для того, чтобы изменить значение аргу¬
функции, вызываемая функция должна исполь¬
следует передавать его адрес.
зовать операцию разыменования
(*).
344
Глава 7
Аргументу, который
передается по ссылке, должен соответствовать па¬
раметр-указатель.
В прототипах функций должны быть обозначены типы указателей, при
этом имена указателей можно не включать. Имена указателей могут
быть
При
включены в качестве комментариев, компилятор игнорирует их.
помощи модификатора const программист может
что
лятору,
значение
переменной
не
должно
сообщить
компи¬
изменяться.
перехватывает попытки изменения значения переменных,
объявленных с модификатором const и выдает либо предупреждение,
либо сообщение об ошибке, в зависимости от того, с каким компилято¬
Компилятор
работаете.
вы
ром
четыре способа передачи указателя в функцию: не-константный указатель на не-константные данные, указатель-константа на
Существуют
не-константные данные, указатель
ты
и
указатель-константа
Массивы
ва
не-константа на данные-констан¬
на данные-константы.
автоматически передаются по ссылке, потому что имя масси¬
является
указателем
на
начало
массива.
передачи по ссылке отдельного элемента массива нужно переда¬
вать его адрес.
Для
Во время компиляции программы можно определить
(или любого другого типа данных) в байтах, используя
размер
массива
унарную опера¬
цию sizeof.
Операция sizeof,
личество
примененная
байтов
в
Операция sizeof
массиве
к имени массива,
как
целое
может применяться к
возвращает
общее
ко¬
число.
переменной любого
типа или к
константе.
С указателями могут
или
+=)
целого
выполняться
следующие
арифметические опера¬
указателя, декремент ( ) указателя, сложение (+
указателя и целого числа, вычитание (- или -=) из указателя
(++)
ции: инкремент
числа
и
вычитание
При прибавлении
одного
указателя
из
другого.
или вычитании целого числа из указателя
увеличивается или уменьшается
объекта, на который ссылается
последний
на произведение этого числа и размера
указатель.
Арифметические
операции над указателем следует применять при ра¬
боте с «непрерывными» областями памяти, например, с массивом. Все
элементы массива располагаются в памяти последовательно, друг за
другом.
Результаты арифметических операций над указателем на символьный
массив будут совпадать с результатами традиционной арифметики, по¬
тому
что
для
хранения
одного
символа
отводится
один
байт
памяти.
Указатели могут присваиваться друг другу, если оба указателя имеют
один и тот же тип. В противном случае нужно выполнять операцию
приведения
на
типа.
Исключением
из
этого
правила является
void, являющийся обобщенным указателем, который
ться на данные
любого
чение указателя
значение
типа.
Указателю
на
void
любого типа, указателю любого
void-указателя без
приведения типа.
указатель
может ссыла¬
можно присвоить зна¬
типа можно присвоить
Указатели
345
Указатель
void
типа
К указателям
*
не
элементы
разыменовываться.
можно применять операции равенства и сравнения.
указателей обычно
нение
может
одного
С указателями,
и
того
имеет
же
смысл,
если
указатели
Срав¬
ссылаются
на
массива.
как и с именами массива,
можно использовать индек¬
сы.
Имя
массива
без индекса
является указателем на
первый
элемент мас¬
сива.
В нотации указатель/смещение смещение
индекс
имеет
тот
же
смысл,
что
и
массива.
Любые выражения
с
индексацией
массива могут быть запис* чы с помо¬
щью указателя и смещения, где в качестве указателя может использо¬
ваться
ло
либо
имя массива,
либо иной указатель, ссылающийся
на нача¬
массива.
Имя
указателем-константой
массива является
в памяти. В отличие от
обычных указателей
на
фиксированное
место
его значение не может из¬
меняться.
Указатели
Указатели могут
Указатель
кода
объединять
можно
ссылаться
функцию
функции.
на
в
на
массивы.
функции.
ссылается
на
начальный
адрес
программного
на функции могут быть переданы функциям, возвращаться
функциями, сохраняться в массивах и присваиваться другим указате¬
Указатели
лям.
Часто указатели
на
функции
находят применение в системах, управля¬
емых с помощью меню.
Терминология
const
sizeof
void
*
константный указатель на
не-константные данные
(указатель на void)
арифметика указателей
бесконечня отсрочка
косвенная адресация
косвенная ссылка на переменную
массив строк
вызов по значению
вызов по ссылке
массив указателей
не-константный указатель
выражение с указателями
вычитание двух указателей
вычитание целого из указателя
на не-константные данные
не-константный указатель
на константные данные
генерация вызова по ссылке
нисходящее пошаговое уточнение
декремент указателя
нотация
динамическое распределение
памяти
операция
указатель/смещение
операция взятия адреса
косвенной
(*)
(*)
привилегий
адресации
индексация указателя
операция разыменования
инициализация указателей
принцип минимума
инкремент указателя
константный указатель
присваивание указателя
прямая ссылка на переменную
константный указатель на
константные данные
разыменование указателя
связанный список
346
Глава 7
сложение указателя
с
указатель NULL
указатель на void (void *)
целым
смещение
указателей
указателей
указатель
сравнение
указатель
на
символ
типы
указатель
на
функцию
Распространенные ошибки программирования
7.1.
Знак операции косвенной адресации * не распространяется на все
*
должен предшествовать
переменные в строке объявления. Символ
имени каждого указателя.
7.2.
Разыменование указателя, который не был инициализирован
которому не присвоили
необходимое
фатальной ошибке во
случайной порче данных, в
к
время
значение адреса.
выполнения
или
Это приводит
программы
или
результате чего выполнение программы
завершается с неверным результатом.
7.3.
Не
7.4.
Функции ошибочно
разыменовывайте указатель, кроме случая, когда вам нужно по¬
лучить значение, на которое он ссылается.
передается параметр по значению, а не по ссыл¬
ке. В таких случаях некоторые компиляторы поступят с получен¬
ным аргументом как с указателем и произведут разыменование это¬
го «указателя». В результате во время выполнения программы
произойдет ошибка
обращения
к
памяти или
Компиляторы, проверяющие аргументы
ошибка сегментации.
параметры на соответст¬
и
вие типов, выдадут сообщение об ошибке.
7.5.
Использование
щимся
7.6.
на
не
Вычитание
арифметических
элементы
или
операций
с
указателем,
ссылаю¬
массива.
сравнение
двух
указателей,
ссылающихся
не
на
один и тот же массив.
7.7.
7.8.
Выход за начало или конец массива при арифметических операциях
с указателем.
Присвоение
значения
указателя
одного
типа
указателю
типа, когда ни один из них не является указателем типа
водит
7.9.
к
синтаксической ошибке.
Разыменование void-указателя.
7.1.0Попытка
изменения
стантой, приводит
Хороший
7.1.
другого
void *, при¬
стиль
являющегося
указателем-кон¬
в имена
переменных-указателей префикс (или суффикс)
такие имена подскажут вам, что эти переменные являются ука¬
зателями
и
должны
7.2. Чтобы избежать
те
массива,
синтаксической ошибке.
программирования
Включайте
ptr;
имени
к
указатели.
обрабатываться
соответствующим
неожиданных результатов, всегда
образом.
инициализируй¬
347
Указатели
7.3.
Используйте вызов по
мая функция должна
ссылке только в том случае,
передаваемый ей
минимума привилегий.)
изменять
один пример принципа
7.4. Прежде
Ссылайтесь
программа
программы
может
функцию, посмотрите на ее прототип, что¬
функция изменять переданные ей значения.
ли
на элементы массива при помощи индексов.
Пусть
ваша
немного дольше, но зато текст
будет компилироваться
будет более понятен.
Советы по повышению
7.1. При
(Еще
аргумент.
чем использовать
бы узнать,
7.5.
когда вызывае¬
эффективности
передаче больших объектов данных, например, структур, испо¬
на константные данные. Этот подход позволит
льзуйте указатель
вам совместить производительность передачи параметра по ссылке и
защиту
7.2.
как
ся
параметра
передаче
и
дополнительных
на элемент массива при
время компиляции
в
значению.
стеке
для
создания
переменным не
как
они
то
сократите
помощи
доступны
индекса
выражение с указателем,
в
будете обращаться
сами
так
ресурсов,
по
требует¬
любом
в
программы.
Ссылка
лей,
места
передаваемой функции. Глобальным
этих
месте
7.4.
при
Передача параметра требует времени
копии
7.3.
данных
преобразуется
во
вы
к элементам массива при помощи указате¬
время
вашей
компиляции
Часто «естественный» алгоритм
программы.
может столкнуться с неожиданны¬
ми препятствиями при своем выполнении, как, например,
ная отсрочка.
если
поэтому,
Ищите алгоритмы, которые
не содержат в
бесконеч¬
себе
таких
«бесконечностей».
Советы по переносимости программ
7.1.
Хотя модификатор const определен
в
стандарте ANSI С, некоторые
системы его не поддерживают.
7.2.
Размер
в
байтах одного
различных системах.
тать на различных
и того же типа данных может
При
быть разным
в
будут рабо¬
небезразличны к пред¬
написании программ, которые
платформах
и
при
этом
ставлению данных, используйте операцию sizeof для определения
размера типа данных.
7.3.
Большинство
компьютеров
сегодня
поддерживают
2-байтовые
и
4-байтовые целые. Некоторые из последних машин имеют еще и
8-байтовые целые. Поскольку результат арифметики указателей за¬
висит от
льтат
размера объектов, на которые ссылается указатель,
арифметических выражений
то резу¬
с указателями является машин¬
но-зависимым.
Общие
7.1.
методические замечания
Употребление модификатора const
соблюдения принципа
можно
минимальных
рассматривать
в
рамках
привилегий. Следование
этому
348
Глава 7
принципу позволяет существенно сократить время отладки програм¬
мы и количество побочных нежелательных эффектов, а также дела¬
ет программу проще для понимания и сопровождения.
7.2.
Если передаваемое
функции значение не изменяется (или не должно
функции, оно должно объявляться с модифи¬
катором const, чтобы гарантировать невозможность даже случайно¬
быть изменено)
го
7.3.
ее
в теле
изменения.
В результате вызова по значению в вызывающей функции может
быть изменена только одна величина. Этой величине должно быть
присвоено возвращаемое вызванной функцией значение. Для изме¬
нения сразу нескольких значений в вызывающей функции необхо¬
димо использовать вызов по ссылке.
7.4.
Размещение прототипа функции в теле другой функции ограничива¬
ет корректные вызовы первой функции только теми, которые будут
сделаны из функции, где объявлен прототип. Это опять принцип ми¬
нимума привилегий.
7.5. При
передаче в функцию массива следует передавать и его размер.
Это сделает функцию более универсальной. Универсальные функ¬
ции могут использоваться большим количеством программ.
7.6.
Глобальные переменные нарушают принцип минимальных привиле¬
гий и являются примером плохого стиля программирования.
Упражнения
7.1.
для самоконтроля
Заполните пропуски в предложениях:
a) Указатель
это переменная, которая в качестве значения содер¬
другой переменной.
жит
b) Только
три величины могут использоваться для инициализации
указателя:
c) Единственное
Являются
.
целое число, которое может быть присвоено указа¬
телю, это
7.2.
или
,
.
ли следующие утверждения
верными? Если
утверждение
неверно, объясните, почему.
a) Операция
взятия адреса
&
может применяться только к констан¬
там, выражениям и переменным,
gister.
b) Указатель
c) Указатели
на
void
может
объявленным
быть
с
быть присвоены друг
Выполните следующие задания. Предположим,
щей точкой обычной
re¬
разыменован.
на разные типы данных не могут
другу без использования операции приведения
7.3.
модификатором
точности занимают 4
типов.
что числа с плаваю¬
байта,
и что
начальный
адрес массива равен 1002500. Учтите, что при выполнении очеред¬
ного задания нужно использовать результаты предыдущих
(там,
где это
необходимо).
заданий
Указатели
349
a) Объявите
элементам
скую
массив
numbers
типа
float
0.0, 1.1, 2.2,
SIZE, равную 10.
значения
константу
...,
из 10 элементов и присвойте
9.9. Используйте символиче¬
b) Объявите указатель nPtr, ссылающийся
c) Выведите
элементы
на
тип
float.
numbers, используя
массива
нотацию
имя
массива/индекс. Используйте цикл for и переменную цикла i. Выве¬
дите каждый элемент с точностью одного знака после запятой.
d) Присвойте указателю nPtr адрес
способами.
e) Выведите
элементам
элементы
массива
массива
по
начала массива
numbers двумя
numbers при помощи обращения
указатель/смещение,
методу
к
где в качестве
указателя используется nPtr.
f) Выведите
элементам
элементы
массива
массива
по
numbers при помощи обращения
указатель/смещение,
методу
к
где в качестве
указателя используется имя массива.
g) Выведите
элементы массива
numbers, используя
индексацию ука¬
зателя nPtr.
h) Сошлитесь
на
способа доступа
четвертый
элемент массива,
к элементам массива: имя
сива/смещение, указатель/индекс
с
используя все четыре
массива/индекс,
указателем
nPtr
имя мас¬
и
указа¬
тель/смещение с указателем nPtr.
i) Если предположить, что nPtr указывает на начало массива num¬
bers, то на какой адрес ссылается nPtr + 8? Что за значение нахо¬
дится по указанному адресу?
j) Предположим,
ссылается nPtr
что
-=
nPtr
ссылается на
4? Какое
значение
numbers[5];
находится
по
на
какой адрес
этому
адресу?
7.4. Выполните
каждую из следующих задач при помощи одиночного
оператора. Считайте объявленными переменные с плавающей точ¬
кой numberl и number2, причем numberl присвоено значение 7.3.
a) Объявите указатель fPtr
на
тип
float.
b) Присвойте адрес переменной numberl указателю fPtr.
c) Выведите
значение
d) Присвойте
величины,
значение величины,
на
которую
ссылается
на которую указывает
fPtr.
fPtr, пере¬
менной number2.
e) Выведите
значение
number2.
f) Выведите адрес numberl. Используйте спецификатор формата %p.
g) Выведите адрес, сохраненный в fPtr. Используйте спецификатор
формата %p. Совпало ли выведенное значение адреса с адресом
numberl?
7.5.
Выполните следующие задания.
a) Напишите
заголовок
функции exchange, которая имеет два параплавающей точкой x и у и не воз¬
метра-указателя на переменные с
вращает значения.
b) Напишите
прототип для функции
из
предыдущего
задания.
350
Глава 7
c) Напишите
функции evaluate, которая возвращает
заголовок
це¬
лое число и имеет в качестве параметров целое число x и указатель
на
функцию poly. Функция poly,
ный параметр
d) Напишите
7.6.
Найдите
и
возвращает
в свою
целое
очередь, имеет целочислен¬
число.
прототип для функции из предыдущего задания.
в
ошибку
из
каждом
следующих
фрагментов программ.
Учитывайте следующую информацию:
int
*zPtr;
int
*aPtr
void
*sPtr
number,
mt
z[5]
=
++zptr;
b)
/*
2,
3,
элементов
0;
i
printf("%d
присвоение
ссылается
number
f)
первого
элемента
number
значения
второго
элемента
*zPtr[2];
=
=
/*
получения
zPtr;
=
/* вывод
for (i
для
указателя
*/
присвоение переменной
массива (число 3)*/
e)
*/
5};
4,
c) /*
d)
z
NULL;
использование
массива
number
массив
z;
a)
number
на
I;
{1,
=
ссылается
NULL;
=
int
sPtr
zPtr
/*
=
массива
<=
5;
",
zPtr[i]);
*/
z
I++)
переменной number
значения,
на
которое
*/
sPtr
указатель
*sPtr;
=
+ + z;
Ответы на упражнения для самоконтроля
7.1.
7.2.
а) адрес, b) 0, NULL, адрес, с) 0.
а) Неверно. Операция
переменным,
взятия
адреса
причем только к тем,
модификатор register.
Указатель
типа void не
b) Неверно.
может
при
применяться
только
объявлении которых
к
не ис¬
пользовался
му что неизвестно, сколько
полученное
при
байт
может
быть разыменован,
пото¬
памяти должцо занимать значение,
разыменовании.
c) Неверно. Указатели типа void могут быть присвоены указателям
других типов, и void-указателям можно присваивать значения ука¬
зателей других
7.3.
типов.
а) float numbers[SIZE]
b) float
c)
for
nPtr
nPtr
{0.0,
б.б,
1.1,
7.7,
*nPtr;
(i
=
0;
i
<=
pnntf("%.lf"
d)
=
SIZE
,
=
numbers;
=
&numbers[0];
-
1;
I++)
numbers[i]);
2.2,
8.8,
3.3, 4.4,
9.9};
5.5,
351
Указатели
e)
for
i
0;
=
(i
<=
",
f)
for
(i
i
0;
=
<=
",
g)
for
0;
=
(i
i
<=
",
1;
-
i));
I++)
+
i));
*(numbers
1;
-
SIZE
printf("%.lf
I++)
+
*(nPtr
SIZE
printf("%.lf
1;
-
SIZE
printf("%.lf
I++)
nPtr[i]);
h) numbers[4]
+
*(numbers
nPtr[4]
*
(nPtr
i) Адрес
ся
+
4)
равен 1002500 + 8
*
4
1002532. По этому адресу находит¬
=
8. 8.
значение
j) Адрес
4)
элемента
numbers[5]
В результате выполнения
4 * 4
1002504.
равен 1002500 + 5
*
4 адрес
в
операции
nPtr
-=
4
=
1002520.
nPtr 1002520
-
=
По этому адресу находится
7.4.
а) float *fPtr;
b)
fPtr
=
&numberl;
c) printf("The
d)
number2
value
of
*fPtr
of
number2
value
f) printf("The
address
of
g) printf("The
address
stored
эти
*fPtr);
%f\n",
значения
in
%f\n",
is
fPtr
number2);
%p\n",
is
&numberl);
%p\n",
fPtr);
совпадают.
а) void exchange(float *x,
c) int evaluate(int
x,
d) int evaluate(int,
а) Ошибка: zPtr
is
numberl
b) void exchange(float *,
7.6.
is
*fPtr;
=
e) printf("The
Да,
7.5.
1.1.
значение
не
float *);
int
int
float *y)
(*poly)(int))
(*)(int));
был инициализирован.
Исправление: инициализируйте zPtr при помощи следующего выра¬
жения zPtr
z;
=
b) Ошибка:
указатель не был разыменован.
Исправление: воспользуйтесь
*zPtr;
Ошибка:
zPtr[2] не является
c)
ber
оператором
следующего вида:
num¬
=
указателем и не должен разыменовы¬
ваться.
Исправление: замените *zPtr[2] на zPtr[2].
d) Ошибка: ссылка на элемент массива со значением
рое
вышло
за границы
Исправление:
замените конечное значение
переменной
значение 4.
e) Ошибка:
индекса, кото¬
массива.
разыменование
void-указателя.
цикла
for на
Глава 7
352
Исправление: чтобы
разыменовать этот указатель, нужно сначала
к
его
виду указателя на целое. Примените оператор вида
привести
number
(int *) sPtr;
=
*
f) Ошибка: попытка изменить значение
ва
в арифметическом выражении.
Исправление: используйте
имени
для
арифметическом выражении
в
массива переменную-указатель,
обращения
к
нужному
имени масси¬
указателя
или
индекс
и
имя
вместо
массива,
элементу.
Упражнения
7.7.
Заполните пропуски
в
предложениях:
возвращает адрес своего операнда.
a) Операция
возвращает значение объекта, на который
b) Операция
операнд указывает.
c) Для
того, чтобы произвести вызов по ссылке при передаче пере¬
менной
(не массива) в качестве
переменной.
7.8. Являются
аргумента функции, нужно передать
ли следующие утверждения
верными? Если
утверждение
неверно, объясните, почему.
a) Не
на
имеет
смысла сравнивать два указателя,
различные
b) Поскольку
массива,
гично
7.9.
которые
указывают
массивы.
имя массива является указателем на
имена
массивов
могут
использоваться
первый
элемент
совершенно
анало¬
указателям.
Выполните следующие задания. Считайте, что целые
ка занимают в памяти 2 байта, и что начальный адрес
1002500.
числа
без
зна¬
массива равен
a) Объявите массив типа целое без знака с именем values, состоящий
из 5 элементов, и инициализируйте элементы массива четными це¬
лыми числами от 2 до 10. Используйте символическую константу
SIZE, равную 5.
b) Объявите указатель vPtr
c) Выведите
на
объект
values
типа
unsigned int.
обращения к
массива/индекс. Используйте для
управляющей переменной i, которую считайте
элементы массива
с использованием
элементам массива по методу имя
этой цели цикл for с
объявленной.
d) Запишите
два оператора, при помощи которых можно присвоить
начальный адрес массива values указателю vPtr.
e) Выведите элементы
тель/смещение.
массива
values, используя
нотацию указа¬
f) Выведите элементы массива values, используя нотацию указа¬
тель/смещение, где в качестве указателя используется имя массива.
g) Выведите
элементы массива
лем на массив.
values,
используя индекс с указате¬
353
Указатели
h) Сошлитесь
5-й
values, используя индексацию
указатель/смещение с именем массива в качестве
указателя, индексацию указателя и нотацию указатель/смещение.
i) На какой адрес ссылается выражение vPtr + 3? Чему равно нахо¬
на
элемент массива
массива, нотацию
дящееся по этому адресу значение?
j) Если
предположить, что vPtr ссылается на values[4], чему будет
равно значение адреса, находящегося в vPtr, после выполнения опе¬
ратора
vPtr
7.10. Выполните
4? Какое
-=
значение
по
находится
данному
заданий, используя
Считайте, что переменные valuel
long объявлены и переменной valuel присвоено
каждое
адресу?
для этой цели
из следующих
value2
только один оператор.
и
типа
значение
200000.
a) Объявите указатель lPtr
b) Присвойте
c) Выведите
значение
значение
d) Присвойте
объект данных
на
переменной valuel
адреса
объекта,
который
на
объекта,
значение
на
long.
типа
указателю lPtr.
ссылается указатель lPtr.
который
ссылается
lPtr,
пере¬
менной value2.
e) Выведите
value2.
значение
f) Выведите адрес valuel.
g) Выведите
веденное
значение адреса, находящееся в
значение
с
lPtr. Совпадает
ли вы¬
адресом valuel?
7.11.Выполните следующие задания.
a) Напишите
функции
заголовок
лочисленный массив
zero, которая имеет параметром це¬
bigIntegers
типа
long
и
не
возвращает значе¬
ние.
b) Напишите
c) Напишите
прототип для функции
из
предыдущего
ленный массив oneTooSmall в качестве параметра и
целое число.
d) Напишите
Замечание:
сложными.
прототип функции
Упражнения
Но
если
задания.
функции addAndSum, имеющей
заголовок
вы
с
с
7.12
ними
из
предыдущего
по
7.15
задания.
достаточно
являются
справитесь,
то
целочис¬
возвращающей
сможете
легко
программировать популярные карточные игры.
7.12.Измените программу
на рис. 7.24 так, чтобы функция раздачи сда¬
вала по пять карт, необходимых для игры в покер. Затем напишите
следующие дополнительные функции, которые могут:
a) Определить,
имеется
ли
на руках
у
игрока
b) Определить,
имеется
ли
на
у
игрока две
c) Определить,
d) Определить,
e) Определить,
ти).
имеется ли на руках
имеет
ли
Зак 801
игрок
пара.
пары.
тройка (например,
каре
имеется ли на руках
f) Определить, имеется
тельных номиналов).
12
руках
флеш (т.е.
ли на руках стрит
(т.е.
три
вальта).
четыре
туза).
пять карт
одной
(например,
мас¬
пять карт последова¬
354
Глава 7
7.13.Используя функции, разработанные
упражнении 7.12, напишите
в покер на руки по пять
в
программу, которая сдает двум игрокам
и
карт
оценивает,
чья
карта лучше.
7.14.Измените программу, разработанную в упражнении 7.13 таким об¬
разом, чтобы она исполняла роль игрока, сдающего карты. Карты
сдающего кладутся «лицом вниз», так что играющий с программой
игрок их не видит.
го и,
Программа
должна затем оценить карту сдающе¬
карт, сдающий должен взять себе
основываясь нд качестве
одну, две или три карты взамен не устраивающих его карт из перво¬
начально розданных. После этого программа должна оценить карты
(Предостережение:
сдающего еще раз.
7.15.Измените
разработанную
программу,
это
она манипулировала картами сдающего,
а
задача!)
трудная
7.14 так, чтобы
в упражнении
играющий с программой
Программа должна
решал бы сам, какие карты ему нужно менять.
затем оценивать карты играющих и определять
сыграйте
с
победителя. Теперь
этой программой 20 игр. Кто выиграет большее
компьютер? После
во игр, вы или
количест¬
этого пусть один из ваших
друзей
сыграет еще 20 игр с компьютером. Кто выиграет большее количест¬
во игр? Основываясь на результатах этих игр, сделайте соответству¬
которые позволили бы усовершенствовать вашу
в
программу игры
покер (это также будет трудной задачей). Сыграй¬
ющие
те
7.16.B
еще
изменения,
20 игр. Не
стала
ли
ваша
тасования,
который
отсрочкам. В связи с
действующий
может
этой проблемой
лучше?
которой приведен на
неэффективный алго¬
программе тасовки и сдачи карт, текст
7.24, мы преднамеренно использовали
рис.
ритм
играть
программа
к
привести
нескончаемым
вам предстоит создать
быстро¬
алгоритм тасования, в котором этой опасности просто
не существует.
Измените программу на рис.
инициализации массива
7.24 следующим
deck так,
образом. Начните
как показано на рис.
с
7.29. Изме¬
функцию shuffle таким образом, чтобы она в цикле по строкам
столбцам массива перебирала элементы массива по разу и меняла
местами значения текущего элемента и случайно выбранного эле¬
ните
и
мента
массива.
0
1
2
3
4
5
6
7
2
3
4
5
_6
7
8
8
9
10
9
10
11
11
12
12
13
о
1
1
14
15
16
J7
18
19
20
21
22
23
24
25
26
2
27
28
29
30
31
32
33
34
35
36
37
38
39
3
40
41
42
43
44
45
46
47
48
49
50
51
52
Рис. 7.29.
Неперетасованный
массив deck
Выведите полученный
в
статочно
перетасована
рис.
fle
ли
7.30). Вы
несколько
хорошо
результате
можете вызывать в
раз,
массив,
чтобы определить, до¬
колода
(как,
например,
на
вашей программе функцию shuf¬
чтобы гарантировать «хорошую» перетасовку.
355
Указатели
0
2
1
4
3
5
7
6
8
9
10
11
12
18
2
44
о
19
40
27
25
36
46
10
34
35
41
1
13
28
14
16
21
30
8
11
31
17
24
7
1
2
12
33
15
42
43
23
45
3
29
32
4
47
26
3
50
38
52
39
48
51
9
5
37
49
22
6
20
Рис. 7.30. Пример перетасованного массива deck
Заметим теперь, что хотя мы и улучшили алгоритм тасования, сда¬
ющий алгоритм
все равно имеет недостаток: он ищет в массиве
1,
с номером
затем карту с номером
карту
т.д. Положение ухудшается еще
2,
deck
затем карту номер
3
и
и тем, что после того, как алгоритм
раздачи нашел и сдал карту, он продолжает поиск уже сданной кар¬
ты в оставшейся части массива deck. Измените программу из
рис.
7.24 так, чтобы
поиск прекращался,
как только
найдена нуж¬
следующей карты.
разработаем эффективный алгоритм сдачи, который
ная карта, и после этого начинался процесс сдачи
В главе 10
требует
мы
выполнения
только
одной операции
7.17.(Компьютерное моделирование: Заяц
и
на
карту.
В этом упражне¬
исторических со¬
Черепаха)
нии вам предстоит воспроизвести одно из великих
бытий, а именно, классический забег черепахи
будет
использовать
генератор
случайных
зайца. Вам нужно
и
чисел, чтобы
разработать
программу, моделирующую это незабываемое соревнование.
Наши соперники начинают гонку в «квадрате 1» на дистанции, со¬
стоящей
из
70 квадратов. Каждый квадрат представляет
Финишная линия
можную позицию на трассе гонки.
70. Спортсмен, который достигнет
или
проскочит
из
себя
воз¬
это квадрат
квадрат 70 пер¬
вым, вознаграждается ведром свежей моркови и салата. Трасса забе¬
га проходит по склону скользкой горы, так что иногда соперники
поскальзываются
Движение
и
скатываются
назад.
соперников регулируется часами, делающими один от¬
счет
в
вать
позиций
секунду.
Ежесекундно
ваша программа должна корректиро¬
животных на трассе согласно следующим правилам:
Животное
Тип движения
Процент времени
Описание движения
Черепаха
Тащится быстро
50%
3 квадрата вправо
Сползание
20%
6 квадратов
Тащится медленно
30%
1 квадрат вправо
Спячка
20%
Движения
20%
9
Большое сползание
10%
12 квадратов
Малый
30%
1
20%
2 квадрата
Заяц
Большой
прыжок
прыжок
Малое сползание
12*
влево
нет
квадратов
вправо
влево
квадрат вправо
влево
356
Глава 7
Используйте переменные для хранения позиции животного на трас¬
се (т.е. числа величиной от 1 до 70). Каждое животное начинает свое
движение с позиции
передвигайте
Выбирайте
его
1
в
1,
животное соскальзывает за позицию
квадрат 1.
позицию
таблице, разыгрывая случайное
1
<= i <=
с
i,
в
когда 6 <= i <= 7 и «та¬
«сползание»
Используйте
этот же метод
зайца.
движения
Начинайте забег
5,
когда 8 <= i <= 10.
щится медленно»
выбора
целое число,
10. Для черепахи, «тащится быстро» выпадает
<= i <
в случае, когда
для
1. Если
начальную
тип движения в соответствии с частотными процентами,
приведенными
диапазоне
в
вывода
сообщения
BANG !!!!!
AND THE RE OFF !!!!!
Затем,
с каждым шагом часов
те строку из 70-ти
позиций,
(т.е.
H (Hare)
ции черепахи и символ
цикла)
с каждым шагом
показывая символ
в позиции
T (Tortoise)
выводи¬
в
пози¬
зайца. Часто соперни¬
будут попадать в один и тот же квадрат. В этом случае черепаха
будет кусать зайца, а ваша программа должна выводить сообщение
«OUCH!!!», начиная с этого квадрата. Все остальные позиции, кро¬
ме тех, где выведено T, H или OUCH!!!, должны быть пустыми.
ки
После
очередной
вывода
му-либо
проверяйте,
строки
не
удалось
ли
ко-
из животных достичь или перепрыгнуть квадрат с номером
70. Если это так, то выводите имя победителя и заканчивайте про¬
цесс моделирования. Если победила черепаха, выводите строку
«TORTOISE WINS!!! YAY!!!» Если выиграл заяц, то строка дол¬
жна
к
быть такой: «Наге wins. Yuch». Если оба
финишу одновременно,
(«аутсайдеру»)
вничью.
новый
Если
животных приходят
вы можете отдать предпочтение черепахе
или вывести
сообщение
о том, что
на данном шаге никто не достиг
забег
закончился
финиша, начинайте
соответствующий следующему временному от¬
будете выполнить вашу программу, пригла¬
сите группу болельщиков, чтобы наблюдать за соревнованиями. Вы
удивитесь тому, как ваша аудитория будет увлечена происходя¬
щим!
шаг
цикла,
счету. Когда вы готовы
Специальный раздел:
как
самому построить компьютер
В следующих задачах
мы временно
рования на языке высокого уровня.
тера
с
и рассмотрим его внутреннее
программированием
сколько
программ.
на
шинном
языке
и
познакомим вас
напишем
на нем
не¬
сделать этот опыт особенно ценным,
мы затем «построим» компьютер
вания),
от программи¬
Мы «снимем корпус» компью¬
устройство. Мы
машинном
И, чтобы
отойдем далеко
(методом
программного
моделиро¬
на котором вы сможете выполнять ваши программы на ма¬
языке!
на машинном языке) Мы создадим компьютер и
Симплетрон. Как следует из самого имени компьютера,
это
простая машина (от англ. simple
простой), но, как мы скоро
в этом убедимся, машина мощная. Симплетрон выполняет програм-
7.18. (Программирование
назовем его
357
Указатели
который он способен пони¬
который мы назовем Машинным языком Симплетрона (Simpletron Mashine Language), или сокращенно SML.
мы, написанные на единственном языке,
мать и
Симплетрон
имеет
«специальный регистр»,
аккумулятор
в кото¬
рый помещается информация для выполнения вычислений или раз¬
личных операций сравнения. Вся информация в Симплетроне хра¬
нится в виде слов. Словом будем здесь называть десятичное число из
четырех цифр со знаком,
Симплетрон
и т.д.
торые
производится
Перед тем как
эту программу
например,
+3364, -1293, +0007, -0001
100 слов, ссылка
99.
имеет память размером в
запустить программу на
в память.
Первая
00, 01,
чисел
посредством
SML,
на ко¬
...,
мы должны
загрузить
команда любой программы на SML
всегда помещается по адресу 00.
Каждая
команда SML занимает одно слово памяти
Симплетрона (и,
следовательно, команды являются десятичными числами из четы¬
цифр со знаком). Мы договоримся о том, что команды SML все¬
гда имеют знак плюс, тогда как знак слова данных может быть либо
плюсом, либо минусом. Каждое слово в памяти Симплетрона может
рех
содержать
мой,
не
или
либо команду, либо данные, обрабатываемые програм¬
быть неиспользуемым (в этом случае значение его
может
определено). Первые
две
операции, которую нужно
SML представлены
на
рис.
цифры каждой SML-команды
выполнить.
Допустимые
коды
это
код
операций
7.31.
Значение
Код операции
Операции ввода/вывода
#define READ 10
Вводит
#define WRITE 11
Выводит
слово с терминала
в указанное место памяти
на терминал слово из указанного адреса
памяти
Операции загрузки/выгрузки
#define LOAD 20
Помещает
#define STORE 21
Выгружает слово из аккумулятора по указанному адресу памяти
в аккумулятор слово
из указанного адреса
памяти
Арифметические операции
#define ADD 30
Выполняет сложение слова в аккумуляторе и слова из указанного
места в памяти (результат операции остается в аккумуляторе)
#define
Вычитает из слова в аккумуляторе слово из указанного места в
памяти (результат операции остается в аккумуляторе)
SUBTRACT 31
#define DIVIDE 32
Выполняет деление слова в аккумуляторе на слово из указанного
места в памяти (результат операции остается в аккумуляторе)
#define MULTIPLY 33
Вычисляет произведение слова в аккумуляторе и слова из
указанного места в памяти (результат операции остается в
аккумуляторе)
Операции передачи
#define BRANCH 40
управления
Переход к указанному адресу памяти
Глава 8
358
Значение
Код операции
#define BRANCHNEG 41
Переход
к указанному адресу памяти,
если
в аккумуляторе
если
в
находится отрицательное число
Переход
#define BRANCHZERO 42
к указанному адресу
памяти,
аккумуляторе
находится ноль
Останов, выполняется при завершении программой своей работы
#define HALT 43
Рис. 7.31. Коды операций SML
-
Машинного языка Симплетрона
две цифры SML-команды
операнд, являющийся адре¬
сом слова, над которым выполняется операция. А теперь давайте
разберем несколько простых программ на SML.
Последние
Пример 1
Адрес
Слово
Описание
00
+1007
(Ввод А)
01
+1008
(Ввод В)
02
+2007
(Загрузка А
03
+3008
(Прибавить В)
04
+2109
(Выгрузить
05
+1109
(Вывод С)
06
+4300
(Останов)
07
+0000
(Переменная А)
08
+0000
(Переменная В)
09
+0000
(Результат С)
Эта программа
на
SML
аккумулятор)
в
в
С)
считывает два числа, введенных с клавиату¬
Команда +1007 вводит пер¬
вое число и помещает его в слово с адресом 07 (которое было иници¬
ализировано нулем). Затем команда +1008 вводит следующее число
ры,
вычисляет их сумму и выводит ее.
и записывает его по адресу 08. Команда загрузки, +2007, помещает
первое число в аккумулятор, а команда сложения, +3008, прибавля¬
ет второе число к числу, записанному в аккумуляторе.
тические команды
ляторе.
SML
Команда
находящийся
в
оставляют результат
выгрузки,
+2109,
обратно
аккумуляторе,
Все арифме¬
вычислений
в аккуму¬
помещает
в память по
результат,
адресу 09, из ко¬
торого команда вывода, +1109, выводит это число
го
целого числа из четырех
+4300, завершает
цифр
выполнение
со
(в виде десятично¬
знаком). Команда останова,
программы.
вводит с клавиатуры два числа, опреде¬
ляет большее из них и выводит это значение. Обратите внимание на
Следующая SML-программа
команду +4107
условную передачу управления, напоминающую
оператор if в языке С. Теперь сами напишите программы на SML,
выполняющие следующие задачи.
359
Указатели
Пример 2
Адрес
Слово
Описание
00
+1009
(Ввод А)
01
+1010
(Ввод В)
02
+2009
(Загрузка А
03
+3110
(Отнять В)
04
+4107
(Переход
05
+1109
(Вывод А)
06
+4300
(Останов)
07
+1110
(Вывод В)
08
+4300
(Останов)
09
+0000
(Переменная А)
10
+0000
(Переменная В)
на
в
аккумулятор)
07,
если
отрицательное)
a) Напишите цикл, прерываемый вводом контрольного значения,
для ввода 10 положительных чисел, вычисления суммы этих чисел
и
ее
вывода.
b) Используя управляемый
ложительных
среднее
и
счетчиком цикл, введите семь чисел, по¬
а затем
отрицательных,
вычислите
и
выведите
их
значение.
c) Введите ряд чисел, определите большее среди них и выведите най¬
денное значение. В первом вводимом числе указывается количество
чисел, которые должны быть обработаны.
7.19.(Программная модель компьютера) Вам
мутительным, но для решения
это может показаться воз¬
этой задачи
вам нужно
собрать свой
собственный
ник
и
компьютер. Нет, вам не придется брать в руки паяль¬
отвертку. Вместо этого вы воспользуетесь мощным методом
компьютерного
и
моделирования
(симулятор) Симплетрона.
ма-симулятор Симплетрона
щий» Симплетрон
и вы
Вы
создадите
При
мерно
***
***
***
***
***
***
В
такой
модель
будете
вать программы SML, которые вы написали
запуске вашего
программную
разочарованы. Програм¬
превратит ваш компьютер в «настоя¬
сможете исполнять, тестировать и отлажи¬
не
Симплетрона
в
упражнении 7.18.
на экран должен выводиться при¬
текст:
Симплетрон приветствует вас!
Пожалуйста, введите вашу программу, по одной команде
(или слову данных) за раз. Я буду выводить в качестве
подсказки текущий адрес и знак вопроса
(?). Введенное
***
вами
слово
будет размещено
по
указанному
прекращения ввода программы введите число
качестве модели памяти
сив memory из
100
адресу.
Для
-99999.
***
***
***
***
***
Симплетрона объявите одномерный мас¬
Теперь представим, что наша симуля¬
элементов.
тор запущен, и разберем процесс ввода программы примера 2 из
упражнения 7.18:
Глава 8
360
00? +1009
01?
+1010
02?
+2009
03?
+3110
04?
+4107
05?
+1109
06?
+4300
07?
+1110
08?
+4300
09?
+0000
10?
+0000
11?
-99999
***
Загрузка программы завершена
***
выполнение
Начинаю
программы
***
***
Программа SML теперь помещена (т. e. загружена) в массив memo¬
ry. Теперь Симплетрон должен выполнить вашу программу. Выпол¬
нение программы начинается с команды, расположенной по адресу
00 и, подобно С, продолжается последовательно,
команда
передачи
управления
в
другую
часть
если не встретится
программы.
представления аккумулятора используйте переменную accumu¬
lator. В переменной instructionCounter храните адрес следующей
Для
исполняемой
команды.
полняемой команды,
зуйте
переменную
Для
т.е.
хранения кода операции
левых двух
operationCode.
цифр
текущей
слова команды,
В переменной
operand
ис¬
исполь¬
можно хра¬
текущей исполняемой команды. Напомним, что
это две правые цифры командного слова. Не вы¬
адрес операнда
полняйте команды непосредственно из памяти. Используйте проме¬
жуточную переменную instructionRegister для хранения исполняе¬
мой команды. И уже из нее выделите две левые цифры кода
нить адрес операнда
операции, поместите их в переменную
те правые две
ременную
Перед
цифры адреса операнда
operationCode,
затем выдели¬
команды и поместите их в пе¬
operand.
тем, как
Симплетрон
начнет
выполнение
программы,
наши
быть инициализированы следующим
переменные-регистры должны
образом:
+0000
00
+0000
00
00
accumulator
instructionCounter
instructionRegister
operationCode
operand
Теперь
давайте
«пройдем»
+1009, расположенной
называется
циклом
выполнение
исполнения
первой SML-команды,
00. Процесс
команды.
по адресу
выполнения команды
В регистре команд instructionCounter расположен адрес следующей
исполняемой команды. Мы выбираем слово этой команды из масси¬
ва memory с помощью следующего оператора:
instructionRegister
Извлечем
льзуя
=
memory[instructionCounter];
из регистра команд код операции и адрес операнда,
операторы
испо¬
Указатели
361
instructionRegister / 100;
instructionRegister % 100;
=
operationCode
operand
=
Теперь Симплетрон должен определить, что данному коду операции
соответствует команда ввода (а не вывода, загрузки и т. д.). Нужный
выбор из двенадцати возможных команд SML можно сделать в опе¬
раторе switch.
В операторе switch различные SML-команды моделируются следую¬
остальными вы должны
щим образом (с
scanf("%d",
ввод:
разобраться самостоятельно):
&memory[operand]);
загрузка:
accumulator
=
сложение:
accumulator
+=
останов:
эта команда выводит
memory[operand];
memory[operand];
команды передачи управления: мы обсудим их
***
По
окончании
Симплетрон
закончил
работы Симплетрон
чуть позже
сообщение
свои
вычисления
***
выводит имя и содержимое каж¬
дого регистра и своей памяти. Такая распечатка часто называется
компьютерным дампом, или дампом памяти. Формат дампа показан
на рис. 7.32. Он поможет вам в программировании
дящей
дамп памяти. Не
ном
программы дамп
ния
команд
Продолжим
и
процесс
scanf("%d",
завершения
фактические
значе¬
программы.
структуре switch при помощи оператора С
в
&memory[operand]);
выполнением
знак вопроса
момент
выво¬
Симплетро-
разбора выполнения первой команды нашей
+1009, расположенной по адресу 00. Мы моде¬
лируем команду ввода
Перед
на
функции,
что после выполнения
памяти должен отражать
данных
программы, а именно
забудьте,
(?)
функции scanf
на экран должен
в качестве запроса ввода данных.
быть выведен
Симплетрон
ждет
до тех пор, пока пользователь не введет число и не нажмет клавишу
Enter.
На
Введенное
значение
будет
помещено в
ячейку
памяти с адре¬
09.
сом
первой команды завершается. Остается только
Симплетрон к выполнению следующей команды. Так
этом выполнение
подготовить
как
команда,
мы
которую
только
командой передачи управления,
гистр
счетчика
что
нам
выполнили,
нужно
просто
не
является
увеличить
ре¬
команд:
++instructionCounter;
На
первой команды завершается полностью. После
(т.е. цикл исполнения команды) начинается снова
памяти следующей исполняемой команды.
этом выполнение
этого весь процесс
выборки из
Теперь давайте рассмотрим моделирование команд перехода (переда¬
это правильно скор¬
чи управления). Все, что мы должны сделать
Безусловная
ректировать значение регистра счетчика команд.
команда перехода (40) моделируется в операторе switch как
с
instructionCounter
РЕГИСТРЫ:
instructionCounter
accumulator
instructionCounter
=
=
operand;
operand;
+0000
00
Глава 7
362
+0000
00
instructionRegister
operationCode
00
operand
ПАМЯТЬ:
0
2
1
4
3
5
7
6
8
9
0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
10 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
20 +0000 +0000 +0000 +0000 +0000 +0000 +0000
30 +0000 +0000 +0000 +0000 +0000 +0000 +0000
40 +0000 +0000 +0000 +0000 +0000 +0000 +0000
50 +0000 +0000 +0000 +0000 +0000 +0000 +0000
60 +0000 +0000 +0000 +0000 +0000 +0000 +0000
+0000
+0000
+0000
+0000
+0000 +0000
+0000 +0000
+0000 +0000
+0000 +0000
+0000 +0000 +0000
70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
90 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000
Рис. 7.32. Пример дампа памяти
Условная команда перехода «переход, если аккумулятор
if
ноль»
быть реализована при помощи следующих операторов:
может
0)
(accumulator
instructionCounter
==
Теперь
=
operand;
модели Сим¬
можете приступать к написанию программы
плетрона,
через
разработанные
в
которую
вы должны
пропустить
все программы,
упражнении 7.18. Вы можете добавить
кие-либо усовершенствования,
но не
забудьте
в
SML
ка¬
включить их й в мо¬
Симплетрона.
дель
Ваш
симулятор должен уметь обрабатывать различные ошибки. На¬
пример, при вводе программы входные данные должны находится в
интервале от -9999 до +9999. Поэтому ввод данных должен происхо¬
while, в котором производится проверка введен¬
ной величины и, если введено неверное значение, процесс ввода дол¬
жен продолжаться до тех пор, пока пользователь не введет верное
значение.
дить внутри цикла
Во время выполнения программы симулятор должен
обрабатывать
различные серьезные ошибки, например, попытку деления на ноль,
попытку выполнить команду с неправильным кодом операции, пере¬
полнение аккумулятора (т.е. получение в процессе вычислений ве¬
личины большей +9999 или меньшей
ошибки
ные
-9999)
и другие.
Такие серьез¬
фатальными ошибками. В случае
ошибки симулятор должен выдать сообщение
называются
возникновения такой
вроде
***
***
Попытка
деления
на
ноль
***
Симплетрон аварийно завершил
и выдать
дамп
полный дамп
памяти в
выполнение
***
программы
формате, предложенном выше. Этот
обнаружить ошибку в программе.
может помочь пользователю
7.20. Измените программу тасования и сдачи карт из рис. 7.24 так, чтобы
процесс тасования и сдачи выполняла бы одна функция (shuffle-
AndDeal). Подобно функции shuffle
из
жна содержать один вложенный цикл.
рис.
7.24
эта
функция
дол¬
Указатели
363
7.21. Что делает эта
#include
программа?
<stdio.h>
void mysteryl(char
const
*,
char
*);
main()
{
char
stringl[80],
string2[80];
two
printf("Enter
scanf("%s%s"
strings:
stringl,
mysteryl(stringl,
printf("%s\n",
return 0;
");
string2);
string2);
stringl);
void mysteryl(char *sl,
{
while (*sl != '\0')
const
char
*s2)
++s 1;
for
(
/*
;
*sl
;
тело
*s2;
=
цикла
sl++, s2++)
*/
пусто
}
7.22.Что делает
программа?
эта
#include
<stdio.h>
int mystery2(const
char
*);
main()
{
char
stnng [80] ;
");
printf(мBвeдитe строку:
scanf("%s", string);
printf("%d\n",
return 0;
mystery2(string));
mt mystery2(const
{
mt x
0;
char
*s)
=
for
( ;
++x;
*s
'
!=
\01;
s++)
return x;
}
7.23.Найдите ошибку
Если ошибку
в
каждом
можно
из
лать.
а)
int
*number;
printf
("%d\n",
следующих
исправить,
*number);
то
фрагментов программы.
объясните
как
это
можно
сде¬
364
Глава 7
b)
float
*realPtr;
long
*mtegerPtr;
=
mtegerPtr
c)
d)
int
*x,
x
у;
=
char
int
short
!=
*s
;
(
("%c
printf
*numPtr,
void
float
=
1
\01;
символов";
x
printf(
s ++
)
*s);
",
result;
=
numPtr;
*genericPtr
+
7;
19.34;
=
float xPtr
=
&x;
"%f\n"
g) char *s;
printf ( "%s\n",
7.24.
массив
*genericPtr
result
f)
"вот
=
s[]
count;
for
e)
realPtr;
у;
,
xPtr);
s);
В примерах и упражнениях главы 6 мы об¬
суждали алгоритмы сортировки, такие, как пузырьковая или блоч¬
ная сортировка. Мы представим вам теперь рекурсивный алгоритм
(Быстрая сортировка)
сортировки, называемый
быстрой сортировкой. Алгоритм для одно¬
образом:
1) Этап разбиения: Возьмите первый элемент несортированного
мерного массива выглядит следующим
массива и определите его расположение в отсортированном массиве.
Это
положение
элемента
будут
будет найдено,
ше значения данного элемента.
положенный
тированных
если
все
значения
слева от данного
меньше, а все значения направо от элемента
Мы теперь
боль¬
имеем один элемент, рас¬
на своем месте в отсортированном массиве и два несор¬
подмассива.
2) Этап рекурсии: Выполните
шаг
1
на каждом из несортированных
подмассивов.
Каждый раз
после
выполнения
элемент массива помещается
сиве,
шага
1
на
подмассиве
следующий
на свое место в отсортированном мас¬
и создаются два несортированных подмассива.
Когда
мы
дой¬
дем до подмассива, состоящего из одного элемента, этот элемент бу¬
дет находится на своем окончательном месте в упорядоченном
массиве.
Это описание алгоритма в целом кажется достаточно ясным, но как
нам определить окончательную позицию первого элемента каждого
подмассива? В качестве примера рассмотрим следующий набор
элемент разбиения,
(элемент, выделенный жирным
чений
зна¬
дол¬
жен быть помещен на свое окончательное место в отсортированном
массиве):
37 2 6 4 89 8 10 12 68 45
1) Начиная с правого элемента
будем сравнивать каждый эле¬
будет найден элемент меньший
массива
мент с числом 37 до тех пор, пока не
найденный элемент и 37 должны поменяться свои¬
Первым элементом, который меньше 37, является число
12, поэтому они меняются местами. Теперь массив выглядит так:
чем
37,
после чего
ми местами.
365
Указатели
12 2 6 4 89 8 10 37 68 45
Элемент 12 выделен курсивом, чтобы указать на то, что он поменял¬
ся местами с числом 37.
2) Теперь
обнаружим
пока не
37
тами
и этот
больший
массив
левой
начинаем движение с
следующего элемента после
чем
элемент
12,
части массива, но начинаем со
и сравниваем
больший
чем
37,
каждый
элемент с
37,
после чего меняем мес¬
найденный элемент. В нашем случае первый элемент
это 89, так что 37 и 89 меняются местами. Новый
37
имеет
вид:
12 2 6 4 37 8 10 89 68 45
начинаем справа,
3) Теперь
89,
щего
дем меньший
Первый
элемент с
элемента,
37 до
предшествую¬
тех пор, пока не
най¬
37, и опять поменяем местами 37 и этот элемент.
это 10,
меняем местами с
который меньше 37
чем
элемент,
37. Теперь
но начинаем с
каждый
и сравниваем
наш
массив
имеет
вид:
12 2 6 4 10 8 37 89 68 45
4) Теперь
начинаем движение с
элемента,
следующего
обнаружим
пока не
тами
37
ших
37,
и этот
элемент
найденный
левой
части массива, но начинаем с
сравниваем каждый элемент с 37,
больший чем 37, после чего меняем мес¬
10,
за
и
элемент.
В
боль¬
нашем случае элементов,
не осталось, и когда мы сравним
37
с самим
собой,
это
будет
означать, что процесс закончен и элемент 37 нашел свое окончатель¬
ное место.
После завершения этой операции мы имеем два неупорядоченных
Подмассив со значениями меньше 37 содержит элемен¬
12, 2, 6, 4, 10 и 8. Подмассив со значениями большими 37 содер¬
жит 89, 68 и 45. Сортировка продолжается путем применения алго¬
подмассива.
ты
разбиения
ритма
первоначальным
На
к
полученным
подмассивам,
как
это
делалось
с
массивом.
основе описанного выше алгоритма напишите рекурсивную
фун¬
quicksort, сортирующую одномерный целочисленный массив.
Функция должна иметь параметрами целочисленный массив, нача¬
кцию
льное значение индекса и конечное значение индекса.
icksort
ние
должна вызывать
Функция qu¬
функцию partition, выполняющую разбие¬
массива.
7.25.(Прохождение лабиринта) Приведенная
лей
двумерный
массив,
1
1
1
1
1
1
1
1
1
1
1
1
1
0
0
0
1
0
0
0
0
0
0
1
0
0
1
0
1
0
1
1
1
1
0
1
1
1
1
0
1
0
0
0
0
1
0
1
1
0
0
0
0
1
1
1
0
1
0
0
1
1
1
1
0
1
0
1
0
1
0
1
1
0
0
1
0
1
0
1
0
1
0
1
1
1
0
1
0
1
0
1
0
1
0
1
1
0
0
0
0
0
0
0
0
1
0
1
1
1
1
1
1
1
0
1
1
1
0
1
1
0
0
0
0
0
0
1
0
0
0
1
1
1
1
1
1
1
1
1
1
1
1
1
ниже
сетка
единиц
представляющий лабиринт.
и
ну¬
366
Глава 7
Единицами обозначены
стенки, а нулями
дорожки
в
лабиринте.
Существует простой алгоритм прохода через лабиринт, который га¬
рантирует, что вы найдете выход (если он, конечно, существует).
Если лабиринт не имеет выхода, то вы вернетесь к тому месту, из
которого вышли. Касайтесь правой рукой стены (которая находится
справа от вас) и начинайте движение вперед. Все время касайтесь
стены.
рукой
Если лабиринт поворачивает направо, вы должны сле¬
И если вы не будете отпускать
довать за поворотом стены направо.
руку, то в конечном счете вы
Возможно, существует и более
доберетесь до выхода из лабиринта.
короткий путь, чем тот, которым вы
прошли, но
указанный метод в любом случае гарантирует вам выход
лабиринта.
Напишите рекурсивную функцию mazeTraverse прохода через лаби¬
ринт. Функция должна получать в качестве аргумента представляю¬
щий лабиринт массив символов 12x12 и точку входа в лабиринт. В
процессе поиска выхода из лабиринта mazeTraverse помещает сим¬
вол X в каждый пройденный квадрат пути вместо символа 0. Функ¬
ция должна перерисовывать лабиринт после каждого перемещения,
из
чтобы
так
7.26.
пользователь
мог
наблюдать процесс решения
(Генератор случайных лабиринтов)
задачи.
Напишите функцию mazeGene-
rator, имеющую параметром двумерный символьный
которая генерирует при помощи случайных
чисел
массив
12x12,
лабиринт. Функ¬
ция должна производить лабиринты, имеющие вход и выход. Испы¬
тайте вашу функцию mazeTraverse из упражнения 7.25 на несколь¬
ких
лабиринтах.
7.27.(Лабиринты произвольного размера) Обобщите функции mazeTra¬
verse и mazeGenerator из упражнений 7.25 и 7.26 для работы с ла¬
биринтами произвольной ширины и высоты.
7.28.
(Массивы указателей на функции) Перепишите программу, приве¬
денную на рис. 6.22, чтобы она управлялась при помощи меню. Про¬
грамма
но
должна предлагать пользователю
таким
выбор
из
4 команд пример¬
образом:
Выберите:
0
Вывести массив
1
Найти минимальную оценку
Найти максимальную оценку
2
3
4
оценок
Вывести среднюю по всем тестам оценку для
Выйти из программы
Существует
каждого
студента
одно ограничение на использование массивов указате¬
лей на
функции, которое состоит в том, что все указатели должны
иметь одинаковый тип. Указатели должны ссылаться на функции,
возвращающие значение одного и того же типа, а также имеющие
параметры
рис.
одинаковых
6.22 должны быть
типов.
По
изменены так,
этой
причине функции на
чтобы они возвращали резу¬
типа и
имели
одного
одинаковый набор параметров.
Модифицированные функции minimum и maximum должны выво¬
льтат
дить
щать.
минимальную
и
максимальную
оценки
и
Измените функцию average для команды
ничего
меню
не
возвра¬
3, представ¬
Указатели
367
средний балл для каждого
Функция
одного).
average не должна ничего воз¬
ленную на рис. 6.22, чтобы она выводила
студента
(а
не для
вращать и должна иметь список параметров,
ями
printArray, minimum
и
одинаковый
с
функци¬
maximum. Сохраните указатели
на че¬
функции в массиве processGrades и используйте выбор,
сделанный пользователем, в качестве индекса массива указателей
для вызова нужной функции.
тыре
7.29.(Усовершенствования симулятора Симплетрона) В
7.19
нии
может
вы
написали
исполнять
программную
программы,
модель
написанные
упражне¬
компьютера, которая
на Машинном языке
Симплетрона (SML). В этом упражнении мы предлагаем вам внести
несколько усовершенствований и расширений в модель Симплетро¬
В упражнениях 12.26
на.
12.27
и
мы предложим вам написать ком¬
транслирующий программы, написанные на языке высоко¬
го уровня (разновидность BASIC), в программы на Машинном языке
Симплетрона. Некоторые из следующих усовершенствований и рас¬
ширений могут потребоваться для выполнения программ, порожден¬
пилятор,
ных этим компилятором.
a) Увеличьте
память
Симплетрона
до 1000 единиц, чтобы он мог вы¬
полнять программы большего размера.
b) Добавьте в Симплетрон возможность вычисления значений по мо¬
дулю. Для этого потребуется ввести новый код операции в Машин¬
ный язык Симплетрона.
c) Добавьте в Симплетрон возможность вычисления степени числа.
Для этого также потребуется ввести новый код операции в SML.
d) Замените десятичные
нии операций в SML.
новый
введите
f) Внесите
код
изменения
чтобы
того,
Симплетрон
в
e) Добавьте
чего
числа шестнадцатеричными при кодирова¬
возможность
операции
в
Симплетрон,
в
вывода
новой
строки,
для
SML.
необходимые для
обрабатывать числа с
программу-симулятор,
кроме целых, мог
плавающей точкой.
g) Добавьте в Симплетрон возможность ввода строк. Совет: каждое
Симплетрона может быть разделено на две части, каждая из
которых является числом из двух цифр. Используйте эти числа для
представления десятичного эквивалента ASCII-кода символа. Введи¬
слово
те
машинный
в
язык
новую
операцию,
которая
осуществляет ввод
строки и ее запись по определенному адресу в памяти
При
Симплетрона.
этом в первую половину первого машинного слова, из числа от¬
веденных для хранения строки,
в строке,
т.е.
длина строки.
записывается количество символов
Каждая
из последующих половин слов
ASCII-кода символа. Эта но¬
преобразовывать каждый вводи¬
соответствующий ASCII-код и записывать его в поло¬
используется для хранения десятичного
вая
машинная
мый символ в
вину
операция
должна
слова.
h) Добавьте
памяти по
в
Симплетрон
возможность вывода строк, хранящихся в
формату, описанному
в задании
(g). Совет: введите в ма¬
расположенной по
шинный язык новую инструкцию вывода строки,
368
Глава 7
указанному адресу памяти. В
первой половине первого слова записа¬
(т.е. длина строки). Каждая из по¬
содержит десятичные ASCII-коды симво¬
но количество символов в строке
следующих
лов
половин
строки.
слов
Машинная
преобразует десятичное
инструкция
число в
проверяет длину строки,
соответствующий
символ и выводит
его.
7.30. Что делает
эта
программа?
<stdio.h>
#include
int mystery3(const
char
*,
const
char
*);
main ()
{
char
stringl
[80],
string2
[80];
");
prmtf("BBeflHTe две строки:
scanf("%s%s", stringl, stnng2);
printf(ffPeзyльтaт
равен
%d\n",
mystery3(stringl,
stnng2) ) ;
return
0;
}
int mystery3(const char *sl, const char *s2)
{
for ( ; *sl != '\0' && *s2 != '\0'; sl++,
if (*sl != *s2)
return
return
1;
0;
s2++)
г
л
в
а
а
8
Символы
и
строки
Цели
Изучить функции библиотеки
для
работы
с символами
(ctype).
Научиться использовать функции
ввода/вывода (stdio).
из
стандартной биб¬
лиотеки
Научиться использовать функции преобразования
строк из библиотеки утилит общего назначения
(stdlib).
Научиться применять функции обработки
библиотеки для
работы
со
строками
Познакомиться с мощными
ставляемыми библиотеками
строк
(string)
возможностями,
функций
из
в
плане
ного использования программного кода.
предо¬
повтор¬
370
Глава 8
Содержание
8.1.
Введение
8.2.
Строки
8.3.
Библиотека обработки
8.4.
8.5.
Функции преобразования строк
Функции стандартной библиотеки ввода/вывода
8.6.
функции операций
и
символы
8.7. Функции сравнения
8.9. Функции
символов
над строками из библиотеки
из
поиска
из
библиотеки обработки строк
8.10.Другие функции
из
библиотеки обработки строк
Резюме
обработки
строк
библиотеки обработки строк
ошибки программирования
Хороший стиль
Советы по переносимости программ
Упражнения для
Ответы к упражнениям для самоконтроля Упражнения
Распространенные
программирования
самоконтроля
Специальный раздел: более
сложные упражнения
по
работе
со
строками
8.1. Введение
со стандартной библиотекой функций С,
обработку строк и символов. Эти функции позволяют про¬
грамме обрабатывать символы, строки, ввод с клавиатуры и блоки памяти. С
помощью этих функций можно осуществлять операции с текстом, подобные
В этой
мы познакомимся
главе
которые упрощают
тем, что выполняются
функциями форматированного ввода/вывода printf
и
scanf.
Методики программирования, обсуждаемые в этой главе, используются
при создании редакторов, текстовых процессоров, издательских программ ма¬
кетирования, компьютерных
наборных
систем и других программ
обработки
текста.
8.2.
Символы
это
Строки
и символы
фундаментальные «кирпичики»
исходного текста про¬
граммы. Любая программа составляется из последовательности символов, ко¬
торые
(если они сгруппированы осмысленно) воспринимаются компьютером
инструкций, реализующих решение задачи. Программа может содер¬
как ряд
жать
символьные
константы.
int, представляемая
Символьная
константа
в виде символа, заключенного в
это величина типа
одинарные кавычки
(апо¬
Символы и строки
371
строфы). Значением символьной
константы является целое значение из
значение символа z, а
набора
Например, V
представляет собой целое
представляет собой целое значение символа новой
символов, используемых машиной.
'\n
строки.
Строкой
Строка
может содержать
специальные символы, такие как
+,
-,
*, /, $
лы, или строки-константы заключаются
"John
Doe"
Q.
"99999
которой обращаются
буквы, цифры и различные
называется последовательность символов, с
как с одним элементом.
Main
в
и
другие. В С строковые литера¬
двойные
кавычки:
(имя)
Street"
и улица)
(город и штат)
(номер телефона)
(дом
"Waltham, Massachusetts"
(201) 555-1212"
"
Строка в С является массивом символов, который заканчивается нулевым
('\0'). Доступ к строке осуществляется через указатель, ссылаю¬
щийся на первый символ строки. Значением строки является адрес ее первого
символом
символа.
Таким образом,
в
С правомерно сказать,
на
тель, фактически указатель
первый
что
это
строка
указа¬
символ строки. В этом смысле строка
аналогична массиву, поскольку массив также является указателем на его пер¬
вый элемент.
типа
char color
char
быть присвоена в объявлении либо массиву символов, либо
char *. Каждое из объявлений
может
Строка
переменной
[]
"blue";
=
*colorPtr
=
"blue";
строкой "blue". Первое объявление создает мас¬
color, состоящий из 5 элементов и содержащий символы: Ъ\ T, u', 'e',
'\0'. Второе объявление создает переменную-указатель colorPtr, который ука¬
инициализирует переменную
сив
зывает на строку
"blue",
находящуюся в каком-то месте памяти.
Совет по переносимости программ 8.1
Если переменная типа char* инициализируется строковым литералом, то некоторые ком¬
пиляторы могут поместить такую строку в неизменяемую область памяти Если, напри¬
мер, вам требуется изменять какие-то символы в литеральной строке, то, чтобы иметь
такую возможность на всех системах, строку нужно запомнить в массиве символов.
Приведенное
образом:
выше
объявление
массива
можно
также
записать
следую¬
щим
char
color
[]
=
{'b',
'1',
'u',
'e',
'\0'};
При объявлении массива символов для хранения строки
достаточный размер, чтобы
вместить строку и
он должен иметь
ограничивающий нулевой
сим¬
(NULL). В
приведенном объявлении размер массива определяется автома¬
тически, исходя из числа инициализаторов в списке.
вол
Распространенная ошибка программирования
8.1
При определении размера
выделено
символ
массива
символов
не
место,
чтобы
запомнить
NULL, который ограничивает строку
Распространенная ошибка программирования 8.2
Попытка
напечатать «строку», которая не содержит ограничивающего символа NULL
372
Глава 8
Хороший
стиль программирования 8.1
Когда вы объявляете массив символов для хранения строки, убедитесь, что размер
его достаточен для хранения самой большой строки, с которой предполагается рабо¬
тать Язык С позволяет запоминать строку любой длины Если строка оказалась длин¬
нее массива, в котором она должна сохраняться, то символы, выходящие за размер
массива, перепишут данные в области памяти, следующей сразу за массивом
Массиву
можно присвоить строку,
используя
функцию scanf. Например,
следующий оператор присваивает строку массиву
scanf("%s",
Строка,
word
символов
word[20].
word);
введенная пользователем,
запоминается в word
(отметьте,
что
разумеется, есть указатель и, следователь¬
но, нет необходимости применять & к аргументу word). Функция scanf будет
является массивом,
который,
пор, пока не встретит символ пробела, новой строки
или признак конца файла. Обратите внимание, что длина строки не должна
превышать 19 символов, чтобы оставалось место для ограничивающего строку
символа NULL. Чтобы можно было напечатать массив символов как строку, в
читать символы до тех
конце массива обязательно должен содержаться символ NULL.
Распространенная ошибка программирования
Обработка
одиночного символа в качестве строки
можно, представляется данными типа
лом
long
замена
вызовет
ошибку,
специального использования
ний операционной системы,
-
-
-
который,
воз¬
int Символ является небольшим целым
чис¬
(значения кодовой таблицы ASCII находятся
такая
8.3
Строка
это указатель,
в диапазоне
0-255) На
многих системах
поскольку младшие адреса памяти резервируются для
например, для адресации программ обработки прерыва¬
так что
произойдет «несанкционированный доступ»
Распространенная ошибка программирования
8.4
Передача символа в качестве аргумента функции, которая оперирует со строками
Распространенная ошибка программирования 8.5
Передача строки
в
качестве
аргумента функции, которая оперирует
8.3. Библиотека
Библиотека обработки
обработки
символов включает в
полняющих ряд полезных проверок и
операций
с
символами
символов
себя
несколько
функций,
с символьными данными.
вы¬
Каж¬
дая функция получает в качестве аргумента символ
представляемый типом
int
или индикатор EOF (конец файла). Как мы уже говорили в 4-й главе, опе¬
рации с символами часто выполняются как с целыми числами, поскольку сим¬
С это однобайтовое целое. Вспомните, что EOF обычно имеет значение -1,
а архитектура некоторых аппаратных средств не позволяет запоминать отрица¬
тельные значения в переменных типа char. Следовательно, функции обработки
вол в
символов оперируют с символами как с целыми числами.
перечень функций библиотеки обработки
символов.
На рис. 8.1 приведен
373
Символы и строки
Описание функции
Прототип
int
isdigit
(int
с)
Возвращает значение true, если
с является
цифрой,
и 0
(false)
в
других случаях
int
isalpha
(int
с)
Возвращает
значение true, если с является
буквой,
и
цифрой
или
0
в других
случаях.
int
isalnum
(int
с)
Возвращает значение true, если
с является
буквой,
и 0
в
других случаях
int
isxdigit
(int с)
значение true, если с является одним из символов
Возвращает
формата, и 0 в других случаях Смотрите
приложение Д, «Системы счисления», где детально обсуждаются
восьмеричная, десятичная и шестнадцатеричная системы счисления
шестнадцатеричного
int
islower
(int
с)
Возвращает
и
int
isupper
(int
с)
0
в
значение true, если с является
буквой
нижнего
буквой
верхнего регистра,
регистра,
других случаях
Возвращает значение true, если
с является
и 0 в других случаях
mt
tolower
(int
с)
Если с является
как
букву
буквой
верхнего регистра, то tolower возвращает с
В других случаях tolower возвращает
нижнего регистра.
аргумент без изменений
int
toupper
(int
с)
Если с является
буквой
нижнего регистра, то
toupper возвращает
с
верхнего регистра. В других случаях toupper возвращает
аргумент без изменений
как
букву
-
int
isspace
(int
с)
Возвращает значение true, если с является
пробельным
символом
пробел (' '),
новая страница ('\f'), новая строка f\n'), возврат
C\r'), горизонтальная табуляция ('\t*) или вертикальная
и 0 в других случаях.
табуляция C\v*),
каретки
-
int
iscntrl
(int
с)
Возвращает значение true, если
в других случаях
с является
Возвращает значение true, если
с является
управляющим символом,
и
0
int
ispunct
(int
с)
символом,
буквам.
int
isprint
(int
с)
int
isgraph
(int
с)
но не относится
к
цифрам
печати
или
Возвращает значение true, если с является отображаемым при печати
символом, включая символ пробела (' *), и 0 в других случаях
Возвращает значение true, если с является
Рис. 8.1. Перечень
пробела f '),
функций библиотеки обработки
отображаемым
и 0
при печати
в других случаях
символов
стиль программирования 8.2
функции библиотеки обработки
файл <ctype.h>.
Если вы вызываете
ный
отображаемым при
символам,
В других случаях функция возвращает значение 0
символом, исключая символ
Хороший
пробельным
символов, подключите заголовоч¬
Программа, приведенная на рис. 8.2, демонстрирует применение функций
isdigit, isalpha, isalnum и isxdigit. Функция isdigit определяет, является ли
ее аргумент цифрой (0-9). Функция isalpha определяет, попадает ли ее аргу¬
мент в список букв верхнего (A-Z) или нижнего (a-z) регистров. Функция isal¬
num определяет принадлежность аргумента буквам верхнего и нижнего реги¬
стров или цифрам. Функция isxdigit определяет, является ли аргумент шест¬
надцатеричной цифрой (A-F, a-f, 0-9).
374
Глава 8
/* Применение функций isdigit,
#include <stdio.h>
#include <ctype.h>
main ()
{
printf("%s\n%s%s\n%s%sYn\n",
isdigit('8') ? "8 is а
isalpha,
"According
"
isdigit('#')
?
"#
is
а
",
"digit",
:
"#
is
not
а
",
"digit");
а
?
"8
а
?
"digit or
"
"# is а
а
:
", "letter",
", "letter",
а
", "letter",
а
", "letter");
"According to isalnum:",
not
а
а
а
",
а
",
letter",
"8
is
not
letter",
:
"# is not а ",
"digit or а letter");
printf ("%s\n%s%s\n%s%s\n%s%s\n%s%s\n%s%s\n",
"According to isxdigit:",
"
"F
а
:
is
not
",
isxdigit('F') ? "F is а
"hexadecimal digit",
"
:
"J
is
not
а
",
isxdigit('J') ? "J is а
"hexadecimal digit",
"
"7
a
:
is
not
",
isxdigit('7') ? "7 is a
"hexadecimal digit",
"
:
"$ is not a ",
isxdigit('5') ? "$ is a
"hexadecimal digit",
"
a
:
"f
is
not
",
isxdigit('f') ? "f is a
"hexadecimal digit");
return 0;
isalnum('#1)
According to isdigit:
8 is a digit
# is
not
a
A
is
b
6
4
digit
to
According
isalpha:
a
letter
is
a
letter
is
not
a
letter
is
not
a
letter
According to isalnum:
A is a digit or a letter
is a digit or a letter
8
# is not a digit or a letter
According
to
isxdigit:
digit
J is not a hexadecimal digit
7 is a hexadecimal digit
$ is not a hexadecimal digit
F
is
a
f
is
a hexadecimal
hexadecimal
Рис. 8.2.
",
not
or
"
is
isdigit:
is
printf("%s\n%s%s\n%s%s\n%s%s\n\n",
"
:
"А is
isalnum('A') ? "А is а
isalnum('8')
isxdigit */
"8
pnntf ("%s\n%s%s\n%s%s\n%s%s\n%s%s\n\n",
"According to isalpha:",
"
:
"А is
not
isalpha('A') ? "А is а
"
а
:
"b
is
?
"b
is
not
isalpha('b')
"
"&
is
:
not
isalpha(1&') ? "& is а
"
:
"4
is
not
isalpha('4') ? "4 is а
"digit
to
и
:
"
а
isalnum
digit
Применение isdigit, isalpha, isalnum, isxdigit
Символы
и строки
375
Программа рис. 8.2 использует с каждой функцией
(?:), чтобы определить, какую из строк « is а » (это) или
условную операцию
«
is not
следует вывести на печать для каждого проверяемого символа.
а » (это не)
Например, вы¬
ражение
isdigit
('8')
?
"8
is
а"
означает, что если '8' является
:
"8
is
цифрой,
not
т.е.
а"
функция isdigit возвращает
8 is
'8
истин¬
(ненулевое),
цифрой, т.е. isdigit возвращает 0, печатается строка « 8 is not а ».
Программа, приведенная на рис. 8.3, демонстрирует применение функций
islower, isupper, tolower и toupper. Функция islower определяет, является ли
ее аргумент буквой нижнего регистра (a-z). Функция isupper определяет, яв¬
ляется ли ее аргумент буквой верхнего регистра (A Z). Функция tolower пре¬
то печатается строка «
ное значение
образует
гистра.
буквы
регистр
Если аргумент
из верхнего в
не является
нижний
а », и, если
и возвращает
букву
не является
нижнего ре¬
верхнего регистра, то функция tolo¬
буквой
wer возвращает аргумент без изменений.
Функция toupper преобразует
ре¬
гистр буквы из нижнего в верхний и возвращает букву верхнего регистра.
Если аргумент не является буквой нижнего регистра, то функция toupper воз¬
вращает аргумент
без изменений.
/* Применение функций islower,
#include <stdio.h>
#include <ctype.h>
tolower,
isupper,
toupper
main ()
{
printf("%s\n%s%s\n%s%s\n%s%s\n%s%s\n\n",
"According to islower:: ",
islower ( 1p1) ? "p is a "
"p is not
"lowercase letter",
islower ('p') ? "p is a "
"p is not
letter",
( 151) ? "5
"lowercase
letter",
islower ( ! ) ? "!
"lowercase
islower
"lowercase
letter"
is
)
"
a
is
"5
"
a
"
!
is
not
is
not
;
prmtf ("%s\n%s%s\n%s%s\n%s%s\n%s%s\n\n"
"According to isupper
tl
/
isupper
('d')
?
"D
"uppercase letter",
isupper ('d') ? "d
is
a
is
a
"
"
:
:
"
D
is
not
an
"d
is
not
an
is
not
an
is
not
an
"uppercase letter",
"
:
"8
isupper ( 8 ) ? "8 is a
"uppercase letter",
"
:
M$
isupper (1 $ ') ? "$ is a
"uppercase letter" );
"%s%c\n%s%c\n%s%c\n%s %c\n",
"u converted to uppercase is ",
'
return
'
toupper
( u')/
"7
converted
to
uppercase
is
",
toupper
('7'),
"$
converted
to
uppercase
is
",
toupper
('$'),
"L
converted
to
lowercase
is
",
tolower
('L')),
0;
Рис. 8.3.
Применение islower, isupper, tolower, toupper (часть
1 из 2)
*/
Глава 8
376
According to islower:
p is a lowercase letter
P
is
not
a
lowercase
letter
5
is
not
a
lowercase
letter
!
is
not
a
lowercase
letter
According
to
isupper:
D
is
an
d
is
not
an
uppercase
letter
8
is
not
an
uppercase
letter
$
is
not
an
uppercase
letter
u
converted
to
uppercase
is
U
7
converted
to
uppercase
is
7
$
converted
to
uppercase
is
$
L
converted
to
lowercase
is
1
uppercase
Рис. 8.3.
letter
Применение islower, isupper, tolower, toupper (часть 2
из
2)
Рис. 8.4 иллюстрирует применение функций isspace, iscntrl, ispunct, isp¬
rint
isgraph. Функция isspace
и
определяет принадлежность аргумента следу¬
пробельных символов: пробел (' ), новая страница ( \f ), ноэая
строка ( \n'), возврат каретки ('\r'), горизонтальная табуляция ('\t'), вертика¬
льная табуляция ('YO- Функция iscntrl определяет принадлежность аргумента
ющему списку
следующему списку управляющих символов: горизонтальная табуляция
вертикальная табуляция ('\v')> новая страница ('\f'), звуковой сигнал
возврат на один символ ('\b'), возврат каретки 0\г)> новая строка ('\п')ция
ispunct
определяет, является ли аргумент знаком пунктуации
('\t'),
(Ла )»
Функ¬
или специа¬
цифрам, буквам или пробелам, например,
$> #> (> )> [> L {> }> ;> :> % и Т-Д- Функция isprint определяет, можно ли отобра¬
зить данный символ на экране (включая символ пробела). Функция isgraph ра¬
ботает так же, как и isprint, но из списка символов исключен символ пробела.
льным символом, не относящимся к
/*
Применение функций isspace,
isgraph */
#include <stdio.h>
#include <ctype.h>
main
iscntrl,
isprint,
ispunct,
()
{
pnntf ("%s\n%s%%s%s\n%s%s%s\n%s%s\n\n",
"According to isspace:",
"Newlme", isspace('\n') ?
"
"
is
"whitespace
character"
,
"
is a
isspace ('\t') ?
"whitespace character",
isspace ('%') ? "% is a
"
"
"
:
"
:
is
"%
is
is
:
а
"Horizontal
not
tab"
a
а
",
",
",
a
not
not
,
character");
"whitespace
printf("%s\n%s%s%s\n%s%s\n\n", "According to
is a
"Newline", iscntrl ('\n') ?
"control
iscntrl
"control
iscntrl:",
"
"
"
:
is
not
a
character",
('$')
?
"$
is
a
"
:
"$
is
not
a
",
character");
Рис. 8.4. Применение isspace, iscntrl, ispunct, isprint, isgraph (часть 1
из
2)
",
Символы и строки
377
"According to ispunct:",
"; is not а ",
pnntf("%s\n%s%s\n%s%s\n%s%s\n\n",
"
:
ispunct (';') ? "; is а
"punctuation character",
"
ispunct ('Y') ? "Y is а
"punctuation character",
"
ispunct ('#') ? "# is a
"punctuation character");
pnntf("%s\n%s%s\n%s%s%s\n\n",
isprint ('$') ? "$ is a
"printing character"
"Alert",
isprint
('\a')
:
"Y
is
not
а
",
:
"#
is
not
a
",
"According to
"
:
"$ is not
a
?
"
is
printf("%s\n%s%s\n%s%s%s\n",
? "Q is
isgraph ('Q')
"
:
is
not
a
",
"According to isgraph:",
"
:
a
"Q is not a ",
"printing character other than
')
isgraph('
character
"printing
return
"
character");
"printing
"Space",
a
ispnnt:",
",
?
"
other
a
a
"
than
a
is
space",
"
:
is
not
a
",
space");
0;
According to isspace:
Newline is a whitespace character
Horizontal tab is a whitespace character
%
is
not
a whitespace
According to
Newline is a
$
is
not
a
character
iscntrl:
control
control
character
character
According to ispunct:
; is a punctuation character
Y
is
#
is
a punctuation character
a punctuation character
not
According to isprint:
$ is a printing character
Alert
is
not
a printing
character
According to isgraph:
Q is a printing character
Space
is
Рис. 8.4.
not
other
than
character
a
other
space
than
a
space
Применение isspace, iscntrl, ispunct, isprint, isgraph (часть 2
8.4.
В
a printing
Функции преобразования
из
2)
строк
функции преобразования строк из биб¬
(stdlib). Эти функции преобразуют строки
этом разделе рассматриваются
лиотеки утилит
общего
назначения
цифр в целые значения и значения с плавающей точкой. На рис. 8.5 приведен
функций преобразования строк. Обратите внимание на модификатор
переменной nPtr в заголовке функции (читается справа нале¬
«nPtr является указателем на символьную константу»); const объявляет,
значение аргумента не будет изменяться.
перечень
const
во:
что
в описании
378
Глава 8
Описание функции
Прототип
double
int
atof
atoi
long
(const
strtod
char
char
(const
atol
double
(const
char
(const
Преобразует строку nPtr в
*nPtr)
*nPtr)
*nPtr)
char
char
*nPtr,
тип double
Преобразует строку
nPtr в тип int
Преобразует строку
nPtr в тип
Преобразует строку
nPtr в тип double
Преобразует строку
nPtr в тип
long
int
**endPtr)
long
strtol
**endPtr,
(const char
int base)
unsigned long
*nPtr,
char
strtoul
**endPtr,
(const
int
Рис. 8.5. Перечень функций
Хороший
Когда
char
*nPtr,
char
Преобразует
long.
строку nPtr в тип unsigned long
base)
преобразования
строк из библиотеки утилит общего назначения
стиль программирования 8.3
вы вызываете
функции
из
библиотеки утилит общего назначения, подключите
заголовочный файл <stlib.h>
Функция atof (рис. 8.6) преобразует аргумент
ставляет число с плавающей точкой,
значение типа
строку, которая пред¬
double
в значение типа
double. Если аргумент функции
не может
например, если первый символ строки не является
и возвращает
быть преобразован
цифрой,
поведение
фун¬
кции atof не определено.
Применение atof
#include <stdio.h>
/*
#include
*/
<stdlib.h>
main ()
{
double
d
=
d;
atof ("99.0")
;
printf("%s%.3f\n%s%.3f\n"
"The
"The
return
,
string \"99.0\"
converted value
converted
divided
to
2
by
is
double
is
",
d
/
",
d,
2.0);
0;
}
The
The
string "99.0" converted to
converted value divided by
double
is
99.000
2 is 49.500
Рис. 8.6. Применение atof
Функция atoi (рис. 8.7) преобразует аргумент
строку цифр, которая
в значение типа int и возвращает значение типа
представляет целое число,
int. Если аргумент функции не может быть преобразован, то поведение функ¬
ции atoi не определено.
Символы и строки
379
/* Применение atoi
#include <stdio.h>
#include
*/
<stdlib.h>
main ()
{
int
i
I;
atoi("2593")
printf("%s%d\n%s%d\n",
"The string \"2593\" converted to int is
"The converted value minus 593 is ", i
return 0;
=
", I,
593);
-
The string "2593" converted to int is 2593
The converted value minus 593 is 2000
Рис. 8.7. Применение atoi
Функция atol
(рис. 8.8) преобразует
аргумент
строку цифр, которая
в значение типа long и возвращает
представляет число типа длинное целое,
значение типа long. Если аргумент функции не может быть преобразован, то
поведение
long
функции atol
не
определено.
Если для хранения
используются 4 байта, то функции atoi и atol
/* Применение atol
#include <stdio.h>
работают
типа
int
и
типа
идентично.
*/
<stdlib.h>
#include
main()
{
long
1
=
1;
atol("1000000");
printf("%s%ld\n%s%ld\n",
"The string \"1000000\" converted to
"The converted value divided by 2 is
return 0;
long int is
", 1 / 2);
",
1,
}
The string "1000000" converted to long int is 1000000
The converted value divided by 2 is 500000
Рис. 8.8. Применение atol
Функция strtod (рис. 8.9) преобразует последовательность символов,
плавающей точкой, в значение типа double. Эта фун¬
кция имеет два аргумента: строку (char *) и указатель строки. Строка содер¬
жит последовательность символов, которые должны быть преобразованы в тип
представляющих число с
double. Указателю присваивается адрес символа, который
символом
d
=
строки-остатка после преобразования
strtod
(string,
&strmgPtr) ;
является
части строки.
первым
Оператор
380
Глава 8
из программы рис. 8.9 означает, что d присваивается значение типа
лученное после
первый
символ
double, по¬
преобразования строки string, а stringPtr будет указывать на
(%) после преобразования значения (51.2) из string.
/* Применение strtod */
#include <stdio.h>
#include <stdlib.h>
main ()
{
double d;
char *string
char
d
=
"51.2%
are
admitted";
*stringPtr;
=
strtod(string, &stringPtr);
printf("The string \"%s\" is converted to the \n",
string) ;
printf("double value %.2f and the string \"%s\"\n",
d, stringPtr);
return
0;
}
string "51.2%
The
double
value
51.20
are
and
admitted"
is
converted
string "%
the
Рис. 8.9.
are
to
the
admitted"
Применение strtod
Функция strtol (рис. 8.10) преобразует последовательность символов,
представляющих целое число, в значение типа long. Эта функция имеет три
аргумента: строку (char *), указатель строки и целое число. Строка содержит
последовательность символов, которые должны быть преобразованы. Указате¬
лю
присваивается адрес символа, который
ки-остатка после
преобразования
вание, по которому производится
x
=
strtol
(string,
Целое число
преобразование. Оператор
&remamderPtr,
8.10 означает,
является первым символом стро¬
части строки.
0)
определяет осно¬
;
long, по¬
remainпреобразования строки string. Второй аргумент
derPtr
после преобразойания будет указывать на остаток строки. Если для
второго аргумента использовать значение NULL, то строка-остаток будет про¬
0
показывает, что преобразуемое значе¬
игнорирована. Третий аргумент
ние может иметь восьмеричный (основание 8), десятичный (основание 10) или
шестнадцатеричный формат (основание 16). Основание может иметь значение
из программы рис.
лученное
что x присваивается значение типа
после
0
или может быть любым значением в диапазоне от 2 до 36. См. приложение Д,
«Системы счисления», где детально обсуждаются восьмеричная, десятичная и
шестнадцатеричная системы счисления. Для численного представления целых
значений от 11 до 35 в диапазоне оснований от 11 до 36 используются символы
A-Z. Например, шестнадцатеричное число может содержать цифры 0-9 и сим¬
волы A-F. Целые по основанию 11 могут содержать цифры 0-9 и символ А. Цепые по основанию 24 могут содержать цифры 0-9 и символы A-N. Целые по
основанию
36 могут содержать цифры 0-9
и символы
A-Z.
Символы и строки
381
/* Применение strtol
#include <stdio.h>
#include <stdlib.h>
*/
()
mam
{
long x;
char *string
"-1234567abc",
=
*remamderPtr;
x
strtol(string, &remainderPtr, 0);
pnntf ("%s\"%s\"\n%s%ld\n%s\"%s\"\n%s%ld\n",
"The original string is ", string,
"The converted value is ", x,
"The remainder of the original string
=
remainderPtr,
"The converted value plus
0;
return
The
The
The
The
567
is
",
x
is
",
567);
+
original string is "-1234567abc"
converted value is -1234567
remainder of the original string is "abc"
converted value plus 567 is -1234000
Рис. 8.10. Применение strtol
Функция strtoul преобразует последовательность символов, представляю¬
unsigned long, в значение типа unsigned long. Эта функция
работает аналогично функции strtol. Оператор
щую целое типа
x
-
strtoul
(string,
&remainderPtr,
0);
из программы рис. 8.11 означает, что x присваивается значение
unsigned long,
преобразования string. Второй аргумент, &remainderPtr, по¬
пока¬
сле преобразования указывает на остаток string. Третий аргумент
0
зывает, что преобразуемое значение может иметь восьмеричный, десятичный
или шестнадцатеричный формат.
полученное после
/* Применение
strtoul
#include
<stdio.h>
#include
<stdlib.h>
*/
main ()
{
unsigned long
char *string
x;
"1234567abc", *remamderPtr;
strtoul(string, &remainderPtr, 0);
printf("%s\"%s\"\n%s%lu\n%s\"%s\"\n%s%lu\n",
"The original string is ", string,
"The converted value is ", x,
x
=
=
"The
remainder of
the
remainderPtr,
"The converted value
original
mmus
567
string is
is
",
return 0;
}
Рис. 8.11. Применение strtoul (часть 1
из
2)
x
-
",
567) ;
382
Глава 8
The
The
original string is
converted value is
The
remainder of
The
"1234567abc"
1234567
the original
converted value minus 567
string is "abc"
is 1234000
Рис. 8.11. Применение strtoul (часть 2 из 2)
Функции стандартной библиотеки ввода/вывода
8.5.
В этом разделе представлены несколько функций
теки
ввода/вывода (stdio), предназначенные
символов и строк из
Прототип
int
стандартной библиотеки ввода/вывода.
Описание функции
*gets
Вводит следующий символ со стандартного
возвращает его в формате целого
(void)
getchar
char
специально
стандартной библио¬
для операций с сим¬
На рис. 8.12 приведен перечень функций
вольными и строковыми данными.
ввода/вывода
из
(char
устройства
ввода и
Вводит символы со стандартного устройства ввода в массив s до
тех пор, пока не встретит символ новой строки или индикатор
*s)
конца
файла После
этого к массиву
добавляется ограничивающий
символ NULL
int
putchar
int
puts
(int
(const
Печать символа, хранящегося
с)
char
*s)
в с
Печать строки s с последующим символом новая строка
int
spnntf (char *s,
const char *format,
...)
Эквивалент функции printf
int
Эквивалент функции scanf за исключением того, что ввод
осуществляется из массива s, а не с клавиатуры
sscanf
const
char
(char
*s,
*format,
.
.
.
)
вывода
запоминается
Рис. 8.12. Символьные и строковые
Хороший
в
за
исключением того,
массиве s,
а
не
что
отображается
результат
на экране
функции стандартной библиотеки ввода/вывода
стиль программирования 8.4
функции из стандартной библиотеки ввода/вывода,
файл <stdio.h>
Если вы вызываете
заголовочный
подключите
8.13, использует функции gets и putchar
устройства (клавиатуры) и
выводит символы строки в обратном порядке. Функция gets чита¬
со стандартного входного устройства и передает их своему аргу¬
Программа, приведенная
на рис.
для чтения строки текста со стандартного входного
рекурсивно
ет символы
менту
массиву типа char
до тех пор, пока не встретит символ новой стро¬
файла. Символ NULL ('\0') добавляется в массив по¬
сле окончания считывания. Функция putchar печатает свой аргумент
сим¬
вол. Для печати строки текста в обратном порядке программа рис. 8.13 вызы¬
вает рекурсивную функцию reverse. Если первый символ массива, получен¬
ный функцией reverse, является нулевым символом '\0', то следует возврат из
функции. В противном случае следует вызов reverse, и ее аргумент указывает
на адрес «нового» массива, который начинается с элемента s[l]. Когда рекур¬
сивный вызов завершается, функция putchar выводит элемент s[0]. Порядок
ки или индикатор конца
Символы и строки
383
следования двух операторов в части else структуры if приводит к тому, что
функция reverse переходит к граничному символу NULL строки до вывода
символа на печать. Как только рекурсивный вызов завершается, символы вы¬
водятся в
обратном
порядке.
/* Применение gets and putchar */
#include <stdio.h>
main ()
{
char sentence[80];
void reverse(char *);
а
printf("Enter
line
of
text:\n");
gets (sentence);
printf("\nThe line printed backwards is:\n");
reverse(sentence);
return 0;
}
void reverse(chai *s)
{
if (s[0]
'\0')
return;
else {
reverse(&s[1]);
putchar(s[0]);
}
}
==
Enter
a
line
Characters
of
text:
and Strings
The
line printed backwards
sgnirtS dna sretcarahC
Enter a
able was
The
line
I
of
ere
is:
text:
I
saw elba
line printed backwards
I ere I saw elba
is:
able was
Рис. 8.13. Применение gets and
putchar
8.14, использует функции getchar и puts
устройства в символьный мас¬
массив символов как строку. Функция getchar читает
входного устройства и возвращает его как целое значе¬
Программа, приведенная
на рис.
для чтения символов со стандартного входного
сив sentence и выводит
символ со стандартного
ние. Функция
puts
в качестве аргумента строку (char *)
новой строки ('\n).
получает
с последующим символом
и выводит ее
384
Глава 8
/* Применение getchar
#include <stdio.h>
main ()
{
char с,
int i
puts
и
*/
sentence[80];
0;
=
line
puts("Enter
while ( ( с
of text:");
=
getchar()
с;
sentence[i++]
)
!=
'\n')
=
sentence[i]
=
/*
'\01;
вставьте
символ
NULL
в
конец
строки
*/
puts("\nThe line entered was:");
puts(sentence);
return 0;
Enter line of text:
This is a test.
The
line
This
is
entered
a
was:
test.
Рис. 8.14.
Применение getchar and puts
Программа прекращает ввод символов, когда getchar
ный пользователем
вол
NULL, чтобы
символ
новой строки. В
считывает
массив sentence
массив мог восприниматься как строка.
введен¬
добавляется
Функция puts
сим¬
выво¬
дит строку, содержащуюся в sentence.
Программа, приведенная на рис. 8.15, использует функцию sprintf для
массив символов. Эта функция
форматированных данных в массив s
использует ту же самую спецификацию преобразования, что и printf (см. гла¬
ву 9, где детально обсуждаются все возможности форматированной печати).
Программа вводит значения типа int и float, которые форматируются и выво¬
дятся в массив s. Массив s является первым аргументом функции sprintf.
печати
/* Применение sprintf */
<stdio.h>
#include
mam
()
{
char
int
s
[80];
x;
float
у;
mteger and float:\n");
&y);
sprintf(s, "Integer:%6d\nFloat:%8.2f", x, у);
prmtf ("%s\n%s\n",
"The formatted output stored m array s:",
return 0;
printf("Enter
an
scanf("%d%f",
&x,
Рис. 8.15. Применение sprintf (часть 1
из
2)
s);
Символы и строки
385
an integer and float:
298 87.375
The formatted output stored in array
298
Integer:
Float:
87.38
Enter
s:
Рис. 8.15. Применение sprintf (часть 2
из
2)
на рис. 8.16, демонстрирует функцию sscanf для
форматированных данных из массива символов s. Эта функция испо¬
ту же самую спецификацию преобразования, что и scanf. Программа
Программа, приведенная
чтения
льзует
считывает значения типа
int
float
и
ветственно в переменных x и у.
является первым аргументом
/* Применение
sscanf
из массива s и запоминает величины соот¬
Величины
x и у выводятся на печать.
Массив
s
функции sscanf.
*/
<stdio.h>
#include
main ()
{
char
int
"31298
=
s[]
87.375";
x;
float
у;
sscanf(s,
"%d%f",
&x,
&y);
printf("&s\n%s%6d\n%s%8.3f\n" ,
"The values stored in character
x,
"Integer:",
return
The
"Float:",
array
s
are:",
у);
0;
values
stored
in
character
array
s
are:
Integer: 31298
Float:
87.375
Рис. 8.16.
8.6.
Применение sscanf
Функции операций над строками
обработки строк
Библиотека обработки строк содержит
выполнения
операций
из библиотеки
множество полезных
функций
иска символов и других строк в строке, для деления строки на лексемы
ческие
части)
и определения длины строки.
этих
Хороший
13
функции
файл <string.h>.
Зак.801
(логи¬
этом разделе представлены
библиотеки
обработки
фун¬
строк.
стиль программирования 8.5
Если вы вызываете
ный
В
операций над строками из
функций приведен на рис. 8.17.
кции для выполнения
Перечень
для
со строковыми данными, для сравнения строк, для по¬
из
библиотеки обработки строк, подключите
заголовоч¬
386
Глава 9
Описание функции
Прототип
char
*strcpy
(char
const
*sl,
char
*s2)
Копирует строку s2
char
*strncpy
(char
const
*sl,
char
Копирует
*s2,
*strcat
(char
const
*sl,
char
значение s1
n)
size_t
массив s1
значение s1
*s2)
Объединяет строку s2
строки s2
Возвращает
массив s1
не более чем n символов строки s2 в
Возвращает
char
в
со
строкой
переписывает символ
массива s1
Первый символ
Возвращает
NULL строки s1
значение s1
char
*strncat
(char
*sl,
const
char
Объединяет
Рис. 8.17.
Функции
более
чем
n
символов строки
Первый символ строки s2
строки s1
Возвращает значение
s2 со
строкой
массива s1
переписывает символ
NULL
s1
для выполнения
Функция strcpy
не
n)
size__t
*s2,
операций
над строками из библиотеки
обработки
строк
в первый аргу¬
второй аргумент
строку
который должен иметь достаточный размер, чтобы
запомнить строку и ограничивающий символ NULL, который также копиру¬
ется. Функция strncpy эквивалентна strcpy за исключением того, что strncpy
задает число символов, которые необходимо скопировать из строки в массив.
Обратите внимание, что функция strncpy не копирует ограничивающий сим¬
мент
вол
копирует
массив символов,
NULL второго аргумента,
если
не выполнено следующее условие:
копируемых символов превышает длину строки, по
меньшей мере,
число
на едини¬
Например, если вторым аргументом является строка "tfcst", то ограничи¬
вающий символ NULL записывается, только если значение третьего аргумен¬
та функции составляет не менее 5 (4 символа в строке "test" плюс 1 символ
NULL). Если значение третьего аргумента больше 5, то символы NULL добав¬
ляются в массив до тех пор, пока общее число записанных символов не станет
цу.
равным числу, заданному в третьем аргументе.
Распространенная ошибка программирования 8.6
Отсутствует ограничивающий
да величина третьего
символ NULL в первом аргументе
аргумента меньше или равна длине строки
функции strncpy,
во
ког¬
втором аргументе
Программа, приведенная на рис. 8.18, вызывает функцию strcpy для ко¬
пирования всей строки из массива x в массив у и первых 14 символов из масси¬
ва x в массив z. Символ NULL ('\0') добавляется в массив z в отдельном опера¬
программы к функции strcpy не копирует нулевой
меньше
длины строки второго аргумента).
(третий аргумент
Функция strcat выполняет объединение строк (конкатенацию): второго
торе, поскольку
обращение
символ
аргумента
строки
щим
Первый
строку.
с первым аргументом
символ
второго
массивом символов, содержа¬
аргумента
заменяет
последний
NULL строки первого аргумента. Программист должен убедиться,
содержащий
первую строку, достаточен
строк и ограничивающего символа NULL,
по
для хранения обеих
копируется из второй стро-
размерам
который
символ
что массив,
Символы и строки
387
ки.
Функция strncat выполняет объединение определенного числа символов
второй строки с первой строкой. Ограничивающий символ NULL добавля¬
ется
к
автоматически.
результата
строке
Программа, приведенная на
рис. 8.19, демонстрирует применение функций strcat и strncat.
из
I
/*
Применение
и
strcpy
strncpy */
#include <stdio.h>
#include <stnng.h>
main
()
{
char x[]
"Happy Birthday to You";
z [15];
=
char у[25],
("%s%s\n%s%s\n",
"The string m array x is:
"The string in array у is:
printf
strncpy(z, x, 14);
'\0';
z[14]
printf ("The string
",
",
x,
strcpy(y,
x));
=
return
m
array
z
is:
%s\n",
z);
0;
}
string in array x:
The string in array у:
The string in array z:
The
Happy Birthday to You
Happy Birthday to You
Happy Birthday
Рис. 8.18. Применение strcpy
/* Применение strcat
и
и
strncpy
*/
strncat
#include <stdio.h>
#include <string.h>
main ()
char
char
"Happy ";
=
sl[20]
char s2[]
"New
=
Year";
"";
=
s3[40]
%s\ns2
%s\n", sl, s2);
%s\n", strcat(sl, s2));
printf("strcat(sl, s2)
%s\n", strncat(s3, sl,
printf("strncat(s3, sl, 6)
%s\n", strcat(s3, sl));
pnntf ("strcat(s3, sl)
printf("sl
=
=
=
=
=
return
0;
sl
=
Happy
s2
=
New
Year
strncat(sl,
Happy New Year
6)
Happy
Happy Happy New Year
=
s2)
strncat(s3, sl,
strcat(s3, sl)
=
=
Рис. 8.19. Применение strcat
13*
и strncat
6));
388
Глава 9
8.7.
Функции
сравнения
библиотеки
из
обработки
строк
В
этом разделе представлены
функции сравнения строк strcmp
библиотеки обработки строк. Прототипы функций
дой из них приведены на рис. 8.20.
из
char
(const
strcmp
*sl,
const
char
*s2)
Сравнивает строку s1
значение
равна,
int
strncmp
Описание функции
Прототип
int
и
и краткое описание каж¬
strncmp
char
(const
*sl,
const
меньше 0
меньше ил^1
char
со
строкой s2 Функция возвращает 0,
больше 0,
больше s2.
*s2,
или
size_t
если s1 соответственно
n)
Сравнивает до n символов строки s1 со строкой s2
Функция возвращает 0, значение меньше 0 или больше 0,
если s1 соответственно равна, меньше или больше чем s2
Рис. 8.20.
Программа,
Функции сравнения строк
показанная
на рис.
из
библиотеки обработки строк
8.21, сравнивает три строки, используя
strncmp. Функция strcmp сравнивает символ за символом
первый аргумент-строку со вторым аргументом-строкой. Функция возвращает
функции strcmp
и
0, если строки равны, отрицательное значение, если первая строка меньше
второй,
ция
и положительное значение, если первая строка
strncmp
strncmp
эквивалентна
больше второй. Функ¬
функции strcmp,
Функция strncmp
за
исключением
сравнивает до n символов в строках.
символы, идущие за символом
NULL. Эта программа
возвращаемое при каждом вызове
что
того,
не сравнивает
печатает целее значение,
функции.
Распространенная ошибка программирования 8.7
Предположение, что strcmp и strncmp возвращают 1 в случае равенства строк На са¬
функции strcmp и strncmp возвращают в этом случае 0 (значение false в С)
мом деле
и, следовательно, при проверке двух строк
ться
с
на равенство результатдолжен сравнива¬
0
Чтобы
понять утверждение, что одна строка
«больше»
или «меньше» дру¬
гой, рассмотрим процесс алфавитной сортировки ряда фамилий. Читатель,
не¬
бы фамилию «Jones» перед фамилией «Smith», поскольку
первая буква фамилии «Jones» стоит в алфавите перед первой буквой фами¬
лии «Smith». Но алфавит
это не просто набор из 26 букв; это упорядочен¬
сомненно, поместил
ный
ке.
Каждая буква занимает свое определенное место в спис¬
буква алфавита, а двадцать шестая буква алфавита.
узнает, что одна буква идет перед другой? Все символы
список символов.
«Z»
Как
не просто
компьютер
представлены в компьютере в виде числовых кодов; когда компьютер сравни¬
вает
две
строк.
строки,
он
фактически сравнивает
числовые
коды
символов
этих
Символы и строки
/*
389
Применение
strcmp
*/
strncmp
и
#include <stdio.h>
#include <string.h>
main
()
{
char
*sl
char *s2
char *s3
=
=
=
"Happy New Year";
"Happy New Year";
"Happy Holidays";
printf("%s%s\n%s%s\n%s%s\n\n%s%2d\n%s%2d\n%s%2d\n\n",
"sl
", sl, "s2
", s2, "s3
", s3,
=
=
"strcmp(sl,
"strcmp(sl,
"strcmp(s3,
s2)
s3)
sl)
=
=
=
=
",
",
",
strcmp(sl,
strcmp(sl,
strcmp(s3,
printf("%s%2d\n%s%2d\n%s%2d\n",
",
"strncmp(sl, s3, 6)
",
"strncmp(sl, s3, 7)
",
"strncmp(s3, sl, 7)
return 0;
=
=
=
sl
=
s2
=
s3
=
strncmp(sl,
strncmp(sl,
strncmp(s3,
s3,
s3,
sl,
6),
7),
7));
Happy New Year
Happy New Year
Happy Holidays
0
strcmp(sl,
s2)
=
strcmp(sl,
s3)
=
1
strcmp(s3,
sl)
=
-I
0
strncmp(sl,
s3,
6)
=
strncmp(sl,
s3,
7)
=
1
strncmp(s3,
sl,
7)
=
-1
Рис. 8.21.
Совет
s2),
s3),
sl));
Применение strcmp
и
strncmp
по переносимости программ 8.2
Внутренние
личаться
числовые коды, использующиеся для представления символов, могут от¬
на
различных
компьютерах.
В целях стандартизации представления символов большинство произво¬
дителей компьютеров разрабатывают свои машины для работы с одной из двух
это аббревиатура
ASCII или EBCDIC. ASCII
популярных схем кодировки
«American Standard Code for Information Interchange» (Американский стан¬
дартный код для обмена информацией), а EBCDIC означает «Extended Binary
Coded Decimal Interchange Code» (Расширенный двоично-десятичный код об¬
мена
информацией). Имеются
более популярны.
ASCII и EBCDIC
и другие схемы кодирования, но эти две
называются
кодами
символов
(character codes)
или
наи¬
набо¬
(characters sets). Фактически
операции со строками и символа¬
ми выполняются не с самими символами, а с соответствующими числовыми
кодами. Это объясняет взаимозаменяемость символов и небольших целых чи¬
рами
сел
в
символов
С. Следовательно, поскольку правомерно говорить,
код «больше»,
«меньше»
или
«равен»
что
один
другому числовому коду,
числовой
то же самое
390
Глава 8
становится правомерным в отношении различных символов или строк, если
сравнивать их между собой посредством ссылки на соответствующие числовые
коды. В приложении Г приведен список кодов символов стандарта ASCII.
8.8.
В
Функции
поиска из
этом разделе представлены
библиотеки
обработки
строк
функции библиотеки обработки строк,
льзующиеся для поиска в строках символов и других строк
испо¬
(подстрок). Пере¬
функций приведен на рис. 8.22. Обратите внимание, что функции
strcspn
strspn возвращают тип size_t. Тип size_t определяется как целочис¬
ленный тип, возвращаемый операцией sizeof.
чень этих
и
Совет по переносимости программ 8.3
Тип
size_t является системно-зависимым
либо для типа unsigned int
либо для
типа
unsigned long,
Описание функции
Прототип
char
синонимом
*strchr
(const
char
*s,
int
с)
Находит позицию первого вхождения
Если с
В
символа
найден, функция возвращает указатель
противном
случае возвращается указатель
с в строку s
на
с в
строке s
со значением
NULL
size_t
strcspn
(const
char
*sl,
const
char
*s2)
и возвращает длину начального сегмента строки
s1, содержащего только те символы, которые не входят в
Определяет
строку s2
size_t
strspn
(const
char
*sl,
const
char
*s2)
и возвращает длину начального сегмента строки
s1, содержащего только те символы, которые входят в строку
s2
Определяет
char
*strprbk
(const
char
char
const
*sl,
*s2)
Находит в строке s1 позицию первого вхождения любого из
символов строки s2 Если символ из строки s2 найден,
функция возвращает указатель на этот символ в строке s1 В
противном случае возвращается указатель со значением NULL
char
*strrchr
(const
char
*s,
int
с)
Находит позицию последнего вхождения символа с в строку
Если
В
с
найден, функция возвращает указатель
противном
случае
возвращается
указатель
на
с в
со значением
NULL
char
*strstr
(const
char
*sl,
const
char
s
строке s
*s2)
Находит позицию первого вхождения строки s2 в строку s1
Если подстрока найдена, возвращает указатель подстроки в
строке s1 В противном случае возвращается указатель со
значением NULL
Символы и строки
391
kПрототип
char
1 Описание функции
*strtok
(char
*sl,
const
char
*s2)
Последовательный вызов
функции
выполняет
разбиение
s1 на лексемы (логические части, такие как слова в
текстовой строке), разделенные символами, содержащимися в
строки
строке s2
При первом вызове функция получает в качестве
аргумента строку s1, а в последующих вызовах, чтобы
продолжить разбиение той же самой строки, в качестве
первого аргумента передается NULL При каждом вызове
возвращается указатель на текущую лексему строки s1 Если
при очередном вызове функция находит, что лексем в строке
не осталось, то возвращается NULL
Рис. 8.22.
Функции операций
Функция strchr
со
(первого
возвращает указатель на этот символ.
вхождения данного символа в
на
иска первого из символов 'a' и первого из
строку
символ не
This is а test .
/* Применение strchr
#include <stdio.h>
#include <stnng.h>
main
строку) и
найден, то strchr возвра¬
рис. 8.23, использует strchr для по¬
символов V, входящих (или не вхо¬
Если
щает NULL. Программа, приведенная
в
библиотеки обработки строк
выполняет поиск заданного символа в строке до первого
положительного результата
дящих)
из
строками
*/
()
(
char
*string
char
characterl
if
"This
=
is
test";
characterl)
(strchr(string,
was
printf("\'%c\'
а
character2
'a',
=
characterl,
found
m
=
'z';
!= NULL)
\"%s\".\n",
stnng);
else
pnntf("\'%c\' was not found in \"%s\".\n",
characterl, string);
if (strchr(string, character2) != NULL)
pnntf("\'%c\' was found in \"%s\".\n",
character2, string);
else
printf("\'%c\' was not found in \"%s\".\n",
character2, string);
return 0;
}
'a'
was
found
'z'
was
not
in
found
"This
in
is
а
"This
Рис. 8.23.
test".
is
а
test".
Применение strchr
Функция strcspn (рис. 8.24) определяет длину начальной
строку второго
части строки в пер¬
(часть) не содержит ни одного из символов, входящих
аргумента. Функция возвращает длину найденного сегмента.
вом аргументе, которая
в
392
Глава 8
/* Применение strcspn
#include <stdio.h>
#include <string.h>
main
*/
()
char
*stringl
char
*string2
=
"The
=
"1234567890";
value
is
3.14159";
printf("%s%s\n%s%s\n\n%s\n%s%u",
", stnng2,
", stringl, "string2
"stringl
"The length of the initial segment of stringl'
",
"containing no characters from stnng2
strcspn(stnngl, stnng2));
return 0;
=
=
=
stringl
string2
=
The
=
1234567890
value
The
length of
containing no
the
is
3.14159
initial
characters
segment of stringl
from string2 = 13
Рис. 8.24. Применение strcspn
Функция strpbrk
лов
строки
второго
выполняет поиск первого вхождения
аргумента в
символов второго аргумента не
строку первого
аргумента.
Если
из симво¬
ни
один
из
найден, strpbrk возвращает NULL. Програм¬
8.25, выполняет поиск в строке stringl
который встречается в строке string2.
ма, приведенная на рис.
ложению символа,
любого
первого по по¬
/* Применение strpbrk */
#include <stdio.h>
#include <stnng.h>
main ()
{
char
*stringl
=
"This
char
*string2
=
"beware";
is
а
test";
printf("%s\"%s\"\n'%c'%s\n\"%s\"\n",
"Of the characters m ", stnng2,
*strpbrk(stnngl,
"
return
is
the
first
string2),
character
to
appear
m
",
stringl);
0;
}
Of
the
'a'
is
"This
characters
the
first
a
test"
is
in
"beware"
character
Рис. 8.25.
Функция strrchr
него
положительного
строку)
и
to
appear
in
Применение strpbrk
выполняет поиск заданного символа в строке до послед¬
результата
(последнее
вхождение
данного
символа
в
возвращает указатель на этот символ. Если символ не найден, то
393
Символы и строки
Программа, приведенная на рис. 8.26, вызывает
V, входящих в строку "А zoo has
strrchr возвращает NULL.
strrchr для
поиска последнего из символов
many animals
including zebras".
/* Применение strrchr
#include <stdio.h>
#include <stnng.h>
main ()
{
char
int
*stringl
с
'z1
=
=
*/
"А
zoo
has
many
animals
including
zebras";
;
printf("%s\n%s'%c1%s\"%s\"\n",
"The
remainder
"last
"
return
is:
of
occurrence
",
strrchr
stringl beginning
of character ", c,
(stringl,
with
the
",
c));
0;
}
The
remainder
last
of
occurrence
stringl beginning with the
of character 'z' is: "zebras"
Рис. 8.26.
Применение strrchr
Функция strspn (рис. 8.27) определяет длину начальной
аргументе.
Функция
*/
/* Применение strspn
#include <stdio.h>
#include <stnng.h>
main()
{
char
char
части
*stringl
*string2
=
"The
=
"aehilsTuv";
value
is
3.14159";
printf("%s%s\n%s%s\n\n%s\n%s%u",
", string2,
", stringl, "string2
"stringl
"The length of the initial segment of stringl",
",
"containing only characters from string2
=
=
=
return
strspn(stringl,
0;
stnng2));
}
stringl
string2
The
=
The
=
aehilsTuv
length of
строки в
содержит только символы строки во втором
возвращает длину найденного сегмента.
(часть)
первом аргументе, которая
value
the
containing only
is
3.14159
initial
characters
segment of stringl
from string2 = 13
Рис. 8.27.
Применение strspn
394
Глава 8
Функция strstr выполняет поиск строки второго аргумента в строке пер¬
вого аргумента до первого положительного результата (первого вхождения
подстроки в строку первого аргумента). Если вторая строка найдена в первой
строке, функция возвращает указатель на найденную подстроку в первом ар¬
гументе. Программа, приведенная на рис. 8.28, использует strstr для поиска
подстроки "def" в строке "abcdefabcdef".
I
/* Применение strstr
#include <stdio.h>
#include
*/
<string.h>
main ()
{
char
*stringl
=
"abcdefabcdef";
char
*string2
=
"def";
printf("%s%s\n%s%s\n\n%s\n%s%s\n",
", stringl, "stnng2
", string2,
"stringl
"The remainder of stringl beginning with the ",
"first occurrence of string2 is: ",
=
=
strstr(stringl,
return
string2));
0;
}
stringl
=
abcdefabcdef
string2
=
def
The
remainder
first
of
occurrence
Функция strtok
stringl beginning
of
with
string2
is:
Рис. 8.28.
Применение
используется для
the
defabcdef
strstr
разбиения
строки на ряд лексем. Лексе¬
ма
это последовательность символов, отделенная
лами
(обычно пробелами
ста каждое слово можно
разделительными
его от других слов, как разделительные символы.
Чтобы разбить строку на несколько лексем, требуется
зов
симво¬
пунктуации). Например, в строке тек¬
рассматривать как лексему, а пробелы, отделяющие
или знаками
функции strtok (предполагаем,
что строка содержит
многократный
вы¬
более одной лексемы).
При первом вызове функция strtok получает два аргумента: строку, которую
нужно разбить
для
на лексемы, и строку, содержащую символы, использующиеся
разбиения. В
tokenPtr
=
программе, приведенной на рис. 8.29, оператор
strtok
11
(string,
");
присваивает tokenPtr значение указателя на первую лексему в строке string.
означает, что лексемы в string отде¬
Второй аргумент функции strtok
"
"
пробелами. Функция strtok выполняет поиск первого символа в string,
который не является разделительным символом (пробелом). С этого символа
ляются
начинается первая лексема.
льный
Посля
этого
функция ищет следующий разделите¬
символ в строке и заменяет его нулевым символом
вершает текущую лексему.
символ,
следующий
текущую лексему.
Функция strtok
('\0').
Этот
символ за¬
запоминает значение указателя на
сразу за найденной лексемой, и возвращает указатель на
Символы и строки
395
/* Применение strtok
#include <stdio.h>
#include
main
*/
<string.h>
()
{
char
string[]
char
*tokenPtr;
"This
=
is
printf ("%s\n%s\n\n%s\n",
"The string to be
"The
tokenPtr
=
tokens
а
sentence
tokenized
with
is:",
7
tokens";
string,
are:");
strtok(string,
"
");
while
(tokenPtr != NULL) {
printf ("%s\n", tokenPtr);
tokenPtr
");
strtok(NULL,
"
=
}
return
0;
The string to be tokenized is:
This is a sentence with 7 tokens
The
tokens
are:
This
is
a
sentence
with
7
tokens
Рис. 8.29.
Применение strtok
1ри последующих вызовах для продолжения разбиения той же строки
функция strtok получает в качестве первого аргумента символ NULL. Аргу¬
мент
NULL показывает,
что при вызове
strtok должна продолжить разбиение
string, которое было записано при последнем вызове функ¬
ции. Если при очередном вызове strtok обнаруживается, что лексем уже не
с того места строки
то
функция возвращает NULL. Программа, приведенная на
8.29, использует функцию strtok для разбиения на лексемы строки "This
осталось,
рис.
is
а
sentence with 7 tokens"
("Это
предложение содержит 7
лексем"). Каждая
Отметьте, что strtok модифицирует входную
строку и, следовательно, если строка будет использоваться в программе после
обращения к функции strtok, то следует сделать копию исходной строки.
лексема
8.9.
печатается
отдельно.
Функции
Функции
памяти
библиотеки
обработки
строк
библиотеки обработки строк, представленные в этом разделе,
операций с блоками памяти, сравнение блоков памяти
упрощают выполнение
и поиск в
блоках
памяти.
Эти функции обращаются
с
блоками памяти,
как с
396
Глава 8
массивами символов и могут оперировать с любыми блоками данных. На
рис. 8.30 приведен перечень функций памяти библиотеки обработки строк.
Далее
в
тексте,
обсуждении функций,
при
термин «объект» обозначает блок
данных.
Параметры-указатели
ве
7
для этих
мы видели, что указатель
ен указателю типа
void *,
функций объявляются
любого
типа данных может
указатель void
наоборот
и
*
void *. В
как
гла¬
быть прямо присво¬
прямо быть
может
присвоен указателю на любой тип данных. Поэтому обсуждаемые функции
могут получать в качестве параметра указатель на любой тип данных. Поско¬
*
не может быть разыменован (т.е. тип объекта, на
льку указатель типа void
который
кция
ссылается
данный указатель,
(байтов),
раздела
быть определен), каждая фун¬
определяющий
число
символов
будет обрабатывать функция. Для простоты примеры
оперируют с массивами символов (блоками символов).
которое
Прототип
void
не может
получает дополнительный аргумент,
этого
Описание функции
*memcpy
(void
*sl,
const
void
*s2,
n)
size_t
/
Копирует n символов из объекта, указываемого s2, в объект,
указываемый s1 Функция возвращает указатель, ссылающийся
на
результирующий объект
void
*memmove
(void
*sl,
const
void
size_t n)
*s2,
Копирует n символов из объекта, указываемого s2, в объект,
указываемый s1 Копирование выполняется последовательно символы
из объекта, указываемого s2, копируются во временный массив и затем
из временного массива копируются в объект, указываемый s1 Функция
возвращает указатель, ссылающийся на результирующий объект
void
*memcmp
(const
void
const
*sl,
Сравнивает первые
void
n символов
Функция возвращает
значения
соответственно равен,
void
*memchr
(const
void
*s,
Выполняет
int
с,
size_t n)
*s2,
объектов, указываемых s1
0,
меньше 0 и больше
меньше или
больше
в
и
s2
если
s1
s2
size_t n)
поиск первого вхождения с
unsigned char)
чем
0,
первых
n
символах
найден, возвращает указатель
на
с в
(преобразованного
в тип
объекта, указываемого
объекте
s
Если
с
В противном случае
возвращает NULL
void
*memset
(const
void
*s,
Копирует
символов
с
int
с,
size_t
(преобразованный
n)
в тип
объекта, указываемого
Рис. 8.30. Функции
памяти из
s
unsigned char) в первые n
Возвращает указатель результата
библиотеки обработки строк
Функция memcpy копирует определенное число символов из объекта, на
который ссылается ее второй аргумент, в объект, на который ссылается пер¬
вый аргумент. Функция может получать указатель на любой тип объекта. По¬
ведение этой функции
приведенная
жащейся
на рис.
в массиве
объекта перекры¬
объекта. Программа,
не определено в том случае, если два
ваются в памяти, т.е. являются частями одного и того же
8.31, использует memcpy
s2, в массив sl.
для копирования строки, содер¬
Символы и строки
397
/* Применение memcpy */
#include <stdio.h>
#include <stnng.h>
main
()
{
char
sl[22],
"Copy this
=
s2[]
memcpy (sl, s2, 17);
pnntf("%s\n%s\"%s\"\n"
"After
"sl
return
After
sl
s2
s2
is
",
sl
mto
with
memcpy,",
sl);
0;
is
contains
into
copied
"Copy
this
sl
with memcpy,
string"
Рис. 8.31.
ло
,
copied
contams
stnng";
Применение memcpy
Функция memmove, как и функция memcpy, копирует определенное чис¬
из объекта, на который ссылается ее второй аргумент, в объект, на
байтов
который
первый аргумент. Сначала функция копирует заданное
временный символьный массив, а затем
массив в первый аргумент. Такой способ позволяет копировать
ссылается
число байтов из второго аргумента во
копирует этот
символы из одной части строки
в
другую часть той же самой строки.
Распространенная ошибка программирования 8.8
Функции операций со строками, за исключением функции memmove, дают неопреде¬
ленный результат, когда копирование происходит между частями одной и той же строки.
приведенная на рис. 8.32, использует функцию memmove для
копирования последних 10-ти байтов массива x в первые 10 байтов того же
массива x.
Программа,
I
*/
/* Применение memmove
#include <stdio.h>
#include <string.h>
main
()
{
char x[]
=
"Home
Sweet
printf("%s%s\n",
"The string
m
Home";
array
x
before
memmove
is:
",
x);
printf("%s%s\n",
"
The
string
memmove(x,
return
0
in
array
&x[5],
x
before
memmove
is:
",
10));
;
}
The
string
in
array
x before memmove
is:
Home
The
string
in
array
x before memmove
is:
Sweet
Рис. 8.32.
Применение
memmove
Sweet
Home
Home
Home
Глава 8
398
Функция memcmp (рис. 8.33) сравнивает определенное число символов
первого аргумента с соответствующими символами второго аргумента. Функ¬
ция возвращает значение больше 0, если первый аргумент больше второго ар¬
гумента, возвращает 0, если аргументы равны, и возвращает значение меньше
0, если первый аргумент меньше второго.
/* Применение memcmp */
#include <stdio.h>
#include <stnng.h>
main
()
{
char
sl[]
"ABCDEFG",
=
s2[]
"ABCDXYZ";
=
printf("%s%s\n%s%s\n\n%s%2d\n%s%2d\n%s%2d\n",
"sl
", sl, "s2
", s2,
=
return
=
"memcmp(sl,
"memcmp(sl,
s2,
s2,
4)
7)
=
"memcmp(s2,
sl,
7)
=
=
",
",
memcmp(sl,
s2,
memcmp(sl,
s2,
4),
7),
",
memcmp(s2,
sl,
7));
0;
}
sl
=
ABCDEFG
s2
=
ABCDXYZ
memcmp(s1,
s2,
4)
=
0
memcmp(s1,
s2,
7)
=
-1
memcmp(s2,
sl,
7)
=
1
Рис. 8.33.
Функция memchr
зультата),
Применение memcpy
выполняет поиск
представленного типом
байта (до первого
unsigned char,
положительного ре¬
в заданном числе
Если заданный байт найден, функция возвращает указатель
байтов объ¬
на найден¬
ный байт объекта. В противном случае функция возвращает указатель со зна¬
чением NULL. Программа, приведенная на рис. 8.34, выполняет поиск симво¬
ла (байта) V в строке "This is а string".
екта.
/* Применение memchr
#include <stdio.h>
#include
*/
<string.h>
main ()
{
char
*s
"This
=
is
а
string";
printf("%s\'%c\'%s\"%s\"\n",
"The remainder of s after
"
return
is
found
is
",
character
memchr(s,
'r',
",
16));
0;
}
The
remainder
of
s
after
character
'r'
is
"ring"
Рис. 8.34. Применение memchr
'r',
Символы и строки
399
Функция memset копирует значение байта второго аргумента в определен¬
ное число байтов объекта, на который указывает первый аргумент. Програм¬
8.35, использует функцию memset
ма, приведенная на рис.
'b' в первые 7 байтов
/* Применение memset
#include <stdio.h>
#include
для копирования
stringl.
*/
<string.h>
main ()
{
char
"BBBBBBBBBBBBBB";
=
stringl[15]
%s\n", stringl);
%s\n",
printf("stnngl после memset
memset(stringl, 'b', 7));
return 0;
pnntf("stringl
=
=
}
BBBBBBBBBBBBBB
stringl
=
stringi
после
memset
=
Рис.
8.10.
Двумя
и
последними
из
библиотеки
обработки
функциями библиотеки обработки строк
strlen. Эти функции приведены
на рис.
строк
являются
8.36.
Описание функции
Прототип
int
8.35. Применение memset
Другие функции
strerror
mt
bbbbbbbBBBBBBB
strerror
strlen
(int
(const
errornum)
char
*s)
Устанавливает соответствие номера ошибки errornum
полной текстовой строке
(зависящей
Возвращает указатель
строку
на
Определяет длину строки
s
от
Возвращает
системы)
число символов,
предшествующих ограничивающему символу NULL
Рис. 8.36.
Функции операций
со строками из
библиотеки обработки строк
Функция strerror получает номер ошибки
ошибке. Функция
ная на рис. 8.37, демонстрирует
сообщения об
Программа, приведен¬
и создает строку
возвращает указатель на эту строку.
работу strerror.
Совет по переносимости программ 8.4
Сообщение об ошибке, генерируемое strerror, является системно-зависимым
Функция strlen получает строку в качестве аргумента и возвращает число
(ограничивающий символ NULL в длину строки не вклю¬
чен). Программа, приведенная на рис. 8.38, демонстрирует работу strlen.
символов в строке
Глава 8
400
/* Применение strerror
#include <stdio.h>
#include
main
*/
<string.h>
()
{
strerror(2));
pnntf("%s\n",
return 0;
Error 2
Рис. 8.37. Применение strerror
/* Применение strlen
#include <stdio.h>
#include <string.h>
main
*/
()
{
char
*stringl
char *stnng2
char
*stnng3
=
"abcdefghijklmnopqrstuvwxyz";
=
"four";
=
"Boston";
printf ("%s\"%s\"%s%lu\n%s\"%s\"%s%lu\n%s\"%s\"%s%lu\n",
is ", strlen(stnngl),
"The length of ", stringl,
"
return
"
"The
length
of
",
string2,
"The
length of
",
string3,
is
",
strlen(stnng2),
is
",
strlen(stnng3))
"
0;
The length of
The length of
The length of
"abcdefghijklmnopqrstuvwxyz"
"four" is 4
"Boston" is 6
is 26
Рис. 8.38. Применение strlen
Резюме
Функция islower
регистра (a-z).
определяет, является ли ее аргумент
буквой
нижнего
Функция isupper
регистра (A Z).
определяет, является ли ее аргумент
буквой
верхнего
Функция ^sdigit
определяет, является ли ее аргумент
цифрой (0 9).
определяет, попадает ли ее аргумент в список
Функция isalpha
верхнего (A Z) или
нижнего
(a-z)
Функция isalnum
определяет принадлежность аргумента
него и нижнего регистров или цифрам.
Функция isxdigit определяет,
ной цифрой (A F, a-f, 0 9).
букв
регистров.
буквам
верх¬
является ли аргумент шестнадцатерич¬
Функция toupper преобразует регистр буквы
возвращает букву верхнего регистра.
из
нижнего в
верхний
и
Символы и строки
401
Функция tolower преобразует регистр буквы
возвращает букву ниж!него регистра.
из
в
верхнего
нижний
и
Функция isspace определяет принадлежность аргумента следующему
списку пробельных символов: (' '), ("\f), ('\n')» ( \r )» ('\t'), ('\v')*
Функция iscntrl
определяет принадлежность аргумента следующему
списку управляющих символов: ('\t'), ('YO> ('\f'), (\a'), ('\b'), ("\r"),
('\n).
Функция ispunct
символом,
Функция isprint
волом,
включая
символ
отображаемым
пробелу.
аргумент
цифрам, буквам
или
определяет, является ли аргумент
Функция isgraph
символом,
ли
является
определяет,
не относящимся к
отображаемым
сим¬
пробела.
ли
является
определяет,
исключая символ
отображаемым
аргумент
пробела.
Функция atof преобразует
аргумент
строку, которая начинается с
в значение
ряда цифр, представляющих число с плавающей точкой,
типа
double.
Функция atoi преобразует
строку, которая начинается с
аргумент
в
ряда цифр, представляющих целое число,
Функция atol преобразует
int.
строку, которая начинается с
аргумент
ряда цифр, представляющих длинное целое
long.
Функция strtod преобразует
типа
значение
(long int)
в
значение
типа
ющих число с
последовательность символов, представля¬
плавающей точкой,
в значение типа
получает два аргумента: строку (char *)
double. Эта функция
и указатель на
(char *). Строка
преобра¬
содержит последовательность символов, ^оторые должны быть
зованы, а указатель
(char *)
после
преобразования
ссылается на стро¬
ку-остаток.
Функция strtol преобразует
последовательность символов, представля¬
ющих целое число, в значение типа
гумента: строку
держит
(char *),
Эта функция получает три ар¬
указатель на char
последовательность
символов,
преобразованы. Указатель (char *)
Целое число
преобразование.
строку-остаток.
дится
long.
после
*
и целое число.
которые
Строка
должны
преобразования
со¬
быть
ссылается на
определяет основание, по которому прово¬
Функция strtoul преобразует
последовательность символов, представ¬
ляющих целое число, в значение типа
лучает три аргумента: строку
(char *),
unsigned long. Эта функция
указатель на char
*
Строка содержит последовательность символов, которые
преобразованы. Указатель (char *) после преобразования
ся на строку-остаток. Третий аргумент определяет основание,
рому проводится преобразование.
ло.
быть
по¬
и целое чис¬
должны
ссылает¬
по кото¬
Функция gets читает символы со стандартного входного устройства
(клавиатуры) до тех пор, пока не встретит символ новой строки или ин¬
дикатор конца файла. Аргументом gets является массив типа char. По¬
сле окончания считывания в массив добавляется символ NULL ('\0').
Функция putchar
печатает
свой аргумент
символ.
Глава 8
402
Функция getchar
читает символ со стандартного входного
возвращает целое значение символа.
файла,
getchar
Функция puts
ее
с
устройства
и
Если встречается индикатор конца
возвращает EOF.
получает
последующим
в качестве
символом
аргумента строку
новой строки.
(char *)
и выводит
Функция sprintf использует ту же самую спецификацию преобразова¬
ния, что и printf и выполняет форматированный вывод в массив типа
char.
Функция sscanf
ния,
использует ту же самую спецификацию
что и scanf и читает
Функция strcopy
массива,
строку
копирует
массив символов.
гумент
и
форматированные
преобразова¬
данные из строки.
в первый ар¬
второй аргумент
строку
Программист должен следить за размером
который должен иметь достаточный размер, чтобы
ограничивающий символ NULL.
запомнить
Функция strncopy эквивалентна strcopy за исключением того, что
strncopy задает число символов, которые необходимо скопировать из
строки в массив. Ограничивающий символ NULL будет скопирован, то¬
лько если число копируемых символов превышает длину строки по ме¬
ньшей мере
на
единицу.
Функция strcat выполняет объединение строк (конкатенацию): второго
с
аргумента
строки, включая ограничивающий символ NULL,
массивом
символов,
первым аргументом
содержащим строку. Пер¬
вый символ второго аргумента заменяет последний символ NULL ('\0')
строки первого аргумента. Программист должен убедиться, что массив,
который содержит первую строку, достаточен по размерам для хране¬
ния обеих строк.
Функция strncat выполняет объединение определенного числа симво¬
лов из второй строки с первой строкой. К результирующей строке до¬
бавляется ограничивающий символ NULL.
Функция strcmp сравнивает первый аргумент-строку со вторым
ментом-строкой символ за символом. Функция возвращает 0,
аргу-
если
строки равны, отрицательное значение, если первая строка меньше вто¬
рой,
и
положительное
значение,
если
первая
строка
больше второй.
Функция strncmp эквивалентна функции strcmp за исключением того,
strncmp сравнивает заданное число символов в строках. Если число
символов в одной из строк меньше чем заданное число, strncmp сравни¬
что
вает символы до тех пор, пока не встретит ограничивающий символ
NULL в более короткой строке.
Функция strchr
выполняет поиск первого вхождения данного символа
в строку и возвращает указатель на этот символ.
Если
символ не
най¬
ден, strchr возвращает NULL.
Функция strcspn
определяет длкну начальной части строки в первом
аргументе, которая
щих в
(часть)
строку второго
денного
сегмента.
не содержит ни одного из символов,
аргумента.
входя¬
Эта функция возвращает длину най¬
Символы и строки
403
Функция strpbrk
волов
строки
аргумента в
щает указатель на этот символ.
мента
любого
из сим¬
аргумента и
возвра¬
выполняет поиск первого вхождения
второго
строку первого
Если
ни один из символов второго аргу¬
найден, strpbrk возвращает NULL.
не
Функция strrchr
выполняет поиск последнего вхождения данного сим¬
вола в строку и возвращает указатель на этот символ.
найден,
Если символ
не
strchr возвращает NULL.
то
определяет длину начальной части строки в первом ар¬
гументе, которая (часть) содержит только те символы, которые входят
в строку во втором аргументе. Эта функция возвращает длину найден¬
ного сегмента.
Функция strspn
Функция strstr
аргумента в
первой
в
выполняет
строку
первого
поиск
первого
аргумента.
вхождения
строки
второго
Если вторая строка найдена
строке, функция возвращает указатель на
в
найденную подстроку
аргументе.
первом
вызов функции strtok выполняет разбиение строки
sl на лексемы, разделенные символами, содержащимися в строке s2.
При первом вызове функция получает в качестве аргумента CTpoKy sl,
а в последующих вызовах, чтобы продолжить разбиение той же самой
Последовательный
в качестве первого аргумента предается NULL. При каждом
вызове возвращается указатель на текущую лексему. Если при очеред¬
ном вызове функция находит, что лексем в строке не осталось, то воз¬
строки,
вращается NULL.
Функция memcpy копирует заданное число символов из объекта, на ко¬
торый ссылается ее второй аргумент, в объект, на который ссылается
первый аргумент. Функция может получать указатель на любой тип
объекта.
Функция memcpy получает указатель
void, который,
тель типа
для использования в
как
char. Функция memcpy оперирует
с
типа
указатель
функции, преобразуется
в
байтами объектов
указа¬
как с
символами.
Функция memmove копирует заданное число байтов из объекта, на ко¬
торый ссылается ее второй аргумент, в объект, на который ссылается
первый аргумент. Сначала функция копирует заданное число байтов из
второго аргумента во временный символьный массив, а затем копирует
этот массив в первый аргумент.
Функция memcmp
рого
сравнивает заданное число символов первого
и вто¬
аргументов.
Функция memchr выполняет
результата), представленного
поиск
типом
байта (до первого
unsigned char,
в
положительного
заданном
числе
байтов объекта. Если заданный байт найден, функция возвращает ука¬
затель на
вращает
найденный байт объекта. В противном случае функция
указатель
со
значением
воз¬
NULL.
Функция memset копирует второй аргумент, который обрабатывается
как unsigned char, в заданное число байтов объекта, на который указы¬
вает первый аргумент.
Глава 8
404
Функция strerror
устанавливает соответствие номера ошибки errornum
полной текстовой строке (зависящей от системы). Возвращает указа¬
тель на строку.
Функция strlen
число символов
ся
в
длину
аргумента и возвращает
определяет длину строки
в
строке;
ограничивающий
символ
NULL
не включает¬
строки.
Терминология
ASCII
strncpy
atof
strpbrk
strrchr
atoi
atol
strspn
strstr
ctype.h
EBCDIC
strtod
getchar
strtok
gets
isalnum
strtol
isalpha
iscntrl
tolower
strtoul
toupper
библиотека утилит общего
isdigit
назначения
isgraph
islower
isprint
длина строки
код символа
ispunct
конкатенация строк
isspace
копирование строк
лексема
isupper
литерал
isxdigit
memchr
набор
символов
обработка строк
обработка текстов
объединение строк
memcmp
memcpy
memmove
memset
с
другими
строками
putchar
отображаемый
символ
puts
sprintf
представление
символа
sscanf
пробельные
stdio.h
stdlib.h
разбиение строки на лексемы
разделительные символы
strcat
символьная
strchr
strcmp
сравнение строк
строка
strcpy
строка
strcspn
строковая константа
строковый литерал
управляющий символ
числовым
кодом
strerror
string.h
strlen
символы
константа
поиска
strncat
функции преобразования строк
функции сравнения строк
strncmp
шестнадцатеричные цифры
Распространенные ошибки программирования
8.1.
При определении размера
бы
запомнить
символ
массива символов не выделено место, что¬
NULL, который ограничивает строку.
Символы и строки
405
8.2.
Попытка напечатать «строку», которая не содержит ограничиваю¬
щего символа NULL.
8.3.
это ука¬
Обработка одиночного символа в качестве строки. Строка
затель, который, возможно, представляется данными типа long int.
Символ является небольшим целым числом (значения кодовой таб¬
лицы ASCII находятся в диапазоне 0-255). На многих системах та¬
кая замена вызовет ошибку, поскольку младшие адреса памяти ре¬
зервируются
адресации
так
мы,
8.4.
Передача
ет
8.5.
использования
например,
обработки прерываний операционной
произойдет «несанкционированный доступ».
что
символа в качестве аргумента
для
систе¬
функции, которая опериру¬
строками.
Передача строки
с
8.6.
со
специального
для
программ
в качестве аргумента
функции, которая оперирует
символами.
Отсутствует ограничивающий
функции
strncpy,
символ
NULL
в
первом
аргументе
когда величина третьего аргумента меньше или
равна длине строки во втором аргументе.
8.7.
что strcmp и strncmp возвращают 1 в случае равен¬
ства строк. На самом деле функции strcmp и strncmp возвращают в
этом случае 0 (значение false в С) и, следовательно, при проверке
Предположение,
двух строк на равенство результат должен сравниваться с
8.8.
0.
Функции операций со строками, за исключением функции memmo¬
неопределенный результат, когда копирование происходит
ve, дают
между частями одной
Хороший
8.1.
стиль
Когда
вы
той
и
же
строки.
программирования
объявляете
массив символов для хранения строки,
убеди¬
тесь, что размер его достаточен для хранения самой большой строки,
с которой предполагается работать. С позволяет запоминать строку
любой длины. Если строка
она должна сохраняться,
ва, перепишут данные в
оказалась
то символы,
длиннее
массива,
в
котором
выходящие за размер масси¬
области памяти, следующей сразу
за масси¬
вом.
8.2.
Если
вы
вызываете
функции библиотеки обработки
файл <ctype.h>.
символов, под¬
ключите заголовочный
8.3.
8.4.
8.5.
Если
вы вызываете
ния,
подключите
Если
вы
вызываете
функции
из
вода,
подключите заголовочный
Если
вы
ключите
Советы
8.1.
функции из библиотеки утилит общего
заголовочный файл <stlib.h>.
по
вызываете
стандартной библиотеки ввода/вы¬
файл <stdio.h>.
функции из библиотеки обработки строк, под¬
файл <string.h>.
заголовочный
переносимости программ
Если переменная
ных
назначе¬
констант,
то
типа
char
некоторые
*
инициализируется
компиляторы
могут
строкой
символь¬
поместить
такую
406
Глава 8
строку в неизменяемую область памяти. Если, например, вам
требу¬
строке, то, чтобы
иметь такую возможность на всех системах, строку нужно запом¬
нить в массиве символов.
ется
8.2.
8.3.
изменят*,
Внутренние
какие-то
числовые
для
использующиеся
на
представления
символов,
могут
Тип size_t
является системно-зависимым синонимом
unsigned long, либо
8.4.
коды,
отличаться
литеральной
в
символы
для типа
Содержание сообщения,
различных
компьютерах.
либо для
типа
unsigned int.
является систем-
генерируемого strerror,
но-зависимым.
Упражнения
8.1.
для самоконтроля
Предположим,
что переменная с
x, у, и z яв¬
(содержащая символ),
int, переменные d, e, и f являются пере¬
плавающей точкой, переменная ptr имеет тип char * и
ляются переменными типа
менными с
массивы
sl [100]
и
s2 [100] являются массивами типа char. Напи¬
чтобы выполнить каждое из следующих
шите по одному оператору,
заданий.
символ, хранящийся в переменной
Присвойте результат переменной с.
a) Преобразуйте
него регистра.
с, в
букву
верх¬
b) Определите, является ли значение переменной с цифрой. Исполь¬
зуйте условную операцию, как показано на рис. 8.2, 8.3, и 8.4, что¬
бы при выводе результата
c) Преобразуйте
фразы «is
печатались
строку «1234567»
long
в тип
«is not
а» или
и выведите
а».
полученное
значение.
d) Определите, является ли значение переменной
символом. Используйте условную операцию, чтобы
с
управляющим
при выводе резу¬
льтата печатались фразы «is а» или «is not а».
e) Введите
с
клавиатуры
строку
текста
в
массив
sl
не
используя
функцию scanf.
f) Выведите строку
текста, хранящуюся в массиве
sl,
не используя
printf.
g) Присвойте ptr
в
последнего вхождения символа с
местоположение
sl.
h) Выведите
значение
i) Преобразуйте
ное
переменной
строку «8.63582»
используя
double
printf.
и выведите получен¬
значение.
j) Определите,
является
ную операцию, чтобы
«is а» или «is not а».
k) Введите
с
ли
значение
при
выводе
клавиатуры
1) Присвойте ptr
m)
не
с
в тип
Определите,
символ
местоположение
является
ли
и
с
буквой. Используйте
результата
сохраните
первого
значение
печатались
его
в
переменной
вхождения
переменной
услов¬
фразы
с
s2
в
с.
sl.
печатаемым
условную операцию, чтобы при выводе резу¬
льтата печатались фразы «is а» или «is not а».
символом.
Используйте
Символы и строки
407
три значения типа float в переменные d, e,
n) Введите
f
и
из строки
«1.27 10.3 9.432».
в
о) Скопируйте строку, хранящуюся
местоположение
p) Присвойте ptr
символа из s2.
в
q) Сравните строку
sl
sprintf
s) Используйте
и z в массив sl.
строкой
со
s2,
в
sl.
массив
в
вхождения
первого
местоположение
r) Присвойте ptr
массиве
sl любого
s2. Выведите результат,
в
в
с
вхождения
первого
sl.
для вывода значений целых переменных x, у,
Каждое
быть
значение должно
напечатано с шири¬
ной поля, равной 7.
t) Выполните
в
строки в
10 символов
конкатенацию
s2 со
строкой
sl.
строку «-21»
v) Преобразуйте
sl
в
u) Определите длину строки
в
и
тип
выведите
int
результат,
и выведите
полученное зна¬
чение.
w) Присвойте ptr
в
s2 отделены
8.2. Покажите два
vowel строкой
8.3.
местоположение
s2. Лексемы
в
лексемы
различных метода инициализации массива символов
гласных
«AEIOU».
Что выводится (если вообще выводится) при выполнении каждого из
следующих операторов С? Если оператор содержит ошибку, охарак¬
теризуйте
лены
ее и укажите, как ее исправить.
следующие
char
sl[50]
Предположим,
"jack",
=
s2[50]
=
strcat(strcat(strcpy(s3,
d) pnntf("%u",
strlen(sl)
e) printf("%u",
strlen(s3));
a)
char
в
каждом из
исправить:
"hello",
b) printf("%s",
if
к
8.1.
с
а)
следующих
операторов
s [12];
"Welcome
(strcmp(stringl,
5);
Home");
strmg2))
are egual\n");
strings
упражнениям для самоконтроля
=
toupper(c);
b) printf("'%c*%sdigit\n",
с, isdigit(c) ?
"
"),
s2));
программы
a');
printf("The
Ответы
and
s);
strcpy(s,
d)
"
s[10];
strncpy(s,
char
sl),
strlen(s2));
+
их
printf("%s\n",
c)
*sptr;
s2));
c) printf("%s",
как
s3[50],
"jill",
strcpy(s3,
объясните,
объяв¬
&sl[l]);
toupper(sl[0]),
b) printf("%s",
Найдите ошибку
что
переменные:
a) pnntf("%c%s",
8.4.
первой
(,).
запятыми
is
а
"
"
:
is
not
а
");
и
Глава 8
408
c) pnntf
( "%ld\n"
atol ("1234567") ) ;
,
d) pnntf("'%c'%scontrol character\n",
"
?
iscntrl(c)
с,
is
"
а
"
:
is
not
а
");
is
not
а
");
is
not
а
e) gets(sl);
f) puts(sl);
g)
=
ptr
strrchr(sl,
с);
h) putchar(с);
i) pnntf("%f\n",
atof (
"
8
.
63582" ) ) ;
j) printf("'%c'%sletter\n",
с,
к)
с
1)
ptr
isalpha(c)
?
"
is
"
а
"
:
getchar ();
=
=
s2);
strstr(sl,
m)printf("'%c'%sprinting character\n",
isprint(c)
с,
п)
sscanf ( "1.27
10.3
?
"
"
is
"
а
:
"%f%f%f",'&d,
9.432",
");
&f);
&e,
о) strcpy(sl, s2);
p)
ptr
=
s2);
strpbrk(sl,
q) pnntf("strcmp(sl,
r) ptr
=
"%7d%7d%7d",
t)
strncat(sl,
s2,
w)ptr
=
char vowel [ ]
=
=
strcmp(sl,
s2));
x,
у,
z);
10);
=
strlen(sl));
%u\n",
atoi ( "-21"));
strtok(s2,
char vowel[]
%d\n",
с);
strchr(sl,
sprintf(sl,
v) printf("%d\n",
8.3.
=
s)
u)printf("strlen(sl)
8.2.
s2)
",");
"AEIOU";
{'A\
'E',
'I',
'0',
'U',
'
\0
}
;
а) Jack
b) jiii
c) jack and jill
d) 8
e)
8.4.
13
а) Ошибка: функция strncpy не записывает ограничивающий символ
NULL в массив s, поскольку ее третий аргумент равен длине строки
«hello».
Исправление: сделайте третий аргумент функции strncpy равным 6
или присвойте '\0* элементу массива s [5].
b) Ошибка: попытка вывести символьную константу как строку.
Исправление: используйте формат %c
ните
*a'
для вывода символа или заме¬
на "а".
c) Ошибка:
массив
символов
уместить ограничивающий
s
имеет
символ
недостаточный размер, чтобы
NULL.
409
Символы и строки
Исправление: объявите массив с большим количеством элементов.
d) Ошибка: в случае равенства строк функция strcmp возвращает 0
и, следовательно, функция printf в структуре if не будет выполнена.
сравните с 0 значение, возвра¬
Исправление: в условном операторе
щаемое функцией strcmp.
Упражнения
8.5.
Напишите
программу, которая вводит символы с клавиатуры и про¬
веряет их с помощью каждой из функций библиотеки обработки
символов.
Программа
должна
выводить
значения,
возвращенные
каждой функцией.
8.6.
Напишите программу, которая
лов s
[100],
нижнем
8.7.
вводит строки текста в массив симво¬
используя функцию gets. Выведите строки
и
регистрах.
в целые числа,
суммирует
и вы¬
Напишите программу, которая вводит 4 строки, представляющие
значения с
ния,
8.9.
верхнем
Напишите программу, которая вводит 4 строки, представляющие
целые значения, преобразует строки
водит сумму 4 значений.
8.8.
в
плавающей точкой, преобразует строки, удваивая
суммирует
и
выводит
Напишите программу,
сумму
которая
strcmp
для
Программа
меньшей, равной
дол¬
использует функцию
сравнения двух строк, введенных
значе¬
4 значений.
пользователем.
жна определить, является ли первая строка
или
большей второй строки.
8.10.Напишите программу, которая использует функцию strcnmp для
сравнения двух строк, введенных пользователем. Программа дол¬
вводить число сравниваемых символов. Программа должна
определить, является ли первая строка меньшей, равной или боль¬
жна
шей второй строки.
8.11.Напишите программу, которая использует генератор случайных
чи¬
должна использовать че¬
предложений. Программа
указателей на char с именами article (артикдь), noun
(существительное), verb (глагол) и preposition (предлог). Програм¬
ма должна создавать предложения,
выбирая случайным образом
слова из массивов в следующем порядке: article, noun, verb, prepo¬
sition, article и noun. После выбора очередного слова выполняется
конкатенация с предыдущим словом в массиве, который должен
сел для создания
тыре массива
быть
достаточно
большим, чтобы уместить
полное
предложение.
Слова должны отделяться пробелами. Заключительное предложение
при выводе должно начинаться с заглавной буквы и оканчиваться
точкой. Программа должна образовать 20 таких предложений.
В
массивах
должны
содержаться
следующие
слова:
article должен
(некоторые), и
«апу» (любой); noun должен содержать существительные «Ьоу»
(юноша), «girl» (девушка), «dog» (собака), «town» (город) и «саг»
(автомобиль); verb должен содержать глаголы «drove» (ехал),
содержать артикли «the», «а»,
«опе»
(один),
«some»
410
Глава 8
«jumped» (подпрыгнул), «гап» (побежал), «walked» (шел) и «skip¬
ped» (скакал); preposition должен содержать предлоги «to» (к),
«from» (от), «over» (через) и «оп» (на).
Когда программа будет написана и отлажена, измените ее так, что¬
бы
она создала
короткий рассказ, состоящий
из
нескольких
таких
предложений.
юмористическое стихотворение из пяти
строк, в котором первая и вторая строки рифмуются с пятой, а тре¬
тья рифмуется с четвертой. Используя методику, рассмотренную в
упражнении 8.11, напишите программу, которая производит слу¬
8.12.(Лимерики) Лимерик
чайные стихотворения. Отладка программы до уровня, на котором
она создавала бы хорошие стихотворения, является серьезной и
увлекательной проблемой,
но
результат
будет
стоить
усилий!
8.13.Напишите
программу, которая кодирует английские фразы во фра¬
зы «свинячьей латыни». Свинячья латынь
это форма кодирован¬
языка, который часто используется в развлекательных целях.
Существует много вариаций методов формирования фраз. Для про¬
стоты используйте следующий алгоритм.
Для формирования фразы свинячьей латыни из фразы английского
языка разбейте последнюю на слова (лексемы), используя функцию
ного
strtok. Чтобы перевести каждое английское слово
латыни, поместите первый символ слова в конец
«ау». Таким образом,
свинячьей
в слово
и
добавьте буквы
«jump» становится «umpjay», слово
«hetay» и слово «computer» становится
«omputercay». Пробелы между словами остаются пробелами. Пред¬
«the»
становится
положим
ных
следующее:
или
два
английская
фраза
состоит
из
слов,
отсутствуют любые знаки препинания
пробелами,
имеют
слово
словом
большее
число
разделен¬
и
все
слова
Функция printLatinWord
символов.
Подсказка: каждый раз, когда при
функции strtok найдена очередная лексема, передайте
лексемы функции printLatinWord и выведите получив¬
должна выводить каждое слово.
обращении
указатель
шееся
к
слово.
8.14.Напишите программу, которая вводит телефонный номер
как стро¬
формате (555) 555-5555. Программа должна использовать фун¬
кцию strtok, чтобы извлекать в качестве лексем код региона, пер¬
вые три цифры номера телефона и последние четыре цифры номера
ку в
телефона. Семь цифр номера телефона должны объединяться
строку операцией конкатенации.
строку кода региона
И
в тип
int,
а
Программа
в одну
преобразовать
телефона в тип long.
должна
строку номера
код региона и номер должны быть выведены на печать.
8.15. Напишите программу, которая вводит строку текста,
лексемы с помощью
функции strtok
разбивает ее на
обратном
и выводит лексемы в
порядке.
8.16.Напишите программу, которая считывает с клавиатуры строку тек¬
ста и строку поиска. Вызвав функцию strstr, найдите местоположе¬
ние первого вхождения строки поиска
ее местоположение
поиска
найдена,
в
строку текста и
переменной searchPtr
типа
присвойте
char *. Если строка
выведите остаток строки текста,
начинающийся
со
411
Символы и строки
строки поиска. Затем снова вызовите strstr, чтобы найти следующее
вхождение строки поиска в строку текста. Если второе вхождение
найдено, выведите остаток строки текста, начинающийся со второго
вхождения
должно
поиска.
строки
содержать
в
Подсказка: второе обращение
качестве
первого
аргумента
к
strstr
значение
se-
archPtr+l.
8.17.Напишите программу, основанную
на программе упражнения 8.16,
которая вводит несколько строк текста и строку поиска и использу¬
ет функцию strstr, чтобы определить суммарное число вхождений
строки поиска в текст. Выведите результат.
8.18. Напишите программу, которая вводит несколько строк текста и сим¬
вол поиска и использует функцию strchr, чтобы определить суммар¬
ное число вхождений символа в текст.
8.19.Напишите программу, основанную
на программе упражнения 8.18,
которая вводит несколько строк текста и использует функцию
strchr, чтобы определить суммарное количество вхождений каждой
буквы алфавита в текст. Буквы верхнего и нижнего регистров дол¬
жны суммироваться вместе. Сохраните в массиве общее число вхож¬
дений каждого символа и выведите эти значения в форме таблицы.
8.20. Напишите программу, которая вводит несколько строк текста
пользует функцию strtok, чтобы сосчитать общее количество
Предположим,
пробелами.
что слова разделяются символами
и ис¬
слов.
новой строки
или
8.21. Используйте функции
сравнения строк, рассмотренные в разде¬
ле 8.6, и методы сортировки массивов, развитые в главе 6, чтобы на¬
алфавитной сортировки списка строк. В качестве
используйте названия 10 или 15 городов вашей облас¬
писать программу
базы данных
ти.
8.22. Таблица, приведенная
ставление символов
является
вите,
ли
в
приложении Г, показывает численное пред¬
в коде
каждое
ASCII. Исследуйте эту таблицу
из
следующих
утверждений
и устано¬
верным или
неверным.
a) Символ «А» находится перед символом
b) Цифра 9 находится перед цифрой 0.
c) Все
тания,
«В».
символы, которые обычно используются для сложения, вычи¬
умножения и деления, находятся перед любой из цифр.
d) Цифры находятся перед символами.
e) Если программа сортирует строки в порядке возрастания значе¬
ний, то она поместит символ правой круглой скобки перед символом
левой круглой скобки.
8.23. Напишите программу,
них,
которая вводит ряд строк
которые начинаются с буквы «Ь».
8.24. Напишите
них,
программу, которая вводит ряд строк
заканчиваются на «ED».
которые
и
выводит
те
из
и
выводит
те
из
412
Глава 8
8.25. Напишите программу, которая вводит код ASCII и выводит соответ¬
ствующий
Измените эту программу так, чтобы
символ.
255
и
попыталась вывести
дит при выполнении этой
8.26. Используя таблицу
она генери¬
000 до
соответствующие символы. Что происхо¬
ровала все возможные коды с тремя
цифрами
в диапазоне от
программы?
ASCII приложения Г как руководство,
собственные версии функций обработки символов,
приведенных на рис. 8.1.
напишите
8.27. Напишите
в
числа,
символов
свои
свои
собственные версии функций преобразования строк
которые
приведены
на
рис. 8.5.
8.28. Напишите по две версии функций копирования строки и конкатена¬
ции строк, приведенных на рис. 8.17. Первая версия должна испо¬
льзовать индексацию массива, а вторая версия должна использовать
указатели
и
арифметические операции
8.29. Напишите ваши собственные версии
и
puts,
показанных
на
рис.
над указателями.
функций getchar, gets, putchar
8.12.
8.30. Напишите две версии для каждой
из функций сравнения строк,
приведенных на рис. 8.20. Первая версия должна использовать ин¬
дексацию массива, а вторая версия должна использовать указатели
и
арифметические операции
над указателями.
8.31. Напишите ваши собственные версии
денных
на
8.32. Напишите
функций
поиска строк, приве¬
8.22.
рис.
ваши
собственные версии функций управления блоками
на рис. 8.30.
памяти, приведенных
8.33.Напишите две версии функции strlen, приведенной на рис. 8.36.
Первая версия должна использовать индексацию массива, а вторая
должна
использовать
указатели
и
арифметические операции
над
указателями.
Специальный раздел:
более сложные упражнения
по
Предыдущие упражнения
работе
со
строками
важны для понимания текста главы и раз¬
работаны, чтобы проверить понимание читателем основных принци¬
пов, применяемых при обработке строк. Данный раздел включает рад
и
задач средней
повышенной сложности. Читатель может найти
неко¬
торые из них чересчур сложными, однако их решение должно прине¬
удовлетворение. Задачи существенно различаются
Некоторые требуют всего одного или двух часов для
написания и отладки программы. Другие полезны для лабораторных
занятий; для их решения могут потребоваться две или три недели.
Некоторые задачи могут послужить основой курсового проекта.
сти
по
8.34.
определенное
сложности.
(Анализ текста) Доступность компьютеров с возможностями обра¬
ботки строк сделала реальными довольно интересные идеи относите¬
льно анализа текстов великих авторов. Много внимания было сосре¬
доточено на том, жил ли когда-нибудь Вильям Шекспир. Некоторые
ученые полагают, что имеются достоверные указания на то, что ав¬
Символы и строки
тором
413
шедевров,
приписываемых
Марло. Исследователи
Шекспиру,
является
Кристофер
применяли компьютеры, чтобы найти совпа¬
в текстах эти двух авторов. Данное упражнение исследует три
метода компьютерного анализа текстов.
дения
a) Напишите программу, которая читает несколько строк текста и
выводит таблицу, показывающую число вхождений каждого симво¬
ла алфавита в текст. Например, фраза
То
or
be,
not
содержит один
b)
to
be:
символ
that
«а»,
is
the
«Ь»,
два
Напишите программу, которая
question:
ни
одного
читает
«с»
несколько
и
т.д.
строк
текста
и
показывающую число слов в тексте, содержащих
один символ, два символа, три символа и т.д. Например, фраза
выводит
таблицу,
Whether
'tis
nobler
in
the
mind
to
suffer
содержит
Длина слова
Число слов
1
0
2
2
3
2
4
2 (включая 'tis)
5
0
6
2
7
1
с) Напишите программу, которая читает несколько строк текста и
таблицу, показывающую число вхождений каждого слова в
текст. Первый вариант вашей программы должен включать слова в
таблицу в том же самом порядке, в котором они появляются в тек¬
сте. Более интересной (и полезной) является таблица, в которой сло¬
выводит
ва
сортируются
То
or
be,
Whether
'tis
содержит три
8.35.
в
not
алфавитном порядке. Например, строки
to
be:
nobler
слова
that
in
the
«to», два
is
the
mind
слова
guestion:
to
suffer
«Ье»,
одно слово
«ог»
и т.д.
(Обработка текстов) Детальное
в
этой
в
главе в
последнее
важных
рассмотрение обработки строк
значительной степени обязано захватывающему росту
время
функций
приложений обработки текстов. Одной из
системах обработки слов является функция вы¬
числа
в
и правому полям стра¬
ницы. Она генерирует профессионально выглядящий документ, ко¬
торый производит впечатление, что его скорее напечатали в
выравнивание слов по левому
равнивания
пишущей машинке. Выравнивание
компьютерной системе, если вставить
один или большее количество символов пробела между словами в
строке таким образом, чтобы самое правое слово выравнивалось по
типографии,
может
быть
правому
чем подготовили на
выполнено
полю.
на
414
Глава 8
Напишите программу, которая читает несколько строк текста
чатает этот текст в
должен
быть
Предположим,
бумаге шириной 8 1/2 дюймов,
выровненном формате.
напечатан на
и пе¬
что текст
а с
левой
стороны напечатанной страницы должны оставаться поля
правой
шириной
и
в один
дюйм. Предположим также, что компьютер печата¬
дюйм по горизонтали. Следовательно, ваша про¬
ет 10 символов на
грамма должна выводить текст
ствует
65
символам
8.36. (Печать дат
в
в
шириной 6 1/2 дюймов,
что соответ¬
строке.
различных форматах) Даты
в
деловой корреспон¬
форма¬
денции печатаются, как правило, в нескольких различных
Двумя
тах.
07/21/55
из
и
наиболее распространенных форматов
July 21,
Напишите программу, которая
водит
8.37
во
являются:
1955
читает дату в первом
формате
и вы¬
втором.
(Защита чеков) Компьютеры
часто используются в системах печати
чеков, например, при расчете зарплат и оплате счетов.
Циркулирует
странных историй, относящихся к ошибочной выдаче чеков
еженедельных зарплат на суммы более одного миллиона долларов.
Сверхъестественные суммы напечатаны автоматизированными сис¬
много
темами из-за ошибки человека
и/или
из-за отказа машины.
тировщики, конечно, прилагают все усилия, чтобы встроить
системы
Проек¬
в
свои
средства контроля, предотвращающие ошибочную выдачу
чеков.
Другая серьезная проблема
намеренное дописывание суммы чека
мошенником, который предполагает получить наличные деньги по
подложному чеку. Чтобы предотвратить дописывание суммы, боль¬
шинство автоматизированных денежных систем используют методи¬
ку, которая называется защитой чеков.
разработанные для компьютерного вывода, содержат фикси¬
число пробелов, в которых компьютер может печатать сум¬
Предположим, что платежный чек содержит восемь пустых по¬
Чеки,
рованное
му.
в которых компьютер печатает еженедельную сумму выплаты.
Если сумма большая, то будут заполнены все восемь полей, напри¬
лей,
мер:
1,230.60
(сумма чека)
12345678
(позиции цифр)
С другой стороны, если сумма меньше $1000, то несколько полей
должны были бы остаться незаполненными. Например, запись
99.87
12345678
содержит три пустых поля.
лями,
Если
чек
будет
напечатан с пустыми по¬
то мошенник достаточно просто может дописать сумму чека.
Для предотвращения этого большая часть систем выдачи чеков пе¬
чатает в пустых полях символы звездочек:
Символы и строки
415
***99.87
12345678
Напишите программу, которая вводит долларовую сумму и печатает
ее на чеке, вставляя, при необходимости, символы звездочек.
8.38.
(Словесный эквивалент суммы чека) Продолжая обсуждение преды¬
дущего примера, мы еще раз подчеркнем важность проектирования
систем выдачи чеков, защищенных от подделок посредством допи¬
сывания суммы. В одном из распространенных методов защиты ис¬
пользуется запись суммы в двух представлениях:
в виде обычной
цифровой записи и в виде ее словесного эквивалента. Если
будь и способен изменить числовую часть чека, то изменить
ну суммы, записанную словами, чрезвычайно трудно.
кто-ни¬
величи¬
Многие автоматизированные системы выдачи чека не печатают сло¬
весный эквивалент суммы. Возможно, основная причина этого упу¬
щения состоит в том, что большинство языков высокого уровня, ис¬
в коммерческих прикладных программах, не содержат
адекватных возможностей обработки строк. Другой причиной явля¬
пользуемых
ется то,
зывает
что логика записи словесных эквивалентов сумм чеков
определенные
вы¬
трудности.
Напишите программу
на
С, которая вводит числовую сумму чека и
Например, сумма 112.43 должна
выводит ее словесный эквивалент.
быть записана как
ONE HUNDRED TWELVE and
8.39.
4 3/100
(Код Морзе) Возможно, самой знаменитой схемой кодирования яв¬
код (азбука) Морзе, разработанный Самуэлем Морзе в 1832
ляется
году для телеграфа. Код Морзе присваивает последовательность
чек и тире каждому символу
специальным
символам
алфавита, каждой цифре
(точке, запятой,
то¬
и нескольким
двоеточию и точке с запя¬
той). В
звуковых системах точка представляется в виде короткого
звука, а тире в виде долгого звука. Другие представления точек и
тире используются со световыми системами и в системах с сигналь¬
ными
флажками.
Разделяющий
слова
говоря, отсутствием
символ
точки
обозначается пробелом или, попросту
тире. В звуковых системах пробел
или
обозначается
коротким временным отрезком, в течение которого
звук не передается. Международная версия кода Морзе приведена на
рис. 8.39.
Напишите программу, которая читает фразу на английском языке
и
азбуку Морзе. Также напишите программу,
которая читает фразу, закодированную азбукой Морзе, и преобразу¬
ет ее в английский языковый эквивалент. Используйте один пробел
между символами азбуки Морзе и три пробела между словами, запи¬
кодирует ее, используя
санными в этом коде.
416
Глава 8
Символ
Символ
Код
А
Код
T
.-
В
-
и
С
V
-.-.
D
w
E
X
F
Y
G
.
-.
Z
.
H
I
Цифры
J
1
.
К
2
-.-
L
3
M
4
N
5
-.
О
6
-
P
.
Q
7
.
8
-.-
R
9
.-.
S
0
Рис. 8.39.
Буквы алфавита,
записанные в международном коде
8.40. (Программа преобразования
му, которая
поможет
систем
пользователю
измерений)
с
Морзе
Напишите програм¬
преобразованиями
систем
из¬
мерений. Ваша
программа должна позволять пользователю опреде¬
лять в виде строк название единиц (сантиметров, литров, грамм и
т.д. для метрической системы единиц и дюймов, кварт, фунтов и
т.д. для английской системы
стые
вопросы
"How
many
inches
m
are
в
дюймов
(Сколько
измерений)
и должна отвечать на про¬
типа:
2
2
meters?"
метрах?)
"How many liters are m 10 quarts?"
(Сколько литров в 10 квартах?)
Ваш& программа должна распознавать недопустимые преобразова¬
ния.
Например,
"How many
вопрос
feet
(Сколько футов
не
имеет
грамм»
смысла,
m
в
5
5
kilograms?
килограммах?)
поскольку
единица веса.
«футы»
единицы длины,
а
«кило¬
417
Символы и строки
8.41.
(Письма должникам) Множество предпринимателей тратят время и
деньги, собирая просроченные долги. Обработка должников
про¬
и
цесс создания повторных
пытках
настойчивых запросов должнику
в
по¬
получить долг.
Для автоматического генерирования писем с серьезностью требова¬
ний, зависящей от срока долга, часто используются компьютеры.
Теория состоит в том, что, поскольку долг становится все более ста¬
рым, его возврат становится все более трудным и, следовательно, та¬
кие
письма
должны
иметь
все
более
угрожающий
характер.
Напишите программу С, которая содержит тексты пяти писем нара¬
стающей серьезности. Ваша программа должна вводить:
1. Имя должника
2.
Адрес
должника
3. Счет должника
4.
Сумма
5.
Возраст
долга
долга
Используйте
(один просроченный
возраст долга для
выбора
месяц, два и
т.д.).
одного из пяти текстов сооб¬
щения и выводите на печать письма, вставляя в него
предоставленную
Сложный проект
информацию,
пользователем.
по теме
обработки
строк
8.42. (Генератор кроссвордов) Большинство людей наверняка
сталкива¬
лись с решением кроссвордов, но немногие пытались сами составить
бы один. Производство кроссвордов является сложной задачей.
Она предлагается здесь как проект по теме обработки строк, требую¬
хотя
щий больших усилий. В этом проекте имеется множество частных
задач, которые должен решить программист, чтобы запустить в ра¬
боту
даже
простейшую
как
программу генерирования кроссвордов. На¬
кроссворда внутри компьютера?
строк или двумерные массивы? Про¬
граммисту нужен источник слов (т.е. компьютерный словарь), к ко¬
торому должна обращаться программа. В какой форме нужно хра¬
пример,
Нужно
ли
представить
использовать
сетку
ряд
нить
слова, чтобы упростить сложную обработку, требующуюся
программе? По-настоящему честолюбивый читатель захочет генери¬
ровать и ту часть головоломки, в которой приводятся вопросы крос¬
сворда. Но даже генерация пустого кроссворда является непростой
задачей.
14
Зак 801
Scan AAW
г
л
а
в
а
9
Форматированный
ввод/вывод
Цели
Понять принципы организации входных
и
выходных
потоков.
Научиться
вания
14*
все возможности
форматиро¬
все возможности
форматиро¬
при выводе.
Научиться
вания
использовать
использовать
при вводе.
420
Глава 9
Содержание
9.1.
Введение
9.2. Потоки
9.3.
Форматированный
вывод с применением
printf
9.4. Печать целых чисел
9.5. Печать чисел с плавающей точкой
9.6. Печать строк и символов
9.7.
Другие спецификаторы преобразования
9.8. Печать с заданием ширины поля и точности представления
9.9. Использование флагов в строке управления
форматом printf
Э.Ю.Печать литералов и Esc-последовательностей
9.11.
Форматированный
ввод с применением scanf
Распространенные ошибки программирования
Резюме
Советы по переносимости программ
Ответы на упражнения для самоконтроля
программирования
самоконтроля
9.1.
Хороший стиль
Упражнения для
Упражнения
Введение
Важная составная часть решения любой проблемы
представление резу¬
льтатов. В этой главе мы детально обсудим форматирующие возможности фун¬
кций printf
и
scanf. Эти функции
тный поток вывода
соответственно выводят данные в
и вводят данные из
стандартного
потока
стандар¬
ввода. Четыре
на стандартных потоках ввода/вывода
обсуждались в главе 8. Включайте заголовоч¬
gets, puts, getchar и putchar
ный файл <stdio.h> в программы, которые вызывают эти функции.
Часть возможностей printf и scanf обсуждалась в предыдущих главах. В
других функции, которые действуют
этой главе суммирована вся эта информация, а также представлены многие
другие аспекты форматирования. В главе 11 обсуждаются несколько других
функций,
включенных в стандартную
библиотеку ввода/вывода (stdio).
421
Форматированный ввод/вывод
9.2. Потоки
Весь ввод
и вывод выполняется
стей символов с
посредством потоков
построчной организацией. Каждая
последовательно¬
строка содержит нулевое
или большее число символов
и заканчивается символом новой строки. Стан¬
С объявляет, что реализация ANSI С должна поддерживать строки
меньшей мере из 254 символов, включая ограничивающий символ новой
языка
дарт
по
строки.
При запуске программы к ней автоматически присоединяются три потока.
Стандартный поток ввода обычно присоединяется к клавиатуре, а стандарт¬
ный поток вывода
к устройству вывода информации на экран монитора (эк¬
рану). Операционные системы нередко позволяют переадресовать эти потоки
на другие устройства. Третий поток
также
стандартный поток ошибок
присоединяется к экрану. В него выводятся сообщения об ошибках. В гла¬
ве 11, «Работа с файлами», потоки рассмотрены более подробно.
9.3.
Форматированный
С помощью функции printf
вод
вывод с применением
можно
программы. Каждый вызов
printf
точнейшим образом форматировать
вы¬
printf
содержит строку управления форма¬
вывода. Строка управления форматом со¬
которой описывается формат
спецификаторы преобразования, флаги, ширину полей, точность
представления и литеральные символы. Вместе с символами процента (%)
они образуют спецификации
преобразования. Функция printf может выпол¬
нять следующие виды форматирования, каждый из которых обсуждается в
том,
в
держит
этой
главе:
1.
Округление
чных
значений с плавающей точкой до указанного числа десяти¬
знаков.
2. Выравнивание столбца
це.
чисел по положению
3. Выравнивание выводимых данных
4. Вставка литеральных символов
5. Представление
числа с
по
десятичной
правому краю
в заданное место
плавающей точкой
точки в
и по
столб¬
левому краю.
выводимой строки.
в экспоненциальном
форма¬
те.
6. Представление целого без
знака в восьмеричном и шестнадцатеричном
формате. См. приложение Д, «Системы счисления», где приведена де¬
тальная информация по восьмеричным и шестнадцатеричным числам.
7. Вывод данных
всех типов с
фиксированной шириной
поля и точностью
представления.
Функция printf
printf
имеет следующую
форму:
(строка_управления_форматом, другие_аргументы)
;
В строке_управления_форматом описывается формат вывода,
из
с
других_аргументов (которые
а
каждый
обязательными) сопоставляется
соответствующей спецификацией преобразования, указанной для него в
не являются
422
Глава 9
строке_управления_форматом. Каждая спецификация преобразования начи¬
нается с символа процента и заканчивается спецификатором преобразования.
В одной строке управления форматом может быть указано несколько специфи¬
каций преобразования.
Распространенная ошибка программирования 9.1
Строка_управления_форматом
Хороший
не заключена
в
кавычки
стиль программирования 9.1
данных при выводе Это делает результаты
программы понятнее и уменьшает число ошибок пользователя
Аккуратно форматируйте представление
9.4. Печать целых
Целое
точки.
тов.
это число вида
число
Целые
776
или
-52,
чисел
т.е. не содержащее
десятичной
форма¬
значения выводятся в одном из нескольких возможных
На рис. 9.1
показаны все
Спецификатор
Описание
d
Выводит десятичное целое со знаком.
Выводит десятичное целое
i
целых чисел.
спецификаторы преобразования
со знаком
(Примечание
различие i и d
проявляется в функции scanf)
о
Выводит восьмеричное целое без
u
Выводит десятичное целое без
x
h
или
или
знака
Выводит шестнадцатеричное целое без знака. Спецификатор X
используется для вывода числа цифрами 0-9 и буквами A-F, а
для вывода числа цифрами 0-9 и буквами a-f
X
1
знака.
1)
(буква
Помещается перед
-
целого, чтобы
спецификатором преобразования
показать, что выводится целое соответственно типа short или
Рис. 9.1. Спецификаторы преобразования
x
long
целых значений
Программа, приведенная на рис. 9.2, печатает целое число, последовате¬
льно используя все спецификаторы преобразования целых. Обратите внима¬
ние,
что
знак
выводится
только
Позже
для
отрицательных
этой главе
чисел,
а
вывод
знаков
внимание, что значение -455 печатается с
формат
также печатались. Также обратите
использованием формата %u, и на
компьютере с
преобразуется
«плюс»
подавляется.
преобразования
в
так, чтобы знаки «плюс»
двухбайтовыми
целыми оно
мы
увидим,
как
написать
в значение
без знака,
равное 65081.
Распространенная ошибка программирования 9.2
Печать отрицательного
без знака.
значения
значения со
спецификатором, который предполагает
наличие
423
Форматированный ввод/вывод
/* Использование спецификаторов преобразования целых значений
#include <stdio.h>
*/
main ()
{
printf("%d\n",
455);
455); /* в printf
+455);
-455);
printf("%hd\n", 32000);
printf("%ld\n", 2000000000);
printf("%o\n", 455);
printf("%u\n", 455);
printf("%u\n", -455);
printf("%x\n", 455);
printf("%X\n", 455) ;
return 0;
printf("%i\n",
printf("%d\n",
printf("%d\n",
i
то
же,
что
и
d*/
455
455
455
-455
32000
2000000000
707
455
65081
lc7
lC7
Рис. 9.2. Использование спецификаторов преобразования целых значений
9.5. Печать
Число
с
чисел с
плавающей точкой
плавающей точкой содержит десятичную точку, например, 33.5
плавающей точкой выводятся в одном из нескольких
форматов, спецификаторы преобразования которых приведены на
или 657.983. Числа с
возможных
рис.
9.3.
Спецификаторы преобразования
E выводят
плавающей точ¬
это компью¬
экспоненциальной нотации. Экспоненциальная нотация
в
эквивалент
терный
физике. Например, в
научной нотации, используемой
научной нотации значение 150.4582 представляется как
кой
e и
значение с
в
1.504582xl02
а в
экспоненциальной
нотации компьютера
1.504582E+02
Эта нотация показывает, что 1.504582 умножается на число
ное во вторую степень
(E+02). E
это условное
обозначение
10,
возведен¬
«экспоненты».
Значения, которые выводятся с использованием спецификаторов преобра¬
E, и f, выводятся по умолчанию с точностью 6 разрядов после деся¬
тичной точки; другие значения точности могут быть заданы явным образом.
Спецификатор преобразования f всегда выводит по меньшей мере одну цифру
зования e,
Глава 9
424
слева от десятичной точки.
ответственно символ
Спецификаторы преобразования
e нижнего регистра и символ
которого следует значение экспоненты,
слева от
десятичной
и
всегда печатают
Спецификатор преобразования g (G)
выводит число в
цифру
одну
значение
оно превышает или равно
формате
1.234). В формате
его преобразования
как
ние числа выводится в том случае, если после
нотацию
точно
точки.
без нулей справа (т.е. 1.234000 выводится
циальную
E печатают со¬
e и
E верхнего регистра, после
экспоненты оказывается меньше
заданной
точности представления
(6
e
e
(E) или f
(E) значе¬
в экспонен¬
-4, либо
когда
значащих цифр
по умолчанию для g и G). В других случаях для вывода значения используют¬
ся спецификатор преобразования f. Со спецификаторами g или G нули справа
дробной части выводимого значения не печатаются. Для вывода десятичной
требуется по меньшей мере одна значащая цифра в дробной части. Со
спецификацией преобразования %g значения 0.0000875, 8750000.0, 8.75,
87.50 и 875 печатаются как 8.75e-05, 8.75e+06, 8.75, 87.5 и 875. Значение
0.0000875 использует нотацию e, поскольку после преобразования в экспонен¬
в
точки
циальный формат
значения
значение экспоненты оказывается меньше
8750000.0
ненты равно точности представления,
или
E
f
g
или
по умолчанию.
Назначение
Спецификатор
e
задаваемой
-4. Для вывода
так как значение экспо¬
также используется нотация e,
G
Выводит
значение с
плавающей точкой
Выводит
значение с
плавающей точкой
Выводит
значение с
плавающей точкой либо
e (или E)
экспоненциальном
в
экспоненциальной нотации
в
формате f, либо
в
формате
Помещается перед любым спецификатором преобразования значения
плавающей точкой, чтобы показать, что в данном случае выводится
L
значение типа
с
long double
Рис. 9.3. Спецификаторы преобразования значений
с
плавающей точкой
Точность представления для спецификаторов преобразования g
и
G
пока¬
цифр, включая цифру
слева от десятичной точки. С использованием спецификации %g значение
1234567.0 будет напечатано как 1.23457e+06 (помните, что все спецификато¬
ры преобразования значений с плавающей точкой имеют по умолчанию точ¬
ность представления, равную 6). Обратите внимание, что выведенный резуль¬
зывает максимальное число печатающихся значащих
6 значащих цифр. Когда значение выводится в экспоненциаль¬
ной нотации, разница между g и G аналогична разнице между e и E: специфи¬
тат содержит
катор g выводит
символ нижнего регистра e, а
спецификатор G
выводит сим¬
вол верхнего регистра E.
Хороший
При
стиль программирования 9.2
выводе данных позаботьтесь о том, чтобы пользователь отдавал себе отчет о си¬
в которых форматирование может давать неточные данные (например,
туациях,
ошибки
округления из-за ограниченного значения точности)
425
Форматированный ввод/вывод
ций
Программа, приведенная на рис. 9.4, демонстрирует три вида специфика¬
преобразования значений с плавающей точкой. Обратите внимание, что
спецификации преобразования %E
/*
Печать
чисел
с
плавающей
%g
и
выводят округленные значения.
точкой
с
использованием
значений
спецификаторов преобразования
#include <stdio.h>
с
плавающей
точкой
*/
main ()
printf("%e\n",
pnntf (11 %e\n" ,
pnntf,(11 %e\n"
pnntf ("%E\n",
pnntf ("%f\n",
pnntf ("%g\n",
pnntf ( %G\n"
,
"
,
1234567.89);
+1234567.89)
-1234567.89)
1234567.89)
1234567.89)
1234567.89)
1234567.89)
return 0;
1.234568e+06
1.234568e+06
-1.234568e+06
1.234568E+06
1234567.890000
1.23457e+06
1.23457E+06
Рис. 9.4. Использование спецификаторов преобразования значений
9.6. Печать строк
Для вывода отдельных
символов
аргумент типа char.
с
и
символы
до
тех
NULL (\0). Программа,
со
пор,
и символов
строк
используются
соответственно
s.
ве^аргумента используется указатель на
выводит
плавающей точкой
Для спецификатора преобразования с
Для спецификатора преобразования s в качест-
спецификаторы преобразования
требуется
и
с
пока
не
char. Спецификатор преобразования
встретится
показанная на рис.
спецификаторами преобразования
9.5,
ограничительный
s
символ
выводит символы и строки
с и s.
Распространенная ошибка программирования
9.3
Использование %c для вывода первого символа строки. Спецификация
ния %c предполагает аргумент типа char
Строка
-
это указатель на
преобразова¬
char,
т e
char
*
Распространенная ошибка программирования 9.4
Использование %s для вывода аргумента типа char Спецификация преобразования
%s предполагает передачу в качестве аргумента указателя на char На некоторых сис¬
темах это приведет к фатальной ошибке при выполнении программы, называемой
«несанкционированным доступом»
426
Глава 9
Распространенная ошибка программирования 9.5
Использование одинарных кавычек для обозначения строки символов есть синтакси¬
ческая ошибка.
Строка
символов должна
быть
заключена
в
двойные
кавычки.
Распространенная ошибка программирования 9.6
Использование двойных кавычек для обозначения символьной константы. Такая за¬
фактически создает строку, содержащую два символа, вторым из которых явля¬
ется символ NULL, ограничивающий строку. Символьная константа содержит один
пись
символ
и
должна
заключаться
в
одинарные кавычки.
/* Печать строк и символов
#include <stdio.h>
main ()
{
char character
char string []
char
*stringPtr
*/
'A';
=
"This
=
=
"This
is
is
а
string";
also a string";
printf("%s\n",
character);
"This is a stnng");
printf("%s\n",
string);
printf("%s\n",
return 0;
stringPtr);
pnntf("%c\n",
This
is
a
This
is
a
This
is
string
string
also a string
Рис. 9.5. Использование спецификаторов
9.7.
преобразования строк
Другие спецификаторы преобразования
Тремя оставшимися спецификаторами преобразования
фикаторы p, n и % (рис. 9.6).
Совет
по
и символов
являются
специ¬
переносимости программ 9.1
Спецификатор преобразования p выводит адрес, на который ссылается указатель, в
зависящем от конкретной реализации виде (на многих системах вместо десятичной
нотации используется шестнадцатеричная)
Спецификатор преобразования n сохраняет количество символов, уже вы¬
printf, а соответствующий аргумент является
веденных текущим оператором
указателем на целую переменную, в которую помещается это значение. Специ¬
фикация
ния
преобразования %n
% просто выводит
ничего не печатает.
знак процента.
Спецификатор преобразова¬
427
Форматированный ввод/вывод
Описание
Спецификатор
Выводит
P
значение указателя в
форме, зависящей
от
конкретной реализации
Сохраняет
n
число символов, уже выведенных текущим
оператором
printf. Соответствующий аргумент
указателем на
Выводит
%
Рис. 9.6.
рес x.
волов в
строке
на рис.
9.7, спецификация %p
печатает значе¬
сохраняет число выведенных третьим оператором
целой переменной у,
printf
ратор
символ процента.
адреса x; эти значения идентичны, поскольку ptr присваивается ад¬
ptr
Далее % n
и
является
Ничего не выводит
Другие спецификаторы преобразования
В программе, приведенной
ние
целое значение
после чего печатается значение у.
printf
Последний
сим¬
опе¬
%, чтобы напечатать символ % в
что каждый вызов printf возвращает
использует спецификацию %
Обратите
символов.
внимание,
или число выведенных символов, или отрицательное значение, если при выво¬
де произошла ошибка.
/* Применение спецификаторов
#include <stdio.h>
main ()
{
int
mt
ptr
*ptr;
=
12345,
n,
и
%
*/
у;
x
=
преобразования p,
&x;
printf("The
value
of
printf("The address
characters
printf("Total
%d\n\n",
printf("
ptr
of x
is
is
%p\n", ptr);
%p\n\n", &x);
printed
on
this
line
is:%n",
у);
pnntf("This line has 28 characters\n");
у
printf("%d characters were printed\n\n", y);
=
printf("Printing
return
a
%%
in
a
format
control
string\n");
0;
}
value of ptr is 001F2BB4
The address of x is 001F2BB4
The
Total
This
8
printed
characters
has
line
characters
Printing
a
%
28
on
this
line
is:
41
characters
were
printed
in
format
a
control
string
Рис. 9.7. Применение спецификаторов преобразования p,
n и %
&y);
428
Глава 9
Распространенная ошибка программирования 9.7
Попытка напечатать символ процента как литерал, используя в строке управления
форматом %, а не %% Если в строке управления форматом появляется символ %, то
за ним должен следовать спецификатор преобразования
9.8. Печать с заданием ширины поля и точности
представления
Точный
поля.
поля
котором печатаются данные,
в
размер поля,
Если ширина
больше,
чем
необходимо
шириной
задается
для печати данных, то дан¬
ные обычно выравниваются внутри поля по его правому краю.
Целое
число, за¬
дающее ширину поля, вставляется в
ком процента
на рис.
из
9.8,
них,
(%)
печатает две группы из пяти чисел, выравнивая по правому краю те
которые
Обратите
поля.
и
спецификацию преобразования между зна¬
спецификатором преобразования. Программа, приведенная
содержат
меньшее
цифр, чем задано шириной
значений, превышающих текущее
количество
внимание, что для вывода
значение ширины поля, она автоматически увеличивается, и что знак «минус»
отрицательного
значения
занимает одну
поля может использоваться со всеми
/* Вывод
целых
чисел
с
символьную
позицию
выравниванием
по
правому
краю
#include <stdio.h>
main ()
{
pnntf (1 %4d\n",
printf (1 %4d\n",
1);
12)
;
printf (1 %4d\n", 123) ;
pnntf (1 %4d\n", 1234);
pnntf (' %4d\n\n'\ 12345)
printf(1
pnntf (1
pnntf (1
pnntf ('
printf(1
return
%4d\n",
%4d\n",
%4d\n",
%4d\n",
%4d\n",
-1) ;
-12) ;
-123);
-1234);
-12345);
0»;
}
1
12
123
1234
12345
-1
-12
-123
-1234
-12345
Рис. 9.8.
Выравнивание
поля.
Ширина
спецификаторами преобразования.
целых чисел по правому краю поля
*/
429
Форматированный ввод/вывод
Распространенная ошибка программирования 9.8
Задание недостаточной ширины поля для вывода текущего значения Это может смес¬
тить другие данные, спутав все результаты Следите за своими данными1
с
Функция printf дает также возможность задать точность представления,
которой будут напечатаны данные. Точность имеет различный смысл для раз¬
личных типов данных.
Если
спецификаторами преобразо¬
цифр, которое дол¬
жно быть выведено. Если выводимое значение содержит меньше цифр, чем за¬
дано точностью, то перед ним будут напечатаны префиксные нули, так чтобы
общее количество цифр стало равно заданной точности. Для целых чисел точ¬
она используется со
вания целых чисел, то показывает минимальное количество
ность по умолчанию равна 1. Если точность используется со спецификаторами
преобразования значений
чество
с
цифр, которое будет
плавающей точкой e, E и f, то
напечатано после десятичной
это коли¬
точность
точки.
Для специфи¬
это максимальное количество знача¬
преобразования g и G точность
цифр, которое будет выведено. Для спецификатора преобразования s точ¬
ность
это максимальное число символов строки, которое будет напечатано.
каторов
щих
Чтобы
та и
лым
использовать точность представления, поместите между знаком процен¬
спецификатором преобразования десятичную точку (.) с последующим це¬
числом, задающим точность представления данных. Программа, приведен¬
ная на рис. 9.9, показывает варианты задания точности представления данных
Обратите внимание, что если при печати зна¬
плавающей точкой задана меньшая точность, чем число десятичных
разрядов дробной части исходного значения, то это значение округляется.
Ширина поля и точность представления могут быть объединены, для чего
между знаком процента и спецификатором преобразования нужно вставить
в
строках управления форматом.
чений с
значение ширины поля, десятичную точку и последующее значение точности,
как в следующем операторе:
pnntf("%9.3f",
123.4 567 8 9)
Он выводит 123.457
с тремя
;
цифрами справа от десятичной
шириной 9 цифр.
точки и вы¬
равнивает результат по правому краю поля,
I /*
Использование
чисел
#include
с
точности
плавающей
представления
точкой и строк */
при
печати
целых
чисел,
<stdio.h>
mam ()
{
int
i
=
float f
char s[]
873;
123.94536;
-
=
"Happy Birthday";
printf("Usmg precision for mtegers\n");
printf("\t%.4d\n\t%.9d\n\n", i, i);
pnntf("Using precision for floating-point numbers\n");
pnntf("\t%.3f\n\t%.3e\n\t%.3g\n\n", f, f, f);
printf("Usmg precision for stnngs\n") ;
printf("\t%.lls\n",
return
s);
0;
}
Рис. 9.9. Использование точности представления для вывода
(часть 1
из
2)
информации различного
типа
430
Глава 9
Using precision for integers
0873
000000873
Using precision for floating-point numbers
123.845
1.239e+02
124
Using precision for strings
Happy Birth
Рис. 9.9. Использование точности представления для вывода информации различного типа
(часть 2 из 2)
Ширину
поля
и
точность
представления
можно задать,
используя
цело¬
форма¬
численные выражения в списке аргументов после строки управления
*
том. Чтобы это сделать, вставьте
вместо ширины поля или точно¬
сти
(или
вместо того и
(звездочку)
другого). Звездочки при
ствующими значениями из списка аргументов.
ны поля может
быть
только
быть отрицательным,
положительным.
печати
будут
заменены соответ¬
Значение аргумента для шири¬
но для точности представления должно
Отрицательное
значение ширины поля приво¬
дит к выравниванию вывода по левому краю поля, как описано в следующем
Оператор
pnntfr%*.*f", 7, 2,
разделе.
98.736);
использует значение 7 для ширины поля, 2 для точности представления
водит значение
98.74
и вы¬
с выравниванием по правому краю поля.
9.9. Использование
флагов в строке управления
форматом printf
Функция printf
щих
возможности
предусматривает также использование флагов, дополняю¬
форматирования. Пользователю доступны пять флагов
строки управления форматом
Чтобы
использовать в
(рис. 9.10).
спецификации преобразования флаг,
непосредственно справа от знака процента.
несколько флагов.
Программа, приведенная на
поместите его
В одной спецификации могут быть
объединены
рис.
ки, целого числа, символов и числа с
9.11, демонстрирует выравнивание стро¬
плавающей точкой по правому и по лево¬
му краю поля.
Флаг
-
+
Описание
(знак
"минус")
(знак
"плюс")
Выравнивание вывода по левому краю в пределах заданной
ширины поля
Вывод знака плюс перед положительными значениями и знака
минус перед отрицательными
пробел
Вывод
пробела перед
отсутствии в
положительным значением при
спецификации преобразования флага
+.
431
Форматированный ввод/вывод
Флаг
Описание
#
Печать префикса 0 перед выводимым значением при
использовании спецификатора преобразования восьмеричных
значений о.
префикса 0x или 0X перед выводимым
спецификатора преобразования
шестнадцатеричных значений x или X
Печать
значением при
использовании
При
использовании со
спецификаторами преобразования
e, E,
f, g или G вынуждает печатать десятичную точку для чисел с
плавающей точкой, которые не содержат дробной части.
(Обычно десятичная
точка
выводится только
при
наличии
бы одной цифры справа от нее ) Для спецификаторов g
отменяет подавление правых
0
Дополняет
(нуль)
Рис. 9.10:
поле до
нулей дробной
хотя
и G
части
заданной ширины нулями
слева
Флаги строки управления форматом
Программа рис. 9.12 печатает положительное и отрицательное число при
включении в спецификацию флага преобразования + и без него. Обратите вни¬
мание, что знак
«минус»
выводится в
жается только при указании
/*
Выравнивание
по
флага
правому
обоих случаях,
но знак
«плюс»
отобра¬
+.
и
по
левому
краю
поля
*/
#include <stdio.h>
main()
{
printf("%10s%10d%10c%10f\n\n", "hello", 7, 'a', 1.23);
printf("%-10s%-10d%-10c%-10f\n", "hello", 7, 'a', 1.23);
return 0;
}
7
hello
hello
7
Рис. 9.11.
1.230000
а
а
1.230000
Выравнивание строк
/* Печать чисел с флагом
#include <stdio.h>
+
и
по правому и по левому краю поля
без
него
*/
main()
{
printf("%d\n%d\n", 786, -786);
printf("%+d\n%+d\n", 786, -786);
return 0;
786
-786
+786
-786
Рис. 9.12. Печать
положительных и отрицательных чисел с
флагом
+ и
без
него
Глава 9
432
Программа
рис. 9.13 выводит префиксные
выравнивания положительных
лезно для
с положительным чис¬
и отрицательных чисел
по¬
с одинако¬
цифр.
вым числом
/*
пробелы
спецификацию преобразования флага пробела. Это
лом при включении в
Вывод
не
перед значениями
+ или
*/
пробелов
со
знаком,
которые
-
сопровождаются
#include <stdio.h>
main
()
{
pnntf("%
return 0;
d\n%
d\n",
547,
-547);
}
547
-547
Рис. 9.13. Использование флага
пробела
Программа, приведенная на рис. 9.14, использует флаг #, чтобы вывести
префикс 0 для восьмеричного значения, 0x и 0X для шестнадцатеричных зна¬
чений и использует принудительный вывод десятичной точки для значения,
печатающегося со спецификатором g.
/*
Использование
флага
#
со
спецификаторами
преобразования
о,
x,
X
и
любым
#include
спецификатором
значений
с
плавающей
точкой
*/
<stdio.h>
mam ()
{
int
с
=
float p
1427;
=
1427.0;
pnntf("%#o\n",
с);
pnntf("%#x\n",
с);
printf("%#X\n",
с);
printf("\n%g\n",
pnntf("%#g\n",
return 0;
p);
p);
}
02623
0x593
0X593
1427
1427.00
Рис. 9.14. Применение флага #
0
Программа, приведенная на рис. 9.15, использует комбинацию флагов + и
(нуль) для вывода 452 в поле шириной 9 позиций, со знаком + и заполняю¬
щими нулями.
зуя только
После
флаг 0
этого программа выводит значение
и поле
шириной 9 позиций.
452 еще раз,
исполь¬
433
Форматированный ввод/вывод
/* Вывод с флагом О(нуль)
#include <stdio.h>
main
*/
()
{
pnntf ( "% + 0
9d" ,
pnntf ( "%09d"
return
,
452)
452) ;
;
0;
}
+00000452
000000452
Рис. 9.15. Использование флага 0 (нуль)
9.10. Печать литералов
и
Esc-последовательностей
Большинство литералов, которые выводятся оператором printf, могут
быть просто включены в строку управления форматом. Однако существуют от¬
кавычек ("), которые ограничивают
Различные
управляющие символы типа
саму строку управления форматом.
символа новой строки и символа табуляции должны быть представлены соот¬
дельные
«проблемные»
символы
типа
ветствующей езсаре-последовательностью, или escape-кодом. Esc-поеледоваобратную косую черту (\) с последующим escaВ
на
таблице
рис. 9.16 приведен список всех Esc-поеледовательре-символом.
ностей и действий, которые они вызывают.
тельность представляет собой
Esc-последовательность
Описание
\'
Вывод
символа
\"
Вывод
символа
двойной кавычки(
\?
Вывод
символа
вопросительного знака
\\
Вывод
символа
обратной
\a
Вызывает звуковой сигнал (звонок) или визуальное предупреждение
\b
Перемещает курсор на одну позицию назад в текущей строке
\f
Перемещает курсор на начало следующей логической страницы
\n
Перемещает курсор на начало следующей строки
\r
Перемещает курсор
\t
Перемещает курсор на следующую позицию горизонтальной
одинарной
кавычки
(')
"
на
)
(
?
)
косой черты ( \ )
начало
текущей строки
табуляции
Перемещает курсор на следующую позицию вертикальной
\v
табуляции
Рис. 9.16. Esc-последовательности
Распространенная ошибка программирования
Попытка вывести в операторе
кавычки, вопросительного
вол
обратной
printf
в качестве литеральных данных символ
знака или
косой чертой.
9.9
обратной
двойной
косой черты, не предваряя этот сим¬
434
Глава 9
9.11.
Форматированный
Ввод
с точным контролем
ввод с применением scanf
формата
выполняется
функцией scanf. Каждый
оператор scanf содержит строку управления форматом, которая описывает
формат входных данных. Строка управления форматом состоит из специфика¬
преобразования
ций
щие возможности
1.
Ввод
всех
и литеральных символов.
форматирования
типов
имеет следую¬
данных.
2. Ввод определенных
символов
3. Пропуск определенных
Функция scanf
Функция scanf
входных данных:
имеет
из
входного
из
символов
потока.
входного
потока.
следующий формат:
scanf(cтpoкa_yпpaвлeния_фopмaтoм, другие_аргументы)
;
В строке_управления_форматпом содержится описание входного форма¬
та,
а
другие_аргументы
это указатели на переменные, в которых сохраня¬
ются введенные данные.
Хороший
стиль программирования 9.3
При вводе данных просите пользователя ввести один элемент или небольшое число
элементов данных за один раз. Не предлагайте ему вводить в ответ на один запрос
значительный объем данных
На рис.
при
9.17 приведены
спецификаторы преобразования, используемые
вводе всех типов данных. В оставшейся части этого раздела приведены
программы, которые демонстрируют чтение данных с различными
специфи¬
каторами преобразования функции scanf.
Спецификатор
Описание
Целые
Прочитать десятичное целое число с необязательным знаком
d
-
Соответствующий аргумент
указатель на целое число
Прочитать десятичное, восьмеричное
i
необязательным знаком
на целое число
число
с
Прочитать восьмеричное
о
на
целое число
-
x
или
-
Соответствующий аргумент
указатель
-
указатель
целое число без знака
Соответствующий,
на целое число без знака
Прочитать шестнадцатеричное
X
шестнадцатеричное целое
без знака
Прочитать десятичное
указатель
аргумент
u
число
или
Соответствующий аргумент
число
Соответствующий аргумент
-
указатель на целое число без знака
h
Числа
e,
E,
Помещается перед любым спецификатором преобразования целых,
чтобы показать, что вводится целое соответственно типа short или long
1
или
с
плавающей точкой
f,
g
или
G
Прочитать
значение с
плавающей точкой Соответствующий аргумент
указатель на переменную с плавающей точкой
-
435
Форматированный ввод/вывод
Спецификатор
Описание
1
Помещается перед любым спецификатором преобразования значений с
плавающей точкой, чтобы показать, что вводится значение типа double
или
L
или
Символы
long double.
и строки
с
Прочитать
Соответствующий аргумент
('\0') не добавляется.
символ
Нулевой символ
типа char
строки
Набор
и
указатель на char.
указатель на массив
Массив должен иметь достаточный размер для хранения
ограничивающего нулевого символа (*\0')
Прочитать строку Соответствующий аргумент
s
-
-
сканирования
Сканирование входного потока и ввод символов, входящих в
[ символы]
сохранением
набор,
с
введенных данных в массиве.
Разное
Прочитать адрес, в том же формате, который генерируется при выводе
адреса со спецификатором %p в операторе printf.
P
Сохранить
n
число символов,
оператором scanf
%
Пропустить при
введенных до настоящего момента данным
-
Соответствующий аргумент
вводе знак процента
указатель на целое.
I
I
I
(%).
Рис. 9.17. Спецификаторы преобразования для scanf
Программа, приведенная
на рис.
9.18,
читает целые числа с различными
целых чисел и выводит их в
спецификаторами преобразования
тации. Обратите внимание, что спецификатор %i дает
десятичной
десятичные, восьмеричные и шестнадцатеричные целые числа.
/*
Чтение
#include
целых
чисел
*/
<stdio.h>
main ()
{
int
а,
b,
с,
d,
e,
f,
g;
printf("Enter seven integers: ");
scanf("%d%i%i%i%o%u%x", &a, &b, &c, &d, &e, &f, &g);
printf("The input displayed as decimal integers is:\n");
printf("%d %d %d %d %d %d %d\n", a, b, c, d, e, f, g);
return
0;
}
-70 -70 070 0x70 70 70 70
seven integers:
The input displayed as decimal integers is:
-70 -70 56 112 56 70 112
Enter
Рис. 9.18. Чтение входных данных
со
но¬
возможность вводить
спецификаторами преобразования
целых чисел
436
Глава 9
При вводе чисел с плавающей точкой
спецификаторов преобразования значений
может
быть
использован
любой
из
плавающей точкой: e, E, f, g или
G. Программа на рис. 9.19 читает три числа с плавающей точкой с тремя вида¬
ми спецификаторов преобразования и выводит их со спецификатором преобра¬
зования
f. Обратите внимание,
ждает тот
/*
факт,
Об
точными.
с
что результат исполнения программы подтвер¬
что выведенные значения с
плавающей точкой
не являются
этом свидетельствует второе по счету напечатанное значение.
Чтение
значений
с
точкой
плавающей
*/
<stdio.h>
#include
main ()
{
float
а,
b,
с;
printf("Enter three floating-point numbers: \n");
scanf("%e%f%g", &a, &b, &c);
pnntf("Here are the numbers entered m plain\n")
pnntf ( "floating-point notation : \ n" ) ;
%f
printf("%f
return 0;
%f\n",
a,
b,
;
c);
}
Enter three floating-point numbers:
1.27987 1.27987e+03 3.38476e-06
Here
are
the
numbers
entered
in
plain
floating-point notation
1.279870
1279.869995
0.000003
Рис. 9.19. Чтение входных данных со спецификаторами преобразования значений
с плавающей точкой
Символы
и строки вводятся с использованием
спецификаторов преобразо¬
Программа на рис. 9.20 предлагает пользователю
Программа считывает первый символ строки со специфика¬
вания соответственно с и s.
ввести
строку.
цией % с
и сохраняет его в
символьной переменной x;
затем вводит оставшую¬
спецификацией %s и сохраняет ее в массиве символов у.
Последовательность символов можно ввести, используя набор сканирова¬
ния. Набор сканирования
это список символов, заключенный в квадратные
ся часть строки со
скобки [], следующий сразу
том.
Набор
за знаком процента в строке управления
сканирования просматривает символы во входном потоке,
только те из них, которые содержатся в
наборе
символов.
форма¬
выбирая
Каждый раз при сов¬
набора скани¬
Набор сканирования
падении символа он сохраняется в соответствующем аргументе
рования.
Аргумент
это указатель на массив символов.
прекращает читать символы после того, как встречается символ, не содержа¬
щийся в наборе. Если уже первый символ входного потока не соответствует ни
одному из символов
набора, то в массиве сохраняется только нулевой символ.
Программа на рис. 9.21 использует набор символов сканирования [aeiou] для
отбора гласных из входного потока. Обратите внимание, что считываются то¬
лько первые семь введенных символов. Восьмой символ (h) не содержится в за¬
данном наборе и ввод завершается.
437
Форматированный ввод/вывод
Чтение
/*
символов
и
*/
строк
#include <stdio.h>
main
()
{
char x,
у[9];
");
string:
&x, у);
а
pnntf("Enter
scanf("%c%s",
printf("The input was:\n");
pnntf("the character \"%c\" ", x);
printf("and the string \"%s\"\n", у);
return
0;
Enter
а string:
The input was:
the
Sunday
"S"
character
and
string "unday"
the
Рис. 9.20. Ввод
символов и строк
/* Применение набора сканирования
#include <stdio.h>
main
*/
()
{
char
z[9];
");
pnntf("Enter а stnng:
scanf("%[aeiou]", z);
pnntf("The
return
input
was
\"%s\"\n",
z);
0;
а string:
ooeeooahah
The input was "ooeeooa"
Enter
Рис. 9.21.
Набор сканирования
содержащихся
в
квадратные
можно также
заданном
сканирования. Чтобы
Применение набора сканирования
списке,
использовать для
если
применить
ввода символов,
не
инвертированный набор
^
инвертировать
скобки перед первым
набор
символом
символов,
набора. В
поместите символ
в
этом случае в массиве
будут сохраняться только те символы, которые не входят в набор. Инвертиро¬
ванный набор завершает ввод после появления во входном потоке символа, ко¬
торый имеется в наборе. Программа на рис. 9.22 использует набор [^aeiou] для
поиска во входном потоке символов, не являющихся гласными.
Для
чтения определенного числа символов из входного потока со
кацией преобразования scanf
Программа
двумя
на рис.
9.23
десятичными
входного потока.
специфи¬
может использоваться значение ширины поля.
читает ряд последовательных
разрядами
и
целое
число,
цифр
состоящее
как целое число с
из
остатка
цифр
Глава 9
438
/* Применение инвертированного набора сканирования
#include <stdio.h>
*/
main ()
{
char
z
[ 9];
а
string: ");
scanf("%[^aeiou]", z);
printf("The input was \"%s\"\n",
pnntf("Enter
return
z);
0;
Enter
a string:
String
The input was "Str"
Рис. 9.22.
/* Ввод
данных
#include
Применение инвертированного набора сканирования
с
заданием
ширины
поля
*/
<stdio.h>
main ()
{
int
x,
у;
printf("Enter а six digit integer: ");
scanf("%2d%d", &x, &y);
printf("The integers mput were %d and %d\n",
return
x,
у);
0;
}
a six digit integer:
123456
The integers input were 12 and 3456
Enter
Рис. 9.23.
Ввод данных
с заданием
ширины поля
Часто бывает необходимо пропустить некоторые символы входного пото¬
ка.
Например,
дата могла бы быть введена как
7-9-91
число даты требуется сохранить, но символы тире, которыми они
разделены, могут быть отброшены. Чтобы устранить ненужные символы,
включите их в строку управления форматом scanf (пробельные символы
пропускают все пробельные символы,
пробел, новая строка и табуляция
Каждое
данным). Например, чтобы
используйте оператор
предшествующие значимым
тире при вводе даты,
scanf
("%d-%d-%d",
&month,
&day,
пропустить символы
&year);
Хотя scanf и устраняет тире в предыдущем примере, дата может быть вве¬
дена и в следующем формате;
7/9/91
В
этом
случае
символы. По
предыдущий оператор scanf
этой причине scanf
не
пропустил
бы ненужные
подав¬
имеет возможность установить символ
*
(звездочка). Символ подавления присваивания дает воз¬
присваивания
можность scanf читать любой тип данных и отбрасывать их, не присваивая пе-
ления
439
Форматированный ввод/вывод
ременной. Программа на рис. 9.24 использует символ подавления присваива¬
ния в спецификации преобразования %c для указания того, что символ, появ¬
ляющийся во входном потоке, должен быть прочтен и отброшен. Сохраняются
только значения месяца, дня и года. Для демонстрации корректности ввода
программа печатает введенные значения.
Обратите
внимание, здесь нет таких
переменных в списке аргументов, которые соответствовали бы
спецификаци¬
преобразования с символом подавления присваивания, поскольку для этих
спецификаций преобразования присваивание не выполняется.
ям
/*
Чтение
и
отбрасывание
из
символов
входного
потока
*/
#include <stdio.h>
main
()
{
int monthl,
dayl,
month2,
yearl,
day2,
year2;
а date
in
form mm-dd-yy:
the
");
scanf("%d%*c%d%*c%d", &monthl, &dayl, &yearl);
%d day
%d year
%d\n\n",
printf("month
monthl, dayl, yearl);
printf("Enter а date in the form mm/dd/yy: ");
printf("Enter
=
=
=
scanf("%d%*c%d%*c%d", &month2, &day2, &year2);
%d day
%d year
%d\n",
printf("month
=
=
month2,
return
day2,
=
year2);
0;
Enter
а
date
month
=
11
in
day
Enter
а date
month
=
11
in
day
the
form
18
=
the
form
18
=
Рис. 9.24. Чтение и
11-18-71
mm-dd-yy:
year
=
71
mm/dd/yy: 11/18/71
year
=
71
отбрасывание
символов из входного потока
Резюме
Весь ввод
и вывод выполняется посредством потоков
ностей символов с
последователь¬
построчной организацией. Каждая строка содержит
нулевое или большее число символов и заканчивается символом новой
строки.
Стандартный поток ввода обычно присоединяется
к экрану.
дартный поток вывода
Операционные
ный
входной
Строка
системы
и
Для
потоки
другим
переадресовать
стандарт¬
устройствам.
printf описывает формат выводимых дан¬
спецификаторы преобразования, флаги, ширину по¬
управления форматом
ных и содержит
лей,
позволяют
нередко
выходной
к клавиатуре, а стан¬
точность
представления
и
литеральные
символы.
печати целых значений используются следующие
преобразования: d
сьмеричных
или
целых
i для целых
без знака,
u
с
для
спецификаторы
необязательным знаком, о для во¬
десятичных целых без знака и x
или X для шестнадцатеричных целых
без
знака.
Модификаторы h
или
Глава 9
440
префикса с вышеперечисленными специ¬
фикатором преобразования, указывают на то, что целое значение имеет
1, использующиеся
соответственно
в качестве
short
тип
long.
или
Для печати значений с плавающей точкой используются следующие
спецификаторы преобразования: e или E для экспоненциальной нота¬
ции, f для обычной нотации чисел с плавающей точкой
рые выводят данные либо в нотации e (или E), либо в
указан спецификатор преобразования g (или G),
-4, либо больше
значение экспоненты меньше
ставления,
g
или
G,
кото¬
f. Если
то в тех случаях, когда
или равно точности пред¬
данное значение, то при выводе испо¬
которой выводится
(или E).
с
и
нотации
льзуется нотация e
Для спецификаторов преобразования g
максимальное
дает
число
выводимых
Спецификатор преобразования
Спецификатор преобразования
ниченных
нулевым
указатель,
системах
этого
точность представления за¬
используется
цифр.
для
печати
символов.
используется для печати строк,
s
огра¬
p выводит адрес, на который ссылается
формате, зависящем
для
G
символом.
Спецификатор преобразования
в
с
и
значащих
используется
от
конкретной
(на многих
нотация).
реализации
шестнадцатеричная
Спецификатор преобразования n сохраняет число символов, уже
оператором printf. Соответствующий аргумент
выве¬
явля¬
денных текущим
ется указателем на целое число.
Спецификатор преобразования %%
Если
больше выводимого объекта,
значение ширины поля
выравнивается
Ширина
в
поле
по
его
(%).
выводит символ процента
правому
то он
обычно
краю.
поля может использоваться со всеми
спецификаторами преоб¬
разования.
Точность представления, используемая
со
спецификаторами преобразо¬
цифр, которое дол¬
вания целых чисел, показывает минимальное число
жно быть выведено. Если выводимое значение содержит меньшее коли¬
чество
общее
цифр,
то
перед
количество
ним
цифр
префиксные нули,
заданной точности.
печатаются
стало
равно
так
чтобы
Если
точность представления используется со спецификаторами преоб¬
это
разования значений с плавающей точкой e, E и f, то точность
число цифр, которые будут напечатаны после десятичной точки.
Для спецификаторов преобразования g
выводимых
значащих
Для спецификатора преобразования
которое будет выведено.
Ширина
чего
поля
между
и
точность
знаком
G
точность
s точность
представления
процента
вить значение ширины поля,
ние
и
это количество
цифр.
и
могут
это число символов,
быть объединены, для
спецификатором преобразования
вста¬
десятичную точку и последующее значе¬
точности.
Ширину
поля и точность представления можно задать, используя цело¬
численные
выражения
в
списке
аргументов
после
строки
управления
441
Форматированный ввод/вывод
*
форматом. Чтобы это сделать, вставьте
поля или точности
будут
(или
вместо того и
заменены соответствующими значениями из списка аргументов.
Значение аргумента для ширины
Флаг
-
(минус)
Флаг +
«-»
быть
быть отрицательным,
но
только положительным.
выравнивает выводимое значение по левому краю поля.
«+»
печатает знак
перед
поля может
оно должно
точност^ представления
для
(звездочку) вместо ширины
другого). Звездочки при печати
перед положительными
и знак
значениями
отрицательными.
Флаг пробела
печатает пробел перед положительным значением, если
его
для
вывода не используется флаг +.
Флаг #
0x
печатает
префикс 0 перед восьмеричными
0X перед шестнадцатеричными
или
значениями,
значениями.
При
префикс
использова¬
спецификаторами преобразования e, E, f, g или G выводит деся¬
тичную точку со всеми числами с плавающей точкой (обычно десяти¬
нии со
чная точка выводится только при наличии у числа значимой
дробной
части).
Флаг 0 выводит перед
числом нули,
если оно не занимает целиком все
поле.
Ввод данных с точным
функцией scanf.
Целые
контролем формата выполняется библиотечной
значения вводятся со
спецификатором преобразования d
и спецификаторами о, u, x и X
целых с необязательным знаком
лых без знака.
Для
ввода целых типа short и
long
и
i для
для це¬
используются моди¬
фикаторы h и 1 соответственно, которые помещаются перед специфика¬
преобразования
тором
целых значений.
плавающей точкой вводятся со спецификаторами преобра¬
E, f, g или G. Перед любым спецификатором преобразования
значений с плавающей точкой можно поместить модификатор 1 или L
Значения
с
зования e,
для указания того,
double
что
вводимое
значение
имеет
тип
double
или
long
соответственно.
Для
ввода символов используется
Для
ввода строк используется
спецификатор преобразования
спецификатор преобразования
с.
s.
сканирования просматривает символы во входном потоке, выби¬
рая только те из них, которые содержатся в наборе. Каждый раз при
совпадении символа он сохраняется в массиве символов. Набор скани¬
Набор
рования прекращает читать символы после того,
не
вол,
содержащийся
в
как встречается сим¬
наборе.
Чтобы создать инвертированный набор сканирования,
поместите
сим¬
^
квадратные скобки перед первым символом набора. В этом слу¬
чае в массиве будут сохраняться только те символы, которые не входят
в набор. Инвертированный набор сканирования завершает ввод после
вол
в
появления во входном потоке символа,
Значения адреса вводятся
со
вает
на
тип
int.
имеется
в
наборе.
спецификатором преобразования р.
Спецификатор преобразования
денных текущим оператором
который
n
сохраняет
число
символов,
уже
вве¬
scanf. Соответствующий аргумент указы¬
Глава 9
442
Спецификатор преобразования %%
Для
и
чтения
отбрасывания
поля используется с
единичный
символ
%.
данных из входного потока используется
символ подавления присваивания
Ширина
вводит
(*).
функцией scanf
для чтения определенно¬
го числа символов из входного потока.
Терминология
*
*
в
качестве
точности
в
качестве
ширины
спецификатор преобразования с
спецификатор преобразования d
спецификатор преобразования
поля
escape-код
escape-код
\
escape-код
\?
escape-код \\
E
спецификатор преобразования h
спецификатор преобразования i
escape-код \a
escape-код
или
e
спецификатор преобразования f
спецификатор преобразования
g или G
escape-код \«
\b
\f
escape-код \n
спецификатор преобразования L
спецификатор преобразования 1
escape-код \r
спецификатор преобразования
escape-код
escape-код
\t
спецификатор
спецификатор
спецификатор
спецификатор
спецификатор
escape-код \v
еэсаре-последовательность
printf
scanf
<sdtio.h>
восьмеричный формат
вставка отображаемых символов
вставка пробелов
выравнивание
выравнивание по левому краю
выравнивание
по
правому
x (или X)
спецификаторы преобразования
спецификаторы преобразования
целых
преобразования
строка управления форматом
спецификация
точность
краю
инвертированный набор
сканирования
литеральные символы
набор сканирования
научная нотация
округление
переадресация потока
флаг
(минус)
флаг
флаг-пробел
флаг #
поток
формат целого со знаком
целое типа long
-
флаг +
(плюс)
флаг 0 (нуль)
формат целого
поток
стандартного
ввода
поток
стандартного
вывода
без знака
поток
целое типа short
число с плавающей точкой
символ
шестнадцатеричный формат
ширина поля
стандартной ошибки
пробельные символы
символ
n
преобразования о
преобразования p
преобразования s
преобразования u
преобразования
экспоненциальный формат
с плавающей точкой
подавления
(*)
спецификатор преобразования %
присваивания
Распространенные ошибки программирования
9.1.
Строка управления форматом
9.2.
Печать отрицательного
полагает
наличие
не
заключена
значения со
значения
без
в
кавычки.
спецификатором, который пред¬
знака.
443
Форматированный ввод/вывод
9.3.
Использование %c для вывода первого символа строки. Специфика¬
ция преобразования % с предполагает аргумент типа char. Строка
это
9.4.
на
указатель
char,
т.е.
char *.
Использование %s для вывода аргумента типа char. Спецификация
преобразования %s предполагает передачу в качестве аргумента
указателя на char. На некоторых системах это приведет к фатальной
ошибке
при выполнении программы, называемой «несанкциониро¬
ванным доступом».
9.5.
одинарных кавычек, заключающих строки симво¬
лов, есть синтаксическая ошибка. Строка символов должна быть за¬
Использование
в
ключена
9.6.
двойные
кавычки.
Использование двойных кавычек для обозначения символьной кон¬
станты. Такая запись фактически создает строку, содержащую два
символа, вторым из которых является символ
щий строку. Символьная
заключаться
9.7.
Попытка
в
одинарные
напечатать
кавычки.
символ
как
процента
литерал,
форматом %,
форматом появляется символ %,
спецификатор преобразования.
% %. Если
Задание недостаточной ширины
для
а
строке управления
не
ния
9.8.
NULL, ограничиваю¬
константа содержит один символ и должна
ния.
Это
дите
за
поля
то
за
ним
в
используя
строке
должен
вывода
управле¬
следовать
текущего
значе¬
может сместить другие данные, спутав все результаты.
своими
в
Сле¬
данными!
9.9. Попытка вывести в операторе printf в качестве литеральных данных
символ двойной кавычки, вопросительного знака или обратной ко¬
сой черты, не предваряя этот символ обратной косой чертой.
Хороший
9.1.
стиль
программирования
Аккуратно форматируйте представление данных при выводе. Это де¬
лает
результаты
программы
понятнее
и
уменьшает
число
ошибок
пользователя.
9.2.
При выводе данных позаботьтесь о том, чтобы пользователь отдавал
себе отчет о ситуациях, в которых форматирование может давать не¬
(например, ошибки
точности).
точные данные
значения
округления из-за ограниченного
9.3. При
вводе данных просите пользователя ввести один элемент или
небольшое число элементов данных за один раз. Не предлагайте ему
вводить в ответ на один запрос значительный объем данных.
Советы по переносимости программ
9.1.
Спецификатор преобразования
ется указатель, способом,
(на многих системах
шестнадцатеричная).
виде
в
p выводит адрес, на который ссыла¬
от конкретной реализации
десятичной нотации используется
зависящем
вместо
Глава 9
444
Упражнения
9.1.
для самоконтроля
Заполните пропуски в следующих утверждениях:
a) Весь
ввод и вывод организован в виде
b)
c
обычно подключен
поток
)
.
к клавиатуре.
обычно подключен
поток
к
экрану
компьютера.
d) Форматированный
e) Строка
вывод выполняется
управления
может
форматом
для
вывода
десятичного
и
целого
со
испо¬
знаком.
g) Спецификаторы преобразования
десятичном
и
шестнадцатеричном
h) Модификаторы
типа short или
без
знака в восьмерич¬
форматах
соответственно.
помещаются перед спе¬
и
цификаторами преобразования
и
,
используются для вывода целых
ном,
,
.
f) Спецификаторы преобразования
льзуются
.
содержать
и
,
,
функцией
целых для вывода целых значений
long.
используется для вы¬
в
значений
с
точкой
экспоненциальной
нотации.
вода
плавающей
i) Спецификатор преобразования
помещается перед любым спецификато¬
j) Модификатор
ром преобразования значений с плавающей точкой для вывода зна¬
чений типа long double.
k) Спецификаторы преобразования
равной
ностью,
e, E и f выводят значение с точ¬
цифрам справа
от
десятичной
точки,
если точность представления не указана.
1) Спецификаторы преобразования
и
испо¬
льзуются для вывода соответственно строк и символов.
m) Все
n) Ширина
поля и точность представления в
функции printf могут
зования
нием
символом.
строки заканчиваются
при
спецификации преобра¬
задаваться целочисленным выраже¬
в
подстановке
качестве
значения
ширины
поля или значения точности и при включении этого целочисленного
выражения в список аргументов
о) Флаг
функции.
выравнивает
выводимое
значение
по
левому
краю поля.
p) Флаг
знаком
q) Ввод
r) Для
сиве
выводит значение вместе со знаком «плюс» или
«минус».
с контролем
формата
выполняется
.
чтения заданных символов из потока и сохранения их в мас¬
символов
используется
s) Спецификатор преобразования
для ввода восьмеричных,
с
функцией
необязательным
.
может использоваться
десятичных и шестнадцатеричных целых
знаком.
445
Форматированный ввод/вывод
t) Спецификатор преобразования
для
u)
и
ввода
значений
используется для чтения данных их
отбрасывания их без присваивания переменной,
v) Чтобы показать,
что
Найдите ошибку
но
из
входного
символов или
определенное число
вания scanf может
9.2.
потока
цифр,
в
входного потока
должно
быть введено
спецификации преобразо¬
использоваться
.
объясните,
в следующих операторах и
как ее мож¬
исправить.
оператор должен напечатать символ V
a) Следующий
'c');
pnntf("%s\n",
оператор должен напечатать 9.375%.
b) Следующий
9.375);
printf("%.3f%",
c) Следующий оператор должен
«Monday»
pnntf("%c\n", "Monday");
d) printf(""A string
e) printf(%d%d, 12,
f) printf("%c",
in
напечатать
первый
символ
строки
quotes"");
20);
"x");
g) printf("%s\n",
9.3.
может использоваться
double.
типа
'Richard');
Напишите операторы для
a) Напечатать 1234
с
выполнения
следующих
действий:
выравниванием по правому краю поля шири¬
ной 10 позиций.
b) Напечатать 123.456789 в экспоненциальной нотации со знаком
или -) и с точностью 3 разряда.
c) Ввести
в
переменную
d) Напечатать 100
e) Ввести строку
f) Ввести
в
в
number
значение
восьмеричном
массив
типа
формате
символов
с
(+
double.
префиксом 0.
string.
символы в массив n до первого встретившегося символа, не
относящегося
цифрам.
к
g) Используйте
целые переменные x и у для задания ширины поля и
точности представления при выводе значения 87.4573 типа double.
h) Введите значение в виде 3.5%. Сохраните число в переменной
percent типа float, отбросив при вводе символ % из входного пото¬
ка. Не используйте символ подавления присваивания.
i) Напечатайте 3. 333333 как значение типа long double со знаком
(+ или -) в поле шириной 20 символов и с точностью 3.
Ответы на упражнения для самоконтроля
9.1.
а) потоков, b) Стандартный, ввода, с) Стандартный, вывода, d)
printf. e) спецификаторы преобразования, флаги, ширину полей,
точность представления, литеральные символы, f) d, i. g) о, u, x
(или X). h) h, 1. i) e (или E). j) L. k) 6. 1) s, с. m) нулевым (\0). n) звез-
Глава 9
446
(*). о)
(минус), p) + (плюс), q) scanf. r) набор сканирования,
i.
lf,
le,
lE,
s)
t)
lg или lG. u) Символ подавления присваивания (*).
дочки
поля.
v) ширина
9.2.
а) Ошибка: спецификатор преобразования
аргумента типа указатель на char.
s
предполагает
передачу
символа с используйте спецификатор пре¬
образования %c или замените V на «с».
b) Ошибка: попытка вывести литеральный символ % не используя
Исправление: для вывода
спецификатор преобразования % %.
Исправление: используйте %% для
вывода литерала %.
c) Ошибка: спецификатор преобразования
аргумента
типа
с
предполагает
передачу
char.
Исправление: чтобы
первый символ «Monday», используйте
спецификацию преобразования %ls.
d) Ошибка: попытка вывести литеральный символ «не используя
еэс-последовательности \».
вывести
Исправление: замените обе внутренние кавычки на \».
e) Ошибка: строка управления форматом не заключена
в
двойные
кавычки.
Исправление: заключите %d%d в двойные кавычки.
f) Ошибка: символ x заключен в двойные кавычки.
Исправление:
символьные
цией преобразования %c,
константы,
выводящиеся
со
специфика¬
должны заключаться в одинарные кавыч¬
ки.
g) Ошибка:
выводимая строка заключена в одинарные кавычки.
Исправление:
сто
9.3.
для вывода строки
используйте двойные
кавычки вме¬
одинарных.
1234);
а) printf("%10d\n",
b) printf ("% +.3e\n",
c) scanf("%lf,
123.456789);
&number);
d) printf("%#o\n",
100);
e)
scanf("%s",
f)
scanf("%[^012345678 9]",
string);
g) printf ("%*.*f\n,
x,
h)
scanf("%f%%",
i)
printf("%+20.3Lf\n",
у,
n);
87.4573);
&percent);
3.333333);
Упражнения
9.4.
Выполните следующие задания,
a) Напечатайте целое
левому
значение
написав оператор
без
знака
40000,
краю поля шириной 15 позиций
b) Введите
шестнадцатеричное значение
и
в
printf
или
scanf:
с выравниванием по
содержащее
переменную
8 цифр.
hex.
447
Форматированный ввод/вывод
c) Напечатайте
d) Напечатайте
значение
200
со
значение
100
в
знаком
и
без
знака.
шестнадцатеричном
формате
с
пре¬
фиксом 0x.
e) Введите
символы в массив s пока во входном потоке не встретится
буква р.
f) Напечатайте
значение
1.234
в поле из
9 цифр
с заполняющими ну¬
лями.
g) Введите время в формате hh:mm:ss, запоминая численные значе¬
ния в целых переменных hour, minute и second и отбрасывая двое¬
точия
(:)
из
Используйте
входного потока.
символ подавления при¬
сваивания.
h) Введите строку «characters» из стандартного входного устройст¬
ва. Сохраните строку в массиве символов s. Отбросьте кавычки из
входного
потока.
i) Введите
время в формате hh:mm:ss, запоминая численные значе¬
ния в целых переменных hour, minute и second и отбрасывая двое¬
точия (:) из входного потока. Не используйте символ подавления
присваивания.
9.5.
Покажите,
имеется
что
печатают
следующие
"This
b) printf("%c\n",
is
а
c) printf("%*.*lf\n", 8, 3,
e) printf("%
ld\n%+ld\n",
17,
g) printf("%10.2g\n",
444.93738);
h) printf("%d\n",
Найдите ошибки
можно
17,
1000000,
444.93738);
их
1008.83689);
1000000);
10.987);
в
следующих
фрагментах программ. Объясните,
исправить.
a) printf("%s\n",
'Happy Birthday');
b) printf("%c\n",
'Hello');
c) printf("%c\n",
"This
is
а
string");
d) Следующий оператор должен напечатать
printf(""%s"", "Bon Voyage");
e)
char
операторе
string");
printf("%10.2E\n",
как
в
1024.987654);
d) pnntf("%#o\n%#X\n%#e\n",
9.6.
Если
10000);
a) printf("%-10d\n",
f)
операторы.
ошибка, укажите, почему.
«Воп
Voyage»
"Sunday";
printf("%s\n", day[3]);
day[]
=
f) printf('Enter your
name:
');
g) pnntf(%f, 123.456);
h) Следующий
оператор должен напечатать символы '0' и 'K'.
printf("%s%s\n",
i)
char
'0',
s [10] ;
scanf("%c",
s[7]);
'K>');
Глава 9
448
9.7.
Напишите программу, которая заполняет массив number из 10-ти
элементов случайными целыми числами в диапазоне от 1 до 1000.
Программа должна вывести каждое значение и текущее общее коли¬
Для этого используйте спецификацию
преобразования %n, чтобы определить число выводимых символов
для каждого текущего случайного значения. Каждый раз при печа¬
ти текущего случайного значения печатайте общее число символов
для всех выведенных значений, включая текущее. Вывод должен
иметь следующий формат:
чество выведенных символов.
Value
Total
342
3
1000
7
963
10
6
и
9.8.
characters
11
т
.
д
.
Напишите программу для проверки различия между спецификато¬
рами преобразования %d и %i при использовании их в операторе
scanf. Для ввода и вывода значений используйте операторы
scanf("%i%d", &x,
printf("%d %d\n",
Проверьте программу
9.9.
10
10
-10
-10
010
010
0x10
0x10
&y);
у);
x,
со
следующим
набором
входных данных:
Напишите программу, которая выводит значение указателя, исполь¬
спецификаторы преобразования целых, а также специфика¬
тор %p. Какой из них дает странные результаты? Какой из них вы¬
зывает ошибку? В каком формате выводит значение адреса на вашей
системе спецификатор преобразования %p?
зуя все
9.10. Напишите программу
для проверки результатов вывода целого зна¬
чения 12345 и значения с
плавающей точкой 1.2345
в полях
разной
ширины. Что происходит, когда значения выводятся в поле, содер¬
жащее меньше цифр, чем выводимые значения?
программу, которая печатает значение 100.453627,
округленное до ближайшего целого, до десятых, сотых, тысячных и
9.11.Напишите
до десятитысячных.
9.12.Напишите программу, которая вводит строку с клавиатуры и опре¬
деляет ее длину. Напечатайте строку в поле с шириной, равной удво¬
енной длине строки.
9.13.Напишите программу, которая
преобразует целые значения темпе¬
Фаренгейта в диапазоне от 0 до 212 градусов в зна¬
температуры с плавающей точкой в шкале Цельсия с точно¬
ратуры в шкале
чения
стью
3 цифры. Для вычислений используйте формулу
celcius
Результат
=
5.0
/
9.0
*
(fahrenheit
должен быть напечатан в два
лов с выравниванием по правому краю.
ры в
шкале
Цельсия
-
32);
столбца шириной 10 симво¬
Перед значением температу¬
должен выводиться знак как для отрицатель¬
ных температур, так и для положительных.
449
Форматированный ввод/вывод
9.14.Напишите программу для проверки всех еэс-последовательностей,
приведенных на рис. 9.16. Для тех из них, которые перемещают
курсор,
напечатайте
любой
еэс-последовательности,
символ
как
так
до,
и
после
вывода
чтобы было видно, куда переместился кур¬
сор.
9.15.Напишите программу, которая выясняет, можно ли вывести в опе¬
раторе printf символ ? как литеральный символ, являющийся ча¬
стью строки управления
следовательность
9.16.Напишите
форматом,
или нужно использовать esc-no-
\?.
программу, которая вводит значение 437, используя все
доступные спецификаторы
Напечатайте
преобразования
спецификаторы преобразования
спецификаторы преобра¬
g для ввода значения 1.2345. Напечатайте все введен¬
значения, чтобы удостовериться, что любой из этих специфика¬
зования e,
ные
функции scanf.
целых.
9.17.Напишите программу, которая использует
f
целых
каждое введенное значение, используя все доступные
и
торов преобразования
самого
может
быть
использован
9.18. В некоторых
ввода
языках программирования при вводе строк
использовать одинарные или
му, которая
двойные
читает три строки: suzy,
кавычки.
«suzy» и
ли те и другие кавычки при вводе в языке
часть
для
того
же
значения.
С
необходимо
Напишите програм¬
'suzy\ Игнорируются
или они читаются как
строки?
9.19. Напишите программу, которая выясняет, можно ли со спецификато¬
ром преобразования %c в строке управления форматом оператора
'?', или нуж¬
printf вывести символ ? как символьную константу
но использовать символьную константу еэс-последовательности
'\?\
9.20. Напишите программу, которая использует спецификатор преобразо¬
вания g для вывода значения 9876.12345. Напечатайте значение с
точностью, изменяющейся от 1 до 9.
15
Зак 801
г
л
а
в
а
10
Структуры, объединения,
операции с битами
и перечисления
Цели
Научиться создавать и использовать структуры, объе¬
динения и перечисления.
Изучить передачу структур
по
Научиться работе
операций.
Научиться
функции
по значению
и
с данными с помощью
поразрядных
поля для
компактного
создавать
хранения данных.
15*
в
ссылке.
битовые
Глава 10
452
Содержание
10.1.
Введение
10.2.
Описания структур
10.3.
Инициализация структур
10.4.
Доступ
к элементам
структур
10.5. Использование структур
с
функциями
10.6.
Typedef
10.7.
Пример: моделирование высокоэффективной
10.8.
Объединения
10.9.
Поразрядные операции
тасовки и раздачи карт
10.10. Битовые поля
10.11.
Перечислимые
Резюме
константы
Распространенные ошибки программирования
Советы
программирования
шению
Общие методические
эффективности
самоконтроля
Ответы
по переносимости программ
на упражнения
10.1.
Структуры
стиль
Упражнения для
Упражнения
Введение
наборы (иногда
это
связанных переменных,
замечания .
для самоконтроля
Хороший
Советы по повы¬
объединенных
их
называют
агрегатами)
логически
под одним именем. В отличие от мас¬
сивов, которые могут содержать элементы только одного типа, структуры мо¬
гут состоять из переменных различных типов данных.
льзуются,
(см.
Структуры
часто испо¬
чтобы определить записи, которые должны сохраняться
главу 11, «Работа с
зой для создания более
файлами»). Указатели
в
файлах
и структуры могут служить
ба¬
сложных структур данных, таких как связанные спис¬
ки, очереди, стеки и деревья
(см.
главу 12,
«Структуры данных»).
Структуры, объединения, операции
с
битами
и
453
перечисления
10.2. Описания структур
Структуры
других
типов.
struct
это
производные
типы
данных,
они создаются из
объектов
следующее описание структуры:
Рассмотрим
card
{
*face;
*suit;
char
char
};
Ключевое слово struct определяет структуру.
Идентификатор card
являет¬
Имя-этикетка именует структуру, и испо¬
словом struct для объявления переменных
ся именем-этикеткой структуры.
льзуется
совместно
с
ключевым
struct card. Перемен¬
структуры. В данном примере тип структуры
ные, объявленные внутри скобок структуры, являются элементами структу¬
ры. Элементы одной структуры должны иметь уникальные имена, но две раз¬
типа
личных структуры могут содержать одинаковые имена, и это не вызовет ника¬
ких конфликтов (скоро мы увидим почему). Каждое определение структуры
должно заканчиваться точкой с запятой.
Распространенная ошибка программирования 10.1
Забывают о точке с запятой, завершающей определение структуры.
Определение struct card содержит два элемента типа char *
Элементы структуры могут быть переменными основных типов
int, float
есть
структуры.
т.д.),
и
Как
или
6,
быть одно¬
данных.
Например,
элемент типа
типа
struct
employee
(то
все элементы массива должны
Элементы структуры, однако, могут
как
массивы
относиться к различным типам
может содержать элементы типа символь¬
фамилии,
char, содержащий 'M*
имени и
suit.
же другие
такими,
го типа.
ной строки для
и
или
агрегатами,
мы видели в главе
face
данных
int для возраста работника,
'F' для обозначения пола, элемент
элемент типа
или
float для хранения данных о размерах почасовой оплаты работника и так
Элемент структуры не может быть структурой того же типа, в которой
далее.
Например,
он содержится.
менную
типа
в
структуры,
нельзя в определении struct
struct card. Однако
которую
он
входит.
ее
объявить
можно
Структура,
тель на структуру того же типа, называется
себя. Ссылающиеся
на
себя структуры будут
card объявить пере¬
как
указатель
содержащая элемент
на
тип
указа¬
структурой, ссылающейся
использованы в главе
12 для
на
по¬
строения различных видов связанных структур данных.
Данное
выше определение структуры не резервирует место под элементы
Оно просто создает новый тип данных, кото¬
объявления переменных. Переменные структу¬
и переменные других типов. Объявление
структуры в памяти компьютера.
рый
можно использовать для
ры объявляются
struct
card
объявляет а
типа struct
так же, как
а,
deck[52],
*cPtr;
массив из 52 элементов
переменную типа struct card, deck
и cPtr
указатель на struct card. Можно объявить перемен¬
card
ные данной структуры и по-другому, разместив разделенный запятыми список
переменных между закрывающей скобкой определения структуры и точкой с
запятой, завершающей ее определение. Например, указанное выше объявле¬
ние, объединенное с определением структуры, будет выглядеть следующим об¬
разом:
Глава 10
454
struct card {
char *face;
char *suit;
}
a,
ние
*cPtr;
deck[52],
Имя-этикетка
структуры
не
не является для
содержит
могут быть объявлены
обязательным. Если определе¬
для этой структуры
структуры
имя-этикетку,
переменные
только в определении структуры, но не отдельным
объ¬
явлением.
Хороший
стиль программирования 10.1
Давать каждой создаваемой структуре имя-этикетку. Имя-этикетка структуры
лит
объявлять далее
в
программе
новые
переменные,
принадлежащие
к
позво¬
типу этой
структуры.
Хороший
стиль программирования 10.2
Осмысленный
выбор
имени-этикетки
структуры позволит сделать программу самодо-
кументированной.
Следующие операции над структурами
являются единственно допустимы¬
ми: присваивание переменных-структур переменным того же типа, взятие ад¬
(&)
реса
структуры,
обращение
применение операции
к
элементам
sizeof для определения
структуры
ее
(см.
раздел
10.4),
и
размера.
Распространенная ошибка программирования 10.2
Присваивают структуру одного типа структуре другого типа.
Нельзя сравнивать структуры, поскольку элементы структуры не обязате¬
байтах памяти. Иногда в структурах быва¬
льно хранятся в последовательных
ют
«дыры»
из-за того,
что
компьютеры могут
хранить
специфические
типы
данных только в определенных позициях памяти, таких как граница полусло¬
Слово
стандартной единицей
ва, слова или
двойного
используемой
для хранения данных в компьютере
слова.
является
байта. Рассмотрим следующее определение структуры,
struct example:
переменные samplel и sample2
обычно два
в
памяти,
или четыре
которой объявляются
рша
struct
char
int
}
example
{
с;
i;
samplel,
sample2;
байта может потребовать, чтобы каж¬
example был выровнен по границе слова, то есть по
началу слова (выравнивание зависит от типа машины). На рис. 10.1 показан
пример выравнивания памяти для переменной типа struct example, которой
был присвоен символ 'a' и целое 97 (показано двоичное представление этих ве¬
личин). Если сохраняемые элементы выравниваются по границе слова, то при
размещении в памяти переменных типа struct example образуется «дырка»
размером в один байт (байт 1 на рисунке). И значение, находящееся в этой
«дырке», не определено. И если значения элементов samplel и sample2 на са¬
Компьютер
дый
с размером слова в два
из элементов struct
мом деле равны, сравнение структур совсем не
обязательно покажет,
что они
Структуры, объединения, операции
с
битами
равны, так как в неопределенном первом
и перечисления
байте
совсем не
455
обязательно будут
со¬
держаться одинаковые значения.
Распространенная ошибка программирования 10.3
Сравнение структур
является синтаксической
темах
существуют отличающиеся друг
Совет
по переносимости программ 10.1
На
ошибкой, потому
от друга
машинах элементы данных отличаются
разных
что на различных сис¬
правила выравнивания.
размером,
а
кроме того,
от типа
машины зависит организация выравнивания памяти, поэтому и расположение струк¬
туры
в
памяти также
Байты
зависит от типа
машины
l
2
о
01100001
з
oooooooo
01100001
Пример выравнивания памяти для переменной типа struct example,
иллюстрирующий появление области памяти с неопределенным значением
Рис. 10.1.
10.3. Инициализация структур
Структуры
можно инициализировать,
как и массивы,
используя список
Чтобы инициализировать структуру, после имени перемен¬
объявлении структуры ставится знак равенства, за которым следует по¬
инициализации.
ной
в
мещенный в фигурные скобки, разделенный
ров. Например, объявление
struct
card
а
=
создает переменную
вает элементу
face
инициализаторов
"Hearts"};
{"Three",
а типа struct
значение
запятыми список инициализато¬
card (структура определена выше) и присваи¬
а элементу suit значение "Hearts". Если
"Three",
в списке меньше, чем элементов в структуре, остальным эле¬
ментам автоматически присваивается значение
0 (или NULL,
если элемент
указатель). Переменным-структурам, объявленным вне определения любой
функции (т.е. глобально) присваиваются 0 или NULL, если они явно не иници¬
ализированы во внешнем
объявлении. Структуры
с помощью оператора присваивания.
ной-структуре переменную
При
того же типа,
можно инициализировать и
этом можно
либо присвоить перемен-
либо присвоить
значения отдельным
элементам структуры.
10.4.
Для обращения
Доступ
к элементам
структур
к элементам структур используются две операции:
ция элемента структуры (.),
опера¬
операцией-точкой, и опера¬
ция указателя структуры (->), также называемая операцией-стрелкой. Опе¬
рация элемента структуры обращается к элементу через имя переменной
структуры. Например, для того чтобы напечатать элемент suit структуры а из
предыдущего объявления, можно написать оператор
также называемая
456
Глава 10
a.suit);
pnntf("%s",
Операция указателя структуры, состоящая из знака минус (-) и знака бо¬
(>) без пробела между ними, обращается к элементу через указатель
структуры. Предположим, что переменная aPtr была объявлена как указатель
льше
на struct
мент
card
и
ей был присвоен адрес структуры
suit структуры
printf("%s",
а при помощи указателя
а.
aPtr,
Чтобы
напечатать эле¬
напишите оператор
aPtr->suit);
Выражение aPtr->suit
эквивалентно
(*aPtr).suit,
обращаясь по
обращается к эле¬
которое,
адресу, содержащемуся в указателе, находит структуру а и
suit, используя операцию элемента структуры. Скобки в данном случае
необходимы по той причине, что операция элемента структуры (.) имеет более
менту
высокий приоритет,
чем операция
косвенной адресации (*). Операции указа¬
скобками и квад¬
теля структуры и элемента структуры, наряду с круглыми
ратными
скобками ([]),
использовавшимися для индексов массивов, являются
операциями наивысшего приоритета и ассоциируются слева направо.
Хороший
стиль
программирования 10.3
Рекомендуется избегать
различного типа
Это
не
использования
запрещено,
одинаковых
но
может
имен
привести
для
к
элементов
структур
недоразумениям.
Хороший стиль программирования 10.4
Не рекомендуется вставлять пробелы слева и справа от знаков операций и > Это
поможет подчеркнуть, что выражение, в которых содержатся данные операции, по
существу являются именем одной переменой.
.
Распространенная ошибка программирования 10.4
и > операции указателя структуры
Вставляют пробел между составными частями
(или вставляют пробелы между составными частями других вводимых при помощи
нескольких символов операций, за исключением ?:).
Распространенная ошибка программирования 10.5
Пытаются сослаться на элемент структуры, используя только имя элемента.
Распространенная ошибка программирования 10.6
Пропускают скобки, когда ссылаются на элемент структуры, используя указатель и
операцию элемента структуры (иными словами, запись *aPtr.suit является синтакси¬
ческой ошибкой).
Программа на рис. 10.2 демонстрирует использование
структуры и указателя структуры.
С помощью операции
элементам а присваиваются значения
зателю
"Асе"
aPtr присваивается адрес структуры
и
а.
"Spades"
операций
элемента
элемента структуры
соответственно.
Оператор printf
Ука¬
печатает эле¬
менты структуры а, используя операцию элемента структуры с именем пере¬
менной а, операцию указателя структуры
мента структуры с указателем
ной адресации.
aPtr,
к
с указателем
aPtr,
и операцию эле¬
которому применена операция косвен¬
Структуры, объединения, операции
Применение операций
/*
и
указателя
с
битами
элемента
и перечисления
457
структуры
*/
структуры
#include <stdio.h>
struct card {
char *face;
char *suit;
main ()
{
struct card
а;
struct
card
a.face
="Ace";
*aPtr;
="Spades";
aPtr =&a;
printf("%s%s%s\n%s%s%s\n%s%s%s\n",
of ", a.suit,
a.face,
a.suit
"
"
aPtr->face,
of
"
(*aPtr).face,
return
", aPtr->suit,
of ",
(*aPtr).suit);
0;
Асе
of
Spades
Асе
of
Spades
Асе
of
Spades
Рис. 10.2. Использование
операций
элемента структуры и указателя структуры
10.5. Использование структур
с
функциями
Структуры могут передаваться функциям посредством передачи отдель¬
ных элементов структуры, передачи всей структуры или передачи указателя
на структуру. Когда структуры или отдельные их элементы передаются функ¬
ции, они передаются вызовом по значению. Поэтому вызванная функция не
может изменять элементы структуры в
вызывающей.
Чтобы
осуществить вызов структуры по ссылке, необходимо передать ад¬
рес структуры. Массивы структур, как и все прочие массивы, вызываются по
ссылке автоматически.
В главе 6 было отмечено, что, используя структуру, можно осуществить
вызов массива по значению.
Для
вызова массива по значению создается струк¬
тура, содержащая массив как элемент.
Вызывая структуру
по значению, мы и
массив вызываем по значению.
Распространенная ошибка программирования
10.7
Полагая, что структуры, как и массивы, вызываются по ссылке автоматически, пыта¬
ются изменить значение структуры вызывающей функции в вызываемой
Совет по повышению эффективности 10.1
Вызов структуры по ссылке более эффективен, чем вызов по значению
происходит копирование всей структуры целиком).
(при
котором
Глава 10
458
10.6.
Ключевое
Typedef
typedef предоставляет программисту механизм для созда¬
(или псевдонимов) для ранее определенных типов данных. Ча¬
сто используют typedef для того, чтобы дать укороченное имя структурному
типу. Например, оператор
слово
ния синонимов
typedef
struct
card Card;
определяет новый тип с именем Card, как синоним типа struct card. Пишу¬
щие на С часто используют typedef, чтобы определить тип структуры, при
этом отпадает необходимость в имени-этикетке.
Например,
следующее опре¬
деление
typedef
char
char
}
struct
{
*face;
*suit;
Card;
создает тип Card без использования отдельного оператора
Хороший
стиль программирования 10.5
Начинать имена, определяемые
факт,
что
typedef.
эти
Теперь Card
имена
являются
typedef,
прописной буквы, чтобы подчеркнуть
с
синонимами
можно использовать для
имен
других
тот
типов
объявления переменных
типа struct
card. Объявление
Card deck
[52];
описывает массив,
состоящий
из
52 структур Card (т.е. переменных
типа struct
card). Создание нового имени с помощью typedef не создает нового типа; type¬
def просто определяет новое имя для уже существующего типа, которое может
использоваться как псевдоним последнего.
позволяет
сделать
программу
Осмысленно выбранный псевдоним
самодокументированной. Например, прочитав
предыдущее объявление, сразу можно догадаться, что «колода
массив из
(deck)
это
52 карт (cards)».
Часто
typedef используется для присвоения псевдонимов основным типам
Например, программа, которой требуются четырехбайтовое целое,
может использовать тип int в одной системе и тип long в другой. В програм¬
мах, предназначенных для работы на разных машинах, часто дают четырех¬
данных.
байтовым целым специальный псевдоним, скажем, Integer. После этого доста¬
Integer в одном месте программы, чтобы програм¬
точно изменить псевдоним
ма
заработала
на
обеих
системах.
Совет по переносимости программ 10.2
Использование
typedef
помогает
облегчить адаптацию программы
паратно-программным платформам.
к
различным ап¬
Структуры, объединения, операции
10.7.
с
битами
Программа на рис.
как
карт,
массив
/*
10.3
раздачи карт
основана на методе моделирования тасовки и раз¬
в главе 7. Программа представляет колоду карт
Она использует высокоэффективные алгоритмы та¬
Результат выполнения программы показан на рис. 10.4.
обсуждавшемся
структур.
сования и раздачи.
I
459
Пример: моделирование высокоэффективной
тасовки и
дачи
и перечисления
Программа
и
тасовки
сдачи,
использующая
*/
структуры
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
struct card {
char *face;
char *suit;
}'
typedef
struct
card Card;
void fillDeck(Card *,
void shuffle(Card *);
void deal(Card *);
char
*[],
char
*[]);
main()
Card deck[52];
char *face[]
=
Five
["Ace", "Deuce , "Three", "Four"
"Six", "Seven", "Eight", "Nine
Ten",
"Jack", "Queen", "King"};
{"Hearts", "Diamonds
Clubs", "Spades'
ii
char
*suit[]
=
и
и
n
srand(time(NULL));
fillDeck(deck,
shuffle(deck);
deal(deck);
return 0;
face,
suit);
}
void fillDeck(Card *wDeck,
{
int i;
for
char
0; i < 52; i++) {
(i
wFace[i
wDeck[i].face
wDeck[i].suit
wSuit[i
*wFace[]
char *wSuit[]
=
=
=
%
/
13];
13];
}
void shuffle(Card *wDeck)
{
int i,j;
Card temp;
Рис. 10.3.
Высокоэффективное моделирование
тасовки и раздачи карт
(часть 1
из
2)
Глава 10
460
for
(i
i
0;
=
<
52;
i++)
rand()0 % 52;
wDeck[i];
temp
wDeck[i]
wDeck[3];
=
j
=
=
=
wDeck[3]
temp;
void deal(Card *wDeck)
{
int i;
for (i
0; i < 52;
=
printf("%5s
(i +
Рис. 10.3.
of
1)
I++)
%-8s%c", wDeck[i].face,
% 2 ? '\t' : '\n');
Высокоэффективное моделирование
Eight
Eight
of
Diamonds
of
Clubs
Seven
of
Hearts
Асе
of
Clubs
Deuce
of
Spades
Seven of Spades
Jack of Clubs
King of Hearts
wDeck[i].suit,
тасовки и раздачи карт
Ace
of
Hearts
Five
of
Deuce
of
Ten
of
Six
of
Deuce
of
Spades
Diamonds
Diamonds
Diamonds
Clubs
Ten
of
Jack
of
Three
of
Nine
of
of
Hearts
Three
of
Clubs
Ten
of
Hearts
Deuce
of
Hearts
Ten
of
Clubs
Seven
of
Diamonds
Six
of
Clubs
Queen
of
Six
of
Hearts
Three
of
Nine
of
Diamonds
Ace
of
Jack
of
Spades
of Diamonds
Five
of
King
Nine of
Seven
of
Spades
Spades
Diamonds
Clubs
Clubs
Four
of
Hearts
Six
Queen
Асе
of
Diamonds
of
King
of
Spades
Clubs
King
of
Spades
Queen
of
Hearts
Four
of
Four
Spades
of Clubs
Рис. 10.4.
Eight
Five
Nine
Five
Four
2)
of Spades
of Diamonds
of Hearts
of Hearts
of
Eight of
Jack of
Queen of
Результат
из
Spades
Diamonds
Diamonds
Clubs
Three
Spades
of Spades
(часть 2
Diamonds
Hearts
Hearts
Clubs
тасовки и раздачи карт
Программа работает следующим образом. Функция fillDeck инициализи¬
рует массив Card в порядке от туза до короля для каждой масти. Массив Card
передается функции shuffle, в которой реализован эффективный алгоритм та¬
Функция получает в качестве аргумента массив из 52 структур Card.
обращается к каждой из 52 карт (индексы массива от 0 до 51), используя
сования.
Она
структуру for. Для каждой карты случайным образом выбирается число меж*
0 и 51. Следующим шагом текущая структура Card и случайно выбранная
структура меняются в массиве местами. Полностью 52 замены выполняются
ду
Структуры, объединения, операции
с
битами
461
и перечисления
Card перетасован! Этот алго¬
бесконечной отсрочкой, возникающей в
7. Так как структуры Card уже переставлены
за один проход всего массива, и массив структур
свободен
ритм
от порока, связанного с
алгоритме тасования из главы
местами, алгоритм раздачи, реализованный в функции deal, требует
чтобы раздать тасованные карты.
всего
лишь одного прохода по массиву,
Распространенная ошибка программирования 10.8
Забывают
индекс массива при ссылке на отдельные структуры в массиве структур.
10.8.
Объединение
Объединения
производный
которого разделяют одну и ту же
тип данных,
область
подобный структуре,
памяти.
На различных
полнения программы одни переменные могут оказаться
элементы
этапах вы¬
невостребованными,
в
используются только в этой части программы,
поэтому объединения экономят пространство вместо того, чтобы впустую тра¬
тить память на не использующиеся в данный момент переменные. Элементы
то время как другие,
наоборот,
объединения могут принадлежать к
любому
типу. Число байтов, используемое
для хранения объединения, должно быть, по крайней мере, достаточным для
хранения наибольшего из элементов. В большинстве случаев объединения со¬
держат два или более типа данных. Ссылаться в данный момент времени мож¬
но только на один элемент и, соответственно, только один тип данных. Задача
обеспечить, чтобы
программиста
на данные,
в
хранящиеся
объединении,
ссылались как на данные соответствующего типа.
Распространенная ошибка программирования 10.9
к данным, хранящимся в объединении, как к данным одного типа, в то
время как хранящиеся в данный момент в объединении данные принадлежат друго¬
му типу, что приводит к появлению логической ошибки
Обращаются
Совет по переносимости программ 10.3
Если сохранять данные в объединении, как принадлежащие одному типу, а обраща¬
дальнейшем как к данным другого типа, то результат будет зависеть от
ться к ним в
реализации
системы
Объединение объявляется
с помощью ключевого слова
явления тот же, что и в случае структуры.
union number
int x;
float
union. Формат объ¬
Объявление union
{
у;
};
означает, что
number
является типом
union
с элементами
деление объединения обычно располагается
main,
в
int
программе
x и
float у. Опре¬
перед
функцией
поэтому определение может использоваться для объявления переменных
во всех функциях программы.
462
Глава 10
Общее методическое замечание 10.1
Как и в случае объявления struct, объявление union просто создает новый тип Раз¬
мещение объявления union или struct вне функции не создает глобальной перемен¬
ной
Над объединениями можно выполнять следующие операции: присваива¬
ние объединения другому объединению того же типа, взятие адреса (&), до¬
ступ к элементам объединения с использованием операций элемента структу¬
ры и указателя структуры.
Объединения
не могут сравниваться по тем же са¬
мым причинам, что и структуры.
При объявлении объединение
можно инициализировать только значения¬
первый
объединении объявление
ми того же самого типа, что и его
ном выше
union
number
value
=
элемент.
Например,
в представлен¬
{10};
является допустимой инициализацией переменной value, потому что объеди¬
нение инициализировано как int, однако объявление следующего вида будет
ошибочным:
union number value
=
{1.43};
Распространенная ошибка программирования 10.10
Сравнение объединений является синтаксической ошибкой, поскольку
существуют различные требования на выравнивание.
в
разных сис¬
темах
Распространенная ошибка программирования 10.11
Инициализация объединения
типа
первого элемента
Совет
в
объявлении значением, имеющим тип, отличный
по переносимости
программ 10.4
Объем памяти, необходимый для хранения объединения,
Совет
по
от
объединения
зависит оттипа
компьютера.
переносимости программ 10.5
Некоторые объединения невозможно просто перенести на другую компьютерную сис¬
тему Является ли объединение переносимым или нет, зависит часто от требований
на выравнивание памяти для типов данных элементов объединения в данной систе¬
ме.
Совет по повышению
Объединения
эффективности 10.2
позволяют сэкономить
память
Программа на рис. 10.5 объявляет переменную value типа union number и
отображает значения, хранящиеся в объединении, как тип int и как float. Ре¬
зультат выполнения программы зависит от реализации. Вывод программы
свидетельствует, что внутреннее представление значения float может значите¬
льно отличаться от представления int.
Структуры, объединения, операции
/* Пример объединения
#include <stdio.h>
union number
int
x;
float у;
с
битами
и
перечисления
463
*/
{
};
main ()
{
union number value;
value.x=
100;
pnntf ("%s\n%s\n%s%d\n%s%f\h\n",
"Put а value in the integer member",
"and printf both members.",
<
"int:
",
value.x,
"float:
",
value.y);
value.y=
100.0;
printf("%s\n%s\n%s%d\n%s%f\n",
"Put а value in the floating member",
"and printf both members.",
return
"mt:
",
value.x,
"float:
",
value.y);
0;
Put a value in the integer member
and print both members/
int:
100
float:
0.000000
Put a value in the floating member
and print both members.
int:
17096
float:
Рис. 10.5.
100.000000
Вывод на печать значения элемента объединения в формате каждого из типов данных
10.9.
Поразрядные операции
Все данные представляются внутри компьютера как последовательности
битов. Каждый бит может принимать значения 0 или 1. В большинстве систем
последовательность из 8 бит образует байт
стандартную единицу памяти
для переменной типа
char, для хранения других типов данных используется
Поразрядные операции используются для операций над
целочисленных операндов (char, short, int и long; как signed, так и un¬
большее число байтов.
битами
signed).
В поразрядных операциях обычно используются беззнаковые целые.
Совет
по
переносимости программ 10.6
Поразрядные операции
с двоичными
данными
являются
машинно-зависимыми.
Глава 10
464
Заметим,
что в поразрядных операциях,
обсуждаемых
льзуется двоичное представление целых операндов. Более
операций
двоичной
в
в этом разделе, испо¬
подробное описание
Д, «Системы
системе счисления приведено в приложении
счисления».
10.10 были протестирова¬
ны на
Кроме того, программы разделов 10.9 и
Apple Macintosh с использованием Think С и на
РС-совместимой
машине с
Borland С++. В обеих системах используются 16-битные (2-бай¬
товые) целые. По причине того, что выполнение поразрядных операций зависит
от типа машины, эти программы могут и не работать на вашей системе.
использованием
К поразрядным
относятс^
следующие операции:
поразрядное
И (&),
исключающее ИЛИ
разрядное
(\), поразрядное
влево («), сдвиг вправо (») и дополнение (~).
включающее ИЛИ
по¬
(^), сдвиг
В операциях поразрядного И,
поразрядного исключающего ИЛИ два
Операция поразрядного И устанавливает
поразрядного включающего ИЛИ
операнда сравниваются побитно.
и
каждый бит значения результата равным 1, если соответствующие биты каж¬
дого из операндов равны 1. Операция поразрядного включающего ИЛИ уста¬
навливает каждый бит значения результата равным 1, если соответствующий
(или в обоих) операндах равен 1. Операция поразрядного исключа¬
ИЛИ
ющего
устанавливает каждый бит значения результата равным 1, если
соответствующий бит равен 1 только в одном из операндов. Операция сдвига
влево сдвигает биты в левом операнде влево на число бит, заданное в правом
бит
в одном
операнде.
Операция
сдвига вправо сдвигает биты в левом операнде вправо на
бит, заданное в правом операнде. В результате выполнения операции
поразрядного дополнения все биты, равные 0, устанавливаются равными 1, а
все биты, равные 1, устанавливаются равными 0. Детальное обсуждение каж¬
число
дой
операций дано в следующих ниже
операций приведена на рис. 10.6.
из поразрядных
лица поразрядных
Сводная таб¬
Описание
Операции
&
примерах.
поразрядное И
Бит результата устанавливается равным 1,
если
соответствующие биты каждого из операндов равны 1
Бит результата устанавливается равным 1, если хотя бы один
соответствующий бит двух операндов равен 1
| поразрядное включающее
ИЛИ
А
поразрядное исключающее
ИЛИ
Бит результата устанавливается равным 1, если только один
соответствующий бит двух операндов равен 1
«
сдвиг влево
Сдвигает биты первого операнда влево на число бит, задаваемое
вторым операндом; справа заполняется нулевыми битами
>>
сдвиг вправо
Сдвигает биты первого операнда вправо на число битов,
задаваемое вторым операндом, метод заполнения слева
зависит от типа данных и конкретной системы
~
Все биты, равные 0, устанавливает равными 1, а все биты,
равные 1, устанавливает равными 0
дополнение
Рис. 10.6.
При
Поразрядные операции
использовании поразрядных
операций
полезно выводить значения опе¬
рандов в двоичном виде, чтобы получить наглядное представление о результатах
их выполнения.
Программа на
рис.
10.7 выводит на печать целое без знака в дво¬
бит. Функция displayBits применяет
ичном представлении группами по восемь
операцию поразрядного И
к переменным
value
и
displayMask. Часто
операция
Структуры, объединения, операции
с
битами
и
465
перечисления
это це¬
поразрядного И используется с операндом, называемым маской. Маска
биты которого установлены равными 1. Маски испо¬
льзуются для того, чтобы спрятать некоторые биты числа. В функции displayBits
маске displayMask присваивается значение 1 « 15 (10000000 00000000). Опера¬
лое значение, определенные
ция сдвига влево сдвигает в
(крайнего справа)
в
displayMask
величину 1 из бита младшего разряда
бит старшего разряда (крайний слева),
и заполняет нулями
биты справа. Оператор
&
putchar(value
displayMask
?
'1'
:
'0');
крайнего слева бита пере¬
0. Предположим, переменная value равна 65000 (11111101
11101000). Когда value и displayMask комбинируются посредством &, все
определяет, что
меной value, 1
требуется
напечатать для текущего
или
переменной value, за исключением старшего, становятся «маскирован¬
(скрытыми), потому что любой бит, подвергнутый операции И с 0,
0. Если крайний слева бит равен 1, value & displayMask имеет значение 1
печать выводится 1, в противном случае на печать выводится 0. Перемен¬
биты
ными»
дает
и на
value затем сдвигается влево на один бит при помощи выражения value
1 (это эквивалентно выражению value
value « 1). И эти шаги повторя¬
ются для каждого бита в переменной value типа unsigned. На рис. 10.8 дана
ная
«=
=
сводная таблица значений объединения двух битов посредством
операции И.
I
/* Побитная печать
#include <stdio.h>
беззнакового
*/
целого
main ()
{
unsigned x;
void displayBits(unsigned)
;
printf("Enter an unsigned integer:
scanf("%u", &x);
displayBits(x) ;
return
");
0;
}
void
displayBits(unsigned value)
{
unsigned
displayMask
с,
1
<<
15;
value);
16; с++) {
putchar(value & displayMask ?
value <<= 1;
printf("%7u
for
=
(с
=
(с
if
1;
%
8
=
",
с
<=
==
putchar(1
'1'
:
'0');
0)
1);
}
putchar(1\n1);
}
Enter
unsigned integer:
an
65000
=
11111101
Рис. 10.7.
65000
11101000
Вывод на печать целого без знака в двоичном виде
поразрядной
Глава 10
466
Bit1
Bit2
Bit1 & Bit2
0
0
0
1
0
0
0
0
1
1
Рис. 10.8. Результаты объединения двух битов посредством поразрядной операции И (&)
Распространенная ошибка программирования 10.12
Использование логической операции И (&&) вместо
поразрядной операции (&)
и на¬
оборот
Программа
на рис.
10.9 иллюстрирует
выполнение поразрядных
операций
И,
включающего ИЛИ, исключающего ИЛИ и операции поразрядного допол¬
нения. Программа вызывает функцию displayBits для вывода на печать целых
величин типа
/*
unsigned. Результаты
программы показаны на рис. 10.10.
Применение операций поразрядного AND, включающего 0R,
исключающего OR и поразрядного дополнения */
#include <stdio.h>
void displayBits (unsigned);
main ()
{
number2,
unsigned numberl,
numberl
ma s k
=
=
mask,
setBits;
65535;
1;
prmtf("The
result
of
combining the
following\n");
displayBits(numberl);
displayBits(mask);
the bitwise AND operator
displayBits(numberl & mask);
printf("using
numberl
=
15;
setBits
=
241;
&
is\n");
pnntf("\nThe result of combining the following\n");
displayBits(numberl);
displayBits (setBits);
printf("using the bitwise inclusive OR operator | is\n");
displayBits(numberl
numberl
=
number2
=
|
setBits);
139;
199;
printf("\nThe result
displayBits(numberl);
of combining the
following\n");
Рис. 10.9. Использование операций поразрядных И, включающего ИЛИ, исключающего ИЛИ
и дополнения
(часть 1
из
2)
Структуры, объединения, операции
с
битами
и перечисления
467
displayBits(number2);
printf("using the bitwise exclusive OR operator
А
displayBits(numberl
numberl
^
is\n");
number2);
2184 5;
=
one's
printf("\nThe
complement
of\n");
displayBits(numberl) ;
printf("is\n") ;
displayBits(~numberl) ;
return 0;
void
displayBits(unsigned value)
{
unsigned
с,
displayMask
printf("%7u
for
(с
=
1;
(с
%
8
1
«
=
",
value);
с
<=
16; с ++) {
& displayMask
putchar(value
value <<=1;
if
=
15;
?
'1'
:
10'
) ;
==0)
1);
putchar(1
}
putchar('\n');
}
Рис. 10.9. Использование операций поразрядных И, включающего ИЛИ, исключающего ИЛИ
и дополнения
(часть 2
из
2)
The result of combining the following
65535
11111111 11111111
=
1
=
00000000
00000001
using the bitwise AND operator
1 = 00000000 00000001
The result of combining the
15 = 00000000 00001111
241
=
00000000
&
is
following
11110001
using the bitwise inclusive OR operator
255 = 00000000 11111111
The result of combining the
139 = 00000000 10001011
199
=
21845
=
43690
=
is
A
is
following
11000111
using the bitwise exclusive OR operator
76 = 00000000 01001100
The one's
|
complement of
01010101 01010101
is
10101010
Рис. 10.10.
Результат
10101010
выполнения программы,
представленной
на рис
10 9
468
Глава 10
переменной mask присваивается значение
переменной numberl значение 65535 (11111111
В программе на рис. 10.9 целой
1
(00000000 00000001),
а
11111111). Переменные mask и numberl объединяются в выражении num¬
berl & mask посредством операции поразрядного И (&). В результате получа¬
ем 00000000 00000001. Все биты, за исключением бита младшего разряда в
переменной numberl маскированы (скрыты) после применения операции И с
переменной mask.
Поразрядная включающая операция ИЛИ используется для того, чтобы
установить в операнде заданные биты равными 1. В программе на рис. 10.9 пе¬
ременной numberl присваивается
значение 15 (00000000 00001111), а пере¬
241 (00000000 11110001). Когда numberl и setBits объеди¬
няются операцией поразрядного ИЛИ в выражении numberl | setBits, в резу¬
льтате получается 255 (00000000 11111111). На рис. 10.11 дана сводная таб¬
лица значений объединения двух битов при помощи поразрядной операции
включающего ИЛИ.
менной setBits
Bit1
Bit2
Bit1 | Bit2
0
0
1
0
1
0
1
1
1
1
1
~
Рис. 10.11.
Результаты объединения двух бит посредством операции поразрядного
включающего ИЛИ
Распространенная ошибка
|
программирования 10.13
Использование логической операции ИЛИ
(||)
вместо
поразрядной операции (|)
и на¬
оборот.
Поразрядная исключающая операция ИЛИ (^) устанавливает бит результа¬
та равным 1, если только один из соответствующих битов в двух операндах ра¬
вен 1. В программе на рис. 10.9 переменным numberl и number2 присваивают¬
соответственно
значения
139 (00000000 10001011) и 199 (00000000
11000111). Комбинируются эти переменные при помощи операции исключаю¬
щего ИЛИ в выражении numberl
number2, и получается 00000000 01001100.
На рис. 10.12 дана сводная таблица результатов объединения двух битов при
помощи поразрядной операции исключающего ИЛИ.
ся
^
Bit1
Bit2
Bit1
0
0
0
1
0
1
0
1
1
1
0
п
Рис. 10.12.
л
Bit2
Результаты объединения двух бит посредством операции поразрядного
исключающего ИЛИ (^)
Структуры, объединения, операции
с
битами
и
469
перечисления
В результате выполнения операции поразрядного дополнения (~) всем би¬
там операнда, имевшим значение
имевшим значение
0,
1, присваивается
соответственно
дополнением до единицы. На рис. 10.9
значение
0,
а всем
битам,
по-другому эту операцию называют
1
переменной numberl присваивается
21845 (01010101 01010101). В результате вычисления выражения
значение
~numberl получаем (10101010 10101010).
Программа на рис. 10.13 представляет собой пример использования опера¬
ций сдвига влево («) и сдвига вправо (»). Функция displayBits использует¬
ся для вывода на печать целых величин типа unsigned.
Операция сдвига влево («) смещает биты левого операнда влево на число
бит, определенное в правом операнде. Биты, освобождаемые справа, заменяются
«сбрасываемые» влево биты теряются. В программе на рис. 10.13 пере¬
менной numberl присваивается значение 960 (00000011 11000000). В результате
выполнения задаваемой выражением numberl « 8 операции сдвига влево на 8
бит переменная numberl получает значение 49152 (11000000 00000000).
нулями;
/* Применение операций
#include
void
сдвига
*/
<stdio.h>
displayBits(unsigned)
;
main ()
{
unsigned numberl
=
960;
printf("\The result of left shifting\n");
displayBits(numberl);
printf("8 bit positions u*sing the ");
pnntf("left shift opertor << is\n");
displayBits(numberl << 8);
printf("\nThe result of right shifting\n");
displayBits(numberl);
pnntf("8 bit positions using the ");
printf("right shift operator » is\n");
displayBits(numberl >> 8);
return 0;
}
void displayBits(unsigned value)
{
1 «
unsigned c, displayMask
=
printf("%7u
for
(c
=
1;
=
",
value);
c
<=
16; c++) {
& displayMask
putchar(value
value <<=1;
if
(c % 8
putchar (1
==
15;
?
'1'
:
'0');
0)
1);
}
putchar(1\n1);
}
Рис. 10.13. Использование операций поразрядного сдвига (часть 1
из
2)
470
Глава 10
The result of left shifting
960
00000011 11000000
=
8 bit positions using the left
49152 = 11000000 00000000
The
shift operator «
is
result of right shifting
960 = 00000011 11000000
8 bit positions using the right
3 = 00000000 00000011
shift operator » is
Рис. 10.13. Использование операций поразрядного сдвига (часть 2 из 2)
Операция сдвига вправо (») смещает биты левого операнда вправо на
число бит, определенное в правом операнде. Выполнение сдвига вправо для це¬
лого типа unsigned приводит к тому, что биты, освобождаемые слева, заменя¬
ются нулями; «сбрасываемые» вправо биты теряются. В программе на
рис. 10.13 в результате выполнения задаваемой выражением numberl » 8
операции сдвига вправо на 8 бит переменная numberl получает значение 3
(00000000 00000011).
Распространенная ошибка программирования
10.14
Значение, получаемое в результате сдвига, становится неопределенным, если
операнд
имеет отрицательное значение или его значение
которых
хранится
Совет
левый
и
1-ми
на
бит,
в
программ 10.7
Реализация операции сдвига вправо
нах
правый
чем число
операнд.
по переносимости
цательного целого
больше,
Сдвиг вправо для отри¬
освобождаемые биты 0-ми на одних маши¬
зависит оттипа машины.
со знаком заполняет
других.
Каждая поразрядная операция (исключая поразрядную операцию допол¬
нения) имеет соответствующие знаки операции присваивания. Эти знаки опе¬
раций поразрядного присваивания показаны на рис. 10.14 и используются точ¬
но так же, как и введенные в главе 3 знаки арифметического присваивания.
Знаки операций поразрядного присваивания
&=
Операция присваивания поразрядного И.
1
Операция присваивания поразрядного включающего ИЛИ
=
^=
Операция присваивания поразрядного
<<=
Операция присваивания сдвига
>>=
Операция присваивания сдвига вправо.
исключающего ИЛИ.
влево
Рис. 10.14. Знаки операций поразрядного присваивания
На рис. 10.15
показаны приоритет и ассоциативность различных опера¬
первой страницы книги и оканчивая этими строка¬
ми. Они расположены сверху вниз в порядке убывания приоритета.
ций,
введенных начиная с
Структуры, объединения, операции
битами
с
Операции
->
о n
+
*
j
++
_
*
&
(type)
~
sizeof
%
/
+
«
»
л
л
и
471
перечисления
Ассоциативность
Тип
спева направо
наивысший приоритет
справа налево
одноместные
слева направо
мультипликативные
слева
аддитивные
направо
слева направо
сдвига
слева направо
отношения
слева направо
равенства
слева направо
поразрядное И
слева направо
исключающее
I
слева направо
поразрядное
&&
слева направо
логическое И
I I
слева направо
логическое ИЛИ
справа налево
условная
!
II
V
V
II
=
&
^
?:
+=
_=
*=
%
/=
=
&=
^=
|=
<<=
>>=
справа
налево
слева направо
t
Рис. 10.15.
Приоритет
и ассоциативность
10.10. Битовые
В С существует
структуры или
ИЛИ
ИЛИ
присваивания
запятая
операций
поля
возможность задать число битов для хранения элемента
объединения
битовых полей. Битовые
типа
требуемом
данные в минимально
должны быть объявлены
Совет по повышению
unsigned
или
int посредством определения
поля позволяют лучше использовать память, храня
как
int
количестве бит.
или
Элементы
битовые
поля
unsigned.
эффективности
10.3
Битовые поля экономят память
Рассмотрим
следующее определение структуры:
struct bitCard {
unsigned face
unsigned suit
unsigned color
:
4;
:
2;
:
1;
};
Оно содержит три битовых поля типа
unsigned
пользуемых для представления колоды из
с помощью двоеточия
(:)
и
целой
face, suit
52 карт. Битовое
константы,
и
поле
задающей ширину
color,
ис¬
объявляется
поля, то есть
выделяемых для хранения элемента, помещаемых после имен эле¬
ментов типа unsigned или int. Константа, задающая ширину, должна быть це¬
число
бит,
Глава 10
472
лым числом, значение которого можно выбрать от 0 до полного числа бит, ис¬
пользуемого для хранения int в вашей системе. Наши примеры проверялись
на компьютерах с
Данное
нится в 4-х
выше
2-байтовыми (16-битными) целыми.
определение структуры показывает,
битах,
элемент
suit
в
2-х битах,
а элемент
face хра¬
1-м бите. Число
что элемент
color
в
отводимых бит определяется диапазоном значений для каждого элемента
структуры. В элементе face хранятся значения величин от 0 (туз) до 12 (ко¬
в четырех битах могут храниться значения от 0 до 15. В элементе suit
роль)
0 до 3 (0
червы, 2
трефы, 3
бубны, 1
битах могут храниться значения от 0 до 3. И наконец, в эле¬
в одном
color хранятся значения цвета 0 (красная) или 1 (черная)
хранятся
пики)
менте
=
значения величин от
=
=
=
в двух
бите могут храниться значения 0 или 1.
Программа на рис. 10.16 (результат ее
выполнения показан на рис.
10.17)
deck, содержащий 52 структуры struct bitCard. Функция fillDeck размещает 52 карты в массиве deck, а функция deal распечатывает кар¬
ты. Заметим, что обращение к битовым полям происходит точно так же, как и
создает массив
к
любым другим
бы
элементам структуры.
иметь возможность
отображать
Элемент color
включен для того, что¬
цвет карты в системах, которые позволяют
выводить цвета.
Допускается определение неименованного битового поля; такое поле ис¬
пользуется как заполнитель структуры. Например, определение структуры
struct example
unsigned а
unsigned
unsigned b
{
13;
3;
4;
:
:
:
};
в этих трех битах невоз¬
шириной в 3 бита
(в случае, если ваш компьютер имеет
другой ячейке памяти.
Неименованное битовое поле нулевой ширины служит для выравнивания
следующего битового поля по границе новой ячейки памяти. Например, опре¬
использует неименованное поле
можно что-либо сохранить. Элемент b
двухбайтовые слова) будет размещен в
деление структуры
struct example
unsigned а
unsigned
unsigned b
{
:
13;
0;
:
4;
:
};
объявляет битовое поле без имени длиной 0 бит для того, чтобы перескочить че¬
рез оставшиеся биты (столько, сколько их будет) ячейки памяти, в которой
хранится
а, и выровнять b по границе
следующей ячейки.
Совет по переносимости программ 10.8
Операции с битовыми полями зависят от типа машины Некоторые компьютеры, напри¬
мер, позволяют битовым полям пересекать границы слов, в то время как другие нет.
Распространенная ошибка программирования
Попытка
ментами
обратиться
массива.
к отдельным
Битовые
поля
битам битового
-
это
вовсе
не
10.15
поля так, как если
«массивы
бит».
бы
они
были
эле¬
Структуры, объединения, операции
с
битами
и перечисления
473
/* Пример применения битовых полей */
#include <stdio.h>
struct
bitCard
{
unsigned face
:
unsigned suit
unsigned color
:
:
4;
2;
1;
};
typedef
struct bitCard Card;
void fillDeck(Card
void deal(Card *);
*);
main()
{
Card deck[52];
fillDeck(deck);
deal(deck);
return
0;
void fillDeck(Card *wDeck)
{
int i;
for
(i
0;
=
i
51;
<=
wDeck[i].face
=
wDeck[i].suit
=
wDeck[ij.color
i++)
i
%
i
/
i
=
{
13;
13;
/
26;
}
Функция
deal
/*
Колонка
1
содержит
карты
0-25,
/*
Колонка
2
содержит
карты
2 6-51,
+
kl
void
deal(Card
печатает
в
/*
колоду
две
колонки
индекс
индекс
kl
*/
*/
k2
*/
*wDeck)
{
int
for
kl,
(kl
k2;
=
0,
k2
=
kl
printf("Card:%3d
26;
Suit:%2d
<=
25; kl++,
Color:%2d
k2++)
{
",
wDeck[kl].face, wDeck[kl].suit, wDeck[kl].color);
Suit:%2d
Color:%2d\n",
printf("Card:%3d
wDeck[k2].face,
wDeck[k2].suit,
wDeck[k2].color);
}
Рис. 10.16. Использование битовых полей для хранения колоды карт,
Распространенная ошибка программирования
Попытка взять адрес битового поля
как они не имеют адреса).
(операция
10.16
& неприменима к битовым полям, так
474
Глава 10
Card:
0
Suit
Card:
0 Suit:
1
Suit
0 Color:
0 Color:
0
Card:
0
Card:
1 Suit:
2 Color:
2 Color:
Card:
2
Suit
0 Color:
0
Card:
2
Suit:
2 Color:
Card:
3
Suit
0 Color:
0
Card:
3 Suit:
2 Color:
Card:
4
Suit
0 Color:
0
Card:
4
Suit:
2 Color:
Card:
5
Suit
0 Color:
0
Card:
5 Suit:
2 Color:
Card:
0 Color:
0
Card:
2 Color:
0 Color:
0
Card:
6 Suit:
7 Suit:
Card:
6 Suit
7 Suit
8 Suit
0 Color:
0
Card:
8
Suit:
2 Color:
Card:
9
Suit
0 Color:
0
Card:
9 Suit:
2 Color:
Card:
10
Suit
0 Color:
0
Card:
10
Suit:
2 Color:
Card:
11
Suit
0 Color:
0
Card:
11
Suit:
2 Color:
Card:
12
Suit
12
Suit:
2 Color:
0
Suit
0
0
Card:
Card:
0 Color:
1 Color:
Card:
0 Suit:
3 Color:
Card:
1
Suit
1 Color:
0
Card:
1
Card:
2
Suit
1 Color:
0
2
Card:
2 Color:
Suit:
3 Color:
Suit:
3 Suit:
3 Color:
Card:
3
Suit
1 Color:
0
Card:
Card:
Card:
4
Suit
1 Color:
0
Card:
4
Suit:
3 Color:
Card:
Card:
1 Color:
1 Color:
0
0
Card:
Card:
3 Color:
1 Color:
1 Color:
0
Card:
5 Suit:
6 Suit:
7 Suit:
Card:
5 Suit
6 Suit
7 Suit
8 Suit
0
Card:
8
Suit:
3 Color:
Card:
9
Suit
0
Card:
9 Suit:
3 Color:
Card:
10
Suit
1 Color:
1 Color:
0
Card:
11
Suit
0
Card:
Card:
12
Suit
1 Color:
1 Color:
0
Card:
10 Suit:
11 Suit:
12 Suit:
3 Color:
Card:
Card:
3 Color:
3 Color:
3 Color:
3 Color:
3 Color:
Рис. 10.17. Результат выполнения программы, представленной на рис. 10.16
Совет по повышению
эффективности
10.4
Хотя битовые поля позволяют сэкономить память, может случиться так, что получен¬
ный с их использованием машинный код будет исполняться заметно медленней. При¬
кроется в том, что для доступа только к части адресуемой единицы памяти
применение дополнительных машинных операций. Это один из многочис¬
примеров, когда необходимо делать выбор между быстродействием и ком¬
чина этого
требуется
ленных
пактностью.
10.11.
И наконец,
язык
С
Перечислимые
константы
определяемый пользователем тип пере¬
Перечисление объявляется при помощи
задается имя переменной и определяется спи¬
имеет еще один
менных, называемый перечислением.
ключевого слова enum, при этом
сок именованных целых констант, называемый списком перечисления. Эти
перечислимые константы фактически являются символическимими кон¬
стантами, значение которых может устанавливаться автоматически. Началь¬
ное значение в enum задается равным 0, если не задано иначе, и увеличивает¬
ся с шагом 1. Например, перечисление
enum
months
создает новый
{JAN,
ОСТ,
FEB,
NOV,
MAR, APR,
DEC};
MAY,
JUN,
JUL,
AUG,
SEP,
тип enum months, в котором идентификаторам автоматически
присваиваются значения от 0 до 11. Для того чтобы перенумеровать месяцы от
1 до 12, можно задать перечислимый тип следующим образом:
Структуры, объединения, операции обитами
enum
months
=
{JAN
SEP,
Так
ным
как
и перечисления
1, FEB, MAR, APR,
ОСТ, NOV, DEC};
первого идентификатора
значение
JUL,
JUN,
MAY,
явно
475
AUG,
устанавливается
рав¬
результате мы по¬
перечислении должны иметь
1,
значение остальных увеличивается, начиная с
1,
и в
лучаем значения от 1 до 12. Идентификаторы в
уникальное имя. Значения каждой перечислимой константы можно задавать
явно путем присвоения значения идентификатору непосредственно в опреде¬
лении. Несколько элементов
в списке перечисления могут иметь одно и тоже
В программе на рис. 10.18 перечислимая переменная months
используется в структуре for для вывода на печать месяцев года из массива
целое значение.
что мы задали monthName[0] как пустую строку "".
Некоторые программисты, возможно, предпочтут установить для monthName[0] значение ***ERROR***, чтобы показать
произошла логическая
monthName. Отметим,
ошибка.
/* Применение перечислимого
#include <stdio.h>
enum
months
{JAN
JUL,
=
типа
*/
1, FEB, MAR, APR, MAY, JUN,
AUG, SEP, ОСТ, NOV, DEC};
main ()
{
month;
enum
months
char
*monthName[]
for
(month
JAN;
=
=
{"", "January", "February", "March",
"April", "May", "June", "July",
"August", "September", "October",
"November", "December"};
month <=
printf("%2d%lls\n",
return
1
January
3
February
March
4
April
5
May
June
7
July
8
August
9
September
October
10
11
12
month++)
monthName[month]);
0;
2
6
DEC;
month,
November
December
Рис. 10.18.
Использование перечисления
Распространенная ошибка программирования 10.17
Присвоение
является
значения
перечислимой
синтаксической
ошибкой.
константе после того, как она
была определена,
476
Глава 10
Хороший
Для
стиль программирования 10.6
буквы только верхнего
будет напоминать про¬
имен перечислимых констант рекомендуется использовать
регистра.
позволит
Это
граммисту,
что
выделить
перечислимые
их
в тексте
константы
-
программы
это
не
и
переменные.
Резюме
Структуры
наборы (иногда
это
называемые
агрегатами)
переменных,
объединенных под одним именем.
могут содержать переменные различных типов.
Структуры
Определение структуры начинается с ключевого слова struct. Между
скобками определения структуры размещаются объявления ее элемен¬
тов.
Элементы
в
пределах
структуры
должны
иметь
уникальные
имена.
структуры создает новый тип данных, который можно ис¬
пользовать для объявления переменных.
Определение
два способа объявления переменных структуры.
Существует
состоит в том,
чтобы объявлять переменные
Первый
так же, как это делается с
других типов данных, используя struct tagName как
Второй способ заключается в том, чтобы поместить переменные
между закрывающей скобкой определения структуры и точкой с запя¬
той, заканчивающей определение структуры.
переменными
тип.
не обязательна. Если определение струк¬
туры не содержит имя-этикетку, переменные для этой структуры могут
быть объявлены только в определении структуры, и никаких других
Имя-этикетка для структуры
новых
Структуры
ции,
данного
переменных
вится
можно
знак
скобки
и
в
дальнейшем объявить
инициализировать,
этого после имени
для
типа
равенства,
разделенный
за
используя
переменной
которым
в
список
нельзя.
инициализа¬
объявлении структуры
следует
помещенный
в
ста¬
фигурные
запятыми список инициализаторов. Если иници¬
ализаторов в списке меньше, чем имеется элементов структуры, остав¬
шимся
NULL,
элементам
если
автоматически
Одна структура
может
инициализация
менной того
же
Операция
имя
значение
быть присвоена другой структуре
Допускается
через
присваивается
0 (или
указатель).
элемент
переменной-структуры
с
того же типа.
помощью
пере¬
типа.
элемента структуры используется для
переменной
обращения
к элементу
структуры.
Операция указателя структуры, состоящая из знака минус (-) и знака
больше (>) без пробела между ними, используется для обращения к
элементу через указатель структуры.
Если структура
передаются
или отдельные ее элементы передаются
вызовом
Чтобы осуществить
адрес структуры.
по
функции,
они
значению.
вызов структуры по
ссылке,
необходимо передать
Структуры, объединения, операции
с
битами
Массивы структур вызываются
Чтобы
массив
по
и перечисления
ссылке
477
автоматически.
вызвать массив по значению, создается структура,
как
Объявление
нового имени с помощью
лишь
создает
содержащая
элемент.
имя,
typedef
синонимом
являющееся
не создает нового типа, а
для
определенного
ранее
типа.
Объединение
ют одну и
производный
область
ту же
произвольный
тип данных, элементы которого разделя¬
памяти.
Элементы объединения могут
иметь
тип.
Память, зарезервированная под объединения, должна быть
достаточ¬
ной для хранения самого большого его элемента. В большинстве случа¬
ев объединение содержит два или более типа данных. В данный момент
времени
только
можно ссылаться только на один
тип
один
Объединение объявляется посредством
объявления
тот
что
же,
и
в
случае
При объявлении объединение
ниями
того
же
элемент и,
соответственно,
данных.
что
типа,
и
ключевого слова
union. Формат
структуры.
можно инициализировать только значе¬
его
первый
элемент.
Поразрядная операция И (&) берет два целочисленных операнда и уста¬
навливает бит результата равным 1 в том случае, если соответствующие
биты в каждом из операндов равны 1.
Маски используются, чтобы спрятать некоторые биты
числа.
(|)
операция включающего ИЛИ
берет два операнда и
устанавливает бит результата равным 1 в том случае, если соответству¬
Поразрядная
ющий бит
хотя
Для каждой
бы
в
одном
из
операндов
равен 1.
поразрядных операций (за исключением одноместной
операции поразрядного дополнения) существует соответствующая опе¬
рация
из
присваивания.
Поразрядная операция исключающего ИЛИ (^) берет два операнда и
устанавливает бит результата равным 1 в том случае, если только один
из соответствующих битов операндов равен 1.
Операция сдвига влево («) смещает биты в левом операнде влево на
число битов заданное правым операндом. Освобождаемые справа биты
заполняются
О-ми.
Операция сдвига вправо (») смещает биты в левом операнде вправо на
число битов заданное правым операндом. При применении сдвига впра¬
во к целому без знака освобождаемые слева биты заполняются 0-ми. В
случае целого со знаком освобождаемые биты могут заполняться как
0-ми, так и 1-ми в зависимости от типа машины.
Операция
поразрядного дополнения (~) берет операнд и заменяет значе¬
ния его битов на противоположные, при этом получается дополнение
операнда до единицы.
Битовые
мом
поля экономят память, храня данные в минимально
числе
Элементы
необходи¬
бит.
битовые
поля
необходимо объявлять
как
int
или
unsigned.
Глава 10
478
Битовое
поле
щаемых
объявляется
имен
после
с помощью двоеточия и ширины поля, поме¬
элементов
unsigned
типа
или
int.
Ширина битового поля должна быть целой константой, значение кото¬
рой заключено между 0 и числом бит, используемым для хранения пе¬
ременной типа int в вашей системе.
Битовое поле, заданное без имени, используется
как
заполнитель.
поле без имени с шириной 0 используется для выравнивания
следующего битового поля по границе нового машинного слова.
Битовое
Перечисление объявляется
задается имя переменной
при помощи ключевого слова enum, при этом
определяется список именованных целых
и
констант, называемый списком перечисления. Значения
ются
с
0,
если
не
задано
иное
число,
и
в enum начина¬
всегда увеличиваются
1.
на
Терминология
struct
операция присваивания
левого сдвига
typedef
union
агрегаты
битовое поле
операция присваивания
битовое поле нулевой ширины
вложенные структуры
выбор между быстродействием
и
компактностью
доступ
к
до
поразрядного исключающего ИЛИ
элементам
структуры
структуры
структуры
стрелка
структуры
определение структур
имя
элемента
перечисление
инициализация
поразрядные операции
структур
маска
операция
битовое
поле
структура, ссылающаяся на себя
тип структуры
дополнения
(~)
левого
сдвиг влево
сдвиг вправо
структур
единицы
,
сдвиг
структур
неименованное
до
присваивание структур
производный тип
битов
маскирование
операция
(->)
перечислимая константа
имя-этикетка
объявление
(^=)
(.)
точка
структуры
операция элемента
имя
массив
(|=)
правого сдвига (>=)
операция элемента
единицы
запись
заполнитель
поразрядного включающего ИЛИ
операция присваивания
операция присваивания
дополнение
дополнение
(<=)
операция присваивания
поразрядного И (&=)
сдвига
(<)
(&)
операция поразрядного И
операция поразрядного
включающего ИЛИ
(|)
типы данных,
определяемые
программистом
указатель на структуру
ширина битового поля
элемент
операция поразрядного
исключающего ИЛИ (^)
этикетка структуры
операция правого сдвига (>)
Распространенные ошибки программирования
10.1.
Забывают
10.2.
Присваивают структуру одного
о точке с
запятой, завершающей определение структуры.
типа
структуре
другого
типа.
Структуры, объединения, операции
10.3.
и перечисления
479
Сравнение структур является синтаксической ошибкой, потому
на различных
правила
10.4.
битами
с
системах
существуют
отличающиеся
друг
от
что
друга
выравнивания.
Вставляют пробел между
и > операции ука¬
составными частями
зателя структуры (или вставляют пробелы между составными час¬
тями других вводимых при помощи нескольких символов опера¬
ций, за исключением ?:).
10.5. Пытаются
сослаться на элемент структуры,
используя только имя
элемента.
10.6.
Не используют скобки, когда
ссылаются на элемент структуры, ис¬
пользуя указатель и операцию элемента структуры
запись
ми,
10.7.
Полагая,
*aPtr.suit
пытаются
щей функции
10.9.
в
изменить
значение
структуры
вызываю¬
вызываемой.
Забывают индекс
массиве
слова¬
что структуры, как и массивы, вызываются по ссылке ав¬
томатически,
10.8.
(иными
синтаксической ошибкой).
является
массива
при
ссылке
на
отдельные
структуры
в
структур.
к данным, хранящимся в
Обращаются
объединении, как к данным
данный момент в объе¬
одного типа, в то время как, хранящиеся в
динении данные принадлежат другому типу,
лению логической ошибки.
10.10. Сравнение объединений
является
что приводит к появ¬
синтаксической ошибкой,
поско¬
требования
на вы¬
льку в разных системах существуют различные
равнивание.
Ю.Н.Инициализация объединения
имеет
тип,
отличный
от
в
типа
объявлении значением,
первого
элемента
10.12.Использование логической операции И (&&)
операции (&)
и
которое
объединения.
вместо
поразрядной
вместо
поразрядной
наоборот.
10.13.Использование логической операции ИЛИ (||)
операции (|) и наоборот.
10.14.3начение,
получаемое в результате сдвига, становится неопреде¬
ленным, если правый операнд имеет отрицательное значение или
его значение больше, чем число бит, в которых хранится левый
операнд.
10.15.Попытка
если
не
бы
обратиться
были
они
«массивы
10.16.Попытка
товым
отдельным
битам битового
Битовые
поля
как
так,
поля это вовсе
бит».
взять адрес
полям,
10.17.Присвоение
к
элементами массива.
так
битового
как
значения
они
поля
не
(операция &
адреса).
неприменима
к
би¬
имеют
перечислимой
константе, после того как она
была определена, является синтаксической ошибкой.
Глава 10
480
Хороший
10.1.
стиль
Давать каждой создаваемой структуре имя-этикетку. Имя-этикет¬
ка
структуры
менные,
10.2.
программирования
объявлять далее в программе
к типу этой структуры.
позволит
Осмысленный выбор
программу
имени-этикетки
10.4.
структуры
позволит
сделать
использования одинаковых имен для эле¬
ментов структур различного типа.
к
пере¬
самодокументированной.
10.3. Рекомендуется избегать
вести
новые
принадлежащие
Это
не запрещено, но может при¬
недоразумениям.
Не рекомендуется вставлять пробелы слева и справа от знаков опе¬
раций. и ->. Это поможет подчеркнуть, что выражение, в которых
содержаться
данные операции,
по
существу являются
именем од¬
ной переменой.
10.5.
Начинать имена, определяемые
typedef,
прописной буквы, чтобы
с
подчеркнуть тот факт, что эти имена являются синонимами имен
другого типа.
10.6.
Для имен перечислимых констант рекомендуется использовать
буквы только верхнего регистра. Это позволит выделить их в тек¬
сте программы и будет напоминать программисту, что перечисли¬
мые
константы
это
не
переменные.
Советы по переносимости программ
10.1.
На разных
того,
кроме
машинах
от
типа
элементы
машины
данных
отличаются
зависит организация
размером,
а
выравнивания
памяти, поэтому и расположение структуры в памяти также зави¬
сит
10.2.
типа
машины.
Использование typedef
к
10.3.
от
различным
помогает
облегчить адаптацию программы
аппаратно-программным платформам.
Если сохранять данные
объединении,
в
как принадлежащие одно¬
обращаться к ним в дальнейшем как к
результат будет зависеть от реализации
му типу, а
то
типа,
10.4.
Объем
типа
10.5.
памяти
необходимый
Некоторые объединения
невозможно
Является
систему.
или нет, часто зависит от
типов
данных
элементов
Поразрядные операции
но-зависимыми
10.7.
вправо
10.8.
объединения
зависит от
с
просто
ли
перенести
битовыми данными
являются
Операции
с
для
машин¬
.
на
одних
машинах
битовыми полями
Сдвиг
освобождае¬
зависит от типа машины.
для отрицательного целого со знаком заполняет
биты 0-ми
на другую
объединение переносимым
требований на выравнивание памяти
объединения в данной системе.
Реализация операции сдвиг вправо
мые
системы.
компьютера.
компьютерную
10.6.
для хранения
данным другого
и
1-ми
на
других.
зависят от типа машины.
Некоторые
компьютеры, например, позволяют битовым полям пересекать гра¬
ницы слов, в то время как другие нет.
Структуры, объединения, операции
с
битами
перечисления
481
более эффективен,
чем вызов по значе¬
и
Советы по повышению
эффективности
10.1.
по ссылке
Вызов структуры
котором происходит копирование всей структуры цели¬
(при
нию
ком).
10.2.
Объединения позволяют сэкономить память.
10.3.
Битовые поля экономят память.
10.4.
Хотя битовые поля позволяют сэкономить память, может получи¬
ться так, что полученный с их использованием машинный код бу¬
исполняться заметно
дет
медленней. Причина
что для доступа только к части
ется
из
применение
многочисленных
между
Общие
10.1.
примеров,
быстродействием
и
машинных
когда
компактностью.
методические замечания
и в случае объявления struct, объявление union просто создает
новый тип. Размещение объявления union или struct вне функции
не создает глобальной переменной.
Как
Упражнения
10.1.
дополнительных
этого кроется в том,
единицы памяти
требу¬
операций. Это один
необходимо делать выбор
адресуемой
для самоконтроля
Заполните пропуски в каждом из следующих утверждений.
a)
это
ных
под
одним
объединение
связанных
логически
перемен¬
именем.
b)
это
объединение переменных
под одним именем,
в котором переменные разделяют одну и ту же область памяти.
c) В результате
чения
каждый бит
выполнения операции
устанавливается
результата
равным
1,
если
зна¬
соответствую¬
щие биты каждого операнда равны 1. В противном случае бит уста¬
навливаются
равным
0.
d) Переменные, объявленные
ся ее
e)
в
определении структуры, называют¬
.
В результате выполнения операции
чения результата устанавливается равным
соответствующих
битов любого
1,
из операндов
каждый бит зна¬
бы один из
равен 1. В противном
если хотя
случае бит устанавливается равным 0.
f) Ключевое
слово
g) Ключевое
слово
мов
ранее
h) Биты
объявляет структуру.
используется для
определенных
типов
задания
данных.
результате выполнения операции
ваются равными 1, если ровно один соответствующий
из
в
операндов
равными 0.
16
Зак 801
равен
синони¬
устанавли¬
бит любого
1. В противном случае биты устанавливаются
Глава 10
482
поразрядного AND
i) Операция
чтобы
битов, «обнулив»
j) Ключевое
(&)
биты,
отдельные
часто
для
используется
того,
то есть выделить некоторые из
остальные.
слово
чтобы
используется,
ввести опреде¬
объединения.
ление
k) Имя структуры
1) Обращение
называют
рации
m) Операции
и
или
влево
либо
n)
с помощью опе¬
либо с помощью операции
,
битов
структуры.
к элементу структуры возможно
вправо
.
применяются
для
сдвига
соответственно.
набором
является
целых
чисел,
представленных
идентификаторами.
10.2.
Установите,
верными;
являются
если
следующие
b) Два объединения
лить,
можно
только
сравнить
или
не¬
объясните, почему.
неверно,
утверждение
a) Структуры могут содержать
верными
утверждения
тип
один
между
данных.
собой, чтобы опреде¬
равны ли они между собой.
c) Наличие
имени-этикетки у структуры является
d) Элементы различных структур должны
необязательным.
иметь уникальные име¬
на.
e) Ключевое
слово
используется для определения новых ти¬
typedef
пов данных.
10.3.
f) Структуры
всегда передаются
g) Структуры
нельзя
Напишите один
из
дое
или
следующих
partNumber
элементов
b) Определите,
что
ссылке.
несколько
операторов
С, выполняющих
каж¬
действий:
могут
Part
part, содержащую перемен¬
partName типа char, значения
названием
с
int
типа
которого
по
сравнивать.
a) Определите структуру
ную
функциям
и массив
иметь
длину
является
до
25
синонимом
символов.
типа
c) Используйте Part для объявления переменной а
массива b[10] типа struct part и переменной ptr
struct part.
типа struct
part,
указателя на
struct
part.
d) Считайте с клавиатуры partNumber
элементы переменной а.
e) Присвойте
значение
f) Присвойте адрес
g) Выведите
на
переменную ptr
ее
10.4.
переменной
массива
печать
и
partName
в
отдельные
а третьему элементу массива
b.
b указателю ptr.
значение
элемента
3
массива
и операцию указателя структуры для
b, используя
обращения к
элементам.
Найдите ошибки
в
каждом
из
следующих
примеров:
card была определена как содержащая
а) Предположим,
тип
на
а
именно face и suit. Кроме того, была
char,
два указателя
объявлена переменная с типа struct card и переменная cPtr как
что struct
Структуры, объединения, операции
с
битами
и перечисления
483
struct card. Переменной cPtr был присвоен адрес пе¬
указатель на
с.
ременной
*cPtr->face);
printf("%s\n",
b) Предположим, было определено, что struct card содержит два
указателя типа char, а именно face и suit. Кроме того, было объяв¬
лено, что массив hearts[13] принадлежит к типу struct card. Сле¬
дующий оператор должен вывести на печать face для десятого эле¬
мента.
hearts.face);
printf("%s\n",
c)
union
values
char
float
x;
double
v
}
=
{
w;
у;
{1.27};
d) struct person {
char lastName[15];
char firstName[15j;
int age;
}
e) Предположим, что struct person была определена как
разумеется, с соответствующими исправлениями.
в примере
(d),
person d;
f) Предположим, было объявлено,
что переменная p принадлежит
к типу struct person, и переменная с
типу
p
объявлена принадлежащей
к
struct card.
=
с;
Ответы на упражнения для самопроверки
10.1.
а) структура, b) объединение, с) поразрядного И (&), d)
e) поразрядного
включающего ИЛИ
элемента¬
(|), f)
struct, g) typedef, h)
поразрядного включающего ИЛИ (^), i) маскировать, j) union, k)
этикеткой, 1) элемента структуры, указателя структуры, m) сдвига
ми,
влево(<<),
10.2.
сдвига
вправо(»), n)
список
перечисления.
может содержать несколько типов данных.
а) Неверно. Структура
b) Неверно. Объединения
не могут сравниваться, по тем же причи¬
нам, что и структуры, из-за
проблем
связанных с выравниванием.
c) Верно.
d) Неверно. Элементы
разных структур могут иметь одинаковые
имена, но элементы одной структуры должны иметь уникальные
имена.
e) Неверно.
новых имен
Ключевое
f) Неверно. Структуры
g) Верно,
16*
слово
(синонимов)
из-за
typedef
используется для определения
для ранее определенных типов данных.
всегда передаются
проблем,
связанных
с
функциям
по значению.
выравниванием.
484
Глава 10
10.3.
а)
struct
part
{
int
partNumber;
char partName[25];
};
b) typedef
struct
part
c)
b[10],
*ptr;
Part
а,
Part;
d) scanf("%d%s", &a.partNumber, &a.partName);
e)
b[3]
f)
ptr
=
=
а;
b;
g) pr.intf("%d %s\n",
(ptr
10.4.
+
(ptr
+
3)->partNumber,
3)->partName);
а) Ошибка: пропущены скобки, в которые необходимо заключить
*cPtr, что приведет к неправильному порядку операций при вычис¬
лении
выражения.
b) Ошибка:
был пропущен индекс
массива. Выражение следует за¬
образом hearts[10].face.
c) Ошибка: объединение может быть инициализировано только зна¬
чениями, которые имеют тот же самый тип, что и первый элемент
писать следующим
объединения.
d) Ошибка: в
конце
определения структуры должна стоять точка с
запятой.
e) Ошибка:
слово
объявлении переменной было пропущено
в
ключевое
struct.
f) Ошибка: нельзя присваивать переменные-структуры различных
типов друг другу.
Упражнения
10.5.
Напишите определения для следующих структур
a) Структуры inventory, содержащей следующие
и
объединений:
элементы: симво¬
partName[30], целую переменную partNumber, дей¬
переменную price, целую переменную stock и целую
льный массив
ствительную
переменную reorder.
b) Объединения data,
double d.
содержащего char с, short s,
long 1, float f,
c) Структуры с именем address, содержащей символьные массивы
streetAddress[25], city[20], state[3] и zipCode[6].
d) Структуры student, содержащей массивы firstName[15] и lastName[15],
а
также
переменную
homeAddress
типа struct
address
(с).
e) Структуры test, содержащей 16 битовых полей шириной
бит. В качестве имен битовых полей возьмите буквы от а
из
10.6.
вопроса
Задано определение структуры
struct customer {
char lastName[15];
char firstName[15];
int customerNumber;
и
объявлены переменные:
в один
до
р.
Структуры, объединения, операции
struct
char
char
char
char
char
}
}
битами
и
485
перечисления
{
phoneNumber[ll];
address[50];
city[15];
state[3];
zipCode[6];
personal;
*customerPtr;
customerRecord,
customerPtr
Напишите
для
с
&customerRecord;
=
отдельные
обращения
к
выражения,
элементам
которые
структуры
в
можно
каждом
использовать
из
следующих
случаев.
a) Элементу lastName структуры customerRecord.
b) Элементу lastName структуры, на которую указывает
custo¬
merPtr.
c) Элементу firstName
d) Элементу firstName
структуры customerRecord.
структуры,
на которую указывает custo¬
merPtr.
e) Элементу customerNumber
f) Элементу customerNumber
структуры customerRecord.
структуры,
на которую указывает
customerPtr.
g) Элементу phoneNumber
элемента
personal
структуры customer¬
Record.
h) Элементу phoneNumber
элемента
personal
структуры, на кото¬
рую указывает customerPtr.
i) Элементу address элемента personal структуры customerRecord.
j) Элементу address элемента personal структуры, на которую ука¬
зывает customerPtr.
k) Элементу city
1) Элементу city
элемента
элемента
personal
personal
структуры customerRecord.
структуры, на которую указыва¬
ет customerPtr.
m) Элементу state элемента personal структуры customerRecord.
n) Элементу state элемента personal структуры, на которую указы¬
вает customerPtr.
о) Элементу zipCode
элемента
p) Элементу zipCode
элемента
personal
personal
структуры customerRecord.
структуры, на которую ука¬
зывает customerPtr.
10.7.
Измените программу на рис. 10.16 так, чтобы колода тасовалась,
применив эффективный алгоритм тасования (см. рис. 10.3). Распе¬
чатайте полученную колоду в две
каждой картой должен стоять ее
10.8.
колонки как на рис.
10.4. Перед
цвет.
Создайте объединение integer с элементами char с, short s, int i и
long 1. Напишите программу, которая вводит значения типов char,
short, int, long, и сохраните значения в переменных типа union in¬
teger. Каждая переменная-объединение должна быть напечатана
486
Глава 10
char, short, int
как
long. Всегда
и
ли значения печатаются прави¬
льно?
10.9.
Создайте объединение floatingPoint с элементами float f, double d
и long double 1. Напишите программу, которая вводит значения ти¬
и long double. Сохраните значения в переменных
floatingPoint. Каждая переменная-объединение должна
быть напечатана как float, double и long double. Всегда ли значе¬
ния печатаются правильно?
пов
float, double
union
типа
Ю.Ю.Напишите
программу, которая сдвигает целое число вправо на че¬
тыре бита.
должна распечатывать целое число в двоич¬
Программа
Выясните, нули
освободившиеся биты.
ном представлении до и после операции сдвига.
или
единицы
Ю.Н.Если
ваш
ваша
помещает
компьютер
руйте программу
имеет
на рис.
система
в
четырехбайтовый
10.7 так, чтобы
она
тип
int, модернизи¬
работала
с четырех¬
байтовыми целыми.
10.12.Сдвиг
влево целого числа типа
умножению на два.
unsigned
Напишите функцию
на один
бит
power2,
которая
эквивалентен
берет
два
целых аргумента number и pow и вычисляет
number
2pow
*
Используйте операцию сдвига. Программа должна выдавать
чать
число
как
в
десятичном,
и
так
в
на пе¬
виде.
двоичном
сдвига влево может применяться для того, чтобы упако¬
вать два символьных значения в двухбайтовое целое без знака. На¬
пишите программу, которая принимает два символа с клавиатуры и
10.13.0перация
передает их функции packCharacters. Чтобы упаковать два символа
в целую переменную без знака, присвойте первый символ перемен¬
ной типа unsigned, сдвиньте переменную влево на 8 двоичных пози¬
ций и
объедините
разрядного
ее со вторым символом посредством операции по¬
включающего
ИЛИ.
Программа должна выводить
до и после того, как они
будут упако¬
ваны в целое без знака, чтобы доказать, что символы действительно
правильно упакованы в переменную.
символы в двоичном
формате
операцию сдвига вправо, операцию поразрядного И и
маску, напишите функцию unpackCharacters, которая берет целое
типа unsigned из упражнения 10.13 и распаковывает его в два сим¬
10.14.Используя
вола.
Для
того
лого без знака,
чтобы распаковать два символа из двухбайтового це¬
объедините по И беззнаковое целое с маской 65280
(11111111
00000000)
Присвойте
полученное значение
объедините
11111111).
типа
ны
И
сдвиньте
беззнаковое
Присвойте
результат
переменной
целое
полученное
char. Программа должна
ичном виде,
волы
по
и
с
вправо на восемь бит.
типа
значение
напечатать
char. После
маской
255
этого
(00000000
другой переменной
беззнаковое целое
в дво¬
прежде чем его распаковать, а потом напечатать сим¬
в двоичном виде,
чтобы подтвердить,
что они
были распакова¬
правильно.
10.15.Если
ваша система имеет 4-байтовые целые, перепишите програм¬
му упражнения 10.13 для упаковывания 4-х символов.
Структуры, объединения, операции
с
битами
и
487
перечисления
10.16.Если ваша система имеет 4-байтовые целые, перепишите функцию
unpackCharacters упражнения 10.14 для распаковывания четырех
символов. Создавая маски, которые понадобятся для распаковки
четырех символов, посредством сдвига значения 255 в переменной
маски на
вы
8 бит 0, 1, 2
или
3 раза (в
зависимости от
байта, который
извлекаете).
10.17.Напишите программу, которая меняет порядок битов в целом чис¬
ле без знака на обратный. Программа должна принимать введенное
пользователем значение и вызывать функцию reverseBits для того,
чтобы вывести на печать биты в обратном порядке. Выведите зна¬
чение в двоичном виде как до, так и после преобразования, чтобы
подтвердить, что биты действительно располагаются в обратном по¬
рядке.
10.18.Измените функцию displayBits на
было использовать как на системах
рис.
10.7 так, чтобы
ее
можно
2-байтовыми, так и с 4-байто¬
выми целыми. Подсказка: чтобы определить, сколько байт выделя¬
ется под целое число на конкретной машине, используйте опера¬
с
цию sizeof.
программа вызывает функцию
10.19.Нижеследующая
определить,
кратным
tiple
и
ли
является
некоторому
скажите,
число,
целое
multiple,
введенное
с
клавиатуры,
X. Исследуйте функцию mul¬
равно значение X.
целому числу
чему
/* Программа определяет,
#include <stdio.h>
является
ли
целое
число
кратным
int multiple(int);
main()
{
int у;
print("Enter
scanf("%d",
an
integer between 1 and 32000;
&y);
if
(multiple(у))
printf("%d is
else
printf("%d is
return
а
multiple of X\n",
not
0;
multiple(int num)
{
int i,
for
mask
=
1,
mult
=
1;
1; i <= 10; i++,
(i
if ((num & mask)
!= 0)
mult
0;
break;
=
=
}
return mult;
у);
multiple of X\n",
a
}
int
чтобы
mask <<=
{
1)
y);
");
x
*/
Глава 10
488
10.20.Что делает следующая
#include
mt
программа?
<stdio.h>
mystery(unsigned);
main()
{
unsigned x;
printf("Enter an integer: ");
scanf("%u", &x);
printf("The result is %d\n", mystery(x));
return 0;
int
mystery(unsigned bits)
{
unsigned
for
i,
mask
=
1
1; i <= 16;
(i
if ( (bits & mask)
=
<<
15,
i++,
bits
mask)
==
++total;
return total
%
2
==
0
?
1
total
:
0;
<<=
=
0;
1)
г
л
а
в
а
11
Работа
с
файлами
Цели
Научиться создавать,
файлы.
читать, записывать и
модифици¬
ровать
Познакомиться
с
обработкой файлов
последователь¬
с
обработкой файлов
произвольного
ного доступа.
Познакомиться
доступа.
Глава 11
490
Содержание
11.1.
Введение
11.2.
Иерархия данных
11.3.
Файлы и потоки
11.4.
Создание
11.5.
Чтение данных из
11.6.
Файлы произвольного доступа
11.7.
Создание
11.8.
файла
последовательного доступа
файла
последовательного доступа
файлов произвольного
Произвольная
запись данных в
доступа
файл
произвольного доступа
11.9.
Последовательное
11.10.
Пример: программа обработки транзакций
из
файла произвольного доступа
Распространенные ошибки программирования
Резюме
Советы
программирования
шению
чтение данных
Хороший
стиль
Советы по повы¬
Ответы на
самоконтроля
по переносимости программ
Упражнения для
Упражнения
эффективности
упражнения для самоконтроля
11.1.
Хранение
данных в переменных и массивах является временным; все эти
данные теряются при завершении
ния
Введение
работы программы. Для
постоянного хране¬
больших объемов данных используются файлы. Компьютеры хранят дан¬
ные на
памяти, главным
устройствах вторичной
В этой
вах.
здавать,
главе мы
обновлять
и
объясним,
образом
дисковых
как с помощью написанных на
обрабатывать файлы
данных.
устройст¬
С программ
со¬
Будут рассмотрены файлы
с последовательным и произвольным доступом к данным.
11.2.
Иерархия
Компьютер обрабатывает
виде
комбинаций нулей
товлять
элементы
и единиц.
Дело
данных
данных
в
в том, что
проще
электронные схемы, которые могут находиться
стояниях
одно из таких состояний представляет
но все впечатляющие задачи,
которые
виде,
двоичном
0,
то
есть
в
и экономичнее изго¬
в двух
а другое
устойчивых
со¬
1. Удивительно,
способен выполнять компьютер,
осно¬
вываются на самых элементарных манипуляциях с нулями и единицами.
Работа
с
491
файлами
Значение наименьшего элемента данных, к которому может
компьютер, равно 0 или 1. Этот элемент данных называется бит
обращаться
(по-англий¬
bit сокращение
ски
двух
от «binary digi£»
число, способное принимать одно из
значений). Совокупность операций, выполняемых компьютером, сводит¬
ся к различным простым
чения
бита, установка
Для
действиям над битами, таким, как определение зна¬
бита, инверсия бита (1 вместо 0 и наоборот).
очень обременительно работать с данными низкого
значения
программистов
являются биты. Вместо этого программисты предпочитают
работать с данными в виде десятичных цифр (то есть 0, 1, 2, 3, 4, 5, 6, 7, 8
и 9), букв (от А до Z и от а до z), и специальных знаков ($, @, %, *, (, ), -, +, ",
уровня,
:, ?, /
какими
и многих
волами.
других). Цифры, буквы
и специальные знаки называются сим¬
Множество символов, которыми
можно пользоваться при написании
программ и представлении элементов данных на конкретном
зывается его набором
символов.
Так
как компьютеры могут
компьютере, на¬
обрабатывать
то¬
каждый символ из набора символов компьютера пред¬
комбинацией из 0 и 1 (называемой байтом). На сегодняшний день
лько нули и единицы,
ставляется
байты чаще всего состоят
граммы
и
элементы
обрабатывают
Подобно тому,
шем
из
восьми
данных,
эти символы как
как
битов. Программисты записывают про¬
пользуясь
символами;
компьютеры
в
дальней¬
наборы битов.
символы состоят из
битов,
поля состоят из символов.
Поле представляет собой группу символов, которые передают значение. На¬
пример, поле, состоящее исключительно из букв верхнего и нижнего регист¬
ра, может использоваться для представления имени человека.
Обрабатываемые компьютерами элементы образуют иерархию
которой
данных,
в
элементы данных становятся больше по размеру и сложнее по струк¬
(байтам), полям и т.д.
struct) состоит из нескольких полей.
начисления заработной платы запись для конк¬
туре по мере продвижения от битов к символам
Запись (соответствует
Например,
ретного
в
в программе для
работника
С
понятию
может состоять из следующих
полей:
1. Номер карточки социального страхования
2. Имя
3.
Адрес
4. Ставка
почасовой
5. Количество
6. Выплаты
оплаты
заявленных
на
данный
налоговых
льгот
момент
7. Размер удержанного федерального налога,
Таким образом,
запись
дыдущем примере каждое
Конечно,
числении
заработной
это группа логически связанных
записи на каждого
работников
платы для каждого из них.
записей. Файл
т.д.
полей. В пре¬
поле принадлежало одному и тому же
компания может иметь много
связанных
и
и
Файлом
заработной
работника. Таким образом, файл
начисления
будет
платы
работнику.
иметь запись о на¬
называется группа
содержит
начисления
по
одной
заработной
платы маленькой компании может содержать 22 записи, в то время как такой
же файл большой компании может содержать 100000 записей. Вполне обычна
ситуация, когда организация имеет сотни и даже тысячи файлов, которые мо¬
гут содержать миллионы или даже миллиарды символов информации. С рос¬
Глава 11
492
том популярности лазерных компакт-дисков и технологии мультимедиа даже
триллионы байт скоро станут обычным явлением. Рис. 11.1 иллюстрирует
иерархию данных.
Sally
Black
Tom
Blue
Judy
6reen
Iris
Orange
Randy
Judy
Файл
Red
6reen
Байт (код
символа J в
ASCII)
Рис. 11.1. Иерархия данных
Чтобы облегчить поиск необходимых данных, по
каждой
записи
файла выбирается
крайней
в качестве ключа записи.
мере одно поле
в
Ключ идентифи¬
объекту. Напри¬
заработной платы, описанной в этом разделе, в каче¬
стве ключа удобно выбрать номер карточки социального страхования.
Существует множество способов организации записей в файле. Наиболее
популярный из них называется последовательным файлом, в котором записи,
как правило, хранятся в порядке, определяемом ключом. В файле начисления
заработной платы, например, записи хранились бы упорядоченными по номе¬
цирует
запись как принадлежащую конкретному лицу или
мер, в записи начисления
ру карточки социального страхования.
Большинство фирм используют для хранения данных много различных
файлов. Например, компании могут иметь файлы начисления заработной пла¬
ты, файлы счетов, по которым необходимо получить деньги (списки сумм,
причитающихся с клиентов), файлы инвентаризации, а также многочислен¬
ные файлы других типов. Группа связанных файлов иногда называется базой
данных. Набор программ, предназначенный для создания и управления база¬
ми данных, называется системой управления базами данных (СУБД, или
DBMS
Database Management System).
Работа
493
файлами
с
11.3. Файлы
С
любой
рассматривает
(рис. 11.2). Каждый файл
и потоки
последовательный
как
файл
оканчивается или
бым байтом, определенным
в
работающей
с
байтов
поток
маркером конца файла,
или осо¬
файлами программе. Когда файл
ему ставится в соответствие поток. В начале исполнения про¬
граммы автоматически открываются три файла и связанные с ними потоки
открывается,
стандартный ввод, стандартный
вывод
стандартная
и
ошибка.
Потоки
обеспечивают каналы передачи данных между файлами и программами. На¬
пример, стандартный поток ввода позволяет программе считывать данные с
клавиатуры, а стандартный поток вывода позволяет выводить данные на экран.
Открытый файл возвращает указатель на структуру FILE (определенную в
<stdio.h>),
лом.
которая содержит информацию, используемую при
Эта структура
дескриптор файла,
включает
с
таблицей открытых файлов. Каждый
системы, называемом
рационной
работе
фай¬
то есть индекс в массиве опе¬
эле¬
мент массива содержит блок
управления файлом (FCB), который используется
операционной системой для доступа к конкретному файлу. Для обращения к
стандартному вводу, стандартному выводу и стандартному потоку ошибок сле¬
дует воспользоваться указателями файлов stdin, stdout и stderr соответственно.
о
i
2
3
4
5
7
6
8
9
n-l
.
маркер конца файла
Рис. 11.2. Вид
файла
из n
байт
с точки зрения С
Стандартная библиотека поддерживает многочисленные функции чтения
файлов и записи данных в файлы. Функция fgetc, подобно getchar,
считывает из файла один символ. Функция fgetc получает в качестве аргумен¬
та указатель на FILE для файла, из которого будет считываться символ. Вызов
данных из
fgetc(stdin)
считывает один символ из
stdin
стандартного ввода. Такой
ее
getchar(). Функция fputc, подобно putchar, запи¬
файл. Функция получает в качестве аргумента символ,
вызов эквивалентен вызову
сывает один символ в
который
лентен
должен быть записан. Вызов
'a'
символ
в
stdout
стандартный
функции fputc('a',stdout) записывает
функции эквива¬
вывод. Такой вызов этой
putchar('a').
Несколько других функций,
дартного ввода и записи в
использовавшихся для чтения данных из стан¬
стандартный
вывод, имеют аналоги со сходными на¬
званиями, предназначенные для выполнения
функции fgets
строки из файла
fputs
и
операций
с
файлами. Например,
могут быть использованы соответственно для чтения
и записи
строки
в
файл. Их «двойники» gets
8. В следующих разделах мы представим аналоги
fscanf и fprintf. А несколько позже обсудим функции
лись в главе
printf
11.4. Создание
В С
не
файла
предусмотрено
ных как часть языка С.
здании структуры
последовательного доступа
возможности
словами, не существует никаких
и puts обсужда¬
функций scanf и
fread и fwrite.
задания
операций
с
файла. Другими
файле, реализован¬
структуры
записями в
Поэтому программист должен сам позаботиться о со¬
файла, отвечающей требованиям конкретного приложения.
494
Глава 11
В следующем примере мы увидим, как программист может задать
подобную
структуру записи.
Программа
который
на рис.
11.3 создает простой файл последовательного доступа,
можно использовать в программе учета оплаты счетов,
помогающей
следить за суммами задолженности клиентов компании. Для каждого клиента
программа просит ввести номер счета, имя клиента и баланс (то есть сумму,
которую
клиент
шлом). Данные
В
должен
компании
за
на каждого из клиентов
товары
и
образуют
услуги,
полученные
в
про¬
«запись» для этого клиента.
этом приложении в качестве ключа записи используется номер счета.
Дру¬
создаваться и поддерживаться упорядоченным по
номерам счетов. Эта программа подразумевает, что пользователи вводят запи¬
си в порядке возрастания номеров. В более удобной для работы системе регист¬
гими словами,
файл будет
рации счетов должна
обеспечиваться
возможность сортировки,
ватель мог вводить записи в произвольном порядке.
В
жны сначала упорядочиваться, а затем уже записываться в
/* Создание последовательного файла
#include <stdio.h>
main()
{
int account;
char name[30];
float balance;
FILE *cfPtr;
if
((cfPtr
=
/*cfPtr
=
файл.
*/
указатель
fopen("clients.dat",
printf("File
else {
чтобы
файла
"w"))
clients.dat
==
NULL)
could not be opened\n");
printf("Enter the account, name, and balance.\n");
printf("Enter EOF to end input.\n");
printf ("? ");
scanf("%d%s%f", &account, name, &balance);
while
(!feof(stdin)) {
fpnntf(cfPtr, "%d %s %.2f\n",
account, name, balance);
);
printf("?
scanf("%d%s%f", &account, name, &balance);
}
fclose (cfPtr);
}
return
0;
}
Enter
Enter
? 100
? 200
? 300
? 400
? 500
the account, name,
EOF to end input.
Jones 24.98
Doe 345.67
White 0.00
Stone -42.16
Rich 224.62
пользо¬
этом случае записи дол¬
and balance.
Рис. 11.3. Создание файла последовательного доступа
*/
Работа
495
файлами
с
Теперь давайте
FILE
посмотрим, как устроена эта программа.
Оператор
*cfPtr;
что cfPtr является указателем на структуру FILE. Программа на
С управляет каждым файлом при помощи отдельной структуры FILE.
объявляет,
языке
Чтобы работать
с
файлами, программисту
Скоро мы увидим, как
ена эта структура.
на
нет
необходимости
структура
блок управления файлом (FCB) операционной
FILE
знать, как устро¬
косвенно ссылается
системы для заданного
файла.
Совет по переносимости программ 11.1
Структура FILE
зависимости
зависит от типа
от
того,
как
Каждый открытый файл
типа
операционной
каждая
система
системы
обращается
(т
e
со
элементы ее меняются
своими
в
файлами).
должен иметь отдельно объявленный указатель
для ссылок на файл. Строка
FILE, который используется
if
((cfPtr
=
fopen("clients.dat",
"w"))
NULL)
==
указывает имя файла
clients.dat",
который будет использоваться про¬
граммой, и устанавливает «канал общения» с файлом. Для файла, открытого
fopen, указателю файла cfPtr присваивается указатель на структуру FILE.
Функции fopen передаются два аргумента: имя файла и режим открытия фай¬
ла. Режим "w" указывает, что файл должен быть открыт для записи. Если фай¬
ла не существует, и его открывают для записи, fopen создает файл. Если для
записи открывается уже существующий файл, его содержимое уничтожается
без предупреждения. Структура if используется
в
программе для того, чтобы
файла cfPtr равным NULL (т. e. файл не
открыт). Если он равен NULL, печатается сообщение об ошибке и выполнение
программы заканчивается. В противном случае вводимые данные обрабатыва¬
ются и записываются в файл.
определить, не является ли указатель
Распространенная ошибка программирования
11.1
созданного файла для записи ("w"), тогда как на самом деле пользо¬
ватель хочет сохранить находящиеся в файле данные, содержимое файла уничтожа¬
Открытие ранее
ется
без предупреждения
Распространенная ошибка программирования 11.2
Забывают открыть файл, перед
тем
как
ссылаться
на
него
в
программе
Программа предлагает пользователю ввести различные поля для каждой
записи либо комбинацию, представляющую конец файла, когда ввод данных
окончен. На рис. 11.4 представлен список
файла
Строка
конец
while
в
комбинаций
клавиш, означающих
различных компьютерных системах.
(!feof(stdin))
функцию feof, чтобы определить, установлен ли индикатор конца
файла, на который ссылается stdin. Индикатор конца файла сообщает про¬
грамме, что чтение данных закончено. В программе на рис. 11.3 индикатор
использует
конца
файла
для стандартного ввода устанавливается, когда пользователь вво¬
дит соответствующую
комбинацию
клавиш.
Аргумент, передаваемый функ¬
Глава 11
496
указатель на
файл, для которого необходимо проверить индикатор
stdin). Функция возвращает ненулевое значение
(true), если индикатор конца файла установлен, в противном случае возвраща¬
ется нуль. В данной программе цикл while, включающий в себя вызов feof, бу¬
дет продолжать выполняться, пока не будет установлен индикатор конца
ции feof,
конца
файла (в
данном случае
файла.
Компьютерные
Комбинация
системы
UNIX
IBM
клавиш
<return> <ctrl>d
PC и совместимые
<ctrl>z
/
<ctrl>d
Macintosh
<ctrl>z
VAX(VMS)
Рис. 11.4. Комбинации
Оператор
fprintf(cfPtr,
"%d
клавиш для конца
%s
%.2f\n",
файла
в различных популярных системах
account,
name,
balance);
файл clients.dat. Позже эти данные могут быть считаны
программой, предназначенной для чтения файлов (см. раздел 11.5). Функция
это аналог printf, за исключением того, что fprintf получает еще ар¬
fprintf
гумент
указатель файла для файла, в который будут записаны данные.
записывает данные в
Распространенная ошибка программирования 11.3
Ссылаясь
на
Хороший
стиль
Убедитесь,
затели
файл, используют неверный указатель файла
программирования 11.1
что вызовы
функций, работающих
с
файлами, содержат корректные ука¬
файлов.
После
того, как пользователь ввел конец файла, программа закрывает
файл clients.dat с помощью fclose и завершает работу. Функции fclose в каче¬
стве аргумента также передается указатель файла
(а не имя файла). Если фун¬
кция fclose не вызывается явно, операционная система обычно сама закрыва¬
ет файлы при завершении работы программы. Это пример поддержания опера¬
ционной системой «порядка
Хороший
стиль программирования 11.2
Явно закрывать
обращаться
Совет
в доме».
к
каждый файл,
как только выяснится, что
программа больше не
будет
файлу
по повышению
Закрывая файл,
мы
эффективности 11.1
освобождаем ресурсы, которые,
льзователям или программам.
возможно,
требуются
другим по¬
Работа
с
497
файлами
На рис. 11.3 представлен
образец работы программы; пользователь вводит
информацию
файла, чтобы показать окончание
ввода данных. Здесь мы не имеем возможности убедиться, что записи данных
в действительности появляются в файле. Для проверки того, что файл был
успешно создан, в следующем разделе будет представлена программа, которая
считывает содержимое файла и выводит его на печать.
Рис. 11.5 иллюстрирует связь между указателями на FILE, структурами
FILE и FCB в памяти компьютера. Когда файл "clients.dat открыт, FCB для
файла скопирован в память. Рисунок показывает связь между возвращаемым
указателем файла и FCB, используемым операционной системой для управле¬
ния файлом.
Программы могут либо вообще не работать с файлами, либо работать с од¬
ним или несколькими файлами. Каждый из обрабатываемых в программе фай¬
лов должен иметь уникальное имя и будет иметь отличающийся от других ука¬
затель файла, возвращаемый fopen. Все функции работы с файлами последовадля пяти счетов, а затем конец
К этой
части имеет доступ пользователь
Только операционная система
имеет доступ к этой части
FILE
(определенную
в
Таблица открытых
файлов
<stdio.h>).
0
1
cfPtr
2
3
а
4
5
6
Структура FILE для
"clients.dat"
содержащая дескриптор,
то есть небольшое целое,
которое является индексом
в таблице открытых файлов.
Программа вызывает процедуру
операционной системы, которая
использует данные в FCB для
управления любыми операциями
ввода и вывода в реальный файл
на диске. Замечание: пользователь
не может получить прямого
доступа к FCB.
7
Этот элемент таблицы
копируется из FCB
на диске, когда
~"
файл открывается.
Когда программа выдает запрос
ввода-вывода, например
fprxntf(cfPtr, "%d %s %.2f",
account,
name,
balance);
программа помещает дескриптор (7) в
структуру FiLE и использует дескриптор
нахождения FCB в таблице открытых
для
файлов.
Рис. 11.5. Связь между указателями FILE, структурами FILE и FCB
Глава 11
498
файл открыт, должны ссылаться на файл с
указателя файла. Файлы могут быть открыты в
тельного доступа после того, как
по¬
мощью соответствующего
од¬
режимов. Чтобы создать файл или уничтожить
ранее были записаны данные, откройте файл для
ном из нескольких возможных
содержимое
файла,
в
который
("w"). Чтобы прочитать существующий файл, откройте файл для чтения
(V). Добавить записи в конец уже существующего файла можно, открыв файл
записи
для
добавления ("а ). Если необходимо
как записывать в файл, так и считывать
откройте файл для обновления в одном из трех предназначен¬
"r+", "w+" или "a+". Режим "r+" открывает файл
режимов
из него данные,
ных для этого
чтения и записи. Режим "w+" создает файл для чтения и записи. Если файл
уже существует, файл открывается и текущее содержание файла уничтожается.
Режим "a+" открывает файл для чтения и записи, все записи производятся в
для
конец
файла. Если файл
не существует, он создается.
Если при открытии файла,
ошибка,
выбранного режима, проис¬
Некоторые часто встречающиеся
вне зависимости от
возвращает NULL.
ошибки перечислены ниже:
ходит
fopen
Распространенная ошибка программирования 11.4
Открытие для чтения несуществующего файла.
Распространенная ошибка программирования 11.5
Открытие файла для
файлу (это
чтения или записи
зависит от
без соответствующих привилегий доступа
к
операционной системы).
Режимы
Описание
r
Открыть файл для
чтения
w
Создать файл для
записи.
Если
файл уже существует,
его содержимое
пропадает.
Добавление: открывает
а
r+
Открывает файл
w+
Создает
файл
или создает
файл
для записи в конец
файла.
для обновления (чтение и запись).
для обновления
Если
файл уже существует,
его содержимое
пропадает.
Добавление: открывает
в конец файла.
a+
или создает
файл
для обновления' запись производится
Рис. 11.6. Режимы открытия файлов
Распространенная ошибка программирования 11.6
Открытие файла для
перечислены
записи при отсутствии
режимы,
в
которых
могут
свободного
места на диске. На рис. 11.6
быть открыты файлы.
Распространенная ошибка программирования 11.7
Неверный выбор режима открытия файла
кам.
ния
может привести к разрушительным
Например, если открыть файл в режиме записи ("w") вместо режима
("r+"), то это приведет к полной потере содержимого файла.
ошиб¬
обновле¬
Работа
499
файлами
с
Хороший
стиль
программирования 11.3
Открывайте файлы
ные в
файле
только для чтения
изменения содержимого
льных
(без
возможности
данные не должны изменяться
файла
обновления),
если записан¬
Это позволит избежать неумышленного
Это еще один пример применения правила минима¬
привилегий
11.5. Чтение данных из
файла
последовательного
доступа
Данные
хранятся
ходимости извлечь и
файл
создать
файлах для
обработать. В
в
того, чтобы их можно было в случае необ¬
предыдущем разделе было показано, как
с последовательным доступом.
прочитать данные из такого
В
этом разделе мы
обсудим,
как
файла.
Программа на рис. 11.7 считывает записи из файла "clients.dat", создан¬
программой на рис. 11.3, и выводит содержимое записей на печать. Опе¬
ного
ратор
FILE
*cfPtr;
объявляет, что cfPtr является указателем на структуру FILE.
if
((cfPtr
=
fopen("clients.dat",
"r"))
==
Строка
NULL)
открывает файл "clients.dat" для чтения ("r") и проверяет успешность выпол¬
нения данной операции (т.е. что fopen не возвратила NULL). Оператор
fscanf(cfPtr,
"%d%s%f",
считывает «запись» из
исключением того, что
файла,
&account,
fscanf
в качестве
из которого считываются данные.
первый
чит значение "Jones"
balance
и
второй оператор fscanf,
name и
&balance);
файла. Функция fscanf
оператор выполнится в
ся
name,
из
balance принимают
это аналог
функции scanf,
за
аргумента передается еще указатель
После того,
как
рассмотренный
выше
раз, account получит значение 100, name полу¬
значение
файла
24.98. Каждый раз, когда
исполняет¬
считывается следующая запись и
новые значения.
При
достижении конца
account,
файла
он
закрывается и выполнение программы завершается.
Для
того
чтобы
считать данные из
файла последовательного доступа, про¬
файла и считывает все данные после¬
грамма обычно начинает чтение с начала
пор, пока они не закончатся. В ходе выполнения программы
может возникнуть необходимость обработать данные из файла последователь¬
ного доступа несколько раз (начиная каждый раз с первой записи). Выполне¬
довательно до тех
ние оператора
rewind(cfPtr);
показывающий номер бай¬
приводит к тому, что указатель позиции файла
та, который должен быть считан следующим
будет перемещен в начало (т.е.
нулевой байт) файла, на который указывает cfPtr. Указатель позиции файла
не указатель в
обычном понимании. Это целое значение, определяющее байт
в
следующей процедуре чтения или запи¬
си. Его еще иногда называют смещением. Указатель позиции является элемен¬
том связанной с файлом структуры FILE.
файле, который будет
использован при
Глава 11
500
/* Чтение и распечатка
#include <stdio.h>
main()
{
int account;
char name[30];
float balance;
FILE *cfPtr;
/*
if
((cfPtr
последовательного
cfPtr
=
указатель
fopen("clients.dat",
=
printf("File
else {
could not be
файла */
файла
"r"))
clients.dat
==
*/
NULL)
opened\n");
printf("%-10s%-13s%s\n", "Account", "Name", "Balance");
fscanf(cfPtr, "%d%s%f", &account, name, &balance);
while
(!feof(cfPtr)) {
printf("%-10d%-13s%7.2f\n", account, name, balance);
fscanf(cfPtr, "%d%s%f", &account, name, &balance);
}
fclose(cfPtr);
}
return
0;
Balance
Account
100
Name
Jones
24.98
200
300
Doe
White
345.67
0.00
400
Stone
-42.16
500
Rich
224.62
Рис. 11.7. Чтение и вывод на печать данных
из
файла
последовательного доступа
Теперь можно представить программу (рис. 11.8), которая позволяет ме¬
неджеру по кредиту получить список клиентов с нулевым сальдо (клиентов,
которые не должны денег), клиентов с положительным сальдо (которым ком¬
пания должна какую-то сумму
денег)
и клиентов с отрицательным сальдо
должны компании деньги за уже полученные товары и
торые
Отметим,
что данные в этом
файле
(ко¬
услуги).
с последовательным доступом не могут
быть изменены без риска разрушить другие хранящиеся в файле данные. Напри¬
мер, если имя **White** необходимо поменять на **Worthington , то нельзя просто
взять и переписать старое имя. Запись для White была введена в файл как
300
White
0.00
Если перезапись
она
будет
300
с новым именем
будет
сделана в том же месте
файла,
то
выглядеть так:
Worthington
Новая запись
0.00
больше,
**Worthington" будут
чем первоначальная.
лема в данном случае состоит в том, что
использованием
Символы
после второго
**o**
в
следующей записи в файле. Проб¬
при форматированном вводе/выводе с
записаны уже поверх
fprintf
и
иметь переменную длину.
fscanf
поля
а следовательно,
Например, 7, 14, -117, 2074
и
и записи
27383
могут
являются це-
Работа
с
501
файлами
лыми типа int и занимают в памяти компьютера одинаковое пространство, од¬
нако при выводе их на экран или при записи на диск с помощью fprintf они
будут
представлены полями разной длины.
/*Программа для справок
#include <stdio.h>
по
*/
кредиту
main ()
{
int request, account;
float balance;
char name[30];
FILE *cfPtr;
if
((cfPtr
fopen("clients.dat", "r"))
printf("File could not be opened\n");
else {
=
==
NULL)
printf("Enter request\n"
"
1
"
2
"
3
"
4
scanf("%d",
while
-
-
-
-
List
accounts
with
List
accounts
with
zero balances\n"
credit balances\n"
accounts
with
debit
List
End
(request
case
run\n?
balances\n"
");
&request);
!=
fscanf(cfPtr,
switch
of
4)
{
"%d%s%f",
(request)
1:
&account,
&balance);
{
printf("\nAccounts
with
while(!feof(cfPtr))
if
name,
(balance
zero
balances:\n");
{
==0)
printf("%-10d%-13s%7.2f\n",
account,
fscanf(cfPtr,
name,
balance);
"%d%s%f",
&account,
name,
&balance);
}
break;
case 2:
printf("\nAccounts
while
with
(!feof(cfPtr))
if
(balance
<
credit
balances:\n");
{
0)
printf("%-10d%-13s%7.2f\n",
account,
fscanf(cfPtr,
name,
balance);
"%d%s%f",
&account,
name,
&balance);
}
break;
Рис. 11.8.
Программа
для вывода справки о кредите (часть 1 из 2)
Глава 11
502
case
3:
printf("\nAccounts with debit balances:\n");
while
(!feof(cfPtr))
if
(balance
{
>0)
printf("%-10d%-13s%7.2f\n",
account, name, balance);
fscanf(cfPtr,
"%d%s%f",
&account,
name,
&balance);
}
break;
}
rewind(cfPtr);
printf("\n? ");
scanf("%d", &request);
}
printf("End
of
run.\n");
fclose(cfPtr);
}
return
0;
Рис. 11.8.
Enter
1
-
2
-
3
-
4
?
-
zero balances
List accounts with credit balances
List
accounts with debit balances
End of
run
1
300
with
zero balances:
White
0.00
2
Accounts
with
400
?
для вывода справки о кредите (часть 2 из 2)
request
List accounts with
Accounts
?
Программа
credit balances:
Stone
-42.16
3
Accounts
with
debit balances:
100
Jones
200
Doe
345.67
500
Rich
224.62
?
24.98
4
End of
Рис. 11.9.
run
Пример
вывода справки о кредите, выполненного
программой
на рис. 11.8
Работа
с
503
файлами
Поэтому последовательный
используется
в
файл целиком. Чтобы выполнить упоминавшееся
необходимо скопировать записи, предшествующие
весь
ни,
fprintf и fscanf обычно не
Вместо
этого переписывают
файле.
доступ с помощью
обновления записей
для
вый файл,
файл
выше изменение име¬
300 White
0.00,
после чего ввести исправленную запись, а затем дописать в
300 White 0.00. Таким образом, ради того, чтобы
все записи после
нить одну запись, придется
11.6.
Как уже
обработать
все записи
в
но¬
новый
изме¬
файла.
Файлы произвольного доступа
функции форматирован¬
длину. Напротив,
отдельные записи в файле с произвольным доступом обычно имеют фиксиро¬
ванную длину, что позволяет получить к ним непосредственный (и следовате¬
льно, быстрый) доступ без поиска по всем записям. Поэтому файлы с произво¬
ного вывода
льным
отмечалось ранее, созданные с помощью
fprintf
доступом
записи
не
используются
банковских системах,
обязаны
в
иметь одинаковую
системах
бронирования
мест
на
самолеты,
обработ¬
транзакций, требующих быстрого доступа к данным. Существуют другие
способы обеспечения произвольного доступа к файлам, но мы ограничим наше
обсуждение подходом, использующим записи с фиксированной длиной, ввиду
системах складского учета и других системах
ки
его простоты и наглядности.
По причине того, что все записи
файла
с произвольным доступом имеют
равную длину, можно вычислить точное положение записи относительно на¬
чала
файла
как
функцию
это помогает получить
чае, когда размер
ключа записи.
Вскоре мы увидим, каким образом
к конкретной записи, даже в слу¬
мгновенный доступ
файла
очень велик.
Рис. 11.10 иллюстрирует один из способов организации файла произволь¬
ного доступа. Такой файл подобен составу из грузовых вагонов, некоторые ва¬
гоны пустые, а некоторые с каким-то грузом. Но каждый вагон состава имеет
одну и ту же длину.
Рис. 11.10.
В файл
с записями постоянной длины
с произвольным доступом можно вставлять новые данные, не раз¬
те, что уже находятся в файле. Ранее сохраненные данные
можно также обновить или удалить без переписывания всего файла. В следую¬
щих разделах мы объясним, как создавать файл произвольного доступа, вво¬
рушая
1
Вид файла произвольного доступа
при этом
дить данные, считывать данные как последовательно, так и в произвольном
порядке, обновлять данные и удалять данные, которые больше не нужны.
Глава 11
504
11.7. Создание
Функция fwrite
файла
пересылает
в
произвольного доступа
файл
заданное число байт начиная с указан¬
файле, которое обо¬
файла. Функция fread пересылает заданное чис¬
ло байт из места в файле, определенного указателем позиции файла, в массив
в памяти, начинающийся с указанного адреса. Теперь при записи целого чис¬
ного адреса памяти.
Данные
записываются с того места в
значено указателем позиции
ла вместо оператора
fprintf(fPtr,
который
знак, требующие
number)';
"%d",
одной до одиннадцати цифр (десять цифр
байту памяти каждый) для четырехбайтного
может напечатать от
по одному
плюс
цело¬
го, мы можем применить оператор
sizeof(int),
fwrite(&number,
fPtr);
1,
байта (или два байта для систем
с двухбай¬
который всегда
товым целым) из переменной number в файл, определяемый fPtr (далее мы
кратко объясним смысл аргумента 1). В дальнейшем, вызвав fread, можно про¬
записывает четыре
читать эти четыре
байта
в переменную
number. Хотя fread
и записывают данные, такие как целые числа, в
формате
и
с
fwrite
не переменным, размером, данные, с которыми они оперируют,
ся в
форме «сырых данных» (то есть в виде байтов), а
формате, с которым оперируют printf и scanf.
считывают
фиксированным,
а
обрабатывают¬
не в привычном для чело¬
века
Функции fwrite
и
fread дают
возможность чтения и записи массивов дан¬
это число
Третий аргумент как в fread, так и в fwrite
элементов массива, которые необходимо считать с диска или записать на диск.
Представленный выше вызов функции fwrite записывает на диск одно целое,
поэтому третий аргумент равен 1 (как если бы записывался один элемент мас¬
сива).
Программы обработки файлов редко записывают в файл единственное
поле. Обычно они записывают за один раз переменную типа struct, как будет
ных с диска и на диск.
показано в приведенных ниже примерах.
Рассмотрим
следующую формулировку задачи:
Создать программу для работы со счетами клиентов
компании, способ¬
ную хранить до ста записей с фиксированной длиной. Каждая запись должна
состоять из номера счета, который будет использоваться как ключ, имени,
фамилии и сальдо. В программе должны быть предусмотрены возможности:
обновлять счета, вставлять записи с новыми счетами,
дить
для
печати, в текстовый
ным
удалять
счет и выво¬
отформатированном виде, пригодном
файл. Использовать для работы файл с произволь¬
список всех записей со счетами в
доступом.
В следующих нескольких разделах описаны технические приемы, необхо¬
димые для создания программы работы со счетами. В программе на рис. 11.11
показано, как открыть файл произвольного доступа, определить формат запи¬
и закрыть файл. Эта програм¬
функцию fwrite, инициализирует все сто записей в файле "credit.dat" пустой структурой struct. Каждая пустая struct содержит 0 для номе¬
ра счета, NULL (заданный пустыми кавычками) для фамилии, NULL для име¬
си с помощью struct, записать данные на диск
ма, используя
ни и 0.0 для баланса.
Файл, инициализированный подобным образом,
вирует на диске место под данные о клиентах
содержит ли конкретная запись данные.
резер¬
и дает возможность определить,
Работа
с
505
файлами
/* Последовательное создание файла
#include <stdio.h>
с
доступом
произвольным
*/
struct clientData {
int acctNum;
char lastName[15];
char firstName(10];
float balance;
};
main ()
{
int in¬
struct clientData blankClient
FILE *cfPtr;
if
((cfPtr
=
{0,
=
fopen("credit.dat",
"",
"w"))
"",
==
0.0};
NULL)
printf("File could not be opened.\n");
else {
for
(i
=
1;
i
<=
100;
I++)
fwrite(&blankClient,
sizeof(struct
clientData),
cfPtr);
1,
fclose(cfPtr);
}
return
0;
}
Рис. 11.11. Создание файла с произвольным доступом
Функция fwrite
В нашей программе
записывает
блок (заданное
число
байтов)
данных в
файл.
в результате выполнения оператора
fwrite(&blankClient,
sizeof(struct
clientData),
1,
cfPtr);
структура blankClient размером
sizeof(struct clientData) будет записана в файл,
указанный cfPtr. Операция sizeof возвращает размер в байтах для объекта, со¬
это struct clientData). Операция si¬
держащегося в скобках (в данном случае
zeof является исполняемой во время компиляции одноместной операцией,
возвращающей целое число без знака. Операция sizeof может использоваться
байтах для любого типа данных или выражения. К
примеру, sizeof(int) используется для того, чтобы определить, хранится целое в
двух или в четырех байтах для данного конкретного компьютера.
для определения размера в
Совет
по повышению
эффективности
11.2
Многие программисты ошибочно думают, что sizeof является
функцией,
и что испо¬
льзование ее приводит к увеличению времени исполнения программы,
вызовом
sizeof
функции. Однако
операцией,
является
Функция fwrite
связанному с
никакого увеличения времени не происходит, поскольку
выполняемой во время компиляции.
может в
действительности
использоваться для записи не¬
Чтобы
записать несколько элементов
скольких элементов массива объектов.
массива, программист использует указатель на массив, как
первый
аргумент в
506
Глава 11
вызове fwrite, а количество элементов, которое необходимо записать, задает
третьим аргументом. В предыдущем операторе fwrite использовалась для за¬
писи одного объекта, который не являлся элементом массива. Запись одного
объекта эквивалентна записи одного элемента массива, поэтому в вызове fwri¬
te
стоит
1.
11.8.
Произвольная
запись данных в
файл
произвольного доступа
файл "credit.dat". Для хра¬
файла используется комби¬
нация fseek и fwrite. Функция fseek устанавливает указатель позиции файла
в заданное положение, после чего fwrite записывает данные.
Образец выпол¬
Программа
на рис
11.12
записывает данные в
нения данных в специально определенных местах
нения программы показан на рис.
/* Запись
#include
в
файл
с
11.13.
произвольным
доступом
*/
<stdio.h>
struct clientData {
int acctNum;
char lastName[15];
char firstName[10];
float balance;
};
main ()
{
FILE *cfPtr;
struct clientData client;
if
((cfPtr
=
fopen("credit.dat",
printf("File
else {
"r+"))
==
NULL)
could not be opened.\n");
printf("Enter account number"
(1 to 100, 0 to end input)\n?
scanf("%d", &client.acctNum);
"
");
while
(client.acctNum !=0) {
printf("Enter lastname, firstname, balance\n?
scanf("%s%s%f", &client.lastName,
&client.firstName, &client. balance);
fseek(cfPtr,
(client.acctNum -1) *
sizeof(struct clientData), SEEK_SET);
fwrite(&client, sizeof(struct clientData), 1,
printf("Enter account number\n? ");
scanf("%d", &client.acctNum);
}
}
fclose(cfPtr)
return
;
0;
}
Рис. 11.12. Запись данных
в
файл произвольного доступа
");
cfPtr);
Работа
с
507
файлами
Enter account number
?
(1
to 100,
to end input)
0
37
balance
Enter lastname, firstname,
? Barker Doug 0.00
Enter account number
29
?
balance
Enter lastname, firstname,
? Brown Nancy -24.54
Enter account number
?
96
Enter lastname, firstname,
? Stone Sam 34.98
balance
Enter account number
?
88
Enter lastname, firstname,
? Smith Dave 258.34
balance
Enter account number
?
33
Enter lastname, firstname,
? Dunn
Stacey 314.33
balance
Enter account number
? 0
Рис. 11.13. Пример выполнения программы на рис 11.12
Оператор
(accountNum -1)
fseek(cfPtr,
*
sizeof(struct clientData),
SEEK_SET);
устанавливает указатель позиции
который ссылается cfPtr на байт,
*
sizeof(struct clientData); значе¬
число
смещением. Поскольку номер счета
файла,
на
определяемый выражением (accountNum-l)
ние этого выражения называется
от
1 до 100, а байты
жения
байта
в
файле
начинаются с нулевого, то при нахождении поло¬
из номера счета вычитается
1. Таким образом, для первой
записи
файла устанавливается на байт 0 файла. Символическая
константа SEEK__SET показывает, что указатель позиции файла устанавлива¬
ется относительно начала файла на величину смещения. Рассмотрение приве¬
денного выше оператора показывает, что поиск в файле счета с номером 1
устанавливает указатель позиции файла на начало файла, поскольку вычис¬
ленное значение равно 0. На рис. 11.14 показан указатель файла, ссылающий¬
указатель
ся
позиции
на структуру
FILE
в
памяти.
Указатель позиции файла показывает,
что
следующий байт, который будет считан или записан, это пятый байт от начала
файла.
Согласно стандарту ANSI прототип функции fseek определяется как
int
fseek(FILE
где offset
*stream,
байт
long
int
offset,
int whence);
whence, в файле, на кото¬
Аргумент whence может иметь одно из трех значе¬
ний
SEEK__SET, SEEK__CUR или SEEK__END,
указывающих место в
файле, с которого начинается поиск. SEEK__SET указывает, что поиск начнет¬
ся с начала файла; SEEK__CUR указывает, что поиск начнется с текущего поло¬
жения в файле; и наконец, SEEK_END указывает, что поиск начнется с конца
файла. Эти три символические константы определены в заголовочном файле
stdio.h.
рый
число
указывает stream.
от положения, задаваемого
508
Глава 11
Память
Рис. 11.14. Указатель позиции файла показывает на сдвиг в пять байт от начала файла
11.9. Последовательное
чтение данных из
файла
произвольного доступа
Функция
fread
считывает заданное число
байт
из
файла
в память.
Напри¬
мер, оператор
fread(&client,
sizeof(struct
считывает число байтов, равное
ссылается
cfPtr,
clientData),
1,
cfPtr);
sizeof(struct clientData),
и сохраняет данные в
из
файла,
на
который
структуре client. Байты считываются из
файла начиная с места, определенного указателем позиции файла. Функцию
fread можно использовать и для чтения нескольких элементов массива с фик¬
сированным размером с помощью указателя на массив, в котором будут храни¬
ться элементы, и указанием числа элементов, которые необходимо прочитать.
Представленный
элемент. Для
соответствующее
быть прочитан один
чтобы прочитать более одного элемента, необходимо задать
выше оператор определяет, что должен
того
значение в третьем аргументе
Программа на рис. 11.15
файла "credit.dat", проверяет
содержащие данные записи в
вает момент,
считывает
функции fread.
последовательно каждую
запись из
все записи на наличие данных и распечатывает
форматированном
когда достигается конец
файла,
а
виде.
Функция feof
отслежи¬
функция fread передает
дан¬
ные с диска в структуру client типа clientData.
11.10.
В
Пример: программа обработки транзакций
будет представлена довольно сложная программа обработки
обрабатывающая файлы произвольного доступа. Она предназначена
этом раз/еле
транзакций,
Работа
для
с
509
файлами
работы
банковскими
с
счетами.
Программа
щие счета, добавлять новые, уничтожать
счетов в
была
файле
обновлять существую¬
сохранять
список всех текущих
для вывода на печать.
формата
текстового
позволяет
счета и
Предполагается,
выполнена программа на рис.
11.11, создающая файл credit.dat.
/*
файла
Последовательное
чтение
с
произввольным
доступом
что
*/
#include <stdio.h>
struct clientData {
int acctNum;
char lastName[15];
char firstName[10];
float balance;
};
main()
{
FILE *cfPtr;
struct clientData client;
if
((cfPtr
fopen("credit.dat", "r"))
printf("File could not be opened.\n");
else {
printf("%-6s%-16s%-lls%10s\n", "Acct",
"First Name", "Balance");
while
(!feof(cfPtr)) {
sizeof(struct
fread(&client,
if
NULL)
==
=
(client.acctNum
"Last Name ,
clientData),
1,
cfPtr);
!=0)
printf("%-6d%-16s%-lls%10.2f\n",
client.acctNum, client.lastName,
client.firstName, client.balance);
}
}
fclose(cfPtr);
return
0;
Acct
29
Last Name
Brown
33
Dunn
37
88
96
Barkey
Smith
Stone
First Name
Nancy
Stacey
Balance
-24.54
314.33
0.00
258.34
34.98
Doug
Dave
Sam
Рис. 11.15. Последовательное считывание
из
файла произвольного доступа
Программа имеет пять опций. Опция 1 вызывает функцию textFile для со¬
хранения отформатированного списка всех счетов в текстовом файле, с име¬
нем accounts.txt, который позднее можйо вывести на печать. Функция испо¬
льзует
fread
и метод последовательного доступа к
зован ранее в программе,
представленной
на рис.
файлу, который был
исполь¬
11.15. После выбора опции 1
создается файл accounts.txt, содержащий следующий
текст:
Глава 11
510
Balance
Acct
Last Name
First Name
29
Brown
Nancy
-24.54
33
Dunn
Stacey
314.33
37
88
Barkey
Smith
Doug
Dave
258.34
96
Stone
Sam
Опция 2
0.00
34.98
для обновления счета.
функцию updateRecord
вызывает
ция предназначена для обновления уже существующих
чала она проверяет,
Запись
считывается в структуру
пустой.
функции fread, после чего
0, то запись не содержит ни¬
client с
0. Если
помощью
он равен
acctNum сравнивается с
какой информации, и выдается сообщение, что запись
вновь появляется
функция updateRecord
меню
выбора. Если
пустая.
account
Barker
Enter charge
37
Barker
Опция 3
та.
to
update (1
Doug
(+)
вызывает
чего на
информацию,
вводит сумму транзакции, вычисляет новый баланс и
нения опции 2 выглядит следующим
37
После
запись содержит
файле. Типичный
образом:
переписывает соответствующую запись в
Enter
сна¬
не является ли указанная пользователем запись
элемент
экране
Функ¬
записей, поэтому
or
37
100):
-
результат испол¬
0.00
(-):
payment
Doug
+87.99
87.99
функцию newRecord
для
добавления
в
файл
нового сче¬
Если пользователь вводит уже существующий номер счета, newRecord
дает
сообщение об ошибке,
информацию,
выбора. Эта функция использует
что данная запись уже содержит
сле чего на экран вновь выводится меню
вы¬
по¬
тот
же процесс добавления нового счета, что и программа на рис. 11.12. Типичный
результат исполнения опции 3 выглядит следующим образом:
Enter
22
new account number (1
100):
Enter lastname, firstname, balance
? Johnson Sarah 247.45
-
Опция 4
функцию deleteRecord
вызывает
для удаления записи из
файла.
Пользователя
просят ввести номер счета, который необходимо удалить, после
чего выполняется повторная инициализация записи. Если счет не содержит
информации, deleteRecord выдает
не существует.
Опция 5 прерывает
граммы представлен на рис.
обновления
/*
*
(чтения
и
сообщение об ошибке,
со
Программа последовательно
обновляет
данные,
ранее
*
данные
для
помещения
*
уже
имеющиеся
в
Полный
файл "credit.dat"
спецификацией режима "r+".
11.16. Заметим,
записи)
что указанного счета
выполнение программы.
читает
что
файл произвольного доступа,
записанные
файл,
и
в
файл,
создает
новые
данные,
*/
#include <stdio.h>
struct
mt
clientData
{
acctNum;
char
lastName[15];
char
firstName[10];
float
balance;
};
Рис. 11.16.
Программа работы
*
*
*
удаляет
файле.
в
текст про¬
открыт для
с банковскими счетами
(часть 1
из
4)
Работа
с
511
файлами
int enterChoice(void);
void textFile(FILE *);
void updateRecord(FILE
*);
void newRecord(FILE *);
void deleteRecord(FILE *);
main ()
FILE *cfPtr;
int choice;
if
((cfPtr
fopen("credit.dat", "r+"))
printf("File could not be opened.\n");
else {
==
=
while
((choice
switch
=
enterChoice())
(choice)
case
!= 5)
NULL)
{
{
1:
textFile(cfPtr);
break;
case
2:
updateRecord(cfPtr);
break;
case
3:
newRecord(cfPtr);
break;
case
4:
deleteRecord(cfPtr);
break;
}
}
}
fclose(cfPtr);
return 0;
void textFile(FILE *readPtr)
{
FILE *writePtr;
struct clientData client;
if
((writePtr
=
fopen("accounts.txt",
"w"))
NULL)
==
printf("File could not be opened.\n");
else {
rewind(readPtr);
fprintf(writePtr, "%-6s%-16s%-lls%10s\n",
"Acct", "Last Name", "First Name",
while
(!feof(readPtr)) {
sizeof(struct
fread(&client,
"Balance");
clientData),
1,
readPtr);
Рис. 11.16. Программа работы с банковскими счетами (часть 2 из 4)
512
Глава *1
if
(client.acctNum
!=0)
fprintf(writePtr, "%-6d%-16s%-lls%10.2f\n",
client.acctNum, client.lastName,
client.firstName, client.balance);
}
}
fclose(writePtr);
}
void updateRecord(FILE *fPtr)
{
int account;
float transaction;
struct clientData client;
printf("Enter account to update
scanf("%d",
fseek(fPtr,
&account);
(account
-
*
1)
(1
-
100):
sizeof(struct
clientData),
SEEK_SET);
fread(&client, sizeof(struct clientData),
if
");
1,
fPtr);
(client.acctNum
0)
printf("Acount #%d has
==
no
information.\n",account);
{
printf("%-6d%-16s%-lls%10.2f\n\n",
client.acctNum, client.lastName,
client.firstName, client.balance);
printf("Enter chrge ( + ) or payment(-): ");
scanf("%f", &transaction);
client.balance += transaction;
else
printf("%-6d%-16s%-lls%10.2f\n",
client.acctNum, client.lastName,
client.firstName, client.balance);
fseek(fPtr,
(account-1) * sizeof(struct clientData),
SEEK_SET);
fwnte(&client, sizeof(struct
clientData),
1,
fPtr);
}
void deleteRecord(FILE *fPtr)
{
struct clientData client, blankClient
=
{0,
"",
"",
0};
int accountNum;
printf("Enter account number to delete (1-100): ");
scanf("%d", &accountNum);
*
fseek(fPtr, (accountNum
1)
sizeof(struct clientData),
-
SEEK_SET);
fread(&client, sizeof(struct clientData),
if
1,
fPtr);
(client.acctNum
0)
printf("Accout %d does not exist.\n", accountNum);
else {
fseek(fPtr, (accountNum
1) * sizeof(struct clientData),
==
-
SEEK_SET);
Рис. 11.16. Программа работы
с
банковскими
счетами
(часть 3
из
4)
Работа
с
513
файлами
fwnte(&blankClient,
sizeof(struct
clientData),
1,
fPtr);
}
void newRecord(FILE
*fPtr)
{
struct
int
clientData
client;
accountNum;
printf("Enter
scanf("%d",
fseek(fPtr,
new
account
number
&accountNum);
(accountNum -1)
SEEK_SET);
fread(&client, sizeof(struct
*
(1-100):
");
sizeof(struct
clientData),
clientData),
1,
fPtr);
if
(client.acctNum !=0)
pnntf("Account #%d already contains information.\n",
client.acctNum);
else {
pnntf ("Enter lastname, firstname, balance\n? ");
scanf("%s%s%f", &client.lastName,
&client.balance);
client.acctNum
accountNum;
&client.firstName,
=
fseek(fPtr,
(client.acctNum
-1)
*
sizeof(struct clientData), SEEK_SET);
fwnte(&client, sizeof (struct clientData), 1,
int
fPtr);
enterChoice(void)
{
int menuChoice;
pnntf("\nEnter your choice\n"
"1
store a formatted text
-
"
"2
"3
"4
"5
-
-
-
-
scanf("%d",
return
file
of
accounts
called\n"
\"accounts.txt\" for printmg\n"
update an account\n"
add
a
new
account\n"
an
account\n"
delete
program\n? ");
&menuChoice);
end
menuChoice;
Рис. 11.16.
Программа работы
с
банковскими
счетами
(часть 4
из
4)
Резюме
Все
элементы данных,
обрабатываемые
компьютером, сводятся к ком¬
бинации нолей и единиц.
Значение
ся
наименьшего элемента данных, к которому может
компьютер,
равно
(по-английски bit
0
или
сокращение от
принимать одно из двух
17
Зак 801
1. Этот
значений).
элемент
данных
«frmary digit»
обращать¬
называется
бит
число, способное
514
Глава 11
Цифры, буквы
и специальные знаки называют символами.
Множество
всех символов, которые можно использовать для написания программ
и представления элементов данных на конкретном компьютере, назы¬
вается
набором
его
Каждый
нулей
символов.
комбинацией
компьютере
символ
из восьми
представляется
и единиц
в
(называемой бай¬
том).
Полем называется группа символов, передающая значение.
Записью
называется
связанных
группа
собой полей.
между
По крайней мере одно поле в каждой записи обычно выбирается в каче¬
стве ключа записи. Ключ записи идентифицирует запись, как принад¬
лежащую конкретному лицу или
Наиболее популярный
записей
тип организации
файлом;
следовательным
объекту.
в
нем
в
файле
называется по¬
осуществляется поочередный доступ к
записям, пока не будут найдены
требуемые
данные.
связанных между собой файлов иногда называют базой данных.
Набор программ, разработанных для создания и управления базами
данных, называется системой управления базами данных (DBMS).
Группу
Язык С рассматривает любой файл
как
последовательный
поток
байтов.
В начале исполнения программы С автоматически открывает три файла
и
связанные
с
ними
потоки:
стандартный
стандартный
ввод,
вывод и
стандартный файл ошибок.
Стандартному
бок
вводу, стандартному выводу и стандартному
присваиваются
соответственно
файлу оши¬
файлов stdin, stdout и
указатели
stderr.
Функция fgetc
Функция fputc
считывает
символ
записывает
Функция fgets
считывает
Функция fputs
записывает
FILE является структурой,
ле
из
строку
из
строку
тип
указанного
файла.
указанный файл.
в
символ
указанного
файла.
указанный файл.
в
которой
определен в заголовочном
stdio.h. Программисту, чтобы работать
сти знать, как устроена эта структура.
с
При
файлами,
открытии
нет
фай¬
необходимо¬
файла
структура
FILE возвращает указатель на файл.
Функция fopen
ром должен
принимает два аргумента: имя
быть открыт файл,
и открывает
файла и режим, в кото¬
файл. Если файл, открыва¬
емый для записи, уже существует, его содержимое уничтожается без
предупреждения. Если файла не существовало и файл открывается для
fopen создает файл.
Функция feof определяет, был
записи,
Функция fprintf
аналог
ли установлен индикатор конца
printf
лучает в качестве аргумента еще и указатель на
записаны
файла.
fprintf по¬
который будут
за исключением того, что
файл,
в
данные.
Функция fclose
ей аргумент.
закрывает
файл,
на
который
указывает
передаваемый
Работа
с
515
файлами
Чтобы создать файл
или
уничтожить
файла,
содержимое
откройте файл
данные были записаны ранее,
в
который
("w").
чтения ("r").
для записи
Чтобы
Чтобы
прочитать существующий файл, откройте файл для
добавить записи в конец существующего файла, откройте файл для до¬
бавления ("а"). Чтобы открыть файл так, чтобы с ним можно было вы¬
полнять
операции
одном из режимов
файл для
чтения и записи.
пись
и
чтения,
или
конец
для обновления в
Режим "w+" создает файл,
Режим "a+" создает файл,
в
откройте файл
"a+". Режим "r+" просто открывает
и стирает текущее содержание
ствовал ранее,
чае.
записи
"r+", "w+"
файла
если он не суще¬
в
противном
файла.
Функция fscanf
аналог
scanf,
за исключением того, что
чает в качестве дополнительного аргумента указатель на
отличный
от
stdin),
из
которого
будут
Выполнение функции rewind приводит
ции
в
начало
слу¬
если он не существует, и производит за¬
указанного
считываться
fscanf полу¬
файл (обычно
данные.
к перемещению указателя пози¬
файла.
произвольного доступа к файлу используется для прямого до¬
ступа к записям.
Процесс
Для упрощения произвольного доступа данные хранятся в записях
фиксированной длины. Так как все записи имеют одинаковую длину,
компьютер может быстро вычислять (как функцию ключа записи) точ¬
ное положение записи относительно начала файла.
В файл произвольного доступа новые данные можно легко добавить без
разрушения уже хранящихся там данных. Данные, хранящиеся в фай¬
ле
с
фиксированной длиной
записи,
удалять без переписывания всего
Функция fwrite
Исполняемая
операнда
в
записывает
можно
также
изменять
или
файла.
блок (заданное
число
во время компиляции операция
байт)
данных в
файл.
sizeof возвращает размер
байтах.
Функция fseek
устанавливает указатель позиции файла в заданную по¬
зицию, исходя из позиции, от которой начинается поиск. Поиск начи¬
одной из трех позиций
SEEK_SET начинает поиск с начала
файла, SEEK_CUR с текущей позиции, а SEEK_END с конца файла.
нается с
Функция fread
считывает
блок (заданное
число
байт)
данных из
Терминология
fclose
fwrite
feof
rewiad
fgetc
SEEK_CUR
fgets
SEEK_END
fopen
fprintf
SEEK_SET
stderr (стандартная ошибка)
stdin (стандартный ввод)
stdout (стандартный вывод)
fputc
fputs
fread
fscanf
fseek
17*
алфавитный порядок
база данных
байт
файла.
516
Глава 11
бит
цифра
десятичная цифра
закрытие файла
режим открытия файла
режим открытия файла
режим открытия файла
режим открытия файла
режим открытия файла
режим открытия файла
режим открытия файла
запись
символ
буква
поле
буквенное
буквенно-цифровое
поле
двоичная
иерархия
конца
файла
w+
данных
структура FILE
файла
набор
символов
и
r+
w
смещение
записи
конец
нули
г
система управления базами
имя
ключ
a+
символьное поле
данных
файла
индикатор
а
файла
указатель файла
файл
файл последовательного доступа
файл произвольного доступа
форматированный ввод/вывод
указатель позиции
единицы
открытие
файла
параметр
числа
записей
поле
поток
целое число
произвольный доступ
численное поле
Распространенные ошибки программирования
11.1.
Открытие ранее созданного файла для
("w"),
записи
тогда как на
самом деде пользователь хочет сохранить дынные, находящиеся в
файле; содержимое файла
11.2.
Забывают открыть
файл,
уничтожается без предупреждения.
перед тем как ссылаться на него в про¬
грамме.
11.3.
Ссылаясь на файл, используют
11.4. Открытие
для чтения несуществующего
11.5. Открытие файла
11.6. Открытие файла
файла.^
указатель
файла.
для чтения или записи без соответствующих при¬
вилегий доступа к
диске.
неверный
файлу (это
зависит от
операционной системы).
для записи при отсутствии
На рис. 11.6 перечислены режимы,
в
свободного
которых
места
могут
на
быть
открыты файлы.
11.7.
Неверный выбор режима открытия файла может привести к разру¬
шительным ошибкам. Например, если открыть файл в режиме за¬
писи
("w")
вместо
режима
обновления
("r+"),
то
это
приведет
к
полной потере содержимого файла.
Хороший
11.1.
стиль
программирования
Убедитесь,
что вызовы
корректные указатели
11.2. Явно
ма
с
файлами,
файл, как только выяснится,
будет обращаться к файлу.
закрывать каждый
больше
не
11.3. Открывайте файлы
ния),
функций, работающих
файлов.
только
если записанные в
для
файле
чтения
(без
содержат
что програм¬
возможности
обновле¬
данные не должны изменяться. Это
позволит избежать неумышленного изменения содержимого
Это
еще один пример применения правила минимума
файла.
привилегий.
Работа
с
517
файлами
Советы по повышению
11.1.
освобождаем ресурсы, которые,
мы
Закрывая файл,
возможно, тре¬
другим пользователям или программам.
буются
11.2.
эффективности
Многие программисты ошибочно думают, что sizeof является фун¬
и что использование ее приводит к увеличению времени ис¬
кцией,
полнения программы,
какого
увеличения
не
происходит,
операцией, выполняемой
является
функции. Однако
связанному с вызовом
времени
во
ни¬
sizeof
поскольку
компиляции.
время
Советы по переносимости программ
11.1.
Структура FILE
операционной
зависит от типа
системы
(т.
e.
эле¬
менты структуры меняются в зависимости от того, как каждая сис¬
обращается
тема
Упражнения
11.1.
своими
пробелы
в
из
каждом
следующих
конечном счете все элементы данных,
ютером,
сводятся
b) Наименьший
элемент данных,
это
e) Группа
и
который
группа
связанных
собой файлов
называется
закрывает
файл.
считывает
данные
из
символ
из
считывает
i) Функция
j) Функция
считывает
строку
открывает
файл.
произвольным
заданное
файла,
так
как
же
из
файла.
заданного
заданного
обычно применяется для
___
1) Функция
в
.
stdin.
из
h) Функция
ла
компь¬
собой записей.
между
g) Оператор
k) Функция
из файлов с
обработать
и специальные знаки в совокупности называются
связанных между
считывает
.
может
f) Функция
scanf
компь¬
.
c)
d) Цифры, буквы
предложений.
обрабатываемых
комбинации
к
называется
ютер,
11.2.
файлами).
для самоконтроля
Заполните
a) В
со
файла.
чтения
данных
доступом
переустанавливает указатель позиции
фай¬
положение.
Установите, верны
или неверны следующие утверждения
объясните, почему):
a) Функцию fscanf нельзя использовать
(если
не¬
верны,
дартного
b) Программист
токов
для чтения данных со стан¬
ввода.
должен явно использовать
стандартного
ввода,
стандартного
fopen
для открытия по¬
и стандартного
вывода
файла ошибок.
c) Программа должна
бы закрыть файл.
явно вызывать
функцию fclose
для того, что¬
518
Глава 11
d)
Если указатель позиции файла установлен
в
файле
с последова¬
тельным доступом на положение, отличное от его начала, то,
файла,
начать чтение с начала
его
необходимо закрыть
чтобы
и вновь от¬
крыть.
e) Функция fprintf
f) Данные
в
файле
может
писать
стандартный
в
вывод.
последовательного доступа всегда обновляются
без переписывания остальных данных.
g) В файле
произвольного доступа нет необходимости просматри¬
вать все записи для того, чтобы найти определенную запись.
h) Записи
файлах произвольного доступа имеют разную длину.
i) Функция fseek может производить поиск только от начала фай¬
в
ла.
11.3.
Напишите одиночные операторы С, выполняющие каждое
действий. Предполагается, что
в одной и той же программе.
дующих
зуются
a) Напишите
из сле¬
все эти операторы исполь¬
который открывает файл "oldmast.dat" на
присваивает возвращаемый указатель файла ofPtr.
b) Напишите оператор, который открывает файл "trans.dat" на
чтение и присваивает возвращаемый указатель файла tfPtr.
c) Напишите оператор, который открывает файл "newmast.dat" на
запись (и создание) и присваивает возвращаемый указатель файла
чтение
оператор,
и
nfPtr.
d) Напишите
оператор, считывающий запись из "oldmast.dat". За¬
пись состоит из целого числа accountNum, строки name и числа с
плавающей точкой currentBalance.
e) Напишите оператор, считывающий
состоит из целого числа
accountNum
запись из
и числа с
"trans.dat". Запись
плавающей запятой
dollarAmount.
f) Напишите оператор, производящий запись в "newmast.dat". За¬
пись состоит из целого числа accountNum, строки name и числа с
плавающей запятой currentBalance.
11.4.
Найдите ошибку в каждом из следующих фрагментов программы.
Объясните, как можно эту ошибку исправить.
a) Файл,
на
который
ссылается
fPtr("payables.dat"),
не
был
от¬
крыт.
fpnntf(fPtr,
"%d%s%d\n",
b) open("receive.dat",
account,
company,
amount);
"r+");
считывать запись из файла "payables.dat". Указатель файла payPtr ссылается на этот файл, а указа¬
тель файла recPtr ссылается няГ файл "receive.dat".
fscanf(recPtr, "%d%s%d\n", &account, company, &amount);
c) Следующий оператор должен
d) Файл "tools.dat" должен быть открыт для добавления
файл без уничтожения уже существующих данных.
if
((tfPtr
=
fopen("tools.dat",
"w"))
!= NULL)
данных в
Работа
с
519
файлами
e) Файл "courses.dat" необходимо открыть
менения текущего содержимого файла.
if
Ответы
11.1.
на
=
((cfPtr
fopen("courses.dat",
для добавления без из¬
"w+"))
!= NULL)
упражнения для самоконтроля
а) нулей, единиц b) бит, с) файл, d) символами, e) базой данных, f)
fclose, g) fscanf, h) gets или fgetc, i) fgets, j) fopen, k) fread, 1) fse¬
ek.
11.2.
а) Неверно. Функцию fscanf
стандартного ввода,
ввода
стандартного
b) Неверно. Эти три
ко
начинает
можно
использовать
используя в вызове
чтения
для
fscanf указатель
со
на поток
stdin.
потока открываются автоматически, как толь¬
исполняться
программа.
c) Неверно. Файлы будут
закрыты при завершении выполнения
программы, однако рекомендуется явно закрывать все файлы с по¬
мощью fclose.
d) Неверно. Для перемещения указателя позиции файла
файла можно использовать функцию rewind.
на начало
e) Верно.
f) Неверно. В большинстве случаев
файлах
Следовательно,
записи в
ного доступа имеют разную длину.
последователь¬
возможно, что
такое обновление записи приведет к тому, что часть других записей
будет
переписана.
g) Верно.
h) Неверно. Записи
в
файлах
произвольного доступа обычно имеют
одинаковую длину.
i) Неверно. Поиск возможен с начала файла, с конца файла и с те¬
кущей позиции в файле, определяемой значением указателя пози¬
ции файла.
11.3. а)
ofPtr
b) tfPtr
c) nfPtr
=
fopen("oldmast.dat",
=
fopen("trans.dat",
=
fopen("newmast.dat",
"r");
"r");
"w");
d) fscanf(ofPtr, "%d%s%f", &accountNum,
¤tBalance);
11.4.
e)
fscanf(tfPtr,
f)
fprintf(nfPtr, "%d%s%.2f",
currentBalance);
а) Ошибка: файл
сослались
при
"%d%f",
&accountNum,
"payables.dat"
помощи
name,
&dollarAmount);
accountNum,
не
указателя
name,
был открыт прежде,
чем на него
файла.
Исправление: вызвать fopen для открытия "payables.dat"
записи, добавления или обновления.
b) Ошибка:
вызывается
Исправление:
функция open
использовать
вместо
функцию fopen.
fopen.
в режиме
520
Глава 11
c) Ошибка:
оператор fscanf использует
при ссылке на файл "payables.dat".
Исправление:
использовать указатель
неверный
указатель
файла
при ссылках на
payPtr
"pay¬
ables.dat".
d)
Ошибка: содержимое файла будет уничтожено, поскольку файл
открыт для
("w").
записи
Исправление:
для того чтобы
добавить
("r+")
крыть его для обновления
или
данные в
файл, следует
добавления ("а").
для
e) Ошибка: файл "courses.dat" открыт для обновления в
"w+", который уничтожает текущее содержимое файла.
Исправление:
файл
открыть
в
режиме
от¬
режиме
"а".
Упражнения
11.5.
Заполните пробелы
в
каждом
из
следующих
утверждений:
a) Компьютеры сохраняют
вторичной памяти, таких
большие объемы данных на устройствах
b)
нескольких
состоит
c) Поле,
дой
буквы
и
пробелы,
назы¬
полем.
того
чтобы облегчить
записи
e) Основная
темах,
.
полей.
которое может содержать цифры,
вается
d) Для
как:
из
выбирается
масса
содержится
f) Группа
в
поиск
записей
в
файле,
качестве
одно поле каж¬
.
информации, хранящейся
в компьютерных сис¬
файлах.
в
связанных символов,
называется
передающая значение,
g) Указатели файла
для трех файлов, которые автоматически от¬
крываются С, как только программа начинает исполняться, назы¬
ваются:
и
.
,
h) Функция
записывает
i) Функция
j) Функция
ных в файл
записывает
в
основном
в
символ
строку
в
указанный файл.
указанный файл.
используется
для
записи
дан¬
произвольного доступа.
k) Функция
перемещает указатель позиции в начало
файла.
11.6. Установите, какие из нижеследующих утверждений верны,
кие нет (если утверждение неверно, объясните, почему):
a) Все,
а ка¬
даже самые сложные задачи, которые способен выполнять
по существу представляют собой обработку нулей и
компьютер,
единиц.
b) Люди
предпочитают иметь дело с битами вместо символов и по¬
лей, потому что биты более компактны.
c) Люди
записывают программы и данные символами; компьютеры в
дальнейшем обрабатывают
d) Почтовый
индекс
эти символы как
наборы нулей
пример числового поля.
и единиц.
Работа
с
521
файлами
e) Почтовый
адрес в компьютерных приложениях обычно рассмат¬
как
буквенное поле.
ривается
f) Элементы обрабатываемых компьютером данных образуют
иерархию, в которой элементы данных становятся больше по раз¬
меру и сложнее по структуре по мере продвижения от
от
волам,
g) Ключ
символов
записи
к
битам
определяет
и
так
полей
к сим¬
далее.
запись
как
принадлежащую
особому
полю.
h) Большинство организаций хранят свою информацию в виде
ного файла для упрощения ее компьютерной обработки.
i)
еди¬
В программах на С ссылки на файл всегда осуществляются
по
имени.
j) Когда
программа создает
компьютером
11.7.
для
файл,
дальнейших
он
автоматически
сохраняется
ссылок.
В упражнении 11.3 мы просили
операторов. В действительности
читателя написать ряд отдельных
эти операторы
образует
ядро важ¬
файлов. В
программах обработки данных для коммерческой сферы обычным
является использование нескольких файлов. Например, в програм¬
ме, предназначенной для работы со счетами клиентов, имеется так
ного типа программ, а именно программ сопоставления
называемый главный файл, содержащий подробную информацию
каждом заказчике,
такую как имя заказчика,
его адрес,
о
номер те¬
задолженность, лимит кредита, условия скидки, условия
договора, и, кроме того, краткая сводка последних заказов и по¬
ступления платежей.
лефона,
Если произошла транзакция
денежный перевод), она
го цикла (то есть месяца
в некоторых случаях
(то
есть совершена покупка и получен
вносится в
для одних
файл. В
конце каждого делово¬
компаний,
дня) файл транзакций (в
недели для других, а
упражнении 11.3 он
"trans.dat") вводится в главный файл (в упражнении 11.3
"oldmast.dat") и, таким образом, производится обновление записей
назван
заказов
и
платежей. После
ный файл переписывается
рый
того как все
как
обновления сделаны,
новый файл
("newmast.dat"),
затем используется в конце следующего делового цикла,
опять
произвести
процесс
глав¬
кото¬
чтобы
обновления.
сопоставления файлов неизбежно сталкивается с неко¬
проблемами, которых не возникает в программах, работаю¬
щих с одним файлом. Например, сопоставление записей возможно
не всегда. В главном файле содержится запись о заказчике, но за
текущий деловой период он не делал никаких покупок или плате¬
жей и, следовательно, записи в файле транзакций для этого заказ¬
чика отсутствуют. А возможна и обратная ситуация, когда заказ¬
Программа
торыми
чик
совершил несколько покупок или денежных платежей, но
сделал это впервые и вполне возможно, что в компании на данный
момент запись в главном файле для этого заказчика отсутствует.
Используя
нии
11.3,
в
в
основы
операторы,
написанные
в
упражне¬
обработки транзакций
с со¬
файлов. Для целей сопоставления используйте номер
каждом файле как ключ. Исходите из того, что каждый
поставлением
счета
качестве
напишите полную программу
Глава 11
522
файл
является
последовательного доступа с записями, хра¬
файлом
нящимися в порядке возрастания номера счета.
Когда
записей, которые
имеется пара
(т.е. имеют¬
файле и файле
файла транзакций к те¬
можно сопоставить
ся записи с одним и тем же номером счета в главном
транзакций), прибавьте сумму в долларах из
кущему балансу главного файла и поместите соответствующую за¬
пись в файл "newmast.dat". (Предположим, что в файле транзакций
заказам соответствуют положительные денежные суммы,
а получен¬
отрицательные.) Когда для конкретного счета име¬
ется главная запись, но не существует соответствующей записи в фай¬
ле транзакций,
то главная запись просто переносится в файл
"newmast.dat". Когда есть запись в файле транзакций, но нет соот¬
ветствующей главной записи, программа должна выдавать сообщение
"Unmatched transaction record for account number ..." (на месте мно¬
готочия должен стоять номер счета из записи файла транзакций).
ным платежам
11.8. Напишите
простую программу для создания тестовых данных, по¬
работу программы из упражнения 11.7. Ис¬
зволяющих проверить
пользуйте следующий образец
Главный
Номер
данных:
файл:
счета
Имя
Баланс
100
Alan
Jones
348 17
300
Магу Smith
2719
500
Sam Sharp
0 00
700
Suzy Green
-14 22
Файл транзакций:
Номер
11.9.
Сумма
счета
100
2714
300
62 11
400
100 56
900
82 17
Запустите программу упражнения 11.7, используя файлы тестовых
данных, созданные в упражнении 11.8. Запустите программу из
раздела
но
11.7, чтобы распечатать новый главный файл. Вниматель¬
проверьте
результаты.
И.Ю.Может случиться (и
редкость), что некоторые записи
одинаковый ключ. Это происходит по той
простой причине, что конкретный заказчик может сделать несколько
покупок и платежей за деловой период. Перепишите вашу программу
с сопоставлением файлов из упражнения 11.7, чтобы предусмотреть
транзакций будут
обработки
возможность
вым
ключом.
включить
в
такие случаи не
иметь
Измените
них
нескольких
тестовые
следующие
записей транзакций
данные
упражнения
дополнительные
записи
с
одинако¬
11.8, чтобы
транзакций:
Работа
с
523
файлами
Номер
Сумма
счета
300
83 89
700
80 78
700
1 53
11.11.Напишите операторы, которые
вия.
Предполагается,
что
выполняют указанные ниже
дейст¬
была определена структура
struct person {
char lastName[15];
char firstName[15];
char age[2];
};
и
что
файл
уже открыт для записи.
a) Инициализируйте файл "nameage.dat"
так,
чтобы в нем было
"
100
age
записей
=
с
lastName
=
"unassigned",
=
',
и
"0".
b) Введите 10 фамилий,
c) Обновите
имен и возрастов и запишите их в
запись; если в записи нет
файл.
информации, сообщите
поль¬
"No info".
зователю
d) Удалите содержащую информацию запись,
ровав
firstName
заново инициализи¬
ее.
11.12.Bbi владелец
ризацию,
магазина инструментов и должны провести
в результате
которой необходимо
инвента¬
выяснить, какие инст¬
рументы вы имеете, в каком количестве, и стоимость каждого ин¬
Напишите программу,
которая инициализирует файл
пустых записей, дает возмож¬
ность ввести данные, относящиеся к каждому инструменту, про¬
смотреть список всех имеющихся инструментов, удалить запись,
струмента.
"hardware.dat" содержащий 100
относящуюся к товару,
обновить
ром для
который закончился, и которая позволяет
любую информацию в файле. Идентификационным номе¬
инструмента должен служить номер записи. Используйте
следующую информацию для того, чтобы начать файл:
Запись №
Название инструмента
Количество
Цена
3
Electric sander
7
57 98
17
Hammer
16
11 99
24
Jig
21
11 00
39
Lawn
3
79 50
56
Power saw
18
99 99
68
Screwdriver
106
6 99
77
Sledge hammer
11
21 50
83
Wrench
34
7 50
saw
mower
Глава 11
524
для телефонного номера. Стандартный набор
телефона содержит цифры от 0 до 9. Каждая цифра от 2 до
связанные с ней три буквы, что отражено в следующей таб¬
11.13.Генератор
кнопок
9
имеет
слова
лице:
Цифра
Буква
2
А
В
С
3
D
E
F
4
G
H
I
5
J
К
L
б
M N
0
7
P
R
S
8
T
U
V
9
X
Y
Z
Многие люди с трудом запоминают номера
используют
брать
соответствие
букв,
слово из семи
номеру.
Например,
воспользоваться
венное
слово
ков
они
подо¬
телефонный номер которого 686-2377,
подобной таблицей и подобрать семибук¬
часто пытаются получить номер
рекламе
простое
несколько
телефона,
кото¬
Если предприниматель
слово,
по
которому клиенты
звонить в его контору, тогда, вне всяких
будет
Каждое
буквами, чтобы
которое соответствовало бы телефонному
легко запомнить их клиентам.
сможет поместить в
бы
телефонов, поэтому
и
«NUMBERS».
Предприниматели
могли
цифрами
человек,
может
рый было бы
между
сомнений,
звон¬
больше.
букв соотЕетствует ровно одному телефонно¬
Ресторан, желающий увеличить количество заказов на
безусловно сможет сделать это, если его номер 825-3688 (т.е.
слово из семи
му номеру.
дом,
«TAKEOUT»).
Каждому
семи
из семизначных
номеров соответствует множество слов из
букв. К
сожалению, большинство из них представляет собой
бессмысленные комбинации букв. Возможно, однако, что владелец
парикмахерской был бы
приятно удивлен, узнав, что его телефон
«HAIRCUT». Владелец магазина, торгую¬
обрадуется, обнаружив, что телефон магазина
424-7288 соответствует
щего алкоголем,
233-7226 соответствует
мер которого
вует
слову
ла записывает в
файл
11.14.Если
на
С, которая
для данного семизначного чис¬
Существует 2187 (три
Избегайте телефонных номеров
на вашем компьютере имеется
цируйте написанную
было
но¬
что этот номер соответст¬
все возможные слова из семи
вующие этому номеру.
слов.
Ветеринар, телефонный
«PETCARE».
Напишите программу
ких
«BEERCAN».
738-2273, будет рад узнать,
в упражнении
искать слова в словаре.
с
букв,
соответст¬
седьмой степени)
цифрами 0 и 1.
в
та¬
электронный словарь, модифи¬
11.13 программу, чтобы
Некоторые комбинации
можно
из семи
букв
Работа
с
525
файлами
могут состоять из двух или более слов
(телефонный
номер 843-2677
«THEBOSS»).
соответствует
11.15.Измените пример из рис. 8.14, использовав функции fgetc и fputs
вместо getchar и puts. Программа должна давать пользователю воз¬
можность выбирать опции либо чтения со стандартного ввода и за¬
писи в стандартный вывод, либо чтения из заданного файла и запи¬
си в заданный файл. Если пользователь выбирает второй вариант,
то должен ввести имена для входного и выходного файлов.
11.16.Напишите программу, применяющую операцию sizeof для опреде¬
ления размеров в байтах различных типов данных на вашей систе¬
ме.
файл "datasize.dat , чтобы иметь воз¬
распечатать.
Формат файла должен быть
Запишите результаты
можность
позже
их
в
следующим:
Data
Size
type
char
1
unsigned char
short int
2
1
unsigned short int
int
2
unsigned int
long int
4
unsigned long int
float
4
4
4
4
double
8
long double
16
Замечание: размер данных для вашего компьютера может отлича¬
ться от значений, показанных на рисунке.
11.17.B
упражнении 7.19 вы написали программу, моделирующую
компьютер, воспринимающий специальный язык, названный Ма¬
шинным языком Симплетрона (SML). Каждый раз, когда вы хоте¬
ли запустить
SML-программу,
Если
ее приходилось вводить в симулятор
ошибку, симуля¬
тор перезапускался и код SML приходилось вводить заново. Было
бы замечательно, если бы существовала возможность считывать
программы из файла, а не набирать их каждый раз заново. Это по¬
с клавиатуры.
зволит
сэкономить
вы допускали во время ввода
время
и
необходимые
силы,
для запуска про¬
грамм SML.
a) Модифицируйте симулятор, который вы написали в упражне¬
нии 7.19, чтобы можно было считывать программы из файла, имя
пользователем
которого
вводится
b) После
выполнения
с
клавиатуры.
программы содержимое регистров
бы
и
памяти
сбросить эту ин¬
поэтому измените симулятор так, чтобы парал¬
лельно с выводом на экран он записывал копию данных в файл.
симулятора выводятся на экран. Было
формацию
в
файл,
неплохо
^
Scan AAW
г
л
а
в
а
12
Структуры
данных
Цели
Научиться
мять
для
динамически выделять и
освобождать
па¬
структур данных.
Научиться организовывать связанные структуры дан¬
указателей, структур, ссылающихся на
себя, и рекурсии.
ных с помощью
Познакомиться
ных списков,
Познакомиться
связанных
с созданием и использованием связан¬
очередей,
с
стеков и двоичных
деревьев.
важнейшими случаями применения
структур данных.
Глава 12
528
Содержание
12.1. Введение
12.2.Структуры,
ссылающиеся на себя
12.3.Динамическое распределение памяти
12.4.Связанные списки
12.5. Стеки
12.6.
Очереди
12.7.
Деревья
Резюме
Распространенные
программирования
Советы
реносимости программ
нения
ошибки программирования
по повышению
Хороший
стиль
Советы по пе¬
Ответы на упраж¬
эффективности
Упражнения для самоконтроля
Упражнения
для самоконтроля
12.1.
Введение
Мы уже изучали ранее структуры данных фиксированного размера, та¬
В этой главе
кие, как одномерные массивы, двумерные массивы и структуры.
будут рассмотрены динамические структуры данных, размер которых,
увеличиваться или уменьшаться при исполнении программы.
может
Связанный
спи¬
«выстроенных в ряд» элементов данных, в любом месте кото¬
рого можно производить вставки и удаления. Стеки, играющие важную роль
в компьютерах и операционных системах, допускают выполнение вставки и
сок
это
набор
удаления только с одного конца стека
сверху. Очереди представляют собой
линии ожидания; вставки производятся в конец (иначе называемый хвостом)
в начале (называемом еще головой) очереди. Двоичные
очереди, а удаление
облегчают
скоростной поиск и сортировку данных, эффективное
повторяющихся элементов данных, представление файловой
структуры каталогов и компиляцию выражений в машинный язык. Каждая
деревья
устранение
из этих структур данных имеет множество других интересных
Мы обсудим каждый
из
основных
типов
структур данных
применений.
и
рассмотрим
работают с ними. В следующей
части книги
введении в С++ и объектно-ориентированное программирова¬
ние, изложенном в главах с 15 по 21,
будет изучаться абстракция данных.
Это позволит нам создавать подобные структуры данных совершенно иным
способом, предназначенным для создания программного обеспечения, которое
программы, которые создают эти структуры и
значительно проще сопровождать и, что особенно важно, легко использовать
повторно.
Структуры
529
данных
Эта глава
потребует
от
читателя
усилий. Разбираемые
определенных
в
учебные программы довольно близки к реальным, при их написании ис¬
пользована значительная часть того материала, что вы изучили в предыдущих
главах. Особенно сложными являются операции с указателями. Наверное,
главе
многие из программистов на вопрос:
на при изучении
С?»
«Какая
тема
не задумываясь, ответят:
разобраться
все же очень полезно
в
была для
вас
«Операции
наиболее
слож¬
с указателями». И
программах, содержащихся
в
этой главе,
дальнейшем при изучении более углубленных
содержит богатую коллекцию упражнений, в кото¬
так как они пригодятся вам в
курсов. Кроме
рых придается
того, глава
особое
значение использованию структур данных.
искренне надеются, что вы попытаетесь реализовать большой про¬
ект, описанный в специальном разделе «Как самому написать компилятор».
Авторы
Для
того
чтобы запустить написанную
вами программу на
С,
вы пользуетесь
В резуль¬
тате реализации проекта вы действительно построите свой собственный ком¬
пилятор. Он будет считывать файл, состоящий из операторов на простом, но
машинный
компилятором, транслирующим эту программу в
язык.
довольно мощном языке высокого уровня, напоминающим ранние версии по¬
пулярного языка
раторы
BASIC. Далее ваш компилятор будет транслировать эти опе¬
Машинного языка Симплетрона
языка, который вы
в инструкции
изучили в специальном разделе главы
И
написанная
вами
ранее
SML-программы,
нять
7, «Как самому построить компьютер».
Симплетрона будет выпол¬
работы компилятора, кото¬
программа-симулятор
получаемые в результате
вы создадите своими руками! Этот проект даст вам прекрасную возмож¬
ность применить на практике большинство тех знаний, чтО были получены
при изучении этого курса. Специальный раздел шаг за шагом проведет вас от
описания высокоуровневого языка к алгоритмам, которые понадобятся для
рый
каждого оператора языка высокого уровня в машинный код.
И если вы получите удовлетворение, закончив эту работу, то можете попробо¬
преобразования
вать ввести как в компилятор, так и в симулятор
Симплетрона
те улучшения,
что предлагаются в упражнениях.
12.2.
Структуры,
тель,
который
struct
Структуры,
ссылающиеся на себя
ссылающиеся на себя, содержат
в
качестве
ссылается на структуру того же типа.
node
элемента указа¬
Например,
определение
{
int data;
struct node
*nextPtr;
};
описывает тип struct node.
Структура
типа struct
node
состоит из двух элемен¬
целого data и указателя nextPtr. Элемент nextPtr указывает на структу¬
ру типа struct node
структуру того же самого типа, что и только что
тов
объявленная нами, отсюда и термин «структура, ссылающаяся на себя». Эле¬
мент nextPtr иногда называют связкой, т.е. nextPtr можно использовать для
того, чтобы связать структуру типа struct node с другой структурой того же
Структуры, ссылающиеся на себя, могут связываться вместе для образо¬
типа.
вания полезных структур данных, таких как списки, очереди, стеки и деревья.
Рис. 12.1 иллюстрирует две структуры, ссылающиеся
с другом и
образующие
список.
Заметьте,
на
себя,
связанные друг
что в связывающем элементе
второй
структуры нарисована представляющая указатель NULL косая черта, чтобы
Глава 12
530
Рис. 12.1. Две ссылающихся
на
себя структуры, связанные друг
с другом
другую структуру. Косая черта ис¬
исключительно
для
пользуется
иллюстрации и не имеет никакого отношения к
символу обратной дробной черты языка С. Указатель NULL обычно обозначает
показать, что этот элемент не указывает на
конец структуры данных, так же как символ NULL обозначает конец строки.
Распространенная ошибка программирования 12.1
Связка последнего узла списка не устанавливается в NULL
12.3. Динамическое распределение
Создание
мического
и
использование динамических
распределения
памяти
структур данных
нения дополнительную память для хранения новых узлов и
ки памяти, ставшие ненужными.
чески памяти определяется
доступным виртуальным
Однако
мятью.
часто
доступной физической
размеры
значительно
разделяется между многими пользователями
Для
и
free,
а также операции
честве аргумента число
памятью компьютера или
в системе с
меньше,
виртуальной
что
потому
па¬
память
(задачами).
динамического распределения памяти
ций malloc
освобождать бло¬
Максимальный размер выделяемой динами¬
адресным пространством
эти
требует дина¬
возможности получать в процессе испол¬
памяти
необходимо применение функ¬
sizeof. Функция malloc принимает
байт, которое необходимо
в ка¬
выделить, и возвращает ука¬
затель на выделенную память типа void*. Указатель void* можно присвоить
переменной-указателю. Функция malloc обычно
Например, оператор
любой
используется совмест¬
но с операцией sizeof.
newPtr
=
malloc(sizeof(struct node));
sizeof(struct node) для определения размера в байтах структуры
node, выделяет новый блок памяти размером в sizeof(struct node)
байт, и сохраняет указатель на выделенную память в переменной newPtr. Если
необходимого количества памяти нет в наличии, malloc возвращает указатель
оценивает
типа struct
NULL.
Функция free освобождает память,
т.е. память возвращается системе, и в
дальнейшем ее можно выделить снова. Для высвобождения памяти, динами¬
чески выделенной предыдущим вызовом оператора malloc, используется опе¬
ратор
free(newPtr);
В
следующих
Каждая
разделах
обсуждаются
списки, стеки, очереди и деревья.
из этих структур данных создается и используется с применением ди¬
намического распределения памяти и ссылающихся на
Совет
по
себя структур.
переносимости программ 12.1
обязательно равен сумме размеров
Размер структуры
не
в
различных
существовании
требований
на
ее элементов
выравнивание
Причина
границ (см
этого
главу 10)
Структуры
531
данных
Распространенная ошибка программирования 12.2
Считают,
что
Хороший
размер
структуры
просто
размеров
сумме
ее
элементов.
стиль программирования 12.1
Используйте операцию sizeof
Хороший
равен
стиль
для определения размера структуры.
программирования 12.2
При использовании malloc проверяйте, не вернула ли функция
печатайте сообщение об ошибке, если запрашиваемая память
указатель NULL На¬
не
выделена
Распространенная ошибка программирования 12.3
Если
не освобождать выделенную динамически память после того, как она перестала
использоваться, то это может привести к тому, что память в системе преждевременно
исчерпается Иногда это называют «утечкой памяти»
Хороший
стиль программирования 12.3
Когда выделенная динамически
free, чтобы
память
становится
ненужной, вызывайте функцию
немедленно вернуть память системе
Распространенная ошибка программирования 12.4
Попытка освободить
память,
которая не была
выделена динамически с помощью
malloc
Распространенная ошибка программирования 12.5
Ссылка
на
блок памяти, который был освобожден.
12.4. Связанные
списки
Связанный список
это линейный набор ссылающихся на себя структур,
называемых узлами, и объединенных указателем-связ/сой, отсюда и назва¬
ние
«связанный» список. Доступ к связанному списку обеспечивается ука¬
первый узел списка. Доступ к следующим узлам производится че¬
связывающий указатель, хранящийся в каждом узле. По общему соглаше¬
нию связывающий указатель в последнем узле списка устанавливается в
NULL, отмечая конец списка. Данные хранятся в связанном списке динами¬
чески
каждый узел создается по мере необходимости. Узел может содер¬
зателем на
рез
жать данные
любого типа, включая другие структуры. Стеки и очереди также
линейным структурам данных, и, как мы увидим, являются
принадлежат к
специальными
вариантами
связанных
нейными структурами данных.
Списки данных могут храниться
дают
определенные
преимущества.
списков.
в
массивах,
Связанный
не известно, сколько элементов данных
Деревья
же
однако
список
являются
связанные
удобен,
нели¬
списки
когда заранее
будет содержать структура. Связан¬
ные списки являются динамическими, поэтому длина списка при необходим >-
Глава 12
532
сти может увеличиваться или уменьшаться.
Размер
же массива изменить не¬
возможно, потому что память под массив выделяется во время компиляции.
Массив может переполниться.
Связанные
чае, если в системе не хватит памяти,
на выделение
динамической
Совет по повышению
списки переполняются лишь в слу¬
чтобы удовлетворить очередной запрос
памяти.
эффективности 12.1
Можно объявить массив заведомо большего размера, чем предполагаемое число эле¬
ментов данных, однако это приводит к излишним затратам памяти. Связанные списки в
подобной ситуации обеспечивают более рациональное использование памяти.
Связанные списки могут содержаться в сортированном виде, если поме¬
щать каждый новый элемент в соответствующую позицию списка.
Совет по повышению
эффективности
12.2
Вставка и удаление в упорядоченном массиве требует определенного времени на вы¬
полнение, так как все элементы, следующие за вставляемым или удаляемым элемен¬
том, необходимо соответствующим образом сдвинуть.
Совет по повышению эффективности 12.3
Элементы массива хранятся в последовательных ячейках памяти Это обеспечивает
мгновенный доступ к любому элементу массива, поскольку адрес любого элемента
можно найти исходя из его позиции относительно начала массива Связанные списки
не дают возможности подобного немедленного доступа к своим элементам
Узлы связанных списков, как правило, не располагаются
одной сплошной области. Логически, однако, связанный
ся
непрерывным.
Рис. 12.2 иллюстрирует связанный
в памяти в виде
список представляет¬
список
с несколькими
узлами.
Рис. 12.2.
Совет по повышению
Графическое представление
эффективности
связанного списка
12.4
для структур данных, размер которых может увеличиваться и уменьшаться
во время исполнения программы, динамическое распределение памяти (вместо мас¬
сивов), можно сэкономить память Не забывайте, однако, что указатели тоже занима¬
Используя
ют место в памяти и что динамическое выделение памяти влечет за собой дополните¬
льные расходы времени на вызов функций
Структуры
533
данных
12.3 (результат
12.4)
Программа обеспечивает две возможности: 1)
вставить символ в список в алфавитном порядке (функция insert) и 2) удалить
символ из списка (функция delete). Это большая и сложная программа. Ниже
мы детально ее обсудим. В упражнении 12.20 читателя просят написать ре¬
курсивную функцию, выводящую на печать список в обратном порядке. А в
Программа
на рис.
ее выполнения показан на рис.
управляет списком символов.
рекурсивную функцию, осуществляющую поиск задан¬
упражнении 12.21
ного элемента данных в связанном списке.
У* Операции над списком
#include <stdio.h>
#include <stdlib.h>
struct
listNode
/*
{
*/
со
структура
ссылкой
*/
на
себя
be
deleted:
char data;
struct listNode *nextPtr;
};
typedef struct listNode LISTNODE;
typedef LISTNODE *LISTNODEPTR;
void insert(LISTNODEPTR *, char);
char delete(LISTNODEPTR *, char);
int isEmpty(LISTNODEPTR);
void printList(LISTNODEPTR);
void instructions(void);
main ()
{
LISTNODEPTR startPtr
int
=
NULL;
choice;
char
item;
instructions();
/*
вывести
меню
*/
pnntf("? ");
scanf("%d", &choice);
while
(choice
switch
!=3)
{
(choice){
case
1:
pnntf("Enter
а
character:
");
scanf("\n%c", &item);
insert(&startPtr, item);
printList(startPtr);
break;
case
if
2:
(!isEmpty(startPtr)) {
printf("Enter character
scanf("\n%c",
if
to
&item);
(delete(&startPtr,
item))
printf("%c deleted.\n",
printList(startPtr);
{
item);
}
Рис. 12.3. Вставка
и
удаление узлов
в списке
(часть 1
из
3)
");
534
Глава 2
else
not
pnntf("%c
found.\n\n",item);
}
else
empty.\n\n");
is
printf("List
break;
default:
pnntf ( "Invalid choice \n\n") ;
.
instructions ();
break;
}
printf ( "? ");
scanf("%d", &choice);
}
pnntf("End of run.\n");
return 0;
}
/* Распечатка инструкции */
void instructions(void)
{
printf("Enter your choice:\n"
"
/*
1
to
insert
an
element
into
the
list.\n"
"
2
to
delete
an
element
from
the
list.\n"
"
3
to
end.\n");
Вставка
void
нового
значения
msert(LISTNODEPTR
в
упорядоченный
char value)
список
*/
*sPtr,
{
LISTNODEPTR
newPtr
if
newPtr,
currentPtr;
previousPtr,
malloc(sizeof(LISTNODE));
=
(newPtr
!= NULL)
newPtr->data
newPtr->nextPtr
previousPtr
currentPtr
while
ли
память?
*/
NULL;
=
NULL;
=
=
/* доступна
{
value;
=
*sPtr;
!= NULL
(currentPtr
previousPtr
currentPtr
=
=
&&
value
>
currentPtr;
currentPtr->nextPtr;
currentPtr->data)
/*
/*
перейти
...
к
следующему
}
if
NULL) {
(previousPtr
newPtr->nextPtr
*sPtr;
*sPtr
newPtr;
==
=
=
}
else
{
previousPtr->nextPtr
=
{
...
newPtr;
Рис. 12.3. Вставка и удаление узлов в списке (часть2 из 3)
*/
*/
Структуры
535
данных
newPtr->nextPtr
=
currentPtr;
}
}
else
inserted.
not
printf("%c
No memory
available.\n",
value);
}
/* Удаление элемента списка */
char delete(LISTNODEPTR *sPtr,
char
value)
{
LISTNODEPTR previousPtr,
if
tempPtr
*sPtr'=
*sPtr;
=
(*sPtr)->nextPtr;
free(tempPtr);
return value;
}
else
tempPtr;
{
(*sPtr)->data)
==
(value
currentPtr,
/*
/*
отсоединить узел */
освободить узел */
{
previousPtr
currentPtr
while
*sPtr;
=
=
(*sPtr)->nextPtr;
}
if
!= NULL
(currentPtr
previousPtr
currentPtr
=
currentPtr->data
/*
currentPtr->nextPtr;
=
!= NULL)
(currentPtr
tempPtr
&&
currentPtr;
=
/*
!= value)
*/
перейти
...
к
следующему
{
currentPtr;
previousPtr->nextPtr
=
currentPtr->nextPtr;
free(tempPtr);
return value;
}
}
return
/*
int
'\0';
Возвратить
1,
если
список
пуст,
в
-
противном
случае
sPtr)
isEmpty(LISTNODEPTR
{
return
sPtr
==
NULL;
/* Print the list */
void printList(LISTNODEPTR currentPtr)
{
if
(currentPtr
printf("List
else {
pnntf("The
while
NULL)
==
is
empty.\n\n");
list
is:\n");
(currentPtr
printf("%c->
currentPtr
=
!= NULL)
",
{
currentPtr->data);
currentPtr->nextPtr;
}
printf("NULL\n\n");
}
Рис. 12.3. Вставка и удаление узлов в списке (часть 3 из 3)
{
...
0
*/
*/
536
Глава 12
Enter your choice:
1 to insert an
?
2
to
delete
3
to
end.
The
a
-->
?
1
Enter
The
list.
the
list.
-->
?
1
Enter
The
a
-->
?
2
is:
B
-->
a
character:
B
NULL
C
is:
-->
C
-->
Enter
character
D
not
found.
?
2
Enter
A
character:
list
A
is:
NULL
list
A
character
NULL
to
be
deleted:
D
to
be
deleted:
B
to
be
deleted:
C
to
be
deleted:
A
deleted.
The
list
A
-->
?
2
is:
-->
C
Enter
NULL
character
deleted.
The
list
A
-->
?
2
is
:
NULL
Enter
A
the
from
character:B
list
B
C
into
element
1
Enter
B
element
an
character
deleted.
List
is
empty.
? 4
Invalid choice.
Enter your choice:
1 to insert an
?
2
to
delete
3
to
end.
an
element
into
the
list.
element
from
the
list.
3
End
of
run.
Рис. 12.4.
Две
isEtnpty
основных
Пример результата работы программы
функции
называется
связанных
списков
предикатной функцией
на рис
insert
и
12 3
delete. Функция
она никак не меняет список, а
(т.е. указатель на первый
списка
Если
список
узел
равен NULL).
пуст, возвращается значение 1, в про¬
тивном случае 0. Функция printList распечатывает список.
всего лишь определяет, является ли список пустым
Структуры
537
данных
алфавитном порядке. Функции insert пе¬
редаются адрес
который необходимо вставить. Адрес списка
необходим, когда значение должно быть вставлено в начало списка. Передача
адреса списка позволяет модифицировать список (т.е. указатель на первый
узел списка) через вызов по ссылке. Так как список сам по себе является ука¬
Символы вставляются в список в
списка и символ,
(на свой первый элемент), при передаче адреса списка создается ука¬
(другими словами, это двойная косвенная адресация).
Это сложное понятие, требующее очень аккуратного программирования. Что¬
бы вставить символ в список, необходимо выполнить следующие шаги (см.
рис. 12.5):
зателем
затель на указатель
1) Вызвав malloc,
памяти,
вить,
а
создать узел, присвоив newPtr адрес выделенного блока
присвоить newPtr->data символ, который необходимо вста¬
newPtr->nextPtr
значение
NULL.
как NULL, и currentPtr как *sPtr (ука¬
списка). Указатели previousPtr и currentPtr исполь¬
2)Инициализировать previousPtr
затель на начало
зуются для хранения
и
узла
после
точки
3)Если currentPtr
rentPtr->data,
не
положений узла, предшествующего
NULL,
значение
rentPtr перемещается
в
ка в списке, в которую
а)
*sPtr
и
вставляемое
значение
*sPtr
больше,
чем
currentPtr присваивается previousPtr,
следующий узел списка. Так
будет вставлено значение.
previousPtr
currentPtr
newPtr
б)
точке вставки
вставки.
previousPtr
currentPtr
Рис. 12.5. Вставка узла на нужное место в списке
cur-
а cur¬
определяется точ¬
Глава 12
538
4)Если previousPtr равен NULL, новый узел вставляется как первый
узел списка. Присвоив newPtr->nextPtr значение *sPtr (предыдущий
указателю-связке нового узла), и присвоив *sPtr значе¬
первый узел
ние newPtr (*sPtr теперь указывает на новый узел). Если previousPtr
не NULL, то новый узел устанавливается на свое место. Для этого зна¬
чение newPtr присваивается previousPtr->nextPtr (указатель нового
а newPtr->nextPtr присваивается
связке предыдущего),
узла
связке нового).
значение currentPtr (указатель текущего узла
Хороший
стиль
программирования 12.4
Присваивайте связующему элементу нового узла NULL. Указатели перед использова¬
должны быть инициализированы.
нием
Рис. 12.5 иллюстрирует вставку узла, содержащего символ 'C', в упорядо¬
ченный список. Часть а) рисунка показывает список и новый узел перед встав¬
кой. Часть b)
результат вставки нового узла. Модифицированные указатели
показаны пунктиром.
Функция delete получает адрес указателя на начало списка и
торый нужно удалить. Удаление символа из списка происходит
образом:
символ, ко¬
следующим
выясняется, что символ, который требуется удалить, находится в
первом узле списка, tempPtr присваивается значение *sPtr (tempPtr
будет использован, чтобы освободить ненужную более память), *sPtr
1)Если
присваивается значение (*sPtr)->nexPtr (*sPtr теперь указывает на
второй узел списка), блок памяти, на которую указывает tempPtr,
освобождается, и функция возвращает символ, который был удален.
2) В
противном случае происходит инициализация
*sPtr
и
currentPtr
значением
previousPtr
значением
(*sPtr)->nextPtr.
3)Если currentPtr не NULL и значение, которое необходимо исключить,
не равно currentPtr->data, previousPtr присваивается значение cur¬
rentPtr, а currentPtr
определяется
значение
местоположение
currentPtr->nexPtr. Таким образом
символа,
который требуется
удалить,
если он содержится в списке.
4)Если currentPtr
не
NULL, tempPtr присваивается значение cur¬
значение currentPtr->nextPtr, узел,
rentPtr, previousPtr->nextPtr
на который указывает tempPtr, освобождается, и возвращается сим¬
вол, который был удален из списка. Если currentPtr равен NULL, воз¬
вращается NULL (символ *\0*) показывающий, что символ, который
предлагается удалить, в списке не найден.
Рис 12.6 иллюстрирует удаление узла из связанного списка. Часть а) ри¬
сунка показывает связанный список после выполнения последней операции
вставки. Часть b) показывает модификацию связывающего элемента previo¬
usPtr
и присваивание
льзуется для
tempPtr
значения
освобождения блока
Функция printList
обращается к
списка и
испо¬
C'.
принимает в качестве аргумента указатель на начало
указателю
не является ли список пустым.
list is empty."
currentPtr. Указатель tempPtr
памяти, выделенного для хранения
и заканчивает
currentPtr. Сначала функция определяет,
Если да,
работу. В
то
printList
выводит
сообщение "The
противном случае она выводит данные
Структуры
539
данных
а)
*sPtr
previousPtr
currentPtr
б)
*sPtr
previousPtr
currentPtr
tempPtr
Рис. 12.6. Исключение узла из списка
До тех пор, пока currentPtr не NULL, функция выводит на печать
currentPtr->data, а currentPtr присваивает значение currentPtr->nexPtr. За¬
метим, что если связующий элемент в последнем узле не NULL, алгоритм пе¬
чати будет выводить значения, находящиеся за границами списка, и это при¬
ведет к ошибке. Для связанных списков, стеков и очередей используется один
списка.
и тот же алгоритм печати.
12.5. Стеки
Стек
это упрощенный вариант связанного списка. Новые узлы могут
добавляться в стек и удаляться из стека только сверху. По этой причине, стек
часто называют структурой вида последним пришел
первым вышел (LIFO).
На
стек ссылаются через указатель на
элемент
в
последнем
узле
стека
верхний
установлен
Связывающий
NULL, чтобы пометить
элемент стека.
равным
границу стека.
На рис. 12.7
показан стек
с несколькими
связанные списки представляются идентично.
Заметим, что стеки и
Разница между стеком и свя¬
узлами.
занными списками в том, что вставку и удаление в связанных списках можно
выполнять в
любом месте,
а в стеке только сверху.
stackPtr
Рис. 12.7.
Графическое представление
стека
Глава 12
540
Распространенная ошибка программирования 12.6
Связке узла стека не присваивается NULL.
работе со стеками
push и pop.
push создает новый узел и помещает его на вершину стека. Функция
pop удаляет верхний узел стека, освобождает память, которая была выделена
Основные функции, используемые при
Функция
изъятому узлу, и возвращает изъятое значение.
Программа на рис. 12.8 (результат ее выполнения представлен на
12.9) работает с простым стеком целых чисел. Программа выполняет три
действия на выбор: 1) помещает значение в стек (функция push), 2) изымает
значение из стека (функция pop), и 3) завершает работу.
рис.
I
/* Программа динамического
#include <stdio.h>
#include <stdlib.h>
стека
struct stackNode { /* структура,
int data;
struct stackNode *nextPtr;
*/
ссылающаяся
на
себя
*/
};
struct stackNode STACKNODE;
STACKNODE *STACKNODEPTR;
typedef
typedef
void push(STACKNODEPTR *, int);
mt pop (STACKNODEPTR *);
mt isEmpty(STACKNODEPTR) ;
void printStack(STACKNODEPTR);
void instructions(void);
main ()
{
STACKNODEPTR stackPtr
int
choice,
=
NULL;
/*
указывает
на
вершину
*/
value;
instructions ();
printf ("?
");
scanf("%d",
while
&choice)
(choice
switch
case
!=3)
;
{
(choice)
{
1:
/* заталкивает
значение
integer:
scanf("%d", &value);
push(&stackPtr, value);
printStack(stackPtr);
break;
pnntf("Enter
case
if
2:
/*
an
выталкивает
в
стек
*/
");
значение
(!isEmpty(stackPtr))
printf("The popped value
из
is
стека
*/
%d.\n",
pop(&stackPtr));
printStack(stackPtr);
break;
Рис. 12.8. Простая программа, иллюстрирующая работу
со стеком
(часть 1
из
3)
Структуры
541
данных
default:
printf (11 Invalid
choice
.
\n\n" )
;
instructions();
break;
}
printf("? ");
scanf("%d", &choice);
}
pnntf("End of run.\n");
return 0;
/* Распечатка инструкций */
void instructions(void)
{
printf("Enter choice:\n"
"1 to push a value on
"2 to pop a value off
"3 to end program\n");
/* Помещение нового значения на
void push (STACKNODEPTR *topPtr,
{
STACKNODEPTR newPtr;
newPtr
if
the
the
stack\n"
stack\n"
вершину
int
стека
*/
info)
malloc(sizeof(STACKNODE));
(newPtr != NULL) {
newPtr->data
info;
=
=
newPtr->nextPtr
*topPtr
=
=
*topPtr;
newPtr;
}
else
printf("%d not
/*
Удаление
узла
на
inserted.
вершине
No memory available.\n",
стека
info);
*/
int pop(STACKNODEPTR *topPtr)
{
STACKNODEPTR tempPtr;
int popValue;
=
tempPtr
*topPtr;
popValue
(*topPtr)->data;
*topPtr
(*topPtr)->nextPtr;
free(tempPtr);
return popValue;
=
=
/* Распечатка стека */
void printStack(STACKNODEPTR currentPtr)
{
if (currentPtr
NULL)
printf("The stack is empty.\n\n");
==
Рис. 12.8. Простая программа, иллюстрирующая работу
со стеком
(часть 2
из
3)
Глава 2
542
else
{
stack
is:\n");
(currentPtr
!= NULL)
pnntf("The
while
>",
pnntf("%d
currentPtr
{
currentPtr->data);
currentPtr->nextPtr;
=
}
printf("NULL\n\n");
}
Стек
/*
int
пуст?
*/
isEmpty(STACKNODEPTR
topPtr)
{
return
topPtr
NULL;
==
Рис. 12.8. Простая программа, иллюстрирующая
Enter choice:
1 to push а value
2
to
pop
а
3
to
end
program
?
1
value
an
integer:
The stack is:
5 --> NULL
Enter
?
on
the
stack
off
the
stack
со стеком
(часть 3
из
5
1
Enter
The
-->
?
1
Enter
The
__>
?
2
The
-->
?
2
The
5
--> NULL
an
б
5
:6
is:
integer:
4
is:
__>
5
__> NULL
popped value
Stack is:
6
The
integer
stack
4
The
an
stack
6
is
4.
is
6.
--> NULL
popped value
Stack is:
5
--> NULL
?
2
The
popped value is 5.
The
stack
is
empty.
stack
is
empty.
?
работу
2
The
Рис. 12.9.
Результат
выполнения программы на рис
12 8
(часть 1
из
2)
3)
Структуры
543
данных
4
Invalid
Enter
I?
choice.
choice.:
1
to
push
on
the
stack
2
to
the
stack
3
to
pop a value off
end program
?
3
End
of
a
value
run.
Рис. 12.9. Результат выполнения программы на рис
12 8 (часть 2 из 2)
Функция push помещает новый узел на вершину стека. В выполнении
функции можно выделить три этапа:
1)Создание
нового узла посредством вызова
malloc, при
этом указателю
newPtr присваивается адрес блока выделенной памяти, переменной
newPtr->data присваивается значение, которое необходимо поместить
в стек, и указателю newPtr->nextPtr присваивается NULL.
2)Указателю newPtr->nextPtr присваивается *topPtr (указатель на вер¬
шину стека); связывающий элемент newPtr теперь указывает на узел,
который был
верхним до этого.
присваивается значение newPtr;
новую вершину стека.
3)*topPtr
Операции,
включающие в себя
*topPtr,
Рис. 12.10 дает наглядное представление
*topPtr
теперь указывает на
меняют значение
о
том,
как
stackPtr
происходит
в
main.
выполнение
функции push. Часть а) рисунка показывает стек и новый узел перед операцией
push. Пунктирные линии в части b) иллюстрируют шаги 2 и 3 выполнения
push, в результате которых узел, содержащий 12, становится новой вершиной
стека.
а)
б)
Рис. 12.10. Выполнение
Функция pop удаляет верхний узел
функции push
стека.
звать pop, main определяет, не пуст ли стек.
В
Отметим,
что перед тем как вы¬
выполнении
функции pop
мож¬
но выделить пять основных этапов:
1)Указателю tempPtr
для
присваивается
*topPtr (tempPtr будет
освобождения ненужной памяти).
использован
544
Глава 12
2)Переменной popYalue
няется
значение,
присваивается значение
находившееся
в
верхнем
присваивается (*topPtr)->nextPtr
рес нового верхнего узла).
3)*topPtr
4)Освобождение
памяти,
(*topPtr)->data (сохра¬
узле).
(*topPtr
на которую указывает
tempPtr.
возвращается значение
12.8. эта функция
main).
5)Вызывающей функции
на рис.
присваивается ад¬
popValue (в
программе
Рис. 12.11 иллюстрирует выполнение функции pop. Часть а) показывает
после
предыдущей операции push. В части b) выделены указатели
tempPtr, указывающий на первый узел стека, и *topPtr, указывающий на вто¬
рой узел. Для освобождения указанной tempPtr памяти вызывается функция
стек
free.
а)
*topPtr
Ж" ' "'
W
ь,
W
12
11
tempPtr
Рис. 12.11. Выполнение
Стеки
имеют
множество
функции pop
разнообразных применений. Например, всякий
раз, когда происходит вызов функции, вызванная функция должна знать, как
вернуться к вызывающей, поэтому адрес возврата помещается в стек. В слу¬
чае, когда происходит целый ряд последовательных вызовов, адреса возврата
размещаются в стеке в порядке последним пришел
первым вышел, так что
после завершения выполнения каждой функции происходит переход к функ¬
ции, ее вызывавшей. Стек поддерживает как обычные нерекурсивные вызовы,
так и рекурсивный вызов функций.
На
стеке выделяется пространство для
каждом вызове
автоматических переменных при
функции. Когда происходит возврат
от
вызванной функции
к
пространство автоматических переменных функции удаляется
из стека, и эти переменные становятся для программы неизвестными.
вызывающей,
Компиляторы
используют стеки
здания кода машинного языка.
применения
стеков.
в процессе вычисления
выражений
и со¬
В упражнениях будут исследованы некоторые
Структуры
545
данных
12.6.
Очереди
Другой распространенной структурой
очередь. Очередь
данных является
сходна с людьми у кассы в магазине: тот, кто ближе всех к кассе,
ется в
очередь,
первую
пока их
только
другие
покупатели
в хвост
dequeue (исключить
Очереди
из
первым ушел (FIFO). Операции
оборудовано
обслуживаться
стоящий
в очереди постепенно перемещается к ее началу по мере
ния впереди стоящих
же
ставятся
пользователи
может
среда
принтер
иметь
может
если принтер в
всего
возникнуть
один
фере на диске, где и ожидают
Информационные пакеты
времени, ожидая в очередях.
момента,
в
сразу
у
нескольких
из
пользователей.
пользователей,
остальные
Эти данные размещаются
в
бу¬
когда получат доступ к принтеру.
компьютерных
Пакет
буфера печати. Многополь¬
Потребность вывести
сетях
также
проводят
часть
в сети переправляется от одного сетевого
узла к другому, пока не достигнет узла назначения.
в
Каждый
обслужива¬
очередь.
принтер.
этот момент занят одним
могут все же посылать свои данные на печать.
каждый
в
пользователей.
также используются при организации
зовательская
вятся
очередь)
только один поль¬
(задача). Остальные
на
в
только одним процессором, поэтому в
момент времени может
зователь
Очереди
постановки
enqueue (поставить
находят в компьютерных системах многочисленные применения.
каждый конкретный
слать в
ждут,
очереди).
Большинство компьютеров
Даже
и
очереди узлы удаляются только с головы, а добавляются
очереди. По этой причине очереди часто называют структура¬
в очередь и удаления из очереди носят названия
данные
обслужива¬
конец
обслужат. В
ми данных типа первым пришел
и
в
пристраиваются
Сетевой узел способен
по¬
момент всего один пакет, поэтому все остальные пакеты стано¬
очередь,
ожидая,
Рис. 12.12 иллюстрирует
когда пересылающий узел сможет их обработать.
очередь с несколькими узлами. Обратите внимание
на указатели головы и хвоста очереди.
tailPtr
headPtr
Рис. 12.12.
Графическое представление очереди
Распространенная ошибка программирования 12.7
Связке последнего узла очереди
не
присваивается
NULL
Программа на рис. 12.13 (результат ее выполнения показан на
12.14)
пример работы с очередью. Программа предлагает выполнить
следующие действия на выбор: поставить узел в очередь (функция enqueue),
удалить узел из очереди (функция dequeue), и выйти из программы.
рис.
18
Зак 801
546
Глава 12
/* Поддержка очереди
#include <stdio.h>
#include <stdlib.h>
*/
struct
queueNode { /* структура,
char data;
struct queueNode *nextPtr;
ссылающаяся
на
себя
*/
} ;
typedef struct queueNode QUEUENODE;
typedef QUEUENODE *QUEUENODEPTR;
/* function prototypes */
void pnntQueue (QUEUENODEPTR)
;
int
isEmpty(QUEUENODEPTR);
char dequeue(QUEUENODEPTR *,
void enqueue(QUEUENODEPTR *,
void instructions(void);
mam
QUEUENODEPTR *);
QUEUENODEPTR *, char);
()
{
QUEUENODEPTR headPtr
int
=
tailPtr
NULL,
NULL;
=
choice;
char
item;
instructions();
printf("? ") ;
scanf("%d", &choice);
while
(choice
!=3)
switch(choice)
case
{
{
1:
pnntf("Enter
scanf("\n%c",
a
character:
enqueue(&headPtr, &tailPtr,
prmtQueue (headPtr) ;
break;
case 2:
if
");
&item);
item);
{
(!isEmpty(headPtr))
item
dequeue(&headPtr,
&tailPtr);
prmtf("%c has been dequeued, \ n", item);
=
}
prmtQueue (headPtr)
;
break;
default:
prmtf ("Invalid choice \n\n" )
instructions();
break;
.
;
}
printf ("? ") ;
scanf("%d", &choice);
}
Рис. 12.13. Работа
с очередью
(часть 1
из
3)
Структуры
547
данных
of
prmtf("End
return 0;
void
run.\n") ;
instructions(void)
{
("Enter
printf
void
choice:\n"
your
"
1
to
add
"
2
to
remove
"
3
to
end\n");
an
item
an
enqueue(QUEUENODEPTR
char value)
if
the
queue\n"
from
*headPtr,
the
queue\n"
QUEUENODEPTR
*tailPtr,
newPtr;
QUEUENODEPTR
newPtr
to
item
=malloc(sizeof(QUEUENODE)) ;
!=
(newPtr
NULL)
newPtr->data
{
value;
=
newPtr->nextPtr
=
NULL;
if
(isEmpty(*headPtr))
*headPtr
newPtr;
else
=
(*tailPtr)->nextPtr
*tailPtr
=
newPtr;
newPtr;
=
}
else
pnntf("%c
not
inserted.
No memory
available.\n",
}
char
dequeue(QUEUENODEPTR
*headPtr,
QUEUENODEPTR
{
char
value;
QUEUENODEPTR
value
(*headPtr)->data;
=
*headPtr;
=
tempPtr
*headPtr
if
tempPtr;
=
(*headPtr)->nextPtr;
(*headPtr
*tailPtr
==
=
NULL)
NULL;
free(tempPtr);
return value;
int
isEmpty(QUEUENODEPTR headPtr)
{
return
headPtr
==
NULL;
Рис. 12.13. Работа с очередью (часть 2 из 3)
18*
*tailPtr)
value);
548
Глава 12
void pnntQueue (QUEUENODEPTR currentPtr)
{
if (currentPtr
NULL)
==
pnntf("Queue
else
empty.\n\n");
is
{
queue
is
;\n");
(currentPtr
-->
!=
NULL)
",
currentPtr->data);
pnntf("The
while
printf("%c
currentPtr
=
{
currentPtr->nextPtr;
}
pnntf ("NULL\n\n") ;
}
Рис. 12.13. Работа с очередью (часть 3 из 3)
Enter your choice:
1 to add an item to
?
2
to
remove
3
to
end
an
the queue
item from the queue
1
Enter а
character:
А
The
queue is:
А --> NULL
?
1
Enter
а
character:
В
The
queue is:
А --> В --> NULL
?
1
Enter а
character:
С
The
queue is:
А --> В --> С
?
>NULL
2
А has been dequeued.
The queue is:
> С --> NULL
В
?
2
В has been dequeued.
The queue is:
С
--> NULL
?
2
С has been dequeued.
Queue is empty.
?
2
Queue
?
is
empty.
4
Invalid choice.
Рис. 12.14. Пример выполнения программы, представленной на рис 12 13 (часть 1 из 2)
Структуры
549
данных
Enter your choice:
1 to add an item to
?
2
to
3
to end
remove
an
the queue
item from the queue
3
End of
run.
Рис. 12.14. Пример выполнения программы,
Функция enqueue получает
от
представленной
на рис
12.13
(часть 2
main три аргумента: адрес указателя
лову очереди, адрес указателя на хвост очереди и значение, которое
мо поставить в очередь.
из
Выполнение функции
2)
на го¬
необходи¬
состоит из трех шагов:
нового узла: вызвать malloc, присвоить адрес выделенного
блока памяти newPtr, присвоить newPtr->data значение, которое необ¬
ходимо поставить в очередь, а newPtr->nextPtr присвоить NULL.
1)Создание
очередь пуста, присвоить *headPtr указатель newPtr; в против¬
ном случае присвоить этот указатель (*tailPtr)->nextPtr.
2)Если
3)И
наконец,
присвоить *tailPtr указатель newPtr.
функции enqueue. Часть а) рисунка
Рис. 12.15 иллюстрирует выполнение
новый узел перед выполнением операции. Пунктирные
показывают шаги 2 и 3 функции enqueue, которые позволя¬
показывает очередь и
b)
линии в части
ют
добавить новый узел
а)
*headPtr
б)
*headPtr
Рис. 12.15.
в конец очереди,
которая не является
*tailPtr
Функция dequeue получает
выполнения
адрес указателя хвоста
нение
складывается из шести шагов:
1)Переменной value
данные).
для
функции enqueue
присвоить
и удаляет
значение
первый
узел очереди. Выпол¬
(*headPtr)->data (сохранить
указателю tempPtr значение *headPtr (tempPtr использует¬
освобождения ненужной памяти).
2) Присвоить
ся
newPtr
в качестве аргументов адрес указателя на го¬
лову очереди и
dequeue
newPtr
*tailPtr
Графическое представление
пустой.
Глава 12
550
3)Присвоить указателю *headPtr значение (*headPtr)->nextPtr. (*he¬
adPtr теперь указывает на новый первый узел очереди).
4)Если *headPtr
указывает на NULL, установить *tailPtr также указы¬
вающим на NULL.
5)Освободить блок
на
памяти,
который указывает tempPtr.
значение value вызывающей функции (в
12.13 функция dequeue вызывалась из main).
6)Передать
рис.
Рис. 12.16
вает
очередь
иллюстрирует выполнение функции
после
выполнения
tempPtr, указывающий
на
новый первый узел очереди.
рый указывает
а)
tempPtr,
операции
Для
dequeue. Часть а)
enqueue.
исключенный узел,
и
b)
показы¬
показывает
headPtr, указывающий
вызывается
на
функция free.
*tailPtr
*headPtr
Рис. 12.16.
Часть
на
возвращения системе блока памяти, на кото¬
*headPtr
б)
программе
*tailPtr
Графическое представление
12.7.
выполнения
функции dequeue
Деревья
Связанные списки, стеки и очереди
все это примеры линейных струк¬
нелинейная, двумерная структура данных с особы¬
тур данных. Дерево же
ми свойствами. Узлы дерева содержат две или более связей. В этом разделе
рассматриваются только двоичные деревья (рис. 12.17)
деревья, узлы кото¬
рых содержат две связки
(при
этом
как
одна,
так
и
обе
связки
могут
NULL). Первый узел дерева называется корневым. Каждая связь
узла ссылается на потомка. Левый потомок
первый узел левого
быть
корневого
поддерева,
первый узел правого поддерева. Потомки одного узла на¬
зываются узлами-сиблингами. Узел, не имеющий потомков, называется лис¬
том. Программисты обычно рисуют деревья от корневого узла вниз
т.е. в
а
правый
потомок
направлении, противоположном росту настоящих деревьев.
В
этом разделе рассматривается специальное двоичное дерево, называемое
двоичным
чениями в
деревом поиска. Двоичное дерево поиска (с неповторяющимися зна¬
узлах) устроено так, что значения в любом левом поддереве мень-
Структуры
551
данных
Рис. 12.17. Графическое представление двоичного дерева
ше, чем значение в родительском узле, а значения в любом правом поддереве
больше,
чем значение в родительском узле.
ное дерево поиска с
12
значениями.
иска для одного и того же
набора
На рис. 12.18 изображено двоич¬
Заметим,
что
форма двоичного дерева по¬
разной, в зависимости от
данных может быть
порядка, в котором значения вставлялись в дерево.
47
Двоичное дерево
Рис. 12.18.
поиска
Распространенная ошибка программирования 12.8
Связкам
листовых
узлов
не
присваивается
NULL
Программа на рис. 12.19 (результат ее выполнения представлен на
12.20) создает двоичное дерево поиска и обходит его тремя способами: с
порядковой выборкой (inorder), предварительной выборкой (preorder) и отло¬
женной выборкой (postorder). Программа генерирует десять случайных чисел
и вставляет их в дерево, за исключением повторяющихся, которые отбрасыва¬
рис.
ются.
Функции,
использованные в программе на рис.
ного дерева поиска и его
обхода,
12.19 для создания двоич¬
являются рекурсивными.
Функции insertNo-
de
передаются два аргумента: адрес дерева и целое значение, которое необхо¬
димо сохранить в дереве. В двоичном дереве поиска узел можно вставлять
только в качестве листа.
обходимо
Чтобы
выполнить следующее:
вставить узел в двоичное дерево поиска, не¬
552
Глава 12
/*
Создание двоичного дерева
и
его
порядковой, предварительной
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
с
struct
treeNode
struct
int
и
обход
отложенной
выборкой */
{
treeNode
*leftPtr;
data;
struct
treeNode
*nghtPtr;
};
treeNode
typedef
struct
typedef
TREENODE
TREENODE;
*TREENODEPTR;
void
msertNode(TREENODEPTR
void
mOrder (TREENODEPTR) ;
void
preOrder(TREENODEPTR);
void
postOrder(TREENODEPTR);
*,
mt) ;
main ()
{
mt
i,
item;
TREENODEPTR
rootPtr
=
NULL;
srand(time(NULL));
попытаться
prmtf("The
разместить в дереве 10 случайных чисел
от
0 до 14 */
numbers bemg placed in the tree are:\n");
for
i
/*
в
диапазоне
(i
item
1;
=
=
<=
rand()
10; i++)
% 15;
prmtf("%3d", item);
insertNode(&rootPtr,
{
item);
}
/* обойти дерево с предварительной выборкой */
pnntf ("\n\nThe preOrder traversal is:\n");
preOrder(rootPtr);
/* обойти дерево с порядковой выборкой */
prmtf("\n\nThe mOrder traversal is:\n");
mOrder (rootPtr) ;
обойти дерево с отложенной выборкой */
/*
pnntf("\n\nThe postOrder traversal is:\n");
postOrder(rootPtr);
return
void
0;
insertNode(TREENODEPTR
*treePtr,
int
value)
{
if
NULL) { /* *treePtr равен NULL
malloc(sizeof(TREENODE));
==
(*treePtr
*treePtr
=
Рис. 12.19. Создание
и
обход
*/
двоичного дерева (часть 1 из 2)
Структуры
553
данных
if
!=
(*treePtr
NULL)
{
value;
=
(*treePtr)->data
(*treePtr)->leftPtr
(*treePtr)->nghtPtr
NULL;
=
=
NULL;
}
else
printf("%d not
value);
inserted.
No
memory
available.\n",
}
else
if
(value
<
(*treePtr)->data)
insertNode(&((*treePtr)->leftPtr),
else
if
>
(value
value);
(*treePtr)->data)
insertNode(&((*treePtr)->rightPtr),
else
value);
printf("dup");
}
void
inOrder(TREENODEPTR
treePtr)
{
if
(treePtr
!=
NULL)
{
inOrder(treePtr->leftPtr) ;
pnntf("%3d",
treePtr->data);
inOrder(treePtr->rightPtr);
void preOrder(TREENODEPTR
{
if (treePtr != NULL)
{
pnntf("%3d",
treePtr)
treePtr->data);
preOrder(treePtr->leftPtr);
preOrder(treePtr->rightPtr);
void postOrder(TREENODEPTR
treePtr)
{
if
(treePtr
!= NULL)
{
postOrder(treePtr->leftPtr);
postOrder(treePtr->nghtPtr) ;
pnntf("%3d", treePtr->data);
}
Рис. 12.19. Создание и обход двоичного дерева (часть 2 из 2)
1)Если *treePtr
равен NULL, создать новый узел. Вызвать malloc, при¬
своить выделенную память *treePtr, присвоить (*treePtr)->data целое
необходимо сохранить, присвоить (*treePtr)->leftPtr
значение NULL, и вернуть управление вызываю¬
щей функции (либо main, либо предыдущему вызову insertNode).
значение, которое
и
(*treePtr)->rightPtr
2)Если
значение
адресом
*treePtr
не
NULL
и значение,
которое
необходимо
вста¬
(*treePtr)->data, функция insertNode вызывается с
(*treePtr)->leftPtr. В противном случае функция insertNode
вить меньше,
чем
Глава 12
554
(*treePtr)->rightPtr. Рекурсивные
не будет обнаружен указатель
вставка нового узла.
пункт 1)
вызывается с адресом
жаются
тех
до
пор,
чего выполняется
The
7
numbers
0
8
6
пока
шаги продол¬
NULL, после
being placed in the tree are:
14
1
Odup 13
Odup 7dup
The preOrder traversal is:
7
0
6
1
8 14 13
The inOrder traversal is:
1
0
6
7
8 13 14
The postOrder traversal is:
1
0 13 14
8
7
6
Рис. 12.20. Пример
на
выходных данных при выполнении программы рис
12 19
Функции inOrder, preOrder и postOrder получают дерево (т.е. указатель
корневой узел) и проходят по нему.
В функции inOrder (порядковая выборка) выполняются следующие шаги:
1)Обойти
с
помощью
2)Обработать
3)Обойти
Значение
с
inOrder
значение
помощью
в
левое
поддерево.
узле.
inOrder правое поддерево.
обрабатывается до тех пор, пока не будут обработаны
Проход с помощью inOrder по дереву, изобра¬
12.21, выглядит следующим образом:
в узле не
значения в его левом поддереве.
женному на рис.
б
13
17
27
33
42
48
27
42
13
6
33
17
48
Рис. 12.21. Двоичное дерево поиска
Отметим,
что проход по двоичному дереву поиска с помощью
дает значения в узлах в возрастающем порядке.
дерева поиска
фактически сортирует данные
ровкой двоичного
Процесс
вы¬
поэтому он называется сорти¬
дерева.
В функции preOrder (предварительная выборка)
выполняются следующие
шаги:
1)Обработать
inOrder
создания двоичного
значение
в
узле.
2)Обойти
с
помощью
preOrder
левое
3)Обойти
с
помощью
preOrder
правое поддерево.
поддерево.
Структуры
555
данных
обрабатывается при остановке в этом узле. После
обработано, обрабатываются значения в левом подде¬
а затем значения правого поддерева. Проход с помощью preOrder по де¬
изображенному на рис. 12.21, выглядит следующим образом:
Значение в каждом узле
того, как значение узла
реве,
реву,
27
В
б
13
17
42
33
48
функции postOrder (отложенная выборка)
выполняются
следующие
шаги:
1)Обойти
с
помощью
postOrder
левое
2)Обойти
с
помощью
postOrder
правое поддерево.
3)Обработать
Значение
значение
в
узле.
в каждом узле не распечатывается до тех пор, пока не
печатаны значения его потомков.
изображенному
б
17
13
поддерево.
33
на рис.
48
42
12.21,
Проход
с
postOrder
образом:
помощью
выглядит следующим
будут
по
рас¬
дереву,
27
Двоичное дерево поиска упрощает удаление повторяющихся значений.
Когда дерево создано, попытка вставить в него повторяющееся значение будет
обнаружена, поскольку это значение будет следовать тем же самым решениям
«поверни налево» или «поверни направо» при каждом сравнении, как и значе¬
ние, введенное первым. Таким образом, повторяющееся значение будет в кон¬
це концов сравниваться с узлом, содержащим первое значение. И в этом месте
его можно просто
Также
очень
отбросить.
быстро можно
провести в двоичном дереве поиск значения,
Если дерево упаковано плотно, то каждый уровень
раза больше элементов, чем предыдущий. Поэтому
совпадающего с заданным.
содержит примерно
в два
двоичное дерево поиска с n элементами должно содержать максимум l0g2n
уровней и, таким образом, необходимо будет сделать максимум log2n сравне¬
ний, чтобы найти нужное значение, или же определить, что такого значения
нет. Это означает, что когда, например, производится поиск в 1000-элемнтном
двоичном дереве поиска (плотно упакованном), то необходимо сделать не более
десяти сравнений,
но
упакованном),
так как
210> 1000. Для
поиска же в двоичном дереве
содержащем 1000000 элементов,
потребуется
дцати сравнений, поскольку 220 > 1000000.
В упражнениях этой главы описаны алгоритмы
операций
для
не
(плот¬
больше два¬
некоторых
других
с двоичными деревьями, таких, как удаление элемента из двоичного
формате и выполнение
обхода дерева по уровням. При проходе двоичного дерева по уровням узлы де¬
рева обрабатываются ряд за рядом, начиная с уровця корневого узла. На каж¬
дом уровне узлы обрабатываются слева направо. В других упражнениях, по¬
священных двоичным деревьям, рассматривается еще целый ряд интересных
вопросов: двоичное дерево поиска, которое может содержать одинаковые зна¬
дерева, вывод двоичного дерева на печать в двумерном
чения, размещение в двоичном дереве значений строкового типа, определение
количества уровней, содержащихся в двоичном дереве.
Резюме
Структуры, ссылающиеся на
рый ссылается на структуру
себя, содержат элемент-указатель, кото¬
того
же
типа.
Структуры, ссылающиеся на себя, позволяют связывать между собой
ряд однотипных структур, объединяя их в стеки, очереди, списки и де¬
ревья.
Глава 12
556
Выделение динамической
байт
для
памяти резервирует определенное количество
объекта
хранения
во
время
выполнения
программы.
качестве аргумента число байт, которое
Функция malloc принимает
необходимо выделить, и возвращает указатель типа void на выделен¬
ную память. Функция malloc обычно используется совместно с опера¬
цией sizeof. Операция sizeof возвращает размер в байтах для структу¬
ры, которой будет выделяться память.
Функция free осуществляет освобождение памяти.
в
Связанный
список
это
набор
данных, хранящихся в группе связан¬
ных ссылающихся на себя структур.
Связанный
ка
Размер
ся
список
может
увеличиваться
или
уменьшаться.
связанного списка может увеличиваться до тех пор, пока имеет¬
доступная
Связанные
данных
Стеки
это динамическая структура данных; длина спис¬
необходимости
при
память.
списки
механизм вставки и удаления
модификации указателей.
путем
это специальные разновидности связанного списка.
и очереди
Узлы добавляются
причине
обеспечивают простой
в
стек
стек называют
и удаляются из
структурой
него
только
По этой
сверху.
данных типа последним пришел
первым вышел.
Связывающий
элемент в последнем узле стека устанавливается равным
NULL, чтобы
отметить
нижнюю
границу
стека.
Для работы
используются две основные операции push и
новый узел и помещает его на вершину сте¬
создает
pop. Операция push
ка. Операция pop удаляет узел с вершины стека, освобождает память,
со
стеками
которая была выделена удаленному узлу,
и возвращает удаляемое зна¬
чение.
В структуре данных типа очереди узлы
ются в хвост. По этой причине очереди
типа первым пришел
пользуют
Деревья
операции
являются
требующие
Двоичные
Корневой
Каждый
более
и
сложными
структурами
Деревья
добавля¬
и удаления ис¬
данных,
чем
связан¬
это двумерные структуры дан¬
двух или более связок на узел.
деревья содержат две связки на узел.
узел является первым узлом дерева.
из
потомок
Для добавления
dequeue.
первым вышел.
enqueue
ные списки, очереди и стеки.
ных,
удаляются с головы и
называют структурами данных
указателей в корневом узле ссылается на
первый узел в левом поддереве, а правый
потомка.
Левый
потомок
пер¬
вый узел в правом поддереве. Потомки одного узла называются узлами-сиблингами. Если узел не имеет потомков, он называется листом.
дерево поиска устроено так, что значение в левом потомке ме¬
ньше, чем в родительском узле, а значение в правом потомке больше
или равно значению в родительском узле. Если повторяющиеся значе¬
ния недопустимы, то значение в правом потомке просто больше значе¬
Двоичное
ния в родительском узле.
Структуры
557
данных
При
выполнении прохода по двоичному дереву с
сначала выполняется
значение
ется
поддерева.
значения
узла,
после чего
Значение
в
левом
такой проход для
в узле не
порядковой выборкой
обрабатыва¬
левого поддерева,
такой
выполняется
обрабатывается,
же
проход
пока не
правого
обработаны
все
поддереве.
При выполнении прохода по двоичному дереву с предварительной выборкай сначала обрабатывается значение узла, затем выполняется про¬
ход левого поддерева и проход правого поддерева с предварительной
выборкой. Значение в каждом узле обрабатывается сразу, как только
он встречается.
При
выборкой
образом проходится левое поддерево, затем правое и
только после этого обрабатывается значение в узле. Значение в каждом
узле не обрабатывается, пока не обработаны значения обоих его подде¬
выполнении прохода по двоичному дереву с отложенной
сначала таким же
ревьев.
Терминология
dequeue
листовой узел
нелинейная структура данных
enqueue
FIFO (первым пришел
первым
обход
обход с отложенной выборкой
обход с порядковой выборкой
ушел)
free
LIFO (последним пришел
первым ушел)
malloc (выделить
обход
предварительной выборкой
с
в
остановка
память)
узле
очередь
pop
push
sizeof
поддерево
вершина
вставка узла
правый
потомки
правое
поддерево
потомок
двоичное дерево
функция
родительский узел
связанный список
предикатная
голова очереди
двоичное дерево поиска
сиблинги
двойная косвенная адресация
сортировка
дерево
стек
двоичного
динамические структуры данных
структура,
динамическое распределение
памяти
удаление
корневой узел
левое поддерево
узел-потомок
дерева
ссылающаяся
на
себя
узла
узел
указатель
NULL
левый потомок
указатель
на
линейная структура данных
хвост
указатель
очереди
Распространенные ошибки программирования
12.1.
Связка последнего узла
12.2.
Считают,
что
размер
списка
структуры
не
устанавливается
равен
просто
в
сумме
NULL.
размеров
ее
элементов.
12.3.
Если
не освобождать выделенную динамически память, после того,
как она перестала использоваться, то это может привести к тому,
558
Глава 12
что память в системе преждевременно исчерпается. Иногда это на¬
зывают «утечкой памяти».
12.4.
Попытка освободить память, которая
ски с помощью malloc.
12.5.
Ссылка
12.6.
Связке
12.7.
Связке последнего узла очереди
12.8.
Связкам
Хороший
12.1.
12.2.
на
была выделена динамиче¬
блок памятц, который был освобожден.
нижнего
узла
листовых
стиль
не
стека
узлов
не
не
присваивается
не
NULL.
присваивается
NULL.
NULL.
присваивается
программирования
Используйте операцию sizeof для определения размера структуры.
При
использовании
malloc проверяйте,
не
вернула
ли
указатель NULL. Напечатайте сообщение об ошибке,
шиваемая
12.3.
память
не
функция
если
запра¬
выделена.
ненужной,
Когда выделенная динамически
память становиться
пользуйте функцию free, чтобы
немедленно вернуть память систе¬
ис¬
ме.
12.4.
связующему элементу нового узла NULL. Указатели
перед использованием должны быть инициализированы.
Присваивайте
Советы по повышению
12.1.
Можно объявить
эффективности
массив заведомо
гаемое число элементов данных,
затратам
чивают
12.2.
Связанные
чем предпола¬
списки
в
подобной ситуации обеспе¬
использование
памяти.
Вставка
и удаление в упорядоченном массиве требует определенно¬
го времени на выполнение, так как все элементы, следующие за
вставляемым или удаляемым элементом, необходимо соответствую¬
щим
12.3.
памяти.
более рациональное
большего размера,
однако это приводит к излишним
образом
сдвинуть.
Элементы массива хранятся в последовательных ячейках памяти.
Это обеспечивает мгновенный доступ к любому элементу массива,
поскольку адрес любого элемента можно найти иСходя из его пози¬
ции относительно начала массива. Связанные списки не дают воз¬
можности подобного немедленного доступа к своим элементам.
12.4.
Используя
для структур данных, размер которых может увеличи¬
ваться и уменьшаться во время исполнения программы, динамиче¬
ское распределение памяти
память.
(вместо массивов),
Не забывайте, однако,
можно
сэкономить
что указатели тоже занимают место
в памяти и что динамическое выделение памяти влечет за
полнительные расходы времени
на
вызов
функций.
собой до¬
Структуры
559
данных
Советы по переносимости программ
12.1.
Размер структуры не обязательно равен сумме размеров ее элемен¬
тов. Причина этого в существовании различных требований на вы¬
равнивание границ (см. главу 10).
Упражнения
12.1.
для самоконтроля
Заполните
a
пробелы
)
на
намических
в
из
каждом
предложений:
следующих
себя структура используется для организации ди¬
структур
данных.
b) Функция
используется для динамического выделе¬
ния памяти.
редуцированный вариант связанного списка, в ко¬
тором узлы могут вставляться и удаляться только в начале.
c)
которые не изменяют связанные списки, а просто про¬
сматривают их, называют
d) Функции,
.
e) Очереди
льку первый вставленный узел будет первым
f) Указатель на следующий узел связанного
называют структурами данных типа
,
поско¬
же и удален.
называется
списка
используется для освобождения динамиче¬
g) Функция
ски выделенной памяти.
редуцированный вариант связанного списка, в
котором узлы могут вставляться только в начало списка, а удаля¬
ться только из его конца.
h)
i)
жащая узлы
j) Стек
с
нелинейная двумерная структура данных, содер¬
или более связями.
двумя
называют
структурой
последний вставленный узел
k) Узлы
n) Узел
в
связь
этого
узле
дерева
12.3.
В
узлом.
указывает
на
или
узла.
дерева, не имеющий потомков, называется
заключается
различие
12.4. Напишите
между
.
и
,
чем заключается различие между связанным списком и
чем
.
прохождения двоичного дерева используются следующие
три алгоритма обхода:
В
потому что
удален первым.
узел дерева является
m) Каждая
12.2.
будет
,
дерева содержат по два связующих элемента.
1) Первый
о) Для
данных типа
стеком
и
стеком?
очередью?
оператор или несколько операторов, выполняющие сле¬
дующие задания. Исходите из того, что все действия выполняются
в main, и сделаны следующие определения:
struct
gradeNode {
char lastName[20];
float grade;
560
Глава 12
struct
gradeNode
*nextPtr;
} ;
struct gradeNode GRADENODE;
GRADENODE *GRADENODEPTR;
typedef
typedef
a) Создайте
сок пустой.
указатель на начало списка с названием startPtr. Спи¬
b) Создайте новый узел
GRADENODE, на который указывает
Присвойте строку "Jones" элемен¬
значение 91.5 элементу grade (используйте strcpy).
необходимые объявления и операторы.
типа
newPtr типа GRADENODEPTR.
ту lastName и
Напишите
все
c) Предположим, что список, на который указывает startPtr, в на¬
стоящий момент состоит из двух узлов, один из которых содержит
"Jones , а второй "Smith . Узлы расположены в алфавитном по¬
рядке. Напишите операторы, необходимые для того, чтобы вста¬
вить (с сохранением порядка) узлы, содержащие следующие дан¬
ные для lastName и grade:
"Adams"
85.0
"Thompson"
"Pritchard"
73.5
66.5
Для выполнения вставок воспользуйтесь
currentPtr и newPtr. Выясните, на что
previousPtr,
previousPtr и
указателями
указывают
currentPtr перед каждой вставкой. Считается, что newPtr всегда
на новый узел, и что новому узлу уже были присвоены
указывает
данные.
d) Напишите
цикл
while, который выводит на печать данные,
Для перемещения по списку
дящиеся в каждом узле списка.
нахо¬
испо¬
льзуйте указатель currentPtr.
e) Напишите
цикл while, который удаляет все узлы списка и осво¬
бождает распределенные под узлы блоки памяти. Используйте ука¬
затель currentPtr и указатель tempPtr для перемещения вдоль
списка и
12.5.
освобождения
памяти
Выполните вручную проходы
женному
на рис.
12.22,
с
соответственно.
по двоичному дереву поиска,
порядковой, предварительной
ной выборкой.
Рис. 12.22. Двоичное дерево
поиска с 15-ю узлами
изобра¬
и отложен¬
Структуры
561
данных
Ответы на упражнения для самоконтроля
12.1.
а) ссылающаяся, b) malloc, с) Стек,
шел
первым вышел, f) связкой,
12.2.
В
d) предикатами, e) первым при¬
g) free, h) Очередь, i) Дерево, j)
последним пришел, первым вышел, k) двоичного, 1) корневым, m)
потомка, поддерево, n) листом, о) с порядковой выборкой, с предва¬
рительной выборкой, с отложенной выборкой.
связанном
списке
узел
так же можно удалить
ться
12.3.
и
удаляться
только
узлы
можно
вставлять
как
вставка,
так
и
GRADENODEPTR
newPtr
в
и
точно
как на голову, так и на хвост, по¬
хвост,
удаление
а) GRADENODEPTR startPtr
b)
любом месте,
а
удалять
из
головы.
Стек
на вершину стека, где и выполняют¬
имеет всего один указатель
ся
в
стеке же узлы могут вставля¬
сверху.
У очереди существуют указатели
этому
12.4.
вставить
можно
любой узел. В
=
узлов.
NULL;
newPtr;
malloc(sizeof(GRADENODE));
strcpy(newPtr->lastName, "Jones");
91.5;
newPtr->grade
newPtr->nexPtr
NULL;
=
=
=
c) Вставка "Adams":
previousPtr равен NULL,
currentPtr указывает на первый элемент
списка.
newPtr->nextPtr
starPtr
Вставка
previousPtr
"Smith")
currentPtr
=
currentPtr;
newPtr;
=
"Thompson":
указывает на последний элемент списка
(содержащий
равен NULL
newPtr->nextPtr
=
currentPtr;
=
previousPtr->nextPtr
newPtr;
Вставка "Pritchard":
указывает на узел, содержащий "Jones"
currentPtr указывает на узел, содержащий "Smith".
previousPtr
newPtr->nextPtr
=
currentPtr;
previousPtr->nexPtr
d)
currentPtr
while
=
newPtr;
=
startPtr;
(currentPtr != NULL)
{
%s\nGrade
%6.2f\n",
currentPtr->lastName, currentPtr->grade);
currentPtr
currentPtr->nextPtr;
printf("Lastname
=
=
=
}
e)
currentPtr
while
=
startPtr;
!= NULL)
(currentPtr
{
currentPtr;
tempPtr
currentPtr
currentptr->nextPtr;
=
=
free(tempPtr);
}
startPtr
=
NULL;
Глава 12
562
12.5.
Обход с
11
18
Обход
49
с
28
Обход
11
с
19
порядковой выборкой
19
28
32
40
44
49
дает:
71
69
72
предварительной выборкой
18
11
19
40
32
44
71
83
92
97
99
72
97
92
99
99
97
83
49
83
дает:
69
отложенной выборкой дает:
18
32
44
40
28
71
69 72
92
Упражнения
12.6.
Напишите программу, выполняющую конкатенацию двух
Программа
ных списков символов.
должна включать
связан¬
функцию
con¬
передаются указатели на
оба списка, и она присоединяет второй список к первому.
catenate, которой
12.7.
в
качестве
аргументов
Напишите программу, которая объединяет два упорядоченных спи¬
ска целых чисел в один упорядоченный список. Функция merge
должна получать указатели на первый узел каждого списка, кото¬
рые необходимо объединить, и вернуть указатель на первый узел
объединенного списка.
12.8.
Напишите программу, которая вставляет 25 случайных целых зна¬
чений от 0 до 100 в упорядоченный связанный список. Программа
должна вычислять сумму элементов и среднее значение,
должно быть числом с
12.9.
Напишите программу, которая
символов,
которое
плавающей точкой.
создает
связанный
из
список
10
после чего создает копию списка с элементами, разме¬
щенными в
обратном
порядке.
12.10.Напишите программу, которая считывает строку текста и использу¬
ет стек для того, чтобы распечатать эту строку в обратном порядке.
программу, которая использует стек для того, чтобы
определить, является ли строка палиндромом (т.е. пишется по бу¬
12.11.Напишите
кам
одинаково
в
и в обратном направлении). Программа
пробелы и знаки пунктуации.
прямом
должна игнорировать
12.12.Стеки используются компиляторами
в процессе оценки
и
следующем
выражений
упражнении
мы
как
вспомогательный
выясним,
как
компиляторы
арифметические выражения, состоящие
знаков операций и скобок.
вают
Люди обычно
элемент
и создания машинного кода.
записывают выражения в виде
только
3 + 4
и
из
В этом
оцени¬
констант,
7 / 9,
т.е. знак
данном случае это + или
/) записывается между опе¬
операции (в
рандами; такая запись называется инфиксной нотацией. Компью¬
теры «предпочитают» постфиксную нотацию, в которой знак опе¬
записывается справа от двух операндов.
Предыдущие
рации
инфиксные выражения в постфиксной нотации будут выглядеть со¬
ответственно как 3 4 + и 7 9 /.
Для
оценки сложного
сначала
перевести
его
инфиксного выражения компилятор должен
в постфиксную форму, а затем оценить по¬
стфиксный вариант выражения. Каждый из
бует всего одного прохода выражения слева
этих алгоритмов тре¬
направо.
Обоим
алго-
Структуры
563
данных
требуется
ритмам
В
стек
операций,
целей.
выполнения
для
для
используется
алгоритмах
разных
этом упражнении вы напишете реализацию на
инфиксной
упражнении
вы напишете
стек
в
этих
С алгоритма пре¬
постфиксную. В следующем
реализацию алгоритма оценки постфикс¬
в
записи
образования
но
выражений.
Напишите программу, которая преобразует обычное инфиксное
арифметическое выражение (подразумевается, что вводится допус¬
тимое выражение) с однозначными целыми, например, такое:
ных
2)
+
(б
*
5
8
-
/
4
в
постфиксное выражение. Постфиксный вариант предыдущего
фиксного выражения выглядит следующим образом
б
2
+
5
*
Программа
4
8
/
-
должна считывать выражение в массив символов infix
и использовать видоизмененные варианты
ком,
этой главе, чтобы
представленных в
постфиксное выражение
в
символьном
создания постфиксного выражения
1) Ввести в
2) Добавить
3) Пока
ин¬
стек
не
')
действия:
Если текущий символ
в
считывать
пуст,
со сте¬
postfix. Алгоритм
массиве
следующий:
скобку '('.
левую
правую скобку
стек
функций работы
с их помощью сохранить
infix.
конец
infix
слева направо и
выполнять
следующие
postfix.
Если текущий символ
в
infix
цифра, копируйте
infix
левая
его в
следующий
элемент
Если текущий
в
символ
в
infix
скобка, помещайте
знак
ее в стек.
операции,
(если они там есть), пока
операции
равный или более высо¬
кий приоритет по сравнению с текущей операцией, и вставляйте
извлеченные знаки операций в postfix.
извлекайте
знаки
соответствующие
Вставьте текущий
Если текущий
из
символ
стека
имеют
символ
извлекайте знаки
тех пор,
операций
им
в
из
infix
infix
операций
в
стек.
правая скобка,
из стека и
вставляйте
их в
Извлеките из стека левую
скобку
и
отбросьте
ских
операций:
+
сложение
вычитание
/
умножение
деление
^
возведение
%
до
ее.
В выражении допускается использование следующих
*
postfix
пока на вершине стека не появится левая скобка.
взятие
Стек задается
по
с
в
степень
модулю
помощью
struct stackNode {
char data;
struct stackNode
следующих
*nextPtr;
объявлений:
арифметиче¬
Глава 12
564
typedef
struct
typedef
STACKNODE
stackNode
STACKNODE;
*STACKNODEPTR;
Программа должна состоять из функции main
функций со следующими заголовками:
void
convertToPostfix(char
Преобразует инфиксное выражение
int
mt
является
ли
с
знаком
что
постфиксную
нотацию.
операции.
char operator2)
precedence(char operatorl,
Определяет,
других
с)
isOperator(char
Определяет,
восьми
char postfix[])
mfix[],
в
и
операции operatorl меньше, равно
operator2. Функция возвращает соответ¬
старшинство
или выше по сравнению с
ственно -1, 0 или 1.
void push(STACKNODEPTR *topPtr,
Помещает значение
char
в
char value)
стек.
pop(STACKNODEPTR *topPtr)
Извлекает
char
значение
из
стека.
stackTop(STACKNODEPTR topPtr)
значение
Возвращает
в
узле
верхнем
стека,
без
извлечения
этого
значения.
int
isEmpty(STACKNODEPTR topPtr
Определяет,
является
ли
стек
пустым.
prmtStack (STACKNODEPTR topPtr)
void
Выводит
стек
12.13.Напишите
на
печать.
оценивающую постфиксное выражение
программу,
(предполагается,
что
вводится
выражение),
допустимое
например
такое:
б
2
+
*
5
Программа
цифр
8
4
/
-
должна вводить постфиксное выражение, состоящее из
и знаков
операций, в символьный
функций работы со
мененные варианты
массив. Используя видоиз¬
стеком, рассмотренных ра¬
нее в
этой главе, программа должна считывать выражение
вать
его.
1) Добавить
Как только
можно
2) Пока
символ
NULL
встречается
('\0')
постфиксного выражения.
NULL, дальнейшую обработку
в конец
символ
прекратить.
не встретился
Если текущий
'\0'
,
считывать выражение слева направо.
цифра,
символ
поместить ее значение в стек
символа
ла
и оцени¬
Используйте следующий алгоритм:
в
таблице
(значение цифры
равно значению ее
символов компьютера минус значение симво¬
'0').
В противном случае,
извлечь
два
Произвести
если
верхних
вычисление
Поместить результат
текущий
элемента
у
из
символ
стека
операция
вычисления
в
в
x.
стек.
знак
операции,
переменные
x
и
у.
Структуры
565
данных
3) Когда
верхнее
выражении встретится символ NULL, извлечь
в
Оно
значение.
будет
и
из стека
результатом постфиксного выраже¬
ния.
Замечание:
в
2,
находится
8 помещается
обратно
в у,
в стек.
операции
2), если знак операции
следующий элемент в стеке 8,
пункте
и
8 / 2,
вычисляется
Все
и
наверху в стеке
2 помещается в x,
результат 4 заталкивается
равной
вышесказанное в
/',
то
степени относится и к
'-'. В выражении допустимы следующие арифметические
операции:
+
сложение
вычитание
*
умножение
/
деление
^
в
возведение
%
взятие
по
Стек задается
с
степень
модулю
помощью
struct stackNode {
int data;
struct stackNode
следующих
объявлений:
*nextPtr;
};
typedef struct stackNode STACKNODE;
typedef STACKNODE *STACKNODEPTR;
Программа
функций
int
должна состоять
со
следующими
функции main
из
и
восьми
других
заголовками:
evaluatePostfixExpression(char *expr)
Вычисляет постфиксное выражение.
mt
calculate(int
opl,
int
op2,
char operator)
Вычисляет выражение opl operator op2.
void push(STACKNODEPTR *topPtr, int value)
Помещает
int
значение
значение
из
стека.
isEmpty(STACKNODEPTR topPtr)
Определяет,
void
стек.
pop(STACKNODEPTR *topPtr)
Извлекает
mt
в
является
ли
стек
пустым.
printStack(STACKNODEPTR topPtr)
Выводит
стек
на
печать.
12.14.Усовершенствуйте
программу упражнения 12.13, вычисляющую
постфиксное выражение, чтобы она могла работать с целыми опе¬
рандами, большими девяти.
12.15.(Моделирование супермаркета)
делирует
очередь
в
кассу
Напишите программу, которая
супермаркета.
Покупатели
мо¬
подходят
к
кассе через случайно выбранные целые интервалы времени, заклю¬
ченные в диапазоне от одной до четырех минут. На обслуживание
каждого покупателя тратится тоже случайно выбранное целое чис¬
ло минут от одной до четырех. Очевидно, что время, через которое
появляется
новый покупатель,
и время,
затрачиваемое на обслужи¬
Глава 12
566
вание покупателя, должны быть уравновешены. Если в среднем ча¬
стота появления покупателей у кассы больше, чем среднее время
обслуживания, очередь будет бесконечно расти. Даже когда эти две
величины сбалансированы, из-за их вероятностного характера все
равно могут образовываться длинные очереди. Создайте программу
моделирования для 12-ти часового дня (720 минут), используя сле¬
дующий алгоритм:
целое число от одного до че¬
1) Случайным образом выбирается
тырех, для определения минуты, в которую появляется
первый
покупатель.
2) После появления первого покупателя:
Определите время обслуживания покупателя (случайно выбран¬
целое число от 1 до 4).
Начните обслуживание покупателя.
ное
появления следующего покупателя (к
прибавляется случайно выбранное целое число
3) Для каждой минуты рабочего дня супермаркета:
Если появился следующий покупатель,
Найдите время
времени
текущему
от 1 до
4).
поставьте его в очередь.
Определите время появления следующего покупателя.
Если обслуживание очередного покупателя завершено,
извлеките из очереди следующего покупателя,
который
обслуживаться.
Определите время обслуживания покупателя (случайно выбран¬
ное целое число от 1 до 4).
должен
Теперь
запустите ваше моделирование для 720 минут и ответьте на
следующие вопросы:
a) Какое
ди
за
максимальное количество
это
покупателей
оказалось в очере¬
время?
b) Чему равно максимальное время, которое покупателю пришлось
ждать обслуживания?
c) Что произойдет, если интервал появления покупателей поменять
с от одной до четырех минут на от одной до трех минут?
12.16.Измените
программу на рис. 12.19 так, чтобы двоичное дерево мог¬
ло содержать повторяющиеся значения.
12.17.Ha основе
на
представленной
программы,
программу,
на
которая вводит строку текста,
отдельные
вставляет
слова,
слова
в
рис.
двоичное
распечатывает результаты его прохода тремя
вой, предварительной
и
отложенной
12.19,
напишите
разделяет предложение
поиска
дерево
способами:
и
с порядко¬
выборкой.
Подсказка: считайте строку текста в массив. Вызовите strtok для
отделения текущего слова. Когда слово выделено, создайте новый
узел дерева,
string
12.18.B этой
присвойте
нового
узла,
и
указатель,
вставьте
возвращаемый strtok,
узел
в
элементу
дерево.
главе мы увидели, что при создании двоичного дерева поис¬
ка повторы устраняются очень просто.
нили удаление повторов, если
бы
Опишите,
как
бы
использовали просто
вы выпол¬
одномерный
Сравните удаление повторений из массива и ту
же самую операцию в двоичном дереве поиска.
массив переменных.
Структуры
567
данных
12.19.Напишите
depth,
функцию
оно
в
принимающую
принимающую двоичное дерево
и
качестве
аргумента
определяющую, сколько
уровней
содержит.
12.20.(Рекурсивный
вывод
обратном порядке) Напи¬
на печать списка в
функцию printListBackwards, которая рекурсивно выводит
элементы данных в списке в обратном порядке. Используйте вашу
функцию в тестовой программе, которая создает упорядоченный
шите
список
12.21 .(Рекурсивный
и
чисел
целых
распечатывает
его
обратном
в
порядке.
списке) Напишите функцию searchList, ко¬
рекурсивный поиск заданного значения в связан¬
том случае, если оно обнаружено, функция должна
поиск в
торая выполняет
ном списке.
В
возвращать указатель на значение; в противном случае должно воз¬
Используйте вашу функцию в тестовой
список целых чисел. Программа дол¬
создает
программе, которая
жна предлагать пользователю ввести значение, которое необходимо
вращаться значение NULL.
обнаружить
в
списке.
12.22. (Удаление двоичного дерева) В
этом упражнении мы обсудим удале¬
ние элементов данных из двоичного дерева поиска. Алгоритм уда¬
ления не так прост, как алгоритм вставки. При удалении элемента
данных возможен один из трех случаев: элемент данных содержит¬
ся в листе
ного
узле, не имеющем
(т.е.
потомка,
или
Если удаляемый
указателю
в
его
Если удаляемый
указатель
узел,
мок
в
в
имеющем
узле,
элемент
содержится
родительском
содержащий
в
в
имеющем од¬
лист
листе,
присваивается
узле устанавливается
В
элемент данных, удаляется.
дереве
место
Третий случай наиболее
двух потомков,
узле
двух
в узле,
потомков.
удаляется,
удаленного
сложен.
на потомка,
то
а
этом случае пото¬
узла.
Если удаляется узел,
его место должен занять
а
NULL.
элемент принадлежит узлу с одним потомком,
его родительском
занимает
потомков),
другой
имеющий
узел дерева. Однако
нельзя просто присвоить указателю в родительском узле указатель
на одного из потомков узла, который должен быть удален. В боль¬
шинстве случаев получающееся в результате дерево не будет удов¬
летворять следующему критерию двоичного дерева поиска: значе¬
ния в любом левом поддереве должны быть меньше, чем значение в
родительском узле,
быть больше,
Какой
чем
а
значения в
значение
же узел должен
в
быть
любом правом
родительском
поддереве должны
узле.
использован в качестве замещающего
узла, чтобы удовлетворить этому условию? Ответ такой: или узел,
содержащий наибольшее из всех значений в дереве, меньшее, чем
значение в удаляемом узле,
всех
значений
в дереве,
или узел,
большее,
чем
содержащий
значение в
наименьшее из
удаляемом узле.
рассмотрим узел с меньшим значением. В двоичном дереве
поиска наибольшее значение, меньшее, чем родительское значе¬
Давайте
ние, расположено
в левом поддереве родительского узла и гаранти¬
рованно будет содержаться в самом правом узле поддерева. Этот
узел можно найти, спускаясь вниз по левому поддереву вправо до
тех пор, пока указатель на правого потомка в текущем узле не бу¬
дет равен NULL. Это и есть
интересующий
нас узел,
который будет
Глава 12
568
или листом, или узлом, имеющим только одного потомка, причем
слева. Если замещающий узел
лист, то для удаления необходимо
выполнить
шаги:
следующие
1) Сохранить указатель на
(этот
временном указателе
узел, который необходимо удалить, во
указатель будет использован для осво¬
бождения выделенной динамически памяти).
2) Установить указатель в родительском, по отношению к удаляе¬
мому, узле, на замещающий узел.
3) Установить указатель в родительском, по отношению к замеща¬
ющему, узле на NULL.
4) Установить указатель на правое поддерево в замещающем узле
на правое поддерево удаляемого узла.
5) Удалить узел, на который указывает
временный
указатель.
Шаги, которые необходимо выполнить при удалении в случае, если
заменяющий узел имеет левого потомка, такие же, как и при заме¬
не узла, не имеющего потомков, но алгоритм, кроме этого, должен
переместить потомка в позицию замещающего узла. Если замеща¬
ющий узел
узел с левым потомком, то для удаления необходимо
выполнить следующие шаги:
1) Сохранить указатель на узел, который должен быть удален, во
временной переменной.
2) Установить указатель в родительском, по отношению удаляемо¬
му, узле на замещающий узел.
3) Установить указатель в родительском, по отношению к замеща¬
ющему, узле на левого потомка замещающего узла.
4) Установить
указатель на правое поддерево в замещающем узле
на правое поддерево удаляемого узла.
5) Удалить узел, на который указывает временный указатель.
Напишите функцию
deleteNode, которая принимает в качестве ар¬
корневой узел дерева и значение, которое
необходимо удалить. Функция должна находить в дереве узел, со¬
держащий указанное значение, и применить обсуждавшийся выше
гументов указатель на
алгоритм для удаления узла.
обнаружено, функция
ние.
Модифицируйте
Если требуемое значение
в дереве не
должна напечатать соответствующее
сообще¬
программу на рис. 12.19 так, чтобы она
вызывала эту функцию. После удаления элемента данных вызовите
функции обхода inOrder,
preOrder
и
postOrder
для проверки кор¬
ректности удаления.
12.23.(Поиск
в
двоичном дереве) Напишите функцию binaryTreeSearch,
которая будет
искать в двоичном дереве заданное значение.
Функ¬
ция должна принимать в качестве аргументов указатель на корне¬
вой узел двоичного дерева поиска и ключевое значение, которое не¬
обходимо найти. Если узел, содержащий
обнаружен, функция
тивном случае
12.24.(Обход
функция
двоичного
ключевое
значение,
должна вернуть указатель на этот узел; в про¬
дерева
должна возвратить NULL.
по
уровням) Программа
на рис.
12.19
ил¬
люстрирует три рекурсивных метода обхода двоичных деревьев: с
порядковой, предварительной и отложенной выборкой. В этом
упражнении
обход
двоичного дерева производится по уровням, т.е.
Структуры
569
данных
значения узлов выводятся на печать ряд за рядом, начиная от кор¬
невого узла. Узлы каждого уровня печатаются слева направо. Об¬
ход по уровням не является рекурсивным алгоритмом. Он исполь¬
зует структуру данных типа очереди для управления выводом
значений узлов.
Алгоритм
заключается
1) Поместить корневой узел
2) Пока в очереди остаются
прочитать следующий
в
следующем:
в очередь.
узлы,
узел в очереди
распечатать значение в узле
если указатель на левого потомка узла не NULL
вставить левого потомка в очередь
если указатель на правого потомка не NULL
вставить правого потомка в очередь.
функцию levelOrder, выполняющую обход двоичного
дерева по уровням. Функция должна принимать в качестве аргу¬
мента указатель на корневой узел двоичного дерева. Измените про¬
Напишите
грамму на рис.
12.19 так, чтобы можно было протестировать эту
Сравните результаты вывода функции с результатами
функцию.
других алгоритмов обхода, чтобы убедиться, что она
вильно. (Примечание: вам также придется изменить
эту
ную
рис.
пра¬
функцию, обрабатывающую очередь, представлен¬
программу
на
работает
и включить в
12.13).
на печать) Напишите рекурсивную функцию outputTree для изображения на экране двоичного дерева. Функция
должна выводить дерево ряд за рядом, вершина дерева должна рас¬
полагаться в левой части экрана, а его низ справа. Каждый ряд вы¬
12.25.(Вывод деревьев
водится
рис.
вертикально.
Например, изображение
двоичного дерева на
12.22 должно выглядеть на экране так:
99
97
92
83
72
71
69
49
44
40
32
28
19
18
11
Отметим,
что
крайний
самой правой колонке,
справа листовой узел появляется вверху в
а корневой узел появляется в левой части
Каждый столбец выводится через пять пробелов от преды¬
дущего. Функция outputTree должна получать в качестве аргумен¬
тов указатель на корневой узел дерева и целое число totalSpaces,
задающее число пробелов перед очередным столбцом (эта перемен¬
экрана.
ная должна иметь начальное значение равное нулю, чтобы корне¬
вой узел выводился с левого края экрана). Функция использует мо¬
Глава 12
570
дифицированный обход дерева
начинается в крайнем правом узле
с
порядковой выборкой. Он
дерева, от которого перемещает¬
ся влево. Алгоритм выполняется следующим образом:
Пока указатель на текущий узел не NULL
рекурсивно вызовите outputTree с правым
totalSpaces+5
воспользуйтесь структурой for для
totalSpaces и вывода пробелов
текущего
поддеревом
и
узла
подсчета
1 до
от
выведите значение в текущем узле
установите указатель текущего узла на левое поддерево
текущего узла
totalSpaces
увеличьте значение
5.
на
Специальный раздел: как самому написать компилятор
В упражнениях 7.18
на
(SML)
и
и
7.19,
мы ввели
Машинный
симулятор
«связываются»
воедино
будем
Мы
рования.
отдельные
моменты
и
запускать
созданном
в
уровня,
вать
одной
if/goto
и
мы начнем
тоже
похожий
на
время
разрабатывать компилятор,
достаточно мощный, язык
версии
первые
популярного
Simple. Каждый оператор
его язык
возрастающем
из
мы здесь со¬
языка
состоит из но¬
инструкции Simple. Номера строк должны следо¬
и
строки
в
который
7.19.
упражнении
BASIC. Назовем
программи¬
процесса
компилированные программы на симуляторе,
12.26.(Язык Simple) Прежде чем
мы обсудим простой, но в
высокого
на
этом разделе
писать программы на языке высокого уровня,
компилировать их с помощью компилятора,
здадим,
написанные
SML. В
в
языке программирования высокого уровня,
Симплетро¬
мы создадим компи¬
который будет транслировать программы,
лятор,
мера
язык
выполняющий про¬
компьютера,
граммы, написанные на SML. В этом разделе
создали
Каждая инструкция
порядке.
начинается
с
команд Simple: rem, input, let, print, goto,
end (см. рис. 12.23). Все команды, за исключением end,
следующих
в программе многократно. Simple оперирует то¬
лько с целыми выражениями, включающими операции +, -, * и /.
могут встречаться
Эти операции
операции
жении,
имеют
такое
же
С. Для того, чтобы
можно
использовать
старшинство,
и
как
аналогичные
изменить порядок вычисления в выра¬
скобки.
Наш компилятор Simple распознает только буквы нижнего регист¬
ра. Все символы в файле Simple должны быть набраны в нижнем
регистре
(буквы, набранные
таксической ошибке,
для
которого
регистр
одной буквы. Simple
на переменных,
ях.
если
в
верхнем
только
они
регистре,
не
входят
приведут
в
к
оператор
игнорируется). Имя переменной
син¬
rem,
состоит
из
не позволяет использовать осмысленные име¬
их
поэтому
желательно
описывать
в
комментари¬
оперирует только с целыми переменными. В нем отсут¬
объявление переменных
простое упоминание имени
Simple
ствует
переменной
в
присвоение
нулевого
программе автоматически вызывает ее
ляет выполнять
значения.
операций
Синтаксис
со строками
языка
(чтение
объявление
Simple
не
и
позво¬
строки, запись стро-
Структуры
571
данных
ки, сравнение двух строк и
чается
(после
т.д.). Если
в
программе на
отличной
команды,
от
Simple
встре¬
rem),
компилятор
генерирует сообщение о синтаксической ошибке. В нашем компи¬
ляторе предполагается, что программа на Simple введена правиль¬
строка
но. В упражнении 12.29 читателю
чтобы
вовать компилятор,
на
будет предложено усовершенст¬
он мог выполнять проверку программы
ошибки.
синтаксические
Команды
Примеры операторов
rem
50
rem this
is
Описания
remark
а
Любой
rem
текст после команды
используется
целей
исключительно для
и
документирования
игнорируется
компилятором
30 input
input
Выводит
x
на
предлагая
число
Считывает число,
и
клавиатуры,
80
let
let
4
=
u
*
(j
56)
-
Присваивает
4
*
(j
знака
10 print
goto
70 goto 45
if/goto
35 if
i
=- z
значение
целое
x
выражения
что справа от
может стоять
равенства
выражение
степени сложности
Выводит
w
u
ввести
введенное с
сохраняет в
56) Отметим,
-
любой
print
знак вопроса,
экран
пользователю
на экран
значение w
Передает управление строке 45
goto 80
Сравнивает i
и
z
на
предмет
передает управление
условие истинно,
выполняется
строке
равенства
80,
и
если
в противном случае
следующий
if/goto
за
оператор
99 end
end
Прерывает исполнение программы
Рис. 12.23. Команды
Simple
Simple использует операторы условного (if/goto) и безусловного
(goto) переходов для изменения порядка выполнения операций в
процессе выполнения программы.
граммы.
В операторе
шений: <, >, <=, >=,
же,
как
в
языке
if/goto
==
его
демонстрирующих
12.24)
в
в операторе
операторе
на
Программа
про¬
допустимы следующие операции отно¬
или !=.
Старшинство
операций
этих
несколько
программ
возможности.
считывает два целых
числа,
Первая
на языке
такое
печать
на рис.
их
сумму
введенных
с
клавиатуры,
b, после чего вычисляет
(хранящуюся в переменной с).
12.25 определяет и
Числа вводятся с
из двух целых чисел.
Simple,
(на
программа
сохраняет их значения в переменных а и
выводит
if/goto
строке
С.
Давайте рассмотрим теперь
рис.
Если условие
заданной
истинно, управление передается
выводит на печать
и
большее
клавиатуры и сохраняются
Оператор if/goto проверяет на истинность усло¬
вие s >= t. Если оно истинно, управление предается строке 90 и на
экран выводится значение s; в противном случае выводится значе¬
в переменных s и t.
572
Глава 12
ние t, управление передается оператору end в стоке 99 и выполне¬
ние программы завершается.
10
rem
15
rem
20
rem
30
determine
and
print the
the
two
integers
mput
mput а
sum
of
two
mtegers
b
40
mput
45
rem
50
rem
add
60
let
с
65
rem
mtegers
=
а
+
and store
result
in
с
b
rem print
the result
80 prmt с
70
90
rem
99
end
terminate
Определение суммы двух
Рис. 12.24.
10
rem
20
input
s
30
mput
rem
t
32
35
rem
40
if
45
rem
test
if
>=
goto
50
rem
60
print
70
goto
rem
75
is
t
>=
s
prmt
the
целых
of
larger
two
mtegers
t
90
greater
than
s,
so
greater
than
or
equal
print
t
t
99
80
rem
90
prmt
end
99
and
determme
s
execution
program
s
is
to
t,
so
prmt
s
s
Рис. 12.25. Нахождение большего из двух целых
Simple
while
конструкций (таких как for,
С). Однако язык позволяет эмулировать
конструкций С, используя операторы
не поддерживает циклических
do/while
или
любую
if/goto
из
в языке
циклических
goto. На
рис. 12.26 показана программа, использующая
контрольным значением для вычисления квадратов
вводимых чисел. Каждое число вводится с клавиатуры и сохраня¬
ется в переменной j. Если вводится контрольное значение -9999,
цикл
и
с
управление передается строке 99
и программа завершается. В про¬
тивном случае переменной k присваивается значение переменной j,
возведенное в квадрат, k выводится на экран и управление переда¬
ется строке 20, где вводится следующее число.
Используя программы на рис. 12.24, рис. 12.25 и рис. 12.26 в каче¬
стве образца, напишите на Simple программы, позволяющие вы¬
полнять следующие действия:
a) Ввести три числа, определить для них среднее значение и вывес¬
ти результат на печать.
b) Используя
цикл с контрольным значением, ввести десять чисел,
вычислить их сумму и вывести полученное значение на печать.
Структуры
573
данных
10
rem
20
23
input
rem
25
rem
30
if
33
rem
35
rem
calculate
40
let
k
50
print
rem
53
calculate
test
for
==
-9999
j
55
rem
60
goto
99
end
the
of
squares
several
integers
3
=
j
*
sentmel
goto
value
99
square
of
and
j
assign
result
k
to
3
k
loop
20
to
get
next
3
Рис. 12.26. Вычисление квадрата вводимых пользователем чисел
c) Используя
цикл с управлением по счетчику, ввести семь чисел,
вычислить их сумму и вывести полученное значение на печать.
d) Ввести
ряд чисел, определить среди них наибольшее и вывести
его на печать. Первое введенное число должно показывать, сколько
чисел должно быть
обработано.
e) Ввести десять чисел и вывести на печать наименьшее
f) Вычислить и вывести на печать сумму четных чисел от 2
g) Вычислить
1 до 9.
из них.
до 30.
и вывести на печать произведение нечетных чисел от
12.27.(Создание
нить
компилятора. Предварительные требования: выпол¬
упражнения 7.18, 7.19, 12.12, 12.13 и 12.26) Теперь, когда опи¬
сан язык
Simple (упражнение 12.26),
мы
обсудим,
как нам создать
Сначала рассмотрим процедуру, посредством
которой программа Simple преобразуется в код SML и выполняется
для него
компилятор.
Симплетрона (см.
Simple, считывается
рис.
симулятором
грамму на
12.27). Файл, содержащий про¬
преобразуется в код
компилятором и
SML. SML-код выводится в файл на диске,
SML на строку. Затем SML-файл загружается
рона, причем результат
на
экран.
Заметим,
что
записывается в
программа
файл
по
одной инструкции
в симулятор
Симплет¬
на диске и выводится
Симплетрона, разработанная в
Поэтому ее не¬
упражнении 7.19, принимает данные с клавиатуры.
обходимо доработать
для считывания из
полнять
созданную
Рис. 12.27.
программу,
Запись, компиляция
нашим
файла, чтобы
она могла вы¬
компилятором.
и выполнение программы на языке
Simple
574
Глава 12
Компилятор выполняет преобразование программы из Simple в SML
за два прохода. Во время первого прохода создается таблица симво¬
лов, в которой любые номера строк, имена переменных и констан¬
ты программы Simple сохраняются со своим типом и соответствую¬
щим положением в конечном коде SML (таблица символов будет
детально обсуждаться ниже). Кроме того, во время первого прохода
для каждого оператора Simple вырабатываются соответствующие
инструкции SML. Как будет видно из дальнейшего, если программа
Simple
содержит
расположенной
мы
после
прохода
а
ются,
оператор,
ниже, часть
первого
который передает управление строке,
инструкций полученной SML-програм-
прохода окажутся
Во время второго
неполными.
неполные инструкции находятся компилятором и дополня¬
конечная
Первый
программа
SML выводится
файл.
в
проход
Компилятор начинает считывание операторов программы на Simple
в память. Для обработки и последующей компиляции строки дол¬
жны быть разделены на лексемы (т.е. «части» оператора; для этих
целей
быть
может
ция strtok). При
начинается
с
использована
этом
переменной
Номер
символов.
если
это
структур
первая
стандартная
следует
номера строки,
дробления оператора
строки,
не
забывать,
если
константой,
каждый оператор
команде.
По мере
лексема является
номером
предшествующего
на лексемы,
или
библиотечная функ¬
что
она
помещается
строки помещается в таблицу символов, только
лексема оператора. symbolTable
это массив
tableEntry,
представляющих символы программы.
ничения на число символов в программе отсутствуют.
но,
для
таблицу
в
отдельных программ
symbolTable
может
Огра¬
Следователь¬
оказаться
очень
большим. Определим на данный момент symbolTable как массив из
100 элементов. Вы можете увеличить или уменьшить этот размер,
если
программа
работает.
Структура tableEntry определяется
tableEntry
mt symbol;
char type;
/*
mt
location;
образом:
следующим
{
struct
'C',
/
*
'L',
00
to
99
or
'V'
*/
*/
}
Каждая структура tableEntry содержит три
symbol целого
типа содержит
Элемент
элемента.
ASCII-представление переменной (на¬
помним, что имена переменных состоят из одного
символа), номер
это одна из следующих
строки или константу. Элемент type
букв, обозначающих тип символа: C'
для константы, 'L'
для
для переменной. Элемент location содер¬
номера строки или 'V'
жит номер ячейки памяти
ссылается
Симплетрона (от 00
Память Симплетрона
хранятся инструкции SML
символ.
это
до
99),
массив
на которую
из
100 це¬
данные. Для номера
котором
строки ячейкой памяти является элемент в массиве памяти Симп¬
летрона, в котором начинаются инструкции SML для оператора
лых,
в
Simple. Для переменной
или
или
константа.
ячейкой
памяти
является
Симплетрона, в котором хранится пере¬
Переменные и константы размещаются от
элемент в массиве памяти
менная
константы
и
Структуры
575
данных
конца памяти
Таблица
к ее началу. Первая переменная или
ячейке 99, следующая 9d и так далее.
Симплетрона
константа сохраняется
в
символов играет центральную роль в
преобразовании
напи¬
программ в код SML. В главе 7 мы узнали, что ин¬
SML
представляет собой четырехзначное целое число, со¬
струкция
кода операции и операнда. Код операции
стоящее из двух частей
санных на
Simple
ответствует коду
Simple input со¬
Simple print
это ячей¬
(операция записи). Операнд
Simple. Например,
определяется командой
соответствует коду SML 11
ка памяти,
ция
(т.е.
команда
SML 10 (операция чтения),
а команда
над которыми выполняется опера¬
содержащая данные,
код операции 10 считывает значение, введенное с клавиа¬
и сохраняет его в заданной операндом ячейке памяти).
Компилятор производит поиск в symbolTable, чтобы найти соответ¬
ствующую символу ячейку памяти Симплетрона, которая затем ис¬
туры,
пользуется для окончательного формирования
Компиляция
Simple
каждого оператора
нем команды.
Например,
таблицу
после того,
тора rem вставляется в
инструкций SML.
зависит от
содержащейся
в
как номер строки для опера¬
символов, оставшаяся часть опера¬
комментарий служит
Операторы input, print, goto
тора игнорируется компилятором, поскольку
только для целей документирования.
и
к
end соответствуют инструкциям SML read, write, branch (переход
определенной ячейке памяти) и halt соответственно. Операторы,
содержащие эти команды
инструкции
разрешенную
ся,
Simple, преобразуются непосредственно в
что оператор goto может содержать не¬
если номер строки, на который он ссылает¬
в файле программы Simple; так называемая
SML (отметим,
ссылку,
находится дальше
вперед).
ссылка
Когда оператор goto компилируется
неразрешенной ссылкой,
с
ин¬
струкция SML должна быть отмечена, чтобы во время второго про¬
хода компилятор дополнил инструкцию. Такие метки хранятся в
flags типа int, состоящем из 100 элементов, в котором
каждому элементу при инициализации присвоено значение -1.
Если ячейка памяти, на которую ссылается номер строки програм¬
мы Simple, еще неизвестна (т.е. ее нет в таблице символов), номер
массиве
flags в элементе с тем же индексом,
Операнд незаконченной инструк¬
ции временно устанавливается равным 00. Например, инструкция
безусловного перехода (выполняющая ссылку вперед) остается в
строки сохраняется
в массиве
что и незаконченная инструкция.
виде
+4000 до второго прохода компилятора. Далее
остановимся
на
втором
Компиляция операторов
гими операторами
в
более
тор
проходе
if/goto
генерирует
код
и
let
проверки
коротко
сложнее по сравнению с дру¬
это единственные операторы,
чем одну инструкцию
мы
компилятора.
транслируемые
SML. Для оператора if/goto
условия
и,
в
случае
компиля¬
необходимости,
Результатом перехода может оказаться
неразрешенная ссылка. Каждую из операций отношения и равенст¬
ва можно смоделировать с помощью инструкций SML перехода по
нулю и перехода по минусу (а возможно, комбинацией из этих двух
переходов).
перехода
на другую строку.
576
Глава 12
оператора let компилятор генерирует код вычисления арифме¬
тического выражения произвольной сложности, состоящего из це¬
Для
лых
и/или
переменных
констант.
В
все
выражении
операнды
и
пробелами. В
упражнениях 12.12 и
алгоритмы преобразо¬
вания инфиксных выражений в постфиксные и вычисления по¬
стфиксных выражений, применяемых в компиляторах для оценки
арифметических выражений. Прежде чем приниматься за разра¬
ботку своего компилятора, вы должны выполнить оба этих упраж¬
нения. Когда компилятор оценивает выражение, он сначала преоб¬
операции
быть
отделены друг от
12.13 были изложены
должны
друга
разует инфиксную нотацию в постфиксную,
вычисляет постфиксное выражение.
и
после
этого
А как компилятор производит машинный код для оценки выраже¬
держит «крючок»,
переменные? Алгоритм постфиксной оценки со¬
который позволяет нашему компилятору созда¬
вать
SML,
ния, содержащего
инструкции
Чтобы
использовать
а
не
выполнять
этот
вкомпиляторе
вычисления.
реальные
«крючок»,
алгоритм
по¬
стфиксной оценки необходимо изменить, чтобы он мог выполнять
поиск в таблице символов каждого символа, который ему встреча¬
ется (и, возможно, вставлять его в таблицу), определять соответст¬
вие символов
мяти
ячейкам
вместо
памяти и помещать в стек эти ячейки па¬
символов.
В
постфиксном
выражении для
ячейки памяти,
выполнения операции из стека выталкиваются две
реализующий операцию машинный код с использо¬
Результат каждого подвы¬
сохраняется во временной ячейке памяти и помещается
и генерируется
ванием этих ячеек в качестве операндов.
ражения
обратно
в стек,
чтобы оценка постфиксного выражения
могла про¬
Когда постфиксная
оценка завершена, ячейка памяти,
содержащая результат, оказывается единственной ячейкой, остав¬
шейся в стеке. Она извлекается оттуда и создается инструкция
должаться.
SML, присваивающая результат переменной, стоящей
ти
оператора
левой
час¬
задачи:
раз¬
в
let.
Второй проход
Во время второго прохода компилятор
решает
все
неопределенные
Разрешение
ссылок
1) Производится
поиск
2) В
происходит
и
выполняет две
выводит
следующим
код
SML
отличных
от
в
файл.
образом:
поиск неразрешенных ссылок в массиве
элементов,
массиве
ссылки
flags (т.е.
-1).
symbolTable ищется структура, содержащая символ,
flags (убедитесь, что это символ типа 'L' для
записанный в массиве
номера строки).
3) Из
элемента структуры
ссылкой
вставляется
содержащая
location
ячейка
неразрешенную
в инструкцию с
памяти
ссылку,
(напомним,
имеет
неразрешенной
что
операнд
инструкция,
00).
4) Повторяются шаги 1, 2 и 3 до тех пор, пока не будет просмотрен
весь массив flags.
После того как процесс разрешения завершен, весь массив, содер¬
жащий код SML, выводится в файл на диске по одной инструкции
SML в строке. Теперь этот файл можно прочитать Симплетроном
Структуры
577
данных
для его исполнения
(после того, как симулятор будет соответствую¬
образом усовершенствован и сможет использовать файл для
ввода данных).
Законченный пример
щим
Следующий прймер иллюстрирует полное преобразование написан¬
ной на Simple программы в SML, так, как оно будет выполнено
компилятором Simple. Рассмотрим программу на Simple, которая
получает с клавиатуры целое число,
го числа. Программа и инструкции
и
суммирует числа от 1 до это¬
SML, созданные
Таблица символов,
прохода, показаны на рис. 12.28.
сле первого прохода, показана на рис.
Программа на Simple
Ячейки памяти
и инструкции SML
5 rem sum 1 to x
10 input x
20 if у
25
rem
30 let у
====
x
==
+1099
считывание x в ячейку 99
нет
rem
01
+2098
загрузка y(98)
02
+3199
вычитание
03
+4200
переход
нет
rem
04
+2098
загрузка у в аккумулятор
05
+3097
прибавление 1(97)
06
+2196
сохранение
во
07
+2096
загрузка из
временной ячейки 96
08
+2198
сохранение аккумулятора в у
нет
rem игнорируется
09
+2095
загрузка
10
+3098
прибавление
11
+2194
сохранение во временной ячейке 94
12
+2094
загрузка из временной ячейки 94
13
+2195
сохранение аккумулятора в t
нет
rem игнорируется
+4001
переход к ячейке 01
нет
rem игнорируется
increment у
у + 1
=
35 rem add у to total
40 let t
=
t + у
45 rem loop у
50 goto 20
Описание
rem игнорируется
x
goto 60
12.29.
нет
00
15 rem check у
после первого
созданная по¬
14
55 rem output result
игнорируется
в
аккумулятор
x(99)
из аккумулятора
по нулю к
неразрешенной ячейке
игнорируется
t(95)
к аккумулятору
временной ячейке 96
в
аккумулятор
у к аккумулятору
60 print t
15
+1195
вывод t на экран
99 end
16
+4300
окончание выполнения программы
п
Рис. 12.28. Созданные после первого прохода компилятора инструкции SML
19
Так 801
Глава 12
Си
Тип
Ячейка памяти
5
00
10
00
'x
99
15
01
20
01
У
98
25
04
30
04
1
97
35
09
40
09
't'
95
45
14
50
14
55
15
60
15
99
16
Таблица
Рис. 12.29.
символов для программы на рис
12 28
Большинство операторов Simple преобразуются непосредственно
одну
инструкцию SML. Исключение
комментарии, оператор if/goto в
ментарии не транслируются в машинные
ки комментария помещается в таблицу
если
в
этой программе составляют
строке 20 и операторы let. Ком¬
в
на
этот
номер
будет
ссылаться
коды.
Однако номер стро¬
на тот случай,
goto или оператор
символов
оператор
if/goto. Строка 20
программы означает, что если условие y==x ис¬
тинно, то управление передается строке 60. Поскольку строка 60
появится в программе позже, во время первого прохода компилято¬
ра 60 не помещается в таблицу символов (номера строк помещают¬
ся в
таблицу символов, только когда они встречаются в качестве
первой лексемы в операторе). Следовательно, невозможно в этот
момент определить операнд в инструкции SML переход по нулю в
ячейке 03 массива инструкций SML. Компилятор помещает 60 в
ячейку 03 массива flags, чтобы показать, что во время второго про¬
хода
компилятора
Мы должны
массиве,
рами
следующей инструкции SML
в
поскольку нет однозначного соответствия между операто¬
Simple
строке
необходимо завершить эту инструкцию.
отслеживать положение
и
инструкциями
20 компилируется
SML. Например, оператор if/goto
в три
да создается инструкция SML,
в
инструкции SML. Каждый раз, ког¬
мы
должны
увеличивать
счетчик
Структуры
579
данных
инструкций,
метим,
перемещаясь
что размер памяти
Simple,
лему для программ
ячейку
в следующую
Симплетрона
SML. От¬
массива
может представлять
проб¬
содержащих большое количество опера¬
торов, переменных и констант. Вполне возможно, что компилятору
не хватит памяти. Чтобы иметь возможность обнаружить такую си¬
туацию, ваша программа должна содержать счетчик данных, что¬
бы отслеживать ячейку памяти в массиве SML, в которой будет со¬
храняться следующая константа или переменная. Если значение
счетчика инструкций больше, чем значение счетчика данных, мас¬
сив SML заполнен. В этом случае процесс компиляции необходимо
прервать
и компилятор должен выдать
процессе
компиляции
обнаружилась
сообщение об ошибке,
нехватка
что в
памяти.
Пошаговый просмотр процесса компиляции
Давайте теперь пройдем процесс компиляции программы Simple,
представленной
на рис.
память
строку
5
первую
1
sum
rem
to
12.28,
по шагам.
Компилятор
считывает в
программы:
x
С помощью strtok выделяется первая лексема оператора
строки (см. в главе 8 описание функций С, работающих со
номер
строка¬
ми). Лексема,
в
целое,
Если
возвращенная strtok, преобразуется с помощью atoi
чтобы символ 5 можно было искать в таблице символов.
обнаружен,
символ не
таблицу. Так как мы
таблице пока нет ни одно¬
таблицу символов, как при¬
он вставляется в
находимся в самом начале программы, в
го
Поэтому 5 вставляется в
(номер строки),
символа.
надлежащее к типу L
ячейка памяти
в массиве
и ему
SML (00). Хотя
присваивается первая
эта строка является ком¬
таблице символов
случай, если на нее будут ссылаться операторы goto или
if/goto). Для оператора rem не создается никаких инструкций
SML, поэтому значение счетчика инструкций не изменяется.
ментарием, для нее все равно выделяется место в
(на
тот
Далее разбивается
10
input
Номер
тип
L
на
лексемы
оператор
x
строки 10 помещается
и ему
в
таблицу
символов
как
имеющий
присваивается первая ячейке массива SML
льку программа начиналась с комментария и счетчик
(00, поско¬
инструкций
по-прежнему равен 00). Команда input означает, что следующая
лексема
переменная (только переменная может присутствовать в
операторе input). Поскольку input непосредственно соответствует
коду операции SML, компилятор просто определяет положение x в
массиве SML. Символ x не обнаружен в таблице символов. Поэтому
он включается в
таблицу
x, получает тип
V,
(данные
начинают
них выделяются в
символов как
ASCII-представление буквы
присваивается ячейка 99 в массиве SML
размещаться с ячейки 99, и ячейки памяти для
и ему
обратном направлении). Теперь
тора можно генерировать код SML.
для этого опера¬
Код 10 (код операции
считыва¬
умножается на 100, и к получившемуся значению при¬
бавляется ячейка таблицы символов, в которой расположен x,
образуя полную инструкцию. Отметим, что инструкция сохраняет¬
ния в
SML)
ся в массиве SML в ячейке 00. Счетчик
инструкций увеличивается
на 1, поскольку была создана одна инструкция SML.
19*
Глава 12
580
Оператор
rem
15
check
разбивается
==
у
x
на лексемы.
В таблице
символов ищется строка с номе¬
строки помещается в таблицу, как
имеющий тип L, и ему присваивается следующая ячейка массива,
01 (напомним, что оператор rem не производит кода, поэтому счет¬
15 (которой
ром
сохраняет свое
инструкций
чик
нет). Номер
там
значение).
Оператор
20
if
==
у
60
goto
x
строки 20 вставляется в
таблицу
ячейку массива SML
01. Команда if означает, что будет производиться проверка на вы¬
полнение условия. Переменная у не обнаружена в таблице симво¬
лов, поэтому она помещается туда, получает тип V и ячейку 98. За¬
разбивается
символов
тем
на
получает тип L
и
if/goto,
и
следующую
SML
инструкции
генерируются
SML отсутствует прямой
в
Поскольку
Номер
лексемы.
для
условия.
для
оператора
его приходится эмулировать, выполняя вычисления с x и у
и ветвление в зависимости от результата.
Если
вычитания
для
if/goto
оценки
эквивалент
x
из
можно
равен
у
нулю,
использовать
поэтому
инструкцию
x равен у, результат
оператора
эмуляции
по
перехода
нулю.
Пер¬
вый шаг требует, чтобы значение у было загружено (из SML ячейки
с
98)
номером
в
го
создается
Для
аккумулятор.
+2098. Следующим
этого создается инструкция
шагом из аккумулятора вычитается x.
инструкция
02 +3199. Значение
в
Для
01
это¬
аккумуляторе мо¬
жет оказаться как нулевым, так и положительным или отрицате¬
Так как рассматривается операция ==, нас интересует
переход по нулю. Сначала в таблице символов ищется ячейка пере¬
льным.
хода (в данном случае 60), которая
ячейку 03
в
щается
+4200 (мы
не
можем
массива
не находится.
flags,
и
Поэтому 60
создается
поме¬
инструкция
03
ячейку перехода, поскольку еще не
ячейку в массиве SML). Счетчик инструкций
указать
присвоили строке 60
увеличивает свое значение до 04.
Компилятор
25
rem
L,
у
номер 25 вставляется
Строка
тип
переходит к оператору
increment
и
таблицу символов, как
04.
Счетчик инструкций
SML-ячейку
получает
в
имеющая
не увели¬
чивается.
Когда происходит разбиение
30
let
у
=
у
+
на
оператора
1
номер строки 30 вставляется в
L
лексемы
таблицу
символов как
имеющий
тип
присваивается ячейка SML 04. Команда let означает, что
данная строка является оператором присваивания. Сначала все
символы строки помещаются в таблицу символов (если они еще
и
ему
там
не
тип
С
на
находятся). Число 1 добавляется
и ему выделяется
справа
формы
в
ражение
от
оператора
в
таблицу
ячейка 97. Далее та часть,
присваивания,
постфиксную. После
(у 1 +). В таблице
переводится
этого вычисляется
как
имеющее
что расположе¬
из
инфиксной
постфиксное
вы¬
символов ищется символ у, и соответст¬
Структуры
581
данных
вующая ему ячейка памяти помещается в стек.
таблице
ищется в
щается
символов и соответствующая
Встретив
в стек.
правый
тель извлекает из стека
здаются
инструкции
04
+2098 (load у)
05
+3097 (add 1)
знак операции
и
1
Символ
также
ячейка также
+, постфиксный
левый операнды,
поме¬
вычисли¬
после чего
со¬
SML
Результат выражения сохраняется
во
временной ячейке
памяти
(96) инструкцией
06
+2196 (store
и адрес
temporary)
временной ячейки
жение оценено, результат
помещается в стек.
Теперь, когда выра¬
в у (т.е. перемен¬
из временной ячейки
необходимо сохранить
ной, стоящей слева бт
=). Поэтому значение
загружается в аккумулятор и аккумулятор сохраняется
мощи инструкций
07
+2096 (load
08
+2198 (store
в у при по¬
temporary)
у)
Читатель, конечно, тут же заметит, что созданные инструкции
SML избыточны. В дальнейшем мы еще вернемся к этой проблеме.
Когда
оператор
35
add
rem
разбивается
на
как
символов,
to
у
total
лексемы,
имеющий
номер
тип
L
строки
и
ему
35
вставляется
выделяется
в
таблицу
ячейка 09.
Оператор
40
let
t
=
+
t
у
30. Переменная t помещается в таб¬
лицу символов, как имеющая тип V, и ей присваивается ячейка
SML 95. Инструкции следуют той же самой логике и формату, что
аналогичен оператору в строке
и для строки 30, и при этом создаются инструкции 09 +2095, 10
+3098, 11 +2194 и 13 +2195. Отметим, что результат выполнения
операции t + у присваивается временной ячейке 94, перед тем как
будет
присвоен t
(95). Читатель,
конечно
инструкции в ячейках 11 и 12 избыточны.
вернемся
к
этой
теме
же,
опять
заметил,
Как уже говорилось,
что
мы
позже.
Оператор
45
rem
loop
у
является комментарием, поэтому строка 45
символов как имеющая тип
L,
и
добавляется
в
таблицу
ей присваивается ячейка SML 14.
Оператор
50
goto
20
передает управление строке 20. Номер строки 50 помещается в таб¬
лицу символов с типом L и ему присваивается ячейка 14. Эквива¬
лентом goto в SML является безусловный переход (40)
инструк¬
ция, передающая управление заданной ячейке SML. Компилятор
просматривает
таблицу
20 и обнаружи¬
Код операции (40) ум¬
символов в поисках строки
вает, что она соответствует ячейке SML 01.
Глава 12
582
ножается на 100 и к нему прибавляется
лучается инструкция 14 +4001.
ячейка 01;
в
результате по¬
Оператор
55
rem
result
output
является комментарием, поэтому строка 55
символов как имеющая тип
L,
и
добавляется
в
таблицу
ей присваивается ячейка SML 15.
Оператор
60 print t
является оператором вывода.
цу
символов
как
Аналогом print
имеющий
Номер
тип
L
строки 60 вставляется
и
в
табли¬
соответствующий ячейке 15.
SML служит код операции 11 (запись). Ячейка,
содержащая t, находится в таблице символов и складывается с ко¬
дом операции, умноженным на 100.
в
Оператор
99 end
последней строкой программы. Номер строки 99 вставля¬
таблицу символов как имеющий тип L, и ему присваивается
ячейка 16. Команда end генерирует инструкцию SML +4300 (43 в
SML означает стоп), которая записывается как последняя инструк¬
является
ется в
ция
в
На
массиве
памяти
первый проход
второй проход. Он
SML.
Теперь рассмот¬
flags значе¬
ний, отличных от -1. Ячейка 03 содержит 60, таким образом, ком¬
что
03 неполна. Компилятор
узнает,
пилятор
инструкция
завершает инструкцию, находя в таблице символов 60, определяя
соответствующую ячейку и прибавляя ее адрес к неполной инст¬
этом
рим
компилятора завершается.
начинается с поиска в массиве
В данном случае оказывается, что 60 соответствует ячейке
SML 15, поэтому 03 +4200 заменяется законченной инструкцией
03 +4215. Теперь компиляция написанной на Simple программы
рукции.
успешно завершена.
Чтобы создать компилятор, вам необходимо решить следующие за¬
дачи:
программу Симплетрона, которую вы напи¬
упражнении 7.19, чтобы она могла считывать данные с за¬
данного пользователем файла (см. главу 11). Кроме того, симуля¬
a) Усовершенствовать
сали
тор
в
должен
формате,
выводить
как
они
результаты
выводятся
b) Модифицировать данный
в
на
в
файл
на
диске
в
том
же
экран.
упражнении 12.12 алгоритм преобра¬
зования инфиксного выражения
в
постфиксное, чтобы
он мог
рабо¬
тать с многозначными целыми операндами и операндами в виде од¬
нобуквенной переменной. Подсказка: для выделения каждой
константы и переменной в выражении можно воспользоваться
стандартной библиотечной функцией strtok, а преобразование кон¬
стант из строк в числа выполнять с помощью стандартной функции
atoi. (Замечание: представление данных в постфиксном выражении
должно быть изменено, чтобы можно было
ременных
и
целыми
константами.)
работать
с именами пе¬
Структуры
583
данных
c) Модифицировать алгоритм вычисления постфиксного выраже¬
ния, чтобы он мог работать с операндами в виде многозначных чи¬
сел и имен переменных. Кроме того, алгоритм должен иметь об¬
суждавшийся выше «крючок», чтобы вместо непосредственного
SML. Подсказка:
вычисления выражения создавались инструкции
чтобы выделить
выражении отдельные константы и переменные,
можно воспользоваться функцией стандартной библиотеки strtok,
в
преобразовать из строк в числа с по¬
функции стандартной библиотеки atoi (Замечание: пред¬
ставление данных в постфиксном выражении должно быть измене¬
но, чтобы была возможность работать с именами переменных и
целыми константами.).
после чего константы можно
мощью
d) Создание компилятора. Объедините части (b) и (с) для оценки
выражений в операторах let. Ваша программа должна содержать
функцию, выполняющую первый проход компилятора, и функ¬
цию, выполняющую второй проход. Обе функции для выполнения
своих задач могут вызывать другие функции.
12.28.(Оптимизация
компилятора
Simple) После
того,
как программа
SML, мы получили набор
преобразована
инструкций. Некоторые комбинации инструкций часто повторяют¬
была компилирована и
ся,
обычно
как
в триплетах,
правило,
прибавить
и
состоят
из
в
порождениями. Порождения,
инструкций, таких как загрузить,
называемых
трех
сохранить. Например,
на
рис.
12.30 представлены
инструкций SML, созданных при компиляции программы из
рис. 12.28. Первые три инструкции составляют порождение, кото¬
рое прибавляет 1 к у. Заметим, что инструкции 06 и 07 сохраняют
значение, находящееся в аккумуляторе, во временной ячейке 96,
пять
после чего вновь загружают это значение в аккумулятор,
струкция
08
рождением
могла сохранить
следует
значение
в
ячейке
инструкция загрузки той
же
чтобы
ин¬
98. Часто за по¬
самой ячейки, в
которую только что производилась запись. Такой код можно опти¬
мизировать путем удаления инструкции сохранения и следующей
непосредственно за ней инструкции загрузки, оперирующей с той
же самой
ячейкой
рону быстрее
памяти.
выполнять
Такая оптимизация
программу,
так
как
в
позволит
Симплет-
ней будет
меньше
инструкций. На
рис. 12.31 показана оптимизированная программа
SML, полученная из представленной ранее на рис. 12.28. Отметим,
что в оптимизированном коде на четыре инструкции меньше, так
что экономия памяти составляет 25%.
04
05
+2098
+3097
(load)
06
+2196
(store)
07
+2096
+2198
(load)
08
(add)
(store)
Рис. 12.30. Не оптимизированный код программы, представленной
на рис
12 28
компилятор, введя в него оптимизацию создава¬
емого кода Машинного языка Симплетрона. Вручную сравните оп¬
Усовершенствуйте
тимизированный и не оптимизированный код и посчитайте,
лько процентов оптимизированный код короче.
на ско¬
Глава 12
584
Программа
Ячейки
Simple
на
Описание
памяти
и инструкции SML
5
sum 1
rem
10
input
to x
x
==
if
x
у
25
rem
30
let
rem
i1099
считывание x
нет
rem
01
+2098
загрузка y(98)
02
+3199
вычитание
03
+4211
переход по
нет
rem
04
+2098
загрузка у в аккумулятор
05
+3097
прибавление 1(97)
06
+2198
сохранение аккумулятора в
нет
rem игнорируется
07
+2096
загрузка t из ячейки
08
r3098
прибавление y(98)
09
+2196
сохранение аккумулятора
нет
rem
+4001
переход
нет
rem
11
+1196
вывод t(96)
12
+4300
окончание выполнения
00
15 rem check у
20
нет
= =
x
goto 60
increment
у
у + 1
=
у
35 rem add у to total
40 let t
=
t + у
45
rem
50
goto 20
55
rem
60
print t
loop у
10
output result
99 end
Рис. 12.31.
Оптимизированный
12.29.(Усовершенствование
дующие
игнорируется
ячейку
в
99
игнорируется
в
x(99)
аккумулятор
из аккумулятора
к ячейке 11
нулю
игнорируется
к
аккумулятору
y(98)
(96)
к аккумулятору
в
t(96)
игнорируется
к ячейке 01
игнорируется
на экран
программы
код для программы на рис. 12 28
компилятора
языка
Simple)
Выполните
модификации компилятора Simple. Некоторые
сле¬
из них мо¬
гут также потребовать усовершенствования программы-симулятора
Симплетрона, написанной в упражнении 7.19.
a) Сделать
(%)
возможным использование операции взятия по модулю
в операторе
язык
let. Необходимо
Симплетрона,
дополнив его
внести изменения и в
инструкцией
Машинный
для взятия по моду¬
лю.
b) Сделать возможным использование операции возведения в сте¬
пень (^) в операторе let. Необходимо внести изменения и в Машин¬
ный язык Симплетрона, дополнив его инструкцией для возведения
в степень.
c) Сделать
букв как вер¬
(т.е. написание 'A' и 'a' должно
приводить к одинаковому результату). Никаких изменений в симу¬
ляторе Симплетрона не требуется.
хнего,
так
возможным распознавание компилятором
и
нижнего
регистра
Структуры
585
данных
d) Сделать
возможным
считывание
оператором
льких переменных, например:
Симплетрона
муляторе
e) Сделать
сразу неско¬
возможным вывод сразу нескольких переменных с помо¬
щью одного оператора
менений
не
input
у. Никаких изменений в си¬
input x,
требуется.
в
симуляторе
f) Ввести проверку
print, например: print а, b,
Симплетрона не требуется.
с.
Никаких
синтаксиса, чтобы компилятор выводил
об ошибке всякий раз, когда
из¬
сообще¬
программе Simple встречается
синтаксическая ошибка. Никаких изменений в симуляторе Симп¬
ние
в
летрона не требуется.
g) Сделать
менений
возможным использование массивов чисел.
в
симуляторе
h) Сделать
Симплетрона
возможным использование
Никаких
из¬
требуется.
не
подпрограмм,
управляемых
return. Команда gosub передает управ¬
ление подпрограмме, а команда return возвращает его обратно опе¬
Simple gosub
командами
ратору,
следующему
функции
за
и
gosub. Это будет
С. Допускается
в
одной
вызов
и
что-то
той
похожее
на вызов
же подпрограммы не¬
gosub, расположенными в разных местах программы.
Никаких изменений в симуляторе Симплетрона не требуется.
сколькими
i) Сделать
for
x
возможным
2
=
to
операторы
next
10
step
использование
структур
повторения
вида
2
Simple
Оператор
строкой for. Ни¬
требуется.
Этот оператор for организует цикл от 2 до 10 с шагом 2.
next отмечает конец тела цикла, начинающегося
изменений
каких
j) Сделать
for
x
2
=
в
to
операторы
next
Симплетрона
симуляторе
возможным
использование
не
структур
повторения
вида
10
Simple
Такой оператор for организует цикл от 2 до 10 с шагом 1. Никаких
в симуляторе Симплетрона не требуется.
возможной
k) Сделать
обработку вывода и ввода строк. При этом по¬
требуется соответствующим образом изменить симулятор Симплет¬
рона, чтобы он мог обрабатывать и хранить данные строкового типа.
Подсказка: каждое слово Симплетрона можно разделить на две час¬
ти, каждая из которых содержит двузначное целое. Каждое двузнач¬
изменений
ное целое представляет
бавьте
инструкцию
десятичный
строку,
начинающуюся
в
Первая
половина
в
слова
эквивалент символа
ASCII. До¬
будет печатать
некоторой ячейке памяти Симплетрона.
машинного
языка,
этой ячейке
которая
числом
символов
содержит
один символ
длиной). Каждое последующее полуслово
ASCII, представленный двумя десятичными
Инструкция
машинного языка проверяет длину и печатает строку,
строки
(т.е.
переводя
каждое
двузначное
число
1) Сделать возможной обработку
вающей точкой. Необходимо
в
соответствующий
цифрами.
символ.
не только целых, но и чисел с пла¬
внести соответствующие изменения и
Симплетрона, чтобы
плавающей точкой.
в симулятор
является
ее
он тоже мог
работать
с числами с
586
Глава 12
12.30.(Интерпретатор Simple) Интерпретатором
которая
называется программа,
считывает операторы языка высокого уровня,
определяет
операции, которые они должны выполнить, и тут же эти операции
Программа не преобразуется предварительно в машин¬
Интерпретаторы работают медленно, поскольку каждый
выполняет.
ный код.
исполняемый оператор программы
вать.
Если оператор помещен
необходимо дешифро¬
дешифровка оператора будет
сначала
в цикл,
каждом его вызове в теле цикла. Ранние версии
языка программирования BASIC были реализованы в виде интерп¬
производиться при
ретаторов.
Напишите
интерпретатор
упражнении
для
языка
Simple, обсуждавшегося
12.26. Для оценки выражений
грамма должна использовать
разработанный
в
упражнении
выражений, разработанный
в
в
операторе let про¬
инфиксно-постфиксный конвертор,
12.12, и вычислитель постфиксных
в упражнении
12.13. Ограничения,
на¬
Simple в упражнении 12.26, должны относиться
и к этой программе. Протестируйте интерпретатор, используя про¬
граммы Simple из упражнения 12.26. Сравните результаты выпол¬
ложенные на язык
нения
ции
этих
программ
программ
разработанном
Simple
в
в
и
интерпретаторе
запуска их
упражнении
7.19.
в
с
результатами
симуляторе
компиля¬
Симплетрона,
г
л
а
в
а
13
Препроцессор
Цели
Научиться применению директивы #include при со¬
здании больших программ.
Научиться применению директивы #define для опре¬
деления макросов и макросов с аргументами.
Познакомиться
с
условной компиляцией.
Научиться выводить сообщения об ошибках
условной компиляции.
с
приме¬
нением
Научиться
использованию
ректности значений
макросов для проверки кор¬
588
Глава 13
Содержание
13.1.
Введение
13.2.
Директива препроцессора #include
13.3.
Директива препроцессора #define:
13.4.
Директива препроцессора #define: макросы
13.5.
Условная компиляция
13.6.
Директивы препроцессора
13.7.
Операции
13.8.
Нумерация строк
13.9.
Предопределенные
13.10.
Макрос подтверждения
символические
константы
#error и #pragma
# и ##
символические
константы
Распространенные ошибки программирования
Хороший стиль
Советы по повышению эффективности
Упражнения
для самоконтроля Ответы на упражнения для самоконтроля Упражне¬
Резюме
программирования
ния
13.1. Введение
Эта
глава
введение в препроцессор
С, выполняющий,
как это следует из
обработку программы до ее компиляции. Вот некоторые из воз¬
можных действий препроцессора: включение других файлов в файл, который
его названия,
будет компилироваться, определение символических констант и макросов,
условная компиляция кода программы и условное выполнение директив пре¬
процессора. Все директивы препроцессора начинаются с #, и только пробель¬
ные символы могут стоять в строке перед этими директивами.
13.2.
Директива
книги.
Директива
препроцессора #include
препроцессора #include использовалась
Директива #include
создает копию указанного
ется в программу вместо директивы.
директивы
#include:
Существует
на протяжении
файла,
две
всей
которая включа¬
формы
использования
589
Препроцессор
#include
<filename>
#include
"filename"
Разница между ними заключается в том, где препроцессор будет искать
которые необходимо включить. Если имя заключено в кавычки, пре¬
процессор ищет его в том же каталоге, что и компилируемый файл. Такую за¬
файлы,
пись
обычно используют для
включения определенных пользователем заголо¬
ис¬
файлов. Если же имя файла заключено в угловые скобки (< и >)
то поиск будет вестись
пользуемые для файлов стандартной библиотеки,
в зависимости от конкретной реализации компилятора, обычно в предопреде¬
вочных
ленных каталогах.
ных
Директива #include чаще всего используется для включения заголовоч¬
файлов стандартной библиотеки, таких как stdio.h и stdlib.h (см.
рис. 5.6). Директива #include
также используется с программами, состоящи¬
которые необходимо компилировать вместе. Часто с
помощью этой директивы включается заголовочный файл, содержащий общие
для отдельных файлов программы объявления. Примерами таких объявлений
файлов,
ми из нескольких
являются
тотипов
В
ды
объявления структур
системе
cc.
и
объединений, перечислимых
констант и про¬
функций.
UNIX программные файлы компилируются посредством коман¬
компилирование и компоновка main.c и square.c произво¬
Например,
дится при помощи команды
cc
main.c
введенной
в
square.c
командной строке UNIX. При
этом создается
a.out. Чтобы получить более подробную информацию
компоновки и выполнения программ,
выполняемый файл
о процессе компиляции,
воспользуйтесь руководством
к вашему
компилятору.
13.3.
Директива препроцессора #define:
символические константы
Директива #define создает
символические
представленные символами, и макросы
лы.
Формат
#define
константы
константы,
операции, определенные как симво¬
директивы #define выглядит следующим
образом.
идентификатор заменяющий_текст
Когда
такая строка появляется в файле, вместо всех последующих появле¬
идентификатора будет автоматически подставлен заменяющий_текст,
перед тем как программа будет компилироваться. Например,
ний
#define
PI
3.14159
заменяет все последующие появления символической константы PI на числен¬
ную константу 3.14159. Символические константы позволяют программисту
создавать имя для константы и использовать это имя в любом месте програм¬
мы. Если необходимо изменить значение константы во всей программе, то до¬
статочно сделать это в одном месте,
в директиве
#define,
и
тически изменены.
будет подставлено
Примечание: при
по
замене вместо именованной константы
все, что стоит справа от нее в
определении #define.
3.14159 приведет к тому, что препроцессор
3.14159. Подобные ошибки
всему тексту программы PI на
пример, строка #define PI
нит
повторной
будут автома¬
после
компиляции программы все вхождения константы в программе
=
=
На¬
заме¬
часто
Глава 13
590
приводят к многочисленным, трудно выявляемым логическим и синтаксиче¬
ским ошибкам. Переопределение символической константы, при котором ей
присваивается новое значение, также является ошибкой.
Хороший
стиль программирования 13.1
Использование осмысленных имен для символических констант делает программу ca-
модокументированной
13.4.
облегчает
и
ее
восприятие
Директива препроцессора #define: макросы
Макрос
операция, определяемая при помощи директивы препроцессо¬
ра #define. Как и в случае символических констант, перед компиляцией про¬
граммы вместо идентификатора макроса в программу подставляется заменяющий_текст. Допускается определение макроса с аргументами или без них.
Макрос без
та.
обрабатывается
аргументов
В макросе
так же, как и символическая констан¬
с аргументами последние подставляются в
заменяющий
текст,
после чего макрос расширяется, то есть в программу подставляется заменяю-
щий_текст вместо идентификатора и списка аргументов.
Рассмотрим следующее макроопределение с одним аргументом
для на¬
хождения площади круга:
#define
СIRCLE_AREA(x)
Всякий раз, когда
в
*
(PI
файле
(x)
*
появляется
(x)
)
CIRCLE__AREA(x),
значение x под¬
ставляется вместо x в заменяющем тексте, символическая константа PI заме¬
няется ее значением
ме.
Например,
area
=
=
и макрос расширяется в програм¬
CIRCLE_AREA(4);
расширяется
area
(определенным ранее),
оператор
(
в
*
3.14159
(4)
*
(4)
);
Так как выражение состоит только из констант, значение выражения оце¬
нивается в процессе компиляции и присваивается переменной area. Скобки, в
которые заключены x в заменяющем тексте,
док вычисления, когда
ние.
Например,
area
=
=
обеспечивают правильный поря¬
аргумента макроса используется
выраже¬
оператор
СIRCLE_AREA(с
расширяется
area
в качестве
+
2);
+
2)
в выражение
*
(3.14159
(с
*
(с
+
2)
);
которое оценивается правильно, поскольку скобки обеспечивают правильный
порядок выполнения операций. Если бы скобок не было, макрос расширился
бы в выражение
area
=
3.14159
*
с
+
2
*
+
с
2;
которое оценивалось бы неправильно как
area
=
(3.14159
*
с)
+
(2
согласно правилам старшинства
*
с)
+
2;
операций.
Распространенная ошибка программирования
13.1
Забывают заключить аргументы в скобки в заменяющем тексте макроса
591
Препроцессор
Вместо макроса CIRCLE_AREA
было использовать функцию. Фун¬
можно
кция circleArea
double circleArea(double x)
{
return 3.14159 * x * x;
}
выполняет те же самые вычисления, что и макрос
зование
ным
функции circleArea,
накладным
CIRCLE__AREA
но исполь¬
расходам.
Преимущества
использования
макроса
состоят в том, что макрос вставляет код непосредственно в про¬
грамму, помогая, таким
вызовом
CIRCLE__AREA,
по сравнению с макросом, приводит к определен¬
функции,
образом, избежать
накладных расходов, связанных с
и при всем при том программа остается
удобочитаемой,
по¬
определено отдельно и названо осмыслен¬
но. Недостатком является то, что аргумент этого макроса оценивается дважды.
CIRCLE__AREA
скольку вычисление
Совет по повышению эффективности 13.1
Иногда вместо вызова функции можно использовать макрос, который встраивает код
в программу до времени исполнения Это исключает накладные расходы, связанные с
вызовом функции
Следующее определение макроса содержит два аргумента и предназначено
для вычисления площади прямоугольника:
#define
RECTANGLE_AREA(x,
Всякий раз, когда
(x, у),
(
у)
(x)
*
в тексте программы
значения x и у подставляются в
(у)
встречается
заменяющий
расширяется в том месте, где стояло его имя.
rectArea
=
RECTANGLE_AREA(а
+
)
b
4,
+
RECTANGLE__AREA
текст макроса, и макрос
Например,
оператор
7);
в
расширяется
=
rectArea
(
(а
+
4)
*
(b
+
7)
)
Значение выражения оценивается
Заменяющий
обычно любой
текст
для
макроса
;
и присваивается
или
переменной rectArea.
символической
константы
это
директиве #define. Если
заменяющий текст для макроса или именованной константы не умещается на
одной строке, в конце строки следует поместить обратную косую черту (\), ко¬
текст в строке после
идентификатора
торая показывает, что текст продолжается на
Символические
в
следующей строке.
константы и макросы можно отменить, используя
дирек¬
тиву препроцессора #undef. Директива #undef отменяет определение симво¬
лической константы или имени макроса. Область действия символической
константы или макроса простирается от места их определения до места отме¬
ны определения с помощью
#undef,
или до конца
файла. После
деления допускается его повторное определение с помощью
отмены опре¬
#define.
Функции стандартной библиотеки иногда определяются как макросы на
других библиотечных функций. В заголовочном файле stdio.h часто
основе
определяют макрос
#define
getchar(
)
getc(stdin)
Макроопределение getchar использует функцию getc, чтобы получить
один символ из стандартного потока ввода. Функция putchar заголовочного
файла stdio.h
и оперирующие с символами
функции
заголовочного
файла cty-
592
Глава 3
pe.h
часто также реализуются в виде макросов.
бочными эффектами (то
Заметим,
есть с изменяемыми значениями
что выражения с по¬
переменных)
не дол¬
жны помещаться в макросы, потому что аргументы макроса могут оценивать¬
ся
более одного раза.
13.5. Условная компиляция
Условная компиляция
директив препроцессора и
позволяет
программисту
управлять
компиляцией программного
кода.
выполнением
Каждая
из услов¬
ных директив препроцессора оценивает значение целочисленного выражения.
невозможна
оценка
выражений приведения
перечислимых констант.
Условные конструкции препроцессора во многом похожи на условный
оператор if. Рассмотрим следующий код препроцессора:
В
директивах
типа,
препроцессора
выражений sizeof
и
#if
!defmed(NULL)
#define NULL 0
#endif
Эти директивы устанавливают,
определен ли
NULL.
Выражение defi-
NULL определен, и 0 в противном случае.
Если результат 0, !defined(NULL) имеет значение 1 и выполняется определе¬
ние NULL. В противном случае директива #define пропускается. Каждая кон¬
это сокра¬
струкция #if заканчивается #endif. Директивы #ifdef и #ifndef
ned(NULL)
имеет значение
1,
если
#if defined(tt^#) и #if !defined(a^#). Условные конструкции пре¬
процессора, проверяющие несколько вариантов, реализуются с помощью ди¬
ректив #elif (эквивалента else if структуры if) и #else (эквивалента else струк¬
щения для
туры if).
При разработке программ программисты, чтобы предотвратить компиля¬
больших кусков программы, часто превращают их в комментарии. Если
цию
сам код программы содержит комментарии, то
/*
и
*/
для этого не подходят. В
этом случае можно использовать следующую конструкцию препроцессора:
#if
0
код,
компиляцию которого
надо
предотвратить
#endif
Чтобы снова разрешить компиляцию кода, следует заменить в представ¬
ленной выше конструкции 0 на 1.
Условная
компиляция часто
служит
средством,
помогающим
программу. Многие реализации С снабжены отладчиками.
Однако
отладить
отладчи¬
ки, как правило, слишком сложны для начинающих программистов, и поэто¬
му новички редко ими пользуются. Вместо этого используют операторы
printf, чтобы
директивы
условной
программы. Если такие операторы
компиляции препроцессора, то их
полняться только в период отладки.
#ifdef
убедиться в правиль¬
printf заключить в
компиляция будет вы¬
вывести на печать значения переменных и
ности хода выполнения
Например,
DEBUG
pnntf("Variable
x
=
%d\n",
x);
#endif
приведет
перед
к тому, что оператор
printf будет
компилироваться
в программе, если
директивой #ifdef DEBUG была определена (#define DEBUG)
ская константа
символиче¬
DEBUG. Когда отладка завершена, директива #define удаляет¬
593
Препроцессор
ся из исходного
файла
и
операторы
printf,
вставленные для
целей
отладки,
при компиляции игнорироваться. В больших программах желательно
определить несколько различных символических констант, чтобы иметь воз¬
будут
можность контролировать условия компиляции отдельных частей исходного
файла.
Распространенная ошибка программирования
13.2
Вставка условно компилируемых операторов printf для целей отладки в места, где С
подразумевает только один оператор В этом случае условно компилируемый фрагмент
должен быть включен в составной оператор Тогда, если программа компилируется с
отладочными операторами, поток управления не изменяется
13.6.
Директивы препроцессора #error
Директива
#error
#error
определенные в директиве.
Лексемы
пробелами. Скажем,
1
#error
-
Out
of
#error,
сообщение,
включая
лексемы,
это последовательности символов, раз¬
директива
error
range
содержит шесть лексем.
директива
#pragma
лексемы
выводит на печать зависящее от реализации
деленные
и
Например,
в
Borland С++ для PC, когда
лексемы в директиве
отображаются
как
выполняется
сообщение об ошиб¬
ке; затем препроцессор останавливается и программа не компилируется.
Директива #pragma
#pragma лексемы
указывает действие, зависящее от реализации. Указание, не распознанное дан¬
ным компилятором, игнорируется. Например, в Borland С++ распознается не¬
сколько указаний, которые позволяют программисту полностью использовать
преимущества, имеющиеся в Borland С++. Более полную информацию относи¬
тельно использования #error и #pragma вы можете узнать из документации к
вашему компилятору
С.
13.7.
Операции
# и ##
Операции препроцессора # и ## имеются только в ANSI С. Операция # вы¬
преобразование текстовой лексемы в строку, заключенную в кавыч¬
полняет
ки.
Рассмотрим следующее макроопределение:
#define
Когда
в
HELLO(x)
файле
"
printf("Hello,
#x
"\n");
программы появляется выражение
HELLO(John),
оно рас¬
ширяется в
pnntf ("Hello,
Строка "John"
"
"John
"\n");
подставляется в
заменяющий
текст вместо #x.
сор производит конкатенацию строк, разделенных
поэтому предыдущий оператор эквивалентен
pnntf("Hello,
John\n");
пробельными
Препроцес¬
символами,
594
Глава 13
Заметим, что операция # должна использоваться в макросах с аргумента¬
ми, поскольку операнд после # ссылается на аргумент макроса.
Операция ## выполняет конкатенацию двух лексем. Рассмотрим следую¬
щее макроопределение:
#define
TOKENCONCAT(x,
Когда TOKENCONCAT
катенация
его
аргументов,
##
x
у)
у
появляется в тексте программы, происходит кон¬
после
пример, T0KENC0NCAT(0,K)
должна иметь два операнда.
13.8.
чего
они
заменяют
Нумерация
На¬
ОК. Операция ##
макроопределение.
заменяется в программе на
строк
Директива препроцессора #line используется для последовательной нуме¬
рации строк исходного кода программы, начиная с числа, заданного констан¬
той. Директива
#line
100
начинает нумерацию строк исходного кода программы со следующей за дирек¬
тивой строки, которой присваивается номер 100. В директиву #line можно
включить имя
#line
100
файла. Директива
"filel.c"
означает, что строки, начиная со следующей за директивой #line, будут прону¬
мерованы, нумерация начнется со 100 и что имя файла для любых сообщений
компилятора "filel.c". Директива обычно используется для того, чтобы сде¬
сообщения компилятора о синтаксических ошибках и предупреждениях
более содержательными. В исходном файле номера строк не появятся.
лать
13.9.
Предопределенные
символические константы
Существует пять предопределенных символических констант (рис. 13.1.)
Идентификаторы каждой из предопределенных констант начинаются и закан¬
чиваются двумя символами подчеркивания. Эти идентификаторы, так же как
и идентификатор defined (упоминавшийся в разделе 13.5), не могут входить в
директивы
#define
или
#undef.
13.10.
Макрос
Макрос подтверждения
assert, определенный
в
заголовочном
файле assert.h,
(false), то
значение выражения. Если значение выражения равно 0
проверяет
assert рас¬
сообщение об ошибке и вызывает функцию abort (из библиотеки
общего
stdlib.h), завершающую работу программы. Это полезный
инструмент отладки, позволяющий проверить, имеет ли переменная верное
значение. Допустим, переменная x в программе никогда не должна превышать
значение 10. Для проверки значения x и выдачи сообщения об ошибке в слу¬
печатывает
назначения
принимает недопустимые значения, можно воспользоваться макро¬
сом подтверждения. Соответствующий оператор будет выглядеть следующим
чае, если x
образом:
595
Препроцессор
assert
(x
<=
10);
больше 10, выдается сообщение,
Если при выполнении этого оператора x
содержащее номер строки и имя файла, а выполнение программы при этом
прерывается. После этого программист может ограничить поиски ошибки дан¬
ной
Если определена символическая
частью кода.
дующие
операторы
будут
подтверждения
константа
игнорироваться.
NDEBUG, после¬
Таким образом,
когда операторы подтверждения уже не нужны, вместо того, чтобы уничто¬
жать их вручную, достаточно в программный файл вставить строку
#define
NDEBUG
Символическая константа
Объяснение
LINE
Номер текущей обрабатываемой строки исходного кода
программы (целая константа)
FILE
Имя компилируемого исходного
Дата
DATE
начала
"Mmm dd
TIME
Время
строка
файла (символьная строка).
компиляции текущего исходного
файла (строка
вида
yyyy", например "Jan 19 1991м)
начала
компиляции текущего исходного
файла (символьная
вида "hh:mm:ss")
Целая константа 1. Показывает, что данная реализация совместима
со стандартом ANSI
STDC
Рис. 13.1.
Предопределенные
символические константы
Резюме
Все директивы препроцессора
Перед директивой
ные
начинаются
с
#.
препроцессора в строке могут стоять только
пробель¬
символы.
Директива #include включает в текст копию указанного файла. Если
имя файла заключено в кавычки, препроцессор ищет его в том же ката¬
логе, что и файл, который компилируется. Если же имя файла заклю¬
чено в угловые скобки (< и >), место, где будет выполняться поиск, за¬
висит
от
реализации
компилятора.
препроцессора #define используется для создания символи¬
ческих констант и макросов.
Директива
Символическая
Макрос
константа
можно
Заменяющий
текст для
определять
в
константы.
на
одной строке,
ную косую черту
следующей
(\),
аргументами
в
текст для
конце
препроцессора #de¬
без них.
или
символической
строке после
#define. Если заменяющий
умещается
с
макроса или
любой текст, расположенный
константы
идентификатора
в
это
дирек¬
макроса или константы
строки
следует
поместить
не
обрат¬
которая показывает, что текст продолжается на
строке.
Определения
помощью
для
это операция, определенная в директиве
fine. Макрос
тиве
имя
это
символических
директивы
констант
препроцессора
и
макросов
#undef.
можно
отменить
с
596
Глава 13
Область действия символической
константы или макроса простирается
от места их определения до места отмены определения с помощью #un¬
def, либо
файла.
до конца
Условная компиляция
ем
директив
позволяет программисту управлять выполнени¬
препроцессора
компиляцией программного
и
Условные директивы препроцессора вычисляют
ного выражения. В директивах препроцессора не
ния
приведения
типа,
выражения
Каждая конструкция #if
Директивы #ifdef
и
defined(tt^#)
и
#if
sizeof
заканчивается
и
значение
кода.
целочислен¬
допускаются выраже¬
перечислимые константы.
#endif.
#ifndef представляют собой сокращения для #if
!defined(a^#).
Условные конструкции препроцессора, проверяющие несколько вари¬
антов,
реализуются с помощью директив #elif (эквивалента else if
структуры if)
Директива
ние,
и
#else (эквивалента else структуры if).
#error выводит на печать зависящее
от реализации
сообще¬
включая указанные в директиве лексемы.
указывает действие, зависящее от реализации.
Указание, не распознанное данным компилятором, игнорируется.
Директива #pragma
Операция
# производит замену текстовой лексемы, которая преобразу¬
заключенную в кавычки. Операция # должна использо¬
ется в строку,
ваться в макросах с аргументами, поскольку операнд после # ссылается
на аргумент макроса.
## выполняет конкатенацию двух лексем.
жна иметь два операнда.
Операция
Операция
## дол¬
препроцессора #line используется для последовательной ну¬
мерации строк исходного кода программы, начиная с числа, заданного
константой.
Директива
Существует
пять предопределенных символических констант.
LINE
Констан¬
текущей обрабатываемой строки исходного кода
FILE
имя компилиру¬
программы (целая константа). Константа
DATE
емого исходного файла (символьная строка). Константа
дата начала компиляции текущего исходного файла (строка). Констан¬
та
TIME
время начала компиляции текущего исходного файла
STDC
целая константа 1; предназначена,
(строка). Константа
чтобы показать совместимость данной реализации со стандартом ANSI.
та
Каждая
двумя
из
номер
предопределенных
символами
констант
начинается
и
заканчивается
подчеркивания.
Макрос assert, определенный
в заголовочном
файле assert.h, проверяет
(false), то asвызывает функцию abort
значение выражения. Если значение выражения равно 0
sert
сообщение об ошибке
работы программы.
распечатывает
для завершения
и
597
Препроцессор
Терминология
#define
аргумент
#elif
директива препроцессора
заголовочные файлы
#else
#if
стандартной библиотеки
заголовочный файл
заменяющий текст
#ifdef
команда cc
#ifndef
макроопределение
#include «filename»
макрос
#include <filename>
макрос
#line
область
#pragma
#undef
отладчик
#endif
#error
UNIX
аргументами
действия символической
константы
\ (обратная дробная черта)
символ
с
в
или
макроса
символические
предопределенные
константы
продолжения
_DATE
FILE
LINE
STDC
TIME
a.out в UNIX
препроцессор
С
препроцессорная
конкатенации
препроцессорная
преобразования
расширение
операция
##
операция
в
строку
abort
символическая
assert
условная
компиляция
assert.h
условное
выполнение
stdio.h
#
макроса
константа
директив
препроцессора
stdlib.h
Распространенные ошибки программирования
13.1.
Забывают
заключить
аргументы
в
скобки
в
заменяющем
тексте
макроса.
13.2.
Вставка условно компилируемых операторов printf для целей от¬
в места, где С подразумевает только один оператор. В этом
ладки
случае условно
компилируемый фрагмент должен быть включен в
Тогда, если программа компилируется с отла¬
составной оператор.
дочными
Хороший
13.1.
стиль
операторами,
управления
не
изменяется.
программирования
Использование
лает
поток
осмысленных имен для именованных констант де¬
программу
самодокументированной
и
облегчает
ее
восприя¬
тие.
Советы по повышению
13.1.
Иногда
эффективности
вместо вызова
функции
можно использовать макрос,
кото¬
встраивает код в программу до времени исполнения. Это иск¬
лючает накладные расходы, связанные с вызовом функции.
рый
598
Глава 3
Упражнения
13.1.
для самоконтроля
Заполните пробелы
а) Каждая
в
из
каждом
директива
следующих
компиляции
начинаться
должна
препроцессора
b) Конструкции условной
утверждений.
можно
использовать
с
для
проверки нескольких вариантов, если воспользоваться директива¬
ми
и
.
создает макросы и символические кон¬
c) Директива
станты.
d) Только
тивой
символы могут стоять в строке перед дирек¬
препроцессора.
e) Директива
отменяет
символические
константы
и
макросы.
f) Директивы
для #if definей(имя)
дает
g)
нением
директив
и
и
сокращенные нотации
#if !definе&(имя).
возможность
программисту
препроцессора
управлять
выпол¬
компиляцией программного
и
кода.
h)
кращающий
макросе
макрос, выдающий на печать сообщение
выполнение
равна
программы,
если
оценка
и
пре¬
выражения
в
0.
i) Директива
вставляет
j) Операция
выполняет
файл
в
другой файл.
конкатенацию
двух
аргумен¬
тов.
операнд в строку.
k) Операция
1) Символ
означает, что
заменяющий
лической
или
продолжается
преобразует
константы
макроса
текст для симво¬
на
следующей
строке.
m) Директива
нумерует строки исходного кода про¬
граммы, следующие непосредственно за директивой, начиная с за¬
данного
13.2.
13.3.
номера.
Напишите программу, выводящую
на печать значения предопреде¬
ленных
перечисленных
символических
констант,
на
рис.
13.1.
Напишите директивы препроцессора, выполняющие каждое
дующих
a) Определите символическую константу YES, присвоив ей
ние
из сле¬
действий.
значе¬
1.
b) Определите символическую константу N0. присвоив ей
0.
значение
c) Включите в программу заголовочный файл common.h. Заголо¬
вочный файл находится в том же каталоге, что и компилируемый
файл.
d) Пронумеруйте
3000.
оставшиеся
строки
в
файле,
начиная
с
номера
599
Препроцессор
e) Если была определена
символическая константа
TRUE,
отмените
ее определение, после чего вновь определите ее, присвоив ей значе¬
ние
1. Сделайте это,
не используя директиву препроцессора
f) Если была определена
символическая константа
TRUE,
#ifdef.
отмените
ее определение, после чего вновь определите ее, присвоив ей значе¬
ние
1.
Сделайте
используя директиву препроцессора #ifdef.
это,
g) Если символическая константа TRUE не равна 0, определите
символическую константу FALSE равной 0. В противном случае
определите FALSE равной 1.
h) Определите макрос SQUARE__VOLUME, вычисляющий объем
куба. Макрос должен иметь один аргумент.
Ответы на упражнения для самоконтроля
13.1.
13.2.
а) #, b) #elif, #else, с) #define, d) пробельные, e) #undef, f) #ifdef,
#ifndef, g) Условная компиляция, h) assert, i) #include, j) ##, k) #,
1) \, m) #line.
/* Напечатать значения
#include <stdio.h>
main
предопределенных
()
{
LINE
FILE
DATE
TIME
STDC
printf(
printf("
printf("
printf("
printf{"
%d\n",
%s\n",
"= %s\n",
^= %s\n",
%d\n",
=
=
=
}
LINE
=
5
FILE
=
macros.с
DATE
=
TIME
=
STDC
13.3.
Sep 08 1993
10:23:47
1
а) #define YES 1
b) #define N0 0
c)
#include "common.h"
d) #line 3000
e)
#if defined(TRUE)
#undef TRUE
#define TRUE 1
#endif
f)
#ifdef TRUE
t#undef TRUE
#define TRUE 1
#endif
LINE
FILE"
DATE^
TIME^
STDC
макросов
*/
600
Глава 13
g)
TRUE
#if
#define
FALSE
0
FALSE
1
#else
#define
#endif
h)
#define
SQUARE_VOLUME(x)
(x)
*
(x)
*
(x)
Упражнения
13.4.
Напишите программу, которая определяет макрос с одним аргу¬
ментом для вычисления объема шара. Программа должна вычис¬
лять объем шаров с радиусом от одного до десяти, и выводить на
печать
результаты
в
виде
таблицы. Объем шара
вычисляется
по
следующей формуле:
*r3
(4/3)
*
П
где n равно 3.14159.
13.5.
Напишите программу,
The
sum
тами
13.6.
x
и
x
of
В программе
у,
and
у
которая выдает следующее
is
13
необходимо определить макрос SUM
и
сообщение:
использовать
с двумя аргумен¬
SUM для вывода сообщения.
Напишите программу, использующую макрос MINIMUM2 для
определения меньшего из двух чисел. Ввод значений должен про¬
изводиться с клавиатуры.
13.7.
Напишите
программу,
использующую
макрос
MINIMUM3
для
Макрос MINIMUM3 дол¬
MINIMUM2, определенный в упражне¬
определения наименьшего из трех чисел.
жен использовать макрос
нии
13.8.
с
клавиатуры.
Напишите программу, использующую макрос PRINT для вывода
на
13.9.
13.6. Ввод значений должен производиться
печать
строки.
Напишите программу, использующую макрос PRINTARRAY для
вывода на печать массива целых чисел. Макрос должен получать в
качестве аргументов имя массива и число элементов в нем.
13.10.Напишите
программу, использующую макрос SUMARRAY для
суммирования значений численного массива. Макрос должен полу¬
чать в качестве аргументов имя массива и число элементов в нем.
г
л
а
в
а
14
Специальные вопросы
Цели
Научиться переадресации
файла.
ввода с клавиатуры на ввод
из
Научиться переадресации экранного
Научиться
гументов
вывода в
писать функции, использующие
переменной длины.
файл.
списки ар¬
Научиться обрабатывать аргументы командной строки.
Научиться присваивать
ный
тип
числовым константам
конкрет¬
данных.
Научиться
использованию
временных
файлов.
Научиться программно обрабатывать непредвиденные
события.
Научиться
динамически выделять память под массивы.
Научиться
изменять
мической
памяти.
размеры выделенной ранее дина¬
602
Глава 14
Содержание
14.1.
Введение
14.2.
Переадресация ввода/вывода
14.3.
Списки
в
системах UNIX
и
DOS
аргументов переменной длины
14.4. Аргументы командной строки
14.5. Замечания
исходных
относительно
компиляции
программ
из
нескольких
файлов
14.6. Выход из программы с помощью exit и atexit
14.7.
Модификатор
типа
volatile
14.8. Суффиксы для целых
14.9. Еще раз
о
констант и
Обработка
14.11.
Динамическое выделение
14.12.
Безусловный
сигналов
памяти:
функции calloc
и
realloc
переход: goto
Распространенные ошибки программирования
симости программ
ческие замечания
для самоконтроля
Советы по перено¬
Советы по повышению эффективности Общие методи¬
Упражнения для самоконтроля Ответы на упражнения
Упражнения
14.1.
В этой
плавающей точкой
файлах
14.10.
Резюме
констант с
Введение
главе представлен ряд вопросов,
С. Многие
обычно
не включаемых во вводные
обсуждаемых здесь возможностей рассматрива¬
ются в применении к конкретным операционным системам, главным образом
курсы по языку
из
UNIX и/или DOS.
14.2.
Переадресация ввода/вывода
UNIX
и
в системах
DOS
Как правило, устройством ввода данных
в программу является клавиату¬
(стандартный ввод), а устройством вывода
вывод). В большинстве операционных систем,
и DOS, можно переадресовать ввод так, чтобы
экран монитора
ра
и
(стандартный
в частности в системах
UNIX
он производился не с клавиату¬
603
Специальные вопросы
ры,
а из
файла,
файл вместо экрана. Обе фор¬
без использования средств стандартной
а также переадресовать вывод в
мы переадресации можно выполнить
библиотеки работы
с фаллами.
Существует несколько способов переадресации ввода/вывода
дной строки UNIX. Рассмотрим исполняемый файл sum, который
из
коман¬
вводит по
одному целые числа и сохраняет значение суммы введенных величин до тех
пор, пока не
будет
вает результат.
бинацию
конца
установлен индикатор конца
Обычно
файла,
после чего распечаты¬
пользователь вводит числа с клавиатуры и вводит ком¬
файла, чтобы
показать, что ввод данных окончен.
При переад¬
файла. На¬
ресации ввода данные, которые необходимо ввести, можно взять из
пример, если данные сохранены в файле input, командная строка
$
sum
<
input
запускает программу sum;
символ
переадресации ввода (<) указывает, что для
файла input. Переадре¬
ввода в программу должны использоваться данные из
сация ввода в системе DOS производится точно так же.
Отметим, что $
приглашение командной строки UNIX (некоторые
UNIX-системы используют в качестве приглашения %). Обучающиеся часто с
факт, что переключение является функцией опера¬
ционной системы, а не еще одной возможностью С.
Вторым способом переназначения ввода является использование конвейе¬
трудом воспринимают тот
Конвейер (|) вызывает переадресацию вывода одной программы на ввод
другой программы. Предположим, программа random выводит ряд случайных
ра.
чисел; вывод программы random можно направить прямо в программу sum,
используя командную строку UNIX
$
random
|
sum
Это приведет к тому, что будет вычислена сумма чисел, выданных ran¬
dom. Конвейер можно использовать в UNIX и DOS.
Чтобы перенаправить вывод из программы в файл, используется символа
переадресации вывода (>) (это возможно как в UNIX, так и в DOS). Например,
для переназначения вывода из программы random в
файл
out воспользуйтесь
следующей командной строкой:
$
random
>
out
И наконец, вывод
из программы можно
добавить
в конец
существующего
файла
присоединения вывода (») (этот символ использу¬
ется и в UNIX, и в DOS). Например, чтобы добавить вывод из программы ran¬
dom в файл out, созданный с помощью предыдущей командной строки, воспо¬
льзуйтесь командой
с помощью символа
$
random
>>
out
14.3. Списки аргументов
переменной
длины
Существует возможность создать функцию, число аргументов которой не
фиксированно. В большинстве программ, представленных в этой книге, испо¬
льзовалась стандартная
библиотечная функция printf, которая, как вы знае¬
Как минимум, printf должна по¬
те, принимает переменное число аргументов.
printf может при¬
Прототип функции
лучить строку в качестве первого аргумента, но, кроме того,
нять
printf
любое
количество
дополнительных
выглядит следующим
образом:
аргументов.
604
Глава 14
int
char
pnnt(const
*format,
...);
(...) прототипе функции означает, что функция получает пе¬
число
аргументов любого типа. Заметим, что многоточие должно
ременное
Многоточие
в
всегда находиться
в конце списка
параметров.
Макросы и определения заголовочного файла переменных аргументов
stdarg.h (рис.14.1) предоставляют программисту средства, необходимые для
построения функций со списком аргументов переменной длины. Программа
на рис.
14.2 демонстрирует функцию average, получающую произвольное
личество аргументов.
мых
Первый
аргумент average
ко¬
это всегда число усредняе¬
значений.
Идентификатор
Объяснение
va_list
Тип, предназначающийся для хранения информации, необходимой
макросам va_start, va_arg и va_end Чтобы получить доступ к аргументам
в списке
переменной
длины,
необходимо объявить объект
типа
va_list
Макрос, который
перед обращением
переменной длины Макрос инициализирует объект, объявленный
помощью va_list для использования макросами va_arg и va_end
va_start
к аргументам списка
вызывается
с
Макрос, расширяющийся до выражения со значением и типом следующего
аргумента в списке переменной длины Каждый вызов va_arg изменяет
объект, объявленный с помощью va_list так, что объект указывает на
va_arg
следующий аргумент
списка
Макрос обеспечивает нормальный возврат из функции,
которой ссылался макрос va_start
| va_end
|i
на
список
аргументов
Рис. 14.1. Тип
и макросы, определенные в заголовочном
Функция average применяет
все
определения
и
файле stdarg.h
макросы
заголовочного
файла stdarg.h. Объект ар, типа va_list, используется макросами va_start,
va_arg и va_end для обработки списка аргументов переменной длины функ¬
ции average. Функция начинается вызовом макроса va_start, инициализиру¬
ющего объект ар. Макрос получает два аргумента: объект ар и идентификатор
самого правого параметра в списке перед многоточием. В данном случае это i
(va_start нужен идентификатор i для определения того, где начинается спи¬
переменной длины). Затем функция average последовательно
переменной total. Прибавляемое к total
сок аргументов
складывает аргументы из списка в
значение извлекается из списка аргументов вызовом макроса
va_arg получает два аргумента: объект ар
ке аргументов
va_arg. Макрос
и тип значения, ожидаемого в спис¬
функции. В данном случае это double. Макрос возвращает зна¬
Функция average вызывает макрос va_end с объектом ар
чение аргумента.
для упрощения нормального возврата
из
average в main. И наконец,
main.
вычисля¬
ется среднее и его значение возвращается в
Распространенная ошибка программирования 14.1
Многоточие
только
в
в
середине списка параметров функции
списка параметров
конце
Многоточие может находиться
605
Специальные вопросы
/*
Использование
списка
аргументов
переменной
длины
*/
#include <stdio.h>
#include <stdarg.h>
double average(int,
mam
...);
()
{
double
w
=
37.5,
22.5,
=
x
у
1.7,
=
z
=
10.2;
printf ("%s%.lf\n%s%.lf\n%s%.lf\n%s%.lf\n\n",
"w
", x, "у
", у, "z
", w, "x
printf ("%s%.3f\n%s%.3f\n%s%.3f\n",
"The average of w and x is ",
average(2, w, x),
"The average of w, x, and y is ",
=
=
=
average(3, w, x, y),
"The average of w, x, y,
average(4, w, x, y, z));
return
and
=
z
is
",
z);
",
0;
double average(int i,
{
double total
0;
int j ;
...)
=
va_list ap;
va_start(ap,
for
=
(j
total
i);
<=
i;
1;
j
+=
va_arg(ap,
3++)
double);
va_end(ap);
return
w
=
37.5
x
=
22.5
y
z
=
1.7
=
10.2
total
/
The
average
of
The
average
The
average
of w,
of w,
w
i;
and
x
x
and
x,
is
y,
y
30.000
20.567
is
and
z
is
17.975
Рис. 14.2. Использование списка аргументов
У читателя может возникнуть
резонный
длины
вопрос, как функции printf и
макросе va__arg. Ответ состо¬
scanf узнают, какой
тип использовать в каждом
ит в том, что
и
printf
переменной
scanf просматривают спецификации преобразования
в
строке формата для определения типа следующего аргумента,
управляющей
который будет обрабатываться.
Глава 14
606
14.4.
Аргументы командной строки
Во многих системах,
передавать аргументы
в том числе
функции main
int argc
чения параметров
и
DOS
из
и UNIX, существует возможность
командной строки посредством вклю¬
char *argv[]
в список параметров
main. Параметр
массив
командной строке. Параметр argv
строк, в котором сохраняются имеющиеся в командной строке аргументы.
Обычное использование аргументов командной строки включает вывод аргу¬
ментов на печать, передачу опций и передачу программе имен файлов.
Программа на рис. 14.3 посимвольно копирует один файл в другой. Допус¬
тим, исполняемый файл данной программы называется сору. Типичная
argc получает
число аргументов в
командная строка для программы сору в системе UNIX выглядит так:
$
copy input
output
/* Использование аргументов
#include <stdio.h>
main(int
argc,
char
командной строки
*/
*argv[])
{
FILE
int
*inFilePtr,
с;
*outFilePtr;
if
(argc !=3)
pnntf("Usage: copy infile outfile\n");
else
if ((inFilePtr
fopen(argv[l], "r")) != NULL)
=
if
((outFilePtr
while
((c
fputc(c,
=
=
fopen(argv[2],
fgetc(inFilePtr))
outFilePtr);
"w"))
!= NULL)
!=EOF)
else
printf("File \"%s\" could
not
be
opend\n",
argv[2]);
else
printf("File
return
\"%s\"
could
not
be
opened\n",
argv[l]);
0;
}
Рис. 14.3. Использование аргументов
командной строки
что файл input будет скопирован в
время выполнения программы, если argc не равен 3 (сору
Данная командная строка показывает,
файл output. Во
считается одним из
аргументов),
она выдает
ется. В противном случае массив argv
сообщение об ошибке
и
заверша¬
будет содержать строки "copy , "input"
и "output". Второй и третий аргументы командной строки воспринимаются
программой в качестве имен файлов. Файлы открываются при помощи функ¬
ции fopen. В случае успешного открытия файлов символы считываются из in¬
put и записываются в output до тех пор, пока для файла input не будет уста¬
новлен индикатор конца файла. После этого программа завершается. Резуль¬
татом является создание точной копии файла input. Заметим, что не на всех
компьютерных системах работать с аргументами командной строки так же
607
Специальные вопросы
просто, как в UNIX и DOS. Например, в системах Macintosh и VMS требуются
специальные установки для обработки аргументов командной строки. Чтобы
получить более полную информацию об использовании аргументов командной
строки, воспользуйтесь руководством
14.5. Замечания
по
вашей
системе.
относительно компиляции
из нескольких исходных
Как было
отмечено ранее,
из нескольких исходных
файлов
можно создавать программы,
файлов (см.
главу 16,
программ
которые состоят
«Классы»). Существует
неско¬
забывать при создании многофайловых про¬
грамм. Например, определение функции должно целиком находиться в одном
лько правил, которые не следует
файле, его нельзя разделить на несколько файлов.
В главе 5 нами были введены понятия классов памяти
Мы узнали,
ций,
что
переменные,
объявленные
вне
области действия.
определения любой из функ¬
и
по умолчанию принадлежат к статическому классу памяти, и на них ссы¬
глобальные переменные. Глобальные переменные доступны лю¬
бой функции, определенной в том же файле после объявления этих перемен¬
ных. Глобальные переменные также доступны и функциям в других файлах,
лаются как на
однако они должны объявляться в каждом из них.
ляем
нее в
Например, если мы опреде¬
глобальную переменную целого типа flag в одном файле, и ссылаемся на
другом файле, то последний должен содержать объявление
extern
int
flag;
будет обращаться к переменной. В этом объявле¬
спецификатор extern указывает компилятору, что переменная flag опреде¬
лена или в другом файле, или в этом же, но позже. Компилятор сообщает ком¬
поновщику, что в файле имеется неразрешенная ссылка на переменную flag
Только после этого можно
нии
(компилятор не знает, в каком месте определена переменная flag, поэтому пре¬
доставляет ее поиск компоновщику). Если компоновщик не найдет определе¬
ние flag, возникнет ошибка компоновки и не будет создано исполняемого фай¬
ла.
Если
же соответствующее
глобальное определение будет обнаружено,
поновщик разрешит ссылки, указав, где находится
ком¬
flag.
Совет по повышению эффективности 14.1
Глобальные переменные увеличивают производительность, потому что они доступны
дополнительные затраты на передачу данных функ¬
напрямую из любой функции
-
циям, таким
образом,
устраняются
Общее методическое замечание 14.1
Следует избегать применения глобальных переменных за исключением случаев, ког¬
да быстродействие приложения является критическим фактором, поскольку они на¬
рушают принцип минимума
Так
же как
привилегий
объявление extern
позволяет
объявлять глобальные перемен¬
файлах программы, прототипы функццй могут расширить об¬
ласть действия функций за пределы файла, в котором они определены (специ¬
фикатор extern в прототипе функции не обязателен). Для этого необходимо
ные
в других
608
Глава 14
включить прототип функции в каждый файл, в котором функция вызывается,
и компилировать файлы совместно (см. раздел 13.2). Прототипы функций ука¬
зывают компилятору, что данная функция или определена в другом файле,
будет определена
или
позже в этом.
шить ссылки на такую
Если компоновщик
функцию,
не сумеет
эта
И снова компилятор не пытается разре¬
обязанность поручается компоновщику.
обнаружить
соответствующее определение функ¬
ции, возникает ошибка.
Как пример использования прототипа функции для расширения ее облас¬
действия рассмотрите любую программу, содержащую директиву препро¬
цессора #include <stdio.h>. Этим директива включает в файл прототипы та¬
ких функций, как printf и scanf. Другие функции в файле могут использовать
printf и scanf для выполнения своих задач. Функции printf и scanf определе¬
ны отдельно, и нам нет необходимости знать, где они определены. Мы просто
постоянно используем их код в своих программах. Компоновщик разрешает
ти
наши ссылки на эти функции автоматически. Этот процесс позволяет нам ис¬
пользовать функции стандартной библиотеки.
Общее
методическое замечание 14.2
Создание программ из нескольких исходных
льзованию кода
ния
и
файлов способствует повторному
испо¬
систематизированному конструированию программного обеспече¬
Функции могут использоваться во многих приложениях В таких случаях следует
функции в отдельных исходных файлах, и каждый исходный файл дол¬
иметь соответствующий ему заголовок, содержащий прототипы функций Это по¬
сохранять эти
жен
программистам использовать
зволяет
включая
вместе
Совет
с
в
различных программах один и тот же код,
и компилируя эти программы
существующим исходным файлом.
них
уже
в
соответствующий заголовочный файл
по переносимости программ 14.1
Некоторые операционные системы не поддерживают
или функций длиной более шести символов Об этом
ботке программ, предназначенных для работы
на
имена
глобальных переменных
забывать при разра¬
не следует
различных машинах.
Существует возможность ограничить область действия глобальной пере¬
менной или функции заданным файлом. Спецификатор класса памяти static,
будучи применен к глобальной переменной или функции, предотвращает об¬
ращение к ней из любой функции, не определенной в том же самом файле.
это называют внутренней компоновкой. Глобальные переменные и
функции, определение которых не предваряется static, допускают внешнюю
компоновку, и другие файлы могут получить к ним доступ, если содержат со¬
Иногда
ответствующие объявления
и/или прототипы функций.
Объявление глобальной переменной
static
float
pi
=
3.14159;
pi типа float, присваивает ей значение 3.14159 и показы¬
pi известна только функциям файла, в котором она определена.
Спецификатор static обычно используется со вспомогательными функция¬
ми, которые вызываются только функциями отдельного файла. Если функция
не используется за пределами отдельного файла, то следует применять принцип
минимальных привилегий, объявляя ее как static. Если функция определена
до того, как она используется в файле, static должен применяться к определе¬
создает переменную
вает,
нию
что
функции. В противном случае static применяется
к ее прототипу.
609
Специальные вопросы
При разработке больших программ, состоящих
из
нескольких
исходных
файлов, компиляция программы становится утомительным делом; если в одном
из файлов сделаны небольшие изменения, приходится перекомпилировать всю
программу. Многие системы снабжены специальными утилитами, которые пе¬
рекомпилируют только модифицированные файлы программы. В UNIX-систе¬
подобная утилита называется make. Утилита make читает файл с именем
makefile, который содержит инструкции по компиляции и компоновке про¬
мах
граммы.
Такие системы, как Borland С++ и Microsoft С/С++ 7.0 для PC, также
make. Более подробную информацию относительно этой утили¬
имеют утилиту
ты можно получить, прочитав руководство по вашей системе.
14.6. Выход
из
программы
с помощью
exit
и
atexit
Библиотека общего назначения (stdlib.h) предусматривает также иные
способы выхода из выполняющейся программы, нежели традиционный воз¬
функции main. Функция exit вызывает завершение работы програм¬
мы, как если бы она выполнилась нормально. Эта функция часто используется
для того, чтобы прекратить выполнение, когда при вводе обнаружена ошибка,
врат из
невозможно открыть файл, который должна обрабатывать
Функция atexit регистрирует функцию, которая будет вызвана
или
ном
завершении программы,
main
(т.е.
или вызывается
т.е.
если
управление
достигает
программа.
при успеш¬
конца
функции
функция exit.
Функции atexit в качестве
имя функции). Функция,
аргумента передается
вызываемая
при
указатель
завершении
может иметь аргументов и не может возвращать значения.
ровать до 32 функций, которые должны
выполняться
на
функцию
программы,
не
Можно зарегистри¬
при
завершении
про¬
граммы.
Функция exit
с
EXIT__SUCCESS,
ния,
от
зависящее
Аргумент, как правило
EXIT_FAILURE. Если exit
принимает один аргумент.
лическая константа
EXIT__SUCCESS
или
симво¬
вызвана
окружению возвращается значение успешного заверше¬
реализации. Если exit вызвана с EXIT_FAILURE,
окружению возвращается значение неудачного завершения. Когда вызывает¬
ся функция exit, все функции, предварительно зарегистрированные с помо¬
щью atexit, вызываются в порядке, обратном их регистрации, все связанные с
программой потоки сбрасываются и закрываются, а управление возвращается
системе. Программа на рис. 14.4 тестирует функции exit и atexit. Программа
предлагает пользователю определить, завершать ли программу с помощью
exit или по достижении конца функции main. Отметим, что функция print
выполняется при завершении программы в
14.7.
В
главах
6
и
7
Модификатор
мы ввели
держивает модификатор
когда для
спецификации
любом случае.
типа
volatile
типа const. ANSI С, кроме того, под¬
volatile. В стандарте ANSI (An90) сказано, что
модификатор
типа
типа используется
volatile, природа доступа к объек¬
Керниган и Ричи (Ke88) указы¬
ту этого типа зависит от реализации системы.
вают,
что
ного рода
20
Зак 801
модификатор volatile
оптимизаций.
должен использоваться для запрещения раз¬
610
Глава 14
/* Применение функций exit
#include <stdio.h>
#include <stdlib.h>
и
atexit
*/
void print(void);
main ()
{
int answer;
atexit (print);
printf("Enter 1
"Enter 2
scanf("%d",
if
/* регистрация функции print */
to terminate program with function exit\n"
to terminate program normally\n");
&answer);
(answer
1) {
printf("\nTerminating program with function exit\n");
==
exit(EXIT_SUCCESS);
}
pnntf("\nTerminating program by reaching the end of main\n");
return 0;
void print(void)
{
printf("Executing function print at program termination\n"
"Program terminated\n");
}
Enter 1
Enter 2
:
to terminate program with function exit
to terminate program normally
1
Terminating program with
Executing function print
Program
function
termination
terminated
Enter
1
to
terminate
program with
Enter
2
to
terminate
program
:
exit
at program
function
exit
normally
2
Terminating program by reaching the
end of main
Executing function print at program termination
Program terminated
Рис. 14.4.
14.8.
Применение функций exit
и atexit
Суффиксы для целых констант
плавающей точкой
В С предусмотрены суффиксы для целых
и констант с
констант и констант с плаваю¬
щей точкой. Целыми суффиксами являются: u или U для целого unsigned, 1
или L для целого типа long, и ul или UL для целого типа unsigned long. Следу¬
ющие константы имеют соответственно тип unsigned, long, и unsigned long:
611
Специальные вопросы
174u
8358L
28373ul
Если целая
шим типом,
константа не имеет
способным хранить
суффикса,
значение
такой
ее тип
ближай¬
определяется
величины
(сначала int,
потом
long int, и, наконец unsigned long int)
Суффиксы констант с плавающей точкой следующие: f или F для float, и 1
или L для long double. Следующие константы имеют соответственно тип float
и long double:
1.28f
3.14159L
Константы с
плавающей точкой без суффикса
14.9. Еще раз
В
главе
11 обсуждалась
возможность
довательным и произвольным доступом.
можность
их
автоматически
получают
double.
тип
обработки
двоичных
не поддерживают.
Двоичные файлы
файлах
обработки
текстовых
файлов
с после¬
Язык С также предусматривает воз¬
но некоторые компьютерные системы
Если двоичные файлы
крыт в двоичном режиме
вый.
файлов,
о
не поддерживаются
(рис. 14.5), файл будет обрабатываться
и
файл
от¬
как тексто¬
должны использоваться вместо текстовых, только ког¬
требования к скорости, памяти и/или условия совмес¬
требуют применения двоичных файлов. В противном случае тексто¬
файлы во всех отношениях предпочтительней благодаря присущей им пе¬
да существуют жесткие
тимости
вые
реносимости, а также возможности использовать другие
менты для просмотра и
работы
И
с текстовыми
в
'
файлами
стандартные инстру¬
данных.
~~
1
Режим
Описание
rb
Открывает двоичный файл для чтения.
wb
Создает двоичный файл для записи
содержимое.
ab
Добавление, открывает или создает двоичный файл для записи в конец файла.
rb+
Открывает двоичный файл для обновления (чтение и запись).
wb+
Создает двоичный файл для обновления
текущее содержимое.
ab+
Добавление, открывает или создает двоичный файл для обновления; все записи
производятся в конец файла.
Если файл уже существует, удаляет текущее
Если файл уже существует, удаляет его
Рис. 14.5. Режимы открытия двоичных
ве
файлов
В дополнение к функциям обработки файлов, которые обсуждались в гла¬
11, в стандартной библиотеке имеется также функция tmpfile, которая от¬
временный файл в режиме "wb+". Хотя это режим для двоичного
файла, некоторые системы обрабатывают временные файлы как текстовые.
Временный файл существует до тех пор, пока не будет закрыт с помощью fcloкрывает
se,
или пока не завершится выполнение программы.
20*
612
Глава 14
Совет по повышению эффективности 14.2
Рассмотрите возможность использования двоичных файлов вместо текстовых в при¬
ложениях, требующих высокой производительности.
Совет по переносимости программ 14.2
Используйте текстовые файлы, когда пишете программы, которые планируется пере¬
носить на другие системы.
Программа на рис. 14.6 заменяет в файле символы табуляции на пробелы.
Пользователю предлагается ввести имя файла, который будет модифицировать¬
ся. Если введенный пользователем файл и временный файл успешно открыты,
программа считывает символы из файла, который необходимо обработать, и за¬
писывает их во временный файл. Если считывается символ табуляции ('\t'), он
заменяется пробелом, который и записывается во временный файл. Когда до¬
стигается конец исходного файла, указатели позиции каждого из файлов уста¬
навливаются на начало вызовом rewind. После этого временный файл копиру¬
ется посимвольно в исходный файл. Программа выводит на печать первонача¬
льный файл по ходу копирования символов из него во временный файл, и выво¬
дит на печать временный файл по ходу копирования его символов в первонача¬
льный, чтобы подтвердить,
^н
что символы
были
записаны.
/* Использование временных файлов */
#include <stdio.h>
mam
()
{
FILE
*filePtr, *tempFilePtr;
mt с;
char fileName[30];
printf("This prgram changes tabs to spaces.\n"
"Enter а file to be modified: ");
scanf("%s",
if
(
if
fileName);
filePtr
(
=
fopen(fileName,
"r+")
)
!= NULL)
( ( tempFilePtr
tmpfile() ) != NULL) {
printf("\nThe file before modification is:\n");
while ( ( с
getc(filePtr) ) != EOF) {
putchar (с);
': c, tempFilePtr);
'\t' ?
putc(c
=
=
1
==
}
rewind(tempFilePtr);
rewind(filePtr);
printf("\n\nThe
while
file
after
modification
(
( c
getc(tempFilePtr)
putchar (c);
putc (c, filePtr);
=
)
is:\n");
!= EOF)
{
}
Рис. 14.6. Использование временных файлов (часть 1
из
2)
613
Специальные вопросы
}
else
printf("Unable
to
open
temporary
file\n");
else
printf("unable
return
This
a
file
file
The
0
fileName);
%s\n",
open
0;
program
Enter
to
changes tabs to spaces.
to
before
be
modified:
data
modification
is:
1
2
3
4
5
6
7
8
The
file after modification is:
0
1
2
3
4
5
6
7
8
9
9
Рис. 14.6. Использование временных файлов (часть 2 из 2)
14.10.
Обработка
сигналов
Непредвиденное событие, или сигнал, может привести к тому, что про¬
грамма будет завершена преждевременно. Вот лишь некоторые из непредви¬
денных событий: прерывания (ввод <ctrl>c в UNIX или DOS), недопустимые
сегментации, запрос на завершение от операцион¬
ной системы, и исключительные ситуации с плавающей точкой (деление на
ноль или переполнение). Библиотека обработки сигналов обеспечивает воз¬
инструкции, нарушение
перехвата непредвиденных событий с помощью функции signal.
номер сигнала, и ука¬
Функция signal получает два аргумента: целое число
затель на функцию обработки сигналов. Сигналы могут генерироваться функ¬
можность
число
номер сигнала в качестве аргу¬
14.7 представлена сводная таблица стандартных сигналов,
цией raise, которая принимает целое
мента.
На рис.
определенных
в заголовочном
стрирует функции signal
Сигнал
и
файле signal.h. Программа
raise
в
на рис.
14.8 демон¬
действии.
J
Пояснение
функции abort)
SIGABRT
Аварийное завершение программы
SIGFPE
Ошибочная арифметическая операция, например деление
операция,
(например,
вызов
на
ноль или
вызывающая переполнение.
SIGILL
Обнаружение недопустимой инструкции
SIGINT
Получение интерактивного сигнала прерывания.
SlGSEGV
Некорректный доступ к памяти.
SIGTERM
Запрос на завершение, направленный программе.
Рис. 14.7 Сигналы, определенные в заголовочном
I
файле signal.h
614
Глава 14
*/
г/* Обработка сигналов
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
void signal_handler(int);
main ()
{
int
x;
i,
signal(SIGINT, signal_handler);
srand(clock());
for
(i
=
x
if
=
1;
1
+
i
<=
100;
%
rand()
I++){
50;
25)
(x
raise(SIGINT);
==
pnntf ( "%4d"
if
(i % 10
pnntf (
,
I) ;
0)
\n" ) ;
==
"
}
return
0;
}
void signal_handler(int
{
int response;
signalValue)
pnntf ("%s%d%s\n%s",
"\nInterrupt signal (", signalValue, ")
"Do you wish to continue (1
yes or 2
scanf("%d", &response);
while (response != 1 && response != 2) {
no)? ");
pnntf(" (1
yes or 2
scanf("%d", &response);
}
=
=
if
=
received.",
no)? ");
=
1)
(response
signal(SIGINT, signal_handler);
else
==
exit(EXIT_SUCCESS);
}
Рис. 14.8 Использование обработки
Программа
терактивный
на рис.
сигнал
14.8
вызывает
сигналов
(часть 1
из
2)
функцию signal, чтобы перехватить
(SIGINT). Программа
начинается вызовом
signal
ин¬
SIGINT
функции является
указателем на ее начало) в качестве аргументов. Когда генерируется сигнал
типа SIGINT, управление передается функции signal___handler, выдается сооб¬
и указателем на
функцию signal_handler (помните,
с
что имя
615
Специальные вопросы
на печать, и пользователь получает возможность продолжить нормаль¬
ное выполнение программы. Если пользователь хочет продолжить выполнение,
обработчик сигналов вновь инициализируется повторным вызовом signal (неко¬
щение
торые системы
и
рован)
требуют, чтобы обработчик
управление передается
жен сигнал.
сигналов
был повторно инициализи¬
в ту точку программы,
в
которой был обнару¬
В этой программе для моделирования интерактивного
сигнала ис¬
функция raise. Выбирается произвольное число между 1 и 50. Если
равно 25, то вызывается raise для генерации сигнала. Обычно интерак¬
пользуется
число
тивные сигналы инициируются извне.
Например,
ввод <ctrl> с во время выпол¬
нения программы в системах UNIX или DOS генерирует
нал,
который
прерывает выполнение программы.
интерактивный сиг¬
Обработку сигналов можно
использовать для перехвата интерактивных сигналов и предотвращения преры¬
вания выполнения программы.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
94
95
86
87
88
Interrupt signal (4) received.
Do you wish to continue (1
yes or 2
89
90
91
92
94
96
97
98
99
93
95
=
Рис. 14.8 Использование обработки
14.11.
Динамическое
100
сигналов
(часть 2
из
выделение памяти:
calloc
В
no)? 1
=
и
2)
функции
realloc
12, «Структуры данных», говорилось о динамическом выделении
функции malloc. Как мы тогда констатировали, массивы
лучше, чем связанные списки, подходят для быстрой сортировки, поиска и до¬
ступа к данным. Однако массивы, как правило, являются статическими
структурами данных. Библиотека общего назначения (stdlib.h) поддержива¬
ет еще две функции для динамического выделения памяти: calloc и realloc. С
помощью этих функций можно создавать и модифицировать динамические
массивы. Как показано в главе 7, «Указатели», указатель на массив может
быть индексирован как массив. Таким образом, указателем на непрерывный
блок памяти, созданным calloc, можно манипулировать как массивом. Функ¬
главе
памяти с помощью
ция calloc динамически выделяет память для массива.
дит следующим
void
*calloc(size_t nmemb,
Функция принимает
каждого
выгля¬
элемента
(size),
size_t size);
два аргумента: число элементов
быть выделена.
(nmemb)
и
и инициализирует элементы массива нулями.
ция возвращает указатель на выделенную память, или
может
Прототип calloc
образом:
NULL,
размер
Функ¬
если память не
Глава 14
616
Функция realloc
изменяет размер
объекта, выделенного
в результате пре¬
дыдущего вызова malloc, calloc или realloc. Содержимое первоначального
объекта сохраняется при условии, что размер вновь выделяемой памяти боль¬
ше, чем первоначальный. В противном случае содержимое остается неизмен¬
ным лишь в пределах размера нового объекта.
дующим
void
Прототип realloc
выглядит сле¬
образом:
*realloc(void
*ptr,
size_t
size);
Функция realloc принимает два аргумента: указатель на первоначальный
объект (ptr) и новый размер объекта (size). Если ptr равен NULL, realloc рабо¬
тает аналогично malloc. Если size равен 0, а ptr не NULL, память, выделенная
для объекта, освобождается. В противном случае, если ptr не NULL и размер
больше нуля, realloc пытается выделить для объекта новый блок памяти.
Если новую память выделить не удается, объект, на который указывает ptr, не
изменяется. Функция realloc возвращает либо указатель на вновь выделенную
память, либо NULL.
14.12.
Безусловный
На протяжении всей
переход: goto
книги мы постоянно подчеркивали важность исполь¬
зования методов структурного программирования,
способствующих
созданию
надежного программного обеспечения, которое легко отлаживать, сопровож¬
дать и модифицировать. В некоторых случаях производительность оказывает¬
строгой приверженности канонам структурного программирования.
В этом случае допустимо использовать и некоторые неструктурные методы.
ся важнее
Например,
туры
Это
мы можем использовать
повторения
до
того,
как
break, чтобы прервать
условие
продолжения
выполнение струк¬
цикла
станет ложным.
исключает ненужные итерации, если задача выполнена до того, как прои¬
зойдет
окончание цикла.
Другим
примером
применение оператора
неструктурного
goto
программирования
безусловного перехода. Результатом
является
исполне¬
goto
указанной
является передача управления первому оператору программы после
ния
в операторе goto. Метка представляет собой идентифика¬
тор, за которым следует двоеточие. Метка должна находиться в той же самой
метки,
функции, что и оператор goto, который на нее ссылается. Программа на
рис. 14.9 использует операторы goto, чтобы десять раз выполнить цикл и при
этом
каждый раз
выводить на печать значение счетчика. После инициализа¬
ции count значением 1 программа проверяет count, определяя, не больше ли
он 10 (метка start пропускается, поскольку метка не выполняет никаких дей¬
ствий). Если больше,
женному за
то управление передается первому оператору,
располо¬
меткой end. В противном случае значение count выводится
на пе¬
чать и увеличивается на единицу, а управление передается первому оператору
после метки start.
В
главе
обходимы
3
мы констатировали, что только три управляющих структуры не¬
для написания любой программы: последовательная,
выбора
и по¬
правилам структурного программирования, можно создать
управляющие структуры с многочисленными уровнями вложенности, из кото¬
рых очень трудно эффективно выйти. Некоторые программисты используют в
вторения.
Следуя
операторы goto, как средство быстрого выхода из структуры
с многократным вложением. Это избавляет от необходимости проверять мно¬
гочисленные условия, чтобы выйти из управляющей структуры.
таких ситуациях
617
Специальные вопросы
Совет
эффективности 14.3
по повышению
Оператор goto
вложенных
Общее
может использоваться для
эффективного
выхода из
глубоких уровней
управляющих структур.
методическое замечание 14.3
должен использоваться только в приложениях, ориентированных на
Оператор goto
высокую производительность.
этим оператором,
как
Оператор goto
не является структурным и программы с
правило, труднее отлаживать, сопровождать
и
модифициро¬
вать.
/* Применение goto */
#include <stdio.h>
main()
{
int
count
1;
=
start:
if (count >10)
goto end;
/*
метка
*/
метка
*/
count);
printf("%d ",
++count;
goto start;
end:
/*
putchar('\n');
return
1 2
4
3
5
0;
6 7
8
9 10
Рис. 14.9 Использование goto
Резюме
Многие операционные системы,
переадресовать
В
системах
UNIX
пользованием
В
системах
потоки
и
и
переадресации
UNIX
и
DOS,
позволяют
программы.
DOS ввод переназначается
символа
UNIX
и в частности
ввода/вывода
ввода
в
командной строке с
или конвейера (|).
ис¬
(<)
DOS вывод переназначается
использованием символа переадресации вывода
в
(>)
командной строке
с
или символа присо¬
(»). Применение символа переадресации вывода
программой данные в файле, а символ
присоединения вывода добавляет выводимые программой данные в ко¬
нец указанного файла.
единения
просто
вывода
сохраняет
Макросы
и
выводимые
определения
заголовочного
файла stdarg.h предоставляют
функций со спис¬
программисту средства, необходимые для построения
ком
аргументов
переменной
длины.
Глава 14
618
Многоточие (...)
прототипе функции указывает на переменное количе¬
в
ство аргументов.
Тип va_list предназначается для хранения информации, необходимой
макросам va__start, va__arg и va__end. Чтобы получить доступ к аргу¬
ментам в списке
переменной
длины,
необходимо объявить объект
типа
va_list.
Макрос va__start вызывается перед обращением к аргументам списка
переменной длины. Макрос инициализирует объект, объявленный как
va_list, для использования макросами va__arg и va__end.
Макрос va_arg
расширяется до выражения со значением и типом сле¬
дующего аргумента
в списке
переменной
объект, объявленный
длины.
Каждый
вызов
va__arg
va__list
так, что объект ука¬
Макрос va__end упрощает нормальный возврат из
аргументов переменной длины которой ссылался
функции, на список
изменяет
зывает на
Во
с помощью
аргумент списка.
следующий
передавать аргументы
включения
DOS
макрос
va__start.
UNIX, существует возможность
функции main из командной строки посредством
многих системах, в частности
и
и char *argv[] в список параметров
число
передает
аргументов в командной строке.
параметров
main.
int argc
Параметр argc
массив
Параметр argv
строк,
в котором сохраняются имеющиеся в
командной строке аргументы.
Определение функции
нельзя
разделить
на
должно целиком находиться в одном
несколько
файле,
его
файлов.
Глобальные переменные должны быть объявлены
в каждом из
файлов,
где они используются.
Прототипы функций могут расширить область действия функций за
пределы файла, в котором они определены (спецификатор extern в про¬
тотипе функции не обязателен). Для этого необходимо включить прото¬
тип
функции в каждый файл,
файлы совместно.
в котором
функция
вызывается, и ком¬
пилировать
Спецификатор класса памяти static, будучи примененным к глобаль¬
ной переменной или функции, предотвращает ее использование любой
функцией, не определенной в том же самом файле. Иногда это называ¬
ют внутренней компоновкой. Глобальные переменные и функции, опре¬
деление которых не предваряется static, допускают внешнюю компо¬
новку; другие файлы могут получить к ним доступ, если содержат
соответствующие объявления и/или прототипы функций.
Спецификатор
static обычно используется
циями, которые вызываются только
функция
со вспомогательными функ¬
функциями отдельного файла. Если
не используется за пределами того
файла, где она определяет¬
привилегий, объявляя
ся, то следует применять принцип минимальных
ее
как
static.
При разработке
больших программ
из
нескольких
исходных
файлов
компиляция программы становится утомительным делом, если в одном
из файлов сделаны небольшие изменения, а приходится перекомпили¬
ровать всю программу. Многие системы снабжены специальными ути¬
литами, которые перекомпилируют только модифицированные файлы
619
Специальные вопросы
программы. В UNIX системах подобная утилита называется make. Ути¬
make читает файл с именем makefile, который содержит инструк¬
лита
ции по компиляции и компоновке программы.
Функция exit
она
была
вызывает
выполнена
завершение
Функция atexit регистрирует
димо
вызвать
при
работы программы,
как
бы
если
нормально.
в
успешном
программе функцию, которую необхо¬
завершении программы, т.е. если
управление достигает конца функции main или была вызвана функция
exit.
Функции atexit в качестве аргумента передается указатель на функ¬
цию (т.е. имя функции). Функция, вызываемая при завершении про¬
граммы,
не может иметь аргументов и не может возвращать значения.
Можно зарегистрировать до 32 функций, выполняющихся при завер¬
шении
программы.
Функция exit
Аргумент, как правило,
EXIT__SUCCESS или константа EXIT_FAIс EXIT_JSUCCESS, окружению возвращается
принимает один аргумент.
символическая
константа
LURE. Если exit
вызвана
значение успешного завершения, зависящее от реализации. Если exit
вызвана с EXIT_FAILURE, окружению возвращается значение неудач¬
ного завершения.
Когда вызывается функция exit, все функции, предварительно зареги¬
стрированные с помощью atexit, вызываются в порядке, обратном их
регистрации, все связанные с программой потоки сбрасываются и за¬
крываются,
а
управление
возвращается
системе.
В стандарте ANSI
(An90) сказано, что когда для спецификации типа ис¬
volatile, характер доступа к объекту этого типа зависит от
реализации. Керниган и Ричи (Ke88) указывают, что модификатор vo¬
пользуется
latile должен
использоваться для
запрещения
разного рода оптимиза¬
ций.
В С предусмотрены суффиксы для целых
констант и констант с плава¬
ющей точкой. Целыми суффиксами являются: u или U для целого
signed, 1 или L для целого типа long, и ul или UL для целого типа
signed long. Если целая константа не имеет суффикса, для
un¬
un¬
нее
принимается ближайший тип, способный хранить значение такой ве¬
личины (сначала int, потом long int, и наконец unsigned long int) Суф¬
фиксы
плавающей точкой следующие: f или F для float, и 1
long double. Константы с плавающей точкой без суффикса
констант с
или L для
автоматически
Язык С предусматривает
некоторые
тип
получают
double.
возможность
компьютерные
системы
их
файлы не поддерживаются и файл
будет обрабатываться как текстовый.
ные
Функция tmpfile
обработки
не
двоичных
поддерживают.
файлов,
но
Если двоич¬
открыт в двоичном режиме, он
временный файл в режиме "wb+". Хотя
файла, некоторые системы обрабатывают вре¬
текстовые. Временный файл существует до тех пор,
открывает
это режим для двоичного
менные
файлы
как
пока не будет закрыт с помощью fclose, или пока не завершится выпол¬
нение программы.
620
Глава 14
Библиотека обработки сигналов обеспечивает возможность перехвата
непредвиденных событий с помощью функции signal. Функция signal
получает
номер сигнала, и указатель на
два аргумента: целое число
обработки.
функцию
Сигналы могут возбуждаться функцией raise, которая принимает целое
число
номер сигнала
Библиотека общего
в
качестве
назначения
аргумента.
(stdlib.h)
поддерживает еще две функ¬
ции для динамического выделения памяти: calloc и realloc. С помощью
этих функций можно создавать и модифицировать динамические мас¬
сивы.
Функция calloc
получает два аргумента: число элементов (nmemb) и
размер каждого элемента (size) и инициализирует элементы массива
нулями. Функция возвращает указатель на выделенную память, или
NULL,
если
память
Функция realloc
не
выделена.
размер объекта, выделенного в результате
предыдущего вызова malloc, calloc или realloc. Содержимое первонача¬
льного объекта сохраняется при условии, что размер выделяемой памя¬
ти больше первоначального.
Функция realloc
ный объект (ptr)
realloc работает
изменяет
принимает два аргумента: указатель на первоначаль¬
и
новый размер объекта (size). Если ptr равен NULL,
аналогично
malloc. Если size равен 0,
а
ptr
не
NULL,
память, выделенная для объекта, освобождается. В противном случае,
если ptr не NULL и размер больше нуля, realloc пытается выделить
для объекта новый блок
памяти.
Если новую
память выделить не удает¬
объект, на который указывает ptr, не изменяется. Функция realloc
возвращает или указатель на вновь выделенную память, или NULL.
ся,
Результатом
оператору
исполнения
программы
goto
после
является
метки,
передача управления
указанной
в
операторе
первому
goto.
Метка представляет собой идентификатор, за которым следует двоето¬
чие. Метка должна находиться в той же самой функции, что и оператор
goto, который
на
нее
ссылается.
Терминология
tmpfile
argc
argv
atexit
calloc
const
exit
va_arg
va_end
va_list
va_start
volatile
EXIT_FAILURE
EXITJSUCCESS
аргументы командной строки
библиотека обработки сигналов
make
внешняя
компоновка
makefile
внутренняя
raise
временный файл
realloc
signal
signal.h
stdarg.h
компоновка
динамические массивы
исключительная
ситуация
плавающей точкой
конвейер |
с
621
Специальные вопросы
нарушение сегментации
недопустимая инструкция
переадресация ввода/вывода
перехват
прерывание
спецификатор
спецификатор
список
класса
памяти
extern
класса
памяти
static
аргументов
переменной
длины
суффикс float
<
символ
переадресации
ввода
символ
переадресации
вывода
символ
присоединения
суффикс
суффикс
суффикс
суффикс
>
вывода
»
событие
(f или F)
long double (1 или L)
long int (1 или L)
unsigned int (u или U)
unsigned long (ul или UL)
Распространенные ошибки программирования
14.1.
Многоточие
в середине списка параметров функции. Многоточие
может находиться только в конце списка параметров.
Советы по переносимости программ
14.1.
операционные системы не поддерживают имена глоба¬
льных переменных или функций длиной более шести символов. 06
Некоторые
разработке программ, предназначен¬
ных для использования на различных машинах.
этом не следует забывать при
Советы по повышению
14.1.
эффективности
Глобальные переменные увеличивают производительность, потому
любой функции
дополнительные
что они доступны напрямую из
затраты на передачу данных
функциям
таким
образом
устраняют¬
ся.
14.2.
Рассмотрите
возможность использования двоичных
текстовых в
приложениях,
требующих высокой
файлов
вместо
производительно¬
сти.
14.3.
Оператор goto может использоваться для эффективного
глубоких уровней вложенных управляющих структур.
Общие
14.1.
выхода из
методические замечания
Следует избегать
применения глобальных переменных за исключе¬
нием случаев, когда быстродействие приложения является крити¬
ческим фактором, поскольку они нарушают принцип минимума
привилегий.
14.2. Создание
программ из нескольких исходных файлов способствует
использованию кода и систематизированному
конструированию программного обеспечения. Функции могут ис¬
повторному
пользоваться во многих приложениях. В таких случаях следует со¬
хранять эти функции в отдельных исходных файлах, и каждый ис¬
ходный файл
содержащий
должен
прототипы
иметь
соответствующий ему заголовок,
функций. Это позволяет программистам
использовать в различных программах один и тот же код, включая
в них
соответствующий заголовочный файл
граммы
вместе
с
уже
существующим
и компилируя эти про¬
исходным
файлом.
622
Глава 14
14.3.
должен использоваться только в приложениях, ори¬
ентированных на высокую производительность. Оператор goto не
Оператор goto
является структурным и программы с этим оператором, как прави¬
ло,
труднее
отлаживать,
сопровождать
модифицировать.
и
Упражнения для самоконтроля
14.1. Заполните пропуски
в
каждом
из
следующих
утверждений.
a) Символ
используется для переключения ввода дан¬
ных с клавиатуры на считывание из файла.
b) Символ
используется для переключения вывода дан¬
ных, при котором они вместо вывода на экран помещаются в файл.
c) Символ
используется
программы в конец
для
присоединения
вывода
файла.
используется для того, чтобы направить выходной
поток одной программы во входной поток другой.
d)
в списке параметров функции указывает, что функ¬
)
ция может принимать переменное количество аргументов.
e
f) Макрос
гументам
должен быть вызван перед
списка
переменной
обращением
к ар¬
длины.
используется для доступа к отдельным аргу¬
g) Макрос
ментам в списке аргументов
переменной
h) Макрос
упрощает
на список аргументов
которой
ссылался
функции main
i) Аргумент
длины.
нормальный
возврат из функции,
макрос
va_start.
получает количество аргу¬
ментов в командной строке.
функции main
j) Аргумент
сохраняет
аргументы
командной строки как символьные строки.
k) Утилита UNIX
рый содержит инструкции
считывает
файл
,
кото¬
для компиляции и компоновки про¬
из
нескольких
исходных файлов. Утилита пере¬
грамм, состоящих
компилирует файл лишь в том случае, если он был изменен со
времени последней компиляции.
принудительно завершает выполнение про¬
1) Функция
граммы.
регистрирует функцию, которую необхо¬
вызвать
димо
при успешном завершении программы.
m) Функция
n) Модификатор
изменяться
о)
типа
после
указывает, что
объект
не должен
инициализации.
целого типа или типа с
плавающей точкой может
с плавающей точ¬
быть добавлен к целой константе или константе
кой для определения точного типа константы.
открывает временный файл, который су¬
ществует до тех пор, пока не будет закрыт или пока программа не
p) Функция
завершится.
623
Специальные вопросы
может
q) Функция
использоваться для перехвата не¬
событий.
предвиденных
r) Функция
сигнал.
программно генерирует
s) Функция
динамически выделяет память для масси¬
ва и присваивает элементам нулевые значения.
t) Функция
го
ранее
изменяет размер динамически выделенно¬
блока
памяти.
Ответы на упражнения для самоконтроля
14.1.
а) переадресации ввода (<), b) переадресации вывода (>), с) присое¬
динения вывода (»), d) Конвейер (|), e) Многоточие (...), f)
va__start, g) va_arg, h) va__end, i) argc, j) argv, k) make, makefile, 1)
exit, m) atexit, n) const, о) Суффикс, p) tmpfile, q) signal, r) raise,
s) calloc, t) realloc.
Упражнения
14.2.
Напишите программу для
чисел,
которые передаются
вычисления
последовательности
функции product
со списком
целых
аргумен¬
тов переменной длины. Проверьте вашу функцию, вызвав ее неско¬
лько раз, каждый раз с новым количеством аргументов.
14.3.
Напишите
программу,
которая
выводит
на
печать
аргументы
командной строки программы.
14.4.
Напишите программу, упорядочивающую массив целых чисел в
восходящем или нисходящем порядке. Программа должна воспри¬
нимать аргументы командной строки
либо ключ -а для восходя¬
щего порядка,
либо -d для нисходящего порядка. (Примечание:
стандартный формат
UNIX.)
для
передачи
программе
опций
в
это
системе
14.5. Напишите программу, вставляющую пробелы между всеми симво¬
лами в файле. Программа должна сначала записать содержимое об¬
рабатываемого файла во временный файл, с пробелами между сим¬
волами, после чего скопировать файл обратно в исходный файл.
При этой операции первоначальное содержимое файла
должно
быть переписано.
14.6.
Изучите
сигналы
руководство по вашей системе, чтобы определить, какие
поддерживаются библиотекой обработки сигналов (sig¬
nal.h). Напишите
программу, которая обрабатывает стандартные
сигналы SIGABRT и SIGINT. Программа должна тестировать пе¬
этих сигналов посредством вызова функции abort для
генерации сигнала типа SIGABRT и ввода <ctrl>c для сигнала
типа SIGINT.
рехват
14.7.
Напишите программу, которая динамически выделяет массив це¬
лых чисел. Размер массива должен вводиться с клавиатуры. Эле¬
ментам массива присваиваются значения, введенные с клавиатуры.
Выведите
значения массива на печать.
После
этого выполните по¬
вторное выделение памяти под массив размером в
1/2
от текущего
Глава 14
624
количества элементов. Выведите на печать значения, оставшиеся
массиве,
чтобы убедиться,
что они совпадают с
в
первой половиной
исходного массива.
14.8.
Напишите программу, которая принимает из командной строки
два аргумента, являющихся именами файлов, считывает посимво¬
первый файл
льно
ном
14.9.
и записывает символы во
второй файл
в
обрат¬
порядке.
Напишите программу, использующую оператор goto для моделиро¬
вания вложенной структуры циклов, которая выводит на печать
показанный ниже квадрат, составленный из символов звездочки:
*****
*
*
*
*
*
*
*
*
Программа должна
printf:
printf("*");
printf(" ");
pritf("\n");
выполнять
только
следующие
операторы
г
л
в
а
а
15
С++
«улучшенный»
как
Цели
Познакомиться
лизованными
Оценить
чения
в
с
усовершенствованиями
языка
С, реа¬
С++.
важность С как основы для
программирования,
в
дальнейшего изу¬
частности
языка
С++.
С
626
Глава 15
Содержание
15.1.
Введение
15.2.
Однострочные комментарии
15.3.
Потоковый
ввод/вывод
С++
С++
15.4. Объявления в С++
15.5. Создание новых типов данных в С++
15.6.
Прототипы функций
контроль соответствия
и
типов
Встроенные функции
15.8. Параметры-ссылки
15.9. Модификатор const
15.7.
15.10.Динамическое распределение
памяти
15.11. Параметры, используемые
умолчанию
по
с
помощью
new и
delete
15.12.Унарная операция разрешения области действия
15.13. Перегрузка функций
15.14.Спецификации внешней
15.15.
связи
Шаблоны функций
Распространенные ошибки программирования
Резюме
программирования
шению
эффективности
самоконтроля
Рекомендуемая
Хороший
Ответы
Общие методические
замечания
упражнениям для самоконтроля
литература
Приложение: ресурсы С++
к
стиль
Советы по повы¬
Советы по переносимости программ
Упражнения для
Упражнения
15.1. Введение
Начиная
с этого момента мы
будем
ет многие характеристики языка
С
ентированного программирования
заниматься языком
и включает в
С++. С++ улучша¬
себя средства
(00P), обещающие
объектно-ори-
значительно повысить
производительность труда программистов и качество программного
обеспече¬
ния, равно
обсужда¬
как степень его повторного использования. В этой главе
ются некоторые из
«улучшений», которые С++
вводит в
старый С.
С++ как «улучшенный» С
Проектировщики С
627
и
разработчики
его первых
что этот язык станет своего рода феноменом
(то
версий
и не
предполагали,
же самое относится и к опера¬
ционной системе UNIX). Когда язык программирования укореняется так глу¬
боко, как это произошло с С, появление новых потребностей диктует развитие
языка, а не простую замену его новым языком.
Язык С++ был разработан Бьерном Страуструпом
в
Bell Laboratories (St86)
первоначально назывался «С с классами». Название С++ включает в себя
операцию инкремента языка С (++) и указывает на то, что С++
версия С с
и
расширенными возможностями.
С++ является «надмножеством» С, поэтому программисты могут исполь¬
зовать компилятор С++ для компиляции существующих программ на С и за¬
тем постепенно переводить эти программы на С++. Некоторые ведущие про¬
давцы программного обеспечения уже поставляют компиляторы С++, не пред¬
лагая при этом отдельных средств разработки для языка С.
С++, хотя комитет ANSI разраба¬
Многие полагают, что наиболее важные среды про¬
С будут переведены на С++ к середине 1990-х годов1.
Пока не существует стандарта ANSI для
тывает свои предложения.
граммирования на
15.2.
Однострочные комментарии С++
Программисты
кода.
и
часто вставляют небольшой
Язык С требует, чтобы комментарий был
*/. С++
позволяет начинать
для текста
комментарий
с
//
комментарий
в конце строки
заключен между символами
комментария; символ конца строки автоматически завершает
ментарий. Эта форма комментария
/*
и использовать остаток строки
экономит несколько
ком¬
нажатий клавиш, од¬
нако она применима только для комментариев, не продолжающихся на следу¬
ющую строку.
Например, комментарий С
/*
Это однострочный комментарий.
требует обоих разделителей /*
нострочная версия
языка
и
*/
*/
даже для однострочного комментария.
Од¬
С++:
// Это однострочный комментарий.
Для многострочных комментариев, например
/* Это
из
один
/*
написания
/*
строчных
нотация
С++
// Это
может показаться
из
один
написания
//
строчных
не
*/
много-
более краткой:
способов
понятных
много-
комментариев.
i
забывайте,
что комментарии в языке
лько строк, так что на самом деле
комментария
/*
(а
написания
1
требуется
из
С могут
охватывать неско¬
только одна пара
не три пары, использованных
Это один
строчных
*/
*/
комментариев.
//
Однако
способов
понятных
ограничителей
выше):
способов
понятных
много¬
комментариев.
*/
В настоящее время стандарт ANSI/ISO С++ уже сформулирован
исключает дальнейшего развития и модификации языка.
и одобрен. Это,
Прим. ред.
однако, не
628
Глава 15
Поскольку С++
мы
является надмножеством
С,
в программе на
С++ приемле¬
обе формы комментария.
Распространенная ошибка программирования 15.1
Забывают закрыть комментарий
Хороший
в
стиле
С
*/
символами
стиль программирования 15.1
Использование комментариев в стиле языка С++ с
//
помогает
комментария, которые возникают при пропуске
ющих комментарии С.
завершенного
избегать ошибок
символов
*/,
не¬
закрыва¬
Это на вид простое упущение может приводить к глубинным трудноулови¬
мым ошибкам, некоторые из которых могут проскользнуть мимо компилятора
Результатом пропуска */ является то, что компилятор
комментарий еще не закончен, следовательно, он продолжа¬
ется во всех последующих строках до тех пор, пока не встретится другой сим¬
вол комментария */ или будет достигнут конец исходного файла. Это может
необнаруженными.
предполагает, что
привести к игнорированию компилятором ключевого фрагмента программы.
15.3. Потоковый
ввод/вывод
С++
Язык С++ предусматривает альтернативную обращениям
printf
и
scanf
ных и строк.
обработки ввода/вывода
Например, простой диалог
возможность
new
tag: ");
&tag);
printf("The new tag is: %d\n",
к
функциям
стандартных типов дан¬
printf("Enter
scanf("%d",
tag);
записывается на С++ в виде
cout << "Enter new tag: ";
cin >> tag;
cout << "The new tag is:
"
<<
tag
В первом операторе используется
операция «
(операция передачи
тор читается так:
"Enter new tag:
<<
'\n';
стандартный выходной
поток cout и
в поток, произносится «послать
в»). Опера¬
"
послать
строку
в
выходной
поток cout.
внимание, что операция передачи в поток обозначается так же,
как поразрядная операция сдвига влево. Во втором операторе используется
Обратите
стандартный входной поток cin и операция » (извлечения
носится «взять из»). Этот оператор читается
из потока, произ¬
Взять из входного потока значение для переменной tag.
Обратите внимание, что операция извлечения из потока становится опера¬
цией поразрядного сдвига вправо, если левый параметр является целочислен¬
ным типом. Операции передачи и извлечения из потока, в отличие от функций
printf и scanf, не требуют форматирующих строк и спецификаторов преобра¬
зования для указания на тип входных и выходных данных.
примеров,
подобных этому,
В С++
есть много
когда он автоматически «знает», какие типы дол¬
жны участвовать в операциях. Также
обратите
внимание на то, что при испо¬
С++ как
С
«улучшенный»
льзовании с
операцией
629
переменной tag не предшествует
&, которую требует scanf.
Для организации потокового ввода/вывода программы на С++ должны
извлечения из потока
операция взятия адреса
включать
заголовочный файл iostream.h. Программа
myAge
ти переменные
в
myAge
friendsAge
на рис.
15.1 просит
ввес¬
и определяет, старше ли вы вашего друга,
возраст совпадает. Обратите внимание на то, что перемен¬
объявляются непосредственно перед ссылкой на них
В главе 21, «Потоковый ввод/вывод», дается детальное
моложе его или ваш
ные
и
friendsAge
и
ввода.
операторах
описание особенностей потокового
// Пример простого
ввода/вывода
потокового
языка
С++.
ввода/вывода
#include <iostream.h>
mam
()
{
cout
<<
"Enter
";
your
age:
your
friend's
int
myAge;
cin >> myAge;
<<
cout
"Enter
age:
";
int
friendsAge;
cin >> friendsAge;
if
>
(myAge
cout
<<
friendsAge)
"You
are
older.\n";
else
if
(myAge
cout
<
friendsAge)
are younger.\n";
<<
"You
<<
"You
else
cout
return
and
your
friend
are
the
same
age.\n";
0;
}
Enter your
Enter your
You are
аде: 23
friend s
аде:
20
аде:
23
older.
Enter your
Enter your
аде: 20
friend's
You are younger.
Enter your
Enter your
аде: 20
friend's
аде: 20
friend are the
You and your
Рис. 15.1. Потоковый
Хороший
ввод/вывод
аде.
с операциями передачи и извлечения из потока
стиль программирования 15.2
Применение ориентированного
программы более
валенты
same
на
С с
на
потоки
удобочитаемыми (и
вызовами
ввода/вывода
в стиле языка
менее уязвимыми для
функций printf
и
scanf.
ошибок),
С++ делает
чем их экви¬
630
Глава 15
15.4. Объявления
В
С
языке
исполняемых
С++
в
объявления должны находиться в начале блока до любых
В С++ объявления могут размещаться всюду, где
все
операторов.
исполняемый оператор, при условии, что они предшествуют
объявляется. Например, во фрагменте кода
может стоять
ис¬
пользованию того, что
<<
cout
int
"Enter
x,
>>
cin
>>
x
<<
cout
two
integers:
";
у;
<<
переменные
у;
"The
sum
"
"
is
x и у
of
<<
x
"
+
<<
<<
x
объявляются
"
в
остаются в
разделе
"
<<
у
после исполняемого оператора
пользования в последующем операторе
являться
and
'\n';
<<
у
инициализации
cout,
но до их ис¬
cin. Кроме того, переменные могут объ¬
for
такие переменные
структуры
области действия до конца блока,
в котором определена структура
for1. Например,
for
i
=
0;
<<
i
<<
(int
cout
i
<=
5;
i++)
1\n1;
объявляет целую переменную i
for.
и инициализирует ее значением
Область действия локальной переменной
в языке
С++
0
в
структуре
начинается с ее объ¬
закрывающей правой фигурной скобки (}).
Следовательно, операторы, предшествующие объявлению переменной, не мо¬
явления
и
распространяется
до
гут ссылаться на эту переменную, даже если эти операторы находятся в том
же
самом
структур
блоке. Объявления переменных
или if.
не
могут
помещаться
в
условиях
while, do/while, for
Хороший
стиль программирования 15.3
Размещение объявлений переменных поблизости
сделать
от их первого использования может
программу более ясной.
Распространенная ошибка программирования 15.2
Объявление переменной после ссылки
15.5. Создание
на
нее
Язык С++ предусматривает
enum.
class. Как
Однако
и в языке
в отличие от
каком-либо операторе.
новых типов данных в
С++
возможность определения пользователем но¬
вых типов с помощью ключевых слов enum,
слова
в
С, перечисления
С перечисление
в
struct, union
и нового ключевого
С++ объявляются при помощи
С++ после его объявления стано¬
в
Для объявления переменных этого нового типа ключевое
слово enum не требуется. То же самое верно для структур, объединений и
классов. Например, в объявлениях
вится новым типом.
1
В
последних редакциях стандарта С++ постулируется, что область действия переменных,
объявленных в заголовке структуры for, ограничивается телом этой структуры. Вне тела
оператора for такие переменные недоступны.
Прим. ред.
С++ как
«улучшенный»
enum
С
631
TRUE};
{FALSE,
Boolean
struct Name {
char first[10];
char last[10];
}
union Number
int i;
float f;
{
};
создаются три новых типа данных с именами-этикетками Boolean, Name и
Number. Эти имена-этикетки могут быть использованы для объявления пере¬
менных следующим
Boolean
done
=
образом:
FALSE;
Name
student;
Number x;
Эти объявления создают переменную done типа Boolean
ванную значением
FALSE),
(инициализиро¬
переменную student типа Name и переменную
x
Number.
Как и в языке С, перечислениям присваиваются значения, начиная по
типа
умолчанию с нуля, и
каждый последующий
элемент увеличивается на едини¬
Таким образом, в перечислении Boolean FALSE присваивается значение 0
TRUE значение 1. Любому элементу в перечислении может быть присвоено
цу.
и
целочисленное
Последующие элементы, которым не присвоено
автоматически получают увеличенное на единицу
значение.
определенного значения,
значение предыдущего элемента.
15.6.
Прототипы функций
и
контроль
соответствия
типов
С контролировать правиль¬
ANSI С прототи¬
функций
функций являются необязательными. В С++ для всех функций требуются
Прототипы функций
пы
их прототипы.
не
требует
Функция,
всех параметров
которая
значение,
int
определенная в файле до первого
отдельного прототипа. В этом случае заголовок
как ее прототип.
square,
позволяют компилятору
с точки зрения соответствия типов. В
ность вызова
имеет
square
обращения к ней,
функции действует
С++, как и ANSI С, требует объявления в круглых скобках
функции в ее определении и прототипе. Например, функция
принимает
целочисленный
параметр
и
возвращает
целое
прототип
(int);
Функции, которые не возвращают значения, объявляются с типом возвра¬
щаемого значения void.
Распространенная ошибка программирования 15.3
Попытка возвратить значение из функции типа void или использовать результат вы¬
зова функции void.
632
Глава 15
В языке С для определения пустого списка параметров в круглые скобки
помещается ключевое слово void. Если в круглых скобках прототипа функции
на С ничего не содержится, то для этой функции полностью отключается про¬
верка параметров и не делается никаких предположений относительно числа
параметров
функции
и их типа.
При обращениях
к
этой функции могут пере¬
каких бы то ни было
даваться любые параметры без сообщения компилятора о
ошибках.
В С++ при задании пустого
списка параметров в круглых
void, либо вообще
писывается слово
ничего не записывается.
скобках либо
за¬
Объявление
void print ();
сообщает, что функция print не принимает параметров и не возвращает значе¬
ния. На рис. 15.2 показаны оба принятых в С++ способа объявления функций,
которые
не принимают параметров.
не принимающей параметров
// Пример функции,
#include <iostream.h>
void fl();
void f2(void);
main()
{
fl()
;
f2 () ;
return
0;
}
void
fl()
{
cout
<<
"Function
fl
takes
f2
also
no
argumens\n";
}
void
f2(void)
{
cout
<<
"Function
Function
£1
takes
Function
£2
also
takes
no
arguments\n";
no argumens
takes no arguments
Рис. 15.2. Два способа объявления
функций,
не принимающих параметров
Совет по переносимости программ 15.1
Смысл пустого списка параметров функции в языке С++ разительно отличается от его
смысла в С. В С это означает, что блокируется любая проверка параметров. В С++ это
означает, что функция не принимает параметров Поэтому программы на С, использу¬
ющие такие объявления, могут выполняться иначе при компиляции в С++.
Распространенная ошибка программирования 15.4
Программы
на
С++
не
компилируются,
прототип или она не определена до
каждой функции
если для
обращения
к
ней.
не
объявлен
ее
С++ как
«улучшенный»
С
633
15.7.
Встроенные функции
Реализация программы в виде набора функций хороша с точки зрения
разработки программного обеспечения, но обращения к функциям связаны с
определенными накладными расходами времени исполнения. В С++ преду¬
смотрены встроенные функции, позволяющие уменьшить накладные расходы
особенно для функций небольшого размера. Модифи¬
при вызове функций
inline,
помещенный
катор
перед типом возвращаемого функцией значения в
функции, рекомендует компилятору генерировать в месте
функции копию ее кода (если это возможно) с тем, чтобы избежать
функции. Компромисс состоит в том, что вместо одной копии функции,
определении
рую передается управление
ся многие копии кода
обычно
всякий раз при
в кото¬
этой функции. Компилятор
может игнорировагь
inline
и
функций.
методическое замечание 15.1
Любое изменение
встроенной функции требует перекомпиляции
ющихся ее клиентами. Это может
занных
вызова
ее вызове, в программу вставляют¬
так и поступает, за исключением самых маленьких
Общее
вызова
с
разработкой
и
быть существенным
в
всех
функций,
явля¬
определенных ситуациях,
свя¬
сопровождением программ.
Встроенные функции обладают преимуществами перед макросами препро¬
(см. главу 13), также расширяющимися в месте «вызова» макроса. Од¬
цессора
ним из преимуществ является то, что встроенные
личаются от
любых других функций
ях к встроенным
функциям
выполняется
функции практически
не от¬
С++. Следовательно, при обращени¬
языка
надлежащий контроль
соответствия
типов; макросы препроцессора не поддерживают контроля соответствия типов.
преимуществом является то, что встроенные функции исключают не¬
предвиденные побочные эффекты, связанные с неправильным использованием
макросов. И наконец, встроенные функции можно отлаживать с помощью от¬
Другим
ладчика. Отладчик не распознает макросы препроцессора в качестве особых
компонентов программы, поскольку они представляют собой просто текстовые
замены, выполняемые препроцессором перед компиляцией. Отладчик может
помочь найти логические ошибки, являющиеся результатом макроподстано¬
вок, но не может отнести эти ошибки к определенным макроопределениям.
Хороший стиль программирования 15.4
Модификатор inline следует использовать только с небольшими, часто используемы¬
ми функциями.
Совет по повышению эффективности 15.1
Использование встроенных
нения программы, но при
функций
этом
может
увеличить
привести
ее
к
уменьшению времени выпол¬
размер.
В программе на рис. 15.3 используется встроенная функция cube для рас¬
объема куба со стороной s. Ключевое слово const в списке параметров
чета
функции cube сообщает компилятору, что функция не
ную s. Ключевое слово const более подробно обсуждается
изменяет
в
перемен¬
разделе 15.9.
634
Глава 15
Использование встроенной функции для расчета
// объема куба
#include <iostream.h>
//
inline
mam
float
cube(const
float
s)
return
{
s
*
*
s
}
s;
()
{
cout
<<
"Enter
lenght of your cube:
side
the
";
float side;
cin
cout
>>
<<
side;
"Volume of cube with side
return
<<
side
<<
"
is
"
<<
"
cube(side)
<<
'\n'
;
0;
Enter the side lenght of your cube: 3.5
Volume of cube with side 3.5 is 42.875
Рис. 15.3. Использование встроенной функции для
вычисления
объема куба
Макросы препроцессора представляют собой операции, определяемые в
директиве препроцессора #define'. Макрос состоит из имени (идентификатора
макроса) и текста замены. Макросы могут определяться со списком парамет¬
ров или без него.
Макросы без
ческим константам
параметров
обрабатываются подобно
символи¬
текст замены подставляется в программе вместо иденти¬
фикатора макроса.
заменяющий текст,
Макросы
с
параметрами
подставляют
параметры
в
и затем происходит расширение макроопределения в про¬
грамме.
Рассмотрим следующее макроопределение с одним параметром для вычис¬
ления площади квадрата:
#define VALIDSQUARE(x)
(x)
*
(x)
Препроцессор находит в программе каждое
SQUARE(x), подставляет x (значение или выражение)
ширяет макрос в программе. Например,
cout
«
вхождение
VALID-
в текст замены и рас¬
VALIDSQUARE(7);
расширяется до
cout
<<
*
(7)
(7);
Круглые скобки в тексте замены обеспечивают
ки выражения макроса. Например,
cout
«
+
VALIDSQUARE(2
правильный
порядок оцен¬
3);
раскрывается в
cout
«
(2
+
3)
*
(2
+
3) ;
что правильно оценивается значением
5
*
5
=
25. Препроцессор
не оценивает
выражений, задаваемых в качестве параметров макроса; он просто заменяет
каждое вхождение имени параметра в тексте замены копией всего выражения.
Следовательно, для того, чтобы гарантировать правильную оценку парамет¬
ров,
необходимы круглые скобки.
С++ как
«улучшенный»
Рассмотрим
С
635
соответствующие макроопрёделение без
круглых
скобок
в
тексте замены:
#define VALIDSQUARE(x)
Если
*
x
x
мы укажем в качестве параметра выражение
2 + 3, макрос раскроет¬
ся следующим образом:
2
+
3
*
+
2
3
Это выражение неправильно оценивается значением 2 + 6 + 3
льку умножение имеет более высокий приоритет, чем сложение.
=
11,
поско¬
Для устранения проблем, связанных с макросами, устранения непроизво¬
дительных затрат, связанных с обращениями к функциям, и обеспечения про¬
типа параметров в С++ предусмотрены встроенные функции. Парамет¬
ры встроенных функций оцениваются до их «передачи» в функцию. Следова¬
верки
тельно, нет
необходимости
заключать
в
круглые
скобки
каждое
вхождение
каждого параметра в ее определении.
Встроенная функция
inline
int
square(int
x)
{
return
*
x
x;
}
вычисляет квадрат своего целочисленного параметра x. При вызове square(2 + 3) происходит оценка параметра 2 + 3 5 и его значение подставляется в
=
функции. В программе на рис. 15.4 показаны макросы VALIDSQUARE
INVALIDSQUARE и встроенная функция square.
тело
// Примеры корректных
//
и
некорректных
и
макросов
и
встроенных функций
#include <iostream.h>
#define
VALIDSQUARE(x)
#define
INVALIDSQUARE(x)
inline
int
(x)
X)
square(int
*
x
*
(x)
x
return
{
*
x
x;
}
main()
{
cout
«
«
«
«
<<
<<
return
"
VALIDSQUARE(2
VALIDSQUARE(2 +
+
3)
=
"
3)
"\nINVALIDSQUARE(2 + 3)
INVALIDSQUARE(2 + 3)
"\n
square(2 + 3)
square(2 + 3) << '\n' ;
"
=
=
11
0;
}
VALIDSQUARE(2 + 3)
=
+ 3)
=
11
+
=
25
INVALIDSQUARE(2
square(2
3)
25
Рис. 15.4. Макросы препроцессора и встроенные функции
На рис. 15.5 приведен полный перечень ключевых слов языка С++. На ри¬
сунке показаны ключевые слова, общие для С и С++, и затем отдельно ключе¬
вые слова, используемые только в С++. Далее в книге подробно разъясняется
каждое из новых ключевых слов языка С++.
636
Глава 5
Ключевые слова языка С++
Языки С и С++
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
!volatile
while
Только язык С++
asm
Определяемое реализацией средство использования кода ассемблера
программах С++ (см. руководства по вашей системе)
catch
Обрабатывает
class
Определяет новый класс.
delete
Разрушает динамический объект
friend
Объявляет
inline
Сообщает компилятору, что
генерироваться встроенный
new
в
исключение, генерируемое оператором throw.
Могут быть созданы объекты
в памяти,
этого класса.
созданный операцией
new.
функцию или класс в качестве дружественного другому классу. Друзья
могут обращаться ко всем элементам данных и элементам-функциям класса.
для
данной функции
вместо вызова должен
код.
Динамически распределяет память под объект в «области свободной памяти»
дополнительной памяти, доступной программе
-
во время выполнения
Автоматически определяет размер объекта.
operator
Объявляет перегруженную операцию.
private
Закрытый
элемент класса,
доступный
для
элементов-функций
и
функций-друзей
класса.
protected
Расширенная форма доступа private;
к защищенным элементам могут также
обращаться элементы-функции производных
классов.
public
Элемент класса, доступный для любой функции.
template
Описывает шаблон
this
Указатель, неявно объявляемый в каждой не статической
класса или
класса. Он указывает на
throw
функции (параметризованный тип).
объект, для которого была
функции-элементе
функция.
вызвана эта
Передает управление обработчику исключительных ситуаций или завершает
соответствующий обработчик не может быть
выполнение программы, если
найден.
try
Создает блок, содержащий набор операторов, которые могут генерировать
исключительные ситуации, и делает возможной обработку всех генерируемых
исключений.
virtual
Объявляет виртуальную
функцию.
Рис. 15.5. Ключевые
слова языка С++
С++ как
С
«улучшенный»
637
Распространенная ошибка программирования 15.5
С++ является развивающимся языком, и некоторые его возможности могут не под¬
держиваться вашим компилятором. Использование нереализованных свойств языка
приводит к синтаксическим ошибкам.
15.8.
В
С при
языке
всех
Параметры-ссылки
обращениях
к
функциям параметры передаются
по
параметра по ссылке имитируется в С путем передачи в
функцию указателя на объект и последующего доступа к этому объекту путем
Передача
значению.
разыменования указателя
С уже
в
вызываемой функции. Не забывайте,
что имена
указателями, поэтому массивы ав¬
томатически передаются путем имитации передачи параметра по ссылке. Дру¬
массивов в
являются
(константными)
гие языки программирования предлагают явные
формы передачи параметра
как, например, параметры var в Паскале. С++ устраняет этот не¬
по ссылке
С, позволяя объявлять параметры-ссылки.
Параметр-ссылка является псевдонимом для соответствующего ему пара¬
метра. Чтобы указать, что параметр функции передается по ссылке, просто по¬
достаток языка
местите после типа параметра в прототипе
(точно
так же, как вы поместили
ния на то, что параметр является
цификации
int
функции
символ
(&)
амперсанда
*
для указа¬
за типом параметра символ
указателем). То
этого параметра в заголовке
же самое относится и к спе¬
функции. Например, объявление
&count
в заголовке
кой на
бы
функции
можно прочитать как «переменная count является ссыл¬
int». При обращении
ременной,
и она
будет
к
функции просто укажите
для параметра имя пе¬
автоматически передана по ссылке.
После
этого ссылка
функции на самом деле относится к исход¬
вызывающей функции, и исходная переменная может непо¬
на локальное имя параметра в теле
ной переменной
в
средственно модифицироваться вызываемой функцией.
На рис. 15.6 сравниваются передача по значению, передача по ссылке с
использованием указателей и передача по ссылке с использованием параметров-ссылок.
Параметры
в вызовах
функций squareByValue
и
squareByRefe-
Нельзя сказать, изменяет ли каждая из этих двух функций
свои параметры, без рассмотрения прототипов этих функций или их определе¬
ний. По этой причине некоторые программисты предпочитают передавать в
rence идентичны.
функции модифицируемые параметры посредством указателей, а немодифицируемые параметры в виде ссылок на константы. Ссылки на константы обес¬
печивают эффективность, достигаемую при передаче указателей, и предотвра¬
изменение параметров вызывающей программы. Чтобы определить
ссылку на константу, поместите модификатор const перед спецификатором
типа в объявлении параметра (в разделе 15.9 модификатор const обсуждается
щают
более подробно). Обратите
параметров этих двух
внимание на положение символов
функций. Некоторые
сать int* bPtr, а не int *bPtr
Хороший
Используйте указатели
функцией, и
не
int& cRef,
и
&
в списках
программисты предпочитают пи¬
а не
int &cRef.
стиль программирования 15.5
зываемой
мера,
и
*
подлежащих
для передачи параметров, которые Ъюгут быть изменены вы¬
ссылки на константы для передачи параметров
изменению.
большого раз¬
638
Глава 15
//
Сравнение
передачи параметра
//
//
по
ссылке
с
использованием
указателей
по
ссылке
с
использованием
ссылок
значению,
и
передачи
передачи
параметра
параметра
<iostream.h>
#include
int
по
squareByValue(int);
void
squareByPointer(int
void
squareByReference(int
main
*);
&);
()
{
int
cout
у
<<
"x
=
<<
"Value
<<
squareByValue(x)
<<
"\nx
=
"
3,
z
=
<<
x
<<
"
=
<<
<<
"
=
"z
<<
<<
<<
<<
z
squareByReference(z)
<<
cout
return
"z
0
"
=
<<
z
squareByValue\n"
"
"
after
before
"
"
squareByValue\n\n
squareByPointer\n
;
"
;
;
"
=
before
by squareByValue:
<<
x
"
=
"
returned
<<
<<
"у
у
squareByPointer(&y)
<<
cout <<
у
"у
cout
cout
4;
2,
=
x
"
"
squareByPointer\n\n"
after
before
;
squareByReference\n"
;
;
<<
"
squareByReference\n"
after
;
;
squareByValue(int а)
int
{
return
*=
а
а
//
;
параметр
в
вызывающей
функции
не
изменяется
}
squareByPomter(int *bPtr)
void
{
*bPtr
*=
*bPtr
;
//
параметр
в
вызывающей
функции
изменяется
вызывающей
функции
изменяется
}
void
squareByReference(mt
&cRef)
{
cRef
x
=
2
Value
*=
cRef
=
2
у
=
3
before
у
=
9
after
=
4
before
z
=
16
//
параметр
before squareByValue
returned by squareByValue:
after squareByValue
x
z
;
в
4
squareByPointer
squareByPointer
after
squareByReference
squareByReference
Рис. 15.6. Пример передачи параметра
по ссылке
С++ как
«улучшенный»
С
639
Распространенная ошибка программирования 15.6
Поскольку доступ
просто по имени,
к параметрам-ссылкам в теле вызываемой
программист может по неосторожности
функции производится
обращаться
с параметрами-ссылками как с параметрами, передаваемыми по значению Это может привести к
неожиданным побочным эффектам, если исходные копии переменных будут измене¬
ны
вызывающей функцией.
Совет
эффективности 15.2
по повышению
Для больших объектов используйте параметр-ссылку
с const, чтобы имитировать
внешний вид и надежность передачи параметра по значению, но избежать накладных
расходов, связанных с передачей копии большого объекта.
Ссылки
ных внутри
также могут служить в качестве псевдонимов для других перемен¬
некоторой функции. Например,
int
count
int
&c
=
=
//
1;
count;
++с;
увеличивает значение
код
объявляет целочисленную переменную count
//
создает
в
качестве
псевдонима
для
//
увеличивает
значение
переменной
count
//
(используя
с
ее
переменной count,
псевдоним)
используя ее псевдоним с.
ные-ссылки должны инициализироваться при их
15.8)
рис.
и не могут
Как
менных.
быть переназначены
count
Перемен¬
объявлении (см. рис. 15.7
и
в качестве псевдонимов других пере¬
только ссылка объявлена в качестве псевдонима
другой перемен¬
якобы над псевдонимом (т.е. ссылкой), на
самом деле выполняются непосредственно над самой исходной переменной.
Псевдоним представляет собой просто другое имя для исходной переменной
ной,
все операции,
выполняемые
для него не резервируется память.
Ссылки не могут разыменовываться путем применения операции косвен¬
ной адресации
различным
нельзя выполнять действия «ссылочной
арифметике указателей, чтобы обращаться к
массива). В число других недопустимых операций со
(*). Над
арифметики» (как
ссылками
это делается в
элементам
ссылками входят указание на ссылки, взятие адресов ссылок и сравнение ссы¬
лок
(на
самом
деле
каждая
из
этих
операций
не
вызывает
синтаксической
которой ссылка
псевдонимом).
Функции могут возвращать указатели или ссылки, но это может представ¬
лять опасность. При возврате указателя или ссылки на переменную вызывае¬
ошибки; каждая операция
выполняется над
переменной,
для
является
мой функции она должна быть объявлена статической. Иначе указатель
будут
или
переменной с автоматическим классом памяти, ко¬
торая разрушается при завершении функции; говорят, что такая переменная
является «неопределенной» и поведение программы может стать непредсказу¬
ссылка
относиться к
емым.
Распространенная ошибка программирования 15.7
Отсутствие
инициализации
переменной-ссылки при
ее
объявлении.
Распространенная ошибка программирования 15.8
Попытка переназначить ранее объявленную ссылку
ременной.
в качестве псевдонима
другой
пе¬
640
Глава 15
// Ссылки должны быть инициализированы
#include <iostream.h>
main ()
{
int
=
X
cout
з
"
<<
_
X
_
"У
<<
7
=
У
<<
x
<<
<<
у
<<
II
1r
1'
\n
\n
1
1
;
;
<<
cout
II
Ошибка:
//
&y ;
,
"x
=
_
"У
<<
return
0
""
II
«
VV
X
>1
1
«
VV
\n
'\n1
1
;
;
Compiling FIG15_7.CPP:
Error
6:
FIG15__7.CPP
Reference variable
'y'
must be
initialized
Рис. 15.7. Попытка использования неинициализированной ссылки
Ссылки должны быть
//
#include
mam
инициализированы
<iostream.h>
()
int
cout
=
У
cout
3,
=
x
<<
"x
<<
"У
7
=
&y
_
II
_
II
X
<<
<<
r
//
X
«
У
<<
у теперь
'
1
\n
'\n'
;
;
<<
"X
return
_
"У
0
п
'
<<
_
<<
и
<<
X
У
<<
<<
\n'
'\n'
;
;
}
x
У
x
У
=
3
=
3
=
7
=
7
Рис. 15.8. Использование инициализированной ссылки
Распространенная ошибка программирования 15.9
Попытка разыменования переменной-ссылки с помощью операции косвенной адре¬
(не забывайте, что ссылка является псевдонимом для переменной, а не указа¬
телем на нее).
сации
Распространенная ошибка программирования 15.10
Возвращение указателя
вызываемой функции.
или ссылки на
автоматическую переменную, определенную
в
С++ как
С
«улучшенный»
641
15.9.
const
Модификатор
В разделе 15.7 иллюстрировалось использование модификатора const
параметр
не
быть
объявления
передаваемый
в
в
функцию
ней изменен. Модификатор const может
объявления так называемых «переменных-констант»
может
применяться для
то
для указания того, что
функции
списке параметров
в
также
(вмес¬
#define),
символических констант в препроцессоре с помощью
например
const
PI
float
3.14159;
=
Это объявление создает «переменную-константу» PI и инициализирует ее
Переменная должна быть инициализирована констант¬
значением 3.14159.
ным выражением при ее объявлении и не может быть в дальнейшем изменена
(рис. 15.9
15.10). Переменные-константы
и рис.
называют также именованны¬
ми константами или переменными только для чтения.
Обратите
внимание, что
термин «переменная-константа» является оксюмороном, т.е. противоречивым
выражением вроде «тщедушного великана» или «холодного ожога».
//
Константный
mam
объект
должен
быть
инициализирован
()
{
int
const
7
=
x
x
;
return
0
;
// Ошибка:
//
Ошибка:
не
x
быть
должен
изменить
могу
инициализирован
константную
переменную
;
}
Compiling FIG15_9.CPP:
Error
Error
FIGl5_9.CPP
FIG15_9.CPP
4:
Constant variable
6:
Cannot modify
a
'x'
const
must be
initialized
object
Рис. 15.9. Объект с модификатором const должен быть инициализирован
//
Использование
константной
//
main
переменной,
надлежащим
инициализированной
#include <iostream.h>
образом
()
{
const
cout
<<
"The
<<
x
return
The
x
int
value
Рис. 15.10.
0
of
<<
=
7
;
//
value
'\n'
of
инициализированная
constant
is:
"
;
constant
Корректная
variable
x
is:
7
инициализация и использование
Константные переменные могут
Зак 801
x
переменная
;
вхождение константного выражения.
21
variable
константная
переменной-константы
использоваться
Например,
всюду,
где
возможно
следующее объявление мас-
642
Глава 15
сива использует переменную с модификатором const для задания размера мас¬
сива:
const int arraySize
int array[arraySize];
=
100;
Константные переменные могут также помещаться
в
заголовочные
фай¬
лы.
Распространенная ошибка программирования 15.11
Использование переменных с модификатором const в объявлениях массивов и поме¬
модификатором const в заголовочные файлы (включаемые в
различные исходные файлы одной и той же программы) запрещено в С, но разреше¬
щение переменных с
но
в
С++
Хороший
стиль программирования 15.6
Преимуществом
переменных с
использования
модификатором
const вместо симво¬
лических констант является то, что переменные с модификатором const видимы для
символического отладчика, константы, определяемые с помощью #define, для него
невидимы.
Существуют и другие общепринятые способы
Например, константный указатель может
const.
применения модификатора
быть объявлен следующим
образом:
mt
*const
iPtr
Здесь объявлено,
ло.
Значение,
=
&integer;
что
iPtr
является константным указателем на целое чис¬
на которое указывает
iPtr,
может быть изменено, однако
iPtr
не
быть присвоено значение, указывающее на другую ячейку памяти.
Указатель на константный объект может быть объявлен следующим обра¬
может
зом:
const
mt
*iPtr
=
&mteger;
Здесь объявлено, что
jPtr
является
указателем
на
целочисленную
кон¬
станту. Значение, на которое указывает iPtr, не может быть изменено с испо¬
льзованием iPtr, однако iPtr может быть присвоено значение, указывающее
на другую область памяти. Таким
ством которого значение может
образом, iPtr
является указателем, посред¬
быть прочитано,
но не может
быть
изменено
сути дела, указателем «только для чтения»). Компилятор защищает дан¬
ные, на которые ссылаются указатели только для чтения, тем, что он не допус¬
кает присваивания таких указателей указателям, не являющимся указателя¬
(по
ми только для чтения.
Распространенная ошибка программирования 15.12
Инициализация
переменной const неконстантным
модификатором const.
выражением, например, перемен¬
ной, не объявленной с
Распространенная ошибка программирования 15.13
Попытка
изменить
константную
переменную
С++ как
«улучшенный»
С
643
Распространенная ошибка программирования 15.14
Попытка изменить константный указатель
15.10. Динамическое распределение
с помощью new и delete
Операции
С++
new
памяти
delete предназначены для управления динамиче¬
В ANSI С динамическое распреде¬
и
ским распределением памяти в программах.
ление памяти
обычно
функций malloc
и
выполняется
при
помощи
стандартных
библиотечных
free. Рассмотрим объявление
typeName *ptr;
где typeName
является
ANSI С следующий
любым
me, возвращает указатель на него
p^r
=
malloc(sizeof(typeName));
Динамическое выделение
malloc
и
(например, int, float, char и т. д.). В
объекту typeNa¬
типа void и присваивает этот указатель ptr:
типом
оператор динамически выделяет память
явной
памяти в языке
ссылки на операцию
С требует обращения
sizeof (или
явного указания
к функции
необходимого
байт). Объект
размещается в специальной области памяти, доступной
во
выполнения.
программе
время
Кроме того, в реализациях С, относящихся к
числа
периоду до
malloc,
появления
стандарта
должен быть
ANSI, указатель, возвращаемый функцией
преобразован
ведения (typeName *).
В С++ оператор
ptr
=
выделяет
new
к соответствующему типу посредством при¬
typeName;
объекта типа typeName из области свободной памяти
(термин языка С++ для дополнительной памяти, доступной про¬
время выполнения). Операция new автоматически создает объект со¬
память для
программы
грамме
во
ответствующего
размера
и
возвращает
указатель
правильного
типа.
Если
(в
С++ для представления нулевого указателя используют значение 0, а не
NULL).
Для освобождения памяти, выделенной для этого объекта, в С++ приме¬
няется следующий оператор:
операция
new не может выделить память, возвращается
delete
нулевой
указатель
ptr;
освобождения памяти вызывается функция free с парамет¬
ptr. Операция delete может быть использована только для освобождения
памяти, выделенной операцией new. Применение операции delete к ранее
В языке С для
ром
освобожденному указателю может привести к непредвиденным ошибкам во
время выполнения программы. Применение delete к нулевому указателю не
оказывает никакого влияния на выполнение программы.
Распространенная ошибка программирования 15.15
Освобождение
21*
с помощью
delete памяти, которая
не
была выделена операцией
new
Глава 15
644
С++ допускает применение инициализатора для вновь создаваемого объ¬
екта.
Например,
float
=
*thingPtr
new
float
(3.14159);
инициализирует создаваемый объект типа float значением 3.14159.
Массивы также могут создаваться динамически с помощью операции new.
оператор динамически выделяет память для одномерного массива
из 100 целых чисел и присваивает указатель, возвращаемый операцией new,
Следующий
указателю на целое
arrayPtr:
mt
*arrayPtr;
new
int[100];
arrayPtr
//
=
динамически
массив
создает
Для освобождения памяти, динамически выделенной
new[], используйте оператор
для
arrayPtr
опера¬
цией
delete
[]
arrayPtr;
В главе 16 мы
обсудим динамическое распределение памяти для объектов
классов. Мы увидим, что операции new и delete выполняют и другие задачи
(например,
делает
автоматически вызывают конструкторы и деструктор
использование
надежным,
квадратных
чем вызов
скобках
в
операций new
функций malloc
и
и
delete
более
класса),
мощным
free. В дальнейшем
не
что
более
и
забывайте
о
операции delete, освобождающей массив объектов.
Распространенная ошибка программирования 15.16
Применение операции delete без скобок ([ и ]) при удалении динамически
созданных массивов (это существенно только для массивов, содержащих объекты по¬
льзовательских типов).
15.11.
Параметры, используемые
Часто при обращении
аргумента.
Программист
к
функциям
может
может
указать,
по
умолчанию
особое
значение
аргумент
является
передаваться
что такого
рода
аргументом по умолчанию, и задать для него соответствующее
такой аргумент при вызове функции опускается, в функцию
значение.
Если
автоматически
передается значение по умолчанию.
Аргументы
по
умолчанию
должны
быть самыми правыми (последними)
аргументами в списке параметров функции. При вызове функции с двумя или
более аргументами по умолчанию, если опущенный аргумент не является в
списке
параметров
крайним справа, то все аргументы справа от
Аргументы по умолчанию должны быть
должны быть опущены.
появлении имени функции
обычно в ее прототипе
файле. Аргументы по умолчанию также могут использоваться
ми функциями.
На рис. 15.11 демонстрируется объявление аргументов по
первом
в
него также
заданы при
заголовочном
и со встроенны¬
умолчанию при
объема прямоугольного параллелепипеда. Для всех трех аргументов
были заданы значения по умолчанию, равные 1. При первом обращении к
встроенной функции boxVolume ни один аргумент не определен и, таким об¬
расчете
разом, используются
ментов
и
width
width
и
все три значения по умолчанию.
length
height. При
передается аргумент
При
втором
обращении
и используются значения по умолчанию для аргу¬
третьем
обращении
передаются аргументы
и значение по умолчанию используется только для аргумента
length
height.
С++ как
«улучшенный»
При
последнем
ким
образом,
I
С
645
обращении
передаются аргументы
length, width
и
height
и, та¬
не используется ни одного значения по умолчанию.
// Использование аргументов
<iostream.h>
по
умолчанию
#include
// Рассчитывает объем прямоугольного параллелепипеда
inline int boxVolume{mt lenght
1, int width
1,
=
int height
*
return
{
length
*
width
=
1
=
)
}
height;
()
main
{
cout
<<
"The
<<
<<
boxVolume()
"\n\nThe volume
<<
"width
<<
<<
boxVolume(10)
"\n\nThe volume
<<
"width
<<
boxVolume(10,
<<
"\n\nThe volume
<<
"width
<<
boxVolume(10,
<<
return
'\n'
0
default
1
and
5
and
box
of
а
of
а
with
box
length 10,
\n"
length 10,
\n"
length 10,
\n"
"
is:
height 1
"
is:
box
height 1
and
5
volume
with
"
is:
5)
of
а
box
height 2
with
is:
"
2)
5,
;
;
}
1
The default box volume is:
The volume of
а
box
with
width 1 and height 1 is:
length 10,
10
The volume of а box with length 10,
width 5 and height 1 is: 50
The
volume
width
5
of
а box with
height 2 is:
and
length
10,
100
Рис. 15.11. Задание аргументов
Хороший
стиль
программирования 15.7
Задание аргументов
нако
по умолчанию
по умолчанию может
упростить отдельные вызовы функций Од¬
некоторые программисты считают, что явное указание всех аргументов
делает
программу
вызова
более понятной
Распространенная ошибка программирования 15.17
Определение
и
самым правым
ми
по
попытка
использовать
аргумент
по
умолчанию, который
(последним) аргументом (если одновременно
умолчанию
все
аргументы
справа
от
него).
не являются
не является
аргумента¬
646
Глава 15
15.12.
В С
Унарная операция разрешения области
действия
С++
и
возможно
одним и тем же именем.
объявление
В
языке
С,
локальных и
глобальных переменных
с
пока локальная переменная находится в
области действия, все ссылки на имя этой переменной относятся к локальной
глобальная переменная не видна в области действия локальной
переменной
переменной. В языке С++ предусмотрена унарная операция разрешения облас¬
ти действия (::) для доступа к глобальной переменной, когда в области дейст¬
Унарная операция раз¬
решения области действия не может использоваться для доступа к переменной
вия находится локальная переменная с тем же именем.
блоке. К глобальной переменной можно обраща¬
без операции разрешения области действия, если имя
глобальной переменной не совпадает с именем локальной переменной в облас¬
ти действия. В главе 16 мы обсудим применение
двухместной операции разре¬
с этим же именем во внешнем
ться непосредственно,
шения области действия с классами.
На рис. 15.12 показана унарная операция разрешения области действия с
локальной и глобальной переменными одного и того же имени. Чтобы подчер¬
кнуть, что значения локальной и глобальной переменных value отличаются
друг от друга, в программе одна из переменных
другая с типом
объявляется
с типом
float,
а
int.
// Использование унарной операции разрешения области действия
#include <iostream.h >
float
main
1.2345;
=
value
()
{
int value
cout
7;
=
<<
"Local
<<
"\nGlobal value
return
0
value
=
"
<<
=
"
value
<<
::
value
<<
'\n'
;
;
}
Local value
Global
7
=
value
=
1.2345
Рис. 15.12. Примечание
унарной операции разрешения области действия
Распространенная ошибка программирования 15.18
Попытка обращения к не глобальной переменной во внешнем блоке
унарной операции разрешения области действия
с использовани¬
ем
Хороший
стиль программирования 15.8
Избегайте объявления
личных
ницу
целей. Во
в программе
переменных
с одним и тем же именем для раз¬
многих случаях это является допустимым, но может создавать пута¬
С++ как
«улучшенный»
С
647
15.13.
В
той
языке
Перегрузка функций
С объявление двух функций с одним и тем же именем в одной и
синтаксической ошибкой. С++ допускает опреде¬
же программе является
функций с одним и тем же именем, пока эти функции раз¬
наборами параметров (по крайней мере их типами). Такая возмож¬
называется перегрузкой функций. При вызове перегруженной функции
ление нескольких
личаются
ность
компилятор С++ автоматически выбирает соответствующую функцию, исходя
из анализа числа, типа и порядка параметров вызова. Перегрузка функций
обычно используется для создания нескольких функций с одним и тем же
действия
именем, производящих схожие
Хороший
стиль программирования 15.9
Перегруженные функции,
лее
над различными типами данных.
удобочитаемыми
и
выполняющие аналогичные задачи, делают программы бо¬
понятными
На рис. 15.13 перегруженная функция square используется для вычисле¬
int и double. В главе 17 мы обсудим, каким образом
ния квадрата чисел типов
можно
перегружать
операции
и
специфицировать способ
их
воздействия
на
объекты определяемых пользователем типов. В разделе 15.15 вводятся шабло¬
ны
функций
для выполнения идентичных задач над многими различными ти¬
пами данных. В главе 20
подробно обсуждаются шаблоны функций
и
шаблоны
классов.
I
// Использование перегруженных функций
#include <iostream.h >
square(int x)
int
double
main
{
x
return
}
x;
return
у
integer
7
{
square(double у)
*
*
у;
}
()
{
cout
<<
"The
<<
square(7)
<<
"\nThe
<<
square(7.5)
return 0
square
of
square
<<
of
double
is
7.5
"
is
"
'\n';
;
}
The square of integer 7 is 49
The square of double 7.5 is 56.25
Рис. 15.13. Использование перегруженных функций
комбинацией
различаются своей сигнатурой
функции и типов ее параметров. Компилятор специальным образом ко¬
дирует идентификатор каждой функции (иногда это называют декорировани¬
Перегруженные функции
имени
имен), используя количество и типы ее параметров, для обеспечения безо¬
пасного по типу редактирования связей (компоновки). Безопасное по типу ре¬
ем
дактирование
связей
гарантирует
вызов
соответствующей перегруженной
648
Глава 15
функции и должное согласование аргументов с параметрами.
ошибки
сообщает
Компилятор
вы¬
Программа, представленная на
рис. 15.14, транслировалась с помощью компилятора Borland С++. Закодиро¬
являет
компоновки и
о них.
функций на языке ассемблера, генерированные компилятором,
показаны в окне вывода. Каждое декорированное имя начинается с символа
ванные имена
@,
функции. Закодированный список параметров на¬
функции nothing2 zc представ¬
ляет тип char, i представляет тип int, pf представляет float * и pd представля¬
ет double *. В списке параметров функции nothingl i представляет тип int, f
представляет тип float, zc представляет тип char и pi представляет int *.
за которым следует имя
чинается с символов
//
В
int
Декорирование
square(mt x)
double
I
I
$q.
В
списке параметров
имен
{
{
square(double у)
void
nothmgl (mt
char
*nothing2(char
main
()
а,
*
x
return
x;
return
}
*
float
b,
char
int
b,
float
а,
}
у;
у
mt
с,
*c,
*d)
double
{ }
*d)
{
return
0;
}
{
return
0;
}
public
public
public
public
public
_main
@nothing2$qzcipfpd
@nothingl$qifzcpi
@square$qd
@square$qi
Рис. 15.14. Декорирование имен, обеспечивающее безопасное по типу редактирование связей
Две функции
объявляется
мых
как
значений
square различаются своими списками параметров;
double,
всех четырех
цируются. Имейте
лятора.
а в
в виду,
другом
как
в одном
d
int. Типы возвращае¬
в закодированных именах не специфи¬
кодирование имен функций зависит от компи¬
функций
что
Поэтому функция,
i объявляется
компилированная
в
Borland С++,
может иметь де¬
корированное имя, отличное от того, которое она получила бы при использова¬
нии других компиляторов.
Перегруженные функции могут иметь разные типы возвращаемых значе¬
ний, но при этом они все равно обязаны иметь различные списки параметров.
Распространенная ошибка программирования 15.19
Создание перегруженных
ми
типами
Чтобы
функций
возвращаемых
отличить
с идентичными списками параметров и различны¬
значений
функции
приводит
к
синтаксической
ошибке
с одним и тем же именем, компилятор использу¬
Перегруженные функции не обязаны иметь одно
и то же количество параметров. Программисты должны проявлять осторож¬
ность при перегрузке функций с аргументами по умолчанию, поскольку это
ет только списки параметров.
может приводить к неоднозначности.
С++ как
С
«улучшенный»
649
Распространенная ошибка программирования 15.20
Функция
как
с опущенными аргументами
другая перегруженная функция,
Общее методическое
Перегрузка функций
по
умолчанию
это
является
может вызываться точно так же,
синтаксической
ошибкой
замечание 15.2
позволяет устранить
использование макросов
#define
языка
С
над различными типами данных и обеспечи¬
вает строгую проверку типа аргументов, невозможную в макросах
для
выполнения
идентичных
15.14.
Программы
лировавшиеся
С++
на
с
операций
Спецификации внешней
связи
С++ могут вызывать функции, написанные на С и компи¬
компилятора С. Как говорилось в разделе 15.13,
помощью
образом кодирует имена функций для безопасного по типу
Однако в языке С имена функций не кодируются. Та¬
ким образом, функция, компилированная в С, не будет распознана при попыт¬
ке скомпоновать код на С с кодом на С++, поскольку код на С++ ожидает спе¬
специальным
редактирования связей.
функции. С++ дает возможность программи¬
спецификацию внешней связи, которая информирует компилятор,
что функция компилировалась с помощью компилятора С, и не допускают ко¬
дирования компилятором С++ имени функции. Спецификации внешней связи
полезны в том случае, когда уже были разработаны большие библиотеки спе¬
циально закодированного имени
сту задать
циализированных
функций
и пользователь или не имеет доступа к исходному
тексту для перекомпиляции его на
С++,
или у него нет времени для
библиотечных функций из С в С++.
Чтобы сообщить компилятору, что одна
преобразо¬
вания
ровались на
С,
запишите прототипы
extern
"С"
прототип
extern
"С"
//
функции
несколько
или несколько
функций
//
одна
следующим
функций
образом:
компили¬
функция
функций
{
прототипы
функций
}
Эти объявления
пилировались на
сообщают компилятору, что указанные функции не ком¬
и что поэтому для функций, перечисленных в специфи¬
С++
кации внешней связи, кодирование имен выполняться не должно. В этом слу¬
чае функции могут быть надлежащим образом скомпонованы с программой.
Среда С++ обычно включает в себя стандартные библиотеки функций на С и
не
требует от программиста
функций.
использования
спецификаций внешней
связи для
этих
15.15.
Шаблоны
функций
Перегруженные функции обычно используются для выполнения сходных
операций над различными типами данных. Если операции для всех типов дан¬
ных идентичны, то же самое можно сделать в более сжатой и удобной форме
путем определения шаблона функции, средства, введенного в последних вер¬
650
Глава 15
сиях С++.
Программист
пишет одно
шаблона функции. Исходя
этой функции, С++ автоматиче¬
определение
из типа аргументов, задаваемых при вызовах
объектный код для отдельных функций, обрабатывающих вы¬
В языке С эту задачу можно выполнить с помощью макро¬
ски генерирует
зовы каждого типа.
сов,
директивой #define. Однако макросы
создаваемых
оставляют
возмож¬
побочных эффектов и не позволяют компилятору осуще¬
соответствия типов. Шаблоны функций, подобно макросам,
ность для серьезных
ствлять контроль
дают
компактное решение
проблемы,
но при этом
обеспечивают полную про¬
верку типов.
Все определения шаблонов функций
late, за которым следует
заключенный в угловые
список
начинаются с ключевого слова temp¬
формальных параметров шаблона функции,
скобки (< и >). Каждому формальному параметру
предшествует ключевое слово class. Эти формальные параметры используют¬
ся, подобно встроенным типам или типам, определенным пользователем, для
задания типов параметров функции, задания типа возвращаемого
функцией
объявления переменных внутри функции. Далее следует определе¬
шаблона, которое не отличается от определения любой другой функции.
Следующий шаблон функции используется в законченной программе на
значения и
ние
15.15.
рис.
template <class T>
void pnntArray(T* array, const int count)
{
for (int i = 0; i < count; i++)
cout << array[i] <<
";
11
<<
cout
'\n';
}
формальный параметр T в
функцией printArray. Когда ком¬
пилятор С++ находит в исходном коде программы вызов функции printArray,
он подставляет тип первого параметра функции printArray вместо T по всему
Этот шаблон функции объявляет единственный
который
качестве типа массива,
определению
шаблона
и создает
выводится
законченный экземпляр функции для вывода
Затем вновь созданная функ¬
массива данных с элементами указанного типа.
На рис.
ция компилируется.
ментами типа
для
float
и
функций
15.15 создаются экземпляры трех
одна из них принимает массив с элементами типа
массив
третья
типа
int выглядит
void
pnntArray(int *array,
с
int, другая
элементами
типа
массив с эле¬
char. Реализация
так:
const
int
count)
{
for
(int
cout
cout
<<
i
<<
=
0;
i
<
array[i]
count;
<<
"
I++)
";
'\n';
}
Каждый формальный параметр из заголовка шаблона должен появиться в
функции по крайней мере один раз. Имя параметра в спис¬
формальных параметров шаблона может использоваться только один раз.
списке параметров
ке
Имена формальных параметров
быть уникальными.
в
различных шаблонах функций
Рис. 15.15 иллюстрирует использование шаблона функции
не
обязаны
printArray.
С++ как
//
«улучшенный»
С
Использование
#include
651
шаблонов
функций
>
<iostream.h
template <class T>
void printArray(T *array, const int
{
for (int i = 0; i < count; i+r)
cout
cout
<<
<<
[i]
array
"
<<
count)
";
'\n';
()
main
{
const int aCount
6;
7, cCount
5, bCount
int а [aCout]
{1, 2, 3, 4, 5};
float b[bCount]
{1.1, 2.2, 3.3, 4.4, 5.5, б.б, 7.7};
char c[cCount]
// 6-я позиция для нуля
"HELLO";
=
=
=
=
=
=
cout
<<
а
"Array
:
contains
\n";
//
версия
cout << "Array b contains:
//
printArray(b, bCount);
версия
pnntArray(a,
cout
<<
"Array
printArray(c,
return
aCount);
с
contains:
cCount);
//
для
целых
чисел
для
чисел
с
\n";
\n";
версия для
плавающей
точкой
символов
0;
}
Array
а
contains:
1
4
5
2
3
Array b contains:
1.1 2.2 3.3 4.4 5.5 б.б 7.7
Array
с
contains:
H
L
О
E
L
Рис.
15.15. Использование шаблонов функций
Распространенная ошибка программирования 15.21
Отсутствие ключевого слова class, которое должно предшествовать каждому форма¬
льному параметру шаблона функции
Распространенная ошибка программирования 15.22
Неиспользование какого-либо формального параметра шаблона
в
сигнатуре функции
Резюме
Язык С++
могут
является
щих программ на
С++.
надмножеством
языка
С, поэтому программисты
использовать компилятор С++ для трансляции уже существую¬
С
и
затем
постепенно
переводить эти программы
на
652
Глава 15
Язык С требует, чтобы комментарий был
/*
*/. С++ допускает
и
шаемые
концом
заключен
между символами
начинающиеся
комментарии,
с
//
и
завер¬
строки.
Язык С++ допускает размещение объявлений всюду, где может находи¬
Объявления переменных могут также
ться исполняемый оператор.
включаться
в
раздел
инициализации
for.
структуры
Язык С++ предусматривает альтернативную обращениям к функциям
и scanf возможность для обработки ввода/вывода стандартных
printf
типов данных и строк.
Выходной
поток cout и операция «
износится
«послать
ток cin
в»)
(операция передачи в поток, про¬
применяются для вывода данных. Входной по¬
»
(операция извлечения
обеспечивают
ввод данных.
из»)
и
«взять
операция
Язык С++ предоставляет программисту
пользователем типы
ляемые
union
с
из потока,
произносится
возможность создавать опреде¬
слов enum,
помощью ключевых
struct,
class. Имена-этикетки перечислений, структур, объединений
и
или классов могут впоследствии использоваться для определения пере¬
менных
нового
Программы
не компилируются,
прототип или
чае
типа.
функция
отдельного
если для
каждой функции
обращения
требуется).
не определена до
прототипа
функции
не
к
не задан ее
ней (в
этом слу¬
которая не возвращает значения, объявляется с типом воз¬
вращаемого значения void. При попытке возвратить значение из этой
Функция,
функции
или
использовать
В
языке
С++ пустой
ее
результат
ции компилятор генерирует
вызова
в
вызывающей функ¬
ошибку.
список параметров
специфицируется либо пусты¬
круглыми скобками, либо записью в них слова void. В С в случае
пустого списка параметров отключается проверка аргументов функции.
ми
Встроенные функции исключают накладные расходы, связанные
зовом функций. Программист, используя ключевое слово inline,
с вы¬
тует компилятору генерировать код функции на месте ее вызова
(если
это
возможно), чтобы
лятор
может
Язык
С++
ссылке
раметр
минимизировать
игнорировать
обращения
к
сове¬
функциям. Компи¬
спецификатор inline.
предусматривает явную
форму передачи параметров
Чтобы указать, что
с использованием параметров-ссылок.
по
па¬
функции передается по ссылке, поместите после типа этого па¬
функции символ амперсанда &. При вызове функ¬
раметра в прототипе
переменной, и она будет передана по ссылке.
В вызываемой функции ссылка на локальное имя этой переменной на
ции укажите просто имя
исходной переменной в вызывающей функции.
Таким образом, исходная переменная может непосредственно модифи¬
цироваться вызываемой функцией.
самом деле относится к
Переменные-ссылки могут также создаваться для локального использо¬
вания внутри некоторой функции в качестве псевдонимов других пере¬
менных. Переменные-ссылки должны инициализироваться при их объ¬
явлении и не могут быть переназначены в качестве псевдонимов других
переменных. Как только ссылка объявлена в качестве псевдонима пере¬
С++ как
«улучшенный»
С
653
менной, все операции, выполняемые якобы над псевдонимом, на самом
деле выполняются над этой
При
помощи
Константная
менные».
переменной.
модификатора const
можно создавать
«константные пере¬
переменная должна быть
инициализирована
константным выражением при ее объявлении и не может быть измене¬
на в
дальнейшем. Константные переменные
часто называют именован¬
ными константами или переменными только для чтения.
переменные могут
использоваться
всюду,
где
ожидается
Константные
константное
Другими
распространенными случаями применения моди¬
фикатора const являются константные указатели, указатели на кон¬
станты и константные ссылки.
выражение.
new и delete языка С++ предоставляют программисту более
изящный способ управления динамическим распределением памяти,
чем функции malloc и free языка С. Операция new принимает тип в ка¬
Операции
честве
мера
операнда,
и
нимает
new,
указатель
объект соответствующего раз¬
создает
правильного
в качестве операнда указатель на
освобождает
и
С++
Язык
автоматически
возвращает
типа.
Операция delete
память.
позволяет
программисту
специфицировать аргументы
умолчанию и значения по умолчанию для этих аргументов.
мент
по
умолчанию
при
вызове
функции опускается,
Аргументы
(последними) параметрами
Аргументы
то
по умолчанию должны
в списке
по
Если аргу¬
используется
по умолчанию должны
значение по умолчанию.
ми правыми
при¬
объект, созданный операцией
быть
самы¬
параметров функции.
объявляться при первом
появлении
функции.
имени
Унарная
операция разрешения области действия
программе
ствия
обращаться
находится
к
локальная
Возможно определение
(::)
дает возможность
глобальной переменной, когда
переменная
нескольких
с
тем
функций
же
в
области дей¬
именем.
с одним и тем же именем,
Это называется
перегрузкой
различными наборами параметров.
функций. При вызове перегруженной функции компилятор автомати¬
чески выбирает соответствующую функцию путем анализа числа и ти¬
но
с
па аргументов вызова.
Перегруженные функции могут иметь разные типы возвращаемых зна¬
чений, но они обязаны иметь различные списки параметров. Объявле¬
ние двух функций, различающихся только типами возвращаемых зна¬
чений,
вызовет
ошибку
компиляции.
В некоторых ситуациях необходимо вызывать функции, написанные на
С и транслированные с помощью компилятора С (например, функции
специализированной библиотеки компании, которые не были преобра¬
зованы в С++ и перекомпилированы). В С++ имена функций обрабаты¬
ваются иначе, чем в
не
С. Таким образом, функция, компилированная
в
С,
распознана при попытке скомпоновать код на С с кодом на
Язык С++ предусматривает спецификацию внешней связи, ин¬
будет
С++.
формирующую компилятор, что функция транслировалась с помощью
компилятора С, и ее имя не должно кодироваться подобно имени функ¬
ции С++.
654
Глава 15
Шаблоны функций дают
возможность
функции, которые
порождать
выполняют одинаковые операции над различными типами данных, од¬
нако
шаблон функции при
сам
этом
только
определяется
один
раз.
Терминология
<iostream.h>
область свободной
asm
catch
однострочный комментарий (//)
операция «взять из» (>)
cin (входной поток)
class
операция «послать в»
операция delete
const
cout (выходной поток)
friend
операция new
операция извлечения из потока (>)
операция передачи в поток (<)
inline
параметр-ссылка
(встроенная) функция
памяти
(<)
template
this
throw
try
virtual
перегрузка
аргументы функции по умолчанию
безопасная по типу компоновка
библиотека потоков
спецификация внешней связи
перегрузка функций
переменная «только для чтения»
переменная-константа
сигнатура
суффикс-амперсанд (&)
типы ссылки
динамические объекты
унарная операция разрешения
области действия (::)
шаблон
именованная константа
инициализатор
класс
шаблон функции
ключевое слово operator
Распространенные ошибки программирования
15.1.
Забывают закрыть комментарий
15.2.
Объявление переменной
в
стиле
С
символами
после ссылки на нее в
*/.
каком-либо операто¬
ре.
15.3.
Попытка возвратить
вать
15.4.
Программы
не
С++
на
объявлен
ее
функции
функции void.
значение из
вызова
результат
не компилируются,
прототип
или
она
не
типа
void
если для
определена
или использо¬
каждой функции
до обращения к
ней.
15.5.
С++
является
не
развивающимся
сти
могут
ние
нереализованных
поддерживаться
свойств
языком,
вашим
языка
и
некоторые
его
приводит
к
особенно¬
Использова¬
компилятором.
синтаксическим
ошибкам.
15.6.
Поскольку доступ к параметрам-ссылкам в теле вызываемой функ¬
ции производится просто по имени, программист может по неосто¬
рожности
обращаться
с параметрами-ссылками как с параметрами,
передаваемыми по значению.
нены
15.7.
Это
может привести к неожиданным
эффектам, если исходные
вызывающей функцией.
побочным
Отсутствие
инициализации
копии переменных
переменной-сс
ялки при ее
будут
изме¬
объявлении.
С++ как
«улучшенный»
15.8.
Попытка
С
655
переназначить
псевдонима
15.9. Попытка
ранее
объявленную ссылку
для
качестве
другой переменной.
переменной-ссылки
разыменования
косвенной адресации (не забывайте,
мом
в
переменной,
а
не
с помощью операции
что ссылка является псевдони¬
указателем
на
нее).
указателя или ссылки на автоматическую перемен¬
ную, определенную в вызываемой функции.
15.10.Возвращение
15.11.Использование переменных с модификатором const в объявлениях
массивов и помещение переменных с модификатором const в заго¬
ловочные
и
той
же
файлы (включаемые в различные исходные
программы) запрещено в С, но разрешено
файлы одной
в
С++.
15.12.Инициализация переменной const неконстантным выражением,
например, переменной, не объявленной с модификатором const.
15.13.Попытка
изменить
константную
15.14.Попьггка
изменить
константный указатель.
15.15.0свобождение с
операцией new.
помощью
переменную.
delete памяти, которая
не
была выделена
операции delete без скобок ([ и ]) при удалении дина¬
мически созданных массивов (это существенно только для масси¬
вов, содержащих объекты пользовательских типов).
15.16.Применение
15.17.0пределение и попытка использовать аргумент по умолчанию, ко¬
торый не является самым правым (последним) аргументом (если
одновременно
менты справа
не
являются
от
него).
аргументами
по
умолчанию
все
аргу¬
15.18.Попытка обращения к не глобальной переменной во внешнем блоке
с использованием унарной операции разрешения области действия.
15.19.Создание перегруженных
функций
с идентичными списками пара¬
метров и различными типами возвращаемых
значений приводит
к
синтаксической ошибке.
15.20.Функция
ваться
ляется
с опущенными аргументами по умолчанию может вызы¬
точно так
же,
как другая
перегруженная
функция;
это
яв¬
синтаксической ошибкой.
ключевого слова class, которое должно предшествовать
формальному параметру шаблона функции.
15.21.0тсутствие
каждому
15.22.Неиспользование какого-либо формального параметра шаблона
в
сигнатуре функции.
Хороший
15.1.
стиль
программирования
Использование комментариев
в стиле языка
С++
с
//
помогает из¬
бегать ошибок незавершенного комментария, которые
при
15.2.
пропуске
символов
*/,
закрывающих комментарии С.
Применение ориентированного
языка
возникают
на
потоки
ввода/вывода
С++ делает программы более удобочитаемыми (и
в
стиле
менее уяз¬
Глава 15
656
вимыми для
ций printf
15.3.
15.4.
чем
их эквиваленты на
Модификатор inline следует
используемыми
Используйте указатели
константы
жащих
для
с
функ¬
использовать
с
только
первого ис¬
небольшими,
для
передачи
передачи параметров,
функцией,
которые
используйте
и
большого размера,
параметров
могут
ссылки
не
на
подле¬
изменению.
Преимуществом
const
использования
переменных
модификатором
с
вместо символических констант является то, что переменные
модификатором const
видимы
Задание аргументов
символического
для
#define,
константы, определяемые с помощью
15.7.
с вызовами
функциями.
быть изменены вызываемой
15.6.
С
scanf.
Размещение объявлений переменных поблизости от их
пользования может сделать программу более ясной.
часто
15.5.
ошибок),
и
отладчика;
для него невидимы.
по умолчанию может упростить отдельные вы¬
некоторые программисты считают, что яв¬
ное указание всех аргументов вызова делает программу более по¬
нятной.
зовы
15.8.
15.9.
функций. Однако
в программе переменных с одним и тем же
именем для различных целей. Во многих случаях это является до¬
пустимым, но может создавать путаницу.
Избегайте объявления
Перегруженные функции, выполняющие
удобочитаемыми
лают программы более
Советы по повышению
15.1.
времени
понятными.
эффективности
Использование встроенных функций
нию
аналогичные задачи, де¬
и
выполнения
может
но
программы,
привести
при
этом
к
уменьше¬
увеличить
ее
размер.
15.2.
Для больших объектов используйте параметр-ссылку с const, что¬
бы имитировать внешний вид и надежность передачи параметра по
значению, но избежать накладных расходов, связанных с переда¬
чей копии большого объекта.
Советы по переносимости программ
15.1.
Смысл пустого
списка
параметров функции
отличается от его смысла в
С. В С
бая проверка параметров. В С++
нимает параметров.
объявления, могут
Общие
15.1.
С++ разительно
блокируется
лю¬
функция не при¬
С, использующие такие
это означает, что
Поэтому программы
выполняться
в языке
это означает, что
иначе
на
при
компиляции
в
С++.
методические замечания
Любое
изменение
встроенной функции требует
всех функций, являющихся
ее клиентами.
венным в определенных ситуациях,
провождением программ.
перекомпиляции
Это может быть сущест¬
связанных с
разработкой
и со¬
С++ как
15.2.
657
С
«улучшенный»
Перегрузка функций
#define
языка
позволяет устранить использование макросов
С для
выполнения
идентичных
операций
над раз¬
личными типами данных и обеспечивает строгую проверку типа ар¬
невозможную в макросах.
гументов,
Упражнения
15.1.
для самоконтроля
Заполните пробелы
a) В
языке
С ++
в
из
каждом
следующих
можно объявить несколько
же именем, но различными типами
с одним и тем
числом аргументов.
Это
функций.
называется
b)
дает возможность
ной
и/или
предложений:
функций
обращаться к глобальной перемен¬
текущей области дейст¬
с тем же именем, что и переменная в
вия.
динамически выделяет память для нового
c) Операция
объекта.
d) Предположим, что
ными и мы образуем
в
С
а и
b
являются целочисленными перемен¬
являются переменными с
Теперь предположим, что с и d
плавающей точкой и мы образуем сумму
с + d.
операции +
сумму а + b.
Очевидно, что
целей. Это
личных
которое
две
также
рые мьГ
обсуждали,
f) Ключевые слова
,
для
менных
только
В С++
с
.
и
,
создания
новых
типов
данных
в
С++.
используется для объявления пере¬
g) Модификатор
ные
раз¬
С ++,
операций.
и
являются
используются
h)
в языке
предопределенными в С++ потоковыми объектами, кото¬
e) Двумя
языке
для
здесь используются
пример свойства, имеющегося
имеется в С и называется
для
чтения.
возможна
помощью
поноваться
с
i)
,
компилятора
программой
на
функций дают
С,
чтобы функции, компилирован¬
могли
надлежащим
образом
ком¬
С++.
возможность
определить
одну
функ¬
цию для выполнения некоторых действий над различными типами
данных.
15.2.
(Верно/неверно) При написании комментария в виде блока, для ко¬
торого требуется много строк текста, более экономным является ис¬
пользование разделителя комментария С++ //, чем обычных разде¬
лителей С /* и */.
15.3. Почему
ние
15.4.
в языке
типа
С++ прототип функции
в виде float&?
может содержать
объявле¬
параметра
(Верно/неверно) Все
вызовы в
С++
выполняются с
передачей
пара¬
метров по значению.
15.5.
Объясните, почему операции « и » в языке С++ являются пере¬
груженными.
658
Глава 15
15.6.
Напишите фрагменты программ на С++ для выполнения каждой
из следующих задач:
a) Запишите
ток
строку «Welcome to С!» в
b) Прочитайте
по¬
переменной age
значение
из
стандартного входного
cin.
потока
15.7.
стандартный выходной
cout.
Напишите законченную программу на С++, которая, используя по¬
токовый ввод/вывод и встроенную функцию sphereVolume, запра¬
шивает у пользователя
(и выводит) объем
значение радиуса шара,
этого
а затем вычисляет
формуле volume
по
шара
=
(4/3)
PI
*
pow(radius, 3).
15.8.
Что бы произошло,
ту
функцию
же
зошло
в
языке
с
если
бы
в языке
различными
С
вы дважды
типами
объявили одну и
бы прои¬
параметров? Что
С++?
Ответы к упражнениям для самоконтроля
15.1.
b) Унарная операция разрешения области действия
d) перегрузкой, e) cin, cout. f) enum, struct, union,
class. g) const. h) спецификация внешней связи, i) Шаблоны.
а) перегрузкой,
(::). с)
new.
15.2. Неверно. Каждый
ходимо
С++
15.3.
15.4.
должен
из обычных
один
в
раз,
появляться
в
разделителей
то
время
начале
С необ¬
//
языка
каждой новой строки.
типа «ссылка
ссылке
полу¬
допускает настоящую передачу по ссылке посредст¬
параметров-ссылок в дополнение к использованию для этой
указателей.
Операция
» является в зависимости от контекста либо
сдвига вправо, либо
операцией
извлечения из потока.
является в зависимости от контекста
либо операцией передачи
15.7.
в стиле языка
разделитель
Неверно. С++
цели
15.6.
как
Потому что программист объявляет параметр-ссылку
на» float, чтобы посредством передачи параметра по
чить доступ к исходной переменной
аргументу.
вом
15.5.
написать
а)
cout
b)
cin
<<
>>
"Welcome
to
inline
mam
либо операцией сдвига влево,
поток.
C!";
аде;
// Встроенная функция,
#include <iostream.h>
const
в
float
float
PI
=
вычисляющая
объем шара
3.14159;
{return
sphereVolume(const float r)
4 .0 / 3.0 * PI * r * r
()
{
float radius;
cout << "Enter
cin
операцией
Операция «
>>
radius;
the
length of the radius
of your sphere:";
*
r;
}
С++ как
659
С
«улучшенный»
<<
cout
of
"Volume
"
"is
<<
sphere with
"
radius
<<
<<
sphereVolume(radius)
radius
<<
'\n';
return 0;
}
15.8.
В
языке
двум
С произошла бы ошибка компиляции, означающая,
функциям было присвоено
является примером перегрузки
какой ошибки не
что
одно и то же имя. В языке С++ это
функций
что допустимо,
и ни¬
бы.
возникло
Упражнения
15.9. Предположим,
что некая организация в настоящее время делает в
основной упор на языке С и хотела бы перейти
программированию в объектно-ориентированной среде С++. Ка¬
кая стратегия подходит для перехода от среды программирования
С к среде С++?
программировании
к
15.10.Напишите несколько поточно-ориентированных операторов
С++ для выполнения каждой из следующих задач:
a) Отобразите
b) Введите
типа float.
с
на
экране
водом
«HELLO».
клавиатуры
15.11.Сравните ввод/вывод
в стиле
значение
в стиле
переменной temperature
для
printf/scanf
с потоковым
вводом/вы¬
в стиле языка С++.
15.12.B этой главе мы упоминали, что существует много случаев, когда
С++ автоматически «знает», какие типы
Перечислите
15.13.Напишите
ввод/вывод
как
можно
программу
больше
на
таких
необходимо
использовать.
ситуаций.
С++, которая использует потоковый
для запроса у пользователя семи целых чисел, а также
определяет и выводит их максимум.
15.14.Напишите законченную программу на
С++, которая использует
по¬
токовый ввод/вывод и встроенную функцию circleArea для запроса
у пользователя радиуса круга, а также вычисления и вывода пло¬
щади
этого
круга.
15.15.Напишите законченную программу
ми
функциями,
бавляет
тем
1
к
описанными ниже,
на
С++
с тремя альтернативны¬
каждая из которых просто до¬
переменной count, определенной
сопоставьте
три
альтернативных
в
функции main. За¬
подхода.
Эти три функции
таковы
a) Функция addlCallByValue, которая получает копию переменной
прибавляет 1 к этой копии и возвращает новое
count по значению,
значение.
b) Функция addlByPointer,
в
которой
доступ к
переменной count
передается посредством имитации ссылки с использованием указа¬
теля и применяется операция разыменования * для прибавления 1
к исходной копии переменной count в main.
c) Функция
addlByReference, в который происходит подлинная пе¬
переменной count посредством параметра-ссылки
редача по ссылке
660
Глава 15
и 1
прибавляется
исходной
к
ванием ее псевдонима
15.16.Для
переменной
(т.е. параметра-ссылки).
копии
count с использо¬
чего используется унарная операция разрешения области
дей¬
ствия?
15.17.Сравните динамическое
ций С++ new и delete с
распределение памяти с помощью опера¬
динамическим распределением памяти по¬
функций malloc
средством
и
free стандартной библиотеки
языка
С.
различные свойства языка С++ из представленных
нами в этой главе, которые представляют собой усовершенствова¬
ния языка С и не связаны с объектно-ориентированным програм¬
15.18.Перечислите
мированием.
15.19.Напишите программу, которая использует шаблон функции с име¬
нем min для определения наименьшего из двух аргументов. Проте¬
программу, используя пары чисел типов int, char и float.
стируйте
15.20.Напишите программу, которая использует шаблон функции с име¬
нем max для определения наибольшего из трех аргументов. Проте¬
стируйте
программу, используя
тройки
чисел типов
int, char
и
flo¬
at.
имеются
15.21.0пределите,
ли
ошибки
граммы. Для каждой ошибки
ние:
что в том
возможно,
граммы ошибок
a)
в
следующих
или
ином
num2,
int
конкретном
нет.
()
main
{
cout
<<
x
int
x;
7;
=
return
0;
}
b) template
int
A>
<class
sum(int
int
numl,
num3)
{
return
+
numl
+
num2
num3;
}
c) /*
d)
Это
void
комментарий
x,
printResults(int
int
у)
{
cout
<<
return
"The
x
+
sum
is
"
<<
x
у;
}
e)
*s
char
delete
f)
float
*ref
g)
int
=
"character
s;
&ref;
=
7;
x;
const
int
у
=
x;
фрагментах про¬
покажите, как ее исправить. Замеча¬
string";
+
у
<<
'\n';
фрагменте про¬
С++ как
«улучшенный»
С
h) template
А
661
<A>
product(A numl,
А
num2,
А
num3)
{
return
*
numl
num2
*
num3;
}
i)
const
pi
=
pi
=
//
k)
char
*s
=
new
char[10];
delete
s;
double
cube(int);
mt
3.14159;
Это комментарий
j)
1)
double
3;
cube(int);
Scan AAW
г
л
в
а
а
i6
Классы
и
абстракция
данных
Цели
Понять принципы инкапсуляции и сокрытия данных
при конструировании программного обеспечения.
Усвоить
пов
понятия
абстракции
данных и
абстрактных
ти¬
'(ADT).
Научиться
именно
создавать
абстрактные
типы данных
С++,
а
классы.
Изучить создание, использование и уничтожение объ¬
ектов класса.
Изучить управление доступом
функциям объектов.
Получить представление
на
объекты.
о
к
элементам
данных
и
преимуществах ориентации
664
Глава 16
Содержание
16.1.
Введение
16.2.
Определение структур
16.3.
Доступ
к
элементам
структур
16.4. Реализация
пользовательского типа Time с
16.5.
Реализация
абстрактного
16.6.
Область действия
16.7.
Отделение интерфейса
класса
16.8. Управление доступом
Функции
16.9.
типа
и
доступа
и
от
данных
доступ
к
структуры
помощью
Time
с
помощью
элементам
класса
класса
реализации
к элементам
сервисные
класса
функции
16.10. Инициализация объектов класса: конструкторы
16.11. Использование
с
конструкторами параметров
по
умолчанию
16.12. Деструкторы
16.13. Когда
вызываются
16.14.Использование
16.15.Скрытая
16.16.
элементов
16.17. Повторное
по
и
данных
ловушка: возвращение
Присваивание
Резюме
конструкторы
деструкторы
и
умолчанию путем
использование
Распространенные
элементов-функций
ссылки на
закрытый
элемент данных
поэлементного
копирования
программного обеспечения
ошибки программирования
Хороший
стиль
эффективности Общие методи¬
ческие замечания
Упражнения для самоконтроля Ответы к упражнени¬
ям для самоконтроля
Упражнения
программирования
Советы
по повышению
16.1.
Введение
Теперь мы вплотную подходим к концепции ориентации на объекты. Мы
увидим, что объектная ориентация является естественным способом восприя¬
тия мира и написания компьютерных программ. Почему же в таком случае
жили
книги? Почему мы отло¬
на С++ до главы 16? От¬
строить, частично будут со¬
первой странице этой
объектно-ориентированное программирование
мы не начали с этого прямо на
вет состоит
объекты, которые мы будем
фрагментов структурных программ, поэтому сначала нам
было заложить фундамент в виде структурного программирования.
стоять
из
в том, что
нужно
Классы и
абстракция
665
данных
В первых главах мы сосредоточились на «традиционных» методах струк¬
турного программирования. В этой главе мы представляем базовые понятия
(т.е.
«мышление
объектами»)
и терминологию
(т.
«разговор на языке
e.
объек¬
тов») объектно-ориентированного программирования. В последующих главах
мы рассмотрим более существенные вопросы и подойдем к решению сложных
задач
при
(OOD)\
мы
методов
помощи
объектно-ориентированного проектирования
проанализируем типичные постановки задач, которых требует по¬
объекты необходимы для ре¬
ализации этих систем, какие атрибуты потребуются для этих объектов, каким
должно быть поведение этих объектов, и зададим способы взаимодействия
объектов друг с другом для достижения общесистемных целей.
строение программных систем, определим, какие
Мы
начнем
с
представления
ориентации. Посмотрите
на
некоторых
реальный
ключевых
терминов
Куда бы
мир вокруг вас.
объектной
вы ни посмот¬
объекты! Люди, животные, растения, автомоби¬
ли, самолеты, здания, газонокосилки, компьютеры и тому подобное. Люди
мыслят представлениями объектов. Мы обладаем замечательной способностью
рели, всюду вы увидите их
абстракции,
которая позволяет нам рассматривать
изображение
на экране в
как, например, люди, растения, деревья и горы, а не отдель¬
ных цветных точек. Мы можем, если захотим, мыслить представлениями пля¬
виде
объектов,
жей, а не песчинок, леса, а не деревьев, и домов, а не
Может быть,
нам покажется разумным разделить
объекты. Живые объекты
кирпичей.
объекты
на две катего¬
определенном смысле
и
Они
Неживые
что-то
объекты,
делают.
«действуют».
движутся
вроде полоте¬
нец, кажется, мало что делают. Они только в некотором роде «создают фон».
Однако все эти объекты имеют немало общего. Все они обладают атрибута¬
живые
рии
и неживые
ми, вроде размера,
формы,
в
цвета, веса и т.п. И все они демонстрируют разно¬
образное поведение, например, мяч катится, подпрыгивает, его надувают и
спускают; ребенок кричит, спит, ползает, ходит и моргает; автомобиль разго¬
няется, тормозит, поворачивает и т.д.
Люди познают объекты, изучая их свойства и наблюдая за их поведением.
Различные объекты могут иметь много одинаковых свойств и демонстриро¬
вать сходное поведение. Можно, например, сравнивать детей и взрослых, лю¬
дей и шимпанзе. Легковые автомобили, грузовики, красные фургончики и ро¬
ликовые коньки имеют много
общего.
Объектно-ориентированное программирование (OOP)
моделирует объек¬
ты реального мира при помощи их программных эквивалентов. Оно использу¬
ет понятие класса, когда объекты некоторого класса
например, класса
имеют одни и те же характеристики.
средств передвижения
отношения
наследования
и даже сложного
наследования,
Оно использует
когда вновь создава¬
емые классы объектов получаются путем наследования характеристик суще¬
ствующих классов и при этом содержат, однако, свои собственные уникаль¬
ные
атрибуты. Дети
наследуют многие особенности своих
родителей бывают высокие
Объектно-ориентированное программирование
иногда у низкорослых
родителей,
однако
дети.
дает нам возможность под¬
ходить к процессу создания программ более естественно и интуитивно, а имен¬
но путем моделирования объектов реального мира, их свойств и их поведения.
OOP
также моделирует коммуникацию между
люди посылают друг другу
новобранцам стать
ством сообщений.
по
объектами. Точно
так же,
сообщения (например, сержант, дающий
стойке смирно),
так и
как
команду
объекты взаимодействуют посред¬
666
Глава 16
00P инкапсулирует данные
называемых
функции (поведение) в пакетах,
объекта
тесно связаны друг с дру¬
функции
данные и
и
Объекты обладают свойством сокрытия информации. Это означает, что
объекты могут знать о способах взаимодействия друг с другом посредст¬
гом.
хотя
вом
объектами;
(атрибуты)
точно
интерфейса, обычно
определенного
они
способах реализации других объектов
не
располагают
информа¬
реализации со¬
крыты внутри самих объектов. Не вызывает сомнения, что можно эффективно
управлять автомобилем и не зная подробностей внутренней работы двигателя,
цией
о
подробности
передач и выхлопной системы. Мы увидим, почему сокрытие инфор¬
является
столь критичным для систематической разработки програм¬
мации
коробки
много обеспечения.
В языке С и других
процедурных
языках программирования существует
действие,
тенденция к ориентации на
в
то
время
функция. В С++ программной единицей
является
которого в конечном итоге создаются
программирование на
как
C+4- имеет тенденцию к объектной ориентации. В С
программной единицей
является
представители
класс,
на основе
класса, т.е. экземпляры
объектов.
Программирующие
С концентрируют
на
ций. Совокупность действий,
важны в языке
на
написании
выполняющих некоторую
ляется в виде функции, а совокупность
безусловно,
функ¬
общую задачу, оформ¬
функций образует программу. Данные,
внимание
С,
однако имеет место точка зрения, что данные
функциями действий.
спецификации системы помогают программисту определить набор
функций, совместная работа которых обеспечит ее реализацию.
существуют прежде всего для поддержки выполняемых
Глаголы
в
Программирующие
венных
С++ концентрируются
на
пользовательских
держит как данные, так и
типов,
называемых
набор функций,
на определении своих
классами.
Каждый
собст¬
класс со¬
манипулирующих этими данными.
класса, входящие в его состав, называются элементами данных клас¬
Данные
са. Функциональные
компоненты
класса
называются
его
элементами-функ¬
циями. Если представитель встроенного типа, например, типа int, называется
переменной,
то представитель пользовательского типа
объектом. Внимание
ях.
Существительные
в
набор классов
работа которых
определить
С++ фокусируется
(т.е. класса)
объектах,
называется
функци¬
спецификации системы помогают программисту
С++, на основе которых будут созданы объекты, со¬
в языке
на
а не на
обеспечит реализацию системы.
Классы в С++ являются естественным развитием понятия struct языка С.
вместная
Прежде
еще
чем
перейти
к специальным вопросам
раз рассмотрим структуры
на структуре.
обосновать
Слабости, которые
Рассмотрим
Определение структур
{
hour;
mt minute;
int second;
//
0-23
//
0-59
0-59
//
классов в
мы выявим при этом подходе,
следующее определение структуры:
struct Time
};
разработки
С++,
мы
пользовательский тип, основанный
понятие класса.
16.2.
int
и создадим
помогут нам
Классы и
вы
абстракция
667
данных
Давайте повторим терминологию, связанную со структурами (см. гла¬
и 12). Ключевое слово struct начинает определение структуры. Иденти¬
10
фикатор Time
структуры. Этикетка структуры име¬
объявления переменных структурно¬
структурного типа является Time (в противо¬
называется этикеткой
нует ее определение и используется для
го типа. В этом примере именем
более длинному
положность
объявленные
ются
в
элементами
структуры.
иметь уникальные имена,
элементы
с
имени
фигурных скобках
одним
и
тем
определение структуры
в
struct Time, обязательному в С).
области определения структуры,
Элементы одной
той
и
же
структуры
Имена,
называ¬
должны
однако две различные структуры могут содержать
же
без
именем
конфликта. Каждое
возникновения
должно заканчиваться точкой с запятой. Как мы ско¬
ро увидим, все вышесказанное применимо и для классов.
hour, mi¬
Определение структуры Time содержит три элемента типа int
nute и second. Элементы структуры могут иметь любой тип. Структура не мо¬
жет, однако, содержать экземпляр
быть объявлен
себя самой. Например,
элемент типа
Time
определении структуры Time. Однако в нее может
быть включен указатель на структуру Time. Структура, содержащая элемент,
являющийся указателем на тот же самый структурный тип, называется ссы¬
не может
лающейся
на
в
себя структурой. Такие структуры
полезны при построении свя¬
(см. главу 12).
определение структуры не резервирует места
занных структур данных
Предыдущее
определение
новый
создает
ния переменных. Структурные
других типов. В объявлении
Time
timeObject,
Time
и
Для
доступа
типа
timePtr указателем
16.3.
Доступ
ция-точка обращается
Time, timeArray
на
это
массивом из
эле¬
структур
(или класса)
операция-точка (.)
и
используются операции
операция-стрелка (->). Опера¬
(или класса) через
Например, чтобы вывести
к элементу структуры
ной объекта или ссылку на объект.
10
объект Time.
к элементам
доступа к элементам структуры
к элементам
памяти;
который
*timePtr;
timeArray[10],
timeObject объявляется переменной
ментов типа
в
используется для объявле¬
переменные объявляются подобно переменным
тип данных,
имя перемен¬
элемент
hour
структуры timeObject, используйте оператор
cout
<<
timeObject.hour;
Операция-стрелка, состоящая из
пробелов между ними, обращается к
указатель на объект.
Предположим,
зцака минус
(-)
и
знака
элементу структуры
что для
обращения
к
больше (>) без
(или класса)
через
объектам Time был
объявлен указатель timePtr и что ему был присвоен адрес структуры timeOb¬
ject. Чтобы вывести элемент hour структуры timeObject при помощи указате¬
ля
timePtr, используйте оператор
cout
<<
timePtr->hour;
Выражение timePtr->hour
эквивалентно
(*timePtr).hour, в
обращение к элементу hour
выражению
котором происходит разыменование указателя и
при помощи операции-точки. Круглые скобки здесь необходимы, поскольку
операция-точка (.) имеет более высокий приоритет, чем операция разыменова¬
ния указателя
(*). Операция-стрелка
и
операция элемента структуры, наряду
668
Глава 16
самый высокий приоритет
с круглыми и квадратными скобками
имеют
(из рассмотренных до
ассоциируются слева направо.
([]),
операций) и
сих пор
16.4. Реализация
с
пользовательского типа
Time
помощью структуры
На рис. 16.1 создается пользовательский структурный тип Time с тремя
hour, minute и second. В программе определя¬
целочисленными элементами:
Time
ется одна структура
dinnerTime
с именем
нием
30
second
и
значением
Обратите
ссылки
структуры
Time
в
константные
функции print...
значением
18, minute
0. Затем программа выводит время
стандартном форматах.
на
и используется операция-точ-
hour
ка для инициализации элемента структура
внимание, что
в
значе¬
военном и
функции print... получают
Time. Это приводит
к
передаче
структур
образом, непроизводи¬
передачей структур функциям
исключая, таким
по ссылке
тельные затраты на копирование, связанные с
по значению, а также предотвращает изменение структуры Time функциями
print.... В
главе
17
мы
обсудим объекты
и
элементы-функции
с
модификато¬
ром const.
// FIG16_1.
//
Создает
#include
CPP
структуру,
Time
struct
у.станавливает
//
определение
int
hour;
//
0-23
int
mmute;
//
0-59
mt
second;
//
0-59
{
ее
выводит
элементы.
структуры
pnntMilitary(const Time &);
void pnntStandard(const Time &);
void
main
и
<iostream.h>
// прототип
// прототип
()
{
Time
//
//
dinnerTime;
присваивает
dinnerTime.hour
элементам
=
dmnerTime.minute
dmnerTime.second
cout
<<
"Dinner
переменная
"
Time
значения
допустимые
30;
0;
=
=
will
be
held
at
";
"
<<
типа
18;
printMilitary(dmnerTime) ;
cout <<
military time,\nwhich is
pnntStandard(dinnerTime) ;
cout
нового
standart
time."
// присваивает элементам
dinnerTime.hour
29;
<<
"
;
endl;
недопустимые
значения
=
dinnerTime.minute
dinnerTime.second
=
=
73;
103;
Рис. 16.1. Создание структуры, инициализация
и вывод ее элементов
(часть 1
из
2)
Классы и
абстракция
cout
<<
669
данных
with
"\nTime
invalid values:
";
pnntMilitary(dinnerTime) ;
cout << endl;
return 0;
// Выводит время в военном формате
void pnntMilitary(const Time &t)
{
cout << (t.hour < 10 ? "0" : ""
<<
(t.mmute
<
10
?
"0м
:
<<
(t.second
<
10
?
"0"
:
<<
)
""
""
t.hour
<<
":"
)
<<
t.minute
)
<<
t.second;
<<
":"
}
// Выводит время в стандартном формате
void printStandard(const Time &t)
{
cout << ( (t.hour
0 | | t.hour
12)
==
<<
":"
<<
<<
":"
<<
<<
(t.hour
<
?
==
(t.minute
(t.second
12
?
"
<
10
<
10
AM"
?
?
:
"0"
:
"0м
:
"
"")
"")
12
:
t.hour
<<
t.mmute
<<
t.second
%
12)
PM");
}
Dinner will be held at 18:30:00 military time,
which is 6:30:00 PM standard time.
Time with invalid values:
29:73:103
Рис. 16.1. Создание структуры, инициализация
Совет
по повышению
и вывод ее элементов
(часть 2
из
2)
эффективности 16.1
Обычно структуры передаются по значению Чтобы избежать накладных расходов
их копирование, передавайте структуры по ссылке
Совет по повышению
эффективности
на
16.2
Чтобы избежать накладных расходов при передаче параметров по значению и вместе
с тем извлечь выгоду из защиты от изменения исходных данИых, передавайте пара¬
метры большого размера
Существуют
в
виде
константных
ссылок
и отрицательные стороны создания новых типов данных с по¬
Поскольку инициализация специально не оговаривается, то
данные могут оказаться неинициализированными и вследствие этого могут
мощью структур.
возникнуть
ся,
что
проблемы. Даже
они
если данные инициализированы, может оказать¬
инициализированы
быть присвоены недопустимые
некорректно.
значения
(как
Элементам структуры могут
мы это сделали на рис.
16.1),
по¬
доступ к данным. Программа присвоила не¬
корректные значения всем трем элементам объекта dinnerTime типа Time.
скольку программа имеет
При
прямой
изменении реализации struct все программы,
должны быть
венно
манипулирует
использующие эту
Это происходит потому, что
типом данных. Не существует
изменены.
struct,
программист непосредст¬
никакого
«интерфейса»,
позволяющего гарантировать, что программист корректно использует тип дан¬
ных и что данные остаются действительными.
Глава 16
670
Общее методическое замечание 16.1
Важно писать понятные и простые для сопровождения программы. Изменения в про¬
граммах являются скорее правилом, чем исключением Программисты должны пред¬
видеть заранее, что их код будет модифицироваться Как мы увидим, классы способ¬
ствуют модифицируемости программ.
Существуют
ка
должны
проблемы,
и другие
С. Эти структуры
выводиться
связанные со структурами в стиле язы¬
нельзя вывести как единое целое, напротив, их элементы
и
форматироваться
по
одному.
Для
вывода элементов
структуры в подходящем формате может быть написана специальная функ¬
ция. В главе 18, «Перегрузка операций», показано, как можно перегрузить
операцию «, чтобы реализовать вывод объектов типа структуры или класса в
удобной форме. Структуры
нельзя сравнивать как единое целое; они должны
В
поэлементно.
сравниваться
зить операции равенства и
главе
18
также
показано,
как
можно перегру¬
отношений для сравнения объектов
типа структу¬
ры и класса.
В следующем разделе
класса и
повторно реализуем нашу структуру Time
мы
продемонстрируем
некоторые
типов данных с помощью классов.
преимущества создания
Мы увидим,
что в
в доступе по умолчанию к их элементам.
16.5. Реализация
С++
классы и структуры
Различие между
могут использоваться практически идентично.
К этому
абстрактного
в виде
абстрактных
ними состоит
мы вскоре вернемся.
типа данных
Time
с помощью класса
Классы дают возможность программисту моделировать объекты, которые
обладают атрибутами (представленными в виде элементов данных) и кото¬
рым
присуще
определенное
поведение
или
действие (представленное
в
виде
определении типов, содержащих элементы данных
и элементы-функции, в С++ обычно используется ключевое слово class.
В других объектно-ориентированных языках программирования элемен¬
элементов-функций). При
ты-функции иногда называют методами и они активируются в ответ на посылае¬
объекту сообщения. Сообщение соответствует вызову элемента-функции.
мые
Сразу
после
определения
объявления объектов этого
класса
класса
класса.
его
имя
может
На рис. 16.2
быть
Time.
class Time {
public:
Time();
void setTime(int, int, int);
void pnntMilitary();
void pnntStandard();
private:
int hour;
23
// 0
-
int
minute;
//
0
int
second;
//
0
-
-
использовано
для
показано простое определение
59
59
Рис. 16.2. Простое определение класса Time
Классы и
абстракция
Определение
нашего
Тело определения
ми
({
класса
класса
}). Определение
и
нашего класса
671
данных
Time,
Time
ограничено
с
начинается
левой
класса завершается
и
точкой
как и в определении структуры
лочисленных элемента
hour, minute
и
ключевого
слова
class.
правой фигурными скобка¬
с
запятой. В определении
Time, содержится три
це¬
second.
Распространенная ошибка программирования 16.1
Забывают
о
точке
Остальные
с
запятой
компоненты
в
конце
класса.
определения
определения
класса
являются
новыми.
Метки
спецификаторами доступа к элементам. Все
элементы данных и элементы-функции, объявленные после спецификатора
public: (и до следующего спецификатора доступа) доступны всюду, где про¬
грамма имеет доступ к какому-либо объекту класса Time. Все элементы дан¬
ных и элементы-функции, объявленные после спецификатора private: (и до
следующего спецификатора доступа) доступны только для функций-элементов
класса. Спецификаторы доступа к элементам класса всегда заканчиваются
двоеточием (:) и могут многократно появляться в определении класса.
public:
и
private:
Хороший
называются
стиль программирования 16.1
Используйте в определении класса каждый спецификатор доступа только один раз из
соображений ясности и удобочитаемости программы Помещайте открытые элементы
в начале определения, чтобы
их
было проще найти
Определение класса после спецификатора public: содержит прототипы
следующих четырех элементов-функций: Time, setTime, printMilitary и printStandard. Эти функции называют открытыми, или публичными элементаинтерфейсом класса. Эти функции будут использова¬
(пользователями) класса для манипулирования его данными.
Элемент-функция с тем же именем, что и сам класс, называется конст¬
это специальная функция-элемент
руктором этого класса. Конструктор
класса, которая инициализирует элементы данных объекта класса. Конструк¬
ми-функциями,
а также
ться клиентами
тор класса вызывается автоматически при создании объекта.
Три
Это
целочисленных элемента появляются после
спецификатора private:.
функций-элемен¬
означает, что эти элементы данных доступны только для
тов класса
образом,
к
и, как мы увидим в
следующей
главе, для
друзей
класса.
Таким
функ¬
(а также друзья
private: класса, а
этим элементам данных могут иметь доступ только четыре
ции, прототипы которых появляются в определении класса
класса). Обычно элементы данных перечисляются в разделе
в разделе public:. Как мы увидим далее, могут сущест¬
элементы-функции
вовать элементы-функции с доступом private и данные с доступом public:.
Сразу после определения класса он может быть использован в качестве
типа в объявлениях, подобных следующим:
Time
sunset,
//
объект
типа
arrayOfTimes[5],
//
массив
объектов
*poiterToTime,
&dinnerTime
sunset;
//
указатель
ссылка на
=
Имя класса становится новым
много
//
на
как может
типа.
быть
типа
объект
объект
спецификатором
объектов класса, подобно тому,
Time
Time
типа
типа
Time
Time
Может существовать
много переменных типа
Глава 16
672
int.
Программист
создавать новые типы клас¬
необходимости
может по мере
сов. Это одна из многих причин,
которым С++ является расширяе¬
благодаря
мым языком.
В программе
на
16.3
рис.
использован
объекта автоматически вызывается конструктор
лизирует
каждый закрытый
ждения того,
Программа
Time.
класс
один экземпляр объекта класса Time с именем t.
При
создает
создании экземпляра
Time, который
явно инициа¬
0. Затем для подтвер¬
были инициализированы должным обра¬
элемент данных значением
что элементы данных
в военном и стандартном форматах. После этого время
устанавливается при помощи элемента-функции setTime и снова выводится в
обоих форматах. Потом элемент-функция Time пытается присвоить элементам
данных недопустимые значения и время снова выводится в обоих форматах.
зом, время выводится
^m
I
I
// FIG16_3.CPP
// Класс Time.
#include <iostream.h>
// Определение
class Time {
public:
Time();
void
setTime(mt,
void
//
//
printMilitary();
private:
int hour;
int
//
0
mmute;
//
0
second;
//
0
//
Конструктор
//
Гарантирует,
в корректном
//
Time::Time()
{
Time
данных
выводит
время
в
военном
выводит
время
в
стандартном
формате
формате
23
--
59
--
59
--
инициализирует каждый элемент данных нулем.
все объекты Time изначально находятся
что
состоянии.
hour
minute
=
second
=
//
Устанавливает
//
Проводит проверку правильности
//
недопустимые
void
(ADT)
// конструктор по умолчанию
// устанавливает элементы
// hour, minute и second
int);
int,
void pnntStandard();
mt
типа
абстрактного
новое
значение
значения
в
времени
0;
}
Time
данных.
в
формате.
военном
Устанавливает
нуль,
int m,
h,
int
Time::setTime(
=
int
s)
{
hour
=
minute
second
(h
=
=
>=
0
(m >=
(s >=
h
<
24
0
&&
m
<
60
0
&&
s
<
60
&&
)
?
)
)
h
?
?
// Выводит Time в военном формате
void Time::pnntMilitary()
{
cout << (hour < 10 ? "0" :
)
""
<<
<<
(mmute
(srcond
<
10
?
"0м
:
<
10
?
"0"
:
""
""
0;
:
m
:
0;
s
:
0;
<<
)
)
hour
<<
":"
<<
minute
<<
second;
<<
":"
}
Рис. 16.3. Реализация абстрактного
типа Time в виде класса
(часть 1
из
2)
Классы и
//
абстракция
Выводит
void
673
данных
в
время
стандартном
формате
Time::pnntStandard()
{
cout
<<
":"
<<
<<
":"
<<
«
<
(hour
0
==
( (hour
<<
hour
| |
(mmute
<
10
(second
"
?
12
<
10
AM"
12
)
"0"
:
"0"
:
==
?
?
"
:
?
12
""
:
)
)
""
hour
%
<<
minute
<<
second
12)
PM");
}
// Программа для тестирования простого класса Time
main()
{
Time t;
// создает экземпляр объекта t класса Time
cout
<<
"The
military
initial
time
is
";
t.printMilitary();
cout
<<
"\nThe
initial
standard
time
is
";
t.pnntStandard();
t.setTime(13,
cout
<<
б);
27,
time
"\n\nMilitary
t.printMilitary();
cout << "\nStandard
time
after
after
setTime
setTime
is
is
";
";
t.printStandard();
// присваивание недопустимых
99);
t.setTime(99,
99,
cout << "\n\nAfter attempting invalid settings :\n"
«
time:
"Military
t.printMilitary() ;
cout << "\nStandard
значений
";
time:
";
t. pnndStandard () ;
cout << endl;
return
0;
The
initial
military
The
initial
standard
time
is
time
is
00:00:00
12:00:00
AM
Military
time
after
setTime
is
13:27:06
Standard
time
after
setTime
is
1:27:06
After
attempting invalid
Military time: 00:00:00
Standard
time:
12:00:00
Рис. 16.3. Реализация
PM
settings:
AM
абстрактного
типа Time в виде класса
(часть 2
из
2)
что элементам данных hour, minute и second
Обычно
private:.
закрытые элементы данных класса недо¬
пределами этого класса. (Как уже говорилось, в главе 17 мы уви¬
закрытым элементам данных класса могут обращаться друзья клас¬
Еще раз обратите внимание,
предшествует
ступны за
дим, что к
са.) Идея
метка
состоит в том, что используемое внутри класса реальное представле¬
ние данных не должно интересовать его клиентов.
рят, что реализация класса
скрыта
от его
Именно
формации способствует модифицируемости программ
класса пользователем.
22
Зак 801
в этом смысле гово¬
пользователей. Такое сокрытие
и
ин¬
упрощает восприятие
674
Глава 16
Общее методическое замечание 16.2
Клиенты класса использует этот класс, не зная внутренних деталей его реализации При
изменении реализации класса (например, из соображений эффективности) клиентов
класса
изменять
не
требуется
Это значительно упрощает модификацию программных
систем.
В
рассматриваемой
нами программе конструктор класса
0 (т.е.
циализирует элементы данных значением
12 AM). Это гарантирует, что объект
Элементам данных не
мени
быть присвоены недействите¬
объекта Time конструктор вызыва¬
могут
льные значения, поскольку при создании
автоматически,
ных отслеживаются
Общее
Обычно
не
а
все
последующие
ини¬
после его создания находится в кор¬
ректном состоянии.
ется
Time просто
военным эквивалентом вре¬
попытки
изменения
элементов
дан¬
функцией setTime.
методическое замечание 16.3
элементы-функции
требуется
состоят всего лишь из нескольких строк кода,
поскольку
никакой логики для проверки корректности значений элементов данных
Заметьте, что элементы данных не могут быть инициализированы при их
объявлении в теле класса. Эти элементы данных либо должны быть инициали¬
зированы конструктором, либо
ными
им могут
быть присвоены
значения специаль¬
функциями установки значений («зеЬ>-функциями).
Распространенная ошибка программирования 16.2
Попытка явной
Функция
льды (~),
инициализации элемента данных класса
с тем же именем, что и класс, с предшествующим символом ти¬
деструктором класса. Деструктор производит заключи¬
«приборку» каждого объекта класса перед тем, как выделенная для
него память будет возвращена системе. Рассматриваемый пример не имеет де¬
структора. Мы обсудим конструкторы и деструкторы более подробно далее в
называется
тельную
этой главе и в главе 17.
Обратите
внимание, что
внешнего мира,
функциям, которые
предшествует метка
public:.
класс
предусматривает для
В открытых (публичных) функ¬
своим
клиен¬
циях реализованы возможности, которые
класс
Открытые функции
терфейсом класса.
интерфейсом либо открытым
там.
Общее
класса называют
предоставляет
ин¬
методическое замечание 16.4
Клиенты класса имеетдоступ к
интерфейсу
класса, но они не должны иметь доступа к
его реализации
Определение класса содержит объявления его элементов данных и элемен¬
тов-функций. Объявления элементов-функций представляют собой прототипы
функций, которые мы обсуждали в предыдущих главах. Элементы-функции
могут быть определены внутри класса, однако хорошим стилем является опре¬
деление
функций
вне определения класса.
Классы и
абстракция
Хороший
стиль программирования 16.2
Определяйте
са.
675
данных
все
элементы-функции, кроме самых маленьких, вне определения
отделению интерфейса класса от его реализации.
клас¬
способствует
Это
Обратите
внимание на использование
области действия (::)
двухместной
операции разрешения
определении каждой из элементов-функций, следую¬
за
класса
на рис. 16.3. Сразу после определения класса и
щих
определением
объявления
в
элементов-функций эти элементы-функции должны быть
функция-элемент класса может быть определена непо¬
средственно в его теле (вместо включения туда ее прототипа) или же она мо¬
жет быть определена после тела класса. Когда элемент-функция определяется
после соответствующего определения класса, имени функции предшествуют
определены.
его
Любая
имя класса и двухместная операция разрешения
области действия (::). Поско¬
классы могут иметь одинаковые имена элементов,
льку различные
разрешения области действия «привязывает»
имя
элемента к
операция
имени
класса,
идентифицируя функции-элементы данного класса.
функция-элемент, объявленная в определении некоторого
однозначно
Хотя
класса,
может быть определена вне его определения, эта функция, тем не менее, нахо¬
дится в области действия класса, т.е. ее имя известно только другим элемен¬
там данного класса, за исключением случаев, когда
дит через объект класса, ссылку
Вскоре
класса.
мы поговорим
на
объект
обращение
класса или
к
подробнее об области действия
класса.
Можно определять элементы-функции и в теле определения
ции размером более одной или двух строк обычно определяются
деления
класса'
это помогает отделить
Если функция-элемент определена
ски становится
встроенной.
интерфейс
ней происхо¬
на объект
указатель
класса.
Функ¬
вне тела опре¬
класса от его реализации.
определении класса, то она автоматиче¬
Элементы-функции, определенные вне определе¬
в
ния класса, можно сделать встроенными путем явного использования ключе¬
вого слова inline. Не забывайте, что компилятор оставляет за собой право не
делать
встроенной любую функцию.
Общее
методическое замечание 16.5
Объявление
элементов-функций внутри определения класса с последующим опреде¬
интерфейс класса от его реализа¬
способствует систематизации разработки программного обеспечения
лением их вне определения этого класса отделяет
ции
Это
Совет по повышению эффективности 16.3
Определение элемента-функции небольшого размера внутри определения класса ав¬
эту функцию встроенной (если с этим согласен компилятор) Это
может повысить производительность системы, однако не всегда согласуется с прин¬
ципами систематической разработки программного обеспечения
томатически делает
Интересно отметить, что элементы-функции printMilitary и printStandard не принимают параметров. Это происходит потому, что элементы-функ¬
ции косвенным образом знают, что они должны выводить элементы данных
Time, для которого они были вызваны. Это делает
элементов-функций более краткими в сравнении с традиционными
того конкретного объекта
вызовы
вызовами
22*
в
процедурном программировании.
676
Глава 16
Общее методическое замечание 16.6
Объектно-ориентированный подход к программированию часто приводит к упроще¬
нию вызовов функций из-за уменьшения числа передаваемых параметров Это преи¬
мущество объектно-ориентированного программирования является следствием того,
что инкапсуляция элементов данных и элементов-функций внутри объекта дает эле¬
ментам-функциям право обращаться к этим элементам данных
Классы упрощают программирование, поскольку клиент (или пользова¬
объекта класса) должен иметь дело только с инкапсулированными, т. e.
тель
в объект, действиями. При разработке такого рода действий
обычно ориентируются на клиентов класса, а не на реализацию. Клиенты не
встроенными
реализацией класса. Интерфейсы тоже изменяются, но
При изменении реализации соответствующим
измениться зависящий от реализации код. Скрывая реализа¬
должны иметь дела с
гораздо реже, чем реализации.
образом
должен
цию, мы устраняем возможность зависимости других частей программы от де¬
талей реализации класса.
Часто нет необходимости создавать классы «с нуля».
могут
производиться
Напротив,
классы
от других классов, предоставляющих программисту
дей¬
ствия, которые могут использовать новые классы. Классы также могут вклю¬
чать в качестве элементов объекты других классов. Такого рода повторное ис¬
пользование программного обеспечения может значительно повысить произво¬
Создание производных классов на основе су¬
и подробно обсуждается в главе 19.
дительность труда программиста.
ществующих называется
Включение
цией
и
наследованием
классов в качестве элементов других классов называется компози¬
обсуждается
16.6. Область
в главе
17.
действия
класса и
доступ
к элементам
класса
Имена переменных и функций, объявленных в определении класса, а так¬
же элементы данных класса и его элементы-функции, принадлежат области
действия класса. Функции, не являющиеся элементами класса, определяют¬
ся в
области действия файла.
Внутри области действия класса
элементы класса непосредственно доступ¬
элементов-функций
обращение просто
по имени. Вне области действия класса на элементы класса можно ссылаться
либо через имя объекта, либо через ссылку, либо через указатель на объект.
ны для всех
этого класса и допускают
Элементы-функции класса могут быть перегружены, но только функция¬
области действия этого класса. Для того, чтобы перегрузить элемент-фун¬
кцию, просто задайте в области определения класса прототип для каждой вер¬
ми в
сии
перегруженной функции
и напишите для
каждой версии
отдельное опре¬
деление.
Распространенная ошибка программирования 16.3
Попытка перегрузить элемент-функцию
действия
этого
класса
класса
функцией,
не
находящейся
в
области
Классы и
абстракция
Элементы-функции
Если
в
677
данных
имеют
внутри
область действия функции.
класса
функции-элементе определена переменная
областью действия класса,
с тем же именем, что и пере¬
будет скрыта переменной
функции-элемента внутри области действия функции. Доступ к таким скры¬
менная с
то
последняя
тым переменным можно получить посредством операции разрешения
действия,
поместив
перед
этой операцией
имя
класса.
Доступ
к
области
глобальным
переменным может быть получен с помощью одноместной операции разреше¬
ния области действия (см. главу 15).
Операции, используемые для доступа к элементам класса, идентичны опе¬
рациям для доступа к элементам структуры.
применяется операция
объекта
(->)
или
со
применяется
В программе
выбора
ссылкой
на
на
объект.
Для
доступа к элементам объекта
в сочетании
(.)
Операция выбора элемента
элемента
точка
с именем
стрелка
объект.
в сочетании с указателем на
рис. 16.4 для иллюстрации доступа к элементам класса
операций выбора элементов используется простой класс с именем
Count, который содержит открытый элемент данных x типа int и открытую
функцию-элемент print. Программа создает экземпляры трех переменных
типа Count
counter, counterRef (ссылка на объект типа Count) и counterPtr
на
объект
типа Count). Переменная counterRef объявляется в каче¬
(указатель
при помощи
ссылки на переменную counter, а переменная counterPtr объявляется
как указатель на counter. Важно обратить внимание на то, что здесь элемент
стве
был сделан открытым просто для демонстрации того, каким образом
происходит обращение к открытым элементам. Как мы констатировали, дан¬
данных x
ные
обычно делают закрытыми;
во всех последующих примерах мы
будем
по¬
ступать именно так.
16.7. Отделение
интерфейса
от
реализации
Одним из наиболее фундаментальных принципов систематической разра¬
ботки программного обеспечения является отделение интерфейса от реализа¬
ции. Это упрощает модификацию программ. Что касается клиентов класса,
изменения в реализации класса не затрагивают их до тех пор,
изменен его
Общее
пока не
будет
интерфейс.
методическое замечание 16.7
Помещайте объявление
класса в заголовочный
файл, который
любым клиентом, желающим использовать этот класс
Помещайте определения элементов-функций
ный файл Это
реализация класса.
класса
Это
-
должен быть включен
открытый интерфейс
этого класса в
отдельный исход¬
-
Общее методическое замечание 16.8
Клиентам класса не обязательно видеть его исходный код для того, чтобы пользова¬
ться этим классом. Однако клиентам необходимо иметь возможность компоноваться
с объектным кодом класса
Это поощряет поставку независимыми производителями программного
обеспечения библиотек классов для продажи или лицензирования. Независи¬
мые производители поставляют свои программные продукты,
включая в них
678
Глава 16
файлы и объектные модули. При этом не
какой-либо информации, связанной с авторским правом,
как
только заголовочные
случае
предоставления исходного кода.
образом,
рывает, таким
изведенных
^Ш
от
большего
независимыми
Простой
класс
Count
было бы
в
выиг¬
библиотек классов, про¬
числа доступных
доступа
к
элементам
//
// Внимание: в дальнейших примерах
// данные.
#include <iostream.h>
//
это
Сообщество пользователей С++
фирмами.
// FIG16_4.CPP
// Демонстрация операций
class
раскрывается
будем
класса
мы
.
->
и
использовать
закрытые
Count
{
public:
int
x ;
void
}
()
print
{
<<
cout
x
<<
'\n'
;
}
;
main
()
{
Count
объект
//
создает
*counterPtr
=
&counter,
//
указатель
&counterRef
=
counter;
//
ссылка
counter,
cout << "Assign
counter.x
7;
7
to
=
counter.pnnt();
cout
<<
"Assign 8
counterRef.x
counterRef.print
cout
<<
to
8;
=
();
"Assign 10
counterRtr->x
=
x
counterRtr->pnnt();
return 0;
print
на
присваивает
//
вызывает
x
and
prmt
the
object's
name:
элементу данных
a
using
присваивает
//
вызывает
and
7
";
x
элeмeнт-фyнкцию'print
//
x
counter
counter
counter
using
//
to
10;
and
на
8
reference:
";
элементу данных
x
элемент-функцию pnnt
using
print
//
присваивает
//
вызывает
10
а
pointer:
";
элементу данных
x
элемент-функцию pnnt
}
to x and print using the object's name:
Assign 8 to x and print using a reference: 8
Assign 10 to x and print using a pointer: 10
Assign 7
Рис. 16.4. Доступ
к элементам данных и
ссылку
и
элементам-функциям объекта через
имя
объекта,
указатель на объект
Однако в реальности дело обстоит
показаться. В заголовочных файлах все же
зации и скрытых намеков на нее.
7
не
так
хорошо,
как
могло
бы
содержится некоторая часть реали¬
Например, встроенные элементы-функции
файле, чтобы при компиляции кода кли¬
обязаны находиться в заголовочном
ента компилятор мог включать в него в нужных местах определение встроен¬
ной функции. Закрытые элементы класса перечислены в его объявлении в за¬
файле, поэтому эти элементы видимы для клиентов, даже если
клиенты могут и не иметь к ним доступа.
головочном
Классы
и
абстракция
Общее
679
данных
методическое замечание 16.9
Информация, являющаяся важной для интерфейса класса, должна включаться в заго¬
файл. Информация, которая будет использоваться только внутри класса и
не потребуется клиентам, должна находиться в неопубликованном исходном файле
Это еще один пример принципа минимума привилегий
ловочный
На рис. 16.5 показана программа из рис. 16.3, разбитая на несколько фай¬
При построении программы на С++ каждое определение класса обычно по¬
мещается в заголовочный файл, а определения элементов-функций этого класса
помещаются в файлы исходного кода с тем же базовым именем. Заголовочные
файлы включаются (посредством #include) во все файлы, использующие этот
класс, а исходный файл с определениями элементов-функций компилируется и
компонуется с файлом, содержащим главную программу. См. документацию по
вашему компилятору, чтобы выяснить, каким образом компилировать и компо¬
новать программы, состоящие из нескольких исходных файлов.
лов.
I //
TIMEl.H
//
Объявление
//
Элементы-функции
//
не
Time.
класса
допускает
в
определены
повторных
TIMEl.CPP
включений
заголовочного
файла
#ifndef
TIMEl_H
#define TIMEl_H
// Определение абстрактного
class
типа
данных
Time
{
Time
public:
Time();
//
void
setTime(int,
int,
void
printMilitary();
конструктор
по
умолчанию
// устанавливает элементы
// hour, minute и second
выводит время в военном формате
выводит время в стандартном формате
int);
//
void printStandard();
private:
23
int hour;
// 0
//
--
int
minute;
//
0
int
second;
//
0
--
--
59
59
};
|
#endif
Рис. 16.5. Заголовочный файл класса Time (часть 1 из 3)
Программа
ром
объявлен
16.5 состоит из заголовочного файла timel.h, в кото¬
Time, файла timel.cpp, в котором определены элемен¬
Time, и файла figl6_5.cpp, в котором определена функция
на рис.
класс
ты-функции класса
main. Выходные данные этой программы идентичны выходным данным про¬
граммы на рис. 16.3.
Обратите
внимание, что объявление класса окружено следующим препро-
цессорным кодом
(см.
// не допускает
#ifndef TIMEl_H
#define TIME1 H
#endif
главу
13):
многократных
включений
заголовочного
файла
Глава 16
680
создании программ большего размера в заголовочные
При
файлы будут
объявления. Предыдущие директи¬
включения кода, находящегося между #ifndef
также помещены и другие определения и
вы препроцессора не допускают
и
#endif,
если
включен в
включение
было определено
TIMEl_H. Если
имя
заголовок ранее не был
директива #define определяет имя TIMEl_H и происходит
операторов заголовочного файла. Если заголовок был включен ра¬
файл,
нее, то имя TIMEl_H уже определено и заголовочный файл повторно не вклю¬
чается. Замечание: по соглашению, которое мы используем для имен символи¬
ческих констант в директивах препроцессора, имя такой константы представ¬
ляет собой просто имя заголовочного файла с символом подчеркивания вместо
точки.
I
// TIMEl.CPP
// Определения элементов-функций
#include <iostream.h>
#include "timel.h"
I
//
I
I
// Гарантирует, что все объекты
// в непротиворечивом состоянии.
Конструктор
Time::Time()
{
Time
minute
=
Time
//
I
// Проводит проверку правильности
I
I
//
//
новое
Устанавливает
значение
недопустимые
элемент
изначально
second
=
I
Устанавливает
каждый
инициализирует
hour
Time.
класса
0;
=
нулем.
}
Time
времени
данных
находятся
в
военном
формате.
данных.
в
значения
нуль
(непротиворечивое состояние).
void
int
Time::setTime(
int
h,
int
m,
s)
{
hour
=
(h
minute
=
second
=
h
<
24
?
h
:
>=
0
&&
m
<
60
)
?
m
:
0;
>=
0
&&
s
<
60
)
?
s
:
0;
>=
(m
(s
0
&&
)
0;
}
// Выводит Time в военном формате
void Time::pnntMilitary()
{
cout « (hour < 10 ? "0м :
)
""
<<
(mmute
<
10
?
"0"
:
<<
(srcond
<
10
?
"0м
:
""
""
«
)
)
«
hour
":"
<<
":"
hour
%
<<
minute
<<
second;
}
//
Выводит
время
в
формате
стандартном
void Time::pnntStandard()
{
0 || hour
cout << ((hour
==
==
<<
":"
<<
«
":"
«
<<
(hour
(minute
(second
<
12
?
"
<
10
<
10
AM"
?
?
"
:
12
)
"0"
:
"0"
:
?
""
""
12
:
12)
)
<<
minute
)
«
second
PM");
}
Рис. 16.5. Исходный файл определений элементов-функций
Хороший
стиль
класса Time
повторного
из
3)
программирования 16.3
Используйте директивы препроцессора #ifndef, #define
тить
(часть 2
включения
в
программу
заголовочных
и #endif, чтобы
файлов.
не допус¬
Классы
абстракция
и
681
данных
//
FIG16_5.CPP
// Программа-тестер
//
ЗАМЕЧАНИЕ:
#include
<iostream.h>
#include
"timel.h"
// Программа
main ()
класса
для
Компилировать
для
с
Timel
TIMEl.CPP
тестирования
Time
класса
простого
{
Time
t;
//
cout
<<
"The
создает
экземпляр
initial
military
t.printMilitary();
cout << "\nThe initial
объекта
t
time
";
is
standard time
класса
Time
";
is
t. pnntStandard () ;
t.setTime(13, 27, 6);
cout << "\n\nMilitary
t.pnntMilitary() ;
cout << "\nStandard
time
time
after
after
setTime
setTime
is
is
";
";
t.pnntStandard() ;
// присваивание недопустимых
t.setTime(99, 99, 99);
cout << "\n\nAfter attempting invalid settings :\n"
<< "Military time: ";
t.printMilitary();
cout << "\nStandard
time:
значений
";
t. pnndStandard () ;
cout << endl;
return 0;
The
The
initial military time
initial standard time
is
00:00:00
is
12:00:00 AM
Military time after setTime is
Standard time after setTime is
13:27:06
1:27:06
PM
After attempting invalid settings:
Military time: 00:00:00
Standard time:
12:00:00 AM
Рис. 16.5. Программа-тестер для класса Time (часть 3 из 3)
Хороший
стиль программирования 16.4
Используйте
имя заголовочного
препроцессора #ifndef
16.8.
и
файла
#define
с подчеркиванием вместо точки в
заголовочных
Управление доступом
Метки public:
и
private: (и protected:,
директивах
файлов
к элементам класса
как мы увидим в главе
19, «Насле¬
для управления доступом к элементам данных и эле¬
дование») используются
ментам-функциям класса.
ется
private:,
Режимом доступа по умолчанию для классов явля¬
поэтому все элементы после заголовка класса и до
первой
метки
Глава 16
682
являются закрытыми. После каждой метки задаваемый ею режим
вплоть до
следующей
действует
завершающей правой фигурной скобки (})
public:, private: и protected: могут повторяться,
метки или до
определения класса. Метки
однако такое их использование нечасто и может приводить к путанице.
К закрытым элементам класса могут иметь доступ только элементы
друзья)
этого класса.
К открытым
бой функции программы.
Основным назначением
ление клиентам
элементам класса можно
открытых
информации
элементов
обращаться
класса является
(и
из лю¬
предостав¬
услугах. Этот набор услуг
Клиенты класса не должны заботить¬
о доступных в классе
образует открытый интерфейс класса.
ся о способе выполнения классом своих задач.
и
определения
его
открытых
Закрытые элементы класса, как
элементов-функций, недоступны для клиентов
класса. Эти компоненты составляют реализацию класса.
Общее методическое замечание 16.10
С++ поощряет независимость программ от реализации Когда реализация класса, ко¬
торый используется в независимом от реализации коде, изменяется, этот код не нуж¬
дается в модификации, однако может потребоваться его перекомпиляция
Распространенная ошибка программирования 16.4
Попытка функции,
не
являющейся
элементом данного класса
(или
его
другом),
полу¬
чить доступ к его закрытому элементу
Рис. 16.6 демонстрирует, что закрытые элементыкласса доступны только
открытый интерфейс этого класса посредством вызова открытых элементов-функций. Во время компиляции этой программы компилятор генерирует
две ошибки, сообщая, что закрытые элементы, на которые ссылаются операто¬
ры, является недоступным. Программа на рис. 16.6 включает timel.h и ком¬
пилируется с файлом timel.cpp, приведенным на рис. 16.5.
через
Хороший
Если
вы
стиль программирования 16.5
хотите сначала
перечислить закрытые элементы
используйте метку private:, несмотря
объявлении класса,
в
на то, что тип доступа
чанию Это делает программу более понятной
лять элементы класса с типом доступа public:.
private: принят
явно
по
умол¬
Мы предпочитаем сначала перечис¬
Хороший стиль программирования 16.6
Несмотря на то, что метки public: и
числите сначала в одной группе все
private: могут повторяться
другой группе все его закрытые элементы Это акцентирует
открытом интерфейсе класса, а не на его реализации
Общее
и
чередоваться, пере¬
открытые элементы класса, а затем перечислите в
внимание пользователя на
методическое замечание 16.11
Оставляйте все элементы данных класса закрытыми Предусмотрите публичные эле¬
менты-функции для установки и получения значений закрытых элементов данных Та¬
кая архитектура помогает скрывать реализацию класса от его клиентов, что уменьша¬
ет количество ошибок и
способствует модифицируемости
программы
Классы и
абстракция
683
данных
//
FIG16_6.CPP
// Демонстрирует ошибки,
//
получить
доступ
к
#include
<iostream.h>
#include
"timel.h"
в
возникающие
закрытым
попыток
результате
элементам
класса.
mam ()
{
Time
t;
Ошибка:
//
t.hour
Ошибка:
//
<<
cout
return
элемент
'Time::hour'
недоступен
7;
=
элемент
'Time::minute'
"
=
"minute
<<
недоступен
t.minute;
0;
Compiling FIG16__6.CPP:
Error FIG16__6.CPP: 12:
'Time::hour'
Error FIG16__6.CPP:
'Time::minute'
15:
is not accessible
is not accessible
Рис. 16.6. Вызвавшая ошибку попытка обращения
Клиентом
класса может
быть
как
к закрытым элементам класса
функция-элемент другого
класса, так и
глобальная
функция.
Доступом по умолчанию
элементам
класса может
явно сделан защищенным или открытым.
ступом по умолчанию для элементов структур и
Доступ
к элементам структуры может
(или, как мы
каторы доступа к
тым
Общее
увидим в главе
быть
явно сделан открытым или закры¬
19, защищенным). С объединением специфи¬
элементам явно использоваться не могут.
методическое замечание 16.12
Проектировщики
для
private. Доступ к
До¬
объединений является public.
для элементов класса является
быть
реализации
Обратите
классов
используют закрытые,
принципов
сокрытия
защищенные
информации
и
и
открытые
минимальных
элементы
привилегий
внимание, что элементы класса являются закрытыми по умолча¬
нию, поэтому всегда можно обойтись без явного использования
спецификато¬
private:. Однако многие программисты предпочитают
сначала перечислять интерфейс класса (т.е. открытые элементы класса); затем
перечисляются закрытые элементы класса и, таким образом, возникает по¬
требность в явном использовании спецификатора доступа private: в определе¬
ра доступа
к элементам
нии класса.
Хороший
стиль программирования 16.7
Использование каждого из
private:
То,
только
один
раз
спецификаторов доступа
в
определении
класса
к элементам
помогает
что данные класса являются закрытыми, не
клиенты не могут производить
изменений
public:, protected:
и
избежать путаницы
обязательно означает,
в этих данных.
Данные
что
могут быть
684
Глава 16
изменены элементами-функциями или друзьями класса. Как мы увидим, та¬
кие функции должны разрабатываться так, чтобы гарантировать целостность
данных.
Доступ к закрытым данным класса может строго контролироваться путем
использования элементов-функций, называемых функциями доступа. Напри¬
мер, для того, чтобы разрешить клиентам считывать значение закрытого эле¬
мента данных, класс может предусматривать так называемую
«ёеЬ>-функцию.
Чтобы
давать возможность клиентам изменять закрытые данные, класс может
иметь так называемую «8^»-функцию. Может показаться, что такого рода из¬
менение
данных
set-функция
нарушает
представление
о
закрытых
Однако
(например,
данных.
может иметь средства проверки допустимости данных
проверку диапазона), чтобы гарантировать правильную установку соответст¬
вующего значения. Get-функция не обязана выдавать данные в «сыром» виде;
напротив, get-функция может редактировать данные и ограничивать пред¬
ставление данных, которые
Общее
будет
видеть клиент.
методическое замечание 16.13
Задание закрытого доступа для элементов данных класса и открытого доступа для его
элементов-функций облегчает отладку, поскольку проблемы,
рованием данными,
ограничиваются
связанные с манипули¬
либо элементами-функциями класса, либо
его
друзьями
16.9.
Не
стью
все
Функции
и
сервисные
интерфейса
функции
общедосФупными, т.е. ча¬
Некоторые элементы-функции остаются закрытыми
вспомогательных для других функций класса.
элементы-функции целесообразно
делать
класса.
и служат в качестве
Общее
доступа
методическое замечание 16.14
Элементы-функции имеют тенденцию подразделяться на ряд различных категорий
функции, которые считывают и возвращают значения закрытых элементов данных,
функции, которые устанавливают значения закрытых элементов данных, функции, ко¬
торых реализуют характерные особенности класса, и функции, которые выполняют
разнообразную
механическую
класса, присваивание
пами данных
объектов
или
работу
для класса, такую, как инициализация объектов
объектов, преобразование между
между классами
и другими
классами и встроенными ти¬
классами,
и
управление памятью для
класса
Функции доступа могут считывать или отображать данные. Другим рас¬
пространенным применением функций доступа является проверка истинности
либо ложности условий
подобные функции часто называют предикатными
функциями. Примером предикатной функции может служить функция isEmдля любого контейнерного класса, такого, как связанный список, стек или
очередь. Программа обычно проверяет условие isEmpty перед попыткой счи¬
тывания еще одного элемента из контейнерного объекта. Предикатная функ¬
ция isFull может проверять объект контейнерного класса, чтобы установить,
pty
что в нем отсутствует дополнительное пространство.
На рис.
функция
16.7
демонстрируется
не является частью
понятие
интерфейса
сервисной функции. Сервисная
класса.
Это закрытая функция-эле-
Классы и
мент,
абстракция
которая
открытых
класса
класса.
SalesPerson
Элементы-функции определены
#ifndef
SALESP_H
#define
SALESP_H
class
функций-элементов
не предназначены для использования клиентами класса.
// SALESP.H
// Определение
//
работу
поддерживает
Сервисные функции
I
I
685
данных
SalesPerson
в
SALESP.CPP
{
public:
SalesPerson();
void setSales();
//
конструктор
пользователь
//
задает
показатели
сбыта
показателей
сбыта
void printAnnualSales();
private:
double
sales
[13];
totalAnnualSales();
double
//
12
//
сервисная функция
ежемесячных
};
#endif
//
//
SALESP.CPP
Элементы-функции для
#include
<iostream.h>
#include
<iomanip.h>
#include
','salesp.h"
//
класса
Конструктор инициализирует
SalesPerson::SalesPerson()
{
for (mt i
0; i <= 12;
0.0;
sales[i]
}
i+
=
SalesPerson
массив
+)
=
//
Функция для установки 12
SalesPerson::setSales()
ежемесячных
показателей
сбыта
void
{
for
(mt
cout
cin
1;
=
i
i
<=
12;
<<
"Enter
sales
<<
i
<<
;
>>
sales
":"
i+
{
for
month
"
[i];
// Закрытая сервисная функция
double
+)
amount
для
вычисления
годового
SalesPerson::totalAnnualSales()
{
double
for
total
(int
total
return
i
+=
=
1;
=
0.0;
i
<=
12;
i++)
sales[i];
total;
Рис. 16.7. Использование
сервисной функции (часть
1 из 2)
сбыта
686
Глава 16
// Выводит итоговый годовой сбыт
void SalesPerson: :pnntAnnualSales ()
{
cout << setprecision (2)
I
{ios::fixed
<<
setiosflags
<<
"\nThe
<<
totalAnnualSales()
total
annual
ios::showpoint)
sales are:
<< endl;
$"
}
//
//
FIG16_7.CPP
Демонстрация
сервисной функции
//
Компилируется с
#include "salesp.h"
main
SALESP.CPP
()
{
SalesPerson
s;
//
создает
объект
s
класса
SalesPerson
s.setSales();
s
.
prmtAnnualSales ()
return
;
0;
Enter
sales
amount
for
month
1
5314.76
Enter
sales
amount
for
month
2
4292.38
Enter
sales
amount
for
month
3
4589.83
Enter
sales
amount
for
month
4
5534.03
Enter
sales
amount
for
month
5
4376.34
Enter
sales
amount
for
month
6
5698.45
Enter
sales
amount
for
month
7
4439.22
Enter
sales
amount
for
month
8
5893.57
Enter
sales
amount
for
month
9
4909.67
Enter
sales
amount
for
month
10:
5123.45
Enter
sales
amount
for
month
11:
4024.97
Enter
sales
amount
for
month
12:
5923.92
The
total
annual
sales
are:
$60120.59
Рис. 16.7. Использование сервисной функции (часть 2
Класс SalesPerson содержит
массив из
12
из
ежемесячных
2)
показателей сбы¬
конструкторе; функция setSales присваива¬
ет показателям сбыта задаваемые пользователем значения. Открытая функ¬
та, инициализируемых нулями
в
printAnnualSales выводит итоговый сбыт за последние 12 меся¬
Сервисная функция totalAnnualSales суммирует 12 ежемесячных пока¬
зателей сбыта для функции printAnnualSales. Элемент-функция printAnnu¬
alSales преобразует показатели сбыта в долларовый формат. Каждая из испо¬
ция-элемент
цев.
льзуемых
здесь
объясняется
возможностей форматирования
21. Сейчас мы дадим только
в главе
С++ более подробно
краткое пояснение. Вызов
языка
setprecision (2)
указывает,
десятичной точки должны выводиться два знака «точ¬
требуется для долларовых сумм вроде 23.47. Этот вызов на¬
«параметризованным манипулятором потока». Программа,
что справа от
ности», как нам и
зывается
Классы и
абстракция
687
данных
применяющая такого рода вызовы, должна также содержать директиву пре¬
процессора
#include
<iomanip.h>
Строка
setiosflags(ios::fixed
отображение
|
ios::showpoint);
сбыта
в так называемом формате с фиксирован¬
ной точкой (в противоположность экспоненциальному формату). Опция showpoint принудительно выводит десятичную точку и нули в конце числа, даже
если последнее является целой долларовой суммой, как в случае 47.00. В С++
такое число было бы выведено просто как 47, если бы опция showpoint не была
задает
показателя
установлена.
16.10. Инициализация объектов
После создания объектов
мощью
конструкторов.
рый
их элементы могут
Конструктор
класса с тем же именем, что
и класс.
затем автоматически вызывается
класса.
Элементы данных
класса:
не
могут
конструкторы
быть инициализированы
собой
представляет
Программист
с по¬
функцию-элемент
пишет конструктор,
кото¬
всякий раз, когда создается объект этого
быть инициализированы в определении
Элементы данных либо должны инициализироваться
в конструкторе
их
либо
значения
быть
после
класса,
создания объек¬
могут
установлены позже,
та. Конструктор не может ни специфицировать тип возвращаемого значения,
класса.
ни возвращать какое-либо значение. Конструкторы могут быть перегружены,
чтобы предусмотреть различные способы инициализации объектов класса.
Распространенная ошибка программирования 16.5
Попытка явной инициализации элемента данных в определении класса
Распространенная ошибка программирования 16.6
Попытка объявить для конструктора тип возвращаемого значения и/или попытка воз¬
вратить значение из конструктора
Хороший
стиль программирования 16.8
это целесообразно (почти всегда), определяйте конструктор, чтобы гарантиро¬
вать надлежащую инициализацию каждого объекта осмысленными значениями
Когда
Хороший
стиль программирования 16.9
Любая функция-элемент (или дружественная
объекта, должна
(действительном) состоянии
менты данных
При объявлении объекта
изменяющая закрытые эле¬
что данные остаются в целостном
функция),
гарантировать,
класса справа от его имени и до точки с
запятой
в
круглых скобках могут быть заданы инициализаторы. Эти инициализаторы
передаются в качестве аргументов в конструктор класса. Скоро мы рассмот¬
рим несколько примеров таких вызовов
конструктора.
Глава 16
688
16.11. Использование
с
по
Конструктор
nute
мате
конструкторами аргументов
умолчанию
timel.cpp (рис. 16.5) инициализировал hour, mi¬
(т.е. 12 часами полуночи в военном фор¬
времени). Конструкторы могут содержать аргументы по умолчанию. На
и
из листинга
second нулевыми
значениями
рис. 16.8 конструктор класса Time переопределяется так, чтобы он включал
для каждой переменной аргумент с нулевым значением по умолчанию. Зада¬
вая для конструктора аргумент по умолчанию, мы гарантируем, что благодаря
объект будет находится в корректном состоянии, даже если
не будет задано никаких значений при вызове конструктора. Предусмотрен¬
ный программистом конструктор, все аргументы которого имеют значения по
умолчанию, называется также конструктором по умолчанию (он может вызы¬
этим аргументам
ваться
без аргументов). Конструктор использует
того же рода код проверки до¬
функция setTime, чтобы гарантировать, что
элемента hour, находится в диапазоне от 0 до 23 и что
пустимости данных, что и
значе¬
ние, заданное для
значе¬
ния для каждого из элементов minute и second находятся в диапазоне от 0
до 59. Если значение попадает за пределы диапазона, оно устанавливается в
(это
Конструктор
нуль
пример обеспечения целостности состояния данных
мог
бы непосредственно
вызывать
setTime,
бы к накладным расходам, связанным с дополнительным вызовом
программе
на рис.
16.8 инициализируются
пять
объектов
объекта).
однако это привело
класса
функции. В
Time
один
со всеми аргументами, принимаемыми по умолчанию при вызове конструкто¬
ра, один с одним указанным аргументом, один с двумя указанными аргумен¬
тами, один с тремя указанными аргументами и один с тремя недопустимыми
значениями аргументов.
Содержание элементов данных каждого
отображается на экране.
объекта
по¬
сле его создания и инициализации
I
// TIME2.H
// Объявление
класса
Time.
//
Элементы-функции определены
//
не
допускает
повторных
в
TIME2.CPP
включений
заголовочного
файла
#ifndef TIME2_H
#define TIME2_H
class Time
{
public:
Time(mt
void
=
0;
mt
setTime(mt,
=
0;
int,
int
mt)
=
0);
//
конструктор
по
умолчанию
;
printMilitary();
void pnntStandard();
private:
void
int
hour;
mt
minute;
mt
second;
} ;
#endif
Рис. 16.8. Использование конструктора с аргументами по умолчанию
(часть 1
из
3)
Классы и
//
абстракция
689
данных
TIME2.CPP
<iostream.h>
#include
//
//
Time.
класса
Определения элементов-функций для
#include "time2.h"
//
инициализации
для
Конструктор
Значения по
Time::Time(int
данных.
закрытых
(см.
умолчанию равны 0
hr, int min, int sec)
определение
класса)
{
hour
min
&&
min
<
60
=
(
sec
>=
&&
sec
<
60
(
second
//
<
(
0 &&
>= 0
hr
=
=
minute
>=
hr
Устанавливает
0
значения
24
hr
?
)
)
)
:
0;
? min
? sec
mmute
hour,
и
:
0;
:
0;
second.
0.
//
Недопустимые знасения устанавливаются
void Time::setTime(mt
h, int m, mt s)
{
hour
( h >= 0 && h < 24) ? h : 0;
mmute
( m >= 0 && m < 60) ? m : 0;
second
( s >= 0 && s < 60) ? s : 0;
=
=
=
//
Отображает
в
время
HH:MM:SS
формате:
военном
void Time::printMilitary()
cout
<<
<<
<<
//
(hour <
(minute
(second
10
?
"0"
10
?
"0м
<
10
?
"0м
Отображает время
в
"")
:
<
стандартном
void Time::prmtStandard()
{
0
cout << ((hour
|| hour
==
==
<<
":"
<<
<<
":"
<<
«
(hour
(mmute
(second
<
12
?
м
<
10
<
10
AM"
// FIG16_8.CPP
// Демонстрация конструктора
// для класса Time.
#include
<iosrteam.h>
#include
"time2.h"
?
?
:
по
<<
"")
:
"
hour
<<
":"
<<
minute
<<
second;
формате:
?
12)
12
"0"
:
"")
"0"
:
"")
<<
":"
HH:MM:SS AM
:
hour
(или PM)
12)
%
<< minute
<< second
PM");
умолчанию
mam ()
Time
t5
cout
tl, t2(2), t3(21,
(27, 74, 99);
34),
t4(12,
with:\n"
arguments defaulted:
tl.printMilitary();
<<
<<
25,
42),
"Consrtucted
"all
Рис. 16.8. Использование конструктора
\n
с параметрами по умолчанию
(часть 2
из
3)
Глава 16
690
<< "\n
";
tl. prmtStandard () ;
cout
cout
<<
"\nhour
specified;
minute
and
second defaulted:\n
specified;
second defaulted:\n
t2.printMilitary();
cout << "\n
";
t2.printStandard();
cout
<<
and mmute
"\nhour
t3.printMilitary();
cout << "\n
";
t3.printStandard();
cout
<<
"\nhour,
minute
and
second
specified:\n
";
t4.printMilitary();
cout << "\n
";
t4.printStandard();
cout
<<
"\nall
invalid values
specified:\n
";
t5.prmtMilitary() ;
cout << "\n
";
t5.prmtStandard() ;
cout << endl;
return
0;
Constructed with:
all
arguments defaulted:
00:00:00
12:00:00 AM
hour
specified;
02:00:00
minute and second defaulted:
2:00:00 AM
hour and minute
specified;
second defaulted:
21:34:00
9:34:00
PM
hour, minute and second specified:
12:25:42
12:25:42
all
PM
invalid values
specified:
00:00:00
12:00:00 AM
Рис. 16.8. Использование конструктора с параметрами по умолчанию (часть 3 из 3)
Если для класса не определено ни одного конструктора, компилятор со¬
здает конструктор по умолчанию. Такой конструктор не выполняет никакой
инициализации, поэтому после создания объекта не гарантируется его коррек¬
тное состояние.
Хороший
стиль
программирования 16.10
Всегда предусматривайте конструктор, который выполняет соответствующую инициа¬
лизацию объектов своего класса.
Классы и
абстракция
691
данных
16.12.
Деструктор
Деструкторы
это специальная
состоит из символа тильды
(~),
функция-элемент
ние о наименовании не противоречит интуиции,
является
класса.
Имя деструктора
за которым следует имя класса. Такое соглаше¬
поразрядной операцией
поскольку операция тильды
дополнения, а в каком-то смысле деструктор
является дополнением конструктора.
Деструктор
класса вызывается автоматически, когда
дит из
области действия. Сам деструктор фактически
скорее
он выполняет заключительную
возвращена выделенная
Деструктор
может
быть
объекту
объект
класса выхо¬
не разрушает
объекта,
будет
прежде чем системе
«приборку»,
память.
не принимает параметров и не возвращает значения.
только один деструктор
У
класса
перегрузка деструкторов не допускает¬
ся.
Распространенная ошибка программирования 16.7
Попытка передать параметры
перегрузить деструктор
в
значение
деструктор, возвратить
из
деструктора или
Обратите внимание, что мы не определяли деструкторов для представлен¬
ных до сих пор классов. На самом деле деструкторы редко используются с про¬
стыми классами. В главе 18 мы увидим, что применение деструкторов целесо¬
образно для классов, объекты которых содержат динамически выделенную па¬
мять
(например,
для массивов и
16.13.
Когда
вызываются
и
Обычно конструкторы
обращения
объекты входят
Как правило,
вызовы
в
вызываются
к этим
автоматически.
функциям,
область действия
деструкторов
рядку вызова конструкторов.
конструкторы
деструкторы
и деструкторы
док, в котором происходят
рядка, в каком
строк).
Поря¬
зависит от того по¬
и выходят из нее.
происходят
в
порядке,
Однако период хранения объектов
обратном
по¬
может влиять
на порядок вызова деструкторов.
Для объектов, объявленных
ры
в
глобальной области действия, конструкто¬
вызываются в начале выполнения программы.
Соответствующие
деструк¬
торы вызываются при ее завершении.
Для автоматических локальных объектов конструкторы вызываются при
объявлении. Соответствующие деструкторы вызываются, когда объекты
выходят из области действия (т.е. происходит выход из блока, в котором они
их
объявлены). Обратите
внимание, что конструкторы и деструкторы для автома¬
тических локальных объектов могут вызываться многократно по мере того,
как объекты входят в область действия и выходят из нее.
Для
при
объектов конструкторы вызываются один раз
объектов. Соответствующие деструкторы вызываются
статических локальных
объявлении
при завершении
этих
работы
программы.
692
Глава 16
Программа
конструкторы
на
и
16.9 демонстрирует порядок,
рис.
деструкторы для объектов
щихся в различных областях
типа
программы,
а
котором вызываются
CreateAndDestroy,
находя¬
действия. Программа объявляет объект first
глобальной области действия. Его конструктор
ния
в
при завершении
деструктор
работы
программы после
разрушения всех других объектов.
//
//
//
CREATE.H
класса
CreateAndDestroy.
Элементы-функции определены в CREATE.CPP.
Определение
#ifndef
CREATE_H
#define
CREATE_H
class
CreateAndDestroy
{
public:
CreateAndDistroy(int);
//
конструктор
~CreateAndDestroy();
//
деструктор
private:
int data;
} ;
#endif
//
CREATE.CPP
//
Определения
элементов-функций для
CreateAndDestroy
класса
#include <iostream.h>
#include "create.h"
CreateAndDestroy::CreateAndPestroy(int value)
{
data
value;
=
cout
<<
"
"Object
<<
data
"
<<
constructor";
}
CreateAndDestroy::~CreateAndDestroy()
<<
<<
data
{ cout << "Object
"
//
//
FIG16_9.CPP
Демонстрация
//
конструкторы
порядка,
и
#include
<iostream.h>
"create.h"
create(void);
CreateAndDestroy
mam
в
котором
destructor
"
<<
endl;
}
вызываются
деструкторы.
#include
void
"
//
прототип
first(l);
//
глобальный
объект
()
{
cout
<<
"
(global created before main)
cout
<<
"
(local automatic
Рис. 16.9. Демонстрация порядка,
\n";
// локальный объект
CreateAndDestroy second(2);
m
main)
\n";
в котором вызываются конструкторы и деструкторы
(часть 1
из
2)
в
вызывается в начале выполне¬
Классы и
абстракция
static
<<
cout
693
данных
CreateAndDestroy
"
create();
(local
//
static
third(3);
//
in mam)
\n";
объект
создающую объекты
функцию,
вызывает
локальный
// локальный объект
CreateAndDestroy fourth(4);
cout << "
(local automatic in main) \n";
return 0;
// Функция, создающая объекты
void create(void)
{
CreateAndDestroy fifth(5);
cout <<
(local automatic
"
in create)
static CreateAndDestroy sixth(6);
cout <<
(local static m create)
"
CreateAndDestroy seventh(7);
cout <<
(local automatic in
"
\n";
\n";
create
)
\n";
}
Рис. 16.9. Демонстрация порядка,
в котором вызываются конструкторы и деструкторы
(часть 2
из
3)
Object 1
constructor
(global
2
constructor
(local
Object
3
constructor
(local
static
Object
5
constructor
(local
automatic
Object
6
constructor
(local
static
Object
7
constructor
(local
automatic
create)
in create)
Object
7
destructor
Object
5
destructor
Object
4
constructor
(local
automatic
in main)
Object
4
destructor
Object
2
destructor
Object
6
destructor
Object
3
destructor
Object
1
destructor
Object
created before main)
in main)
.
automatic
in main)
in create)
in
Рис. 16.9. Демонстрация порядка, в котором вызываются конструкторы и деструкторы
(часть 2 из 2)
В
ются
функции main объявлены три объекта. Объекты second и fourth явля¬
автоматическими объектами, а объект third статическим
локальными
объектом. Конструкторы для каждого из этих объектов вызывают¬
объявлении каждого из них. Деструкторы для объектов fourth и second
локальным
ся при
(в таком порядке) при достижении конца функции main. Поско¬
льку объект third является статическим, он существует с момента своего объ¬
явления до завершения работы программы. Деструктор для объекта third вы¬
вызываются
зывается перед деструктором для объекта
гих
first,
но после разрушения всех дру¬
объектов.
В функции create объявлены три объекта. Объекты fifth и seventh явля¬
автоматическими локальными объектами, а объект sixth статическим
ются
локальным
объектом. Деструкторы для объектов seventh
и
fifth
вызываются
Глава 16
694
таком порядке) при достижении конца функции create. Поскольку объект
sixth является статическим, он существует с момента своего объявления до за¬
(в
работы
вершения
программы.
Деструктор для
first, но
деструкторами для объектов third и
объекта sixth вызывается перед
после разрушения
всех осталь¬
объектов.
ных
16.14. Использование
и
Закрытыми
тировка
элементов-функций
элементами
ты-функции (и друзья)
данных
могут
манипулировать
элемен-
только
класса. Типичным примером может служить коррек¬
банковского баланса
BankAccount)
элементов данных
клиента
(т.е.
закрытого элемента данных класса
элемента-функции computeInterest.
Классы часто предусматривают открытые элементы-функции, чтобы дать
возможность клиентам класса устанавливать (т. e. записывать) или получать
(т.е. считывать) значения закрытых элементов данных. Эти функции не обяза¬
ны называться именно «set»- (установить) и «get»- (получить) функциями, но
с помощью
часто их называют именно так.
Более конкретно, элемент-функция, устанавли¬
данных interestRate, обычно называется setInterestRate, а
элемент-функция, которая получает значение interestRate, обычно называется
getInterestRate. Get-функции часто называют также «функциями опроса».
Может показаться, что предоставить клиенту возможность как установки,
вающая
элемент
так и получения значений элементов данных
это, по сути, то же самое, что
сделать эти элементы открытыми. В этом состоит еще одна тонкость языка
С++, которая
делает его столь привлекательным для разработчиков програм¬
много обеспечения. Если элемент данных является открытым, то любая функ¬
ция в программе может произвольно считывать этот элемент данных или запи¬
информацию. Если элемент данных является закрытым, то, ко¬
общедоступная «ge^-функция дает возможность
другим функциям произвольно считывать данные, однако эта get-функция мо¬
жет контролировать форматирование и отображение данных. Общедоступная
сывать
в него
нечно, может показаться, что
set-функция может
вать любую попытку
и, по всей вероятности, будет
тщательно контролиро¬
изменения значения элемента данных. Это гарантирует,
что новое значение элемента данных
будет
соответствовать его смыслу.
Напри¬
37,
попытка
присвоить весу человека отрицательное значение, попытка присвоить
числовой
мер,
будут отвергнуты
попытка присвоить дню месяца значение
величине литерное значение, попытка присвоить
экзаменационной
чение 185
100)
(при
возможном диапазоне от нуля до
Общее методическое
Закрытие
оценке зна¬
и т.д.
замечание 16.15
элементов данных и контроль доступа
(особенно доступа
для
записи)
к этим
посредством открытых элементов-функций помогает гарантировать цело¬
стность данных
элементам
Выгоды, связанные с целостностью данных, не реализуются автоматиче¬
ски просто потому, что элементы данных сделаны закрытыми,
программист
должен обеспечить проверку допустимости данных. Однако С++ предоставля¬
ет программистам некую основу, создающую удобства при проектировании бо¬
лее качественных программ.
Классы и
абстракция
Хороший
695
данных
стиль программирования 16.11
Элементы-функции, присваивающие
корректность
вновь
вводимых
значения закрытым данным, должны проверять
значений;
должны приводить закрытые элементы
стность данных
случае их некорректности set-функции
гарантирующее цело¬
в
в некоторое состояние,
В программе на рис. 16.10 производится расширение нашего класса Time
путем включения get- и set-функций для закрытых элементов данных hour,
minute
и
second. Set-функции строго контролируют установку
элементов дан¬
Попытки присвоить любому элементу данных некорректное значение
вызывают установку этого элемента данных в нуль (оставляя, таким образом,
ных.
просто возвращает зна¬
элемента данных. Сначала программа использует
set-функции для присваивания закрытым элементам данных объекта t класса
Time допустимых значений, затем она использует get-функции для извлече¬
данные в целостном
чение
состоянии). Каждая get-функция
соответствующего
ния значений для их вывода. После этого set-функции пытаются присвоить
элементам hour и second недопустимые значения, а элементу minute допусти¬
get-функции возвращают значения для вывода. Вы¬
ходные данные программы подтверждают, что недопустимые значения приво¬
дят к присваиванию элементам данных нулевых значений. И наконец, про¬
мое значение, после чего
в
грамма устанавливает время
путем
вызова
11:58:00
и увеличивает на
3
значение
minute
функции incrementMinutes. Функция incrementMinutes
не яв¬
ляется элементом класса и использует для увеличения элементов данных эле¬
менты-функции get...
производительности,
и
set.... Этот метод
связанному
с
работает,
но приводит к снижению
неоднократными
следующей главе мы обсудим понятие дружественных
ного из способов устранения подобных издержек.
Наличие
гибкость
в классе
вызовами
функций
функций.
set-функций представляет программисту определенную
в написании
конструкторов.
I
// TIME3.H
I // Объявление
класса
I // Элементы-функции
I
// не допускает
#ifndef TIME3_H
#define
class
TIME3
Time
Time.
в
определены
повторных
TIME3.CPP
включений
заголовочного
файла
H
{
public:
Time(mt
=
0,
int
// set-функции
void setTime(mt,
=
// get-функции
int getHour() ;
int getMmute ()
,
getSecond(),
0,
mt,
void setHour(int);
void setMinute(int);
void setSecond(int);
int
В
в качестве од¬
int
int)
=
0)
//
конструктор
//
//
устанавливает hour,
second
// устанавливает hour
// устанавливает mmute
// устанавливает second
;
// возвращает hour
// возвращает minute
// возвращает second
Рис. 16.10. Объявление
класса Time
(часть 1
из
6)
minute,
696
Глава 16
void prmtMilitary();
//
выводит
время
в
военном
void printStandard();
//
private:
int hour;
23
// 0
выводит
время
в
стандартном
формате
формате
-
mt
mmute;
//
0
int
second;
//
0
59
-
59
-
#endif
Рис. 16.10. Объявление класса Time (часть 2 из 6)
//
TIME3.CPP
//
Определения
#include
"time3.h"
#include
<iostream.h>
//
для
Конструктор
Значения по
//
класса
элементов-функций
инициализации
данных.
закрытых
(см.
умолчанию равны 0
hr, int min, int sec)
Time::Time(int
Time
определение
класса).
{
hour
=
mmute
second
(
=
=
hr
(
(
&&
hr
<
min
>=
0
&&
mm
<
sec
>=
0
&&
sec
<
>=
0
24)
? hr
60)
60)
0;
:
?
?
mm
:
0;
sec
:
0;
}
//
Устанавливает
void
значения
Time::setTime(int h,
hour, mmute
mt m, mt s)
и
second.
{
hour
h
>=
h
<
mmute
=
(
m
>=
0
&&
m
second
=
(
s
>=
0
&&
s
}
//
{
{
{
//
&&
24) ? h
60) ?
<
60) ?
:
:
0;
0;
?
h
:
0;
60)
?
m
:
0;
}
?
s
:
0;
}
return hour;
}
hour
значение
=
значение
=
Получает значение
Time::detHour()
hour
//
Получает
mmute
//
mt
значение
Получает
значение
Time::getSecond()
Рис. 16.10.
{
<
second
Time::setSecond(int s)
second
( s >= 0 && s
Time::getMinute()
}
minute
Time::setMinute(mt m)
mmute
( m >= 0 && m
int
int
0;
s
значение
{
:
m
<
=
Устанавливает
void
0
Time::setHour(int h)
hour
( h >= 0 && h < 24)
Устанавливает
void
//
(
Устанавливает
void
//
=
<
60)
return
minute;
}
second;
}
second
{
return
Определения элементов-функций
класса Time
(часть 3
из
6)
Классы и
//
абстракция
Выводит
void
697
данных
в
время
военном
HH:MM:SS
формате:
Time::prmtMilitary()
{
cout
<<
(
hour
<<
(
minute
<
10
?
"0"
:
"")
<<
minute
<<
(
second
<
10
?
"0"
:
"")
<<
second;
<
10
?
"0"
:
<<
"")
<<
hour
":"
<<
":"
}
//
Выводит
void
в
время
стандартном
AM
HH:MM:SS
формате:
(или
PM)
12)
<<
Time::prmtStandard()
{
cout
<<
((
<<
(
<<
«
hour
0
==
||
minute
<
10
?
(
second
<
10
?
hour
?
"
(
<
12
hour
set-
#include
<iostream.h>
#include
"time3.h"
void
и
:
"")
<<
minute
"0м
:
"")
<<
second
AM"
:
"
&,
hour
%
<<
":"
":"
PM");
класса Time
класса
get-функций
incrementMmutes(Time
12
:
Рис. 16.10. Определения элементов-функций
// FIG16_10.CPP
// Демонстрация
?
12)
==
"0м
const
(часть 4
из
6)
Time
int);
main ()
{
Time
t;
t.setHour (17);
t. setMmute (34) ;
t.setSecond(25);
cout
<<
"Result
<<
t.getHour()
<<
<<
"
"
of
setting
Mmute:
"
Second:
"
<<
<<
all
valid
t.getMinute()
t.getSecond()
values:\n
<<
Hour:
"
"\n\n";
// недопустимое значение hour установлено
t.setHour(234);
t.setMinute(43);
t.setSecond(6373); // недопустимое значение second
// установлено в 0
cout
"Result
<<
"
second:
"
Minute:
"
"
Second:
"
<<
<<
to
attempting
<<
of
\n
Hour:
<<
<<
"
set
invalid
hour
<<
t.getHour()
t.getMinute()
t.getSecond() << "\n\n";
t.setTime(ll, 58, 0);
incrementMinutes(t, 3);
return 0;
void incrementMinutes(Time
{
&tt,
Рис. 16.10. Использование set-
const
и
int count)
get-функций (часть
5 из 6)
and"
в
0
698
Глава 16
cout
"
"Incrementing minute
<<
"
<<
time:
times:\nStart
<<
cout
";
tt.printStandard();
for
(mt
i
=
1;
i
tt.setMmute( (
if
+)
{
tt.getMmute()
+
<=
i +
count;
0)
(tt.getMinute()
tt.setHour ((tt.getHour()
%
1)
60);
==
cout
<<
"\nmmute
+
1:
+
1)
%
24);
";
tt.prmtStandard() ;
}
cout
<<
endl;
Result of
17
Hour:
setting all valid values:
Minute: 34
Second: 25
Result of attempting to
Hour: 0
Minute: 43
set invalid hour and second:
Second:
0
Incrementing minute 3 times:
Start time: 11:58:00 AM
minute +
1:
11:59:00 AM
minute +
1:
12:00:00
PM
minute +
1:
12:01:00
PM
Рис. 16.10. Использование set- и get-функций (часть б из 6)
Распространенная ошибка программирования 16.8.
Конструктор может вызывать другие элементы-функции класса, например, set- или
get-функции, однако, поскольку конструктор инициализирует объект, элементы дан¬
ных могут еще не находиться в целостном состоянии. Использование элементов дан¬
ных до
их
надлежащей инициализации
Set-функции, безусловно,
граммного
важны
с
может
точки
приводить
зрения
к
ошибкам
конструирования
про¬
обеспечения,
корректности данных.
ные в отношении
выполнять
они
поскольку
могут
проверку
Set- и get-функции имеют и другие достоинства, важ¬
конструирования программного обеспечения.
Общее методическое замечание 16.16
и get
не только
посредством функций-элементов set
от присваивания им недопустимых значений, но также
изолирует клиентов класса от представления данных Таким образом, если представ¬
ление данных по некоторым причинам меняется (часто в целях уменьшения количест¬
Доступ
к закрытым данным
защищает элементы данных
ва
требуемой
памяти или повышения
производительности), требуется
-
изменить толь¬
клиенты класса не нуждаются в изменениях до тех пор, пока
элементы-функции
интерфейс, предоставляемый этими элементами-функциями, остается тем же самым
ко
Однако
может
потребоваться
перекомпиляция программ-клиентов
Классы и
абстракция
16.15.
Ссылка
льно,
699
данных
Скрытая ловушка: возвращение
на закрытый элемент данных
объект
на
является псевдонимом для самого
левой
она может стоять в
это
Один
из
заставить
крытый
способов
Другими
сло¬
lualue, способного принимать
зна¬
воспользоваться этим ее
функцию-элемент
открытую
свойством (к сожалению!)
класса возвращать
ссылку
на
за¬
элемент данных этого класса.
На рис. 16.11 используется упрощенная версия
рации возврата
мое
объекта и, следовате¬
части оператора присваивания.
вами, ссылка вполне приемлема в качестве
чение.
ссылки
ссылки на
фактически
значение
закрытый
Time для демонст¬
Такого рода возвращае¬
класса
элемент данных.
функции badSetHour псевдоним
функции можно использо¬
делает из вызова
для закрытого элемента данных hour! Этот вызов
вать
любым
из
способов,
lvalue
числе в качестве
I
TIME4.H
Объявление
возможных для открытого элемента данных,
в операторе
присваивания!
I
//
//
I
// Элементы-функции определены
I //
не
класса
допускает
Time.
повторных
в
TIME4.CPP
включений
файла
заголовочного
#ifndef TIME4_H
#define TIME4_H
class Time
{
public:
Time(mt
0,
0, int
void setTime(int, int,
int getHour();
int &badSetHour(int);
=
=
int
0);
=
int);
ОПАСНОЕ возвращение
//
ссылки
private:
int hour;
int minute;
int
second;
};
#endif
//
TIME4.CPP
//
Определения
элементов-функций
#include
"time4.h"
#include
<iostream.h>
//
Конструктор
//
Вызывает
элемент-функцию
//
Значения
по
Time::Time(int
{
//
setTime
(
для
умолчанию
hr,
int
hr,
min,
Устанавливает
void
инициализации
mm,
(
int
0
int
sec);
значения
Time::setTime
для
(см.
данных.
установки
определение
переменных.
класса).
sec)
}
minute
hour,
h,
закрытых
setTime
равны
Time.
класса
int
m,
int
и
second.
s)
{
hour
=
(h
>=
0
&&
Рис. 16.11. Возвращение
h
<
24)
ссылки на
?
h
:
0;
закрытый
элемент данных
(часть 1
из
2)
в том
700
Глава 16
minute
=
(
m
>=
0
&&
m
<
second
=
(
s
>=
0
&&
s
<
6Cf)
60)
?
?
m
:
s
:
hour;
}
0;
0;
}
//
Получает
//
ПЛОХОЙ
//
Возвращение
int
СТИЛЬ
=
//
return
ПРОГРАММИРОВАНИЯ:
ссылки
>=
hh
(
return
//
{
на
закрытый
&Time::batSetHour(mt
hour
}
//
//
hour
значение
Time::getHour()
int
0
//
hour;
&&
элемент
данных,
hh)
hh
24)
<
? hh
0;
:
ОПАСНОЕ возвращение
ссылки
FIG16_11.CPP
Демонстрация открытой функции-элемента, которая
возвращает ссылку на закрытый элемент данных.
Для этого примера класс Time был несколько урезан.
#include
<iostream.h>
#include
"time4.h"
mam
()
{
Time
mt
cout
t;
&hourRef
"
<<
"Hour
<<
hourRef
hourRef
cout
t.badSetHour(20);
=
30;
=
before
//
<<
modification:
'\n'
;
модификация
<<
"Hour
<<
t.getHour()
after
недопустимым
modification:
значением
"
'\n';
<<
Опасность:
Вызов функции, возвращающей
ссылку, может быть использован в качестве lvalue.
74;
t.badSetHour(12)
"
"
<<
соut
\n* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \n
<<
"BAD
PROGRAMMING
PRACTICE!!!!!!!!!\n"
<<
an
"badSetHour as
lvalue, Hour:
//
//
=
"
<<
t.getHour()
"
<<
return
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
before
Hour
after
modification:
modification:
PROGRAMMING
badSetHour
*
Рис. 16.11.
\n
"
*
0;
Hour
BAD
\n*
as
an
20
30
PRACTICE! ! ! ! ! ! ! ! !
lvalue,
*
hour:
Возвращениессылки
на
74
закрытый
элемент данных
(часть 2
из
2)
Классы и
абстракция
Хороший
701
данных
стиль программирования 16.12
Никогда не допускайте, чтобы открытая функция-элемент возвращала неконстантную
ссылку (или указатель) на закрытый элементданных Возвращение такого рода ссыл¬
ки
нарушает
инкапсуляцию
Выполнение программы
ссылки
данных
класса.
начинается с
объявления объекта t
t.badSetHour(20). Программа отображает
зове
класса
Time
hourRef, которая инициализируется ссылкой, возвращаемой при
и
вы¬
на экране значение псевдонима
hourRef. Затем этот псевдоним используется для присваивания элементу hour
значения 30 (недопустимое значение) и его значение снова отображается на эк¬
И наконец,
ране.
сам вызов
сваивается значение
бражается
функции используется
в качестве
74 (еще одно недопустимое значение)
lvalue
и ему при¬
и это значение ото¬
на экране.
16.16.
Присваивание
по
поэлементного
умолчанию путем
копирования
Операция
присваивания (=) используется для присваивания некоторого
объекта другому объекту того же типа. Такое присваивание обычно выполня¬
ется при помощи поэлементного
екта
по
отдельности
рис. 16.12). (Замечание:
ные
проблемы,
содержат
каждый
копирования
копируется
в
такой
же
элемент
элемент одного объ¬
другом
объекте (см.
поэлементное копирование может вызывать серьез¬
если оно используется с классом,
динамически
в
выделяемую
память;
в
элементы данных которого
главе
18, «Перегрузка опера¬
будем обсуждать эти проблемы и покажем способы их решения.)
Объекты можно передавать функциям в качестве аргументов и можно воз¬
вращать их из функций. По умолчанию такая передача параметров (и их воз¬
вращение) происходит по значению
передается или возвращается копия объ¬
екта (мы представим несколько примеров в главе 18, «Перегрузка операций»).
ций»,
мы
Совет
по повышению
эффективности 16.4
Передача объектов по значению хороша с точки зрения безопасности, поскольку вы¬
зываемая функция не имеетдоступа к исходному объекту, однако вызов по значению
может снижать производительность системы в случае создания копии большого объ¬
екта. Объект может передаваться по ссылке путем передачи либо указателя на объ¬
ект, либо ссылки на него. Передача по ссылке повышает производительность, но сла¬
бее с точки зрения безопасности, поскольку вызываемой функции предоставляется
доступ к исходному объекту Безопасной альтернативой является передача объекта по
ссылке на константу.
16.17.
Повторное
использование
программного
обеспечения
Программисты, пишущие объектно-ориентированные программы, концен¬
трируют внимание на реализации полезных классов. Существует многообещаю¬
щая возможность создания каталогов классов, к которым могут иметь доступ
большие группы программистов. Уже имеется множество библиотек классов, и
по всему миру
разрабатываются
новые
библиотеки. Прилагаются
все усилия к
702
Глава 16
тому, чтобы сделать эти библиотеки широко доступными. В этом случае про¬
граммное обеспечение могло бы создаваться на основе уже существующих точ¬
но определенных, тщательно проверенных, хорошо документированных, пере¬
носимых и широкодоступных компонентов. Такого рода повторное использова¬
ние программного кода может ускорить разработку мощного и высококачест¬
венного программного обеспечения. Становится возможной ускоренная разра¬
ботка прикладных программ (RAD
Rapid Application Development).
\^ШI
//
FIG1 6__12
CPP
.
// Демонстрация возможности присваивания по умолчанию
I // объектов класса друг другу с помощью поэлементного
#include
I // Простой
class
копирования
<iostream.h>
класс
Date
{
Date
public:
Date(int
int
1,
=
=
1,
int
1990);
=
// конструктор
// по умолчанию
void print();
private:
mt month;
int day;
int year;
};
I
//
Простой конструктор
int
Date::Date(mt m,
{
month
m;
класса
d,
int
Date
без
диапазона
проверки
у)
=
d;
=
day
year
=
у;
}
I
//
объект
Выводит
void
{
Date
в
<<
'-'
Date::prmt()
cout << month
формате
<<
day
mm-dd-yyyy
<<
'-'
<<
year;
}
mam ()
{
Date
cout
dl
(
<<
7,
"dl
4,
=
dl.print();
cout << "\nd2
19 93),
d2;
//
//
d2
принимает
значение
по
умолчанию
1/1/90
";
=
"
;
d2.print();
d2
=
cout
dl;
<<
// присваивание с помощью поэлементного копирования
// по умолчанию
"\n\nAfter default memberwise copy, d2
";
=
d2.print();
cout << endl;
return
0;
}
Рис. 16.12. Присваивание одного объекта другому при помощи поэлементного копирования
по умолчанию (часть 1 из 2)
Классы и
абстракция
dl
=
7-4-1993
d2
=
1-1-1990
703
данных
After default memberwise
copy,
d2
=
7-4-1993
Рис. 16.12. Присваивание одного объекта другому при помощи поэлементного копирования
по умолчанию (часть 2 из 2)
Однако, прежде чем потенциал повторного использования программного
обеспечения можно будет реализовать полностью, должны быть решены суще¬
ственные проблемы. Нам потребуются схемы каталогизации, схемы патенто¬
вания,
механизмы
классов,
схемы
защиты,
описания,
гарантирующие
позволяющие
достоверность
исходных
новых
проектировщикам
копий
систем
потребностям существующие объекты, меха¬
низмы просмотра, позволяющие определять, какие классы доступны и наско¬
лько близко они соответствуют требованиям разработчика программного обес¬
печения и т.д. Предстоит решить множество интересных научно-исследоватеопределять, соответствуют ли их
льских задач. И эти задачи
их решения огромно.
будут
решены, поскольку потенциальное значение
Резюме
Структура
основе
представляет собой
объектов других
Ключевое
агрегатный
тип данных,
создаваемый
на
типов.
слово struct начинает определение структуры.
ры ограничивается фигурными скобками
({
и
Тело структу¬
}). Определение
структу¬
ры должно завершаться точкой с запятой.
Имя-этикетка структуры
ременных
структурного
Определения
может
быть использовано для объявления
структур не резервируют места
вые типы данных,
пе¬
типа.
в памяти; они создают но¬
которые используются для объявления переменных.
доступа к элементам структуры (или класса) используются опера¬
ции доступа к элементам
операция-точка (.) и операция-стрелка (->).
Для
Операция-точка обращается к элементу структуры посредством имени
переменной-объекта или ссылки на объект. Операция-стрелка обраща¬
ется
к
элементу
структуры
посредством
на
указателя
объект.
Отрицательными сторонами создания новых типов данных с помощью
ключевого слова struct языка С являются возможность получения неи¬
нициализированных данных; возможность
некорректной инициализа¬
ции; все программы, использующие объекты типа struct в стиле языка
С, должны быть изменены при изменении реализации этого типа
struct;
также
рантировать,
правильные
Классы дают
не
что
предусматривается
данные
с
в
защитных
целостном
мер,
позволяющих
состоянии
и
га¬
содержат
значения.
возможность программисту моделировать
дающие свойствами
ляться
хранятся
помощью
и
поведением.
ключевых
этой цели используется
слов
ключевое
Классы
class
слово
и
в
языке
объекты, обла¬
С++ могут опреде¬
struct, однако обычно для
class.
704
Глава 6
Имя
класса
может
быть
использовано для
объявления объектов
этого
класса.
Определение
класса начинается с ключевого слова
класса
завершается
class. Тело определе¬
фигурными скобками ({
ния класса ограничивается
точкой
с
и
}). Определение
запятой.
элементы данных и элементы-функции, объявленные в классе по¬
спецификатора доступа public:, являются видимыми для всех фун¬
кций, имеющих доступ к объекту этого класса.
Все
сле
Все
сле
элементы-функции, объявленные в классе по¬
спецификатора доступа private:, являются видимыми только для
элементы данных и
друзей
класса
и
других
Спецификаторы доступа
(:) и могут многократно
Закрытые
Говорят,
его
элементов.
к элементам всегда оканчиваются двоеточием
появляться
в
класса.
определении
данные недоступны за пределами класса.
что реализация класса скрыта от его клиентов, или «инкапсу¬
лирована».
Конструктор
это
специальная
функция-элемент
с
тем
же
именем,
что и класс,
которая используется для инициализации элементов
екта класса.
Конструкторы
вызывается при создании
объекта
объ¬
их клас¬
са.
Функция с тем же именем, что и класс,
тильды (~), называется деструктором.
которому предшествует символ
Множество открытых элементов-функций
сом
класса,
или
открытым
интерфейсом
класса называется
интерфей¬
класса.
определении элемента-функции за пределами области определения
класса ее имени предшествует имя класса и двухместная операция раз¬
решения области действия (::).
При
Элементы-функции, определяемые за пределами области определения
класса с использованием операции разрешения области действия, нахо¬
дятся внутри области действия этого класса.
определяемые в определении класса, автоматиче¬
ски становятся встроенными. Компилятор оставляет за собой право не
Элементы-функции,
встроенной любую функцию.
делать
Вызов элементов-функций
является более кратким, чем вызов
функций
программирования, поскольку для элемен¬
тов-функций предполагается, что необъявленные имена являются эле¬
ментами класса, в котором определена эта функция-элемент.
в
процедурных
языках
Внутри области действия
класса на элементы класса можно ссылаться
просто по их именам. Вне
области действия
класса ссылка на элементы
объекта, либо посредством
объект, либо посредством указателя на объект.
класса происходит либо посредством имени
ссылки
Для
на
доступа к элементам класса используются операции
мента
.
и
->.
выбора
эле¬
Классы и
абстракция
705
данных
принципом систематического конструирования про¬
является отделение интерфейса от реализации с
обеспечения
граммного
Фундаментальным
целью упрощения модификации программ.
Обычно определения
деления
классов помещают в заголовочные
элементов-функций помещают
его
в
файлы
файлы,
а опре¬
исходного кода с
тем же базовым именем.
Режимом доступа
по умолчанию для классов является
му все элементы после заголовка класса и до
таются
private:,
поэто¬
метки доступа счи¬
закрытыми.
Открытые
ляемых
Доступ
ться
первой
элементы класса дают представление об услугах,
предостав¬
классом.
к закрытым элементам класса может тщательно контролирова¬
путем
закрытые
элементов-функций,
использования
ми доступа.
Если
называемых
функция¬
класс хочет дать своим клиентам возможность читать
он
данные,
может
определять
соответствующую
get-функ-
цию. Чтобы дать клиентам возможность изменять закрытые данные,
класс может иметь соответствующую
Обычно
элементы
ты-функции
данных
класса
set-функцию.
делают
а
закрытыми,
его
крытыми и служат в
элемен-
Некоторые элементы-функции остаются
качестве сервисных для других функций класса.
открытыми.
за¬
Элементы данных класса не могут быть инициализированы в его опре¬
делении. Они должны инициализироваться в конструкторе, либо им
быть присвоены значения
объектов.
могут
щих
Конструкторы
Как
после
создания
соответствую¬
могут быть перегруженными.
объект
только
позже
класса
корректно
ты-функции, манипулирующие
этим
все
инициализирован,
объектом,
элемен¬
должны гарантировать,
что объект остается в целостном состоянии.
При объявлении объекта
класса
быть заданы инициализаторы.
конструктор класса.
могут
Эти инициализаторы передаются
в
В конструкторах могут быть специфицированы аргументы
по умолча¬
нию.
В конструкторах
не
может
быть специфицирован
тип
возвращаемого
значения, а также не могут предприниматься попытки возвратить зна¬
чение.
Если для
класса не определено ни одного конструктора, компилятор со¬
по умолчанию.
Предусмотренный компилятором
конструктор по умолчанию не выполняет никакой инициализации, по¬
этому после создания объекта не гарантируется целостность его состоя¬
здает
конструктор
ния.
Деструктор
ходит
из
класса вызывается автоматически, когда
объекта, однако
чем системе
23
Зак 801
он
будет
Деструкторы
класса
объект
области действия. Сам деструктор фактически
может
не
выполняет
принимают аргументов
быть
только
«приборку»,
объекту память.
заключительную
возвращена выделенная
один
класса вы¬
не разрушает
и не
деструктор.
возвращают
прежде
значений. У
Глава 16
706
присваивания (=) используется для присваивания некоторого
объекта другому объекту того же типа. Такое присваивание обычно вы¬
полняется посредством поэлементного копирования
каждый элемент
Операция
одного
объекта
по отдельности копируется в
такой
же элемент в другом
объекте.
Терминология
class
операция разрешения области
действия ::
get-функция
inline (встроенная)
операция ссылки &
определение класса
функция-элемент
private:
открытый интерфейс класса
protected:
поведение
public:
set-функция
повторно используемый код
повторное использование
абстрактный
абстракция
тип
данных
(ADT)
программного кода
поэлементное копирование
данных
автоматический локальный объект
атрибут
предикатная функция
представитель класса
вхождение в область действия
принцим минимума привилегий
выход из области действия
процедурное программирование
глобальный объект
расширяемость
реализация класса
деструктор
динамические объекты
заголовочный
файл
сервисная функция
создание экземпляра объекта
имя-этикетка
класса
сокрытие данных
инициализатор
элемента
инициализация
объекта
сокрытие информации
класса
сообщение
спецификаторы доступа
к элементам
инкапсуляция
интерфейс
клиент
класса
статический локальный объект
класса
тильда
конструктор
конструктор
по
тип
умолчанию
метод
тип,
область
область
действия класса
действия файла
операция
выбора
адреса
деструктора
пользователем
доступом
к
элементам
разработка прикладных
программ (RAD)
услуги, предоставляемые классом
файл исходного кода
функция доступа
&
элемента класса
операция присваивания
имени
ускоренная
Объектно-ориентированное
программирование (OOP)
объявление класса
взятия
в
определяемый
управление
объект
операция
(~)
данных
(.)
функция-элемент
целостное состояние элементов данных
элемент данных
(=)
Распространенные ошибки программирования
16.1.
Забывают
16.2.
Попытка явной инициализации
16.3.
Попытка перегрузить элемент-функцию класса
дящейся в области действия этого класса.
о
точке
с
запятой в конце определения
элемента
данных
класса.
класса.
функцией,
не нахо
Классы и
абстракция
16.4.
Попытка
класса
707
данных
являющейся элементом-функцией данного
другом), получить доступ к закрытому элементу
функции,
(или
его
не
этого класса.
16.5.
Попытка явной инициализации элемента данных класса в опреде¬
лении класса.
16.6.
Попытка объявить для конструктора тип возвращаемого значения
и/или попытка возвратить значение из конструктора.
16.7.
Попытка передать параметры в деструктор, возвратить значение из
деструктора или перегрузить деструктор.
16.8.
Конструктор
например,
вызывать другие элементы-функции класса,
get-функции, однако, поскольку конструктор
объект, элементы данных могут еще не находить¬
может
set- или
инициализирует
ся в целостном состоянии.
надлежащей
Хороший
16.1.
стиль
Использование
программирования
Используйте
в
только
раз
один
определении класса
из
соображений
граммы. Помещайте открытые
чтобы их было проще найти.
16.2.
Определяйте
все
16.3.
от
его
каждый спецификатор доступа
и удобочитаемости про¬
ясности
в
элементы
элементы-функции, кроме
определения класса.
са
элементов данных до их
инициализации может приводить к ошибкам.
начале
определения,
самых маленьких,
Это способствует отделению интерфейса
реализации.
Используйте директивы препроцессора #ifndef, #define
чтобы
ных
вне
клас¬
и
#endif,
не допустить повторного включения в программу заголовоч¬
файлов.
16.4. Используйте
имя
заголовочного
файла
точки в директивах препроцессора
с
#ifndef
подчеркиванием
и
#define
вместо
заголовочных
файлов.
16.5. Если
вы хотите
сначала перечислить
закрытые элементы
в
объяв¬
используйте метку private:, несмотря на то, что
private: принят по умолчанию. Это делает программу
лении класса, явно
тип доступа
более понятной. Мы предпочитаем сначала перечислять элементы
класса с типом доступа public:.
16.6. Несмотря
на то, что метки public: и private: могут повторяться и
чередоваться, перечислите сначала в одной группе все открытые
элементы класса, а затем перечислите в другой группе все его за¬
крытые элементы. Это акцентирует внимание пользователя на от¬
крытом
16.7.
интерфейсе
Использование каждого из
public:, protected:
помогает
16.8.
класса, а не на его реализации.
Когда
чтобы
это
и
private:
спецификаторов доступа
к
элементам
только один раз в определении класса
избежать путаницы.
целесообразно (почти всегда), определяйте конструктор,
гарантировать надлежащую инициализацию каждого объек¬
та осмысленными значениями.
23*
708
Глава 16
16.9.
Любая функция-элемент (или дружественная функция), изменяю¬
щая закрытые элементы данных объекта, должна гарантировать,
что данные остаются в целостном (действительном) состоянии.
16.10.Всегда предоставляйте конструктор, который
инициализацию
вующую
объектов
своего
выполняет соответст¬
класса.
присваивающие значения закрытым данным,
должны проверять корректность вновь вводимых значений; в случае
их некорректности set-функции должны приводить закрытые эле¬
менты в некоторое состояние, гарантирующее целостность данных.
16.11.Элементь,1-функции,
данных.
допускайте, чтобы открытая функция-элемент возвра¬
(или указатель) на закрытый элемент
Возвращение такого рода ссылки нарушает инкапсуляцию
данных
класса.
16.12.Никогда не
щала неконстантную ссылку
Советы по повышению
16.1.
эффективности
Обычно структуры передаются
кладных
расходов
на
их
по
значению.
копирование,
Чтобы избежать
передавайте
на¬
структуры по
ссылке.
16.2.
Чтобы избежать накладных расходов при передаче параметров по
значению и вместе с тем извлечь выгоду из защиты от изменения
исходных данных, передавайте параметры большого размера в виде
константных ссылок.
16.3.
Определение элемента-функции небольшого размера внутри опре¬
деления класса автоматически делает эту функцию встроенной
(если с этим согласен компилятор). Это может повысить производи¬
тельность системы, однако не всегда согласуется с принципами
систематической разработки программного обеспечения.
16.4.
Передача объектов
сти, поскольку
по значению хороша с точки зрения
вызываемая
функция
безопасно¬
не имеет доступа к исходному
однако вызов по значению может снижать производитель¬
ность системы в случае создания копии большого объекта. Объект
объекту,
может передаваться по ссылке путем передачи либо указателя на
объект, либо ссылки на него.
слабее
Передача
по ссылке повышает произ¬
безопасности, поскольку
вызываемой функции предоставляется доступ к исходному объек¬
водительность,
но
с
точки
ту. Безопасной альтернативой
ке
Общие
16.1.
на
зрения
является передача
объекта
по ссыл¬
константу.
методические замечания
Важно писать понятные и простые для сопровождения программы.
Изменения в программах являются скорее правилом, чем исключе¬
нием.
Программисты должны предвидеть заранее, что их код будет
модифицироваться. Как мы увидим, классы способствуют модифи¬
цируемости программ.
16.2.
Клиенты класса использует этот класс, не зная внутренних деталей
его реализации. При изменении реализации класса (например, из
Классы и
абстракция
709
данных
соображений эффективности)
ся.
16.3.
Это
значительно облегчает
Обычно элементы-функции
строк кода,
16.4.
Клиенты
модификацию программных
состоят
из
всего
требуется никакой
поскольку не
корректности
клиентов класса изменять не
лишь
из
требует¬
систем.
нескольких
логики для проверки
значений элементов данных.
класса имеет доступ к
интерфейсу
класса, но они не дол¬
жны иметь доступа к его реализации.
16.5.
внутри определения класса с по¬
их
вне
определения этого класса отделя¬
следующим определением
ет интерфейс класса от его реализации. Это способствует система¬
Объявление
тизации
16.6.
элементов-функций
разработки
программного обеспечения.
Объектно-ориентированный
подход
приводит к упрощению вызовов
передаваемых параметров.
к
программированию
функций
часто
из-за уменьшения числа
Это преимущество объектно-ориентиро-
ванного программирования является следствием того, что инкапсу¬
ляция
элементов
данных
и
элементов-функций внутри объекта
обращаться к этим элементам дан¬
дает элементам-функциям право
ных.
16.7.
Помещайте объявление
класса в
заголовочный файл, который
дол¬
жен быть включен любым клиентом, желающим использовать этот
открытый интерфейс класса. Помещайте определения
реализа¬
элементов-функций этого класса в исходный файл. Это
класс. Это
ция класса.
16.8.
Клиентам класса не обязательно видеть его исходный код для того,
чтобы пользоваться этим классом. Однако, клиентам необходимо
иметь возможность компоноваться с объектным кодом класса.
16.9.
являющаяся важной для
интерфейса класса, должна
файл. Информация, которая будет ис¬
пользоваться только внутри класса и не потребуется клиентам
класса, должна находиться в неопубликованном исходном файле.
Это еще один пример принципа минимума привилегий.
Информация,
включаться в заголовочный
16.10. С++
поощряет независимость программ от реализации. Когда реа¬
который используется в независимом от реализа¬
ции коде, изменяется, этот код не нуждается в модификации, одна¬
лизация класса,
ко может
потребоваться
16.11.0ставляйте
его
перекомпиляция.
все элементы данных класса закрытыми.
Предусмотри¬
для установки значений закры¬
тых элементов данных и получения значений закрытых элементов
те
публичные элементы-функции
данных. Такая архитектура помогает скрывать реализацию класса
от его клиентов, что уменьшает количество ошибок и способствует
модифицируемости программы.
16.12.Проектировщики
открытые
классов
элементы
для
мации и минимальных
используют
реализации
закрытые,
принципов
защищенные
сокрытия
и
инфор¬
привилегий.
закрытого доступа для элементов данных класса и откры¬
того доступа для его элементов-функций облегчает отладку, поско¬
16.13.3адание
Глава 16
710
льку
проблемы, связанные с манипулированием данными, ограни¬
элементами-функциями класса, либо его друзьями.
чиваются либо
16.14.Элементы-функции имеют тенденцию подразделяться
личных категорий: функции, которые считывают и
на
ряд
раз¬
возвращают
функции, которые устанав¬
функции, которых
реализуют характерные особенности класса, и функции, которые
выполняют разнообразную механическую работу для класса, та¬
кую, как инициализация объектов класса, присваивание объектов,
преобразование между классами и встроенными типами данных
значения закрытых элементов данных,
ливают значения закрытых элементов данных,
или между классами и другими классами,
для
объектов
и управление памятью
класса.
16.15.3акрытие элементов данных и контроль доступа (особенно доступа
для записи) к этим элементам посредством открытых элементов-функций помогает гарантировать целостность данных.
к закрытым данным посредством функций-элементов set...
и get... не только защищает элементы данных от присваивания им
недопустимых значений, но также изолирует клиентов класса от
представления данных. Таким образом, если представление дан¬
16.16.Доступ
ных по некоторым причинам меняется
количества
требуемой
сти), требуется изменить
класса
не
памяти
предоставляемый
самым.
Однако
грамм-клиентов
Упражнения
16.1.
в
нуждаются
или
только
этими
до
тех
пор,
клиенты
пока
интерфейс,
элементами-функциями, остается тем же
потребоваться перекомпиляция про¬
для самоконтроля
в
a) Ключевое
слово
элементам
к
осуществляется
объектом
с
совместно
рации
элементов-функций
d)
является
используется
e) Доступом
предложений:
для
посредством
на
объект класса.
как
класса
и
опера¬
класса или посредством опе¬
указателем
c) Элементы класса, определенные
ко для
следующих
предваряет определение структуры.
класса
совместно с
ции
из
каждом
b) Доступ
,
друзей
доступны толь¬
класса.
специальной функцией-элементом,
инициализации
элементов
данных
кото¬
класса.
по умолчанию для элемента класса является
.
функция используется для присваивания значений
f)
закрытым
g) Для
са
уменьшения
производительно¬
.
Заполните пропуски
рая
в целях
элементы-функции
изменениях
может
(часто
повышения
элементам
может
быть
класса.
использовано
h) Элементы-функции
менты
данных
присваивания объекта класса другому
класса
данных
i)
крытых
объекту
того же клас¬
.
обычно делают
,
а его эле¬
.
функция используется для
данных
класса.
извлечения
значений
за¬
Классы и
абстракция
711
данных
j) Множество
открытых
элементов-функций
класса
называется
класса.
k) Говорят,
1) Для
ва
что
реализация
класса
от
скрыта
его
клиентов,
или
определения класса могут быть использованы ключевые сло¬
и
.
m) Элементы
класса, специфицированные как
доступ¬
ны везде, где объект класса находится в области действия.
16.2.
,
Найдите ошибку(и)
как ее исправить.
a)
Предположим,
в
каждом из
что в классе
случаев и объясните,
следующих
Time объявлен следующий прототип:
void ~Time(int);
b) Далее следует фрагмент определения
класса
Time.
class Time
{
public:
// прототипы функций
private:
int hour
0;
=
int minute
int second
=
=
0;
0;
};
c) Предположим,
что в классе
Employee
объявлен следующий про¬
тотип:
int
Employee(const char *,
const
char
*);
Ответы к упражнениям для самоконтроля
16.1.
а) struct. b) -точки (.), -стрелки (->), с) private. d) Конструктор, e)
private. f) set-. g) поэлементное копирование по умолчанию, h) pri¬
vate, public. i) get-. j) интерфейсом, k) инкапсулирована. 1) public.
m) class, struct.
16.2.
а) Ошибка:
деструкторам не разрешается возвращать значение или
принимать параметры.
Исправление: удалите из объявления тип возвращаемого значения
void и параметр int.
b) Ошибка:
при
элементы
определении
данных
Исправление: удалите из
цию и инициализируйте
c) Ошибка: возвращение
Исправление:
не
могут
явно
инициализироваться
класса.
определения класса явную инициализа¬
элементы
данных
в
конструкторе.
конструкторами значений недопустимо.
удалите из объявления тип возвращаемого значения
int.
Упражнения
16.3.
Для
16.4.
Сравните понятия структуры
чего
нужна
операция
разрешения
и
класса
области действия?
в
языке
С++.
712
Глава 16
16.5.
Напишите конструктор,
который может использовать текущее вре¬
объявленной в заголовочном
функцией time()
для инициализа¬
файле time.h стандартной библиотеки языка С
мя, возвращаемое
ции объекта класса Time.
16.6.
Создайте класс
ских операций
именем
комплексными
для тестирования
Комплексные
i
где
равно
вашего
числа
выполнения
для
арифметиче¬
Напишите программу
числами.
класса.
имеют
вид
*
+
realPart
Complex
с
с
imaginaryPart
i,
лГл.
Используйте
переменные с плавающей точкой для представления
закрытых данных класса. Определите конструктор, который дает
возможность инициализировать объект класса при его объявлении.
Конструктор должен содержать значения по умолчанию на случай
отсутствия инициализаторов. Предусмотрите элементы-функции
для каждого из следующих
действий:
a) Сложение двух
Complex:
чисел типа
складываются вместе их ве¬
щественные и мнимые части.
b) Вычитание
двух чисел типа Complex: вещественная часть право¬
го операнда вычитается из вещественной части левого операнда и
мнимая часть правого операнда вычитается из
мнимой
части лево¬
го.
c) Вывод
ная
16.7.
чисел типа
часть
b
и
Complex
мнимая
Создайте
класс с именем
тических
действий
с
в виде
(а, b),
где а
его веществен¬
часть.
RationalNumber для
дробями. Напишите
выполнения
арифме¬
программу для тестирова¬
ния вашего класса.
Используйте
тых
целочисленные переменные для представления закры¬
класса
числителя и знаменателя. Предусмотрите
данных
конструктор, который дает возможность инициализировать объект
класса при его объявлении. Конструктор должен содержать значе¬
ния по умолчанию на
дробь
хранить
хранится 1
в
в
случай
отсутствия инициализаторов и должен
(т.е. для дроби 2/4 в объекте
знаменателе). Предусмотрите откры¬
несокращаемом виде
числителе
и
2
в
тые элементы-функции для каждого из следующих действий:
a) Сложение
двух рациональных чисел.
Результат
должен быть со¬
хранен в несокращаемом виде.
b)
Вычитание двух рациональных
чисел.
Результат
должен быть
двух рациональных чисел.
Результат
должен быть
сохранен в несокращаемом виде.
c) Умножение
сохранен в несокращаемом виде.
d) Деление
двух рациональных чисел.
Результат
должен быть со¬
хранен в несокращаемом виде.
e) Вывод
лем и b
f) Вывод
рациональных чисел в виде
a/b,
где а является числите¬
знаменателем.
рациональных чисел
в
формате
с
плавающей точкой.
Классы и
16.8.
абстракция
713
данных
Измените класс Time
на рис.
16.10
так,
мент-функцию tick, которая увеличивает
чтобы
на
он
одну
включал эле-
секунду
время,
объекте Time. Объект Time всегда должен оставаться в
корректном состоянии. Напишите программу, тестирующую эле¬
хранимое в
мент-функцию tick в цикле, который выводит время в стандартном
формате на каждой итерации цикла для иллюстрации правильной
работы элемента-функции tick. Убедитесь, что проверены следую¬
щие
случаи:
a) Переход
при увеличении времени к
b) Переход
при увеличении времени к следующему часу.
c) Переход
при увеличении времени к следующему дню
11:59:59 PM
16.9.
Измените
к
класс
следующей
минуте.
(т.е.
от
12:00:00 AM).
Date
на рис.
16.12 так, чтобы
в
нем выполнялась
проверка на наличие ошибок значений инициализаторов для эле¬
ментов данных month, day и year. Кроме того, напишите эле¬
мент-функцию nextDay
для увеличения даты на единицу. Объект
Date должен всегда оставаться в корректном состоянии. Напишите
функцию nextDay в цикле, который вы¬
каждой итерации цикла для иллюстрации правиль¬
работы функции nextDay. Убедитесь, что проверены следую¬
программу, тестирующую
водит дату на
ной
щие
случаи:
a) Переход
при увеличении даты к следующему месяцу.
b) Переход
при увеличении даты на следующему году.
16.10.0бъедините модифицированные классы Time из упражнения 16.8
и Date из упражнения 16.9 в один класс с именем DateAndTime (в
главе 19 мы обсудим наследование, которое даст нам возможность
быстро решить эту задачу без изменения существующих определе¬
ний классов). Измените функцию tick таким образом, чтобы она
вызывала
реходим
функцию nextDay,
к
следующему
printMilitary
дню.
если при увеличении времени мы пе¬
Измените функции
printStandard
и
так, чтобы в дополнение ко времени они выводили
дату. Напишите программу для тестирования нового класса Date¬
AndTime.
Специально проверьте переход
увеличении
времени.
к
следующему
дню
при
Scan AAW
r
л
а
в
а
17
Классы:
часть
II
Цели
Научиться
динамически создавать и уничтожать объ¬
екты.
Научиться определять константные объекты
функции-элементы.
и констан¬
Понять смысл определения дружественных
функций
тные
и
классов.
Понять
тов
принципы
данных
и
Познакомиться
использования статических элемен¬
функций-элементов.
с
различными типами
контейнерных
классов.
Научиться разрабатывать классы итераторов, переби¬
рающих элементы контейнерного класса.
Изучить применение указателя this.
Научиться
создавать и использовать шаблоны классов.
716
Глава 17
Содержание
17.1.
Введение
17.2.
Константные объекты и константные
17.3.
Композиция: классы в качестве элементов других классов
17.4.
Дружественные функции
17.5.
Указатель
17.6.
и
элементы-функции
дружественные
this
Динамическое распределение памяти
и delete
17.7.
Статические
17.8.
Абстракция
17.8.1.
17.8.2.
элементы
с
помощью
данных и сокрытие
массив
Пример: абстрактный
тип данных
строка
тип
очередь
Контейнерные
17.10. Шаблоны
классы
new
информации
Пример: абстрактный
абстрактный
операций
класса
тип данных
17.8.3. Пример:
17.9.
классы
и
данных
итераторы
классов
Распространенные ошибки программирования
Хороший стиль
Советы по пе¬
Советы по повышению эффективности
Общие методические замечания Упражнения для
реносимости программ
самоконтроля Ответы к упражнениям для самоконтроля Упражнения
Резюме
программирования
17.1.
В этой
обсудим
Введение
главе мы продолжим изучение классов и
несколько
более сложную тематику
абстракции данных. Мы
фундамент для обсуж¬
и заложим
операций в главе 18. Обсуждение, проводимое в
18, побуждает программистов к использованию объектов, пока¬
дения классов и перегрузки
главах с
16
по
зывая ряд их достоинств.
ния и
Затем
в главах
19
и
20 вводятся
понятия наследова¬
полиморфизма, составляющих подлинную суть объектно-ориентирован¬
ного программирования.
717
Классы: часть II
17.2. Константные объекты
и константные
элементы-функции
Мы неоднократно акцентировали внимание на принципе минимума при¬
вилегий как на одном из наиболее фундаментальных принципов систематиче¬
ского конструирования программного обеспечения. Давайте рассмотрим один
из
путей применения этого принципа к объектам.
Некоторые объекты должны быть модифицируемыми,
а другие нет.
Про¬
граммист может использовать ключевое слово const, чтобы указать, что объ¬
ект не является модифицируемым и что любая попытка изменения этого объ¬
екта является
const
ошибкой. Например,
Time
noon(12,
0,
0);
объявляет константный объект
noon класса
Time
и
инициализирует его 12 ча¬
сами дня.
Общее методическое замечание 17.1
Объявление объекта с модификатором const способствует последовательному прове¬
дению в жизнь принципа минимума привилегий Случайные попытки изменить объ¬
ект выявляются во временя компиляции и не приводят к ошибкам времени выполне¬
ния
Компиляторы С++ соблюдают объявления
ими полностью отвергаются
ных
const настолько строго, что
любые вызовы элементов-функций для констант¬
объектов (некоторые компиляторы
только выдают
предупреждение). Это
объекта, возможно, захотят исполь¬
«£еЪ>-функции. Чтобы предоставить клиен¬
жесткое ограничение, поскольку клиенты
зовать с этим объектом различные
там такую
возможность,
программист может
объявить
ты-функции; только эти функции могут оперировать с
ми. Разумеется, константные функции-элементы не
объект.
константные
константными
могут
элемен¬
объекта¬
модифицировать
Распространенная ошибка программирования 17.1
Определение
данных
с
модификатором
const
элемента-функции, которая
изменяет элемент
объекта
Распространенная ошибка программирования 17.2
с модификатором
функцию-элемент
Определение
стантную
const
элемента-функции, которая
вызывает не-кон-
Распространенная ошибка программирования 17.3
Вызов не-константной функции-элемента
для константного объекта
Распространенная ошибка программирования 17.4
Попытка изменения константного объекта
718
Глава 17
Функция специфицируется
в качестве
константной
как в ее
объявлении,
так и в определении путем вставки ключевого слова const после списка пара¬
функции (т.е. в случае определения функции
перед левой фигурной
скобкой, с которой начинается ее тело). Например, функция-элемент
метров
int
const
getValue()
{return privateDataMember};
объекта, объ¬
вне
определяется
функция-элемент
значение одного из элементов данных
которая просто возвращает
является константной. Если
определения
класса,
должны включать
Общее
то как
константная
объявление этой функции,
так и ее определение
модификатор const.
методическое замечание 17.2
Константная функция-элемент может быть перегружена своей не-константной вер¬
сией Компилятор автоматически выбирает, какая из перегруженных функций должна
быть вызвана, исходя из того, был ли объект объявлен как const
Здесь возникает интересная проблема в связи с конструкторами и деструк¬
торами, каждый из которых, несомненно, изменяет объекты. Для конструкто¬
ров и деструкторов константных объектов объявлений с модификатором const
не требуется. Конструктору должно быть разрешено изменять объект, с тем
чтобы этот объект мог быть надлежащим
образом инициализирован. Деструк¬
тор должен иметь возможность выполнять свою заключительную «приборку»
до разрушения объекта.
В программе на рис. 17.1 создается константный экземпляр объекта клас¬
са Time и делается попытка его изменения при помощи не-константных эле¬
ментов-функций setHour, setMinute
setSecond. В окне вывода показаны
компилятором Borland С++. Компилятор был
предупреждения, генерируемые
настроен
таким
образом, чтобы
в
и
случае выдачи любых предупреждений ком¬
пиляция не проводилась.
I
//
//
//
TIME5.H
Объявление
класса
Time.
Элементы-функции определены
#ifndef
#define
class
Time
{
:
0,
int
=
0,
// set-функции
void setTime(mt, int,
void setHour(mt);
void
TIME5.CPP
TIME5_H
TIME5 H
public:
Time(int
void
в
setMinute(int);
setSecond(int);
int
0)
//
конструктор
по
умолчанию
// устанавливает время
int);
// устанавливает элемент hour
// устанавливает элемент mmute
// устанавливает элемент second
// get-функции (обычно объявляются константными)
mt getHour() const;
// возвращает hour
int getMinute() const;
// возвращает minute
int getSecond() const;
// возвращает second
Рис. 17.1. Использование
класса Time с константными обьектами и константными
элементами-функциями (часть 1
из
3)
Классы: часть II
719
// pnnt-функции (обычно объявляются константными)
void printMilitary() const; // выводит время в военном формате
void printStandard()
const;
//
выводит
//
формате
время
в
стандартном
private:
mt
hour;
//
0
int
minute;
//
0
int
second;
//
0
23
-
59
-
59
-
#endif
//
TIME5.CPP
класса
//
Определения элементов-функций
#include "time5.h"
<iostream.h>
#include
//
для
Конструктор
Значения по
//
Time.
инициализации
Time::Time(int
данных.
закрытых
умолчанию равны 0 (см.
hr, int min, int sec)
класса).
определение
{
hour
=
(
minute
=
second
=
hr
(
(
>=
&&
hr
<
mm
>=
0
&&
min
<
sec
>=
0
&&
sec
<
// Устанавливает
void
0
значения
Time::setTime(int
24)
? hr
int
int
m,
:
0;
:
0;
minute
hour,
элементов
h,
0;
:
? min
? sec
60)
60)
и
second.
s)
{
hour
=
minute
second
(
=
=
h
(
(
h
<
m
>=
>=
0
&&
m
s
>=
0
&&
s
0
&&
24) ? h
60) ?
<
60) ?
0;
:
<
m
:
0;
s
:
0;
}
// Устанавливает
void
{
{
{
//
mt
//
mt
//
mt
<
значение
Time::setTime(int m)
minute
(m >= 0 &&
=
// Устанавливает
void
элемента
=
// Устанавливает
void
значение
Time::setHour(mt h)
hour
( h >= 0 && h
значение
24)
Получает
60)
}
0;
:
}
second
?
s
:
0;
}
hour
значение
const
значение
Time::getMinute()
Получает
m
элемента
<
0;
;
minute
?
60)
<
M
=
Time::getHour()
h
?
элемента
Time::setSecond(int s);
second
( s >= 0 && s
Получает
hour
значение
Time::getSecond()
{
return
hour;
}
minute
const
{
return
minute;
}
{
return
second;
}
second
const
Рис. 17.1. Использование класса Tlme с константными объектами и константными
элементами-функциями (часть 2
из
3)
720
Глава 17
//
Отображает
в
время
cout
//
<<
(
hour
<<
(
minute
<
10
?
"0'
"")
<<
minute
<<
(
second
<
10
?
"0'
"")
<<
second;
время
в
стандартном
Отображает
<
HH:MM:SS
формате:
const
военном
void Time::pnntMilitary()
10
?
"0"
void Time::printStandard()
:
«
')
hour.<<
":"
<<
":"
HH:MM:SS AM
формате:
(или PM)
const
{
cout
//
//
//
<<
((
<<
(minute
<
10
<<
(second
<
10
"0"
<<
(hour
?
AM"
hour
<
FIG17_1.CPP
Попытка доступа
с
помощью
==
12
к
0
hour
||
hour
12) ? 12
') << minute <<
') << second
PM") ;
==
"0"
12)
«
объекту
элементов-функций.
константному
не-константных
#include <iostream.h>
#include "time5.h"
main
()
{
const
Time
52);
//
константный объект
t(19,
33,
t.setHour (12);
t.setMinute (20);
//
ОШИБКА
не-константная
//
ОШИБКА
не-константная
t.setSecond(3 9);
//
ОШИБКА
не-константная
return
элемент-функция
элемент-функция
элемент-функция
0;
Compiling FIG17_l.CPP:
Warning FIG17_l.CPP: Non-const function
Time::setHour(int) called for const object
Warning FIG17_l.CPP: Non-const function
Time::setMinute(int) called for const object
Warning FIG17_l.CPP: Non-const function
Time::setSecond(int) called for const object
Рис. 17.1. Использование
класса Time с константными объектами и константными
элементами-функциями (часть 3
Хороший
из
3)
стиль программирования 17.1
Объявляйте
константными все
элементы-функции, предназначенные
для использова¬
ния с константными объектами
Константный объект нельзя изменить с помощью присваивания, поэтому
он должен быть инициализирован. Если элемент данных является констант¬
ным
объектом,
то для того,
чения для этого
На рис. 17.2
чтобы конструктор
объекта, должен
мог установить начальные зна¬
использоваться
инициализатор элемента.
показаны примеры использования синтаксиса инициализатора
Классы: часть II
константного элемента данных increment класса Increment.
элемента для
Конструктор
721
класса
Increment модифицирован следующим образом:
int i)
Increment::Increment(int с,
:
increment(i)
с;
{ count
}
=
^m
I
// FIG17_2.CPP
// Использование инициализатора элемента
// константы встроенного типа данных.
инициализации
<iostream.h>
#include
class
для
Increment
{
public:
Increment(int с
0,
void addIncrement()
void print() const;
int
=
private:
int count;
const int increment;
1
=
1);
count
{
//
+=
}
increment;
константный
элемент
данных
};
//
Конструктор класса Increment
Increment::Increment(int с, int i)
:
// Инициализатор
increment(i)
count
с;
{
}
элементата
данных
=
//
Выводит
void
данные
const
Increment::print()
{
cout
<<
"count
<<
",
"
=
<<
increment
count
=
"
<<
increment
endl;
<<
}
mam
()
{
Increment value(10,
cout
<<
"Before
5);
M;
incrementing:
value prmt () ;
.
for
1; j <= 3; j++)
(int j
value.addIncrement();
cout << "After increment
=
{
"
<<
j
<<
"
":
;
value.print();
}
return
0;
}
Before
incrementing:
count
After
increment
1:
count
=
After
increment 2:
count
=
After
increment
count
=
Рис. 17.2.
3:
increment
5
10,
increment
5
15,
increment
5
20,
5
increment
25,
=
=
Ислользование.инициализатора
=
=
=
элемента для инициализации константы
встроенного типа данных
Глава 17
722
Запись
нием
:increment(i)
вызывает инициализацию элемента
i. В случае необходимости
сто включите их в
increment
значе¬
нескольких инициализаторов элементов про¬
разделенный
запятыми список после двоеточия.
Рис. 17.3 иллюстрирует ошибки компилятора при трансляции програм¬
мы, которая пытается инициализировать increment с помощью операции при¬
а не инициализатора элемента.
сваивания,
^H
// FIG17_3
.
CPP
//
Попытка
//
встроенного
#include
class
константы
инициализации
типа
с
данных
помощью
присваивания.
<iostream.h>
Increment
{
public:
Increment(int
с
int
0,
=
void addIncrement()
void ,print() const;
1);
=
i
count
{
+=
increment;
}
private:
int count;
const int increment;
//
Конструктор класса Increment
Increment::Increment(mt с, int i)
// Константный элемент
{
=
count
//
Выводит
не
//
//
ОШИБКА:
объект
данных
increment'
инициализирован
с;
increment
void
//
i;
=
Невозможно
изменить
константный
данные
const
Increment::pnnt()
{
cout
main
<<
"count
<<
",
"
=
<<
increment
count
=
"
<<
increment
<<
'\n'
()
{
Increment value(10,
cout
<<
"Before
5);
"
incrementing:
;
value.print();
for
1; 3 <= 3; j++)
(int 3
value.addIncrement();
cout << "After increment
{
=
"
<<
3
<<
value.print();
Щ
return
0;
Рис. 17.3. Ошибочная попытка инициализации константы встроенного типа данных с помощью
присваивания (часть 1 из 2)
«
723
Классы: часть II
Compiling FIG17_3.CPP:
Warning FIG17_3.CPP 18:
Constant member
is
increment'
not initialized
Ошибка
Warning
FIGl7_3.CPP 20: Cannot modify a const object
FIGl7_3.CPP 21: Parameter fif is never used
Рис. 17.3. Ошибочная попытка инициализации константы
присваивания
(часть 2
встроенного
из
типа данных с помощью
2)
Распространенная ошибка программирования 17.5
Не предусматривают инициализатор
элемента для
константного
объекта
-
элемента
данных
Общее
методическое замечание 17.3
Как константные
объекты,
ровать, используя
«переменные» необходимо инициализи¬
инициализатора элемента. Присваивания в этом случае
так и константные
синтаксис
не допускаются.
17.3. Композиция:
классы в качестве элементов
классов
других
Объекту
класса
AlarmClock (будильник) необходимо знать о времени, ког¬
так почему бы не включить объект Time в качестве
да он должен зазвонить,
элемента
Класс
объекта AlarmClock? Такое
включение
композицией.
называется
может иметь в качестве элементов другие классы.
Общее
методическое замечание 17.4
Одной из
когда
При
форм повторного
класс содержит
в
использования программного кода является композиция,
качестве элементов
объекты других
классов.
действия его конструктор вызывается
необходимо специфицировать способ передачи
вхождении объекта в область
автоматически,
аргументов
в
поэтому
нам
конструкторы объектов
элементов
данных.
Элементы дан¬
объекты создаются до создания объектов включающего
ных
Общее
их класса.
методическое замечание 17.5
Если объект содержит несколько объектов в качестве элементов данных, эти элемен¬
ты данных
не менее,
-
объекты создаются
не пишите кода,
конструкторов
этих
Программа на
рации объектов в
в том
который
порядке,
зависит от
в
котором
они
были определены. Тем
специфического порядка
выполнения
объектов
рис.
17.4 использует
качестве
элементов
классы
данных
и Date для демонст¬
объектов. Класс Emp¬
Employee
других
содержит закрытые элементы данных lastName, firstName, birthDate и
hireDate. Элементы birthDate и hireDate являются объектами класса Date,
который содержит закрытые элементы данных month, day и year. Программа
loyee
создает экземпляр объекта
Employee
и инициализирует,
а затем выводит его
Глава 17
724
элементы данных.
Обратите внимание
конструктора Employee:
Employee::Employee(char *fname,
на синтаксис заголовка в определении
char *lname,
mt bmonth, int bday, int byear,
int hmonth, int hday, int hyear)
birthDate(bmonth, bday, byear),
:
hireDate(hmonth,
hday,
hyear)
Конструктор принимает восемь параметров (fname, lname, bmonth,
byear, hmonth, hday
и
hyear). Двоеточие
ры элементов от списка
параметров.
Инициализаторы
руют параметры Employee, которые передаются
данных
структор
bday,
в заголовке отделяет инициализато¬
в
элементов
специфици¬
конструкторы
элементов
объектов. Таким образом, bmonth, bday и byear передаются в кон¬
birthDate и hmonth, hday и hyear передаются в конструктор hireDa¬
te. Если инициализаторов несколько, они отделяются друг от друга запятыми.
I
I
//
//
DATEl.H
Объявление
класса
Date.
I // Элементы-функции определены
#ifndef
DATEl_H
#define
DATEl_H
class
Date
=
//
сервисная
//
для
//
//
//
11
//
любой
=
//
//
1990);
выводит
дату
конструктор
по
в
умолчанию
формате
месяц/день/год
12
-
31
в
от
зависимости
месяца
год
для проверки
и года
функция
месяца
данных
int
1,
=
const;
print()
year;
mt
int
1,
private:
int month;
int day;
int
DATEl.CPP
{
public:
Date(int
void
в
корректности дня
checkDay(int);
};
#endif
I
I
I
I
//
//
DATEl.CPP
Определения элементов-функций
#include
<iostream.h>
#include
"datel.h"
//
Конструктор:
//
вызывает
//
корректности
для
класса
Date.
подтверждает корректность значения month;
функцию checkDay для подтверждения
сервисную
Date:Date(int
значения
mn,
int
dy,
для
int
day.
yr)
{
month
=
year
day
=
=
(
mn
>
0
&&
yr;
checkDay(dy);
mn
<=
12)
?
mn
:
1;
//
проверяет корректность
//
также
могла
бы
проверять
// проверяет корректность
Рис. 17.4. Использование инициализаторов для объектов
-
элементов данных
(часть 1
из
3)
725
Классы: часть II
cout
<<
print ();
cout <<
//
for
constructor
object
"Date
Сервисная функция
для
подтверждения
корректности
=
31,
31,
!= 2)
( month
if
(
;
endl;
I // day, исходя из значений month и year.
int Date:checkDay(int testDay)
{
static int daysPerMonth[13]
{0, 31,
if
"
date
28, 31, 30,
31, 31, 30,
31};
{
>
testDay
return
30,
30,
значения
0
&&
<=
testDay
daysPerMonth[month])
testDay;
}
else
//
{
mt
Февраль:
(year
if
(
>
testDay
return
возможность
проверяет
(year %
=
days
0
%
400
4
&&
0
==
&&
0
==
testDay
високосного
года
| |
year
<=
%
100
!=
Set
to
0)
?
29
:
28);
days)
testDay;
}
cout
<<
return
"Day"
1;
<<
<<
testDay
//
оставляет
//
некорректого
"
объект
invalid.
в
целостном
значения
day l.\n";
состоянии в случае
}
//
Выводит
объект
Date
в
void Date::print() const
{ cout << month << '/'
//
//
формате
<<
day
месяц/день/год
<<
*/1
<<
year;
}
EMPLYl.H
Объявление
класса Employee.
// Элементы-функции определены
в
EMPLYl.CPP
#ifndef EMPLYl_H
#define EMPLYl_H
#include
"datel.h"
class Employee {
public:
Employee(char *, char
void print() const;
private:
char lastName[25];
char firstName[25];
Date birthDate;
Date
*,
int,
int,
int,
int,
int,
mt) ;
hireDate;
};
#endif
Рис. 17.4. Использование инициализаторов для объектов
-
элементов данных
(часть 2
из
3)
Глава 17
726
//
//
EMPLY1.CPP
Определения
элементов-функций
Employee.
класса
#include <iostream.h>
#include <string.h>
#include "emplyl.h"
#include "datel.h"
*
fname, char *lname,
int bmonth, int bday, int byear,
mt hmonth, mt hday, mt hyear)
birthDate(bmonth, bday, byear),
hireDate(hmonth, hday, hyear)
Employee::Employee(char
:
{
24);
strncpy(firstName, fname,
'\0 ';
firstName[24]
=
24);
strncpy(lastName, lname,
'\0 ';
lastName[24]
cout << "Employee object
=
<<
void
'
<<
firstName
"
constructor:
'
<<
<<
lastName
<<
firstName
endl;
const
Employee::print()
{
<<
cout
"
<<
lastName
",
hireDate.print();
cout <<
Birthday:
birthDate.print();
<<
"\nHired:
"
;
"
"
cout
<<
;
endl;
FIG17_4.CPP
// Демонстрация
//
объекта
#include
<iostream.h>
#include
"emplyl.h"
с
элементом
данных-объектом.
main ()
{
e("Bob",
Employee
cout
<<
"Jones",
7,
24,
3,
49,
12,
88);
endl;
e.print();
cout
<<
Date
d(14,
return
"\nTest
35,
Date
constructor
//
94);
значения
constructor
for
date
7/24/49
for
date
3/12/88
object
Date
Jones,
Bob
Hired:
3/12/88
Test
Date
values:\n"
для
объекта
;
Date
0;
object constructor
Bob Jones
Employee:
Date
mvalid
with
некорректные
Birthday:
constructor
with
7/24/49
invalid values:
Day 35 invalid. Set to day 1.
Date object constructor for date
1/1/94
Рис. 17.4. Использование инициализаторов для объектов
-
э'лементов данных (часть 3 из 3)
Классы: часть II
727
объект
Элемент данных
инициализатора элемента.
не обязательно инициализировать с
Если инициализатор элемента не задан,
помощью
автомати¬
будет вызван конструктор по умолчанию. Значения, установленные
конструктором по умолчанию (если вообще такие имеются), могут быть в по¬
следствии изменены при помощи set-функций.
чески
Распространенная ошибка программирования
17.6
Не объявлен конструктор по умолчанию для объекта
него нет
Совет
Явно инициализируйте объекты
элементов-объектов,
та
-
-
элементы
-
элемента данных,
первый раз при
вызове
посредством
связанные с
инициализаторов
двойной инициализацией
конструктора
по умолчанию для
элемента данных и снова при инициализации элемента с помощью
17.4.
когда для
случае генерирует ошибку
данных
Это устраняет накладные расходы,
-
в этом
эффективности 17.1
по повышению
элементов.
Компилятор
элемента.
инициализатора
Дружественные функции
и
объек¬
set-функций.
дружественные
классы
Дружественная функция
класса определена вне
области действия
этого
(и, как мы увидим в
Функция или целый
класса, однако она имеет право доступа к его закрытым
главе
19, «Наследование», защищенным) элементам.
быть объявлены в качестве «друзей» иного
класс могут
класса.
Чтобы объявить функцию в
перед прототипом этой функции
качестве друга некоторого класса, поместите
Чтобы объявить класс ClassTwo
объявление вида
в качестве друга класса
в определении класса ключевое слово
ClassOne,
friend.
поместите
friend ClassTwo;
в качестве элемента класса
Общее методическое
ClassOne.
замечание 17.6
private, protected
Понятия типа доступа
и
public
ний дружественных функций и классов, поэтому
любом месте определения класса.
эти
не имеют смысла для объявле¬
объявления могут размещаться в
-
Хороший стиль программирования 17.2
Помещайте все объявления дружественных функций и классов в начале определения
класса сразу после его заголовка и не помещайте перед этими объявлениями никаких
спецификаторов доступа.
Дружба даруется, но не может быть навязана, т.е. для того, чтобы класс В
А, в классе А должно быть объявлено, что класс В является
был другом класса
его другом.
Кроме
того, понятие дружественности не является ни симметрич¬
ным, ни транзитивным, т.е. из того, что класс А является другом класса
класс
В
В
является другом класса
является другом класса
также того, что класс
А
А,
С,
В,
а
вы не можете заключить ни того, что класс
ни того, что класс
С
является другом класса
является другом класса
С.
В,
а
Глава 17
728
Общее методическое замечание 17.7
Некоторые программисты считают, что «дружественность» разрушает сокрытие ин¬
формации и ослабляет значимость объектно-ориентированного подхода к проекту.
В программе на рис.
17.5 демонстрируется объявление и использование
дружественной функции setX для установки закрытого элемента данных x
класса Count. Обратите внимание, что объявление friend появляется (по со¬
глашению) первым в объявлении класса, даже до объявления открытых эле¬
ментов-функций. В программе на рис. 17.6 демонстрируются сообщения ком¬
пилятора, возникающие при вызове не-дружественной функции cannotSetX
для изменения закрытого элемента данных x.
^m
//
FIG17_5
I // Друзья
#include
.
CPP
класса
обращаться
могут
к
его
закрытым
элементам.
<iostream.h>
// Модифицированный
class Count {
friend void setX
класс
(
Count
Count
&,
int);
public:
Count()
{ x = 0; }
void print() const
private:
int x;
// элемент
{
<<
cout
x
<<
//
объявление
//
функции
дружественной
// конструктор
// вывод
endl; }
данных
};
//
Может
//
setX объявлена
void
изменять
setX(Count
закрытые
в
&c,
данные
качестве
Count,
поскольку
дружественной функции
класса
Count
int val)
{
c.x
val;
=
/
допустимо:
setX
является
другом
Count
}
mam
()
{
Count
object;
"
;
"object.x after instantiation:
object.print ();
cout << "object.x after call to setX friend function:
setX(object, 8); // установка x с помощью дружественной
// функции
obj ect.print ();
cout
<<
"
return
0;
}
0
object.x after instantiation:
object.x after call to setX friend
Рис. 17.5.
Друзья
класса могут
function:
обращаться
8
к его закрытым элементам
;
Классы: часть II
729
//
//
FIG17_6.CPP
Функции, не
//
не
элементами
являющиеся
обращаться
могут
к
его
или
закрытым
друзьями
класса,
элементам.
<iostream.h>
#include
// Модифицированный класс
class Count {
Count
public:
{ x = 0; }
Count()
void print() const
private:
int x;
// элемент
{
// конструктор
cout << x << '\n'
;
}
//
вывод
данных
// Функция пытается изменить закрытые данные Count,
// но не может этого сделать, поскольку она не является
// другом класса Count.
void
cannotSetX(Count &с, int val)
{
c.x
// ОШИБКА: 'Count::x' недоступен
val;
}
=
main ()
{
Count
object;
cannotSetX(
return
3);
object,
Warning
Рис. 17.6.
cannotSetX
не
является
другом
0;
Compiling FIG17__6.CPP:
Error FIG17__6.CPP
17:
Warning
//
FIG17_6.CPP
FIG17__6.CPP
Функции,
'Count::x'
is not accessible
18:
Parameter
'c'
18:
Parameter
'val'
is
never used
is
never used
не являющиеся элементами или друзьями класса, не могут
обращаться
к
его закрытым элементам
Можно определять в качестве друзей класса перегруженные функции.
Каждая перегруженная функция, которую собираются сделать дружествен¬
ной, должна быть явно объявлена в определении класса в качестве друга.
\
17.5. Указатель this
Когда элемент-функция
объекте
этого класса, каким
ссылается на
образом С++
на надлежащий объект? Ответ состоит
ет указатель на самого
себя
другой
элемент класса в некотором
гарантирует, что происходит ссылка
в том, что
каждый объект поддержива¬
который явля¬
называемый указателем this
ется неявным аргументомво всех ссылках на элемейты внутри этого объекта.
Указатель this может также использоваться явно. Каждый объект может
определить свой собственный адрес, используя ключевое слово this.
730
Глава 17
Указатель this неявно используется для ссылок как на элементы данных,
так и на элементы-функции объекта. Тип указателя this зависит от типа объ¬
екта, а также от того, объявлена ли константной функция-элемент, в которой
используется указатель this. В не-константной функции-элементе класса Em¬
*
const (константный указатель на
указатель this имеет тип Employee
ployee
объект
Employee).
В константной функции-элементе класса Employee указа¬
Employee const * const (константный указатель на кон¬
тель this имеет тип
стантный объект
Сейчас
Employee).
мы покажем
простой пример
явного использования указателя
this;
позже мы продемонстрируем некоторые важные и тонкие примеры использо¬
this-указателя. Каждая функция-элемент
вания
this
на
объект,
имеет
доступ
к
this,
На рис. 17.7 демонстрируется явное использование указателя
ством которого функция-элемент класса Test выводит
ных x
объекта Test. Программа использует
телем
this,
ЩШ
так и операцию-точку
//
FIG17_7
//
Использование
#include
class
.
(.)
закрытый
как операцию-стрелку
Test
this
указателя
для
ссылки
на
элементы
{
0);
Test(int
void print() const;
private:
int x;
=
};
Test::Test
(int
а
)
{
=
x
=
"
"\n(*this).x
<<
=
//
конструктор
X
"
=
}
а;
void Test:':print() const
{
<<
X
cout << "
<<
"\n this->x
<<
"
<<
this->x
(*this).x
<<
'\n'
}
(12);
а.print();
return 0;
}
x
=
12
this->x
=
12
=
12
(*this).x
(->)
с указа¬
this.
CPP
<iostream.h>
а
посред¬
элемент дан¬
с разыменованным указателем
public:
main()
{
Test
указателю
для которого она вызывается.
Рис. 17.7. Использование указателя this
;
объекта,
Классы: часть II
731
Совет по повышению эффективности 17.2
Из-за
соображений
экономии памяти для класса
существует
только одна копия каж¬
элемента-функции, которая вызывается всеми объектами этого класса С другой
стороны, каждый объект имеет собственную копию элементов данных класса
дого
Обратите
указателя
внимание
this
с
на
скобки вокруг *this при
круглые
операцией выбора
использовании
(.). Круглые скобки требук>тся,
элемента
поскольку операция-точка имеет более высокий приоритет, чем операция *.
Без круглых скобок выражение
*this.x
оценивалось бы так, как если бы скобки были расставлены следующим
обра¬
зом:
*(this.x)
Компилятор C+4- обработал бы
ку, поскольку операция выбора
это выражение как синтаксическую
элемента не может применяться к
ошиб¬
указателю.
Распространенная ошибка программирования 17.7
Попытка применить операцию выбора
выбора
элемента может
быть
элемента
(.)
к
использована только с
указателю на объект
объектом
(операция
или ссылкой
на
объ¬
ект)
Одним интересным применением указателя this является недопущение
юбъекта себе самому. Как мы увидим в главе 18, «Перегрузка
присваивания
операций», присваивание объекта себе самому может вызвать серьезные
ошибки, если объекты содержат указатели на память, динамически распреде¬
ленную операцией new.
Рис. 17.8 иллюстрирует возвращение
можность конкатенации вызовов
объект Time, что дает воз¬
функций-элементов класса Time. Каждая из
функций setTime, setHour, setMinute
со значением
ки?
*this
и
ссылки на
setSecond возвращает ссылку
на
Time
.
Почему работает метод, основанный на возвращении *this
Операция-точка (.) ассоциируется слева направо, поэтому
в качестве ссыл¬
в
выражении
t.setHour(18).setMinute(30).setSecond(22);
сначала оценивается
t.setHour(18),
качестве значения вызова
затем возвращается ссылка на
этой функции. Оставшееся выражение
объект t
в
затем интерп¬
ретируется как
t.setMinute(30).setSecond(22);
Выполняется
t.setMinute(30)
вызов
и
возвращается эквивалент t. Остав¬
шееся выражение интерпретируется как
t.setSecond(22);
Обратите
внимание, что в вызовах
t.setTime(20,
20,
20).prmtStandard();
также используется свойство конкатенации. Эти вызовы должны появляться в
этом выражении именно в таком порядке, поскольку printStandard, согласно
своему определению в классе, не возвращает ссылки на t.
дущем операторе вызова
таксической ошибке.
printStandard перед
Помещение
в преды¬
вызовом setTime приводит к син¬
732
Глава 17
//
//
TIME6.H
Объявление
//
Элементы-функции определены
#ifndef
Time.
TIME6.CPP
в
TIME6_H
TIME6_H
#define
class
класса
Time
public:
Time
(
{
mt
int
0,
=
// set-функции
Time &setTime(mt,
Time
Time
Time
0,
=
int,
&setHour(int);
&setMinut6(mt);
&setSecond(int);
int
//
0);
=
конструктор
по
// устанавливает hour,
// second
// устанавливает hour
// устанавливает minute
// устанавливает second
int);
умолчанию
mmute,
// get-функции (обычно объявляются константными)
int getHour() const;
// возвращает hour
int getMinute() const;
// возвращает minute
int getSecond() const;
// возвращает second
// pnnt-функции (обычно объявляются константными)
void printMilitary() const;
// выводит время
// в военном формате
void prmtStandard()
private:
int hour;
int
int
//
0
minute;
//
0
second;
//
0
const;
//
выводит
//
в
время
стандартном
формате
23
-
59
-
59
-
#endif
//
TIME6.CPP
//
Определения
#include
"time6.h"
#include
<iostream.h>
//
//
Конструктор
Значения по
Time::Time(mt
класса
элементов-функций
для
инициализации
Time.
закрытых
данных.
(см.
умолчанию равны 0
hr, int min, int sec)
определение
>=
0;
класса).
{
hour
//
=
(
minute
=
second
=
hr
(
(
min
>=
)
< 24)
&& min
<
sec
>=
0
&&
<
Устанавливает
Time
0
?
&&hr
значения
&Time::setTime(int
h,
sec
hr
60)
60)
элементов
int
m,
:
? min
? sec
hour,
int
:
:
0;
0;
minute
и
second.
s)
{
hour
=
(
h
>=
0
&&
h
<
24)
?
h
:
0;
Рис. 17.8. Формирование цепочки вызовов элементОв-функций (часть 1
из
3)
Классы: часть II
733
minute
second
( m
( s
*this;
=
>=
0
&&
m
<
=
>=
0
&&
s
<
return
//
дает
?
?
60)
60)
m
:
s
:
0;
0;
цепочки
вызовов
цепочки
вызовов
формирования цепочки
вызовов
формирования
возможность
}
Устанавливает
//
Time
значение
&Time::setHour(int
hour
элемента
h)
{
hour
(
=
return
h
>=
0
дает
?
24)
<
h
&&
//
*this;
h
0;
:
формирования
возможность
}
// Устанавливает
элемента
значение
mmute
&Time::setMinute(mt m)
Time
{
mmute
return
=
(
m
>=
0
//
*this;
}
// Устанавливает
m
&&
дает
<
60) ? m
возможность
элемента
значение
&Time::setSecond(int
Time
0;
:
second
s)
{
second
=
return
(
s
>=
*this;
0
&&
//
?
60)
<
s
дает
0;
:
s
возможность
формирования
цепочки
вызовов
}
//
Получает
mt
hour
значение
const
Time::getHour()
{
return
hour;
}
/
//
Получает
int
//
Получает
int
//
mmute
значение
const
Time::getMinute()
const
в
время
return
minute;
}
{
return
second;
}
second
значение
Time::getSecond()
Отображает
{
военном
void Time::prmtMilitary()
формате:
HH:MM:SS
const
{
cout
<
(hour
<<
(mmute
<
10
?
"0м
:
"")
<<
minute
<<
(second
<
10
?
"0"
:
"")
<<
second;
?
10
"0м
:
"")
<<
<<
<<
hour
":"
<<
":"
}
//
Отображает
void
в
время
стандартном
Time::printStandard()
формате:
HH:MM:SS
AM
(или
PM)
const
{
cout
«
( (hour
«
(minute
(second
(hour <
<<
«
==
0
||
<
10
?
<
10
12
?
hour
12) ? 12 : hour % 12)
"") « minute << ":"
"") << second
PM");
==
"0"
:
?
"0"
:
"
AM"
:
«
":"
"
}
//
//
//
FIG17_8.CPP
Формирование цепочки
при помощи указателя
#include
<iostream.h>
#include
"time6.h"
вызовов
элементов-функций
this
main()
Рис. 17.8.
Формирование
цепочки вызовов
элементов-функций (часть
2 из 3)
Глава 17
734
Time
t;
t.setHour (18) .setMinute(30) .setSecond(22)
cout
<<
cout
<<
"Military
t.printMilitary();
time:
"\nStandard
;
";
time:
";
t.printStandard();
cout
<<
"\n\nNew
t.setTime(20,
cout
<<
return
";
0;
standard
Рис. 17.8.
tirne:
20).printStandard();
endl;
Military time:
Standard time:
New
standard
20,
18:30:22
6:30:22 PM
time:
8:20:20
Формирование
PM
цепочки вызовов
элементов-функций (часть
17.6. Динамическое распределение
помощью
операций
new и
3 из 3)
памяти с
delete
Операции new и delete предоставляют программисту более изящный способ
управления динамическим распределением памяти по сравнению с вызовами
функций malloc и free в языке С. Рассмотрим следующее объявление кода:
TypeName *typeNamePtr;
В ANSI С для динамического создания объекта
TypeName
типа
вы
бы
на¬
писали
TypeNamePtr
=
malloc(sizeof(TypeName));
Здесь требуется обращение к функции malloc и явное
sizeof. В версиях языка С, предшествующих ANSI С, вы
применение операции
должны
были бы
так¬
преобразовать указатель, возвращенный malloc, с помощью операции при¬
ведения типа (TypeName *). Функция malloc не предусматривает какого-либо
же
метода инициализации блока выделенной
TypeNamePtr
=
new
памяти.
В С++
вы просто пишете
TypeName;
Операция new автоматически создает объект надлежащего размера, вызы¬
вает для объекта конструктор (если таковой доступен) и возвращает указатель
соответствующего типа. Если new не может найти свободной памяти, она воз¬
вращает указатель 0. В С++ для освобождения памяти, выделенной этому объ¬
екту, используется операция delete:
delete
typeNamePtr;
С++ допускает спецификацию инициализатора для вновь созданного объ¬
екта, например:
float
который
3.14159.
*thingPtr
=
new
инициализирует
float
вновь
(3.14159);
созданный объект
типа
float
значением
Классы:
735
часть II
Массив
может
быть создан
и присвоен
int *chessBoardPtr следующим об¬
разом:
chessBoardPtr
Этот
быть удален
с помощью оператора
chessBoardPtr;
[]
мы
mt[8][8];
new
массив может
delete
Как
=
увидим,
использование new и
также и другие преимущества.
зывает конструктор,
а
delete
delete
malloc
вместо
В частности, операция
и
free
имеет
new автоматически вы¬
автоматически вызывает деструктор класса.
Распространенная ошибка программирования 17.8
Смешивание
динамического распределения памяти в стиле new и delete с вызовами
malloc и free. Память, выделенная функцией malloc, не может быть освобождена с
помощью
delete; объекты, созданные
с
помощью
new,
не
могут
быть удалены
функцией free.
Хороший
Хотя
в
программах
на
С++
освобождаемая
malloc и
и
стиль программирования 17.3
удаляемые
с
помощью
как память,
может применяться
с помощью
delete,
free,
все
так и
же лучше
17.7. Статические
выделяемая
объекты, создаваемые
только
использовать
с помощью
с помощью new
new
и
delete
элементы класса
Обычно каждый объект класса имеет собственную копию всех элементов
Иногда это становится расточительным. В некоторых случаях
данных класса.
все элементы класса должны разделять только одну копию конкретного эле¬
По этой и другим причинам используются статические элемен¬
Статический элемент данных представляет собой «общеклассо¬
вую» информацию. Объявление статического элемента начинается с ключево¬
го слова static.
мента данных.
ты
данных.
Совет
по повышению
Используйте
но только
Хотя
эффективности 17.3
статические элементы данных с целью экономии памяти, если достаточ¬
одной
копии данных.
может показаться, что статические элементы данных
льным переменным, они имеют
область действия
подобны глоба¬
Статические
класса.
элемен¬
быть открытыми, закрытыми или защищенными. Статические эле¬
данных должны быть инициализированы в области действия файла. К
ты могут
менты
статическим элементам класса можно обращаться посредством лю¬
бого объекта этого класса, либо посредством имени класса с использованием
операции разрешения области действия. К закрытым и защищенным статиче¬
открытым
ским элементам класса
тых
элементов-функций
обращение
класса.
должно происходить посредством откры¬
Статические
даже если не существует ни одного
объекта
класса
элементы
этого класса.
крытому статическому элементу класса в случае, когда не
го
объекта
операцией
существуют,
Для обращения
существует
к от¬
ни одно¬
этого класса, просто поместите перед именем элемента имя клдсса с
разрешения области действия.
736
Глава 17
Для обращения
к
закрытому
или
защищенному
класса в случае, когда не существует ни одного
быть предусмотрена открытая статическая
торой
ее
статическому
объекта
функция-элемент, при
имени должны предшествовать имя класса и операция
области действия.
В программе на рис. 17.9 демонстрируется
вызове ко¬
разрешения
использование закрытого ста¬
открытой статической функции-элемента. Эле¬
тического элемента данных и
count инициализирован нулем
мент данных
элементу
этого класса, должна
в
области действия файла
с помо¬
щью оператора
int
=
Employee::count
0;
Элемент данных count содержит общее число уже созданных объектов
класса Employee. Если объекты класса Employee существуют, то на элемент
в
count может ссылаться любая функция-элемент объекта класса Employee
нашем примере, на count ссылаются как конструктор, так и деструктор. Если
не существует ни одного
объекта
класса
Employee,
то на элемент
можно ссылаться, однако только путем следующего вызова
getCount:
Employee: :getCount
count
все же
статической функ-
ции-элемента
()
В этом примере функция getCount используется для определения числа
созданных* к настояящему моменту объектов Employee. Обратите внимание,
в
что
ция
отсутствия представителей класса
функции. Однако, если представители
случае
выше вызов
getCount
может
быть
применяется
показанный
класса существуют,
функ¬
вызвана через один из них:
elPtr->getCount ()
I
I
// EMPLOYl.H
// Класс Employee
#ifndef EMPLOYl_H
#define EMPLOYl_H
class Employee {
public:
// конструктор
Emplouee(const char*, const char*);
// деструктор
~Employee();
char *getFirstName() const;
// возвращает имя
char
*getLastName()
const;
// возвращает фамилию
// статическая элемент-функция
static int getCount(); // возвращает число созданных
// экземпляров объектов
private:
char *firstName;
char *lastName;
//
статический
static
int
элемент
count;
//
//
данных
количество
созданных
экземпляров объектов
};
#endif
Рис. 17.9. Использование
статического элемента данных для хранения числа
(часть 1
из
3)
объектов
класса
Классы:
737
часть II
// EMPL0Y1.CPP
// Определения элементов-функций
#include
#include
#include
#include
класса
Employee
<iostream.h>
<string.h>
<assert.h>
"employl.h"
// Инициализирует статический
mt Employee::count
0;
элемент
данных
=
// Определяет
// возвращает
mt
I
статическую элемент-функцию,
количество
Employee::getCount()
//
Конструктор
//
имени
и
созданных
{
return
динамически
и
фамилии
count;
распределяет
использует
которая
экземпляров
функцию
// имени и фамилии в объект
Employee::Employee(const char *first,
объектов
Employee.
}
память
для
strcpy
для
const
char
копирования
*last)
{
=
firstName
new
assert(firstName
char[ strlen(first) + 1
!= 0);
// убеждается,
];
что
память
выделена
new
lastName
char[ starlen(last) + 1 ];
// убеждается, что
assert(lastName != 0);
strcpy(lastName, last);
память
выделена
strcpy(firstName,
first);
=
// увеличивает статический элемент данных
++count;
<<
<
firstName
cout
"Employee constructor for
count
"
'
<<
'
<<
lastName
<<
"
called.\n"
;
}
// Деструктор освобождает динамически распределенную
память
Employee::-Employee()
{
cout
<<
"~Emplouee()
'
<<
delete
delete
[]
[]
count;
'
<<
called
lastName
<<
"
for
<<
firstName
endl;
firstName;
// возвращает обратно память
// возвращает обратно память
lastName;
// уменьшает статический элемент данных count
}
//
Возвращает
char
*
имя
служащего
*Emplouee::getFirstName()
const
(
char
*tempPtr
assert
(
=
char[strlen(firstName) + 1];
!= 0);
// убеждается, что память
new
tempPtr
strcpy ( tempPtr,
return tempPtr;
выделена
firstName);
}
// Возвращает фамилию служащего
char *Employee::getLastName() const
{
new
char *tempPtr
char[strlen(lastName)
=
Рис. 17.9. Использование
Зак 801
1];
статического элемента данных для хранения числа объектов класса
(часть 2
24
+
из
3)
Глава 17
738
!=
assert(tempPtr
strcpy(tempPtr,
return
//
0);
убеждается,
что
память
выделена
lastName);
tempPtr;
}
FIG17_9.CPP
// Программа для
//
тестирования
класса
Employee
#include <iostream.h>
#include "employl.h"
main (\
{
cout
employees before instantiation is
// использует
Employee::getCount() << endl;
<<
"Number
<<
Employee *elPtr
Employee *e2Ptr
cout
new
=
new
клас-
"
1:
"\nEmployee
<<
elPtr->getFirstName()
<< elPtr->getLastName()
"
"
"
2:
<<
"\nEmployee
<<
e2Ptr->getFirstName()
<< e2Ptr->getLastName()
"
<<
"
delete elPtr;
delete
e2Ptr;
//
//
<<
возвращает обратно
возвращает обратно
"\n\n";
память
память
employees after deletion is
Employee::getCount() << endl;
<<
"Number
<<
return
имя
Employee('Susan", "Baker");
Employee("Robert", "Jones");
<<
<<
cout
=
"
"Number of employees after instantiation is"
elPtr->getCount() << endl;
<<
<<
cout
of
"
of
0;
of employees before instantiation is 0
Employee constructor for Susan Baker called.
Employee constructor for Robert Jones called.
Number of employees after instantiation is 2
Number
Employee 1: Susan Baker
Employee 2: Robert Jones
~Employee()
called
for
Susan
Baker
~Employee() called for Robert Jones
Number of employees after deletion is
0
Рис. 17.9. Использование статического элемента данных для хранения числа объектов класса
х
Функция-элемент
может
(часть 3
из
3)
быть объявлена статической,
если она не
обра¬
щается к не-статическим элементам класса. В отличие от не-статических эле¬
ментов-функций,
статическая
функция-элемент
не имеет указателя
this,
по¬
Классы: часть II
739
скольку статические элементы данных и статические элементы-функции су¬
ществуют независимо от объектов класса.
Распространенная ошибка программирования 17.9
Обращение
к
указателю this внутри статической функции-элемента
Распространенная ошибка программирования 17.10
Объявление статической функции-элемента с модификатором const.
Общее методическое замечание 17.8
Статические элементы данных
обращаться,
можно
17.8.
и статические
элементы-функции существуют
и
к ним
даже если не было создано ни одного представителя класса
Абстракция
данных и сокрытие
информации
Классы обычно скрывают подробности своей реализации
(т.е. пользователей). Это
называется сокрытием
жет, например, создать класс стека и скрыть от
от своих клиентов
информации. Программист
пользователей
мо¬
его реализацию.
Стеки могут быть легко реализованы с помощью массивов или связанных спис¬
ков. Клиент класса стека не должен знать о его внутреннем устройстве. Что дей¬
ствительно
заботит
данных в стек они
вышел»). Эта
вым
пользователя
знать детали
этих
деталей. Это
ет стек
и
версией,
ется
в порядке
концепция носит название
С++ определяют абстрактные
гут
так это то,
будут выбираться
типы
абстракции данных,
данных (ADT). Хотя
а
классы
пользователи и мо¬
будут писать код, зависящий от
(например, тот, который реализу¬
«вытолкнуть») можно заменить его новой
реализации класса,
означает, что
что при помещении элементов
LIFO («последним вошел, пер¬
они не
данный
класс
операции «втолкнуть» и
остальной системы, до
не затрагивая при этом
открытый интерфейс класса.
Задачей языка высокого уровня
является
тех пор, пока не изменя¬
формирование
«точки зрения»
программирование, удобной для программистов. Не существует какой-то
это одна из причин наличия такого боль¬
одной общепринятой точки зрения
на
Объектно-ориентированное программирова¬
ние на С++ являет собой еще одну такую точку зрения.
Большинство языков программирования акцентируют действия. В этих
языках данные существуют для поддержки действий, которые должны выпол¬
шого числа программных языков.
нять программы. Во всяком случае, данные «менее интересны», чем действия.
Данные
«примитивны».
Существует
только несколько встроенных типов дан¬
ных, и программистам нелегко создавать свои
Такое представление
меняется в
С++
и
собственные
новые типы.
объектно-ориентированном
подхо¬
де к программированию. С++ повышает значимость данных. Основное поле
это создание новых типов (т.е. классов) и отражение
деятельности в С++
взаимодействия между объектами
этих типов.
Для
того, чтобы продвигаться в этом направлении, специалистам в облас¬
ти языков программирования требовалось формализовать некоторые понятия,
касающиеся данных.
ем
абстрактного
24*
Рассматриваемая нами формализация связана с поняти¬
(ADT). Абстрактные типы данных привлекают
типа данных
Глава 17
740
сегодня такое же внимание, как и структурное программирование на протя¬
жении последних двух десятилетий. Абстрактные типы данных не вытесняют
программирования. Скорее они создают дополнительный уро¬
формализации, способствующий дальнейшему усовершенствованию про¬
структурного
вень
разработки программного обеспечения.
Что же это такое
абстрактный тип данных?
цесса
int. В голову сразу приходит
тип
int
Рассмотрим встроенный
понятие целого числа в математике, однако
обычное целое число. В частно¬
обычно довольно ограничены по величине.
на компьютере не совсем то же самое, что
сти,
компьютерные
целые
числа
тип
int на 32-разрядном компьютере ограничен значениями
примерно от -2 до +2 миллиардов. Если результат вычисления выходит за
пределы этого диапазона, происходит переполнение, и компьютер реагирует
некоторым машинно-зависимым образом. С математическими целыми числа¬
Например,
Таким образом,
ми
этой проблемы
int
на самом деле является только
не возникает.
понятие компьютерного типа
приближением
понятия
це¬
«настоящего»
Тот же самое верно и для типа float.
Даже тип char является таким же приближением. Значения типа char
обычно представляют собой 8-битовые последовательности, которые выглядят
лого числа.
совсем не похожими на символы, которые они должны представлять, как, на¬
пример,
прописная
цифра (напр., 5)
буква Z,
и т.д.
символ
Значения
нижнего
char
типа
на
регистра
z,
знак
доллара
($),
большинстве компьютеров дово¬
льно ограниченны в сравнении с множеством реально существующих симво¬
символов ASCII предусматривает 127 различных сим¬
значений. Это совершенно недостаточно для представления таких
как японский и китайский, для которых требуются тысячи символов.
7-разрядный набор
лов.
вольных
языков,
Дело
в том, что даже встроенные типы данных, предоставляемые языками
программирования,
приближениями,
подобными С++,
или
моделями,
в
действительности
понятий
и
поведения
являются
только
объектов реального
мы воспринимали тип int как нечто само собой
однако
теперь читатель может взглянуть на все по-новому.
разумеющееся,
все они являются примерами так
Типы, такие как int, float, char и другие,
мира. До настоящего времени
типов данных. Абстрактные типы данных явля¬
способами представления понятий реального мира внутри
компьютерной системы, удовлетворяющими определенному уровню точности.
называемых
абстрактных
ются, по существу,
тип данных на самом деле охватывает два понятия, а именно
Абстрактный
представление данных и операции,
Например,
ния,
понятие
умножения,
int
в языке
деления
и
которые допустимы над этими данными.
С++ определяет операции сложения,
взятия
по
модулю,
определено; и выполнению этих разрешенных
метры компьютера, такие как
терной
системы.
фиксированный
однако
деление
на
вычита¬
нуль
не
операций небезразличны
пара¬
размер слова базовой компью¬
В С++ для реализации абстрактных
типов данных програм¬
мист использует классы.
17.8.1.
Пример: абстрактный
тип данных
массив
Мы обсуждали массивы в главе 6. Массив мало чем отличается от указате¬
Примитивная адресация элементов, встроенная вчС, достаточна для выпол¬
нения операций над массивами, если программист проявляет осторожность.
Существует много операций, которые было бы желательно выполнять над мас¬
сивами, но которые не встроены в С++. Используя С++, программист может
ля.
741
Классы: часть II
разработать абстрактный
рые»
Класс
массивы.
Присваивание
Слабым
чика,
совершенный,
реализовать
чем «сы¬
множество
весьма
диапазон индексов.
массивов.
массивов.
Ввод/вывод
«Знание»
может
диапазона индекса.
Произвольный
Сравнение
массива
возможностей, например:
привлекательных
Проверку
массив, более
тип данных
для массивов.
массивами
своих
размеров.
настроенный на заказ¬
который вряд ли будет доступен точно в та¬
реализаций С++. Цель применения С++ и объек¬
местом здесь является то, что мы создаем
нестандартный
тип данных,
кой же форме в большинстве
это более быстрая разработка
тно-ориентированного программирования
в
полной
потенциала
программ. Для реализации
мере
ориентации на объекты
в
программировании критически важным является работа программистов в
крупномасштабной стандартизации и распространения библио¬
направлении
тек классов.
С++ включает небольшой
данных расширяют
Общее
базовый
встроенных типов.
набор
Абстрактные
типы
язык программирования.
методическое замечание 17.9
С помощью механизма классов программист может создавать новые типы. Эти новые
типы могут быть спроектированы таким образом, что их будет столь же удобно испо¬
это расширяемый язык.
льзовать, как и встроенные типы Другими словами, C+H
Хотя язык С++ и несложно расширять с помощью новых типов, сам
менений
базовый
язык из¬
не допускает.
Новые абстрактные
типы
данных,
созданные
в
средах
разработки С++,
могут быть собственностью отдельных программистов, небольших групп или
компаний. Абстрактные типы данных могут также помещаться в стандартные
библиотеки классов, предназначенные для широкого распространения. Это не
обязательно
способствует распространению стандартов, хотя de facto делает
появление стандартов вполне вероятным. В полной мере значение языка С++
будет
осознано только тогда, когда станут широко доступными
дартизованные библиотеки классов.
большие
стан¬
Должен быть
инициирован формальный
стандартизованных библиотек. В Соеди¬
процесс, способствующий разработке
ненных Штатах такая стандартизация
часто происходит благодаря ANSI,
Американскому национальному институту стандартов. В настоящее время
ANSI разрабатывается стандартная версия языка С++. Независимо от того,
каким образом, в конечном счете, появятся эти библиотеки, читатель, изучаю¬
щий С++ и объектно-ориентированное программирование, будет готов воспо¬
льзоваться преимуществами новых видов ускоренной, ориентированной на
компоненты разработки программного обеспечения, которую делают возмож¬
ной библиотеки абстрактных типов данных.
742
Глава 17
17.8.2.
Пример: абстрактный
тип данных
строка
C+4- намеренно не является всеобъемлющим языком и предоставляет про¬
граммистам только исходный материал для создания широкого диапазона сис¬
тем. Проектировщики языка не хотели вводить в язык ничего, что ограничива¬
эффективность. Они стремились к тому, чтобы С++ подходил для сис¬
которое требует эффективного выполнения про¬
грамм. Конечно, можно было бы включить строковый тип во встроенные типы
языка С++, однако проектировщики предпочли вместо этого снабдить язык ме¬
ло
бы
его
темного
программирования,
ханизмом для создания и реализации таких
абстрактных
типов данных посред¬
Мы разработаем свой собственный абстрактный
18.
ством классов.
в главе
строк
17.8.3.
Каждый
Пример: абстрактный
тип данных
из нас время от времени стоит в очередях.
Мы
ожидание в ряд, один за другим.
стоим в очереди на
бензоколонке,
чтобы
тип данных для
очередь
Очередью
мы называем
стоим в очереди в кассу в супермаркете, мы
мы стоим в очереди,
чтобы
сесть в
автобус,
мы
магистрали, а
все студенты слишком хорошо знают об очередях при регистрации курсов, кото¬
рые они хотели бы прослушать. Компьютерные системы внутри себя используют
стоим в очереди,
оплатить пошлинный
сбор
на
скоростной
разновидностей очередей, поэтому нам необходимо уметь писать програм¬
мы, которые моделируют свойства очередей и операции над ними.
много
Очередь является замечательным примером абстрактного типа данных.
Очередь предоставляет своим клиентам хорошо понятную схему поведения.
Клиенты по одному помещают объекты в очередь
используя для этого опе¬
и по одному получают эти объекты обратно по
рацию постановки в очередь
мере
необходимости, используя
для этого операцию исключения из
очереди.
Чисто
умозрительно очередь может стать бесконечно длинной. Реальная оче¬
редь, разумеется, имеет конечную длину. Элементы возвращаются из очереди
в порядке FIFO («первым пришел, первым вышел»).
Очередь скрывает внутреннее представление данных, в котором каким-то
образом предусмотрено отслеживание элементов, ожидающих в очереди в на¬
стоящее время, и предлагает своим клиентам набор операций, а имещго опера¬
цию постановки в очередь и операцию исключения из очереди. Клиентов не
интересует реализация очереди.
«как
Клиенты просто хотят, чтобы очередь функ¬
клиент ставит в очередь новый элемент,
обещалось». Когда
ционировала
очередь должна принять этот элемент и как-то поместить его в некоторую
структуру данных FIFO. Когда клиенту нужен следующий элемент из начала
очереди, очередь должна удалить этот элемент из своего внутреннего представ¬
ления
и
передать его
оператора
мент,
внешнему миру в
исключения
находившийся
из
очереди
в очереди
порядке
следующим
FIFO,
должен
т.е.
при
выполнении
быть возвращен
эле¬
наибольшее время.
Абстрактный тип данных очереди гарантирует целостность своей внутренней
структуры данных. Клиенты не могут непосредственно манипулировать этой
структурой. Только сам абстрактный тип очереди имеет доступ к своим внутрен¬
ним данным. Клиенты могут инициировать выполнение только допустимых опе¬
раций над представлением данных: операции, не предусмотренные открытым
интерфейсом абстрактного
разом. Это
типа данных, отвергаются некоторым адекватным
может означать выдачу
сообщения об ошибке, завершение
ния программы или просто игнорирование запроса этой операции.
об¬
выполне¬
Классы: часть II
743
17.9.
Контейнерные
К наиболее популярным
сы
(также
называемые
классы и
итераторы
классами-коллекциями),
Контейнерные
т.е.
классы,
для хранения совокупности объектов.
ставляют услуги типа вставки,
удаления,
классы
обычно предо¬
поиска, сортировки,
Массивы
мента на принадлежность классу и т.п.
проверки эле¬
и связанные списки являют¬
контейнерных классов.
классом-коллекцией ассоциируют объекты-итераторы,
ся примерами
Обычно
контейнерные клас¬
разработанные
типам классов принадлежат
с
или
это объект, который возвращает следующий
итераторы. Итератор
элемент коллекции. После реализации итератора класса несложно написать
просто
выражение для получения следующего элемента
контейнера. Итераторы
пи¬
классов, для которых они выполняют итерации. Подобно
тому, как книга, которую читают одновременно несколько читателей, может
одновременно содержать несколько закладок, на контейнерном классе может
друзей
шут в виде
действовать сразу
несколько итераторов.
17.10. Шаблоны
В
главе
15
мы
классов
обсуждали шаблоны функций
и то,
как они
повторному использованию программного обеспечения.
главу
обсуждением шаблонов
способствуют
Мы завершаем
эту
классов.
Можно понять, что такое стек, независимо от типа элементов, которые в
Однако, когда дело доходит до реального программирова¬
ния стека, должен быть задан тип данных. Это создает чудесную возможность
для повторного использования программного обеспечения. Все, что нам нуж¬
него помещаются.
но
это
способ
описания понятия стека в
обобщенном виде и создания экзем¬
обобщенного класса, однако
пляров классов, которые являются копиями этого
при этом зависят от типа.
Такую
возможность в
С++ предоставляют шаблоны
классов.
Общее
методическое замечание 17.10
Шаблоны классов
способствуют повторному
использованию
программного обеспече¬
ния.
Шаблоны
классов
были введены
в версии
что они являются относительно недавним
Шаблоны
классов часто называют
3 компилятора С++ AT&T,
добавлением
так
к языку.
параметризованными типами, поско¬
требуется один или более параметров-типов, специфицирующих
способ настройки обобщенного шаблона.
Программист, желающий использовать шаблоны классов, просто пишет
одно обобщенное определение шаблона класса. Всякий раз, когда программи¬
льку для
них
сту требуется новый класс,
компилятор генерирует
Например,
использует
сжатую,
несложную
int»,
«стека
нотацию,
и
код для указанного программистом класса.
образом, базой для
типа
«стека'значений
float», «стека зна¬
(например,
значений типа char» и т.д.), необходимых программе.
один шаблон классов стека может стать, таким
создания многих классов
чений типа
он
исходный
Глава 17
744
Обратите
внимание на определение
Оно выглядит
похожим
шаблона
класса стека на рис.
17.10.
класса за исключением
на стандартное определение
того, что ему предшествует заголовок
template <class
T>
чтобы показать, что это определение шаблона класса, которое получает один
тип
параметр
T, указывающий
обобщенном виде (как T)
элементов-функций.
лишь в
ниях
^Ш //
//
тип класса стека,
который
Тип элементов, которые будут храниться
граммист.
хочет создать про¬
в этом стеке, упоминается
повсюду в заголовке класса стека и в определе¬
TSTACKl.H
#ifndef
TSTACKl_H
#define
TSTACK1
class
Stack
Stack
H
T>
<class
template
класса
шаблон
Простой
{
public:
10) ;
=
Stack(int
~Stack() { delete []
int push(const T&);
mt pop (T&) ;
int
isEmpty()
int
isFull()
const
{
Конструктор
//
по
с
умолчанию
//
stackPtr; }
// Помещает
{
const
//
Деструктор
в
элемент
стек
// Выталкивает
элемент
return top
return top
-1;
==
==
стека
размером
10
size
}
-
из
стека
// 1,
1; ]
//
если
пуст
если
полон
private:
int
size;
int
top;
T
//
// количество элементов в
// местоположение вершины
// указатель на стек
*stackPtr;
Конструктор
с
template <class
размером
стека
по
стеке
стека
умолчанию
10
T>
Stack<T>::Stack(int
s)
{
size
top
=
s;
//
-1;
=
stackPtr
=
Пустой
стек
T[size];
new
}
// Помещает элемент в стек
// возвращает 1 в случае успеха,
template <class
int
0
в
противном
случае
T>
(const
Stack<T>::push
T
&item)
{
if
(!isFull())
{
item;
помещение
stackPtr[++top]
return
}
return
0;
1;
//
//
=
поместить
в
в
стек
стек
не
успешно
удалось
}
Рис. 17.10. Определение шаблона
класса Stack
(часть 1
из
3)
Классы: часть II
//
745
Выталкивает
элемент
из
стека
template <class T>
int Stack<T>::pop(T &popValue)
{
if (!isEmpty())
{
popValue
stackPtr[top-];
=
return
//
1;
выталкивание
из
стека
успешно
}
return
0;
//
вытолкнуть
из
стека
не
удалось
#endif
FIG17_10.CPP
// Программа-тестер
//
для
#include
<iostream.h>
#include
"tstackl.h"
шаблона
Stack
main ()
{
Stack<float>
f
float
<<
cout
=
floatStack(5);
1.1;
onto
"Pushing elements
while
{
(floatStack.push(f))
cout << f << 1 1;
f += 1.1;
floatStack\n";
//
успех
1)
(возвращена
}
cout
<<
<<
"\nStack is
"\n\nPopping
full.
while
//
(floatStack.pop(f))
1
cout << f <<
1;
<<
cout
"\nStack
Stack<int>
int
i
cout
=
<<
is
"
Cannot push
elements
from
успех
Cannot
empty.
<<
f
<<
floatStack"
(возвращена
<<
pop"
endl;
1)
endl;
intStack;
1;
"\nPushing elements
while
{
(intStack.push(i))
cout << i « 1 1;
i++;
onto
//
intStack\n";
успех
(возвращена
1)
}
cout <<
<<
"\nStack
is
"\n\nPopping
while
full.
(intStack.pop(i))
1
cout << i <<
1;
cout
<<
return
"\nStack
is
Cannot push
elements
//
empty.
from
успех
"
<<
i
intStack"
(возвращена
Cannot pop"
<<
<<
endl;
1)
endl;
0;
Рис. 17.10. Определение шаблона класса Stack (часть 2 из 3)
746
Глава 17
elements
1.1
2.2
Stack
IPushing
4.4
full.
is
Popping
5.5 4.4
Stack
3.3
onto
Cannot push
from
elements
3.3
is
floatStack
5.5
2.2
floatStack
1.1
Cannot pop
empty.
Pushing elements onto
1 2 3 4 5 6 7 8 9 10
Stack
is
full.
is
intStack
Cannot push
empty.
11
from intStack
Popping elements
10 9 8 7 6 5 4 3
Stack
6.6
2
1
Cannot pop
Рис. 17.10. Определение шаблона класса Stack (часть 3 из 3)
Теперь давайте
рассмотрим тестовую программу
Программа
лона классов стека.
(функцию main)
начинается с создания экземпляра
для шаб¬
объекта flo¬
atStack размера 5. Этот объект объявляется объектом класса Stack<float>.
Компилятор ассоциирует тип float с параметризованным типом T шаблона и
генерирует исходный код для класса стека типа float.
Затем в программе значения типа float 1.1, 2.2, 3.3, 4.4
floatStack. Цикл помещения
и
5.5 последовате¬
завершается при по¬
пытке программы поместить шестое значение в стек floatStack (который уже
полон, поскольку он был создан с 5-ю элементами).
льно помещаются в
в стек
Далее в программе эти пять значений выталкиваются из стека (в порядке
LIFO). Программа пытается вытолкнуть и шестое значение, однако floatStack
уже пуст и, таким образом, цикл выталкивания завершается.
После этого создается экземпляр целочисленного стека с помощью объяв¬
ления
Stack<int>
Поскольку
intStack;
не
10,
по умолчанию
было задано никакого размера, размер принимает значение
как было определено в конструкторе по умолчанию. Опять
программа в цикле помещает значения
в
intStack до
тех пор, пока стек не
дет заполнен, и затем в цикле выталкивает значения из
бу¬
intStack,
пока стек не
шаблона
класса начи¬
станет пустым.
Все определения элементов-функций
вне заголовка
наются с
template <class T>
В дальнейшем каждое определение
исключением
того,
что
виде типа-параметра
тип
элемента
похоже на стандартное определение за
стека
всегда указывается
обобщенно
Т. Операция разрешения области действия,
используется для привязки определения
в
как всегда,
каждой функции-элемента
к
области
действия соответствующего класса. В данном случае именем класса является
Stack<T>. Когда создается экземпляр объекта floatStack типа Stack<float>,
конструктор Stack
создает для представления стека массив элементов типа Т.
Вместо T подставляется тип float и из оператора
stackPtr
=
new
T[size];
компилятор генерирует
в версии
Stack<float> оператор вида
Классы: часть II
stackPtr
747
=
new
[size];
float
типы
Параметризованные
(т.е. шаблоны)
могут принимать более одного
параметра, например:
template
<class T,
Каждому
параметру должно предшествовать ключевое слово class.
class
R>
class
S,
Резюме
Ключевое
объект
ния
слово const может
не является
быть использовано для указания на то, что
и что любая попытка его измене¬
модифицируемым
ошибкой.
является
Компилятор С++
запрещает вызовы не-константных
функций
для кон¬
стантных объектов.
Константная функция-элемент
Функция специфицируется
нии,
так
и
в
в
может
качестве
изменять
константной
объект.
как
в
ее
объявле¬
определении.
Константная функция-элемент
стантной версией. Компилятор
груженных
не
функций
может
быть перегружена своей
автоматически
выбирает,
не-кон-
какая из пере¬
должна быть использована, исходя из того, был
ли объект объявлен как const.
Константный объект должен быть инициализирован.
Если
элементы-объекты,
класс содержит константные
должны
быть
предоставлены
инициализаторы
Классы могут содержать объекты других
Элементы данных
ющего
их
то конструктору
элементов.
классов.
объекты создаются до создания объектов включа¬
класса.
Если для объекта
элемента данных не задан инициализатор,
этого объекта вызывается конструктор по умолчанию.
Дружественная функция
право
доступа
к
его
то для
класса определяется вне этого класса и имеет
закрытым
и
защищенным
Объявления дружественных функций
в любое место определения класса.
и классов
элементам.
могут быть помещены
Каждая перегруженная функция, которую собираются сделать друже¬
ственной, должна быть явно объявлена в качестве друга класса.
Каждый объект поддерживает указатель
указателем this
который становится
ссылках на элементы внутри этого
пользуется для ссылки как на
ных
на самого
себя
неявным
параметром
называемый
объекта. Указатель this
элементы-функции,
во
всех
неявно ис¬
так и элементы дан¬
объекта.
Каждый объект
может
ключевое
this.
слово
Указатель this
пользуется
может
неявно.
определить
быть
свой собственный адрес, используя
использован явно, однако чаще всего он ис¬
748
Глава 17
Операция
new
автоматически
создает
объект надлежащего размера
Для освобождения
возвращает указатель соответствующего типа.
ти,
выделенной этому объекту,
Статический
Объявление
элемент
данных
в
и
памя¬
С++ используется операция delete.
«общеклассовую» информацию.
хранит
с
статического элемента начинается
ключевого
слова sta¬
tic.
Статические
К
элементы
данных
имеют
область действия
статическим элементам класса можно
обращаться
операцией
класса, либо посредством имени класса с
ти действия.
класса.
через объект этого
разрешения облас¬
Функция-элемент может быть объявлена статической, если она не обра¬
щается к не-статическим элементам класса. В отличие от не-статических,
функция-элемент
статическая
исходит потому,
что
статические
элементы-функции существуют
Шаблоны
тия
не имеет указателя
элементы
независимо
обобщенном
статические
объектов
от
классов предоставляют программисту
класса в
this. Это про¬
и
данных
способ
класса.
описания поня¬
виде и создания экземпляров классов, кото¬
рые являются копиями этого
обобщенного
класса, однако при этом за¬
висят от типа.
Шаблоны
классов часто называют параметризованными типами, поско¬
льку для них
требуется один или более параметров-типов,
настройки обобщенного шаблона.
специфици¬
рующих способ
Определения шаблонов
template <class
классов
начинаются
с
заголовка
T>
предшествующей определению класса. Может объявляться
более одного параметра-типа. В этом случае они разделяются запятыми
и каждому типу предшествует ключевое слово class.
на строке,
Экземпляры шаблона
которое
следует
за
класса
именем
создаются
шаблона,
путем
как
в
типа
указания
следующем
класса,
примере:
ClassName<typeName> objectName;
Компилятор подставляет typeName
определению
класса
и
определениям
вместо
типа-параметра
по
всему
элементов-функций.
Терминология
вложенный
класс
двухместная
операция
разрешения
действия (
области
::
)
деструктор
динамические объекты
функция
дружественная
дружественный
инициализатор
класс
элемента
конструктор
конструктор объекта-элемента
конструктор по умолчанию
контейнерные классы
область действия класса
объект
-
операция
элемент
данных
delete
операция delete[]
операция new
итератор
композиция
конкатенация
функция-элемент
константный объект
константная
вызовов
элементов-функций
операция
выбора
элемента
операция выбора элемента
стрелка
точка
(.)
Классы: часть II
749
параметризованный
тип
статическая
принцип
привилегий
статический
минимума
расширяемость
спецификаторы доступа
к
указатель
функция-элемент
элемент
данных
this
указатель на элемент
шаблон класса
элементам
Распространенные ошибки программирования
17.1. Определение
изменяет
17.2.
Определение
вызывает
17.3.
с
модификатором const элемента-функции, которая
элемент
с
объекта.
данных
модификатором const элемента-функции, которая
функцию-элемент.
не-константную
Вызов не-константной функции-элемента
для константного объек¬
та.
17.4.
Попытка изменения константного объекта.
17.5.
Не
17.6.
предусматривают
объекта
элемента
инициализатор
элемента
для
константного
данных.
Не объявлен конструктор
объекта
элемента
данных, когда для него нет инициализатора элемента.
Компилятор
в
этом
случае
17.7. Попытка
генерирует
по
умолчанию
для
ошибку.
применить операцию
выбора
элемента
объект (операция выбора элемента может быть
ко с объектом или ссылкой на объект).
(.)
к указателю на
использована толь¬
17.8. Смешивание
динамического распределения памяти в стиле new и
delete с вызовами malloc и free. Память, выделенная malloc, не мо¬
жет
быть освобождена
с помощью
delete; объекты, созданные
функцией free.
с по¬
мощью new, не могут быть удалены
17.9. Обращение
к указателю
this внутри статической функции-элемен-
та.
17.10.0бъявление
статической функции-элемента
с
модификатором
const.
Хороший
17.1.
программирования
Объявляйте
ные
17.2.
стиль
для
константными
использования
с
все
элементы-функции, предназначен¬
константными
объектами.
Помещайте все объявления дружественных функций и классов в
начале области определения класса сразу после его заголовка и не
помещайте
перед этими объявлениями никаких спецификаторов
доступа.
17.3.
Хотя в программах на С++ может применяться как память, выде¬
ляемая с помощью malloc и освобождаемая с помощью free, так и
объекты, создаваемые с помощью new и удаляемые с помощью de¬
lete, все же лучше использовать только new и delete.
750
Глава 17
Советы по повышению
17.1.
эффективности
Явно инициализируйте объекты
элементы данных посредством
Это устраняет накладные расходы,
инициализаторов элементов.
двойной инициализацией элементов-объектов,
пер¬
вый раз при вызове конструктора по умолчанию для объекта
связанные с
элемента данных и снова при инициализации элемента с помощью
set-функций.
17.2. Из-за соображений
экономии памяти для класса существует только
одна копия каждого
объектами
17.3.
элемента-функции, которая
копию
Используйте
статические
если
Общие
элементов
элементы данных
одной
имеет
класса.
данных
только
достаточно
вызывается всеми
С другой стороны, каждый объект
класса.
собственную
мяти,
17.1.
этого
с целью
копии
экономии па¬
данных.
методические замечания
Объявление объекта
с
модификатором const способствует последо¬
привилегий.
вательному проведению в жизнь принципа минимума
Случайные
пиляции
17.2.
попытки изменить
и
не
объект
выявляются во временя ком¬
ошибкам времени
к
приводят
выполнения.
Константная функция-элемент может быть перегружена своей
не-константной версией. Компилятор автоматически выбирает, ка¬
кая
из
перегруженных
функций
должна быть вызвана, исходя из
того, был ли объект объявлен как const.
17.3.
Как константные
объекты,
так и константные
ходимо инициализировать, используя
элемента.
17.4.
Одной
из
Присваивания
форм повторного
ется композиция,
екты
17.5.
в
других
этом
«переменные»
синтаксис
случае
не
необ¬
инициализатора
допускаются.
использования программного кода явля¬
когда класс содержит в качестве элементов объ¬
классов.
Если объект содержит
несколько
объектов
в
качестве
объекты создаются
данных, эти элементы данных
элементов
в том
порядке,
в котором они были определены. Тем не менее, не пишите кода, ко¬
торый
торов
17.6.
зависит от
этих
Понятия
специфического порядка
выполнения
конструк¬
объектов.
private, protected
типа доступа
и
public
не
имеют
объявлений дружественных функций и классов, поэто¬
объявления могут размещаться в любом месте определения
смысла для
му эти
класса.
17.7.
Некоторые программисты считают,
шает сокрытие
информации
ентированного подхода
17.8.
Статические
существуют
элементы
к
и
что
«дружественность»
разру¬
ослабляет значимость объектно-ори¬
проекту.
данных
и к ним можно
и
статические
обращаться,
ни одного представителя этого класса.
элементы-функции
даже если не было создано
Классы: часть II
17.9.
751
С помощью механизма классов программист может создавать но¬
вые типы. Эти новые типы могут быть спроектированы таким обра¬
зом, что их
будет столь же удобно использовать,
это расширяемый
образом, С++
типы. Таким
С++
как и встроенные
язык.
и несложно расширять с помощью новых типов,
изменений
язык
17.10.Шаблоны
не
язык
базовый
допускает.
способствуют
классов
Хотя
сам
повторному
использованию
про¬
граммного обеспечения.
Упражнения
17.1.
для самоконтроля
Заполните пропуски
в
каждом
из
следующих
утверждений:
a) Синтаксис
используется для инициализации кон¬
стантных элементов класса.
b)
должен быть объявлен в классе, чтобы иметь доступ
к закрытым элементам данных класса.
на динамически
c) Операция
возвращает
созданный объект класса.
d) Константный
он
e
не
может
объект должен быть
быть
)
;
после
создания
изменен.
элемент данных хранит
«общеклассовую» информа¬
цию.
f) Каждый
емый
объект поддерживает указатель
указателем
g)
обобщенном
h) Ключевое
ная
обеспечивают
указывает, что
не
предусмотрен
то
вызывается
к
инициализатор
может
1) Шаблоны классов
m) Элементы данных
включающего
n) Операция
операцией
ленную
Найдите ошибку(и)
a)
char
можно
иногда
=
new
b) class Example
public:
могут
обращаться
и
к
называют
типами.
объ¬
возвращает память, предварительно выде¬
new.
в
каждом из следующих случаев и
char[20];
{
обраща¬
класса.
исправить.
free(string);
она не
класса.
объекты класса создаются
их
объекта
элемента
класса.
*string;
string
для
быть статической, если
элементам
элементам
ее
или перемен¬
объекта.
k) Дружественные функции
как
объект
немодифицируемыми.
j) Функция-элемент
17.2.
называ¬
средства описания понятия класса в
слово
являются
класса,
екта
себя самого,
виде.
i) Если
ется
на
.
объясните,
752
Глава V
Example(mt
10)
=
у
data
{
int
=
у;
const
getIncrementedData()
static int getCount()
{
}
return
++data;
}
{
<<
cout
return
"Data
is
11
<<
<<
data
endl;
count;
}
private:
int data;
static int count;
};
Ответы
17.1.
к
упражнениям для самоконтроля
элемента, b) friend. с) new, указатель, d) иници¬
Статический,
ализирован. e)
f) this. g) Шаблоны классов, h) const.
i) конструктор по умолчанию, j) не-статическим. k) закрытым, за¬
щищенным. 1) параметризованными, m) до. n) delete.
а) инициализатора
17.2. а) Ошибка:
бождается
операцией new, осво¬
стандартной библиотечной функции языка С
память, динамически выделенная
с помощью
free.
Исправление: используйте для возвращения памяти операцию de¬
lete языка С++. Динамическое распределение памяти в стиле язы¬
ка С не следует смешивать с операциями С++ new и delete.
b) Ошибка:
Первая
в определении класса
объявлена
содержится две ошибки.
Example
функции getIncrementedData. Эта функция
модификатором const, однако она изменяет объект.
в
появляется
с
Исправление: чтобы
ошибку, уберите ключевое
функции getIncrementedData.
Ошибка: вторая ошибка находится в функции getCount. Эта функ¬
ция объявлена статической, поэтому ей не разрешен доступ к
слово
const
из
не-статическим
исправить первую
определения
элементам
Исправление: уберите
класса.
строку,
в
которой
выводятся
данные,
из
функции getCount.
определения
Упражнения
17.3.
Сравните динамическое распределение памяти с помощью опера¬
ций С++ new и delete и распределение памяти с использованием
стандартных
17.4.
Объясните
Может
языка
понятие дружественности в языке
цательные
17.5.
библиотечных функций
аспекты
как
дружественности,
ли корректное определение класса
следующих
Time(int
С malloc
free.
и
С++. Объясните отри¬
это
Time
описано
в
включать
книге.
оба
ниже¬
конструктора?
h
=
0,
int
m
=
0,
int
s
=
0);
Time();
17.6.
Что происходит, если для конструктора или деструктора определя¬
ется тип возвращаемого значения (даже void)?
17.7. Создайте
класс
Date
со
следующими
возможностями:
Классы: часть II
753
дату в нескольких форматах, например:
a) Выведите
DDD YYYY
MM/DD/YY
June 14, 1992
перегруженные конструкторы для создания объек¬
тов Date, инициализированных датами в форматах из пункта (а).
b) Используйте
c) Создайте
ную
дату,
конструктор класса Date,
используя
вочного
файла time.h,
В
18
главе
и
17.8.
одна
другой
дата
или
следует
систем¬
заголо¬
чтобы определить, предшествует
за ней.
Руководствуясь программой обработки списка на
стройте класс List. Этот класс должен обеспечивать
ности, что и в примере.
ной проверки
17.9.
считывает
мы сможем создавать операции для проверки двух дат
на равенство и для сравнения дат,
ли
который
библиотечные функции
устанавливает элементы Date.
стандартные
рис.
12.3,
по¬
те же возмож¬
Напишите программу-тестер для тщатель¬
класса.
Руководствуясь программой обработки очереди на рис. 12.13, по¬
стройте класс Queue. Этот класс должен обеспечивать те же воз¬
можности, что и в примере. Напишите программу-тестер для тща¬
тельной проверки класса.
17.10.Исйользуя
в качестве
отправной
точки класс
List, разработанный
в
упражнении 17.8, создайте шаблон класса List, способный созда¬
вать экземпляры объектов класса List для хранения различных ти¬
пов данных.
ки
Напишите программу-тестер для тщательной провер¬
шаблона.
17.11.Используя
ный
в
качестве
отправной
точки
класс
Queue, разработан¬
упражнении 17.9, создайте шаблон класса Queue, способный
создавать экземпляры объектов класса Queue для хранения раз¬
личных типов данных. Напишите программу-тестер для тщатель¬
в
ной проверки шаблона.
Scan AAW
г
л
в
а
а
18
Перегрузка операций
Цели
Понять принципы переопределения
операций
для ра¬
боты с новыми классами.
Понять,
как
другой
Изучить
разна,
объекты одного
класса
преобразуются
в
класс.
и
Изучить
случаи, когда перегрузка
когда
она
операций целесооб¬
нецелесообразна
несколько интересных классов, использующих
перегруженные операции.
756
Глава 18
Содержание
18.1.
Введение
18.2. Основные принципы перегрузки
18.3. Запреты на перегрузку
18.4.
Функции-операции
операций
операций
как элементы
класса
и
как
дружественные
функции
18.5. Перегрузка операций передачи
в
поток
Перегрузка
18.7.
Перегрузка двухместных операций
18.8.
Пример:
18.9.
Преобразование
18.10.Пример:
одноместных
класс
извлечения
из
потока
операций
18.6.
класс
и
Array
типов
String
18.11. Перегрузка ++ и
18.12.Пример:
Резюме
класс
Date
Распространенные ошибки программирования
программирования
ческие замечания
ям
для
Советы
по повышению
Упражнения для самоконтроля
Упражнения
В 16-й
и
Хороший стиль
Общие методи¬
Ответы
к упражнени¬
самоконтроля
18.1.
типами
эффективности
17-й
данных
Введение
главах мы познакомились с классами
(ADT). Операции
с
С++
и с
абстрактными
объектами класса (т.е. представителями
посылки сообщений объектам (в форме вы¬
функций-элементов). Нотация такого вызова функции становится гро¬
моздкой для определенных категорий классов, в особенности для математиче¬
ских. Чтобы оперировать с объектами таких классов, было бы удобно исполь¬
зовать широкий набор встроенных операций С++. В этом разделе мы пока¬
ADT) осуществлялись посредством
зова
жем,
цесс
как разрешить операциям
называется
С++ работать
перегрузкой операций
тем к расширению
С++
с
объектами
класса.
Этот про¬
и является прямым и естественным пу¬
за счет этих новых
возможностей.
757
Перегрузка операций
Операция
« в
С++
может иметь
различный
смысл и используется в каче¬
стве операции передачи в поток и операции сдвига влево.
женной операции. Операция
Это пример перегру¬
» также
перегружена, поскольку она использу¬
ется как операция извлечения из потока и как операция правого сдвига.
эти операции перегружаются в библиотеке классов
С++. Сам
по
Обе
себе С++ уже
перегружает операции +
и -. Эти операции выполняют различные функции,
в целочисленной арифметике,
которые зависят от контекста применения
арифметике чисел с плавающей точкой и арифметике указателей.
В целом С++ дает программисту возможность перегружать большинство
операций, так чтобы
няются. Компилятор
были чувствительны
они
к контексту, в котором приме¬
генерирует соответствующий
программный
код, исходя
данной операции. Некоторые операции перегружаются
особенно операции присваивания и различные арифметиче¬
из способа применения
достаточно часто,
ские операции, такие как + и -.
Действие,
выполняемое
перегруженной опера¬
обращения к функ¬
цией, может также быть выполнено посредством явного
ции, но, как правило, нотация операцци легче читается.
Мы обсудим случаи, когда следует использовать перегрузку операции и
когда этого делать не следует. Ниже будет приведено достаточное количество
законченных программ, которые иллюстрируют технику перегрузки опера¬
ций.
18.2. Основные принципы перегрузки
Программирование
С++
в
является
процессом,
операций
чувствительным
к
типу
Программист может использо¬
вать встроенные типы и может определять новые. Встроенные типы могут ис¬
пользоваться с широким набором операций С++. Операции дают программи¬
стам сжатую нотацию манипуляций с объектами встроенных типов.
Программист также может использовать операции с типами, определен¬
ными пользователем. Несмотря на то, что С++ не позволяет создавать новые
данных и ориентированным на типы данных.
операции,
он
позволяет
перегружать
существующие
операции
и,
когда
они
объектам класса, операции приобретают смысл, соответствую¬
типам. Это очень сильная сторона С++. Перегрузка операций спо¬
применяются к
щий
новым
расширяемости С++ и, несомненно, является одной из наиболее
привлекательных особенностей этого языка программирования.
собствует
Хороший стиль программирования 18.1
Используйте перегрузку операции, когда это помогает сделать программу яснее, чем
при выполнении тех же действий посредством явного вызова функции
Хороший
стиль программирования 18.2
Избегайте избыточного
ций,
так
как это
Несмотря
или
непоследовательного
может сделать
на то, что перегрузка
кая экзотическая возможность,
ют перегруженные операции.
венно различным
образом
использования
перегрузки
опера¬
программу трудно читаемой
операций
может восприниматься как не¬
большинство программистов
Например,
операция плюс
с целыми, числами с
неявно использу¬
(+) работает сущест¬
плавающей точкой и числами
758
Глава 18
с двойной точностью. Но плюс, тем не менее, прекрасно
ми типа
int, float
и
double,
скольку операция плюс
(+)
работает
с переменны¬
а также с числами других встроенных типов, по¬
перегружена уже
в самом языке
С++.
Операции перегружаются посредством написания обычного определения
функции (с заголовком и телом) за исключением того, что именем функции
становится ключевое слово
операции.
Например,
имя
operator с последующим символом перегружаемой
функции operator+ могло бы использоваться для
перегрузки аддитивной операции.
Чтобы использовать операцию
быть перегружена,
может использоваться с
любым
нию операции присваивания
чивается
с
объектами класса,
но здесь есть два исключения.
поэлементным
(в
классом
эта операция
Операция
присваивания
без перегрузки. Поведение
тех случаях, когда она не
должна
(=)
по умолча¬
перегружена) обеспе¬
класса. Далее мы
копированием элементов данных
увидим, что такая поэлементная копия, создаваемая по умолчанию,
представ¬
ляет опасность для классов, элементы которых ссылаются на динамически вы¬
деляемые
области памяти; для
жать операцию присваивания.
объектами любого
ся с
класса
адрес объекта в памяти.
таких классов мы, как правило,
Операция
адреса
без перегрузки;
Операция
(&)
будем
перегру¬
также может использовать¬
эта операция просто возвращает
адреса может быть также перегружена.
Перегрузка в наибольшей степени подходит для математических классов.
часто требуют перегрузки большого перечня операций, чтобы сохраня¬
лась согласованность со способом обработки математических объектов, кото¬
рые существуют в реальной жизни. Например, было бы странно перегружать
Они
только операцию суммирования для класса комплексных чисел, поскольку
другие арифметические операции над комплексными числами производятся
не менее часто.
Язык С++ содержит богатый набор операций. Программисты, работаю¬
щие с С++, понимают смысл и контекст каждой операции. Так что, если дело
доходит до перегрузки операций для нового класса, программист скорее всего
примет достаточно оправданное решение.
Ключевой пункт при выполнении перегрузки операции
обеспечение той
для типов, определяемых пользователем, кото¬
рую обеспечивает С++ для встроенных типов за счет богатого набора опера¬
же лаконичности
выражений
Перегрузка операций не является автоматическим процессом, и для вы¬
нужной процедуры программист должен написать перегружающую
операцию функцию. Иногда эту роль лучше выполняют функции-элементы,
ций.
полнения
иногда дружественные функции и, время от времени, эту роль могут выпол¬
нять функции, не являющиеся ни элементами, ни друзьями.
Неправильным случаем перегрузки была бы перегрузка операции + с це¬
лью выполнения операции, аналогичной вычитанию, или перегрузка / для
выполнения операции, аналогичной умножению. Такого рода перегрузка мо¬
жет существенно затруднить понимание программы.
Хороший
стиль программирования 18.3
Перегружайте операцию для выполнения тех же действий с объектами класса, или
близких по смыслу действий, которые выполняетэта операция со встроенными типами.
759
Перегрузка операций
Хороший
Перед
стиль программирования 18.4
тем
водство
как писать
программу
по вашему компилятору,
на
С++, перегружающую операции, изучите руко¬
чтобы учесть различные ограничения
и
требования,
накладываемые на конкретные операции
18.3.
Большая
Запреты
часть
на рис.
приведены
на
перегрузку
операций
операций С++ может быть перегружена. Эти операции
18.1. На рис. 18.2 приведены операции, которые не могут
перегружаться.
Операции, которые могут быть перегружены
+
-
I
~
*
/
%
^
=
<
>
+=
-=
=
«
»
/=
%=
^
<<=
==
i
=
&=
=
<=
>=
& &
->
[]
0
->*
l
&
I
=
_*
I
>>=
++
I
new
delete
_
Рис. 18.1.
Операции, которые
не могут
Операции, которые могут быть перегружены
быть перегружены
?;
*
.
Рис. 18.2.
sizeof
Операции, которые
не могут
быть перегружены
Приоритет операций не может быть изменен посредством перегрузки. Это
неудобным ситуациям, когда операция перегружается так,
ее фиксированный приоритет плохо соответствует смыслу выполняемых
может привести к
что
действий. Тем не менее, остается возможность использовать скобки для опре¬
операций в выражении.
операций также не может быть изменена посредством
Для перегрузки операций нельзя использовать аргументы по
деления порядка оценки перегруженных
Ассоциативность
перегрузки.
умолчанию.
Отсутствует
возможность изменить число операндов,
которое подразуме¬
Одноместная операция остается одноместной и при перегрузке,
а двухместная остается двухместной.
Единственная трехместная операция
С++, условная (?:), не может быть перегружена. Каждая из операций &, *, + и
имеет одноместную и двухместную формы, которые могут перегружаться
раздельно.
Нельзя создавать новые операции; только существующие операции могут
вает операция.
-
быть перегружены. Это запрещает программисту пользоваться такими попу¬
лярными нотациями, как операция
**, означающая
в степень.
Распространенная ошибка программирования 18.1
Попытка
создать новую операцию
в языке
Basic возведение
760
Глава 18
Способ
менен
работы операции
посредством
объектами,
ситуациях, когда
с
объектами встроенных
Например,
перегрузки.
способ суммирования
только с
с
операцией
типов не может
быть
из¬
программист не может изменить
+ двух целых.
Перегрузка операций работает
тип которых определен пользователем, или в смешанных
объект
объектом встроенного
пользовательского типа участвует в операции вместе
типа.
Распространенная ошибка программирования 18.2
Попытка модифицировать способ работы операции
Общее
с
объектами встроенных
типов
методическое замечание 18.1
По меньшей мере один аргумент функции-операции должен быть объектом класса
или должен ссылаться на объект класса Это делает невозможным изменение способа
воздействия операции на объекты встроенных типов
При перегрузке ( ), [ ], -> или
перегружающая операцию функция дол¬
жна объявляться как функция-элемент. Для других операций перегружаю¬
=
щие функции могут быть друзьями.
Перегрузка операции присваивания и операции суммирования с целью
разрешить такие операции, как
+
object2
=
object2
objectl;
не означает, что автоматически
будет
перегружена операция +=, чтобы выпол¬
нялся такой оператор, как
+=
object2
Однако
objectl;
такое поведение может быть достигнуто посредством явной пере¬
грузки операции += для этого класса.
Распространенная ошибка программирования 18.3
Предположение,
операции
явным
образом;
18.4.
что
перегрузка
неявная
операций (таких
как
как +) автоматически перегружает
+=). Операции могут быть перегружены только
перегрузка отсутствует.
Функции-операции
и как
Функции-операции
функции
(такие
присваивания
как элементы класса
дружественные
функции
могут быть функциями-элементами или не быть ими;
не элементы
обычно
являются друзьями.
Функции-элементы
ис¬
неявный указатель this, чтобы получить один из аргументов-объек¬
своего класса. Этот аргумент класса должен быть указан явным образом
пользуют
тов
при
вызове
функции,
Независимо
мент или как
не
от того,
функция,
являющейся
объявляется
элементом класса.
ли
функция-операция
как
функция-эле-
не являющаяся элементом, в выражении операция ис¬
образом. Так какой же вариант лучше?
Когда функция-операция объявляется в качестве функции-элемента,
пользуется одинаковым
ле¬
вый (или единственный) операнд должен быть объектом (или ссылкой на объ¬
ект), принадлежащим классу этой операции. Если необходимо, чтобы левый
761
Перегрузка операций
операнд был объектом другого класса или объектом встроенного типа, эта фун¬
кция-операция должна объявляться как функция, не являющаяся элементом
класса, так, как показано в разделе 18.5, где проиллюстрирована перегрузка
« и » в качестве операций передачи и извлечения из потока. Функции-опе¬
рации, объявляемой в качестве функции, не являющейся элементом класса,
нужно быть другом, если она должна получать прямой доступ к закрытым или
защищенным элементам класса.
Перегруженная операция « должна иметь в качестве левого операнда
тип ostream & (такой, как cout в выражении cout « classObject), так что она
должна
быть функцией,
не
являющейся
элементом класса.
» должна иметь в качестве левого операнда тип
ция
выражении cin »
Аналогично опера¬
как cin
istream & (такой,
функцией-элементом. Кроме
функций требует доступа к
элементам
класса
данных
объектов,
закрытым
который должен выводиться
или вводиться. Так что эти перегружающие операцию функции делаются
обычно дружественными функциями класса по причинам, связанным с
в
classObject)
и не
являться
того, каждая из этих перегружающих операцию
эффективностью.
Совет
по повышению
эффективности 18.1
можно перегрузить как функцию, не являющуюся элементом или дружест¬
венной функцией, но, если такой функции требуется доступ к закрытым или защи¬
щенным данным класса, то ей придется обращаться к открытому интерфейсу этого
класса (функциям вроде get или set)
Такое опосредованное обращение к данным
Операцию
может
привести
к
потере производительности.
элемент класса вызывается только тогда, когда ле¬
Функция-операция
вый операнд двухместной операции является объектом именно этого класса
или когда единственный операнд одноместной операции является объектом
этого класса.
Другая причина, по которой для перегрузки операции можно выбрать
требование коммутативности опера¬
функцию, не являющуюся элементом
ции. Например, у нас есть объект number типа long int и объект bigIntegerl,
принадлежащий к классу HugeInteger (класс, в котором целые могут быть
произвольно большими вне зависимости от ограничений разрядности аппарат¬
ной части; класс HugeInteger разрабатывается в разделе упражнений). Опера¬
ция суммирования (+) генерирует временный объект HugeInteger как сумму
HugeInteger и long int (согласно выражению bigIntegerl + number), или как
сумму long int и HugeInteger (согласно выражению number + bigIntegerl).
Таким образом, мы требуем, чтобы операция суммирования была коммутатив¬
на (как и в обычной ситуации). Проблема состоит в том, что объект класса дол¬
жен
стоять
в
левой
части
если
суммы,
операция
перегружена
как
функ-
образом, чтобы
разрешить HugeInteger находиться в пра¬
вой части суммы, мы перегружаем операцию как друга. Функция operator+,
ция-элемент. Таким
которая
обрабатывает HugeInteger
цией-элементом
.
с
левой стороны,
может оставаться
функ¬
762
Глава 18
18.5.
Перегрузка операций передачи
в поток
и извлечения из потока
В С++
имеется возможность ввода и вывода стандартных типов данных с
использованием
операций
потока »
извлечения из
и передачи в
поток
«.
(в библиотеке классов, поставляе¬
мых с компиляторами С++), и могут обрабатывать любой стандартный тип
данных, включая строки и адреса памяти. Кроме того, операции передачи и
Эти операции
являются
перегруженными
извлечения из потока могут быть перегружены, чтобы выполнять ввод и вы¬
вод типов,
определяемых пользователем.
На рис.
18.3 показана перегрузка
операций передачи и извлечения из потока, позволяющая им обрабатывать
данные класса телефонного номера (определенного пользователем) PhoneNumber. В этой программе предполагается, что телефонный номер введен кор¬
ректно. Проверку корректности ввода номера мы оставляем читателю в каче¬
стве упражнения.
(operator») принимает в каче¬
(input) на istream и ссылку (num) на тип, определяе¬
(PhoneNumber), и возвращает ссылку на istream. На
Функция-операция
извлечения из потока
стве аргументов ссылку
мый
пользователем
рис. 8.13 функция-операция operator» используется для ввода телефонного
номера
формате
в
(800)
555-1212
в объект класса PhoneNumber.
cin
>>
встречает в main выражение
Когда компилятор
phone
он генерирует вызов
функции
operator>>(cm, phone);
для
Когда
cin, а
выполняется
этот вызов,
ция использует функцию-элемент
тей телефонного номера
объекта
параметр
input
становится
параметр num становится псевдонимом для
типа
getline
класса
псевдонимом
phone. Функция-опера¬
istream для
чтения трех час¬
areaCode, exchange и line
функции-операции и phone в
в качестве строк в элементы
PhoneNumber (ссылки
num
в
main). Функция getline обсуждается детально в главе 21. Символы скобок,
пробела и тире пропускаются вызовом ignore (функции-элемента istream), ко¬
торая отбрасывает заданное число символов во входном потоке (один символ
по умолчанию). Функция operator» возвращает input (т.е. cin) как ссылку
на
istream. Это разрешает конкатенацию операций ввода объектов Phone¬
с операциями ввода других объектов PhoneNumber или объектов дру¬
Number
гих типов.
Например,
два объекта PhoneNumber могут вводиться следующим
образом:
cm
>>
phonel
Сначала
>>
phone2
выполнилось
бы выражение cin »
phonel
посредством вызова
функции
operator>>(cin,
phonel);
Этот вызов вернул бы затем cin как значение выражения
так
что
оставшаяся
cin>>phone2.
часть
выражения
интерпретировалась
cin>>phonel,
бы
просто
как
763
Перегрузка операций
// FIG18_3.CPP
// Перегрузка операций
//
и извлечения
поток
<iostream.h>
#include
class
передачи в
потока
из
PhoneNumber
{
friend
ostream
&operator<<(ostream
&,
const
friend
istream
&operator>>(istream
&,
PhoneNumber
private:
char areaCode[4];//
char exchange[4];//
char
&)
&);
3-цифры и null
3-цифры
line[5];// 4-цифры
и
и
null
null
операции передачи в
// Перегрузка
PhoneNumber
поток
может
(не
быть
//
функцией-элементом).
ostream &operator<<(ostream
const
&output,
PhoneNumber
&num)
{
<<
output
«
"("
<<
"
num.areaCode
<<
")
"-"
<<
num.line;
«
num.exchange
return output;
cout
// разрешить
<<
«
а
b
<<
с;
}
// перегрузка операции
istream
извлечения
из
&operator>>(istream &input,
потока
PhoneNumber &num)
{
input.ignore();
input.getline(num.areaCode,
input.ignore(2);
input.getline(num.exchange,
input.ignore() ;
input;
4);
//пропуск (
//ввести area code
//пропуск ) и пробела
//ввод exchange
//пропустить тире (-)
//ввести
5);
input.getline(num.line,
return
4);
//
разрешить
//
создать
cin
>>
а
line
>>
b
>>
с;
main ()
{
PhoneNumber
cout
«
<<
>>
cin
//
вызывая
>>
<<
cout
//
вызывая
<<
<<
return
(123)
phone
phone
number
объект
in
the
phone
"
456-7890:\n";
вызывает
функцию operator>>
operator>>(cin,
phone).
phone;
//
cout
а
"Enter
"form
//
cin
phone;
phone
вызывает
функцию operator<<
operator<<(cout, phone).
"The phone number entered was:\n"
phone
«
endl;
0;
Рис. 18.3. Определяемые пользователем операции передачи и извлечения из потока
(часть 1 из 2)
764
Глава 18
а phone number in the
form
(800) 555-1212
The phone number entered was:
(800) 555-1212
(123)
456-7890:
IEnter
Рис. 18.3. Определяемые
пользователем операции передачи и извлечения из потока
(часть 2
из
2)
Операция передачи в поток принимает в качестве аргументов ссылку (out¬
put) на osrteam и ссылку (num) на тип, определяемый пользователем (Phone¬
Number), и возвращает ссылку на ostream. Функция operator<< выводит объ¬
екты типа PhoneNumber.
cout
<<
Когда компилятор встречает
в
main выражение
phone
он генерирует вызов
функции
operator<<(cout, phone);
Функция operator<< выводит части телефонного номера как строки, т.к.
они хранятся в формате строки (функция-элемент getline из istream записы¬
вает нулевой символ после завершения ввода).
Обратите внимание, что функции operator» и operator<< объявляются в
классе PhoneNumber в качестве дружественных функций, не являющихся
Эти операции не могут быть
PhoneNumber всегда появляются в
объекты
элементами.
элементами
класса
операции в качестве правого опе¬
ранда; операнд класса должен стоять в левой части,
вывода должны
можно
было пере¬
функцию-элемент. Перегруженные операции ввода и
объявляться как друзья, если им необходимо иметь прямой
к элементам класса, не
Общее
чтобы
т.к.
как
гружать операцию
доступ
класса,
объявленным
как
public.
методическое замечание 18.2
Новые возможности
введены в С++ без
ввода/вывода типов, определяемых пользователем, могут быть
модификации объявлений или элементов данных private в лю¬
бом
из классов ostream или istream Это способствует расширяемости С++ как языка
программирования, что является одним из наиболее привлекательных аспектов С++.
18.6.
Перегрузка
одноместных
Одноместная операция для класса
функция-элемент, не имеющая
ческая
ющаяся
элементом
и
имеющая
один
операций
может быть перегружена как не-статиаргументов, или как
аргумент.
функция,
не явля¬
Этот аргумент должен быть
либо объектом класса, либо ссылкой на него.
Позже в этой главе мы будем перегружать одноместную операцию !, чтобы
проверить, является ли строка пустой. При перегрузке одноместной операции,
например, ! в качестве не имеющей аргументов не-статической функции-эле¬
мента компилятор, когда он встречает выражение
!s, генерирует
rator!(). Операнд s является объектом класса, для которого
ция-элемент operator!:
class
String
{
public:
int operator!()
const;
вызов s.ope-
вызывается
функ¬
765
Перегрузка операций
Одноместная операция, такая как !, может быть перегружена в качестве
функции, не являющейся элементом и имеющей один аргумент, двумя раз¬
личными способами: либо с аргументом, являющимся объектом, либо с аргу¬
ментом, который ссылается на объект. Если s является объектом класса, то за¬
пись !s воспринимается точно так же, как вызов operator!(s).
class
String {
friend int operator!(const
String
&);
};
Хороший
стиль программирования 18.5
При перегрузке
одноместных
операций предпочтительнее делать функции-операции
функциями, не являющимися элементами.
элементами класса, а не дружественными
более аккуратное решение. Нужно избегать дружественных функций и дружест¬
венных классов, если только это не является крайне необходимым Использование
друзей нарушает инкапсуляцию класса.
Это
18.7.
Перегрузка двухместных операций
Двухместная операция может быть перегружена как не-статическая функция-элемент с одним аргументом или как функция, не являющаяся элемен¬
том и имеющая два аргумента (один из этих аргументов должен быть либо
объектом класса, либо ссылкой на него).
Позже в этой главе мы будем перегружать операцию +=, чтобы выполнять
объектов-строк. Если у и z являются объектами класса,
то, когда двухместная операция += перегружается как не-статическая функ¬
ция-элемент класса String с одним аргументом, выражение у += z восприни¬
конкатенацию двух
мается точно так же, как
class
String
y.operator+=(z).
{
public:
String
&operator+=(const
String
&);
};
операция += может быть перегружена как функция с двумя
не
являющаяся элементом. Один из аргументов должен быть
аргументами,
объектом класса или ссылкой на объект. Если у и z являются объектами клас¬
Двухместная
са, тогда у += z воспринимается в программе точно так же, как вызов opera-
tor+=(y, z).
class
String {
friend int &operator+=(String
&,
const
String
&);
};
18.8.
Пример:
класс
Array
Нотация массива в С++ является просто альтернативой указателям, поэто¬
му массивы никак не защищены в отношении возможных ошибок. Например,
программа может просто «проскочить» массив, поскольку С и С++ не проверя¬
ют, вышел индекс за пределы массива или нет.
В
массиве размера n элементы
766
Глава 18
должны нумероваться следующим образом: 0,..., п-1. Альтернативные пределы
индексации не разрешены. За один раз нельзя ввести или вывести массив в це¬
лом;
каждый
массива
Когда
элемент должен считываться и записываться индивидуально.
нельзя
сравнивать,
массив передается
используя
операцию
функции общего
равенства
назначения,
или
Два
отношения.
предназначенной
для об¬
любого размера, размер массива должен быть передан в каче¬
стве дополнительного аргумента. Один массив не может быть присвоен другому
массиву через операцию присваивания. Подобные свойства и действия выгляде¬
работки
массивов
ли бы «естественно» при
зация.
Однако
С++
в
работе
с массивами, но в
С
и
С++ отсутствует
имеется возможность реализовать
их реали¬
подобные свойства
операций.
разработаем класс массива, который
мас¬
сивов посредством механизма перегрузки
В
следующем примере мы
проверку индексации,
массива.
Этот
чтобы убедиться,
выполняет
что она остается в заданных границах
класс позволяет использовать операцию присваивания для при¬
сваивания одного
объекта-массива другому. Объекты массива этого класса ав¬
свой размер, поэтому нет необходимости передавать его в
томатически знают
качестве аргумента при передаче массива
жет
быть введен
функции. Весь массив
операций извлечения
или выведен при помощи
операции передачи в
поток.
Сравнение
в целом мо¬
из потока и
массивов может осуществляться при
или !=. Наш класс массива использует статические эле¬
операций
менты, чтобы отслеживать число объектов-массивов, которые были созданы в
программе. Этот пример улучшит ваше понимание абстрактных данных. Ве¬
==
помощи
вы сможете предложить много расширений такого класса массива.
это интересная задача, требующая немалых интеллекту¬
Разработка класса
альны x усилий.
Программа, приведенная на рис. 18.4, демонстрирует класс Array и его
перегруженные операции. Сначала мы пройдемся по программе-тестеру в
роятно,
main.
Затем
мы
рассмотрим
определение
класса,
определения
его
функ¬
Программа создает
два объекта класса Array
integersl с семью элементами и integers2, имеющий,размер 10 элементов по умолчанию (значение по умолчанию определено
конструктором Array). Статический элемент данных arrayCount класса Array
содержит число объектов Array, созданных во время выполнения программы.
Статическая функция-элемент getArrayCount возвращает это значение. Функция-элемент getSize возвращает размер массива integersl. Программа выво¬
дит размер массива integersl и выводит сам массив, используя перегружен¬
ций-элементов
и
определения дружественных функций.
ную операцию передачи в поток для подтверждения того, что элементы масси¬
ва были корректно инициализированы конструктором. Затем выводится раз¬
мер
массива
integers2
и выводится сам массив с помощью
перегруженной
опе¬
рации передачи в поток.
Пользователю предлагается
оба
массива
используется
ввести
17 целых. Для
перегруж.енная
операция
чтения этих
значений
извлечения
из
в
потока,
выполняемая оператором
cm
>>
integersl
>>
integers2;
integersl, а остальные значения в
integers2. Чтобы подтвердить корректность ввода, эти два массива выводятся
операцией передачи в поток.
Далее программа проверяет перегруженную операцию неравенства вычис¬
Первые
семь значений сохраняются в
лением условия
mtegersl
и
сообщает,
!=
integers2
что два массива
действительно
не равны.
767
Перегрузка операций
//
ARRAYl.H
//
Простой
#ifndef
класс
(для
Array
целых)
arrayl_h
ARRAYl_H
#define
<iostream.h>
#include
class
Array {
friend ostream
&operator<<(ostream
&,
friend istream
&opefator>>(istream
&,
10);
Array(const Array &);
-Array();
int getSize() const;
const Array &operator=(const Array
//
//
//
//
const Array &);
Array &);
public:
=
Array(int
int operator==(const Array &)
int operator!=(const Array &)
int
// указатель
//
size;
int
static
на
по
конструктор
копии
const;
const;
умолчанию
деструктор
size
возврат
&);
// присваивание
// массивов
//
//
//
сравнение
сравнение
//
возврат
//
созданных
на
равенство
на
!равенство
// операция индексации
int &operator[](int);
static int getArrayCount();
private:
int *ptr;
конструктор
первый
элемент
числа
массивов
массива
массива
размер
arrayCount;
//
число
созданных
массивов
};
#endif
Рис.18.4. Определение класса Array (часть 1 из 7)
//
ARRAY1.CPP
//
Определения функций-элементов
класса
Array
<iostream.h>
<stdlib.h>
<assert.h>
#include
#include
#include
#include
"arrayl.h"
// Инициализация статических данных в области действиячфайла
int Array::arrayCount
// пока объектов нет
0;
=
//
Возврат
числа
созданных
int Array::getArrayCount()
Array
return arrayCount;
объектов
{
}
// Конструктор по умолчанию для класса Array
Array::Array(int arraySize)
{
// приращение счетчика
++arrayCount;
=
size
ptr
=
arraySize;
new
assert(ptr
Рис. 18.4.
int[size];
!= 0);
// размер
по
на
-
умолчанию
один
объект
10
// создать свободное место для массива
// выход, если размещения памяти
// не произошло
Определения функций-элементов
класса
Array (часть 2
из
7)
768
Глава 18
for
i
(mt
//
0;
0;
=
=
ptr[i]
i
<
копии
Конструктор
size;
для
и
инициализировать
массив
Array
класса
// приращение счетчика на один объект
// размер этого объекта
// создать свободное место для массива
// выход, если памяти не размещена
++arrayCount;
size
init.size;
new
int
[size];
ptr
assert(ptr != 0);
=
=
(int
0;
=
.i
=
ptr[i]
//
)
//
(const Array &init)
Array::Array
for
i++
<
size;
класса
для
Деструктор
i
i++)
// скопировать init
imt.ptr[i];
в
объект
Array
Array::~Array()
--arrayCount;
delete [] ptr;
//
int
//
int
Получить размер
одним
//
восстановить
меньше
объектом
место
для
массива
массива
const
Array::getSize()
Перегруженная
//
return
{
size;
}
индексации
операция
&Array::operator[](int subscript)
{
// проверка
выхода
<=^
assert(0
за
индекса
subscript
&&
return ptr[subscript];
//
массива
пределы
<
subscript
возврат
size);
ссылки
создает
lvalue
}
//
Определение равенства двух
//
1,
int
если
условие
выполнено
массивов
и
0,
если
и
возврат
не
выполнено,
const
Array::operator==(const Array &right)
{
if
!=
(size
return
for
return
//
//
int
//
массивы
различных
i
0; i < size; i++)
!- right.ptr[i])
(ptr[i]
return 0; // массивы не
размеров
=
(int
if
right.size)
0;
//
1;
массивы
равны
равны
Определение неравенства двух массивов и возврат
1, если условие выполнено и 0, если не выполнено,
Array::operator!=(const Array &right)
const
{
if
!=
(size
return
Рис. 18.4.
1;
right.size)
//
массивы
различных
Определения функций-элементов
размеров
класса
Array (часть 3
из
7)
769
Перегрузка операций
for
0; i < size; i++)
(int i
if (ptr[i] != right.ptr[i])
return 1;
// массивы
=
//
0;
return
массивы
не
равны
равны
}
// Перегруженная операция присваивания
const Array &Array::operator=(const Array
{
if (&right != this)
// проверка на
{
[]
delete
=
size
ptr;
right.size;
new
i
(int
for
=
=
ptr[i]
0;
i
<
самоприсваивание
//
восстановить
//
изменить
место
свободное
этого
размеры
объекта
// создать место для копии массива
// выход, если память не размещена
int[size];
assert(ptr != 0);
=
ptr
&right)
size;
right.ptr[i];
i++)
// скопировать
в
массив
объект
}
return
*this;
// разрешить
x=y=z;
}
//
//
Перегруженная операция ввода для
ввод
istream
значений
всего
для
класса
Array;
массива,
&operator>>(istream &input,
Array &a)
{
for
(int i=0; i< a.size;
input >> a.ptr[i];
return
i++)
// разрешить cin
input;
>>
x
>>
у;
}
// Перегруженная операция вывода для класса Array
ostream &operator<<(ostream &output, const Array &a)
{
for (int i
0; i < a.size; i++) {
=
<<
output
( (i + l)
if
output
a.ptr[i]
%
<<
10
==
<<
'
';
0)
endl;
}
if
(i
%
10
output
!= 0)
endl;
<<
return output;
// разрешить
cout
<<
x
<<
у;
}
Рис. 18.4 Определения функций-элементов класса Array (часть 4
Программа создает третий массив, названный integers3,
ет его массивом
integersl. Программа
водит сам массив,
бы подтвердить,
конструктором.
25
Зак.801
и
выводит размер массива
из
инициализиру¬
integers3
используя перегруженную операцию передачи
что
элементы
массива
7)
и вы¬
в поток, что¬
были корректно инициализированы
Глава 18
770
//
//
FIG18_4.CPP
Тестер для простого
Array
класса
#include <iostream.h>
#include "arrayl.h"
main (
{
//
)
пока
cout
объектов
"#
<<
<<
of
нет
instantiated
arrays
Array::getArrayCount()
"
=
'\n';
<<
// создать два массива и вывести значение
Array integersl (7), integers2;
cout << "# of arrays instantiated
//
вывести
cout
и
содержимое
"
is
<<
integersl.getSize()
"\nArray after initialization:\n"
integersl;
вывести
array
integersl
integers2
размер
и
содержимое
"
is
<<
"\nSize
<<
integers2.getSize()
"\nArray after initialization:\n"
integers2;
<<
<<
//
of
"\n\nSize
<<
//
integersl
размер
;
<<
<<
cout
Array::getArrayCount()
of
array
Array
"
=
<<
счетчика
integers2
вывод integersl и integers2
"\nInput 17 integers:\n";
cin » integersl >> integers2;
the arrays contain:\n"
cout << "After input,
<<
<< integersl
"integersl:
<<
<< integers2;
"integers2:
ввод
cout
и
<<
"
"
//
создать
//
инициализатора;
Array
cout
integers3
<<
<<
//
if
используя
размер
и
of
в
качестве
integers3;
"
array
after
"\nArray
<<
<<
integersl
содержимое
(integersl);
<<
initialization:
\n"
перегруженную операцию неравенства(!=)
!= integers2\n";
"\nEvaluating: integersl
!= integers2)
(integersl
cout
//
вывести
integers3 is
integers3.getSize()
"\nSize
использовать
cout
integers3,
массив
<<
"They
использовать
cout
<<
<<
<<
egual\n\n";
перегруженную
"Assigning
integers2
операцию присваивания
to integersl: \n";
=
integers2;
"integersl:
"mtegers2:
integersl
cout
not
are
"
"
<<
<<
integersl
integers2;
Рис. 18.4 Тестер для
класса
Array (часть 5
из
7)
(=)
771
Перегрузка операций
//
использовать
cout
перегруженную
равенства(==)
integers2\n";
операцию
<<
"\nEvaluating: integersl
if (integersl
mtegers2)
cout << "They are equal\n\n";
==
==
//
использовать
//для
cout
<<
перегруженную
индексации
операцию
rvalue
создания
"integersl[5]
"
is
<<
integersl[5]
// использовать перегруженную операцию
// для создания lvalue
cout << "Assigning 1000 to integersl[5]
1000;
integersl[5]
cout << "integersl:
<<
endl;
индексации
\n";
=
//
попытка
"
<<
использовать
integersl;
индекс,
выходящий
за
размеры
массива
"\nAttempt to assign 1000 to integersl[15] \n";
// ОШИБКА: выход за границы массива
1000;
mtegersl[15]
cout
<<
=
0;
return
}
Рис. 18.4 Тестер для
Array (часть б
класса
из
7)
Затем программа проверяет перегруженную операцию присваивания (=)
средством выражения
integersl
=
integers2. Оба
Интересно
на печать для подтверждения правильности присваивания.
что
integersl
массива,
увидим,
содержит изначально 7 целых
чтобы
10
он сохранял копию
и
по¬
массива после этого выводятся
требуется
отметить,
изменение размера этого
элементов из массива
integers2. Как
мы
перегруженная операция присваивания выполняет изменение размера
вызвавшей операцию программы образом.
Затем программа применяет перегруженную операцию равенства (==),
чтобы убедиться, что объекты integersl и integers2 после выполнения опера¬
массива скрытым от
действительно идентичны.
Далее программа использует перегруженную
ссылки на элемент integersl[5], находящийся в
gersl. Это индексированное имя используется как
ции присваивания
integersl[5]
своить
и как
lvalue
новое значение
с
операцию индексации для
пределах inte¬
rvalue для печати значения
левой стороны операции присваивания, чтобы при¬
1000
элементу
допустимых
integersl[5]. Обратите
внимание, что
operator[]
возвращает ссылку.
После этого программа пытается присвоить значение 1000 элементу inte-
gersl[15], который выходит за границы массива. Программа перехватывает
ошибку и прекращает работу аварийно.
Интересно, что операцию индексации [] не запрещено перегружать и для
других целей: она может использоваться для выбора элементов из других
видов контейнерных классов, таких, как связанные списки, строки, словари
и т.д.
гут
Индексам
быть
также не
обязательно быть
только целыми, например, они мо¬
символами или строками.
Теперь,
когда
мы
разобрались
пройдемся по заголовкам классов
и
работой главной
с
программы, давайте
определениям функций-элементов.
ки программы
int
*ptr;
int
size;
static
25*
int
// указатель на первый
// размер массива
arrayCount;//
число
элемент
созданных
массива
массивов
Стро¬
Глава 18
772
объявляют закрытые элементы данных. Массив содержит указатель ptr
лается на
щий
соответствующий
число
в
элементов
показывающий
тип, в данном случае
массиве,
число созданных
# of arrays instantiated
#
of
instantiated
arrays
Size
элемент
статический
size,
элемент
(ссы¬
показываю¬
arrayCount,
объектов-массивов.
=
0
=
2
of
array integersl is 7
after initialization:
Array
0 0 0
Size
0
0
0
0
of
array integers2 is 10
after initialization:
Array
0 0 0
0
0
17
Input
1 2 3
4
5
0
0
0
0
0
integers:
6 7 8 9 10
After
the
input,
integersl: 1
2
3
8
9
10
integers2:
11
12
13
14
15
16
16
17
17
arrays contain;
4 5 6 7
11
12
13
14
15
of
Size
array integers3 is 7
after initialization;
Array
1 2 3
4
5
7
6
!=
Evaluating: integersl
They are not equal
Assigning
integersl:
8
Evaluating:
are
integers2
integers2 to integersl:
8 9 10 11 12 13 14 15 16
integers2:
They
и
int),
9
10
11
12
13
==
integersl
14
15
16
17
17
integers2
equal
integersl[5] is 13
Assigning 1000 to integersl[5]
integersl: 8 9 10 11 12 1000 14
Attempt to assign
Assertion failed:
1000
0
<=
to
15
16
17
integersl[15]
&& subscript <
subscript
98
size,
file ARRAYl.CPP, line
Abnormal program termination
Рис. 18.4 Вывод программы-тестера для класса
Array (часть 7
Строки программы
friend ostream &operator<<(ostream &, const Array
friend istream &operator>>(istream &, Array &);
объявляют перегруженные операции передачи
как
друзей
cout
класса
<<
он вызывает
Array. Когда
из
7)
&);
в поток и извлечения из потока
компилятор встречает выражение, аналогич-
arrayObject
функцию operator<<, генерируя
вызов
773
Перегрузка операций
operator<<(cout, arrayObject)
Когда компилятор встречает выражение,
cin
>>
аналогичное
arrayObject
он вызывает
функцию operator», генерируя
operator>>(cin,
вызов
arrayObject)
Мы еще раз отметим,
функции-операции не могут быть элемента¬
объект, принадлежащий Array, всегда находится с
правой стороны операций передачи и извлечения из потока. Функция opera¬
tor<< выводит число элементов, определяемое size, из массива, хранящегося
ми класса
Array,
что эти
так как
по адресу ptr. Функция operator» осуществляет ввод данных прямо
сив,
хранящийся
по адресу
ет соответствующую
ptr. Каждая
ссылку,
их
в
мас¬
этих*функций-операций возвраща¬
чтобы сделать возможной конкатенацию
вызо¬
вов.
Строка
программы
Array(int arraySize
объявляет конструктор
умолчанию
=
10);
// конструктор
по
умолчанию
класса по умолчанию и определяет размер массива по
(10 элементов). Когда компилятор встречает такое
объявление, как
Array integersl (7);
или эквивалентную запись
Array integersl
7;
=
он вызывает конструктор по умолчанию.
Конструктор по умолчанию
функдействует следующим образом: увеличивает значение
Array
arrayCount; копирует аргумент в si^e (элемент данных); использует
ция-элемент
счетчика
new для выделения места под хранение внутреннего представления этого мас¬
сива и присваивает ptr значение указателя, возвращаемого new; вызывает asвызова new;
для
проверки
успешности
организует цикл for для
инициализации всех элементов массива значением 0. Было бы также вполне
допустимым определить такой класс Array, который не проводит инициализа¬
sert
цию своих элементов, если, например,
требуется, чтобы
эти элементы считыва¬
лись позднее.
Строка
программы
!
Array(const Array &);// конструктор
копии
конструктором копии. Он инициализирует объект Array копирова¬
объекта Array. Такое копирование должно выполняться
осторожно, чтобы избежать ловушки получения двух объектов Array, указы¬
является
нием существующего
вающих на одну и ту же динамически выделенную память, т.е. здесь та же са¬
мая проблема, что и при поэлементном копировании элементов данных по
Конструктор копии вызывается всякий раз, когда необходимо ко¬
объекта: при вызове по значению, при возврате объекта из вызван¬
ной функции; при инициализации, когда один объект копируется в другой
умолчанию.
пирование
объект
того же класса.
создается объект
Array,
Конструктор копии вызывается при объявлении, когда
Array и инициализируется другим объектом класса
класса
как в следующем
Array integers3
объявлении:
(integersl);
или эквивалентном ему
Array integers3
=
integersl;
774
Глава 18
Конструктор копии
функция-элемент Array
увеличивает значение
arrayCount; копирует из массива значение size, использующееся для
счетчика
инициализации массива, в элемент данных size; использует new для получе¬
ния свободного места под хранение внутреннего представления этого массива
и присваивает ptr значение указателя, возвращаемого new; использует assert
для проверки успешности вызова new; использует цикл
всех элементов массива-инициализатора в этот массив.
если конструктор копии просто копирует
for для копирования
Важно отметить, что,
ptr объекта-источника
в
ptr
создава¬
емой копии, то оба объекта могут указывать на одну и ту же область динами¬
чески размещаемой памяти. Первый вызов деструктора должен был бы после
этого очистить динамически выделенную память, и другие
рые
ссылается
ptr,
стали
вызывает серьезную
Хороший
ошибку при
и
конструктор
копии класса
обычно реализуются
группа
программы
//
~Array();
деструктор
объявляет деструктор класса.
существование
объекта
значение счетчика
Деструктор вызывается автоматически, когда
Array заканчивается. Деструктор уменьшает
класса
arrayCount
использует delete для освобождения динами¬
и
ческой памяти, выделенной new
int
всего
стиль программирования 18.6
как единая
Строка
на кото¬
выполнении программы.
Деструктор, операция присваивания
Строка
объекты,
бы неопределенными. Такая ситуация чаще
в
конструкторе.
программы
getSize()
const;//
возвращаемый размер
объявляет функцию, которая читает размер массива.
Строка
программы
const Array
&operator=(const Array
&);
//присваивание
объявляет перегруженную функцию-операцию присваивания
массива
класса.
Когда
компилятор встречает выражение
integersl
он вызывает
=
integers2
функцию operator=, генерируя
вызов
integersl.operator=(integers2)
Функция-элемент operator= производит проверку
на самоприсваивание.
Если делается попытка самоприсваивания, операция пропускается (т.е. если
объект уже является сам собой). Если это не самоприсваивание, тогда функция-элемент использует delete для освобождения памяти, первоначально отве¬
денной под массив-приемник; копирует из массива-источника значение size в
size массива-копии; использует
присваевает указатель,
ет
new для выделения места под массив-копию и
возвращаемый
assert для проверки успешности
массива-источника в
рования
элементов
от
самоприсваивание
того,
вызова свой
new, элементу
это
объект (т.е. *this)
ptr приемника;
вызова new; использует цикл
или
нет,
массив-приемник.
Вне
вызыва¬
for для
копи¬
зависимости
функция-элемент возвращает
после
как константную ссылку; это разрешает конка¬
присваиваний Array, таких, как x=y=z. Если бы контроль самоприс¬
функция-элемент не могла бы начинаться с освобож¬
дения памяти массива-приемника. Поскольку при самоприсвоении он также и
тенацию
ваивания был пропущен,
массив-источник, этот массив не должен быть разрушен.
775
Перегрузка операций
Распространенная ошибка программирования 18.4
Отсутствие проверки и обхода самоприсваивания при перегрузке операции присваи¬
вания класса, который содержит указатель на динамическую память.
Распространенная ошибка программирования 18.5
Отсутствие перегруженной операции присваивания и конструктора копии класса, ког¬
да объекты этого класса содержат указатели на динамическую размещаемую память.
Общее методическое замечание 18.3
Имеется возможность избежать присваивания одного объекта класса другому. Это
можно сделать, определив операцию присваивания как закрытый элемент класса.
Строка
программы
int operator==(const Array
const;
&)
//
сравнение
объявляет перегруженную операцию равенства (==)
встречает в main выражение
integersl
он вызывает
==
класса.
на
равенство
Когда компилятор
integers2
функцию-элемент operator==, генерируя
вызов
integersl.operator==(integers2)
Функция-операция operator== сразу возвращает 0 (false),
если размеры
size различны. В противном случае функция-элемент срав¬
пару элементов. Если все они одинаковы, возвращается 1
элементов массива
нивает
каждую
элементов сразу приводит к возврату
(true). Первая пара отличающихся
0.
Строка программы
int operator!=(const Array &)
const;
// сравнение
на
!равенство
объявляет перегруженную операцию неравенства (!=) класса. Вызывается фун¬
кция-элемент operator!=, которая работает аналогично перегруженной функ¬
ции-операции проверки на равенство.
Строка
программы
int operator[](int);//onepaunn индексации
объявляет перегруженную операцию индексации класса. Когда компилятор
встречает в main выражение
integersl[5]
он вызывает
функцию-элемент operator[], генерируя
вызов
mtegersl.operator[] (5)
Функция-операция operator[] проверяет, находится
ли индекс в допусти¬
завершается. Если индекс име¬
ет допустимое значение, возвращается соответствующий элемент массива как
ссылка, которая может использоваться как lvalue (например, с левой стороны
мых пределах и, если нет,
программа
аварийно
операции присваивания).
Строка программы
static int getArrayCount();
// возврат
объявляет статическую функцию-элемент
значение статического элемента данных
объектов класса
Array.
счетчика
массивов
getArrayCount, которая возвращает
arrayCount, даже если не существует
776
Глава 18
18.9.
Преобразование
типов
Большинство программ обрабатывают информацию разных
все операции «остаются в том же типе».
лым дает целое
(до
Например,
типов.
Иногда
суммирование целого с це¬
тех пор, пока результат не становится слишком
большим,
значением). Однако
часто воз¬
когда он уже не может быть представлен целым
необходимость преобразования данных одного типа в данные другого
типа. Это может происходить при присваивании, в вычислениях, при передаче
значений функциям и при возврате значений функциями. Компилятор знает,
никает
преобразование для встроенных типов. Программист может
преобразованиями встроенных типов посредством приведения типов.
Но как насчет типов, определяемых пользователем? Компилятор не может
автоматически знать, как проводить преобразования между типами, опреде¬
ляемыми пользователем, и встроенными типами. Программист должен опре¬
делить в явном виде, каким образом проводить такие преобразования. Преоб¬
как
выполнять
управлять
разования такого рода могут проводиться с использованием конструктора
преобразований,
т.е. конструктора с одним аргументом,
который просто пре¬
будем использо¬
вращает объект некоторого типа в объект данного класса. Мы
вать
конструктор
преобразования
обычных строк символов char
*
в
позже
объекты
Операция преобразования (также
жет использоваться для
в
этой главе для преобразования
С++ String.
класса
называемая
преобразования объекта
операцией приведения)
го класса или в объект встроенного типа. Такого рода операция
ния должна
может
мо¬
одного класса в объект друго¬
быть не-статической функцией-элементом;
преобразова¬
этот вид операции не
быть функцией-другом.
Прототип функции
operator char *()
const;
объявляет перегруженную функцию-операцию приведения для создания вре¬
менного объекта char *, не принадлежащего типу объекта, определяемого по¬
льзователем. Перегруженная функция-операция приведения не специфициру¬
преобразуется данный объект). Ког¬
(char *) s, он генерирует вызов s.operator
char *(). Операнд s является объектом класса s, для которого вызывается функ¬
ция-элемент operator char *.
Перегруженная функция-операция приведения может определяться с це¬
лью преобразования объектов типов, определяемых пользователем, в объекты
ет возвращаемый
тип
(т.е.
тот, к которому
да компилятор встречает выражение
встроенных типов или в объекты других типов,
созданных пользователем.
Прототипы функций
operator int() const;
operator otherClass()
const;
объявляют перегруженные функции-операции приведения для преобразова¬
ния объектов типа, определяемого пользователем, в целое, и для преобразова¬
ния
объектов типа, определяемого пользователем,
в
объекты
типа
otherClass,
также созданного пользователем.
Одной их замечательных возможностей операций приведения и конструк¬
преобразования является то, что при необходимости компилятор может
автоматически вызывать эти функции, чтобы создавать временные объекты.
Например, если объект s класса String, определенного пользователем, появля¬
торов
ется в программе в том месте, где предполагается обычная строка char *,
например
777
Перегрузка операций
cout
<<
s;
компилятор вызывает перегруженную функцию-операцию приведения ope¬
rator char *, чтобы преобразовать этот объект в char * и использовать в выра¬
жении полученную строку.
то
18.10.
Пример:
класс
В целях исследования перегрузки операций
String
мы построим класс,
который
управляет созданием и обработкой строк. Ни в С, ни С++ не имеется встроен¬
ного типа строковых данных. Но С++ позволяет нам ввести свой собственный
тип в качестве класса и, посредством механизма перегрузки, определить набор
операций
удобной обработки строк. На рис. 18.5 показаны различные час¬
String: заголовок класса, определения функций-элементов и про-
для
ти класса
грамма-тестер для проверки класса.
Сначала мы рассмотрим заголовок
String. Мы обсудим закрытые
String. Затем мы прой¬
обсуждая каждый вид услуг, предо¬
класса
данные, использующиеся для представления объектов
демся по открытому интерфейсу класса,
ставляемых классом.
После этого мы рассмотрим программу-тестер в main. Мы
обсудим
стиль
программирования, к которому мы стремимся, т.е. к насыщенным операция¬
ми выражениям, которые мы могли
бы
составить из
объектов
нашего класса
String и набора перегруженных операций.
Затем мы разберем определения функций-элементов класса String. Для
каждой из перегруженных операций мы приведем программу-тестер, которая
вызывает перегруженную функцию-операцию и объясним, как она работает.
Мы начнем со внутреннего представления String. Строки программы
private:
char *sPtr;
int
// указатель
//
length;
объявляют закрытые
длина
на
начало
строки
строки
элементы данных класса.
Объект String
имеет указатель
на динамически распределяемую память для представления строки символов и
поле длины, которое представляет число символов в строке, исключая ограни¬
чивающий строку нулевой символ.
Теперь перейдем к заголовочному файлу
класса
String (рис. 18.5). Строки
программы
friend ostream &operator<<(ostream &,
friend istream &operator>>(istream &,
const String
String &);
&);
объявляют перегруженную функцию-операцию передачи в поток
перегруженную
функцию-операцию
извлечения из потока
operator<< и
operator» как дру¬
зей класса. Их применение очевидно.
Строка программы
String(const char
*
=
"");
//
конструктор преобразования
объявляет конструктор преобразования. Этот конструктор принимает в качест¬
ве аргумента char * (который является по умолчанию пустой строкой) и создает
объект String, который содержит ту
же самую строку символов.
Любой
конст¬
руктор с единственным аргументом можно рассматривать в качестве конструк¬
тора
преобразования. Как мы увидим, такой конструктор полезен, когда мы
String строку символов. Конструктор преобразует обыч¬
String, который, в свою очередь, присваивается объекту
присваиваем объекту
ную строку в объект
778
Глава 18
String. Наличие
такого конструктора
преобразования
означает, что нет необхо¬
димости предусматривать специальную операцию присваивания для присвое¬
ния объекту String строки символов. Компилятор автоматически вызывает
конструктор
преобразования, чтобы
создать
временный объект String, содержа¬
указанную строку. После этого вызывается перегруженная операция при¬
сваивания, чтобы присвоить временный объект String другому объекту String.
щий
Обратите внимание, что когда С++
ким образом, он может применить
использует конструктор
лишь один конструктор,
преобразования
та¬
чтобы попробовать
удовлетворить потребности перегруженной операции присваивания. Наоборот,
невозможно реализовать такое присваивание посредством выполнения ряда не¬
преобразований, определенных пользователем. Конструктор преобразо¬
String может вызываться в таком объявлении, как String sl("happy").
Конструктор преобразования вычисляет длину строки символов и присваивает
это значение закрытому элементу данных length; использует new, чтобы присво¬
явных
вания
ить указатель на выделенную под строку память закрытому элементу данных
sPtr; вызывает assert для проверки успешности вызова new и, если память вы¬
делена, вызывает strcpy, чтобы скопировать строку символов в объект.
//
//
STRING2.H
Определение
String
класса
#ifndef STRINGl_H
#define STRINGl_H
#include <iostream.h>
class String {
friend ostream &operator<<(ostream &,
friend istream &operator>>(istream &,
public:
String(const char
*
String &);
String &);
const
=
// конструктор
// конструктор
// деструктор
&operator=(const String &);
//
String &operator+=(const String: &) ;
int operator! () const;
//
int operator==(const String &) const;
//
преобразования
int
operator!=(const
int
operator<(const
String
String
String(const
-String();
const String
String
"");
&);
String
копии
// присваивание
конкатенация
String пустая?
sl
==
проверка
s2
const;
//
проверка
sl
!=
&)
const;
//
проверка
sl
<
s2
&)
s2
&)
s2
int
operator>(const
const;
//
проверка
sl
>
int
operator>=(const
String
&)
const;
//
проверка
sl
>=
s2
int
operator<=(const
String
&)
const;
//
проверка sl
ссылки char
<=
s2
char
&operator[](int);
String &operator()(int,
int getLength() const;
private:
char *sPtr;
int
length;
int);
//
возврат
//
возврат
//
возврат
//
указатель
//
длина
подстроки
длины строки
на
начало
строки
};
#endif
Рис. 18.5. Определение класса
String (часть
1 из 7)
строки
779
Перегрузка операций
//
STRING2.CPP
//
Определения
#include
<iostream.h>
#include
#include
<string.h>
<assert.h>
#include
"string2.h"
//
Конструктор
преобразования:
класса
для
функций-элементов
Преобразует
String
*
char
String
в
Strmg::Strmg(const char *s)
{
cout
<<
length
sPtr
=
"Conversion
//
//
//
1];
0);
s);
!=
assert(sPtr
strcpy(sPtr,
//
+
char[length
new
"
constructor:
strlen(s);
=
<<
s
«
endl;
вычислить
выделить
длину
память
выход, если не выделена
// скопировать строку в объект
копии
Конструктор
String::String(const String ©)
{
cout
<<
length
sPtr
=
constructor:
"Copy
<<
!=
strcpy(sPtr,
[]
+
1];
0);
copy.sPtr);
// Деструктор
String::~String()
{
cout << "Destructor:
sPtr;//
copy.sPtr
<<
// скопировать
copy.length;
new char[length
assert(sPtr
delete
"
=
"
<<
очистить
sPtr
endl;
длину
//
выделить
//
//
проверить результат
скопировать строку
<<
endl;
память
строку
// Перегруженная операция =; избегать
const String &String::operator=(const
самоприсваивания
String
&right)
{
cout
if
<<
"operators
(&right != this)
delete []
sPtr;
length
sPtr
=
assert
=
return
<<
<<
endl;
{
right.length;
char[length +1];
new
(sPtr
strcpy(sPtr,
}
else
cout
called"
!=
//
избегать
//
предотвратить
//
//
новая
разрешить
строки
память
// проверка результата
// скопировать строку
0);
right.sPtr);
//
длина
выделить
"Attempted assignment of
*this;
самоприсваивания
утечку памяти
а
String
конкатенацию
to
itself
\n";
присваиваний
}
// Конкатенация правого операнда
//
сохранение результата
в
этом
с
этим
объектом
и
объекте.
String &String::operator+=(const String &right)
Рис. 18.5 Определения функций-элементов для
класса
String (часть
2 из 7)
Глава 18
780
char
*tempPtr
length
sPtr
+=
=
sPtr;
//
чтобы иметь
сохранить,
//стереть
// новая длина
right.length;
char[length +1];
assert(sPtr != 0);
strcpy(sPtr, tempPtr);
strcat(sPtr, right.sPtr);
delete [] tempPtr;
//
//
выйти,
//
левая
return
//
=
new
// String пустая?
int Stnng::operator! ()
const
String
свободное
создать
если
часть
{
разрешить
==
&right)
0; }
// String не равна правой String?
int String::operator!=(const String &right)
return strcmp(sPtr,
{
right.sPtr) != 0; }
не
размещена
новой
-String
новой
Strmg
старое
место
конкатенацию
return length
// String равна правой String?
int String::operator==(const String
{ return strcmp(sPtr, right.sPtr)
место
память
// правая часть
// восстановить
*this;
возможность
==
0;
вызовов
}
const
const
// String меньше чем правая String?
int String::operator<(const String &right)
return strcmp(sPtr,
{
right.sPtr) < 0; }
const
// String больше чем правая String?
int String::operator>(const String &right)
{ return strcmp(sPtr, right.sPtr) > 0; }
const
// String больше чем или равна правой String?
int String::operator>=(const String &right) const
{ return strcmp(sPtr, right.sPtr) >= 0; }
//
String
чем или равна правой String?
::operator<=(const String &right) const
(sPtr,
strcmp
right.sPtr) <= 0; }
меньше
int String
{
return
// Возврат ссылки на символ в String.
char &String::operator [](int subscript)
{
// Сначала проверяем выход индекса за
assert(subscript
return
sPtr
>=
0
<
subscript
&&
//
[subscript];
границы
length);
lvalue
создать
}
//
Возврат подстроки,
//
длиной subLength
начинающейся
как
ссьшки
на
с
index
String &String::operator()(int index,
{
// убедимся, что index находится в
// и длина подстроки
assert(index >= 0 &&
>=
и
String.
int subLength)
объект
границах
0
index
<
length
&&
subLength
>=
Рис. 18.5 Определения функций-элементов для класса String (часть 3
0);
из
7)
781
Перегрузка операций
*subPtr
String
=
// пустая String
// проверка выделения
String;
new
!= 0);
assert(subPtr
памяти
// определение длины подстроки
int size;
if
((subLength
size
0)
==
||
(index
+
index
=
length
=
subLength
-
+
subLength
>
length))
1;
else
size
1;
+
//
разместить память для подстроки
subPtr->sPtr;
size;
subPtr->length
subPtr->sPtr
new char[size];
delete
=
=
!= 0);
assert(subPtr->sPtr
//
скопировать
for
(int
i
return
=
=
j
=
subPtr->sPtr[j]
subPtr->sPtr[j]
в
подстроку
index,
=
0;
// проверка
<
памяти
String
новую
i
выделения
index
+
size
-
1;
i++,
j++)
sPtr[i];
// ограничить новую строку
// нулевым символом
// возврат новой String
'\0';
*subPtr;
}
// Возврат длины строки
int String::getLength() const
{
return
// Перегруженная операция вывода
ostream &operator<<(ostream &output,
{
output
<<
return
output;
length;
const
}
String
&s)
s.sPtr;
// разрешить
конкатенацию
}
//
Перегруженная операция ввода
&operator>>(istream &input,
String &s)
istream
{
static char temp[100];
input
s
=
>>
ввода
temp;
// используем операцию присваивания
// класса String
temp;
return
// буфер
// разрешить
input;
конкатенацию
}
Рис. 18.5
Строка
Определения функций-элементов
для класса
String (часть
4 из 7)
программы
String(const String
//
&);
конструктор
копии
является конструктором копии. Он инициализирует объект String копирова¬
нием существующего объекта. Такое копирование должно выполняться акку¬
ратно, чтобы избежать ловушки, когда в результате копирования будут
получены два объекта
деленную память,
String,
указывающих на одну и ту же динамически вы¬
случай, который может произойти при поэле-
т.е. тот же
Глава 18
782
ментном копировании
по умолчанию.
преобразования
length из исходного объекта
за
конструктору
элемент
Конструктор
исключением
класса
копии
того,
String
в
объекта
в
sPtr объекта-копии,
то
он
аналогично
просто
копирует
объект-копию String. Об¬
ратите внимание, что конструктор копии заново выделяет
ней строки символов объекта-копии. Если бы он просто
исходного
работает
что
память для внутрен¬
sPtr
скопировал
оба объекта указывали бы
из
на ту же
область памяти. При первом вызове деструктора динамически выделен¬
ная память была бы очищена и sPtr другого объекта стал бы неопределенным.
Такая ситуация с большой вероятностью вызовет серьезную ошибку при вы¬
самую
полнении программы.
//
//
FIG18_5.CPP
Тестер для класса
String
#include <iostream.h>
#include "strmg2.h"
main ()
{
String sl("happy"),
s3;
s2(" birthday"),
// проверка перегруженных операций равенства
cout
<<
"sl
<<
"\";
\""
s3 is
<<
"The
results
<<
"\ns2
"\ns2
"\ns2
"\ns2
<<
<<
<<
«
is
sl
of
comparing
'
sl
yields
!=
sl
yields
>
sl
<
sl
sl
sl
"\ns2
>=
<<
"\ns2
<=
is
\""
и
отношений
«
empty\n"
==
<<
s2
"\";
«
'
yields
yields
yields
yields
'
'
'
'
and
s2
sl:"
<<
(s2
<<
<<
(s2
(s2
>
sl)
sl)
<<
(s2
<
sl)
<<
(s2
(s2
>=
«
==
!
=
<=
sl)
sl)
sl)
<<
// проверка содержимого String перегруженной операцией(!)
cout << "Testing !s3:\n";
if
(!s3)
{
assigning sl
cout
<<
"s3
is
empty;
s3
sl;
<< "s3
//
is
проверка перегруженной операции присваивания
\"" << s3 << "\"\n\n";
=
cout
to
s3;\n";
// проверка перегруженной операции конкатенации Strmg
cout << "sl += s2 yields sl
";
=
sl
+=
cout
s2;
<< sl
//
<<
проверка перегруженной конкатенации
"\n\n";~
// проверка перегруженного конструктора
cout << "sl += \" to you\" yields\n";
"
to you";
sl +=
// проверка перегруженного
cout
//
//
<<
"sl
"
<<
sl
<<
конструктора
"\n\n";
проверка подстроки перегруженной
функцией-операцией вызова ()
cout
<<
<<
«
substring of sl starting at\n"
"location 0 for 14 characters, sl(0,
sl (0,
14) « "\n\n";
"The
Рис. 18.5 Тестер для проверки
класса
String (часть
14),
Б из 7)
is:
"
Перегрузка операций
7B3
// проверка опции подстроки (до конца строки)
cout << "The substring of sl starting at\n"
//
<<
"location
<<
sl(15,
проверка
<<
sl(15,
"\n\n";
конструктора
String *s4Ptr
cout
0)
15,
<<
=
"*s4Ptr
"
is:
0 означает
"
<<
*s4Ptr
*s4Ptr
cout
"assigning *s4Ptr
<<
=
<<
*s4Ptr;
"*s4Ptr
//
=
строки"
копии
<<
"\n\n";
// проверка операции присваивания
cout
"до конца
String(sl);
new
=
0),
//
"
<<
to
(=)
с
самоприсваиванием
*s4Ptr\n";
проверка перегруженного
*s4Ptr << endl;
самоприсваивания
// проверка деструктора
delete s4Ptr;
// проверка операции индексирования, возвращающей lvalue
'H';
sl[0]
'B';
sl[6]
cout << "\nsl after sl[0]
'H' and sl[6]
'B' is:
-
=
=
sl
<<
<<
"\n\n";
// проверка на выход индекса из границ
cout << "Attempt to assign 'd' to sl[30]
sl[30]
=
return
0;
"
=
//
'd';
ОШИБКА:
индекс
вышел
yields:\n";
за
границы
}
Рис. 18.5 Тестер для проверки класса String (часть б
из
7)
Строка программы
-String();// деструктор
объявляет деструктор для
класса String. Деструктор применяет операцию de¬
lete, чтобы очистить динамическую память, полученную new в конструкторе.
Строка
программы
const String
&operator=(const String
&);
//
присваивание
объявляет перегруженную операцию присваивания. Когда компилятор встре¬
чает такое выражение, как stringl
string2, он вызывает функцию
=
stringl.operator=(string2);
Перегруженная функция-операция присваивания operator= проверяет
операнды на самоприсваивание. Если регистрируется самоприсваивание, то
следует просто возврат из функции, потому что сам объект уже и является тре¬
буемым объектом. Если бы эта проверка была опущена, то функция немедлен¬
но очистила бы sPtr в объекте и строка символов была бы потеряна. Если это
не самоприсваивание, функция особождает старую строку и копирует поле
длины
исходного
объекта
в
объект-копию;
заново
выделяет
память
для
строки-копии; вызывает assert для проверки успешности вызова new и после
этого вызывает
екта
в
strcpy, чтобы
объект-копию. Вне
скопировать строку символов из исходного объ¬
зависимости от того, самоприсваивание это или нет,
функция возвращает *this, чтобы была
ний.
возможность конкатенации присвое¬
784
Глава 18
Conversion constructor:
happy
Conversion constructor:
birthday
Conversion constructor:
"
sl is "happy"; s2 is
birthday"; s3 is empty
The results of comparing s2 and sl:
s2
sl yields 0
==
s2
!=
sl
yields
1
s2
>
sl
yields
0
s2
<
sl
yields
1
s2
>=
sl
yields
0
s2
<=
sl
yields
1
Testing !s3:
s3 is empty;
sl
assigning
operator= called
s3 is "happy"
sl
+=
s2
sl
yields
=
to
s3;
happy birthday
"
sl +=
to you" yields
Conversion constructor:
Destructor:
sl
=
to
happy birthday
Conversion
to
you
you
to
you
constructor:
substring of sl starting
The
0
location
for
Conversion
14
location
Copy
sl(0,
15,
sl
(15,
is:
0),
to
assigning *s4Ptr
operator=
to
Destructor:
you
to
itself
*s4Ptr
String
a
to
you
happy birthday to you
sl after sl(0]
==
'H'
and
Attempt to assign
Assertion failed:
'd'
to
Abnormal
to
called
Attempted assignment of
*s4Ptr = happy birthday
file
happy birthday
you
happy birthday
happy birthday to you
=
is:
at
constructor;
*s4Ptr
14),
constructor:
substring of sl starting
The
at
characters,
sl[30]
subscript >=
STRING2.CPP, line 98
program
'B'
=
sl[6]
0
is:
Happy
yields:
&& subscript
<
Birthday
to
you
length,
termination
Рис.18.5 Вывод программы-тестера для проверки класса (часть 7 из
7)
Строка программы
String &operator+=(const String &);
//
конкатенация
объявляет перегруженную операцию конкатенации строк. Когда компилятор
встречает в main выражение sl += s2, он генерирует вызов функции sl.opera-
tor+=(s2). Функция operator+=
создает
временный указатель
для хранения
785
Перегрузка операций
текущего указателя строки символов объекта до того момента, когда становит¬
ся возможным очистить память, содержащую строку символов; вычисляет
суммарную длину двух строк; использует new, чтобы зарезервировать место
для строки результата; вызывает assert для проверки успешности операции
new; вызывает strcpy для копирования исходной строки во вновь выделенную
и strcat для конкатенации ее со строкой второго операнда; использует delete,
чтобы освободить место, занятое исходной строкой объекта, и возвращает *this
как String &, чтобы была возможность конкатенации операций +=.
Нужна ли нам вторая перегруженная операция, чтобы разрешить конка¬
*
String и char*? Нет. Конструктор преобразования const char преоб¬
разует обычную строку в объект String, который после этого может участво¬
вать в существующей операции конкатенации. С++ может выполнять такие
преобразования только на один уровень глубины, чтобы удовлетворить требо¬
тенацию
вания к типам операндов. C+4- может также производить неявное, определен¬
ное компилятором преобразование между встроенными типами перед тем, как
он выполнит
преобразование
между встроенным типом и классом.
Совет по повышению эффективности 18.2
Перегрузка операции конкатенации +=, которая получает единственный аргумент
типа const char *, работает более эффективно, чем использование неявного преобра¬
зования с
ньше
последующей конкатенацией. Для
программного
и
кода
они
преобразований требуется
неявных
менее уязвимы для
ме¬
ошибок.
Строка программы
int operator!()
const;//
является
ли
String пустой?
объявляет перегруженную операцию отрицания. Эта операция обычно исполь¬
зуется с объектами String для проверки содержимого строки. Например, когда
он генерирует вызов функции
strmgl.operator! ()
Эта функция просто возвращает результат проверки равенства нулю дли¬
компилятор встречает выражение !stringl,
ны
(length)
Строки
строки.
программы
int operator==(const Strmg &)
const;
//
проверка sl
int
operator!=(const
const;
//
проверка
sl
!
int
operator<(const
String
&)
const;
//
проверка
sl
<
s2
int
operator>(const
String
&)
const;
//
проверка
sl
>
s2
int
operator>=(const
String
&)
const;
//
проверка
sl
>=
int
operator<=(const
String
&)
const;
//
проверка
sl
<=
String
&)
объявляют
перегруженные
String. Они
все аналогичны, так что
перегруженную
операцию
операции
>=.
равенства
и
компилятор
=
отношений
давайте обсудим
Когда
==
s2
s2
s2
s2
для
класса
только один пример
встречает
выражение
stringl>
string2, он генерирует вызов функции
stringl.operator>=(string2)
который возвращает 1 (true), если строка stringl больше или равна string2.
Каждая из этих операций использует strcmp для сравнения строк символов в
объектах String.
Строка программы
=
char
&operator[]
(int);//
возврат
ссылки
char
786
Глава 18
объявляет перегруженную операцию индексации. Когда компилятор встречает
такое выражение, как stringl [0], он генерирует вызов функции stringl.opera-
tor [] (0). Сначала функция operator[]
вызывает assert для проверки диапазона
сообщение
аварийно завершит работу. Если индекс находится в допустимых
operator[] возвращает как char & соответствующий символ объекта
индексации; если индекс выходит из диапазона, программа выведет
об ошибке
пределах,
и
String; char &
может
В
использоваться как
lvalue для
изменения
указанного
String.
символа в объекте
строке программы
&operator() (int,
String
int);
//
возврат подстроки
объявляется перегруженная операция вызова. В классах строк перегрузка этой
операции является обычной процедурой для выбора подстроки в объекте
String. Два целых
определяют
String.
на
значения, использующиеся в качестве параметров
начальное
местоположение
Если местоположение
имеет
подстроки
и
длину
подстроки,
функции,
выбираемой
из
начала выходит из допустимых границ или дли¬
отрицательное
значение,
то
генерируется
сообщение об
ошибке. В соответствии с соглашением, если длина подстроки равна 0, то под¬
строка выбирает все символы до конца объекта String.
например, что stringl является объектом String, содержа¬
символов "AEIOU". Когда компилятор встречает выражение
Предположим,
щим
строку
stringl(2, 2),
функции stringl.operator( )(2, 2). При его
временный объект String, содержащий строку "10", и
он генерирует вызов
выполнении создается
возвращается ссылка на этот
является
объект. Перегрузка функции-операции вызова ()
сильным средством, поскольку
функции могут получать произволь¬
сложный список параметров. Мы можем использовать эту осо¬
бенность для многих интересных целей. Другим применением функции-операции вызова является альтернативная нотация индексации массива: вместо ис¬
но
длинный
и
пользования неуклюжих двойных квадратных скобок С для двумерных масси¬
вов типа а [b] [с] некоторые программисты предпочитают перегрузить функ¬
цию-операцию вызова, чтобы разрешить нотацию a(b, с). Перегруженная фун¬
кция-операция вызова может быть только не-статической функцией-элемен-
Эта операция используется
объектом класса String.
том.
Строка
только тогда, когда «имя
является
программы
int getLength()
const;
//
возврат длины строки
объявляет функцию, которая возвращает длину объекта
мание, что эта
Теперь
функции»
функция возвращает
читатель
должен
окно вывода и проверить
изучить
каждый
String. Обратите вни¬
String.
значение закрытых данных класса
из
программный
код
main, исследовать
вариантов применения перегруженных
операций.
18.11.
Все четыре
типа
Перегрузка
++ и
операций приращения (инкремента)
и уменьшения
(дек¬
преинкремент и постинкремент, предекремент и постдекремент
могут быть перегружены. Мы увидим, как компилятор определяет, какая
форма
префиксная или постфиксная
используется в операции прираще¬
ния или уменьшения.
ремента)
787
Перегрузка операций
Чтобы перегрузить операции приращения, разрешив тем самым использо¬
и преинкремента и постинкремента, каждая перегруженная функ¬
вание
ция-операция должна иметь различную запись, чтобы компилятор мог опре¬
делить, какая из операций ++ подразумевается. Префиксные версии перегру¬
жаются точно так же, как перегружалась бы любая другая префиксная одно¬
местная операция.
Предположим,
ется
что мы хотим
прибавить 1
к значению дня, которым явля¬
объект dl класса Date (дата). Когда компилятор встречает выражение
преинкремента
++dl
он генерирует вызов
функции-элемента
dl.operator++()
Прототипом этой функции было бы
Date
operator++();
Если преинкремент реализуется как
функция,
не являющаяся элементом
класса, то, когда компилятор встречает выражение
++dl
он генерирует вызов
функции
operator++(dl)
Прототип этой функции
friend
Date
Перегрузка
мог бы
быть объявлен
в классе
Date
как
operator++(Date &);
операции
постинкремента
представляет
некоторую
слож¬
ность, поскольку компилятор должен видеть разницу между сигнатурами пе¬
функций-операций преинкремента и постинкремента. Соглаше¬
ние, которое было принято в С++, состоит в том, что, когда компилятор встре¬
чает выражение постинкремента
регруженных
dl++
он будет генерировать вызов функции-элемента
dl.operator++(0)
Прототипом этой функции была бы
Date
запись
operator++(int)
Здесь 0 является просто значением-пустышкой, чтобы список аргументов
operator++, использующийся для постинкремента, был отличим от списка ар¬
гументов operator++ для преинкремента.
Если постинкремент реализуется как функция, не являющаяся элемен¬
том, то, когда компилятор встречает выражение
dl++
он генерирует вызов
функции
operator++(dl, 0)
Прототипом этой функции было бы
friend
Date
Повторим,
operator++(Date
что
список аргументов
аргумент
0
&,
типа
int);
int используется компилятором, чтобы
operator++, используемый
для операции постинкремента,
отличался бы от списка аргументов для операции преинкремента.
Все, что мы сказали в этом разделе по поводу перегрузки операций преин¬
кремента
и
постинкремента, применимо
и для
перегрузки операций предекре-
788
Глава
мента и постдекремента. В следующем разделе мы исследуем класс
рый объявляет перегруженные операции преинкремента
18.12.
Пример:
класс
Date,
18
кото¬
и постинкремента.
Date
На рис. 18.6 показан класс Date. Класс использует перегруженные опера¬
ции преинкремента и постинкремента, чтобы увеличить на 1 значение даты в
объекте класса Date.
Класс содержит следующие функции-элементы: перегруженную опера¬
цию передачи в поток, конструктор по умолчанию, функцию setDate, перегру¬
женную функцию-операцию преинкремента, перегруженную функцию-опера¬
цию постинкремента и перегруженную операцию присваивания со сложением
(+=).
Программа-тестер в main создает объект-дату dl, который инициализиру¬
ется значением January 1, 1990; объект-дату d2, который инициализируется
значением December 27, 1992,
занного
новится
и
d3, который инициализируется
Конструктор Date
значением не¬
setDate для проверки ука¬
месяца, дня и года. Если месяц недействителен, то его значением ста¬
1. Недействительное значение года заменяется на 1900. Недействите¬
действительной
льный день
даты.
заменяется на
// DATEl.H
// Определение
1.
класса
#ifndef
DATEl_H
#define
DATEl_H
<iostream.h>
#include
class
Date
friend
{
ostream
public:
Date(mt
m
=
1,
Date
&operator<<(ostream
int d
=
void setDate(mt, mt,
Date operator++();
Date operator++(-int);
Date &operator+=(mt);
private:
int month;
int day;
int year;
static
void
вызывает
int days[];
const
Date
&);
// конструктор по
// умолчанию
// установить дату
int);
// преприращение
// постприращение
// добавить дни, модифицировать объект
1,
//
helpIncrement();
int у
&,
массив
=
1900);
дней
в
месяце
// сервисная функция
#endif
Рис. 18.6. Определение
класса Date
Программа-тестер выводит каждый
(часть 1
из созданных
из
5)
объектов Date
с помо¬
передачи в поток. Чтобы добавить 7 дней к d2,
к нему применяется перегруженная операция +=. После этого, чтобы устано¬
вить d3 на February 28, 1992, вызывается функция setDate. Затем новый объ¬
щью
ект
перегруженной операции
Date, d4, устанавливается
на
March 18, 1969. После
этого
d4 увеличивает-
789
Перегрузка операций
ся на 1 с помощью
перегруженной
операции приращения.
Полученная
дата
выводится как до, так и после выполнения операции преинкремента для подтвердждения того, что все сделано правильно. Наконец, следует приращение
d4 с использованием перегруженной операции постинкремента. Для проверки
того, что все сделано правильно, полученная дата выводится как до, так и по¬
сле выполнения операции постинкремента.
//
DATEl.CPP
// Определения функций-элементов для
#include <iostream.h>
#include "datel.h"
// Инициализация
// одна копия на
статических
int у)
// Установить дату
void Date::setDate(int mm,
{
month
(mm >= 1 && mm
=
//
if
=
>=
(yy
проверка
(month
day
1900
на
==
в
действия файла;
области
{0, 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
=
// конструктор Date
Date::Date(int m, int d,
year
элементов
Date
класс.
Date::days[]
int
класса
2
&&
года
&&
4
%
==
0
}
&&
1;
:
<=
изменение
у);
int yy)
12) ? mm
2100) ?
<=
d,
setDate(m,
int dd,
yy
year
{
yy
1900;
:
(year
%
400
==
year
%
100
!=
:
1;
? dd
=
(dd
>=
1
&&
dd
<=
29)
=
(dd
>=
1
&&
dd
<=
days[month])
0
|!
0))
1;
:
else
day
?
dd
}
//
Операция
Date
преинкремента
как
перегружается
функция-элемент.
Date::operator++()
{
helpIncrement();
return
*this;
//
возврат
значения,
а
не
возврат
ссылки
}
//
Операция
//
Отметьте,
Date
|
постинкремента перегружается как функция-элемент.
что пустой целочисленный параметр не имеет имени.
Date::operator++(int)
{
Date
*this;
temp
helpIncrement();
=
// возврат временного объекта
return temp; // возврат значения,
а
не
ссылки
}
Рис. 18.6. Определения функций-элементов для класса Date (часть 2
из
5)
790
Глава 18
//
Добавить
Date
к
дате
указанное
&Date::operator+=(int
число
дней
additionalDays)
{
fo,r
i
(int
i
1;
=
<=
I++)
additionalDays;
helpIncrement();
return
//
*this;
конкатенацию
разрешить
}
//
для
Функция
void
даты
инкремента
Date::helpIncrement()
{
// проверка
(month
if
смены
2
==
%
(year
400
++day;
else if
(day
года
&&
<
==
day <
0
||
29
&&
year
%
%
year
100
Date::days[month])
++day;
else if
(month
<
12)
{
4
0))
//
не
//
последний
//
December
0
==
!=
&&
последний
день
месяца
<
день
месяца
December
++month;
day
=
1;
}
else
{
month
day
=
31
1;
=
1;
++year;
I
// Перегруженная операция вывода
ostream &operator<<(ostream &output,
{
static char *monthName[13]
{"",
const
Date
&d)
=
"January11,
"February", "March", "April", "May", "June",
"July", "August", "September", "October",
"November"
"December" } ;
,
output
<<
<<
return
monthName[d.month]
d.day
output;
<<
//
",
"
<<
<<
1
1
d.year;
разрешить
конкатенацию
}
Рис. 18.6.
Перегрузка
Определения функций-элементов
для класса Date (часть 3 из 5)
операции преинкремента является очевидной.
Программа
функцию helpIncrement, чтобы сделать фактическое
приращение. Эта функция должна иметь дело с системой счисления дат, т.е.
вызывает
сервисную
с правилами переноса, которые должны выполняться, когда мы пытаемся
увеличить значение дня, уже достигшего максимального значения. В этом
случае правила переноса
требуют, чтобы было увеличено значение месяца.
12, то значение года должно быть также
Если значение месяца уже равно
увеличено.
791
Перегрузка операций
// FIG18_6.CPP
// Тестер для класса
Date
#include
<iostream.h>
#include
"datel.h"
mam ()
{
Date dl,
cout <<
cout
d2(12,
"dl
"\nd2
is
"
<<
"\nd3
is
"
<<
"\n\nd2
cout
<<
cout
<<
"
is
18,
Testing
<<
d4 is
\n++d4
d4
\n
<<
<<
'
<<
<<
«
return
'
'
8045);
d2
d3;
<<
7
is
"
<<
+=
(d2
7)
"\n\n";
<<
1992);
<< d3;
"
<<
++d3
<<
"\n\n";
1969);
<<
<<
"
is
"\n++d3
Date d4(3,
cout
28,
d3
99,
dl
<<
+=
d3(0,
1992),
<<
<<
d3.setDate (2,
cout
27,
"
is
the
"
is
is
preincrement
«
"
"
operator:\n"
d4
<<
++d4
<<
d4;
\n\nTesting the postincrement
d4 is " << d4
<< d4++
\nd4++ is
operator:\n'
"
\n
d4
is
"
<<
d4
<<
endl;
0;
Рис. 18.6.
Тестер для
класса Date
(часть 4
из
5)
dl is January 1, 1900
d2 is December 27, 1992
d3 is January 1, 1900
d2
+=
7
is
January 3,
d3 is February 28,
++d3 is February 29,
1993
1992
1992
Testing
d4 is
++d4 is
d4 is
the preincrement operator:
March 18, 1969
March 19, 1969
March 19, 1969
Testing
d4 is
d4++ is
d4 is
the postincrement operator:
March 19, 1969
March 19, 1969
March 20, 1969
Рис. 18.6. Вывод программы-тестера для
класса Date
(часть 5
из
5)
Глава 18
792
Перегруженная операция преинкремента возвращает приращенную ко¬
пию текущего объекта. Это происходит потому, что текущий объект, *this,
возвращается как Date. Это фактически вызывает конструктор копии.
Процедура перегрузки операции постинкремента несколько сложнее. Что¬
бы имитировать эффект постинкремента, мы должны вернуть неувеличенную
копию объекта Date. На входе в operator++ мы записываем текущий объект
в temp. После этого мы вызываем helpIncrement, чтобы дать объекту
приращение. Затем мы возвращаем немодифицированную копию объекта в
temp.
(*this)
Резюме
« в
Операция
С++
может
различный
иметь
смысл
и
используется
мер перегруженной операции. Операция
в
Это при¬
качестве операции передачи в поток и операции сдвига влево.
» также перегружена, поско¬
льку она используется как операция извлечения из потока и как опера¬
ция
правого
сдвига.
Вообще говоря, С++ дает программисту возможность перегружать боль¬
шинство операций, чтобы их можно было применять к различным
типам.
Компилятор генерирует соответствующий программный код,
исходя из контекста применения данной операции.
Перегрузка операций способствует
Операции
расширяемости С++
перегружаются посредством определения функции
телом). Ключевое слово operator с
перегружаемой операции становится именем
и
ловком
Чтобы
жна
использовать
операцию
быть перегружена,
сваивания
(=)
но
с
последующим
класса
заго¬
функции.
объектами класса,
эта
здесь есть два исключения.
дол¬
операция
Операция
при¬
может использоваться с любым классом без перегрузки
для выполнения поэлементного копирования по умолчанию.
взятия
(с
символом
адреса
(&)
также
без перегрузки;
может
использоваться
с
Операция
объектами
эта операция просто возвращает адрес
любого
объекта
в
памяти.
Смысл перегрузки операций
обеспечение той же лаконичности выра¬
жений для типов, определяемых пользователем, которую обеспечивает
С++ для встроенных типов за счет богатого набора операций.
Большая
часть
Приоритет
средством
и
операций С++
ассоциативность
может
быть
операций
не
перегружена.
могут
быть
изменены
по¬
перегрузки.
Для перегруженных операций нельзя использовать аргументы по умол¬
чанию.
Нельзя
которое задано операцией; одноме¬
одноместной при перегрузке, а двухместная
изменить число операндов,
стная операция остается
остается
двухместной.
Нельзя создавать обозначения для новых
щие операции могут быть перегружены.
Способ работы операции
с
операций;
только существую¬
объектами встроенных типов
изменен посредством перегрузки.
не может
быть
793
Перегрузка операций
перегружающая операцию функция дол¬
перегрузке (), [], -> или
жна объявляться как элемент класса.
При
=
Функции-операции
могут быть как функциями-элементами, так
и
фун¬
не являющимися элементами.
кциями,
Когда функция-операция реализуется в качестве функции-элемента,
левый (или единственный) операнд должен быть объектом (или ссыл¬
кой на объект), принадлежащим к классу этой операции.
Если необходимо, чтобы левый операнд был объектом другого класса,
функция-операция
то данная
ляющаяся
элементом
должна применяться как
функция,
не яв¬
класса.
элемент класса вызывается только тогда, когда
Функция-операция
левый операнд двухместной операции является некоторым объектом
этого класса, или когда единственный операнд одноместной операции
является объектом этого класса.
Одна из причин, по которой для перегрузки операции можно выбрать
требование коммутативности
функцию, не являющуюся элементом
данной операции.
Одноместная операция для класса может быть перегружена
тическая функция-элемент, не имеющая аргументов, или
как не-стакак
функ¬
ция, не являющаяся элементом и имеющая один аргумент. Этот аргу¬
мент должен быть либо объектом класса, либо ссылкой на объект.
операция может быть перегружена как не-статическая
функция-элемент с одним аргументом или как функция, не являющая¬
ся элементом и имеющая два аргумента (один из этих аргументов дол¬
жен быть либо объектом класса, либо ссылкой на него).
Двухместная
Операцию
гих
индексации массива
[]
целей, например, для выбора
не запрещено использовать и для дру¬
элементов из разного рода
контейнер¬
ных классов, таких, как связанные списки, строки, словари и т.д.
дексам
быть
также
не
символами
Конструктор
и
только
инициализирует
Конструктор
объекта
Компилятор
образования
например,
Ин¬
могут
объект, используя другой объект
копии вызывается также
необходимо копирование объекта: при
возврате
целыми,
строками.
копии
того же класса.
да
обязательно быть
из
вызове
по
всякий раз,
ког¬
и
при
значению
вызванной функции.
не может автоматически определить,
как выполнять пре¬
между типами, определяемыми пользователем, и встроен¬
ными типами. Программист должен определить в явном виде, как про¬
преобразования. Эти преобразования могут осуществлять¬
преобразования, т.е. конструктора с
аргументом, который просто превращает объект некоторого типа
водить такие
ся с использованием конструктора
одним
в
объект данного класса,
Операция преобразования (или приведения) может использоваться для
преобразования объекта данного класса в объект другого класса или в
объект встроенного типа. Такого рода операция преобразования должна
быть не-статической функцией-элементом; этот вид операции преобра¬
зования
не
может
быть функцией-другом.
794
Глава 18
Конструктор преобразования
ментом,
са,
использующийся
это конструктор с единственным аргу¬
для
преобразования аргумента в объект клас¬
Компилятор может вызывать
образом.
которому принадлежит конструктор.
такой конструктор
Операция
неявным
наиболее
присваивания
Обычно
она
объекту
того
используется
же
преобразования,
присваивания
класса,
она
для
но,
также
часто
перегружаемая
одного
присваивания
посредством
может
использования
применяться
объектов различных
в
операция.
объекта другому
конструктора
качестве
операции
классов.
Если перегруженная операция присваивания
не определена, то присва¬
будет просто поэ¬
лементным копированием каждого элемента данных. В некоторых слу¬
чаях это приемлемо. Для объектов, которые содержат указатели на
ивание разрешено,
но,
по умолчанию,
присваивание
динамически выделенную память, поэлементное копирование даст в ре¬
зультате два различных объекта, указывающих на ту же самую дина¬
мически выделенную память. Вызов деструктора для любого из этих
объектов очищает выделенную память. Если после этого другой объект
обратится
к
этой области памяти,
то результат такого
обращения будет
неопределенным.
Чтобы перегрузить операции инкремента, разрешив
льзование
и
преинкремента
и
постинкремента,
тем самым испо¬
каждая
перегружен¬
должна иметь
различную сигнатуру, чтобы
компилятор мог определить, какая из операций ++ подразумевается.
Префиксные версии перегружаются точно так же, как перегружаась
ная
бы
функция-операция
любая
другая
префиксная
одноместная
операция.
Обеспечение
уникальной сигнатуры для функции-операции постинкремента дости¬
гается посредством второго аргумента, который должен быть целым
(int). На
самом деле пользователь не передает значение этого специа¬
льного аргумента целого типа.
префиксные
личить
и
Это просто
помогает компилятору раз¬
постфиксные версии операций инкремента
декремента.
Терминология
встроенный
тип
перегруженная операция
класс
Аггау
перегруженная
операция
класс
Date
перегруженная
операция
класс
String
ключевое
конкатенация
operator
вызовов функций
копии
преобразования
неперегружаемые операции
неявные преобразования типа
операции как функции
операция преобразования
перегружаемые операции
перегруженная дружественная
функция-операция
перегруженная операция !=
перегруженная операция []
конструктор
<=
==
слово
конструктор
<
перегруженная операция >
перегруженная операция >=
перегруженная операция
присваивания (=)
Перегруженная функция-операция
элемент
класса
перегрузка
перегрузка
перегрузка
перегрузка
перегрузка
перегрузка
перегрузка
двухместной операции
одноместной операции
операций
постфиксной операции
префиксной операции
функций
и
795
Перегрузка операций
явные
преобразования типа
(операция приведения)
разрешение неоднозначности
преобразования
тип, определяемый пользователем
преобразование, определяемое
пользователем
преобразования между
встроенными типами
и
классами
между классами
различного типа
преобразования
Распространенные ошибки программирования
18.1.
Попытка создать новую операцию.
18.2.
Попытка модифицировать способ работы операции
встроенных
18.3.
Предположение,
чески
что перегрузка
перегружает
кие как
с
объектами
типов.
операций (таких
соответствующие
+=). Операции
операции
как
+)
автомати¬
присваивания
(та¬
могут быть перегружены только явным об¬
разом, а неявная перегрузка отсутствует.
18.4.
Отсутствие проверки и обхода самоприсваивания при перегрузке
операции присваивания класса, который содежит указатель на ди¬
намическую память.
18.5.
Отсутствие перегруженной операции присваивания и конструктора
копии класса, когда объекты этого класса содержат указатели на
динамическую размещаемую память.
Хороший
18.1.
стиль
программирования
Используйте перегрузку операции, когда
чем
грамму яснее,
явного
18.2.
вызова
при
выполнении
тех
это помогает сделать про¬
же
действий посредством
функции.
Избегайте избыточного или непоследовательного
регрузки операций, так как это может сделать
использования пе¬
программу
трудно
читаемой.
18.3.
Перегружайте операцию для
тами класса, или близких по
эта
18.4
операция
Перед
со
действий с объек¬
действий, которые выполняет
выполнения тех же
смыслу
встроенными
типами.
тем как писать программу на
С++, перегружающую опера¬
ции, изучите руководство по вашему компилятору, чтобы учесть
различные ограничения и
требования,
накладываемые на конкрет¬
ные операции.
18.5.
При перегрузке одноместных операций предпочтительнее делать
функции-операции элементами класса, а не дружественными фун¬
кциями, не являющимися элементами. Это более аккуратное реше¬
Нужно избегать дружественных функций и дружественных
классов, если только это не является крайне необходимым. Исполь¬
зование друзей нарушает инкапсуляцию класса.
ние.
18.6.
операция присваивания и конструктор копии класса
обычно реализуются как единая группа.
Деструктор,
Глава 18
796
Советы по повышению
18.1. Операцию
эффективности
функцию, не являющуюся эле¬
дружественной функцией, но, если такой функции тре¬
буется доступ к закрытым или защищенным данным класса, ей
придется обращаться к открытому интерфейсу этого класса (функ¬
циям вроде get или set). Такое опосредованное обращение к дан¬
можно перегрузить как
ментом или
ным
18.2.
может
привести
к
потере
производительности.
Перегрузка операции конкатенации +=, которая получает единст¬
венный аргумент типа const char *, работает более эффективно,
преобразования с последующей конка¬
преобразований требуется меньше про¬
чем использование неявного
тенацией. Для
18.1.
они
менее
для
уязвимы
ошибок.
методические замечания
По меньшей мере один аргумент функции-операции должен быть
объектом класса или должен ссылаться на объект класса. Это дела¬
ет невозможным изменение способа воздействия операции на объ¬
екты
18.2.
и
кода
граммного
Общие
неявных
типов.
встроенных
Новые
ввода/вывода типов, определяемых пользова¬
добавлены в С++ без модификации объявлений
возможности
телем, могут быть
или элементов данных
private
в
любом
ostream
из классов
или
ist¬
Это способствует расширяемости С++ как языка программи¬
рования, что является одним из наиболее привлекательных аспек¬
ream.
тов
18.3.
С++.
Имеется
возможность
са другому.
как
закрытый
Упражнения
18.1.
Это
избежать присваивания одного объекта
класса.
элемент
для самоконтроля
Заполните пропуски
a) Предположим,
а +
в
следующих
что а и
b
предложениях:
переменные целого типа
b. Теперь предположим,
руем сумму
плавающей точкой
и мы
что с и
формируем сумму
с +
d
и мы
форми¬
переменные с
d. Очевидно,
данном случае две операции + используются для различных
Это
клас¬
можно сделать, определив операцию присваивания
является
b) Ключевое
примером
что в
целей.
.
начинает
слово
функции-операции.
c) Чтобы использовать операции
определение
перегружен¬
на объектах класса,
они должны
ной
быть
перегружены,
d)
регрузкой.
18.2. Поясните
18.3.
В
каком
за
исключением
операций
и
неоднозначность
контексте
может
операций
операций
не могут
«
и
использоваться
»
имя
быть
в
и
изменены пе¬
С++.
operator/
в
С++?
797
Перегрузка операций
18.4. (Верно/неверно) В С++ могут быть перегружены
ствующие
18.5.
Как
только уже суще¬
операции.
перегруженной операции
соотносится приоритет
в
С++
с
прио¬
ритетом первоначальной операции?
Ответы к упражнения для самоконтроля
18.1.
а) перегрузки операций
d) Приоритет,
18.2.
присваивание
(=),
адрес
(&),
Операция » является как операцией сдвига вправо, так и опера¬
цией извлечения из потока в зависимости от контекста. Операция
« является как
чи
18.3.
b) operator с)
ассоциативность
в
операцией
сдвига влево, так и
операцией переда¬
поток.
Для перегрузки операции:
это
было бы
дала бы новую версию операции
именем
функции, которая
/.
18.4. Верно.
18.5. Идентичны.
Упражнения
18.6. Приведите
как можно больше примеров неявной перегрузки опера¬
С. Приведите сколько сможете примеров неявной перегрузки
операций в С++. Дайте мотивированный пример ситуации, в кото¬
ций
в
рой
вы могли
бы
захотеть перегрузить операцию в
С++
явным
об¬
разом.
18.7.
Операциями С++, которые
18.8.
Для конкатенации строк требуются два операнда
не могут
быть перегружены,
являются
две строки, ко¬
торые должны быть соединены. В тексте мы показали, как приме¬
нить перегруженную операцию конкатенации, которая присоеди¬
второй объект String в конец первого объекта String, изменяя
первый объект String. В некоторых приложениях жела¬
тельно получить объединенный объект String без изменений двух
передаваемых аргументов String. Примените operator+ таким об¬
няет
тем самым
разом, чтобы были разрешены действия
stringl
18.9.
=
string2
+
типа
string3;
(Упражнение по оптимальной перегрузке операций) Чтобы оценить
осторожность, которая требуется при отборе операций для пере¬
грузки, перечислите операции С++, которые являются перегружае¬
мыми, и для
каждой операции перечислите
нения для каждого из нескольких классов,
этом курсе.
Мы предлагаем, чтобы
следующих
классов:
a) Array
b) Stack
c) String
вы
ее допустимые приме¬
которые вы изучили в
попробовали
это сделать для
798
Глава 18
После
выполнения
операции
классов.
имеют
этого
Какие операции
регруженных? Какие
прокомментируйте, какие
широкого разнообразия
небольшую ценность в качестве пе¬
упражнения
важное
значение
имеют
для
операции кажутся неоднозначными?
предыдущей задаче, в об¬
Перечислите все операции С++, которые яв¬
перегружаемыми. Для каждой из них напишите, какое
проведите процесс, описанный в
18.10.Теперь
ратном направлении.
ляются
действие, по вашему мнению, может
операцией наилучшим образом. Если
имеется
ших
эти
18.11 .(Проект) С++
но
все
несколько
данной
наилуч-
действия.
новые
языки.
Какие дополнительные опера¬
бы рекомендовать добавить
в С++, или в будущий
подобный С++, который поддерживал бы как процедурное,
вы
язык,
так и
список
представлено
язык в процессе развития, и, кроме того, постоян¬
разрабатываются
ции
в
внесите
вариантов,
быть
могли
объектно-ориентированное программирование? Напишите
ар¬
гументированное обоснование. Вы могли бы рассмотреть возмож¬
ность отправки ваших предложений в Комитет ANSI С++.
18.12.0дним
из
хороших
примеров
ции-операции вызова
привычную
форму
chessBoard
( )
записи
[row]
применения
перегруженной функ¬
является возможность использовать более
двумерных
массивов.
Вместо нотации
[column]
для массива объектов перегрузите функцию-операцию
образом, чтобы
chessBoard
18.13.Перегрузите
мент
можно
было
использовать
форму
column)
(row,
операцию индексации, чтобы вернуть
связанного
вызова таким
альтернативную
указанный
эле¬
списка.
операцию индексации, чтобы вернуть наибольший
элемент списка, второй по величине элемент, третий и т.д.
18.14.Перегрузите
18.15.Рассмотрите
класс
Complex,
показанный
на рис.
18.7. Этот
класс
позволяет производить
комплексными числами, кото¬
рые записываются в
+
ет
значение
действия над
форме realPart
imaginaryPart
*
i, где i
име¬
лГл.
образом, чтобы разрешить ввод и вывод
операций » и «
из
класса
(вы должны удалить
функцию печати).
b) Перегрузите операцию умножения таким образом, чтобы стало
a)
Измените
класс
таким
комплексных чисел посредством перегруженных
возможным
правилам
умножение двух
комплексных
c) Перегрузите
операции
==
и
по
стандартным
!= для сравнения комплексных
сел.
//
//
чисел
алгебры.
C0MPLEX1.H
класса
Определение
#ifndef C0MPLEX1__H
#define C0MPLEX1_H
class Complex
public:
{
Complex
чи¬
799
Перегрузка операций
0.0);
0.0, double
Complex(double
Complex operator+(const Complex &) const;
//
//
Complex operator-(const Complex &) const;
Complex &operator=(const Complex &);
void print() const;
// вычитание
// присваивание
=
=
//
.
private:
double
double
real;
// действительная
imaginary;
//
мнимая
конструктор
сложение
вывод
часть
часть
};
#endif
Рис. 18.7. Определение класса Complex (часть 1
//
4)
COMPLEXl.CPP
//
#include
класса
для
Определения функций-элементов
#include <iostream.h>
//
из
Complex
"complexl.h"
Конструктор
Complex::Complex(double
double
r,
i)
{
real
=
r;
=
imaginary
I;
}
//
Перегруженная
сложения
операция
Complex
Complex Complex::operator+(const
{
Complex sum;
sum.real
real
+
=
sum.imaginary
return
//
const
operand2.real;
imaginary
=
&operand2)
+
operand2.imaginary;
sum;
Перегруженная
вычитания
операция
Complex Complex::operator-(const Complex &operand2)
const
{
Complex diff;
real
diff.real
operand2.real;
operand2.imaginary;
imaginary
diff.imaginary
=
-
=
return
//
-
diff;
Пререгруженная
операция
=
Complex& Complex::operator=(const Complex &right)
{
real
right.real;
imaginary
right.imaginary;
=
=
return
//
Вывести
void
{
//
*this;
объект
конкатенацию
разрешить
Complex в
const
виде:
(а,
b)
Complex::print()
cout
<<
1('
<<
real
<<
"
",
<<
imaginary
<<
')';
Рис. 18.7. Определения функций-элементов для класса Complex (часть 2
из
}
4)
Глава 18
800
//
//
FIG18_7.CPP
Тестер для класса Complex
#include <iostream.h>
#include "complexl.h"
main ()
{
Complex
<<
cout
8.2),
y(4.3,
x,
"x:
1.1);
z(3.3,
";
print () ;
cout <<
"\ny:
";
у. print () ;
cout << "\nz:
";
x.
z.print();
x
=
+
у
z;
<<
cout
"\n\nx
x.print () ;
"
cout «
у.print () ;
cout <<
=
";
+
";
"
z
x
.print ()
=
-
у
+
z:\n";
;
z;
у
<<
cout
=
"\n\nx
x.print();
cout << "
у.print () ;
cout << "
=
";
-
";
=
z:\n";
-
у
z.print();
cout << 1\n1;
return
0;
}
Рис. 18.7. Тестер для
x:
(0,
у:
z:
(4.3,
8.2)
(3.3,
1.1)
x
=
у
(7.6,
x
=
(1,
класса
Complex (часть
3 из 4)
0)
+
z:
9.3)
-
у
7.1)
=
(4.3,
8.2)
+
(3.3,
1.1)
z;
=
(4.3,
8.2)
-
(3.3,
Рис. 18.7. Вывод программы-тестера для
1.1)
класса
Complex (часть
4 из 4)
компьютер может представлять целые числа в диа¬
пазоне приблизительно от -2 миллиардов до +2 миллиардов. Это
ограничение достаточно редко создает сложности. Но имеется мно¬
18.16.32-разрядный
го
приложений,
зовать намного
в которых хотелось
бы
иметь возможность исполь¬
более широкий диапазон целых
чисел.
ена возможность создавать новые типы данных.
В С++ встро¬
Рассмотрите
класс
801
Перегрузка операций
HugeInt, приведенный
и
после
на рис.
18.8. Тщательно изучите
этот класс
этого
a) Точно
b) Какие
опишите, как он
работает.
ограничения имеет класс?
c) Измените
образом, чтобы он мог обрабатывать про¬
(Подсказка: используйте связан¬
представления HugeInt)
класс таким
извольно большие целые числа.
ный
список
для
d) Перегрузите
операцию умножения *.
e) Перегрузите операцию
f) Перегрузите
//
все
деления
операции
/.
отношений
и
равенства.
HUGEINT1.H
Определение класса Hugeint
#ifndef HUGEINTl_H
#define HUGEINTl_H
//
#include <iostream.h>
class Hugeint {
friend ostream &operator<<(ostream &, HugeInt &);
public:
// конструктор преобразования
0);
HugeInt(long
=
HugeInt(const char *);
HugeInt operator+(HugeInt
private:
short integer[30];
};
&);
//
//
конструктор преобразования
суммирование
#endif
// HUGEINTl.CPP
// Определения Функций-элементов
и
друзей
для
класса
HugeInt
#include <stnng.h>
#include "hugeintl.h"
// Конструктор преобразования
Hugeint::Hugemt(long val)
{
for (int i
0; i <= 29; i++)
=
integer[i]
for
=
// инициализация
0;
29; val != 0
(i
val %
mteger[i]
=
=
val
/=
&&
i
>=
0;
i--)
массива
обнулением
{
10;
10;
}
char
HugeInt::HugeInt(const
*string)
{
int
for
i,
(i
j;
=
0;
i
integer[i]
<=
=
29;
0;
Рис. 18.8. Пользовательский
26
Зак.801
i++)
класс
сверхбольших
целых (часть 1 из
3)
Глава 18
802
for
(i
30
=
strlen(strmg), j= 0;
'0';
string[j]
-
=
integer[i]
29;
<=
i
i++,
j++)
-
}
//
Сложение
&op2)
HugeInt HugeInt::operator+(HugeInt
{
HugeInt temp;
int carry
0;
=
for
i
(int
i
29;
=
0;
>=
(temp.mteger[i]
if
i--)
{
+
integer[i]
=
temp.integer[i]
temp.integer[i]
1;
carry
>
%=
op2.integer[i]
+
carry;
9) {
10;
=
}
else
0;
=
carry
}
return temp;
}
ostream&
operator<<(ostream &output,
&num)
Hugeint
{
for
if
0;
(int i
// пропуск
=
;
(i
<=
(i
29);
30)
==
output
else
for ( ;
0;
<<
i
<=
29;
output
<<
num.integer
return
&&
0)
==
(num.integer[i]
нулей
начальных
i++)
[i];
output;
}
FIG18_8.CPP
// Тестер для класса HugeInt
//
#include <iostream.h>
#include "hugeintl.h 1
main()
{
HugeInt nl(7654321), n2(7891234),
n3 ("99999999999999999999999999999" )
n4("l"), n5;
cout
"nl
<<
"\nn3
is
"
<<
"\nn5
is
"
=
nl
+
cout
<<
nl
n5
"
<<
is
n2;
<<
<<
"
+
"
Рис. 18.8. Пользовательский
<<
nl
"
is
"\nn2
<<
n3
<<
"\nn4
<<
n5
<<
"\n\n";
<<
класс
n2
«
"
=
is
"
сверхбольших
<<
<<
"
,
n2
<<
n5
n4
<<
"\n\n";
целых (часть 2 из 3)
i++)
803
Перегрузка операций
cout
<<
n5
=
nl
+
cout
<<
nl
n5
=
n2
+
cout
«
n2
"
<<
n3
"
+
"
"\n=
<<
n4
<<
<<
9
«
"10000"
<<
(n3
+
n4)
<<
"\n\n";
9;
"
<<
"
+
"10000";
+
«
"
"
"
<<
"
=
<<
<<
"
n5
=
"
<<
"\n\n";
«
n5
«
"\n\n";
return 0;
}
nl
n2
n3
n4
n5
is
is
is
is
is
7654321
7891234
99999999999999999999999999999
1
0
7654321
+
7891234
15545555
=
99999999999999999999999999999
+
1
100000000000000000000000000000
=
7654321
+
9
7891234
+
10000
7654330
=
7901234
=
Рис. 18.8. Пользовательский класс
18.17.Создайте
класс
сверхбольших
целых (часть 3 из
rationalNumber (класс дробей)
3)
со следующими воз¬
можностями:
который предотвращает появление 0 в
сокращает или упрощает дроби, которые имеют
сокращаемую форму, и избегает появления отрицательных знаме¬
нателей.
a) Создайте
конструктор,
знаменателе
дроби,
b) Перегрузите операции сложения, вычитания, умножения
ния
для
этого
и деле¬
класса.
c) Перегрузите операции
18.18.0перация sizeof
отношения и равенства для этого класса.
объекте
String возвращает только размер эле¬
String, но не возвращает размер памяти
String, отведенной под строку при вызове new. Перегрузите опера¬
цию sizeof таким образом, чтобы возвращать размер объекта
String, включая динамически выделенную память.
на
ментов данных объекта
18.19.Изучите функции
дую из функций
библиотеки обработки строк С
как
часть
класса
и реализуйте каж¬
String. Затем примените эти
функции для обработки текста.
18.20.Разработайте класс Polynomial. Внутренним представлением Poly¬
nomial является связанный список членов полинома. Каждый член
содержит
коэффициент
и
степень.
Член
2x4
имеет коэффициент 2 и степень 4. Разработайте полный класс, со¬
держащий соответствующие функции конструктора и деструктора,
804
Глава 18
get. Класс должен определять следующие
перегруженные операции:
а также функции set
a) операцию
и
сложения
(+), чтобы
складывать два объекта
Polyno¬
mial;
b)
al
операцию вычитания
из
(-), чтобы
c) операцию присваивания,
mial другому;
d) операцию
mial;
e)
вычитать один
объект
Polynomi¬
другого;
умножения
чтобы присваивать один объект
(*), чтобы
операцию присваивания суммы
разности
(-=)
и
умножать два объекта
(+=),
Polyno¬
Polyno¬
операцию присваивания
операцию присваивания произведения (*=).
г
л
а
а
в
19
Наследование
Цели
Научиться созданию новых
свойства уже существующих.
классов,
наследующих
Понять важность наследования для реализации прин¬
ципа повторного использования программного кода.
Изучить
понятия
базовых
Изучить применение
здания
зовых.
производного
и
производных
классов.
наследования для
со¬
класса на основе нескольких
ба¬
сложного
806
Глава 19
Содержание
19.1.
Введение
19.2.
Базовые
19.3.
Защищенные
19.4.
и
классы
производные
элементы
Приведение указателей базового
производный класс
класса к указателям
на
функций-элементов
19.5.
Использование
19.6.
Переопределение
19.7.
Открытые,
19.8.
Непосредственные
19.9.
Применение конструкторов
19.10. Неявное
элементов
класса в
производном
классе
защищенные и закрытые базовые классы
и
косвенные
преобразование
19.11. Наследование
19.12. Композиция
19.13.0тношения
базового
в
в
и
базовые
классы
деструкторов
в
производных
объектов производного
класса к
классах
базовому
конструировании программного обеспечения
сравнении
«использует»
с наследованием
и
«знает»
19.14.Пример: Point, Circle, Cylinder
19.15. Сложное наследование
Резюме
Распространенные
Совет
программирования
ческие замечания
для самоконтроля
ошибки программирования
по повышению
Упражнения для
Упражнения
19.1.
В этой
и
Хороший
эффективности
самоконтроля
Ответы
стиль
Общие методи¬
на
упражнения
Введение
обсудим две самые существенные особенно¬
объектно-ориентированное программирование
полиморфизм. Наследование представляет собой механизм
следующей
главах мы
сти, которые характеризуют
это
наследование
и
повторного использования программного обеспечения,
в соответствии с кото¬
рым новые
свойства и
дополнительно новые,
классы создаются на основе существующих.
поведение
необходимые
базовых
классов и
приобретают
Эти
классы наследуют
для новых классов, качества. Возможность повторного использо¬
вания программного обеспечения
сберегает
время, затраченное на его разра¬
807
Наследование
ботку, способствует повторному
использованию
ного высококачественного программного
плутационных
богатые
проблем, когда система начинает функционировать. Это сулит
Полиморфизм позволяет нам писать программы в об¬
обработки большого разнообразия существующих и определяе¬
дальнейшем
в
и отлажен¬
и уменьшает число экс-
возможности.
щем виде для
мых
апробированного
обеспечения
логически
связанных
классов.
Наследование
физм представляют собой эффективные методики для
и
полимор¬
разработки
сложных
программных систем.
При
создании нового класса вместо того, чтобы писать совершенно новые
элементы данных и
функции-элементы, программист
может просто указать,
что новый класс должен наследовать элементы ранее определенного базового
класса.
Этот новый класс называется
водный
производным
классом.
Каждый произ¬
класс, в свою очередь, может быть базовым для каких-то
будущих
При простом наследовании производный класс получа¬
ется на основе только одного базового класса. При сложном наследовании про¬
изводный класс наследует свойства от многих (возможно, логически не свя¬
занных) классов. Новый класс, как правило, вводит свои элементы данных и
функции, поэтому производный класс, вообще говоря, «больше» исходного ба¬
зового класса. Новый производный класс имеет больше специфических
производных классов.
свойств в сравнении с исходным базовым и представляет меньшую группу объ¬
ектов. В случае простого наследования производный класс в своей основе оста¬
ется по существу таким же, как базовый класс. Настоящая сила наследования
определяется возможностью добавлять, замещать и уточнять наследуемые от
базовых
классов свойства.
Каждый объект производного класса является также объектом базового
класса, из которого получен этот производный класс. Однако противополож¬
объекты базового класса не являются объектами производных
«объект
базового объекта. Мы воспользуемся этим отношением
производного класса является объектом базового класса», чтобы выполнять
ное неверно
классов
некоторые
много
интересные
разных
списке
объектов базового
одним общим
вах,
преобразования. Например,
объектов,
это
связанных
Это
класса.
образом. Как
является
мы в
одним из
отношением
позволит
мы
можем
наследования,
обрабатывать
перечислить
в
связанном
разные объекты
в этой и следующей гла¬
объектно-ориентированного
дальнейшем увидим
важнейших методов
программирования.
Мы введем в этой главе новое средство для управления доступом к элемен¬
ту
речь идет о защищенном доступе. Производные и дружественные им
классы
имеют
право
доступа
к
защищенным
права
функций
Опыт построения программных
остальных
такого
элементам
базового класса; у
нет.
систем
показывает,
что
большие
части
программы имеют дело с тесно связанными специальными случаями. В таких
системах трудно увидеть «целую картину», потому что проектировщик и про¬
заняты своими частностями. Объектно-ориентированное програм¬
мирование предоставляет некоторые средства для того, чтобы «за деревьями
видеть лес». Один такой процесс, позволяющий «видеть лес», называют ино¬
граммист
гда
абстракцией.
Если программа перегружена
тесно связанными специальными случаями,
обычно бросаются в глаза переключатели switch, которые распознают эти
специальные случаи и обеспечивают обрабатывающую логику, которая зани¬
мается каждым случаем по отдельности. В главе 20 мы покажем, как, исполь¬
то
808
Глава 19
зуя наследование и полиморфизм, более просто реализовать логику, содержа¬
щую операторы switch.
Мы делаем различие между отношениями «является» и «имеет».
относится к наследованию.
класса можно обрабатывать
В отношении
как
ет» представляет композицию
са содержит один или несколько
Производный
объект
(см.
рис.
Первое
объект типа производного
базового класса. Отношение «име¬
«является»
типа
17.4). В
отношении «имеет»
объектов других
объект
клас¬
классов в качестве элементов.
класс не может иметь доступа к закрытым элементам своего
базового класса; такой доступ нарушал бы инкапсуляцию последнего. Однако
производный класс может иметь доступ к открытым и защищенным элемен¬
там базового класса. Элементы базового класса, доступ к которым не разрешен
для производного класса через отношение наследования, должны быть объяв¬
лены в базовом классе как закрытые. Производный класс может иметь доступ
к закрытым элементам базового класса только через специальные функции
доступа, предоставляемые открытым интерфейсом базового класса.
Одна проблема с наследованием заключается в том, что производный
класс может наследовать открытые функции-элементы, которые не нужно
или не следовало
зового класса
переопределен
Наиболее
бы
иметь в «потомстве» явно.
Если какой-нибудь
элемент
не подходит для производного класса, этот элемент может
в самом производном классе с
ба¬
быть
соответствующей реализацией.
замечательна сама идея, что новые классы могут наследовать от
библиотек классов. Организации разрабатывают свои собст¬
библиотеки классов, а могут использовать другие библиотеки, доступ¬
ные во всем мире. Есть точка зрения, что когда-нибудь программное обеспече¬
ние
будут создавать из стандартизованных повторно используемых
существующих
венные
компонентов точно так же, как сегодня часто
аппаратуру. Это помо¬
жет удовлетворить
мощного программно¬
собирают
разработке еще более
понадобится в будущем.
требования
го обеспечения, которое нам
по
19.2. Базовые и производные классы
Часто объект одного класса «является» также и объектом другого класса.
Прямоугольник
rectangle, конечно же, является четырехугольником
quadrilateral (как являются и квадрат, и параллелограм, и трапеция). Таким
образом, о классе Rectangle можно сказать, что он наследует классу Quadrila¬
teral. В этом смысле класс Quadrilateral называется базовым, а класс Rectan¬
gle
называется
производным. Прямоугольник
является специальным типом
четырехугольника, но нельзя исходя из этого утверждать, что четырехуголь¬
ник
является
прямоугольником.
Рис. 19.1
показывает
примеров отношения наследования.
Базовый класс
Производные
Студент
Живущий
в городе
Живущий при
Фигура
классы
колледже
Круг
Треугольник
Прямоугольник
несколько
простых
809
Наследование
Базовый
Производные
класс
Ссуда
классы
Ссуда
на
автомобиль
Ссуда
на
ремонт дома
Ссуда
по
закладной
Член факультета
Сотрудник
Вспомогательный персонал
Счет
Текущий
счет
Сберегательный
Рис. 19.1.
счет
Некоторые простые примеры наследования
Другие языки объектно-ориентированного программирования, такие, как
Smalltalk, используют другую терминологию: в отношении наследования базо¬
вый класс называется надклассом, а производный класс
подклассом. По¬
скольку наследование обычно образует производные классы «больше» базо¬
вых, термины «надкласс» и «подкласс», по-видимому, не очень удачны; мы
будем избегать их применения.
к
Наследование приводит
вый
класс
классами.
класс
находится
Один
в
древовидным иерархическим структурам. Базо¬
иерархическом
класс, конечно,
включается
в
механизм
отношении со
своими
может существовать сам по
наследования,
классом, который определяет свойства
он
становится
производными
себе,
но
когда
либо базовым
других классов, либо про¬
и поведение.
чьи-то
свойства
наследует
и поведение
который
простой пример иерархии наследования (рис. 19.2 ). В ти¬
пичном университетском сообществе тысячи людей, которые являются его
членами (CommunityMember). Эти люди
сотрудники (Employee) и студенты
относятся
к
либо
(Student). Сотрудники
профессорско-преподавательскому со¬
либо к вспомогательному пер¬
ставу
сотрудники факультетов (Faculty),
соналу (Staff). Факультетские сотрудники являются либо администраторами
(Administrator), либо преподавателями (Teacher). Это дает иерархию наследо¬
изводным классом,
Построим
один
вания, показанную на рис. 19.2.
Другой важный
пример иерархии наследования дает понятие формы
19.3. Как правило, студен¬
частично иерархия этого класса показана на рис.
ты, впервые изучающие объектно-ориентированное програмирование,
что
в
Именно
то
ют,
реальном
мире
существует
огромное
число
примеров
замеча¬
иерархий.
обстоятельство, что они никогда не задумывались о такой категори¬
зации мира, приводит к необходимости перестройки их мышления.
Для того, чтобы указать, что класс CommissionWorker получается из
Employee, класс CommissionWorker обычно определяется следующим
класса
образом:
class CommissionWorker
:
public Employee
{
};
Это пример открытого наследования. Вероятно, читателю часто придется
Мы также будем говорить о защищенном и закрытом насле¬
им пользоваться.
довании. В случае открытого наследования открытые
и защищенные элемен¬
Глава 19
810
Член сообщества
Студент
Сотрудник
Персонал
Факультет
Администратор
Преподаватель
Рис. 19.2. Иерархия наследования для
членов университетского
сообщества
Форма
Двумерная форма
Квадрат
Круг
Трехмерная форма
Треугольник
Рис. 19.3. Часть иерархии наследования
ты
базового
Куб
Сфера
для класса
Тетраэдр
Shape (Форма)
класса наследуются в качестве открытых и защищенных элемен¬
тов производным классом.
Можно рассматривать объекты базового
класса аналогичным
базового
класса и
выражается
в
объекты производного
атрибутах
и поведении
Объекты любых открыто-производных классов общего базо¬
можно рассматривать как объекты этого базового класса. Мы рас¬
класса.
вого класса
смотрим
образом. Общность
много примеров, в которых мы используем достоинства этого отноше¬
ния, заключающегося в легкости программирования,
ектных языках,
каким является
19.3. Защищенные
Все функции программы
недостижимой
в
не-объ-
С.
имеют доступ
элементы
к
открытым
элементам базового
Доступ к закрытым элементам базового класса имеют только элемен¬
ты-функции самого класса и его «друзья».
Защищенный доступ представляет собой промежуточный уровень защиты
между закрытым и открытым доступом. Защищенные элементы базового
класса могут быть доступны только для элементов и друзей самого класса и
для элементов и друзей производных классов.
класса.
Элементы производного класса могут вызывать открытые
элементы
базового
использовать операцию разрешения области
ся в виду
При
по
действия
класса просто по имени элемента.
текущий объект.
и защищенные
этом не
обязательно
умолчанию имеет¬
811
Наследование
Приведение указателей базового
19.4.
к
указателям
Объект производного
вать как
производный
на
класса
класс
класса с открытым наследованием можно
объект соответствующего базового
класса.
Это
обрабаты¬
позволяет выполнять
Например, несмотря на то, что объекты
целого ряда классов, полученных из некоторого базового класса, могут совер¬
шенно отличаться друг от друга, мы все же можем создать из них связанный
если мы будем рассматривать их как объекты базового класса. Од¬
список,
некоторые интересные манипуляции.
нако обратное неверно: объекты базового класса не являются автоматически
объектами производного.
Распространенная ошибка программирования 19.1
Использование объекта базового класса как объекта производного класса.
Однако программист может применить явное приведение типа, чтобы пре¬
указатель базового класса в указатель производного. Но эту опера¬
если такой указатель должен быть разыме¬
цию надо выполнять аккуратно
образовать
нован,
то нужно
быть уверенным,
что он указывает на
объект производного
класса.
Распространенная ошибка программирования 19.2
Использование элементов производного класса, не определенных в базовом классе,
после
преобразования
изводного
указателя объекта базового класса
к
указателю
на
объект про¬
класса.
Наш первый пример
показан на рис.
19.4,
части
1-5. Части 1
и
2
показы¬
функций-элементов. Части 3
и 4 показывают определение класса Circle и определения функций-элементов
Circle. Часть 5 представляет программу-тестер, в которой иллюстрируется
вают определение класса Point и определения его
присваивание указателя производного класса указателям базового класса и
приведение
указателей базового
класса к указателям производного класса.
Сначала рассмотрим определение
класса Point (рис. 19.4, часть 1). Откры¬
тый интерфейс для Point содержит функции-элементы setPoint, getX и getY.
Элементы-данные x и у класса Point указаны как защищенные
protected.
Это не позволяет пользователям объектов класса Point иметь прямой доступ к
данным, но разрешает его производным классам непосредственно обращаться
к унаследованным элементам данных.
как закрытые
private,
Если бы
открытые функции-элементы
женной операции передачи
были определены
было бы вызывать
эти данные
то для доступа к данным нужно
Point. Заметьте, что функция перегру¬
объекта Point (рис. 19.4, часть 2) может
класса
в поток
прямо ссылаться на переменные x и у несмотря на то, что они
защищенные
элементы класса Point. Заметьте также, что необходимо ссылаться на x и у че¬
рез объект (то есть использовать запись p.x и p.y, ) так как функция перегру¬
женной операции вставки в поток является
Circle, а дружественной ему функцией.
не
функцией-элементом
класса
812
Глава 19
//
//
.H
POINT
класса
Определение
Point
POINT_H
#define POINT_H
#ifndef
Pomt
class
friend
public:
Pomt
void
{
ostream
&operator<<(ostream
0) ;
0, float
(float, float);
const { return x;
float getX()
float getY()
protected:
float x, у;
const
{
return
y;
&);
// конструктор по умолчанию
// установка координат
=
=
(float
setPoint
Point
const
&,
}
//
получение
координаты
x
}
//
получение
координаты
у
// доступна производному классу
x
//
и
точки
-
у
координаты
};
#endif
Рис. 19.4.
//
POINT.CPP
//
Функции-элементы
Определение
класса
#include
<iostream.h>
#include
"point.h"
//
Constructor
for
Point::Point(float
class
а,
класса Point
(часть 1
из
5)
Point
Pomt
float
b)
{
x
у
//
=
а;
=
b;
Set
void
coordinates
of
Point
Point::setPoint(float
а,
float
x
=
а;
=
b;
and
у
b)
// Вывод Point (перегруженная операция передачи в поток)
ostream &operator<<(ostream &output, const Point &p)
{
output
<<
<< p.x <<
return
output;
'['
",
11
<<
p.y
<<
1
]
1
// разрешает конкатенацию
;
вызовов
}
Рис. 19.4. Определения функций-элементов для
Класс Circle (Рис. 19.4,
часть
того наследования. Это указано в
class
Circle
:
public Point
Двоеточие (:)
слово public
класса Point
(часть 2
из
5)
3) выводится из класса Point по типу
первой строке определения класса:
{
// Circle
наследует
от
откры¬
Point
в заголовке определения класса указывает на наследование.
Ключевое
определяет тип наследования
(см.
раздел
19.7). Все
интерфейс
эле¬
Point наследуются классом Circle. Это означает, что открытый
класса Circle включает в себя открытые функции-элементы класса
менты класса
Point и, кроме того,
функции-элементы area, setRadius
и
getRadius.
813
Наследование
//
CIRCLE.H
//
Определение
класса
Circle
#ifndef
CIRCLE_H
#define CIRCLE_H
#include
<iostream.h>
#include
<iomanip.h>
#include
"point.h"
class
: public Point {
// Circle
ostream &operator<<(ostream &,
Circle
friend
public:
// конструктор
Circle(float
r
по
=
от
наследует
const
Circle
Point
&.);
умолчанию
float
0.0,
=
x
void setRadius(float);
float getRadius() const;
float area() const;
0,
float у
=
0);
// установка радиуса
// возврат радиуса
// вычисление площади
protected:
float radius;
#endif
Рис. 19.4. Определение
//
Конструктор Circle вызывает конструктор Point
затем инициализирует
с инициализатором элементов,
Circle::Circle(float
:
{
//
Point(a,
radius
=
r;
Установить
void
r,
float
//
b)
вызов
радиус
const
// Вычислить площадь Circle
float Circle::area() const
*
radius *
{ return 3.14159
Вывести Circle
базового
конструктора
в
форме:
Radius
{
radius
{
return
radius;
~
r;
}
radius;
[x,
y];
=
<<
"
"];
<<
setprecision(2)
return output;
//
&c)
11
=
<<
Radius
}
}
#.##
ostream &operator<<(ostream &output, const Circle
{
[" << c.x << ",
c.y
output << "Center
=
радиус.
b)
Circle
Circle::setRadius(float r)
// Center
float
а,
}
// Получить радиус Circle
float Circle::getRadius()
//
Б)
Circle
Определения функций-элементов
#include "circle.h"
//
из
CIRCLE.CPP
//
//
Cirde (часть 3
класса
=
<<
<<
setiosflags(ios::showpoint)
c.radius;
разрешает конкатенацию
вызовов
}
Рис. 19.4. Определения функций-элементов для класса Circle (часть 4
из
5)
Глава 19
814
//
//
FIG19_4.CPP
Приведение указателей базового
к
класса
производному
<iostream.h>
#include
<iomanip.h>
#include "point.h"
#include "circle.h"
#include
main ()
{
Point *pomtPtr, p(3.5, 5.3);
Circle *circlePtr, c{2.1, 1.2,
cout,<< "Pomt p:
//
Рассматривает
"
<<
Circle
"\nCircle
<<
p
8.9);
как
с:
"
<<
с
<<
endl;
Circle
//
(с промежуточным приведением)
// присвоить Circle указателю pomtPtr
&c;
pointPtr
circlePtr
// привести базовый
(Circle *) pointPtr;
// к производному
<<
cout
"\nArea of с (via circlePtr):
=
=
"
circlePtr->area()
<<
//
=
circlePtr
cout
<<
<<
return
=
of
с
как
Circle
// присвоить адрес Point указателю pomtPtr
&p;
// привести базовый
(Circle *) pointPtr;
// к производному
"\nRadius
of
circlePtr
object
circlePtr->getRadius()
<<
8.9]; Radius
(via circlePtr):
«
Конструктор Circle (см. рис. 19.4,
Point, чтобы инициализировать
используется
синтаксис
17, следующим образом:
Circle::Circle(float r,
:
"
Point(a,
b)
4/02e-38
часть
4)
к указателям производного
должен вызывать конструктор
данные базового класса объекта Circle.
Для
в
гла¬
инициализатора
float
//
2.70
22.90
Рис 19.4. Приведение указателей базового класса
(часть Б из Б)
ве
to:
endl;
Radius of object circlePtr points to:
этого
points
0;
Point p: [3.5, 5.3]
Circle с: Center = [1.2,
Area
endl;
Рассматривает Point
ОПАСНО:
pointPtr
<<
а,
вызов
float
элемента,
описанный
b)
базового
конструктора
Вторая строка заголовка функции конструктора вызывает конструктор
Point по имени. Значения параметров а и b передаются конструктором Circle
конструктору Point, чтобы непосредственно инициализировать элементы x и у
базового класса. Заметьте, что перегруженная функция операции передачи в
поток Circle может непосредственно обращаться к элементам x и у, потому что
они являются
защищенными
элементами
класса
Point,
а
функция дружест¬
815
Наследование
венна производному от него классу.
ся на элементы x и у через
Заметьте также,
объект (c.x
и
что
необходимо
ссылать¬
c.y), поскольку функция перегружен¬
ной
операции помещения в поток не является элементом класса Circle. Она то¬
лько дружественна ему.
В приведенной программе-тестере (рис. 19.4, часть 5) создается указатель
на объект Point и объект p класса Point, затем создается указатель circlePtr на объект Circle и экземпляр объекта с класса Circle. Объекты классов
Point и Circle выводятся с помощью перегруженных операций передачи в по¬
ток, чтобы показать правильность инициализации. Затем выполняется присва¬
pointPtr
ивание указателя производного класса
зового класса и приведение указателя
(адрес объекта с) указателю pointPtr ба¬
pointPtr обратно к указателю на класс
Circle*. Результат операции приведения присваивается указателю circlePtr.
Площадь объекта с класса Circle выводится через указатель circlePtr. Это при¬
водит к правильному значению площади, так как указатели все время
ссылаются на объекты производного класса. Присваивание указателя производ¬
ного класса указателю базового всегда допустимо, так как объект производного
класса является объектом базового класса. Указатель базового класса позволя¬
ет «видеть» только ту часть производного класса, которая унаследована от базо¬
вого. Компилятор сам выполняет неявное преобразование указателя производ¬
ного класса в указатель базового класса. Указатель базового класса нельзя пря¬
мо присвоить указателю производного класса, так как такое присваивание по¬
тенциально опасно
предполагается, что указатели производного класса ука¬
зывают на объекты производного класса. В этом случае компилятор не выпол¬
няет неявного преобразования. Использование явного приведения показывает
компилятору,
указателя
что программист осознает
опасность
подобного преобразования
и учитывает все возможные его последствия.
Затем программа демонстрирует присваивание адреса объекта p класса
Point указателю базового класса pointPtr и приведение pointPtr к типу Circ¬
le*. Результат операции приведения присваивается указателю circlePtr. Ради¬
ус объекта, указанного circlePtr (объекта p класса Point) выводится через circ¬
lePtr. Это приводит к неправильному результату, так как все указатели ссы¬
лаются на объект класса Point. Объект класса Point не имеет элемента radius.
Поэтому
программа выводит любое значение, которое находится
том месте, где, как считает указатель
19.5.
circlePtr,
в
памяти в
находится элемент radius.
Применение функций-элементов
Если производный
класс
образуется из базового класса, функциям-эле¬
потребоваться доступ к определенным эле¬
ментам производного класса может
ментам базового класса.
Общее методическое замечание 19.1
Производный класс
базового
не может иметь прямого доступа
к закрытым элементам своего
класса.
Это критический аспект технологии программного обеспечения в С++.
Если бы производный класс мог иметь доступ к закрытым элементам базового
класса, то это нарушало бы инкапсуляцию последнего. Возможность прятать
закрытые
элементы
дает огромные преимущества
в отношении
тестирования,
Глава 19
816
отладки и
корректной модификации
иметь доступ к закрытым элементам
систем.
Если бы производный класс мог
то оказалось бы возмож¬
базового класса,
ным, что классы, полученные из этого производного класса, имели
доступ и к этим данным и т.д.
Это открыло бы доступ
считаются закрытыми, и, таким образом,
бы потеряно во всей иерархии классов.
19.6.
Переопределение
в
Когда
тически
также
которые
преимущество инкапсуляции было
элементов
производном
базового
класса
классе
функцию-элемент базового
употребляется имя этой функции, автома¬
выбирается функция производного класса. Для доступа из производ¬
Производный
класса.
бы
к тем данным,
класс
может
переопределить
в производном классе
ного класса к функции базового класса можно использовать операцию разре¬
шения области действия.
Распространенная ошибка программирования 19.3
Когда функция-элемент базового класса переопределяется в производном классе,
функция производного класса обычно вызывает одноименную функцию базового и
выполняет некоторую дополнительную работу. В случае, когда для вызова функции
базового класса не используется операция разрешения области действия, возникает
бесконечная рекурсия, так как функция-элемент производного класса фактически вы¬
зывает саму себя.
Хороший
стиль программирования 19.1
в производном классе наследуются ненужные свойства, маскируйте их, переоп¬
ределяя функции.
Когда
Общее методическое замечание 19.1
Переопределение функции-элемента в производном классе не
сигнатуры с функцией базового класса
Рассмотрим упрощенный
трудников
firstName
и
класс
Employee. Он
хранит имена
lastName. Эта информация
сотрудников, в том числе и сотрудников,
требует одинаковой
информация
является
и фамилии со¬
общей для всех
о которых содержится в
Employee классах. Из базового класса Employee построим те¬
сотрудников HourlyWorker, PieceWorker, Boss и CommissionWor¬
производных от
перь классы
ker. Сотрудники-почасовики HourlyWorker получают почасовую заработную
плату и в случае, когда их переработка в неделю превышает 40 часов, эти до¬
полнительные часы оплачиваются по расценкам, в
полтора раза превышающим
обычные. Класс сотрудников со сдельной оплатой PieceWorker получает фик¬
для простоты предполо¬
сированную оплату за единицу выполненной работы
жим, что
каждый
из них выполняет только один вид
работы,
так что закрытые
величиной опла¬
Класс сотрудников-начальников Boss получает фиксированную
недельную ставку. Класс сотрудников по продаже CommissionWorker получает
небольшую фиксированную недельную плату и дополнительно фиксированный
элементы-данные
ты за единицу.
будут
количеством произведенных единиц и
817
Наследование
процент
Программный
Части 1
Для простоты
HourlyWorker.
с проданного за неделю товара.
класса сотрудников
2
Employee
и
рассмотрим только два
19.5,
код для этого примера приведен на рис.
части
1-5.
Employee и определения его эле¬
ментов-функций. Части 3 и 4
определение класса HourlyWorker и опреде¬
ления его функций-элементов. Часть 5 показывает программу-тестер иерар¬
хии наследования Employee / HourlyWorker, который просто создает экземп¬
ляр объекта класса HourlyWorker, инициализирует его и вызывает функ¬
цию-элемент print класса HourlyWorker для вывода данных об объекте.
//
и
показывают определение класса
EMPLOY.H
Определение класса
#ifndef EMPLOY_H
#define EMPLOY_H
//
class
Employee
Employee
{
public:
Employee(const char*,
void pnnt() const;
//
//
char*);
вывод
имени
и
конструктор
фамилии
// деструктор
~Employee ();
private:
char *firstName;
char
const
*lastName;
//
динамически
выделяемая
строка
//
динамически
выделяемая
строка
};
#endif
Рис. 19.5.
Определение
класса
Определение
класса
Employee (часть
Employee (рис. 19.5,
char
*
часть
firstName
1)
1 из 5)
состоит из двух закры¬
lastName
и трех функций-элементов
конструктора, деструктора и функции print. Конструктор
(рис. 19.5, часть 2) получает две строки и выделяет динамически массивы сим¬
волов для хранения строк. Заметьте, что макрокоманда assert (рассмотренная
в главе 14) используется здесь для того, чтобы определить, была ли выделена
тых элементов-данных типа
память для размещения firstName
и
и
lastName. В противном случае програм¬
сообщением об ошибке,
ма
в котором указаны проверяемоеое
завершается
условие, номер строки, где возникло условие, и файл, в котором производится
проверка. Поскольку данные класса Employee закрытые, доступ к ним возмо¬
жен только через функцию-элемент
имя и
print, которая просто выводит на печать
фамилию служащих. Деструктор возвращает системе динамически вы¬
деленную
область
памяти.
Класс HourlyWorker (рис. 19.5,
открытым наследованием.
первой
Это,
часть
3)
выводится из класса
Employee
с
как и в предыдущем примере, указывается в
строке определения класса:
class HourlyWorker
:
public Employee
класса HourlyWorker включает функцию print
Employee и функции-элементы getPay и print класса HourlyWorker.
Обратите внимание, класс HourlyWorker определяет свою собственную функ¬
цию print. Таким образом, класс имеет доступ к двум функциям print. Кроме
того, класс HourlyWorker содержит закрытые элементы-данные wage и hours
Открытый интерфейс для
класса
для
вычисления
еженедельной заработной
платы сотрудников.
818
Глава 19
// EMPLOY.CPP
// Member function definitions
#include <string.h>
#include
<iostream.h>
#include
#include
<assert.h>
for class
Employee
"employ.h"
//
//
имени
и
//
имени
и
Конструктор динамически выделяет пространство для
фамилии
фамилии
и
вызывает
в
объект.
strcpy
для
Employee::Employee(const char *first,
копирования
const
char
*last)
{
firstName
=
new
assert(firstName
char[ strlen(first) +
!= 0); // прерывание,
lastName
new
char[ strlen (last) + 1
(lastName != 0); // прерывание,
=
assert
//
Вывод
void
{
//
<<
firstName
'
'
<<
lastName;
Деструктор освобождает выделенную
[]
выделена
память
не
выделена
};
если
const
<<
Employee::~Employee ()
{
delete [] firstName;
delete
не
сотрудника
Employee::print()
cout
память
last);
(lastName,
имени
если
first);
strcpy(firstName,
strcpy
];
1
lastName;
}
память
// освободить
динамическую
память
//
динамическую
память
освободить
}
Рис. 19.5.
//
Определения функций-элементов
класса
Employee (часть
2 из 5)
HOURLY.H
Определение класса
#ifndef HOURLY_H
#define HOURLY_H
//
#include
HourlyWorker
"employ.h"
class HourlyWorker : public Employee {
public:
HourlyWorker (const char*, const char*, float, float);
float getPay()
const; // вычисление и возврат значения
// зарплаты
void print () const;
// переопределенная print
private:
float wage;
// почасовая ставка
float
hours;
//
рабочие
часы
за
неделю
};
#endif
Рис. 19.5. Определение класса HourlyWorker (часть 3 из 5)
819
Наследование
// HOURLY_B.CPP
// Определения функций^элементов HourlyWorker
#include <iostream.h>
#include <iomanip.h>
#include "hourly.h"
// Конструктор для HourlyWorker
HourlyWorker::HourlyWorker(const char *first,
float
:
Employee
(first,
//
last)
mitHours,
вызов
const
float
базового
char
*last,
initWage)
конструктора
{
hours
wage
=
initHours;
initWage;
=
}
// Получить зарплату HourlyWorker
float HourlyWorker::getPay() const
//
Напечатать
имя
и
для
плату
void HourlyWorker::pnnt()
{
return
*
wage
hours;
}
HourlyWorker
const
{
cout
<<
"HourlyWorker::print()\n\n";
Employee::pnnt();
cout
<<
<<
<<
"
is
an
"
$"
<<
//
вызов
hourly worker
pnnt
базовой
with
pay
of"
setiosflags(ios::showpoint)
setprecision(2) << getPay() << endl;
}
Рис. 19.5. Определения функций-элементов класса HourlyWorker (часть 4
// FIG19_5.CPP
// Переопределение функций-элементов
#include
<iostream.h>
#include
"hourly.h"
main
в
производном
из
5)
классе
()
HourlyWorker
h("Bob",
"Smith",
40.0,
7.50);
h.pnnt () ;
return 0;
}
HourlyWorker::print()
Bob
Smith
is
an hourly worker with pay of
$300.00
Рис. 19.5. Переопределение функции-элемента базового класса в производном классе
(часть 5 из 5)
Конструктор HourlyWorker (рис. 19.5,
часть 4) использует синтаксис ини¬
и last конструктору класса Em¬
first
элемента
для передачи строк
циализатора
ployee, чтобы инициализировать элементы базового класса, затем инициали¬
зирует элементы wage и hours.
ную плату для
HourlyWorker.
Функция-элемент getPay
вычисляет
заработ¬
820
Глава 19
Функция-элемент print класса HourlyWorker представляет собой пример
переопределения функции-элемента базового класса в производном классе.
Часто функции-элементы базового класса переопределяются в производном
классе для того, чтобы придать последнему большие функциональные воз¬
можности.
Переопределяемые функции
иногда вызывают
для выполнения части своей задачи. В этом примере
базовую функцию
функция print производ¬
функцию print базового для вывода на печать имени со¬
(эта функция print базового класса является единственной функ¬
ного класса вызывает
трудника
цией
с доступом к закрытым данным
водного класса, кроме того, выводит
ка.
Обратите
базового класса). Функция print произ¬
заработную плату сотрудни¬
функция print базового класса:
на печать и
внимание, как вызывается
Employee::print();
Поскольку функции базового и производного классов имеют одинаковые
имена, функции базового класса должно предшествовать имя этого класса с
операцией разрешения области действия. В противном случае будет вызвана
функция производного класса, что приведет к бесконечной рекурсии (функ¬
ция print класса HourlyWorker вызывала бы саму себя).
19.7.
Открытые,
защищенные
и
закрытые базовые
классы
При выводе из базового класса производный класс может наследовать от¬
крытый, защищенный либо закрытый базовый класс. Защищенный и закры¬
тый
тип наследования редко используются и применять их следует с
ми предосторожностями.
крытый
При
В этой
больши¬
книге мы используем в примерах только от¬
тип наследования.
выводе класса из открытого базового класса открытые элементы базо¬
вого класса становятся открытыми элементами производного, а защищенные
элементы базового класса становятся защищенными элементами производно¬
го класса. Из производного класса к закрытым элементам базового класса ни¬
когда нет доступа.
При
выводе класса из защищенного базового класса открытые и защищен¬
ные элементы базового класса становятся защищенными элементами произ¬
водного.
При выводе класса из закрытого базового класса открытые и защищенные
элементы базового класса становятся закрытыми элементами производного.
19.8.
Непосредственные
Базовый
зовый
нии.
быть
базовые
классы
непосредственным
базовым классом про¬
или его косвенным базовым классом.
Непосредственный ба¬
класс может
изводного класса,
и косвенные
или
класс явно указывается в заголовке производного класса при его описа¬
Косвенный базовый
воря, косвенный
базовый
ня иерархии классов.
класс не указывается явно в заголовке
класс наследуется с некоторого
точнее го¬
более высокого уров¬
821
Наследование
19.9.
Применение конструкторов
в
производном
и
деструкторов
классе
Когда создается экземпляр объекта производного класса, то, поскольку
последний наследует элементы своего базового класса, необходимо вызывать
базового
конструктор
класса для инициализации
базовых
элементов
объекта
(производного класса). Конструктор производного класса может вызвать кон¬
структор базового класса либо неявно, либо можно предусмотреть в конструк¬
торе производного класса
инициализатор базового класса (который,
видели, использует синтаксис инициализатора
базового
конструктор
наследуются
присваивания
как мы
явно вызвать
класса.
Конструкторы базового
не
элемента), чтобы
класса и операции присваивания базового класса
производными
классами.
Однако конструкторы
производного класса могут вызывать конструкторы
и
операции
и операции
базового.
Конструктор производного класса всегда вызывает конструктор базового
класса, чтобы сначала инициализировать элементы базового класса в произ¬
присваивания
водном классе.
Если конструктор производного
класса опущен,
тор по умолчанию производного класса вызывает конструктор
са.
Деструкторы
вызываются в порядке,
обратном
то конструк¬
базового
клас¬
вызовам конструктора, поэ¬
тому деструктор производного класса вызывается прежде деструктора его ба¬
зового класса.
Общее
методическое замечание 19.3
Когда создается объект производного класса, сначала
класса, затем конструкторы для объектов
нец, конструктор производного класса
вызовам
соответствующих
са.
Порядок,
в котором
работает
конструктор базового
элементов производного класса и,
Деструкторы
вызываются в порядке,
нако¬
обратном
конструкторов
Согласно стандарту С++ порядок,
соответствует порядку,
-
в
котором создаются элементы-объекты,
внутри определения клас¬
перечисляются инициализаторы элементов, не влияет
в котором они перечислены
на конструирование.
При наследовании конструкторы базовых классов вызы¬
ваются в том порядке, в котором указывается наследование в определении
производного класса. Порядок, в котором указаны конструкторы базовых
классов в конструкторе производного, не влияет на конструирование.
Распространенная ошибка программирования
19.4
Создание программы, зависящей от конкретного порядка конструирования элемен¬
тов-объектов, может приводить к труднообнаружимым, тонким ошибкам
Программа
на рис.
19.6
показывает порядок, в котором вызываются кон¬
базового и производного классов. Программа состо¬
частей. Части 1 и 2 показывают простой класс Point, который со¬
структоры и деструкторы
ит из пяти
стоит из конструктора, деструктора и защищенных элементов данных x и у.
и деструктор оба печатают объект Point, для которого они вызы¬
Части 3 и 4 показывают простой класс Circle, полученный из класса
Point по типу открытого наследования, в котором содержатся конструктор,
Конструктор
ваются.
Глава 19
822
Конструктор и деструктор оба
печатают объект Circle, для которого вызваны. Конструктор Circle, кроме это¬
го, активирует конструктор Point по синтаксису инициализатора элемента и
деструктор и элемент закрытых данных radius
передает значения для а и
b, инициализируя
.
таким
образом
элементы данных
базового класса.
I
//
//
P0INT2.H
класса
Определение
Point
P0INT2_H
#define P0INT2_H
#ifndef
class
Point
{
public:
0.0,
=
Point(float
// конструктор по умолчанию
// деструктор
// доступны производным классам
// x и у
координаты точки
float
~Point();
protected:
float x,
} ;
у;
0.0);
=
-
#endif
Рис. 19.6.
Определение
// POINT2.CPP
// Определения функций
#include
<iostream.h>
#include
"point2.h"
// Конструктор Point
Pomt::Pomt(float а,
класса Point
класса
(часть 1
5)
из
Point
float b)
{
x
У
=
а;
=
b;
cout
<<
"Point
<<
<<
'['
"
constructor:
X
«
",
"
<<
у
<<
']'
<<
endl;
у
<<
']'
<<
endl;
}
//
Деструктор
Point
Point::~Point()
{
cout
<<
<<
"Point
'['
<<
destructor:
x
<<
",
"
"
<<
}
рис. 19.6.
На рис. 19.6,
Определения функций-элементов
часть
5
класса Point
показан тестер для иерархии
ма начинается с создания экземпляра
объекта Point
(часть 2
из
5)
Point / Circle. Програм¬
собственной области
в его
действия внутри main. Объект входит и немедленно выходит из области дейст¬
вия, поэтому сразу вызываются конструктор и деструктор. Затем программа
создает экземпляр объекта circlel класса Circle. Это активирует конструктор
Point с выводом на печать значений, переданных из конструктора Circle, за¬
тем выполняется печать, указанная в конструкторе Circle. После этого созда¬
ется экземпляр circle2 класса Circle. И снова вызываются оба конструктора
Point и Circle. Обратите внимание, что тело конструктора Point выполняется
823
Наследование
раньше,
чем
тело
конструктора Circle. Достигается конец программы main,
вызвать деструкторы для объектов circlel и circle2. Дест¬
поэтому необходимо
рукторы
порядке)
в
вызываются
рукторов.
порядке,
обратном
вызовам
соответствующих
том же порядке для
// CIRCLE2.H
// Определение
#ifndef
#define
объекта circlel.
класса
Circle
CIRCLE2_H
CIRCLE2_H
#include
"pomt2.h"
#include
<iomanip.h>
class Circle : public Pomt {
public:
!/ конструктор по умолчанию
(float
Circle
~Circle
private:
float
0.0,
=
r
float
x
=
0,
float
0);
=
у
// деструктор
();
//
radius;
радиус круга
};
#endif
Рис. 19.6. Определение
// CIRCLE2.CPP
// Определения функций
класса Circle
класса
(часть 3
5)
из
Circle
"circle2.h"
#include
//
конст¬
деструкторы Circle и Point вызываются (именно в таком
объекта
circle2, затем деструкторы Circle и Point вызываются в
для
Поэтому
Конструктор
Circle
вызывает
Pomt
конструктор
Circle::Circle(float r, float а, float b)
:
// вызов базового конструктора
Point(a, b)
{
radius
cout
=
r;
<<
"Circle
<<
radius
constructor:
<<
"
["
<<
// Деструктор Circle
Circle::~Circle()
{
cout << "Circle destructor:
<<
radius
<<
11
["
<<
x
а
radius
<<
"
",
"
is
<<
b
']'
<<
<<
endl;
"
radius
<<
",
is
"
<<
Рис. 19.6. Определения функций-элементов
у
<<
класса
'
']
<<
endl;
Cirde (часть 4
из
5)
824
Глава 19
//
//
FIGi9_6.CPP
Демонстрирует
//
и
порядок
#include
<iostream.h>
#include
"point2.h"
"circle2.h"
#include
main
вызова
и
базового
деструкторов
конструкторов
производного
классов.
()
Показать
//
вызовы
конструкторов
и
деструкторов
для
Point
{
Point
2.2) ;
p (1.1,
}
cout << endl;
Circle circlel(4.5,
cout
<<
Circle
cout
<<
return
2.9);
7.2,
endl;
circle2(10,
endl;
0;
5,
5);
Point constructor:
[1.1, 2.2]
Point destructor: [1.1, 2.2]
Point
Circle
constructor:
constructor:
[7.2, 2.9]
radius is 4.5
[5, 5]
radius
Point
constructor:
Circle
constructor:
Circle
Point
Circle
Point
destructor:
destructor:
destructor:
destructor:
Рис. 19.6. Порядок,
radius
[5, 5]
radius
[7.2,
is
10
is
10
is
4.5
[7.2,
[5,
[5,
2.9]
5]
5]
[7.2,
2.9]
2.9]
в котором вызываются конструкторы и
производного классов
(часть 5
из
деструкторы базового
и
5)
преобразование объектов
производного класса к базовому
19.10. Неявное
объект производного класса также «является» и объ¬
объектов производного класса и базового класса различ¬
Объекты производного класса можно обрабатывать как объекты базового
Несмотря
ектом
ны.
на то, что
базового,
класса.
Это
имеет смысл, так как
ответствующие
те, обычно
типы
каждому
производный
из
производный
класс содержит элементы, со¬
как вы помни¬
базового класса,
больше элементов, чем базовый класс. В
элементов
класс имеет
обратном направлении операция не имеет смысла, так как преобразование
объекта базового класса в объект производного класса оставило бы дополните¬
льные (по отношению к базовому классу) элементы неопределенными. Хотя
такое
преобразование «естественно» не допускается, ему можно придать
смысл при наличии соответствующей перегруженной операции присваивания
или конструктора преобразования.
825
Наследование
Распространенная ошибка программирования 19.5
Присваивание объекта производного
класса
и
затем
обращения
попытка
объекту
класса
к элементам,
соответствующего
наличным
только
в
базового
производном
классе
Указатель
объект производного класса
объект базового класса, так
объектом базового.
на
ван в указатель на
са является
Существует четыре
указателей базового
возможных
может быть неявно
как
способа для
смешивания
и
клас¬
согласования
производного классов с объектами базового
и
преобразо¬
объект производного
и
производ¬
ного классов:
1. Ссылка на объект базового класса с помощью указателя баз( вого клас¬
са естественна.
2. Ссылка на объект производного класса с помощью указателя производ¬
ного класса также естественна.
3. Ссылка на объект производного класса с помощью указателя базового
класса безопасна, так как объект производного класса является также
и
объектом базового. Программа
базового
личные
в
только
производном
класса, компилятор сообщит
4. Ссылка
может ссылаться только на элементы
класса. В случае, когда программа ссылается на элементы, на¬
на
объект базового
классе,
о
с
помощью
указателя
базового
синтаксической ошибке.
класса с помощью указателя производного
синтаксической ошибкой. Необходимо сначала привес¬
производного класса к указателю базового класса.
класса является
ти
указатель
Распространенная ошибка программирования
19.6
Приведение указателя базового класса к указателю производного класса может при¬
вести к ошибкам, если далее этот указатель используется для ссылки на объект базо¬
вого класса, который не содержит требуемых элементов производного класса
Как
ни
иногда манипулировать объектами производного класса
делать это с помощью указателей базового
удобно
как объектами
базового, причем
проблема. Например, в системе для платежных
обрабатывать связанный список сотрудников
еженедельную заработную плату для каждого персонально.
класса, тут существует одна
ведомостей было бы
и
рассчитывать
желательно
Однако использование указателей базового класса позволяет программе вызы¬
вать только процедуру базового класса для расчета платежей (если такая про¬
базовом классе имеется). Нам нужен метод активации нужной проце¬
дуры расчета для каждого объекта, вне зависимости от того, является ли он
цедура в
объектом базового
щью указателя
или производного класса, причем делать это
базового
туальных функций
и
просто
с помо¬
Такое решение достигается посредством вир¬
полиморфизма, как вы увидите в главе 20.
класса.
19.11. Наследование
в
конструировании
обеспечения
программного
Мы
ивать,
можем использовать механизм наследования для того, чтобы настра¬
т.е.
максимально
приспосабливать,
существующее программное обес¬
826
Глава 19
печение для конкретных целей. Мы наследуем свойства и поведение существу¬
ющего класса, а затем вводим или удаляем какие-то атрибуты и поведение,
чтобы получить нужный
водный
нам класс.
В
С++
можно сделать так, чтобы произ¬
класс не имел доступа к исходному коду
компоновки
необходимо, чтобы
программы
базового класса, однако для
он
имел
доступ
к
объектному
коду. Эта замечательная возможность привлекательна для независимых раз¬
работчиков программного обеспечения. Они могут разрабатывать собственные
классы
для продажи или
лицензирования
и поставлять
их
пользователям
в
объектном формате. Пользователи смогут быстро получать новые классы из
существующих библиотечных и в то же время не будут иметь доступа к исход¬
производителей. Все,
разработчикам потребуется
файлы.
Студентам трудно представить себе проблемы, с которыми сталкиваются
проектировщики и разработчики больших программных проектов. Люди, ис¬
ному коду
поставлять вместе с
что независимым
объектным кодом,
это заголовочные
кушенные в таких делах, постоянно утверждают, что ключом к совершенство¬
ванию процесса разработки является возможно более широкое повторное ис¬
пользование программного обеспечения. Объектно-ориентированное програм¬
мирование вообще, и С++ в частности, способствуют такому подходу.
Имеются и доступны очень полезные и хорошие библиотеки классов, предо¬
ставляющие максимальные возможности для повторного использования про¬
граммного кода. И поскольку интерес к С++ растет, то и интерес к библиотекам
классов также будет расти. Точно так же, как с разработкой программного обес¬
печения, поставляемого независимыми проектировщиками и разработчиками,
которая становится быстро развивающейся отраслью по мере распространения
будет обстоять дело и с созданием и продажей биб¬
Разработчики приложений будут строить их на основе библио¬
проектировщики библиотек классов будут вознаграждаться за
персональных компьютеров,
лиотек классов.
тек классов, а
поставку своих библиотек с этими приложениями. Поставляемые в настоящее
время с трансляторами библиотеки классов являются в основном библиотеками
общего назначения и имеют ограниченную сферу применения. То, что мы уви¬
ближайшее время
это всемирное массированное наступление в области
разработки библиотек классов для огромного множества сфер приложений.
дим в
Общее
методическое замечание 19.4
Создание производного класса не влияет на
го
класса;
Базовый
целостность
базового
класса
класс описывает нечто
исходный
при
или
объектный код
наследовании
обобщенное. Все
личных
разработчик
общность
ищет
базово¬
классы, полученные из
одного базового класса, наследуют его свойства. В процессе
рованного проектирования
его
сохраняется.
и
объектно-ориенти¬
выделяет
ее
в
раз¬
структурах, формируя базовые классы. Затем производные классы на¬
страиваются
приобретают
новые
свойства,
дополнительные к унаследован¬
ным от базового класса.
Точно так же, как проектировщик не ориентированных на объекты систем
стремится избегать
употребления ненужных функций, так и проектировщик
объектно-ориентированных классов должен избегать разрастания ненужных
классов. Такое разрастание классов создает проблемы управления ими и мо¬
жет мешать повторному использованию программного обеспечения просто по¬
тому, что усложняет поиск нужного класса в огромной библиотеке. Решение
заключается в том,
новыми
чтобы создавать
существенными
меньше классов,
дополнительными
снабжая каждый
из них
функциональными свойствами.
827
Наследование
Возможно,
такие классы для определенных
вать эту
бы
избыточность, «принижая»
таким
пользователей будут
В
сыщены потенциальными возможностями.
слишком на¬
этом случае они могут маскиро¬
образом свойства
классов так, что¬
это соответствовало их задачам.
Совет по повыщению
эффективности
19.1
образованные путем наследования классы больше, чем требуется, то это может
привести к ненужным расходам ресурсов памяти и снижению производительности
Если
Обратите
внимание
класса может вводить в
на то,
что
набора определений
чтение
заблуждение
производного
из-за того, что унаследованные элементы
не показаны, но тем не менее присутствуют в нем.
Общее
методическое замечание 19.5
объектно-ориентированной системе классы часто тесно связаны. «Вычеркните» об¬
атрибуты и модели поведения и поместите их в базовом классе, а затем исполь¬
зуйте наследование для построения производных классов.
В
щие
Общее
методическое замечание 19.6
Производный
класс содержит
атрибуты
и модели поведения своего
определять дополнительные
может также
атрибуты
и поведение
базового
класса
Он
Благодаря
наследова¬
нию базовый класс можно компилировать независимо от производного класса. Необхо¬
димо только компилировать затем вновь введенные атрибуты и поведение, чтобы мож¬
было
но
Общее
их
комбинировать
с
базовым
классом для
создания
производного
класса
методическое замечание 19.7
При модификации базового класса не требуется изменять производные
открытый интерфейс базового класса остается неизменным
классы, если
только
19.12. Композиция
Мы обсудили
Мы
с наследованием
сравнении
отношения типа
мом наследования.
объект
в
«является», они поддерживаются
также рассматривали отношения типа
механиз¬
«имеет»,
в них
может содержать другие объекты в качестве элементов. Такие отношения
Например, за¬
соответст¬
телефонных номеров
создают новые классы путем композиции существующих классов.
даны классы сотрудников,
венно
ployee
Employee, BirthDate
дней рождения
и
TelephoneNumber. Неправильно
«является» днем рождения
neNumber,
а
правильно,
день рождения и
Number.
Вспомните,
и
BirthDate
конечно,
телефонный
сказать,
телефонным номером Telepho¬
что
сотрудник
соответственно
номер
говорить, что Em¬
или
Employee «имеет»
и Telephone¬
BirthDate
что порядок, в котором конструируются элементы, задается тем,
в каком порядке эти
объекты объявляются. Конструкторы базовых
классов вы¬
зываются в порядке, в котором указывается наследование в производном классе.
Общее
методическое замечание 19.8
При модификации класса элемента
только открытый интерфейс
если
имейте
в
не
требуется
изменять его
класса-элемента
виду, что составной класс, возможно,
остается
потребуется
охватывающий класс,
Однако
неизменным
перекомпилировать
828
Глава 19
19.13. Отношения «использует»
и «знает»
И наследование,
и композиция способствуют повторному использованию
обеспечения
благодаря возможности создавать новые классы,
программного
имеющие много общего с существующими. Имеются и другие способы исполь¬
Конечно, нельзя сказать, что некто «является»
объект, связанный с ним, не содержит автомобиль, но, конеч¬
зовать возможности классов.
автомобилем,
и
субъект «использует» автомобиль. Функция испо¬
функции-элементы этого объекта
Объект может «знать» другой объект. Базы знаний часто имеют такие от¬
ношения. Один объект может содержать указатель или ссылку на другой объ¬
ект, зная таким образом об этом объекте. В таком случае говорят что один объ¬
но же, можно сказать, что
льзует
объект, просто
вызывая
объектом
ект находится с другим
к основному упражнению
хию классов: Point
точка, Circle
в
представим
пример,
(рис. 19.9).
Рис. 19.7,
ние
на
и
наконец,
Cylinder
элементы
класс Circle из класса
координаты x
улучшить
данных
Point
friend
void
getY()
x,
вызывать
может
классов.
Point
&operator<<(ostream
0) ;
0, float
(float, float);
const { return x;
const
//
{
доступны
//
у;
return
у;
&,
}
//
}
производным
координаты
const
Point
&);
// конструктор по умолчанию
// установка координат
// получение координаты x
=
=
(float
setPoint
protected:
float
Circle могут
класса
функции доступа. Это
а не через
внима¬
когда мы получаем
{
ostream
float getX()
float
Point. Обратите
Поэтому,
Point, функции-элементы
I // P0INT2.H
I // Определение класса
#ifndef
P0INT2_H
#define P0INT2_H
public:
Pomt
защищены.
и у непосредственно,
эффективность
class
затем представим один при¬
Circle (рис. 19.8),
класс
часть 1 показывает определение класса
что
то,
(рис. 19.7),
Point
главы. Рассмотрим иерар¬
цилиндр. Сна¬
Cylinder
котором из класса Circle получается класс
котором выведем из
в
класса
этой
окружность,
чала создадим и применим класс Point
мер,
«знания».
Пример: Point, Circle, Cylinder
19.14.
Теперь перейдем
в отношении
получение
координаты
у
классам
точки
Ь
#endif
Рис. 19.7.
Рис. 19.7,
int. Заметьте,
и
getY
часть
что
2
Определение
класса Point
показывает определения
(часть 1
из
3)
функций-элементов
класса
Po¬
программа main должна использовать функции доступа getX
для чтения значений защищенных элементов-данных x и у; помните,
829
Наследование
что защищенные элементы данных доступны только для элементов и
класса,
I
//
а также для элементов и
друзей
P0INT2.CPP
// Функции-элементы Pomt
#include <iostream.h>
#include
"pomt2.h"
// Конструктор Pomt
Point::Point(float а,
{
x
=
а;
-
b;
У
float b)
}
//
Установка
и
x
координат
у
Pomt::setPoint(float а,
void
float b)
{
x
а ;
-
b;
=
У
}
//
Вывод
точки
ostream
I
const
&operator<<(ostream &output,
Pomt
&p)
!
output
<<
return
output;
<<
'['
p.X
<<
"
"
<< p.y
",
']';
// разрешает конкатенацию
}
Рис. 19.7. Определения функций-элементов
I
// FIG19_7.CPP
// Тестер для класса
<iostream.h>
#include
"pomt2.h"
mam
класса Point
(часть 2
из
3)
Pomt
#include
()
{
Pomt p(7.2,
//
//
11.5);
защищенные данные
cout
"X
<<
"\nY coordinate
coordinate
p.setPomt(10,
cout
<<
return
создать
объект p
Pomt недоступны
<<
is
"
is
<<
"
в
класса
mam
p.getX()
p.getY();
<<
new
location
of
p
is
"
<<
p
<<
0;
}
X
coordinate
is
7.2
Y
coordinate
is
11.5
The
new
location
Pomt
10);
"\n\nThe
of p
Рис. 19.7.
is
Тестер
[10,
друзей
производных от него классов.
10]
для класса Point (часть 3 из 3)
endl;
их
830
Глава 19
Следующий наш пример показан на рис. 19.8. Определения класса Point и
функций-элементов из рис. 19.7 здесь используются повторно. Рис. 19.7, час¬
ти 1-3 показывают соответственно определение класса Circle, функций-элементов класса Circle и программу-тестер. Обратите внимание на то, что класс Circle
выводится из Point по типу открытого наследования. Это означает, что открытый
интерфейс класса Circle включает в себя функции-элементы класса Point, а так¬
же функции-элементы класса Circle
setRadius, getRadius и area. Заметьте,
его
функция передачи
что перегруженная
в поток для
Circle
может непосредственно
ссылаться на элементы x и у, потому что они являются защищенными элемента¬
ми базового класса.
через
Заметьте также,
объект, используя нотацию
помещения
что
необходимо
c.x и c.y
ссылаться на элементы x и у
перегруженная функция-операция
в поток не является элементом класса
Circle. Она
только дружествен¬
Программа-тестер создает экземпляр объекта класса Circle, затем исполь¬
£е£-функции, чтобы получить информацию об объекте класса Circle. Опять
на ему.
зует
же, поскольку программа main
не только не
функция-элемент
класса
Circle,
но и
не дружественна к нему, она не может прямо ссылаться на защищенные данные
класса
Circle. Затем тестер использует set-функции: setRadius
переопределить радиус
полняет
нечто
pRef типа
и
особенно
«ссылки на
объект Point» (Point &) объектом
ект
// CIRCLE2.H
// Определение
печатается как
класса
#ifndef
CIRCLE2_H
#define
CIRCLE2_H
#include
Circle. Програм¬
объект Point.
Circle
"pomt2.h"
Circle
friend
:
public Point
ostream
public:
// конструктор
Circle(float r
{
&operator<<(ostream
по
=
float
//
=
//
//
// доступны
radius;
x
//
setRadius(float);
float getRadius() const;
float area() const;
protected;
&,
const
Circle
&);
умолчанию
0.0,
void
float
с класса
которая, несмотря на тот факт, что она ини¬
объектом Circle, «думает», что это объект Point, в результате объ¬
Circle фактически
class
setPoint, чтобы
pRef,
ма затем печатает переменную
циализирована
и
координаты центра окружности. Наконец, тестер вы¬
интересное. Он инициализует переменную-указатель
радиус
в
0);
0, float у
установка радиуса
получение радиуса
=
вычисление
площади
производных
классах
круга
};
#endif
Рис. 19.8. Определение
Наш последний пример
класса Cirde
показан на рис.
(часть 1
из
3)
19.9. Определения
классов
Circle
приведенные на рис. 19.7 и 19.8,
используются здесь повторно. Части 1-3 показывают соответственно опреде¬
ления класса Cylinder, его функций-элементов и программу-тестер. Обратите
и
Point, определения
их
функций-элементов,
внимание на то, что класс
Cylinder
следования. Это означает, что
в
выводится из Circle по типу открытого на¬
открытый интерфейс класса Cylinder включает
себя функции-элементы класса Circle, а также функции-элементы класса
831
Наследование
Cylinder
setHeight, getHeight, area (переопределяет функцию класса Circ¬
le) и volume. Заметьте, что перегруженная функция-операция передачи в по¬
ток
объекта Cylinder
может непосредственно ссылаться на элементы x и у, по¬
тому что они являются защищенными элементами
Circle
у наследуются классом
от класса
базового
Point). Заметьте
класса
radius, используя нотацию c.x,
функция-операция помещения в поток
мо ссылаться на элементы x, у и
так как перегруженная
функцией-элементом
здает экземпляр
класса
Circle (x
также, что
и
необходи¬
c.y, c.radius,
не является
Cylinder, а только дружественна ему. Тестер со¬
Cylinder, затем вызывает get-функции, чтобы
объекта класса
получить информацию об объекте Cylinder. Снова, поскольку main не только
не функция-элемент класса Cylinder, но и не дружественна ему, она не может
прямо ссылаться на защищенные данные класса
setHeight, setRadius
Cylinder. Затем
тестер испо¬
setPoint, чтобы переопределить
Наконец, тестер инициализует пере¬
льзует set-функции
и
высоту, радиус и координаты цилиндра.
менную-указатель pRef типа «ссылки на объект Point» (Point &) объектом cyl
Cylinder. Затем программа печатает переменную pRef, которая, не¬
смотря на тот факт, что она инициализирована объектом Cylinder, «думает»,
что это объект Point, в результате объект Cylinder фактически печатается как
класса
объект Point. После этого переменная-указатель cRef
Circle» (Circle &) инициализуется объектом cyl
класс
затем печатает переменную
циализирована
класса
на тот
что она ини¬
Cylinder, «думает», что это объект класса Circle, в
Cylinder фактически печатается как объект класса Circle.
объекта
печать также выводится площадь
класса
Circle.
// CIRCLE2.CPP
// Определения функций-элементов Circle
#include <iostream.h>
#include <iomanip.h>
#include "circle2.h"
// Конструктор Circle
вызывает
конструктор
Point
// с инициализатором элемента и инициализирует радиус
Circle::Circle(float r, float а, float b)
:
Point
// вызов базового конструктора
(а, b)
r;
{ radius
}
// Set radius
void Circle::setRadius(float r) { radius
r;
}
// Получение радиуса
float Circle: :getRadius() const { return radius; }.
=
=
// Вычисление площади круга
float Circle::area() const
{ return 3.14159 * radius
//
Вывод круга
// Center
в
форме:
y]; Radius
*
radius;
}
#.##
ostream &operator<<(ostream &output, const Circle
{
<< c.y
[" << c.x << ",
output << "Center
[x,
=
=
Radius
"
<<
"];
<<
setprecision(2)
output;
//
&c)
"
=
return
=
<<
<<
setiosflags(ios::showpomt)
c.radius;
Разрешеает
конкатенацию
вызовов
}
Рис. 19.8.
объект
Cylinder. Тестер
факт,
объектом
результате объект
На
cRef, которая, несмотря
типа «ссылки на
Определения функций-элементов
класса Cirde
(часть 2
из
3)
832
Глава 19
//
//
FIG19_8.CPP
Тестер для класса
Circle
<iostream.h>
#include
"point2.h"
#include "circle2.h"
#include
main()
Circle c(2.5,
cout
4.3);
3.7,
"X
<<
<<
"\nY coordinate is
c.getY()
"
<<
"\nRadius is
c.getRadius();
coordinate
is
<<
c.getX()
"
<<
c.setRadius(4.2 5
)
;
setPomt (2,
2) ;
cout << "\n\nThe new
с
"
<<
.
<<
&pRef
Pomt
<<
cout
<<
с
=
pRef
<<
=
Area:
"
<<
and
radius
<<
c.area()
of
c
are\n"
endl;
printed
as
а
Point
of
c
are
is:
"
endl;
0;
X coordinate is 3.7
Y coordinate is 4.3
Radius is 2.5
The new location and
Center
location
с;
"\circle
<<
return
"\nArea
radius
2]; Radius
[2,
=
4.25
56.74
Circle printed as
a
Point
is:
[2.00,
2.00]
Рис. 19.8. Тестер для класса Cirde (часть 3 из 3)
//
CYLINDR2.H
Определение класса
#ifndef CYLINDR2_H
//
#define
#include
Cylinder
CYLINDR2_H
"circle2.h"
Cylinder : public Circle {
friend ostream& operator<<(ostream&, const Cylinders);
public:
// конструктор по умолчанию
0.0, float r = 0.0,
Cylinder (float h
class
=
float
void setHeight
0.0,
(float);
x
float
getHeight()
float
area()
float
volume()
protected:
float height;
=
const;
const;
const;
float y
0.0);
// установка высоты
=
//
получение
//
вычисление
и
возврат
площади
//
вычисление
и
возврат
объема
//
высота
высоты
цилиндра
};
#endif
Рис. 19.9. Определение класса Cylinder (часть 1
из
3)
833
Наследование
Пример прекрасно демонстрирует открытое наследование, а также опреде¬
Читатель теперь должен
ление и ссылку на защищенные элементы данных.
иметь хорошее
щей
представление об основах механизма наследования. В следую¬
главе мы покажем, как
в общем виде с помощью иерар¬
полиформизм. Абстракция, наследование и по¬
объектно-ориентированного программирования.
программировать
хий наследования, используя
лиморфизм
составляют суть
CYLINDR2.CPP
//
//
Определение функций-элементов
Cylinder
класса
#include <iostream.h>
#include <iomanip.h>
#include "cylmdr2.h"
// Конструктор Cylinder вызывает конструктор
Cylinder::Cylinder(float h, float r, float x,
:
{
(r,
Circle
h;
=
height
x,
//
у)
вызов
Получение
float
//
высоты
у)
h)
h;
{
height
=
{
return
height;
}
цилиндра
Cylinder::getHeight()
Вычисление
float
float
конструктора
}
// Установка высоты цилиндра
void Cylmder::setHeight(float
//
базового
Circle
площади
const
цилиндра
(т.е.
площади
}
поверхности)
const
Cylinder::area()
{
return
*
2
*
2
Circle::area()
*
3.14159
+
radius
*
height;
}
// Вычисление объема цилиндра
float Cylmder::volume() const
*
return Circle::area()
{
height;
}
// Вывод размеров цилиндра
operator<<(ostream &output, const Cylinders с)
{
<< c.y
[" << c.x << ",
output << "Center
<<
<<
"]; Radius
setiosflags(ios::showpoint)
<<
setprecisaon(2) << c.radius
<<
<< c.height;
";
Height
ostream&
"
=
"
=
=
return
output;
//
"
разрешает
конкатенацию
вызовов
}
Рис. 19.9.
Определения функций-элементов и дружественной функции
(часть 2 из 3)
класса
Cylinder
19.15. Сложное наследование
До
сих пор в
котором
этой
каждый
класс может
образования
быть получен
Зж 801
обсуждали
механизм простого наследования, в
базового
класса.
Однако
базовых классов; такой механизм
наследованием. Сложное наследова¬
из нескольких
класса называется сложным
ние означает, что
27
главе мы
класс выводится только ш одного
производный
класс наследует элементы нескольких
базовых
Глава 19
834
классов. Этот мощный механизм дает интересные формы повторного исполь¬
зования программного обеспечения, но вместе с тем может порождать ряд
проблем, связанных с неоднозначностью.
ЖЩ
/7
//
FIG19_9.CPP
Тестер для класса Cylmder
<iostream.h>
#include
#include
#include
#include
#include
<iomanip.h>
"point2.h"
"circle2.h"
"cylmdr2
.
"
h
main ()
{
//
//
объект
создать
Cylinder
Вызов
<<
cout
Cylinder
2.5,
cyl(5.7,
"\nHeight is
<<
"
is
"
<<
вывода
get-функций для
"X coordinate is
"\nYcoordinate
"\nRadius is
<<
2.3);
1.2,
<<
"
<<
Cylmder
<<
cyl.getX()
<<
cyl.getY()
cyl.getRadius()
cyl.getHeight();
// Вызов set-функций для изменения атрибутов Cylinder
cyl. setHeight (10) ;
cyl. setRadius (4.25);
cyl. setPoint (2, 2) ;
cout <<
"\n\nThe new location,
radius,
"
"and
<<
<<
//
Вывести
&pRef
Point
Cylinder
cyl;
of
cyl
"
<<
как
are:\n"
"
Volume:
<<
cyl
<<
endl
<<
"Area:
cyl.volume();
Point
// pRef "думает"
=
"\n\nCylinder printed
<<
cout
height
cyl.area()
<<
as
a
что
Point
это
is:
Point
"
<<
pRef;
//*Вывести Cylinder как Circle
&cRef
// cRef "думает" что это Circle
cyl;
<<
cout
"\n\nCylinder printed as a Circle is:\n" << cRef
=
Circle
"\nArea:
<<
return
"
<<
cRef.area()
<<
endl;
0;
X coordinate is 1.2
Y coordinate is 2.3
Radius is 2.5
Height is 5.7
The
location,
new
Center
Area:
=
radius,
2]; Radius
[2,
=
380.53
Volume:
and height of cyl are:
10.00
4.25; Height
=
567.45
as a Point is:
[2.00,
Cylinder printed as a Circle is;
4.25
Center s [2.00, 2.00); Radius
Cylinder printed
2.001
=
Area:
56.74
Рис. 19.9. Тестер для класса
Cylinder (часть
3 из 3)
"
835
Наследование
Хороший
стиль программирования 19.2
-
Сложное наследование
мощный
механизм при правильном его использовании. Его
следует применять, когда между новым типом и двумя или
(т.е.
отношение «является»
Рассмотрим пример
тип А является
сложного
наследования,
Класс Basel содержит один защищенный
же
один
элемент
который устанавливает
которая считывает value.
конструктор,
цию-элемент
//
//
как типом
getData,
В,
более типами существует
так
и
типом
С).
показанный на рис. 19.10.
value целого типа, а так¬
value,
и
открытую
функ¬
BASEl.H
Определение
Basel
класса
#ifndef
BASEl_H
#define BASEl_H
class
Basel
public:
Basel
{
x)
(int
int getData()
protected:
int
x;
{ value
}
const { return value; }
// доступно производным
=
//
value;
наследуется
классам
классом
производным
};
#endif
Рис. 19.10.
Класс Base2
Определение
аналогичен классу
класса Base1
(часть 1
из
6)
Basel во всем, за исключением того, что
собой переменную letter символьного
его защищенные данные представляют
типа
эта
char. Класс Base2
функция
//
функцию-элемент getData,
переменной letter.
тоже имеет открытую
считывает значение символьной
но
BASE2.H
Определение класса
#ifndef BASE2_H
Base2
//
#define BASE2_H
class
Base2
public:
Base2
{
(char
с)
char
getData()
protected:
{
letter
=
с;
}
const { return letter; }
// доступно производным
char letter;
//
наследуется
классам
производным
классом
};
#endif
Рис. 19.10.
Определение
Класс Derived выводится
из
класса Base2
обоих
классов
(часть 2
Basel
и
из
6)
Base2 путем
закрытый элемент данных real типа float
функцию-элемент getReal, которая читает значение real.
наследования. Он имеет
сложного
и открытую
как обозначается сложное наследование с помощью
class Derived
просто списком открытых базовых клас¬
Обратите внимание,
двоеточия
(:)
после
сов, разделенных запятыми. Заметьте также, что конструктор производного
класса Derived вызывает каждый из своих базовых конструкторов, Basel и
Base2, посредством
27*
синтаксиса инициализатора элементов.
Глава 19
836
I
DERIVED.H
//
//
Определение
класса
//
два
базовых
класса
Deriyed,
и
(Basel
который
наследует
Base2).
#ifndef
DSRIVED_H
#define DERIVED_H
#include
<iostream.h>
#include
"basel.h"
#include
"base2.h"
//
сложное
наследование
: public Basel, public Base2 {
ostream &operator<<(ostream &, const
Derived
class
friend
Derived
&);
public:
char,
Denved(int,
float
private:
float
//
real;
float);
const;
getReal()
данные
закрытые
производного
класса
};
#endif
Рис. 19.10.
//
DERIVED.CPP
//
Объявления
#include
//
//
Определение
Конструктор Derived вызывает конструкторы
классса Basel и класса Base2.
:
//
real
Basel(i),
=
f;
Возврат
i,
Вывести
ostream
char
Base2(c)
float
с,
//
вызов
из
6)
для
f)
обоих
базовых
конструкторов
}
значения
real
Denved::getReal()
float
//
(часть 3
Derived
класса
функций-элементов
"derived.h"
Derived::Derived(mt
{
класса Derived
все
данные
const
{
return
real;
}
Derived
&operator<<(ostream &output,
const
Derived
&d)
{
output
<<
"
<<
Character:
"\n
"\nReal number:
<<
return
output;
Integer:
"
<<
"
"
d.value
<<
d.letter
<<
d.real;
// разрешает конкатенацию
вызовов
}
Рис. 19.10. Определения функций-элементов класса Derived (часть 4
из
6)
Перегруженная операция передачи в поток для класса Derived использует
нотацию с точкой для производного объекта d, чтобы напечатать переменные
value, letter и real. Функция этой операции является дружественной классу
Derived, поэтому operator<< может иметь прямой доступ к закрытому эле¬
менту данных real класса Derived. Кроме того, так как эта операция является
классу, она может иметь доступ к защищенным
элементам value и letter соответствующих базовых классов Basel и Base2.
дружественной производному
837
Наследование
//
FIG1
//
Тестер
9__10
.
CPP
для
#include
примера
<iostream.h>
#include
"basel.h"
#include
"base2.h"
#include
"derived.h"
сложного
наследования
main ()
класса
Basel
bl
(10),
*baselPtr;
//
создать
объект
базового
Base2
b2('z1),
*base2Ptr;
//
создать
объект
другого
//
класса
объект
производного
Derived
//
d(7,
напечатать
cout
3.5);
A ,
данные
<<
"Object
<<
bl.getData()
bl
создать
//
класса
базовых
contains
b2
//
классов
"
integer
contains
character
<<
"\nObject
<<
b2.getData()
"\nObject d contains:\n"
<<
базового
<<
"
d;
// напечатать данные производного класса
// операция разрешения области действия устраняет
// неоднозначность getData
cout << "\n\nData members of Derived can be"
"
individually:\n"
<< d.Basel::getData()
<< d.Base2::getData()
Character:
"\n
<<
<< d.getReal()
"\nReal number:
"\n\n";
<<
Integer:
"
"
<<
"
<<
cout
accessed
"
<<
<<
"Derived
<<
can
be
// рассматривает Derived
=
baselPtr
cout
return
"
как
объект
Basel
yields
baselPtr->getData();
// рассматривает Derived
<<
an
&d;
"baselPtr->getData()
=
base2Ptr
<<
as
"
<<
<<
cout
treated
"object of either base class:\n";
как
объект
Base2
&d;
"
"\nbase2Ptr->getData()
base2Ptr->getData()
<<
yields
endl;
0;
}
Рис. 19.10. Пример для тестирования сложного наследования (часть 5 из 6)
Содержимое
каждого из объектов базовых классов печктается при стати¬
Несмотря даже на то, что существуют две функции
ческом их связывании.
вызовы будут однозначными, поскольку они указывают сразу на вер¬
getData объекта Ы ц версию getData объекта b2.
Затем мы печатаем содержимое объекта d класоа Derived, связывание
здесь также статическое. Но в этом случае действительно возцикает проблема
getData,
сию
неоднозначности, поскольку этот объект содержит две функции getData
от класса Base2. Эта неодно¬
одну, унаследованную от класса Basel, другую
значность легко снимается с помощью операции разрешения
области действия
Глава 19
838
(т.е.
d.Basel::getData(), чтобы напечатать целое значе¬
d.Base2::getData(), чтобы напечатать символьное в letter. Значе¬
применяется нотация
ние в
value
ние с
плавающей запятой
и
в
real
печатается однозначно, поскольку есть вызов
d.getReal().
Далее покажем,
что отношения простого наследования является приме¬
нимы также и к сложному наследованию. Мы присваиваем адрес производно¬
го объекта d указателю baselPtr базового класса и печатаем целое значение в
value, вызывая функцию-элемент getData() через указатель baselPtr. Затем
присваиваем адрес производного объекта d указателю базового класса
base2Ptr и печатаем символьное значение в letter, вызывая функцию-элемент
getData через указатель base2Ptr.
Ы
Object
contains
integer 10
character z
Object b2 contains
Object d contains:
Integer: 7
Character: A
Real number:
3.5
of Derived can be
Data members
accessed individually:
Integer: 7
Character: A
Real number:
3.5
Derived can be
treated as
an object of either base
7
baselPtr->getData()
yields
base2Ptr->getData()
yields A
class:
Рис. 19.10. Пример для тестирования сложного наследования (часть 6 из 6)
Этот пример показал механику сложного наследования и дал представле¬
ние об
одной
из
проблем неоднозначности. Сложное наследование
непростой механизм, подробнее оно рассмотрено в учебни¬
простых
представляет собой
ках по
С++,
а также
в
нашей
«С++: How to Program»
книге
(Prentice-Hall,
1994)1.
Резюме
Одним
из
ключевых аспектов
объектно-ориентированного программи¬
рования является повторное использование программного обеспечения
посредством наследования.
Программист
может указать, что новый класс должен наследовать эле¬
менты данных и
са.
В
этом
функции-элементы ранее определенного базового
случае
новый
класс
называется
производным
клас¬
классом.
простом наследовании новый класс выводится только из одного ба¬
зового класса. При сложном наследовании новый производный класс
При
наследует
базовым
нескольким
(возможно,
Как правило, производный
данных
и
класс
не
связанным
между
собой)
вводит
свои
собственные
элементы
вообще говоря, производный
более обширное определение, чем его базовый класс. Про-
функции-элементы,
класс имеет
1
никак
классам.
так
что,
Русский перевод: X.M. Дейтел, П.Дж. Дейтел «Как программировать на С++», БИНОМ, М.,
1999.
Прим. ред.
839
Наследование
изводный класс содержит больше специфических свойств,
вый, и обычно представляет меньшую группу объектов.
Производный
своего
класс
базового
не
может
класса:
это
иметь
доступа
нарушило
к
закрытым
чем
базо¬
элементам
бы инкапсуляцию последнего.
Однако производный класс имеет доступ
элементам своего базового класса.
к
открытым
и защищенным
производного класса всегда вызывает конструктор своего
базового класса, чтобы сначала создать и инициализировать элементы
базового класса в объекте производного класса.
Конструктор
Деструкторы
Наследование
льзования
на
класса
вызовам
конструкторов,
вызываются
раньше
дест¬
можно рассматривать как инструмент повторного
испо¬
программного
обеспечения, позволяющий сберегать время
применению уже отлажен¬
высокого качества.
кода
программного
разработку программ
апробированного
и
ного и
Наследование
обратном
вызываются в порядке,
поэтому деструкторы производного
рукторов своего базового класса.
способствующий
позволяет применять существующие
библиотеки
классов.
Когда-нибудь программное обеспечение будет в основном строиться из
стандартных повторно используемых компонентов точно так же, как
конструируется в настоящее время аппаратная часть компьютеров.
программного обеспечения не обязательно иметь доступ
к исходному коду базового класса, нужно только знать интерфейс базо¬
вого класса и иметь для компоновки его объектный код.
Разработчику
Объект производного
вующего
Базовый
класса можно
обрабатывать
как
объект
соответст¬
базового класса, однако обратное неверно.
открытого
класс находится в иерархическом отношении с производным
классом.
Один
себе. Когда этот класс участвует
либо базовым классом, который постав¬
другим классам, либо производным классом
класс может существовать сам по
в наследовании, он становится
ляет
с
атрибуты
и поведение
унаследованными
Иерархия
свойствами
и
поведением
наследования может иметь
базовых
любую глубину
в
классов.
рамках физиче¬
ских пределов какой-то частной системы.
Иерархии
являются
управления
полезными
сложными
сложности программного
для
инструментами
Что
структурами.
обеспечения,
то
касается
все
понимания
и
возрастающей
С++ предусматривает поддер¬
жку иерархических структур посредством
механизмов наследования и
полиморфизма.
указателя базового класса к указателю производ¬
ного можно использовать явное призедение. Если такой указатель дол¬
жен быть разыменован, то сначала следует убедиться, что он указывает
Для преобразования
на
объект производного класса.
Защищенный доступ
между открытым
выполняет
роль
промежуточного уровня защиты
К защищенным элементам ба¬
и закрытыми доступами.
зового класса имеют доступ
функции-элементы
и друзья самого класса, а
также элементы и друзья классов, производных от данного; никакие
гие
функции
не
могут
обращаться
к
защищенным
элементам
дру¬
класса.
840
Глава 19
Защищенные элементы используются для расширения привилегий
производных классов, причем эти привилегии не распространяются на
не-дружественные функции и функции других классов.
Сложное наследование приводит
фы
к
графоподобным иерархиям. Эти гра¬
не имеют циклов, потому что все стрелки указывают в одном и том
же направлении.
Такой граф
называется направленным ациклическим
графом (DAG).
При
выводе нового класса из базового класса
вить
последний
открытый, защищенный либо закрытый.
как
можно
объя¬
выводе нового класса из открытого базового класса открытые эле¬
менты базового класса становятся открытыми элементами производно¬
При
го класса, а защищенные элементы базового класса становятся защи¬
щенными элементами производного.
выводе нового класса из защищенного базового класса открытые и
защищенные элементы базового класса становятся защищенными эле¬
ментами производного класса.
При
При выводе нового класса из закрытого базового класса открытые и за¬
щищенные элементы базового класса становятся закрытыми элемента¬
ми производного класса.
Базовый
класс может быть либо непосредственным базовым классом
производного класса, либо косвенным базовым классом производного
класса. Непосредственный базовый класс явно указывается при объяв¬
лении
производного
он
указывается;
Косвенный
класса.
на
располагается
базовый
несколько
уровней
класс
выше
явно
по
не
древо¬
видной иерархической структуре.
Если
элемент
можно
базового
просто
класса не подходит для производного класса, его
переопределить
в
производном
классе.
Важно различать отношения «является» и «имеет». В отношении «име¬
ет» объект одного класса содержит объект другого класса. В отношении
объект
«является»
вать как
объект
следование.
типа производного класса можно также рассматри¬
базового класса. Отношение
Отношение «имеет»
композиция.
типа
«является»
это на¬
Объект производного класса можно присваивать объекту базового клас¬
Такое присваивание имеет смысл, так как производный класс имеет
са.
элементы,
однозначно
Указатель
на
зован
в
соответствующие
элементам
объект производного класса может быть
на объект базового класса.
Указатель должен
ссылать¬
объект производного класса.
Базовый
класс определяет
общность. Все
классы, производные от него,
наследуют свойства этого базового класса. В
общее, чтобы
сы затем могут
рамки
объектно-ориентирован¬
ищет общее и выделяет это
создать осмысленные базовые классы. Производные клас¬
ном процессе проектирования
за
преобра¬
класса в указатель производ¬
ного класса с помощью явного приведения.
на
неявно
класса.
указатель
Можно преобразовать указатель базового
ся
базового
разработчик
быть настроены
унаследованных
от
и
снабжены свойствами, выходящими
базового
класса.
841
Наследование
Чтение ряда объявлений производного
заблуж¬
класса может вводить в
дение, так как не все элементы производного класса присутствуют в
этих объявлениях. В частности, унаследованные элементы не перечис¬
ляются в объявлениях производного класса, хотя на самом деле они в
нем имеются.
Отношение
основе
«имеет»
композиции
является
Конструкторы элементов-объектов
элементы
сов
новых
создания
примером
существующих
классов
вызываются
в
порядке,
в
котором
объявлены. При наследовании конструкторы базовых
вызываются
в
в
порядке,
производного
конструктором
на
классов.
котором
наследование,
указано
и
клас¬
перед
класса.
Для объекта
производного класса сначала вызывается конструктор ба¬
зового класса, затем
конструктор производного класса.
Когда объект производного
рукторы вызываются
в
области действия, дест¬
класса выходит из
порядке,
обратном
сначала
конструкторам
вызывается деструктор производного класса,
затем деструктор
базово¬
го.
Один
ется
класс можно вывести из нескольких
сложным
базовых
классов.
Это
называ¬
наследованием.
Сложное наследование обозначается двоеточием (:)
ском разделенных запятыми базовых классов.
с последующим* спи¬
производного класса вызывает конструкторы для каждого
из своих базовых классов посредством синтаксиса инициализатора эле¬
Конструктор
ментов.
Терминология
абстракция
направленный ациклический граф
базовый класс
наследование
библиотеки классов
настройка программного
обеспечения
деструктор базового класса
деструктор производного класса
неоднозначность в сложном
друзья базового класса
друзья производного класса
непосредственный базовый класс
наследовании
защищенное наследование
защищенный базовый класс
Объектно-ориентированйое
программирование
объект-элемент
открытое наследование
закрытое наследование
закрытый базовый класс
защищенный элемент класса
открытый базовый
иерархическое отношение
иерархия классов
отношение
знает
отношение
имеет
инициализатор базового класса
класс элемента
отношение
использует
ключевое слово
ошибка бесконечной рекурсии
protected
композиция
конструктор базового класса
коцструктор по умолчанию базового
класса
конструктор производного класса
косвенный базовый класс
надкласс
класс
отношение рвляется
переопределение
элемента базового
класса
повторное использование
программного
кода
подкласс
пользователь класса
производный
класс
Глава 19
842
простое наследование
сложное наследование
указатель
объект
базового
на
объект
производного
класса
класса
обеспечения
базового
на
указатель
компоненты
стандартизованные
программного
указатель
указатель
класса
производного
доступом
управление
к
класса
элементам
Распространенные ошибки программирования
19.1.
Использование объекта базового
класса как
объекта производного
класса.
19.2.
Использование элементов производного класса,
базовом классе,
не определенных в
указателя объекта базового
класса к указателю на объект производного класса.
19.3.
после
преобразования
Когда функция-элемент базового класса переопределяется в произ¬
водном классе, функция производного класса обычно вызывает од¬
ноименную функцию базового и выполняет некоторую дополните¬
льную работу. В случае, когда для вызова функции базового класса
не используется операция разрешения области действия, возникает
бесконечная рекурсия, так как функция-элемент производного
класса фактически вызывает саму себя.
19.4.
Создание программы, зависящей от конкретного порядка констру¬
ирования элементов-объектов, может приводить к труднообнаружимым, тонким ошибкам.
19.5.
Присваивание объекта
щего базового класса
личным
19.6.
в
обращения
к элементам, на¬
производном классе.
указателя базового класса к указателю производного
класса может привести к ошибкам, если далее этот указатель испо¬
льзуется для ссылки на объект базового класса, который не содер¬
Приведение
жит
Хороший
19.1.
только
производного класса объекту соответствую¬
и затем попытка
требуемых
стиль
Когда
в
элементов
производного
класса.
программирования
производном классе наследуются ненужные свойства, мас¬
кируйте их, переопределяя функции.
19.2.
Сложное наследование
мощный
механизм
при правильном
его
Его следует применять, когда между новым типом
более типами существует отношение «является» (т.е.
использовании.
и двумя или
тип
А является
как
типом
В,
так
и
типом
Совет
по повышению
19.1.
Если образованные путем наследования
С)
эффективности
классы
больше,
чем
требу¬
ется, то это может привести к ненужным расходам ресурсов памяти
и снижению производительности.
Общие
19.1.
методические замечания
Производный
элементам
класс не может иметь прямого доступа к закрытым
своего
базового
класса.
843
Наследование
19.2.
Переопределение функции-элемента
19.3.
в
производном классе
не
тре¬
Когда создается объект производного класса, сначала работает
структор базового класса, затем конструкторы для объектов
кон¬
бует одинаковой сигнатуры
функцией базового
с
класса.
эле¬
ментов производного класса и, наконец, конструктор производного
Деструкторы
класса.
ответствующих
19.4.
его
класса не влияет на
исходный
базового класса; целостность базового
объект¬
или
класса при на¬
сохраняется.
следовании
19.5.
вызовам со¬
конструкторов.
Создание производного
ный код
обратном
вызываются в порядке,
В объектно-ориентированной
системе классы часто тесно связаны.
«Вычеркните» общие свойства и модели поведения и
в базовом классе, а затем используйте наследование
поместите их
для построе¬
ния производных классов.
19.6.
Производный
базового
буты
и
атрибуты
класс содержит
Он
класса.
поведение.
компилировать
наследованию базовый класс можно
Благодаря
независимо
от
чтобы
создания
19.7.
было
комбинировать
их
классы,
остается
базовым
классом для
если только
не требуется изменять
открытый интерфейс базового
класса
произ¬
класса
неизменным.
При модификации
тывающий
та
с
класса.
При модификации базового
водные
19.8.
можно
производного
Необходимо
атрибуты и поведе¬
класса.
производного
вновь введенные
только компилировать затем
ние,
и модели поведения своего
может также определять дополнительные атри¬
требуется изменять его охва¬
открытый интерфейс класса-элеменОднако имейте в виду, что составной
класса элемента не
класс, если только
остается
неизменным.
класс, возможно, потребуется перекомпилировать.
Упражнения
19.1.
для самоконтроля
Заполните пропуски
a) Если
класс
Alpha
в
следующих утверждениях:
наследует классу Beta, то класс
а
ется
классом,
b) С++ реализует
механизм
класс
Beta
,
Alpha
называ¬
классом.
который
позволяет произ¬
базовых классов, даже
между собой.
водному классу наследовать от нескольких
если
базовые
эти
c) Наследование
работки,
а
также
проверенного
d)
классы
не
связаны
позволяет
,
обеспечивает
высококачественного
Объект
что укорачивает время раз¬
возможность
использования
программного
уже
кода.
класса можно рассматривать как
объект
со¬
класса.
ответствующего
e) Чтобы преобразовать
указатель базового класса в указатель про¬
так как ком¬
,
изводного класса, следует использовать
пилятор
считает
это
опасной операцией.
Глава 19
844
f) Три спецификатора
доступа к
и
элементам
это
,
.
выведении нового класса с открытым наследованием откры¬
элементами
тые элементы базового класса становятся
g) При
производного класса, а защищенные элементы базового класса ста¬
новятся
элементами производного класса.
выведении нового класса из одного базового класса с защи¬
щенным наследованием открытые элементы базового класса стано¬
h) При
элементами производного класса, а защищенные
элементами произ¬
элементы базового класса стаовятся
вятся
водного класса.
i) Отношение
«имеет» между классами представлено
«является»
отношение
представляется
,
механизмом
а
.
Ответы на упражнения для самоконтроля
19.1.
с) повторно
d) производного, базового;
типа; f) public, protected, private; g) открытыми, за¬
h) защищенными, защищенными; i) композицией,
а) производным, базовым; b)
сложного
наследования;
использовать программное обеспечение;
e)
приведение
щищенными;
наследования.
Упражнения
19.2.
Рассмотрим
Bicycle. Используя
класс велосипедов
ваше знание
общих деталей велосипедов, покажите иерархию клас¬
сов, в которой класс Bicycle наследует другим классам, которые, в
свою очередь, наследуют еще некоторым классам. Обсудите созда¬
ние различных представителей класса Bicycle. Обсудите наследова¬
ние класса Bicycle для других, тесно связанных с ним, производ¬
некоторых
ных классов.
19.3.
Кратко определите каждый
сложное
19.4.
наследование,
Выпишите
класса
и
производный
все
Иерархия
классы
того,
сов.
все
считает
компилятор
формы, которые
мерные, так и трехмерные,
постройте
класс.
в
указатель
опасным.
вы можете придумать
и
как дву¬
из них иерархию
форм.
должна иметь базовый класс
TwoDimensionalShape
Shape, которому наследуют
ThreeDimensionalShape. После
и
как вы получили иерархию,
определите
каждый
из ее клас¬
Мы используем эту иерархию в главе 20, чтобы обрабатывать
формы как объекты базового класса Shape. Эта методика назы¬
вается
19.6.
класс
Обсудите, почему преобразование базового указателя
производного
19.5.
из следующих терминов: наследование,
базовый
полиморфизмом.
Часто используются классы, называемые коллекционными или
контейнерными. Такие классы хранят элементы других классов.
Типичные разновидности коллекционных
ки,
связанные
списки,
деревья,
портфели, словари, таблицы
вило,
массивы, сте¬
множества,
Коллекционный класс, как пра¬
действия, как «вставить элемент»,
и т.д.
предусматривает такие
классов
символьные строки,
845
Наследование
«найти элемент», «соединить две коллек¬
«исключить элемент»,
ции», «определить пересечение двух коллекций» (т.е. найти их об¬
щие элементы), «напечатать коллекцию», «найти наибольший эле¬
мент»,
и
«найти наименьший элемент»,
«найти сумму
элементов»
т.д.
a) Напишите
сможете
список
придумать
b) Организуйте
но,
захотите
всех
(в
том
типов
числе
из этих классов
разделить
их
классов
коллекций, которые
здесь
упомянутые
коллекций иерархию. Вы,
на упорядоченные
и
вы
списки).
возмож¬
неупорядоченные
коллекции.
c) Реализуйте
дование,
написать
столько классов, сколько сможете, используя насле¬
минимизирующее
для
d) Напишите
создания
число
новых
нового
кода,
который придется
классов.
тестер, который проверяет каждый из классов
шей иерархии наследования.
в
ва¬
Scan AAW
г
л
а
в
а
20
Виртуальные функции
и полиморфизм
Цели
Познакомиться с понятием
Понять,
нии
и
как
полиморфизма.
реализуется полиморфизм при объявле¬
виртуальных функций.
использовании
Понять различие между конкретными и
абстрактными
классами.
Изучить объявление
создания
Оценить
чисто
абстрактных
важность
виртуальных
функций
для
классов.
полиморфизма
сопровождения расширяемых
для
систем.
разработки
и
Глава 20
848
Содержание
20.1.
Введение
20.2.
Обработка
различных
типов данных
при помощи операторов switch
20.3. Виртуальные функции
20.4.
Абстрактные базовые
классы
и
конкретные классы
Полиморфизм
20.6. Пример: программа начисления заработной
возможности полиморфизма
20.5.
платы, использующая
20.7. Новые классы и динамическое связывание
20.8.
Виртуальные деструкторы
20.9.
Пример:
Резюме
наследование
интерфейса
Распространенные
программирования
ческие замечания
для самоконтроля
реализации'
ошибки программирования
Советы по повышению
эффективности
Упражнения для самоконтроля
Упражнения
20.1.
Благодаря
и
Хороший стиль
Общие методи¬
Ответы
на упражнения
Введение
механизму виртуальных
функций
и
полиморфизму
стало воз¬
можным проектировать и реализовывать системы, которые легко расширять.
Такие программы могут обрабатывать объекты существующих
сов, которых не существует на момент
разработки
классов и клас¬
программы. Если эти клас¬
сы получены на основе известных программе классов, то, поскольку програм¬
ма обеспечивает механизм обработки объектов базовых классов, она сможет
успешно
обрабатывать
и
объекты производных
классов. Многие мощные про¬
граммные продукты построены на этом принципе.
Виртуальные функции
20.2.
Один
и
849
полиморфизм
Обработка
типов
данных
при помощи операторов switch
различных
использование
способов обработки объектов различных типов
switch. Однако при этом возникает ряд проблем. Программист мо¬
забыть сделать проверку типа в нужном месте программы. Он может вы¬
из
оператора
жет
switch обработку
полнить в операторе
не всех типов данных.
нового типа данных программист может
операторы
забыть вставить
При добавлении
новые case-метки в
switch. И наконец, поскольку при добавлении нового типа данных
быть внесены во все операторы switch, это займет много
появятся новые ошибки.
изменения должны
времени и
Как
мы скоро увидим,
механизм виртуальных
функций
позволяет отказаться от логики программирования,
switch. Механизм виртуальных функций
тически.
При
Общее
этом не возникает
и
полиморфизма
на операторе
необходимую логику
следствием использования виртуальных
простота
и ясность
функций
и
полиморфизма
программ В них становится меньше фрагментов
и
явля¬
с ветвлением
больше обычного линейного кода. Такие программы проще тестировать,
вать
автома¬
ошибок, свойственных прежней методике.
методическое замечание 20.1
Интересным
ется
вносит
и
основанной
отлажи¬
сопровождать.
20.3.
Виртуальные функции
Предположим, что следующий набор классов геометрических фигур: Circ¬
le, Triangle, Rectangle, Square и т.д. выведен из базового класса Shape. В объ¬
ектно-ориентированном программировании каждый иЗ этих классов может
быть наделен способностью нарисовать самого себя. Каждый класс имеет соб¬
ственную функцию draw и эти функции существенно различаются. При рисо¬
вании
любого
вого класса
такого
объекта было бы удобно трактовать его как объект базо¬
чтобы нарисовать любую фигуру, мы могли бы про¬
Shape. Тогда,
функцию draw базового класса Shape, переложив на программу
динамическое (т.е. времени выполнения) определение того, функцию какого
сто вызывать
производного класса требуется вызвать.
Чтобы сделать возможным такой способ поведения, мы объявляем функ¬
цию draw в базовом классе как виртуальную функцию и затем переопределяем
функцией, которая может нарисовать со¬
ответствующую фигуру. Функция объявляется виртуальной при помощи клю¬
чевого слова virtual в прототипе функции в базовом классе.
ее в каждом из производных классов
Общее
Если
методическое замечание 20.2
функция была объявлена
иерархии
Общее
как
виртуальная,
остается
виртуальной
во всей
методическое замечание 20.3
Если в производном классе виртуальная
следует
она
наследования
функцию
своего
функция
непосредственного
не определяется, то он просто на¬
класса-предшественника
850
Глава 20
Хороший
Хотя
стиль программирования 20.1
функция производного
класса является
виртуальной,
если на высших уровнях
было сделано соответствующее объявление, многие программисты, для
придания ясности программе, предпочитают использовать явное определение функ¬
ции как виртуальной на всех уровнях иерархии.
иерархии
Если
как
мы
вызываем
виртуальная,
ссылающийся
чески
(т.е.
Это
са.
на
во время
называется
Общее
функцию draw, которая объявлена
в
базовом
классе
этой цели указатель на базовый класс,
объект производного класса, то программа выберет динами¬
и
используем для
выполнения) функцию draw нужного производного клас¬
связыванием (см. рис. 20.1 и рис. 20.2).
динамическим
методическое замечание 20.4
Переопределяемая виртуальная функция
список
параметров,
что
и
должна иметь тот же
виртуальная функция базового
возвращаемый
тип и
класса
Распространенная ошибка программирования 20.1
производном классе виртуальной функции, не совпадающей по типу
списку параметров с функцией в базовом классе, при¬
синтаксической ошибке.
Определение
в
возвращаемого значения или
водит
к
Если виртуальная функция вызывается при помощи ссылки на объект по
его имени и операции-точки выбора элемента класса, то ссылка разрешается
во время компиляции (это называется статическим связыванием) и вызыва¬
функция из того класса (определенная в нем или унаследо¬
ванная), которому принадлежит данный объект.
Перегрузка функций не использует динамическое связывание. Перегруз¬
ется виртуальная
разрешается во время компиляции подбором функции с сигнатурой, соот¬
ветствующей вызову (возможно, с неявным приведением типов). Это пример
статического связывания.
ка
20.4.
Абстрактные
базовые
классы и
конкретные
классы
будут созда¬
Однако во многих случаях бывает полезно опреде¬
лять классы, объекты которых не будут создаваться. Такие классы называют¬
ся абстрактными классами. Поскольку они используются в наследовании в
качестве базовых классов, мы будем называть их абстрактными базовыми
Когда
мы говорим о классе как о типе, мы подразумеваем, что
ваться объекты этого типа.
классами. Объекты абстрактного базового класса создавать нельзя.
Единственная цель определения абстрактного класса состоит в том, чтобы
предусмотреть обобщенный базовый класс, на основе которого строится иерар¬
хия наследования.
Классы,
для которых могут создаваться
объекты,
называ¬
конкретными классами.
Например, мы могли бы определить абстрактный базовый класс TwoDimensionalObject и произвести от него классы типа Square, Circle, Triangle и
ются
т.д.
Мы
могли
бы также создать абстрактный базовый класс ThreeDimensio-
Виртуальные функции
nalObject
Pyramid
851
полиморфизм
и
Cube, Sphere, Cylinder,
обобщенны для того,
объекты; нужно внести в них
и получить от него конкретные классы типа
и т.д.
Эти абстрактные базовые
классы слишком
чтобы из них можно было создавать реальные
какую-то конкретику, прежде чем мы сможем говорить о создании
классы
Конкретные
имеют
объекты
имеет смысл создавать
объектов.
специфические особенности, благодаря которым
таких классов.
Класс, имеющий виртуальные функции,
становится абстрактным, если
функций объявлены чистыми. Чистая
одна или несколько его виртуальных
это
виртуальная функция
=
циализатором
virtual
0,
float
функция, объявление которой завершается
ини¬
как в следующем примере:
const
earnings()
=
//
0;
чистая
функция
виртуальная
Общее методическое замечание 20.5
Если в классе, базовый
функция
не
класс которого содержит чистую виртуальную
определяется,
производный
следствие,
то она остается чистой и в этом
сам
класс
абстрактным
является
функцию,
(производном)
классе
эта
Как
классом
Распространенная ошибка программирования 20.2
Попытка
чистых
абстрактного класса (т e. содержащего одну
функций) приводит к синтаксической ошибке.
создать объект
виртуальных
Иерархия
классов не должна в
или
несколько
обязательном порядке содержать абстракт¬
объектно-ориен¬
тированные системы имеют иерархии классов, возглавляемые абстрактным
базовым классом. В некоторых случаях абстрактные классы занимают неско¬
лько верхних уровней иерархии. Хороший пример такой иерархии
иерар¬
хия геометрических фигур. Такая иерархия могла бы возглавляться абстракт¬
ные классы, но, как мы увидим, многие хорошо продуманные
ным
два
базовым
классом
Shape.
абстрактных базовых
mensionalShape.
И
На следующем уровне
класса, а именно
мы можем
произвести еще
TwoDimensionalShape
и
ThreeDi-
на третьем уровне иерархии можно определять конкрет¬
ные классы двумерных
фигур вроде кругов
и
квадратов
и
конкретные классы
трехмерных форм вроде сфер и кубов.
20.5.
Понятие полиморфизма
классов,
одной
и
в
Полиморфизм
С++
способность объектов различных
означает
образом реагировать на вызов
функции-элемента. Если, например, класс Rectangle выведен
более специфическая форма
Quadrilateral, то объект Rectangle
связанных наследованием, различным
той
из класса
же
объекта Quadrilateral. Действие (например, вычисление периметра
щади),
которое может быть выполнено над объектом класса
объекте
или пло¬
Quadrilateral,
мо¬
Rectangle.
Полиморфное поведение обеспечивается механизмом виртуальных функ¬
ций. При вызове виртуальной функции через указатель (или ссылку) на базо¬
жет также выполняться на
вый
класс
класса
С ++ выбирает переопределенную функцию правильного производ¬
ного класса, соответствующего
объекту,
на
который
В производном классе может переопределяться
ция-элемент базового
класса.
Если такая
функция
ссылается указатель.
и
не-виртуальная
вызывается
через
функ¬
указа¬
852
Глава 20
тель на базовый класс, компилятор генерирует вызов функции базового клас¬
са. Если функция-элемент вызывается при помощи указателя на производный
функции производного класса. Это пример неполи¬
морфного поведения.
Использование виртуальных функций и полиморфизма позволяет одному
и тому же вызову функции-элемента выполнять различные действия, завися¬
щие от типа объекта, получившего вызов. Этот механизм предоставляет про¬
класс, генерируется вызов
граммисту
«выразительные средства», которые трудно переоценить.
ющих разделах
ных
главы мы увидим примеры мощи
полиморфизма
В следу¬
и виртуаль¬
функций.
Общее
методическое замечание 20.6
Благодаря полиморфизму и виртуальным функциям программист может сосредото¬
на общих задачах, переложив частные вопросы на среду испол¬
нения. Программист может управлять большим набором объектов, каждый из кото¬
чить свое внимание
рых
имеет свое
Общее
специфическое
поведение, не зная даже типов этих объектов
методическое замечание 20.7
Полиморфизм обеспечивает расширяемость системы, программный код, опираю¬
полиморфное поведение объектов, не зависит от типа объектов, которым
передается вызов. Таким образом, без всяких изменений основной системы в нее мо¬
жет быть введен новый тип объектов, который может реагировать на существующий
набор сообщений Программы не придется перекомпилировать, за исключением,
щийся на
возможно, некоторой части клиента, в которой создаются объекты нового типа.
Общее методическое замечание 20.8
В
абстрактном
классе определяется
интерфейс
всех узлов иерархии классов
Абстрак¬
тный класс содержит чистые виртуальные функции, которые будут определятся в про¬
изводных классах. Благодаря полиморфизму этот интерфейс класса будет доступен
всем функциям в иерархии
абстрактных базовых классов, мы
абстрактные базовые классы. Такие указатели
полиморфных манипуляциях с объектами про¬
Хотя мы не можем создавать объекты
можем объявлять указатели на
могут затем использоваться в
изводных конкретных классов, когда
такие
объекты будут созданы.
Давайте рассмотрим пример полиморфизма
и
виртуальных
функций
в
действии. Это будет некий экранный менеджер, который выводит на экран
разнообразные объекты, включая объекты тех типов, что могут быть введены
систему уже после создания программы
менеджера. Системе может потре¬
боваться выводить на экран различные геометрические формы (т.е. базовым
классом здесь является класс Shape), как, например, квадраты, окружности,
в
треугольники, прямоугольники и другие
базового
фигуры (каждый
из этих классов
фи¬
экрана для работы с объ¬
ектами всех этих классов использует указатели на базовый класс. Для того,
гур выводится
из
класса
чтобы нарисовать любой объект
Shape). Менеджер
(независимо
от уровня
иерархии классов,
в
объект появляется), менеджер использует указатель на базовый
класс, ссылающийся на нужный объект, и передает этому объекту сообщение
draw. Функция draw объявлена чистой виртуальной в базовом классе Shape и
котором этот
определяется
в каждом
из производных
классов.
Каждый объект
знает, как
Виртуальные функции
и
853
полиморфизм
Экранный менеджер не должен беспокоиться относи¬
тельно этих деталей, он просто передает каждому объекту сообщение «нарисо¬
вать самого себя».
нарисовать самого себя.
Полиморфизм особенно эффективен
при создании многоуровневых систем
В операционных системах, например, каждый
программного обеспечения.
физического устройства может функционировать совершенно отлично от
устройств других типов. Независимые от устройства команды read или write,
читающие или записывающие данные с устройств и на устройства, являются
тип
write, посланная объекту «драйвер
устройства», должна интерпретироваться этим драйвером в соответствии с
тттем, каким образом он управляет устройствами конкретного типа. Но сам
универсальными.
командами
себе
Команда
вызов команды write не зависит от
устройства
это просто команда nep<
некоторое число байт данных из памяти. Объект¬
но-ориентированная операционная система может использовать абстрактный
базовый класс, чтобы обеспечить соответствующий интерфейс для всех драй¬
дать указанному
устройству
устройств. Все производные классы наследуют общее поведение этого аб¬
страктного базового класса. Возможные действия драйверов устройств (т.е. об¬
щий интерфейс) определяются чистыми виртуальными функциями абстракт¬
ного базового класса. Реализация этих виртуальных функций, соответствую¬
веров
щих специфическим типам
ных классах.
драйверов устройств,
В главе 17 мы ввели понятие итераторов.
ление
класса-итератора, который
осуществляется в производ¬
Общепринятым
может осуществлять
является опреде¬
обход
всех
объектов
в
совокупности. Если вы хотите напечатать список объектов в связанном спис¬
ке, то вы, например, можете создать объект класса-итератора, который будет
при своем вызове возвращать следующий элемент связанного списка. Итерато¬
ры обычно используются в полиморфном программировании при обходе свя¬
занного списка из объектов различных уровней иерархии. В таком списке все
указатели были бы указателями на базовый класс.
20.6.
Пример: программа
начисления
платы, использующая возможности
Давайте
числения
применим виртуальные функции
зарплаты,
(рис. 20.1). Мы будем
которое
использовать
следующие производные
от
и
производится
Employee
заработной
полиморфизма
полиморфизм
с
учетом
в
программе
типа
базовый класс Employee. Мы создадим
Boss
работники, получающие
классы:
фиксированное еженедельное жалованье, независимо от количества
танных часов;
на¬
служащего
отрабо¬
CommissionWorker
сотрудники, которые получают фиксиро¬
ванный оклад плюс процент от продаж; PieceWorker
рабочие, жалованье
которых определяется количеством единиц
произведенной
продукции,
и
Ho¬
urlyWorker
те, кто имеют почасовую оплату.
Ко всем служащим, разумеется, должен быть применим вызов функции
начисления зарплаты earnings. Но способ, которым вычисляется доход каж¬
дого сотрудника, определяется классом, к которому он принадлежит, и все эти
классы являются производными от
ция
базового
earnings объявляется виртуальной
в
класса
Employee. Поэтому функ¬
Employee, a соответ¬
базовом классе
Затем, при
функцию earnings
ствующие ее реализации вводятся в каждом производном классе.
расчете дохода любого сотрудника, программа
вызывает
854
Глава 20
при помощи указателя на базовый класс, ссылающегося на
соответствующий
объект производного класса. В настоящей программе расчета заработной пла¬
ты объекты, соответствующие типам работников, могли бы храниться в свя¬
занном списке с указателями типа
Давайте теперь рассмотрим класс
крытой
*. И тогда программа просто про¬
Employee
ходила бы по узлам этого списка, используя
ла бы функцию earnings каждого объекта.
указатели
фамилией работника
в
и вызыва¬
части 1 и 2). В от¬
функции-элементы: конструк¬
Employee (рис. 20.1,
секции класса объявляются следующие
тор именем и
Employee *,
качестве
аргументов; деструктор, кото¬
рый освобождает динамически выделенную память; get-функция, которая
возвращает имя сотрудника; get-функция, возвращающая фамилию; и нако¬
нец, две чистых виртуальных функции
earnings и print. Почему эти функ¬
виртуальные? Ответ прост: потому что не имеет смысла опреде¬
лять эти функции в классе Employee. Объявляя эти функции чистыми виртуа¬
льными, мы указываем на то, что будем определять эти функции в производ¬
чистые
ции
ных классах, но не в
жащего
I
//
вообще
базовом
классе.
Мы
не можем рассчитать доход для слу¬
сначала нужно знать категорию этого служащего.
EMPLOY2.H
I // Абстрактный
базовый
класс
Employee
#ifndef
EMPLOY2_H
#define EMPL0Y2_H
class
Employee
{
public:
Employee(const
char
const
*,
-Employee();
const char *getFirstName()
const
//
char
Чистые
виртуальные
Employee в
virtual float
*);
const;
const;
*getLastName()
//
char
функции превращают
базовый класс.
класс
абстрактный
earnings()
virtual void pnnt()
const
const
=
=
0;
//
чистая
виртуальная
// функция
// чистая виртуальная
// функция
0;
private:
char *firstName;
char *lastName;
}'
#endif
Рис. 20.1. Абстрактный базовый
класс
Employee (часть
1 из 12)
(рис. 20.1, части 3 и 4) выведен из класса Employee с откры¬
Открытые функции-элементы этого класса включают в
себя конструктор, который имеет параметры: имя, фамилию и еженедельное
жалованье, и передает имя и фамилию конструктору класса Employee, кото¬
рый присваивает значения элементам-данным firstName и lastName (наследу¬
емым производными классами); set-функцию, присваивающую новое значе¬
Класс Boss
тым
наследованием.
ние закрытому элементу данных
nings
weeklySalary;
вычисления дохода служащего
виртуальную функцию ear¬
Boss; виртуальную функцию print,
рая выводит категорию служащего и его имя.
кото¬
Виртуальные функции
и
855
полиморфизм
//
EMPL0Y2.CPP
//
Определение функций-элементов
абстрактного базового класса Employee.
//
//
// Замечание:
чистые
<iostream.h>
frinclude
#include
<string.h>
<assert.h>
#include
"employ2.h"
//
//
//
функции
виртуальные
#include
не
определяются
Конструктор динамически выделяет память для
имени и фамилии и при помощи функции strcpy
копирует имя и фамилию в соответствующие элементы данных.
char
Employee::Employee(const
*first,
const
char
*last)
{
firstName
=
new
assert(firstName
char[ strlen(first) + 1 ];
!= 0);
// проверка выделения
strcpy(firstName,
lastName
=
new
assert(lastName
char[ strlen(last) + 1 ];
!= 0);
// проверка выделения
strcpy(lastName,
памяти
first);
памяти
last);
}
// Деструктор освобождает
Employee::~Employee()
{
delete [] firstName;
delete [] lastName;
}
динамически
выделенную
память
// Возвращает указатель на имя сотрудника
char
*Employee::getFirstName() const
const
{
//
const
//
данные.
//
строку,
//
память
return
//
не
прежде
и
char
вызываемой
станет
//
после
//
освобождена
указатель
на
скопировать
удалит
деструктор
указателя
закрытые
возвращаемую
динамически
выделенную
неопределенным.
память
вызова
фамилию
изменять
функции
должна
функция
чем
значение
firstName;
Возвращает
sonst
позволяет
Вызывающая
должна
быть
сотрудника
*Employee::getLastName()
const
{
//
const
//
данные.
не
//
строку,
//
память
return
Рис. 20.1.
позволяет
Вызывающая
прежде
и
чем
значение
lastName;
вызываемой
функция
деструктор
указателя
//
после
освобождена
скопировать
удалит
станет
//
изменять
функции
должна
вызова
закрытые
возвращаемую
динамически
выделенную
неопределенным.
память
должна
Определения функций-элементов абстрактного базового
(часть 2 из 12)
быть
класса
Employee
Глава 20
856
//
//
I
BOSSl.H
класс
Boss,
производный
от
Employee
класса
#ifndef B0SS1_H
#define B0SS1_H
#include "employ2.h"
class Boss : public Employee {
public:
Boss(const char *, const char
float
*,
0.0);
=
void setWeeklySalary(float);
virtual float earnings() const;
virtual void print() const;
private:
float weeklySalary;
};
#endif
Рис. 20.1. Класс Boss, производный
//
I
от
абстрактного базового
класса
Определение функций-элементов
#include <iostream.h>
Конструктор
класса
Boss::Boss(const
I
//
weeklySalary
=
Boss
char
s
>
0
?
s
//
:
char
const
*first,
last)
Employee(first,
:
Boss
"bossl.h"
#include
{
3 из 12)
BOSSl.CPP
//
//
Employee (часть
класса
вызов
0;
*last,
float
конструктора
s)
базового
класса
}
Задание
типа
жалованья работника
Boss::setWeeklySalary(float s)
>
0
?
:
s
s
0; }
weeklySalary
Boss
void
I
{
I
//
=
сумму,
Возвращает
float
начисленную работнику
Boss::earnings()
const
{
return
// Вывод имени и фамилии Boss'a
void Boss::pnnt() const
{
cout << "\n
Boss:
<<
'
'
<<
типа
Boss
weeklySalary;
"
}
getFirstName()
<<
getLastName();
}
Рис. 20.1. Определение функций-элементов
Класс CommissionWorker (рис. 20.1,
зового
класса
Employee
с
менты класса включают:
открытым
части
класса Boss
5
и
6)
который
из
12)
является производным
наследованием.
конструктор,
(часть 4
ба¬
Открытые функции-эле¬
имеет
в
качестве
параметров
фамидию, жалованье, комиссию и количество проданных товаров и пере¬
дает имя и фамилию конструктору класса Employee для инициализации насле¬
дуемых данных-элементов базового класса firstName и lastName; set-функции,
присваивающие новые значения закрытым элементам данных salary, commis¬
имя,
sion и quantity; виртуальную функцию earnings, определяющую порядок на¬
числения жалованья работника CommissionWorker, и виртуальную функцию
print, которая
выводит категорию служащего, его имя и
фамилию.
Виртуальные функции
//
C0MMIS1.H
//
Класс
и
857
полиморфизм
CommissionWorker,
производный
от
класса
Employee
#ifndef
C0MMIS1_H
#define C0MMIS1_H
#include "employ2.h"
class
:
CommissionWorker
public
{
Employee
public:
char
CommissionWorker(const
float
char
const
*,
0.0,
=
float
*,
0.0,
=
int
=
0);
void setSalary(float);
void setCommission(float);
void setQuantity(mt);
virtual float earnings() const;
virtual void pnnt() const;
private:
float
float
mt
salary;
commission;
quantity;
//
//
недельный оклад
//
количество
комиссионные
за
единицу
проданного
проданного
за
неделю
товара
товара
};
#endif
Рис. 20.1. Класс CommissionWorker, производный
(часть
I
//
I
//
абстрактного базового
класса
Employee
COMMISl.CPP
Определения функций-элементов
#include <iostream.h>
#include
I
от
5 из 12)
класса
CommissionWorker
"commisl.h"
// Конструктор класса CommissionWorker
CommissionWorker::CommissionWorker(const char
const char *last, float s, float с, int q)
:
//
last)
Employee(first,
вызов
*first,
конструктора
базового
{
=
s
salary
commission
=
quantity
//
//
?
s
:
0;
>
0
?
с
>
0
?
q
:
q
0;
:
0;
CommissionWorker
работнику
CommissionWorker::setSalary(float
salary
=
s
>
0
?
s
:
0;
недельный
}
комиссионные
CommissionWorker::setCommission(float с)
commission
=
с
>
0
?
с
:
0;
}
// Задает количество проданного работником
// CommissionWorker товара
CommissionWorker: :setQuantity(mt q)
{ quantity
}
q > 0 ? q : 0;
=
//
Вычисляет
заработок
оклад
s)
Устанавливает работнику CommissionWorker
void
{
0
с
Устанавливает
void
{
>
=
работника CommissionWorker
const
CommissionWorker::earnings()
{ return salary + commission
*
quantity;
}
класса
Глава 20
858
// Выводит имя и фамилию работника CommissionWorker
void CommissionWorker::prmt() const
{
<<
cout << "\nCommission worker:
getFirstName()
"
1
'
<<
'
<<
getLastName();
}
Рис. 20.1. Определение функций-элементов
CommissionWorker (часть 6
класса
из
12)
Класс PieceWorker (рис. 20.1,
части 7 и 8) выведен из класса Employee с
кон¬
Открытые функции-элементы этого класса
структор с параметрами: имя, фамилия, плата за единицу произведенной про¬
дукции и количество произведенной продукции, который передает имя и фа¬
милию конструктору класса Employee для инициализации наследуемых из ба¬
зового класса элементов-данных firstName и lastName; set-функции, исполь¬
зуемые для присвоения значений закрытым элементам данных wagePerPiece
и quantity; виртуальная функция earnings начисления заработка работнику
типа PieceWorker и виртуальная функция print, которая выводит тип служа¬
открытым наследованием.
щего, его*имя и
фамилию.
//
PIECEl.H
//
Класс
PieceWorker,
класса
от
производный
Employee
#ifndef
PIECEl_H
#define PIECEl_H
#include "employ2.h"
class
PieceWorker
:
public
{
Employee
public:
PieceWorker(const
float
char
=
*,
0.0,
const
int
char
*,
0);
=
void setWage(float);
void setQuantity(int);
virtual float earnings() const;
virtual void print() const;
private:
float wagePerPiece;
int quantity;
//
//
//
оплата
за
единицу
произведенной продукции
произведено за неделю
};
#endif
Рис. 20.1. Производный класс PieceWorker
(часть
от
абстрактного базового
класса
Employee
7 из 12)
HourlyWorker (рис. 20.1, части 9 и 10) является производным от
Employee с открытым наследованием. Открытыми функциями-элемен-
Класс
класса
этого класса являются
конструктор с параметрами: имя, фамилия,
ставка почасовой оплаты и число отработанных часов, передающий имя и фа¬
тами
милию конструктору класса Employee, который присваивает значения насле¬
дуемым данным-элементам firstName и lastName; set-функции, присваиваю¬
щие значения закрытым элементам данных wage и hours; виртуальная функ¬
ция начисления заработка earnings для рабочего типа HourlyWorker; виртуа¬
льная функция
print,
которая выводит тип служащего и его имя и фамилию.
Виртуальные функции
//
PIECE1.CPP
//
Определение
и
859
полиморфизм
класса
функций-элементов
#include
<iostream.h>
#include
"piecel.h"
// Конструктор класса PieceWorker
PieceWorker::PieceWorker(const char
float
:
//
last)
Employee(first,
*first,
int
w,
PieceWorker
вызов
char
const
*lasu,
q)
конструктора
базового
класса
{
=
wagePerPiece
quantity
q >
=
w
>
0
?
w
0
?
q
:
0;
:
0;
}
//
Установка
void
{
wagePerPiece
//
оплаты
ставки
за
единицу
PieceWorker::setWage(float
Задание
=
w
>
количества
0
?
w
:
0;
продукции
w)
}
произведенного
товара
PieceWorker::setQuantity(int q)
}
quantity
q > 0 ? q : 0;
void
{
=
//
заработка рабочему PieceWorker
PieceWorker::earnings() const
return quantity * wagePerPiece;
}
Начисление
float
{
// Вывод имени и фамилии рабочего PieceWorker
void PieceWorker::print() const
{
<< getFirstName()
Piece worker:
cout << "\n
"
<<
1
'
<<
getLastName();
Рис. 20.1. Определение функций-элементов
класса PieceWorker
(часть 8
из
12)
// HOURLYl.H
// Определение класса HourlyWorker
#ifndef HOURLYl_H
#define HOURLYl_H
#include "employ2.h"
class HourlyWorker : public Employee {
public:
HourlyWorker(const char *, const char *,
float
0.0);
0.0, float
void setWage(float);
void setHours(float);
virtual float earnings() const;
virtual void print() const;
private:
float wage;
// оплата за час
=
=
float
hours;
//
часы,
отработанные
за
неделю
#endif
Рис. 20.1. Класс HourlyWorker, производный от абстрактного базового
(часть 9
из
12)
класса
Employee
860
Глава 20
HOURLYl.CPP
//
класса
//
Определение функций-элементов
#include <iostream.h>
#include
HourlyWorker
"hourlyl.h"
// Конструктор класса HourlyWorker
HourlyWorker::HourlyWorker(const char *first,
float
:
=
hours
=
h
Установка
//
void
{
>
w
wage
I
0
?
w
>=
0
&&
вызов
=
w
Задание
>
0
?
w
<
h
168
h
?
почасовой
:
количества
конструктора
:
0;
I
базового
класса
w)
}
отработанных
заработка
float
{
*last,
оплаты
часов
=
Вычисление
char
0;
I void HourlyWorker::setHours(float h)
h >= 0 && h < 168 ? h : 0;
{ hours
//
const
h)
0;
:
ставки
//
HourlyWorker::setWage(float
wage
//
last)
Employee(first,
float
w,
}
работника типа
const
HourlyWorker
HourlyWorker::earnmgs()
return wage * hours;
}
// Вывод имени и фамилии работника
void HourlyWorker::pnnt() const
типа
HourlyWorker
{
cout
<<
<<
"\n
'
'
<<
"
Hourly worker:
getLastName();
<<
getFirstName()
}
Рис. 20.1. Определение функций-элементов
класса
HourlyWorker (часть
10 из 12)
Программа-тестер (рис. 20.1, части 11 и 12) начинается с объявления ука¬
ptr на базовый класс типа Emplopee *. Все три последующих фрагмен¬
та кода функции main подобны друг другу, так что мы обсудим только первый
фрагмент, в котором обрабатывается объект Boss. В строке
зателя
b
Boss
("John",
"Smith",
800.00);
создается объект b производного класса Boss с необходимыми конструктору ар¬
гументами, включая имя, фамилию и фиксированное жалованье. В строке
ptr
=
указателю
класса.
&b;
ptr
//
//
на
указателю на базовый
объекта производного
базовый
Это действие
класс
присваивается
класс присваивается адрес
мы должны выполнить
полиморфного поведения. Строка
// динамическое
ptr->print();
адрес
класса
объекта b производного
обязательно,
если хотим
добиться
связывание
функцию-элемент print объекта, на который ссылается ptr. Посколь¬
базовом классе функция print объявлялась виртуальной, будет вызвана
вызывает
ку в
такое поведение мы и называем полифункция print производного класса
пример динамического связывания, когда
мирфным. Этот вызов функции
функция вызывс ется через указатель на базовый класс, и решение, какую фун¬
кцию вызывать, откладывается до времени исполнения программы. В строке
Виртуальные функции
cout
11
<<
и
earned
861
полиморфизм
<<
$"
//
ptr->earnings();
связывание
динамическое
функции-элемента earnings объекта,
который ссылает¬
ptr. Поскольку в базовом классе earnings была объявлена виртуальной фун¬
кцией, во время выполнения программы будет вызываться функция earnings
производного класса, соответствующего обрабатываемому объекту. Это был
производится вызов
на
ся
еще один пример динамического связывания. В строке
//
b.print();
статическое
связывание
функция-элемент print класса Boss применением операции
выбора элемента (точки) к объекту b класса Boss. Это пример статического свя¬
зывания, поскольку тип объекта, для которого вызывается функция, известен
во время компиляции. Этот вызов функции включен как доказательство того,
что при динамическом связывании вызывается правильная функция
print. В
строке
явно
вызывается
cout
"
<<
earned
явно вызывается
ции-точки
ния. Этот
к
<<
$"
функция-элемент earnings
объекту b
класса
Boss. Это
класса
для
// FIG20_1.CPP
// Программа формирования
#include
<iostream.h>
#include
<iomanip.h>
#include
"employ2.h"
"bossl.h"
#include
#include
"cortimisl.h"
#include
"piecel.h"
#include
"hourlyl.h"
Boss применением опера¬
доказательства
динамическом связывании вызывается правильная
I
связывание
также пример статического связыва¬
функции используется
вызов
статическое
//
b.earmngs();
иерархии
того,
что
при
функция earnings.
классов
main ()
{
//
Установка
<<
cout
Employee
Boss
//
*ptr;
b("John",
=
ptr
параметров
форматирования
setiosflags(ios::showpoint)
&b;
//
//
указатель
"Smith",
на
базовый
<<
11
класс
b.print();
"
earned
cout <<
$м
$"
<<
<<
CommissionWorker
=
&c;
//
//
ptr->print();
Рис. 20.1.
Иерархия
адрес
динамическое
связывание
ptr->earnings();
связывание
//
динамическое
//
статическое
связывание
статическое
связывание
b.earnings();
//
ptr
присваивается
класса
//
earned
класс
800.00);
указателю на базовый
объекта производного
ptr->pnnt();
cout
вывода
<< setprecision(2);
c("Sue",
"Jones",
указателю на базовый
объекта производного
200.0,
класс
3^.0,
150);
присваивается
адрес
класса
//
динамическое
классов, производных от абстрактного
(часть 11 из 12)
базового
связывание
класса
Employee
862
Глава 20
<<
cout
"
earned
c.pnnt();
"
cout <<
earned
$"
<<
ptr->earnings()r
// динамическое
//
$"
<<
PieceWorker
=
"Lewis",
p("Bob",
//
&p;
//
<<
"
p.pnnt();
"
cout <<
earned
$"
<<
<<
//
&h;
=
//
"Pnce",
<<
"
earned
h.pnnt();
"
cout <<
earned
$"
<<
<<
<<
return
статическое
связывание
40);
адрес
динамическое
связывание
ptr->earnmgs();
связывание
//
динамическое
//
статическое
связывание
статическое
связывание
h.earnmgs() ;
endl;
0;
Commission
Boss:
John
Smith
earned
Boss:
John
Smith
earned
worker:
Commission worker:
Sue
$800.00
$800.00
Jones
earned
$650.00
Sue
Jones
earned
$650.00
Piece
worker:
Bob
Lewis
earned
$500.00
Piece
worker:
Bob
Lewis
earned
$500.00
Hourly worker:
Hourly worker:
Рис. 20.1.
присваивается
класса
//
cout
связывание
связывание
класс
//
$"
связывание
статическое
13.75,
указателю на базовый
объекта производного
ptr->pnnt();
cout
адрес
p.earnings();
h("Karen",
HourlyWorker
динамическое
ptr->earnings();
// динамическое
//
ptr
присваивается
класса
//
$"
связывание
класс
//
earned
статическое
200);
2.5,
указателю на базовый
объекта производного
ptr->pnnt{);
cout
связывание
c.earnings();
//
ptr
связывание
статическое
Иерархия
Karen
Price
earned
$550.00
Karen
Price
earned
$550.00
классов, производных от
(часть
20.7. Новые
Полиморфизм
и
абстрактного базового
класса
Employee
12 из 12)
классы и динамическое связывание
виртуальные функции
безусловно будут работать, когда
работают и
все возможные классы известны заранее. Но они также успешно
тогда, когда постоянно вводятся новые виды классов.
Новые
вания
классы адаптируются к системе при помощи динамического связы¬
(называемого
функции
нения в
тип
также
объекта
такой
вызов
поздним связыванием). При
вызове
во время компиляции знать не нужно.
«подставляется»
вызов
виртуальной
Во время
функции-элемента
выпол¬
класса, соот¬
обрабатываемому объекту.
Программа экранного менеджера может работать с новыми объектами, ко¬
торые вводятся в систему, и ее не нужно каждый раз перекомпилировать. Все
ветствующего
Виртуальные функции
и
863
полиморфизм
функции draw остаются без изменений; новые объекты содержат фун¬
кции рисования непосредственно в себе. В такую систему легко могут быть
введены новые возможности, причем с минимальными затратами. И, как ре¬
зультат, расширяется область применения такой программы.
вызовы
Динамическое
связывание дает
лям программного
обеспечения (ISV
возможность
независимым
производите¬
Independent Software Vendors)
распро¬
странять свои программы, не раскрывая являющихся их собственностью сек¬
Распространяемое ими программное обеспечение может состоять толь¬
файлов заголовков и объектных файлов. Исходные тексты можно не рас¬
пространять. Разработчики, получившие такой продукт, могут затем, исполь¬
ретов.
ко из
зуя механизм наследования, вывести новые классы из полученных от ISV.
Программное обеспечение,
будет работать
мическому
которое
работает
с поставляемыми
ISV классами,
(благодаря дина¬
функции производных
и с классами, производными от них, вызывая
связыванию)
замещающие
виртуальные
классов.
При динамическом связывании во время выполнения программы вызов
виртуальной функции-элемента перенаправляется функции из соответствую¬
щего класса. Для этой цели используется таблица виртуальных функций, на¬
зываемая
на
vtable, которая реализована
функции. Каждый
цу vtable.
затель
на
в виде массива, содержащего указатели
содержащий виртуальные
Для каждой виртуальной функции класса в
класс,
функцию, используемую
функции, имеет табли¬
vtable
включается ука¬
для объекта этого класса.
Виртуальная
используемая в конкретном классе, может быть либо определена в
нем, либо унаследована, непосредственно или косвенно, от стоящего выше в
иерархии базового класса.
функция,
Определенная
в
базовом
классе виртуальная
функция-элемент
может
быть
замещена в производном классе, но производные классы не должны делать это
в обязательном порядке. Это значит, что производный класс может использо¬
вать виртуальную
функцию-элемент
базового класса,
из
что
будет
отражено в
vtable.
Каждый объект класса с виртуальными функциями содержит указатель
на vtable своего класса. Этот указатель для программиста недоступен. Соответ¬
ствующий указатель на функцию выбирается из vtable и разыменовывается;
на этом завершается формирование вызова
полнения программы.
На просмотр vtable
виртуальной функции
во время вы¬
и разыменование указателя
требу¬
ются незначительные ресурсы системы.
Совет по повышеншЬ
эффективности
20.1
Обеспечиваемый виртуальными функциями и динамическим связыванием полимор¬
эффективен. Использование
эффективность системы.
физм довольно
на
Совет по повышению
эффективности
этих механизмов незначительно влияет
20.2
Полиморфизм, обеспечиваемый виртуальными функциями идинамическим связыва¬
нием, можно рассматривать как конкурента оператора switch И здесь стоит заме¬
тить, что обычно оптимизирующие компиляторы С++ для программ, использующих
полиморфизм, генерируют столь же
оператора switch.
эффективный
код, как и код на основе логики
864
Глава 20
20.8.
При
использовании
объектами иерархии
Виртуальные деструкторы
полиморфизма
в
работе
с динамически выделяемыми
Если объекты раз¬
рушаются при помощи операции delete с указателем на базовый класс, то вы¬
зывается деструктор базового класса. Это происходит независимо от типа объ¬
екта, на который ссылается указатель на базовый класс, и от того факта, что
классов могут возникнуть трудности.
деструктор каждого класса имеет другое имя.
Имеется простое решение этой проблемы
деструктор базового класса
объявляется виртуальным. Это приведет к тому, что деструкторы всех произ¬
водных классов будут виртуальными, даже если их имена отличаются от име¬
ни деструктора базового класса. И теперь, если объект в иерархии разрушает¬
ся при помощи операции delete, а указатель, ссылающийся на объект, являет¬
ся
указателем
на
базовый класс,
вызывается
деструктор
соответствующего
производного класса.
Хороший
Если
стиль
программирования 20.2
класс имеет виртуальные
даже если это и не
сы
могут
иметь
требуется
свои
функции, определяйте деструктор
в данном
деструкторы,
и
также виртуальным,
конкретном случае Производные
тогда
вызываться
будут
именно
от него клас¬
они
Распространенная ошибка программирования 20.3
Объявление конструктора виртуальной функцией Конструктор
не может
быть вирту¬
альным
20.9. Пример: наследование
и реализации
интерфейса
В нашем следующем примере
(рис. 20.2) мы вернемся к иерархии классов:
предыдущей главы, только на этот раз на вершину
иерархии поставим абстрактный базовый класс Shape. Shape содержит чис¬
и, следовательно, является
тую виртуальную функцию
printShapeName
абстрактным базовым классом. В Shape имеются две других виртуальных
функции, а именно area и volume, каждая из которых возвращает нулевое
значение. Класс Point наследует эти функции от Shape. И в этом есть смысл,
точка, круг, цилиндр
из
потому что и площадь и объем точки равны нулю. Класс Circle наследует фун¬
кцию volume от класса Point, но вводит свою реализацию функции area.
Класс Cylinder имеет свои версии как функции volume, так и функции area.
Обратите
внимание на то, что хотя
Shape
классом, в нем определяются некоторые
и является
абстрактным базовым
функции-элементы,
и реализации этих
функций наследуются. Будем говорить, что три виртуальные функции класса
Shape определяют наследуемый интерфейс, который будет присущ всем элемен¬
там иерархии. Кроме этого, класс Shape предусматривает некоторые реализа¬
ции, которые наследуются производными классами в первых уровнях иерархии.
Этот пример подчеркивает, что класс может наследовать от базового клас¬
интерфейс и реализацию. Иерархии, наследующие реализацию, ассоцииру¬
ют свои функциональные свойства с верхними уровнями иерархии. Функцио¬
са
нальные
свойства иерархий, наследующих интерфейс, ассоциированы
низкими уровнями.
с
более
Виртуальные функции
Базовый
альных
класс
функций
Shape (рис. 20.2,
и не
865
полиморфизм
и
часть
1)
состоит из трех открытых вирту¬
содержит данных. Функция
printShapeName
объявля¬
ется как чисто виртуальная, поэтому переопределяется в каждом из производ¬
Функции area и volume определены и возвращают значение 0.0.
Эти функции замещаются в тех производных классах, для которых изменяют¬
ся формулы вычисления площади или объема.
ных классов.
//
//
SHAPE.H
Определение абстрактного базового
класса
Shape
#ifndef SHAPE_H
#define SHAPE_H
class Shape {
public:
virtual float<area()
}
virtual
float
virtual
void
const
volume()
{
const
printShapeName()
return
{
0.0;
return
const
=
}
0.0;
0;
}
//
чистая
//
функция
виртуальная
;
#endif
Рис. 20.2.
Определение абстрактного базового
класса
Shape (часть
1 из 9)
(рис. 20.2, части 2 и 3) выведен из класса Shape открытым на¬
Поскольку точка не имеет ни площади, ни объема, функции-эле¬
Класс Point
следованием.
менты
базового
класса area и
volume
Shape. Здесь
определяется функция
на как чисто виртуальная в базовом
не замещаются, а наследуются от класса
printShapeName, которая была объявле¬
классе. Другие функции-элементы вклю¬
себя set-функцию, присваивающую новые значения x и у координатам
объекта Point, и get-функции, возвращающие значения координат x и у.
чают в
//
POINTl.H
//
Определение
#ifndef
класса
Point
P0INT1_H
#define POINTl_H
#include <iostream.h>
#include
class
"shape.h"
Point
friend
:
{
public Shape
ostream
&operator
<<
(oatream
&,
const
Point
&);
public:
// конструктор по умолчанию
Pomt(float
0, float
0);
void
setPoint(float, float);
float getX() const { return x; }
float getY()
const
return у;
{
}
virtual void printShapeName()
const
cout << "Point::
";
{
private:
float x, у;
// x и у координаты Point
=
=
};
#endif
Рис. 20.2. Определение
28
Зак.801
класса Point
(часть 2
из
9)
}
866
Глава 2Q
//
P0INT1.CPP
// Определение функций-элементов
#include <iostream.h>
#include
Point
класса
"pomtl.h"
Point::Point(float
а,
float b)
{
x=a;
У=Ь;
}
void
Point::setPomt(float
float b)
а,
{
x=a;
y=b;
}
ostream
&operator
<<
(ostream &output,
const
Pomt
&p)
{
output
<<
<<
'['
return output;
p.x <<
11
",
<<
// разрешает
p.y
<<
'] ;
вызовы
последовательные
}
Рис. 20.2.
Определения функций-элементов
Класс Circle (рис. 20.2,
наследованием.
Круг
части
не имеет
4
5)
и
класса Point
(часть 3
из
9)
выводится из класса Point с открытым
объема, поэтому функция-элемент volume базо¬
Shape (через
класс Point). Функ¬
Функция printShapeNaреализацией чистой виртуальной функции, определенной в базо¬
вого класса не замещается, а наследуется из
ция area замещается, так как Circle имеет площадь.
me является
вом классе.
класса
Если эту функцию
не переопределить здесь, класс унаследует ее из
Point.
// CIRCLE1.H
// Определение
класса
Circle
#ifndef CIRCLEl_H
#define CIRCLEl_H
#include "pointl.h"
class
Circle
friend
:
public Point
ostream
public:
// конструктор
Circle(float
r
&operator
по
=
{
<<
(ostream
radius;
const
Circle
&);
умолчанию
0.0,
float
x
void setRadius(float);
float getRadius() const;
virtual float area() const;
virtual void printShapeName()
private:
float
&,
=
float у
0.0,
const
{
cout
<<
0.0);
=
"Circle:
// радиус Circle
};
#endif
Рис. 20.2. Определение
класса Cirde
(часть 4
из
9)
";
}
Виртуальные функции
//
и
867
полиморфизм
CIRCLE1.CPP
класса
//
Определение функций-элементов
#include <iostream.h>
#include
<iomanip.h>
"circlel.h"
#include
//
класса
Конструктор
Circle::Circle(float
:
//
Задание значения радиуса
Circle::setRadius(float
>
r
0
?
r
0;
:
Получение
значения
r)
const
// Расчет площади круга
float Circle::area() const
//
Вывод параметров
круга
<<
&operator
базового
класса
{
radius
=
0
?
{
return
radius;
}
>
r
r
:
0;
*
radius;
}
радиуса
Circle::getRadius()
ostream
Point
b)
}
void
float
класса
конструктор
float
а,
конструктора
radius
=
вызывает
float
вызов
//
b)
Point(a,
Circle
r,
{
//
Circle
return
{
в
3.14159
*
radius
}
Center=[x, y]; Radius=#.##
&output, const Circle &c)
виде:
(ostream
{
output
<<
<<
<<
return
|
'['
"];
<<
c.getX()
Radius
=
"
setprecision(2)
//
output;
«
<<
<<
11
<< c.getY()
setiosflags(ios::showpomt)
",
c.radius;
разрешает последовательные
вызовы
}
Рис. 20.2. Определение функций-элементов
класса Circle
(часть 5
из
9)
Set-функция присваивает новое значение радиуса объекту класса Circle и
get-функция возвращает радиус Circle.
Класс Cylinder (рис. 20.2, части 6 и 7) выводится из класса Circle откры¬
тым наследованием. Поскольку Cylinder имеет площадь и объем, функции
area и volume замещаются в этом классе. Функция printShapeName
реали¬
зация виртуальной функции, которая была определена как чистая виртуаль¬
ная в базовом классе. Если эту функцию не заместить в этом классе, то он уна¬
следует ее из класса
Circle. Другая функция-элемент
ция, присваивает новое значение элементу данных
Программа-тестер (рис. 20.2,
части
8
и
9)
этого класса,
height
класса
set-функ¬
Cylinder.
начинается с создания
объекта
Point, объекта circle класса Circle и объекта cylinder класса Cy¬
linder. Далее вызывается функция printShapeName каждого объекта, кото¬
рая выводит параметры объекта, используя перегруженную операцию поме¬
point
класса
щения в поток вывода, как доказательство того, что
циализированы.
тель
Затем объявляется указатель ptr
используется
для
ссылки
на
производные
от
Сначала указателю ptr присваивается адрес point
вызовы:
ptr->pnntShapeName
ptr->area ()
ptr->volume()
28*
()
объекты правильно ини¬
Shape *. Этот указа¬
типа
базового
класса
объекты.
и производятся следующие
868
Глава 20
//
//
CYLINDER.H
Определение
Cylmder
класса
#ifndef CYLINDER_H
#define CYLINDER_H
#include "circlel.h"
class Cylinder : public Circle {
friend ostream &operator << (ostream &,
public:
// конструктор по умолчанию
0.0, float r
Cylinder(float h
0.0,
float x = 0.0, float у
0.0);
=
const
Cylinder
=
=
void setHeight(float);
virtual float area() const;
virtual float volume() const;
virtual void prmtShapeName()
private:
float height;
);
//
высота
const
объекта
{
cout
<<
"Cylinder
Cylmder
#endif
Рис. 20.2. Определение
//
класса
#include
класса Cylinder
<iomanip.h>
#include
"cylindrl.h"
h,
Cylmder::Cylinder(float
{
6 из 9)
CYLINDR1.CPP
// Определения функций-элементов
#include <iostream.h>
:
Cylinder (часть
//
Circle(r, x, у)
h > 0 ? h :
height
вызов
=
0;
float
r,
float
конструктора
x,
float
базового
у)
класса
}
Cylinder::setHeight(float h)
h > 0 ? h : 0;
}
height
void
{
=
float
const
Cylmder::area()
{
//
площадь
return
*
2
*
2
поверхности
Cylinder
Circle::area()
*
3.14159
+
Circle::getRadius()
*
height;
}
float Cylmder::volume()
const
i
float
r
return
=
Circle::getRadius();
*
*
*
r
r
height;
3.14159
}
ostream
&operator
<<
(ostream
&output,
const
Cylinder &c)
{
output
<<
<<
<<
<<
return
'['
"];
«
"
<<
<< c.getY()
",
<<.setiosflags(ios::showpoint)
setprecision(2) << c.getRadius()
<< c.height;
";
Height
output;
c.getX()
Radius
=
//
"
=
"
разрешает
конкатенацию
вызовов
}
Рис. 20.2.
Определения функций-элементов
класса
Cylinder (часть
7 из 9)
);
Виртуальные функции
и
869
полиморфизм
функций объектов тех классов, на которые ссылается ptr. Из вывода програм¬
мы видно, что функции вызываются правильно для объекта point: выводится
строка, начинающаяся с символов "Point: ", и значения площади и объема,
равные 0.00. Затем ptr присваивается адрес объекта circle и вызываются те же
самые функции. Программа выдает правильный результат
выводится стро¬
ка "Circle: ", вычисленная площадь круга и объем, равный 0.00. В заключение
ptr получает адрес объекта cylinder и производятся вызовы тех же самых фун¬
вы¬
кций. Вывод программы подтверждает правильность вызовов функций
"
водится
строка
цилиндра. Все
при помощи
"Cylinder:
вызовы
и
значения
площади
функций printShapeName,
поверхности
area и
и
объема
volume разрешаются
механизма динамического связывания во время выполнения про¬
граммы.
FIG20_2.CPP
// Программа-тестер
//
#include
иерархии
<iostream.h>
#include
<iomanip.h>
#include
"shape.h"
#include
#include
"pointl.h"
"circlel.h"
#include
"cylindrl.h"
классов
Circle
Point,
и
Cylinder
main ()
{
//
создание
Pomt
геометрических
point(7,
Circle
circle(3.5,
Cylinder
фигур
11);
8);
22,
cylinder(10,
3.3,
10,
10);
point.printShapeName();
cout << pomt << endl;
//
статическое
связывание
circle.printShapeName();
cout << circle << endl;
//
статическое
связывание
cylinder.prmtShapeName();
cout << cylinder << "\n\n";
//
статическое
связывание
<<
cout
Shape
//
//
setiosflags(ios::showpoint)
//
*ptr;
указатель на
класса Point
определение
базовый
класс
<<
setprecision(2);
на базовый класс
указателя
ссылается
на
объект
производного
&point;
// динамическое связывание
ptr->printShapeName();
<< pomt.getX()
cout << "x
<<
<< pomt.getY()
";
у
<<
<< ptr->area()
"\nArea
<<
<< ptr->volume()
<<
"\nVolume
"\n\n";
ptr
=
=
"
=
11
=
"
=
11
// указатель на базовый
// класса Circle
&circle;
ptr
класс
ссылается
на
объект
производного
=
ptr->printShapeName();
Рис. 20.2.
// динамическое
Программа-тестер иерархии объектов point, cirde
и
связывание
cylinder (часть
8 из 9)
Глава 20
870
cout
<<
11
=
"x
<<
»
<<
";
<<
"\nArea
<<
=
у
=
"\nVolume
//
указатель
//
класса
на
circle.getX()
circle.getY()
<< ptr->area()
<< ptr->volume()
<<
11
11
=
базовый
класс
"\n\n";
<<
ссылается
на
объект
производного
Cylmder
ptr
&cylinder;
// динамическое связывание
ptr->printShapeName();
<< cylmder.getX()
cout << "x
<<
<< cylinder.getY()
";
у
<<
<< ptr->area()
"\nArea
<< ptr->volume()
<< endl;
<<
"\nVolume
=
11
=
"
=
=
11
"
=
return
Point:
Circle:
0/
[7, 11]
[22, 8];
[10,
Cylinder:
Point:
x
Area
0.00
=
Volume
=
Volume
=
=
11.00
y
=
8.00
y
=
3.30;
Height
=
10.00
x
22.00;
=
38.48
=
0.00
=
Cylinder: x
Area = 275.77
Volume
у
3.50
=
Radius
0.00
=
Circle:
Area
7.00;
=
Radius
10];
=
10.00;
10.00
342.12
Рис. 20.2. Программа-тестер иерархии классов point, cirde и
cylinder (часть
9 из 9)
Резюме
Благодаря виртуальным функциям и полиморфизму стало возможным
разрабатывать системы, которые легко расширяются. Программы мо¬
гут обрабатывать объекты, тип которых не был известен на момент их
разработки.
Виртуальные функции
оператора
switch.
обеспечивает
и
полиморфизм
позволяют отказаться от логики
Механизм виртуальных функций
автоматически
логику поведения, и в результате исключа¬
свойственные switch-логике. Программа-клиент, кото¬
необходимую
ются ошибки,
рая сама определяет типы
объектов
и их поведение,
свидетельствует о
проработке
Виртуальная функция объявляется
недостаточной
классов.
при помощи ключевого слова virtu¬
al, с которого начинается прототип функции в базовом классе.
Производные классы могут вводить свои версии виртуальных функций
базового класса, если им это нужно. Если они не делают этого, то полу¬
чают в наследство
функции базового
класса.
Виртуальные функции
и
871
полиморфизм
Если виртуальная функция вызывается при помощи
класса
(что
и
операции-точки,
классом),
к
во
разрешается
связыванием)
имени
время
объекта
компиляции
будет виртуаль¬
унаследованная этим
которому принадлежит данный объект.
называется статическим
ная функция,
Во
ссылка
определенная в том классе
многих случаях
назначены для
и
вызвана
(или
бывает удобно определять классы, которые не
чтобы программист создавал объекты этих
того,
пред¬
клас¬
Такие классы называются абстрактными. И так как они использу¬
то
обычно их
ются в наследовании в качестве базовых классов,
сов.
абстрактными базовыми
называют
класса
нельзя
Классы, объекты которых могут быть созданы,
ми
Объекты абстрактного
классами.
создавать.
называются конкретны¬
классами.
Класс, имеющий виртуальные функции, может быть сделан абстракт¬
ным, если одну или несколько виртуальных функций объявить чисты¬
ми. Чистая виртуальная функция
виртуальная функция, в объявле¬
нии
которой содержится
Если
класс
выведен
определяет эту
из
инициализатор =0.
чистой виртуальной функцией и не
функция остается чистой и в производном
класса
функцию,
то
с
классе. Как следствие, производный класс тоже будет являться абст¬
рактным классом.
С++ поддерживает полиморфизм
классов, связанных наследованием,
одной и той же функции-элемента.
Полиморфизм обеспечивается
Когда виртуальная функция
базовый класс,
го
механизмом
виртуальных
на
вызов
функций.
С++ выбирает замещающую функцию соответствующе¬
производного
класса,
связанного
ссылается
При
использовании виртуальных
симости
по-разному
вызывается через посредство указателя на
рый
же вызов
способность объектов различных
реагировать
с
конкретным
объектом,
на кото¬
указатель.
функций и полиморфизма один
функции-элемента приводит к различным действиям в
от
типа
объекта, получившего
и тот
зави¬
вызов.
можем создавать объекты абстрактных базовых классов,
объявлять указатели на абстрактные базовые классы. Такие
указатели могут затем использоваться для обеспечения полиморфного
поведения объектов производных конкретных классов.
Хотя
мы
не
мы можем
В
системы могут вводиться новые виды классов.
Новые
руются к этим системам механизмом динамического
классы адапти¬
(позднего)
связы¬
разрешения вызова виртуальной функции тип объекта не
обязательно должен быть известен во время компиляции. Во время вы¬
полнения вызов виртуальной функции передается функции-элементу
вания.
Для
объекта.
участвующего
в
Динамическое
связывание дает
телям
вызове
программного
возможность независимым
обеспечения (ISV) распространять
производи¬
свои
програм¬
Распространяемое ими программное
обеспечение может состоять только из файлов заголовков и объектных
файлов. Исходные тексты можно не распространять. Разработчики, по¬
лучившие такой программный продукт, могут затем, используя меха¬
мы, не раскрывая своих секретов.
872
Глава 10
низм
наследования,
выводить новые классы из полученных от ISV.
Программное обеспечение, которое работает с поставляемыми ISV клас¬
сами, будет работать и с классами, производными от них, вызывая
(благодаря динамическому связыванию) замещающие виртуальные
функции производных классов.
При динамическом связывании во время выполнения программы вызов
виртуальной функции-элемента перенаправляется функции из соответ¬
ствующего класса. Для этой цели используется таблица виртуальных
функций,
vtable, реализованная
называемая
в
виде
массива,
содержа¬
указатели на функции. Каждый класс, содержащий виртуальные
функции, имеет таблицу vtable. Для каждой виртуальной функции
щего
класса в
vtable включается указатель
виртуальной функции,
Виртуальная функция, испо¬
на версию
используемую для объекта этого класса.
льзуемая
в
конкретном
может
классе,
либо унаследована, непосредственно
иерархии базового класса.
Определенная
в
базовом
быть переопределена
в
классе
быть либо определена
или косвенно,
функция-элемент
виртуальная
в
нем,
из вышестоящего в
может
производном классе, но производные классы не
должны делать это в обязательном порядке. Это значит, что производ¬
ный класс может использовать виртуальную функцию-элемент из базо¬
вого
класса,
что
Каждый объект
тель на
vtable
будет отражено
класса
виртуальными
функциями содержит указа¬
Этот указатель недоступен для программи¬
указатель на функцию выбирается из vtable и
своего класса.
Соответствующий
ста.
с
vtable.
в
разыменовывается; на этом завершается формирование во время вы¬
полнения вызова виртуальной функции. На просмотр vtable и разыме¬
нование указателя требуются незначительное время, обычно меньшее,
чем
в
Если
самом
лучшем
коде
клиента.
класс содержит виртуальные
функции,
то и деструктор
базового
объявлять виртуальным. Это приведет к тому, что дест¬
рукторы всех производных классов будут виртуальными, хотя их име¬
на отличаются от имени деструктора базового класса. И теперь, если
класса следует
объект
в иерархии разрушается при помощи операции delete, а указа¬
тель, ссылающийся на объект, является указателем на базовый класс,
вызывается деструктор правильного производного класса.
Терминология
абстрактный
базовый класс
наследование
альтернатива операторам switch
непосредственный базовый класс
неявное преобразование указателей
виртуальная функция
объектно-ориентированное
виртуальная функция базового
класса
переопределенная виртуальная
абстрактный класс
виртуальный деструктор
динамическое связывание
иерархия классов
ключевое слово virtual
конструктор производного класса
косвенный базовый класс
логика switch
программирование
функция
повторное использование
программного кода
позднее связывание
полиморфизм
преобразование объекта производного
класса в объект
Виртуальные функции
873
полиморфизм
и
преобразование
ссылка на производный класс
статическое связывание
указателя
производного класса в указатель
базового класса
указатель на абстрактный класс
указатель на базовый класс
указатель на производный класс
чисто виртуальная функция (=0)
производный класс
раннее связывание
расширяемость
ссылка на абстрактный класс
явное преобразование указателей
ссылка на базовый класс
Распространенные ошибки программирования
20.1.
Определение
20.2.
Попытка создать объект абстрактного
в производном классе виртуальной функции, не сов¬
падающей по типу возвращаемого значения или списку параметров
с функцией в базовом классе, приводит к синтаксической ошибке.
20.3.
Объявление конструктора виртуальной
может
Хороший
20.1.
функцией. Конструктор
программирования
Хотя функция производного
многие
класса является
виртуальной,
для
программисты,
ясности
придания
предпочитают использовать явное определение
20.2.
Если
на
всех
класс
уровнях
имеет
если на
было сделано соответствующее объявле¬
высших уровнях иерархии
альной
не
быть виртуальным.
стиль
ние,
(т.е. содержащего
функций) приводит к
класса
одну или несколько чистых виртуальных
синтаксической ошибке.
программе,
функции
как вирту¬
иерархии.
виртуальные
функции, определяйте деструктор
требуется в данном конк¬
также виртуальным, даже если это и не
ретном случае.
рукторы,
и
Производные
тогда
Советы по повышению
20.1.
будут
именно
полиморфизм
механизмов
они.
эффективности
Обеспечиваемый виртуальными функциями
зыванием
20.2.
от него классы могут иметь свои дест¬
вызываться
и
динамическим
свя¬
эффективен. Использование этих
повлияет на эффективность системы.
довольно
незначительно
Полиморфизм, обеспечиваемый виртуальными функциями
и дина¬
рассматривать как конкурента опе¬
И
switch.
стоит
здесь
заметить, что обычно оптимизирую¬
ратора
щие компиляторы С++ для программ, использующих полимор¬
мическим связыванием, можно
физм, генерируют столь же эффективный код, как и код на основе
логики оператора switch.
Общие
20.1.
методические замечания
Интересным
следствием
полиморфизма
вится меньше
ного кода.
использования
виртуальных
является простота и ясность программ.
фрагментов
с ветвлением и
функций
В
больше обычного линей¬
Такие программы проще тестировать, отлаживать
провождать.
и
них стано¬
и со¬
Глава 20
874
20.2.
Если функция была объявлена как виртуальная, она остается вир¬
туальной
20.3.
во
всей иерархии наследования.
Если
в производном классе виртуальная функция не определяется,
то он просто наследует функцию своего непосредственного клас-
са-предшественника.
20.4.
Переопределяемая
вращаемый
базового
20.5.
Если
виртуальная функция должна иметь тот же воз¬
параметров, что и виртуальная функция
тип и список
класса.
в классе,
базовый
класс которого содержит чистую виртуаль¬
функцию, эта функция не определяется, то она остается чис¬
той и в этом (производном) классе. Как следствие, производный
класс сам является абстрактным классом.
ную
20.6.
Благодаря полиморфизму
и
частные вопросы на среду исполнения.
лять
большим набором объектов, каждый
специфическое
20.7.
функциям программист
общих задачах, переложив
Программист может управ¬
виртуальным
может сосредоточить свое внимание на
поведение,
которых
имеет
свое
Полиморфизм обеспечивает расширяемость системы: программный
код, опирающийся на полиморфное поведение объектов, не зависит
от типа объектов, которым передается вызов. Таким образом, без
изменений основной системы,
всяких
вый
тип
объектов, который
может
в нее может
реагировать
набор сообщений. Программы не придется
исключением, возможно, некоторой части
здаются
20.8.
из
не зная даже типов этих объектов.
объекты
нового
на
быть введен
но¬
существующий
перекомпилировать,
клиента,
в
которой
за
со¬
типа.
В абстрактном
классе определяется интерфейс всех узлов иерархии
Абстрактный класс содержит чистые виртуальные функ¬
ции, которые будут определятся в производных классах. Благодаря
полиморфизму этот интерфейс класса будет доступен всем функци¬
классов.
ям
в
иерархии.
Упражнения
20.1.
для самоконтроля
Вставьте пропущенные слова
a) Использование
ться
от
в
следующих
наследования и
логики
утверждениях:
полиморфизма
позволяет отказа¬
.
b) Чистая
в
конце
виртуальная функция определяется символами
прототипа, объявляемого в базовом классе.
c) Если класс содержит
функций, он является
d) Определение
вается
одну или
несколько чистых
вызываемой функции
во время компиляции назы¬
связыванием.
e) Определение вызываемой функции
ется
виртуальных
.
связыванием.
во время исполнения называ¬
Виртуальные функции
Ответы
20.1.
на
и
875
полиморфизм
упражнения для самоконтроля
а) оператора switch. b)
тическим.
e)
=
0. с) абстрактным базовым
классом,
d)
ста¬
динамическим.
Упражнения
20.2.
Что
20.3.
Зная, что конструкторы не могут быть виртуальными, предложите
вариант, как можно реализовать нечто подобное.
20.4.
В упражнении 19.5 вы разработали иерархию класса Shape и опре¬
делили классы иерархии. Измените иерархию так, чтобы класс
такое виртуальные функции? Опишите, при каких обстоятель¬
ствах следует применять виртуальные функции.
Shape
был абстрактным базовым
классов иерархии.
Выведите
из
классом
и
определял
интерфейс
TwoDimensionalShaэти классы также должны быть аб¬
и размеров объекта определите вир¬
Shape
классы
pe и ThreeDimensionalShape
страктными. Для вывода типа
туальную функцию print. Также определите функции area и
volume
для вычисления соответствующих параметров объектов
каждого конкретного класса в иерархии. Напишите программу для
тестирования иерархии класса Shape.
20.5. Используя
класса List из упражнения 17.10, создайте свя¬
указателей на объекты класса Shape (т.е. указате¬
лей на любой объект в иерархии). Определите перегруженную опе¬
шаблон
занный
список
рацию
передачи
в
поток
для
класса
вызывает чистую виртуальную функцию
Shape, который просто
print. Используя возмож¬
ности вывода элементов связанного списка,
ведите
каждый
элемент.
пройдите
по нему и вы¬
Scan AAW
г
л
в
а
а
21
Потоки
ввода/вывода
в
С++
Цели
Понять принципы
вого
объектно-ориентированного
ввода/вывода С++.
Научиться форматированию
Изучить
иерархию
классов
ввода и вывода.
ввода/вывода.
потоков
Изучить организацию ввода/вывода
ляемого пользователем типа.
Научиться
потоко¬
объектов
опреде¬
создавать пользовательские манипуляторы
потока.
Научиться различать успешный
операций ввода/вывода.
или
неудачный
резуль¬
тат
Освоить привязку выходного потока ко входному.
878
Глава 21
Содержание
21.1. Введение
21.2. Потоки
21.2.1. Заголовочные
21.2.2.Классы
файлы библиотеки
объекты
и
потокового
iostream
ввода/вывода
21.3. Потоковый вывод
21.3.1.
Операция передачи
в
поток
21.3.2.Конкатенация операций передачи
21.3.3.Вывод переменных
21.3.4.функция-элемент put
21.4.1.
поток и
извлечения
из
потока
*
для вывода символов;
Конкатенация вызовов
21.4. Потоковый
char
типа
в
put
ввод
Опер.ация
извлечения
из
21.4.2.функции-элементы get
потока
и
21.4.3.Другие функции-элементы
getline
класса
istream
(peek, putback, ignore)
21.4.4.Безопасный по типу
21.5.
ввод/вывод данных
Функции неформатируемого ввода/вывода read, gcount
21.6. Манипуляторы
21.6.1.
write
потоков
установки основания целых чисел: dec, oct, hex
Манипуляторы
и
и
setbase
21.6.2.Установка
точности
представления
чисел
с
плавающей точкой
(precision, setprecision)
21.6.3.Ширина
поля
(setw, width)
21.6.4.0пределяемые
21.7.
пользователем
Флаги форматирования
манипуляторы
потока
флаги форматирования (setf, unsetf,
flags)
десятичные
(ios::showpoint)
ios::internal)
21.7.3.Выравнивание (ios::left, ios::right,
21.7.1.
21.7.2.Конечные нули
и
точки
21.7.4.3аполнение (fill, setfill)
21.7.5.флаги установки
основания целых чисел
(ios::dec, ios::oct, ios::hex,
ios::showbase)
21.7.6.Числа
с
плавающей точкой; научная
нотация
(ios::sdentific, ios::fixed)
21.7.7.Управление верхним/нижним регистрами (ios::uppercase)
21.7.8.Установка и сброс флагов форматирования (flags, setiosflags,
resetiosflags)
21.8. Состояния ошибки
21.9.
Ввод/вывод
21.10.Привязка
потоков
определяемых
потока
вывода
к
пользователем типов
потоку ввода
Потоки
ввода/вывода
879
в С++
Распространенные ошибки программирования
Хороший стиль
Советы по повышению эффективности
Общие методи¬
ческие замечания
Упражнения для самоконтроля Ответы на упражнения
для самоконтроля
Упражнения
Резюме
программирования
Введение
21.1.
Стандартные библиотеки С++ предоставляют вам широкий выбор возмож¬
ностей ввода/вывода. В этой главе обсуждается ряд методов, достаточных для
выполнения основных
даются в виде
операций ввода/вывода, а остальные
обзора.
Многие из описанных здесь особенностей ввода/вывода являются объект¬
краткого
но-ориентированными. Читателю, несомненно, будет интересно узнать, как
эти возможности реализованы в языке. Заметим, что такой стиль операций
ввода/вывода
интенсивно
ссылки и перегрузка
Как
использует
функций
и
особенности С++,
другие
такие,
как
операций.
С++ обеспечивает безопасный по типу ввод/вывод. Каж¬
ввода/вывода выполняется по-разному в зависимости от типа об¬
рабатываемых данных. Если для специфического типа данных определена
функция ввода/вывода, то она автоматически вызывается для обработки этого
типа. Если для некоего типа данных не найдена соответствующая функция,
компилятор выдает сообщение об ошибке. Такая защита не пропускает через
систему недопустимый тип данных (как это происходило в С; из-за этого недо¬
мы увидим,
дая операция
С происходили трудноуловимые и часто причудливые ошибки).
В дополнение к вводу/выводу предопределенных типов в С++ пользователь
может вводить операции для ввода/вывода своих собственных типов. Расширяе¬
статка в
одна из наиболее ценных особенностей С++.
мость языка
Правило хорошего программирования
В программах на С++ используйте
функции ввода/вывода
меняйте
Общее
ввод/вывод
из
только в стиле языка С++ и не при¬
библиотеки С,
хотя
они
и доступны
в
С++.
методическое замечание 21.1
Ввод/вывод
Общее
21.1
в
С++
безопасен
в
отношении
использования
различных типов.
методическое замечание 21.2
С ++ предлагает единообразный интерфейс
типов и типов, определяемых пользователем.
легчается
процесс
программного
разработки
программ и,
ввода/вывода
для предопределенных
Благодаря такому общему подходу об¬
в
частности,
повторное
использование
обеспечения
21.2. Потоки
В С++ ввод/вывод данных производится потоками байтов. Поток
это
байтовая последовательность. При операциях ввода байты направляют¬
просто
ся от
устройства (например,
память.
клавиатуры, дисковода, сетевой
В операциях вывода
поток
байтов направляется
устройство (например, экран монитора, принтер,
из
платы)
в основную
основной
дисковод, сетевую
памяти на
плату).
Глава 21
880
с этими байтами некоторый специ¬
Байты
ASCII-символы, сырые данные
могут
представлять
фический
внутреннего формата, графические изображения, оцифрованную речь, цифро¬
вое видео или любой другой вид информации, с которой работает прикладная
Прикладные
программы ассоциируют
смысл.
программа.
Работа системных механизмов
байтов данных от
стности
и
надежности
всех
ввода/вывода сводится к
обратно, и поддержании
в память и
данных.
перемещению
при этом цело¬
В процессах передачи данных нередко
устройства типа вращающегося магнитного диска,
Время, которое уходит на обмен данными с такими
механические
участвуют
ленты,
устройств
клавиатуры.
устройствами, обычно значительно превышает время, которое тратит процес¬
сор на обработку этих данных. Поэтому операции ввода/вывода требуют тща¬
тельного планирования и настройки для того, чтобы обеспечивалась максима¬
льная производительность системы. Эти вопросы подробно обсуждаются в
операционным системам (см. De90).
В С++ имеются возможности ввода/вывода «высокого уровня» и «низкого
уровня». Низкоуровневый (т.е. неформатируемый) ввод/вывод обычно состо¬
ит в передаче заданного числа байт от устройства в память или из памяти
устройству. В таких операциях единицей информации является байт. Опера¬
ции низкого уровня способны передавать, и передавать быстро, большие объе¬
мы информации, но в не слишком удобной для человека форме.
Люди предпочитают пользоваться операциями ввода/вывода высокого
уровня, т.е. форматируемым вводом/выводом, в котором байты группируются
в элементы данных типа целых чисел, чисел с плавающей точкой, символов,
книгах по
строк и определяемых пользователем типов.
ки данных
Совет
Такой, ориентированный
на тип
большинстве задач ввода/вывода, кроме обработ¬
большого объема.
данных, подход
требуется
по повышению
в
эффективности 21.1
Применяйте неформатируемый ввод/вывод при обработке файлов большого объема.
21.2.1. Заголовочные
Библиотека
языка
файлы библиотеки iostream
С++ iostream предоставляет программисту
сотни раз¬
нообразных возможностей ввода/вывода. Части библиотечного интерфейса со¬
держатся в нескольких файлах заголовков.
Большинство программ С++ должно включать файл заголовка iostream.h,
который содержит основную информацию, необходимую для всех операций
потокового ввода/вывода. В файле заголовка iostream.h содержатся объек¬
ты-потоки cin, cout, cerr и clog, которые, как мы вскоре увидим, ассоцииру¬
ются со стандартным потоком ввода, стандартным потоком вывода
ным потоком
ошибок. Здесь поддерживаются
неформатируемого ввода/вывода.
Файл заголовка iomanip.h содержит
управления
возможности
информацию,
форматируемым вводом/выводом
и стандарт¬
форматируемого
необходимую
и
для
с помощью так называемых па¬
раметризованных манипуляторов потока.
Заголовок fstream.h содержит важную информацию, нужную для управ¬
файлового ввода/вывода.
файле strstream.h содержится информация, необходимая
форматирования в памяти. Эти операции напоминают опе¬
ляемого пользователем
В
заголовочном
для выполнения
Потоки
ввода/вывода
рации при
881
в С++
обработке файла,
файлами,
только выполняются они не над
а над
массивами символов.
Заголовок stdiostream.h должен включаться в программы, которые совме¬
ввода/вывода языков С и С++. Во вновь создаваемых програм¬
щают операции
мах следует
избегать ввода/вывода
С++, найдут такую
возможность
С;
в стиле
жкой существующих программ на языке
С
но люди, занимающиеся поддер¬
или
переводящих
С-программы
на
полезной.
Каждая система программирования на С++ содержит дополнительные
библиотеки ввода/вывода для работы со специфическими устройствами, на¬
ввода/вывода
пример,
аудио-
21.2.2. Классы
и
и
видео-информации.
объекты
потокового
ввода/вывода
Библиотека iostream содержит большое количество классов, поддержива¬
ющих разнообразные операции ввода/вывода. Класс istream поддерживает
операции потокового ввода. Класс ostream обеспечивает выполнение операций
потокового вывода. Класс iostream поддерживает операции обоих типов: пото¬
кового ввода и потокового вывода.
Оба класса, istream и ostream, выводятся простым наследованием из базо¬
ios. Класс iostream выводится путем сложного наследования от
вого класса
istream
21.1.
классов
и
ostream. Отношения наследования
этих классов
изображе¬
ны на рис.
ios
iostream
Рис. 21.1. Часть иерархии
классов потоков
ввода/вывода
Для операций ввода/вывода используется удобная нотация
с применением
Для обозначения
операции потокового вы¬
вода используется перегруженная операция левого сдвига («), которая назы¬
вается операцией передачи в поток. Аналогично, для обозначения операции
знаков
перегруженных операций.
потокового ввода используется перегруженная операция правого сдвига
применяться
со
(»),
Эти операции могут
объектами
потоков
cin,
cout,
cerr, clog и с объ¬
стандартными
которая называется
операцией
извлечения
из
потока.
ектами потоков, определяемыми пользователем.
Объект cin
является
объектом
класса
istream, ассоциированным
со стан¬
В следующем
от
из
показана
извлечения
значение,
потока;
полученное
операторе
операция
дартным
объекта
cin
устройством
ввода данных;
обычно это клавиатура.
cin, присваивается переменной grade
>>
целого типа:
grade;
Заметим здесь,
что операция извлечения из потока достаточно
была должным образом объявлена,
то тип
ции извлечения из потока, специально
ся, например, в случае
ввода/вывода
в
«сообрази¬
Если переменная
grade
переменной, участвующей в опера¬
указывать не нужно (как это требует¬
языке С).
тельна» и «знает», с каким типом данных имеет дело.
882
Глава 21
Объект cout класса ostream ассоциирован со стандартным
вывода; обычно это монитор.
следующем
<<
устройство
устройством
передачи в поток, использованная
операторе, выводит значение целой
на стандартное
cout
Операция
в
переменной grade (из памяти)
вывода:
grade;
Заметим, что операция передачи в поток также достаточно «умна» и «зна¬
переменной grade (которая должна быть должным образом объявле¬
на), так что никакая дополнительная информация о типе для операции поме¬
ет»
тип
требуется.
Объект cerr класса ostream ассоциируется со стандартным устройством
ошибок с небуферизованным выводом. Последнее означает, что результат
щения в поток не
каждой операции передачи в поток cerr выводится немедленно, а не помеща¬
ется в буфер; это необходимо для оперативного оповещения пользователя о
возникшей
проблеме.
Объект clog принадлежит классу ostream и ассоциируется со стандартным
устройством ошибки. Вывод на clog буферизован.
При работе с файлами в С++ класс ifstream используется для выполнения
для операций файлового вывода
файловых операций ввода, класс ofstream
и класс fstream
для обоих видов операций. Класс ifstream является потом¬
ком класса istream, класс ofstream
потомок ostream и класс fstream
производный от класса iostream. Связи наследования этих классов ввода/вы¬
вода графически представлены на рис. 21.2. Полная иерархия классов потоко¬
вого ввода/вывода, поддерживаемая в большинстве систем, включает в себя
намного
больше классов, однако классы, показанные здесь, обеспечивают до¬
большинства программистов их будет
статочно широкие возможности и для
достаточно. За
описанию
подробной информацией
библиотеки
классов
вашей
по
работе
с
файлами обращайтесь
системы программирования на
к
С++.
ioa
fstraam
Рис. 21.2. Часть иерархии классов потокового
ввода/вывода
файлового ввода/вывода
с основными классами
21.3. Потоковый вывод
Класс ostream
матируемого
в
С++ обеспечивает
выполнение
форматируемого
и
нефор-
вывода. Его возможности включают в себя: вывод стандартных
типов данных при помощи операции передачи в поток; вывод символов при
функции-элемента put; неформатируемый вывод, осуществляемый
функцией-элементом write (раздел 21.5); вывод целых чисел в десятичном, во¬
сьмеричном и шестнадцатеричном форматах (раздел 21.6.1); вывод чисел с
плавающей точкой с различной точностью (раздел 21.6.2), с принудительной
помощи
Потоки
ввода/вывода
в С++
883
десятичной точкой (раздел 21.7.2),
нии и с
в экспонешщальном
фиксированноз£тточкой (раздел 21.7.6);
ем в полях
заданной щирины (раздел 21.7.3);
и
21.3.1.
представле¬
вывод данных с символами-за¬
(раздел 21.7.4) и использование символов
шестнадцатеричной записи (раздел 21.7.7).
полнителями
научной
(научном)
вывод данных с выравнивани¬
Операция передачи
верхнего
регистра
в
в поток
Вывод данных в поток выполняется при помощи операции передачи в по¬
ток, а именно, перегруженной операции левого сдвига «. Перегруженная
операция « может выводить данные предопределенных типов, строки симво¬
лов и значения указателей. В разделе 21.9 мы узнаем, как перегрузить опера¬
цию «, чтобы ее мождо было использовать с определяемыми пользователем
типами данных. На рис. 21.3 показан пример вывода строки символов.
//
//
fig21_03.cpp
Вывод строки
использованием
операции
передачи
в
поток,
<iostream.h>
#include
main()
{
cout
с
<<
"Welcome
to
C++!\n";
return 0;
}
wexcome
to
с++!
Вывод строки
Рис. 21.3.
с использованием операции передачи в поток
В указанном примере используется один оператор с операцией передачи в
поток для вывода строки. Можно написать несколько операторов вывода в по¬
ток, как на рис. 21.4. При выполнении эта программа выводит ту же самую
строку, что и предыдущая.
//
fig21_04.cpp
//
Вывод
строки
с использованием двух
операций передачи
в
поток,
<iostream.h>
#include
main()
{
cout
<<
"Welcome
cout
<<
"C++!\n";
return
to
";
0;
}
Welcome
to С++!
Рис. 21.4. Вывод строки с использованием двух
операций передачи
в поток
Глава 21
884
Тот же самый эффект, что дает esc-код
\n («новая строка»),
может быть
endl («конец строки»), как пока¬
endl выдает символ «новая строка» и,
потока
манипулятора
21.5. Манипулятор потока
кроме того, сбрасывает данные из буфера вывода
получен при помощи
зано на рис.
данные из
буфера,
даже если он не
(т.е. принудительно выводит
заполнен). Буфер вывода также может быть
очищен при помощи манипулятора
cout
<<
flush;
Манипуляторы
потоков
Можно выводить
подробно обсуждаются
в
разделе 21.6.
и выражения, как показано на рис.
21.6.
// fig21_05.cpp
// Использование манипулятора endl.
#include <iostream.h>
main()
{
cout
<<
"WelcOme
cout
«
"С++!";
cout
<<
endl;
to
";
потоковый
//
манипулятор
"конец
строки"
return 0;
Welcome to С++!
Рис. 21.5. Применение манипулятора endl
// fig21_06.cpp
// Вывод значения
выражения.
<iostream.h>
#include
main ()
{
cout
<<
"47
cout
<<
47
cout
<<
endl;
return
+
plus
53;
53
is
";
//
выражение
0;
}
47
plus
53
is
100
Рис. 21.6. Вывод значения выражения
Хороший
При
стиль программирования 21.2
выводе
выражений заключайте
ных со старшинством
21.3.2. Конкатенация
скобки, чтобы избежать конфликтов,
операций в выражении.
их в
<<
операции
и
операций
связан¬
помещения в поток
и извлечения из потока
Перегруженные операции
« и » могут использоваться в
тенации, как показано на рис.
21.7.
форме
конка¬
Потоки
ввода/вывода
в С++
//
fig21_07.cpp
//
Конкатенация
885
операций передачи
<iostream.h>
#include
в
поток,
+
53
main()
<<
cout
return
47
"47
53
is
"
<<
47
<<
endl;
0;
53
plus
plus
is
100
Рис. 21.7. Конкатенация
операций передачи
в поток
Операции передачи в поток из примера на рис. 21.7 выполняются таком
порядке, как если бы они были записаны следующим образом:
<<
( ( (cout
"47
plus 53
is
")
<<
47
+
<<
53)
в
endl);
Конкатенация возможна потому, что перегруженная операция « возвра¬
щает ссылку на свой левый операнд, т.е. cout. Таким образом, крайнее левое
скобках будет выполнено первым
выражение в
(cout
<<
и
plus 53 is ")
"47
выведет заданную строку символов
следующее по порядку вычисления
cout. В результате
скобках будет преобразовано
и возвратит ссылку на
выражение в
к виду
(cout
затем
будет
жение в
cout
и
в
<<
+
47
53)
выведено число 100 и возвращена ссылка на cout.
Последнее выра¬
скобках примет вид
<<
endl
результате его выполнения
строки, очищен
буфер
в поток вывода
будет
помещен символ новой
потока cout и возвращена ссылка на cout.
ссылка на cout использована не
Последняя
будет.
21.3.3. Вывод переменных
типа
char
*
При вводе/выводе в С необходимо было сообщать соответствующую ин¬
формацию о типе данных. В С++ тип данных определяется автоматически, что
является очень удобным нововведением. Но иногда этот автоматизм «встает
поперек дороги». Рассмотрим пример. Как мы знаем, символьная строка име¬
ет тип char *.
Предположим,
что мы хотим вывести значение такого указате¬
ля, т.е. адрес памяти первого символа строки.
выводит данные типа
char
лом. Решение состоит
*
как
в том,
нужно поступать с указателем
Но перегруженная операция
«
строку символов с завершающим нуль-симво¬
чтобы привести указатель
любого типа, когда нужно
к типу
void
*
(и
так
вывести адрес, на ко¬
указатель). На рис. 21.8 переменная типа char * выводится в
виде адреса и строки символов, расположенной по этому адресу. Обратите вни¬
мание, что адрес выводится в виде шестнадцатеричного (по основанию 16) чис¬
ла. Шестнадцатеричные числа в С++ начинаются с символов 0x или 0X. Мы
торый
ссылается
поговорим подробнее
о различных системах представления чисел в разделах
21.6.1, 21.7.4, 21.7.5
и
21.7.7.
886
Глава 21
//
//
fig21_08.cpp
Вывод значения
//
на
тип
адреса,
содержащегося
в
переменной-указателе
char
<iostream.h>
#include
main()
{
char
*string
cout
<<
"Value
of
Value
of
"
of
"
<<
Value
"test";
<< string
string is:
"\nValue of (void *)string is:
(void *)string « endl;
<<
return
=
0;
string is: test
(void *)string is:
0x00aa
Рис. 21.8. Вывод значения адреса, содержащегося в
21.3.4.
Символ
переменной-указателе
на тип
char
Функция-элемент put
для вывода символов;
вызовов
конкатенация
put
можно вывести с помощью
функции-элемента put,
как в следую¬
щем примере,
cout.put(1А1);
в
котором на экран выводится символ А. Вызовы put могут конкатенировать-
cout.put('А') .put (1\n');
При
этом сначала выводится символ
Функции put
А,
символ
а за ним
ASCII-код символа, например, cout.put(65),
будет выведен символ А.
щееся как
также
новой строки.
можно передавать в качестве аргумента выражение, оцениваю¬
и в результате на экран
21.4. Потоковый ввод
Теперь давайте рассмотрим потоки ввода. Для получения данных из пото¬
ка используется'перегруженная операция правого сдвига », называемая опе¬
рацией извлечения (из потока). Эта операция по умолчанию пропускает в пото¬
ке ввода пробельные символы (такие, как пробел, символы табуляции и новой
строки). Позже
обработку этих символов.
(false), когда в потоке встре¬
чается признак конца файла. Каждый поток имеет набор битов состояния, с
помощью которых можно управлять поведением потока (т.е. форматировани¬
Операция
мы
узнаем,
как
можно
изменить
извлечения из потока возвращает нуль
ем, состояниями ошибок и т.д.). При вводе неправильного типа операция изв¬
лечения из потока устанавливает бит потока failbit, в случае завершения опе¬
рации с
ошибкой
бит badbit. Мы скоро узнаем, как
биты
состояния потока
можно опросить состоя¬
операций ввода/вывода. В
обсуждаются подробно.
ние потока после завершения
разделах 21.7
и
21.8
Потоки
ввода/вывода
887
в С++
21.4.1.
Операция
извлечения из потока
Если вам нужно ввести два целых числа, используйте объект-поток cin и
перегруженную операцию » извлечения из потока, как показано на рис. 21.9.
// fig21_09.cpp
// Вычисление суммы двух чисел,
// введенных с клавиатуры и полученных из потока
// с помощью операции извлечения из потока.
<iostream.h>
#include
main ()
{
int
x,
у;
<<
cout
>>
cin
cout
cm
"Enter
>>
x
<<
"Sum
<<
x
return
two
integers:
";
у;
+
"
of
<<
у
<<
<<
x
"
"
<<
and
у
"
<<
"
is:
endl;
0;
}
Enter two integers: 30 92
Sum of 30 and 92 is: 122
Рис. 21.9. Вычисление суммы двух чисел, введенных
с клавиатуры и полученных из потока cin
с помощью операции извлечения
Относительно высокий приоритет операций
некоторые
проблемы. Например,
»
и
«
если в программе на рис.
может
21.10
порождать
вы не заклю¬
круглые скобки условное выражение, то программа будет компилиро¬
вана неверно. Читателю стоит проверить это утверждение.
чите в
I
// fig21_10.cpp
// Пример конфликта, связанного с приоритетом операций.
// Условное выражение необходимо заключить в скобки.
<iostream.h>
#include
main
()
{
int x,
у;
<<
cout
»
cin
cout
"Enter
>>
x
<<
x
<<
"
return
two
integers:
";
у;
(x
==
equal
to
<<
у
?
"
is"
"
<<
у
<<
:
"
is
not")
endl;
0;
}
Enter
7
is
Enter
two
not
integers:
equal to 5
two
8 is equal
integers:
to
7
5
8 8
8
Рис. 21.10. Как избежать
конфликта старшинства операций между операцией передачи
поток и условным выражением
в
Глава 21
888
Распространенная ошибка программирования 21.1
Попытка чтения данных из потока ostream (или любого другого потока вывода).
Распространенная ошибка программирования 21.2
Попытка записи данных в поток istream (или любой
другой
поток
ввода)
Распространенная ошибка программирования 21.3
В операторе ввода/вывода
опускают скобки, и в результате из-за относите¬
операции выполняются в неправильном порядке.
из потока
льно высокого приоритета <<
и >>
Один из популярных способов ввода ряда значений
рации извлечения из потока в заголовке цикла
возвращает значение false
(нуль),
while,
это размещение опе¬
т.к. операция извлечения
когда в потоке встречается признак конца
фай¬
программу на рис 21.11, которая определяет максимальное зна¬
чение среди введенных чисел. Имейте в виду, что число вводимых данных не из¬
вестно заранее и, чтобы обозначить конец ввода, пользователь должен ввести
ла.
Рассмотрим
файла. Условие цикла while (cin » grade) ста¬
(принимает значение false), когда пользователь вводит ко¬
комбинацию клавиш для конца
новится равным нулю
нец
файла.
// fig21_ll.cpp
// Операция извлечения
//
значение
false
при
из
потока
вводе
возвращает
конца
файла.
#include <iostream.h>
main ()
{
int grade,
highestGrade
=
-1;
grade (enter end-of-file
grade) {
if (grade > highestGrade)
highestGrade
grade;
cout
<<
while
"Enter
(cin
to
end):
";
>>
=
<<
cout
"Enter
grade
(enter end-of-file
to
end):
";
}
cout
"\nHighest grade is:
<<
<<
return
"
<<
highestGrade
endl;
0;
}
Enter
Enter
Enter
Enter
Enter
Enter
Enter
grade
grade
grade
grade
grade
grade
grade
(enter
(enter
(enter
(enter
(enter
(enter
(enter
Highest grade is:
end-of-file
end-of-file
end-of-file
end-of-file
end-of-file
end-of-file
end-of-file
to
to
to
to
to
to
to
end):
end):
end):
end):
end):
end):
end):
67
87
73
95
34
99
AZ
99
Рис. 21.11. Операция извлечения
из потока возвращает значение
конца файла
false при вводе признака
Потоки
ввода/вывода
в С++
21.4.2.
889
Функции-элементы get
и
getline
Функция-элемент get без аргументов вводит один символ из указанного
(даже если этот символ
пробельный) и возвращает его в каче¬
потока ввода
стве своего значения.
Эта версия функции get возвращает макрос EOF, когда
в
встречается конец файла. Макрос EOF обычно имеет значение -1,
значение можно было отличить от ASCll-символа (значение EOF мо¬
потоке ввода
чтобы
это
системах).
На рис. 21.12 показан пример использования функций-элементов eof и
get объекта-потока cin и функции-элемента put объекта-потока cout. Про¬
грамма сначала выводит значение вызова cin.eof(), т.е. 0 (false) для того, что¬
бы показать, что конец файла в cin еще не встретился. Затем пользователь вво¬
дит строку текста, завершая ее концом файла (<ctrl>-z на IBM PC и 'овместимых cncTeMax,<return>, <ctrl>-d для UNIX и Macintosh). Программа читает
каждый символ и выводит его в поток cout, вызывая функцию-элемент put.
При вводе конца файла цикл while завершается и возвращаемое значение
жет различаться в разных
а теперь это 1 (true)
завершает вывод программы, демонстрируя
тем самым, что для cin установлен признак конца файла. Обратите внимание,
cin.eof()
что в этой программе используется версия
am, которая
функции-элемента get класса istre¬
введенный символ.
не принимает аргументов и возвращает
// fig21_12.cpp
// Использование функций-элементов get,
put
и
eof.
<iostream.h>
#include
main ()
{
int
с;
cout
<<
"Before
<<
cin.eof()
«
"Enter
while
cin.eof()
input,
а
<<
'\n'
sentence
=
«
<<
followed
by end-of-file:\n";
!= EOF)
((c
cin.get())
cout.put(с);
cout
"
is
"\nAfter input, cin.eof()
cin.eof() << endl;
is
"
return 0;
}
Before input, cin.eof() is 0
Enter a sentence followed by end-of-file:
Testing the get and put member functions^Z
Testing the get and put member functions
After input, cin.eof() is 1
Рис. 21.12. Использование функций-элементов get, put
Функция-элемент get
да
следующий
символ
eof
и
с символьным аргументом извлекает из потока вво¬
(включая
и
пробельные символы). Эта
get возвращает значение false при появлении конца файла;
версия функции
в
противйом слу¬
890
Глава 21
чае эта версия возвращает ссылку на объект класса istream, для которого фун¬
get вызывалась.
Третья версия функции-элемента
кция
массив, размер
версия
get имеет три аргумента
массива и символ-ограничитель
(по
символьный
умолчанию это
считывает символы из входного потока следующим
образом:
'\n).
Эта
вводится
на один символ меньше, чем
указанный во втором аргументе размер массива,
после чего ввод прекращается, либо ввод прекращается досрочно, как только
встретится символ-ограничитель. В конец строки, считанной в символьный
массив, дописывается нуль-символ. Символ-ограничитель не записывается в
символьный массив, а остается в потоке ввода. На рис. 21.13 приводится при¬
мер, сравнивающий ввод
потока и
// fig21_13.cpp
// Сравнение ввода
H
из потока
cin при помощи операции
извлечения из
функции cin.get.
строки
символов
с
использованием
// cin и cin.get.
#include <iostream.h>
const
int
SIZE
=
80;
main ()
{
char bufferl[SIZE],
buffer2[SIZE];
cout << "Enter а sentence:\n";
cin >> bufferl;
cout << "\nThe string read with cin was:\n"
<< bufferl << "\n\n";
cin.get(buffer2, SIZE);
cout << "The string read with cin.get was:\n"
<< buffer2
<< endl;
return
0;
}
Enter
а
sentence:
Contrasting string input with cin and cin.get
The string read with cin
Contrasting
was:
The string read with cin.get was:
string input with cin and cin.get
Рис. 21.13. Ввод строки
символов из потока cin при помощи операции извлечения из потока и
функции cin.get
Функция-элемент getline работает подобно третьей версии функции-эле¬
get, также добавляя нуль-символ в конец считанной в символьный мас¬
мента
сив строки.
Функция getline удаляет символ-ограничитель из потока, но не
Программа на рис. 21.14 демонстрирует использова¬
сохраняет его в массиве.
ние
функции-элемента getline при
вводе строки текста.
Потоки
ввода/вывода
в С++
// fig21_14.cpp
// Ввод символов
891
помощи
при
функции-элемента getline.
#include <iostream.h>
const SIZE
=
80;
main ()
{
char buffer[SIZE];
cout
<<
"Enter
а
sentence:\n";
SIZE);
cin.getline(buffer,
cout
«
"\nThe
sentence
<<
buffer
«
return
Enter
a
entered
is:\n"
endl;
0;
sentence:
Using the getline member function
The sentence entered is:
Using the getline member function
Рис. 21.14. Ввод
21.4.3.
символов при помощи
функции-элемента getline
Другие функции-элементы класса istream
(peek, putback, ignore)
Функция-элемент ignore пропускает указанное число символов (по умол¬
один символ) или завершается, если встретит указанный сим¬
вол-ограничитель (по умолчанию им является макрос EOF).
Функция-элемент putback возвращает последний символ, полученный
функцией get из потока ввода, обратно в поток. Эта функция удобна для испо¬
чанию
льзования в прикладных программах, которые просматривают
входной
поток
фрагмента, начинающегося со специфического символа. Когда та¬
кой символ обнаруживается, приложение возвращает его обратно в поток, что¬
в поисках
бы нужный фрагмент данных затем был считан целиком.
Функция-элемент peek возвращает следующий символ из потока ввода, но
не удаляет его из потока.
21.4.4. Безопасный
С++ реализует безопасный
» перегружены для
работы
со
по
по
типу
ввод/вывод
данных
типу ввод/вывод данных. Операции
специфическими
типами данных.
ботке
« и
При обра¬
недопустимого типа устанавливаются различные флаги ошибок, кото¬
рые пользователь может проверить, чтобы определить, завершилась ли опера¬
ция ввода/вывода успешно или неудачно. Таким образом программа «держит
все под контролем». Мы
обсудим
эти
флаги ошибок
в
разделе 21.8.
892
Глава 21
21.5.
Функции неформатируемого ввода/вывода
read, gcount и write
Неформатируемый ввод/вывод выполняется функциями-элементами read
write. Каждая
из этих функций вводит или выводит некоторое число байт в
массив или из массива в памяти. Эти блоки байт никак не форматируются.
и
Они просто
полнения
вводятся или выводятся в сыром виде.
Например,
в результате вы¬
операторов
char buffer[]
=
"HAPPY
cout.wnte(buffer,
BIRTHDAY";
10);
будут выведены на экран. Так как символьная
строка представляется адресом ее первого символа, то в результате следующего
вызова
первые 10 байт массива buffer
cout.write(
"ABCDEFGHI
JKLMNOPQRSTUVWXYZ",
10);
будут выведены первые 10 символов алфавита.
Функция-элемент read вводит в символьный массив определенное количе¬
ство символов. Если прочитано меньшее количество символов, чем требова¬
лось, устанавливается бит failbit. Мы скоро узнаем, как определить состояние
этого бита (см. раздел 21.8).
В
тов
программе на рис. 21.15 показан пример применения
read
и
gcount
Программа
массив
класса
istream
вводит 20 символов
buffer,
и
функций-элеменфункции-элемента write класса ostream.
(из более длинной строки ввода) в символьный
read, определяет количество реально вве¬
используя функцию
денных символов при помощи
функции gcount
и выводит символы из
buffer
функцией write.
// fig21_15.cpp
// Неформатируемый ввод/вывод
//
функций-элементов read,
mam
использованием
и
write.
<iostream.h>
#include
const
с
gcount
int
SIZE
=
80;
()
{
char buffer[SIZE];
cout
<<
"Enter
а
cin.read(buffer,
cout
<<
"\nThe
sentence:\n";
20);
sentence
cout.write(buffer,
cout « endl;
return
entered
was:\n";
cin.gcount());
0;
}
Enter
a
sentence:
Using the read, write, and gcount member functions
The sentence entered
Using the read, writ
was:
Рис. 21.15. Нгформатируемый ввод/вывод с использованием функций-элементов
read, gcount и write
Потоки
ввода/вывода
в С++
21.6.
В С++
893
Манипуляторы
имеются различные
форматирование
манипуляторы потоков, которые
Манипуляторы
данных.
потоков
выполняют
потоков выполняют следующие за¬
дачи: установка ширины поля, задание точности представления, установка и
сброс флагов форматирования,
задание символа-заполнителя полей, очистка
вставка в поток вывода символа новой строки и очистка потока,
вставка нуль-символа в поток вывода и пропуск пробельных символов во вход¬
потоков,
ном потоке.
21.6.1.
Эти
моменты описываются в следующих разделах.
Манипуляторы установки
dec, oct, hex
основания целых чисел:
setbase
и
обычно представляются в десятичной системе счисления (по
10). Для того, чтобы изменить основание системы счисления, в ко¬
торой целые числа будут интерпретироваться в потоке, используют манипуля¬
для шестнадцатеричной системы (по основанию 16), манипулятор
тор hex
oct
для восьмеричной системы (по основанию 8). Манипулятор потока dec
Целые
числа
основанию
возвращает поток к десятичной системе счисления.
Основание системы счисления в потоке также может
быть изменено мани¬
setbase, который имеет один целочисленный аргумент, при¬
нимающий значения 10, 8 или 16 для соответствующей системы счисления.
Поскольку манипулятор setbase имеет аргумент, он называется параметризо¬
ванным манипулятором потока. При использовании setbase или любого дру¬
гого параметризованного манипулятора необходимо включить в программу
файл заголовка iomanip.h. Установленное в потоке основание остается в силе,
пока не будет явно изменено. В программе на рис. 21.16 показано применение
манипуляторов hex, oct, dec и setbase.
пулятором потока
I
// fig21_16.cpp
// Использование
потоковых
#include
<iostream.h>
#include
<iomanip.h>
манипуляторов
hex,
oct,
dec
и
setbase
main ()
{
int
cout
n;
<<
"Enter
а
decimal
number:
";
cin>>n;
cout
n
«
hex
«
n
«
<<
dec
<<
n
«
«
oct
«
n
«
«
<<
return
"
<<
<<
in
hexadecimal
setbase(10)
<< endl;
"
is:
'\n'
"
in
octal
is:
'\n'
<<
n
«
"
in
decimal
is:
"
n
0;
}
Рис. 21.16. Применение потоковых манипуляторов hex, oct, dec
и
setbase (часть 1
из
2)
894
Глава 21
а
IEnter
decimal
20
in
20
in
octal
20
in
decimal
Рис. 21.16.
number:
hexadecimal
is:
20
14
24
is:
is:
Применение
20
потоковых манипуляторов
hex, oct, dec
и
setbase (часть 2
21.6.2. Установка точности представления чисел с
из
2)
плавающей
точкой (precision, setprecision)
Мы можем управлять точностью представления чисел
кой,
а именно
пулятор потока
вызовов
пока не
с плавающей точ¬
цифр справа от десятичной точки1, используя мани¬
setprecision или функцию-элемент precision. Любой из этих
числом
устанавливает
будет
сделан
точность
следующий
для
последующих операций вывода,
Функция-элемент precision без аргу¬
всех
вызов.
мента возвращает текущую установку точности. В программе на рис. 21.17 ис¬
и функция-элемент precision и манипулятор setpre¬
пользуются оба способа
cision
для вывода
няющейся
таблицы квадратного корня
двойки
из
с точностью, изме¬
0 до 9. Обратите внимание, что точность 0 имеет специфическое
Это значение аргумента восстанавливает заданную по умолчанию
от
назначение.
точность, равную
6.
21.6.3.
Ширина
поля
(setw, width)
Функция-элемент width устанавливает ширину поля и возвращает преды¬
дущее значение этой величины. В случае, если обрабатываемые значения за¬
нимают места меньше, чем ширина поля, в оставшихся позициях выводится
символ-заполнитель.
Если значение требует для своего размещения места бо¬
усечение не производится, значение
выводится полностью. Установленное значение ширины применяется только
для последующей операции передачи или извлечения, после чего ширина
льше,
чем
отведенная ширина поля,
поля устанавливается равной 0, т.е. выводимые значения
ширины,
необходимой
для вывода данного значения.
будут занимать поля
Функция width без аргу¬
мента возвращает текущее значение ширины поля.
Распространенная ошибка программирования 21.4
Если установленное значение ширины поля меньше, чем необходимо для вывода,
выводимая величина будет помещена в поле необходимой ширины, но при этом ре¬
зультат
вывода
будет неудобочитаем
или
будет восприниматься
неверно.
На рис. 21.18 показано использование функции-элемента width при вводе
Обратите внимание, что при вводе максимально считывается на
оставляется место для нуль-симво¬
один символ меньше, чем ширина поля,
и
выводе.
ла,
добавляемого
в конец
введенной строки. Помните,
ния из потока завершается при появлении в потоке
он не
ведущий). Для
что операция извлече¬
пробельного
символа
(если
задания ширины поля можно также использовать мани¬
пулятор потока setw.
1
Если установлен флаг форматирования ios::scientific
флагов
не устанрвлен,
на самом деле
«точность» задает
будет выводить
рисунке.
Прим. ред.
общее
или
ios::fixed. Если
число значащих
ни один
цифр. Пример
из этих
из рис. 21.17
для каждого числа на одну цифру меньше, чем показано на
Потоки
ввода/вывода
895
в С++
fig21_17.cpp
// Управление точностью
//
представления
чисел
с
плавающей
точкой
<iostream.h>
#include
<iomanip.h>
#include <math.h>
#include
main ()
{
double root2
cout
"Square root of 2 with precisions 0-9.\n"
"Precision set by the
"precision member function:\n";
<<
"
<<
<<
for
sqrt(2.0);
=
0; places
cout.precision(places);
cout << root2 << endl;
(int
places
=
<=
9;
places++)
{
}
cout
"\nPrecision
<<
for
(places
cout
return
"
by the
"setprecision manipulator:\n";
<<
<<
=
0;
set
places
<=
9;
places++)
setprecision(places)
<<
root2
<<
endl;
0;
Square root of 2 with precisions 0-9.
Precision set by the precision member function:
1.414214
1.4
1.41
1.414
1.4142
1.41421
1.414214
1.4142136
1.41421356
1.414213562
Precision set by the setprecision manipulator:
1.414214
1.4
1.41
1.414
1.4142
1.41421
1.414214
1.4142136
1.41421356
1.414213562
Рис. 21.17. Управление точностью представления чисел
с
плавающей точкой
896
Глава 21
//
//
fig21_18.cpp
Использование
#include
функции-элемента
<
iostream.
=
4;
width
h>
main()
{
int
w
char
string[10];
cout
<<
"Enter
а
sentence:\n";
cin.width(5);
while
(cin >> string) {
cout.width(w++);
cout << string <<
'\n';
cin.width(5);
}
return
а
Enter
This
0;
sentence:
is
а
test
of
the
width
member
function^Z
This
is
а
test
of
the
widt
h
memb
er
func
tion
Рис. 21.18. Использование
21.6.4.
Определяемые
функции-элемента width
пользователем
манипуляторы
Пользователи могут создавать свои собственные манипуляторы потока. На
рис. 21.19 показано создание и проверка новых манипуляторов потока bell
(звонок), ret (возврат каретки), tab и endLine. Пользователи могут создавать
собственные параметризованные манипуляторы потока. О том, как это сде¬
лать, смотрите в руководствах по вашему компилятору.
21.7. Флаги
Потоки С++
форматирования
потока
набор различных флагов форматирования, которые
преобразования данных, выполняемые при операциях потокового
ввода/вывода. Для работы с флагами используются функции-элементы setf,
unsetf и flags.
определяют
имеют
Потоки
//
//
//
ввода/вывода
в С++
897
fig21_19.cpp
Пример создания и использования определенных
непараметризованных манипуляторов
пользователем
#include <iostream.h>
// манипулятор bell (используется escape-код \a)
ostream& bell(ostream& output)
{
return output <<
'\a';
}
// манипулятор ret (используется escape-код \r)
ostream& ret(ostream& output)
{
return output <<
'\r';
}
// манипулятор tab
ostream&
(используется escape-код \t)
output)
tab(ostream&
{
return output
<<
'\t';
}
// манипулятор endLine (используется escape-код
// и функция-элемент flush)
\n
ostream& endLine(ostream& output)
{
return output << '\n' << flush;
}
main()
{
cout
"Testing the tab manipulator:\n"
<<
<<
'a'
<<
"Testing the
tab
"
<<
for
<<
=
<<
return
'b'
ret
<<
and
tab
<<
bell
'c'
<<
endLine
manipulators:\n"
";
(int i
1; i
cout << bell;
cout
<<
<<
ret
<=
"
100;
"
<<
i++)
endLine;
0;
Testing the tab manipulator:
a
b
Testing the
c
ret
Рис. 21.19. Создание
29 Зак.801
и
and bell
manipulators:
проверка определенных пользователем непараметризованных
манипуляторов
Глава 21
898
21.7.1. Флаги
Флаги форматирования,
ios
форматирования
показанные на рис.
как константы перечислимого типа и
скольких
21.20, определены
будут обсуждаться
в
классе
в следующих не-
разделах.
ios: :skipws
ios
:
ios :
: left
:right
ios : :internal
ios : : dec
ios : : oct
ios: : hex
ios : :showbase
ios :
:showpoint
ios : :uppercase
ios :
:showpos
ios : :scientific
ios : :fixed
ios :
:unitbuf
ios : :stdio
Рис.
21.20. Флаги форматирования
Этими
setf
и
торы
флагами можно управлять с помощью функций-элементов flags,
unsetf, но многие программисты предпочитают использовать манипуля¬
потока (см. раздел 10.7.8). Программист может использовать операцию
|,
поразрядного
ИЛИ
long (см.
10.23). При
рис.
тов устанавливаются
вращается значение,
мое значение
чтобы объединить различные опции
в одном числе типа
функции-элемента flags с таким набором би¬
новые опции форматирования для данного потока и воз¬
содержащее предыдущее состояние флагов. Возвращае¬
вызове
обычно сохраняется для того, чтобы
потом вызвать
flags
с этим
значением и восстановить предыдущие опции потока.
Функция flags устанавливает значения всех флагов. Функция setf с од¬
другой стороны, специфицирует один или большее количе¬
ство флагов, объединяемых операцией ИЛИ с существующими значениями
флагов, формируя новое состояние форматирования.
Параметризованный манипулятор потока setiosflags выполняет те же са¬
мые функции, что и функция-элемент setf. Потоковый манипулятор resetiosflags выполняет те же самые функции, что и функция-элемент unsetf. Для
того, чтобы использовать любой из этих потоковых манипуляторов, не забудь¬
ним аргументом, с
#include <iomanip.h>.
Установленный флаг skipws указывает, что операция » должна пропус¬
кать пробельные символы в потоке ввода. Этот режим является режимом по
изменить
Чтобы
умолчанию.
установку,
используйте вызов unэту
те включить в программу строку
setf(ios::skipws). Для
задания режима пропуска
может использоваться манипулятор потока ws.
пробельных
символов также
Потоки
ввода/вывода
в С++
899
21.7.2. Конечные нули
При
установленном флаге
и десятичные точки
showpoint
числа с
(ios::showpoint)
плавающей точкой
ся с десятичной точкой и конечными нулями. Число 79.0
будет
флаг showpoint сброшен, и как 79.000000 (или
нулей, соответствующим установленной точности),
выводят¬
выведено в
виде 79, если
с количеством
конечных
когда он уста¬
новлен.
функции-элемента setf
флага showpoint, контролирующего вывод нулей в младших
десятичной точки у вещественных чисел.
В программе на рис. 21.21 показано использование
для установки
разрядах
и
fig21_21.cpp
// Управление выводом
//
//
и
десятичной
//
у
чисел
с
нулей
в
младших
разрядах
точки
плавающей
точкой
<iostream.h>
#include
<iomanip.h>
#include <math.h>
#include
main ()
{
<<
cout
"cout
prints 9.9900
as:
9.9000
"
<<
9.9900
"
<<
9.9000
"\ncout prints
"
<<
9.0000
"\ncout prints 9.0000 as:
"\n\nAfter setting the ios::showpoint
<<
<<
<<
as:
flag\n";
cout.setf(ios::showpoint);
"cout prints 9.9900 as: "
"\ncout prints 9.9000 as:
"\ncout prints 9.0000 as:
cout <<
<<
<<
return
<<
"
"
9.9900
<<
9.9000
<<
9.0000
<<
endl;
0;
}
cout prints
cout
cout
After
cout
cout
cout
9.9900
as:
9.99
prints 9.9000
prints 9.0000
as:
9.9
as:
9
setting the ios::showpoint flag
prints 9.9900 as: 9.990000
prints 9.9000 as: 9.900000
prints 9.0000 as: 9.000000
Рис. 21.21. Управление выводом нулей в младших разрядах и десятичной точки для чисел
с плавающей точкой
21.7.3.
Флаги left
Выравнивание (ios::left, ios::right, ios::internal)
и
right
позволяют выводить данные с левым выравниванием в
поле и заполняющими символами в
ниванием
и
заполняющими
правой
символами
части поля, или с правым вырав¬
слева соответственно.
Символ,
кото¬
рый нужно использовать для заполнения, определяется функцией-элементом
fill или параметризованным манипулятором потока setfill (см. раздел 21.7.4).
29*
900
Глава 21
На рис. 21.22 показан пример использования манипуляторов setw,
и
resetiosflags
и
функций-элементов setf
и
setiosflags
unsetf для управления
левым
и
правым выравниванием целочисленных данных.
fig21_22.cpp
// Применение правого
//
#include
<iostream.h>
#include
<iomanip.h>
main
(
и левого
выравнивания.
)
{
x
int
cout
12345;
=
<<
"Default
is
right justified:\n"
<<
setw(10)
«
x
«
"\n\nUSING
<<
"Use
setf
MEMBER
to
set
FUNCTIONS\n"
ios::left:\n"
<<
setw(10);
cout.setf(ios::left, ios::adjustfield);
cout << x << "\nUse unsetf to restore default:\n";
cout.unsetf (ios::left);
cout « setw(10) << x
<<
"\n\nUSING PARAMETERIZED STREAM MANIPULATORS"
<<
"\nUse
<<
setw(10)
setiosflags to set ios::left:\n"
<< x
<< setiosflags(ios::left)
"\nUse resetiosflags to restore default:\n"
setw(10) << resetiosflags(ios::left)
<< x << endl;
return 0;
<<
<<
}
Default is right justified:
12345
USING MEMBER FUNCTIONS
Use setf to set ios::left:
12345
Use unsetf to restore default:
12345
USING PARAMETERIZED STREAM MANIPULATORS
Use setiosflags to set ios::left:
12345
Use resetiosflags to restore default:
12345
Рис. 21.22. Применение правого
и левого
Флаг internal указывает, что знак числа
флаг ios::showbase установлен;
(или
выравнивания
основание системы счисле¬
21.7.5) должен выравни¬
ваться по левому краю поля, значение числа должно быть выровнено по право¬
му краю, а в оставшееся пустое место выводятся символы-заполнители. Флаги
ния, если
left, right
eld. При
нужно
и
internal содержатся
установке флагов left,
в
качестве
второго
см. раздел
в статическом элементе данных
ios::adjustfi-
internal при помощи функции setf ей
аргумента
ios::adjustfield. Это
передавать
right
и
Потоки
ввода/вывода
в С++
901
гарантирует, что только один из трех флагов выравнивания будет установлен
(поскольку онй взаимно исключают другдруга). На рис. 21.23 показан пример
использования в программе потоковых
установки выравнивания по ширине.
манипуляторов
Обратите
флага ios::showpos, устанавливающего принудительный
I
// fig21_23.cpp
// Вывод числа с выравниванием по
// и принудительным знаком "плюс".
#include
<iostream.h>
#include
<iomanip.h>
setiosflags
внимание
на
и
setw для
использование
вывод знака «плюс».
ширине
main()
{
cout
<<
<<
return
setiosflags(ios::internal
setw(10) << 123 << endl;'
|
ios::showpos)
0;
}
+
123
Рис. 21.23. Вывод числа с выравниванием по ширине и принудительным знаком «плюс»
21.7.4. Заполнение
Функция-элемент fill
выравнивании данных
в
(fill, setfill)
определяет символ-заполнитель, используемый при
поле; если не определено иначе, для заполнения
выводятся пробелы. Функция fill возвращает значение предшествующего за¬
полняющего символа. Задать символ-заполнитель можно, такл;е при помощи
манипулятора потока. Пример задания символа-заполнителя приведен на
рис.
тор
21.24,
где для этой цели применяется
функция-элемент fill
и манипуля¬
setfill.
21.7.5.
Флаги установки
основания целых чисел
(ios::dec, ios::oct, ios::hex, ios::showbase)
Статический элемент данных ios::basefield (аналогцчный используемому с
setf ios::adjustfield) включает в себя биты oct, hex и dec, определяющие, что
целые числа должны
обрабатываться
соотзетственно как, восьмеричные, шест¬
надцатеричные и десятичные значения. Значение по умолчанию для операции
десятичные числа, если ни один из этих битов не уста¬
помещения в поток
новлен; извлекаемые из потока данные по умолчанию
обрабатываются
соот¬
форме, в которой они вводятся: целые числа, начинающиеся с 0,
обрабатываются как восьмеричные, целые числа, начинающиеся с 0x или 0X,
трактуются как шестнадцатеричные значения, а все другие целые числа пред¬
ветственно
полагаются десятичными.
цифическое
Но
как только для потока определено какое-то спе¬
значение основания, все целые числа в этом потоке считаются за¬
писанными в
установленной
системе счисления, пока не
вое зйачение основания, или до конца программы.
будет определено
но¬
Глава 21
902
//
//
//
fig21_24.cpp
Использование функции-элемента
для замены символа заполнителя
//
полей,
больших
по
чем
ширине,
#include
<iostream.h>
#include
<iomanip.h>
fill
и
манипулятора
setfill
значения.
выводимые
main()
{
int
cout
=
x
<<
<<
<<
10000;
x
"
printed
"and
as
int
as
hex
with
right and left justified\n"
internal
justification.\n"
"Using the default pad character
cout.setf(ios::showbase);
<<
cout
<<
setw(10)
<<
x
<<
setw(10)
<<
x
<<
cout.setf (ios::internal,
cout
<<
setw(10)
<<
'\n';
ios::adjustfield);
cout.setf (ios::left,
cout
<<
(space):\n";
hex
'\n';
ios::adjustfield);
<<
x
«
"\n\n";
<< "Using various padding characters:\n";
cout.setf(ios::right, ios::adjustfield);
cout.fill('*');
cout
cout
<<
setw(10)
<<
dec
<<
x
<<
'\n';
cout.setf (ios::left,
ios::adjustfield);
<< x <<
<< setfill('%')
cout << setw(10)
cout.setf (ios::internal,
cout
<<
setw(10)
<<
x
return
<<
<<
'\n';
ios::adjustfield);
setfill('^')
<<
hex
endl;
0;
10000 printed as int right and left justified
and as hex with internal justification.
Using the default pad character (space):
10000
10000
2710
0x
Using various padding characters:
*****10000
10000%%%%%
0xAAAAA2710
Рис. 21.24. Использование функции-элемента fill
и манипулятора setfill для замены символа
заполннения полей, больших по ширине, чем выводимые значения
Установите флаг showbase для принудительного указания основания при
выводе целого числа. Десятичные числа при этом будут выводится как обыч¬
но, восьмеричные числа выводятся с ведущим 0, а при выводе шестнадцате¬
ричных чисел перед числом выводятся символы
0x
или
0X (регистр определя¬
флага uppercase). На рис. 21.25 показано использование флага
showbase при выводе целого числа в десятичном, восьмеричном и шестнадца¬
теричном форматах.
ется значением
Потоки
ввода/вывода
//
//
903
в С++
fig21_2 5.cpp
Применение флага
ios::showbase
#include <iostream.h>
#include <iomanip.h>
main ()
int
cout
100;
=
x
<<
setiosflags(ios::showbase)
"Printing integers preceded by their base:\n"
<<
<<
'\n'
«
x
<< oct
<<
X
<<
<<
<<
x
<<
return
hex
'\n'
endl;
0;
Printing integers preceded by their base:
100
0144
0x64
Рис. 21.25. Применение флага ios::showbase
21.7.6. Числа
плавающей точкой; научная
с
нотация
(ios::scientific, ios::fixed)
Флаги ios::scientific
и
ios::fixed содержатся
в статическом элементе дан¬
ios::floatfield, аналогичном статическим данным-элементам ios::adjustfield и ios::basefield, используемым функцией setf. Эти флаги управляют фор¬
матом вывода чисел с плавающей точкой. Если установлен флаг scientific, то
числа с плавающей точкой будут выводиться в научном формате. Флаг fixed
устанавливает для чисел с плавающей точкой формат с фиксированным чис¬
лом цифр после запятой (число цифр определяется функцией-элементом preci¬
sion). Если ни один из этих флагов не установлен, то выходной формат опреде¬
ных
ляется значением выводимого числа.
В результате вызова
ное
значение
рис. 21.26
по
для
вывода
показан пример вывода чисел с
ном и научном
21.7.7.
При
cout.setf(0, ios::floatfield)
умолчанию
восстанавливается систем¬
плавающей точкой. На
плавающей точкой в фиксирован¬
чисел
с
форматах.
Управление верхним/нижним регистрами
(ios::uppercase)
символы X и E, используемые в
форматах, выводятся в верхнем регистре
в этом случае все буквы, используемые в шестнадца¬
числа, будут выводиться в верхнем регистре.
установленном флаге
шестнадцатеричном
(рис. 21.27). Кроме
и
ios::uppercase
научном
того,
теричном представлении
Глава 21
904
// fig21_26.cpp
// Вывод 4Hcejj с плавающей точкой в формате
// научном и фиксированном форматах.
по
умолчанию,
#include <iostream.h>
main()
{
double
.001234567,
=
X
у
*
1.946e9;
<< "Di?played in default format:\n"
« x «. '\t' « у « '\n';
cout. setf (ios : : scientific, ios : : floatfield) ;.t
cout
cdut
<<
"Displayed in scientific format:\n"
«
« y «
'\t1
'\n';
cout.unsetf(ios::scientific);
cout << "Displayed in default format after unsetf:\n"
<< x «
<< y «
'\t'
'\n';
cout.setf(ios::fixed, ios::floatfield);
cout << "Displayed in fixed format:\n"
<< x <<
<< y << endl;
'\t'
«
x
return 0;
Displayed in default format:
0.001235
1.946e+09
Displayed in scientific format:
1.23567e-03
1.946e+09
Displayed in default format after unsetf:
0.001235
1.946e+09
Displayed in fixed format:
0.001235
1946000000
Рис. 21.26. Вывод чисел
и в
с плавающей точкой по умолчанию,
фиксированном форматах
в научном
// fig21_27.cpp
// Использование флага ios::uppercase
#include <iostreanuh>
#include <iomanip.h>
main ()
{
cout
<<
<<
<<
<<
<<
return
setiosflags(ios::yppercase)
"Printing uppercase letters in scientific\n"
"notation exponents and hexadecimal values:\n"
4.345el0 << '\n'
<< 123456789
hex
«
endl;
0;
Printing uppercase
notation exponents
4.345E+10
letters %n scientific
and hexadecimal values:
75BCDl5
Рис. 21.27. Использование флага
ios::uppercase
Потоки
ввода/вывода
в С++
905
21.7.8. Установка
сброс флагов форматирования
(flags, setiosflags, resetiosflags)
лое)
Функция-элемент flags без аргумента просто возвращает (как длинное це¬
текущее состояние флагов форматирования. Функция-элемент flags, вы¬
званная с аргументом типа
с
и
их
значениями
в
long,
аргументе,
устанавливает флаги формата в соответствии
возвращает предыдущее состояние флагов.
и
Флаги формата, не установленные
Программа
на рис.
для установки
параметров
в аргументе
функции flags, сбрасываются.
21.28 демонстрирует применение функции-элемента flags
новых параметров
форматирования
и
форматирования
последующим
с сохранением предыдущих
восстановлением
первоначаль¬
ных установок.
I
//
fig21_28.cpp
I // Демонстрация
#include
возможностей
функции-элемента
flags.
<iostream.h>
mam ()
{
i
mt
=
double d
cout
1000;
0.0947628;
=
<<
"The
<<
cout.flags()
value
<<
"Print
«
i
<<
of
<<
"The
flags variable is:
'\n'
int and double
1\t1
<<
value
<<
d
"
in original
format:\n"
"\n\n";
cout.flags(ios::oct | ios::scientific);
the flags variable is:
=
long originalFormat
cout
the
<<
"
of
<<
cout.flags()
<<
"Print
<<
"specified using the flags member function:\n"
<<
i
<<
int
'\t'
<<
and
<<
d
'\n'
double
<<
in
a
new
format\n"
"\n\n";
cout.flags(originalFormat);
cout << "The value of the flags variable is:
<< cout.flags()
<<
'\n'
<< "Print values in original format again:\n"
"
<<
return
i
<<
'\t'
<<
d
<<
endl;
0;
}
value of the flags variable is:
1
Print int and double in original forma_.
1000
0.094763
The
The value of the flags variable is: 4040
Print int and double in a new format
specified using the flags member function:
1750
9.47628e-02
The value of the flags variable is: 1
Print values in original format again:
1000
0.094763
Рис. 21.28. Демонстрация возможностей функции-элемента flags
Глава 21
906
В следующем операторе функция-элемент setf устанавливает флаги фор¬
матирования, указываемые в аргументе, и возвращает предыдущее состояние
всех флагов в формате длинного целого.
long previousFlagSettings
=
|
cout.setf(ios::showpoint
Если функция-элемент
ios::showpos);
long,
вызывается с двумя аргументами типа
как в
следующей строке,
cout.setf (ios::left,
ios::adjustfield);
очищаются
ios::adjustfield и затем устанавливается флаг
ios::left. Эта версия функции setf используется с битовыми полями ios::basefield (содержит флаги ios::dec, ios::oct и ios::hex), ios::floatfield (содержит
то
биты
сначала
ios::scientific и ios::fixed) и ios::adjustfield (содержит ios::left, ios::right и
ios::internal).
Функция-элемент unsetf сбрасывает указанные флаги и возвращает пре¬
дыдущее состояние всех флагов.
21.8. Состояния ошибки
потоков
Состояние потока может быть проверено при помощи битов, определен¬
ios
базовом классе классов istream, ostream и iostream, кото¬
ных в классе
рые мы используем для
Бит
состояния
ввода/вывода.
eofbit
автоматически
когда в потоке встречается конец
кцию-элемент
Следующий
для
устанавливается
файла. Программа
eof, чтобы определить, был
потока
ввода,
может использовать
ли достигнут конец
файла
фун¬
в потоке.
вызов
cin.eof()
true, если в cin встретился конец файла и false в противном случае.
Бит failbit устанавливается, когда в потоке происходит ошибка формати¬
возвращает
рования,
но символы при этом не теряются.
Функция-элемент fail
ошибку можно исправить.
Установленный бит badbit свидетельствует
связанная с
потерей
данных.
о том, что произошла
Функция-элемент bad
зошла ли в потоке такого типа
возвращают
ошибка,
может определить, прои¬
ошибка. Такая серьезная ошибка обычно неис¬
правима.
Бит goodbit устанавливается, если для потока
ilbit или badbit не установлен.
Функция-элемент good
может быть
потоковой операции; обычно такую
использована для определения результата
ни один из
битов eofbit, fa¬
возвращает true, если функции bad, fail
false. Операции ввода/вывода должны
выполняться
и
eof,
только
все
на
«хороших» потоках.
Функция-элемент rdstate возвращает
cout.rdstate() возвратит состояние потока
состояние потока.
Например,
вызов
cout, которое можно затем исследо¬
проверить флаги ios::eofbit, ios::badbit,
вать при помощи оператора switch и
ios::failbit и ios::goodbit. Но предпочтение
следует отдать
в
тестировании
функциям-элементам eof, bad, fail
и
good
состояния
потока
при использовании
функций не приходится иметь дело непосредственно с обработкой битов.
Функция-элемент clear обычно используется для восстановления «рабоче¬
этих
го»
состояния
потока,
да/вывода. Заданный
в результате вызова
после
чего
на
нем
можно
по умолчанию аргумент для
продолжать
clear
операции
ios::goodbit,
вво¬
так что
Потоки
ввода/вывода
907
в С++
cin.clear();
будут очищены биты потока cin
goodbit. Оператор
и установлен
cin.clear(ios::failbit)
вызовет установку failbit. Пользователю может понадобиться такая операция
при возникновении ошибок ввода из cin определяемых пользователем типов.
Имя clear (очистить) кажется в этом контексте неподходящим, но ошибки
здесь нет.
Программа на рис. 21.29 иллюстрирует
te, eof, fail, bad, good и clear.
вызовы
функций-элементов rdsta-
Функция-элемент operator! возвращает true, если установлен бит badbit,
failbit или установлены оба этих бита. Функция-элемент operator void * воз¬
вращает false, если установлен бит badbit, failbit или установлены оба этих
бита. Эти функции удобны при обработке файлов.
21.9.
Ввод/вывод
С++
определяемых
пользователем типов
позволяет вводить и выводить стандартные типы данных, используя
операцию извлечения из потока » и операцию « передачи в поток.
регруженные
операции
могут
обрабатывать
все
стандартные
типы
Эти
пе¬
данных,
включая адреса памяти и строки. Но программист может перегрузить опера¬
ции передачи в поток и извлечения из потока,
чтобы
иметь возможность вы¬
определяемых пользователем типов. На рис. 21.30 пока¬
зана перегрузка операций извлечения и передачи для обработки данных опре¬
полнять
ввод/вывод
деляемого пользователем класса
телефонных номеров PhoneNumber. Обрати¬
телефонов вводятся
те внимание, что эта программа предполагает, что номера
правильно. Мы оставляем проверку ошибок ввода для упражнения.
Определенная в программе на рис. 21.30 перегруженная операция извле¬
чения из потока используется для ввода телефонного номера в виде
555-1212
(800)
в
объекты
мера
типа
телефона
класса
PhoneNumber. Операция
извлечения считывает три части но¬
areaCode, exchange и line объекта
функции-операции и phone в функции
дефиса отбрасываются при вызове функ¬
и помещает их в элементы
PhoneNumber (с
именем num в
main). Скобки, пробелы и символы
ции-элемента ignore класса istream. Функция-операция возвращает ссылку
на
объект input класса istream. Поскольку операция возвращает ссылку на объ¬
ект-поток, операции извлечения объектов PhoneNumber могут конкатениро¬
ваться с операциями извлечения других объектов PhoneNumber и объектов
других
типов.
Например,
два объекта PhoneNumber можно ввести следующим
образом:
>>
cin
phonel
>>
phone2;
Операция передачи в поток имеет два аргумента: ссылку на
ссылку на определяемый пользователем тип (в данном случае
ber),
PhoneNum¬
ostream. В программе на рис. 21.30 перегру¬
поток выводит объекты типа PhoneNumher в
и возвращает ссылку на тип
женная операция помещения в
той
тип ostream и
же
самой форме,
в
какой
они вводились.
Операция
выводит части номера
телефона
как строки символов, потому что они хранятся в
помним,
что
функция-элемент getline
класса
строки нуль-символ после того, как завершает
формате строки (на¬
istream дописывает
ввод).
в
конец
Глава 21
908
//
//
fig21_29.cpp
Проверка состояния
'
потока.
<iostream.h>
#include
main ()
{
int
cout
x;
<<
"Before
<<
а
bad
mput operation:\n"
<< cin.rdstate()
"cin.rdstate():
<< cin.eof()
"\n
cin.eof():
"\n
cin.fail()
cin.fail():
<< cin.bad()
"\n
cin.bad():
<<
"\n
"
<<
"
<<
"
<<
<<
"
"
<<
cin.good()
cin.good():
"\n\nExpects an integer, but enter
<<
>>
cin
cout
а
character:
";
x;
<<
"\nAfter
а
bad
input
"
<<
<<
"cin.rdstate():
"
<<
"\n
cin.eof():
<<
"\n
<<
"\n
cin.fail():
cin.bad():
<<
"
"
<<
<<
"
cin.good():
"\n
<<
<<
operation:\n"
cin.rdstate()
cin.eof()
cin.fail()
cin.bad()
cin.good()
<<
"\n\n";
cin.clear();
cout
<<
<<
return
"After cin.clear()\n"
"cin.fail ():
cin.fail()
"
<<
<<
endl;
0;
Before
а bad input operation:
cin.rdstate(): 0
cin. eof () : 0
() :
cin.bad():
cin.good():
cin.fail
Expects
After
an
a bad
0
0
1
integer,
input
2
cin.rdstate():
cin. eof () :
0
cin.fail():
2
cin.bad():
0
cin.good():
0
but
enter
a
character:
A
operation:
After
cin.clear()
cin.fail (): 0
Рис. 21.29.
Проверка
состояния потока
Перегруженные функции-операции объявлены
как дружественные
функции. Эти функции
в
классе
PhoneNumber
должны быть дружественными,
чтобы иметь доступ к закрытым элементам класса.
Потоки
ввода/вывода
в С++
fig21_30.cpp
// Определяемые
909
//
пользователем
извлечения
операции
и
помещения
<iostream.h>
#include
PhoneNumber
{
friend ostream& operator<<(ostream&,
friend istream& operator>>(istream&,
private:
char areaCode[4];
char exchange[4J;
char line[5];
class
ostream& operator<<
{
output << "(" <<
<<
(ostream& output,
PhoneNumber&);
PhoneNumber&);
PhoneNumber& num)
"
num.areaCode
<<
")
"-"
<<
num.line;
num.exchange
<<
return output;
}
istream& operator>>(istream&
{
input,
input.ignore();
input.getline(num.areaCode,
input.ignore(2);
input.getline(num.exchange,
input.ignore();
input.getline(num.line, 5);
return
PhoneNumber& num)
//
пропуск
(
// пропуск
)
4);
и
пробела
4);
// пропуск
-
input;
}
main ()
{
PhoneNumber phone;
cout
<<
"Enter
«
cin
>>
cout
<<
"form
Enter
а
(123)
"
456-7890:\n";
phone number entered was:\n"
phone << endl;
"The
0;
phone nvunber
(800) 555-1212
The phone number
(800)
phone number in the
phone;
<<
return
а
in
entered
the
form
(123)
456-7890:
was:
555-1212
Рис 21.30. Определяемые
пользователем операции извлечения и передачи.
Глава 21
910
Общее методическое замечание 21.3
Для того, чтобы ввести в С++ возможности ввода/вывода новых, определяемых по¬
льзователем типов данных, не требуется вносить изменения в закрытые секции дан¬
ных классов ostream и istream.
Расширяемость
языка С+н
одна из его
наиболее
привлекательных черт.
21.10.
Привязка
потока вывода к
Интерактивные прикладные
для ввода данных
и
ostream
потоку ввода
программы обычно используют класс istream
для вывода. Часто пользователь вводит необхо¬
димые данные в ответ на приглашение программы.
глашения должен появляться перед
операцией
Очевидно,
что текст при¬
ввода данных, но так как опе¬
рации вывода буферизованы, то вывод появляется только тогда, когда буфер
заполнен, или когда буфер очищается явно программой, или сбрасывается ав¬
томатически по окончании ее
работы. В С++ имеется функция-элемент tie для
действий классов istream и ostream, вызов
синхронизации, т.е. «привязки»,
которой гарантирует, что вывод появится перед связанным с ним вводом. В
следующем примере вызова:
cin.tie(cout);
(ostream) привязывается к потоку cin (istream). Приведенный при¬
мер вызова функции привязки потоков избыточен, потому что С++ выполняет
эту операцию автоматически, обеспечивая стандартный интерфейс пользовате¬
поток cout
ля.
Однако
istream
и
пользователю может
потребоваться
ostream. Чтобы «отвязать» входной
связывание других пар потоков
поток
inputStream
от выходного
потока, используется вызов
inputStream.tie(0);
Резюме
Операции ввода/вывода
выполняются в соответствии с типом данных.
В С++ ввод/вывод данных производится
просто байтовая последовательность.
Работа
системных механизмов
байтов данных от
эффективность
В С++
и
устройств
потоками
байтов. Поток
это
ввода/вывода сводится к перемещению
обратно, обеспечивая при этом
в цамять и
надежность.
для ввода/вывода «высокого уровня» и
Низкоуровневый ввод/вывод обычно занимается пере¬
дачей некоторого числа байт от устройства в память или из памяти
устройству. Функции ввода/вывода высокого уровня оперируют байтами,
имеются
возможности
«низкого уровня».
сгруппированными в элементы данных вроде целых чисел, чисел с плава¬
ющей точкой, символов, строк и определяемых пользователем типов.
С++ имеет средства
неформатируемого
и
форматируемого ввода/выво¬
быстро, но обрабатыва¬
Неформатируемый ввод/вывод
ет сырые данные, которые неудобны для восприятия их человеком.
Форматируемый ввод/вывод обрабатывает данные в виде имеющих
смысл групп байтов, но требует больше времени для своего выполне¬
ния, что может негативно сказаться при обработке данных большого
да.
объема.
выполняется
Потоки
ввода/вывода
911
в С++
Большинство программ С++
включает
файл
заголовка
iostream.h,
ко¬
торый содержит основную информацию, необходимую для всех опера¬
ций потокового ввода/вывода.
В файле-заголовке
iomanip.h содержится информация,
ввода/вывода с параметризованными
необходимая
для форматируемого
рами
манипулято¬
потоков.
Заголовок fstream.h содержит данные, нужные для операций обработ¬
ки
файлов.
Заголовок strstream.h содержит информацию, необходимую для опера¬
ций форматирования в памяти.
Файл
заголовка
stdiostream.h необходим для программ,
в стиле С и потоки С++.
Класс istream поддерживает операции ввода
из
Класс ostream поддерживает операции вывода
Класс iostream поддерживает
операции
Оба
из
istream
базового
и класс
и
операции
в
поток.
потокового
так
ввода,
и
ostream выведены путем простого наследо¬
ios.
класса
Класс iostream выведен путем
am
как
потока.
вывода.
класса
вания
в которых со¬
ввод/вывод
вмещаются
сложного наследования из классов
istre¬
ostream.
Перегруженная операция левого сдвига («) обозначает вывод в поток
и называется операцией передачи в поток.
Перегруженная операция правого сдвига (») обозначает ввод из пото¬
ка и называется операцией извлечения из потока.
Объект cin
класса
ввода данных,
Объект cout
istream ассоциирован
обычно с
со
стандартным
устройством
клавиатурой.
класса ostream ассоциируется со стандартным
устройством
вывода, обычно это экран монитора.
Объект
устройством
буферизован; результат каждой операции
cerr класса ostream ассоциирован со стандартным
ошибки. Вывод
в
cerr
не
передачи в cerr появляется немедленно.
Манипулятор
сывает буфер
потока
endl передает
в поток символ
новой строки
и
сбра¬
вывода.
Компилятор С++
определяет типы данных при
вводе/выводе
автомати¬
чески.
По умолчанию адреса отображаются
Чтобы
вывести
адрес,
находящийся
char *, приведите указатель
к
Функция-элемент put выводит
вызовов функции put.
Ввод
в
типу
шестнадцатеричном формате.
в
переменной-указателе
типа
void *.
один символ. Возможна конкатенация
из потока выполняется при помощи операции » извлечения из
потока.
Эта операция
пробельные
символы.
автоматически
пропускает
во
входном
потоке
Глава 21
912
» возвращает
Операция
false,
когда в потоке встречается конец
фай¬
ла.
операциях извлечения из потока failbit устанавливается, когда
возникает ошибка во входных данных, а badbit устанавливается, если
возникает серьезная ошибка.
При
Ряд значений
ния
в
можно
заголовке
(нуль), когда
в
вводить
в
качестве
своего
потока,
поместив
операцию
извлечения
извлече¬
возвращает
false
встречается конец файла.
потоке
Функция-элемент get без
его
из
while. Операция
цикла
аргументов вводит один символ
значения;
при
вводе
и возвращает
файла функция get
конца
возвращает EOF.
Функция-элемент get
потока
появлении
на
ссылку
с
символьным
конца
файла;
объект
класса
в
противном
istream,
Версия функции-элемента get
ный массив, размер
символ новой
аргументом
извлекает
Эта версия функции get возвращает
ввода.
с
случае
символ
значение
эта
версия
из
false при
возвращает
для которого она вызывалась.
тремя
получает
аргументами
символь¬
умолчанию это
из
считывает
символы
входного пото¬
версия
массива и символ-ограничитель
строки). Эта
образом: вводится
ка следующим
(по
на один символ меньше, чем указан¬
ный во втором аргументе размер массива, после чего ввод прекращается,
либо ввод прекращается раньше, если встретится символ-ограничитель.
В конец строки, вводимой в символьный массив, дописывается
нуль-символ.
в
остается
Ограничитель
потоке
не
записывается
в
символьный массив,
а
ввода.
Функция-элемент getline работает подобно третьей версии
ции-элемента get, но удаляет символ-разделитель из потока.
функ¬
Функция-элемент ignore пропускает определенное количество симво¬
один символ) во входном потоке; выполнение за¬
(по умолчанию
вершается досрочно, если встречается символ-ограничитель (по умолча¬
нию
EOF).
лов
Функция-элемент putback возвращает обратно
символ, полученный из потока функцией get.
Функция-элемент peek
ка,
но
не
удаляет
его
извлекает
из
личные
«
и
»
по типу
недопустимого
флаги ошибок, которые
определить,
поток
предыдущий
символ из входного пото¬
потока.
В С ++ реализован безопасный
рациями
следующий
в
ввод/вывод. При обработке
типа
данных
устанавливаются
опе¬
раз¬
пользователь может проверить, чтобы
завершилась ли операция
ввода/вывода
успешно или неу¬
дачно.
Неформатируемый ввод/вывод выполняется функциями-элементами
read и write. Каждая из этих функций вводит или выводит некоторое
число байт в память или из памяти, используя для этого указанный ад¬
Эти функции вводят/выводят сырые
форматируются.
рес.
данные,
которые никак не
возвращает число символов, введенных из по¬
тока предыдущим вызовом read.
Функция-элемент gcount
Потоки
ввода/вывода
в С++
913
Функция-элемент read
вводит заданное число символов в символьный
массив. Если было введено меньшее количество символов, устанавлива¬
ется бит failbit.
Для
того, чтобы изменить основание системы счисления, в
которой це¬
будут помещаться в поток, применяется манипулятор hex
для шестнадцатеричной системы (по основанию 16) и манипулятор
oct
для восьмеричной (по основанию 8). Манипулятор потока dec
возвращает поток к десятичной системе счисления. Установленное в
потоке основание остается в силе, пока не будет явно изменено.
лые числа
Основание
быть
системы счисления в потоке также может
изменено ма¬
setbase, который имеет один целочисленный аргу¬
принимающий значения 10, 8 или 16 для соответствующих сис¬
нипулятором потока
мент,
тем
счисления.
Можно управлять
точностью представления чисел с плавающей точ¬
кой, используя манипулятор потока setprecision или функцию-элемент
precision. Любым из этих способов точность устанавливается для всех
последующих операций вывода до следующей установки. Функция-эле¬
мент
precision без
Точность
аргумента возвращает текущую установку точности.
0 устанавливает точность, заданную по умолчанию (рав¬
ную 6).
При
использовании
включить
в
параметризованных
программу
файл
заголовка
манипуляторов
необходимо
iomanip.h.
Функция-элемент width устанавливает ширину поля и возвращает пре¬
дыдущее значение этой величины. В случае, если обрабатываемые зна¬
требуют
чения
места
чем
меньше,
ширина
поля,
Установленное
выводится символ-заполнитель.
в
пустые
позиции
значение ширины при¬
только для последующей операции помещения или извлече¬
ния, после чего ширина поля устанавливается равной 0, т.е. выводи¬
меняется
мые значения
будут
занимать поля ширины,
текущего значения. Если величина
та
больше,
чем
отведенная
требует
ширина
поля,
необходимой
для вывода
для своего размещения мес¬
усечение
не
производится,
Функция width без аргумента возвра¬
ширины поля. Для установки ширины поля
значение выводится полностью.
щает текущее значение
можно также использовать манипулятор потока setw.
При
вводе манипулятор потока setw устанавливает максимальный раз¬
мер вводимой строки; если вводится строка большего размера, то она
разбивается
на части, не превышающие размер,
Пользователи могут создавать
свои
Функции-элементы setf, unsetf
и
установленный setw.
собственные манипуляторы
flags
потока.
управляют значениями флагов.
указывает, что операция » должна пропускать во вход¬
ном потоке пробельные символы. Манипулятор потока ws также за¬
Флаг
skipws
ставляет игнорировать во
входном потоке ведущие
пробельные
симво¬
лы.
Флаги
форматирования определены
Флагами форматирования
ментов
flags
и
setf,
но
в
классе
ios.
можно управлять с помощью
многие
ваться манипуляторами потока.
программисты
Программист
функций-эле¬
предпочитают
пользо¬
может применить опера¬
Глава 21
914
|,
цию побитового ИЛИ
long. При
числе типа
чтобы объединить различные опции
вызове
функции-элемента flags
в
с таким
одном
набором
битов
устанавливаются новые опции форматирования для данного по¬
тока и возвращается значение типа long, содержащее предыдущее со¬
стояние
можно
щие
битов. Это
значение
было вызвать
flags
обычно сохраняется, для
того
чтобы
с этим значением и восстановить
потом
предыду¬
потока.
опции
Функция flags должна вызываться со значением, представляющим со¬
стояние всех флагов. Функция setf с одним аргументом, с другой сторо¬
ны, объединяет по ИЛИ один или большее количество флагов с сущест¬
вующими значениями флагов, формируя таким образом новое
состояние
форматирования
потока.
При установленном флаге showpoint вещественные числа выводятся с
десятичной точкой и заданным числом цифр после десятичной точки,
определяемым точностью представления*
Флаги left
и
и
right позволяют выводить данные с левым выравниванием
правой части поля вывода символами-заполнителями,
заполнением
или с правым выравниванием и заполняющими символами слева соот¬
ветственно.
Флаг internal указывает,
что знак числа
(или
основание системы счис¬
флаг ios::showbase установлен)
должен выравниваться по
значение
числа
быть
должно
левому краю поля,
выровнено по правому
краю, а в оставшиеся пустые позиции выводится символ-заполнитель.
ления,
если
ios::adjustfield содержит флаги left, right и internal.
Функция-элемент fill определяет символ-заполнитель, используемый
при left-, right- и internal-выравнивании данных в поле (по умолчанию
Элемент данных
пробел). Функция fill возвращает значение предшествующего за¬
полняющего символа. Задать символ-заполнитель можно также при по¬
мощи манипулятора потока setfill.
это
Статический
hex
и
элемент данных
dec, определяющие,
ios::basefield
включает в
что целые числа должны
себя биты oct,
обрабатываться
со¬
ответственно как восьмеричные, шестнадцатеричные и десятичные зна¬
чения.
По умолчанию операция передачи в поток выводит
если ни один из этих битов не установлен;
тичном виде,
из потока данные по умолчанию
в
которой
они
обрабатываются
числа в деся¬
извлекаемые
соответственно
форме,
вводятся.
Установите бит showbase, чтобы при выводе целых чисел указывалось
основание
системы
счисления.
и fixed содержатся в статическом элементе данных
ios::floatfield. Если установлен флаг scientific, то числа с плавающей
Флаги scientific
точкой будут выводиться в научном формате. Флаг fixed устанавливает
плавающей точкой формат с фиксированным числом цифр
после запятой; число цифр определяется функцией-элементом precisi¬
для чисел с
on.
Вызов функции cout.setf(0, ios::floatfield) восстанавливает заданный
по
умолчанию
формат
для чисел с
плавающей точкой.
Потоки
ввода/вывода
При
в С++
установленном
915
флаге uppercase
символы
X
и
E, используемые
шестнадцатеричном и научном форматах, выводятся
ре.
Кроме
ставлении
того, все
в
в верхнем регист¬
буквы, используемые в шестнадцатеричном
будут выводиться в верхнем регистре.
пред¬
также
числа,
Функция-элемент flags без аргумента просто возвращает (как длинное
целое) текущее состояние флагов форматирования. Функция-элемент
flags, вызванная с аргументом типа long, устанавливает флаги форма¬
та, заданные аргументом, и возвращает предыдущее состояние флагов.
Функция-элемент setf устанавливает флаги форматирования, указыва¬
емые в аргументе, и возвращает предыдущее состояние флагов в форма¬
те длинного целого.
Функция-элемент setf,
битом флага
вызываемая с двумя аргументами типа
битовым полем,
и
очищает биты
флаг, передаваемый
устанавливает
в
Функция-элемент unsetf сбрасывает
первом
в
битовом
long
поле и затем
аргументе.
указанные
флаги
и
возвращает
предыдущее состояние флагов.
Параметризованный
самые
действия,
Параметризованный
же
самые
Состояние
ленные
Бит
в
действия,
потока
классе
состояния
манипулятор потока
и
что
setiosflags
выполняет
те
же
функция-элемент flags.
манипулятор потока
что
можно
и
resetiosflags
выполняет
те
функция-элемент unsetf.
узнать,
просмотрев
биты состояния, опреде¬
ios.
eofbit устанавливается для
потока
ввода,
когда
опера¬
проверки состояния бита eofbit
можно использовать функцию-элемент eof.
ция ввода встречает конец
файла. Для
Бит failbit устанавливается, когда в потоке происходит ошибка форма¬
тирования; символы при этом не теряются. Функция-элемент fail так¬
же может быть использована для определения, не произошла ли такого
рода ошибка; обычно такую ошибку можно исправить.
Установленный бит badbit свидетельствует о том, что произошла ошиб¬
ка, связанная с потерей данных. Функция-элемент bad также может
определить, произошла ли такого рода ошибка в потоке. Такая серьез¬
ная
ошибка обычно неисправима.
Функция-элемент good
все возвращают
лько
на
возвращает true, если функции bad, fail
false. Операции ввода/вывода должны
«хороших»
и
eof
выполняться то¬
потоках.
Функция-элемент rdstate возвращает
состояние
потока.
Функция-элемент clear обычно
используется для восстановления «хо¬
рошего» состояния потока, чтобы на нем можно было продолжать опе¬
рации
ввода/вывода.
Пользователь
может перегрузить операции передачи в поток и извлече¬
ния из потока,
чтобы
иметь возможность выполнять
деляемых пользователем типов.
ввод/вывод опре¬
916
Глава 21
операция извлечения из потока принимает в качестве
аргументов ссылку на объект istream и ссылку на определяемый поль¬
Перегруженная
зователем
тип
данных,
и
возвращает
на
ссылку
объект istream.
операция передачи в поток принимает ссылку на объ¬
ект ostream и ссылку на определяемый пользователем тип данных и
возвращает ссылку на объект ostream.
Перегруженная
Перегруженные функции
дружественные,
В С++
чтобы
имеется
классами
потоковых
операций
часто
объявляются
как
иметь доступ к не-открытым элементам класса.
функция-элемент tie для синхронизации действий с
и ostream, вызов которой гарантирует, что вывод по¬
istream
явится перед связанным с ним вводом.
Терминология
badbit
манипулятор
cerr
потока
resetiosflags
манипулятор потока setbase
манипулятор потока setfill
cin
clog
манипулятор потока setprecision
манипулятор потока setw
cout
endl
неформатированный ввод/вывод
операция извлечения из потока (»)
операция передачи в поток («)
eofbit
failbit
ios::adjustfield
ios::basefield
ios::fixed
ios::floatfield
ios::internal
ios::scientific
ios::showbase
ios::showpoint
ios::showpos
left
skipws
параметризованный манипулятор потока
потоки, определяемые пользователем
uppercase
width
состояния
потоковый ввод
потоковый вывод
правое выравнивание
предопределенные потоки
пробельные символы
расширяемость
символ-заполнитель
символ-заполнитель по умолчанию
(пробел)
безопасный по типу ввод/вывод
библиотека классов потоков
восьмеричные числа
(начинаются с 0)
класс
fstream
ifstream
класс
ios
класс
iostream
класс
istream
класс
ofstream
класс
ostream
конец
левое
точность
по
умолчанию
флаги формата
форматирование в памяти
форматированный ввод/вывод
функция-элемент bad
функция-элемент clear
функция-элемент eof
функция-элемент fail
функция-элемент fill
заполнение
класс
формата
стандартный заголовочный файл
<iomanip.h>
файла
выравнивание
манипулятор
потока
манипулятор
потока
манипулятор
потока
flush
манипулятор
потока
hex
манипулятор
потока
oct
dec
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
flags
flush
gcount
get
getline
good
ignore
operator void*
operator!
Потоки
ввода/вывода
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
функция-элемент
в С++
917
функция-элемент tie
функция-элемент unsetf
функция-элемент write
функция-элемент ws
peek
precision
put
putback
rdstate
read
setf
шестнадцатеричные
(начинаются с 0x
ширина поля
числа
или
0X)
Распространенные ошибки программирования
21.1.
Попытка
тока
чтения данных из потока
ostream (или любого другого
по¬
вывода).
21.2. Попытка записи данных в поток istream (или любой другой поток
ввода).
21.3. В операторе ввода/вывода из потока вы опускают скобки, и в резу¬
льтате
из-за относительно
ния « и извлечения »
высокого
приоритета операций помеще¬
операции выполняются не в
нужном
по¬
рядке.
21.4.
Если установленное
значение ширины поля меньше, чем
мо для вывода, выводимая величина
будет
димой ширины, но при этом результат вывода
будет
или
Хороший
21.1.
стиль
С++
хотя
21.2.
программирования
и
на
доступны
в
в
только в стиле язы¬
из
библиотеки С,
С++.
При выводе выражений заключайте
их
в
скобки, чтобы избежать
операций
связанных со старшинством операции « и
конфликтов,
выражении.
Советы по повышению
21.1.
С++ используйте ввод/вывод
используйте функции ввода/вывода
и не
они
будет неудобочитаем
восприниматься неверно.
В программах
ка
необходи¬
помещена в поле необхо¬
эффективности
Применяйте неформатируемый ввод/вывод при обработке файлов
большого объема.
Общие методические замечания
21.1.
ных
21.2.
С++ безопасен
в
Ввод/вывод
в
отношении
использования
различ¬
типов.
С ++ предлагает единообразный интерфейс ввода/вывода для пре¬
типов и типов, определяемых пользователем. Бла¬
допределенных
годаря такому общему подходу облегчается
программ и,
обеспечения.
21.3.
в
частности,
Для того, чтобы ввести
в
повторное
С++
процесс
использование
возможности
разработки
программного
ввода/вывода новых,
требуется вносить
определяемых пользователем типов данных, не
изменения
в
закрытые секции данных
Расширяемость языка С++
ных черт.
классов
ostream
и
istream.
одна из его наиболее привлекатель¬
918
Глава 21
Упражнения
21.1.
для самоконтроля
Заполние пропуски в следующих утверждениях :
Перегруженные функции потоковых операций должны быть
функции класса.
Биты
b)
форматирования, которые отвечают за выравнивание,
a)
объявлены как
включают
себя
в
c) Ввод/вывод
в
и
,
С++ рассматривается
.
байтов.
как
и
потока
d) Параметризованные манипуляторы
могут быть использованы для установки
сброса флагов формати¬
и
рования.
e) Большинство программ С ++ должно включать файл
который содержит основную информацию,
мую для всех потоковых операций ввода/вывода.
,
f) Функции-элементы
ться
для
g) Файл
мую
установки
и
заголовка
могут использова¬
содержит
для выполнения
форматирования
h) При
использовании
грамму
должен
i) В файле
необходи¬
сброса флагов форматирования.
и
быть
заголовка
в
информацию, необходи¬
памяти.
параметризованных
файл
включен
заголовка
содержится
димая для выполнения
управляемой
в
манипуляторов
заголовка
про¬
.
информация, необхо¬
обработки фай¬
пользователем
лов.
j) Манипулятор
кив
выходной
k) Файл
потока
поток
и
помещает
сбрасывает буфер
заголовка
n) Вывод
стандартный
,
о) Операции
вывода.
ввода поддерживаются классом
ект-поток
p) Для
класса ostream используется для
неформатируемого
на
информацию, нужную
ввод/вывод С и С++.
совмещают
I) Функция-элемент
m) Операции
новой стро-
вывода.
содержит
программам, которые
выполнения
символ
потока
поток
или
на
.
ошибок направляется
объект
или на
вывода поддерживаются классом
операции передачи
объ-
.
.
в поток используется знак
q) Четыре объекта, соответствующих стандартным
это
устройствам,
,
.
системным
,
и
r) Для
операции извлечения из потока используется знак
s) Для
вывода целых чисел в восьмеричном, шестнадцатеричном и
форматах используются
десятичном
,
t) Заданная
и
u) При
установленном флаге
водятся со знаком «плюс».
манипуляторы
.
по умолчанию точность
щей точкой равна
потоковые
.
представления чидел с плаваю¬
.
положительные числа вы¬
Потоки
ввода/вывода
21.2.
в С++
919
Являются
a) Потоковая функция-элемент
flags()
потока
flags
и
ее
возвращает
предыдущее
b) Перегруженные операции передачи («)
(») обрабатывают все предопределенные
строки,
памяти
адреса
и
Если
long уста¬
переменной
с аргументом типа
навливает соответствующее своему аргументу значение
состояния
нет?
или
ли следующие утверждения верными
утверждение неверно, объясните, почему.
значение.
и извлечения из потока
типы
данных,
пользователем
определяемые
включая
типы
дан¬
ных.
c) Потоковая функция-элемент flags() без аргументов сбрасывает
все флаговые биты в переменной состояния потока flags.
d) При
»
операции
перегрузке
извлечения
ция-операция получает ссылку на тип istream
ляемый пользователем
e) Манипулятор
лы
в
потоке
тип
и
из
потока
функ¬
и ссылку на опреде¬
возвращаетссылку
потока ws пропускает ведущие
на
istream.
пробельные
симво¬
ввода.
перегрузке операции « помещения в поток функция-операция принимает ссылку на тип istream и ссылку на определяемый
f) При
пользователем
g) Операция »
ном
потоке
и
тип
возвращает
извлечения из
ведущие
i) Функция-элемент
потока всегда пропускает
пробельные
h) Обработка ввода/вывода
потока
istream.
на
ссылку
во вход¬
символы.
является
rdstate()
частью
языка
С++.
возвращает текущее состояние
потока.
j) Поток cout по умолчанию ассоциируется с экраном монитора.
k) Потоковая функция-элемент good() возвращает true, если функ¬
ции-элементы bad(), fail() и eof() все возвращают false.
1) Поток cin
m) Если
во
по
умолчанию
время
операции
ассоциируется
потокового
неустранимая ошибка, функция-элемент
n) Вывод
на
cerr
не
буферизован,
а
с
экраном
монитора.
ввода/вывода происходит
bad() возвращает true.
вывод
clog
буферизован.
о) Когда флаг ios::showpoint
установлен, то значения с плавающей
точкой выводятся по умолчанию с шестью цифрами после запятой,
при условии, что значение точности не изменялось, в противном
случае числа выводятся с
p) Функция-элемент put
установленной
класса
точностью.
ostream выводит заданное
количе¬
символов.
ство
q) Действие
ется
только
r) Адреса
потоковых манипуляторов
на
следующую
операцию
dec, oct
вывода
памяти по умолчанию выводятся в
и
hex распространя¬
целого
формате
числа.
длинного це¬
лого.
21.3.
Для
выполнения каждого из следующих
ному
оператору
языка
действий
С++.
а) Выведите строку "Enter your
name:
".
напишите по од¬
920
Глава 21
b) Установите флаг, отвечающий за вывод экспоненты в научной
нотации и букв в шестнадцатеричных числах в верхнем регистре.
c) Выведите адрес, записанный в переменной string типа char *.
d) Установите нужный флаг, чтобы числа с плавающей точкой вы¬
научном формате.
Выведите
ё)
адрес, записанный
водились
в
переменной integerPtr
f) Установите флаг, задающий режим добавления
ния при
ных
выводе
типа
int *.
символов основа¬
восьмеричных и шестнадцатеричных целочислен¬
значений.
g) Выведите значение,
типа float *.
на
которое
ссылается
указатель
h) Используя потоковую функцию-элемент, установите
floatPtr
символ-за-
полнитель '*' поля большей ширины, чем выводимое значение. На¬
выполняющий
пишите оператор,
ту же задачу при помощи мани¬
пулятора потока.
i) Выведите
символы
put
ции-элемента
j) Получите
k) Введите
'0'
'K'
и
класса
в одном операторе с помощью
функ-
ostream.
символ из потока ввода, не удаляя его из этого потока,
символ
в
переменную
с
типа
char двумя различными
класса istream.
способами, используя функцию-элемент get
и отбросьте шесть символов из потока ввода,
m) Введите 50 символов в массив line типа char, используя функ-
1) Введите
цию-элемент read класса istream.
n) Считайте 10
ется, если встречается ограничитель '.'. Не
Ввод прекраща¬
удаляйте его из потока
Напишите другой оператор, который
выполняет эту задачу и
ввода.
удаляет
символов в массив символов name.
ограничитель
из
потока.
о) Используя функцию-элемент gcount
число
нем
те
введенных
число,
p) Напишите
символьный
класса
line
istream, определите
символов при
послед¬
к
сброса (принудительного вывода данных)
функцию-элемент и манипулятор потока,
два оператора
потока вывода, используя
q) Выведите
"String".
r) Выведите
массив
функции-элементу read класса istream и выведи¬
вызвав функцию-элемент write класса ostream.
обращении
это
в
следующие
значения:
124,
18.376,
'Z', ip00000
и
текущую установку точности, используя функцию-эле¬
мент.
s) Введите
целое значение в переменную months типа int и вещест¬
венное число в переменную percentageRate типа float.
t) Выведите 1.92, 1.925
и
1.9258
с точностью,
равной
трем цифрам,
установив ее с помощью манипулятора потока.
манипуляторы потока, выведите целое число 100 в
восьмеричном, шестнадцатеричном и десятичном форматах.
u) Используя
v) Выведите
целое число 100 в десятичном, восьмеричном и шест¬
надцатеричном представлении, применив для этого один и тот же
манипулятор установки основания.
Потоки
ввода/вывода
w) Выведите
из
921
в С++
число
1234
с выравниванием по правому краю в поле
10 цифр.
x) Введите
символьный
символы в
'z',
символ
но
более
не
массив
20 символов
line,
пока не встретится
(включая
завершающий
нуль-символ). Не извлекайте символ-разделитель из потока.
помощи целых переменных x и у для задайте ширину поля
и точность; используйте эти установки при выводе значения двой¬
у) При
ной
21.4.
точности
87.4573.
Найдите ошибку
как
a)
ее
можно
<<
cout
"Value
c)
21.5.
<<
<<
cout
Для
объясните,
x
of
<=
у
is:
"
<<
<=
x
у;
оператор должен вывести числовое значение V.
b) Следующий
cout
в каждом из следующих операторов и
исправить.
'с';
""
А
string
in
quotes
каждого из следующих
"";
фрагментов
кода покажите, что
будет
выведено на экран.
а)
<<
cout
"12345\n";
cout.,width(5);
cout. fill (
'
*
,
b)
'
);
cout
<<
123
cout
<<
setw(10)
«
'\n'
<<
<<
«
123;
<<
setfill('$')
10000;
с)
cout
<<
setprecision(3)
<<
d)
cout
<<
setiosflags(ios::showbase)
<<
<<
'\n'
e)
f)
cout
cout
setw(8)
<<
hex
«
<<
<<
99
99;
<<
100000
<<
setiosflags(ios::showpos)
<<
setw(10)
«
1024.987654;
oct
'\n'
<<
<<
setprecision(2)
setiosflags(ios::scientific)
100000;
<<
<<
444.93738;
Ответы на упражнения для самоконтроля
21.1.
а) дружественные, b) ios::left, ios::right и ios::internal. с) потоки, d)
setiosflags, resetiosflags. e) iostream.h. f) setf, unsetf. g) strstream.h. h) iomanip.h. i) fstream.h. j) endl. k) stdiostream.h. I) write.
m) istream. n) cerr или clog. о) ostream. p) «. q) cin, cout, cerr и
clog, r) ». s) oct, hex, dec. t) шесть цифр после запятой, u) ios::
showpos.
21.2.
а) Верно.
и извлечения не перегружены для
всех определяемых пользователем типов. Для каждого определяе¬
мого пользователем типа программист должен написать соответст¬
b) Неверно. Операции передачи
вующие перегруженные функции-операции.
c) Неверно. Функция-элемент flags()
щает текущее значение переменной
d) Верно.
e) Верно.
без
аргументов просто возвра¬
состояния
потока
flags.
922
Глава 21
f) Неверно. При
перегрузке операции передачи
в
поток
«
функ¬
должна принимать ссылку на тип ostream и ссылку
ция-операция
на определяемый
пользователем тип и возвращать ссылку на тип
ostream.
g) Верно. Если флаг ios::skipws не сброшен.
h) Неверно. Возможности ввода/вывода в С++ обеспечиваются
стандартными библиотеками С++. Сам
раций ввода/вывода
или
язык не поддерживает опе¬
обработки файлов.
i) Верно,
j) Верно,
k) Верно.
I) Неверно. Поток cin ассоциируется со стандартным устройством
ввода компьютера, обычно
клавиатурой.
m) Верно.
n) Верно.
0) Верно.
p) Неверно. Функция-элемент put
класса
ostream
выводит
один
символ.
q) Неверно. Потоковые
манипуляторы dec, oct
и
hex устанавлива¬
ют основание системы счисления целых чисел в потоке для всех по¬
операций,
следующих
вание
или
программа
пока
не
другой манипулятор
свою работу.
не изменит осно¬
завершит
r) Неверно. Адреса памяти по умолчанию выводятся в шестнадца¬
теричном формате. Чтобы вывести адрес в формате длинного цело¬
го, адресное выражение нужно преобразовать к типу длинного це¬
лого.
21.3.а)
cout
<<
"Enter
your
name:
";
b) cout.setf(ios::uppercase);
c)
cout
<<
long(string);
d) cout.setf(ios::scientific,
ios::floatfield);
e)
cout
<<
integerPtr;
f)
cout
<<
setiosflags(ios::showbase);
g)
cout
<<
*floatPtr;
h)
cout.fill (
coutt
1)
<<
cout. put (
f
*
'
);
setfill('*');
'
0
f
)
.
put (
'
К' ) ;
j) cin.peek();
k)
с
1)
cin ignore (6)
cin.get ();
cin.get (с);
=
.
;
m)cin.read(line, 50);
n) cin.get(name, 10,
cin.getline(name,
'.');
10, '.');
Потоки
ввода/вывода
923
в С++
о) cout.write(line, cin.gcount());
p)
cout.flush();
cout << flush;
q)
cout
r)
cout.precision();
s)
cin
t)
cout
«
>>
«
124
18.376
»
months
setprecision(3)
«
1.925
'\t'
«
cout
<<
oct
<<
v)
cout
<<
100
<<
w)cout
<<
setw(10)
21.4.
<<
hex
setbase(8)
<<
1000000
«
"String";
1.92
<<
<<
100;
'\t'
<<
100
<<
dec
<<
100
<<
setbase(16)
<<
100;
1234;
'z );
<<
setw(x)
«
1.9258;
«
<<
100
x) cin.get(line, 20,
<<
'Z'
percentageRate;
<<
u)
у) cout
«
setprecision(y)
<<
87.4573;
а) Ошибка: приоритет операции « выше, чем приоритет <=, в ре¬
зультате чего оператор будет оцениваться неправильно и компиля¬
сообщение об ошибке.
Исправление: чтобы исправить оператор, заключите
скобки выражение x <= у. Такая ошибка произойдет с
выдаст
тор
в
круглые
любым
вы¬
котором используются операции с меньшим приорите¬
том, чем у операции «, если выражение не заключить в круглые
ражением, в
скобки.
С ++
b) Ошибка:
в
лые
как
числа,
символы не рассматриваются как короткие це¬
это
Исправление: чтобы
было
в
языке
С.
вывести численное значение символа из табли¬
цы допустимых символов компьютера, его нужно привести к цело¬
му
типу,
cout
как
<<
символы
строки,
Исправление:
cout
если
выражении:
<<
<<
а) 12345
123
123
b)$$$$$10000
c)1024.988
d)0143
0x63
e)100000
+100000
f) 4.45e+02
двойных
не
кавычек не могут быть выведены
использовать
escape-код.
выведите строку одним из следующих способов:
'"'
cout
21.5.
следующем
int(1 с1);
c) Ошибка:
составе
в
<<
string in quotes" << '"';
"\"A string in quotes\"";
"А
в
924
Глава 21
Упражнения
21.6.
Выполните каждое
из
действий при помощи
следующих
одного
оператора языка:
a) Выведите
целое число 40000 в поле из 15
цифр
с левым выравни¬
ванием.
b) Введите
строку в символьный массив state.
c) Выведите
число
d) Выведите 100
вания
200
со
знаком
и
без
знака.
формате
в шестнадцатеричном
e) Считайте
читель
символы в массив s, пока не встретится символ-ограни-
'p',
но
Ограничитель
f) Выведите
g) Введите
не
10
более
(включая нуль-символ).
отброшен.
символов
должен быть извлечен из потока и
число
1.234
в
со стандартного
ters". Сохраните строку
поле
9 цифр
из
21.7.
кавычек
ведущими
в символьном массиве s.
входного
потока.
не
нулями.
форме "charac¬
Отбросьте
более 50
симво¬
символов
Напишите программу ввода целых чисел в десятичном, восьмерич¬
ном и шестнадцатеричном форматах. Выведите каждое прочитан¬
ное целое число во всех трех форматах. Проверьте программу со
следующими
21.8.
из
с
потока ввода строку в
Введите
(включая завершающий нуль-символ).
лы
с указанием осно¬
0x.
входными
данными:
10, 010, 0x10.
Напишите программу, которая выводит
значения указателей, при¬
веденных к различным целочисленным типам. В каких случаях вы
получите странные значения? В каких случаях произойдут ошиб¬
ки?
21.9.
Напишите
тестовую программу вывода целочисленного значения
12345 и вещественного числа 1.2345 в поля различной ширины.
Что получается, когда значение выводится в поле, содержащее ме¬
ньшее количество позиций, чем выводимое значение?
21.10.Напишите программу, которая выводит варианты округления чис¬
ла 100.453627 до целого значения и до числа с дробной частью с де¬
сятыми,
сотыми,
тысячными
и
десятитысячными.
21.11.Напишите программу, которая вводит строку
ры и определяет ее длину.
символов с клавиату¬
Выведите эту строку,
задав в качестве
ширины поля вывода удвоенную длину.
21.12.Напишите программу перевода температуры по шкале Фаренгей¬
в температуру по
та
целое число, изменяющееся от 0 до 212
шкале Цельсия
вещественное число
льзуйте формулу преобразования
celsius
=
5.0/9.0
Данные выведите
*
в два
(fahrenheit
столбца
значения температуры по
-
с точностью 3 цифры. Испо¬
32);
с правым выравниванием,
Цельсию
причем
выводите со знаком как для от¬
рицательных, так и для положительных значений.
21.13.B некоторых языках программирования строки могут заключаться
в одиночные или двойные кавычки. Напишите программу, которая
Потоки
ввода/вывода
вводит
21.14.B
строки
три
двойные
925
в С++
suzy,
"suzy"
и
'suzy'. Будут
кавычки введены как часть строки или
на рис.
программе
21.30
операции
ли
одиночные
и
будут отброшены?
извлечения
из
потока
и
были перегружены, чтобы иметь возможность
ввода/вывода объектов класса PhoneNumber. Перепишите функцию-операцию извлечения из потока, чтобы она выполняла опи¬
передачи
санную
в
поток
ниже
процедуру
проверки
корректности
ввода.
Функ-
цию-операцию » для этого придется полностью переписать.
a) Введите
том,
сразу весь номер телефона
что введено
в
было соответствующее
массив.
Затем убедитесь
в
(символов
число символов
быть 14, при записи номера телефона в форме (800)
555-1212). Используйте функцию-элемент потока clear, чтобы
установить бит ios::fail, если ввод неверен.
должно
b) Поскольку
код региона и код
телефонной
0
станции в номере теле¬
1, проверьте первую цифру
этих кодов на 0 и 1. Используйте функцию-элемент потока clear,
чтобы установить бит ios::fail, если ввод неверен.
фона
не
должен
начинаться
c) Средняя цифра
с
или
кода региона всегда 0 или
0
1, поэтому проверьте
1. Используйте функцию-элемент потока
clear, чтобы установить бит ios::fail, если ввод неверен. Если ни
одна из вышеупомянутых проверок не установила флаг ios::fail,
среднюю цифру
на
и
копируйте три части номера телефона в элементы-данные объекта
PhoneNumber
areaCode, exchange и line. В случае, если бит
ios::fail установлен, то в основной программе выведите сообщение
об ошибке, а не номер телефона.
21.15.Напишите программу, которая
a) Создайте
выполняет
следующие
действия:
point, который содержит элементы целого типа
yCoordinate и объявляет дружественные функ¬
класс
xCoordinate
и
ции-операции
извлечения
из
потока
и
в
передачи
поток.
b) Определите операцию передачи в поток и операцию извлечения
из потока. Функция-операция извлечения из потока должна вы¬
полнять проверку введенных данных и, в случае
устанавливать
бит ios::fail. Если при вводе
ошибка, операция передачи
не
c) Напишите функцию main,
в
должна
ошибки
значения
выводить
в данных,
произошла
объект point.
которой вводятся и выводятся опре¬
point при помощи перегружен¬
деляемые пользователем объекты
ных
операций
извлечения
и
передачи.
21.16.Напишите программу, которая
a) Определите пользовательский
выполняет
класс
следующую
задачу:
complex, который содержит
imaginary и объ¬
закрытые целочисленные элементы-данные real и
являет дружественные классу
и
извлечения
из
функции-операции передачи
в поток
потока.
b) Определите операцию передачи в поток и операцию извлечения
из потока. Функция-операция извлечения из потока должна вы¬
полнять проверку введенных данных и, в случае
ливать
бит ios::fail. Вводимые данные должны
ошибки, устанав¬
форму
иметь
926
Глава 21
3
+
8i
Действительная
и
мнимая части
положительными,
Если
вводиться.
должен
данных
ция передачи
мат
должен
кроме того,
значение
не
могут
быть отрицательными или
значений может не
одно из двух
введено,
соответствующий
элемент
быть приравнен 0. В случае ошибки ввода опера¬
в поток не должна выполнять вывод.
быть
идентичен
входному формату,
Выходной фор¬
приведенному
отрицательных мнимых значений вместо знака «плюс»
приведенном выше формате данных должен выводиться знак
выше.
в
и,
Для
«минус».
с) Напишите функцию main, которая вводит и выводит определяе¬
мые пользователем объекты complex при помощи перегруженных
операций
извлечения
и
передачи.
21.17.Напишите программу,
в которой применяется оператор for для вы¬
вода ASCII-символов со значениями от 33 до 126. Программа дол¬
жна вывести десятичное, восьмеричное, шестнадцатеричное значе¬
ние
каждого символа и сам символ.
потока
dec, oct
и
Используйте
21.18.Напишите программу, чтобы убедиться,
ream
ку
getline
и
get
манипуляторы
hex.
что
функции-элементы ist¬
с тремя аргументами завершают вводимую стро¬
ограничивающим
нуль-символом.
Также покажите,
что
оставляет символ-ограничитель в потоке ввода, в то время как
line
извлекает
ограничитель
непрочитанными
символами
21.19.Напишите программу,
и
отбрасывает
его.
Что
get
get¬
происходит с
потока?
которой создается определяемый пользова¬
skipwhite, пропускающий в потоке ввода про¬
Манипулятор должен использовать функцию isв
телем манипулятор
бельные
символы.
space из библиотеки ctype.h для проверки, не является ли символ
пробельным. Символы должны вводится функцией-элементом get
класса istream. Как только встречается не-пробельный символ, ма¬
нипулятор skipwhite заканчивает свою работу, помещает этот сим¬
вол обратно в поток ввода и возвращает ссылку на istream.
этот манипулятор в функции main, сбросив перед
флаг ios::skipws, чтобы оператор извлечения из потока не про¬
пускал пробельные символы автоматически. Затем проверьте мани¬
пулятор, вводя символ, которому предшествует пробельный сим¬
вол. Выводите извлеченные из потока символы, чтобы убедиться в
том, что пробельные символы отбрасываются.
Протестируйте
этим
Библиография
(A193)
Allison, C., «Code Capsules: А С++ Date Class, Part 1,» The С
Users Journal, Vol. 11, No. 2, February 1993, pp. 123
131.
-
(An92)
Anderson, A. E., and W. J. Heinze, C++ Programming and Funda¬
mental Concepts, Englewood Cliffs, NJ: Prentice Hall, 1992.
(Ba93)
Bar-David, T., Object-Oriented Design for C++, Englewood Cliffs,
NJ: Prentice Hall, 1993.
Потоки
ввода/вывода
в С++
927
(Be93)
Berard, E. V., Essays on Object-Oriented Software Engineering: Vo¬
lume I, Englewood Cliffs, NJ: Prentice Hall, 1993.
(Br91)
Borland, Borland C++ Programmer's Guide, Part No.
14MN-TCP04, Scotts Valley, CA: Borland International, Inc., 1991.
(Br91a) Borland, Borland C++ Getting Started, Part No. 14MN-TCP02,
Scotts Valley, CA: Borland International, Inc., 1991.
(Br91b) Borland, Borland C+4- 3.0 Programmers Guide, Scotts Valley, CA:
Borland International, Inc., 1991.
(Bv93)
Byron, D., «The Case for Object Technology Standards,» CASE
Trends, September 1993, pp. 22-26.
(Co93)
Computerworld, «The CW Guide to Object-Oriented Programming,»
Computerworld, June 14,1993, pp. 107-130.
(De90)
Deitel, H. M., Operating Systems, Second Edition, Reading, MA:
Addison-Wesley, 1990.
(E190)
Ellis, M. A. and B. Stroustrup, The Annotated C+4- Reference Ma¬
nual, Reading, MA: Addison-Wesley, 1990.
(F193)
Flamig, B., Practical Data Structures
1993.
(Ha93)
Hagan, Т., «С4-4- Class Libraries for GUIs,» Open Systems Today,
Februnry 15, 1993, pp. 54, 58
(Ja93)
Jacobson, I., «Is Object Technology Software s Industrial Plat¬
form?» IEEE Software Magazine, Vol. 10, No. 1, January 1993,
pp. 24-30.
(Ko93)
Kozaczynski, W., and A. Kuntzmann-Combelles, «What It Takes to
Make OO Work,» IEEE Software Magazine, Vol. 10, No. 1, Janua¬
in C++, John
Wiley & Sons,
ry 1993, pp. 20-23.
(Li91)
Lippman, S. 8., C4-4- Primer (Second Edition), Reading, MA: Addison-Wesley Publishing Company, 1991.
(Lo93)
Lorenz, M., Object-Oriented Software Development: A Practical Gu¬
ide, Englewood Clif*fs. NJ: Prentice Hall, 1993.
(Lu92)
Lucas, P. J., The C++ Programmer's Handbook, Englewood Cliffs,
NJ: Prentice Hall, 1992.
(Ma93)
Martin, J., Principles of Object-OrientedAnalysis and Design, Eng¬
lewood Cliffs, NJ: Prentice Hall, 1993.
(Me93)
Matsche, J. J., «Object-oriented programming in Standard C,» Ob¬
ject Magazine, Vol. 2, No. 5, January/February 1993, pp. 71-74.
(Mi91)
Microsoft, Microsoft C/C4-4- Class Libraries Reference (Versi¬
on 7.0), Redmond, WA: Microsoft Corporation, 1991.
(Mi91a) Microsoft, Microsoft C/C4-4- C4-4- Language Reference (Version
7.0), Redmond, WA: Microsoft Corporation, 1991.
(Mi91b) Microsoft, Microsoft C/C4-4- C4-4- Tutorial (Version 7.0), Redmond,
WA: Microsoft Corporation, 1991.
928
Глава 21
(Pi93)
Pittman, М., «Lessons Learned in Managing Object-Oriented Deve¬
lopment,» IEEE Software Magazine, Vol. 10, No. 1, January 1993,
pp. 43-53.
(Pr93)
Prieto-Diaz, R., «Status Report: Software Reusability,» IEEE Soft¬
ware, Vol.
10, No. 3, May 1993, pp. 61-66.
(Ra92)
Ranade, J., and S. Zamir, C++ Primer for C Programmers, New
York, NY McGraw-Hill, Inc., 1992.
(Re91)
Reed, D. R., «Moving from C to C++,» Object Magazine, Vol. 1,
No. 3, September/October, 1991, pp. 46-60.
(R193)
Rettig, M., G. Simmons, and J. Thomson, «Extended Objects,»
Communications of the ACM, Vol. 36, No. 8, August 1993,
pp. 19-24.
(Sa93)
Saks, D., «Inheritance, Part 2,» The C Users Journal, May 1993,
pp. 81-89.
(Sh91)
Shiffman, H., «С++ Object-Oriented Extensions to C,» SunWorld,
Vol. 4, No. 5, May 1991, pp. 63-70.
(SI693)
Skelly. C., «Pointer Power in C and C++, Part 1,» The C Users Jo¬
urnal, Vol. 11, No. 2, February 1993, pp. 93-98.
(Sn93)
Snyder, A., «The Essence of Objects: Concepts and Terms,» IEFE
Software Magazine, Vol. 10, No. 1, January 1993, pp. 31-42.
(St91)
Stroustrup, B., The C+4- ProgrammingLanguage (Second Edition),
Reading, MA: Addison-Wesley Series in Computer Science, 1991.
(St93)
Stroustrup, B., «Why Consider Language Extensions?: Maintaining
a
Delicate Balance,» C++ Report, September 1993, pp. 44-51.
(Vo93)
Voss, G., «Objects and Messages,» Windows Tech Journal, Februa¬
ry 1993, pp. 15-16.
(Wi93)
Wiebel, M., and S. Halladay, «Using OOP Techniques Instead of
switch in C++,» Vol. 10, No. 10, The C Users Journal, October
1992, pp. 105-106.
(WI93)
Wilde, N., P. Matthews, and R. Huitt, «Maintaining Object-Orien¬
ted Software, IEEE Softwa?'e Magazine, Vol. 10, No. 1, January
1993, pp. 75-80.
(Wt93)
Wilt, N., «Templates in C++,» The C Users Journal, May 1993,
pp. 33-51.
п
p
и
л
о
ж
e
и
н
e
А
Синтаксис С
В применяемой
ниже
нотации
синтаксические
категории
символы) обозначены курсивом, а литеральные слова
льных наборов (терминальные символы)
полужирным
ные
(нетерминаль¬
и элементы симво¬
шрифтом.
Двоето¬
чие, следующее за нетерминальным символом, предваряет его определение.
определения располагаются на отдельных строках, за исклю¬
чением случаев, когда им предшествуют слова «один из». Опциональные сим¬
волы обозначаются подстрочным индексом opt, так что
Альтернативные
{
выражениеоР1
}
означает опциональное выражение, заключенное в
Сводка
фигурные скобки.
синтаксиса
A.1. Лексическая грамматика
A.1.1.
Лексемы
лексема:
ключевое-слово
идентификатор
константа
строковый-литерал
операция
пунктуатор
препроцессорная-лексема:
имя-заголовка
идентификатор
Этот материал является сокращением и адаптацией документа Американского Националь¬
ного Стандарта для Информационных систем
Язык программирования
С, ANSI/ISO
9899; 1990. Копии
30
Зак 801
Американского Национального
этого стандарта могут быть получены от
по адресу: 11 West 42nd Street, New
Института Стандартов
York, NY 10036.
Приложение А
930
рр-число
символьная-константа
строковый-литерал
операция
пунктуатор
любой не-пробельный
символ, не
являющийся
одним из
вышеперечисленных
A.1.2. Ключевые слова
ключевое-слово: одно из
auto
double
int
struct
break
else
long
switch
case
enum
register
typedef
char
extern
return
union
const
float
short
continue
default
do
for
stgned
sizeof
static
unsigned
void
volatile
while
A.1.3.
goto
if
Идентификаторы
идентификатор:
не-цифра
идентификатор не-цифра
идентификатор цифра
не-цифра:
одна из
а
b
e
f
g
h
i
j
k
n
о
p
А В С
q r
D E
s
t
u
v w
x
F
G
H
I
N
О
Q
R
S
T
U V W X
5
6
7
с
d
__
m
Y
Z
одна из
цифра:
0
P
1
у z
J К L M
1
2
3
4
8
9
A.1.4. Константы
константа:
плавающая-константа
целая-константа
перечисляемая-константа
символьная-константа
плавающая-константа:
дробная-константа экспоненциальная-частьорг плавающий-суффикс0Р1
последовательность-цифр экспоненциальная-часть плавающий-суффикс0р1
дробная-константа:
nocлeдoвameльнocmь-цuфpopt. последовательность-цифр
последовательность-цифр.
Синтаксис С
931
экспоненциалъная-частъ:
e 3naKopt
E 3naKopt
последовательность-цифр
последовательность-цифр
знак: один из
+
-
последовательность-цифр:
цифра
последовательность-цифр цифра
один из
плавающий-суффикс:
f
1
F L
целая-константа:
десятичная-константа
целый-суффиксорг
восьмеричная-константа целый-суффиксорг
шестнадцатеричная-константа целый-суффикс0^
десятичная-константа:
ненулевая-цифра
десятичная-константа цифра
восьмеричная-константа:
0
восьмеричная-константа
восьмеричная-цифра
шестнадцатеричная-константа:
0x
шестнадцатеричная-цифра
шестнадцатеричная-цифра
шестнадцатеричная-константа шестнадцатеричная-цифра
0X
ненулевая-цифра:
1
2
3
4
5
6
одна из
7
8
восьмеричная-цифра:
0
1
2
3
4
5
6
9
одна из
7
шестнадцатеричная-цифра:
0
1
а
А
2
3
4
b
с
d
e
f
В
С
D
E
F
5
6
7
8
одна из
9
целый-суффикс:
беззнаковый-суффикс длинный-суффиксорг
длинный-суффикс беззнаковый-суффиксорг
беззнаковый-суффикс:
один из
u и
длинный-суффикс:
1
30*
L
один из
Приложение А
932
перечисляемая-константа:
идентификатор
символьная-константа:
'с-сНаг-последователъностъ'
Ъ'с-сНаг-последователъностъ'
с-сЬаг-последователъностъ:
c-char
с-сЬаг-последователъностъ c-char
c-char:
любой элемент набора символов кроме одинарной
косой черты \ и символа новой строки
кавычки
, обратной
евсаре-последовательность
еэсаре-последовательность:
простая-евсаре-последовательность
восьмеричная-евсаре-последовательность
шестнадцатеричная-евсаре-последовательность
простая-евсаре-последовательность:
V
\"
\?
\\
\a
\b
\f
\n
\r
\t
одна из
\v
восьмеричная-евсаре-последовательность:
\ восъмеричная-цифра
\ восъмеричная-цифра восъмеричная-цифра
\ восъмеричная-цифра восъмеричная-цифра восъмеричная-цифра
шестнадцатеричная-евсаре-последовательностъ:
\x шестнадцатеричная-цифра
шестнадцатеричная-езсаре-последователъностъ
шестнадцатеричная-цифра
A.1.5. Строковые литералы
строковый-литерал:
"в-сЬаг-последователъностъорГ
Ъ^'в-сЬаг-последователъностЪорГ
s-char-последователъностъ:
s-char
s-char-последователъностъ
s-char
s-char:
любой
элемент исходного набора символов кроме двойной
обратной косой черты \ и символа новой строки
езсаре-последователъностъ
A.1.6. Операции
операция: одна
из
кавычки
",
Синтаксис С
[
933
(
]
)
++
->
-
+
&
/
~
-
>
%
«
>>
<
/
%
+=
#
##
!
<=
>=
<<=
»=
sizeof
==
!=
7
=
A.1.7.
=
=
-
=
&=
^=
i
=
Пунктуаторы
пунктуатор: один
[
]
(
)
{
из
}
*
=
:
,
;
...
#
A.1.8. Имена заголовков
имя-заголовка:
<Н-с1гаг-последовательность>
"q-char-последователъностъ"
Н-сНаг-последовательность:
h-char
Н-сНаг-последовательность h-char
h-char:
любой
элемент исходного
набора
символов кроме символа
новой строки
и >
q-char-последовательность:
q-char
q-char-последовательность q-char
q-char:
любой
элемент исходного
набора
"
и
A.1.9.
Препроцессорные
числа
рр-число:
цифра
цифра
рр-число цифра
рр-число не-цифра
рр-число e знак
.
рр-число E знак
рр-число
A.2.
.
Структурная грамматика
A.2.1.
Выражения
первичное-выражение:
идентификатор
константа
строковый-литерал
( выражение )
символов кроме символа
новой строки
Приложение А
934
постфиксное-выражение:
первичное-выражение
постфиксное-выражение [ выражение ]
постфиксное-выражение ( список-выражений-аргументовоР1 )
постфиксное-выражение идентификатор
.
постфиксное-выражение
постфиксное-выражение
постфиксное-выражение
->
идентификатор
++
--
список-выражений-аргументов:
присваиваемое-выражение
список-выражений-аргументов
,
присваиваемое-выражение
унарное-выражение:
постфиксное-выражение
++ унарное-выражение
--
унарное-выражение
унарная-операция
выражение-приведения
sizeof унарное-выражение
sizeof ( имя-типа )
унарная-операция:
&
*
+
одна из
!
выражение-приведения:
унарное-выражение
(
имя-типа
) выражение-приведения
мулыпипликативное-выражение:
выражение-приведения
мультипликативное-выражение
*
выражение-приведения
мулътипликативное-выражение / выражение-приведения
мультипликативное-выражение % выражение-приведения
аддитивное-выражение:
мультипликативное-выражение
аддитивное-выражение
аддитивное-выражение
+ мультипликативное-выражение
-
мультипликативное-выражение
выражение-сдвига:
аддитивное-выражение
выражение-сдвига « аддитивное-выражение
выражение-сдвига » аддитивное-выражение
выражение-отношения:
выражение-сдвига
выражение-отношения <
выражение-отношения >
выражение-отношения
выражение-отношения
выражение-сдвига
выражение-сдвига
<
выражение-сдвига
>=
выражение-сдвига
935
Синтаксис С
выражение-равенства:
выражение-отношения
==
выражение-отношения
выражение-равенства != выражение-отношения
выражение-равенства
AND-выражение:
выражение-равенства
AND-выражение & выражение-равенства
исключающее-ОК-выражение:
AND-выражение
исключающее-ОК-выражение
^
AND-выражение
включающее-ОЯ-выражение:
исключающее-ОЯ-выражение
включающее-СЖ-выражение \ исключающее-СЖ-выражение
лoгuчecкoe-AND-выpaжeнue:
включающее-ОК-выражение
лoгuчecкoe-AND-выpaжeнue && включающее-СЖ-выражение
логическое-ОК-выражение:
лoгuчecкoe-AND-выpaжeнue
логическое-ОЯ-выражение \ \ лoгuчecкoe-AND-выpaжeнue
условное-выражение:
логическое-ОК-выражение
логическое-ОК-выражение
? выражение
:
условное-выражение
присваиваемое-выражение:
условное-выражение
унарное-выражение операция-присваивания присваиваемое-выражение
операция-присваивания: одна
=
*=
/= %=
+=
-=
«=
из
»=
^=
|
=
выражение:
условное-выражение
выражение
,
присваиваемое-выражение
константное-выражение:
условное-выражение
A.2.2. Объявления
объявление:
спецификаторы-объявления cnucoк-uнuцuaлuзupyющux-дeклapamopoвopt;
спецификаторы-объявления:
спецификатор-класса-хранения спецификаторы-объявленияорг
спецификатор-типа спецификаторы-объявления091
модификатор-типа cneцuфuкamopы-oбъявлeнuяopt
936
Приложение
А
список-инициализирующих-деклараторов:
инициализирующий-декларатор
список-инициализирующих-деклараторов
,
инициализирующий-декла¬
ратор
инициализирующий-декларатор:
декларатор
декларатор
инициализатор
=
спецификатор-класса-хранения:
typedef
extern
static
auto
register
спецификатор-типа:
void
char
short
int
long
float
double
signed
unsigned
спецификатор-в^ис^или-итоп
спецификатор-епит
имя-typedef
спецификатор^гисЬили-итоп:
sttuct-или-итоп идентификаторорг {
struct-или-итоп идентификатор
список-объявлений-struct
}
struct-или-итоп:
struct
union
список-объявлений-struct:
объявление-struct
список-объявлений-struet объявление-struct
объявление-struct:
список-спецификаторов-и-модификаторов список-деклараторов-struct
список-спецификаторов-и-модификаторов:
спецификатор-типа список-спецификаторов-и-квалификаторов0р1
модификатор-типа cnucoк-cneцuфuкamopoв-u-квaлuфuкamopoвopt
список-деклараторов-struct:
декларатор-struct
список-деклараторов-struct
,
декларатор-struct
;
Синтаксис С
937
декларатор-struct:
декларатор
декларатороР1 : константное-выражение
спецификатор-епит:
enum
enum
идентификатор0р1 { список-перечислителей }
идентификатор
список-перечислителей:
перечислитель
список-перечислителей
,
перечислитель
перечислитель:
перечисляемая-константа
=
перечисляемая-константа
константное-выражение
модификатор-типа:
const
volatile
декларатор:
указателЬорг
прямой-декларатор
прямой-декларатор:
идентификатор
прямой-декларатор [ константное-выражение ]
прямой-декларатор ( список-параметрического-типа
прямой-декларатор ( список-идентификаторовоР1 )
)
указатель:
*
*
список-модификаторов-типаоР1
список-модификаторов-типаорг
указатель
список-модификаторов-типа:
модификатор-типа
список-модификаторов-типа модификатор-типа
список-параметрического-типа:
список-параметров
список-параметров
.
,
.
.
список-параметров:
объявление-параметра
список-параметров
,
объявление-параметра
объявление-параметра:
спецификаторы-объявления декларатор
спецификаторы-объявления абстрактный-декларатороР1
список-идентификаторов:
идентификатор
список-идентификаторов
,
идентификатор
938
Приложение
А
имя-типа:
список-спецификаторов-и-квалификаторов абстрактный-декларатор0^
абстрактный-декларатор:
указатель
указателЬорг прямой-абстрактный-декларатор
прямой-абстрактный-декларатор:
( абстрактный-декларатор )
npямoй-aбcmpaкmный-дeклapamopopt [ константное-выражениеорг ]
прямой-абстрактный-деклараторорг ( список-параметрического-типа )
имя-typedef:
идентификатор
инициализатор:
присваиваемое-выражение
{ список-инициализаторов }
{ список-инициализаторов
}
,
список-инициализаторов:
инициализатор
список-инициализаторов
A.2.3.
,
инициализатор
Операторы
оператор:
помеченный-оператор
составной-оператор
оператор-выражение
оператор-выбора
оператор-итерации
оператор-перехода
помеченный-оператор:
идентификатор : оператор
case константное-выражение
default : оператор
:
оператор
составной-оператор:
{ список-объявленийоР1 cnucoK-onepamopoeopt }
список-объявлений:
объявление
список-объявлений объявление
список-операторов:
оператор
список-операторов оператор
оператор-выражение:
выражение0Р1 ;
939
Синтаксис С
оператор-выбора:
if ( выражение ) оператор
if ( выражение ) оператор else оператор
switch ( выражение ) оператор
оператор-итерации:
while ( выражение ) оператор
do оператор while ( выражение ) ;
for ( выражениеорг; выражениеорг; выражение0Р1) оператор
оператор-перехода:
goto идентификатор
continue
break
;
;
;
return выражениеорг ;
A.2.4. Внешние
определения
модуль-трансляции:
внешнее-объявление
модуль-трансляции внешнее-объявление
внешнее-объявление:
определение-функции
объявление
определение-функции:
спецификаторы-объявленияорг декларатор список-объявлений0рг
ставной-оператор
A.3. Директивы препроцессора
препроцессорный-файл:
epynnaopt
группа:
часть-группы
группа часть-группы
часть-группы:
рр-лексемы0Р1 новая-строка
раздел-if
управляющая-строка
раздел-if:
if-группа elif-2pynnbiopt else-2pynnaopt endif-строка
if-группа:
# if
# ifdef
константное-выражение новая-строка группа0Р1
идентификатор новая-строка группаорг
# ifndef идентификатор новая-строка zpynnaopt
co-
940
Приложение
А
elif-группы:
elif-группа
elif-группы elif-группа
elif-группа:
# elif
константное-выражение новая-строка группа0Р1
else-группа:
# else
новая-строка группаорг
endif-строка:
# endif
новая-строка
управляющая-строка:
# include рр-лексемы новая-строка
# define
идентификатор список-замены
новая-строка
#
define
идентификатор lparen список-идентификаторов0рг )
#
undef
идентификатор
#
line
рр-лексемы новая-строка
#
error
список-замены
новая-строка
новая-строка
рр-лексемы0Р1 новая-строка
# pragma
#
рр-лексемьiopt новая-строка
новая-строка
lparen:
символ левой
скобки, которому
не
предшествует пробельный
список-замены:
рр-лексемЫорг
рр-лексемы:
препроцессорная-лексема
рр-лексемы
препроцессорная-лексема
новая-строка:
символ новой строки
символ
п
и
p
л
ж
о
e
н
и
e
Б
Стандартная библиотека
Б.1. Ошибки <errno.h>
EDOM
ERANGE
Расширяются
в
препроцессора
константные
целочисленные
значениями,
ненулевыми
пригодны
для
выражения
с
отличающимися
использования
в
директивах
#if.
errno
Переменная
ному
типа
номеру
int,
значение
которой устанавливается равным
положитель¬
ошибки различными библиотечными функциями. Значение
rno равно нулю при запуске программы,
er¬
но никогда не устанавливается рав¬
ным нулю какой-либо библиотечной функцией. В программе, использующей
errno для контроля ошибки, следует установить значение errno равным нулю
перед вызовом библиотечной функции и проверить ее значение до следующего
вызова библиотечной функции. Библиотечная функция может сохранить зна¬
чение errno на входе и установить его равным нулю; исходное значение сохра¬
функции в
Ненулевое значение
няется до момента выхода из
том
все еще равна нулю.
errno может
лиотечной функцией независимо
rno
Б.2.
не
документировано
случае,
ошибки,
функции в
от наличия
описанием
этой
если
errno
в
это
время
быть установлено биб¬
если использование er¬
стандарте.
Стандартные определения <stddef.h>
NULL
Зависящая от реализации константа нулевого указателя.
Этот материал
ного
Националь¬
С, ANSI/ISO
является сокращением и адаптацией документа Американского
систем
Язык программирования
для
Стандарта
9899; 1990. Копии
Информационных
этого стандарта могут быть получены от
Института Стандартов
по
адресу:
11
West
42nd
Американского Национального
Street, New York, NY 10036.
Приложение Б
942
оffseto f(тип,
обозначение-элемента)
в
Расширяется
байтах
ние-элемента) от начала содержащей его
тип). Обозначение-элемента
ченного как
size__t,
(указанного как
выражение типа
константное
целочисленное
которого представляет смещение в
элемента
значение
обозначе¬
структуры заданного типа
(обозна¬
должно быть таким, что если объ¬
явлено
тип
static
t;
(t. обозначение-элемента) является адресная кон¬
(Если специфицированный элемент является битовым полем, то пове¬
не определено).
то значением выражения &
станта.
дение
ptrdiff__t
Знаковый целочисленный
тип
size__t
Беззнаковый целочисленный
wchar__t
Целочисленный
тип
диапазон
тип,
вычитания
результата
результата
значений
двух
операции
указателей,
sizeof.
может
которого
представлять
набора символов,
нулевой символ будет иметь
уникальные коды для всех элементов наиболее широкого
определенного среди поддерживаемых локалов;
код
со
значением
и
нуль
каждый
элемент
базового набора символов будет
иметь значение кода, равное его значению при использовании как одиночного
целой символьной
в
символа
константе.
Б.З. Диагностика <assert.h>
void assert
(int expression)
assert
реализует диагностику в программах. Если в момент его испол¬
Макрос
нения expression равно false, макрос assert записывает информацию о данном
вызове (включая текст аргумента, имя исходного файла и номер строки ис¬
ходного
последние являются также соответственно значениями мак¬
кода
и
LINE
) в стандартный файл ошибок в
формате. Выведенное сообщение может быть в сле¬
FILE
росов препроцессора
зависящем от реализации
дующей форме:
Assertion failed:
expression,
file
xyz,
line
nnn
Затем макрос assert вызывает функцию abort. Если в исходном
рый
включен
#define
то
Б.4.
все
assert.h,
директива
файле,
в кото¬
препроцессора
NDEBUG
макросы
Обработка
Функции
имеется
в
assert
данном
символов
файле игнорируются.
<ctype.h>
в этом разделе возвращают ненулевые значения
(true)
в том, и толь¬
ко в том случае, когда значение аргумента с соответствует категории, опреде¬
ленной
в
описании
int isalnum
Проверка
int
на
isalpha
Проверка
на
функции.
(int с);
символ,
для
равно true.
которого
isalpha
или
isdigit
которого
isupper
или
islower равно true.
(int с);
символ,
для
Стандартная библиотека
int iscntrl
Проверка
int
(int с);
isdigit
int
Проверка
Проверка
isprint
Проверка
int
печатаемый
символ,
за
исключением
пробела (
').
(int с);
являющийся буквой
символ,
нижнего
регистра.
(int с);
на
int ispunct
символ.
(int с);
на
Проверка
которых
десятично-цифровой
на
int islower
символ.
(int с);
на
isgraph
int
управляющий
на
Проверка
943
печатаемый
символ,
включая
пробел (' ').
(int с);
на печатаемый символ,
отличный от
пробела ('
)
и
символов,
для
isalnum равно true.
isspace
Проверка
(int с);
символ,
относящийся к стандартным пробельным символам.
Стандартными пробельными символами являются: пробел ( ), перевод стра¬
на
('VO. новая строка ( \п )» возврат каретки ('\r'),
('\t') и вертикальной ('\v') табуляции.
ницы
ной
int isupper
Проверка
на
(int с);
(int с);
на
int tolower
являющийся буквой верхнего регистра.
символ,
int isxdigit
Проверка
символы горизонталь¬
символ
шестнадцатеричной цифры.
(int с);
в нижний. Если аргументом является
символ, для которого isupper равно true и существует соответствующий сим¬
вол, для которого islower равно true, то функция tolower возвращает соответст¬
Преобразование буквы верхнего регистра
вующий
int
символ; в противном случае аргумент возвращается без изменений.
toupper
(int с);
Преобразование буквы
символ,
для
нижнего регистра в
которого islower равно
верхний. Если аргументом является
существует соответствующий сим¬
true и
isupper равно true, то функция toupper возвращает соответ¬
ствующий символ; в противном случае аргумент возвращается без изменений.
вол, для которого
Б.5.
Локализация <locale.h>
LC_ALL
LC_COLLATE
LC__CTYPE
LC_MONETARY
LC__NUMERI С
LC_TIME
Расширяются
значениями,
в
целочисленные
константные
выражения
с
отличающимися
пригодны для использования в качестве первого аргумента
кции setlocale.
фун¬
Приложение Б
944
NULL
Зависящая от реализации константа нулевого указателя,
struct
lconv
элементы, связанные с форматированием числовых значений.
Структура должна содержать по крайней мере следующие элементы в любом
Содержит
порядке. В
локале
"С"
элементы должны иметь значения,
указанные
в
ком¬
ментариях.
/*
к
*
II
II
X
II
II
*
*mt_curr_symbol;
*currency_symbol;
*mon_decimal_point;
*mon_thousands_sep;
*mon_groupmg;
/*
/*
/*
/*
/*
/*
/*
м и
char
*pos1t1ve_s1gn;
/*
char
*negative_sign;
/*
char
char
mt_frac_digits ;
frac_digits;
/*
*decimal_pomt;
char
char
char
char
char
char
char
char
*thousands_sep;
*groupmg;
и
*
j
j
I
I
j
I
j
IIII
*
IIII
*
II
II
*
II
II
*
I
II
II
X
II
II
*
j
I
/*
CHAR_MAX
CHAR_MAX
*/
*/
char p_cs_precedes;
char p_sep_by_space ;
char n_cs_precedes;
char n_sep_by_space;
/*
CHAR_MAX
*/
/*
/*
CHAR_MAX
CHAR_MAX
*/
/*
char p_sign_posn;
char n_sign_posn;
/*
/*
CHAR_MAX
CHAR_MAX
CHAR_MAX
*/
*/
char
*setlocale
(int
category,
const
*/
*/
char
*locale);
Функция setlocale выбирает часть локала программы, специфицированную
аргументами category и locale. Функция setlocale может быть использована
для
изменения
или опроса всего текущего локала программы или его части.
для category обозначает весь локал; другие значения для
специфицируют только часть локала программы. LC__COLLATE влияет на по¬
ведение функций strcoll и strxfrm. LC__CTYPE влияет на поведение функций
обработки символов и многобайтовых функций. LC__MONETARY влияет на
информацию о форматировании денежных значений, возвращаемую функ¬
цией localeconv. LC__NUMERIC влияет на символ десятичной точки для фун¬
кций форматированного ввода/вывода, функций преобразования строк и ин¬
формацию о форматировании не-денежных данных, возвращаемую localconv.
Значение
LC__TIME
LC__ALL
влияет
на
поведение
strftime.
Значение "С" для locale специфицирует
минимальные
установки для
транс¬
""
С; значение
специфицирует естественные установки для данной реа¬
лизации. Функции setlocale могут быть переданы и другие строки в зависимо¬
ат
сти
При запуске программы производятся действия,
реализации.
эквивалентные вызову функции
лятора
setlocale
(LC_ALL, "С").
на строку и данный выбор может быть удов¬
функция setlocale возвращает указатель на строку, ассоцииро¬
указанным значением category для нового локала. Если данный вы¬
Если для locale задан указатель
летворен,
ванную с
бор
null
то
не может
и
локал
быть удовлетворен, то функция setlocale возвращает указатель
программы
не
изменяется.
Стандартная библиотека
945
Нулевой
указатель для locale заставляет функцию setlocale возвратить указа¬
тель на строку, ассоциированную со значением category для текущего локала
который при
программы,
этом
не
изменяется.
возвращаемый функцией setlocale, таков, что последую¬
ассоциированной с ним категорией вос¬
становят соответствующую часть локала программы. Строка, на которую ссы¬
лается указатель, может модифицироваться программой,
но последующий
Указатель на строку,
щий
вызов с этим значением строки и
функции setlocale
вызов
struct
lconv
может
*localeconv
переписать
ее.
(void);
Функция localeconv устанавливает компоненты объекта типа struct lconv со
значениями, предназначенными для форматирования числовых величин (де¬
нежных
и
других)
в
Элементы структуры
соответствии
типа
char
*
правилами
локала.
текущего
являются указателями на
decimap__point)
которых (исключая
с
строки, любой из
может указывать на пустую строку
"",
по¬
казывающую, что значение не доступно в текущем локале или имеет нулевую
Элементы
длину.
которых
в
текущем
char
типа char являются неотрицательными числами, каждое из
быть CHAR__MAX, показывающую, что значение недоступно
локале. В число элементов входят:
может
*decimalj?oint
Символ
«десятичной точки», используемый для форматирования не-де-
нежных
величин.
char
*thousands__sep
Символ, используемый
кой
в
для отделения групп
форматированных
цифр перед десятичной
точ¬
не-денежных величинах.
char
*grouping
Строка, элементы которой представляют размер каждой
группы цифр в
форматированных не-денежных величинах.
char
*int__curr__symbol
Международное обозначение
валюты,
применяемое
в
текущем
локале.
три символа строки содержат алфавитные международные обозна¬
чения валюты в соответствии со спецификацией ISO 4217:1987. Четвертый
Первые
(непосредственно предшествующий нулевому символу) использует¬
ся для отделения обозначения валюты от денежной величины.
символ
char
*currency__symbol
Обозначение валюты, применяемое
char
в
текущем
локале.
*mon__decimalj?oint
Символ десятичной точки, используемый при форматировании денежных
величин.
char
*mon__thousands_sep
Разделитель
нии
для групп
денежных
цифр перед десятичной точкой при форматирова¬
величин.
char
*mon__grouping
Строка, элементы которой представляют размер каждой группы цифр
форматируемых
31
Зак. 801
денежных величинах.
в
946
Приложение Б
char
*positive_sign
Строка, используемая для
индикации неотрицательных значений форма¬
тируемых денежных величин.
char
*negative_sign
Строка, используемая для
индикации отрицательных значений формати¬
руемых денежных величин.
char
int__frac__digits
Количество цифр
мых
char
при
в
дробной
«международном»
части
(после десятичной точки), отображае¬
форматировании
денежных величин.
frac__digits
Количество цифр
мых
в
в дробной части (после десятичной точки), отображае¬
форматированной денежной величине.
char
p__csj?recedes
При установке в 1 или
0
currency__symbol
выводится соответственно впере¬
ди или после значения для неотрицательных форматированных денежных
величин.
char
p__sep__by__space
При установке в 1 или
не отделяется
ных
пробелом
денежных
0
currency__symbol
от
соответственно отделяется или
значения для неотрицательных
форматирован¬
величин.
char
n_cs_precedes
При установке в 1 или
0
currency__symbol
выводится соответственно впере¬
ди или после значения для отрицательных
форматированных
денежных
величин.
char
n__sep__by__space
При установке в 1 или
не
отделяется
от
0
currency__symbol
значения
для
соответственно отделяется или
отрицательных
форматированных
денеж¬
ных величин.
char
p__sign_j?osn
Устанавливается
равным значению, показывающему позицию positive__sign для неотрицательных форматированных денежных величин.
char
n__sign_posn
Устанавливается
равным
значению,
показывающему
позицию
ve__sign для отрицательных форматированных денежных
Элементы grouping
и
mon__grouping
negati-
величин.
интерпретируются следующим образом:
CHAR_MAX
Далее
0
Предыдующий элемент должен повторяться для всех остав¬
шихся цифр
другое
Целое
производить группирование не требуется.
значение, равное числу цифр, которое содержит теку¬
щая группа. Для определения размера группы цифр перед те¬
кущей группой проверяется следующий элемент.
947
Значения p__sign_posn
и
n__sign__posn
интерпретируются следующим
0
Величина
1
Знаковая
2
Знаковая строка следует за величиной
3
Знаковая
4
Знаковая строка непосредственно следует за
и
currency__symbol
строка
заключаются
предшествует
величине
и
и
в
круглые
скобки
currency__symbol
currency__symbol
строка непосредственно предшествует
Функция localeconv
образом:
currency__symbol
currency__symbol
возвращает указатель заполненного объекта.
Структура,
возвращаемый функцией указатель, не должна моди¬
фицироваться программой, но может быть переписана последующим вызовом
функции localeconv. Кроме того, вызовы функции setlocale с категориями
на которую
ссылается
LC__ALL, LC__MONETARY
LC__NUMERIC
или
могут переписывать содержи¬
мое структуры.
Б.6. Математика <math.h>
HOGE_VAL
Символическая
константа, представляющая положительное выражение типа
double.
double
acos(double x);
Вычисление
главного
значения
находящихся в диапазоне
функции arccos(x). В случае аргументов,
[-1,4-1],
не
происходит ошибка области определения.
Функция acos возвращает значение арккосинуса в диапазоне
[0,л]
радиан.
double
asin(double x);
Вычисление главного значения функции arcsin(x). В случае аргументов, не
находящихся в диапазоне [-1,4-1], происходит ошибка области определения.
Функция asin возвращает значение арксинуса в диапазоне [-rc/2,+rc/2] радиан.
double
atan(double x);
Вычисление
значение
главного значения
арктангенса
в
функции arctg(x). Функция atan возвращает
диапазоне
[-rc/2,+rc/2]
радиан.
atan2(double у, double x);
Функция atan2 вычисляет главное значение arctg(y/x),
double
используя знаки обо¬
их аргументов для определения квадранта возвращаемого значения. Ошибка
области определения может появиться, если оба аргумента равны нулю. Фун¬
кция atan2 возвращает значение арктангенса
double
у/х
в диапазоне
[-л,+я]
радиан.
cos(double x);
Вычисление cos(x) (аргумент
в
радианах).
в
радианах).
double sin(double x);
Вычисление sin(x) (аргумент
double tan(double x);
Возвращает
значение
tg(x) (аргумент
в
радианах).
double cosh(double x);
Вычисление гиперболического
возникает ошибка диапазона.
31*
косинуса x. Если величина x слишком велика,
948
Приложение
double
x);
sinh(double
Вычисление гиперболического синуса x. Если величина
возникает ошибка диапазона.
double
tanh(double
exp(double
x
слишком
велика,
x);
Вычисление гиперболического тангенса
double
Б
x.
x);
Вычисление экспоненциальной функции
лика, возникает ошибка диапазона.
от x.
Если величина
x слишком
frexp(double value, int *exp);
Разделение числа с плавающей точкой на нормализованную дробь
ве¬
double
степень
2. Сохраняет целую часть
frexp
ция
интервале
ldexp(double
Произведение
происходить
на
2
double
в
объекте
ошибка
int
x,
exp);
плавающей точкой и целой степени 2. Может
диапазона. Функция ldexp возвращает произведение x
exp.
log(double
double
целую
с
числа
степени
x);
x.
Если аргумент является отрицатель¬
возникает ошибка области определения.
никнуть,
и
int, указываемом exp. Функ¬
нулю, то обе части результата равнЫ нулю.
Вычисление натурального логарифма
ным,
типа
возвращает значение x такое, что x является double со значением в
[1/2, 1 ] или ноль, а value равно произведению x на 2 в степени
*exp. Если value равно
double
в
если
аргумент
равен
Ошибка диапазона
может воз¬
нулю.
loglO(double x);
Вычисление десятичного логарифма x. Если аргумент является отрицатель¬
ным, возникает ошибка области определения. Ошибка диапазона может воз¬
никнуть, если аргумент равен нулю.
double
modf(double
value,
double
*iptr);
Разделение
аргумента value на целую и дробную части, каждая из которых
имеет тот же знак, что и аргумент. Сохраняет целую часть как double в объек¬
те, на который указывает
lue
со
iptr. Функция modf возвращает дробную
часть va¬
знаком.
double pow(double
x,
double
у);
Ошибка области определения происходит, если x
является отрицательным, а у имеет нецелое значение. Ошибка области опре¬
деления происходит также, если результат не может быть представлен, когда
Вычисление
x
в
степени
у.
x равно нулю и у меньше или равно нулю.
Может возникать ошибка диапазо¬
на.
double
sqrt(double
x);
Вычисление неотрицательного квадратного корня из x. Если аргумент
ся отрицательным, происходит ошибка области определения.
double
ceil(double
x);
Вычисление наименьшего целого значения, не меньшего x.
являет¬
949
double
fabs
(double x);
Вычисление абсолютного
значения
числа
x
с
плавающей точкой.
double floor(double x);
Вычисление наибольшего целого
double у);
fmod(double x,
double
Вычисление
остатка
от
значения, не превосходящего x.
х/у
с
плавающей точкой.
Б.7. Нелокальные переходы
<setjmp.h>
jmp_buf
Тип массива, используемый для хранения информации, необходимой для вос¬
становления исходного состояния окружения.
setjmp(jmp__buf env)
int
Сохраняет
исходное
нейшего использования
longjmp,
Активация макроса
окружения
в
jmp_buf
аргументе
для
позд¬
функцией Iongjmp.
Если возврат происходит
setjmp возвращает нулевое
зова функции
;
состояние
непосредственного вызова, то макрос
значение. Если возврат происходит вследствие вы¬
макрос setjmp возвращает ненулевое значение.
вследствие
то
setjmp
должна происходить только в одном из следую¬
щих контекстов:
управляющее выражение оператора
макрос
выбора
или
цикла;
один из операндов операции сравнения или равенства, с другим
макрос
целочисленным константным выражением, причем получен¬
ное выражение является управляющим выражением оператора выбора или
операндом
цикла;
макрос
операнд унарной операции !, причем полученное выражение яв¬
ляется управляющим выражением оператора выбора или цикла; или
оператор-выражение.
макрос
void
longjmp(jmp__buf
env,
int
vai) ;
Восстановление окружения, сохраненного самым последним вызовом в про¬
грамме макроса setjmp с соответствующим jmp__buf-apryMeHTOM. Если не
было
такого
setjmp,
Все
вызова
закончилось
доступные
longjmp,
или
к
выполнение
данному
объекты
имеют
те
за исключением значений
нения, локальных для
функции, содержащей
моменту,
же
то
поведение
значения,
объектов
что
не
и
вызов
макроса
определено.
во
время
вызова
с автоматическим периодом хра¬
функции, содержащей
вызов соответствующего макро¬
setjmp, не относящихся к типу volatile и изменившихся между
setjmp и longjmp; эти значения являются неопределенными.
са
вызовами
Поскольку longjmp обходит обычные механизмы вызова и возврата из функ¬
ции, она будет исполняться корректно в контексте прерываний, сигналов и
всех ассоциированных с ними функций. Однако, если функция longjmp вызы¬
вается из вложенного обработчика сигнала (т.е. из функции, вызванной в ре¬
зультате возбуждения сигнала в процессе обработки другого сигнала), то пове¬
дение
longjmp
не
определено.
longjmp программа продолжит исполнение, как только со¬
ответствующий макрос setjmp вернет значение, специфицированное как val.
Функция longjmp не может вынудить макрос setjmp вернуть значение 0; если
После завершения
val равно нулю, то
setjmp
возвращает значение 1.
950
Приложение Б
Б.8.
Обработка
сигналов
<signal.h>
sig_atomic_t
Целочисленный
величине
даже
тип
при
объекта,
обращаться
прерываний.
к которому можно
наличии
асинхронных
как к
атомарной
SIG_DFL
SIGJERR
SEG_IGN
Расширяются
рые
в константные выражения с отличающимися значениями, кото¬
имеют тип,
нием
совместимый со
функции signal,
каким адресом
росов,
каждый
вторым аргументом и возвращаемым
значе¬
и чьи значения не могут сравниваться как равные ни с
объявляемой функции;
а также ни с каким из следующих мак¬
из которых расширяется в положительное целочисленное кон¬
стантное выражение, являющееся номером сигнала для указанного условия:
SIGABRT
аварийное завершение,
цией abort
SIGFPE
ошибочная арифметическая операция,
аналогичное
функ¬
инициируемому
такая, как деление на
ноль или вызывающая переполнение результата
обнаружение недействительного образа функции,
SIGILL
такого, как
запрещенная инструкция
SIGINT
получение интерактивного сигнала
SIGSEGV
недействительный доступ
SIGTERM
запрос
Реализация
не
исключением
void
завершения,
нуждается
случая
вызова
void
один из трех способов
памяти
посланный
генерировании
явного
(*signal(int sig,
Выбирает
в
к
программе
каких-либо из
этих
сигналов,
за
функции raise.
(*func)(int)))(int);
обработки
полученного впоследствии сигнала
sig. Если значением func является SIG__DFL, то для сигнала будет
осуществляться обработка по умолчанию. Если значением func является
SIG__IGN, то сигнал будет игнорирован. В других случаях func будет указы¬
с номером
вать на
вается
Когда
функцию, вызываемую при получении сигнала.Такая функция
обработчиком
вызы¬
сигнала.
появляется сигнал и
func указывает
на
функцию,
то сначала выполня¬
signal(sig, SIG__DFL); или производится определяе¬
мая реализацией блокировка сигнала. (Если значение sig равно SIGILL, то ре¬
ализацией определяется, будет ли выполнена обработка для SIG__DFL.) Далее
выполняется следующий эквивалент (*func) (sig). Функция func может завер¬
шиться выполнением оператора return, вызовом abort, exit или longjmp.
Если func выполняет оператор return и значение sig было SIGFPE или ка¬
ется
эквивалент
вызова
кое-либо другое определяемое реализацией значение, соответствующее
вычис¬
лительным
случае
программа
исключениям,
будет
то
поведение
не
определено.
В
противном
продолжать исполнение от точки прерывания.
Если сигнал возникает как-либо иначе, чем в результате вызова функции abort
или raise, поведение не определено, если обработчик сигнала вызывает какую-либо функцию стандартной библиотеки помимо самой функции signal (с
номером сигнала, соответствующим тому, что вызвал
первым аргументом
активацию
периодом
обработчика)
хранения
или ссылается на
как-либо
иначе,
чем
какой-нибудь объект
путем
менной типа
voliatile sig__atomic__t. Более того,
nal приведет
к возврату
SIG__ERR,
присваивания
со статическим
значения
то значение errno
пере¬
функции sig¬
будет неопределенным.
если такой вызов
Стандартная библиотека
При
951
запуске программы может быть выполнен эквивалент функции
SIG_IGN);
signal(sig,
выбранных в соответствии с особенностями реализа¬
ции; для всех других сигналов, определяемых реализацией, выполняется эк¬
вивалент функции
для некоторых сигналов,
signal(sig,
SIG_DFL);
Если запрос может быть удовлетворен,
ние func для самого последнего вызова
В
то
со
других случаях возвращается значение
положительное
int raise
(int
функция signal возвращает значе¬
специфицированным сигналом sig.
SIG_JERR
и в errno устанавливается
значение.
sig);
sig исполняемой программе. Функция raise
возвращает ноль в случае успешного завершения и ненулевое значение
при
Функция raise
посылает сигнал
неуспешном.
Б.9.
Переменные
списки
аргументов
<stdarg.h>
va__list
Тип, пригодный для хранения информации, требуемой макросами va_start,
va_arg и va_end. Если нужен доступ к переменному списку аргументов, то
вызываемая функция должна объявить объект (обозначаемый
ар), имеющий
в этом разделе
va_list. Объект
ар может быть передан как аргумент
другой функции; если эта функция вызывает макрос va__arg с параметром ар,
то значение ар в вызывающей функции будет неопределенным и должно быть
как
передано макросу
void
тип
va_end
va_start(va_list
Должна быть
до любой
ар,
другой
ссылки
на
ар.
parmN);
обращения к неименованным аргумен¬
Макрос va_start инициализирует ар для последующего использования в
va_arg и va__end. Параметр parmN является идентификатором крайнего пра¬
вого параметра в переменном списке аргументов в определении функции (т.е.
первого перед многоточием). Если параметр объявлен с классом памяти regis¬
ter, с типом функции или массива, или с типом, не совместимым с тем, что
вызвана до какого-либо
там.
получается после применяемого по умолчанию возведения аргументов, то по¬
не
определено.
type va__arg
(va_list
ведение
Расширяется
в
ар,
выражение,
type) ;
которое
имеет
тип
и
значение следующего
аргу¬
Параметр ар должен быть тем же, что и va_list, инициализи¬
рованный функцией va__start. Каждый вызов va_arg модифицирует ар так,
что по очереди возвращаются значения последовательных аргументов. Пара¬
метр type является именем типа, специфицированного так, что тип указателя
на объект, который имеет указанный тип, может быть получен просто приме¬
нением постфиксной операции * к type. Если нет следующего аргумента или
type не совместим с типом следующего аргумента (после возведения типа, про¬
изводимого по умолчанию), то поведение не определено. Первый вызов макро¬
са va_arg после вызова va_start возвратит значение аргумента, следующего
за указываемым parmN. Последовательные вызовы возвращают значения
мента в вызове.
оставшихся
аргументов
по
порядку.
952
Приложение Б
void
va_end (va__list ар)
;
Упрощает нормальный возврат из функции, переменный список аргументов
которой был указан в va_start, расширением которого был инициализирован
объект va_list ар. Макрос va_end может модифицировать ар так, что его не¬
льзя будет использовать (без промежуточного вызова va_start). Если нет соот¬
ветствующего вызова макроса va_start, или перед возвратом не вызван мак¬
рос
Б.10.
va_end,
то
поведение
Ввод/вывод
__IOFBF
__IOLBF
__IONBF
Целочисленные
не
определено.
<stdio.h>
константные выражения с отличающимися значениями, при¬
годные для использования в качестве третьего аргумента
функции setvbuf.
BUFSIZ
Целочисленное константное выражение,
пользуемого функцией setbuf.
представляющее размер
буфера,
ис¬
EOF
Отрицательное целочисленное константное выражение, которое возвращается
несколькими функциями для указания конца файла, т.е. окончания ввода из
потока.
FILE
объекта, способный
информацию, необходимую для
файла, указатель на ассо¬
циированный с ним буфер (если имеется), индикатор ошибки, в который за¬
писывается, были или нет ошибки чтения/записи, и индикатор конца файла,
регистрирующий, достигнут ли конец файла.
Тип
записывать
всю
управления потоком, включая индикатор позиции
FILENAME_MAX
Целочисленное
константное выражение, значение которого представляет раз¬
мер массива типа
файла, который
char,
может
достаточного для хранения наибольшей строки имени
быть открыт
в
данной реализации.
FOPEN_MAX
Целочисленное константное выражение, представляющее минимальное число
файлов, которое гарантированно может быть одновременно открыто в данной
реализации.
fpos_t
Тип объекта, способный записывать всю информацию, необходимую для уни¬
кального указания каждой позиции в файле.
L_tmpnam
Целочисленное константное выражение, представляющее необходимый раз¬
мер для массива типа char, достаточного для хранения строки с именем вре¬
менного файла, генерируемым функцией tmpnam.
NULL
Определяемая реализацией
константа нулевого
указателя.
953
SEEK_CUR
SEEKJEND
SEEK_SET
Целочисленные
константные выражения с отличающимися значениями, при¬
годные для использования в качестве третьего аргумента
size__t
Беззнаковый целочисленный
тип
результата
операции
функции fseek.
sizeof.
strderr
типа
Выражение
связанный
со
«указатель
стандартным
на
FILE», которое ссылается на
сообщений об ошибках.
объект
FILE», которое указывает
на
объект FILE,
на
объект
FILE,
потоком
stdin
Выражение
типа
«указатель
на
связанный со стандартным потоком ввода.
stdout
Выражение типа «указатель на FILE», которое указывает
связанный
со
потоком
стандартным
TMP_MAX
Целочисленное
FILE,
вывода.
константное выражение, представляющее минимальное число
файлов, которые могут быть генерированы функцией
tmpnam. Значение макроса TMP_MAX должно быть равно, по крайней
мере, 25.
имен
уникальных
int remove(const char *filename);
Делает файл
этим
вызовет отказ,
Последующая
если только
функции
поведение
щает ноль,
указанной filename, более не доступным под
этот файл по данному имени
он не будет создан заново. Если файл открыт, то
зависит от реализации. Функция remove возвра¬
с именем в строке,
именем.
remove
попытка открыть
если операция завершена успешно,
и ненулевое значение при от¬
казе.
int rename(const char
*o!d,
const char
Делает файл, имя которого содержится
*new);
указываемой old, известным
строкой, указываемой new. Файл с именем old
является более недоступным под данным именем. Если файл с именем в стро¬
ке, указываемой new, существует до вызова функции rename, то ее поведение
зависит от реализации. Функция rename возвращает ноль, если операция за¬
в строке,
впредь под именем, заданным
вершена успешно, и ненулевое значение при отказе, в случае чего существую¬
щий файл
остается
под
исходным
именем.
*tmpfile(void);
Создает временный двоичный файл, который будет
FILE
автоматически удален при
его закрытии или при завершении программы. Если программа завершается
аварийно,
открытый временный файл, зависит от реализации.
модификации в режиме "wb+". Функция tmpfile воз¬
указатель на поток созданного файла. Если файл не может быть со¬
функция tmpfile возвращает нулевой указатель.
то останется ли
Файл открывается для
вращает
здан,
то
9S4
Приложение Б
char
*tmpnam(char
*s);
с допустимым именем файла, которое не
файла. Функция tmpnam генерирует раз¬
Функция tmpnam генерирует строку
совпадает с именем существующего
личные
строки
превысило
при
каждом
TMP_JVLAX,
Если аргументом
то
является
вызове,
до
поведение
TMP__MAX
функции
нулевой указатель,
раз. Если число вызовов
зависит
то
от
реализации.
функция tmpnam
оставляет
свой результат во внутреннем статическом объекте и возвращает указатель на
этот объект. Последующие вызовы функции tmpnam могут модифицировать
данный объект. Если аргумент не является нулевым указателем, предполага¬
ется,
что
он
указывает
на
массив
символов
размером
не
менее
L_tmpnam;
функция tmpnam
записывает результат в этот массив и возвращает аргумент
в
значения.
int
качестве
своего
fclose(FILE
*stream);
Функция fclose сбрасывает поток, указываемый параметром stream,
ассоциированный
файл. Любые
и закры¬
буферизованные
данные потока передаются системному окружению для записи в файл; все не¬
прочитанные буферизованные данные отбрасываются. Поток отсоединяется от
файла. Если ассоциированный буфер был выделен автоматически, то он осво¬
бождается. Функция fclose возвращает ноль, если поток был успешно закрыт,
или EOF в случае обнаружения ошибок.
вает
ним
с
незаписанные
int fflush(FILE *stream);
Если stream
указывает на выходной поток или модифицируемый поток, на
котором самая последняя операция не была вводом, то функция fflush застав¬
ляет передать все незаписанные данные этого потока системному окружению
для записи в файл; в других случаях поведение не определено.
Если stream является нулевым указателем, то функция flush выполняет опе¬
сброса на всех потоках, для которых ее поведение определено
Функция flush возвращает EOF, если произошла ошибка записи, и
рацию
противном
FILE
выше.
ноль
в
указываемой filename,
и
случае.
*fopen(const
char
ассоциирует с ним поток.
const
*filename,
Функция fopen открывает файл
char
с именем в строке,
Аргумент mode
*mode);
указывает на строку, начинающую¬
ся с одной из следующих последовательностей:
r
открыть текстовый файл для чтения
w
усечь файл до нулевой длины или создать текстовый файл для
записи
а
дополнить; открыть или создать текстовый
данных в конец файла
rb
открыть двоичный файл
wb
усечь
файл
до
нулевой
файл
для записи
для чтения
длины или создать двоичный
файл
для
записи
ab
дополнить; открыть или создать двоичный
данных в конец файла
r+
открыть текстовый файл для обновления (чтение и
w+
усечь файл до
обновления
a+
дополнить; открыть или создать текстовый
нулевой
файл
для записи
длины или создать текстовый
ния, записывая данные в конец
файл
запись)
файл
для
для обновле¬
Стандартная библиотека
95S
r+b
или
rb+ открыть
w+b
или
wb+ усечь
двоичный файл для обновления (чтение
файл
нулевой
до
запись)
и
длины или создать двоичный
файл
для
обновления
дополнить; открыть или создать двоичный
ния, записывая данные в конец
a+b
Открытие файла
отказ,
файла
режиме
(V
в режиме чтения
если
вызовет
файл
не
('a'
первый
как
существует
или
не
символ
может
файл
для обновле¬
mode)
Открытие
mode) на¬
в аргументе
читаться.
первый символ в аргументе
файл в текущий конец файла, вне зависи¬
мости от возможных вызовов функции fseek. В некоторых реализациях от¬
крытие двоичного файла в режиме дополнения ('b' как второй или третий
в
дополнения
как
правляет все последующие записи в
символ в вышеприведенном списке значений аргумента
mode)
может устано¬
для потока за пределами
последних записанных данных из-за дополнения нулевыми символами.
файла
вить начальное значение индикатора позиции
Когда
файл открыт
обновления ( +
в режиме
второй
mode), то
как
вышеприведенном списке значений аргумента
потоке может производиться как ввод, так и вывод.
посредственно
fflush
или
следовать
за
выводом
или
третий
Однако ввод
без промежуточного
не может не¬
вызова
функции позиционирования файла (fseek, fsetpos
вод также не может непосредственно следовать за вводом
символ в
на ассоциированном
или
функции
rewind);
вы¬
без промежуточного
функций позиционирования, если только в операции ввода не встре¬
файла. Открытие (или создание) текстового файла в режиме об¬
новления может в некоторых реализациях вместо этого открыть (или создать)
двоичный поток.
вызова
тится конец
Когда
поток открыт,
он полностью
буферизуется
тогда и только тогда, когда
он с определенностью не ссылается на интерактивное устройство. Индикаторы
ошибки и конца файла для потока очищаются. Функция fopen возвращает
указатель на управляющий объект потока. Если при открытии происходит от¬
каз, то fopen возвращает нулевой указатель.
FILE
*freopen(const
FILE
char
*filename,
const
char
*mode,
*stream);
Функция freopen открывает файл с именем в строке, на которую указывает fi¬
lename, и ассоциирует с ним поток, на который указывает stream. Аргумент
mode
используется так же, как в функции
Функция freopen
fopen.
сначала пытается закрыть любой
указанным потоком.
файл, ассоциированный
с
Неудача
при закрытии файла игнорируется. Индикато¬
ры ошибки и конца файла для потока очищаются. Функция freopen возвра¬
щает нулевой указатель, если операция открытия закончилась неудачно. В
других случаях freopen возвращает значение stream.
void
setbuf(FILE
*stream,
char
*buf);
Функция setbuf эквивалентна функции setvbuf, вызванной со значениями
__IOFBF для mode и BUFSIZ для size или (если buf является нулевым указате¬
лем) со значением __IONBF для mode. Функция setbuf ничего не возвращает.
int
setvbuf(FILE
Функция
setvbuf
*stream,
char
*buf,
int mode,
size_t size);
может быть использована только после того,
который указывает stream, будет ассоциирован
как поток,
на
файлом и до вы¬
полнения каких-либо других операций с потоком. Аргумент mode определяет,
как поток будет буферизован: при _IOFBF ввод/вывод будет полностью буфе¬
ризован; при _IOLBF ввод/вывод будет буферизован построчно; при _IONBF
ввод/вывод не буферизуется. Если buf не является нулевым указателем, то
с открытым
Приложение Б
956
буфера, выделенного функцией setvbuf может использоваться массив,
который указывает buf. Аргумент size специфицирует размер массива. Со¬
держание массива в любой момент является неопределенным. Функция setvbuf
возвращает ноль при успешном завершении и ненулевое значение, если задано
вместо
,
на
значение
неверное
mode
для
или
fprintf(FILE *stream,
int
запрос
const
не
может
char
быть удовлетворен.
...);
*format,
указываемый stream, под управ¬
лением строки, указываемой format, которая специфицирует, как последую¬
щие аргументы будут преобразованы при выводе. Если для формата недоста¬
точно аргументов, то поведение не определено. Если формат исчерпан, а
аргументы остались, то оставшиеся аргументы оцениваются (как обычно), но
в остальных отношениях игнорируются. Функция fprintf возвращает управ¬
ление при достижении конца строки формата. См. главу 9, «Форматирован¬
ный ввод/вывод», где приведены детали спецификации преобразований при
выводе. Функция fprintf возвращает число переданных символов или отрица¬
Функция
тельное
int
fprintf
записывает вывод в поток,
значение
fscanf(FILE
возникновении
при
*stream,
const
ошибки
char
вывода.
*format,
...);
который указывает stream, под
управлением строки, указываемой format, которая специфицирует допусти¬
мые входные последовательности и то, как они будут преобразованы для при¬
сваивания, используя последующие аргументы как указатели на объекты, по¬
лучающие
преобразованный ввод. Если для формата недостаточно
Функция fscanf
считывает ввод из потока, на
аргументов, то поведение не определено. Если формат исчерпан, а аргументы
остались, то оставшиеся аргументы оцениваются (как обычно), но в остальном
См. главу 9, «Форматированный ввод/вывод», где приведены
детали спецификации преобразований при вводе.
игнорируются.
Функция scanf возвращает значение макроса EOF,
если
ошибка ввода прои¬
преобразований. В
противном случае функция fscanf
возвращает количество присвоенных элементов, которое может быть меньше,
чем предусматривалось, или даже равно нулю в случае ранней неудачи при
зошла
до
каких-либо
сопоставлении формата.
printf(const char *format,
int
...);
stdout, помещенным перед
аргументами функции printf. Функция printf возращает количество выведен¬
Функция printf эквивалентна fprintf
с аргументом
ных символов или отрицательное значение, если произошла
int
scanf(const
char
*format,
ошибка вывода.
...);
Функция scanf эквивалентна функции fscanf с аргументом stdin, помещен¬
scanf. Функция scanf возвращает значение макроса
каких-либо преобразований произошла ошиб¬
scanf возвращает число введенных элементов, которое
ным перед аргументами в
EOF,
если при вводе до начала
ка. В противном случае
может быть меньше, чем предусматривалось, или даже нулю в случае
неудачи при
сопоставлении
int sprintf(char *s,
ранней
формата.
const char *format,
Функция sprintf эквивалентна fprintf за
специфицирует массив, в который вместо
...);
исключением
того,
что
аргумент
s
будет производиться вывод.
нулевой символ; он не учитывает¬
потока
В конце записанных символов помещается
ся при подсчете возвращаемого количества выведенных символов. Поведение
при копировании между перекрывающимися объектами не определено. Функ¬
sprintf возвращает количество символов, записанных в массив, не считая
ограничивающего нулевого символа.
ция
957
int
sscanf(const char
const char
*s,
Функция sscanf эквивалентна fscanf
специфицирует строку,
из
которой
за
*format,
исключением
...);
того,
что
s
аргумент
Дости¬
вместо потока производится ввод.
конца строки эквивалентно появлению конца файла для функции
fscanf. Поведение при копировании между перекрывающимися объектами не
жение
определено.
Функция sscanf возвращает
EOF,
значение макроса
если при вводе до начала
каких-либо преобразований произошла ошибка. В противном случае функция
sscanf возвращает количество введенных тем, которое может быть меньше,
чем предусматривалось, или даже равно нулю в случае
ранней ошибки
сопо¬
ставления.
int vfprintf(FILE
const
*stream,
Функция vfprintf эквивалентна fprintf
va__list arg);
*formar,
с заменой списка аргументов перемен¬
ной
длины аргументом arg, который инициализируется макросом va_start
(возможно, с последующими вызовами va_arg). Функция vfprintf не вызыва¬
va_end. Функция vfprintf возвращает количество переданных сим¬
волов или отрицательное значение, если при выводе произошла ошибка.
ет макрос
vprintf(const char *format, va_list arg);
Функция vprintf эквивалентна printf с заменой списка аргументов переменной
int
аргументом arg, который должен быть инициализирован макросом
va_start (возможно, с последующими вызовами va_arg). Функция vprintf не
вызывает макрос va_end. Функция vprintf возвращает количество переданных
длины
символов или отрицательное значение, если при выводе произошла ошибка.
vsprintf(char *s,
int
Функция vsprintf
const
char
ной длины аргументом arg,
va_start (возможно,
*format, va__list arg);
sprintf
который должен
эквивалентна
с заменой списка аргументов перемен¬
быть инициализирован макросом
с последующими вызовами
va_arg). Функция vsprintf
не
Если имеет место копирование между перекрываю¬
щимися объектами, то поведение не определено. Функция vsprintf возвраща¬
ет количество символов, записанных в массив, не считая ограничивающего
вызывает макрос
нулевого
int
va_end.
символа.
fgetc(FILE
*stream);
символ (если он есть) как unsigned char,
int, из потока ввода с указателем stream и перемещает ин¬
дикатор позиции файла, связанного с потоком (если он определен). Функция
fgetc возвращает следующий символ из потока ввода, указываемого stream.
Если поток находится в конце файла, то для потока устанавливается индика¬
тор конца файла и fgetc возвращает EOF. Если произошла ошибка чтения, то
Функция fgetc получает следующий
преобразованный
в
для потока устанавливается индикатор ошибки и
char
*fgets(char *s,
Функция
fgets
int
n,
FILE
fgetc
возвращает EOF.
*stream);
считывает не более n символов из потока, указываемого stre¬
указываемый s. Никаких дополнительных символов после сим¬
(который не передается) или после конца файла не считы¬
вается.
Непосредственно за последним символом, считанным в массив,
записывается нулевой символ.
am, в массив,
вола новой строки
Функция fgets возвращает
конец файла
s
сива остается
без изменений
цессе выполнения произошла
ся
в
при
успешном
завершении.
Если встретился
и никаких символов в массив не прочитано, то содержимое мас¬
неопределенным
и
и возвращается
нулевой указатель. Если
ошибка чтения, то содержимое массива
возвращается
нулевой указатель.
в
про¬
являет¬
958
Приложение Б
int
FILE
с,
fputc(int
*stream);
(преобразованный в unsigned char) в по¬
указываемый stream, в позиции, указываемой индикатором пози¬
связанного с потоком файла (если определен) и перемещает индикатор.
Функция fputc
записывает символ с
ток вывода,
ции
Если файл
не может поддерживать запросы позиционирования или в случае,
если поток был открыт в режиме дополнения, то символы
добавляются
в ко¬
возвращает записанный символ. Если про¬
изошла ошибка записи, то устанавливается индикатор ошибки для потока и
fputc возвращает EOF.
нец потока вывода.
int
Функция fputc
char
fputs(const
FILE
*s,
*stream);
Функция fputs записывает строку, на которую указывает s, в поток, указыва¬
емый stream. Нулевой символ конца строки не записывается. Функция fputs
возвращает EOF
тивном
в случае
ошибки записи
и неотрицательное значение в
int getc(FILE
*stream);
Функция getc
эквивалентна
fgetc
за исключением того, что если она реализо¬
вана как макрос, то может оценивать stream
честве
про¬
случае.
аргумента
следует
использовать
более одного раза, поэтому в ка¬
без побочных эффектов.
выражение
Функция getc возвращает следующий символ из потока ввода, указываемого
stream. Если поток находится в конце файла, то для него устанавливается ин¬
дикатор конца
тока
файла
и
устанавливается
getc возвращает EOF. В случае ошибки чтения
ошибки и getc возвращает EOF.
для по¬
индикатор
int getchar(void);
Функция getchar
ток находится в конце
ца
char
файла
getchar
и
с
аргументом
stdin. Функция getchar
stdin. Если по¬
символ из потока ввода, указываемого
файла,
то для потока устанавливается индикатор кон¬
возвращает EOF.
*gets(char *s);
Функция gets
сив,
stdin, в мас¬
будет считан символ новой
файла. Символы новой строки отбрасываются
считывает символы из потока ввода, указывемого
который указывает
на
s, до тех пор, пока не
строки или не встретится конец
и
getc
эквивалентна
возвращает следующий
непосредственно после последнего считанного
в массив символа записывает¬
возвращает s в случае успешного заверше¬
ния. Если встретился конец файла и никаких символов в массив не прочита¬
но, то содержимое массива остается без изменений и возвращается нулевой
ся
нулевой
символ.
Функция gets
указатель. Если произошла ошибка чтения в процессе выполнения, то содер¬
жимое массива является неопределеннным и возвращается нулевой указа¬
тель.
int
putc(int
с,
FILE
*stream);
Функция putc эквивалентна fputc за исключением того,
лизации
как макроса возможна неоднократная оценка
что в случае ее реа¬
stream,
так что
аргу¬
быть выражением с побочными эффектами. Функция putc
возвращает записанный символ. Если произошла ошибка записи, то для пото¬
ка устанавливается индикатор ошибки и putc возвращает EOF.
мент
не
должен
int putchar(int с);
Функция putchar
putc
эквивалентна
putchar возвращает записанный
вается
индикатор
ошибки
для
со вторым аргументом
stdout. Функция
символ. В случае ошибки записи устанавли¬
потока
и
putchar
возвращает EOF.
Стандартная библиотека
959
int puts(const char *s);
Функция puts
stdout,
и
записывает
добавляет
ограничения
в
не
строки
указываемый
Нулевой символ
Функция puts возвращает EOF, если
s,
указываемую
строку,
конце
вывода
символ
записывается.
новой
в
поток,
строки.
произошла ошибка записи; в противном случае она возвращает неотрицатель¬
ное
значение.
int ungetc(int
FILE
с,
*stream);
Функция ungetc возвращает
символ с
(преобразованный
в
unsigned char) об¬
ратно в поток ввода, указываемый stream. Введенные в поток символы будут
возвращены при последующих считываниях из этого потока в обратном по¬
рядке. Успешные промежуточные вызовы
позиционирующих функций (fseek, fstpos
(для
или
потока, указываемого
stream)
rewind) отбрасывают
все вве¬
денные в поток символы. Внешняя память, соответствующая потоку, остается
без
изменений.
Гарантируется возврат
в поток одного символа.
Если функция ungetc
вызыва¬
ется слишком много раз для одного и того же потока без промежуточных опе¬
раций
дачу.
функция может потерпеть неу¬
EOF, операция не удается и поток не
чтения или позиционирования на нем,
Если значение с равно значению
изменяется.
Успешный
ка.
вызов
функции ungetc очищает индикатор конца файла для пото¬
потока после чтения или отбра¬
Значение индикатора позиции файла для
сывания всех введенных в поток символов
том
символов
в
поток.
Для
текстового
будет
таким же, как перед возвра¬
потока значение индикатора
позиции
файла после успешного вызова функции ungetc является неспецифицированным до тех пор, пока все введенные в поток символы не будут
считаны или отброшены. Для двоичного потока индикатор позиционирования
файла определен при каждом удачном вызове функции ungetc; если до вызова
связанного с ним
его
значение
было
равно
то
нулю,
после
оно
вызова
становится
Функция ungetc возвращает после преобразования
поток, или EOF при неудачной операции.
леннным.
ный в
size__t fread(void *ptr, size_t size,
FILE *stream) ;
Функция fread
считывает в массив,
неопреде-
символ, введен¬
size__t nmemb,
указанный ptr,
до nmemb элементов, раз¬
мер которых указан параметром size, из потока, указываемого stream.
катор
позиции
файла
для потока
(если определен)
Инди¬
продвигается вперед на ко¬
случае ошибки конечное
успешно прочитанных символов. В
значение индикатора позиции файла для потока становится неопределенным.
Если элемент считан частично, то его значение не определено.
личество
Функция fread возвращает количество упешно считанных элементов, которое
может быть меньше, чем nmemb, в случае ошибки чтения или появления кон¬
ца файла. Если size
жимое
массива
и
или
nmemb равны нулю,
состояние
остатка
потока
то
не
fread возвращает ноль; содер¬
изменяются.
size__t fwrite(void *ptr, size__t size, size__t nmemb,
FILE
*stream);
Функция fwrite записывает из массива, указанного ptr, до nmemb элементов,
размер которых указан параметром size, в поток, указываемый stream. Инди¬
катор позиции файла для потока (если определен) продвигается вперед на ко¬
личество успешно записанных символов. В случае ошибки конечное значение
индикатора позиции файла для потока становится неопределенным. Функция
fwrite возвращает количество упешно записанных элементов, которое может
быть меньше, чем nmemb, только в случае ошибки записи.
960
Приложение Б
fgetpos(FILE *stream,
int
fpos__t *pos);
Функция fgetpos
передает текущее значение индикатора позиции файла для
потока, указываемого stream, в объект, указываемый pos. Переданное значе¬
ние содержит неспецифицированную информацию, используемую функцией
fsetpos
для установления позиции потока в положение, соответствующее мо¬
менту вызова
функции fgetpos. В случае успеха функция fgetpos возвращает
функция fgetpos возвращает ненулевое значение и передает
реализацией положительное значение в errno.
ноль; при отказе
определяемое
int
fseek(FILE
*stream,
long
int offset,
int whence);
Функция fseek устанавливает индикатор позиции файла для потока, указыва¬
Для двоичного потока новая позиция,
файла, получается прибавлением offset
емого stream.
от
начала
отсчитанная в символах
к
whence. Заданная позиция соответствует началу файла,
равно SEEK__SET, текущему
whence равно SEEK__CUR, или
задаваемой
whence
файла, если
файла при whence равном SEEK__END.
значению
концу
позиции,
если значение
индикатора
позиции
offset должно быть равно либо нулю, либо
значению, возвращенному более ранним вызовом функции ftell для того же
потока, а значение whence в последнем случае должно равняться SEEK__SET.
Для
текстового
Успешный
потока значение
вызов
функции fseek очищает индикатор конца файла для
функции ungetc. После
и устраняет все последствия вызовов для него
fseek следующей операцией
и вывод.
Функция fseek
на
потока
вызова
обновляемом потоке может быть как ввод,
так
возвращает ненулевое значение только для запроса,
который не может быть удовлетворен.
int
fsetpos(FILE *stream,
const
fpos_t *pos);
Функция fsetpos
устанавливает индикатор позиции файла для потока, указы¬
ваемого stream, в соответствии со значением объекта, указываемого pos, кото¬
рое должно быть значением, полученным от более раннего вызова функции
fgetpos
для данного потока. Успешный вызов
файла для
функции ungetc. После
катор конца
быть
функции fsetpos очищает
инди¬
потока и устраняет все последствия вызовов для него
вызова
fsetpos следующей операцией
на
обновляемом
функция fsetpos
возвращает ноль; при отказе функция fsetpos возвращает ненулевое значение
и передает определяемое реализацией положительное значение в errno.
потоке
может
как
ввод,
так
и
вывод.
long int ftell(FILE *stream);
Функция ftell получает текущее значение
В случае успеха
индикатора позиции файла для по¬
указываемого stream.
Для двоичных потоков данное значение равно
числу символов от начала файла. Для текстового файла его индикатор пози¬
ции файла содержит неспецифицированную информацию, используемую фун¬
кцией fseek для возвращения индикатора позиции файла для потока в поло¬
жение, соответствующее моменту вызова функции ftell; разность между
тока,
возвращаемыми ftell значениями не может быть использована для определе¬
ния числа записанных или считанных символов. В случае успеха функция
ftell возвращает текущее значение индикатора позиции файла для потока.
При сбое функция ftell возвращает -lL
положительное
значение
void rewind(FILE
в
и передает определяемое
реализацией
errno.
*stream);
Функция rewind устанавливает индикатор позиции файла для потока, ука¬
занного
(void)
stream,
fseek
на
начало
(stream,
файла. Это
0L,
эквивалентно
SEEK_SET)
за исключением того, что индикатор
ошибки для
потока также очищается.
Стандартная библиотека
961
void clearerr(FILE *stream);
Функция clearerr очищает индикаторы
конца
файла
и
ошибки для потока,
указанного stream.
int
feof(FILE
*stream);
Функция feof проверяет индикатор конца файла для потока, указанного stre¬
am.
Функция feof возвращает ненулевое значение
конца файла для stream установлен.
тогда и только тогда, когда
индикатор
int
error(FILE *stream);
Функция ferror проверяет индикатор ошибки для потока,
am. Функция ferror возвращает ненулевое значение в том
указываемого stre¬
и только в том слу¬
чае, если индикатор ошибки для stream установлен.
void
perror(const
char
*s);
Функция perror переводит номер ошибки, содержащийся
в целом выражении
сообщение об ошибке. Она пишет последовательность символов в
стандартный поток ошибок следующим образом: сначала (если s не является
нулевым указателем и символ, на который указывает s, не является нулевым)
строку, указываемую s, двоеточие (:) и пробел; затем соответствующую строку
с сообщением об ошибке и символ новой строки. Содержание строки сообще¬
errno,
в
ния об ошибке точно такое же, как то, что возвращается функцией strerror с
аргументом errno.
Б.11.
Утилиты общего
EXIT_FAILURE
EXIT_SUCCES
Целочисленные
гумента
<stdlib.h>
назначения
выражения, которые могут быть использованы в качестве ар¬
exit, возвращающей соответственно неуспешный или
функции
успешный статус завершения в системное окружение.
MB_CUR_MAX
Положительное целочисленное выражение, значение которого представляет
собой максимальное число байт в многобайтовом символе для набора расши¬
ренных символов, установленного
в текущем локале
и
MB_LEN_JMAX.
которое
никогда
не
превышает
(категория LC_CTYPE),
NULL
Определяемая реализацией константа нулевого указателя.
RAND_MAX
Целочисленное
константное
выражение,
значение которого
представляет
со¬
функцией rand. Значение макроса
крайней мере 32767.
бой максимальное значение, возвращаемое
RAND_MAX болжно быть равно
div_t
Тип структуры, представляющей
по
тип значения, возвращаемго
ldiv_t
Тип структуры, представляющей
ldiv.
тип
значения,
функцией div.
возвращаемого
функцией
962
Приложение Б
size__t
Беззнаковый целый
wchar_t
Целочисленный
тип
тип,
результата
диапазон
операции
значений
sizeof.
может
которого
представлять
уникальные коды для всех элементов наиболее широкого
набора символов,
определенного среди поддерживаемых локалов; нулевой символ будет иметь
код со значением нуль и каждый элемент базового набора символов будет
иметь значение кода, равное его значению при использовании как одиночного
символа
double
целой символьной
в
atof(const
char
константе.
*nptr);
Преобразует строку, указанную nptr,
atof возвращает преобразованное
int
atoi(const
char
в представление типа
*nptr);
в представление типа int.
Преобразует строку, указанную nptr,
возвращает преобразованное
long int atol(const
doqble. Функция
значение.
char
*nptr);
Преобразует
строку, указанную nptr, в представление типа
возвращает преобразованное значение.
atol
strtod(const char *nptr,
double
Функция atoi
значение.
long. Функция
char **endptr);
строку, указанную nptr, в представление double. Сначала произ¬
водится декомпозиция строки ввода на три части: начальную, возможно пус¬
Преобразует
пробельных символов (как они определяются функ¬
цией isspace), предметную последовательность, аналогичную константе с
плавающей точкой, и конечную строку из одного или более нераспознанных
символов, включая нулевой ограничивающий символ входной строки. Затем
функция пытается преобразовать предметную последовательность в число с
плавающей точкой и возвратить результат.
тую, последовательность
Расширенная форма предметной последовательности содержит знаки плюс
или минус, затем непустую последовательность цифр, опционально содержа¬
щую символ десятичной точки, опциональную экспоненциальную часть, но не
суффикс. Предметная последовательность определяется как
начальная последовательность строки ввода, начинающаяся с
«плавающий»
наибольшая
не-пробельного
первого
строка
символа,
не содержит символов,
исключительно
отличен
от
форму. Предметная
пустой, содержит
первый не-пробельный символ
имеющая ожидаемую
если строка ввода является
пробельные символы или если
цифры или десятичной точки.
знака,
форму, то последова¬
первой цифры или десятичной точки (с
символов) интерпретируется как константа с плава¬
Если предметная последовательность имеет ожидаемую
тельность символов, начинающаяся с
первого любого из данных
ющей точкой, с тем отличием, что вместо точки используется «символ десяти¬
чной точки» и если нет ни экспоненциальной части, ни символа десятичной
точки, то десятичная точка предполагается следующей за последней цифрой в
строке.
Если предметная последовательность
мое
после
преобразования
значение
конечную строки передается в
endptr
Если
мой
не
является
предметная
формы,
то
нулевым
начинается со знака минус, то получае¬
является
отрицательным.
Указатель
объект, указываемый endptr, при условии,
на
что
указателем.
последовательность является
преобразование
пустой
не производится;
или
значение
не имеет ожидае¬
nptr передается
в
963
Стандартная библиотека
объект, указываемый endptr,
при условии, что
endptr
не является нулевым
указателем.
Функция strtod возвращает преобразованное
не может быть выполнено,
выходит из диапазона представимых
нус
HUGE__VAL (в
Если преобразование
Если полученное значение
значение.
возвращается ноль.
то
значений, то возвращается плюс или ми¬
значения) и в errno передается зна¬
соответствии со знаком
макроса ERANGE.
чение
long int strtol(const
char
char **endptr,
*nptr,
int base);
Преобразует строку, указанную nptr, в представление типа long int. Сначала
производится декомпозиция строки ввода на три части: начальную, возможно
пустую, последовательность пробельных символов (как они определяются
функцией isspace), предметную последовательность, являющуюся допусти¬
мым представлением целого в некоторой системе счисления с основанием,
определенным значением base, и конечную строку с одним или более другими
символами, включая нулевой ограничивающий символ входной строки. После
этого осуществляется попытка преобразовать предметную последовательность
в целочисленное значение
Если
и
возвратить
base равно нулю,
значение
тельности является такой
же,
результат.
форма предметной последова¬
то ожидаемая
как
целая
знаком
константа со
или
плюс
ми¬
целый суффикс. Если значение base находится между
2 и 36, то предполагаемая форма предметной последовательности является по¬
следовательностью букв или цифр представления целого с основанием, ука¬
занным base, с предшествующим знаком плюс или минус, но не включая це¬
лый суффикс. Буквам от а (или А) до z (или Z) приписываются значения от 10
нус, но не включающая
до 35; допускаются только буквы, чьи значения меньше, чем base. Если значе¬
ния base равно 16, то вслед за знаком, если он имеется, последовательности
букв
или
цифр могут предшествовать
0x
символы
или
0X.
последовательность, определенная как наибольшая начальная по¬
следовательность строки ввода, начинающаяся с первого не-пробельного сим¬
вола, имеющая ожидаемую форму. Предметная последовательность не содер¬
Предметная
жит символов, если входная строка является
из
символов, или если
пробельных
знака
или
разрешенных
букв
пустой или
первый не-пробельный
или
то
тельность
имеет
последовательность
цифры, интерпретируется
как
имеет ожидаемую
и
значение
то оно используется как основание для
писывается
значение,
как
описано
ожидаемую
символов,
является
отрицательным.
и
значение
с
первой
Если предметная последова¬
base находится между 2 и 36,
преобразования,
выше.
Если
Указатель
на
объект, указываемый endptr, при условии,
каждой букве при¬
последователь¬
преобразования
конечную
что
а
предметная
ность начинается со знака минус, то полученное после
ние
форму
начинающаяся
константа.
целая
форму
состоит
символ отличен от
цифр.
Если предметная последовательность
base равно нулю,
полностью
endptr
строку
не
значе¬
передается
в
нуль.
Если предметная последовательность является пустой или не имеет ожидае¬
мой формы, то преобразование не производится; значение nptr передается в
объект, указываемый endptr, при условии,
что
endptr
не
является
нулевым
указателем.
Функция strtol возвращает преобразованное
не может быть выполнено,
выходит из диапазона представимых
или
LONG__MIN (в
чение
макроса
Если
преобразование
Если полученное значение
значений, то возвращается LONG_MAX
значения) и в errno передается зна¬
зависимости от знака
ERANGE.
значение.
то возвращается нуль.
Приложение Б
964
unsigned
int
long
strtoul(const char
int base);
в
Преобразует строку, указываемую nptr,
int. Функция strtoul работает
возвращает преобразованное
осуществлено,
то
rno
int
передается
значение
char
представление
**endptr,
типа
unsigned long
функции strtol. Функция strtoul
Если преобразование не может быть
идентично
значение.
возвращается
Если полученное значение находится
ноль.
значений,
вне диапазона представимых
*nptr,
макроса
то возвращается
ULONG_MAX
и в er¬
ERANGE.
rand(void);
Функция rand
псвдослучайных целых чисел в
RAND_MAX. Функция rand возвращает псевдослучайное
вычисляет последовательность
диапазоне от 0 до
целое число.
void
srand(unsigned int seed);
Использует
аргумент как «семя» для новой последовательности псевдослучай¬
ных чисел, возвращаемой последовательными вызовами rand. Если srand вы¬
зывается с одним и тем же семенем, то последовательность псевдослучайных
чисел будет повторена. Если rand вызывается без какого-либо предваритель¬
ного вызова srand, то будет генерирована такая же последовательность, как в
случае вызова srand с начальным значением 1. Следующие функции опреде¬
ляют переносимые реализации rand и srand.
static
int
unsigned
rand(void)
long
/*
int
next
RAND_MAX
=
1;
полагается
равным
327 67
*/
{
next
=
return
next
*
1103515245
int)
(unsigned
+
12345;
(next/6553 6)
%
327 68;
}
void
srand
(unsigned
int
seed)
{
next
=
seed;
}
*calloc(size_t nmemb, size_t size);
Выделяет пространство для массива из nmemb объектов,
void
каждый из которых
имеет размер size. Выделенная область памяти инициализируется нулевыми
байтами. Функция calloc возвращает либо нулевой указатель, либо указатель
на выделенную область.
free(void *ptr);
Освобождает область памяти,
void
шего использования.
ствий
не
указанную
Если ptr
происходит.
Если
ptr,
делая ее
доступной
для
дальней¬
дей¬
является нулевым указателем, то никаких
аргумент
не
является
указателем,
ранее
возвра¬
функциями calloc, malloc или realloc, или область уже была
освобождена вызовом free или realloc, то поведение не определено.
щенным
*malloc(size_t size);
Выделяет область памяти для объекта
void
с размером, задаваемым size. Функция
malloc возвращает либо нулевой указатель, либо указатель на выделенную об¬
ласть.
Стандартная библиотека
965
void *realloc(void *ptr, size_t size);
Изменяет размер объекта, указываемого ptr, на
размер, заданный size. Содер¬
жимое объекта в диапазоне от начала и вплоть до меньшего из старого и ново¬
го размеров сохраняется. Если новый размер больше, то значения во вновь
выделенной области объекта являются неопределенными. Если ptr является
нулевым указателем, то поведение функции realloc такое же, как функции
malloc для указанного размера. Если ptr не является указателем, ранее воз¬
вращенным функциями calloc, malloc или realloc, или область уже была
освобождена вызовом free или realloc, то поведение не определено. Если об¬
ласть не может быть выделена, то объект, указываемый ptr, не изменяется.
Если size равен нулю и ptr не является нулевым указателем, то указываемый
объект освобождается. Функция realloc возвращает нулевой указатель или
указатель на выделенную область, возможно, перемещенную.
void
abort(void);
завершение программы, если только не перехватывает¬
ся сигнал SIGABRT и обработчик сигнала не возвращает управления. Сбрасы¬
ваются ли открытые потоки вывода, закрываются ли открытые потоки и уда¬
Производит аварийное
ли временные файлы, определяется реализацией. В системное
окружение возвращается зависящая от реализации форма статуса неуспешно¬
го завершения посредством вызова функции raise(SIGABRT). Функция abort
ляются
не
может
возвратить
int atexit(void
управление
в
вызывающую
программу.
(*func)(void));
Регистрирует функцию, указываемую func, для
вызова
без
аргументов
Реализация должна поддерживать
функций. Функция atexit возвращает
нормальном завершении программы.
страцию по меньшей мере 32-х
если регистрация прошла успешно,
void
при
реги¬
ноль,
и ненулевое значение при отказе.
exit(int status);
Вызывает нормальное завершение программы. Если программа
исполняет бо¬
функции exit, то поведение не определено. Сначала вызыва¬
ются все функции, зарегестрированные функцией atexit, в порядке, обратном
их регистрации. Каждая функция вызывается столько раз, сколько раз она
была зарегистрирована. Далее, все открытые потоки с незаписанными буфе¬
ризованными данными сбрасываются, все открытые потоки закрываются и
все файлы, созданные функцией tmpfile, удаляются.
лее одного вызова
Наконец, управление возвращается
status равно нулю
зацией форма
или
в
системное
EXIT__SUCCESS,
состояния успешного завершения.
EXITJFAILURE,
то
возвращается
окружение.
Если значение
то возвращается определяемая реали¬
определяемая
Если
значение status равно
реализацией форма
состоя¬
неуспешного завершения. В осталных случаях возвращаемое состояние
зависит от реализации. Функция exit не может возвратить управление в вы¬
ния
зывающую
char
программу.
*getenv(const char
Поиск
*name);
окружения, предусмотренном системным окружением, стро¬
ки, допускающей сопоставление с той, на которую указывает name. Набор
имен
в
списке
окружения
и
метод
изменения
списка
окружения
зависят
от
реализа¬
указатель на строку, ассоциированную с сопоставленным
элементом списка. Указываемая строка не должна модифицироваться про¬
граммой, но может быть переписана последующим вызовом функции getenv.
ции.
Возвращает
Если данное имя не может быть найдено, то возвращается нулевой указатель.
966
Приложение Б
int
system(const char
*string);
Передает
строку, указываемую string, системному окружению для выполне¬
ния командным процессором в соответствии с конкретной реализацией. В ка¬
честве
string
нулевой указатель
для выяснения на¬
личия командного процессора. Если аргументом является
нулевой указатель,
может быть использован
функция system возвращает ненулевое значение только в том случае, если
командный процессор доступен. Если аргумент не равен нулевому указателю,
то функция system возвращает значение, зависящее от реализации.
то
void
*bsearch(const void
const void
*key,
*base,
size_t nmemb, size_t size,
int
Поиск
в
массиве
(*compar)(const
из
void
const void
*,
nmemb объектов, начальный
элемент
*));
которых
указан
base,
элемента, соответствующего объекту, указываемому key. Размер каждо¬
го элемента массива специфицируется аргументом size. Функция сравнения,
указываемая compar,
на объект
key
вызывается с двумя аргументами,
щать целое, меньшее, равное или большее нуля,
ект
key
которые указывают
Функция должна возвра¬
если рассматриваемый объ¬
и элемент массива, в данном порядке.
соответственно меньше, равен или
больше,
чем элемент массива.
Мас¬
сив должен состоять из: всех элементов, которые меньше чем, всех элементов,
и всех элементов, которые больше чем объект
которые равны,
таком
key,
именно в
порядке.
Функция bsearch возвращает указатель
соответствующего элемента массива
или нулевой указатель, если подходящий элемент не найден. Если два эле¬
мента массива эквивалентны, то не специфицируется, который из них будет
возвращен.
void
qsort(void *base,
int
size_t nmemb, size_t size,
(*comar)(const void
*,
const void
*));
Сортировка массива из nmemb объектов. Начальный элемент указывается
base. Размер каждого объекта специфицируется size. Содержимое массива
сортируется
в
восходящем
в
порядке
указываемой compar. Функция
равное или большее нуля, если
соответствии
с
функцией сравнения,
должна возвращать целое число,
первый аргумент
меньшее,
соответственно меньше, ра¬
вен или больше второго. Если два элемента эквивалентны, то их порядок сле¬
дования
в
сортируемом
int abs(int
j)
массиве
не
определен.
;
Вычисление абсолютного значения целого
представлен, то поведение не определено.
ное
j. Если результат не может быть
Функция abs возвращает абсолют¬
значение.
div_t div(int
numer,
Вычисление частного
int denom) ;
и
остатка от деления числителя numer на знаменатель
denom. Если аргументы
не делятся нацело, то полученное частное представля¬
ется целым,
меньшим, чем ближайшее
алгебраическое
частное. Если резуль¬
тат не может быть представлен, то поведение не определено; в противном слу¬
чае выражение частное * denom + остаток должно быть равно numer.
Функция div возвращает структуру
и остаток.
Структура будет
типа div_t, включающую как частное, так
содержать следующие элементы в указанном по¬
рядке:
int
int
quot;
rem;
/*
частное
/*
остаткок
*/
*/
967
long
int
labs(long int j);
Аналогична функции abs за исключением того,
значение
имеют
что аргумент и возвращаемое
long int.
тип
ldiv_t ldiv(long int numer, long int denom);
Аналогична функции div за исключением того, что аргументы
возвращаемой структуры (типа ldiv_t) имеют тип long int.
int mblen(const char *s,
Если s He является нулевым
байт, содержащихся
в
и
элементы
size_t n);
указателем, то
функция mblen определяет
многобайтовом символе, указываемом
s.
число
Если s
ну¬
левой указатель, то функция mblen возвращает ненулевое или нулевое значе¬
ние, если среди кодировок многобайтовых символов соответственно имеются
или не имеются кодировки, зависящие от страны. Если s не является нуле¬
вым указателем, то функция mblen возвращает ноль (если s указывает на ну¬
левой символ)
или количество
байт, содержащихся
в
многобайтовом символе
(если следующие n или менее байтов образуют допустимый многобайтовый
символ), или возвращает -1 (если они не образуют допустимого многобайтово¬
го символа).
int
mbtowc(wchar_t *pwc,
char
const
*s,
size_t n);
Если s не является нулевым указателем, то функция mbtowc определяет число
байт, которое содержится в многобайтовом символе, указываемом s. Она также
определяет код для значения типа wchar_t, который соответствует этому
мно¬
гобайтовому символу. (Значение кода, соответствующего нулю, равно нулю).
Если многобайтовый символ является допустимым и pwc не является нулевым
указателем, то функция mbtowc сохраняет код в объекте, указываемом pwc.
Проверяться будут
Если s
самое
большее
нулевой указатель,
левое значение,
n
байт
массива,
указываемого
s.
функция mblen возвращает ненулевое
то
если среди кодировок
многобайтовых
или ну¬
символов соответствен¬
но имеются или не имеются кодировки, зависящие от страны. Если s не явля¬
ется
нулевым
указывает на
указателем,
то
нулевой символ)
функция mblen возвращает ноль (если s
количество байт, содержащихся в много¬
или
байтовом символе (если следующие n или менее байтов образуют допустимый
многобайтовый символ), или возвращает -1 (если они не образуют допустимо¬
го многобайтового
жет
быть больше
символа). Ни
n
или
int wctomb(char *s,
в каком случае возвращаемое значение не мо¬
значения
макроса
MB_CUR_MAX.
wchar_t wchar);
Функция wctomb
определяет число байт, необходимое для представления
многобайтового символа, соответствующего коду со значением wchar (вклю¬
чая какие-либо изменения регистра). Она сохраняет представление многобай¬
тового символа в массиве, указываемом s (если s не является нулевым указа¬
телем). Сохраняется не более MB_CUR_MAX
равно нулю, то функция wctomb остается в
Если s
нулевой указатель,
левое значение,
то
символов. Если значение wchar
начальном
состоянии.
функция mblen возвращает ненулевое
если среди кодировок
многобайтовых символов
или ну¬
соответствен¬
но имеются или не имеются кодировки, зависящие от страны. Если s не явля¬
ется
функция wctomb возвращает -1, если значение
многобайтовому символу или возвращает
байт, которое содержится в многобайтовом символе, соответствующем
нулевым указателем,
wchar
число
то
не соответствует допустимому
значению wchar. Ни
в каком случае возвращаемое значение не может
льше значения макроса MB_CUR_MAX.
быть бо¬
Приложение Б
968
size_t mbstowcs(wchar_t *pwcs, const char
Функция mbstowcs преобразует последовательность
которая начинается
*s,
size_t n);
многобайтовых символов,
в исходном состоянии регистра, из массива, указываемо¬
го s, в последовательность соответствующих кодов и сохраняет не
кодов
более чем n
Никакие многобайтовые символы, следу¬
в массиве, указываемом pwcs.
ющие за нулевым символом (который преобразуется
в код со значением ноль)
будут проверяться или преобразовываться. Каждый многобайтовый символ
преобразуется так, как если бы вызывалась функция mbtowc, за исключени¬
ем того, что состояние регистра функции mbtowc не изменяется.
не
В массиве, указываемом pwcs, будут модифицированы не более n элементов.
Поведение при копировании между перекрывающимися объектами не опреде¬
лено. Если встречается неправильный многобайтовый символ, то функция
(size__t) -1. В противном случае функция mbstowc возвра¬
щает количество модифицированных элементов массива, не считая нулевого
символа завершения строки.
bstowc возвращает
size_t wcstombs(char *s, const wchar_t *pwcs, size_t n);
Функция wcstombs преобразует последовательность кодов, которые соответст¬
вуют многобайтовым символам, из массива, указываемого pwcs, в последова¬
тельность
нии
многобайтовых
регистра,
и
символов,
сохраняет
эти
которая
начинается
многобайтовые
в
исходном
символы
в
состоя¬
массиве,
будет превышать
нулевой символ. Каждый код пре¬
образуется, как если бы вызывалась функция wctomb, за исключением того,
что состояние регистра функции wctomb не изменяется.
указываемом s, останавливаясь, если многобайтовый символ
по размеру n байтов или если сохраняется
В
массиве,
s,
указываемом
будет модифицировано
не
более
n
байтов. Если
имеет место копирование между перекрывающимися объектами, то поведение
не определено. Если встречается код, который не соответствует допустимому
то функция wcstombs возвращает (size__t) -1. В про¬
функция wcstombs возвращает число модифицированных бай¬
многобайтовому символу,
тивном случае
тов,
Б.12.
не считая нулевого символа завершения строки.
Обработка
строк
<string.h>
NULL
Определяемая реализацией константа нулевого указателя,
size_t
Беззнаковый целый тип результата операции sizeof.
void
*memcpy(void *sl,
const
void
size_t n);
*s2,
Функция
memcpy копирует n символов из объекта, указываемого s2, в объ¬
ект, указываемый sl. Если копирование имеет место между объектами, кото¬
рые перекрываются, то поведение не определено. Функция memcpy возвраща¬
ет значение sl.
void
*memmove(void
Функция
*sl,
const void
*s2,
memmove копирует n символов из
size_t n);
объекта, указываемого s2,
в
объ¬
ект, указываемый sl. Копирование происходит так, как если бы n символов
из объекта, указанного s2, сначала копировались во временный массив из n
символов, который не перекрывается с объектами, на которые указывают sl и
s2, и затем n символов из временного массива копировались бы в объект, ука¬
зываемый sl. Функция
memmove
возвращает
значение
sl.
969
Стандартная библиотека
char *strcpy(char *sl,
const
char
*s2);
Функция strcpy копирует строку, указываемую s2 (включая нулевой символ
ограничения строки) в массив, указываемый sl. Если копирование имеет мес¬
то между перекрывающимися объектами, то поведение не определено. Функ¬
ция strcpy возвращает значение sl.
char
*strncpy(char *sl,
const
char
size_t n);
*s2,
Функция strncpy
копирует не более n символов (символы, которые следуют за
нулевым символом, не копируются) из массива, указываемого s2, в массив,
указываемый sl. Если копирование
имеет
место между
перекрывающимися
объектами, то поведение не определено. Если массив, указываемый s2, явля¬
ется строкой, которая содержит менее n символов, то к их копии в массив,
указываемый sl, добавляется соответствующее количество нулевых символов.
Функция strncpy возвращает
char
const
*sl,
*strcat(char
значение
Функция strcat присоединяет
между
*strncat(char
Функция
конец
*§1,
*s2);
указываемой s2 (включая огра¬
указываемой sl. Начальный
строки,
символ в конце sl. Если копирование имеет
объектами,
перекрывающимися
Функция strcat возвращает
char
char
копию строки,
ничивающий нулевой символ), в
символ s2 переписывает нулевой
место
sl.
значение
то
поведение
не
определено.
sl.
const char
*s2,
size_t n);
strncat присоединяет не более n символов (нулевой символ
и символы,
ки,
копируются) из массива, указываемого s2, в конец стро¬
указываемой sl. К результату всегда присоединяется ограничивающий нуле¬
вой
символ. Если копирование имеет место между перекрывающимися объекта¬
следующие за ним,
ми,
то
поведение
не
не
определено.
int memcmp(const void
*sl,
Функция strncat возвращает
const void
*s2,
значение
sl.
size_t n);
Функция
memcmp сравнивает первые n символов объекта, указываемого sl, с
первыми n символами объекта, указываемого s2. Функция memcmp возвра¬
щает целое большее, равное или меньшее нуля, если объект, указываемый sl,
соответственно больше, равен или меньше объекта, указанного s2.
strcmp(const char *sl,
int
const
char
*s2);
Функция strcmp сравнивает строку, указываемую sl, со строкой, указывае¬
мой s2. Функция strcmp возвращает целое большее, равное или меньшее
нуля, если строка, указываемая
строки,
sl,
соответственно
больше, равна
или меньше
указываемой s2.
int strcoll(const char *sl, const char *s2);
Функция strcoll сравнивает строку, указываемую sl, со строкой, указывае¬
мой s2, причем обе интерпретируются как соответствующие категории
LCJLOCAL текущего локала. Функция strcoll возвращает целое большее, рав¬
ное или меньшее нуля, если строка, указываемая sl, соответственно больше,
равна или меньше чем строка, указываемая s2, если обе строки интерпретиро¬
вать в текущем локале.
int
strncmp(const char *sl,
const
char
s2,
size_t n);
Функция strncmp сравнивает не более чем n символов (символы, следующие
за нулевым символом, не сравниваются) из массива, указываемого sl, с сим¬
волами из массива, указываемого s2. Функция strncmp возвращает целое бо¬
льшее, равное или меньшее нуля, если массив (возможно завершающийся ну¬
лем), указываемый sl, соответственно больше, равен или меньше массива
(возможно завершающегося нулем), указываемого s2.
970
Приложение
Б
size_t strxfrm(char *sl, constchar s2, size_t n);
Функция strxfrm преобразует строку, указывемую s2, и помещает преобразо¬
ванную строку в массив, указываемый sl.
Преобразование
таково, что если к
преобразованным строкам применяется функция strcmp, то возвращаемое
ею значение будет больше, равно или меньше нуля в соответствии с результа¬
том применения функции strcoll к двум исходным строкам. В массив результа¬
та, указываемый sl, записывается не более n символов, включая ограничиваю¬
щий нулевой символ. Если n равно нулю, то sl может быть нулевым
указателем. Если имеет место копирование перекрывающихся объектов, то по¬
ведение не определено. Функция strxcmp возвращает длину преобразованной
строки (не считая конечного нулевого символа). Если значение равно или боль¬
ше n, то содержимое массива, указываемого sl, является неопределенным.
двум
void
*memchr(const void
Функция
*s,
int
с,
size_t n);
memchr находит первое появление аргумента
с
(преобразованного
в
unsigned char) в начальных n символах (каждый из которых интерпретирует¬
ся как unsigned char) объекта, указываемого s. Функция memchr возвращает
указатель на найденный символ или нулевой указатель, если символ в объек¬
те не встретился.
char
*strchr(const char *s,
int с);
Функция strchr находит первое вхождение параметра с (преобразованного в
char) в строку, указываемую s. Завершающий нулевой символ рассматривает¬
ся как входящий в строку. Функция strchr возвращает указатель на найден¬
ный символ или нулевой указатель, если символ в строке не встретился.
size_t strcspn(const
Функция strcspn
char
указываемой sl, который
ку,
char
указываемую
*sl,
char
const
полностью состоит из символов, не входящих в стро¬
s2. Функция strcspn возвращает длину данного сегмента.
*strpbrk(const
char
const
*sl,
char
s2
char
в
на символ или
*s2);
sl, како¬
Функция strpbrk возвращает
нулевой указатель, если нет вхождений символов из
Функция strpbrk находит первое вхождение в
го-либо символа из строки, указываемой s2.
указатель
*s2);
вычисляет длину максимального начального сегмента строки,
строку, указываемую
sl.
*strrchr(const char
*s,
int с);
Функция strrchr находит последнее вхождение параметра
го в
char)
в строку,
входящим в строку.
с (преобразованно¬
Завершающий нулевой символ считается
Функция strrcch возвращает указатель на символ или ну¬
указываемую s.
левой указатель, если с в строке не обнаружен.
size_t strspn(const
Функция strspn
ки,
ной
char
*sl,
const char
*s2);
вычисляет длину максимального начального сегмента стро¬
указываемой sl, который полностью состоит из символов
s2. Функция strspn возвращает длину сегмента.
строки, указан¬
*strstr(const char *sl, const char *s2);
Функция strstr находит первое появление в строке, указываемой sl, последо¬
вательности символов (исключая завершающий нулевой символ) строки, ука¬
зываемой s2. Функция strstr возвращает указатель на найденную строку или
char
нулевой указатель, если строка не найдена. Если s2 указывает на строку нуле¬
вой длины, то функция возвращает sl.
Стандартная библиотека
971
char *strtok(char *sl,
const
char
*s2);
Последовательность вызовов функции strtok разбивает строку, указываемую
sl, на последовательность лексем, которые отделены друг от друга символами
из строки, указанной s2. Первый вызов в последовательности использует как
аргумент sl; последующие вызовы осуществляются с нулевым указателем в
качестве первого аргумента. Разделяющая строка, указанная s2, может отли¬
чаться от вызова к вызову.
Первый вызов осуществялет поиск
в строке, указываемой sl, первого симво¬
который не содержится в текущей строке разделителей, указываемой s2.
Если таких символов не найдено, то в этом случае в строке, указываемой sl,
нет лексем и функция strtok возвращает нулевой указатель. Если такой эле¬
мент найден, то он будет началом первой лексемы.
ла,
Затем функция strtok ищет символ, который содержится
в текущей разделя¬
ющей строке. Если такой символ не найден, то текущая лексема простирается
до конца строки, указываемой sl, и следующий поиск лексемы возвратит ну¬
левой указатель. Если такой символ найден, то он заменяется нулевым симво¬
лом, который завершает текущую лесему. Функция strtok сохраняет указа¬
тель на следующий символ, с которого начнется поиск следующей лексемы.
Каждый последовательный
вызов с нулевым указателем как значением перво¬
го аргумента начинает поиск от сохраненного указателя аналогично описан¬
Функция strtok возвращает указатель на первый
нулевой указатель, если лексемы отсутствуют.
ному выше.
или
void
*memset(void *s,
Функция
каждый
size_t n);
memset копирует значение с (преобразованное
из
возвращает
char
int с,
первых
n
символов
символ лексемы
объекта, указываемого
в
s.
unsigned char)
в
Функция memset
значение s.
*strerror(int errnum);
Функция strerror
ставит в соответствие номеру ошибки в errnum строку сооб¬
Функция strerror возвращает указатель на строку, содер¬
которой определяется реализацией. Указываемый массив не должен
модифицироваться программой, но может быть переписан последующим вы¬
зовом функции strerror.
щения об ошибке.
жимое
size_t strlen(const
Функция strlen
char
*s);
вычисляет длину строки,
указываемой
s.
Функция strlen
воз¬
вращает количество символов, которое предшествует ограничивающему стро¬
ку
нулевому
Б.13. Дата
и
символу.
время <time.h>
CLOCKS_PER_SEC
Число, соответствующее
одной секунде в значении, возвращаемом функцией
clock.
NULL
Определяемая реализацией
константа нулевого указателя,
clock_t
Арифметический
тип, способный представлять время,
time_t
Арифметический
тип, способный представлять время.
972
Приложение Б
size_t
Беззнаковый целый
тип
результата
операции
sizeof.
tm
struct
Содержит
менем.
компоненты календарного времени, называемого
Структура
разделенным вре¬
должна содержать по
любом порядке. Семантика
элементов
крайней мере следующие элементы в
и их нормальный диапазон значений
представлены в комментариях.
/*
int
tm_sec;
int
int
int
int
int
tm_min;
tm_hour;
tm_mday;
tm_mon;
tm_year;
секунды после минут
[0,61] */
/* минуты после часов
[0,59] */
/* часы с полуночи
[0,23] */
/* день месяца
[1,31] */
/* месяц с января
[0,11] */
/* год с 1900 */
/* день с воскресенья
[0,6] */
/* день с января
[0,365] */
/* флаг перехода на летнее время */
-
-
-
-
-
int tm_wday;
int tm_yday;
int tm _isdst;
Значение tm_isdst
имеет место,
нет
этом
-
-
является
равно нулю,
положительным,
если
если его не происходит,
переход
на
летнее
и отрицательным,
время
если об
информации.
clock_t clock(void);
Функция clock определяет
использованное
процессорное
время.
Функция
clock возвращает
наилучшее приближение процессорного времени, использо¬
ванного программой, от начала определяемой реализацией эры, относящейся
только к активации программы. Для определения времени в секундах значе¬
ние, возвращаемое
са
функцией clock,
должно быть поделено на значение макро¬
Если использованное процессорное время недоступно
может быть представлено как clock_t, то функция возвра¬
CLOCKS_PER_SEC.
или его значение не
щает
значение
(clock_t) -1.
double difftime(time_t timel, time_t timeO);
Функция difftime вычисляет разность между двумя календарными
ми времени: timel
выраженную
в
-
timeO. Функция difftime возвращает разность
момента¬
как
double,
секундах.
time_t mktime(struct
tm
*timeptr)
;
Функция mktime преобразует разделенное время, выраженное как местное, в
структуре, указываемой timeptr, в значение календарного времени с такой же
кодировкой, как значение, возвращаемое функцией time. Исходные значения
компонентов tm_wday и tm__yday структуры игнорируются, а значения дру¬
гих компонентов не ограничиваются диапазонами, указанными выше. При
успешном завершении значения компонентов tm_wday и tm_yday структуры
устанавливаются соответствующим образом и другие компоненты устанавли¬
ваются представляющими специфицированное календарное время, но со зна¬
чениями в диапазонах, указанных выше; окончательное значение tm_mday
устанавливается до тех пор, пока не определятся значения tm_mon и
tm_year. Функция mktime возвращает специфицированное календарное
время, кодированное как значение типа time_t. Если данное календарное
время не может быть представлено как time_t, то функция возвращает значе¬
не
ние
(time_t)
-1.
973
time_t time(time_t *timer);
Функция time определяет текущее
календарное время. Функция time возвра¬
щает наилучшее представление текущего календарного времени в данной реа¬
лизации. В случае,
ется
если календарное время является недоступным, возвраща¬
(time_t) -1. Если timer
значение
не
является
нулевым
объекту,
возвращаемое значение присваивается также
на
указателем,
то
который указывает
timer.
char *asctime(const struct tm *timeptr);
Функция asctime преобразует разделенное время
timeptr, в строку следующего вида:
Sun
Sep 16 01:03:52
*ctime(const
указываемой
1973\n\0
Функция asctime возвращает указатель
char
из структуры,
на
строку.
time_t *timer);
Функция
время
ctime преобразует календарное время, указываемое timer,
виде строки. Это эквивалентно следующему вызову:
в
в местное
asctime(localtime(timer))
Функция ctime возвращает указатель, возвращаемый функцией asctime
ким
разделенным
временем
как
*gmtime(const time_t *timer);
Функция gmtime преобразует календарное время,
struct
tm
gmtime возвращает указатель
на
указываемое timer, в разде¬
Coordinated Universal Time. Функция
ленное время, выраженное как UTC
UTC
с та¬
аргументом.
этот
объект
или
нулевой указатель,
если
недоступно.
tm *localtime(const
time_t *timer);
Функция localtime преобразует календарное время, указываемое
struct
деленное
время, выраженное
указатель на этот объект.
как
местное.
Функция
size__t strtime(char *s, size_t maxsize,
const
Функция
с
struct
tm
timer, в раз¬
localtime
const
char
возвращает
*format,
*timeptr);
strtime помещает символы в массив, указываемый s,
управляющей строкой format. Строка format содержит нуль
в соответствии
или
более спе¬
цификаторов преобразования и ординарные многобайтовые символы. Все ор¬
динарные символы (включая нулевой символ ограничения строки) копируют¬
ся без изменения в массив. Если имеет место копирование между
перекрывающимися объектами, то поведение не определено. В массиве разме¬
щается не более maxsize символов. Каждый спецификатор преобразования за¬
мещается соответствующими символами, ^ак описано в приведенном ниже
списке.
Эти символы определяются категорией LC_TIME текущего
значениями,
содержащимися
в
структуре,
%a
заменяется
локальным
сокращенным
%A
заменяется
локальным
полным
%b
заменяется
локальным
сокращенным
%B
заменяется
локальным
полным
%c
заменяется
локальным
обозначением
названием
дня
обозначением
названием
представлением
дня
недели.
недели.
месяца.
месяца.
соответствующей
даты и време¬
ни.
%d
локала и
указываемой timeptr.
заменяется днем месяца как десятичным числом
(01-31).
Приложение Б
974
%H
заменяется часом (24-часовые часы) как десятичным числом (00-23).
%I
заменяется часом (12-часовые часы) как десятичным числом (01-12).
%j
%m
заменяется
днем
заменяется
месяцом
как
десятичным
числом
%M
заменяется
минутой
как
десятичным
числом
%p
заменяется локальным обозначением
как
года
числом
десятичным
(001-366).
(01-12).
(00-59).
AM/PM,
связанным с 12-часовыми
часами.
%S
заменяется
секундой
%U
заменяется
десятичным
первый
%w
день недели
как
числом
десятичным
номером
недели
года
(00-61).
(первое воскресенье
как
1) (00-53).
заменяется днем недели как десятичным числом
(0-6),
где воскресенью
соответствует номер 0.
%W
заменяется десятичным номером недели года
первый
день недели
1)
(первый
%x
заменяется
локальным
представлением
даты.
%X
заменяется
локальным
представлением
времени.
%y
%Y
заменяется
годом
века,
заменяется
годом
с
%Z
заменяется
названием
вой
пояс
не
заменяется
%%
понедельник как
(00-53).
как
веком
десятичным
как
числом
десятичным
часового
пояса
или
(00-99).
числом.
остается
пустым,
если часо¬
определен.
%.
Если спецификатор преобразования
то поведение не определено. Если
не
общее
является одним
из указанныхвыше,
число символов результата, включая
нулевой символ ограничения строки, не превышает maxsize, то функция
strtime возвращает число символов, записанных в массив, указываемый s, не
считая нулевого символа окончания строки. В противном случае возвращает¬
ся ноль и содержание массива является неопределенным.
Б.14.
Ограничения реализации
<limits.h>
Следующие макросы должны быть определены со значениями
(по абсолютной величине)
#define CHART BIT
Число бит для
не меньшими
указанных ниже.
8
наименьшего объекта, который не является битовым полем
(байт).
-127
#define SCHAR MIN
Минимальное
#define
значение
для
объекта
типа
+127
SCHAR МАХ
Максимальное значение для объекта типа
#define
signed char.
signed char.
UCHAR МАХ
Максимальное значение для объекта типа
255
unsigned char.
975
#define
0
CHAR_MIN
или
CHAR__MIN
Минимальное значение для объекта типа char.
#define
CHAR_MAX
UCHAR_MAX
Максимальное значение для объекта
#define
типа
или
SCHAR_MAX
char.
1
MB_LEN_MAX
Максимальное число байт
в
многобайтовом символе для любого из поддержи¬
ваемых локалов.
#define
Минимальное
#define
значение
для
объекта
типа
short int.
+32767
SHRT_MAX
Максимальное
#define
-32767
CHRT_MIN
для
объекта
типа
значение для
объекта
типа
значение
short int.
unsigned short int.
65535
USHRT_MAX
Максимальное
#define INTJMIN
Минимальное значение
#define INT_MAX
Максимальное значение
#define UINT_MAX
Максимальное значение
#define LONG_MIN
Минимальное значение
-32767
для
объекта
типа
int.
+32767
для
объекта
типа
int.
65535
для
объекта
типа
unsigned int.
-2147483647
для
объекта
типа
long int.
+2147483647
#define LONG_MAX
Максимальное значение
для
объекта
#define ULONG_MAX
Максимальное значение
для
объекта типа
типа
long int
4294967295
unsigned long int
<float.h>
#define FLTJROUNDS
Режим округления при
-1
сложении чисел с плавающей точкой.
неопределенный
0
к нулю
1
к ближайшему
2
к положительной бесконечности
3
к отрицательной бесконечности
Следующие макросы должны быть определены со значениями не меньшими
(по абсолютной величине) указанных ниже.
Приложение Б
976
#define
FLT
2
RADIX
Основание экспоненциального представления, b
#define FLT_MANT_DIG
#define LDBLJMANT_DIG
#define DBL_MANT_DIG
Количество цифр (в
системе с основанием
FLT_RADIX)
для чисел с плаваю¬
щей точкой, p.
б
#define FLT_DIG
10
#define DBLJDIG
10
#define LDBL_DIG
Количество десятичных цифр, q, такое, что любое число с плавающей точкой,
содержащее q десятичных цифр, может быть округлено до числа с плавающей
точкой по основанию b, содержащего p цифр, и преобразовано обратно без из¬
менения q десятичных цифр.
#define FLT_MIN_EXP
#define DBLJMINJEXP
#define LDBL_MIN_EXP
Минимальное отрицательное целое,
такое, что число
FLT_RADIX,
возведен¬
ное в эту степень минус 1, является нормализованным числом с плавающей
точкой.
#define
#define
#define
Минимальное отрицательное целое, такое,
в
-37
FLTJMIN_10JEXP
DBL_MIN_10_EXP
LDBL_MIN_10_EXP
допустимом
диапазоне
-37
-37
что
нормализованных
10
в
чисел
данной
плавающей точкой.
степени находится
с
#define FLT_MAXJEXP
#define DBL_MAXJEXP
#define LDBL_MAXJEXP
Максимальное целое, такое,
нус 1,
что FLT_RADIX, возведенное в эту степень ми¬
является представимым конечным числом с плавающей точкой.
+37
+37
+37
#define FLT_MAX_10JEXP
#define DBL_MAX_10_EXP
#define LDBL МАХ 10 EXP
Максимальное целое, такое, что 10 в данной степени находится в допустимом
диапазоне представимых конечных чисел с плавающей точкой.
Следующие макросы
должны быть определены со значениями не меньшими
указанных ниже.
lE+37
lE+37
lE+37
#define FLT_MAX
#define DBL_MAX
#define LDBL МАХ
Максимальное представимое конечное число с
Следующие макросы должны быть определены
указанных
ниже.
плавающей точкой.
со значениями
не
большими
977
Стандартная библиотека
lE-5
lE-9
lE-9
#define FLT_EPSILON
#define DBL_EPSILON
#define LDBL_EPSILON
Разность между 1.0 и наименьшим значением, большим 1.0, которая предста¬
вима в данном типе с плавающей точкой.
#define FLT_MIN
#define DBL_MIN
#define LDBL_MIN
Минимальное нормализованное
32
Эшс.801
lE-37
lE-37
lE-37
положительное число
с
плавающей точкой.
п
p
и
л
о
ж
e
н
и
e
в
Таблица приоритета операций
т
Операции
ним.
расположены в порядке
убывания
приоритета от верхних к ниж¬
п
л
и
p
ж
о
e
н
и
e
г
Набор
ASCII
символов
0
1
2
3
4
5
6
7
8
9
о
nul
soh
stx
etx
eot
enq
ack
bel
bs
ht
1
nl
vt
ff
cr
so
si
dle
dc1
dc2
dc3
2
dc4
nak
syn
etb
can
em
sub
esc
fs
gs
i
-
#
$
%
&
/
0
rs
3
us
sp
4
(
)
*
5
2
3
6
<
7
8
,
4
5
6
7
8
9
=
>
?
@
А
В
С
D
E
F
G
H
I
J
К
L
M
N
О
P
Q
R
S
T
U
V
W
X
а
b
с
I
m
v
w
А
9
Z
[
\
10
d
e
f
rt:
n
о
x
У
LJLl
Цифры
слева от
последнюю
32*
'&'
цифру
38.
]
_
g
h
i
J
k
P
q
r
s
t
u
z
{
l
}
таблицы
валента кода символа
символа
1
+
-
а
кода символа.
I
del
цифрами десятичного экви¬
цифры вверху таблицы представляют собой
Например, код символа 'F' равен 70, а код
являются первыми
(0-127),
-
,
п
и
p
л
о
Системы
e
ж
н
и
e
счисления
Цели
Усвоить основные понятия систем счисления, такие,
как основание системы счисления, позиционное значе¬
ние и числовое значение.
методику работы с числами, представленными
различных системах счисления: двоичной, восьме¬
Понять
в
ричной
и
Научиться
ричных
и
шестнадцатеричной.
записывать двоичные числа в виде восьме¬
шестнадцатеричных чисел.
Научиться преобразовывать восьмеричные
цатеричные
Научиться
числа
к
выполнять
и шестнад¬
двоичному виду.
прямое
и
обратное преобразова¬
ние десятичных чисел и их двоичных,
восьмеричных
и
шестнадцатеричных эквивалентов.
Понять двоичную
арифметику
цательных двоичных
нения
до
двух.
и
представление отри¬
чисел с помощью метода допол¬
981
Стандартная библиотека
Содержание
Д.1. Введение
Д.2. Представление двоичных
и шестнадЦатеричных
чисел как восьмеричных
Д.З. Преобразование восьмеричных
и
шестнадцатеричных чисел
в двоичные
Д.4. Преобразование двоичных, восьмеричных
чисел
Д.5. Перевод десятичных
или
или
шестнадцатеричных
в десятичные
чисел
в двоичные,
восьмеричные
шестнадцатеричные
Д.6. Отрицательные двоичные
Резюме
числа: дополнение до
Упражнения для самоконтрОля
Упражнения
двух
Ответы на упражнения для само¬
контроля
Д.1. Введение
В данном приложении
мы
представим
основные системы счисления, кото¬
рые используют в программировании на языке
да
разрабатываются
программы,
на «машинном» уровне.
К
мы, сетевое программное
также
приложения,
С,
особенно
в тех случаях, ког¬
требующйе взаимодействия
с
оборудованием
таким программам относятся операционные систе¬
обеспечение, компиляторы, системы баз данных, а
требуется высокая эффективность исполне¬
от которых
ния.
Когда
мы
пишем
целое,
например,
227
или
-63,
то используем
десяти¬
10) систему счисления. Исходными цифрами в десяти¬
чную (по
чной системе являются 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Наименьшей из них является
основанию
0, наибольшей 9, которая
на единицу
себя» компьютер использует
Двоичная
0
и
чем
основание
основанию
является
0, наибольшей 1
10. «Внутри
2) систему
система счисления содержит только две исходных
1. Наименьшей
вание
меньше,
двоичную (по
счисления.
цифры,
а именно
на единицу меньше, чем осно¬
2.
Мы увидим, что двоичные числа,
или на языке высокого
как правило,
значительно длиннее их
Программисты, работающие на языке ассемблера
уровня, подобном С, предоставляющем возможность
десятичных эквивалентов.
982
Приложение Д
«опуститься» на машинный уровень программирования, находят работу с дво¬
ичными числами весьма обременительной. Поэтому две другие системы счис¬
восьмеричная (по основанию
ления
16)
нию
8)
шестнадцатеричная (по основа¬
удобной запись
и
являются очень популярными, поскольку делают
двоичных чисел.
В восьмеричной системе исходные цифры изменяются
до 7. Поскольку как двоичная, так и восьмеричная системы
меньше
цифр,
чем десятичная, то их
цифры
в
диапазоне
от
0
счисления имеют
такие же, как и в
десятичной
сис¬
теме.
В шестнадцатеричной системе
ку здесь
требуется
шестнадцать
возникает
цифр
проблема
шая со значением, эквивалентным десятичному числу
основания
записи чисел, посколь¬
наименьшая из которых
0
и
наиболь¬
15 (на единицу
меньше
16). Для представления шестнадцатеричных чисел со значениями
(в десятичном исчислении) условились использовать буквы от А до
от 10 до 15
F. Так, в шестнадцатеричном представлении мы можем написать, например,
число 876, имеющее десятично-подобный вид и содержащее только цифры;
число 8A55F, содержащее цифры и буквы, и, например, число FFE, состоящее
только из
вам,
букв. Порой
подобно
словам,
шестнадцатеричное число можно произносить по
например FACE или FEED
странным программисту, привыкшему
работать
это
может
бук¬
показаться
с десятичными числами.
Двоичные
цифры
Восьмеричные
цифры
Десятичные
цифры
Шестнадцатеричные цифры
0
0
0
0
1
1
1
1
2
2
2
3
3
3
4
4
4
5
5
5
6
6
6
7
7
7
8
8
9
9
Рис. Д.1. Цифры двоичной, восьмеричной, десятичной
и
А (десятичное
значение
10)
В (десятичное
значение
11)
С (десятичное
значение
12)
D (десятичное
значение
13)
E (десятичное
значение
14)
F (десятичное
значение
15)
шестнадцатеричной
систем счисления
Системы счисления
983
В любой из указанных систем счисления используется позиционная нота¬
каждое место, на котором стоит
ция
цифра,
имеет различное позиционное
Например, в десятичном числе 937 (цифры 9, 3 и 7 называют симво¬
лическими значениями), мы говорим, что 7 записана в позиции единиц, 3
в
значение.
в позиции сотен. Отметим, что каждая из этих пози¬
позиции десятков, 9
ций представляет собой соответствующую степень основания 10; эти степени
начинаются с 0 и увеличиваются на 1 по мере перемещения влево вдоль числа
(рис. Д.З).
Двоичная
Восьмерич¬
Десятичная
Шестнадцатеричная
система
ная система
система
система
2
8
10
16
цифра
0
0
0
0
Наибольшая цифра
1
7
9
F
Атрибут
Основание
Наименьшая
Рис. Д.2. Сравнение двоичной, восьмеричной, десятичной
и
шестнадцатеричной
систем
счисления
Позиционные
значения в
десятичной
системе счисления
Десятичная цифра
9
3
7
Имя позиции
Сотен
Десятков
Единиц
Позиционное
значение
100
10
1
Позиционное
значение как
102
101
10°
степень основания
(10)
Рис. Д.З. Позиционные
значения в
десятичной
системе счисления
Для больших десятичных чисел следующими позициями слева будут по¬
(10 в 3-й степени), десятков тысяч (10 в 4-й степени), сотен ты¬
(10 в 5-й степени), миллионов (10 в 6-й степени), десятков миллионов (10 в
зиции тысяч
сяч
7-й степени) и т.д.
В двоичном числе 101
мы видим, что крайняя правая единица записана в
0
находится в позиции двух и крайняя левая единица в пози¬
позиции единиц,
ции четырех. Заметим, что каждая из этих позиций представляет собой сте¬
пень основания (числа 2); эти степени начинаются с 0, увеличиваясь на едини¬
цу по мере смещения по числу влево (рис. Д.4).
Для больших двоичных чисел следующими позициями слева будут пози¬
ции восьми (2 в 3-й степени), шестнадцати (2 в 4-й степени), тридцати двух
(2 в 5-й степени), шестидесяти четырех (2 в 6-й степени) и т.д.
Для восьмеричного числа 425 мы говорим, что 5 записана в позиции еди¬
ниц, 2
в
позиции восьми и 4
каждая из этих
ни начинаются
в позиции
шестидесяти четырех. Заметим,
что
позиций представляет степень основания (числа 8); эти степе¬
с 0, увеличиваясь на единицу по мере смещения по числу влево
(рис. Д.5).
Для больших восьмеричных чисел следующими слева будут позиции пяти¬
сот двенадцати (8 в 3-й степени), четырех тысяч девяноста шести (8 в 4-й),
тридцати двух тысяч семисот шестидесяти восьми (8 в 5-й степени) и т.д.
Приложение Д
984
Позиционные
значения в
двоичной
системе счисления
1
Двоичная цифра
1
0
Имя позиции
Четырех
Двух
Единиц
4
2
1
~2г
^
Позиционное
значение
Позиционное
значение как
1°
(2)
степень основания
Рис. Д.4. Позиционные значения в двоичной системе счисления
Позиционные значения в восьмеричной системе счисления
Восьмеричная цифра
4
2
5
Имя позиции
Шестидесяти четырех
Восьми
Единиц
Позиционное значение
64
8
1
81
8°
J2
Позиционное значение как
степень основания (8)
Рис. Д.5. Позиционные значения в
восьмеричной
системе счисления
шестнадцатеричного числа 3DA мы говорим, что А находится в пози¬
в позиции шестнадцати и 3
в позиции двухсот пятидеся¬
ции единиц, D
Для
ти шести.
Заметим также,
что каждая из этих
основания системы счисления
(числа 16);
позиций представляет степень
0, увели¬
эти степени начинаются с
(рис. Д.6).
чиваясь на единицу по мере смещения по числу влево
Для
будут
3), шестидесяти
длинных шестнадцатеричных чисел следующими слева
ции четырех тысяч
тысяч пятисот
девяноста
тридцати
Позиционные значения в
шести
шести
(16
шестнадцатеричной
(16
в
в степени
4-й степени)
пози¬
пяти
и т.д.
системе счисления
Шестнадцатеричная цифра
3
D
А
Имя позиции,
Двухсот пятидесяти
Шестнадцати
Единиц
шести
Позиционное
значение
256
16
1
Позиционное
значение как
162
161
N5°
степень основания
(16)
Рис. Д.6. Позиционные
значения в
шестнадцатеричной
системе счисления
Д.2. Запись двоичных чисел в виде восьмеричных
и
Основное
шестнадцатеричных
применение
восьмеричных
и
шестнадцатеричных
чисел
в
компьютерных вычислениях заключается в сокращении длины записи двоич¬
ного
представления
чисел.
Рис. Д.7 наглядно демонстрирует,
как
большие
Системы счисления
985
двоичные числа можно записать в очень
счисления с
большим основанием,
краткой, сжатой форме
в системах
чем двоичная система.
Двоичное
представление
Восьмеричное
представление
Шестнадцатеричное
представление
_0
0
0
0
1
1
1
1
2
10
2
2
3
11
3
3
4
100
4
4
5
101
5
5
6
110
6
6
7
111
7
7
8
1000
10
8
9
1001
11
9
10
1010
12
А
11
1011
13
в
12
1100
14
С
13
1101
15
D
14
1110
16
E
15
1111
17
F
1000
20
10
Десятичное
число
Jf
Рис. Д.7. Десятичные, двоичные, восьмеричные и шестнадцатеричные эквиваленты
Весьма важцым соотношением между этими системами является то, что
как восьмеричная, так и шестнадцатеричная системы счисления имеют осно¬
вания (соответственно 8 и 16), являющиеся степенями основания двоичной си¬
стемы
его
(числа 2). Рассмотрим следующее двенадцатизначное двоичное число и
восьмеричный и шестнадцатеричный эквиваленты. Попробуйте, если уда¬
стся, установить взаимосвязь между
двоичной
ным и шестнадцатеричным эквивалентами.
записью числа и его восьмерич¬
Ответ
на это дают следующие чис¬
ла.
Двоичное
Восьмеричный
Шестнадцатеричный
число
эквивалент
эквивалент
100011010001
4321
8D1
Чтобы понять,
как можно легко
преобразовать
двоичное число в восьме¬
ричное, просто разделите щшведенное выше 12-значное двоичное число на
группы по три бита в каждой и затем запишите эти группы над соответствую¬
щими;
цифрами восьмеричного
эквивалента, следующим
100
011
010
001
4
3
2
1
образом:
986
Приложение Д
Заметьте, что восьмеричные цифры, записанные под каждой группой из
бит, точно соответствуют восьмеричным эквивалентам трехзначных дво¬
ичных чисел, приведенным на рис. Д.7.
Подобным же образом можно заметить закономерность преобразования
числа из двоичной системы в шестнадцатеричную. А именно,
разделите
12-значное двоичное число на группы по четыре бита в каждой и затем запи¬
шите эти группы над соответствующими цифрами шестнадцатеричного экви¬
трех
валента, как это сделано ниже:
1000
1101
0001
8
D
1
Опять
же заметьте, что шестнадцатеричные
дой группой
лентам
из четырех
бит,
цифры,
записанные под каж¬
точно соответствуют шестнадцатеричным эквива¬
4-значных двоичных чисел, приведенным
Д.7.
на рис.
Д.З. Преобразование восьмеричных
и
В предыдущем разделе
восьмеричные
групп
или
мы видели, как
шестнадцатеричные
цифр
из двоичных
ющего эквивалентного
кой
чисел в двоичные
шестнадцатеричных
и перезаписи
преобразовать
эквиваленты
каждой
двоичные числа в их
путем
формирования
из групп с помощью соответству¬
восьмеричного или шестнадцатеричного значения.
же подход можно использовать в
Та¬
обратном
порядке при нахождении двоич¬
ного эквивалента для данного восьмеричного или шестнадцатеричного числа.
Например, восьмеричное число 653 переводится в двоичное путем простой
записи
6
как его трехзначного двоичного эквивалента
110, 5
как
3-значного
как 011, так что в целом число записывается
двоичного эквивалента 101, 3
в виде 9-значного двоичного числа 110101011.
Шестнадцатеричное число FAD5 преобразуется в двоичное с помощью за¬
писи F как 4-значного двоичного эквивалента 1111, числа А как двоичного
1010, числа D как 1101 и 5 как 0101, что дает 16-значное число
1111101011010101.
Д.4. Преобразование
или
из
двоичной, восьмеричной
шестнадцатеричной форм
Поскольку
числа в
десятичную
работать в десятичной системе счисления, то
преобразовывать двоичные, восьмеричные или шест¬
надцатеричные числа в десятичные для того, чтобы почувствовать его дейст¬
вительное значение. Наши диаграммы в разделе Д.1 выражают позиционные
значения в десятичной системе. Для преобразования числа из какой-либо сис¬
мы
привыкли
очень часто приходится
темы
в
чный
эквивалент
десятичную
ее
необходимо умножить каждую цифру
позиционного
значения
и
все
числа на десяти¬
результаты
просуммиро¬
примера, двоичное число 110101 преобразуется в десятичное чис¬
ло 53 так, как показано на рис. Д.8.
Для преобразования восьмеричного числа 7614 в соответствующее десяти¬
вать.
Для
чное 3980 используется аналогичная методика с применением в этом случае
восьмеричных позиционных значений, как показано на рис. Д.9.
987
Системы счисления
Преобразование двоичного
Позиционные
I
числа в десятичное
32
16
8
4
2
1
1
1
0
1
0
1
Произведения
1*32=32
1*16=16
0*8=8
1*4=4
0*2=0
1*1=1
Сумма
=32+16+8+4+0+1=53
значения
Символические
значения
Рис. Д.8.
Преобразование восьмеричного
Позиционные
значения
Символические
двоичного числа в десятичное
Преобразование
числа в десятичное
512
64
8
1
7
6
1
4
7*512=3584
6*64=384
1*8=8
4*1=4
значения
Произведения
=
Сумма
Рис. Д.9.
3584 + 384 + 8 + 4
Преобразование
=
3980
восьмеричного числа в десятичное
Преобразование шестнадцатеричного числа AD3B в десятичное 44347 осу¬
ществляется аналогично с применением в этом случае шестнадцатеричных по¬
зиционных значений, как показано на рис. Д.10.
Преобразование
шестнадцатеричного числа в десятичное
Позиционные значения
4096
256
16
1
Символические
значения
А
D
3
В
Произведения
A*4096=40960
D*256=3328
3*16=48
B*1=11
=
Сумма
Рис. Д.10.
40960 + 3328 + 48 + 11
Преобразование
Д.5. Преобразование
восьмеричное
Преобразования
из соответствующих
разования
=
44347
шестнадцатеричного числа в десятичное
десятичного
или
числа в
шестнадцатеричное
в предыдущих разделах естественным
соглашений
о
двоичное,
позиционной
образом
записи чисел.
вытекали
Способы преоб¬
из десятичного представления числа в двоичное, восьмеричное или
шестнадцатеричное также вытекают из данных
положений.
Предположим, что мы хотим перевести десятичное число 57 в двоичное.
Мы начнем с записи в ряд позиционных значений справа налево в порядке их
возрастания.
Ненужные
Позиционные
члены ряда
значения:
64
отбросим. Итак,
32
16
8
4
сначала запишем:
2
1
Приложений Д
988
Отбросим
член ряда с позиционным значением
Позиционные
32
значения:
16
8
64
:
2
4
1
Затем начнем работать слева направо. Разделив 57 на 32 видим, что в 57
укладывается одно значение 32 с остатком 25, поэтому запишем 1 в столбец
с 32. Далее, делим 25 на 16; в 25 укладывается одно значение 16 и остаток 9,
поэтому записываем 1 в столбец с 16. Делим остаток 9 на 8, получаем единицу,
которую запишем в столбец с 8. При делении остатка 1 на позиционные значе¬
ния
в
следующих
двух
позициях
получаем
нули,
которые
столбцы соответствующие 4 и 2. И, наконец, деление 1
торую пишем в столбце с 1. В результате:
Позиционные
значения:
Символические
Таким
значения:
образом,
на
32
16
8
4
2
1
1
1
1
0
0
1
57
десятичному числу
и
записываем
1 даст единицу,
соответствует двоичное
в
ко¬
число
111001.
Для преобразования десятичного
значений
нем с записи позиционных
103
числа
ряд до
в
в восьмеричное мы также нач¬
тех пор, пока не получим пози¬
ционное значение, превышающее данное десятичное число.
ся
последний
отбросим
член ряда, поэтому
Нам
не
потребует¬
Таким образом, сначала
его.
запи¬
шем:
Позиционные
значения:
Затем, отбрасывая
Позиционные
512
64
1
8
член с позиционным значением
64
значения:
512, получаем:
1
8
Далее работаем со столбцами слева направо. Делим 103 на 64, получаем
единицу и 39 в остатке, поэтому записываем 1 в столбец с 64. Делим остаток
39 на 8, получаем 4 и 7 в остатке; записываем 4 в столбец с 8. Наконец, деля
остаток 7 на 1, получаем значение 7, которое записываем в столбец с 1. В резу¬
льтате:
Позиционные
значения:
Символические
же
значения:
64
8
1
1
4
7
Таким образом, десятичному числу 103 соответствует восьмеричное 147.
Для преобразования десятичного числа 375 в шестнадцатеричное мы так¬
начнем с записи позиционных значений в ряд, пока не получим позицион¬
ное значение, превышающее данное десятичное число.
следний
член ряда, поэтому
Позиционные
значения:
Затем, отбрасывая
Позиционные
отбросим
4096
его.
256
Нам
Таким образом,
16
256
16
потребуется
по¬
1
член с позиционным значением
значения:
не
сначала запишем:
4096, получаем
1
Делим 375 на 256, получаем единицу и 119
столбец с 256. Делим остаток 119 на 16, по¬
лучаем 7 и 7 в остатке; записываем 7 в столбец с 16. Наконец, деля остаток 7
на 1, получаем значение 7, которое записываем в столбец с 1. В результате:
Далее работаем
слева направо.
в остатке, поэтому записываем
Позиционные
значения:
Символические
ное
значения:
1
в
256
16
1
1
7
7
Таким образом, десятичному числу 375 соответствует шестнадцатерич¬
177.
Системы счисления
989
двоичные числа: дополнение
Д.6. Отрицательные
до двух
Обсуждение
боте
в данном приложении до сих пор
с положительными числами.
В
было сфокусировано
этом разделе мы
объясним,
терах представляются отрицательные числа с помощью нотации
до двух. Сначала поясним,
на ра¬
как в компью¬
дополнения
дополнение до двух и
затем покажем, почему оно представляет отрицательное значение данного дво¬
ичного числа.
сел.
как
формируется указанное
Предположим, что имеем машину
Пусть в программе объявлено:
int value
с
32-битным представлением целых
13;
=
Тогда 32-битовое представление value
00000000
чи¬
00000000
00000000
будет
иметь вид
00001101
Для представления отрицательного значения value мы сначала использу¬
ем применяемую в языке С операцию поразрядного дополнения (~):
ones_complementjDf_value
=
~value;
Во внутреннем представлении ~value представляет собой value, в котором
каждый бит заменен на противоположное значение: единицы заменены нуля¬
ми, а нули
единицами:
value:
00000000
-value
00000000
(т.е.
11111111
00000000
дополнение value до
11111111
Для образования
11111111
Дополнение value
В
11111111
11110011
действительно равно -13,
Если это число
0. Попробуем проделать
00000000
00000000
00000000
00001101
11111111
11111111
11110011
00000000
00000000
00000000
00000000
из
крайнего
левого разряда
вительно в результате получили ноль.
то,
прибавив
к нему
13,
мы дол¬
это:
+11111111
Бит переноса
прибавим единицу
этом случае имеем:
до двух
11111111
жны получить
единицы):
11110010
дополнения value до двух мы просто
к дополнению его до единицы.
11111111
00001101
Если
отбрасывается, так что мы дейст¬
прибавим дополнение числа до
мы
единицы к самому числу, то в результате получим 1 во всех битах. Ключ к по¬
лучению результата со всеми нулями в том, что дополнение до двух на 1 боль¬
ше, чем дополнение до единицы. Поэтому прибавление 1 привело к тому, что в
каждой позиции получился ноль с переносом единицы. Этот перенос прошел
по всему числу до крайней левой позиции и был за ней отброшен, что в резуль¬
тате дало все нули.
Разность двух
x
=
а
-
чисел
value;
представляется в компьютере с использованием дополнения value до двух сле¬
дующим
образом:
990
Приложение Д
x
=
а
(~value
+
Предположим,
+
1).
что число а равно
27
и
value,
как и ранее, равно
13. Если
дополнение value до двух действительно представляет отрицательное
ние
value,
последней формуле
то по
вайте проделаем
а
(т.е.
+
(~value
эти операции:
00000000
00000000
00000000
00011011
+11111111
11111111
11111111
11110011
00000000
00000000
00000000
00001110
27)
Ч-
значе¬
14. Да¬
мы должны получить в результате
1)
в результате чего мы
действительно получаем 14.
Резюме
мы в программе на языке С записываем целое, такое, как 19, 227
или -63, то по умолчанию предполагается, что число представлено в де¬
сятичной (по основанию 10) системе счисления. Цифрами в десятичной
Когда
0, 1, 2, 3, 4, 5, 6, 7, 8 и 9. Наименьшей
0, наибольшей 9, которая на единицу меньше основа¬
системе счисления являются
цифрой
ния 10.
является
внутреннего представления компьютер использует двоичную (по
2) систему счисления. Двоичная система счисления имеет
Для
основанию
две цифры,
наибольшей 1, что
только
Восьмеричная (по
нию
16) системы
0
а именно
на
основанию
счисления
0,
меньше,
8)
и
шестнадцатеричная
являются
прежде всего потому, что дают
ных
1. Наименьшей цифрой является
чем основание 2.
и
единицу
весьма
(по
основа¬
распространенными
удобную форму короткой
записи двоич¬
чисел.
Цифры
в
восьмеричной
системе счисления изменяются в диапазоне от
0 до 7.
В шестнадцатеричной
ний,
поскольку
системе
требуется
счисления
шестнадцать
имеется
цифр
льшая со значением, эквивалентным десятичному
ше,
чем
основание
16). По
системы
Каждая
0
и
от
которой
записывается
цифра,
мень¬
представления
А до F, соответству¬
для
система счисления использует позиционную запись
позиция, в
наибо¬
15 (на единицу
соглашению
шестнадцатеричных цифр используются буквы
ющие десятичным значениям от 10 до 15.
проблема обозначе¬
наименьшая
каждая
имеет различное позиционное
значение.
Весьма
важным
является
то,
что
восьмеричная
и
шестнадцатеричная
(соответственно 8
и 16), представ¬
ляющие собой степени 2, т.е. основания двоичной системы счисления.
системы счисления имеют основания
Для
перевода восьмеричного числа в
нить каждую восьмеричную
цифру
Двоичное необходимо просто
на
трехзначный двоичный
заме¬
эквива¬
лент.
перевода шестнадцатеричного числа в двоичное необходимо просто
заменить каждую шестнадцатеричную цифру на четырехзначный дво¬
Для
ичный эквивалент.
Системы счисления
Поскольку
991
мы привыкли
часто бывает
что
это
за
в
число
десятичное
«на
Для преобразования
десятичной
в
самом
числа из
Компьютеры
значения
и
чтобы лучше почувствовать,
того,
для
деле».
какой-либо
ходимо умножить каждую цифру числа
позиционного
системе счисления, то
перевести двоичное, восьмеричное или шестнад¬
удобным
цатеричное число
работать
все
системы в десятичную
на
десятичный
необ¬
эквивалент ее
просуммировать.
результаты
представляют отрицательные числа с использованием но¬
тации дополнения до двух.
Чтобы получить
сначала
двоичной
в
образуется
ния
операции
Тем
самым
его
системе
дополнение
до
отрицательное
единицы
значение
посредством
числа,
примене¬
С для поразрядного дополнения (поразрядного НЕ, ~).
числа оказываются обращенными (1 вместо 0 и наобо¬
биты
рот). Образование дополнения
прибавлением
числа до двух осуществляется
простым
единицы к дополнению его до единицы.
Терминология
восьмеричная система счисления
двоичная система счисления
десятичная система счисления
преобразование
система
счисления
по
основанию
метод дополнения до единицы
система
счисления
по
основанию
16
метод дополнения до двух
основание системы счисления
система
счисления
по
основанию
2
система счисления
по
основанию
8
отрицательная величина
позиционная запись
цифра
числовое
позиционное значение
шестнадцатеричная
системы
в
из
одной
другую
10
значение
система
счисления
поразрядная операция
дополнения
числа
счисления
(~)
Упражнения для самоконтроля
Д.1. Основаниями десятичной, двоичной, восьмеричной
ричной
систем
счисления
и
,
Д.2.
В
общем
данного
ше/меньше) цифр,
чем
Д.З. (Верно/неверно) Одной
и
восьмеричное
двоичного
двоичное
из
и
шестнадцате¬
соответственно
,
.
случае десятичное,
представление
Д.4.
являются
числа
шестнадцатеричное
будет содержать (боль¬
представление.
главных
причин
использования
десяти¬
чной системы счисления является то, что эта
для со¬
кращенной записи двоичных чисел
чной цифры на группу из четырех
десяти¬
путем
форма удобна
простой замены
двоичных
бит.
представление боль¬
шого двоичного числа является наиболее кратким.
(Восьмеричное/шестнадцатеричное/десятичное)
Д.5. (Верно/неверно) Наибольшая цифра любой
единицу
больше
ее
Д.6. (Верно/неверно) Наименьшая цифра любой
единицу
меньше
ее
системы
счисления
на
системы
счисления
на
основания.
основания.
Приложение Д
992
Д.7. Позиционное
значение для
крайней правой цифры любой
двоичной, восьмеричной, десятичной
счисления
теричной
всегда равно
системы
или шестнадца¬
.
значение слева от крайней правой цифры любого чис¬
двоичной, восьмеричной, десятичной или шестнадцатеричной
Д.8. Позиционное
ла
в
системах
Д.9.
всегда
равно
Заполните пропуски
.
таблице позиционных значений для
в
данных
систем счисления:
1000
десятичная
шестнадцатеричная
двоичная
...
100
1
10
256
512
восьмеричная
Д.10. Преобразуйте
двоичное
число
110101011000
в
восьмеричное
и
шестнадцатеричное представления.
Д.11. Преобразуйте
шестнадцатеричное число FACE
Д.12. Преобразуйте
восьмеричное число 7316 в двоичное.
в
Д.13. Преобразуйте шестнадцатеричное число 4FEC
(Подсказка: сначала переведите 4FEC в двоичное
в
двоичного
двоичное.
в
восьмеричное.
число, а затем из
восьмеричное).
Д.14. Преобразуйте
двоичное число 1101110 в десятичное.
Д.15. Преобразуйте
восьмеричное число 317 в десятичное.
Д.16. Преобразуйте
шестнадцатеричное число EFD4
Д.17. Преобразуйте
десятичное число 177 в двоичное,
в
десятичное.
восьмеричное и
шестнадцатеричное.
Д.18. Укажите
найдите
Д.19.
двоичное представление десятичного числа 417.
дополнения 417 до единицы и до двух.
Какой результат получится,
с самим числом?
если
дополнение
числа
до
Затем
единицы
сложить
Ответы на упражнения для самоконтроля
Д.1.
10, 2, 8, 16.
Д.2. Меньше.
Д.З. Неверно.
Д.4. Шестнадцатеричное.
Д.5. Неверно
цу
меньше
Д.6. Неверно
ся
Д.7.
наибольшая цифра
в
любой
наименьшей цифрой
в
ноль.
1 (основание
Д.8. Основанию
системе счисления на едини¬
основания.
в
нулевой степени).
системы
счисления.
любой
системе счисления являет¬
Системы счисления
Д.9.
993
Заполните пропуски
в
таблице позиционных значений
для данных
систем счисления:
десятичная
1000
100
10
1
шестнадцатеричная
двоичная
4096
256
16
1
8
4
2
1
512
64
8
1
восьмеричная
Д.Ю.Восьмеричное 6530;
шестнадцатеричное D56.
Д.Н.Двоичное 1111 1010 1100 1110.
Д.12.Двоичное 111 011 001 110.
Д.13.Двоичное 0 100 111 111 101 100; восьмеричное 47754.
Д.14.Десятичное 2+4+8+32+64=110.
Д. 15 .Десятичное 7+1*8+3*64=7+8+192=207.
Д. 16.Десятичное 4+13*16+15*256+14*4096=61396.
Д.17.Перевод
десятичного числа 177
в двоичное:
256
128
128
64
64
32
32
16
8
4
4
2
1
8
16
2
1
(l*128)+(0*64)+(l*32)+(l*16)+(0*8)+(0*4)+(0*2)+(l*l)
10110001
в восьмеричное:
512
64
8
64
8
1
1
(2*64)+(6*8)+(l*l)
261
в шестнадцатеричное:
256
16
16
1
1
(11*16) 4- (1*1)
(B*16)+(1*1)
Bl
Д.18.Двоичное:
512
256
128
256
128
64
64
32
32
16
8
16
8
4
4
2
1
2
1
(1*256) + (1*128) + (0*64) + (1*32) + (0*16) + (0*8) + (0*4) + (0*2) + (1*1)
110100001
Дополнение
до единицы:
001011110
Дополнение
до двух:
001011111
Проверка:
исходное двоичное число + его дополнение до двух
110100001
001011111
000000000
Д.19.Ноль.
Приложение Д
994
Упражнения
Д.20.Некоторые утверждают,
теме
счисления
что наши вычисления
с основанием
на большее количество чисел,
дет наименьшая
цифра
в
системе
может служить символом для
счисления? Чему при
рех
были бы проще
в сис¬
12, поскольку 12 делится без остатка
чем 10 (для основания 10). Какой бу¬
счисления
с
основанием
наибольшей цифры
в
данной
12? Что
системе
этом равны позиционные значения для четы¬
крайних правых позиций?
Д.21.Как наибольшая цифра
в
обсуждавшихся выше системах счисления
первой цифры слева от край¬
соотносится с позиционным значением
ней правой цифры
Д.22.3аполните
для любого числа в данных системах счисления?
пропущенные позиции в
ных значений для
каждой
1000
десятичная
по
основанию
б
по
основанию
13
по
основанию
3
Д.23.Преобразуйте
.
.
.
.
.
.
.
.
10
.
.
число
систем
.
.
.
..
.
.
.
.
.
.
.
.
.
.
позицион¬
счисления:
1
б
.
169
.
следующей таблице
указанных
100
27
двоичное
из
100101111010
в
восьмеричное
и
в
шестнадцатеричное.
Д.24.Преобразуйте
шестнадцатеричное число 3A7D в двоичное.
Д.25.Преобразуйте
шестнадцатеричное число 765F в восьмеричное.
сказка: сначала
ичное
число
преобразуйте 765F
в восьмеричное.)
Д.26.Преобразуйте
двоичное число 1011110 в десятичное.
Д.27.Преобразуйте
восьмеричное число 426 в десятичное.
Д.28.Преобразуйте
шестнадцатеричное число FFFF в десятичное.
Д.29.Преобразуйте
десятичное
число
(Под¬
в двоичное число и затем это дво¬
299
в
двоичное,
восьмеричное
и
шестнадцатеричное представления.
двоичное представление десятичного числа 779.
напишите дополнения 779 до единицы и до двух.
Д.ЗО.Приведйте
Д.31.Каков будет
Затем
результат сложения дополнения числа до двух и самого
числа?
Д.32.Приведите
дополнение до двух целого числа со значением -1 для ма¬
шины с 32-битным представлением целых чисел.
Предметный
char 154, 196
cin (входной поток) 628, 880, 881
class 630, 636, 670
clog 880, 882
COBOL 38
Concurrent С 42
const 316, 609, 641
continue 156
cout (выходной поток) 628, 880, 882
СРИ(ЦПУ) 33
ctype.h 373
dequeue 545
double 147, 196
#define 589, 590
#define NDEBUG 594
#elif 592
#else 592
#endif 592
#error 593
#if 592
#ifdef 592
#ifndef 592
#include "filename" 588
#include <filename> 588
#line 594
#pragma 593
#undef 591
*
в качестве точности
430
*
ширины поля
в качестве
E
430
\ (обратная дробная черта,
символ продолжения) 591
DATE
595
FILE
595
_LINE_
595
_STDC_ 595
TIME
595
<ctrl-z> 151
<return><ctrl-d> 151
В
А
a.out в UNIX
a[i]
589
249
a[iIj] 277
abort 594
606
argc
указатель
EBCDIC 389
endl 884
enqueue 545
eofbit 906
escape-код 56, 433
-
-
-
-
V 433
\" 433
\? 433
--\\ 433
\a 433
\b 433
\f 433
\n 433
--\r 433
\t 433
\v 433
евсаре-последовательность 433
-
-
-
-
-
-
-
-
-
-
-
-
-
-
argv 606
ASCII 389
ese-символ
asm
EXIT_FAILURE 609
EXIT_SUCCESS 609
636
assert
594
assert.h
atexit
378
atoi
378
atol
379
badbit
break
886, 906
151, 156
С
D
С 36
С++ 43, 626
calloc 615
catch 636
cerr
55
609
594
609
atof
exit
880, 882
F
G
failbit 886
false 68
fclose 496
feof 495
fgetc 493
fgets 493
FIFO (первым пришел
первым ушел)
105, 107, 196
fopen 495
FORTRAN 38
float
fprintf
496
545
Как программировать на С
996
putchar 382
puts 382, 383
raise 613
rand 199
RAND_MAX 199
realloc 616
return 190, 193
rewind 499
rvalue ("значение справа") 162
fputc 493
fputs 493
fread 504
free 530
friend 636
fscanf 499
fseek 506
fwrite 504
getchar 382, 383
gets 382
get-функция 684
s
i
inline
встроенная функция
59, 154
ios::adjustfield 900
633, 675
int
901
ios::basefield
ios::fixed
903
ios::floatfield
ios::internal
903
900
ios::scientific
903
ios::showbase
900, 902
ios::showpoint 899
ios::showpos 901
isalnum
373
isalpha 373
iscntrl 373, 376
isdigit 373
isgraph 373, 376
islower 373, 375
isprint 373, 376
373, 376
373, 376
isupper 373, 375
isxdigit 373
ispunct
isspace
L
R
left 899
LIFO (последним пришел
первым ушел) 539
Long 154, 196
lvalue ("значение слева")
162, 699
main 55
make 609
makefile 609
malloc (выделить память) 530
memchr 396, 398
memcmp
396, 398
memcpy 396
memmove 396, 397
396, 399
31, 38, 39
540
scanf 434
SEEK_CUR 507
SEEK_END 507
SEEKJSET 507
set-функция 684
short 154
signal 613
signal.h 613
size_t 390
sizeof 324, 530
skipws 898
sprintf 382, 384
srand 201
sscanf 382, 385
stdarg.h 604
stderr (стандартная ошибка) 493
stdin (стандартный ввод) 493
stdio.h 382, 420
stdlib.h 378
stdout (стандартный вывод) 493
strcat 386
strchr 390, 391
strcmp 388
strcpy 386
strcspn 390, 391
strerror 399
string.h 385
strlen 399
strncat 386, 387
strncmp 388
strncpy 386
strpbrk 390, 392
strrchr 390, 392
strspn 390, 393
strstr 390, 394
strtod 378, 379
strtok 391, 394
strtol 378, 380
strtoul 378, 381
struct 453
memset
Pascal
pop
printf 421
private: 671, 681
protected: 681
public: 671, 681
push 540
т
w
template 636,
this 636
throw 636
time
203
611
tmpfile
650
9&7
Предметный указатель
tolower
toupper
true 68
try
бесконечный цикл 98, 155
библиотека классов потоков
373, 375
373, 375
-
636
-
880
613
сигналов
629
потоков
378
утилит общего назначения
библиотеки классов 808
typedef 458
union 461
UNIX 36, 39, 41, 151
unsigned 202
uppercase 902
va_arg 604
va_end 604
-
бинарная операция разрешения области
действия ( :: ) 675
бит
491
472
битовое поле
471
нулевой ширины
блок 55, 98, 192
va_list 604
va_start 604
virtual 636
void 191
void
обработки
блок default в структуре switch
152
33
блок памяти
*
(указатель
volatile 609
width 894, 896
на
void) 328
148,
90
блок-схема
блочный метод построения программ
буква 491
буквенное поле
37
491
А
абстрактный базовый
-
класс
в
850
класс
ввод/вывод (I/O)
850
(ADT) 670, 739
абстракция 190, 807
-
тип данных
-
данных
207
автоматический класс памяти
локальный объект
206
691
управляющие структуры
вложенный класс 723
внешняя компоновка
190
альтернатива операторам switch
863
амперсанд (&) 61
анализ данных опроса
аргумент
849,
37
-
арифметика указателей
326
арифметические операции
временный файл 611
вставка отображаемых символов
-
узла
встроенная функция-элемент
встроенный тип 758, 760
64
второе уточнение
входной блок 33
операции присваивания:
+=,-=,*=,/=H%= 113
ассоциативность операций
справа налево
665
71
-
66
679
103, 111
вхождение в область действия
71
421
537, 551
-
слева направо
608
внутренняя компоновка 608
возведение 107, 195
восьмеричные числа (начинаются с 0)
893, 901
269
55, 188
аргументы командной строки 606
функции по умолчанию 644
-
92, 108
восьмеричный формат 421, 422, 434
временная область для обмена значений
269
аппаратная платформа
аппаратная часть 31
66
структуры 453
if/else 95
-
452
активация функции
алгоритм 89
вложенные скобки
-
автоматическое приведение
аргументов 195
агрегаты
34
539
виртуальная функция 849
базового класса 849
виртуальный деструктор 864
665, 739
автоматическая переменная
-
вершина
691
выбор между быстродействием и компак¬
тностью 474
выбор 91
вызов по значению
атрибут
ссылке
Б
198, 312
198, 313
функции 187
функция 187
вызывающая функция 187
выравнивание 421
по левому краю
148, 430
правому краю 148, 430
-
база данных 492
базовый класс 807
байт
вызываемая
491
безопасная по типу компоновка
безопасный по типу ввод/вывод
бесконечная отсрочка
355
647
879
~
выражение
в качестве индекса
249
Как программировать на С
-
-
закрытый базовый
326
с указателями
со смешанными типами
заменяющий
196
запись
257
выход за пределы массива
действия 691
выходной блок 33
-
целого из указателя
Г
случайных
чисел
312
-
199
257, 258
глобальная переменная 208, 607
глобальный объект 691
голова очереди
левому краю
-
равенства
61
491
значение
550
-
-
двухместные операции
действие 55, 67, 88
декремент 139
идентификатор
данных
491
классов
810, 851, 852
-
-
550
674, 691
базового класса
имя
821
-
производного класса 821
десятичная цифра 491
диалоговое вычисление
динамические массивы
59, 63
61
-
615
файла 495
элемента
308
связывание
850, 862
директива препроцессора 588
инвертированный набор сканирования
437
-
индекс
-
253
249
столбца 277
-
строки
длина строки 399
дополнение 464
функция
класс
друзья базового класса
330
индикатор конца файла
инициализатор 644
доступ к элементам структуры
дружественный
277
индексация указателя
469
дружественная
453
667
класса
-
до единицы
453
имя-этикетка
644, 734
-
455
структуры
структуры данных 528
динамическое распределение памяти
#define
249
массива
-
-
455
727
базового класса
элемента
-
-
35
массива
структур
821
100
252, 258, 278
объекта класса
-
495
687
инициализация
-
829
производного класса 814
естественный язык компьютера
-
-
727
687
455
указателей 309
инкапсуляция 666
инкремент указателя 326, 327
интерактивное вычисление 61
интерфейс класса 674, 677
90
исключение goto
исключительная ситуация с плавающей
точкой 613
-
з
файлы стандартной биб¬
197, 589
заголовочный файл 197, 588, 679
загрузчик 41
задача 34
закрытие файла 496
закрытое наследование 809
заголовочные
лиотеки
809
641
именованная константа
-
-
59
иерархическое отношение
иерархия возведения 196
65
объекты
250
элемента
и
61, 65
указателя 326, 327
деление на 0 65, 104
-
61
переменной 63
537
-
-
61
(операция присваивания)
=
-
двойная косвенная адресация
двумерный массив 277
деструктор
148
процента % (ese-символ)
32
нацело
(*) 64
знак минуса для выравнивания по
545
двоичная цифра
двоичное дерево
поиска
550
-
810, 811
элемент класса
звездочка
гистограмма
дерево
472
зарезервированные слова 71
защищенное наследование 809
защищенный базовый класс 820
E
данные
65
901
заполнитель структуры
запуск программы 39
326, 328
генерация вызова по ссылке
-
253, 589
452, 491
в строчку
заполнение
указателей 326, 327
вычитание двух
-
-
из области
820
класс
текст
исполнение программы
исполняемый
образ 41
39
999
Предметный указатель
68
743
-
истинность
итератор
базового
итерация 213, 219
итоговая сумма 100
-
-
квадратные скобки
клавиша enter 61
return
-
-
-
-
-
-
-
-
-
-
корневой узел 550
косвенная адресация
-
309
ссылка на переменную
Date
крах программы
788
fstream
882
ifstream
ios
круглые скобки
881
iostream
istream
элемента
-
391, 394
линейная структура данных
линейный поиск 273
лексема
827
206
674, 676
492
636, 758
protected 681
virtual 849
литеральные символы
логика switch 849
логические блоки
71, 636
-
-
-
158, 160
строк 386
конкретные классы 850
константная элемент-функция
-
макроопределение
макрос 589
590
590
с аргументами
манипулятор потока 893
dec 893
flush 884
hex 893
oct 893
resetiosflags 898
setbase 893
setfill 901
setprecision 894
-
-
717, 718
717
setw 894
ws 898
константный указатель 320, 321
на константные данные 321
на не-константные данные 320
конструирование программного обеспече¬
ния 825
конструктор 671, 687, 734
базового класса 821, 827
207, 209
м
32
762
вызовов функций
элементов-функций 731
маска
465
маскирование битов
массив
-
248
n на m
-
строк
-
277
333
-структур
727
158, 159
159
локальная переменная
конкатенация
773, 774
(||) 158,
ИЛИ
отрицание (!)
ложность 68
102
"конец ввода данных
конец файла 151, 496, 889, 906
конечное значение управляющей пере¬
менной 142
конечный символ 118
объекта-элемента
33
операции 158
логическое И (&&)
389
команда cc в UNIX 589
комментарий 55, 627
компилятор 36, 39, 40
композиция 723
компоновщик 41
компьютер 32
компьютерная программа
конвейер | 603
421, 433
97
логическая ошибка
символа
копии
55, 371
литерал
273
константный объект
550
линии перехода 90
листовой узел 550
35
ключевые слова
код
899
поддерево 550
левый потомок 550
882
String 777
-
65, 66
()
левое выравнивание
881
ostream
поиска
820
104
л
881
881
ofstream
класса
309
882
ключевое слово operator
-
821
743
косвенный базовый класс
ключ записи
-
производного класса
Array 765
клиент
-
249
61
классы памяти
-
821
преобразования 777
102, 139
копирование строк 386
копия значения 198
666, 670
класс
-
690, 727
класса
контейнерные классы
контрольное значение
к
-
по умолчанию
-
458
указателей 333
465
Как программировать
1000
масштабирование 199
масштабируемость 253
машинно-зависимый 35
машинно-независимый 37
машинный язык 35
медиана
метод
о
область действия 206, 209
блока 209
676, 735
прототипа 209
символической константы
класса
269
метка case
на С
148
670
или
591
макроса
файла 209, 676
функции 209
свободной памяти 643
-
многозадачность
многомерный
39
массив
277
выбор 91, 148
моделирование 199, 204
модель действие/решение 56
модель клиент/сервер 35
модульная программа 186
мультипликативные операции 107
мультипрограммирование 34
мультипроцессор 43
100
*мусор
-
множественный
-
символов
ASCII
-
43, 665
-
вание (OOP)
43, 665, 807, 826, 849
объектный код 40
272
68
не-константный указатель на констант¬
ные данные 318
на не-константные данные
59
объявление
840
недопустимая инструкция 613
неименованное битовое поле 472
317
не-фатальная ошибка 65, 97
нелинейная структура данных 550
неоднозначность в сложном наследова¬
нии 834, 838
неопределенное повторение 102, 139
неперегружаемые операции 759
непосредственный базовый класс 820,
849
неразрушающее чтение из ячейки 64
неформатируемый ввод/вывод 892
неявное преобразование
107
неявные преобразования типа
785
нисходящее пошаговое уточнение 33,
102, 109, 335
номер позиции 249
нотация указатель/смещение 330
нулевой элемент 249
(false) 68
нуль-символ '\0' 258
551, 555
элемент данных
723, 727
объектно-ориентированное программиро¬
настройка программного обеспечения 825
научная нотация 423
начальное значение управляющей пере¬
менной 139, 142
490
отложенной выборкой
с
386
объект
объект
направленный ациклический граф
нарушение сегментации 613
наследование 806, 807, 849
нули и единицы
\ (ese-символ) 55
-
150
не равное нулю (true)
с
черта
порядковой выборкой 551, 554
с предварительной выборкой
551, 554
общая сумма элементов массива 255, 280
объединение строк с другими строками
--
389, 491
сканирования 435
надкласс 809
наиболее вероятное значение
370
текстов
обратная косая
обход 551
н
набор
370, 385
обработка строк
-
-
670
класса
251
массива
-
структур
453
функции 195
однострочный комментарий (//) 627
55
окончание оператора (;)
округление 107, 421, 425
окружение (рабочая среда) 39
операнд 61
оператор 55
goto 90, 616
-
-
присваивания 61
операции как функции
-
-
-
отношения
758
68, 69
равенства 68, 69
61
операция
-
-
-
-
-
-
-
delete
643, 734
delete[] 643
new
643, 734
взятия адреса &
61, 310
"взять из" (») 628
взятия по модулю
выбора
элемента
(%) 65
-
стрелка
730
точка (.) 730
класса (.) 667
-
-
-
-
-
-
-
нуль
-
декремента( ) 114
(~) 469
(») 628, 881
дополнения до единицы
извлечения из потока
инкремента (++) 114
косвенной адресации (*)
левого сдвига
309
(«) 464, 469
передачи в поток («) 628, 881
поразрядного И (&) 464, 465
1001
Предметный указатель
-
-
-
присваивания (=)
(«=) 470
поразрядного И (&=) 470
-
ссылки
&
(|=)
-
-
-
-
-
структур
-
675
стрелка (->)
455
двухместной операции 765
одноместной операции 764
по ссылке
263
-
-
-
-
"только для чтения"
класса static
-константа
са
641
828
808, 827
отображаемый символ 373, 376
отступ 57
очередь 545
ошибка бесконечной рекурсии 816
времени компиляции
60
переопределенная виртуальная
849, 850
перехват 613
перечисление 474
перечислимая константа
смещения индекса
142
п
поведение
повторение
880
первичная память
переадресация
743
637
33
103
ввода/вывода 602
474
,
665, 670
98
управляемое счетчиком 100, 139
повторное использование программного
кода 37, 189, 702, 826
-
поле
33, 63
параметр в определении функции 189,
192
параметр числа записей 506, 508
параметризованный манипулятор потока
память
функция
персональный компьютер 34
побочные эффекты 198
поиск в массиве
тип
206
поддерево 550
подкласс 809
позднее связывание
250
-
базового клас¬
816
период хранения
является
параметр-ссылка
139, 143
управления циклом
37, 42
переопределение элемента
828
параметризованный
641
206, 208
переносимость
104
808, 827
первое уточнение
элемент класса
управления 90
переменная 59
отладчик 592
счетчика
771, 774
-
операций 757
постфиксной операции 787
префиксной операции 786
функций 647
передача массивов в функции 263
-
555
отношение знает
-
функция-операция
-
открытое наследование 809
открытый базовый класс 820
открытый интерфейс класса 674, 682
использует
785
-
файла 495, 498
-
>=
-
-
453
отказ программы
имеет
771, 775
785
-
функции 191
определенное повторение 100, 139
оптимизирующий компилятор 207
основной случай рекурсии 212
открытие
>
785
-
-
остановка в узле
==
перегрузка
(*) 64
элемента структуры
-
785
<=
760
470
точка (.) 455
определение класса 671
-
-
470
699
умножения
-
-
-
разрешения области действия ::
разыменования (*) 310
-
-
-
766, 775
[] 771, 775
присваивания (=)
исключающего ИЛИ (^=)
правого сдвига (»=) 470
-
-
-
левого сдвига
включающего ИЛИ
-
операция !=
--<
61
-
-
-
107
приведения
-
-
преобразования 776
-
функ-
ция-операция 761
114
преинкремента
759
перегруженная дружественная
постинкремента 114
правого сдвига (») 464, 469, 470
предекремента 114
-
421
потока
перегружаемые операции
114
постдекремента
-
переадресация
(^) 464, 468
"послать в" («) 628
-
-
464, 468
(|)
включающего ИЛИ
исключающего ИЛИ
-
-
862
273
491
полиморфизм 848, 851, 853
пользователь класса 676
поразрядные операции
порядок действий 88
463
последовательная структура 90
последовательное выполнение 90
поток
-
421
стандартного ввода
-
вывода
-
420
420
стандартной ошибки 421
1002
Как программировать на С
потоки, определяемые
пользователем
p
881
-
рабочая станция 35
разбиение на функции 33
разбиение строки на лексемы
886
потоковый ввод
882
вывод
потомки
550
пошаговое уточнение
102
поэлементное копирование
правило
вложения
701
разделительные символы
операций 65, 66
правила старшинства
суперпозиции
899
правое выравнивание
поддерево 550
и
рандомизация
550
распределенные вычисления
расширение макроса 590
предикатная функция 536, 684
предопределенные потоки 881
-
символические константы
представитель класса
расширяемость
666
представление символа числовым кодом
388
класса в объект базового класса
a+
г
824
776
преобразования
613
в
строку #
594
498
498
w+
498
рекурсивная функция 210
рекурсивный вызов 212
рекурсия
решение
препроцессорная операция конкатенации
## 594
498
w
-
определяемое пользователем 776
преобразования между встроенными ти¬
пами и классами 776
495
498
r+
указателя производного класса в указа¬
тель базового класса 825
классами различного типа
препроцессор С 40, 58, 588
672, 741, 848, 852, 910
677
режим открытия файла
а 498
преобразование объекта производного
-
35
реализация класса
редактор 39
594
210, 219
67, 93
551
родительский узел
с
прерывание
связанный список
60
приглашение
принцип минимума привилегий
316, 320, 679, 717
принятие решения 67
207,
приоритет операций 66
приращение управляющей переменной
139
454
присваивание структур
-
указателя
309, 310, 328
сдвиг
-
308, 531
203, 464
влево
-
вправо
464, 469
464, 470
сервисная функция
сиблинги 550
684
сигнальное значение
102
сигнатура 647
символ 491
^
437
-
пробельные символы 71, 92, 376, 898
проверка на выход за пределы массива
783
-
-
-
программа-транслятор
программист 32
36
-
действия 91
новой строки (\n)
овала
тип
вывода >
603
603
-
подавления присваивания
-
-
55, 57
91
переадресации ввода <
-
программное обеспечение 31, 32
производный класс 808, 849
453
подчеркивания (
) 59
присоединения вывода »
прямоугольника 91
(*) 438
_
произвольный доступ 503
простое наследование 807
-
-
-
условие
158
решения
-
прототип функции
проход сортировки
190, 194
268
процедурное программирование 666
прямая ссылка на переменную 309
псевдокод
89
псевдослучайные числа
пузырьковая сортировка
пустой оператор
(;) 98
201
268
63
201
-
правый потомок
394
властвуй" 186, 189
различение регистра 59
разрушающее считывание в ячейку
разыменование указателя 310
165
163
-
"разделяй
394
34
разделение времени
603
91
ромба 91
стрелки 90
символ-заполнитель
-
901
по умолчанию (пробел) 901
символическая константа 253, 589
символы блок-схемы
символьная константа
символьное поле
491
90
370
1003
Предметный указатель
60, 97
синтаксическая ошибка
система управления базами данных
492
264
скаляр
264
скалярная величина
-
сложение указателя с целым
сложное наследование
-
326
807, 833
стек
двоичного дерева
-
погружением
-
-
-
267
97
формата 898
сохранение программы 39
спецификатор класса памяти
-
while
207
-
-
-
-
-
с
425, 435
d
62, 422, 434
e или E
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
426, 427, 435
%
-
-
-
-
ссылающаяся на себя
422, 434
422, 434
L 424, 435
-
i
-
-
422, 434
-
о
426, 427, 435
422, 434
39, 54
90, 139
32
p
263, 426, 427, 435
92
long double (1 или L) 611
long int (1 или L) 610
unsigned int (u или U) 610.
unsigned long (ul или UL) 610
637
-
-амперсанд (&)
счетчик 100
-
139
цикла
s
преобразования
т
к элементам
целых
таблицу
-
-
422
649
виртуалъных функций
преобразования 61, 69, 71, 421
переменной длины
инициализации массива
252
форма 251
функции 55
тело
-
цикла, 98, 154
34
тернарная операция
-
тип возвращаемого значения
-
данных
-
ссылка на базовый класс
851
-
стандартизованные компоненты програм¬
808
компоновки
стандартная библиотека С
(stdout) 41
заголовочный файл <iomanip.h>
453
оцределяемый
типы ссылки
-
206
переменной 63
структуры
37, 56, 187
стандартная ошибка (stderr) 41
стандартный ввод (stdin) 41
-
пользователем
666, 757
637
указателей 309, 328
(окончание оператора)
55
точка с запятой ;
880
191
669
-
много обеспечения
95
тильда(~).вименидеструктора 674
388
указателей 326, 329
среднее 269
вывод
159
истинности
терминал
сравнение строк
-
863
значенцй 277
табличная
603
-
453, 529
суперпозиция управляющих структур
суффикс float (f или F) 611
список аргументов
-
выбором 91
выбором 91
со множественным
повторения
спецификация внешней связи
-
с единичным
суперкомпьютер
201, 425, 435
u 422, 434
x или X
422, 434
спецификаторы доступа
671, 727
-
98, 154
двойным выбором 91
-
423, 434
h
n
с
154
структурное программирование
структуры выбора 90
f 423, 434
g или G 424, 434
1
выбора if 93
if/else 94
switch 148
повторения do/while
for 141
208, 607
register 207
static 208, 608
преобразования 422
-
55
-
206
extern
-
394
строковый литерал 371
структура FILE 493, 495, 497
состояния
auto
поиска
символов
управления форматом 421, 434
строковая константа 371
554
элементов массцэ&
850
539
-
268
составной оператор
связывание
столбцовая диаграмма 257
строка 55, 257, 259
-
-
735
элемент данных
статическое
смещение 330, 499
событие 613
создание экземпляра объекта 666
сокрытие данных 666, 673
информации 209
сообщение 55, 665, 670
сортировка 267
^
ввода/вывода 59
статическая элемент-функция 736
статический локальный объект 69й
период хранения 206, 208
1004
Как программировать на С
точность
-
107, 429
107, 423, 894
файловый сервер 35
фатальная ошибка 65, 97
по умолчанию
фигурные скобки {}
фиктивное значение
флаг 421, 430
у
удаление узла 533, 538, 539
узел 531
узел-потомок 550
указатель 308
-
-
-
-
this
-
-
309, 530
729
811
void (void *)
328
-
-
формат целого без знака
со знаком 422
объект базового класса
825
объект производного класса
производный класс 852
825
333, 337
453, 456
-
позиции
-
537
-
файла 499
производного класса
преобразования строк 377
сравнения строк 388
функция 37, 56, 187
-
339
функцию
422
форматирование в памяти 880
форматированный ввод/вывод 500, 880
функции математической библиотеки
188
-
структуру
указатель
431
(плюс) 430
0 (нуль) 431
флаги формата 898
флаговое значение 102
абстрактный класс 852
базовый класс 850
символ
+
-
базового класса
на
пробел 430
(минус) 430
-#
NULL
-
-
-
55, 97
102
getchar 150
-
pow
811
-
файла 495
унарная (одноместная) операция 160
147
printf 58, 62
-
-
операция разрешения области действия
( :: ) 681, 807
управление доступом к элементам
807
управление, поток управления
681,
-
scanf 58, 61
-
доступа
-
-
определяемая программистом
-
-элемент
-
-
-
-
-
-
72
программой 89
управляющая строка формата 61, 62
-
-
структура
-
-
if
90
68
управляющие структуры с одним вхо¬
дом / одним выходом 92, 163
управляющий символ 376
усечение
107
ускоренная разработка прикладных про¬
грамм (RAD) 702
условие
68
-
завершения
-
100
продолжения цикла
условная компиляция
143, 154
592
операция (?:) 95
условное выполнение директив препро¬
цессора 588
-
услуги, предоставляемые классом
устройство ввода 33
-
вывода
682
684
факториала 213
-
-
-
33
666, 670
bad 906
clear 906
eof 906
fail 906
fill 901
flags 896, 898
gcount 892
get 889
getline 890
good 906
ignore 891
operator void* 907
operator! 907
peek 891
precision 984
put 886
putback 891
rdstate 906
read 892
setf 896, 898
tie 910
unsetf 896, 898
892
write
Ф
фаза
завершения
-
инициализации
x
105
хвост очереди
обработки 105
файл 491
-
-
исходного кода
целое
-
-
последовательного доступа
произвольно! о доступа
503
492
-
545
59
типа
long 422, 434
типа
short
679
-
-
я
105
число
500
422, 434
187
1005
Предметный указатель
целочисленное деление
целостность
107
(корректность)
687,
688, 694
центральное процессорное устройство
(CPU) 33
цикл 92, 139
число с плавающей точкой
105, 423, 434
чисто виртуальная функция (=0)
851
шаблон 649
-
класса
743
функции 649
шестнадцатеричные цифры 373
-
(начинаются с 0x или 0X) 901
шестнадцатеричный формат 421
ширина битового поля 471
-
числа
поля
148, 428
экран 33, 41
экспоненциальный формат
точкой 423
элемент 453, 461
данных 666, 670
-
данных
с
плавающей
-
-
-
массива
249
случайности 199
этикетка структуры
453, 667
преобразование 107
типа (операция приведения)
776
язык высокого уровня
36, 38
явное
-
программирования
ясность 42
ячейка памяти
63
35
Scan AAW
Харви Дейтел, Пол Дейтел
Как программировать
на
С
Научное издание
Компьютерная верстка С В. Лычагина
Подписано
в печать
22.03.2000. Формат 70x100
Гарнитура Школьная. Бумага
Тираж 3000
Vi6.
Усл.
газетная. Печать
печ. л.
81,9.
офсетная.
экз. Заказ № 801.
ЗАО «Издательство БИНОМ», 2000 г.
103473, Москва, Краснопролетарская, 16
Лицензия
на издательскую деятельность №
065249 от 26 июня 1997
Отпечатано с готовых диапозитивов
Трудового Красного Знамени ГП «Техническая книга»
Министерства Российской Федерации по делам печати,
в ордена
коммуникаций
198005, Санкт-Петербург, Измайловский пр., 29.
телерадиовещания и средств массовых
г.