Обложка 1
3.9. Формулирование алгоритмов на основе нисходящего пошагового уточнения: пример
Титульный
Аннотация
Содержание
Предисловие
Об этой книге
Обзор книги
Глава 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. Объектно-ориентированное программирование и С++
Глава 2. Введение в программирование на С
2.2. Простая программа на С: печать строки текста
2.3. Еще одна простая программа на С: сложение двух целых чисел
2.4. Общие понятия о памяти компьютера
2.5. Арифметика в С
2.6. Принятие решений: операции равенства и отношения
Глава 3. Структурная разработка программ
3.2. Алгоритмы
3.3. Псевдокод
3.4. Управляющие структуры
3.5. Структура выбора if
3.6. Структура выбора if/else
3.7. Структура повторения while
3.8. Формулирование алгоритмов: пример 1 øповторение, управляемое счетчикомù
øповторение, управляемое контрольным значениемù
øвложенные управляющие структурыù
3.12. Операции инкремента и декремента
Глава 4. Управление программой
4.1.Введение
4.3. Повторение, управляемое счетчиком
4.4. Структура повторения for
4.5. Структура for: замечания и рекомендации
4.6. Примеры структур for
4.7. Структура со множественным выбором switch
4.8. Структура повторения do/while
4.9. Операторы break и continue
4.10. Логические операции
4.11. Смешивание операций равенства ø“ù и присваивания ø=ù
4.12. Краткая сводка по структурному программированию
Глава 5. Функции
5.2. Программные модули в С
5.3. Функции математической библиотеки
5.4. Функции
5.5. Определения функций
5.6. Прототипы функций
5.7. Заголовочные файлы
5.8. Вызов функций: вызов по значению и по ссылке
5.9. Генерация случайных чисел
5.10. Пример: стохастическая игра
5.11. Классы памяти
5.12. Правила области действия
5.13. Рекурсия
5.14. Пример применения рекурсии: числа Фибоначчи
5.15. Рекурсия в сравнении с итерацией
Глава 6. Массивы
6.2. Массивы
6.3. Объявление массивов
6.4. Примеры работы с массивами
6.5. Передача массивов в функции
6.6. Сортировка массивов
6.7. Пример: вычисление среднего значения, медианы  и наиболее вероятного значения с использованием массивов
6.8. Поиск в массивах
6.9. Многомерные массивы
Глава 7. Указатели
7.2. Объявление и инициализация переменной-указателя
7.3. Операции над указателями
7.4. Передача параметра по ссылке
7.5. Использование модификатора const с указателями
7.6. Программа пузырьковой сортировки, использующая  вызов по ссылке
7.7. Выражения и арифметические операции с указателями
7.8. Связь между указателями и массивами
7.9. Массивы указателей
7.10. Пример: программа тасовки и сдачи колоды карт
7.11. Указатели на функции
Глава 8. Символы и строки
8.2. Строки и символы
8.3. Библиотека обработки символов
8.4. Функции преобразования строк
8.5. Функции стандартной библиотеки ввода/вывода
8.6. Функции операций над строками из библиотеки  обработки строк
8.7. Функции сравнения из библиотеки обработки строк
8.8. Функции поиска из библиотеки обработки строк
8.9. Функции памяти библиотеки обработки строк
8.10. Другие функции из библиотеки обработки строк
Глава 9. Форматированный ввод/вывод
9.2. Потоки
9.3. Форматированный вывод с применением printf
9.4. Печать целых чисел
9.5. Печать чисел с плавающей точкой
9.6. Печать строк и символов
9.7. Другие спецификаторы преобразования
9.8. Печать с заданием ширины поля и точности представления
9.9. Использование флагов в строке управления форматом printf
9.10. Печать литералов и Еэс-последовательностей
9.11. Форматированный ввод с применением scanf
Глава 10. Структуры, объединения, операции с битами  и перечисления
10.2. Описания структур
10.3. Инициализация структур
10.4. Доступ к элементам структур
10.5. Использование структур с функциями
10.6. Typedef
10.7. Пример: моделирование высокоэффективной тасовки  и раздачи карт
10.8. Объединения
10.9. Поразрядные операции
10.10. Битовые поля
10.11. Перечислимые константы
Глава 11. Работа с файлами
11.2. Иерархия данных
11.3. Файлы и потоки
11.4. Создание файла последовательного доступа
11.5. Чтение данных из файла последовательного доступа
11.6. Файлы произвольного доступа
11.7. Создание файла произвольного доступа
11.8. Произвольная запись данных в файл произвольного доступа
11.9. Последовательное чтение данных из файла произвольного доступа
11.10. Пример: программ обработки транзакций
Глава 12. Структуры данных
12.2. Структуры, ссылающиеся на себя
12.3. Динамическое распределение памяти
12.4. Связанные списки
12.6. Очереди
12.7. Деревья
Глава 13. Препроцессор
13.2. Директива препроцессора #include
13.3. Директива препроцессора #define: символические константы
13.4. Директива препроцессора #define: макросы
13.5. Условная компиляция
13.6. Директивы препроцессора #error и #pragma
13.7. Операции # и
13.8. Нумерация строк
13.9. Предопределенные символические константы
13.10. Макрос подтверждения
Глава 14. Специальные вопросы
14.2. Переадресация ввода/вывода в системах UNIX и DOS
14.3. Списки аргументов переменной длины
14.4. Аргументы командной строки
14.5. Замечания относительно компиляции программ  из нескольких исходных файлов
14.6. Выход из программы с помощью exit и atexit
14.7. Модификатор типа volatile
14.8. Суффиксы для целых констант и констант  с плавающей точкой
14.9. Еще раз о файлах
14.10. Обработка сигналов
14.11. Динамическое выделение памяти: функции calloc и realloc
14.12. Безусловный переход: goto
Глава 15. С++ как «улучшенный» С
15.2. Однострочные комментарии С++
15.3. Потоковый ввод/вывод С++
15.4. Объявления в С++
15.5. Создание новых типов данных в С++
15.6. Прототипы функций и контроль соответствия типов
15.7. Встроенные функции
15.8. Параметры-ссылки
15.9. Модификатор const
15.10. Динамическое распределение памяти  с помощью new и delete
15.11. Параметры, используемые по умолчанию
15.12. Унарная операция разрешения области действия
15.13. Перегрузка функций
15.14. Спецификации внешней связи
15.15. Ӹаблоны функций
Глава16. Классы и абстракция данных
16.2. Определение структур
16.3. Доступ к элементам структур
16.4. Реализация пользовательского типа Time с помощью структуры
16.5. Реализация абстрактного типа данных Time  с помощью класса
16.6. Область действия класса и доступ к элементам класса
16.7. Отделение интерфейса от реализации
16.8. Управление доступом к элементам класса
16.9. Функции доступа и сервисные функции
16.10. Инициализация объектов класса: конструкторы
16.11. Использование с конструкторами аргументов по умолчанию
16.12. Деструкторы
16.13. Когда вызываются конструкторы и деструкторы
16.14. Использование элементов данных и элементов-функций
16.15. Скрытая ловушка: возвращенце ссылки  на закрытый элемент данных
16.16. Присваивание по умолчанию путем поэлементного копирования
16.17. Повторное использование программного обеспечения
Глава 17. Классы: часть II
17.2. Константные объекты и константные элементы-функции
17.3. Композиция: классы в качестве элементов других классов
17.4. Дружественные функции и дружественные классы
17.5. Указатель this
17.6. Динамическое распределение памяти с помощью операций new и delete
17.7. Статические элементы класса
17.8. Абстракция данных и сокрытие информации
17.9. Контейнерные классы и итераторы
17.10. Ӹаблоны классов
Глава 18. Перегрузка операций
18.2. Основные принципы перегрузки операций
18.3. Запреты на перегрузку операций
18.4. Функции-операции как элементы класса  и как дружественные функции
18.5. Перегрузка операций передачи в поток  и извлечения из потока
18.6. Перегрузка одноместных операций
18.7. Перегрузка двухместных операций
18.8. Пример: класс Array
18.9. Преобразование типов
18.10. Пример: класс String
18.11. Перегрузка ++ и
18.12. Пример: класс Date
Глава 19. Наследование
19.2. Базовые и производные классы
19.3. Защищенные элементы
19.4. Приведение указателей базового класса к указателям на производный класс
19.5. Применение функций-элементов
19.6. Переопределение элементов базового класса в производном классе
19.7. Открытые, защищенные и закрытые базовые классы
19.8. Непосредственные и косвенные базовые классы
19.9. Применение конструкторов и деструкторов в производном классе
19.10. Неявное преобразование объектов производного класса к базовому
19.11. Наследование в конструировании программного обеспечения
19.12. Композиция в сравнении с наследованием
19.13. Отношения «использует» и «знает»
19.14. Пример: Point, Circle, Cylinder
19.15. Сложное наследование
Глава 20. Виртуальные функции и полиморфизм
20.2. Обработка различных типов данных при помощи  операторов switch
20.3. Виртуальные функции
20.4. Абстрактные базовые классы и конкретные классы
20.5. Полиморфизм
20.6. Пример: программа начисления заработной платы, использующая возможности полиморфизма
20.7. Новые классы и динамическое связывание
20.8. Виртуальные деструкторы
20.9. Пример: наследование интерфейса  и реализации
Глава 21. Потоки ввода/вывода в С++
21.2. Потоки
21.3. Потоковый вывод
21.4. Потоковый ввод
21.5. Функции неформатируемого ввода/вывода read, gcount и write
21.6. Манипуляторы потоков
21.7. Флаги форматирования потока
21.8. Состояния ошибки потоков
21.9. Ввод/вывод определяемых пользователем типов
21.10. Привязка потока вывода к потоку ввода
Приложение А. Синтаксис С
Приложение Б. Стандартная библиотека
Приложение В. Таблица приоритета операций
Приложение Г. Набор символов ASCII
Приложение Д. Системы счисления
Д.2. Запись двоичных чисел в виде восьмеричных и шестнадцатеричных
Д.З. Преобразование восьмеричных и шестнадцатеричных чисел в двоичные
Д.4. Преобразование числа из двоичной, восьмеричной или шестнадцатеричной форм в десятичную
Д.5. Преобразование десятичного числа в двоичное, восьмеричное или шестнадцатеричное
Д.6. Отрицательные двоичные числа: дополнение до двух
Выходные данные
Обложка 2
Текст
                    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, &currentBalance); 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 &copy) { 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. телерадиовещания и средств массовых г.