Text
                    

Bradley L. Jones and Peter Aitken   ‚_ - „- . а. ‚‹ ` [_‚4 _ _ "‹ \ ‚_ ‚" ‘I ", “- ё‘ъ . Ч "\ ' ›.   |: i112] вниз  SIXTH EDITION  sAMs  201 West 103rd St., Indianapolis, Indiana, 46290 USA 
Брэдли Л. джонс Питер Эйткен   .‘-"1~~' .. , " —ь \. .‘4. I , v  Леви вампвшпяшепьнп   за 21ml.  шестое издание   Издательский ддм “Вильямс ” Москва 0 Санкт—Петербург 0 Киев 2005 
ББК 32.973.26-018.2.75 Д42 УДК 681.3.07  Издательский дом “Вильямс” Зав. редакцией С.Н. Тригуб  Перевод с английского и редакция В.Л. Бродового  По общим вопросам обращайтесь в Издательский дом “Вильямс” по адресу: info@williamspublishing.com, http://www.williamspublishing.com  Джонс, Брэдли, Эйткен, Питер.  Д42 Освой самостоятельно С за 21 день, 6-е издание. : Пер. с англ. —— М. : Изда- тельский дом “Вильямс”, 2005. —-- 800 с. : ил. ——. Парал. тит. англ.  ISBN 5-8459-0492-7 (pyc.)  B книге представлен учебный курс по языку программирования С, соответствующий новейшему стандарту ANSI/ISO. Подробно описан синтаксис и стандартные средства языка. Даны рекомендации по применению профессиональных приемов и подходов программирования. Изложение проиллюстрировано многочисленными примерами, в том числе полными текстами программ. Дано введение в объектно-ориентированное программирование и основы языков С++, Java, C#. Книга содержит обширный справоч- ный материал. Книга рассчитана на студентов, преподавателей и специалистов в области компью- терных технологий.  ББК 32.973.26—018.2.75  Все названия программных продуктов являются зарегистрированными торговыми марками соответ- ствующих фирм.  Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни бьшо форме и какими бы то ни было средствами, будь то электронные или механические, включая фо- токопнрование и запись на магнитный носитель, если на это нет письменного разрешения издательства Sams Publishing.  Authorized translation from the English language edition published by Sams Publishing, Copyright © 2003  All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher.  RusSian language edition published by Williams Publishing House according to the Agreement with R&l Enterprises lntemational, Copyright © 2003  ISBN 5-8459-0492-7 (pyc.) © Издательский дом “Вильямс", 2003 ISBN 0-672-32448-2 (англ.) © Sams Publishing, 2003 
Оглавление  Введение  Неделя 1 . Основные вопросы  День 1-й. Первые шаги в С Самостоятельная работа 1. Распечатка листингов День 2-й. Составные части программы на языке С День 3-й. Хранение данных в переменных и константах День 4-й. Элементы программы на С: операторы, выражения и операции Самостоятельная работа 2. Программа угадывания чисел День 5-й. Функции День 6-й. Циклы День 7-й. Основные средства ввода-вывода  Неделя 1 . Итоги  Неделя 2. Основные вопросы  День 8-й. Массивы числовых данных День 9-й. Указатели Самостоятельная работа 3. Приостановка выполнения программы День 10-й. Символы и строки День 11-й. Структуры, объединения и нестандартные типы данных День 12-й. Область действия переменных Самостоятельная работа 4. Секретные сообщения День 13-й. Дополнительные средства управления программой День 14-й. Работа с экраном, принтером и клавиатурой  Неделя 2. Итоги  Неделя 3. Основные вопросы  День 15-й. Дополнительные сведения об указателях День 16-й. Работа с файлами Самостоятельная работа 5. Подсчет символов День 17-й. Операции над строками символов День 18-й. Дополнительные возможности функций День 19-й. Некоторые библиотечные функции С Самостоятельная работа 6. Расчет выплат по займу День 20-й. Работа с памятью День 21-й. Совершенствование организации программ  Неделя 3. Итоги  Оглавление  23  27  29 44 46 57 72 100 102 125 145  164  169  170 187 208 210 232 264 281 285 310  345  353  354 399 432 437 468 483 507 509 533  555 
дополнительная неделя. Основные вопросы 563  День 22-й. Языки объемно-ориентированного программирования 564 День 23-й. Введение в язык С++ 582 День 24-й. Классы и объекты С++ 595 День 25-й. Основы языка Java 628 День 26-й. Классы и методы Java 645 День 27-й. Дополнительные средства Java 662 День 28-й. Введение в язык С# 682 Неделя 4. Итоги 695 Приложение А. Таблица кодов и символов ASCII 697 Приложение Б. Ключевые слова С и С++ 701 Приложение В. Двоичныеи шестнадцатеричные числа 703 Приложение Г. Вопросы переносимости кода 706 Приложение Д. Стандартные функции С 730 Приложение Е. Ответы 735 Приложение Ж. Среда разработки Бем-С++ 782 Предметный указатель 792  6 Оглавление 
СОДержание  Об авторах Благодарности  Введение  Соглашения Улучшения Прилагаемый компакт-диск Дополнительное оформление  Неделя 1 . Основные вопросы  Что дальше  День 1-й. Первые шаги в С  Краткая история языка С Зачем нужен С Подготовка к программированию Цикл разработки программы Создание исходного кода Компилирование исходного кода Создание исполняемого файла Финальная стадия разработки программы Первая программа на С Ввод и компиляция программы HELLO.C Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  Самостоятельная работа 1. Распечатка листингов Приступаем к работе  День 2-й. Составные части программы на языке С  Короткая программа на С Составные части программы Функция таіп() (строки 8—23) Директива #includc (строка 2) Объявление переменных (строка 4) Прототип функции (строка 6) Операторы (строки 11, 12, 15, 16, 19, 20, 22, 28) Определение функции (строки 26—29) Комментарии (строки 1, 10, 14, 18, 25) Фигурные скобки (строки 9, 23, 27, 29)  21 22  23  23 25 25 26  27 27  29  29 30 31 31 32 33 34 34 36 37 40 40 41 41 42  44 44  46  46 47 48 48 48 48 49 49 50 51 
Выполнение программы О точности и аккуратности Снова о компонентах программы Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  День 3—й. Хранеиие данных в переменных и константах  Основные сведения о памяти компьютера Хранение информации в переменных Имена переменных Типы числовых переменных Объявления переменных Ключевое слово туресіеі` Инициализация переменных Константы! Литер ьные константы Символические константы Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  День 4-й. Элементы программы на С: операторы, выражения и операции  Операторы Роль свободного пространства в операторах Пустой оператор Составные операторы Выражения Простые выражения Сложные выражения Операции Операция присваивания Арифметические операции Двуместные арифметические операции Приоритет операций и скобки Порядок вычисления вложенных выражений Операции отношения Оп'ератор if ‘ Ключевое слово else Вычисление логических выражений Приоритет операций отношения Логические операции Работа с логическими величинами Приоритет операций Составные операторы присваивания Операция выбора по условию Запятая  51 51 52 54 54 55 55 55  57  57 58 58 60 63 63 64 65 65 66 69 70 70 71 71  72  72 73 74 74 74 74 75 76 76 76 78 80 82 82 83 86 88 90 91 92 92 94 95 95  Садержание 
Снова о приоритете операций 96  Резюме 96 Вопросы и ответы 97 Коллоквиум 98 Контрольные вопросы 98 Упражнения 98 Самостоятельная работа 2. Программа угадывания чисел 100 День 5-й. Функции 102 Что такое функции 102 Определение функции 102 Пример функции 103 Как работают функции 104 Объявление и определение функций 105 Функции и структурное программирование 106 Преимущества структурного программирования 106 Планирование структурированной программы 107 Метод программирования сверху вниз 108 Написание функции 109 Заголовок функции 109 Тип возвращаемого значения 109 Имя функции 109 Список параметров 109 Тело функции 112 Прототип функции 116 Передача аргументов в функцию 117 Вызов функций 117 Рекурсивный вызов 119 Местонахождение функций 121 Встраиваемые функции 121 Резюме 122 Вопросы и ответы 122 Коллоквиум 123 Контрольные вопросы 123 Упражнения 123 День 6-й. Циклы 125 Массивы: основные сведения 125 Управление выполнением программы 126 Оператор for 126 Вложенные операторы for 131 Оператор while 133 Вложенные операторы while 136 Цикл оо…шЬііе 138 Вложенные циклы 142 Резюме 143 Вопросы и ответы 143 Коллоквиум 144 Контрольные вопросы 144 Упражнения 144  Садержание 9 
День 7-й. Основные средства ввода-вывода 145  Вывод данных на экран 145 Функция printf() 145 Строка формата функции printf() 146 Вывод сообщений с помощью функции puts() 153 Ввод числовых данных с помощью функции scanf() 154 Специальные последовательности из трех символов 159 Резюме 160 Вопросы и ответы 160 Коллоквиум 161 Контрольные вопросы 161 Упражнения 162 Неделя 1 . Итоги 164 Неделя 2. Основные вопросы 169 Что дальше 169 День 8-й. Массивы числовых данных 170 Что такое массив 170 Одномерные массивы 171 Многомерные массивы 175 Имена и объявления массивов 175 Инициализация массивов 178 Инициализация многомерных массивов 178 Максимальный размер массива 181 Резюме 184 Вопросы и ответы 184 Коллоквиум 185 Контрольные вопросы 185 Упражнения 185 День 9-й. Указатели 187 Что такое указатель 187 Память компьютера 187 Создание указателей 188 Указатели и простые переменные 189 Объявление указателей 189 Инициализация указателей 189 Работа с указателями 190 Указатели и типы переменных 192 Указатели и массивы 193 Имя массива как указатель 193 Размещение элементов массива в памяти 194 Адресная арифметика 196 Потенциальные опасности 200 Индексная запись и указатели 200 Передача массивов в функции 201 Резюме 205 Вопросы и ответы 205 Коллоквиум 206  10 Содержание 
Контрольные вопросы 206  Упражнения 207 Самостоятельная работа 3. Приостановка выполнения программы 208 День 10—й. Символы и строки 210 Тип данньтх с1тат 210 Работа с символьными переменными 21 1 Работа со строками 214 Массивы символов 214 Инициализация символьных массивов 214 Строки и указатели 215 Работа со строками без массивов 215 Выделение памяти для строки при компиляции 216 Функция та11ос() 216 Работа с функцией та11ос() 217 Вывод строк и символов 221 Функция puts() 221 Функция printf() 222 Ввод строк с клавиатуры 222 Ввод строк с помощью функции gets() 222 Ввод строк с помощью функции scanf() 226 Резюме 228 Вопросьт и ответы 228 Коллоквиум 229 Контрольные вопросы 229 Упражнения 230 День 11-й. Структуры, объединения и нестандартные типьт данных 232 Простейшие структуры 232 Объявление и определение структур 232 Обращение к полям структуры 233 Сложные структуры 236 Включение структур в структуры 236 Структуры, содержащие массивы 239 Массивы структур 241 Инициализация стРУКТУР 245 Структуры и указатели 246 Указатели как поля СТРУКТУР 247 Создание указателей на структуры 249 Указатели и массивьт структур 250 Передача структур в функции 253 Объединения 254 Определение, создание и инициализация объединений 255 Обращение к элементам объединения 25 5 Создание структурных типов с помощью typedef 260 Резюме 261 Вопросьт и ответы 261 Коллоквиум 261 Контрольные вопросьт 262 Упражнения 262  Содержание 1 1 
День 12-й. Область действия переменных  Что такое область действия Пример области действия Важность области действия переменных Создание внешних переменных Область действия внешних переменных Принципы использования внешних переменных Ключевое слово ехтет Создание локальных переменных Статические и автоматические переменные Область действия параметров функции Внешние статические переменные Регистровые переменные Локальные переменные в функции таіп() Классы памяти Локальные переменные в блоках Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  Самостоятельная работа 4. Секретные сообщения  День 13-й. Дополнительные средства управления программой  Прерывание циклов Оператор break Оператор continue Оператор goto Бесконечные циклы Оператор switch Выход из программы Функция ехі1() Выполнение команд операционной системы Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  День 14-й. Работа с экраном, принтером и клавиатурой  Потоки ввода-вывода Что такое ввод-вывод Понятие потока Текстовые и двоичные потоки Стандартные потоки Функции потокового ввода-вывода Простой пример работы с потоками Ввод с клавиатуры Ввод символов Вв0д сгрок  264  264 265 266 267 267 267 268 269 269 272 272 273 273 274 275 276 276 277 277 278  281  285  285 285 287 289 291 295 303 304 304 307 307 308 308 308  310  310 310 311 312 312 312 313 314 314 319  Содержание 
Форматированный ввод Вывод на экран Вывод символов Функции вывода строк puts() и fputs() Функции форматированного вывода printf() и fprintf() Перенаправление ввода-вывода Перенаправление ввода Рекомендации по использованию функции fprintf() Вывод в поток stderr Вывод на принтер из DOS и командной строки Windows Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  Неделя 2. Итоги  Неделя 3. Основные вопросы Что дальше  День 15-й. Дополнительные сведения об указателя/х  Указатели на указатели Указатели и многомерные массивы Массивы указателей Указатели и строки: повторение Массивы указателей типа char Пример программы сортировки Указатели на функции Объявление указателя на функцию Инициализация и использование указателя на функцию Дополнительный раздел: введение в связанные списки Основные сведения о связанных списках Работа со связанными списками Пример простого связанного списка Реализация связанного списка Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  День 16-й. Работа с файлами  Связь между потоками и файлами Типы дисковых файлов Имена файлов Открытие файла Запись и чтение данных Форматированный ввод-вывод Символьный ввод-вывод Блочный ввод-вывод  Содержание  321 329 329 331 332 337 339 339 340 340 341 342 343 343 344  345  353 353  354  354 355 363 363 363 366 370 371 371 379 380 381 386 388’ 395 396 396 397 398  399  399 399 400 401 404 405 408 41 0  13 
Буферизация файлов: закрытие и очистка буферов Последовательный и произвольный доступ Функции fiellO и rewindO Функция fseekO Определение конца файла Функции управления файлами Удаление файлов Переименование файлов Копирование файлов Использование временных файлов Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  Самостоятельная работа 5. Подсчет символов  День 17-й. Операции над строками символов  Определение длины строки Копирование строк Функция strcpy() Функция stmcpy() Функция strdup() Конкатенация (сцепление строк) Функция strcat() Функция stmcat() Сравнение строк Сравнение двух целых строк Сравнение фрагментов строк Сравнение двух строк без учста регистра Поиск в строках Функция strchr() Функция strrchr() Функция strcspn() Функция strspn() Функция strpbrkO Функция strstr() Преобразования символов в строках Разные функции для работы со строками Функция strrev() Функции strset() и strnset() Преобразования строк в числа Преобразование строк в целые числа Преобразование строк в длинные целые числа Преобразование строк в числа типа long long Преобразование строк в числа с плавающей точкой Функции анализа символов Изменение регистра символов по стандарту ANSI Резюме Вопросы и ответы  414 415 415 418 420 422 423 424 425 427 428 429 430 430 430  432  437  437 438 439 440 441 442 442 444 445 446 447 449 449 449 450 451 452 453 453 455 456 456 456 457 458 458 458 459 460 463 465 465  Содержание 
Коллоквиум 466  Контрольные вопросы 466 Упражнения 466 День 18—й. Дополнительные возможности функций 468 Передача указателей в функции 468 Указатели типа void 472 Функции с переменным числом аргументов 475 Функции, возвращающие указатели 478 Резюме 480 Вопросы и ответы 480 Коллоквиум 481 Контрольные вопросы 481 Упражнения 481 День 19-й. Некоторые библиотечные функции С 483 Математические функции 483 Тригонометрические функции 483 Степенные и логарифмические функции 484 Гиперболические функции 484 Другие математические функции 485 Демонстрация математических функций 485 Обработка времени \ 486 Представление времени 486 Функции работы со временем 487 Пример использования функций времени 490 Обработка ошибок 492 Макрос assert() 492 Заголовочный файл ermo.h 494 Функция реггог() 495 Поиск и сортировка 496 Поиск 497 Сортировка 498 Поиск и сортировка: два демонстрационных примера 498 Резюме 504 Вопросы и ответы 504 Коллоквиум 505 Контрольные вопросы 505 Упражнения 505 Самостоятельная работа 6. Расчет выплат по займу 507 День 20-й. Работа с памятью 509 Преобразование типов 509 Автоматические преобразования типов 509 Явные преобразования типов 51 1 Распределение памяти 513 Распределение памяти с помощью функции та11ос() 514 Распределение памяти с помощью функции са11ос() 516 Распределение дополнительной памяти с помощью функции геа11ос() 518 Освобождение памяти с помощью функции free() 519 Манипулирование блоками памяти 521  Содержание 15 
Инициализация памяти с помощью функции memsetO 522  Копирование блоков памяти функцией тетсру() 522 Перемещение блоков памяти с помощью функции memmoveO 522 Операции над отдельными битами памяти 524 Операции поразрядного сдвига 524 Поразряцные логические операции 526 Операция дополнения 527 Битовые поля в структурах 528 Резюме 529 Вопросы и ответы 530 Коллоквиум 531 Контрольные вопросы 531 Упражнения 532 День 21-й. Совершенствование организации программ 533 Программы, состоящие из нескольких модулей исходного кода 533 Преимущества многомодульного программирования 533 Техника многомодульного программирования 534 Содержание модулей 536 Внешние переменные и многомодульное программирование 537 Работа с объектными файлами 538 Утилита построения проекта 539 Препроцессор С 540 Директива препроцессора #defme 540 Директива #include 545 Директивы #if, #elif, #else и #endif 545 Отладка с помощью конструкции #if...#endif 546 Как избежать многократного включения заголовочных файлов 547 Директива #undef 548 Стандартные макросы 549 Аргументы командной строки 549 Резюме 552 Вопросы и ответы 552 Коллоквиум 553 Контрольные вопросы 553 Упражнения 553 Неделя 3. Итоги 555 дополнительная неделя. Основные вопросы 563 Что дальше 563 День 22-й. Языки объекта-ориентированного программирования 564 Процедурные и объектно-ориентированные языки 564 Принципы объектно-ориентированного программирования 565 Полиморфизм 566 Инкапсуляция 569 Наследование 570 ООП в действии 571 Связь между С и С++ 574 Программные модули на языке С++ 574  16 Содержание 
Язык программирования Java 575  Связь Java с языками С и С++ 575 Аппаратная независимость Java 576 Пакеты 577 Аплеты Java 577 Библиотеки классов Java 577 Как сказать Hello, world Ha языке Java 577 Язык программирования С# 579 Резюме 579 Вопросы и ответы 580 Коллоквиум 580 Контрольные вопросы 580 Упражнения 581 День 23—й. Введение в язык С++ 582 Hello World на языке С++ 582 Вывод на экран в С++ 583 Ключевые слова С++ / 584 Типы данных С++ / 585 Объявление переменных в С++ 585 Операции в С++ 587 Функции в С++ 587 Перегрузка функций 587 Установка параметров функций по умолчанию 588 Встраиваемые функции 590 Резюме 593 Вопросы и ответы 593 Коллоквиум 594 Контрольные вопросы 594 Упражнения 594 день 24-й` Классы и объекты С++ 595 Сложные типы данных в С++ 595 Функции для работы со структурами 597 Классы 603 Управление доступом к данным в классах 603 Разграничение доступа к данным классов 605 Функции доступа к членам классов 608 Различие между классами и структурами 611 Создание и уничтожение объектов 61 1 Конструкторы 612 Деструкторы 612 Использование конструкторов и деструкторов 612 Снова о перегрузке функций 614 Реализация принципов ООП в С++ 614 Вложенные классы 615 Обращение к вложенным классам 616 Наследование в С++ 616 Построение базового наследуемого класса 617 Модификатор доступа protected 619 Наследование базового класса 619  Содержание 17 
Еще о конструкторах и деструкторах Дополнительные возможности С++ Что читать дальше Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  День 25-й. Основы языка Java  Программы на языке Java Составные части программы на Java Импортирование классов Методы Комментарии Ключевые слова Java Идентификаторы в Java Типы данных Элементарные типы данных Объявление и инициализация переменных Область действия переменных Строковые данные Ввод и вывод Массивы Операции Управляющие операторы Оператор if...else Циклы while и do...while Оператор switch Оператор for Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  День 26-й. Классы и методы Java  Определение класса Создание пакета классов Создание свойств класса Демонстрационный пример Методы классов Демонстрация методов Перегрузка методов Конструкторы классов Наследование Резюме Вопросы и ответы Коллоквиум Контрольные вопросы  622 624 625 625 625 626 626 627  628  628 629 629 630 630 630 632 632 633 634 634 635 637 639 640 640 64 | 64 | 642 643 643 644 644 644 644  645  645 646 647 647 648 649 652 653 657 660 660 660 661  Садержание 
день 27—й. дополнительные средства Java 662  Исключительные ситуации 662 Файловый ввод-вывод 663 Чтение из текстовых файлов 664 Запись в текстовые файлы 665 Работа с графикой и оконным интерфейсом 667 Создание приложений с оконным интерфейсом 667 Рисование линий и геометрических фигур 669 Использование кнопок и всплывающих окон 672 Программирование аплетов 676 Различия между аплетами и приложениями 676 Структура аплета 676 Помещение аплета на \УеЬ-страницу 677 Пример аплета 678 Резюме 680 Вопросы и ответы 680 Коллоквиум 681 Контрольные вопросы 681 день 28-й. Введение в язык С# 682 Первое знакомство с С# 682 Зачем нужен С# 683 Простота С# 683 Современность С# 684 Объектно-ориентированный характер С# 684 Гибкость и мощь С# 684 Лаконичность С# 684 Модульная структура С# 685 Возрастающая популярность С# 685 Сравнение С# с другими языками программирования 686 Типы программ на С# 686 Разработка программ на С# 687 Имена файлов исходного кода 687 Выполнение программы на С# 687 Компиляция исходного кода С# 688 Компилятор С# и среда .NET Runtime 688 Пример программы на С# 689 Основы консольного вывела 689 С# и Web 691 Резюме 692 Вопросы и ответы 693 Коллоквиум 693 Контрольные вопросы 693 Упражнения 694 Неделя 4. Итоги 695 Приложение А. Таблица кодов и символов ASCII 697 Приложение Б. Ключевые слова С и С++ 701  Содержание 19 
20  Приложение В. Двоичные и шестнадцатеричные числа  Десятичная система счисления Двоичная система Шестнадцатеричная система  Приложение Г. Вопросы переносимости кода  Стандарт ANSI Ключевые слова ANSI C Чувствительность к регистру Переносимость символов Обеспечение совместимости со стандартом ANSI Отказ от стандарта ANSI Переносимость чисел и числовых переменных Максимальные и минимальные значения Классификация `чисел Преобразование регистра символов Переносимость структур и объединений Выравнивание по словам Чтение и запись структур Порядок элементов в структуре Директивы препроцессора Стандартные константы Использование нестандартных средств в переносимых программах Стандартные заголовочные файлы ANSI Резюме Вопросы и ответы Коллоквиум Контрольные вопросы Упражнения  Приложение Д. Стандартные функции С Приложение Е. Ответы  Приложение Ж. Среда разработки Dev-C++  Что представляет собой Dev-C++ Установка Dev-C++ B системе Microsoft Windows Состав пакета Dev-C++ Работа с Dev-C++ Настройка параметров среды Dev-C++ Ввод и сохранение программы Компиляция программы Запуск программы на выполнение Резюме Предметный указатель  703 703 704 704  706  706 707 707 708 709 710 710 712 717 721 722 722 723 723 724 724 724 726 727 727 727 727 728  730 735  782  782 783 785 785 785 787 789 789 791 792  Содержание 
3506 авторах  Брэдли Л. Джонс (Bradley L. Jones) работает на Web-came Intemet.com B качестве управляющего каналом EarthWeb, посвященным разработке программного обеспечения, где руководит такими сайтами, как Developer.com, CodeGuru.com, Gamelan.com. OH занимался разработкой самых разных систем -——— как локальных, так и сетевых —— на всевозможных ап- паратно—программных платформах от Palm OS до суперкомпьютеров. Его опыт системного программирования включает использование таких средств разработки, как С, С#, С++, XML, ‘SQL Server, PowerBuilder, Visual Basic, Active Server Pages (ASP), Satellite Forms и т.д. Перу 15, Джонса принадлежат книги Sams Teach Yourself Advanced C in 21 Days (Sams Publishing) и Sams Teach Yourself C# in 21 Days (Sams Publishing). Питер Эйткен (Peter Aitken) пишет о компьютерах и программировании вот уже более 10 нет. Он является автором около 30 книг, а также сотен статей в журналах и коммерческих изданиях. В числе его недавно вышедших книг —— Visual Basic .NET Programming With Peter Aitken, Office XP Development with VBA, XML the Microsoft Way, Windows Script Host, Sams Teach Yourself Visual Basic .NET Internet Programming in 21 Days. B течение нескольких лет ion сотрудничал с журналом Visual Developer B качестве внештатного редактора, где вел по— пулярную рубрику по Visual Basic. Также он является постоянным автором журнала Microsoft OflicePro и \УеЬ—сайта DevX. П.Эйткен владеет компанией PGA Consulting, которая предлага- ет услуги по разработке программ и Интернет-приложений для предприятий, учебных заве- дений и правительственных организаций с 1994 г. Связаться с ним можно по адресу peterfipgacon . com. 
Благ0дарности  В первую очередь хочу поблагодарить своею соавтора Брэдли Джонса (Bradley Jones) 3a его тяжкий труд и преданность делу. Также я очень признателен всем сотрудникам Sams Pub- lishing— к сожалению, здесь невозможно перечислить их имена— благодаря которым эта книга прошла путь от замысла до воплощения. Текст и программы в этой книге прошли все- стороннюю проверку, и мы надеемся, что ошибок в ней очень мало, а может быть и вообще нет. Если читателю все-таки удастся найти ошибку, мы хотели бы знать о ней. Со мной мож- но связаться по электронной почте peterepgacon.com. Питер Эйткен  Первым делом хочу выразить свою благодарность моей жене за ее долготерпение и пони- мание в то тяжелое время, когда я бываю занят написанием очередной книги. Хорошая книга — это всегда результат слаженных усилий большого количества людей. Мне бы хотелось поблагодарить всех тех читателей, редакторов и им подобных, которые не пожалели своего времени и высказали замечания и комментарии к этой книге. Хочу на- деяться, что после’*того, как мы с Питером учли значительную часть этих замечаний, наша книга стала одним из лучших пособий для быстрого и легкого освоения программирования на языке С. Брэдли Л. Джонс joneseiquest.net  /‘—Ч\  22 Введение 
Введение  Как можно догадаться из названия, эта книга призвана помочь читателю научиться про- граммировать на языке С за 21 день. Несмотря на жесткую конкуренцию со стороны таких языков, как С++, Java и С#, язык С остается наилучшим выбором для тех, кто только начина— ет осваивать науку программирования. Как это будет рассмотрено подробнее в ходе первого дня занятий, язык С представляет собой универсальное средство разработки программ и по- этому никогда вас не подведет. Мы полагаем, что читатель принял правильное решение, избрав эту книгу своим самоучите- лем по языку С. Хотя по С написано множество книг, мы считаем, что в нашей книге этот язык излагается в наиболее логичной и легкой для изучения последовательности. И тот факт, что пять предыдущих изданий нашей книги попали в списки бестселлеров, кажется, подтверждает, что читатели с нами согласны! Предполагается, что читатель будет прорабатывать книгу по поряд— ку, по одной главе в день. От читателя не требуется никаких предварительных знаний в области программирования, хотя знакомство с другими языками, например, BASIC, может ускорить процесс обучения. Также не требуется какого-либо конкретного компилятора, компьютера или операционной среды— в книге преподается именно язык С, независимо от того, какими про- граммно-аппаратными платформами пользуется читатель: IBM РС, Мас или UNIX. Эта книга содержит и бонус —— семь дополнительных дней-уроков. Дополнительные уро— ки содержат введение в объектно—ориентированное программирование и наиболее популяр— ные объектно—ориентированные языки — С++, Java и С#. Конечно, по одним только допол- ннтельным главам нельзя освоить эти языки в совершенстве, но для начала и это неплохо.  Соглашения  В настоящей книге применяется целый ряд специальных приемов и средств для того, что— бы облегчить тернистый путь к совершенству в С. В блоках под знаком “Синтаксис” опреде- ляются специфические понятия С и объясняется их использование. В каждом таком блоке приводятся конкретные примеры и исчерпывающие объяснения команд и понятий языка. Чтобы прочувствовать стиль этих объяснений, взгляните на пример. (Не пытайтесь понять, что там написано — ведь День 1 еще не наступил!)  #include <stdio.h> printf( строка-формата[ ‚ аргументы, . . . ] ) ; ‘printf( ) _— эта функция принимает последовательность аргументов, каждый из которых соответствует спецификации формата в заданной строке формата. Она выводит данные в форматированном виде на стандартное устройство выво— да, обычно на экран дисплея. Для того, чтобы воспользоваться функцией printf( ), необходимо подключить стандартный заголовочный файл ввода- вывода stdio.h. Строка формата— обязательный параметр, тогда как остальные аргументы необязательны. Для каждого аргумента в этой строке должна присутствовать   Введение 23 
спецификация формата. Строка формата может также содержать управляющие по- следовательности символов. Вот примеры обращения к функции printf ( ) и резуль- татов такого обращения:  Пример1  #include <stdio.h> int main( void )  {  printf("This is an example of something printedl");  }  Результат выполнения примера 1  This is an example of something printed!  Пример2  printf( "This prints a character, %c\na number, %d\na floating point, %f", ’2’, 123, 456.189 ),  Результат выполнения примера 2  This prints a character, 2 a number, 123 a floating point, 456.789  B книге часто встречаются блоки “Рекомендуется/Не рекомендуется”, в которых гово- рится, что следует и чего не следует делать будущему программисту.    Рекомендуется Не рекомендуется  дочитайте этот раздел. Здесь рассказы- ; Не пропускайте контрольные вопросы‘ ий; вается про “Коллоквиум”, который про- 3 { упражнения Если ‚Ёвы успешг-ю проём/г; ‹ водится в конце каждого дня занятий. 3 %* “Коллоквиум в “вконце дня. “lieu rOTfigfbifwm ›, {42:3 5:: ‚_ ‚:“; “ё a“ «=\  \ $" за} 'at" ‚\ 3 ‚ *БА‘ 3:3?!      !  g следующему дню № W“; а ‚„ №  - ‚«       Также в тексте книги встречаются'блоки “Совет”, “Примечание” и “Предупреждение”. В “Советах” рассказывается о полезных приемах программирования на С. В “Примечаниях” даются более подробные пояснения некоторых важных понятий языка. “Предупреждения” помогут избежать возможных проблем. Многочисленные примеры текстов программ иллюстрируют понятия и свойства языка, которые можно применять в своих собственных программах. Обсуждение каждого примера программы состоит из трех частей: собственно программы, исходных данных для нее и выво- димого на экран результата ее выполнения, а также посгрочного анализа последовательности ее работы. Эти этапы отмечаются специальными пиктограммами. Каждый день занятий заканчивается разделом “Вопросы и ответы”. Он содержит ответы на самые распространенные вопросы, которые могут возникнуть при изучении материала. В конце каждого дня также проводится “Коллоквиум”. В ходе него читате- лю предлагаются контрольные вопросы и упражнения. Контрольные вопросы призваны проконтролировать знание нового материала, пройденного за день. Для проверки отве-  24 Введение 
тов или в том случае, когда вопрос оказывается слишком сложным, в приложении Е приведены правильные ответы. И все-таки невозможно выучить язык С, просто читая эту книгу. Тот, кто хочет стать про- граммистом, должен писать программы. Поэтому после каждого набора контрольных вопро- „сов идет набор упражнений. Рекомендуется попробовать себя в каждом упражнении. Лучший .способ изучить язык С — это писать программы на нем. По нашему мнению, наиболее полезные упражнения —— это те, которые отмечены заго— ловком “Поиск ошибок”. Упражнения “Поиск ошибок” — это тексты программ, содержа- щие самые распространенные среди программистов ошибки. Задача читателя —— найти и ис- править ошибку. Если с этим возникнут трудности, можно посмотреть правильный ответ в приложении Е. По мере продвижения в изучении материала книги некоторые упражнения становятся все длиннее, а в других появляется целый набор возможных ответов. Поэтому к упражнениям бо- лее поздних дней не всегда даются все ответы.  Улучшения  Идеал недостижим, но стремиться к нему не возбраняется. Вот поэтому перед вами уже шестое издание книги “Освой самостоятельно С за 21 день”. При подготовке этого издания мы приложили все усилия, чтобы сделать тексты программ стопроцентно совместимыми с как можно более широким кругом компиляторов. Книга проверялась несколько раз, чтобы ддосгичь высочайшего уровня технической точности. В нее были внесены исправления, пред- ложенные внимательными читателями предыдущих изданий.   . Исходные тексты программ, приведенные в этой книге, компилировались и @! проверялись в следующих операционных средах: DOS. Windows. Macin- tosh, UNIX, Linux и 08/2. Кроме того, читатели предыдущих изданий ис- пользовали эти программы практически во всех средах, в которых только поддерживается язык С!     В этом издании впервые появился раздел “Самостоятельная работа”. Всего таких разде- ;flon в книге шесть. В каждом из них приведена короткая программа на языке С, которая вы- gimme/r определенные операции для развлечения или с пользой, одновременно иллюстрируя %,;Ёриемы программирования. Эти программы можно набрать и запустить. Затем можно Ёпоиграть” с текстом программы — изменять его и смотреть, на что еще она способна. В об- Эдем, “Самостоятельная работа” предназначена для того, чтобы поэкспериментировать с про- “граммами. Надеемся, что работа с этими дополнительными программами доставит вам удо-  ‚Зольсгвие и принесет пользу.  I  Прилагаемый компакт—диск  Для удобства читателей все тексты программ из книги помещены на компакт-диск, прила- Ъемый к книге. На компакт-диске также можно найти несколько компиляторов языка С. % Приложении Ж “Основы работы с Бем-С++” рассказывается, как работать с компилятором ‘?Віоосізпесі Бем-С++, который также находится на компакт-диске. 53:“ Список опечаток и обновлений исходных текстов, если таковые будут, можно найти в Internet no адресу http: / / samspublishing .com.  Warm 25 
Дополнительное оформление  Чтобы отличить команды языка С от обычного текста, а также выделить важные понятия и утверждения, в книге используются разные шрифты. Тексты программ и команд набраны особым шрифтом постоянной ширины. В примерах исходных данных и результатов работы программ полужирным шрифтом постоянной ширины отмечается то, что пользователь дол- жен ввести, набрав на клавиатуре. Слова-заменители тех данных, которые фактически долж- ны стоять в тексте команды, выделены курсивным шрифтом постоянной ширины. Новые или особо важные понятия выделены курсивом.  26 Введение 
sAms пввпіі самостоятельно Неделя 1  Основные вопросы  Чтобы подготовиться к первой неделе занятий по программированию на языке С, необхо— димо следующее: компилятор, текстовый редактор и'эта книга. В данном издании книги все это уже есть. На прилагаемом компакт-диске есть два редактора программ, Bloodshed Dev- C++ и DJGPP. Оба редактора ссдержат в своем составе компиляторы. В Приложении Ж “Основы работы с Dev-CH” рассказывается, как установить компилятор Bloodshed для Win- dows. Файлы readme на компакт-диске содержат информацию o DJGPP. Если у вас уже есть редактор и компилятор, то пользоваться программами с компакт-диска совсем не обязатель- но. Поскольку в этой книге язык С рассматривается с позиций стандарта ANSI, псдойдет лю- бой стандартный редактор и компилятор. Чтобы как следует изучить программирование, нужно не только читать книги. Необходи— мо набирать и запускать на выполнение программы. Программы на С, имеющиеся в этой книге в изобилии, прекрасно подходят для такой тренировки. Если же хочется пойти более коротким путем, можно взять тексты большинства программ с прилагаемого компакт- диска— просто скопировать и запустить. Тем не менее, рекомендуется все же набрать эти программы в текстовом редакторе. Это, конечно, отнимет больше времени, но зато даст воз- можность лучше понять текст программы, взглянув на него повнимательней.  Что дальше  В течение первой недели изучается базовый материал, которым необхоцимо овладеть для понимания языка С. Первое и второе занятия посвящены тому, как составить простейшую программу на языке С и из каких основных элементов она состоит. В ходе третьего занятия информация первых двух занятий будет дополнена определениями типов переменных. На четвертом занятии из переменных конструируются простые выражения, которые позволяют вычислять новые значения по уже имеющимся. На этом же занятии рассматривается, как принять решение и изменить ход выполнения программы с помощью оператора if. Ha пятом зашттии рассматриваются функции в языке С и принципы структурного программирования. В ходе шестого занятия вводятся новые команды, с помощью которых можно управлять поряд- ком выполнения программы. Неделя заканчивается седьмым занятием, на котором изучается, 
как выводить данные на устройства выв0да и как организовать взаимодействие программы с клавиатурой и экраном. Кроме этого, читателю будет предложено несколько программ для самостоятельной рабо- ты. Они помещены между первым и вторым, а также между четвертым и пятым занятиями. Первая программа распечатывает тексты программ, снабжая их номерами строк. Вторая про— грамма играет с ее пользователем в игру — угадывание чисел. Каждое занятие Одного дня заканчивается коллоквиумом — контрольными вопросами и упражнениями. После освоения материала очередного занятия будет уже несложно ответить на все вопросы и выполнить упражнения. Ответы на вопросы и упражнения первых занятий можно найти в приложении Е. В дальнейших занятиях уже не все упражнения будут снабже- ны ответами, потому что возможных правильных ответов может оказаться очень много. Не- пременно выполняйте упражнения и проверяйте свои ответы. Всего за Одну неделю будет изучено очень много материала. Но если браться за дело по- степенно —— изучать Одну главу-занятие за один день — то больших трудностей не возникнет.   ный документом |80/|ЕС 9899:1999. Поэтому неважно, какой компилятор С используется, если он следует этому стандарту.  (_ В этой книге рассматривается стандарт ANSI языка С, регламентирован- Причи:     28 Неделя 1 
№№ ve    Первые шаги B С  Добро пожаловать на страницы книги Освой самостоятельно С за 21 день! С сегодняш- него урока начинается ваш путь будущего профессионального программиста на С. В ходе этого занятия будут рассмотрены следующие вопросы.  I Чем язык С выделяется среди множества языков программирования I Этапы разработки программы . I Как написать, скомпилировать и запустить первую программу на С I Сообщения об ошибках от компилятора и компоновщика  Краткая история языка С  ‚_ Наверняка читателю интересно узнать, откуда взялся язык С и его название. С был создан `Деннисом Ричи (Dennis Ritchie) B лабораториях Bell Telephone Laboratories B 1972 г. Этот flux создавался не от скуки, а для конкретной задачи — разработки операционной системы UNIX (которая сейчас используется на множестве компьютеров). С самого начала язык С “;преследовал весьма практическую цель— помочь программистам, очень занятым людям, Mo писать программы. 535 Язык С оказался таким гибким и мощным, что быстро вышел за пределы лабораторий Bell. Им стали пользоваться повсюду для разработки самых разных программ. Однако вскоре Ъёзличные организации стали применять свои собственные версии языка, и различия между ;;ітими версиями, пусть и небольшие, доставляли программистам немало неудобств. В ответ ;йа возникновение этой проблемы Американский Национальный Институт Стандартов \iAmerican National Standards Institute — ANSI) сформирован в 1983 году комитет для созда- Jinn стандарта языка С. Именно этот стандарт известен как стандарт ANSI языка С. Практи- дііюски во всех современных компиляторах С предусмотрена возможность ограничить синтак- дсис программы этим стандартом. }. * Язык С изменяется очень редко. Самое недавнее изменение произошло в gall 1999 r., когда был принят стандарт ANSI 099. Этот стандарт закрепил в „ языке несколько новых возможностей, которые описаны далее в этой кни- ге. Разумеется, более ранние компиляторы не поддерживают этого но- веишего стандарта.     
Что же означает само название С? Язык называется так, потому что его непосредственный предшественник назывался В. Язык В был разработан Кеном Томпсоном (Ken Thompson) из лабораторий Bell. A почему тот первый язык назывался В -— это уж вы догадайтесь сами.  Зачем нужен С  В современном мире компьютерного программирования существует широкий выбор язы- ков высокого уровня, например, С, Perl, BASIC, Java, C#. Все они превосходны и подходят для решения практически любых задач программирования. Однако есть ряд причин, по кото- рым многие специалисты компьютерной индустрии ставят С на первое место.  l C — язык мощный и гибкий. Единственное ограничение возможностей программиро- вания на нем — это пределы вашей собственной фантазии. Сам язык почти никаких ограничений не накладывает. С используется при разработке таких программных про- дуктов, как операционные системы, текстовые редакторы, графические приложения, программы делопроизводства и даже компиляторы других языков.  I C — популярный язык, который предпочитают многие профессиональные программи- сты. Следствие этого — наличие большого количества разнообразных компиляторов С и полезных дополнений к ним.  I C — хорошо переносимый язык. Переносимый означает, что программу на С, напи- санную для одной компьютерной системы (например, для IBM PC), можно скомпили- ровать и запустить в другой среде (скажем, на DEC VAX) без особых или даже без всяких изменений. Кроме того, программу для операционной системы Microsofi Win- dows можно перенести на компьютер под управлением Linux почти без доработки. Переносимости способствует стандарт ANSI, который устанавливает требования к компиляторам С.  I Словарь С очень краток — в нем всего несколько фиксированных терминов, которые называются ключевыми словами. Они служат основой для функциональных возможно- стей языка. Может показаться, что язык с большим количеством ключевых слов (еще называемых служебными словами) был бы более мощным. Но это не так. Всякий, кто берется за программирование на С, вскоре обнаруживает, что с его помощью можно решить практически любую задачу.  I C — модульный язык. Программу на С можно (и нужно) писать в виде набора отдель- ных подпрограмм-модулей, которые называются функциями. Такие функции можно затем использовать в других программах и в других целях. С помощью корректной пе- редачи данных в функции можно добиться эффективной и экономной работы про- граммы.  Эти черты языка показывают, что С — прекрасный выбор в качестве первого языка про- граммирования для начинающих. А как насчет С++? Возможно, вы слышали о С++ и о мето— де программирования под названием объектно-ориентированное программирование. Поэто- му может возникнуть вопрос, в чем разница между С и С++, и не следует ли изучать С++ вместо С. Насчет этого не стоит беспокоиться. С++ является расширением С. Это означает, что в С++ содержится все, что есть в С, а также дополнительные средства для объектно- ориентированного программирования. При переходе к С++ оказывается, что все изученное про С по-прежнему справедливо и для его расширения, С++. Таким образом, изучение С не только дает знание одного из мощнейших и популярнейших современных языков програм- мирования, но и подготавливает к изучению объектно-ориентированного программирования.  30 Неделя 1. Основные вопросы 
Еще один язык, привлекающий к себе всеобщее внимание, —— это Java. Этот язык, как и С++, основан на С. Если позднее вам придется изучать Java, то вы обнаружите, что практиче- ски все знания С пригодятся и для программирования на Java. Самый новый из языков, родственных С, называется С# (произносится “си-шарп”). По- добно С++ и Java, C# — объектно-ориентированный язык, произошедший от С. И снова при его изучении оказывается, что значительная часть знаний о С вполне применима к програм- мированию на языке С#.   Поэтому мы включили в книгу бонус — краткое введение в С++, Java и С# в виде нескольких дополнительных уроков. В ходе этих уроков предпола— гается, что читатель уже овладел основами С.  `,‘—` Очень многие изучающие С впоследствии берутся за С++, Java или С#.     П0дготовка к программированию  Решение задачи всегда происходит в несколько этапов. Прежде всего следует поставить задачу —— ведь если не знать, в чем она заключается, то и решать нечего! Зная задачу, можно наметить план ее решения. Имея план, пора приступать к его реализации. После реализации плана необходимо проверить результаты, чтобы убедиться, что задача решена. Такая логика решения применяется во многих областях знаний, в том числе и в программировании. Разрабатывая программу на языке С (или, если уж на то пошло, программу на любом язы- ке программирования), следует придерживаться той же последовательности этапов.  _  . Определить цели и задачи программы. 2. Определить методы, которые будут использоваться для написания программы. 3. Написать программу для решения задачи. 4. Запустить программу и посмотреть на результат.  Примером целей и задач (пункт первый) может быть создание текстового редактора или системы ’управления базой данных. Намного более простая задача— отобразить свое собст- венное имя на экране. Если бы никакой цели не было, незачем было бы писать программу, поэтому считаем, что первый этап выполнен. Второй этап — определить метод решения задачи. Необходимо ли применение компью- терной программы для ее решения? Какие данные нужно получить? Какие формулы исполь- зовать? В ходе этого этапа нужно определить, что необходимо знать для решения задачи и в каком порядке получать решение. Для примера предположим, что вас попросили написать программу для определения площади, ограниченной кругом. Этап №1 на этом завершен, поскольку цель уже есть: найти площадь круга. На этапе №2 нужно определить, какие данные необходимы для нахождения этой площади. В данном примере пользователь будет обязан задать радиус круга. Зная его, можно взять знаменитую формулу “пи эр квадрат” и получить ответ. Имея все это, пора пере- ходить к этапам №3 и 4, которые собственно и составляют цикл разработки программы.  Цикл разработки программы  В цикле разработки программы имеются свои этапы. На первом этапе с помощью тексто- вого редактора создается дисковый файл, содержащий исходный текст (код) программы. На  День 1 -й. Первые шаги в С 31 
втором этапе исходный текст компилируется, и создается объектный файл. На третьем этапе скомпилированный код обрабатывается компоновщиком для получения исполняемого файла. Наконец, на четвертом этапе программа запускается на выполнение с целью посмотреть, де- лает ли она то, что изначально запланировано.  Создание ИСХОДНОГО к0да  НПБЫП MGHMHH Исходный код — 3T0 последовательность команд (операторов), которые требуют 0T KO’WW’T‘W"l выполнить заданные операции. Как уже говорилось, первым эта-  пом цикла разработки программы является ввод исходного кода в текстовый редактор. Вот пример строки исходного кода:  printf("Hello, Moml");  Эта команда отдает компьютеру распоряжение отобразить на экране сообщение " Hello, Mom! ". Пока что не стоит стараться понять все подробности ее работы.  Текстовые редакторы  Большинство компиляторов снабжены всгроенным текстовым редактором, с помощью которого можно ввести исходный код. Однако некоторые компиляторы не имеют своею ре- дактора. Чтобы узнать, имеет ли компилятор встроенный редактор текста, воспользуйтесь его руководством по эксплуатации. Если встроенного редактора нет, можно воспользоваться од- ним из многочисленных имеющихся в наличии внешних редакторов. В большинстве операционных сред имеются программы, которые можно использовать как редакторы. В среде UNIX или Linux 3T0 редакторы ed, ex, edit, emacs, vi. B среде Micro- soft Windows для этой цели имеются Notepad и WordPad. Операционная система MS DOS версии 5.0 или более новая имеет редактор Edit, a более ранние ее версии — редактор Edlin. Система РС DOS версии 6.0 или более поздняя предлагает редактор Е. В системе 05/2 име- ются редакторы Е и EPM. B большинстве программ обработки текста применяются средства форматирования для оформления документов. Такие тексты не могут правильно читаться другими программами. Стандарт ASCII (American Standard Code for Information Exchange—— Американский стан- дартный код обмена информацией) задает типовой текстовый формат, который может ис- пользоваться и обрабатываться почти любой программой, включая компиляторы С. Многие программы работы с текстом, такие как WordPerfect, Microsoft Word, WordPad, WordStar, мо- гут сохранять файлы исходного кода в формате ASCII (как текстовый файл, а не файл доку- мента с полиграфическим оформлением). Чтобы сохранить документ, созданный с помощью такой программы, в виде файла ASCII, при сохранении следует специально задать формат простого текста или ASCII. Если не подходит ни один из перечисленных редакторов, всегда можно приобрести какой- нибудь другой. Существуют пакеты программ, как коммерческие, так и условно-бесплатные, разработанные специально для написания исходного кода программ.   № заказать по почте. Информация о них также содержится в рекламных раз- ` \: делах компьютерных журналов. На прилагаемом компакт-диске имеется два компилятора со встроенными редакторами текста: Bloodshed Dev-C++ и DJGPP. Более подробная информация о них находится на компакт-диске и в Приложении Ж “Основы работы с Dev-CH".  (" МОЖНО OTbICKaTb ЭТИ редакторы В местных КОМПЬЮТЭрНЫХ магазинах ИЛИ     32 Неделя 1. Основные вопросы 
При сохранении файла исходного кода ему нужно дать имя. Оно должно быть описатель- ным —— отражать то, что делает программа. Кроме того, при сохранении файла программы на С ему следует присвоить расширение .с. Хотя файл исходного кода может иметь любое имя и расширение, именно расширение .с считается стандартным и наиболее подходящим по  смыслу.  Компилирование исходного кода  НПВЫЙЩМНЙ Программист обычно понимает то, что он написал в своем исходном коде на - ' ' языке С (TOT, КТО читал и изучал эту книгу, наверняка поймет). А вот компьютер  не понимает. Компьютеру нужны цифровые, или двоичные, инструкции на так называемом машинном языке. Чтобы программа на С заработала на компьютере, ее нужно перевести из исходного кода на машинный язык. Этот перевод, или компиляция, выполняется программой под названием компилятор и составляет второй этап цикла разработки программы. Компиля- тор берет файл исходного кода в качестве исходных данных и генерирует дисковый файл, со- держащий инструкции на машинном языке в точном соответствии с командами исходного кода. Последовательность инструкций на машинном языке, сгенерированная компилятором, называется объектным кодом, а дисковый файл, содержащий ее, —— объектным файлом.   - B этой книге рассматривается стандарт ANSI языка С. Поэтому не имеет "№№ значения, какой конкретный компилятор С используется, если он поддер- ` живает этот стандарт. Но следует помнить, что не все компиляторы под— держивают его. Обозначение нынешнего стандарта языка С выглядит так: ISO/IEC 9899:1999. Это слишком сложное название, поэтому дальше в книге используется обозначение C-99.     Каждому компилятору требуется особая команда для создания объектного кода. Обычно для компиляции исходного кода вволится эта команда, и следом —— имя соответствующего файла. Вот примеры команд, которые используются для компиляции файла исходного кода radius .с с помощью различных компиляторов для DOS и Windows:   Компилятор Команда Microsoft C c1 radius.c Borland Turbo C tcc radius.c Borland C bcc radius.c  Чтобы скомпилировать файл radius.c Ha компьютере под управлением системы UNIX, используйте команду  сс radius.c  Ha компьютере, где есть компилятор GCC, нужно ввести команду gcc radius.c  Эта же команда gcc используется и в системе DOS, если в ней установлен компилятор GNU C/C++ версии DJ Delorie. OH имеется на прилагаемом компакт-диске. Чтобы узнать, ка- кой именно командой запускается конкретный компилятор, обратитесь к его документации. В графической интегрированной среде разработки программ процедура компилирования еще проще. В большинстве графических сред текст программы можно отправить на компи- ляцию, просто щелкнув на пиктограмме или выбрав команду меню. После завершения ком- пиляции можно щелкнуть на другой пиктограмме или снова выбрать команду меню для за-  День 1-й. Первые шаги в С 33 
пуска программы на выполнение. Подробности процедуры компиляции и запуска программы описаны в документации к конкретной среде программирования. Примером графической среды разработки, работающей под Microsoft Windows, служит Bloodshed Dev-C++. Ее мож- но найти на прилагаемом компакт—диске. Графические среды разработки имеются почти для всех известных аппаратно—программных платформ. Итак, компиляция дает объектный файл. Просмотрев список файлов каталога или папки, в котором происходила компиляция, вы найдете файл с тем же именем, что и текст программы, но с расширением .obj (вместо .с). Расширение .obj обозначает объектный файл, который используется компоновщиком. В системе Linux или UNIX компиляторы создают объектные файлы с расширением .о, а не .obj.  Создание исполняемого файла  НПВЫЙ mum" I'Ipeme чем запускать программу, НЪ/Жно пройти еще один этап. Частью стан- дарта ANSI C является библиотека функций, которая содержит объектный код  (т.е. код, уже скомпилированный заранее) стандартных функций. Стандартная функция содержит код, написанный ранее и предоставленный вместе с компилятором в форме, гото— вой к употреблению.  Функция printf( ), использованная в предыдущем примере, —— зто библиотеч— ная функция. Библиотечные функции выполняют самые необходимые и распро— ограненные операции, например, отображение информации на экране или чтение данных из дисковых файлов. Если в программе используются такие функции (трудно представить себе такую программу, в которой бы они не использовались), то объектный файл, сгенерирован— ный из исходного кода, необходимо скомпоновать со стандартной библиотекой функций для получения окончательной исполняемой программы. Термин “исполняемая” означает. что эту программу уже можно непосредственно запустить на выполнение. Процесс связывания объ— екгного кода и библиотек функций называется компоновкой, а осуществляет его программа п0д названием компоновщик. На рис. 1.1 показан весь процесс превращения исходного кода в объектный, а затем в ис— полняемую программу.  Финальная стадия разработки программы  После того, как написанная вами программа скомпилирована и полключены внешние функции— т.е. создан исполняемый файл— программу можно запустить на выполнение, введя ее имя в командной строке. Так запускаются все программы, в том числе написанные кем—то другим. Если выполнение программы дало не те результаты, которые ожидались, не— обходимо вернуться к первому этапу разработки. Нужно выяснить, в каком месте программы возникла неполадка, и исправить ошибку в исходном тексте. Виеся изменения в исходный код, необходимо перекомпилировать программу и снова создать исполняемый файл, уже ис— правленный. Этот процесс следует повторять, пока программа не заработает так, как от нее ожидают. И еще одно замечание по поводу компиляции и компоновки: хотя эти два этапа рассмат— риваются отдельно, многие компиляторы выполняют их вместе, например, компиляторы для DOS, упоминавшиеся выше. В большинстве графических сред разработки программ компи— ляция и компоновка могут выполняться как отдельно, так и вместе по желанию программи— ста. И все же, независимо от способа выполнения эти два процесса следует считать отдель— ными, даже если они запускаются одной командой.  34 Неделя 1. Основные вопросы 
  Редактор текста    Исходный код   Компилятор  Объектный кед    `,—  Исполняемая  и лио ки Б 6 те Компоновщик программа    Рис. 1 . 1 . Исходный код на языке С преобразуется компилятором в объектный код, а затем компоновщиком —— в исполняемый файл  Этап 1   Этап 2  Этап 3  Этап 4  Цикл разработки программы на С  Создание исходного кода программы с помощью текстового редактора. Традиционно файлы исходного кода С имеют расширение . с (например, myprog . с, database . с и т.п.). Компиляция программы. Если компилятор не нашел в программе никаких ошибок, он создает объектный файл. Объектный файл имеет то же имя, что и файл ИСХОДНОГО кода, и расширение .obj или .0. Например, компи- mum файла myprog.c дает объектный файл myprog.cbj или myprog.c. Если компилятор находит ошибки, то сообщает о них. Тогда необходимо вернуться к этапу 1 и внести исправления в исх0дный код. Компоновка исполняемого файла. Если не возникнет никаких ошибок, компоновщик сгенерирует исполняемую программу в виде дискового файла с расширением . exe и тем же именем, что и у объектного файла. Например, из файла myprog . obj получится программа myprog . exe. Выполнение программы. Следует протестировать программу, чтобы убедиться в правильности ее работы. Если она не работает как следует, придется вернуться к этапу 1 и внести необх0димые исправления.  На рис. 1.2 показаны все этапы разработки программы. Через эти этапы придется прохо- дить многократно, чтобы заставить работать даже сравнительно несложную программу. Ведь и самые опытные программисты не могут просто сесть и с ходу написать целую программу без единой ошибки! Поскольку вам придется прох0дить по цепочке “редактирование текста —- компиляция —— компоновка — тестирование” множество раз, необходимо хорошо овладеть средствами, которые для этого используются: редактором, компилятором и компоновщиком.  День 1-й. Первые шаги в С  35 
Ввод > исходного  Ё“?  Компиляция исходного кода             Ошибки комПиляции?  Нет   Компоновка программы        Ошибки компоновки?   Выполнение программы        Рис. 1.2. Этапы разработки программы на языке С  Первая программа на C  Читателю, наверное, уже не терпится попробовать написать свою первую программу на языке С. Чтобы помочь вам в знакомстве с компилятором, в листинге 1.1 приводится корот- кая программа, с которой можно поработать. Возможно, пока не все в ней будет понятно. Это не страшно. Главное— прочувствовать процесс написания, компиляции и выполнения на- стоящей программы на С. Эта демонстрационная программа называется hello.c. Она всего лишь отображает на эк- ране слова Не110, Worldl. Это традиционное введение в программирование на С, неизменно полезное для начинающего программиста. Исходный текст программы приведен в листин- ге 1.1. Вводя текст в редакторе, опускайте номера строк и двоеточия.  36 Неделя 1. Основные вопросы 
Листинг 1.1 . HBLLO.C   T  ЧФШЬШМ   #include <stdio.h>  int main(void)  { printf("Hello, Worldl\n");  return 0;  He забудьте установить и настроить свой компилятор в соответствии с его документаци-  ей. В любой операционной среде —— Linux, UNIX, DOS, Windows и т.д. — необходимо внача- ле освоить основные операции по использованию редактора и компилятора. П0дготовив ре- дактор и компилятор к работе, перех0дите к ввоцу, компиляции и выполнению программы HELLO.C, как это описано далее.  Ввод и компиляция программы НЕ|_|_О.С  1.  (" Не вводите номера CTpOK И ДВОЗТОЧИЯ после НИХ. ОНИ приведены ТОЛЬКО впиши:  3. 4. 5.  Для вв0да и компиляции программы HELLO.C выполните следующие операции.  Запустите редактор, нахоцясь в том каталоге, где будет размещена программа. Как уже говорилось, можно пользоваться любым текстовым редактором, но многие компиляторы (такие как Borland Turbo C или Місгозой` Visual C++) входят в состав интегрированных сред разработки (ИСР) , которые позволяют введить, компилировать и запускать про- граммы из одной удобной оболочки. Обратитесь к документации, чтобы выяснить, снаб- жен ли конкретный компилятор интегрированной средой.  Введите исхолный текст программы HELLO.C с клавиатуры именно так, как он приведен в листинге 1.1. В конце каждой строки нажимайте клавишу <Enter>.   для удобства ссылок.     Сохраните исхоцный к0д. Дайте файлу имя hello. 0. Проверьте наличие файла hello. с в нужном каталоге, просмотрев список файлов.  Выполните компиляцию файла hello.c и создайте исполняемый файл с помощью ком- поновщика. Для этого воспользуйтесь соответствующей командой компилятора. Должно появиться сообщение об отсутствии ошибок компиляции.  Прочитайте сообщения компилятора. Если нет ошибок и предупреждений, то все идет хорошо. Если при наборе текста программы была допущена опечатка, то компилятор заметит ее и выдаст сообщение об ошибке. Например, если вместо слова printf набрать prntf, поя- вится сообщение примерно такого вида:  Error: undefined symbols: _prntf in hello.c (hello.OBJ) Вернитесь к пункту 2, если появилось это или какое-нибудь другое сообщение об ошиб-  ке. Откройте файл hello.c B редакторе. Тщательно сравните текст файла с листингом 1.1, внесите исправления и перейдите к пункту 3.  День 1-й. Первые шаги в С 37 
8. Ваша первая программа на С теперь должна пройти компиляцию благополучно и стать готовой к запуску. В списке файлов каталога вашей программы, имеющих имя hello и любое расширение, должны появиться следующие файлы:  hello.c _— файл исходного текста, набранного в редакторе;  hello.obj или hello.o-——— объектный код, соответствующий исходному тексту про- граммы hello.c;  hello.exe-— исполняемая программа, созданная путем компиляции и компоновки файла hello. с.  9. Для запуска программы hello.exe Ha выполнение просто введите hello. Ha экране поя- вится сообщение Hello, Worldl.  Примите наши поздравления —— вы только что ввели, скомпилировали и запустили на вы- полнение первую программу на языке С. Конечно, программа hello.c очень проста и ничего полезного не делает, но это только начало. На самом деле, большинство нынешних мастеров программирования начинали изучать С таким же образом —— компилированием программы hello.c ———— так что вы попали в хорошую компанию.   Если вы пользуетесь компилятором Bloodshed Dev-C++ с прилагаемого @!!! компакт—диска, прочитайте приложение Ж. В нем рассказывается об уста- новке среды Бем-С++ и разработке программ с ее помощью. Этот компи— лятор работает в операционной системе Windows 95 или более новой.     Ошибки компиляции  Ошибка компиляции случается тогда, когда компилятор встречает в исходном коде что—то такое, чего он не может понять. Это может произойти из—за любой опечатки по сотне разных причин. К счастью, современные компиляторы не ограничиваются тупым непониманием: они сообщают пользователю, в каком месте они споткнулись и в чем проблема. Это облегчает поиск ошибки и ее устранение. Мы проиллюстрируем сказанное, нарочно введя ошибку в текст ранее рассмотренной программы hello.c. Если вы проработали этот пример (искренне надеемся, что это так), то должны иметь копию файла hello.c на диске. Откройте ее в редакторе, подведите курсор к концу пятой строки, в которой вызывается функция printf( ), и удалите точку с запятой. Программа hello .с станет такой, как в листинге 1.2.  Листинг 1 .2. HELL02 .C —- то же, что HELLO.C, но c ошибкой   1: #include <stdio.h>  2: 3: int main(void) 4: { 5: printf("Hello, World!\n“) 6: return 0; 7:}   Теперь сохраните файл. Программа готова к компиляции— введите соответствующую команду. Из—за нарочно внесенной ошибки компиляция не завершится корректно. Вместо этого компилятор выдаст примерно такое сообщение об ошибке:  hello.c(6): Error: ';' expected  38 Неделя 1. Основные вопросы 
Разберем эту строку подробнее. Она состоит из трех частей:  `11е11о.с Имя файла, в котором найдена ошибка (6 ) : Номер строки, в которой найдена ошибка Error: ’ ; ' expected Описание ошибки  Это сообщение очень информативно: в нем говорится, что в строке 6 файла hello.c ком— пилятор ожидал встретить точку с запятой, но ее там не оказалось. Однако мы знаем, что на самом деле точка с запятой была удалена из пятой строки. Что же здесь не так? Перед нами загадка, почему компилятор сообщает об ошибке в строке 6, хотя ошибка была намеренно сделана в строке 5. Ответ на эту загадку состоит в том, что в языке С такие мелочи, как пере- нос строк, не учитываются. Поэтому точка с запятой после функции printf( ) могла бы сто- ять и в следующей строке, хотя это выглядело бы несуразно и представляло тем самым пло- хой стиль программирования. Только встретив следующую команду программы (return) B строке 6, компилятор убедился в том, что точка с запятой действительно пропущена. Вот по- этому компилятор и сообщил об ошибке в строке 6. Итак, обнаружен один неоспоримый факт в отношении компиляторов С и их сообщений об ошибках: компиляторы ловко обнаруживают ошибки, но с мышлением у них туговато. Чтобы найти истинное местонахождение ошибки, о которой сообщает компилятор, нужно правильно интерпретировать его сообщение— а для этого нужны знания языка С. Часто _ошибки находятся именно в той строке, на которую указывает сообщение, а если это не так, то почти всегда в предыдущей. Поначалу найти ошибку бывает не так-то просто, но со вре— менем придет опыт и навык их обнаружения.   Вид сообщений об ошибках зависит от конкретного компилятора. В боль- №1!!! шинстве случаев эти сообщения дают понять, где произошла ошибка и ка- кая именно.     Прежде чем покончить с этой темой, рассмотрим еще один пример ошибки компиляции. Снова откройте файл hello. с в вашем редакторе и внесите в него следующие изменения.  1. Верните на место точку с запятой в конце строки 5. 2. Удалите двойную кавычку перед словом Hello. Сохраните файл на диске и снова скомпилируйте программу. На этот раз компилятор должен выдать примерно такие сообщения об ошибках: hello.c(5): Error: undefined identifier 'Hello' hello.c(7): Lexical error: unterminated string  Lexical error: unterminated string Lexical error: unterminated string  Fatal error: premature end of source file  Первое сообщение описывает действительную ошибку, имеющую место в строке 5 из-за слова Не11о. Сообщение undefined identifier означает, что компилятор не знает, как по- нять слово Hello, потому что теперь оно без кавычек. А как насчет остальных четырех оши— бок? Пока не стоит беспокоиться о том, что написано в сообщениях. Важно понять, что одна- единственная ошибка в исходном коде С может породить сразу несколько сообщений. Из этого нужно извлечь следующий урок: если компилятор сообщает о нескольких ошиб- ках, а вы нашли только одну, то исправьте ее и перекомпилируйте программу. Может ока— заться, что одного исправления достаточно для того, чтобы программа заработала правильно.  День 1-й. Первые шаги в С 39 
Сообщения об ошибках компоновки  Ошибки компоновки встречаются сравнительно редко. Их причиной обычно бывает опе- чатка в имени какой-нибудь библиотечной функции С. В нашем случае это сообщение Error: undefined symbols:, 3a которым следует неправильно введенное имя (со знаком п0дчеркивания перед ним). Чтобы избавиться от этой ошибки, достаточно исправить имя.  Резюме  После изучения материала сегодняшнего урока вы, наверное, уже убедились, как вы муд- ро поступили, выбрав язык С для освоения программирования. С предлагает непревзойден- ное сочетание эффективности, популярности и совместимости. Эти факторы, дополненные родственными связями с такими объектно-ориентированными языками, как Java и С#, ставят С вне конкуренции. На сегодняшнем занятии бьши рассмотрены этапы написания программы на С — процес- са, известного также под названием разработки программы или программирования. Очень важно освоить цикл “редактирование — компиляция —— компоновка — тестирование”, а так- же овладеть инструментальными средствами, используемыми на каждом этапе. Ошибки составляют неотъемлемую часть процесса разработки программы. Компилятор С обнаруживает ошибки в исх0дном к0де и выдает сообщения, в которых говорится о сущности ошибок и их местонахождении. Используя эту информацию, ошибки можно найти и испра- вить в исх0дном к0де программы. Однако следует помнить, что компилятор не всегда точно сообщает местонахождение и причину ошибки. Иногда бывает необходимо отследить истин- ную причину сообщения об ошибке, пользуясь своими знаниями и опытом программирова- ния на С.  Вопросы и ответы  Если нужно передать кому-то написанную программу, какие файлы следует переда- вать? Язык С — компилируемый язык, и это Одно из его крупных достоинств, т.е. после компи- ляции программы образуется исполняемый файл, который является самостоятельной, готовой к выполнению программой. Чтобы передать программу hello всем своим друзьям- обладателям компьютеров, дайте им исполняемый файл hello.exe. Им не понадобится ни файл исхолного к0да hello.c, ни файл объектного кеда hello.cbj или hello.o. Компилятор С им тоже не нужен. Однако новые владельцы исполняемого файла должны иметь ту же ап- паратно-программную среду, что и вы, например, РС, Macintosh, Linux и т.п.  Есть ли необходимость хранить файл исходного кода (.с) или объектный файл (.obj) после создания исполняемого ехе-файла? Удалить файл исходного к0да означает потерять возможность вносить в программу изме- нения B будущем, так что его лучше оставить. Объектные файлы —— другое дело. Хотя в опре- деленных обстоятельствах есть причины хранить также и объектные файлы, это пока выхо- дит за пределы наших нынешних познаний. Пока что можно удалять объектные файлы после создания исполняемого файла. Когда объектный файл понадобится, можно заново скомпили- ровать исх0дный код.  40 Неделя 1. Основные вопросы 
Многие интегрированные среды разработки создают и другие файлы в дополнение к ис— ходным (.с), объектным (.obj или .о) и исполняемым (.ехе). Пока существует файл исход— ного кода (. с), всегда можно создать эти другие файлы заново.  Если компилятор снабжен встроенным редактором, обязательно ли пользоваться именно этим редактором? Совершенно необязательно. Можно пользоваться любым редактором, который способен сохранить исходный код в виде текстового файла. Если к компилятору прилагается редактор, можно попробовать поработать с ним. Не понравится ———- выберите другой. Лично мы пользу— емся отдельным редактором, хотя у всех наших компиляторов есть свои собственные. Редак— торы, входящие в комплект компиляторов, постепенно совершенствуются. Некоторые из них автоматически форматируют и оформляют исходный код. Многие редакторы выделяют раз- личные фрагменты кода разными цветами для удобства поиска ошибок и других целей.  Что делать, если есть компилятор С++, но нет компилятора С? Как уже упоминалось в ходе этого занятия, С++ представляет собой расширение С. Это значит, что компилятор С++ можно использовать для компиляции программ на C. Многие программисты пользуются компилятором Microsoft Visual C++ для компиляции программ на С в среде Windows и компилятором GNU B системах Linux и UNIX. Компиляторы, находя— щиеся на прилагаемом компакт—диске, справятся с программами, написанными как на С, так и на С++.  Можно ли игнорировать предупреждающие сообщения? Некоторые предупреждения вызваны причинами, которые не повлияют на выполнение программы, а некоторые могут повлиять. Если компилятор выдает предупреждение, это сиг- нал 0 том, что не все в порядке. В большинстве компиляторов можно установить уровень вы— дачи предупреждающих сообщений. На разных уровнях можно получать либо только самые серьезные предупреждения, либо все подряд вплоть до самых нусгяковых. В неко горых ком— пиляторах есть даже промежуточные уровни предупреждения, При отладке программы нуж— но прочитать все предупреждающие сообшения и принять решение. Всегда лучше довести программу до такого состояния, чтобы не получать вообще никаких сообщений —— ни оши- бок, ни предупреждений. (Конечно, при наличии ошибок компилятор никогда не сгенерирует исполняемого файла.)  Коллоквиум  Сегодняшний коллоквиум содержит контрольные вопросы, которые помогут вам закре- пить полученные знания, а также упражнения, призванные дать вам некоторый опыт приме- нения этих знаний. Постарайтесь ответить на все вопросы и выполнить упражнения, прежде чем переходить к следующему занятию. Правильные ответы приведены в Приложении Е.  Контрольные вопросы  . Назовите три причины, почему язык С —— один из лучших языков программирования.  Каково назначение компилятора? Какие этапы содержит цикл разработки программы?  вузы—а  Какую команду нужно ввести, чтобы скомпилировать программу под именем programl .с на вашем компьютере?  День 1—й. Первые шаги в С 41 
9.  Выполняет ли ваш компилятор компиляцию и компоновку за один шаг после ввода одной команды, или же необходимо вводить две команды?  Какое расширение должны иметь файлы исходного кода С? Подходит ли название FILENAME. TXT для файла исходного кода на С?  Если вы запускаете на выполнение написанную и скомпилированную вами программу на f C, a она работает не так, как ожидалось, что делать в этом случае?  Что такое машинный язык?  10. Каково назначение компоновщика?  Упражнения  1.  42  Откройте объектный файл, созданный для листинга 1.1, в вашем текстовом редакторе. Похож ли объектный файл на файл исходного кода? (Не сохраняйте этот файл, завершая работу с редактором.)  Введите приведенную ниже программу и скомпилируйте ее. Что она делает? #include <stdio.h>  int radius, area;  int main( void )  { printf( "Enter radius (i.e. 10): " ); scanf( "%d", sradius ); area = (int) (3.14159 * radius * radius); printf( "\n\nArea = %d\n", area ); return 0; }  Введите приведенную ниже программу и скомпилируйте ее. Что она делает? #include <stdio.h>  int x, y;  int main( void )  { for ( x = 0; x < 10; x++, printf( "\n" ) ) for ( y = 0; у < 10; Y++ ) printf( "X" ); return 0; }  Поиск ошибок. В этой программе есть ошибка. Введите программу и скомпилируйте ее. В каких строках обнаружены ошибки?  1: #include <stdio.h> 2: 3: int main( void ); 4: {  Неделя 1. Основные вопросы 
5: printf("Keep looking!" )); 6: printf("You \'11 find it!\n" )); 7: return 0; 8: } 5. Поиск ошибок. В этой программе есть ошибка. Введите программу и скомпилируйте ее. В каких строках обнаружены ошибки?  1: #include <stdio.h>  2: 3: int main( void ) 4: { 5: printf( "This is a program with a " )): 6: do_it( "problem!" ); 7: return 0; 8: }  6. Внесите следующее изменение в программу упражнения 3. Перекомпилируйте и снова 3a- пустите программу. Что она делает теперь?  #include <5tdio.h> int x, y;  int main( void )  { for ( x = 0; x < 10; х++, printf( "\n" ) ) for ( y = 0; у < 10: у++ ) printf( “%C“; 1 ); return О; }  День 1-й. Первые шаги в С 43 
Самостоятельная работа 1  Распечатка листингов  В книге встречается несколько разделов под названием “Самостоятельная работа”. В этих разделах приводятся программы несколько более длинные, чем примеры в основных главах. Назначение листингов этих программ —-— дать вам материал для самостоятельной работы, т.е. возможность набрать, скомпилировать. и запустить программу. Листинти могут содержать еще не изученные элементы языка. Программы для самостоятельной работы выполняют определенные операции —— практи- чески ценные или просто для развлечения. Например, первая программа называется print_it. Она печатает исходный код, снабжая его номерами строк точно так же, как это сделано в книге. Этой программой можно пользоваться впоследствии для распечатки листин- гов программ, проработанных в ходе занятий. Рекомендуем не просто набирать и запускать такие программы, а потратить немного вре- мени на экспериментирование с их текстами. Внесите изменения, перекомпилируйте, запус— тите снова и посмотрите, что получится. Объяснений того, как программа работает, не бу- дет —— только того, что она делает. Пусть это вас не волнует —— к моменту окончания работы над книгой вы поймете все, что написано в листингах. А тем временем просто получите удо- вольствие и полезные навыки от работы с готовыми работающими программами. /  Приступаем к работе  Введите и скомпилируйте приведенную ниже програміму. Если появятся сообщения об ошибках, проверьте, все ли набрано правильно. Запускается эта программа командой print_it filename.ext, где filename.ext —- имя файла исходного кода вместе с расширением. Обратите внимание, что протрамма добавляет номера к строкам кода. Пусть вас не смущает длина этой программы -— вы вовсе не обязаны понимать ее в совершенстве. Она приведена здесь с целью помочь вам сравнить распечатки своих собственных программ с теми, которые приведены в книге.  Листинг C1 . print__it. с   /* print_it.c -- программа печатает листинг с номерами строк */ #include <stdlib.h> #include <stdio.h> void do_heading(char *filename);  int line = 0, page = 0;  \офчфшдшюн .. .. .. .. .. .. .. .. .0  int main( int argv, char *argc[] ) 10: { 11: char buffer[256]; 12: FILE *fp; 13: 14: if( argv < 2 ) 
fprintf(stderr, "\nProper Usage is: " ); fprintf(stderr, "\n\nprint_it filename.ext\n" ); return(1);  } if (( fp = fepen( argc[1], "r" )) == NULL ) fprintf( stderr, "Error opening file, %s!", argc[1]); return(1); } page = 0; line = 1;  do_heading( argc[1]);  while( fgets( buffer, 256, fp ) != NULL f  { if( line % 55 == 0 ) do_heading( argc[1] ); fprintf( stdprn, "%4d:\t%s", line++, buffer )? } fprintf( stdprn, "\f" ); fclose(fp); return 0;  void do_heading( char *filename )  paqe++z  if ( page > 1) fprintf( stdprn, "\f" );  fprintf( stdprn, "Page: %d, %s\n\n", page, filename ):   Пища": ров на платформе |ВМ РС, но не обязательное для всех. В то время как   В ЭТОМ тексте ИСПОЛЬЗУЭТСЯ значение, СТЭНДЭРТНОВ дЛЯ МНОГИХ КОМПИЛЯТО-  значение stdout регламентируется стандартом ANSI, значение stdprn He относится к этому стандарту. Чтобы выяснить, как послать данные на принтер. обратитесь к документации компилятора. Один из способов обойти это затруднение— заменить stdprn Ha stdout. ВыходнЫе данные программы будут отображаться на экране. Далее, ис- пользуя средства перенаправления вывода, имеющиеся в конкретной операционной системе (в Linux и UNIX для этого используются каналы -— piping), можно послать выходную информацию на принтер вместо экрана.     На занятии 14 методы и средства, использованные в этой программе, рассматриваются  более подробно.  Самостоятельная работа 1. Распечатка листингов 45 
   Составные Части программы на языке С  Всякая программа на языке С состоит из нескольких составных частей, объединенных оп- ределенным образом, Эта книга в основном и посвящена изучению этих частей и их исполь— зованию. Для иллюстрации общей картины возьмем пусть маленькую, но законченную про- грамму на С и обсудим все ее компоненты. В ходе этого занятия будут изучены следующие вопросы,  I Короткая программа на языке С и ее составляющие I Назначение компонентов программы I Компиляция и запуск пробной программы  Короткая программа на C  B листинге 2,1 приведен исходный код программы multiply.c. Это очень простая про- грамма, Все, что она делает, — запрашивает два числа, которые нужно ввести с клавиатуры, и вычисляет их произведение, На этом этапе не стоит беспокоиться, если вы не понимаете каких-то элементов программы, Сейчас перед вами стоит цель ознакомиться с составными частями программы на С, чтобы впоследствии лучше понимать структуру листингов про— грамм.  Прежде чем разбирать предлагаемую программу, познакомимся с понятием  Hamil принц функции — центральным понятием программирования на С, Функция представ-  \\  ляет собой независимый фрагмент программы, имеющий имя и выполняющий определенную /  задачу, Код функции выполняется путем ее вызова по имени в программе. Программа также может посылать в функцию данные, которые называются аргументами, а функция может возвращать другие данные в основную часть программы, Две основные разновидности функ- ций С —— это библиотечные (стандартные) функции, входящие в состав пакета компилятора С, и нестандартные функции, которые создаются программистом, В книге подробно рас- сматриваются обе разновидности функций. 
Помните, что в листинге 2.1, как и во всех листингах книги, номера строк с двоеточиями не являются частью программы. Они приведены только для удобства ссылок, так что их на- бирать не нужно. Листинг 2.1 можно найти на прилагаемом компакт-диске в каталоге Вау01.   Листинг 2. 1 . multiply . с —— программа умножения двух чисел   /* Программа вычисляет произведение двух чисел */ #include <stdio.h>  int vall, valZ, val3; int product(int х, int у);  int main( void )  H H H N H Ф Ф a: " ОЧ U1 @ Ы N H I. I. II I. II II II I. II II II I.  { /* Ввод первого числа */ printf("Enter a number between 1 and 100: "); scanf(”%d”, &vall); 13: 14: /* Ввод второго числа */  15: printf("Enter another number between 1 and 100: ”); 16: scanf("%d", &va12); 17: 18: /* Вычисление и вывод произведения */ 19: val3 = product(vall, valZ); 20: printf ("%d times %d = %d\n", vall, valZ, val3); 21: 22: return 0; 23: } 24: 25: /* функция возвращает произведение двух переданных ей чисел */ 26: int product(int x,’int y) `  27: { Ъ 28: return (x * y); f 29: } / ц   Magnum Enter a number between 1 and 100: 35 . . - Enter another number between 1 and 100: 23  35 times 23 = 805  Составные части программы  В следующих разделах рассматриваются компоненты приведенного выше примера про- граммы. Ссылки на номера строк помогут сориентироваться в программе, выделяя в ней ос- новные составляющие.  День 2-й. Составные части программы на языке С 47 
Функция таіп() (строки 8-23)  Единственный компонент, который обязательно должен присутствовать в исполняемой программе на С, —— это функция main( ). B простейшем виде функция main( ) состоит из име— ни main, за которым следует пара круглых скобок со словом void между ними (вот так—— (void)), а затем пара фигурных скобок {}. Слово void можно опустить— и в таком виде программа будет корректно обработана большинством компиляторов. Стандарт ANSI требу- ет наличия слова void для явного указания того, что в функцию main не передаются никакие данные. В фигурных скобках стоят операторы, образующие тело программы. Согласно стандарту ANSI, единственный оператор, который обязательно должен быть в теле программы, —— это оператор return в строке 22.  Директива #include (CTpOKa 2)  Директива #include указывает компилятору С добавить содержимое включае- В… мого файла в программу во время компиляции. Включаемый файл _- это отцель— ный файл на диске, соцержащий информацию, которая может понадобиться программе или компилятору. Несколько таких файлов (также именуемых заголовочными файлами) входят в комплект поставки компилятора. Вносить какие—либо изменения в эти файлы не рекоменду— ется, вот почему они хранятся отдельно от файлов исходного кода. Заголовочные файлы должны иметь расширение .h (например, stdio.h). Директива #include приказывает компилятору включить тот или иной заголовочный файл в программу на стадии компиляции. В листинге 2.1 директиву #include следует пони— мать как распоряжение “Вставить содержимое файла stdio.h”. Почти всегда в программах на языке С используется подключение одного или нескольких заголовочных файлов. Более подробно использование заголовочных файлов рассматривается на занятии 21.  Объявление переменных (строка 4)  НПВЫЁШЕВМНН Переменная—_ это имя, которое присвоено месту в памяти, отведенному для - . хранения данных. Переменные используются для хранения информации разного  рода в ходе выполнения программы. В языке С переменную необходимо вначале объявить для того, чтобы ею можно было пользоваться. Объявление переменной сообщает компилято- ру ее имя и тип данных, которые эта переменная должна хранить. В нашем примере строка 4 содержит объявление int vall, va12, va13;, определяющее три переменных с именами vall , va12 , va13, причем каждая из них будет использоваться для хранения целочисленных значений. Более полробно переменные и их объявление изучаются на занятии 3.  Прототип функции (строка 6)  “0ma MIMI“ Прототип функции сообщает компилятору С имя и аргументы функции, входя- щей в состав "РОГРЗММЫ— ПРОТОТИП должен предшествовать любым обращениям  к функции. Прототип функции отличается от фактического определения функции. Определе- ние должно уже содержать все команды, которые составляют эту функцию. (Определения функций рассматриваются позже в ходе этого занятия.)  48 Неделя 1. Основные вопросы 
Операторы (строки 11, 12, 15, 16, 19, 20, 22, 28)  Вся полезная работа программы выполняется ее операторами. Операторы С отображают информацию на экране, считывают данные с клавиатуры, выполняют математические вычис- ления, вызывают функции, читают данные из дисковых файлов и отвечают за все остальные операции программы. Большая часть книги как раз и посвящена изучению разнообразных операторов С. Пока что следует запомнить, что в исходном коде каждый оператор обычно помещается в отдельной строке и обязательно заканчивается точкой с запятой. Операторы программы multiply. с вкратце рассматриваются в следующих разделах.  Оператор printf()  Оператор printf() (CM. строки 11, 15 и 20) представляет собой вызов библиотечной функции, которая отображает данные на экране. Оператор printf() может вывести на экран простое текстовое сообщение (как в строках 11 и 15) или сообщение вместе со значениями переменных программы (как в строке 20).  Оператор scanf()  Оператор scanf() (CM. строки 12 и 16) —— это вызов еще одной библиотечной функции. Она считывает данные с клавиатуры и присваивает считанные значения одной или несколь- ким переменным. Оператор в строке 19 вызывает функцию под именем product( ). Другими словами, при этом выполняются операторы, которые содержатся в функции product( ). B эту функцию пе— редаются аргументы vall и va12. После выполнения всех операторов функции product( ), эта функция передает в основную программу некоторое значение. Оно помещается в переменную val3.  Оператор return  B строках 22 и 28 находятся операторы return. Оператор в строке 28 входит в функцию product( ). OH вычисляет произведение переменных x и у, а затем возвращает результат в программу, которая вызвала функцию product( ). Оператор в строке 22 возвращает значение 0 B операционную с ему непосредственно перед окончанием программы.  ‚‚/ Определение фунЩии (строки 26—29)  "01111111 № 1411111 Функция —— это независимый, замкнутый фрагмент кода, созданный для выпол- - ' П нения определенной задачи. У каждой функции есть имя, a код функции выпол- няется в программе путем включения имени функции в оператор программы. Такое выполне- ние функции называется ее вызовом. Функция под именем product( ), находящаяся в строках 26—29, не является стандартной. Нестандартные функции пишутся программистом в ходе разработки программы. Данная кон— кретная функция очень проста и занимает всего лишь четыре строки. Все, что она делает, ~— перемножает два числа и возвращает результат в программу, откуда она была вызвана. На за— нятии 5 будет рассмотрен вопрос, почему правильное использование функций—_ это важ- нейшая составляющая мастерства в программировании на С. Отметим, что в настоящей, практически важной программе такая простая задача, как ум- ножение двух чисел, скорее всего не потребовала бы использования функции.'В этом приме- ре функция используется только для иллюстрации.  День 2-й. Составные части программы на языке С 45 
Компиляторы С всегда содержат в своем составе наборы библиотечных функций. Такие функции выполняют наиболее распространенные и необходимые программные операции (например, ввод/вывод с использованием клавиатуры, дисплея и жесткого диска). В нашем примере библиотечными функциями являются printf( ) и scanf( ).  Комментарии (строки 1, 10, 14, 18, 25)  Всякий фрагмент программы, который начинается с символов / * и заканчивает— [… ся символами */, называется комментарием. Компилятор игнорирует все ком- ментарии, так что они не оказывают ни малейшего влияния на ход выполнения программы. В комментариях можно написать все, что угодно, и работа программы от этого не изменится. Комментарий может занимать часть строки, всю строку или несколько строк. Вот три примера: /* Комментарий в одной строке */ int a,b,c; /* Комментарий, занимающий часть строки */ /* Комментарий, занимающий несколько строк */  Не следует использовать вложенные комментарии. Блаженный комментарий —— это такой комментарий, который вставлен внутрь другого. Большинство компиляторов сочтут непра- вильной следующую конструкцию: /* /* Это вложенный комментарий */ */ Некоторые компиляторы все-таки разрешают использование вложенных комментариев. Даже если это соблазнительная возможность, ее следует избегать. Одно из преимуществ язы- ка С —— это его хорошая переносимость, а вложенные комментарии и тому подобные нестан- дартные средства могут ограничить переносимость программы. Также вложенные коммента- рии могут привести к возникновению трудно обнаруживаемых ошибок. Многие начинающие программисты считают, что комментарии к программам излишни, а их написание —— пустая трата времени. Это грубая ошибка! Работа вашей программы может казаться совершенно очевидной в процессе написания. Но со временем программы становят- ся все больше и сложнее. При их разработке, а также при необходимости внести изменения в программу, написанную полгода назад, комментарии оказываются уже просто бесценными. Поэтому сейчас самое время начинать вырабатывать привычку подробного комментирования всех структур данных и операций в программах. В новейшем стандарте ANSI появилась возможность использовать односгрочные ком- ментарии. Такие комментарии уже давно использовались в языках С++ и Java, так что их по- явление в С вполне закономерно. Для обозначения Однострочного комментария используется двойная косая черта. Вот два примера таких комментариев:  // Здесь вся строка — это комментарий int x; // Комментарий начинается с двух косых черт  Две косые черты указывают, что вся оставшаяся часть строки —— это комментарий. Такая возможность в языке С появилась с введением стандарта ANSI С-99.  /  50 Неделя 1. Основные вопросы 
фигурные скобки (строки 9, 23, 27, 29)  Pqflblii "WWW Фигурные скобки {} заключают в себе строки кода, образующие любую функ- Ь I I ' цию С -—- в том числе и функцию ma1n( ). Группа из одного или нескольких опе-  рапторов, заключенная в фигурные скобки, называется блоком. Как мы узнаем на последую- щих занятиях, блоки находят очень широкое применение в языке С.   Рекомендуется Не рекомендуется уиеитируите исходный код программ ,     \\ .  g He комментируйте места в программе? ‘. :ёожнотодробнее, особенно те опе— Ё которые и так совершенно очевидны.  -222!” или функции которые He вполне , '~ идны SH МОГУР создать затруднения г;, идальнеишем HaMeHeHHH программы ° Йците эффективныи стиль ком- Ъ рования От? Немногословных или  Например, следующий комментарии Ё/* Этот оператор отображает Hello World! на экране */ ‚ printf( ”Hello World!" ), пожалуй, будет излишним —-— по крайней мере, после того, как Bbl 06воитесь 03 функцией printf() H всеми тонкостями ее работы.   “»  »›  {\\ т \\…чмух,» ›\  ым”.  km “mm... x.-- <.»  "' ‚_ $3551": £45213“ :33: K (* (i :1 2 г винам: + к 12" ‚ «, ‚ a _, \{ \ ‘. }\; M ,. ‚Лёд \ „ ‘ " \‘C h: " >“ "^ "$ " за:-„› S, ' ) М:…) ‚" »?! "` $ › “:"! “к:! a") ЁЁ ‘2» W?" Ж ›  М…»   че— / ‹ |т№№№т »›еж „аг ‹  .. д ‚ v “w m » щ…… v v v ! »… г..-_ v ` „_…. .. Ыкья’“(}\—М мА.—\- NAM „махач—ю. ..‹ м-….№›»‹м.… …. an.-.  Выполнение программы  Не пожалейте времени, чтобы набрать, скомпилировать и запустить программу multiply.c. Это будет хорошей дополнительной тренировкой в обращении с редактором и компилятором. Вспомним процесс создания программы, изученный на первом занятии.  1. Сделайте каталог, в котором будет находиться программа, текущим. / 2. Запустите редактор.  3. Введите исходный код multiply.c точно в таком виде, как в листинге 2.1, и не забудьте убрать номера строк и двоЁгочия. /  / / 1 /  4. Сохраните файл программ I  / /  5. Выполните компиляцию и создайте исполняемый файл, введя соответствующие команды. Если не было сообщений об ошибках, запустите программу, набрав multiply B командной   строке. 6. Если появились сообщения об ошибках, начните снова со второго этапа и исправьте ошибки. В Приложении Ж рассказывается, как ввести и скомпилировать ““…"" программу с помощью среды разработки Dev-C++.      O точности и аккуратности  Компьютер — машина быстрая и точная, но излишне педантичная. У него не хватает во- ображения даже для того, чтобы исправить простейшую ошибку в программе. Он все пони- мает буквально _ именно так, как вы ввели, а не так, как вы имели в виду.  День 2-й. Составные части программы на языке С 51 
Эю относится и к исходному коду на языке С. Простейшая опечатка в программе может сбить с толку и озадачить компилятор, а то и прервать его работу. К счастью, хотя у компилятора и не хватает ума исправить ошибки (а ошибки будут ~— их делают все!), все же он в состоянии обнару- жить сам факт наличия ошибки. На предыдущем занятии уже изучалось, какие сообщения может выдать компилятор в ответ на ошибку в исходном коде и как их следует понимать.  Снова о компонентах программы  Мы рассмотрели основные составные части программы. Теперь вы уже сможете выделить эти или аналогичные компоненты в других программах. Попробуйте найти знакомые элемен- тывлжпшпеіі  Листинг 2.2. list_it . с —— распечатка листинга программы   /* list_it.c - Программа отображает листинг с номерами строк! */ #include <stdio.h> #include <stdlib.h>  void display_usage(void); int line;  (I) ‘-J (7‘ ‘JT rfih (J0 Р`, "‘ а. а. а. а. а. а. а. ll  int main( int argc, char *argv[] )  9: { 10: char buffer[256]; \ 11 FILE *fp;  H H (A) P~J .. .. .. \  if( argc < 2 )  р.; ‚_а (J1 38> а. с.  display_usage(); return 1;  }  if (( fp = fopen( argv[1], "r” )) == NULL ) {  NNHHHH о—ъоыооочсл 000.000...  fprintf( stderr, ”Error opening file, %s£", argv[l] ); return(1);  P~J P~J  Р`) P~J “>- (A) а. а. а. а.  }  1іпе = 1;  "’ Р`) "’ ‘-J С’\ {11 а. а. а.  while( fgets( buffer, 256, fp ) != NULL ) fprintf( stdout, "%4d:\t%s”, 1ine++, buffer );  (:) Ъ`Э bx) (:> \‘3 (ХЭ ..  (A) (A) а. а. а. а. а.  fclose(fp); return О;  (A) (A) Р`) Р-*  }  34: void display_usage(void) 35: { 36: fprintf(stderr, “\nProper Usage is: ” ); // 52 Неделя 1. Основные вопросы 
37: fprintf(stderr, "\n\nlist_it filename.ext\n" ): '38: }  с: >list it list it.c Резиньташ \ - -  /* list_it.c - Программа отображает листинг с номерами строк! */ #include <stdio.h> #include <stdlib.h>  void display_usage(void); int line;  int main( int argc, char *argv[] )  ШФЫФШф-ШЮН .. о. о. о. о. о. о. .. о.  { 10: char buffer[256]3 11: FILE *fp;  12: 13: if( argc < 2 ) 14: { 15: display_usage(): 16: return 1; 17: } 18: 19: if (( fp = fopen( argv[1], "r" )) == NULL ) 20: { 21: fprintf( stderr, "Error opening file, %s!", argv[1] ); 22: return(1); 23: } 24: 25:’ line = 1; 26: . 27: while( fgets( buffé» 256, гр,/{’ := NULL › 28: fprintf( stdout, "%4d:it%s", line++, buffer ); 29: 30: fclose(fp); 31: return О; 32: } 33: 34: void display_usage(void) 35: { 36: fprintf(stderr, "\nPrOper Usage is: " ); 37: fprintf(stderr, "\n\nlist_it filename.ext\n" ); 38: }  \ Программа list__it.c, приведенная в листинге 2.2, выволит на экран тексты  ‚ : имеющихся на диске программ на С. При показе текстов на экране к ним добавля- ются номера строк. Проанализировав текст этой программы, можно выделить все уже извест— ные компоненты. Обязательная функция main( ) расположена в строках 8—32. Строки 2 и 3 со- держат директивы включения заголовочных файлов #include. B строках 6, 10 и 11 стоят объяв— ления переменных. В строке 5 объявляется прототип функции void display_usage(void). B   День 2-й. Составные части программы на языке С 53 
программе довольно много операторов (см. строки 13, 15, 16, 19, 21, 22, 25, 27, 28, 30, 31, 36, 37). Определение функции display_usage( ) занимает строки 34—38. По всей программе можно обнаружить блоки, заключенные в фигурные скобки. Кстати, комментарий есть только в строке 1. Желательно включать в программу побольше комментариев, а не только одну строчку. В программе list_it.c вызывается много функций. Из них только одна нестандартная — написанная авторами программы -- a именно display_usage( ). Используемые библиотечные функции — 3T0 fopen() в строке 19; fprintf() в строках 21 , 28, 36 и 37; fgets() в строке 27; f close( ) B строке 30. Эти библиотечные функции будут рассмотрены более подробно на сле- дующих занятиях.  Резюме  Это занятие оказалось довольно коротким, но очень важным, поскольку в ходе него вы ознакомились с основными составными частями программы на языке С. Единственной обяза- тельной частью всякой программы на С является функция main( ). Всю работу программы выполняют операторы ~— элементы языка, представляющие собой команды выполнить ту или иную операцию. Важной частью программы являются переменные, которые необходимо объявить, а также комментарии к исходному коду. Кроме главной функции main( ), B программе на С могут использоваться подчиненные функции двух видов: стандартные библиотечные, которые входят в комплект поставки ком- пилятора; и нестандартные, которые пишет сам программист для своих целей. На следующих занятиях основные компоненты программы на С будут изучены более подробно с целью их реального практического применения.  Вопросы и ответы  Как влияют комментарии в тексте программы на ее работу? Комментарии предназначены для программистов. Когда компилятор преобразует исход- ный код в объектный, он просто отбрасывает все комментарии, а также пробелы. Другими словами, на выполнение программы они не влияют. Программа с большим количеством ком- ментариев будет работать с той же скоростью, что и программа совсем без комментариев. Конечно, объем исходного кода будет больше при наличии комментариев, но, как правило, это не имеет особого значения. Короче говоря, следует активно использовать комментарии, пробелы и переносы строк для того, чтобы сделать исходный код как можно более понятным и удобочитаемым.  В чем разница между оператором и блоком? Блок—_ это группа операторов, заключенная в фигурные скобки {}. Как правило, блоки можно использовать во всех местах, где допускается наличие операторов.  Где взять список существующих библиотечных функций? Ко многим компиляторам прилагается документация, содержащая описание их библио- течных функций, обычно в алфавитном порядке. Такие описания можно найти и в книгах по программированию. Довольно много стандартных функций описывается в приложении Д. После приобретения достаточных знаний языка С полезно будет прочитать это приложение, чтобы ненароком не написать заново какую-нибудь из библиотечных функций. (Что толку снова изобретать колесо!)  54 Неделя 1. Основные вопросы 
Коллоквиум  В этом коллоквиуме вам предлагается ряд вопросов на закрепление изученного материа- ла, а также упражнения для приобретения практических навыков.  Контрольные вопросы  1.  ренген-езду  Как называется группа из Одного или нескольких операторов языка С, заключенных в фи- гурные скобки?  Какой обязательный компонент всегда должен присутствовать в любой программе на С?  Как добавить в программу комментарии и зачем они нужны?  Что такое функция?  B языке С есть два вида функций. Какие это виды и чем они отличаются?  для чего используется директива #include?  Можно ли использовать вложенные комментарии?  Могут ли комментарии располагаться на нескольких строках?  Как еще называются включаемые файлы?  10. Что такое включаемый файл?  Упражнения  1. Напишите самую маленькую программу, правильную с точки зрения языка С.  2. Рассмотрим следующую программу:  "’ ..  10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:  C13 ——4 "` "` lab СА, ") Р-д .. .. .. .. .. .. .. ..  /* ех02-02.с */ #include <stdio.h>  void display_linéf§oid);  int main(void)  { display_1ine(); printf("\n Teach Yourself C In 21 Days!\n"); display_1ine(); return 0; }  /* Вывод строки звездочек */ void display_line(void) {  int counter;  for( counter = 0; counter < 30; counter++ ) printf("*" );  День 2-й. Составные части программы на языке С 55 
3. 4.  5.  56  22: 23:  }  /* конец программы */ а) Какие строки солержат исполняемые операторы? 6) Какие строки содержат объявления переменных? в) Какие строки солержат прототипы функций? г) Какие строки содержат определения функций?  д) Какие строки содержат комментарии?  Напишите пример комментария.  Что делает следующая программа? Введите, скомпилируйте и выполните ее.  1:  (inflame-mm  9: 10: 11: 12: 13:  /* ex02-04.c */ #include <stdio.h> int main(void) { int ctr; for( ctr = 65; ctr < 91; ctr++ ) printf("%c", ctr ); return 0; }  /* конец программы */  Что делает следующая программа? Введите, скомпилируйте и выполните ее.  1:  ФЧФШФШМ .. .. .. .. .. .. ..  ЦЭ  11: 12: 13: 14: 15:  /* ex02-05.c */  #include <stdio.h> #include <string.h> int main(void) { char buffer[256]; printf( “Enter your name and press <Enter>:\n"); gets( buffer ); printf( "\nYour name has %d characters and spaces!", strlen( buffer )); return 0; }  Неделя 1. Основные вопросы 
    Хранение данных в переменных и константах  Компьютерные программы работают с данными самого разного реда, поэтому им необ- ходимо иметь способ хранения этих данных. Значения данных могут быть числовыми или символьными. В языке С есть два способа хранения числовых значений — в переменных и в константах— и множество разновидностей этих способов. Переменная представляет собой участок памяти, хранящий некоторое значение, которое может изменяться в процессе выпол- нения программы. В противоположность этому, константа имеет фиксированное значение,  которое не может изменяться. На этом занятии будут изучены следующие темы. I Как хранить информацию в переменных языка С / Способы хранения числовых данных различных типов  СХОДСТВО И ОТЛИЧИЯ между СИМВОЛЬНЫМИ И ЧИСЛОВЫМИ данными  Как объявлять и и:?іциализировать переменные Два типа числовы констант в С  Прежде чем переходить—"к изучению переменных, уделим некоторое внимание тому, как работает память компьютера.  Основные сведения о памяти компьютера  Подготовленный читатель, который знает, как работает‘память компьютера, может про- пустить этот раздел. Начинающим рекомендуется прочитать его. Знание устройства и функ- ций памяти компьютера облегчает понимание многих аспектов программирования на С. Компьютер используег свою оперативную память (random-access memory— RAM), или оперативное запоминающее устройство (ОЗУ), для хранения информации в ходе работы. ОЗУ устанавливается внутри компьютера. ОЗУ — временная память, т.е. информация из нее уда- ляется и заменяется новой так часто, как это необх0димо. Она также энергозависима. Это 03- 
начает, что ОЗУ “помнит” что-либо только пока электропитание компьютера включено. Ко- гда компьютер выкл_ючают, все данные в ОЗУ пропадают. В каждом компьютере установлен определенный объем оперативной памяти. Этот объем обычно указывают в мегабайтах (Мбайт), например, 2 Мбайт, 4Мбайт, 8 Мбайт, 32 Мбайт и т.д. по возрастающей. Один мегабайт памяти равен 1024 килобайтам. Один килобайт содер— жит 1024 байта. Итак, в системе с 4Мбайт памяти установлено 4х 1024 = 4096 килобайт памя- ти. Это составляет 4096><1024 = 4 194 304 байт памяти. Байт — это самая фундаментальная единица хранения данных в компьютере. Более под- робно о байтах рассказывается на занятии 20. А сейчас ознакомимся с тем, какой объем па— мяти в байтах используется для хранения данных тех или иных типов (табл. 3.1 ).  Таблица 3. 1 . Объем памяти, необходимый для хранения данных    данные Требуемое количество байт Буква x Число 500 2 Число 241.105 Фраза “Освой самостоятельно С“ 23 Одна машинописная страница Примерно 3000   Оперативная память компьютера организована последовательно — в ней один байт сле- дует за другим. Каждый байт памяти имеет свой адрес, который однозначно определяет его, т.е. по этому адресу любой байт можно отличить от всех остальных. Адреса присваиваются участкам памяти последовательно, начиная с нуля и далее по возрастанию до максимального объема установленной памяти. На данном этапе нет необходимости думать и беспокоиться об адресах. Всю соответствующую работу проделывает компилятор С. Для чего используется ОЗУ компьютера? В нескольких целях. Программиста на языке С должно интересовать только ее применение для хранения данных программы. Данные — это вся та информация, с которой работает программа. Хранит ли программа список адресов, от- слеживает ли динамику биржевых котировок, ведет ли домашний бюджет или каталог цен на продукты, все равно соответствующая информация (имена, цены, доходы, расходы) хранится в оперативной памяти, пока работает программа. Вот вы и получили некоторое представление об устройстве памяти и хранении информа- ции. Теперь самое время вернуться к программированию на С и обсудить, как же использо— вать память для хранения данных в своих программах.  Хранение информации B переменных  "№№ ШШ Переменная — это участок оперативной памяти компьютера, предназначенный I! для хранения отдельного фрагмента данных и имеющий собственное имя. Ис— пользуя имя переменной в программе, можно таким образом фактически ссьшаться на поме- шенные в нее данные.  Имена переменных  Для использования переменных в программе на С нужно знать, как создавать их имена. В языке С имена переменных должны подчиняться следующим требованиям.  58 Неделя 1. Основные вопросы 
I Имена могут содержать латинские буквы (от a до z и от А до Z), цифры (от 0 до 9) и символ подчеркивания (_).  I Первым символом имени должна бьггь буква. Допускается также применение знака подчеркивания, но желательно в начале имени его не использовать. Цифра (от 0 до 9) не может быть первым символом имени.  I Регистр символов имеет значение (т.е., являются ли буквы прописными или строчны— ми). Язык С чувствителен к регистру, так что имена count и Count обозначают две разные переменные. I Ключевые слова С нельзя использовать в качестве имен переменных. Ключевое сло-  во — это слово, являющееся частью языка. (Полный список ключевых слов С можно найти в приложении Б.)  В списке, приведенном ниже, даются некоторые примеры допустимых и недопустимых в С имен переменных.   Имя переменной Допустимость Percent Допустимо y2x5_fg7h Допустимо annual _prof it Допустимо _l990_tax Допустимо, но не рекомендуется savingsiaccount Недопустимо: неразрешенный символ # double Недопустимо: ключевое слово С 4за1е Недопустимо: первый символ — цифра  Поскольку в языке С регистр имеет значение, имена percent, PERCENT и Percent будут обозначать три разных переменных. Программисты, как правило, пишут все переменные в нижнем регистре (строчными буквами), хотя это и не требуется. Слова из букв только в верх- нем регистре обычно применяются для обозначения имен констант (как будет рассмотрено позже в ходе этого занятия). Во многих компиляторах длина имени переменной не должна превышать 31 символа. (На самом деле, имя может быть и длиннее, но компилятор воспримет из него только 31 символ, начиная с первого.) Имея в своем распоряжении такое большое пространство для маневра, можно и нужно давать переменным имена, которые хорошо описывают помещенные в них данные. Например, в программе, которая вычисляет объемы выплат по займу, переменную interest_rate можно исполь овать для хранения величины процентной ставки. Имя пере- менной должно облегчать потіимание ее использования. Ту же переменную можно было бы назвать х или даже озау_озЬот1_1:пе_т:‚компилятору С это безразлично. Однако предназначе- ние этой переменной уже не будет столь ясным и понятным для кого—нибудь, кто возьмется читать текст программы. Хотя ввод длинных описательных имен с клавиатуры занимает больше времениГлегкость чтения и понимания программы с лихвой оправдывает эти затраты. Для имен переменных, состоящих из нескольких слов, имеется несколько соглашений по их написанию. Один стиль мы уже видели: interest_rate. Использование знака подчеркива- ния облегчает чтение словосочетания, из которого состоит имя. Есть и другой стиль, который называется записью в смешанном регистре (camel notation). Вместо использования знака подчеркивания для разделения слов, первая буква каждого слова делается прописной, т.е. вместо interest_rate пишется InterestRate. Такая запись приобретает все большую попу— лярность, потому что прописную букву писать быстрее, чем знак подчеркивания. В этой кни- ге используется знак подчеркивания, поскольку большинству людей так легче читать. Про-  граммисту следует самому решить для себя, какой стиль выбрать. \  День 3-й. Хранение данных в переменных и константах 59 
  Рекомендуется Не рекомендуется  3Mcnonbsymefigonucgrensabre3 имена, пе-д; New ачинатё‘ниме 3;; аде ремонт-иш со .- @@@“: !$: 9353.35“ ‚ (Ёёдёёуеідъ г“; тд?—'За 33 670“? б _} _ @@@;„щ 336.3113 № … ;; ЁЁЁЫ Ёэнака подче иваё!№ ‘9°° $#“ и; .. ьтрабоший'опрёделенньпи стиль име—‚ ; №одио 3 ; mfg : _ g ‘ ования переменных и придерживатесь; Не злоупотребляите именами состоя- ; Ёего.';_; :sfzf :'т‘Ё №“? ”ЁЁ“ ; „ ; .1 Ё щими Из всех прописных букв? № 33 ° 3<'  {  ii ^ №… mumm- ‚...—\ „1: „имт; ’.`—моим….- .          дтн-№.: и. ‚…… ...… А…… „___… …и: >.…    Типы числовых переменных  В языке С есть несколько типов числовых переменных. Различные типы переменных нужны потому, что для хранения разных числовых значений используется разный объем па- мяти, и арифметические операции, выполняются по—разному. Небольшие целые числа (например, 1, 99 или —8) требуют совсем небольшого объема памяти, и математические вы- числения над ними (сложение, умножение и т.п.) выполняются с очень высокой скоростью. А вот большие целые числа и числа с плавающей точкой (такие как 123 ООО 000, 3.14 или 0.000000871256) нуждаются в большем объеме памяти, да и операции над ними более трудо- емки. Используя подходящие типы переменных, можно добиться того, что программа будет работать с максимальной эффективностью. Числовые типы переменных в языке С делятся на две основные категории.  I Целочисленные переменные, в которых хранятся числа без дробной части (т.е. целые числа). В свою очередь, целочисленные переменные делятся на две разновидности: целые со знаком могут хранить как положительные, так и отрицательные значения, в то время как целые без знака — только положительные значения и ноль.  I Переменные с плавающей точкой, в которых хранятся числа с дробной частью (т.е. вещественные или действительные числа).  В каждой из этих категорий существует два или более типов переменных. Они сведены в табл. 3.2, где также указан объем памяти в байтах, обычно необходимый для хранения пере- менной соответствующего типа.  Таблица 8.2. Числовые типы данных в С    Тип Ключевое слово Требуется диапазон байт Символ char 1 —128...127 Короткое целое short 2 -32 767...32 767 Целое int 4 -2 147 483 647... 2 147 483 647 Длинное целое long 4 -2 147 483 647... 2 147 483 647 Очень длинное целое long long 8 -9 223 372 036 854 775 807... 9 223 372 036 854 775 807 Символ без знака unsigned char 1 0...255 Короткое целое без знака unsigned short 2 0...65 535 Целое без знака unsigned int 4 0...4 294 967 295 Длинное целое без знака unsigned long 4 0...4 294 967 295   60 Неделя 1. Основные вопросы 
Окончание табл. 3.2    Тип Ключевое слово Требуется диапазон байт Очень длинное целое без unsigned long long 8 0-.- .знака 18 446 744 073 709 551 615 Число с плавающей точкой float 4 1.2Е—-38...3.4Е381 одинарной точности Число с плавающей точкой double 8 2.2Е--308...1.8Е3082  двойной точности   |Примерный диапазон; точность = 7 цифр 2Примерный диапазон; точность = 19 цифр   - Примерный диапазон описывает самое большое и самое маленькое чис— lll na, которые могут храниться в той или иной переменной.      примут типы данных long long и unsigned long long.    6“ Компиляторы, не поддерживающие стандарт С—99, скорее всего, не вос— IIIIIII   B табл. 3.2 можно заметить, что типы переменных int и long идентичны. Зачем тогда нужны два разных типа? Дело в том, что эти типы идентичны на 32—ра3рядной платформе In- tel (IBM PC), но в другой системе они могут оказаться разными. Например, на машине VAX эти типы отличаются: int занимает 2 байта, а long _— 4 байта. Помните, что С —— гибкий, хо— рошо переносимый язык, вот поэтому в нем и предусмотрены два ключевых слова для этих двух типов. На [ВМ—совместимом компьютере они взаимозаменяемы. Для целых переменных со знаком совсем не обязательно использовать ключевое слово signed. Целые величины считаются имеющими знак по умолчанию. Тем не менее, при жела— нии можно применять это ключевое слово. Служебные слова, перечисленные в табл. 3.2, ис— пользуются в объявлениях переменных, o которых речь пойдет в следующем разделе. Программа из листинга 3.1 определяет размер переменных в байтах на вашем конкретном компьютере. Не удивляйтесь, если величины, которые она выдаст после выполнения, будут отличаться от приведенных ниже.  Листинг 3. 1 . sizeof . с — программ/а для вывода длины переменных разных типов   1: /* sizeof.cvvnporpanma для выводаёдпины типов переменных С */ 2: /* в байтах */ 3: 4: #include <stdi0.h> 5: 6: int main(void) 7: { 8: printf( "\nA char is %d bytes", sizeof( char )); 9: printf( "\nAn int is %d bytes", sizeof( int »› ‚10: printf( "\nA short is %d bytes", sizeof( short )); 11: printf( "\nA long is %d bytes", sizeof( long ));  12: printf( "\nA long long is %d bytes\n", sizeof( long long)); 13: printf( "\nAn unsigned char is %d bytes", sizeof( unsigned char ));  День 3-й. Хранение данных в переменных и константах 61 
14: printf( "\nAn unsigned int is %d bytes", sizeof( unsigned int )); 15: printf( "\nAn unsigned short is %d bytes", sizeof( unsigned short )); 16: printf( "\nAn unsigned long is %d bytes", sizeof( unsigned long )); 17: printf( "\nAn unsigned long long is %d bytes\n", 18: sizeof( unsigned long long)); 19: printf( "\nA float is %d bytes", sizeof( float )); 20: printf( "\nA double is %d bytes", sizeof( double )); 21: printf( "\nA long double is %d bytes\n", sizeof( long double )); 22:   23: return О; 24: } A char is 1 bytes An int is 4 bytes A short is 2~bytes A long is 4 bytes  A long long is 8 bytes  An unsigned char is 1 bytes An unsigned int is 4 bytes An unsigned short is 2 bytes An unsigned long is 4 bytes An unsigned long long is 8 bytes  A float is 4 bytes A double is 8 bytes A long double is 12 bytes  ШШШ}! Результат выполнения программы— количество байт, которое занимает пере- менная каждого из числовых типов в памяти компьютера. На обычном 32-  разрядном [ВМ-совместимом персональном компьютере эти цифры должны быть такими же, как в табл. 3.2. _ Не старайтесь понять все до единого элементы приведенной программы. Большинство из них должны быть вам уже знакомы, хотя есть и новые— например, sizeof. Строки I и 2 представляют собой комментарии: название программы и краткое описание ее работы. В сгроке 4 подключается стандартный заголовочный файл вв0да—вывода, необходимый для вы- дачи информации на экран. Программа относительно проста и состоит только из одной функции main( ), расположенной в строках 6—24. Строки 8—21 содержат исполняемое тело программы. Каждая из этих строк выв0дит на экран описание Одного из типов данных и его длину, вычисляемую оператором sizeof. Оператор в строке 23 возвращает значение 0 в опе- рационную систему перед завершением работы программы. Хотя уже было сказано, что на разных компьютерных платформах размеры типов данных могут отличаться, С дает и кое-какие гарантии относительно этих размеров. Пять следующих утверждений всегда безусловно верны. I Длина переменной типа char - один байт. I Длина переменной short меньше или равна длине переменной int. I Длина переменной int меньше или равна длине переменной long. I Длина переменной unsigned равна длине переменной int. I Длина переменной float меньше или равна длине переменной double. /,  /  62 Неделя 1 . Основные вопросы 
 В табл. 3.2 перечислены ключевые слова, которые обычно используются (№№ для обозначения типов переменных. В следующей таблице (табл. 3.3) приводятся полные наименования всех этих типов.     Таблица 3.3. Полные наименования типов данных  __—  Обычное наименование Полное наименование    char signed char short signed short int int signed int long signed long int long long signed long long int unsigned char unsigned char Unsigned short unsigned short int unsigned int unsigned int unsigned long unsigned long int unsigned long long unsigned long long int   Объявления переменных  Прежде чем пользоваться переменной в программе на С, эту переменную необходимо объявить. Объявление переменной сообщает компилятору ее имя и тип. В объявлении можно также инициализировать переменную, присвоив ей некоторое начальное значение. Если по— пытаться использовать необъявленную переменную, компилятор выдаст сообщение об ошиб- ке. Объявление переменной имеет следующий вид: тип имя;  Здесь тип указывает тип переменной и должен быть одним из ключевых слов, перечис— ленных в табл. 3.2; имя представляет собой имя переменной и должно удовлетворять требо— ваниям, о которых говорилось ранее. Можно объ вить несколько переменных Одного и того же типа, разместив их на Одной строке и отделив ;Зауг от друга запятыми: lint count, number, start; /* три целых переменных */ float percent, total; /* две вещественных переменных */ На занятии 12 вы узнаете, как важно правильно расположить объявление переменных в ‘исхолном коде, поскольку их местоположение влияет на способ использования переменных в программе. Сейчас пока что можете помешать все объявления переменных вместе перед на- чалом функции шаіп( ).  Ключевое слово typedef  Ключевое слово typedef используется для присвоения нового имени существующему ти— пу данных. Фактически с помощью typedef создается синоним. Например, оператор typedef int integer; создает имя integer, которое должно означать то же, что и int. После этого можно пользо— ваться словом integer для объявления переменных типа int, например: integer count;  День 3—й. Хранение данных в переменных и константах 63 
Обратите внимание, что typedef не создает нового типа данных, а просто позволяет ис- пользовать новое имя для уже известного типа. Наиболее популярное применение typedef связано с составными типами данных и рассматривается на занятии 11. Составные типы дан— ных представляют собой совокупности простых типов, рассмотренных на этом занятии.  Инициализация переменных  При объявлении переменной компилятор получает указание отвести ячейку памяти для хранения этой переменной. Однако величина, находящаяся в этой ячейке—_ значение пере— менной— все еще не определена. Она может оказаться нулем или случайным числовым “мусором”. Прежде чем использовать переменную, ее необходимо инициализировать —— при- своить ей некоторое начальное значение. Это можно сделать независимо от объявления пе- ременной, используя оператор присваивания, например:  int count; /* Выделение места в_памяти для count */ count = 0; /* Помещение в count значения 0 */  Обратите внимание, что здесь используется знак равенства (=), который в С обозначает операцию присваивания. O нем будет сказано подробнее на занятии 4. А сейчас стоит запом— нить, что знак равенства в программировании далеко не всегда обозначает то же самое, что в математике. Если считать запись = 12  математической формулой, то это простая констатация факта: “х равно 12”. А в языке С это означает нечто совсем иное: “присвоить значение 12 переменной с именем х”.  Переменную можно инициализировать одновременно с объявлением. Для этого следует по- ставить знак равенства после имени переменной в ее объявлении, а затем ее начальное значение: int count = 0; / double percent = 0.01, taxrate = 28.5; " В первом операторе объявляется переменная count целого типа с начальным значением О. Bo втором операторе объявляются и инициализируются две вещественные переменные двой— ной точности. Первая, percent, получает начальное значение 0.01, а вторая, taxrate, —— зна— чение 28.5. Соблюдайте осторожность, чтобы не инициализировать переменные значениями, выхо— дящими за их допустимый диапазон. Вот два примера присвоения начальных значений, вы— ходящих за диапазон: int weight = 100000; unsigned int value = -2500;  Компилятор С может и не обнаружить эти ошибки. Программа благополучно пройдет ком- пилирование и компоновку, и только на этапе выполнения она выдаст неожиданные результаты.  Рекомендуется Не рекомендуется . , "     ”Выясните. какую длину в байтах имеют : { Не используйте переменную? еще не Ёпеременные разных типов на вашемё инициализироваНную. Результаты могут компьютере. Д Ёбыть непредсказуемыми. \ Мспользуйте 1:урес1еі‚ чтобы сделатьі ‚Не пол___ь_зуйт_есь переменными типа  float или double для хранения целых чи—Ё сел. Хотя этот способ работает он пера-Ё ционапен. ‚  . тексты программ яснее и понятнее. ‘ Инициализируйте переменные при их i объявлении везде, где это возможно.    w— › …,».  64 Неделя 1. Основные вопросы 
”f”? адресата присвоить: переменной ‘3 “` '“ ’ц’ік “"ёопьшоампи маленькое значе—і $31406; 'ВЫХОВЯЩЁЁ‘ЗЁ диапазон g? ›типаЁэ : =< ; Ёе, ведём рйсваиваите отрицательные значе- g %  ““” на; Чат @:. ‚ WT. , $$?;   №53: 2;"th тий переменным типа unsigned … … „  «…,» &` $№ х...-.. „...:-… “м.-‚№№ MA “MW... з.,—„..… …. „к...   6%   Константы  Как и переменная, константа —— это именованный участок памяти, используемый про- граммой для хранения данных. Но в отличие от переменной значение константы не может изменяться в хоце выполнения программы. В языке С имеется два типа констант, каждый из которых применяется для своих целей.  I Литеральные константы.  I Символические константы.  Литеральные константы  Литеральная константа, или литерал, представляет собой значение, стоящее в исходном коде программы непосредственно там, где оно используется, в его первоначальном вице Вот два примера: int count = 20; float tax_rate = 0.28;  Здесь 20 и 0.28 … литерапы. В результате выполнения этих операторов значения кон- cram помещаются в переменные count и tax_rate. Обратите внимание, что в одной из кон- стант есть десятичная точка, а в другой нет. Именно наличие или отсутствие десятичной точ- ки определяет, является ли константа вещественной и и целой. Литеральная константа с десятичной точкой— то всегда вещественная константа, и :; компилятор С представляет ее в виде числа двойной точности с плавающей точкой. Вещест- ' венные константы можно записывать в обычной/десятичной форме, например: 123. 456 // $0.019 д 100 . … ' Обратите внимание, что третья константа записана с десятичной точкой, хотя по сути яв- ляется целым числом (не имеет дробной части) Десятичная точка нужна для того, чтобы " компилятор С воспринимал эту константу как вещественное число Без точки она считалась бы целочисленной константой. Вещественные константы можно также записывать в экспоненциальной форме. Вспомни- , те школьную или вузовскую математику: в экспоненциальной записи вещественное число представляется в виде мантиссы, умноженной на 10 в положительной или отрицательной степени. Особенно удобно записывать в экспоненциальной форме очень большие или очень маленькие числа. В С число в экспоненциальной форме записывают как мантиссу (десятичное число), потом E или е без пробела, затем показатель степени:   1. 23Е2 1.23 умножить на 10 во 2-й степени, т.е. 123 4 - 0886 4.08 умножить на 10 в 6-й степени, или 4 080 000 0 . 85е—4 0.85 умножить на 10 в минус 4-й степени, или 0.000085  День 3-й. Хранение данных в переменных и константах 65 
Константа, записанная без ДеСЯТИЧНОЙ ТОЧКИ, воспринимается компилятором как целое ЧИСЛО. Целочисленные константы можно записывать В ОДНОЙ ИЗ трех систем счисления.  I Константа, которая начинается с любой цифры, кроме 0, считается десятичной (т‚е. представленной в самой распространенной системе счисления с основанием 10), Деся- тичпые константы мотут содержать цифры от О до 9, а также стоящий впереди них знак “плюс” или “минус”. Если знака нет, константа считается положительной.  I Константа, которая начинается с цифры 0, считается восьмеричной (выраженной в системе с основанием 8), Восьмеричные константы мот содержать цифры от О до 7, а также предшествующий знак “плюс” или “минус”.  I Константа, которая начинается с ОХ или Ox, воспринимается как шестнадцатеричная (в системе с основанием 16), Шестнадцатеричные константы могут содержать цифры от 0 до 9, буквы от А до F и стоящий впереди знак “плюс” или “минус”.   ~ Работа с шестнадцатеричными константами рассматривается более под— (lllllmm робно в Приложении 8.     Символические константы  Символическая константа —— это константа, представленная в программе своим . именем (символическим обозначением). Как и литеральные, символические кон— станты не могут изменяться. Когда в программе необходимо использовать такую константу, на нее ссылаются по имени, как и на любую переменную. Фактическое значение символиче- ской константы необходимо ввести только од н раз, при ее объявлении. Символические константы имеют два существенных преимущества перед литералами. Эти преимущества демонстрирует следующий пример. Предположим, нужно написать про- грамму, выполняющую различные геометрические вычисления. Для этих целей в программе должно часто применяться число 1t (3.14), Из геометрии известно, что это число равно отно- шению длины окружности к ее диаметру. Например, вычисление длины окружности и пло- щади круга заданного радиуса выполняется следующими операторами: circumference = 3.14 * (2 * radius); area = 3.14 * (radius)*(radius); Звездочка (*) обозначает операцию умножения в С, которая рассматривается на занятии 4. Таким образом, первый из операторов означает “Удвоить значение переменной radius, a за- тем умножить полученное на 3.14. Присвоить результат переменной circumference”. Если же объявить символическую константу с именем PI и значением 3 . 14, можно записать: circumference = PI * (2 * radius); area = PI * (radius)*(radius); B результате получается более понятный код. Не нужно ломать голову над смыслом цифр 3.14 —— сразу видно, что используется константа PI. Второе преимущество символических констант становится очевидным, когда нужно из- менить значение константы. Продолжим предыдущий пример и предположим, что для по- вышения точности вычислений необходимо использовать з\нрограмме значение PI с боль- шим количеством значащих цифр: 3.14159 вместо 3.14. Если бы использовались литераль- ные константы, а не PI, пришлось бы перстряхнуть весь исходный код, заменяя в нем число  66 Неделя 1. Основные вопросы 
3.14 Ha 3 . 14159. А вот при использовании символической константы достаточно внести ис- правление только в объявление константы. Весь остальной код трогать не нужно.  Объявление символических констант  В языке С есть два способа объявления символических констант: директива #define и ключевое слово const. Директива #define используется следующим образом:  #define ИМЯКОНСТ литерал  Эта директива создает символическую константу с именем ИМЯКОНСТ и значением лите— рал, где литерал представляет собой литеральную константу (см. выше). РШЯКОНСТ должно следовать тем же правилам именования, которым подчиняются и имена переменных. Приня- то писать имена символических констант всеми прописными буквами —— так их легче отли- чить от имен переменных, которые обычно пишутся строчными буквами. В предыдущем примере директива #define для объявления константы PI выглядела бы следующим образом: {define PI 3.14159  Обратите внимание, что строки с директивами #define не заканчиваются точкой с запя- той (;). Директивы можно ставить в любом месте исходного кода, но объявленные ими кон- станты будут действительны только в той части программы, которая идет после #define. Ча- ще всего программисты собирают все директивы #define вместе и располагают их где-то в начале файла исходного кода перед функцией main( ).  Принцип работы директивы #define  Директива #define ——-— это, в сущности, указание компилятору “заменить ИМЯКОНСТ на ли— ферал везде в исходном коде”. Эффект этого указания точно такой же, как если бы самостоя- ‚тельно пройтись по всему тексту программы и произвести эту амену вручную. Следует обратить внимание, что директива #define не заменяет те вхождения имени константы в \‘тексте, которые являются частями других имен, стоят внутри двойных кавычек или в ком- ментариях. Например, в следующем примере буквосочетание PI He будет заменено во вто- Трой и третьей строках: . {define PI 3.14159 / 7* Объявлена константа PI. */ E .idefine PIPETTE 100 \   ~ Директива #define—-—— 3T0 одна из директив препроцессора С. Более под- @:“! робно работа лрепроцессора рассматривается на занятии 21.     Объявление констант с помощью ключевого слова const  Второй способ задания символических констант включает в себя применение ключевого слова const. Это модификатор, который можно применять к объявлению любой переменной. Если переменная объявлена как const, ee значение нельзя изменять в ходе работы програм- мы. Значение инициализируется в момент объявления и дальше уже не может изменяться. Вот несколько примеров: `сопзі'. int count = 100; const float pi = 3.14159; const long debt = 12000000, float tax_rate = 0.21;  День 3-й. Хранение данных в переменных и константах 67 
Объявление const действует на все переменные в строке, в которой оно расположено. Так, в последней строке примера объявлены символические константы debt и tax_rate. Возьмите на заметку, что объявлены также и их типы: debt имеет тип long, a tax_rate —— тип float. Если попытаться изменить значение переменной, объявленной как const, no ходу про- граммы, компилятор выдаст сообщение об ошибке. Например, следующий код содержит ошибку: const int count = 100; count = 200; /* He пройдет компиляцию! Нельзя присваивать константе */ /* новое значение. */  Каковы же практические отличия между символическими константами, созданными с по- мощью директивы препроцессора #define, и теми, которые объявлены путем добавления ключевого слова const? Различия имеют отношение к указателям и области действия пере- менных. Как указатели, так и область действия переменных относятся к важнейшим аспектам программирования на С и изучаются на занятиях 9 и 12. А сейчас рассмотрим программу, которая демонстрирует объявления переменных, а таюке использование литеральных и символических констант. Программа из листинга 3.2 приглашает пользователя ввести свой вес в фунтах (I фунт = 454 грамма) и год рождения. Затем она вы- числяст и отображает вес B граммах и возраст в 2010 году. Введите, скомпилируйте и запустите эту программу, выполнив для этой цели процедуры, изученные в ходе первогозанятия.  _;—  ~ Современные программисты на С в массе своей пользуются оператором (Ipmm const, a He директивой #define, для объявления символических констант      Листинг 3.2. const . с —— демонстрация применения переменных и констант   /* Переменные и константы в действии */ #include <stdio.h>  /* Объявление константы для перевода фунтов в граммы */ #define GRAMS_PER_POUND 454  /* Объявление константы года */ const int TARGET_YEAR = 2010;  ...: ОФФЧФШ-ЪШМН  /* Объявление переменных */ 11: long weight_in_grams, weight_in_pounds; 12: int year_of_birth, age_in_2010;  13: 14: int main( void ) 15: { 16: /* Ввод данных пользователем */ 17: 18: printf("Enter your weight in pounds: "); 19: scanf("%d", &weight_in_pounds);\\\\ 20: printf("Enter your year of birth: "); 21: scanf("%d", &year_of_birth); 22: 23: /* Вычисление результатов */ 24:  68 Неделя 1. Основные вопросы 
25: ’26: 27: 28: 29: 30: 31: 32: 33: 34:   weight__in_grams = weight__in__pounds * GRAMS_PER_POUND; age_in_2010 = TARGET__YEAR - year_of__birth;  /* Вывод результатов на экран */  printf("\nYour weight in grams = %ld", weight_in_grams); printf(“\nIn 2010 you will be %d years old\n", age_in~2010);  return 0;  ' Enter your weight in pounds: 175 Pe3gnhmamr Enter your year of birth: 1965  Your weight in grams = 79450 In 2010 you will be 45 years old  № В этой программе объявляются две символические константы двух разновидно-  стей —— в строках 5 и 8. Константа в строке 5 используется для того, чтобы сде-  лать значение 454 понятнее. После введения имени GRAMS_PER_POUND строка 25 стала проще для восприятия. В строках 11 и 12 объявляются переменные, используемые в программе. 06— ратите внимание на описательные имена переменных, например, weight_in__grams (что озна- "чает “вес в граммах”). Благодаря этому сразу можно сказать, для чего предназначена данная переменная. В строках 18 и 20 на экран выводится приглашение ввести данные. Функция z’printf() подробно изучается далее. Чтобы пользователь мог ответить на приглашение, ис- пользуется еще одна библиотечная функция —— scan\fT), которая тоже изучается позже. Функция scanf( ) считывает информацию, введенную с клавиатуры и отображенную на экра— не. Пока вам придется поверить на слово, что она работает именно так, как написано в про- грамме. В строках 25 и 26 вычисляется вес пользователя в граммах и его/ее возраст в 2010 г. Эти и другие операторы подробно изучаются на следующем занятии. Наконец, последними операциями программы является вывод результатов на экран в строках 30 и 31.    Рекомендуется / He рекомендуется     *“ “303%“ “ИТР…‘ЁЗЕЮЗ "WPM’MMM :?” MM M? W   „Используйте константы, чтобы …, облег-ъ LHe пытаитес “Ёприсвои'гв константе зна-  Meme после ее иниЦиализации › 2  папин-ч.0; WV 4 mm-  Резюме  На ЭТОМ занятии были рассмотрены числовые переменные, которые ИСПОЛЬЗУЮТСЯ В С ДЛЯ  хранения соответствующих данных в ходе выполнения программы. Имеется два обширных класса числовых переменных: целые и вещественные (с плавающей точкой). Каждый класс включает в себя несколько стандартных типов переменных. Какие типы переменных исполь— зовать для конкретной цели —— int, long, float или double —— зависит от природы данных, которые должны храниться в этих переменны . Прежде чем пользоваться переменными в программе на С, их необходимо объявить. Об явление переменной указывает компилятору имя и тип этой переменной.  Также были изучены два типа констант в языке С: литеральные и символические. В отли—  чие ОТ переменных, значения констант не могут изменяться В процессе выполнения програм—  День З-й. Хранение данных в переменных и константах 69 
мы. Значения литералов просто вписываются в исходный код там, где они нужны. Символи— ческим константам присваиваются имена, которые используются там, где нужны их значения. Символические константы можно объявлять с помощью директивы #define или ключевого слова const.  Вопросы и ответы  Переменные long int могут содержать очень большие числа, так почему бы не применять их всегда вместо переменных int? Переменные long int занимают больше места B оперативной памяти, чем просто int. B небольших программах это не создает особых проблем. А вот в очень больших программах приходится использовать память с максимальной эффективностью.  Что произойдет, если присвоить число с дробной частью целочисленной перемен- ной? ` Вполне допускается присваивать десятичные вещественные (дробные) числа переменным типа int. Если при этом переменная объявлена как const, то компилятор скорее всего выдаст предупреждение, У присваиваемого значения будет отброшена дробная часть и останется только целая. Например, если целочисленной переменной рі присвоить значение 3 .14, pi 6y- дет содержать значение 3, а . 14 будет отброшено.  Что произойдет, если присвоить переменной слишком большое значение, которое выходит за ее допустимый диапазон? Многие компиляторы допускают это и не сообщают об ошибке. Число урезается с исполь- зованием максимально допустимой величины, а потому заносится в переменную не с тем значением, которое планировалось. Например, если присвоить значение 32768 двухбайтной переменной со знаком типа short, эта переменная на самом деле будет содержать число — 32767. Если присвоить ей значение 65535, B переменной окажется число -1. Обычно B пере- менную помещается число, из которого вычитается максимально допустимое значение.  Что произойдет, если присвоить переменной без знака (unsigned) отрицательное значение? Как сказано в ответе на предыдущий вопрос, компилятор может и не сигнализировать об ошибке в этом случае, Компилятор выполнит такое же усечение, как если бы присваивалось слишком большое значение. Например, если присвоить —1 переменной типа unsigned int длиной два байта, компилятор поместит B нее максимально возможное значение (65535).  Каковы практические отличия между символическими константами, созданными с помощью директивы #define, и теми, которые объявлены с помощью ключевого слова const? Различия относятся к указателям и области действия переменных. Указатели и область действия переменных —-— это важнейшие аспекты программирования на С, которые изучаются на занятиях 9 и 12.  Коллоквиум  Вам предлагаются контрольные вопросы для повторения и усвоения изложенного мате- риала, а также упражнения для приобретения практического опыта B программировании.  70 Неделя 1 . Основные вопросы 
Контрольные вопросы  I. B чем разница между целочисленной и вещественной переменной (переменной с плаваю- щей точкой)?  2. Укажите две причины, зачем может понадобиться использовать вещественную перемен— ную двойной точности (типа double) вместо переменной одинарной точности (типа float)  3. Какие пять принципов всегда соблюдаются компиляторами при распределении места в памяти для числовых переменных?  4. Назовите два преимущества СИМВОЛИЧеСКИХ констант перед литеральными.  5. Опишите два способа объявления символической константы под именем MAXIMUM co зна- чением 100.  9‘  Какие символы разрешено использовать в именах переменных в языке С?  Ч .  Каких принципов следует придерживаться при назначении имен переменных и констант?  9::  B чем разница между СИМВОЛИЧеСКИМИ И литеральными KOHCTaHTaMH?  Р  Какое наименьшее значение может храниться в переменной типа int?  Упражнения  l. Какие типы переменных лучше всего использовать для хранения следующих значений: а) возраст человека в текущем году;  \  6) вес человека в фунтах; 8) радиус круга; г) годовой доход; д) стоимость предмета;  / / е) максимально возможная оценка по предмету (всегда 100);  ж) температура; \ f 3) размер личного денежного баланса; и) расстояние до звезды в километрах. 2 ` Придумайте подходящие имена для переменных из упражнения 1. 3. Напишите объявления переменных из упражнения 2.  4. Какие из следующих имен переменных допустимы для использования, а какие нет?  а) 123variable е) gross-cost 6) x ж) RADIUS B) total_score 3) Radius r) Weight_in_#s и) radius \\ д) one K) this_i s_a;variable_to_hold_the_width_of_a_box  День 3-й. Хранение данных в переменных и константах 71 
    Элементы программы на C: операторы, выражения и операции  Программы на языке С состоят из операторов, а операторы очень часто состоят из выра- жений и операций. Чтобы грамотно писать программы, необходимо разбираться в этих вы- ражениях, операциях и операторах. На этом занятии будут рассмотрены такие вопросы. I Что такое оператор I Что такое выражение  I Использование арифметических и логических операций, а также операций отношения в С  I Приоритет операций I Оператор if  Операторы  HflBbii ШВПМШ! Оператор _ это законченная инстрУкция, которая приказывает компьютеру вы- полнить ТО ИЛИ иное действие. В программах на С операторы обычно пишутся  по одному в строке, хотя некоторые могут занимать и несколько строк. Операторы С всегда заканчиваются точкой с запятой (за исключением директив препроцессора— таких как #define и #include — которые изучаются на занятии 21). Некоторые из операторов С вам уже встречались. Например, ›‹ = 2 + 3;  представляет собой оператор присваивания. Он приказывает компьютеру сложить числа 2 и 3, а затем присвоить результат переменной х. По мере изложения в книге будут вводиться все новые типы операторов С. 
E0133 свободного пространства в операторах  Определение “свободное пространство” относится к символам пробела, гори- зонтальной и вертикальной табуляции, а также пустым строкам в исходном коде. Компилятор С не обращает внимания на свободное пространство. Когда компилятор считы- вает оператор из исходного кода, он распознает символы, из которых тот состоит, и ищет за- вершающую точку с запятой, игнорируя при этом все промежуточные символы. 'Гаким обра- зом, оператор =2+3; эквивалентен оператору x = 2 + 3; атакже следующему оператору: x = 2 ""   №№ термин   3' ; Это позволяет очень гибко и разнообразно оформлять исходный текст программы. Прав- да, не.рекомендуется использовать такой формат, как в последнем примере. По стандарту операторы нужно располагать по одному на строке, окружая пробелами переменные и знаки операций. Если вы будете следовать принятым в этой книге правилам оформления программ, то разовьете хороший стиль программирования. По мере накопления опыта ваши предпочте- ішы в оформлении текстов программ могут измениться. Это нормально. Главное —— позабо- титься об удобочитаемости исходного кода. … Правило игнорирования свобоцного пространства в языке С имеет одно исключение. В літеральных константах пробелы не игнорируются —- они считаются частью строки. Стро— 1ёаь— это набор символов. Литеральные строковые константы представляют собой строки, Заключенные в кавычки и обрабатываемые компилятором посимвольно— причем пробел считается символом, а не игнорируется. Вот пример литеральной строки:  ‘n‘ow now brown cow"  /  А эта строкаотличается от предыдуъи'ей: > “Бои now brown cow " I  '. ._ н ., 3 , Разница между строками заключается в количестве пробелов. Итак, компилятор С учиты- вает количество пробелов в литерапьных строках. Следующий фрагмент кода вполне допускается компилятором, хотя и представляет очень Пасхой стиль программирования: вып… “Ве11о‚иогід!" A BOT этот фрагмент содержит ошибку: printf("Hello, worldl"); Чтобы разорвать строку внутри литеральной кон танты, необходимо вставить символ об- ратной косой черты (\) непосредственно перед раз ывом строки. Правильно будет записать вот так:  printf("Hello,\ worldl");  день 4-й. Элементы программы на С: операторы, выражения и операции 73 
Пустой оператор  Нины; термин  Если поставить в отдельной строке только точку с запятой и больше ничего, по` лучится пустой оператор. Пустой оператор не выполняет никаких действий  Это вполне допустимо в С. Позже вы узнаете, для чего бывают нужны пустые операторы.  Составные операторы   ‚№№ термин  printf("Hello,") printf("world!“)  о I о I  &  Составной оператор, или блок, представляет собой группу из двух или более операторов С, заключенных в фигурные скобки. Вот пример блока:  В языке С блоки могут применяться везде, где может стоять отдельный оператор. В этой книге приведено множество примеров использования блоков. Обратите внимание, что фи- гурные скобки можно ставить птразному. Например, такая запись эквивалентна предыду-  {ней: {printf("Hello,"); printf(“world!");}  Рекомендуется располагать скобки в отдельных строках, чтобы начало и конец блока бы- ли хорошо видны. Также в этом случае будет легче обнаружить отсутствие открывающей или  закрывающей скобки.   РекомендУется  ;;9облюдайте “Esau: собственный с_т№ь 361рименения пробелов в операторах. ,3, _ 3,. №асполагайте скобки; блоков в отделы; 1:14;le строках—+1] так легче читать H6x0g3~ ный код., “:5: г >; те у:; Выравниваите скобки друг; относитель- ; {то друга чтобы легко/было ‚найти начало ; “:11 Конец 6110Ka …… …; ` 33,: %;  о «\ ... _… ;]; 5:333 х*й (,3: gig?“ 34): 3  3:33... ..… \ _…- L «аъ—…да ‹.;    9:331   \ ...іе_№ас.…!' „ма...…Ё $.}. ..1  Выражения  Простые  СЛОЖНОСТИ.  выражен ия  Не реком'ендУется : (“;Не растягивайте один; оператор Ha не- ;;скопъько строк; без особой Ha “зто необхо— g димости. >Вмещаите оператор в :,[одну ; строку Egan-.116 когда это возможно 3:7? > >» Ё He забывайте использовать K0691o черту: ; для переноса строковых констант Ha нод; 113?"? “'”“ _ гг:; „>: ‘ 5 ‘  I "v 11%-    B языке С выражением называется любое сочетание элементов, которое в итоге образует числовое значение. Выражения в С могут быть самой различной  Простейшее выражение состоит из ОДНОГО элемента: переменной числового типа, лите- ральной константы или символической константы. Вот примеры четырех выражений.  74  Неделя 1. Основные вопросы 
, Выражение Описание   Т:}? Символическая константа, определенная где-то в программе {320 Литеральная константа Ётава Переменная -1.25 Литерапьная константа  Литеральная константа (литера/1) равна своему явным образом выписанному . значению. Символическая константа равна значению, присвоенному ей при ее объявлении директивой #define. Переменная равна своему текущему значению, присвоенно- му ей в ходе выполнения программы.   Сложные выражения  НПВЫП NEWER Сложное выражение состоит из более простых выражений, соединенных знака- ми операций. Например:  2+8  Это выражение состоит из двух простых выражений 2 и 8, соединенных знаком операции сложения +. Выражение 2 + 8, очевидно, при вычислении дает 10. Можно записать и очень сложное выражение С:  1.25 / 8 + 5 * rate + rate * rate / cost Если выражение содержит несколько знаков операций, то порядок его вычКсления зави- сит от приоритета операций. Приоритеты операций и другие тонкости использования выра— „жений рассматриваются далее на этом занятии. Выражения в С могут быть еще интереснее. Рассмотрим такой оператор присваивания: x = a -+ 10; B этом операторе вычисляется значение выражения а + 10 и присваивается перемен- ной ›‹ Кроме того, весь оператор присваивания x = a + 10 также является выражением,  которое равно 3Haqumo переменной слева ОТ знака равенства. ЭТОТ ПРИНЦИП ПРОИЛЛЮСТ-  рирован на рис 4 1 /,  Вычисляется значение №  переменная : m050e_8blpaxeflne; W  Равно этому же значению Рис. 4.1. Оператор присваивания — это так-  же выражение  Поэтому можно записать оператор, который присваивает значение выражения а + 10 сразу двум переменным ›: и у: \ у = х = а + 10;  День 4-й. Элементы программы на С: операторы, выражения и операции 75 
Допускается и такая запись: х=6+(у=4+5); В результате выполнения этого оператора переменная у приобретает значение 9, а x -— значение 15. Обратите внимание на использование круглых скобок, необходимых для того,  чтобы оператор прошел компиляцию без ошибок. Правила применения круглых скобок также рассматриваются на этом занятии.   ~ За исключением нескольких случаев, оговоренных в книге, не рекоменду- (”Milli ется вставлять оператор присваивания в другие выражения.     Операции  НПВЬШЪДШЕДМИН Знак операции — это символ, который приказывает компилятору С выполнить * определенные действия (операции) над двумя или более операндами. Операнд -—  это объект в программе, над которым совершаются операции. В языке С все операнды явля— ются выражениями. Операции С делятся на несколько категорий.  I Операция присваивания. I Арифметические операции. I Операции отношения. I  Логические операции.  Операции присваивания  Операция присваивания обозначается знаком равенства (=). Ее смысл в программировании отличается от привычного нам математического употребления. Предположим, в программе на С написано: /  х=у  Это означает совсем не/‘х равен у”, а “присвоить значение у переменной и”. В операторе присваивания языка С правая часть может быть любым выражением, а левая часть должна быть переменной. Таким образом, синтаксис этого оператора следующий:  переменная : выражение;  При выполнении ЭТОГО оператора вычисляется значение выражения и присваивается пере- манной.  Арифметические операции  Арифметические операции в С имеют тот же характер, что и в математике, например, сложение или вычитание. В С есть две одноместных и пять двуместных операций.  Одноместные арифметические операции  Одноместные операции называются так потому, что их аргументом является один опе— раНД. В С имеются две одноместные операции, перечисленные в табл. 4.1.  76 Неделя 1. Основные вопросы 
таблица 4. 1 . Одноместные арифметические операции С    hem-mm: Знак Эффект Примеры Чикремент ++ Увеличивает операнд на единицу ++х, X++ Hgfpeuewr -- Уменьшает операнд на единицу -—х‚ x--   „__—___  Операции инкремента и декремента можно совершать только над переменными, но не над константами. Выполняемая операция заключается в прибавлении к операнду или отнятии от него единицы. Другими словами, операторы ++х; "Y; эквивалентны операторам х x + 1; Y = Y - 1: Следует заметить из табл. 4.1, что оба знака одноместных операций могут стоять как перед  операндом (префиксная форма), так и после него (постфиксная форма). Эти две формы не эквивалентны. Они отличаются способом выполнения операций инкремента и декремента.  I B префиксной форме операции инкремента и декремента выполняются над операндом перед тем, как его значение будет использовано в другом включающем его выражении.  I B постфиксной форме операции инкремента и декремента выполняются над операндом уже после того, как его значение было использовано во включающем его выражении. Для ясности приведем пример. Рассмотрим два оператора: x = 10; y = x++; После выполнения этих операторов х приобретает значение 11, а у —— значение 10. Сперва ;начение x было присвоено переменной y, и только затем увеличено на единицу. В противо- /положность этому, выполнение следующих операторов сделает х и у равными 11, потому что вначале значение х увеличивается, а потом присваивается переменной у. x = 10; \ /  y = ++x;  \  Помните, что символ “=” обозначает операцию присваивания, а не утверждение о равен- `стве двух величин. Представьте себе эту операцию как “ксерокопирование” некоего значения. Оператор у = ›‹ выполняет копирование величины х в у, и все последующие изменения х по- сле копирования никак не повлияют на у. Программа, приведенная в листинге 4.1, иллюстрирует разницу между префиксной и постфиксной формой одноместных операций.  Листинг 4. 1 . unary . с —— демонстрация префиксной и постфиксной записи   /* Префиксная и постфиксная запись одноместных операций */ #include <stdio.h> int a, b;  int main(void)  {  фЧФШдШМО—д .3 II n on n n n n  День 4-й. Элементы программы на С: операторы, выражения и операции 77 
9: /* Сделать а и Ь равными 5 */  10: 11: а = Ь = 5; 12: 13: /* Вывести, каждый раз уменьшая на единицу. */ 14: /* Префиксная форма для Ь, постфиксная для а */ 15: 16: printf("\nPost Pre"); 17: printf("\n%d %d", a--, --Ь); 18: printf("\n%d %d", a--, --b); 19: printf("\n%d %d", a--, -—Ь); 20: printf("\n%d %d", a--, --Ь); 21: printf(“\n%d %d\n", a--, --Ь); 22: 23: return О; 24: }   Post Pre Результат 4  ышшь… онмш  В строке 5 этой программы объявляются две переменные: а и Ь. В строке 11 их значения становятся равными числу 5. Вместе с выполнением каждого оператора printf( ) (строки 17—21) переменные а и Ь уменьшаются на единицу. Значение a уменьшается после того, как оно выволится на экран, а значение Ь —— до того.  диализ ,   На занятии 3 была рассмотрена еще одна одноместная операция—— ("Ритм sizeof. Может показаться логичным. что операция всегда должна обозна- чаться специальным значком. но это не всегда так. Ключевое слово sizeof тоже следует считать знаком операции.     Двуместные арифметические операции  Двуместные операции С выполняются над двумя аргументами. Список таких операций, совпадающий со стандартным набором операций микрокалькулятора, приведен в табл. 4.2. Первые чегыре операции из табл. 4.2 обшеизвестны, как и правила их применения. А вот пятая операция, деление по модулю, может оказаться новинкой. Деление по молулю дает ос- таток от целочисленного деления первого опера а на второй. Например, ll разделить по моду- лю на 4 равно 3 (т.е. четверка два раза помещаёя1 в ll плюс остаток 3). BOT еще примеры: 100 разделить по модулю на 9 равно 1 10 разделить по модулю на 5 равно 0 40 разделить по модулю на 6 равно 4  78 Неделя 1 . Основные вопросы 
таблица 4.2. двуместные арифметические операции в С    Baht-ham Знак Эффект Пример ложение + Складывает два операнда ›‹ + у ычитанйе - Вычитает второй операнд из первого x - у ноженйе * Перемножает два операнда x * у пение / Делйт первый операнд на второй x / у Д ление по мвдулю % Образует остаток от деления первого операнда на х % у L второй нацело   _—`Г  _Пример из листинга 4.2 показывает, как можно использовать операцию взятия остатка для преобразования большого числа, обозначающего промежуток времени в секундах, в часы,  минуты И секунды.  Листинг 4.2. seconds . с — демонстрация операции деления по модулю   /* Иллюстрация операции деления по модулю. */ /* Вводит количество секунд, преобразует */ /* в часы, минуты и секунды. */ #include <stdio.h>  /* Объявление констант */  ФЧФШ-ЬШЮН .. an an an II at на...  9: #define SECS_PER_MIN 60 10: #define SECS_PER_HOUR 3600  12: unsigned seconds, minutes, hours, secs_left, mins_left;  ”14: int main( void )  15: { 16: /* Ввод количества секунд */ 17: 18: prihtf("Enter number of seconds (< 65000): И) 19: scanf("%d", &seconds); ‚ 20: 21: hours = seconds / SECS_PER_HOUR; 22: minutes = seconds / SECS_PER_MIN; 23: mins_left = minutes % SECS_PER_MIN; 24: secs_left = seconds % SECS_PER_MIN; 25: 26: printf("%u seconds is equal to ", seconds); 27: printf("%u h, %u m, and %u s\n", hours, mins_left, secs_left); 28: 29: return 0; 30: }   Enter number of seconds (< 65000): 60 60 seconds is equal to 0 h, 1 m, and 0 s Enter number of seconds (< 65000): 10000 10000 seconds is equal to 2 h, 46 m, and 40 s   День 4-й. Элементы программы на С: операторы, выражения и операции  79 
№ Программа seconds .с оформлена так же, как и все предыдущие. Строки 1—3 со- держат комметарий, поясняющий назначение программы. Строка4 оставлена пустой для удобства чтения программы. Так же, как любое свободное пространство в опера— торах и выражениях, пустые строки игнорируются компилятором. В строке 5 подключается заголовочный файл, необходимый для работы программы. В строках 9—-1О объявляются две константы SECS_PER_MIN и SECS_PER_HOUR, благодаря которым операторы программы стано— вится легче читать. В строке 12 объявляются все используемые переменные. Некоторые про— граммисты предпочитают объявлять каждую переменную в отдельной строке. Как и многое другое в С, это дело вкуса и личного стиля, поскольку оба способа правильны. Строка 14 содержит заголовок функции main( ), основного тела программы. Чтобы пре образовать секунды в минуты и часы, программе вначале нужно заполучить значение в се— кундах. Для этого в строке 18 вызывается функция printf( ), которая отображает приглаше- ние на экране. Далее в строке 19 вызов функции scanf( ) позволяет получить введенное поль— зователем значение. Функция scanf( ) помещает значение в секундах в переменную seconds для его дальнейшего преобразования. Функции printf( ) `и scanf() рассматриваются более подробно на занятии 7, посвященном средствам ввода/вывода в С. Строка 21 содержит вы— ражение для определения количества часов путем деления количества секунд на константу SECS_PER_HOUR. Поскольку hours —— целочисленная переменная, остаток от деления просто игнорируется. В строке 22 тем же способом вычисляется количество минут в общем количе- стве секунд. Количество минут, вычисленное в строке 22, содержит также их количество в целом числе часов, поэтому в строке 23 снова выполняется деление по модулю, чтобы отбро— сить часы и оставить чистый остаток в минутах. В строке 24 выполняется аналогичный рас- чет количества оставшихся секунд. Строки 26 и 27 содержат уже привычные операторы, ко— торые отображают вычисленные значения на экране. В строке 29 перед завершением про- граммы в операционную систему возвращается значение 0.  Приоритет операций и скобки  Если выражение содержит несколько операций, то возникает вопрос, в каком порядке их выполнять. Важность этого вопроса можно проиллюстрировать следующим оператором при— сваивания: х = 4 + 5 * 3;  Если вначале выполнить сложение, то получается следующий оператор, который в итоге присваивает переменной x значение 27:  х = 9 * 3; Если же вначале выполнить умножение, то переменная x приобретет уже значение 19 в результате выполнения следующего оператора: / х = 4 + 15; / совершенно очевидно, ЧТО НУЖНЫ какие-то правила, определяющие порядок выполнения операций. Этот порядок, именуемый приоритетом операций, строго регламентирован в язы— ке С. У каждой операции есть определенный уровень приоритета. При вычислении выраже—  ний первыми выполняются операции с более высоким приоритетом. В табл. 4.3 приведен список приоритетов арифметических операций в С.  80 Неделя 1. Основные вопросы 
‚;Ёблица 4.3. Приоритет арифметических операций в С    \Ъерации Относительный приоритет + —- 1 * % 2 + 3  3 табл. 4.3 видно, что в любом выражении С операции выполняются в следующем по-  Одноместный илкремент и декремент. I Умножение, деление, деление по модулю. I Сложение и вычитание.  Если выражение содержит несколько операций с одинаковым приоритетом, то они вы— полняются слева направо в порядке их появления в выражении. Например, B следующем вы- ражении операции % и * имеют одинаковый приоритет, но при этом % стоит первым, поэтому и выполняется раньше второго: „12 % 5 * 2  Это выражение равняется 4 (12 % S равно 2, затем 2 умножить на 2 равно 4). Возвращаясь к предыдущему примеру, заметим, что оператор ›‹ = 4 + S * 3; присваив(в- ет переменной x значение 19, поскольку умножение выполняется раньше сложения. А что если из—за приоритета операций выражение вычисляется не в том порядке, который нужен? Пусть B предыдущем примере необходимо все—таки сначала сложить 4 и 5, а затем ,УМножить сумму на 3. B этом случае в С используются круглые скобки для изменения поряд— `‚ка операций. Вложелное выражение в круглых скобках вычисляется первым независимо от "ііриоритета операций, т.е. можно записать: ! = (4 + 5) * 3; /BBIpa)Keune 4 + 5 в скобках вычисляется первым, так что переменной x присваивается значение 27. 515, В выражениях можно использовать несколько пар скобок, в/том числе вложенных. При {gunman вложенных скобок вычисления производятся. начиная с внутренних, глубже всего е№положенных, пб направлению наружу. Рассмотрим, наприМер, следующее сложное выра- 1f» A ние: ЁЁ: 25 — (2 * (10+ (в / 2);); 5*:.<; ‘ Это выражение вычисляется B таком порядке. l. Глубже всех вложенное выражение 8 / 2 вычисляется первым и дает значение 4: 25 - (2 * (10 + 4)) 2. Двигаясь наружу` вычисляем следующее выражение, 10 + 4, которое дает значение 14: 25 — (2 * 14) _3. Последнее, самое внешнее, выражение в скобках 2 * 14 дает значение 28: 25 - 28  4. Наконец, вычисляется последнее выражение 25 — 28 и получается -3, которое и присваи— вается переменной х:  х=—З  {день 4—й. Элементы программы на С: операторы, выражения и операции 81 
Иногда полезно употреблять скобки для большей ясности порядка выполнения операций, даже если они и не нужны для изменения приоритета. Открывающая скобка всегда должна иметь пару —— закрывающую скобку, в противном случае компилятор выдаст сообщение o6 ошибке.  Порядок вычисления вложенных выражений  Как уже упоминалось в предыдущем разделе, если выражение языка С содержит несколь— ко операций с одинаковым приоритетом, то они выполняются по порядку слева направо. Вот пример: w * x / у * z Вначале w умножается на х, затем результат делится на у, наконец, получившееся снова умножается на z. Если же по ходу выражения уровень приоритета то повышается, то понижается, уже нет никакой гарантии соблюдения порядка “слева направо”. Рассмотрим следующее выражение: w * х / y + z / у Умножение и деление имеют более высокий приоритет, поэтому выполняются первыми. Однако в С не оговаривается, должно ли выражение w * х / у вычисляться раньше или поз- же выражения 2 / у. Может возникнуть вопрос, зачем нужно это знать. Что ж, рассмотрим еще один пример: w * x / ++y + z / у  Если первым вычисляется левое выражение, то у инкрементируется перед вычислением правого. Если же первым вычисляется правое выражение, когда значение у еще не инкремен- тировано, то результат будет отличаться. Следует избегать неопределенных выражений тако- го рода в своих программах. В конце этого занятия, в разделе “Снова о приоритете операций”, рассматривается при— оритет выполнения всех имеющихся в С операций.    Рекомендуется Не рекомендуется  ;іНе жалейте скобок чтобы сделать no- ;гНе лерегружаитетыраженияі Нередк9 },рядок выполнения операций в выражен %}операцииі' „ становятся понятнее „После      іниях как можно более понятным., 1253; ЁЁё;^ ::‘разбиения выражения на ›два или болеё} 333. 3.31:4 -‘ т, , ‚ A :3 : ‚:…,5”: }} операторов. Особенно это справедливо „ 11,332, $$?}? , A ‚15,3 {при исполыовании одноместных опера- g " „в; „ ции (--)или (ism; „„ ,. ЁЁ? „„ *,; ‚,;  5  Мч.—Ь… .. "4w ш вид—Ам..— ..“—“.:.…дЪц , .“… Зцщ,›\_‚г .… ` .. ‚“ ‚и:—№.- `„„_,^< ‚_,. . . …»" Эдди—..в…“- ~J(:-i— “№; WWW “нам…—№ ”:th ма...... „г.№ №№. „№ мм.“.  Операции отношения  Операции отношения в С используются для сравнения выражений, т.е. чтобы задавать во— просы наподобие “34 больше 100?” или “у равно 0?”. Вычисление выражения, содержащего операцию отношения, дает логическое значение TRUE (1), что означает ИСТИНА, или FALSE (0), что означает ПОЖЬ. В табл. 4.4 перечислено шесть операций отношения языка С. А в табл. 4.5 даются некоторые примеры использования операций отношения. В этих примерах использу- ются литеральные константы, но все то же самое относится и к переменным.  /  82 Неделя 1. Основные вопросы 
 TRUE (ИСТИНА) — это то же самое, что "да". и считается эквивалентным зна- чению 1. FALSE (ПОЖЬ) обозначает то же. что и "нет“, и эквивалентно 0.        Знак Задаваемый вопрос Пример == Равен ли операнд 1 операнду 2? x == > Больше ли операнд 1. чем операнд 2? x > y Mei-lame < Меньше ли операнд 1, чем операнд 2? x < у Бопьше или равно >= Правда ли. что операнд 1 больше или равен операн- x >= y ду2? 'Мшьше или равно <= Правда ли. что операнд 1 меньше или равен операн- x <= y ду 2? ije равно != Правда ли. что операнд 1 не равен операнду 2? x != y   Таблица 4.5. Использование операций отношения    _ Выражение Как читается Результат 5 == 1 5 равно 1? О (FALSE) 5 > 1 5 больше. чем 1? 1 (TRUE) 5 != 1 5 не равно 1? 1 (TRUE) / „(5 + 10) == (3 * 5) (5+10) равно (3*5)? 1 (TRUE)    He рекомендуется  Не путайте операцию отношения == с оператором присваивания -. Это одна из самых распространенных білибок кото- рые встречаются в программах на С z  ` www _а 4W    т „Ё… } ..." ”интерпретацию {логических зна— 2  germ „тока ‚В язЁжеС B onepa‘- ‚утешения утверхёдение истина aK- e‘mfid ”Ё“ подьешувапенто O Bf“:  ! Ё Ё Ё f. : Ё any .3»11~"m~‘ WEB a“ №№   Ё E Ё E  / /  /  ператор if  I! ?? ‚ ;& ` '\_. Зн-   Операции отношения B основном используются для построения логических выражений B ператорах if и while. Эти операторы псдробно рассматриваются на занятии 6. А сейчас ЁЁнэучим синтаксис оператора if B общих чертах для того, чтобы знать, как с помощью опера- Ёций отношения строятся управляющие операторы. Что же такое управляющий оператор? Известно, что операторы B программе обычно вы- полняются в том же порядке, в каком они написаны в исходном к0де. А вот управляющий "оператор изменяет порядок выполнения инструкций в программе. Такой оператор может за- ставить определенные фрагменты программы выполняться несколько раз или не выполняться вообще B зависимости от обстоятельств. Оператор if как раз и представляет собой один из управляющих операторов С. Остальные управляющие операторы, такие как do и while, изу- чаются на занятии 6.  День 4-й. Элементы программы на С: операторы, выражения и операции 83 
В своей простейшей форме оператор іі вычисляет значение некоторого выражения и на- правляет ход программы в зависимости от результата этого вычисления. Вот синтаксис опе- ратора іі: іі (выражение)  {  опера тор;  Если выражение истинно, выполняется оператор. Если выражение ложно, оператор не вы— полняется. В любом случае управление затем передается операторам, следующим за іі. Итак, можно сказать, что выполнение оператора зависит от значения выражения. Обратите внима- ние, что строка іі (выражение) и строка оператор; образуют единый оператор іі, а не яв- ляются отдельными операторами. С помощью оператора іі можно управлять выполнением нескольких операторов, объеди- нив их в составной оператор, или блок. Как уже говорилось в ходе этого занятия, блок —— это группа из двух и более операторов, заключенных в фигурные скобки. Блок можно использо- вать везде, где допускается применение одного оператора. Таким образом, можно записать оператор іі вот так: іі (expression) { оператор]; оператор2; /* еще операторы */ операторп;   Рекомендуется Не рекомендуется EPacnonaralmse операторы é блоке (в том Не…перестараитесь &' уч '  «тн >51”  чИспЪ Ъ" операторе fikic отступом :oj' нат—Ё , ` u Ивать дни Гйн-„д   3M  нала строки „‚ чтобы обп’ётчить их “1’6"“th        - Ни в коем случае не ставьте точку с запятой после выражения в операто- < "Ш"" ре if. Этот оператор должен заканчиваться оператором или блоком. В следующем примере наличие точки с запятой приводит к тому, что опера- тор1 выполняется независимо от того, равно ли ›‹ двум или нет. Каждая строка считается независимым оператором, а не единым оператором if:  if( х == 2); /* лишняя точка с запятой! */ /  оператор1 ;    Компилятор обычно не замечает этой ошибки.   Можно заметить, что оператор іі чаще всего употребляется с выражениями отношения. Другими словами, его можно сформулировать так: “выполнить такие-то операторы, если та- кое-то условие справедливо”. Например: іі (х > у) Y = X;  84 Неделя 1. Основные вопросы 
“_,: Этот оператор присваивает переменной y значение ›‹ только в том случае, если x превос— хёдшёу по величине. Если это не так, присваивание не выполняется. В листинге 4.3 приведе- на программа, иллюстрирующая использование оператора if:  Листинг 4.3. List-.0403 . с — демонстрация работы оператора if   /* демонстрация использования оператора if */ #include <stdio.h> int х, y;  1: 2: 3: 4: 5: 6: 7: int main( void ) 8:  { 9: /* Ввести два числа для анализа */ 10: 11: printf(“\nInput an integer value for x: "); 12: scanf("%d", &x); 13: printf("\nInput an integer value for y: "); 14: scanf(“%d", &y); 15: 16: /* Сравнить значения и вывести результат */ \ 17: 18: if (x == y) 19: printf("x is equal to y\n"); 20: 21: if (x > y) 22: printf("x is greater than y\n"); 23: /24: if (x < y) 25: printf("x is smaller than y\n“); '26: 27: return 0; /  Input an integer value for x: 100 . Input an integer value for y: 10 r,; х is greater than y   Input an integer value for x: 10 Input an integer value for y: 100 х is smaller than y  Input an integer value for x: 10 Input an integer value for y: 10 х із equal to y   Программа List0403.c демонстрирует работу трех операторов if (строки 18— . 25). Многие операторы программы должны быть уже знакомы вам. В строке 5 “объявляются две переменные x и у, а строки ] 1—14 запрашивают у пользователя значения для  ;гдень 4-й. Элементы программы на С: опера торы, выражения и операции 85 
этих переменных. В строках 18—25 с помощью операторов if два значения сравниваются, чтобы определить, равны ли переменные x и у между собой или какая-либо из них больше другой. Так, в строке 18 оператор if проверяет, равны ли переменные между собой. Не пу- тайте: знак == в этой строке означает равенство, а не присваивание. Далее в строке 21 выпол- няется проверка, не превосходит ли значение x значение у, а в строке 24— наоборот, не больше ли у, чем x. Если вам показалось, что проверка организована неэффективно, то вы правы —— 3T0 так и есть. Попытаемся это исправить в следующей программе. А пока запусти- те программу, введите какие-нибудь значения для x и у и посмотрите результат.   Можно заметить, что операторы в блоке после if имеют отступ от начала “WWW“ строки. Это общепринятая практика для облегчения чтения программ.     Ключевое слово else  Оператор if может содержать необязательную часть, начинающуюся c ключевого слова else: if (выражение) оператор]; else оператор2; Если выражение истинно, выполняется оператор] . Если выражение ложно, управление по— лучает оператор2 после else, который и выполняется. Как оператор1, так и оператор2 могут быть составными операторами (блоками).  B листинге 4.4 приведена программа из листинга 4.3, персписанная с использованием ключевого слова else.  Листинг 4.4. List0404 — применение оператора if c ключевым словом else   /* демонстрация использования оператора if с ключевым словом eljwe */  #include <stdio.h>  2: 3: 4: 5: int x, y; 6: 7: int main( void ) 8:  { 9 /* Ввести два числа для анализа */ /‚ 10: ‚// 11: printf("\nInput an integer value for x: "); 12: scanf("%d", &х); 13: printf("\nInput an integer value for y: "); 14: scanf(“%d“, &у); 15: 16: /* Сравнить значения и вывести результат */ 17: 18: if (x == y) 19: printf("x is equal to y\n"); 20: else  86 Неделя 1. Основные вопросы 
‚521: if (x > y)  -g21am printf("x is greater than y\n"); 23: else 24: printf("x is smaller than y\n"); 25: 26: return 0; 27: } _““   ‘ Input an integer value for x: 99 ИЮШШШШ" Input an integer value for y: 8 х is greater than y  Input an integer value for x: 8 Input an integer value for y: 99 х is smaller than y  Input an integer value for x: 99 Input an integer value for y: 99 x is equal to y  Строки 18—24 несколько отличаются от аналогичных строк предыдущего лис- тинга. В строке 18 по-прежнему проверяется, равны ли между собой х и у. Если  это так, сообщение “x is equal to y” появляется на экране, как и в листинге 4.3 (List0403 .c). Ho в данной программе выполнение на этом заканчивается, и строки 20—24 по— просту не выполняются. Строка 21 выполняется, только если ›‹ не равно у —— точнее, если вы— ражение “х равно у” ложно. Итак, если х не равно у, то в строке 21 проверяется утверждение “x больше у”. Если оно верно, в строке 22 на экран выводится сообщение “х is greater than у”. В противном случае выполняется строка 24. / " В программе листинга 4.4 используется вложенный оператор if. Вложенность означает, что один или несколько операторов помещаются внутрь структуры такого же оператора С. В приведенной программе второй оператор if входит в состав блока else первого оператора if.  / /  Оператор if /  Первая форма  if ( выражение )  { }  следующий_оператор; Это оператор if в его простейшей форме. Если выражение истинно, выполняется оператор}. Если же выражение ложно, операторі игнорируется.   опера тор} ;  Вторая форма if( выражение )  {  оператор] ;  }  % день 4-й. Элементы программы на С: операторы, выражения и операции 87 
else  { оператор2 ; } и следующии_ опера тор; Это самая общая форма оператора if. Если выражение истинно, выполняется опера- тор], в противном случае выполняется операторг.  Третья форма  if( выражение] ) оператор]; else if( выражение.? ) оператор2; else операторЗ; следующии_оператор; Это вложенный оператор if. Если выражение] истинно, то выполняется оператор], а затем программа переходит на следующии_оператор. Если выражение] ложно, проверя— ется выражение2. Если выражение.? истинно (а выражение] ложно), то выполняется оператор2. Если ложны оба выражения, выполняется операторЗ. В любом случае вы— полняется только Один из трех операторов.  Пример1 if( salary > 450000 ) { tax =.30; } else { tax =.25; } Пример2 if( age < 18 )  printf("Minor"); else if( age < 65 ) printf("Adult");  else //  printf("Senior Citizen");  Вычисление логических выражений  Следует помнить, что логические выражения (т.е. выражения с операциями отношения) ничем не отличаются от других и при вычислении дают некоторое значение — FALSE (0) или TRUE (1). Хотя чаще всего логические выражения применяются в операторах if и других ус- ловных операторах, их можно использовать и как обычные числовые значения. Этот факт ил- люстрирует программа из листинга 4.5.  88 Неделя 1. Основные вопросы 
Листинг 4.5. List0405 .c — вычисление выражений с операциями отношения   1: /н демонстрация вычисления выражений с операциями отношения */   2: 3: #include <stdio.h> \\\ 4: 5: int a; 6: 7: int main( void ) 8: { 9: a = (5 == 5); /* Результат равен 1 */ 10: printf("\na = (5 == 5)\па = %d", a); 11: 12: a = (5 != 5); /* Результат равен 0 */ 13: printf("\na = (5 != 5)\па = %d", a); 14: 15: a = (12 == 12) + (5 != 1); /* Результат равен 1 + 1 */ 16: printf("\na = (12 == 12) + (5 != 1)\па = %d\n", a); 17: return 0; 18: } a = (5 == 5) а = (5 != 5) а = 0 a = (12 == 12) + (5 I: 1) a — 2  Ananua Вначале результат выполнения этой программы может запутать неопытного пользователя. IIOMHHWB, что самая распространенная ошибка, связанная с опера—  циями отношения, —- это использование одинарного знака равенства, т.е. оператора присваи- вания, вместо двойного. Следующее выражение равно 5 (помимо того, что оно также при— сваивает переменной x значение 5): x = 5  A BOT это выражение может оказаться равным 0 или 1 (в зависимости от того, равно ли x пяти) и при этом не изменяет значения x: X ::= Если записать по ошибке следующее: if (x = 5) printf("x is equal to 5"); то сообщение о том, что x равно 5, будет выводиться всегда, потому что выражение в опера— торе if всегда будет истинным (ненулевым) вне зависимости от того, какое значение находи- лось в переменной x ранее. Теперь вы должны уже начать понимать из текста программы в листинге 4.5, почему а принимает указанные выше значения. В строке 9 число 5 действительно равно 5, поэтому пе— ременной а присваивается значение TRUE (1). B строке 12 утверждение “5 не равно 5” ложно, вот поэтому а получает значение 0.  День 4-й. Элементы программы на С: операторы, выражения и операции 89 
Итак, повторим: операции отношения служат для построения логических выражений, в которых фактически задаются вопросы о соотношениях между определенными величинами. Вычисление выражения с операциями отношения дает численное значение, равное либо 1  (TRUE), либо о (FALSE).  Приоритет операций отношения  Как и арифметические операции, рассмотренные ранее на этом занятии, операции отно- шения тоже имеют разные уровни приоритета, которые определяют порядок их выполнения в выражениях. Аналогичным образом можно использовать и скобки для изменения этого по- рядка. Список всех операций С и их приоритетов можно найти далее в разделе “Снова 0 при- оритете операций”. Во-первых, все операции отношения имеют более низкий приоритет, чем арифметические операции. Для примера запишем выражение, в котором 2 прибавляется к x a затем все это сравнивается с у: if (x + 2 > у) Эта запись эквивалентна приведенной ниже -— последняя, в свою очередь, является хоро- шим примером применения скобок для лучшей читаемости кода. if ( (X + 2) > у) Хотя компилятор С и не требует наличия скобок в выражении (х + 2), все же при их на- личии становится совершенно ясно, что сумма )( + 2 сравнивается с у.  Среди самих операций отношения имеется два уровня приоритетности, которые приведе— ны в табл. 4.6.  Таблица 4.6. Приоритет операций отношения в С    Операции Относительный приоритет ‹ <= › >= 1 != == 2   Таким образом, если записать ›‹ == у > z то это будет эквивалентно ){ == ( у > 2 ) потому что вначале вычисляется выражение у > 2, дающее значение 0 или 1. Затем опреде- ляется, равна ли переменная x единице или нулю, полученным в результате первой операции. Подобные конструкции встречаются очень редко, но и о них следует знать.     Рекомендуется Не рекомендуется ;Не помещайте операторы„ю рисеаивания Q;  Ёусповньпе выражения “оператораЧЁЁЧЁдругие * люди, читающие или дорабатывающие ваш 1 код, могут принять такое выраікение за 911.1146-  Ё з ку и изменить оператор присваивания неярпе Ё Ё    VA»... „дугу.—№   ..‹ ‚>….  %  Не используйте операцию “не равно'“(:—) в операторе if с блоком else. При наличии еіве ;  „ & рацию сравнения. „;, …— 3%” :о ‹: чим ‹ ;„5 1  ‚..»—мгц ‚…,   $  90 Неделя 1. Основные вопросы 
ёпрактичесКИ Всегда удобнее пользоватьсяё Eauaxou == изменив порядок блоков в if. E E Например, этот оператор ` E E} if (х != 5 ) ‚ E . …“]“доператорі ‚ \ і else ^ ’^ „ …; ° E iioneparopZ; ~‘ ; Ёж“ Ёлучше записать так: ’ " Eif(x==5) И“ операторг’; ‘ і g \ ‘ else „ , “, i E &' :,Ц оператор]; ….  дчд№№уч№ап ….— .д ь…- wy „…; ...… . „…… ‚. „„„-„».….- u … …... ‚ дм.-„„ „…. ..и  п;— ! : < A „я»-ммм «„„„». .     Логические операции  Иногда бывает необхолимо поставить несколько условий одновременно. Например, “если сейчас 799 утра, рабочий день и не отпуск, то приказать будильнику зазвонить”. Логические "„операции позволяют объединить два или более выражений отношения в одно, которое в ито- дуо дает значение ИСТИНА или ЛОЖЬ. В табл. 4.7 перечислены три логические операции С.  Таблица 4.7. Логические операции в языке С     wErwepaumv Знак Пример :0 E (AND) && expl && ехр2 или (0R) | | expl | | epo НЕ (NOT) 1 !ехр1 В табл. 4.8 объясняется, как работают эти операции. //  / /  Таблица 4.8. Выполнение логических операций c   Выражение Значение выражения   . ` (ехрі && exp2) TRUE (1), только если как expl, так и ехр2 истинны. В противном случае FALSE (0). ‚5 (expl H epo) TRUE (1), если хотя бы одно из expl и ехр2 истинно. FALSE (0), только если оба " ложны. , ~ (lexpl) FALSE (0), если exp] истинно. TRUE (1), если ехр1 ложно.  a l X и   :."! ‘ / Очевидно, выражения с логическими операциями принимают значения TRUE или FALSE в зависимости от того, истинны или ложны их операнды. В табл. 4.9 приведены примеры вы- _ражений с логическими операциями.  ‚Таблица 4.9.  Выражение Значение выражения    (5 == 5) && (5 != 2) TRUE (1), т.к. оба операнда истинны (S > 1) || (6 ‹ 1) TRUE (1), т.к. один операнд истинный (2 == 1) && (5 == 5) FALSE (0), т.к. один операнд ложный !(5 == 4) TRUE(1), т.к. операнд ложный   „э,—День 4-й. Элементы программы на С: операторы, выражения и операции 91 
В выражениях можно использовать сразу несколько логических операций. Например, во— прос “равна ли переменная ›‹ числу 2, 3 или 4?” записывается так: ‹х==2›||‹х==3›||‹х==4› , C помощью логических операций нередко можно выразить один и тот же вопрос несколь— кими способами. Например, если переменная х— целочисленная, то предыдущий вопрос можно записать еще в двух формах: (х>1) && (х<5) (х >= 2) && (х <= 4)  Работа с логическими величинами  Как уже говорилось, значение логического выражения (содержащего операции отноше— ния) может быть либо 0, т.е. ПОЖЬ, либо 1, т.е. ИСТИНА. Также необходимо знать, что любое числовое значение интерпретируется как ИСТИНА или ПОЖЬ, если оно используется в выраже- нии или операторе там, где ожидается логическое значение (т.е. ИСТИНА или ПОЖЬ). Правила здесь очень просты.  I Значение 0 соответствует логическому значению ПОЖЬ. I Любое ненулевое значение соответствует логической ИСТИНЕ.  Это можно проиллюстрировать следующим примером, в котором на экран выводится значение переменной х: х = 125; if (х) printf("%d", х); Поскольку ›‹ имеет ненулевое значение, выражение (х) в операторе if будет воспринято как ИСТИНА. Можно обобщить это утверждение таким образом. Две следующих строки в язы- ке С всегда эквивалентны для любого стоящето в них выражения:  ( выражение) (выражение != 0 )  Вычисление обеих строк дает логическое значение ИСТИНА, если выражение не равно нулю, и ложь, если равно 0. Используя знак операции НЕ (!), можно также записать ( ! выражение) Это эквивалентно следующей записи: (выражение == 0)  Приоритет операций  Очевидно, логические операции в С также должны иметь различный приоритет, причем как между собой, так и в отношении других видов операций. Операция ! имеет равный при- оритет с одноместными арифметическими операциями ++ и ——. Таким образом, операция ! старше по приоритету, чем все операции отношения и двуместные арифметические операции. А вот логические операции && и || имеют намного более низкий приоритет, чем все арифметические операции и операции отношения, хотя при этом && старше, чем | | С помо- Щью скобок можно изменить приоритет логических операций так же, как и всех остальных.  92 Неделя 1. Основные вопросы 
 мотрим пример. Пусть необходимо записать логическое выражение, в котором выполня- язразу три сравнения:   Cg: _ 3113?; '. ;], a меньше, чем Ь? ’2. a меньше, чем с? 3. сменьше‚ чем d?  Все выражение должно быть истинно, если истинно условие 3 и одно из двух условий 1 и 2. Казалось бы, это можно записать так: ‘a < Ь || а ‹ с && с ‹ d Увы, ничего из этого не выйдет. Поскольку операция && имеет более высокий приоритет, „чем | |, это выражение эквивалентно такому: {a < Ь |! ( а ‹ с && с ‹ d ) А это выражение, в свою очередь, будет истинно всегда, если a < Ь, независимо от ис- тинности выражений (а ‹ с) и (с ‹ d). Поэтому необхоцимо записать ,;(а<Ь||а<с)&&с<с1 Скобки вынуждают логическое ИЛИ (| 1) выполняться раньше логического И (&&). Иллюст-  рацией служит листинг 4.6, в котором записанное выражение вычисляется обоими перечис— : ленными способами.  Листинг 4.6. List0406 . с — приоритет логических операций   ТЕ: #include <stdio.h> ‹;2: ‘53: /* Инициализация переменных. с не меньше d, */ 4: /* и это условие проверяется в программе. */ 1,5: /* Поэтому все выражение должно быть ложным. */ А6: / ‘57: int a = 5, b = 6, с = 5, d = 1; 8: int x; * ‚9, : / `10: int main( void ) | “?11: { Й 112: /* Вычислить выражение без скобок */ 13: ;і4: x = a < b || а ‹ с && с ‹ d; :15: printf("\nWithout parentheses the expression evaluates as %d", x); ‚16: ‘17: /* Вычислить выражение со скобками */ >18: 219: x = (a < Ь || а ‹ с) && с ‹ d; 90: printf("\nWith parentheses the expression evaluates as %d\n", x); 21: return О; ’32; }   ‚ Ре mmnm Without parentheses the expression evaluates as 1 _ a! ' With parentheses the expression evaluates as 0  День 4—й. Элементы программы на С: операторы, выражения и операции 93 
№ Введите и выполните эту программу. Обратите внимание, что два способа вы- числения дали разные результаты. В строке 7 приведенной программы инициа- лизируются четыре переменные, которые затем сравниваются. В строке 8 объявляется пере- менная х, в которой хранится выводимый на экран результат. В строках 14 и 19 используются логические операции. В строке 14 нет скобок, поэтому порядок выполнения операций опре- деляется их приоритетом, и результат получается не такой, как нужно. В строке 19 используются скобки, поэтому выражение вычисляется в правильном порядке.  Составные операторы присваивания  Составные операторы присваивания в С используются для более краткой и удобной запи- си операции присваивания, выполняемой одновременно с двуместными арифметическими операциями. Пусть, например, требуется увеличить значение переменной х на 5, т.е. приба- вить 5 к х и снова поместить результат в x. Можно записать так: x = x + 5; Применение составного оператора присваивания, который можно считать просто спосо- бом сокращения записи, дает следующее: x += 5; B более общей формулировке составной оператор присваивания имеет следующий син- таксис (где 0p обозначает знак двуместной операции): expl op= exp2 Обычным способом это можно записать так: ехр1 = expl op ехр2 Составные операторы присваивания образуются с помощью пяти двуместных арифмети-  ческих операций, рассмотренных ранее на этом занятии. В табл. 4.10 приведены некоторые примеры.  Таблица 4.10. Примеры составных операторов присваивания    Составной оператор Эквивалентная форма дну х=х*у у—=2+1 y=y-z+1 a/=b a=a/b x+=y/8 x=x+y/8 y%=3 y=y%3   C помощью составных операторов присваивания можно удобно записать многие сложные выражения. Особенно ярко это удобство проявляется, когда переменная в левой части опера- тора присваивания имеет очень длинное имя. Как и обычный, составной оператор присваива- ния сам по себе является выражением, равным своей левой части. Поэтому после выполнения следующего фрагмента кода переменные x и 2 станут Одновременно равными 14: X = 12; / z = x += 2;  94 Неделя 1. Основные вопросы 
ЁОперация выбора по условию  Ё Операция выбора по условию —-—"это единственная трехместная операция в С, т.е. такая,  *которая работает сразу с тремя операндами. Вот ее синтаксис: (_ ngpl ? exp2 : ехрЗ; é‘ Если значение ехр1 равно TRUE (любому ненулевому значению), то все выражение стано- Ёится равным ехр2. Если же exp} принимает значение FALSE (другими словами, нуль) то все іыражение становится равным ехрЗ. Например, следующий оператор присваивает перемен- Ёюй х значение 1, если у ненулевое, и значение 100, если у равно нулю: ? = у ? 1 : 100; 3 Аналогично, чтобы присвоить переменной z наибольшее из двух значений ›‹ и у, можно писать (X > Y) ? х = у: E Возможно, вы заметили, что операция выбора по условию чем-то похожа на условный {оператор if. Предыдущий оператор можно записать и так:   {поил E z=x; glse ;…”  Операцию выбора по условию не удастся использовать во всех случаях вместо конструк- ции if . . .else. Все же краткость и удобство этой операции очевидны. Кроме того, ее можно ’применять там, Где оператор if недопустим, например, внутри вызова функции. Вот пример 'ее использования при вызове функции printf( ):  printf( "The larger value is %d", ((x > y)? х : у)); /  Запятая  Запятая часто используется в С в качестве знака препинания, чтобы разделить объявления переменных, аргументы функций и т.п. В некоторых случаях запятая применяется m знак операции, а не простой разделитель. Разделив запятой два выражения, можно образовать но- вое выражение. Вот что при этом получается. I Вычисляются оба выражения, причем левое выражение —— первым.  I Значением всего выражения считается результат вычисления правого внутреннего вы- ражения.  В следующем примере значение Ь присваивается переменной х, после чего инкрементиру- ется a, a затем b.  х = (a++, b++); Поскольку операция ++ используется в постфиксной форме, вначале значениеь присваива- ется переменной x, и только потом инкрементируется. Скобки обязательны, потому что запятая как знак операции имеет очень низкий приоритет, даже ниже, чем оператор присваивания.  Как мы узнаем на следующем занятии, чаще всего запятая в виде знака операции приме- няется в операторе for.  День 4-й. Элементы программы на С: опера торы, выражения и операции 95 
  Рекомендуется Не рекоменцуется  ИспольЗуйте Логические операции && и ? 5 Не путайте оператор прджиВаНия (=})? у……” вместо вложенных операторов if „; знаком pants-”max -= „№№/„ув за,: _„  _ » „. ‚д…… >..т..›__ь._._к.._.._д_2_д .. ‚.- ‚:»/„…:…     Снова о приоритете операций  В табл. 4.11 перечислены операции языка С в порядке убывания их приоритета. Сперах ции, находящиеся в одной строке, имеют одинаковый приоритет.  Таблица 4.1 1 . Приоритет операций в С   Приоритет Операции  () []->-  ! ' ++ __ * (ссылка по адресу) & (адрес) ( тип) sizeof + (одноместный) - (одноместный)    3 * (умножение) / % 4 + - 5 « >> 6 < <= > >= 7 == I: 8 & (побитовое и) 9 А 10 | 1 1 && 12 || 13 ? : 14 = += —= *= /= %= &= ^= = <<= >>= 15 , ( ) —— вызов функции, [ ] — ссылка на элемент массива   _ Держите эту таблицу под рукой, пока не освоитесь с приоритетами “MEI“ операций. В практике программирования на С необходимость знать приоритеты возникает постоянно.     Резюме  На этом занятии было рассмотрено много важных тем. Вы узнали, что основной лексиче— ской единицей языка С является оператор. Операторы заканчиваются точкой с запятой. Компи— лятор С игнорирует свободное пространство в программах, позтому операторы следует/оформ— лять определенным образом для улучшения читаемосги программы. Несколько операторов можно объединить в блок, заключенный в фигурные скобки. Блоки могут использоваться везде, где и отдельные операторы. Поэтому блоки также называют составными операторами.  96 Неделя 1. Основные вопросы 
5?  ЁЁ» Многие из операторов включают выражения, соединенные знаками операций. Выраже- іние —— это объект программы, который моЖно вычислить и получить числовое значение. Вы— Ёражения могут быть очень сложными и состоять из набора более простых выражений. Знаки операций в языке С приказывают компыотеру выполнить те или иные операции над ‚одним или несколькими выражениями. Некоторые из операций — одноместные, т.е. выпол- няются над одним операндом. Но большинство операций в С — двуместные, и выполняются над двумя операндами. Одна операция —— выбор по условию — трехместная. Операции в С имеют четко разграниченный уровень приоритета, который определяет порядок их выполне- ния в выражениях. Операции, изученные на этом занятии, делятся на три категории.  I Арифметические операции выполняют арифметические действия над операндами (такие как сложение и т.п.).  I Операции отношения выполняют сравнение операндов (больше, меньше, равно и т.п.).  I Логические операции выполняются над логическими значениями ИСТИНА/ПОЖЬ. Следу- ет помнить, что в С ПОЖЬ соответствует нулю, а ИСТИНА —— единице, и что любое нену- левое числовое значение также интерпретируется как ИСТИНА.  На этом занятии также рассматривался оператор іі, с помощью которого можно перена- Ётравить выполнение программы в зависимости от того или иного условия.  ;Вопросы и ответы  , какое влияние оказывают на выполнение программы пробелы, пустые строки и fan. B ее исходном тексте? “ Свободное пространство в исходном тексте (строки, пробелы, табуляции) помогают сде- шать код более удобочитаемым. При компилировании программы все эти символы игнориру- дется и не оказывают никакого влияния наисполняемый код. Поэтому можно смело пользо- Зваться такими символами для улучшения читаемости.  Что лучше: составной оператор іі или несколько вложенных операторов іі? Исходный код должен быть легко понятным. Вложенные операторы іі анализируются ‚Компилятором так, как было показано на этом занятии. В одном составном операторе іі его ёыражение-условие вычисляется либо до конца, либо пока оно не станет безусловно ложным.  В чем разница между одноместиыми и двуместными операциями? ёё” Как подразумевают сами названия, одноместные операции выполняются над одним опе- Ёрандом, а двуместные — над двумя.  '5'" '  _ Является ли знак “минус” (-) знаком одноместной или двуместной операции? % И той, и другой. В первом случае это обращение знака, во втором —-— вычитание. Компи- {wimp успешно различает эти два случая, анализируя выражение, в котором этот знак встре- Ёчается. Например, в следующем примере это одноместная операция:  &‘ "' 'У? _ А здесь—двуместная: ; = a — b;  ‹,  іі Если рассматривать отрицательное число как логическое значение, оно будет ЁЁистиной” или “ложью”? Ё Значение 0 считается “ложью”, а все ненулевые значения, включая и отрицательные—— Ё'Ё‘истиной”. Жень 4—й. Элементы программы на С: операторы, выражения и операции 97 
Коллоквиум  В ЭТОМ КОЛЛОКВИУМС вам ПРСДЛЗГЗЮТСЯ КОНТРОЛЬНЫС ВОПРОСЫ ДЛЯ закрепления теоретиче- СКОГО материала и УПРЭЭКНСНИЯ ДЛЯ практики.  Контрольные вопросы  1. Как называется этот оператор С, и каково его назначение? ›‹ = S + 8 2. Что такое выражение?  3. Если выражение содержит несколько операций, какие факторы определяют порядок их выполнения?  4. Пусть переменная ){ имеет наЧальное значение 10. Каким будут значения ){ и a после вы— полнения каждого из следующих операторов по отдельности?  a = X++; a = ++X; 5. Вычислите значение выражения 10 % 3. 6. Вычислите значение выражения 5 + 3 * 8 / 2 + 2.  7. Перепишите выражение из вопросаб с использованием круглых скобок так, чтобы его значение стало 16.  8. Если выражение ложно с точки зрения его логического значения, то каково его числовое значение?  9. Какие операции из пар в следующем списке имеют более высокий приоритет? а) == или < 6) * или + в) != или == г) >= или >  10. Что такое составные операторы присваивания, и в чем их удобство?  Упражнения  1. Следующая программа плохо оформлена. Введите и скомпилируйте ее. Запустите ее на выполнение и посмотрите, что она делает.  #include <stdio.h> int x,y;int main(){ printf( "\nEnter two numbers");scanf( "%d %d",&x,&y);printf( "\n\n%d is bigger",(x>y)?x:y);return 0;}  2. Перепишите программу из упражнения 1 в более удобном для чтения виде.  3. Измените листинг 4.1 так, чтобы программа увеличивала, а не уменьшала значения пере-  МСННЫХ. /  98 Неделя 1. Основные вопросы 
Напишите оператор if, который присваивает значение )( переменной y, только если ›‹ на- ходится между 1 и 20. Если )( окажется за пределами этого диапазона, переменную у из- менять не нужно.  Выполните упражнение4 с помощью операции выбора по условию вместо условного оператора if.  Перепишите следующие вложенные операторы if с использованием одного оператора if и логических операций. (x < 1) if (x > 10) оператор;  Вычислите значение каждого из приведенных ниже выражений. а) (1 + 2 * 3) 6)10%3*3—(1+2) B) ((1 + 2) * 3) г) (5 == 5) д)(х = 5)  8, Пусть )( = 4, у = 6, z = 2. Определите, является ли каждое из этих выражений ложным  \ , / at? l  или истинным: а) if(x == 4) 6) if(}: != у - z) B)if(2 = 1) г) if(y)  ‚_9; Напишите оператор if для определения того, является ли некто взрослым дееспособным  ‚›‹ '*` :?  гражданином (достигнувшим 21 года), но не пенсионного возраста (65 лет и старше).  I9. Поиск ошибок. Устраните ошибки в следующей программе:  19 Is? .  1 „. с ‘.'т  “: ‘ ’3‘“: И.. - "' ""`! "53‘" . .  а; .?  2  (gigging, мг%.  ‚\ L  ”by зщ  ЁЁ. r ‚’  %%  3“ .а . «"_: \ Y ‹ f z“ u; № T „ ‚ . ": “(и .  _;   /* программа с ошибками... */ iinclude <stdio.h>  int х =1: int main( void ) { if( х = 1 ); printf(“ х equals 1 " ); otherwise printf(" х does not equal 1 “); return 0; }  b 4-й. Элементы проГРЭММЫ на С! oneparopbl, выражения И операции 99 
Самостоятельная работа 2  Программа угадывания чисел  Перед вами вторая самостоятельная работа. Помните, что цель самостоятельной рабо- ты ——- поработать с примерами программ более сложных, чем на основных занятиях, выпол- няющих приближенные к практике задачи. В этом листинге встречаются элементы, еще не рассмотренные на наших занятиях. Надеемся, что вы все-таки поймете текст_программы без особого труда. После ввода и запуска программы поэкспериментируйте с ее текстом —— изме- ните что-нибудь, перекомпилируйте и посмотрите, что получится при выполнении. Если  встретятся ОШИбКИ, ТЩЗТСЛЬНО исправьте ИХ.  Листинг С2. find_nbr.c   NNND—‘D—‘D—‘D—‘D—‘D—‘D—‘D—ID—‘D—l [`) Ь-д (:> \‘3 С!) "] (" {11 “>- (ь) [`) Ь-д <=> \‘D .. .. .. .. .. .. .. .. .. .. .. ..  ") ‚Ь .  (I: ": "` ‘11 IC‘ См) ") Р-‘ .. .. о. о. о. о. о. о.  ") СА) . .. по о.  /* Имя: find_nbr.c  * Назначение: Программа выбирает случайное число  И предлагает пользователю угадать ЕГО  * Возвращает: нуль  #include <stdio.h> #include <stdlib.h> #include <time.h>  #define NO 0 #define YES 1  int main( void )  {  int guess_value = -1; int number; int nbr_pf_guesses; int done = NO;  printf("\n\nGetting a Random number\n");  / /* инициализация генератора случайных чисел текущим временем */ srand( (unsigned) time( NULL ) ); 
25; number = rand(); 26: 27; nbr_of_guesses = О; 23; while ( done == N0 ) 29: { .30; printf("\nPick a number between 0 and %d> ", RAND_MAX); 31; scanf( "%d", &guess_value ); /* Get a number */ 32: 33; nbr_of_guesses++; 34: 35; if ( number == guess_value ) 36: { 37; done = YES; 38: } 39: else 40: if ( number < guess_value ) 41: { 42: printf("\nYou guessed highl"); 43: } 44: else 45: { 46: printf("\nYou guessed low!"); 47: } 48: } 49: 50: printf("\n\nCongratulations! You guessed right in %d Guessesi", 51: nbr_of_guesses); 52: printf("\n\nThe number was %d\n\n", number); 53: 54: return 0; 55: }   “Эта программа играет с человеком в простую игру — угадывание. Пользователь старается угадать число, наугад выбранное компьютером. После каждой попытки программа отвечает, попал ли пользователь выше или ниже задуманного числа. Как только число угадано, про- грамма поздравляет игрока и сообщает, сколько попыток y него ушло на отгадывание числа. Если хотите схнтрить, добавьте в программу строку, выводящую число на экран сразу по— сле того, как компьютер его выберет. Можно вставить эту строку, например, после первой компиляции и выполнения программы.  26: printf("The random number (answer) is: %d", number ); /* хитрость */  Таким образом можно проверить, правильно ли работает программа. Когда вы решите дать эту программу на пробу друзьям, не забудьте убрать строку с хитростью!  Самостоятельная работа 2. Программа угадывания чисел 101 
  Фун кции  Функции —— это ключ к пониманию всего программирования на С и всей идеологии по— строения программ на этом языке. Вы уже ознакомились с некоторыми библиотечными функциями С, которые в готовом виде вх0дят в комплект поставки компилятора. На этом за— нятии будет рассмотрено создание нестандартных функций, которые проектирует и пишет сам программист для своих (конечно, нестандартных) целей. Рассматриваются следующие вопросы.  I Что такое функции и из каких частей они состоят Преимущества структурного программирования с применением функций Создание функции Объявление локальных переменных внутри функции  Возвращение значения из функции в программу  Передача аргументов в функцию  Что такое функции  Ответ на вопрос “Что такое функции?” будет дан в два этапа. Вначале рассмотрим опре- деление функций, а потом их применение.  Определение функции  НПВЫЙ IRIIIMIIH Итак, определение: функцией называется независимый фрагмент кода на С, имеющий собственное имя, предназначенный для выполнения определенной спе-  циализированной задачи и возвращающий значение в вызвавшую его программу (последнее не обязательно). Теперь рассмотрим по очереди все составляющие этого определения.  l Имя. У каждой функции есть свое неповторимое имя. Используя это имя B другой час— ти программы, можно выполнить операторы, содержащиеся в функции. Это называет— ся вызовом функции. Функцию можно вызывать из другой функции.  l Независимость. Функция может выполнить свою задачу без вмешательства других функций и, в свою очередь, не вмешиваясь в их работу. 
i i ъ‘ч I. і і" l    ‚`: ?,  I Специализированная задача. Это самая легкая часть определения. Специализирован-  ная задача— ЭТО 0ТДСЛЬН8Я, законченная ЧЗСТЬ общей работы программы, например, nocsmxa СТРОКИ TCKCTa на ПСЧЗТЬ‚ copmposKa Maccnsa, ВЫЧИСЛСНИС КУбИЧеСКОГО КОРНЯ И Т.П.  I Возвращение значения. Когда программа вызывает функцию, выполняются операторы  этой функции. При необходимости функция может передать определенную информа- цию назад в вызывающую программу.  Вот и все определение функции. Изучая следующий раздел, не упускайте его из виду.  Пример функции  В листинге 5.1 приведен пример функции, определенной программистом.  истинг 5. 1 . cube . с — программа, использующая функцию для _` ычисления куба (третьей степени) числа   rvtr  " = ‚…,—„7 . . `О co `] Ф … ab Ш N п п п п .. п .. ..  V7   27:  ‚_в ..  /* демонстрация простой функции */ #include <stdio.h>  long cube(long x);  long input, answer;  int main( void )  {  }  printf("Enter an integer value: "); scanf("%d", &input); answer = cube(input); /* Примечание: %ld - спецификация формата */ /* для вывода длинного целого - іоги; int.*/ printf("\nThe cube of %ld is %ld.\n", input, answer);  return 0;  /* Функция: cube() - вычисляет куб своего аргумента */ long cube(long x)  {  }  long x_cubed;  х cubed = х * х * x; return x_cubed;  Enter an integer value: 100 MW” The cube of 100 is 1000000.  Enter an integer value: 9 The cube of 9 is 729. Enter an integer value: 3 The cube of 3 is 27.  День 5-й. Функции 103 
 — Приведенный ниже анализ посвящен объяснению только тех компонентов (Пишим программы, которые относятся непосредственно к функциям, a не всего ` ее текста.  № В строке 4 находится прототип функции —— ее предварительное опи— сание. Сама же функция появится в программе позже. Прототип со— стоит из имени функции, списка передаваемых в нее переменных и типа возвращаемого зна— чения (если оно есть). В строке 4 можно прочесть, что функция называется cube, принимает переменную типа long и возвращает значение того же типа long. Передаваемые в функцию переменные называются аргументами. Они стоят в круглых скобках после имени функции. В этом примере у функции один аргумент: long x. Ключевое слово перед именем функции ука— зывает тип значения, которое функция возвращает. В данном чае возвращается перемен— ная типа long. 700’ B строке 12 вызывается функция cube. Ей передается в качестве аргумента переменная input. Возвращенное функцией значение присваивается переменной answer. Обратите вни— мание, что в строке 6 эти переменные объявляются и им присваивается тип long, совпадаю- щий с типом из прототипа функции в строке 4.  НПБЫЙ №№… Сама фУНКция -—— ее полный текст -— называется определением функции. В дан— ном случае функция называется cube и находится в строках 21—27. Как и ее про—  тотип, определение функции состоит из нескольких частей. Функция начинается с ее заголов— ка в строке 21. Заголовок функции всегда стоит в ее начале и содержит имя функции (т.е. cube B нашем примере). В заголовке также указаны тип возвращаемого значения и список аргу- ментов функции. Обратите внимание, что заголовок функции совпадает с ее прототипом — B нем нет только точки с запятой. Тело функции заключено в фигурные скобки и находится в строках 22—27. Тело содержит операторы, такие как в строке 25, которые выполняются всякий раз при вызове функции. Строка 23 содержит объявление переменной точно такое же, как вы уже встречали, но с Од- ним отличием— это локальная переменная. Локальные переменные объявляются в теле функции. (Локальные объявления рассматриваются на занятии 12, посвященном области дей- ствия переменных.) Функция заканчивается оператором return B строке 26, который выпол- няегся последним. Этот оператор передает в вызывающую программу значение переменной x_cubed. C точки зрения структуры функции cube() и main() совершенно одинаковы. Функция main() —— такая же функция, как и другие. В числе других уже встречавшихся вам функций можно вспомнить printf() и scanf( ). Хотя printf() и scanf() являются библиотечными функциями (и не пишутся программистами прямо в их программах), они точно так же при- нимают аргументы и возвращают значения, как и любые нестандартные функции.     Как работают функции  HflBblfi №№… Программа на С не выполняет операторы, содержащиеся в функции, до тех пор, пока функцию не вызовут из какой-нибудь другой части программы. При вызове  функции программа может послать в нее информацию в виде одного или нескольких аргу- ментов. Аргумент— это элемент данных из программы, который посылается в функцию. Этот элемент может использоваться функцией для выполнения ее задачи. После вызова функции выполняются ее операторы, делая положенную им работу. Когда выполнение функ- ции заканчивается, управление передается в ту самую точку программы, откуда функция бы-  104 Неделя ?. Основные вопросы 
@313 вызвана. Функция может послать некоторую информацию назад B вызвавшую программу в "виде возвращаемого значения. \ На рис. 5.1 показаны три функции, каждая из которых вызывается один раз. При вызове какой-либо из функций управление передается в нее, а после ее окончания _— назад в то место программы, откуда функция вызывалась. Функции можно вызывать сколько угодно раз и B любом порядке.  Главная программа main() { / вызов func 14"”7 вызов func2:E:::::  вызов func3\     v, 4”\9~R§~-’<‘ psi-$1. «L:    H. c: 1:1 а м   в..-! 5 „… №№    func3 ()  }  ’1 WWI“ “ * w” W > „ «“М … ^“ &……хйч …щ "№№ №№…     ,] “` 3: % Ён     у“; .,д Рис. 5.1. Передача управления при вызовах функций  Вот вы уже и знаете, что такое функции и_ зачем они нужны. Теперь займемся созданием ' своих собственных функций. I  Объявление и определение функций  Прототип функции  тип-возвраЩ-значения имя_функции( тип-арг имя-1, . . . ‚ тип-арг имя-п);   Определение функции  тип-возвраЩ-значения имя_функции( тип-арг имя-1, . . . ‚ тип-арг имя-п) { / * операторы; */ } Прототип функции сообщает компилятору описание функции, которая будет опре- делена B программе позже. Прототип включает тип возвращаемого из функции зна— чения, а также имя функции, которое по возможности должно отражать то, что функция делает. Также прототип содержит типы аргументов (тип-арг), передавае- мых B функцию. Можно, хотя и необязательно, включить в прототип имена аргу- ментов. Прототип всегда должен заканчиваться точкой с запятой. Определение функции —-— это, собственно, полный текст функции. Определение включает весь ее выполняемый код. Если прототип содержит имена аргументов, то- гда первая строка определения (заголовок функции) полностью совпадает с прото- типом за исключением точки с запятой — B заголовке функции ее быть не должно. Кроме того, хотя имена аргументов необязательны B прототипе функции, B ее за- головке они должны присутствовать непременно. После заголовка функции идет ее  День 5-й. Функции 105 
тело, которое содержит выполняемые операторы. Тело должно начинаться с откры— вающей фигурной скобки и заканчиваться закрывающей. Если тип возвращаемого из функции значения — не void, то в теле должен быть хотя бы один оператор return для возвращения значения соответствующего типа.  Примеры прототипов функций double squared( double number ); void print_report( int report_number ); int get_menu_choice( void ); Примеры определений функций  double squared( double number ) /* заголовок */  { /* открывающая скобка */ return( number * number ); /* тело функции */ } /* закрывающая скобка */ void print_report(int report_number ) {  if( report_number == 1 ) puts( "Printing Report 1" ); else puts( "Not printing Report 1" );  Функции и структурное программирование  "ПВЫЙ ШЕПМШ] Использование функций в программе на С позволяет практиковать такой подход, как структурное програмлшрование. При этом отдельные задачи программы  выполняются независимыми фрагментами кода. Не правда ли, похоже на цитату из определе- ния функций, приведенного ранее? Получается, что структурное программирование и функ— ции тесно связаны между собой.  Преимущества структурного программирования  Чем же.замечательно структурное программирование? Существуют две весомые причи- ны, почему это так.  l Структурированную программу легче писать, потому что сложная задача разбивается на много мелких простых задач. Каждая задача выполняется функцией, в которой и код, и переменные изолированы от остальных частей программы. Быстрый прогресс в разработке программы достигается благодаря сосредоточению всякий раз на одной сравнительно простой задаче.  I Структурированную программу легче отлаживать. Если в программе есть ошибка (неправильный оператор, из—за которого программа работает не так, как нужно), структурированность помогает локализовать ошибку в той или иной части кода, на- пример, в конкретной функции.  106 Неделя 1. Основные вопросы 
Еще одно преимущество структурного программирования состоит в экономии времени. Написав функцию, которая выполняет в программе определенную задачу, можно легко и бы— стро перенести эту функцию в другую программу для выполнения Tim же задачи. Даже если в новой программе понадобится сделать что—то несколько отличное т прежнего, почти всегда легче немного изменить уже существующую функцию, чем написать новую с нуля. Напри- мер, мы очень часто использовали функции printf ( ) и scanf( ), так ни разу и не заглянув в их исходный код. Если ваши собственные функции созданы для !Ыполнения четко постав- ленной задачи, использовать их в других программах будет намного легче.  Планирование структурированной программы  Если вы собрались писать хорошо структурированную программу, придется вначале по— трудиться над планом. Планированием нужно заниматься, еще не написав ни единой строчки кода, зато вооружившись карандашом и бумагой. План должен включать список конкретных задач, которые будет выполнять программа. Начать следует с глобальной идеи работы про- граммы. Если, например, программа должна вести список контактов (имен и адресов), то чего от нее следует требовать? Вот несколько очевидных операций.  I Ввод новых имен и адресов. I Изменение существующих пунктов списка. I Сортировка списка по фамилиям. I Печать наклеек на почтовые конверты.  С помощью этого списка программа уже разделена на четыре основные задачи, каждую из которых можно отдать отдельной функции. Теперь можно пойти еще дальше и разделить эти задачи на подзадачи. Например, задачу ввода новых имен и адресов можно разделить на сле- дующие подзадачи.  I Чтение существующего списка адресов с диска. I Приглашение пользователя ввести один или несколько новых пунктов. I Добавление новых данных в список. I Сохранение измененного списка на диске.  Аналогичным образом задачу изменения существующих пунктов списка можно разбить на такие подзадачи.  I Чтение существующего списка адресов с диска. I Изменение одного или нескольких пунктов списка. I Сохранение измененного списка на диске.  Заметьте, что у этих списков имеются две общие подзадачи: чтение с диска и сохранение на диске. Можно написать одну функцию для чтения списка адресов с диска и вызывать ее как из функции ввода новых адресов, так и из функции изменения существующих пунктов. То же самое справедливо для функции сохранения списка на диске. Вы уже должны увидеть как минимум одно преимущество структурного программирова- ния. Удачно разделив программу на подзадачи, можно определить, в каких частях программы выполняются одинаковые операции. Далее можно написать функции обращения к диску, имеющие многоцелевое назначение, тем самым экономя время и улучшая эффективность программы.  День 5-й. Функции 107 
Такой метод программирования делает структуру программы иерархической, или много— уровневой. На рис. 5.2 показан иерархический подход K написанию программы, предназна- ченной для ведения списка адресов.   Функция таіп       Г—ітениЫ [Изменент 'Сохранение!  "` I I ‘ I I . o     Puc. 5.2. Иерархическая организация x0- рошо сіпруктурированной программы  Следуя этому планомерному подходу, можно быстро составить список отдельных задач, которые должна выполнять программа. Потом можно браться за каждую отдельную задачу по очереди, уделяя внимание каждый раз лишь сравнительно небольшой и несложной части общей работы. Как только очередная функция написана и работает, можно переходить к на- писанию следуюшей. Вы и оглянуться не успеете, как программа начнет принимать оконча- тельные очертания.  Метод программирования сверху вниз  Используя структурный подход, программисты на С фактически следуют методу про- граммирования сверху вниз. Это видно из рис. 5.2, на котором структура программы напоми- нает перевернутое дерево. Как правило, вся фактическая работа программы выполняется функциями на концах “ветвей” этого дерева. Функции, находящиеся ближе к “стволу”, B ос- новном распределяют выполнение программы между этими функциями. В результате, функции main( ) -—— т.е. тела — многих программ на С содержат сравнитель- но немного операторов. Основной объем кода заключен в функциях. Тело шаіп( ) может со- держать всего несколько десятков строк кода, которые распределяют задания между функ- циями. Часто пользователю предоставляется меню. Выполнение программы разветвляется в зависимости от выбора пользователя. Каждая ветвь выполняется отдельной функцией.   - Меню — хорошее средство создания удобных в использовании программ. HIM!!!“ Ha занятии 13 рассматривается применение оператора switch для созда- ` ния многофункционапьных систем под управлением меню.     Итак, вы ознакомились со строением функций и их использованием. Теперь пора взяться за написание своих собственных функций.      Рекомендуется Не рекомендуется _:Планируйте программу заранее, до того. ‚ % Не пытайтесь выполнить все операции ?іііак приступить к написанию кода. Опре- в одной функции. Одна функция должна % ';}делив структуру программы. вы сэконо- , выполнять одну задачу. например. чте- , гамиъте время на ее написание и отладку. , ние данных из файла.    108 Неделя 1. Основные вопросы 
[Написание Функции  l Первый шаг в написании функции —— это определить, зачем она вообще нужна и что будет делать. Как только это станет ясно, технические детали написания функции уже не будут представлять особой сложности.  Заголовок функции  Первая строка любой функции —— это ее заголовок, состоящий из трех частей. Каждая из „ его частей служит своей цели. Это показано на рис. 5.3 и рассматривается в следующих раз- делах.  Имя функции  Тип возвращ. значения Список параметров  Ф  тип mdpynxmapaul, . . . )  Рис. 5.3. Три компонента заголовка функции  "Тип возвращаемого значения  , Тип возвращаемого функцией значения определяет тип данных, которые возвращаются из ‚ функции в вызывающую программу. Этот тип может быть любым из имеющихся в языке С, Ёнапример, char, int, long, float, double. Можно объявить функцию, которая не возвращает никакого значения, задавая пустой тип void. BOT несколько примеров:  ^ім: func1(...) /* Возвращает число типа int. */ float func2(...) /* Возвращает число типа float. */ void func3(...) /* Ничего не возвращает. */  В этих примерах функция funcl возвращает целое число (int), funcZ —— вещественное (float), a func3 He возвращает никакого значения.  Имя функции  Функции можно дать любое желаемое имя, лишь бы оно удовлетворяло требованиям С к именам переменных (см. занятие 3 о переменных и константах). В программах на С имена функций должны быть уникальными (не совпадать ни с какими другими именами функций или переменных). Рекомендуется давать функции такое имя, чтобы оно описывало выпол- няемую ею задачу.  Список параметров  У многих функций есть аргументы —— значения, которые передаются в функцию при ее вызове. Функции должно быть известно, каких аргументов ожидать, т.е. тип данных каждого аргумента. В функцию можно передать аргумент любого имеющегося в С типа. Информация о типах приводится в заголовке функции в виде списка параметров.  День 5-й. Функции 109 
Для каждого аргумента, передаваемого B функцию, список параметров должен содержать соответствующий пункт. Пункт списка состоит из типа данных и имени параметра. Вот при- мер заголовка функции из листинга 5 . 1:  long cube(long x) Список параметров состоит из объявления long x. Это объявление декларирует, что  функция принимает один аргумент типа long, представленный параметром x. Если парамет— ров несколько, они отделяются друг от друга запятыми. Заголовок  void funcl(int x, float y, char 2) _ / определяет функцию с тремя аргументами: x типа int’, у типа float, z типа char. Некоторые  функции вообще не принимают аргументов, и их список параметров должен состоять из од“ ного ключевого слова void:  int func2(void)   B конце заголовка функции нельзя ставить точку с запятой. Если случай- (flfllflfllfll но все же поставить ее, компилятор сгенерирует сообщение об ошибке.     Нередко возникает путаница между понятиями параметра и аргумента. Параметр —— это пункт в списке, стоящем в заголовке функции. Он служит всего лишь заменителем аргумента. Параметры функции фиксированы, и выполнение программы на них не влияет. В то же время аргумент —— это реальное значение, передаваемое в функцию вызывающей ее программой. Каждый раз при вызове функции ей могут передаваться другие аргументы. В С функция должна принимать одно и то же количество аргументов фиксированных типов при каждом вызове, но их значения могут быть всякий раз другими. Внутри функции обращение к аргументу производится по имени соответствующего ему параметра. Все это станет понятнее после рассмотрения примера. В листинге 5.2 приведена очень простая программа с одной функцией, вызываемой дважды.  Листинг 5.2. List0502 . с —— различие между аргументами и параметрами   1: /* Иллюстрация различия между параметрами и аргументами. */ 2: 3: #include <stdio.h> 4: 5: float x = 3.5, у = 65.11, 2; 6: 7: float half_of(float k); 8: 9: int main( void ) 10: { 11: /* B этом вызове x - аргумент функции half_of(). */ 12: 2 = half_of(x); 13: printf("The value of 2 = %f\n", 2); 14: 15: /* B этом вызове у - аргумент функции half_of(). */ 16: 2 = half_of(y); 17: printf("The value of 2 = %f\n", 2); 18: 19: return 0; 20: }  110 Неделя 1. Основные вопросы 
 2 : float half_of(float k) 2 : { 24: /* k - параметр. Всякий раз при вызове half_of() */ 25: /* k принимает значение переданного аргумента. */ 26: 27: return (k/Z); 28: } The value of z = 1.750000 Pezgnbmam The value of z = 32.555000  Рис. 5.4 демонстрирует соотношение между аргументами и параметрами.  № В листинге 5.2, в строке 7 объявляется прототип функции half_of( ). B строках 12 и 16 выполняется вызов функции half_of( ), a сама функция наХОДится в строках 22—28. В строках 12 и 16 в функцию half_of( ) посылаются два разных аргумента. В строке 12 это значение переменной х, которое равно 3 . 5, a B строке 16 это уже переменная у, равная 65.11. При запуске программы она выводит значения, вычисленные функцией для ка- ждого аргумента. Значения, сопержащиеся в х и у, передаются в функцию через ее параметр k. Это похоже на копирование значений :: и у в k. Затем функция half_of( ) возвращает пере- данное ей значение, разделив его на два (строка 27).  Рекомендуется Не рекомендуется         ;Испоцьзуйте имена функций хорощо‘ *Не передаваите в функцию значение, описывающие выполняемые функциями если оно там He требуется „… ‚>> {: … * задачи „;; `: „% …::: …:}; ~15 « Не ,; пытайтесь передать в функцию Ё Следите за тем чтобы передаваемые в „Меньше (или больше) аргументов. чем` ’ „функцию аргументы были того же типа, y нее’ должно быть параметров В языи “; :HTo и параметры этой функции ' ’~ :: ке C количество переданных аргумен— g „ :; 31 35“?” адм „; …% >?“ 33“ Ё тов должно точно совпадать c колиЧе- … ;;: …… ;; ›… …; …ствокпгеамеірчвэ сити: : … Первый вызов функции z=ha1f__of (x) ;  float half_of(float k)  Второй вызов функции z=half_of (y) ; 65 11  float half_of(float k)  Puc. 5. 4. Передача аргументов в функцию, объявленную со спи- ском параметров  День 5—й. Функции 111 
Тело функции  Тело функции должно быть заключено в фигурные скобки и стоять сразу же после заго— ловка. Именно в теле выполняется вся работа функции. При вызове функции ее выполнение начинается с первого оператора тела и заканчивается оператором return или закрывающей скобкой (с возвращением в вызвавшую программу).  Локальные переменные  HflBblfl MIMI“ B теле функции можно объявлять переменные. Такие переменные называются локальными. Термин локальные означает, что переменные принадлежат только  данной конкретной функции и отличаются от переменных с такими же именами, объявлен— ными в других частях программы. Вскоре мы дадим более подробные объяснения, а пока научимся объявлять локальные переменные. Локальная переменная объявляется так же, как и всякая другая: с использованием типов и соглашений об именах, изученных на третьем занятии. Локальные переменные также можно инициализировать при объявлении. В функции можно объявить переменную любого имею— щегося в С типа. Вот пример объявления четырех локальных переменных в функции:  int funcl(int y)  { int a, b = 10; float rate; double cost = 12.55; /* дальше идет код функции... */ }  В этом фрагменте объявляются локальные переменные а, b, rate, cost, которые затем мо- гут использоваться в теле функции. Обратите внимание, что параметры функции также счита- ются объявленными переменными, и их можно использовать в этом качестве внутри функции. Объявленная в теле функции переменная совершенно отделена и независима от всех ос- тальных переменных, объявленных где-то в другом месте программы. Это справедливо даже в том случае, если ее имя совпадает с именем какой-нибудь другой переменной. Листинг 5.3 демонстрирует эту независимость переменных.  Листинг 5.3. var .c -- демонстрация локальных переменных   /* демонстрация локальных переменных. */  #include <stdio.h>  intx=1,y=2;  void demo(void);  фЧФШ-ЬЫМН .. о. ..  9: int main( void ) 10: { 11: printf("\nBefore calling demo(), х = %d and y = %d.", х, у); 12: demo(); 13: printf("\nAfter calling demo(), x = %d and y = %d\n.", x, y); 14: 15: return О;  112 Неделя 1. Основные вопросы 
фб: }  17: $8: void demo(void) 139 : { 20: /* Объявление и инициализация двух локальных переменных. */ 21: 22: int x = 88, у = 99; 23: 24: /* Отображение их значений. */ 125: `26: printf("\nWithin demo(), x = %d and y = %d.", х, у); 27: }   7—7 ‚  › Before calling demo(), х = 1 and у = 2. _ Pezunbmam Within demo(), х = 88 and у = 99.  After calling dem0(), x = 1 and у = 2.  Листинг 5.3 во многом аналогичен предыдущим программам этого занятия. В строке 5 объявляются переменные х H y. OHH объявлены за пределами функций и поэтому считаются глобальными. В строке7 находится прототип демонстрационной функции под названием demo( ). Она не принимает параметров, поэтому в ее прототипе стоит объявление void. Функ— ция также ничего не возвращает в программу, поэтому и тип возвращаемого значения у нее void. B строке 9 начинается функция main( ), очень простая. Вначале, в строке 11 вызывается функция printf ( ) для отображения значений ›‹ и у, а затем вызывается функция demo( ). Об— ратите внимание, что в строке 22 функции demo() объявляются ее собственные локальные переменные х и у. Строка 26 показывает, что локальные переменные имеют более высокий приоритет внутри их функции, чем глобальные. После вызова функции demo() в строке 13 снова выводятся значения ›‹ и у. Управление находится уже не у функции demo( ), поэтому выводятся прежние глобальные значения. Как видно, локальные переменные ›‹ и у абсолютно независимы от глобальных перемен- ных x H y, объявленных за пределами функции. Использование переменных в функциях рег- ламентируется следующими тремя правилами.  I Чтобы использовать переменную внутри функции, ее необходимо объявить в заголов- ке или теле функции (за исключением глобальных переменных, которые рассматрива- ются на занятии 12).  I Чтобы функция могла получить значение из вызывающей программы, это значение следует передать как аргумент.  I Чтобы вызывающая программа могла получить значение из функции, его необходимо вернуть явным образом из функции.  Строго говоря, эти “правила” не абсолютны. Позже вы узнаете, как их обойти. А пока следуйте этим правилам, H тогда неприятности вам не грозят. Тот факт, что переменные внутри функции отделены от других переменных програм- мы,—— это одно из проявлений независимости функций. Функции могут выполнять любые мыслимые операции над данными с использованием своего собственного набора локальных переменных. Не следует беспокоиться, что эти операции окажут нежелательное влияние на другие части программы.  День 5-й. Функции 113 
Операторы функции  Не существует практически никаких ограничений на операторы, которые могут содер— жаться в теле функции. Единственное, чего нельзя делать внутри функции —-— это определять другую функцию. Однако можно использовать любые другие операторы С, в том числе циклы (они изучаются на занятии 6, посвященном управлению программой), условные операторы if, операторы присваивания. Можно вызывать библиотечные и свои собственные функции. А как насчет длины функции? Язык С не накладывает никаких ограничений на длину функций, но на практике лучше писать сравнительно короткие функции. Помните, что с точ— ки зрения структурного программирования кажд функция должна выполнять относительно простую задачу. Если обнаружится, что та или ин функция разрослась до огромных разме— ров, то, возможно, вы пытаетесь заставить ее делать слишком много. Вероятно, такую функ- цию можно разбить на две или большее количество мелких функций. Что же такое слишком длинная функция? Однозначного ответа на этот вопрос дать нель— зя. Все же в практике программирования редко встречаются функции длиннее 25—30 строк фактического кода (не считая пустые строки и комментарии). Здесь следует полагаться на собственное суждение. Некоторые задачи требуют довольно длинных функций, а некото- рые— всего лишь нескольких строк. По мере накопления опыта программирования у вас появится более определенное понятие о том, насколько длинные или короткие функции сле- дует использовать.  Возвращение значения  Для возвращения значения из функции используется оператор return, 3a которым следует выражение языка С. Когда выполнение программы подходит к оператору return, это выра— жение вычисляется и передается в вызывающую программу одновременно с передачей в нее управления. Итак, возвращаемое значение как раз и есть значение того самого выражения по- сле return. Рассмотрим такую функцию:  int func1(int var)  { int x; /* Здесь идет код функции... */ return x; }  с  При вызове этой функции ее тело выполняется до оператора return. Ha этом операторе выполнение заканчивается, и значение x возвращается в вызывающую программу. Выраже- ние после ключевого слова return может быть любым синтаксически правильным выраже— нием С. В функции может быть и несколько операторов return. Фактически выполняется только один из них, до которого быстрее всего дошло выполнение. Несколько операторов return могут эффективно применяться для возвращения из функции нескольких различных значе- ний, как это показано в листинге 5.4.  Листинг 5.4. returns . с — использование нескольких операторов return в функции  1: /* Демонстрация нескольких операторов return B функции. */   2 З: #include <stdio.h> 4  114 Неделя 1. Основные вопросы 
5: int х, у, z; 6: 7: int larger_of( int a, int b); 8: 9: int main( void ) 10: { 11: puts("Enter two different integer values: "); 12: scanf("%d%d", &х, &у); 13: 14: z = larger_of(x,y); 15: , 16: printf("\nThe larger value is %d.", z); 17: 18: return 0; 19: } 20: 21: int larger_of( int a, int b) 22: { 23: if (a > b) 24: return a; 25: else 26: return b; 27: }   , Enter two different integer values: Рншштшп 200 300 The larger value is 300. Enter two different integer values: 300 200  Peaunhmam  The larger value is 300.   Анализ Как и в других примерах, листинг 5.4 начинается с комментария, описывающего назначение программы (строка 1). Заголовочный файл stdio.h включается для  того, чтобы иметь возможность пользоваться стандартными функциями ввода—вывода, кото- рые вводят данные пользователя с клавиатуры и выводят информацию на экран. В строке 7 находится прототип функции larger_of ( ). Обратите внимание, что эта функция принимает два аргумента типа int и возвращает значение того же типа. В строке 14 функция larger_of ( ) вызывается с двумя аргументами ›‹ и у. Она содержит два оператора return. C помощью оператора if B строке 23 функция проверяет, является ли a большим, чем Ь. Если это так, в строке 24 выполняется оператор return, и функция заканчивает выполнение. В этом случае строки 25 и 26 игнорируются. Если a не больше, чем Ь, то пропускаегся стро- ка 24 и выполняется блок else, т.е. оператор return B строке 26. Убедитесь, что в зависимо— сти от переданных в функцию larger_of () аргументов будет выполнен либо первый, либо второй оператор return, и соответствующее значение будет передано назад в программу. И еще одно замечание касательно этой программы. В строке 11 используется новая функ- ция, которая ранее не рассматривалась: puts (сокращение от put string — “вывести строку”).  День 5-й. Функции 115 
Эта функция просто выводит строку на стандартное устройство вывода —— обычно на экран. Строки рассматриваются более подробно на занятии 10, специально посвященном обработке символьной и строковой информации. Пока же считайте строки просто текстом, взятым B двойные кавычки. Помните, что тип возвращаемого функцией значения объявлен B прототипе и заголовке функции. Фактически возвращаемое значение должно быть того же типа, который объявлен, иначе компилятор сообщит об ошибке.   — В структурном программировании принято, чтобы функция имела одну “FIRE“!!! точку входа и одну — выхода. Эйсзначает, что следует стараться обой-  тись одним оператором return B функции. Однако иногда код может стать более удобочитаемым и эффективным именно при наличии нескольких return. 8 каждом таком случае заботьтесь прежде всего об удобстве и эффективности.     Прототип функции  Программа должна содержать прототипы всех используемых ею функций. Вы видели пример прототипа функции в строке 4 листинга 5.1, и B других листингахтоже встречались подобные прототипы. Что же такое прототип функции и зачем он нужен? Из рассмотренных примеров можно было заметить, что прототип совпадает с заголовком функции, только дополнительно имеет точку с запятой на конце. Как и заголовок, прототип функции содержит информацию о типе возвращаемого значения, имени и параметрах функ— ции. Задача прототипа -— описать функцию компилятору. Зная имя, параметры и тип возвра- щаемого значения, компилятор всякий раз проверяет, встретив вызов функции в программе, правильно ли переданы аргументы и правильно ли использовано возвращенное значение. Ес- ли найдено несоответствие, компилятор сообщает об ошибке. Строго говоря, прототип не обязан совпадать с заголовком функции до мелочей. Имена параметров могут и отличаться -— лишь бы совпадали их типы, количество и порядок следо— вания. Однако нет причин делать заголовок и прототип различными -— если они одинаковы, текст программы становится легче как читать, так и писать. Написав функцию, воспользуй— тесь средствами копирования-вставки в текстовом редакторе, чтобы скопировать заголовок и создать прототип. Не забудьте добавить точку с запятой в конец прототипа. Где B исходном коде следует помещать прототипы функций? Обязательно до начала пер— вой функции. Для удобства чтения лучше сгруппировать все прототипы вместе.  Рекомендуется Не рекомендуется     Используйте только локальные пере— ; Не пытайтесь вернуть из функции знач g _ менные всегда, когда это возможно f f чение не того типа, который объявлен ` % ‚*Ограничивайте обязанности одной ( ЁНе делайте функции слишкбм длинны- % А’функции одной отдельной задачей g Ё ми. Если функция разрастается попы— % 5:1… , … ‚3,5 … ‚„ * тайтесь разделить ее на несколЬко % ^ ` :  на} t д , › меньших функций xi: „сад… * *… с, ‚… Не используйте несколько операторов; : , , д : return без особой необходимости. Ста-Ё  . „ . ‚` „ , & райтесь всегда ограничиваться одним “ … вс“ j ‘ ^ ” … z return. Тем не менее,“иногда примене- * fez gig?” a» ; … : * ;“ * g ние нескольких таких операторов делает 3:5;C °“; , * , ‘ : і код более понятным и удобным. =  „ ^ \ « Еды—‚,…… и,… мы. —м№ми"&_\н .. . ты…». ‹ „ммм…—ц-‘ . ттт-«№ „., „_ „…. ‚“им.—..:…» ...—‚о… … „…… › тм ‚ ‚ „ „мо—……-  №№„ ›    116 Неделя 1. Основные вопросы 
Передача аргументов в функцию  Для передачи аргументов в функцию их пишут в круглых скобках после имени функции. Количество аргументов и их типы должны совпадать со списком параметров в прототипе и заголовке функции. Например, если объявлено, что функция принимает два аргумента типа int, то в нее следует передавать именно два аргумента типа int —— не больше и не меньше, и никаких других типов. Если попытаться передать в функцию аргументы других типов или в другом количестве, то компилятор это заметит, зная прототип функции. Если функция принимает несколько аргументов, то перечисленные в вызове значения ар- гументов присваиваются параметрам в порялке очередности: первый аргумент— первому параметру, второй аргумент — второму параметру и т.д., как показано на рис. 5.5.  Вызов функции funcl (a, b, c);  m  Заголовок функции void func1(int х, int y, int z)  Puc. 5.5. Соответствие между аргументами и па— раметрами функции в порядке очередности  Каждый из аргументов может быть любым допустимым выражением на языке С: констан- той, переменной, арифметическим или логическим выражением и даже вызовом другой функции (если она возвращает какое-то значение). Пусть, например, half(), square(), third( ) —- 3T0 функции, вычисляющие и возвращающие определенные значения. Тогда в программе можно записать так: х = half(third(square(half(y))));  Вначале вызывается функция half( ), и в нее передается аргумент у. Когда выполнение возвращается из half( ), программа вызывает функцию square() и передает в нее возвра- щенное из half( ) значение как аргумент. Далее вызывается функция third( ) co значением, возвращенным из square(), B качестве аргумента. Наконец, снова вызывается функция half( ), Ha сей раз со значением, полученным из third( ), B виде аргумента. Окончательно вычисленное функцией half() значение присваивается переменной x. Все это эквивалентно следующему фрагменту кода:  a = half(y); b = square(a); с = third(b); x = half(c);  BbI3OB функций  Существует два способа вызова функций. Во-первых, любую функцию можно вызвать, просто назвав ее по имени со списком аргументов. Получится такой оператор, как в следую- щем примере. Функция выполняется, а возвращаемое значение просто игнорируется. wait(12); Вторым способом можно вызвать только функции, возвращающие значение непустого типа. Поскольку вызов функции по имени дает некоторое значение (а именно возвращаемое ею зна- чение), такой вызов относится к допустимым выражениям языка С, и его можно использовать  День 5—й. Функции 117 
везде, где только разрешены выражения. Вы уже видели пример выражения с возвращаемым значением в правой части оператора присваивания. Приведем еще несколько примеров. В следующем примере вызов half_of( ) сам является аргументом функции: printf("Half of за is за.", х, half_of(x));  Вначале вызывается функция half_of ( ) с аргументом х, а затем — функция printf с ар— гументами "Half of %d is %d.", х Hhalf_of(x).  Bo втором примере в выражении встречается сразу несколько вызовов функций: у = half_of(x) + half_of(z); Хотя здесь дважды используется одна и та же функция half_of( ), второй могла быть вы- звана какая угодно другая функция. Следующие несколько операторов эквивалентны одному предыдущему: ‚‹— = half of(x); half—of(z); a + Б ; Наконец, следующие два примера демонстрируют эффективные способы использования возвращаемых функциями значений. Здесь вызов функции используется в операторе if: if ( half_of(x) > 10 ) {  a b Y  /* здесь могут стоять любые операторы! */  Если возвращаемое функцией (здесь это half_of) значения удовлетворяют некоторому критерию (в данном случае, больше 10), то выражение в условном операторе истинно, и вы- полняются операторы в блоке. Если возвращаемое значение не удовлетворяет критерию, эти операторы не выполняются. Следующий пример даже лучше предыдущего: if ( do_a_process() != OKAY ) {  /* операторы обработки ошибки */  Здесь также не приведены конкретные операторы, да и функция do_a_process( ) —— всего лишь абстракция. Тем не менее этот пример очень важен. Возвращаемое функцией значение проверяется для того, чтобы выяснить, сработала ли функция правильно. Если нет, операторы обработки ошибки выполнят необходимые действия по исправлению ситуации. Этот прием обычно используется при обращении к файлам данных, сравнении значений, выделении памяти.   """"" Если попытаться использовать в выражении функцию, возвращающую  значение типа void (то есть ничего не возвращающую). то компилятор со— общит об ошибке.  Рекомендуется Не рекомендуется        Выбирайте самый естественный поря-` ‚ Не делайте операторы запутанными‚ док`передачи параметров в функцию„ Ё’помещая в них множество вызовов чтобы сделать ее удобной для много— i = функций. Операторы должны содержать ‚кратного использования. ’ * Ё фУнкции только в том случае, если это Пользуйтесь преимуществом, которое Ё % делает код более удобочитаемым.  дает: право использования вызовов % : функций в выражениях.  …. LAWN.“ .…    118 Неделя 1. Основные вопросы 
Рекурсивный вызов  НПВЫЙ lfiflllMllH Термин рекурсивный вызов (или рекурсия) относится к ситуации, когда функция вызывает сама себя явным или неявным образом. Косвенный рекурсивный вызов  означает, что одна функция вызывает другую, а та затем вызывает первую. В языке С рекур- сивные вызовы вполне допустимы и даже могут оказаться полезными в некоторых случаях. Например, с помощью рекурсии можно вычислить факториал числа. Факториал числа х обозначается х! и вычисляется следующим образом:  х! = х * (х—1) * (х—2) * (х—З) * ... * (2) * 1 А можно записать это и так: х! = х * (х—1)! Сделав еще шаг, можно вычислить и ( х-1) ! аналогичным образом: `(х-1)! = (х-1) * (х—2)! Рекурсивное вычисление продолжается до тех пор, пока мы не дойдем до единицы и не закончим на этом. Программа в листинге 5.5 использует рекурсивную функцию для вычисле- ния факториала. Поскольку в программе используются числа типа unsigned, исходные дан-  ные не должны превосходить числа 8. Факториал числа 9 (и больших значений) выходит за ‹пределы диапазона целых чисел этого типа.  `Листинг 5.5. recurse. с — вычисление факториала c помощью рекурсии   1: /* демонстрация рекурсивного вызова. */ HZ: /* Вычисление факториала числа. */ 3: 4: #include <stdio.h> 5: 6: unsigned int f, x; 7: unsigned int factorial(unsigned int a); 8: 7 9: int main( void ) 10: { 11: puts("Enter an integer value between 1 and 8: "); 12: scanf("%d", &x); 13: 14: if( х > 8 || х < 1) (15: { 16: printf("0nly values from 1 to 8 are acceptable!"); „$17: } 18: else 19: { 20: f = factorial(x); 21: printf(“%u factorial equals %u\n", х, f); 22: } 23: 24: return 0; 25: } 26: 27: unsigned int factorial(unsigned int а) 28: {  День 5-й. Функции 119 
29: if (a == 1) 30: return 1; 31: else 32: { 33: a *= factorial(a-l); 34: return a; 35: } 36: }   Enter an integer value between 1 and 8: 6 “\  6 factorial equals 720  A Первая половина программы очень похожа на многие предыдущие примеры. Она начинается с комментариев в строках 1 и 2. В строке 4 включается заголо- вочный файл, содержащий прототипы функций ввода-вывода. В строке 6 объявляются две переменных типа unsigned int. B строке 7 находится прототип функции вычисления факто- риала. Эта функция принимает и возвращает значения типа unsigned int. Строки 9—25 со- держат функцию таіп( ). B строках 1 1 и 12 пользователя приглашают ввести число от 1 до 8, а затем это число вводится. * В строках 14—22 расположен интересный оператор if. Поскольку программа не сможет обработать значение больше 8, этот условный оператор проверяет введенное число на допус- тимость. Если оно больше 8, выв0дится сообщение об ошибке. В противном случае в стро- ке 20 вычисляется факториал, и его значение выводится в строке 21. Всякий раз, когда вы знаете, где в программе может возникнуть проблема (например, с диапазоном значений), до- бавьте в программу операторы, которые обнаружат и предотвратят эту проблему. Рекурсивная функция factorial( ) находится в строках 27—36. Передаваемое в нее значе- ние присваивается параметру а. B строке 29 это значение проверяется. Если оно равно 1, функция также возвращает значение 1. Если же оно не равно 1, то его устанавливают равным себе самому, умноженному на factorial(a—l ). Потом снова вызывается функция факториа- ла, на этот раз с параметром а—1 в качестве a. Если и а-1 не равно 1, то функция factorial( ) вызывается снова, уже с параметром ( (а—1 )—1 ), т.е. (а—2 ). Этот процесс пр0должается до тех пор, пока условие в строке 29 не станет истинным. Например, если аргумент равен 3, то фак- ториал будет равен следующему: 3 * (3—1) * ((3—1)—1)  Рекомендуется Не рекомендуется  Овладейте всеми тоніюстями работы с Не пользуйтесь рекурсивным вызоізом, Ёрекурсивными вызовами, прежде чем если итераций, т…е повторений кода. на  Резцлыиш     1 i і Ё  ;}.использовать их в распространяемых , верняка будет нескольКо. Рекурсивные; вами программах. , гвызовы требуют много ресурсов по-` ,; ‹… , , , , „дж . … * ,скольку функции необходимо помнить, '  где она находится  к ‚ „ _ …- h... _. … ‚‚_—. ‚‹. ».… ‚...… …ты ‚ .… „- …»…    \.\ м _. _. {мд  120 Неделя 1. Основные вопросы 
Местонахождение функций  Может возникнуть вопрос, где же в исхоцном коде должны находиться определения функций. Пока что располагайте их в том же файле исходного кода, что и функцию main( ), "вслед за концом этой функции. На рис. 5.6 показана примерная структура программы, в кото- Ёрой используются функции.  _ void funcl(int х, int y, int 2); Прототипы ФУНКЦИИ—Ч int func2(double a, double b);  int main(void)  {  funcl(p, q, r); Вызовы функций————-—› . . . z = func2(al, bl);  void funcl(int х, int y, int 2)  {  }  On e еления к й [зд ¢w1uM int func2(double а, double b)  { }  Рис. 5.6. Расположение прототипов и определений функций в исходном коде  Нестандартные функции, написанные автором программы, могут храниться в обособлен- you файле исходного кода отдельно от main( ). Это бывает полезно в больших программах и в тех случаях, когда один и тот же набор функций используется сразу в нескольких разных №ограммах. Эти вопросы обсуждаются на занятии 21, посвященном дополнительным воз- іюжностям компиляторов С.  Встраиваемые функции  В языке С можно создавать функции особого типа. Они называются встраиваемыми (inline). Более старые версии языка могут и не поддерживать встраиваемые функции, однако все новые компиляторы, совместимые со стандартом С-99, их поддерживают. Обычно встраиваемые функции невелики по размеру. Компилятор пытается выполнить их Самым быстрым возможным способом. Это можно сделать путем непосредственного копиро- ‘вания кода функции в ту функцию, которая ее вызывает. Сам термин “встраиваемые” возник  День 5-й. Функции 121 
оттого, что вместо вызова функции выполняется ее прямая вставка (встраивание) в тело вы- зывающей функции. Функцию можно сделать встраиваемой, добавив к ее объявлению ключевое слово іп1іпе. B следующем примере объявляется встраиваемая функция toInches:  inline int toInches( int Feet )  { return (Feet/12);  Всюду, где используется функция toInches( ), компилятор постарается максимально оп- тимизировать ее выполнение. В связи с этим следует знать, что код вызываемой функции скорее всего, но не обязательно будет подставлен в вызывающую функцию. Единственное, что компилятор гарантирует, — это то, что он приложит все усил я для оптимизации выпол- нения функции. Вызов встраиваемых функций ничем не отличаетЁя от вызова любых других функций.  Резюме  На этом занятии рассматривались функции -—— важная часть программирования на С. Функции представляют собой независимые фрагменты кода, выполняющие свои специализи- рованные задачи. Когда программе нужно выполнить ту или иную задачу, она вызывает соот- ветствующую функцию. Умение пользоваться функциями необходимо для структурного про- граммирования —— метода разработки программ, в котором центральное место занимает мо- дульный подход, разработка сверху вниз. С помощью структурного программирования создаются эффективные программы и облегчается труд программиста. Функция всегда состоит из заголовка и тела. Заголовок содержит информацию о типе воз- вращаемого функцией значения, ее имени и параметрах. Тело содержит объявления локаль— ных переменных и операторы С, выполняемые при вызове функции. Локальные переменные полностью независимы от всех остальных переменных программы, объявленных в других местах.  Вопросы и ответы  Что делать, если необходимо возвратить из функции несколько значений? Довольно часто может возникнуть такая потребность -—— возвратить из функции несколько значений. В более общем случае бывает необходимо передать в функцию какое—либо значе- ние, изменить его там и сохранить изменение после выхода из функции. Как это сделать, изу- чается на занятии 18, посвященном дополнительным возможностям функций.  Какое имя функции может считаться хорошим? Хорошее имя описывает как можно конкретнее, что именно делает данная функция.  Если переменные объявляются вне функций, то они глобальные и могут использо— ваться везде, а если в функциях, то локальные и могут использоваться только в своих собственных функциях. Почему бы не сделать все переменные глобальными? Область действия переменных рассматривается более подробно на занятии 12. Там и бу- дет рассказано, каковы преимущесгва объявления переменных локальными, а не глобальными.  Где используется рекурсивный вызов?  122 Неделя 1. Основные вопросы 
Один из самых фундаментальных примеров рекурсивного вызова — это вычисление фак- ториала. Эта функция используется во многих математических и статистических расчетах. Рекурсия представляет собой просто цикл, хотя и отличается от обычных циклов. При рекур- сивном вызове каждый раз создается новый набор переменных, а в циклах это не так. Об этом будет сказано подробнее на следующем занятии.  Должна ли функция шаіп( ) быть первой функцией в программе? Совсем не обязательно. Стандарт языка С гласит, что функция main() выполняется пер- вой, а вот находиться она может где угодно в файле исходного кода. Обычно для удобства _ поиска ее располагают или в начале, или в конце файла.  Что такое функции-члены? Функции-члены, или методы, — это специальные функции, используемые в таких объект- но-ориентированных языках, как С#, С++ и Java. Они являются частью классов — структур особого вида, применяемых в объектно-ориентированных языках. На дополнительных заня- тиях мы изучим функции-члены более подробно.  Коллоквиум  Этот коллоквиум содержит контрольные вопросы для повторения пройденного и упраж— ‘нения для приобретения практических навыков.  Контрольные вопросы  Нужно ли применять структурный подход при написании программ на С?  Каковы принципы структурного программирования? Как связаны функции С и структурное программирование?  Какой должна быть первая строка определения функции, и какую информацию она со- держит?  Сколько значений может возвращать функция?  \ркт‘тш у…“ ""“  Если функция не возвращает ничего, каким должен быть тип возвращаемого значения?  тщ* Фа“  В чем разница между прототипом и определением функции?  90°  Что такое локальная переменная? В чем особенности локальных переменных?  , 0. Где в исходном коде должна находиться функция main( )?  „… .. мнет .  ;Уп ражне H ия 1. Напишите заголовок функции под именем do_it( ), которая принимает три аргумента ти— па char и возвращает значение типа float.  _2. Напишите заголовок функции под именем print_a_number( ), которая принимает один аргумент типа int и не возвращает никакого значения.  3. Значения каких типов возвращают следующие функции? с int print_error( float err_nbr );  0 long read_record( int rec_nbr, int size );  <День 5-й. Функции 123 
9.  Поиск ошибок. Найдите ошибку в следующей программе:  #include <stdio.h> void print_msg(void ); int main(void )  { print_msg( "This is a message to print" ); return 0; } void print_msg(void ) {  puts( "This is a message to print" ); return 0; }  \  Поиск ошибок. Найдите ошибку в следующем определении функции:  int twice(int y);  {  return (2 * y);  }  Перепишите листинг 5.4 так, чтобы в функции larger_of() был только один оператор  return.  Напишите функцию, принимающую два числа в качестве аргументов и возвращающую  значение ИХ ПРОИЗВЕДЕНИЯ.  Напишите функцию, принимающую два числа в качестве аргументов. Функция должна делить первое число на второе. Не делите на второе число, если оно равно нулю.  (Подсказка: воспользуйтесь оператором if.)  Напишите функцию с операторами вызова функций из упражнений 7 и 8.  10. Напишите программу, которая использует функцию для нахождения среднего арифмети-  ческого пяти чисел типа float, вводимых пользователем.  11. Напишите рекурсивную функцию для возведения числа 3 в степень, равную некоторому другому числу. Например, если передается аргумент 4, функция должна возвратить 81.  124  Неделя 1. Основные вопросы 
    .;Ци кл ы  На занятии 4, посвященном операторам и выражениям языка С, был рассмотрен условный литератор if, с помощью которого можно до некоторой степени управлять процессом выпол— :нрния программы. Однако часто возникает необходимость в еще более гибком управлении дом выполнения программы, чем простое разветвление на основе значений ИСТИНА—ПОЖЬ. этом занятии изучаются три новых способа управления программой. Будут рассмотрены Флешющие вопросы.  \  &‘Ьі I Применение простых массивов  I Использование циклов for, while и do. . .while для многократного выполнения опера- торов  I Вложенные управляющие операторы  Мы не собираемся исчерпать эти темы за одно занятие. Предлагаемая информация скорее ``„і`1`ослужит тому, чтобы вы смогли написать свои первые настоящие программы. Более поц- {ъ-робно тематика этого урока рассматривается также на занятии 13.  ‚ ‘Ё’А …,  ЁЁМассивы: основные сведения  'ПреЖДе чем перейти к оператору for, полезно узнать кое-что о массивах. (Полностью эта тема рассматривается на занятии 8.) Массивы и использование оператора for тесно связаны в "языке С, поэтому трудно объяснить Одно без другого. Чтобы помочь вам сориентироваться в `) фльнейших примерах с оператором for, мы дадим здесь самое общее понятие о массивах.  Массив —— это группа пронумерованных (индексированных) однотипных эле- ментов данных под единым общим именем. Различают отцельные элементы мас— ma no индексу — числу, стоящему после имени массива в квадратных скобках. (Мы рас- `’емсэтрим это дальше на примерах.) Как и другие переменные в С, массивы необходимо объ— mars. Объявление массива включает как тип его элементов, так и общий размер массива {количество элементов в массиве). Например, следующий оператор объявляет массив под щенем data, содержащий 1000 элементов типа int:  int data[lOOO]; Обращение к отдельным элементам массива производится по индексу: от data[O] до =Ёда11а[999]. Первым элементом массива является data[O], a не data[ 1]. B других языках про—  “ вып термин  
граммирования, например BASIC, первый элемент массива может иметь номер 1, но в С это не так. В языке С первый элемент массива всегда имеет номер O.   ~ Индекс можно рассматривать как смещение элемента от начала массива. llllllfllllll Первый элемент, естественно, не имеет смещения, т.е. имеет смещение ` 0. Второй элемент смещен на единицу от начала массива, поэтому его номер1.     Каждый отдельно взятый элемент данного массива — точно такая же целочисленная пе- ременная, как и обычные переменные типа int, и может использоваться точно таким же 06- разом. Индекс массива тоже может быть переменной, как показано в следующем примере:  int data [10001; int index; index = 100; \\\ data[index] = 12; /* Эквивалентно data[100] = 12; */  Итак, мы рассмотрели краткое введение в массивы. Его должно быть достаточно для по- нимания дальнейших примеров программ этого занятия. Если вам пока не ясны все детали работы с массивами, не беспокойтесь. Более подробно массивы рассматриваются на заня— тии 8.    Рекомендуется Не рекомендуется  „№0  ;_Помните, что в языке Ономера элеменч' ‘, ! ;Не объявляитеёмассивы большей дли- тов массивбв всегда “начинаются с нуля, ”ны. :`чем это неОбХОДимо іэто напрас- a "e ° единицы ‹ : №№ „№№!!! Ёжая Тратакресуроов паМяти *!*: … № ……“ к.… ‚«Ди? 7 ‘ &‘ \"  212‘^ w       ,….мм мм.-нм Ммм… Hm WA ...… …. «.4. щим—№  Управление выполнением программы  По умолчанию программа на С выполняется в порядке следования операторов. Выполне— ние начинается с начала функции main( ) и продолжается оператор за оператором, пока функция шаіп( ) не закончится. Однако столь строгий и последовательный порядок выполне— ния программ встречается крайне редко. В языке С есть целый ряд управляющих операторов, с помощью которых можно изменить порядок выполнения программы. Один из таких опера— торов, позволяющий принимать решения и разветвлять выполнение, —— это изученный ранее условный оператор if. Здесь мы рассмотрим еще три полезных управляющих оператора.  I Оператор for I Оператор while I Оператор do. . .while  Оператор for  Оператор for —— это синтаксическая конструкция С, выполняющая блок из одного или не- скольких операторов определенное число раз. Его еще называют циклом for из-за его повто- ряющегося, циклического выполнения. Ранее в примерах программ цикл for уже встречался несколько раз. Теперь рассмотрим подробнее‚ как он работает. Оператор for имеет следующее строение:  126 Неделя 1. Основные вопросы 
$? gar ( инициализация; условие; приращение )  ., fin ‹  опера тор;  Три его элемента —— инициализация, условие и приращение -—- представляют собой выра—  ")!Ёения С, а оператор может быть как простым (Одиночным), так и составным (блоком). Как только в программе встречается оператор for, выполняются следующие действия:  1.  _ ‚2.  ’35  ‘4.  his.  Вычисляется выражение инициализация. Обычно это оператор присваивания, который ус- танавливает значение некоторой переменной.  Вычисляется выражение условие. Как правило, это логическое выражение, выражающее отношения между величинами.  Если условие ложно (т.е. равно нулю), выполнение оператора for прекращается, и управ— ление передается непосредственно следующему за for оператору.  Если условие истинно (т.е. не равно нулю), выполняется оператор в цикле. Это может быть и составной оператор.  Вычисляется выражение приращение, и выполнение снова переходит к этапу 2.  На рис. 6.1 показано, как работает оператор for. Обратите внимание, что оператор не вы-  ПОЛНЯСТСЯ ни разу, если условие ЯВЛЯС’ГСЯ ложным при Первом же его вычислении.  "гум’ "    Вычислить  Инициализация приращение  for (иншшадшзаштя; условие; приращение) операторы   ИСТИНА   Выполнить операторы  Вычислить условие   Рис. 6.1. Схематическое представление оператора for  Листинг 6.1 представляет несложный пример использования оператора for для отображе—  `ния чисел от 1 до 20. Можно убедиться, что благодаря циклу текст программы намного ком- `-пактнее‚ чем если бы функция printf( ) вызывалась отдельно для каждого числа.  —`  Листинг 6. 1 . forstate. c — простой пример оператора for  oi  \тч— ` ‘}іі дг‘ъ' ` i Ё , О О » ‚ с 4 А Х , \7. 9‘1 ‘ :  .. ‹  /* демонстрация простого оператора for */  #include <stdio.h>  ,Ёень 6-й. Циклы 127 
int count;  ooumm  : int main( void )   { 9 /* Вывести числа от 1 до 20 */ 10: 11: for (count = 1; count <= 20; count++) 12: printf(“%d\n", count); 13: 14: return 0; 15: } ‚ 1 _"` 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  Рис. 6.2 иллюстрирует работу цикла for B листинге 6.1.  В строке 3 включается заголовочный файл стандартных средств ввода-вывода. В строке 5 объявляется переменная типа int под именем count,‘ которая затем ис— пользуется в цикле for. Строки 11 и 12 содержат сам цикл for. Как только программа дохо- дит до цикла, первым выполняется оператор инициализации. В этой программе оператор инициализации — это count = 1. Переменная count инициализируется с тем, чтобы исполь- зоваться в цикле. Далее вычисляется значение условия count <= 20. Значение count только что было сделано равным 1, поэтому оно очевидно меньше 20, и выполняется оператор внут- ри for, т.е. printf( ). После вывода очередного числа вычисляется третье выражение цикла, соппс++.1Теременная count увеличивается на единицу и становится равной 2.11рограмвп1 возвращается к началу цикла и снова проверяет условие. Если оно справедливо (TRUE), то снова выполняется оператор printf ( ), затем count получает приращение и становится рав- ным 3, снова проверяется условие. Этот цикл продолжается до тех пор, пока условие не при- обретает значение FALSE. После этого цикл завершается, и выполнение программы переходит к следующей строке (строке 14). которая возвращает значение 0 перед окончанием работы программы.  диализ  128 Неделя 1. Основные вопросы 
…% Vt }  `*‘ «v {і ‚$$$     Увеличить count на 1  Присвоить 1 счетчику count    for (count = 1; count <= 20; count++) printf) ( "\n%d", count);    Выполнить printf)   Puc. 6.2. Pa60ma цикла for 6 листинге 6.1  Как видно из предыдущего примера, оператор for часто используется для отсчета прого- вов цикла путем увеличения переменной—счетчика. Можно использовать его и для обратного отсчета, уменьшая (а не увеличивая) счетчик: ;{іог (count = 100; count > 0; count--) При этом не обязательно отсчитывать по единице — вполне допустимо и другое прира— щение, например, 5 в следующем примере: ЁЁ: (count = 0; count < 1000; count +=5)  Оператор for обладает большой гибкостью в применении. Например, можно опустить оператор инициализации, если переменная цикла уже была инициализирована ранее где-то в программе. Тем не менее, точку с запятой нужно оставить на месте. 990111: = 1; ‚Бог ( ; count < 1000; count++) Оператор инициализации в действительности не обязан выполнять какую бы то ни было инициализацию. Он может представлять собой любое выражение С. Каким бы он ни был, он выполняется ровно один раз, как только в программе встречается оператор for. Например, в ;шедующем примере выводится сообщение Now sorting the array. . .: épunt =1; for (printf(“Now sorting the array...“); count < 1000; count++) /* Здесь идут операторы сортировки */ Можно также опустить и оператор приращения в заголовке цикла for, выполняя соответ- ствующую операцию где-нибудь в теле цикла. И снова точка с запятой должна остаться на своем месте. Например, для вывода чисел от 0 до 99 можно записать следующее: for (count = 0; count < 100; ) ` printf("%d“, count++); Условное выражение, отвечающее за окончание цикла for, может быть абсолютно любым виражением С. Пока его значение равно TRUE (He ноль), оператор for продолжает выпол—  *&нь 6—й. Циклы 729 
няться. С помощью логических операций С можно строить сложные условные выражения для циклов. Например, следующий оператор for выводит на печать элементы массива псд именем array[ ] и останавливается тогда, когда выведены все элементы или в массиве всгре- тилсяэлемент,равНЬЦЗнулкх for (count = 0; count < 1000 && array[count] != 0; count++) printf(“%d“, array[count]);  Этот цикл можно еще упростить, записав его следующим образом. (Если вам непонятны изменения в условном выражении цикла, повторите материал занятия 4.) for (count = 0; count < 1000 && array[count]; ) printf(“%d", array[count++]);  После оператора for может стоять и пустой оператор — вся работа цикла будет выпол— няться прямо в его заголовке. Вспомните, что пустой оператор -—— это одна точка с запятой. Например, для присвоения всем элементам массива длиной 1000 значения 50 можно записать следующее: for (count = 0; count < 1000; array[count++] = 50)  I  B этом операторе присваивание значения 50 каждому элементу массива выполняется в третьей части заголовка for, отвечающей за приращение. Лучше записать этот оператор вот так:  for (count = 0; count < 1000; array[count++] = 50) {  Помещение точки с запятой в блок (между двумя фигурными скобками) наглядно демон- стрирует, что B теле цикла не выполняются никакие операции. На занятии 4 упоминалось, что запятая как оператор С чаще всего применяется B циклах for. Например, можно создать выражение, разделив два входящих в него выражения запятой. Вначале вычисляются два внутренних выражения в последовательности слева направо, а за- тем все выражение получает значение своего правого внутреннего выражения. С помощью запятой можно добиться того, чтобы каждая часть оператора for выполняла сразу несколько операций. Пусть у нас есть два массива по 1000 элементов в каждом, а[ ] и И ]. Нужно скопировать содержимое массива а[ ] B b[ ] B обратном порядке, чтобы после копирования b[ 0] = a[999], b[1] = a[ 998] и т.д. Это делается следующим оператором for: for (i = 0, j = 999; i < 1000; i++, j--) b[ j] = a[i]; C помощью запятой инициализируются сразу две переменные і и j. Таким же образом производится и изменение этих двух переменных в циюю.  ЁЁ; Оператор for  for (инициализация; условие; приращение) оператор( Ы )   Блок инициализация может быть любым допустимым выражением С. Обычно это оператор присваивания, инициализирующий некоторую переменную.  130 Неделя 1. Основные вопросы 
Блок условие может быть любым допустимым выражением С. Обычно это условное выражение. Как только условие принимает значение 0 (FALSE), оператор for прекра- щает работу, и управление передается первому же оператору, следующему за блоком оператор(ы). Пока условие истинно (не равно 0), выполняется блок оператор(ы ). Блок приращение также может быть любым допустимым выражением С. Обычно в нем выполняется изменение переменной, инициализированной в первой части. Блок оператор (ы) содержит один или несколько операторов С, которые выполняются, пока условие цикла остается истинным. Итак, оператор for —— это оператор цикла. Он может включать в себя блок инициали- зации, условие продолжения, блок приращения. Вначале выполняется блок инициали- зации. Затем проверяется условие. Если условие равно TRUE, выполняется блок опера— торов. После его завершения вычисляется выражение в блоке приращения. Затем сно- ва проверяется условие. Цикл продолжается до тех пор, пока условие не станет равным FALSE.  Пример1  /* Вывод значения x при его изменении от О до 9 */ int x; for (x = O; x < 10; x++) printf("\nThe value of х is %d", x );  Пример2  /* Ввод чисел с клавиатуры, пока не встретится 99 */ int nbr = O; for ( ; nbr != 99; ) scanf( "%d", &nbr );  ПримерЗ  /* Пользователь вводит до 10 целых чисел. */ /* Числа помещаются в массив value. Как только */ /* вводится число 99, цикл прекращает работу */ int value[lO]; int ctr, nbr = O; for (ctr = O; ctr < 10 && nbr != 99; ctr++)  { puts( "Enter a number, 99 to quit " ); scanf("%d", &nbr); value[ctr] = nbr; .}  Вложенные операторы for  Оператор for можно выполнять внутри другого оператора for. Такой оператор называет- ё'ся вложенным. (На занятии 4 уже рассматривались вложенные операторы if.) C помощью изложенных операторов for можно программировать довольно сложные задачи. Листинг 6.2 представляет не особенно сложную программу, которая тем не менее иллюстрирует приме- нение вложенных операторов for.  День 6-й. Циклы 131 
Листинг 6.2. nestfor.c — вложенные операторы for   l: /* демонстрация двух вложенных операторов for */ 2: 3: #include <stdio.h> 4: 5: void draw;box( int, int); 6: 7: int main( void ) 8: { 9: draw box( 8, 35 ); 10: _ \\\ 11: return О; 12: } 13: 14: void draw_box( int row, int column ) 15: { 16: int col; 17: for ( ; row > 0; row--) 18: { 19: for (col = column; col > 0; col-—) 20: printf("X"); 21: 22:. printf("\n“); 23: } 24: }   ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх ххххххххххххххххххххххххххххххххххх  Основную работу в этой программе выполняет строка 20. При запуске програм- Ашишз мы на экран вывоцятся 280 букв х, образуя прямоугольник 8x35. Для отображе-  ния х используется всего Одна команда, вложенная в два цикла. В этом листинге прототип функции draw_box() находится в строке 5. Эта функция при- нимает два аргумента row и column типа int, которые ссдержат размеры изображаемого пря- моугольника. В строке 9 функция main() вызывает draw_box() и передает в нее значения 8 (гон)т135(со1пшп) Присмотревшись повнимательнее к функции draw_box( ), можно обнаружить две неоче- видные вещи. Во-первых, это объявление локальной переменной col. BO-BTOple, еще Один вызов функции printf() B строке 22. Все это станет понятнее после рассмотрения вложенных циклов for. B строке 17 начинается первый цикл for. Инициализация пропущена, потому что началь- ное значение row передано в функцию как аргумент. Согласно условию цикла, он  132 Неделя 1. Основные вопросы 
выполняется, пока row не станет равным 0. В первой строке цикла row равно 0, поэтому вы— полнение переходит к строке 19. В строке 19 находится еще один цикл for. Здесь переданный в функцию параметр column копируется в локальную переменную col типа int. После инициализации значение col равно 35 (передано через column), a сам параметр column сохраняет свое исходное значение. По— скольку col больше 0, выполняется строка 20 и выводится Х. Затем col уменьшается, и цикл продолжает работу. Как только col становится равным нулю, цикл заканчивается, и управление переходит к строке 22. В этой строке экранный вывод переходит на новую строку. (Вывод информации на экран подробно рассматривается на занятии 7, посвященном средствам ввода-вывода.) После перехода на новую строку достигается конец первого цикла for, поэтому выполняется блок приращения —— от row отнимается единица, и переменная становится равной 7. Управление снова переходит к строке 19. Обратите внимание, что после ‚своего последнего использования переменная col стала равной нулю. Если бы вместо col ис- `пользовалась column, условие цикла перестало бы выполняться, поскольку ноль не может быть больше нуля. В результате была бы напечатана только первая строка. Попробуйте уб- Драть инициализацию из строки 19 и заменить два вхождения переменной col Ha column —— и увиците, что получится.   "’ Рекомендуется Не рекомендуется “ратите особое внимание на точку с H9 перегружайте; заголовок Lgagma forf‘     ‚».,  {_ "? „.. 'f‘ki. 5‘  ПЯТОЙ, если chonbay9T9 ПУСТОЙ опера- ` ИСПОПНЯЭМЫМИ операторамиг ”“ ОТЯ ВЫ …  a:  ___р в цикле for; Ставьте точку с валятой :имеете полное право использовать отдельной строке или помещайте про— сколько угодно операторов отдеііяя ихч'        между ней и 9K0HH3HH9M оператора … запятой; чаще всего бывает удобнееі ;…:‚4 ог Боле понятно запись цикла выглядит хпоместить большую часть кода в тело _ * `;Ёі‘да, к_огда тонка с 33HHT0H стойт 999 о`т- ЦИКЛЗ …, 239%? ” ‚№:; . … … ** строке * ‘“ …… № 1 от; (сопит;= О, count < 1000, ,;ёдііі „“З“ 1,99,99,91 ЁЁ} 1,3”… ; ; Ёап'ау [сост/п: ]«= 50$; ;;…” E ;и“ 99929993“ „№53… ”`; „№333? „‹ 9 ; ….…;::і……… …- арфе… ЧЁ: i117" ‘… …:Ё … …:…31…Ё…………5:53…Ё…………………ЁЁ? :5?” 99999999999, ›……'.………і Оператор while  Оператор while, также именуемый циклом while, выполняет блок операторов, пока неко- : торое условие остается истинным. Он имеет следующую форму:  while ( условие) опера тор  Выражение условие может быть любым выражением С, а оператор ——— это любой одиноч- ный или составной оператор С. Когда в программе встречается оператор while, выполняются следующие действия:  1. Вычисляется выражение условие.  2. Если условие равно FALSE (т.е. 0), то выполнение оператора while прекращается, и управ- ление переходит к первому же оператору после блока опера тор.  3. Если условие равно TRUE (не 0), то выполняется блок оператор.  4. Выполнение возвращается B пункт 1.  День 6-й. Циклы 133 
Работа цикла while показана на рис. 6.3.    while (условие) операторы ;  Вычислить ИСТИНА ‘  условие  Рис. 6.3. Работа цикла while      Выполнить операторы  В листинге 6.3 приведена простая программа, которая использует цикл while для печати  чисел от 1 до 20. (Эту же задачу выполняла и программа из листинга 6.1 с помощью операто— pafor)  Листинг 6.3. whilest . с — простой пример оператора while   mummthr—l .. со .. о. о. о. о. о.  /* демонстрация простого оператора while */ #include <stdio.h> int count;  int main( void )   { 9: /* Печать чисел от 1 до 20 */ 10: 11: count = 1: 12: 13: while (count <= 20) 14: { 15: printf("%d\n", count); 16: count++: 17: } 18: return О; 19: } . . 1 2 3 4 5 6  134  Неделя 1. Основные вопросы 
7 8 9 10 11 12 13 14 15 16 17 18 19 20  Сравните листинг 6.3 и листинг 6.1, в котором для той же задачи применяется _ оператор for. B строке 11 и`нициализируется переменная count. Поскольку опе— Эатор while не содержит специального блока инициализации, необходимо позаботиться о Ёрисваивании всех необходимых значений еще до начала цикла. В строке 13 начинается сам yum while. Условие продолжения в нем то же самое, что и в цикле листинга 6.1, gaunt <= 20. B строке 16 внутри цикла while переменная count получает приращение. Как {вы думаете, что произойдет, если убрать строку 16 из программы‘? В этом случае цикл нико- ;да не закончится, поскольку переменная count всегда будет равна 1 и, соответственно, йеньше 20. Ё Возможно, вы заметили, что оператор while аналогичен оператору for без блоков ини- уиализации и приращения. Таким образом, две следующие формы полностью эквивалентны: for ( ; условие ; ) while ( условие )  i   Ввиду этой эквивалентности все, что можно сделать с помощью оператора for, может :быть также выполнено с помощью оператора while. При использовании оператора while инициализацию нужно выполнить заранее в отдельном операторе, а обновление значения :?чегчика следует выполнять внутри тела цикла. Если необходимы инициализация и обновление счетчика, то большинство опытных про- _траммистов на С предпочитают пользоваться оператором for, a не while. Это предпочтение рсается в основном удобства чтения кода программы. В операторе for инициализация, усло- fine и приращение расположены вместе, поэтому при необходимости их легко найти и откор- :ректировать. В операторе while инициализация и приращение выполняются отдельно от про— ёерки условия, поэтому и найти их может быть не так легко.  ‚> %% Оператор while . ж: Ё while (условие) оператор(ы) Блок условие может быть любым допустимым выражением С. Обычно это логиче- ское выражение. Если условие ложно (равно 0), выполнение оператора while пре- кращается, и выполнение переходит к следующему оператору после блока опера— тор(ы). B противном случае выполняется блок оператор (51). Блок оператор(ы) представляет собой последовательность из одного или несколь- ких операторов С, которые выполняются, пока условие истинно (TRUE).   _День 6—й. Циклы тд 
Оператор while — 3T0 оператор цикла. Пока его условие выполняется (т.е. равно TRUE, или не равно нулю), выполняется повторение оператора или блока операторов. Если условие ложно при первой же его проверке, операторы в блоке после while не выпол— няются ни разу.  Пример1  int х = 0; while (х < 10)  printf("\nThe value of х is %d", х ); x++; \\ }  Пример2  /* Вводить числа, пока не встретится число больше 99 */ int nbr = 0; while (nbr <= 99) scanf("%d", &nbr );  ПримерЗ  /* Пользователь вводит до 10 целых чисел */ /* Числа помещаются в массив с именем value */ /* Kax только вводится 99, цикл заканчивается */ int value[10]; int ctr = О; int nbr; while (ctr < 10 && nbr != 99) { puts("Enter a number, 99 to quit "); scanf("%d", &nbr); value[ctr] = nbr; ctr++;  Вложенные операторы while  Так же, как операторы if и for, операторы while могут быть вложенными. В программе из листинга 6.4 приведен пример вложенных операторов while. Хотя это не лучшее примене- ние данного оператора, все же здесь вводятся некоторые новые идеи.  Листинг 6.4. whiles .c — вложенные операторы while   `] Ф 01 “> И N r-- .. .. .. .. .. .. ..  136  /* демонстрация вложенных операторов while */ #include <stdio.h> int array[5];  int main( void )  Неделя 1. Основные вопросы 
8: {  9: int ctr = 0, 10: nbr = 0; 11: 12: printf(“This program prompts you to enter 5 numbers\n"); 13: printf(“Each number should be from 1 to 10\n"); 14: 15: while ( ctr < 5 ) 16: { 17: nbr = 0; 18: while (nbr < 1 || nbr > 10) 19: { 20: printf(“\nEnter number %d of 5: ", ctr + 1 ): 21: scanf("%d", &nbr ): 22: } 23: 24: array[ctr] = nbr; 25: ctr++; 26: } 27: #8: for (ctr = 0: ctr < 5; ctr++) 29: printf(“Value %d is %d\n“, ctr + 1, array[ctr] ): ЗО: : : return 0; 32: }   ЁЁЁЁ ЁЬЁЁЁЁЁЁЁЗ‘ЁЁЁЁЗЁЁОЁЁе‘ёёеіо5 № Enter number 1 of 5: 3 Enter number 2 of 5: 6 Enter number 3‘of 5: 3 Enter number 4 of 5: 9 Enter number 5 of 5: 2  Value Value Value Value Value  Ш-ЬШМН I'" U) ЮФО—‚ФШ  Как и в предыдущих примерах, строка 1 содержит комментарий к назначению программы. В строке 3 помещен оператор #include для включения стандартного  заголовочного файла ввола-вывола. Строка5 солержит объявление массива (псд именем array), который может содержать пять целых чисел. В функции шаіп() объявляются две до- полнительные локальные переменные ctr и nbr (CM. строки9 и 10). Эти переменные инициализируются значением 0 при объявлении. В конце строки9 используется запятая в  День 6-й. Циклы 137 
качестве разделителя, благодаря чему nbr также объявляется как переменная типа int без не- обходимости начинать новый оператор. Это традиционная для программирования на С фор- ма объявления. В строках 12 и 13 на экран выводятся сообщения о том, что программа делает и каких данных она ожидает от пользователя. В строках 15—26 находится первый оператор while и его исполняемый блок. Строки 18—22 содержат еще один, вложенный оператор while с его собственным блоком, которые вместе входят в состав внешнего оператора while. Внешний цикл выполняется, пока ctr меньше 5 (CM. строку 15). Пока это так, в строке 17 переменной nbr присваивается О, в строках 18—22 (во вложенном цикле) значение для nbr вводится с клавиатуры, в строке 24 оно помещается в массив array, a B строке 25 счетчик ctr получает приращение. Затем цикл начинается снова. Таким образом, внешний цикл собирает в массив array пять чисел, пронумерованных с помощью счетчика ctr. Внутренний цикл —— это пример удачного использования цикла while. Разрешены только числа от 1 до 10, и программе нет смысла работать с неправильно введенными пользователем числами. Поэтому строки 18—22 контролируют процесс ввода. Этот оператор while проверя- ст, находится ли введенное число в допустимых рамках от 1 до 10, и если нет, то выводит приглашение ввести число заново. В строках 28—29 числа, помещенные в массив, отображаются на экране. Обратите внима— ние на то, что цикл while уже отработал свое с переменной ctr, и цикл for может снова ис- пользовать ее для своих целей. Начиная с нуля и увеличивая счетчик всякий„ раз на единицу, цикл for выполняется пять раз: отображает номер числа в виде ctr+l (поскольку фактическая нумерация начинается с нуля) и соответствующее значение из массива. Для упражнения можно попробовать изменить программу следующим образом. Во- первых, расширить диапазон допустимых значений: вместо диапазона от 1 до 10 ввести диапа- зон от 1 до 100. Во-вторых, изменить количество вводимых чисел: вместо пяти вводить десять.  Рекомендуется Не рекомендуется  *Используйте оператор for вместо while, 1 % Не Используйте следующую запись‘ без , ;;Ёесли в цикле нужно выполнять инициа-Ё ;, крайней необходимости: , ',;‘лизацию и изменение счетчика. Все эти; Гане (x) ` \ I“ _ : ё é       „операции вместе с проверкой условия в : * … » Ё кпе for производятся в одном месте, а BMecTo этого лучше записать так . … , ;,Ё цикле while— нет. 3 ‘ While (x 1" °) ‘ с * ‘ ЁХотя обе формы эквивалентны, вторая % удобнее при отладке (исправлении оши— ;бок в программе). После компиляции обе формы дают практически один и тот же ; тд. ‚ д ‚…  “н…-»...“ к ;.. …… шчнчмУ mum» `.`… mw „чиж …… м...; ... » «Au-m … ‚„„„ \ ……“  „`ы—»›... \  “я: .. V, \ : »›…к ›‚д№№ж\ю»ю №    Цикл do...while  Третья циклическая конструкция в языке С —— это цикл do. . .while, который выполняет блок операторов, пока истинно некоторое условие. В отличие от циклов for и while, этот  Цикл проверяет условие в конце, а не в начале. Структура цикла do. . .while такова: do  оператор while ( условие) ;  138 Неделя 1. Основные вопросы 
Условие представляет собой любое выражение С, а оператор может быть одиночным или составным. Когда в программе встречается оператор do. . .while, выполняются следующие действия: 1. Выполняется оператор (одиночный или блок). 2. Вычисляется значение условия. Если оно истинно (равно TRUE), снова выполняется пункт 1. Если ложно, цикл прекращает работу. На рис. 6.4 показано, как работает цикл do . . .while.  Начало     Выполнить операторы  do операторы; while (условие);    Вычислить ИСТИНА ,  условие  Рис. 6.4. Работа цикла do. . . While  Операторы в цикле do. . .while всегда выполняются хотя бы один раз. Это происходит потому, что условие цикла проверяется в конце, а не в начале цикла. В противоположность этому в циклах for и while условие проверяется в начале цикла, поэтому их операторы не выполняются ни разу, если условие ложно с самого начала. Цикл do. . .while используется реже, чем for и while. Его применение наиболее естест- венно тогда, когда операторы цикла необходимо выполнить обязательно хотя бы раз. Конеч- но, то же самое можно сделать и с помощью цикла while, заставив условие выполняться пе- ред тем, как программа дойдет до цикла. Но все-таки конструкция do. . .while B этом случае удобнее. В листинге 6.5 приведен пример использования цикла do. . .while.  Листинг 6.5. do.c —— простой цикл do. . .while    /* Демонстрация простого цикла do...while */  #include <stdio.h>  int get_menu_choice( void );  \lO‘U‘loh-WNH  int main( void )  День 6-й. Циклы 139 
8: {  9: int choice; 10: 11: choice = get_menu_choice(); 12: ‚ 13: printf(“You chose Menu Option %d\n", choice ); 14: 15: return 0; 16: } 17: 18: int get_menu_choice( void ) 19: { 20: int selection = 0; 21: 22: d0 \\ 23: { 24: printf("\n“ ); 25: printf("\nl - Add a Record" ); 26: printf("\nZ - Change a record"); 27: printf("\n3 - Delete a record"); 28: printf("\n4 - Quit"); 29: printf(“\n" ); 30: printf(“\nEnter a selection: " ); 31: 32: scanf("%d“, &selection ); 33: 34: } while ( selection < 1 I] selection > 4 ); 35: 36: return selection; 37: }   P 1 - Add a Record “№№“… 2 - Change a record  3 - Delete a record 4 - Quit  Enter a selection: 8  Add a Record Change a record Delete a record Quit  дым.—- |  Enter a selection: 4 You chose Menu Option 4  Анализ В этой программе пользователю предлагается меню с вариантами выбора. Мож- но выбрать любой из предложенных пунктов, после чего программа выводит на  экран номер выбора. Этот принцип организации программ еще не0днократно встретится нам в книге. А пока в этой программе все должно быть понятно. В функции шаіп (строки 7—16) Her ничего нового по сравнению с уже изученным.  140 Неделя 1. Основные вопросы 
 @“ Тело данной функции шаіп можно было бы записать в одной строке: ШШ! printf("You chose Menu Option %d”, get_menu_choice()); Правда, при необходимости как-то обрабатывать выбор пользователя  значение get_menu_choice() все равно понадобилось бы. Поэтому разумно сразу присвоить это значение переменной (например, choice).     Строки 18—37 солержат функцию get_menu_choice( ). Эта функция отображает на экране меню (строки 24—30) и получает от пользователя его выбор. Для получения этого выбора нужно хотя бы раз показать меню, поэтому цикл do. . .while как раз ПОДХОДИТ для этой цели. Программа отображает меню до тех пор, пока не выбран пункт из числа предложенных. В токе 34 находится блок while оператора do, который проверяет номер выбранного пункта gelection. Если введенное значение выходит за пределы диапазона от 1 до 4, меню отобра- Ёнаегся снова, и пользователь должен ввести новое значение. Когда наконец Сделан правиль- film выбор, программа переходит к строке 36, которая возвращает выбранное значение через Ёеременную selection.  Оператор do...while  do {  оператор(ы) } while (условие)   Условие может быть любым выражением С. Обычно это логическое выражение. Ес— ли условие равно FALSE (нулю), цикл прекращается, и управление переходит к сле— дующему после while onepaTopy. B противном случае операторы в теле цикла вы— полняются снова. Оператор(ы) представляют собой Один или несколько операторов С, которые вы- полняются вначале Один раз, а затем повторяются, пока условие истинно. Оператор do. . .while — это Оператор цикла. В нем многократно выполняется блок операторов или отдельный оператор в зависимости от истинности некоторого условия. В отличие от цикла while, цикл do. . .while всегда выполняется хотя бы Один раз.  Пример1  /* Число выводится, даже если условие ложно! */ int x = 10; do { printf(”\nThe value of х is %d”, х ); } while (x != 10);  Пример2  /* 830дить числа, пока не встретится большее 99 */ int nbr; do { scanf("%d", &nbr ); } while (nbr <= 99);  День 6—й. Циклы 141 
ПримерЗ  /* Пользователь вводит до 10 целых чисел */ /* Числа помещаются в массив чадлде. Как только */ /* введено 99, цикл заканчивается. */  int value[10]; int ctr = 0; int nbr; do { puts("Enter a number,99 to quit "); scanf("%d", &nbr); value[ctr] = nbr; ctr++; / } while (ctr < 10 && nbr != 99);  Вложенные циклы  Блаженный цикл —— это конструкция, в которой внутрь одного цикла помещен другой цикл. Примеры вложенных циклов уже привоцились ранее. Язык С не накладывает никаких ограничений на вложение циклов, кроме того, что цикл должен содержаться целиком внутри другого цикла. Частично перекрывающиеся циклы не допускаются. Поэтому следующий код ошибочен:  for ( count = 1; count < 100; count++ ) { do { /* цикл do...while */ } /* конец цикла for */ } while (x != 0);  A BOT если цикл do. . .while целиком поместить внутрь цикла for, то все будет B порядке: for ( count = 1; count < 100; count++ )  { do { /* цикл do...while */ } while (х := 0); } /* конец цикла for */  При использовании вложенных циклов следует помнить, что изменения, внесенные внут- ренним циклом B какие-либо данные, могут повлиять и на внешний цикл. Внутренний цикл может как зависеть, так и не зависеть от переменных внешнего цикла. Если бы цикл do. . .while B нашем последнем примере изменял значение переменной count, 3T0 повлияло бы на количество вьшолнений внешнего цикла. Чтобы текст программы с циклами было легче читать, используйте отступы от начала строки. Отступайте на один шаг дальше в каждом следующем вложенном цикле по сравне- нию c предыдущим — так гораздо легче определить, какие операторы выполняются B том или ином цикле.  142 Неделя 1. Основные вопросы 
 —-——  (Рекомендуется Не рекомендуется  Не пытайтесь частичНо накладывать циклы. Можно вложить один в другой, но обязательно полностью, a He частично.  № „км.. и: и… м,.      ЗО? % :! Х I ZAO O от 20 (I) E.’ Ф :! v I O Ф Е.". ‘.'.‘1 O : ЁЁ .' ё  r...,........-M.u _3м `и№чч\-ш‚.‚ w .»…» …‚м„.…._._д.  Резюме  По окончании этого занятия вы уже практически готовы писать самостоятельно свои соб— ственные программы на С. В языке С есть три управляющих оператора для организации циклов: for, while и flo. . .while. C помощью любого из них можно заставить блок операторов выполняться один раз, несколько раз или вообще ни разу в зависимости от состояния определенных перемен— ных. Многие прикладные задачи программирования успешно решаются путем многократного повторения операций с помощью этих циклов. Хотя все три цикла по сути предназначены для аналогичных целей, у них есть и свои осо— бенности. В операторе for можно записать инициализацию, проверку условия и приращение счетчика одной строкой. Блок оператора while выполняется, пока истинно его условие. Блок оператора do. . .while всегда выполняется хотя бы раз, а затем до тех пор, пока условие не перестанет быть истинным. Вложение в языке С означает помещение одного оператора внутрь другого. Можно вкла- дывать друг в друга любые управляющие операторы. Вложенные операторы if рассматрива— лись на занятии 4. На этом занятии приведены примеры вложенных циклов for, while и до. . .while.  Вопросы и ответы  Каким циклом лучше пользоваться: for, while или do. . .while? Еще раз внимательно изучите синтаксис всех трех операторов, и вы увидите, что каждый из них пригоден для организации цикла. Однако у каждого есть и свои особенности. Цикл for лучше всего применять тогда, когда в цикле нужно инициализировать и изменять перемен— ные-счетчики. Если же нужно только проверять условие, и ни o каких счетчиках речь не идет, воспользуйтесь циклом while. Наконец, если тело цикла должно гарантированно выполнять— ся не менее одного раза, попытайтесь организовать цикл do. . .while. Поскольку каждый из этих циклов годится для решении прикладных задач, лучше изучить их все и при случае вы— брать наилучший.  Насколько глубоко можно вкладывать циклы? Вложенных циклов может быть сколько угодно. Если в программе необходимо организо— вать более двух вложенных циклов, подумайте, не вынести ли некоторые из них в отдельную функцию. Не так—то легко проследить за большим количеством фигурных скобок, так что введение функции может облегчить чтение и написание кода.  Можно ли вкладывать друг в друга разные циклы? Операторы if, for, while, do. . .while и т.п. можно вкладывать друг в друга в любых соче- таниях. В большинстве программ, которые вы напишете, вам наверняка придется это делать.  День 6-й. Циклы 14 
Коллоквиум  Этот коллоквиум содержит контрольные вопросы для повторения пройденного, а также  упражнения ДЛЯ приобретения практических навыков.  Контрольные вопросы  РРР!"  РРЯРУ'  Какое значение имеет индекс первого элемента массива? В чем разница между оператором for и оператором while? B чем разница между оператором while и оператором do. . .while?  Правда ли, что с помощью цикла while можно получить те же самые результаты, что и с помощью цикла for?  Какие требования необходимо соблюдать при вложении циклов? Можно ли вложить оператор while B оператор do. . .while? Назовите четыре составные части оператора for. Назовите две составные части оператора while. Назовите две составные части оператора do. . .while.  Упражнения  99°99  10.  Напишите объявление массива, состоящего из 50 элементов типа long. Напишите оператор, который присваивает значение 123 . 456 пятидесятому элементу мас- сива из первого упражнения. Какое значение приобретает переменная x после выполнения следующего оператора? for (x = 0; x < 100; x++) ; Какое значение приобретает переменная ctr после выполнения следующего оператора? for (ctr = 2; ctr < 10; ctr += 3) ; Сколько букв X выведет на экран следующий фрагмент кода? for (x = 0; x < 10; x++) for (у = 5; у > 0; у-—) puts("x"); Напишите оператор for для счета от 1 до 100 через 3. Напишите оператор while для счета от 1 до 100 через 3. Напишите оператор do. . .while для счета от 1 до 100 через 3. Поиск ошибок. Найдите ошибку в следующем фрагменте кода: record = О; while (record < 100)  {  printf("\nRecord %d ",record ); printf("\nGetting next number..." );  } Поиск ошибок. Найдите ошибку в следующем фрагменте кода (MAXVALUES следует счи- тать известной величиной):  for (counter = 1; counter < MAXVALUES; counter++); printf("\nCounter = %d", counter );  144 Неделя 1. Основные вопросы 
    Практически в каждой программе возникает необходимость отображать информацию на ане или вводить данные с клавиатуры. Во многих примерах программ, рассмотренных на едыдущих занятиях, эти операции уже выполнялись, но мы до сих пор избегали подробных Ёмснений, как именно это делается. На этом занятии будут рассмотрены следующие вопросы.   & , I Основные операторы ввода-вывода в С {ii  Ё“: I Вывод информации на экран с помощью библиотечных функций printf( ) и puts( ) I Форматирование отображаемой на экране информации  №№ 5  -s>~1 'Ъ {ta-r  I Чтение данных с клавиатуры при помощи библиотечной функции scanf()  Ha этом занятии мы не сможем охватить указанные темы во всей полноте, но представ- Ёенной информации будет вполне достаточно для того, чтобы писать практически ценные №фаммьп. На последующих занятиях эти вопросы рассматриваются более подробно.  таж—: _  :т‘д  ВЫВОД данных на экран  Ё В своем подавляющем большинстве программы отображают на экране определенную ин- ‚формацию. Довольно часто для этого используются такие библиотечные функции С, как  фильц ) и puts().  “БГ—"‘  ’SP‘"  Функция printf()  Функция printf ( ) входит в стандартную библиотеку функций С и определена в стандарте ANSI. Пожалуй, именно с ее помощью можно отображать данные на экране наиболее гибко и разнообразно. Ее вызов встречался во многих ранее приведенных примерах. Сейчас мы узна- Бри, как же она работает. F. Вывести на экран текстовое сообщение совсем просто. Следует вызвать функцию ЁГіпъц ), передав ей в качестве аргумента сообщение в двойных кавычках. Например, сооб-  “_ -  ' ение How Now Brown Cow! выводится следующим образом: „=А _ Ёппёцтон Now Brown Cowl"); 
Но кроме текстовых сообщений часто необходимо показывать и значения переменных. Это немного сложнее, чем просто вывести сообщение. Предположим, нужно отобразить зна— чение числовой переменной myNumber вместе с сопроводительным текстом. Более того, все это нужно вывести с новой строки. Тогда функция printf( ) вызывается так:  printf("\nThe value of myNumber is %d", myNumber);  Предположим, текущее значение myNumber равно 12. B результате на экране появится текст The value of myNumber is 12  B этом примере функция printf ( ) принимает два аргумента. Первый аргумент заключен в двойные кавычки и называется строкой формата. Второй аргумент — это имя переменной (myNumber), содержащей требуемое значение для вывода на экран.  Строка формата фуНкции printf()  Строка формата функции printf( ) определяет, какой вид должны иметь выводимые дан— ные. Строка формата может содержать следующие компоненты.  l Текстовые строки отображаются точно в таком виде, в каком они стоят в строке формата. В предыдущем примере текстовая строка включала в себя символы от про— писного T до пробела перед % (не включая самого знака процента).  I Управляющие последовательности (escape sequences), или специальные символы, предназначены для специального управления выводом. Управляющая последователь- ность состоит из обратной косой черты (\) и одного символа следом за ней. В преды- дущем примере символы \п образуют управляющую последовательность, которая на— зывается символом конца строки и эквивалентна команде “перейти к началу новой строки”. Управляющие последовательности также отвечают за печать некоторых сим— волов. Основные управляющие последовательности перечислены в табл. 7.1.  I Спецификации вывода состоят из знака процента (%) и символа, следующего за ним. В данном примере %d представляет спецификацию вывода. Эти спецификации сообщают функции printf( ), как следует воспринимать и отображать выводимые данные. Так, спецификация %d указывает, что переменную myVariable следует вывести как целое десятичное число со знаком.  Таблица 7. 1 . Наиболее употребительные управляющие последовательности   Последовательность Значение   \а Звуковой сигнал (предупреждение) \Ь Возврат на один символ назад \f Прогон страницы \n Перевод строки \r Возврат каретки \t Горизонтальная табуляция \v Вертикальная табуляция \\ Обратная косая черта   146 Неделя 1. Основные вопросы 
g Окончание табл. 7. l #" „...?  ? Последовательность Значение  ‚‚_—— , \?   Знак вопроса \' Одинарная кавычка ' \" Двойная кавычка  ___—_  Управляющие последовательности функции printf()  Управляющие последовательности помогают разместить данные на экране, передвигая курсор в желаемое положение. Они также используются для вывода символов, которые сами ‚по себе имеют особый смысл для функции printf ( ). Например, для отображения одной об- ратной косой черты в строке формата нужно использовать двойную косую черту (\\). Первая тз них сообщает функции, что вторую следует считать просто текстовым символом, а не на- гналом управляющей последовательности. Вообще говоря, обратная косая черта всегда указы- ;ваег функции printf( ), что следующий за ней символ необходимо интерпретировать особым Зобразом. Вот некоторые примеры.   Ёмледовательность Значение @ Буква n 3% Символ конца экранной строки EV Символ двойной кавычки “3" Начало или конец текстовой строки  В табл. 7.1 приведены наиболее распространенные специальные символы языка С. Про- Ч };!рамма в листинге 7.1 демонстрирует применение некоторых часто используемых специаль— Гдых символов. ;}:  ;Йистинг 7. 1 . escape . с —-— использование специальных символов в “функции printf( )   {:1: /* демонстрация популярных специальных символов */ -Ё2: % 3: #include <stdio.h> 4: 3 5: #define QUIT 3 >" ,;6: $$?: int get_menu_choice( void ): "В: void print_report( void ); {**-"9: §»0: int main( void ) 9:11: { #12: int choice = О; $113: :14: while (choice != QUIT) £1215: { gflG: choice = get_menu_choice(); $17: Egg: if (choice == 1)  W135»- -1  , ень 7—й. Основные средства ввода—вывода 147 
19: printf("\nBeeping the computer\a\a\a" ); 20: else 21: { 22: if (choice == 2) 23: print_report(); 24: } 25: } 26: printf("You chose to quit!\n"); 27: 28: return 0; 29: } 30: 31: int get_menu_choice( void ) 32: { 33: int selection = 0; 34: 35: do 36: { 37: printf( "\n" ); 38: printf( "\nl - Beep Computer" ); 39: printf( "\n2 - Display Report"); 40: printf( "\n3 - Quit"); 41: printf( "\n" ); 42: printf( "\nEnter a selection:" ); 43: 44: scanf( "%d", &selection ); 45: 46: }while ( selection < 1 || selection > 3 ); 47: 48: return selection; 49: } 50: 51: void print_report( void ) 52: { 53: printf( "\nSAMPLE REPORT" ); 54: printf( "\n\nSequence\tMeaning" ); 55: printf( "\n=========\t=======" ), 56: printf( "\n\\a\t\tbell (alert)" ); 57: printf( "\n\\b\t\tbackspace" ); 58: printf( "\n...\t\t..."); 59: }   148  1 - Beep Computer 2 - Display Report 3 - Quit  Enter a selection:1  Beeping the computer  Неделя 1. Основные вопросы 
1 — Веер Computer 2 - Display Report 3 - Quit Enter a selection:2  SAMPLE REPORT  Sequence Meaning \a bell (alert) \b backspace  1 - Beep Computer 2 - Display Report 3 - Quit  Enter a selection:3  You chose to quit!  Листинг 7.1 кажется довольно длинным по сравнению с предыдущими примера- ми, зато в нем появилось кое—что новое, достойное внимания. В строке 3 вклю- _ я заголовочный файл stdio.h, поскольку в этой программе используется функция intf ( ). B строке 5 объявляется константа QUIT. После третьего занятия, посвященного пе- " " енным и константам, должно быть понятно, что с помощью директивы #define мы дела- имя QUIT эквивалентным литеральному значению 3. B строках 7 и 8 находятся прототипы кций. В этой программе используются две функции: get_menu_choice() и   ункции меню в листинге 6.5. Строки 37—41 содержат вызовы функции printf ( ) с символа- перехода на новую строку. В строках 38, 39, 40 и 42 также используются эти символы, но, _Ёдое того, еще и выводится текст. Строку 37 можно было бы вообще убрать, изменив стро- 38 следующим образом: }_intf( “\n\n1 - Beep Computer" ); Однако в том виде, в каком строки 37 и 38 стоят в программе, читать и понимать код ста- _.;; вится легче. В строке 14, в функции main( ), мы видим начало цикла while. Этот цикл выполняется, :Жто переменная choice не равна QUIT. Поскольку QUIT — это константа, ее можно было бы енить соответствующим значением 3, но программа до некоторой степени утратила бы ю удобочитаемость. В строке 16 ВВОДится значение переменной choice, которое затем E; изируется оператором if B строках 18—25. Если пользователь выбрап 1, в строке 19 вы- дится символ перехода на новую строку и сообщение, а затем три звуковых сигнала. Если Ёользователь выбирает 2, в строке 23 вызывается функция print _.report() Ё Функция print_report() определена в строках 51—59. Эта несложная функция показыва- ‘ , как легко с помощью printf ( ) и специальных символов организовать форматированный Ёывод на экран. Символ конца строки уже встречался нам ранее. В строках 54—58 использу— я также символ табуляции \t. C помощью этого специального символа столбцы отчета ыравниваются по вертикали. Строки 56 и 57 могут показаться запутанными, но если читать !!х по порядку слева направо, все станет ясно. В строке 56 выполняется перевоц строки (\п),  Ёень 7-й. Основные средства введа-вывода 149 
затем выводится обратная косая черта (\), затем буква a и еще два символа табуляции (\t\t). Строка заканчивается описательным текстом (bell (alert)). B строке 57 используется та же форма записи. Программа выводит на экран первые две строки табл. 7.1, а также название отчета и заго- ловки столбцов. В упражнении 9 в конце этого занятия вам предстоит дополнить эту про— грамму так, чтобы она печатала и остальную часть таблицы.  Спецификации вывода функции printf()  Строка формата должна содержать по одной спецификации вывода для каждой выводи— мой переменной. Функция printf() отображает значение каждой переменной в таком виде, какой указан соответствующей спецификацией. На занятии 15 вы узнаете об этом более под— робно. Пока же не забывайте употреблять именно ту спецификацию вывода, которая соответ- ствует типу выводимой переменной. Поясним эту рекомендацию. Для вывода десятичной целочисленной переменной со зна- ком (типа int или long) используйте спецификацию %d. Для вывода переменной без знака (типа unsigned int или unsigned long) используйте %u. Наиболее распространенные специ— фикации вывода перечислены в табл. 7.2.  Таблица 7.2. Часто используемые спецификации вывода    Спецификация Значение Типы выводимых переменных % Одиночный символ char %d Десятичное целое число со знаком int, short %1d Десятичное длинное целое число со знаком long %f Десятичное число с плавающей точкой float. double %5 Строка символов Массивы типа char %u десятичное целое число без знака unsigned int, unsigned short %1u Десятичное длинное целое число без знака unsigned long    Imam B каждой программе, использующей функцию printf(), должен быть включен заголовочный файл stdio.h.  ‚\     Все элементы строки формата, не являющиеся спецификациями или управляющими сим- волами, воспринимаются как текстовые строки. Строки отображаются именно так, как напи— саны, включая все их пробелы. Можно ли вывести несколько переменных сразу? Безусловно. В одном вызове функции printf() можно вывести любое количество значений, но строка формата должна обязательно содержать по одной спецификации для каждого значения. Спецификации соответствуют пе— ременным в порядке слева направо. Например, в следующей записи:  printf("Rate = %f, amount = %d", rate, amount); переменной rate соответствует спецификация %f, a переменная amount ассоциируется со спе— ЦИФикацией %d. Положение спецификации в строке формата определяет, где в выводимой строке будет стоять соответствующее ему значение. Если в функцию printf() передается переменных больше, чем спецификаций, то лишние переменные игнорируются и не выводят-  150 Неделя 1 . Основные вопросы 
ся. Если спецификаций больше, чем переменных, на месте лишних спецификаций отобража- ются случайные, непредсказуемые значения. С помощью функции printf() можно выводить не только значения переменных. Аргу- ментами функции могут быть любые выражения С. Например, для отображения значения суммы :( и у можно записать: total = х + у; printf("%d", total); A можно использовать и такую форму записи: ргіпсі("%д"‚ х + y); B листинге 7.2 приведен пример использования функции printf() B программе. Допол- Ёительные сведения об этой функции даются также в ходе занятия 15.  фистинг 7.2. num.c -— вывоц числовых значений с помощью функции  ёхіпъц )   1: /* демонстрация вывода чисел с помощью функции printf(). */ 2: 3: #include <stdio.h> 4: 5: int a = 2, b = 10, с = 50; 6: float f = 1.05, g = 25.5, h = —0.1; 7: 8: int main( void ) 9: { 0: printf("\nDecimal values without tabs: %d %d %d", a, b, c); ll: printf("\nDecimal values with tabs: \t%d \t%d \t%d", a, b, c); ,2: E3: printf("\nThree floats on 1 line: \t%f\t%f\t%f", f, g, h); {4: printf("\nThree floats on 3 lines: \n\t%f\n\t%f\n\t%f", f, g, h); НБ: ЭБ: printf("\nThe rate is %f%%", f); 07: printf("\nThe result of %f/%f = %f\n", g, f, g / f); i8: 39: return 0; 30: } г Decimal values without tabs: 2 10 50 Paaunmam Decimal values with tabs: 2 10 50 Three floats on 1 line: 1.050000 25.500000 -0.100000 Three floats on 3 lines: 1.050000 25.500000 —0.100000  The rate is 1.050000% The result of 25.500000/1.050000 = 24.285715  'm . Программа из листинга 7.2 выводит на экран шесть строк с информацией. В строках 10 и 11 выводится по три целых переменных: a, b и с. Оператор в стро-  ке 10 выводит их без табуляции, а в строке 11 -—- с табуляцией. В строках 13 и 14 выводится  День 7-й. Основные средства ввода-вывода 151 
по три вещественных переменных: f, g и h. B операторе строки 13 они выводятся одной стро— кой, а в строке 14 — каждое число с новой строки. В строке 16 выводится переменная f типа float co знаком процента после нее. Поскольку обычно со знака процента начинается специ- фикация формата вывода, необходимо поставить сразу два таких знака, чтобы получить на экране один символ процента. Кстати, именно таким образом выводится и обратная косая черта. В строке 17 демонстрируется тот факт, что для вывода значений на экран не обяза- тельно использовать одиночные переменные —— спецификациям формата могут соответство— вать и выражения наподобие g / f, и даже константы.    Рекомендуется Не рекомендуется  Обязательно используйте символ кон-‚; Не пытайтесь выводить несколько строк ё ца строки’при выводе нескольких строк Ё текста одним вызовом функции printf?” Ё информации отдельными функциями % В большинстве случаев удобнее ВЫВести „РППЁЁО , ~ \ …; ; , несколько строк несколькими чоперато— ‘“" * * “’ gpaMId чем OGXOAMTbCfl одним вЫзовом ё функции с несколькими символами кон— {    ‚инь-«№    »“ № ‹ a: „ s : : ца строки (\п). , : “‹‚3322833 ‚_ » . 3 „137 “ ”’ 37,735”; *’ Ё Не ошибитесь в написаНии stdio. h , , @@ . Ё, тщьддтё ‘ ; ‚ Многие программисты no ошибке пишут 53555 ° 353’ >;”’:‘§":"":5””«””“ ” Ё * studio. h;1 тогда как в названии; этого Ё ‚ , :—*ё7*-`…‚е,№ '“ … і“… ` ё:файла нет буквы u. , …, , 53:15.:2‘33 % Ч  7 х 5 „ ‹ "‘/"У Jag \ \ ` „: « ‚ \ ‚: ‚ри ,{щ ": {, EC \ о. ~A’n\ 9"  ‹ „Ё... „ 11… м3… >1... . ‘ ‚, ,. …… ‚_ „ м№№мь№№№Мм …… . m nmm“l\ мики   Функция printf()  #include <stdio.h> printf( строка-формата [,аргументы ,...]);   Функция printf() принимает последовательность аргументов, каждый из которых должен соответствовать спецификации вывода в заданной строке формата. Функция printf ( ) выводит данные в форматированном виде на стандартное устройство вы- вода, обычно на экран дисплея. Для того, чтобы иметь возможность использовать функцию printf( ) B программе, необходимо подключить заголовочный файл STDIO.H. Строка формата (строка-формата) — обязательный аргумент при вызове функции, тогда как остальные аргументы необязательны. Для каждого аргумента в строке формата должна присутствовать спецификация вывода. В табл. 7.2 перечислены наиболее часто используемые спецификации. Строка формата может также содержать управляющие последовательности, или специальные символы. Самые распространенные из них перечислены в табл. 7.1. Ниже приведены примеры использования функции printf( ) и результатов ее работы.  Пример1 #include <stdio.h> int main( void )  {  printf("This is an example of something printed! "); return 0;  152 Неделя 1. Основные вопросы 
Результат  This is an example of something printed!  Пример2  printf("This prints a character, %c\na number, %d\na floating \ point, %f", 'z', 123, 456.789 );  Результат  This prints a character, 2 a number, 123 a floating point, 456.789   кода косая черта (\) указывает, что строка формата продолжается B следующей строке кода. Компилятор воспринимает обе части строки формата как единое целое.  — Во втором примере можно заметить, что строка формата в функции ‹ cflflflm printf() переносится на следующую строку. В конце первой строки    Вывод сообщений с помощью функции puts()  Для выв0да текстовых сообщений на экран B С существует также функция puts( ), однако ее нельзя использовать для отображения значений переменных. Аргументом функции должна быть Одна текстовая строка. При ее выволе на экран в конце автоматически выполняется пе— реход на новую строку. Например, такой оператор  _puts("Hello,world.");  выполняет те же действия, что и оператор printf("Hello,world.\n");  B строках, являющихся аргументами puts( ), можно использовать специальные символы $(например, \л). Эффект от них будет такой же, как и B функции printf( ) (CM. Ta6n. 7.1, co- держащую часто встречающиеся специальные символы). Как и в случае printf( ), для использования функции puts() B программе необходимо подключить заголовочный файл stdi0.h. Обратите внимание, что при любом количестве функций ввода-вывода в программе достаточно всего лишь одного включения этого файла.  . Рекомендуется Не рекомендуется      1 He используйте спецификации вывода в функции puts(). '   День 7—й. Основные средства введен-вывода 153 
Функция puts()  #include <stdio.h> puts( строка );   Функция puts( ) копирует строку текста на стандартное устройство вывша, обычно на экран дисплея. Для использования puts( ) необходимо подключить стандартный заголовочный файл ввода-вывша stdio.h. После вывода строки текста функция puts( ) автоматически выполняет переход на новую строку экрана. Ее строковый аргумент может содержать специальные (управляющие) символы. Наиболее часто используемые символы перечислены в табл.7.1. Ниже приведены примеры использования функции puts( ) и результатов ее работы.  Пример1 puts("This is printed with the puts() functionl");  Результат  This is printed with the puts() function!  Пример2 puts(“This prints on the first line. \nThis prints on the second line.");  puts("This prints on the third line."); puts(“If these were printf()s, all four lines would be on two lines!“);  Результат  This prints on the first line. This prints on the second line. This prints on the third line. If these were printf()s, all four lines would be on two lines!  Ввод числовых данных C помощью функции scanf()  Почти все программы не только отображают определенную информацию на экране, но и требуют от пользователя ввести некоторые данные с клавиатуры. Наиболее гибкий способ ввода числовой информации с клавиатуры обеспечивается библиотечной функцией scanf( ). Функция scanf( ) считывает данные с клавиатуры в соответствии с заданным форматом и помещает введенные данные в одну или несколько переменных. Как и в printf( ), для описа- ния формата входных данных в scanf ( ) применяется строка формата. В этой строке исполь- зуются те же спецификации, что и в printf ( ). Например, следующий оператор считывает с клавиатуры десятичное число и присваивает его целочисленной переменной х:  scanf(“%d", &x);  154 Неделя 1. Основные вопросы 
Аналогичным образом следующий оператор считывает вещественное число и присваива- ет его переменной rate:  scanf("%f", &rate);  Что означает знак амперсанда (&) перед именем переменной? Символ & в языке С обозна— чает операцию взятия адреса. О ней подробно рассказывается на занятии 9, посвященном указателям и адресации. А пока что просто запомните: в списке аргументов функции scanf ( ) перед каждым именем числовой переменной должен стоять символ &. Одним вызовом функции scanf( ) можно выполнить ввод сразу нескольких значений. Для этого достаточно поставить в строке формата несколько спецификаций ввода, а в списке, со- ответственно, несколько имен переменных (не забудьте — перед каждым именем должен стоять знак &). Например, следующий оператор вводит сразу целое и вещественное значения, присваивая их соответственно переменным )( и rate:  scanf("%d %f", &x, &rate);  При вводе нескольких значений функция scanf( ) распознает отдельные поля по свобод- ному пространству между ними. Понятие свободного пространства включает пробелы, табу- ляции, переходы на новую строку. Каждой спецификации формата в scanf() должно соот— ветствовать поле ввода, а конец поля ввода определяется по свободному пространству, кото- Трое за ним следует. Это обеспечивает значительную гибкость ввода. В ответ на запрос предыдущей функции ;івсапц ) можно ввести следующее: 510 12. 45 ’ А можно ввести и так: gm 12 .45 И такая форма вполне возможна:  {. `г 9“  @.  ‚Со $312.45 51 ЁЁ Функция scanf() сама распределит значения между переменными, если только во вве-  “# gamma фрагменте эти значения будут отделены друг от друга пустым пространством. і.   @ * Пользуйтесь функцией scanf() c осторожностью. Если, например, ожида- @"" ется ввод символа, а пользователь ВВОДИТ число, или если нужно число, а %; вводится символ, то программа может выдать пользователю весьма не- …] ожиданные результаты.     *>№“ , ‚.  “енд-тж  Чтобы использовать функцию scanf(), как и другие рассмотренные на этом занятии Пункции‚ в программе, необходимо включить заголовочный файл stdio.h. B листинге 7.3 f иводятся примеры использования scanf( ). Более подробную информацию об этой функ— ‘ и вы получите на занятии 15.   _истинг 7.3. scanit .с — ввод числовых данных с использованием ункции scanf ( )  /* демонстрация использования scanf() */ #include <stdio.h>  Б: #define QUIT 4  Ёнь 7—й. Основные средства ввода—вывща 155 
int get_menu_choice( void );  ФЧФ 00.000  9: int main( void )  10: { 11: int choice = О; 12: int int_var = О; 13: float float_var = 0.0; 14: unsigned unsigned_var = О; 15: 16: while (choice != QUIT) 17: { 18: choice = get_menu_choice(); 19: 20: if (choice == 1) 21: { 22: puts("\nEnter a signed decimal integer (i.e. -123)"); 23: scanf("%d", &int_var); 24: } 25: if (choice == 2) 26: { 27: puts("\nEnter a decimal floating-point number\ 28: (e.g. 1.23)"); 29: scanf("%f", &float_var); 30: } 31: if (choice == 3) 32: { 33: puts("\nEnter an unsigned decimal integer \ 34: (e.g. 123)" ): 35: scanf( "%u", &unsigned_var ); 36: } 37: } 38: printf("\nYour values are: int: %d float: %f unsigned: %u \n", 39: int_var, float_var, unsigned_var ); 40: 41: return 0; 42: } 43: 44: int get_menu_choice( void ) 45: { 46: int selection = О; 47: 48: do 49: { 50: puts( "\n1 - Get a signed decimal integer" ); 51: puts( "2 — Get a decimal floating-point number" ); 52: puts( "3 — Get an unsigned decimal integer" ): 53: puts( "4 — Quit" ); 54: puts( "\nEnter a selection:" ); 55:  156 Неделя 1. Основные вопросы 
56: scanf( "%d", &selection ); 57: 58: }while ( selection < 1 [| selection > 4 ); 59: 60: return selection; gl: }   l - Get a signed decimal integer 2 - Get a decimal floating-point number 3 - Get an unsigned decimal integer  4 - Quit Enter a selection: 1 Enter a signed decimal integer (i.e. -123) -123 1 - Get a signed decimal integer 2 - Get a decimal floating-point number 3 - Get an unsigned decimal integer 4 - Quit Enter a selection: 3 Enter an unsigned decimal integer (e.g. 123) 321 1 - Get a signed decimal integer 2 - Get a decimal floating-point number 3 - Get an unsigned decimal integer 4 - Quit Enter a selection: 2 Enter a decimal floating-point number (e.g. 1.23) 1231.123 1 - Get a signed decimal integer 2 - Get a decimal floating-point number 3 - Get an unsigned decimal integer 4 - Quit  Enter a selection: 4  Your values are: int: -123 float: 1231.123047 unsigned: 321  Бень 7-й. Основные средства ввода-вывода 157 
В листинге 7.3 используются те же самые принципы работы с меню, что и B лис— № тинге 7.1. Отличие B функции get__menu__choice( ) (строки 44—61) незначительно, Однако заслуживает внимания. Во-первых, используется puts() вместо printf( ) ——- pa3 нет выводимых переменных, незачем вызывать printf( ). Поскольку используется puts( ), из строк 51—53 удалены символы перехода на новую строку. Изменилась и строка 58, поскольку теперь B меню четыре пункта. Обратите внимание, что строка 56 не изменилась, но теперь она должна стать понятнее. Функция scanf ( ) считывает целое число и помещает его в пере— менную selection. B строке 60 функция возвращает значение selection B вызывающую программу. Функции main( ) B листингах 7.3 и 7.1 имеют аналогичную структуру. Оператор if прове— ряет значение choice, возвращенное из функции get_menu_choice( ). B зависимости от зна—- чения choice на экран выводится сообщение, запрашивается и считывается с использованием scanf() число. Обратите внимание на разницу между строками 23, 29 и 35. B них ввоцятся значения различных типов, а B строках 12—14 объявляются соответствующие переменные. Когда пользователь выбирает пункт меню Quit, программа отображает последние введен— ные значения всех трех типов. Если пользователь не ввел никаких значений, выводятся нули. поскольку все переменные были инициализированы B строках 12, 13 и 14. По поводу строк 20—36 необходимо сделать одно замечание: операторы if B этих строках плохо струк— турированы Если вы заметили, что лучше было бы использовать if. . .else, то вы правы. А вот на занятии 14, посвященном работе с экраном, принтером и клавиатурой, вволится новый управляющий оператор switch. C ero помощью можно организовать нашу программу еще  лучше.    Рекомендуется Не рекомендуется  Используйте функции priBtfu и puts“ , Незабываитеёиспопьзовать знак опера- B связке со scanf() Сначала выведите *Йііаи взятия aBpeca‘“(&) перед переменны- * сообщение о том какие Еданные должен % ми B функции Scanfn ‘ ввести пользователь а затем ^введите , ‹ :, , ‘2 эти данные функцией scanf() , ,5: g „:,:‚д ‹ „, “,  » <3` - д …. . :. ,; . ‚ „ „. „> ‚ д` ‚ : 5~:m><.:~\1 a. Р.у.. L … ‘ ми.-шик 8 1 ("SJ U " " ^ “A?“ ( 14…" мыми… `…жЖЬММ`Ё—щм—н—Жмжм      fizz": Ё' ‹ }Й1“\“МЪЁ›1  „‹… … ___, ‘f mm: ‚ : >3:    *а тт “СМЖ“? ‹ an ‹ “7):: „ …. “&‘“: $ a") ‚…(е , E   Функция scanf()  #include <stdio.h> scanf( строка—формата[‚ аргументы,...1);   Функция scanf( ) выполняет ввод значений согласно спецификациям ввода в строке формата и помещает эти значения B переменные-аргументы. Фактическими аргу- ментами должны быть адреса переменных, а не сами переменные. Адреса числовых переменных передаются в функцию с помощью знака операции взятия адреса (&) перед именем переменной. Для использования функции scanf ( ) необходимо вклю- чить заголовочный файл stdio.h. Функция scanf() считывает поля ввода из стандартного потока ввода, обычно с клавиатуры. Каждое из считанных полей помещается B соответствующую перемен- ную. При помещении данных они преобразуются к форматам, заданным специфика- циями ввода. Каждой переменной в списке аргументов должна соответствовать спе- цификация ввода. Самые распространенные спецификации перечислены в табл. 7.2.  158 Неделя 1 . Основные вопросы 
. Пример1 ‚ int х, у, z; = scanf( "%d %d %d", &x, &у‚ &z); Пример2  ‘ #include <stdio.h> int main(void )   ? { Ё float y; Ё int x; L puts( "Enter a float, then an int" ); : scanf( "%f %d", &y, &x); printf( "\nYou entered %f and %d ", y, x ); return 0; }   пециальные последовательности ! 3 трех символов  Итак, мы изучили основы ввоца-вывода информации с помощью таких функций, как intf() и scanf( ). Ha этом занятии мы рассмотрим еще Одну дополнительную тему. Она ' ается скорее не ввола—выв0да как такового, а специальных сочетаний символов, которые в ‘ Одном к0де обозначают совсем не то, что в нем написано буквально. Такие специальные ’ стания состоят из трех символов.   стями. Они рассматриваются здесь для того, чтобы вы знали источник проблемы в следующем случае: если вы нечаянно употребите псдобное сочетание в исходном тексте программы, оно будет преобразовано ком- пилятором в некоторый символ.  Б' ; Вряд ЛИ BaM вообще ПРИДЭТСЯ ПОЛЬЗОВЭТЬСЯ ТЭКИМИ ПОСЛЭДОВЭТЭЛЬНО-     ' Упомянутые сочетания напоминают управляющие последовательности (специальные Mason“), о которых уже говорилось ранее. Основное различие состоит в том, что как только . пилятор обнаруживает в тексте такое сочетание, сразу же преобразует его в соответст- щий символ. ,} Эти последовательности начинаются с двух знаков вопроса (??). В табл. 7.3 перечислены Ёедовательности из трех символов, определенные стандартом ANSI. Согласно этому  g,” дарту, никаких других специальных трехсимвольных последовательностей существовать ‚дедолжно   »ёіблица 7.3. Специальные последовательности из трех символов  ёзпоследовательность Эквивалентный символ   ’ ё??= # Ц?“ [ ??і \    7—й. Основные средства ввода-вывода 159 
Окончание табл. 7.3   П оследовательность ЭКВИВЗЛЭНТНЬ1й СИМВОЛ ? ? ) ]  ??'   ›  ??<  ??!  ??>  гг. '   Если в исходном коде встречаются сочетания, перечисленные в табл. 7.3, они преобразу— ются в эквивалентные им символы. Преобразование выполняется даже над сочетаниями, вхо- дящими в символьные строки. Например, такая строка printf( "??(WOW??)" ) ; будет преобразована в следующую: printf("[WOW]“); Дополнительные знаки вопроса перед последовательностями не преобразуются. Напри- мер, такая строка: printf(“???~"); превратится в такую: printf("?~");  Резюме  Проработав это занятие, вы должны быть готовы писать собственные программы на С. Функции printf ( ), scanf( ) и puts() B сочетании с управляющими операторами, изученными на предыдущих занятиях, образуют набор средств, вполне пригодных для написания несложных программ. Вывод на экран выполняется функциями printf() и puts( ). Функция puts( ) может вы- воцить только текстовые сообщения, а printf() способна отображать как текст, так и значе- ния переменных и выражений. В обеих функциях можно использовать специальные управ— ляющие символы. Функция scanf( ) осуществляет ввод одного или нескольких числовых значений с клавиа— туры и интерпретирует каждое из них в соответствии с заданными спецификациями формата. Каждое значение присваивается определенной переменной. В конце занятия вы также узнали о специальных сочетаниях из трех символов, которые преобразуются компилятором в эквивалентные им одиночные символы.  Вопросы и ответы  Зачем использовать функцию puts( ), если printf() обладает более богатыми воз- можностями?  160 Неделя 1. Основные вопросы 
Поскольку функция printf( ) имеет более обширные возможности, она также задействует больше ресурсов компьютера, При написании маленьких, но сверхскоростных программ или огромных программных продуктов, где каждый байт на счету, необхоцимо использовать вы- зовы функций как можно эффективнее, отбрасывая все лишнее. Здесь-то и оправдывает себя функция puts( ). Вообще, всегда стоит использовать самый экономный ресурс из числа под- ходящих для решения задачи.  Зачем нужно включать заголовочный файл stdio.h при использовании функций printf( ), puts() или scanf( )? Файл stdio.h содержит прототипы стандартных функций ввода-вывода, в число которых входят printf( ), puts( ) и scanf( ). Попробуйте скомпилировать программу без заголовочно- го файла stdio.h, и посмотрите сообщения об ошибках и предупреждения, которые возник- нут в результате.  Что произойдет, если не поставить знак адреса (8) перед переменной—аргументом функции scanf( )? Это довольно распространенная ошибка, которую легко сделать. Если опустить взятие ад- реса, результат будет непредсказуемым. Вы поймете это лучше после изучения указателей на занятиях 9 и 13. Пока же знайте, что если забыть об операции взятия адреса, то функция scanf( ) поместит считанные данные не в требуемую переменную, а в какое-то другое место в памяти. Нз-за этого может не произойти вообще ничего, а может случиться и “зависание” системы, требующее перезагрузки.  Коллоквиум  На этом коллоквиуме вам предлагаются контрольные вопросы на закрепление пройдеННО- , го материала и упражнения для развития практических навыков.  ^Контрольные вопросы  1. В чем разница между printf() и puts( )? 2. Какой заголовочный файл необходимо включить, если используется функция printf( )? 3. Что делают следующие управляющие символы? а)\\ б) \Ь в) \п г) \’с д)\а 4. Какие спецификации формата используются для вывода следующих данных? а) Строки текста 6) Целого десятичного числа со знаком в) Десятичного вещественного числа с плавающей точкой  5. В чем состоит различие при использовании этих символов в строках, выводимых функци- ей puts( )?  День 7-й. Основные средства ввода—вывода 161 
а) b 6) \b в) \ г) \\  Упражнения  (-— Начиная с этого занятия, в некоторых упражнениях будет необходимо на- пиши   писать целую программу, которая выполняет какую-то конкретную задачу. Поскольку в С всегда есть несколько способов сделать Одно и то же, от- веты в конце книги не следует воспринимать как единственно правиль- ные. Будет замечательно, если вы напишете свою собственную програм- му и заставите ее сделать требуемое. Если же вы столкнетесь с трудно- стями, посмотрите правильный ответ. Ответы даны с минимумом комментариев, потому что гораздо полезнее выяснить самостоятельно, как работают эти программы.     Напишите операторы c вызовами функций printf() и puts( ) для перехода на новую строку. Напишите оператор c вызовом scanf () для ввода символа, целого числа без знака, и еще ОДНОГО символа. Напишите операторы для ввоца целого числа и вывода его же на экран. Измените упражнение 3 так, чтобы могли вводиться только четные числа (2, 4, 6 и т.д.). Измените упражнение4 так, чтобы программа вводила числа, пока не будет получено  значение 99 или пока не будет введено шесть чисел. Сохраните числа в массиве. (Подсказка: потребуется цикл.)  Напишите законченную исполняемую программу для задачи из упражнения 5. Добавьте функцию вывода значений, разделенных символами табуляции, в ряд в одной строке. (Выведите только те значения, которые помещены в массив.) Поиск ошибок. Найдите ошибки в следующем фрагменте кода: printf("Jack said, "Peter Piper picked a peck of pickled peppers.""); Поиск ошибок. Найдите ошибки в следующей программе: int get_1_or_2( void )  { int answer = 0; while (answer < 1 II answer > 2) { printf(Enter 1 for Yes, 2 for No); scanf("%f", answer ); } return answer; }  Дополните функцию print_report( ) из листинга 7.1 так, чтобы она печатала и остальную часть табл. 7.1.  162 Неделя 1. Основные вопросы 
10. Напишите программу для ввода- двух вещественных чисел с клавиатуры и отображения их произведения.  1 1. Напишите программу для ввода десяти целых чисел с клавиатуры и отображения их суммы.  12. Напишите программу для ввода целых чисел и помещения их в массив. Ввод следует 3a- канчивать, когда введено число O или достигнут конец массива. Затем программа должна найти в массиве наибольшее и наименьшее значения. (Примечание: это сложная задача, так как на наших занятиях давались только краткие сведения о массивах. Если задача окажется слишком трудной, попытайтесь снова решить ее после занятия 8, посвященного работе с массивами чисел.)  День 7-й. Основные средства ввода-вывода 163 
sAms Неделя 1 ппвпіі вампвшпяшвпьип  Итоги  Вот и закончилась первая неделя занятий по программированию на языке С. Теперь вы уже сумеете легко набрать и запустить программу с использованием редактора и компилято— ра. Приведенная ниже программа построена на материале предыдущих занятий. Этот раздел отличается от программ для самостоятельной работы‚ две из которых уже были рассмотрены. После текста программы имеется анализ ее работы. Все используемые в программе элементы, средства и метолы программирования рассматривались на предыдущих занятиях. После второй и третьей недели обучения вам также будут предложены аналогичные программы для поцведения итогов занятий.   — Если вам непонятен какой-то оператор программы, поищите необходимые @ объяснения в материале соответствующего занятия.     Листинг И1 . 1 . weekl . с — итоговая программа к первой неделе занятий        1: /* Имя: week1.c */ 2: /* Назначение: ввод возраста и дохода людей в количестве */ 3: /* до 100. Вывод отчета на основании введенных */ 4: /* данных. */ 5: /*- -- ---------------------------------- */ 6: /* */ 7: /* Включаемые файлы */ 8: /*--- ---*/ 9: #include <stdio.h> 10: 11: /* */ 12: /* defined constants */ 13: /* */ 14:  15: #define MAX 100 16: #define YES 1 17: #define NO 0    18: 19: /* */ 20: /* variables */ 21: /* */ 22:  23: long income[MAX]; /* доходы */ 24: int month[MAX], day[MAX], year[MAX]; /* даты рождения */ 
25: int x, y, ctr; /* счетчики */    26: int cont; /* управляющая переменная */ 27: long month_total, grand_total; /* итоговые цифры */ 28: 29: /* */ 30: /* Прототипы функций */ 31: /* */ 32:  33: int main(void); 34: int display_instructions(void); 35: void get_data(void); 36: void display_report(void); 37: int continue_function(void); 38: 39: /* */ 40: /* Начало программы */ 41: /* */ 42: 43: int main(void) 44: { 45: cont = display_instructions(); 46: 47: if ( cont == YES ) 48: { 49: get_data(); 50: display_report(); 51: } 52: else 53: printf( "\nProgram Aborted by User!\n\n"); 54: 55: return О; 56: } 57: /* 58: 59: 60: 61:     Функция: display_instructions() Назначение: отображает инструкцию по использованию программы и просит пользователя ввести 0 для завершения, 1 для продолжения 62: Возвращает: NO - если пользователь ввел 0 63: YES - если пользователь ввел не ноль 64: * */ 65: int display_instructions( void ) 66: { 67: printf("\n\n"): 68: printf("\nThis program enables you to enter up to 99 people\'s "); 69: printf("\nincomes and birthdays. It then prints the incomes by"); 70: printf("\nmonth along with the overall income and overall average."); 71: printf("\n");  я-и-я-я-я-я-я-  * * * * * *   72: 73: cont = continue_function(); 74: return( cont );  Итоги 165 
75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99°  166  100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124:     } /* __________________________________________ * * Функция: get_data() * * Назначение: ввод данных. Ввод продолжается, пока не * * получено 100 порций данных или пока не * * введен нулевой номер месяца * * Возвращает: ничего * * Примечание: можно ввести дату рождения 0/0/0 B случае, * * если пользователь не уверен. Считается, что * * во всех месяцах 31 день. * * __________________________________________________________ */ void get_data(void) { for ( cont = YES, ctr = 0; ctr < MAX && cont == YES; ctr++ ) { printf("\nEnter information for Person %d.", ctr+1 ); printf("\n\tEnter Birthday:"); do { printf("\n\tMonth (0 - 12): "); scanf("%d", &month[ctr]); }while (month[ctr] < 0 || month[ctr] > 12 ); do { printf("\n\tDay (0 - 31): "); scanf("%d", &day[ctr]); }while ( day[ctr] < 0 || day[ctr] > 31 ); do printf("\n\tYear (0 - 2002): "); scanf("%d", &year[ctr]); }while ( year[ctr] < 0 || year[ctr] > 2002 ); printf("\nEnter Yearly Income (whole dollars): "); scanf("%ld", &income[ctr]); cont = continue_function(); } /* ctr равно количеству введенных порций данных. */ } /*___ _________ _. ___________ * * Функция: display_report() * * Назначение: отображает на экране отчет * * Возвращает: ничего * * Примечание: можно было бы вывести больше данных. * * _________________________________________________________ */  Неделя 1 
125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172:  !4тоги  void display_report()  {  grand_total = 0;   printf("\n\n\n"); /* пропустить несколько строк */ printf("\n SALARY SUMMARY"); printf("\n "):  for( x = 0; x <= 12; x++ ) /* no всем месяцам, включая 0 */ { month_total = 0; for( y = 0; y < ctr; y++ ) { if( month[y] == x ) month_total += income[y];  } printf(”\nTotal for month %d is %ld", x, month_total); grand_total += month_total;  } printf("\n\nReport totals:”); printf("\nTotal income is %ld”, grand_total); printf("\nAverage Income is %ld", grand_total/ctr );  printf("\n\n* * * End of Report * * *\n");    * Функция: continue_function() * Назначение: спрашивает, продолжать или нет. * Возвращает: YES — если пользователь решил продолжить * N0 - если пользователь решил закончить * */  int continue_function( void )  {  printf("\n\nDo you wish to continue? (0=N0/1=YES): ”); scanf( "%d", &x );  while( x < 0 || x > 1 )  printf("\n%d is invalid!", x); printf("\nPlease enter 0 to Quit or 1 to Continue: "); scanf("%d", &x); } if(x == 0) return(N0); else return(YES);  167 
№ Ввод и компиляция этой программы вполне доступны вам уже после изучения материала занятий 1 и 2, посвященных введению в С и основным компонентам программ. Данная программа содержит значительно больше комментариев, чем все до сих пор встречавшиеся в книге. Эти комментарии типичны для настоящей, практической про- граммы на С. В частности, они имеются перед началом программы и каждой важнейшей функцией. Комментарии в строках 1—5 содержат общие сведения о программе, в том числе ее имя. Некоторые программисты приводят в комментариях такую информацию, как имя автора программы, используемый компилятор, номер версии, подключаемые при компоновке биб— лиотеки, дату создания программы. Комментарии перед функциями описывают их назначе- ние, возможные возвращаемые значения, соглашения по вызову функций, и вообще все важ— ное, что следует о них знать. Комментарии в строках 1—5 указывают, что программа принимает персональные данные о людях в количестве до 100 человек. Перед вводом данных в строке 45 вызывается функция display_instructions( ). Эта функция отображает указания по использованию программы и спрашивает пользователя, хочет ли он продолжить работу. В строках 67—71 для отображения инструкций вызывается функция printf ( ), изученная на занятии 7 по средствам ввода- вывода. Строки 157—172 содержат функцию continue_function( ), которая использует ряд средств, изученных на последних занятиях. Эта функция спрашивает, хочет ли пользователь продолжить работу (строка 159). С помощью управляющего оператора whiie, изученного на занятии 6, функция следит, чтобы введенный ответ бьш равен либо 0, либо 1. Если ответ не равен ни одному из этих двух значений, функция спрашивает снова и т.д. Когда, наконец, введен допустимый ответ, оператор if. . .else (CM. занятие 4, посвященное операторам и вы— ражениям) возвращает константу YES или N0. Все основные операции программы выполняются функциями get_data() H display_report( ). Функция get_data() приглашает ввести данные и помещает введенную информацию в массивы, объявленные в начале программы. В цикле for (строка 89) пользова— тель приглашается вводить данные до тех пор, пока cont не перестает быть равным предо- пределенной константе YES (это значение возвращает функция continue_function( )), или до тех пор, пока счетчик ctr не становится больше или равен максимальному числу элементов массива МАХ. Программа проверяет каждый введенный элемент данных на допустимость. На— пример, в строках 94—98 выводится приглашение ввести месяц. Принимаются только значе- ния от 0 до 12. Если ввести число, большее 12, программа снова попросит ввести месяц и т.д. В строке 115 вызывается функция continue_function( ), которая спрашивает, хочет ли поль- зователь продолжать ввод данных. Как только пользователь вводит 0 в ответ на запрос функции continue_function( ), или как только закончится ввод максимально допустимого количества наборов данных (МАХ), про- грамма возвращается к строке 50 функции main() H вызывает функцию display_report( ). Эта функция, определенная в строках 119—149, отображает на экране отчет. В этом отчете используется вложенный цикл for для подсчета общего дохода за каждый месяц и общего дохода за год. Этот отчет может показаться сложным. В таком случае обратитесь к материалу занятия 6, на котором рассматриваются вложенные циклы. Многие отчеты, которые вам при— дется создавать в качестве программиста, еще сложнее, чем этот. В этой программе используется большинство средств С, которые вы изучили в ходе ва- шей первой недели самостоятельных занятий. Всего за одну неделю мы рассмотрели очень много материала, и надеемся, что вы его благополучно усвоили. С применением изученного вы уже можете писать собственные программы на С, хотя пока что весьма ограниченные в своих возможностях.   168 Неделя 1 
 sAms Вовой самостоятельно Неделя 2  Основные вопросы  Закончилась первая неделю занятий по программированию на языке С. К настоящему мо- ‘менту вы уже наверняка чувствуете себя уверенно в работе с редактором и компилятором, метко набираете, компилируете и запускаете на выполнение программы.  Что дальше  На этой неделе занятий будет изучено очень много нового материала. Будет рассказано о Зоногих средствах, составляющих фундаментальную основу программированплпя на С. Это ра- Жота с числовыми и символьными массивами, создание массивов и строк на основе символь- дт'іого типа данных, группирование данных различных типов в структурах. Материал второй недели занятий основан на том, что было изучено в ходе первой недели. ЁдЗдесь вводятся новые управляющие операторы, более подробно рассматриваются функции, в {Юм числе новые, ранее не использованные. На занятиях 9 и 12, посвященных соответственно указателям и области действия пере- ;Ёенных, изучаются понятия, исключительно важные для понимания самой сути С. Тему ука- ‘Ёвателей и их применения следует проработать со всей тщательностью. Кроме этого, на занятиях 8 и 11 будут изучены новые способы хранения и обработки дан- ішх с помощью массивов, структур, объединений и т.д. Работа с символьной и строковой ійнформацией рассматривается на занятии 10. Вопросам вывода данных на экран и принтер посвящено занятие 14. , К концу первой недели вы овладели достаточными навыками для написания некоторых простых программ на С. После изучения материала второй недели вам будет уже нетрудно писать довольно сложные программы, способные решать самые разные задачи. 
   Массивы числовых данных  Массивы очень часто используются для хранения данных в программах на языке С. На за— нятии 6 мы уже сделали краткое введение в массивы. Сегоцня будут рассмотрены следующие вопросы.  l Что такое массив l Определение оцномерных и многомерных массивов  l Объявление и инициализация массивов  Что такое массив  Массив—— это совокупность ячеек памяти, хранящих элементы данных одного _ D типа под единым общим именем. Каждая такая ячейка называется элементом массива. Зачем нужны массивы в программах? Это лучше всего пояснить на примере. Допустим, вы ведете учет своих расходов за 2003 г., раскладывая счета и чеки по месяцам. Можно завести отцельную папку для каждого месяца, но удобнее иметь Одну папку за весь гол с двенадцатью отделениями, соответствующими месяцам. Теперь перенесем этот пример в область программирования. Необходимо написать про- грамму, поцсчитываюшую расхоцы помесячно. Можно объявить двенадцать отцельных пе- ременных, чтобы хранить значения общих расхоцов за каждый месяц. Это аналогично двена- дцати отцельным папкам. Но согласно хорошему стилю программирования лучше объявить массив из 12 элементов. Каждый элемент хранит значение общей суммы расходов за опреде- ленный месяц. Этот поцход аналогичен одной папке с двенадцатью отцелениями для чеков и счетов. На рис. 8.1 показана разница между поцходами, использующими отдельные перемен- ные и массив. 
 Отдельные переменные Массив  Рис. 8.1. Аналогии между переменными и отдельными пап- ками, массивом и папкой со многими отделениями  ЁОдномерные массивы  ,  ЗНПВЫП mepmuu одномерный массив имеет всего один индекс. Индекс ~— это номер в квадрат- : ных скобках после имени массива. По этому номеру различают отдельные эле—  іменты массива. Для большей ясности приведем пример. В программе учета расходов можно ;объявить следующий массив типа float:  Eloat expenses[12];  Массив имеет имя expenses и содержит 12 элементов. Каждый отдельный элемент явля- Ёется (и обладает всеми свойствами) одиночной переменной типа float. Любой из типов данных С можно использовать для организации массива. Элементы мас- Ёсива всегда нумеруются, начиная с 0, так что 12 элементов массива expenses имеют номера Бот О до 11. В предыдущем примере сумма расходов за январь помещается в элементе ;ехрепвев [ 0], за февраль ~— в expenses [ 1] и т.д. Сумма расходов за декабрь находится в эле- Ёменте expenses [ 11]. При объявлении массива компилятор выделяет для него блок памяти, достаточный для }хранения всего массива сразу. Отдельные элементы располагаются в памяти последователь- но, друг за другом, как это показано на рис. 8.2.  int array[10];     Errfifilh‘lirééyn’fl анилина] Га """"" аналіз] или    Рис. 8.2. Последовательное размещение элементов массива  Большое значение имеет то, где именно в программе объявляются массивы. Как и в слу- чае отдельных переменных, не входящих в массивы, от местонахождения объявления зависит  день 8-й. Массивы числовых данных 171 
допустимое использование массива. На занятии 12, посвященном области действия перемен— ных, вопросы местонахождения объявлений и их действия рассматриваются более подробно` ПЬка же располагайте объявления массивов там же, где и объявления отдельных переменных. Элемент массива можно использовать в любом месте программы, где допускается приме- нение числовой переменной соответствующего типа. К отдельным элементам массива обра… щаются по имени массива и индексу элемента в квадратных скобках. Например, следующий оператор помещает значение 89 . 95 во второй элемент массива (помните, что первым элемен— том является expenses [ 0 ], а не expenses [ 1 ]):  expenses[1] = 89.95;  Аналогичным образом следующий оператор копирует значение элемента массива ex» penses[11] B элемент expenses[ 10 ]:  expenses[10] = expenses[11];  При обращении к элементу массива его индекс может быть литеральной константой, как в приведенных примерах. Но в программах можно использовать и индексные выражения: перс- менные или выражения С, и даже элементы других массивов. Приведем несколько примеров: float expenses[100];  int a[10]; /* примеры индексных выражений */ expenses[i] = 100; // і — целая переменная  expenses[2 + 3] = 100; // эквивалентно expenses[5] expenses[a[2]] = 100; // a[] - целочисленный массив  Последний пример лучше пояснить. Пусть у нас есть массив целочисленных значений под именем а[ ], и в его элементе a[2] хранится значение 8. Тогда следующие две строки полностью эквивалентны: expenses{a[2]}; expenses[8}; Применяя массивы, всегда помните схему адресации их элементов: в массиве из п элемен— тов допустимые индексы находятся в диапазоне от 0 до 114. Использование индекса n B про- грамме может вызвать ошибку. Компилятор С не проверяет, выходит ли индекс за границы  массива. Программа благополучно пройдет компиляцию и компоновку, а вот на этапе выпол— нения выход индекса за пределы массива обычно приводит к неправильным результатам.   Помните. что нумерация элементов массива начинается с 0. a He с 1. Так- @"! же помните, что номер последнего элемента на единицу меньше. чем 06— щее количество эпементов в массиве. Например, в массиве из десяти элементов они нумеруются от 0 до 9.     Иногда бывает необхолимо работать с массивом из n элементов так, словно элементы пронумерованы от 1 до n. B предыдущем примере с месяцами было бы более естественно по- местить расходы за январь в элемент expenses [ 1 ], за февраль —— в элемент expenses [ 2] и т.д. Проще всего это сделать, объявив массив на один элемент длиннее, чем нужно, и далее игноч рировать элемент под номером 0. B этом случае массив объявляется таким образом: float expenses[13];  Кстати, нулевой элемент можно дополнительно использовать каким-нибудь образом, на- пример, поместить в него общую сумму расходов за год.  172 Неделя 2. Основные вопросы 
Программа ехрепзез.с в листинге 8.] демонстрирует использование массивов. Никакого практического применения эта простая программа не имеет, но все же облегчает понимание работы с массивами.  Листинг 8. 1 . expenses .c — работа с массивом   CD\IOS'J‘l-h-IJAJPCDD-fll  `О  /* expenses.c - демонстрация работы с массивами */ #include <stdio.h> /* Объявление массива для учета расходов и счетчика */  float expenses[13]; int count;  10: int main( void )  11: { 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: }  /* Ввод данных с клавиатуры в массив */  for (count = 1; count < 13; count++)  {  printf("Enter expenses for month %d: ", count); scanf(“%f", &expenses[count]);  }  /* Вывод содержимого массива */  for (count = 1; count < 13; count++)  { printf("Month %d = $%.2f\n", count, expenses[count]); } return 0;   Enter expenses for month 1: 100 Enter expenses for month 2: 200.12 Enter expenses for month 3: 150.50 Enter expenses for month 4: 300 Enter expenses for month 5: 100.50 Enter expenses for month 6: 34.25 Enter expenses for month 7: 45.75 Enter expenses for month 8: 195.00 Enter expenses for month 9: 123.45 Enter expenses for month 10: 111.11 Enter expenses for month 11: 222.20 Enter expenses for month 12: 120.00  Month 1 = $100.00  Month 2 = $200.12 Month 3 = $150.50 Month 4 = $300.00 Month 5 = $100.50  День 8-й. Массивы числовых данных 173 
Month 6 = $34.25 Month 7 = $45.75 Month 8 = $195.00 Month 9 = $123.45 Month 10 = $111.11 Month 11 = $222.20 Month 12 = $120.00  № При запуске программы expenses.c выводится приглашение ввести суммы рас- ходов за месяцы от 1 до 12. Введенные значения помещаются в массив. Пользо-  ватель должен ввести значение для каждого месяца. После ввода 12-го значения содержимое массива отображается на экране. Структура программы аналогична ранее изученным. В строке 1 находится комментарий, поясняющий назначение программы. Обратите внимание, что он содержит имя программы expenses.c. Благодаря этому становится понятно, какую программу мы изучаем в данный момент. Это удобно при чтении распечаток текстов программ. Строка 5 содержит дополнительный комментарий с объяснением объявленных перемен- ных. В строке 7 объявляется массив из 13 элементов. Программа фактически использует 12 элементов, по одному на каждый месяц, а вот объявлено их 13. В цикле for (строки 14—18) элемент с номером 0 игнорируется. Благодаря этому программа может пользоваться элемен- тами от 1 до 12, непосредственно соответствующими номерам месяцев. В строке 8 объявля- ется переменная count, которая затем в программе служит счетчиком и индексом массива. Функция main() начинается со строки 10. Как уже говорилось, в программе используется цикл for для вывода сообщения и ввода значения расходов по каждому из 12 месяцев. Обра- тите внимание на использование функции scanf( ) B строке 17 с элементом массива. В стро- ке 7 массив expenses был объявлен как float, поэтому применяется спецификация ввода %f. Перед элементом массива стоит знак операции взятия адреса (&), точно так же, как он стоял бы перед обычной вещественной переменной, не являющейся элементом массива. Строки 22—25 содержат второй цикл for, который выводит только что введенные значе- ния. В функцию printf() добавлена одна новая спецификация формата для более упорядо- ченного вывода значений расходов. Спецификация %.2f указывает, что должно выводиться вещественное число с двумя цифрами после десятичной точки. Дополнительные команды форматирования рассматриваются более подробно на занятии 14, посвященном работе с эк- раном,принтеромттклавиатурой.    Рекомендуется  ?'Объявляите массивы ‘вместо несколь— Ё *ких переменных. содержащих однотип— начин іо`т „ную информацию Например, для хране— F" —    Не. забывёй‘т‘ёе что  Мет—‚\;?к-Ё      мня данныХ o6 обьеме' сбыта за 12 Me- , іёёЁаЖЁ: * ’ ,ссяцев лучше завести массив из “12 ‘ Ёе… › ;…:лемёнтов чем двенадцать отдельных * i”? * > , , j” , «\ , › … - переменных ”, a _: . i _ „_ Ё ! ‘ 3.33;“ * . “3:”: gm её, _ , ‚ … » „,; i {_ M... A „м…: ”___”… J L _ _ .. a   …ьм…№…__„…ді…’ » н...—№ ‚.... ‚. .` m _; мы. м.… › ›— ! »…" „__-ам   174 Неделя 2. Основные вопросы 
Многомерные массивы  В многомерных массивах используется сразу несколько индексов. Например, двумерный массив имеет два индекса, трехмерный —— три, и т.д. Количество измерений массива в языке С не ограничено. (Как будет показано далее, имеется ограничение на общий размер массива.) Пусть, например, мы пишем программу, играющую в шашки. Шашечная доска содержит 64 клетки в восемь рядов и восемь столбцов. Эту доску можно представить в программе как двумерный массив:  int checker[8][8];  B массиве содержится 64 элемента: checker[0][0], checker[0][l], checker[0][2], ..., checker[ 7] [ 6 ], checker[ 7] [7]. Структура этого двумерного массива показана на рис. 8.3.  int checker[8][8];  checker[0][0] lchecker[0][1]§ --------- |сЬесКег[1][0]Ё checker[1][1] ......... |сЬесКег[2][0]Ё Ichecker[2][1] --------- lchecker[2][7]  lcheckérnumE |checkérm[1]E --------- lcheckérmmg  Puc. 8.3. Табличная структура двумерного массива       checker[0][7]  checker[1][7] %    …        Аналогично, трехмерный массив можно представить себе в виде куба. Чегьтрехмерньте (и большей размерности) массивы уже с трудом поддаются воображению. Все массивы незави— симо от их размерности располагаются в памяти последовательно. Более подробную инфор- мацию о хранении массивов в памяти можно найти в материале занятия 15.  Имена и объявления массивов  Правила именования массивов ничем не отличаются от требований, предъявляемых к именам переменных (см. занятие 3). Имя массива должно быть уникальным, Его нельзя ис- пользовать для другого массива или объекта любого другого вида (переменной, константы, функции и т.д.). Как вы уже заметили, объявление массива имеет ту же форму, что и объявле- ние обычных переменных, только за именем массива должно следовать количество его эле- ментов в квадратных скобках. При объявлении массива количество элементов можно указывать литеральной константой (как в предыдущих примерах) или символической константой, созданной с помощью дирек- тивы #define. Вполне допустима такая запись:  #define MONTHS 12 int array[MONTHS];  Она эквивалентна следующему объявлению:  День 8-й. Массивы числовых данных 175 
int array[12];  Учтите, что компиляторы, как правило, не допускают указания длины массива в виде Kon- CTaHTbI, объявленной с помощью ключевого слова const: const int MONTHS = 12; int array[MONTHS]; /* Ошибка! */  Листинг 8.2 представляет программу grades .с, еще один пример использования одно- мерного массива. Программа помещает в массив десять оценок успеваемости студентов.  Листинг 8.2. grades . с ——-— ввод оценок в массив   /* grades.c - простая программа c массивом */ /* Ввод 10 оценок и вычисление средней */  #include <stdio.h>  #define MAX_GRADE 100 #define STUDENTS 10  С!) ‘~J (,5 (J1 ::> \А’ ") "‘ .. .. .. .. .. .. .. ..  \О ..  int grades[STUDENTS];  ыыы Р`) t-J <=> .. .. о.  int idx; int total = 0; /* для средней оценки */  Н..—] .:В СА) .. во,  int main( void ) { 16: for( idx = 0; idx < STUDENTS; idx++) 17: { 18: printf( "Enter Person %d's grade: ", idx +1); 19: scanf( "%d", &grades[idx] );  ..; (J1 ..  21: while ( grades[idx] > MAX_GRADE ) 22: { 23: printf( "\nThe highest grade possible is %d", 24: MAX_GRADE ) ; 25: printf( "\nEnter correct grade: “ ); 26: scanf( "%d“, agrades[idX] ); 27: }  29: total += grades[idx]; 30: }  32: printf( "\n\nThe average score is %d\n", ( total / STUDENTS) );  34: return (О);   Enter Person 1's grade: 95 Enter Person 2's grade: 100 Enter Person 3's grade: 60 Enter Person 4's grade: 105   176 Неделя 2. Основные вопросы 
The highest grade possible is 100 Enter correct grade: 100 Enter Person 5's grade: 25 Enter Person 6's grade: 0 Enter Person 7's grade: 85 Enter Person 8's grade: 85 Enter Person 9's grade: 95 Enter Person 10's grade: 85 The average score is 73  Как и expenses. с, эта программа запрашивает у пользователя данные. Необхо- , димо ввести оценки десяти студентов Вместо вывода всех оценок программа вычисляет и отображает среднюю оценку. Как вы уже знаете, имена массивов подчиняются тем же требованиям, что и имена обыч- gftux переменных. B строке 9 объявляется массив под именем grades Из имени легко понять, что в массиве будут храниться оценки В строках 6 и 7 объявляются две константы МАХ _GRADE Д STUDENTS При желании значения констант можно легко изменить Зная, что константа ЭЩЮШТБ равна 10, мы знаем, что массив grades содержит десять элементов Объявлены . две переменные: idx и total. Переменная idx — сокращение от index — используется “качестве счетчика и индекса массива. Текущая сумма всех оценок накапливается в пере- ' ‚нной total. Основную работу выполняет цикл for B строках 16—30. В цикле переменной idx … сваивается начальное значение 0, первый индекс массива. Затем цикл выполняется, пока ' меньше количества студентов. При каждом проколе цикла idx увеличивается на единицу. .1. який раз программа запрашивает оценку очередного студента (строки 18 и 19). Обратите ` ’ мание, что в строке 18 к idx прибавляется единица, чтобы считать студентов от 1 до 10, а от О до 9. Поскольку нумерация элементов массивов начинается с нуля, первая оценка по- ;. щается в элемент grades[0] Согласитесь, все-таки лучше запрашивать оценку студента . мер 1, чем смущ шать пользователя просьбой и“ввести оценку студента номер 0”. З В строках 21 -—27 находится цикл while, вложенный в цикл for. Цикл выполняет про- рку правильности введенной оценки: она не должна превышать максимальную оценку . Х __.GRADE Если оценка больше допустимой, пользователь приглашается ввести пра— immune значение Всегда проверяйте допустимость введенных данных в своих програм- ах, когда это возможно. В строке 29 введенная оценка прибавляется к уже накопленной сумме. В строке 32 по общей Ёумме оценок вычисляется средняя оценка (total/ STUDENTS), которая и выводится на экран.    1&—  ‚тРекомендуется Не рекоМендуется  спользуйте в объявлениях массивов” He используйте Мйогомерньіе массивы ; определенные через #define. g с размерностью больше трех если этого E „;их помощью можно легко изменить ко— g E можно избежать Помните;` что объем „чество элементов в массиве Напри- Es3aHuMaeMofi массивом памяти растет :zjjff'em в программе grades. с можно ;очень быстро с увеличением его… раз—; ” вменить количество студентов в дирек- Ё мернбсти №; E ве #def1ne и никаких других изменений i ** ":?” E 1 E      ‚де понадобится E ;  I , ` _; ’ I \ ‹ и №…; мс… . ‚_.—дни.… ‘. ‚.. „№,—.д….” › апа—.и…» д.ц-ими. М .… w, A” 4.4 .;…4 ‘ . „ ___   %день 8-й. Массивы числовых данных 177 
Инициализация массивов  При объявлении массива можно инициализировать его полностью или частично. Для это- го следует поставить после объявления массива знак равенства и список значений, разделен— ных занятыми, в фигурных скобках. Перечисленные в списке значения присваиваются эле— ментам массива, начиная с нулевого. Рассмотрим такой пример: int array[4] = { 100, 200, 300, 400 }; B этом примере значение 100 присваивается элементу array[ 0 ], значение 200 —— элементу array[1 ], значение 300 —-— элементу array[ 2 ], и значение 400 —-— элементу array[ 3 ]. Если не указать длину массива при инициализации, компилятор создаст массив такой длины, чтобы поместились все начальные значения. Поэтому следующий оператор полно- стью равносилен предыдущему: int array[] = { 100, 200, 300, 400 }; Впрочем, можно указать и меньше начальных значений, чем элементов массива: int array[10] = { 1, 2, 3 }; Если не инициализировать элемент массива явным образом, то неизвестно, какое значе- ние он будет содержать при запуске программы. Если указать слишком много начальных зна- чений (больше, чем элементов в массиве), компилятор сообщит об ошибке. Согласно стан- дарту ANSI, неинициализированным элементам присваивается значение 0.   … Не полагайтесь на автоматическую инициализацию массивов компи- CUBE“! лятором. Для гарантированного результата лучше самостоятельно присваивать начальные значения.     Инициализация многомерных массивов  Многомерные массивы также можно инициализировать. Начальные значения присваива- ются по порядку элементам массива, причем первым пробегается последний индекс массива. Рассмотрим следующий пример инициализации:  int array[4][3] = { 1, 2, з, 4, 5, 6. 7. 8, 9, 10, 11, 12 }; Этот оператор эквивалентен следующим операторам присваивания:  array[0][0] = 1; array[0][1] = 2; array[0][2] = 3; array[1][0] = 4; array[1][1] = 5; array[1][2] = 6; array[2][0] = 7; array[2][1] = 8; array[2][2] = 9; array[3][0] = 10; array[3][l] = 11; array[3][2] = 12;  При инициализации многомерных массивов можно сделать код понятнее, введя дополни- тельные скобки для группировки начальных значений и располагая группы в отдельных стро- ках. Предыдущий оператор инициализации массива эквивалентен следующему:  178 Неделя 2. Основные вопросы 
int array[4][3] = { {1: 2: 3}: {4: 5: 6}: {7! 81 9}! {10! 111 12} };  Не забывайте отделять запятой начальные значения даже в том случае, когда между ними есть скобка. Следите за соответствием открывающих и закрывающих скобок, иначе компиля- тор сообщит об ошибке. А сейчас рассмотрим пример, демонстрирующий преимущества работы с массивами. Программа из листинга 8.3, random.c, создает трехмерный массив из 1000 элементов и за- полняет его случайными числами. Затем программа отображает элементы массива на экране. Только представьте себе, сколько сил и времени занял бы набор исходного кода для решения ` той же задачи без применения массивов. В этой программе используется новая библиотечная функция getchar( ). Она считывает с клавиатуры отдельный символ. В листинге 8.3 эта функция приостанавливает выполнение программы, пока пользователь не нажмет клавишу <Enter>. Более подробно функция getchar() рассматривается на занятии 14.  ; Листинг 8.3. random. с — создание многомерного массива   1: /* random.c - демонстрация работы с многомерным массивом */ 2: 3: #include <stdio.h> 4: #include <std1ib.h> 5: /* Объявление трехмерного массива из 1000 элементов */ 6: 7: int random_array[10][10][10]; 8: int a, b, c; 9: 10: int main( void ) 11: { 12: /* Заполнение массива случайными числами. Библиотечная */ 13: /* функция rand() возвращает случайное число. По каждому */ 14: /* индексу массива организуется цикл for. */ 15: 16: for (a = 0; a < 10; a++) 17: { 18: for (b = 0; b < 10; b++) 19: { 20: for (с = 0; с ‹ 10; с++) 21: { 22: random_array[a][b][c] = rand(); 23: } 24: } 25: } 26: 27: /* Вывод элементов на экран порциями по 10 */ 28: 29: for (a = 0; a < 10; a++) 30: { 31: for (b = 0; Ь ‹ 10; Ь++) 32: { 33: for (с = 0; с < 10; c++)  День 8-й. Массивы числовых данных 179 
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:  }  printf("\nrandom_array[%d][%d][%d] = ", a, b, c); printf("%d", random_array[a][b][c]);  } printf("\nPress Enter to continue, CTRL—C to quit."); getchar(); } } return 0;  /* конец шаіп() */  180  random_array[0][0][0] random_array[0][0J[1] random_array[0][0][2] random_array[0][0][3] random_array[0][0][4] random_array[0][0][5] random_array[0][0][6] random_array[0][0][7] random_array[0][0][8] random_array[0][0][9]  346 130 10982 1090 11656 7117 17595 6415 22948 31126  Press Enter to continue, CTRL—C to quit.  random_array[0][1][0] random_array[0][1][1] random_array[0][1][2] random_array[0][1][3] random_array[0][1][4] random_array[0][1][5] random_array[0][1][6] random_array[0][1][7] random_array[0][1][3] random_array[0][1][9]  9004 14558 3571 22879 18492 1360 5412 26721 22463 25047  Press Enter to continue, CTRL-C to quit  random_array[9][8][0] random_array[9][8][1] random_array[9][8][2] random_array[9][8][3] random_array[9][8][4] random_array[9][8][5] random_array[9][8][5] random_array[9][8][7] random_array[9][8][8] random_array[9][8][9]  6287 26957 1530 14171 6951 213 14003 29736 15028 18968  Press Enter to continue, CTRL-C to quit.  random_array[9][9][0] random_array[9][9][1]  28559 5268  Неделя 2. Основные вопросы 
random_array[9][9][2] = 20182 random_array[9][9][3] = 3633 random_array[9][9][4] = 24779 random_array[9][9][5] = 3024 random_array[9][9][6] = 10853 random_array[9][9][7] = 28205 random_array[9][9][8] = 8930 random_array[9][9][9] = 2873 Press Enter to continue, CTRL-C to quit.  Ha занятии 6 мы рассмотрели программу, в которой использовался вложенный цикл for. A B этой программе используется сразу два вложенных цикла. Прежде {нем перейти к подробному рассмотрению циклов, отметим объявление четырех переменных 9 строках 7—8. Первая из них— это трехмерный целочисленный (int) массив random __array для хранения 10x 10x 10 = 1000 случайных целых значений. Представьте себе, каково было бы 9635131411, B программе тысячу отдельных переменных, каждую со своим именем, если не Ьспользовать массив! В строке 8 объявляются три переменные а, b и с для управления цикла- и for. ' Программа включает заголовочный файл stdlib.h (ero название происходит от словосо- етання standard library) B строке 4. Он содержит прототип функции rand( ), используемой в . оке22. *» Основная работа программы выполняется в двух группах вложенных циклов for. Первая Ё’руппа содержится в строках 16—25, а вторая —— в строках 29—42. Оба вложенных цикла име- одну и ту же структуру. Они аналогичны циклам из листингаб. 2, но на один уровень бже. В первом наборе циклов for многократно повторяется оператор в строке 22. Этот ератор присваивает значение функции rand() текущему элементу массива random_array, queM rand( ) представляет собой библиотечную функцию, генерирующую случайное число. Вернемся немного назад. В строке 20 переменная с изменяется от 0 до 9. Благ0даря этому икл прохолит все значения последнего (правого) индекса массива random_array. B строке 18 , ыполняется цикл по b, среднему индексу. При каждом значении b no очереди перебираются е значения с. В строке 16 выполняется цикл по переменной &, и соответственно по первому ‘ евому) индексу массива. При каждом значении этого индекса выполняется перебор 10 зна- ‚ений индекса b, a при каждом значении индекса b перебирается десять значений индекса с. ЁЁ итоге все элементы массива random _array получают случайные значения. E9 Строки 29—42 содержат вторую группу вложенных циклов for. Принцип их работы тот fine, что и у предыдущих, но в них присвоенные значения выволятся на экран. После готображения десяти значений в строке 38 выводится сообщение и ожидается нажатие клавиши <Етег>. В строке 40 нажатие обрабатывается с помощью функции getchar( ). Если абыла нажата другая клавиша, функция getchar() ожидает нажатия <Етег>. Запустите про- "грамму на выполнение и посмотрите, какие значения она выведет.    Максимальный размер массива  В ваших нынешних программах вам не следует создавать структуры данных общим объе- “мом более 64 Кбайт. Это связано со свойствами моделей памяти. Объяснение этого ограни- Ёнения выходит за пределы нашей книги, но беспокоиться здесь не о чем: ни Одна программа в ;(ниге не нарушает установленного предела. Для прояснения ситуации или для преодоления  ::пого ограничения обратитесь к руководству пользователя вашего компилятора. Обычно  Ёдень 8-й. Массивы чИсловых данных 181 
64 Кбайт данных вполне достаточно для несложных программ наподобие тех, что приведены в этой книге. Массив может сам занимать до 64 Кбайт памяти, если в программе не исполь— зуются никакие другие переменные. В противном случае следует соответственно распреде- лить имеющуюся память.   В некоторых операционных системах этого 64-килобайтного ограничения Пишет!!! не существует. Например, в DOS оно есть, а в Windows нет.     Размер массива в байтах зависит от количества элементов в нем и от длины каждого эле- мента. Длина элемента зависит от его типа, от характеристик компьютера и операционной системы. Длины числовых типов данных из табл. 3.2 приведены здесь еще раз для удобства в табл. 8.1. Это типичные размеры элементов данных на различных моделях персональных компьютеров.  Таблица 8. 1 . Размеры ячеек памяти для данных на персональных компьютерах `   Тип элемента данных длина элемента данных (в байтах)   int short long long long float double  Ф-ЬФ-ЬМ-Ь   Чтобы вычислить объем памяти, необходимый для хранения массива, следует умножить количество элементов в массиве на длину элемента. Например, для хранения массива из 500 элементов типа float потребуется 500х4 = 2000 байт. Как уже было сказано, длину переменных различных типов в байтах можно определить с помощью операции sizeof. Это одноместная операция, а не функция. Ее аргументом может быть имя переменной или типа данных. Она возвращает длину аргумента в байтах. Использо- вание операции sizeof иллюстрируется программой листинга 8.4.  Листинг 8.4. arraysize .с — определение объема памяти, занимаемого массивом, с помощью sizeof   1: /* демонстрация операции sizeof() */ 2: 3: #include <stdio.h> 4: 5: /* Объявление нескольких массивов по 100 элементов */ 6: 7: int intarray[100]; 8: float floatarray[100]; 9: double doublearray[100]; 10: 11: int main() 12: { 13: /* Отображение размеров числовых типов данных */  182 Неделя 2. Основные вопросы 
J4:   .15: printf("\n\nSize of int = %d bytes", sizeof(int)); 16: printf("\nSize of short = %d bytes", sizeof(short)); 17: printf("\nSize of long = %d bytes", sizeof(long)); 18: printf("\nSize of long long = %d bytes", sizeof(long long)); ,19: printf("\nSize of float = %d bytes", sizeof(float)); ”20: printf("\nSize of double = %d bytes", sizeof(double)); 21: ;22: /* Отображение размеров трех массивов */ '23: ??4: printf("\nSize of intarray = %d bytes", sizeof(intarray)); #25: printf("\nSize of floatarray = %d bytes", 126: sizeof(floatarray)); ;27: printf(“\nSize of doublearray = %d bytes\n", Ё28: sizeof(doublearray)); Ё29: 30: return 0; 2,31: } %; Вот какой результат выдает программа на 32—разрядном компьютере под управлением Ё№"іпсіош5 3.1:   "\ 'г, > Ъ  $   Size of int = 2 bytes Size of short = 2 bytes Size of long = 4 bytes Size of long long = 8 bytes Size of float = 4 bytes Size of double = 8 bytes Size of intarray = 200 bytes Size of floatarray = 400 bytes Size of doublearray = 800 bytes  Результат  А на 32—разрядном компьютере под управлением Windows NT, Linux или UNIX результат удет следующим: Size of int = 4 bytes Size of short = 2 bytes Size of long = 4 bytes Size of long long = 8 bytes Size of float = 4 bytes Size of double = 8 bytes Size of intarray = 400 bytes Size of floatarray = 400 bytes Size of doublearray = 800 bytes  - №№; …пд№= 'r_ _ панды-«„ * № 9.1?“ % №, „;;. Sf E3253. 53”… вы… , _ s   Резцпьтат  Ia а % ;& Ь & `: t g: "Я»: „ Ё . ‚:”  Введите и скомпилируйте эту программу, следуя процедуре, изученной на самом первом занятии. После запуска программы она выведет на экран длину (в бай- flax) трех массивов и шести числовых типов данных. 3; На занятии 3 была рассмотрена аналогичная программа, но отличие нынешней програм- ;иы состоит в том, что она вычисляет также и длины массивов. В строках 7, 8 и 9 объявляются ”гри массива разных типов. В строках 24—27 выводятся длины этих массивов. Длина каждого   a Г  Ёдень 8-й. Массивы числовых данных 183  
массива должна равняться количеству его элементов, умноженному на размер элемента. На- пример, длина значения типа int — 4 байта, значит, intarray должен иметь длину 4x100 = 400 байт. Запустите программу и проверьте это. Как видно из приведенных результатов, в различных аппаратно-системных средах типы данных имеют разную длину.   Количество элементов в массиве можно определить, разделив длину ШШШ" массива на длину одного элемента. Для массива doublearray из лис- тинга 8.4 количество элементов можно найти следующим образом:  arraySize = sizeof(doublearray) / sizeof(double);     Резюме  На этом занятии были рассмотрены числовые массивы —- мощное средство хранения дан- ных, которое позволяет группировать однотипные элементы под единым именем. Отдельные элементы массива различаются по индексу, который стоит после имени. Особенно удобны массивы при обработке однотипных компьютерных данных, требующей многократного по- вторения одних и тех же операций. Как и другие переменные, массивы необходимо объявлять до использования. При объяв- лении массива его элементы можно, хотя и не обязательно, инициализировать — присвоить им начальные значения.  Вопросы и ответы  Что произойдет, если при обращеиии к массиву использовать индекс, выходящий за его пределы? Программа, в которой использован такой индекс, скорее всего благополучно пройдет компиляцию и запустится на выполнение. Однако результаты подобной ошибки окажутся не- предсказуемыми. К тому же когда эта ошибка начнет причинять неприятности, ее не так-то легко будет найти в программе. Поэтому при инициализации элементов массива и обращении к ним будьте особенно осторожны.  Что произойдет, если обратиться к элементам иеинициализированного массива? Эта ошибка не обнаруживается компилятором. В неинициапизированном массиве могут находиться какие угодно значения. Поэтому результат предсказать нельзя. Чтобы знать на- верняка, какие значения хранятся в переменных и массивах, их нужно инициализировать яв- ным образом. На занятии 12 будет рассмотрено одно исключение из этого правила. А пока что следуйте самым безопасным путем.  Какова максимально допустимая размерность массива? Как уже говорилось на этом занятии, количество измерений массива не ограничено. Чем больше размерность, тем больше места занимает массив в памяти. Чтобы избежать ненужных затрат памяти, объявляйте массивы точно таких размеров, какие требуются.  Есть ли простой способ инициализировать сразу весь массив? Всегда необходимо инициализировать каждый отдельный элемент массива. Самый про- стой способ для начинающих программистов на С —— 3T0 инициализация при объявлении массива (см. выше в этой главе) или в цикле for. Есть и другие способы инициализации мас- сивов, рассказ о которых выходит за пределы этой книги.  184 Неделя 2. Основные вопросы 
Можно ли складывать массивы (а также умножать, делить или вычитать)? Два отдельных массива нельзя сложить между собой одной операцией. Разрешается скла- дывать только отдельные элементы. Упражнение 10 иллюстрирует это утверждение. Можно, например, написать функцию, которая складывает два массива. Конечно, внутри этой функ- ции по-прежнему придется складывать отдельные элементы этих массивов.  В чем преимущество массивов над набором отдельных переменных? В массиве можно объединить группу Однотипных данных п0д одним именем. Например, в листинге 8.3 массив содержал 1000 чисел. Чтобы объявить 1000 отдельных переменных и инициализировать каяшую случайным числом, потребовался бы огромный объем исходного кода. А с помощью массива эта же задача решается легко и быстро.  Что делать, если требуемый размер массива не известен заранее в процессе написа- ния программы? В языке С есть функции, позволяющие выделить память для переменных и массивов по ходу выполнения программы. Они изучаются на занятии 15.  {Коллоквиум ‘3  [и E: B этом коллоквиуме читателю предлагаются контрольные вопросы для закрепления изу-  ЁЁННОГО материала и упражнения ДЛЯ развития практических НЗВЫКОВ.  & В).  ііонтрольные вопросы  ;… Зі. Какие типы данных С можно использовать для объявления массивов? Какой индекс имеет первый элемент массива, содержащего десять элементов? } " Какой индекс имеет последний элемент одномерного массива, содержащего п элементов?  Что случится, если в программе произойдет обращение к элементу массива с индексом, выходящим за объявленные пределы?  Как объявляется многомерный массив?   _,.» Пусть массив объявлен следующим оператором. Сколько всего в нем элементов? а:, . g; 1nt array[2][3][5][8]; ?. Каким будет индекс десятого по порядку элемента в массиве из вопроса 6? $. Как определить количество элементов в массиве xyz, если его элементы имеют тип long?  31, Напишите оператор С для объявления трех одномерных целочисленных массивов с име- Ё, нами one, two и three, содержащих 1000 элементов каждый.  Ё. Напишите оператор для объявления целочисленного массива из 10 элементов и инициали- ‘ зации всех его элементов значением 1.  а, &; Имея объявленный ниже массив, напишите фрагмент кода для инициализации всех его элементов значением 88:  int eightyeight[88];   Т:*нь 8—й. Массивы числовых данных 185 
4. Имея объявленный ниже массив, напишите фрагмент кода для инициализации всех его элементов значением 0:  int stuff[121[10];  5. Поиск ошибок. Найдите ошибку в следующем фрагменте кода: int x, y; int array[101[31; int main( void )  { for ( x = 0; x < 3; x++ ) for ( Y = 0: Y < 10: У++ ) arraYIXIIYI = 0; return 0; }  6. Поиск ошибок. Найдите ошибку в следующем фрагменте кода: int array[10];  intx= 1; int main( void ) {  for ( x = 1; x <= 10; x++ ) array[x1 = 99; return 0; } 7. Напишите программу, которая заполняет случайными числами двумерный массив разме— ром 5x4 элемента. Выведите числа столбцами на экран. (Подсказка: воспользуйтесь функцией rand( ) из листинга 8.3.)  8. Перепишите программу из листинга 8.3 так, чтобы использовался одномерный массив. Выведите на экран среднее арифметическое 1000 чисел, а затем все числа по отдельности. Примечание: не забудьте делать паузы после вывода каждых десяти чисел.  9. Напишите программу, инициализирующую массив из десяти элементов. Каждый элемент должен стать равным своему индексу. Затем программа должна вывести все десять эле— ментов на экран.  10. Измените программу из упражнения 9. После вывода начальных значений массива про- грамма должна скопировать их в новый массив, прибавив 10 к каждому элементу. Затем необходимо вывести на экран новые значения.  186 Неделя 2. Основные вопросы 
 „.... г.. гк     Указатели    . Ha этом занятии мы рассмотрим очень важное средство языка С — указатели. Их приме ] ение позволяет очень мощно и гибко управлять данными в программах. Будут изучены сле ющие вопросы.  I Определение указателя Применение указателей Объявление и инициализация указателей  Использование указателей с простыми переменными и массивами  Использование указателей для передачи массивов в функции  _ При изучении материала сегодняшнего занятия вам, возможно, не сразу станет ясно, за ем нужны указатели. Их преимущества можно разделить на две категории. Одна включае' " — операции, которые с помощью указателей выполняются легче и удобнее, чем без указате V ей. Вторая же включает операции, которые без применения указателей вообще невозможны ' о мере освоения этого и следующих занятий специфические особенности указателей стану онятнее. А сейчас поверьте на слово: если хотите стать настоящим программистом на С, Ban ридется освоить указатели.  то такое указатель  Чтобы хорошенько понять указатели, необХОДимо кое-что знать о хранении данных в па яти компьютера. В следующем разделе кратко рассказывается об организации компьютер `_ ой памяти.  амять компьютера  Память (ОЗУ) персонального компьютера состоит из многих тысяч и даже миллионов по … едовательных ячеек, каждой из которых присвоен свой адрес. Адреса памяти образуют не рерывный диапазон от нуля до максимального значения, зависящего от объема установлен [ ой в системе памяти. _ Когда компьютер работает, часть памяти используется операционной системой. При вы олнении программы ее код (последовательность инструкций на машинном языке) и данньп 
(информация, используемая программой) также занимают часть памяти. В этом разделе мы интересуемся именно хранением данных. При объявлении переменной в программе на С компилятор выделяет для хранения этой переменной участок памяти с уникальным адресом. Компилятор ассоциирует этот адрес с именем переменной. Всякий раз, когда в программе используется имя переменной, автомати— чески происходит обращение по адресу к соответствующему месту в памяти. Программист не знает этого адреса, и обычно его не нужно знать. На рис. 9.1 это показано схематически. Объявлена и инициализирована значением 100 пе— ременная с именем rate. Компилятор выделил для нее ячейку памяти с адресом 1004 и ассо— циировал имя rate с этим адресом.  1000 1001 1002 1003 1004 1005 I I I I ято! TJ  Переменная rate    Puc. 9.1. Размещение переменных в памяти по адресам  Создание указателей  Заметьте, что адрес переменной rate (как и любой другой переменной)—— это просто число, и с ним можно обращаться как с обычным числовым значением. Зная адрес перемен- ной, можно завести вторую переменную и поместить в нее адрес первой. Первый шаг в этой процедуре— объявить переменную для хранения адреса переменной rate. Назовем ее, на* пример, p_rate. Вначале она не инициализирована. Для нее выделяется память, но никакое значение туда не помещается, как показано на рис. 9.2.  1000 1001 1002 1003 1004 1005  I I I" ‘ I  I |?11Г106ГП 1  p_rate rate   Puc. 9.2. Выделение памяти для переменной p_rate  Следующий шаг——— поместить адрес переменной rate B p_rate. Поскольку переменная p_rate теперь содержит адрес rate, она указывает на rate, как выражаются в терминах С. или является указателем на rate. Иллюстрацией к этому служит рис. 9.3.  1000 1001 1002 1003 1004 1005 ;г— ‚г—‚, 17 \ ,?”№* чдг „, 41' 1 J1004 | J J 100 |  1' 11  p_rate rate    Puc. 9.3. Переменная p_rate ——- указатель на rate  188 Неделя 2. Основные вопросы 
Итак, указатель — это переменная, содержащая адрес другой переменной. Теперь можно переходить к технике работы с указателями в С.  ;Указатели и простые переменные  * В приведенном примере переменная—указатель указывала на простую переменную (то есть е массив). В этом разделе рассказывается, как создавать и использовать указатели на про- ' ые переменные.   {О бъя вление указателей  Указатель представляет собой числовую переменную, поэтому его необХОДимо объявить еред использованием. Имена указателей должны подчиняться тем же требованиям, что и {Тюбые другие имена переменных, и быть уникальными. На этом занятии используется сле- %, ощее соглашение: если имя переменной — name, то имя указателя на нее будет p_name. Это ’ равило не является обязательным: указатели можно называть как угодно, лишь бы собЛЮДа— f; ись правила именования переменных в С.  Объявление указателя выглядит следующим образом: - вла *Имяука 3;  Здесь вмятина — любой из типов данных С. Он определяет тип переменной, на которую {: казывает указатель. Звездочка (*) —— это знак операции ссылки по указателю, и в данном чае он означает, что имяуказ является указателем на переменную типа вмятина, а не самой " еременной этого типа. Указатели можно объявлять вместе с обычными переменными. Вот {— ще несколько примеров: Зла: *chl, *ch2; /*ch1 и ch2 - указатели на char */ ’?loat *value, percent; /* value — указатель на float, a */ %* percent - обычная переменная типа float */   Символ * используется для обозначения как ссылки по указателю, так и № умножения. Не стоит беспокоиться, что компилятор не сможет различить эти два случая. Контекст использования * вполне четко определяет, что именно значит этот символ в каждом конкретном случае.     нициализация указателей  Вот мы и объявили указатель. А что с ним можно делать дальше? Пока что ничего. Вна— `; але нужно сделать так, чтобы он на что—то указывал. Как и в случае обычных переменных, спользование неинициализированных указателей дает непредсказуемые результаты и потен— ально опасно, хотя и возможно. Пока указатель не содержит адреса переменной, он беспо- Ёлезен. Адрес в указателе не появится сам собой —— его необходимо туда поместить с помо— Е<`щью операции взятия адреса, обозначаемой амперсандом (&). В сочетании с именем перемен— Ёной, перед которой стоит амперсанд, эта операция возвращает адрес данной переменной. {Поэтому указатель можно инициализировать таким оператором присваивания:  Ёухазатель = &переменная; E ъ  {День 9-й. Указатели 189 
Вернемся к примеру на рис. 9.3. Оператор присваивания адреса переменной rate указате- лю p_rate выглядит следующим образом:  p_rate = &rate; /* присвоить адрес rate указателю p_rate */  До инициализации с помощью этого оператора указатель 'р rate ни на что конкретно не указывает. После инициализации он указывает на переменную rate.  Работа C указателями  Теперь вы знаете, как объявлять и инициализировать указатели. Может возникнуть во— прос, как же работать с ними дальше. В первую очередь надо знать операЦию ссылки по ука- зателю (*). Когда знак этой операции стоит перед именем указателя, такая конструкция обо— зиачает переменную, на которую указывает указатель. Вспомним предыдущий пример, в котором указатель p_rate был инициализирован адре— сом переменной rate. Записав *p_rate, мы тем самым ссылаемся на rate. Если необходимо вывести значение rate (B данном примере это 100), то можно записать это двумя способами: printf("%d", rate); printf("%d", *p_rate);  /  B языке С оба эти оператора эквивалентны. Использование значения переменной " hlll [1 путем указания ее имени называется прямым обращением к переменной. Ис- пользование значения переменной через указатель на нее называется косвенным обращением или ссылкой по указателю (адресу) . На рис. 9.4 показано, как имя указателя со знаком ссыл- ки перед ним ссылается на значение переменной.   1000 1001 1002 1003 1004 1005      Г {1064 '1' ` Г 100 №! 1 l 3 1 \ p_rate rate *p_rate  Puc. 9.4. Использование операции ссылки по указателю  Давайте остановимся на некоторое время и обдумаем изученное. У‘казатели являются не- отъемлемой частью программирования на С, и понимать их очень важно. Многие люди не сразу свыклись с идеей указателей, поэтому и вам не стоит беспокоиться. Может понадобить- ся время и многократное повторение материала. Возможно, полезным будет следующее крат— кое резюме. Если есть указатель с именем ptr, которому присвоено значение адреса переменной var, то справедливы следующие утверждения:  I *ptr и var обозначают содержимое переменной var (т.е. значение, помещенное в нее программой); I ptr и &var обозначают адрес этой переменной в памяти.  Как видите, имя указателя без операции ссылки обозначает само значение указателя, то есть адрес переменной, на которую он ссылается. В листинге 9.1 приведены примеры элементарных операций с указателями. Введите. скомпилируйте и запустите эту программу.  190 Неделя 2. Основные вопросы 
іистинг 9. 1 . рік . с — элементарная работа с указателями  іпі  {  1 2 3 4 5 Б 7 8 9 О 1 2 В @ 55 % Ё @ to a а в 5L5 g; Ю @ @   Резцпыпат    /* Элементарная работа c указателями. */ #include <stdio.h>  /* Объявление и инициализация целой переменной */  var = l;  /* Объявление указателя на целую переменную */ іпі *ptr;  int main( void )  /* Инициализация рік адресом var */ ptr = svar; /* Прямое и косвенное обращение к var */  printf("\nDirect access, var = %d", var); printf("\nlndirect access, var = %d", *ptr);  /* Вывод адреса var двумя способами */  printf("\n\nThe address of var = %d", &var); printf("\nThe address of var = %d\n", ptr);  return 0;  Direct access, var = 1 Indirect access, var = 1  The address of var = 4202504 The address of var = 4202504   дет таким ЖЭ, как здесь ПОКЭЗЭНО.   На дРУгом компьютере адрес переменной var совсем не обязательно бу-    іень 9-й. Указатели  В этой программе объявляются две переменные. В строке 7 объявляется пере— менная var типа іпі и инициализируется значением 1. В строке ll объявляется ідазатель рік на переменную типа іпі. В строке 17 этому указателю присваивается адрес Ёременной var с помощью операции взятия адреса (&). Остальные операторы программы ;;ЫВОДЯТ значения этих двух переменных на экран. В строке 21 выводится значение var, a B ngOKe 22 — значение по адресу, находящемуся в указателе ptr. B обоих случаях это ОДНО и Ь же значение ~— единица. В строке 26 выводится адрес var с помощью операции взятия ад— Геса. То же самое значение выводится и в строке 27 из самой переменной-указателя ptr. 
Эта программа очень полезна. В ней демонстрируется связь между переменной, ее адре` сом, указателем и ссылкой по указателю.        Рекомендуется He рекомендуется a _. .-, §Ocaofi1e приятие указателей и работу с Не испопь`3уйтё неинициацизированным 1EHHMH‘: Это Heofixonman часть языка С. j j Ёуказатепь noxa‘jeMy не Присвоен адрес *‘…" ***”… ““ gnocnencr’ama могут оказаться катастро- срическиыпт ‚ … ч‚>‹_3-і;   ‹ д…,—т..…. . __ ..(д\—1‹«„ч-Ы…М№А\`  Указатели и типы переменных  До сих пор игнорировался тот факт, что переменные различных типов занимают различ— ный объем памяти. В самых распространенных системах на базе персональных компьютеров переменные типа short занимают 2 байта, float —— 4 байта, и т.п. Кажцый отдельный баиг памяти имеет свой адрес, поэтому переменная фактически занимает несколько адресов. Как же указатели ссылаются на адреса многобайтных участков памяти? А вот как: адрес- переменной фактически представляет собой адрес первого (младшего) байта из числа зани— маемых ею. Приведем пример объявления и инициализации трех переменных: short vshort = 12252;  char vchar = 90; float vfloat = 1200.156004;  Эти переменные размещаются в памяти так, как показано на рис. 9.5. На этом рисунке пе— ременная типа short занимает два байта, Char -— один байт, float —-— четыре байта.   vint vchar vfloat "H r* fl 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010  1200156004    Рис. 9.5. Размещение переменных различных типов в па- мяти  Теперь объявим и инициализируем указатели на эти три переменные: int *p_ vshort; char *p_ vchar; float *p_ vfloat; /* дополнительные операторы... */ p_vshort = svshort; p_vchar = &vchar; p_vfloat = avfloat;  Каждый указатель равен адресу первого байта переменной, на которую он указывает. По-- этому p_vshort равен 1000, p_vchar равен 1003, а p_vfloat равен 1006. Следует помнить, что все указатели указывают на переменные различных типов. Компилятору известно, что указа- тель на short указывает на первый из двух байтов, указатель на float -— на первый из четы— pex байтов и т.д. Это иллюстрирует рис. 9.6.  192 Неделя 2. Основные вопросы 
vint vchar vfloat     PM г—“Ч r— а 1000 1001 1002 1003 1004 1005 1006 r1007 1008 1009 1010 I 12252 : “до: : ; 120.0.156004-3ЧТ 1 I-p_vint I-p__vchar: tp_vfloat ! (2 байта по (1 байтпо (4 байта по адресу 1000) адресу 1003) адресу 1006)  Рис. 9.6. Компилятор знает длины переменных, на которые ссыла- ются указатели   ‘ - Ha рис. 9.5 и 9.6 между переменными показаны пустые промежутки в па- № мяти. Это сделано для наглядности. Фактически же большинство компи- ляторов C постараются разместить три переменных вплотную дРУг к другу без промежутков между ними. Однако не стоит специально рассчитывать L/ ни на тот, ни на другой вариант.      КЗЗЭТЭЛИ И МЭССИВЫ  .1 Хотя указатели могут быть полезны и при работе с простыми переменными, наиболее добны они в работе с массивами. В языке С указатели и массивы тесно связаны. Используя ндекс для обращения к элементу массива на занятии 8, посвященном ч‘исловым массивам,  уже фактически работали с указателем, еще не зная этого. В следующих разделах об этом › ассказывается подробнее.   Имя массива как указатель  Имя массива без квадратных скобок является указателем на первый элемент массива. На— ример, если объявлен массив data[ ], то data --— это адрес первою элемента данного массива. ; Как же так, спросите вы, ведь для получения адреса нужно поставить амперсанд перед \ менем? Это верно. Получить адрес первого элемента массива можно и таким способом: xdata[ 0 ]. В языке С справедливо соотношение (data == 8data[ 0] ). &’ Итак, имя массива является указателем на его первый элемент. Это имя к тому же являет- вся постоянным указателем (адресной константой): его нельзя изменить, и оно остается фик- Ёсированным все время выполнения программы. В этом есть здравый смысл: если изменить значение указателя, он будет указывать куда-то в другое место, а не на массив, который оста- днется в памяти там же, где он был. Для решения проблемы можно объявить отдельный указатель и инициализировать его *так, чтобы он указывал на массив. В приведенном ниже фрагменте кода переменная- ‚указатель p_array инициализируегся адресом первого элемента массива array[ ]: r"int array[100], *p_array; г/* дополнительные операторы... */ Ёр_аггау = array;   ; Поскольку р array — переменная-указатель, ее можно изменить так, чтобы она указыва-  Ёла на заданный адрес. В отличие от имени массива array, переменная р array не привязана к !  Шень 9-й. Указатели 193 
началу массива array[ ]. Она может, например, указывать и на другие элементы array[ ]. Как этого добиться? Вначале необходимо понять, как элементы массива располагаются в памяти.  Размещение элементов массива в памяти  Как вы, наверное, помните из материала занятия 8, элементы массива располагаются друг за другом в последовательных ячейках памяти, причем первый элемент— по самому млад— шему адресу. Следующие элементы (с индексами больше 0) помещаются по более старшим адресам. То, насколько следующий адрес старше предыдущего, зависит от типа данных в массиве: char, int, float и т.д. Возьмем для примера массив типа short. Как вы узнали на занятии 3 в связи с типами данных переменных и констант, значение этого типа занимает 2 байта памяти. Поэтому каж- дый следующий элемент массива находится на два байта дальше в памяти, чем предыдуший‚ и адрес следующего элемента соответственно на 2 больше, чем адрес предыдущего. А вот значение типа float занимает 4 байта. Поэтому в массиве типа float адрес каждого следую- щего элемента на 4 больше, чем предыдущего. На рис. 9.7 показано соотношение между адресами размещенных в памяти значений для массива типа int из шести элементов и массива типа float из трех элементов.  int x[6]; 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011  ГМЗ] J “111;“? Lida 1WD [WEI—E  Д . _ ‹ №…. .3 ‚каш “uni-v ‚ ’Г - «F5331   „и. m вы №№» 5‘ n-wwrw ›-  float expensesf3];  1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261  [ ЁрепЁеЗЕШГ ёхреёзезт J 39%?st 1°?  ‹… в‚№ Ёко—"БФ «› ‘i. Мда/іі м \ «шт nvnv ukk‘hA w Атм—›„Ъ. ».\mxvx~ “ „о I ы'хд w / ls» \    Рис. 9. 7. Размещение массивов разных типов в памяти  Из рис. 9.7 становятся очевидными следующие соотношения:  x == 1000 .&x[0] == 1000 &x[l == 1002  expenses == 1250 &expenses[0] == 1250 &expenses[l] == 1254  O‘U‘IthAJNl-J  ОО .‘ JO JO JO “  Символ x 6e3 квадратных скобок и индекса— это адрес первого элемента массива х[0 ]. Можно видеть, что этот адрес равен 1000. В строке 2 написано то же самое. Ее следует пони- мать так: “адрес первого элемента в массиве х равен 1000". Строка 3 показывает, что адрес второго элемента (с индексом 1) равен 1002. В этом тоже можно убедиться по рис. 9.7. Стро- ки 4, 5 и 6 практически тождественны строкам 1, 2 и 3. Их отличие состоит в разнице между адресами соседних элементов массива. В массиве ›‹ типа short разница составляет 2 байта, тогда как в массиве expenses типа float эта разница равна 4 байта. Как же обращаться к последовательным элементам массива с помощью указателя? Из приведенных примеров видно, что указатель нужно увеличивать на 2 для перебора элементов  194 Неделя 2. Основные вопросы 
`ассива типа short, и на 4 -- в массиве типа float. Обобщая, можно сказать, что указатель ' еличивается на sizeof(THII-11aHHHX) для обращения к следующему элементу. Операция izeof, которая дает размер элемента данных С в байтах, рассматривалась на занятии 3. Листинг 9.2 иллюстрирует соотношение между адресами и элементами массивов различ— типов. В программе объявляются массивы типа short, float и double, a затем выводятся Ё`__ 2 peca их элементов, следующих друг за другом.  истинг 9.2. asize .c — вывод адресов последовательных элементов ,;ассивов   $1: /* демонстрация соотношений между адресами и */ Ёг: /* элементами массивов разных типов. */ 3._ 3 : $4: #include <stdio.h> ‚Ёб : Ё”: /* Объявление счетчика и трех массивов. */ : int ctr; '42: short array_s[10];  'ёц- float array_f[10]7 {@: double array_d[10];  “Ё: int main( void )         7$: { {З: /* Вывод заголовка таблицы. */ Ё.: printf("\t\tShort\t\tFloat\t\tDouble"); L67: i3: printf("\n "); fi'- printf(" ")7 0 1 /* Вывод адресов всех элементов массивов. */ .::2.‘ `1:13: for (ctr = 0; ctr < 10; ctr++) fi4- printf("\nElement %d:\t%ld\t\t%ld\t\t%ld", ctr, gjs &array_s[ctr], &array_f[ctr], &array_d[ctr]); :6: f7: printf("\n "); ве: printf("-" --\n"); #0: return 0; 1: } i?" 3 Fl D bl РВЗЦЛЬШЗШ hort oat ou e Ё ' Element 0: 4206736 4206592 4206656 ; Element 1: 4206738 4206596 4206664 Ё Element 2: 4206740 4206600 4206672 6 Element 3: 4206742 4206604 4206680 Ё Element 4: 4206744 4206608 4206688 f Element 5: 4206746 4206612 4206696  r k  "я  hm) 9-й. Указатели 195 
Element 6: 4206748 4206616 4206704 Element 7: 4206750 4206620 4206712 Element 8: 4206752 4206624 4206720 Element 9: 4206754 4206628 4206728    “№ B другой системе точные значения выводимых адресов будут, скорее всего, от- личаться от приведенных, но соотношения между ними останутся такими же. В  этой распечатке результатов расстояние между адресами элементов типа short составляет 2 байта, float —- 4 байта, double -——- 8 байт.   — В некоторых системах эти переменные могут иметь другую длину. В таком "пишиш случае разница между адресами будет хотя и отличаться от приведенной, но останется постоянной и полностью соответствующей конфигурации системы.    \  . В этой программе используются специальные управляющие символы, рассмотренные на занятии 7. В вызовах printf() B строках 16 и 24 применяются символы табуляции (\t) для выравнивания столбцов таблицы. В программе объявляются три массива: в строках 8, 9 и 10. В строке 8 объявляется массив array_s типа short, B строке 9 —— массив array_f типа float, B строке 10 _; массив array_d типа double. B строке 16 выводится заголовок таблицы адресов. В строках 18 и 19, а также 27 и 28, выводятся пунктирные линии над и под данными таблицы. Это хороший стиль оформ- ления отчетов. Строки 23—25 содержат цикл for для вывода каждой строки таблицы. Вначале выводится номер элемента ctr, a затем адрес соответствующего элемента каждого из трех массивов.  Адресная арифметика  Адрес первого элемента массива легко получить по указателю —— его имени. Чтобы ука- зывать на последующие элементы, указатель должен получать приращения, кратные размеру элементов. Как же обращаться к элементам массива с помощью указателей? Для этого ис- пользуется адресная арифметика. “Ну вот”, ———- скажете вы, -——- “только этого нам не хватало: еще одна арифметика!”. Не стоит беспокоиться: адресная арифметика совсем простая, и с ее помощью работать с указа- телями в программе очень легко. По сути, следует знать только две основные операции: ин- крементирование и декрементирование.  Инкрементирование указателей  Инкрементировать указатель означает увеличить его значение. Например, если значение указателя инкрементируется на единицу, адресная арифметика автоматически увеличивает хранимый адрес так, чтобы он указывал на следующий элемент массива. Другими словами, компилятор знает тип указателя (из его объявления) и увеличивает хранимый в нем адрес на длину элемента данных. Предположим, ptr_to~short является указателем на какой-то элемент массива типа short. Пусть выполняется оператор:  ptr_to_short++;  196 Неделя 2. Основные вопросы 
; В результате значение ptr_to_short увеличится на длину типа short (обычно это 2 бай— Э“), И указатель теперь будет указывать на следующий элемент массива. Аналогичным обра- {зом, если ptr_to_float указывает на значение типа float, то следующий оператор увеличит Ёего значение на 4, то есть на обычную длину данных типа float B байтах: ргг_то_ііоас++; : Это утверждение справедливо и для инкрементирования больше чем на 1. Если прибавить Тк указателю число п, компилятор “передвинет” указатель по массиву на п элементов соответ- швующего типа. Поэтому следующий оператор фактически увеличит значение ptr_to_short на 8 (в предположении, что длина типа short равна 2 байта), и указатель будет указывать на Ёэлемент, находящийся на четыре позиции дальше: Ёіг_го_злогг += 4; E‘ Соответственно, следующий оператор увеличивает значение ptr_to_float Ha 40 (считая Ёлину типа float равной 4), и теперь указатель содержит адрес элемента на 10 позиций ьше, чем ранее:  ёс _to_float += 10;   l екременти рование указателей  То, что бьшо сказано об инкрементировании указателей, относится и к их декрементиро- ию. Собственно, декрементирование — это особый случай инкрементирования путем uH6aBJIeHHfl отрицательного значения. Если декрементировать указатель с помощью опера- ; й —- или -=, то адресная арифметика компилятора автоматически полстроится non размер ементов массива. _; В листинге 9.3 приведен пример использования адресной арифметики для работы с мас- ивом с помощью указателей.  истинг 9.3. ptr_math . с -— использование указателей и адресной ; рифметики для работы с массивом    „1: /* демонстрация использования адресной арифметики */ g2: /* для обращения к нассиван через указатели. */ г ,3: £4: #include <stdio.h> 5: #define MAX 10 :6: f7: /* Объявление и инициализация целочисленного массива. */ ,8: Ё9: int i_array[MAX] = { 0,1,2,3,4,5,6,7,8,9 }; 30: al: /* Объявление указателя на int и переменной типа int. */ 32: 93: int *i_ptr, count; J4: '15: /* Объявление и инициализация массива типа float. */ 16:  17: float f_array[MAX] = { .0, .1, .2, .3, .4, .5, .6, .7, .8, .9 };  19: /* Объявление указателя на float. */  День 9-й. Указатели 197 
21: float *f_ptr;   22: 23: int main( void ) 24: { 25: /* Инициализация указателей. */ 26: 27: i_ptr = i_array; 28: f_ptr = f_array; 29: 30: /* Вывод элементов массивов. */ 31: 32: for (count = 0; count < MAX; count++) 33: printf("%d\t%f\n", *i_ptr++, *f_ptr++); 34: 35: return 0; 36: } . 0 0.000000 1 0.100000 2 0.200000 , 3 0.300000 4 0.400000 5 0.500000 6 0.600000 7 0.700000 8 0.800000 9 0.900000  № В сгроке 5 программы объявляется константа МАХ, равная 10. Она активно ис- пользуется на протяжении всей программы. В строке 9 с ее помощью объявляет- ся количество элементов в массиве i_array типа int. Элементы в этом массиве инициализи- руются одновременно с его объявлением. В строке 13 объявляются две дополнительных пе- ременных. Первая из них — это указатель i__ptr. To, что это указатель, видно по звездочке перед его именем. Вторая переменная представляет собой целочисленный счетчик count. B сгроке 17 объявляется и инициализируется второй массив. Этот массив имеет тип float, со- держит МАХ значений и инициализирован вещественными числами. В строке 21 объявляется указатель f__ptr на переменную типа float. Функция шаіп() находится в строках 23—36. Программа присваивает адреса первых эле- ментов массивов указателям соответствующих типов B строках 27 и 28. Вспомните, что име- на массивов без квадратных скобок — это указатели на их первые элементы. Оператор for B строках 32 и 33 использует счетчик count для перебора от 0 до МАХ. При каждом значении в строке 33 выполняется обращение по указателям и вывод значений функцией printf( ). Затем каждый из двух указателей инкрементируегся, чтобы указывать на следующий элемент соот— ветствующего массива. Далее выполняется следующий шаг цикла for и т.д. Можно подумать, что все то же самое легко проделывается и с помощью индексной запи— си обращений к массивам, вовсе без указателей. Так и есть: в простых задачах, наподобие этой, применение указателей не особенно эффективно по сравнению с индексами. Однако в больших программных проектах, выполняющих сложные задачи, использование указателей может дать огромные преимущества.  198 Неделя 2. Основные вопросы 
Помните, что нельзя инкрементировать и декрементировать указатели— константы Такими константами, например, являются имена массивов без квадратных скобок.) Также '*` ,. едует помнить, что при обращении к массивам через указатели компилятор не следит за со— _ юдением границ, то есть за началом и концом массива. По небрежности можно заставить т азатель ссылаться на какое-то место в памяти до или после массива. Там, конечно, будет """ аходиться какое—то значение, но не из требуемого массива. Поэтому необходимо следить за ' м, куда указатели в действительности указывают.  : ругие операции с указателями  Еще одна действительно полезная операция с указателями — это вычисление смещения, то ‘ ь фактически вычитание двух указателей. Если есть два указателя на разные элементы од- ого массива, то их можно вычесть друг из друга и выяснить, на каком расстоянии они нахо— ся. Адресная арифметика обеспечивает автоматическое масштабирование результата, и в , ге расстояние выражается в количестве элементов. Пусть ptrl и ptr2 указывают на эле- "ен-ты, одного и того же массива (любого типа). Тогда следующее выражение дает расстояние И этими элементами: — ptr2 Указатели можно также сравнивать. Сравнение имеет смысл только для указателей на . ин и тот же массив. При соблюдении этого условия операции сравнения ==, !=, <, >, >= и <= полняются корректно. Младшие элементы массива (то есть элементы с меньшими индек— „ и) всегда имеют и меньшие адреса, чем старшие элементы. Таким образом, если ptrlu tr2 указывают на элементы одного массива, то следуюшее соотношение истинно тогда, ко- _ а ptrl указывает на элемент с меньшим индексом, чем ptr2: ;дст1 ‹ ptr2 Мы рассмотрели все возможные операции над указателями. Некоторые арифметические перации, выполняемые над числовыми переменными, не имеют никакого смысла в приме— нии к указателям — например, умножение и деление. Поэтому компиляторы С не позволя— H выполнить эти операции. Например, если ptr — указатель, то следующий оператор вызо- ‘ сообщение об ошибке: ”e *= 2; « В табл. 9.1 перечислены все допустимые операции над указателями, рассмотренные на м занятии.  % аблица 9.1 . Операции над указателями    Описание  Указателю можно присвоить значение: адрес, полученный операцией &, или взятый из адресной константы (имени массива)   & 1‘1  „{; * Ссылка на значение Операция ссылки (*) дает значение, находящееся в переменной, на ко- E3 торую указывается (иногда называется разыменованием) .-s’ ЁЁ, Получение адреса Можно применить таюке к указателю, получив указатель на указатель. :52 Эта тема рассматривается на занятии 15 \ ёё» Инкрементирование Прибавление целого числа к указателю дает новый адрес в памяти ? декрементирование Вычитание целого числа из указателя дает новый адрес в памяти  іВычисление смещения Расстояние (смещение) между двумя указателями вычисляется вычи- ` танием одного указателя из другого  Сравнение Операция действительна только для указателей на один и тот же массив   ! ень 9-й. Указатели 199 
Потенциальные опасности  При написании программ, в которых используются указатели, необходимо избегать одной серьезной ошибки: использования неинициализированного указателя в левой части оператора присваивания. Например, в этом операторе объявляется указатель на значение типа int:  int *ptr;  Этот указатель еще не инициализирован, поэтому ни на что не указывает. Точнее говоря, он не указывает ни на что известное. У неинициализированного указателя есть какое-то зна— чение, просто мы его не знаем. Во многих случаях это 0. Допустим, этот неинициализирован— ный указатель используется в операторе присваивания:  *ptr = 12;  Значение 12 помещается по адресу, на который указывает ptr. Этот адрес может отно- ситься к произвольному месту памяти, где, например, размещается операционная система или к0д самой программы. Значение 12 может затереть важную информацию, и в результате слу- чится все, что угодно: от странных ошибок выполнения программы до полного краха сисгемы. Итак, в левой части оператора присваивания неинициализированный указатель наиболее опасен. Однако его использование и в других местах программы тоже может вызвать ошибки разной степени серьезности. Поэтому старайтесь корректно инициализировать указатели пе- ред тем, как их использовать. Это необхоцимо делать самостоятельно, ни в коем случае не полагаясь на компилятор.  Рекомендуется Не рекомендуется  :Помните‚ что прибавление целого числа :He применяйте к указателям такие K‘ указателю или вычитание из него из- арИФметические операции как умноже- Меняет уіказатель на количество байт, ние и деление,(в т; ч до мадулю) допус— Ьсоответствующее его типу, a, не Ha это кается „только инкрементирование и вы— туш—‚пе. Совпадение иМеет место толёко в читание для вЫчиспения смещения.: “: ’ :спучае символьного однобайтного типа. » ёНе пытайтесь инКрементироватьй‘йли Узнаите размеры типов данных ’Ha ва- T nexpenfieHmeBaw лереМенную-массив шем KQManojrepé Эта информация He-f Присвойте адрес начала массива дРУГо— обходима для работы с указателями и му указателю, а_ потом работаите с ним  )*,“  памятью „:::: № 9i 52‘ w£3,552;jig”:(cm2rmc1'HHr93) - „ {@  :’( 31'“? цін?“ „ a: 2" ”щ", …: \;; @@     i Ё [ 2  у;..і:       Индексная запись и указатели  Имя массива без квадратных скобок — это указатель на первый элемент массива. Поэтому к первому элементу можно обращаться с помощью операции ссылки по указателю. Пусть объявлен массив array[ ]. Тогда выражение *array дает его первый элемент,  * (array + 1) — второй, и т.д. Обобщая на весь массив, можно записать следующие соотно— имения: *(array) == array[0] *(array + 1) == array[1] *(array + 2) == array[2] *(array + n) == array[n]  200 Неделя 2. Основные вопросы 
Отсюда становится очевидной связь между обращением к массивам по индексам и через ‘ ерации с указателями. Можно пользоваться любым из способов по своему выбору. Компи- ор С воспринимает оба способа как два разных пути ссылки по адресам.  ередача массивов в функции  ___—.; Мы уже обсудили вопрос о том, как связаны в языке С указатели и массивы. Эта связь ".: обенно важна для передачи массивов через аргументы функций. Единственный способ пе- - дать в функцию массив — это воспользоваться указателем. Из материала занятия 5, посвященного функциям, вы знаете, что аргумент—_ это переда- емое в функцию значение. Оно может иметь любой простой тип, например, int, float H и но обязательно должно быть одиночным числовым значением. Это может быть отдель- _тй элемент массива, но не весь массив. Что же делать, если необходимо передать в функ- 1 o целый массив? Воспользоваться указателем на массив, который представляет собой оди- дчное числовое значение —— адрееттервого элемента массива. Если передать его в функцию, ункция будет знать адрес массива и сможет обратиться к нему с помощью операции ссылки іё.указателкъ Рассмотрим еще одну проблему. Если функция должна принимать аргумент-массив, то дотелось бы, чтобы она могла работать с массивами различной длины. Например, функция _} ожет находить максимальный элемент в произвольном массиве целых чисел. Мало пользы ыло бы от функции, ограниченной B своих возможностях массивами какой-то одной кон- . ной размерности. Откуда функция должна знать длину массива, адрес которого ей передан? Ведь значение __."0TBCTCTBleLHeFO аргумента ——- это всего лишь указатель на первый элемент массива, кото- . й с одинаковым успехом может быть первым из 10 или первым из 1000 cm элементов. шествует два основных способа сообщить функции размер массива. __ Во-первых, можно пометить последний элемент массива, поместив в него специальное ачение. При обработке массива функция проверит все элементы, и как только обнаружит ', . специальное значение, будет знать, что массив закончился. Недостаток этого способа за- ‘ ючается в том, что придется зарезервировать некоторое значение для единственной цели ~— , . ывать конец массива. Это ухудшит гибкость обработки информации в массиве. :, Второй способ более гибкий и непосредственный. Именно он используется в программах й книги: передача длины массива в функцию в явном виде. Это может быть просто аргу- " HT типа int. Таким образом, функция получает минимум два аргумента: указатель на пер- ый элемент массива и целое число, обозначающее количество элементов в массиве. ;, Программа листинга 9.4 получает список значений от пользователя и помещает их в мас- в. Затем вызывается функция largest( ), B которую передается массив (указатель и длина), ' нкция находит максимальное значение в массиве и возвращает его B вызывающую про-  ‚7:  _амму. истинг 9. 4. passing. с —- передача массива в функцию  /* Передача массива в функцию. */ #include <stdio.h> #define MAX 10 : int array[MAX], count;  E1: 2. Ёз: 4: E5 6 E7  Ё Ёень 9-й. Указа тели 201 
8: 9: int largest(int num_array[], int length);  10: 11: int main( void ) 12: { 13: /* Ввод чисел с клавиатуры в количестве MAX. */ 14: 15: for (count = 0; count < MAX; count++) 16: { 17: printf("Enter an integer value: "); 18: scanf("%d", aarray[count]); 19: } 20: 21: /* Вызов функции, отображение полученного значения. */ 22: ргіпсі("\п\пЬагчезс_уа1пе = %d\n", largest(array, MAX)); 23: 24: return 0; 25: }  26: /* Функция largest() возвращает максимальное значение */ 27: /* в массиве целых чисел */   28: 29: int largest(int num_array[], int length) 30: { 31: int count, biggest = —12000; 32: 33: for ( count = О; count < length; count++) 34: { 35: if (num_array[count] > biggest) 36: biggest = num_array[count]; 37: } 38: 39: return biggest; 40: } ' Enter an integer value: 1 Enter an integer value: 2 Enter an integer value: 3 Enter an integer value: 4 Enter an integer value: 5 Enter an integer value: 10 Enter an integer value: 9 Enter an integer value: 8 Enter an integer value: 7 Enter an integer value: 6  Largest value = 10 B этом примере указатель на массив передается в функцию largest( ). Прототип  функции находится в строке 9, и, если не считать точки с запятой, он идентичен заголовку функции в строке 29.  202 Неделя 2. Основные вопросы 
Все элементы заголовка функции в строке 29 и общий его смысл должны быть уже знако- мы вам: функция с именем largest() возвращает целочисленное значение; ее второй аргу- мент —— целое значение, представленное параметром length. Новостью здесь является запись int num_array[ ]. Это означает, что первый аргумент— указатель на int, представленный параметром num_array. Заголовок функции можно было бы записать и таким образом:  int largest(int *num_array, int length);  Эта форма эквивалентна предыдущей: как int num_array[ ], так и int *num_array озна— чают “указатель на int”. Первая форма часто бывает предпочтительней: она напоминает, что мы имеем дело с указателем на массив. Конечно же, сам указатель от этого не меняется, но функция обращается с ним именно как с указателем на массив. Рассмотрим функцию largest( ). При ее вызове парамегр num_array получает значение указателя на первый элемент массива. Соответственно, с ним можно работать как с указате— лем или как с массивом. В функции largest() обращение к элементам массива происходит через индексы (см. строки 35 и 36)… Можно также использовать и ссылки по указателям, пе— реписав оператор if B цикле следующим образом: for (count = 0; count < length; count++)  { if (*(num_array+count) > biggest) biggest = *(num_array+count);  B листинге 9.5 продемонстрирован другой способ передачи массивов в функции.  Листинг 9.5. passingZ . с —-— другой способ передачи массива в функцию   1: /* Передача массива в функцию. Второй способ. */ 2: 3: #include <stdio.h> 4: 5: #define MAX 10 6: 7: int array[MAX+1], count; 8: 9: int largest(int num;array[]); 10: 11: int main( void ) `12: { 13: /* Ввод чисел с клавиатуры в количестве MAX. */ 14: 15: for (count = 0; count < MAX; count++) 16: { 17: printf("Enter an integer value: "); 18: scanf("%d", &array[count]); 19: 20: if ( array[count] == 0 ) 21: count = MAX; /* для выхода из цикла for */ 22: } 23: array[MAX] = O; 24: 25: /* Вызов функции, отображение nonyqennoro значения. */  День 9-й. Указатели 203 
 26: printf("\n\nLargest value = %d\n", largest(array)); 27: 28: return 0; 29: } 30: /* Функция largest() возвращает максимальное значение */ 31: /* в массиве целых чисел */ 32: 33: int largest(int num_array[]) 34: { 35: int count, biggest = -12000; 36: 37: for ( count = 0; num_array[count] != 0; count++) 38: { 39: if (num_array[count] > biggest) 40: biggest = num;array[count]; 41: } 42: 43: return biggest; 44: } Enter an integer value: 1 РЮШЪШШП Enter an integer value: 2 Enter an integer value: 3 Enter an integer value: 4 Enter an integer value: 5 Enter an integer value: 10 Enter an integer value: 9 Enter an integer value: 8 Enter an integer value: 7 Enter an integer value: 6 Largest value = 10  A это результат повторного выполнения программы:  : Enter an integer value: 10 Enter an integer value: 20 Enter an integer value: 55 Enter an integer value: 3 Enter an integer value: 12 Enter an integer value: 0 Largest value = 55  В этой программе используется функция largest() того же назначения, что и в ‘ ‚ листинге 9.4. Разница состоит в том, что функция принимает только указатель на массив. Цикл for B строке 37 ищет максимальное значение до тех пор, пока не обнаруживает 0, и на этом заканчивает поиск. Уже в начале программы видны отличия от листинга 9.4. Во-первых, в строке 7 массив объявляется с одним лишним элементом, в котором будет храниться завершающий ноль.  204 Неделя 2. Основные вопросы 
_СтРоки 20—21 содержат оператор if, который проверяет, не ввел ли пользователь число 0, штобы сообщить о конце вв0да чисел. Как только вводится 0, счетчику count присваивается gem максимально возможное значение, чтобы аккуратно выйти из цикла for. B строке 23 по- Ёдаледнему элементу массива присваивается значение 0 на тот случай, если пользователь ввел Ётаксимально допустимое количество элементов (МАХ). … Итак, добавив пару команд при вводе данных, можно заставить функцию largest( ) pa60- ть c массивами любой длины. Однако здесь есть одна ловушка. Что если пропустить ноль в ‘онце массива? В этом случае функция largest( ) пойдет дальше за пределы массива, пере— рая числа в памяти, пока не обнаружит 0. Как видите, передача массивов в функции не представляет особых трудностей. В функ- ю просто передается первый элемент массива. В большинстве случаев передается также и ...личество элементов в массиве. Внутри функции переданный указатель используется для . рашения к элементам массива через индексы или операцию ссылки по указателю.    Вспомните материал занятия 5: при передаче простой переменной в - Illlllll функцию фактически передается копия ее значения. Функция может ис- пользовать это значение. но не может изменить исХОДную переменную, потому что не имеет доступа к ней. При передаче массива все происхо- дит по-другому. Функция получает адрес ИСХОДНОГО массива, а не просто копии значений. Поэтому операторы внутри функции работают напрямую с элементами массива и вполне могут их изменить.     eslome \  \  _ На этом занятии были рассмотрены указатели — центральная тема всего программирова— H ия на С. Указатель представляет собой переменную, в которой хранится адрес другой пере— _енной. Говорят, что указатель “указывает” на переменную, адрес которой он хранит. Для аботы с указателями необходимы две операции: взятие адреса (&) и ссылка по адресу (*). мя переменной со знаком операции взятия адреса перед ним обозначает не само значение ;: ременной, а ее адрес. Имя переменной—указателя со знаком ссылки перед ним обозначает . значение, которое хранится по указанному им адресу. ”7 Указатели и массивы находятся в тесной связи. Имя массива без квадратных скобок явля- я указателем на его первый элемент. Особые средства адресной арифметики позволяют гко манипулировать элементами массивов с помощью указателей. Способ обращения к ассиву по индексу фактически является одним из способов работы с указателями. ` Мы рассмотрели также передачу массивов в функции через аргументы путем передачи азателя. Зная адрес массива и количество элементов в нем, функция может обращаться к го элементам как через индексы, так и посредством ссылки по указателю.  ЁВопросы и ответы  Почему указатели так важны для программирования на С? Ё Указатели обеспечивают очень гибкие возможности управления памятью компьютера и ;данными в ней. С помощью их передачи в функции можно изменять значения переменных- Ёргументов независимо от места их происхождения. Дополнительные возможности указате— тей будут рассмотрены на занятии 15.  Шень 9—й. Указатели 205 
Как компилятор различает случаи, когда символ звездочки * используется для ум. ножения, объявления указателя и ссылки по указателю? Компилятор распознает различные употребления звездочки по контексту. Если оператор начинается с объявления типа, то ясно, .что это объявление указателя. Если звездочка стоит перед переменной-указателем, но не в объявлении, значит, это ссылка. Если звездочка встре. чается в арифметическом выражении, но не перед указателем, следовательно, это операция умножения.  Что произойдет, если употребить знак операции взятия адреса перед указателем? Взятие адреса даст адрес переменной-указателя, ведь указатель -—— это всего лишь пере. менная, которая содержит адрес какой-то другой переменной.  Всегда ли переменные располагаются по одним и тем же адресам? Нет. При каждом запуске программы ее переменные всякий раз помещаются в разные места памяти. Поэтому нельзя присваивать указателям постоянные значения адресов.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления пройденно— го и упражнения для приобретения практических навыков программирования.  Контрольные вопросы  Какой знак операции используется для получения адреса переменной?  3°!“  Какой знак операции используется для получения значения по адресу, хранящемуся в ука— зателе?  Что такое указатель?  Что такое ссылка по указателю (по адресу)?  Как элементы массива располагаются в памяти?  ашдьы  Опишите два способа, которыми можно получить адрес первого элемента в массиве data[].  7. Опишите два способа определения конца массива, переданного в функцию посредством указателя.  8. Какие шесть операций можно выполнять над указателями?  9. Рассмотрим два указателя. Пусть первый из них указывает на третий элемент массива це- лых чисел (int), а второй указывает на четвертый элемент. Какое значение получится, ес— ли вычесть первый указатель из второго? (Предполагается, что длина целой переменной равна 2 байта.)  10. Пусть массив из вопроса9 содержит вещественные числа типа float. Какое значение получится в этом случае при вычитании указателей? (Предполагается, что длина вещест- венной переменной этого типа равна 2 байта.)  206 Неделя 2. Основные вопросы 
  ражнения  \ Объявите указатель на переменную типа char. Указатель должен получить имя char ptr.  ‘ I Пусть имеется переменная типа mt под именем cost. Объявите указатель р cost и ини- Циализируйте его значением адреса cost.  Продолжая упражнение 2, присвойте значение 100 переменной cost как прямым, так и косвенным обращением.  Продолжая упражнение 3, выведите на экран как значение указателя, так и значение пе- ременной, на которую он указывает.  Присвойте значение адреса переменной radius типа float некоторому указателю. Присвойте третьему элементу массива data[ ] значение 100 двумя способами.  Напишите функцию с именем sumarrays( ), принимающую два массива в качестве аргу- ментов, которая бы складывала все элементы обоих массивов и возвращала итоговую сумму. Напишите простую программу с использованием функции из упражнения 7. Напишите функцию с именем addarrays( ), принимающую два массива одинаковой дли-  ны. Функция должна складывать соответствующие элементы двух массивов и помещать результаты в третий массив.  День 9-й. Указатели 207 
Самостоятельная работа 3  Приостановка выполнения программы  Перед вами третья самостоятельная работа. Помните, что листинги в разделах само- стоятельной работы представляют дополнительные, нередко весьма практичные, приемы программирования по сравнению с изученными на обычных занятиях. Программа этого занятия содержит совсем немного новых элементов, так что у вас не должно возникнуть труцностей с ее пониманием и реализацией. После того, как вы введете и выполните про- грамму, некоторое время поэкспериментируйте с ее кодом. Внесите изменения, переком- пилируйте и выполните программу, посмотрите, что произойдет. При наличии ошибок по— старайтесь найти и исправить их.  Листинг СЗ. seconds .c — программа c функцией приостановки sleep   /* seconds.c */ /* Программа делает паузу. */  #include <stdio.h> #include <stdlib.h> #include <time.h>  Cl) "’ "` ‘1‘ СРБ СА, ") O-J о. о. о. о. о. о. о. о.  void sleep( int nbr_seconds );  ‚.: <=> \‘D о. о.  int main( void )  {  у.: ‚.А ") Ъ—д .. о.  int ctr; int wait = 13;  ‚.; р.: ‚.; (J1 0:5 (a) о. о. о.  /* Пауза на заданное количество секунд. Вывод * * точки каждую секунду ожидания. */  ‚.: ‚.; ‚.: (I) __! (5‘ I. о. о.  printf("Delay for за seconds\n", wait ); printf(">");  "’ Р—д <=> ", .. .. 
21: for (ctr=1; ctr <= wait; ctr++)  22: 23:; printf("."); /* вывод точки */ 24:; fflush(stdout); /* форсированный вывод из буфера */ 25: sleep( (int) 1 ); /* задержка на 1 секунду */ 25: } 27: printf( "Done!\n"); ?8: return (О); 29: } go: %1: /* Приостановка на заданное количество секунд */ :2: void sleep( int nbr_seconds ) { f4: clock_t goal; {$: Ёб: goal = ( nbr_seconds * CLOCKS_PER_SEC ) + clock(); „7: ;8: while( goal > clock() ) E: { _ё: ; /* пустой цикл */ = } ‘: }   Эта программа содержит функцию, которая может пригОдиться вам в других программах. нкция з1еер( ) приостанавливает программу на некоторое время, выраженное количеством нд. Все это время компьютер просто проверяет, дост точно ли долго длится ожидание. только заданное количество секунд прошло, функция Заканчивается и возвращает управ- ние программе. Оказывается, эта функция или ее варианты находят целый ряд применений. _‚ -за высокого быстродействия компьютеров иногда бывает нужно приостановить програм- f , чтобы пользователь успел прочитать выводимый на экран текст. Например, при запуске ограммы она может отображать информационную заставку. ;: В основном теле программы во время ожидания на экран просто для примера выводятся 'Ёдъчки. Между выводом двух точек программа приостанавливается на одну секунду с исполь- дванием описанной функции в1еер( ). Для интереса можно попробовать увеличить длитель- ь паузы и замерить время секундомером, чтобы проверить точность измерения времени } мпьютером. _ Программу можно изменить таким образом, чтобы она выводила точки (или что-нибудь _яугое) на протяжении заданного времени вместо простого ожидания. Попробуйте заменить оку 40 следующим оператором:  inntf("x");  _. _ ' -.'_.'. . ‚"-‘.' '‚':‚_` __ .. 4, » ' ‚__- ` _ №…, ..   Д Illllalll Если в вашей операционной системе уже есть программа под названием — з1еер. замените имя приведенной программы на другое. чтобы избежать конфликтов.     'Ёмостоятельная работа 3. Приостановка выполнения программы 209 
  Символы и строки  НПВЫЙШВПМПН ГОВОРЯ 0 символе, МЫ подразумеваем букву, инфру‚ знак препинания и т.п. Строка — это любая последовательность символов. В строках хранится тексто-  вая информация, состоящая из букв, цифр, знаков препинания и других символов. Строки и символы совершенно необхоцимы в программировании для выполнения целого ряда задач. На этом занятии будут изучены следующие темы.  I Использование стандартного типа данных char для хранения одиночных символов Хранение символьных строк в массивах типа char Инициализация символов и строк Работа со строками с помощью указателей  Ввод-вывод символов и строк  Тип данных Char  B языке С для хранения отдельных символов используется тип данных char. Ha занятии 3 было сказано, что тип char — это один из целочисленных типов данных С. Если char представ- ляет собой числовой тип, как же его можно использовать для хранения символьных данных? На этот вопрос легко ответить, если знать представление символьной информации в ком- пьютере. В памяти компьютера все данные представлены в числовой форме. Непосредствен- ного способа хранить символы не существует. Вместо этого каждому символу соответствует числовой код. Совокупность кодов отдельных символов образует код ASCII или набор симво- лов ASCII (сокращение от American Standard Code for Information Interchange —- Американ- ский стандартный код обмена информацией). В этом наборе буквам верхнего и нижнего ре- гистра, цифрам, знакам препинания и другим символам ставятся в соответствие числа от 0 до 255. Коды набора символов ASCII приведены в приложении А.   — Коды набора символов ASCII ориентированы на компьютерные системы 0 №“… представлением символов в виде одного байта. В системах, где символы ` представлены несколькими байтами, используются другие кодовые набо- ры. Обсуждение этой темы выходит за пределы нашей книги.    
Например, буква а в наборе ASCII представляется числом 97. Помещая символ a B nepe- менную типа char, мы фактически помещаем туда число 97. Поскольку диапазон допустимых значений типа char B точности соответствует длине набора ASCII, этот тип идеально подхо- дит для хранения символов. Казалось бы, здесь может возникнуть путаница. Если символы хранятся в виде числовых значений, как же программа различает, что именно содержит та или иная переменная типа char: числа или символы? Как будет показано позже, при работе со значениями типа char решающее значение имеет контекст их использования.  I Если переменная типа char используется в программе там, где ожидается символьная информация, то ее значение интерпретируется как символ. I Если переменная типа char используется в программе там, где ожидается числовая информация, то ее значение интерпретируется как число.  Вот вы и получили самое общее представление o том, как в языке С символьная информа- ция представляется с помощью числовых типов данных. Теперь перейдем к более подробно- му обсуждению.  Работа с символьными переменными  Переменные типа char, как и любые другие переменные, должны быть объявлены до ис- пользования. При объявлении их можно инициализировать. Вот несколько примеров:  char a, b, с; /* Объявление трех переменных без инициализации */ char code = 'х'; /* Объявление переменной code и присвоение ей */ /* начального значения в виде символа х */ code = '!'; /* Помещение символа ! в переменную code */  Для создания литеральной символьной константы один-}\символ заключается в одинарные кавычки. Компилятор автоматически переводит такой литерал в соответствующее числовое значение и присваивает его переменной. Можно создавать именованные символические константы, соответствующие отдельным символам, с помощью директивы #define или ключевого слова const: #define Ех 'х' char code = Ex; / * Помещает в code символ 'х' */ const char A = 'Z';  Итак, вы уже знаете, как объявлять и инициализировать символьные переменные. Пора привести пример небольшой программы. В листинге 10.1 демонстрируется числовая природа символьной информации. Для этой цели используется функция printf( ), изученная на заня- тии 7, посвященном вопросам ввода-выв0да. Функция printf( ) способна выводить как сим- волы, так и числа. Для вывода символа используется спецификация формата %с, а для вывода целого десятичного числа— спецификация %d. B листинге 10.1 объявляются и инициализиру- ются две переменных типа char. Затем они вывоцятся как в виде чисел‚ так и в виде символов.  Листинг 1 0.1 . chars . с — демонстрация числовой природы символьных данных 1  2 3: #include <stdio.h>   /* демонстрация числовой природы символьных переменных */  День 10-й. Символы и строки 211 
/* Объявление и инициализация двух символьных переменных */  ‚_в <2) KC) (X) -J C7\ (J1 nth  char c1 = 'a'; char с2 = 90; : int main( void ) 11: { 12: /* Вывод переменной с1 в виде символа и в виде числа */ 13: 14: printf("\nAs a character, variable с1 is %c", c1); 15: printf("\nAs a number, variable с1 is %d", c1); 16: 17: /* Проделать то xe самое с переменной с2 */ 18: 19: printf("\nAs a character, variable с2 is %c", c2); 20: printf("\nAs a number, variable c2 is %d\n", c2); 21: 22: return 0; 23: } -*   № На занятии 3 говорилось, что максимальное допустимое значение переменной типа char равно 127. В то же время диапазон символов набора ASCII простира- ется до 255. Но фактически набор ASCII делится на две части. Основной набор кодов заклю— чен в диапазоне 0—127: это все латинские буквы, цифры, знаки препинания и другие символы, имеющиеся на клавиатуре. Коды от 128 до 255 образуют расширенный набор ASCII и пред- сгавляют различные специальные символы: буквы других алфавитов (не латинского). симво— лы псевдографики и т.п. В приложении А приведена таблица с полным списком всего набора ASCII, как основного, так и расширенного. Таким образом, для символьной информации из основного набора достаточно и типа char, a BOT для работы с расширенным набором симво— лов необходимо использовать тип unsigned char. Программа в листинге 10.2 вывшит на экран некоторые специальные символы.  Листинг 1 0.2. ascii . с — вывод символов из расширенного набора ASCII   /* демонстрация вывода символов из расширенного набора ASCII */ #include <stdio.h> unsigned char mychar; /* unsigned — для расширенного набора */  int main( void )  ФЧФШОЁ-ШМН .. .. .. .. .. .. .. .. ..  { 9 /* Вывод символов расширенного набора от 180 до 203 */ 10: 11: for (mychar = 180; mychar < 204; mychar++) 12: { 13: printf("ASCII code %d is character %c\n", mychar, mychar); 14: } 15:  212 Неделя 2. Основные вопросы 
16: return 0; 17: }  ASCII code 180 is character ASCII code 181 is character ° ASCII code 182 is character ASCII code 183 is character ASCII code 184 is character ASCII code 185 is character ASCII code 186 is character ASCII code 187 is character ASCII code 188 is character ASCII code 189 is character ASCII code 190 is character ASCII code 191 is character ASCII code 192 is character ASCII code 193 is character ASCII code 194 is character ASCII code 195 is character ASCII code 196 is character ASCII code 197 is character ASCII code 198 is character ASCII code 199 is character ASCII code 200 is character ASCII code 201 is character ASCII code 202 is character ASCII code 203 is character  "1.  . и  08023“):  ЩИЫЖЫЫШЬЧШШЗ’Н3  / / / /  7 Если операционная система русифицирована или настроена на работу с ""д“"! каким—либо другим языком, выводимые этой программой символы расши-  ренного набора могут отличаться от приведенных выше.    В строке 5 этой программы объявляется переменная mychar типа unsigned char. Диапазон ее значений составляет от 0 до 255. Как и в случае других числовых переменных, ей нельзя присваивать начальное значение, выходящее за пределы допустимого диапазона, иначе можно получить непредсказуемые результаты. В сгроке 11 переменная mychar получает значение 180, причем это значение не выходит за пределы диапазона. В цик— ле for переменная mychar всякий раз получает приращение 1, пока не достигает значения 204. При каждом проходе цикла в строке 13 выводится значение mychar B виде кода ASCII и в ви— де символа. Помните, что спецификация формата %с указывает функции printf( ) вывести именно символ, являющийся значением mychar.  Manna;         Рекомендуется Не рекомендуется Используйте спецификацию %c для вы— не: используите Ami-1H8 кавычки pong 5 вода символьного значения целочислен— *инициапизанйи “ЁИЧВЁЁРНЁ‘Ё nepemeH- Ё Ёной Переменной. жид,; „же; ных №№ EM“ any; „я;—“М i Используйте одинарные кавычки прин Hé nbl‘l'aul'eCB поместить СИЙВОП ИЗ ; Ёинициализации символьных перемен—ЁЁ грасширенного набора АЗСЁЙЁ в пере-Ё :’ных. ‚ > ‚ дд % менную типа сЬаіЁсо знакомз ‘ ’ :    День 10-й. Символы и строки 213 
Изучитвт'абл й№к9д6 ’ASC‘W ggplfi ЁЁ - жении А;; Тама ёсть диво“- %  №6… „… «„ №№  рей 591%?”  v ’., l и! '1; “fig? &{Ёд ^    В некоторых компьютерных системах используется другой расширенный набор символов, но основной набор ASCII от 0 до 127. как правило, один и тот же.  `.     Работа со строками  В переменных типа char можно хранить только одиночные символы, так что от них не так уж много пользы. Для практических целей необходимо иметь способ хранить строки. Строки представляют собой не что иное, как последовательности символов. Хотя специаль— ного строкового типа данных в языке С нет;/строки можно хранить в массивах символов.  Массивы символов  Пусть, например, нам нужно работать со строкой длиной шесть символов. Для этого не обходимо завести массив символов из семи элементов. Массивы типа char объявляются точ- но так же, как и любые другие массивы. Например, в следующем операторе объявляется мас— сив типа char из десяти элементов:  char string[10];  B этом массиве можно хранить девять или меньшее количество элементов. Вы, наверное, удивитесь: ведь массив содержит десять элементов, почему же в нем можно хранить только девять символов? В языке С строка определяется как последовательносгь символов, заканчивающаяся нулевым символом. Нулевой символ —- это специальный символ, который обозначается \0. Хотя он записывается в виде двух знаков —- обратной косой черты и нуля —— компилятор воспринимает его как один символ, соответствующий коду ASCII 0. Он относится к числу управляющих последовательностей, или специальных символов языка С.   (пикши: Управляющие последовательности (специальные символы) языка С рас- сматривались на занятии 7.     Например, если в программе на С используется строка Alabama, фактически в ней хранит- ся семь символов A, l, a, b, a, m, a, и еще нулевой символ \0 —- всего восемь.  ИНИЦИЭЛИЗЭЦИЯ СИМВОЛЬНЫХ МЭССИВОВ  Как и данные других ТИПОВ, массивы СИМВОЛОВ МОЖНО инициализировать при объявлении. МОЖНО присвоить начальные значения КЮКДОМУ отдельному элементу, как показано здесь:  char string[10] ={ 'A', '1', 'a', 'b'. 'a', 'm', '3': '\0' }?  "ПВЫЙ MIME Гораздо удобнее, однако, использовать строковый литерал, т.е. последователь— ность символов в двойных кавычках:  214 Неделя 2. Основные вопросы 
char string[10] = "Alabama"; Всякий раз, когда в программе используется строковый литерал, компилятор автоматиче- ски добавляет нулевой символ в конец строки. Если при объявлении и инициализации масси- Ba СИМВОЛОВ ОПУСТИТЬ CFO длину, TO КОМПИЛЯТОР сам ВЫЧИСЛИТ ее ПО ДЛИНС литерала. Таким образом, в следующей строке создается и инициализируется массив из восьми элементов:  char string[] = "Alabama";  Помните, что каждая строка должна заканчиваться нулевым символом. Функции С для работы со строками (см. занятие 17) определяют длины строк по их завершающим символам. Другого способа распознать конец строки в этих функциях не существует. Если нулевой сим- вол пропущен, программа сочтет, что строка заканчивается только там, где в памяти случайно окажется нулевой символ. По этой заурядной причине случается немало досадных ошибок.  Строки и указатели  Итак, строки хранятся в массивах типа char и оканчиваются нулевым символом, причем могут и не занимать всего массива. Поскольку конец строки четко обозначен нулевым симво- лом, для однозначного определения строки нужно лишь задать нечто, указывающее на ее на- чапо. “Указывающее” — это именно то слово, которое описывает суть дела. С такой п0дсказ- кой вы уже и сами можете догадаться, о чем пойдет речь. Из материала занятия 9, посвященного указателям, вы знаете, что имя массива является указателем на первый элемент массива. Поэтому для обращения к строке, хранящейся в мас- сиве, достаточно знать имя этого массива. Использование этого имени является по сути стан- дартным методом С для работы со строками. Точнее говоря, библиотечные функции С для обработки строк используют имя массива для обращения к строкам. Стандартная библиотека функций С содержит целый ряд функций для манипулирования строками (они рассматрива- ются на занятии 17). Для передачи строки в одну из таких функций в нее просто передается имя массива. То же самое имеет место и для функций вывода текстовой информации printf() и puts( ), которые мы обсудим чуть позже. Выше упоминалось о “строке, хранящейся в массиве”. Значит ли это, что некоторые стро- ки могут и не храниться в массивах? Именно так! В следующем разделе объясняется, как это происходит.  Работа со строками без массивов  Из предыдущего раздела вы знаете, что строка определяется именем массива и нулевым символом. Имя массива представляет собой указатель типа char на начало строки. Нулевой символ задает конец строки. Фактическая длина строки может не совпадать с длиной отве- денного под нее массива. Собственно говоря, массив нужен для единственной цели -—— выделения памяти для хранения строки. А есть ли возможность выделить память без применения массива и поместить туда строку с завершающим нулевым символом? Указатель на первый символ строки определил бы стро- ку с тем же успехом, что и массив. Каким же образом выделить требуемую память? Для этого есть два способа: либо выделить память для строки-литерала при компиляции программы, либо воспользоваться функцией та11ос() для распределения памяти непосредственно при выполнении программы. Второй способ называется динамическим распределением памяти.  День 10-й. Символы и строки 215 
Выделение ПЭМЯТИ ДЛЯ CTpOKM ПРИ КОМПИЛЯЦИИ  Как было сказано ранее, начало строки определяется указателем на переменную типа char. BaM уже известно, как объявляются такие указатели:  char *message;  B этом операторе объявляется указатель с именем message на переменную типа char. По- ка он ни на что не указывает. А что если изменить объявление указателя таким образом: char *message = "Great Caesar\ ' s Ghost! ";  При выполнении этого оператора строка Great Caesar\ ' s Ghost! вместе с ее завершаю- щим нулевым символом помещается в память, и указатель message инициализируется таким образом, чтобы указывать на первый символ этой строки. Не стоит беспокоиться о том, где и как выделить память для строки, — об этом позаботится компилятор. После такого объявле— ния message становится указателем на строку, и с ним можно работать. , Предыдущее объявление с инициализацией эквивален—тно следующему:  сна;: messagen == "Great Caesar\'s словы";  ЭВ данном случае обе записи *message и message[ ] взаимозаменяемы и означают одно и то же} “указатель на ...”. „Этот способ распределения памяти для строк хорош, если длина строки известна заранее приінаписании программы. Но что если программа должна работать со строками переменной WI, зависящими, например, от введенных пользователем данных или других заранее неиз- №ьгх факторов? В этом случае используется функция ша11ос( ), с помощью которой мож‹ но‘ёраспределить память прямо по ходу выполнения программы.  @ункция malloc()  “функция malloc() принадлежит к числу функций распределения памяти языка С. При №№ malloc() в функцию передается требуемое количество байт памяти. Функция находит трудшет блок памяти требуемого размера и возвращает адрес первого байта этого блока. диск и распределение памяти происходит автоматически, программисту не следует об этом беспокоиться. ”’Функция malloc( ) возвращает адрес, поэтому тип возвращаемого ею значения определен ш указатель на void. Почему именно void? Потому что указатель этого типа совместим с Mum! типами данных. Его применение в данном случае логично и закономерно, поскольку №`1сция ша11ос( ) может использоваться для размещения в памяти объектов любого типа.  Функция malloc()  #include <stdlib.h> void *malloc(size_t размер);  Функция malloc() выделяет блок памяти длиной столько байт, сколько указано в , A аргументе размер. Если память распределяется при необходимости по хоцу выпол- нения программы, a не вся сразу перед ее началом, то компьютерные ресурсы ис- пользуются более экономно и эффективно. Для использования функции ша11ос() „„ программа должна подключить заголовочный файл stdlib.h. Некоторые компиля- торы позволяют подключать другие заголовочные файлы для этой цели. Однако из соображений переносимости программы лучше пользоваться stdlib.h.   Ёб Неделя 2. Основные вопросы 
Функция ша11ос() возвращает указатель на выделенный блок памяти. Если функция не смогла выделить требуемый объем памяти, она возвращает нулевой указатель. При любой попытке распределения памяти всегда следует проверять возвращаемое значе- ние, даже если запрашиваемый объем достаточно мал.  Пример1  E #include <stdlib.h> ; #include <stdio.h> % int main( void ) { /* выделение памяти для 100—символьной строки */ char *str; str = (char *) malloc(lOO); if (str == NULL)  "W“ ‘. ".` „"_-г,… .—  { E printf( "Not enough memory to allocate buffer\n" ); exit(l); ё .} = printf( "String was allocated1\n" ); return 0; } Пример2  /* выделение памяти для массива из 50 целых чисел */ , int *numbers; numbers = (int *) malloc(50 * sizeof(int));  ПримерЗ  /* выделение памяти для массива 10 вещественных чисел */ float *numbers; numbers = (float *) malloc(10 *sizeof(float));   .тв'чктм. "'”  абота с функцией та||ос()  Функцию ша11ос() можно использовать для размещения в памяти одиночного СИМВОЛа char). Вначале следует объявить указатель на char: char *ptr; Затем нужно вызвать функцию malloc( ) и передать в нее размер требуемого блока памя- ти. Переменная типа char занимает один байт памяти —— именно такой блок нам и понадобит- ся. Возвращаемое функцией ша110с( ) значение следуег присвоить объявленному указателю:  ptr = malloc(1);   Ё Этот оператор выделяет блок памяти размером один байт и присваивает его адрес указа-  телю ptr. B отличие от переменных, объявленных в программе, этот байт не имеет имени. К нему можно обратиться только через указатель. Например, для занесения в него символа 'х' используется следующий оператор: {*ptr = 'x';  ень 10-й. Символы и строки 217 
Память для строки с помощью функции malloc( ) выделяется почти так же, как и для от- дельной переменной типа char. Основное различие состоит в том, что необходимо знать дли- ну строки, т.е. количество символов в ней. Эта длина зависит от нужд программы. Пусть, на- пример, необходимо выделить место в памяти для строки из 99 символов плюс завершающий нулевой символ — всего 100. Для этого вначале объявляется указатель на char, a затем вызы- вается malloc( ): char *ptr; ptr = malloc(lOO); Теперь ptr указывает на выделенный блок длиной 100 байт, который можно использовать для хранения строки и всевозможных операций с ней. Этот блок обладает точно такими же свойствами, как и непосредственно объявленный в тексте программы массив: char ptr[100];  C помощью функции malloc( ) _программа может распределить память по мере необходи— мости и в требуемом объеме. Разумеется, доступный объем памяти не бесконечен. Он зависит от объема физической памяти в системе и от других запросов программы. Если памяти для выполнения запроса не хватает, malloc( ) возвращает нулевой указатель (0). Программа должна обязательно проверять возвращаемое из ша110с() значение, чтобы убедиться в пра— вильном распределении памяти. Возвращаемое значение следует сравнивать с символической константой NULL, определенной в заголовочном файле stdlib.h. B листинге 10.3 иллюстри- руется применение функции ша110с(). Напоминаем, что любая программа, вызывающая эту функцию, должна включить заголовочный файл stdlib.h с помощью директивы #іпсіисіг-Ё/  /   … В приведенных примерах память для строк распределялась с явным совет указанием их длины в байтах. Однако на практике всегда нужно ука- зывать эту длину в виде количества элементов данных, умноженного ‘ на размер одного элемента. Выше мы подразумевали, что один сим- вол имеет длину один байт. Ho в других системах символ может за- нимать и несколько байт. Тогда выделенной памяти не хватило бы. Поэтому объявление  ptr = malloc(lOO); следовало бы заменить следующим: ptr = malloc( 100 * sizeof(char));     Листинг 1 0.3. memalloc . с — выделение памяти для текстовых строк c помощью функции ша11ос( )   1: /* демонстрация выделения памяти для текстовых строк */ 2: /* с помощью функции malloc(). */ 3: 4: #include <stdio.h> 5: #include <stdlib.h> 6: 7: char count, *ptr, *p; 8: 9: int main( void ) 10: { 11: /* Выделение блока из 35 байт. Проверка правильности. */ 12: /* Библиотечная функция exit() завершает программу. */  218 Неделя 2. Основные вопросы 
13: 14: ptr = malloc(35 * sizeof(char));   ‘15: 16: if (ptr == NULL) 17: { 18: puts("Memory allocation error."); -19: return 1; 20: } 21: 22: /* Заполнение строки значениями от 65 до 90, */ 23: /* т. е. кодами ASCII для букв A—Z .*/ ‘24: ‘25: /* p - указатель для прохождения строки. */ 26: /* Перед началом цикла ptr указывает */ '27: /* Ha начало строки. */ 328: .29: p = ptr; 30: '31: for (count = 65; count < 91 ; count++) :32: *p++ = count; і'33: Ё34: /* добавление завершающего нулевого символа. */ ,35: '36: *р = '\0'; 37: `38: /* Отображение строки на экране. */ L39: I40: puts(ptr); ;41: '42: free(ptr); i43: 44: return О: г45: } ; Pummmmn ABCDEFGHIJKLMNOPQRSTUVWXYZ  '}; Эта программа иллюстрирует простейшее применение функции malloc( ). Ha , вид программа кажется довольно длинной, но это из- за изобилия комментариев. Подробное описание операций программы занимает строки 1, 2, 11, 12, 22—27, 34 и 38. В „строке 5 включен заголовочный файл stdlib. h для работы с malloc( ), a B строке 4 включен файл stdio.h, необходимый для использования функции puts( ). B строке 7 объявляются два Указателя и символьная переменная. Пока что ни одна из этих переменных не инициализиро- вана, поэтому не пригодна к использованию. В строке 14 вызывается функция malloc( ) с аргументом в виде произведения числа 35 и размера типа данных char. Почему нельзя обойтись просто числом 35? В принципе, можно, Однако тогда мы бы молчаливо предполагали, что переменная типа char всегда занимает ОДИн байт памяти. Вспомните занятие 3: мы уже говорили, что разные компиляторы могут :использовать переменные разной длины для одних и тех же типов данных. Включение в Ёрограмму операции sizeof улучшает переносимость кода.  ёё : Жень 10-й. Символы и строки 219 
Не следует думать, что функция malloc( ) всегда вылеляет требуемую память. Фактиче— ски, мы не приказываем распределить память, а запрашиваем ее у операционной системы. В строке 16 выполняется простая проверка, выделила ли функция malloc( ) нужное количество памяти. Если это так, указатель ptr указывает на соответствующий блок; если нет, ptr равен нулевому указателю. Если программе не удалось вьщелитьщеобхолимое количество памяти, то в строках 18 и 19 выводится сообщение об ошибке и выполняется корректное безаварий— ное завершение программы. В строке 29 инициализируется р —— второй указатель, объявленный в строке 7. Ему при— сваивается тот же адрес, что и ptr. Этот указатель используется в цикле for для помещения значений в выцеленный блок памяти. В строке 31 счетчик count инициализируется значением 65 и увеличивается на 1, пока не достигнет 91. При каждом проходе цикла for значение пе— ременной count помещается по адресу, на который указывает указатель р. Заметьте, что одновременно с приращением count указатель p также получает приращение. В результате значения помещаются в памяти одно за другим. Вы, наверное, заметили, что переменной count типа char присваиваются числовые значе- ния. Вспомните o к0де ASCII — числовых эквивалентах символов. Число 65 соответствует символу А, 66 соответствует В, 67 соответствует С и т.д. Цикл for заканчивается после поме— щения всего латинского алфавита в выделенный блок памяти. В строке 36 символьная строка завершается нулевым символом, помещаемым по последнему адресу, на который указывает р. После внесения в память нулевого символа вся совокупность значений становится полно- правной символьной строкой. Указатель ptr по-прежнему указывает на первый символ этой строки —— букву A. Поэтому если этот указатель передать в функцию вывода как строку, то/на экране будет отображена вся строка до завершающего ее нулевого символа. В строке 40 именно это и делается с помощью функции puts( ) для вывода окончательного результата. В строке 42 используется новая функция —— free( ). Если память распределяется динами- чески, то по окончании работы с выделенным блоком его нужно освобоцить —— вернуть опе— рационной системе. Функция free() B строке 42 высвобождает ранее выделенный блок, на который указывает ptr, и возвращает его в распоряжение системы.   Рекомендуется Не рекомендуется  ‚,ЁЙЁЁЧ'ЁЁЁЙЁ a4“ '. * Hemp Quantum сим gummy ма сё?“  {blue СИМВО    J „ mu.- ‘ ._ _ _; v ’ЁГЁЗ‘ - _ ‘ ` . ЁЦЁЧЁЁ “"`—:( «_.-9% .‘ "’  ‚, „№ \ №Щ'№рьезную ошибку  ‚; Hg Han ешассир садержал три символа,  ’^4 %ЁеЗЁГтч: 4% U’n‘s  №№ ‘4me4 И за ршающиу ъРуль Теперь в 4  Hero noMe „ Tea 4‘Hem4é4’cfiMaona4’W': °  " 4’ :, ЁЁЁ “№? … ‚в., и :Hyn евра ажно предсказать  i *‘;  какие даъ’ін bye ветре ‹ ‚„;гвертвпи chanson ЙЁ;  yucca?” 4‘4  ьТахе прэйзойдеш№№ 5$ W:      220 Неделя 2. Основные вопросы 
Вывод строк и символов  Если программа работает со строковыми данными, то ей почти наверняка приходится вы- водить их на экран. Вывод строк обычно осуществляется функциями puts( ) и printf( ).  Функция puts()  Библиотечная функция puts() уже встречалась в некоторых программах нашей книги. Эта функция выводит строку текста на экран, отсюда и ее имя —— сокращение от put string (вывести строку). Единственным ее аргументом является указатель на строку, которую нуж— но вывести. Поскольку литеральные строки интерпретируются компилятором как указатели на строки, функция puts( ) может выводить как переменные строкового типа, так и литералы. После каждой выведенной строки функция автоматически выполняет переход на новую стро— ку, так что результаты нескольких последовательных вызовов puts() будут располагаться в отдельных строках. Использование функции puts( ) иллюстрируется в листинге 10.4.  Листинг 1 0.4. put . с — отображение текста на экране c помощью puts ( )   /* демонстрация вывода строк с помощью puts(). */ #include <stdio.h>  char *messagel = "C"; char *messageZ = "is the"; char *message3 = "best“; char *message4 = "programming“; char *messages = "language!!";  H ouoooxsmmutht—A  11: int main( void ) 12: { 13: puts(messagel); 14: puts(messageZ); 15: puts(message3); 16: puts(message4);   17: puts(messageS); 18: 19: return 0; 20: } ° №№… is the best programming language!!  ПНШШЗ Это очень простая программа, и разобраться в ней совсем нетрудно. Включение заголовочного файла stdio.h в строке 3 НСОбХОДИМО для того, чтобы можно бы—  ло использовать функцию puts( ). B строках 5—9 объявляются и инициализируются пять раз-  День 10—й. Символы и строки 221 
личных переменных, содержащих строковые сообщения. Каждая из них одновременно явля- ется и указателем на char, и строковой переменной. В строках 13—17 все эти строки выводят-  ся на экран с помощью функции puts( ).  Функция printf() \ “—  Строки можно выводить на экран также с помощью библиотечной функции printf( ). Вспомните занятие 7: в printf() для оформления выводимых данных используется строка формата и спецификации вывода. Для вывода строковых данных применяется спецификация %s. Когда в строке формата printf() встречается спецификация %s, функция сопоставляет ей соответствующий аргумент из списка. В данном случае аргумент должен иметь тип указателя на строку. Строка выводится на экран, пока не встретится завершающий нулевой символ. Вот пример: char *str = "A message to display"; printf(“%s", str); Можно вывести сразу несколько строк, перемежая их с переменными — как числовыми, так и символьными: char *bank = “First Federal"; char *name = "John Doe“; int balance = 1000; printf("The balance at %s for %s is %d.", bank, name, balance);  B результате будет выведено следующее: ’ The balance at First Federal for John Doe is 1000.  Пока что этих знаний вполне достаточно, чтобы вы могли выводить строковые данные в ваших программах. Более полное и подробное описание возможностей функции printf() бу— дет дано на занятии 14, посвященном работе с экраном, клавиатурой и принтером.  ВВОД строк C клавиатуры  Кроме вывода строк на экран, программы часто нуждаются в том, чтобы ввести опреде— ленную строковую информацию, которую пользователь набирает на клавиатуре. Для этой це- ли в С есть две функции: gets() и scanf( ). Но прежде чем вводить строку с клавиатуры, нужно выделить для нее место в памяти. Свободное пространство для вводимой строки мож- но создать двумя способами: объявлением массива или с помощью функции malloc( ).  Ввод строк с помощью функции gets()  Функция gets( ) вводит с клавиатуры строку текста. При вызове gets( ) она считывает все символы, набранные на клавиатуре, вплоть до первого символа конца строки (который гене- рируется при нажатии клавиши <Enter>). Функция сбрасывает символ конца строки, добавля— ет нулевой завершающий символ и передает получившуюся строку в вызывающую програм— му. Строка помещается по адресу, переданному в функцию gets() через аргумент— указа- тель на char. Для использования функции gets( ) необходимо включить в программу заголовочный файл stdio.h с помощью директивы #include. B листинге 10.5 приведен при- мер работы функции gets( ).  222 Неделя 2. Основные вопросы 
Листинг 1 0.5. get .c — ввод строковых данных с клавиатуры с помощью функции gets( )  /* демонстрация использования библиотечной функции gets(). */   #include <stdio.h> /* Выделение символьного массива для хранения введенной строки. */  char input[257];  mummwaH о. о. о. о. о. о. о. .. о.  9 int main( void ) 10: { 11: puts("Enter some text, then press Enter: "); 12: get5(input); 13: printf("You entered: %s\n", input); 14: 15: return 0; 16: }   , '. Enter some text, then press Enter: .Pmflhfllflfll This is а test  You entered: This is a test  № В этом примере аргументом функции gets() служит переменная input, которая является массивом типа char, а следовательно и указателем на первый элемент массива. В строке 7 длина массива объявляется равной 257 элементов. Поскольку максималь- но допустимая длина строки на большинстве компьютерных текстовых экранов равна 256 символов, этот массив как раз предоставляет достаточно места для самой длинной воз— можной строки плюс завершающий нулевой символ, который функция gets( ) автоматически добавляет в конец строки. Функция gets() возвращает значение, которое в этом примере просто игнорируется. Оно равно указателю на введенную строку. Да, это именно тот указатель, который передавался в функцию. Зачем же его нужно возвращать? Возвращаемое значение можно использовать для проверки ввода пустой строки. Как это делается, показано в листинге 10.6.  Листинг 1 0.6. getback. с — проверка ввода пустой строки посредством возвращаемого из gets( ) значения   1: /* демонстрация использования возвращаемого из gets() значения. */ 2: 3: #include <stdio.h> 4: 5: /* Объявление символьного массива для ввода строки и указателя. */ 6: 7: char input[257], *ptr; 8: 9: int main( void ) 10: { 11: /* Вывод инструкции. */  День 10-й. Символы и строки 223 
12:  / 13: puts("Enter text a line at a time, then press,finter."); 14: puts("Enter a blank line when done."); 15: 16: /* Выполнять цикл, пока не будет введена пустая строка. */ 17: 18: while ( *(ptr = gets(input)) != NULL) 19: printf("You entered %s\n", input); 20: 21: puts("Thank you and good-bye\n"); 22: 23: return 0: 24: }   Enter a blank line when done. First string You entered First string Two You entered Two Bradley L. Jones You entered Bradley L. Jones  Enter text a line at a time then ress Enter. Резцпыааш ‘ p  //, Thank you and good-bye /’  № Программа работает следующим образом. Если пользователь вводит пустую строку (т.е. просто нажимает <Enter>) B ответ на запрос и ожидание в строке 18.  то строка не соцержит никаких символов, кроме завершающего нулевого символа на первом месте. Как раз на этот символ и указывает адрес, возвращаемый из функции gets( ). Поэтому если проверить этот символ и обнаружить ноль, станет ясно, что была введена пустая строка. Указанная проверка выполняется оператором while B строке 18 листинга 10.6. Этот опе- ратор устроен довольно сложно, поэтому стоит тщательно разобраться, что к чему. На рис. 10.1 показаны составные части этого оператора.   Функцию gets() следует использовать с осторожностью. потому что не llllllll всегда известно заранее. сколько символов введет пользователь, и вследствие этого функция может записать часть строки уже вне выделен-     ного буфера. 4 3 1 2 6 5 while ( *( ptr = gets (input)) != NULL)  Puc. 10.1. Компоненты оператора While для проверки ввода пустой ‹лцроки  224 Неделя 2. Основные вопросы 
1. Функция gets( ) вводит символы с клавиатуры, пока не встречает символ конца строки.  2. Введенная строка с отброшенным символом конца строки и добавленным нулевым сим- волом помещается в участок памяти, на который указывает переменная input.  3. Адрес строки (совпадающий со значением input) возвращается и присваивается указате- лю ptr.  4. Оператор присваивания одновременно представляет собой выражение, которое имеет то же значение, что и переменная в левой части оператора. Следовательно, все выражение ptr = gets(input) получает значение ptr. Заключив это выражение в скобки и поставив перед ним знак ссьшки по указателю (*), получаем то значение, которое хранится по дан- ному адресу. Это, конечно же, первый символ введенной строки.  5. NULL представляет собой символическую константу, определенную в заголовочном файле stdio.h. Она равна коду нулевого символа (О).  6. Если первый символ введенной строки не равен нулю (т.е. была введена непустая строка), то операция сравнения дает значение ИСТИНА, и цикл while продолжает выполняться. Если первый символ равен нулю (была введена пустая строка), операция сравнения дает значе— ние ложь, и цикл заканчивается.  При использовании gets( ) или любой другой функции, которая помещает данные по ука- зателю, необходимо следить, чтобы указатель указывал на специально выделенныи для этои щели участок памяти. Несложно сделать, например, такую ошибку: Ёна: *ptr ; gets (ptr) ,  \  ;; Указатель ptr объявлен, но не инициализирован. Он на что—то указывает, но на что— Ётолком неизвестно. Функции gets() неизвестно, что указатель ptr не инициализирован ёюлжным образом, так что она просто выполняет свою работу — помещает введенную строку ;10 тому адресу, который находится в ptr. Эта строка может затерегь важную информацию, например, код программы или данные операционной системы. Как правило, компиляторы этой ошибки не замечают, поэтому ее выявление и исправление лежит целиком на плечах ётрограммистов.  Функция gets()  #include <stdio.h> char *gets(char *str);  Функция gets() считывает строку str co стандартного устройства ввода, обычно клавиатуры. В строку помещаются все вводимые символы, пока не встретится символ конца строки. В этот момент в строку помещается завершающий нулевой символ. Функция gets() возвращает указатель на считанную строку. Если с вводом строки возникли какие-либо проблемы, возвращается нулевой адрес.   Пример /* Пример использования gets() */ #include <stdio.h> char line[256]; void main( void )  {  printf( "Enter a string:\n" );  День 10-й. Символы и строки 225 
gets( line ); / printf( “\nYou entered the following string:\n“ ); . . / pr1ntf( "%s \n", 11ne );  Ввод строк с помощью функции scanf()  Ha занятии 7 вы узнали, как вводить числовые данные с клавиатуры с помощью библио— течной функции scanf(). Эта функция может также вводить и строки. Вспомните, что в scanf() для указания типа вводимых данных используется строка формата. Для ввода сим- вольной строки следует включить спецификацию ввода %s в строку формата функции scanf( ). Как и в gets( ), в scanf () передается указатель на участок памяти, выделенный для хранения строки. Как функция scanf() определяет начало и конец строки? Началом является первый всгреченный ею непустой символ (не пробел, не табуляция и т.п.). Конец строки можно ука- зать одним из двух способов. Если в строке формата указана спецификацйя %s, то строка счи- тывается до следующего пустого символа (пробела, табуляции, конца строки), но не включая его. Если спецификация задана в виде %ns, где n —— целое число, обозначающее ширину поля ввода, то функция scanf () считывает первые n символов по порядку или все до следующего пустого символа, в зависимости от того, какое условие выполнится раньше. » С помощью функции scanf() можно ввести сразу несколько строк. Для этого достаточно включить в строку формата нужное количество спецификаций %s. Для каждой специфик№ используются уже упомянутые правила выделения строк из потока ввода. Например: /  scanf("%s%s%s", sl, 52, s3);  Если в ответ на ожидание функции scanf() ввести строку January February March, то строка sl будет содержать January, строка 52 — February, a s3 —-— March.  Как работает спецификация ширины поля? Возьмем следующий оператор: scanf(“%3s%3s%3s", sl, 82, s3);  Если ввести September, то строка sl получит значение Sep, строка 52 —-— значение tem и строка s3 —- ber. Что если ввести меньше или больше строк, чем этого требует функция scanf( )? Если вве- сти меньше строк, функция станет ожидать ввода недостающих данных, и программа не сдвинется с места, пока эти недостающие строки не будут введены. Рассмотрим следующий оператор: scanf(“%s%s%s“, sl, 52, $3);  Если при его выполнении ввести January February, программа будет ждать ввода треть- ей строки, указанной в спецификациях формата. Если же ввести больше строк, чем требуется, то лишние строки останутся невостребованными в буфере клавиатуры и будут считаны при следующих вызовах scanf() или других функций ввода. Пусть выполняются следующие опе— раторы: scanf("%s%s", 51, 52); scanf("%s", 53);  Если ввести January February March, строка January будет помещена в 51, а February —— в 52 при первом вызове scanf( ). Ввод строки March автоматически откладывается до сле- дующего вызова scanf( ), при котором она помещается в s3.  226 Неделя 2. Основные вопросы 
Функция scanf ( ) возвращает количество успешно введенных элементов данных. Это воз- вращаемое значение нередко игнорируется в программах. Если необходимо ввести только текстовые строки, лучше воспользоваться функцией gets( ), a He scanf( ). Функция scanf() полезна в тех случаях, когда необходимо ввести смешанный набор текстовой и числовой ин- формации. Иллюстрацией этому служит листинг 10.7. Помните, что при вводе числовых зна- чений с помощью scanf ( ) перед переменными необходимо ставить знак операции взятия ад- реса (вы это изучали на занятии 7).  Листинг 1 0.7. input . с — ввод числовой и текстовой информации с помощью функции scanf( )   1: /* Ввод числовых и текстовых данных c помощью scanf(). */ 2: 3: #include <stdio.h> 4: 5: char 1name[257], fname[257]; 6: int count, id_num; 7: 8: int main( void ) 9: { 10: /* Вывод приглашения. */ 11: 12: puts("Enter last name, first name, ID number separated"); 13: puts("by spaces, then press Enter."); 14: 15: /* Ввод трех элементов данных. */ 16: 17: count = scanf("%s%s%d", 1name, fname, &id_num); 18: 19: /* Display the data. */ 20: 121: printf("%d items entered: %s %s %d \n", count, fname, 1name, id_num); 22: ,23: return 0; .34: }   .‘:  г.: ' Enter last name, first name, ID number separated  } Pesunhmam by spaces, then press Enter.  Jones Bradley 12345 3 items entered: Bradley Jones 12345 { Помните, что аргументы функции scanf() должны быть адресами переменных. В листинге 10.7 1name и fname являются указателями (т.е. фактически адресами), поэтому перед ними не нужно ставить знак взятия адреса (&). А вот id__num — обычная пере- менная, поэтому перед ее именем необходимо поставить знак & при передаче в функцию scanf() B строке 17. Некоторые программисты склонны считать, и небезосновательно, что ввод данных с по- “мощью функции scanf() часто порождает ошибки. Они предпочитают вводить все данные, как текстовые, так и числовые, с помощью функции gets( ), a затем уже в программе преоб- разуют строковые представления чисел в числовые значения. Эти приемы не обсуждаются в   1 Am: '  с;‘Цень 10-й. Символы и строки 227 
нашей книге. При желании можете запрограммировать нечто подобное самостоятельно в ка— честве упражнения. Для этого вам понадобятся функции обработки строк, которые рассмат— риваются на занятии 17.  \  \ "  Резюме  На этом занятии был рассмотрен такой стандартный тип данных языка С, как char. Одно из применений этого типа состоит в хранении отдельных символов. Символы фактически хранятся в виде чисел: числовых кодов, присвоенных символам согласно кодировке ASCII. Поэтому небольшие целые числа тоже можно хранить в переменных типа char. Существуют типы signed и unsigned char, т.е. char co знаком и без знака. Строки представляют собой последовательности символов, оканчивающиеся, нулевым символом. Строки используются для хранения текстовой информации. В языке С строковые данные хранятся в массивах типа c‘har. Для помещения строки длиной 12 символов необх0дим массив типа char длиной п+1 элемент. Для оперативного размещения данных в памяти используются стандартные функции рас- пределения памяти, например, malloc( ). С помощью этой функции можно гибко управлять распределением нужного объема памяти для данных программы. Без наличия таких функций пришлось бы оценивать требуемый объем памяти заранее, и если он оказаЁпся бы завышен— ным, память тратилась бы впустую. После завершения работы с выделенным участком памя— ти его необходимо освободить и вернуть в распоряжение операционной системы, используя функцию free( ). /  Вопросы и ответы  В чем разница между строкой и массивом символов? По определению, строка—— это последовательность символов с завершающим нулевым символом. Массив же, по определению, представляет собой последовательность символов. ОгсЮДа заключаем, что строка — 3T0 массив символов с завершающим нулем. При объявлении массива типа char фактический объем вьтделенной памяти в точности равен объявленному, а не меньше его на единицу. Этот объем является предельным — нельзя поместить в массив строку большей длины. Вот пример:  char state[10] = "Minneapolis"; /* Ошибка! Строка длиннее массива. */  char state2[10] = "MN"; /* Ошибки нет, но память тратится впустую, */ /* т.к. строка короче массива. */  Если же объявить и инициализировать указатель на char, то этих ограничений не будет. Однако переменная будет представлять собой всего лишь указатель _ переменную для хра— нения адреса —— а сама строка будет находиться где-то в другом месте. Не стоит беспокоиться о том, где именно —— компилятор обо всем позаботится, и проблема недостатка или избытка места не возникнет. При этом указатель на char может указывать на строку любой длины.  Почему бы вместо вызова функции распределения памяти ша110с( ) просто не объя- вить достаточно большой массив, в который заведомо поместились бы все нужные зна- чения? Хотя на первый взгляд объявление большого массива кажется более простым способом, на самом деле это просто напрасная трата памяти. В таких простых и маленьких программах,  228 Неделя 2. Основные вопросы 
как примеры этого занятия, применение функции malloc( ) вместо объявления массива может показаться неуместной скупостью. Но в больших и сложных программах уже придется вьше- лять ровно столько памяти, сколько необходимо. Закончив работу с этой памятью, ее необхо- димо освободить——— вернуть в распоряжение операционной системы. После освобожцения памяти она может использоваться для других переменных или массивов. (Освобождение па- мяти подробно рассматривается на занятии 20.)  Все ли компьютеры поддерживают расширенный набор символов ASCII? Нет. Большинство современных персональных компьютеров полдерживают этот набор, но некоторые старые модели не обеспечивают такой поддержки. Количество таких старых компьютеров постепенно сокращается. Программисты часто используют символы псевдо- графики из расширенного набора для оформления выводимых данных. Кроме того, существуют наборы символов национальных алфавитов, которые содержат еще и дополнительные к набору ASCII символы. Обычно для их хранения в программе ис- пользуются переменные типа wchar_t вместо char. Этот тип определен в заголовочном файле stddef.h. B переменных типа wchar_t можно хранить символы из более широкого nuanaso- :Ha. Дополнительную информацию об этом типе данных и альтернативных наборах символов можно найти в документации стандарта ANSI.  Что произойдет, если поместить в символьный массив строку, которая длиннее его? Это может вызвать трудно обнаруживаемую ошибку. В языке С такая операция вполне допустима. Все, что записывается в память непосредственно после массива, затирает нахо- Ёдящиеся там данные. Это может быть неиспользуемая область памяти, какие-то программные Ётанные или же жизненно важная информация операционной системы. Последствия этой опе- драции зависят от того, какие именно данные затираются. Например, довольно часто вообще @тичего не происходит. Тем не менее, не стоит допускать п0добной операции.  I  I  &  х  …»  ЁКоллоквиум  :  “=}—\=-  {a B этом коллоквиуме вам предлагаются контрольные вопросы для закрепления пройденно- это материала и упражнения для выработки практических навыков.  Контрольные вопросы  '1. Каков диапазон числовых значений символов в наборе ASCII? . Как компилятор С интерпретирует одиночный символ, заключенный в одинарные каВЫЧКИ? Каково определение строки в языке С?  Что такое строковый литерал?  … ь рун”  Для хранения строки из n символов необх0дим массив из п+1 элементов. Зачем нужен до- полнительный элемент?  S"  Как компилятор С интерпретирует строковый литерал, встречая его в программе?  7. Определите числовые коды ASCII приведенных ниже символов по таблице из приложе— ния А:  а)а б)А в)9  День 10-й. Символы и строки 229 
10.  г) пробел 10% е) А \ ‚\  Определите символьные эквиваленты следующих числовых кодов ASCII no таблице из приложения А:  а) 73 6) 32 B) 99 г) 97 д) 110 е) 0 ж) 2  Сколько байт памяти занимает каждая из этих переменных? (Предполагается, что один символ занимает один байт.)  a)char *strl = { "String 1“ }; 6)char str2[] = { "String 2" }; a)char string3; r)char str4[20] = { "This is String 4" }; /// n)char str5[20]; Имеется следующее объявление: char *string = "A string:"; Определите, чему равны следующие выражения: a)string[0] 6)*string B)string[9] r)string[33] n)*string+8  e)string  Упражнения  1.  Напишите объявление переменной типа char под именем letter и инициализируйте ее символом $.  Напишите объявление массива типа char и инициализируйте его строкой "Pointers are fun!". Сделайте размер массива в точности достаточным для хранения этой строки.  Напишите оператор, который выделяет место для строки "Pointers are fun! " из упраж— нения 2, но без использования массива.  230 Неделя 2. Основные вопросы 
4. Напишите фрагмент кода для объявления массива из 80 символов, ввода строки с клавиа- туры и помещения ее в объявленном массиве.  5. Напишите функцию для копирования одного массива символов в другой. (Подсказка: ' воспользуйтесь программами из упражнений занятия 9.)  16. Напишите функцию, принимающую две строки в качестве аргументов. Подсчитайте ко- личество символов в каждой строке и возвратите указатель на более длинную строку.  7. Самостоятельная работа. Напишите функцию, принимающую два строковых аргумента. С помощью функции ша11ос() выделите достаточно места для строки —— результата их конкатенации (сцепления). Выполните конкатенацию и возвратите указатель на эту новую строку. Например, при передаче строк "Не11о" и "World!" функция должна возвратить строку “HelloWorld! ". Помещение сцепленных строк в новую строку —— это самый простой спо-  соб выполнить операцию сцепления. (Можете воспользоваться программами из упражне- ний 5 и 6.)  1.1  '.`—::..? ’K‘  Поиск ошибок. Найдите ошибку в следующем операторе: char a_string[10] = “This is a string";  *$“ “9”?er Ё” vr= '  Поиск ошибок. Найдите ошибку в следующем операторе: char *quote[100] = { "Smile, Friday is almost here!" };  1:10. Поиск ошибок. Найдите ошибку в следующем фрагменте кода: Echar *stringl; {фак *string2 = "Second"; gstringl = string2;  ’ 11. Поиск ошибок. Найдите ошибку в следующем фрагменте кода:  char stringll]; char string2[] = "Second"; stringl = string2;  “12. Самостоятельная работа. Напишите программу, которая выводит на экран рамку из двойных линий, пользуясь символами псевдографики из таблицы кодов ASCII.   ~ Ha этом занятии много раз применялось распределение памяти c помо— @"! щью функции malloc( ). При динамическом распределении памяти обяза- тельно возвращайте ее операционной системе (освобождайте) после окончания работы. Освобождение памяти рассматривается на занятии 20.     День 10—й. Символы и строки 231 
  \… Структуры, объеди нения  И НЭСТЭНДЭРТНЫЭ ТИПЫ ДЭННЫХ  Многие задачи удобно и экономно программируются с применением составных конструк— ций данных языка С, которые называются структурами. Структура представляет собой нестандартный объект для хранения разнородных данных, создаваемый программист/( для своих целей. B этой связи будут рассмотрены следующие вопросы. '  I Простые и сложные структуры Объявление и определение структур Обращение к данным в структурах Создание структур, содержащих массивы, и массивов структур Объявление указателей внутри структур и на структуры Передача структур в качестве аргументов функций Объявление, определение и использование объединений  Определение новых структурных типов  Простейшие структуры  Hflflblfl №№…" Структура _- ЭТО СОВОКУПНОС'ГЬ нескольких переменных ПОД единым именем, рассматриваемых как ОДНО целое. В отличие OT массива, переменные В структуре  могут иметь различные типы. Переменные внутри структуры называются ее членами или по— лями. Структуры могут содержать данные любых мыслимых типов С, в том числе массивы и другие структуры. В следующем разделе приведен простой пример. В языке С допускаются структуры любой сложности, но мы начнем с самых простых.  Объявление и определение структур  Если вы пишете программу с графикой, она обязательно должна работать с координатами точек экрана. Экранные координаты записываются в виде двух целых чисел: x описывает то- 
Зризонтальное положение точки, а у —— вертикальное. Для этой цели можно определить струк- Ёгуру под именем coord, которая будет содержать сразу и х, и у:  Estruct coord  Н % int x; int y;   ньтх, которые создаются программистом. Имя структуры называют также ее меткой (tag), а . . е именем структурного типа. Позже мы рассмотрим использование этой метки. После метки структуры должна стоять открывающая фигурная скобка. В скобках после ени структуры находится список переменных — полей структуры. Каждый элемент струк-  В приведенном выше примере определяется структурный тип под названием coord, co- ржащий два целочисленных поля к и у. Однако это объявление структуры и ее членов само j}. ce6e не создает фактического объекта-структуры coord или ее переменных х и у. Другими овами, объявление структурного типа—— это еще не объявление экземпляров этого типа, при этом не вьтделяегся память для фактических структур. Объявить фактическую струк- 3, py можно двумя способами. Один из них состоит в том, чтобы поставить список имен пе- менньтх сразу после определения структурного типа, как показано ниже:  int у; first, second; B этом операторе определяется структурньтй тип coord, и объявляются две структуры это- _ . типа—— first и second. Эти две структуры являются экземплярами типа coord — каждая з них содержит две целочисленных переменных х и у. : При таком способе объявления структур определение структурного типа совмещается с бъявлением его экземпляров. Другой способ состоит в том, что объявление экземпляров : руктур помещается в исходном к0де отдельно от определения типа. В следующем примере _` . кже объявляются два экземпляра структурного типа coord: struct coord { E int x; int у; }: _/* Здесь могут стоять другие операторы */ struct coord first, second;  B этом примере определение структурного типа coord помещено отдельно от объявления переменных. В отдельно стоящем объявлении первым должно ндти ключевое слово struct, затем метка структурьт, затем имена объявляемьтх переменных.  Обращение к полям структуры  Отдельные элементы структуры могут использоваться точно так же, как простые пере- менньте тех же типов. Чтобы извлечь их значения из структуры, применяется знак операции обращения к элементу структуры (.), представляющий собой точку между именем структу-  День 11 -й. Структуры, объединения и нестандартные типы данных 233 
ры и именем элемента—поля. Таким образом, чтобы структура first содержала экранные ко— ординатьтх = 50,у = 100,следуетзаписать: first.x = 50; \\ first.y 100; ‘“\  Для вывода экранных координат, хранящихся в структуре second, запишем следующее:  printf("%d,%d", second.x, second.y);  Может возникнуть вопрос, в чем же преимущество структур перед отдельными перемен— ными. Одно большое преимущество заключается в том, что можно копировать информацию из одной структуры в другую простым присваиванием. Продолжая предыдущий пример, за— пишем следующее:  first = second;  Этот оператор эквивалентен следующим двум операторам присваивания:  first.x = second.x; first.y = second.y;  Если в программе используются сложные структуры с большим количеством элементов, такая запись может сэкономить много времени и места. Остальные преимущества структур станут понятны по мере изучения методов работы с ними. Вообще говоря, структуры полезно применять в тех случаях, КОГДа совокупности разнородных переменных должны восприни— маться как единое целое. Например, в базе данных, содержащей адреса, каждая отдельная за- пись может быть представлена структурой, а отдельные информативные поля такойзаписи (имя, фамилия, адрес и т.д.) —— элементами структуры. / B листинге 11.1 представлен пример для иллюстрации всего сказанного. Эта программа не имеет особой практической ценности, однако иллюстрирует работу с простой структурой.  Листинг 1 1 . 1 . simple.c — объявление и использование простой структуры   1: /* simple.c - демонстрация использования простой структуры */ 2: 3: #include <stdio.h> 4: 5: int length, width; 6: long area; 7: 8: struct coord{ 9: int х; 10: int y; 11: } myPoint; 12: 13: int main( void ) 14: { 15: /* помещение значений в поля координат */ 16: myPoint.x = 12; 17: myPoint.y = 14; 18: 19: printf("\nThe coordinates are: (%d, %d).", 20: myPoint.x, myPoint.y); 21: 22: return 0; 23: }   234 Неделя 2. Основные вопросы 
«The coordinates a e: 12 Резцпьшат 1 „_ r ‹ ’  № В этой программе определяется простая структура для хранения. координат точ— ки. Примеры с этой структурой уже встречались вам ранее. В строке 8 находится  ключевое слово struct и ее метка coord. Тело структуры находится в строках 9—1 1. Объявле- но два поля структуры — целочисленные переменные x и у. В строке 11 объявляется экземпляр структуры coord — переменная myPoint. Это же объ- явление можно было записать в отдельной строке таким образом:  struct coord myPoint;  B строках 16 и 17 полям структуры myPoint присваиваются значения. Как уже упомина- .т'тось, для этого используется имя структуры, затем точка и имя поля. В строках 19 и 20 эти же . поля выводятся на экран с помощью функции printf( ).  Ключевое слово struct  struct метка { элемент (ы)_ структуры; /* дополнительные операторы */ } экземпляр;   С помощью ключевого слова struct определяются структурные типы и объявляют- ся структуры. Структура— это совокупность одной или нескольких переменных (элемент(ы)_структуры) под единым именем для удобства обработки. Переменные не обязаны иметь один и тот же тип, а также не обязательно должны быть простыми переменными. В структуры можно включать массивы, указатели и другие структу- ры. Определение структуры начинается с ключевого слова struct. Следом идет метка структуры, представляющая собой имя создаваемого структурного типа. Затем в фи- гурных скобках перечисляются элементы структуры. Можно сразу же объявить и переменную (экземпляр) данного структурного типа. Если структура определяется без объявления переменных—экземпляров, то такое определение является всего лишь шаблоном, который можно использовать позже в программе для объявления струк— турных переменных. Такой шаблон имеет следующий формат:  struct метка { элемент (ы)_ структуры; /* дополнительные операторы */ }; Объявление переменных с использованием этого шаблона структуры имеет следую— щий вид: struct метка экземпляр;  Это объявление будет синтаксически правильным только в том случае, если ранее была определена структура с данной меткой.  Пример1  /* Объявление шаблона структуры под именем SSN */ struct SSN { int first_three;  День 11—й. Структуры, объединения и нестандартные типы данных 235 
char dashl; int second_two; char dash2; int last_four; ‘\v\\ } /* Использование шаблона для объявления */ struct SSN customer_ssn;  Пример2  /* Совместное объявление структуры и экземпляра */ struct date { char month[2]; char day[2]; char year[4]; } current_date;  Примерз  /* Объявление и инициализация структуры */ struct time { int hours; int minutes; int seconds; // } time_of_birth = { 8, 45, 0 }; /  Сложные структуры  Вот вы и получили некоторое представление о простейших структурах в языке С. Настало время перейти к более сложным и интересным структурным типам данных, которые могут включать в себя другие структуры или массивы в качестве элементов.  Включение структур в структуры  Как уже упоминалось, структуры могут содержать элементы любых типов данных С, в том числе и других структурных типов. Проиллюстрируем это, доработан наш преды- дущий пример. Предположим, нашей графической программе необходимо работать с прямоугольниками. Прямоугольник можно задать координатами его противоположных (по диагонали) углов. Структура для хранения координат одной точки уже объявлялась ранее. Для задания прямо- угольника необходимы две такие структуры, объединенные в единое целое. Это можно сде- ‚лать следующим образом (при условии, что структурный тип coord уже определен):  'struct rectangle { struct coord topleft; struct coord bottomrt;  № ; Неделя 2. Основные вопросы 
В этом операторе определяется структурный тип rectangle, включающий две структуры типа coord: topleft и bottomrt. Это дает нам только новый тип; необходимо еще объявить экземпляр структуры: struct rectangle mybox;  Как и ранее для структуры coord, определение типа и объявление его экземпляров можно совместить: struct rectangle { struct coord topleft; struct coord bottomrt; } mybox; Для обращения к полям, где хранятся фактические числовые данные, необходимо два раза применить точку: mybox . toplef t . x Это выражение представляет собой обращение к полю х поля topleft структуры типа  rectangle, имеющей имя mybox. Для задания прямоугольника с координатами вершин (0, 10), (100, 200) необходимо записать следующее:  mybox.topleft.x = 0; mybox.topleft.y = 10; mybox.bottomrt.x = 100; mybox.bottomrt.y = 200;  Все это может показаться довольно запутанным. Для лучшего понимания взгляните на рис. 11.1, на котором показаны соотношения между структурой типа rectangle, двумя со- держащимися в ней структурами типа coord и отдельными полями типа int, включенными в каждую из структур coord. Имена структур совпадают с именами из последнего приведенно— го примера.  Переменная типа int (х){ I mybox.topIeft.x I Структура типа  Переменная типа int (у){ Imvbox.t0pieft.y I coord (topleft)         уктура типа rectangle (mybox)   Переменная типа int (x) { ГтуЬохропотгіЛ  Переменная типа int (y) { I mybox.bottomrt.y I  Puc. 11.1. Соотношения между структурой, ee полями структурных типов u простыми полями во вложенных структурах   Стр а типа coord Ibottomrt)         B листинге 11.2 представлен пример работы со структурами, которые содержат другие структуры. Эта программа запрашивает у пользователя координаты прямоугольника, а затем вычисляет и выводит на экран его площадь. Обратите внимание на предположения, сделан- ные B программе. Они приведены в комментариях в начале исходного кода (строки 3—8).  Листинг 1 1 .2. struct . с — демонстрация структур-членов других структур   1: /* Демонстрация структур, содержащих другие структуры. */ 2:  День 11-й. Структуры, объединения и нестандартные типы данных 237 
3: /* Принимает координаты вершин прямоугольника и вычисляет 4: ero площадь. Предполагается, что координата у нижнего 5: правого угла больше, чем координата у верхнего левого\ 6: угла, и что координата x нижнего правого угла больше,\’\\ 7: чем координата x верхнего левого угла. Все координаты 8: считаются положительными. */ 9: 10: #include <stdio.h> 11:  12: int length, width; 13: long area;  14: 15: struct coord{ 16: int x; 17: int у; 18: }; 19: 20: struct rectangle{ 21: struct coord topleft; 22: struct coord bottomrt; 23: } mybox; 24: 25: int main( void ) ( 26: { \ 27: /* Ввод координат */ 28: 29: printf("\nEnter the top left x coordinate: "); 30: scanf("%d", &mybox.topleft.x); 31: 32: printf("\nEnter the top left у coordinate: "); 33: scanf("%d", smybox.topleft.y); 34: 35: printf("\nEnter the bottom right x coordinate: "); 36: scanf("%d", smybox.bottomrt.x); 37: 38: printf("\nEnter the bottom right у coordinate: "); 39: scanf("%d", &mybox.bottomrt.y); 40: 41: /* Вычисление длины и ширины прямоугольника */ 42: 43: width = mybox.bottomrt.x - mybox.topleft.x; 44: length = mybox.bottomrt.y - mybox.topleft.y; 45: 46: /* Вычисление и вывод площади */ 47: 48: area = width * length; 49: printf("\nThe area is %ld units.\n", area); 50: 51: return 0; 52: }   238 Неделя 2. Основные вопросы 
 Enter the to left х coordinate: l Резцльтат 9  Enter the top left y coordinate: l г Enter the bottom right х coordinate: 10 5 Enter the bottom right y coordinate: 10  Ё The area is 81 units.  Структурный тип coord определяется в строках 15—18. Его полями являются две целочисленные переменные ›‹ и у. В строках 20—23 определяется структура  opleft и bottomrt, являющиеся структурами типа coord. B строках 29—39 структура шуЬох заполняется значениями. На первый взгляд может пока- ться, что значений всего два, поскольку в шуЬох два элемента. Но это не так, потому что ‚аждый из элементов mybox сам является структурой и содержит свои собственные поля: в apleft и bottomrt no два элемента, ›‹ и у, из структуры coord. Всего получается четыре эле— ента, которые нужно заполнить значениями. После заполнения вычисляется площадь. Для Ьтого используются имена структуры и ее полей. Чтобы воспользоваться значениями ›‹ и у, еобходимо знать имена экземпляров структур. Поскольку ›‹ и y находятся в структуре .‘ нутри другой структуры, в вычислениях используются имена экземпляров на двух уровнях ёртруктурной вложенности: mybox.bottomrt.x, mybox.bottomrt.y, mybox.topleft.x и турок. topleft. y. $ В языке С нет ограничений на глубину вложенности структур, хотя стандарт ANSI гаран- Этирует поддержку не более 63 уровней. Пока позволяет память, можно определять структуры, `;содержащие структуры со структурами структур и т.д. —- идея понятная, однако по достиже— ;нии определенного предела совершенно непродуктивная. На практике в программах на С ‚почти никогда не используется больше трех уровней вложенности структур.  Структуры, содержащие массивы  Можно определять структуры, содержащие массивы в качестве своих элементов (полей) . Массивы могут быть любых типов (int, char и т.д.). Например, в следующем фрагменте кода ‚определяется структурный тип data, содержащий целочисленный массив ›‹ из четырех эле— ментов и символьный массив у из десяти элементов: struct data { int x[ 4]; char y[10];  Затем можно объявить переменную record этого структурного типа: struct data record; Организация этой структуры показана на рис. 11.2. Заметьте, что на рисунке элементы массива x занимают вдвое больше места в памяти, чем элементы массива у. Это происходит потому, что данные типа int обычно занимают два байта памяти, тогда как данные типа char —- только один (что обсуждалось на занятии 3, посвященном хранению данных в пере— менных и константах).  День 11 -й. Структуры, объединения и нестандартные типы данных 239 
Г—хесоха.х[О]   ? record   V   régord.x \    % Ь—-гесога.у             A     “&` e I . & .1) .. \ см м..-  :есога.у[8]-—ы   Рис. 11.2. Организация структуры с массивами в качестве элементов  Чтобы обратиться к отдельному элементу массива, являющегося полем структуры, при- меняется комбинация точки и индекса массива: record.x[2] = 100; record.y[1] 'x';  Вы наверняка помните, что массивы символов чаще всего применяются для хранения строк. Следует также помнить (из материала занятия 9), что имя массива без квадратных ско- бок представляет собой указатель на этот массив. То же самое справедливо и для массивов ——— полей структур. Рассмотрим следующее выражение:  record.y  B силу сказанного, это указатель на первый элемент массива у[ ] в структуре record. По— этому содержимое массива у[] можно вывести на экран следующим оператором:  puts(record.y);  Рассмотрим еще один пример. В листинге 11.3 используется №№, состоящая из пе- ременной типа float и двух символьных массивов.  Листинг 1 1 .3. array. с —- структура с массивами в качестве элементов   1: /* демонстрация структуры с массивами в качестве элементов. */ 2: 3: #include <stdio.h> 4: 5: /* Определение и объявление структуры данных. */ 6: /* Содержит одно вещественное поле и два символьных массива. */ 7: 8: struct data{ 9: float amount; 10: char fname[30]; 11: char lname[30]; 12: } rec; 13: 14: int main( void ) 15: { 16: /* Ввод данных с клавиатуры. */ 17: 18: printf("Enter the donor's first and last names,\n"); 19: printf("separated by a space: "); ЁЁ: scanf("%s %з“, rec.fname, rec.lname); 240  Неделя 2. Основные вопросы 
22: ртіпсі("1пЕпіет the donation amount: “);  23: scanf("%f”, &rec.amount); 24: 25: /* Вывод данных. */ 26: /* Примечание: спецификация %.2f задает вывод */ 27: /* вещественного числа с двумя цифрами после */ 28: /* десятичной точки. */ 29: ‚30: /* Вывод данных на экран. */ 31: 32: printf("\nDonor %s %s gave $%.2f.\n”, rec.fname, rec.lname, 33: rec.amount); 34: 35: return 0; 36: }   „___—  Pulmmmn Enter the donor’s first and last names, ‚ U separated by a space: Bradley Jones Enter the donation amount: 1000.00 Donor Bradley Jones gave $1000.00.  Эта программа использует структуру с двумя массивами под именами fname[ 30] и lname[30]. Оба эти массива— символьные и предназначены соответственно для хранения имени и фамилии человека. Структурный тип, объявленный в строках 8—12, имеет метку data. B качестве полей он содержит символьные массивы fname и lname, a также вещественную переменную amount типа float. Эта структура прекрасно подходит для хране- ния таких данных, как имя лица (точнее, имя и фамилия) в сочетании с некоторым относя- щимся к нему числом, например, суммой взноса в благотворительную организацию. В строке 12 также объявляется экземпляр структуры под названием rec. Остальная часть программы использует структуру rec для получения данных от пользователя (строки 18—23) и вывода их на экран (строки 32 и 33).   Массивы структур  Если в структурах могут содержаться массивы, то нельзя ли .хранить структуры в масси- вах? Оказывается, можно. Фактически, массивы структур являются мощнейшим средством программирования. Далее мы рассмотрим, как это средство работает. Вы уже знаете, как с помощью структурных типов можно организовать данные в точном соответствии с потребностями вашей программы. Как правило, программе недостаточно все- го одного экземпляра данных структурного типа. Пусть, например, программа предназначена для ведения телефонной книги. Можно определить структуру для хранения номера телефона каждого абонента:  struct entry  {  char fname[lO]; char lname[12];  День 11-й. Структуры, объединения и нестандартные типы данных 241 
char phone[8]; } : \ Но в телефонной книге обычно хранится много номеров, так что от одиого экземпляра та— кой структуры немного толку. Поэтому необходим массив структур типа entry. После опре— деления структуры массив объявляется следующим образом: struct entry list[lOOO];  Этот оператор объявляет массив с именем list из тысячи элементов. Каждый элемент представляет собой структуру типа entry и распознается по индексу, как и в любом другом массиве. В каждой из структур содержится три поля, причем эти поля —— массивы типа char. Вся получившаяся сложная конструкция данных показана схематически на рис. 11.3.  list[O].fname[0]   list[O].fname list[01Jname list[O].phone list[1].fname list[1].lname list[1].phone list[2].fname list[2].lname list[2].phone  list[O]  list[‘l ]  { { list[2] { Е { .  list[999].fname list[999].lname list[QQQIQ/hone  list[999]  L— list[999].phone[2]  Puc. 11.3. Организация массива структур  После объявления массива структур можно использовать его для разнообразных задач об— работки данных. Например, для присвоения данных из одного элемента массива другому можно записать следующее: list[l] = list[S]; Этот оператор присваивает всем полям структуры list[l] значения соответствующих полей структуры list[S]. Можно также перемещать данные между отдельными полями структур. Например, следующий оператор копирует строку list[ 5] .phone в строку list[l].phone: strcpy(list[l].phone, list[S].phone); (Здесь для копирования строк используется библиотечная функция strcpy( ). Она будет подробно рассмотрена на занятии 17.) При желании можно даже обмениваться данными ме— жду отдельными элементами массивов, являющихся полями структур: list[5].phone[1] = list[2].phone[3]; Этот оператор копирует четвертый символ номера телефона из записи list[Z] во вторую позицию номера в записи list[ 5]. (He забывайте, что индексы массивов начинаются с нуля, а не с единицы.)  242 Неделя 2. Основные вопросы 
В листинге 11.4 приведен пример использования массивов структур. Более того, структу-  ры В ЭТИХ массивах сами содержат массивы В качестве своих полей.  Листинг 1 1 .4. strucarr. с — массивы структур   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:  /* демонстрация работы с массивами структур. */ #include <stdio.h> /* Определение структуры отдельных элементов массива. */ struct entry { char fname[20];  char 1name[20]; char phone[10];  }; /* Объявление массива структур. */ struct entry list[4]; int 1;  int main( void )  { /* Цикл ввода данных о четырех лицах. */ for (і = 0; i < 4; i++) { printf("\nEnter first name: "); scanf(”%s”, list[i].fname); printf(”Enter last name: "); scanf(”%s”, list[i].1name); printf(”Enter phone in 123-4567 format: ”); scanf(”%s”, 1ist[i].phone); } /* Вывод двух пустых строк. */ printf("\n\n"); /* Цикл для вывода данных. */ for (i = 0; і ‹ 4; i++) { printf("Name: %s %s”, 1ist[i].fname, list[i].lname); printf("\t\tPhone: %s\n", 1ist[i].phone); } return 0; }   День 11 -й. Структуры, объединения и нестандартные типы данных 243 
Р Enter first name: Bradley nagnhmam Enter last name: Jones  Enter phone in 123-4567 format: 555-1212 \  \ Enter first name: Peter Enter last name: Aitken Enter phone in 123-4567 format: 555-3434 Enter first name: Melissa Enter last name: Jones Enter phone in 123-4567 format: 555-1212 Enter first name: Kyle Enter last name: Rinni Enter phone in 123-4567 format: 555-1234 Name: Bradley Jones Phone: 555-1212 Name: Peter Aitken Phone: 555-3434 Name: Melissa Jones Phone: 555-1212 Name: Kyle Rinni Phone: 555-1234  Эта программа следует хорошо знакомой вам форме наших примеров. Она на- чинается с комментария в строке 1 и включения заголовочного файла средств  ввода-вывода stdio.h B строке 3. B строках 7—11 определяется шаблон структуры entry, со- стоящей из трех символьных массивов: fname, lname и phone. B строке 15 с помощью этого шаблона объявляется массив из четырех структур типа entry под им\енем list. B строке 17 объявляется переменная типа int, которая используется как счетчик на протяжении всей про- граммы. Функция main() начинается в строке 19. Первое, что она делает— это выполняет цикл for H3 четырех повторений для ввода данных в массив структур в строках 24-32. За- метьте, что обращение к массиву выполняется по индексу— точно так же, как и в случае простых массивов, рассмотренных на занятии 8. В строке 36 на экран стандартным способом выводятся две пустые строки, чтобы отле- лить введенные данные от выводимых результатов. Строки 40—44 выполняют вывод на экран данных, введенных пользователем ранее. Чтобы вывести значения отдельных полей структур, используются индексы и точка. Внимательно ознакомьтесь с приемами, использованными в листинге 11.4. Массивы структур, каждая из которых в свою очередь содержит массивы, — это важный инструмент программирования практически важных задач.   Рекомендуется Не рекомендуется „Используите ключевое слово struct при  д\д  юбцъявлениитхэкземпляра структуры on‘v peheneHI-Iofi paHee”~v‘<~s~fi’-‘>*“ „,а; W»: my. in “*Не путайте метку структу ‚,Объявляите экземпляры структур ‹: той тименем экземпляра…фструктурхд .… „, && областью деиствия‚„нто и обычные жиспользуется толькодтдля спред“, _; ия… теремеНные (См занятие 12)…ь % ”$531; \шаблонакил‘и фОРМа'і'а ' „@ “№ %* №“ ; ;, a?” $$$? эт, , :3“: жёг/‘ _ .. :?“; %% Ётические’ даннЬ’і’е хранятся в  … , .2" 55. 1:83;. „ 3"“ , „№ \ “` «5,3… „ ,… {j №; @@@ г.№… №352; “,; № gig? «„ __обтеленномспомощью mamas-a. @@@?т     ……………  Shore ти   3,4 5" a”       244 Неделя 2. Основные вопросы 
Инициализация структур  Как и переменные других типов, структуры можно инициализировать при объявлении.  Это делается аналогично инициализации массивов: после объявления структуры ставится знак равенства и список начальных значений, разделенных запятыми и заключенных в фи— гурные скобки. Рассмотрим следующий пример:  1 2 3 4 5: 6: 7 8 9  : struct sale {  char customer[20]; char item[20]; float amount;  } mysale = { "Acme Industries", "Left-handed widget", 1000.00 }:  При выполнении этих операторов происходит следующее:  1. Определяется структура с меткой sale (строки 1—5).  2. Объявляется экземпляр структурного типа $а1е с именем mysale (строка 5).  3. Поле структуры mysale.customer инициализируется литералом "Acme Industries"  (строки 5 и6). Поле структуры mysale.item инициализируется литералом "Left-handed widget"  (строка 7). Поле структуры mysale.amount инициализируется значением 1000 . 00 (строка 8).  Если структура в свою очередь содержит другие структуры в качестве полей, то началь-  ные значения должны строго следовать порядку как структур, так и полей в их определениях. Вот пример, являющийся обобщением предыдущего:  ?“ 9’ P’ r‘  struct customer { char firm[20]; char contact[25]; }  struct sale { struct customer buyer; char item[20]; float amount; } mysale = { { "Acme Industries", "George Adams" }, "Left-handed widget", 1000.00 }: В этом фрагменте выполняются следующие инициализации: Поле mysale.buyer. firm инициализируется литералом "Acme Industries" (строка 10). Поле mysale.buyer. contact инициализируется литералом "George Adams" (строка 10). Поле mysale. item инициализируется литералом "Left-handed widget" (строка 1 1).  Поле mysale.amount инициализируется значением 1000.00 (строка 12).  День 11-й. Структуры, объединения и нестандартные типы данных 245 
‚__/"‘  Можно инициализировать и целые массивы структур. Заданные начальные значения по- мещаются в структуры массива в строгом порядке. Например, в следующем фрагменте кода объявляется массив структур типа 5а1е и инициализируются два его первых элемента (т.е.  две первые струкгурЫ): struct customer { char firm[20]; char contact[25];  }:  struct sale { struct customer buyer; . char item[20]; 9: float amount; 10: }; 11: 12: 13: struct sale y1990[100] = { 14: { { "Acme Industries", "George Adams" }, 15: "Left-handed widget", 16: 1000.00 17: } 18: { { "Wilson & Со." ‚ "Ed Wilson" }, 19: "Type 12 gizmo", 20: 290.00 21: } 22: }; \  Эти операторы работают следующим образом:  1 2 3 4: 5: б. 7 8  Поле у1990 [ 0] . buyer . firm инициализируется литералом "Acme Industries" (строка 14). Hone y1990 [ 0] .buyer. contact инициализируется литералом "George Adams " (строка 14). Hone y1990 [0 ] . item инициализируется литералом "Left-handed widget" (строка 15). Hone y1990 [ 0 ] .amount инициализируется значением 1000 . 00 (строка 16). Поле у1990 [ 1 ] .buyer. firm инициализируется литералом "Wilson & Co. " (строка 18). Hone y1990[1].buyer.contact инициализируется литералом "Ed Wilson" (строка 18).  Hone y1990 [1 ] .item инициализируется литералом "Type 12 gizmo" (строка 19).  .°°.".°`Е":“Е”!°1"  Hone y1990 [ 1] .amount инициализируется значением 290 . 00 (строка 20).  Структуры и указатели  Учитывая важность указателей в языке С, совсем не удивительно, что их можно эффек- тивно использовать для работы со структурами. Во-первых, указатели могут быть элементами структур, а во-вторых, можно объявлять и указатели на структуры. Давайте рассмотрим эти вопросы поподробнее в следующих разделах.  246 Неделя 2. Основные вопросы 
Указатели как поля структур  Программист имеет полную свободу в использовании указателей как элементов структур. Указатели-поля структур объявляются точно так же, как и любые указатели, не являющиеся членами структур, т.е. с помощью звездочки:  struct data  { int *value; int *rate; } first;  B этом примере определяется и создается структура, в которой оба элемента являются указателями на int. Как вы уже знаете, объявить указатели недостаточно —— их необходимо еще инициализировать. Это можно сделать, присвоив им подходящие адреса переменных. Если есть две переменные типа int с именами cost и interest, то можно записать:  first.value = &cost; first.rate = &interest;  Теперь указатели получили конкретные значения, и для обращения к ним можно исполь- зовать операцию ссылки (*), изученную на занятии 9. Выражение *first.value равно значению переменной cost, a выражение *first . rate — значению переменной interest.  Наверное, самым распространенным указателем, используемым в структурах, можно счи- тать указатель на char. Вспомните материал занятия 10: последовательность символов, одно- значно определенная указателем на ее начало и нулевым символом на конце, называется строкой. Для повторения пройденного объявим указатель на строку символов и инициализи- руем его: char *p_message; p_message = "Teach Yourself C In 21 Days";  То же самое можно проделать и с указателями на char, являющимися поляіии структур:  struct msg { char *pl; char *p2; } myptrs; myptrs.p1 "Teach Yourself C In 21 Days"; myptrs.p2 = "By SAMS Publishing";  Ha рис. 11.4 показан результат выполнения этих операторов. Каждый из указателей в структурах указывает на первый байт строки, помещенной в памяти где-то в другом месте. Сравните этот способ с рис. 11.3, где показано, как данные хранятся в структурах, содержа- щих массивы символов. Указатели, входящие в структуры, можно использовать везде, где вообще допускается применение указателей. Например, чтобы вывести на экран содержимое строки, пишем сле- дующее: ргіпті("%з %s", myptrs.p1, myptrs.p2);  B чем разница между использованием массива типа char и указателя того же типа в каче- стве элементов структуры? В сущности, оба эти способа предназначены для “помещения” строк в структуры, как показано ниже. Здесь в структуре msg используются оба способа:  struct msg  {  День 11-й. Структуры, объединения и нестандартные типы данных 247 
char p1[30]; char *p2; /* осторожно: не инициализирован! */ } mYPtIS; myptrs myptrs.p1 myptrs.p2   Puc. 11.4. Структура, содержащая указатели типа char  Вспомните: имя массива без квадратных скобок само по себе является указателем на пер- вый элемент массива. Поэтому к двум приведенным элементам структуры можно обращаться одинаковым образом (не забудьте инициализировать р2‚ прежде чем помещать что-либо по хранящемуся в нем адресу): strcpy(myptrs.p1, "Teach Yourself C In 21 Days"); strcpy(myptrs.p2, "By SANS Publishing"); /* здесь идет дополнительный код */ puts(myptrs.p1); puts(myptrs.p2);  B чем же разница между этими двумя способами? А вот в чем: если определить структуру с массивом типа char, то каждый экземпляр такой структуры будет непосредственно солер- жать участок памяти нужной длины для хранения этого массива. Кроме того, этот размер ни- как нельзя превысить, поместив в массив строку большего размера. Вот пример:  struct msg { char p1[10]; char p2[10]; } mYPtIS; strcpy(p1, "Minneapolis"); /* Ошибка! Строка длиннее массива. */ strcpy(p2, "MN"); /* Нет ошибки, но напрасная трата памяти, */ /* поскольку строка короче массива. */  Если же определить структуру, имеющую в своем составе указатель на строку, то эти ограничения отпадут. В каждом экземпляре структуры выделяется место лишь для указате- ля, а сами строки находятся в других местах (где именно — это не ваша забота). Ни на- прасной траты памяти, ни ограничения на длину строки не будет, поскольку сами строки не хранятся непосредственно в структуре. Каждый указатель может указывать на строку любой длины, и строка фактически становится частью структуры, хотя и не помещена в память в едином блоке с ней.  248 Неделя 2. Основные вопросы 
 — Если не инициализировать указатель, то можно случайно затереть ин- Illllllll формацию в памяти, предназначенную для чего-то важного. Поэтому, ис- ` пользуя указатели вместо массивов, выполняйте их корректную инициа- лизацию. Это можно пр0делать с помощью динамического выделения памяти или ассоциируя указатели с адресами реальных переменных.     Создание указателей на структуры  В языке С можно объявлять и использовать указатели на структуры -— точно так же, как указатели любых других типов. Как будет показано далее, указатели на структуры часто ис- пользуются при передаче структур в функции через аргументы. Еще указатели на структуры применяются в чрезвычайно мощном метоле хранения и обработки данных под названием связанные списки. Этот мет0д подробно разбирается на занятии 15, посвященном дополнич тельным возможностям указателей. Сейчас мы обсудим, как же объявить и использовать указатель на структуру в программе на С. Вначале определим структуру: struct part  { short number;  char name[10];  Теперь объявим указатель на переменную типа part: struct part *p_part;  Напомним, что знак ссылки (*) в объявлении показывает, что p_part является указателем на значение типа part, a He экземпляром этого структурного типа. Можно ли теперь инициализировать этот указатель? Увы, нет. Хотя структурный тип part уже определен, пока что не создано ни Одного экземпляра этого типа. Помните, что не опре— деление структурного типа, а объявление переменной выделяет место в памяти для объекта данных. Указатель должен с0держать вполне определенный адрес объекта. Поэтому необхо- димо вначале объявить хотя бы Один экземпляр типа part, чтобы указатель смог указывать на что-то реально существующее. Вот это объявление:  struct part gizmo; BOT сейчас уже можно инициализировать указатель: p_part = &gizmo; Этот оператор присваивает адрес структуры gizmo указателю p_part. (Вспомните опера- цию взятия адреса, рассмотренную на занятии 9.) На рис. 11.5 показано соотношение между структурой и указателем на структуру.  gizmo.number gizmo.name[] ___JL__,  '... fl                   .. . ..,. „„ „…, и … „ам-, v' „ on new ›. мь‹ь‚№м…_зм‚__„ъц › мд. *__*—‚№№3и №Их№зй№ йщща …  §p_part      клюв» .»- w дг‘  Рис. 11.5. Указатель на структуру: указывает на первый байт хранимых данных  День 11 -й. Структуры, объединения и нестандартные типы данных 249 
Итак, у нас есть указатель на структуру gizmo. Как же обращаться к структуре, используя этот указатель? Один из способов—— воспользоваться операцией ссылки по указателю (*). Напомним материал занятия 9: если ptr — указатель на элемент данных, то выражение *ptr обозначает само значение этого элемента. Применим эти знания к данному примеру. Если p_ptr — указатель на структуру gizmo, то *p_ptr ——-— это сама структура. К отдельным полям структуры можно обращаться с помощью точки (. ). Например, чтобы присвоить значёчие 100 полю gizmo.number, запишем следующее: (*p_part).number = 100; Выражение *p_part необходимо заключить в скобки, поскольку операция обращения к эле- менту структуры (.) имеет более высокий приоритет, чем операция ссылки по указатетпо (*). Есть еще один способ работы с полями структуры через указатели. Он предполагает ис- пользование косвенного обращения к элементам структуры. Знак этой операции состоит из двух символов -> (дефиса и знака “больше”). Когда эти символы стоят рядом без пробела между ними, компилятор воспринимает их как один знак операции, а не два. Знак косвенного обращения ставится между именем указателя и именем поля. Например, для обращения к по- лю number структуры gizmo через указатель p_part можно записать следующее:  p_part->number Соответственно, если str _— структура, p_str — указатель на эту структуру, а memb _— по- ле структуры str, то запись str .memb имеет то же значение, что и следующая: p_str->memb Итак, есть три способа обращения к полю структуры. I Через имя структуры I Через указатель на структуру и ссылку по указателю (*) I Hepes указатель на структуру и косвенное обращение к элементам структуры (->) Пусть p_str указывает на структуру str. Тогда следующие три выражения эквивалентны:  str . memb (*p_str) .memb p_str->memb   Косвенное обращение к элементу структуры называют еще обращением к "№"! структуре по указателю.     Указатели и массивы структур  Указатели на структуры и массивы структур являются весьма мощными средствами про- граммирования. Оба этих средства можно комбинировать, используя указатели для обраще- ния к структурам — элементам массивов. Для примера возьмем определение структуры из ранее приведенных фрагментов кода: struct part  {  short number; char name[10];  Определив структурный тип part, можно объявить массив структур этого типа:  250 Неделя 2. Основные вопросы 
struct part data[100];  Далее объявим указатель на структуру типа part и присвоим ему адрес первого элемента в массиве data:  struct part *p_part; p_part = &data[0];  Напоминаем, что имя массива без квадратных скобок является указателем на первый эле— мент массива, поэтому вторую строку можно записать еще и так:  p_part = data;  Теперь у нас есть массив структур типа part и указатель на первый элемент массива (т.е. первую структуру в массиве). Можно, например, вывести на экран содержимое этого первого элемента: printf("%d %s", p_part->number, p_part->name);  Что если необходимо вывести все элементы массива? Вероятно, понадобится цикл for, B котором за один проход будет выводиться один элемент. Для обращения к элементам через указатель необходимо изменять p_part таким образом, чтобы он указывал на следующую структуру в массиве после каждого прохода цикла. Как же это сделать? На помощь нам приходит адресная арифметика языка С. Одноместная операция инкре- ментирования (приращения) в применении к указателям имеет особый смысл. Она означа- ет “увеличить указатель на размер объекта, на который он указывает”. Другими словами, пусть у нас есть указатель ptr на объект данных типа obj. Тогда следующие два оператора зквивалентньк ptr++; ptr += sizeof(obj); Этот аспект адресной арифметики особенно важен для работы с массивами, поскольку элементы массивов располагаются в памяти последовательно один за другим. Если указатель указывает на элемент массива п, то после его инкрементирования (операция ++) он будет ука- зывать на элемент п+1. Иллюстрацией этому служит рис. 11.6, на котором показан массив х[ ], состоящий из 4—байтных элементов (например, структур, содержащих по два двухбайт- ных элемента типа short). Указателю ptr присвоено значение адреса х[0]. Всякий раз после инкрементирования ptr OH указывает на следующий элемент массива.  Х[О] Х[11 X[2] L .1. *   г Y ‘T ‘1 1001 1002100310041005100610071000100910101011 1012 -------    “ptr++ :ptr++ ’ -‘ _,  Рис. I 1.6. Движение указателя вдоль массива структур  Все это означает, что программа может перебирать массив структур (или если уж на то пошло, массив вообще любых данных) путем инкрементирования указателя. Данный способ работы с массивом обычно дает более удобную и краткую запись, чем обращение по индексу. В листинге 11.5 приведен пример такой записи.  День 11—й. Структуры, объединения и нестандартные типы данных 251 
Листинг 1 1 .5. access .c — обращение к последовательным элементам массива с помощью указателя   ШЧФШЬШЮН .. о. .. о. .. о. о. ..  ") о.  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:  /* демонстрация перебора массива структур *] /* с помощью указателей. */  #include <stdio.h>  #define MAX 4  /* Объявление структуры, затем объявление и инициализация */ /* массива из четырех структур. */  struct part {  short number; char name[10];  } data[MAX] = {1, "Smith",  2, "Jones", 3, "Adams", 4, "Wilson" }:  /* Объявление указателя на тип part и переменной—счетчика. */  struct part *p_part; int count;  int main( void )  {  }  /* Установка указателя на первый элемент массива. */  ' p_part = data;  /* Перебор массива с приращением указателя */ /* на каждом проходе цикла. */  for (count = 0; count < MAX; count++)  {  printf("At address %d: %d %s\n", p_part, p_part—>number,  p_part—>name); p_part++; }  return 0;  0 At address 4202504: 1 Smith №№… At address 4202516: 2 Jones  252  At address 4202528: 3 Adams At address 4202540: 4 Wilson  Неделя 2. Основные вопросы 
№ Вначале в строках 11—18 объявляется и инициализируется массив с именем data структур типа part. B строке 22 объявляется указатель p_part для указания на массив структур data. Первое, что выполняет функция шаіп( ), это устанавливает указатель p_part на начало объявленного массива структур. После этого в строках 34—39 B цикле for выводятся все элементы массива. Для перебора элементов используется указатель, получаю— щий приращение на каждом проходе цикла. Программа также отображает адрес каждого элемента. Посмотрите внимательно на выведенные программой адреса. В вашей конкретной систе- ме их значения могут отличаться, но расстояние между ними будет одинаковым: в точности размер структуры-элемента массива. Это демонстрирует со всей наглядностью, что инкре- ментирование указателя дает ему приращение, равное размеру объекта данных в массиве.  Передача структур в функции  Как и данные других типов, структуры можно передавать в функции в качестве аргумен- тов. Листинг 11.6 демонсгрирует, как это делается. Приведенная в нем программа является модификацией программы из листинга 11.3. В ней используется функция, которая выводит на экран данные из переданной в нее структуры, в то время как в программе листинга 11.3 эта же задача выполняется непосредственно в функции шаіп( ).  Листинг 1 1 .6. func . c — передача структуры в функцию   1: /* демонстрация передачи структуры в функцию. */ 2: 3: #include <stdio.h> 4: S: /* Определение и объявление структуры data. */ 6: 7: struct data { 8: float amount; 9: char fname[30]; 10: char lname[30]; 11: } rec; 12: 13: /* Прототип функции. Функция не возвращает никакого значения. */ 14: /* Единственный аргумент - структура типа data. */ 15: 16: void print_rec(struct data displayRec); 17: 18: int main( void ) 19: { 20: /* Ввод данных с клавиатуры. */ 21: 22: printf("Enter the donor's first and last names,\n"); 23: printf("separated Ьу а space: "); 24: scanf("%s %s", rec.fname, rec.lname); 25: 26: printf("\nEnter the donation amount: "); 27: scanf("%f", &rec.amount); 28:  День 11—й. Структуры, объединения и нестандартные типы данных 253 
29: /* Call the display function. */  30: print_rec( rec ); 31: 32: return 0; \ 33: } k 34: void print_rec(struct data displayRec) ` 35: { 36: printf("\nDonor %s %s gave $%.2f.\n", displayRec.fname, 37: displayRec.lname, displayRec.amount); 38: }    P m" Enter the donor 's first and last names, 239nm separated by a space: Bradley Jones  Enter the donation~amount: 1000.00  Donor Bradley Jones gave $1000.00.  № В строке 16 находится прототип функции, принимающей структуру в качестве аргумента. Как полагается, в прототипе объявлен список ее параметров —— в дан-  ном случае это структура типа data. Прототип совпадает с заголовком функции в строке 34. При вызове функции достаточно передать в нее имя экземпляра структуры, т.е. в данном слу- чае rec (строка 30). BOT, собственно, и все. Передача структуры в функцию ничем не отлича— ется от передачи простой переменной. В функцию можно передавать и адрес структуры (т.е. указатель на нее) вместо самой структуры. Фактически, в старых версиях С это был единственный способ передачи структур в функции. Сейчас в этом нет необх0димости, но вам могут встретиться старые программы, которые все еще применяют этот способ. Если в функцию передается указатель на структуру, то для обращения к отдельным полям структуры в теле функции необходимо использовать косвенное обращение (->).     Рекомендуется He рекомендуется №['Сольэмт‘есь npemyme MUM , ` ‘ .    ”:0"; (`)"д …  УПУР №111: гЙсподъзуите 15MMBMM ‚‚ аэлементрм (_?) npwp  ми на струкгуры. „зі  А…‘мм::чп_-—      Объединения  Объединения (unions) внешне похожи на структуры. Объединение объявляется и исполь- зуется аналогичным образом. В отличие от структуры, всякий раз только Один из элементов объединения доступен для обращения. Причина этого проста—_ все элементы объединения занимают одну и ту же область памяти, как бы располагаясь один поверх другого.  254 Неделя 2. Основные вопросы 
Определение, создание и инициализация объединений  Объединения создаются и объявляются аналогично структурам. Единственным различием при их объявлении является использование ключевого слова union вместо struct. Для при- мера объявим простейшее объединение, состоящее из одной целой и одной символьной пе- ременной: union shared  {  char 0; int i;  Определение этого объединения, shared, можно дальше использовать для создания его экземпляров, которые могут соцержать или целочисленное значение і, или символьное с. Тут очень важно понять условие “или —-— или”. В отличие от структуры, в которой сразу хранились бы оба значения, объединение может содержать только одно из них. На рис. 11.7 показано, как это объединение размещается в памяти.  shared.i  тт   shared.c  Puc. 11. 7. Способ размещения объе- динения в памяти  При объявлении объединения его можно инициализировать. Раз уж только один элемент объединения может использоваться при каждом обращении к нему, то и инициализировать нужно только один элемент. Bo избежание путаницы инициализируется первый элемент. Вот пример объявления и инициализации объединения типа shared: union shared generic_variab1e = { ’9’ }; Обратите внимание, что объединение инициализируется точно так же, как первый эле- мент структуры.  Обращение к элементам объединения  Обращение к отдельным полям объединения выполняется так же, как к полям структуры: с помощью точки (.). Однако есть и существенное различие. Обращаться всякий раз можно только к одному элементу объединения, поскольку элементы хранятся один поверх другого. В листинге 11.7 представлен пример.  День 11 -й. Структуры, объединения и нестандартные типы данных 255 
Листинг 1 1 .7. ипіоп.с — пример некорректного использования объединения   _/ /* Пример одновременного обращения к нескольким полям объединения */ //  #include <stdio.h>  int main( void )  фЧФШЬШЮН " п п п п п п "   { ипіоп shared_tag { char c; int i; 9: long 1; 10: float f; 11: double d; 12: } Shared; 13: 14: shared.c = '$'; 15: 16: printf("\nchar с = %с", shared.c); 17: printf("\nint і = %d", shared.i); 18: printf("\nlong l = %ld", shared.l); 19: printf("\nfloat f = %f", shared.f); 20: printf("\ndouble d = %f", shared.d); 21: 22: Shared.d = 123456789.8765; 23: 24: printf("\n\nchar с = %с", shared.c); 25: printf("\nint і = %d", shared.i); 26: printf("\nlong l = %ld", shared.l); 27: printf("\nfloat f = %f", shared.f); 28: printf("\ndouble d = %f\n", shared.d); 29: 30: return О; 31: } char с = $ … і : 65572 long 1 = 65572 float f = 0.000000 double d = 0.000000 char с = 7 int і = 1468107063 long 1 = 1468107063 float f = 284852666499072.000000 double d = 123456789.876500 Анализ B строках 6—12 этой программы объявляется и создается объединение shared.  Объединение содержит пять элементов, все различных типов. В строках 14 и 22 выполняется инициализация отдельных элементов объединения shared. Затем в строках 16— 20 и 24—28 выполняется вывод всех элементов с помощью функции printf( ).  256 Неделя 2. Основные вопросы 
Обратите внимание, что за исключением значений char с = ’ $ ’ и double (1 = 123456789.876500, результаты на экране вашего компьютера могут отличаться от приведен- ных. Раз в строке 14 было инициализировано символьное поле объединения с, то и использо- ваться должно только оно -— до тех пор, пока не будет инициализировано следующее. Ре— зультаты вывода остальных элементов (і, 1, f и d) B строках 16—20 непредсказуемы. В стро- ке 22 значение помещается уже в вещественное поле ‹:1. Теперь результат вывода всех остальных полей, кроме d, становится непредсказуемым. Помещенное в с значение (строка 14) теряется, поскольку его затирает значение ‹:1, присвоенное в строке 22. Все это по- казывает, что поля объединения действительно занимают одну и ту же область памяти.  Ключевое слово ипіоп  union метка { член ( ы )_объедннення; /* здесь могут стоять дополнительные операторы */ } экземпляр;   Ключевое слово union используется для объявления объединений. Обьединение представляет` собой совокупность одной или нескольких переменных (член(н)_объедннения) под одним общим именем. Кроме того, все члены обьеди— нения занимают один и тот же участок в памяти. Ключевое слово union указывает начало определения объединения. За ним следом идет метка типа объединения. Затем в фигурных скобках стоят члены объединения. Можно также объявить экземпляр, т.е. фактический объект этого типа. Если опре- делить объединение, не создавая экземпляра, это определение будет всего лишь шаблоном, который можно использовать далее в программе для объявления экземп- ляров объединений. Такой шаблон имеет следующий синтаксис: union метка { член( 15! ) объединения; /* здесь могут стоять дополнительные операторы */ }: Объявление объединения с помощью шаблона выглядит следующим образом: union метка экземпляр;  Конечно, объединение с этой меткой должно было быть определено заранее.  Пример1  /* Объявление шаблона объединения tag */ union tag { int nbr; char character; } /* Использование шаблона в объявлении экземпляра */ union tag mixed_yariable;  Пример2  /* Совместное объявление типа и экземпляра */ union generic_type_tag { char 0;  День 11-й. Структуры, объединения и нестандартные типы данных 257 
int i; float f; double d; } generic;  Примерз  /* Инициализация объединения. */ union date_tag { char fu11_date[9]; struct part_date_tag { char month[2]; char break_va1ue1; char day[2]; char break_va1ue2: char year[2]; } part_date; } date = { "01/01/97“ };  В листинге 11.8 демонстрируется часто встречающееся на практике, хотя и несколько уп- рощенное, применение объединений`  Листинг 1 1 .8. union2 .c — практическое применение объединений   1 2 3 4: 5: 6. 7 В  9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:  258  /* Типичный пример применения объединений */ #include <stdio.h>  #define CHARACTER ’C’ #define INTEGER ’I' #define FLOAT ’F’  struct generic_tag{ char type; union shared_tag {  char c; int i; float f; } shared;  }: void print_function( struct generic_tag generic ); int main( void ) { struct generic_tag var; var.type = CHARACTER: var.shared.c = '$';  print_function( var );  var.type = FLOAT;  Неделя 2. Основные вопросы 
29: var.shared.f = (float) 12345.67890;  30: print_function( var ); 31: 32: var.type = 'x'; 33: var.shared.i = 111; 34: print_function( var ); 35: return 0; 36: } 37: void print_function( struct generic_tag generic ) 38: { 39: printf("\n\nThe generic value 13..."); 40: switch( generic.type ) 41: { 42: case CHARACTER: printf("%c", generic.shared.c); 43: break; 44: case INTEGER: printf(“%d", generic.shared.i); 45: break; 46: case FLOAT: printf("%f", generic.shared.f); 47: break; 48: default: printf("an unknown type: %c\n", 49: generic.type); 50: break; 51: } 52: }   The generic value is...$ Резцпьшаш The generic value із… .12345.678711  The generic value is...an unknown type: K  Эта программа представляет весьма упрощенную версию того, что можно делать с помощью объединений, а именно способ поочередного хранения данных раз- ных типов в одном участке памяти. Структура generic_tag позволяет помещать в одну об- ласть памяти либо символ, либо целое число, либо вещественное число. Эта область памяти представлена объединением shared, усгроенным точно так же, как в листинге 11.7. B струк- туре generic_tag имеется дополнительное поле под именем type. Это поле используется для хранения информации o типе переменной, помещенной в объединение shared. Поле типа препятствует некорректному обращению к объединению shared, таким образом помогая из- бежать ошибочного вывода данных, показанного в листинге 11.7. Рассмотрим эту программу подробнее. В строках 5, 6 и 7 объявляются константы CHARACTER, INTEGER и FLOAT. Они используются далее в программе для удобства обозна- чения типов. В строках 9—16 определяется структурный тип generic_tag, который будет использоваться в объявлениях чуть позже. В строке 18 представлен прототип функции print_function( ). B строке 22 объявлена структура var, которая затем инициализиру- ется символьными значениями в строках 24—25. Вызов функции print_function() в строке 26 выводит значение на экран. В строках 28—30 и 32—34 эта процедура повторя- ется с другими значениями. Центральной частью программы является функция print_function( ). Здесь она исполь- зуется для вывода значенийиз структуры типа generic_tag. Аналогичную функцию можно бьшо бы применять и для присваивания сгрукгуре значений. Функция print_function() ана-  День 11—й. Структуры, объединения и нестандартные типы данных 259 
лизирует переменную type для вывода корректного значения из объединения с подобающим сопроводительным текстом. Благодаря этому не происходит ошибочного вывода данных, по- казанного в листинге 11.7.   Рекомендуется Не рекомендуется   ё-Бейійтіііф mm знов объе мнений ЁЁ …"? ёпт № " ""   “&:&.   ` …ч ;“ «“ВЭЙЁЁЕЁ “ЁЁ аННЁй ouem‘m „ чение водно попе:, с›шфнабватьзя дРУЩМЁЁ  „&;ч;‚;ч“ " “392 \; пред/Ёе- уемйи№упьтётёё  Ф “Maw *M‘w       “IQ-I10   Создание структурных типов с помощью typedef  C помощью ключевого слова typedef создается синоним структурного типа или метки объединения. Например, в следующем объявлении создается синоним coord для некоторого структурного типа: typedef struct { int x; int y; } coord; Далее можно объявить экземпляры этого типа, пользуясь его идентификатором: coord topleft, bottomright;  Обратите внимание, что созданный таким образом тип отличается от метки структуры. В следующей записи идентификатор coord является меткой структуры: struct coord { int x; int y;  C помощью этой метки можно объявлять экземпляры данного структурного типа, но в отличие от определенных через typedef типов, для этого придется писать ключевое слово struct: struct coord topleft, bottomright; Выбор между typedef и меткой структуры является делом вкуса. Большой практической разницы тут нет. Определение типа через typedef несколько короче, потому что не надо пи- сать ключевого слова struct. C другой стороны, использование этого слова и метки струк-  турного типа ясно показывает, что объявляется именно структура, и это удобно для програм- миста при чтении кода.  260 Неделя 2. Основные вопросы 
Резюме  На этом занятии мы рассмотрели использование структур ——-— составных типов данных, проектируемых специально для нужд конкретной программы. Структура может содержать элементы любых типов данных С, в том числе другие структуры, массивы и указатели. Об- ращение к отдельному элементу данных внутри структуры, называемому также членом или полем, производится с помощью знака точки (.) между именем структуры и именем поля. Структуры могут использоваться по отдельности или объединяться в массивы. Объединения имеют сходство со структурами. Основное различие между структурами и объединениями состоит в том, что в объединении все поля хранятся в одной и той же области памяти, накладываясь друг на друга. В результате элементами объединения можно пользо- ваться только по очереди, а не вместе, как в структуре.  Вопросы и ответы  Есть ли смысл определять структуру без объявления ее экземпляров? Сегодня бьши рассмотрены два способа объявления структур. Один из них ——-— это одно- временное объявление метки, тела и экземпляров структурного типа. Второй способ — это объявление метки и тела без экземпляров. Экземпляры структурных переменных можно объ- явить потом, записав ключевое слово struct, метку типа и имя экземпляра. Наиболее распро- страненным на практике является именно второй способ. Многие программисты объявляют структурные типы отдельно, а экземпляры этих типов — позже по мере необходимости. На следующем занятии будет рассмотрена область действия переменных, и в этой связи важно знать, что область действия имеет значение для экземпляров структур, но не для их типов.  Какой способ объявления структурного типа более распространен: с помощью typedef или метки структуры? Многие программисты используют typedef для удобочитаемости программ, но на самом деле большой практической разницы здесь нет. На рынке программного обеспечения предла- гается много библиотек дополнительных функций. Обычно в них применяется много опреде- лений типов с помошью typedef, чтобы сделать их код оригинальным. Особенно это спра- ведливо в отношении программных продуктов для работы с базами данных.  Можно ли попросту скопировать одну структуру в другую с помощью оператора присваивания? И да, и нет. Новейшие версии компиляторов С позволяют это делать, а вот более ста— рые — нет. Работая со старым компилятором, придется копировать каждый элемент структу- ры отдельно. Это относится и к объединениям.  Как вычислить размер объединения? Все члены объединения располагаются в одной области памяти, накладываясь друг на друга. Поэтому объем памяти, необходимый для размещения объединения, равен размеру его самого длинного элемента.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления пройденно- го материала, а также упражнения для выработки практических навыков программирования.  День 11-й. Структуры, объединения и нестандартные типы данных 261 
Контрольные вопросы  999592"  В чем разница между структурой и массивом? Зачем нужна операция обращения к элементу структуры и каким знаком она обозначается? С помощью какого ключевого слова объявляется структура? В чем разница между меткой структурного типа и экземпляром структуры? Что делает следующий фрагмент кода? struct address  { char name[31]; char add1[31]; char add2[31]; char city[11]; char state[3]; char zip[11]; } myaddress = { "Bradley Jones", "RTSoftware", "P.0.Box 1213", "Carmel", "IN", "46082-1213"};  Если вы создали тип п0д названием word с помощью ключевого слова typedef, то как вы объявите переменную myWord этого типа?  Предположим, вы объявили массив структур и указатель ptr, который указывает на его первый элемент (т.е. первую структуру в массиве). Как изменить ptr так, чтобы он указы- вал на второй элемент массива?  Упражнения  1. Напишите определение структуры time, содержащей три поля типа int.  Напишите фрагмент кола, выполняющий две задачи: во-первых, определение структуры data, состоящей из одной переменной типа int и двух переменных типа float, во-вторых, объявление экземпляра типа data с именем info.  Продолжая упражнение 2, присвойте значение 100 целочисленному полю структуры info.  4. Напишите операторы объявления и инициализации указателя на структуру info.  5. Процолжая упражнение 4, присвойте значение 5.5 первому из вещественных (float)  полей структуры info с использованием указателя, причем двумя способами. Напишите определение структурного типа с именем data, B котором может храниться строка длиной до 20 символов.  Объявите структуру, содержащую пять строк: addressl, addressz, city, state, zip. Оп- ределите тип RECORD с помощью ключевого слова typedef, чтобы можно бьшо объявлять структуры этого типа. Используя определение типа из упражнения 7, объявите и инициализируйте переменную myaddress.  Поиск ошибок. Найдите ошибки в следующем коде:  262 Неделя 2. Основные вопросы 
struct { char zodiac_sign[21]; int month; } sign = "Leo" , 8;  10. Поиск ошибок. Найдите ошибки в следующем коде:  /* объявление объединения */ union data { char a_word[4]; long a_number; } generic_yariable = { "WOW", 1000 };  День 11-й. Структуры, объединения и нестандартные типы данных  263 
  Область действия переменных  В примерах занятия 5 часто оказывалось так, что переменная, объявленная внутри функ- ции, отличалась по своим свойствам от переменной, объявленной вне функции. Уже тогда неявно использовалось понятие области действия переменных— важный аспект програм- мирования на С. На этом занятии вы узнаете следующее.  I Область действия переменных и важность этого понятия Что такое внешние переменные и почему их желательно избегать Тонкости работы с локальными переменными Различие между статическими и автоматическими переменными  Локальные переменные и блоки  Выбор класса памяти для переменных  Что такое область действия  НПБЫП [HEWIIH Термин область действия в применении к переменной означает право досгупа различных частей программы к этой переменной, т.е. в каких частях программы  эта переменная видима, а в каких —— нет. В отношении переменных языка С термины область доступности и область видимости взаимозаменяемы. На этом занятии мы будем называть переменными любые объекты допустимых в С типов данных: простые переменные, массивы, структуры, указатели и т.д. Сюда же мы включаем и символические константы, определен- ные через ключевое слово const.  НОВЫЙ mBDMIJH Область действия также влияет на время жизни переменной, т.е. время, в тече- ние которого переменная хранится в памяти. Имеются в виду в первую очередь  моменты, в которые для переменной выделяется или освобождается память. Далее поговорим об этих понятиях подробнее. 
Пример области действия  В программе, приведенной в листинге 12.1, объявляется переменная ){ (строка 5), затем ее значение выводится на экран функцией printf ( ) в строке 11 и, наконец, еще раз выводится путем вызова функции print_va1ue( ). Заметьте, что в функцию print_va1ue( ) не передают- ся никакие аргументы. Она просто выводит значение x B строке 19 с помощью все той же функции printf( ).  Листинг 1 2. 1 . scope . с — доступность переменной x из функции print_va1ue( )   1: /* Иллюстрация области действия переменной. */ 2: 3: #include <stdio.h> 4: 5: int x = 999; 6: 7: void print_va1ue(void); 8: 9: int main( void )  10: { 11: printf("%d\n", x); 12: print_va1ue(); 13: 14: return 0; 15: } 16: 17: void print_va1ue(void) 18: { 19: printf("%d\n", x); 20: }   999 Результат 999  Эта программа проходит компиляцию и запускается на выполнение без каких—либо про— блем. А теперь внесем в нее небольшое изменение, перенеся объявление переменной x B функцию main( ). Получившаяся программа приведена в листинге 12.2. Объявление перемен- ной x теперь находится в строке 9.  Листинг 1 2.2. scope2 . с — недоступность переменной x из функции print_va1ue( )   1: /* Иллюстрация области действия переменных. */ Ё: #include <stdio.h> Ё: void print_va1ue(void); 3: int main( void )  День 12-й. Область действия переменных 265 
8: { 9: int х = 999; 10: ll: printf("%d\n", x); 12: print_value(); 13: 14: return О; 15: } 16: 17: void print_value(void) 18: { l9: printf("%d\n", x); 20: }   При попытке компиляции листинга 12.2 компилятор выдаст примерно такое со- общение об ошибке:  list1202.c(l9) : Error: undefined identifier 'x'.  B сообщениях об ошибках номер в скобках обозначает строку программы, в которой про- изошла данная ошибка. В строке 19 у нас нах0дится вызов функции printf( ) внутри print_value( ). Это сообщение говорит нам, что при компиляции строки 19 переменная x оказалась неоп- ределенной, или невидимой, внутри функции ргіп1:_ча1ие( ). Однако просим заметить, что при вызове printf() B строке 11 никакого сообщения об ошибке не было. В этой части про- граммы, в отличие от функции ргіп1:_уа1ие( ), переменная x является видимой. Единственная разница между листингами 12.1 и 12.2 состоит в том, где определена пере- менная x. Переместив объявление x B другое место, мы изменили ее область действия. В лис- тинге 12.1 переменная определена вне функции шаіп( ), поэтому она является внешней пере- менной, и ее область действия распространяется на всю программу. Она доступна как в функции шаіп( ), так и в функции print_value( ). B листинге 12.2 эта же переменная x опре- делена внутри функции шаіп( ), поэтому является локальной переменной для этой функции и недоступна за ее пределами. С точки зрения функции ргіп1:_ча1ие() переменная x вообще не существует, потому-то компилятор и сообщил об ошибке. Несколько позже мы обсудим ло- кальные и внешние переменные более подробно. Но вначале необходимо осознать всю важ- ность такого понятия, как область действия переменных.  Важность области действия переменных  Чтобы осознать, как важно хорошо разбираться в области действия переменных, вспом- ните занятие 5 и обсуждавшееся на нем структурное программирование. Структурный подход к программированию, как вы помните, заключается в разделении программы на независимые функции, каждая из которых выполняет свою специальную задачу. Ключевой момент здесь— независимость функций. Для настоящей независимости необходимо, чтобы пере- менные каждой функции были полностью изолированы от возможного вмешательства других частей программы. Только полное отделение данных каждой функции от всех остальных данных гарантирует, что функция будет спокойно выполнять свою работу, и никакая другая часть программы не окажется по отношению к ней “слоном в посудной лавке”. Как вы скоро узнаете, объявление переменных внутри функции “прячет” их от других функций, т.е. делает совершенно недоступными.  266 Неделя 2. Основные вопросы 
Если вы полагаете, что полная изоляция всех локальных данных друг от друга не всегда желательна, то вы правы. Умело манипулируя областью действия переменных, программист может обеспечить гибкий и надежный контроль за взаимодействием данных в программе.  Создание внешних переменных  Внешняя переменная — это переменная, объявленная вне каких бы то ни было функций, в том числе, конечно же, и вне функции main( ). До сих пор в боль— шинстве примеров нашей книги переменные были в основном внешними и помещались в на- чале исходного кода программ перед функцией шаіп( ). Внешние переменные также называ— ют глобальными переменными.  Новый термин   — Если не инициализировать внешнюю переменную при объявлении явным Infill! образом (присвоением начального значения), то компилятор сам инициа- лизирует ее значением 0.     Область действия внешних переменных  Облаегь действия внешних переменных охватывает всю программу. Это означает, что внешняя переменная видна как из функции main( ), так и из всех остальных функций про— граммы. Например, переменная x B листинге 12.1 является внешней. Как вы уже убедились при компиляции и запуске программы, переменная x доступна в обеих функциях — main( ) и print_value( ) — и была бы видна из любой другой функции, какую вы бы пожелали доба— вить в программу. Строго говоря, утверждение о том, что область действия внешней переменной — вся про— грамма, не совсем верно и требует оговорки. Правильнее будет сказать, что область действия такой переменной распространяется на весь файл исходного кода, в котором она объявлена. Если вся программа помещается в один файл исходного кода, то эти две формулировки сов- падают. Множество сравнительно небольших программ на С занимают всего один файл. Это относится и ко всем программам, которые мы разбирали на занятиях или вы писали при вы— полнении упражнений. Тем не менее, исходный код программы может храниться и в нескольких отдельных фай— лах. Как это делается, вы узнаете на занятии 21, и тогда же мы обсудим, какие специальные средства языка С нужно применять для корректной работы с внешними переменными в этом  случае.  Принципы использования внешних переменных  НПВЫЙ ШЕПМНН Хотя в примерах программ у нас все время использовались внешние перемен— ные, В практике программирования их следует по возможности избегать. Поче—  му? Потому что их применение нарушает ключевой принцип структурного программирова- ния ———- независимость модулей программы. Он состоит в том, что каждая функция (модуль) программы должна содержать весь код, необходимый ей для работы. В небольших програм— мах, с которыми мы до сих пор имели дело, этот принцип не играл никакой роли. Однако по мере возрастания объема и сложности программ избыток внешних переменных начинает ска— зываться отрицательно, ухудшая как работу программы, так и ее удобочитаемость.  День 12—й. Область действия переменных 267 
Так когда же все—таки использовать внешние переменные? Делайте переменную внешней только в том случае, если доступ к ней нужен подавляющему большинству функций про- граммы. Наилучшими кандштатами на роль внешних переменных часто являются символиче- ские константы, определенные с помощью ключевого слова const. Если же доступ к пере- менной нужен только некоторым из функций программы, то передавайте ее как аргумент вместо того, чтобы объявлять внешней.  Ключевое слово extern  Если в функции используется внешняя переменная, то согласно хорошему стилю программирования ее следует объявить внутри этой функции с помощью ключевого слова extern B следующем виде:  extern тип имя;  Здесь тип должен обозначать тип переменной, а имя— ее идентификатор. Например, можно добавить объявление внешней переменной x B функции шаіп() и print_value() B листинге 12.1. B итоге получится программа, приведенная в листинге 12.3.  Листинг 1 2.3. extern . 0 ~— объявление внешней переменной в функциях   1: /* Иллюстрация объявления внешних переменных. */ 2: 3: #include <stdio.h> 4: 5: int x = 999; 6: 7: void print_value(void); 8: 9: int main( void ) 10: { 11: extern int x; 12: 13: printf(”%d\n", x); 14: print value(); 15: ' 16: return 0; 17: } 18: 19: void print_value(void) 20: { 21: extern int x; 22: printf(”%d\n”, x); 23: }   _ ‚ 999 рЕЗЦЛЬШНШ 999  Эта программа выводит значение x дважды— вначале в строке 13 в составе функции main( ), а затем в строке 22 в функции print_value( ). B строке 5 объ— является переменная x типа int с начальным значением 999. В строках 11 и 21 эта перемен—  268 Неделя 2. Основные вопросы 
ная объявляется как extern int. Обратите внимание на различие между объявлением пере- менной, при котором для нее выделяется память, и декларацией extern. B последней факти- чески говорится: “Эта функция использует переменную такого-то типа и с таким-то именем, определенную где-то в другом месте”. В данной программе объявление 'extern, строго гово- ря, совсем не нужно — программа работала бы точно так же и без строк 11 и 21 . Но если бы функция print_value() находилась не в одном программном модуле (файле исходного кода) с глобальным объявлением переменной х (строка 5), то декларация extern была бы совер- шенно необходима. Если убрать объявление переменной х из строки 5, то программа все равно пройдет ком- пиляцию благополучно. А вот при ее компоновке возникнет ошибка, потому что переменная х все-таки должна быть объявлена, если не в функции, где она непосредственно используется, то хотя бы где-нибудь в программе.  Создание локальных переменных  НПВЬШШВВМНН Локальная переменная— это переменная, определенная внутри функции. O6- ласть действия такой переменной ограничена той функцией, где она объявлена.  На занятии 5 уже рассматривались локальные переменные внутри функций, их объявление и преимущества работы с ними. Компилятор сам не присваивает локальным переменным ника- кого автоматического начального значения (например, 0). Если не инициализировать локаль- ную переменную явным образом при ее объявлении, то она будет содержать непредсказуе- мое, случайное значение. Поэтому перед обращением к такой переменной всегда следует присвоить ей значение тем или иным способом. Переменные могут быть локальными и по отношению к функции main( ). Именно такой переменной была х в листинге 12.2. Переменная х была объявлена внутри функции main( ). Как иллюстрирует компиляция и выполнение программы, она была видна только в пределах этой функции.   омендуется Не рекомендуется  №…_только {йохаліёіные обьявдч» ; suggbaymeék внешними перемещает-; передн…енны как Nam" 3 ;{м ЁЁ %" тёйько may? 3my `на нужен ; №… %; №№ №№ ЁЩЁЁЁ , … “3%; (РУ“ ауте; „ереддеННы …;св функциях грамм,“ *‘“ 395%"; м;; «";—" зб цййёаннш “тра "ёс ‘ ' *           Статические и автоматические переменные  Локальные переменные по умолчанию являются автоматическими. Это означает, что при каждом вызове функции они создаются заново, а при выхоце из функции уничтожаются. На практике это значит, что автоматические переменные не хранят свои значения между вызо- вами функций, в которых они определены. Предположим, в программе есть функция, которая использует локальную переменную х. Пусть при первом вызове этой функции переменная получила значение 100. Затем управление передается главной программе, и вскоре функция вызывается снова. Содержит ли перемен- ная х по-прежнему значение 100? Нет. Первый экземпляр переменной х был уничтожен при  День 12-й. Область действия переменных 269 
выходе из функции после ее первого вызова и выполнения. При новом вызове создается но— вый экземпляр этой переменной, а старое значение х пропадает. Что если функции все—таки необходимо сохранить значение некоторой переменной между вызовами? Например, функция печати хотела бы знать, сколько сгрок уже послано на прин— тер, чтобы определить, не следуег ли начать новую страницу. Для того, чтобы локальные пе— ременные сохраняли свои значения между вызовами функций, их необходимо объявить ста— тическими с помощью ключевого слова static. Например:  void print(int х)  {  static int lineCount; /* далее идут операторы функции */  Листинг 12.4 демонстрирует различие между сгатическнми и автоматическими локаль— ными переменными.  Листинг 1 2.4. static . с — различие между статическими и автоматическими локальными переменными    1: /* демонстрация автоматических и статических локальных переменных. */ 2: #include <stdio.h> ” 3: void func1(void); 4: int main( void ) 5: { 6: int count; 7: 8: for (count = 0; count < 20; count++) 9: { 10: printf(“At‘iteration %d: “, count); 11: func1(); 12: } 13: . 14: return 0; 15: } 16: 17: void func1(void) 18: { 19: static int х = 0; 20: int y = 0; 21: 22: printf(“x = %d, y = %d\n“, x++, y++); 23: } At iteration 0: х = 0, у = 0 At iteration 1: х = 1, у = 0 At iteration 2: х = 2, у = 0 At iteration 3: х = 3, y - 0 At iteration 4: х = 4, у = 0 At iteration 5: х = 5, y = 0 At iteration 6: х = 6, у = 0 At iteration 7: х = 7, у = 0  270 Неделя 2. Основные вопросы 
At iteration 8: x = 8, у = 0 At iteration 9: x = 9, у = 0 At iteration 10: x = 10, у = 0 At iteration 11: x = 11, у = 0 At iteration 12: x = 12, у 0 At iteration 13: x = 13, у 0 At iteration 14: x = 14, у = 0 At iteration 15: x = 15, у = 0 At iteration 16: x = 16, у = 0 At iteration 17: x = 17, у = 0 At iteration 18: x = 18, у = 0 At iteration 19: x = 19, у = 0  № В этой программе имеется функция func1( ), B которой объявляется и инициали- зируется Одна статическая и одна автоматическая локальная переменная. Функ- ция содержится в строках 17—23. При каждом ее вызове обе переменные отображаются на эк- ране и инкрементируются. Функция щаіл() в строках 4—15 с0держит цикл for (строки 8—12), в котором выв0дится сообщение (строка 10) и вызывается функция func1( ) (строка 11). Цикл for повторяется 20 раз. Выводимые в цикле значения x всякий раз увеличиваются на единицу, потому что пере— менная сохраняет свое значение и между вызовами функции. С другой стороны, автоматиче- ская переменная у всякий раз при вызове переустанавливается в 0, а потому не увеличивается. Эта программа также иллюстрирует различие между способами, которыми выполняется явная инициализация статических и автоматических переменных (при их объявлении).` Ста- тическая переменная инициализируется только при первом вызове функции. Впоследствии программа помнит, что переменная уже инициализирована, и в итоге переменная все время сохраняет свое значение после последнего вызова. В то же время автоматическая переменная инициализируется заново при каждом вызове функции. Экспериментируя с автоматическими переменными, можно получить результаты, отли- чающиеся от приведенных в книге. Например, изменим листинг 12.4 так, чтобы две локаль- ные переменные не получали начальных значений при объявлении, то есть перепишем функ- цию func1( ) B строках 17—23 таким образом:  17: void func1(void)  18: { 19: static int x; 20: int у = 0; 21: 22: printf("x = %d, у = %d\n", X++: Y++); 23: }  При выполнении этой модифицированной программы обнаруживается, что значение у увеличивается на единицу при каждом вызове функции! Другими словами, переменная у со— храняет свое значение между вызовами даже при том, что она автоматическая, а не статиче— ская. Получается, все сказанное про потерю автоматическими переменными их значений—— неправда? Поверьте, все сказанное нами — сущая правда. Если вы обнаружите, что автоматическая переменная сохраняет значение между вызовами ее функции, то знайте — это простая слу- чайность. Вот что при этом происх0дит: при каждом вызове функции создается новая пере- менная у. Компилятор вполне может воспользоваться для ее размещения тем же участком памяти, что и при предыдущем вызове. Поскольку у не инициализируется в функции явным  День 12-й. Область действия переменных 271 
образом, этот участок по-прежнему содержит то же, что в нем было раньше. На первый взгляд переменная сохраняет свое значение, но это чистая случайность: всегда рассчитывать на это нельзя. А раз это происходит не всегда, то на это не нужно рассчитывать вообще никогда! Локальные переменные по умолчанию являются автоматическими, поэтому их не нужно объявлять таковыми явным образом. Если вы все же хотите сделать это по каким-то сообра- жениям, добавьте к объявлению переменной слово auto, поставив его перед ключевым сло- вом типа:  void func1(int у) {  auto int count; /* далее идет основной код функции */  Область действия параметров функции  Переменная в списке параметров, который стоит в заголовке функции, имеет ло- кальную область действия. Рассмотрим пример:  void func1(int x)  {  Новый термин  int у; /* далее идет основной код функции */  Как х, так и у являются локальными переменными, область действия которых совпадает с функцией func1( ). Конечно, x c самого начала содержит то значение, которое передается в функцию как ее аргумент. Этой переменной и ее значением можно пользоваться точно так же, как и любой другой локальной переменной. Переменные-параметры всегда имеют начальное значение, переданное как аргумент в функцию. Поэтому понятия “статический” и “автоматический” не имеют никакого смысла в приложении к ним.  Внешние статические переменные  Внешнюю переменную можно сделать статической, добавив к ее объявлению ключевое слово static:  static float rate;  int main( void )  {  /* Код программы */  Различие межщі обычной внешней переменной и статической внешней переменной co- стоит в области действия. Обычная внешняя переменная вндима из всех функций файла ис- ходного кода, а также может использоваться функциями из других файлов. Статическая внешняя переменная видна только из функций ее файла и только ниже точки объявления. Конечно, данные различия имеют значение в основном для программ, чей исходный код распределен по двум или более файлам. Эта тема будет рассмотрена на занятии 21.  272 Неделя 2. Основные вопросы 
Регистровые переменные  В языке С есть ключевое слово register. Оно используется в объявлении автоматической локальной переменной для того, чтобы попросить компилятор поместить ее не в оператив- ную память, а в регистр процессора. Что такое регистр процессора, и какие преимущества можно извлечь из его использования? Центральный процессор (central processing unit —-— CPU) компьютера содержит несколько ячеек для хранения данных, которые называются регистрами. Фактические операции над данными, такие как сложение или умножение, выполняются именно в регистрах. Для работы с данными процессор перемещает их из памяти в регистры, выполняет над ними операции, а затем перемещает их назад в память. Перемещение данных из памяти в регистры и назад за- нимасг некоторое время. Если бы какую—нибудь переменную можно было все время держать в регистре, операции с ней существенно ускорились бы. Ключевое слово register B объявлении автоматической переменной как раз и просит компилятор поместить эту переменную в регистр. Рассмотрим пример:  void funcl(void) { register int x; /* Код функции */  Обратите внимание, что это именно просьба, а не приказ. При большой загрузке процес- сора его регистров может и не хватить для всех переменных. Если регистров не хватает, то с регистровой переменной работают как с обычной автоматической переменной. Итак, ключе— вое слово register — 3T0 предложение, а не категорическое распоряжение. Преимущества регистровых переменных наиболее ярко проявляются тогда, когда эти переменные использу— ются в программе очень часто, например, как счетчики циклов. Ключевое слово register может употребляться только с простыми числовыми переменны- ми, но не с массивами или структурами. Его таюке нельзя совмещать с объявлением переменной как статической или внешней. Нельзя и определить указатель на регисгровую переменную.    Рекомендуется Не рекомендуется      уачальноеъэчнаёі №№, mam с к . ‘“ 3°  , с м      ? 4'v33:‘;:€'&§1);;_;3) _ .w‘ …?“ „$524 ЖЖЖ} 3:?“ › ,; ‚„На 4 …; f“ .!ЪЁАЦЁЗ, „ {. ';ЁЁЁ‘Ё? {‹ " А; _ ;&гіАъ/д ‚(АЧ-"‘.” ’ …% ." 3?: flaw? fififix :jgt':} $$$ „«Е» * „же:,‚ЁЖДЪіЁЁЁ, ”:\: "‘ ::  Man—«.30  Локальные переменные в функции таіп()  Все сказанное до сих пор о свойствах локальных переменных внутри функций относится в полной мере и к функции main( ). Вообще говоря, main( ) — это такая же функция, как и дру-  День 12-й. Область действия переменных 273 
гие. Единственное различие состоит в том, что она вызывается операционной системой при запуске программы, и после завершения программы управление снова передается в операци- онную систему. Это означает, что локальные переменные, определенные в функции main( ), создаются при запуске программы и существуют до самого ее завершения. В силу этого понятие стати- ческой переменной, сохраняющей свое значение между вызовами шаіп( ), не имеет смысла: переменная не может существовать в промежутках между запусками программы. Поэтому внутри шаіп() не существует различия между автоматическими и статическими локальными переменными. Можно, конечно, с полным правом объявить локальную переменную в функ- ции main( ) как статическую, но это не будет иметь никакого реального эффекта.       ТСЯ ‚ , м,…  дня }‚с.‘   Классы памяти  При выборе класса памяти, т.е. способа хранения переменной в памяти при выполнении программы, может оказаться полезной табл. 12.1, в которой сведены данные о пяти классах памяти в языке С.  Таблица 1 2.1 . Классы памяти переменных в языке С    Класс памяти Ключевое Время жизни Где объявлена Область слово действия Автоматическая Нет1 Временная В функции Локальная Статическая static Временная В функции Локальная Регистровая register Временная В функции Локальная Внешняя Нет2 Постоянная Вне функции Глобальная (все файлы) Внешняя static Постоянная Вне функции с|;3лс›_ба;льная (один аил   ' Допускается необязательное ключевое слово auto  2 Для объявления статической переменной, определенной в другом месте, используется ключевое слово extern  При выборе класса памяти, как правило, следует отдавать предпочтение автоматическим переменным, применяя остальные классы в случае необходимости. Вот еще некоторые реко- мендации по назначению классов памяти.  I Для начала сделайте все переменные автоматическими.  I Если простая переменная должна использоваться очень интенсивно, например, как счетчик цикла, сделайте ее регистровой (добавьте к ее объявлению ключевое слово register)  274 Неделя 2. Основные вопросы 
I B функциях, не являющихся шаіп( ), объявите статическими те переменные, которые должны сохранять свои значения между вызовами функций.  I Если переменную используют все или почти все функции программы, сделайте ее внешней.  Локальные переменные B блоках  До сих пор мы обсуждали только переменные, локальные по отношению к функциям. Это основной способ локализации переменных, но тем не менее можно объявить и переменную, локальную в любом блоке программы (т.е. в любом фрагменте кода, заключенном в фигур- ные скобки). Объявления переменных должны стоять в блоке первыми, раньше всех осталь— ных операторов. В листинге 12.5 приведен пример такого объявления.  Листинг 1 2.5. block. с — объявление локальных переменных в блоке   /* денонстрация локальных переменных в блоках. */  2: 3: #include <stdio.h> 4: 5: int main( void ) 6: 7: /* Объявление переменной, локальной в main(). */ 8: 9: int count = 0; 10: 11: printf("\n0utside the block, count = %d", count); 12: 13: /* Start a block. */ 14: 15: /* Объявление переменной, локальной в блоке. */ 16: 17: int count = 999: 18: printf("\nWithin the block, count = %d", count); 19: } 20: 21: printf(”\n0utside the block again, count = %d\n", count); 22: return 0; 23: }   P Outside the block, count = 0 “№№… Within the block, count = 999  Outside the block again, count = 0  Эта программа демонстрирует, что переменные с одним и тем же именем count, объявленные в блоке и вне блока, полностью независимы друг от друга. В стро- ке 9 объявляется переменная count типа int с присвоением ей начального значения 0. По- скольку она объявлена в начале функции main( ), ее можно использовать везде в этой функ- ции. В строке 11 начальное значение count выводится на экран, и таким образом демонстри-   День 12_й_ Область действия переменных 275 
руется,’ что это ноль. В строках 14—19 находится блок, а внутри блока объявлена еще одна пе- ременная count типа int. Эта переменная инициализируется значением 999 в строке 17. В строке 18 переменная count выводится на экран, и мы видим уже значение 999, а не 0. Блок заканчивается в строке 19, и оператор вывода в строке 21 вновь выводит прежнее значение count, которое переменная получила при об'ъявлении в строке 9. Эта разновидность локальных переменных встречается в практике программирования крайне редко. Возможно, вам вообще никоща не придется использовать такую форму объяв- ления. Основное применение локальных переменных в блоках состоит в том, чтобы локали- зовать смысловую ошибку в программе: отделить часть программы фигурными скобками, объявить там локальные переменные и проследить за тем, что с ними произойдет. Еще одним их преимуществом можно считать то, что объявление и инициализация переменной выпол- няются в непосредственной близости от операторов, которые ее используют. Это помогает сделать программу более удобочитаемой.     Рекомендуется Не рекомендуется  ‹ „— тажка» 4m  WWW“ "°“ № 1 gt ен “ ЗЧ     $$$ ‚ ‚ Н … аз? ящъиаед W2” -‹ д` › “ “* ^ H гудит :*…111*`°^ г › .… › „ ай I“ if? A“ %?v“‘§}p&f . „ЁЁ-"Чвщ %%(влропйа 3№35^Ёгіёёі С“: ' `5 „' r; " 'нр35`..`№ ‹ {А4 Y ’ › а _. ,   „%і 133$3‘ … „ ‹ \!. ‚_д &. ` 4…“ . `.‘. FAQ“ ` ": "‘)! t w & “In“   Резюме  На этом занятии были рассмотрены вопросы области действия и времени жизни перемен- ных, относящихся к различным классам памяти языка С. Каждая переменная программы, будь то массив, структура, простая числовая переменная и т.д., хранится и обрабатывается в соответствии с некоторым классом памяти, определяющим два аспекта: область действия, или видимости, переменной, а также время жизни, т.е. как долго переменная существует в памяти. Правильное использование классов памяти очень важно для структурного программиро- вания. По возможности отделяя переменные одной функции от другой (делая их локальны- ми), можно добиться высокой степени независимости этих функций. Предпочтительный класс памяти ——-— это автоматические переменные, если нет особых причин объявить их внеш- ними или статическими.  Вопросы и ответы  Если глобальные переменные можно использовать в любом месте программы, то почему бы не сделать все переменные глобальными? В ходе разработки программы по мере ее разрастания в ней появляется все больше и больше переменных. Глобальные переменные занимают память в течение всего времени вы- полнения программы, в то время как автоматические локальные переменные— только при выполнении соответствующих функций. Поэтому использование локальных переменных снижает затраты памяти. Но еще более важно то, что локализация переменных намного  276 Неделя 2. Основные вопросы 
уменьшает вероятность нежелательного вмешательства одних частей программы в работу других. Это позволяет избежать многих ошибок, следуя при этом хорошему стилю структур- ного программирования.  На занятии 11 при изучении структур говорилось, что экземпляр структуры имеет область действия, а метка и определение структурного типа — нет. Почему это так? При объявлении структуры без экземпляров создается только определение-шаблон струк- турного типа. Никакие переменные при этом не создаются. Только когда объявляется экземп- ляр структуры, в памяти появляется структурный объект— переменная, которая имеет об- ласть действия. Поэтому определение структурного типа можно разместить в тексте вне всех функций, и на распределение памяти это никак не повлияет. Многие программисты помеща- ют определения часто используемых структурных типов с их метками в заголовочный файл, а затем включают в программу такой файл, если необходимо создать экземпляр структуры. (Заголовочные файлы подробно обсуждаются на занятии 21.)  Откуда компьютеру известно, что две переменные с одинаковыми именами— на самом деле одна локальная и одна глобальная переменная? Ответ на этот вопрос несколько выходит за рамки нашей книги. Важно знать, что про- грамма временно игнорирует глобальную переменную, находясь в области действия локаль- ной переменной с тем же именем. Когда же программа выходит из этой области действия, она снова “вспоминает” о существовании глобальной переменной.  Можно ли объявить глобальную и локальную переменную с одним и тем же именем, но разных типов? Да. При объявлении локальной переменной с тем же именем, что и у глобальной, создает- ся совершенно новая переменная. Это значит, что она может иметь любой желаемый тип. Тем не менее стоит проявлять осторожность при объявлении глобальных и локальных пере- менных с одинаковыми именами. Некоторые программисты добавляют ко всем глобальным именам префикс “g” (например, gCount вместо Count). Благодаря этому становится легче проследить, какая переменная является глобальной, а какая — локальной.  Коллоквиум  В этом коллоквиуме предлагаются контрольные вопросы для закрепления пройденного материала, а также упражнения для приобретения практических навыков программирования. Ответы на вопросы и упражнения можно найти в приложении Е.  Контрольные вопросы  1. Что такое область действия переменных?  2. В чем состоит самое фундаментальное различие между классами памяти локальных и внешних переменных?  3. Как местонахождение объявления переменной влияет на cc класс памяти? 4. Какое время жизни может иметь локальная переменная в зависимости от cc объявления?  5. Как автоматические, так и статические переменные можно инициализировать при объяв- лении. Когда именно выполняется их инициализация в каждом из двух случаев?  6. Всегда ли регистровая переменная располагается в регистре процессора?  7. Какое значение получает глобальная переменная, не инициализированная явным образом?  День 12-й, Область действия переменных 277 
8. 9.  Какое значение получает локальная переменная, не инициализированная явным образом?  Что выведет на экран программа из лисгинга 12.5, если убрать из нее строки 9 и 1]? Сна- чала подумайте сами, а затем проведите эксперимент.  10. Если локальную переменную типа int, объявленную внутри некоторой функции, необхо-  димо хранить в памяти между вызовами этой функции, то как ее следует объявить?  11. Каково назначение ключевого слова extern?  12. Каково назначение ключевого слова static?  Упражнения  7.  Напишите объявление переменной, которую желательно хранить в регисгре процессора.  Исправьте листинг 12.2 так, чтобы программа работала без ошибок. При этом не исполь- зуйте внешних переменных.  Напишите программу с объявлением глобальной переменной var типа int. Инициализи- руйте var каким-нибудь значением. Программа должна вывести это значение на экран с помощью функции (вне пределов main( )). Необходимо ли передавать значение var B функцию как параметр? Измените программу из упражнения 3. Вместо объявления var глобальной переменной сделайте ее локальной в функции main( ). Пусть программа по-прежнему выводит значе- ние var с помощью отдельной функции. Необходимо ли теперь передавать значение var B функцию как параметр? Могут ли в программе использоваться глобальная и локальная переменные с одинаковы— ми именами? Напишите программу с объявлением как глобальной, так и локальной пере- менной под совпадающими именами, чтобы проверить ваш ответ. Поиск ошибок. Можете ли вы найти ошибку в коде этой функции? Подсказка: ошибка связана с местонахождением объявления переменной. void a_samp1e_function( void )  {  int ctrl;  for ( ctrl = 0; ctrl < 25; ctrl++ ) printf( "*“ );  puts( “\nThis is a sample function" );  {  char star = '*'; puts( "\nIt has a problem \n" ); for ( int ctr2 = 0; ctr2 < 25 ; ctr2++ ) {  printf( "%c", star );  }  Поиск ошибок. Есть ли ошибки в следующем коде?  278 Неделя 2. Основные вопросы 
8.  /* Подсчет количества четных чисел от 0 до 100.*/ #include <stdio.h>  int main( void )  { int x = 1; static int tally = 0; for (x = 0; x < 101; x++) { if (x % 2 == 0) /* если x - четное число... */ ta11y++; /* увеличить счетчик на 1.*/ } printf( “There are %d even numbers.\n“, tally); return 0; }  Поиск ошибок. Найдите ошибки в следующей программе: #include <stdio.h>  void print_function( char star ); int ctr;  int main( void )  { char star; print_function( star ); return 0; } void print_function( char star ) { char dash; for (ctr = 0; ctr < 25; ctr++) { printf( “%c%c“, star, dash ); } }  Что выволит на экран следующая программа? Не запускайте программу на выполнение ——  постарайтесь дать ответ, читая ее текст. #include <stdio.h>  void print_1etter2(void); /* прототип функции */  int ctr; char letterl char 1etter2  II II II ><  `.  День 12-й. Область действия переменных  279 
int main( void )  { for( ctr = 0; ctr < 10; ctr++ ) { printf( "%c", letterl ); print_letter2(); } return 0; } void print_letter2(void) { for( ctr = 0; ctr < 2; ctr++) printf( "%c", letter2 ): }  10. Поиск ошибок. Будет ли работать предыдущая программа? Если нет, то в чем состоит проблема? Внесите необходимые исправления.  280 Неделя 2. Основные вопросы 
Самостоятельная работа 4  Секретные сообщения  В этой, четвертой по порядку, самостоятельной работе вам предлагается несколько более сложная и практически полезная программа, чем примеры программ обычных занятий. Мно- гие ее элементы вам хорошо знакомы, а вот некоторые будут изучены только на последую- щих занятиях. К числу таковых относится работа с дисковыми файлами, которые рассматри- ваются на занятии 16. Далее вы познакомитесь с программой, которая позволяет зашифровывать и расшифро- вывать секретные сообщения. Для запуска этой программы наберите в командной строке сле- дующее: coder имя_файла операция  Здесь имя_файла — это либо имя файла, который следует создать для сохранения нового секретного сообщения, либо имя существующего файла с сообщением, которое следует рас- шифровать. Вместо параметра операция подставьте D для расшифровки или С для зашифров- ки секретного сообщения. Если запустить программу без параметров, то на экране появится инструкция, как ввести правильные аргументы. Поскольку программа предназначена для шифровки сообщений, вы можете передать ее копию друзьям или сотрудникам. Затем можно будет зашифровывать сообщения и пересы- лать им, чтобы только они смогли расшифровать и прочитать их, пользуясь той же програм— мой. и те, у кого нет такой программы, не смогут прочитать тайное послание в файле сооб— щения!  Листинг C4. Coder. с    1: /* Программа: Coder.c 2: * Запуск: Coder [иия_файла] [операция] 3: * где иия_файла = имя файла шифрованных данных 4: * где операция = D для расшифровки, любой символ для 5: * зашифровки б: * — */ 7: 8: #include <stdio.h> 9: #include <std1ib.h> 10: #include <string.h> 11: 12: int encode_character( int ch, int val ); 
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:  282  int decode_character( int ch, int val );  int main( int argc, char *argv[])  {  FILE *fh; /* указатель на файл */ int rv = 1; /* возвращаемое значение */ int ch = 0; /* переменная для хранения символов */ unsigned int ctr = 0; /* счетчик */ int val = 5; /* ключ для шифровки */ char buffer[257]; /* строковый буфер */  if( argc != 3 ) { printf("\nError: Wrong number of parameters..." ); printf("\n\nUsage:\n %s filename action", argv[0]); printf("\n\n Where:"); printf("\n filename = name of file to code or decode");  printf("\n action — D for decode or C for encode\n\n”); rv = —1; /* возвращаемый код ошибки */ } else  if(( argv[2][0] == 'D') || (argv [2][0] == 'd' )) { /* Это блок расшифровки. */  fh = fopen(argv[1], "r"); /* открытие файла */ if( fh <= 0 ) /* проверка правильности */ { printf( "\n\nError opening file..." ); rv = —2; /* возвращаемый код ошибки */ } else { ch = getc( fh ); /* чтение символа */ while( £feof( fh ) ) /* проверка, не конец ли файла */ { ch = decode_character( ch, val ); putchar(ch); /* вывод символа на экран */ ch = getc( fh); } fclose(fh); printf( "\n\nFile decoded to screen.\n" ); } } else /* подразумевается шифрование в файл. */ { fh = fopen(argv[1], "и"); if( fh <= 0 ) {  printf( "\n\nError creating file..." );  Неделя 2. Основные вопросы 
63: rv = —3; /* возвращаемый код ошибки */ 64: } 65: else 66: { 67: printf("\n\nEnter text to be coded. "); 68: printf("Enter a blank line to end.\n\n"); 69: 70: while( gets(buffer) != NULL ) 71: { 72: if( buffer[0] == 0 ) 73: break; 74: 75: for( ctr = О; ctr < strlen(buffer); ctr++ ) 76: { 77: ch 78: ch 79: } 80: } 81: printf( "\n\nFile encoded to file.\n" ); 82: fclose(fh); 83: } 84: 85: } 86: return (rv); 87: } 88: 89: int encode_character( int ch, int val ) 90: { 91: ch = ch + val; 92: return (ch); 93: } 94: 95: int decode_character( int ch, int val ) 96: { 97: ch = ch — val; 98: return (ch); 99: }  encode_character( buffer[ctr], val ); fputc(ch, fh); /* запись символа в файл */   Вот пример секретного сообщения: Ymnx%nx%f%xjhwjy%rjxxflj&  После расшифровки оно гласит: This is a secret message! Данная программа зашифровывает и расшифровывает данные, просто прибавляя некото рое значение к каждому символу (и, соответственно, вычитая его). Взломать такой шифр до  вольно легко. Можно сделать его более трудным для взлома, заменив строки 91 и 97 следую шими операторами:  ch = ch ^ val;  Самостоятельная pa бота 4. Секретные сообщения 28: 
Пока что вам достаточно знать, что знак ^ обозначает двухместную операцию, которая модифицирует символ на уровне его отдельных битов. От этого ваши секретные сообщения станут еще более секретными. Если вы хотите дать копию программы нескольким людям, то желательно добавить в ко- мандную строку третий аргумент для значения переменной val. Как мы уже видели, это зна-  чение используется для зашифровки и расшифровки сообщений.  284 Неделя 2. Основные вопросы 
  Дополнительные средства управления программой  На занятии 6, посвященном циклам, уже было изучено несколько операторов, предназначен- ных для управления порядком выполнения программы. На предстоящем занятии будут рассмот- рены дополнительные средства управления программой, в том числе оператор goto и некоторые интересные возможности циклов. Итак, предлагаются следующие темы для изучения.  I Операторы break и continue Что такое бесконечные циклы и зачем они нужны Оператор goto и почему его следует избегать Оператор switch  Управление завершением программы  Выполнение системных команд внутри программы  Прерывание циклов  На занятии 6 вы узнали, как управлять выполнением операторов и блоков программы с помощью циклов for, while и do. . .while. B зависимости от поставленных условий блоки операторов С выполняются в этих циклах один или несколько раз, или вообще ни разу. Во всех случаях прекращение работы цикла, т.е. выход из него, происходит только при выполне- нии некоторого условия. Однако иногда возникает потребность в более гибком управлении циклами. Для этого в языке С существуют операторы break и continue.  Оператор break  Оператор break может находиться только в теле цикла for, while или do. . .while. Строго говоря, его также можно использовать и в операторе switch, но это мы обсудим чуть позже. Как только в теле цикла встречается оператор break, выполнение цикла немедленно прекра- щается. Вот пример: 
for ( count = 03 count < 10; count++ )  { if ( count == 5 ) break;  Предоставленный сам себе, цикл for выполнился бы десять раз. Но в данном примере на шестом прохоле цикла счетчик count становится равным 5, и тут Же выполняется оператор break, прекращая работу цикла. Управление передается оператору, который непосредственно следует после закрывающей скобки цикла. Если оператор break находится внутри вложенно- го цикла, то прекращает работу только самый внутренний из вложенных циклов. В листинге 13.1 демонстрируется использование оператора break.  Листинг 1 з. 1 . breaking . c —- использование оператора break   /* демонстрация оператора break. */ #include <stdio.h> char s[] = ”This is a test string. It contains two sentences.”; int main( void )  { \  int count;  CDNONU'I-wai—I .. о. о. .. .. .. .. о.  ", ..  10: 11: printf("\n0riginal string: %s", s); 12: 13: for (count = 0; s[count]!='\0’; count++) 14: { 15: if (s[count] == ’.’) 16: { 17: s[count+1] = ’\0’; 18: break; 19: } 20: } 21: printf("\nModified string: %s\n“, s); 22: 23: return 0; 24: }   9% bma Original string: This is a test string. It contains two sentences. И“ … Modified string: This is a test string.  № Эта программа извлекает первое предложение из текстовой строки. Строка пе- ребирается символ за символом, пока не встретится точка (которая и считается  концом предложения). Это делается в цикле for, в строках 13-20. B строке 13 находится за- головок цикла со счетчиком count для перебора символов строки s. Оператор в строке 15 проверяет, не является ли текущий символ строки точкой. Если да, то сразу после точки вставляется нулевой символ (строка 17). Он завершает строку. После ее завершения цикл больше продолжать не нужно, поэтому в строке 18 находится оператор break, который пре-  286 Неделя 2. Основные вопросы 
рывает работу цикла и передает управление первому оператору после цикла (т.е. в строку 21). Если точка не найдена, строка остается неизменной. Цикл может содержать и несколько операторов break, но выполняется только первый встретившийся из них (может быть, вообще ни один). Если ни один оператор break не вы- полняется, Цикл заканчивается естественным образом в соответствии со своим условием. Рис. 13.1 иллюстрирует схему работы оператора break.  continue:  Ые'ак;  ------d  Puc. 13.1. Схема работы one- раторов break u continue  Оператор break break;  Оператор break используется для прерывания работы цикла или оператора switch. Встретив оператор break, программа немедленно завершает выполнение текущего цикла (for, while, do. . .while) или оператора switch. Оставшиеся проходы цикла  не выполняются, и управление передается первому оператору после цикла или switch.   Пример  int x; printf ( ”Counting from 1 to 10\n" ); /* отсутствие условия в цикле заставило бы его */ /* выполняться бесконечно, если бы не break. */  for( x = 1; ; x++ )  { if( x == 10 ) /* Сравнение c 10 */ break; /* Выход из цикла */ printf( ”\n%d", x ); }  Оператор continue  Как и break, оператор continue может находиться только в теле цикла for, while или do. . .while. Выполнение оператора continue приводит к тому, что немедленно начинается следующая итерация цикла. Операторы, стоящие в теле Цикла между continue и закрываю- Щей скобкой блока, игнорируются. Работа оператора continue также показана на рис. 13.1. Обратите внимание на его отличие от оператора break.  День 13-й. Дополнительные средства управления программой 287 
В листинге 13.2 приведен пример использования оператора continue. Эта программа  принимает введенную с клавиатуры строку и выводит ее на экран после удаления всех глас— ных нижнего регистра.  Листинг 1 3.2. cont. с — использование оператора continue   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:  /* Пример оператора continue. */ #include <stdio.h>  int main( void )  {  /* Объявление буфера для ввода и переменной-счетчика. */  char buffer[81]; int ctr;  /* Ввод строки текста. */  puts(“Enter a line of text:"); gets(buffer);  /* Перебор строки с выводом только тех символов, которые */ /* не являются гласными в нижнем регистре. */  for (ctr = 0; buffer[ctr] !='\0'; ctr++) {  /* Если символ — гласная в нижнем регистре, пропустить */ /* без вывода на экран. */  if (buffer[ctr] == 'a' || buffer[ctr] == 'е' ‘ buffer[ctr] -= 'i' || buffer[ctr] == '0'  buffer[ctr] = 'u') /* Если не гласная, вывести на экран. */   continue;  putchar(buffer[ctr]); }  return 0;  }  Enter a line of text: Результат This is a line of text  Ths s In f txt  Хотя никакой практической пользы от этой программы нет, оператор continue  используется в ней достаточно эффективно. В строках 9 и 10 объявляются пере-  менные программы. Массив buffer[ ] содержит строку символов, получаемую от пользовате-  288 Неделя 2. Основные вопросы 
ля в строке 15. Переменная-счетчик ctr служит для перебора элементов массива buffer[] B цикле for (строки 20—34) с целью поиска гласных букв. Оператор if B строках 26—28 прове- ряет каждый символ, не является ли он гласной буквой нижнего регистра. Если это так, то выполняется оператор continue, который передает управление в строку 20, в заголовок цикла for. Если буква не является гласной, выполняется строка 33. В ней мы встречаем ранее не использовавшуюся библиотечную функцию putchar( ), которая отображает на экране один символ.  „незна ‘ - ЁЁ;= ‚ Оператор contlnue „Защ {ЁЁ ‚ contlnue; ; ъ- _ Ё Оператор contlnue используется внутри цикла. При его выполнении операторы, ос- о ТаВШИССЯ ДО конца цикла, игнорируются, И управление передается сразу В начало  цикла, начиная его следующую итерацию.  Пример  int x; printf("Printing only the even numbers from 1 to 10 \n“); for( x = 1; x <= 10; X++ )  { if( x % 2 != 0 ) /* Проверка нечетности числа */ continue; /* Переход к следующему числу */ printf( "\n%d", x ); }  Оператор goto  Оператор goto является оператором безусловного перехода. Он принадлежит к числу one- раторов ветвления. Как только в программе встречается оператор goto, cpa3y же выполняет— ся переход, или прыжок, к месту программы, обозначенному в этом операторе. Переход на- зывается безусловным, потому что не зависит ни от каких условий (в отличие от if) и всегда выполняется при достижении оператора goto. Место, куда выполняется переход, обозначается текстовой меткой и двоеточием в начале строки программы. Метка может стоять как в отдельной строке, так и в начале строки, со- держащей какой—либо оператор С. Все метки должны быть уникальны и не могут повторяться. Оператор goto и метка должны находиться в одной функции, но не обязаны стоять в од— ном блоке. В листинге 13.3 приведена простая программа с использованием оператора goto.  Листинг 1 3.3. gotoit .с — пример оператора goto   /* Демонстрация оператора goto */  #include <stdio.h>  int main( void )  {  int n;  CDxlO‘U'lob-WNl—l  День 13-й. Дополнительные средства управления программой 289 
9: start:  10: 11: puts("Enter a number between 0 and 10: "); 12: scanf("%d", &n); 13: 14: if (n < o [In > 10 ) 15: goto start; 16: else if (n == 0) 17: goto locationO; 18: else if (n == 1) 19: goto locationl: 20: else 21: goto location2; 22: 23: locationO: 24: puts("You entered 0.\n"){ 25: goto end; 26: 27: locationl: 28: puts("You entered 1.\n"); 29: goto end; 30: 31: location2: 32: puts("You entered something between 2 and 10.\n"); 33: 34: end: 35: return 0; 36: }   98311111113311] finter a number between 0 and 10:  You entered 1.  РВЗЦЛЬШЗШ Enter a number between 0 and 10:  You entered something between 2 and 10.  Эта простая программа получает от пользователя число от 0 до 10. Если введен- ное число He принадлежит этому диапазону, оператор goto B строке 15 выполня- ет переход на метку start B егроке 9. B противном случае программа сравнивает число с ну- лем в строке 16. Если число равно нулю, оператор goto B строке 17 выполняет переход на метку locationO (строка 23). В результате выполняется вывод сообщения в строке 24 и еще Один переход. Оператор goto B строке 25 выполняет переход на метку end B конце програм— мы. То же самое пр0делывается со значением 1 и диапазоном от 2 до 10. Метка для перех0да может находиться в программе как до, так и после оператора goto. Единственное ограничение, как уже говорилось, состоит в том, что оператор goto и соответ- ствующая метка должны нахолиться в Одной функции. Правда, они могут входить в состав разных блоков. Оператор goto можно использовать для передачи управления как B циклы (например, for), так и из них. Но делать этого не стоит. Более того, настоятельно рекоменду- ем вообше никогда не использовать оператор goto B программах. На то есть две причины.  Ф  290 Неделя 2. Основные вопросы 
I B операторе goto нет никакой реальной необходимости. Абсолютно любую задачу программирования на С всегда можно решить с помощью других управляющих операторов.  I Оператор goto опасен. В некоторых задачах он может показаться идеальным решени- ем, но на самом деле с его помощью легко наделать случайных ошибок. При передаче управления через goto выполнение программы непредсказуемо перескакивает взад- вперед, и получается хаотический, запутанный код, плохо согласующийся с принци- пами структурного программирования.  Далеко не все программисты способны написать безупречно правильную программу с применением goto. Впрочем, бывают ситуации, в которых разумное использование оператора goto является самым простым решением поставленной задачи. Однако это решение всегда, по меньшей мере, не единственное. Если вы не собираетесь прислушиваться к нашим сове- там, то, по крайней мере, будьте осторожны!  Рекомендуется Не рекомендуется  Избегайте использовать оператор goto ° `Не путайте операторы break и continue где только возможно (а это всегда воз- E Оператор break завершает выполнение  можно!) . „ ,; :, „ :3 „‹ „ „„ :, Ё ; цикла, а continue начинает его следую- , , a „ It * >;, , :3 2 ёщуюитерацию  , \ vulmmpp. La «and- . ‚см Мак ‚\ и №..…. мы ж и ч…... у.м.-. „м r „мм „«…в › „кидман… _. не…“ .и ;-       Оператор goto goto метка;  Оператор goto выполняет безусловный переход в место программы, которое отме- чает заданная метка. Метка состоит из идентификатора и двоеточия перед произ- вольным оператором (наличие которого не обязательно):  метка: опера тор;   Метка может находиться в отдельной строке. В этом случае некоторые программи- сты ставят после нее пустой оператор (точку с запятой), хотя это и не обязательно:  метка: ;  Бесконечные циклы  Что такое бесконечный цикл, и зачем он может понадобиться? Бесконечный цикл -— это такой цикл, выполнение которого никогда не закончится естественным образом по его усло- вию. Бесконечный цикл может принадлежать к любому из известных нам видов: for, while, do. . .while. Например, в следующем фрагменте кода организуется бесконечный цикл while: while (1)  /* операторы тела цикла */  Проверяемое в цикле условие —— это просто константа 1, которая всегда соответствует логическому значению ИСТИНА. Программа никак не сможет сделать ее равной 0, т.е. ПОЖЬ. А поскольку единица никогда не изменится, то и цикл никогда не закончится.  День 13-й. Дополнительные средства управления программой 291 
В предыдущем разделе мы принудительно заканчивали выполнение циклов с помощью оператора break. Без этого оператора от бесконечных циклов не было бы никакой пользы. А вот с его применением такие циклы могут сослужить хорошую службу. С тем же успехом можно организовать бесконечный цикл таким образом:  for (:;  {  /* операторы */  Вполне возможен и следующий бесконечный цикл: do { /* операторы */ } while(1);  Bce три вида циклов основаны на одном и том же принципе, поэтому в дальнейших при- мерах ограничимся циклом while. Бесконечный цикл/можно использовать там, где необходимо проверять много условий за- вершения цикла. Нередко бывает трудно поместить все эти проверки в заголовок while. То- гда можно проверять условия по отдельности в теле цикла и выходить из него при необходи- мости с помощью оператора break. Рекомендуем не злоупотреблять бесконечными циклами, если есть разумная альтернатива. С помощью бесконечного цикла можно организовать систему меню, которая будет на- правлять работу программы в то или иное русло. На занятии 6 вы узнали, что функция main( ) часто служит лишь “регулировшиком”, который распределяет поток заданий между другими функциями. Это достигается с помощью системы меню: пользователю предлагается список вариантов, из которых он выбирает желаемый. Одним из этих вариантов заданий должен быть выход из программы. Как только пользователь сделал выбор, выполнение программы следует направить соответствующим образом с помощью операторов ветвления. В листинге 13.4 демонстрируется использование системы меню.  Листинг 1 3.4. menu.c — реализация системы меню с помощью бесконечного цикла   1: /* демонстрация системы меню, организованной с помощью */  2: /* бесконечного цикла. */ 3: #include <stdio.h> 4: #define DELAY 15000000 /* Используется в цикле задержки. */ 5: 6: int menu(void); 7: void delay(void); 8: 9: int main( void ) 10: { 11: int choice; 12: 13: while (1) 14: { 15: 16: /* Пользователь делает выбор. */ 17:  292 Неделя 2. Основные вопросы 
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:  }  choice = menu();  /* Разветвление по выбору пользователя. */  if (choice == 1) { puts("\nExecuting task А."); delay(); } else if (choice == 2) { PUtS("\nExecuting task В."); delay(); else if (choice == 3)  puts("\nExecuting task С."); delay();  }  else if (choice == 4)  Puts("\nExecuting task В.");  delaY(): else if (choic == 5) /* Выход из программы. { puts("\nExiting program now...\n"); delay(); break; } else puts("\nInvalid choice, try again."); delay(); } } return О;  /* Отображение меню, ввод выбора пользователя. */ int menu(void)  {  int reply;  puts("\nEnter 1 for task А."); puts("Enter 2 for task B."); puts("Enter 3 for task C."); puts("Enter 4 for task D."); puts("Enter 5 to exit program.");  День 13-й. Дополнительные средства управления программой  */  293 
68: scanf("%d", &reply); 69: 70: return reply; 71: } 72: 73: void delay( void ) 74: { 75: long x; 76: for ( х = 0; x < DELAY; x++ ) 77: ; 78: }  Enter 1 for task A. Enter 2 for task B. Enter 3 for task C. Enter 4 for task D. Enter 5 to exit program.  1   Executing task A.  Enter 1 for task A. Enter 2 for task B. Enter 3 for task C. Enter 4 for task D. Enter 5 to exit program. 6  Invalid choice, try again.  Enter 1 for task A. Enter 2 for task B. Enter 3 for task C. Enter 4 for task D. Enter 5 to exit program. 5  Exiting program now...   __ Листинг13.4 не содержит контроля ошибок ввода. Если пользователь “Will" введет не число, а что-либо другое, результат может оказаться непред— сказуемым.     В строке 18 листинга 13.4 вызывается функция menu( ), определенная в строках 58—71. Эта функция выволит на экран меню, получает от пользователя его выбор и возвращает введен- ное число в главную программу. B функции main() имеется несколько вложенных операто- ров if, которые анализируют введенное значение и соответственно направляют выполнение  294 Неделя 2. Основные вопросы 
программы. Единственное, что программа делает в ответ на запрос пользователя, —- это вы- водит сообщение на экран. В реальной программе для выполнения выбранной задачи вызы- вались бы соответствующие функции. В программе используется еще одна функция с именем delay( ). Эта функция определена в строках 73—78. Работы у нее немного. Цикл for (строка 76) прокручивается delay pa3, ниче- го не делая (пустой оператор в строке 77). Это эффективный способ на время приостановить выполнение программы. Если пауза окажется слишком длинной или короткой, значение delay можно изменить в соответствующую сторону. В комплект многих компиляторов, например, производства Borland или Symantec, входит функция под названием 81еер( ). Эта функция приостанавливает выполнение программы на количество секуНД, передаваемое в нее как аргумент. Для использования этой функции необходимо подключить заголовочный файл time.h при работе с компилятором Зутатес, или файл dos.h, если используется компилятор Borland. Если у вас есть один из этих компи- ляторов или любой другой, поддерживающий функцию 81еер( ), то вы можете заменить ею функцию с1е1ау( ) в приведенном примере.   В листинге 13.4 использован—далеко не наилучший способ приостановки @і"! программы. Если же вы решили воспользоваться функцией Sleep( ), о ко- торой мы только что говорили, будьте осторожны. Эта функция не опре- делена в стандарте ANSI, поэтому работает не во всех системах и про— граммных средах.     Оператор switch  Наиболее гибким управляющим оператором языка С можно считать оператор switch. C его помощью можно выполнить различные блоки программы в зависимости от значений не- которого выражения, причем возможных значений может быть больше двух. Управляющий оператор if, рассмотренный ранее, пригоден для анализа только выражений, принимающих два возможных значения: ПОЖЬ или ИСТИНА. Если же программа должна была разветвляться на большее количество потоков в зависимости от нескольких значений, нам приходилось ис- пользовать вложенные операторы if, как показано в листинге 13.4. Оператор switch избав- ляет нас от необходимости работать с вложенными операторами if. Синтаксис оператора switch таков:  switch (выражение)  { case шаблон_1: оператор( Ы) ; case шаблон_2: оператор(н); case шаблон_п: оператор(ы); default: оператор(ы); }  Здесь выражение должно принимать целочисленное значение типа long, int или char. Оператор switch вычисляет значение выражения и сравнивает значение с шаблонами- константами, заданными после каждого ключевого слова case. Затем выполняется одна из следующих операций.  День 13-й. Дополнительные средства управления программой 295 
I Если значение выражения совпало с одним из заданных шаблонов, то выполняются операторы, стоящие после соответствующей метки case.  I Если значение не совпало ни с одним из шаблонов, выполняются операторы после ключевого слова default. Наличие этого слова и операторов после него в операторе switch He обязательно.  I Если значение не совпало ни с одним шаблонным значением и при этом в switch OT- сутствует блок default, управление передается следующему оператору после закры- вающей скобки блока switch.  Работа оператора switch демонстрируется в листинге 13.5. Эта программа выводит сооб- щение в зависимости от введенного пользователем числа.  Листинг 1 3.5. switch . с — использование оператора switch   1: /* Демонстрация использоваиия оператора switch. */ 2: 3: #include <stdio.h> 4: 5: int main( void ) 6: { 7: int reply; 8: 9: puts("Enter a number between 1 and Б:"); 10: scanf("%d", &reply); 11: 12: switch (reply) 13: { 14: case 1: 15: puts("You entered 1."); 16: case 2: 17: puts("You entered 2."); ’ 18: case 3: 19: puts("You entered З."): 20: case 4: 21: puts("You entered 4."); 22: case 5: 23: puts("You entered Б."): 24: default: 25: puts("0ut of range, try again."); 26: } 27: 28: return О: 29: }   Enter a number between 1 and 5: 2 You entered 2. You entered 3. You entered 4. You entered 5. Out of range, try again.   296 Неделя 2. Основные вопросы 
Определенно, здесь что-то не в порядке. Похоже, оператор switch нашел соот- ветствие в своем списке констант и выполнил не только идущий следом опера- тор, но и все операторы после него (независимо от совпадения значений). Но именно так и должен работать этот оператор согласно семантике языка С. По сути, он выполняет переход goto no метке,найдяілаблон,совпадакпций с проверяемьштзначениемь`Чтобы выполнить только операторы после соответствующей метки, поставьте в нужном месте оператор break. B листинге 13.6 приведена исправленная программа с добавлением оператора break. Теперь она работает правильно.   Листинг 1 3.6. switch2 . с — правильная работа switch с операторами break   /* Правильное использование оператора switch. */ #include <stdio.h> int main( void )  { int reply;  mummwar—I II II II .. II II II II  9: puts("\nEnter a number between 1 and 5:"); 10: scanf("%d", &rep1y); 11: 12: switch (reply) 13: { 14: case 0: 15: break; 16: case 1: 17: { 18: puts(“You entered 1.\n“); 19: break; 20: } 21: case 2: 22: { 23: puts("You entered 2.\n"); 24: break; 25: } 26: case 3: 27: { 28: puts(“You entered 3.\n“); 29: break; 30: } 31: case 4: 32: { 33: puts("You entered 4.\n"); 34: break; 35: } 36: case 5: 37: { 38: puts("You entered 5.\n“); 39: break; 40: }  День 13-й. Дополнительные средства управления программой 291 
41: 42: 43: 44: 45: 46: 47:  default:  { puts("0ut of range, try again.\n"); } } /* конец switch */ return 0;  }  ’ Enter a number between 1 and 5: Резвпыпаш 1  You entered 1. Enter a number between 1 and 5: 6 Out of range, try again.  Скомпилируйте и запустите эту версию программы, чтобы убедиться в правильности ее  работьъ  Одним из популярных применений оператора switch является организация меню такого  типа, как в листинге 13.4. В листинге 13.7 для реализации меню используется не набор вло- женных операторов if, a оператор switch.  Листинг 1 3.7. menu2 .c -— использование оператора switch для организации системы меню   фЧФШЬШЮН 00 00 со с. 00 00 00 со  HHH Nl—‘OKO 00 00 00 00  13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:  298  /* Использование бесконечного цикла и оператора switch */ /* для реализации системы меню. */ #include <stdio.h> #include <std1ib.h>  #define DELAY 150000  int menu(void); void delay(void);  int main( void )  { int command = 0; command = menu();  while (command != 5 )  { /* Ввод выбора пользователя, разветвление по выбору. */ switch(command) { case 1: { puts("\nExecuting task А."); delaY(>: break;  Неделя 2. Основные вопросы 
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:  День 13-й. Дополнительные средства управления программой  }  саве 2:  {  PUtS("\nExecuting task В.")-  сіеідУН: break;  }  case 3:  {  PUtS("\nExecuting task С.")-  СіеідУП: break;  }  case 4:  {  puts("\nExecuting task 0.")-  delay(); break;  }  саве 5:  {  puts("\nExiting program now  } default: {  puts("\nInvalid choice, try  } } /* Конец switch */ command = menu(); } /* Конец while */ return 0;  }  /* Вывод меню и ввоц выбора пользователя. */ int menu(void)  { int reply;  puts("\nEnter 1 for task A."); puts("Enter 2 for task В."); puts("Enter 3 for task С."); puts("Enter 4 for task Б."); puts("Enter 5 to exit program.");  scanf("%d", &rep1y);  return reply;  }  void delay( void )  /* Выход из программы.  I  I  I  */  ...\n");  again.");  299 
77: {  78: long x; 79: for( x = 0; x < DELAY; x++ ) 80: ; 81: }   ` 1>_ Enter 1 for task A. Enter 2 for task B. Enter 3 for task C. Enter 4 for task D. Enter 5 to exit program.  1  Executing task A.  Enter 1 for task A. Enter 2 for task B. Enter 3 for task C. Enter 4 for task D. Enter 5 to exit program. 6  Invalid choice, try again.  Enter 1 for task A. Enter 2 for task B. Enter 3 for task C. Enter 4 for task D. Enter 5 to exit program. 5  Exiting program now...  B этой программе оператор switch выполняет разветвление в зависимости от введенного пользователем номера пункта меню. Предварительно меню отобра- жается на экране. В строке 14 меню выводится в первый раз. Пользователь вводит свой вы- бор. Если введенное число не равно 5, то выполняется цикл while. Этот цикл в основном со- стоит из оператора switch, который выполняет различные операции, соответствующие пунк- там меню. После выполнения одного из пунктов в строке 55 меню вновь выводится на экран, и пользователь опять делает свой выбор. Иногда бывает удобно, чтобы в конструкции switch некоторые блоки выполнялись “насквозь”, минуя ключевые слова case. Пусть, например, некоторый блок операторов дол- жен выполняться, если проверяемое выражение принимает любое из нескольких значений. Для этого достаточно убрать операторы break и поставить все метки сазе перед этим бло- ком. Если выражение принимает одно из перечисленных значений, то все следующие метки case пропускаются, и выполняется сам блок кода. В листинге 13.8 приведен пример.  300 Неделя 2. Основные вопросы 
Листинг 1 3.8. fallthru. с — вариант использования оператора switch   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:   1 2 3 4: 5: 6. 7 8  /* Вариант использования оператора switch. */  #include <stdio.h> #include <stdlib.h>  int main( void )  { int reply;  while (1) {  puts("\nEnter a value between 1 and 10, 0 to exit:  scanf("%d", &reply);  switch (reply) { case О: exit(0); case 1: case case case case { puts("You entered 5 or below.\n"); break; } case case case case case { puts("You entered 6 or higher.\n"); break;  L11 6C) LA) ?\J .. со .. ..  *** ") (I) "д CTN со .. со ..  0:  } default: puts("Between 1 and 10, please£\n"); } /* end of switch */ } /*end of while */ return 0;  Enter a value between 1 and 10, 0 to exit: 11 Between 1 and 10, please!  Enter a value between 1 and 10, 0 to exit:  День 13-й. Дополнительные средства управления программой  "):  301 
1 You entered 5 or less.  Enter a value between 1 and 10, 0 to exit: б You entered 6 or more.  Enter a value between 1 and 10, 0 to exit: 0  Анализ  Эта программа вв0дит с клавиатуры число и анализирует его значение: больше  или равно 6, меньше или равно 5, вне диапазона 1—10. Если число равно 0, в строке 18 вызывается функция exit( ), и выполнение программы заканчивается. В строке 18 нельзя ограничиться одним оператором break, как мы это делали в листин- ге 13.4. Выполнение оператора break завершило бы только оператор switch, но не бесконеч- ный цикл while. A функция exit() выполняет выход из программы. В следующем разделе  поговорим о ней подробнее.  Оператор switch  switch (выражение)   { case шаблон_1: оператор(ы); case шаблон_2: оператор(ы); сазе шаблон_п: оператор(ы); default: оператор(ы); }  Оператор switch разветвляет выполнение программы в зависимости от значений выражения. Он более удобен в применении, чем набор вложенных операторов if, используемых для той же цели. Оператор switch вычисляет выражение и передает управление на метку case, шаблонное значение которой совпало со значением вы- ражения. Если выражение не/ принимает ни одного из заданных шаблонных значе- ъппъ управление передается оператору default. Если оператор default B блоке switch отсутствует, управление передается первому оператору после switch. Выполнение программы пр0должается с мегки сазе и далее по порядку, пока не встретится оператор break. Если это происхолит‚ управление передается в конец  оператора switch.  Пример1 switch( letter ) { case 'A': case 'a': printf( "You entered A“ ); break; case '8': case 'b': printf( "You entered В" );  302  Неделя 2. Основные вопросы 
break;  printf( "I don ’t have a case for %c", letter );  default: } Пример2 switch( number ) { case 0: puts( "Your number case 1: puts( "Your number case 2: puts( "Your number case 3: puts( "Your number case 99: puts( "Your number break; default: puts( "Your number }  is is is is  is  is  0 or less."); 1 or less."); 2 or less."); 3 or less."); 99 or less.");  greater than 99.");  После первых блоков case нет операторов break. Поэтому в программе находится совпадающее с Одним из шаблонов case значение, и далее выполняются все операто- ры вывода вплоть до break B блоке case 99. Если, например, число равно 3, то про- грамма сообщит, что число меньше или равно 3, меньше или равно 4, меньше или рав- но 5 и т.д. до меньше или равно 99. Вывод сообщений прололжается вплоть до опера-  тора break B блоке case 99.   Рекомендуется    Включайте блок default: в оператор switch, даже если вы охватили все возд ; можные значения в блоках case.   if если необходимо проанализиро-   или выражения. › Выравнивайте блоки case B тексте с помощью пробелов и отступов для удобст ва чтения программы.  „— ‚_, … „`…. ‚». Mafia».  тд...... - „…м— № »› с „в. _… «Mag.  Используйте оператор switch вместе;);  BaTb более дВУХ ЗНЗЧЗНИЙ переменной  ш,.   L... хмм». “мы. … ‚‚, .“  Не рекомендуется  {He аабывайте ставить оператор break в местах, где необходимо прерывать вы— полнение оператора switch.    Выход из программы  Программа на языке С обычно заканчивается тогда, котла выполнение доходит до закры- вающей скобки функции шаіп( ). Но можно закончить программу и преждевременно, вызвав библиотечную функцию exit( ). Вполне возможно также назначить одну или несколько функций для автоматического выполнения перед вых0дом из программы.  День 13-й. Дополнительные средства управления программой 303 
Функция exit()  Функция exit( ) прекращает выполнение программы и возвращает управление в операци- онную систему. Эта функция принимает один аргумент типа int, который посылается в опе- рационную систему для индикации успешного или аварийного завершения программы. Функция имеет следующий синтаксис: exit ( код_возвра та )  Если код_возврата имеет значение 0, это указывает на успешное завершение программы. Значение 1 указывает, что произошла какая-то ошибка. Как правило, операционная система игнорирует код возврата. В системе DOS этот код можно проанализировать в командном .ЬаЁ—файле с помошью оператора if errorlevel. Наша книга не посвящена системе DOS, поэтому если вы пользуетесь ею или какой-нибудь другой операционной системой и хотите использовать возвращаемое значение, ищите подробности в системной документации. Для использования функции exit() необходимо подключить к программе заголовочный файл stdlib.h. B этом файле также определяются две символические константы, передавае- мые как аргументы в функцию exit( ).  #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1  Таким образом, для выхода из программы с кодом возврата 0 используйте вызов exit(EXIT_SUCCESS), a для выхода с кодом 1 ———вызов exit(EXIT_ FAILURE).  Рекомендуется Не рекомендуется  ‹  Вызывайте функцию exit() для ава— рийного выхода из программы в случае ; возникновения ошибки. ‚Передавайте в функцию exit() аргу— менты, соответствующие возникшей ;, ситуации. ( %  „м……— ч— ~ . . щу— ‚м…… , д.ц-№№ „ .. №…       $ z Ё   Выполнение команд операционной системы  В стандартной библиотеке функций С имеется функция system( ), с помощью которой можно выполнять команды операционной системы прямо из работающей программы. Это может оказаться полезным для чтения списка файлов в каталоге или форматирования диска без выхода из программы. Для использования этой функции программа должна включить за- головочный файл stdlib.h. Функция имеет следующий формат: system( команда) ;  Аргумент команда может быть строковой константой или указателем на строку. Напри- мер, чтобы вывести на экран список всех файлов каталога в системе DOS, можно записать следующий оператор:  system("dir");  Возможна и такая запись:  304 Неделя 2. Основные вопросы 
1 ichar *command = “dir”;  'system(command); После того, как команда операционной системы выполнена, управление возвращается в программу в то же место, откуда был сделан вызов system( ). Если переданная в system) строка не является допустимои командой операционной системы, то перед возвращением в программу на экране появится сообщение об ошибке Bad command or file name error. Ис- пользование функции system( ) демонстрируется в листинге 13.9.  Листинг 1 3.9. system. с —— выполнение команд операционной системы с помощью функции system( )    1: /* демонстрация работы функции system(). */ 2: #include <stdio.h> 3: #include <stdlib.h> 4: 5: int main( void ) 6: { 7: /* Объявление буфера для хранения введенной команды. */ 8: 9: char input[40]; 10: 11: while (1) 12: { 13: /* Ввод команды пользователя. */ 14: 15: puts(“\nInput the desired system command, blank to exit“); 16: gets(input); 17: 18: /* Выход при вводе пустой строки. */ 19: 20: if (input[0] == '\0') 21: exit(0); 22: 23 /* Выполнение команды. */ 24: 25 system(input); 26 } 27: return 0; 28: }   ' Input the desired system command, blank to exit Wm. dir * bak  Volume in drive E is BRAD_VOL_B Directory of E:\BOOK\LISTINGS LIST1414 ВАК 1416 05—22-99 5:18p 1 file(s) 1416 bytes 240068096 bytes free  Input the desired DOS command, blank to exit  День 13-й. Дополнительные средства управления программой 305 
 ~ dir *.bak—— это команда системы DOS для отображения списка всех (пишет"! файлов в текущем каталоге с расширением ВАК. Эта команда работает и в ` среде Microsoft Windows. B системе UNIX тот же результат можно полу- чить с помощью команды ls * .bak. Другие операционные системы пред- назначают для этой цели другие команды, информацию о которых можно найти в системной документации.     Листинг 13.9 иллюстрирует использование функции system( ). С помощью цик- ла while B строках 11—26 программа выполняет команды операционной системы. В строках 15—16 пользователя приглашают ввести команду. Если он нажимает <Enter> без ввода команды, то B строках 20—21 выполняется выход из программы. В строке 25 вызывает- ся функция system) с введенной пользователем командой B качестве аргумента. Выполнение программы B различных системах, разумеется, даст совершенно разные результаты, отли- чающиеся от приведенного. Передаваемые B функцию system( ) параметры не обязаны быть только командами опера- ционной системы — например, форматирования дисков или вывода содержимого каталогов. Можно также передать имя любой программы или командного файла — они также будут вы— полнены. Например, при передаче аргумента LIST1308 будет выполнена программа с именем LIST1308. После выхода из нее управление будет возвращено в точку, откуда вызывалась функция system( ). Единственное ограничение на работу с функцией system() связано с оперативной памя— тью. Дело в том, что при выполнении system) основная программа остается B памяти, соз- дается новая копия командного процессора операционной системы, и B ней запускается зав данная программа или команда. Если для этих операций не хватит памяти, то на экране поя- вится сообщение об ошибке.   Если вы воспользуетесь компилятором BloodShed Dev-C++ с прилагаемо- №і№ го компакт-диска, то обнаружите, что при создании нового файла исх0дно- го кода в его среде используется функция system( ). Для создания нового файла выберите команду New Source File из меню Рі|е‚ и появится сле- дующий текст: #include <iostream.h> #include <stdlib.h> / int main() { system("PAUSE"); return 0;  }  Оператор system(“PAUSE") приостанавливает выполнение программы до тех пор. пока пользователь не нажмет клавишу. Это сделано для того. чтобы можно было прочитать результаты работы на экране — 8 против- ном случае программа отображает данные на экране и мгновенно закан- чивается, не давая ничего прочитать.     306 Неделя 2. Основные вопросы 
Резюме  На этом занятии были рассмотрены важные средства управления программой. Вы познац комились с оператором goto и причинами, по которым его не следует использовать в своих программах. Дополнительные возможности для управления циклами предоставляются опера- торами break и continue. Организуя бесконечные циклы с применением этих операторов, можно создавать полезные программные конструкции для решения важных задач. Для при- нудительного завершения работы программы используется функция exit( ). Наконец, в про- грамме возможно выполнение команд операционной системы и внешних программ с помо- щью функции system( ).  Вопросы и ответы  Что лучше: оператор switch или вложенные операторы if? Если требуется проанализировать выражение, принимающее более двух разных значений, и в зависимости от этого выполнить те или иные действия, то оператор switch почти всегда подходит для этой цели лучше. К тому же он делает программу более удобочитаемой. Если же требуется только проверить истинность или ложность выражения, используйте оператор if.  Почему следует избегать применения оператора goto? При первом знакомстве с этим оператором создается обманчивое впечатление, что он очень удобен в использовании. На самом деле он скорее создает проблемы, чем решает их. Оператор goto идет вразрез со структурным стилем программирования, поскольку прыжками перебрасывает управление в другое место программы. Многие отладчики (программы, пред-‚ назначенные для отслеживания ошибок в программах) даже не могут корректно обработать оператор goto. B общем, этот оператор только создает хаос в исходном коде программы.  Почему не у всех компиляторов С один и тот же набор функций? На этом занятии вы узнали, что некоторые функции С совсем не обязательно входят в комплект поставки всех компиляторов этого языка и не обязательно поддерживаются всеми операционными системами. Например, функция sleep( ) имеется в средах программирования от Borland, но не поддерживается программным обеспечением от Microsoft. Хотя существуют стандарты, поддерживаемые всеми АМЗЬсовместимыми компилятора- ми, эти стандарты вовсе не запрещают производителям программного обеспечения добавлять в свои продукты дополнительные функции, разработанные ими самими. Это делают все раз— работчики средств программирования, если полагают, что дополнительные функции могут оказаться полезными потребителям.  Разве С -— He стандартизированный язык? Язык С стандартизирован в очень высокой степени. Американский национальный инсти— тут стандартов (Americal National Standards Institute —— ANSI) разработал стандарт ANSI язы- ка С, в котором строго зафиксированы почти все средства языка и его синтаксис, в том числе и требуемый набор функций. Разработчики компиляторов и сред программирования добав- ляют K этому набору свои функции, не определенные стандартом ANSI, чтобы обойти конкурентов. Кроме того, есть и компиляторы, не претендующие на полную совместимость со стандартом ANSI. Если же ограничиться только АМЗі-совместимыми компиляторами, то окажется, что 99% синтаксиса и функций программ совершенно одинаковы во всех средах программирования.  День 13„й_ Дополнительные средства управления программой 307 
Насколько безопасно применение функции system” B программе для выполнения системных операций? Применение функции system( ) может показаться легким путем проделывать такие опера- ции, как выв0д списка файлов в каталоге, но при этом следует соблюдать осторожность. Большинство команд операционных систем отличаются от своих аналогов в других системах. Поэтому программа, выполняющая системные команды, скорее всего окажется плохо со- вместимой с другими операционными системами. Если же функция system() используется для запуска других программ (а не команд операционной системы), то проблема переносимо- сти не возникнет.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления изученного материала и упражнения для приобретения практических навыков программирования.  Контрольные вопросы  В каких случаях применение оператора goto является целесообразным?  В чем разница между операторами break и continue?  Что такое бесконечный цикл и как его создать?  Какие два события приводят к окончанию работы программы? Выражения каких типов используются в операторе switch?  Для чего предназначен оператор default?  Что делает функция exit( )?  OOxlONyI-RMNH  Каково назначение функции system( )?  Упражнения  1. Напишите оператор, вынуждающий программу перейти к выполнению следующего про- хода цикла.  2. Напишите оператор, вынуждающий программу прервать работу цикла и выйти из него.  3. Напишите строку к0да для вывода списка всех файлов в текущем каталоге (в системе DOS).  4. Поиск ошибок. Есть ли ошибки в следующем фрагменте кода? switch( answer )  { case 'Y': printf("You answered yes"); break; case 'N': printf("You answered no"); }  5. Поиск ошибок. Есть ли ошибки в следующем фрагменте кода? switch(choice )  {  308 Неделя 2. Основные вопросы 
default: printf("You did not choose 1 or 2"); case 1: printf("You answered 1"); break; case 2: printf("You answered 2"); break;  }  6. Перепишите упражнение 5 с использованием операторов if. 7. Напишите бесконечный цикл do. . .while.  Следующие упражнения имеют великое множество правильных решений. Поэтому мы не приводим ответов, а оставляем эти упражнения вам для самостоятельной работы без посто- ронней помощи.  8. Самостоятельная работа. Напишите программу, работаюшую как калькулятор. Про- грамма должна выполнять сложение, вычитание, умножение и деление.  9. Самостоятельная работа. Напишите программу, предлагающую пользователю меню из пяти пунктов. Пятый пункт должен быть выходом из программы. Каждый из остальных ПУНКТОВ должен ВЫПОЛНЯТЬ какую-либо системную команду С ПОМОЩЬЮ функции system().  День 13-й. Дополнительные средства управления программой 309 
  V‘Wx  Работа с экраном, принтером и клавиатурой  Практически во всех программах выполняется ввод и вывод данных. Очень часто о каче- стве и практической ценности программы можно судить именно по тому, насколько хорошо она справляется с задачами ввода—вывода. Ранее мы уже рассматривали некоторые основные средства ввода-вывода в языке С. На этом занятии вы изучите следующие вопросы. I Использование потоков ввода-вывода в С Средства ввода с клавиатуры Средства вывода текстовых и числовых данных на экран  Посылка данных на принтер  Перенаправление ввода-вывода  Потоки ВВОДа-ВЫВОДа  Прежде чем заняться деталями ввода-вывода, рассмотрим такие важные средства языка С, как потоки. Весь ввод и вывод в программах на С выполняется через потоки независимо от того, откуда вводятся данные и куда они выводятся. Как вы увидите позднее, этот стандартный метод ввода-вывода весьма удобен для программистов. Так что знать о потоках и понимать принципы их работы очень важно. Но вначале давайте определим сами термины ввод и вывод.  Что такое ввоц-вывоц  Как уже неоднократно упоминалось, программа при выполнении хранит данные в оператив- ной памяти (ОЗУ). Эти данные представлены в виде переменных, структур и массивов, объяв- ленных в программе. Откуда же появляются эти данные, и что программа может с ними делать?  I Данные могут поступить из-за пределов программы. Процесс перемещения данных с внешних носителей информации в оперативную память компьютера называется 6‘80- дом. Чаще всего ввод выполняется с клавиатуры и из файлов на дисках. 
l Программа также может послать данные из памяти на внешний носитель информации. Этот процесс называется выводом. Вывод чаще всего направляется на экран, принтер или в дисковые файлы.  Источники данных для ввода и места назначения вывода имеют собирательное название устройства. Устройствами являются клавиатура, экран монитора и т.д. Некоторые устройст- ва (например, клавиатура) служат только для ввода, другие (такие как экран) _ только для вывода, а есть и устройства, пригодные для пересылки данных в обе стороны (жесткие дис- ки). Это показано схематически на рис. 14.1.  Экран   : Модем Клавиатура   Программа  Файлы на диске    [:1 Принтер O l]    Последовательный порт    Рис. 14.1. Ввод-вывод с помощью различных устройств  Понятие потока  Поток представляет собой последовательность символов, Точнее говоря, это последовательность байт данных, Поток байт, принимаемый программой, назы- вается потоком ввода, а поток, посылаемый программой на устройства, называется потоком вывода` Сосредоточившись на самих потоках, можно не заботиться о том, откуда или куда они направляются. Таким образом, основное преимущество работы с потоками состоит в том, что потоки являются независимыми от устройств. Программистам не приходится писать отдельные функции ввода—вывода для каждого конкретного устройства (клавиатуры, диска и т.д.). Программа рассматривает ввод-вывод как работу с непрерывным потоком байт неза— висимо от их происхождения или места назначения.  "MM“ REM]!!! Каждый поток в С связан с файлом. В этом контексте термин файл не относится только к файлам на дисках. Скорее это промежуточный объект между потоком  ввода или вывода, с которым работает программа, и реальным физическим устройством, фак— тически выполняющим ввод-вывод данных. Начинающему программисту на С B основном не приходится беспокоиться о деталях работы этих файлов, поскольку автоматическое обеспе- чение взаимодействия между потоками, файлами и устройствами берут на себя библиотечные функции С и операционная система.  День 14—й. Работа с экраном, принтером и клавиатурой 311 
ТЕКСТОВЫЕ И ДВОИЧНЫЭ ПОТОКИ  Потоки бывают двух разновидностей: текстовые и двоичные. Текстовые потоки состоят из символов, например, текстовых данных для вывода на экран. Текстовые потоки организо- ваны B виде строк до 255 символов длиной с завершающим символом конца строки. Некото- рые символы в текстовых потоках воспринимаются как специальные, т.е. имеющие особые управляющие функции. Это, например, все тот же символ конца строки. На этом занятии мы рассматриваем только текстовые потоки.  НПВЫЙ №№"?! Двоичный поток может содержать данные любых видов, в том числе и тексто— вые. Байты данных в двоичном потоке не интерпретируются никаким особым  образом. Они просто воспринимаются как есть. Двоичные потоки используются в основном для работы с дисковыми файлами, которые рассматриваются на занятии 16.  Стандартные потоки  В языке С стандарта ANSI имеется три стандартных потока, известных также под назва- нием стандартных файлов ввода-вывода. В системе DOS или Windows Ha [ВМ-совместимом персональном компьютере имеется еще два дополнительных стандартных потока. Эти потоки автоматически открываются при запуске программы и закрываются при ее завершении. Про- граммисту не надо предпринимать никаких действий, чтобы сделать эти потоки доступными программе. В табл. 14.1 перечислены стандартные потоки и устройства, с которыми они обычно ассоциируются. Все пять стандартных потоков работают в текстовом режиме.  Таблица 1 4. 1 . Стандартные потоки заеда-выведа    Имя Поток Устройство stdin Стандартный поток ввода Клавиатура stdout Стандартный поток вывода Экран stderr Стандартный поток ошибок Экран stdprn" Стандартный поток печати Принтер (LPT1 :) эшацх' Стандартный вспомогательный поток Последовательный порт (COM1:)   * Полдерживаются только в Windows и DOS. Не определены в стандарте ANSI  Фактически, мы уже неоднократно пользовались двумя потоками из этого списка. Всякий раз, как в программе выполнялся вызов функций printf() или puts( ) для отображения тек— ста на экране, включался в работу поток stdout. Аналогичным образом при вызове gets() или scanf ( ) мы работали с потоком stdin. Стандартные потоки открываются автоматически, но другие (например, предназначенные для работы с данными на диске) необходимо откры- вать явным образом. Об этом вы узнаете на занятии 16. А на текущем занятии будем работать только со стандартными потоками.  Функции потокового ввоца-вывоца  В стандартной библиотеке функций С имеется большое количество функций ДЛЯ ввода- вывода с использованием потоков. Большинство этих функций имеют две разновидности: в одной всегца используются стандартные потоки, в другой поток определяется программи-  312 Неделя 2. Основные вопросы 
стом. Эти функции перечислены в табл. 14.2. Приведенный здесь список функций ввода— вывода С далеко не полон, причем не все из перечисленных функций рассматриваются на те— кущем занятии.  Таблица 1 4.2. Стандартные функции потокового ввода—вывола   Функции, использующие Функции, требующие Назначение   стандартные потоки указания имени потока printf ( ) fprintf( ) Форматированный вывод vprintf() vfprintf( ) Форматированный вывод о пе- ременным списком аргументов PUtSH fPut5( ) Вывод строк putchar() putc( ), fputc() Вывод символов scanf( ) fscanf () Форматированный ввод vscanf() vfscanf() Форматированный ввод с пере- менным списком аргументов gets() fgets( ) Ввод строк getchar( ) getc( ), fgetc() Ввод символов perror( ) Вывод строк в stderr   Все эти функции требуют включения в программу заголовочного файла stdio.h. Для функции perror() может понадобиться также файл stdlib.h. Функции vprintf() и vfprintf() требуют еще и включения файла stdargs.h. B системе UNIX для работы этих функций может потребоваться файл varargs.h. Чтобы узнать, какие именно заголовочные файлы нужны для работы той или иной функции, обратитесь к документации стандартной библиотеки функций вашего компилятора.  Простой пример работы с потоками  Короткая программа в листинге 14.1 демонстрирует автоматическое использование пото- ков stdin и stdout.  Листинг 1 4. 1 . stream.c — автоматическое использование стандартных потоков   /* Демонстрация работы потоков ввода и вывода. */ #include <stdio.h>  int main( void )  { char buffer[256];  ЮЫФШЬЫМН II II II II II II II II  /* Ввод строки и немедленный ее вывод. */  ") II  10: puts(gets(buffer)); 11: 12: return О; 13; }   День 14—й. Работа с экраном, принтером и клавиатурой 313 
В строке 10 выполняется ввод строки с клавиатуры (из потока stdin) с помощью функции gets( ). Функция gets( ) возвращает указатель на строку, который можно сразу передать как аргумент в функцию puts( ). для отображения на экране (т.е. вывода в поток stdout). B итоге программа получает строку символов от пользователя и тут же выводит ее на экран.    Рекомендуется Не рекомендуется  Пользуйтесь преймуществами стендер} 4y Ц Ч„переише›!шэываите их не изменяйте ных потоков ввода-вывода С при напи- , ндартныеё потоки без особой Необхо- сании своих программ ., . W? ? димостиъё’іё, 5%"? „ = _ „ , ‘ WM? '_ M „*не пытайтесь Твоспользоваться потоком ` ° ` ‘) ‘ ' {ввела (stdin) ідля цепей вывода напри-  mm W: с помощью функции ,rrprintm        : хмм-ых… чм «… >v‘o;~'v джи-…»»  Ё ›  Для учебных программ, написанных начинающими программистами на С, функция gets() вполне подходит. Однако в реальных программах следует отдавать предпочтение функции fgets( ), поскольку использование gets() сопряжено с определенным риском. Под- робности рассмотрим несколько позже.  ВВОД с клавиатуры  В очень многих программах на С используется ввод с клавиатуры (т.е. из стандартного потока stdin). Функции ввода подразделяются на три иерархических уровня: ввода отдель- ных символов, ввода строк и форматированного ввода.  ВВОД символов  ФУНКЦИИ ввода СИМВОЛОВ СЧИТЫВЗЮТ ДЗННЫС ИЗ ПОТОКЗ ввода ПО ОДНОМУ СИМВОЛУ за раз. При вызове одной из таких функций она возвращает следующий символ из потока или EOF, если достигнут конец файла или случилась какая-либо ошибка. Символическая константа EOF определена в файле stdio.h H равна -1. Функции ввода символов по-разному выполняют бу— феризацию и дублирование вводимых символов.  I Некоторые из функций ввода символов работают с буферизацией. Это означает, что операционная система хранит символы во временном буфере памяти, пока пользова— тель не нажмет клавишу <Enter>, и только после этого символы попадают в поток ввода stdin. Другие функции работают без буферизации, т.е. символ, вводимый с их помощью, попадает в stdin немедленно после нажатия соответствующей символьной клавиши.  I Некоторые функции ввода дублируют каждый введенный символ, копируя его в поток stdout. Другие этого не делают, т.е. символ фигурирует только в потоке stdin. Дуб— лируемый символ появляется на экране, поскольку именно это устройство ассоцииро— вано с потоком stdout.  Подробности буферизации и дублирования введенных символов в потоке stdout paCCMaT- риваются в следующих разделах.  314 Неделя 2. Основные вопросы 
Функция getchar( ) J  Функция getchar() получает следующий по очередности символ из потока stdin. OH буферизует ввод и дублирует символ на экране, Ее прототип имеет следующий вид: int getchar(void); Использование getchar() проиллюстрировано в листинге 14.2. Отметим в тексте проч граммы функцию putchar( ), выводящую символ на экран. О ней будет рассказано позже.   Листинг 1 4.2. getchar.c — использование функции getchar.c   /* демонстрация функции getchar(). */ #include <stdio.h>  int main( void )  mflOanyh-ri-I о. о. о. о. о. о. о. .. оо  { int ch; 9 while ((ch = getchar()) != ’\п’) 10: putchar(ch); 11: 12: return О; 13: }   This is what’s typed in. гадит”, This is what's typed in.  B строке 9 этой программы вызывается функция getchar( ). Она ожидает полу- чения символа из потока stdin. Поскольку эта функция работает с буферизаци—  ей, символ в потоке не появляется до тех пор, пока пользователь не нажмет <Enter>. Тем не менее символ каждой нажатой клавиши немедленно появляется на экране. После нажатия клавиши <Enter> все введенные символы, включая символ конца строки, посылаются операционной системой в поток stdin. Функция getchar( ) возвращает по одно— му символу за один вызов, присваивая каждый из них по очереди переменной ch. Каждый символ сравнивается с символом конца строки ’ \n' и отображается на экране с помощью функции putchar( ), если не является им. Как только функция getchar( ) возвраща- ет символ конца строки, цикл while заканчивает работу. Функцию getchar() можно использовать для ввода целых строк текста, как это проде- монстрировано в листинге 14.3. ПраВДа‚ для этой цели больше подходят другие функции вво- да, которые рассмотрим позже.  Листинг 1 4.3. getcharZ . с —— ввод целых строк текста с помощью функции getchar( )   /* Использование функции getchar() для ввода строк. */ #include <stdio.h>  #define MAX 80  а\шьшмн  День 14-й. Работа с экраном, принтером и клавиатурой 315 
7: int main( void )  8: { 9: char ch, buffer[MAX+1]; 10: int x = О; 11: 12: while ((ch = getchar()) != '\n' && х ‹ МАХ) 13: buffer[x++] = ch; 14: 15: buffer[x] = ’\0’; 16: 17: printf("%s\n", buffer); 18: 19: return О; 20: }   This is a string. This is a string.  B этой программе функция getchar( ) используется почти таким же образом, как и в программе листинга 14. 2. Здесь в цикл добавлено дополнительное условие: символы извлекаются из потока ввода с помощью getchar( ) до тех пор, пока не встретится символ конца строки или пока не будет считано 80 символов. Все символы помещаются в массив с именем buffer. По окончании ввода в конец массива помещается нулевой завер- шающий символ (строка 15) для корректного вывща введенной символьной строки функцией printf() B строке 17. Почему массив buffer объявляется в строке 9 с длиной МАХ-+1, а не просто МАХ? Это дела— ется для того, чтобы в массив поместились 80 символов плюс завершающий нулевой символ. Не забывайте предусматривать место для завершающего нуля в массивах символов.   Функция getch()  Функция getch( ) получает следующий символ из потока stdin. Она вводит символы без буферизации и без дублирования на экране. Функция getch() He определена в стандарте ANSI. Это означает, что она имеется не во всех компиляторах и работает не во всех системах. Кроме того, даже в тех средах, где она есть, для ее работы могут потребоваться разные заго- ловочные файлы. Обычно прототип функции getch( ) находится в заголовочном файле conio.h и имеет следующий вид:  int getch(void);  Поскольку функция работает без буферизации, символы возвращаются ею немедленно при нажатии соответствующих клавиш без ожидания нажатия <Елтег>. Символы не отобра- жаются на экране, поскольку не дублируются в потоке вывода. Использование функции getch( ) проиллюстрировано в листинге 14.4.   "д…,… В приведенном ниже листинге используется функция getch( ), He опреде- пенная в стандарте ANSI. Соблюдайте осторожность при работе с такими функциями, поскольку не все компиляторы гарантируют их поддержку. Если при компиляции приведенной программы появляется сообщение об ошибке, то вероятная причина этого —— отсутствие в нем поддержки функ— ции getch( ).     316 Неделя 2. Основные вопросы 
Листинг 1 4.4. getch .с — использование функции getch()   1: /* демонстрация функция getch(). */ 2: /* Не полностью АЫЗШ-совместимый код */ 3: #include <stdio.h> 4: #include <conio.h> 5: 6: int main( void ) 7: { 8: int ch; 9: 10: while ((ch = getch()) != '\п‘) 11: putchar(ch); 12: 13: return 0; 14: }   Testin the etch fun tio Magnum Ч 9 ( ) C n  При выполнении этой программы функция getch() возвращает введенный сим- " вол сразу же после нажатия его клавиши, не ожидая нажатия <Enter>. Символы не дублируются в потоке вывода автоматически, так что единственная причина их появления на экране —— это вызов функции putchar( ). Чтобы лучше понять, как работает getch( ), до- бавьте точку с запятой в конец строки 10 и уберите строку 11 (оператор putchar(ch)). При запуске программы вы обнаружите, что на экран ничего не выв0дится, потому что функция getch() вволит символы без их дублирования в потоке вывода. В том, что символы вообще вволятся, можно убедиться только благодаря тому, что в программе используется функция putchar( ) для их отображения на экране. Почему в этой программе символы сравниваются не с концом строки \п, а с \r? Код \r представляет собой специальный управляющий символ возврата каретки. Именно он посыла- ется в поток stdin при нажатии клавиши <Enter>. При буферизованном вводе этот символ автоматически преобразуется в символ конца строки, поэтому для определения, была ли на— жата клавиша <Enter> , нужно сравнивать введенные символы именно с концом строки. А вот при вводе без буферизации такого преобразования не происходит, и символ \r остается в по- токе как есть. В листинге 14.5 функция getch() используется для ввода целой строки текста. Выполне- ние этой программы убедительно демонстрирует, что вводимые с помощью getch( ) символы не отображаются на экране. За исключением замены getchar( ) Ha getch( ), программа прак- тически идентична листингу 14.3.  Листинг 1 4.5. getch2 .c — использование функции getch( ) для ввода целой строки   1: /* Использование getch() для ввода строк. */ /* Не полностью АЫБШ-совместимый код */ #include <stdio.h> #include <conio.h>  ChU'IbbJN "о.  #define MAX 80  День 14-й. Работа с экраном, принтером и клавиатурой 317 
8: int main( void )  9: { 10: char ch, buffer[MAX+1]; 11: int x = 0; 12: 13: while ((ch = getch()) != '\r' && x < MAX) 14: buffer[x++] = ch; 15: 16: buffer[x] = '\0'; 17: 18: printf(“%s“, buffer); 19: 20: return О; 21: }   P Here's a string B3uflhlflfllfl Here's a string  @:“: Помните, что функция getch() He определена в стандарте АМЗі. Это озна- чает, что некоторые компиляторы могут ее не поддерживать. Компиля- торы от Symantec и Borland поддерживают эту функцию, а компилятор Microsoft— аналогичную функцию _getch(). Наличие функции getch() в библиотеке компилятора можно выяснить из документации. Если вас ин- тересует совместимость программы с различными средами, то постарай- тесь избегать нестандартных функций. Компилятор Bloodshed Dev-C++, приложенный к книге, поддерживает функцию getch(), но не совсем в та- ком виде, как описано выше. Это еще одна распространенная проблема нестандартных функций.      Функция getche()  Этот раздел совсем короткий, потому что getche() практически ничем не отличается от getch( ), с тем единственным исключением, что функция getche( ) выводит введенный сим— вол в поток stdout. B качестве упражнения измените программу в листинге 14.4 так, чтобы вместо getch() там использовалась функция getche( ). При выполнении программы каждое нажатие клавиши будет отображать соответствующий символ дважды: сперва функция getche() будет дублировать его в потоке вывода, а потом putchar( ) — вторично выв0дить в тот же поток.   @;д" Функция getche() He определена в стандарте ANSI, но многие компилято- ры ее п0ддерживают.     Функции getc() и fgetc()  Функции getc() и fgetc() также предназначены для ввша символов, но не обязательно из потока stdin. Поток ввода должен указать программист в программе. В основном эти функции используются для ввода данных из дисковых файлов. Более подробно мы поговорим о них на занятии 16.  318 Неделя 2. Основные вопросы 
  Рекомендуется  Уясните для себя разницу между вводом E Не Используйте функции He опреде- “ с буферизацией и без нее.… д „‹ :;г ‹ E ленные в стандарте ANSI, если програм- , _ , 7:33:13“; …} E ма должна быть переносимой из одной 1 ‘ — “ “ ‘ - ;, среды Вдругую * Помните что при вводе сиЁ.!волов некс— 5 He ожидайте от нестандартных функций % торые функции отображает вводиМЫе E ОдиНакового поведения в средах различ- E символы на экране а некоторые —— “мнет; ? E ных компиляторов. “  уямм‘м. … мы». ‚„ ‚ ‚,  Не рекомендуется     .ччщ`  "к. .   чм:   Возвращение символов в поток ввода  Зачем нам может понадобиться вернуть символ в поток вв0да? Поясним на примере. Предположим, программа читает символы из потока и прекращает ввод, только получив на один символ больше, чем нужно. Например, пусть считываются только цифры, и вв0д закан— чивается, как только появляется нецифровой символ. Этот символ может быть частью Иду— щих следом важных данных, но что делать, раз он уже пропал из потока ввода? Неужели он потерян насовсем? Оказывается, нет. Его можно вернуть назад в поток, и следующая опера- ция вв0да из этого потока прочитает первым именно его. Для возвращения введенного символа в поток ввода используется библиотечная функция ungetc( ). Она имеет следующий прототип: int ungetc(int ch, FILE *fp); Аргумент ch — это символ, который нужно вернуть в поток. Аргумент *fp указывает на сам поток. Это может быть любой поток ввода. Пока что вы знаете только ОДИН такой по- ток — stdin, поэтому указывайте именно его в качестве второго аргумента: ungetc(ch, stdin); Вообще же второй аргумент нужен для работы c дисковыми файлами, которые мы рас- смотрим на занятии 16. В поток можно вернуть только один символ между двумя операциями ввода. Возвраще- ние символа EOF не допускается. Функция ungetc() возвращает символ ch B случае успеха или EOF, если символ невозможно вернуть в поток.  Ввод строк  Функции ввода строк считывают из потоков целые строки, т.е. последовательности сим- волов до первого встретившегося символа конца строки. В стандартной библиотеке С имеют- ся две такие функции: gets() и fgets( ).  Функция gets( )  Функция gets() уже рассматривалась нами на занятии 10, посвященном обработке сим- вольной информации. Ничего сложного в ее работе нет: функция считывает последователь- ность символов из потока stdin и помещает ее в заданную строку. Ее прототип выглядит  следующим образом: char *gets(char *str);  Это уже знакомый вам прототип. Функция gets( ) принимает в качестве аргумента указа- тель на строку и возвращает указатель того же типа. Функция считывает символы из потока ввода до тех пор, пока не встретится конец строки (\n) или конец файла. Символ конца стро- ки автоматически заменяется нулевым символом, и вся строка помещается по адресу, указан- ному аргументом str.  День 14—й. Работа с экраном, принтером и клавиатурой 319 
Возвращает функция все тот же указатель str. Однако в случае обнаружения конца файла до ввода каких—либо символов или B случае другой ошибки, функция gets() возвращает нулевой указатель. Прежде чем вызывать gets( ), необходимо позаботиться о выделении достаточного количества памяти для хранения строки` Для Этой цели используются методы, рассмотренные на занятии 10. У данной функции нет никакого способа узнать, выделена ли память для указателя str —— строка всегда вводится и помещается по указанному адресу независимо от этого. Если память не была распределена корректно, то введенная строка может затереть важные данные и вызвать ошибки в программе. Функция gets( ) использовалась в листингах 10.5 и 10.6.  Функция fgets()  Библиотечная функция fgets( ) аналогична по своему назначению функции gets( ) — она также считывает строку символов из потока ввода. Однако она является более гибкой, чем gets( ), так как позволяет указать поток (не обязательно stdin) и максимально допустимое количество вволимых символов. Функция fgets() часто используется для ввода текста из дисковых файлов (см. материал занятия 16). Для ввода из stdin следует передать в нее имя потока как аргумент. Вот прототип этой функции:  char *fgets(char *str, int n, FILE *fp);  Последний параметр, FILE *fp, как раз и указывает поток ввода. Поскольку других пото- ков вы сейчас не знаете, передавайте имя стандартного потока stdin. Указатель str указывает, куда поместить введенную строку. Аргумент n задает макси- мальное количество символов для ввода. Функция fgets() считывает символы из потока ввода до тех пор, пока не встретится символ конца строки или не будет прочитано п—1 символов. Символ конца строки включается B состав самой строки B виде завершающего ее нулевого символа. Функция fgets() возвращает то же значение, что и рассмотренная ра- нее функция gets( ). Строго говоря, функция fgets( ) не обязательно вводит целую текстовую строку, если оп— ределить это понятие как последовательность символов с завершающим ее символом конца строки. Если в строке больше, чем п—1 символ, то она булет введена не полностью. При рабо- те с потоком stdin выполнение функции fgets( ) не прерывается до нажатия клавиши <Enter>, но в строку помещаются только первые п—1 символов. Работа функции fgets() ил- люстрируется в листинге 14.6.  Листинг 1 4.6. fgets .с — использование функции fgets( ) для ввода склавиатурьп   1: /* Демонстрация функции fgets(). */ 2: 3: #include <stdio.h> 4: 5: #define MAXLEN 10 6: 7: int main( void ) 8: { 9: char buffer[MAXLEN]; 10: ll: puts("Enter text a line at a time; enter a blank to exit.”);  320 Неделя 2. Основные вопросы 
12:  13: while (1) 14: { 15: fgets(buffer, MAXLEN, stdin); 16: 17: if (buffer[0] == '\n’) 18: break; 19: 20: puts(buffer); 21: } 22: return О; 23: }   Roses are red Roses are red Violets are blue Violets a re blue Programming in C Programmi ng in C Is for people like you! Is for pe ople like you!  Enter text a line at a time: enter a blank to exit. Резштьшат '  № Строка 15 с0держит вызов функции fgets( ). Чтобы проверить работу програм- мы, введите несколько строк большей и меньшей длины, чем MAXLEN. Если вво-  дится строка длиннее MAXLEN, то первый вызов fgets() считывает первые MAXLEN-l ее симво- лов, а остальные остаются в буфере клавиатуры, откуда они вводятся последующими вызова- ми fgets( ) или любой другой функции вв0да из потока stdin. Программа заканчивает рабо- ту после ввода пользователем пустой строки (см. строки 17 и 18).  Форматированный ввод  Функции ввода, которые мы рассматривали до сих пор, всего лишь получали Один или не- сколько символов из потока вв0да и помещали их куда-то в память. Они не выполняли ника- кой интерпретации или форматирования введенных данных, и пока неясно, можно ли вводить таким образом числовые значения. Например, как организовать вв0д с клавиатуры числа 12 . 86 и его присваивание переменной типа float? Вот тут-то на сцене и появляются функции scanf ( ) и f scanf ( ). Функция scanf( ) уже рассматривалась на занятии 7, посвященном осно- вам вв0да-выв0да в С. В этом разделе поговорим о ней поцробнее. Две вышеупомянутые функции совершенно аналогичны по своему назначению, только scanf() всегда работает с потоком stdin, a при вызове fscanf() необх0димо задать поток ввода. Здесь мы рассмотрим только функцию scanf ( ), поскольку fscanf() обычно использу- ется для ввода из файлов и поэтому будет изучена на занятии 16.  День 14-й. Работа с экраном, принтером и клавиатурой 321 
Аргументы функции scanf( )  Функция scanf ( ) принимает переменное число аргументов — как минимум два. Первым ее аргументом должна быть строка формата, в которой с помощью специальных управляю- щих символов задаются правила интерпретации введенных данных. Второй аргумент и все последующие являются адресами переменных, по которым следует поместить введенные данные. Вот пример:  scanf("%d", ax);  Первый аргумент "%d" представляет собой строку формата. В данном случае он сообщает функции scanf( ), что следует ожидать ввода одного целого числа со знаком. Второй аргу- мент содержит знак операции взятия адреса (&) и сообщает функции, что введенное значение следует поместить в переменную x. Теперь рассмотрим подробнее, какие элементы может содержать строка формата. В строке формата функции scanf( ) допускаются следующие элементы.  I Пробелы и символы табуляции, которые просто игнорируются (но с их помощью можно сделать строку более удобочитаемой).  I Символы (кроме %), которые ставятся в соответствие непустым символам в потоке ввода.  I Спецификации ввода, состоящие из символа % и следующего за ним специального символа. Одна спецификация ввода соответствует одному вводимому значению опре- деленного типа.  НОВЫЙ mepMUH Единственное, что непременно должно бьгть в строке формата, — зто хотя бы одна спецификация ввода. Каждая такая СПСЦИФикация начинается с символа % и  содержит обязательные и необязательные компоненты в определенном порядке. Функция scanf( ) применяет спецификации ввода из строки формата к полям ввода. Поле ввода — это последовательность непустых символов, которая считается законченной, как только встре- тился первый пустой символ (пробел, табуляция и т.п.) или как только исчерпана указанная ширина поля. Спецификация ввода может содержать такие компоненты.  I Необязательный флаг пропуска поля ввода (*), следующий непосредственно за %. Если он есть в спецификации, то указывает функции scanf ( ) выполнить ввод соответствующего значения, но проигнорировать результат и не присваивать его никакой переменной.  I Следующий элемент, ширина поля, также не обязателен. Ширина поля выражается де- сятичным целым числом и обозначает количество символов в поле ввода. Она указы- вает, сколько символов следует ожидать из потока stdin для получения требуемого элемента данных. Если ширина поля не указана, ввод продолжается до первого пусто- го символа.  I Далее в спецификации может идти необязательный модификатор точности — символ h, l или L. При его наличии меняется смысл спецификации типа, идущей следом. Это мы обсудим несколько позже.  I Единственный обязательный элемент спецификации ввода (кроме символа %) —— это спецификация типа. Она состоит из одного или нескольких символов, указывающих функции scanf( ), как ей интерпретировать вводимые данные. Эти символы перечис- лены и описаны в табл. 14.3. В столбце Аргумент перечислены типы, к которым должны принадлежать соответствующие переменные. Например, спецификации d должна соответствовать переменная типа int * (указатель на значение типа int).  322 Неделя 2. Основные вопросы 
Таблица 1 4.3. Спецификации типов для функции scanf( )   Тип Аргумент  Описание типа   d int * i int * о int * u unsigned int * х int * с char * s char *  a, e, f, g float *  [...] char *  [^.ОО] Char *  % Нет  Десятичное целое число  десятичное, восьмеричное (с 0 впереди), шестнадцатеричное (с ox или 0х впереди) целое число  Восьмеричное целое число с или без 0 впереди десятичное целое число без знака Шестнадцатеричное целое число с или без 0х (0х) впереди  Один или несколько введенных символов помещаются по адресу, ука— занному в аргументе. Завершающий \0 не добавляется. Если не задана ширина поля, вв0дится Один символ. Если задана, то вводится указан- ное количество символов, включая пустые символы  Вводится строка непустых символов и помещается в указанную пере- менную сдобавлением завершающего \0  Вещественное число с плавающей точкой. Можно вводить в дробно- десятичной или экспоненциальной форме  Строка. Вводятся только символы, перечисленные в скобках. Ввод за- канчивается, как только встретился символ не из списка, исчерпалась ширина поля или была нажата клавиша <Enter>. Для ввода символа ] поставьте его первым: []. . . 1. В конец строки добавляется \0  To же, что [ . . .], но вводятся только символы, не перечисленные в скобках Считывается символ % без его присваивания какой—либо переменной   Прежде чем рассмотреть примеры использования scanf( ), разберем подробнее модифи— каторы точности. Они перечислены в табл. 14.4.  Таблица 14.4. Модификаторы точности   Модификатор Назначение   hh Находясь перед спецификациями d. і, 0, u, x, )( или n. показывает, что аргумент является указателем типа signed char или unsigned char  h Находясь перед спецификациями d. 1, о, u. x. к или п, показывает, что аргумент является указателем типа short int или unsigned short int  1 Нах0дясь перед спецификациями d. i. о, u. x, к или n, показывает, что аргумент является указателем типа long или unsigned long. Перед спецификациями а, А, е, Е, f , г, g или G показывает, что аргумент имеет тип double  ll Находясь перед спецификациями d, i, о, u, х, к или п, показывает, что аргумент является указателем типа long long или unsigned long long  L Находясь перед спецификациями a. A, e. E, f. г, g или G, показывает, что аргу— мент имеет тип long double   Обработка лишних символов  Функция scanf( ) выполняет буферизованный ввод: ни один символ не вводится из потока stdin, пока не нажата клавиша <Enter>. После ее нажатия из потока поступает целая строка, которая затем обрабатывается функцией scanf( ) no порядку следования символов. Выполне- ние scanf( ) заканчивается только после того,’ как введены все данные, соответствующие спе—  День 14-й. Работа с экраном, принтером и клавиатурой 323 
цификациям в строке формата. Причем вводится ровно столько символов из stdin, сколько требуют эти спецификации. Если в потоке stdin есть лишние символы, они остаются в нем в ожидании следующих операций ввода. Эти символы могут стать источником проблем. Рас- смотрим работу функции scanf() более подробно, чтобы понять, как это может случиться. При вызове функции scanf ( ) и вводе строки текста пользователем возможны три ситуа- ции. Предположим, что необходимо ввести два целых десятичных числа, т.е. выполнить сле- дующий оператор: scanf( "%d %d", &х, &у );  Вот что может при этом произойти.  I Пусть строка, введенная пользователем, в точности соответствует требованиям строки формата. Например, пользователь ввел 12 14 и нажал <Епіет>. В этом случае никаких проблем не будет. Функция scanf () корректно завершит работу, не оставив ничего в потоке stdin.  I Пусть строка, введенная пользователем, содержит меньше элементов, чем указано в строке формата. Предположим, пользователь ввел число 12 и нажал <Enter>. B этом случае функция scanf( ) будет ожидать ввода недостающих элементов. Как только они будут введены, выполнение программы продолжится, и снова в потоке ввода stdin не останется ничего лишнего.  I A теперь пусть введенная строка содержит больше элементов, чем требует строка формата. Например, пользователь ввел 12 14 16 и нажал <Enter>. Тогда функция scanf () прочитает числа 12 и 14, после чего закончит работу. Лишние символы 1 и 6 останутся в потоке stdin в ожидании.  Именно в третьем случае могут возникнуть проблемы из-за лишних символов. Они оега- ются в потоке в течение всего времени выполнения программы до тех пор, пока не будет вы- полнен следующий ввод из stdin. При этом следующем вводе первыми считываются именно лишние символы, оставшиеся от предыдущего ввода. Легко понять, что это может стать при- чиной ошибок. Пусть, например, пользователь должен ввести целое число, а затем строку: puts("Enter your age."); scanf("%d", &age); puts("Enter your first ваше."); scanf( "%з", name);  Допустим, в ответ на приглашение ввести свой возраст (первая строка) пользователь про- явит излишнее рвение в точности ответа и введет 29 . 00. При первом вызове функция scanf( ) ожидает целое число, поэтому из потока stdin считывается значение 29 и присваивается пе- ременной age. Символы .00 остаются ожидать в потоке. При следующем вызове функции scanf () она ожидает ввода строки. Функция обращается в поток stdin 3a строкой и обнару- живает там символы . 00. B итоге переменной name присваивается строка . 00. Как же избежать этих неприятностей? Возникает мысль писать программы только для тех ‚ людей, которые никогда не делают ошибок при вводе, но вряд ли это можно назвать решени- ем проблемы. Более практичное решение —— это сделать так, чтобы перед обращением к пользователю с просьбой ввести какие-либо данные в потоке stdin He оставалось символов. Этого можно до- биться, вызвав функцию gets() для считывания оставшихся в потоке символов вплоть до символа конца егроки, включая и его. Вместо непосредственного вызова gets() из програм- мы это можно сделать в отдельной функции, назвав ее c1ear_kb() no смыслу выполняемой операции. Пример работы с этой функцией приведен в листинге 14.7.  324 Неделя 2. Основные вопросы 
Листинг 1 4.7. clearing.c — очистка потока stdin от лишних символов во избежание ошибок   ") ..  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:  (ID ‘~J С’\ {11 ‚:>. С;) Ъ`) |-4 о. о. .. оо .. о. о. о.  /* Очистка потока stdin от лишних символов. */  #include <stdio.h>  void clear_kb(void);  int main( void )  {  }  int age; char name[20];  /* Запрос возраста пользователя. */  puts("Enter your age:"); scanf("%d", &age);  /* Очистка stdin от лишних символов. */ clear_kb(); /* Запрос имени пользователя. */ puts("Enter your first name:"); scanf("%s", name);  /* Display the data. */  printf("Your age is %d.\n", age); printf("Your name is %s.\n", name);  return 0;  void clear_kb(void)  /* Очистка stdin от ожидающих в потоке символов.  {  }  char junk[80]; gets(junk);  Enter your age: Wuwmmm 29 and never older!  Enter your first name: Bradley Your age is 29. Your name is Bradley.  День 14-й. Работа с экраном, принтером и клавишурой  */  325 
№ Запустив программу из листинга 14.7, введите несколько лишних символов по- сле возраста, затем нажмите клавишу <Enter>. Убедитесь, что программа игно- рируст эти лишние символы и вводит имя правильно. Теперь измените программу, удалив вызов clear_kb( ), и запустите ее снова. На этот раз лишние символы, введенные в Одной строке с возрастом, окажутся в строке имени.  Обработка лишних символов с помощью fflush( )  Существует и другой способ избавления от лишних символов в потоке ввода. С помощью функции fflush( ) можно выполнить очистку любого потока, в том числе и стандартного потока ввода. Обычно функция fflush() используется для работы с дисковыми файлами (см. занятие 16). С ее помощью можно сделать листинг 14.7 еще проще. В листинге 14.8 функция f f lush( ) используется вместо функции clear_kb( ), фигурирующей в листинге 14.7.  Листинг 1 4.8. c1ear.c —— очистка потока stdin 01' лишних символов c помощью функции fflush( )   1: /* Очистка потока stdin от лишних символов. */ 2: /* Использование функции fflush() */ 3: #include <stdio.h> 4: 5: int main( void ) 6: { 7: int age; 8: char name[20]; 9: 10: /* Запрос возраста пользователя. */ 11: puts("Enter your age."); 12: scanf("%d", sage); 13: 14: /* Очистка stdin от лишних символов. */ 15: fflush(stdin); 16: 17: /* Запрос имени пользователя. */ 18: puts("Enter your first name."); 19: scanf("%s", name); 20: 21: /* Вывод данных. */ 22: printf("Your age is %d.\n", age); 23: printf("Your name is %s.\n", name); 24: 25: return 0; 26: }   Enter your age. 29 and never olderl Enter your first name. Bradley Your age is 29.  Your name is Bradley.  326 Неделя 2. Основные вопросы 
МШШЗ B строке 15 этой программы используется функция fflush( ). Она имеет сле- дующий прототип:  int fflush( FILE *stream);  Аргументом stream служит имя потока, который следует очистить. В листинге 14.8 таким артументом выступает имя стандартного потока ввода stdin.  Примеры использования функции scanf( )  Наилучший способ изучить работу функции scanf() —— это почаще пользоваться ею. Функция обладает достаточно широкими возможностями, хотя иногда может поставить в ту- пик своим поведением. Смелее применяйте метод проб и ошибок при изучении ее работы. Листинг 14.9 демонстрирует некоторые необычные применения scanf( ). Скомпилируйте и запустите эту программу, а затем поэкспериментируйте с ней, внося изменения в строку фор- maTascanf().  Листинг 1 4.9. scanf .с — некоторые приемы ввода c клавиатуры с помощью функции scanf( )   1 /* демонстрация некоторых возможностей scanf(). */ 2 3: #include <stdio.h> 4: 5- int main( void ) 6 { 7 int il; 8: int i2; 9: long 11; 10: 11: double dl; 12: char bufl[80]; 13: char buf2[80]; l4:  15: /* Использование модификатора 1 для ввода чисел двойной точности. */ 16:  17: puts("Enter an integer and a floating point number."); 18: scanf("%ld %lf", &ll, &dl); 19: printf("\nYou entered %ld and %lf.\n",ll, d1); 20: puts("The scanf() format string used the l modifier to store"); 21: puts("your input in a type long and a type double.\n"); 22: 23: fflush(stdin); 24: 25: /* Использование ширины поля для отделения данных. */ 26: 27: puts("Enter a 5 digit integer (for example, S4321)."); 28: scanf("%2d%3d", &i1, &i2); 29: 30: printf("\nYou entered %d and %d.\n", i1, i2); 31: puts("Note how the field width specifier in the scanf() format"); 32: puts("string split your input into two values.\n");  День 14-й. Работа с экраном, принтером и клавиатурой 327 
33:  34: fflush(stdin); 35: 36: /* Использование исключенного пробела для разделения введенной */ 37: /* строки на две по пробелу между ними. */ 38: 39: puts("Enter your first and last names separated by a space."); 40: всапі("%[^ ]%s", buf1, buf2); 41: printf("\nYour first name is %s\n", buf1); 42: printf("Your last name is %s\n", buf2); 43: puts(“Note how [^ ] іп the scanf() format string, by excluding"); 44: puts("the space character, caused the input to be split."); 45: 46: return О; 47: }   Enter an integer and a floating point number. Peaunbmam 123 45.6789  You entered 123 and 45.678900. The scanf() format string used the l modifier to store your input in a type long and a type double.  Enter a 5 digit integer (for example,54321). 54321  You entered 54 and 321. Note how the field width specifier in the scanf() format string split your input into two values.  Enter your first and last names separated by a space. Gayle Johnson  Your first name is Gayle Your last name is Johnson Note how [^ ] іп the scanf() format string, by excluding the space character, caused the input to be split.  Эта программа начинается с объявления переменных в строках 7—13. Далее вы— полняется ввод нескольких различных групп данных. В строках 17—21 вводятся длинные целые числа (long int) и вещественные числа двойной точности (double). B стро— ке 23 вызывается функция fflush( ), которая выполняет очистку потока ввода от ненужных символов. В строках 27—28 вводится следующее значение — целое число из пяти цифр. Поскольку в этом случае спецификация формата с0держит ширину поля, число из пяти цифр разбивается на два числа: одно из двух цифр, и другое — из трех. В строке 34 снова вызыва— ется функция fflush( ) для очистки буфера клавиатуры. В последнем фрагменте (строки 36— 44) используется символ исключения. Строка 40 содержит спецификацию "%[" 1", которая указывает функции scanf() ввести строку, остановившись на пробеле. В результате введен— ная строка разбивается на две. Поэкспериментируйте с кодом программы, внося изменения и вводя различные данные.  328 Неделя 2. Основные вопросы 
Функция scanf () пригодна для реализации почти любых операций ввода, в особенности числовых значений. Строки удобнее вводить функцией gets( ). Тем не менее у программи— стов часто возникает необходимость в создании своих собственных функций ввода. Некото— рые примеры такого рода будут рассмотрены на занятии 18.      Рекомендуется J Не рекомендуется    Вывод на экран  Функции вывода на экран делятся на три категории, как и функции ввода: это функции вы— вода символов, вывода строк и форматированного вывода. Некоторые из них уже рассматрива— лись в общих чертах на предьщ/щих занятиях. Здесь дается более подробное их описание.  ВЫВОД символов  Стандартные библиотечные функции вывода символов С посьшают в поток вывода оди- ночные символы. Функция putchar() посылает выводимые данные в stdout (обычно ассо— циируемый с экраном). Функции fputc() и putc() посылают данные в поток, указанный в списке аргументов.  Функция putchar( ) Прототип функции putchar( ), определенный в файле stdio.h, имеет следующий вид: int putchar(int c);  Эта функция записывает символ с в поток stdout. Хотя в прототипе указан параметр типа int, все же следует передавать в функцию аргумент типа char. Можно, конечно, передать и значение типа int, если оно гарантированно не выходит за символьный диапазон (от О до 255). Функция возвращает только что записанный символ или EOF в случае ошибки. Использование putchar( ) уже демонстрировалось в листинге 14.2. Программа листин— га 14.10 отображает на экране символы с кодами ASCII от 14 до 127.  Листинг 1 4. 1 O. putchar .c -— демонстрация функции putchar( )   /* демонстрация putchar(). */  #include <stdio.h> int main( void )  {  int count;  for (count = 14; count < 128; ) putchar(count++);  ОЮФЧФШФШМН ..  ‚_в  День 14-й. Работа с экраном, принтером и клавиатурой 329 
11: return 0; 12: }  Р ?????1§??????????!"#$%&'()*+,-./0123456789:;<=>?@ABCDEF “№№… ' GHIJKLMNOPQRSTUVWXYZ [ \ 1 ^_„' abcdefghijklmnopqrstuvwxyz{ | }“ С помощью функции putchar( ) можно также отображать на экране и целые строки текста  (как это сделано в листинге 14.11), однако для этой цели больше полхоцят другие специали- зированные функции.   Листинг 1 4. 1 1 . putchar2 . с -— отображение строки с помощью функции putchar( )   /* Использование putchar() для вывода строк. */ #include <stdio.h> #define MAXSTRING 80  char message[] = "Displayed with putchar()."; int main( void )  CD " Ф U1 lb Ш N H .. о. .. .. .. о. о. о.  9: { 10: int count; 11: 12: for (count = 0; count < MAXSTRING; count++) 13: { 14: 15: /* Проверяется конец строки. Как только он найден, */ 16: /* записывается нулевой символ, н цикл заканчивается. */ 17: 18: if (message[count] == '\0') 19: { 20: putchar('\n'); 21: break; 22: } 23: else 24: 25: /* Если конец строки не достигнут, записать следующии символ. */ 26: 27: putchar(message[count]); 28: } 29: return 0; 30: }   РВЗБЛЫННШ Displayed with putchar( ) .  Функции putc() и fputc( )  Функции putc( ) и fputc( ) выполняют Одну и ту же задачу: они посылают Один символ в заданный поток вывола. Функция putc( ) является вариантом fputc( ), реализованным в виде  330 Неделя 2. Основные вопросы 
макроса. Макросы будут рассмотрены на занятии 21. Пока что ограничимся функцией fputc( ). Функция имеет следующий прототип:  int fputc(int с, FILE *fp);  Второй аргумент, FILE *fp, служит для указания потока вывода. (Подробнее об этом — на занятии 16.) Если в качестве потока задать stdout, то функция fputc( ) будет делать в точ- ности то же самое, что и putchar( ). Получается, что следующие два оператора эквивалентны между собой: putchar('x'); fputc( 'x' , stdout);  Функции вывода строк puts() и fputs()  Программам чаще приходится выводить на экран строки, чем отдельные символы. Для вывода строк служит библиотечная функция puts( ). Функция fputs() посылает строку в за- данный поток вывода. Если этот поток— стандартный stdout, то puts( ) и fputs() эквива- лентны. Функция puts( ) имеет следующий прототип:  int puts(char *cp);  Ее аргументом является указатель ср на первый символ отображаемой строки. Функция выводит на экран всю строку, кроме завершающего нулевого символа, и добавляет символ конца строки. Затем puts() возвращает положительное значение при успешном выводе, или EOF в случае ошибки. (Помните, что EOF —- это символическая константа, равная -1 и опреде- ленная в заголовочном файле stdio.h.) Функцию можно использовать для вывода строк лю- бого вида, как это продемонстрировано в листинге 14.12.  Листинг 1 4.1 2. puts . c -- использование функции puts( ) для отображения строк   1: /* демонстрация puts(). */ 2: 3: #include <stdio.h> 4: 5: /* Объявление в инициализация массива указателей. */ 6: 7: char *messages[5] = { "This", "is“, "a", "short", "message." }; 8: 9: int main( void ) 10: { 11: int x; 12: 13: for (x=0; x<5; x++) 14: puts(messages[x]); 15: 16: puts(“And this is the вид!"); 17: 18: return 0; 19: }   День 14-й. Работа с экраном, принтером и клавиатурой 331 
Р This езцпьтат is  a short message. And this is the end!  B этой программе объявляется массив указателей на строки. До сих пор вы еще не работали с такими объектами, но на следующем занятии этот пробел в вашем  образовании будет заполнен. В строках 13 и 14 печатаются все строки, помещенные в массив сообщений messages при его инициализации.  Функции форматированного вывода printf() M fprintf()  Пока что все рассмотренные функции выведа позволяли нам вывести на экран только отдельные символы или строки. А как насчет чисел? Для отображения чисел необходи- мо использовать библиотечные функции форматированного вывола и fprintf( ). C по- мощью этих же функций можно выводить также символы и строки. Наше официальное знакомство с функцией printf() состоялось на занятии 7, и с тех пор она применялась в очень многих примерах программ. В этом разделе вы узнаете o функции printf() все, чего еще не знали. По своему назначению функции printf( ) и fprintf() полностью аналогичны, только printf () всегда выполняет вывод в поток stdout, тогда как для fprintf() необхоцимо ука— зать поток вывода явным образом. Функция fprintf () используется в основном для вывода в файлы, поэтому она рассматривается на занятии 16. Функция printf() принимает переменное число аргументов, но не менее одного. Ее пер— вым и единственным обязательным аргументом должна быть строка формата, которая указы- вает функции printf( ), как оформлять выведимые данные. Необязательные аргументы после строки формата — это переменные и выражения, значения которых нужно вывести. Прежде чем вдаваться в поцробности, рассмотрим несколько характерных примеров:  I Оператор printf ( “Hello, world. " ); выводит сообщение Hello, world. на экран. Это пример вызова функции printf() с одним аргументом — строкой формата, кото— рая в данном случае содержит только текст для вывода на экран.  I Оператор printf ( “15d“ , i); отображает на экране значение целочисленной перемен— ной і. Строка формата содержит единственную спецификацию %d, указывающую, что нужно вывести Одно целое десятичное число. Второй аргумент і — это имя перемен- ной, значение которой нужно вывести.  I Оператор printf ( “%d plus %d equals %d" , a, b, a+b); отображает на экране со— общение 2 plus 3 equals 5, конечно, при условии, что целочисленные переменные а и Ь имеют значения соответственно 2 и 3. В этом вызове printf () используется четы- ре аргумента: строка формата, содержащая текст и спецификации вывела, а также две переменных и выражение, значения которых необходимо вывести. Теперь рассмотрим егроку формата функции printf() более педробно. Она может со- держать следующие элементы.  332 Неделя 2. Основные вопросы 
I Одну, две или ни одной спецификации формата вывода, указывающих, как выводить значения из списка остальных аргументов. Спецификация вывода начинается с симво- ла %, за которым идет один или несколько символов.  I Символы, не входящие в спецификации формата, которые отображаются на экране в том виде, в каком они приведены в строке. В третьем примере строка формата имеет вид “%d plus %d equals %d". Здесь присутст- вуют три спецификации %d, a все остальные символы в строке, включая пробелы, отобража- ются, как есть. Теперь давайте “препарируем” спецификацию вывода. Компоненты спецификации пере- числены ниже, а соответствующие объяснения даются чуть позже. Компоненты, стоящие в квадратных скобках, являются необязательными.  % [флаг] [шнрнна_поля] [ . [ точность] ] [1 ] символ_ форма та  Единственной обязательной частью спецификации вывода (кроме символа %) является символ формата. Символы формата и их значения перечислены в табл. 14.5.  Таблица 1 4.5. Символы формата в спецификациях вывода   Символ Значение   d, і Целое десятичное число со знаком u Целое десятичное число без знака о Целое восьмеричное число без знака х, х Целое шестнадцатеричное число без знака в нижнем (x) ипи верхнем (X) регистре с Одиночный символ (аргумент должен представлять его код ASCII) e, Е Вещественное число типа float ипи double в экспоненциальной форме (например,  123.45 выводится в виде 1.234500е+002). Если не указана точность, то выводится шесть цифр после точки. Регистром символов можно управлять, задавая спецификацию е ипи E  f Вещественное число типа float ипи double в дробно-десятичной форме (например, 123.45 выводится в виде 123.450000). Если не указана точность, то выводится шесть цифр после точки  g. G Используется формат е, E ипи f. Формат е ипи E применяется в том случае, если пока- затепь степени меньше -3 или больше, чем точность (по умолчанию 6). В противном случае используется формат f. Лишние нупи отбрасываются  n Ничего не выводится. Аргумент, соответствующий этой спецификации, должен быть указателем на переменную типа int. Функция printf() присваивает этой переменной количество выведенных симвопов  s Строка симвопов. Аргумент должен быть указателем на строку типа char. Строка выво- дится, пока не встретится нупевой симвоп или пока не будет выведено заданное копи- чество символов. Завершающий нулевой симвоп не выводится  % Вывод символа %   Непосредственно перед символом формата можно поставить модификатор 1. Он приме- няется только к спецификациям о, u, x, X, i, d и b. Этот модификатор указывает, что целочис- ленный аргумент имеет тип long, a не просто int. B приложении к символам формата е, Е, f, g, G он указывает, что аргумент имеет тип double. Если модификатор 1 поставить перед лю- бым другим символом формата, он будет проигнорирован. Наряду с 1 есть еще и модификатор 11. Этот модификатор работает точно так же, как и 1, только задает тип long long, a не long.  День 14-й. Работа с экраном, принтером и клавиатурой 333 
Спецификация точности состоит из десятичной точки (.), за которой может следовать число. Спецификация точности применима только к символам формата е, Е, f, g, G и s. Она указывает количество цифр после десятичной точки, которые следует вывести, а в случае спецификации s — количество выводимых символов. Если после десятичной точки в специ- фикации больше ничего нет, принимается точность 0. Спецификация ширины поля задает минимальное количество символов, которое должно быть выведено. Эта спецификация может иметь одну из следующих форм.  l Десятичное целое число, не начинающееся с 0. Выведенное значение дополняется пробелами слева, чтобы заполнить указанную ширину.  I Десятичное целое число, начинающееся с 0. Выведенное значение дополняется нулями слева, чтобы заполнить указанную ширину.  l Символ *. В качестве ширины поля берется значение следующего аргумента в списке, который должен иметь тип int. Например, если переменная w имеет тип int и значение 10, то оператор printf ( "%*сі" ‚ w, a) ; выведет на экран значение а с шириной поля 10. Если ширина поля не указана или ее недостаточно для вывода значения, то значение вы- водится с такой шириной, как необходимо. Последней необязательной частью строки формата printf() является флаг, который дол— жен стоять непосредственно после символа %. Имеется четыре допустимых флага.  - Выводимое значение следует выровнять по правому краю поля вместо левого, как это делается по умолчанию.  + Число должно обязательно выводиться со знаком + или — перед ним. Означает, что перед положительным числом обязательно должен стоять пробел.  # Применяется только к спецификациям х, Х или о. Указывает, что ненулевые шестна- дцатеричные значения (в случае x или Х) должны отображаться с символами 0x или ох, а восьмеричные (в случае 0) —— с нулем перед ними.  Строка формата функции printf() может представлять собой строковый литерал в двой— ных кавычках, а также строку с завершающим нулем где—то в памяти, указатель на которую передается в функцию. Возьмем, например, следующие два оператора: char *fmt = "The answer is %f . ”; printf(fmt, x); Они эквивалентны одному оператору: printf(”The answer is %f.”, x); Как уже говорилось на занятии 7, строка формата printf() может содержать специаль— ные символы для управления выводом. В табл. 14.6 перечислены наиболее распространенные из них. Если, например, включить в строку формата символ конца строки (\п), то вывод сле— дующих за ним значений будет выполняться с новой строки.  Таблица 1 4.6. Часто используемые специальные управляющие символы    Символ Значение Символ Значение \а Звуковой предупреждающий сигнал \\ Обратная косая черта \Ь Возврат на один символ назад \? Знак вопроса \п Перевод строки \’ Одинарная кавычка \t Горизонтальная табуляция \" Двойная кавычка   334 Неделя 2. Основные вопросы 
Функция printf( ) довольно сложна в применении. Лучший способ освоиться с ней —-—— это разбирать примеры и экспериментировать самостоятельно. В листинге 14.13 демонстрирует- ся целый ряд способов использования функции printf( ).  Листинг 1 4. 1 3. printf .с -— некоторые способы использования функции printf( ) /* демонстрация возможностей printf(). */   H  #include <stdio.h>  char *m1 = "Binary"; char *m2 = "Decimal"; char *m3 = "Octal"; char *m4 = ”Hexadecimal";  wmqmmbww  10: int main( void )  11: { 12: float d1 = 10000.123; 13: int n, f; 14: 15: 16: puts("Outputting a number with different field widths.\n"); 17: 18: printf("%5f\n", d1); 19: printf("%10f\n", d1); 20: printf("%15f\n", d1); 21: printf("%20f\n", d1); 22: printf("%25f\n", d1); 23: 24: puts("\n Press Enter to continue..."); 25: fflush(stdin); 26: getchar(); 27: 28: puts("\nUse the * field width Specifier to obtain field width"); 29: puts("from a variable in the argument list.\n"); 30: ` 31: for (n=5;n<=25; n+=5) 32: printf("%*f\n", n, d1); 33: 34: puts("\n Press Enter to continue..."); 35: fflush(stdin); 36: getchar(): 37: 38: puts(“\nInclude leading zeros.\n"); 39: 40: printf("%05f\n“, d1): 41: printf("%010f\n", d1): 42: printf("%015f\n”, d1): 43: printf("%020f\n", d1): 44: printf("%025f\n", d1);  День 14-й. Работа с экраном, принтером и клавиатурой 33 
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: }  puts("\n Press Enter to continue..."); fflush(stdin); getchar(); puts("\nDisplay in octal, decimal, and hexadecimal."); puts("Use # to precede octal and hex output with О and 0X.“); puts("Use - to left-justify each value in its field."); puts("First display column labels.\n"); printf("%-15s%-15s%-15s“, m2, m3, m4);  for (n = 1;n< 20; n++) printf("\n%-15d%-#150%-#15X", n, n, n);  puts("\n Press Enter to continue..."); fflush(stdin); getchar(); puts("\n\nUse the %n conversion command to count characters.\n"); printf("%s%s%s%s%n", m1, m2, m3, m4, &n);  printf("\n\nThe last printf() output %d characters.\n", n);  return О;   РШЩШШЮШ OUtPutting a number with different field widths.  336  10000.123047 10000.123047 10000.123047 10000.123047 10000.123047  Press Enter to continue...  Use the * field width specifier to obtain field width from a variable in the argument list.  10000.123047 10000.123047 10000.123047 10000.123047 10000.123047 Press Enter to continue...  Include leading zeros.  Неделя 2. Основные вопросы 
10000.123047 10000.123047 00010000.123047 0000000010000.123047 000000000000010000.123047  Press Enter to continue...  Display in octal, decimal, and hexadecimal. Use # to precede octal and hex output with 0 and ox. Use - to left-justify each value in its field. First display column labels.  Decimal Octal Hexadecimal 1 01 0x1 2 02 0x2 3 03 0x3 4 04 0x4 5 05 0x5 6 06 0X6 7 07 от 8 010 oxe 9 011 0x9 10 012 OXA 11 013 охв ‘ 12 014 охс 13 015 0XD 14 016 OXE 15 017 OXF 16 020 0x10 17 021 0x11 18 022 0x12 19 023 0x13  Press Enter to continue...  Use the %n conversion command to count characters. BinaryDecimalOctalHexadecimal  The last printf() output 29 characters.  Перенаправление ввода-вывода  Программа, в которой используются потоки stdin и stdout, может воспользоваться та- ким средством операционной системы, как перенаправление. Это дает возможность проде- лать следующее.  День 14-й. Работа с экраном, принтером и клавиатурой 337 
I Послать данные, предназначенные для вывода в поток stdout, не на экран, а в диско- вый файл или на принтер.  I Ввести данные, которые должны вводиться из потока stdin, не с клавиатуры, а из дискового файла.  Перенаправление ввода-вывода не задается в самом коде программы. Вместо этого оно указывается в командной строке при запуске программы. В системе DOS и командной строке Microsoft Windows, a также в системе UNIX для перенаправления используются символы < и >. Вначале обсудим перенаправление вывода. Помните вашу первую программу hello.c? B ней использовалась библиотечная функция printf( ), которая выводила на экран сообщение Hello, world. Как вы теперь знаете, функ- ция printf( ) посылает данные в поток stdout, поэтому ее вывод можно перенаправить. Вве- дите имя программы в командной строке, поставьте после него символ > и идентификатор нового места назначения:  hello > идентификатор  Таким образом, если ввести hello >prn, то программа выведет данные не на экран, а на принтер (идентификатор prn B системе DOS обозначает принтер, подключенный к порту LPT1:). A если ввести hello >hello.txt, то вывод пойдет в файл с именем hello.txt. Направляя вывод в файл, соблюдайте осторожность. Если файл с указанным именем уже существует, он будет удален и заменен новым. Если же такого файла не существует, то он будет создан. Для перенаправления вывода в файл можно также использовать символ >>. В этом случае, если файл уже существует, выводимые данные будут дописаны в его конец. Перенаправление вывода демонстрируется в листинге 14.14.  Листинг 1 4.14. redirectc — перенаправление ввода-вывода   1: /* Перенаправление stdin и stdout. */ 2: 3: #include <stdio.h> 4: 5: int main( void ) 6: { 7: char buf[80]; 8: 9: gets(buf); 10: printf("The input was: %s\n", buf); 11: return 0; 12: }   Эта программа вводит строку из stdin и посьшает ее в stdout, предваряя ее со- общением The input was:. После компиляции и компоновки программы запус- тите ее без перенаправления ввода-вывода, набрав в командной строке redirect. Если затем набрать строку I am teaching myself C, программа выведет на экран следующее:  Manna   The input was: I am teaching myself C  Если запустить программу командой redirect >test.txt, a затем ввести то же, что раньше, то на экране ничего не появится. Вместо этого на диске возникнет файл 13е513.13х13. Воспользуйтесь командой 13уре (или каким-либо ее эквивалентом) в системе DOS для ото- бражения на экране файла 13е513 . 13х13:  338 Неделя 2. Основные вопросы 
и  . ЧнЫН^"')" «A ' ::Ёрг’г. _т-дьг fit»: r _ "` “ \. L  g.  gn'qg’jt‘flxdff‘.  _! ‚п\п ` szga'si .`  "'*'" ”%?{ё'д \.И' Ё Ё‘т' “afar-fl 213““ , " .r - ' ' ` " .А . . ‹. и , _ `  эщщ“ {К- А" \  гат'тсчтат'тг  _ 43““ I _ ‚№1 .‘infik-vfi-JWafif № …, №№ . a» Y .  .."   „„ ' -‘.-д№х ` ‹ „_ __? -  type test.txt  B результате вы увидите, что файл содержит строку The input was: I am teaching myself C. Аналогичным образом, если запустить программу командой redirect >prn, то эта же строка будет напечатана на принтере (потому что prn —-—— это системное имя принтера в DOS). Еще раз запустите программу, на этот раз перенаправив вывод в файл TEST.TXT с помо- щью символа >>. Теперь файл не будет затерт и записан заново. Вместо этого в его конец бу- дет дописана еще Одна строка.  Перенаправление ввоца  Теперь давайте разберемся, как перенаправить ввод. Прежде всего нам понадобится файл исходных данных. Создайте файл с именем INPUT.TXT с помощью вашего текстового редак- тора и введите в него одну-единственную строку William Shakespeare. Теперь запустите программу листинга 14.14, введя следующую команду в командной строке: redirect < INPUT.TXT  Программа не будет ждать от вас ввода с клавиатуры. Вместо этого на экране немедленно появится следующее сообщение:  The input was: William Shakespeare  Поток stdin перенаправлен в файл INPUT.TXT, поэтому вызов функции gets() считывает одну строку из файла, а не с клавиатуры. Можно одновременно перенаправить и ввод, и вывод. Попробуйте запустить программу  следующей командой, которая перенаправит stdin в файл INPUT.TXT, a stdout— B файл JUNK.TXT:.  redirect < INPUT.TXT > JUNK.TXT  B определенных ситуациях перенаправление вв0да-вывода может оказаться очень полез- ным. Например, программа сортировки может в одних случаях сортировать данные из файла, a B других — данные, введенные с клавиатуры. Программа управления базой данных почто- вых адресов может отображать адреса на экране, сохранять их в файле или выводить на принтер для печати наклеек на конверты.   ("… Помните, что перенаправление потоков stdin и stdout -- это возможность операционной системы, а не самого языка С. Тем не менее, это еще Одно свидетельство гибкости и удобства потоков введа-выв0да. Более псдроб-  ную информацию o перенаправлении можно найти в документации опера- ционных систем.     Рекомендации по использованию функции fprintf()  Как уже говорилось, библиотечная функция fprintf () no своему назначению идентична функции printf( ), только в нее необходимо передавать имя потока вывода. В основном эта  функция используется для работы с дисковыми файлами, как это будет рассмотрено на заня- тии 16. А еще у нее есть два полезных применения, описанных ниже.  {‚ День 14-й. Работа с экраном, принтером и тавиатурой 339 
Вывод в поток stderr  B число стандартных потоков вывода С входит поток ошибок stderr. Сообщения об ошибках, выдаваемые программами, традиционно посылаются именно в этот поток, а не в stdout. Почему это так? Как вы только что узнали, вывод в поток stdout можно перенаправить в другое место, так что данные не буцут отображаться на экране. Если поток stdout перенаправить, то пользова- тель может не увидеть сообщений об ошибках, посланных программой в stdout. A вот поток stderr в отличие от stdout нельзя перенаправить —— он всегда ассоциируется с экраном (по крайней мере, в системе DOS, потому что в UNIX можно перенаправить и его). Направляя сообщения об ошибках в stderr, можно с уверенностью ожидать, что пользователь их обяза- тельно увидит. Это делается с помощью функции fprintf( ):  fprintf(stderr, “An error has occurred.");  Можно написать функцию для вывода сообщений об ошибках и вызывать ее вместо fprintf ( ), когда случается ошибка:  error_message(“An error has occurred.");  void error_message ( char *msg)  { }  Используя свою собственную функцию вместо непосредственного вызова fprintf ( ), можно даже улучшить гибкость работы программы (это еще одно преимущество структурно- го программирования). Например, в определенных случаях сообщения об ошибках могут по- сьшаться в файл или на принтер. Все, что для этого нужно, —— это внести изменения в функ- цию error_message( ), чтобы сообщения посылались в нужное место.  fprintf(stderr, шве):  Вывод на принтер из DOS и командной строки \Мпсіои/з  B системе DOS или Windows данные посылаются на принтер через стандартный поток stdprn. Ha [ВМ-совместимых персональных компьютерах этот поток ассоциируется с уст- ройством LP'I'I: (первым параллельным портом принтера). В листинге 14.15 приведен про- стой пример его использования.   (… Чтобы использовать поток stdprn. необходимо отказаться от совместимо- _ сти с ANSI B вашем компилятора. Обратитесь к документации компилято- ра за более подробной информацией. Поскольку лоток stdprn He опреде- лен B стандарте ANSI, He каждый компилятор его поддерживает.     Листинг 1 4. 1 5. printer . с — вывод данных на принтер   1: /* демонстрация вывода на принтер. */ 2: /* stdprn не определен в стандарте ANSI */ 3: #include <stdio.h> 4:  340 Неделя 2. Основные вопросы 
 5: int main( void ) 6: { 7: float f = 2.0134; 8: 9: fprintf(stdprn, "\nThis message is printed.\r\n"); 10: fprintf(stdprn, "And now some numbers:\r\n"); 11: fprintf(stdprn, "The square of %f is %f.”, f, f*f); 12: 13: /* Послать сигнал прогона страницы. */ 14: fprintf(stdprn, "\f"); 15: 16: return 0; 17: } This message is printed. Peaunbmam And now some numbers:  The square of 2.013400 is 4.053780.    (… Эта информация не вывоцится на экран. Она печатается на принтере. 1   Если на вашем компьютере есть принтер, подключенный к порту ЬРТ1:‚ то вы можете скомпилировать и запустить эту программу. Она печатает на странице три строки. В стро- ке 14 на принтер посьшается символ ”\f". Это управляющая последовательность для прогона страницы —— команда, которая заставляет принтер перевести печать на новую страницу.   РекомеНДуется »_ „спопьзУите функциют _, вписании программ пасы & … _ 013% E3“? защещены     Не рекомендуется      ‚\ :\№?&Ё:  . } АЕ "1 11%  … 2. ‹ т m “my?     Резюме  Это довольно длинное занятие было посвящено изучению важных вопросов ввода- вывода. Вы узнали, как в С используются потоки— объекты, благодаря которым любая входная и выходная информация всегда воспринимается просто как последовательность байт. Всего в стандарте ANSI определено три стандартных потока:  stdin Клавиатура stdout Экран stderr Экран  День 14-й. Работа с экраном, принтером и клавиатурой 341 
Данные, вводимые с клавиатуры, поступают в программу через поток stdin. C использо- ванием библиотечных функций С можно вводить данные с клавиатуры по одному символу, по одной строке или текст и числа вперемешку в форматированном виде. Ввод символов мо- жет быть буферизованным или не буферизованным, а также с дублированием символов в по- токе вывода или без него. Для вывода на экран обычно используется поток stdout. Как и ввод, вывод может выпол- няться по одному символу, по строке или в виде форматированных символов и чисел. Для вывода данных на принтер используется функция fprintf() и поток stdprn. При работе с потоками stdin и stdout можно перенаправить ввод и вывод программы. Входные данные могут поступать из файла, а не с клавиатуры, а выходные данные могут вы- водиться не на экран, а в файл или на принтер. Сообщения об ошибках лучше посылать не в поток stdout, а в stderr. Поскольку поток stderr всегда ассоциируется с экраном, это гарантирует, что пользователь обязательно уви- дит сообщения. Бьши также рассмотрены два потока, не определенные стандартом ANSI: stdprn Принтер stdaux Последовательный порт  Вопросы и ответы  Что произойдет, если попытаться получить входные данные из потока вывода? Написать нечто подобное в программе можно_, однако это не сработает. Например, если попытаться ввести данные из stdprn функцией scanf( ), то программа пройдет компиля- цию, но не введет никаких данных при ее выполнении, поскольку принтер неспособен вы- полнять ввод.  Что произойдет, если перенаправить одни из стандартных потоков? Это может впоследствии вызвать проблемы. Если перенаправить поток, то затем его нуж- но вернуть на место в той же программе. Многие рассмотренные нами функции используют стандартные потоки, причем одни и те же, поэтому если изменить направление потока в од- ном месте, это повлияет на работу всех функций ввода или вывода. Для примера установите поток stdout эквивалентным stdprn B одной из программ этого занятия и посмотрите, что произойдет.  Опасно ли использовать в программе функции, не определенные стандартом ANSI? Библиотеки ряда компиляторов содержат множество полезных функций, не определенных в стандарте ANSI. Если вы не планируете менять компилятор или переносить программу на другие аппаратно-программные платформы, то никаких проблем не будет. Если же програм- ма должна быть совместимой с различными компиляторами и платформами, то позаботьтесь о соблюдении ею стандарта ANSI.  Почему бы всегда не использовать функцию fprintf( ) вместо printf( )? И соответ- ственно f scanf ( ) вместо scanf ( )? При работе со стандартными потоками ввода и вывода следует пользоваться функция- ми scanf( ) и printf( ). Это более простые функции, избавляющие программиста от лиш- них хлопот.  342 Неделя 2. Основные вопросы 
Коллоквиум  В ЭТОМ КОЛЛОКВНУМе вам предлагаются контрольные ВОПРОСЫ ДЛЯ закрепления изученного  материала, а также упражнения дЛЯ приобретения практических навыков программирования.  Контрольные вопросы  1. Что такое потоки и для чего они используются B программах на С?  Какие из перечисленных ниже устройств являются устройствами ввода, а какие — вывода? а) принтер 6) клавиатура в) модем г) монитор д) жесткий диск Перечислите пять стандартных потоков и устройства, с которыми они ассоциированы. Какие потоки используются следующими функциями? а) printf() 6)put8() B)scanf() r)getSU д) fprintf() B чем разница между вводом символов из потока stdin с буферизацией и без нее?  В чем разница между вводом символов из потока stdin с дублированием B потоке вывода и без него?  Можно ли вернуть более одного символа функцией ungetc() B поток ввода за один раз? Можно ли вернуть в поток ввода символ конца файла ЕОР?  Как определяется конец строки при вводе символьных строк с помощью соответствую- щих функций?  Какие из перечисленных спецификаций ввода-вывода являются допустимыми? a)"%d" 6)"%4d" B)"%3i%c" r)"%q%d" д)"%%%і" e)"%91d"  10. B чем разница между потоками stderr и stdout?  День 14-й. Работа с экраном, принтером и клавиатурой 343 
Упражнения  1. 2. 3.  Напишите оператор для вывода на экран строки "Hello world". Используйте две различные функции, чтобы выполнить задание упражнения 1.  Напишите оператор для вывода строки "Hello Auxiliary Port" B стандартный вспомо- гательный поток.  Напишите оператор для ввода строки из не более чем 30 символов. Если встретится звез- дочка, прекратите ввод.  напишите оператор ДЛЯ вывода следующего текста:  Jack asked, "What is a backslash?" Jill said, "It is '\'"  Следующие упражнения не снабжены ответами, потому что возможных решений слиш-  ком много. Тем не менее, попробуйте их выполнить.  6.  7.  Самостоятельная работа. Напишите программу, которая перенаправляет файл на прин- тер по одному символу за раз.  Самостоятельная работа. Напишите программу, которая вводит данные из файла с по- мощью перенаправления, подсчитывает количество отдельных букв алфавита в файле и выводит результат на экран. (В приложении Е имеется подсказка, как это ёделать.)  Самостоятельная работа. Напишите программу для распечатки ваших файлов исходного кода С. Используйте перенаправление для ввода из файла и функцию fprintf( ) для печати.  Самостоятельная работа. Измените программу из упражнения 8 так, чтобы перед каж- дой строкой листинга выводился ее номер. (Приложение Е содержит подсказку, как это сделать.)  10. Самостоятельная работа. Напишите программу “машинописи”, которая вводит символы  с клавиатуры, дублирует их на экране и выводит на принтер. Программа должна подсчи- тывать строки и по мере необходимости прогонять страницу в принтере, начиная новую. Для завершения программы используйте нажатие функциональной клавиши.  344 Неделя 2. Основные вопросы 
Mus ° 1 игвпіі гампгшпяшепьип Неделя 2  Итоги  Вот и закончилась ваша вторая неделя изучения программирования на С. К этому времени вы уже должны вполне освоиться с языком С и знать почти все основные его операторы. В приведенной ниже программе используется Целый ряд средств языка, изученных на наших прошедших занятиях.   @ Если вам непонятен какой—то оператор программы, поищите необходимые объяснения в материале соответствующего занятия.     Листинг И2. 1 . weekz .c — итоговая программа второй недели занятий      1: /*--—— - - -- - --*/ 2: /* Иня: week2.c */ 3 /* Программа ввода данных про людей в количестве */ 4 /* до 100 чел. Программа выводит отчет на */ 5: /* основании введенных данных. */ 6 /*—— — —— ——*/ 7 /* — —*/ 8: /* included files */ 9: /* - - - ---*/   10: #include <stdio.h> 11: #include <stdlib.h>   12: 13: /*-- - - - -*/ 14: /* defined constants */ 15: /*—— — —*/   16: #define MAX 100 17: #define YES 1 18: #define N0 0    19: 20: /*-- - - */ 21: /* variables */ 22: /* - - */ 23: 24: struct record { 25: char fname[15+1]; /* имя + NULL */ 26: char lname[20+1]; /* фамилия + NULL */  27: char phone[9+1]; /* номер телефона + NULL */ 
28: long income; /* размер дохода */   29: int month; /* месяц рождения */ 30: int day; /* день рождения */ 31: int year; /* год рождения */ 32: }; 33: 34: struct record list[MAX]; /* объявление фактической структуры */ 35: 36: int last_entry = 0; /* общее количество записей */ 37: 38: /* */ 39: /* прототипы функций */ 40: /* */   41: int main(void); 42: void get_data(void); 43: void display_report(void); 44: int continue_function(void); 45: void clear_kb(void);    46: 47: /* */ 48: /* начало программы */ 49: /* */ 50: 51: int main( void ) 52: {  53: int cont = YES; 54: int ch;  55: 56: while( cont == YES ) 57: { 58: printf( “\n"); 59: printf( "\n MENU"); 60: printf( "\n ========\n"); 61: printf( "\nl. Enter names"); 62: printf( "\n2. Print report"); 63: printf( "\n3. Quit"); 64: printf( "\n\nEnter Selection ==> "); 65: 66: ch = getchar(); 67: 68: fflush(stdin); /* очистка буфера клавиатуры от лишних символов */ 69: 70: switch( ch ) 71: { 72: саве '1’: get_data(); 73: break; 74: case '2': display_report(); 75: break; 76: case ’3’: printf("\n\nThank you for using this program!\n"); 77: cont = NO;  346 Неделя 2 
    78: break; 79: default: printf("\n\nInvalid choice, Please select 1 to 3!"); 80: break; 81: } 82: } 83: return 0; 84: } 85: /* ----- -- * 86: * Функция: get_data() * 87: * Назначение: функция получает данные от пользователя. Это * 88: * делается до тех пор, пока не введено 100 записей * 89: * или пользователь не решает прекратить ввод. * 90: * Возвращает: ничего * 91: * Примечания: можно ввести 0/0/0 вместо даты рождения, * 92: * если она точно не известна. Считается, что в каждом * 93: * месяце 31 день. * 94: * --------- ---*/ 95: void get_data(void) 97: { 98: int cont; 99: {00: for ( cont = YES; last_entry < MAX && cont == YES;last_entry++ ) 101: { 102: printf("\n\nEnter information for Person %d.",last_entry+l ); 103: 104: printf("\n\nEnter first name: "); 105: gets(list[last_entry].fname); 106: 107: printf("\nEnter last name: "); 108: gets(list[last_entry].lname); 109: 110: printf("\nEnter phone in 123-4567 format: "); 111: gets(list[last_entry].phone); 112: 113: printf("\nEnter Yearly Income (whole dollars): "); 114: scanf("%ld", slist[last_entry].income); 115: 116: printf("\nEnter Birthday:"); 117: 118: do 119: 120: printf("\n\tMonth (0 - 12): "); 121: scanf("%d", slist[last_entry].month); 122: }while ( list[last_entry].month < 0 [I 123: list[last_entry].month > 12 ); 124: 125: do 126: { 127: printf("\n\tDay (0 - 31): “);  Итоги  347 
128: scanf("%d", &1ist[1ast_entry].day);    129: }while ( 1ist[1ast_entry].day < 0 || 130: 1ist[1ast_entry].day > 31 ); 131: 132: do 133: { 134: printf(“\n\tYear (1800 - 2003): “); 135: scanf("%d", &1ist[1ast_entry].year); 136: }while (1ist[1ast_entry].year != 0 && 137: (1ist[1ast_entry].year < 1800 {| 138: 1ist[1ast_entry].year > 2003 )); 139: 140: cont = continue_function(); 141: } 142: 143: if( 1ast_entry == MAX) 144: printf("\n\nMaximum Number of Names has been entered!\n"); 145: } 146: 147: /* t 148: * Функция: display_report() * 149: * Назначение: Функция отображает на экране отчет * 150: * Возвращает: ничего * 151: * Примечания: выводится не вся информация. для печати * 152: * отчета на принтере измените stdout на stdprn * 153: * */ 154: 155: void display_report() 156: { 157: long month_tota1 = 0, 158: grand_tota1 = 0; /* Итоговое число */ 159: int x, у; 160: 161: fprintf(stdout, “\n\n"); /* пропуск нескольких строк */ 162: fprintf(stdout, “\n REPORT"); 163: fprintf(stdout, "\n ========“); 164: 165: for( x = 0; x <= 12; x++ ) /* no всем месяцам, включая 0 */ 166: { 167: month_tota1 = 0; 168: for( у = 0; у ‹ 1ast_entry; у++ ) 169: { 170: if( 1ist[y].month == x ) 171: { 172: fprintf(stdout,"\n\t%s %s %s %1d",1ist[y].fname, 173: 1ist[y].1name, 1ist[y].phone,1ist[y].income); 174: month_tota1 += list[y].income; 175: } 176: } 177: fprintf(stdout, “\nTotal for month %d is %1d",x,month_tota1);  348 Неделя 2 
178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227:  !4поли  /*  ".-№№.-  grand_tota1 += month_total;  } fprintf(stdout, “\n\nReport totals:“);  fprintf(stdout, “\nTotal Income is %ld“, grand_total); fprintf(stdout, “\nAverage Income is %ld", grand~total/last_entry );  fprintf(stdout, "\л\л* * * End of Report * * t");   * Функция: continue_function() * Назначение: спрашивает пользователя, хочет ли он продолжать. * Возвращает: YES — если пользователь хочет продолжать, * N0 — если пользователь хочет прекратить работу * */   int continue_function( void )    { int ch; printf('\n\nDo you wish to continue? (Y)es/(N)o: '); fflush(stdin); ch = getchar(); while( ch != 'л’ && ch != 'N' && ch != ’у' && ch != 'Y' ) { printf('\n%c is invalid!“, ch); printf("\n\nPlease enter \'N\' to Quit or \'Y\' to Continue: "); fflush(stdin); /* очистка буфера клавиатуры (stdin) */ ch = getchar(); } c1ear_kb(); /* эта функция аналогична fflush(stdin) */ if(ch == 'n' || ch == 'N') return(NO); else return(YES); } /* i * Функция: с1еаг_КЬ() * * Назначение: очищает буфер клавиатуры от лишних символов. * * Возвращает: ничего * * Примечание: функцию можно заменить вызовом fflush(stdin); * а */ void clear~kb(void) 349 
228: {  229: char junk[80]; 230: gets(junk); 231: }   № По мере того, как вы узнаете все больше о языке С, ваши программы, похоже, увеличиваются в объеме. Хотя приведенная выше программа очень похожа на итоговую программу первой недели, в ней изменились несколько выполняемых функций и появились новые. Как и в программе первой недели, пользователь может ввести до 100 на- боров данных. Каждый набор представляет собой свод персональной информации об од- ном человеке. Вы, должно быть, заметили, что эта программа может вывести на экран от- чет прямо в процессе ввода данных, тогда как предыдущая программа не позволяла этого до окончания ввода. Также следовало заметить изменение в структуре, используемой для хранения данных. Структура определена в строках 24—32. Структуры часто используются для группирования данных об одном объекте, как это обсуждалось на занятии 11. В этой программе все данные об одном человеке объединены в структуру record. Многие элементы данных вам уже встре- чались, но есть и новые. В строках 25—27 объявляются три массива символов (строки) для хранения имени, фамилии и номера телефона. Обратите внимание, что все три массива име- ют длину а+1. Вы, наверное, помните из материала занятия 10, что лишняя ячейка в массиве нужна для хранения нулевого символа конца строки. В этой программе корректно используется область действия переменных (см. занятие 12). В строках 34 и 36 объявляются две глобальные переменные. Переменная в строке 36 имеет тип int и имя last_entry. Она используется для хранения общего количества введенных за- писей. В итоговой программе первой недели для этой цели использовалась переменная ctr. Другая глобальная переменная — это массив list[MAX] структур типа record. B каждой из остальных функций программы используются локальные переменные. Особо следует отме- тить переменные month_total, grand_total, x и у в строках 157—159 функции display_report( ). B итоговой программе первой недели они были глобальными переменны- ми. Но поскольку эти переменные используются только в функции display_report( ), их, ко- нечно же, лучше сделать локальными. В строках 70—81 используется управляющий оператор switch (см. занятие 13). Оператор switch заменяет несколько вложенных операторов if. . .else и при этом делает программу намного более удобочитаемой. В строках 72—79 выполняются различные задачи, определяе- мые выбором пункта меню. Обратите внимание, что на случай ввода несуществующего пунк- та меню в операторе switch имеется блок default. B функцию get_data( ) внесено несколько изменений по сравнению с программой первой недели. В строках 104 и 105 пользователя приглашают ввести строку символов. В строке 105 используется функция gets( ) (см. занятие 14) для ввода имени человека. Эта функция вводит строку и помещает ее в элемент list[last_entry] .fname. Вы должны помнить из материала занятия 11, как выполняется обращение к элементам структур, организованных в массив. Функция display_report() подверглась изменениям. В ней для вывода информации ис- пользуется функция fprintf ( ) вместо printf( ). Причина этого изменения проста: облегчить переход от выв0да на экран к выводу на принтер. Для этого достаточно в каждом вызове fprintf() изменить stdout на stdprn. Функция fprintf( ), a также потоки stdout и stdprn рассматривались на занятии 14. Помните, что эти потоки служат для вывода информации на экран и принтер соответственно. Изменилась также и функция continue_function( ) B строках 194—219. Теперь на вопрос о желании продолжить работу следует отвечать Y или N вместо 0 или 1. Это больше похоже  350 Неделя 2 
на человеческий язык, чем первоначальный вариант. Заметьте также, что для очистки потока от лишних введенных символов в строку 213 добавлена функция clear_kb() из листин- га 13.9. Кроме этого, буфер очищается от лишних оставшихся в нем символов с помощью вызова функции fflush( ).   @ Операторы в строках 229 и 230 можно заменить вызовом fflush(stdin), „ ничего не меняя в программе. А вот все возможные вызовы fflush() He получится заменить на clear_kb( ). Если вы еще не поняли, почему это так, повторите материал занятия 14.     В этой программе используется почти все, что вы изучили за первые две недели програм—. мирования на С. Как видите, многие средства и возможности языка, изученные в течение второй недели, способны сделать программы более эффективными, a процесс программиро- вания— менее трудоемким. В ходе третьей недели занятий мы постараемся узнать еще больше об этих средствах и возможностях.  Итоги 35 1 
sAms llcnnii саипсшпяшепьпп Неделя 3  Основные вопросы  Итак, закончилась ваша вторая неделя изучения С. Вы наверняка уже чувствуете себя бо- лее уверенно с этим языком после того, как хотя бы вкратце коснулись всех важнейших его средств. Но все-таки вам еще предстоит многому научиться, и для этого мы начинаем нашу третью неделю занятий.  Что дальше  В течение третьей недели вы завершите изучение языка С. Вы освоите рЯд новых, ранее вам неизвестных аспектов С, а также расширите и углубите свои знания некоторых тем первой и второй недели. После завершения третьей недели в вашем распоряжении окажется полный свод знаний о языке С. На занятии 15 рассматривается одна из сложнейших, но зато и самых важных тем С — указатели и приемы их использования. Как и на занятии 9, следует особенно тща- тельно проработать этот материал. На занятии 16 изучается работа с дисковыми файлами — совершенно необходимое средство для целого ряда приложений. Будет рассмотрено исполь- зование файлов для хранения информации и обмена ею. Занятие 17 посвящено средствам С для работы с текстом. На занятии 18 изучаются некоторые нетривиальные вопросы работы с функциями. Занятие 19 дает обзор целого ряда полезных стандартных функций из библиотеки языка С. На занятии 20 более подробно, чем раньше, рассматривается управление памятью. Занятие 21 освещает некоторые важные дополнительные вопросы програм- мирования на С, такие как аргументы командной строки и директивы препроцессора. 
  Дополнительные сведения об указателях  На занятии 9 вы уже ознакомились с указателями, одним из важнейших средств програм- мирования на С. На этом занятии мы пойдем дальше и расскажем о дополнительных средст- вах и приемах работы с указателями, которые предоставляют очень гибкие возможности про- граммирования. Будут рассмотрены следующие вопросы. Указатели на указатели Использование указателей с многомерными массивами Массивы указателей  Указатели на функции  Создание СВЯЗЗННЫХ СПИСКОВ ДЛЯ организации данных С ПОМОЩЬЮ указателей  Указатели на указатели  Как уже говорилось на занятии 9, указатель —— это числовая переменная, соцержащая ад- рес другой переменной. Указатель объявляется с помощью знака звездочки (*). Например, в следующем операторе объявляется указатель с именем ptr на переменную типа int:  int *ptr;  Для того чтобы указатель указывал на определенную переменную, ему присваивается ее адрес с помощью операции взятия адреса (&). Пусть переменная myVar имеет тип int. Тогда следующий оператор присваивает адрес myVar указателю ptr, отчего ptr становится указате- лем на myVar:  ptr = &myVar; C помощью операции ссылки по указателю (*) можно обратиться к значению той пере-  менной, на которую указывает указатель. Оба следующих оператора присваивают перемен- ной myVar значение 12: 
myVar = 12; *ptr = 12; Поскольку указатель и сам является числовой переменной, он располагается в памяти компьютера по определенному адресу. Поэтому можно создать указатель на указатель—— переменную, которая содержит адрес переменной-указателя. Вот как это делается: int myVar = 12; / * myVar имеет тип int. */ int *ptr = &myVar; /* ptr - указатель на myVar. */ int **ptr_to_ptr = &ptr; /* ptr_to_ptr - указатель на */ /* указатель на int.*/ Обратите внимание, что при объявлении указателя на указатель используется двойной  знак операции ссылки по указателю (**). Соответственно, он же используется для обращения к значению переменной по указателю на указатель:  **ptr_to_ptr = 12; Этот оператор присваивает значение 12 переменной myVar. A следующий оператор ото- бражает значение myVar Ha экране: printf( " %d" , **ptr_to_ptr) ; Если по ошибке воспользоваться одинарной, а не двойной операцией ссылки примени-  тельно к указателю на указатель, то ничего хорошего из этого не выйдет. Например, следую- щий оператор присваивает значение 12 указателю ptr:  *ptr_to_ptr = 12;  B результате ptr начинает указывать на некий адрес х___юоо 12 в памяти компьютера. Очевидно, это ошибка с потен- 188; циально неприятными последствиями. 1003 Обращение к значению по указателю на указатель 1883 еще называют многократном, или вложеннои ссылкои по 1006 указателю. На рис. 15.1 показаны соотношения между рег.—‚1007 переменной, указателем на нее и указателем на указатель. 1333 Ограничений на глубину вложенности указателей не су- 1010 ществует— можно объявить указатель на указатель на 10” 1012 указатель... и так до бесконечности, но на практике нет Ptr_t°_Ptr—>1o13 смысла использовать больше двух уровней вложенности, 1014  1015   так как возникающие при этом сложные конструкции слишком подвержены ошибкам. Зачем нужны указатели на указатели? Самое распро- страненное их применение связано с массивами указате- лей, которые рассматриваются на этом занятии чуть позже. Пример вложенной ссылки по указателю можно найти в листинге 19.5 занятия 19.  Рис. 15.1. Указатель на указатель  Указатели и многомерные массивы  На занятии 8 мы уже познакомились с особой связью, существующей между массивами и указателями. Говоря по существу, имя массива без квадратных скобок представляет собой указатель на первый элемент массива. Благодаря этому обращение к некоторым типам масси- вов легче выполнять через указатели, чем через индексы массива. Но все рассмотренные ра- нее примеры относились только к одномерным массивам. А как насчет многомерных?  День 15-й. Дополнительные сведения об указателях 355 
Вспомните, что многомерный массив объявляется таким образом, что каждому ею изме- рению соответствует одна пара квадратных скобой. Например, следующий оператор объявля- ет двумерный массив, содержащий восемь значений типа int:  int multi[2][4];  Этот массив можно представить себе как таблицу, состоящую из строк и столбцов _— в данном случае двух строк и четырех столбцов. Однако есть и другой способ представления многомерного массива, и он больше соответствует тому, как компилятор С фактически рабо- тает с массивами. Массив multi можно представить в виде массива из двух элементов, каж- дый из которых сам является массивом из четырех целых чисел. Если это не совсем понятно, взгляните на рис. 15.2. На нем объявление массива разобрано по частям.  4 1 2 3 Ф Ф H int multi[2] [4];  Puc. 15.2. Компоненты объяв- ления многомерного массива  Вот как следует интерпретировать составные части этого объявления.  1- Объявление массива multi. 2. B массиве multi —— два элемента. 3- Каждый из этих двух элементов содержит четыре элемента. 4. Каждый из этих четырех элементов имеет тип int.  Объявление многомерного массива следует читать слева направо, начиная с его имени, по одной паре скобок за раз. Как только прочитана последняя пара скобок (последнее измерение массива), переходите в начало объявления, чтобы узнать тип элементов массива. Согласно схеме ”массив массивов”, многомерный массив можно представить себе так, как показано на рис. 15.3.  multiIO]   __“ `.`. \.    Рис. 15. 3. Двумерный массив, представленный в виде массива массивов  Давайте снова вернемся к теме имен массивов как указателей (в конце концов, наше заня- тие посвящено именно указателям). Имя многомерного массива, как и одномерного, также является указателем на первый элемент массива. Итак, в нашем примере multi является ука- зателем на первый элемент двумерного массива, объявленного как int multi[2] [4]. A что  356 Неделя 3. Основные вопросы 
же в этом контексте представляет собой первый элемент multi? Это вовсе не элемент multi[0] [0] типа int, как можно было бы подумать. Вспомните, что multi —- это массив массивов, так что его первым элементом будет multi[0 ], который в свою очередь представ- ляет собой массив из четырех значений типа int (всего multi содержит два таких массива). Раз multi [ 0] представляет из себя массив, является ли его имя указателем? Конечно! Имя multi [ 0] указывает на первый элемент массива, т.е. multi[0] [0]. Вы можете удивиться, по- чему это вдруг multi [ 0] является указателем. Вспомните, что имя массива без скобок являет- ся указателем на его первый элемент. Выражение multi[0] можно воспринимать как имя элемента multi[0] [ 0] без второй пары квадратных скобок, поэтому оно подходит под опре- деление указателя. Если вы немного запутались, не огорчайтесь. Этот материал довольно сложен, и с наскока его не одолеешь. Возможно, следующие правила обращения с п-мерными массивами облегчат вам понимание этой темы.  I Имя массива с п парами квадратных скобок после него (и с индексами в скобках, ко- нечно же) имеет значение соответствующего элемента массива.  I Имя массива, после которого стоит меньше чем п пар скобок, представляет собой ука- затель на элемент массива.  Поэтому в нашем примере multi и multilO] являются указателями, а multi[0][0] —— элементом массива. Теперь давайте разберемся, на что же на самом деле указывают все эти указатели. В лис- тинге 15.1 объявляется двумерный массив, подобный тому, который использовался в приме- рах, а затем выводятся значения связанных с ним указателей. Программа также выводит ад- рес первого элемента массива.  Листинг 1 5. 1 . multi .с — связь между многомерным массивом и указателями   1: /* демонстрация связи указателей я многомерных массивов. */ 2: 3: #include <stdio.h> 4: 5: int multi[2][4]; 6: 7: int main( void ) 8: { 9: printf("\nmulti = %u", multi); 10: printf("\nmulti[0] = %u", multi[0]); 11: printf("\n&multi[0][0] = %u\n', amulti[0][0]); 12: 13: return 0; 14: }   multi = 4206592 Резцлыпат multi[0] = 4206592 amulti[0][0] = 4206592  В вашей системе программа не обязательно выведет адрес 4206592, но, тем не  диализ . менее, все три значения будут одинаковыми. Адрес массива multl равен адресу  День 15-й. Дополнительные сведения об указателях 357 
массива multi[0], и оба эти адреса вместе равны адресу первого целочисленного элемента массива multi [ 0 ] [ 0 ] . Если все три указателя имеют одно и то же значение, есть ли между ними какая—либо практически важная разница? Вспомните из материала занятия 9, что компилятор всегда зна— ет, на какой объект указывает тот или иной указатель. В частности, компилятор знает, каков размер этого объекта. Какие же размеры имеют элементы, указатели на которые мы использовали? В листин- ге 15.2 используется операция sizeof( ) для вычисления размера этих элементов в байтах.  Листинг 1 5.2. multisize . с — определение размеров элементов   /* Вычисление размеров элементов многомерного массива. */ #include <stdio.h> int multi[2][4];  int main( void )  {  printf(“\nThe size of multi = %u", sizeof(multi)); printf("\nThe size of multi[0] = %u", sizeof(multi[0])); 11: printf("\nThe size of multi[0][0] = %u\n", sizeof(multi[0][0])); 12: 13: return 0; 14: }  ‚...: ОФФЧФШЁЗШМН   При условии, что ваш компилятор использует четырехбайтные целые числа, программа выведет на экран следующее:  Р fllflflfll The size of multi = 32 ВЗЦЬ The size of multi[0] = 16 The size of multi[0][0] = 4  № В различных системах эти размеры могут отличаться. Если у вас 32—разрядная операционная система, то скорее всего числа будут именно такими: 32, 16 и 4,  потому что в подобных системах значение типа int занимает четыре байта. В более старых 16-разрядных системах результат может состоять из чисел 16, 8 и 2. Обдумайте этот результат. Массив multi содержит два массива, каждый из которых со— стоит из четырех целых значений. Каждое целое число занимает четыре байта. Восемь целых чисел по четыре байта — всего получается 32 байта. Вот откуда взялось первое число. Далее, multi[0] является массивом из четырех целых чисел. Четыре целых числа по че— тыре байта дают всего 16 байт. Вот мы и объяснили второе число. Наконец, multi[0] [0] представляет собой просто целочисленную переменную длиной 4 байта. Запомните эти размеры. Теперь вернемся к материалу занятия 9, на котором рассматрива— лась адресная арифметика. Компилятор С всегда знает размеры объектов, на которые указы— вают указатели. В адресной арифметике эти размеры учитываются. При инкрементировании указателя его значение увеличивается ровно настолько, чтобы он указывал на “следующий” объект того же типа и размера. Другими словами, указатель получает приращение в виде раз- мера объекта, на который он указывает.  358 Неделя 3. Основные вопросы 
В нашем примере имеем следующее Указатель multi указывает на массив из четырех це- лочисленных элементов общей длиной 16 байт. При иикрементировании multi он увеличива- ется на 16, т.е. на размер указываемого элемента. Если multi указывает на элемент multi[0], то (multi+1) указывает на multi[1 ]. Все это демонстрируется в листинге 15.3.  Листинг 1 5.3. multmath . c - адресная арифметика в многомерных массивах   /* демонстрация адресной арифметики указателей */ /* на многомерные массивы. */  #include <stdio.h> int multi[2][4];  int main( void )  {  ‚_. ФЮФЧФШЧЪЫМН  printf(“\nThe value of (multi) = %u", multi); 11: printf("\nThe value of (multi + l) = %u“, (multi+1)); 12: printf("\nThe address of multi[l] = %u\n", &multi[l]); 13: 14: return 0; 15: }  The value of (multi) = 4206592 Результат The value of (multi + 1) = 4206608  The address of multi[1] = 4206608   Точные значения адресов в вашей системе могут отличаться от приведенных, но ПШШЗ соотношения между ними должны остаться теми же. Инкрементирование multi на единицу увеличивает его адресное значение на 16 (или на восемь в старой 16-разрядной системе), отчего указатель начинает указывать на следующий элемент массива, multi[1]. Из этого примера вы убедились, что multi _— это указатель на multi[0 ]. В свою очередь, multi[0] также является указателем, но уже на multi[0] [0]. Поэтому multi представляет собой указатель на указатель. Для того чтобы обратиться через multi K элементам массива, следует пользоваться двойной ссылкой по указателю. Например, чтобы вывести на экран зна- чение multi [ 0] [0 ], можно воспользоваться любым из следующих трех операторов:  printf("%d", multi[0][0]); printf("%d", *multi[0]); printf("%d", **multi);  To же самое относится и к массивам с тремя и более измерениями. Например, трехмерный массив представляет собой массив элементов, каждый из которых сам является двумерным массивом, а его элементы —— это, в свою очередь, одномерные массивы. Весь этот материал о многомерных массивах и указателях может поначалу показаться сложным и запутанным. Пусть это вас не смущает. Работая с многомерными массивами, имейте в виду следующее: массив п-й размерности состоит из элементов, являющихся масси- вами п—1-й размерности. Когда п доходит до 1, элементы соответствующего массива стано- вятся переменными того типа, с которым объявлен массив. До сих пор мы использовали имена массивов, представляющие собой указатели- константы, которые нельзя было изменить. А можно ли объявить переменную-указатель на  День 15-й. Дополнительные сведения об указателях 359 
элементы многомерного массива? Вспомним предыдущий пример, в котором двумерный массив объявлялся следующим образом:  int multi[2][4];  Для объявления переменной-указателя ptr, которая может указывать на элементы multi (т.е. на массивы из четырех целых значений), запишем следующее:  int (*ptr)[41; Теперь сделаем так, чтобы ptr указывал на первый элемент multi: ptr = multi; Может возникнуть вопрос, зачем нужны круглые скобки в объявлении указателя. Дело в том, что квадратные скобки [] имеют более высокий приоритет, чем операция ссылки *. Поэтому следующий оператор объявляет массив из четырех указателей на значения типа int, a He указатель на массив: int *ptr[4]; Да, у нас есть полное право объявлять и использовать массивы указателей. Но это совсем не то, что мы собираемся сделать сейчас. Какое применение имеют указатели на элементы многомерных массивов? Как и в случае одномерных массивов, указатели используются для передачи массивов в функции. Это иллю-  стрируется в листинге 15.4, в котором многомерный массив передается в функцию двумя способами.  Листинг 1 5.4. ptrmulti .c —- передача многомерного массива в функцию через указатель   /* демонстрация передачи указателя на многомерный */ /* массив в функцию. */  #іпс1ибе <stdio.h>  void printarray_1(int (*ptr)[4]); void printarray_2(int (*ptr)[4], int n);  ФчФи'і-ЬШЮН .. .. с. с. с. с. со ..  9: int main( void ) 10: { 11: int multi[3][4]= { {1, 2, 3, 4 }, 12: { 5, 6, 7, 8 }, 13: { 9,10,11,12 } }; 14: 15: /* ptr — указатель на массив из четырех чисел типа int. */ 16: 17: int (*ptr)[4], count; 18: 19: /* Установить ptr на первый элемент массива multi. */ 20: 21: ptr = multi; 22: 23: /* При каждом проходе цикла ptr указывает на очередной элемент */ 24: /* (т.е. очередной 4—эпементный целый массив) массива multi. */  360 Неделя 3. Основные вопросы 
25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 3B: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:  for (count = 0; count < 3; count++) printarray_1(ptr++);  puts(“\n\nPress Enter..."); getchar(); printarray_2(mu1ti, 3); printf("\n"); return 0;  }  void printarray_1(int (*ptr)[4]) { /* Вывод элементов массива из четырех целых чисел. */ /* р указывает на переменную типа int. Необходимо приведение */ /* типа для присвоения указателю р адреса из ptr. */  int *p, count; P = (int *)Ptr:  for (count = 0; count < 4? count++) printf("\n%d", *p++); }  void printarray_2(int (*ptr)[4], int n) {  /* Вывод элементов n массивов по четыре числа в каждом. */  int *p, count; P = (int *)Ptr:  for (count = О; count < (4 * n); count++) printf("\n%d". *P++)? }  ФЧФШ-ЬШМН  \О  10 11 12  Press Enter...  День 15-й. Дополнительные сведения об указателях  361 
ФЧФШв-ЬШМР—д  \O  10 11 12  № В строках 11—13 этой программы объявляется и инициализируется массив целых чисел multi[3] [4]. B строках 6 и 7 находятся прототипы функций printarray_1( ) и printarray_2( ), предназначенных для вывода содержимого массива. В функцию printarray_1() (строки 36—47) передается всего один аргумент— указатель на массив из четырех целочисленных значений. Эта функция выводит на экран все четыре элемента переданного массива. При первом вызове функции printarray_1() из main() B строке 27 в нее передается указатель на первый элемент массива multi (его первый четырех— элементный массив). Затем функция вызывается еще два раза, причем указатель инкременти- руется так, чтобы указывать соответственно на второй и третий элементы multi. После трех вызовов функции все 12 элементов массива multi оказываются выведенными на экран. Во второй функции, printarray_2( ), применяется другой подход. В нее также передается указатель на массив четырех целочисленных значений. Но, кроме того, в нее передается це- лый аргумент, задающий количество элементов многомерного массива, на который фактиче— ски и указывает первый аргумент. Все содержимое массива multi выводится на экран Одним— единственным вызовом функции printarray_2( ) B строке 31. В обеих функциях для перебора целочисленных элементов массива используется обраще- ние по указателю. Запись (int *)ptr B обеих функциях (строки 43 и 54) может показаться непонятной. Выражение (int *) представляет собой явное преобразование, или приведение, типа, которое временно изменяет объявленный тип переменной на другой во время присваи— вания ее значения. Преобразование типа необходимо при присвоении значения ptr указателю р, поскольку они являются указателями на разные типы данных (р указывает на int, a ptr —— на массив из четырех чисел типа int). B языке С указателю одного типа нельзя присваивать значение указателя другого типа. Приведение типа приказывает компилятору следующее: “B этом операторе считать ptr указателем на int”. Преобразования типов рассматриваются более подробно на занятии 20.   Рекомендуется Не рекомендуется  «Помните что двойной знак ссылки по , ;;;Не забывайте о круглых скобках при чёука‘з‘ателю (* *) используется при объяв- Ё f объявлении указателя на массив. _ ` лейии указателей на указатели: , , „ ; для объявления указателя Ha массив CHM} %‹›Помните что при инкрементировании волов используется cnenyrommfi оператор {указателя он фактически увеличивается ; char (*1etters)[26], …на размер указываемого типа данных % А так объявляется массив указателей на ; ; ` „ „ , „; `:символы , ‹… ,; „;и: _ „ '“ ` “ ‘“ char *1е1313ег5[2б]; ~ ‹  ”1 ` ‹…. ‚х \ ` 2%, дм ` 5 “I“ ‹ ‚` ‹ эч \ _ ‚ <> » ‹ „ nu- . мдм—__— ‚ #» „_… __ „„„„. wM\A( .-—.«umum ч..-…но». .    Mme-rim» гы.—^ ‚м.  wpm—aw »”... …  "'-г)  {. »   "‘  - ` . q”) x ›, _- . 1 v а ‚ #1925?“ «. \ u h , _ f _ _ 4 R ;. ‹ › U ( ‚ ‹ ‚ ^ % Ё.… ам.—ммм: ..…. „ы-—„4 …… .... . - ь ' :— -„№…_ ___-«...мц-„м. „А „‹…. „… „`“—›  ….— »‚уфрц. №№… м- чп. ‹ - коим Mom.- до,—{ии ) »› ~o   362 Неделя 3. Основные вопросы 
Массивы указателей  На занятии 8 говорилось, что массив -——— это совокупность элементов данных одного типа под единым именем. Указатели тоже являются элементами данных определенных типов, по- этому их также можно организовывать в массивы. В некоторых ситуациях такие программ- ные конструкции могут оказаться очень мощными и полезными. Пожалуй, самым распространенным применением массивов указателей следует считать массивы символьных строк. Как вы узнали на занятии 10, строка представляет собой после- довательность символов, расположенных в памяти последовательно. Начало строки обозна- чается указателем на ее первый символ (указателем типа char), а конец строки -——— нулевым символом. Объявив и инициализировав массив указателей типа char, можно манипулировать сразу большим количеством строк с помощью этого массива. Каждый элемент массива ука- зывает на отдельную строку, поэтому их перебор, например, в цикле дает доступ ко всем строкам по очереди.  Указатели и строки: повторение  Напомним некоторые сведения из материала занятия 10, касающиеся размещения строк в памяти и их инициализации. Одним из способов размещения и инициализации строки являет— ся объявление массива типа char:  char message[] = "This is the message."; То же самое можно проделать и с помощью указателя типа char: char *message = "This is the message.";  Оба объявления эквивалентны. В каждом случае компилятор выделяет достаточное коли— чество памяти для хранения строки вместе с ее заключительным нулевым символом, причем выражение message является указателем на начало строки. А теперь рассмотрим следующие два объявления:  char message1[20]; ‚ char *message2;  . В первой строке объявляется массив типа char из двадцати символов, причем идентифика- тор messagel указывает на начало массива. Для массива выделено место в памяти, хотя и без инициализации, так что содержимое массива пока что не определено. Во второй строке объяв- ляется messageZ -—-— указатель типа char. Это объявление вообще не выделяет для строки ника- кой памяти -— только место для хранения указателя. Если необходимо создать строку и сделать так, чтобы message2 на нее указывал, то вначале придется выделить память для строки. На заня— тии 10 вы узнали, что для этой цели может использоваться функция распределения памяти malloc( ). Не забывайте, что каждая строка должна иметь отведенное для нее пространство в “памяти, созданное либо на этапе компиляции при ее объявлении, либо при выполнении про— граммы с помощью функции malloc( ) или аналогичной функции распределения памяти.  Массивы указателей типа Char  BOT вы и вспомнили все, что узнали ранее об указателях на строки. Как же объявить массив таких указателей? Следующий оператор объявляет массив из десяти указателей типа char:  char *message[10];  День 15 -й. Дополнительные сведения об указателях 363 
Каждый элемент массива message является указателем типа char. Как вы уже догадывае— тесь, объявление можно совместить с инициализацией и выделением памяти для егрок:  char *message[10] = { "one", "two", "three" }; Этот оператор выполняет следующие действия.  l Размешает в памяти массив из десяти элементов с именем message; каждый элемент массива message является указателем типа char.  l Выделяет пространство в памяти (неважно, где именно — это забота компилятора) и размещает в нем три заданные строки, завершая каждую нулевым символом.  l Инициализирует указатель message[0] адресом первого символа строки "one", message[1] ——- адресом первого символа "two", message[ 2] —-— первого символа "three“  Эти соотношения между массивом указателей и строками показаны схематически на рис. 15.4. Обратите внимание, что элементы массива от message[3] до message[9] не ини— циализированы никакими адресами.  message[0] message[1] message[2] message[3] message[4] message[5] message[6] message[7] message[8] messagel9]   Рис. 15.4. Массив указателей типа char A теперь обратите внимание на листинг 15.5, B котором используется массив указателей.  Листинг 1 5.5. message . с —- инициализация и использование массива указателей типа char   1: /* Инициализация пассива указателей типа char. */ 2: 3: {include <stdio.h> 4: 5: int main( void ) 6: { 7: char *message[8] = { "Four", "score", "and", "seven“, 8: "years", "ago,", "our", "forefathers" }; 9: int count; 10: 11: for (count = 0; count < 8; count++) 12: printf("%s ", message[count]); 13: printf("\n“); 14: 15: return 0; 16: }   Four score and seven ears a 0 our forefathers Рездпьшаш Y 9 '  364 Неделя 3. Основные вопросы 
№ В этой программе объявляется массив из восьми указателей типа char, который одновременно инициализируется восемью символьными строками (строки 7 и 8). В строках 11 и 12 с помощью цикла for элементы этого массива выводятся на экран. Вы уже, наверное, заметили, что манипулировать массивом указателей легче, чем собст- венно строками, на которые они указывают. Это преимущество становится еще более замет- ным B больших и сложных программах наподобие той, которая будет представлена позже на этом занятии. Там мы продемонстрируем, что самое большое преимущество предоставляется массивами указателей в том случае, коГДа используются функции. Передать массив указате- лей B функцию значительно проще, чем несколько строк. Для доказательства этого факта пе- репишем программу из листинга 15.5 так, чтобы для вывода строк использовалась функция. Модифицированная программа приведена в листинге 15.6.  Листинг 1 5.6. messagez .c — передача массива указателей в функцию   1: /* Передача массива указателей в функцию. */  2: 3: #include <stdio.h> 4: 5: void print_strings(char *p[], int n); 6: 7: int main( void ) 8: { 9: char *message[8] = { "Four", "score", "and", "seven", 10: "years", "ago,", "our", "forefathers" }; 11: 12: print_strings(message, 8); 13: return 0; 14: } 15: 16: void print_strings(char *p[], int n) 17: { 18: int count; 19: 20: for (count = 0; count < n; count++) 21: printf("%s ", p[count]); 22: printf("\n"); 23: }   Four score and seven years ago, our forefathers № Функция print_strings( ), начинающаяся в строке 16, принимает два аргумен- та. Один из них -— это массив указателей типа char, a второй —— количество эле- ментов B массиве. Таким образом, функция print_strings() может использоваться для вы- вода на экран строк из массива указателей. В разделе об указателях на указатели мы обещали вам пример их использования. Именно таким примером и является приведенная выше программа. В листинге 15.6 объявляется мас- сив указателей, причем имя массива является, как обычно, указателем на его первый элемент.  При передаче массива в функцию фактически передается указатель (имя массива) на указа- тель (первый элемент массива).   День 15-й. Дополнительные сведения об указателях 365 
Пример программы сортировки  Настало время разобрать довольно сложный пример. В листинге 15.7, приведенном далее, используется множество изученных нами приемов и средств программирования, в том числе массивы указателей. Эта программа принимает ряд строк текста с клавиатуры, распределяет память для них по мере ввода и хранит данные этих строк в массиве указателей типа char. Как только пользователь сообщает об окончании ввода строк, введя пустую строку, програм— ма сортирует строки по алфавиту и выводит их на экран. Представьте себе, что вы должны написать эту программу самостоятельно, “с чистого листа”. Для ее разработки целесообразно выбрать структурный подх0д. Вначале перечислим задачи, которые программа должна выполнять:  1. Ввод строк с клавиатуры по одной до тех пор, пока не будет введена пустая строка. 2. Сортировка строк текста по алфавиту. 3. Вывод отсортированных строк на экран.  Из этого списка понятно, что в программе должно быть как минимум три функции: одна для вв0да, одна для сортировки строк и еще одна для вывода на экран. Каждую из них можно разрабатывать отдельно. Что, например, должна делать функция ввода строк get_1ines( )? Составим еще один список:  1. Подсчет количества введенных СТРОК, возвращение ЭТОГО КОЛИЧССТ ва в вызывающую ПРО- грамму после ввода всех СТРОК.  2. Запрет ввода большего количества строк, чем предусмотренный в программе максимум. 3. Распределение памяти для каждой строки.  4. Запоминание местонахождения каждой строки путем помещения указателя на нее в массив.  5. Возвращение в вызывающую программу после ввода пустой строки.  Теперь обдумаем вторую функцию, предназначенную для сортировки строк. Назовем ее sort( ) — какое оригинальное имя, не правда ли? Воспользуемся самым простым и прямоли- нейным методом сортировки, в котором соседние строки сравниваются между собой и меня- ются местами, если вторая меньше первой. Точнее говоря, должны сравниваться две строки, указатели на которые соседствуют друг с другом в массиве указателей и, при необходимости, эти указатели должны меняться местами. Для полной сортировки необходимо пройти весь массив от начала до конца, сравнивая все пары строк и меняя их местами в случае необходимости. Массив из п элементов придется перебрать п—1 раз. Почему это необходимо? При каждом прохождении массива отдельно взятый элемент может сдвинуться в лучшем случае на одну позицию. Например, если строка, которая должна стоять в начале массива, на- ходится в самом его конце, то первый проход массива сдвигает ее на предпоследнее место, второй проход —— еще на одну позицию выше и т.д. Для перемещения этой строки в начало массива, где ей и место, потребуется п—1 переборов массива. Отметим, что это довольно неэкономичный и грубый метод сортировки. Вместе с тем он прост для понимания и реализации. Для коротких списков, которые должна сортировать на- ша программа, его вполне достаточно. Наконец, последняя функция выводит отсортированный список строк на экран. Вы уже фактически встречали ее в листинге 15.6. Потребовалась лишь небольшая переделка для того, чтобы функцию можно было использовать в листинге 15.7.  366 Неделя 3. Основные вопросы 
истинг 1 5.7. sort . с —- программа для чтения строк c клавиатуры, ортировки по алфавиту и вывода на экран   ' HIM—”J  a: `: bx … .=— … м ›- I. .. .. .. .. .. ..  /* Ввод списка строк с клавиатуры, сортировка и */ /* вывод строк на экран. */ #іпс1псе <stdlib.h> #include <stdio.h> #include <string.h>  #define HAXLINES 25  int get_lines(char *lines[]); void sort(char *p[], int n); void print_strings(char *р[]‚ int n);  char *lines[HAXLINES];  int main( void )  {  int number_of_lines; /* Чтение строк с клавиатуры. */ number_of_lines = get_lines(lines);  if ( number_of_lines < 0 ) { PUtS(" Memory allocation error"); exit(-1); }  sort(lines, number_of_lines); print_strings(lines, number_of_lines); return О;  int get_lines(char *lines[])  { int n = О; char buffer[80]; /* Временное хранилище для строки. */  puts("Enter one line at time; enter a blank when done.");  while ((n < HAXLINES) && (gets(buffer) != 0) && (buffer[0] != '\o'))  { if ((lines[n] = (char *)malloc(strlen(buffer)+1)) == NULL) return -1; strcpy( lines[n++], buffer ); } return n;  День 1 5 -й. Дополнительные сведения об указателях  367 
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:  } /* Конец get_lines() */  void sort(char *р[1‚ int n)  { int a, b; char *x;  for (a = 1; a < n; a++) for (b = 0; b < n-1; b++) if (strcmp(p[b], p[b+1]) > 0) {  x = Pib]; Pib] = Р[Ь+11: р[Ь+1] = х;`  }  void print_strings(char *p[], int n)  {  int count;  for (count = 0; count < n; count++) printf("%s\n“, p[count]); }   Enter one line at time; enter a blank when done. Paagnbmam  dOg apple zoo program merry  apple dog merry program zoo  должен быть включен заголовочный файл string .h.  B функции get_lines( ) ввод строк осуществлятся оператором while в строках 41 и 42:  while ((n < MAXLINES) && (gets(buffer) != 0) &&  368  (buffer[0] != '\o'))  Неделя 3. Основные вопросы  Будет полезно ознакомиться с этой программой поподробнее. В ней для различ- ных операций со строками используется несколько новых библиотечных функ- ций. Кроме краткого описания ниже, более подробную информацию о них можно найти в ма- териале занятия 17. В программе, которая использует эти функции для работы со строками, 
Условие цикла состоит из трех частей. Первая часть, п ‹ HAXLINES, представляет собой проверку, не введено ли макснмально допустимое количество строк. Во второй части,  géts(buffer) != 0, вызывается библиотечная функция gets( ), которая считывает строку с клавиатуры в buffer н проверяет, не произошла лн какая—нибудь ошибка. В третьей части, buffer[ 0] != ' \0 ’ , выполняется проверка, не равен лн первый символ введенной строки ну-  левому символу, что означало бы ввод пользователем пустой строки. Если какое—либо из условий не удовлетворяется, цнкл while прекращает работу, н управт ленне передается в вызывающую программу с возвращением колнчества введенных строк. Если все три условия выполняются, то срабатывает следующий оператор if B строке 44:  if ((lines[n] = (char *)malloc(strlen(buffer)+l)) == NULL)   B этом операторе вызывается функцня malloc() для вьшелення памятн под только что введенную строку. Функция strlen( ) возвращает длину строки, переданной ей как аргумент, и это значение увеличивается на единицу, чтобы выделенной памятн хватило на строку и ее завершающий нулевой символ. Выражение (char *) непосредственно перед вызовом malloc() в строке 44 представляет собой приведение типа, которое указывает, как следует воспрнннмать возвращаемый функцией malloc( ) указатель … в данном случае как указатель тнпа char. Прнведенне тнпов будет рассмотрено более подробно на занятнн 20. Как известно, бнблнотечная функцня ша11ос() возвращает указатель. Затем этот указа— тель прнсванвается соответствующему элементу масснва. Если функция malloc() возвращает NULL, оператор if в цикле while возвращает управление в вызывающую программу вместе со значением -—1. B самой вызывающей программе — функцнн main( ) ~— возвращаемое нз get_lines() значенне проверяется. Если оно меньше нуля, то строки 23—27 ннформнруют пользователя об ошибке распределения памяти и завершают работу программы. Если распределение памятн бьшо выполнено успешно, то в строке 46 с помощью функции strcpy() введенная строка копнруется нз временного храннлнща buffer B участок памятн, вы- деленный функцией malloc( ). Затем цикл while повторяется, выполняя ввод очередной строки. К моменту возвращения управления из функцнн get_lines( ) в main” завершается выпол— ненне следующих операций (при условии, что не пронзоиша ошнбка распределения памяти): I C клавиатуры считывается последовательность строк текста, которые помещаются в память в виде строк с завершающнм нулевым снмволом. I Массив lines[] заполняется указателями на все введенные строкн. Порядок указате— лей в массиве соответствует порядку ввода строк. I Переменная number_of__lines получает значенне количества введенных строк. Пора начннать сортировку. Обратите внимание, что сами строки ннкуда перемещать не придется. Изменяться будет только порядок указателей в массиве lines[ ]. Функция сорти— ровкн sort( ) содержит вложенные цнклы for — однн внутрн другого (строкн 57—68). Внеш— ний цикл выполняется number_of_lines - 1 раз. При каждом прохождении внешнего цнкла во внутреннем цнкле перебирается весь масснв указателей, н выполняется сравненне строк номер п и номер п+1 от n = 0 до п = number_of__lines - l. Для этого сравнения в строке 61 вызывается бнблнотечная функцня strcmp( ), в которую передаются два указателя на сравни— ваемые строки. Функция strcmp( ) возвращает одно из трех следующих значеннй.  I Положительное число, если первая строка больше второй (в алфавитном смысле). I Ноль, если две строки равны. I Отрицательное число, еслн вторая строка больше первой.  День 15-й. Дополнительные сведения об указателях 369 
Если функция возвращает положительное число, то предыдущая строка в массиве “больше” последующей (по алфавиту), а это неправильный порядок строк, поэтому их нужно поменять местами. Для этого служит временная переменная tmp. Обмен выполняется в строках 63—65. После завершения функции sort() указатели в массиве lines[] оказываются упорядо- ченными по алфавиту: указатель на самую “младшую” строку находится в lines[O], указа— тель на следующую по старшинству строку— в lines[l] и т.д. Пусть, например, введены следующие пять строк (именно в таком порядке): dog apple zoo program merry  Размещение строк и указателей в памяти перед вызовом функции sort( ) показано схема- тически на рис. 15.5, а их размещение после возврата из sort( ) — Ha рис. 15.6. Наконец, для вывода на экран отсортированного массива строк вызывается функция print_strings( ). Эта функция должна быть хорошо знакома вам по предыдущим примерам.  d o lines[O] lines[O] lines[l] lines[l] lines[Z] lines[Z] lines[3] lines[3]  lines[4] lines[d]   m e r r m e r r Puc. 15.5. Порядок указателей на строки Рис. 15.6. Алфавитный порядок указате- после ввода, но до сортировки лей на строки после сортировки  Программа из листинга 15.7 —— самая сложная из всех, которые до сих пор встречались в нашей книге. В программе используется множество средств языка С, изученных на предыду— щих занятиях. С помощью приведенных пояснений вы должны без труда разобраться, как ра- ботает эта программа. Если же отдельные места окажутся непонятными, повторите материал соответствующих разделов книги.  Указатели на функции  Указатели на функции —— это одно из дополнительных средств вызова функций, что на первый взгляд может показаться странным: ведь указатели содержат адреса переменных, а не функций, не так ли? И да, и нет. Указатели действительно содержат адреса, но не обязательно адреса перемен- ных. При выполнении программы код каждой ее функции располагается в памяти по опреде- ленному адресу. Указатель на функцию содержит начальный адрес функции —— ее точку входа. Зачем нужны указатели на функции? Как уже упоминалось, это дополнительное гибкое средство вызова функций. С помощью указателей программа может выбрать вызов одной из нескольких функций по какому-либо критерию или условию.  370 Неделя 3. Основные вопросы 
‚Объявление указателя на функцию  Как и любая другая переменная, указатель на функцию должен быть объявлен, чтобы его можно было использовать. Общая форма этого объявления такова:  тип ( *ptr_to_func) (список_параметров) ;  Этот оператор объявляет переменную ptr_to_func указателем на функцию, возвращаю- щую значение заданного типа и принимающую указанный список_параметров. Вот несколь- ко конкретных примеров: int (*func1)(int x); void (*func2)(double у, double 2); char (*func3)(char *p[]); void (*func4)();  B первой строке объявляется указатель funcl на функцию, принимающую один аргумент типа int и возвращающую значение того же типа. Во второй строке объявляется указатель func2 на функцию с двумя аргументами типа double и возвращаемым значением типа void (т.е. фактически без возвращаемого значения). B третьей строке объявляется указатель func3 на функцию, которая принимает массив указателей типа char и возвращает значение типа char. B последней строке переменная func4 объявлена как указатель на функцию без аргу- ментов и с пустым типом (void) возвращаемого значения. Зачем нужны скобки вокруг имени указателя? Почему бы не записать, например, следующее:  int *func1(int x);  Причина заключается в приоритете операции *. Он сравнительно низок— ниже, чем у скобок со списком параметров. Поэтому приведенный выше оператор без скобок вокруг име- ни указателя является объявлением функции funcl, возвращающей указатель типа int, a во— все не объявлением указателя на функцию. (Функции, возвращающие указатели, рассматри— ваются на занятии 18.) При объявлении указателя на функцию всегда заключайте его имя и знак * в круглые скобки во избежание неприятностей.  Инициализация и использование указателя на функцию  Чтобы указатель действительно указывал на что—то, его необх0димо не только объявить, но еще и инициализировать. Это “что-то” —— конечно же, функция. Никаких особых требований к функции, на которую указывают, не предъявляется. Единственное, что должно строго соблю- даться, —— это соответствие возвращаемого типа и списка параметров функции. Например, в следующем фрагменте объявляется и определяется функция, а также указатель на эту функцию:  float square(float x); // Прототип функции. float (*ptr)(float x); // Объявление указателя. float square(float x) // Определение функции. { return x * х; }  Поскольку функция square() и указатель ptr имеют совпадающие списки параметров и типы возвращаемых значений, ptr можно инициализировать следующим образом:  ptr = square;  День 15—й. Дополнительные сведения об указателях 371 
Затем можно вызвать функцию с использованием указателя: answer = ptr(x);  Как видите, все просто. Для практики скомпилируйте и запустите листинг 15.8, в котором объявляется и инициализируется указатель на функцию. Затем эта функция вызывается дваж- ды —— один раз по имени, второй раз по указателю. Оба вызова дают один и тот же результат.  Листинг 1 5.8. ptr.c — вызов функции по указателю   /* Объявление и использование указателя на функцию. */  #include <stdio.h>  1: 2: 3: 4: 5: /* Прототип функции. */ 6: 7: double square(double x); 8:  9° /* Объявление указателя. */  10: 11: double (*ptr)(double x); 12: 13: int main( void ) 14: { 15: /* Инициализация р адресом square(). */ 16: 17: ptr = square; 18: 19: /* Вызов square() двумя способами. */ 20: printf("%f %f\n", square(6.6), ptr(6.6)); 21: return 0; 22: } 23: 24: double square(doub1e x) 25: { 26: return х * x; 27: }   43.560000 43.560000 Результат  @… Из—за особенностей машинного представления чисел некоторые веществен- - Hble значения могут отображаться не совсем так, как были введены. Напри- мер, правильное значение 43.56 может быть выведено в виде 43.559999.      _В строке 7 объявляется функция square( ), a B строке 11 — указатель ptr Ha функцию, принимающую аргумент типа double и возвращающую значение типа double. Таким образом, указатель и функция полностью совместимы. В строке 17 ука- затель ptr устанавливается равным square. Заметьте, что при этом ни ptr, ни square не употребляются со скобками. В строке 20 на экран выводятся значения, полученные вызо- вами square() и ptr( ).  372 Неделя 3. Основные вопросы 
Итак, имя функции без скобок является указателем на функцию. (Проведите аналогию с массивом, имя которого без скобок— тоже указатель.) Так зачем же нужно объявлять от- дельный указатель на ту же самую функцию? Дело в том, что имя функции само по себе — указатель- константа, которую нельзя изменить. (Снова напрашивается параллель с масси- вом.) В противоположность этому указатель является переменной, и ее значение можно из- менить как угодно. В частности, в разные моменты времени такая переменная может указы- вать на разные функции, если в этом возникнет необходимость. В листинге 15.9 вызывается функция 0 целочисленным аргументом. В зависимости от значения аргумента функция инициализируег указатель так, чтобы он указывал на одну из трех разных функций. Затем через этот указатель выполняется вызов соответствующей функ- ции. Каждая из трех упомянутых функций выводит на экран свое сообщение.  Листинг 1 5.9. ptr2 . с — использование указателя для вызова разных функций по обстоятельствам   /* Вызов нескольких разных функций через указатель. */ #include <stdio.h> /* Прототипы функций. */ void func1(int x); void one(void);  void two(void); 10: void other(void);  фЧФШдШЮР-і со II II II II II II II  \;) II  11: 12: int main( void ) 13: { 14: int nbr; 15: 16: for (;;) 17: { 18: puts("\nEnter an integer between 1 and 10, 0 to exit: “); 19: scanf("%d", &nbr); 20: 21: if (nbr == 0) 22: break; 23: func1(nbr); 24: } 25: return 0; 26: } 27: 28: void funcl(int val) 29: { 30: /* Указатель на функцию. */ 31: 32: void (*ptr)(void); 33: 34: if (val == 1) 35: ptr = one; 36: else if (val == 2)  День 15-й. Дополнительные сведения об указателях 373 
37: ptr = two;  38: else 39: ptr = other; 40: 41: ptr(); 42: } 43: 44: void one(void) 45: { 46: puts("You entered 1."); 47: } 48: 49: void two(void) 50: { 51: puts(“You entered 2."); 52: } ' 53: 54: void other(void) 55: { 56: puts("You entered something other than 1 or 2."); 57: }   Enter an inte er between 1 and 10, 0 to exit: Peaunbman 2 9  You entered 2.  Enter an integer between 1 and 10, 0 to exit: 9 You entered something other than 1 or 2.  Enter an integer between 1 and 10, 0 to exit: 0  В этой программе используется бесконечный цикл, который начинается в стро- ке 16. Его выполнение продолжается, пока пользователь не введет 0. Если вво—  дится ненулевое значение, оно передается в функцию funcl( ). Отметим объявление указате- ля ptr Ha функцию в строке 32 внутри funcl( ). Это локальное объявление, т.е. указатель ptr недоступен из других частей программы, кроме функции funcl( ). Ho поскольку в других частях программы он и не нужен, значит, его локальное объявление правильно. Функция funcl( ) использует переданное в нее значение для выбора одной из функций и помещения ее адреса в ptr (строки 34—39). После этого в строке 4| выполняется вызов ptr( ), т.е. соответствующей функции через него. Конечно, эта программа годится только для иллюстрации. Того же результата можно было бы легко добиться и без указателя на функции. А теперь изучим еще один способ использования указателя для вызова различных функций: передачу его в качестве аргумента другой функции. Листинг 15.10 представляет собой модификацию листинга 15.9.  '  374 Неделя 3. Основные вопросы 
Листинг 1 5.1 О. passptr.c — передача указателя как аргумента в функцию   /* Передача указателя в функцию в качестве аргумента. */  #include <stdio.h>  1 2 3 4 5: /* Прототипы функций. Функция func1() принимает один */ 6 /* аргумент - указатель на функцию без параметров и */ 7 /* возвращаемого значения. */ 8. 9: void func1(void (*p)(void)); 10: void one(void); 11: void two(void); 12: void other(void);  13: 14: int main( void ) 15: { 16: /* Указатель на функцию. */ 17: void (*ptr)(void); 18: int nbr; 19: 20: for (;;) 21: { 22: puts("\nEnter an integer between 1 and 10, 0 to exit: “); 23: scanf("%d", &nbr); 24: 25: if (nbr == 0) 26: break; 27: else if (nbr == 1) 28: ptr = one; 29: else if (nbr == 2) 30: ptr = two; 31: else 32: ptr = other; 33: func1(ptr); 34: } 35: return 0; 36: } 37: 38: void func1(void (*p)(void)) 39: { 40: PM; 41: } 42: 43: void one(void) 44: { 45: puts("You entered 1."); 46: } 47: 48: void two(void) 49: {  День 15 -й. Дополнительные сведения об указателях 375 
50: puts("You entered 2.");  51: } 52: 53: void other(void) 54: { 55: puts("You entered something other than 1 or 2."); 56: }   Enter an inte er between 1 and 10, 0 to exit: Резцпьшаш 2 9  You entered 2.  Enter an integer between 1 and 10, 0 to exit: 11 You entered something-other than 1 or 2.  Enter an integer between 1 and 10, 0 to exit: 0  № Обратите внимание на различия между листингами 15.9 и 15.19. Объявление указателя на функцию переместилось в функцию шаіп( ), в строку 17, где оно необходимо. Именно в коде функции шаіп( ) теперь инициализируется указатель таким обра- зом, чтобы он указывал на нужную функцию, в зависимости от введенного пользователем значения (строки 25—32). Затем инициализированный указатель передается в функцию func1( ). Собственно говоря, в листинге 15.10 эта функция практически не нужна. Все, что она делает, —- это вызывает другую функцию через указатель ptr. Напомним, что и эта про- грамма предназначена для чисто иллюстративных целей. Тем не менее, те же самые принци- пы и приемы можно использовать и в настоящих, практических программах, как это будет показано в следующем разделе. Одна из задач, для которой указатели на функции могут оказаться полезными, — это сортировка. Иногда в зависимости от ситуации приходится применять сортировку по раз- личным правилам. Например, один раз необходимо сортировать по алфавиту, а в другой раз — в порядке, обратном алфавитному. С помощью указателей программа сможет кор- ректно вызвать нужную функцию сортировки. Точнее говоря, обычно речь идет o различ- ных функциях сравнения. Снова обратимся к листингу 15.7. Порядок сортировки в функции sort( ) фактически оп- ределяется значениями, которые возвращает библиотечная функция strcmp( ). Она сообщает программе, является ли та или иная строка “большей” или “меньшей”, чем другая. А что если написать две функции сравнения: одну для сортировки по алфавиту (считая А меньше Z), a другую —— для сортировки в обратном порядке (считая Z меньше А)? Такая программа могла бы спросить пользователя, какой порядок сортировки он желает и в зависимости от этого вы- звать нужную функцию сравнения. Листинг 15 .1 1, являясь модификацией листинга 15.7, реа- лизует эту возможность.  Листинг 1 5. 1 1 . ptrsort.c — управление порядком сортировки c помощью указателей на функции 1 /* Ввод списка строк с клавиатуры, сортировка no */  2: /* возрастанию или убыванию, вывод на экран */ 3: /* on the screen. */   376 Неделя 3. Основные вопросы 
ФЧФШ-ЁЬ  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:  #include <stdlib.h> #include <stdio.h> #include <string.h>  #define MAXLINES 25  int get_lines(char *lines[]); void sort(char *p[], int n, int sort_type); void print_strings(char *p[], int n); int alpha(char *pl, char *p2); int reverse(char *pl, char *p2);  char *lines[MAXLINES];  int main( void )  {  }  int number_of_lines, sort_type; /* Чтение строк с клавиатуры. */ number_of_lines = get_lines(lines);  if ( number_of_lines < 0 ) { puts("Memory allocation error"); exit(-1); }  puts("Enter 0 for reverse order sort, 1 for alphabetical:" ); scanf("%d", &sort_type);  sort(lines, number_of_lines, sort_type); print_strings(lines, number_of_lines); return 0;  int get_lines(char *lines[])  {  int n = 0; char buffer[80]; /* Временное хранилище для строки. */  puts("Enter one line at time; enter a blank when done.");  while (n < MAXLINES && gets(buffer) != 0 && buffer[0] != ’\0') { if ((lines[n] = (char *)malloc(strlen(buffer)+1)) == NULL) return -1; strcpy( lines[n++], buffer ); }  return n;  День 15-й. Дополнительные сведения об указателях 377 
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: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99°  378  100: 101: 102: 103:  } /* Конец get_lines() */ void sort(char *p[], int n, int sort_type) { int a, b; char *x; /* Указатель на функцию. */  int (*compare)(char *sl, char *s2);  /* Присваивание указателю адреса нужной функции сравнения */ /* в зависимости от аргумента sort_type. */  compare = (sort_type) ? reverse : alpha;  for (a = l; a < n; a++) { for (b = 0; b < n-l; b++) { if (compare(p[b], p[b+l]) > 0) { X = Plb]; Plb] = p[b+l]; p[b+l] = X;  }  } } /* Конец sort() */  void print_strings(char *p[], int n)  {  int count;  for (count = 0; count < n; count++) printf(”%s\n”, p[count]);  }  int alpha(char *pl, char *p2) /* Сравнение в алфавитном поряцке. */  { return(strcmp(p2, pl))3  }  int reverse(char *р1, char *p2) /* Сравнение в порядке, обратном алфавитному. */  { return(strcmp(pl, p2))3  }  Неделя 3. Основные вопросы 
Enter one line at time; enter a blank when done. Peaunbmam Roses are red  Violets are blue C has been around, But it is new to you!  Enter 0 for reverse order sort, 1 for alphabetical: O  Violets are blue Roses are red C has been around, But it is new to you!  B строках 32 и 33 функции main() пользователя спрашивают, в каком порядке выполнять сортировку. Введенное значение помещается в переменную sort__type. Оно затем передается в функцию sort() вместе с другими данными, как было описано в листинге 15.7. В функцию sort() внесено несколько изменений. В строке 64 объ- является указатель compare на функцию, принимающую два указателя на строки в качестве аргументов. В строке 69 указатель compare устанавливается равным адресу одной из двух но- вых функций, добавленных в программу. Это делается в зависимости от значения sort__type. Две новые функции — это alpha() и reverse( ). Функция alpha() вызывает библиотечную функцию strcmp( ) точно так же, как в листинге 15.7, тогда как reverse( ) передает в нее па— раметры в обратном порядке, чтобы получить противоположную сортировку.  Рекомендуется Не рекомендуется  „ омните‚ {Что для объявления указателя; He используйте указатель не инициа— ,; функцию его обязательно нужно взять лизировав его перед этим. ‘_5` круглые скобки.:йнй’ё ’ :*“ЁЁЦ ` не используйте указатель на функцию, обьявление указателя; Ha функцию объявленный со списком аргументов или 3 аргументов, возвращающую символ:… ‘ ; возвращаемым значением, которые от—  %%“ (“КРОН? _ 3‘2; {if …?” іличаются от аналогичных параметров  iv“?        % g‘ __ это“ объявление функции, возвращаю % указываемых фУН'ЩИЙ i Egr’ipn указателн на символ „, ; ‘ Ё ; ‚"‘/”33$“ ,? „ …… ‘ *funcf) x‘ сёдзё; %% **;ЁЁЁЁЁЪ № &’:И‘ЗЁЁ: » :… … ? Ё @ ‘ {м и., :73“: ; ;…Кю ?, z~ w … ?'.Ii}€'- 3 '~ 23‘“? ”@@@“ ° яг— “313% * , . ‘*` “м…/:* Т. …:… 2 № ”* *‘ „адм…. :   Дополнительный раздел: введение B связанные списки  Связанные списки — это полезный метод организации данных, который можно легко реа- лизовать средсгвами С. Почему мы рассматриваем его на занятии, посвященном указателям? Потому что указатели играют решающую роль в организации связанных списков, как вы ско— ро убедитесь. , Имеется несколько разновидностей связанных списков: однонаправленные, двунаправ- ленные, двоичные деревья. Каждая из разновидностей служит определенным целям органи— зации данных. Общим для всех этих видов списков является то, что связь между элементами  День 15 —й. Дополнительные сведения об указателях 379 
их данных определяется информацией, хранящейся в самих элементах в виде указателей. Такой способ связи между отдельными элементами существенно отличается от массива, в ко- тором связи между элементами данных заранее определены способом хранения и взаимного расположения данных. В этом разделе рассматривается самый основной вид связанных спи- сков: однонаправленные связанные списки., Именно эту разновидность мы будем далее назы- вать просто связанным списком.  OCHOBHble сведения O СВЯЗЭННЫХ СПИСКЭХ  Каждый элемент данных связанного списка представляет собой структуру. (Структуры рассматривались на занятии 11.) Как обычно, каждая структура содержит набор переменных, необходимых для хранения информации в зависимости от назначения списка и программы. Кроме того, в структуре имеется еще один элемент — указатель. Именно этот указатель оп- ределяет связи между элементами в списке. Вот простой пример:  struct person {  char name[20]; struct person *next;  Здесь объявлена структура (фактически, структурный тип) с именем persbn. B качестве данных эта структура содержит массив из двадцати символов. Конечно, для таких простых данных связанные списки обычно не используются, но сейчас мы всего лишь рассматриваем пример. Структура person также содержит указатель на структуру типа person — т.е. другую структуру своего же типа. Получается, что каждая структура типа person может не только хранить некоторое количество данных, но и указывать на другую аналогичную структуру. На рис. 15.7 показано, как этот факт можно использовать для связывания структур в список.  данные данные данные NULL указатель на Г указатель на .J—"' указатель на т  следующий элемент следующий элемент следующий элемент                Рис. 15. 7. Связи между элементами в связанном списке  Обратите внимание, как на рис. 15.7 каждая структура person указывает на следующую структуру person. Последняя из структур ни на что не указывает. Чтобы обозначить послед- ний элемент связанного списка, его указателю присваивается значение NULL.   @.." Структуры, образующие связанный список, называются звеньями, узлами или элементами списка.     Итак, вы узнали, как определяется последний элемент в связанном списке. А как распо— знать его первый элемент? Для этой цели используется специальный указатель (не структу- ра) —-— начальный указатель. Он всегда указывает на первый элемент в списке. Первый эле— мент содержит указатель на второй, второй элемент— на третий, и т.д. до тех пор, пока не встретится элемент с указателем, равным NULL. Если весь список пуст (не содержит ни одного элемента), то начальный указатель устанавливается равным NULL. Ha рис. 15.8 схематически показано значение начального указателя до и после добавления в список первого элемента.   (ШШШ Начальный указатель (head pointer) указывает на первый элемент свя- занного списка. Его еще называют старшим указателем (top pointer) или просто указателем на первый элемент.     380 Неделя 3. Основные вопросы 
  ( Указатель на нача® @затель на Havana       NULL данные NULL До первого добавления После первого добавления  Рис. 15.8. Начальный указатель связанного списка  Работа со связанными списками  Со связанными списками можно проделывать такие манипуляции, как добавление, удаление или изменение элементов списка. Изменение элементов не представляет собой ничего особен- ного, а вот их добавление и Удаление — далеко не тривиальные операции. Как уже говорилось, элементы списка связаны между собой через указатели. Большая часть работы по добавлению и удалению элементов состоит в манипулировании этими указателями. Элементы можно добав- лять в начало, середину или конец списка. От этого зависит способ изменения указателей. Несколько позже продемонстрируем работу с простым связанным списком, а также более сложную программу обработки таких списков. Прежде чем углубиться непосредственно в дебри кода, давайте рассмотрим общие подх0ды к некоторым операциям со связанными спи- сками. При этом будем использовать ту же структуру person, что и в предыдущих примерах.  Предварительная подготовка  Прежде чем организовывать связанный список, необходимо определить структуру данных для этого списка и объявить начальный указатель. Поскольку список создается пустым, его начальный указатель следует инициализировать значением NULL. Нужен также еще один ука- затель на ту же структуру, чтобы с его помощью добавлять элементы в список. (Как вы скоро убедитесь, может понадобиться и несколько дополнительных указателей.) В итоге получается следующий код: struct person { char name[20]; struct person *next; }: struct person *new; struct person *head; head = NULL;  Добавление элемента в начало списка  Если начальный указатель равен NULL, то список пустой, и новый элемент станет его единственным членом. Если же начальный указатель не равен NULL, то список уже содержит Один или несколько элементов. Тем не менее, в каждом из этих случаев процедура добавле— ния элемента в начало списка остается одной и той же: 1. Создать экземпляр структуры с выделением памяти для него функцией malloc( ). 2. Установить указатель следующего узла в структуре нового элемента списка равным теку- щему значению начального указателя. Если список пустой, это будет NULL, 8 противном случае -—— адрес текущего первого элемента.  3. Установить начальный указатель равным адресу нового элемента.  День 15-й. Дополнительные сведения об указателях 381 
Вот код, выполняющий эти задачи: new = (struct person*)malloc(sizeof(struct person)); new->next = head; head = new; Обратите внимание на приведение типа перед функцией malloc() для корректного при- сваивания возвращаемого адреса указателю на структуру.   ( IIIIIIII Очень важно изменять указатели в правильном порядке. Если первым из- менить начальный указатель. то потеряется весь список!     Рис. 15.9 схематически демонстрирует процедуру добавления нового элемента в пустой список, a рис. 15.10 —— добавление нового первого элемента в непустой список.  Указатель на начало          NULL новые данные NULL Перед добавлением Указатель на начало _ новые данные ' NULL     После добавления  Рис. 15. 9. Добавление нового элемента в пустой связанный список  Обратите внимание, что для распределения памяти под новый элемент используется функция ша11ос( ). По мере добавления новых элементов для них также выделяется память без размещения уже существующих заново. Для этой цели можно также использовать функ- цию са11ос( ). Следует знать важное различие между двумя функциями распределения памя- ти: са11ос() инициализирует вновь созданный элемент, а malloc() этого не делает.   ...да". В приведенных фрагментах не выполняется проверка правильности рас- - пределения памяти (путем анализа возвращаемого из malloc() значения).  В практической программе это следует делать всегда.     t f оставляйте указатели с неопределенными значениями —таким образом вы Ё % просто напрашиваетесь на ошибку.… _ LL …. …. .;}…е „, 13W №…; `… LL 5  I {)“)—і в „№44 ‘,‘ A‘s” )‹&…‹_  (chm Ё Всегда инициализируите указатели значением NULL? при их объявлении. Не E  "ь  {(:к) 38% „№ 1.  It;  ...-…!. …… и.о— Нам/.' „… ……“ „мама….ьмме,‘ … …..о «нимф,… . ‚. и!‘ 4444A.  382 Неделя 3. Основные вопросы 
 Оказатель на началсэ  данные Г данные Г данные указатель указатель NULL                                              новые данные NULL Перед добавлением (Указатель на Mama данные данные _|—› данные указатель указатель NULL новые данные указатель После добавления  Рис. 15.10. Добавление нового первого элемента в непустой связанный список  Добавление элемента в конец списка  Для добавления элемента в конец списка следует сначала дойти от начального указателя до последнего элемента в списке. После нахождения последнего элемента следует выполнить следующие операции:  1. Создать экземпляр структуры данных, распределив память с помощью функции malloc( ).  2. Установить указатель в текущем последнем элементе на добавляемый элемент (адрес ко- торого возвратила функция malloc( )).  3. Установить указатель в добавленном элементе равным NULL, чтобы таким образом сделать его новым последним элементом списка.  Вот кол для выполнения этих операций: person *current;  current = head; while (current->next != NULL) current = current->next; new = (struct person*)malloc(sizeof(struct person)); current->next = new; new->next = NULL;  Ha рис. 15.11 схематически показана процедура добавления нового элемента в конец свя— занного списка. ` Добавление элемента в середину списка  При работе со связанным списком в основном прих0дится добавлять элементы не в нача- ло и не в конец, а куда-то в середину списка. Кула именно помещается новый элемент, зави— сит от назначения списка и способа его организации —— например, от наличия сортировки по  День 15-й. Дополнительные сведения об указателях 383 
одному или нескольким полям данных. Поэтому вначале необходимо найти то место в спи- ске, на которое нужно поместить новый элемент, а затем уже добавить сам элемент. Для это- го следует выполнить такие операции:                                              (Указатель на начало) данные Г данные _|_-› данные новые данные указатель указатель NULL NULL Перед добавлением (Указа тель на Havana данные данные данные новые данные указатель указатель указатель NULL После добавления  Рис. 15.11. Добавление нового элемента в конец связанного списка  1. Найти существующий элемент в списке, после которого должен стоять новый элемент. Назовем его маркером.  2. Создать экземпляр структуры, выделив для него память с помощью функции ша11ос( ).  3. Установить указатель в маркере на новый элемент (адрес которого возвратила функция ша11ос( )).  4. Установить указатель в новом элементе на тот элемент, на который перед этим указывал маркер. Вот как может выглядеть соответствующий код: ’  struct person *marker; /* Сначала - поиск маркера в списке. */ new = (struct person*)malloc(sizeof(struct person)); new->next = marker->next; marker->next = new;  Ha рис. 15.12 этот процесс показан схематически.  Удаление элемента из списка  Удаление элемента из списка представляет собой довольно простую задачу, связанную с манипулированием указателями. Эта операция зависит от того, в каком месте списка нахо— дится элемент.  I Для удаления первого элемента сделайте начальный указатель равным адресу второго элемента списка.  384 Неделя 3. Основные вопросы 
I Для удаления последнего элемента сделайте указатель 3 предпоследнем элементе рав- ным NULL.  I Для удаления любого другого элемента сделайте указатель в предшествующем ему элементе равным адресу элемента, следующего за удаляемым.                                              новые данные (Указатель на Havana NULL данные Г данные Г данные указатель указатель NULL Перед добавлением петтинг бказатель на начала next pointer данные Г данные данные указатель указатель NULL После добавления  Рис. 15.12. Добавление нового элемента в середину связанного списка  Кроме того, память, занимаемую этим элементом, необходимо освободить, чтобы про- грамма не тратила памяти больше, чем ей действительно необходимо (это называется утеч- кой памяти). Освобождение памяти выполняется функцией free( ), подробно рассматривае- мой на занятии 20. Вот как удаляется первый элемент из списка:  free(head); head = head->next;  A этот фрагмент кода удаляет последний элемент из списка:  struct person *currentl, *current2; currentl = head; current2 = currentl->next; while (current2->next != NULL) { currentl - current2; current2 currentl->next;  }  free(current1->next); currentl->next = NULJq if (head == currentl) head = null;  Наконец, удаление элемента из середины cnucxa ВЫПОЛНЯСТСЯ таким образом:  struct person *currentl, *current2; /* Здесь идет код для установки указателя currentl */  День 15-й. Дополнительные сведения об указателях 385 
/* на элемент непосредственно перед удаляемым */ currentz = current1->next; free(current1->next); current1->rnext = current2->next;  После выполнения любой из этих групп операторов без вызова функции free() удален- ный элемент все еще существовал бы где—то в памяти, хотя в списке его уже не было бы, по- тому что ни один указатель из списка на него не указывал бы. В программе, предназначенной для реального практического применения, обязательно нужно освобождать память, занимае- мую удаленным элементом. Для этого и нужна функция free( ). Подробности ее работы об- суждаются на занятии 20.  Пример простого связанного списка  В листинге 15.12 демонстрируются основные приемы работы “со связанными списками. Про— грамма предназначена только для иллюстративных целей, поскольку не принимает никаких данных от пользователя и вообще не делает ничего полезного, кроме демонстрации нескольких операций со связанным списком. Говоря по существу, программа выполняет следующее:  1. Объявляет структуру и указатели, необходимые для организации списка. 2. Добавляет первый элемент в список. 3. Добавляет элемент в конец списка. 4. Добавляет элемент в середину списка. 5. Выводит данные списка на экран.  Листинг 1 5.1 2. linkdemo. с — пример связанного списка   /* демонстрация основных приемов работы */ /* со связанным списком. */  #include <std1ib.h> #include <stdio.h> #include <string.h>  /* Структура элемента данных списка. */ struct data { char name[20]; struct data *next;  ‚_в… Howooumm-p-wNv—s О...  12: }; 13: 14: /* Определение структурного типа */ 15: /* и типа указателя на эту структуру. */ 16: typedef struct data PERSON; 17: tyPedef PERSON *LINK;  18: 19: int main( void ) 20: { 21: /* Указатели на первый, новый, текущий элементы. */  22: LINK head = NULL; 23: LINK new = NULL;  386 Неделя 3. Основные вопросы 
24: LINK current = NULL;   25: 26: /* Добавление первого элемента в список. He */ 27: /* предполагается, что список пустой, котя */ 28: /* в этой простой программе это всегда так. */ 29: 30: new = (LINK)malloc(sizeof(PERSON)); 31: new—>next = head; 32: head = new; 33: strcpy(new->name, "Abigail"); 34: 35: /* добавление элемента в конец списка. */ 36: /* Предполагается, что в списке есть хотя бы один элемент. */ 37: 38: current = head; 39: while (current->next != NULL) 40: { 41: current = current->next; 42: } 43: 44: new = (LINK)malloc(sizeof(PERSON)); 45: current->next = new; 46: new->next = NULL; 47: strcpy(new—>name, "Carolyn"); 48: 49: /* Добавление элемента во вторую позицию списка. */ 50: new = (LINK)malloc(sizeof(PERSON)); 51: new->next = head->next; 52: head->next = new; 53: strcpy(new->name, "Beatrice"); 54: 55: /* Вывод всех пунктов списка по порядку. */ 56: current = head; 57: while (current != NULL) 58: { 59: printf("\n%s", current->name); 60: current = current->next; 61: } 62: 63: printf("\n"); 64: 65: return 0; , 66: } ршЩтшиш> Beatrice Carolyn д‘ № . По крайней мере, часть этого кода вам уже знакома. В строках 9-12 объявляется  структура элемента данных связанного списка. В строках 16 и 17 с помощью typedef объявляются типы, соответствующие самой структуре и указателю на нее. Хотя  День 15-й. Дополнительные сведения об указателях 387 
особой необх0димости в этом нет, таким образом облегчается программирование: достаточ— но написать PERSON вместо struct data и LINK вместо struct data *. B строках 22—24 объявляется начальный указатель на список и еще два указателя для ра- боты со списком. Все эти указатели инициализируются значением NULL. B строках 30—33 в начало списка добавляется новый элемент. В строке 30 вьхделяется па- мять для новой структуры данных. Обратите внимание, что здесь без проверки предполагает- ся корректная работа функции ша11ос(). B действительности этого предположения никогда не следует делать. В строке 31 указатель next B новой структуре устанавливается равным тому адресу, кото- рый СОдержится в начальном указателе. Почему бы просто не присвоить ему значение NULL? Это годится только в том случае, когда точно известно, что список пустой. А в том виде, как это сделано в программе, этот оператор будет работать, даже если в списке уже были данные. Новый первый элемент в итоге будет указывать на элемент, который был первым раньше, че— го мы и добивались. В строке 32 начальному указателю присваивается адрес новой записи, а в строке 33 в эту запись помещаются данные. Добавление элемента в конец списка несколько более трудоемко. Хотя в нашем случае из— вестно, что в списке только один элемент, в практической программе этого предполагать нельзя. Поэтому необх0димо B цикле перебрать весь список, начиная с первого элемента, по— ка не найдется последний элемент (его можно обнаружить по тому, что указатель next равен NULL). Это делается в строках 38—42. Найдя последний элемент, необх0димо вьхделить память для новой структуры данных, установить указатель next B последнем элементе на новый эле- мент, а указатель next B новом элементе сделать равным NULL, потому что именно этот эле— мент теперь стал последним. Все это пр0делывается в строках 44—47. Обратите внимание на преобразование указателя, возвращаемого из ша110с( ), к типу LINK. Подробнее о преобразо— ваниях типов говорится на занятии 20. Следующая задача состоит в том, чтобы добавить элемент в середину списка, а именно во вторую позицию. После распределения памяти для новой структуры данных (строка 50) ука— затель next нового элемента устанавливается равным адресу элемента, который раньше был вторым, а теперь стал третьим в списке (строка 51). Указатель next первого элемента стано— вится равным адресу нового элемента (строка 52). Наконец, программа выв0дит на экран все данные списка. Для этого достаточно взять на- чапьный указатель и двигаться от него по списку до последнего элемента (он распознается по указателю на следующий элемент, равному NULL). Эту задачу выполняют строки 56—61.  Реализация связанного списка  Итак, изучен методы работы с данными, организованными в виде связанного списка. Те— перь рассмотрим более сложный пример, в котором применяются все эти методы. Лис- тинг 15.13 соцержит довольно сложную программу, использующую связанный список для хранения пяти символов. Вместо символов можно использовать адреса, имена или любые другие данные. Чтобы сделать пример как можно проще, мы ограничились одиночными сим— волами в качестве элементов данных. Сложность этой программе придает сортировка данных по мере их добавления. Конечно, от этого и ценность программы повышается. Элементы добавляются в начало, середину или конец списка в зависимости от их значений. Данные списка всегда находятся в отсортирован— ном порядке. Программа, в которой данные просто добавлялись бы в конец списка, была бы устроена значительно проще. Но и пользы от такой программы было бы значительно меньше.  388 Неделя 3. Основные вопросы 
Листинг 1 5. 1 3. linklist.c — реализация связанного списка символов        1: /* """"""" * 2 * Программа: listlist.c * 3 * Книга: Освой самостоятельно С за 21 день * 4 * Назначение: реализация связанного списка * 5: *'“ */ 6' #include <stdio.h> 7 #include <stdlib.h> 8. 9: #ifndef NULL 10: #define NULL 0 11: #endif 12: 13: /* Структура данных элемента списка */ 14: struct list 15: { 16: int ch; /* B переменной типа int хранится символ типа char */ 17: struct list *next_rec; 18: }; 19: 20: /* Определения типов для структуры и указателя на нее. */ 21: typedef struct list LIST; 22: typedef LIST *LISTPTR; 23: 24: /* Прототипы функций. */ 25: LISTPTR add_to_list( int, LISTPTR ); 26: void show_list(LISTPTR); 27: void free_memory_list(LISTPTR)7 28: 29: int main( void ) 30: { 31: LISTPTR first = NULL; /* указатель на первый элемент */ 32: int i = 0; 33: int Ch; 34: char trash[256]; /* для очистки буфера stdin. */ 35: 36: while ( i++ < 5 ) /* построение списка из 5 элементов */ 37: { 38: Ch = 0; 39: printf("\nEnter character %d, “, i); 40: 41: do 42: { 43: printf("\nMust be a to z: "); 44: ch = getc(stdin); /* ввод следующего символа из буфера */ 45: gets(trash); /* очистка буфера */ 46: } while( (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')); 47: 48: first = add_to_list( ch, first ); 49: }  День 15-й. Дополнительные сведения об указателях 389  
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: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100:  390    show_list( first ); /* Вывод всего списка */ free_memory_list( first ); /* Освобождение всей памяти */ return 0; } /* * * Функция : add_to_list() * Назначение: Inserts new link in the list * Вызов : int ch = символ для помещения в список * LISTPTR first = адрес начального указателя * Возвращает: адрес указателя на первый элемент * — */ LISTPTR add_to_list( int ch, LISTPTR first ) { . LISTPTR new_rec = NULL; /* Адрес новой записи */ LISTPTR tmp rec = NULL; /* Временный указатель */  LISTPTR pre5_rec = NULL;  /* Распределение памяти. */ new_rec = (LISTPTR)malloc(sizeof(LIST)); if (lnew_rec) /* Ошибка распределения памяти */  printf("\nUnable to allocate memory£\n"); exit(1); }  /* запись новых данных */ new_rec->ch = ch; new_rec->next_rec = NULL;  if (first == NULL) /* добавление первого элемента в список */  { first = new_rec; new_rec—>next_rec = NULL; /* лишнее, но так безопаснее */  else /* если не первый элемент */ { /* проверка, не вставлять ли перед первым элементом */ if ( new_rec—>ch < first->ch) { new;rec—>next_rec = first; first = new rec; }  else /* если вставляется в середину или конец */  { tmp_rec = first->next_rec; prev_rec = first;  /* Анализируем, куда вставить элемент. */  Неделя 3. Основные вопросы 
101: 102: if ( tmp_rec == NULL ) 103: 104: /* добавляем вторую запись в конец */ 105: prev_rec—>next_rec = new_rec; 106: } 107: else 108: { 109: /* проверка, не вставляется ли в середину */ 110: while (( tmp_rec->next_rec != NULL)) 111: { 112: if( new_rec->ch < tmp_rec->ch ) 113: { 114: new_rec->next_rec = tmp_rec; 115: if (new_rec->next_rec != prev_rec—>next_rec) 116: { 117: printf("ERROR"); 118: getc(stdin); 119: exit(0); 120: } 121: prev_rec—>next_rec = new_rec; 122: break; /* элемент вставлен, выход из while */ 123: } 124: else 125: { 126: tmp_rec = tmp_rec->next_rec; 127: prev_rec = prev_rec—>next_rec; 128: } 129: } 130: 131: /* проверка, не вставляется ли в конец */ 132: if (tmp_rec—>next_rec == NULL) 133: { 134: if (new_rec->ch < tmp_rec->ch ) /* предпоследний */ 135: { 136: new_rec->next_rec = tmp_rec; 137: prev_rec->next_rec = new_rec; 138: } 139: else /* B конец */ 140: { 141: tmp_rec->next_rec 142: new_rec—>next_rec 143: } 144: } 145: } 146: } 147: } 148: return(first); 149: } 150: 151: /* *  new_rec; NULL; /* лишнее */   День 15-й. Дополнительные сведения об указателях 391 
152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190:  Функция : show_list Назначение: вывод информации из списка   */  void show_list( LISTPTR first )  LISTPTR cur_ptr; int counter = 1;  printf("\n\nRec addr Position Data Next Rec addr\n"); printf("==:===:: ======== ———— \nu);   cur_ptr = first; while (cur_ptr != NULL )  { printf(" %Х ", cur4ptr ); printf(" %2i %c", counter++, cur_ptr->ch); printf(" %X \n",cur_ptr->next_rec); cur_ptr = cur_ptr->next_rec; }   Функция : free_memory_list Назначение: освобоЖдение всей памяти, занятой списком   * */ void free_memory_list(LISTPTR first) { LISTPTR cur_ptr, next_rec; cur_ptr = first; /* Начать сначала */ while (cur_ptr != NULL) /* Идти до конца списка */ { next_rec = cur_ptr->next_rec; /* Получение адреса след. записи */ free(cur_ptr); /* Освобождение текущей записи */ cur_ptr = next_rec; /* Смещение на одну запись */ } }   ' Enter character 1, Развивал: Must be a to 2: q  192  Enter character 2, Must be a to z: b  Enter character 3, Must be a to z: 2  Enter character 4, Must be a to z: с  Неделя 3. Основные вопросы 
Enter character 5, Must be a to z: a  Rec addr Position Data Next Rec addr  -——————- -—-——-—- ___-— ___——_—— ___—___—  2247A0 l a 224770 224770 2 Ь 224790 224790 3 0 223748 223748 4 q 224780 224780 5 2 0   “Риш.“ При запуске этой программы в разных системах могут получиться разные значения адресов.     Mann B программе демонстрируется добавление записей в связанный список. Эта про- грамма далеко не самая простая для понимания. Тем не менее, если разобраться, в ней используются все те же три метода работы со списком, которые мы уже рассмотрели раньше. Здесь записи могут добавляться в начало, середину или конец списка. Кроме того, программа учитывает особые случаи добавления первого элемента (в начало списка) и второ- го элемента (в середину). "“ Ё Самый простой способ разобраться в приведенной программе —— это од- @" новременно выполнять ее по шагам в отладчике и читать анализ, приве-  % EA денный ниже Наглядная демонстрация порядка, в котором выполняется °Брограмма значительно облегчит понймание ее работы.  С „. №№ .… від… М.… „мг v-iwwmrr-W №№ ями—‚мм .д./№ мим… M Аднан—..?… …,…— “№і`4\щ…№щ ««М …»… .а яша  Многие операторы в начале листинга 15.13 должны быть вам знакомы и легко понятны. В егроках 9—11 проверяется, определена ли константа NULL. Если не определена, то в строке 10 она объявляется со значением 0. B егроках 14—22 объявляется егруктура данных для связанного списка, а также нестандартные типы для удобства работы со структурой и указателями на нее. Функция шаіп() в этой программе устроена довольно просто. В строке 31 объявляется начальный указатель с именем first. OH инициализируется значением NULL. Помните, что указатель никогда не следует оставлять неинициализированным. Строки 36—49 содержат цикл while для ввода пяти символов с клавиатуры. Внутри этого внешнего цикла while, повто- ряющегося пять раз, используется еще Один цикл do. . .while, который гарантирует, что бу- дут введены только буквы. Для этой цели, кстати, можно было бы с успехом использовать функцию isalpha( ). После ввода элемента данных вызывается функция add__to__list(). B нее передаются указатель на начало списка и элемент данных, который следует добавить. Функция main() заканчивается вызовом функции show_list() для отображения данных из списка и вызовом free_memory_list() для освобождения всей памяти, занятой списком. Обе эти функции работают аналогично: каждая начинает движение по списку с указателя first Ha его первый элемент. Далее в цикле while список перебирается элемент за элементом с помощью указателя next_ptr. Как только next_ptr становится равным NULL, достигается конец списка, и функция заканчивает работу. Самой важной (и самой сложной!) функцией в этом листинге является функция add__to_list( ) B строках 56—149. Строки 66—68 содержат объявления трех указателей, кото- рые затем используются для указания на три различных элемента списка. Указатель new_rec  День 15-й. Дополнительные сведения об указателях 393 
предназначен указывать на новый элемент, добавляемый B список. Указатель tmp__rec пред- назначен для указания на текущий обрабатываемый элемент списка. Если в списке больше одного элемента, то используется еще и указатель prev_rec на предыдущий обработанный элемент. В строке 71 выделяется память для нового добавляемого элемента. Указатель new_rec ус— танавливается равным значению, которое возвращает функция ша11ос( ). Если память рас— пределена ошибочно, в строках 74 и 75 на экран выводится сообщение об ошибке, и про— грамма завершается. Если же память распределена успешно, программа продолжает работу. В строке 79 элемент данных, переданный как аргумент, помещается B структуру. Факти— чески эта операция представляет собой просто присваивание переданного символа ch полю данных новой записи rec->ch. B более сложной программе, по-видимому, присваивалось бы несколько полей. В строке 80 поле next_rec B новой записи устанавливается равным NULL, чтобы не указывать на какой-нибудь случайный адрес. В строке 82 начинается операция добавления записи в список. Для начала выполняется проверка, есть ли в списке хотя бы один элемент. Если добавляемый элемент— первый в списке, как это показывает равенство указателя first нулевому адресу NULL, то начальный указатель делается равным адресу новой записи, и дело с концом. Если новая запись не является первой, то выполняется блок else (строка 87). В строке 90 проверяется, не следует ли поместить новую запись B самое начало списка. Как вы помните, это один из трех возможных случаев при добавлении записи. Если новая запись действитель- но должна стать первой, в строке 92 указатель next__rec B новом элементе устанавливается равным адресу записи, ранее бывшей первой. Затем B строке 93 начальному указателю first присваивается адрес новой записи. В результате новая запись добавляется в начало списка. Если новая запись не должна стать самой первой B пока еще пустом списке, или первой в списке, где уже были записи, значит, ее следует добавить B середину или конец списка. В строках 97 и 98 устанавливаются значения указателей tmp_rec и prev_rec, объявленных ранее. Указателю tmp_rec присваивается адрес второй записи в списке, а prev__rec —— адрес первой записи. Заметьте, что если в списке только одна запись, то указатель tmp_rec станет равным NULL. Это происходит потому, что tmp_rec присваивается значение указателя next_ptr B первом элементе, который гарантированно равен NULL B этом случае. В строке 102 проверяется этот специальный случай. Если tmp__rec равен NULL, это означает, что в список добавляется второй элемент. Поскольку новый элемент не может встать B начало, единственно возможным его положением оказывается конец списка. Чтобы его туда поместить, указателю prev_rec- >next_ptr просто присваивается адрес нового элемента, и на этом операция закончена. Если указатель tmp_rec не равен NULL, это означает, что B списке уже есть больше двух записей. Тогда цикл while B строках 110—129 перебирает остальные записи в поисках места для нового элемента. В строке 112 выполняется проверка, не является ли символ в новом элементе меньше, чем B текущем. Если это так, то именно сюда и следует поместить новую запись. Если же новый символ превосходит по значению символ текущего элемента, то пере- ходим к следующему звену списка. В строках 126 и 127 указателям tmp_rec и next_rec для этой цели присваиваются соответствующие адреса. Если символ “меньше или равен” символу B текущем элементе, то следует воспользо— ваться уже рассмотренным приемом добавления элемента B середину связанного списка. Это делается B строках 114—122. В строке 114 указателю нового элемента присваивается ад- рес текущего элемента tmp_rec. B строке 121 указателю предыдущего элемента присваивает- ся адрес нового элемента. После этого операцию можно считать законченной. Для выхода из цикла while используется оператор break.  394 Неделя 3. Основные вопросы 
 @“;“е Строки 115—120 содержат контрольный код для проверки и отладки. Он ос— тавлен в листинге просто для справки. Эти строки можно было бы уда- лить— если программа работает правильно, выполнение до них не дохо- дит. После того, как указатель нового элемента становится равным текуще- му указателю, он должен равняться и указателю на следующий элемент по отношению к предыдущему указателю, т.е. опять-таки текущему указателю. Если же они не равны, то в программе произошла серьезная ошибка.     Рассмотренные выше операторы выполняли задачу добавления элементов в середину спи— ска. Если же достигнут его конец, то цикл while B строках 110—129 закончится без добавле— ния записи. Тогда в строках 132—144 выполняется добавление элемента в конец списка. Если достигается последний элемент списка, то указатель tmp_rec->next__rec становится равным NULL. B строке 132 проверяется это условие. В строке 134 проверяется, следует ли вставлять новый элемент до или после последнего элемента списка. Если новую запись сле— дует добавить после последней, указателю next_rec последней записи присваивается адрес нового элемента (строка 132), а указатель next_rec этого нового элемента становится равным NULL (строка 142).  Усовершенствование программы  Связанные списки —— это не самая легкая тема для изучения. Все же, как видно из листин— га 15.13, это превосходный способ организации данных в отсортированном по порядку виде. Поскольку добавлять новые данные в любое место связанного списка достаточно просто, код для поддержания связанного списка в упорядоченном виде написать намного легче, чем, ска— жем, для организации упорядоченного массива. Программу этого листинга легко изменить так, чтобы она сортирована имена, номера телефонов или любые другие данные. Кроме того, хотя программа ориентирована на сортировку в алфавитном порядке (от А до Z), столь же легко сделать из нее программу сортировки в обратном порядке (от Z до А).  Удаление из связанного списка  Возможность добавления элементов в связанный список очень важна, но не менее важно уметь удалять элементы при необходимости. Удаление элементов (записей, узлов, звеньев) выполняется во многом аналогично их добавлению. Элементы можно удалять из начала, се- редины или конца списка. В каждом из этих случаев требуется корректная модификация ука- зателей. Также необходимо освободить память, занимаемую удаленным элементом списка.       Рекомендуется Не рекомендуется ‹, Помните о различиях в работе функций ; He забывайте освобождать память, за- % malloc() и calloc(): функция malloc() He : Ё нятую элементами связанного списка, , инициализирует… выделенный участок па— ` которые ВЫ удаляете. ; мяти, а вот calloc() —-— инициализирует. _,   Резюме  На этом занятии были рассмотрены некоторые нетривиальные приемы работы с указате- лями. Как вы уже, наверное, убедились, указатели представляют собой одно из важнейшую средств языка С. Редко можно встретить программу на С, в которой не применялись бь  День 15-й. Дополнительные сведения об указателях 395 
указатели. Мы рассмотрели использование указателей на указатели и работу с массивами указателей для обработки строковой информации. Вы узнали, что в языке С многомерные массивы считаются массивами массивов, и для работы с ними тоже можно применять указа- тели. Были изучены указатели на функции—— важный инструмент в руках Программист, Наконец, вы ознакомились с реализацией связанных списков —— гибкого и мощного средства организации данных с помощью указателей. Это занятие было длинным и непростым, Некоторые из рассмотренных тем довольно сложны, но в той же мере и интересны. На этом занятии мы углубились в некоторые весьма изощренные возможности С, Мощь и гибкость этого языка——— вот причины, по которым он столь популярен в мире программирования.  Вопросы и ответы  Есть ли ограничение на глубину вложенности указателей на указатели? Поищите ответ на этот вопрос в документации компилятора, Вообще же необходимость в глубине более трех уровней (указатели на указатели на указатели) возникает крайне редко. Более того, в большинстве программ не встречаются даже указатели глубже второго уровня (указатели на указатели).  Хи  Существует ли различие между указателем на строку и указателем на массив сим- волов? Никакого различия нет. Строку можно считать массивом символов.  Необходимо ли пользоваться средствами и приемами, изученными на этом занятии, чтобы полностью раскрыть преимущества С? Можно писать программы на С, вообще не пользуясь указателями. Правда, при этом вы утратите множество преимуществ этого языка. Манипуляции с указателями, в том числе рас- смотренные на этом занятии, могут сильно облегчить решение ряда задач программирования.  В каких еще случаях, кроме рассмотренных, могут пригопиться указатели на функции? Указатели на функции широко применяются в связи с меню. В зависимости от значения выбранного пункта меню указатель устанавливается на ту или иную функцию, которая долж- на выполнить выбранное задание.  Назовите два основных преимущества связанных списков. Первое: размер связанного списка можно увеличить или уменьшить по ходу работы иро- граммы; этот размер не должен фиксироваться при написании кода. Второе: связанный спи- сок легко сделать упорядоченным, поскольку элементы можно без труда добавлять в любое место списка или удалять из него.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления пройден- ного материала, а также упражнения для совершенствования практических навыков про- граммирования.  396 Неделя 3. Основные вопросы 
:oHTpoanbIe вопросы  Напишите фрагмент кода с объявлением переменной типа float, объявлением и инициа- лизацией указателя на эту переменную, а также с объявлением и инициализацией указате- ля на этот указатель.  Продолжая пример из контрольного вопроса 1, предположим, что вам необходимо присво- ить вещественной переменной x значение 100 ECTb ли ошибка в следующем операторе?   `д *ррх =1oo; „ Если есть, то какая запись будет правильной? ;,Предположим объявлен следующий массив: int array[2][3][4]; Как устроен этот массив с точки зрения компилятора С? ‘ \ Продолжая пример из контрольного вопроса 3, что означает выражение array[ 0] [0]?  ` Продолжая пример из контрольного вопроса 3, какие из приведенных ниже равенств ис- тинны?  array[0][0] == &array[0][0][0]; fig array[0][1] == array[0][0][1]; i:’~_array[0] [1] == &array[0] [1] [0] ; " Напишите прототип функции, которая принимает массив указателей типа char и ;д возвращает void. 1,1 Каким образом функция из контрольного вопросаб могла бы определить, какова длина переданного ей массива указателей? t Что такое указатель на функцию?  Напишите объявление указателя на функцию, возвращающую значение типа char и принимающую массив указателей типа char B качестве аргумента.  .Ответ на вопрос 9 мог бы выглядеть так: char *ptr(char *x[]); Что в этом объявлении неправильно?  1. Какой элемент обязательно должен присутствовать в структуре данных, предназначенной *‘I для организации связанного списка?  "'? .Как связаны между собой элементы однонаправленного связанного списка? 5:.4 Какие переменные объявляются в следующих операторах? а) int *varl; 6)int var2;  a)int **var3;  \?  … »:…—  M  7:: n: :=: = а :1 о U о Z о :: ::: E: о o от 0" an ш .': an ES '-l 0 an по о .': Ё 5 E = ›‹ o :1 о '8 '-l 0 U D) >< о  a)int a[3][12]; 6)int (*b)[12]; a)int *c[12];   _ ; ЧЁЁЁЁЁШЁ  нь 15—й. Дополнительные сведения об указателях 397 
16. Какие переменные объявляются в следующих операторах?  a)char *z[10]; 6)char *y(int field); B)char (*x)(int field);  Упражнения  1.  2.  Напишите объявление указателя на функцию, принимающую целочисленный аргумент и возвращающую значение типа float.  Напишите объявление массива указателей на функции. Функции должны принимать стро- ку символов в качестве аргумента и возвращать целое число. Зачем может понадобиться такой массив?  Напишите оператор для объявления массива из 10 указателей типа char. Поиск ошибок. Найдите ошибку в следующем коде: int x[3][12]; int *ptr[12]; ptr = x;  Напишите структуру данных для организации Однонаправленного связанного списка. Эти структуры должна содержать имена и адреса ваших друзей.  Ответы к следующим упражнениям не приводятся из-за большого количества различных  возможных решений.  6.  Самостоятельная работа. Напишите программу с объявлением массива символов разме- ром 12x12. Поместите в ка>кдый второй элемент символ Х. Затем используйте указатель на массив для вывода всего массива на экран в виде прямоугольной решетки. Самостоятельная работа. Напишите программу с указателями на переменные типа double для ввода данных от пользователя, их сортировки и вывода на экран. (Подсказка: посмотрите листинг 15.10.)  Самостоятельная работа. Измените программу из упражнения 7 так, чтобы пользователь мог запрашивать прямой или обратный порядок сортировки данных.  398 Неделя 3. Основные вопросы 
   Работа C файлами  Очень многие программы работают с информацией, хранящейся на дисках компьютера в виде файлов. Файлы могут содержать самые разнообразные данные, необходимые програм— мам. Поэтому программисту нужно уметь обращаться с файлами. Это занятие посвящено изучению следующих вопросов. I Связь между потоками и дисковыми файлами Два типа файлов в языке С Команды открытия файла Запись данных в файл Чтение данных из файла Закрытие файла Управление файлами на диске  Использование временных файлов  Связь между потоками и файлами  Как уже обсуждалось на занятии 14, в языке С все операции ввода-вывода, в том числе из файлов и в файлы, выполняются с помощью _потоков. Вы узнали, как пользоваться стандартны- ми потоками С, связанными с устройствами ввода—вывода, такими как клавиатура, экран и (в не- которых системах) принтер. Потоки для работы с файлами устроены и работают точно так же. Это одно из преимуществ потокового ввода-вывода: отлаженная техника работы с одним пото— ком почти или вообще не меняется при переходе к другому потоку. Основное отличие работы с потоками дисковых файлов от стандартных потоков состоит в том, что такой поток необходимо создать в программе явным образом и ассоциировать его с конкретным файлом на диске.  Типы дисковых файлов  На занятии 14 говорилось, что в С имеется два типа потоков: текстовые и двоичные. По- ток любого из этих типов можно ассоциировать с файлом, но при этом важно понимать раз- ницу между ними, чтобы корректно ими пользоваться. 
Текстовые потоки ассоциируются с текстовыми файлами. Текстовый файл состоит из по- следовательности строк. Каждая строка содержит нулевое или ненулевое количество симво— лов и заканчивается одним или несколькими символами, обозначающими конец строки. Важ— но помнить, что строка в текстовом файле — это не то же самое, что символьная строка в программе на С, потому что в ней нет заВершающего нулевого символа (\0). При работе с текстовым потоком выполняется автоматическое взаимное преобразование символа конца строки С (\п) и символа (или символов), который используется операционной системой для обозначения концов строк в файлах. В системе DOS или в консольном режиме Microsoft Windows —— это сочетание “возврат каретки—перевод строки” (CR-LF). При записи данных в текстовый файл каждый символ \п преобразуется в сочетание CR-LF, а при чтении данных из файла каждая комбинация CR-LF заменяется на \п. B системе UNIX такое преобразование не выполняется _— символ конца строки остается неизменным. Двоичные потоки ассоциируются с двоичными файлами. Все данные считываются и запи- сываются в неизменном виде, без разделения на строки и символов конца строки. Нулевой символ (\0) и символ конца строки (\п) не имеют никакого особого смыслового значения и воспринимаются как любой другой байт данных. Некоторые функции ввода-вывода могут работать только в одном из двух режимов, тогда как другие —— в обоих. На этом занятии мы познакомимся и с теми, и с другими, всякий раз оговаривая, какие функции в каком режиме используются.  Имена файлов  Каждый файл на диске имеет имя, используемое для доступа к нему и его данным. В про- граммах имена файлов представляются в виде строк, как и любые другие текстовые данные. Требования к именам файлов (какими они могут или не должны быть) отличаются в разных операционных системах. В системах DOS и Windows 3.x полное имя файла может состоять из собственно имени длиной от одного до восьми символов, а также необязательных точки и расширения (от одного до трех символов) после него. А вот в системе Microsoft Windows 95 и более поздних версиях Windows (в том числе NT, XP и .NET) имя файла может иметь длину до 256 символов. Такие же требования к именам файлов выдвигаются и большинством сис- тем UNIX. Операционные системы также отличаются тем, какие символы разрешены в именах фай- лов, а какие— нет. Например, в Windows 95/98 B именах файлов нельзя использовать сле- дующие символы:  /\:*?"<>|  Всегда необходимо знать требования к именам файлов в операционной системе, для кото- рой пишется программа. Имя файла в программе на С может также содержать информацию о пути к этому файлу. Путь указывает диск и/или каталог (папку), где находится данный файл. Если имя файла ука— зано без пути к нему, то подразумевается, что файл находится в том каталоге, который опера- ционная система в данный момент считает текущим. Хороший стиль программирования предписывает всегда включать информацию о пути в имена файлов.   llpnueqaun: Если не указан путь к файлу, то программа должна предполагать, что те- _ кущим каталогом является тот же. в котором находится и сама программа.  Для указания пути к файлу программы следует добавить в нее соответст- вующий код.     400 Неделя 3. Основные вопросы 
На КВМ-совместимых персональных компьютерах каталоги и подкаталоги в пути K файлу обычно разделяются обратной косой чертой. Рассмотрим пример полного имени файла в DOS и Microsoft Windows:  c:\data\list.txt  Это имя относится к файлу list.txt, находящемуся в каталоге \ВАТА на диске С:. Не за- будьте, что обратная косая черта в символьных строках С имеет особый смысл. Чтобы вклю- чить в строку сам символ косой черты, необходимо использовать двойную косую черту \\. Таким образом, в программе на С приведенное выше имя файла будет представлено следующеи строкои: char *filename = "c:\\data\\list.txt";  Однако, если при выполнении программы приходится набирать на клавиатуре имя файла с его путем, вводите в нужных местах одинарную, а не двойную косую черту. Не во всех системах разделителем имен подкаталогов служит именно обратная косая чер- та. Например, в системе UNIX используется косая черта / .  Открытие файла  Открытием файла называется процесс, в ходе которого создается поток ввода или выво— да, ассоциированный с конкретным файлом на диске. При открытии файла он становится доступным для чтения (т.е. передачи данных из файла в программу), записи (сохранения дан- ных программы в файле) или обеих этих операций. Закончив работу с файлом, его необходи— мо закрыть. Закрытие файла рассматривается несколько позже на этом же занятии. Для открытия файла используется библиотечная функция fopen( ). Прототип fopen() на— ходится в файле stdiQ. h и выглядит следующим образом:  FILE *fopen(const char *filename, const char *mode);  B этом прототипе сообщается, что функция fopen( ) возвращает указатель типа FILE * —- указатель на структуру, объявленную в файле stdio.h. Элементы структуры FILE использу- ются программой для разнообразных операций с файлом, но вам об этих элементах знать не- зачем. Для каждого открываемого файла необходимо объявить указатель на структуру FILE. При вызове функции fopen() она создает экземпляр структуры FILE и возвращает указатель на нее. Этот указатель затем используется в последующих операциях с файлом. Если функция fopen() не смогла открыть файл, она возвращает нулевой указатель NULL. Это может про- изойти из-за какого-либо сбоя в устройствах ввода-вывода или, например, при попытке от- крыть файл на неформатированном диске. Аргумент filename —-— это строка, содержащая имя открываемого файла. Как уже говори- лось, строка filename может — и как правило, должна —— содержать путь K файлу. Эта строка может быть литералом, т.е. последовательностью символов в двойных кавычках, или указа- телем на строковую переменную. Аргумент mode указывает режим открытия файла: является ли он текстовым или двоич- ным, открывается ли файл для чтения, записи или обеих этих операций. Возможные значения аргумента mode перечислены в табл. 16.1. По умолчанию файл открывается для работы в текстовом режиме. Чтобы открыть его в двоичном режиме, к аргументу mode следует добавить букву b. Таким образом, если аргумент mode равен a, то файл открывается для добавления данных в текстовом режиме, а если аЬ ——- то в двоичном.  День 16-й. Работа с файлами 401 
Таблица 1 6.1 . Значения аргумента mode функции fopen()   Режим Значение   *.  r Открытие файла для чтения. Если файл не существует, функция fopen() возвращает NULL w Открытие файла для записи. Если файл не существует, он создается. Если файл суще- ствует, он удаляется без предупреждения, а взамен создается новый пустой файл. а Открытие файла для добавления данных в конец. Если файл не существует, он созда- ется. Если файл существует, новые данные дописываются в его конец. r+ Открытие файла для чтения и записи. Если файл не существует, он создается. Если существует, новые данные записываются с его начала, затирая уже имеющуюся ин- формацию. w+ Открытие файла для чтения и записи. Если файл не существует, он создается. Если  файл существует, он очищается, и новые данные записываются в очищенный файл.  а+ Открытие файла для чтения и добавления данных. Если файл не существует, он соз- дается. Если файл существует, новые данные дописываются в его конец.   Не забудьте, что функция fopen( ) возвращает NULL B случае неудачи. Возможны следую- щие ситуации, которые считаются ошибками и в результате возникновения которых возвра— щается NULL.  I Использование неправильного или несуществующего имени файла.  I Попытка открыть файл на диске, не готовом к вводу-выводу (например, не закрыта дверца дисковода или диск не отформатирован).  I Попытка открыть файл в несуществующем каталоге или на несуществующем диске. I Попытка открыть несуществующий файл с флагом r (B режиме чтения).  При каждом вызове fopen() следует проверять, не произошла ли ошибка. По возвращае— мому значению невозможно определить, какая именно ошибка случилась, но тем не менее можно выдать сообщение пользователю и попытаться снова открыть файл или же закончить выполнение программы. Многие компиляторы С предлагают те или иные функции (не опрен деленные стандартом ANSI) для диагностики источника ошибки. Узнать об этом подробнее можно из документации конкретного компилятора. В листинге 16.1 демонстрируется применение fopen( ).  Листинг 1 6. 1 . fopen. с — использование функции fopen( ) для открытия файлов в разных режимах   1: /* демонстрация функции fopen(). */ 2: #include <stdlib.h> 3: #include <stdio.h> 4: 5: int main( void ) 6: { 7: FILE *fp; 8: char ch, filename[40], mode[4]; 9: 10: while (1) 11: { 12: 13: /* Ввод имени и режима открытия. */ 14:  402 Неделя 3. Основные вопросы 
15: printf("\nEnter a filename: ");  16: gets(filename); 17: printf("\nEnter a mode (max 3 characters): "); 18: gets(mode); 19: 20: /* Попытка открытия файла. */ 21: 22: if ( (fp = fopen( filename, mode )) != NULL ) 23: { 24: printf("\nSuccessful opening %s in mode %s.\n", 25: filename, mode); 26: fclose(fp); 27: puts("Enter x to exit, any other to continue."); 28: .if ( (ch = getc(stdin) == 'x') 29: break; 30: else 31: continue; 32: } 33: else 34: { 35: fprintf(stderr, "\nError opening file %s in mode %s.\n", 36: filename, mode); 37: puts("Enter x to exit, any other to try again."); 38: if ( (ch = getc(stdin) == 'x') 39: break; 40: else 41: continue; 42: } 43: } 44: return 0; 45: }   E t r a fil a : 'unk.txt Peauahmam n e en me ]  Enter a mode (max 3 characters): w  Successful opening junk.txt in mode w. Enter x to exit, any other to continue.  J  Enter a filename: morejunk.txt Enter a mode (max 3 characters): r Error opening morejunk.txt in mode r.  Enter x to exit, any other to try again. x   № В строках 15—18 эта программа приглашает пользователя ввести имя файла и режим его открытия. После ввода имени в строке 22 делается попытка открыть  файл и присвоить указатель на него переменной fp. Оператор if в этой строке может слу-  День 16-й. Работа с файлами 403 
жить примером хорошего стиля программирования. Он проверяет, не равен ли указатель открытого файла значению NULL. Если fp He равен NULL, то выводится сообщение о том, что открытие файла прошло успешно и можно пр0должать работу. Если указатель равен NULL, выполняется блок else оператора if. B этом блоке (строки 33—42) выводится сооб- щение о возникшей проблеме. Программа задает пользователю вопрос, следует ли про- Должать работу. Поэкспериментируйте с различными именами файлов и режимами их открытия, чтобы посмотреть, какие из них вызовут сообщение об ошибке. В приведенных выше результатах работы программы попытка открыть файл more junk.txt B режиме r закончилась неудачей, потому что такого файла не существовало на диске. Если случается ошибка, программа пре- доставляет возможность повторить ввод данных или завершить работу. Чтобы намеренно вы- звать ошибку, введите недопустимое имя файла наподобие [ ].  Запись и чтение данных  Программа, работающая с дисковыми файлами, может выполнять чтение данных из фай- ла, запись данных в файл или обе эти операции в различных сочетаниях. Запись данных в файл может выполняться тремя способами.  I Используя форматированный вывод, можно сохранять данные в оформленном, упо- рядоченном виде. Этот способ допускается для использования только с текстовыми файлами. Основным применением форматированного вывода можно считать создание файлов текстовой и числовой информации типа электронных таблиц или баз данных. Этим способом неудобно создавать файлы данных, которые затем должна считывать другая программа на С.  I Можно сохранять данные порциями по одному символу или строке, используя средст- ва символьного вывода. Технически возможно использовать этот способ в двоичном режиме, хотя это нетривиальная задача, и поэтому лучше ограничиться текстовым ре— жимом. Основным применением этого способа следует считать организацию тексто- вой (но не числовой) информации в таком виде, чтобы ее затем могла читать другая прикладная программа на С или программы наподобие текстовых редакторов.  I Наконец, можно использовать средства блочного вывода для непосредственного со- хранения в файле целого участка (блока) памяти. Этот способ применяется только с двоичными файлами. Блочный вывод— лучшее средство подготовки данных для их последующего чтения другими программами на С.  При чтении данных из файла у вас есть все те же три возможности: форматированный ВВОД, символьный ВВОД и блочный ввод. Практически всегда природа данных в файле одно- значно определяет то, каким способом они должны считываться или записываться. Обычно данные считываются в том же режиме, в каком они записывались, но это не обязательное требование. Однако для чтения данных в режиме, отличающемся от режима их записи, тре— буется очень хорошее знание языка С и форматов применяемых файлов. В приведенных выше описаниях способов вв0да-вывода файловой информации предла- гаются некоторые типичные применения каждого из способов. Однако это не набор строгих правил. Язык С очень гибкий (это Одно из его бесспорных преимуществ!), так что сообрази- тельный программист всегда найдет лучший способ для решения своей конкретной задачи. Начинающим же программистам лучше следовать приведенным рекомендациям, по крайней мере первое время.  404 Неделя 3. Основные вопросы 
Форматированный ВВОД-ВЫВОД  Форматированный ввод—вывод предназначен для работы с данными, организованными и оформленными специфическим образом. Средства этого ввода-вывода полностью аналогич— ны средствам форматированного ввода с клавиатуры и вывода на экран —— функциям printf () и scanf () —— изученным на занятии 14. Вначале рассмотрим форматированный вы— вод, а затем займемся вводом.  Форматированный ВЫВОД  Форматированный вывод выполняется библиотечной функцией fprintf( ). Прототип этой  _функции находится в заголовочном файле stdio.h и выглядит следующим образом:  int fprintf(FILE *fp, char *fmt, ...);  Первым ее аргументом является указатель на структуру FILE. Для записи данных в кон—  r. крегный файл следует передать в функцию указатель, возвращенный функцией fopen() при "открытии файла.  ? 4 . \  \.  r I " ‘; 1 д`  \! .,  Второй аргумент ——- это строка формата. Строки формата рассматривались на занятии 14 B связи с функцией printf( ). Строки формата в функции fprintf( ) ничем не отличаются по своему назначению, содержат те же элементы и подчиняются в точности тем же требовани— ям. За подробностями обращайтесь к материалу занятия 14. Последний аргумент функции fprintf () представляет собой многоточие. Как это пони- мать? В прототипе функции многоточие обозначает переменный список аргументов. Другими словами, в дополнение к указателю на структуру файла и строке формата функция fprintf( ) может принимать дополнительные аргументы в количестве ноль, один и т.д. В этом fprintf() и printf() полностью аналогичны. Дополнительные аргументы являются пере- менными или выражениями, значения которых следует вывести в заданный поток. Помните, что функция fprintf() работает точно так же, как printf( ), за единственным ис- ключением: она посьшает выводимые данные в поток, заданный первым в списке аргументов. Если в качестве потока указать stdout, то различия между работой этих функций и вовсе исчезнут. * В листинге 16.2 демонстрируется использование fprintf( ).  Ё-Листинг 1 6.2. fprintf .с — эквивалентность форматированного вывода в  ‚% &   ‚г. тн»  ‚ 1‘ L  › _\ ! \ \  -\ і  @  :n 76):“,me xv нн)  '*' 'Т'Ч “гп 3;! W»  файл и в поток stdout  1: /* демонстрация функции fprintf(). */ 2: #include <stdlib.h> 3: #include <stdio.h> 4: 5: void clear_kb(void); 6: 7: int main( void ) 8: { 9: FILE *fp; 10: float data[5]; 11: int count; 12: char filename[20]; 13: 14: puts("Enter 5 floating-point numerical values."); 15:  День 16-й. Работа с файлами 405 
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:  }  {  }  for (count = 0; count < 5; count++) scanf("%f", &data[count]);  /* Ввод имени файла и его открытие. Вначале поток stdin */ /* очищается от оставшихся в нем символов. */  c1ear_kb();  puts("Enter a name for the file."); gets(fi1ename);  if ( (fp = fopen(fi1ename, "w")) == NULL) {  fprintf(stderr, "Error opening file %s.", filename); exit(1); }  /* Запись числовой информации в файл и поток stdout. */  for (count = 0; count < 5; count++)  { fprintf(fp, "\ndata[%d] = %f", count, data[count]); fprintf(stdout, "\ndata[%d] = %f", count, data[count]); } fclose(fp); printf("\n“); return 0;  void c1ear_kb(void) /* Очистка stdin OT лишних символов. */  char junk[80]; gets(junk);  Enter 5 floatin - oint numerical values. Резцпьшаш 3 14159 g р  406  9.99 1.50 3. 1000.0001 Enter a name for the file. numbers.txt data[0] = 3.141590 data[1] = 9.990000 data[2] = 1.500000 data[3] = 3.000000 data[4] = 1000.000122  Неделя 3. Основные вопросы 
Может показаться странным, что программа выводит на экран число , 1000. 000122 при введенном значении 1000. 0001. Но это не ошибка в программе. “это обычный результат того, как компилятор С хранит числа. Иногда вещественные числа с авающей точкой не удается запомнить с идеальной точностью, поэтом ,! могут возникнуть :n01106HbIe небольшие погрешности. В строках 37 и 38 этой программы вызывается функция fprintf( ), посылаюшая форма- Ёированный текст и числовые данные в поток stdout и в файл, имя которого ввел пользова- gens. Единственное различие между этими двумя строками заключается в первом аргументе Ёфункции, т.е. потоке вывода. После выполнения программы просмотрите содержимое файла с именем numbers .с (или тем именем, которое вы ему присвоили) с помощью текстового ре- ГГактора. Файл должен находиться в том же каталоге, что и программа. Убедитесь, что текст в f айле идентичен тексту, выведенному на экран. ‚1 Обратите внимание, что в листинге 16. 2 используется функция clear __kb(), рассмотренная Ёі'іа занятии 14. Она необходима для удаления из потока stdin лишних символов, которые могли Ёшаться в нем после вызова scanf (). Если не очистить stdin, эти лишние символы (в частно-  і&`;1-и, символ конца строки) могут попасть в имя файла и вызвать ошибку при его создании.   7>о    орматированный ввод  ‚, Для форматированного ввода используется библиотечная функция fscanf( ), совершенно іі» алогичная по своему назначению функции scanf () (CM. материал занятия 14) с тем исклю- {Гением, что ввод выполняется из заданного файлового потока, а не из stdin. BOT прототип ункции fscanf( ):  д.т- fscanf(FILE *fp, const char *fmt, ...);  Аргумент fp — это указатель структурного типа FILE, возвращенный из fopen( ), a fmt — : атель на строку формата, задающую правила считывания потока ввода. Строка формата 5>"строена точно так же, как и в функции scanf( ). Наконец, многоточие (. . .) указывает на на-  Г 'рые следует поместить введенные данные. Прежде чем заняться функцией fscanf(), рекомендуем повторить материал занятия 14, 1v сающийся функции scanf(). Эти две функции работают совершенно одинаково, только '- canf() выполняет ввод данных из заданного файлового потока, а не из stdin. Для демонстрации работы f scanf( ) вам понадобится текстовый файл с несколькими чис-  1H ющий ВИД. 3612.3 45 87.001 ::00.02 31.00456 1.0005  Теперь скомпилируйте и запустите программу из листинга 16.3.  ›. l  фистинг 1 6. 3. fscanf. с — чтение форматированных данных из файла помощью функции fscanf( )  n»  №  #—  %%   /* Чтение форматированных данных с использованием fscanf(). */ #include <stdlib.h> #include <stdio.h>  Lump-4 ..  тг" п- чгчтчтд: ›.с' чать—№. ..  @ень 16-й. Работа с файлами 407 
int main( void )  float f1, f2, f3, f4, f5;  .... ОЮФЧФШФ  FILE *fp; : if ( (fp = fopen("INPUT.TXT", "r")) == NULL) 11: { 12: fprintf(stderr, "Error opening file.\n"); 13: exit(1); 14: } 15: 16: fscanf(fp, "%f %f %f %f %f", &f1, &f2, &f3, &f4, &f5); 17: printf("The values are %f, %f, %f, %f, and %f\n.", 18: f1, f2, f3, f4,.f5); 19: 20: fclose(fp); 21: return 0; 22: }   The values are 123.449997, 87.000999, 100.019997, 0.004560, and Peaunhmam 1.000500   при…“ Неточность машинного представления чисел является причиной того, что выведенные значения отличаются от введенных на очень малые величи- ны. Например, число 100.02 выводится как 100.01999.     Manna Эта программа вводит пять чисел из заранее созданного файла и затем отобра- жает их на экране. Вызов fopen() B строке 10 открывает файл в режиме чтения. Там же проверяется, правильно ли открылся файл. Если при открытии файла произошла ошибка, то в строке 12 выводится сообщение об ошибке, и программа завершает работу (строка 13). В строке 16 демонстрируется использование функции fscanf( ). За исключе— нием ее первого параметра, в остальном функция fscanf() идентична scanf( ), которая неоцнократно использовалась на протяжении этой книги. Первый параметр указывает на файл, из которого программа должна читать данные. Можете проделать еще ряц экспери— ментов с функцией fscanf ( ), создавая новые файлы исходных данных для ввода с помо- щью функции f scanf( ).   Символьный ввод-вывод  В отношении файлов термин символьный ввод-вывод относится к вводу-выв0ду как оди- ночных символов, так и целых строк. Вспомните, что строка— это последовательность из нескольких символов с завершающим ее специальным символом конца строки. Символьный вв0д-выв0д следует применять к текстовым файлам (точнее, открытым в текстовом режиме). В следующих разделах описывается несколько функций символьного вв0да—выв0да. Затем будет рассмотрена демонстрационная программа.  408 Неделя 3. Основные вопросы 
имвольный ввод .11 ' Для символьного ввода из файлов в языке С имеется три функции: getc( ) и fgetc( ) счи-  ывают одиночные символы, а fgets( ) —— строки символов. Функции getc() и fgetc( ). Эти две функции полностью идентичны и взаимозаменяе- ы. Они вв0дят Один символ из заданного потока вв0да. Прототип функции getc( ) наХОДится ‘файле stdio.h и имеет следующий вид: 3.1 getc(FILE *fp); * Аргумент fp —— это указатель на поток ввода, возвращенный из функции fopen( ) при от- %(.ытии файла. Функция возвращает введенный символ или EOF B случае ошибки. Функция getc( ) уже использовалась ранее в наших примерах программ для вв0да симво- o с клавиатуры. Вот еще Один пример гибкости потоков ввола-вывода: одна и та же функ- - может выполнять ввод как из файла, так и с клавиатуры. i Если функции getc( ) и f getc( ) возвращают одиночный символ, то почему возвращаемые и значения имеют тип int? Причина заключается в том, что при чтении из файла нужно еть возможность считывать и распознавать признак конца файла, который в некоторых сгемах имеет тип іп1:, а не char. Мы уВИДим функцию getc( ) B работе чуть позже, в лис- inre 16.10.  „ mm Для ввода отдельных символов также предназначена функция getchar( ). Однако она выполняет ввод только из потока stdin, a He из файлов.      _ Функция fgets( ). Для чтения строки символов из файла используется библиотечная ункция f gets( ). Ona имеет следующий прототип: : а1 *fgets(char *str, int n, FILE *fp);  Аргумент 51:1 представляет собой указатель на буфер, в который следует поместить вве- нную строку; n ——максимальное число символов, которое разрешается ввести; fp —— указа- …, ь на структуру FILE, возвращенный из функции fopen( ) при открытии файла. Функция fgets() считывает символы из потока fp B память, начиная с адреса в указа- , е 51:1. Символы считываются до тех пор, пока не встретится символ новой строки или Пока не будет прочитано п-1 символов. Ввод заканчивается тогда, когда случается первое этих событий. Установив n равным количеству байт, выделенных для буфера 51:1, мы :penompauxaem переполнение буфера и запись в неразрешенные области памяти. Вводится _менно п—1, а не n символов, чтобы в буфер поместился еще и нулевой завершающий сим- ол \0, который автоматически добавляется в конец строки функцией fgets( ). После ус- ешного завершения ввода fgets( ) возвращает указатель 51:1. Значение NULL возвращается Одном из двух случаев:  I прежде чем функция ввела хотя бы один символ, встретился конец файла (EOF) или *, произошла другая ошибка ввода. В этом случае с0держимое буфера 51:1 остается не-  Ё тронутым; I функция fgetc() ycnena ввести один или несколько символов в буфер 51:1, а затем ; встретился конец файла или произошла другая ошибка. Тогда после возвращения NULL  буфер 51:1 содержит случайный набор символов. Как ВИДите, функция fgets() не всегда вводит целую строку (т.е. последовательность Ёсимволов до символа конца строки). Если ввод п—1 символа закончился раньше, чем встре- “гился конец строки, то f gets( ) завершается. Следующая операция ввода из файла начинается   ЁЦень 16-й. Работа с файлами 409 
с того же места, на котором завершилась предыдущая. Для гарантированного ввода только целых строк позаботьтесь о том, чтобы размер буфера и количество символов n были доста— точно большими.  Символьный вывод  Имеется несколько функций символьного вывода, которые необходимо знать: putc(), fputc( ), puts() и fputs( ). Функции putc() и fputc( ). Библиотечные функции putc() и fputc( ) записывают оди— ночный символ в заданный поток вывода. Функция putc( ) имеет следующий прототип, объ- явленный в файле stdio.h:  int putc(int ch, FILE *fp);  Аргумент ch— 3T0 символ, который нужно вывести. Как и в других функциях сим— вольного вывода, он формально имеет тип int, но фактически используется только его младший байт. Аргумент fp —— это указатель на поток, ассоциированный с файлом (возвращается из функции fopen( ) при открытии файла). Функция putc() возвращает вы- веденный символ (в случае успеха) или EOF (B случае ошибки). Символическая константа EOF определена в файле stdio.h и имеет значение -1. Поскольку это значение не соответ- ствует никакому реальному символу, его можно использовать для указания конца файла (только в текстовом режиме).   “дышим Для вывода отдельных символов также используется функция putchar( ). - Однако она выполняет вывод только в поток stdout, a He в файлы.     Функция fputs( ). Для записи строки символов в файл используется библиотечная функ— ция fputs( ). Она практически идентична функции puts( ), изученной на занятии 14. Единст— венное отличие состоит в том, что для fputs( ) необходимо указать поток вывода. Кроме то- го, fputs( ) He добавляет символ конца строки автоматически —— если он нужен, программи—  сту приходится делать это самостоятельно другими средствами. Ее прототип объявлен в файле stdio.h:  char fputs(char *str, FILE *fp);  Аргумент str —— это указатель на строку с завершающим нулевым символом, которую следует вывести, а fp —— указатель на структуру FILE, возвращенный из функции fopen() при открытии файла. Строка str записывается в файл, причем нулевой завершающий символ \0 отбрасывается. Функция fputs() возвращает положительное значение в случае успеха или EOF, если произошла ошибка.  Блочный ввод-вывод  Блочный ввод-вывод используется в основном при сохранении данных, которые затем бу- дет считывать та же самая или другая программа на С. Этот способ ввода—вывода использует— ся только с двоичными файлами. В процессе блочного вывода блоки данных копируются це— ликом из памяти на диск. Блочный ввод представляет собой обратный процесс: блоки данных считываются с диска в память. Например, с помощью одного вызова функции блочного вы- вода можно записать на диск сразу целый массив типа double, a затем одним вызовом функ- ции блочного ввода считать весь этот массив с диска обратно в память. Блочный ввод-вывод выполняется функциями fread( ) и fwrite( ).  410 Неделя 3. Основные вопросы 
Функция fwrite( )  Библиотечная функция fwrite() записывает блок данных из памяти в двоичный файл. Вот ее прототип, объявленный в файле stdio.h:  int fwrite(void *buf, int size, int count, FILE *fp);  Аргумент buf представляет собой указатель на участок памяти, содержащий данные для записи в файл. Он объявлен с типом void —— это означает, что аргумент может быть указате- лем любого типа. Аргумент size указывает размер в байтах отлельных элементов данных в блоке, а аргу— мент count — их общее количество. Например, если нужно записать в файл массив из 100 целочисленных значений, то size будет равен 2 (переменные типа int занимают два байта), а count будет равен 100 (в массиве 100 элементов). Для вычисления аргумента size можно воспользоваться операцией sizeof( ). Аргумент fp, как и ранее, является указателем на структуру FILE, возвращенным из функции fopen() при открытии файла. Функция fwrite() возвращает количество элементов данных, записанных в файл. Если это количество меньше аргумента count, то произошла какая-то ошибка. Для проверки правильности вывода обычно используется следующая конструкция: if( (fwrite(buf, size, count, fp)) != count) fprintf(stderr, “Error writing to file.");  Ниже приведено несколько примеров использования функции fwrite( ). Для записи в файл одной переменной x типа double используется следующий оператор:  fwrite(&x, sizeof(double), 1, Ер);  Массив data[ ], содержащий 50 структур типа address, можно записать любым из двух способов:  fwrite(data, sizeof(address), 50, fp); fwrite(data, sizeof(data), 1, fp);  Первый оператор записывает массив из 50 элементов, каждый длиной со структуру address. Bo втором операторе массив data считается одним элементом. Результаты выпол- нения этих операторов совершенно одинаковы. В следующем разделе рассматривается функция fread( ), a затем приводится пример про- граммы с использованием функций fread( ) и fwrite( ).  Функция fread()  Библиотечная функция fread( ) считывает блок данных из двоичного файлового потока в память. Вот ее прототип, объявленный в файле stdio.h:  int fread(void *buf, int size, int count, FILE *fp);  Аргумент buf представляет собой указатель на участок памяти, в который помещаются данные из файла. Как и в функции fwrite( ), указатель имеет тип void. Аргумент size указывает размер (в байтах) отдельных элементов данных считываемого блока, а count— их общее количество. Вспомните, что аналогичные аргументы имеет и функция fwrite( ). Для вычисления аргумента size часто используется операция sizeof( ). Аргумент fp — это, как всегда, указатель на структуру FILE, возвращенный функцией fopen( ) при открытии файла. Функция f read( ) возвращает количество считанных элементов данных. В случае ошибки или достижения конца файла это число может быть меньше аргу— мента count.  День 16—й. Работа с файлами 411 
В листинге 16.4 демонстрируется работа функций fwrite( ) и f read( ).  Листинг 1 6.4. direct.c — использование функций fwrite() и fread() для блочного ввода—вывода   1: /* Блочный файловый ввод-вывод c помощью fwrite() и fread(). */ 2: #include <stdlib.h> 3: #include <stdio.h> 4: 5: #define SIZE 20 6: 7: int main( void ) 8: { 9: int count, array1[SIZE], array2[SIZE]; 10: FILE *fp; 11: 12: /* Инициализация массива array1[]. */ 13: 14: for (count = О; count < SIZE; count++) 15: array1[count] = 2 * count; 16: 17: /* Открытие файла B двоичном режиме. */ 18: 19: if ( (fp = fopen("direct.txt", "wb")) == NULL) 20: { 21: fprintf(stderr, "Error opening file."); 22: exit(1); 23: } 24: /* Сохранение массива array1[] B файле. */ 25: 26: if (fwrite(array1, sizeof(int), SIZE, fp) != SIZE) 27: { 28: fprintf(stderr, "Error writing to file."); 29: exit(1); 30: } 31: 32: fclose(fp); 33: 34: /* Открытие того же файла для чтения в двоичном режиме. */ 35: 36: if ( (fp = fopen("direct.txt", "rb")) == NULL) 37: { 38: fprintf(stderr, "Error opening file."); 39: exit(1); 40: -} 41: 42: /* Считывание данных в массив array2[]. */ 43: 44: if (fread(array2, sizeof(int), SIZE, fp) != SIZE) 45: { 46: fprintf(stderr, "Error reading file.");  412 Неделя 3. Основные вопросы 
47: exit(1);    48: } 49: 50: fclose(fp); 51: 52: /* Вывод обоих массивов, чтобы показать, что они идентичны. */ 53: 54: for (count = 0; count < SIZE; count++) 55: printf("%d\t%d\n", array1[count], array2[count]); 56: return 0; 57: } 0 0 _… 4 4 6 6 8 8 10 10 12 12 14 14 16 16 18 18 20 20 22 22 24 24 26 26 28 28 30 30 32 32 34 34 36 36 38 38  B листинге 16.4 демонстрируется использование функций fread() и fwrite( ). B строках 14—15 инициализируется массив. Затем в строке 26 он записывается в файл на диск путем вызова функции fwrite( ). B строке 44 эти же данные считываются функцией fread( ) B другой массив. Наконец, оба массива выводятся на экран для того, чтобы продемонстрировать полную идентичность данных в двух массивах (строки 54—55). При сохранении данных с помощью функции fwrite( ) особых ошибок, как правило, не бывает, разве что из-за каких-то проблем с диском. А вот с fread( ) следует быть осторож- ным. Функция fread( ) воспринимает данные на диске просто как последовательность байт, не имея ни малейшего понятия, какую информацию они представляют. Например, в 16-разряцной системе блок из 100 байт может содержать 100 переменных типа char, 50 ne- ременных типа int, 25 ——- типа long, или 25 —— типа float. Если попросить функцию fread( ) считать этот блок в память, она беспрекословно подчинится. Однако если блок при записи представлял собой массив типа int, a считывается в массив типа float, то никакой ошибки не случится, но результаты окажутся, мягко говоря, неожиданными. При написании программ следует правильно пользоваться функцией fread( ), считывая данные в переменные и масси- вы соответствующих типов. Заметьте, что в листинге 16.4 все результаты вызова функций fopen( ), fwrite( ) и fread( ) проверяются на отсутствие ошибок.   День 16-й. Работа с файлами 413 
Буферизация файлов: закрытие и очистка буферов  Закончив работу с файлом, его следует закрыть с помощью функции fclose( ). Эта функ- ция уже использовалась в примерах программ ранее. Она имеет следующий прототип:  int fclose(FILE *fp);  Аргумент fp является указателем на структуру FILE, ассоциированную с потоком; fclose( ) возвращает 0 B случае успеха или -1 B случае ошибки. При закрытии файла его бу- фер очищается (записывается в файл и удаляется из памяти). Можно также закрыть все от- крытые потоки сразу (кроме стандартных stdin, stdout, stdprn, stderr и stdaux) с помо— щью функции fcloseall( ). Она имеет следующий прототип:  int fcloseall(void);  Функция очищает буферы всех файлов и возвращает количество закрытых потоков. При завершении программы (по достижении конца функции шаіп() или путем вызова функции exit( )) все потоки автоматически очищаются и закрываются. Тем не менее, лучше закрывать потоки -—— особенно файловые — явным образом сразу после окончания работы с ними. Причина заключается в том, как устроены буферы потоков. При создании потока, связанного с дисковым файлом, автоматически создается ассоции- рованный с ним буфер. Буфер представляет собой блок памяти, используемый как временное хранилище данных, которые считываются из файла или записываются в него. Буферы необ- ходимы потому, что диски компьютера ориентированы на работу с блоками данных опреде- ленного размера, зависящего от характеристик устройства. Обычно это несколько сотен или тысяч байт. Точный размер блока не представляет сейчас для нас особого интереса. Буфер, ассоциированный с файловым потоком, служит посредником между потоком (в ко— тором элементарной единицей данных является символ или байт) и дисковым устройством (которое оперирует блоками). Когда программа записывает данные в поток, они фактически по- мещаются в буфер до тех пор, пока тот не заполнится. Затем все содержимое буфера записыва- ется одним блоком на диск. Аналогичный процесс имеет место при чтении данных из файла. Создание буфера и манипулирование им возложено на операционную систему и полностью ав- томатизировано. Программисту нет нужды заботиться об этом. (На самом деле в С есть не- сколько функций для работы с буферами, но эта тема выходит за пределы нашей книги.) На практике все это означает, что во время выполнения программы данные, которые программа записывает на диск, могут некоторое время находиться в буфере, а не на самом диске. Если программа “зависает” или отключается подача электроэнергии, или случается еще что-нибудь в этом роде, то данные в буфере теряются, и невозможно предсказать, что окажется в файле. Можно очистить буфер потока и без его закрытия. Это делается с помощью библиотеч- ных функций fflush( ) и flushall( ). Функция fflush( ) записывает буфер файла на диск, ос- тавляя файл открытым и доступным для использования. Функция flushall( ) очищает буфе— ры всех открытых потоков. Вот прототипы этих двух функций: int fflush(FILE *fp); int flushall(void);  Аргумент fp является указателем на структуру FILE, возвращенным из функции fopen() при открытии файла. Если файл открыт для записи, то fflush( ) очищает буфер и записывает его на диск. Если файл открыт для чтения, его буфер очищается. Функция fflush() возвра-  414 Неделя 3. Основные вопросы 
щает 0 B случае успеха и EOF B случае неудачи. Функция ff lushall( ) возвращает количество  ОТКРЫТЫХ ПОТОКОВ. Рекомендуется Не рекомендуется  рткройте файл прежде чем пытать- Не предполагайте что операции с фай- .ся читать из него данные или запи- % `Ёеми заканчиваются успешно, всегда про- жывать их.^ 3" w? ‘ ' } *веряйте, Как завершились операции от- слользуйте операцию {віиеоіі )3: при g гдкрытия; чтения, записи файлов. blaoae функций fwrite” и JJJfread( );, J: ' Неапоупотребляите функцией fcloseall (), ` ' % %        ‚“ :Jecnla у вас нет особых причин закрывать % %“сразу все потоки.  «№ ..А» мы.—ж Д…… .….. и…“… ч….. …и …,… „…с . .…..“    ь ...Амі „№№ Ц….»  Последовательный и произвольный [доступ  , С каждым открытым файлом ассоциирован указатель позиции (не путать с указателем как : адресной переменной). Это системный объект, указывающий место в файле, в котором в дан- Ёный момент выполняются операции ввода-вывода. Позиция всегда указывается в байтах от `начала файла. При открытии нового файла указатель позиции устанавливается на начало :файла и равен 0. Это неудивительно, поскольку вновь созданный файл имеет длину О, и дру- гих позиций в файле просто нет. При открытии существующего файла указатель устанавлива- тегся на конец файла, если файл открывается в режиме добавления Данных, и на его начало во ›всех остальных режимах. Функции файлового ввода-вывола, рассмотренные ранее в этой главе, активно использу- ют указатель позиции, хотя все манипуляции с ним остаются за кулисами. Операции чтения и ”записи выполняются в текущем положении указателя, и при этом его положение изменяется ;соответствующим образом. Например, после открытия файла для чтения и ввода из него де- ,сяти первых байт указатель оказывается на позиции 10, откуда и начинается следующая опе- -_ рация ввода. Поэтому при последовательном вводе всех данных из файла или последователь- " ном же выводе нег никакой необходимости принимать во внимание указатель позиции— функции ввода-вывода сами обо всем позаботятся. Если же вам необходим более полный контроль над операциями ввода—вывода, восполь- .. зуйтесь библиотечными функциями, которые определяют или изменяют положение указателя позиции. С их помощью можно осуществлять произвольный досгуп к файлу. Произвольный доступ означает, что данные можно считывать из файла или записывать в файл в любой же- лаемой позиции без перебора всех предыдущих его байт.  Функции fte||() и rewind()  Библиотечная функция rewind( ) используется для установки указателя позиции на начало файла. Ее прототип объявлен в файле stdio.h: void rewind(FILE *fp);  Аргумент fp — 3T0 указатель на структуру FILE, ассоциированный с требуемым потоком. Вызов rewind() устанавливает указатель позиции на начало файла (байт номер 0). Функция  День 16-й. Работа с файлами 415 
rewind( ) применяется в том случае, если из файла уже были считаны какие-то данные и при этом необходимо начать чтение с начала, не закрывая и открывая файл еще раз. Чтобы определить положение указателя в файле, используется функция ftell( ). Прото— тип этой функции объявлен в файле stdio.h:  long ftell(FILE *fp);  Аргумент fp представляет собой указатель на струкгуру FILE, возвращенный из функции fopen( ) при открытии файла. Функция ftell( ) возвращает значение типа long, которое ука- зывает номер текущего байта в файле от его начала (номер первого байта равен 0). В случае ошибки ftell( ) возвращает -1L (константу -1 типа long). B листинге 16.5 приводятся примеры использования функций rewind( ) и ftell( ).  Листинг 16.5. ftell .c _ использование функций rewind() и fte11()   /* демонстрация функций ftell() и rewind(). */ #include <stdlib.h> ` #include <stdio.h> #define BUFLEN 6  char msg[] = "abcdefghijklmnopqrstuvwxyz";  CD `] Ф … “› Ш N H II II II II II II II II  KO ll  int main( void )  {  H C) II II  FILE *fp; char buf[BUFLEN];  H H H ‚:> (...) M II II II  if ( (fp = fopen("TEXT.TXT", "w")) == NULL) {  O"! Ш II II  fprintf(stderr, "Error opening file."); exit(l);  H H 00 `! .. II  }  N H О \D II II  if (fputs(msg, fp) == EOF) {  N N N Ш … H II II II  fprintf(stderr, "Error writing to file."); exit(1);  N Ю … >> II II  }  N Ф ll  fclose(fp);  N N CD `] II II  /* Открытие файла для чтения. */  Ш Ю CD \.О II  Ш Ю II II II  if ( (fp = fopen("TEXT.TXT", "r")) == NULL) {  Ш ;...—А  fprintf(stderr, "Error opening file."); exit(l);  Ш Ш vb Ш II II  } printf("\nImmediately after opening, position = %1d", ftell(fp));  Ш Ш Ш " Ф … .. II II  /* Считывание пяти символов. */  415 Неделя 3. Основные вопросы 
38:  39: fgets(buf, BUFLEN, fp); 40: printf("\nAfter reading in %s, position = %ld", buf, ftell(fp)); 41: 42: /* Считывание следующих пяти символов. */ 43: 44: fgets(buf, BUFLEN, fp); 45: printf("\n\nThe next 5 characters are %s, and position now = %1d", 46: buf, ftell(fp)); 47: ‘48: /* Возвращение в начало потока. */ 49: 50: rewind(fp); 51: 52: printf("\n\nAfter rewinding, the position is back at %ld", 53: ftell(fp)); .54: 55: /* Считывание пяти символов. */ ›56: 57: fgets(buf, BUFLEN, fp); ;58: _printf("\nand reading starts at the beginning again: %s\n", but); :59: fclose(fp); 60: return 0;  g61: }  Ё PB' Ш!“ Immediately after opening, position = 0 $ 3mm 3 After reading in abcde, position = 5  The next 5 characters are fghij, and position now = 10   g After rewinding, the position is back at 0 н- and reading starts at the beginning again: abcde Ё Эта программа записывает строку msg B файл text. txt. Строка состоит из 26 букв латинского алфавита по порядку. В строках программы 14—18 файл Etext txt открывается для записи. Одновременно проверяется, было ли успешным открытие Ё “файла. B строках 20—24 сообщение msg записывается в файл с помощью функции fputs( ), Ё с проверкой правильности записи. В строке 26 файл закрывается путем вызова функции ‘… fclose( ). Ha этом процесс создания файла для последующей работы программы завершается. В строках 30—34 файл открывается снова, но на этот раз для чтения. В строке 35 на экран ‚ выводится значение, возвращенное функцией fte11( ). Заметьте, что указатель позиции нахо- Ёдится в начале файла. В строке 39 из файла считывается пять символов с помощью функции iz‘"fgets(). Эти пять символов и новое положение файлового указателя выводятся на экран в Ё _сгроке 40. Обратите внимание на правильное значение смещения, выв0димое функцией ftell( ). B строке 50 вызывается функция rewind( ), и указатель снова устанавливается в на- ЁЁ чало файла. В строке 52 положение указателя снова выводится на экран. Это сделано с целью гподтверждения того, что функция rewind() действительно установила указатель в начало Ёфайла. Еще одна операция ввода в строке 57 снова подтверждает, что текущее положение `‹указателя —— действительно в начале файла. В строке 59, перед самым окончанием програм- Ё мы, файл закрывается.  Mam: 16 й. Работа с файлами 417 
Функция fseek()  Библиотечная функция fseek() предоставляет дополнительные возможности для управ- ления указателем позиции в файле. С ее помощью этот указатель можно установить на любое произвольное место файла. Прототип функции находится в файле stdio.h и выглядит сле— дующим образом: int fseek(FILE *fp, long offset, int origin); Аргумент fp, как обычно, указывает на структуру FILE, ассоциированную с файлом. Рас- стояние, на которое следует сместить указатель позиции, задается аргументом offset, выра- женным в байтах. Аргумент origin задает точку, от которой отсчитывается смешение. Этот  аргумент может иметь одно из трех значений —— символических констант, определенных в файле io.h и перечисленных в табл. 16.2.  Таблица 1 6.2. допустимые значения аргумента origin функции fseek( )   Константа Значение Описание   SEEK_SET 0 Установка указателя на байт с порядковым номером offset, считая от начала файла SEEK_CUR 1 Смещение указателя на offset байт от его текущего положения SEBK_END 2 Установка указателя на расстоянии offset байт от конца файла   Функция fseek() возвращает 0, если смещение указателя было выполнено без ошибок, или ненулевое значение в случае ошибки. В листинге 16.6 функция fseek( ) используется для произвольного доступа к файлу.  Листинг 1 6.6. fseek. с — произвольный доступ к файлу при помощи функции fseek()   1: /* Произвольный доступ к файлу c помощью функции fseek(). */ 2: 3: #include <stdlib.h> 4: #include <stdio.h> 5: 6: #define MAX 50 7: 8: int main( void ) 9: { 10: FILE *fp; 11: int data, count, array[MAX]; 12: long offset; 13: 14: /* Инициализация массива. */ 15: 16: for (count = 0; count < MAX; count++) 17: array[count] = count * 10; 18: 19: /* Открытие двоичного файла для записи. */ 20: 21: if ( (fp= fopen(“RANDOM. DAT“ "wb")) == NULL)  418 Неделя 3. Основные вопросы 
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:  fprintf(stderr, "\nError opening file."); exit(l); }  /* Запись массива в файл и закрытие файла. */  іі ( (fwrite(array, sizeof(int), MAX, fp)) != МАХ)  { fprintf(stderr, "\nError writing data to file."); exit(l); } fclose(fp);  /* Открытие файла для чтения. */  іі ‹ (ір = f0pen("RANDOM.DAT", "rb")) == NULL) { fprintf(stderr, "\nError opening file."); exit(l); }  /* Спрашиваем пользователя, какой элемент вводить. Вводим номер */ /* и выводим элемент, завершая работу программы при вводе -1 . */  while (1)  { printf("\nEnter element to read, 0-%d, —1 to quit: ",MAX-l);  scanf("%ld", &offset);  if (offset < 0) break; else if (offset > МАХ-1) continue;  /* Перемещение указателя позиции к заданному элементу. */ іі ( (fseek(fp, (offset*sizeof(int)), SEEK_SET)) != 0) {  fprintf(stderr, "\nError using fseek()."); exit(l); }  /* Чтение одного целого числа. */ fread(&data, sizeof(int), 1, ір);  printf("\nElement %ld has value %d.", offset, data);  День 16-й. Работа с файлами 419 
72:  73: fclose(fp); 74: return О; 75: }   Enter element to read, 0—49, -1 to uit: 5 Резцльшат q  Element 5 has value 50. Enter element to read, 0—49, -1 to quit: 6  Element 6 has value 60. Enter element to read, 0-49, —1 to quit: 49  Element 49 has value 490. Enter element to read, 0—49, —1 to quit: 1  Element 1 has value 10. Enter element to read, 0—49, —1 to quit: 0  Element 0 has value 0. Enter element to read, 0—49, —1 to quit: —1  № Строки 14—35 этой программы аналогичны соответствующим строкам лис- тинга 16.5. В строках 16—17 инициализируется массив с именем data, содержа-  щий 50 элементов типа int. Значение каждого элемента равно его индексу, умноженному на десять. Затем массив записывается в двоичный файл RANDOM.DAT. Двоичный режим записи задается аргументом "wb" B строке 21. В строке 39 файл снова открывается для чтения в двоичном режиме, а затем начинается бесконечный цикл while. B этом цикле пользователя приглашают ввести номер элемента массива, который он хочет увидеть на экране. Заметьте, что в строках 53—56 проверяется, не выходит ли этот номер за пределы файла. А можно ли прочитать элемент, нах0дящийся вне этих пределов? Да, в языке С это возможно —— так же, как и вых0д за пределы массива. Результаты чтения из области за концом файла (или перед его началом) будут непредсказуе— мыми, поэтому всегда лучше проверять операции файлового ввода—вывода (как это делается в программе в строках 53—56). После ВВОДа номера элемента оператор в строке 60 передвигает указатель позиции на нужное смещение путем вызова fseek( ). Используется аргумент 5ЕЕК_$Е'1‘, поэтому смеще- ние отсчитывается от начала файла. Обратите внимание, что фактическое смещение в байтах от начала файла равно не offset, a произведению offset Ha размер элементов массива. В строке 68 указанное значение считывается, а в строке 70 выводится на экран.  Определение конца файла  Бывает так, что программе точно известна длина файла, с которым она работает, и нет нужды в специальных средствах для распознавания конца этого файла. Например, если с по- мощью функции fwrite() B файл был записан массив из 100 целочисленных элементов дли— ной по два байта, то ясно, что файл имеет длину 200 байт. Однако бывает и так, что длина  420 Неделя 3. Основные вопросы 
файла неизвестна, а данные тем не менее считывать нужно _— от начала и до самого конца. Есть два способа для того, чтобы определить, достигнут ли конец файла. При чтении файла в текстовом режиме посимвольно можно проверять, не встретился ли символ конца файла. В файле stdio.h определена символическая константа EOF, равная -1 —— то есть значению, не соответствующему никакому “реальному” символу. Как только функция символьного ввода считывает EOF из текстового потока, это значит, что достигнут конец фай- ла. Например, можно организовать такой цикл считывания с проверкой конца файла:  while ( (с = fgetc(fp))!= EOF )  B двоичных потоках невозможно определить конец файла путем проверки значения -1, потому что байт данных из двоичного потока вполне может иметь это значение. В результате ввод данных закончится преждевременно. Вместо этого используется библиотечная функция feof( ), работающая как с текстовыми, так и с двоичными файлами:  int feof(FILE *fp);  Аргумент fp является указателем на структуру FILE, возвращенным из функции fopen( ) , при открытии файла. Функция feof( ) возвращает О, если конец файла еще не достигнут, или ‘*` ненулевое значение, если конец файла уже достигнут. Если при вызове feof ( ) обнаружен ко- нец файла, то никакие дальнейшие операции чтения из этого файла не допускаются до тех пор, пока не будет вызвана функция rewind( ) или fseek( ), либо же файл не будет закрыт и снова открыт. В листинге 16.7 демонстрируется использование функции feof( ). После того, как про- грамма попросит ввести имя файла, выберите один из существующих на компьютере тексто- вых файлов, например, файл исходного кода на С или заголовочный файл наподобие stdio. h, и введите его имя. Все, что требуется _— это чтобы файл находился в текущем каталоге. В противном случае вам прндегся ввести еще и путь к файлу как часть его имени. Программа считывает файл по одной строке за раз и отображает строки в потоке stdout, пока функция feof( ) не обнаружит конец файла.  Листинг 1 6.7. feof .c — использование функции feof( ) для обнаружения конца файла   1: /* Обнаружение конца файла. */ 2: #include <stdlib.h> 3: #include <stdio.h> 4: 5: #define BUFSIZE 100 6: 7: int main( void ) 8: { 9: char buf[BUFSIZE]; 10: char filename[60]; 11: FILE *fp; 12: 13: puts("Enter name of text file to display: "); 14: gets(filename); 15: 16: /* Открытие файла для чтения. */ ‚17: if ( (fp = fopen(filename, "r")) == NULL) 18: {  Edema 16-й. Работа с файлами 421 
19: fprintf(stderr, “Error opening file."); 20: exit(1); 21: } 22: 23: /* Если не достигнут конец файла, считать строку и отобразить ее. */ 24:  25: while ( Ifeof(fp) ) 26: { 27: fgets(buf, BUFSIZE, fp); 28: printf("%s",buf); 29: } 30: 31: fclose(fp); 32: return 0; 33: }   Enter name of text file to diSplay: Peaunhmam hello . с  #include <stdio.h> int main(void ) { printf(“Hello, world."); return(0);  }  Цикл while B этой программе (строки 25—29) достаточно типичен для программ, - выполняющих последовательную обработку данных. Пока не достигнут конец файла, повторяется выполнение операторов в теле цикла (строки 27—28). Как только функция feof() при очередном вызове возвращает ненулевое значение, цикл заканчивается, файл 3a- крывается, и программа прекращает работу.  Рекомендуется Не рекомендуется  Используйте функции геніхісц ) ’ ли * Ё Не считывайте данные из облаСтей па- {веещ fp, SEEK __SE'I;, 0 ) дпя переуста- Ё мяти после конца или до начаЛа файла. новки указателя позиции в начало файла Ё ЧТОбЫ избегнуть ЭТЧГО ПРОВЭРЯЙТЭ "0- Используйте функцию feof() для про- ЁЛОЖЗНИЭ указателя „ верки окончания файла при работе с Ё H’e используйте константу 30? "P" 9350‘ %      ›мош   двоичными файлами те с двоичными файлами.  ЧМ AWMWE—‘V \: I‘M м rw \W \wwv-wv-enr- Мик-ъ „№ m №0 ММ „\ д‘“… 6 UV~ нмщч" ow „.и-чом чём…-., _. ` м..…. _.мщюцщ. .». w. v“ A   in „Миног-Мази“ WAG“? W::n  Функции управления файлами  Под управлением файлами будем понимать операции над файлами в целом, без чтения или записи данных, т.е. копирование, переименование и удаление. В стандартной библиотеке С есть функции для удаления и переименования файлов, а функцию копирования файлов можно написать самостоятельно.  422 Неделя 3. Основные вопросы 
Удаление файлов  Для удаления файла используется библиотечная функция remove( ). Ее прототип нах0дит- ся в файле stdi0.h и выглядит следующим образом:  int remove( const char *filename );  Переменная *filename является указателем на имя файла, который следует удалить. (Имена файлов были рассмотрены ранее на этом же занятии.) Указанный файл не должен быть открытым в момент удаления. Если файл существует, операционная система его удаляет точно так же, как при выполнении команды DEL B командной строке DOS или Windows либо команды rm B UNIX. B этом случае функция remove( ) возвращает 0. Функция возвращает -1 в тех случаях, когда файл не существует, является доступным только для чтения, программа не имеет надлежащих прав доступа к нему, а также в случае ЦРУгих ошибок. В листинге 16.8 демонстрируется использование функции remove( ). Соблюдайте осто- рожность при работе с этой программой: удаленный файл пропадает навсегда.  Листинг 1 6.8. remove . с — использование функции remove( ) для удаления “файла c диска   ‘ 1  \ 1: /* демонстрация функции remove(). */ .:". 2 : Г'З: #include <stdio.h> Q34: ё 5: int main( void ) Ё_б‘ { ?„7: char filename[80]; 32 8: Ё29: printf(“Enter the filename to delete: "); $10: gets(filename); 311: _ €12: if ( remove(filename) == 0) §§3:, printf(“The file %s has been deleted.\n“, filename); Ёдд: else £15: fprintf(stderr, “Error deleting the file %s.\n", filename); gjfiz return 0;   Enter the filename to delete: *.bak Error deleting the file *.bak. Enter the filename to delete: list1414.bak The file listl414.bak has been deleted.   B строке 9 эта программа приглашает пользователя ввести имя файла, который ‚, нужно удалить. В строке 12 для удаления этого файла вызывается функция ,Ёшоуы ). Если она возвращает значение 0, то файл удаляется, и выводится сообщение 06 % Если возвращается ненулевое число, это значит, что случилась ошибка, и удаление а не состоялось.    Рекомендуем.“,чтобы программа всегда спрашивала пользователя. дейст— % вительно ли он хочетяудалитьфайл.`  .... . …. „."  - 16-й. Работа с файлами 423 
Переименование файлов  Для переименования файла, существующего на диске, служит функция rename( ). BOT ка— ков прототип этой функции, находящийся в файле stdio.h:  int rename( const char *oldname, const char *newname ); Аргументы oldname и newname должны удовлетворять описанным ранее требованиям к именам файлов. Еще ОДНО ограничение состоит в том, что оба имени должны ссылаться на один и тот же логический диск компьютера: этим методом нельзя переименовать (переместить) файл на другой диск. Функция rename( ) возвращает 0 B случае успеха или -1  в случае неудачи. Ошибки могут быть вызваны следующими причинами (их список далеко не полон).  I Файл с именем oldname He существует. I Файл с именем newname уже существует. I Предпринята попытка переименовать файл, переместив его на другой диск. Использование функции rename( ) иллюстрируется листингом 16.9.  Листинг 1 6.9. renameit. с -— использование функции rename( ) для изменения имени файла на диске   1: /* Переименование файла c помощью функции rename(). */ 2: 3: #include <stdio.h> 4: 5: int main( void ) 6: 7: char oldname[80], newname[80]; 8: 9: printf(“Enter current filename: "); 10: gets(oldname); 11: printf("Enter new name for file: "); 12: gets(newname); 13: 14: if ( rename( oldname, newname ) == 0 ) lS: printf("%s has been renamed %s.\n“, oldname, newname); 16: else 17: fprintf(stderr, "An error has occurred renaming %s.\n", oldname); 18: return 0; 19: }   Р h 3 Enter current filename: list1609.c ШШ [B [I] Enter new name for file: renameit.c  list1609.c has been renamed renameit.c.  МШШЗ Листинг 16.9 демонстрирует всю мощь языка С. Всего лишь 18 строк кода реа— лизуют одну из популярных команд всех операционных систем, причем в друже—  ственной к пользователю манере. В строке 9 у пользователя запрашивают имя файла, кото- рый должен быть переименован. В строке 11 предлагается ввести новое имя. Вызов функции rename() с0держится в операторе if в строке 14. Этот оператор проверяет, прошло ли пере—  424 Неделя 3. Основные вопросы 
- ›,ьит-  Ф” ца № ,  "‘еы  именование файла без ошибок. Если это так, в строке 15 выводится сообщение об успешном завершении операции. В противном случае в строке 17 выводится сообщение об ошибке.  Копирование Файлов  Часто бывает необходимо создать копию файла — ero точный дубликат под другим име— нем (или с тем же именем, но в другом каталоге) . В системе DOS это делается с помощью команцы COPY. B других операционных системах также имеются аналогичные команды. А как  * это сделать в программе на языке С? Стандартной библиотечной функции на этот случай не  существует, поэтому придется написать нашу собственную. На первый взгляд это может показаться непростым делом, но в действительности на язы— ке С благодаря потокам ввода-вывода эта задача решается совершенно элементарно. Вот что необходимо для этого сделать:  1. Открыть исходный файл для чтения в двоичном режиме. (Двоичный режим необходим, чтобы иметь возможность копировать файлы любых типов, а не только текстовые.)  2. Открыть файл—копию для записи в двоичном режиме.  3. Прочитать байт из исходного файла. Помните, что при открытии указатель позиции авто— матически устанавливается в начало файла, так что его не нужно переустанавливать спе— циальным оператором.  4. Если функция feof() обнаруживает конец исходного файла, то копирование завершено. Можно закрыть оба файла и вернуться в вызывающую программу. 5. Если конец файла еще не достигнут, записать символ в файл—копию и вернуться к п. 3.  Листинг 16.10 содержит функцию f ile_copy( ), в которую передаются имена исходного и конечного файлов. Эта функция выполняет копирование в точности так, как описано выше. Если при открытии любого из файлов случается ошибка, то функция не пытается выполнить копирование, а сразу возвращает -1. После завершения копирования оба файла закрываются, и в главную программу возвращается значение 0.  Листинг 1 6. 1 О. copyit . с -— функция копирования файлов   1: /* Копирование файла. */ 2: 3: #include <stdio.h> 4: 5: int file_copy( char *oldname, char *newname ); 6: 7: int main( void ) 8: { 9: char source[80], destination[80]; 10: 11: /* Ввод имен исходного файла и его копии. */ 12: 13: printf("\nEnter source file: "); 14: gets(source): 15: printf("\nEnter destination file: "); 16: gets(destination); 17: 18: if ( fi1e_copy( source, destination ) == 0 )  День 16—й. Работа с файлами 425 
19: puts(“Copy operation successful“);  20: else 21: fprintf(stderr, "Error during copy operation"); 22: return(0); 23: } 24: int fi1e_copy( char *oldname, char *newname ) 25: { 26: FILE *fold, *fnew; 27: int c; 28: 29: /* Открытие исходного файла для чтения в двоичном режиме. */ 30: 31: if ( ( fold = fopen( oldname, "rb" ) ) == NULL ) 32: return -1; 33: 34: /* Открытие файла-копии для записи в двоичном режиме. */ 35: 36: if ( ( fnew = fopen( newname, "но“ ) ) == NULL ) 37: { 38: fclose ( fold ); 39: return -1; 40: } 41: 42: /* Чтение исходного файла no одному байту; если */ 43: /* не достигнут конец файла, выполняется запись */ 44: /* байта в файл-копию. */ 45: 46: while (1) 47: { 48: с = fgetc( fold ); 49: 50: if ( lfeof( fold ) ) 51: fputc( с, fnew ); 52: else 53: break; 54: } 55: 56: fclose ( fnew ); 57: fclose ( fold ); 58: 59: return 0; 60: }   \ Enter source file: list1610.c Hflflhflfllh  Enter destination file: tmpfile.c Copy operation successful  Функция file_copy( ) универсальна — она копирует любые файлы от маленьких текстовых до гигантских исполняемых. Однако и у нее есть свои недостатки. Если  426 Неделя 3. Основные вопросы 
на диске уже существует файл с тем же именем, что и копия, он будет удален без предупреж- дения. Вот вам хорошее упражнение по программированию: измените функцию file_copy( ) так, чтобы она проверяла, существует ли файл под тем же именем, что и будущая копия. Если это так, то функция должна спросить пользователя, хочет ли он затереть существующие дан- ные и записать новый файл поверх них. Функция шаіп() в листинге 16.10 должна быть вам хорошо знакома. Она практически идентична функции шаіп( ) в листинге 16.9, за исключением строки 14. Вместо функции rename( ) B этой программе используется функция f і1е_сору( ). Поскольку стандартной функции с таким именем в С нет, она определяется прямо в программе в строках 24—60. B строках 31——32 в двоичном режиме чтения открывается исходный файл fold. B строках 36—40 открывается файл-копия fnew — B двоичном режиме записи. Обратите внимание, что в стро- _‚ ке 38 исходный файл закрывается, если при открытии файла-копии произошла ошибка. Цикл while B строках 46—54 выполняет фактические операции по копированию файла. В строке 48 ‚из исходного файла fold считывается символ. В строке 50 проверяется, не встретился ли при этом конец файла. Если конец файла действительно обнаружен, выполняется оператор break, который и завершает работу цикла while. Если же конец файла еще не достигнут, символ за- писывается в файл-копию fnew. B строках 56 и 57 оба файла закрываются, а затем выполня- ется возвращение в функцию шаіп( ).  Использование временных файлов  Некоторые программы в ходе выполнения пользуются одним или несколькими времен- ными файлами. Такие временные файлы создаются программой для выполнения каких-либо оперативных задач во время ее работы, а затем удаляются перед ее завершением. Поэтому при создании временного файла его имя не играет особой роли, раз файл все равно будет уда- лен. Все, что нужно, —— это позаботиться о том, чтобы имя временного файла не совпало с именем уже существующего файла. Стандартная библиотека С содержит функцию tmpnam( ), которая создает имя, удовлетворяющее требованиям к именам файлов и не совпадающее ни с каким из имеющихся имен. Ее прототип в файле stdio.h объявлен следующим образом:  char *tmpnam(char *в); Аргумент s должен быть указателем на достаточно большой символьный буфер, в кото- рый будет помещено созданное имя. Можно также передать в функцию указатель NULL —— B этом случае имя временного файла помещается в буфер, создаваемый функцией tmpnam( ),  после чего функция возвращает указатель на него. В листинге 16.11 демонстрируются оба способа использования функции tmpnam( ) для создания имен временных файлов.  Листинг 1 6. 1 1 . tempname. с — создание имен временных файлов c помощью функции tmpnam( )   /* Создание имен для временных файлов. */  #include <stdio.h>  int main( void )  char buffer[10], *c;  ЮЧФШЬШЮН ..  День 16-й. Работа с файлами 427 
9: /* Помещение имени в существующий буфер. */  10: 11: tmpnam(buffer); 12: 13: /* Получение еще одного имени, на этот раз */ 14: /* во внутренний буфер функции. */ 15: 16: с = tmpnam(NULL); 17: 18: /* Вывод имен на экран. */ 19: printf("Temporary name 1: %s", buffer); 20: printf("\nTemporary name 2: %s\n", c); 21: return О; 22: }   Temporary name 1: \séus. Pnagnhmam Temporary name 2: \s3us.1  (”mm Имена для временных файлов, созданные в вашей системе. по всей ве- роятности будут отличаться от приведенных выше.      Эта программа всего лишь генерирует и выводит на экран имена для временных файлов, не создавая при этом никаких файлов. В‘ строке 11 имя помещается в за- ранее созданный символьный массив buffer. B строке 16 указатель на символьную строку, возвращенный функцией tmpnam( ), присваивается переменной с. В реальной программе это имя использовалось бы для создания временного файла, который перед окончанием про- граммы следовало бы удалить. Эти операции иллюстрирует следующий фрагмент кода:  char tempname[80]; FILE *tmpfile; tmpnam(tempname); tmpfile = fopen(tempname, "н"); /* Откройте в нужном режиме */ fclose(tmpfi1e); remove(tempname);       Рекомендуется Не рекомендуется Удаляите созданные вами B программе 3 Не удаляйте файл, котоЁЫй Mozicegr5 вам * *временные файлы так как они не уда— * еще понадобиться * — …Т„_дг‚,ь:,‚і* іляются автоматически. : :,… : ,.; …,… ‹ E; од,: :*, “мы, , і—„с :ьгчътё; ш...“... ...... „№ ....-. ...… … 22.23.. . .. ……№……3..…_„.^………і…і-д………4 май-„Д… “ ‘ ’ С”: ’ ***,“ ‘55 ` „„„„… ‚ :   Резюме  На этом занятии вы научились работать с файлами в программах на С. В языке С файл воспринимается как поток (последовательность символов)— точно так же, как любой из стандартных потоков, рассмотренных на занятии 14. Поток, ассоциированный с файлом, необходимо открыть перед использованием, а по окончании работы с ним — закрыть. Файл можно открыть в текстовом либо двоичном режиме.  428 Неделя 3. Основные вопросы 
 ЕЁ одходит для выполнения своей задачи ввода-вывода данных, организованных определен- :ым образом. ' У каждого открытого файла имеется ассоциированный с ним указатель позиции. Этот азатель определяет, в каком месте файла в данный момент должен выполняться ввод или вёывод данных. Позиция отсчитывается в байтах от начала файла. В некоторых режимах вво- Eye-351mm указатель позиции обновляется автоматически, и программисту нет нужды об этом №отиться В режиме произвольного доступа указателем позиции можно манипулировать с Ё и мощью стандартных библиотечных функций С. В языке С имеются функции для элементарных операций по управлению файлами, таких _ .; удаление и переименование. В ходе этого занятия также была разработана функция для ; пирования файлов, которой нет в стандартной библиотеке.  ЁЁ; ЁЁіж  Вопросы и ответы  Можно ли указывать в именах файлов путь к ним (диск и каталог) при работе с айловыми функциями наподобие remove( ), rename( ), fopen( ) и другими? Да. Можно использовать как имя с полным путем, так и одно только имя. Если имя файла ЁЁъиведено без пути к нему, то файловая функция разыскивает его в текущем каталоге. Пом- , что обратную косую черту в пути к файлу (\) необходимо изображать специальным 'имволом —— двойной косой чертой (\\). Также не забудьте, что в системе UNIX для разделе- ',! ия имен каталогов используется прямая косая черта (/ ).  „‘ `." ;},- г.:-ь  Можно ли считывать данные из области за концом файла? "д Технически, да. Можно также считывать данные из области перед его началом. Однако зультаты подобных манипуляций могут оказаться плачевными. Ввод из файлов аналогичен . . боте с массивами: в обоих случаях необходимо найти элемент данных по заданному сме- ению от начала отсчета. Если вы используете функцию f seek( ), удостоверьтесь в том, что  ;Не выходите за пределы файла.  !??!-  Что случится, если не закрыть файл после окончания работы с ним? Хороший стиль программирования подразумевает закрытие всех открытых файлов после то, как работа с ними окончена. По умолчанию файл должен автоматически закрываться и выходе из программы, но рассчитывать на это не следует. Если не закрыть файл явным разом, то позже можно не получить доступа к нему, так как операционная система будет считать что файл все еще используется для ввода или вывода.  тп  “if?”  “дій:  а м...  Сколько файлов можно держать открытыми одновременно? На этот вопрос трудно ответить Однозначно. Ограничение на количество одновременно ‚открытых файлов зависит от установленных значений системных переменных. В системе E DOS это количество определяется значением системной переменной FILES (причем откры- тыми файлами считаются и выполняемые в данный момент программы). За более подробной „, информацией обращайтесь к руководствам по операционным системам.  Можно ли считывать файл последовательно с помощью функций произвольного доступа? При последовательном чтении файла нет никакой необходимости в применении функций наподобие fseek( ), поскольку указатель позиции всегда автоматически передвигается имен-  Ё’День 16-й. Работа с файлами 429 
но в то место, откуда следует читать следующий элемент данных. Конечно, для последова— тельного чтения из файла можно пользоваться и f seek( ), но это не даст абсолютно ника- кого выигрыша.  Коллоквиум  В ЭТОМ КОЛЛОКВИУМе вам предлагаются контрольные ВОПРОСЫ ДЛЯ закрепления получен- НЫХ знаний, а также упражнения ДЛЯ приобретения практического опыта В их применении.  Контрольные вопросы  1. В чем разница между текстовыми и двоичными потоками? 2. Что должна сделать программа перед тем, как обращаться к данным, находящимся в файле?  3. Какую информацию необходимо передать в функцию fopen( ) для открытия файла, и ка- кое значение возвращает эта функция?  Перечислите три общих метода файлового ввода-вывода.  Назовите два способа чтения данных из файла.  Какое значение имеет константа EOF?  Для чего используется константа EOF?  Как распознать конец файла в текстовом и двоичном режимах?  создашь  . Что такое указатель позиции в файле и как можно изменить его положение?  10. Каково положение указателя позиции в файле сразу после открьггия этого файла? (Если вы не уверены в ответе, посмотрите листинг 16.5.)  Упражнения  1. Напишите фрагмент кода для закрытия всех файловых потоков.  2. Продемонстрируйте два различных способа для переустановки указателя позиции в нача— ло файла. 3. Поиск ошибок. Есть ли ошибка в следующем коде? FILE *fp; int с ;  if ( ( fp‘= fopen( oldname, "rb“ ) ) == NULL ) return -1; while (( с = fgetc(fp) ) != ЕОР ) fprintf( stdout, "%c", с );  fclose (fp );  Для следующих упражнений существует слишком много возможных правильных реше- ний. Поэтому ответы к ним не приводятся.  4. Напишите программу для вывода текстового файла на экран.  430 Неделя 3. Основные вопросы 
5.  6.  7.  8.  9.  Напишите программу для открытия файла и вывода его на печать (в поток stdprn). Про- грамма должна печатать 55 строк на каждой странице.  Измените программу из упражнения 5 таким образом, чтобы на страницах также печата- лись заголовки. Заголовок должен содержать имя файла и номер страницы.  Напишите программу для открытия файла и подсчета символов в нем. Программа должна выводить на экран подсчитанное количество символов.  Напишите программу, которая бы открывала существующий текстовый файл и копирова- ла его в новый файл с заменой всех букв нижнего регистра на соответствующие буквы верхнего. Остальные символы следует оставить без изменений.  Напишите программу, которая бы открывала файл, считывала его блоками по 128 байт и вы- водила содержимое каждого блока на экран в шестнадцатеричном виде и формате ASCII.  10.`Напишите функцию для открытия временного файла в заданном режиме. Все временные  файлы, созданные этой функцией, должны автоматически закрываться и удаляться по окончании программы. (Подсказка: примените библиотечную функцию atexit( ).)  День 16—й. Работа с файлами 4 
Самостоятельная работа 5  П0дсчет символов  Пришло время выполнить очередную самостоятельную работу. Сегодняшняя программа под именем count_ch открывает заданный текстовый файл и подсчитывает количество раз, которое каждый символ встречается в этом файле. Учитываются все стандартные символы латинской клавиатуры, включая буквы верхнего и нижнего регистра, цифры, пробелы и знаки препинания. Результаты подсчетов выводятся на экран. Кроме того, что эта программа может представлять практический интерес, она еще и демонстрирует некоторые интересные приемы программирования. Ее вывод в файл можно перенаправить c помощью операции ›. Напри- мер, следующая команда запустит программу, перенаправив ее вывод в файл Results.txt вместо экрана:  COUNT_CH > RESULTS . TX‘I‘  Результаты вывода, перенаправленного в файл, можно просмотреть или распечатать с по- иощью текстового редактора.  Пистинг С5. count_ch .с — программа подсчета символов в файле   /* Подсчет количества вхождений каждого символа в текстовом файле. */ #include <stdio.h> #include <stdlib.h> int file_exists(char *filename); int main( void ) { char ch, source[80]; int index; long count[127]; FILE *fp;  /* Ввод имени исходного файла. */ fprintf(stderr, "\nEnter source file name: "); gets(source);  /* Проверка существования исходного файла. */ if (lfile_exists(source))  { 
}  fprintf(stderr, "\n%s does not exist.\n", source); exit(1);  /* Открытие файла. */ іі ((ір = fopen(source, ”rb”)) == NULL) { fprintf(stderr, ”\nError opening %s.\n", source); exit(1); } /* Обнуление элементов массива. */ for (index = 31; index < 127 ; index++) count[index] = 0;  while ( 1 ) { ch = fgetc(fp); /* Закончить, если встретился конец'файла */ іі (іеоі(ір)) break; /* Считать только символы между 32 и 126. */ іі (ch > 31 && ch < 127) count[ch]++; } /* Вывод результатов. */ printf("\nChar\t\tCount\n"); for (index = 32; index < 127 ; index++) printf(”[%c]\t%d\n”, index, countfindex]); /* Закрытие файла и выход. */ іс1ове(ір); return(0);  int fi1e_exists(char *filename)  {  }  /* Возвращает TRUE, если файл с таким именем существует,  FALSE, если не существует. */  FILE *fp; if ((fp = іорел(іі1елаше, "r")) == NULL)  return 0;  else  fclose(fp); return 1;  Вначале рассмотрим функцию fi1e__exist( ) B строках 51—63. Получая в качест- ве аргумента имя файла, эта функция возвращает TRUE, если файл существует, и FALSE B противном случае. Проверка выполняется путем открытия файла для чтения (строка 56). Это функция весьма общего назначения, которую можно использовать во многих  других программах.  Самостоятельная работа 5. Подсчет символов 
Обратите внимание на то, что для вывода сообщений на экран используется не функция printf( ), a функция fprintf( ). Примером служит строка 14. Зачем это делается? Поскольку функция printf ( ) всегда посылает выводимые данные в поток stdout, пользователь не уви- дит никаких сообщений, если вывод программы перенаправить в файл. А с помощью fprintf( ) вывод можно направить в поток stderr, который всегда связан с экраном. Наконец, заметьте, как числовое значение каждого символа используется в качестве ин— декса массива, в котором хранятся результаты подсчета (строки 40 и 41). Например, числовой код 32 соответствует символу пробела, поэтому элемент count[32] содержит общее количе— ство пробелов в файле. После запуска программы она пригласит вас ввести имя файла:  Enter source file name: count_ch.c После ввода имени файла на экране появится список символов и количество каждого сим-  вола в файле. Вот как будет выглядеть результат после подсчета символов в исходном тексте самой же этой программы: '  Char Count [ ] 434 [П 1 ["] 14 [#1 2 [$1 0 [961 4 [&1 7- [‘1 0 [(1 29 [)1 29 [*1 24 [+1 6 [‚] 11 [-1 0 [.] l3 {/1 20 [01 4 [l] 11 [21 7 [31 4 [41 0 [51 0 [61 1 [71 4 [81 1 [91 0 [=1 1 [:1 27 [<] 5 [=] 10 [>1 3 [?] 0 [@] 0 [A1 1  434 Неделя 3. Основные вопросы 
[S]  Самостоятельная работа 5. Подсчет символов  МОШОФНФНООООШНММООНМОЧООМОНШФМШО  6  LB  35 25 100 50 4 23 62 0 1 27 9 69 40 18 0 51 43  
[t] 59  [u] 25 [v] 1 [w] 2 [x] 19 [у] 3 [2] 0 [{] 6 [|] 0 [}] б ['] 0  436 Неделя 3. Основные вопросы 
  Текстовые данные являются важным компонентом практически любой программы. Для  ff: 31x хранения и обработки в языке С используются строки символов. После наших предыду-  I Как определить длину строки Как скопировать и объединить строки Функции сравнения строк Поиск в строках Преобразование строк  Анализ символов  Определение длины строки  % ; Из материала прошлых занятий вы должны помнить, что в программах на С символьная Ё- строка представляет собой последовательность символов, начало которой указывает указа— тель символьного типа, а конец обозначается нулевым символом \0. Иногда бывает необхо- guano знать длину строки, т. е. количество символов в ней. Эту длину можно получить с по- Ё; мощью библиотечной функции strlen( ). Ее прототип находится в файле string.hz  size_t strlen(char *str);  Что это за тип —— size_t? Он определен в файле string.h как unsigned, так что фактиче— „ с'ки функция strlen( ) возвращает целое число без знака. Тип size_t используется во многих ` функциях, предназначенных для работы со строками. Просто запомните, что этот тип иден— тичен типу unsigned. Аргумент, передаваемый в функцию strlen() —— это указатель на строку, длину которой нужно узнать. Функция возвращает количество символов от начала str и до первого нулевого символа не включая его. Применение этой функции демонстрируется в листинге 17.1.  ЁЁ“?- "3“"? ' ' 
Листинг 1 7. 1 . strlen. с — использование функции strlen( ) для определения длины строки   /* Использование функции strlen(). */  #include <stdio.h> #include <string.h>  int main( void )  ФЧФШ-ЬШМЭ—д II II II II II II II II  { size_t length; 9: char buf[80]; 10: 11: while (1) 12: { l3: puts("\nEnter a line of text, a blank line to exit."); 14: gets(buf); 15: 16: length = strlen(buf); 17: 18: if (length != 0) 19: printf("\nThat line is %u characters long.", length); 20: else 21: break; 22: } 23: return 0; 24: }   Enter a line of text, a blank line to exit. реванша"! . Just do it!  That line is 11 characters long. Enter a line of text, a blank line to exit.  Программа совсем проста и всего лишь демонстрирует работу функции strlen( ). B строках 13 и 14 выводится приглашение и вв0дится строка buf.  B строке 16 функция strlen() вычисляет длину buf. Значение длины присваивается пере- менной length. Строка 18 проверяет, является ли строка пустой, т.е. равна ли ее длина нулю. Если строка не пуста, ее размер выводится в строке 19 программы.  Копирование строк  В библиотеке С имеется две функции для копирования строк. Из-за специфической техники работы со строками в С нельзя просто присвоить одну строку другой, как это до- пускается в некоторых других языках программирования. Необходимо выполнить копиро- вание исходной строки из занимаемого ею участка памяти в другой участок— туда, где чаходится строка-копия. Функции копирования строк имеют имена strcpy() и strncpy( ). Эбе они объявлены в заголовочном файле string.h, который необхолимо полключить для ах использования.  138 Неделя 3. Основные вопросы 
Функция strcpy()  Библиотечная функция strcpy( ) копирует целую строку в заданное место памяти. Вот ее прототип:  char *strcpy( char *destination, const char *source );  По определению, функция strcpy( ) выполняет копирование строки, находящейся по ад— ресу source (вместе с ее завершающим нулевым символом \0), B участок памяти, начинаю— щийся по адресу в указателе destination. Функция возвращает указатель на новую строку, destination. До вызова функции strcpy() необходимо выделить память для новой строки. Сама функ- ция не проверяет, достаточно ли памяти имеется по указанному ей адресу. Если распределе- ние памяти не выполнялось, то функция затрег strlen( source) байт новой информацией, на- чиная с адреса, указанного в destination. При этом могут возникнуть совершенно непред- сказуемые ошибки. Использование функции strcpy( ) проиллюстрировано в листинге 17.2.   @.“. Если для распределения памяти в программе используется функция ша11ос( ), \ то рекомендуется обязательно освобождать выделенную память с помо— щью функции free( ) после окончания работы с ней. Подробнее об этой функции пойдет речь на занятии 20.     Листинг 1 7.2. strcpy. c — распределение памяти перед вызовом функции strcpy()   1: /* демонстрация strcpy(). */ 2: #include <stdlib.h> 3: #include <stdio.h> 4: #include <string.h> 5: 6: char source[] = "The source string."; 7: 8: int main( void ) 9: { 10: char destl[80]; 11: char *dest2, *dest3; 12: 13: printf("\nsource: %s", source ); 14: 15: /* Копирование в destl допускается, поскольку dest1 указывает */ 16: /* на 80 байт выделенного объема памяти. */ 17: 18: strcpy(dest1, source); 19: printf("\ndest1: %s", destl); 20: 21: /* для копирования в dest2 необходимо выделить память. */ 22: 23: dest2 = (char *)malloc(strlen(source) +1); 24: strcpy(dest2, source); 25: printf("\ndest2: %s\n", dest2); 26:  День 174?. Операции над строками символов 439 
27: /* Копировать без выделения памяти нельзя ни в коем случае. */  28: /* Следующий оператор мог бы вызвать серьезные проблемы. */ 29: 30: /* strcpy(dest3, source); */ 31: return 0; 32: }   P8 source: The source string. ЗЦЛЫЕШП destl: The source string.  dest2: The source string.  B этой программе демонстрируется копирование строки как в символьный мас- сив (destl, объявленный в строке 10), так и в области памяти, определенные  указателями dest2 и dest3 (объявлены вместе в строке 11). В строке 13 текст исходной стро— ки выводится на экран. Затем эта строка копируется в destl с помощью функции strcpy() (строка 18). B строке 24 этот же текст копируется в dest2. Затем строки destl и dest2 выво- дятся на экран, чтобы продемонстрировать успешность выполненных операций копирования. Заметьте, что в строке 23 выполняется распределение нужного количества памяти с помощью функции ша11ос( ) для буфера dest2. Если скопировать строку в буфер, для которого не вы- делена память, можно получить непредсказуемые ошибочные результаты.  Функция strncpy()  Функция strncpy() аналогична strcpy( ) no своему назначению, с тем исключением, что с ее помощью копируется определенное количество символов. Она имеет следующий прото— тип:  char *strncpy(char *destination, const char *source, size_t п);  Аргументы source и destination указывают на начальную и конечную строки. Функция копирует не более п символов из source B destination. Если строка source короче, чем п символов, то к ней добавляется достаточное количество нулевых символов, чтобы всего в destination их копировапось ровно п. Если строка source длиннее, чем п символов, то к destination не добавляется завершающий нулевой символ. Функция возвращает указатель destination. Использование функции strncpy() демонстрируется в листинге 17.3.  Листинг 1 7.8. strncpy.c — демонстрация функции strncpy()   1: /* Использование фУнкции strncpy(). */ 2: 3: #include <stdio.h> 4: #include <string.h> 5: 6: char dest[] = ".........................."; 7: char source[] = "abcdefghijklmnopqrstuvwxyz"; 8: 9: int main( void ) 10: { 11: size_t п; 12:  440 Неделя 3. Основные вопросы 
13: while (1)  14: { 15: puts("Enter the number of characters to copy (1-26)"); 16: scanf("%d", &n); 17: 18: if (n > 0 && п< 27) 19: break; 20: } 21: 22: printf("\nBefore strncpy destination = %s", dest); 23: 24: strncpy(dest, source, n); 25: '26: printf("\nAfter strncpy destination = %s\n", dest); 27: return 0; »28: }   ___—  :` Enter th numb r f charact r t c 1-26 ,; Резцпыпат 15 e 8 ° 8 S ° op“ )  1) Г  Before Strncpy destination: ........ОООООООООООООООООО After strncpy destination = abcdefghijklmno...........  № Кроме работы функции strncpy( ), эта программа также демонстрирует эффек— : тивный способ ввода данных, гарантирующий их правильность. В строках 13—20 " содержится цикл while, который запрашивает у пользователя число от 1 до 26. Цикл продол— діжается до тех пор, пока не будет введено правильное число, так что программа даже не пе- рейдет к последующим операциям, не получив от пользователя правильные данные. После ‚того, как вводится число от 1 до 26, в строке 22 отображается исходное содержимое буфера dest, B строке 24 указанное пользователем количество символов копируется из source B dest, a B строке 26 на экран выводится окончательное содержимое dest.   ‘(lllllllle Убедитесь в том, что количество копируемых символов не превосходит длины буфера, в который они копируются. Помните также, что функция strncpy() не добавляет нулевой завершаЮщий символ в конец строки.     Функция strdup()  Существует еще одна функция копирования строк, o которой полезно знать. Ее имя —— strdup( ). По назначению она аналогична strcpy( ), однако сама выполняет распределение памяти для буфера, в который копируется строка, с помощью вызова функции malloc( ). Фактически, она делает то же самое, что мы вынуждены были проделывать своими силами !: листинге 17.2, вызывая ша11ос() для распределения памяти, а затем strcpy() для копиро— ваниястроки. Следует знать, что функция strdup( ) не определена в стандарте ANSI. Она входит в биб— лиотеки многих компиляторов, таких как Microsoft, Borland и Symantec C, но другие компи— ляторы могут ее и не поддерживать (или поддерживать в другом виде). Функция strdup() имеет следующий прототип:  ‚спа: *strdup( char *source );  Ёдень 17—й. Операции над строками символов 441 
Аргумент source представляет собой указатель на копируемую строку. Функция возвра- щает указатель на строку, в которую было выполнено копирование —— т.е. на буфер, создан- ный с помощью ша11ос( ) — или NULL, если успешное распределение памяти оказалось не— возможным. Использование функции strdup( ) демонстрируется в листинге 17.4.  Пистинг 1 7.4. strdup.c —- использование strdup( ) для копирования строки c автоматическим распределением памяти   /* Применение не-АЫБі-стандартной функции strdup(). */ #include <std1ib.h> #include <stdio.h> #include <string.h>  char source[] = “The source string.";  mqmmeWNH в. .. во .. .. во .. во  int main( void )  9: { 10: char *dest; 11: 12: if ( (dest = strdup(source)) == NULL) 13: { 14: fprintf(stderr, “Error allocating memory."); 15: exit(1); 16: } 17: 18: printf("The destination = %s\n", dest); 19: return 0; 20: }   The destination = The source strin . Реэцльшат g  Manna B ЭТОЙ программе выделение памяти для строки dest выполняется функцией strdup() автоматически. Она же копирует строку source B созданный буфер.  В строке 18 скопированная строка выводится на экран.  Конкатенация (сцепление строк)  Если вы еще не знаете термина “конкатенация” (который означает всего—навсего “сцеп- ление”), то у вас может возникнуть вопрос, что это такое и законно ли это. Ответим на это так: при конкатенации одна строка попросту присоединяется к концу другой, и в суд вас за это не потащат. В стандартной библиотеке С имеется две функции для сцепления строк: strcat( ) и strncat( ). Для работы с ними необходим заголовочный файл string.h.  Функция strcat()  Функция strcat( ) имеет следующий прототип:  char *strcat(char *str1, const char *str2);  442 Неделя 3. Основные вопросы 
Эта функция помещает копию строки str2 непосредственно после конца строки strl и ставит завершающий нулевой символ в конце получившейся новой строки. Предварительно необходимо позаботиться о том, чтобы в строке strl было достаточно места для хранения результата сцепления двух строк. Функция strcat() возвращает указатель на strl. Ее ис- пользование демонстрируется в листинге 17.5.  Листинг 1 7.5. strcat. с —— сцепление строк c помощью функции strcat( )   so co `я a" ш as ы ы H‘ .. .. .. .. .. .. .. .. ..  /* Применение функции strcat(). */  #include <stdio.h> #include <string.h>  char str1[27] = "a"; char str2[2];  int main( void )   10: { 41: int n; 32: $3: /* Помещение нулевого символа в конец строки str2[]. */ 34: $5: str2[1] = '\0'; 16: 17: for (n = 98; n< 123; n++) № { 49: str2[0] = n; #0: strcat(strl, str2); 31: puts(str1); $2: } $3: return 0; $1 } ‘ ab :? abcd abcde abcdef abcdefg abcdefgh abcdefghi abcdefghij abcdefghijk abcdefghijkl abcdefghijklm abcdefghijklmn abcdefghijklmno abcdefghijklmnop dbcdefghijklmnopq abcdefghijklmnopqr abcdefghijklmnopqrs  Wm, 17-й. Операции над строками символов  443 
abcdefghijklmnopqrst abcdefghijklmnopqrstu abcdefghijklmnopqrstuv abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvwx abcdefghijklmnopqrstuvwxy abodefghijklmnopqrstuvwxyz  № Буквам от b до z соответствуют коды ASCII от 98 до 122. Именно эти.символы используются в программе для демонстрации работы функции strcat( ). B цик- ле for, B строках 17—22, эти коды по очереди присваиваются элементам массива str2[0]. Поскольку str2[l] является нулевым символом (см. строку 15), в результате в буфер str2 no очереди помещаются строки "b", "c" и т.д. Каждая из этих строк прицепляется к strl (B строке 20), а затем strl отображается на экране (строка 21).  Функция strncat()  Библиотечная функция strncat( ) также выполняет конкатенацию строк, но при этом позволяет указать, сколько именно символов из исходной строки следует добавить в конец строки назначения. Она имеет следующий прототип:  char *strncat(char *strl, const char *str2, size_t n);  Если str2 содержит более n символов, то к strl добавляются только первые n из них. Если str2 содержит менее или ровно n символов, то к strl добавляется вся эта строка. В каждом из случаев к строке автоматически добавляется нулевой завершающий символ. Необходимо позаботиться о выделении достаточного количества памяти для строки strl, чтобы в нее поместился результат. Функция возвращает указатель на строку strl. B лис- тинге 17.6 с помощью функции strncat( ) достигается практически тот же результат, что и в листинге 17.5.  Листинг 1 7.6. strncat . с — сцепление строк с помощью функции strncat( )   l: /* Пример использования функции strncat(). */ 2: 3: #include <stdio.h> 4: #include <string.h> 5: 6: char str2[] = "abcdefghijklmnopqrstuvwxyz"; 7: 8: int main( void ) 9: { 10: char strl[27]; 11: int n; 12: 13: for (n=1; n< 27; n++) l4: { 15: strcpy(strl, ""); l6: strncat(strl, str2, n); 17: puts(strl); 18: }  444 Неделя 3. Основные вопросы 
a Peaunhmam аЬ  аЬс abcd abcde abcdef abcdefg abcdefgh abcdefghi abcdefghij abcdefghijk abcdefghijkl abcdefghijklm abcdefghijklmn abcdefghijklmno abcdefghijklmnop abcdefghijklmnopq abcdefghijklmnopqr abcdefghijklmnopqrs abcdefghijklmnopqrst abcdefghijklmnopqrstu abcdefghijklmnopqrstuv abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvwx abcdefghijklmnopqrstuvwxy abcdefghijklmnopqrstuvwxyz  № Может возникнуть вопрос, зачем нужна строка 15 c оператором strcpy(str1, " " ) ;. Этот оператор копирует в символьный буфер strl пустую строку, состоящую из  одного только нулевого символа. В результате первый символ строки strl —— т.е. str1[0] —— становится равным 0 (нулевому символу). То же самое можно было бы сделать с помощью операторов str1[0] = 0; или str1[0] = '\0';.  Сравнение строк  Строки сравниваются для того, чтобы выяснить, равны ли они между собой. Если строки не равны, то Одна из них “меньше”, а другая —— “больше”. Определения “больше” и “меньше” основываются на кодах символов ASCII. B случае букв порядок следования кодов полностью совпадает с алфавитным порядком —— c тем довольно странным исключением, что все буквы верхнего регистра (большие) “меньше”, чем буквы нижнего (маленькие). Это происходит по- тому, что большие буквы от А до Z представлены кодами ASCII от 65 до 90, тогда как малень- кие от a до 2 представлены кодами от 97 до 122. Таким образом, при сравнении с помошью функций С слово "ZEBRA" окажется меньшим, чем "арр1е". Библиотека ANSI C содержит функции для двух видов сравнения строк: сравнения двух целых строк и сравнения фрагментов строк, имеющих определенную длину.  День 17—й. Операции над строками символов 445 
Сравнение двух целых строк  Функция strcmp() предназначена для посимвольного сравнения двух строк. Она имеет следующий прототип:  int strcmp(const char *strl, const char *str2); Аргументы strl и str2 указывают на сравниваемые строки. Значения, возвращаемые функцией, перечислены в табл. 17.1. Обратите внимание, что оба строковых аргумента объяв—  лены константами, поскольку ни один из них не изменяется внутри функции. Применение strcmp( ) демонстрируется в листинге 17.7.  Таблица 1 7.1 . Значения, возвращаемые функцией strcmp()    Возвращаемое значение Смысл Отрицательное strl меньше str2 0 strl paBHa str2 Положительное strl больше str2    Листинг 1 7.7. strcmp . с — сравнение строк с помощью функции strcmp( )  1: /* Пример работы функции strcmp(). */ 2: 3: #include <stdio.h> 4: #include <string.h> 5: 6: int main( void ) 7: { 8: char strl[80], str2[80]; 9: int x; 10: 11: while (1) 12: { l3: 14: /* Ввод двух строк. */ 15: 16: printf("\n\nInput the first string, a blank to exit: "); 17: gets(strl); 18: 19: if ( strlen(strl) == 0 ) 20: break; 21: 22: printf(“\nInput the second string: "); 23: gets(str2); 24: 25: /* Сравнение и вывод результата. */ 26: 27: x = strcmp(strl, str2); 28: 29: printf("\nstrcmp(%s,%s) returns %d", strl, str2, x); 30: }  446 Неделя 3. Основные вопросы 
31: return 0; 32: }  Input the first string, a blank to exit: First string Input the second string: Second string strcmp(First string,Second string)returns -1 Input the first string, a blank to exit: test string Input the second string: test string strcmp(test string,test string)returns 0 Input the first string,a blank to exit: zebra Input the second string: aardvark strcmp(zebra,aardvark)returns 1  Input the first string, a blank to exit:   ”…“… В некоторых из систем UNIX функции сравнения строк не обязательно _ возвращают -1, если строки не равны. Тем не менее. при неравенстве строк возвращаемое значение обязательно будет ненулевым.  Стандарт ANSI требует лишь, чтобы возвращаемое значение было отри— цательным, нулевым или положительным в соответствующих случаях.     В этой программе демонстрируется использование функции strcmp( ). У пользо— вателя запрашивают две текстовые строки (см. строки 16, 17, 22 и 23), а затем  результат их сравнения c помощью функции strcmp() отображается на экране (строка 29). Поэкспериментируйте c этой программой, чтобы получить более полное представление o том, как данная функция выполняет сравнение. Попробуйте, например, ввести две строки, от- личающиеся только регистром символов: Smith и SMITH. Вы убедитесь, что функция strcmp( ) различает регистр символов, т.е. большие и маленькие буквы считаются разными.  Сравнение фрагментов строк  Библиотечная функция strncmp() сравнивает фрагмент заданной длины из одной строки c другой строкой. Она имеет следующий прототип:  int strncmp(const char *strl, const char *str2, size_t n); Эта функция сравнивает n символов из строки str2 co строкой strl. Сравнение выполня— ется до тех пор, пока не исчерпаются все n символов или не будет достигнут конец strl.  Способ сравнения и возвращаемые значения— те же, что и у функции strcmp( ). Строки сравниваются c учетом регистра. Применение этой функции показано в листинге 17.8.  День 17-й. Операции над строками символов 447 
Листинг 1 7.8. strncmp. c — сравнение фрагментов строк c помощью функции strncmp()   ФЧФшдШМН 00 00 00 00 00 00 00 со  char str1[] char str2[]  /* Пример использования функции strncmp(). */  #include <stdio.h> #include <string.h>  "The first string."; "The second string.";  9° int main( void )  10: { 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: }  size_t n, x;  puts(str1); puts(str2);  while (1) {  puts("\n\nEnter number of characters to compare, 0 to exit."); scanf("%d", &n);  if (n <= 0) break;  х = strncmp(str1, str2, n); printf("\nComparing %d characters, strncmp() returns %d.", n, x);  }  return 0;   Р The first string. %ШЪШИ‘ The second string.  448  Enter number of characters to compare, 0 to exit. 3  Comparing 3 characters, strncmp() returns 0.  Enter number of characters to compare, 0 to exit. 6  Comparing 6 characters, strncmp() returns —1. Enter number of characters to compare, 0 to exit. 0  Неделя 3. Основные вопросы  Эта программа сравнивает две символьные строки, объявленные и инициализи- рованные в строках 6—7. В строках 13—14 они выводятся на экран для пользова- теля. Затем в строках 16—27 выполняется цикл while, позволяющий произвести несколько 
сравнений. Если пользователь запрашивает сравнение фрагмента длиной 0 символов (строки 18—19), то программа заканчивает работу (строка 22). В противном случае в строке 24 вызывается функция strncmp( ), и результат ее работы выводится на экран в строке 26.  Сравнение двух строк без учета регистра  К сожалению, в стандартной библиотеке ANSI C нет функций для сравнения строк без различения регистра символов. Однако многие распространенные компиляторы С предостав- ляют нестандартные функции для этой цели. Например, компилятор Symantec предлагает функцию strcmpl( ), B библиотеке Microsoft на этот случай имеется функция _stricmp( ), a {в библиотеке Borland _— сразу две функции strcmpi( ) и stricmp( ). Чтобы выяснить наличие подобной функции в библиотеке вашего компилятора, обратитесь к его документации. При двызове такой функции строки Smith и SMITH окажутся равными. Измените строку 27 в лис- ринге 17.7 так, чтобы использовалась функция сравнения без учета регистра, и запустите про- Ёграмму еще раз.   оиск в строках  : Библиотека С содержит несколько функций, позволяющих выполнять поиск в строках. ными словами, эти функции определяют, встречается ли одна строка внутри другой, и ес- и да, то где именно. Всего таких функций шесть, и все они объявлены в заголовочном _‚ЁіЁч-оил string.h:  strchr() strrchr()  strc5pn() strspn() strpbrk() strstr()    ‚пункция strchr()  , Функция strchr( ) обнаруживает первое вхождение заданного символа в заданной строке. Ё! на имеет следующий прототип: har *strchr( const char *str, int ch) ;  Функция strchr( ) выполняет поиск в строке str слева направо, пока не найдет символ ch Elam пока строка не закончится нулевым символом. Если символ ch найден, функция возвра- 1 ает указатель на него, a B противном случае —— NULL. Ёщ Итак, когда функция strchr() находит символ, она возвращает указатель (адрес) этого имвола. Зная, что str указывает на первый символ в строке, можно вычислить положение скомого символа, отняв str OT возвращенного функцией значения. Эта операция иллюстри- Ёдііуется в листинге 17.9. Помните, что первый символ в строке имеет номер 0. Как и многие " оугие функции С, функция strchr() различает регистр символов, поэтому она не найдет, апример, букву "F" B строке "raf f le".   Ёіень 17-й. Операции над строками символов 449  . и 
Листинг 1 7.9. strchr.c — поиск символа в строке с помощью функции strchr( )   /* Поиск одиночного символа в строке с помощью strchr(). */  #include <stdio.h> #include <string.h>  int main( void )  ШЧФШОЬШЮН II II II II II II II II  { char *loc, buf[80]; 9: int ch; 10: 11: /* Ввод строки и символа. */ 12: 13: printf("Enter the string to be searched: "); 14: gets(buf); 15: printf("Enter the character to search for: "); 16: ch = getchar(); 17: 18: /* Поиск. */ 19: 20: loc = strchr(buf, ch); 21: 22: if ( loc == NULL ) 23: printf("The character %c was not found.", ch); 24: else 25: printf("The character %c was found at position %d.\n", 26: ch, loc—buf); 27: return О; 28: }   mammal" Enter the character to search for: C The character C was found at position 14.  - Enter the string to be searched: How now Brown Cow?  Эта программа разыскивает символ в символьной строке с помощью функции strchr( ), которая вызывается в строке 20. Функция возвращает указатель на первую позицию строки, в которой встречается заданный символ. Если символ не найден, возвращается NULL. B строке 22 проверяется, не равен ли указатель 10c значению NULL, и если да, то выводится соответствующее сообщение. Как мы уже говорили, порядковый номер символа в строке определяется вычитанием указателя на строку из адреса, который возвра- щает функция.  диализ ,  Функция strrchr()  Библиотечная функция strrchr( ) no своему назначению идентична функции strchr( ), 3a тем исключением, что она разыскивает последнее вхождение заданного символа в строку. Она имеет следующий прототип:  char *strrchr(const char *str, int ch);  450 Неделя 3. Основные вопросы 
Функция strrchr( ) возвращает указатель на последнюю позицию заданного символа ch B строке str или NULL, если этого символа в строке нет. Чтобы посмотреть, как она работает, в строке 20 листинга 17.9 замените strchr( ) на strrchr( ).  Функция strcspn()  Библиотечная функция strcspn() разыскивает в строке первое вхождение любого симво- ла из второй строки. Она имеет следующий прототип:  size_t strcspn(const char *str1, const char *str2);  Функция strcspn() начинает поиск с первого символа str1, проверяя, не встретится ли ей любой из символов строки str2. Важно запомнить, что эта функция ищет в str1 именно от— Ёдельные символы из str2, a не всю строку целиком. Как только совпадение найдено, функция 5i возвращает смещение от начала строки str1, описывающее местонахождение найденного сим— Чцола. Если ни одного совпадения не обнаружено, функция возвращает значение strlen(str1). 91o означает, что первое найденное соответствие найдено между завершающими нулевыми „! символами двух егрок. Использование функции strcspn( ) иллюстрируется листингом 17.10.  че?-иё;  L:  V  Листинг 1 7.1 O. strcspn . с — поиск в строке c помощью функции strcspn( )  д. 5 u   w1: /* Поиск в строке c помощью функции strcspn(). */ „ 2: 3 3: #include <stdio.h> ? 4: #include <string.h> f 5: ? 6: int main( void ) 7: { 8: char buf1[80], buf2[80]; 9: size_t loc; 10: 11: /* Ввод строк. */ 12: 13: printf(“Enter the string to be searched: “); 14: gets(buf1); 15: printf("Enter the string containing target characters: “); 16: gets(buf2); 17: 18: /* Поиск символов из второй строки в первой. */ 19: 20: loc = strcspn(bufl, buf2); 21: 22: if ( loc == strlen(buf1) ) 23: printf(“No match was found."); 24: else 25: printf("The first match was found at position %d.\n", loc); 26: return О; 27: }   PE Enter the string to be searched: How now Brown Cow? autumn Enter the string containing target characters: Cat  The first match was found at position 14.  День 17-й. Операции над строками символов 451 
Эта программа очень похожа на программу из листинга 17.9. Однако вместо по- иска одного заданного символа она выполняет поиск в строке любого из симво- лов другой строки. В строке 20 вызывается функция strcspn() с аргументами bufl и buf2. Если какой-либо из символов строки buf2 обнаруживается в строке buf 1, функция strcspn( ) возвращает смещение от начала bufl до местоположения найденного символа. В строке 22 проверяется возвращенное функцией значение. Если возвращена длина строки, значит, в пер- вой строке не бьш найден ни один символ из второй. В этом случае на экран выводится соот- ветствующее сообщение (строка 23). Если же символ был найден, выводится сообщение о его местонахожцении (порядковом номере) в строке.  Функция strspn()  Эта функция родственна предыдущей, strcspn( ), как показано ниже. Ее прототип име- етвид:  size_t strspn(const char *str1, const char *str2);  Функция strspn( ) перебирает строку str1 символ за символом, пока не встретит символ, которого нет в строке str2. Она возвращает номер позиции первого символа в строке strl, не входящего в строку str2. Можно сказать и так, что эта функция возвращает длину началь- ного фрагмента строки str1, состоящего только из символов, которые встречаются в str2. Если такой символ встретился сразу же, функция возвращает 0. Работа функции strspn( ) де- монстрируется в листинге 17.11.  Листинг 1 7. 1 1 . strspn .c — поиск в строке первого символа, не совпадающего с набором символов из заданной строки   1: /* Поиск в строке c помощью strspn(). */ 2: 3: #include <stdio.h> 4: #include <string.h> 5: 6: int main( void ) 7: { 8: char buf1[80], buf2[80]; 9: size_t 1ос; 10: 11: /* Ввод строк. */ 12: 13: printf(“Enter the string to be searched: "); 14: gets(buf1); 15: printf(“Enter the string containing target characters: "); 16: gets(buf2); 17: 18: /* Поиск по строке. */ 19: 20: 1ос = strspn(bufl, buf2); 21: 22: if ( 1ос == 0 ) 23: printf("No match was found.\n"); 24: else  452 Неделя 3. Основные вопросы 
25: printf("Characters match up to position %d.\n", loo-1); 26: return 0; _27: }  ___——  ‚‹. P33“ man Enter the string to be searched: How now Brown Cow? ё !” Enter the string containing target characters: How now what?  ; ‚у  Characters match Up to position 7.  Эта программа аналогична предыдущим примерам, только в ней для выполнения _ поиска вызывается функция strspn() (строка 20). Функция возвращает номер :'позиции символа (смещение от начала строки), в которой находится первый встретившийся `{символ, не принадлежащий к числу символов из строки str2. B строках 22—25 анализируется ;значение, которое возвращает функция, а затем вывоцится соответствующее сообщение.   5: ‚„  %,. ;Функция strpbrk() E Библиотечная функция strpbrk() аналогична strcspn() -— она также разыскивает в од- ёной строке первое появление любого из символов другой строки. Разница между ними состо- дёит в том, что strpbrk() не включает завершающий \0 в число анализируемых символов.  {Прототип этой функции таков:   I  {char *strpbrk(const char *strl, const char *str2);   Функция strpbrk() возвращает указатель на первый символ в strl, совпадающий с г-любым из символов str2. Если соответствие не найдено, возвращается NULL. Как уже гово—  Е … ;рилось в связи с функциеи strchr(), смещение первого совпадающего символа относи—  Ётельно начала строки можно получить, отняв указатель на строку strl от указателя на Ёсимвол (если он, конечно, не равен NULL). Для примера замените strcpsn() B строке 20  (листинга 17.10 на strpbrk( ).  Функция strstr()  Последняя и, пожалуй, самая полезная из всех функций С для поиска в строках— это функция strstr( ). Она ищет первое появление одной строки внутри другой, причем всей строки сразу, а не ее отдельных символов. Функция имеет следующий прототип:  char *strstr(const char *strl, const char *str2);  Функция strstr() возвращает указатель на первую позицию, с которой начинается строка str2 внутри строки strl. Если между строками нет такого соответствия, функция воз- вращает NULL. Если вторая строка имеет длину 0, функция возвращает указатель strl. Найдя место вхождения одной строки в другую, можно вычислить смещение str2 относительно на- чала strl с помощью вычитания указателей, как в случае с strchr( ). Поиск и сравнение вы- полняются с различением регистра символов. Использование функции strstr() иллюстриру- ется в листинге 17.12.  Листинг 1 7. 1 2. strstr. с — поиск одной строки в другой с помощью функции strstr( )   1: /* Поиск в строке с помощью функции strstr(). */ 2:  День 17—й. Операции над строками символов 453 
#include <stdio.h> #include <string.h>  3 4 5. 6: int main( void ) 7 8  = { : char *loc, buf1[80], buf2[80]; 9: 10: /* Ввод строк. */ 11: 12: printf("Enter the string to be searched: "); 13: gets(buf1); \ 14: printf(“Enter the target string: “); 15: gets(buf2); 16: 17: /* Поиск. */ 18: 19: loc = strstr(bufl, buf2); 20: 21: if ( loc == NULL ) 22: printf("No match was found.\n“); 23: else 24: printf("%s was found at position %d.\n“, buf2, loc-bufl); 25: return 0; 26: }   Рицльшаш  Enter the string to be searched: How now brown cow? Enter the target string: cow Cow was found at position 14.  Данная функция представляет еще один способ поиска в строках, на этот раз це—  лой строки внутри другой. В строках 12—15 пользователя приглашают ввести две строки. В строке 19 вызывается функция strstr() для поиска второй строки buf2 внутри первой bufl. Возвращается указатель на первую позицию второй строки внутри первой или NULL, если совпадение не обнаружено. В строках 21—24 проверяется значение loc, возвра- шенное функцией, и выводится соответствующее сообщение.    Рекомендуется  {Помните что у многих функций С для Ё работы со строками имеются аналоги. : * которые работают с фрагментами строк заданной длины Такие функции прини— % “ мающие длину фрагмента как один из ; ; своих аргументов обычно имеют имена g é типа випххщ) где символы xxx зависят ; Ё от конкретнои функции ‹ :5  м.. »‚о-им _щтжь-мцч „ ..; \ ___‘мч-ч-‚н   щ   454    Не рекомендуется  іНе забуцьте о том что в языке С “симво- Ё лы верхнего и HmKHero регистров разли—З 1 чаЮтся. так что например. a и А не CHM- Ё таются одинаковыми. ‚ '; ‹ … ”Ё  : і 5  ‚ ` !-.…» v № .Мы… „..,... _ „_- w u “w х……— 1 u» … ч…^„_н.ьь—„ ‚ц…-«до.…»  Неделя 3. Основные вопросы 
Преобразования символов в строках  Bo многих библиотеках С имеются две функции для изменения регистра символов внутри строки. Эти функции не определены в стандарте ANSI, поэтому отдельные компи— ляторы могут их не поддерживать или же поддерживать в каком—либо другом виде. Здесь мы даем их описание, потому что эти функции весьма полезны. Их прототипы содержатся в файле string.h для компилятора Microsoft C (B других средах разработки они должны иметьаналогичньцівиду  char *strlwr(char *str); char *strupr(char *str);  Функция strlwr( ) преобразует все буквенные символы строки str B нижний регистр, & функция strupr( ) делает прямо противоположное — преобразует все буквы в верхний регистр. Символы, не являющиеся буквами, не подвергаются никакому изменению. Обе функции возвращают указатель str. Обратите внимание, что эти функции не создают новых : строк, а выполняют преобразования в уже существующих. Работа этих функций проиллюст— рирована листингом 17.13. Помните, что для компиляции программы с нестандартными (не ”определенными в стандарте ANSI) функциями бывает необходимо отключить в параметрах компилятора совместимость со стандартом ANSI.  ‘Листинг 1 7. 1 3. upper.c —- преобразование регистра символов в строке c помощью функций strlwr() и strupr( )   1: /* Функции преобразования символов strlwr() и strupr(). */ 2: 3: #include <stdio.h> 4: #include'<string.h> 5: 6: int main( void ) 7: { 8: char buf[80]; 9: 10: while (1) 11: { 12: puts("Enter a line of text, a blank to exit.“); 13: gets(buf); 14: 15: if ( strlen(buf) == 0 ) 16: break; 17: 18: puts(str1wr(buf)); 19: puts(strupr(buf)); 20: } 21: return О; 22: }   Pg Enter a line of text, a blank to exit. ЗШЪШШБ Bradley L. Jones  bradley 1. jones  День 17-й. Операции над строками символов 455 
BRADLEY L. JONES Enter a line of text, a blank to exit.  B строке 12 этой программы пользователя приглашают ввести символьную строку. Затем в строке 15 проверяется, не введена ли пустая строка. В строке 18  выполняется преобразование к нижнему регистру и выв0д результата на экран. В строке 19 тот же текст выводится на экран в верхнем регистре. Эти функции входят в библиотеки компиляторов Dev-C++, Symantec, Microsofi, Borland C. Прежде чем пользоваться ими, следует проверить по документации компилятора, полдержи- ваются ли они. Если важна переносимость исходного к0да, избегайте пользоваться функция- ми, не определенными в стандарте ANSI.  Разные функции для работы со строками  В этом разделе мы расскажем о нескольких функциях для различных операций со строка- ми, не подпадающих ни п0д одну из рассмотренных ранее категорий. Все они определены в заголовочном файле string. h. Будут рассмотрены следующие функции: I strrev( ) I strset() I strnset( )  Функция strrev()  Функция strrev( ) изменяет порядок следования символов в строке на противоположный. Она имеет следующий прототип:  char *strrev(char *str);  B результате ее вызова все символы в строке str выстраиваются в порядке, противопо- ложном исходному. Завершающий нулевой символ, конечно, остается там же, где был —-—— в конце строки. Функция strrev() не определена в стандарте ANSI. Как уже говорилось, это означает, что ее могут поддерживать не все компиляторы (или поддерживать по-разному). Функция возвращает указатель str. После того, как мы рассмотрим также функции strset( ) и strnset( ), работа всех трех будет продемонстрирована в листинге 17.14.  Функции strset() и strnset()  Эти функции, как и предьтдущая, не входят в стандартную библиотеку ANSI C. Функция strset() заменяет все символы (а strnset( ) — указанное количество символов) на Один и тот же заданный символ. Их прототипы имеют вид:  char *strset(char *str, int ch); char *strnset(char *str, int ch, size_t n);  Функция strset() заменяет все символы в строке str, за исключением завершающего ну- левого, на символ ch. Функция strset() заменяег первые n символов строки. Если n больше  456 Неделя 3. Основные вопросы 
или равно strlen(str), то strnset() заменяет все символы в строке str. Использование этих функций демонстрируется в листинге 17.14.  Листинг 1 7. 1 4. strings . c — демонстрация функций strrev( ), strnset() и strset()    1: /* демонстрация функций strrev(), strset() и strnset(). */ 2: #include <stdio.h> 3: #include <string.h> 4: 5: char str[] = “This is the test string."; 6: 7: int main( void ) 8: { 9: printf(“\nThe original string: %s", str); 10: printf(“\nCalling strrev(): %s“, strrev(str)); 11: printf(“\nCalling strrev() again: %s“, strrev(str)); 12: printf(“\nCalling strnset(): %s", strnset(str, '!', 5)); 13: printf("\nCalling strset(): %s", strset(str, '!')); 14: return 0; ‘15: }   . ___ 1 й—  , P83 man The original string: This is the test string. ЦП Calling strrev(): .gnirts tset eht si sihT Calling strrev()again: This is the test string.  Calling strnset(): !!!!!із the test string. Calling strset(): !!!!!!!!!!!!!!!!!!!!!!!1  № В этой программе демонстрируются три различные функции работы со строка— ми. Демонстрация заключается в том, что на экран несколько раз выводится од-  :на и та же строка str. B строке 9 она выводится в своем исходном виде, в строке 10 —— с про— тивоположным порядком следования символов после вызова strrev( ), a B строке 11 —— после повторного вызова той же функции и возвращения символов к первоначальному поряцку. В строке 12 с помощью функции strnset() первые пять символов str заменяются восклица— тсльными знаками. Наконец, в строке 13 все символы заменяются на восклицательные знаки. Хотя три рассмотренные функции не определены в стандарте ANSI, они тем не менее включены в библиотеки компиляторов Dev-C++, Symantec, Microsoft и Borland C. При не— обходимости проверьте по документации вашего компилятора, поддерживает ли он эти функции.  Преобразования строк B числа  Нередко возникает необходимость преобразовать строковое представление числа в его фактическое числовое значение. Например, строку "123" можно преобразовать в значение 123 и присвоить его переменной типа int. Для преобразования строк в числа используются четыре функции. Их прототипы объявлены в заголовочном файле stdlib.h, a сами функции рассматриваются в следующих разделах.  День 17-й. Операции над строками символов 457 
Преобразование строк в целые числа  Для преобразования строки в целочисленное значение используется библиотечная функ— ция atoi( ). Ее прототип имеет вид:  int atoi(const char_*ptr);  Функция atoi() преобразует строку ptr B целое число, соответствующее строковому представлению. Кроме цифр, строка может содержать пробелы в начале, а также знак + или —. Преобразование начинается с первого символа строки и продолжается до тех пор, пока не встретится недопустимый в числе символ (например, буква или знак препинания). Получен- ное целое число возвращается в вызывающую программу. Если функция atoi() не находит в строке символов, пригодных для преобразования в число, то возвращает 0. B таблице 17.2 приведены некоторые примеры.  Таблица 1 7.2. Примеры преобразования строк в чИспа функцией atoi( )   Строка Значение, возвращаемое функцией atoi ()   "157" 157 "—1.6" —1 "+50x" 50 ”twelve" 0 "х506" 0   Первый пример очевиден. Во втором примере смущает то, что при преобразовании были ' пропущены символы ".6". Однако вспомните, что мы говорим o преобразовании строки в целое число —— при этом пробная часть числа отбрасьтвается. Третий пример также очевиден —— функция распознает знак +, считая его частью числа. В четвертом примере строка гласит "twelve", но функция atoi() не понимает слов и видит здесь не число, а всего лишь набор букв. Поскольку строка не начинается с цифры, функция возвращает 0. B последнем примере происходит то же самое.  Преобразование строк в длинные целые числа  Библиотечная функция atol() полностью аналогична atoi( ), но возвращает значение ти- па long. Прототип функции имеет следующий вид: long atol(const char *ptr);  Если взять строки из табл. 17.2 и применить к ним функцию atol( ), то получатся значе- ния, идентичные приведенным, но имеющие тип long.  Преобразование строк 8 числа типа long long  Функция atoll() аналогична по своему назначению функциям atoi() и atol( ), но пре- образует строки в значения типа long long. Она имеет следующий прототип:  long long atoll(const char *ptr);  458 Неделя 3. Основные вопросы 
Преобразование строк в числа с плавающей точкой  Для преобразования строк в вещественные числа с плавающей точкой (double) использу- ется функция atof( ). Она имеет следующий прототип:  double atof(const char *str);  Аргумент str указывает на преобразуемую строку. Она может содержать пробелы перед началом числа, а также знак + или -. Число может состоять из цифр от О до 9, десятичной  ‚точки, а также знака показателя степени е или Е. Если в строке не найдены символы, пригод— “ные для преобразования в число, atof() возвращает О. В табл. 17.3 приведены некоторые Т'примеры использования atof( ).  ”Таблица 1 7.3. Примеры преобразования строк 3 числа функцией atof ( )  2  ;…  i‘"12" 12:000000  &   Гдтрока Значение, возвращаемое функцией atof( )   ‘ '—0`123" -0`123000  {‘. ‘,  l '122+3" 123ooo:oooooo  \  2 Р v  '123`1е—5" 0:001231   Использование функции atof() иллюстрируется листингом 17.15. Эта программа при- глашает пользователя ввести строку для преобразования.  Листинг 1 7.1 5. atof . с ‹— преобразование строк в вещественные числа c помощью функции atof()   1: /* демонстрация работы функции atof(). */ 2: 3: #include <string.h> 4: #include <stdio.h> 5: #include <stdlib.h> 6: 7: int main( void ) 8: { 9: char buf[80]; 10: double d; 11: 12: while (1) 13: { 14: printf("\nEnter the string to convert (blank to exit): "); 15: gets(buf); 16: 17: if ( strlen(buf) == 0 ) 18: break; 19: 20: d = atof( buf ); 21: 22: printf("The converted value is %f.", d);  День 17-й, Операции над строками символов 459 
23: } 24: return 0; 25: }  - - Enter the string to convert (blank to exit): l009.l2 The converted value is 1009.120000. Enter the string to convert (blank to exit): abc The converted value is 0.000000. Enter the string to convert (blank to exit): 3 The converted value is 3.000000. Enter the string to convert (blank to exit):   № Цикл while B строках 12—23 организован таким образом, чтобы пользователь мог выйти, введя пустую строку. В строках 14 и 15 запрашивается и вволится строка. В строке 17 проверяется, не пустая ли она. Если это так, программа выхолит из цикла while и заканчивает работу. В строке 20 вызывается функция atof( ), которая преобразует введенную строку buf B вещественное число и присваивает его переменной d типа double. B строке 22 Ha экран выволится окончательный результат.  Функции анализа символов  Заголовочный файл ctype.h содержит прототипы ряда функций, которые анализируют символы и возвращают TRUE или FALSE B зависимости от того, удовлетворяют ли те опреде- _ ленным условиям. Например, с их помощью можно узнать, является ли символ буквой, пиф- рой и т.п. Эти функции с именами типа isxxxx( ) на самом деле представляют собой макро- сы, определенные в файле ctype.h. Подробнее o макросак будет рассказано на занятии 21. После этого будет полезно заглянуть в файл ctype.h и посмотреть, как устроены эти макро— сы. Пока же вполне достаточно знать, как ими пользоваться. Макросы isxxxx( ) имеют идентичные прототипы:  int isxxxx (int ch);  B этом прототипе аргумент ch представляет собой анализируемый символ. Каждый мак— рос возвращает значение TRUE (не ноль), если его условие удовлетворяется, и FALSE (ноль), если оно не удовлетворяется. В табл. 17.4 приведен полный список макросов isxxxx( ).  Таблица 17.4. Макросы isxxxx()   Макрос Выполняемая операция   isalnum() Возвращает TRUE, если ch -— буква или цифра isalpha( ) Возвращает TRUE, если ch -— буква isblank() Возвращает TRUE, если ch -— пустой символ iscntrl( ) Возвращает TRUE, если ch -— управляющий символ  isdigit() Возвращает TRUE, если ch -— цифра isgraph() Возвращает TRUE, если ch —— отображаемый символ (не пробел) islower() Возвращает TRUE, если ch -— буква в нижнем регистре  isprint() Возвращает TRUE, если ch -— отображаемый символ (в том числе пробел)   460 Неделя 3. Основные вопросы 
Окончание табл. 17.4  Макрос Выполняемая операция   ispunct() Возвращает TRUE. если ch -— знак препинания isspace() Возвращает TRUE, если ch — символ свободного пространства (пробел. табуляция, вертикальная табуляция, перевод строки, прогон страницы, возврат каретки) isupper() Возвращает TRUE. если ch — буква в верхнем регистре  isxdigit() Возвращает TRUE, если ch -— шестнадцатеричная цифра (0—9. a—f. A—F)  C помощью макросов для анализа символов можно выполнять много интересных опера- ций. Примером служит функция get_int( ) B листинге 17.16. Эта функция вводит целое число из потока ввода stdin и возвращает его как значение типа int. Функция пропускает пустые символы и возвращает 0, если первый же непустой символ является недопустимым в числе и тем самым не дает выполнить преобразование.  Листинг 1 7. 1 6. getint .c — использование макросов isxxxx( ) в функции ввода целых чисел   H  ”\IO‘U'IObWN II II II II II II II II II  /* Использование макросов анализа символов для создания */ /* функции ввода целых чисел. */  #include <stdio.h> #include <ctype.h>  int get_int(void);  9 int main( void ) 10: { 11: int x; 12: x = get_int(); 13: 14: printf("You entered %d.\n", x); 15: } 16: 17: int get_int(void) 18: { 19: int ch, i, sign = 1; 20: 21: /* Пропуск пустых символов. */ 22: 23: while ( isspace(ch = getchar()) ) 24: ; 25: 26: /* Если первый xe символ - не числовой, вернуть символ */ 27: /* в поток ввода и возвратить 0. */ 28: 29: if (ch != '-' && ch != '+' && £isdigit(ch) && ch != EOF) 30: { 31: ungetc(ch, stdin); 32: return 0;  День 17-й. Операции над строками символов 461 
33: }   34: 35: /* Если первый символ — знак минус, то установить */ 36: /* соответствующий знак числа. */ 37: 38: if (ch == '—' 39: sign = -1; 40: 41: /* Если первый символ — знак плюс или минус, */ 42: /* получить следующии символ. */ 43: 44: if (ch == ‚ ‚ || ch == ._, 45: ch = getchar(); 46: 47: /* Считывать символы, пока не встретится не-цифра. Присваивать */ 48: /* значения, умножая на степени 10, переменной і. */ 49: 50: for (i = 0; isdigit(ch); ch = getchar() ) 51: i = 10 * i + (ch — '0'); 52: 53: /* Смена знака для случая отрицательного числа. */ 54: 55: і *= sign; 56: 57: /* Если встретился не EOF, то наверняка нецифровой символ, */ 58: /* так что вернуть его в поток ввода. */ 59: 60: if (ch != EOF) 61: ungetc(ch, stdin); 62: 63: /* Возврат введенного целочисленного значения. */ 64: 65: return i; 66: } -100 You entered -100. abc3.145 ' You entered 0. 9 9 9 You entered 9. 2.5  You entered 2.  B строках 31 и 61 эта программа использует библиотечную функцию ungetc( ), изученную на занятии 14. Напомним, что функция возвращает символ в задан- ный поток. Именно этот символ будет первым, который программа введет при следующей операции ввода из данного потока. Это необходимо — если функция get_int() вводит нечи- словой символ из stdin, она должна вернуть его обратно. Этот символ может еще понадо- биться программе B будущем.  462 Неделя 3. Основные вопросы 
Ё- Функция main” B этой программе —— совсем простая. В ней объявляется целая перемен- Зная ›‹ (строка 11), которой в строке 12 присваивается значение, возвращаемое функцией “get int( ). Затем это значение выводится на экран (строка 14). Весь остальной код програм- мы заключен в функции get_ int( ). A BOT функция get_int( )совсем не так проста. Чтобы избавиться от лишних пустых сим- Ёволов перед числом, в ней выполняется цикл while (строка 23). Символ ch, введенный функ- Змей getchar( ), анализируется макросом isspace( ). Если ch относится к числу пустых сим- Ёволов, то вводится следующий символ, пока наконец в потоке ввода не появится значащий, Ёнепустой символ. В строке 29 проверяется, разрешен ли данный символ в записи числа. Эту :{mpoxy можно прочитать примерно так: “если символ не является знаком минус, плюс, циф- Ёрой или концом файла”. Если это утверждение справедливо, то с помощью функции ungetc( ) g; строке 31 символ помещается обратно в поток ввода, и функция возвращает управление в Ёдаіщ ). Если же символ принадлежит к тем, которые могут использоваться при записи целых Ё‘зшсел, то функция продолжает работу. $ В строках 38—45 обрабатывается знак числа. В строке 38 проверяется, не был ли введен `нак минус. Если это так, то переменная Sign устанавливается равной -1. Она используется gum того, чтобы в будущем определить знак окончательного значения (строка 55). Заранее, в ,начале функции, число считается положительным, поэтому после учета отрицательного знака Ёможно продолжать ввод числа. Если был введен знак, то необходимо получить следующий символ из потока. Это делается в строках 44 и 45. " Ключевым фрагментом данной функции является цикл for B строках 50 и 51. B нем символы вводятся подряд один за другим, пока являются цифрами. Строка 51 на первый взгляц может ‚показаться непонятной. В ней отдельно введенный символ каким—то образом превращается в Ёцифру и вставляется в число. Символ преобразовывается в числовое значение путем вычитания него '0' (вспомните о коде ASC11). После получения этого числового значения оно умножа- ется на соответствующую степень 10. Цикл for продолжается до тех пор, пока не будет введен Ёнецифровой символ. После этого в строке 55 к числу применяется знак, и процедура завершена. , Перед возвращением из функции необходимо немного прибрать за собой. Если последний ‘введенный символ не является символом конца файла, то его необходимо вернуть в поток ввода (он может еще понадобиться). Это делается в строке 61, а затем в строке 65 выполняет— ‘Ёся возвращение из функции.  .‘v’ )-  г...…  t  Чджй М W}  :W   ‚ Рекомендуется Не рекомендуется Чопьзуитесь такИми удобными средст- . Ёне используйте функіііий, ;…не принадле— °    обработки текстовой информации ‚пжащих к стандарту ANSI, если собирае- Ё к функции paficm'cg строкамйЁіЁ W %: тесь переносить программу на дРУГИе Ё  `платформы. 35 ж д ‹ ^ . … : д ‚ He путайте символьі и числа. Часто за- — даёт,; 9:5: \ «9“? " „ _ 3W “_ щ бывают, что символ ”1" -—— это совсем не  5$: “ “{ Ё і“ … „ _ :51 Ёто же самое, что число1   (…За _ так… ‚...,-\? ‚:\—«:\,   к („ЁХЁ  . _, __ ‚‹ ‹ 1 'l … " i ^ > вш'ніьм\мы…._і`ъыщмт ...—- .. е_ммю и№й№"`№ …… u...“ ‘ дм…" “…и—ммм“. …»… мм..—и… ^  \,< _ L All» ‚А . … я... ‹ я у … . y...3~/.\m.... x- \». : \"   Изменение регистра символов по стандарту ANSI  Хотя функции strlwr() и strupr() прекрасно справляются с изменением регистра сим— волов в строках на нижний или верхний, они не определены в стандарте ANSI. Все же и этот  День 17-й. Операции над строками символов 463 
стандарт предусматривает два макроса для преобразования символов в верхний или нижний регистр. Наряду с макросами ізхххх() существуют еще два стандартных макроса для изменения регистра символов: toupper() и tolower( ). Использование этих макросов иллю— стрируется в листинге 17.17.  Листинг 1 7. 1 7. upper2 . с — преобразование регистра символов в строках с помощью макросов toupper( ) u~tolower( )   : /* Функции преобразования символов tolower() H toupper(). */  2: #include <ctype.h>' 3: #include <stdio.h> 4: #include <string.h> 5: 6: int main( void ) 7: { 8: char buf[80]; 9: int ctr; 10: 11: while (1) 12: { 13: puts("Enter a line of text, a blank to exit."); 14: gets(buf); 15: 16: if ( strlen(buf) == 0 ) 17: break; 18: 19: for ( ctr = 0; ctr< strlen(buf); ctr++) 20: { 21: printf("%c", tolower(buf[ctr])); 22: } 23: 24: printf("\n"); 25: for ( ctr = О; ctr< strlen(buf); ctr++) 26: { 27: printf("%c", toupper(buf[ctr])); 28: } 29: printf("\n"); 30: } 31: return О; 32: }   Enter a line of text, a blank to exit. Pesgnhmam і My aunt's name is Carolyn C.  my aunt's name is carolyn c. MY AUNT'S NAME IS CAROLYN C.Enter a line of text,a blank to exit.  I Программа запрашивает строку текста у пользователя в строке 13, a затем вво— 11311113 > дит ее с помощью функции gets() B строке 14. Затем выполняется проверка, не является ли введенная строка пустой (строка 16). Поскольку макросы tolower() и toupper() работают с отдельными символами, а не с целыми строками, в этой программе строка buf  464 Неделя 3. Основные вопросы 
;выводится на экран не таким способом, как в листинге 17.14. В строке 19 организуется цикл “for no всем символам buf, каждый из которых приводится к нужному регистру.   @.… Везде, где это возможно, лучше пользоваться макросами tolower() и toupper() вместо нестандартных функций strlwr() и strupr( ). Можно на— _ писать и свои собственные аналоги этих функций с помощью макросов 4 tolower() и toupper( ). B этом случае функции будут полностью совмести— мы со стандартом ANSI.     ”71+ „**-мн *:! ‹ ”  ёРезюм е Ё ".:! На этом занятии мы рассмотрели разнообразные манипуляции со строками, возможность торых предоставляет язык С. Копирование, сцепление, сравнение строк, поиск в строках гко выполняются с использованием стандартных библиотечных функций С —— а иногда и— `ункций, не определенных в стандарте ANSI, но достаточно распространенных в целом ряде . д разработки. Все эти операции, как правило, встречаются в большинстве программных .. ектов. Стандартная библиотека содержит также функции для изменения регистра симво— в в строках и преобразования строк в числа. Наконец, в С имеется ряд функций для анализа , мволов — точнее, макросов, выполняющих различные проверки принадлежности отдель— 'ых символов к той или иной категории. Такого рода проверки могут оказаться полезными ЁЁри разработке ваших собственных функций ввода.  _ I А   »  Jr  ЁВопросы и ответы  ‚‹  ??:-_.-  ь  v  Как узнать, является ли та или иная функция АПЗі—совместимой? В руков0дствах к компиляторам и средам разработки, как правило, есть справочный раз— дел по функциям. В нем должны быть перечислены и описаны все предоставляемые библио— ?гекой компилятора функции. Обычно в таких руководствах указывается и то, совместима ли функция со стандартом ANSI. Иногда из них можно узнать даже совместимость функции с доперационными системами DOS, UNIX, Windows или 05/2, а также с компиляторами С++. Во всяком случае, в руководстве к компилятору должна присутствовать вся важная информация о его функциях.  ч;“?цтгхич ‚  Все ли функции для работы со строками были рассмотрены на этом занятии? Нет, не все. Однако те из них, которые бьши рассмотрены, позволяют выполнить почти все встречающиеся на практике операции. Справку об остальных функциях можно получить ‹Из руководства к компилятору.  Игнорирует ли функция strcat() пробелы в конце строк при их сцеплении? Нет. Эта функция воспринимает пробелы как обычные символы.  Можно ли преобразовать число в строку символов? Да, можно. Например, можно написать свою собственную функцию, подобную той, что использовалась в листинге 17.16, или найти в справочном руководстве сведения о библиотечных функциях, таких как itoa( ), ltoa( ), ultoa( ). Функция sprintfl ) также под— `Х0дит для этой цели.  Wm: 17—й. Операции над строками символов 465 
Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления изученного материала и упражнения для приобретения практических навыков программирования.  Контрольные вопросы  Что такое длина строки, и как ее можно вычислить? Что необходимо сделать перед тем, как копировать строку? Что означает термин конкатенация? Каков смысл выражения “одна строка больше другой” при сравнении строк? В чем разница между функциями strcmp( ) и strncmp( )? B чем разница между функциями strcmp( ) и strcmpi( )?  Принадлежность к какой категории символов проверяется макросом isascii( )?  @399'99‘5"?  Какой макрос из табл. 17.4 возвратит значение TRUE для аргумента var? int var = 1; 9. Какой макрос из табл. 17.4 возвратит значение TRUE для аргумента x? char х = 65;  10. Для чего используются функции анализа символов?  Упражнения  1. Какие значения возвращают функции анализа символов?  2. Какое значение возвратит функция atoi() при передаче в нее следующих аргументов? а)"65" б)"81.23" в)"—34.2" r)"ten" n)"+12hundred" e)"negative100" 3. Какое значение возвратит функция atof() при передаче в нее следующих аргументов? а)"65" б)"81.23" в)"—34.2" r)"ten" A)"+12hundred" e)"1e+3"  466 Неделя 3. Основные вопросы 
Поиск ошибок. Есть ли ошибки в следующем коле? char *stringl, stringZ; stringl = "Hello World“; strcpy( stringz, stringl); printf( "%s %s“, stringl, stringz );   , " По причине большого разнообразия возможных решений следующие далее упражнения . иводятся без ответов.  Напишите программу, которая по отдельности запрашивает фамилию, имя и отчество пользователя. Затем эту информацию следует поместить в новую строку в следующем по— рядке: первый инициал, точка, пробел, второй инициал, точка, пробел, фамилия. Напри— мер, если ввести Bradley, Lee и Jones, программа должна сформировать строку В. L. Jones. Выведите новую строку на экран.  Напишите программу для подтверждения правильности ваших ответов на контрольные if; вопросы 8 и 9. … " Функция strstr() находит первое вхождение одной строки в другую, причем различает  символы верхнего и нижнего регистров. Напишите функцию, которая бы выполняла ту же операцию, но без различения регистра.  Напишите функцию, определяющую количество раз, которое одна строка встречается в ЁёдддРУРОЙ-  вода номеров всех строк файла, в которых она встречается. Например, если взять один из файлов исходного кода С и задать поиск строки "printf ", программа должна перечислить " номера всех строк, в которых вызывается функция printf( ). . B листинге 17.16 демонстрируется функция, которая вводит целое число из потока stdin.  ЁЁ _ Напишите функцию get_float( ), которая вводит вещественное число из того же потока “ stdin.  m In i;~a‘l§ui§'s,n '; - { „‚едь-га _ ‚:у *. - !- “"  `,:— ..?—‚*  ‚ „-, а " и  4№№№`БЁ  ь 17—й. Операции над строками символов 467 
  Дополнительные возможности функций  Вы уже наверняка осознали, что ключом к искусству программирования на С является правильное обращение с функциями. На этом занятии вы узнаете о дополнительных способах работы с функциями в программах. Будут рассмотрены следующие вопросы.  I Передача указателей в функции в качестве аргументов I Передача в функции указателей типа void I Использование функций с переменным числом аргументов I Возвращение указателя из функции Некоторые из этих тем уже заграгивались ранее. На этом занятии обсудим их более подробно.  Передача указателей в функции  Стандартным методом передачи аргумента в функцию является передача по значению. Это означает, что в функцию передается копия значения аргумента. Этот метод включает три этапа:  1. Вычисляется значение выражения-аргумента. 2. Результат копируется в стек —— временное хранилище данных в памяти. 3. Функция извлекает значение аргумента из стека.  Ключевым моментом тут является то, что функция никак не может изменить значение пе- ременной-аргумента, если аргумент передается по значению. Рис. 18.1 иллюстрирует переда- чу аргумента по значению. В данном случае аргумент представляет собой просто переменную типа int, но тот же принцип используется и для передачи переменных других типов или вы- ражений любой сложности. При передаче переменной как аргумента по значению функция имеет доступ только к зна- чению, но не к исходному экземпляру переменной. В результате функция никоим образом не может изменить значение исходной переменной. Вот почему именно передача по значению является стандартным способом передачи аргументов: таким образом данные снаружи функ- ции защищены от неосторожного вмешательства. 
w = half (x);x  1000 16 /  1001 1002 _ _ 1003 Значение Х 1nt half (1nt у) ° копируется в стек {  return y/2; }   16 " Функция может обращаться к значению x     Память      Стек  Рис. 18.1. Передача аргумента по значению. Функция не может изменить значение исходной переменной  '*' Но есть и другой способ передачи аргументов. Так, передача по значению возможна для Базовьтх типов данных (char, short, int, long, long long, float, double и long double), a We структур. А вот альтернативным способом является передача указателей на перемен- ные вместо значений этих переменных. Этот метод называется передачей аргументов по ад— реву. Поскольку функция получает адрес исходной переменной, она может изменить ее зна- нение через этот адрес. Как говорилось на занятии 9, передача по адресу является единственным способом пере- дать массив в функцию. Передача массива по значению невозможна. Однако c другими типа- ми данных можно работать как одним, так и другим способом. Если в программе использу— іотся большие структуры, то передача их по значению может привести к переполнению стека. Кроме этого, передача аргументов по адресу имеет как преимущество, так и недостаток по épaBHeHmo c передачей по значению.  I Преимущество передачи по адресу заключается в том, что функция может модифици— ровать значение переменной-аргумента.  I Недостаток передачи по адресу заключается в том, что функция может модифициро— вать значение переменной-аргумента.  “Как прикажете это понимать?”— спросите вы. “Преимущество и недостаток в одном лице?!” Именно так. Все зависит от конкретной задачи. Если функции необходимо изменять значение ее аргумента, то передача по адресу является преимуществом. Если же этого делать, Наоборот, не следует, передача по адресу становится опасной из—за возможности нежелатель— ной модификации переменной. Может возникнуть вопрос, почему бы не использовать возвращаемое функцией значение для модифицирования исходного аргумента. Это можно сделать, например, так: = half(x); float half(float у) { return y/Z;  &  день 18-й. Дополнительные возможности функций 469 
Помните, однако, что функция может возвратить только одно значение. А вот при передаче одного или нескольких аргументов по адресу можно заставить функцию “возвращать” сразу не- сколько значений. Рис. 18.2 иллюстрирует передачу одною аргумента B функцию по адресу.  half (&х); X   1000 16 1001 1002 void half (int *y) КЮЗ { ' Ацресх *у = *y/Z; копируется в стек } _ 1000 Зная адрес, функция может  обращаться к значению x     Память — —      Стек  Рис. 18.2. Передача аргумента по адресу позволяет функ- ции модифицировать его исходное значение  Функция, использованная B рис. 18.2, — это не слишком убедительный пример передачи аргумента по адресу. Вряд ли нечто подобное использовалось бы B реальной программе. Но . для иллюстрации основного принципа этого вполне достаточно. Для передачи аргумента по адресу необходимо, чтобы в прототипе и заголовке функции был отражен тот факт, что в функцию передается именно указатель на переменную, а не сама переменная. В теле функ- ции для обращения к этому аргументу нужно использовать операцию ссылки по указателю. Листинг 18.1 демонстрирует передачу аргументов по адресу, а также стандартный способ передачи по значению. Те данные, которые программа выводит на экран, убедительно дока— зывают, что переменную, переданную по значению, невозможно изменить изнутри функции, тогда как переданную по адресу — вполне возможно. Разумеется, функция вовсе не обязана изменять значение аргумента, переданного по адресу. Но B таком случае незачем и применять этот способ — можно ограничиться передачей по значению.  Листинг 1 8. 1 . args . с — передача аргументов по адресу и по значению   1: /* Передача аргументов в функцию по значению и по адресу. */ 2: 3: #include <stdio.h> 4: 5: void by_value(int a, int b, int c); 6: void by_ref(int *a, int *b, int *c); 7: 8: int main( void ) 9: { 10: int x = 2, y = 4, z = 6; 11: 12: printf("\nBefore calling by_value(), x = %d, у = %d, z = %d.", 13: Х, Y! Z);  470 Неделя 3. Основные вопросы 
,15: by_value(x, y, z);   16: 917: printf("\nAfter calling by_value(), х = %d, y = %d, 2 = %d.", 183 X, Y! Z); 319: 30: by_ref(&x, &у, &2); :21: printf("\nAfter calling by_ref(), x = %d, y = %d, z = %d.\n", $223 X, Y! 2); £93: return 0; Q4: } ".25. ' $26: void by_value(int a, int b, int с) $27: { $38: a = 0; 329: Ь = 0; $30: c = 0; E31: } #52: ЁЗЗ: void by_ref(int *a, int *b, int *c) $34: { 335: *а = 0; $36: *b = О; Q57: *с = 0; g} Before calling by_value(), x = 22, у = 44, 2 = 6. After calling by_value(), x = 2, y = 4, 2 = 6. After calling by_ref(), x = 0, у = 0,2 = 0.   Эта программа демонстрирует разницу между передачей аргументов по адресу и по значению. Строки 5 и 6 солержат прототипы двух функций, вызываемых в №грамме. Функция Ьу__ value(), объявленная в строке 5, принимает три аргумента типа int. QB противоположность ей, функция by_ref( ) принимает три указателя типа int B качестве ар- і3і‘ументов. Заголовки этих функций нахоцятся в строках 26 и 33 и по форме совпадают с про- Ёі'бтипами. Тела двух функций похожи, но не идентичны. Обе функции присваивают 0 трем ‚: еременным, переданным в них через аргументы В функции Ьу_ value( ) значение 0 присваи- ЁЁаегся непосредственно переменным. В функции Ьу_ ref( ) это значение присваивается через {указатели, для чего необхолимо использовать знак ссылки. ’ Каждая функция вызывается из шаіп() Один раз. Вначале в строке 10 трем переменным :ьЁприсваиваются ненулевые значения. В строке 12 эти значения выв0дятся на экран. B стро- же 15 вызывается первая из двух функций —— Ьу __.value() B строке 17 те же три переменные ‚‚;‘снова выв0дятся на экран. Заметьте, что они не изменились. Поскольку функция Ьу_ value() Ёполучает свои аргументы по значению, она не может изменить с0держимое исх0дных пере- ;хменных в главном теле программы. В строке 20 вызывается функция Ьу_ ref( ), и в строке 21 {an третий раз выв0дятся на экран все те же три переменные. На этот раз их значения измени- ись на 0. Таким образом, передача аргументов по адресу дает функции by_ref() возмож- ”‘ность доступа к с0держимому исхоцных переменных.  %* :,  : дэ !  за: .с; .. " ним А  Edema 18-й. Дополнительные возможности функций 471 
Вполне возможны и функции, в которых одни аргументы передаются по значению, а дру- гие — no адресу. Важно лишь правильно обращаться с такими аргументами внутри функций и использовать знак ссылки по указателю везде, где это необходимо, т.е. с аргументами, пе— реданными по адресу.   Рекомендуется Не рекомендуется  передавайте ^аргументы по значению если He хотите чтобы функция изменяла    ных, гдеЪначениям ‘если ‚в этом I ',“  исхтщные значения переменных … м „„ … бой необходимости Это может Используите знак ссылки no указатеы клерепопнению отека.… _ WSW}; ЁЛЮ … для обращения к аргументу, пеш” \Не,;забьяваите что;і аргум “fine, _ ; ,. ” „,‚реданному в функцию no адресу ” daffy ваемыи по адресу, всегда долж ”і   ,) ?>";   указателем д; *  м \. \ , ‚, ‚` 5x A ‘;“ “ “ … v > \… , » _ …А, 15M „“ ›__,\ №№… .- ‘ .- ? ^^ \ *4 ’ … \ ' '8: x} \ ‘5 x" ‘ и I. ‚ . … ‘.‘“ \ ^ 5R *в" _А^ и ‚ _ 5M \?     Указатели типа void  Ключевое слово void уже встречалось в заголовках и прототипах функций, которые либо не принимали никаких аргументов, либо не возвращали никаких значений? Это ключевое слово можно также использовать для создания нетипизированных указателей (указателей без типа), т.е. указателей на объекты данных любого произвольного типа. Например, следующий оператор объявляет нетипизированный указатель ptr:  void *ptr;  Таким образом, указатель ptr указывает на какой-то объект памяти с временно неопреде- ленным типом. . Наиболее распространенным применением указателей типа void следует считать объявление параметров функций. Например, может понадобиться функция, которая должна обрабатывать аргументы различных типов. Хотелось бы, чтобы в такую функцию можно было один раз пере- дать аргумент типа int, второй раз — аргумент типа float и т.д. Объявив ее аргумент указате- лем типа void, мы не ограничиваем себя одним конкретным типом данных. Если сделать аргу- мент функции нетипизированным указателем, то в нее можно передать адрес всего, чего угодно. Возьмем простейший пример. Пусть необходимо написать функцию, которая бы прини- мала число в качестве аргумента, делила бы его на два и возвращала ответ в самом же аргу- менте. Допустим, если переменная val содержит значение 4, то после вызова функции half( val) она становится равной 2. Поскольку аргумент необходимо модифицировать, мы передаем его по адресу. Также мы хотим иметь возможность работать с любым из числовых типов данных С, поэтому определяем аргумент функции как указатель типа void:  void half(void *val);  При вызове этой функции в нее можно передать указатель любого типа. Но в этом месте на- до сделать оговорку: можно передать указатель типа void, не зная, на какой объект он указыва- ет, однако через него не получится так просто обратиться к этому объекту внутри функции. Перед тем как выполнять операции над объектом по указателю, необходимо каким-то обра— зом определить его тип. Это делается с помощью приведения типа, т.е. процедуры, которая со— общает программе, на что должен указывать данный указатель типа void. Пусть, например, pval — указатель типа void. Тогда приведение его типа выполняется следующим образом:  (тип * )pval  472 Неделя 3. Основные вопросы 
Здесь тип— это тип данных, к которому приводится указатель. Например, чтобы привес- ти указатель pval к типу int, запишем следующее:  (int *)pval  Чтобы обратиться по указателю pval к значению типа int, используется следующее дырэжение: &(іп: *)pval Приведение типов рассматривается более подробно на занятии 20, посвященном работе с gamma A сейчас вернемся к главной теме —— передаче указателя типа void B функцию. Что- fiu воспользоваться указателем, функция должна иметь информацию о том, на значение како- &о типа он указывает. В нашем случае с функцией, которая делит число на два, имеется четы- ре возможных типа: int, long, float, double. Кроме передачи в функцию указателя типа void pa переменную, которую нужно разделить на два, необходимо также сообщить функции ее hm ——- один из четырех возможных. Определение функции можно изменить, например, сле— дующим образом: Эоід half(void *pval, char type);  _ B зависимости от значения аргумента type функция преобразует указатель pval типа void & соответствующему типу. После этого через указатель можно добраться и до значения. Юкончательная версия функции half( ) представлена в листинге 18.2.  3101mm"- 1 8. 2. typecast. с — использование нетипизированного указателя Ёля передачи данных различного типа в функцию   '1: /* Работа с указателями тйпа void. */ *2: на: #include <stdio.h> ..4: _5: void half(void *pval, char type); `4:6: ?7: int main( void ) .8: { 9: /* Инициализация одной переменной каждого типа. */ 10: ll: int i = 20; 12: long 1 = 100000; 13: float f = 12.456; 14: double d = 123.044444; &5: 16: /* Отображение начальных значений. */ 17: 18: printf("\n%d", i); §9: printf(“\n%ld", l); 30: printf("\n%f", f); %1: printf("\n%lf\n\n", d); 52: %3: /* Вызов half() с каждой переменной. */ ‘4: £5: half(ai, 'i');   36: half(&l, '1');  нь 18-й. Дополнительные возможности функций 473 
27: half(&d, 'd');   28: half(&f, 'f'); 29: 30: /* Вывод новых значений. */ 31: printf("\n%d", i); 32: printf("\n%ld", l); 33: printf("\n%f", f); 34: printf("\n%lf\n", d); 35: return 0; 36: } 37: 38: void half(void *pval, char type) 39: { 40: /* B зависимости от значения type, привести указатель х */ 41: /* к нужному типу и разделить адресуемое значение на 2. */ 42: 43: switch (type) 44: { 45: case 'i': 46: { 47: *((int *)pval) /= 2; 48: break; 49: } 50: case '1': 51: { 52: *((long *)pval) /= 2; 53: break; 54: } 55: case 'f': 56: { 57: *((float *)pval) /= 2; 58: break; 59: } 60: case 'd': 61: { 62: *((double *)pval) /= 2; 63: break; 64: } 65: } 66: } 20 100000 12.456000 123.044444 10 50000 6.228000 61.522222  474 Неделя 3. Основные вопросы 
№ В данной программе функция half( ) (строки 38—66) не выполняет никакого контроля ошибок (например, в случае передачи недопустимого аргумента type). Это происходит исключительно потому, что в реальной программе настолько простая функ- ция, как деление на два, не пригодится, так что наш пример — чисто иллюстративный. ` Может возникнуть такое впечатление, что необходимость передавать тип аргумента дела- ет функцию менее гибкой. Конечно, функция имела бы более общий характер, если бы ей во— все не нужно было знать тип передаваемого объекта. Но этот номер в С не пройдет; Перед тем, как обратиться к значению аргумента, необходимо привести указатель на него к нужно- МУ типу явным образом. Следуя этому подходу, можно ограничиться всего одной функцией, тогда как без указателя типа void пришлось бы писать целых четыре —— по одной для кажлого ?}ипа данных. … В тех случаях, когда необходима функция для работы с несколькими различными типами ;данных, часто можно выйти из положения с помощью макросов. Представленный выше при- {мер, в котором функция выполняет сравнительно несложную задачу, прекрасно подходит для этого. Макросы рассматриваются на занятии 21, посвященном дополнительным возможно- іп'ям компилятора.  Рекомендуется Не рекомендуется  введитегтп указателя voidlfl' „, прежде Не пытайтесц инкрементировать или  ЪБ  ; 'i_.-_o бращаться через него к значению “” ЁЁ Ё декрементировать указатель типа void.  Марш ым „„„„ „“$— ›. .»… …… ‘      п— "› Mi” “г:-(‚(Х J  @yHKLLMM C переменным числом ;заргументов  Нам уже встречались библиотечные функции, принимающие переменное количество ар- іументов, например, printf() или scanf (). Вы можете написать и ваши собственные функ- щин с переменным числом аргументов. Программы, в которых используются такие функции, должны подключить заголовочный файл stdarg. h. При объявлении функции с переменным числом параметров сначала нужно перечислить {фиксированные параметры —— те, которые всегда присутствуют в вызове функции. Ее прото— {тип должен содержать, по меньшей мере, один фиксированный аргумент. В конце списка па- ; егров ставится многоточие (. . .) для того, чтобы указать, что следом должны идти допол- Ёдительные аргументы — один или несколько. Не следует забывать также о разнице межлу ар- {инетом и параметром, отмеченной на занятии 5. ;;:Г Как функция узнает, сколько аргументов передается в нее при вызове? Это должен преду- @мотреть ее автор. Один из фиксированных параметров должен сообщить функции количест- ‘ЁЪО ее аргументов. Например, при вызове функции printf() количество дополнительных ар- №ентов в списке определяется по числу спецификаций формата в ее первом аргументе—— оке формата. Если действовать совсем прямолинейно, то можно просто сделать один из фиксированных аргументов равным общему количеству аргументов. Далее будет приведен эвример с использованием этого подхода, но вначале нужно рассмотреть некоторые средства EX для работы с переменным списком аргументов.  ‘s  Ё.; Кроме количества, функция должна знать также типы всех аргументов в списке. В случае  ,  хіпщ ) тип каждого аргумента указывается его спецификацией формата. В других случаях в рассмотренном ниже примере) все аргументы могут быть одного типа, что решает про-   ень 18-й Дополнительные возможности ФУНКЦИЙ 475 
блему. Для создания функции, принимающей переменное количество аргументов, да еще и разных типов, необходимо придумать способ передачи в нее информации об этих типах. На— пример, можно воспользоваться символьным кодом, как это делается в функции half() из листинга 18.2. Средства для работы с переменным списком аргументов определены в заголовочном фай— ле stdarg.h. Они используются внутри функции для получения аргументов из списка. Эти средства включают в себя:  va_list Тип данных для указателя на аргументы. va_start( ) Макрос для инициализации списка аргументов. va_arg() Макрос для получения аргументов по очереди из списка. va_end( ) Макрос для очистки лишних данных после получения всех аргументов.  Далее покажем на примере, как эти средства используются в функции. При вызове функ— ции ей необходимо выполнить следующие операции для правильной обработки полученных аргументов:  1. Объявить переменную-указатель типа va_list. Этот указатель будет использоваться для обращения к отдельным аргументам. Обычно (хотя и не обязательно) ему дают имя arg ptr.  2. Вызвать макрос va_start( ), передав в него указатель arg_ptr, a также имя последнего фиксированного аргумента. Макрос va_start() не возвращает никаких значений. Он инициализирует указатель arg_ptr так, чтобы тот указывал на первый аргумент перемен— ного списка.  3. Для получения отдельных аргументов следует вызывать макрос va_arg( ), передавая ему указатель arg_ptr и тип данных следующего аргумента. Макрос va_arg() возвращает значение следующего аргумента в списке. Если функция получила n аргументов в пере— менном списке, значит, следует n раз выполнить макрос va_arg( ), чтобы извлечь из спи— ска все аргументы в порядке их следования.  4. После того, как все аргументы из списка извлечены, необходимо вызвать макрос va_end() с указателем arg_ptr B качестве аргумента. В некоторых версиях компиляторов С этот макрос не выполняет никаких операций, тогда как в других он делает все необходимое для очистки временных данных, ставших ненужными. Вызов va_end() можно считать по- лезной привычкой программиста, особенно если используется та реализация С, в которой этот макрос действительно что-то делает. Теперь обратимся к примеру. Функция average() B листинге 18.3 вычисляет среднее арифметическое последовательности целых чисел. В данной программе в эту функцию пере— дается единственный фиксированный аргумент, который указывает, сколько дополнительных аргументов содержит следующий за ним список.  Листинг 1 8.3. vary. с — использование списка аргументов переменной длины   1: /* Функции с переменным количеством аргументов. */ #include <stdio.h> #include <stdarg.h>  Gama-MN 0.  float average(int num, ...);  476 Неделя 3. Основные вопросы 
7: 5 8: int main( void )  3-9 { .10: float x; ;11: #12: х = average(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); E13: printf("\nThe first average is %f.", x); “14: х = average(5, 121, 206, 76, 31, 5); $15: printf("\nThe second average is %f.\n", x); 16: return 0; 317: } ;и: £19: float average(int num, ...) §30: { :31: /* Объявление переменной типа va_1ist. */ an: 3&3: va__list arg__ptr; 224: int count, total = 0; %25: $26: /* Инициализация указателя на аргументы. */ 527: $28: уа_зъагъ(аг9_ръг, num); Shh :30: /* Получение аргументов из списка. */ $31: $32: for (count = 0; count < num; count++) f§3: total += va_arg( arg_ptr, int ); 334: €35: /* Очистка ненужных данных. */ £36: {37: vahend(arg_ptr); 538: :39: /* деление суммы на общее количество чисел для получения */ ЁЁО: /* среднего. Приведение суммы к типу float, чтобы возвращаемое */ $$]: /* значение также имело тип float. */ 532: 343: return ((float)total/num);    l {и о L ”З ' } ‚…, .4  The first average is 5.500000. The second average is 87.800000.  ’3‘; ~:1\  , дНШШЗ Функция average() впервые вызывается в программе в строке 12. Первый из пе- ‚ реданных в нее аргументов, причем единственный фиксированный, указывает  ЁЁолнчество чисел в списке переменной длины. В теле функции каждый аргумент, извлекае— %Ёщй из списка в строках 32 и 33, прибавляется к переменной total. После извлечения всех гументов в строке 43 переменная total прив0дится к типу float, a затем делится на num 5} я получения среднего арифметического. В этой программе стоит отметить еще два обстоятельства. Во-первых, в строке 28 вызы-  я макрос va_start() для инициализации списка аргументов. Это необхопимо сделать до   нь 18-й. Дополнительные возможности функций 477 
того, как приступить к извлечению чисел. В строке 37 вызывается макрос va_end( ) для очи— щения ненужных данных, поскольку работа со списком окончена. Эти два макроса обяза— тельно используются в любой функции с переменным списком аргументов. Строго говоря, функция, принимающая переменное количество аргументов, не обязана иметь фиксированный параметр, сообщающий ей о числе передаваемых аргументов. Конец списка аргументов, например, можно указать каким-нибудь специальным значением, не ис- пользуемым ни в каком другом качестве. Однако этот способ накладывает ограничения на передаваемые аргументы, поэтому его стоит избегать.  ФУНКЦИИ, возвращающие указатели  На предыдущих занятиях нам уже встречались функции из стандартной библиотеки С, возвращающие указатели. Можно написать и свои собственные функции, также возвра- щающие указатели. Как и следует ожидать, в прототипе и заголовке такой функции должен присутствовать знак ссылки по указателю (*). Общая форма объявления подобных функ— ций такова:  тип * func ( список_параметров) ;  В этом операторе объявляется функция func( ), которая возвращает указатель на пере— менную заданного типа. Вот два примера таких функций:  double *іипс1(список_параметров) ; struct address *func2 ( список_параметров) ;  В первой строке объявляется функция, возвращающая указатель типа double. Bo второй` строке объявляется функция, возвращающая указатель на структуру address (здесь подразу- мевается, что этот структурный тип объявлен где—то в другом месте). Ни в коем случае не путайте функцию, которая возвращает указатель, 0 указателем на функцию. Если добавить в любое из этих объявлений лишнюю пару круглых скобок, получим указатель на функцию:  double (*func)(...); /* Указатель на функцию, возвращающую double */ double *func(...); /* Функция, возвращающая указатель на double */  Итак, мы разобрались с объявлением функции, возвращающей указатель. А как пользо- ваться функциями такого типа? Здесь нет ничего особенного: точно так же, как и другими функциями, присваивая возвращаемые ими значения переменным подходящего типа (в дан— ном случае указателям). Поскольку вызов функции является выражением С, его можно ис- пользовать везде, где может стоять переменная-указатель того же типа. В листинге 18.4 приведен несложный пример функции, принимающей два аргумента и определяющей, какой из них больше. В листинге продемонстрировано два способа сделать это: одна функция возвращает int, a другая —— указатель на int.  Листинг 18.4. return.c -— возвращение указателя из функции   /* Функция, возвращающая указатель. */  #include <stdio.h>  int larger1(int х, int y); int *larger2(int *x, int *y);  \lmU‘JE-LONH  478 Неделя 3. Основные вопросы 
8: int main( void )  9: { 10: int a, b, biggerl, *bigger2; 11: 12: printf("Enter two integer values: "); 13: scanf("%d %d", &а, Eb); 14: 15: biggerl = largerl(a, b); 16: printf(“\nThe larger value is %d.", biggerl); 17: bigger2 = larger2(&a, &Ь); 18: printf("\nThe larger value is %d.\n", *bigger2); 19: return 0; 220: } Ё21: 232: int largerl(int х, int y) 523: { €24: if (y > x) :“25: return y; 926: return х; $27: } £28: '29: int *larger2(int *x, int *y) 130: { *31: if (*y > *x) 232: return y; 33: 34: return x; 735: }   !  % РЮШШШШП Enter two integer values: 1111 3000 : The larger value is 3000. The larger value is 3000.  „№№ Это сравнительно несложная программа, и вам не должно составить труда разо- браться в ней. Строки 5 и 6 содержат прототипы двух функций Первая из них,  1argerl( ), принимает два аргумента типа int H возвращает значение того же типа Вторая „ larger2( ), принимает два указателя типа int H возвращает указатель того же типа. Функция main( ) в строках 8—20 устроена совсем просто, В строке 10 объявляются четыре переменные: Lia H Ь должны содержать сравниваемые значения, а biggerl H bigger2 —— значения, возвра- 'Щаемые функциями largerl() H larger2() соответственно. Заметьте, что bigger2-—— это {чуказатель типа int, тогда как bigger2 ——- простая переменная типа int. Ё“ В строке 15 вызывается функция largerl() с целочисленными аргументами a H b. Значе- ‘ние‚ возвращаемое функцией, присваивается переменной biggerl, H выволится на экран в “строке 16. В строке 17 вызывается функция larger2( ) с передачей в нее адресов тех же двух ;переменных, Возвращаемое из larger2() значение представляет собой указатель, поэтому Ё присваивается указателю biggerz. Затем программа получает значение по указателю и выво- » дит его на экран в следующей строке  Иж  v-m.‘ _ Ч ,  Ёдень 18-й. Дополнительные возможности функций 479 
Две функции сравнения, используемые в программе, очень похожи между собой. Обе они сравнивают два числа и возвращают большее из них. Разница между ними состоит в том, что larger2() работает с указателями, а larger1( ) — со значениями. Обратите внимание, что в функции larger2() операция ссылки по указателю используется при сравнении, но не при возвращении значения оператором return B строках 32 и 34. Во многих случаях, как, например, в листинге 18.4, не имеет большого значения, что именно будет возвращать функция —— число или указатель. Затраты на разработку функции в обоих случаях будут примерно одинаковыми. Все зависит от специфики программы, вернее, в основном от того, как должно использоваться возвращаемое значение.          Рекомендуется Не рекомендуется '‚Испопьзуйте все средства рассмот— Не путайте указатели на функЦииё с функ— __ fpeHHble на этом занятии, для разработ- ‚ циями… которые возвращают y} j ‚дм функций с переменным числом ар— g H * “:? 232 №255“ тументов Это рекомендуется даже Чё “і ,1'3‹7*<3іі„›і„ _; j у; ”f? {, iii % атом случае, если компилятор Hé требу Ё _ 559135: “5 335,33, 35? {*” ‹ … - тет наличия всех этих средств; va __15356, g; Ё…ЁЪДЁЁ'Ё“ 1 ‚33,53 *’ 354133331: M :;Ёча _start(3. v5 arg(3 ита __end() 33533325??? :53 ‚ LE?" : ”ЁЁ; №… ЁЁ“ “L  x ‹ _, . ‚ .. - ‚и „ ...„ Ц…»… мин-п Jam/H? „на, \…- .…. ! …Я“). МЫ   Резюме  На этом занятии были рассмотрены некоторые дополнительные приемы работы с функ- циями B C. Существует разница между передачей аргументов в функции по значению и по адресу, причем в последнем случае функция способна как бы “возвращать” несколько значе- ний сразу. Для указания на произвольный объект данных в С можно создавать указатель типа void — т.е. фактически указатель без типа. Указатели типа void чаще всего используются для передачи в функции аргументов, представляющих адреса переменных, без привязки к кон- кретным типам этих переменных. Однако перед тем, как обращаться к значению через такой указатель, его необходимо привести к нужному типу. Также на этом занятии были изучены макросы, определенные в stdarg.h для создания функций с переменным количеством аргументов. Такие функции являются гибким средст- вом программирования самых разных задач. Наконец, мы рассмотрели функции, возвра- щающие указатели.  Вопросы и ответы  Является ли передача указателей в функции общепринятой практикой программи- рования на С? Вне всякого сомнения! Во многих случаях функции необходимо изменять значения сразу нескольких переменных программы, и это можно проделать двумя способами. Первый спо- соб состоит в том, чтобы объявить и использовать глобальные переменные. Второй способ —- передать указатели в функцию, чтобы она могла сама изменить значения переменных. Пер- вый способ рекомендуется только тогда, когда практически всем функциям программы необ- х0димы эти глобальные переменные. В противном случае его следует избегать. (См. также материал занятия 12 об области действия переменных.)  480 Неделя 3. Основные вопросы 
Какой способ изменения переменной с помощью функции лучше: присвоение ей  значения, возвращаемого из функции, или же передача в функцию указателя на эту переменную?  Если с помощью функции необходимо изменить всего лишь одну переменную, то лучше  воспользоваться возвращаемым значением. Логика этого решения проста: не передавая ука- зателя, вы не рискуете случайно изменить данные, которые не собирались изменять. Функция при этом остается независимой от кода и данных остальной части программы.  Коллоквиум  В ЭТОМ КОЛЛОКВИУМе BaM предлагаются контрольные ВОПРОСЫ ДЛЯ закрепления изученного  материала и упражнения для приобретения практических навыков программирования.  Контрольные вопросы  '.'"  т ‹: Л „"' тгчт-Птхч .  ‚дню  %"  В чем разница между передачей аргументов в функции по адресу и по значению? Что представляет собой указатель типа void? Для чего используются указатели типа void?  Что такое приведение типа в применении к указателям типа void, и в каких случаях его необходимо выполнять?  Можно ли написать функцию, которая принимает только список аргументов переменной длины, без единого фиксированного аргумента?  Какие макросы используются при разработке функций с переменным количеством аргу— ментов?  Какое значение добавляется к указателю типа void при его инкрементировании? Может ли функция возвращать указатель?  Какой макрос используется для извлечения аргументов из списка переменной длины, пе- редаваемого в функцию?  10. Какие элементы необходимо использовать при работе со списком аргументов переменной  длины? . 1. Напишите прототип функции, возвращающей значение типа int. Ее аргументом должен быть указатель на массив символов. 2. Напишите прототип функции с именем numbers, принимающей три целочисленных аргу- мента. Аргументы должны передаваться по адресу. 3. Продемонстрируйте вызов функции numbers() из упражнения 2 с передачей трех цело- численных аргументов intl, int2 и int3. 4. Поиск ошибок. Есть ли ошибки в следующем коде?  void squared(void *nbr)  {  День 18—й. Дополнительные возможности функций 481 
*nbr *= *nbr; }  5. Поиск ошибок. Есть ли ошибки в следующем коде?  float total( int num, ...) { int count, total = О; for ( count = О; count < num; count++) total += va_arg( arg_ptr, int ); return ( total ); } По причине большого разнообразия возможных решений ответы к следующим упражне- ниям не приводятся.  6. Напишите функцию, которая: а) принимает переменное количество строк в качестве аргументов; б) сцепляет строки по порядку в одну длинную строку; в) возвращает указа— тель на новую строку в основную программу.  7. Напишите функцию, которая: а) принимает массив чисел любого типа в качестве аргумен- та; б) находит наименьшее и наибольшее число в массиве; в) возвращает указатели на эти числа в основную программу. (Подсказка: нужно найти способ сообщить функции коли— чество элементов в массиве.)  8. Напишите функцию, которая принимает в качестве аргументов строку и символ. Функция должна найти первое появление символа в строке и возвратить указатель на его местона- хождение.  482 Неделя 3. Основные вопросы 
  Некоторые библиотечные функции С  Примеры, приведенные в нашей книге, убедительно доказывают, что значительная часть богатых возможностей языка С содержится в его стандартных библиотечных функциях. На этом занятии мы обсудим некоторые из них, не относящиеся к категориям, изученным на других занятиях. Будут рассмотрены следующие классы функций. I Математические функции I Функции работы с временем и датами I Функции обработки ошибок I Функции поиска и сортировки данных  Математические функции  Стандартная библиотека С содержит целый ряд функций для выполнения математических операций. Прототипы математических функций находятся в заголовочном файле math.h. Все они возвращают значения типа double. Аргументы тригонометрических функций должны выражаться в радианах, а не в привычных нам градусах. Один радиан равен 57.296 градуса, а полный круг (360 градусов) содержит 2n радиан.  Тригонометрические Функции  Тригонометрические функции предназначены для вычислений, часто встречающихся в инженерных расчетах и графических приложениях.   Функция Прототип Описание  acos() double acos(double x) Возвращает арккосинус аргумента. Аргумент должен ле- жать в интервале -1 <= х <= 1. Значение лежит в интерва—  ле от 0 до и включительно 
Окончание табл.   Функция  Прототип  Описание   asin()  atan()  atan2()  cos() sin() tan()  double asin(double x)  double atan(double x)  double atan2(double x, double у)  double cos(double x)  double sin(double х) double tan(double x)  Возвращает арксинус аргумента. Аргумент должен лежать в интервале -1 <= x <= 1. Значение находится в интервале  от -л/ 2 до п/ 2 включительно  Возвращает арктангенс аргумента. Значение принадлежит интервалу от -n/ 2 до п/ 2 включительно  Возвращает арктангенс х/у. Значение принадлежит интер— валу от -л до тг включительно  Возвращает косинус своего аргумента Возвращает синус своего аргумента Возвращает тангенс своего аргумента   Степенные и логарифмические функции  Степенные (экспоненциальные) и логарифмические функции используются для различно— го рода математических и технических расчетов.  „   Функция Прототип Описание ехр() double exp(double x/) Возвращает экспоненту своего аргумента, т.е. е", где е рав— но 2.7182818284590452354 log() double log(double x) Возвращает натуральный логарифм своего аргумента. Ар- гумент должен быть положительным 109100 double loglO(double x) Возвращает логарифм аргумента по основанию 10. Аргу— мент должен быть положительным frexp() double frexp(double x, Вычисляет нормализованную дробь, представляющую x. int *Y) Возвращает дробь r из диапазона 0.5 <= r <= 1.0. Пере— менной у присваивается значение целого показателя сте— пени, такое, что x = r * 2Y. Если в функцию передается 0, то r и у становятся равными 0 ldexp() double ldexp(double х, Возвращаетх * 2?  int у)   Гиперболические функции  С помощью этих библиотечных функций С выполняется вычисление известных в матема- тике гиперболических функций.     Функция Прототип Описание cosh() double cosh(double x) Возвращает гиперболический косинус своего аргумента sinh() double sinh(double x) Возвращает гиперболический синус своего аргумента tanh() double tanh(double x) Возвращает гиперболический тангенс своего аргумента 484 Неделя 3. Основные вопросы 
Другие математические функции  Стандартная библиотека С также содержит следующие функции для различных матема- тических вычислений.   ___—__  Функция Прототип  Описание   ЁЁ…  сеі1()  abs() labs() _ floor()  ~modf()  pom  fmod()  double sqrt(double х)  double ceil(double х)  int abs(int х) long labs(long х) double floor(double х)  double modf(double х, double *y)  double pow(double х, double y)  double fmod(double х, double у)  Возвращает квадратный корень из аргумента. Аргумент дол- жен быть неотрицательным  Возвращает наименьшее целое число, превосходящее аргу- мент по величине. Например, ceil(4.5) возвращает 5.0, а сеі1(-4.5) возвращает -4.0. Хотя возвращается целое чис- ло, оно тем не менее имеет тип double  Возвращают абсолютные значения их аргументов  Возвращает наибольшее целое число, не превосходящее ар- гумента. Например, floor(4.5) возвращает 4.0, а floor(-4.S) возвращает -5 . 0  Разбивает ›‹ на целую и дробную части с тем же знаком, что и у к. Функция возвращает дробную часть, а целую помеща— ет в *у  Возвращает хУ. Если ›‹ == 0 и у <= 0 или если ›‹ < 0 и у —-— не целое число, то сигнализируется ошибка  Возвращает вещественный остаток от деления х/у с тем же знаком, что и у х. Если ›‹ == 0, функция возвращает 0   %Демонстрация математических функций  Демонстрация всех математических функций С в действии заняла бы отдельную книгу по- ‘ толще этой. Листинг 19.1 демонстрирует всего лишь некоторые из них.  Листинг 1 9. 1 . math . с — использование математических функций 3 из библиотеки С   {  ФЧФШЬШМН .. о. о. о. о. о. о. ..  `О ..  10: 11: 12: 13: 14: 15: 16:  #include <stdio.h> #include <math.h>  int main( void )  double x;  /* демонстрация некоторых математических функций С */  printf("Enter a number: “); scanf( “%1f", 8X);  printf("\n\n0rigina1 value: %1f“, x);  printf("\nCei1: %1f“, cei1(x));  F День 19-й. Некоторые библиотечные функции С 485 
17: printf("\nFloor: %lf", floor(x));  18: if( x >= 0 ) 19: printf("\nSquare root: %lf", sqrt(x) ); 20: else 21: printf("\nNegative number" ); 22: 23: printf("\nCosine: %lf\n", cos(x)); 24: return 0; 25: }   Enter a number: 100.95 Peaunhmam  Original value: 100.950000 Ceil: 101.000000 Floor: 100.000000 Square root: 10.047388 Cosine: 0.913482  № В этой программе используется всего лишь несколько функций из обширной ма- тематической библиотеки С. В строке 12 вводится число, заданное пользовате— лем, а затем оно вывОдится на экран. После этого число передается как аргумент в четыре стандартные математические функции C: сеі1( ), floor( ), sqrt() и cos( ). Обратите внима— ние, что sqrt() вызывается только в том случае, если число неотрицательное, потому что из отрицательных чисел нельзя извлечь квадратный корень. Вы можете добавить в программу вызовы любых других математических функций, чтобы посмотреть, как они работают.  Обработка времени  Библиотека С содержит ряд функций для работы со временем. В языке С это понятие включает не только собственно время в часах, минутах и секундах, но также и дату, выраженную днем, месяцем и годом. Прототипы функций и определение типов данных, используемых для работы со временем, находятся в заголовочном файле time .h.  Представление времени  Функции работы со временем в С представляют время двумя способами. Самый эле- ментарный способ —-— это отсчет количества секунд, прошедших с наступления полуночи 1 января 1970 года. Для представления более ранних моментов времени используются отри— цательные числа. Значения времени в этом виде представляются целыми числами типа long int. B заголовочном файле time.h с помошью ключевого слова typedef определены символические имена time__t и clock_t с одним и тем же значением —— идентификатором long. B прототипах функций, работающих со временем, используются эти символические имена, а не ключевое слово long. Второй способ — это представление времени, разбитого на составные части: год, месяц, день и т.д. Для этого представления функции работы со временем используют структуру tm, определенную в файле time .h следующим образом:  486 Неделя 3. Основные вопросы 
.вігцсс tm { int tm_sec; // номер секунды после целой минуты — [0,59] int tm min; // номер минуты после целого часа — [0,59] int tm:hour; // номер часа после полуночи — [0,23] int tm_mday; // день месяца — [1,31] int tm_mon; // номер месяца после января — [0,11] int tm_year; // номер года, начиная с 1900 int tm_wday; // номер дня с воскресенья — [0,6] int tm_yday; // номер дня с 1—го января — [0,365] int tm_isdst; // флаг перехода на летнее/зимнее время  }:  :Функции работы со временем  В этом разделе описаны разнообразные библиотечные функции С для работы со значе- 5'ниями времени. Напомним, что термин время здесь относится как к часам, минутам и секун- Ёдам, так и к дате — дню, месяцу и году. После объяснений будет приведен пример програм- мы, использующей эти функции.  Получение текущего времени  Для получения текущего времени от внутреннего таймера компьютера используется ’функция time( ). Ее прототип имеет следующий вид:  i»’t:ime__t time ( time_t *timeptr) ; Напомним, что тип time_t определен в файле time.h как синоним типа long. Функция time( ) возвращает количество секунд, прошедших с момента наступления полуночи 1 ян- ‚варя 1970 г. Если в нее передается указатель, не равный NULL, функция time() помещает это же значение еще и по адресу, на который указывает аргумент timeptr. Таким образом, чтобы поместить значение текущего времени в переменную now типа time_t, можно запи- сать следуюшее: time t now; now Ё time(0);  МОЖНО проделать T0 же самое и ПО-ДРУГОМУі  time_t now; time_t *ptr_now = &now; time ( ptr_now) ;  Преобразование представлений времени  Работать с количеством секунд, прошедших после 1 января 1970 г., не всегда удобно. поэтому в С имеется возможность преобразовать время в виде значения типа time_t B струк- туру tm с помощью функции localtime( ). Структура tm содержит день, месяц, год и другую информацию о времени в виде, более удобном для отображения на экране или для печати. Прототип этой функции имеет следующий вид:  struct tm *localtime(time_t *ptr);  Эта функция возвращает указатель на статическую структуру типа tm, поэтому объявлять саму структуру не нужно — только указатель на нее. Эта статическая структура перезаписы-  День 19—й. Некоторые библиотечные функции С 487 
вается заново при каждом очередном вызове функции localtime( ). Если вы хотите сохранить полученное значение времени, объявите в программе отдельную структуру tm и скопируйте в нее значения из статической структуры. Обратное преобразованне— из структуры tm B значение типа time_t— выполняется функцией mktime( ). Она имеет следующий прототип:  time_t mktime(struct tm *ntime);  Эта функция возвращает количество секунд, прошедших от полуночи 1 января 1970 г. до времени, представленного в структуре ntime типа tm.  Отображение времени на экране  Для преобразования времени в форматированные строки, пригодные для вывода на экран, используются функции ctime( ) и asctime( ). Обе эти строки возвращают время в виде строки особого формата. Отличие между ними состоит в том, что ctime( ) принимает время в виде ар- гумента типа time__t, а asctime( ) — в виде структуры tm. Они имеют следующие прототипы:  char *asctime(struct tm *ptr); char *ctime(time_t *ptr);  Каждая из функций возвращает указатель на статическую строку из 26 символов с завер- шающим нулем, представляющую время из аргумента функции в следующем формате:  Thu Jun 13 10:22:23 1991  Время указывается по 24-часовой шкале. Обе функции используют статическую строку, которая обновляется при каждом вызове любой из них. Для получения большего контроля над форматом времени можно воспользоваться функ-° цией strftime( ). B эту функцию время передается в виде структуры tm. Она создает строку представления времени согласно заданной строке формата. Прототип функции имеет вид:  size_t strftime(char *в, size_t max, char *fmt, struct tm *ptr);  Функция принимает указатель ptr на структуру tm, содержащую время, форматирует вре- мя согласно строке формата fmt и записывает результат в символьную строку по адресу, на который указывает указатель s. Аргумент max должен указывать объем памяти, доступный по адресу s. Если результирующая строка (включая ее завершающий нулевой символ) имеет длину более max символов, функция возвращает 0, а строка s становится неопределенной. В противном случае функция возвращает количество записанных символов strlen ( s ).  Строка формата состоит из одной или нескольких спецификаций формата, перечисленных в табл. 19.1.  Таблица 1 9.1 . Спецификации формата для функции strftime()   Спецификация Подставляемое значение   %а Сокращенное название дня недели % Полное название дня недели %Ь Сокращенное название месяца %в Полное название месяца %с дата и время (например, 10:41:50 30—Jun—91) %C Год в виде числа от 00 до 99 %d день месяца в виде числа от 01 до 31   488 Неделя 3. Основные вопросы 
Окончание табл. 19.1   Спецификация Подставляемое значение   %0 То же, что "%m/%d/%y" %е День месяца в виде числа от 1 до 31 &‘ Эквивалент "%Y-%m-%d" % То же, что "%Ь" —-— сокращенное название месяца %Н Час в виде числа от 00 до 23 (по 24—часовой шкале) %I Час в виде числа от 00 до 11 (по 12—часовой шкале) %3' День года в виде числа от 001 до 366 $111 Номер месяца в виде числа от 01 до 12 вы Минута в виде числа от 00 no 59 %р Флаг AM (время до полудня) или РМ (время после полудня) %: Местное время по 12-часовой шкале за Эквивалент "%Н: %М" ts Секунда в виде числа от 00 no 59 %т Эквивалент "%I-I:%M:%S" Qu Номер дня недели в виде числа от 1 до 7. 1 соответствует понедельнику w Номер недели года в виде числа от 00 до 53. Первым днем недели считается воскресенье %w Номер дня недели в виде числа от 0 no 6 (0 соответствует воскресенью) W Номер недели года в виде числа от 00 no 53. Первым днем недели считается понедельник 93): Представление даты (например, 30-Jun—91) %х Представление времени (например, 10:41:50) %у Год без числа столетий —-— в виде числа от 00 до 99 %! Год с указанием числа столетии в виде десятичного числа %z Часовой пояс— сокращенное название. Если часовой пояс неизвестен, то по- ле останется пустым % Название часового пояса, если эта информация доступна, или пустое поле, если нет %% Знак процента %   Вычисление разницы во времени  Разница между двумя моментами времени в секундах вычисляется с помощью макроса difftime( ), который вычитает два значения типа time_t и возвращает разность. Он имеет следующий прототип: double difftime(time_t later, time_t earlier); Эта функция вычитает earlier из later и возвращает разность— количество секунд прошедших между двумя указанными моментами времени. Обычно difftime( ) применяется  для вычисления временного промежутка между двумя событиями, как это демонстрируется далее в листинге 19.2 вместе с другими операциями над временем.  День 19-й. Некоторые библиотечные функции С 489 
Можно определить расстояние во времени несколько другого рода — время, прошедшее с начала выполнения программы. Это время возвращается функцией clock( ) и измеряется в единицах, равных 1/100 секунды. Функция имеет следующий прототип:  clock_t clock(void);  Чтобы определить длительность выполнения какого-либо фрагмента программы, вызови- те функцию clock( ) дважды —— до и после этого фрагмента — и вычтите два значения.  Пример использования функций времени  В листинге 19.2 демонстрируется применение некоторых библиотечных функций С, пред- назначенных для работы со временем.  Листинг 1 9.2. times .с — использование библиотечных функций времени   1: /* демонстрация функций времени. */ 2: 3: #include <stdio.h> 4: #include <time.h> 5: 6: int main( void ) 7: { 8: time_t start, finish, now; 9: struct tm *ptr; 10: char *с, buf1[80]; 11: double duration; 12: 13: /* Получение времени начала программы. */ 14: 15: start = time(0); 16: 17: /* Получение текущего времени другим способом: */ 18: /* вызовом функции time(). */ 19: 20: time(&now); 21: 22: /* Преобразование значения time_t B структуру tm. */ 23: 24: ptr = localtime(&now); 25: 26: /* Создание и отображение форматированной строки */ 27: /* с текущим временем. */ 28: 29: с = asctime(ptr); 30: puts(c); 31: getc(stdin); 32: 33: /* Вызов функции strftime() для создания нескольких разных */ 34: /* форматированных представлении времени. */ 35:  490 Неделя 3. Основные вопросы 
36: strftime(buf1, 80, "This is week %U of the year %!", ptr); 37: puts(buf1);  38: getc(stdin); 39: 40: strftime(buf1, 80, "Today is %A, %x", ptr); 41: puts(buf1); 42: getc(stdin); 43: 44: strftime(buf1, 80, "It is %M minutes past hour %I.", ptr); 45: puts(buf1); 46: getc(stdin); 47: 48: /* Вычисление длительности программы по текущему времени. */ 49: 50: finish = time(0); 51: duration = difftime(finish, start); 52: printf("\nProgram execution time using time() = %f seconds.", 53: duration); 54: 55: /* Отображение длительности программы в сотых долях секунды */ 56: /* с помощью функции с1осК(). */ 57: 58: printf("\nProgram execution time using clock() = %ld, 5%5", 59: clock(), " hundredths of sec." ); 60: return 0; 61: }   _ ' в., м 19 13:28:53 2002 123mm “Г ау  This is week 20 of the year 2002 Today is Sunday,05/19/02 It is 28 minutes past hour 01.  Program execution time using time() = 14.000000 seconds. Program execution time using clock() = 14290 hundredths of sec.  ‚ МШШЗ В этой программе довольно много комментариев, так что понять ее нетрудно. ' Заголовочный файл time.h включен в строке 4 для того, чтобы иметь возмож-  ность работать с функциями времени. В строке 8 объявляются три переменные типа time_t: start, finish и now. Эти переменные призваны хранить время в виде количества секунд, прошедших с 1 января 1970 г. В строке 9 объявляется указатель на структуру tm. Структура tm уже обсуждалась нами ранее. Типы остальных используемых переменных хорошо известны. В строке 15 программа фиксирует момент своего запуска на выполнение. Это делается с помощью вызова функции time( ). Затем программа Делает фактически то же самое, но другим способом. Вместо того, чтобы просто использовать возвращенное функцией time( ) значение, в строке 20 в функцию time() передается указатель на переменную now. B строке 24 делается именно то, что утверждает комментарий в строке 22: значение now типа time_t преобразуется в структуру типа tm. Следующие далее операторы выводят текущее время на экран в различных  День 19-й. Некоторые библиотечные функции С 491 
форматах. В строке 29 с помощью функции asctime() информация о времени помещается в символьную строку с указателем с. В строке 30 форматированная строка выводится на экран. Затем программа ждет, чтобы пользователь нажал клавишу <Enter>. B строках 36—46 с помощью функции strftime( ) дата трижды выводится на экран в трех различных форматах. Имея под рукой табл. 19.1, вы вполне сможете определить, что именно и в какой форме будет выведено на экран. Затем программа снова определяет время в строке 50. Это время окончания программы. В строке 51 это время используется вместе с временем начала программы для вычисления ее длительности с помощью функции difftime( ). Эта величина выводится на Экран в строке 52. Программа заканчивается выводом на экран ее длительности, вычисленной с помощью функции clock( ).  Обработка ошибок  Стандартная библиотека С содержит большое количество функций и макросов для обра- ботки ошибок, случающихся при выполнении программ.  Макрос assert()  Макрос assert( ) предназначен для диагностики ошибок в программе. Он определен в 3a- головочном файле assert .h и имеет следующий прототип:  void assert(int expression);  Аргумент expression может быть любым выражением С, которое нужно проанализи- ровать на предмет ошибки (в том числе, он может быть и отдельной переменной). Если expression равно TRUE, макрос assert( ) ничего не делает. Если expression равно FALSE, макрос assert() выводит в поток stderr сообщение об ошибке и завершает выполнение программы. Как пользоваться макросом assert( )? Наиболее распространенным его применением является отслеживание ошибок при выполнении программы (принципиально отличающих— ся от синтаксических ошибок). Ошибка выполнения не препятствует компиляции про- граммы, но из-за нее программа выдает неправильные результаты или ведет себя при выполнении неадекватно (например, “зависает”, не реагируя на команды пользователя). Например, пусть разрабатываемая вами программа, выполняющая финансовые расчеты, выдает заведомо неправильные ответы на основе правильных данных. Вы подозреваете, что проблема вызвана переменной interest_rate, принимающей отрицательные значения. Этого не должно происходить, потому что переменная содержит величину процента за кредит или ссуду. Чтобы проверить это, поставьте в тех местах программы, где встречает— ся переменная interest_rate, следующий оператор:  assert(interest_rate >= 0);  Как только переменная interest_rate станет отрицательной, макрос assert() преду- предит вас об этом. Теперь вы сможете локализовать и обнаружить источник ошибки в ис- ходном коде. Иллюстрацией к работе макроса assert( ) служит программа из листинга 19.3. Если запустить ее и ввести ненулевое число, программа выведет на экран данные и завершится нормальным образом. Если ввести ноль, макрос assert() вызовет аварийное завершение  492 Неделя 3. Основные вопросы 
Ъ  , {YMV5  PW?  программы. Точный текст сообщения об ошибке зависит от компилятора. Вот типичный пЬимер такого сообщения:  Assertion failed: x, file list1903.c, line 13 Обратите внимание, что для того, чтобы макрос assert() работал, программа должна бьггь скомпилирована в отладочном режиме. Обратитесь к документации компилятора, чтобы  узнать, как активизировать этот режим. Для компиляции окончательной версии программы отключите этот режим, и все макросы assert( ) будут автоматически отключены.  Листинг 1 9.3. assert . с — использование макроса assert( )   \  1: /* Макрос assert(). */ 2: 3: #include <stdio.h> 4: #include <assert.h> 5: 6: int main( void ) 7: { 8: int x; 9: 10: printf("\nEnter an integer value: "); 11: scanf("%d", 8x); 12: , 13: assert(x >= 0); ‘14: 15: printf("You entered %d.\n“, x); 16: return 0; 17: }   д Enter an integer value: 10 Peaummam You entered 10.  Enter an integer value: -1  Assertion failed: х, file list1903.c, line 13 Abnormal program termination  Конкретная форма сообщения об ошибке зависит от системы и от компилятора, но общая идея остается той же. Например, компилятор Бем-С++, находящийся на прилагаемом ком- пакт-диске, выдает следуюшее сообщение при вводе числа -1:  Enter an integer value: -1 Резцпыват c:\assert.c:13: failed assertion 'х >= 0'  This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.  Запустите эту программу и убедитесь, что сообщение об ошибке от макроса assert() B строке 13 включает в себя выражение, проверка которого вызвала  ошибку, имя файла и номер строки, в которой находится макрос assert( ). Работа макроса assert( ) зависит от другого макроопределения с именем NDEBUG (сокращение от “no debugging”, т.е. “без отладки”). Если константа с именем NDEBUG не опре-  День 19-й. Некоторые библиотечные функции С 493 
делена (именно это условие выполняется по умолчанию), то макрос assert() активен и мо- жет диагностировать ошибки. Если же она определена, то этот макрос отключен и никак не влияет на выполнение программы. Если вы расставили макросы assert( ) no всей программе для облегчения отладки, а затем решили проблему и нашли все ошибки, то можно определить константу NDEBUG для отключения макроса assert( ). Это намного легче, чем прочесать весь текст программы, удаляя операторы с вызовами assert( ), a потом с ужасом обнаружить, что они снова нужны, так как возникли новые ошибки. Для определения константы NDEBUG вос- пользуйтесь директивой #define. Например, добавьте в листинг 19.3 строку следующего со- держания (сделав ее второй строкой программы):  #define NDEBUG  Теперь программа выводит на экран введенное значение, а затем завершает работу нор- мальным образом, даже если ввести -1. Обратите внимание, что константа NDEBUG He нуждается в каком бы то ни было значении. Важен сам факт ее определения с помощью директивы #define. Дополнительные сведения о директиве #define вы получите на занятии 21.  Заголовочный файл errno.h  Заголовочный файл errno.h содержит определения нескольких макросов для выявления и документирования ошибок в программах. Эти макросы используются совместно с функцией perror( ), которая рассматривается в следующем разделе. В число макрообъектов, определенных в файле errno.h, входит глобальная целочислен- ная переменная errno. Многие библиотечные функции С присваивают этой переменной раз- личные кодовые значения в том случае, если при выполнении программы произошла та или иная ошибка. В файле errno.h также определена группа символических констант, представ- ляющих эти кодовые значения. В табл. 19.2 приведен список кодов ошибок.  Таблица 1 9.2. Символические коды ошибок, определенные в файле errno . h   Имя Значение Смысл сообщения об ошибке   Е2В16 1000 Слишком длинный список аргументов (длина списка превышает 128 байт) BACCES 5 Отказано в доступе (например. при попытке записать в файл, открытый для чтения) EBADF 6 Неправильный дескриптор файла ввом 1002 Аргумент математической функции выходит за ее область определения (допустимый диапазон значений) EEXIST 80 Файл существует EMFILE 4 Слишком много открытых файлов ENOENT 2 He существует указанного файла или каталога ENOEXEC 1001 Ошибка в исполняемом формате ENOMEM 8 Недостаточно ресурсов Ядра системы (например, не хватает памяти для вы- полнения функции ехес( )) ЕЫОРАТН 3 Не найден указанный путь к файлу ERANGE 1003 Результат выходит за допустимый диапазон значений (например, результат математической операции слишком велик или мал для данного типа пере- менной)   494 Неделя 3. Основные вопросы 
Переменную errno можно использовать двумя способами. Некоторые функции сигна— зируют через возвращаемые ими значения 0 том, что произошла ошибка. Когда это про- исходит, можно проанализировать значение errno, определить природу ошибки и пред— принять соответствующие меры. Если же ошибка вообще не сигнализируется, можно про— сто проверить значение errno B определенной точке программы. Если оно не равно нулю, то произошла ошибка, и текущая величина этой переменной описывает конкретную ошиб— ку. Не забудьте обнулить переменную errno после обработки ошибки программой. В сле- дующем разделе рассматривается функция perror( ), а затем в листинге 19.4 демонстриру— ется использование errno.  Функция реггог()  Функция perror( ) —— это одно из средств С для обработки ошибок. При ее вызове функ— ция реггог() выводит в поток stderror сообщение о последней ошибке, случившейся при вызове какой-либо библиотечной или системной функции. Ее прототип в файле stdio.h име— ет следующий вид: void perror(const char *msg);  Аргумент msg указывает на необязательное сообщение, задаваемое пользователем. Внача— ле на экран выводится это сообщение, а затем двоеточие и сообщение о самой последней ошибке, зависящее от конкретного компилятора. Если вызов perror( ) сделан при полном от— сутствии ошибок, выводится сообщение no error. Строго говоря, функция perror( ) не занимается обработкой ошибок. За любые действия по исправлению аварийной ситуации отвечает исключительно программист. Эти действия могут включать в себя просьбу к пользователю сделать что—то, например, приказать про- грамме завершиться. Решение об операциях, которые должна выполнить программа, можно принять на основе значения переменной errno и природы ошибки. Заметьте, что для пользо— вания глобальной переменной errno программе вовсе не обязательно подключать заголовоч- ный файл errno.h. Этот файл необходим только в том случае, если программа использует символические константы, перечисленные в табл. 19.2. Листинг 19.4 иллюстрирует использо- вание функции perror( ) и переменной errno для обработки возможных ошибок выполнения.  Листинг 1 9.4. perror.c —- использование perror( ) и errno для обработки ошибок   1: /* демонстрация обработки ошибок с помощью реггог() и errno. */ 2: 3: #include <stdio.h> 4: #include <stdlib.h> 5: #include <errno.h> 6: 7: int main( void ) 8: { 9: FILE *fp; 10: char filename[80]; 11: 12: printf("Enter filename: "); 13: gets(filename); 14:  День 19—й. Некоторые библиотечные функции С 495 
15: if (( fp = fopen(filename, "r")) == NULL)  16: { 17: perror(“You goofedl"); 18: printf(“errno = %d.\n", errno); 19: exit(1); ' 20: } 21: else 22: { 23: puts(“File opened for reading."); 24: fclose(fp); 25: } 26: return 0; 27: }   Enter file name: perror.c РНЗЦПЫВНШ File opened for reading. Enter file name: notafile.xxx  You goofedlz No such file or directory errno = 2.  Эта программа отображает одно из двух сообщений в зависимости от того, по-  w лучается ли у нее открыть файл для чтения. В строке 15 предпринимается по- пытка открыть файл. Если файл открывается, выполняется блок else оператора if и на экра-  не ПОЯВЛЯСТСЯ следующее СООбЩСНИС:   File opened for reading.  Если при открытии файла произошла ошибка —— например, файл не существует — то вы- полняются строки 17—19 оператора if. B строке 17 вызывается функция perror() co строкой- аргументом " You goofed! ". Затем выводится номер ошибки. В результате попытка открытия несуществующего файла вызовет следующие сообщения:  You goofedl: No such file or directory errno = 2.     Рекомендуется Не рекомендуется  " ,jHe подключат :; программе заголоврчдяЁ  :l'lpoeepnfire все}; возможные ou‘m6KWe , 3%  ваших программах Никогда не предпод: ный файл errno. h если} не? собирает“  ‹<ЁМЁ“`  лагайте‚і ЧТО каКая-либо операция 38-; пользоваться символИческими кодами Ё вершилась благополучно если суЩест—… ошибок перечисленнымив ЁЁт‘ё'ібгі 19: Я“.“Ё’ :, вует возможность ошибки._„_ „@ №… ** v.1 _ Ё u : „: „… №№ “идеи!:„ё“? і:щ`*1*`;„ }ЁЁ  } .. “;;—_:Ё‘дщшди «дм—Ун w'Wm: щшхшшджі     .ь-ивмю М….АЬе-ма чан—к...: L ... ”,...”...—   Поиск и сортировка  Поиск отдельных элементов в массивах данных и их сортировка по порядку принадле- жат к числу наиболее распространенных задач программирования. Стандартная библиоте- ка С содержит несколько функций общего назначения, которые можно использовать для этих двух целей.  496 Неделя 3. Основные вопросы 
Поиск  Библиотечная функция bsearch( ) выполняет поиск в массиве данных методом дихото- мии (деления пополам). Она разыскивает элемент, совпадающий с заданным ключом. Для применения функции bsearch() массив должен быть отсортирован по возрастанию. Про- грамма также должна предоставить ей функцию сравнения, которая используется bsearch( ) для определения того, равны ли два элемента данных между собой, и если нет, то какой из них больше, а какой — меньше. Прототип функции bsearch( ) находится в за- головочном файле stdlib . h:  void *bsearch(const void *key, const void *base, size_t пит, size_t width, int (*cmp)(const void *elementl, const void *element2));  Это довольно сложный прототип, так что разберем его подробно. Аргумент key указывает на разыскиваемый элемент данных, а base ———- на первый элемент массива, в котором выпол- няется поиск. Оба эти аргумента объявлены указателями типа void, так что они могут указы- вать на любые объекты данных С. Модификаторы const гарантируют, что передаваемые ар- гументы остаются неизменными при выполнении функции. Аргумент num задает количество элементов в массиве, а width — размер каждого элемен- та в байтах. Спецификация типа size_t соответствует типу данных, возвращаемому операци- ей sizeof( ), т.е. unsigned. Именно операция sizeof() обычно используется для получения значений num и width. Последний аргумент cmp указывает на функцию сравнения. Это может быть как функция, написанная автором программы самостоятельно, так и библиотечная, например, strcmp( ) для сравнения строк. Функция сравнения обязана удовлетворять двум критериям.  I B нее должны передаваться два указателя на сравниваемые элементы данных. I Она должна возвращать значение типа int: 0 отрицательное, если первый элемент меньше второго; . ноль, если элементы равны между собой; . положительное, если первый элемент больше второго. Функция bsearch() возвращает указатель типа void. Это либо указатель на первый эле- мент массива, совпадающий с заданным ключом, либо NULL, если совпадение не обнаружено. Возвращенный из функции указатель необходимо привести к нужному типу, прежде чем пользоваться им для получения значения. Значения аргументов num и width удобно получать с помощью операции sizeof( ). Пусть  array[ ] — это массив, в котором выполняется поиск. Тогда величину width —— размер одного элемента массива в байтах — можно получить оператором  sizeof(array[0]); Выражение sizeof (array) дает длину всего массива в байтах, так что количество элемен- тов в массиве легко вычисляется таким образом:  sizeof(array)/sizeof(array[0])  Алгоритм поиска методом деления пополам очень эффективен. Он быстро находит задан- ные элементы в больших массивах. Его применимость зависит от того, отсортированы ли элементы по возрастанию. Вот как работает этот алгоритм:  День 19-й. Некоторые библиотечные функции С 497 
1. Ключ сравнивается со средним по порядку элементом массива (его медианой). Если дос— тигнуто совпадение, то поиск окончен. В противном случае ключ должен быть либо меньше, либо больше среднего элемента.  2. Если ключ меньше среднего элемента, 'то совпадающий с ним элемент должен нахо— диться в первой половине массива, если, конечно, он вообще существует. Аналогичным образом, если ключ больше среднего элемента, то он должен быть где-то во второй по— ловине массива.  3. Поиск продолжается в первой (или, соответственно, второй) половине массива, и алго— ритм возвращается к операции 1.  Таким образом, каждое сравнение в ходе поиска устраняет из рассмотрения половину ос— тавшихся элементов массива. Например, поиск в массиве из 1000 элементов требует 10 срав— нений, а 16 000 элементов— всего 14. Общая закономерность такова, что за n сравнений можно найти элемент в массиве длиной не более 2n элементов.  Сортировка  Библиотечная функция qsort() реализует алгоритм быстрой сортировки, разработанный К.А.Р. Хоаром (С.А.К. Ноаг). Эта функция сортирует массив по порядку — как правило, по возрастанию, но, в принципе, может отсортировать и по убыванию. Прототип функции опре— делен в файле stdlib.h и имеет следующий вил: void qsort(void *base, size_t лиш, size_t size, int (*cmp) (const void *elementl, const void *element2));  Аргумент base указывает на первый элемент массива, num задает количество элементов в массиве, а size — размер одного элемента в байтах. Аргумент cmp представляет собой указа— тель на функцию сравнения. Требования к функции сравнения предъявляются те же, что и в случае функции bsearch( ), o которой рассказывалось в предыдущем разделе. Часто для них обеих используется одна и та же функция сортировки. Функция qsort() не возвращает ника- кого значения.  Поиск и сортировка: два демонстрационных примера  В листинге 19.5 демонстрируется использование функций qsort( ) и bsearch( ). Програм- ма сортирует массив чисел и выполняет поиск в нем. Обратите внимание, что используется функция getch( ), не определенная в стандарте ANSI. Если ваш компилятор не поддерживает эту функцию, замените ее на стандартную getchar( ).  Листинг 1 9.5. sort.c — применение функций qsort( ) и bsearch( ) длясортировкичисел   /* Функции qsort() и bsearch(). Сортировка и поиск чисел. */  #include <stdio.h> #include <stdlib.h>  ФШьЬШЮР—д  #define MAX 20  498 Неделя 3. Основные вопросы 
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:  int intcmp(const void *v1, const void *v2);  int main( void )  {  }  int arr[MAX], count, key, *ptr; /* Ввод целых чисел пользователем. */ printf("Enter %d integer values; press Enter after each.\n",MAX);  for (count = О; count < МАХ; count++) scanf("%d", &arr[count]);  puts("Press Enter to sort the values."); getc(stdin);  /* Сортировка массива по возрастанию. */ qsort(arr, MAX, sizeof(arr[0]), intcmp); /* Вывод отсортированного массива. */  for (count = 0; count < МАХ; count++) printf("\narr[%d] = %d.", count, arr[count]);  puts("\nPress Enter to continue.“); getc(stdin);  /* Ввод ключа поиска. */  printf("Enter a value to search for: "); scanf("%d", &key);  /* Поиск числа в массиве. */ ptr = (int *)bsearch(&key, arr, MAX, sizeof(arr[0]),intcmp);  if ‹ ptr := NULL ) printf("%d found at arr[%d].", key, (ptr - arr)); else printf("%d not found.", key); return О;  int intcmp(const void *v1, const void *v2)  { }  return (*(int *)v1 _ *(int *)v2);  День 19-й. Некоторые библиотечные функции С 499 
Enter 20 integer values; press Enter after each. Ривальта… 45  12 999 1000 321 123 2300 954 1968 12 2 1999 1776 1812 1456 1 9999 3 76 200 Press Enter to sort the values.  1. 2. 3. 12. 12. 45. 76. 123. 200. 321. 954. 999. 1000. 1456. 1776. 1812. 1968.  arr[0] arr[1] arr[2] arr[3] arr[4] arr[5] arr[6] arr[7] arr[8] arr[9] arr[10] arr[11] arr[12] arr[13] arr[14] arr[15] arr[16] arr[17] 1999. arr[18] 2300. arr[19] = 9999. Press Enter to continue.  Enter a value to search for: 1776 1776 found at arr[14]  500 Неделя 3. Основные вопросы 
MEWS ' В листинге 19.5 реализованы все средства сортировки и поиска, o которых гово— I ’ РИЛОСЬ ВЫШС. Эта программа запрашивает у пользователя последовательность  чисел в количестве до МАХ штук (что в нашем случае соответствует 20). Затем числа сортиру- ются и выводятся на экран в возрастающем порядке. После Этого у пользователя запрашива— ется число для поиска в массиве. Результат поиска выводится на экран. Для ввода чисел в строках 18 и 19 используются уже знакомые операторы. В строке 26 для сортировки массива вызывается функция qsort( ). Первым ее аргументом является указа- тель на первый элемент массива. Затем идет МАХ — количество элементов в массиве. Следом передается размер первого элемента, чтобы функция qsort() знала длину обрабатываемых данных. Наконец, последний аргумент указывает на функцию сравнения intcmp. Функция intcmp() определена в строках 52—55. Она возвращает разность переданных в нее аргументов. На первый взгляд этот способ сравнения вызывает подозрение своей прими- тивностью. Но вспомните, какие значения должна возвращать функция сравнения. Если эле— менты равны между собой, следует возвратить 0, если первый элемент больше, то положи- тельное число, а если первый элемент меньше, то отрицательное. Функция intcmp( ) удовле- творяет всем указанным критериям. Сам поиск выполняется функцией bsearch( ). Заметьте, что аргументы этой функции практически те же, что и у qsort( ). Разница заключается только в том, что первый аргумент bsearch( ) — это ключ поиска. Функция возвращает указатель на местоположение найденно- го ключа., или NULL, если совпадение не обнаружено. В строке 43 указателю ptr присваивается значение, возвращаемое из bsearch( ). Затем ptr используется в операторе if (строки 45—48) для вывода на экран результата поиска. В листинге 19.6 решаются задачи, аналогичные реализованным в листинге 19.5. Вместо сортировки целых чисел выполняется сортировка и поиск строк.  Листинг 1 9.6. strsort. c — применение функций qsort( ) и bsearch( ) для сортировки строк   1: /* Функции qsort() и bsearch(). Сортировка строк. */ 2: 3: #include <stdio.h> 4: #include <stdlib.h> 5: #include <string.h> 6: 7: #define MAX 20 8: 9: int comp(const void *sl, const void *sZ); 10: 11: int main( void ) 12: { 13: char *dataIMAX], buf[80], *ptr, *key, **key1; 14: int count; 15: 16: /* Ввод списка слов. */ 17: 18: printf("Enter %d words, pressing Enter after each.\n",MAX); 19: 20: for (count = О; count < MAX; count++) 21: { 22: printf("Word %d: ", count+1);  День 19-й. Некоторые библиотечные функции С 501 
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:  int  { }  gets(buf); data[count] = malloc(strlen(buf)+1); strcpy(data[count], buf);  } /* Сортировка слов (фактически, указателей на них). */ qsort(data, MAX, sizeof(data[0]), comp); /* Вывод отсортированных слов. */  for (count = 0; count < MAX; count++) printf("\n%d: %s", count+1, data[count]);  /* Ввод ключа поиска. */  printf("\n\nEnter a search key: "); gets(buf);  /* Поиск в массиве. Вначале keyl устанавливается равным */. /* адресу ключа поиска.*/  key = buf; keyl = &Кеу; ptr = bsearch(key1, data, MAX, sizeof(data[0]), comp);  if (ptr := NULL) printf("%s found.\n", buf); else printf("%s not found.\n", buf); return 0;  comp(const void *sl, const void *s2)  return (strcmp(*(char **)s1, *(char **)s2));  Enter 20 words, pressing Enter after each. Peaunhmm Word apple  502  1: Word 2: orange Word 3: grapefruit Word 4: peach Word 5: plum Word 6: pear Word 7: cherries Word 8: banana  Word 9: lime Word 10: lemon Word 11: tangerine  Неделя 3. Основные вопросы 
Word 12: star Word 13: watermelon Word 14: cantaloupe Word 15: musk melon Word 16: strawberry Word 17: blackberry Word 18: blueberry Word 19: grape Word 20: cranberry  apple banana blackberry blueberry cantaloupe cherries cranberry grape grapefruit lemon lime musk melon 13: orange 14: peach 15: pear 16: plum 17: star 18: strawberry 19: tangerine 20: watermelon  HHOWN¢U1I§WNH  H мно—о  Enter a search key: orange orange found.  диализ В листинге 19.6 есть несколько важных моментов, о которых стоит упомянуть. Эта программа работает с массивом указателей на строки —-— объектом, который  рассматривался на занятии 15. Тогда мы выяснили, что строки можно сортировать, переме- щая только указатели на них, но не сами последовательности символов в памяти. Но этот способ требует модификации функции сравнения. В функцию сравнения передаются указате- ли на два сравниваемых элемента массива. Однако нам нужно, чтобы массив указателей сор— тировался не по значениям самих указателей, а по значениям строк, на которые те указывают. Вот поэтому нам нужна функция сравнения, принимающая указатели на указатели. Каж- дый аргумент функции сошр() представляет собой указатель на элемент массива, и сам эле- мент также является указателем (на строку). В итоге получается указатель на указатель. Внут- ри самой функции по указателям извлекаются строки, так что возвращаемое функцией значе- ние зависит от строк, а не от их адресов —— как и должно быть. Тот факт, что аргументы функции сошр() являются указателями на указатели, служит причиной еще одной проблемы. Ключ поиска помещается в переменную buf [ ], и имя масси- ва в этом случае является указателем на него. Однако передавать-то нужно не сам buf, a ука- затель на него. Проблема состоит в том, что buf является адресной константой, а не перемен-  День 19—й. Некоторые библиотечные функции С 503 
ной, и у нее нет адреса в памяти —— это просто символ, имеющий значение адреса массива. Поэтому нельзя просто поставить знак взятия адреса перед именем buf и получить таким об— разом требуемый указатель на указатель &buf. Что же делать? Вначале следует создать адресную переменную —— указатель — и присво- ить ей значение buf. B программе эта переменная носит имя key. Поскольку key — перемен— ная, а не константа, она имеет адрес, и можно смело создавать указатель на нее. В нашем слу- чае это переменная keyl. При вызове функции bsearch( ) ее первым аргументом будет keyl, указатель на указатель на строку—ключ поиска. Далее функция bsearch( ) передает этот аргу- мент в comp( ), и вся процедура срабатывает безупречно.  Рекомендуется Не рекомендуется -     % Изучите документацию вашего компиля— Не забудьте отсортировать массив по тора или стандарта ANSI, чтобы узнать о ‘ возрастанию прежде чем применить дополнительных возможностях, п'редос- Ё функцию bsearch() \ ,  д …? ›    тавляемых библиотеками функций. „ ‚ „ „  Ч ‹ …… . …… …… _.. ‹ “" “"`“ \.мммА да…-`., ц .. „„… ‚„„„, №№ …… , \№ „…. ,…мч№…^  Резюме  На этом занятии были изучены некоторые полезные функции, имеющиеся в стандартной библиотеке С. Среди них — функции для математических вычислений, работы со временем, обработки ошибок. Очень полезны также функции сортировки и поиска. Их использование в ваших программах может сэкономить много времени на разработку. ‘  Вопросы и ответы  Почему практически все математические функции возвращают значения типа double? Цель этого в том, чтобы добиться не столько единообразия, сколько более высокой точ- ности. Точность типа double намного выше, чем других числовых типов, поэтому результаты получаются достаточно точными для практического применения. На занятии 20 будет рас- смотрено приведение и расширение типов переменных. Эти вопросы имеют непосредствен- ное отношение к точности вычислений.  Являются ли функции bsearch() и qsort() единственными средствами С для сор- тировки и поиска? Эти две функции имеются в стандартной библиотеке, но вы не обязаны пользоваться именно ими. Во многих пособиях по программированию рассказывается об алгоритмах поис— ка и сортировки, которые можно реализовать в ваших собственных функциях. В языке С для этого есть все возможности. Можно также приобрести уже готовые программы и функции поиска и сортировки. Самое большое преимущество функций bsearch( ) и qsort( ) состоит в том, что они уже написаны и включены в стандартные библиотеки всех АЫ51-совместимых компиляторов.  П роверяют ли математические функции корректность своих аргументов? Никогда не следует молчаливо предполагать, что вводимая в программу извне информа- ция правильна. Наоборот, всегда проверяйте данные, введенные пользователем. Например.  504 Неделя 3. Основные вопросы 
если вызвать функцию sqrt() с отрицательным аргументом, будет выдано сообщение об ошибке. Если вы заботитесь о презентабельности вв0да-вывода вашей программы, вам вряд ли захочется видеть на экране подобное постороннее сообщение. Для примера удалите опе— ратор if B листинге 19.1, запустите программу, введите отрицательное ЧИСЛО и посмотрите, что получится.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления изученного материала и упражнения для приобретения практических навыков программирования.  `Контрольные вопросы  1. Какой тип имеют значения, возвращаемые практически всеми математическими функ- циями С?  2. Какому стандартному типу данных эквивалентен тип time_t? 3. B чем разница между функциями time( ) и clock( )? 4. Какие действия предпринимает функция perror() для исправления ошибки, произошед- шей при выполнении программы? S. Что необходимо сделать до того, как вызывать функцию bsearch( ) для поиска в массиве?  6. Сколько сравнений выполнила бы функция bsearch( ), чтобы найти заданный элемент в массиве из 16 000 элементов?  7. Сколько сравнений выполнила бы функция bsearch() для нахождения заданного элемен— та в массиве из всего лишь 10 элементов?  8. Сколько сравнений потребовалось бы функции bsearch( ), чтобы найти заданный элемент в массиве из двух миллионов элементов?  9. Какие значения должны возвращать функции сравнения, используемые внутри функций bsearch() и qsort( )?  10. Что возвращает функция bsearch( ), если не может найти заданный элемент в массиве?  Упражнения  1. Напишите вызов функции bsearch( ). Массив данных называется names, a хранятся в нем символы. Функция сравнения называется comp_names( ). Предполагается, что все имена одинаковой длины. 2. Поиск ошибок. Есть ли ошибки в следующей программе? #include <stdio.h> #include <stdlib.h> int main(void ) { int values[10], count, key, *ptr; printf("Enter values"); for(ctr = 0; ctr < 10; ctr++)  День 19-й. Некоторые библиотечные Функции С 505 
3.  4.  5.  6.  7.  8.  9.  scanf("%d", svalues[ctr] ); qsort(values, 10, compare_function());  } Поиск ошибок. Есть ли ошибки в следующей функции сравнения? int intcmp(int elementl, int element2)  { if ( element 1 > element 2 ) return -1; else if ( element 1 < element2 ) return 1; else return О; }  Следующие далее упражнения приводятся без ответов.  Самостоятельная работа. Измените листинг 19.1 так, чтобы функция sqrt() могла рабо— тать с отрицательными числами. Для этого воспользуйтесь абсолютным значением числа.  Самостоятельная работа. Напишите программу с меню, пункты которого выполняют различные математические функции. Включите в программу как можно больше таких функций. ’ Самостоятельная работа. Напишите функцию для приостановки программы примерно на пять секунд с использованием функций времени, изученных на этом занятии.  Самостоятельная работа. Добавьте функцию assert( ) B программу из упражнения 4. При вводе отрицательного значения программа должна выв0дить на экран сообщение.  Самостоятельная работа. Напишите программу для ввода 30 имен и их сортировки с помощью функции qsort( ). Программа должна выводить отсортированный список имен на экран. Самостоятельная работа. Измените программу из упражнения 8 так, чтобы после ввода пользователем слова QUIT программа прекращала ввод и выполняла сортировку введен- ных имен.  "”  10. Самостоятельная работа. Найдите в материале занятия 15 “лобовои метод сортировки  массива указателей на строки по их символьным значениям. Напишите программу, засе- каюшую время сортировки большого массива строк этим методом, а затем сравнивающую его со временем, которое требуется для сортировки этого же массива библиотечной функцией qsort( ).  506 Неделя 3. Основные вопросы 
Самостоятельная работа 6  Расчет выплат по займу  Предлагаемая вам программа для самостоятельной работы имеет имя mortgage.c. Как по- казывает ее название (“mortgage” означает “заем, ссуда под закладную”), программа предна- значена для расчета сумм выплат по закладной или займу любого другого вида. Для работы этой программы ей необходимы следующие данные, которые она запрашивает при запуске.  I Сумма: сколько взято в долг (эта сумма еще называется номиналом).  I Годовой процент: норма процента за год. Эта величина вводится в единицах процен— тов, а не в виде десятичной дроби. Таким образом, 8,5% следует вводить как 8 .5, а не как 0 . 085. Программа сама переведет эту величину в удобный для расчета вид.  I Срок займа в месяцах: количество месяцев, в течение которых следует его выплатить.  После того, как вы введете и скомпилируете эту программу, у вас появится удобное сред— ство для поцсчета объемов денежных выплат по ссудам и займам.  Листинг C6. mortgage. с — расчет выплат по процентным займам   1: /* Расчет выплат по займам. */ 2: 3: #include <stdio.h> 4: #include <math.h> 5: #include <stdlib.h> 6: 7: int main( void ) 8: { 9: float principal, rate, payment; 10: int term; 11: char ch; 12: 13: while (1) 14: { 15: /* Ввод данных о займе */ 16: puts("\nEnter the loan amount: "); 17: scanf("%f", &principal); 18: puts("\nEnter the annual interest rate: ");  19: scanf("%f", &rate); 
20: /* Приведение процента в удобную для расчетов форму. */ 21: rate /= 100;  22: /* Приведение к месячной процентной ставке. */ 23: rate /= 12; 24: 25: puts("\nEnter the loan duration in months: "); 26: scanf("%d", &term); 27: payment = (principal * rate) / (1 — pow((1 + rate), ~term)); 28: printf("Your monthly payment will be $%.2f.\n", payment); 29: 30: puts("Do another (y or n)?"); 31: do 32: { 33: ch = getchar(); 34: } while (ch != 'п' && ch != 'y’); 35: 36: if (ch == 'n') 37: break; 38: } 39: return(0); 40: }   № В этой программе предполагается расчет выплат по обычным займам с фиксиро— ванной процентной ставкой, таким как закладные под дом или автомобиль. Выпла- ты вычисляются по стандартной формуле, применяемой B финансовых расчетах:  выплата = (Р * R) / ( 1 — ( 1 + R )^(—Т))  Здесь Р— номинал, R — процент, Т— срок займа. Обратите внимание на символ ", кото— рый означает “в степени”. Для правильного расчета по этой формуле необходимо, чтобы срок и процент были приведены к одной единице времени. Таким образом, если срок займа задан в месяцах, то и ставку процента следует привести к этому же промежутку времени. Поскольку ссуды обычно выдаются с указанием годового процента, в строке 23 гоцовой процент делится на 12 для получения месячной процентной ставки. Объем ежемесячной выплаты вычисляется в строке 27, а в строке 28 на экран выводится ответ.  508 Неделя 3. Основные вопросы 
   Karim. Работа C памятью  &’ Ё‘ F2 ё ›  На этом занятии рассматриваются некоторые дополнительные средства для работы с па— дятью в программах на языке С. Будут изучены следующие темы.  I Преобразование типов I Распределение и освобождение ресурсов памяти I Манипулирование блоками памяти I  Обращение к отдельным битам памяти  Преобразование типов  Все объекты данных в языке С имеют определенный тип. Например, числовая переменная может иметь тип int или float, указатель может быть типа double или char и т.д. Часто воз— никаег необходимость сочетать данные различных типов в выражениях и операторах. Что происходит в таких случаях? Нередко компилятор С сам справляется с приведением типов, так что программисту нет нужды беспокоиться. А бывает и так, что приходится выполнять явное преобразование одного типа данных в другой во избежание ошибочных результатов. На предыдущих занятиях разбиралась ситуация, в которой указатель типа void необходимо было приводить к определенному типу, чтобы этим указателем можно было воспользоваться. В таких случаях нужно четко понимать роль явного преобразования типа и возможные ошиб— ки из-за отсутствия такового. В следующих разделах как раз и рассматриваются всевозмож- ные явные и автоматические преобразования типов в языке С.  Автоматические преобразования типов  Как это очевидно из названия, автоматические преобразования типов выполняются ком- пилятором С автоматически без вмешательства программиста. Тем не менее, полезно знать, как это делается, чтобы лучше понять процесс вычисления выражений в С.   llpumanua Автоматические преобразования типов называют также неявными преоб- разованиями.    
Расширение ТИПЗ В ВЫРЗЖЭНИЯХ  При вычислении выражения в С результат имеет вполне определенный тип. Если все со— ставляющие этого выражения Одного и того же типа, то и результат будет принадлежать к нему же. Например, если переменные x и у имеют тип int, то следующее выражение также будет иметь этот тип:  Х+у  А что если компоненты выражения отличаются по типу данных? В этом случае для ре— зультата будет выбран наиболее расширенный из входящих в выражение типов. Ниже приве- ден список числовых типов данных в порядке их расширения:  char short int long long long float double long double  Таким образом, результат выражения, сОдержащего переменные типа char и int, получит тип int, выражение с переменными типа long и float даст результат типа float и т.п. При вычислении выражений компилятор Одновременно оперирует только двумя перемен— ными или значениями. Пусть, например, вычисляется выражение:  Y+X*2  Вначале компилятор возьмет переменную X и константу 2, вычислит их произведение, а затем к получившемуся выражению прибавит переменную Y. Типы отдельных операндов в выражениях расширяются по мере необхОдимости, чтобы добиться соответствия между их парами. Расширение выполняется над операндами попарно для каждой двуместной операции. Разумеется, если Оба операнда имеют Один и тот же тип, расширения типов не требуется. А если оно все—таки необходимо, то выполняется по сле- дующим правилам.  I Если Один из операндов имеет тип long double, тип другого из них расширяется до long double. I Если Один из операндов имеет тип double, тип другого расширяется до double. I Если Один из операндов имеет тип float, тип другого расширяется до float. I Если Один из операндов имеет тип long, тип другого также расширяется до long.  Например, пусть переменная x имеет тип int, a у — тип float. Тогда при вычислении вы— ражения х/у вначале тип переменной x расширяется до float, a уже затем выполняется вы— числение. Тип переменной x при этом не изменяется — всего лишь создается копия ›‹ типа float, которая и используется при вычислении выражения. Значение всего выражения, как мы уже говорили, будет иметь тип float. Аналогичным образом, если х имеет тип double, a у —— тип float, то тип у расширяется до double.  Преобразование типа при присваивании  Расширение типа также имеет место и в операции присваивания. Выражение в левой части оператора присваивания всегда расширяется по своему типу до типа объекта данных. находящегося в левой части. Заметьте, что при этом вполне возможно “сужение” вместо  510 Неделя 3. Основные вопросы 
f асширения. Если переменная f имеет тип float, a і — тип int, то тип і расширяется до ffloat в следующем операторе присваивания: f = i; A BOT B ходе следующего присваивания тип переменной f, напротив, сужается до int: іі = f; Дробная часть переменной f отбрасывается при ее присваивании і. Помните, что сама f дри этом никак не изменяется ——- расширение типа воздействует только на копию перемен- gm“. Пусть имеется следующий фрагмент кода: флаг f= 1.23 ‚ int і' 91 = f Ёб; После выполнения этих операторов переменная і приобретает значение 1, а переменная f  прежнему сохраняет значение 1. 23 Как показывает этот пример, при преобразовании ве- Знает-венного числа в целое дробная часть теряется  ““`—_   «тю-эм  Большинство компиляторов выдаст предупреждение при неявном сужении типа переменной.  […     3:17. ‚ч,-\» 3111  Следует знать, что при преобразовании целого числа в вещественное с плавающей точкой Ёрезультат может несколько отличаться от исх0дного целого значения Это происхоцит пото- му, что внутреннее машинное представление вещественных чисел не способно передать с идеальной точностью все возможные целые числа Например, после выполнения следуюших генераторов на экране может появиться число 2 . 999995 вместо требуемого 3:  float f; inti= 3; i=1; printf("%f", f);  B большинстве случаев такая потеря точности несущественна. Тем не менее, для надеж- _‚ности лучше хранить целые числа в переменных типа short, int, long, long long.  ЁЯвные преобразования типов  Для непосредственного управления преобразованиями типов в программах используется {операция приведения типа. Для ее выполнения перед выражением ставится имя типа в круг- Ётых скобках. Приведение типа может выполняться над арифметическими выражениями или {гад указателями. Результат преобразуется к типу, указанному в скобках. Таким образом мож- о получить полный контроль над типами выражений, не полагаясь на автоматические пре- разования компилятора С  › риведение типов арифметических выражений  Приведение типа арифметического выражения — это команда компилятору представить ачение выражения определенным образом. Фактически, приведение типа эквивалентно его Ёрасширению или сужению, рассмотренному ранее. Разница состоит в том, что явное приве- gimme типа нахоцится под вашим контролем, а не компилятора. Пусть, например, переменная ;ъ. имеет тип int Тогца в следующем выражении она приводится к типу float:  float ) і   ень 20-й. Работа с памятью 511 
Другими словами, программа создает скрьпую копию переменной і в формате вещест— венного числа с плавающей точкой. В каких случаях используется приведение типов в арифметических выражениях? Наибо— лее распространенное его применение состоит в том, чтобы избежать потери дробной части числа при целочисленном делении. Листинг 20.1 иллюстрирует эту операцию. Скомпилируй— те и выполните эту программу.  Листинг 20. 1 . casting. с — как избежать потери дробной части при делении целых чисел   і: #include <stdio.h> 2: 3: int main( void ) 4: { 5: int іі = 100, і2 = 40; 6: float іі; 7: 8: іі = іі/і2; 9: printf("%1f\n", іі); 10: return 0; іі: } ,   2.000000 Peaunbmam  № Программа выдала ответ 2.000000, тогда как 100/40 равняется 2.3. Что же пр0« изошло? Выражение іі/і2 в строке 8 содержит две переменные типа int. Со- гласно правилам, рассмотренным ранее на этом занятии, если оба операнда имеют тип int, то и значение выражения также получает тип int. B итоге целочисленный результат вычисления не может вместить дробную часть, которая автоматически отбрасывается. Можно прибегнуть к такому способу, как присвоение результата деления іі/ і2 перемен— ной типа float. При этом значение типа int расширяется до типа float. Это частично пра— вильное, но запоздалое решение —— дробная часть числа уже пропала безвозвратно при цело— численном делении. Чтобы избежать такого рода погрешностей, необходимо привести одну из целочисленных переменных к типу float. Раз одна из переменных будет приведена к этому типу, над второй соответствующее преобразование выполнится автоматически, и в итоге все выражение будет иметь тип float. Дробная часть ответа не будет отброшена. Чтобы убедиться в этом, замени— те строку 8 в предыдущем листинге таким оператором присваивания:  f1 = (float)i1/i2;  Теперь программа выдаст правильный ответ.   llpllflam B более сложных выражениях может понадобиться несколько явных при— - ведении типов.     Приведение типов указателей  Вы уже знакомы с приведением типов указателей из материала предыдущих занятий. На занятии 18 рассматривался нетипизированный указатель типа void, который мог указывать на  512 Неделя 3. Основные вопросы 
объект произвольного типа. Перед тем, как использовать такой указатель для Обращения к памяти, его нужно привести к требуемому типу. Обратите внимание, что для присвоения та— кому указателю значения или для сравнения его с NULL приведение типа вовсе не обязательно. А вот для обращения к значению, на которое он указывает, или для выполнения операций ад— ресной арифметики это необх0димо. Более подробную информацию о работе с указателями типа void можно найти в материале занятия 18.    Рекомендуется  № расширен; … ‚, _… . _ ”WWW; “време.“ "№.; [$3532 "  K » _ 5 „а » 555» … «…»? №№№ 5 5 Mgm; «МАРТЕ; {ЧЁ %‘4'3'2‘8’0' 39' L“! . ` 035%?- №34 »: д’дъ! 55ml? “"> ' mm к:“и!т„<`‚‹ a. 5%, .. " ф! 5’; › a.» ‘ь&э.№‘3 - ‚Ё; " “5:45;? ‘ ;: : ее в: …а " " ›…  Ё‘УЁЁГДМ; A "1" ‚.— : `не int ? і; 5‘     рейсу-№№ …__ f: i, , _ ЁЁЬЁЁОЁ      : имидж:?“ 51% A W? ' тчВедЪМ ’ " .:::? ”"‘Ёча . 23 HO "ЁЁ WWW” : „ &&&—же? …% :01' лредУпреясдаЮЩе „иж „выясните… точную литва? Q ` …" … ется   № й’О  ормдщщ" %; ;;М'Э'Ё'З. _'.‚‚›‹4`“м ‹  г\ An‘gh‘ .'5'."'‚‚ » 3W3: “Ё“;яд %% @VJ'EM чину/& ‚ЁЁЁЗЁ: ждём   Распределение памяти  Библиотека С содержит функции для распределения памяти непосредственно в ходе выполнения программы. Этот процесс называется динамическим распределением памяти. Нередко именно этот способ оказывается более выигрышным по сравнению с явным распре- делением памяти в коде программы путем объявления переменных, структур и массивов — называемым также статическим распределением памяти. Для статического распределения памяти необходимо знать, сколько памяти потребуется для ее объектов, еще при написании программы. А вот динамическое распределение позволяет программе оперативно реагиро— вать на запросы к памяти в ходе ее выполнения —-—— например, на ввод пользователем опреде- ленных значений. Для работы всех функций динамического распределения памяти необходи- мо подключить заголовочный файл stdlib.h. Некоторые компиляторы требуют также файла malloc.h. Заметьте, что все функции распределения памяти возвращают указатель типа void. Как говорилось на занятии 18, указатель типа void необходимо сначала привести к опреде— ленному типу, а потом уже использовать его для обращения по адресу. Прежде чем углубляться в подробности, скажем пару общих слов o распределении памяти компьютера. Что, собственно, это такое? В каждом компьютере установлен определенный объ— ем оперативной памяти (которую довольно старомщно называют также оперативным запоми— нающим устройством —- ОЗУ). Этот объем различен в разных компьютерных системах. При за— пуске программы, будь то текстовый или графический редактор либо программа, написанная вами собственноручно на языке С, она загружается с диска в память компьютера. Занимаемое программой пространство в памяти содержит код программы и ее статические данные, т.е. эле- менты данных, объявленные непосредственно в тексте программы. Оставшийся объем памяти можно распределить для прочих нужд программы, используя функции этого раздела. Сколько памяти доступно для динамического размещения данных? Однозначно ответить нельзя. Если большая программа работает в системе с небольшим объемом ОЗУ, то для ди- намических операций остается очень малый объем памяти. Напротив, если маленькая про- грамма запущена в системе с сотнями мегабайт памяти, то для ее динамического распределе— ния останется много пространства. Таким образом, программе не следует делать каких бы то ни было предположений об объеме доступной памяти. При вызове одной из функций распре-  День 20—й. Работа с памятью 513 
деления памяти необходимо проверять возвращаемое ею значение, чтобы убедиться в успеш- ности ее работы. Кроме того, программа должна корректно обработать случай, когда опера- ционная система отказывает ей в распределении памяти. Несколько позже на этом же занятии вы узнаете, как определить доступный объем памяти в системе. Наличие свободной памяти в системе и ее распределение существенно зависит от опера- ционной системы. Некоторые операционные системы предоставляют программе всего лишь часть оперативной памяти. К их числу относится DOS 6.x и более ранние ее версии. Даже ес- ли на компьютере установлены десятки или сотни мегабайт памяти, программа под управле- нием DOS получает доступ только к ее первым 640 Кбайт. (Прибегнув к специальным сред- ствам, можно добраться и до остальной памяти, но эта тема выходит за пределы тематики нашей книги.) А вот UNIX предоставляет программе весь наличный объем оперативной па- мяти. Бывают и еще более сложные варианты: некоторые системы, например, Windows или 05/2, располагают виртуальной памятью, которая представляет собой область на жестком диске с возможностью обращения к ней, как к “обычной” оперативной памяти. В этом случае пространство памяти, доступное для динамического размещения объектов, включает не толь- ко физический объем ОЗУ, но и виртуальную память на жестком диске. В большинстве случаев то, как конкретная операционная система распоряжается памятью, не должно особенно заботить программиста. Когда вызывается одна из функций распределе- ния памяти С, ее запрос к операционной системе на участок памяти определенной длины ли- бо удовлетворяется, либо нег. Подробности функционирования системы остаются за кадром.  Распределение памяти с помощью функции гпа||ос()  На предыдущих занятиях вы узнали, как пользоваться библиотечной функцией ma110c() для распределения памяти под строки символов. Возможности этой функции однако не огра- ничиваются работой со строками. Она может распределять память для объектов любого типа. Выделяемый ею объем памяти исчисляется в байтах. Напомним прототип этой функции:  void *malloc(size_t лиш);  Тип аргумента size_t определен в заголовочном файле stdlib.h как unsigned. Функция ma110c() вьшеляет в памяти блок из num байт и возвращает указатель на его первый байт. Функция возвращает NULL, если запрашиваемый объем памяти выделить невозможно или ес- ли num == 0. Все это уже изучалось на занятии 10 в разделе “Функция ma110c( )”. Повторите этот раздел, если вам что-то неясно. Листинг 20.2 демонстрирует, как можно использовать функцию ша110с() для определе- ния объема свободной памяти в системе. Программа отлично работает в системе DOS, a так- же в командной среде DOS под управлением одной из старых версий Windows. Но не удив- ляйтесь, если получите странные результаты при выполнении программы в Windows одной из последних версий, 03/2 или UNIX, т.е. в системах, использующих виртуальную память на жестких дисках. Программе может понадобиться очень много времени для того, чтобы ис- черпать всю имеющуюся в таких системах память.   ( lllllllll Если ваша операционная система многозадачная, т.е. в ней могут рабо- тать одновременно несколько программ, то перед выполнением листин- га 20.2 необх0димо сначала закрыть все активные программы. Дело в том, что при работе этой демонстрационной программы может произойти ка- кой-либо сбой или “зависание" после того, как исчерпается вся свободная память в системе.     514 Неделя 3. Основные вопросы 
Листинг 20.2. ша11ос. с — определение объема свободной памяти в системе с помощью функции ша11ос( )   /* Использование malloc() для определения свободной памяти.*/  #include <stdio.h> #include <stdlib.h>  /* Объявление структуры размером 1024 байта (1 килобайт). */  оо `.: сл … .=. ш N ‚_- .. .. .. .. .. .. .. ..  9: struct kilo { 10: struct kilo *next; 11: char dummy[1022]; 12: }: 13: 14: int FreeMem(void); 15: 16: int main( void ) 17: { 18: 19: printf("You have %d kilobytes free.\n", FreeMem()); 20: return 0: 21: } 22: 23: int FreeMem(void) 24: { 25: /* Возвращает количество килобайт (порций по 1024 байта) 26: свободной памяти в системе. */ 27: 28: long counter; 29: struct kilo *head, *current, *nextone; 30: 31: current = head = (struct kilo*) malloc(sizeof(struct kilo)); 32: 33: if (head == NULL) 34: return 0; /* Нет свободной памяти. */ 35: 36: counter = 0: 37: do 38: { 39: counter++; 40: current—>next = (struct kilo*) malloc(sizeof(struct kilo)): 41: current = current—>next; 42: printf("\r%d", counter); 43: } while (current != NULL); 44: 45: /* Теперь счетчик counter содержит количество структур 46: типа kilo, которое нам удалось разместить в памяти. 47: Эту память необходимо освободить перед возвращением. */ 48:  День 20—й. Работа с памятью 515 
49: current = head;  50: do 51: { 52: nextone = current->next; 53: free(current); 54: current = nextone; 55: } while (nextone != NULL); 56: 57: return counter; 58: }   You have 60 kilobytes free. Peaunbmam  Программа, представленная в листинге 20.2, действует напролом. Она попроету запраши- вает у системы блоки памяти объемом один килобайт в цикле, пока память не закончится, т.е. пока функция malloc() не возвратит NULL. B системе с большим объемом памяти на это мо- жет уйти несколько минут. В строке 42 программы находится оператор вывода, который ото- бражает на экране счетчик. Он предназначен для того, чтобы пользователь видел, что про- грамма действительно работает, а не простаивает. Итак, объем свободной памяти равен количеству выделенных блоков, умноженному на длину блока. Определив этот объем, функция освобождает все размещенные в памяти блоки и возвращает их количество в вызывающую программу. Каждый блок имеет размер один ки- лобайт, и это обеспечивает удобное соответствие между количеством блоков и объемом дос- тупной памяти в килобайтах. Как вы, наверное, знаете, килобайт содержит не тысячу байт ровно, а 1024 байта (2 в десятой степени). Блок данных объемом 1024 байта создается в про- грамме путем определения структуры, специально названной kilo, B которую входит массив длиной 1020 байт и указатель длиной 4 байта. В функции FreeMem( ) используется техника связанных списков, достаточно подробно рассмотренная на занятии 15. Вкратце говоря, связанный список состоит из структур, кото- рые помимо прочих элементов данных содержат указатель на структуру того же типа. В спи- ске имеется также начальный указатель, который указывает на первый элемент списка (в на- шем случае это head, указатель на структуру типа kilo). Первый элемент в списке указывает на второй, второй —— на третий и т.д. Последний элемент в списке выделяется тем, что указа- тель в его структуре равен NULL, т.е. следующего элемента не существует. За более подробной информацией на этот счет обращайтесь к материалу занятия 15.  Распределение памяти с помощью функции calloc()  Функция са11ос() также предназначена для распределения памяти. Однако вместо того, чтобы выделять группы байт для размещения данных, как это делает malloc( ), функция са110с( ) размещает в памяти группу объектов. Эта функция имеет следующий прототип:  void *calloc(size_t num, size_t size);  Помните, что для большинства компиляторов тип size t — синоним типа unsigned. Ар- гумент num равен количеству объектов для размещения в памяти, а size — 3T0 размер каждо- го объекта в байтах` Если размещение прошло успешно, вся выделенная память очищается  516 Неделя 3. Основные вопросы 
(заполняется нулями), и функция возвращает указатель на первый байт. Если num или size равны 0 или же распределение памяти потерпело неудачу, функция возвращает NULL. Использование са11ос( ) иллюстрируется листингом 20.3.  Листинг 20.3. calloc. с —— использование функции са11ос( ) для динамического распределения памяти   \  ‚_.-  ФЧФШОЬШЮ .. II II II II II II II II  /* демонстрация функции са11ос(). */  #include <stdlib.h> #include <stdio.h>  int main( void )  { unsigned long num; 9 int *ptr; 10: ll: printf("Enter the number of type int to allocate: "); 12: scanf("%ld", &num); l3: l4: ptr = (int*)calloc(num, sizeof(long long)); 15: ‘16: if (ptr := NULL) 17: puts("Memory allocation was successful."); 18: else 19: puts("Memory allocation failed."); 20: return О; 21: }   P Enter the number of type int to allocate: 100 eaunbmam Memory allocation was successful.  Enter the number of type int to allocate: 99999999 Memory allocation failed.  № В строках 11 и 12 программа запрашивает у пользователя число. Это число обо- значает объем памяти, который программа попытается выделить. В строке 14 предпринимается попытка создать буфер памяти для хранения указанного количества пере- менных типа long long. Если распределение памяти оказалось неудачным, функция са11ос( ) возвращает NULL; B противном случае возвращается указатель на созданный буфер. В нашей программе возвращаемое из са11ос( ) значение помещается в указатель ptr типа int. Onepa- тор if B строках 16—19 проверяет по значению указателя ptr, как закончилось распределение памяти, и выводит на экран соответствующее сообщение. Введите несколько разных чисел и посмотрите, сможет ли программа выделить указан- ный ей объем памяти. Максимальный объем в значительной степени зависит от конфигу- рации системы. Например, в конкретном случае 25 000 чисел типа long long могут помес- титься в памяти, а 30 000 —- уже нет. Помните, что сама длина данных типа long long за- висит от системы. Если объем физической памяти составляет несколько сотен мегабайт, то может потребоваться очень большое число для того, чтобы распределение памяти оказа- лось неудачным.  День 20-й. Работа c памятью 517 
Распределение дополнительной памяти с помощью функции realloc()  Функция realloc() изменяет размер блока памяти, ранее созданного с использованием функции malloc() или calloc( ). Функция имеет следующий прототип:  void *realloc(void *ptr, size_t size);  Аргумент ptr указывает на исходный блок памяти. Новый размер в байтах указывается параметром size. При вызове realloc() возможен Один из следующих исх0дов.  I Если для расширения блока, находящегося по адресу ptr, имеется достаточно памяти, то произволится ее вылеление в нужном количестве, и функция возвращает ptr.  I Если памяти недостаточно для_того, чтобы расширить существующий блок по его те- кущему адресу, то создается новый блок размером size, и имеющиеся данные копи— руются из старого блока в начало нового. Старый блок освобождается, и функция воз- вращаег указатель на новый блок памяти.  I Если аргумент ptr равен NULL, то функция действует точно так же, как malloc(), выделяя блок памяти размером size байт и возвращая указатель на него.  I Если аргумент size равен 0, блок памяти по адресу ptr освобождается, и функция воз- вращает NULL.  I Если для перераспределения недостаточно памяти (т.е. нельзя ни расширить старый блок, ни разместить новый), функция возвращает NULL, и исходный блок остается не- изменным.  Использование realloc( ) демонстрируется в листинге 20.4.  Листинг 20.4. realloc . с — динамическое увеличение размера блока памяти с помощью функции realloc( )   : /* Использование функции realloc() для перераспределения памяти. */ 2: 3: #include <stdio.h> 4: #include <stdlib.h> 5: #include <string.h> 6: 7: 8:  int main( void )  9 char buf[80], *message; 10: 11: /* Ввод строки. */ 12: 13: puts("Enter a line of text."); 14: gets(buf); 15: 16: /* Размещение первоначального блока и копирование строки в него. */ 17: 18: message = realloc(NULL, strlen(buf)+1); 19: strcpy(message, buf); 20:  518 Неделя 3. Основные вопросы 
21: /* Вывод сообщения. */  22: 23: puts(message); 24: 25: /* Ввод еще одной строки от пользователя. */ 26: 27: puts("Enter another line of text."); 28: gets(buf); 29: 30: /* Расширение блока и присоединение к нему новой строки. */ 31: 32: message = realloc(message,(strlen(message) + strlen(buf)+1)); 33: strcat(message, buf); 34: 35: /* Вывод нового сообщения. */ 36: puts(message); 37: return 0; 38: }   Enter a line of text. Peaunbmam This is the first line of text.  This is the first line of text. Enter another line of text. This is the second line of text. This is the first line of text.This is the second line of text.  Эта программа принимает от пользователя строку текста (см. оператор в стро- ке 14), считывая ее в массив символов под именем buf. Затем строка копируется  в участок памяти, на начало которого указывает указатель message (строка 19). Блок памяти для message выцеляется с помощью функции realloc() B строке 18. Здесь используется именно функция realloc( ), хотя до этого никакого распределения памяти не было. Для вы- полнения такой операции достаточно передать NULL B функцию в качестве первого аргумента. В строке 28 выполняется ввод второй строки в буфер buf. Эта строка дописывается в хвост к той, которая уже находится по адресу message. Поскольку буфер message имеет дли- ну, достаточную лишь для хранения первой сгроки, его необходимо разместить в памяти за- ново для того, чтобы поместились обе введенные строки одна за другой. Именно это и дела- ется в сгроке 32. Завершается программа выводом обеих строк, сцепленных в одну.  Освобождение памяти с помощью функции free()  При распределении памяти c помощью функции та11ос() или са11ос( ) эта память берет- ся из динамической области, досгупной программам. Эту область иногда называют “кучей” (heap) . Она не бесконечна, т.е. имеет определенные пределы. Когда программа заканчивает работу с блоком динамически распределенной памяти, этот блок следует освободить. Впо- следствии объем освобожденной памяти может снова использоваться для динамического размещения данных и т.д. Для освобождения динамически распределенной памяти использу- ется функция f ree( ). Она имеет следующий прототип:  void free(void *ptr);  День 20-й. Работа с памятью 519 
Функция f ree( ) освобождает блок памяти, на который указывает аргумент ptr. Этот блок должен был быть выделен ранее с помощью функций malloc( ), calloc( ) или realloc( ). Ес— ли указатель ptr равен NULL, функция free( ) ничего не делает. Ее использование демонстри— руегся в листинге 20.5. Она уже применялась также и в листинге 20.2.  Листинг 20.5. free . с — освобождение динамически распределенной памяти с помощью функции free( )   <33 "] (3‘ (J1 #>> С:) Ъ`) Р-д an an an .. an ll .. ..  ШЮЮМЮЮЮЮЮЮЮНННННННННН <=> NC) (I) -J (" ‘)1 J>> С;) Ъ`) *_* ‘=>' \С> (I) "! (3‘ (J1 nth. (a) Ъ`) Р-д <=> ‘43 и. и. и. и. .. и. и. и. и. и. и. и. и. и. .. an I. an an I. I. ll  “» “» J)» J)» (A) ‘A’ (A) (A) ‘A’ ‘A’ (A) (A) ‘A’ (A) ") О—* С:) ", (I) "] (3‘ LII “5» LA) ") О—д 00 on и. и. и. и. an an an I. I. I. an  520  /* Применение функции free() для освобождения динамической памяти. */  #include <stdio.h> #include <stdlib.h> #include <string.h>  #define BLOCKSIZE 3000000  int main( void )  {  void *ptrl, *ptr2; /* Выделение одного блока. */  ptrl = malloc(BLOCKSIZE);  if (ptrl != NULL) ’ printf("\nFirst allocation of %d bytes successful.", BLOCKSIZE); else { printf("\nAttempt to allocate %d bytes failed.\n", BLOCKSIZE); exit(1); }  /* Попытка выделения второго блока. */ ptr2 = malloc(BLOCKSIZE);  if (ptr2 != NULL)  { /* B случае успеха вывести сообщение и выйти. */ printf("\nSecond allocation of %d bytes successful.\n", BLOCKSIZE); exit(0); }  /* B случае неудачи освободить первый блок и попробовать снова.*/ printf("\nSecond attempt to allocate %d bytes failed.",BLOCKSIZE);  free(ptrl); printf(“\nFreeing first block.");  Неделя 3. Основные вопросы 
44: ptr2 = malloc(BLOCKSIZE);   45: 46: if (ptr2 != NULL) 47: printf("\nAfter free(), allocation of %d bytes successful.\n“, 48: BLOCKSIZE); 49: return 0; 50: }  Программа пытается динамически разместить в памяти два блока. Длина этих блоков определяется константой BLOCKSIZE, объявленной в программе. В стро- ке 15 выполняется первое распределение памяти с помощью функции ша11ос(). B стро- ках 17—23 проверяется, было ли это распределение успешным. Для этого возвращенное функ— цией значение сравнивается с NULL. Затем выводится сообщение о результате операции по распределению памяти. Если эта операция закончилась неудачей, программа завершает рабо— ту. В строке 27 делается попытка разместить второй блок памяти, и снова проверяется ус- пешность этой операции (строки 29—36). Если второе распределение памяти оказалось ус- пешным, программа завершается вызовом функции exit( ). Если же произошла ошибка, вы- водится сообщение o неудачном распределении памяти. Затем первый блок освобождается функцией f ree( ) (строка 41), и делается вторая попытка разместить в памяти второй блок. Значение символической константы BLOCKSIZE можно при желании изменить. В некото— рых системах при ее текущем значении будут получены следующие сообщения:  First allocation of 3000000 bytes successful. Second attempt to allocate 3000000 bytes failed. Freeing first block. After free(), allocation of 300000 bytes successful.  P First allocation of 3000000 bytes successful. eagnhmm Second allocation of 3000000 bytes successful.  B системах с виртуальной памятью распределение памяти почти всегда дает положитель— ные результаты.     Рекомендуется Не рекомендуется  вебомате инамйэчески: ’bacnpe: ЁЁ H9 :npennonaramg заранее ::::что asaba еленную: Память ,;после завершения: функции malloc(),5.callucn или теапрц )* „,ТЁЫ Флем. 95:55:55 :: ‘ — *::::::;:…:::ЗЁ‘ЁЁ 5.: :? Езавершится успешно: Другими еловами„  :: 5 5 :95: г:, 59:59“. 529993995325955::9$.55 5 : ' 9-5.9: 595.- 55. 33`Ё3Ё*:':*:`°° 5599555955599 :, Ё Edema проверяте пронзошло ли распре—;: ‚ ` ' *:*, ‘vz  .т3“р}117‚$`$эіі`,‹ 7: „… .5 "; 3:3 , r: ": ?:…“ „на и “ ‚& :::—@:: 5599995559. 5 : 59959 '95?“*5§5%:55519f§55“4 Ё делениеЁпамяти на шмом деле, проверяя ‚ _::“:3:3: ‚:Ё :… ,:ЁЁЁЁМ: 999:9 5 55 5 Ёзначениб“сбошетствующего указателя  … ш.../.д.… ‚..и—Ё,— …." ……    мита-: кв.м—№“ _.—    » I Н.м-г.р— ч.:—« м»...   Манипулирование блоками памяти  До сих пор мы занимались исключительно вопросами выделения и освобождения блоков памяти. Однако в библиотеке С имеются и функции для других манипуляций с блоками, на- пример, помещения во все байты блока определенных значений, копирования и перемещения информации из одного места в другое.  День 20-й. Работа с памятью 521 
Инициализация памяти с помощью функции memset()  Функция memset() используется для того, чтобы сделать все байты определенного блока памяти равными одному и тому же заданному значению. Она имеет следующий прототип:  void *memset(void *dest, int c, size_t count);  Аргумент dest указывает на блок памяти, c -——— это значение, которое следует поместить в байты блока, а count —— количество этих байтов, начиная с dest. Заметьте, что переменная c имеет тип int, a используется в качестве char. Другими словами, используется только ее младший байт, так что всегда задавайте для нее значения из диапазона от 0 до 255. Поскольку функция memset() инициализирует блок памяти побайтно значениями типа char, нет смысла использовать ее для работы с блоками данных других типов. Исключение составляет тот случай, когда нужно обнулить весь блок. Например, массив чисел типа int нельзя заполнить числами 99 с помощью функции memsét( ), a BOT сделать все элементы равными нулю —— вполне возможно. Применение memset( ) будет продемонстрировано в листинге 20.6.  Копирование блоков памяти функцией тетсру()  Функция memcpy( ) копирует байты данных из одного блока памяти в другой. Такие блоки часто называются буферами. Функция не различает тип копируемых данных, просто выпол— няя аккуратный перенос информации байт за байтом. Ее прототип имеет следующий вид:  void *memcpy(void *dest, void *src, size_t count); -  Аргументы dest и src указывают соответственно на блоки памяти, куда и откуда копируют- ся данные. Аргумент count задает количество копируемых байт. Функция возвращает значение dest. Если два блока памяти накладываются один на другой, функция может сработать некор— ректно: часть данных в блоке src окажется затертой еще до их копирования. Для работы с пере— крывающимися блоками памяти используйте функцию memmove( ), которая рассматривается ни- же. Далее в листинге 20.6 будет проиллюстрировано применение функции шешсру( ).  Перемещение блоков памяти с помощью функции memmove()  Функция memmove() напоминает по своему назначению шешсру() -——— она копирует задан- ное количество байт из одного блока памяти в другой. Однако она обладает большей гибко- стью, поскольку корректно справляется с ситуацией взаимного наложения блоков. Раз memmove() делает все то же, что и шешсру( ), да еще и работает лучше с перекрывающимися блоками памяти, вполне естественно, что функция memcpy() применяется очень редко. Про— тотип функции memmove() имеет следующий вид:  void *memmove(void *dest, void *src, size_t count);  Указатели src и dest указывают на исходный и конечный блоки памяти соответственно. Параметр count задает количество копируемых байт. Функция возвращает dest. Если блоки накладываются один на другой, то функция гарантированно выполняет копирование инфор— мации из перекрываемой области прежде, чем затереть ее новыми данными. Применение функций memset( ), memcpy( ) и memmove( ) демонстрируется в листинге 20.6.  522 Неделя 3. Основные вопросы 
Листинг 20.6. mem. с -— демонстрация работы функций шешв‘ец ), memcpy( ) и memmove()   1: /* демонстрация функций memset(), memcpy() и memmove(). */  #include <stdio.h> #include <string.h>  char message1[60] char message2[60] char temp[60];  "Four score and seven years ago ..."; "abcdefghijklmnOpqrstuvwxyz";  ”\Ic‘mohWN II II II II II II II  9: 10: int main( void ) 11: { 12: printf("\nmessage1[] before memset():\t%s", message1); 13: memset(message1 + 5, '6', 10); 14: printf("\nmessage1[] after memset():\t%s", message1); 15:  16: strcpy(temp, message2); 17: printf("\n\n0riginal message: %s", temp); 18: memcpy(temp + 4, temp + 16, 10); 19: printf("\nAfter memcpy() without overlap:\t%s", temp); 20: strepy(temp, message2); 21: memcpy(temp + 6, temp + 4, 10); 22: printf("\nAfter memey() with overlap:\t%s", temp); 23: 24: strcpy(temp, message2);  25: printf("\n\n0riginal message: %s", temp); 26: memmove(temp + 4, temp + 16, 10); 27: printf("\nAfter memmove() without overlap:\t%s", temp); 28: strcpy(temp, message2); 29: memmove(temp + 6, temp + 4, 10);  30: printf("\nAfter memmove() with overlap:\t%s\n", temp); 31: return 0; 32: }   Pflmmmmn message1[] after memset(): Four @@@@@@@@@@seven years ago ...  Original message: abcdefghijklmnOpqrstuvwxyz After memcpy() without overlap: abcdqrstuvwxyzopqrstuvwxyz After memcpy() with overlap: abcdefefefefefefqrstuvwxyz  Original message: abcdefghijklmnopgrstuvwxyz After memmove() without overlap: abcdqrstuvwxyzopqrstuvwxyz After memmove() with overlap: abcdefefghijklmnqrstuvwxyz  message1[] before memset(): Four score and seven years ago ...  Функция memset( ) выполняет вполне очевидную операцию. Обратите вниман на то, как с помощью адресного выражения message1 + 5 задается начальн  смещение в массиве message1[ ]. Заполнение начинается с шестого символа массива, которо  День 20-й. Работа с памятью 
соответствует смещение 5 (не забывайте —— нумерация элементов массива начинается с нуля). В результате символы с шестого по пятнадцатый заменяются на @. Если блоки памяти не перекрываются, функция memset() прекрасно справляется с задачей. Десять символов из массива temp[ ], начиная с позиции №17 (буквы от q до z), копируются в по- зиции с 5—й по 14-ю, в которых первоначально располагались буквы от е до n. Если же блоки перекрываются, ситуация отличается от предыдущей. Когда функция пытается копировать 10 символов, начиная с 4-й позиции, в участок памяти, начинающийся с 6-й позиции, происхолит наложение целых восьми байт. Можно было бы ожидать, что буквы от е до n будут скопирова- ны на место букв от g до р. Но вместо этого буквы е и f повторяются пять раз. Если блоки памяти не перекрываются, функция memmove() работает точно так же, как memcpy( ). Однако если перекрывание всеотаки присутствует, то только функция memmove( ) гарантирует правильное копирование символов из одного блока в другой.   Рекомендуется Не рекомендуется Мейби "е`аі`епптіочеі )ивместб” шарит  ЁЁ ёЁнеЪБХОДтлмо переносить; данные??} алерт: рывающ мнея Участком»; &  '*'-"'ё 1:“ $313? %% 52.1%” ‚ $523: 5T6 “:“ бв?“ :‘K e 05:28" a ` . ( ‚3! fig" ! ** ‚$$?/Ё ’1‘ m @9953? «;: ті Jain!) .… № . ~21 ' ` №№“; {#3 "© fig}? 3;: „“;“; m “a "‘" ‘…»ЁЗЕЩ “m... Mama и 332…“ “м…… &..….‘Ёёвььё  U      S : 3 $ :     Операции над отдельными битами памяти  Как вы наверняка знаете, самой элементарной единицей хранения информации в компью— терах является бит. Бывают ситуации, в которых возможность манипулирования отдельными битами в программе на С становится очень полезной. В языке С имеется целый ряд средств для этой цели. Битовые (двоичные поразрядныс) операции С дают возможность манипулировать отдель- ными битами целочисленных переменных. Не забывайте, что бит, наименьшая возможная единица хранения информации, может принимать одно из двух значений ~-— 0 или l. Пораз— рядные операции можно выполнять только над значениями целочисленных типов: char, int и long. Прежде чем продолжать изучение этого раздела, освежите свои знания двоичной сис- темы счисления — теоретической основы хранения и обработки чисел в памяти компьютера. Всю необходимую информацию можно найти в приложении В, Поразрядные двоичные операции особенно часто используются в тех случаях, когда про- грамма на С напрямую обращается к аппаратным ресурсам компьютера. Эта тема выходит за пределы нашей книги. Но операции над отлельными битами находят и другие применения, о которых будет сказано далее.  Операции поразрядного сдвига  Существует две операции для сдвига битов в целочисленных переменных на заданное число позиций. Операция « сдвигает биты влево, а операция » — вправо. Эти операции имеют следующий синтаксис:  x<<n x>>n  524 Неделя 3. Основные вопросы 
Каждая из операций сдвигает биты на n позиций в указанном направлении. При сдвиге вправо n старших битов переменной заполняются нулями, а при сдвиге влево ЗЗПОЛНЯ'ЮТСЯ n младших битов. Вот несколько примеров.  I Двоичное число 00001100 (десятичное значение которого равно 12) при сдвиге вправо на 2 разряда становится равным 00000011 (в десятичной записи —— 3).  I Двоичное число 00001100 (десятичное 12) после сдвига влево на 3 разряда будет равно 01100000 (десятичному 96).  I Сдвиг двоичного числа 00001100 (десятичного 12) на 3 разряда вправо дает 00000001 (десятичную единицу).  I Двоичное число 00110000 (десятичное 48) при сдвиге на 3 разряда влево дает 10000000 (десятичное 128).  При определенных обстоятельствах операциями сдвига можно воспользоваться для ум- ножения и деления целочисленных переменных на числа, равные степеням двойки. Сдвиг на „ позиций влево эквивалентен умножению на 2”, а вправо— делению на 2”. Результаты ум- ножения путем сдвига влево будут правильными только в том случае, если не произошло пе- реполнения -—— т.е. ни один бит не потерялся при сдвиге за пределы самого старшего разряда. деление путем сдвига вправо является целочисленным делением, при котором отбрасывается дробная часть результата. Например, если сдвинуть вправо число 5 (двоичное 00000101) на одну позицию с целью разделить его на два, то результат будет равен 2 (двоичному 00000010) вместо правильного ответа 2.5. Дробная часть .5 отбрасывается. Операции сдвига демонст- рируются в листинге 20.7.  Листинг 20.7. shiftit.c — применение операций сдвига   1: /* демонстрация операций сдвига. */ 2: 3: #include <stdio.h> 4: 5: int main( void ) 6: { 7: unsigned int у, х = 255; 8: int count; 9: 10: printf("Decimal\t\tshift left by\tresult\n"); 11: 12: for (count = 1; count < 8; count++) 13: { 14: у = х << count; 15: printf(“%d\t\t%d\t\t%d\n", х, count, у); 16: } 17: printf("\n\nDecimal\t\tshift right by\tresult\n"); 18: 19: for (count = 1; count < 8; count++) 20: { 21: y = х » count; 22: printf("%d\t\t%d\t\t%d\n“, х, count, y); 23: }  День 20-й. Работа с памятью 525 
24: return 0;   25: } Decimal shift left by result 255 1 … 255 2 252 255 3 248 255 4 240 255 S! 224 255 6 192 255 7 128 Decimal shift right by result 255 1 127 255 2 63 255 3 31 255 4 15 255 S 7 255 6 3 255 7 1  Поразрядные логические операции  В языке С имеются три поразрядные логические операции, c помощью которых можно манипулировать отдельными битами в целочисленных переменных. Они перечислены в табл. 20.1. Обозначения этих операций похожи на знаки логических операций, дающих ре- зультаты TRUE H FALSE, но сами операции выполняются по-другому.  Таблица 20. 1 . Пораэрядные логические операции   Знак операции Суть операции   & Побитовое логическое и (AND) I Побитовое логическое включающее или (0R)  А  Побитовое логическое исключающее или (XOR)   Bce 3TH операции —— поразрядные, делающие биты результата равными 0 или 1 в зависи- мосги от значений битов в операндах. Они работают следующим образом.  I Применение поразрядного И (AND) делает бит результата равным 1, только если соот- ветствующие биты обоих операндов равны 1; в противном случае бит становится рав- ным 0. Операция И используется для отключения (очистки) Одного или нескольких би- тов целочисленной переменной.  I Применение поразрядного ИЛИ (0R) делает бит результата равным 0, только если co- ответствующие биты обоих операндов равны 0, в противном случае —— 1. Операция ИЛИ используется для включения (установки) одного или нескольких битов целой переменной.  I Применение поразрядного исключающего ИЛИ (0R) делает бит результата равным 1, если соответствующие биты обоих операндов различны (один равен нулю, другой _ единице). В противном случае бит становится равным 0.  526 Неделя 3. Основные вопросы 
Вот несколько примеров того, как работают эти операции:   Операция Пример  и 11110000 & 01010101  01010000  Включающее или 11110000 | 01010101  11110101  Искпючающее или 11110000 " 01010101  10100101   Только что говорилось о том, что побитовые операции логического сложения (И) и умноже- ния (ИЛИ) могут использоваться для включения-отключения заданных битов B целочисленной переменной. Поясним, что же имелось B виду. Пусть, например, y нас есть переменная типа char, и нам необходимо очистить нулевой и четвертый биты (т.е. сделать их равными 0), оста- вив остальные неизменными. Выполним операцию логического И нашего числа с двоичным числом 11101110 и получим желаемый результат. Давайте разберем эту операцию подробнее.  I B каждой позиции, где во втором числе стоит единица, результат сохранит то значение бита (0 или 1), которое было B исходной переменной: 0 & 1 == 0 1 & 1 == 1 I B каждой позиции, где во втором числе стоит ноль, B результате будет находиться ну- левой бит вне зависимости от того, какое значение имел этот бит B первоначальной  переменной: 0 & 0 == 0 1 & 0 == 0  Установка (включение) битов с помощью операции ИЛИ выполняется аналогично. В каж- дой позиции, B которой второе число содержит 1, B результате возникнет единица. Везде, где во втором числе стоит 0, значение исходного бита не изменится:   o 1 == 1 1 == 1 o | 0 == о 1 0 == 1 Операция дополнения  Последняя двоичная поразрядная операция, которую мы рассмотрим —— это операция до- полнения ". Она представляет собой одномеегную операцию. Ее действие состоит B обраще- нии всех битов операнда, т.е. замене нулей на единицы и наоборот. Например, "254 (двоичное 11111110) становится равным 1 (двоичиому 00000001).  День 20-й. Работа с памятью 527 
Во всех примерах этого раздела использовались переменные типа char. Над более длин— ными целочисленнымн переменными типа int или long рассмотренные операции выполня- ются точно так же.  Битовые поля в структурах  Последняя тема, которую мы рассмотрим в связи с манипулированием битами памяти, —- это битовые поля в структурах. На занятии 11 было рассматрено определение новых струк- турных типов данных. Использование битовых полей дает дополнительные возможности при работе со структурами, а также экономит память. Битовое поле — это элемент структуры, состоящий из заданного количества битов. Мож— но объявить битовое поле, состоящее из одного бита, двух битов или любого их количества` необходимого для хранения данных° Какие преимущества мы при этом получаем? Допустим, вы пишете программу для ведения базы данных о всех сотрудниках вашей ор- ганизации. Многие из полей в такой базе содержат данные типа ответов “да” или “нет” на за- данные вопросы, например, о наличии медицинской страховки или высшего образования. Каждый такой ответ можно держать в однобитовом поле, где единица соответствует “да”, а ноль —— “нет”. Что касается стандартных типов данных С, то самый короткий из них, который можно использовать в структуре, это тип char. Можно, конечно, завести для каждого Элемента дан- ных “да—нет” отцельную переменную типа char. Но в каждой такой переменной семь из восьми битов будут пропадать зря. А вот применение битовых полей позволит поместить в одну переменную типа char целых восемь ответов “да—нет”. Битовые поля могут иметь и более широкое применение, чем хранение данных типа “да— нет”. Продолжая наш пример с базой данных, предположим, что в вашей организации преду- смотрено три различных системы медицинской страховки. В базу данных необходимо помес- тить сведения o том, по какой системе застрахован тот или иной сотрудник (если вообще за- страхован). В связи с этим можно обозначить нулем отсутствие страховки, а числами 1, 2 и 3 —— соответствующие системы. Для этого достаточно двух битов, поскольку два бита пред- ставляют числа от 0 до 3 включительно. Аналогичным образом, поле из трех битов может со- держать числа от 0 до 7, поле из четырех битов —— от 0 до 15 ит.д. Битовые поля именуются по тем же правилам, что и другие элементы структур. Обраще- ние к ним также ничем не отличается от обычного. Все битовые поля должны иметь тип unsigned int, a размер поля указывается целым числом после точки с запятой, следующей за именем поля. Вот определение структуры, содержащей однобитовые поля dental и college, a также двухбитовое поле health:  struct emp_data  { unsigned dental : 1; unsigned college : 1; unsigned health : 2; }:  Здесь многоточие (. . .) заменяет объявления остальных элементов структуры. Этими эле- ментами могут быть как битовые поля, так и поля обычных типов. Обратите внимание, что битовые поля следует ставить в начале определения структуры, перед всеми остальными по- лями. Для обращения к битовым полям структуры используется тот же знак точки, что и для  528 Неделя 3. Основные вопросы 
любых других полей. Например, можно расширить приведенное выше определение структу— ры до следующего, вполне применимого на практике:  struct emp_data  { unsigned dental : l; unsigned college : l; unsigned health : 2;  char fname[20]; char lname[20]; char ssnumber[10];  }: Затем объявим массив структур; ,struct emp_data workers[lOO];  Чтобы заполнить первую структуру массива, можно записать нечто вроде этого: workers [ 0]. dental= l; " ЕЁуохКегвШ]. college = 0; ЁногКегв[0]. health= 2; Estrcpy (workers [ 0] . fname , "Mildred" ) ;  Е Ё ›. l  Текст программы станет понятнее, если воспользоваться символическими констан- Ётами YES и N0 вместо цифр 1 и 0 при работе с битовыми полями. В любом случае бито— Ёвые поля следует воспринимать как небольшие целые числа без знака с заданным коли— Ёчеством двоичных разрядов. Битовое поле длиной п бит может содержать числа из диа— ;дазона от О до 2"" включительно. Если попытаться поместить в такое поле число, не {входящее в допустимый диапазон, то сообщения об ошибке не будет, но результаты "окажутся непредсказуемыми.      " Рекомендуется He рекомендуется  рользуи Egypt» символиче их„ HT 33335 „Зотин твик & варвар №№   "‘3  . 1 I L ( . › ‘ „'‚і l ‘ "?ч „”_.3 и ‹ "ё ’ 5‘ " ` :03» ‚ск : ';?Ёіхён ‹?“ „< if {“;“/Ё, :. (‚( ТЖ”; , &&&, w ": "! ; " x , …“ к?“ { ‚.. 3‘33; ‹; Щ „‹   ..... .’.‘д ------    іРезюме  ‹ На этом занятии были рассмотрены некоторые темы, существенные для программирова— _ дня на С. Вы узнали, как распределять, перераспределять и освобождать память в ходе вы- іполнения программы с помощью разнообразных средств языка С. Мы обсудили приведение ;типов элементарных переменных и указателей. Когда o приведении типов забывают или при- меняют его неправильно, часто возникают ошибки, которые трудно найти и исправить, так что эта тема заслуживает самого тщательного изучения. Были изучены также способы мани— Ёпулирования блоками памяти с помощью функций memset(), memcpy( ) H memmove( ). Наконец, были рассмотрены способы обращения к отдельным битам данных, используемых в про— ‚‹граммах на С, и манипулирования ими. №.  r f,“  %день 20—й. Работа с памятью 529 
Вопросы и ответы  В чем преимущество динамического распределения памяти? Почему нельзя просто объявить переменные в достаточном объеме и количестве при написании исходного текста программы? При объявлении всех данных прямо в исходном тексте объем памяти, выделяемый про— грамме, оказывается фиксированным. Приходится знать заранее, сколько памяти понадобится программе, еще при ее написании. А вот динамическое распределение памяти дает возмож— ность управлять ее рабочим объемом прямо “на ходу”, подстраиваясь под меняющиеся усло— вия и данные, введенные пользователем. Программа может заказать ровно столько памяти, сколько ей необходимо, вплоть до всего имеющегося в системе свободного пространства.  Зачем нужно освобождать память? Когда вы только начинаете изучать программирование на С, вы пишете совсем небольшие программы. По мере роста вашей квалификации увеличивается и длина программ, и объем потребляемой ими памяти. Следует писать программы так, чтобы они использовали память с максимальной эффективностью. Закончив работу с блоком памяти, его следует освободить. Если программа выполняется в многозадачной среде, то неиспользуемая ею память может в любой момент понадобиться другой программе. Некоторые системы автоматически освобо- ждают память после завершения программы, но так поступают не все из них. Вот почему нужно всегда освобождать динамически распределенную память явным образом.  Что произойдет, если вторично поместить данные в строку символов, не вызвав предварительно функцию realloc( )? Вовсе не обязательно вызывать realloc( ), если в строке и без того достаточно места. Вы- зывайте эту функцию только тогда, когда длины строки недостаточно для помещения в нее но- вых данных. Помните, что компилятор С позволяет вам делать практически все, что уг0дно (даже то, чего не следовало бы делать вовсе!). Так что вы можете безболезненно записать по- верх одной строки другую, более длинную, если только ее длина не превосходит размера перво- начально выделенного блока памяти. Однако если новая строка длиннее этою блока, то можно затерегь какие-нибудь данные, находящиеся в памяти после блока. А эти данные могут оказать- ся как мапозначительными, так и жизненно важными. Поэтому, если новая информация больше по объему, чем выделенное пространство памяти, воспользуйтесь функцией realloc( ).  B чем состоят преимущества функций memset( ), memcpy( ), memmove( )? Почему бы не воспользоваться вместо них циклами и операторами присваивания для инициализации или копирования участков памяти? Bo многих случаях для инициализации памяти вполне допустимо использовать оператор присваивания в цикле. Часто такая инициализация оказывается единственно возможной— например, трудно представить себе другой способ заполнить массив типа float числами 1.23. В других же ситуациях участок памяти может и не ассоциироваться с массивом или списком. Тогда не остается другого выбора, кроме как воспользоваться функциями mem. . . ( ). Бывает и так, что с задачей можно справиться с помощью цикла и присваивания, но приме- нение функций шеш. . . ( ) оказывается проще и быстрее.  Для каких целей применяются операции поразрядного сдвига и побитовые логиче— ские операции? Наиболее частое применение зти операции находят при прямом доступе к аппаратным ре- сурсам компьютера. Подобные задачи нередко включают генерирование и анализ различных битовых шаблонов и масок. Но эта тема ВЫХОДИТ за пределы нашей книги. Даже если вам ни- когда не придется напрямую работать с аппаратными устройствами компьютера, операции  530 Неделя 3. Основные вопросы 
дпоразрядного сдвига иногда могут пригодиться для умножения и деления целых чисел на {степени двойки.  1 Действительно ли битовые поля струк‘тур могут дать большой выигрыш в ресурсах, ;Ь'кономичности программы и T. п.. " M Это действительно так. Представим себе пример, аналогичный приведенному ранее в ходе на- шею занятия. Пусть файл базы данных содержит результаты опроса большого количества людей. дей просили дать ответы “да’ ’(TRUE) или “нет ”(FALSE) на ряд вопросов. Если задать по 100 во- Ёпросов десяти тысячам людей и каждый ответ поместить в переменную типа char в виде Т или F,  %;;отребуется 10 000х100 = 1 000 000 байт (каждый символ занимает один байт). Если же ор- ЁЁганизовать битовые поля и выделить по одному биту на каждый ответ, будет достаточно é}! 000 000 бит, а не байт. В одном байте 8 бит, так что это составит 130 000 байт (поскольку Ё100 ответов помещаются в 13 байт). А этот объем существенно меньше, чем миллион байт.   ;Контрольные вопросы  Ёі. В чем разница между функциями распределения памяти ша11ос( ) и са11ос( )?  Wyn-m-  3:2. Какова самая распространенная причина для применения операции приведения типа к чи- словым переменным?  3. Какой тип данных будут иметь результаты следующих операций? Предположим, что с — переменная типа char, i — типа int, а 1 —— типа long.  a)( с + і + 1 ) б)( і + 32 ) в)( с + 'A' ) r)( і + 32.0 ) д)( 100 + 1.0 ) ~ 4. Что такое динамическое распределение памяти? 5. B чем разница между функциями шешсру() и memmove( )?  6. Пусть в вашей программе используется структура, в которой один из элементов —— номер дня недели в виде числа от 1 до 7. Как объявить этот элемент структуры наиболее эко- номно с точки зрения затрат памяти?  7. В каком наименьшем объеме памяти можно поместить текущую дату? Имеется в виду день, месяц и год, причем год отсчитывается от 1900.  8. Чему равен результат выражения 10010010 << 4? 9. Чему равен результат выражения 10010010 » 4? 10. Опишите разницу между результатами этих двух выражений:  ‹ 01010101 ^ 11111111 ) ‹ “01010101 )  ‘День 20-й. Работа с памятью 531 
Упражнения  1.  Напишите оператор с вызовом функции ma110c() для выделения памяти под 1000 чисел Tunalong.  Напишите оператор с вызовом функции са110с() для выделения памяти псд 1000 чисел Tunalong.  Предположим, объявлен следующий массив: float data[1000];  Продемонстрируйте два способа инициализации всех элементов этого массива числами 0, В одном случае воспользуйтесь циклом и присваиванием, а во втором — функцией memset().  Поиск ошибок. Есть ли ошибки в следующем фрагменте коца? void func()  { int numberl = 100, number2 = 3; float answer; answer = numberl /number2; printf ("%d/%d =%1f", numberl, number2, answer)  }  Поиск ошибок. Найдите ошибки в следующем фрагменте кода. void *p; p = (float*) malloc(sizeof(float)); *p = 1. 23;  Поиск ошибок. Допустимо ли следующее объявление структуры? struct quiz_answers  { char student_name[15]; unsigned answer1 : unsigned answer2 unsigned answer3 unsigned answer4 unsigned answerS  `.  . `.  "  .. .. .. .. .... H H H H `  "  }  Следующие упражнения приводятся без ответов к ним.  Напишите программу, использующую все побитовые операции. Программа должна при- менять битовую операцию к числу, а затем применять ее еще раз к результату. При этом следует наблюдать за значениями данных на экране, чтобы быть в курсе происходящего.  Напишите программу, выводящую на экран двоичное представление числа. Например, ес- ли пользователь введет 3, на экране должно появиться число 00000011. (Подсказка: вам обязательно понадобятся побитовые операции.)  532 Неделя 3. Основные вопросы 
  1 Итак, начинается последнее, 21-е, занятие по программированию на языке С. К этому }…менту вы овладели почти всеми ключевыми средствами языка. На этом занятии будут Ёссмотрены некоторые дополнительные возможности С для создания гибких и мощных ограммных проектов.  I Разработка программ, состоящих из нескольких модулей исходного кода I Использование препроцессора С  I  › i f  (“,4-  Ё I Использование аргументов командной строки  ё'ірограммьь состоящие из нескольких іиодулей исходного кода  До сих пор все ваши учебные программы на языке С состояли из одного файла исход— ного кода (не считая, конечно, стандартных заголовочных файлов). Для небольшой про— граммы одного файла чаще всего бывает достаточно. Но при желании текст программы можно разделить на два и более файлов. Такая практика называется многомодульным про— гршшированием. Зачем это делается? Рассмотрим этот вопрос более подробно в следую— щих разделах.  преимущества МНОГОМОДУЛЬНОГО программирования  В основном многомодульное программирование связано с подразделением программ на функции, т.е. с техникой структурного программирования. По мере накопления опыта в про— граммировании вы будете разрабатывать функции все более широкого назначения, которые можно использовать не только в той программе, для которой они создавались, но и в ряде 
других программ. Например, можно написать достаточно общий набор функций для форма- тированного вывода данных на экран. Поместив их в один отдельный файл, эти функции за— тем можно включить в другие программы, которым также требуются средства вывода на эк— ран. При разработке программ, состоящих из нескольких файлов исходного кода, каждый та— кой файл называют модулем.  Техника мноюмоцульного программирования  В программе на языке С может быть только одна функция main( ). Модуль, содержащий функцию main( ), называется `главным модулем, а остальные— вторичными или подчинен— ными модулями. Как правило, с каждым вторичным модулем ассоциируется отдельный заго— ловочный файл (об этом еще будет говориться позже). Рассмотрим несколько простых при— меров, иллюстрирующих принципы многомодульного программирования. В листингах 21.1, 21.2 и 21.3 приведены соответственно главный модуль, вторичный модуль и заголовочный файл программы, которая запрашивает у пользователя число и выводит на экран его квадрат.  Листинг 21 .1 . list2101.c — главный модуль   1: /* Ввод числа и вывод его квадрата. */ 2: 3: #include <stdio.h> 4: #include "calc.h" 5: 6: int main( void ) 7: { 8: int x; 9: 10  printf("Enter an integer value: "); 11: scanf("%d", &x); 12: printf("\nThe square of %d is %ld.\n", x, sqr(x)); 13: return 0; 14: }   Листинг 21 .2. calc. c — вторичный модуль   /* Модуль с функциями для вычислений. */ #include "calc.h" long sqr(int х) {  return ((long)x * x);  mummpwmw  п..-о  Листинг 21 .3. calc.h — заголовочный файл для са1с . с   1: /* calc.h: заголовочный файл для са1с.с. */ 2: 3: long sqr(int x); 4: 5:  /* конец calc.h */   534 Неделя 3. Основные вопросы 
"'РВЗЦЛЬШНШ Enter an integer value: 100  The square of 100 is 10000.  Рассмотрим содержимое всех трех файлов кода более подробно. Заголовочный файл calc.h содержит прототип функции sqr( ), определенной в файле са1с.с. В каждом модуле, использующем функцию sqr( ), должен быть известен прототип этой функции. Для этого к модулю необх0димо подключить заголовочный файл са1с .h. Вторичный модуль са1с.с содержит определение функции sqr( ). Для включения заголо- вочного файла calc.h используется директива #include. Заметьте, что имя заголовочного _файла заключено в кавычки, а не в угловые скобки. Причина этого будет рассмотрена позже. Главный модуль list2101 .с содержит функцию main( ). Этот модуль также включает за— головочный файл са1с. h. Как же скомпилировать и скомпоновать исполняемую программу после создания этих 'трех файлов исходного кода? Компилятор справится с этим сам. В командной строке необхо— димо ввести следующее:  xxx list2101.c calc.c  Здесь ххх— это команда запуска компилятора. Выполнение этой командной строки со- стоит из следующих этапов:  1. Компилируегся файл list2101.c, создается объектный файл list2101.cbj (или list2101.c B системах UNIX). Если в тексте встречаются синтаксические ошибки, выда- ются соответствующие сообщения.  2. Компилируется файл са1с.с, создается объектный файл calc.cbj (или са1с.о в системе UNIX). При необходимости снова выдаются сообщения о синтаксических ошибках.  3. Выполняется компоновка файлов list2101.c, calc.cbj, a также необходимых функций из стандартных библиотек,. и создаегся исполняемый файл list2101. с.   Ён; №№“? „‚_-‚инга ‹, , . сли испбЁтьзуется интегрированная среда разработки (ИСР) а не компилятор команд“;  got? стропило можно создать проект, автоматически объединяющий несколько файлов сходного кода ыодну программу, Например, в среде Оеч30++ (находящейся на прива“ IraeMoM компакт-диске,) можно предпринять следующие шаги для компиляции и кому еновки примера приведенного выше 1:3: 9* 35$… *”  ' ”ё; 3 ‚›‚Цк‘дёі „ , № 3.1%: „. ` %;??? 0“,: ч-‘ЁЧ'ЁАЁЁЁ  ;1. Запустите оболочку Dev-CH- ‚… * a. * :“ ’ 33>???" .: ‚3535$? Ё? геВыберите пунклменю File | New Project. Откроется диалоговое окно; показанное на ‚ ‹… .. puc..2 ‘21." ‚%… I „› . „д…„ „г., … к г ‘ . Выберите вариант C project и тип программы Console Application. Откроется диало- grower“ окно Nevaroject Name, показанное на рис. 21 ..2 ‚а,  4 Введите имя для проекта, например. List2101. BaM будет предложено сохранить файл проекта. &}Откроется ИСР с безымянным файлом исх0дного кода C, как показано на рис. 21 3. , Можете ввести исходный текст одного из мвдулей программы в этом файле или I}: › просто закрыть файл. Если вводится к0д‚ файл желательно сохранить. Затем соз- if»: дайте еще два файла исходного кода. для этого каждый раз выбирайте пункт меню , о??? Project | Add Source File, вводите код и сохраняйте файл. >  6. Если файлы исходного кода уже существуют, добавьте их в созданный проект. Для … …:з‘зтого используется пункт меню Project | Add to project. Выберите три файла исход— ^ .3 Horo кода Их окна будут открыты в ИСР  тем…-Ай мес:» “ммм—`....“— чп ><`: '…" ;. “>и-им ‚… _.  « ‚. ›ытмч'тжми ым,  \— ».)  nun."—   День 21-й. Совершенствование организации программ 535 
’ кг‘/ш “Va-:1:   ‚‹ °, ‚& 1- . … 9 { „5,3%… , ,9 {{ ‚ 9 .9 { . { 7 $3113.”. Nzé’f‘zf‘lfl 792Jt§1i1mtgn9 333$; {v.93}: {{“ ;ЭАЬ .H ‘ь ‚… =`{ ’91:: {{ { к} 9X91?" %ЁЁЁБЁ ЁЁЁЁЁ“ %„ $$?/%#; :{Ёішз MA: -{{{ . { . .. [9.650% A ' ' 3 ‘ю {„ :23 C омпйлирУите проект так же как ранее 160mm; [№№ mnejgwglfla .9 а 3 9 , ‚, , 9 Ы с:; ’… { o , _ 9 (. {_ {, £1.91 Д:,ж-Сзу …““; "9"}- E41 ‘ ”Horo Koaa‘hporpaMM\ ’ 3. * ›‹ {{”/{ж?}??? i: ‘1‘ ‚Ж; ” {Ёж 3E: ‚”?”,‘Ъіг‘іь'хм 137.? ;;.ейго; „ ' ^. <1 ’ 3:3 ?, , № … . ’ и 3% "“ '3’; ЁЁТЕЁЪЁЁ „"*** дашь“; “ ’ {313237-19 “^ Ё '1  Более подробную информацию о командировании {Снг-Ь…можно Baum в справочном системе, етой ИСР;  ‚    5) ‹ „ ‚мым        s " …" `_ 9 ' &?!АЦ" .92. г cm}, %„» CHM/331)., ».» «Ни-;и,“ &; И $“) YB ; ‘2‘ " "№ Г‘ МЙЁМ№„5`, 3" …А №.:, ^“ Y „>„ .,: :’А'      Рис. 21.1. Диалоговое окно New Project в среде Рис. 21.2. Ввод имени для нового                   Dev-C++ проекта в диалоговом окне New РГО/ес! среды Dev-C++ films/19H --’1-Pro;ect I Flo е_а: загсе» View _в_?“ аще _0ptmr топь № "ЁЁ inf?“- wing“ "" ‹… , фмс-{"" fie @ №! ‚в: .9 9:1 №№ 2 ‚щ…—№№ __ gajaegyrvv @ імаъжіт°ч^у№`ьПЁТ№ { ‘9‘ п..". …% ь- 4... ь ». r, Н…, 13% {’\;*.‚{‚г {, WWW.“ , ;", { ‘8: “№1533; D мины „? {‹ №№ 9.92:9.“15W \ * %* №№:—'? ёк… 1med1 ,] {’ З 9-9.6 5.53- };m—uafifr ' " """ ““""—“"" . \ { 2° „34153'“! машин: uac. опц- mrqvm .9321}? ЁЁ „'.; ;;? return .». , ) Х _ 919;; 39'3” 23:1,: №223" ‘ 119;: деда, , , 3;;1. №5 и 31:43? ’“- 3319318933?“ 5W № ‚„А 3 { (:1 … ` v н{\` ‚1,7{1151}; {,} (А? I ‚"-{ .: "\f‘ifiii‘i 19v- „;;/{“{ "9 ”° 9.,gfivfi3”:‘f. if: 1923 fi>§Qfigfiv " ;… 13' :” 9-2999 **, 9‘ ‚&=&: и , ~33 „і, 93;;- x memamamlamml м1№ „№ %% ., -9 „ , ‚ “Н.В—Ц Ami”. {… 393433.: \' f) ( my" ('?Ш” ”% &&& 1:184)"; Ц.? „ 998:1;Wizfi3.JmfigggzwliwwggigfiéfikK1..1.f§:§§§@ ‘3‘? Йиёхі'щд'ё        Рис. 21.3. Проект, открытый в среде Dev-C++  Содержание модулей  Как видите, компилирование и компоновка многомодульной программы выполняются совсем несложно. Основной вопрос для программиста состоит в том, что именно поместить в каждый из модулей. В этом разделе даются некоторые общие рекомендации на этот счет. Вторичные модули должны ссдержать функции более—менее общего назначения, т.е. та— кие, которые можно использовать и в других программах. Обычная практика состоит в том, чтобы создавать по одному вторичному модулю для функций каждой разновидности. Напри- мер, функции ввода с клавиатуры можно поместить в файл keyboard.c, функции вывода на  536 Неделя 3. Основные вопросы 
экран — B файл screen.c и т.д. Чтобы скомпилировать и скомпоновать более двух модулей исходного кода, введите следующее в командной строке:  tcc mainmod.c screen.c keyboard.c  Главный модуль должен содержать, как минимум, функцию main( ), a также функции, специфичные для программы (т.е. такие, которые не имеют широкого применения и не будут переноситься в другие программы). Обычно каждому вторичному модулю соответствует один заголовочный файл. Имя заго- ловочного файла совпадает с именем вторичного файла кода, но имеет расширение .h. B за— (головочный файл следует включить следующую информацию:  , I прототипы функций, определенных в соответствующем модуле кода;  : I директивы #define для всех символических констант и макроопределений, исполь- , зуемых внутри модуля;  ;" I объявления структур и внешних переменных, используемых в модуле.  Поскольку такой заголовочный файл может включаться сразу в несколько файлов исход— ;ного кода, часто бывает необходимо избежать многократной компиляции отдельных его {‚фрагментов. Это делается с помощью директив препроцессора, предназначенных для услов- 5‘ ной компиляции (она будет рассмотрена позже на этом же занятии). ъ:  в ;Внешние переменные и многомодульное  {программирование   , Во многих случаях единственный способ обмена данными между главным и вторичным %модулями заключается в передаче аргументов в функции и возвращении ими значений. EB этом случае нет нужды предпринимать никаких дополнительных мер, чтобы обеспечить Ёвидимость данных. Но что если в программе используются внешние переменные, которые ёдолжны быть доступны в обоих модулях? ; Вспомните из материала занятия 12, что внешняя переменная —— это переменная, которая *объявлена вне каких бы то ни бьшо функций. Внешняя переменная видна (доступна) во всем Ё файле исходного кода, в котором она объявлена. Однако это еще не делает ее автоматически ' доступной и в других модулях исходного кода. Чтобы сделать ее доступной и там, необходи- мо объявить эту переменную в каждом файле с использованием ключевого слова extern.  ; Например, пусть внешняя переменная объявлена в главном модуле таким образом: float interest rate;  Ее можно сделать видимой во вторичном модуле, включив в его файл следующее объяв- ление (за пределами всех функций):  extern float interest_;ate;  Ключевое слово extern сообщает компилятору, что исходное объявление переменной interest_rate (которое фактически выделяет для нее память) находится где-то в другом месте, но сама переменная должна быть доступна в данном модуле. Все переменные, объявленные как extern, являются статическими и дос1упными из всех функций модуля. На рис. 21.4 продемон— стрировано использование конструкции extern B многомодульной программе.   ( IIIIIIII! Если воспользоваться декларацией extern и при этом не объявить пере- менную где—нибудь в другом модуле, возникнет ошибка компоновки.     День 21—й. Совершенствование организации программ 537 
На рис. 21.4 переменная x видима во всех трех модулях программы. Напротив, перемен- ная у видима только в главном модуле и вторичном модуле №1.   /* подчиненный mod1.c */ extern int x, у: funcl() {   /* главный модуль */2 int х, у; ; ) шаіп() Ь   {   /* подчиненный mod2.c */ extern int x, у; func4() {    )    Puc. 21.4. Обеспечение доступа к внешней пере— менной из других модулей с использованием клю— чевого слова extern  Работа C объектными файлами  Если какой-либо вторичный модуль уже написан и полностью отлажен, то нет необходи- мости всякий раз перекомпилировать его заново вместе с программой. Создав объектный файл из исходного текста модуля, можно просто скомпоновать его с любой программой, в которой используются функции из этого модуля. Как известно, при компиляции программы создается объектный файл с тем же именем, что и у соответствующего файла исходного кода С, и расширением .obj (или .о в системе UNIX). Предположим, вы написали модуль keyboard.c и компилируете его вместе с главным модулем database . с, используя следующую комаНду:  tcc database.c keyboard.c  После этой компиляции на диске появится файл keyboard.obj. Когда функции в модуле keyboard.c будут отлажены и заработают как следует, этот модуль больше не нужно будет перекомпилировать всякий раз, когда компилируется файл database.c и вся программа соби- рается в единый исполняемый файл. Вместо этого можно скомпоновать программу с объект— ным файлом keyboard. obj. BOT как это делается:  tcc database.c keyboard.obj  Компилятор скомпилирует модуль database.c и скомпонует получившийся объектный файл database.obj с файлом keyboard.obj для получения окончательного исполняемого файла database.exe. B результате экономится время, поскольку компилятору не нужно тра- тить его на перекомпиляцию кода функций в keyboard. с. Правда, если в этот файл будут вне- сены какие—либо изменения, его все-таки придется перекомпилировать. Кроме того, если из— менения вносятся B заголовочный файл, то следует перекомпилировать и все файлы исходно— го кода, к которым он подключается.  538 Неделя 3. Основные вопросы 
  Рекомендуется Не рекомендуется  “" Чё ‘“ W  его нааначения    Не mafia" cflxomnmnupflarbmecxonb- =  ”Mailers; кода;? ЁЪа-‚ё; №9 фЁЁП Wampum? дюда ВМётё если буцейлегкоиском—а ;  ? цитрата… ма 1; з ЁЁЬЁЁЁЁ1"_3`ЁЁЧЁ%ЁЪ № %%Ёёщ  фразу Ёнескопьких медупяхтвстречаетъ % Erica ¢yfixunag§fiamunflaxan функция мо- д ::ёкет бытьщтопько одна? (точнее говоря в ? прогрЁйме МОжет встречаться только одна функция ‹: любым… конкретным име- ‚днем; никакие имена функции не могут повторяться) … ‚д;… ‚ Неатерекомпилируите acg фаилы "0-: _? одного кода С при кахщоижойпоновке z Ён? ?фаммы“ Перекомпилируитё только { фаипьг которые подверглись измене-  WNW»,     ‚.`? ;”  F и используйте готовые объектные Ё ‘;остапьных модулей Это сэкойод "; =32" много времени„ „кадр„ f}; ‚ … Ё  №№ `?    ТИЛ ma I'IOCTpOeH ИЯ П роекта  іі Практически все компиляторы С имеют в своем составе утилиту построения проекта make utility), которая облегчает и ускоряет работу с многомодульным программным проек- ; ом. Эта утилита обычно называется nmake. exe. Она позволяет создать фаил связей проекта make file), который называется так потому, что определяет взаимосвязи между различными егавными частями многомодульной программы. Какие же взаимосвязи имеются в виду? Представьте себе проект, содержащий главный модуль program. c и вторичный модуль second. c. B нем имеется также два заголовочных файла—_ program. h и second. h. Модуль rogram. c подключает оба заголовочных файла, тогда как second.c —— только second.h. Из модуля program. c вызываются функции модуля second. c. Таким образом, модуль program.c связан с двумя заголовочными файлами. Если внести “изменения в любой из этих файлов, то придется перекомпилировать модуль program.c, чтобы ;учесть эти изменения. А вот модуль second.c связан с файлом second.h, но не с program.h. ;Если изменить файл program.h, то не придется перекомпилировать second.c —— можно про— то скомпоновать уже существующий объектный файл second.obj, созданный в ходе послед— ней компиляции second .c. Файл связей описывает отношения между файлами программного проекта наподобие тех, которые были только что рассмотрены. Всякий раз после внесения изменений в один или не— сколько файлов исходного кода файл связей проекта “запускается на выполнение” утилитой nmake. Эта утилита анализирует время и дату модификации исходного кода и объектных фай- Лов, а затем на основании существующих связей между модулями проекта указывает компи— Е‚шпору, какие модули следует перекомпилировать (только те, которые связаны с МОДифици- :рованными файлами). Ненужное компилирование не выполняется, и процесс разработки про— таммы становится максимально экономичным. В проектах, состоящих из одного-двух файлов исходного кода, создание файла связей проекта не дает особого выигрыша во времени. А вот в больших и разветвленных проектах ‚это неоценимое преимущество. Информацию o работе с утилитой nmake конкретного компи— лятора можно найти в его справочной системе.  №№ таща}. : -.---;  Ё  ыръ‘ь  ‚день 21 -й. Совершенствование организации программ 539 
Препроцессор С  Препроцессор входит в состав всех без исключения компиляторов С. При компиляции программы на языке С именно препроцессор первым включается в работу. В большинстве версий С препроцессор непосредственно включен в состав компилятора. При запуске про- граммы на компиляцию препроцессор стартует автоматически. Препроцессор модифицирует исходный текст программы на основании инструкций, или директив препроцессора, включенных в состав кода. На выходе препроцессора получается модифицированный файл исходного кода, который поступает на дальнейшую компиляцию. Обычно программист вообще не видит этого файла, поскольку компилятор удаляет его непо- средственно после перев0да в объектный код. Несколько позже вы узнаете, как всечтаки увидеть и прочитать текст этого промежуточного файла. Но сначала необходимо ознако- миться с директивами препроцессора, которые все начинаются с символа #.  Директива препроцессора #define  Директива препроцессора #define имеет два применения: создание символических кон- стант и функциональных макроопределений. ›  Макросы с простой подстановкой  Подставляемые макросы (т.е. такие, которые заменяются на другие литералы или выра-и жения простой подстановкой) рассматривались на занятии 3. Тогда они назывались символи- ческими константами. Подставляемый макрос создается директивой #define. Его суть со- стоит в простой замене Одного фрагмента текста другим. Например, чтобы заменить textl Ha text2, запишем следующее:  #define textl text2  Эта директива указывает препроцессору последовательно перебрать весь исх0дный код, заменяя все вхождения фрагмента textl Ha сет. Единственное исключение составляет литерал textl внутри двойных кавычек, т.е. когда он является частью строковой константы. В этом случае замена не произв0дится. Наиболее распространенное применение подставляемых макросов ——— это объявление сим- волических констант, как уже говорилось на занятии 3. Пусть, например, программа с0дер— жит следующие строки:  #define MAX 1000  х = у * MAX; 2 = MAX - 12; Препроцессор заменит этот фрагмент кода на следующий: х = у * 1000; z = 1000 - 12;  Эффект этой замены будет таким же, как если воспользоваться средствами поиска и заме— ны в текстовом редакторе для глобального изменения символической константы МАХ на число 1000. Конечно, файл исходного кода при этом не подвергается никаким изменениям —— они вносятся только во временный файл, который создается специально для этой цели. Заметьте, что возможности директивы #define не ограничиваются одним лишь созданием символиче—  540 Неделя 3. Основные вопросы 
ских числовых констант. Теоретически можно объявить и такой макрос (хотя трудно пред— ставить себе причину, по которой кому-нибудь понадобилось бы это сделать):  {define ZINGBOFFLE printf ZINGBOFFLE(“Hello, world.");  Символические константы также называют именованньиии константами (manifest constants). Помните, что многие авторы называют макросами символические константы, объ— явленные с помощью директив #define. Однако мы прибережем термин “макрос” только для функциональных макросов (макрофункций), о которых пойдет речь дальше.  Создание макрофункций с помощью директивы #define  Директива #сіеііпе применяется и для создания функциональных макросов, или макро- функций. Макрофункция — это нечто вроде “стенографического символа”, т.е. способ заме- тны сложных и длинных записей короткими и простыми. Такие макросы называются функ— циональными потому, что они мотут принимать аргументы, как это делают обычные функции LC. Одно из преимуществ макрофункций заключается в том, что они нечувствительны к типу {аргументов. Другими словами, если макрофункция принимает числовой аргумент, то он мо- жет иметь любой из стандартных числовых типов С. Рассмотрим пример. Следующая директива определяет функциональный макрос с именем ЁШЁОЕ‘, принимающий параметр value: gidefine HALFOF(value) ((value)/2) 1  Всякий раз, когда препроцессор встретит в исходном коде текст HALFOF(value), OH заме- ;нит этот текст на строку из макроопределения, подставив нужный аргумент. Пусть программа {содержит следующую строку: 'result = HALFOF(10); После обработки препроцессором она будет заменена такой строкой: _хезц1т = ((10)/2); " Вот чуть более сложный пример: Ёрхіпъі(*%і"‚ HALFOF(x[1] + у[2])); Препроцессор заменит этот код следующим: :printf("%f", ((Xlll + Yl2])/2)); Макрофункция может иметь и несколько параметров, причем каждый параметр может  встречаться в ее определении несколько раз. Например, следующая макрофункция вычисляет среднее арифметическое пяти чисел и соответственно имеет пять параметров:  {define AVGS(v, w, x, y, z) (((v)+(w)+(x)+(y)+(z))/5) Приведенная ниже макрофункция определяет большее из двух чисел с помощью операции  выбора по условию. В ней каждый параметр используется дважды. (Операция выбора по ус- ловию изучалась на занятии 4.)  {define LARGER(x, у) ((х) › (у) ? (х) = (у))  Макрос может принимать сколько угодно параметров. Единственное обязательное усло- вие состоит в том, чтобы все они использовались в макроопределении. Например, следующее определение недопустимо, поскольку параметр 2 не используется в строке подстановки:  gidefine ADD(x, у, 2) ((X)+(Y))  Шень 21—й. Совершенствование организации программ 541 
При вызове макрофункции необходимо, конечно же, передать в нее соответствующее ко- личество аргументов. В определении макрофункции открывающая круглая скобка должна стоять непосредст- венно после имени —— без пробела. Наличие открывающей скобки сообщает препроцессору, что перед ним определение функционального макроса, а не простой подставляемой символи- ческой константы. Рассмотрим следующее определение:  #define SUM (x, y. 2) ((X)+(y)+(2)) Из-за пробела между SUM и скобкой препроцессор воспримет эту строку как определение символической константы. Каждое вхождение литерала SUM B исходном коде будет заменено строкой (x, y, z) ( (x)+(y)+(z) ), т.е. совсем не тем, чем следовало бы. Заметьте, что в подставляемой строке каждый параметр заключен в скобки. Это необхо- димо для того, чтобы избежать нежелательных побочных эффектов, случающихся при пере-  даче в макрофункцию составных выражений. Рассмотрим макрос, в котором аргументы не заключены в скобки:  #define SQUARE(x) x*x Если вызвать этот макрос, передав в него простую переменную, то никаких проблем не возникнет. Но что если передать в него следующее выражение? result = SQUARE(x + y); B результате подстановки (раскрытия) макроса эта строка изменится на следующую—— очевидно, неправильную: result = x + y*x + y; Этих неприятностей можно избежать, используя скобки: #деііпе SQUARE(x) (x)*(x) Результатом раскрытия этого макроопределения будет следующая строка, на этот раз пра- вильная: result = (x + y)*(x + y); Дополнительную гибкость макроопределениям можно придать с помощью операции пре- образования в литерал #. Если в определении макроса его параметру предшествует знак #, то  при раскрытии макроса соответствующий аргумент преобразуется в текстовую строку (литерал), заключенную в двойные кавычки. Для примера определим такой макрос:  #define 0UT(x) printf(#x) Теперь выполним следующий вызов этого макроса: 0UT(Hello Mom);  Раскрытие макроса даст строку: printf("Hello Mom"); При преобразовании аргумента макроса в литерал принимаются во внимание специальные символы. Таким образом, если символ в аргументе должен изображаться в текстовых строках  специальной управляющей последовательностью, то перед ним автоматически вставляется обратная косая черта. Продолжим приведенный выше пример. Выполним вызов:  0UT("Hello Mom");  Раскрытие этого макроса даст следующее: printf("\"Hello Mom\"");  542 Неделя 3. Основные вопросы 
Применение операции # продемонстрировано в листинге 21.4. Но вначале рассмотрим еще одну операцию, используемую в макрофункциях, _— операцию конкатенации. Ее обозна— чение имеет вид ##. Операция соединяет (сцепляет) вместе две строки при раскрытии макро- са. Она не добавляет к строке двойные кавычки и не обрабатывает специальные символы. Основное применение этой операции —— автоматизированное создание строк кода на языке С. Например, определим следующий макрос, а затем вызовем его:  #define CHOP(x) func ##x salad = CHOP(3)(q, w); После подстановки функционального макроса во вторую строку получим следующее: salad = func3(q, w); Итак, с помощью операции ## можно до некоторой степени управлять вызовом функций.  Фактически, при этом исходный код программы модифицируется в ту или иную сторону. В листинге 21.4 демонстрируется одно из применений операции #.  Листинг 21 .4. preproc.c — использование операции #   /* демонстрация операции # при раскрытии макроса. */  #include <stdio.h>  int main( void )  2: 3: 4: . 5: #define OUT(x) printf(#x " is equal to %d.\n", x) 6: 7: 8: {  9 int value = 123; 10: 0UT(value); 11: return 0; 12: }   \  al e is e al t 123. Резцпьшат v ° q“ °  № Благодаря применению операции # в строке 5, при раскрытии макроса с аргу- ментом value этот идентификатор заключается в кавычки. Это необходимо для его передачи в функцию printf( ). После раскрытия макроса OUT B строке 10 он выглядит следующим образом:  printf("value" " is equal to %d.", value );  Макросы и функции: что выбрать  Как следует из предыдущего раздела, макросы можно использовать вместо “настоящих" функций. По крайней мере, это можно делать, если код макроса не слишком длинный и его под— сгановка не удлинит программу во мною раз. Функциональные макросы могут занимать не только одну, но и несколько строк, хотя многострочные макросы, как правило, нерациональны в применении. Итак, что же выбрать в каждом конкретном случае: функцию или макрос? Здесь нужно принять во внимание соображения быстродействия и объема кода программы. Всякий раз, когда в тексте программы встречается вызов функционального макроса, его определение вставляется непосредственно в код. Если макрос вызывается в программе 100 раз, в текст будет вставлено 100 его копий. А вот функция существует в единственном экзем-  День 21-й. Совершенствование организации программ 543 
пляре. Другими словами, если необходимо максимально сократить код программы, то луч- шим выбором будет функция. При вызове функции операционная система должна выполнить некоторые дополнитель- ные операции по передаче аргументов в код функции и возвращению значения в вызываю- щую программу. В этих операциях нет нужды при вызове макроса, поскольку его определе- ние находится прямо на месте, в теле вызывающей программы. Таким образом, с точки зре- ния быстродействия следует предпочесть макрос. Для начинающего программиста соображения выбора между размером программы и ее быстродействием практически не играют роли. Только при написании огромных, многофунк- циональных программ, выполняющих большие объемы вычислений, эти вопросы встают со всей серьезностью.  Просмотр макросов после подстановки  Иногда возникает необходимость просмотреть, как будет выглядеть код после подстанов- ки в него всех макросов. Это особенно важно в процессе отладки, если макросы работают с ошибками. Чтобы увидеть результат подстановки всех макросов, следует приказать компиля- тору создать файл, содержащий исходный код после его обработки препроцессором. Это мо- жет оказаться невозможным в интегрированной среде программирования. Тогда придется ра- ботать с компилятором командной строки. Многие компиляторы имеют флажок (сигнальный аргумент командной строки), который может передаваться в них при запуске и указывать тем самым, что компилятор должен создать файл с результатом препроцессорной обработки. Например, чтобы выполнить препроцессорную обработку программы program.c компи- лятором Microsoft, введите следующее:  cl /E program.c Чтобы запустить компилятор UNIX B режиме предварительной компиляции, введите сс —Е program.c  Первый прох0д по исходному коду выполняется препроцессором. При этом в текст включа- ются все заголовочные файлы, раскрываются (подставляются) все определенные директивами #define макросы, а также выполняются все остальные директивы препроцессора. В зависимо- сти от компилятора полученный текст выводится либо в поток stdout (т.е. на экран), либо в файл с тем же именем, что и у программы, и специальным расширением. Компилятор Microsofi выводит предварительно обработанный текст программы в поток stdout. К сожалению, видеть, как текст программы стремительно пролетает по экрану, не очень-то удобно. Поэтому лучше перенаправить вывод в файл на диске, как это делается в следующем примере:  cl /E program.c > program.pre  Затем файл program.pre можно загрузить в текстовый редактор для просмотра или распечатки.   Рекомендуется Не рекомендуется  Используйте директивы #define, осо—, ”He?" aqoynomefinnme Макрофункциями. бенно mm определения символических Используите их по мере необходимости констант Это позволяет ynyHumTS удо- ёно‘ сначала убедитесь, что это даст вам бочитаемость кода; Типичные данные;, определенные преимуЩеспза перед обыч-        AIM которых} рекомеНДУется объявлятіз , «ными функциями _ ,} 2%? „дд; символические константы -—‹эт_о коды % ЁЁ, (jig; ,… ‘;“ " „, Np}; ;цветов экраЁа или клавиш значения ти—_ * 3;: ‚,;- „ ‚, :3 fl” “Hf :, \  „”`…7 . ‚и… <- MW v _ „ (ч ’ ‹ ., "1" " . 4 . ‚. `3.‘ \ \и \. ’z: Ъ ‚, » мыми-ы  '5 “ д“ ( ,с ”‘   Мпа да-нет' правда-ложь максимшть “  544 Неделя 3. Основные вопросы 
 Ы,“ и › \ ‘ ›— ‹ .. ‹ ‹ ’ », A 53.. bxflmzww‘humm     Директива #include  Использование директивы препроцессора #include для п0дключения заголовочных фай- лов уже рассматривалось ранее. Когда препроцессор встречает B тексте программы директиву #іпс1ис1е, он считывает текст указанного заголовочного файла и вставляет его в то место, где находится директива. Одной директивой #include нельзя считать сразу целую группу файлов с помощью символов подстановки * и ?. Правда, допускается использование вложенных ди- ректив #include. Другими словами, включаемый файл может сам содержать директивы `#іпс1и‹іе, а включаемые B Hero файлы —— свои директивы и т.д. Как правило, компиляторы ограничивают глубину вложенности этих директив. Если компилятор поддерживает стандарт ANSI, TO допустимое количество уровней вложенности составляет 15. Имя включаемого файла B директиве #include можно записать двумя способами. Если за- ключить имя файла B угловые скобки, например, #include <stdio.h> (эта конструкция мно- жество раз встречается B книге), то препроцессор будет искать файл B стандартном каталоге. Если файл не найден или не указан стандартный каталог, то препроцессор будет искать файл в текущем каталоге. Что такое стандартный каталог? В системе DOS 3T0 каталог, задаваемый системной пере- менной INCLUDE. Таких каталогов может быть несколько. Подробную информацию об этом можно найти B документации DOS. Вкратце говоря, значение системной переменной уста- навливается командой SET (обычно, хотя и не обязательно, B файле autoexec.bat). Большин- ство компиляторов устанавливают требуемое значение переменной INCLUDE B файле autoexec .bat автоматически B процессе их инсталляции. Второй способ заключается B том, чтобы записать имя файла B двойных кавычках: #include "myfile.h". B этом случае препроцессор ищет файл не B стандартных каталогах, а в текущем каталоге, B котором выполняется компиляция. Нестандартные заголовочные фай- лы для программы желательно хранить B том же каталоге, что и файлы ее исходного кода, и заключать их имена B двойные кавычки B директиве #include. B стандартном каталоге следу— ет хранить только заголовочные файлы, входящие B стандартную библиотеку компилятора.  Директивы #if, #elif, #else и #endif  Эти четыре директивы препроцессора управляют условной компиляцией. Термин услов- ная компиляция означает, что блоки исходного к0да С компилируются только B том случае, когда выполняются определенные условия. Во многих отношениях семейство директив #if работает точно так же, как оператор if языка С. Разница состоит B том, что оператор if оп- ределяет, должна ли группа операторов выполняться или нет B зависимости от условия, а ди- ректива #if определяет, должна ли она компилироваться. Блок #if имеет следующую структуру: #if условие_1 блок_опера торов__1 #elif условие_2 блок_ опера торов_ 1  День 21й .Совершенствование организации программ 545 
#elif условие_л блок;операторов_л #else альтернативный_блок_операторов #endif  Выражение, которое проверяется в директиве #if, может быть любым выражением, равным числовой константе. В этом выражении нельзя использовать оператор sizeof( ), преобразования типов и вещественные числа типа float или double. Чаще всего директива #if используется для анализа значений символических констант, определенных директи- вами #define. Каждый блок_операторов может состоять из одного или нескольких операторов С произ- вольного типа, в том числе и директив препроцессора. Их не обязательно, хотя и можно, за- ключать в фигурные операторные скобки. Наличие директив #if и #endif обязательно для организации условной компиляции, а вот директивы #elif и #else необязательны. Количество директив #elif B одном блоке не огра- ничено, тогда как #else должна быть только одна. Когда компилятор встречает в тексте про- граммы директиву #if, OH проверяет указанное в ней условие-выражение. Если оно равно TRUE (не нулю), то операторы, стоящие после #if, компилируются. Если же выражение равно FALSE (нулю), компилятор проверяет условия в директивах #elif no порядку их следования. Компилируются операторы, ассоциированные с первой директивой #elif с ненулевым усло- вием. Если ни одно из условий не выполняется (т.е. все выражения равны нулю), компилиру- ется блок операторов после #else. Заметьте, что всегда компилируется не более чем один блок из конструкции #if . . . #endif. Если в этой конструкции нет директивы #else, то вполне возможно, что ком- пилятор не скомпилирует ни одного блока операторов. Возможности условной компиляции, реализуемые с помощью рассмотренных выше ди- ректив, практически безграничны. Вот один пример. Предположим, разрабатывается про- грамма, которая использует большое количество данных, локализованных по отношению к стране пользователя. Эта информация содержится в заголовочных файлах— по одному на каждую страну. Чтобы скомпилировать программу для ее использования в разных странах, можно составить следующую конструкцию #if . . . #endif:  #if ENGLAND == #include "england.h" #elif FRANCE == #include "france.h" #elif ITALY == #include "italy.h" #else #include "usa.h" #endif  Затем с помощью директивы #define определяется символическая константа, обозначающая страну, и с ее помощью выбирается заголовочный файл, подключаемый при компиляции.  Отладка с помощью конструкции #if...#endif  Конструкция #if . . . #endif имеет еще одно распространенное применение — включение в программу отладочного кода. Для начала следует определить символическую константу J  546 Неделя 3. Основные вопросы 
DEBUG, равную 1 или 0. Далее по всей программе нужно расставить отладочный код сле— дующим образом: #if DEBUG == отладо чный код #endif  Если в процессе разработки программы определить DEBUG как 1, то в программу будет включен отладочный код для отслеживания ошибок. Как только программа заработает правильно, сделайте константу DEBUG равной 0 и перекомпилируйте программу без отла— дочного кода. Оператор defined() бывает полезен при написании директив для условной компиля- ции. Этот оператор позволяет проверить, определен ли тот или иной идентификатор. Сле- дующее выражение может быть равным TRUE или FALSE B зависимости от того, определено или нет имя NAME:  defined( NAME )  C помощью оператора def ined( ) можно управлять компиляцией на основании созданных ранее в тексте программы макроопределений, не занимаясь при этом проверкой конкретных значений констант. Возвращаясь к предыдущему примеру с отладочным кодом, можно пере— писать блок #if . . . #endif следующим образом:  #if defined( DEBUG ) отладочный код #endif  Оператор def ined() используется также для того, чтобы дать определение имени в том случае, если раньше оно определено не было. Для этого используется оператор NOT (!):  #if ldefined( TRUE ) /* Если константа TRUE не определена. */ #define TRUE 1 #endif  Обратите внимание, что оператор defined( ) He требует от идентификатора, чтобы он был равен какому—то конкретному значению. Например, после выполнения следующей директивы имя RED становится определенным, но не равным никакому значению:  #define RED  Несмотря на отсутствие значения у RED, выражение defined( RED ), тем не менее, равно TRUE. Конечно, все вхождения символической константы RED будут просто удалены из текста программы без замены на что-либо, поэтому следует соблюдать осторожность с подобными определениями.  Как избежать многократного включения заголовочных Файлов  По мере разрастания программы и увеличения количества заголовочных файлов, подклю- чаемых к ней, возрастает и риск включить какой-либо из этих файлов в программу несколько раз. Из-за этого возникают неприятные ошибки компиляции или компоновки. Но этой про- блемы можно легко избежать, применяя изученные директивы препроцессора. Рассмотрим пример, приведенный в листинге 21.5.  День 21—й. Совершенствование организации программ 547 
Листинг 21 .5. prog. h — использование директив препроцессора при подключении заголовочных файлов   |—' п  /* prog.h - файл с предотвращением многократного включения! */  2: 3: #if defined( prog_h ) 4: /* случай, когда файл уже включен */ 5: #else 6: #define prog_h 7: 8: /* далее идут основные данные заголовочного файла... */ 9: 10: 11: #endif   Manna Давайте попробно проанализируем, что же делается в этом заголовочном файле. В строкеЗ проверяется, определена ли константа prog_h. Обратите внимание,  что константа носит имя prog_h, похожее на имя самого заголовочного файла. Если констан- та определена, то выполняется блок, с0держащий только комментарий в строке 4, а затем компилятор перех0дит к директиве #endif и заканчивает компиляцию файла. Больше ника- ких операций не выполняется. ‚ Как же константа prog_h может стать определенной? Это делается в строке 6. Когда за— головочный файл включается впервые, и препроцессор обнаруживает, что константа prog_h He определена, он перех0дит к блоку #else. Первое, что выполняется в этом бло- ке, ——— это определение константы prog_h, чтобы все остальные попытки включения файла заканчивались пропуском основного его тела. В строках 7—11 можно поместить любые операторы или объявления.  l2 Включайте проверки подобные той которая выполняется в листинге 21 5 IIBEIII BO все ваши заголовочные файлы Это позволит избежать опасности иХЁ g£4”MHOI'OKpaTHOI'O включения. . v „, ,‘ЁЁЁЁ W ”13% %}; ГБ;: ‘ ТЭЁЁ  ,. № ми.—- .А…— ..“,-а… ...… еще Ч-ц-Ь—‘ьц‘ичткч-ч-ч инди… …… J    Директива #undef  Директива #undef противоположна по своему назначению директиве #define —— она де- лает идентификатор неопределенным. Вот пример: #беііпе DEBUG 1 /* В этой части программы константа DEBUG заменяется */ /* единицей, а выражение defined(DEBUG ) равно TRUE. */ #undef DEBUG /* B этой части программы константа DEBUG не заменяется */ /* ничем, и выражение defined(DEBUG ) равняется FALSE.*/  C помощью директив #undef и #define можно создавать макроопределения, действитель- ные только в отдельных частях исх0дного к0да. Привлекая еще и директиву #if, рассмотрен- ную ранее, можно добиться большой гибкости в управлении условной компиляцией.  548 Неделя 3. Основные вопросы 
Стандартные макросы  Большинство компиляторов определяют ряд стаНДартных макросов. Наиболее полезными из них можно считать _DATE_, ___ТШЕ_, _LINE_ и _FILE_. Обратите внимание, что до и после каждого из идентификаторов стоит двойной знак подчеркивания. Это делается для того, чтобы программист случайно не переопределил какой-нибудь из них —— авторы компи- ляторов надеются на то, что программистам вряд ли придет в голову идея начинать и закан- чивать свои собственные макроопределения двойными знаками подчеркивания. Указанные макросы подчиняются всем правилам, которые были рассмотрены ранее в этой главе. Когда препроцессор встречает один из этих макросов, он заменяет его соответствую- щим кодом. Макросы _ОАТЕ_ и _Т1МЕ_ заменяются текущими значениями даты и време- ни. Это дата и время предварительной компиляции файла исходного кода. Такая информация может оказаться полезной при работе с несколькими различными версиями программы. По- требовав от программы вывести дату и время ее компиляции, можно сразу определить, по- следняя ли это версия или более ранняя. Следующие два макроса представляют еще большую ценность. Макрос _LINE_ заменя- ется номером текущей строки исходного кода. Макрос _FILE_ заменяется именем файла исходного кода. Эти два макроса лучше всего использовать для поиска ошибок в ходе отлад- ки программы. Рассмотрим следующий оператор printf ( ): 31: 32: printf("Program %s: (%d) Error opening file" , _FILE_, _LINE_); 33:  Если бы эти строки входили в программу с именем myprog.c, то было бы выведено сле- дующее сообщение:  Program myprog.c: (32) Error opening file  Сейчас это может показаться вам незначительным преимуществом. Но поверьте —-— по ме- ре роста ваших программ и распределения их кода по множеству файлов поиск ошибок будет становиться все более затруднительным делом. Использование макросов __LINE_ и _FILE_ B значительной степени облегчает отладку программ.    Рекомендуется Не рекомендуется  Используйте макросы ”LINE M ___~FILE___ )не забывайте завершать блок … для того чтобы сделать сообщения об сити—б: 3 3 директивой #endif %    ках в программе более информативными ‚_ Заключайте параметры макрофункций в ; круглые скобки. Это поможет избежать мно- : гих ошибок. Например, первая строка из , следующих ниже безопасна, тогда как вто— , рая может привести к ошибке: Ё #define CUBE(x) (x )* (x )* (x ) \ #define CUBE(x) x*x*x   $ $ ›  „ь „` Mad,» 4  › %   Аргументы комаНДной строки  Программа на С может принимать аргументы, переданные в программу при ее запуске из командной сгроки. Имеются в виду любые данные, введенные в этой строке после имени самой  День 21-й. Совершенствование организации программ 549 
программы. Для запуска программы с именем progname, пользуясь командным приглашением С:>‚ можно ввести следующее:  C:\>progname smith jones  Два аргумента командной строки smith и jones загружаются B программу и обрабатыва- ются B ходе ее выполнения. Эти данные можно воспринимать как аргументы, передаваемые B функцию main( ). Вообще, аргументы командной строки позволяют передавать информацию B программу при ее запуске, а не B ходе работы‚ и это часто оказывается удобным. Количест- во передаваемых аргументов командной строки не ограничено. Заметьте, что такие аргумен— ты может принимать только функция main( ). Для этого она должна быть объявлена следую- щим образом:  main(int argc, char *argv[])  {  /* Операторы тела программы */  Первый параметр argc— 3T0 целое число, указывающее количество переданных аргу— ментов командной строки. Его значение всегда не меньше 1, поскольку имя самой программы считается первым аргументом. Параметр argv[] представляет собой массив указателей на строки. Индексы этого массива варьируются B диапазоне от 0 до argc-l. Указатель argv[O] указывает на строку с именем программы (включая полный путь к ней), argv[ 1*] —-— на первый аргумент после имени программы и т.д. Имена argc и argv[ ] не являются обязательными —— можно использовать вместо них любые идентификаторы, подчиняющиеся правилам синтак- сиса С. Однако эти два имени широко и традиционно используются для работы с аргумента- ми командной строки, поэтому их желательно придерживаться. Командная строка подразделяется на отдельные аргументы благодаря наличию свободно— го пространства любого вида. Если необходимо передать аргумент, содержащий пробелы и т.п., заключите весь аргумент B двойные кавычки. Вот пример:  C:>progname smith "and jones" Здесь smith —-— первый аргумент (помещаемый B argv[1]), а and jones —-— второй (ему со-  ответствует указатель argv[2]). Листинг 21.6 демонстрирует, как обращаться к аргументам командной строки внутри программы.  Листинг 21 .6. args . s — передача аргументов командной строки в функцию шаіп( )   1: /* Обращение к аргументам командной строки. */ 2: 3: #include <stdio.h> 4: 5: int main(int argc, char *argv[]) 6: { 7: int count; 8: 9: printf("Program name: %s\n“, argv[0]); 10: 11: if (argc > 1) 12: { 13: for (count = 1; count < argc; count++) 14: printf(“Argument %d: %s\n“, count, argv[count]);  550 Неделя 3. Основные вопросы 
15: }  16: else 17: puts("No command line arguments entered."); 18: return 0; 19: }   № hmfllfl list21_6 U" Program name: C:\LIST2106.EXE  No command line arguments entered.  list2106 first second "3 4" Program name: C:\LIST21_6.EXE Argument 1: first Argument 2: second Argument 3: 3 4  № Эта программа всего-навсего выводит на экран аргументы командной строки, введенные пользователем. Обратите внимание, что в строке 5 объявляются па— раметры argc и argv. B строке 9 на экран выв0дится тот аргумент командной строки, ко— торый присутствует всегда—— это имя программы. Ему соответствует указатель argv[0]. B строке 1 1 проверяется, содержит ли командная строка более одного аргумента. Почему бо— лее одного, а не более нуля? Повторим еще раз: потому что как минимум один аргумент—— имя самой программы -— в ней всегда есть. Если в строке имеются и другие аргументы, все они выводятся на экран в цикле for (строки 13 и 14). В противном случае выводится соответ— ствующее сообщение (строка 17). Аргументы командной строки можно подразделить на две рТТГ—ТТД— категории — обязательные аргументы, без которых она вооб- А ` :” ” ще не выполнит никаких полезных операций, и необязатель- №:“… ные, наподобие флагов выбора того или иного режима работы. ‹ *гЁФ№“$№“'№”°°° Для примера представим себе программу, которая выполняет * ‚Мы…, {___,—___…“ сортировку данных в файле. Если программа считывает имя файла данных из командной строки, то это имя как раз и будет обязательным аргументом. Если пользователь не введет его в Мёд Щ №№: _… :…“ командной строке, то программа вообще не сможет отсорти— _ ` ровать данные, и ей придется как-то урегулировать эту ситуа- @” Ewe“ I  цию. Программа может также принимать аргумент / r, указы- ЕЫ’шыюшдг ‘ ` _в  вающий, что данные следует сортировать в обратном порядке. „ Этот аргумент является необязательным, потому что при его наличии программа просто выполняет один вид операций сор-  тировки, а при его отсутствии —— другой.     ..   ___—___)мь-Ё——— ' : винампе» {307(2- „ › дрим  „_.... м..—&`» ` .;і „.: J       Puc. 21.5. Ввод аргументов командной строки в графи- ческой ИСР   IIIIIII'IIIIIII В интегрированных средах разработки с графическим интерфейсом, как ., правило, можно ввести аргументы команднои строки в диалоговом окне. Например, система Dev-C++, находящаяся на прилагаемом компакт— диске, позволяет ввести эти аргументы, щелкнув на кнопке Рагате1егз в диалоговом окне компиляции. На рис. 21.5 показано это диалоговое окно после активизации кнопки Parameters.     День 21—й. Совершенствование организации программ 551 
 Рекомендуется  ”Используйте имена argc и argv для па-г раметров main(), ассоциированных с ар- гументами командной строки Больший: ств9 программистов на языке С проту—^     Не рекомендуется  не полагаитесьйна то? ч`т9 попрзовательч …введет правильное количество аргуменг Ё   те проверку Если““ пользователь ввел  тов КОМЗНДНОИ строки. Всегда выполняи—Ё  3» “}\, „\ «цы-кии ‚«.—:.… :: ‚и… ‘“  что-то неправильно„ выведите на экран сообщение об ошибке на  , »›>›‘›_‘‚. `,“ ' ‚ №“ А !ідт’іё’ Ё   лапотименно так. а,    AAAA  Резюме  На этом занятии были изучены некоторые средства и методы С, позволяющие усовершен- ствовать организацию программ и повысить эффективность их разработки. Программа на С может состоять из нескольких файлов исходного кода, называемых также модулями. Прин- цип многомодульного программирования позволяет легко переносить из ОДНОЙ программы в другую более-менее универсальные функции с минимальной доработкой или вообще без та- ковой. Компиляцией программы можно управлять с помощью директив препроцессора для создания макрофункций, условного компилирования и многих других задач. Некоторые мак- рофункции являются стандартными и предоставляются компиляторами в готовом виде. Про- грамма также может принимать аргументы командной строки, являющиеся еще Одним удоб- ным способом передачи данных от пользователя в программу.  Вопросы и ответы  Откуда компилятор узнает, какое имя дать исполняемому файлу программы, если компилируется программа из нескольких файлов? На первый взгляд может показаться, что программа должна получить имя моцуля, содер- жащего функцию шаіп( ). Но на самом деле это не так. При компиляции из командной строки программа получает имя первого из перечисленных файлов исходного кода. Например, если запустить компиляцию следующей командой, то компилятор Borland Turbo C создаст испол- няемый файл FILE1.EXE:  tcc fi1e1.c main.c prog.c  Обязательно ли включаемые заголовочные файлы должны иметь расширение .h? Нет, не обязательно. Заголовочному файлу можно дать какое угодно имя и расширение. Но расширение .h общепринято на практике н является неписаным стандартом в программи- ровании.  Можно ли использовать полный путь к заголовочному файлу при его включении? Да, вполне. Это ваше право -—— включить в имя файла полный путь к нему. В этом случае имя файла вместе с путем следует заключить в двойные кавычки.  Все ли стандартные макросы и директивы препроцессора были рассмотрены на этом занятии? Нет, не все. Рассмотренные макросы и директивы — наиболее распространенные на прак- тике и общие для всех компиляторов. Но многие компиляторы предлагают и другие макросы, равно как и директивы.  552 Неделя 3. Основные вопросы 
Допустим ли следующий заголовок функции main() для работы с аргументами ко— мандной строки? main(int argc, char **argv) B приведенном заголовке используется указатель на символьный указатель вместо масси- ва символьных указателей. Поскольку переменная-массив по сути является указателем, это объявление в точности эквивалентно тому, которое рассматривалось ранее на занятии, т.е. char *argv[ ]. Такая форма заголовка правильна и широко распространена. (См. также мате- риал занятий 8 и 10.)  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления изученного материала, а также практические упражнения для развития навыков программирования.  Контрольные вопросы  1. Что означает термин многомодульное программирование? 2. Что такое главный модуль B терминах многомодульного программирования?  3. Зачем каждый параметр B определении функционального макроса заключается B круглые скобки?  4. Каковы аргументы за и против применения макрофункций B сравнении с обычными функциями?  5. Что делает оператор defined( )? 6. Какой элемент является обязательным B блоке директив #if?  7. Какое расширение имеют скомпилированные файлы кода С? (Предполагается, что файлы еще не прошли компоновку в исполняемую программу.)  8. Что делает директива #include?  9. B чем разница между этими двумя строками к0да‘?  #include <myfile.h> #include "myfile.h" 10. Для чего используется макрос __DATE__?  11. Ha какие данные указывает элемент массива argv[ 0 ]?  Упражнения  Ответы к этим упражнениям не приводятся ввиду их большого возможного разнообразия.  1. Скомпилируйте программу из нескольких модулей исхоцного кода в один исполняемый файл. (Для этой цели можно использовать листинги 21.1, 21.2, 21.3 или ваши собственные программы.)  2. Напишите функцию обработки ошибок, принимающую B качестве аргументов номер ошибки, номер строки и имя модуля, а также фрагмент программы с ее использованием. Функция должна выводить на экран хорошо оформленное сообщение об ошибке и завер-  День 21—й. Совершенствование организации программ 553 
шать выполнение программы. Для определения номера строки и имени модуля следует использовать стандартные макросы, а сами эти данные передавать в функцию из того места, где произошла ошибка. Вот пример оформления сообщения об ошибке:  module.c (Line ##): Error number ##  Измените результат упражнения 2 так, чтобы сообщение об ошибке стало более информа- тивным. Создайте текстовый файл, содержащий номера и описания ошибок. Назовите его ERRORS .TXT. OH может содержать, например, следующий текст:  1 Error number 1 2 Error number 2 90 Error opening file 100 Error reading file  Сделайте так, чтобы функция обработки ошибок искала в этом файле информацию об ошибке по ее номеру и выводила на экран соответствующее сообщение.  При разработке многомодульных программ некоторые заголовочные файлы могут вклю- чаться в модули по несколько раз. Используя директивы препроцессора, напишите костяк заголовочного файла, который компилируется только при его первом включении куда- либо.  Напишите программу, принимающую два имени файлов в качестве аргументов командной строки. Программа должна выполнять копирование первого файла во второй файл. (0 ра- боте с файлами см. материал занятия 16).  Это последнее упражнение в цикле занятий по С (если не считать дополнительной неде- ли), поэтому его содержание оставлено на ваше усмотрение. Выберите и решите задачу по вашему вкусу и интересам, которая бы в то же время соответствовала вашим практиче- ским потребностям. Например, можно написать программу для ведения каталога компакт— дисков, подсчета расходов и доходов, финансовых расчетов, связанных с приобретения- ми, ссудами и вложениями. Нет ничего лучше, чем сформулировать и решить реальную задачу, для того чтобы крепко усвоить все, что вы узнали из всего цикла занятий по языку С, и отточить свое мастерство программиста.  554 Неделя 3. Основные вопросы 
$АМ5 псвпіі гампсшпяшепьнп Неделя 3  Итоги  Вот и закончилась третья, последняя неделя занятий по языку С. (Но не забудьте — впе- реди вас ждет еще дополнительная неделя!). В начале недели были рассмотрены достаточно сложные и важные темы —— указатели и работа с дисковыми файлами. Далее произошло зна— комство с некоторыми из огромного количества функций, имеющихся в стандартной библио— теке С. В конце недели были изучены некоторые способы совершенствования структуры про— грамм, позволяющие достичь максимальной гибкости при их разработке и компиляции. Про— грамма, приведенная в этом разделе, сводит воедино все изученные средства и методы.   Ilplmalu: Если вам непонятен какой-либо из фрагментов программы, поищите объ— - яснения в материале соответствующего занятия.     Листинг ИЗ. 1 . week3 .с — итоговая программа третьей недели   1: /* Имя программы: week3.c */ 2: /* Программа хранит список имен и номеров телефонов. */ 3: 4: /* Информация записывается в файл на диске, имя */ 5: /* которого указывается в командной строке. */ 6: 7: #include <stdlib.h> 8: #include <stdio.h> 9: #include <time.h>  10: #include <string.h> 11: 12: /*** Определения символических констант ***/ 13: #define YES 1 14: #define N0 0 15: #define REC_LENGTH 54  16: 17: /*** Переменные ***/ 18: 19: struct record { 20: char fname[15+1]; /* имя + NULL */ 21: char lname[20+1]; /* фамилия + NULL */ 22: char mname[10+1]; /* отчество + NULL */ 23: char phone[9+1]; /* номер телефона + NULL */  24: } rec; 
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:  556  /*** Прототипы функций ***/  int main(int argc, char *argv[]); void display_usage(char *filename); int display_menu(void); void get_data(FILE *fp, char *progname, char *filename); void display_report(FILE *fp); int continue_function(void); int look_up( FILE *fp );  /* Начало программы */  int main(int argc, char *argv[])  {  FILE *fp; int cont = YES;  if( argc < 2 ) { display_usage("WEEK3”); exit(1); }  /* Открытие файла. */ if ((fp = fopen( argv[1], "a+")) == NULL)  { fprintf( stderr, ”%s(%d)--Error opening file %s", argv[0],__LINE__, argv[1]); exit(1); } while( con == YES ) { switch( display_menu() ) { case '1': get_data(fp, argv[0], argv[1]); /* Day 18*/ break; case '2': display_report(fp); break; case '3': look_up(fp); break; case '4': printf("\n\nThank you for using this program!\n"); cont = N0; break; default: printf("\n\nInvalid choice, Please select 1 to 4!"); break; } } fclose(fP); /* закрытие файла */  Неделя 3. Основные вопросы 
75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124:  return(0);  } /* Функция display_menu() */  int display_menu(void)  { char ch, buf[20]; printf( "\n"); printf( "\n MENU"); printf( "\n ========\n"); printf( "\nl. Enter names"); printf( "\n2. Print report"); printf( "\n3. Look up number"); printf( "\n4. Quit"); printf( "\n\nEnter Selection ==> "); gets(buf); ch = *buf; return(ch); }  /****************************************************  Функция: get_data()  *****************************************************/  void get_data(FILE *fp, char *progname, char *filename)  { int cont = YES;  while( cont == YES ) {  printf("\n\nPlease enter information: " );  printf("\n\nEnter first name: "); gets(rec.fname); printf("\nEnter middle name: "); gets(rec.mname); printf("\nEnter last name: "); gets(rec.lname); printf("\nEnter phone in 123—4567 format: "); gets(rec.phone);  if (fseek( fp, 0, SEEK_END ) == 0) if( fwrite(&rec, 1, sizeof(rec), fp) != sizeof(rec)) { fprintf( stderr, "%s(%d) Error writing to file %s", progname,__LINE__, filename); exit(2);  День 21-й. Итоги 55 
125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174:  558  cont = continue_function();  } }  /*****************************#************************** Функция: display_report() Назначение: форматированннй вывод имен и номеров  телефонов в файл. *********************************************************/  void display_report(FILE *fp)  { time_t rtime; int num_of_recs = 0; time(&rtime); fprintf(stdout, "\n\nRun Time: %s“, ctime( &rtime)); fprintf(stdout, "\nPhone number report\n"); if(fseek( fp, 0, SEEK_SET ) == 0) { fread(&rec, 1, sizeof(rec), fp); while(!feof(fp)) { fprintf(stdout,"\n\t%s, %s %c %s", rec.lname, rec.fname, rec.mname[0], rec.phone); num_of_recs++; fread(&rec, 1, sizeof(rec), fp); } fprintf(stdout, "\n\nTotal number of records: %d", num_of_recs); fprintf(stdout, "\n\n* * * End of Report * * *"); } else fprintf( stderr, "\n\n*** ERROR WITH REPORT ***\n"); }  /**************************************************  * Функция: continue_function() **************************************************/  int continue_function( void )  { char ch, buf[20];  do  printf("\n\nDo you wish to enter another? (Y)es/(N)o "); gets(buf);  Неделя 3. Основные вопросы 
175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224:  ch = *buf; } while( strchr( "NnYy", ch) == NULL );  if(ch‘== 'n' || ch == ’N') return(NO); else : return(YES);  }  /**********************************************************  * Функция: display_usage()  ***********************************************************/ void display_usage( char *filename )  printf("\n\nUSAGE: %s filename", filename ); printf("\n\n where filename is a file to store peop1e\'s names"); printf(”\n and phone numbers.\n\n");  }  /**********************************************  * Функция: look_up() * Возвращает: количество найденных имен *************************************************/  int look_up( FILE *fp ) { char tmp_1name[20+1]; int ctr = 0;  fprintf(stdout, "\n\nP1ease enter last name to be found: "); gets(tmp_1name);  if( str1en(tmp_1name) != 0 ) { if (fseek( fp, 0, SEEK_SET ) == 0) { fread(&rec, 1, sizeof(rec), fp); while( lfeof(fp))  { if( strcmp(rec.1name, tmp_1neme) == 0 ) /* if matched */ { fprintf(stdout, "\n%s %s %s - %s", rec.fname, rec.mname, rec.1name,  rec.phone); ctr++; }  fread(&rec, 1, sizeof(rec), fp);  День 21-й. Итоги 55! 
225: }  226: } 227: fprintf( stdout, "\n\n%d names matched.", ctr ); 228: } 229: else 230: { 231: fprintf( stdout, "\nNo name entered." ); 232: } 233: return(ctr); 234: }   МШ!— , Bo многих отношениях эта программа похожа на те, которые рассматривались в l ‘ ‹ U U U › итоговых разделах первои и второи недель занятии. Здесь обрабатывается мень-  ше полей данных, но зато в программе появились новые возможности. Используя программу week3 .с, пользователь может вести с`писок имен и номеров телефонов своих друзей, коллег и т.д. В том виде, в каком программа приведена здесь, она позволяет занести в базу данных только имя, фамилию, отчество или второе имя, а также номер телефона. Добавить к этим данным еще несколько полей ——— совсем несложная задача, и вы можете попытаться сделать это самостоятельно в качестве упражнения. Основное различие меЖду этой программой и предыдущими итоговыми программами заключается в том, что нет ограничений на количест— во вводимых и хранимых записей. Это достигается благодаря использованию файла на диске для хранения базы данных. При запуске программы необходимо ввести имя файла данных в командной строке. Функция main( ) начинается в строке 38, и в ее заголовке объявляются параметры для работы с командной строкой argc и argv. Работа с такими параметрами рассматривалась на заня- тии 21. В строке 43 проверяется значение argc с целью выяснить, сколько аргументов ввел пользователь в командной строке. Если argc меньше двух, то пользователь ввел только один параметр (имя самой программы), т.е. не указал имя файла данных. В этом случае программа вызывает функцию display_usage( ) с аргументом argv[0]. Этот аргумент представляет со— бой имя программы, которое пользователь ввел в командной строке. Функция display_usage( ) находится в строках 188—193. Подобную функцию полезно включать в любую программу, работающую с аргументами командной строки, поскольку она сообщает пользователю, как правильно запускать программу. Почему в функции использует— ся первый аргумент командной строки, а не просто имя программы (week3) B виде литерала‘? Ответ прост. При получении имени программы из командной строки нет нужды беспокоиться о том, что пользователь может переименовать программу. Даже если он это сделает, функция display_usage( ) автоматически подстроится п0д новое имя. Большинство новых средств программы, ранее не использовавшихся в итоговых разде- лах, было рассмотрено на занятии 16, посвященном работе с файлами. В строке 40 объяв- ляется файловый указатель fp, используемый далее для доступа к данным в файле. В стро- ке 50 предпринимается попытка открытия файла в режиме "а+". Вспомните, что argv[1] является вторым аргументом командной строки, то есть именем файла данных. Режим "a+" используется затем, чтобы иметь возможность как читать из файла существующие записи, так и добавлять в его конец новые данные. Если операция открытия файла терпит неудачу, в строках 52 и 53 выводится сообщение об ошибке, а в строке 54 выполнение программы завершается. Обратите внимание на информативность сообщения об ошибке. Заметьте также, что для указания номера строки, где произошла ошибка, используется макрос __LINE___, изученный на занятии 21.  560 Неделя 3. Основные вопросы 
Если файл открылся успешно, на экране появляется меню. Когда пользователь выбирает выход из программы, в строке 74 с помощью функции fclose( ) закрывается файл данньтх, а затем программа возвращает управление в операционную систему. Остальные пунктьт меню позволяют пользователю ввести новую запись, вывести все записи на экран или найти в базе данньтх конкретное имя. Функция get__data( ) подверглась нескольким значительным изменениям по сравнению с ее предыдущими версиями. Строка 101 содержит заголовок функции. Теперь эта функция принимает три указателя в качестве аргументов. Первый указатель —— самый важный. Он ис- пользуется для записи в файл данных. В строках 105—126 находится цикл while, B котором данньте запрашиваются у пользователя и считываются с клавиатуры, пока пользователь не решит закончить ввод. В строках 107—1 16 выполняется запрос на данньте в том самом форма- те, который уже использовался в итоговой программе 2-й недели. В строке 118 вызывается функция fseek( ), которая устанавливает указатель позиции на конец файла для добавления новых данных. Заметьте, что программа ничего не делает, если установка указателя не сраба- тывает. Расширенная версия программы обрабатывала бы эту ситуацию более корректно, но здесь соответствующий код опущен, чтобы не делать программу слишком длинной. В стро— ке 119 данньте записываются в файл путем вьтзова функции fwrite( ). Средства вьтвода отчета в этой программе также подверглись доработке. Одной из новых черт, отличающих “реальные” программы, является включение в отчет текущей даты и вре- мени. В строке 137 объявляется переменная rtime, которая передается в функцию time( ), a затем выводится с помощью функции ctime( ). Функции работьт со временем и датой изуча- лись на занятии 19. Прежде чем программа сможет вывести на экран записи из файла, она должна переустановить указатель позиции на начало файла. Это делается в строке 145 вьтзовом все той же функции f seek( ). После того, как указатель позиции переустановлен, программа получает возможность считывать записи одну за другой. В строке 147 выполняется первое считывание. Если оно прохо— дит успешно, программа начинает цикл while, продолжающийся до самою конца файла (т.е. до то- во момента, когда функция feof () возвратит ненулевое значение). Если конец файла еще не дос- тигнут, в строке 150 данньте выводятся на экран, в строке 153 счетчик записей получает прираще- ние, а в строке 154 делается попьтгка считывания следующей записи. Нужно отметить, что все эти функции используются здесь без проверки возвращаемых ими значений для того, чтобы программа не слишком разрослась в объеме. На практике все эти функции должны содержать достаточное ко- личество проверок, чтобы избежать ошибок в ходе выполнения программы. Одна из функций в этой программе —— совершенно новая и раньше не встречалась в дру- гих программах. Строки 200—234 содержат функцию look_up( ), которая ищет в файле все записи с заданной фамилией. В строках 205 и 206 программа запрашивает у пользователя фамилию, которую следует найти, и помещает ее в локальную переменную tmp_lname. Если введенная строка не пустая (что проверяется в строке 208), то указатель позиции устанавли- вается на начало файла. Затем считываются все записи по порядку. С помощью функции strcmp() B строке 215 фамилия из очередной прочитанной записи сравнивается со строкой tmp_lname. Если имена совпадают, то запись выводится на экран (строки 218—222). Так про- должается до тех пор, пока не будет достигнут конец файла. И снова отметим, что не все зна- чения, возвращаемые функциями, проверяются на правильность. В практически важных про— граммах такие проверки должны всегда выполняться во избежание сбоев и ошибок. Полученньтх вами знаний и навыков должно бьтть уже достаточно для того, чтобы само- стоятельно адаптировать программу к работе с файлами данньтх произвольного назначения. Используя функции, изученные на занятиях третьей недели, а также другие функции из биб- лиотеки С, можно создавать программы для обработки практически любьтх данньтх, встре- чающихся на практике.  День 21—й. Итоги 561 
Дополнительная неделя  Основные вопросы  Вот и закончился основной курс изучения С за 21 день. В течение этих занятий язык С от— крыл вам многие свои секреты и теперь уже не является тайной за семью печатями. Помните, что самый важный результат изучения С должен заключаться в том, чтобы освоиться с напи- санием собственных программ для решения практически важных задач.  Что Дальше  У многих начинающих программистов после знакомства с языком С появляется желание изучить и другие языки программирования. На занятиях дополнительной недели будут рас- смотрены еще три языка—— С++, Java и С#. Конечно, за неделю нельзя освоить их в совершенстве, но все же можно получить достаточно ясное представление об их синтаксисе, структуре программ и общих идеях. Кроме того, на дополнительных занятиях будет рассмотрено достаточно средств этих новых языков, чтобы можно было написать свою собственную —— пусть элементарную, но работающую —— программу на любом из них. Все, кто знает С, легко обнаружат при изучении С++, Java и С#, что эти языки имеют много общего с С. Однако есть и различия, о которых будет сказано далее. На занятии 22 рассматриваются общие понятия объектно—ориентированного программирования, а также ос- новные черты языков С++, Java и С#. Вы узнаете o ключевых методах и принципах, исполь- зуемых для разработки объектно-ориентированных программ. Занятия 23 и 24 посвящены изучению С++. Это самый распространенный из объектно- ориентированных языков, и он же имеет наибольшее сходство с С. Фактически, С++ является расширением С. Весьма вероятно, что тот компилятор, которым вы пользовались для изуче- ния языка С, может также компилировать и программы, написанные на С++. На занятиях 25—27 будет изучаться язык Java. Он также имеет много общего с языками С и С++. В последние годы Java приобрел большую популярность по причине его хорошей пе- реносимости между различными платформами и тесной интеграции со средствами работы в глобальной сети World Wide Web. Всего за пару занятий вы научитесь создавать простейшие аплеты и элементарные программы на языке Java. Его изучение начнется на занятии 25. Дополнительная неделя завершится занятием, посвященным обзору Одного из новейших языков объектно—ориентированного программирования — С#. 
      Языки объектно- ориентированного программирования  Основной курс программирования, занявший двадцать одно занятие, был посвящен языку С. Этот язык считается процедурно-ориентированным. Нынешнее занятие представ- ляет собой введение в объектно-ориентированное программирование (ООП) и языки, предназначенные для разработки объектно-ориентированных программ. Будут рассмотре- ны следующие темы.  I Различия между процедурными и объектно-ориентированными языками  I Наиболее распространенные объектно-ориентированные языки и основные принципы их построения  I Стратегические различия между С и такими языками, как С++, Java и С# (произносится “си—шарп”)  I Простейшая программа на языке Java  Процедурные и объектно-ориентированные языки  Язык С считается процедурным языком. Этот вопрос рассматривался еще на занятии 1, посвященном введению в язык. Процедурный язык отличается тем, что выполнение написан- ной на нем программы, грубо говоря, начинается с первой строки и заканчивается последней. Строки выполняются практически одна за другой. Хотя выполнение программы может раз- ветвляться по условию или перескакивать через фрагменты кода, но все же эти переходы и ветвления выполняются по явным командам, включенным в текст и выполняемым последова- тельно по мере их достижения. Программа, написанная на процедурном языке, состоит из со- вокупности функций или подпрограмм, и это обстоятельство диктует как последовательность ее разработки, так и структуру данных. 
В последние два десятилетия было создано несколько языков объектно-ориентированного программирования. Самые распространенные из них _— это С++ и Java. Как подразумевается в самом названии, объектно-ориентированные языки основаны на использовании объектов. На этом занятии об объектах будет говориться много и подробно. Вкратце говоря, объект — это независимый фрагмент кода, легко переносимый из одной программы в другую, который включает в себя как данные, так и функциональные средства для выполнения определенной узкоспециализированной задачи. В объектно-ориентированных языках также используются процедуры, но структуру программы определяют не они, а совокупность объектов и взаимо- отношения между ними. Зачем были созданы объектно-ориентированные языки? Основной причиной явилось воз- растание сложности и объема программ. Несмотря на всю мощь и гибкость процедурных языков типа С, они оказались не вполне подходящими для разработки и совершенствования крупномасштабных многофункциональных программных продуктов. Если написать большую и сложную программу типа текстового редактора или системы управления базами данных на процедурном языке, то ее будет сложно сопровождать, дорабатывать и отлаживать. Объект- но-ориентированные языки были созданы именно для решения этой проблемы. Хотя на языке С тоже можно писать объектно-ориентированные программы, все же сред- ства ООП не предусмотрены в явном виде в самом языке. Поэтому С довольно неудобно ис- пользовать в этом качестве. А вот языки С++ и Java специально созданы для того, чтобы об- легчить задачу разработки объектно-ориентированных программ. Прежде чем углубляться в подробности синтаксиса языков С++, Java и С#, необходимо понять основную идею-— что же делает язык объектно-ориентированным.   @:… Объектно-ориентированное программирование часто обозначают сокра- щением ООП. Это очень распространенная в настоящее время аббревиа-  тура.      ”mum С++ ЯВЛЯЭТСЯ объектно-ориентированным ЯЗЫКОМ, НО Ha нем МОЖНО ПИ- CaTb И ВПОЛНЭ ПРОЦЭДУРНЫЙ КОД — ХОТЯ ЭТО И не рекомендуется, ПОСКОЛЬКУ нерационально.     Принципы объектно-ориентированного программирования  Как уже говорилось, программы на объектно-ориентированных языках работают с объек- тами. Что же это за объекты? ООП основано на трех ключевых принципах. Именно реализа- ция этих принципов делает язык объектно-ориентированным. По традиции их обозначают следующими терминами. I Полиморфизм I Инкапсуляция I Наследование  Иногда в их число включают еще и переносимость кода объектов между программами без серьезной переработки. Но, фактически, при правильной реализации перечисленных выше принципов переносимость объектов достигается автоматически.  День 22-й. Языки обьектно-ориентированного программирования 565 
 ~ Именно переносимость кода объектов является той причиной, по которой llllllfl'llllll серьезные программисты предпочитают ООП процедурному подходу. Хотя ` функции и библиотеки С тоже неплохо переносятся из одной программы в другую, все же они не обеспечивают того удобства и гибкости. которые дости- гаются благодаря использованию классов и шаблонов. Классы будут рассмот- рены на занятии 24.     Первой основной чертой ООП является полиморфизм. Поли означает “много”, а морф —— “форма”. Программа, реализующая полиморфизм, может принимать разнообраз-  ные формы, т.е. автоматически адаптироваться к меняющимся обстоятельствам. Рассмот- рим для ясности пример. Если вас попросят начертить окружность, какие данные для этого нужно знать? Окружность можно начертить, например, зная положение центра и любой произвольной точки на окружности. Также достаточно знать три точки, лежащие на ок- ружности. Еще один вариант —— это знать центр и радиус. На рис. 22.1 показаны все пере- численные способы построения окружности. Если необходимо написать программу на С, которая бы выполняла задачу построения ок- ружности, в эту программу придется включить три различные функции для трех разных спо- собов построения, соответственно назвав их разными именами:  draw_circle_with_points(int x1, int yl, int x2, int y2, int x3, int y3); draw_circle_with_radius(int ctrx, int ctrY, long radius); draw_circle_with_center_and_point(int ctrX, int ctrY, int x1, int yl);  (x. v) (x! y)  (x. у)  Рис. 22.1. Построение окружности no различным ис— ходным данным  Имена этих трех функций могли бы быть и другими, например, draw_circle( ), draw_circle2( ) и draw_circle3( ). Это еще менее удобно и довольно непрактично. А теперь рассмотрим листинг 22.1. Эта программа рисует квадраты, состоящие из симво— лов, двумя способами. Квадраты выбраны потому, что их легче нарисовать, чем круги. Знато- ки С легко заметят в программе нечто странное —— в ней присутствуют две функции square().  Листинг 22.1 . square. cpp — программа с повторяющимися именами функций "   1: /* Программа на С с одной странностью - */ /* присутствуют две функции square() */  3: #include <stdio.h> 4: #include <stdlib.h> 5:  566 Дополнительная неделя. Основные вопросы 
Ф ..  /* функция square - первая! */ void square( int topleftX, int topleftY, long width )  `.! ..  8: { 9: іпі хсіх = О; 10: іпі усіх = О; 11: /* Предполагается, что нижние значения больше верхних */ 12: 13: for ( xctr = 0; xctr < width; xctr++) 14: { 15: printf("\n"); 16: 17: for ( yctr = О; усіх ‹ width; yctr++ ) 18: { 19: printf("*"); 20: } 21: } 22: } 23:  24: /* функция square - вторая! */ 25: void square( int topleftx, int topleftY, int bottomleftx, int bottomleftY)  26: { 27: int xctr = 0; 28: int yctr = О; 29: 30: // Предполагается, что нижние значения больше верхних 31: 32: for ( xctr = 0; xctr < bottomleftx - t0pleftx; xctr++) 33: { 34: printf("\n"); 35: 36: for ( усіх = О; усіх ‹ bottomleftY — t0pleftY; yctr++ ) 37: { 38: printf("*"); 39: } 40: } 41: } 42: 43: int main(int argc, char* argv[]) 44: { 45: int рі_х1 = О, рі_у1 = О; 46: іпі рі_х2 = 5, pt_y2 = 5; 47: іпі рі_х3 = О, рі_у3 = О: 48: long side = 4; 49: 50: // Вызов функций square двумя способами 51: square( рі_х1, рі_у1, рі_х2, рі_у2); 52: 53: printf("\n\n"): // пустые строки между квадратами 54: 55: square( pt_x3, рі_уЗ, side);  День 22-й. Языки обьектно-ориентированного программирования 567 
56: 57: return 0; 58: }  ***** РЕЗНЛЪШБШ ……  *****   ***** *****  **** **** **** ****  При попытке скомпилировать эту программу компилятором С будет выдано со— общение об ошибке. В то же время компиляция ее как программы на С++ прой—  дет благополучно. Как можно убедиться, программа включает две функции с одинаковыми именами (строки 7 и 25). В строках 51 и 55 функция square( ) вызывается двумя различными способами. Раньше уже говорилось, что в программе на С это недопустимо. А» вот в объект- но—ориентированной программе это вполне возможно. Это и есть полиморфизм в действии. При вызове функции square() программа сама определит, какую из двух функций с этим именем активизировать. В программе на С++ программисту не придется беспокоиться 0 вы- боре между функциями, коль скоро они обе отлажены и работают правильно — этот выбор будет сделан автоматически.   IIIIIIIII Листинг 22.1 представляет собой программу на языке С с использованием \ некоторых возможностей С++. При его трансляции компилятором С++ (например, Microsoft Visual C++ или Borland C++) программа благополучно пройдет компиляцию и запустится на выполнение. При использовании бо- лее старого компилятора С можно получить сообщение об ошибке, по- скольку переопределение (перегрузка) функций не пордерживается в "чистом" С. Кроме того, программа не пройдет компиляцию и в том случае, если установлен флаг ANSI С-совместимости, так как она фактически следует стандарту ANSI C++.     Полиморфизм —— явление намного более широкое, чем это видно из примера. То, что де- монстрирует приведенная выше программа, по сути является перегрузкой или переопределе— нием функции. Ключевая идея полиморфизма состоит в том, чтобы заставить функции пол- страиваться под типы данных, передаваемые в них. Полиморфизм делает программы и функ- ции более гибкими и удобными для переноса в другие проекты.   №№, Имеются и более характерные и сложные примеры полиморфизма, ко- торые, к сожалению, нет возможности здесь рассмотреть. В частности, объекты могут изменять свой тип при необходимости их использования в другом контексте. Можно, например, создать объекты типа ”человек" и типа ”клиент”, а затем воспользоваться принципом полиморфизма для того, чтобы работать с объектом ”клиент” как с объектом ”человек". Об- суждение всех возможностей полиморфизма далеко ВЫХОДИТ за преде- лы нашей книги.     568 Дополнительная неделя. Основные вопросы 
Инкапсуляция  Второй характерной чертой языка ООП является инкапсуляция. Это принцип, позволяю— щий создавать замкнутые, самоцостаточные объекты.   ”Ш“.“ В СОЧЭТЭНИИ С ПОПИМОРфИЗМОМ ИНКЭПСУЛЯЦИЯ ПОЗВОПЯЭТ nerKo переносить — СОЗДЭННЫЭ объекты В ДРУГИЭ программы, ПОПЬЗУЯСЬ ИХ ГОТОВЫМИ ВОЗМОЖ- НОСТЯМИ.     Инкапсуляция делает объект чем—то вроде “черного ящика”. Это означает, что про— граммисту, использующему его функциональные возможности, незачем знать, как именно он устроен внутри. Достаточно знать, как активизировать нужные функции и какой резуль— тат получится. Для примера снова рассмотрим задачу вычерчивания окружности. Чтобы получить окружность на экране, совсем не обязательно знать алгоритм ее вывода —— доста— точно знать, как пользоваться соответствующими средствами. Еще более характерный пример показан на рис. 22.2.  Area = Calculate_circle_area( center_x, center_y, radius);   Центр и радиус Площадь   Calculate_circle_area()  Puc. 22.2. Инкапсуляция как средство для создания “черного ящика ”  Как видно из рисунка, подпрограмму calculate_circle_area( ) можно воспринимать как “черный ящик”. Чтобы пользоваться им, совершенно не обязательно разбираться в его внут— реннем усгройстве. Все, что нужно знать, —— это какие параметры принимает процедура и ка— кое значение она возвращает. Внутри может находиться, например, следующий код:  PI = 3.14; area = PI * r * r;  Это, конечно, не весь текст процедуры. Обратите внимание, что значение PI здесь ус— танавливается равным 3.14. B следующей строке это значение используется для вычисле- ния пЛОЩади. Оно инкапсулировано внутри процедуры, поэтому его изменение не повлия— ет на код за ее пределами, в том числе на программы, из которых она будет вызываться. Например, можно изменить PI на 3.14159, и вычисление площади будет работать как и прежде, но с большей точностью. Внешние программы не будут затронуты изменениями в этом коде. Все это очень напоминает принципы, по которым устроены функции С, и воз— никает вопрос, можно ли реализовать инкапсуляцию средствами этого языка. Да, можно. Однако в объектно—ориентированных языках роль инкапсуляции намного важнее, а средст— ва для ее воплощения — значительно совершеннее.  День 22-й. Языки обьектно-ориентированного программирования 569 
Инкапсуляция данных  Кроме инкапсуляции функциональных возможностей, существует еще и инкапсуляция данных. Снова вернемся к процедуре вычерчивания окружности. Для хранения полной ин- формации об окружности достаточно знать только ее координаты центра и радиус. Как уже говорилось ранее, пользователь может потребовать, чтобы программа начертила окруж- ность по трем точкам на ней или же по центру и одной точке на границе, или еще каким- нибудь способом. А вот внутри нашего “черного ящика” достаточно хранить центр и ради- ус. Пользователю не обязательно знать, какими данными пользуется функция для построе- ния окружности. Независимо от того, какие данные предоставляет пользователь, функция может хранить наиболее удобную для себя информацию. Например, функция вычисления площади круга calculate_circle_area() может воспользоваться данными о центре и ра- диусе, не отчитываясь перед пользователем об источнике своих знаний. Итак, инкапсулировав и данные, и функциональные возможности, можно создать объект типа “черного ящика”. Этот объект не только хранит координаты центра и ради- ус окружности, но и знает, как начертить ее на экране. Преимущество такого объекта со- стоит в том, что им можно пользоваться или переносить его в другую программу, даже не зная, как он устроен и работает. Этот уровень разграничения кода и данных позволяет программисту совершенствовать функциональные возможности, быстродействие объек- та и т.п. внутри него самого, не перерабатывая внешние программы, которые обращают- ся к этому объекту.   |||Illllllalll£ Объекты и кпассы будут изучаться более` подробно на занятии 24. Клас- ~ сы —— это средство инкапсуляции данных и функциональных возможностей в языках ООП, т.е. С++, Java и С#.     Наследование  Третьим характерным свойством языков ООП является наследование. Это возможность создавать новые объекты на базе уже существующих, расширяя их возможности за счет но- вых свойств. Рассмотрим объект “квадрат” из примера программы, приведенного ранее. Этот объект может содержать следующую информацию.  I Координаты верхнего левого угла I Длину стороны I Символ, которым рисуется квадрат I Функцию, возвращающую площадь квадрата  Зная координаты левого верхнего угла и длину стороны, уже можно начертить квадрат. Функция для вычисления его площади также может на всякий случай инкапсулироваться в объекте. Используя принцип наследования, можно расширить объект “квадрат” до объекта “куб”. “Куб” будет наследовать “квадрат” в том смысле, что все характеристики квадрата войдут в число характеристик куба. В объекте “куб” функцию вычисления площади квадрата заменит функция для вычисления объема куба, но в остальном все свойства будут унаследованы от квадрата. Программисту, использующему объект “куб” в своей программе, даже и знать неза- чем, что тот произошел от квадрата (унаследовал его свойства). См. рис. 22.3, иллюстрирую- щий этот принцип.  570 Дополнительная неделя. Основные вопросы 
. : чьём" ‘х  \\ .. .— L we "»! w, ‘;:- ‹т"&'н „. ‹  ООП в действии  Чтобьттнпляднее Проиллккприровать три принципа объекпкъориентированного про- граммирования, в листинге 22.2 приведена небольшая программа на языке С++. Этот листинг дается здесь еще и для того, чтобы продемонстрировать одно важное обстоятельство —— то, что программа на С++ по своей структуре значительно отличается от программы на С. После освоения материала следуюших занятий текст этой программы станет понятнее.          I ,! | (X. y) (x. y),’ ‚’ : : | длина длина ‚: квадрат куб Площадь квадрата = длина * длина Объем куба = Площадь квадрата * длина  Рис. 22.3. Наследование “кубом” свойств “квадрата”  „ ‚Листинг 22.2. cube . cpp — объектно-ориентированная программа на языке С++   1: // Программа на С++ c классами square и cube ° #include <iostream.h>  2 3 4. // Простой класс square 5: class square { б. 7 8  public: square(); : square(int); 9: int length; 10: long area(); 11: int draw(); 12: }; 13:  : -14: // Простой класс cube, наследующий класс square ‘ 15: class cube: public square { 16: public:  17: cube( int ); 18: long area(); 19: }; 20:  21: // Конструктор объекта square 22: square::square()  23: { 24: length = 4; 25: } 26:  27: // Перегруженный конструктор класса square  День 22-й. Языки объектноориентированного программирования 5 71 
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:  572  square::square( int init_length )  { }  length = init_length;  // Функция вычисления площади объекта square long square::area( void ) { return((long) length * length);  }  // Функция рисования объекта square int square::draw() { int ctr1 int ctr2  0; 0;  for (ctr1 = 0; ctr1 < length; ctr1++ )  { cout << "\n"; /* new line */ for ( ctr2 = 0; ctr2 < length; ctr2++) { COut << "*"; } }  cout << "\n";  return 0;  }  // Конструктор класса cube cube::cube( int init_length) { length = init_length; }  // Функция вычисления объема в классе cube long cube::area()  { return((long) length * length * length); } int main() {  square square1; square1.length = 5; square square2(3); square square3; cube cube1(4);  cout << “\nDraw square 1 with area of “ << square1.area() << "\n“;  Дополнительная неделя. Основные вопросы 
79: square1.draw();   80: 81: cout <<"\nDraw square 2 with area of “ << square2.area() << "\n"; 82: square2.draw(); _83: 84: cout << "\nDraw square 3 with area of “ << square3.area() << “\n"; 85: square3.draw(); 86: 87: cout << "\nDraw cube 1 with area of " << cube1.area() << "\n"; 88: cube1.draw(); // Фактически используется функция draw класса зсгщаге 89: 90: return О; 91: } _ Draw square 1 with area of 25 ***** ***** ***** ***** *****  Draw square 2 with area of 9  *** *** ***  Draw square 3 with area of 16  **** **** **** ****  Draw cube 1 with area of 64  **** **** **** ****   "идиш". Объект cout в языке С++ используется для вывода на экран. Он будет — изучен более подробно на следующем занятии.     IIIIIE Эту программу_нельзя отнести к образцам безупречного стиля программирова- ' › › ния на С++. Ho все же она достаточно ярко демонстрирует различия между С и  С++, причем всего лишь несколькими строками кода. Позже на этом же занятии будет про- демонстрировано, что и язык Java использует похожие принципы и конструкции. В течение  День 22—й. Языки обьектно-ориентированного программирования 573 
нескольких следующих занятий вы узнаете достаточно, чтобы попытаться усовершенствовать приведенную выше программу. А на этом занятии еще будет рассказано дополнительно об объекте cout, используемом в языке С++ для вывода информации на экран. В приведенной программе этот объект используется неоднократно. В листинге 22.2 реализованы все характерные черты ООП —— полиморфизм, инкапсуляция H наследование. В строках 5—12 объявлен класс под именем square. Подробности синтаксиса будут обсуждаться на занятии 24, а пока заметьте, что объявление включает как функции (в строках 7, 8, 10 H 11), так H данные (строка 9). Благодаря этому все свойства H методы работы с объектом типа “квадрат” инкапсулируются B одном месте. Инициализация объекта класса square( ) может происходить двумя способами, как это видно из строк 22 H 28. B программе встречается также H наследование. Класс cube наследует класс square, как это можно видеть в строке 15. Наконец, здесь имеет место H перенос кода одного объекта в другой. В объявле- нии объекта cube используются функциональные возможности объекта square. Кроме того, объявления классов cube H square написаны так, чтобы их легко было перенести в другую программу. Все это станет более понятным после занятия 24, на котором будут рассмотрены классы H обьекты С++.  Связь между С и С++  Язык С++ является расширением С. Другими словами, все возможности G доступны H B C++. Обратное неверно: далеко не все возможности С++ имеются в языке С. В листинге 22.2 значительная часть операторов H выражений знакома любому, кто знает С. Но несколько конструкций все же окажутся новыми. С++ создавался с таким расчетом, чтобы облегчить написание объектно-ориентированных программ за счет внедрения средств ООП в сам язык. Например, были введены ключевые слова class H template, позволяющие легко создавать H переносить объекты. Кроме того, благодаря введению оператора try-catch была усовершенствована обработка ошибок. Эти H другие модификации сделали язык гораздо более мощным, чем С, H вместе с тем легко переносимым.  Программные модули на языке С++  Как H C, язык С++ пригоден не только для того, чтобы писать на нем выполняемые программы. Имеется еще несколько типов программных модулей, которые можно создавать с помощью средств этого языка H его компиляторов. Все они перечислены в табл. 22.1.  Таблица 22.1 . Типы программных мадулей на С++    Типы программных Описание модулей Исполняемые файлы Файлы программ, запускаемые на выполнение Библиотеки Совокупности подпрограмм, функций и т.п., подключаемые к дру-  гим программам в процессе их компоновки  Библиотеки динамической ком- Совокупности подпрограмм, функций и т.п., которые могут нахо- поновки диться в памяти компьютера и подключаться к другим програм- мам при их выполнении по мере необходимости  Элементы управления Совокупности данных и средств, используемых при разработке других программ для организации их рабочих функций   574 Дополнительная неделя. Основные вопросы 
Язык программирования Java  Сейчас в это трудно поверить, но первоначально язык Java создавался как средство для программирования устройства под названием Star7 (*7), представлявшего собой контроллер домашней бытовой техники. Создатели Java —— компания Sun Microsystems —— предвосхищали времена, когда все домашние бытовые устройства, от видеомагнитофона и стереосистемы до холодильника и электроплиты, будут подключены к компьютерной сети и управляться цен- тральным компьютером. Какими же чертами, по мнению разработчиков, должен был обла- дать язык, предназначенный для этих целей‘? Вот некоторые, самые важные из них.  I Независимость от архитектуры. Это всего лишь другой способ сказать, что про- грамма, написанная на языке, должна выполняться одинаково на всех процессорных устройствах, которые поддерживают этот язык.  I Устойчивость. Программы, написанные на Java, должны отличаться высокой устой- чивостью K сбоям разного рода.  I Объекгно-ориентированный характер. Преимущества ООП в значительной мере снижают вероятность возникновения непредвиденной ошибки. Кроме того, ошибки, которые все-таки возникают, легко найти и исправить.  I Безопасность. Java создавался для работы в сети, поэтому очень важна его сопротив- ляемость потенциальным атакам вирусов.  I Сочетание мощи и простоты. Язык Java He должен никоим образом связывать руки программисту в смысле функциональных возможностей и при этом не должен быть чрезмерно запутанным и сложным в изучении.  Все эти черты давно известны в разных языках и по отдельности никого не поражают сво- ей новизной. Принципиально новой является лишь попытка совместить их в одном языке программирования, Довольно скоро стало ясно, что возможности нового языка далеко не ог- раничиваются программированием устройств бытовой техники. В настоящее время сотни ты— сяч программистов пользуются Java KaK основным языком для разработки самых разнообраз- ных программных продуктов.  Связь Java C языками С и С++  При первом же взгляде на код Java многое в нем покажется знакомым, Это и неудиви— тельно, ведь Java основан на С++, а С++, в свою очередь, основан на С, как уже говорилось ` ранее. Тем не менее, Java и С++ соотносятся между собой по-другому, чем С++ и С. Напомним, что С++ представляет собой расширение С. Это означает, что С++ содержит в себе все, что есть в С, и еще ряд дополнительных средств (в основном относящихся K OOH). Что ж, здесь все понятно. Можно ли пойти дальше и сказать, что Java — это С++, в который опять добавлены дополнительные возможности? Нет, это не так. На самом деле удобнее и точнее всего представлять себе язык Java KaK С++, из которо- го удалены некоторые черты. Это может показаться странным, но, по сути, все так и есть. С++ является исключительно мощным и гибким языком. С его помощью можно делать практически все, что угодно, и любым способом. Но эта гибкость сопровождается повы- шенным риском и большой сложностью, что нежелательно и поэтому ликвидировано в та- ком языке, как Java, т.е. Java —— это нечто вроде С++ без тех его черт, которые создают не— нужную сложность, подвергают программы различным опасностям, ухудшают аппаратную независимость и т.д.  День 22-й. Языки обьектно-ориентированного программирования 575 
Еще одно различие между Java и С++ состоит в том, что С++ позволяет писать программы в объектно-ориентированном стиле, а Java требует этого. Использование этого языка гарантиру- ет, что разрабатываемая программа будет целиком и полностью основана на принципах ООП. А как насчет С#? Это еще более новый язык, также произошедший от С и С++. Как и в случае Java, B программах на С# объектно-ориентированные конструкции обязательны для применения. Подобно Java, язык С# лишен некоторых недостатков С++ и при этом более по- следовательно реализует принципы ООП.  Аппаратная независимость Java  Когда язык Java впервые появился на свет, его аппаратная независимость показалась про- граммистам, пожалуй, самым многообещающим преимуществом перед другими языками. Теоретически предполагалось, что можно написать программу на Java H выполнить ее с оди- наковым успехом, причем без всяких модификаций, на персональном компьютере семейства [ВМ PC или Macintosh, рабочей станции Sun или любой другой аппаратно-системной плат- форме, поддерживающей Java. Это действительно было бы огромным преимуществом. Но как аппаратная независимость Java была реализована на практике? Программа на С, Java или любом другом языке программирования высокого уровня со- стоит из легко понятных английских ключевых слов, математических выражений и других приемлемых для человека синтаксических конструкций. Компьютер, однако, не может по- нять такую программу непосредственно. Центральный процессор компьютера способен по- нять только ограниченный набор двоичных инструкций, предусмотренный его стандартом. Поэтому для выполнения программы ее следует перевести (транслировать) с языка программи- рования на язык двоичных инструкций процессора. При компиляции программы на С именно это и происходит —— исходный код программы преобразуется в двоичный код процессора. К сожалению, разные компьютеры оснащены разными процессорами. Все они основаны на общих принципах, но отличаются в деталях. Ввиду этого приходится генерировать двоич- ный код программы для каждого конкретного процессора. Компьютер семейства Macintosh не поймет машинный код для [ВМ-совместимого компьютера, и наоборот. Решение, предложенное в языке Java, состоит в следующем. При компиляции программы на Java генерируется не последовательность двоичных инструкций процессора, а так назы- ваемый байт-код. Это промежуточный этап между исходным кодом и двоичным кодом само- го низкого машинного уровня. Важным свойством байт-кода является то, что он никак не привязан к конкретному процессору. Другими словами, байт-код в достаточной мере при- ближен к машинному двоичному коду и в то же время является аппаратно-независимым. На каждой платформе, т.е. в каждой конкретной аппаратно-программной среде, имеется свой собственный интерпретатор Java. Этот интерпретатор разрабатывается для того, чтобы транслировать байт-код Java B последовательность двоичных инструкций конкретного про- цессора. Интерпретатор называется виртуальной машиной Java (Java Virtual Machine —— JVM). Процесс перевода байт-кода в двоичные машинные инструкции выполняется на ходу по мере выполнения программы. Таким образом, после компиляции программы на языке Java один и тот же байт-код может далее распространяться для использования в любой системе, имеющей виртуальную машину Java.   IIIIIIII Хотя Java и претендует на аппаратную независимость, на практике все \ складывается не так просто. B этом отношении Java значительно лучше остальных языков, но все-таки в силу целого ряда обстоятельств этот язык не настолько независим от аппаратуры, как первоначально задумывалось разработчиками.     576 Дополнительная неделя. Основные вопросы 
Пакеты  Легкость переноса готового кода в новые проекты является козырем любого объектно- ориентированного языка. А наследование можно считать основным механизмом реализации такого переноса. В Java 3T0 делается еще лучше, чем в других языках, за счет использования пакетов (называемых также библиотеками классов). Пакеты Java облегчают и стандартизи- руют организацию, использование и перенос классов (объектов). Они во многом аналогичны интерфейсам разработки приложений (Application Programming Interfaces — API), известным в других языках. С помощью пакетов довольно просто организуются пространства имен. Идея пространст- ва имен возникла в свое время в связи с тем фактом, что два класса могут случайно получить одно и то же имя. Всякая программа на Java почти наверняка использует несколько пакетов классов: некоторые из них стандартны и поставляются вместе со средствами языка, другие разрабатываются различными производителями программного обеспечения, третьи могут быть написаны самим программистом. Поэтому вполне вероятно, что имена нескольких раз- личных классов могут совпасть. В других языках программирования это привело бы к ошиб- кам. В языке Java каждый пакет определяет свое собственное пространство имен, и имя клас- са обязано быть уникальным только внутри него. Совпадение с внешним именем не причиня- ет никакого вреда. Другими словами, класс определяется как своим собственным именем, так и именем пакета классов (пространства имен).  Аплеты Java  Язык Java создан для целей разработки двух различных типов программ. Один из типов включает пршожения, т.е. самостоятельные полнофункциональные программы, рассчитан- ные на автономную работу. К этому же типу относятся и все программы, которые вы написа- ли в ходе работы над книгой. Второй тип — это аплеты, программы особой разновидности, предназначенные для распространения по Internet и выполнения в окне программы-браузера. Приложения и аплеты на языке Java почти ничем не отличаются в разработке. Аплеты не- сколько проще, поскольку браузер Intemet берет на себя часть функций, o которых приложе- нию приходится заботиться самостоятельно.  Библиотеки классов Java  Java-— 3T0 He просто еще один язык программирования. Установив среду разработки Java, вы получаете универсальный набор классов, готовых к использованию. Библиотека классов Java напоминает по своему назначению библиотеки функций С и библиотеки классов С++. Практически для всех стандартных операций, которые могут понадобиться при про- граммировании, например, операций вывода на экран, связи по локальной сети или доступа к Internet, в библиотеках классов Java уже имеются готовые, хорошо отлаженные функцио- нальные средства. К любой среде разработки Java прилагается подробная документация об имеющихся в ее составе библиотеках классов.  Как сказать Hello, world Ha языке Java  Первое занятие по языку С в этой книге начиналось с программы Hello, World!. Это давняя традиция, и на следующем занятии таким же образом начнется введение в язык  День 22-й. Языки обьектно-ориентированного программирования 577 
С++. А сейчас наступает очередь языка Java. B листинге 22.3 приведен код Java-npor- раммы Hello, World!.  Листинг 22.3. hello. java — дама-версия программы Hello, World!   1: public class HelloWorld 2: { 3: public static void main(String args[]) 4: { 5: Say("Hello, world!"); 6: } 7: private static void Say(String message) 8: { 9: System.out.println(message); 10: } ll: }   H 11 w 1d: Резцльшаш е 0’ or  Manna Эта программа очень проста, но ее более чем достаточно для демонстрации того, как в Java организованы функции (именуемые мет0дами). В строке ] на-  чинается определение программы HelloWorld. Обратите внимание, что вся программа оп— ределена как класс. Это пример обязательного использования принципов ООП в языке Java. B строке 3 начинается определение функции main( ), которая является непременной частью приложений Java —-— так же, как и программ на языке С. А вот в аплетах Java функ- ция main() не нуяапъ как будет показано на последукпцих занятиях.!1ри запуске Java- приложения его выполнение начинается с функции main( ). B строке 5 вызывается функция 5ау( ), в которую передается аргумент "Hello, World! ". Конец функции шаіп() обознача- ется фигурной скобкой в строке 6. Функция 5ау() определена в строках 7—10. Она очень похожа по своему синтаксису на обычные функции С, не правда ли? Заметьте, что ее аргумент имеет тип String — это один из стандартных объектов, определенных в библиотеках классов Java. B строке 9 выполняется собственно вывод текста на экран. Для этого используется еще один стандартный объект System.out, который соответствует потоку stdout B С и объекту cout B C++. При вызове ме- тода println объекта System. out строка текста отображается на экране. Эта простая программа является консольной, т.е. работает только с текстовым вв0дом- выв0дом. То, куда конкретно будет выводиться текст, зависит от инструментальной среды разработки Java. Если это компилятор командной строки, то текст будет выведен в текстовую командную строку. Если же используется графическая среда разработки, то выведенный текст появится в окне с заголовком Java Console.   Рекомендуется Не рекомендуется      Изучите язык Java или С++, если вы @ гнамерены разрабатывать большие и“; % 2  % Не пренебрегайте языком С — он может % пригодиться для написания программ не- ; большого объема и самого разнообразно- го назначения.  сложные программы.   578 Дополнительная неделя. Основные вопросы 
Язык программирования С#  Язык С# (произносится “си-шарп”) совсем молод —— он был представлен широкой обще- ственности в июне 2000 г. Это новый язык, разработанный корпорацией Microsoft и пред- ставленный в ЕСМА для стандартизации. Целью создания С# было устранение целого ряда проблематичных мест других языков программирования. Он задумывался как язык с большим потенциалом, устремленный в бу- душее. Как будет показано, С# имеет много общего с С, С++ и Java. Простая программа на этом языке приведена в листинге 22.4. Более подробное введение в С# будет дано на послед- нем, 28-м занятии этой дополнительной недели.  Листинг 22.4. sample. cs -—- простая программа на языке С#   // app.cs - Простейшая программа на С#  1 2 3. 4: using System; 5 6 class sample 7 8  `О .. .. .. .. ..  { public static void Main() { 10: // Объявление переменных 11: 12: int radius = 4; 13: const double PI = 3.14159; 14: double area; 15: 16: // Вычисление 17: 18: area = PI * radius * radius; 19: 20: // Вывод результатов 21: 22: Console.WriteLine("Radius = {0}, PI = {1}", radius, PI ); 23: Console.WriteLine("The area is {0}", area); 24: } 25: }   Radius = 4, рх = 3.14159 ”№№" The area is 50.3344  Резюме  На этом занятии были рассмотрены общие принципы объектно-ориентированного про- граммирования, или ООП. Обсуждались основные объектно-ориентированные концепции, реализованные в языках ООП, такие как полиморфизм, инкапсуляция, наследование, пере- носимость кода. Элементарные основы языка С++ практически совпадают с основами языка С, а сам язык является объектным расширением С. Еще два эволюционировавших  День 22-й. Языки обьектно-ориентированного программирования 579 
потомка С и С++ —— 3T0 языки Java и С#. На следующих шести занятиях будет дано более подробное введение B C++, Java и С#.  Вопросы и ответы  Зачем изучать язык С, если С++, Java и С# настолько богаче его по своим воз- можностям? Языки объектно-ориентированного программирования значительно сложнее по своим идеям и языковым конструкциям, чем С. Для начинающих С проще B освоении, потому что это процедурный язык, и неопытному программисту значительно легче следить за последова- тельностью выполнения программ на нем. Языки С++, Java и С# основаны на С, поэтому многое из того, что уже изучено в курсе С, не понадобится учить заново при их освоении. Конечно, для разработки больших программных проектов и обеспечения хорошей переноси- мости кода между программами лучше прибегнуть к С++ или Java. Для работы со средой Мі- crosoft .NET следует подумать об изучении С# наряду с С++ и Java. Если же в перспективе у вас доработка и поддержка существующих программ, то скорее всего вам понадобится и С, поскольку в обиходе разработчиков находится огромное количество готового кода на С.  Насколько важно знать и понимать основные принципы объектно-ориентиро- ванного программирования? Р Это, без преувеличения, жизненно важно. Чтобы эффективно использовать всю мощь средств ООП, необХОДимо как следует разобраться в его принципах. Не беспокойтесь, если B данный момент вы еще не освоились с такими понятиями, как полиморфизм, инкапсуляция и наследование. По мере освоения С++, Java и С# все они станут более понятными.  Коллоквиум  B ЭТОМ КОЛЛОКВИУМе BaM предлагаются контрольные ВОПРОСЫ ДЛЯ закрепления изученного, а также упражнения на развитие практических НЗВЫКОВ программирования.  Контрольные вопросы  1. В чем состоят особенности языков объектно-ориентированного программирования?  2. Допустим, Одна и та же функция определена несколько раз под Одним именем, но с раз- ными списками аргументов. Какой из принципов ООП реализуется таким образом?  Какие ключевые слова есть в С, но отсутствуют в С++?  Можно ли писать объектно-ориентированный код на С? Содержит ли язык Java B себе все средства и возможности С++?  Можно ли написать программу с чисто процедурной структурой на С++ или на Java?  цену-ды  В языке С для вывода данных на экран используется функция printf( ). A как это делает- ся B C++?  Как осуществляется вывод на экран в языке Java?  9°  9. Каким образом данные выводятся на экран в С#?  580 Дополнительная неделя. Основные вопросы 
Упражнения  1. Выясните, может ли ваш компилятор С также компилировать программы на С++. Если это так, переключите компилятор на работу с С++ и перекомпилируйте одну-две про— граммы на С, приведенные в этой книге.   “п.№…“ Компиляторы С. находящиеся на прилагаемом компакт-диске, компилиру- —— ют также и программы на С++.     День 22—й. Языки обьектно—ориентированного программирования 581 
  Введение B язык С++  С++ является самым известным и наиболее широко распространенным из языков объект- но-ориентированного программирования. На предыдущем занятии изучались основные принципы построения таких языков, а также несколько характерных отличий С++ от С. Ны- нешнее занятие целиком посвящено С++. На нем будут рассмотрены следующие темы.  I Пример программы на С++ Общность типов данных и операторов в языках С и С++  Перегрузка функций и ее особенности  Встраиваемые функции  Ключевые слова С++  Hello World Ha языке С++  Программа под названием Hello World была самой первой программой на языке С, изу- ченной на занятии l. Для повторения она приведена ниже в листинге 23.1.  Листинг 23. 1 . hello.c — программа Hello World Ha языке С   #include <stdio.h>  int main(void) { : Printf("Hello World!\n"); = return 0;  = }  н 11 w rldl Разиль-пт e ° °  Сейчас, после завершения цикла занятий по С, эта программа может показаться вам со- всем простой. Столь же элементарной будет и первая программа на С++. В листинге 23.2 представлен аналог приведенной выше программы Hello World, но уже на языке С++.  ЧФШФШМН u 
Листинг 23.2. hello.cpp -— программа Hello World Ha языке С++   1: #include <iostream.h> 2: 3: int main( void ) 4: { 5: cout << "Hello C++ World!\n"; 6: return О; 7: }   Hell с++ w 1d! Результат ° or  № Как видите, эта программа на С++ лишь B нескольких местах отличается от ее аналога на языке С из листинга 23.1. И первое из этих отличий встречается в строке 1. В С++ используется другой набор библиотек, функций и объектов. Вместо стан- дартных функций С на этапе компоновки к программе на С++ подключаются объектно- ориентированные функции и другие специфические средства. В частности, B строке 1 исполь- зуется заголовочный файл iostream.h, a не stdio.h. Этот файл содержит все необходимое для стандартных операций ввода и вывода в программах на С++. В строке 5 можно видеть еще одно значительное отличие. Вместо вызова функции с круглыми скобками для посылки текста на устройство вывода используется поток cout. Это объект, который принимает на себя всю работу по выводу текстовой информации на экран. Данные посылаются B этот объект не с помощью передачи аргументов, а путем пе- ренаправления. Для этой цели используется знак операции «. В данном случае B объект cout перенаправляется строка Hello C++ World!. Каждый оператор С++, как и С, заканчи— вается точкой с запятой.   (Hummus Объекты и классы будут рассмотрены более ПОдрОбНО на следующем за- нятии.     ВЫВОД на экран в С++  В программах на С для вывода на экран используются такие функции, как puts() или printf( ). B программах на С++ их тоже можно использовать для той же цели, однако этого не рекомендуется делать. Такие функции не являются объектно-ориентированными и входят B противоречие с идеологией С++. Кроме того, они не дают никакого выигрыша по сравне- нию с имеющимися в С++ средствами. Как видно из листинга 23.2, для вывода информации на экран B программе на С++ следует использовать объект cout. C помощью этого объекта данные посылаются на стандартное устройство вывода. Как уже говорилось, этот объект используется не совсем так, как функция в С. Выводимые данные посылаются B него путем перенаправления, а он уже сам отправляет их на устройство вывода. Одно из преимуществ объекта cout заключается в том, что его использование согласуется с принципами ООП. Объект cout инкапсулирует все необходимые средства вывода, при этом реализуя полиморфизм. Например, при использовании printf() необходимо указывать типы выводимых данных, а объекту cout этого не требуется —— он сам приспосабливается к посы- лаемым в него данным. Эти особенности cout проиллюстрированы в листинге 23.3.  День 23-й. Введение в язык С++ 583 
Листинг 23.3. cout . cpp -— вывод на экран через объект cout   1: // Вывод различных данных через объект cont 2: #include <iostream.h> 3: 4: int main(int argc, char* argv{]) 5: { 6: int an_int = 123; 7: long a_long = 987654321; 8: float a_float = (float) 123.456; 9: char a_char = ’A’; 10: char *a_string = "A String"; 11: bool a_boolean = true; 12: 13: cout << "\n"; ` 14: cout << “an int: “<< an_int << ’\n’; 15: cout << "a long: “<< a_long << ’\n'; 16: cout << “a float: "<< a_float << ’\n’; 17: cout << “a char: “<< a_char << ’\n’; 18: cout << “a string: “<< a_string << ’\n'; 19: cout << "a bool: "<< a_boolean << '\n'; 20: 21: return 0; 22: }   an int: 123 Резцпьтвш along: 987654321  a float: 123.456 a char: A a string: A String a bool: 1  МШШЗ В строках 6—11 объявляются и инициализируются несколько переменных раз- личных типов. В строках 14—19 каждая из этих переменных выводится на экран  через объект cout. Как видно из результата, все они вывоцятся в соответствующих форматах.   (“mam Чтобы использовать объект cout, необХОДимо п0дключить к программе за- — головочный файл iostream.h.     Ключевые слова С++  Все ключевые слова языка С целиком унаследованы языком С++. Не забывайте, что С++ является расширением С. Все средства языка С работают и в С++. Но далеко не все из них следует применять в программах на С++! Кроме ключевых слов С, в языке С++ имеются и новые, дополнительные. В списке, при- веденном ниже, перечислены все основные ключевые слова стандарта ANSI C++. Полужир- ным шрифтом выделены те из них, которые не являются ключевыми словами С. Как видите, все знакомые слова С имеются в этом списке. Более того, их назначение и способ использо- вания в С++ именно таковы, как это изучалось ранее в С.  584 Дополнительная неделя. Основные вопросы 
 Основные ключевые слова С++   _БЁБак case catch class const continue delete do double else float for goto if inline int long namespace new Operator private protected public static struct switch throw try union void while    @… Идентификаторы, выделенные полужирным шрифтом, являются ключе— , выми словами ANSI С++ и не входят в ANSI C.     Типы данных С++  На занятии 3 изучались базовые типы данных B языке С. Все те же самые типы используются и B С++. Кроме типов данных, представленных на занятии 3, язык С++ предлагает еще два об— щеупотребительных типа. Один из них —— это тип bool. Значения этого типа представляют собой булевы (логические) числа длиной Один байт и могут равняться True или False.   IIIIII'IIIIII Значение False соответствует 0, а True— всем остальным ненулевым числам.     Кроме того, данные B С++ можно организовать B виде так называемых классов. Классы —— это составные типы данных, с помощью которых определяются объекты. Более поцробно о классах будет рассказано на следующем занятии.  Объявление переменных в С++  Одно существенное отличие С++ от С связано с объявлением переменных. В языке С пе— ременные можно объявлять только B начале блока. Чаще всего их объявляют в начале функ- ций, но теоретически не запрещается поместить объявление переменной и B другое место функции, если только оно нах0дится B начале блока. В С++ переменные можно объявлять B любом месте программы. Это означает, что с объ— явлением можно подождать до того момента, когда переменная действительно понадобится. Область доступности локальных переменных начинается с места, где они объявлены, и за- канчивается закрывающей скобкой блока. В листинге 23.4 пр0демонстрировано, как пере- менные могут объявляться B любом месте программы на С++.  Листинг 23.4. vars .cpp — объявление переменных не в начале блока  1: // Объявление переменных в языке С++ 2: #include <iostream.h> 3:  День 23—й. Введение в язык С++ 585 
 4: int main(int argc, char* argv[]) 5: { 6: char a_char = 'x'; 7: 8: for (int ctr = 1; ctr < 10; ctr++ ) 9: { 10: cout << "\nLine: “ << ctr << " - printing the char: " << a_char; 11: } 12: 13: char *just_for-fun = "Just For Fun!!!"; 14: 15: cout << “\n\njust_for_fun = " << just-for_fun << “\n"; 16: 17: return 0; 18: } Line: 1 - printing the char: x Peaunmam Line: 2 - printing the char: x Line: 3 - printing the char: x Line: 4 - printing the char: x Line: 5 — printing the char: x Line: 6 - printing the char: x Line: 7 - printing the char: x Line: 8 - printing the char: x Line: 9 - printing the char: x  just_for_fun = Just For Fun!!!  ` Manna Как видите, программа довольно элементарна. Строка 8, в которой объявлена переменная ctr, представляет особый интерес. В программе на С переменная  ctr должна была быть объявлена в начале функции, а иначе это была бы грубая ошибка. В то же время в языке С++ объявление переменной-счетчика в начале цикла (как это делается в листинге 23.4) общепринято. Вообще, переменные в программах на С++ объявляются в тех местах программы, где в них впервые возникает необходимость. Переменная just__for__fun также объявлена не в начале блока (строка 13) и уж тем более не в начале функции main( ). Однако, как видно из этого примера, такое объявление отлично работает в программе на С++, и с0держимое строки just__for__fun благополучно выв0дится на экран в следующей строке с помощью объекта cout. При объявлении переменных в середине функции следует соблюдать осторожность. Хотя объявления могут располагаться в любой части программы на С++, рекомендуется все равно группировать объявления в начале функций, чтобы другим программистам легче было найти их и понять текст программы. Это также облегчает и отладку программ.  Рекомендуется Не рекомендуется  ›,Объявляйте переменные в начале бло— 3 Не забывайте о том. что локальные пе— ; “:;ков или функций. а не в их середине, : ременные имеют более высокийприори— ; ›чтобы сделать текст программы понят- % (тет внутри своих блоков. чем глобаль- ; ‚ нее и легче для отладки. “ ) ’ %  ’ные переменные с теми же именами.  l “`, пт:-№№ …… „..—мым «‹ п.м—м исшущттпьд Мэн—жж! «.     М Mmemow    «ир—тп—  586 Дополнительная неделя. Основные вопросы 
Операции B С++  В языке С++ выполняются практически те же операции над данными, что и в С, с теми же знаками, обозначениями и правилами применения. Это означает, что весь ранее изученный материал по С целиком распространяется и на С++. В С++ имеется дополнительная операция обработки ошибок. Она выполняется операто- ром throw. B этой книге нет возможности подробно рассказать об обработке исключитель- ных ситуаций с помощью оператора throw. Тем не менее, стоит запомнить, что в С++ суше- ствует такой оператор, облегчающий перехват и обработку ошибок выполнения.  Функции B С++  На предыдущем занятии рассматривался пример перегрузки (переопределения) функций в С++. На этом примере стало ясно, что одно и то же имя может, оказывается, использоваться несколько раз для различных функций. В следующих разделах тема перегрузки функций рас- сматривается более подробно. Будут изучены и другие возможности языка С++, имеющие от- ношение к функциям, а именно:  I Задание значений по умолчанию для параметров функций  I Использование встраиваемых (inline) функций  Перегрузка функций  Перегрузка функций -—— это самая простая разновидность полиморфизма из всех, поддер- живаемых языком С++. Как было показано на примерах прошлого занятия, компилятор С++ не выдает сообщений об ошибках, встретив функции с одинаковыми именами. Возможность использовать имена функций по несколько раз позволяет разрабатывать программы, которые легко адаптируются к данным разного рода. Пользователь такой функции, т.е. программист, применяющий ее в своей программе, имеет на выбор сразу несколько вариантов. Вернемся к примеру прошлого занятия. В программе рисования квадрата эта операция выполнялась раз- ными способами -— no двум точкам или по одной точке и длине стороны —— но всякий раз ус- пешно. ' Чтобы корректно выполнить перегрузку функции, соблюдая при этом принцип полимор- физма, необходимо следовать нескольким правилам. Несколько функций с одним и тем же именем должны быть объявлены и определены таким образом, чтобы они принимали различ- ные параметры. Каждая функция должна хотя бы одним параметром отличаться от других. Имеется в виду отличие типов, а не имен параметров. Для примера возьмем два следующих прототипа функций:  int rectangle( int topleftx, int toplefty, int width, int length ); int rectangle( int topleftx, int toplefty, int bottomrightx, int bottomright у );  Хотя на первый взгляд эти два прототипа кажутся различными, на самом деле это не так. Попытка перегрузить функцию таким образом вызовет сообщение об ошибке. Передаваемые параметры кажутся разными —— один раз передаются координаты точки, а другой раз ширина и длина -— но компилятор не сочтет их таковыми. Вместо этого компилятор воспримет оба прототипа как заголовки одной и той же функции с четырьмя параметрами типа int. Изменим второй прототип на следующий:  День 23-й. Введение в язык С++ 587 
int rectangle( int topleftx, int toplefty, long bottomrightx, long bottomright у );  Теперь компилятор сможет различить две функции, потому что вторая принимает два ар- гумента типа long, a не int. Можно определить еще и третью функцию рисования прямо- угольника:  int rectangle( int topleftx, int toplefty ); Эта функция отличается от двух остальных тем, что принимает только два параметра.  Компилятору этого достаточно —— он поймет, что функции различны. Однако, как станет ясно из следующего раздела, с третьей функцией нужно быть осторожным.  Установка параметров функций по умолчанию  Кроме перегрузки функций, существует такая интересная возможность, как установка значений по умолчанию для параметр'ов функций. Благодаря этому функция приспосаблива- ется к отсутствию требуемых ею параметров, решая самостоятельно, какими будут их значе- ния. Рассмотрим пример с прямоугольником, представленный в листинге 23.5.  Листинг 23.5. rect . срр — установка значений параметров в функциях по умолчанию °   1: // Установка параметров по умолчанию #include <iostream.h>  2 3 4. // Прототип функции со значениями параметров по умолчанию 5: void rectangle (int width = 3, int length = 3, char draw_char = 'Х'); 6. 7 8  int main(int argc, char* argv[])  - { 9: cout << "\nrectangle( 8, 2, \'*\' );\п"; 10: rectangle( 8, 2, '*' ); 11: 12: cout << "\nrectangle( 4, 5 );\п"; 13: rectangle( 4, 5 ); 14: 15: cout << "\nrectangle( 2 );\п"; 16: rectangle( 2 ); 17: 18: cout << "\nrectangle( );\n"; 19: rectangle( ); 20: 21: return 0; 22: } 23: 24: void rectangle ( int width, int length, char draw_char ) 25: { 26: int ctr1 = 0; 27: int ctr2 = 0; 28: 29: for (ctrl = 0; ctrl < length; ctr1++ )  588 Дополнительная неделя. Основные вопросы 
30: {   31: cout << "\n"; 32: for ( ctr2 = 0; ctr2 < width; ctr2++) 33: { 34: cout << draw_char; 35: } 36: } 37: cout << "\n"; 38: } rectangle( 8, 2, '*' ); ******** ********  rectangle( 4, S );  XXXX XXXX XXXX XXXX XXXX  rectangle( 2 );  XX XX XX  rectangle( );  XXX XXX XXX  Manna Как видно из листинга, в программе объявляется и используется только одна функция rectangle( ). Этот пример отличается от предыдущих тем, что здесь объявляются значения по умолчанию для параметров функции. Прототип функции в строке 5 содержит операторы присваивания значений параметрам. Перечисленные в прототипе значе— ния считаются принятыми по умолчанию на тот случай, если при вызове функции они не бу- дут указаны в явном виде. В строке 10 функция rectangle() вызывается привычным способом, с передачей в нее трех параметров. Однако в строке 13 значение параметра draw_char опущено. Но прямо- угольник все-таки выводится, причем он состоит из символов ’Х'. Именно этот символ был указан в прототипе (строка 5) как значение по умолчанию для данного параметра. В стро- ках 16 и 19 функция rectangle( ) вызывается еще дважды, и всякий раз список передаваемых аргументов сокращается еще на один. В каждом из этих случаев вместо недостающих пара- метров используются их значения по умолчанию из прототипа в строке 5. Если используются значения параметров по умолчанию, то всегда предполагается, что пе- реданные аргументы соответствуют первым параметрам в порядке их следования слева на-  День 23-й. Введение в язык С++ 589 
право. Например, нельзя вызвать функцию rectangle() B приведенном примере, опустив значения width и length. Следующая запись ошибочна: —  rectangle( '*' );  B этом вызове функции rectangle() переданный аргумент (вернее, его код ASCII) ис- пользуется как значение параметра width, a He draw_char. Как объявлено в строке 5 листин— га 23.5, первый аргумент функции rectangle( ) всегда должен быть width, второй —— length, a третий — draw_char. Изменить этот порядок нельзя, можно лишь опустить некоторые ар— гументы ——— причем всегда из конца списка. На первый взгляд кажется, что с помощью сле- дующей записи удастся “обмануть” компилятор и опустить параметры из начала списка:  rectangle ( . . '*' );  Но это не так. Компилятор сочтет этот оператор ошибочным, потому что нельзя переда- вать реальные аргументы не по порядку, заменяя их пустыми местами.  I: ` g Параметры,; для которых ожидается Или может понадобиться введение m l значений по умолчанию лучше заранее сдвинуть Ha правым край списка ; аргументов функции ”L; W :?:? № 1‘ ^ ‚ , ‚ ;& ц…… … …... „»……іі..… „ім… ^ вид»-адм “Mm … „`… ‘ …ЗЁК … „№№ „ Wm-“ mldm' ……й “J: „„„„; …да; … ……4„›…5’..‹  Встраиваемые функции  Встраиваемые (inline) функции представляют собой еще одно специфическое средство С++, недоступное в С. Встраиваемая функция обладает всеми свойствами обычной функции. Разница состоит только в том, как ее воспринимает компилятор. Когда функция объявляется встраиваемой, в программе фактически выполняется запрос к компилятору. Суть запроса состоит в том, чтобы компилятор заменил все вызовы функции в программе копиями кода самой функции. Встраиваемые функции используются исключительно для повышения быстродействия. При каждом вызове функции расходуется некоторое время на передачу в нее аргументов и возвращение значений. Если функция объявлена встраиваемой, указанное время экономится, поскольку данные никуда не передаются, а код находится прямо здесь, на месте. За повышение быстродействия приходится платить возрастанием объема кода. Очевидно, что код функции дублируется при каждом ее вызове, т.е. если функция вызвана в программе 15 раз, то в объектном коде будет находиться 15 копий этой функции. Пример программы, использующей встраиваемые функции, приведен в листинге 23.6.  Листинг 23.6. inline . срр — использование встраиваемых функций   1: // Использование встраиваемых функций 2: #include <iostream.h> 3: 4: inline long square( long value ) 5: { 6: return (value * value ); 7: } 8: 9: inline long halve( long value ) 10: { 11: return (value / 2); 12: }  590 Дополнительная неделя. Основные вопросы 
13: 14: int main(int argc, char* argv[])  15: { 16: long nbr; 17: 18: cout <<"\nEnter a number: "; 19: cin >> nbr; 20: 21: cout <<"\n\nSquared: " << square(nbr); 22: cout <<"\nHa1ved: " << halve(nbr); 23: 24: cout <<"\nHa1f the square: "; 25: cout << halve(square(nbr)); 26: 27: cout << "\n\nDone!"; 28: 29: return 0; 30: }   Е te umber: 16 Разильшат n r a “  Squared: 256 Halved: 8 Half the square: 128  Done!  Manna Приведенная программа довольно незатейлива. Пользователь вводит число Чтобы получить это число, в программе используется сіл —— стандартный объ  ект вв0да С++. Объект сіл выполняет задачу, прямо противоположную той, которая поручен: объекту cout, изученному раньше. Ввод перенаправляется из объекта сіл в переменную —-— 1 данном случае nbr. После ввода ее значение возводится в квадрат, делится на два, а затем квадрат числа также делится на два. “Все получившиеся результаты выв0дятся на экран. В строках 4 и 9 этой программы объявляются и определяются две встраиваемые функции. 3: исключением служебного слова іл1іле в заголовке, объявление встраиваемой функции ничеь не отличается от обычной. Как уже говорилось, спецификация inline выражает запрос к ком пилятору о помещении кода функции прямо в те строки, откуда она вызывается. Если компиля тор удовлетворяет этот запрос, код функции многократно дублируется в тексте программы Листинг 23.7 показывает, каким примерно стал бы код программы после удовлетворения запроса.   llplllllllllme B этом листинге показан только эффект ключевого слова inline. Кроме этого, препроцессор также включает заголовочные файлы, отбрасывает комментарии, сжимает свободное пространство до минимума и т.д.    Листинг 23.7. Результирующий код после вставки встраиваемых функций   1: // Использование встраиваемых функций 2: #include <iostream.h> 3:  День 23-й. Введение в язык С++ 59 
      4: int main(int argc, char* argv[]) 5: { 6: long nbr; 7 : 8: cont <<"\nEnter a number: "; 9: сіп » nbr; 10: 11: cout <<"\n\nSquared: " << (nbr * nbrfi; 12: cout <<"\nHa1ved: “ << (nbr/2); 13 : 14: cout <<"\nHalf the square: "; 15: cout << ((nbr * rflar)/2); 16 : 17: cout << "\n\nDone1"; 18: 19: return 0; 20: } ИСХОДНЫЙТЕКСТ int main() { func(); funekii } inline func(){//Kon func} После компиляции int main () int main ()  _  IIIIIIIIII Служебное слово іп1іпе выражает запрос, а не приказ компилятору. Нет  592  { func(); ‚_,—› fund) {  ... // код func func(); :/ }  } { // код func  }      Buwmwmmmmumm Cmmmmmmmmmm  Puc. 23.1. Сравнение между встраиваемыми и обычными функциями   никакой гарантии, что этот запрос бУДет удовлетворен.     Дополнительная неделя. Основные вопросы 
 * В качестве встраиваемых следует использовать только очень короткие и Причин: компактные функции. Если функция занимает больше одной—двух строк, ` ее не стоит делать встраиваемой.     Резюме  На этом занятии представлено введение в программирование на языке С++. Были рас- смотрены ключевые слова и некоторые операции С++, расширяющие язык С. Также изуча— лись характерные особенности работы с функциями в С++, делающие этот язык более гибким ;и мощным, чем С. Следующее занятие будет посвящено изучению и построению основных структурных блоков всех программ на С++ —— классов.  Вопросы и ответы  Только ли функции можно перегружать в С++? Нет, не только. Перегрузка возможна и для знаков операций. Эта тема выходит за преде— лы нашей книги. На следующем занятии станет ясно, почему перегрузка операторов является добщепринятой при работе с классами.  Чем отличаются булевы, или логические, значения '(переменные типа bool) от зна— чений, хранящихся в отдельных битах? Булевы переменные занимают один байт и могут равняться True или False. B байт поме— щается только одна булева переменная. Конечно, бит также может принимать любое из зна— »чений true (1) или false (0). Однако, хотя один байт содержит целых восемь битов, более одной переменной типа bool B него все равно не поместить.  На этом занятии рассматривались встраиваемые и перегружаемые функции. А что такое функции—члены? Функции—члены или методы —— это еще одно средство С++, отсутствующее в языке С. ,Функции—члены являются составными частями классов. Как классы, так и функции-члены будут рассмотрены более подробно на следующем занятии.  Можно ли пользоваться функциями printf() и fprintf() B программах на :языке С++? :“ Это вполне возможно, однако не рекомендуется. Объект cout организован намного эко— іномичнее и удобнее, чем довольно громоздкая функция printf( ). Это обстоятельство вместе с его объектно—ориентированной природой делает объект cout более подходящим для про— грамм на С++. Использование функции printf() нарушает объектно—ориентированную цело— стность программы.  Из каких книг можно узнать больше про С++? Дополнительные занятия в этой книге не претендуют на изложение языка С++ в сколько— нибудь полном объеме. Поэтому полезно прочитать книгу, специально посвященную С++‚ например, “Освой самостоятельно С++ за 21 день” Дж. Либерти.  День 23—й. Введение в язык С++ 593 
Коллоквиум  В ЭТОМ КОЛЛОКВИУМС вам ПРСДЛЗГЗЮТСЯ КОНТРОЛЬНЫС ВОПРОСЫ ДЛЯ закрепления ИЗУЧСННОГО,  а также упражнения на развитие практических навыков программирования. Ответы к вопро- сам и упражнениям приведены в приложении Е.  Контрольные вопросы  @0190)“.—  Какой объект С++ используется для вывода на экран? В каких случаях рекомендуется использовать функцию printf() B программах на С++? Какие значения может принимать переменная типа bool? B чем различие между объявлением переменных в С и С++? Какие операции есть в языке С++, но нет в С?  В чем разница между перегрузкой функций и использованием значений параметров по умолчанию?  Какие из приведенных далее прототипов могут использоваться для перегрузки одной и той же функции, а какие нет?  int triangle( int angle, int sidel, int side2); int triangle( int sidel, int side2, int side3); int triangle( int sidel, int sideZ, int angle);  Объявите функцию с именем triangle, принимающую три параметра: sidel, side2 и side3. Все параметры должны иметь тип int. Сделайте каждую из сторон треугольника равной 0 no умолчанию.  Верно ли следующее утверждение: встраиваемой функцией называется функция, код ко- торой непосредственно подставляется в код программы?  Упражнения  1.  2.  Напишите программу для вычисления площади поверхности обычной коробки с прямо- угольными гранями. Пользователь должен задавать высоту, длину и ширину коробки.  Измените программу из упражнения 1 так, чтобы она вычисляла стоимость доставки ука— занной коробки заказчику. Предполагается, что стоимость доставки зависит исключи- тельно от объема коробки, выраженного в условных кубических единицах. Если объем коробки меньше 100 кубических единиц, доставка будет стоить 5 долларов. Если коробка больше 100, но меньше 1000 единиц в объеме, ее доставка стоит $10, а если больше 1000 -— $20. Напишите функцию, которая бы в качестве значения по умолчанию исполь- зовала $20.  Сделайте функцию, которая вычисляет площадь поверхности коробки, встраиваемой.  594 Дополнительная неделя. Основные вопросы 
  Классы и объекты С++  На предыдущих занятиях неоднократно говорилось о том, что ключевым понятием B язы- ке С++ и вообще B объектно—ориентированном программировании является класс. На этом занятии будет дано более подробное введение B классы. В частности, будут рассмотрены сле- ‚тающие темы.  I Определение класса Что такое объекты Как создать объект (экземпляр) класса Как B классах используются функции и данные Использование конструкторов и десгрукторов Вложенные классы  Наследование и его применение  Как узнать больше о С++  Сложные типы данных B С++  '*' Как известно из предыдущих занятий, для хранения и обработки информации B языках С и С++ предусмотрен целый ряд различных типов данных. Например, тип int предназначен для работы с целыми числами, тип char — для обработки символьной информации, а массив типа char может содержать строку текста. В реальной жизни существует множество разных информационных объектов, например, имена людей, изображения предметов, документы, статистические таблицы, финансовые операции и т.д. Простые типы данных С и С++ не всегда эффективно справляются с модели- рованием таких сложных объектов. Для этой цели больше подходят составные типы дан— ных — структуры. С их помощью создаются нестандартные типы данных для описания раз- личных объектов, например, анкетных данных человека, размеров геометрической фигуры или финансовой операции. Такой объект, как время, можно описать следующей структурой:  struct time  {  int hours; 
};  int minutes; int seconds;  Используя этот структурный тип, можно объявить несколько переменных для работы со  временем в программе. Например, в листинге 24.1 две такие структуры хранят начальный и конечный моменты некоторого интервала времени.  Листинг 24.1 . endtime . cpp — использование структуры времени   \О  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:  596  ФЧФШЬЫЮН'  // Программа с использованием структуры времени  //  #include <iostream.h>  struct time { int hours; int minutes; int seconds;  }; void print_time(struct time tm);  int main(int argc, char* argv[]) { struct time start_time; struct time end_time;  start_time.hours = 8; start_time.minutes = 15; start_time.seconds 20;  end_time.hours = 10; end_time.minutes = 11; end_time.seconds = 12;  cout << "\nStart time: "; print_time(start_time); cout << "\n\nEnd time: "; print_time(end_time);  return 0;  }  // Вывод структуры времени в формате ч:м:с   //  void print_time(struct time tm)  {  cout << tm.hours << ":" << tm.minutes << ":" << tm.seconds;  } Start time: 8:15:20  End time: 10:11:12  Дополнительная неделя. Основные вопросы 
№ В листинге 24.1 используется несложный структурный тип, определенный в строках 5—9 и представляющий время в часах, минутах и секундах. В строках 15 и 16 объявляются две структуры этого типа —— start_time и end_time. Элементы этих структур инициализируются в строках 18—24. Затем значения времени выводятся на экран функцией display_time( ), прототип которой объявлен в строке 11, а тело определено в строках 36—39.   {Функции для работы со структурами  Для создания сложного объекта данных в листинге 24.1 используется структура. С этой структурой можно ассоциировать несколько функций, выполняющих те или иные задачи. *Одна простая функция такого рода уже рассматривалась —— это display_time( ). BOT еще не- Ёркоітько функций, которые можно поставить в соответствие структуре time: fadd hour() Fadd:minute() :hdd_second() _ Назначение этих функций очевидно. Каждая из них добавляет единицу к значению часа, [минуты или секунды соответственно. В листинге 24.2 процемонстрировано использование {этих трех функций для работы со структурами start_time и end_time.  & %Листинг 24.2. time2 . cpp — использование функций add_. . . для работы iiso структурами time   g;1: // Программа с использованием структуры time и дополнительных Ё 2: // функций приращения времени. “(З: // 4: #include <iostream.h> 5: Ё‘б: struct time { Ё 7: int hours; Ё 8: int minutes; : 9: int seconds; E10: }; :11:  112: void print_time(struct time tm); :33: void add_hour(struct time *tm); ~14: void add_minute(struct time *tm); 515: void add_second(struct time *tm);  L16: 17: int main(int argc, char* argv[]) Ё'18: { :19: struct time start_time = {7, 15, 20}; 320: struct time end_time = {10, 20, 30}; E21: 522: // Вывод первоначальных значений времени E23: cout << "\nStart time: "; E24: print_time(start_time); $25: cout << "\nEnd time: "; gig: print_time(end_time); , .  ;Пень 24-й. Классы и объекты С++ 597 
28: // добавление 1 часа, 1 минуты и 1 секунды к end_time 29: add _hour(&end_ time);      30: add _minute(&end_ time); 31: add _second(&end_ time); 32: 33: // Вывод окончательных значений времени 34: cout << "\n\nStart Time: "; 35: print _time(start_ time); 36: cout << "\nNew End Time: "; 37: print_time(end_time); 38: 39: return 0; 40: } 41: 42: // Вывод структуры time B форме ч:м:с 43: //---- 44: void print_time(struct time tm) 45: { 46: cout << tm.hours << ":" << tm.minutes << ”:" << tm.seconds; 47: } 48: 49: // добавление 1 к количеству часов 50: // 51: void add_hour(struct time *tm) 52: { 53: tm->hours += 1; 54: while (tm->hours >= 24 ) 55: { 56: tm->hours -= 24; 57: } 58: } 59: 60: // добавление 1 к количеству минут 61: // 62: void add_minute(struct time *tm) 63: { 64: tm->minutes += 1; 65: while (tm->minutes >= 60) 66: { 67: add_hour(tm); 68: tm->minutes -= 60; 69: } 70: } 71: 72: // добавление 1 к количеству секунд 73: // 74: void add_second(struct time *tm) 75: { 76: tm->seconds += 1; 77: while (tm->seconds >= 60)  598 Дополнительная неделя. Основные вопросы 
78: {   79: add minute(tm); 80: tm-;seconds -= 60; '81: } £2: } 5”—  г Start time: 7:15:20 `РЕЗЁШЬШЗШ End time: 10:20:30  , „\  ; Start Time: 7:15:20 L. New End Time: 11:21:31  Листинг 24.2 соцержит несколько существенных отличий от листинга 24.1. Первое из них состоит в том, что в программе появились три функции ’gdd_ . . .. Их прототипы объявлены в строках 13— 15, а сами функции определены в токах 51— 82. B строках 29,30 и 31 функция шаіп() передает адрес структуры end t_ime поочередно в каждую из трех функций add_ . . .. B результате к каждому из трех полей Ёфуктурьт добавляется единица. *7 В функциях add_. . . предусмотрена корректная работа со временем. Каждая из них со- ржит несколько дополнительных строк кола, в которых проверяется, не вышло ли значе- he соответствующего поля за его допустимый диапазон. Если это так, то старшее поле Эремени увеличивается на единицу. Например, 60 секунд составляют 1 минуту. Соответст- ;‘енно, когда поле секунд становится равным 60 после приращения, вызывается функция ёббдпіпийщ ), и поле минут получает приращение 1. Аналогичные операции выполняются ‘an секундами и часами.   &… `. » №,… В этом листинге поля структур инициализируются по-другому, чем в пре— “+ дыдущем. Ранее полям присваивались значения по отдельности, а теперь ‘; инициализация выполняется прямо в объявлении (см. строки 19 и 20). Та— т кие операции допустимы как в С, так и в С++.     ‚: I ; Если вам13неп9нятно как используются указатели в листинге 24. 2 повто- , mm {рите материал занятии 9 и 11 ; 0” Ё“ „@ ‚5,31 *   Юэункции- -члены  Ь. Ё На занятии 22 были рассмотрены некоторые характерные особенности объектно- IOpHeHTupoaaHHbe языков программирования. Одной из них является инкапсуляция— воз- 'можность создания замкнутых, самоцостаточных объектов. Рассмотрим пример со структу- Ёрой time. C помощью средств С++ ее можно сделать более автономной, ассоциировав функ- щии из листинга 24.2 непосредственно со структурным типом time. Точно так же, как струк- Ётура может с0держать поля данных (часы, минуты и секунды), она может включать и ;Функциональные поля, или функции-члены (которые также называются методами в некото- ;рых языках программирования). Эти функции являются частью структуры. Для примера в [листинге 24.3 приведена программа, аналогичная по своему назначению листингу 24.2, в ко- ;торой функции add_hour( ), add_minute( ), add_second() и display_time() являются члена-  NH структуры time.  119% 24-й. Классы и объекты С++ 599 
Листинг 24.3. time3 . cpp — использование функций-членов в структуре time   (13 -J (3\ L11 ::> (А) ") р.; о. |. о. о. о. о. |. ..  р.; р.; р.; р.; р.; р.; р.; р.; -J C7‘ ()1 ::> LA) ") F—‘ (:> ") .. о. о. |. о. о. .. о. о.  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:  600  // Программа с использованием функций—членов работы со временем //  #include <iostream.h>  struct time { // Поля данных: int hours; int minutes; int seconds;  // Функции—члены: void print_time(void); void add_hour(void); void add_minute(void); ' void add_second(void);  }:  int main(int argc, char* argv[]) { struct time start_time = {7, 15, 20}; struct time end_time = {10, 20, 30};  // Вывод первоначальных значений времени cout << “\nStart time: "; start_time.print_time(); cout << "\nEnd time: "; end_time.print_time();  // добавление 1 часа, 1 минуты и 1 секунды к end_time end_time.add_hour(); end_time.add_minute(); end_time.add_second();  // Вывод окончательных значений времени cout << “\n\nStart time: “; start_time.print_time(); cout << "\nNew end time: "; end_time.print_time();  return 0;  }  // Вывод структуры времени в форме ч:м:с // ————————————— void time::print_time(void)  {   cout << hours << ":" << minutes << "=" « seconds;  }  Дополнительная неделя. Основные вопросы 
50: // добавление 1 к количеству часов  51 // -------------------------------- 52: void time::add_hour(void) 53: { 54: hours += 1; 55: while (hours >= 24 ) '56: { ‚57: hours -= 24; 358: } 559: } S60: $61: // добавление 1 к количеству минут Ё62: // -------------------------------- E63: void time::add_minute(void) %64: { 65: minutes += 1; gfi6: while (minutes >= 60)  1573 { 8: add_hour(); minutes -= 60;  }   // добавление 1 к количеству секунд    void time::add_second(void) { seconds += 1; while (seconds >= 60)  {  add_minute(); seconds -= 60;  NHO‘OQ‘IO‘Lflbu-‘NHQ‘O „ „ „ „ „ „ „ „ „ „  “" ‘W'f я? ~'.- "А.Г -›-:- " * 7 {\“-дс .’ ’ —.'-.. :… №%^*Ъ'№… - ; № .W - т „ ` _ . - ..  Ы .. "'-‘  g _ Start time: 7:15:20 \ Peagnhmam End time: 10:20:30  Start time: 7:15:20 New end time: 11:21:31  Хотя эта программа существенно отличается от предыдущей, она выдает тот же конечный результат, Основное отличие состоит в том, что эта программа значи- льно ближе к объектно-ориентированному подходу. В строках 5—16 определяется структура 1ime. Строки 12—15 с0держат объявления функций add_. . . и функции print_time( ), причем определения структуры. Именно такое объявление делает эти функции членами уктуры time аналогично полям данных, объявленным в строках 7—9, В строках 20—24 выполняются те же операции, что и в предыдущей программе. строке 25 появляется существенное отличие, которое состоит в вызове функции-члена,_Как ожно вилеть в строке 25, функция-член вызывается таким же образом, каким выполняется   іень 24-й. Классы и объекты С++ 601 
обращение к полю данных. Напомним, что для обращения к полю структуры используется следующая конструкция:  имя_ структуры. имя_ поля  Обращение к функциональному полю, т.е. функции—члену, выполняется аналогично: имя_ структуры. имя_ функции_ члена( [список_аргумеитов] )  При сравнении этой и предыдущей программ возникает вопрос, почему в функции add_. . . и print_time( ) не передается указатель на структуру. Дело в том, что каждая функ- ция-член ассоциирована с конкретным экземпляром структуры точно так же, как и любое из полей данных. При вызове функции-члена обязательно указывается конкретная структура ти— па time, к которой эта функция принадлежит. Например, в строке 25 вызывается функция print_time( ), принадлежащая к структуре start_time, а в строке 27 —— та же функция-член, но уже относящаяся к структуре end_time. Поскольку при этом известно, из каких структур вызываются функции, нет необходимости передавать в них еще и указатель на структуру. Следует заметить, что внутри определений функций—членов не нужно указывать, какая из структур используется (см. строку 68). Компилятор подразумевает, что внутри функции-члена используются поля данных и функции-члены именно того экземпляра структуры, из которого она вызывается. Поэтому внутри тел функций-членов не нужно использовать имен структур ни перед функциями, ни перед полями данных.  Определение функций-членов  Функции-члены с одними и теми же именами вполне могут использоваться в разных структурных типах. Поэтому при определении функции-члена необходимо явно указывать, к какому структурному типу она относится. Как видно из листинга 24.3, для этой цели исполь- зуегся несколько другой формат заголовка, чем в языке С. Например, в строке 52 приведен заголовок функции add_hour( ). OH включает в себя имя структурного типа, за которым сле- дует двойное двоеточие, а затем уже имя самой функции. Благодаря этой синтаксической конструкции функция ассоциируется с конкретным структурным типом. Определение функции-члена имеет следующий общий синтаксис:  возвращаемое__ зна чение имя__ типа: :имя_фунхции( параметры )  { / / Тело функции  Помните, что имя структурного типа обязательно должно включаться в заголовок функ- ции-члена при ее определении, потому что одно и то же имя может использоваться для функ— ций—членов нескольких различных типов. Например, можно объявить структурный тип для хранения дня рождения друга или родственника и еще один структурный тип для хранения текущей даты. Каждый из них будет содержать поля даты day, month и year и вполне может включать также функцию—член с одним и тем же именем, например, display_date( ) для вы- вода даты на экран. Включение имени структурного типа непосредственно в определение функции-члена (как это делается в листинге 24.3) позволяет ассоциировать функцию с кон— кретной структурой.   приш“… Как и поля данных, функции-члены структур могут использоваться только в контексте конкретной структуры. Если функция-член вызывается не из  другой функции-члена той же структуры внутри ее определения, то вызов должен сопровождаться указанием имени структуры. Без имени структуры такой вызов будет ошибочным.    602 Дополнительная неделя. Основные вопросы 
Классьп  Классы уже фактически использовались в наших учебных примерах программ на С++, хо- тя об этом и не говорилось явно. Например, структура является частным случаем класса. Да- лее об этом будет сказано подробнее. В языке С++ имеется ключевое слово class. Как и слово struct, оно используется для создания сложных нестандартных типов данных. Классы объявляются почти так же, как дструктуры. В предыдущих примерах программ использовался тип time, организованный в :виде структуры. Его можно объявить и в виде класса:  Ёсіазз time { int hours; int minutes; int seconds;  'я №  „ ЁЁ}: %“ В результате такого объявления тип time является уже не структурным типом, а классом. ЁЕдинственное отличие от объявления структуры состоит в использовании ключевого слова “1 ass вместо struct. Кроме данных, класс может также содержать функции-члены подобно " .уктурам, использованным в листинге 24.3. В отличие от структур, при объявлении переменных, принадлежащих к конкретному ' ассу, — экземпляров класса— ключевое слово class не используется. Достаточно одного шь имени нового типа-класса. По сути, это эквивалентно объявлению структурного типа с омощью ключевого слова typedef. Чтобы объявить переменные start_time и end_time уктурного типа time, используются следующие конструкции:   truct time start_time; truct time end_time; г.” ЁЁ В то же время переменные, принадлежащие к новому классу, объявляются таким образом:  1  .jtime start_time; ime end_time;   ‚ Отличие очевидно. Обратите внимание, что при объявлении экземпляров класса создают- объекты. В языке С++ под объектом понимают переменную, объявленную с использова- f3 ием класса. Например, start_end и end_time являются объектами класса time. Помните, что хотя структуры являются частным случаем классов, между ними есть суще— аётвенные различия. Не осознав этих различий, трудно понять, насколько принципиально из- ё,}!!енится листинг 24.3, если в нем будут использоваться ютассы вместо структур. Принципи- Ёальное отличие состоит в стратегии доступа к данным, инкапсулированным в классах, по  ;сравнению с тем, как это делается в структурах.    "mm“. Классы используются для создания объектов. Объект определенного класса еще называется экземпляром класса. Таким образом, объявление переменной представляет собой создание экземпляра класса.  ‹     — “№ свет:/%.    ‘\  f правление доступом K данным в классах  Внутри класса —— в частности, структуры — можно разграничить право доступа к дан- ым из различных частей программы. Для этого используются три следующих ключевых лова С++:  %18НЬ24-Й. Классы и объекты С++ 603 
I public (открытые); I private (закрытые); I protected (защищенные).  По умолчанию все члены класса являются закрытыми (private). Это относится как к эле— ментам данных, так и к функциям-членам. В структурах все поля по умолчанию являются от- крытыми (public). Это существенное отличие между двумя разновидностями составных ти- пов данных.  Открытые данные  Если элемент данных из класса объявлен как public, он доступен не только в функциях- членах его собственного класса, но и в любом месте программы, где используется объект это- го класса. Пусть объявлена следующая структура:  struct name { string firstname; string lastname; string formatted_name();  Все ее три поля открыты по умолчанию. Это означает, что к полям лэюбого экземпляра этой структуры можно обращаться из любого модуля программы, в котором доступен данный экземпляр. Обращение выполняется стандартным образом: ння_ объекта . f i r s tname имя_объекта. lastname имя__объекта . f ormatted_name ( )  Здесь нмя_объекта— это имя переменной (объекта или экземпляра) структурного типа. Поля структуры открыты, поэтому обращение к ним разрешается везде, где досту- пен сам объект.  Закрытые данные  Итак, поля структур по умолчанию открыты, а члены классов — закрыты. Закрытые (private) элементы недоступны извне класса в произвольных местах программы —— на то они и закрытые. По определению, доступ к таким данным разрешен только функциям—членам то- го экземпляра класса, в котором они определены. Пусть объявлен следующий класс:  class name { string firstname; string lastname; string formatted_name();  Все элементы этого класса по умолчанию объявлены private. Обращение к полям firstname и lastname может выполняться только из функций-членов этого же класса, т.е. только внутри функции formatted_name( ). Поэтому следующий фрагмент кола содер- жит ошибку: class name { string firstname; string lastname;  604 Дополнительная неделя. Основные вопросы 
string formatted_name();  }: int main(int argc, char* argv[]) { name myName myName.firstname = "Bradley"; // Ошибка - это закрытое поле return 0; }  Ошибка возникла из-за того, что объект myName содержит лишь закрытые поля данных. Они доступны только внутри функций—членов этого же объекта.  g Старайтесь объявлять элементы данных в классах как private всегда, BIIBBIII , когда это возможно. Для доступа к закрытым полям данных в случае необходимости используются функции—члены  …. Ющ…мчщамм, …… ….и—Мш ...-„Мк  ?  {Защищенные данные „  % Модификатор protected служит для объявления так называемых защищенных полей дан— @ых. Этот способ доступа отличается и от public, и от private. Защищенные данные классов Будут рассмотрены позже, после более полробного знакомства с наследованием.  I, г 7  Разграничение доступа к данным классов  E ' По умолчанию все поля структур в С++ открыты, а поля классов -—— закрыты Что же де- ірать, если необходимо изменить права доступа к этим данным? Это делается довольно про— gm— внутри классов используются ключевые слова public, private, protected B листин- Fe 24.4 представлена программа из листинга 24.3, переработанная с использованием ключе—  рых слов public и private.  Листинг 24. 4. access. срр — определение, объявление и использование данных c правами доступа public и private   ‚;(-;:ТЩГ: ..  // Управление доступом к данным и функциям классов  2: #include <iostream.h> із: t4: class time { {5: %B: private: $7: // Поля данных: 58: int hours; 59: int minutes; 10: int seconds; 11: 12: public: ЁЗ: // Функции-члены: 4: void init( int h, int m, int 5); 15: void print_time(void);  Hem: 24-й. Классы и объекты С++ 605 
16: void add hour(void);    17: void add:minute(void); 18: void add_second(void); 19: }; 20: 21: int main(int argc, char* argv[]) 22: { 23: time start_time; 24: time end_time; 25: 26: start_time.init(7, 15, 20); 27: end_time.init(10, 20, 30); 28: 29: // Вывод первоначальных значений времени 30: cout << "\nStart Time: _"; 31: start_time.print_time(); 32: cout << "\nEnd Time: "; 33: end_time.print_time(); 34: 35: // добавление 1 часа, 1 минуты и 1 секунды к end_time 36: end_time.add_hour(); 37: end_time.add_minute(); 38: end_time.add_second(); 39: 40: // Вывод окончательных значений времени 41: cout << "\n\nstart time: ”; 42: start_time.print_time(); 43: cout << "\nNew end time: ”; 44: end_time.print_time(); 45: 46: return О; 47: } 48: 49: // Вывод структуры времени в форме ч:м:с 50: // 51: void time::print_time(void) 52: { 53: cout << hours << ":" << minutes << ":” << seconds; 54: } 55: 56: // добавление 1 к количеству часов 57: // 58: void time::add_hour(void) 59: { _ 60: hours += 1; 61: while (hours >= 24 ) 62: { 63: hours -= 24; 64: } 65: }  606 Дополнительная неделя. Основные вопросы 
67: // добавление 1 к количеству минут  68 // -------------------------------- 69: void time::add_minute(void) 70: { 71: minutes += 1; 72: while (minutes >= 60) 73: { 74: add_hour(); :75: minutes -= 60; ;76: } .177: } $78:   :79: // добавление 1 к количеству секунц  1 void time::add_second(void) 2 { 3 seconds += 1; 2&4 while (seconds >= 60) #153 { 5&6: add_minute(); 7- seconds -= 60; В } 9 } 0 1 // Инициализация полей данных   // ----- : void time::init(int h, int m, int s) ‘4: { hours = h; ’36: minutes = m;  seconds = s;   Start time: 7:15:20 End time: 10:20:30  Start time: 7:15:20 New end time: 11:21:31  B этой программе права доступа к членам класса time. регулируются c помощью рассмотренных выше модификаторов. В строке 6 c помощью ключевого слова  'и секунды — объявлены закрытыми, т.е. право доступа к ним имеют только функции—члены Ёцанного класса. Ключевое слово public B строке 12 открывает группу объявлений открытых &даннълх, а именно функций—членов init( ), display_time( ) и add_. . .. Ё Обратите внимание на новую функцию, появившуюся B классе time. Это функция init( ), Ёбъявленная B строке 14 и отвечающая за инициализацию закрытых элементов класса. Вклю- ' ение в класс специальной функции—члена B данном случае представляет единственный способ   ‘ ень 24—й. Классы и объекты С++ 607 
инициализации закрытых данных — в силу их статуса им нельзя присвоить значения в функ- ции main( ), KaK это делалось в листинге 24.3. В строках 26 и 27 вызывается функция init( ) для инициализации полей данных в объектах класса time. Из определения этой функции в строках 91—98 видно, что она просто помещает в закрытые поля класса значения часов, минут и секунд, переданные в нее как аргументы. Чтобы нагляднее представить себе действие модификаторов доступа, вставьте следую— щую строку в программу между строками 27 и 29:  time.hour = 5;  Компилятор сообщит об ошибке в этой строке. Причина ошибки заключается в том, что hour — закрытый член класса time, a закрытые данные доступны только функциям-членам в пределах данного класса.  Функции доступа к членам классов  Если данные в классе объявлены закрытыми, как же осуществляется доступ K ним извне класса? Например, в листинге 24.4 нет никакой возможности обратиться K значениям часов, минут или секунд в объектах типа time no отдельности. Таким образом, нельзя ни присвоить, ни получить значение ни одного из этих полей данных. Как же все-таки добраться до них? Ответ дан в названии этого раздела. Для доступа K данным используются функции-члены, специально включенные для этой цели в класс. Функция доступа K членам класса представляет собой открытую (public) функцию, един- ственное назначение которой состоит в том, чтобы присваивать значения какому-либо из за- крытых полей класса или извлекать значение из этого поля. Обычно такие функции имеют небольшой объем— всего несколько строк кода. В листинге 24.5 представлена структура time, B которую добавлены функции доступа K членам класса. Для сокращения объема из листинга удалены функции ad_. . . , использовавшиеся в предыдущих примерах.  Листинг 24.5. property. cpp — использование функций доступа к членам класса   1: // Использование функций доступа K членам класса 2: #include <iostream.h> 3: 4: class time { 5: 6: private: 7: int hours; 8: int minutes; 9: int seconds; 10: 11: public: 12: void init( int h, int m, int s); 13: void print_time(void); 14: 15: void set_hours( int h); 16: void set_minutes( int m ); 17: void set_seconds( int 8 ); 18:  608 Дополнительная неделя. Основные вопросы 
" << myTime.get_hours();   19: int get_hours( void ); 20: int get_minutes( void ); 21: int get_seconds( void ); 22: }; 23: 24: int main(int argc, char* argv[]) 25: { 26: time myTime; 27: 28: myTime.init(11, 43, 20); 29: 30: // вывод инициализированного времени 31: cout << "\nMy time is: "; 32: myTime.print_time(); 33: 34: // установка членов класса time no отдельности 35: cout << "\n\nResetting time to 3:12:30...\n"; 36: 37: myTime.set_hours( 3 ); 38: myTime.set_minutes(12); 39: myTime.set_seconds (30); 40: 41: // вывод членов класса по отдельности 42: cout << "\nThe hours are now: 43: cout << "\nThe minutes are now: " << myTime.get_minutes(); 44: cout << "\nThe seconds are now: " << myTime.get_seconds(); 45: 46: return 0; 47: } 48: 49: // Вывод значения времени в форме ч:м:с 50: // ------- - 51: void time::print_time(void) 52: { 53: cout << hours << ":" << minutes << ":" << seconds; 54: } 55: 56: // Инициализация членов класса 57: // ----------------------------- 58: void time::init(int h, int m, int s) 59: { 60: hours = h; 61: minutes = m; 62: seconds = s; 63: } 64: 65: // Функции доступа 66: // ----------------- 67: int time::get_hours() 68: {  День 24-й. Классы и объекты С++  609 
69: return hours;  70: } 71: int time::get_minutes() 72: { 73: return minutes; 74: } 75: int time::get_seconds() 76: { 77: return seconds; 78: } 79: void time::set_hours( int h ) 80: { 81: hours = h; 82: } 83: void time::set_minutes( int m ) 84: { 85: minutes = m; 86: } 87: void time::set_seconds( int s ) 88: { 89: seconds = s; 90: }   M ti i : 11:43:20 Pesunbmam Y me 3  Resetting time to 3:12:30...  The hours are now: 3 The minutes are now: 12 The seconds are now: 30  № В этом листинге отсутствуют некоторые из функций-членов класса time, исполь- зовавшиеся в предыдущих программах, но зато добавлены шесть функций дос- тупа. Первые три из них, объявленные в строках 15—17, предназначены для присваивания значений полям данных объекта. В строках 19—21 объявлены еще три функции, каждая из ко- торых возвращает значение одного из трех полей данных. Строки 67—90 содержат определе— ния этих функций. Возникает вопрос, зачем делать данные класса закрытыми, а затем писать специальные функции для доступа к ним. На первый взгляд это только загромождаег программу, и кажет- ся, что было бы намного экономичнее просто сделать все данные открытыми. Хотя функции доступа к членам классов и кажутся лишними, на самом деле они облегча- ют работу с классом в стратегическом плане, поскольку способствуют инкапсуляции данных класса и операций с ними. Благодаря этому становится возможным менять все, что угодно, внутри класса, не затрагивая при этом те части программы, которые используют его объекты. Рассмотрим один пример того, как применение функций доступа облегчает программирова- ние. До сих пор в классе time часы, минуты и секунды хранились в целочисленных полях ти- па int. Ho можно изменить объявления членов класса так, чтобы эти же данные помещались в переменных типа char. Целые числа типа char занимают диапазон от О до 255, так что их вполне хватит для хранения часов, минут и секунд, не превосходящих значения 60.  610 Дополнительная неделя. Основные вопросы 
Какие изменения необхоцимо внести в программу в связи с этим? Если доступ к цело- численным полям класса осуществляется непосредственно из программы, а сами поля объ— явлены как public, то придется внести исправления везде, где происходит обращение к объекту, заменив данные типа int на char. A BOT использование функций доступа позволя- ет по—прежнему передавать аргументы типа int. Достаточно внести исправления только в сами функции доступа, добавив преобразование из int B char и наоборот. Благодаря этому данные и функции класса остаются инкапсулированными, а во внешних программах ничего не меняется. Замена данных типа іп’с на char —— это„ конечно, чрезмерное упрощение. Рассмотрим другой пример. Пусть имеется класс для описания прямоугольника, в котором хранятся коор- динаты его верхнего правого угла, а также его ширина и длина. Автор класса решает изме- нить способ описания прямоугольника— хранить координаты правого верхнего и левого нижнего углов вместо ширины и длины. Не будь функций доступа, из-за таких модификаций пришлось бы переписывать все программы, использующие класс прямоугольника. А при на- личии функций доступа можно просто включить в них соответствующее преобразование, чтобы они возвращали длину и ширину.     Рекомендуется Не рекомендуется  Q!cnonbzyme ”%кпассы, а не структуры… % Не объявпяйте никакой элемент данных ‘ и предполагается что, объекты будут т Ё или функцию как public если в этом нет кпючать в себя функции—члены № ЁЁ E необходимости ” её“, … “ Используйте функции доступа к членам E ё ‚ _, „ 3 { ‚№3 »‘ " асса вместо трямого обращения K ним Ё “ > как „мг * , U a  éane, где @Т0@03М0№0 №№»; ‚$$—* j “ “Ё…     ., ! n ' ‹ ‹ : : . › 1-. wwmn my A ‹…  ча…”…—   Различие между классами и структурами  Стоит повторить, что хотя между структурами и классами имеется много общего, су— ществуют и заметные различия. Все поля данных в структурах по умолчанию являются от— крытыми, т.е. к ним возможно прямое обращение из любого места программы, где объяв- лен объект структурного типа. В классе по умолчанию все члены являются закрытыми. Это означает, что доступ к ним разрешен только внутри каждого конкретного объекта и только его функциям-членам.  Создание и уничтожение объектов  В предылущих примерах использовалась функция init( ), B задачу которой входила ини— циализация полей данных класса. В С++ имеется механизм, благодаря которому инициализа- цию объекта можно выполнять в момент его создания, т.е. объявления экземпляра класса. Кроме того, существует и механизм очистки объекта после того, как потребность в нем за- кончилась. Это делается автоматически в момент уничтожения объекта. В языке С++ эти ме— ханизмы реализованы в виде конструкторов и деструкторов.  День 24-й. Классы и объекты С++ 611 
Конструкторы  Конструктор— это функция—член класса, имеющая особые свойства и особое назначение. Имя конструктора всегда совпадает с именем класса. По умолчанию компилятор С++ создает неявный конструктор, который отвечает за создание объекта данного класса. Однако можно на- писать конструктор и самостоятельно. В этом случае автоматический конструктор теряет силу. Поскольку конструктор вызывается автоматически в момент создания экземпляра класса, он идеально подходит для выполнения необходимых инициализаций и распределения памяти, предназначенной для данных объекта.  Деструкторы  Деструктор также представляет собой специальную функцию-член класса. Его имя всегда состоит из знака тильды (") и следующего за ним имени класса. Например, деструктор класса time должен иметь имя 'time( ). Деструктор вызывается автоматически при уничтожении объ- екта. Объект уничтожается в тот момент, когда блок его видимости заканчивает выполнение. С помощью деструктора удобно выполнять очистку памяти. Если, например, при создании объекта какого-либо класса имеет место динамическое распределение памяти, то выделение па- мяти удобно осуществлять в конструкторе этою класса, а ее освобождение — в деструкторе.  ‘J  Использование конструкторов и деструкторов  В листинге 24.6 представлен простой пример использования конструктора и деструктора, включенных в состав некоторого класса. Чтобы продемонстрировать, когда именно вызыва- ются эти члены класса, на экран выводятся сообщения.  Листинг 24.6. const.cpp — пример использования конструктора и деструктора   1: // Использование конструкторов и деструкторов 2: #include <iostream.h> 3: 4: class value { 5: private: 6: int val; 7: public: 8: int get_value(); 9: value(int nbr = 99); 10: "value()7 11: }; 12: 13: int main(int argc, char* argv[]) 14: { 15: cout << "\nGetting ready to declare myValue..."; 16: 17: value myValue; 18: 19: cout <<"\nmyValue is now declared..."; 20:  612 Дополнительная неделя. Основные вопросы 
21: cout <<"\n\nPrinting myValue: " << myValue.get_value(); 22:    23: cout << "\n\nEnding program."; 24: 25: return 0; 26: } 27: 28: int value::get_value() 29: { 30: return val; 31: } 32: 33: // Конструктор класса value 34: // 35: value::value( int nbr ) 36: 37: // Do initializations. 38: val = nbr; 39: cout << "\n...In the constructor...\n"; 40: } 41: 42: // деструктор класса value 43: // 44: value::”value() 45: 46: // No real logic here for this class 47: cout << "\n...In the destructor...\n"; 48: }   Getting ready to declare myValue... Peaunbmam . . . In the constructor. . .  myValue is now declared... Printing myValue: 99  Ending program. ...In the destructor...  №3 В листинге 24.6 создается простой класс под именем value, единственным элемен- том данных которого является целочисленная переменная. В строках 9 и 10 объяв- лены соответственно конструктор и деструктор класса. Здесь используется еще одно специфи- ческое средство С++ — возможность задания параметров по умолчанию, — которое рас- сматривалось на занятии 23. В конструкторе value() используется именно такой параметр. Если конструктор вызвать без параметра nbr, то по умолчанию будет подставлено значение 99.   ("№ Помните, что значения параметров по умолчанию можно указывать для любых функций С++. Их использование в конструкторах — обычная прак- тика. Благодаря этому конструктор всегда имеет хотя бы какие—нибудь на- чальные значения для инициализации объекта.     День 24-й. Классы и объекты С++ 613 
В строках 35—40 находится определение конструктора. Эта функция вызывается всякий раз, когда создается экземпляр класса value. B данном случае конструктор просто присваива- ет элементу val значение, переданное в объект через параметр nbr (строка 38). Затем на эк- ран выводится сообщение, которое указывает, что в данный момент выполняется функция- конструктор. В строках 44—48 содержится определение функции-деструктора. Для класса value факти- чески нет никакой необходимости создавать деструктор. Того неявного деструктора, который создается компилятором автоматически, вполне достаточно. Единственное, для чего в эту программу все-таки включен явный деструктор, —— это вывод сообщения (строка 47) о том, что в данный момент выполняется функция-деструктор. Просмотрев сообщения программы, выводимые ею при выполнении, можно обнаружить одну интересную вещь: оказывается, де- структор выполняется после вывода сообщения о конце программы Ending program. Таким образом, деструктор действительно вызывается в момент уничтожения объекта— в данном случае перед самым выходом из функции шаіп( ).  Снова о перегрузке функций  На прошлом занятии бьша рассмотрена важная тема перегрузки функций. Это средство язы- ка С++ можно очень эффективно и разнообразно использовать в классах: в частности, при раз- работке их конструкторов. Рассмотрим конструктор для класса date, содержащего дату. Можно создать несколько разных конструкторов для инициализации объекта разными способами в за- висимости от формата начальных данных, который может быть, например, следующим.  I Три числа, обозначающих день, месяц и год, например, 21, 1, 2001. I Одна строка символов наподобие "21 January 2001". I Строка и два числа типа "January", 21, 2001.  Чтобы класс был как можно более гибким и полезным в применении, нужно иметь воз- можность вызывать его конструкторы и другие функции-члены самыми разнообразными спо- собами в зависимости от обстоятельств.  Реализация принципов ООП B С++  На занятии 22 много раз говорилось о том, что С++ является языком объектно- ориентированного программирования (ООП). Там же были рассмотрены три принципа, на которых основано ООП.  I Полиморфизм I Инкапсуляция I Наследование  В предыдущих разделах бьши приведены примеры реализации таких принципов, как по- лиморфизм и инкапсуляция, в С++. Одним из основных подходов к реализации полиморфиз- ма в С++ является перегрузка функций, т.е. создание семейства функций под одним именем, реагирующих на вызовы с различными параметрами. Перегрузка конструкторов дает воз- можность создавать объекты с несколькими различными наборами параметров для инициали- зации. Например, на предыдущем занятии рассматривался класс объектов типа “дата”. Такие объекты могут создаваться на основе данных, представленных в различных форматах,  614 Дополнительная неделя. Основные вопросы 
например, строки текста "December 25, 2001", трех чисел 25, 12, 2001 или же одной сгроки  и двух чисел " December", 25, 2001. Перегружая конструктор объекта, можно добиться того ‚ чтобы объекты создавались по любому из представленных сценариев.   ®,." Не менее распространенный вид полиморфизма — это такая организация ОБЪЕКТОВ. при котором они используются в разных местах как объекты разных типов. Но эта тема вых0дит за пределы нашей книги.     Ранее также рассматривалась инкапсуляция в С++. На предыдущем занятии обсужда- лось, как объединить в одном классе и данные, и функциональные возможности. С исполь— зованием такого класса можно создать объект, “умеющий” одновременно хранить данные и выполнять операции. Последним (но не по важности) принципом ООП является наследование. Введение в оди— ночное наследование будет дано в последующих разделах. Но перед этим необходимо рас- смотреть такую важную тему, как использование классов в качестве элементов данных внут- ри других классов.  Вложенные классы  При изучении структур и операторов циклов языка С было сказано о том, что эти синтак- сические конструкции могут быть вложенными. Вложенность —— это помещение оператора или блока внутри аналогичного оператора или блока. Как и все средства языка С, вложен- ность используется в С++. В конечном счете, класс есть просто разновидность сложного структурного типа данных. Поскольку внутри класса можно использовать любые структуры данных, очевидно, должна существовать возможность включать объекты одних классов в другие классы в качестве их членов. Такая возможность действительно существует. Рассмотрим класс, в котором хранятся координаты точки:  class point  { private: int x; int y; public: int get_x(); int get_Y(); void set_x(int val); void set_y(int val); Point(); point(int valx, int valy); };  Реализация этого класса будет предложена в конце занятия как упражнение. Видно, что в классе хранятся координаты x и у точки на плоскости. Он также содержит функции доступа для получения и установки значений этих координат. Наконец, в классе имеется два конст- руктора —— один без параметров, и второй, принимающий два значения координат. Если необходимо создать класс для описания отрезка прямой линии, объекты класса point можно использовать в нем в качестве полей данных. Класс line объявляется следую— щим образом:  День 24-й. Классы и объекты С++ 615 
class line  { private: point start; point end; public: point get_start(); point get_end(); void set_start(point val); void set_end(point val); Point( ): }:  Как видно, в этом классе объекты типа point используются точно так же, как в классе point целочисленные координаты. Линия описывается начальной и конечной точкой. Поэтому класс line включает два объекта point. В— классе имеются функции доступа для получения и установ- ки этих точек. Объекты типа point используются точно так же, как любые другие поля данных.  Обращение K вложенным классам  Доступ K объекту класса, вложенного в другой класс, осуществляется так же, как и K вло- женной структуре. Следует помнить, что обращение выполняется по имени объекта данных, а не типа. Например, чтобы обратиться K координате x начальной точки объекта 1іпе1, запишем  line1.start.x  Координату y конечной точки того же объекта можно получить следующим обращением: line1.end.y  Наследование в С++  Вложенные классы — это далеко не самое мощное средство С++ для работы с классами. Гораздо большие возможности предлагает реализация третьего принципа ООН в этом язы- ке — наследования. Наследование —-— это возможность создания новых классов на основе существующих. Рас- смотрим пример. Мир населен людьми. Любого человека в мире можно описать набором ха— рактеристик, который может включать следующие данные:  I имя и фамилию; I возраст; I страну проживания; I другие дополнительные сведения.  Теперь возьмем работника фирмы. Не все люди в мире работают в фирмах, однако все, кто работает в них, являются людьми. Работник фирмы обладает всеми перечисленными вы- ше характеристиками человека, но при этом у него есть и дополнительные “свойства”:  I название фирмы, где он работает; I размер заработной платы; I другие характеристики.  616 Дополнительная неделя. Основные вопросы 
Рассмотрим студента. Студент — это не работник фирмы, но тоже живой человек. У него также есть все описанные выше характеристики, плюс некоторые дополнительные:  I номер студенческого билета; I вуз и факультет, где он учится; I другие характеристики.              В терминах программирования говорится, что “работник” и “студент” наследуют свойства “человека”. На рис. 24.1 пока- человек заны взаимосвязи между этими тремя классами данных. Создавая классы для описания людей вообше, работников и м и студентов в зов можно сделать несколько абочих ф p _, у ’ p работник студент предположении. I “Человек” считается базовым классом. Рис. 24.1. Взаимосвязи меж- I “Работник” и “студент” являются подклассами. ду объектами человек ‹  “ст дент“ и “ аботник" I Между классами “работник” и “студент” нет взаимосвя- у р  зи, хотя оба происходят от Одного базового класса.      @ Базовый класс — это класс, который наследуется другим классом.     (№ Подкласс — это класс, который наследует свойства другого класса.   Построение базового наследуемого класса  Лучше всего прочувствовать мощь такого средства ООП. как наследование, на примере. Прежде чем организовывать наследование, необходимо построить базовый класс, от которо- го будут наследоваться свойства. Базовый класс ——- это самый обычный класс, и объявляется он стандартным образом. В этом примере базовым классом будет person (“человек”). Он объявляется в листинге 24.7. Там же приведена небольшая программа для демонстрации это- го класса в действии.  Листинг 24.7. base.cpp — класс “человек” для использования в качестве базового класса   1: #іпсіиое <iostream.h> 2: #include <string.h> 3: 4: #define MAX_LEN 81 5: 6: class person { 7: protected: 8: char fname[MAX_LEN]; 9: char lname[MAX_LEN]; 10: int age; 11: public: 12: void set_fname(char {р[} ) { strcpy(fname, fn); }; 13: void set_lname(char ln[] ) { strcpy(lname, 1n); }:  День 24-й. Классы и объекты С++ 617 
14: void set_age( int a ) { age = a : }?  15: char *get_name(char *fullname); 16: int get_age( void ) { return age; }; 17: person(char fn[] = "blank", char ln[] = "blank"); 18: }; 19: 20: person::person( char fn[], char ln[] ) 21: { 22: strcpy(fname, fn); 23: strcpy(lname, ln); 24: age = -1; 25: } 26: 27: char *person::get_name(char fullname[]) 28: { 29: strcpy(fullname, fname); 30: strcat(fullname, " “); 31: strcat(fullname, lname); 32: 33: return fullname; 34: } 35: 36: int main(int argc, char* argv[]) 37: { 38: char full[MAX_LEN + MAX_LEN]; 39: 40: person brad("Bradley", "Jones"); 41: brad.set_age(21); 42: 43: person blank; 44: 45: cout << "\nPerson brad: " << brad.get_name(fu11); 46: cout << "\n age: “ << brad.get_age(); 47: 48: cout << "\nPerson blank: " << blank.get_name(full); 49: cout << "\n age: “ << blank.get_age(); 50: cout << "\n"; 51: 52: return 0; 53: }   „ Person brad: Bradley Jones Pflflflflbflafll age: 21  Person blank: blank blank age: -1  B листинге 24.7 представлен относительно простой класс под именем person. ' ' Кроме него, листинг содержит функцию main( ) в строках 36—53, в которой соз—  дается два объекта класса person. B строке 40 объявляется объект brad, инициализируемый именем и фамилией. В следующей строке устанавливается возраст объекта brad, равный  618 Дополнительная неделя. Основные вопросы 
21 году. (Увы, в отношении себя автор выдал желаемое за действительное.) В строке 43 соз- дается второй объект с именем blank. Для его инициализации в конструкторе используются значения по умолчанию. Программа выводит на экран данные, помещенные в объекты при их инициализации. В определении класса, находящемся в строках 6—34, можно заметить некоторые нововве- дения по сравнению с предыдущими примерами. Код некоторых из открытых функций класса person помещен непосредственно после их объявлений. Например, в строке 12 находится код функции set_fname( ), состоящий из вызова функции копирования строк. Такое определение функции эквивалентно ее объявлению встраиваемой (inline). Если функция очень короткая, бывает проще поместить ее текст прямо в объявление класса. Если же функция сравнительно длинная — например, get__name( ) в строке 15, —— то ее код лучше вынести за пределы объяв- ления класса. Определение этой функции находится в строках 27—34. Большая часть остального кода основана на уже изученном материале. B строке 2 к про— грамме подключается заголовочный файл string.h. Это необходимо для работы с функция— ми копирования и сцепления строк, которые используются внутри класса. В строке 4 опреде— лена константа, ограничивающая максимальную длину имен.   IIIIIIIII Следует заметить, что в этой программе полностью отсутствует проверка ошибок. Например, ничто не мешает имени стать длиннее 81 символа. Код для контроля ошибок не включен в листинг 24.7, чтобы не загромождать его.     Моцификатор доступа protected  Самое важное новшество, которое следует отметить в листинге 24.7, находится в стро- ке 7. Это ключевое слово protected там, где следовало бы ожидать модификатора private. Как уже говорилось, при наличии ключевого слова private внешние модули программы не могут получить доступ к закрытым данным класса. Однако при использовании наследования бывает необходимо, чтобы к этим закрытым данным можно было обращаться из классов, на- следующих базовый. Именно для этого используется ключевое слово protected. Этот моди- фикатор разрешает доступ к данным только из самого класса и его наследников.  Наследование базового класса  Теперь у нас есть базовый класс, и можно заняться наследованием от него. В листин— ге 24.8 представлен класс employee (“работник”), являющийся наследником или подклассом созданного ранее класса person.   (Iguana: Для удобства в листинге 24.8 выделены жирным шрифтом фрагменты, добавленные по сравнению с листингом 24.7.     Листинг 24.8. employee . cpp — наследование базового класса   // демонстрация наследования #include <iostream.h> #include <string.h>  (п.№-ЫЫЫ 00 00 00 со ..  #define MAX_LEN 81  День 24-й. Классы и объекты С++ 619 
620  class person { protected: char fname[MAx_LEN]; char lname[MAx_LEN]; int age; public: void set_fname(char fn[] ) { strcpy(fname, fn); }; void set_lname(char ln[] ) { strcpy(lname, ln); }; void set_age( int a ) { age = a ; }; char *get_name(char *fullname); int get_age( void ) { return age; }; person(char fn[] = "blank", char ln[] = "blank");  }:  class employee : public person { protected: long salary; public: void set_salary(long sal) { salary = sal; }; long get_salary() { return salary; }; employee(char fn[] = 'eblank', char ln[] = 'eblank');  }; person::person( char fn[], char ln[] ) { strcpy(fname, fn); strcpy{lname, ln); age = -1; } char *person::get_name(char fullname[]) { strcpy(fullname, fname); strcat(fullname, “ "); strcat(fullname, lname); return fullname; }  employee::employee( char fn[], char ln[] ) : person(fn, ln)  { }  salary = О;  int main(int argc, char* argv[]) { char full[MAx_LEN + MAX_LEN];  person brad("Bradley", "Jones");  Дополнительная неделя. Основные вопросы 
57: brad.set_age(21);  58: 59: person blank; 60: 61: cout << "\nPerson brad: “ << brad.get name(full); 62: cout << “\n age: " << brad.get:age(); 63: 64: cout << “\nPerson blank: " << blank.get_name(full); 65: cout << "\n age: " << blank.get_age(); 66: cout << “\n"; 67: 68: employee kyle( “Kyle“, "Rinne'I ); 69: Ку1е.зей_за1агу( 50000 ); 70: kyle.set_age(32); 71: 72: cout << “\nEmployee kyle: ' << kyle.get_name(full); 73: cout << “\n age: ' << kyle.get~age(); 74: cout << “\n salary: ' « kyle.get_salary(); 75: cout << '\n\n'; 76: 77: return 0; 78: }   Person brad: Bradley Jones Peaunbmam age: 21  Person blank: blank blank age: -1  Employee kyle: Kyle Rinne age: 32 salary: 50000  Многое в этом листинге повторяет предыдущий. Класс person объявлен точно так же, как в листинге 24.7. Новые фрагменты отмечены полужирным шрифтом для удобства изучения. В строке 21 встречается первое серьезное новшество — объявление класса employee. Об- ратите внимание на заголовок: с1азз employee : public person { Как и раньше, объявление класса employee начинается с ключевого слова class. Затем идет имя класса, а дальше новый элемент — двоеточие и следующий за ним текст. Двоеточие указывает, что объявляется подкласс другого класса. После него задается имя базового класса, т.е. person. Наличие ключевого слова public дает право классу employee 06pa- щаться к данным объектов типа person. Если необходимо определить подкласс student, то используется следующий заголовок:  class student : public person {  Наследование классов можно продолжить. Например, объявленный ниже класс temp__employee является подклассом по отношению к employee:  class temp_employee : public employee {  День 24-й. Классы и объекты С++ 621 
В этом случае уже employee представляет собой базовый класс, а temp_employee наследу- ет его. Поскольку класс employee B свою очередь наследует person, класс temp_employee также имеет доступ K данным и функциям класса person. Объявление класса employee B листинге 24.8 сравнительно короткое, потому что в нем уже содержится все, что есть в классе person. B объявление подкласса добавляются только те данные и методы, которых нет в базовом классе. В данном случае это поле данных salary для размера заработной платы, две функции доступа K этому полю и конструктор.  Конструктор подкласса  В строках 46—49 последнего примера находится конструктор класса employee. CTOHT об- ратить внимание на его отличия от конструктора person. Заголовок конструктора имеет та- кой же вид, как и раньше:  employee::employee( char fn[], char ln[])  Конструктор employee принимает в качестве аргументов строки имени и фамилии. В строке 27 задано значение по умолчанию — если имя или фамилия опущены, вместо них используется строка eblank. При создании объекта employee фактически сначала создается объект person, a затем уже employee. При вызове конструктора класса employee вначале вызывается конструктор базо- вого класса person. Только после его завершения вызывается конструктор подкласса. Объект не считается созданным до тех пор, пока не будут выполнены оба конструктора по порядку. Позже это правило будет проиллюстрировано в листинге 24.9. В листинге 24.8 необходимо передать аргументы, полученные конструктором employee, дальше в конструктор person. Для этого вызов конструктора базового класса добавляется в конец строки заголовка, как показано в строке 46. Благодаря этому имя и фамилия для созда- ваемого объекта будут переданы далее в конструктор класса person. B строке 48 конструктор employee инициализирует поле salary значением 0. После за- вершения этой операции имя и фамилия устанавливаются равными тем значениям, которые переданы в конструктор employee через аргументы, или eblank, если аргументы не указаны. За эту операцию отвечает конструктор person. Возраст инициализируется значением -1, а зарплата —— значением 0.  Работа с подклассом  В строке 68 листинга 24.8 создается объект типа employee с именем kyle. B конст- руктор класса передаются строки "Ку1е" и "Rinne". B строке 69 вызывается функция set_salary( )‚член класса employee, a B строке 70 ———- функция set_age( ). Несмотря на то, что set_age() является членом класса person, она может вызываться из объекта типа employee как его собственная в силу наследования по типу public. B строках 72—74 с помощью функ- ций доступа на экран выводятся все необходимые данные из созданного объекта employee.  Еще о конструкторах и деструкторах  Ранее уже говорилось, что при вызове конструктора подкласса обязательно вызывается конструктор его базового класса. Аналогичное верно и в отношении деструкторов, только в обратном порядке — сначала вызывается деструктор подкласса, а потом уже базового класса. Возвращаясь K примеру класса employee, при уничтожении его объекта вначале выполня— ется деструктор employee, a затем — деструктор person. Для иллюстрации этого правила  622 Дополнительная неделя. Основные вопросы 
в листинге 24.9 печатаются сообщения, которые показывают порядок выполнения деструкто- ров. Изучите код этой программы и выдаваемые ею результаты.  Листинг 24.9. order. cpp -— порядок выполнения конструкторов и деструкторов при наследовании классов   #include <iostream.h>  class base {  protected: int Bval; public:  void set_Bval(int x) { Bval = x; }; int get_Bval(int x) { return Bval; };  фчфшьшюн II II II || II II II ||  9: base( int x = —99 ); 10: "base(); // деструктор 11: }; 12:  13: class sub : public base { 14: protected: 15: int Sval; 16: public: 17: void set_Sval( int x ) { Sval = x; };  18: int get_Sval() { return Sval; }; 19: sub( int x = —22 ); 20: ”зиЬ(); // деструктор 21: }; 22: 23: base::base( int x ) 24: { 25: Bval = x; 26: cout << “\n B >> in base class constructor..."; 27: } 28: 29: base::"base() 30: 31: cout << "\n B >> ...in base class destructor"; 32: } 33: 34: sub::sub( int x ) : base ( —1 ) 35: { 36: cout << “\n 5 >> in sub class constructor..."; 37: } 38: 39: зиЬ::“зиЬ() 40: { 41: cout << “\n 5 >> ...in sub class destructor"; 42: } 43: 44: int main(int argc, char* argv[]) 45: {  День 24-й. Классы и объекты С++ 623 
46: cout << "\n . >> Instantiating a sub class...\n";  47: 48: sub subl; 49: 50: cout << "\n . >> ...sub class instantiated...", 51: cout << "\n . >> ...ending program...\n"; 52: 53: return 0; 54: }   . >> Instantiatin a sub class... Резцпъташ g  B >> in base class constructor... 8 >> in sub class constructor... . >> ...sub class instantiated... . >> ...ending program...  S >> ...in sub class destructor B >> ...in base class destructor  ”8%”? $:??? “ЙМЁЁЧЁ kvgéfidfiflv . ,? ванна… включённые 24,3+ “может ё‘  :  2.42%.. gm.    г."/№33 final-“n  ',.§;P» нём-а №№ "13°.” We 09% _л ":, a ` „ E Xv “ТВ‘  Дополнительные возможности С++  На двух последних занятиях было изучено довольно много материала об объектно- ориентированном программировании вообще и С++ в частности. Однако это всего лишь вер— хушка того айсберга, который Представляет собой этот язык. В С++ имеется еще множество средств и возможностей. Некоторые из них перечислены ниже: l множественное наследование; [паблоньтклассов; RTTI (Run-Time Type Information) -— задание типа в ходе выполнения программы; дружественные функции; закрытое наследование (не по типу public);  замещение функций вместо перегрузки;  переопределение знаков операций.  Каждое из этих средств делает язык С++ еще гибче и мощнее, при этом улучшая пере- носимость объектно—ориентированного кода между программными проектами. Поэтому все перечисленные возможности широко используются в реальных прикладных програм- мах. К сожалению, здесь нет возможности даже вкратце рассказать o них. Однако расска- занного уже должно хватить читателю для того, чтобы приступить к самообразованию в области С++.  624 Дополнительная неделя. Основные вопросы 
Что читать дальше  То, что изложено в этой книге, — это всего лишь основы С++. Для полного освоения язы- ка рекомендуется приобрести книгу, целиком посвященную С++ и всем его возможностям. Это, например, книга Дж. Либерти “Освой самостоятельно С++ за 21 день”. Существуют и более сложные пособия для программистов профессионального уровня.  „Резюме  На этом занятии были рассмотрены черты и средства С++, делающие его объектно- Ё ориентированным языком. В С++ полностью реализованы все принципы ООН. В этом языке F есть возможность объявлять классы и создавать с их помощью объекты. Объекты включают в себя элементы данных и функции-члены, или методы. Для создания и уничтожения объектов к. в них включаются специальные функции — конструкторы и деструкторы. { Понимание материала, рассмотренного на сегоцняшнем занятии, имеет решающее значение *для освоения С++. Объект является центральным пунктом всего объектно-ориентированного Ёпрограммирования. Объекты инкапсулируют данные и функциональные возможности. С по- ; мощью перегрузки функций-членов можно делать объекты более полиморфными. , K Объекты классов могут использоваться в качестве элементов данных других классов. Классы могут нахоциться в отношениях наследования, представляя собой базовые классы и подклассы. В этом случае конструкторы и деструкторы выполняются в специфическом по- . рядке для корректного создания и уничтожения объектов произв0дных классов. Несмотря на 3 значительный объем изученного на этом занятии материала, читателю следует понимать, что на этом освоение С++ еще только начинается. Следующее занятие будет посвящено роцственной теме — введению в объектно-ориенти- ірованный язык программирования Java.  т  ! , ‚ т. „ …   WWH’VW {-5. „„  ”'58.;  час-тт  ‹  ‚Вопросы и ответы  9 F %  Если структуры обладают теми же возможностями, что и классы, почему бы не ис- & пользовать только классы или только структуры? . Вообще говоря, структуры используются тогда, когда объект содержит только поля дан- Ё? ных. Если же в составе объекта должны инкапсулироваться и функциональные возможности, :? 'ю рекомендуется объявить класс, а не структуру.  g: Можно ли включать функции-члены в состав структур в программах на языке С? 3?! Нет, нельзя. В С++ структура является частным случаем класса. Функции-члены мо- Ё‘, гут входить в состав класса, поэтому и структуры тоже могут их содержать. В С нет по- ;нятия класса, поэтому ни объекты, ни структуры с функциями-членами в этом языке … создавать нельзя.  %? Можно ли написать большую полнофункциональную программу на С++, применяя Е, только то, что было рассказано на двух вводных занятиях по этому языку? Ё До некоторой степени это так. С использованием изученных средств С++ можно написать аработающую программу. Однако здесь были рассмотрены только самые основы языка. При ;попытке написать большую программу или моцифицировать существующую может оказать- ‘3 ся, что она использует намного более сложные средства, чем изученные в этой книге.    !ень 24-й. Классы и объекты С++ 625 
Может ли класс наследовать несколько классов? Да, может. В языке С++ допускается множественное наследование от нескольких базовых классов. Подкласс может получать свои свойства от нескольких базовых родительских клас— сов, так же как человек наследует черты матери и отца. Более подробное изложение этой те- мы выходит за рамки книги.  Коллоквиум  В этом коллоквиуме предлагаются контрольные вопросы на закрепление изученного ма- териала и ряд упражнений для развития практических навыков. Ответы к вопросам и упраж- нениям можно найти в приложении Е.  Контрольные вопрбсы  . В чем разница между структурой и классом в С++? Можно ли присвоить классу значение?  Что такое объект?  В чем суть создания экземпляра класса? Какие принципы ООП реализуются с помощью классов?  ampule—  Какие части или модули программы имеют доступ к данным класса, объявленным как private?  7. Из каких частей программы возможен доступ к данным класса, объявленным как public? 8. Когда выполняется конструктор класса? 9. Когда выполняется деструктор класса?  10. Чем отличается использование объекта класса внутри другого класса в качестве поля от использования элемента другого типа?  11. Пусть класс guppy наследует класс fish. B этом случае guppy является: а) базовым классом 6) подклассом в) ни базовым классом, ни подклассом 12. Пусть класс guppy наследует класс fish. B этом случае fish является: а) базовым классом 6) подклассом в) ни базовым классом, ни подклассом 13. Какой заголовок должно иметь объявление класса guppy из вопросов ll и 12? 14. Какой конструктор выполняется раньше: базового класса или подкласса? 15. Какой деструктор выполняется раньше: базового класса или подкласса?  626 Дополнительная неделя. Основные вопросы 
Упражнения  1.  2.  Создайте класс point для хранения объекта “точка”. Класс должен содержать закрытые (private) числовые элементы данных x и у, а также функции-члены для доступа к ним.  Создайте класс circle для хранения объекта “окружность”. Переменными-членами класса  должны быть координаты центра окружности и ее радиус. Данные о координатах центра должны храниться в объекте типа point из упражнения 1.  Напишите конструктор для класса circle из упражнения 2. Координаты центра по умол- чанию должны быть равны нулю, а радиус — единице.  Добавьте в класс circle открытую функцию-член, возвращающую длину окружности.  Напишите реализацию (полный текст) класса point, объявленного в разделе о вложенных классах.  Напишите код класса line, обсуждавшегося в разделе о вложенных классах.  Перепишите код класса line так, чтобы он наследовал класс point и расширял его путем добавления переменной, описывающей длину отрезка.  Измените листинг 24.8 так, чтобы в нем фигурировал класс student вместо employee co всеми полагающимися атрибутами.  День 24-й. Классы и объекты С++ 627 
‚ЧМ—‚м…ьщчттм—цьпц”,    Основы языка Java  Язык Java был создан для того, чтобы избежать многих неудобств и недостатков, имеющихся в С и С++, не только сохранив при этом все их достоинства, но и обогатив но— выми возможностями и подхоцами. Для программистов, начинавших с изучения С, язык Java поначалу кажется странным, но, тем не менее, многие предпочитают именно его всем другим. Это занятие открывает небольшой вводный курс Java. Будут рассмотрены сле— дующие темы.  l Основные компоненты программы на Java Ключевые слова и идентификаторы Java Типы данных для хранения числовой и строковой информации Ввод и вывод текста  Операции в Java и операторы для управления хоцом программы  Программы на языке Java  Как говорилось на занятии 22, программы на Java бывают двух типов. Один из них -—— это аплеты. Аплеты представляют собой программы небольшого или среднего разМера, разрабатываемые с целью распространения по World Wide Web и выполняемые, как прави— ло, на \УеЬ-страницах. Приложения —- это программы, выполняемые сами по себе в среде операционной системы точно так же, как изученные в этой книге программы на С. В на— стоящее время язык Java находит самые разнообразные применения. В первую очередь наибольшее внимание привлекают к себе аплеты Java no причине всеобщей популярности World Wide Web. C использованием Java можно также разрабатывать компоненты, сервер- ные приложения Web, хорошо переносимые сложные объекты данных. Что самое важное, с такой же легкостью на Java можно писать и самостоятельные приложения. В этом крат- ком вводном курсе Java внимание будет сосредоточено как раз на автономных приложени— ях, поскольку это позволяет сравнить достоинства языка с уже рассмотренными ранее воз— можностями С и С++. 
Составные части программы на Java  B самом простом варианте программа на языке Java состоит из двух частей, помещенных одна внутрь другой. Как и почти любой объект Java, программа является классом и определя- ется следующим образом:  public class ProgramName  { }  Код, образующий программу, помещается между открывающей и закрывающей фигур- ными скобками. Весь файл исходного кода сохраняется на диске с расширением java. На- пример, программа с именем ProgramName хранилась бы в файле ProgramName. java. Вторая необходимая часть программы на Java— это функция main. При запуске программы на выполнение начинает выполняться именно функция main точно так же, как это делается в С и С++. Она имеет следующую форму: public class ProgramName { public static void main (String args[])  { }  Заметьте, что функция main принимает аргумент args[ ]. Это способ передачи аргументов командной строки в программу, используемый в Java. B отличие от С здесь не нужно указы- вать количество переданных аргументов с помощью параметра argc. Вместо этого параметра используется величина args . length.  Импортирование классов  Практически любая программа на Java, 3a исключением разве что самых примитивных, использует оператор import для подключения внешних классов. Этим способом обеспечива- ется доетуп как к стандартным классам, вх0дящим в состав Java, так и к любым другим, раз- работанным самостоятельно. Операторы import располагаются в начале файла исходного ко- да до определений классов. Импортировать классы можно по одному, как в следующем при- мере, где импортируется класс someClass из пакета my.package:  import my.package.someClass;  А можно воспользоваться символом подстановки ДЛЯ импортирования сразу всех классов из пакета:  import java.io.*; Этот оператор импортирует все классы из пакета java.io. Имена стандартных пакетов, вхолящих в комплект поставки языка Java, начинаются с java. Если программа попытается  обратиться к классу, не импортировав его, компилятор сообщит об ошибке. В программе на Java можно использовать столько операторов import, сколько нужно.   “Mill: Пакет представляет собой файл, в котором хранятся классы. Использова- ние символов подстановки позволяет импортировать все классы из такого файла.     День 25-й. Основы языка Java 629 
Методы  Метод в языке Java— это фактически то же самое, что функция в С или С++. Это название используется по той причине, что Java— целиком и полностью объектно— ориентированный язык, и согласно его подходу объекты содержат методы, а не функ- ции. Пример метода уже рассматривался в конце занятия 22 в программе Hello, World. B языке Java большая часть функциональных возможностей программ инкапсулируется в методах.  Комментарии  Никакой из языков программирования не может обойтись без возможности добавлять комментарии в исходный текст программ. В Java существует три стиля комментирования. Первый из них следует синтаксису языка С —— текст, помещенный между парами символов / * и */, считается комментарием и игнорируется компилятором:  /* Все это - один сплошной и очень длинный комментарий */  Второй стиль следует синтаксису языка С++. Все, что помещено после символов //‚ вплоть до конца строки считается комментарием:  // Это комментарий. X = S; //Это тоже комментарий.  Третий стиль комментариев Java используется для автоматического генерирования доку— ментации. Его синтаксис похож на комментарии С с добавлением лишней звездочки:  /** Этот комментарий будет включен в документацию класса, созданную программой Javadoc. */  Такие комментарии предназначены для создания документации на программу с помощью утилиты Javadoc. Работа с этой утилитой в нашей книге не рассматривается, но о существо- вании таких комментариев полезно знать на случай, если они попадутся в исходном коде ка— кой-либо программы.  Ключевые слова Java  Как и во всех языках программирования, в Java есть набор ключевых слов, составляю- щих ядро языка. Эти слова нельзя использовать в качестве идентификаторов, например, имен переменных. В Java больше ключевых слов, чем в С, хотя и не очень много - около пятидесяти. Некоторые из них пока что не используются, но зарезервированы для возмож— ного будущего применения. В табл. 25.1 перечислены ключевые слова языка Java, crpyn- пированные по категориям. До сих пор смысл и назначение большинства этих слов не рас— сматривались, но в течение этого и последующих двух занятий положение будет отчасти исправлено.  630 Дополнительная неделя. Основные вопросы 
Таблица 25. 1 . Ключевые слова языка Java   Категория  Ключевое слово   Встроенные ТИПЫ данных  Компоненты выражений  Операторы выбора и ветвления  Операторы циклов  Другие операторы  Модификаторы объявлений  Ключевые слова классов и методов  Слова, зарезервированные для будущего использования  Ьоо1еап byte char double float int long short strictfp widefp void  new this super  break case default else if switch continue do for while  catch finally return synchronized throw try  abstract final private protected public static  class extends implements import instanceof interface native package throws transient volatile  const goto   День 25—й. Основы языка Java  631 
Идентификаторы B Java  Идентификатор -— это имя, которое присваивается переменной, классу или любому дру- гому объекту программы. Правила языка Java B отношении идентификаторов отличаются большой гибкостью. Имена могут иметь любую длину и содержать буквы, знак подчеркива— ния, знак доллара, цифры от О до 9 (однако первый символ идентификатора не может бьггь цифрой). П0д буквами в Java подразумеваются не только 26 букв латинского алфавита, но и все те тысячи букв других алфавитов, которые представлены в символьном наборе Unicode. Точнее говоря, в идентификаторе Java можно использовать все символы Unicode с шестна- дцатеричными кодами больше 00С0. Вот несколько примеров:  interestRate $_9 ^ 91’1 Как и в любом другом языке, ключевые слова Java нельзя использовать в качестве иден- тификаторов. Хорошим стилем программирования считается применение описательных имен, которые отражают предназначение объектов данных. Первый из приведенных выше примеров идентификаторов следует хорошему стилю, а остальные ——— нет, хотя и допускаются синтаксисом языка. Таких имен желательно избегать.  gm, …” ”№  ‘ В Идентификаторах и ключевыхосповах `1а\‘:а различается/“регистр сима ‹ linIIIIII _лов, также как и в С/С++: Поэтому необходимо aKKypamfi‘HH HaH клавиатуре ВыраБОтаны; определенные правила для THMeH :qufibgmawfis J следуя которым можно избежатв грубых ошибок,»;Эти правиііа‘; „, H Haza- ;   ;“телвны ,однако их рекомендуется придерживаться J %: J :, “‚,ЁгёЁЁ J; Имена классов H интерфейсов донжны быть сушествитёль " вооочетаниями, в которых все слова начинаются о“ больших букв напр мер, BarGraph или AddressList. Ите№исьп иногда имеют:“ Ё‚например, Sortable Hnwuallable "іі % ‘AWHHHHJM’ ^ 53.5w „, ОбЁЁЁеЁкгы и имена переменных должны быть суще ительньіми ‘Ё ‘ Ё восочетаниями, первое слово которых ,начинается Wage ь№ _ “№№  33“ №№  остальные — с бОльших, например; ln’terthRate или march}!  “Имена методов должны быть глаголами H‘nH maroansmw: у , гнили/ви, первое слово которых начинается б`ілаленькои бУКЁы,’ ”№ ЁТные % о больших, например, calculateAverage или clearAuDat Имена констант должны обстоять H3 всех больших букв. слова отделяются знаками подчеркивания, например HAXIMUH ‹ :… OLD__ INTEREST RATE д _ J Д, А", ‚,З«,‘^:‹%$%;3т, J J „%%,:,;;;%`°„"5^%;%Ёс‹,да? ` ;. "  Ё ~ , 3"“ mm? …… v 3223:» JJJxJJJH’Lffi’nx. › .“ “ M133 I \… Р“ ` m „}\… wi‘w".v\ v *$ `  (… , „ \ а….. ‚мы. „щ.... …»…Ьм…ь3_…; v \ ы…ы „...Мы .… .. „...- . …... „№ …ьъмт_ц_з_-і—— ‚м.—…я… ‹ м.. .на—.….. `.›._.\ …      Типы данных  В Java различают две группы типов данных. Необъектные типы данных называются эле— ментарными. Это практически те же типы, которые изучались в С и С++, только с некото— рыми отличиями. Имеется также объектный тип для хранения символьных строк и операций над ними. Он называется String. Рассмотрим эти типы по порядку.  632 Дополнительная неделя. Основные вопросы 
Элементарные типы данных  В Java имеется восемь встроенных типов данных, которые называются элементарными, потому что переменные этих типов не являются объектами классов. Имена этих типов тако- вы: boolean, char, byte, short, long, int, long, float и double. B них хранятся различные числовые или логические данные.  Логические данные  Данные, имеющие характер “да—нет”, “правда—ложь”, хранятся в переменных типа boolean. Такая переменная может иметь только одно из двух значений — true или false. B отличие от других языков программирования, ассоциирующих false с нулем, а true с едини- цей, в Java логические значения не являются числовыми.  Целочисленные данные  В языке Java имеется четыре типа для хранения целых чисел, т.е. чисел без дробной части. Эти четыре типа — byte, short, int и long — отличаются друг от друга объемом занимаемой памяти и диапазоном принимаемых значений. Если приходится работать с целыми числами, всегда можно выбрать наиболее подходящий тип по максимальному и минимальному значе- нию, которое могут принимать данные. Все эти типы — знаковые, т.е. переменные этих ти— пов могут иметь как положительные, так и отрицательные значения. Подробная характери— стика целочисленных типов Java дана в табл. 25.2.   (|№ Обратите внимание на тот факт, что переменные элементарных типов Java имеют одну и ту же длину на всех аппаратных платформах. В этом заключается отличие от С и С++, в которых длина переменных варьирует- ся в зависимости от аппаратуры и системы.     Таблица 25.2. Элементарные целочисленные типы данных Java    ТИП данных длина диапазон ЗНЗЧЭНИЙ byte 1 байт (a бит) -128…127 short 2 байта (16 бит) -З2768...32767 int 4 байта (32 бита) -2147483688...2147483647 long 8 байт (64 бита) -9.22>‹101"...-9.22›‹1о18 (примерный диапазон)   Числа с плавающей точкой  Для представления вещественных чисел с плавающей точкой — т.е. чисел, имеющих дробную часть — язык Java предлагает два элементарных типа. В подавляющем большинстве случаев следует остановить свой выбор на типе double. Переменные этого типа занимают 8 байт памяти и позволяют представить число с точностью 14—15 десятичных цифр в широ- чайшем диапазоне от —1.7›<103°в до 1.7х10308, обеспечивающем все мыслимые потребности математиков и инженеров. Второй тип чисел с плавающей точкой, float, существует в основном для совместимости со старыми файлами данных, использующими этот формат. Переменные типа float занима- ют четыре байта памяти, обеспечивают точность всего лишь 6—7 цифр и диапазон значений от -3.4›‹ 1038 до 3.4x 1038.  День 25-й. Основы языка Java 633 
Символьные данные  Для хранения отображаемых и печатаемых символов Java предлагает один элементарный тип данных с именем char. Переменные этого типа имеют длину 2 байта и содержат числа от 0 до 65535, представляющие коды символьного набора Unicode. Хотя с технической точки зрения значения типа char являются числами, их не следует использовать в этом качестве.    Рекомендуется Не рекомендуется  fl/Icnonbayifie числовые типы, п0дх0дя- щие по диапазону для конкретных дан-    ;Не используйте тип flpat, emf/3 еёэтом Ёнет особой необходимости,“ ‚например, ных ваших программ. * если не нужно поддерживать совмести- _ , „ 135,3, * E мость со старыми фаилами данных..  _ .х ». ...... . ,. . ‚„ …,.„ё/ .. \ .“Simgu ‹; №_………„ .. _ .. \. „…… „.….. .… …… _. . … ‚и '. __ „мысы—„„… ‚...-„ц …… … \ ___„Ь…`.\№щи . Mug-wfimxmmMMan    Константы  Константа— это элемент данных, значение которого не изменяется в ходе выполнения программы. Переменная любого из элементарных типов Java может использоваться в качест— ве константы. Для этого в ее объявлении должно присутствовать ключевое слово final:  final double INTEREST_RATE = 0.05; final int MAXIMUM_COUNT = 200;  Объявление и инициализация переменных  Чтобы объявить одну или несколько переменных одного из элементарных типов, следует поставить список идентификаторов после имени типа: double f; int counter; byte b1, b2, b3;  При желании переменные можно инициализировать в,момент объявления. Для этого по- сле переменной ставится знак присваивания (=) и ее начальное значение: double f = 1.23; int counter 0; byte b1, b2 = 13, b3; Начальное значение должно соответствовать типу переменной. Следующие инициализа— ции ошибочны, поскольку в первой из них число 2000 слишком велико для типа byte, a BO второй присутствует дробная часть, недопустимая для типа int: byte bl = 2000; int counter = 1.23;  Область действия переменных  В языке Java как переменные, так и другие компоненты программы, например, методы и объекты, имеют область действия. Область действия элемента данных определяет, какие час- ти программы имеют доступ к нему. Для задания области действия используются специаль— ные ключевые слова _— модификаторы доступа —— в объявлении элемента. Кроме того, играет роль местонахождение самого объявления. Если элемент объявлен внутри метода, то его  д34 Дополнительная неделя. Основные вопросы 
область действия ограничена только этим методом, и никакие ключевые слова не работают. Если же объявление элемента находится вне методов, его область действия регулируется сле- дующими правилами.  I При наличии модификатора private элемент доступен только в том классе, в котором объявлен.  I При отсутствии модификаторов элемент доступен во всех классах пакета, к которому относится данный класс.  I При наличии ключевого слова protected элемент доступен во всех классах пакета и в п0дкпассах, происх0дящих от данного класса.  I При объявлении с м0дификатором public элемент доступен везде, где доступен дан- ный класс.  _Ё Ниже представлены некоторые примеры. В комментариях поясняется область действия ;соответствующих переменных.  ЭпЬ1іс class MyClass { private int count; // доступна только в этом классе. long averageWeight; // доступна и в дРУгих классах этого пакета. protected boolean paymentReceived; // доступна в других классах этого // пакета и в подклассах. 5 public double sumOfPayments; // доступна везде, где и MyClass.  плащик  public void someMethod () // Метод можно вызывать отовсюду, { // где доступен данный класс. short temporaryTotal; // доступна только внутри someMethod.  _ pro-:- д.т’ни'чг ‚« ‚,и…   private void anotherMethod () // Этот метод можно вызывать { // только из этого класса. short temporaryTotal; // доступна только внутри anotherMethod. // He пересекается c переменной под // тем же именем в someMethod.  ' _ ъ; "ы’пы'т'д"   _,Tp0KOBbIe данные  Напомним, что как в С, так и в С++ строки текста хранятся в массивах символов. В языке  любых других переменных: ‘gtring lastName;  :: нь 25-й. Основы языка Java 635 
Имея С'ГрОКОВУЮ переменную, МОЖНО поместить В нее данные С ПОМОЩЬЮ оператора при- СВЗИВЗНИЯ:  lastName = "Smith"; Объявление и присваивание можно совместить: String lastName = "Smith";  Для операции сцепления строк, известной также как конкатенация, используется знак +. Компилятор Java сам распознает, что выполняется операция над строками, а не над числами, и выполнит конкатенацию вместо сложения. Вот несколько примеров:  String fullName, firstName, lastName; firstName = "Peter"; lastName = "Aitken"; fullName _ firstName + " " + lastName;  B результате этих операций переменная fullName приобретет значение "Peter Aitken". Строковые литералы записываются именно так, как показано выше — в виде текстовых строк в двойных кавычках. Точно так же, как в языках С и С++, некоторые символы пред- ставляются специальными управляющими последовательностями. Специальные символы Java представлены в табл. 25.3.  Таблица 25.3. Специальные символы в Java    Управляющий кол Символ, представленный колом \b Возврат на один символ назад %— \t Символ табуляции \n Конец строки \f Прогон страницы \r Возврат каретки \" Двойная кавычка \' Одинарная кавычка \\ Обратная косая черта   Методы класса String  Объект String инкапсулирует ряд методов для работы со строковыми данными. Одним из полезных методов является length( ). OH возвращает количество символов в строке: MyString = "Java programming"; n = MyString.length(); // Теперь п равно 16. Заметьте, что вызов метода length( ) дает значение 16, a не 17. B Java завершающий ну- левой символ не включается в общую длину строки, в отличие от С и С++. Имеются и другие методы для модификации строки, сравнения строк, извлечения симво- лов из строки и т.д. Более подробную информацию 0 классе String и его методах можно най— ти в документации‚ прилагаемой к конкретной среде разработки Java. B листинге 25.1 демонстрируются некоторые методы класса String.  636 Дополнительная неделя. Основные вопросы 
Листинг 25. 1 . StringTest . java — демонстрация некоторых методов класса String   1 import java.lang.System; 2 import java.lang.String; 2 public class StringTest { ЁЁ public static void main(String args[]) { ;: String s1 = "Teach Yourself C in 21 Days"; 9:  10: System.out.println("The original string: " + s1); 11: System.out.println("Converted to uppercase: " + s1.toUpperCase()); 12: System.out.println("Converted to lowercase: " + s1.toLowerCase()); 13: System.out.println("The first Y is at position " + s1.index0f('Y')); -14: System.out.println("Replacing 'e' with '1': "+s1.replace('e', '!')); 15: System.out.println("This string has " + s1.length()+" characters."); 16: } 17: }  ’ Рн ЬШШП The original string: Teach Yourself C in 21 Days ЦП Converted to uppercase: TEACH YOURSELF C IN 21 DAYS  Converted to lowercase: teach yourself c in 21 days The first Y is at position 6. Replacing 'e' with '!': T!ach Yours!lf C in 21 Days This string has 27 characters. The original string is unchanged: Teach Yourself C in 21 Days   Эта программа очень проста. Строки 1—6 содержат знакомые элементы — импор- тирование классов, определение класса программы и мет0да main( ). Внутри main() B строке 8 объявляется и инициализируется экземпляр класса String. Затем методы этого класса используются для выполнения ряда операций над строкой и выв0да получившихся резуль- татов на экран. Важно отметить, что используемые методы класса String не изменяют исхоцную `егроку, а возвращают новую строку, над которой выполнены те или иные преобразования.  ВВОД и ВЫВОД  Наиболее элементарные программы на Java представляют собой консольные приложения, в которых весь вв0д с клавиатуры и выв0д на экран выполняются только в текстовом режиме. В Java предусмотрены богатые графические возможности, реализованные в виде пакета Abstract Windowing Toolkit (AW’I‘) и библиотеки классов Swing, но эта тема выходит за пределы нашей книги. Для первого знакомства с Java достаточно будет и консольного вв0да—выв0да. Выв0д текста на экран выполняется мет0дом println класса System.out. Этот метод уже использовался ранее в программе Hello, World, рассмотренной на занятии 22. Его синтаксис прост:  \ ‚вузЪет.оиЪ.ргіпЪ1п(“ТЬіз text will be displayed on the screen."); System.out.println(s); // Переменная s должна быть объектом String.  День 25-й. Основы языка Java 637 
Ввод текста выполняется несколько сложнее. Хотя для ввода с клавиатуры можно использо- вать объект System. in, сам по себе этот объект пригоден только для получения отдельных сим- волов. Поскольку обычно бывает необходимо вводить строки символов, приходится прибегать к двум классам более высокого уровня —— BufferedReader и InputStreamReader. He вдаваясь в подробности, приведем только необходимые инструкции по вводу символьных строк:  1. Импортируйте классы java.lang.System, java.io.InputStreamReader и java.io. BufferedReader.  2. Объявите переменную типа BufferedReader.  3. Инициализируйте переменную, созданную на шаге 2, следующим образом (для примера переменная названа kb):  kb = new BufferedReader(new InputStreamReader(System.in)); 4. Вызовите метод readLine объекта kb для ввода строки текста с клавиатуры.  Все эти операции продемонстрированы в листинге 25.2.  Листинг 25.2. InputOutputTest . java —- демонстрация консольного ввода-вывода   |...  co " О" U'l uh Ш N .. .. .. .. .. .. .. .. ..  import java.lang.System; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException;  public class InputOutputTest {  public static void main(String args[]) throws IOException {  9 // Подготовка к чтению строк c клавиатуры. 10: BufferedReader kb; 11: String s1; 12: 13: kb = new BufferedReader(new InputStreamReader(System.in)); l4: System.out.println("Enter a line of text: "); 15: s1 = kb.readLine(); 16: System.out.println("You entered " + sl); 17: 18: } 19: }   Р m Enter a line of text: Java programming Baum: am You entered Java programming  № В строках 1—4 этой программы импортируются необходимые стандартные клас- сы Java. B строке 6 определяется класс InputOutputTest, включающий собст-  венно программу. Фрагмент ее строки заголовка throws IOException представляет собой обращение к механизму обработки ошибок Java, который будет рассмотрен на занятии 27. В строке 10 объявляется переменная типа BufferedReader с именем kb, поскольку она будет использоваться для ввода с клавиатуры. В строке 11 объявляется переменная типа String, используемая в Java для хранения текстовых данных (это обсуждалось в одном из предыду- щих разделов). В строке 13 объект kb инициализируется перед последующим использованием.  638 Дополнительная неделя. Основные вопросы 
В егроке 14 на экран выводится приглашение. В строке 15 программа получает с клавиатуры строку текста и помещает ее в переменную sl. Наконец, в строке 16 введенный текст выво- дится на экран для контроля и демонстрации. Возможности консольного ввода-вывода в Java не исчерпываются приведенными приме- рами. Однако рассмотренных методов достаточно, чтобы работать с программами на Java, приведенными в нашем кратком вводном курсе.  Массивы  Массивы в Java представляют способ хранения индексированных однородных данных — точно так же, как в С и С++. Все элементы массива хранятся под единым именем и различа- ются только по целочисленным индексам. Например, массив MyArray, содержащий 100 зна- чений типа int, можно представить себе как совокупность переменных MyArray[ О], HyArray[ 1] и т.д. до MyArray[99]. B массивах можно хранить как данные элементарных ти- пов, так и объекты. В Java создание массива происходит в два этапа. Вначале необходимо "объявить идентификатор для ссылки на массив. Это объявление имеет следующий синтаксис:  тип Идентифика торМа ссива [] ;  Здесь тип— это имя класса или элементарного типа данных, а ИдентификаторМасснва — имя самого массива. Квадратные скобки отличают объявление массива от объявления про- стой переменной или одиночного объекта. На втором этапе массив фактически создается в памяти, и объявленный идентификатор инициализируется так, чтобы он указывал на соз- данный массив:  ЯдевтификаторМассива = new тип [количество_элементов] ; ЗДССЬ ТИП—— ЭТО снова ТИП данных maccnaa, КОТОРЫЙ ДОЛЖСН connana'rb C ТИПОМ, 06BX3-  ленным на первом этапе. В квадратных скобках указывается количество элементов в массиве. Оба этапа создания массива можно объединить в одном операторе:  тип Идентифика торМа сснва = new тип [количество__ элементов] ; Если массив содержит данные элементарных типов или типа String, то можно начинать пользоваться им без всякой дополнительной подготовки:  String Names = new String [50]; int Numbers = new int [100];  LNames[1] = "Peter“; Names[2] = "John"; Numbers[1] = 2;  'Numbers[2] = Numbers[1] + S; Если же массив содержит объекты сложных типов, то необходимо явно инициализировать каждый элемент массива путем ссылки на объект: iHyClass ClassArray = new MyClass[10]; :for (int i=0; i<10; i++) { ClassArray[i] ' new MyClass; }  Массивы в Java могут быть и многомерными. Чтобы создать такой массив, необходимо указать по одной паре Еквадратных скобок на каждое измерение массива при его объявлении. ‚В остальном синтаксис остается тем же:  L r ,  Шень 25-й. Основы языка Java 639 
int twoDimensionalArray [][] = new int [10][5]; byte fourDimensionalArray [][][][]; fourDimensionalArray = new byte [4][4][5][5];   @:." Массивы в Java всегда начинаются с элемента номер 0, а не 1. Например, если в массиве 100 элементов, то они пронумерованы от [0] до [99]. и элемента [100] He существует. Если же необходим массив с нумерацией от [1] до [n] (например, в соответствии с днями года от [1] до [365]), то единственным решением проблемы будет создание массива на один эле— мент длиннее, чем нужно, и игнорирование элемента [О].     Операции  Над данными могут выполняться операции, например, сложение или вычитание, которые имеют свои обозначения, или знаки. Например, математический знак равенства (=) соответ— ствует операции присваивания, которая используется в операторе присваивания для помеще- ния значений B переменные. Большая часть операций Java не отличается от аналогичных опе— раций С и С++. Это такие стандартные арифметические операции, как сложение (+), вычита— ние (-), умножение (*), деление (/ ), деление по молулю (%). Так же, как в С”и С++, в Java имеются Одноместные операции инкрементирования (++) и декрементирования (-—), которые соответственно увеличивают или уменьшают целое число на 1. Удобным средством сокра— щения записи и времени являются сочетания арифметических операций (: присваиванием +=, —=, *= и / =, например:  х += 5; // То не, что x x + 5; Y *: 1.5; // To же, что y = Y * 1_5;  Операции сравнения в Java также не отличаются от их аналогов в С и С++. Они обозна— чаются знаками “равно” ==, “не равно” !=, “больше” >, “больше или равно” >=, “меньше” ‹, “меньше или равно” <=. Наконец, в Java имеются логические операции, которые тоже рассматривались раньше: 1113(1), и (вв), или (l |). Более подробную информацию об операциях и их свойствах можно получить из материа- ла занятия 4, посвященного аналогичным операциям языка С.  Управляющие операторы  Управляющие операторы предназначены для того, чтобы определять, какие фрагмен- ты кола и сколько раз выполнить в данный момент. Без управляющих операторов не обойтись при оперативном реагировании на действия пользователя, состояние данных и другие факторы.   @… Классы или методы Java заключаются в фигурные скобки {}. Такие же —— скобки используются и для организации составных операторов, или бло- ков (как в С и С++). Всякая группа из одного или нескольких операторов Java, заключенная в фигурные скобки, является составным оператором и воспринимается при выполнении кода как одно целое.     640 Дополнительная неделя. Основные вопросы 
Оператор if...else  Условный оператор іі в языке Java устроен точно так же, как в С и С++. Блок после іі выполняется только в том случае, если условное выражение оператора равно true. B onepa- тор іі можно включить необязательный блок else, который выполняется, только если услов- ное выражение равно false. Блок может содержать любое количество операторов. Если опе- раторов в блоке больше одною, они должны быть заключены в фигурные скобки. Оператор іі имеет следующий синтаксис:  іі (условие) блох1 else операторг;  Здесь условие — это выражение, которое равно true или false. Если условие равно true, то выполняется блок1, если же false — то блокг. Блок else можно вообще опустить, если при равенстве условия false не нужно выполнять никаких действий: \  іі ( условие) блок];  Конструкции іі . . .else можно вкладывать друг в друга при необходимости проверки не- скольких условий:  іі (условие1) { іі (условиег) блок]; else блокг; } else блокЗ;  Операторы іі можно делать вложенными непосредственно, не помещая их в фигурные скобки:  іі (условиеі) блок1 ; else if (условиег) блок2; else if (условиеЗ) блокЗ; else блок4;  Циклы while и do...while  Конструкции while и do. . .while вызывают циклическое повторение блока операторов в течение всего времени, пока выполняется заданное условие. Оператор while имеет сле- дующий синтаксис: while (условие) блок_ опера торов;  Оператор do. . .while имеет следующий синтаксис:  do блок; while ( условие)  День 25-й. Основы языка Java 641 
Основное различие между этими двумя конструкциями состоит в том, что в операторе while условие проверяется перед выполнением блока операторов, так что если условие равно false с самого начала, то цикл не выполняется вообще. В противоположность этому, оператор do. . .while проверяет условие в конце цикла, так что одно выполнение блока всегда гарантиро— вано. Как видите, оба оператора совершенно эквивалентны своим аналогам в языке С.  Оператор switch  Конструкция switch используется для сравнения выражения с несколькими возможными его значениями и выполнения блоков операторов в зависимости от результата этого сравнеъ ния. Оператор switch имеет следующий синтаксис:  switch (выражение) { case значение!: блокі; case значенне2: блокг; case значеннеЗ: блокЗ; default: блок4;  Если выражение равно зна ченнюі, выполняется блок.! и т.д. Если выражение не совпало ни с одним из зна ченнй, выполняется блок после ключевого слова default. Его наличие не обя- зательно. Если слово default отсутствует и нет совпадений выражения с образцами значе- ний, то просто не выполняется ни один из заданных блоков операторов. Если совпадений не- сколько, выбирается только первое из них. Каждый блок после case должен заканчиваться оператором break, чтобы не допустить нежелательного перехода в следующий блок. Рас- смотрим пример оператора switch:  switch(numberOfSiblings) { case 0: System.out.println("An only child, I зав."); break; case 1: System.out.println("The typical family!"); break; case 2: System.out.println("Three kids can be a handful."); break; default: System.out.println("It must have been crowded at your house!");  Уберем оператор break из блока case 0:. Если переменная numberOfSiblings равна 0, то будет выполнен не только оператор после case 0:, но и блок после case 1:. Другими слова— ми, когда оператор switch находит совпадение, операторы после соответствующего case вы- полняются по порядку до тех пор, пока не встретится оператор break или конец всего опера- тора switch. Как и в предыдущих случаях, этот оператор аналогичен соответствующей кон- струкции в языках С и С++.  642 Дополнительная неделя. Основные вопросы 
Оператор for  Конструкция for используется для выполнения блока операторов заданное количество раз. Она имеет следующий синтаксис: for (переменная = на чальное_зна чение; выражение}; выраженнег )  {  блок_ опера торов ;  Здесь переменная может принадлежать к любому из числовых типов, а выражение} долж- но иметь булевский (логический) тип. Вот как работает конструкция for, когда до нее дохо- дит выполнение:  1. Переменная устанавливается равной начальному зна чении.  2. Вычисляется выражение}. Если оно равно true, выполняется блок_операторов, если же false — выполнение всего блока for заканчивается.  3. Вычисляется выраженнег. 4. Выполняется переход на шаг 2.  Наиболее часто конструкция for используется для перебора последовательности целочис- ленных параметров с целью выполнения какой-либо циклической операции. Например, в следующем цикле выводятся на экран все числа от 0 до 100:  for (і = 0; 1 < 101; і++) {  System.out.println(i);  Объявление переменной, используемой как счетчик цикла, может выполняться как зара- нее, так и прямо в начале цикла. В этом примере цикл for перебирает числа от 100 до 50 че- рез одно: for (int 1 = 100; і >= 50; і —= 2) { }  Шаг цикла может быть и дробным, а не обязательно целым, лишь бы использовались пе- ременные соответствующего типа:  for (double d = 0.0; d < .99; d += 0.01) ;{ }  Резюме  На этом занятии был изучен большой объем материала по основам языка Java. Впро- чем, многое из рассмотренного, по всей вероятности, оказалось легко понятным, посколь- аку в языках С и С++ имеются совершенно аналогичные конструкции. Обсуждались такие фундаментальные понятия и составные части программ на Java, как ключевые слова, иден- тификаторы, переменные, массивы, операции, управляющие операторы. Освоив все это, Ёможно переходить к самым интересным частям языка Java, которые рассматриваются на &двух следующих занятиях.  Шень 25-й. Основы языка Java 643 
Вопросы и ответы  Совпадает ли синтаксис типов данных, операций и управляющих операторов Java с аналогичными элементами языков С и С++? Практически совпадает, хотя и с некоторыми отличиями. Во всяком случае, знание языков С и С++ дает существенное преимущество при изучении Java.  Чем отличается способ хранения и обработки текстовых (строковых) данных в Java от С и С++? Как в С, так и в С++ для хранения текстовых данных (строк) используются массивы сим— волов. В отличие от них в Java для этой цели служит класс String. Будучи классом, а не про- сто массивом, тип String предлагает гораздо большие возможности для работы с текстом.  Что такое консольное приложение? Пригоден ли язык Java для создания других типов программ? Консольное приложение —— 3T0 программа, использующая только текстовый ввод с кла— виатуры и вывод на экран без графики. На Java можно писать программы любой сложно- сти, B том числе с применением графики и оконных интерфейсов. В представленных приме- рах использовался только консольный ввод—ВЫВОД, чтобы не перегружать лишним материа— лом изложение основ языка…  Коллоквиум  Этот КОЛЛОКВИУМ состоит из контрольных вопросов на закрепление изученного материала и упражнений для развития практических навыков программирования. Ответы к вопросам содержатся в приложении Е.  Контрольные вопросы  1. Какой из элементарных типов Java имеет самый широкий диапазон для представления це— лых чисел?  2. Поддерживает ли язык Java простые функции?  3. Какая конструкция используется в Java для многократного выполнения блока операторов, пока верно определенное условие?  4. Каково назначение оператора import? 5. Как написать комментарий к программе на Java?  6. B чем заключается ошибка в следующем коде?  int count; Count = О;  7. Какие типы данных Java могут использоваться в массивах?  Упражнения  1. Установите компилятор Java и поработайте с ним. Скомпилируйте и запустите на вьшоли нение листинги программ, представленных на этом занятии.  644 Дополнительная неделя. Основные вопросы 
  Классы и меТОДы Java  B языке Java все без исключения делается с помощью классов‘ В сущности, процесс про- граммирования на этом языке состоит из разработки классов для выполнения тех или иных функций, требуемых от конечного программного продукта‘ На этом занятии будут рассмот- рены следующие темы`  I Определение классов I Разработка свойств и методов классов I Организация пакетов I Наследование в Java  Определение класса  Определение класса состоит из нескольких частей, причем некоторые из них не обяза- тельны` Простейшие определения классов уже рассматривались на занятии 25. В языке Java буквально все состоит из классов` Даже программа— и та представляет собой класс` Конечно, большинство классов программами все-таки не являются, а наоборот, использу- ются в программах для создания объектов. Рассмотрим определение классов более подробно` Самый элементарный вариант такого определения имеет следующий вид:  class имяКласса  { }  Этот вариант имеет очень простой синтаксис. Он ничем не отличается от определения класса программы, хотя, вообще говоря, класс не обязан содержать метод main( ). Наличие ключевого слова class обязательно` Идентификатор иияКласса должен удовлетворять требо- ваниям Java к именам объектов, рассмотренным на занятии 25. По этому имени выполняется ссылка на класс в программах. Немногие классы обходятся столь простым определением, как показано выше` Как прави- ло, в заголовке класса имеются дополнительные элементы. Вот какой вид имеет полный син— таксис определения класса, включая необязательные элементы в квадратных скобках: 
[модификаторы] class нмяКласса [extends нмяСуперкласса]  { }  Модификаторы определяют две характеристики класса. Одна из них —— это область дейст- вия илн степень доступности класса (об этом уже упоминалось на занятии 25). Ключевые слова, относящиеся к области действия, перечислены в табл. 26.1. Большая часть классов принадлежит к категории public.  Таблица 26. 1 . Ключевые слова, описывающие область доступности классов    Ключевое слово Описание public Класс открыт для доступа private Класс полностью закрыт отсутствует Класс доступен в пределах своего пакета protected Класс доступен в пределах своего пакета и подклассов   Кроме области доступности, ключевые слова определяют также возможность наследования. В определении класса можно использовать один из следующих модификаторов (но _не оба сразу).  I Abstract. Разрешает наследование от класса (т.е. использование его в качестве супер- класса), но запрещает создание его экземпляров. Этот модификатор применяется в тех случаях, когда класс должен служить шаблоном для своих наследников, но запрещен для непосредственного создания объектов.  l Final. Разрешает создание экземпляров класса, но запрещает его использование в ка- честве суперкласса (т.е. наследование от этого класса).  Необязательный элемент заголовка extends имяСуперкласса задает наследование, т.е. указывает, основан ли новый класс на уже существующем, определенном ранее. Класс, на- следуемый данным классом, может как принадлежать к стандартной библиотеке Java, так и быть нестандартным, лишь бы в его заголовке не фигурировало ключевое слово final. При наследовании классом какого-либо суперкласса все свойства и меюды последнего автомати- чески переходят к новому классу. Наследование будет рассмотрено более подробно позже на этом же занятии.   ( "|..." Для компиляции отдельного кпасса, как и целой программы на Java, тре- - буется падключение всех классов и пакетов, используемых в его коде. Со- ответствующие операторы import должны нах0диться в файле исходного кода непосредственно перед определением класса.     Создание пакета классов  Каждый класс в Java является частью пакета. Чтобы указать пакет‚ в файле определений классов используется ключевое слово package. Этот оператор должен стоять самым первым в файле, раньше всех операторов import:  package нняПахета;  Если опустить оператор, задающий имя пакета, то класс будет автоматически помещен в пакет без имени, созданный по умолчанию. Это неудачное решение. Всегда следует создавать  646 Дополнительная неделя. Основные вопросы 
пакеты с именами, содержащие наборы родственных классов, что позволяет упорядочить структуру классов. Если тот или иной класс пакета нужен другой программе, то можно воспользоваться оператором import для импортирования всего пакета или отдельных его _ классов.  Создание свойств класса  Свойство—— это элемент данных, ассоциированный с классом. Например, класс Circle, представляющий окружность, скорее всего будет содержать свойство Radius для хранения ее радиуса. По сути свойство — это переменная-член (или поле) класса, объявленная внутри не- го и доступная снаружи. На занятии 25 уже были рассмотрены переменные Java и их объяв- ление. Переменная, используемая в качестве свойства, должна объявляться в начале класса вне его методов:  public class circle { public double radius; public int anotherProperty; public byte yetAnotherProperty; public MyClass anObjectProperty; // Knacc MyClass - нестандартный // и создается программистом. public String oneLastProperty;  // Остальная часть кода класса.   (… Как и в С++. свойства классов Java следует по возможности делать закры- тыми (private) и создавать по две функции для доступа к каждому из них — одну для получения, другую для установки значения.     Демонстрационный пример  Прежде чем углубляться в тонкости работы с классами, рассмотрим в качестве примера определение и использование простого класса. Демонстрационный проект Java будет состо- ять из двух файлов. Один из них, SimpleClass . java, будет включать определение очень про- стого класса всего с двумя свойствами —— одно для текста, другое для числа. Второй файл, ClassBasicsDemo. java, будет содержать программу, использующую объект SimpleClass. Исходный код обоих файлов приведен в листингах 26.1 и 26.2.  Листинг 26. 1 . Текст файла SimpleClass . java   import java.lang.String; public class SimpleClass {  public double data; public String text;  ЧФШоЁ—ЫЮН  День 26-й. Классы и методы Java 647 
Листинг 26.2. Текст файла ClassBasicsDemo. java   public class ClassBasicsDemo { public static void main(String args[])  {  SimpleClass MyClass;  MyClass = new SimpleClass(); MyClass.data = 1.2345; MyClass.text = "A class act.";  ЮЧФШдШМН II II II II II II II II II  9 System.out.print("The number stored in MyClass is "); 10: System.out.println(MyClass.data); 11: System.out.print("The text stored in MyClass is "); 12: System.out.println(MyClass.text); 13: } 14: }   P The number stored in MyClass is 1.2345 ЮШЬШЩП The text stored in MyClass is А class act  № Вначале рассмотрим класс SimpleClass.java. B строке 1 импортируется класс String, поскольку объект этого класса используется в качестве свойства. В стро—  ке 3 находится объявление класса, показывающее, что он не наследует никакой другой класс и является открытым (public). B строках 5 и 6 объявляются две переменные в качестве свойств класса. Код ClassBasicsDemo.java немного сложнее. Строка1 содержит объявление класса, а строка 2 —-— заголовок метода шаіп( ), который должен присутствовать в любой автономно выполняемой программе на Java. B строке 4 объявляется переменная типа SimpleClass, т.е. ссылка на экземпляр объявленного ранее класса. В строке 6 этот экземпляр создается с ис— пользованием оператора new, и ссылка на него присваивается переменной MyClass. B стро— ках 7 и 8 свойства объекта получают значения. Для обращения к ним используется стан— дартный синтаксис нмя_объекта.имя_своЙства. В строках 9—12 значения этих свойств вы— водятся на экран.   (Iplll‘lllll Может возникнуть вопрос, почему в программе ClassBasicsDemo He исполь— эуется оператор импортирования для ссылки на класс SimpleClass. B этом нет необходимости, поскольку оба класса являются частями одного проек— та. Если бы SimpleClass был скомпилирован в составе другого пакета, его подключение требовало бы наличия оператора import.     МеТОДы классов  Хотя некоторые классы содержат в своем составе только свойства, большинство классов все—таки включает один или несколько методов. Метод—— это всего лишь другое название для функции в Java. Метод представляет собой отдельный фрагмент кода с собственным именем, в который можно передавать аргументы и получать от него возвращаемое значе— ние. В Java, как и во всех языках программирования, функции используются для обособле— ния частей кода, выполняющих частные задачи. Методы имеют следующий синтаксис:  648 Дополнительная неделя. Основные вопросы 
доступность тип нмяМ'етода (параметр1, параметр2, . . .)  {  Ключевое слово доступность определяет право доступа к мегоцу из разных частей про— граммы. Чтобы метод можно было вызвать из любого места программы, где доступен объект данного класса, используйте ключевое слово public. Это самый распространенный случай. Ключевое слово private используется в том случае, когда необходимо, чтобы метод мог вы— зываться только из других методов этого же объекта. Ключевое слово тип задает тип возвращаемого методом значения, т.е. тип объекта, кото- рый метод посылает назад в вызвавшую его программу. Это может быть как значение одного из элементарных типов, так и объект класса, например, String. Идентификатор нмяМетода определяет имя, по которому выполняется обращение к методу. Ё Он должен подчиняться требованиям Java K именам переменных, изложенным на занятии 25. Ё Метод может принимать любое количество аргументов. Для каждого из них объявление "  \  ‚метода должно содержать тип и идентификатор соответствующего параметра, по порядку } следования. Например, объявление метода с двумя параметрами типа int, возвращающего Ё значение типа long, выглядит так:  public long SomeMethod (int argl, int argZ) {  A в В ! »  } Ё Если метод не принимает аргументов, после его имени ставится пустая пара круглых СКО- ‘g’ бок. При вызове мегода список аргументов Должен совпадать ПО количеству и типам со спи— .} ском параметров в его объявлении.  Для возвращения значения из метода используется оператор return:  пн.—ч  тт  return выражение; % Фигурирующее после return выражение должно при вычислении давать значение того же Ё. типа, который объявлен в заголовке метода. Как только выполнение метода достигает опера— Ё тора return, выражение вычисляется, метод завершается, и вычисленное значение передается % в вызвавший м0дуль. В методе может быть несколько операторов return, но сработает толь-  „› ко Один из них, который встретится первым при выполнении метода.   „„„}   Некоторые мет0ды не возвращают значений в вызывающую программу. Для объявления такого метода используется ключевое слово void в его заголовке. Возвращение управления из меТОДа выполняется с помощью оператора return без выражения после него. Если оператора return в ме- т0де нет вообще. то метод завершается. когда выполнение доходит до его закрывающей фигурной скобки.  ‘ <—‹ TV‘ 'K‘“W“‘-‘7““W_ wrung-w: vac-     чу „чи- к  ;Демонстрация меТОДов  В приведеннойниже программе демонстрируется класс, включающий ряд методов. Фак— тически, в этом классе нет ничего другого, кроме меТОДов, он даже лишен свойств. Опреде- ление класса ClassWithMethods приведено в листинге 26.3, а текст демонстрационной про- граммы с использованием этого класса ——— в листинге 26.4.  mwu’mn‘wwv ms ч.:-« чаи—щадит  День 26-й. Классы и методы Java 649 
Листинг 26.3. Исходный Kan. класса ClassWithHethods . java   1 import java.lang.String; 2 3 public class ClassWithMethods { 4: 5- public void displayText(String message, boolean newline) { 6 // Вывоп сообщения на консоль, переход на начало 7 // новой строки после вывода сообщения в том случае, 8. // если newline равно true. 9: if (newline) 10: System.out.println(message); 11: else 12: System.out.print(message); 13: } 14: 15: public double half0f(double value) { 16: // Возвращает половину своего аргумента. 17: return value / 2; 18: } 19: 20: public long sum0f(long valuel, long value2) { 21: // Возвращает сумму своих аргументов. 22: long result; 23: result = valuel + value2; 24: return result; 25: } 26: }   Листинг 26.4. Программа HethodsDemo. java, использующая меТОДы класса ClassWithHethods   1: import java.lang.Double; import java.lang.Long;  2 3 4 public class HethodsDemo { 5: public static void main(String args[]) { 6: ClassWithMethods The_Class; 7 String temp; 8  . double d; 9: long 1; 10: 11: The_Class = new ClassWithMethods(); 12: The_Class.displayText("Using ClassWithMethods:", true); 13: The_Class.displayText("Half of 99 is ", false); 14: d = The_C1ass.halfOf(99); 15: temp = Double.toString(d); 16: The_Class.displayText(temp, true); 17: The_Class.displayText("The sum of 12345 and 997766 is ", false); 18: l = The_Class.sum0f(12345, 997766);  650 Дополнительная неделя. Основные вопросы 
19: temp = Long.tostring{l); 20: ThefiClass.dispiayText(temp, true); 21: } 22: }  Using ClassWithMethods: Half of 99 is 49.5 The sum of 12345 and 997766 is 1010111  B строке 1 файла ClassWithMethods. java импортируется класс String. Строка 3 ' содержит определение самого класса ClassWithMethods. B строках 5—13 опреде- ляется метод displayText, принимающий один аргумент типа String и Один —— типа boolean. Этот метод не возвращает никаких значений. В коце мет0да проверяется значение аргумента newline. Если оно равно true, то с помощью метода System.0ut.println Ha эк- ран выводится сообщение, а затем выполняется переход на новую строку. Если же аргу- мент равен false, то вызывается метал System.out.print для вывода сообщения без пере- хола на новую строку, В строках 15—18 определяется метод halfOf. Он принимает один аргумент типа double и возвращает значение типа double. Мегол с0держит всего одну сгроку кода, в которой аргу- мент делится на два, и получившееся значение возвращается. В строках 20—25 определяется мегол sumOf, принимающий два аргумента типа long и воз- вращающий значение того же типа. В строке 22 объявляется переменная для хранения вре- менного результата, в строке 23 выполняется вычисление, а в строке 24 мегов заканчивает работ;/„ возвращая значение. Теперь рассмотрим текст файла MethodsDemo. java. B строках ! и 2 импортируются два класса, используемых программой (как будет показано далее). Строки 4 н 5 ео,-терм жат заголовки класса программы и ее метода main( ). B строке 6 объявляется переменная типа ClassWithMethods, a в строках с 7 по 9 — несколько переменных элементарных типов. В строке 11 создается экземпляр класса ClassWithHethods. Теперь программа готова приступитьтсработе. В строках 12 и 13 с помощью метода display'l‘ext на экран выводятся два сообщения. Перекоп на новую строку выполняется после первого сообщения, но не после второго. В строке 14 вызывается метод halfOf для вычисления половины числа 99, и результат при- сваивается переменной d. B строке 15 вызывается метод tOString стандартного класса Double. B результате числовое значение d преобразуется в строку и помещается в перемен- ную temp. B строке 16 получившийся текст выводится на экран. Строки 17—20 практически повторяют строки 13—16, за исключением того, что для вы- полнения вычислений вызывается метод sumOf, a затем для преобразования числа в текст ис- пользуется метод Long . tOString. і ‘в на me шесте: ;; № …»: взявшие (“""“ i: пё… данных о… называются №№ ‹ % жмете gm Double 3:5 канатом ііз этих массой? —~ Зі „а 09%- -; * '3 '5’ "`“-”› l“: bfléfibflv  … №№… ..   'х ‚: {`;-) _"`А`ЪЗ"\Ё‹ Q, dWfiVifi-f?’ 5365" ")!/$  {мер. метод tostnng для <:‹:›э‚а‚;а№с „коленные классы обл :г ; прочитать в Awemaummounm  4p»,    День 26-й. Классы и метады Java 651 
Перегрузка методов  На занятии 22 говорилось, что одним из характерных средств ООП является перегрузка функций и операций. Эта техника позволяет создать два или больше метоцов (функций- членов) с одинаковыми именами, отличающихся количеством и/или типами аргументов. При вызове одного из таких методов компилятор Java сам выбирает нужный в соответствии со списком переданных аргументов. Для демонстрации этой техники ниже приводится класс, содержащий три метода sumOf. Каждый из них возвращает сумму своих аргументов, но первый принимает два аргумента, второй — три, а третий — четыре. Класс имеет имя Overloaded, и его код представлен в лис- тинге 26.5: Программа, использующая этот класс, называется OverloadDemo и приведена в тшшише2&6  Листинг 26.5. Overloaded. java — mace, содержащий перегруженные метады   1 public class Overloaded { 2 3 public double sumOf(doub1e v1, double v2) { 4: return v1 + v2; 5- } 6 7 public double sumOf(doub1e v1, double v2, double v3) { 8: return v1 + v2 + v3; 9: } 10: 11: public double sumOf(double v1, double v2, double v3, double v4) { 12: return v1 + v2 + v3 + v4; 13: } 14: }   Листинг 26.6. OverloadDemo . java —— демонстрация перегруженных методов класса Overloaded   import java.lang.String; import java.lang.Double;  1 2 3 4 public class OverloadDemo 5: { 6 public static void main(String args[]) 7 8  { : Overloaded MyClass; 9: double d; 10: 11: MyClass = new 0verloaded(); 12: System.out.println("Adding two numbers: "); '— 13: System.out.print(" The sum of 1.4 and 6.7 is "); 14: d = MyClass.sumOf(1.4, 6.7); 15: System.out.println(Double.toString(d)); 16: System.out.println("Adding three numbers: "); 17: System.out.print(” The sum of 1.4, 6.7, and 12.2 is ”); 18: d = MyClass.sumOf(1.4, 6.7, 12.2);  652 Дополнительная неделя. Основные вопросы 
19: System.out.print1n(Doub1e.toString(d));  20: System.out.print1n("Adding four numbers: "); 21: System.out.print(" The sum of 1.4, 6.7, 12.2, and -4.1 is "); 22: d = MyClass.sumOf(1.4, 6.7, 12.2, -4.1); 23: System.out.print1n(Doub1e.toString(d)); 24: } 25: }    ‘ Р ' Adding two numbers: The sum of 1.4 and 6.7 is 8.1. Adding three numbers: The sum of 1.4, 6.7, and 12.2 is 20.299999999999997 Adding 4 numbers:  The sum of 1.4, 6.7, 12.2, and -4.1 is 16.199999999999997  № Первый из двух листингов представляет довольно простой класс Overloaded. B строках 3—5 определяется метод sumOf, принимающий два аргумента типа double. B строках 7—9 и 11—13 определяются еще два метода sumOf: один с тремя, а другой ~— с четырьмя аргументами типа double. Внутри каждого из методов выполняется очень про- стой расчет -—— все аргументы складываются, и возвращается результат этого сложения. В строках 1 и 2 программы OverloadDemo импортируются два‚класса Java, используемые в программе. В строках 8 и 9 объявляются две необходимые переменные, а в строке 11 созда- ется экземпляр класса Overloaded. B строках 12 и 13 на экран выв0дится пояснительный текст. В строке 14 вызывается метод sumOf для сложения двух чисел, и в строке 15 на экране отображается результат сложения. Та же процедура выполняется над тремя и четырьмя чис- лами в строках 16—19 и 20—23 соответственно.   @.". Заметьте. что второй и третий результаты вычислений. проделанных про- граммой, имеют небольшую погрешность. Это происходит потому. что компьютер представляет с идеальной точностью не все числа, хранящие- ся в вещественном формате с плавающей точкой. Как правило. эта по- грешность пренебрежимо мала и не сказывается на результате.     Конструкторы классов  В каждом классе есть специальный метод под названием конструктор. Как и в С++, кон- структор класса вызывается автоматически при создании нового экземпляра класса. К0д, по— мещаемый в конструктор, обычно служит для инициализации объекта. Некоторые —— совсем простые —— классы обходятся без конструктора. Но, как правило, если класс отличается срав- нительной сложностью и имеет практическое применение, то в нем есть конструкторы. Объявление конструктора имеет следующий синтаксис:  ИмяКла cca (СписокПараметров)  { }  Как видно из заголовка, имя метода—конструктора всегда совпадает c именем самого клас- са. СписокПараметров— это необязательный список аргументов, передаваемых в конструк- тор. Список имеет тот же синтаксис, что и в рассмотренных ранее методах. Если конструктор не должен принимать аргументов, после его имени ставится пустая пара круглых скобок.  День 26—й. Классы и методы Java 653 
В листинге 26.7 приведен пример класса с конструктором. Класс circle включает свойст- во radius. Необходимо гарантировать, что при создании объекта этого класса данное свойст- во всегда будег определено. Поэтому класс circle содержит конструктор, выполняющий инициализацию.  Листинг 26.7. circle . java — пример класса c конструктором   1: public class circle {  2: 3: public double radius; 4: 5: circle (double r) { 6: radius = r; 7: } 8: }   Короткая программа в листинге 26.8 демонстрирует использование этого конструктора.  Листинг 26.8. UseCircle. java — создание объекта circle c вызовом конструктора   1: import java.lang.String; 2: import java.lang.Double; 3: 4: public class ConstructorDemo { 5: public static void main(String args[]) { 6: 7: circle cl; 8: cl = new circle(l.25); 9: System.out.println("The circle's radius is " 10: + Double.toString(cl.radius)); ll: } 12: }   The circle’s radius is 1.25 Резцльшат  В первом из листингов определяется класс circle. Строка 1 содержит заголовок класса, а строка 3 —— объявление свойства radius. Конструктор находится в строках 5—7. Он принимает один аргумент типа double. При вызове конструктора переданное в него значение r присваивается свойству radius. Bo втором листинге демонстрируется использование конструктора. В строках 1—5 нет ни- чего нового. В строке 7 объявляется переменная типа circle. B строке 8 создается экземпляр класса circle с использованием оператора new. При создании экземпляра вызывается конст- руктор, и в него передается указанный в скобках аргумент 1 . 25. В строках 9 и 10 путем выво- да на экран демонстрируется тот факт, что свойство radius действительно получило значение1.25. Java предъявляет очень строгие требования к соответствию между аргументами, переда- ваемыми в конструктор, и параметрами, объявленными в его заголовке. Если количество и типы аргументов не соответствуют списку параметров, программа не пройдет компиляцию.  654 Дополнительная неделя. Основные вопросы 
Если в классе вообще нет конструктора, или его конструктор объявлен без параметров, то при создании экземпляра класса используются пустые круглые скобки:  HyObject = new ClassName():  Как и всякий другой метод, конструктор можно перегрузить. Благодаря этому можно иметь несколько способов инициализации объекта. Напомним, что перегружаемые методы должны иметь одинаковые имена, однако разные списки параметров — по количеству и/или типам. Для иллюстрации изменим предыдущий пример так, чтобы класс circle содержал еще одно свойство, name, с текстовым описанием объекта. Теперь добавим в класс еще два конструктора, чтобы иметь сразу три разных способа инициализации его объектов.  I Без передачи аргументов в конструктор. Свойство radius устанавливается равным 0, а свойство name — " Unnamed ".  I C передачей одного числового аргумента. Свойство radius делается равным передан- ному аргументу, а name — равным "Unnamed".  I C передачей числа и строки. Инициализируются оба свойства — и radius, и name.  B листингах 26.9 и 26.10 приведен исходный код нового класса и демонстрационной про- граммы с его использованием.  Листинг 26.9. circ1e2 . java -- класс c перегруженными конструкторами   1: import java.lang.String; 2: 3: public class circle { 4: 5: public double radius; 6: public String name; 7: 8: circle () { 9: radius = 0; 10: name = "Unnamed"; 11: } 12: 13: circle (double r) { 14: radius = r; 15: name = "Unnamed"; 16: } 17: 18: circle (double r, String n) { 19: radius = r; 20: name = n; 21: } 22: }   Листинг 26. 1 О. UseCircleZ . java — демонстрация использования перегруженных конструкторов  1° import java.1ang.String; 2 import java.lang.Double; 3  День 26-й. Классы и методы Java 655 
4: public class ConstructorDemo { 5: public static void main(String args[]) { 6: 7: circle c1; 8: circle c2; 9: circle c3; 10: 11: c1 = new circle(); 12: c2 = new circle(99.99); 13: c3 = new circle(0.001, ”Harold");  14: System.out.print1n("For c1:"); 15: System.out.println(" The radius is " + Double.toString(c1.radius)); 16: System.out.println(" The name is " + c1.name); 17: System.out.println("For c2:"); 18: System.out.println(" The~radius is " + Double.toString(c2.radius)); 19: System.out.println(" The name is " + c2.name); 20: System.out.println("For c3:"); 21: System.out.println(" The radius is ' + Double.toString(c3.radius));  22: System.out.println(" The name is " + c3.name); 23: } 24: }   _ For Cl: резцпьташ The radius is 0.0  The name is Unnamed For c2: The radius is 99.99 The name is Unnamed For c3: The radius is 0.0010 The name is Harold  № Вначале рассмотрим класс circle. B строках 5 и 6 объявляются два свойства класса. В строках 8—11 определяется конструктор без аргументов, в коде которо- го свойства инициализируются значениями по умолчанию. В строках 13—16 определяется конструктор с одним аргументом типа double, который помещается в поле radius. При этом свойство name получает значение по умолчанию. В строках 18—21 определяется третий конст- руктор с двумя аргументами — один типа double, другой типа String. B этом конструкторе оба свойства объекта инициализируются переданными аргументами. Демонстрационная программа в листинге 26.10 создает и использует три экземпляра клас- са circ1e. B строках 7—9 объявляются переменные для хранения ссылок на объекты. В стро- ке 11 создается новый экземпляр без аргументов, поэтому вызывается первый из трех конст- рукторов. В строках 12 и 13 создаются еще два экземпляра, с передачей соответственно Одно- го и двух аргументов в конструктор класса. В остальных строках кода на экране отображаются значения свойств трех объектов circle, чтобы продемонстрировать правиль- ность их инициализации.  656 Дополнительная неделя. Основные вопросы 
Наследование  Наследование —- одна из мощнейших возможностей как Java, так и других объектно- ориентированных языков. С помощью наследования можно создавать классы не с нуля, а на основе уже существующих классов. При этом новый, дочерний, класс автоматически приоб- регает, т.е. наследует, все свойства и методы исходного, или родительского, класса (иногда называемого еще суперклассом). После этого можно добавить новые свойства и методы в до- черний класс либо же заменить некоторые из унаследованных свойств и методов. Рассмотрим гипотетический пример. Предположим, разрабатывается программа для бы- товых финансовых расчетов. Имеется класс, выполняющий почти все нужные вычисления: выплаты по рассрочке, проценты по закладным и т.п. — но в нем не хватает одной-двух не- обходимых возможностей, например, расчета выплат по займам. Надо ли начинать с нуля и создавать совершенно новый класс, включающий все необходимые средсгва? При наличии наследования это вовсе не обязательно. Достаточно лишь создать дочерний класс на основе существующего и добавить метод, выполняющий нужный расчет. Для создания дочернего класса используется ключевое слово extends B заголовке класса:  public class ИкяДочКласса extends ИщРодКласса {  }   Illlllll Помните, что нельзя наследовать класс, в объявлении которого использу- ется слово final. Такие классы не могут быть радительскими.     Рассмотрим пример, демонстрирующий наследование. Предположим, существует класс под именем ListOfNumbers, хранящий статистику некоторого набора чисел, а именно количе- ство чисел в списке и их общую сумму. В классе также есть метод для добавления чисел в список. Обратите внимание, что отдельные числа не хранятся в объекте этого класса — толь- ко их сумма и количество. Исходный текст класса ListOfNumbers представлен в листин- ге 26.11.  Листинг 26. 1 1 . Lists . java — класс ListOfNumbers, хранящий статистику спискачисел  . public class ListOfNumbers {   protected int icount; protected double itotal;  // Конструктор. ListOfNumbers() { icount = 0; itotal = 0; public void Add(double x) {  icount++; itotal += x;  ФШЬШЮНОФФЧФШд‘ШЮ  нын.—анны  День 26-й. Классы и методы Java 657 
17: public int count() { 18: return icount; 19: } 20: 21: public double total() { 22: return itotal; 23: } 24: }    № В строках 3 и 4 объявляются два поля данных (свойства) класса. Обратите вни- мание, что они объявлены с модификатором protected, поэтому непосредствен- но доступны только тем внешним классам, которые наследуют данный. Косвенный доступ к этим свойствам осуществляется через методы класса. Строки 7—10 содержат конструктор ютасса, инициализирующий оба его свойства значением 0. В строках 12——15 определяется ме- тод Add, добавляющий новое число в список. В его коде свойства icount и itotal модифици- руются для учета добавленного числа. В строках 17—19 определяется метод с именем count. который всего-навсего возвращает значение свойства icount. B строках 21——23 определяется еще один метод, выполняющий аналогичную операцию с полем класса itotal.   @… В этом классе используется эффективный прием скрытия свойств: они объявляются private или protected. a затем в класс включаются методы для обращения к ним. Этот прием полезен во многих случаях, в частности (как здесь) для того, чтобы сделать свойство только читаемым, без возможности его произвольной модификации. Данный класс содержит метод для чтения свойств icount и itotal. но не имеет методов для их из- менения, поэтому код вне класса не сможет модифицировать эти свойства по отдельности.     Итак, класс ListOfNumbers написан, отлажен и готов к работе. Но тут разработчик заме- чает, что нужен еще один класс — почти такой же, но с возможностью вычисления среднего арифметического всех чисел списка. Вместо того, чтобы переписывать и править код, можно создать подкласс, наследующий ListOfNumbers, но располагающий дополнительными воз- можностями. Исходный код этого класса, BetterListOfNumbers, приведен в листинге 26.12.  Листинг 26. 1 2. Better. java — класс BetterListOfNumbers, наследующий класс ListOfNumbers   1: class BetterListOfNumbers extends ListOfNumbers { 2: 3: public double average() { 4: if (icount > 0) 5: return itotal / icount; 6: else 7: return 0; 8 9  .‘ ..  }   nm' Строка } открывает определение нового класса. В этой строке с помощью клю- ' чевого слова extends указывается, что BetterListOfNumbers— это подкласс  (т.е. наследник) класса ListOfNumbers. B строках 3—8 определяется новый метод под именем  658 Дополнительная неделя. Основные вопросы 
average. B этом методе анализируется значение свойства icount. Если оно не равно нулю (т.е. в списке имеется хотя бы Одно число), то вычисляется и возвращается среднее арифметическое. Если же icount равно 0 (B списке нет чисел), то возвращается 0. Заметьте, что переменные icount H itotal объявлены в классе ListOfNumbers, но доступны также и в классе BetterListOfNumbers, потому что в родительском классе они имеют модификатор доступа protected. 06a рассмотренных класса для работы со списком чисел демонстрируются в программе NumberList, представленной в листинге 26.13.  Листинг 26. 1 3. NumberLi st. java — демонстрация классов ListOfNumbers и BetterListOfNumbers  public class NumberList {   public static void main(String args[]) {  ListOfNumbers MyList = new ListOfNumbers(); BetterListOfNumbers MyBetterList = new BetterListOfNumbers();  MyList.Add(4)7 MyList.Add(8); MyList.Add(9.6); MyBetterList.Add(4); MyBetterList.Add(8)7 MyBetterList.Add(9.6);  System.out.println("From class List0fNumbers:"); System.out.print("Tota1 = n);  мнннннннннн ОФФЧФШ'ЬЫМНОФФЧФШ'ЬЫМН II   : System.out.println(MyList.total())7 : System.out.print("Count = "); : System.out.println(MyList.count())7 : System.out.println("From class BetterListOfNumbers:"); 21: System.out.print("Total = "); 22: System.out.println(MyBetterList.total()); 23: System.out.print("Count = "); 24: System.out.println(MyBetterList.count()); 25: System.out.print("Average = "); 26: System.out.println(MyBetterList.average()); 27: } 28: } № From class ListOfNumbers: Total = 21.6 Count = 3 From class BetterListOfNumbers: Total = 21.6 Count = 3  Average = 7.2  № В строке 5 объявляется объект типа ListOfNumbers, который тут же создается с помощью оператора new. B строке 6 таким же образом создается объект класса  День 26-й. Классы и методы Java 659 
BetterListOfNumbers. B строках 8—10 B объект ListOfNumbers добавляются три числа с ис- пользованием его метода Add. B строках 11—13 3T0 же проделывается с созданным объектом BetterListOfNumbers. Далее на экран выводятся значения свойств двух объектов. Как видно, объект класса BetterListOfNumbers имеет все свойства и мет0ды своего родительского клас- са, а также свой собственный, дополнительный метод average.  Резюме  Классы составляют фундамент программирования на Java. Ha этом занятии были изучены основы создания и использования классов B программах Java. Класс представляет собой неза— висимый программный компонент, включающий свойства (переменные для хранения дан— ных) и методы (функции для выполнения операций). В языке Java классы, родственные по своему назначению, группируются B Пакеты. Чтобы подключить к программе классы, не вхо- дящие непосредственно B программный проект, используется оператор import. B конце заня- тия была рассмотрена очень мощная техника наследования, позволяющая создавать новые классы на основе существующих.  Вопросы и ответы  Чем отличается класс Java, являющийся самостоятельной программой, от класса, который таковым не является? Чтобы быть программой, класс должен включать метод шаіп. Именно с него начинается выполнение программы при ее запуске. Если B классе нет метода шаіп, он не является про- граммой.  Из каких основных частей состоят классы Java? Классы включают B себя две группы данных. Одна из них—_ это переменные или поля данных, называемые свойствами. Вторая —— это методы. Методы содержат код для выполне- ния различных операций над данными.  При перегрузке метода его имя остается прежним. Как же компилятор Java разли- чает перегруженные методы с одинаковыми именами? Перегруженные методы действительно имеют одинаковые имена, однако они должны от- личаться количеством и/или типом своих аргументов. При вызове одного из перегруженных методов компилятор выбирает нужный по списку переданных аргументов.  Чем отличаются методы Java от функций С и С++? Существенной разницы между методами и функциями нет. В Java метод обязан быть членом класса. Таким образом, методы Java аналогичны функциям-членам классов B С++, но отличаются от самостоятельных, не входящих в классы функций С.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления получен- ных знаний. Ответы на них можно найти в приложении Е.  660 Дополнительная неделя. Основные вопросы 
Контрольные вопросы  . Какое имя должен иметь конструктор класса? Где и зачем используется ключевое слово extends?  Все ли методы Java возвращают значения в вызывающую программу? Можно ли перегрузить конструктор класса? Когда вызывается конструктор класса?  Фирмы—-  Любой ли класс Java можно использовать в качестве родительского класса при насле— довании?  7. Какое ключевое слово используется в Java для того, чтобы запретить наследование от класса?  8. Можно ли создать объект абстрактного класса (объявленного с модификатором abstract)?  День 26—й. Классы и методы Java 661 
  Дополнительные средства Java  Java обладает всеми чертами полноценного и многофункционального языка программи— рования. На двух предыдущих занятиях были рассмотрены фундаментальные основы Java. Но этот язык далеко не исчерпывается теми средствами, которые были изучены. К сожалению, здесь можно рассмотреть лишь некоторые из богатейших возможностей Java. Ha этом занятии будут обсуждаться следующие вопросы.  I Использование исключений для обработки ошибок выполнения I Основы файлового ввода—вывода I Работа с графикой в Java I Программирование аплегов Java  Исключительные ситуации  Вне зависимости от квалификации программиста ему никогда не удастся гарантиро— вать, что при выполнении его программы не возникнет ошибка или аварийное состояние. Причиной ошибок могут быть самые разные обстоятельства (не подвластные разработчику): неправильно введенные пользователем данные, неполадки в аппаратуре компьютера, пере- бои в работе компьютерной сети и т.п. Хорошо написанная программа должна корректно обрабатывать эти ситуации. Другими словами, программа не должна завершаться аварий- но, “зависать”, уничтожать важные данные и т.п. в результате непредвиденной ошибки. Кор- рекгная обработка ошибок также предотвращает крах всей операционной системы из-за сбоя в одной программе, В языке Java ошибку выполнения называют исключительной ситуацией или исключением. Когда случается ошибка, программа генерирует исключение. Разные ошибки приводят к ге- нерированию различных видов исключений. В Java определено множество классов исключе- ний для обработки практически любых ошибок, которые могут произойти. В языке Java имеются ключевые слова try и catch, предназначенные специально для работы с исключениями. В целом, процесс обработки ошибок в Java можно описать сле- дующим образом: 
1. С помощью ключевого слова try определяются блоки кода, в которых могут возникнуть те или иные исключительные ситуации.  2. С помощью ключевого слова catch определяется код, который следует выполнить при возникновении исключительной ситуации в заданном блоке try. Блок кода, соответст- вующий ключевому слову catch, является обработчиком исключительной ситуации. Для каждого из ожидаемых исключений в тексте программы должен присутствовать отдельный блок catch.  3. Если в блоке try случается ошибка, генерируется исключение того типа, который соот- ветсгвует ошибке.  4. Исключение, сгенерированное на шаге 3, сравнивается с блоками catch, следующими за блоком try. Если обнаружено соответствие между типами сгенерированного и об- рабатываемого исключений, то выполняется код соответствующего обработчика в блоке catch.  Если исключительная ситуация не возникла, блок catch He выполняется. Ниже приведен примерный синтаксис операторов try и catch:  try { // Здесь помещается код, который может  // вызвать исключительную ситуацию. } catch (тяпИсклпчения1 е){ // Здесь находится под для обработки ошибки, // которой соответствует твиИсКлпчения1. } catch (ТилИСклпчения2 е){ // Здесь находится под для обработки ошибки, // которой соответствует тяпИСКлючения2.  Некоторые стандартные классы Java разработаны с таким расчетом, чтобы генериро— вать исключения в случае ошибки. Код, в котором используются эти классы, должен обя— зательно содержать соответствующие блоки try и catch, иначе компилятор его не пропус— тит, заставив внести исправления. Исключения удобны тем, что их можно передавать “вверх” по иерархии методов. Например, если метод А вызывает метод Б, и в методе Б ге— нерируется исключение, то он может либо сам обработать его, либо передать “вверх” в ме— тод А для дальнейшей обработки. Исключительные ситуации в Java— это довольно обширная тема для изучения. Не— которые примеры работы с ними будут рассмотрены далее в связи с файловым вводом— выводом.  Файловый ВВОД-ВЫВОД  В Java имеется полный набор классов для всех возможных операций по вводу из файлов и выводу в файлы. Рассмотреть файловые операции во всех подробностях в рамках этой книги не удастся, поэтому ограничимся текстовым вводом-выводом.  День 27-й. Дополнительные средства Java 663 
Чтение из текстовых файлов  Чтение из текстовых файлов выполняется с помощью классов FileReader и BufferedReader, входящих B пакет java.io. При создании экземпляра класса FileReader B ero конструктор передается имя файла для чтения (B случае необходимости, с полным путем):  FileReader inFile = new FileReader(HMg_¢afina);  Затем ссылка на объект FileReader используется для создания экземпляра класса BufferedReader:  BufferedReader buff = new Bufferedreader(inFile);  BOT теперь уже можно выполнять чтение текста из файла с помощью метода readLine. Этот метод считывает одну строку текста и возвращает ее как объект типа String. Если дос— тигнут конец файла, он возвращает специальное значение null. Повторяя вызов readLine многократно до получения результатагшіі, можно считать все содержимое текстового файла строказастрокой. В операциях с файлами ошибки возникают очень часто. Чтобы обработать их, необхо— димо воспользоваться имеющимся в Java оператором try-catch для перехвата исключитель— ных ситуаций. Именно это делается B демонстрационной программе, представленной B лис— тинге 27.1. Перед ее выполнением подставьте B строку 6 имя реально существующего на дискетекстовогосрайла.  Листинг 27. 1 . reading. c — построчное чтение текста из файла   1 import java.io.*; 2 public class ReadTextFile { 3 public static void main(String args[]) { 4 String s; 5: try { 6° FileReader inFile = new FileReader("c:\\test.txt"); 7 BufferedReader buff = new BufferedReader(inFile); 8: boolean endOfFile = false; 9: while (zendOfFile) { 10: s = buff.readLine(); 11: if (3 == null) 12: endOfFile = true; 13: else 14: System.out.println(s); 15: } 16: buff.close(); 17: } 18: catch (IOException e) { 19: System.out.println("An error occurred: " + e.toString()); 20: } 21: } 22: }   PB! ЪБ!!! (если файл не существует): ЦВ An error occurred: java.io.FileNotFoundException: c:\test.txt  (The system cannot find the file specified)  664 Дополнительная неделя. Основные вопросы 
(если файл существует): Pe3unhmam This is a test file.  It contains 3 lines of text. This is the last line.   (№№“! На этом занятии все имена файлов следуют стандартам Windows и DOS. Если же используется UNIX или другая п0добная операционная система, то, конечно, имена файлов должны подчиняться ее требованиям. Напри- мер, в UNIX имена подкаталогов в пути к файлу обычно отделяются зна— ками деления (/), а не обратными косыми чертами (\).     № То, что программа выведет на экран, зависит от содержимого конкретного тек- стового файла. Строки 1—4 не0днократно встречались ранее и не требуют поясч нений. В строке 5 начинается блок try, обозначая код, в котором может возникнуть исклю- чительная ситуация. В строке 6 создается объект типа FileReader, который ассоциируется с файлом c:\test.txt. Обратите внимание на двойную косую черту \\, представляющую раз— делитель \ в полном имени файла — это Один из специальных символов Лача. B строке 7 соз— дается объект класса BufferedReader, ассоциированный с только что созданным объектом класса FileReader. B строке 8 создается переменная типа Boolean, служащая признаком кон- ца файла. В строке 9 начинается цикл while, повторяющийся все время, пока переменная endOfFile равна false. B строке 10 из файла считывается строка текста. В строках 11—15 анализируются данные, полученные из файла. Если строка равна null, переменная endOfFile также делается равной null, и цикл заканчивает работу. В противном случае введенный текст выводится на экран. В строке 16' файл закрывается, а в строке 17 завершается блок try. Строки 18—20 с0держат блок catch, код которого выполняется в том случае, если внутри блока try возникнет исключительная ситуация типа IOException. При этом на экране отображается сообщение об ошибке, показанное в результате вывода (см. выше).  Запись в текстовые файлы  Для записи текста в файл применяется класс FileWriter. Объект этого класса создается следующим образом:  FileWriter outFile = new FileWriter(HMz_41afina, режнм_добавлення);  Параметр имя_файла соответствует имени файла (при необходимости с полным путем). Аргумент рехнм_добавлення учитывается только в том случае, если файл с заданным именем уже существует. Для добавления новых данных в конец файла этот аргумент должен быть ра— вен true, a для записи данных поверх существующих — false. Затем на основе объекта FileWriter следует создать объект класса Buff eredWriter:  BufferedWriter buff = new BufferedWriter(outFile); Теперь можно воспользоваться методом write объекта BufferedWriter для вывода текста в файл: buff .write ( текст) ; Аргумент техст— это литерал или объект типа String, содержащий строку текста для вывода в файл. Переход на новую строку не выполняется автоматически. Если необходимо,  чтобы текст выводился с новой строки, в его начале следует поместить символ новой строки \п. Запись текста в файл демонстрируется в листинге 27.2.  День 27-й. Дополнительные средства Java 665 
Листинг 27.2. writing. java — запись текста в файл    1 import java.1ang.System; import java.io.*; 2 3 public class WriteTextFile { 4 public static void main(String args[]) { 5: String s; 6- BufferedReader kb; 7 boolean fileError = false; 8: 9: kb = new BufferedReader(new InputStreamReader(System.in)); 10: try { 11: FileWriter outFile = new FileWriter("c:\\output.txt"); 12: BufferedWriter buff = new BufferedWriter(outFile); 13: System.out.println("Enter lines of text to put in the file."); 14: System.out.print1n(WEnter a blank line when done."); 15: boolean done = false; 16: while (ldone) { 17: s = kb.readLine(); 18: if (s.1ength() > 0 ) { 19: в = s + "\n"; 20: buff.write(s); 21: } 22: else 23: done = true; 24: } 25: buff.close(); 26: } 27: catch (IOException e) { 28: System.out.print1n("Error: " + e.toString()); 29: fileError = true; 30: } 31: if (lfileError) 32: System.out.println("File successfully written."); 33: else 34: System.out.println("An error occurred - file not written."); 35: } 36: } РЕЗЦПЬШНШ (если при выводе на диск a: произошла ошибка — B дисководе нет дискеты):  Error: java.io.FileNotFoundException: a:\output.txt(The device is not ready) An error occurred - file not written.  Pea пьшаш (“"" все "Р°ШЛ° без ОшибОК): Ц Enter lines of text to put in the file.  666  Enter a blank line when done. This is line 1. _ This is line 2. This is the last line.  File successfully written.  Дополнительная неделя. Основные вопросы 
№ Строки 1——5 не нуждаются в комментариях. В строках 6 и 9 создается объект класса BufferedReader для чтения строк текста с клавиатуры. Этот прием бьш  рассмотрен на занятии 26. В строке 10 начинается блок try, B котором ожидаются потенци— альные ошибки. В строках 11 и 12 создаются объекты классов FileWriter и BufferedWriter, используемые для записи текста в файл. В результате создается файл с именем output.txt B корневом каталоге на диске С :. При необходимости имя и путь к файлу можно изменить. В строках 13 и 14 на экране отображаются инструкции для пользователя. В строке 15 соз— дается и инициализируется булевский флаг завершения вывода с именем done. B строке 16 организуется цикл while, условие выполнения которого состоит в том, чтобы флаг done был равен false. B строке 17 считывается строка текста с клавиатуры, а в строке 18 проверяется длина этого текста. Если длина не равна нулю (т.е. пользователь действительно что—то ввел), то после введенного текста добавляется символ конца строки, а затем текст записывается в файл (строки 19—20). Если длина текста равна нулю, т.е. введена пустая строка, то флагу done присваивается значение true, и тем самым цикл завершает работу. Строки 27—30 содержат блок catch, соответствующий оператору try B строке 10. Если генерируется исключение, то в результате его перехвата на экране появляется сообщение об ошибке, и переменная fileError устанавливается в true. Если же исключение не генерирует- ся, то блок catch не выполняется вообще. Наконец, в строках 31—35 на экран выводится одно из двух итоговых сообщений в зави- симости от значения флага fileError — false или true.  Работа с графикой и оконным интерфейсом  Всякий современный язык программирования должен иметь средства для работы с графикой. Консольных программ, рассматривавшихся до сих пор, вполне достаточно начинающему про- граммисту на Java для ознакомления с основами языка. Однако на сегодняшний день почти ника- кая практически важная программа не может обойтись без диалоговых окон, меню и других эле- ментов графического интерфейса. Разработчики Java подошли к решению этой проблемы путем создания библиотеки Abstract Window Toolkit (AWT). Библиотека AWT является, пожалуй, са— мой сложной часгью Java — о ней написаны целые книги. Здесь будет представлено только не- сколько примеров и краткий обзор. Чтобы узнать больше, обратитесь к специальной литературе.   ("Willa B Java есть и другие библиотеки для работы с графическими интерфей- сами, например, Swing. Рассмотрение этих библиотек выходит за рамки нашей книги.     Создание приложений с оконным интерфейсом  Окна в программах на Java основаны на объектах класса Frame. Любая программа, ис— пользующая оконный интерфейс, имеет заголовок следующего вида:  public class MyWindowingApplication extends Frame {  }  B классе Frame имеются практически все необходимые свойства и методы для работы оконного интерфейса, например, строка заголовка, параметры границ окна, возможность  День 27-й. Дополнительные средства Java 667 
изменения размеров окна и т.д. В дочерний класс можно добавить любые дополнительные средст— ва, необхоцимые данной конкретной программе. В листинге 27.3 создается окно, а затем в нем отображается сообщение. Можно убедиться, что средствами Java это делается достаточно просто.  Листинг 27.3. window. java — выводсообщения в окне   1: import java.awt.*; 2: 3: public class AWT_Test1 extends Frame {  // Конструктор. public AWT_Test1(String title) {  7: super(title); 8: Label lbl = new Label("Hello from Java", Label.CENTER); 9: lbl.setFont(new Font("Helvetica", Font.PLAIN, 16)); 10: add("Center", lbl); 11: } 12: 13: public static void main(String args[]) { 14: AWT_Test1 Test = new AWT_Test1("A Java Window"); 15: Test.resize(350,250); 16: Test.show(); 17: } 18: }   Pfi3unhmam CM. рис. 27.1.  ЁЁА Java Window   Hello from Java  ‘ ..….“     Рис. 27.1. Результат выполнения программы из листинга 2 7. 3  № В строке 1 этой программы импортируются классы AWT. B строке 3 объявляет- ся класс (программа) AWT_Test1 как подкласс класса Frame. Строки 6—11 содер—  жат конструктор класса. Он объявлен с одним аргументом типа String, который представля- ет собой заголовок окна. В строке 7 заголовок передается в конструктор суперкласса с помо— щью ключевого слова super —— другими словами, в метод-конструктор класса Frame (см. под— робнее об этом на врезке внизу). Остальные операторы конструктора служат для создания  668 Дополнительная неделя. Основные вопросы 
нового объекта “метка” с текстом "Hello from J ava" (строка 8), выбора начертания и высо— ты для шрифта метки (строка 9) и отображения метки B центре окна (строка 10). В методе main выполняются следующие операции. В строке 14 создается переменная с именем Test, инициализируемая новым экземпляром класса AWT_Test1. При создании нового экземпляра вызывается конструктор класса (строки 6—11), и B него передается строка "A Java window". Затем эта строка передается B конструктор суперкласса, как описано выше. В строке 15 размеры окна устанавливаются равными 350х250 пикселям. Наконец, B строке 16 окно отображается на экране. Итак, работа с графикой с помощью библиотеки AWT целиком основывается на создании и применении объектов. Это идеально согласуется с объектно-ориентированным характером самого языка Java.   @…“ Ключевое слово super используется внутри ПОДкласса для вызова метоцов его суперкласса. Например, оператор super.someMethod( ); вызывает метод someMethod, принадлежащий к суперклассу. Использование слова super без имени меТОДа соответствует вызову конструктора суперкласса. Единствен- ное место, где это можно сделать, —-—- первая строка в конструкторе ПОДклас— са. Следует отметить, что конструктор суперкласса в любом случае вызыва- ется автоматически из конструктора л0дкласса, однако с помощью операто- ра super можно еще и указать аргументы для конструктора.     Рисование линий и геометрических фигур  В библиотеке AWT имеется множество классов для рисования двумерных и трехмерных фигур. Классы для рисования основных геометрических форм содержатся B пакете java.awt.geom. Для каждой базовой формы там есть соответствующий класс: rectangle (прямоугольник), line (прямая), ellipse (эллипс) и т.д. Общая процедура создания и ото- бражения геометрических фигур такова:  1. Создается окно (объект Frame), как это демонстрировалось B предыдущем примере.  2. Создается по одному экземпляру каждой разновидности фигуры, которую необходимо нарисовать: прямой линии, прямоугольника, эллипса и т.д. Задаются свойства этих фи— гур — координаты, размеры, цвет. 3. Создается класс, производный от класса Canvas. B этом классе определяется метод paint, содержащий код для рисования фигур. Метод paint автоматически вызывается операци— онной системой всякий раз, когда изображение на экране требует обновления. 4. Создается экземпляр класса, объявленного на шаге 3, и помещается B окно, созданное на шаге 1. Как видите, операции по созданию объектов фигур и отображению их на экране строго разграничены. Таков объектно—ориентированный подход к работе с графикой. Это довольно сложная и обширная тема для обсуждения. Чем пытаться разъяснить здесь все детали, лучше привести пример демонстрационной программы. В листинге 27.4 создается окно, и B нем отображаются несколько простых геометрических фигур.  Листинг 27.4. Drawing . java — рисование геометрических фигур с применением классов AWT   1: import java.awt.*; 2: import java.awt.geom.*;  День 27—й. Дополнительные средства Java 669 
public class DrawingTest extends Frame { Shape shapes[] = new Shape[4]; public DrawingTest (String title) {  ЮЧФШОЬШ .. о. .. ......  super(title); setSize(500, 400); 9: drawShapes(); 10: add("Center", new MyCanvas()); 11: } 12: public static void main(String args[]) { 13: DrawingTest app = new DrawingTest("Drawing test"); 14: app.show(); 15: } 16: void drawShapes () { 17: shapes[0] = new RectangleZD.Double(12.0,12.0, 98.0, 120.0); 18: shapes[1] = new EllipseZD.Double(150.0, 150.0,90.0,30.0); 19: shapes[2] = new RoundRectangle2D.Double(200.0, 25, 20: 235.0, 250.0, 50.0, 100.0); 21: GeneralPath path = new GeneralPath(new Line2D.Double(100.0, 22: 350.0, 150.0, 300.0)); 23: path.append(new Line2D.Double(150.0, 300.0, 24: 200.0, 350.0), true); 25: path.append(new Line2D.Double(200.0, 350.0, 26: 250.0, 300.0), true); 27: path.append(new Line2D.Double(250.0, 300.0, 28: 300.0, 350.0), true); 29: shapes[3] = path; 30: } 31: 32: class MyCanvas extends Canvas { 33: public void paint(Graphics graphics) { 34: Graphics2D gr = (Graphics2D) graphics; 35: for (int i=0; i<4; i++) 36: gr.draw(shapes[i]); 37: } 38: } 39: }   Результат CM- РИС- 27-2.  В строках1 и 2 импортируются нужные классы AW'I'. B строке4 программа объявляется подклассом Frame, автоматически становясь приложением с окон-  ным интерфейсом. В строке 5 объявляется массив типа Shape, представляющего собой класс AWT для рисования геометрических примитивов. В строках 6—11 определяется конструктор класса, который выполняется при создании эк- земпляра программы (не забывайте, что программа — это тоже объект). В строке 7 с помо- щью оператора super заголовок окна программы передается в конструктор суперкласса Frame. B строке 8 размер окна программы устанавливается равным 500 пикселей в ширину и 400 пикселей в высоту. В строке 9 вызывается метод drawShapes (определяемый позже) для  670 Дополнительная неделя. Основные вопросы 
создания фигур, а в строке 10 создается, добавляется и центрируется в окне экземпляр класса MyCanvas (также определенного в другом месте). Строки 12—15 содержат метод main. выполняемый при запуске программы. В данном слу- чае этот метод очень короткий и простой, потому что большая часть операций программы выполняется другими методами. В строке 13 создается экземпляр программы. При этом ав- томатически вызывается ее конструктор, определенный в строках 6—1 1. B строке 14 окно программы отображается на экране.          if  L „_     Рис. 27.2. Фигуры, нарисованные на экране программой из листинга 27. 4  Метод drawShapes B строках 16—30 выполняет всю работу по созданию объектов геомет- рических фигур. В строках 17—19 создаются три различные фигуры, которые помещаются в элементы 0, 1 и 2 массива shapes[ ], объявленного ранее. В строке 21 создается объект класса GeneralPath, используемый для объединения нескольких отдельных графических элементов в одну фитуру. В этом операторе сразу же определяется первый элемент фигуры — отрезок прямой линии. Затем в строках 23—28 к фигуре добавляется еще три линии, а в строке 29 по- лучившийся графический объект помещается в третий элемент массива shapes[ ]. Обратите внимание на то, что здесь объекты геометрических фигур только создаются, но не отобража- ются на экране. В строках 32—38 определяется класс MyCanvas, производный от класса Canvas библиотеки AWT. Как подразумевает его имя (“сапуаз” означает “холст”), этот класс служит для создания поверхности, на которой можно рисовать объекты. Экземпляр класса MyCanvas помещается в окно программы в строке 10, после чего он готов к использованию. В строках 33—37 опреде- ляется метод paint (фактически, он замещает собственный метод класса Canvas c тем же именем), Этот метод вызывается автоматически всякий раз, когда изображение окна необхо- димо обновить, например, при запуске программы или восстановлении окна после минимиза- ции. В коде метода создается экземпляр класса GraphicsZD, a затем выполняется цикл по элементам массива shapes[ ]. В этом цикле каждая из фигур рисуется в окне путем вызова метода draw.  День 27-й. Дополнительные средства Java 671 
 Рекомендуется Не рекомендуется        ч\п сс; … yr“ * .1. ‚_ я ‚: _, ‹. , &', ‹ ' ‘ ”I \, ""’ \; 1 ‹ „чита „№ срыве; 3№№ ог чтобы » , ‚\ a» ‘; а 'u - "1.1 , r _ т": ‚ r ; №- , & _ ›:А v ду зі “£35251"? , ‚5$ { Ёжъ?“ „"-'.… f A. ` 3/ ‘C .fir% r › ь“, 19° Ъё'ь/ ‘Q ‹ ’ ; ‚\,; % ` «Зи \ `на На. - ‚ЧЗ ** :xafig‘k „5%; "… ,  ‚‚_‚Зе . 6 »” 0‘ $? Hm 33313:;° ди ъ… „`; _ › › >” ( в . N &Ж … V": ‹ , . ** ‚45 _ “&Ч \ \ в№№№№вш … ›    ’ы %“:  ЁЁАЁЦ ' :0 A  «A?  ИСПОЛЬЗОВЭНИЭ КНОПОК И ВСПЛЫВЭЮЩИХ ОКОН  Рисование геометрических фигур —— это, несомненно, очень важная разновидность опера- ций c графикой. Однако гораздо чаще программе на Java бывают необхоцимы стандартные элементы графического интерфейса, такие как кнопки, меню, поля ввода и т.п. В библиотеке AWT имеется полный набор классов для работы со всеми графическими элементами, кото- рые встречаются в приложениях с оконным интерфейсом. Кроме того, Java позволяет рас- познавать события ввода, например, когда пользователь щелкает мышью на пункте меню или кнопке, и реагировать на них соответственно. Несколько позже будет представлена демонстрационная программа, использующая эти возможности. Но вначале —— некоторые общие сведения.  Диспетчеры компоновки  Нередко приходится разрабатывать окна программ, содержащие много элементов интер— фейса —— кнопок, текстовых меток, полей ввода. При этом необходимо придать окну удобный и привлекательный вид, сохранив рациональность интерфейса и его соответствие назначению программы. Некоторые среды разработки, например, Visual Basic, позволяют программисту идеально точно расположить элементы окна, перемещая их мышью или задавая их координа— ты и размеры численно. Разработчики Java отказались от этого способа, поскольку програм— мы на Java призваны работать одинаково на самых разнообразных платформах с различными системами отображения графики. Поэтому был принят другой подход к дизайну окна. Оконный интерфейс программы на Java основан на использовании диспетчера компонов— ки. Диспетчер компоновки ассоциируется с окном или любым другим объектом—контейнером, содержащим элементы графического интерфейса. По мере добавления кнопок и других эле- ментов в контейнер диспетчер компоновки регулирует их расположение внутри окна. По- скольку это происходит прямо в ходе выполнения программы, диспетчер имеет доступ к ин- формации о видеосистеме компьютера и поэтому может расположить элементы в соответст— вии c текущими параметрами экранного вывода. В библиотеке Java AWT имеется пять базовых диспетчеров компоновки. По умолчанию выбирается диспетчер FlowLayout, который располагает элементы слева направо и сверху вниз. Остальные диспетчеры имеют имена GridLayout, BorderLayout, CardLayout и GridBagLayout. Диспетчеры компоновки представляют собой классы, как и практически все компоненты программ на Java, поэтому для использования одного из них необходимо создать экземпляр соответствующего класса:  FlowLayout lm = new FlowLayout();  Затем объект—диспетчер компоновки ассоциируется с объектом-контейнером с использо- ванием метода setLayout. Обычно это делается в конструкторе:  setLayout(lm);  Более подробно эти операции будут показаны в демонстрационной программе, представ- ленной далее.  672 Дополнительная неделя. Основные вопросы 
Обработка событий  Для обработки событий ввода, например, щелчка мыши на кнопке или другом элементе интерфейса, в Java используется меТОД action. OH имеет следующий синтаксис: public boolean action(Event evt, Object arg) {  }  Первый аргумент мет0да—— это объект класса Event, который представляет произошед- шее событие. Второй аргумент зависит от вида компонента, с которым произошло событие. Виды компонентов и соответствующие типы аргумента arg перечислены в табл. 27.1.  Таблица 27. 1 . Вторые аргументы метода action для различных компонентов    Компонент Тип аргумента Данные аргумента Кнопка String Метка кнопки Флажок Boolean Всегда true Переключатель Boolean Всегда true Меню String Метка выбранного пункта Текстовое поле String Текст поля   Moron action вызывается всякий раз, когда фиксируется событие вв0да. В к0де метода анализируются его аргументы и определяется, над каким из компонентов было выполнено действие. Затем производятся ответные операции для реагирования на событие. Метод дол- жен возвращать true, если событие обработано адекватно, и false, если событие не обрабо- тано и должно передаваться вверх по иерархии объектов для обработки в другом месте.  Работа с элементами управления и событиями  Выше обсуждались некоторые общие принципы работы с элементами интерфейса и собы- тиями вв0да. Теперь рассмотрим пример. В приведенной ниже программе используются кнопки, диспетчер компоновки, всплывающие окна, события ввода. Программа отображает на экране главное окно с двумя кнопками. Щелчок на Одной из кнопок открывает всплываю- щее окно, а щелчок на кнопке Close (3aKpblTb) B этом окне закрывает его. Щелкнув на вто- рой кнопке в главном окне, можно закрыть всю программу. В листинге 27.5 приведен класс PopUpWindow, a B листинге 27.6 —— главная программа.  Листинг 27.5. PopUpWindow. java - определение класса PopUpWindow   1: import java.awt.*; 2: 3: class PopUpWindow extends Frame { 4: 5: FlowLayout lm = new FlowLayout(FlowLayout.CENTER); 6: 7: public PopUpWindow(String title) { 8: super(title); 9: setSize(300, 100); 10: setLayout(lm); 11: Button b = new Button("Close");  День 27-й. Дополнительные средства Java 673 
12: add(b); 13: } 14: 15: public boolean action(Event evt, Object arg) { 16: hide(); 17: return true; 18: } 19: }   B строке 3 объявляется класс PopUpWindow, наследующий класс Frame. B строке 5 конструируется объект класса FlowLayout с аргументом CENTER, который указы- вает, что диспетчер компоновки должен центрировать компоненты интерфейса в окне по ме- ре возможности. Строки 7—13 содержат конструктор класса, принимающий аргумент типа String _— заголовок окна. В строке 8 заголовок окна передается в конструктор суперкласса с помощью оператора super. B строке 9 определяется размер всплывающего окна, а в стро- ке 10 указывается, что для расстановки компонентов в окне следует использовать только что созданный диспетчер компоновки. В строках 11 и 12 создается и добавляется в окно объект- кнопка класса Button с надписью Close. B строках 15—18 определяется обработчик событий для данного класса. Поскольку в этом классе возможно всего одно событие —— щелчок мышью на кнопке Close, _— внутри обработ- чика нет необходимости анализировать, какое именно событие произошло. Обработчик про- сто вызывает метод hide для того, чтобы закрыть окно, а затем возвращает true, сообщая тем самым, что событие обработано.  Листинг 27.6. PopUpDemo. java — демонстрация класса PopUpWindow исобытийввода   1 import java.1ang.System.*; 2 import java.awt.*; 3 4 public class PopUpWindowDemo extends Frame { 5: 6' Button open, quit; 7 Frame popup = new PopUpWindow("I am a popup window"); 8: FlowLayout lm = new FlowLayout(FlowLayout.CENTER); 9: 10: public PopUpWindowDemo (String title) { 11: super(title); 12: setLayout(lm); 13: setSize(400, 250); 14: open = new Button("Show pop-up window"); 15: add(open); 16: quit = new Button("Quit program"); 17: add(quit); 18: } 19: public static void main(String args[]) { 20: PopUpWindowDemo app = new PopUpWindowDemo ("Pop-up window demo"); 21: app.show(); 22: } 23:  674 Дополнительная неделя. Основные вопросы 
24: public boolean action(Event evt, Object arg) {  25: if (evt.target instanceof Button) { 26: String label = (String)arg; 27: if (label.equals("Show pop-up window")) { 28: if (£popup.isShowing()) 29: popup.show(); 30: } 31: else { 32: System.exit(0); 33: } 34: } 35: return true; 36: } 37: }   Pnauuhmam CM. рис. 27.3.     ‚‹ мии—о ‚     ‹ х_ш . : ш u * v a и…: за и х мадам - чаши …; h …   Рис. 27.3. Всплывающее окно в демонстраци- онной программе  “№ Первые четыре строки программы в комментариях не нуждаются. В строке 6 соз- даются две переменные класса Button, соответствующие двум кнопкам в окне про—  граммы. В строке 7 создается экземпляр класса PopUpWindow, и B ero конструктор передается текст заголовка окна. При выполнении этого оператора окно создается как объект с помощью конструктора, но еще не отображается на экране. В строке 8 создается объект диспетчера ком- поновки, который будет управлять размещением компонентов в главном окне программы. Строки 10—18 содержат конструктор программы. В строке 11 заголовок окна передается в суперкласс с использованием оператора super. B строке 12 диспетчер компоновки (созданный в строке 8) ассоциируется с окном программы. В строке 13 устанавливаются исходные размеры окна. В сгроках 14 и 17 создаются две кнопки с надписями Show pop-up window (Показать всплывающее окно) и Quit program (3aBepUJl/ITb программу), которые добавляются в окно. Строки 19—22 содержат метод main. OH состоит всего из двух операторов. В строке 20 созда- ется экземпляр класса программы, а в строке 21 окно программы отображается на экране. В строках 24—36 находится метод action данной программы. Вначале B строке 25 про- веряется, не случилось ли какое-либо событие с объектом класса Button. Если это так, опера- тор в строке 26 распознает метку кнопки, на которой щелкнул пользователь, и помещает ее в переменную label. Если это кнопка Show pop-up window, то B строке 28 проверяется, не отображено ли всплывающее окно на экране. Если да, то никакие операции не выполняются,  День 27-й. Дополнительные средства Java 675 
а если нет, то в строке 29 всплывающее окно выводится на экран. Если же пользователь щелкнул на кнопке Quit, программа завершается оператором в строке 35. Как видите, даже такая сравнительно примитивная программа на Java дает возможность поупражняться в достаточно сложных методах программирования с применением кнопок, всплывающих окон, обработки событий ввода. Классы AWT обладают намного более об— ширными возможностями, чем было продемонстрировано, так что приведенные примеры — это только начало.  Программирование аплетов  До сих пор рассказ о Java был посвящен только разработке автономных приложений, т.е. программ, выполняемых самостоятельно в среде операционной системы. Другим основным применением языка Java, причем весьма популярным, является программирование апле- тов — небольших программ, предназначенных для распространения в сети World Wide Web и выполнения в программах-браузерах Web, таких как Netscape Navigator или Microsoft Inter- net Explorer. Аплет Java— это программа, являющаяся органичной частью \Х’еЬ-страницы. При открытии Web-cafi'ra, использующего аплеты Java, 6pay3ep отображает аплет в своем ок- не как компонент \Х’еЬ-страницы. Аплеты могут выполнять самые разные задачи, например, отображение анимационных клипов, выполнение вычислений, или практически любые Дру- гие операции, которые можно себе представить.  Различия между аплетами и приложениями  В общих чертах программирование аплетов Java мало чем отличается от разработки авто- номно выполняемых приложений. Можно даже написать программу на Java, являющуюся Одновременно и полноценным приложением, и аплетом. Различия между приложениями и аплетами на языке Java сводятся к следующим.  I По соображениям безопасности аплетам запрещен доступ к файлам на дисках. Оче- видно, никто из пользователей Internet не захочет, чтобы программа-шпион с Web- страницы вдруг начала “рыскать” по дискам его компьютера, добывая конфиденци- альную информацию или удаляя системные файлы. I Аплеты не нуждаются в методе main. Выполнение аплета управляется браузером, и поэтому метод main не играет в нем никакой роли. Если программа написана так, что- бы быть сразу и аплетом, и приложением, то в ней есть метод шаіп, однако при ее вы- полнении как аплета он игнорируется.  I Программирование аплетов в целом несколько проще, чем приложений, потому что браузер берет на себя некоторые детали управления программой —— в частности, пози— ционирование и масштабирование окон, т.е. те операции, о которых приложение должно заботиться само.  I Аплеты не выполняют консольный ввод-вывод.  Структура аплета  Для первого знакомства с аплетами рассмотрим листинг 27.7. Это не настоящий аплет, а всего лишь его костяк. Далее будут подробно рассмотрены его составные части.  676 Дополнительная неделя. Основные вопросы 
Листинг 27.7. applet. java — костяк аплета на языке Java   1: import java.applet.Applet; Ё: public class AppletTest extends Applet { Ё: public void init() {} 3: public void start() {} 3: public void stop() {} %?: public void destroy() {} ii: public void paint() {} 14: }   Строка 1. Все аплеты должны импортировать класс java.applet.Applet B до- полнение к другим необходимым классам.  Строка 3. Аплет должен быть объявлен как подкласс стандартного класса Applet. B этом классе инкапсулирована большая часть функциональных средств, выполняющих всю заку- лисную работу аплета. Строка 5. Метод init вызывается первым при запуске аплета. Его можно считать анало- гом конструктора. Строка 7. Метод start вызывается автоматически непосредственно после метода init. Bo многих отношениях он аналогичен методу main автономно выполняемых приложений. Строка 9. Метод stop вызывается при завершении аплета — всегда перед вызовом мето- да destroy. Строка 11. Метод destroy вызывается самым последним перед выходом из аплета. Строка 13. Метод paint вызывается всякий раз, когда изображение аплета на экране требует обновления. В этот метод помещается код для отображения графического интерфейса аплета. Важно знать и помнить, что ни один из этих методов не вызывается программистом самостоятельно. Они вызываются автоматически тогда и при таких условиях, когда это необходимо, из программы-браузера, в среде которой выполняется аплет. В методы аплета помещается сответствуюший код для выполнения необходимых инициализаций, рабочих операций и освобождения памяти по завершении аплета. В некоторых простейших аплетах используется только Один—два метода из вышеперечисленных, тогда как более сложные требуют применения всех ДО единого.  Помещение аплета на \А/еЬ—страницу  Большинство современных браузеров Web имеют встроенную поддержку Java, но некоторые старые версии ее лишены. Кроме того, даже если браузер поддерживает аплеты Java, они могут быть отключены (блокированы) по различным причинам. К счастью, в этом случае браузеры не объявляют всю \УеЬ-страницу нечитаемой — они просто игнорируют ап- лет и показывают все остальное. Для включения аплета в \УеЬ—страницу используется дескриптор <APPLET> B коде HTML этой страницы. Его синтаксис имеет следующий вид:  День 27—й. Дополнительные средства Java 677 
<АРРЬЕТ Name = ИМяКЛассаАплета Code = ПОЛНЫЙЦУТЬКАПЛВЩУ WIDTH = Ш HEIGHT = B АЬТ=текст > </APPLET>  ИмяКлассаАплета — это имя файла класса (имяаплета. class), созданного при компиляции аплета. ПолныйПутьКАплету указывает полный путь к этому файлу класса. Значения Ш и в со- ответствуют ширине и высоте окна аплета в пикселях. Строка текст соответствует текстово- му сообщению, которое должно отображаться в окне браузера при отсутствии полдержки Java. Этот элемент необязателен, но рекомендуется для соблюдения хорошего стиля. Web- страница может содержать сколько уг0дно аплетов, каждому из которых должен соответст- вовать свой собственный дескриптор <APPLET>. BOT пример одного из вполне реальных деск- рипторов: <applet name="AppletTest" code="AppletTest" codebase=filez/C:/WINDOWS/jws/AppletTest"  width="200" height="100" align="Top" alt="If you had a java-enabled browser, you would see an applet here." > </applet>  Пример аплета  Аплеты представляют собой чрезвычайно увлекательную тему для изучения в курсе про- граммирования. Им посвящены целые книги по Java. B завершение нашего третьего занятия по Java читателю предлагается пример аплета, иллюстрирующий основные особенности раз- работки этой разновидности программ. Эта программа отображает текст в окне браузера и меняет его цвет по щелчку на этом тексте. В листинге 27.8 приведен к0д HTML, используемый для вывода аплета на экран. Листинг 27.9 представляет к0д самого аплета. Для загрузки аплета в браузер необх0димо отредактировать дескриптор <APPLET> так, чтобы он указывал на папку, содержащую файл AppletTest. class.  Листинг 27.8. AppletTest . html — код HTML для отображения аплета AppletTest Ha экране  <html> <body> Here is the applet: <applet name="AppletTest" code="AppletTest" codebase="filezAppletTest"   width="400" height="100" align="Top"  alt="If you had a java—enabled browser, you would see an applet here."  678 Дополнительная неделя. Основные вопросы 
</applet> </body> </html>   Листинг 27.9. AppletTest. java — простой аплет на языке Java   : import java.applet.Applet; : import java.awt.*;  1 2 3 4: public class AppletTest extends Applet { 5: Font f = new Font("TimesRoman", Font.BOLD, 36); 6: boolean useRed = true; 7 8  : public void paint(Graphics screen) { 9: screen.setFont(f); 10: if (useRed) 11: screen.setColor(Color.red); 12: else 13: screen.setColor(Color.blue); 14: screen.drawString("This is an applet!", 5, 30); 15: } 16: 17: public boolean mouseDown(Event evt, int x, int y) { 18: useRed = EuseRed; 19: repaint(); 20: return true; 21: } 22: }   P83flllhmlfl! CM. рис. 27.4.    . * \--‘ › or ' \ " :__ .'А‘ .-‘ - _ .` _ ’ ’ '~ .ъ "’15 5g» „" `, $“: .53:  щ   $$$» ;., _›_:‚_::‚‘— и ::..-: ...… «m. Jew: i- . om. №; aim" № __94 c \WINDOWS\meletTesthietTesLhml "’“… ', 'г— .. “… Ам‘кщ'” 'г ".—_и г.… 17.x mam  _:_    Here is the applet:        Рис. 27.4. Аплет Applet Test, выполняемый в окне Microsoft Internet Explorer  День 27-й. Дополнительные средства Java 679 
“№3 В строках 1 и 2 импортируются нужные классы ~— в данном случае класс Applet и библиотека AWT. B строке 4 объявляется класс данного аплета как наследник класса Applet. B строке S создается объект класса Font для представления шрифта. Для шрифта задается гарнитура Times Roman, полужирное начертание и кегль 36 пунктов. В строке 6 объявляется переменная типа Boolean для управления цветом отобра- жаемого текста. Строки 8—15 содержат метод paint, автоматически вызываемый из суперкласса Applet при необходимости. В строке 9 указывается, что объект шрифта, созданный ранее в строке S, должен использоваться для вывода текста на экран. В строке 10 проверяется значение флага useReg. Если он равен true, то в строке 11 цвет текста становится красным. Если флаг цвета равен false, в строке 13 выбирается синий цвет текста. Наконец, в строке 14 текст выводится на экран с помощью метода drawstring. B строках 17—20 определяется метод, используемый для реагирования на действия мы- шью — в данном случае на щелчок мыши в области аплета. В коде этого метода флаг useReg меняет свое значение на противоположное, после чего вызывается метод repaint. Метод repaint принадлежит суперклассу Applet. Одной из его обязанностей является вызов метода paint из класса AppletTest для перерисовывания текста в новом цвете.  чм  виш <APPLET> представляет собой Один йз множества дескрипторов Языка  HTML или языка разметки гипертекста, на котором написаны Web- страницы. Для эффективной разработки ресурсов Web рекомендуетсяё знать основы языка HTML — даже при том, что современные средства Web- -дизайна берут на себя основную работу по созданию дескрипто- ров HTML. ‚ , _ „ \ „,  ‹ ,… _-…ммдм Mvavfi“: ‚мм…/и Мм„м-а№ _№ “№…… ...—‚№ “.д.—…а; „…“-№ ‚…,-мА.. „…… ими ничо-шт“ „. …м‹м ` „ммм.… „меж.….. …и…»  r {V % Ё r i $ $ ; ! 3 Ё»  Резюме  Этим занятием завершается вводный курс языка Java. Этот язык отличается от С и С+`+ тем, что вынуждает программиста строго следовать принципам объектно—ориентированного программирования. Такая обязательность поначалу делает этот язык несколько сложнее в изучении, чем его предшественники. Однако по мере его освоения оказывается, что с помо— щью средств Java многие задачи программирования решаются проще и быстрее, чем может показаться. Кроме того, программы на этом языке сравнительно легко отлаживать и дораба— тывать. Такая разновидность программ на Java, как аплеты, широко используется в World Wide Web. Ha следующем занятии будет рассмотрен один из новейших языков программиро- вания — С#, имеющий ряд сходных черт с Java.  Вопросы и ответы  Как в Java выполняется обработка ошибок, возникающих при выполнении про- граммы? В языке Java ошибки выполнения называются исключениями. Всякий фрагмент кода, в котором может возникнуть исключение, должен быть заключен в блок try. Каждый такой блок должен сопровождаться одним или несколькими блоками catch для обработки исклю— чений, возникающих (сгенерированных) в бЛоках try.  680 Дополнительная неделя. Основные вопросы 
Обязательно ли применять обработку исключений в программах на Java? Это зависит от назначения программы и классов, которые она использует. В некоторых классах Java перехват исключений обязателен, например, в классах для файлового вв0да и вывода (при таких операциях исключения возникают особенно часто). В некоторых других ситуациях перехват исключений не является обязательным. Тем не менее, наличие в тексте программы дополнительных операторов для корректной и тщательной обработки потенци- альных ошибок выполнения считается признаком хорошего стиля программирования.  Как контролируется размещение в окне программы на Java стандартных элементов оконного интерфейса? Это делается косвенным, а не прямым образом. С окном ассоциируется так называемый диспетчер компоновки, который располагает элементы согласно определенным правилам.  Предположим, графический объект, например, отрезок прямой линии или прямо- угольник‚ создается с помощью конструктора соответствующего класса Java. Происхо- дит ли при этом автоматическое отображение объекта на экране? Нет, не происходит. Создание и отображение графического объекта —— это две совершен- но отдельные операции. Для выв0да созданного объекта на экран используются специальные мет0ды классов AWT.  B чем состоят основные различия между аплетами и приложениями Java? B составе аплета нет метода main. Вместо этого выполнение аплета начинается с методов init и start. Кроме того, аплет не может выполнять чтение и запись дисковых файлов.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления изученного материала. Ответы на них можно найти в приложении Е.  Контрольные вопросы  1. Должен ли каждый блок try сопровождаться блоком catch? 2. Выполняет ли метол write класса BufferedWriter автоматический переход на новую строку при записи в файл? 3. Какой класс является базовым для всех аплетов Java?  4. Какой метод вызывается в приложении с оконным интерфейсом для отображения окна программы на экране?  Каким образом приложение Java реагирует на события вв0да?  Может ли аплет Java содержать метод main?  Какие два метода И В каком порядке ВЫЗЫВЗЮ'ГСЯ В самом начале ВЫПОЛНСНИЯ аплета?  cogent).  Какой метод вызывается самым последним при завершении работы аплета?  День 27-й. Дополнительные средства Java 681 
, `- „„У; т …  . … … _ ...-:-- . ` … «… ' u , IE." „"П- _- П "    v.  Введение в язык С#  Итак, в ходе работы над этой книгой читатель основательно изучил С, бросил беглый взгляд на С++ и попробовал на вкус Java. Последнее занятие посвящается знакомству с еще одним объектно-ориентированным языком программирования, частично происходящим от С и С++. Он известен под именем С# (произносится “си-шарп”). Будут рассмотрены сле- дующие темы. I Достоинства нового языка С# Процесс разработки программ на С# Написание, компиляция и выполнение ознакомительной программы  Типы приложений, которые можно писать на С#  Пример использования С# в Web  Первое знакомство C С#  Язык С# появился совсем недавно ~— он стал доступен широкой общественности с июня 2000 г. Это новый язык, разработанный в “корпорации Microsoft и стандартизованный организацией ЕСМА. Группу создателей С# возглавлял Андерс Хайлсберг (Anders Hejlsberg), носящий звание заслуженного разработчика Microsoft. A. Хайлсберг участвовал в реализации таких программных проектов и языков программирования, как Borland Turbo C++, Borland Delphi и др. Что касается С#, его разработчики постарались собрать воедино все удачные черты существующих языков программирования и усовершенствовать их для достижения максимальной эффективности.   @;ш [ Стандарт С#. установленный ЕСМА, известен под названием ЕСМА-ЗЗ4. 1   С# представляет собой мощный и гибкий язык программирования. Как и другие языки, он пригоден для создания самого широкого круга приложений. Потенциальные возможности С# практически не ограничены. Он уже использовался для реализации таких разнообразных про— ектов, как динамические \МеЬ-сайты, среды разработки и даже компиляторы. Так же, как С++ и Java, язык С# с самого начала задумывался как язык объектно- ориентированного программирования (ООП). В других языках также реализованы принципы 
ООП, но лишь единицы являются целиком и полностью объектно-ориентированными. Впро— чем, при разработке С# созданию \УеЬ-компонентов и интеграции в Web уделялось даже большее внимание, чем его объектно-ориентированному характеру. Несколько позже будет представлено сравнение языка С# с упомянутыми выше языками программирования, а также рассказано о том, какие типы приложений можно писать на С#.  Зачем нужен С#  Многие полагают, что в новом языке программирования не было никакой нужды. Есть та— кая точка зрения, что Java, С++, Perl, Microsoft Visual Basic и другие существующие языки вполне обеспечивают все мыслимые потребности программистов. Как и Java, язык С# имеет свои корни в С и С++, но, несмотря на это, он создавался “с ну- ля”. Разработчики из Microsoft взяли за основу многие удобные средства С и С++, положи- тельно зарекомендовавшие себя, и добавили в язык новые черты, значительно облегчающие программирование. Многие из этих черт имеют аналоги в языке Java. B процессе создания языка разработчики придерживались определенных стратегических целей и задач. В резуль— тате получился язык, о котором его авторы заявляют следующее.  I C# — простой. I C# — современный. I C# — объектно—ориентированный.  Кроме этого, есть и другие причины, по которым стоит изучать С#.  I C# обладает большой гибкостью и мощным потенциалом. I C# лаконичен — в нем совсем мало ключевых слов. I C# имеет модульную структуру. I C# пользуется возрастающей популярностью.  Простота С#  В С# устранены многие сложные и потенциально опасные черты таких языков, как С++, в том числе макросы, шаблоны, множественное наследование, виртуальные базовые классы. Все это средства, вызывающие большую путаницу и порождающие много ошибок при разра- ботке программ на С++. Если вашим первым языком ООП является С#, то будьте уверены —— вам не придется тратить на изучение этих вопросов ни минуты времени. Для многих С# прост в изучении еще и потому, что он происходит от С и С++. Всем, кто знает эти два языка — а в придачу еще и Java, — C# сразу же покажется старым знакомым. Операторы, выражения, арифметические и логические операции, прочие базовые конструк- ции взяты непосредственно из С и С++, а внесенные усовершенствования сделали их еще удобнее. Устранены некоторые лишние, избыточные элементы, что значительно упростило синтаксис. Например, в С++ существует целых три знака операций для работы с членами классов: двойное двоеточие (: :), точка (.) и стрелка (—>). Они употребляются в разных кон- текстах, и не так-то легко запомнить, в каких именно. В С# все они заменены одним- единственным знаком точки. Как эта, так и многие другие черты языка наверняка избавят на- чинающих программистов от лишней путаницы.  !  День 28-й. Введение в язык С# 683 
Современность С#  Что требуется от современного языка программирования? Такие черты, как перехват и обработка исключительных ситуаций, так называемая “сборка мусора”, расширяемые типы данных, высокая степень безопасности коцаі В С# все это присутствует.   @:“… На занятии 9 подробно рассматривались указатели —— неотъемлемая часть . программирования на С. Они же составляют и самый изобильный источник ошибок в программах на С и С++. В С# большая часть проблем и сложно— стей с указателями устранена —— имеются автоматические средства для ра- боты с типами данных и указателями, такие как "сборка мусора" (garbage collection) . Благодаря сборке мусора указатели и области памяти освобож- даются автоматически по мере того, как надобность в них отпадает.     Объектно-ориентироВанный характер С#  Принципы ООП включают в себя инкапсуляцию, наследование и полиморфизм. Эти по- нятия рассматривались в ходе изучения С++ и Java. Все они реализованы и в С#. Напомним, что инкапсуляция —— это объединение данных и функциональных средств в единое целое, На- следование представляет удобный структурный механизм для переноса и расШирения суще- ствующего кода при разработке новых программ и объектов. Полиморфизм —— это способ- ность объекга приспосабливаться к различным способам его использования.  Гибкость и мощь С#  Как уже говорилось, возможности программ на С# ограничены только воображением программиста. Сам язык никаких ограничений не накладывает. С помощью С# можно разра- батывать самые разнообразные и сложные проекты, например, текстовые или графические редакторы, электронные таблицы и даже компиляторы других языков. С# позволяет легко создавать классы, объекты и другие компоненты, легко объединяемые в библиотеки и переносимые в другие программы. Кроме того, при разработке языка учитывалась необходимость его интеграции в Internet. Использование атрибутов позволяет неограниченно расширять функциональные возможности языка без необходимости измене- ния его синтаксиса.  Лаконичность С#  В языке С# используется очень небольшое количество ключевых слов, служащих основой всех его обширных возможностей. Эти ключевые слова перечислены в табл. 28.1. Как видите, многие из них совпадают с ключевыми словами С, хотя, конечно, есть и отличия.  Таблица 28. 1 . Ключевые слова С#   abstract as base bool break byte case catch char checked 7" class const continue decimal default delegate do double else enum event explicit extern false finally   684 Дополнительная неделя. Основные вопросы 
Окончание табл. 28. 1   fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void while    ""…"“ В программах на С# используется еще несколько зарезервированных ~ слов. Формально они не являются ключевыми словами, но относиться к ним следует как к таковым. Эти слова — get, set и value.     Модульная структура С#  Как и в случае Java, код на С# пишется законченными порциями под названием классы, которые содержат функции-члены, или методы. Созданные классы и методы можно легко пе- реносить в другие программы и использовать там c самой минимальной доработкой или во- все без нее. Помещая данные и функции в классы и методы, программист создает хорошо структурированный, модульный, легко переносимый код.  Возрастающая популярность С#  С# — один из новейших языков программирования. В течение всего времени написания этой книги С# стремительно набирал популярность. Хотя его будущее пока что кажется неопре- деленным, есть все шансы, что этот язык получит всеобщее распространение. Для этого сущест— вует целый ряд причин, и одна из самых веских — перспективность платформы Microsofi .NET. Корпорация Microsoft всячески способствует распространению С#. Конечно, никакая ком- пания.не способна сделать свое детище популярным одним только своим желанием. Не так дав- но потерпели полный крах надежды, возлагавшиеся на новую операционную систему Microsofi Bob, несмотря на то, что разработчики добивались широкой популярности этого продукта. У языка С# шансы стать популярным гораздо выше, чем у Microsoft Bob. Неизвестно, пользуются ли сотрудники Microsoft системой Bob B своей каждодневной работе. А вот С# ——- это действительно повседневный инструмент разработчиков из Microsoft. Многие продукты этой корпорации уже частично переписаны на С#. Таким образом, возможности С# непре— рывно тестируются для их приспособления к нуждам программистов. Microsoft .NET — вот действительно серьезная причина, 1’10 которой С# имеет все шансы на успех. Платформа .NET представляет собой новый подход к разработке и реализации приложений. Хотя для работы в .NET можно использовать практически любой язык про— граммирования, С# уверенно завоевывает позиции как наилучшим образом приспособленный к этой среде.  День 28—й. Введение в язык С# 685 
Популярности С# также способствуют все те его черты, которые были перечислены ранее: простота, реализация принципов ООП, модульная структура, гибкость, краткость синтаксиса.  Сравнение С# с другими языками программирования  Часто возникают вопросы, чем же С# отличается от таких языков, как С++ и Java, и стоит ли вообще изучать его.   llplmalue Ha форумах в Internet, посвященных .NET, B числе главных тем широко ~- обсуждаются следующие вопросы.  о В чем разница между Java и С#? 9 Не является ли С# всего лишь клоном Java? о В чем разница между С++ и С#? 9 Что лучше изучать: Visual Basic .NET или С#? __     По утверждению корпорации Microsoft, C# сочетает мощь С++ с простотой Visual Basic. Действительно, С# — очень мощный язык, но настолько ли он прост, как Visual Basic? Что ж, он, пожалуй, посложнее Visual Basic 6, но уж точно проще, чем Visual Basic .NET. Последняя, 7-я версия Visual Basic была переработана заново и переписана “с нуля”, в результате чего этот язык теперь не уступает по сложности С#. Фактически, многие задачи программируются на С# короче и экономнее, чем на Visual Basic. Отказ от многих средств С++, всегда вызывавших у программистов головную боль, не привел к потере языком С# ни малейшей части его функциональных возможностей. Некото- рые из ошибок, постоянно допускающихся при программировании на С++, просто невозмож- но сделать в С#. Это позволяет экономить целые часы, а то и дни на отладку программ.  Типы программ на С#  Полезно знать, какие разновидности программ можно создавать с помощью С#. Основные среди них таковы.  I Консольные приложения. Такие приложения запускаются из командной строки. Практически все примеры программ в этой книге относятся к этому классу приложе- ний, потому что в них используется только текстовый ввод-вывод, и поэтому структу— ра программ остается сравнительно несложной.  I Приложения Windows. Ha C# можно писать программы для Windows, работающие с оконным графическим интерфейсом, который предоставляет эта система.  I Службы Web. Это программы для выполнения самых разных операций, вызываемые по сети Web.  I Приложения ASP.NET. Приложения ASPNET выполняются на \МеЬ-серверах, гене- рируя динамические \Ь/еЬ—страницы.  Кроме этих типов приложений, язык С# также пригоден для разработки программных мо— дулей других типов, в частности библиотек, элементов управления и др.  686 Дополнительная неделя. Основные вопросы 
Разработка программ на С#  Существует несколько сред разработки С#. Корпорация Microsoft добавила поддержку С# в свой пролукт Visual Studio, который теперь включает Visual C# .NET. Это основной из имеющихся редакторов. Но программированием на С# можно заниматься, и не имея интег- рированной среды Visual Studio .NET. Имеется ряд других редакторов и компиляторов С#. Как и Visual Studio .NET, многие из них позволяют пройти все этапы разработки программы без выхода из редактора исходного кода. Кроме того, в таких редакторах исходный текст программы размечается различными цветами, облегчая поиск ошибок. Некоторые из них подсказывают, что необходимо ввести в тот или иной момент, и обладают мощной интерактивной справочной системой. Если у вас нет редактора С#, не беда. В большинстве операционных систем есть стан- дартные текстовые редакторы, пригодные для набора программ. Например, Microsoft Win- dows включает редакторы Notepad и WordPad. Как будет сказано позже, в отличие от компиляторов С, С++ и Java, компилятор С# мож- но получить бесплатно. Поэтому можно приступать к изучению языка С# прямо сейчас, не теряя ни минуты и не затратив ни копейки!  Имена файлов исходного коца  Файлы исходного кода программ на С обычно имеют расширение .с, хотя это и не обяза- тельно. Программы на С++ обычно снабжены расширением .срр, а программы на Java —— расширением . java. Для программ на С# следует использовать расширение .cs. Как и в npe- дыдущих случаях, это необязательное расширение, которое, тем не менее, согласуется с об- щепринятои практикои.  Выполнение программы на С#  Для работы с С# очень важно понимать, как выполняются программы, написанные на этом языке. Дело в том, что no принципу их выполнения они отличаются от программ на дру- гих языках программирования. Программы на С# предназначены для выполнения в среде Common Language Runtime (CLR). Это означает, что при отсутствии такой среды в операционной системе попытка за- пустить программу на С# не сработает. Преимуществом создания программ для промежуточной исполняющей среды является их переносимость. Программы на С или С++ невозможно скомпилировать для выполнения сразу в нескольких разных операционных системах — придется делать несколько исполняемых мо- дулей. Например, если программа на С должна работать и под Windows, и под Linux, то не- обходимо скомпилировать ее два раза, создав два разных исполняемых файла —— один для Windows, второй для Linux. B случае же С# создается один исполняемый файл, который бу- дет работать в обеих системах. Если важно быстродействие программы, то лучше компилировать программу для выпол- нения в конкретной операционной системе, без промежуточных исполняющих сред. Про- граммы на С и С++ именно так и создаются —— после компиляции они работают в конкретной операционной системе без всяких посредников. Что касается С#, то его компилятор генерирует исполняемый файл не на машинном языке, а на так называемом промежуточном языке — intermediate Language (IL). Поскольку такой файл не может выполняться операционной системой напрямую, требуется еще одна  День 28-й. Введение в язык С# 687 
“инстанция” для компиляции с промежуточного языка на машинный. Эти функции возложе— ны на CLR или другую совместимую с С# исполняющую среду. Первое, что CLR делает с файлом на промежуточном языке, —— выполняет его оконча- тельную компиляцию. В ходе этого код переводится с переносимого промежуточного языка на окончательный машинный язык, который понимает система. Фактически, CLR компили- рует только некоторые части программы для экономии времени. Кроме того, после полной компиляции фрагмента в конкретной системе его уже не придется больше компилировать, потому что готовый машинный код сохраняется для последующего использования.   №№! Поскольку исполняющей среде приходится компилировать промежуточ- ный файп перед его первым выполнением, первый запуск программы на С# занимает больше времени, чем программ на С или С++. Однако в дальнейшем эта разница во времени исчезает, поскольку используется уже полностью скомпилированный файл. Эту же операцию можно проде— лать и в ходе установки программы.     "ВВЫЙ №№…" окончательная компиляция программы на С# называется также компиляцией по требованию (Just In Time compiling или сокращенно fitting).  Компиляция исходного к0да С#  Для создания кода на промежуточным языке используется компилятор С#. Обычно для запуска программы на компиляцию вводится команда csc с именем файла исходного кода после нее. Например, для компиляции программы radius .cs необходимо ввести в командной строке следующее:  csc radius.cs  B интегрированной среде разработки с графическим интерфейсом компиляция выполня— ется еще проще. Обычно для этого достаточно щелкнуть на пиктограмме или выбрать пункт меню. После компиляции щелчок на другой пиктограмме или выбор соответствующего пунк- та меню позволяет запустить программу на выполнение. Подробности можно найти в доку— ментации к конкретной среде разработки. В результате компиляции получается файл на промежуточном языке (Ц,—файл). ll Просмотрев список файлов в каталоге, в котором выполнялась компиляция, можно обнаружить файл с тем же именем, что и файл исходного кода, но с расширением .ехе вместо .сз. Это и есть файл скомпилированной программы, называемый также ассемб- лерным файлом (assembly). Такая программа уже готова к выполнению в среде CLR —— ас— семблерный файл содержит всю информацию, которая необходима исполняющей системе для окончательной компиляции и запуска программы. После компиляции программы в [Ь—файл можно набрать ее имя в командной строке для отправки на выполнение (или запустить любым другим способом, принятым в конкретной операционной системе). Как уже говорилось, программа запустится только в том случае, если на компьютере установлена среда Microsoft .NET Runtime. Если это не так, будет выдано со— общение об ошибке.  Компилятор С# и среда .NET Runtime  Чтобы компилировать программы на С#, необходимо иметь компилятор этого языка. 3 предыдущем разделе говорилось, что необходима также среда Microsoft .NET Runtime. Эта  388 Дополнительная неделя. Основные вопросы 
среда входит в состав платформы Microsofi .NET Framework вместе с компилятором С#. Платформу .NET Framework можно загрузить бесплатно с \УеЬ-сайта Microsoft.  Пример программы на С#  В листинге 28.1 демонстрируется простейшая программа на языке С#. Она называется hello.cs и выполняет всего лишь отображение на экране слов Hello, World!. Программы такого типа традиционно используются для ознакомления с программированием.  Листинг 28.1 . hello. cs —- простейшая программа на С#   1: class Hello 2: { 3: static void Main() 4: { 5: System.Console.WriteLine("Hello, Worlds"); 6: } 7: }   Из строки 1 видно, что программы С# заключены в классы. Это согласуется с принципами ООП. Главной точкой входа в программу является метод Main( ). B этом проявляется следо- вание традициям С и С++. Единственным отличием от С является написание имени Маіп с большой буквы вместо маленькой. Поскольку в языке С#, как и в С, различается регистр сим- волов, это важная особенность, о которой следует помнить.  Основы консольного вывода  В рамках одного занятия нет возможности сколько-нибудь подробно рассказать o C#. B качестве небольшого примера здесь будут рассмотрены всего лишь два метода. Они пред- назначены для консольного вывода данных и имеют следующие имена:  l System.Console.WriteLine() l System.Console.Write() Оба метода выводят информацию на экран в текстовом режиме. Единственная разница между ними состоит в том, что WriteLine() выводит свои аргументы в отдельной строке. Это похоже на работу функции puts( ) B C. Метод Write( ) выводит данные без перехода на новую строку подобно функции printf( ) B C. Данные, которые следует вывести на экран, передаются в эти методы как аргументы. На- пример, если нужно вывести строку текста, ее помещают в двойные кавычки и передают в  один из методов, заключая аргумент в скобки. Следующий оператор выводит на экран текст "Hello World":  System.Console.WriteLine("Hello World");  B следующих примерах на экран выводятся другие строки текста:  System.Console.WriteLine("This is a line of text"); System.Console.WriteLine("This is a second line of text");  День 28-й. Введение в язык С# 689 
последовательное выполнение ЭТИХ операторов даст следующее:  This is a line of text This is a second line of text  Рассмотрим еще два оператора. Каким будет результат их выполнения?  System.Console.WriteLine("Hello"); System.Console.WriteLine("World!");  Можно подумать, что на экране появится следующий текст: Hello World!  Однако на самом деле это не так. Результат будет таким:  Hello World!  Заметьте, что каждое слово выводится в отдельной строке. Если же для вывода этих строк воспользоваться методом Write( ), получится как раз то, что нужно: Hello World! Как видите, разница между двумя методами состоит в том, что WriteLine( ) автоматиче-  ски переходит на новую строку после вывода текста, тогда как Write( ) этого не делает. Лис- тинг 28.2 демонстрирует оба метода в действии.  Листинг 28.2. display. cs — использование методов WriteLine( ) и Write( )   // display.cs - вывод c помощью WriteLine и Write   1: 2: // 3: 4: class diSplay 5: { 6: public static void Main() 7: { 8: System.Console.WriteLine("First WriteLine Line"); 9: System.Console.WriteLine("Second WriteLine Line"); 10: 11: System.Console.Write("First Write Line"); 12: System.Console.Write(“Second Write Line"); 13: 14: // Передача параметров 15: System.Console.WriteLine("\nWriteLine: Parameter = {0}", 123 ); 16: 17: System.Console.Write("Write: Parameters = {0} and {1}", 456, 789); 18: } 19: }   First WriteLine Line Ившшшшп Second WriteLine Line First Write LineSecond Write Line  WriteLine: Parameter = 123 Write: Parameters = 456 and 789  B строках 8 и 9 вызывается метод System.Console.WriteLine( ) для вывода двух  Атина фрагментов текста. Из результата видно, что оба фрагмента выводятся в отдельных  690 Дополнительная неделя. Основные вопросы 
агмента текста выводятся в одной строке без перехода на новую. В строках 15 и 17 демон- стрируегся применение этих же методов со спецификациями вывода. Следует отметить, что :ёспециф`икации в С# реализованы по- другому, чем в С. В функции printf ( ) языка С специфи— {нации формата вывода начинаются со знака процента, а в С# они заключаются в пары фигур— Ёвых скобок. Кроме того, сами спецификации представляют собой не обозначения типов, а {номера выволимых аргументов. Первый из них соответствует {0}, второй —— {1} и т.д.  I  ЕЁБгроках. В строках 11 и 12 вызывается метод System.Console.Write(). B результате два Р  {. @@.“ Как и в С, отсчет номеров, индексов и т.п. в С# начинается с нуля, а не * с единицы.      ;: Рассмотрим еще один пример оператора вывода в С#: 55ystem.Console.Write("Value l is {0} and value 2 is {1}", 123, "Brad”);  B результате его работы на экране отображается следующее: ЁЧа1ие 1 is 123 and value 2 is Brad  >  Как видите, в отличие от С, при консольном выводе данных нет необходимости заботить— ‚?‘ся о соответствии типа и спецификации формата. Несмотря на то, что выводилось число и }‘гекстовая строка, спецификации вывода выглЯДели Одинаково. Компилятор сам обо всем по— ‚ізаботился. Тем не менее, в языке С# есть и спецификации формата, позволяющие изменить іпредставление данных при необхолимосги.   ;@;lllll Важно знать. что методы WriteLine() и Write() являются частью среды .NET Framework, B которую могут интегрироваться программы на любых языках программирования, в том числе Visual Basic .NET.     C# и Web  Кроме стандартных типов приложений, предназначенных для выполнения в среде опера— .ционной системы, С# пригоден также для разработки \\]еЬ—приложений. Конечно, подробно "рассказать об этом в рамках одного занятия невозможно, поэтому ограничимся примером, представленным далее в листинге 28.3. Здесь С# используется в среде ASPNET как средство создания \УеЬ-приложения типа "hello world". Для его выполнения потребуется Web- ‚.сервер, поддерживающий ASP.NET.  Листинг 28.3. hello.aspx — приложение ASP.NET типа "hello world", ’HanMCaHHOB Ha языке С#   1: <script runat="server" language="C#"> 2: void doClick(object sender, EventArgs e) { 3: MyLabel.Text = "Не11о, world! (С# ASP.NET in actionl)"; 4: 5: </script> 6: <html> 7: <head><title>Hello World C# ASP.NET App</title></head> 8: <body> 9: <P>C# Web application...</P> 10: <form runat="server"> 11: <asp:button runat="server" text="Say Hello"  День 28-й. Введение в язык С# 691 
12: onclick="doClick" />  13: <p> 14: <asp:label runat="server" text="" id="MyLabel" /> 15: </form>  16: </body> 17: </html>   Ha рис. 28.1 представлена программа hello.aspx, выполняемая при открытии \МеЬ-страницы. Щелчок на кнопке дает результат, показанный на рис. 28.2.            _ё‘йнёіъё *0“me нп Къ; Winn?” ЧТЗ gafii ‘W’fiWWW‘WfiWfi‘ N“ = _ На вы у… Рашіез Tools Heb He Edit View Favorites Tods Heb & {Эт. . @ д'? . {’;} }‚ээщь » {Энци - g} 3.33 Мёд „‚ і—ЭЗнисЬ » *’ min‘iwfivwwfiva __ „З.Ы ” №” QWLMW-W LEE-6°; №1” o ! 3mm» № o Г ;.]… aw; a» С# Web apphcafion С# ch apphcanon. Say Hello ‘ 1 Say HelloVi   Hcflo, шопа! (С# ASPNET In acnonl)  у          £30m „, "iitoca'mm ; $10000 7 … :, " @щгчщжч 2.3 Рис. 28.1. Выполнение программы Рис. 28.2. Результат после щелчка hello.aspx npu открытии Web- на кнопке и выполнения соответ- страницы ствующего метода С#  “№3 ._ Это сравнительно короткая программа с очень небольшим объемом выполняе- ` ` , мых операций. Она относится к классу \\]еЬ-приложений и нуждается в сервере с полдержкой ASPNET для своего выполнения. Такую поддержку обеспечивает, например, Microsoft Internet Information Server. При первом запуске программы открывается \\]еЬ-страница, показанная на рис. 28.1. Строки 6—1 7 листинга 28.3 напоминают обычный кол HTML для вывода на экран диалоговой формы. Строки 10, ll и 14 содержат дополнительную команду ASPNET runat=“server“. За этим единственным исключением, все остальное в этих строках написано на обычном HTML. Язык С# используется в строках 1—4 внутри сценария ASP.NET. Этот сценарий выполняется при щелчке на кнопке формы. В строке 1 С# выбирается в качестве языка сценария. Вся ра- бота делается в строке 3: заданный строковый литерал ассоциируется с текстовой меткой, благоларя чему и появляется на экране. В данном случае \\іеЬ-приложение на языке С# было совсем простым. Но можно разрабо- тать и очень сложные программы на С# для включения в динамические \УеЬ-страницы, кото- рые будут выполнять множество разнообразных операций. Идея приведенного примера со- стояла в том, чтобы показать, как легко приложения на С# интегрируются в Web.  Резюме  В начале этого занятия было сказано о том, что С# открывает новые горизонты в програм- мировании, являясь Одновременно гибким, мощным и объектно-ориентированным языком. Об- суждались те его черты, которые делают язык простым и в то же время сверхсовременным.  692 Дополнительная неделя. Основные вопросы 
Затем была рассмотрена простейшая программа на С#, этапы ee компиляции и выполне- ния. Было сказано несколько слов о консольном выводе данных в С#. В целом следует отме- тить сильное сходство между синтаксисом С# и его предшественников, С и С++. В конце этого занятия действие переместилось в глобальную сеть Web. Было пр0демонст- рировано простое приложение типа "hello world", написанное на языке С# и выполняемое на платформе ASP.NET.  Вопросы и ответы  Какие книги рекомендуются для изучения С#? Можно порекомеНДовать пособие “Использование С#. Специальное издание”.  Может ли программа на С# выполняться на любой аппаратно-программной платформе? Нет. Программа, написанная на С#, может выполняться только в системах, в которых ус- тановлена среда Соттоп Language Runtime (CLR). Если скопировать исполняемый файл в систему, не имеющую CLR, будет выдано сообщение об ошибке, и программа не заработает. В системе Windows, лишенной CLR, обычно выдается сообщение о нехватке нужной дина- мической библиотеки (DLL).  Какие файлы передаются другим пользователям, если необхоцимо предоставить им программу, написанную на С#? Одним из достоинств языка С# является то, что его программы компилируемы. Другими словами, после компиляции исхолного юда получается исполняемый файл. Чтобы распро- странить программу hello среди друзей и знакомых, достаточно отдать им файл hello.exe. Файл исходного КОДа, hello.cs, им не будет нужен, как не понадобится и компилятор С#. Единственное, что им действительно нужно для выполнения этой программы, —— среда вы- полнения С#, например, Соттоп Language Runtime от Microsoft.  Где взять компилятор С#? Компилятор С# является частью среды Microsofi .NET Framework. Ее можно загрузить с \УеЬ-сайта Microsofi (www.microsoft . com).  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления изученного материала и упражнения для приобретения практических навыков. Ответы на них можно найти в приложении Е.  Контрольные вопросы  Назовите три достоинства языка С#, делающих его достойным выбором для изучения.  Что означают сокращения IL и CLR?  Из каких этапов состоит процесс разработки программы?  Какой командой запускается на компиляцию программа my_prog . cs?  СЛА?!“—  Какое расширение следует присваивать файлам программ на С#?  День 28-й. Введение в язык С# 693 
6. Пригодно ли имя f ilename.txt для файла программы на С#?  7. Если после компиляции программы она работает не так, как ожидалось, то что следует делать в этом случае?  8. Что такое машинный язык?  9. Какие два оператора используются для вывода данных в программах на С#?  Упражнения  1. Загрузите ехе-файл, созданный путем компиляции программы из листинга 28.1, в текстовый редактор. Похож ли он на файл исходного кода? (Не сохраняйте файл при его закрытии.) 2. Введите и скомпилируйте приведенную ниже программу. Что она делает? class AClass  {  H ..  static void Main() { int x,y; for ( х = 0; x < 10; x++, System.Console.Write( "\n" ) ) for(y=0;y<10;y++) " System.Console.Write( "X" );  o- с. со .. .. со .. со ..  ‚_в  694 Дополнительная неделя. Основные вопросы 
sAms пввпіі вампвтпятепьип Неделя 4  Итоги  Вот и закончился основной учебный курс этой книги. С завершением последней, дополни- тельной недели ваши знания С пополнились сведениями о таких языках, как С++, С# и Java. Однако наиболее важным итогом дополнительных занятий следует считать не знание тех или иных конкретных языков, а общее знакомство с идеями объектно—ориентированного про— граммирования (ООП). На занятии 22 говорилось о том, что наиболее многообещающее пре- имущество ООП состоит в хорошей переносимости кода в новые проекты. Для ООП харак- терна реализация таких принципов, как полиморфизм, инкапсуляция и наследование. На занятиях 23 и 24 рассматривались основы языка С++. Было введено понятие класса -— фундаментальной конструкции для создания объектов. Рассматривалось как определение классов, так и создание их объектов. Кроме классов, обсуждались и другие средства С++, такие как:  I встраиваемые функции —— функции, тела которых при компиляции непосредственно копируются в место вызова для ускорения работы;  I перегрузка функций —— повторное использование имени функции с другим списком параметров; I параметры по умолчанию — для параметров функций могут задаваться значения, подставляемые автоматически при отсутствии нужных аргументов;  I функции-члены — составные элементы классов; I конструкторы — функции, вызываемые при создании объектов классов; I деструкторы —— функции, вызываемые при уничтожении объектов классов.  Занятия 25—27 были посвящены основам языка Java. Ha занятии 25 рассматривались ос— новные компоненты программы на Java, a также используемые в нем ключевые слова, иден- тификаторы, управляющие операторы и типы данных. На занятии 26 было изучено определе- ние классов, объектов и пакетов Java. Под классом в этом языке понимают независимый программный компонент, определяемый своими свойствами (переменными, содержащими данные) и методами (функциями—членами, выполняющими операции). Также на занятии 26 рассказывалось о том, как с помощью оператора import полключать к программе на Java внешние классы и пакеты, не входящие в разрабатываемый программ- ный проект. В конце этого занятия была рассмотрена исключительно мощная техника насле- дования, характерная для ООП, которая позволяет легко создавать новые классы на основе существующих. Занятие 27 было последним в кратком курсе Java. Рассматривался перехват исключитель- ных ситуаций с целью обработки ошибок, чтение и запись файлов, а также основы работы с 
графикой в Java. B конце занятия был представлен пример аплета Java и рассказано об осо- бенностях этой разновидности программ. Дополнительная неделя завершилась занятием 28, посвященным С#. Этот новейший из языков программирования создавался в расчете на широкую популярность, которую ему, по всей видимости, удастся заслужить. Было рассмотрено несколько простых демонстрацион— ных программ на языке С#. В ходе дополнительных занятий 22—28 был всего лишь заложен фундамент будущих зна- ний по программированию на С++, Java и С#. Тем не менее, взяв за основу этот вводный курс и углубив свое образование‚ читатель наверняка сможет через самое короткое время присту- пить к разработке своих собственных программ и аплетов.  696 Неделя 4 
SAMS *- ПриложениеА поооіі самостоятельно  Таблица кедов и символов ASCII    Десятичный Шестнадцате- Символ Д есятичный Шестнадцате- Символ код ричный код код ричный код 0 00 null 22 16 - 1 01 ( 23 17 0 2 02 ) 24 18 T 3 03 c 25 19 $ 4 04 о: 26 1А —› 5 05 Q 27 1 В ‹— 6 06 g 28 1C 2 7 07 |:] 29 1D ) 8 08 3 30 1 Е > 9 09 о 31 1 F ? 10 ОА 4 32 20 пробел 11 OB % 33 21 ! 12 ос & 34 22 " 13 оо * 35 23 # 14 ОЕ 36 24 $ 37 25 % 15 0F 38 26 & 16 10 39 27 ' 17 11 ‹ 40 28 ( 18 12 D 41 29 ) 19 13 U 42 2A * 20 14 1] 43 28 + 21 15 §  44 2С   
  Десятичный Шестнадцате- Символ Десятичный Шестнадцате- Символ    код ричный код код ричный код 45 2D - 84 54 Т 46 2Е 85 55 U 47 2F / 86 56 V 48 30 0 87 57 W 49 31 1 88 58 X 50 32 2 89 59 Y 51 33 3 90 5A 2 52 34 4 91 5B [ 53 35 5 92 5C \ 54 36 6 93 5D 1 55 37 7 94 5E ^ 56 38 8 95 5F _ 57 39 9 96 60 58 3A 97 61 a 59 3B ; 98 62 b 60 3C < 99 63 с 61 3D = 100 64 d 62 3E > 101 65 e 63 3F ? 102 66 f 64 4o @ 103 67 g 65 41 A 104 68 h 66 42 B 105 69 i 67 43 C 106 6A j 68 44 D 107 6B R 69 45 E 108 6C 1 70 46 F 109 6D m 71 47 G 1 10 6E n 72 48 H 1 11 6F 0 73 49 я 112 70 p 74 4A J 1 13 71 q 75 4B K 1 14 72 r 76 4C L 1 15 73 s 77 4D M 1 16 74 t 78 4E N 1 17 75 u 79 4F 0 1 18 76 v 80 50 P 1 19 77 w 81 51 Q 120 78 х 82 52 R 121 79 y 83 53 S 122 7A 2   698 Приложение А 
  десятичный Шестнадцате- Символ десятичный Шестнадцате- Символ    код ричный код код ричный код 123 76 { 161 А1 і 124 7с 1 162 A2 6 125 7D } 163 A3 о 126 7Е ~ 164 A4 в 127 7F A 165 A5 N 128 80 С 166 A6 a 129 81 CI 157 A7 o 130 62 é 158 А8 a 131 63 2“! 169 А9 ‚_ 132 64 5 170 АА „ 133 35 д 171 А8 V2 134 35 3 172 АС % 135 37 C 173 AD »? 135 33 "3 174 АЕ « 137 39 ё 175 AF » 133 3A ё 176 во | 139 33 Т 177 B1 I 140 80 T 176 32 I 141 6D `: 179 33 | 142 BE А 160 B4 1 143 8F A 161 вв =1 144 90 Ё 162 B6 1! 146 91 ae 163 87 'п 146 92 76 164 86 1 147 93 б 165 B9 4 148 94 д 166 ВА || 149 95 b 1 87 ВВ `'п 150 96 а 133 BC Щ 161 97 а 139 во ” 152 98 y 190 BE 4 153 99 c“) 191 BF 1 154 9A 0 192 С° L 155 9в ‹: 193 C1 i 156 9с г. 194 02 T 157 90 * 195 c3 Ъ 158 9Е 9. 196 С4 — 159 91= f 197 C5 + 160 A0 а 196 C6 |= 199 C7 I}    Таблица кодов и символов ASCII 699 
 десятичный Шестнадцате- Символ десятичный Шестнадцате- Символ    код ричный код код ричный код 200 08 № 231 E7 y 201 09 17 232 E8 ф 202 СА * 233 E9 0 203 CB 1'г' 234 EA Q 204 CC % 235 ЕВ 8 205 CD = 236 ЕС 00 206 CE %% 237 во :3; Ё: i 238 EE 6 239 EF 0 209 … ?. 240 F0 5 21° D2 " 241 F1 + 21 1 D3 ‘L ‘ 212 D4 L 242 F2 г 213 0 5 F 243 F3 5 214 об 1r 244 F4 г 215 D7 ++ 245 F5 J 216 D8 + 246 F6 + 217 D9 J 247 F7 „…. 218 DA I' 248 F3 ° 219 DB I 249 F9 0 220 DC . 25° FA 221 DD I 251 РВ «I 222 DE | 252 F0 „ 223 DF ' 253 FD 2 224 E0 (1 254 FE I 225 Е1 В 255 FF 226 E2 г 227 E3 тг 228 E4 2 229 Е5 с 230 E6 „   700 Приложение А 
~~ Приложение Б  5AM5 псвпіі сампсшпяшепьнп  Ключевые слова С и С++  . Идентификаторы, перечисленные в табл. 5.2, представляют собой ключевые (служебные) Ёлова языка С. Они не могут использоваться в программах на С ни для какой другой цели, громе той, которая определена синтаксисом языка. При этом в строковых литералах внутри , войных кавычек их можно использовать без всяких ограничений. E B этом приложении также представлен список ключевых слов С++, не являющихся слу- Экебными словами С. Их назначение здесь не описывается. Они приведены на тот случай, ес- Ёш возможен перенос программы на С в среду компилятора С++, и необходимо избежать не-  и ЗЗЯННОГО использования этих СЛОВ В качестве идентификаторов программы.  Ьаблица 5.2. Ключевые слова языка С  L  E'— rfiKmoqesoe Описание   Ёслово 5?— „ ;іазш Блок кода на языке ассемблера, вставленныи в программу на С énauto Класс памяти, принятый по умолчанию. Переменная создается при входе в блок и уничтожается при выходе из него 'gjbreak Оператор безусловного выхода из операторов for, while, switch, do. . .while ‘case Обозначает блок внутри оператора switch ;.char Элементарный тип данных С 1:» „ „ . ‘сопзт. Модификатор объявления, запрещающии изменение переменнои. Ср. volatile gcontinue Оператор перехода на следующую итерацию цикла for, while или do. . .while Ldefault Блок внутри оператора switch. содержащий код для случаев, не предусмотренных блоками case do Оператор цикла, используемый c ключевым словом while. Такой цикл всегда выпол— няется не менее одного раза 'допЫе Тип данных для хранения вещественных чисел двойной точности с плавающей точкой else Блок в операторе if, содержащий код для выполнения в том случае, если условное выражение оператора равно false enum Перечислимый тип данных, переменные которого могут принимать значения только из строго ограниченного набора extern Модификатор объявлений, обозначающий внешнюю переменную, которая объявле—  1——  на в другом модуле программы 
Окончание табл. 5.2   Ключевое Описание   слово float Тип данных для работы с вещественными числами с плавающей точкой goto Оператор принудительного перехода на заданную метку if Оператор ветвления программы по условию, которое может быть истинным (TRUE) или ложным (FALSE) inline Объявление функции встраиваемой. Везде, где это возможно, тело такой функции подставляется прямо в место вызова int Тип данных для хранения целых чисел long Тип данных для хранения целых чисел большего диапазона, чем int register Модификатор объявления, указывающий, что переменная должна по возможности храниться в регистре про_цессора restrict Модификатор доступа, используемый при объявлении указателей return Оператор выхода из функции и возвращения в вызывающий модуль. Может таюке возвращать значение short Тип данных для хранения целых чисел. Используется редко и в большинстве систем идентичентипуіпъ signed Модификатор объявления, указывающий, что числовая переменная имеет знак и может быть как положительной, так и отрицательной sizeof Оператор, вычисляющий длину элемента данных в байтах static Модификатор объявления, указывающий, что переменная должна храниться в памя- ти постоянно, пока выполняется программа struct Ключевое слово для объявления струкгур — составных типов данных С switch Оператор многовариантного ветвления в зависимости от целочисленного значения. Состоит из блоков case и default typedef Модификатор для создания новых типов на основе существующих union Модификатор для объявления составных агрегатов данных, в которых все элементы занимают один и тот же участок памяти unsigned Модификатор объявления, указывающий, что числовая переменная может прини- мать только положительные значения void Используется для объявления объектов трех видов: функций, не принимающих ар- гументов; функций, не возвращающих значений; нетипизированных указателей, ука- зывающих на данные любого типа volatile Модификатор объявления, разрешающий изменение переменной. Ср. const while Оператор цикла, тело которого выполняется все время, пока заданное в нем условие истинно (равно TRUE) _Bool Тип данных для хранения значений 0 или 1 _Complex Поддержка комплексных числовых переменных. Не обязательна для всех компиляторов  _Imaginary Поддержка мнимых числовых переменных. Не обязательна для всех компиляторов   В дополнение к вышеперечисленным, в языке С++ имеются также следующие ключевые слова:  catch new template except protected try class operator this finally public virtual delete private throw friend  702 Приложение 6 
$АМ$ Приложение В “8805 ШМПСШПЯШВПЬНП  "‘я  Двоичные и шестнадцатеричные числа  Программистам нередко приходится пользоваться числами, выраженными в двоич- ной или шестнадцатеричной системе счисления. В этом приложении объясняется, что это за системы и как с ними работать. Чтобы лучше понять принципы, лежащие в их ос- нове, сначала рассмотрим организацию самой распространенной —— десятичной —— сис- темы счисления.  Десятичная система счисления  Десятичная система счисления общеизвестна и общеупотребительна в повседневной жизни. В ее основе лежит число 10. Любое число выражается в десятичной системе через степени десятки. Возьмем, например, число 342. Первая его цифра, считая справа, дает ко— личество десяток в нулевой степени, вторая —— десяток в первой степени, и т.д. Поскольку 10 в нулевой степени равно 1, а любое число в первой степени равно самому себе, для чис- ла 342 получим следующее:  3 3х102=3х100=300 4 4х1о'=4›‹1о=4о 2 2x1w=2x1=2 Сумма=342  Для работы с десятичной системой необходимо иметь десять различных цифр, от О до 9. Следующие два правила справедливы как для десятичной системы, так и для системы счис- ления с любым другим основанием. I Любое целое число можно разложить в сумму целых степеней основания системы. I B системе с основанием п числа записываются с использованием п различных цифр.  Теперь обратимся к другим системам счисления, отличающимся от десятичной. 
Двоичная система  Двоичная система счисления имеет основание 2, поэтому для записи чисел в ней доста— точно всего двух цифр — 0 и 1. Двоичную систему необходимо знать всем программистам, потому что именно таким способом данные представляются в памяти и на дисках компьюте- ра —— с помощью двух физических состояний “есть ток — нет тока”, “намагничено — не намаг- ничено”. Вот пример двоичного числа и его представления в более привычной десятичной форме (число 1011 записано по вертикали):  1 1xf=1x8=8 0 0xf=0x4=0 1 1xT=1x2=2 1 1х2Ч=1х1=1  Сумма = 11 (десятичное)  У двоичной системы счисления есть недостаток: большие числа записываются в ней очень громоздко, так как требуют огромного количества двоичных цифр.  Шестнадцатеричная система  Шестнадцатеричная система счисления имеет основание 16, т.е. требует шестнадцати раз— личных цифр для записи числа. Для этой цели десять цифр от 0 до 9 дополняются буквами от А до F, представляющими шестнадцатеричные цифры от 10 до 15. Вот пример пересчета шестнадцатеричного числа 2[)А в его десятичный эквивалент:  ’/  2 2х162=2х256=512 D 13х1ё=43х16=дю А 10х1ё=40х1=10  Сумма = 730 (десятичное)  Шестнадцатеричная система удобна в работе с компьютерными данными, потому что ее основание равно степени двойки. Каждая цифра шестнадцатеричного числа соответствует че- тырем цифрам этого же числа, записанного в двоичной системе счисления. Таким образом, шестнадцатеричное число, состоящее из двух цифр, в двоичном представлении будет содер- жать восемь цифр. В табл. B.3 приведены соответствия между некоторыми шестнадцатерич— ными, двоичными и десятичными числами.  Таблица В.З. Шестнадцатеричные числа c их десятичными и двоичными эквивалентами   Шестнадцатеричное число десятичный эквивалент двоичный эквивалент   0 0 0000 1 1 0001 2 2 0010 3 3 0011 4 4 0100 5 5 0101 6 6 0110   704 Приложение B 
Окончание табл. B.3  Шестнадцатеричное число Десятичный эквивалент Двоичный эквивалент   7 7 0111 8 8 1000 9 9 1001 A 10 1010 B 11 1011 C 12 1100 о 13 1101 Е 14 1110 F 15 1111 10 15 10000 го 240 11110000 FF 255 11111111   Двоичные и шестнадцатеричные числа 705 
i S ПриложениеГ ПВВПЙ ВПМПВШПЯШЕПШП  Вопросы переносимости КОДа  Термин переносимость кода обозначает легкость, с. которой исходный код, разработан- ный в одной аппаратно-программной среде, можно использовать в другой. Можно ли, напри- мер, программу, написанную для IBM PC, скомпилировать на рабочей станции под управле- нием UNIX? A Ha компьютере Macintosh? Одна из основных причин, по которой програм- мисты часто выбирают С для разработки своих программ, заключается в хорошей переносимости кода, написанного на этом языке. С является, в принципе, одним из лучших по переносимости языков программирования. Почему “в принципе”? Дело в том, что большинство компиляторов С предлагает разра- ботчикам дополнительные, расширенные возможности языка. Они могут сослужить хорошую службу программисту, но при этом не определены в стандарте С. При попытке перенести программу, использующую расширенные возможности конкретного компилятора С, в другую среду и скомпилировать ее другим компилятором почти наверняка возникнут проблемы, и придется переписывать часть программы. Только в том случае, если программа никогда не будет переноситься из своей родной среды, в которой она разработана, можно со спокойной совестью пользоваться всеми дополнительными возможностями компилятора. Если же во- прос переносимости может возникнуть, то лучше по возможности избегать лишнего. В этом приложении обсуждаются те моменты, которые следует учитывать при разработке хорошо переносимых программ.  Стандарт ANSI  Код становится переносимым не сам собой, а только в том случае, если соблюдается ряд правил и стандартов, которых придерживается как основная масса программистов, так и большинство компиляторов. По этой причине разумно выбирать такой компилятор, который поддерживает стандарт Американского Национального Института Стандартов (America! Na- tional Standards Institute) —— ANSI. Комитет ANSI устанавливает стандарты во многих облас- тях, в том числе и на языки программирования. Практически все компиляторы С можно на- строить на полное соответствие стандарту ANSI. 
Ключевые слова ANSI C  B языке С сравнительно немного ключевых слов. Ключевое слово —— это Идентификатор, зарезервированный для использования только в качестве команды языка программирования. Ключевые слова С приведены в материале занятия 1, а также перечислены в приложении Б. В различных реализациях языка могут существовать также дополнительные ключевые слова. Примерами слов, специфичных для отдельных компиляторов, являются near и huge. Хотя эти дополнительные слова могут использоваться даже не одним, а несколькими компи- ляторами, нет никакой гарантии, что они окажутся переносимыми в среды всех ANSI- совместимых компиляторов.  Чувствительность K регистру  Чувствительность к регистру, т.е. различение больших и маленьких букв, играет важную роль в программировании. В отличие от некоторых языков, где различие между регистрами символов игнорируется, в С большие и маленькие буквы считаются различными. Например, различаются переменные A и а. Это свойство языка демонстрируется в листинге Г. 1.  Листинг Г. 1 . listDOI .с — чувствительность к регистру символов     1: /* * 2: * Программа: listDOl.c * 3: * Книга: Освой самостоятельно С за 21 день * 4: * Назначение: демонстрация чувствительности к регистру * 5: * */ 6: #include <stdio.h> 7: int main(void) 8: { 9: int var1 = 1, 10: var2 = 2; 11: char VARl = 'A', 12: VAR2 = 'B'; 13: float Varl = 3.3, 14: Var2 = 4.4; 15: int xyz = 100, 16: XYZ = 500; 17:  18: printf( "\n\nPrint the values of the variables...\n" ); 19:  20: printf( "\nThe integer values: var1 = %d, var2 = %d", 21: var1, var2 ); 22: printf( "\nThe character values: VAR1 = %с, VAR2 = %c", 23: VARl, VARZ ); 24: printf( "\nThe float values: Var1 = %f, Var2 = %f", 25: Var1, Var2 ); 26: printf( "\nThe other integers: xyz = %d, XYZ = %d", 27: xyz, XYZ ); 28:  Вопросы переносимости кода 707 
29: printf( "\n\nDone printing the values!\n" ); 30: 31: return 0; 32: }  Print the values of the variables... Peagnbmam  The integer values: varl = 1, var2 2 The character values: VARl А, VAR2 B The float values: Varl 3.300000, Var2 = 4.400000 The other integers: хуи = 100, XYZ 500   Done printing the values!  МШШЗ B этой программе используется несколько переменных с Одинаковыми, за исключе- нием регисгра, именами. В строках 9 и 10 объявляются целочисленные переменные  varl и var2. B строках 11 и 12 те же имена, но в верхнем регисгре —— VARl и VAR2 — используются для объявления других переменных. Строки 13 и 14 содержат третий набор объявлений, и снова с отличающимся регистром символов —— две переменные Varl и Var2 объявлены вещественными, с плавающей точкой. Каждая из этих деклараций содержит инициализацию, чтобы переменные можно было позже вывести на экран. Вывоц выполняется в строках 20—25. Как видно из результа- та, все значения, помещенные в переменные, сохранились. Компилятор не перепутал имена. В строках 15 и 16 объявляются две целочисленные переменные с одинаковыми именами, только одно из них в верхнем регистре, а другое —— в нижнем. Обеим присваиваются значе- ния, которые затем выводятся на экран в строках 26 и 27. Хотя для различения имен переменных можно, в принципе, использовать только регистр символов, этого делать не рекомендуется. Не все компьютерные системы, поддерживающие компиляторы С, различают регистр. По этой причине исходный к0д может оказаться непере- носимым. Чтобы добиться полной переносимости кода, рекомендуется всегда давать пере- менным имена, отличающиеся не только регистром символов. Чувствительность к регистру может вызвать проблемы не только с компилятором, но и с компоновщиком. Бывает так, что компилятор различает имена на основании регистра, а вот компоновщик эту разницу игнорирует. Большинство компиляторов и компоновщиков позволяют указать, следует ли игнориро- вать регистр или принимать его во внимание, путем установки какой-либо опции, флажка и т.п. Информацию об этом можно найти в их документации. Если регистр символов игнориру— ется, то при компиляции программы с именами, отличающимися только регистром, будет выдано примерно следующее сообщение об ошибке (конечно, на месте varl может быть лю- бая переменная):  listD01.c: Error listD01.c 16: Multiple declaration for 'varl' in function main *** 1 errors in Compile ***  Переносимость символов  Символы представляются в компьютере в виде числовых кодов. Например, на IBM- совместимом персональном компьютере буква А представляется числом 65, а буква a —— чис— лом 97. Эти числа берутся из к0довой таблицы ASCII (CM. приложение А) .  708 Приложение Г 
При разработке программ для переноса в другие среды нет оснований предполагать, что в них всегда будет использоваться код ASCII. B других компьютерных системах есть свои ко- довые таблицы. Например, на больших ЭВМ, в отличие от персональных компьютеров, сим- вол с кодом 65 может и не представлять букву a.   _.  “дни““ Следует соблюдать осторожность при использовании числовых кодов символов. Они могут оказаться непереносимыми, т.е. не соответствовать тем же символам в другой среде.     Набор символов на практически любой платформе подчиняется двум правилам. Первое из них гласит, что числовое значение символа не может выходить за диапазон типа char. Ha- пример, в системе с восьмиразрядными символами максимальное число, которое можно по- местить в переменную типа char, равно 255. Поэтому в ней не может быть символов с кода- ми, превосходящими 255. Если же допускаются 16-ра3рядные символы, то максимально до- пустимый код символа будет равен 65535. Второе правило, ограничивающее набор символов, состоит в том, что символы представ- ляются только положительными числами. В кодовой таблице ASCII переносимая часть включает символы с кодами от 1 до 127 включительно. Переносимость символов с кодами от 128 до 255 не гарантирована. Символы из этого расширенного набора не могут считаться переносимыми прежде всего потому, что переменная типа char co знаком (signed char) мо- жет принимать положительные значения только от 0 до 127.  Обеспечение совместимости со стандартом ANSI  Для того, чтобы гарантировать совместимость кода со стандартом ANSI, используется стандартная, определяемая компилятором символическая константа __STDC_. Если про- грамма компилируется с включенной АМ51—совместимостью, то эта константа определена (как правило, равна 1). При отключенной АМ51—совместимости константа не определена. Почти любой компилятор поддерживает возможность полной принудительной ANSI- conmecmmou и. Обычно для этой цели устанавливается специальный флажок в ИСР (интегрированной среде разработки) либо передается дополнительный аргумент в комаНДной строке компилятора. Компиляция с принудительной АЫ81-совместимостью практически га— рантирует переносимость кода в другие среды и на другие компиляторы. Например, для компиляции программы в АМЗі-совместимом режиме с помощью компи- лятора командной строки BorIand C++ следует ввести такую команду:  ВСС -А program.c Запуск компилятора Microsoft для этой же цели выполняется следующей командой:  CL /Ze program.c   Примечание В большинстве интегрированных сред разработки имеется возможность компиляции с полной АМЗі-совместимостью. Таким способом можно прак- тически гарантировать переносимость кода на другие платформы.     При включенной АМЗі-совместимости компилятор обеспечивает дополнительную про— верку ошибок. В то же время некоторые сообщения об ошибках и предупреждения в этом  Вопросы переносимости када 709 
режиме перестают выдаваться. В качестве примера можно привести проверку прототипов функций. Обычно компиляторы выдают предупреждение, если в программе отсутствует про- тотип функции, который обычно стоит в коде раньше ее первого вызова. Однако стандарт ANSI не требует обязательного наличия прототипа, вот почему это предупреждение отсутст- вует B режиме АЫЫ-совместимости.  Отказ от стандарта ANSI  Существует несколько причин, по которым можно отказаться от полной совместимости со стандартом ANSI. Самая распространенная из них—— это желание воспользоваться пре- имуществами, дополнительно предоставляемыми компилятором. Многие удобные средства программирования, например, функции управления экраном, не определены стандартом ANSI либо меняются от компилятора к компилятору. При желании воспользоваться этими средствами следует отказаться от АМБі-совместимости. Конечно, при этом ухудшается пере— носимость программы. Позже мы раСсмотрим, как обойти это ограничение.  Рекомендуется Не рекомендуется  Шспользуйте не только регистр симво-’ Ё Не используйте; числовые эначеічия F лов, но и их количество порядок и т || Ё символов —— они могут отличаться в раз-; для того чтобы сделать имена перемен- {“3“ ных системах i ‚* :“,      ‚ if  ‚ I'IJI `“ ) ., ‚" n `, „ |, ‹ > , \ ‚, ‚_” „ н »,к “\{;  \ n \ __…”— мы…“ №№Ё№а м2 …». см`-:…- m. A ! „№ › ) ім.—‚м….  ных различными? :5 ,  ‹ ” .../т.;»: _ …А“ ‚| \ wmwm м;…ьтммм  .д.-дм;—    Переносимость чисел и числовых переменных  Числовые значения, хранящиеся в числовых переменных определенных типов, могут оказаться непереносимыми на другие платформы. Стандарт ANSI определяет всего лишь не— сколько общих правил, касающихся числовых типов и значений числовых переменных. На занятии 3 было рассмотрено, какие значения могут храниться в числовых переменных на !ВМ-совместимых компьютерах. Но приведенная там таблица 3.1 не дает гарантии перено- симости во все аппаратно-программные среды. 'Гипы переменных подчиняются следующим правилам.  I Символьный тип (char) является самым коротким. Переменная типа char занимает один байт памяти. I Короткий целочисленный тип (short) меньше или равен по длине целочисленному типу (int). I Целочисленные переменные (int) меньше или равны по длине длинным целым переменным (long).  I Целочисленные переменные без знака (unsigned) равны по длине целочисленным пе- ременным со знаком (signed).  I Переменные с плавающей точкой одинарной точности (float) меньше или равны по длине переменным двойной точности (double).  710 Приложение Г 
В листинге Г.2 с помощью стандартного способа выясняется, какую длину имеют число- вые переменные в системе, в которой программа скомпилирована и выполняется.  Листинг Г.2. Отображение длины числовых переменных      1 /*““ * 2 * Программа: list002.c * 3 * Книга: Освой самостоятельно С за 21 день * 4 * Назначение: программа отображает размеры типов данных * 5: * в системе, в которой она скомпилирована * б * */ 7 #include <stdio.h> 8 int main(void) 9: { 10: printf( "\nVariable Type Sizes" ); 11: printf( "\n " ); 12: printf( "\nchar %d", sizeof(char) ); 13: printf( "\nshort %d", sizeof(short) ); 14: printf( "\nint %d", sizeof(int) ); 15: printf( "\nfloat %d", sizeof(float) ); 16: printf( "\ndouble %d", sizeof(double) ); 17:  18: printf( "\n\nunsigned char %d", sizeof(unsigned char) ); 19: printf( "\nunsigned short %d", sizeof(unsigned short) );    20: printf( "\nunsigned int %d\n", sizeof(unsigned int) ); 21: 22: return 0; 23: } Variable T e Sizes Peaummam ур char 1 short 2 int 2 float 4 double 8  unsigned char 1 unsigned short 2 unsigned int 2  Как видно из результата, оператор sizeof( ) помогает узнать размер переменной или типа данных в байтах. Представленный результат получен после компиляции и выполнения программы на 16-разрядном [ВМ-совместимом персональном компьютере с 16-разрядным компилятором. В другой системе или при использовании друго- го компилятора программа выдала бы другой результат. Например, в З2-разрядной среде ПРИ компиляции З2-разрядным компилятором размер целочисленного типа (int) равен четырем байтам, а не двум.  Вопросы переносимости кода 711 
Максимальные и минимальные значения  Если B разных системах типы переменных имеют разную длину, то как же узнать диапазон значений, которые можно помещать B эти переменные? Диапазон зависит от количества байт B переменной, а также от наличия у нее знака. На занятии 3 рассматривались диапазоны значений числовых переменных в зависимости от их длины B байтах (см. табл. 3.2). Максимальное и минимальное значение целочисленного типа зависит только от количества битов B переменной. А вот вещественные переменные могут хранить более широкий диапазон чисел за счет снижения точности их представления. В табл. Г.] перечис— лены диапазоны значений как целочисленных, так и вещественных типов С.  Таблица Г. 1 . диапазоны значений числовых типов данных    Количество Максимальное Минимальное Максимальное байт значение без знака значение со знаком значение со знаком целочисленные типы 1 255 —1 28 127 2 65 535 —32 768 32 767 4 4 294 967 295 -2 147 483 648 2 147 483 647 8 1.844674хЕ1'9 Вещественные типы 4* 1.1 Е—38 3.4 E38 8“ 1.7 E—308 1.7 E308 10*“ 3.4 E—4932 1.1 E4932  *Точность до 7 цифр ”Точность до 15 цифр ***Точность до 19 цифр   Знать диапазон значений на основании длины и типа переменной всегда полезно. Но как уже было показано, длину переменных нельзя считать фиксированной B программе, разрабо- танной для переноса B другие среды. Кроме того, нельзя заранее знать и точность представ- ления вещественных чисел с плавающей точкой. Поэтому присваивание следует выполнять с осторожностью. Например, присваивание числа 3000 переменной типа int совершенно безо— пасно. А как насчет 100000? Если переменная имеет тип unsigned int, то B 16-разрялной системе результат окажется непредсказуемым, поскольку максимум диапазона равен 65535. Если же целая переменная имеет длину 4 байта, B нее поместится и гораздо большее число.   `  lllllllll Значения, приведенные в табл. Г.1‚ не обязательно одинаковы для всех — компиляторов. Особенно это справедливо для вещественных типов, для которых используются разные уровни точности. Лучшей переносимости кода можно добиться, используя информацию из табл. Г.2 и Г .3.     Стандарт ANSI определяет ряд символических констант, которые обязательно должны присутствовать B заголовочных файлах limits.h и float.h. Эти константы определяют ко- личество битов B переменных разных типов, а также минимальные и максимальные значения этих типов. Константы, определенные B файле limits.h, перечислены B табл. Г.2. Они отно- сятся к целочисленным типам данных. Константы, определенные B файле float.h, описыва- ют диапазоны вещественных числовых типов и представлены в табл. Г .3.  712 Приложение Г 
Таблица Г .2. Константы стандарта ANSI, определенные в файле limits.h    Константа Значение CHAR_BIT Количество битов в переменной символьного типа CHAR_MIN Минимальное значение переменной символьного типа (со знаком) CHAR_MAX Максимальное значение переменной символьного типа (со знаком) SCHAR_MIN Минимальное значение знаковой переменной символьного типа SCHAR_MAX Максимальное значение знаковой переменной символьного типа UCHAR_MAX Максимальное значение беззнаковой символьной переменной INT_MIN Минимальное значение целой переменной ШТ_МАХ Максимальное значение целой переменной UINT_MAX Максимальное значение целой переменной без знака SHRT_MIN Минимальное значение короткой целой переменной SHRT_MAX Максимальное значение короткой целой переменной USHRT_MAX Максимальное значение короткой целой переменной без знака LONG_MIN Минимальное значение длинной целой переменной LONG_MAX Максимальное значение длинной целой переменной ULONG_MAX Максимальное значение длинной целой переменной без знака LLONG_MAX Максимальное значение очень длинной целой переменной ULLONG_MAX Максимальное значение очень длинной целой переменной без знака   Таблица Г .3. Константы стандарта ANSI, определенные в файле f loat.h    Константа Значение FLT_DIG Количество значащих десятичных цифр в переменной типа float DBL_DIG Количество значащих десятичных цифр в переменной типа double LDBL_DIG Количество значащих десятичных цифр в переменной типа long double FLT_MAX Максимальное значение переменной типа float  FLT_MAX_10_EXP FLT_MAX_EXP  FLT_MIN FLT_MIN_10_EXP  FLT_MIN_EXP  DBL_MAX DBL_MAX_10_EXP  DBL_MAX_EXP  DBL_HIN  Максимальное значение показателя степени в переменной типа float (no осно- ванию 10)  Максимальное значение показателя степени в переменной типа float (no осно- ванию 2)  Минимальное значение переменной типа float  Минимальное значение показателя степени в переменной типа float (no осно- ванию 10)  Минимальное значение показателя степени в переменной типа float (no OCHO? ванию 2)  Максимальное значение переменной типа double  Максимальное значение показателя степени в переменной типа double (no осно- ванию 10)  Максимальное значение показателя степени в переменной типа double (no осно— ванию 2)  Минимальное значение переменной типа double   Вопросы переносимости кода  1713 
Окончание табл. Г.3   Константа  Значение   DBL_MIN_10_EXP DBL_MIN_EXP  LDBL_nAx LDBL_MAX_10_EXP  LDBL_MAX_EXP  LDBL_MIN LDBL_MIN_10_EXP  LDBL_MIN_EXP  Минимальное значение показателя степени в переменной типа double (no осно- ванию 10)  Минимальное значение показателя степени в переменной типа double (no осно- ванию 2)  Максимальное значение переменной типа long double  Максимальное значение показателя степени в переменной типа long double (no основанию 10)  Максимальное значение показателя степени в переменной типа long double (no основанию 2)  Минимальное значение переменной типа long double  Минимальное значение показателя степени в переменной типа long double (no основанию 10)  Минимальное значение показателя степени в переменной типа long double (no основанию 2)   При работе с стантами из табл.  числовыми значениями и переменными бывает удобно пользоваться кон- Г.2 и Г.3. Если то или иное число гарантированно меньше или равно мак—  симальному значению для данного типа (а также больше или равно минимальному), то к0д становится переносимым на платформы с различным представлением чисел. В листинге Г.3 на экран выводятся значения перечисленных выше стандартных констант ANSI, a B листин- ге Г.4 демонстрируется применение некоторых из них. Отображаемые на экране значения мо-  гут варьироваться  Листинг Г.Э. В  В определенных пределах В ЗаВИСИМОСТИ ОТ КОМПИЛЯТОРЗ.  ывод на экран стандартных констант ANSI     1: * * 2: * Программа: listD03.c * 3: * Книга: Освой самостоятельно С за 21 день * 4: * Назначение: вывод стандартных констант. * 5: : */ 6: #include <stdio.h> 7: #include <float.h> 8: #include <limits.h> 9: 10: int main( void ) 11: { 12: printf( "\n CHAR_BIT %d ", CHAR_BIT ); 13: printf( "\n CHAR_MIN %d ", CHAR_MIN ); 14: printf( "\n CHAR_MAX %d ", CHAR_MAX ); 15: printf( "\n SCHAR_MIN %d ", SCHAR_MIN ); 16: printf( "\n SCHAR_MAX %d ", SCHAR_MAX ): 17: printf( "\n UCHAR_MAX %d ", UCHAR_MAX ); 18: printf( "\n SHRT_MIN %d ", SHRT_MIN ): 19: printf( "\n SHRT_MAX %d ", SHRT_MAX ); 20: printf( "\n USHRT_MAX %d ", USHRT_MAX ); 21: printf( "\n INT_MIN %d ", INT_MIN ); 22: printf( "\n INT_MAX %d ", INT_MAX ): 714 приложение!“ 
 23: printf( "\n UINT_MAX %ld ", UINT MAX ›; 24: printf( "\n L0NG_MIN %1d ", LONG-MIN ›; 25: printf( “\n L0NG_MAX %ld ", LONG'MAX ›; 26: printf( "\n ULONG_MAX %e ", НЬ0Ы6_МАХ ›; 27: printf( “\n FLT_DIG %d ", FLT DEG ›; 28: printf( "\n DBL_DIG %d ", DBL—DIG ); 29: printf( "\n LDBL_DIG %d ", LDBE DIG ›; 30: printf( “\n FLT_MAX %e ", FLT_MAX ); 31: printf( "\n FLT_MIN %e ", FLT_MIN ›; 32: printf( “\n DBL_MAX %e ", DBL_MAX ›; 33: printf( "\n DBL_MIN %e \n", DBL_MIN ›; 34: 35: return О; 36: } CHAR BIT 8 CHAR:MIN -128 CHAR_MAX 127 SCHAR_MIN -128 SCHAR_MAX 127 UCHAR_MAX 255 SHRT_MIN -32768 SHRT_MAX 32767 USHRT MAX 65535 INT_MEN -2147483648 INT_MAX 2147483647 UINT MAX -1 LONG—MIN -2147483648 LONG—MAX 2147483647 ULONE_MAX 8.121967e-298 FLT_DIG 6 DBL_DIG 15 LDBL DIG 15 FLT_MAX 3.402823e+038 FLT_MIN 1.175494e-038 DBL_MAX 1.797693e+308 DBL_MIN 2.225074e-308   Для разных компиляторов результаты могут оказаться различными. Те, что приведены после листинга Г.З‚ верны лишь для определенных систем и компиляторов.  @;И“     M B листинге Г .3 нет ничего сложного. Программа целиком состоит из вызовов ПЗ функции printf( ). При каждом таком вызове на экран выводится одна из стан- дартных символических констант. Следует обратить внимание на спецификации формата, со- провождающие вывод каждой из констант —— они соответствуют типам констант, как, напри- мер, %с1, %е, %ld. Выполнение приведенной программы дает полную сводку информации о том, какие числа используются в данной компьютерной системе. Соответствующие макрооп- ределения констант можно найти в файлах float.h и limits.h, a эта программа позволяет выяснить все их значения сразу одним махом.  Вопросы переносимости кода 715 
Листинг Г.4. Использование стандартных констант ANSI   (ID __! (:\ L" ‚Р> LA) [\J V-* an an an an an an an ..  САЗ Р`) Р`) Р`) Р`) Р`) Р`) Р`) Р`) ") ") Р-* Р-* Р-* Р-* Р-* Р-* Р-* Р-* Р-* Ъ—д <=> ") (I) "] (" 1:1 “Db С;) &`) Ъ-д <=> ") (I) *`] (3‘ IJ‘ “Db ") Р`) Ъ-д С:) ") с. с. с. с. с. с. an .. an an an an an an an an an an an an .. ..  шышшшшш "] (:\ LII “Db С;) &`) V-* an .. с. an an an ..    /*——— * * Программа: listD04.c * * Книга: Teach Yourself C іп 21 Days * * * * Назначение: использование пределов числового диапазона. * * Примечание: не все символы с допустимыми кодами могут * * выводиться на экран * *——— */  #include <float.h> #include <limits.h> #include <stdio.h>  int main( void )  {  }  unsigned char ch; int i;  printf( "Enter a numeric value.");  printf( "\nThis value will be translated to a character.");  printf( "\n\n==> " ); scanf("%d", &i);  while( і ‹ o || і › UCHAR_MAX )  { printf("\n\nNot a valid value for a character."); printf("\nEnter a value from 0 to %d == ", UCHAR_MAX); scanf("%d", &i); }  ch = (char) i; printf("\n\n%d is character %c\n", ch, ch );  return 0;  Enter a numeric value. Pnauuhmam This value will be translated to a character.  716  ==> 5000  Not a valid value for a character. Enter a value from 0 to 255 ==> 69  69 is character E  Приложение Г 
№ Листинг Г.4 демонстрирует константу UCHAR_MAX в действии. Новые элементы появляются в этой программе в строках 10 и 11. Как уже говорилось, подклю- чаемые заголовочные файлы содержат определения констант. Если у вас возникли сомнения, нужно ли включать файл float.h B строке 10, то вы правы —— в этом заголовочном файле нет необходимости, поскольку ни одна из констант для вещественных типов не используется. А вот строка 11 необходима. В заголовочном файле limits.h содержится определение кон- станты UCHAR_MAX, которая используется в программе позже. В строках 16 и 17 объявляются переменные, необходимые для работы программы, сим— вольная ch H целочисленная і. Далее выводится сообщение, приглашающее пользователя ввести число. Полученное число помещается в целую переменную, потому что она длиннее, чем символьная, и может содержать числа, выходящие за пределы диапазона символьных ко- дов. Если бы для ввода использовалась символьная переменная, то слишком большие числа урезались бы до значений из символьного диапазона. В этом легко убедиться, заменив і на ch встроке23. В строке 25 с помощью стандартной константы выполняется проверка того, не превосхо- дит ли введенное число максимального значения, разрешенного для символьных переменных без знака. Проверяется именно символьный диапазон, поскольку введенное число, преобра- зованное по таблице ASCII, должно затем выводиться на экран в виде символа. Если это чис- ло выходит за пределы диапазона символьных значений без знака, то пользователь получает сообщение, указывающее допустимый диапазон (строка 28) и приглашающее его ввести пра— вильноечисло. В строке 32 целое число приводится к типу char. B сложных программах это очень важно для сохранения корректности данных. Если и дальше пользоваться переменной типа int вме- сто char, можно пропустить выхолящее за диапазон значение. Но в данной программе это не так важно, и в строке 34 можно использовать і вместо ch для вывода символа на экран.  Классификация чисел  В некоторых случаях необходимо охарактеризовать диапазон, к которому принадлежит то или иное числовое значение. Например, требуется выяснить по коду символа, является ли он цифрой, буквой верхнего регистра, управляющим символом и т.д. Насчитывается почти дю— жина категорий классификации символов. Есть два способа проверить принадлежность сим— вола к той или иной категории. Рассмотрим листинг Г .5, в котором представлен один из спо- собов определения того, является ли символ буквой алфавита.  Листинг Г.Б. Проверка принадлежности символа к буквам алфавита     1: /*—- ---- ----- * 2: * Программа: listD05.c * 3: * Примечание: программа может оказаться непереносимой 4: * из—за ее способа работы с символами. * 5: * """"""" */ 6: #include <stdio.h> 7: int main(void) 8: { 9: unsigned char х = 0; 10: char trash[256]; /* для удаления лишних нажатий клавиш */ 11: while( x != 'Q’ && х != ’q’ ) 12: {  Вопросы переносимости кода 717 
13: printf( "\n\nEnter a character (Q to quit) ==> " ); 14: 15: x = getchar(): 16: 17:` if( х >= 'А' && х <= 'Z') 18: { 19: printf( "\n\n%c is a letter of the alphabet!", x ); 20: printf("\n%c is an uppercase letter!", х ); 21: } 22: else 23: { 24: if( x >= 'a' && x <= '2') 25: { 26: printf( "\n\n%c is a letter of the alphabet!", x ); 27: printf("\n%c is a~lowercase letter!", х ); 28: } 29: else 30: { 31: printf( “\n\n%c is not a letter of the alphabet!", x ); 32: } 33: } 34: gets(trash); /* убираем нажатие клавиши <Enter> */ 35: } 36: printf("\n\nThank you for playing!\n"); 37: return 0; 38: }  Enter a character to uit ==> A Резциьшат (Q q )  А is a letter of the alphabet! А is an uppercase letter!   Enter a character (Q to quit) ==> f  f is a letter of the alphabet! f is a lowercase letter!  Enter a character (Q to quit) ==> 1 1 is not a letter of the alphabet! Enter a character (Q to quit) ==> * * is not a letter of the alphabet! Enter a character (Q to quit ==> q  q is a letter of the alphabet! q is a lowercase letter!  Thank you for playing!  718 Приложение Г 
Manna Эта программа проверяет, НЯХОдится ли код введенного символа между кодами букв A и Z верхнего регистра или же между а и z. Если символ x попадает в один  из этих двух диапазонов, то, наверное, можно смело предположить, что символ является бук- вой алфавита? А вот и нет! Никакой стандарт не оговаривает порядок следования символов в кодовой таблице. При работе с набором ASCII этот трюк с проверкой диапазонов еще рабо- тает, но вот переносимость программы в другие среды уже не гарантирована. Для того, чтобы обеспечить переносимость, следует использовать функции анализа, или классификации, сим- волов. Таких функций несколько, и все они перечислены в табл. Г.4 вместе с описанием их назначения. Функции возвращают 0, если символ не прошел проверку на принадлежность к определенной категории, или ненулевое число в противном случае.  Таблица Г.4. Функции классификации символов    Функция Описание isalnum() Проверяет, является ли символ буквой алфавита или цифрой isalpha() Проверяет, является ли символ буквой алфавита iscntrl() Проверяет, является ли символ управляющим isdigit() Проверяет, является nu символ десятичной цифрой isgraph() Проверяет, является ли символ отображаемым (за исключением пробела) islower() Проверяет, является nu символ буквой в нижнем регистре isprint() Проверяет, является ли символ отображаемым ispunct() Проверяет, является ли символ знаком препинания isspace() Проверяет, является nu символ знаком пустого пространства (пробелом, табуля- цией и т.п.) isupper() Проверяет, является ли символ буквой в верхнем регистре isxdigit() Проверяет, является ли символ шестнадцатеричной цифрой   За исключением равенства, не рекомендуется выполнять никаких других сравнений между кодами символов. Например, вполне можно проверить символьную переменную на равенство букве 'А' , но нежелательно применять операцию “больше или равно”:  if( х › 'А' ) /* нвпврвносимый код:: */ if( == 'А' ) /* Переносимый код */ В листинге Г.6 приведена подкорректированная программа из листинга Г.5. Вместо про-  верки диапазона здесь вызываются функции классификации символов. Программа из листин- га Г.6 обладает намного лучшей переносимостью, чем предыдущая.  Листинг Г.6. Применение функций классификации символов     1: /* * 2: * Программа: listD06.c * 3: * Книга: Освой самостоятельно С за 21 день * 4: * Назначение: представляет другой подход к той же задаче, * 5: * которую выполняет и листинг D.5. Обладает * 6: * гораздо лучшей переносимостью! * 7: * */ 8: #include <ctype.h>  Вопросы переносимости кода 719 
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:  int main(void)  { unsigned char x = 0; char trash[256]; /* очистка лишних нажатий клавиш */ while( x != 'О’ && X != 'Ч' } { printf( "\n\nEnter a character (0 to quit) ==> " ); x = getchar(); if( isalpha(x) ) { printf( "\n\n%c is a letter of the alphabet!", x ); if( isupper(x) ) printf("\n%c is an uppercase letter!", x ); } else printf("\n%c is a lowercase letter!", х ); } } else { printf( "\n\n%c is not a letter of the alphabet!", x ); } gets(trash); /* очищаются лишние нажатия клавиш */ } printf("\n\nThank you for playing!\n"); return 0; }   Е ter character to uit ==> z Резцльшат n a (Q q )  720  z is a letter of the alphabet! z is a lowercase letter!  Enter a character (0 to quit) ==> T  T is a letter of the alphabet! T is an uppercase letter!  Enter a character (0 to quit ==> #  # is not a letter of the alphabet! Enter a character (0 to quit ==> 7  7 is not a letter of the alphabet!  Приложение Г 
Enter a character (Q to quit) ==> Q  Q is a letter of the alphabet! Q is an uppercase letter!  Thank you for playing!  Как видите, результат работы этой программы практически совпадает с резуль- татом работы предыдущей (если по приглашению программы ввести те же сим— волы, что и раньше, то результаты совпадут полностью). В этот раз вместо анализа диапазона применялись функции классификации символов. Обратите внимание на включение заголо— вочного файла ctype.h B строке 8. Он необходим для того, чтобы работать с функциями классификации. В строке 20 с помощью функции isalpha() проверяется принадлежность символа к буквам алфавита. Если символ —— буква, то в строке 22 выв0дится сообщение об этом. Затем в строке 23 проверяется регистр буквы с использованием функции isupper( ). Если x -— буква в верхнем регистре, то об этом выводится сообщение в строке 25. B про— тивном случае отображается сообщение в строке 29. Если символ не является буквой алфа— вита, в строке 34 на экран выводится сообщение. Поскольку в строке 14 организуется цикл while, программа продолжает работу до тех пор, пока пользователь не введет Q или q. Можно подумать, что оператор в строке 14 нарушает переносимость программы, но на са— мом деле это не так. Напомним, что проверка равенства, а также неравенства между сим— волами —— вполне переносимые операции, а вот другие отношения (“больше”, “меньше”) могут оказаться непереносимыми.      Рекомендуется Не рекомендуется “ Используйте функции классификации ,Не используйте конкретных числовых „символов, если необх0димо проверить g значений в качестве границ диапазонов “принадлежностьдсимвопа к определен—3 %для различных типов данных. Чтобы ной категории. \ * % Ё сделать программу переносимой, попь— .Помните‚ что операция "!=" (“не равно”) ; зуйтесь стандартными константами, оп— {,так же хорошо _пёреносима, как и "==" ‹ % ределенными всаголовочных файлах. (“melt-10")“ ‚, ‹ 2 №№ №№    Преобразование регистра символов  Такие операции над символами, как изменение их регистра с верхнего на нижний и на— оборот, часто встречаются в практике программирования. Для этих целей многие разработ— чики пишут функции наподобие следующей:  char conv_to_upper( char х )  { if(x >= 'a' && х <= '2' ) { х -= 32; } return( х ) }  Вопросы переносимости кода 721 
Как уже говорилось, оператор if B этой функции может оказаться причиной непереноси- мости кода в другие среды из-за операций сравнения. Его можно изменить следующим обра— зом с помощью переносимых функций, рассмотренных в предыдущем разделе:  char conv_to_upper( char х )  if( isalpha( х ) && islower( х ) )  { х -= 32; } return( х )  Эта функция уже больше соответствует стандарту ANSI, но все-таки и в ней решены еще не все проблемы с переносимостью. В коде функции предполагается, что буквы верхнего ре— гистра по своим числовым значениями меньше соответствующих букв нижнего регистра на 32. Но это справедливо только в том случае, если используется кодовый набор ASCII. Только в нем 'A' + 32 действительно равняется 'а', однако это далеко не так в произвольной ком- пьютерной системе —— в особенности в системе, вообще не использующей код ASCII. Чтобы решить эту проблему, в стандарте ANSI определены две функции для измене- ния регистра символов. Функция toupper() приводит символы к верхнему регистру, а tolower( ) —— к нижнему. Если переписать предыдущий код с их использованием, получит— ся следующее:  toupper();  Как видите, такая функция уже существует, и нет необходимости писать ее заново. Кроме того, эта функция определена в стандарте ANSI, что обеспечивает переносимость кода.  Переносимость структур и объединений  При работе со структурами и объединениями следует соблюдать осторожность, если про— грамма должна подчиняться требованиям переносимости. Непереносимость кода, исполь- зующего эти конструкции языка, может быть вызвана двумя причинами: порядком следова- ния элементов в структурах и выравниванием по словам.  Выравнивание по словам  Выравнивание по словам является важным фактором переносимости структуры. Оно представляет собой выравнивание элементов данных в памяти компьютера по границам ма- шинных слов. Машинное слово —— это фиксированное количество байт. Обычно его длина со- ответствует разрядности процессора, установленного в компьютере. Например, на I6- разрядном IBM PC слово имеет длину 2 байта, или I6 битов. Поясним сказанное на примере. Рассмотрим следующую структуру. Зная, что переменные типа int занимают два байта, а типа char —— один байт, определите объем памяти, занимае— мой всей структурой: ' struct struct_tag { int x; / * поля типа int имеют длину 2 байта */  722 Приложение Г 
char a; /* поля типа char имеют длину 1 байт */ int y; char b; int 2; } sample = { 100, 'A', 200, 'B', 300 };  Складывая длину символьных и целочисленных полей, получаем 8 байт. Однако в зави- симости от обстоятельств этот ответ может оказаться как правильным, так и неправильным для всей структуры в целом. Если применяется выравнивание по словам, то структура занимает 10 байт. Два возможных случая расположения структуры в памяти показаны на рис. Г .1 и Г .2.  x a b 2 fif‘fif‘dLr-A'fif—fi  61 „то и мило 1| | П? I 7  Puc. Г. 1. Расположение структуры в памяти без выравни- вания по словам  x a у Ь z №№ №№ № /  ‚61 | «тем | имамата I и]  Рис. Г.2. Расположение структуры в памяти с выравнива- нием по словам  Нельзя предполагать, что при выпоЛнении программы выравнивание всегда будет Одним и тем же. Элементы структуры могут выравниваться по границам двух-, четырех-, восьми— байтных слов, и заранее знать это невозможно.  Чтение и запись структур  При чтении или записи структур необходимо соблюдать осторожность. Рекомендуется никогда не пользоваться числовыми константами для обозначения длины структуры или объ- единения. В противном случае файлы для записи или чтения структур окажутся непереноси- мыми, и придется “подгонять” программу пол данные в каждой новой системе. Чтобы этого не произошло, нужно учитывать специфику системы и компилятора автоматически. Вот при- мер оператора чтения, который легко переносится в другие среды:  fread( &the_struct, sizeof( the_struct ), 1, filepointer );  Как видите, вместо числовой константы, обозначающей длину структуры, используется выражение sizeof( ). Независимо от выравнивания структуры в памяти, из файла будет счи— тано нужное количество байт.  Порядок элементов B структуре  При объявлении структуры в исходном к0де программы ее элементы расположены в оп- ределенном порядке. Можно было бы предположить, что в том же порядке они хранятся и в памяти. Но в стандарте не определено, в каком порядке должны стоять поля структур при их расположении в памяти. Поэтому при разработке программы нельзя делать предположений о том, как именно следуют друг за другом элементы составных типов данных.  Вопросы переносимости кода 723 
Директивы препроцессора  На занятии 21 были изучены некоторые директивы препроцессора, часто используемые в программах на С. Многие из этих директив определены стандартом ANSI. B примерах этой книги постоянно встречались две из них: include и define. Но стандарт определяет и ряц других, которые можно использовать для большей гибкости кода. Дополнительные директи- вы, разрешенные для использования в стандарте ANSI, перечислены в табл. Г.5 .  Таблица Г.Б. директивы препроцессора, определенные в стандарте ANSI   #define #if #elif #ifdef #else #ifndef #endif #include #error #pragma   Стандартные константы  Каждый компилятор имеет свой набор заранее определенных констант. Как правило, боль- шинсгво из них специфичны для конкретного компилятора, т.е. непереносимы в другие среды. Но существует также несколько подобных констант, определенных в стшщарте ANSI и поэтому поддерживаемых всеми АМ81-совместимыми компиляторами. Вот некоторые из них.   Константа Описание   __БАТЕ___ Заменяется датой в момент компиляции программы. Дата представляется в виде литерала (текстовой строки в двойных кавычках), в форме "Mm DD, чит". Напри- мер, дате 1 января 1998 г. будет соответствовать строка "Jan DD, 1998"  ___FILE__ Заменяется именем файла исходного кода в момент компиляции. Имеет формат литерала в двойных кавычках  ___LINE___ Заменяется номером строки, в которой данная константа находится в исх0дном ко- де. Имеет формат целого десятичного числа  ___STDC___ Равна 1, если файл компилируется с включенной АМЗі—совместимостыо. Если флаг АМ8|—совместимости отключен, константа не определена  _TIME___ Заменяется текущим временем в момент компиляции программы. Время имеет формат литерала в двойных кавычках "HH:MM:SS", например, "12:15:03"   Использование нестандартных средств B переносимых программах  Вообще говоря, в программе можно использовать константы и другие средства, не опре- деленные стандартом ANSI, и при этом сохранить переносимость кода. Для этого необходи- мо сделать так, чтобы определенные фрагменты кода компилировались только в том случае, если компилятор поддерживает соответствующие средства и возможности. Другими словами, программа должна узнать, какой компилятор используется в данный момент для трансляции программы, и подстроиться под него. Как правило, для этой цели определены символические константы, по которым можно распознать компилятор. Переносимость программы достига-  724 Приложение Г 
ется тем, что в различных средах для одной и той же цели компилируются различные фраг- менты кода. Этот прием демонстрируется в листинге Г .7.  Листинг Г.7. Программа, учитывающая специфику компилятора   С!) "4 C" {J1 “>> С;) bx) Р-‘ II II II II .| II II II  NNNNNNNNl—‘l—‘l—‘l—‘l—‘Hl—‘l—‘HH \lmwwaHoomuamwah-toxo I. I. I. I. I. I. I. I. I. I. I. I. .. .. .. .. ..  bx) bx) ") (I) о. II II .-  С’1 С;) bx) F—‘ С:) .. II .| II II .-  (а) ‚Ь  -=› “>. (A) (a) (A) (a) bx) CZ) ") (I) "] (3‘ .. .. II II .| II .-  .p. р.;  © СА) II  44: 45:   /* * * Программа: listD07.c * * Назначение: демонстрирует использование макро— * * констант для улучшения переносимости программы * * ПРИМЕЧАНИЕ: в разных средах эта программа дает разные * * результаты * * */   #include <stdio.h> #ifdef _WINDOWS  #define STRING "DOING А WINDOWS PROGRAM!\n” #else #define STRING "NOT DOING A WINDOWS PROGRAM\n” #endif int main(void) { printf( "\n\n") ; printf( STRING ); #ifdef _MSC_VER  printf( "\n\nUsing a Microsoft compiler!" ); printf( "\n Your Compiler version is %s\n", _MSC_VER );  #endif #ifdef __TURBOC__  printf( "\n\nUsing the Turbo C compiler!" ); printf( "\n Your compiler version is %x\n", __TURBOC__ );  #endif #ifdef __BORLANDC__ printf( ”\n\nUsing a Borland compiler1\n" ); #endif  return 0;  Вопросы переносимости кода 725 
РВЗЦЛЬШВШ Вот какой результат выдаст эта программа после ее компиляции компилятором Turbo C 3.0 и выполнения в системе DOS:  NOT DOING A WINDOWS PROGRAM  Using the Turbo C compiler! Your compiler version is 300  A это сообщение появится на экране при использовании компилятора Borland C++ B сис- теме DOS:  NO‘I‘ DOING A WINDOWS PROGRAM  Using a Borland compiler!  Наконец, если используется компилятор Microsoft для DOS, результат будет следующим: NOT DOING A WINDOWS PROGRAM  Using a Microsoft compiler! Your compiler version is >>  № В этой программе используются символические константы для того, чтобы оп- ределить используемый компилятор. В строке 9 с помощью директивы препро- цессора #ifdef выполняется проверка, определена ли некоторая константа. Если это так, то компилируются все следующие за директивой операторы, пока не встретится директива #endif. Проверяемая константа носит имя _WINDOWS. После проверки определяется константа STRING, эквивалентная текстовому сообщению. В строке 22 это сообщение выводится на эк- ран, информируя пользователя о том, бьша ли программа скомпилирована в среде Windows. B строке 24 проверяется, определена ли константа _MSC_VER. Ее значение соответствует номеру версии компилятора Microsoft. Если используется другой компилятор, эта константа не определена, а если компилятор Microsoft, то вместо нее в текст подставляется номер вер- сии компилятора. В строке 26 на экран выводится сообщение o том, что программа скомпи- лирована компилятором Microsoft, a B строке 27 —номер версии компилятора. В строках 31—36 и 38—42 выполняются аналогичные операции. В них проверяется, бьш ли использован для трансляции программы компилятор Turbo C или какой-либо из профессио— нальных компиляторов Borland. B зависимости от определенности соответствующих кон- стант на экране отображается информационное сообщение. Как видите, эта программа распознает компилятор, проверяя определенность тех или иных констант. Цель программы — вывод сообщения об используемом компиляторе— не зависит от среды, в которой она выполняется. Если разработчик знает, в какие системы мо- жет переноситься написанный им код, он вправе добавить в программу операторы, исполь- зующие расширенные возможности конкретных компиляторов (одного или нескольких), и организовать компиляцию так, как было показано выше, чтобы программа автоматически подстроилась под новую среду.  Стандартные заголовочные файлы ANSI  Стандарт ANSI определяет ряд обязательных заголовочных файлов, которые должны по- ставляться вместе с любым АМБі-совместимым компилятором. Для создания хорошо перено- симых программ полезно знать, какие именно файлы определены этим стандартом. Список таких файлов вместе с функциями, объявленными в них, приведен в приложении Е.  726 Приложение Г 
Резюме  В этом приложении содержится много важного материала, посвященного проблеме пере- носимости кода. Язык С обладает хорошей, если не наилучшей, переносимостью по сравне- нию с другими языками программирования. Переносимость не возникает сама собой — ее нужно добиваться. Для того, чтобы иметь возможность переноса программ на С из одной среды в другую и их компиляции различными компиляторами, создан стандарт ANSI. При разработке переносимых программ следует обращать внимание на ряд обстоятельств: регистр имен переменных, кодовый набор символов, совместимость числовых значений, размеры типов данных, отношения сравнения между символами, устройство структур и объединений, директивы и константы препроцессора. В конце этого занятия был рассмотрен вопрос, как учесть специфику того или иного компилятора в исх0дном к0де программы.  Вопросы и ответы  Как разрабатываются переносимые программы, работающие с графикой? Станцарт ANSI не устанавливает правил и подходов для программирования графики. Де- ло в том, что машинная графика в значительно большей степени зависима от аппаратных средств компьютера, чем другие разделы программирования, поэтому и обеспечить перено- симость программ с графикой значительно труднее. Если все же необх0димо переносить работу с графикой в разные среды, то желательно от- делить фрагменты КОда с обращением к графическим средствам и поместить их в отдельные модули, вызываемые Одним и тем же способом независимо от платформы. При перех0де в другую среду эти модули необходимо заменить.  Всегда ли необходимо заботиться о переносимости кода? Нет, переносимость коца совсем не обязательна для многих программ. Некоторые из про- грамм, написанных программистом, никогда не выходят даже за пределы его компьютера, а другие всегда выполняются в Одной и той же операционной среде. В таких программах можно применять средства, недопустимые в переносимой программе, например, функцию system( ).  Переносимы ли комментарии, обозначенные символами / / вместо / * и */ ? Согласно станцарту ISO/IEC 9899:1999 (новейшему станцарту ANSI C), комментарии после двух косых черт являются допустимыми. Более ранние версии стандарта ANSI He поддерживают такие комментарии, хотя они и встречаются во многих средах разработки того времени.  Коллоквиум  В этом коллоквиуме вам предлагаются контрольные вопросы для закрепления пройденно- го материала и упражнения для развития практических навыков программирования.  Контрольные вопросы  1. Что важнее: эффективность программы или удобство ее поддержки и доработки?  2. Каков числовой код символа ‘а‘?  Вопросы переносимости кода 727 
“Р.“?  Какое число является гарантированным максимумом для символьного типа данных без знака?  Что означает аббревиатура ANSI? Можно ли употреблять следующие имена переменных в одной и той же программе на С? а) int lastName, 6) LASTNAME, B) LastName, r) Lastname Для чего предназначена функция isalpha( )? Для чего предназначена функция isdigit( )? Зачем используются такие функции, как isalpha( ) и isdigit( )?  Можно ли записывать структуры в файл на диске, не беспокоясь о переносимости данных и кода программы?  10. Можно ли использовать символическую константу __Т!МЕ__ для вывода на экран теку-  щего времени с помощью функции printf( )? BOT пример: printf( " The Current Time is: %s" , _TIME_);  Упражнения  1.  Поиск ошибок. Есть ли ошибки (и если да, то какие) в следующей функции? void Print_error( char *msg )  {  static int ctr 0 CTR = 0 printf(“\n“); for( ctr = 0; ctr < 60; ctr++ ) {  " `  printf("*");  } printf("\nError %d, %s - %d: %s.\n", CTR, __FILE_, __LINE_, msq ); for( ctr = 0; ctr < 60; ctr++)  { printf(“*");  }  Напишите функцию для проверки того, является ли символ гласной буквой латинского алфавита.  Напишите функцию, принимающую символ и возвращающую 0, если символ не является буквой, 1— если символ представляет собой букву в верхнем регистре, и 2 —— если в нижнем. Позаботьтесь о максимально возможной переносимости кода.  Самостоятельная работа. Изучите опции и флаги вашего компилятора. Выясните, какие  параметры следует установить для игнорирования верхнего регистра, выравнивания по байтам, гарантирования совместимости со стандартом ANSI.  728 Приложение Г 
5. Обладает ли следующий код свойством переносимости? void list_a_file( char *file name )  { }  6. Обладает ли следующий код свойством переносимости? int to_upper( int x )  system("TYPE " file__name );  { if(x >= 'a' && x <= '2' ) { toupper( x ); return( х ); }  Вопросы переносимости кода 729 
Приложение Д  5AM5 пввш'і самостоятельно  Стандартные функции С  В этом приложении приведены прототипы функций, объявленные в стандартных заголо- вочных файлах компиляторов С. Функции, имена которых помечены звездочкой, рассматри- вались в нашей книге. Список функций упорядочен по алфавиту. Для каждой функции даны имя, соответствую- щий заголовочный файл и полный прототип. Обратите внимание, что форма этих прототипов отличается от той, которая использовалась ранее — для каждого параметра функции указы- вается только тип, без имени. Вот два примера прототипов, первый из которых следует фор- ме, принятой в этом приложении, а второй — более ранней форме: int funcl(int, int *); int func1(int x, int *y);  u ` ' v B обоих объявлениях задано по два параметра. Первыи имеет тип 1nt, a второи является указателем типа int. Для компилятора эти два объявления абсолютно равноценны.  Таблица д. 1 . Алфавитный перечень основных функций языка С   Функция Заголовочный Прототип функции   файл abort* stdlib.h void abort(void); abs stdlib.h int abs(int); acos* math.h double acos(doub1e); asctime* time.h char *asctime(const struct tm *); asin* math.h double asin(doub1e); assert* assert.h void assert(int); atan* math.h double atan(doub1e); atan2* math.h double atan2(doub1e, double); atexit* stdlib.h int atexit(void (*)(void)); atof* stdlib.h double atof(const char *); atof* math.h double atof(const char *); atoi* stdlib.h int atoi(const char *); atol* stdlib.h long atol(const char *);  
Продолжение табл. Д.!    Функция Заголовочный Прототип функции файл bsearch* stdlib.h void *bsearch(const void *, const void *, size_t, size_t, int(*) (const void *, const void *)); calloc* stdlib.h void *calloc(size_t, size_t); сеі1* math.h double ceil(double); clearerr stdio.h void clearerr(FILE *); clock* time.h clock_t clock(void); cos* math.h double cos(double); cosh* math.h double cosh(double); ctime* time.h char *ctime(const time_t *); difftime time.h double difftime(time_t, time_t); div stdlib.h div_t div(int, int); exit* stdlib.h void exit(int); ехр* math.h double exp(double); fabs* math.h double fabs(double); fclose* stdio.h int fclose(FILE *); fcloseall* stdio.h int fcloseall(void); feof* stdio.h int feof(FILE *); fflush* stdio.h int fflush(FILE *); fgetc* stdio.h int fgetc(FILE *); fgetpos stdio.h int fgetpos(FILE *, fpos_t *); fgets* stdio.h char *fgets(char *, int, FILE *); floor* math.h double floor(double); flushall* stdio.h int flushall(void); fmod* math.h double fmod(double, double); fopen* stdio.h FILE *fopen(const char *, const char *); fprintf* stdio.h int fprintf(FILE *, const char *, ...); fputc* stdio.h int fputc(int, FILE *); fputs* stdio.h int fputs(const char *, FILE *); fread* stdio.h size_t fread(void *, size_t, size_t, FILE *); free* stdlib.h void free(void *); freopen stdio.h FILE *freopen(const char *, const char *, FILE *); frexp* math.h double frexp(double, int *); fscanf* stdio.h int fscanf(FILE *, const char *, ...); fseek* stdio.h int fseek(FILE *,long,int); fsetpos stdio.h int fsetpos(FILE *, const fpos_t *); ftell* stdio.h long ftell(FILE *); fwrite* stdio.h size_t fwrite(const void *, size_t, size_t, FILE *); getc* stdio.h int getc(FILE *);   Стандартные Функции С  731 
!Продолтсениеіпабл.1[1     Функция Заголовочный Прототип функции файл getch* stdio.h int getch(void); getchar* stdio.h int getchar(void); getche* stdio.h int getche(void); getenv stdlib.h char *getenv(const char *); gets* stdio.h char *gets(char *); gmtime time.h struct tm *gmtime(const time_t *); isalnum* ctype.h int isalnum(int); isalpha* ctype.h int isalpha(int); isascii* ctype.h int isascii(int); iscntrl* ctype.h int iocntrl(int); isdigit* ctype.h int isdigit(int); isgraph* ctype.h int isgraph(int); islower* ctype.h int islower(int); isprint* ctype.h int isprint(int); ispunct* ctype.h int ispunct(int); isspace* ctype.h int isspace(int); isupper* ctype.h int isupper(int); isxdigit* ctype.h int isxdigit(int); labs stdlib.h long int labs(long int); ldexp math.h double ldexp(double, int); ldiv stdlib.h ldiv_t div(long int, long int); localtime* time.h struct tm *localtime(const time_t *); log* math.h double log(double); loglO* math.h double loglO(double); malloc* stdlib.h void *malloc(size_t); mblen stdlib.h int mblen(const char *, size_t); mbstowcs stdlib.h size_t mbstowcs(wchar_t *, const char *, size_t); mbtowc stdlib.h int mbtowc(wchar_t *, const char *, size_t); memchr string.h void *memchr(const void *, int, size_t); memcmp string.h int memcmp(const void *, const void *, size_t); memcpy string.h void *memcpy(void *, const void *, size_t); memmove string.h void *memmove(void *, const void*, size_t); memset string.h void *memset(void *, int, size_t); mktime* time.h time_t mktime(struct tm *); modf math.h double modf(double, double *); perror* stdio.h void perror(const char *); pow* math.h double pow(double, double); 732 Приложение Д 
Продолжение табл. Д.]    Функция Заголовочный Прототип функции файл printf* stdio.h int printf(const char *, ...); putc* stdio.h int putc(int, FILE *); putchar* stdio.h int putchar(int); puts* stdio.h int puts(const char *); qsort* stdlib.h void qsort(void*, size_t, size_t, int (*)(const void*, const void *)); rand stdlib.h int rand(void); realloc* stdlib.h void *realloc(void *, size_t); remove* stdio.h int remove(const char *); rename* stdio.h int rename(const char *, const char *); rewind* stdio.h void rewind(FILE *); scanf* stdio.h int scanf(const char *, ...); setbuf stdio.h void setbuf(FILE *, char *); setvbuf stdio.h int setvbuf(FILE *, char *, int, size_t); sin* math.h double sin(double); sinh* math.h double sinh(double); sleep* time.h void sleep(time_t); sprintf stdio.h int sprintf(char *, const char *, ...); sqrt* math.h double sqrt(double); srand stdlib.h void srand(unsigned); sscanf stdio.h int sscanf(const char *, const char *, ...); strcat* string.h char *strcat(char *, const char *); strchr* string.h char *strchr(const char *, int); strcmp* string.h int strcmp(const char *, const char *); strcmpl* string.h int strcmpl(const char *, const char *); strcpy* string.h char *strcpy(char *, const char *); strcspn* string.h size_t strcspn(const char *, const char *); strdup* string.h char *strdup(const char *); strerror string.h char *strerror(int); strftime* time.h size_t strftime(char *, size_t, const char *, const struct tm *); strlen* string.h size_t strlen(const char *); strlwr* string.h char *strlwr(char *); strncat* string.h char *strncat(char *, const char *, size_t); strncmp* string.h int strncmp(const char *, const char *, size_t); strncpy* string.h char *strncpy(char *, const char *, size_t); strnset* string.h char *strnset(char *, int, size_t); strpbrk* string.h char *strpbrk(const char *, const char *); strrchr* string.h char *strrchr(const char *, int);  Стандартные функции С  733 
Окончание табл. Д. 1     Функция Заголовочный Прототип функции файл strspn* string.h size_t strspn(const char *, const char *); strstr* string.h char *strstr(const char *, const char *); strtod stdlib.h double strtod(const char *, char **); strtok string.h char *strtok(char *, const char*); strtol stdlib.h long strtol(const char *, char **, int); strtoul stdlib.h unsigned long strtoul(const char*, char **, int); strupr* string.h char *strupr(char *); system* stdlib.h int system(const char *); tan* math.h doublo tan(double); tanh* math.h double tanh(double); time* time.h time_t time(time_t *); tmpfile stdio.h FILE *tmpfile(void); tmpnam* stdio.h char *tmpnam(char *); tolower ctype.h int tolower(int); toupper ctype.h int toupper(int); ungetc* stdio.h int ungetc(int, FILE *); va_arg* stdarg.h (type) va_arg(va_list, (type)); va_end* stdarg.h void va_end(va_list); va_start* stdarg.h void va_start(va_list, lastfix); vfprintf stdio.h int vfprintf(FILE *, const char *, ...); vprintf stdio.h int Vprintf(FILE*, const char *, ...); vsprintf stdio.h int vsprintf(char *, const char *, ...); wcstombs stdlib.h size_t wcstombs(char *, const wchar_t *, size_t); wctomb stdlib.h int wctomb(char *, wchar_t); 734 Приложение Д 
I 5 ПриложениеЕ llcnnii вампвшпяшепьнп  Ответы  В ЭТОМ приложении приведены ОТВСТЫ на контрольные ВОПРОСЫ и упражнения, которые  даются В конце каждой ГЛЗВЫ. Обратите внимание, ЧТО некоторые упражнения МОЖНО ВЫПОЛ- НИТЬ несколькими способами, так ЧТО В КЮКДОМ случае приведен ТОЛЬКО один из ВОЗМОЖНЫХ ответов или же дана noncxa3xa, облегчающая самостоятельное выполнение упражнения.  День 1  Контрольные вопросы  Nit-l  Филдс-ы  С — универсальный, широко распространенный и хорошо переносимый язык.  Компилятор переволит исходный текст программы на С на язык машинных инструкций, понятный компьютеру.  Ввод/редактирование, компиляция, компоновка, тестирование. Ответ зависит от компилятора. Обратитесь к документации. Ответ зависит от компилятора. Обратитесь к документации. Файлы исходного кода на языке С обычно имеют расширение . с (или .С).  Примечание: для файлов на языке С++ используется расширение .срр. Его можно при- сваивать и программам на С, по лучше придерживаться расширения . 0.  Файл с именем FILENAME.TXT пройдет компиляцию, но все-таки программам на С реко- мендуется давать расширение . с, а не . ТХТ.  Чтобы исправить ошибки, необходимо внести изменения в исходный код. Затем програм- му нужно перекомпилировать и скомпоновать заново. После этого программу снова 3a- пускают на выполнение, чтобы проверить, действительно ли ошибки исправлены.  Машинный язык состоит из двоично-цифровых инструкций, понятных компьютеру. По- скольку компьютеры не способны понимать исходный кед на языке С, его приходится преобразовывать в машинный код, также называемый объектным.  10. Компоновщик подключает к объектному коду программы код функций из стандартных  библиотек, создавая тем самым готовый к выполнению файл. 
Упражнения  1.  2.  Открыв объектный файл, можно увидеть хаотическую мешанину из непонятных симво- лов. Среди всего этого иногда попадаются фрагменты исходного кода программы.  Программа вычисляет площадь круга. ‘Она запрашивает у пользователя радиус, а затем выдает ИСКОМУЮ площадь.  Программа выводит на экран квадрат размером 10x10 символов, образованный буквами 'Х'. Похожая программа рассматривается также на занятии 6.  Программа содержит синтаксическую ошибку. При ее компиляции будет вьщано пример— но следующее сообщение:  Error: chlex4.c: Declaration terminated incorrectly  Ошибка вызвана точкой с запятой в конце третьей строки. После удаления точки с запя- той программа пройдет компиляцию и компоновку без ошибок.  Программа пройдет компиляцию, но при ее компоновке возникнет ошибка. Будет выдано примерно следующее сообщение об ошибке:  Error: Undefined symbol _do_it in module...  Эта ошибка произошла потому, что компоновщик не смог найти в библиотеке функцию do_it. Чтобы исправить ошибку, замените do_it на printf. °  Вместо блока из 10x10 букв 'Х' программа будет выводить на экран такой же блок, со- стоящий из символов улыбающихся рожиц.  День 2  Контрольные вопросы  73  Блок. Функция main( ).  Комментарии используются для пояснения того, как устроены и что делают те или иные части программы. Любой текст между символами /* и */ считается комментарием, и компилятор его игнорирует. Используются также однострочные комментарии, начинаю- щиеся с двойной косой черты / / и заканчивающиеся концом строки.  Функция — это независимый фрагмент кода программы, выполняющий определенную за- дачу и имеющий свое имя. Программа вызывает функцию по имени. При этом в соответ- ствующем месте программы выполняется код функции.  Нестандартные функции создаются программистом Для своих целей. Библиотечные функ— ции поставляются готовыми в комплекте с компилятором.  Директива #include приказывает компилятору вставить заданный внешний файл B ука— занное место файла исходного кода.  комментарии не следует Делать вложенными. ХОТЯ некоторые компиляторы допускают такую ВОЗМОЖНОСТЬ, КОД C их использованием может оказаться непереносимым.  Да, комментарии могут быть произвольной длины. Комментарий начинается с символов / * и продолжается так долго, как это необходимо, пока не встретятся символы */ .  6 Приложение Е 
9. Включаемые файлы также известны как заголовочные.  10. Включаемый файл —— это отдельный файл на диске, который содержит информацию, не- обходимую компилятору для подключения внешних функций и т.п.  Упражнения  1. В программах на С обязательно наличие только функции main( ). Ниже приведена самая маленькая программа на С (которая, правда, ничего полезного не делает):  void main(void) { }  Эту же программу можно переписать еще короче: void main(void){} 2. BOT как можно классифицировать строки этой программы. а) операторы находятся в строках 8, 9, 10, 12, 20 и 21; б) единственное объявление переменной —— в строке 18; в) единственный прототип функции display_line() -— в строке 4; г) определение функции display_1ine() содержится в строках 16—22; д) комментарии находятся в строках 1, 15 и 23.  3. Комментарием называется любой текст между парами символов / * и */ . Примеры:  /* Это комментарий */ /* ??? */  /* А это еще один комментарий */  Существуют также однострочные комментарии: любой текст после двойной косой черты / / до конца строки.  4. Эта программа выводит на экран весь латинский алфавит большими буквами. Программа станет понятнее после занятия 10, посвященного символам и строкам. Вот результат ее работы:  ABCDEFGHI J KLMNOPQRSTUVWXYZ  5. Программа подсчитывает количество введенных символов и пробелов, а затем выводит его на экран. Рекомендуем вернуться к ней после занятия 10 для повторения.  День 3  Контрольные вопросы  1. Целочисленная переменная может хранить целые числа (числа без дробной части), тогда как переменная с плавающей точкой —— вещественные числа с дробной частью.  Ответы 737 
2.  Переменная типа double имеет более широкий диапазон значений, чем float (т.е. может содержать намного большие и намного меньшие числа). Также этот тип обеспечивает большую точность мантиссы —— количество десятичных значащих цифр.  Эти принципы таковы. а) размер переменной типа char -—— один байт; 6) размер переменной типа short меньше или равен размеру int; B) размер переменной типа int меньше или равен размеру long; r) размер переменной типа unsigned int равен размеру int; д) размер переменной типа float меньше или равен размеру double.  Символические константы с именами делают программу более удобочитаемой, а также позволяют очень просто изменить значение константы сразу во всем тексте программы.  Первый способ: #define MAXIMUM 100 Второй способ: const int MAXIMUM = 100; Буквы, цифры, знак подчеркивания.  Имена переменных И констант ДОЛЖНЫ ХОРОШО описывать помещаемые В НИХ данные. Имена переменных желательно записывать В нижнем регистре, а констант —— В верхнем.  Символическими константами называются символические имена (идентификаторы), за- меняющие литеральные константы.  Если это переменная без знака (unsigned), то ее наименьшее значение равно 0. Если со знаком, то -32768. Предполагается, что длина переменной равна 2 байта.  Упражнения  1.  2.  Рекомендации по выбору типов.  а) Возраст в годах можно считать целым числом, и отрицательного возраста не быва— ет, поэтому предлагается тип unsigned int.  6)unsigned int B)float  r) Если не претендовать на слишком большой доход, то хватит и переменной типа unsigned int. Если же имеются шансы получать больше 65535, следует воспользо— ваться переменной типа long. (C овет: верьте в себя — используйте long.)  д) float. He следует забывать о дробной части, выражающей копейки или центы.  е) По условию, высшая оценка всегда равна 100, поэтому она должна быть константой. Объявить ее можно с помощью директивы #define или декларации const int.  ж) float. Если измерять температуру только в целых градусах, то int или long.  3) Определенно, это должно быть число со знаком, если учитывать и отрицательный баланс, т.е. долги: int, long или float. CM. также вопрос о размере годового дохода.  и) double  Здесь одновременно даются ответы к упражнениям 2 и 3.  738 Приложение Е 
3. 4.  Помните, что имя переменной должно соответствовать по смыслу ее значениям. Объявле- ние переменной —- это оператор ее создания. При этом может выполняться или не выпол- няться инициализация — присвоение начальных значений. Имена могут быть любыми идентификаторами, кроме ключевых слов С.  а) unsigned int age; 6) unsigned int weight; B)float radius = 3; r) long annual_salary; д) float cost = 29.95; e) const int max_grade = 100; или #define MAX_GRADE 100 ж) float temperature; 3) long net__worth = -30000; и) double star_distance; CM. ответ к упражнению 2.  Допустимы имена (б), (в), (д), (ж), (з), (и), (к). Обратите внимание, что имя (к) — в спи- ске разрешенных для использования, хотя злоупотреблять такими длинными именами неразумно. (Да и кто, скажите на милость, захочет набирать их на клавиатуре?!) Боль- шинство компиляторов воспринимают не все имя целиком, а только первые 31 символ или около того.  А вот эти имена недопустимы: а) идентификатор не может начинаться с цифры; г) в именах нельзя использовать символ #;  е) в именах переменных не допускается дефис.  День 4  Контрольные вопросы  1.  2.  Это оператор присваивания, приказываюший компьютеру сложить числа 5 и 8 и помес- тить результат B переменную x.  Выражение —— зто любая совокупность констант, переменных, знаков операций и т.п., вы- числение которой дает числовой ответ.  Сравнительный приоритет операций.  После выполнения первого оператора значение a будет равно 10, а x получит значение 11. После второго —- и a, и x CTaHyT равными 11. (Операторы должны выполняться отдельно друг от друга, например, в разных программах.)  Ответ равен 1. Ответ равен 19. (5+3)*8/(2+2)  Ответы 739 
8. Ложному логическому значению соответствует 0. 9. Для ответа воспользуйтесь таблицей приоритетов. Ответы таковы: а) < старше, чем ==; 6) * старше, чем +; в) != и == по старшинству равны, поэтому выполняются слева направо;  г) >= имеет равный приоритет с >. Если в одном выражении употребляется несколько сравнений, следует ставить скобки.  10. Составные операторы присваивания позволяют совместить двуместные математические операции с присваиванием. Получается удобная сокращенная запись. На этом занятии рассматривались такие операторы, как +=, -=, / =, *= и %=.  Упражнения  1. Эта программа будет работать даже несмотря на плохое оформление и структурирование. Ее цель состоит в том, чтобы продемонстрировать независимость работы программы от количества пробелов и переносов строк в ней. Поэтому свободное пространство следует использовать для улучшения удобочитаемости программ.  2. В таком виде программа воспринимается гораздо лучше: #include <stdio.h>  int х, у;  int main( void )  { printf(”\nEnter two numbers ”); scanf("%d %d", &х, &у); printf("\n\n%d is bigger\n", (х > у) ? х : у); return 0; }  Программа запрашивает два числа —— x и у, после чего выводит на экран большее из них.  3. Необходимо внести следующие изменения в листинг: 16: printf("\n%d %d", a++, ++b) 17: printf("\n%d %d", a++, ++b) 18: printf("\n%d %d", a++, ++b) 19: printf("\n%d %d", a++, ++b) 20: printf("\n%d %d", a++, ++b) 4. Следующий фрагмент кода —— лишь один из многих правильных ответов. В нем проверя- ется, находится ли ›‹ в пределах от 1 до 20. Если да, то значение ›‹ присваивается перемен— ной у. Если нет, то ›‹ не присваивается, т.е. у остается неизменным. if ((х >= 1) && (х <= 20)) Y : х; 5. Соответствующий код выглядит так: у = ((х >= 1) && (х <= 20)) ? х : у;  " `. `. " "  740 Приложение Е 
Если выражение равно TRUE, значение x присваивается у. В противном случае у присваи- вается самому себе, т.е. не изменяется.  6. Требуемый оператор имеет вид: if ( х ‹ 1 && x > 10 ) оператор; 7. Результаты вычислений таковы: a) 7 6) 0 в) 9 г)1 (TRUE) д)5 8. Выражения имеют следующие значения: a)TRUE 6)FALSE  B) TRUE. Обратите внимание на одинарный знак равенства, из-за чего в операторе if выполняется присваивание вместо сравнения.  г) TRUE  9. BOT ОДИН ИЗ ВОЗМОЖНЫХ вариантов ответа: if( age < 21 ) printf( “You are not an adult" ); else if( age >= 65 ) printf( "You are a senior citizen" ); else printf( "You are an adult" );  10. B этой программе четыре ошибки. Первая из них встречается в третьей строке —— оператор присваивания должен заканчиваться точкой с запятой, а не двоеточием. Вторая ошибка— это точка с запятой после if и скобок. Третья ошибка очень широко распространена. Вместо знака равенства (==) в операторе if очень часто по ошибке пишут знак присваивания (=). Наконец, последняя ошибка заключается в том, что вместо слова else B строке 8 употребле- но otherwise. После исправлений программа будет иметь следующий вид: /* программа без ошибок... */ #include <stdio.h>  int х = 1; int main( void ) { if( х == 1 ) printf(“ х equals 1 " ); else printf(" х does not equal 1 "); return О; }  Ответы 74' 
День 5  1.  2.  Да, обязательно! (Если хотите стать высококлассным программистом на С, отвечайте только так.)  При структурном подходе к программированию сложная прикладная задача разбивается на ряд мелких и более простых задач, с которыми затем можно справиться поодиночке.  Разбив большую задачу на мелкие, необходимо написать отдельную функцию для каждой мелкой задачи. Таким образом, функция —— это основная структурная единица программы, тесно связанная со структурным подходом.  Первая строка определения функции должна быть ее заголовком. Заголовок содержит имя функции, тип возвращаемого значения и список параметров.  Функция может возвращать одно значение любого из типов С или не возвращать никако- го. На занятии 18 рассказывается, как заставить функцию возвратить несколько значений (правда, обходными путями). Тип возвращаемого значения должен быть void. Определение функции —— это ее полный текст, включающий заголовок и тело со всеми операторами. Определение функции является фактическим кодом и задает операции, вы- полняемые функцией. А вот прототип —— 3T0 всего лишь заголовок функции, заканчиваю—  щийся точкой с запятой. Прототип сообщает компилятору имя функции, ее аргументы и тип возвращаемого значения.  Локальная переменная — это переменная, объявленная внутри функции.  Локальные переменные полностью независимы от переменных из других модулей про- граммы.  10. Желательно, чтобы функция main( ) была первой функцией в файле исходного кода.  Упражнения  1.  float do_it(char a, char b, char с) Если добавить точку с запятой в конце строки, получится прототип функции. Если же до- бавить тело в фигурных скобках, получится определение функции. void print_a_number( int a_number ) Функция не возвращает значений. Как и в упражнении 1, для получения прототипа следу- ет завершить строку точкой с запятой, а для определения функции добавить тело. Возвращаемые значения имеют следующие типы: a)int 6)long B этой программе две ошибки. Во-первых, функция print msg() объявлена как void и, тем не менее, возвращает значение. Необходимо удалить из нее оператор return. Вторая ошибка содержится в пятой строке. При вызове функции print_msg() B нее передается  строка, а в прототипе функция имеет пустой список параметров, т.е. не может принимать аргументов. Исправленная программа выглядит следующим образом:  #include <stdio.h> void print_msg( void );  742 Приложение Е 
int main( void )  { print_msg(); return 0; } void print_msg(void ) { puts( "This is a message to print“ ); }  После заголовка функции не должна стоять точка с запятой. Достаточно внести изменения только в функцию larger_of( ): 21: int larger_of( int a, int Ь )  22: { 23: int save; 24: 25: if (a > Ь) 26: save = a; 27: else 28: save = b; 29: 30: return save; 31: }  BOT целочисленная версия требуемой функции: int product( int х, int у )  {  return ( х * у ); }  В следующем листинге проверяется, не равен ли второй из переданных аргументов нулю, так как деление на ноль приволит к сбою. Никогда не следует предполагать, что переда- ваемые аргументы имеют корректные значения —— всегда проверяйте все данные, какие возможно.  int divide_em( int a, int Ь )  { int answer = О; if( b == ) answer = О; else answer = a / b; return answer; }  Здесь используется функция main( ), хотя можно было бы заменить ее любой другой. В строках 9, 10 и 11 выполняются вызовы двух функций. В строках 13—16 на экран выво- дятся числа. Для выполнения приведенной программы необходимо вставить функции из упражнений 7 и 8 после строки 19. 1: #include <stdio.h> 2:  Ответы 743 
3: int main( void ) 4:{ 5: int numberl = 10, 6: number2 = 5; 7 int х, y, z; 8  9:  х = product( numberl, number2 ); 10: y = divide_em( numberl, number2 ); 11: z = divide_em( numberl, О ); 12:  13: printf("\nnumberl is %d and number2 is %d", numberl, number2 ); 14: printf("\nnumberl * number2 is %d", х ); 15: printf("\nnumberl / number2 is %d", y ); 16: printf("\nnumberl / 0 is %d", z ); 17: 18: return 0; 19: }  10. Исходный текст программы выглядит следующим образом:  11.  /* Усреднение пяти вещественных чисел, введенных пользователем. */ #include <stdio.h> float v, w, х, y, 2, answer; float average(float a, float b, float с, float d, float е);  int main( void )  { puts("Enter five numbers:"); scanf("%f%f%f%f%f", &v, &w, &х, &у, &2); answer = average(v, w, х, У, 2); printf("The average is %f.\n", answer); return 0; } float average(float a, float b, float с, float d, float е) { return ((a + b + с + d + e)/5); }  B следующем далее коде используются переменные типа int. Поэтому программа может работать только со степенями, не превосхоцящими 9. Чтобы расширить диапазон, необ- ходимо перейти к переменным типа long.  /* Программа с использованием рекурсивной функции */ #include <stdio.h> int three_powered( int power );  int main( void ) { int a = 4; int b = 9; printf("\n3 to the power of %d is %d", a, three_powered(a));  744 Приложение Е 
printf("\n3 to the power of %d is %d\n", b three_powered(b));  I  return О; } int three_powered( int power ) { if ( power < 1 ) return( 1 ); else return( 3 * three_powered( power - l )); }  День 6  Контрольные вопросы  1. Первый элемент массива в языке С всегда имеет индекс 0. 2. B отличие от while, оператор for наряду с условием ссдержит блоки инициализации и приращения. 3. Оператор do. . .while содержит блок while B конце. Условие цикла проверяется после выполнения его тела, поэтому такой цикл всегда выполняется как минимум один раз. 4. Да, это так. С помощью цикла while можно проделать те же операции, что и B цикле for, однако придется добавить инициализацию счетчика перед телом цикла и приращение счетчика в конце тела. 5. Циклы не могут частично накладываться (перекрываться). Вложенный цикл должен цели- ком помещаться внутри внешнего. 6. Да, вложение оператора while B оператор do. . .while разрешается так же, как и вложение любых циклов друг B друга в любом порядке. 7. Блок инициализации, блок условия, блок приращения, тело цикла. 8. Условие и тело цикла . Тело цикла и условие (в конце оператора, после тела). 1. long array[50]; 2. Обратите внимание, что в ответе элемент №50 имеет индекс 49. Не забывайте, что нуме- рация элементов массива всегда начинается с 0. array[49] = 123.456; 3. После завершения цикла переменная х становится равной 100.  Ответы 745 
4.  6.  7.  8.  9.  После завершения цикла переменная ctr становится равной 11 (ее начальное значение равно 2, а затем переменная каждый раз получает приращение 3, пока не станет больше или равна 10).  Внутренний цикл выводит на экран пять букв Х. Внешний цикл выполняет внутренний де- сять раз. Итого получается пятьдесят букв X.  Код имеет следующий вид (можно расположить его B две строки для удобства): int x; for( x = l; х <= 100; x += 3 ); Код имеет следующий вид (можно расположить его B две строки для удобства): int x = 1; while( х <= 100 ) x += 3; Код имеет следующий вид: int ctr = 1; do { ctr += 3; } while( ctr < 100 ); Эта программа никогда не закончится, поскольку B ней присутствует зацикливание. Пере- менная record инициализируется значением 0. Цикл while проверяет, является ли record меньше 100. Ноль меньше ста, поэтому цикл выполняется, выводя на экран два сообше- ния. Затем условие проверяется снова. Ноль всегда будет меньше ста, поэтому цикл будет все продолжаться и продолжаться. Чтобы исправить ситуацию, переменная record долж- на получить приращение внутри цикла. После второго вызова printf () B тело цикла сле- дует вставить следующий оператор:  record++;  10. Использование символических констант является обычной практикой при организации  циклов. Такие операторы часто встречаются B ходе занятий второй и третьей недели. Ошибка B этом фрагменте кода очень проста —— после закрывающей скобки оператора for не должно быть точки с запятой. Эта ошибка широко распространена.  День 7  Контрольные вопросы  1.  2. 3.  Между этими функциями существует два основных различия:  а) функция printf () может выводить данные любых типов, B том числе числовые, а puts( ) —— только символьные строки;  б) функция puts( ) автоматически добавляет символ конца строки после вывода своего аргумента.  Для использования функции printf( ) следует включить заголовочный файл stdio.h. Назначение этих управляющих символов таково: а) вывод обратной косой черты;  б) сдвиг на один символ назад с удалением;  746 Приложение Е 
в) переход на новую строку; г)горизонтальнаятабуляция; д)подачазвуковопэсигнала Используются следующие спецификации формата: а) %5 для строки текста; 6) %d для целого десятичного числа со знаком; в) %f для десятичного вещественного числа с плавающей точкой. Различие состоит в следующем: а) Ь —— это латинская буква, выводимая на экран как она есть; 6) \Ь соответствует возврату назад на один символ с его удалением;  в) встретив обратную косую черту \, компилятор ожидает после нее управляющи1 символ;  г) встретив \\, программа выводит на экран одну обратную косую черту.  Упражнения  1.  Функция puts() автоматически выполняет переход на новую строку, а printf () этого н делает. Правильный ответ таков: printf( "\n" ); puts ( ll " ) ;  Ввод можно выполнить таким образом: char c1, с2; int d1; scanf( "%с %ud %c", &с1, &d1, &c2 ); Ответы могут быть разными. Вот один из возможных:  #include <stdio.h> int X;  int main( void ) { puts( "Enter an integer value" ); scanf( "%d", &x ); printf( "\nThe value entered is %d\n", x ); return 0;  Проверка вводимых пользователем значений— очень распространенная практика. Во один из способов выполнения данного задания:  #include <stdio.h> int x;  int main( void )  {  puts( "Enter an even integer value" );  Ответы 741 
scanf( "%d", &x ); while( x % 2 != 0 )  printf( "\n%d is not even, Please enter an even \ number:", х ); scanf( "%d", &х ); }  printf( "\nThe value entered is %d\n", x ); return 0;  } 5. Модернизированная программа из предыдущего упражнения выглядит следующим образом:  #include <stdio.h> int array[6], x, number;  int main( void )  { /* Выполнить цикл 6 раз или пока элемент не равен 99 */ for( x = 0; x < 6 && number != 99; х++ ) { puts( "Enter an even integer value,or 99 to quit" ); scanf( "%d", &number ); while( number % 2 == 1 && number != 99 ) printf( "\n%d is not even, Please enter an even \ number: ", number ); scanf( "%d", &number ); } array[x] = number; } /* вывод чисел... */ for( x = 0; x < 6 && array[x] !!= 99; х++ ) printf( "\nThe value entered is %d", array[x] ); } return 0; }  6. Предьшущие ответы уже представляют собой исполняемые программы. Остается доба- вить только вызов printf( ). Чтобы вывести все числа, отделяя их табуляциями, замените оператор вызова printf( ) Ha следующий:  printf( "%d\t", array[x] ); 7. Нельзя ставить двойные кавычки внутри таких же кавычек непосредственно. Для этого используется специальный символ \". Кроме того, в конце первой строки необходимо  поставить обратную косую черту для перехода на вторую строку без разрыва. Правильный оператор выглядит следующим образом:  printf("Jack said, \"Peter Piper picked a peck of pickled \ peppers.\""):  8. B этой программе есть три ошибки. Первая—_ это отсутствие кавычек в операторе printf( ). Вторая —— пропущенный знак взятия адреса перед переменной answer B вызове  748 Приложение Е 
функции scanf( ). Третья ошибка сделана в том же операторе: вместо спецификации фор- мата "%f" следует использовать "96d", поскольку ввоцится целое число, а не вещественное. После исправления ошибок функция приобретает следующий вид: int get_1_or_2( void )  { int answer = 0; while (answer < 1 || answer > 2) { printf( "Enter 1 for Yes, 2 for No" ); /* исправлено */ scanf("%d", &answer ); /* исправлено */ } return answer; }  9. Это текст функции print_report( ) после ее дополнения:  void print_report( void )  { printf( "\nSAMPLE REPORT" ); printf( "\n\nSequence\tMeaning" ); printf( "\n=========\t=======" ), printf( "\n\\a\t\tBell (alert) " ); printf( "\n\\b\t\tBackspace" ); printf( "\n\\f\t\tForm feed " ); printf( "\n\\n\t\tNew line" ); printf( "\n\\r\t\tCarriage Return" ); printf( "\n\\t\t\tHorizontal tab" ); printf( "\n\\v\t\tVertical tab" ); printf( "\n\\\\\t\tBackslash" ); printf( "\n\\\?\t\tQuestion mark" ); printf( "\n\\\'\t\tSingle quote" ); printf( "\n\\\"\t\tDouble quote" ); printf( "\n...\t\t..." ); }  10. Программа имеет следующий вид:  /* Ввод двух вещественных чисел и вывод их произведения.  #include <stdio.h>  float x,y; int main(void ) { puts("Enter two values: "); scanf("%f %f", БХ: &У)? printf("\nThe product is %f\n", x * у); return 0; }  11. Следующая программа приглашает ввести десять целых чисел и отображает их сумму.  /* Ввод десяти целых чисел и вывод их суммы. */ #include <stdio.h>  Птпатн  */  741 
12.  int count, temp; long total = 0; /* Тип long гарантирует, что не выйдем */ /* из диапазона типа int. */  int main( void )  { for ( count = 1; count <= 10; 00111113++ ) { printf("Enter integer #%d: ", count); scanf("%d", &temp); total += temp; } printf("\n \nThe total is %d\n", total); return 0; }  Программа ДЛЯ ввода целых чисел И ПО’МСЩСНИЯ ИХ В массив:  /* Ввод целых чисел и их помещение в массив. Ввод */ /* прекращается после получения нуля. Поиск и вывод */ /* наибольшего и наименьшего чисел в массиве */ #include <stdio.h>  #define MAX 100  int array[MAX]; int count = -1, maximum, minimum, num_entered, temp;  int main( void ) { puts("Enter integer values one per line."); puts(”Enter 0 when finished.“);  /* Ввод чисел */ do  scanf("%d". &temp); array[++count] = temp; } while ( count < (MAX-l) && temp != 0 );  num_entered = count;  /* Поиск максимума и минимума. */ /* Вначале делаем максимум очень маленьким, */ /* а минимум — очень большим. */ maximum = -32000; minimum = 32000; for (count = 0; count <= num_entered && array[count] != 0; count++) { if (array[count] > maximum) maximum = array[count]; if (array[count] < minimum)  750 Поиложение Е 
minimum = array[count];  }  printf("\nThe maximum value is %d", maximum); printf("\nThe minimum value is %d\n", minimum);  return 0;  День 8  1. Массив может иметь любой тип. Все элементы одного и того же массива по определению должны принадлежать к одному типу. . Независимо от размера массива, его первый элемент всегда имеет номер 0. . Последний элемент имеет индекс п—1 4. Программа пройдет компиляцию и компоновку без ошибок, но при ее выполнении ре- зультаты будут непредсказуемыми. 5. После имени массива в его объявлении ставится несколько пар квадратных скобок——— по одной на каждое измерение массива. Каждая пара скобок должна содержать размер мас- сива по данному измерению. 6. Всего элементов 240. Чтобы получить это число, следует перемножить длины массива по всем измерениям, указанные в скобках. . Элемент будет иметь индекс array[ О] [0] [1] [1]. . I1o<bOpmynesizeof(xyz) / sizeof(long). Упражнения 1. int one[1000], two[1000], three[1000]; . Это упражнение можно выполнить несколькими способами. Первый —— это инициализа- ция массива прямо при объявлении: int eightyeight[88] = { 88, 88, 88, 88, 88, 88, 88, ..., 88}; Правда, для этого придется восемьдесят восемь раз записать число 88 в скобках (увы, в реальной программе троеточием не обойтись). Большие массивы неудобно инициализи- ровать таким способом. Это следует делать так: int eightyeight[88]; int x; for ( x = О; x < 88; x++ ) eightyeight[x] = 88; 4. Это код для инициализации массива нулями:  int stuff[12][10]; int subl, sub2;  Ответы 75 7 
for( subl = O; subl < 12; sub1++ ) for( SUb2 = O; sub2 < 10; sub2++ ) stuff[subl][sub2] = O; ‘  Здесь следует соблюдать осторожность: п0добные ошибки встречаются достаточно часто. Обратите внимание, что массив объявлен с размерами 10x3, a инициализируегся как 3x 10.  Другими словами, первый индекс может иметь значения от О до 9, но в цикле for переменная x пробегает только первые три. Второй индекс имеет диапазон от О до 2, но во вложенном цикле пробегаются его значения от О до 9. Из-за этого результаты могут ока- заться непредсказуемыми. Исправить положение можно Одним из двух способов. Первый способ —— это поменять местами x и у в цикле при ссылке на элементы массива: int x, y; int array[lO][3]; int main( void )  { for ( x = O; x < 3; x++ ) for ( y = O; у < 10; Y++ ) array[y][x] = O; /* исправлено */ return 0; }  Второй, рекомендуемый, способ —— это изменить диапазоны изменения индексов x и у в цикле: int x, y; int array[10][3]; int main( void )  { for ( x = O; x < 10; x++ ) /* исправлено */ for ( y = O; y < 3; y++ ) /* исправлено */ arraYIXIIY] = 0: return 0; }  B этом листинге сделана сравнительно простая ошибка. Программа пытается инициали- зировать элемент массива за пределами его границ. Если в массиве 10 элементов, их ин- дексы изменяются от О до 9, а в программе выполняется присваивание значений элемен- там с номерами от 0 до 10. Элемент array[ 10] нельзя инициализировать, поскольку он не существует. Оператор for необходимо заменить на один из следующих:  for ( x = 1; x <= 9; x++ ) /* инициализация 9 элементов из 10 */ for ( x = 0; x <= 9; x++ ) Заметим, что выражения x <= 9 и x < 10 эквивалентны, но второе более общеупотреби- тельно. Это одно из возможных решений:  /* Использование двумерного массива и функции rand() */ #include <stdio.h> #include <stdlib.h>  /* Объявление массива */ int array[5][4];  752 Ппиппжянио F 
int a, b; int main( void )  { for ( a = 0; a < 5; a++ ) { for ( b = 0; b < 4; b++ ) { array [a ][b ] ==rand(); } }  /* Вывод элементов массива */ for ( a = 0; a < 5; a++ )  { for ( b = 0; b < 4; b++ ) { printf( "%d\t, array[a][b] ); £rintf( "\n" ); /* переход на новую строку */ } return 0;  }  8. Одно из многих возможных решений таково:  /* random.c: работа с одномерным массивом */ #include <stdio.h> #include <stdlib.h>  /* Объявление одномерного массива из 100 элементов */ int random[1000]; int a, b, c; long total = 0;  int main( void ) { /* Заполнение массива случайными числами. Библиотечная функция С */ /* под именем rand() возвращает случайное число. По каждому */ /* индексу организуется свой вложенный цикл. */ for ( a = 0; a < 1000; a++ ) { random[a] = rand(); total += random[a];  } printf( "\n\nAverage is:%ld\n", total / 1000 );  /* Вывод элементов массива порциями по 10 */ for ( a = 0; a < 1000; a++ )  printf( "\nrandom[%d] = ", a ); printf( "%d", random[a] );  (ЧТдртъл 753 
9.  .if ( a % 10 == 0 && а > 0 )  printf( “\nPress Enter to continue, CTRL-C to quit.“ );  getchar(); } } return 0; } /* конец функции шаіп()*/  Здесь приведены два решения. В первом массив инициализируется в момент объявления, а во втором —— в цикле for.  Решение 1:  #include <stdio.h> . /* Объявление одномерного массива */  int elements[10] = { 0, 1, 2, 3, 4, 5, б, 7, 8: 9 }? int idx; '  int main( void )  { for ( idx = 0; idx < 10; idx++ ) { printf( "\nelements[%d] = %d“, idx, elements[idx] ); } return 0; }  Решение 2:  #include <stdio.h> /* Объявление одномерного массива */  int elements[10]; int idx;  int main( void )  { for ( idx = 0; idx < 10; idx++ ) elements[idx] = idx; for ( idx = 0; idx < 10; idx++ ) printf( "\nelements[%d] = %d", idx, elements[idx] ); return 0; }  10. Одно из возможных решений дается ниже:  #include <stdio.h> /* Объявление одномерного массива */  int elements[10] = { 0, 1, 2, 3, 4, 5, б, 7, 3: 9 }? int new_array[10]; int idx;  754 Приложение Е 
int main( void )  { for ( idx = O; idx < 10; idx++ ) { new_array[idx] = elements[idx] + 10; } for ( idx = O; idx < 10; idx++ ) { printf( “\nelements[%d] = %d \tnew_array[%d] = %d", idx, elements[idx], idx, new_array[idx] ); } return 0; }  День 9  Контрольные вопросы  1. 2.  Знак взятия адреса —-— это амперсанд &.  Для этого используется знак операции разыменования *. Если перед именем указателя на переменную стоит этот знак, то все выражение равно значению переменной.  Указатель —— это переменная, содержащая адрес другой переменной (участка памяти).  Ссылка по указателю (адресу) —-— это операция обращения к данным, располагающимся по заданному указателем адресу.  Элементы массива располагаются в памяти последовательно, Один за другим, вплотную дРУГ к дРУГУ— Один способ: &data[ 0 ], а второй ——- просто data.  Один способ —— это передать длину массива как параметр в функцию. Второй —— это по— местить в массив специальное значение, например NULL, обозначающее конец массива.  Присваивание, ссылка, взятие адреса, инкрементирование, вычисление смещения, сравнение.  Вычитание двух указателей дает количество элементов между двумя адресами. В данном случае ответ равен 1. Размер элементов в массиве не имеет никакого значения.  10. Ответ по—прежнему равен 1.  Упражнения  1. 2.  char *char_ptr;  Ниже объявляется указатель на переменную cost, и в него помещается адрес этой пере— менной:  0712er 755 
int *p_cost; p_cost = acost;  3. Прямое обращение: cost = 100; Косвенное обращение: *p_cost = 100; 4. printf( "Pointer value: %d, points at value: %d", p_cost, *p_cost ); float *variable = &radius; 6. BOT эти два способа: data[2] = 100; *(data + 2) = 100; 7. B этом к0де Одновременно дан также ответ к упражнению 8. #include <stdio.h>  #define MAXI 5 #define MAXZ 8  int array1[MAX1] int array2[MAX2] int total;  { ll 2! 3! 4! 5 }; { ll 2! 3! 4! 5! 6! 7! 8 };  int sumarrays(int x1[], int len_x1, int x2[], int len_x2);  int main( void )  { total = sumarrays(array1, MAXI, array2, MAXZ); printf("The total is %d\n", total); return О; } int sumarrays(int x1[], int len_x1, int x2[], int len_x2) { . int total = 0, count = 0; for ( count = 0; count < len_x1; count++ ) total += x1[count]; for ( count = 0; count < len_x2; count++ ) total += x2[count]; return total; }  8. CM. ответ к упражнению 7. 9. Это одно из многих возможных решений:  #include <stdio.h> #define SIZE 10  /* Прототип функции */ void addarrays( int[], int[] );  756 Ппмпттцмп F 
int main( void )  { int a[SIZE] = { 1, 1, 1, 1, 1. 1. 1. 1. 1, 1 }; int b[SIZE] = { 9 8 7, 6, 5, 4, 3, 2, 1 О }; addarrays(a, b); return 0; } void addarrays( int first[], int second[] ) { int total [SIZE]; int *ptr_total = &total[0]; int ctr = 0; for ( ctr = 0; ctr < SIZE; ctr ++ ) { total[ctr] = first[ctr] + second[ctr]; printf( "%d + %d = %d\n", first[ctr], second[ctr], total[ctr] ); } }  День 10  1. Символы в наборе ASCII имеют коды от 0 до 255. Основной набор занимает диапазон от 0 до 127, а расширенный — от 128 до 255.  2. Как число, соответствующее символу согласно кодовой таблице ASCII.  3. Строка— ЭТО ПОСЛСДОВЗ'ГСЛЬНОС’ГЬ СИМВОЛОВ, ОКННЧИВНЮЩНЯСЯ СПСЦИЭЛЬНЫМ Н'УЛСВЫМ СИМВОЛОМ.  4. Последовательность из одного или нескольких символов, заключенных в двойные кавычки. 5. Для хранения завершающего нулевого символа.  6. Как последовательность числовых кодов ASCII, соответствующих символам в кавычках, с нулевым символом (к0д 0) B конце.  7. Символы имеют следующие числовые коды: а)97 б)65 в)57 г)32 д)206 e)6 8. Эти коды имеют следующие символьные эквиваленты: a)I б)пробел  Ответы 757 
в)с г)а д)п  е) NULL, нулевой символ  Ж))  9. Переменные занимают следующий объем памяти:  а) 9 байт. Фактически, переменная является указателем на строку, а сама строка со— держит 8 байт для символов и 1 для завершающего нуля;  б) 9 байт; в) 1 байт; г) 20 байт; д) 20 байт.  10. Выражения равны следующему:  а)А; б)А stringl; в) 0 (NULL);  г) индекс выходит за пределы массива, поэтому выражение может принять любое, за- ранее не предсказуемое, значение; \  д)!;  6) адрес первого элемента в строке.  Уп ражнен ия 1 char letter = '$'; 2 char array[18] = "Pointers are fun!"; 3. char *array = "Pointers are funl"; 4. Для этой операции используется следующий код:  char *ptr; ptr = malloc(81); getS(ptr); Ниже дается одно из возможных решений. Приведена полная, законченная программа, а не только одна функция.  #include <stdio.h> #define SIZE 10  /* прототип функции */ void copyarrays( int[], int[] );  int main( void )  {  int ctr = О;  758 Приложение Е 
int a[SIZE ] = { 1, 2: 3: 4: 5: 6: 7: 8: 9! 10 }? int b[SIZE];  /* значения до копирования */ for ( ctr = 0; ctr < SIZE; ctr++ ) { printf( "a[%d] = %d, b[%d] = %d\n", ctr, a[ctr], ctr, b[ctr].);  } copyarrays(a, Ь);  /* значения после копирования */ for ( ctr = 0; ctr < SIZE; ctr++ ) { printf( "a[%d] = %d, b[%d] = %d\n", ctr, a[ctr], ctr, b[ctr] );  } return 0; } void copyarrays( int orig[], int newone[] ) { int ctr =0; for ( ctr = 0; ctr < SIZE; ctr++ ) { newone[ctr] = orig[ctr]; } }  6. Здесь приведен Один из возможных правильных ответов.  #include <stdio.h> #include <string.h>  /* прототип функции */ char *compare_strings( char *, char * );  int main( void )  { char *a = "Hello"; char *b = "World!"; char *longer; longer = compare_strings(a, Ь); printf( "The longer string is: %s\n", longer ); return О; }  char *compare_strings( char *first, char *second )  {  int x, у;  Ответы 759 
7. 8.  9.  strlen(first); strlen(second);  x Y  if(x>y) return(first); else return(second); }  A BOT это задание следует выполнить самостоятельно! Подсказок не будет.  Массив a_string объявлен состоящим из 10 символов, но инициализирован более длин- ной строкой. Следует увеличить длину массива, чтобы поместилась строка вместе с нуле- вым завершающим символом.  Если этот оператор предназначался для объявления и инициализации символьной строки, то он ошибочен. Следует отбросить либо звездочку, либо квадратные скобки, так что пра- вильная форма —— *quote или quote[ 100].  10. Ошибок нет.  11. В к0де есть ошибка: хотя указатели можно присваивать друг другу, с массивами этого де-  лать нельзя. Вместо присваивания следует воспользоваться операцией копирования строк, например, функцией strcpy( ).  День 11  Контрольные вопросы  1.  2.  Все элементы массива должны иметь Один и тот же тип, тогда как B структуре могут од- новременно храниться данные разных типов.  Операция обращения к элементу структуры обозначается точкой после имени структуры. Она необходима для работы с полями структур как с отдельными переменными.  С помощью слова struct.  Метка структурного типа обозначает структурный тип (шаблон структуры), но не кон- кретную переменную. Экземпляр структуры представляет собой переменную, размещен- ную B памяти и хранящую фактические данные.  В этом фрагменте кола объявляется структурный тип и создается его экземпляр с именем myaddress. Затем экземпляр инициализируется. Элементу myaddress .name присваивается строка "Bradley J ones", элементу myaddress .addl — строка "RTSoftware", элементу myaddress.add2 —— строка "Р.О.Вох 1213". Поле myaddress.city инициализируется строкой "Carmel", поле myaddress . state — строкой "IN", a поле myaddress . zip —— стро- кой "46082-1213".  word myWord;  Чтобы указатель ptr указывал на второй элемент массива, следует выполнить оператор ptr++;.  760 Приложение Е 
Упражнения  1. Это определение таково:  struct time { int hours; int minutes; int seconds; }; 2. Обе указанные задачи выполняются следующим объявлением: struct data { int valuel; float value2; float value3; } info; 3. info.value1 = 100;  4. Это делается следующими операторами:  struct data *ptr; ptr = &info;  5. Два способа присваивания таковы:  ptr->value2 = 5.5; (*ptr).value2 = 5.5; 6. BOT это определение: struct data { char name[21]; struct data *ptr; }: 7. Требуемые структура и тип определяются так: typedef struct { char address1[31]; char address2[31]; char city[11]; char state[3]; char zip[11]; } RECORD;  8. B следующем операторе инициализации используются значения из ответа на контрольный вопрос5: RECORD myaddress = { "RTSoftware", “Р.О. Вох 1213“, "Carmel", "IN", "46032-1213" };  9. Этот фрагмент содержит две ошибки. Первая из них состоит в том, что структура должна иметь метку типа, которой здесь нет. Вторая состоит в способе инициализации перемен- ной sign. Инициализирующие значения должны стоять в фигурных скобках. Вот исправ— ленньпікод: struct zodiac { char zodiac_sign[21];  Ответы 761 
int month; } sign = { "Ьео" ‚ 8 }; 10. Этот фрагмент содержит всего одну ошибку. В каждый момент может использоваться для работы с данными только Один член объединения. Это же справедливо и для инициализа- ции объединения: инициализировать можно только первый элемент. Вот исправленный код: /* объявление объединения */ union data { char a_word[4]; long a_number; } generic_variable = { "WOW" };  День 12  Контрольные вопросы  1. Область действия переменных —— это доступность, или видимость, переменной из различ- ных составных частей (например, функций) программы  2. Локальные переменные доступны только в блоке, в котором они объявлены. Внешние пе- ременные доступны из любого блока программы.  3. Если переменная объявлена внутри функции, это делает ее локальной. Если объявление нахоцится вне функций, переменная является внешней. \ 4. Локальная переменная может быть автоматической (по умолчанию) или статической. Ав- ‚томатическая переменная создается заново при каждом вызове функции и уничтожается при выхоце из нее. Статическая переменная остается в памяти и сохраняет свое значение даже в промежутке между вызовами функции.  5. Автоматические переменные инициализируются при каждом вызове функции, в которой они объявлены. Статические переменные инициализируются только при первом вызове функции.  6. Не всегда. Объявление регистровой переменной является всего лишь запросом, и нет га- рантии, что компилятор его выполнит.  7. Неинициализированная глобальная переменная инициализируется значением 0 по умол- чанию. Тем не менее, всегда лучше прибегать к явной инициализации.  8. Не инициализированная явным образом локальная переменная вообще не инициализиру- ется автоматически. Она может соцержать все, что “угодно. К такой переменной не стоит обращаться, не присвоив ей предварительно какое-либо значение.  9. Переменная count теперь является локальной по отношению к блоку. Функция printf() не будет иметь доступа к переменной count, и компилятор сообщит об ошибке.  10. Если локальную переменную необходимо помнить между вызовами функции, она объяв- ляется статической. Например, целочисленная переменная vari объявляется так:  static int vari;  11. Ключевое слово extern используется как молификатор класса памяти. Оно указывает, что соответствующая переменная объявлена где-то в другом месте программы.  762 Приложение Е 
12. Ключевое слово static используется как модификатор класса памяти. Компилятор  хранит такую переменную на протяжении выполнения всей программы, не уничтожая и не создавая заново. Переменная сохраняет свое значение между вызовами функции.  Упражнения  1. 2.  register int х = 0; Ниже приведен исправленный код:  /* Иллюстрация области действия переменных. */ #include <stdio.h>  void print_value(int x);  int main( void )  { int х = 999; printf( "%d", х ); print_value( х ); return О; } void print_value( int x ) { printf( "%d", х ); }  Переменная var объявлена глобальной, поэтому нет необходимости передавать в функ- цию какие-либо параметры.  /* Использование глобальной переменной */ #include <stdio.h>  int var = 99; void print_value( void );  int main( void )  { print_value(); return 0; } void print_value( void ) { printf( "The value is %d \n", var ); }  Да, теперь переменную var необходимо передать в функцию печати как параметр.  /* Использование локальной переменной */ #include <stdio.h>  Ответы 763 
void print_value( int var );  int main( void )  { int var = 99; print_value( var ); return 0; } void print_value( int var ) { printf( "The value is %d\n", var ); }  Да, в одной программе можно использовать глобальную и локальную переменные с од- ним и тем же именем. В таких случаях локальные переменные имеют более высокий при- оритет, чем глобальные, внутри своих функций.  /* Использование глобальной и локальной переменных */ #include <stdio.h>  int var = 99; void print_func( void );  int main( void )  { int var = 77; printf( "Printing in function with local and global:" ); printf( "\nThe value of var is %d", var ); print_func(); return 0; }  void print_func( void )  printf( "\nPrinting in function only globalz" ); printf( "\nThe value of var is %d \n", var );  }  Код функции a_sample_function() содержит всего одну ошибку. Переменные можно объявлять только в начале блока, так что с объявлениями ctrl и star все в порядке. А вот переменная ctr2 объявлена не в начале блока. Ниже приведена исправленная функция и текст всей программы.  Примечание: если используется компилятор С++, а не С, то компиляция пройдет без ошибок. В языке С++ правила, касающиеся объявления переменных, несколько отлича- ются от С. Тем не менее, в этом учебном упражнении следует строго придерживаться правил С, даже если компилятор делает “поблажки”.  #include <stdio.h> void a_sample_function( void );  764 Приложение Е 
int main( void )  { a_sample_function(); return 0; } void a_sample_function( void ) { int ctrl; for ( ctrl = O; ctrl < 25; ctr1++ ) printf( "*" ); puts( "\nThis is a sample function" ); { char star = '*'; int ctr2; /* исправлено */ puts( "\nIt has a problem \n" ); for ( ctr2 = 0; ctr2 < 25 ; ctr2++ ) { printf( "%c", star ); } } }  7. Программа работает без ошибок, хотя в ней есть что усовершенствовать. Прежде всего, нет необхолимости инициализировать переменную x значением 1, поскольку в заголовке оператора for она снова инициализируется нулем. Также бессмысленно объявлять пере- менную tally статической, так как внутри функции main() ключевое слово static не действует.  8. Значения переменных star и dash не определены, потому что они не инициализированы. Переменные являются локальными, и каждая может содержать произвольное случайное значение. Заметьте, что компиляция и компоновка прохолят без проблем, хотя скрытая угроза остается.  Имеется и второй сомнительный момент. Переменная ctr объявлена глобальной, но исполь- зуется только внутри функции print_function( ). Это небрежный подход к структурирова- нию программы. Переменную ctr лучше сделать локальной внутри print function( ).  9. Эта программа бесконечно выволит на экран приведенный ниже нехитрый узор. См. так- же упражнение 10.  X== ==х== :: :: ==Х== :: :: ==х== :: ==х==х== :: ==___  10. Проблема возникает из—за глобальной доступности переменной ctr. Как функция шаіп( ), так и print_letter2() одновременно используют переменную ctr в циклах. Функция print_letter2() изменяет значение ctr, поэтому цикл for B функции main() никогда не заканчивается. Это можно исправить несколькими способами. Во-первых, использовать два разных счетчика. Во—вторых, изменить область доступности ctr, объявив ее локаль- ной в шаіл() и print_letterz ( ).  Ответы 765 
Следует обратить внимание также на переменные letter1 и letter2. Каждая из них ис- пользуется только в одной функции, поэтому их следует сделать локальными. Текст ис- правленной программы приведен ниже. #include <stdio.h> void print_letter2(void); /* прототип функции */  int main( void )  { char letterl = 'X'; int ctr; for( ctr = 0; ctr < 10; ctr++ ) { printf( "%с”, letterl ); print_letter2(); } return О; } void print_letter2(void) { char letter2 = '='; ітп: ctr; /* это локальная переменная, не имеющая */ /* ничего общего c ctr в функции main() */ for( ctr = 0; ctr < 2; ctr++) \ printf( "%c", letter2 ); }  День 13  Контрольные вопросы  1.  2.  Ни в каких, разве что если соблюдать предельную осторожность. Всегда есть способ сде- лать то же самое по-другому, без goto, причем более рационально и безопасно.  Оператор break выполняет немедленный принудительный выход из циклов for, do. . .while, или while. Оператор continue передает управление в начало цикла, на его новую итерацию.  Бесконечный цикл выполняется неограниченное количество раз, потому что отсутствуют условия для его завершения. Это явление еще называют зацикливанием. Для организации бесконечного цикла следует поставить в заголовке for, do. . .while, или while условие, которое неизменно остается истинным.  Программа заканчивает работу, когда достигнут конец функции main() или встречается вызов функции exit( ).  Выражение в заголовке оператора switch может иметь тип char, int или long.  766 Приложение Е 
Оператор default — это один из блоков оператора многовариантного выбора switch. Ес- ли выражение в заголовке switch не совпало ни с одним значением из блоков case, то выполняется блок default.  Функция exit() принудительно завершает программу. В нее можно передать число, ко- торое возвращается в операционную систему при завершении программы.  Функция system( ) выполняет заданную команду на уровне операционной системы.  Упражнения  continue; break; B системе DOS ответ будет таким: system( "dir" ); B этом фрагменте кода ошибок нет. После оператора printf( ) в блоке case 'N' не нужно ставить break, потому что оператор switch все равно заканчивается в этом месте.  На первый ВЗГЛЯД можно подумать, что блок default: должен находиться в конце опера- тора switch, но на самом деле это не ошибка. Блок default: может стоять где угодно. Тем не менее, ошибка в листинге есть — B конце блока default: должен присутствовать оператор break.  Код из упражнения 5, переписанный с использованием операторов if, будет выглщеть примерно так:  if( choice == 1 ) printf( "You answered 1" ); else if( choice == 2 ) printf( "You answered 2" ); else  printf( "You did not choose 1 or 2" );  Вот пример бесконечного цикла:  do { /* любые операторы С */ } while ( 1 );  День 14  1.  2.  Поток — это последовательность байт. Программы на С используют потоки для выполне- ния операций ввода-вывода.  Перечисленные устройства классифицируются следующим образом. а) принтер — устройство вывода; 6) клавиатура —— устройство ввода; в) модем _— устройство как ввода, так и вывода;  г) монитор —— устройство вывода (хотя при наличии светового пера может стать и уст- ройством ввода);  Ответы 767 
д) жесткий диск —— устройство ввода и вывода.  Все компиляторы С поддерживают три стандартных потока: stdin (клавиатура), stdout (экран) и stderr (экран). Некоторые компиляторы также поддерживают потоки stdprn (принтер) и stdaux (последовательный порт СОМ1:). На компьютерах Macintosh поток stdprn не поддерживается.  Вот перечень используемых потоков: а) stdout 6)stdout B) stdin r)stdin д) функция fprintf() может работать с любым стандартным потоком вывода, т.е. stdout, stderr, stdprn и stdaux.  При вводе с буферизацией данные посылаются в программу только после нажатия поль- зователем клавиши <Enter>. При отсутствии буферизации данные посылаются в поток по одному символу за раз непосредственно при нажатии символьных клавиш.  При вводе с дублированием введенный символ автоматически посылается в поток stdout. При вводе без дублирования этого не происходит.  Между двумя операциями ввода можно вернуть в поток ввода только один символ. Сим- вол конца файла EOF K ним не относится — его вернуть нельзя.  Когда пользователь нажимает клавишу <Enter>, автоматически генерируется символ пе- рехода на новую строку. Он и обозначает конец ввода строки\.  Из перечисленных спецификаций ввода-вывода: а) "%с1" допустима; б) "%4с1" допустима; в) "%3і%с" допустима;  г) "%q%d" недопустима, поскольку спецификации или управляющего символа %q не существует;  д) " % % % і " допустима; е) " %9ld" допустима.  10. Поток stderr нельзя перенаправить. Он всегда соответствует экрану. В то же время поток  stdout можно перенаправить на другое устройство вывода.  Упражнения  1. 2.  printf( "Hello world" ); fprintf( stdout, "Hello world" ); puts( "Hello world" ); fprintf( stdaux, "Hello Auxiliary Port" );  Правильный ответ:  768 Приложение Е 
char buffer[31]; scant( "%30[‘*]”. buffer );  Этот текст можно вывести на экран следующим оператором: printf( "Jack asked, \"What is a backslash\?\"\nJill said, \ \"It is\r\\\1\nu): Подсказка: воспользуйтесь целочисленным массивом из 26 символов (по количеству букв  латинского алфавита). При вводе символа инкрементируйте соответствующий ему эле- мент массива.  Подсказка: вводите строки по одной за раз, а затем выводите форматированный номер строки, знак табуляции и введенную строку. Еще одна подсказка: все это уже сделано в программе print_it из “Самостоятельной работы 1”.  День 15  Контрольные вопросы  1.  8. 9.  Это фрагмент кода со всеми необходимыми объявлениями: float x; float *px = &х; float **ppx = &рх; Ошибка состоит в том, что используется всего одна операция обращения по указателю вместо двух, поэтому число 100 присваивается указателю рх, а не переменной x. Оператор следует переписать с использованием двойного обращения по указателю:  **ррк =100; Переменная array являегся массивом, состоящим из двух элементов. Каждый из элемен— тов сам является массивом и содержит три элемента. В свою очередь, каждые три элемен-  та обоих массивов тоже представляют собой массивы и содержат по чегыре элемента ти- па int.  Выражение array[0] [0] соответствует указателю на первый четырехэлементный массив типа int.  Первое и третье равенства истинны, второе ложно. void func1(char *p[]);  B данном случае это невозможно. Чтобы знать длину массива внутри функции, необходи- мо передать ее как еще один аргумент.  Указатель на функцию —— это переменная, содержащая адрес начала функции в памяти. char (*ptr)(char *x[]);  10. Если опустить круглые скобки вокруг ptr, получится прототип функции, возвращающей  ll. 12.  указатель типа char, a не указатель на функцию. Струкгура должна содержать указатель на структуру того же типа.  Это означает, что связанный список пуст.  Ответы 769 
13. Каждый элемент списка содержит указатель, который определяет следующий элемент в списке. Для обозначения первого элемента в списке используется старший указатель.  14. Переменные представляют собой следующее: а) varl ———— указатель на целочисленную переменную; 6) var2 —— целочисленная переменная; в) var3 ———— указатель на указатель на целочисленную переменную; 15. Смысл объявленных переменных таков: а) a -— массив из 36 (3x12) элементов типа int; 6) b —— указатель на массив из 12 элементов типа int; B) с —— массив из 12 указателей на переменные типа int. 16. Эти операторы объявляют следующие переменные: а) z ———— массив из 10 указателей типа char; 6) y —— функция, принимающая целый аргумент и возвращающая указатель типа char;  B) x -— указатель на функцию, принимающую целый аргумент и возвращающую сим- вольное значение типа char.  Упражнения  1. float (*func)(int field); 2. int (*menu_option[10])(char *title); Массив указателей на функции можно использовать в связи с меню. Номер пункта, вы—  бранного из меню, соответствует индексу массива указателей. Например` если выбран пункт 5, выполнится функция, указатель на которую стоит в пятой позиции массива.  3. char *ptrs[10];  4. Переменная ptr объявлена как массив из 12 указателей типа int, a не указатель на массив из 12 целых чисел. Правильный код имеет такой вид: int x[3][12]; int (*ptr)[12]; ptr = x;  5. Ниже приведено одно из возможных правильных решений.  struct friend { char name[35+1]; char street1[30+1]; char street2[30+1]; char city[15+1]; char state[2+1]; char zipcode[9+1]; struct friend *next;  770 Приложение Е 
День 16  Контрольные вопросы  I-t  2. 3.  9.  Текстовый поток автоматически преобразует символ конца строки (\п), отмечающий ко- нец ввоцимого текста, в пару символов “возврат каретки — перевод строки”, используе- мую для разделения строк в файлах системы DOS. Двоичный поток этого не делает, вос- принимая все байты как равноправные данные.  Файл необходимо открыть с помощью библиотечной функции fopen( ).  Необходимо указать имя файла и режим его открытия. Функция fopen() возвращает ука- затель на структуру типа FILE, который затем используется во всех операциях с файлом для ссылки на него.  Форматированный, символьный и блочный. Последовательный и произвольный. Это признак конца файла. Он является символической константой, равной —1.  Эта константа используется при работе с текстовыми файлами для распознавания конца файла. В двоичном режиме для этой цели используется функция feof( ). B текстовом режиме можно сравнивать данные с EOF или также пользоваться f eof( ).  Указатель позиции определяет точку в заданном файле, в которой должна выполняться следующая операция чтения или записи. Его положение можно изменить с помощью функций rewind( ) или fseek( ).  10. Указатель позиции стоит в начале файла, на смещении 0. Исключение составляет случай  открытия в режиме добавления данных, когда указатель позиции устанавливается в конец файла.  Упражнения  fcloseall(); rewind(fp); или fseek(fp, 0, SEEK_SET);  Ошибка состоит в том, что нельзя проверять символ на равенство с EOF при работе с дво- ичными файлами. Вместо этого следует пользоваться функцией f eof( ).  День 17  Контрольные вопросы  1.  Длина строки — это количество символов в ней, начиная с первого и до завершающего нулевого (но не считая его). Длину строки можно определить с помощью функции strlen().  Ответы 771 
2. Необходимо выделить достаточный объем памяти для хранения новой строки.  3. Конкатенация означает сцепление, слияние двух строк, при котором одна присоединяется в “хвост” другой.  4. Выражение “одна строка больше другой” означает, что коды ASCII символов в Одной строке превосходят по величине коды символов в другой.  5. Функция strcmp() сравнивает две строки целиком, тогда как strncmp() —— только задан- ное количество символов внутри строки.  6. Функция strcmp() выполняет сравнение двух строк с учетом регистра букв (например, 'А' и 'а' считаются разными). Функция strcmpi() игнорирует регистр символов, т.е. те же две буквы принимаются ею за Одинаковые.  7. Макрос isascii( ) проверяет, принадлежит ли переданный в него символ к стандартному набору ASCII от 0 до 127. Принадлежность к расширенному набору не проверяется.  8. Макросы isascii() и iscntrl() возвратят TRUE, a все остальные — FALSE. Помните, что анализируется код символа.  9. Число 65 соответствует символу A no кодовой таблице А‘ЗСП. Значение TRUE возвратят следующие макросы: isalnum( ), isalpha( ), isascii( ), isg‘raph( ), isprint( ), isupper( ).  10. Функции анализа символов проверяют принадлежность того или иного символа к опреде— ленной категории -— буквам, знакам препинания и т.п.  Уп ражнения 1. TRUE (1) или FALSE (О).  2. Функция atoi() возвратит следующее: а)65 б)81 в)—34 г)0 д)12 е)0 3. Функция atof() возвратит следующее: а)65.000000 б)81.230000 в)—34.200000 г)0.000000 д)12.000000 e)1000.000000  4. Для строковой переменной stringZ не было выделено место в памяти до обращения к ней. В итоге функции strcpy( ) некуда копировать значение stringl.  772 Приложение Е 
День 18  1.  8.'  9.  При передаче по значению функция получает копию переменной-аргумента. При передаче по адресу функция получает адрес переменной-аргумента. В первом случае функция не может изменить значение исходной переменной, а во втором -— может. Указатель типа void может указывать на любой объект языка С без различия типа. Отсю- да его название -— нетипизированный указатель. Наиболее частое применение указателей типа void _— это объявление параметров функ- ций, которые таким образом могут принимать указатели на данные произвольного типа. Приведение типа определяет, на объект какого типа указывает данный указатель типа void B данный момент. Эту операцию необходимо выполнить перед обращением к указа- телю для получения данных. Функция, которая принимает список аргументов переменной длины, должна обязательно принимать хотя бы один фиксированный аргумент. Таким образом функция получает ин- формацию о длине списка аргументов при ее вызове. Макрос va_start( ) используется для инициализации списка аргументов, va_arg( ) _— для получения аргументов, а va_end() —— для очистки данных после считывания аргументов. Это вопрос “на засыпку”. Указатели типа void нельзя инкрементировать, потому что ком- пилятор не знает, какое смещение к нему прибавить.  Функция может возвращать указатель на переменную любого из типов С, а также на мас- сивы, структуры или объединения.  va_arg()  10. Этими элементами языка являются макросы va_list( ), va_start( ). va_arg() и va_end( ).  Упражнения  1.  int function( char array[] ); int numbers(int *nbrl, int *ner, int *nbr3); Правильный ответ: int numberl = 1, numberZ = 2, number3 = 3; numbers(&number1, 8number2, &number3);  Ha первый взгляд код кажется странным, но ошибок в нем нет. Функция берет значение, на которое указывает nbr, и возводит его в квадрат, умножая на само себя.  При работе с переменным списком аргументов необходимо использовать все стаНДартные макросы: va_list( ), va_start( ), va_arg() и va_end( ). Пример правильного обращения с переменным списком аргументов приведен в листинге 18.3.  Ответы 773 
День 19  Контрольные вопросы  . Тип double. 2. B большинстве компиляторов тип time_t эквивалентен типу long, но полной гарантии нет. Это можно уточнить по документации или тексту стандартного заголовочного файла time.h. 3. Функция time() возвращает количество секунд, прошедшее с полуночи 1 января 1970 г., тогда как clock() —— количество сотых долей секунды, прошедшее с момента запуска программы. 4. Никаких. Эта функция просто выводит на экран сообщение об ошибке. 5. Отсортировать массив по возрастанию. 6. 14. 7. 4. 8. 21. „ 9. 0, если элементы равны; положительные, если элемент 1 больше элемента 2; отрицатель- ные, если элемент 1 меньше элемента 2. 10. NULL. Упражнения l. Правильный ответ: bsearch( myname, names, (sizeof(names)/sizeof(names[0])), sizeof(names[0]), comp_names); 2. B программе допущены три ошибки. Во-первых, при вызове функции qsort( ) He указана ширина поля. Во-вторых, при передаче имени функции как аргумента в qsort() после него не нужно ставить круглые скобки. В третьих, в программе не хватает функции сравнения: qsort( ) использует функцию compare function( ), которая в программе не определена. _ 3. Функция сравнения возвращает неправильные значения. Она должна возвращать положи-  тельное число, если elementl больше elementZ, и отрицательное, если elementl меньше element2.  День 20  Контрольные вопросы  1.  Функция malloc( ) выделяет указанное количество байт памяти, в то время как са11ос( ) выделяет объем памяти для указанного количества объектов заданной длины. Функция  774 Приложение Е 
са11ос( ) заполняет все байты выделенного участка памяти нулями, а ша11ос( ) не выпол— няет никакой инициализации.  2. Чтобы сохранить дробную часть при делении целых чисел и поместить результат в веще- ственную переменную. .  3. Типы результатов будут следующими: a)long 6)int B)char г) float n)float  4. Динамическое распределение памяти выполняется в ходе работы программы. С его по— мощью можно запросить у операционной системы ровно столько памяти, сколько необ— ходимо. 5. Функция memmove() работает корректно даже тогда, когда копируемые участки памяти накладываются друг на друга, а memcpy() не справляется с этой ситуацией. Если же участ- ки не накладываются, функции работают Одинаково.  6. Объявить битовое поле длиной 3 бита. Поскольку 2 в третьей степени равно 8, трех битов достаточно для хранения чисел от 1 до 7.  7. В двух байтах. Это делается следующим образом с помощью битовых полей структуры: struct date  {  unsigned month : 4; unsigned day : 5; unsigned year : 7;  }  B этой структуре данные занимают 16 бит, т.е. два байта. Четырехбитовое поле month с диапазоном значений от 0 no 15 содержит месяцы, пятибитовое поле day с диапазоном от 0 no 31— дни, а семибитовое поле year— годы. Диапазон поля year OT 0 no 127 дает возможность обозначать годы с 1900 по 2027.  8. 00100000 9. 00001001  10. Вычисление этих двух выражений дает один и тот же результат. Операция исключающего ИЛИ с числом 11111111 эквивалентна операции побитового дополнения —- все биты ис- х0дного числа меняются на противоположные.  Упражнения 1. Правильный ответ: long *ptr;  ptr = malloc( 1000 * sizeof(long) );  2. Правильный ответ:  Ответы 775 
long *ptr; ptr = са110с( 1000, sizeof(1ong) );  C HCHOJ’IbBOBaHHCM цикла И присваивания:  int count; for ( count = 0; count < 1000; count++ ) data[count] = 0;  C использованием функции memset( ): memset( data, 0, 1000 * sizeof(f10at) );  Код благополучно пройдет компиляцию и запустится на выполнение, но результат расчета будет неправильным. Переменные numberl и number2 — целочисленные, поэтому и ре- зультат их деления также будет целочисленным, от которого отброшена дробная часть. Чтобы получить правильный ответ, необходимо привести Один из аргументов к типу float:  answer = ( (float)number1 ) і number2;  Поскольку р — указатель типа void, ero следует привести к определенному типу, прежде чем использовать в операторе присваивания. Третья строка кода должна иметь следую- щий вил:  *(float*)p = 1.23;  Нет, такая форма недопустима. Битовые поля должны стоять в структуре первыми. Вот исправленное объявление структуры:  struct quiz_answers { unsigned answer1 : unsigned answerz : unsigned answer3 : unsigned answer4 : unsigned answers : char student_name[15]  `. '... '... '... '... '... `. " " `. "  День 21  Контрольные вопросы  1.  Термин многомодульное программирование обозначает такой подход к разработке слож- ных программ, при котором они разбиваются на несколько файлов исходного кода по оп- ределенной системе.  Главный модуль содержит функцию main( ).  Это делается для того, чтобы избежать возможной двусмысленности кода при передаче сложных выражений. Скобки гарантируют, что аргумент будет вычислен полностью до выполнения операций макроса.  По сравнению с обычными функциями макрофункции позволяют добиться большего бы- стродействия программы, но приводят к возрастанию объема кода.  776 Приложение Е 
Оператор defined() проверяет, было ли указанное символическое имя определено дирек— тивой #define. OH возвращает TRUE, если имя определено, и FALSE, если нет.  Заключительная директива #endif.  Скомпилированные файлы исходного кода С являются файлами объектного кода и имеют расширение .OBJ.  Директива #include вставляет копию содержимого файла в указанное место другого файла.  Директива #include с именем файла в двойных кавычках требует, чтобы файл разыски— вался в текущем каталоге. Если имя файла заключено в угловые скобки <>, то заголовочный файл разыскивается в стандартном каталоге С.  10. Макрос _DATE_ используется для помещения даты компиляции в код программы.  11. На строку с именем исполняемого файла программы, включая полный путь к нему.  День 22  Контрольные вопросы  1.  В OCHOBC ЯЗЫКОВ объектно—ориентированного nporpaMMuponaHml лежат следующие прин— ЦИПЫ:  а) полиморфизм; б) инкапсуляция; в) наследование; г) повторное использование объектов и кода. Полиморфизм. Таких нет. Все средства С целиком и полностью входят в С++.  На С можно программировать в объектно—ориентированном стиле, но это довольно слож— ный и мучительный процесс. А вот языки С++ и Java специально разработаны для ООП. Если уж заниматься объектно—ориентированным программированием, то стоит всерьез подумать об освоении С++ или Java.  Нет. Язык Java задумывался как вариант С++, из которого удалены все черты, создающие необоснованную сложность и повышенную вероятность ошибок.  Для С++ ответ будет положительным. В этом языке можно следовать как процедурному, так и объектно—ориентированному подходу по своему выбору, и совмещать их в любых пропорциях. А вот в случае Java выбора нет— автор программы обязан использовать только объектно—ориентированную технику программирования.  С помощью потока cout. C помощью метода System.out.print1n() Для этого служит метод System. Console.WriteLine( )  Ответы 777 
День 23  Контрольные вопросы  acupunc—  Это объект cout. Ни в каких. Эта функция не рекомендуется для использования в С++. True или False. Переменные в С++ можно объявлять в любом месте программы. throw  C помощью значений по умолчанию можно просто сделать так, чтобы количество аргу- ментов у функции варьировалось в некоторых пределах. А вот перегрузка функций дает возможность создать под одним именем целое семейство функций, принимающих аргу- менты разных типов и в разных количествах.  Все три прототипа имеют одинаковое количество и тип параметров, поэтому не могут быть перегружены. int triangle( int sidel = 0, int sideZ = 0, int side 3 = 0 );  Это вопрос с подвохом. Хотя определение и правильное, но встраиваемая функция не все- гда подставляется в код программы, а только тогда, когда компилятор сочтет нужным сделать это для оптимизации исполняемого кода.  День 24  Контрольные вопросы  1.  В С++ структура представляет собой частный случай класса. Члены структуры по умолча- нию являются данными открытого типа (public), a члены класса — закрытого (private). Если применяются функции-члены, следует пользоваться классом.  Класс —— 3T0 всего лишь шаблон, определение нового типа, а не конкретный объект. Он не может иметь значений. С помощью объявленного класса можно создать объект, а затем присваивать ему значение.  Объект — это переменная, объявленная с помощью класса; экземпляр класса. Создание экземпляра класса — это создание объекта, благодаря которому абстрактное описание типа воплощается в конкретной совокупности данных.  Классы обладают характеристиками, следующими всем трем принципам ООП. В них реа- лизована инкапсуляция, поскольку данные и операции объединены в единое целое. Поли- морфизм проявляется в том, что в классах могут быть перегруженные члены, включая их конструкторы. Наследование также имеет самое прямое отношение к классам С++ (см. дополнительное занятие 25).  Закрытые данные (private) объекта класса доегупны только другим членам того же объекта.  Если данные класса объявлены как public, то полный доступ к ним имеют все модули программы, в которых доступен сам объект класса.  778 Приложение Е 
8. Конструктор класса выполняется при создании объекта (экземпляра). 9. Деструктор класса вызывается при уничтожении объекта.  10. Объект класса ничем не отличается ПО СВОЙСТВЗМ как поле. данных класса ОТ ЧЛСНОВ ТОГО же класса, имеющих ЦРУГИС ТИПЫ. Knacc, т.е. ТИП, МОЖНО ИСПОЛЬЗОВЗТЬ В ТОМ же контек- сте, ЧТО И элементарные ТИПЫ, М&ССИВЫ, СТРУКТУРЫ и Т.П.  11. Класс guppy является п0дклассом. 12. Класс fish является базовым классом. 13.class guppy : public fish { 14. Вначале выполняется конструктор базового класса.  15. Вначале выполняется деструктор подкласса.  День 25  Контрольные вопросы  . Тип long.  Да, но они называются методами.  Цикл while или do. . .while.  Импортирование (подключение) внешних классов в программу на Java.  (“идр-ЮН—  В Java имеется три разновидности комментариев. а) текст, заключенный между символами / * и */ ; 6) текст после символов / / до конца строки; в) текст между символами /** и */.  6. Идентификаторы в Java чувствительны к регистру, поэтому переменные count и Count считаются различными.  7. Любые типы Java. Нкаких ограничений нет.  День 26  Контрольные вопросы  1. Здесь выбора нет —— метод-конструктор всегда обязан иметь то же имя, что и сам класс.  2. Ключевое слово extends используется в первой строке определения класса для указания родительского класса, свойства которого наследует данный.  3. Нет. Если возвращаемое из метода значение объявлено как void, то метод не возвращает ничего.  4. Да, можно. Перегружеиные конструкторы должны отличаться списком параметров —— их количеством и/или типом.  Ответы 773 
КОНСТРУКТОР ВЫЗЫВЗСТСЯ В МОМСНТ СОЗДЗНИЯ ЭКЗСМПЛЯРЗ класса.  Нет, не любой. Если класс объявлен с ключевым словом final, то он не может быть роди- тельским _— наследование от него невозможно.  Чтобы класс не мог иметь наследников, в его определении используется ключевое слово final.  Her, объект абстрактного класса создать нельзя. От такого класса можно только наследовать.  День 27  Контрольные вопросы  Да, должен. Компилятор Java не разрешает использовать блоки try без catch.  Нет, не выполняет. Если необходимо перейти на новую строку. следует добавить символ \n B выводимый текст самостоятельно.  Класс java . applet . Applet. Окно приложения отображается на экране при вызове метода Show.  Есть несколько способов реакции на события ввода, но самым важным из них является метод action, вызываемый автоматически, когда случается событие, например, Щелчок мыши.  Да., может, но при выполнении аплета этот метод игнорируется. Вызываютсв методы init и start, именно в таком порядке.  Метод destroy.  День 28  Контрольные вопросы  1.  2.  3.  Можно назвать гораздо больше, чем три достоинства языка С#. а) простота; б) современность; в) объектно-ориентированный характер; г) мощность и гибкость; д) краткость выражений и операторов; е) модульная структура; ж) возрастающая популярность.  Сокращение [L означает Intermediate Language (промежуточный язык), а CLR — Common Language Runtime (3T0 название операционной среды для выполнения программ на С#).  Эти этапы таковы:  780 Приложение Е 
а) создание файла исходного кода; 6) компиляция программы; в) выполнение программы. csc my_prog.cs .cs  Да, пригодно, ХОТЯ И He рекомендуется.  .“???  Пересмотреть исхоцный текст программы и найти фактическую ошибку, вынуждающую программу выполнять неправильные операции.  8. Это язык, понятный компьютеру на самом низком, аппаратном уровне. 9. Write” и WriteLine( ).   1  Упражнения ?  l  1. Исполняемый ене-файл в окне текстового редактора будет выглядеть как хаотический на‹ бор символов, не имеющий никакого внешнего сходства с файлом исходного кода.  2. Программа выводит на экран блок символов 'х ' :  XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX  Ответы 78 
Приложение Ж    мм; пввпіі вампвшпяшвпьнп  Среда разработки Dev-C++  Интегрированная среда разработки Бем-С++ находится на прилагаемом к книге компакт- диске. Хотя это компилятор языка С++, его можно с равным успехом использовать и для соз- дания программ на С. В этом приложении рассматриваются следующие вопросы.  I Что представляет собой Dev-C++ I Как установить Бем-С++ I Как ввести, скомпилировать и выполнить программу в среде Dev-C++   IIIIIIIIII Обратите внимание, что Bloodshed Dev-CH является приложением для ‘ Windows. Этот программный продукт может работать только под управле- нием операционной системы Windows 95 или более поздних версий Win- dows. Bonee подробную информацию o системных требованиях компиля— тора можно найти в файлах его документации.     Что представляет собой Dev-C++  Bloodshed Dev-C++ является интегрированной средой разработки, используемой для соз— дания, компилирования и выполнения программ на языках С и С++. Полный комплект фай- лов для установки этой среды (инсталляционный пакет) имеется на прилагаемом к книге компакт—диске. В состав Dev-C++ входит множество инструментальных средств, например, мастер созда- ния инсталляционных пакетов, автоматический генератор библиотек динамической компо- новки, шаблоны проектов, библиотека пиктограмм и т.д. Более подробную информацию об этих средствах можно найти в справочных файлах, входящих в комплект ИСР. Для работы Бем-С++ необходима система Windows 95 или более новая ее версия (98, 2000, NT и т.д.). Кроме того, компьютер должен иметь не менее 8 Мбайт оперативной памяти, 30 Мбайт свободного пространства на жестком диске и процессор с тактовой частотой не менее 100 МГ ц. Для более качественной и быстрой работы рекомендуется иметь 32 Мбайт памяти, 45 Мбайт на жестком диске и процессор с тактовой частотой не ниже 233 МГ ц. 
Установка Dev-C++ B системе Microsoft Windows  Как уже неоднократно говорилось ранее, инсталляционный пакет Dev—C++ имеется на компакт-диске, прилагаемом к книГе. При вставке компакт-диска в дисковод открывается ме- ню (в том случае, если включен режим автозапуска). Чтобы начать установку Бем—С++, выбе- рите пункт Launch Dev-C++ Installer (Запустить установщик Dev-CH) в разделе Software Library (Библиотека программ). Если автозапуск отключен, то программу установки придется запустить с диска вручную. Она имеет имя Setup . EXE H находится в каталоге Dev-C++. Если дисководу для компакт- дисков соответствует буква D : , то, соответственно, для запуска установки следует ввести ко- манду D: \ Dev-C++\Setup . EXE. Независимо от способа запуска этой программы, прежде всего на экране появится окно лицензионного соглашения (рис. Ж.1). Среда разработки Бем—С++ распространяется в соответствии со стандартным лицензион- ным соглашением GNU. Если вы незнакомы с этим соглашением, прочитайте текст соглаше— ния в диалоговом окне, показанном на рис. Ж.].   ‚52:5  :, ь ’;; ‹ ‚4, "__ ‚ддт… \ «А… "к… .а д;, « .` \ \— &`: … «‹ \{ ‘ «А __…1 к… . ‚‹ — ‘ „ fix, * „:; №94»; о“ Setup naval-$323.,  Вен-С++ 4     Mohawk“? я“?“  х…“    Рис. Ж'. 1. Лицензионное соглашение Dev—C++  Чтобы продолжить установку, щелкните на кнопке Yes. Откроется диалоговое окно, по- казанное на рис. Ж.2. В окне, показанном на рис. Ж.2, необходимо выбрать желаемый тип установки. Основным выбором является обычная установка (Typical). B этом же диалоговом окне можно изменить каталог, в который устанавливается Бем-С++. По умолчанию назначен каталог Dev—C++ Ha диске С :. Если необходимо установить программу в другое место, щелкните на кнопке Browse... (Изменить...) и выберите новый каталог. Сделав свой выбор в данном диалоговом окне, щелкните на кнопке Next для продолжения установки.  Среда разработки Dev-C++ 783 
  " шпшызщьшшцчтмшм ' :'(} mflborumdhhmam " Р…”) @ іР‘Ч RWMM шкивы  I \  :\ #,##…ЁРМЩЙЬЫЫиФштдщ-и’ ` .' и“. №, \ ”. “.  .Г № ?шщйшоіэсьйсмщшщыд .` д` . (" __ :! яес‘т‘шшжг‘свпны ” x !…" ‘    Dev-OH- 4  , 0№ЩМС№9М “3.22" ` och-Ia = . ‚ ‚ ` \\  ')“ ‚\ \    Рис. Ж'. 3. Установка файлов Dev-C+ +   @…“ Установка Вен—С++ может выполняться также в режимах Compact (Компактная) и Custom (Управляемая пользователем). При компактной ус— тановке программа занимает меньше всего места на диске за счет отказа от некоторых возможностей. При установке типа Custom пользователь имеет полный контроль над процессом.     784 Приложение Ж 
После выбора типа установки начинается копирование файлов на жесткий диск, как пока— зано на рис. Ж.3. После завершения копирования программа установки добавляет ярлыки Dev-C++ в ме— ню Start (Пуск) системы Windows. Затем открывается последнее диалоговое окно. В этом окне можно выбрать, следует ли далее открыть файл readme для чтения и запустить среду Dev-C++. Обе эти операции необязательны, так что можно просто щелкнуть на кнопке Finish (Закончить) для завершения установки. После этого программу можно запускать из меню Start (Пуск).  Состав пакета Dev-C++  При установке среды разработки Dev-C++ в меню Start (Пуск) появляется целый ряд но- вых ярлыков. Все они собраны в каталоге Dev-C++ и указывают на программы (сама ИСР) или файлы (справочные и текстовые), входящие в состав коммента поставки Dev-C++. C их помощью программы можно запускать на выполнение, а файлы —— открывать для чте- ния. Самый важный ярлык, Dev-C++, запускает среду разработки и позволяет начать рабо- ту с программным проектом. Наша книга посвящена программированию на С, а не работе в среде Dev-C++. Подробную информацию об этой ИСР можно найти в ее справочной и обучающей системах. В следую- щих разделах будет лишь вкратце рассказано, как создать и скомпилировать программу на языке С в среде Dev-C++.  :! 53:1; „:», flew ah: (Emu t { imp 625 Ён? :: :  Работа с Dev-C++  B среде Dev-C++ можно разрабатывать программы как на С, так и на С++. Чтобы запус- тить среду, выберите пункт меню ЭіапФОеч-СНФОеч—СН (ПускФОеч—СнфОеч-СН). В результате откроется главное окно среды разработки, показанное на рис. Ж.4. Хотя обычно в Dev-C++ для создания программ используются файлы проектов, в ней можно также ввести и скомпилировать отдельный файл исходного кода С. Файлы проектов в основном предназначены для разработки многомодульных приложений. Большинство примеров программ этой книги помещаются в один файл, так что для их компиляции про- ект не понадобится.   Настройка параметров среды Dev-C++  Прежде чем вводить и компилировать программы, необходимо настроить несколько важ- ных парамегров. Эти операции достаточно выполнить один раз и в будущем не повторять. Чтобы настроить компилятор на соответствие стандарту ANSI, выберите пункт меню Ор- тіопзФСотрііег options (ПараметрыФПараметры компилятора). Откроется диалоговое окно, показанное на рис. Ж.5. В этом диалоговом окне перейдите на страницу С/С++ compiler (Компилятор C/C++). Откроется диалоговое окно, показанное на рис. Ж.6. В нем прежде всего следует установить флажок Support all ANSI standard C programs (Поддержка всех программ стандарта  Среда разработки Dev-C++ 785 
ANSI C). После этого компилятор будет придерживаться всех команд и требований'сгандарта ANSI. Затем следует установить флажок Attempt to support some aspects of traditional C preprocessors (Попытка пощержки некоторых средств традиционных препроцессоров С). Более подробно о назначении этих флажков°можно узнать из справочной системы ИСР.    ___—_.__——__—___—____—- ___—  .__... DL‘V- С++ 4          : ; /\ 2.2- . ‚ _ $32.42 щдмгв Em “412:4.- . „“ГМК; &:&, ‚2% ‘ 5.1-3 2 ‹: 22; „,и… › . „ Ji— ‘— ._ ‚$$—а.с. “...4. ".—вид 3 gm“ М…": ' “*$ 251‘ ‹; $23.34 .; 33/“: ' _"~W‘g “… fl"; ”$$$“ " 239‘15 “‘*’.”ё, “Ы‘ЁЁ‘ ЁЁ“ 93:94- “5,5% 3'5 ЁЁ д……м 22-22 17,5335 $$$ . „‚_ … . “a, \ 1.2..- .f . .... 2 А.: _ь_\_"4 *— '2" Emma ' ЧЗЁЯЬЁ, *‘]. _- ЁЁЗ‹ {Ёе-Ач; „{; -__________№_- -..--. _.--.-- .. .-.-_- ‘-.—=-.-_ *` * ^`Ё3 ’ “i2 5“: * $т^*щ* 2 1222:5222“ 22' «…: E“? 2» 2 ЁЁ 2:372“ №№}? $" № .! {b2 ' -‘2 02‘2". „Ё \"22-2 ... „`.… " 2‘ 'T W“’t ‚ЗА, ‚_ р…е’рЖ i «1.35.93373 им} В}? E $22}? 71,712 2, %*ЁМЫМ.Ё<№№„ or; 2 “* «2 mafia-3}» М „д … митек… Alibi- Шо.}. л.:… :. *, “№5 $2.27??? пч— * ` 2 2 ` » „$ \. > "`}; W 23.2, ' _ \\ ` . . “АПЗ. \ д,… ‚_в/‚е:, ‹… , f" 'J-n‘v: ' ‚, 2‘9" 3 J V) в.”}: ‚. ^ ^ TEX )2 ‚,; (A "? "‘2E{::\ "^ ‘3‘" " \ ,7 m “(:"КПЁ; f’z" ‚‘ .:!‘ь‘ А \~ .‚Ё" (' ”My 1 ”Wu.“ “‘ „., {„ ›“ A . :* @? :…“ %: ‘-о .‹ ‚…Не—*..…. г » ,“ $ ‚_ “а 2302 ‘4‘)??? \‘v ’” „?:—`;} _ » ‘ ,а 6):”); 52" °\" 2 „› › 5 . , ,4 ‚?(—.; д`  * ,  2 ‚и ‚Т «и  ‚“  M212. @@@. 331, §§§f  и“ …№^Э› * “№№; » $$?—’,' 3232??         .-__.__-_......__ ___... _... -.....'___..._.._-__. __ ___—___„ц . _.... ... ...-- _. _.... . …  ` 2  = 2..- … * * : -ь* i "' " " . „ "" ~ . """ _г‘ . . „б…,-па.” {Ё .. 5? 23:2 I“ _ё'?‘ ‘2 заветы… 2‘ 2*":"23'2 22-22222 ш…ъ’ёмм ‘ Li ‹ %% \_`_<>.`:`›^› Ъікёэгед 923  Рис. Ж 4. Главное окно среды Dev-C+ +    Пат; к… 2; ‚с.:… "_                          : ?,… 1322M r w.%\"§*&° "'ЁМЪ`5ЦЖх $$$“, ё‹ій‹ ‘53)“   ни.—.‘}    г wax-‘22:: ‚ …Мы? …… л‘г ‚\. {рд 2-22 №…… „ 2 ` WM \‘0 W [" ’3" .и, ’* >’ \\ г. о . м V ^): W2 15$;Ёіцгд J: №№} Mimi . 2g? * ‚№ “38W. .222 №№ map-4w № $52: 2'2 23° ,: s2: “:, ^ >“; ‚+ ‘42-: 2.22» “‹ WWW“ 22;“ в new“: :7":er Ъ:“ ”'2 „Маги 22w ' ?”…" w Ё 2-. “Ву,: №М№”‚*\*ЁМЧ”СЪЁ _' . …. … ‘Ъё ° %%; “43%” » д.9 , _ - = Жар"! …. ‚ \ ^ ’ ., “ ' xg‘g‘f' Wig $$$ . Р‘ ‚&&& 5"- “а. 2 2 Ёжъ: 51"“ °“Р`д’№ "2 “\“ “2‘22; «Ё _, ж.д; ‚* ‚^ - 2 ~ № ‹ ** ^“ “… *‘:Ь. дім—№ ёу “=“ "272242“ 2 & ЁЁ} 22-2222 , gig“; {щ 2. „джа &ъіі“? . а , hr “$2253?! Y _ , “g? „ЁЬ\ SW39 "1? _ .\ ;Ё‘Н $ЪЁЁ№°Ё ¢k1wwl$‘<"f\§egd(3‘ 3 у, дій—‚:\ ?“\l:\ »“ 95.2“ r „"А; 9 ЖЁ№ Ё *» *‘ ”* "”2“ №№? ‚3 *::‘ё {*; №№” W он "' 229W "22:51:22“ 5 ' h ^ \\ № - $§§2§‘v§°3~’ AS \ «к "+ я ' ‹ "іі“ . *‘А ‘2‘)» ‘ ‘ @. Ац'і_-\ь№ “`;, *';д' \ г}, _ ";—№ $2 Ех}; \ акт-531%,» № дд… $ \ ‘3} `." “\ дэ .": 2 = *‘ W922 дзэн“ * 2 ‘l‘ ‘ Wm?" \ `За-$5— :*нізёж \ 99$ "*2 * \ \ \иа „:, ` ’*` \ \, (\; 2- :‘bw ‘§ 22‘» ‚ и, … а.“ \ ЁЁ}. %“?ЗЁЗ ` § ^ ‹ \\ 22" Ъ ‘; 159 \V’N“ … «д\д ‹ «> с" Rm ° 33 ч .222 ;&. — 3' .. 2 . . ... “МЧ—“:? _ ддт—ых ‘22 & “»“ЁЪ‘КЪ ;; ' , ...2"“.'-“ ’ ‘ 513%“ ..:", %;… Ъцд` (\ “\\-.::] $ ‚\ “ . 2 _ .. ' _ч .2 ` ‘ 2 - `*` . .… .. 2 ' gr KW” 2. ;.}; } к“ :* ’ . .. гдэ“ «5" ЁЁЁЁЁЁ ’5'7 . \ » {№: * m ім „… up .:" - : “a \“ › 5,2 :Жч …? ‚* *’ ‚ , , 2224923“ ^ ”31$? „… 42*”, {: {:};— › \ ЁЁ ‘T‘kcnlé- 2‘32- \? ЁЁ: „ 2222-2222;:- \{ :` ›“ }\ 223‘? 2' W2; &, № . *.— ' ’ 2 & . Чик? ‘*м "ЁЧ `3. % . . ° ' ` “. “2.2223535 23- * ЁЁ  J0 “&&&… 2 №№ __ __ 2 win-Egg” .; г №? 4'2 "7‘35?!“ мг:…“де w‘nnx из на - "’ * 2‘ *$?» " &`” \"N'i‘ *афзіёш ;‹ ‘23"??? 3&2???” I №№- HOWE-11M: : : _ГЁііпСшъсЁвЫогпЦ'типйММ №Ё$Ё°  : ‚ЕФ-$} у        ‚3% 222.22% ‚к; . I _ _ _ ; „.: „:,..‚32 и. "ЁЁ?“ 5.5.322 } 2 ***“ 2&1" WWW , ”' “"` L“ ‘ ," .....2.~2x.-.2......”-1M’M25‘Mfifil:g:ml“ms 2 .=. с. ...Мы—__. ;&№?дд *_*" ...:::‚3 №№Й$а5№°54к Kr W : „:"—‘ ` ‚. `; . ’, к f‘gdfihfiw 2‘ H ““а r};- ‚ 4}.     : с 2'2 д\д} №№; ? $ ` * 2 ‚ ““***“-‘ *’” 2 Г'— m 2.? ‹ ;и № „асс—бое “7214-5-22. 4-2“; !‘__ „ЕЁ 315$? 2271: 2 лгать., wi‘xiw ‚.  Рис. ЖЗ. Диалоговое окно Compiler Puc. Жб. Диалоговое окно свойств options (Параметры компилятора) компилятора С/С++    Следует заметить, что компилятор Dev-C++ поддерживает более ранние стандарты ANSI, чем ANSI C-99 (т.е. ISO/IEC 9899:1999). Это означает, что новейший стандарт неизвестен данной версии Dev-C++, и некоторые его элементы не поддерживаются, например, одно— строчные комментарии, начинающиеся с двух косых черт. Наличие таких комментариев в программе при установленном флажке Support all ANSI standard C programs вызовет ошибку компиляции, поэтому для использования однострочных комментариев в программе данный флажок следует отключить. О других поддерживаемых или не поддерживаемых воз— можностях можно узнать из справочной системы Dev-C++.  786 Приложение Ж 
Ввод и сохранение программы  Чтобы скомпилировать листинги программ из нашей книги, создавать проект Dev-C++ не придется —- можно работать с каждым листингом как с Одним файлом исходного кода. В листинге Ж.8 представлена небольшая программа, которую можно ввести и скомпилиро— вать в качестве упражнения.  Листинг Ж.8. sample. c -— простейшая программа   1: // $ашр1е.с 2: #include <stdlib.h> 3: 4: int main(void ) 5: { 6: printf("Dev-C++ Sample Program at work!"); 7: 8: system("PAUSE"); 9: return О; 10: }    Не вводите номера строк и двоеточия после них. Они приведены здесь только для удобства и не являются частью исходного текста программы.  ?      :“… Dev (:› . 1 , №535  по а: m m _'""-"" m. canons Tools № new .3“ ;&” ~ , ‚. _ ' ..“ énfn‘é 1%W%g\,p§y XE? ‚№3: {рф ,) (ч;-№№)" \\‘o:.}\:3  cigar. mm TT'j:“ '" is?“ 31kg,” ъ: in {Q van??? в g …… " “№№-“"“ Лионъ Ё  1.3“? ЁЁ?» if №№ 53% д……Ё" i3? мы:“ …… №…:…_….… „бытию…  include мнет-эш 22> ac-u.le смени: г.}     $пвиі % Nkaaif .». ”A:     in! Mint) (  эувсеш("?дзвнръ:   .4 1 ’2 " ` * "…‚пі ‘ Q Q \ з I: у \ ‘\ ‘ „‹ ч ,» 1 „№№… ‚ … : … —. ‚ . …, . … , «\ г .. M ”…“» _):ч—х. 3“ ж\д… _ “№ %} LEA-Eff}; (Э.-г\“: ....ь... »" „) ‘ ’Х " *‘ \\ xxx}? ц“ …!” v{ “m. , \‚_ “: ‹` „_ . :1      «$$$? заказ; . [_  ‚ ‚;…/3 :  … … "? $5: *г^ : . „  \ ) :. : ; . „&` „ $5 я… , ‹ “\    ‹ \ _ " › I _, _ \ „$$: " идёт, : \ >.! ” ^ . … ’ ›^ I    v ‹ -.‹ \ … . о ' !а\_‹,.7 „&& уЁЧХ . ^“ „ .4 , [f     Puc. Ж 7. Новый файл исходного кода  Для ввода и компиляции программы sample . с выполните следующие действия:  1. Создайте новый файл исходного кода.  Для этого в главном окне Dev-C++ выберите пункт меню РііеФМеш Source File (ФайлФ Новый исх0дный файл). В результате будет автоматически создан и открыт в окне новый  Среда разработки Dev-C++ 7871i 
файл исходного кода. В него будет помещено несколько операторов, образуя костяк про- граммы. Новый файл в окне ИСР показан на рис. Ж.7.  Введите исходный текст программы.  Замените код в диалоговом окне кодом из листинга Ж.8, приведенного выше, как показа- но на рис. Ж.8. Точно таким же образом можно ввести любую из программ этой книги. Отметим, что в строке 6 листинга Ж.8 на экран выводится простое сообщение, а в стро— ке 8 выполнение программы приостанавливается до нажатия пользователем любой кла— виши. Без оператора в строке 8 результат работы программы нельзя было бы прочитать, потому что текстовое окно DOS с выведенным сообщением закрылось бы слишком быст- ро. Позже об этом будет сказано подробнее.  Сохраните файл исходного кода.  После ввода текста программы ее следует сохранить. В среде Dev-C++ файлу автомати- чески присваивается имя Untitled-c номером. Для сохранения программы под более информативным именем выберите пункт меню File=>Save Unit As (ФаЙЛФСохранить модуль как). Откроется стандартное диалоговое окно для сохранения файлов, показанное на рис. Ж.9.    gram-c” 4 По Щ Знать mg? тэн—'      ‚Ъ. \  ОтвщТЗоЬпМЫНфд-  ЁЁ! v; 7 ":5 . :; ___ hm saw №№" х_{нЪЕх 'ЁЁЁ „_‘ЁЁЁЭЁ ЕЁ 13.3%.  ‘  ЁЁ ' _ ` '”1‘"‘_I’~§[$>;{gg.-'L3 „ " —_—=== 2a 2222522 сё %%” …. 22.2m- ___—_в_- a. :.23.3x „а... ий. 3.3.3.5144-а_-. $$$-{і, „Ёы__№ >-".(`  «ЭС \Documont .д.… "ОШ'ЮБШШЩМУ 090 umen!s\kadrm\deh‘% ипріес _.-___ : ’% -I/ $щ1;.с  Три-553351136: «шнг: =>  @@ ям mint toil ) “VS (        1  ...—___. |    „"',  Ь ; — ргіпсіг'Зеч—Сн вшэ—:»: i=2 cgztu'é в.:. curb"):    & : system ! "?ЫЪ'ЗЕ") .'      ` "“ дцп, ’› ‘ 32.3 в“??? t Ё …“ '“ …ЗЙЪЖ'”! ? №ЫЁКЁЁ &МЁМ'ЁЁ‘ rid; ` ; ..1. 3&3: 5" % ‘Ё‘ё- „.'-&`: № ` I??? #3555"? Kfiwfimm къ№$ …! “_“ 2; ’{flfi‘it‘u ’*4 &* ‘ ` ` “ ею….ас;т‚':..:::,' - ' -      ЩЖ 3922 ”давит „53:2 *.:ЁЁЁЁА.‘ ‚  Рис. Ж. 8. Ввод исходного текста программы  В этом диалоговом окне файлу можно дать новое имя, а также выбрать или изменить ка— талог, в котором файл должен храниться. Прежде чем сохранять файл, параметр Save source as (Сохранить файл как) следует установить равным С source file (Файл исходного кода С). Благодаря этому файл получит расширение . с, а не . срр, принятое по умолчанию в данной среде разработки. Присвойте файлу имя Sample. Поскольку тип сохраняемого файла уже выбран, расширение . с будет добавлено автоматически. После сохранения файла под новым именем строка заголовка в окне исходного к0да отразит произошедшее изменение имени, так что впоследствии файл можно будет сохранять щелчком на кнопке панели инструментов.  788 Приложение Ж 
 é‘fN-w H1 mmMM“a~ MoWTodsWHeb  каза“ №№ “ @ ён a” ‚‚ __ . Ema; 3%9 дай ›… Ё 2- -- .  [@С \{)осцтептз аг__ло ЗьптччггаЩМу Вогптетчиісгюп а\поПаатріе _с _ v <; _ .. Iri// Swim:  4  W” #‘nc‘ ude (333.91 ш.     ___…Ч  1M. шим void 1 ptind("2e\'-C*~ Sample f  j? sweet-("newt") : ”ЁЁ return 0:  ‘: 3W ‹*“)№331№“Ё3Ё5ЁН’% " ‘ № &’“ ЕЁ %; r W ^ ‚ ` rm; ‘ › 9% ^ » „; “:; ,?“ ЁЁ“ fifth 38$?“ . дзы…- „_.: и „ r « „ … Миа—ш №№ №№ Ё№№№ ;… Щ“ “r …%“? 4?»?me №52“ **?ё‘ёт " ** диакона ” №№; „СШЁшшіс-й" "' , “:.:..."           ттт—г; - ” “*'—' :" 14%?! ‚‹ ц,. ’ ' ЪЁ’ЪЫЪ , „, \““, … “','чд'ъ; " 3:4 I   u К— Ф “3%  Рис. Ж9. Диалоговое окно Save Unit AS   (“Fm КОМЗНДЫ меню И КНОПКИ панели инструментов взаимозаменяемы. ОНИ ВЫ- ПОЛНЯЮТ ОДНИ И те же операции.     Компиляция программы  После ввода и сохранения программы наступает время для ее компиляции. Чтобы сде- лать это в среде Dev-C++. выберите пункт меню ЕхесціеФСотрііе (ВьпполнениеФСком- пилировать) или щелкните на кнопке с “птичкой". или нажмите клавиши <Ctrl+F9>. При компиляции программы на экране появится диалоговое окно с сообщением об отсутствии ошибок (рис. Ж. 10). Как видно из рис. Ж.10_ в поле Total errors (Bcero ошибок) находится число 0. т.е. в про- грамме нет ошибок. Если это поле не равно нулю. значит. ошибки есть. Ошибки перечисля- ются внизу окна в специальном списке. Если при компиляции введенного листинга были вы- даны сообщения об ошибках. проверьте правильность ввода, а также убедитесь. что не введе- ны номера строк и двоеточия. Щелчок на кнопке Continue (Продолжить) закрывает окно компиляции и возвращает управление в окно редактирования кода.  Запуск программы на выполнение  Диалоговое окно. показанное на рис. Ж.10. можно использовать и для запуска программы. Для этого щелкните на кнопке Execute (Выполнить). Если программа написана для DOS (a именно такова программа из листинга Ж.8). то откроется текстовое окно DOS. H програм- ма будет запущена на выполнение. После ее завершения окно закроется автоматически. На рис. Ж.! 1 представлена программа sample . с. выполняемая в окне DOS после активизации  Среда разработки деи-С++ 789 
кнопки Execute. Если диалоговое окно, показанное на Ж.10, уже закрыто, программу можно запустить нажатием клавиши <F9> или выбором пункта меню Executecb Run.                           . .. . . - \ _ - I." N- "" 4 ^ 1 ‚ ` }. .- Ed Search View «№№:—_ же 09001: Tool-s №“ Hub „мвд-%; т;," *; „дэн 511113..” ^ L: ‚ .1 \ H . w („__—н… ) ' №.. ёё: 3; {31° м::—… к \ Qt: #(}: w 039$? $43: ": >”; ‚$ “31%“, i `;" @:“ “&&; 1.2.)? \, т у no :1}. *$ 1W _ \ № . “r , r1 :4”- « ‘ 1 `.' *' тг ‘ l а_гій Ёж) ".` ? _ М} У…. … ‚ч.,: ‹5 «.! 1. ‚55 . 1. a) , {32,1 1 ‚&‘/а:; 5‘ 31… 39:933.? ”,…-ежа. “(”&/м "`/‘*`… 1 :1}: * ‹” : ‘ ~ Nay: „ЁЁ щи .1} ”duo ~ №№» .1 … ` " v ,} д “ ‘1 ‹„ ‚',‘! ?›`“` ? h : ЁЪЪУГЁЁЪ' ‘» ; ‘ ”76:” \"д, ;& ‹ _ ‘ \. .. .— … .. ___ ‚.... " , ‚. ч ‚„ › . >” П "— х " ' " , км,… . … . ”ЁЁ // Зашла. с 291 . , ‚ 1%: на …за смарт,» ;} ;: fix :5 ’ Чад» 1111: Mint void ) {331}, %%р - '1 3 … ` ”’ . ’ . (4‘94 _ { i 2?: .- _ . 1 № print: ("Этими-о Закари: 1 fig! ~ вау: .;‘ : $7233“ нд l +9?- ' › - „“` . ЗУЗБШ("ЁА_ ЗЁ' ) .’ “&‘ к, »— k‘" return С: \ Ё ‘2‘ д` ‚эмо : М ) m “a“ ” , , “ 111‘ 1*- = 1 с- ) › › .. ‘ 11R: .. М&— д … ‘ I ‚$$$ Ev ‚ 1. ь. 0v. * „3%: 9433193 » ;& , дикий? аъ; …. _ .. ...... _) V E) f- "‘ V . «д‘ 1 , 1‘ W- ‚№ ед;; '39 @мёмз'чгспыё, №4 " ‘ : 7' \“ '1 ’б- вт r ". ' . .. ., 1 \\ xx. ‹ очч _, «3 A” 342'1” ‘ , ›, `, ‚» › , „ ‘v ^ . > ; ”д… "? ..… ‹ ”мы &&&, б 1‘ " 1. ’Мы“: "K " ‘. a ‚\ ‚*$ ;&)“; » “: №3— ”7’73?- «>? 1- » .22, ‘ ім‘ьт’ВМы ю . }›\\‹.<’‚ё,м“‹>< _5 ‚„; I)}£m“‘.:: ›:ЁЁЁЁЬ WW“). V“. xérvadpcflf к, , . , \{? н , .>:\" ‘Q‘F‘M “к " «`*` 1310‘ > ‘r w l ж ? ‚...:—. ’ „?(… “\5' ^ дий А4 V' ( ’ "" “: “V (171%? "2 V а 11:11.11“? \ WK... ° : ‹ * Ё {Ё @ - {"/№№ ; ‚‚ . “> 4 4 „ \ ~ ст у " „,» из ” W 1' "" … >}? их}… ‚ :“ ‚' ‚>,/\,“ ,с», ‹ \“ ` i } ‘.-,‘ ‘. ‘ v к “v 2 y “2,“ 7)‹ ($ "`: \ ‘ °^ ЁЁУ‘Ё . _ 1' ‘ ‘ ‚ джэм „ \“ ‘ . i … _ ““:, «'”фщтіжду ‚3; ч;; „$:, ‘ . .Mfixgz/‘gr‘LSJi. \ №…! *‘ ч . ":; ~4- ” 1 ^ „ " „ ~ ' ` $:“ "‘ №, ? 11M: ‚хг “‹ “<; : 1, {\= ` у” > Mr" › , …“ 35V ›` ‚ Ъ$`ч“^’ё Я)"; ; “:… 131$; \}9. : V c “M “`,’"; \ЁиЁ ’ < „> “"2. 51 %” ЁЁ“ "” E, ‚" › и,“ 3., ` \ > v? А WV 13;“ in: А “‘;“ ; f,” № « 4 "” <- , №»МЧ "? » ** ‚ці ' :т *“ “Kw вм” ‘ * а5„-„‚ .. “> “""M >——“ As. „ * ’ Ф! W {Ir ‹ ›‹ > M"? :“ 1- , … 1 : 1 - L " » \‘t \: ‹ Ё… rv‘t“ ' ”» .1“ „** ' 1M *+1’+><~ * é. ‘ъ ~12“ 1 … … ‹ 111. . «' щ ЁЩвіа ВМЫЁ WWI. , *за № …“… -. 111*“ …… ‹ ‚ Тимм „№№ …: _ ^ F - ‹. ›’ —-^ v ч ~ 1"?” 1- ч_ „ \. … 1‘14 › №2”? » , №54 \»“Ъ "№ “5.“ 35:3“1’4 952311 › "ж2“`< gm {° 1? M “`;“ $1"? © \… ` „„„} ГЬЁМЯЁ; w a}; NEW?” " . і\ I ‚| : цв I ___—14 С.Шоатя and 5011-1993:an …… № 1 `. . „: ; & …… _ ”`”—ЁЁ”°°` - -- .. ‚ …… …… ‚ „…… … „…… «Mm... “**—“;&?" .. , ”к {1 1-1.1....— , „ \ , 1 ^ ‚ ‚ А ., ' Akwé‘g‘ > v? › V'A“ " ‚(Л… _ v гп,…” " „*‘-"'г” ‚“у/‘;,эё" {"` * № ){ vat-gt "‘ „`,—° “I; ,”    Рис. Ж 10. Компиляция программы    ls … er.) Dov-pH 4    ;Ёёчцдь ”W \… >- VIM a  _ ,! . ample I‘roqrun at uork'i’l'c: цпч )-1 y [u стяги-пь-   u: ‹ ?> 21th  xx «1              V ’ „Уд.;` ‹ {,к—›," w. 7.9.4; \ , J ‚‚ .` It ’ м)»? к II I А [J IF _. СШ№1Ы5№|№У- - „. ‚--.,...сшпівицшМ - L u- __ \ … и , «- А : . . > , : ‹ „ › „‘, ': ’,‚3’33` „<. ' "9, * . , “,;   Рис. Ж 1 1. Выполнение программы в среде Dev-C+ +  Благодаря наличию строки 8 с командой system("PAUSE") программа приостанавли- вается, и пользователь сам завершит ее, когда захочет, нажав любую клавишу. В большинстве примеров программ, приведенных в этой книге, операция приостановки отсутствует. По этой причине можно не заметить, что именно программа вывела на экран из—за слишком высокого  790 Приложение Ж 
ее быстродействия — окно DOS открывается, в нем отображаются сообщения, и окно мгно- венно закрываегся. Чтобы обойти это затруднение, можно открыть окно DOS самостоятель- но, выбрав пункт АссеззогіезФСоттапо Prompt (СтандартныеФКомандная строка) системного меню Start (Пуск). Открыв консольное окно DOS, необходимо перейти в каталог с программой и ввести ее имя (можно без расширения .ехе). Программа будет запущена на] выполнение, и на экране появится результат, показанный на рис. Ж. 12. !   … \ _ ‹ ю№№г№ё №№ №№, *‘ в“ “г“? *‘a‘fiw’w ,; . ч "‘-и \. .— >  C\DOCUME 1\Вгаб\МУ00си—1\п’огКагеа\ое1>5атр1е теч- с++ Samp1e Program at work!Press any key to continue .   .А А 1 A  Puc. Ж 12. Выполнение программы в консольном окне DOS  Резюме  В этом приложении содержится вполне достаточно информации для того, чтобы устано- вить компилятор Dev-C++ и начать работу с ним. Данная среда разработки обладает еще це- лым рядом возможностей, не рассмотренных здесь. О них можно узнать из справочной доку- ментации Dev-C++.   @… Обратите внимание, что в числе справочных файлов системы Dev-C++ имеется список ответов на часто задаваемые вопросы (FAQ). B нем мож- но найти решение многих распространенных пробпем, возникающих при работе с этой средой разработки.     Среда разработки Dev-C++ 7914 
Предметный указатель  А  ANSI институт, 29; 307; 706 стандарт, 29; 33; 307; 706 ASCII кодовая таблица, 697 набор символов, 210 расширенный, 212 стандарт, 32  В  B, язык программирования, 30 Bell, лаборатории, 29  С  С#, язык программирования, 31 ; 579; 682 CLR, среда выполнения, 687  D  DJGPP, компилятор, 27; 32 DOS выв0д на принтер, 340 имя файла, 400 конец строки, 400 ограничение памяти, 182 открытые файлы, 429 перенаправление, 338; 340 потоки ввода-вывода, 312 распределение памяти, 514 редактор, 32 совместимость функций, 465 стандартный каталог INCLUDE, 545  H HTML, 680; 692  Java, 31; 575; 628 аплет, 628; 678 аппаратная независимость, 576 библиотека классов, 577 виртуальная машина, 5 76  792  пакеты, 5 77 приложение, 628 пространство имен, 5 7 7  L  Linux  компиляция С, 41 объектный файл, 34 перенаправление, 45 редактор, 32  О  OS/2  виртуальная память, 514 распределение памяти, 514 редактор, 32 совместимость функций, 465  U  UNIX  имя файла, 400 компиляция, 33 компиляция С, 41 конец строки, 400 объектный файл, 34 перенаправление, 45; 338; 340 распределение памяти, 514 редактор, 32 совместимость функций, 465 создание С, 29 сравнение строк, 447  W  Windows  вывод на принтер, 340 имя файла, 400 компиляция С, 41 конец строки, 400 ограничение памяти, 182 перенаправление, 338 потоки ввода—вывода, 312 распределение памяти, 514 редактор, 32 совместимость функций, 465  Предметный указатель 
А  Адрес, 58; 188 адресная арифметика, 196 Аплег, 577; 628; 676; 678 Аргумент, 46; 104; 109 командной строки, 549 передача по адресу, 469 передача по значению, 468 по умолчанию, 588  Б  Байт, 58 Байт-код, 5 76 Библиотека, 5 74 AWT, 667 динамической компоновки, 574 Бит, 524 Блок, 51; 54; 74; 640 Буфер памяти, 522 потока, 414 Буферизация ввода., 314  В  Ввод, 310 блочный, 41 1 лишние символы, 323 с клавиатуры, 314 символов, 314 символьный, 409 строк, 319 форматированный, 321; 407 Время, 487 жизни переменной, 264 Вывод, 31 1 блочный, 404; 41 1 в поток stderr, 340 на принтер, 340 на экран, 329 символьный, 404; 410 форматированный, 404; 405 Выравнивание по словам, 722 Выражение, 74 вложенное, 82 логическое, 88 простое, 74 сложное, 75  Предметный указатель  Графика, 667  Деструктор, 612 подкласса, 622 Директива #define,67;540 #elif, 545 #else, 545 #endif, 545 #if, 545 #include, 48; 545 #undef, 548 препроцессора, 540; 724 Диспетчер компоновки, 672 Дублирование ввода, 314  И  Идентификатор, 632 Индекс, 125; 171 Инкапсуляция, 569; 614 Инкремент, 77 Исключение, 662 Исключительная ситуация, 662 Исполняемая программа., 34 Исполняемый файл, 34; 35 ИСР (интегрированная среда разработки), 37 Исходный код, 32 Исходный текст, 31  Килобайт, 58 Класс, 603; 645 Applet, 677 BufferedReader, 638; 664 Canvas, 671 FileReader, 664 FileWriter, 665 Frame, 667 InputStreamReader, 638 Shape, 670 String, 635 System.out, 637 базовый, 61 7 вложенный, 615 для рисования фигур, 669 дочерний, 657  793 
конструктор, 653 памяти, 274 родительский, 65 7 Ключевое слово, 59; 707 abstract, 646 auto, 272 break, 287 case, 302 catch, 662 class, 603 const, 67 continue, 289 default, 302; 642 do,138;141 else, 86 extends, 646 extern, 268; 537 final, 646; 657 for, 126; 130 goto, 291 if, 84 inline, 122; 591 package, 646 private, 604; 635; 646; 649 protected, 605; 619; 635 ; 646 public, 604; 635; 646; 649 register, 273 signed, 61 static, 270; 2 72 struct, 235 super, 669 switch, 302 try, 662 typedef, 63; 260 union, 25 7 while, 133; 135 языка С, 701 языка С#, 684 языка С++, 584; 702 языка Java, 630 Комментарий, 50; 54; 630 вложенный, 50 однострочный, 50 Компилятор, 33 Borland C, 33 C#, 688 Dev-C++, 27; 32; 34; 535; 782 DJGPP, 27; 32 GNU C/C++, 33; 41 Microsoft C, 33 Turbo C, 33 Visual C++, 41 Компиляция, 33; 35  794  по требованию, 688 предварительная, 544 условная, 545; 725 Компоновка, 34; 35 Компоновщик, 34 Конкатенация, 442; 543; 636 Константа, 65 _STDC__, 709 EOF, 421 вещественная, 65 восьмеричная, 66 Десятичная, 66 диапазона, 712 именованная, 541 литеральная, 65; 75 символическая, 66; 75 стандартная, 724 целочисленная, 66 шестнадцатеричная, 66 Конструктор, 612; 653 перегрузка, 655 подкласса, 622 Куча, 519  Л  Литерал, 65; 73; 75; 214 Логическое значение FALSE (ЛОЖЬ), 82; 92 TRUE (ИСТИНА), 82; 92  М  Макрос __DATE_, 549 _FILE__, 549 _LINE__, 549 _TIME_, 549 assert(), 492 difftime(), 489 tolower ( ) , 464 toupper(), 464 va__arg ( ) , 476 va__end ( ) , 476 va_start(), 4 76 подставляемый, 540 функциональный, 541 Макрофункция, 541 Массив, 125; 170; 639 имя, 175; 193; 200; 215 инициализация, 178 многомерный, 175; 355; 356  Предметный указа тель 
объявление, 175 одномерный, 171 передача в функцию, 201; 203 поиск,496 связь с указателями, 193; 355 символов, 214 сортировка,496 строк, 363 структур, 241 указателей, 363 Машинный язык, 33; 576; 688 Мегабайт, 58 Меню, 292 Метка для перехода, 289; 291 структуры, 233; 235; 260 Метод, 123; 599; 630; 648 action, 673 paint, 677 println, 637 readLine, 664 System.Console.Write(), 689 System.Console.WriteLine(), 689 перегрузка, 652 Модификатор abstract, 646 final, 646 l и II, 333 доступа, 634 точности, 323 Модуль, 534 вторичный, 534 главный, 534 подчиненный, 534  H Наследование, 5 70; 616; 657  0  Область вилимости, 264 действия, 264; 634; 646 внешней переменной, 267 параметра функции, 272 доступности, 264 Объединение, 254; 257 инициализация, 255 обращение, 255 определение, 255; 25 7 Объект, 603  Предметный указа тель  сіп, 591 cout, 583 создание, 612 уничтожение, 612 Объектный код, 33; 35 { Объектный файл, 33; 35 ‘ ОЗУ (оперативное запоминающее устройство), 57; 187; 513 ООП (объектно-ориентированное программирование), 565 Операнд, 76 Оператор, 49; 72 break, 285; 287; 297 continue, 287; 289 defined, 547 do...while, 138; 141; 641 for, 126; 130; 643 вложенный, 131 goto, 289; 291 if, 84; 87; 641 вложенный, 87; 88 import, 629 return, 48; 49; 114; 649 sizeof, 62 switch, 295; 302; 642 throw, 587 typedef, 63 while, 133; 135; 641 вложенный, 136 безусловного перехода, 289 присваивания, 76 составной, 94 пустой, 74 составной, 74; 640 управляющий, 83; 640 Операция, 76; 96; 640 sizeof, 62; 78; 182 арифметическая, 76 битовая, 524 взятия адреса, 155; 189 выбора по условию, 95 вычисления смещения, 199 вычитания, 79 двуместная, 78 ` декремента, 77 деления, 79 деления по модулю, 79 дополнения, 527 запятая, 95; 130 знак, 76; 78 инкремента, 77 конкатенации, 543 логическая, 91  795 
обращения к структуре по указателю, 250 обращения к элементу структуры, 233 косвенного, 250 одноместная, 76 постфиксная форма, 77 префиксная форма, 77 отношения, 82 поразрядная логическая, 526 поразрядного сдвига, 524 преобразования в литерал, 542 приведения типа, 472; 511 приоритет, 80; 90; 92; 96 присваивания, 76 составная, 94 сложения, 79 ссылки по адресу, 190 ссылки по указателю, 190; 354 двойная, многократная, вложенная, 355 сцепления строк, 636 трехместная, 95 умножения, 79 Отлацчнк, 307 Ошибка компиляции, 38 компоновки, 40 обработка, 492 предупреждение, 41 сообщение, 39  П  Пакет, 629; 646 Память, 187 виртуальная, 514 временная, 57 динамическое распределение, 215; 513 класс, 274 оперативная, 5 7; 513 статическое распределение, 513 утечка, 385 энергозависимая, 57 Параметр, 110 по умолчанию, 588 Переменная, 48; 58 егтпо, 494 автоматическая, 269 внешняя, 267; 53 7 время жизни, 264 диапазон, 712 имя, 58 запись в смешанном регистре, 59 инициализация, 64; 271 косвенное обращение, 190  796  локальная, 104; 112; 269 в блоке, 275 в функции таіп(), 273 область действия, 264 объявление, 63 прямое обращение, 190 регистровая, 273 с плавающей точкой, 60 статическая, 270 целочисленная, 60 числовые типы, 60; 63 Перенаправление, 33 7; 583 ввода, 339 вывода, 45; 338 Переносимость кода, 706 символов, 708 чисел, 710 Подкласс, 617 деструктор, 622 конструктор, 622 Поле битовое, 528 ввода, 155; 322 структуры, 232 Полиморфизм, 566; 614 Поток, 311 сіп, 591 cout, 583 stdcrr, 340 stdin, 312 stdout, 3 I 2 stdpm, 340 ввода, 311 вывода, 311 двоичный, 3 I 2; 400 связь с файлом, 399 стандартный, 312 текстовый, 400 Предупреждение, 41 Преобразование регистра, 721 типа, 509 Препроцессор, 540 Приложение, 628; 676; 686 ASP.NET, 691 консольное, 63 7; 644; 686 с оконным интерфейсом, 667 Приоритет операций, 80; 90; 92; 96 Программа Hello, World, 36; 5 77; 582; 689 Программирование многомодульное, 533 объектно-ориентированное, 30; 565  Предметный указатель 
сверху вниз, 108 структурное, 106 Промежуточный язык, 687 Путь к файлу, 400  Р  Расширение .с, 33; 35 .срр, 68 7 .cs, 687 .cxc, 35 .о, 34; 35 .obj, 34; 35 java, 629 Регистр процессора, 273 чувствительность, 59; 707 Рекурсия, 1 1 9  С  Сборка мусора, 684 Свободное пространство, 73; 97; 155 Свойство, 647 Связанный список, 249; 379 добавление элемента, 381 ; 383 звено, элемент, узел, 380 начальный (старший) указатель, 380 однонаправленный, 380 реализация, 388 удаление элемента, 384; 395 Символ, 210 возвращение в поток ввода, 319 конца строки, 146 нулевой, 214 специальный, 146 из трех символов, 159 формата, 333 Система счисления двоичная, 704 десятичная, 703 шестнадцатеричная, 704 Слово ключевое, 59 машинное, 722 Собьггие ввода, 673 Сортировка, 366; 376 Спецификация ввода, 155; 158; 322 вывода, 146; 150; 333; 691 ширины поля, 226; 334  Предметный указатель  Среда разработки Dev-CH, 782 графическая, 34 интегрированная, 37 Стандарт ANSI, 29; 33 ASCII, 32 Стек, 468 Стиль программирования плохой, 39; 73; 291 хороший, 73; 74; 170; 268; 277; 400; 404; 429; 632; 681 Строка, 73; 210; 214; 635 в текстовом файле, 400 копирование, 438 литеральная, 73; 214 поиск, 449 преобразование в число, 457 символьная, 437 сравнение, 445 без учета регистра, 449 формата, 146; 152; 226; 332 Структура, 232; 235 FILE, 401 tm, 486 вложенная, 239 инициализация, 245 массив структур, 241 сложная, 236 содержащая массив, 239 указатели-члены, 247 указатель на, 249 Суперкласс, 65 7  Тип bool, 585 char, 60; 210 clock_t, 486 int, 60 long, 60 longlong,60 short, 60 sizc_t, 437 timc_t, 486 unsigned, 60 va_list, 476 автоматическое преобразование, 509 возвращаемого функцией значения, 109 неявное преобразование, 509 приведение, 369; 472; 509; 511 расширение, 510  797 
струкгурный, 233; 235 создание, 260 сужение, 511 числовой, 60; 63 элементарный, 632; 633; 651 явное преобразование, 511  У  Указатель, 188; 354 адресная арифметика, 196 вычисление смешения, 199 декрементирование, 197 инкрементирование, 196 использование с массивом структур, 250 как поле структуры, 247 массив указателей, 363 на структуру, 249 на указатель, 355 на функцию, 370 нетипизированный, 472 объявление, 189 операции, 199 позиции в файле, 415 сравнение, 199 типа void, 472 Управляющая последовательноеть, 146; 147 Устройство, 311 Утилита построения проекта, 539  Ф  Файл, 311 ermo.h, 494 ассемблерный, 688 включаемый, 48 временный, 427 двоичный, 400 заголовочный, 48 закрытие, 414 запись, 404; 665 имя, 400 копирование, 425 объектный, 538 открытие, 401 переименование, 424 произвольный доступ, 415 путь, 400 связей проекта, 5 39 связь с потоком, 399 текстовый, 400 удаление, 423  798  чтение, 404; 664  Функция, 30; 46; 49; 102  asctimco, 488 atof(), 459 atoi(), 458 atol(), 458 atoll(), 458 bscarch(), 497 calloc(), 516 clock(), 490 ctimc(), 488 cxit(), 304 fclosc(), 414 fcof(), 421 fflush(), 326; 414 fgctc(), 318; 409 fgcts(), 320; 409 flushall(), 414 fopcn(), 401 fprintf () , 332; 339; 405 fputc(), 330; 410 fputs(), 331; 410 frcad(), 411 frec(), 519 fscanf(), 407 fscck(), 418 ftcll(), 416 fwritc(), 411 gctc(), 318; 409 gctch(), 316 gctchar(), 315; 409 . gctcth, 318 gets(), 222; 225; 319 localtich, 487 таіп()‚ 48 malloc(), 216; 514 тетсру()‚ 522 mcmmoch; 522 mcmsctO, 522 mktimco, 488 pcrror(), 495 printf(), 49; 145; 152; 222; 332 putc(), 330; 410 putchar(), 329; 410 puts(), 153; 221; 331 qsort(), 498 геаНос(), 518 removc(), 423 rcnamc(), 424 гетто, 415 scanf(), 49; 154; 158; 226; 322 strcat(), 442 strchr(), 449  Предметный указатель 
strcmp(), 446 strcpy(), 439 strcspn(), 451 strdup(), 441 strf’timeo, 488 strienO, 437 strlwr(), 455 strncatO, 444 stmcmp(), 44 7 stmcpy(), 440 stmsctO, 456 strpbrkO, 453 strrchr(), 450 strrev(), 456 strsct(), 456 strspn(), 452 вито, 453 strupr(), 455 system(), 304 time(), 487 tmpnam(), 427 tolower(), 722 toupper(), 722 ungetc(), 319 анализа символов, 460; 719 библиотечная, 34; 46; 54 ввода символов, 314 ввода строк, 319 ввода-вывода, 312 возвращающая указатель, 478 встраиваемая, 121; 590 вывода на экран, 329 вывода символов, 329 вывода строк, 331 вызов, 49; 102; 117 гиперболическая, 484 доступа к членам класса, 608 заголовок, 104; 105; 109 изменения регистра, 455 имя, 109 логарифмическая, 484 математическая, 483 нестандартная, 46 определение, 48; 49; 104; 105 параметры по умолчанию, 588 перегрузка, 568; 587; 614; 652 прототип, 48: 104; 105; 116 работы с блоками памяти, 521 работы со временем, 487 распределения памяти, 216 рекурсивный вызов, 119  Предметный указа тель  косвенный, 119 с переменным числом аргументов, 475 стандартная, 34; 46; 54; 730 степенная (зкспоненциапьная), 484 тело, 1 12 точки входа и выхода, 116 тригонометричсская, 483 указатель на, 370 форматированного вывода, 332 член, 123; 599  Ц Цикл  do...whilc, 138; 141; 641 for, 126; 643 while, 133; 135; 641 бесконечный, 291 вложенный, 142 разработки программы, 31  Ч  Число двоичное, 704 десятичное, 703 шестнадцатеричное, 704 экспоненциальная запись, 65 Член структуры, 232 Чувствительность к регистру, 59; 707  З  Экземпляр, 603 структуры, 233 Элемент массива, 170; 194 структуры косвенное обращение, 250  Я  язык программирования В, 30 С#, 31; 579; 682 С++, 30; 574; 582 Java, 31; 575; 628 объектно-ориентированный, 565 переносимый, 30 процедурный, 564 
Научно-популярное издание Брэдли Джонс, Питер Эйткен  Освой самостоятельно С за 21 день, 6—е издание  Литературный редактор Т Т. Шматко Верстка М.А. Удалов  Художественный редактор Е.П. Дынник Корректоры Л.А. Гордиенко, ГА. Корзун, Л.В. Коровкина, О.В. Мищутина  Издательский дом “Вильямс”. 101509, Москва, ул. Лесная, д. 43, стр. 1.  Подписано в печать 08.02.2005. Формат 70Х1ОО/1 6. Гарнитура Times. Печать офсетная. Усл. печ. л. 64,5. Уч.—изд. л. 39,55. Доп. тираж 3000 экз. Заказ № 274.  Отпечатано с фотоформ в ФГУП “Печатный двор” Министерства РФ по делам печати, телерадиовещания и средств массовых коммуникаций. 1971 10, Санкт—Петербург. Чкаловский пр., 15.