Primer Plus Fifth Edition Stephen Prata sAмs 800 East 96th St., Indianapolis, Indiana, 46240 USA Язы1< программирования Лекции и упра)l<нения 5-е издание Стивен Прата Москва • Санкт- Петербург • Киев 2013 ББК 32.973.26-0 1 8.2.75 П70 УДК 68 1 .3.07 Издательский дом "Вильяме" Зав. редакцией С.Н тригу6 Пере вод с английс кого Ю.И. Карииеико, Н.А . Мухииа, В.Д. Цеки'Ча, С.А . Шестакова Под редакцией Ю.Н. Артемеико По об щим вопросам обращайтесь в Издательский дом "Вильяме" по адресу: info@williams pub lishing .com, http://www.williams pub lishing .с от Прата, Стивен. П70 Язык программирования С . Лекции и упражнения , 5-е издание . : Пер. с англ. М . : Издательский дом "Вильяме " , 20 1 3 . - 960 с . : ил. - Парал . тит. англ. ISBN 9 78-5-8459-0986·4 (рус . ) Книга известного специалиста и лектора в области компьютерных технологий по­ священа последнему стандарту (С99) одного из наиболее распространенных языков программирования - С, который послужил основой для создания операционной систе­ мы Unix. Книгу отличает простой и доступный стиль изложения , изобилие примеров и множество рекомендаций по написанию высококачественных программ. Подробно расс матриваются такие вопросы, как представление данных в языке С, операции и операторы, управляющие структуры и функции. Немалое внимание уделяется обработ­ ке строк, вводу-выводу, работе с массивами и структурами и вопросам управления памя­ тью . Исчерпывающие сведения о препроцессоре и стандартных библиотечных функ­ циях дадут возможность эффективно создавать программный код. Приводимые в конце каждой главы вопросы для самоконтроля и упражнения для самостоятельной прора· ботки позволят надежно закрепить полученные знания. Книга рассчитана на программистов разной квалификации, а также будет полезна для студентов и преподавателей дисциплин, с вязанных с программированием. ББК 32.973.26-018.2.75 Все названия проrраммных продуктов являются зарегистрированными торговыми марками соот­ ветствующих фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет rm:сьменного разрешения и:ща­ тельства Sams PuЫishing. Authorized translation from the English language edition published Ьу Sams Publishing, Copyright © 2005. А11 rights reserved. No part of this Ьооk may Ье reproduced or transmitted in any form or Ьу any means, electronic or mechanical, including photocopying, recording or Ьу any information storage retrieval system, without permission from the publisher. Russian language edition is puЫished Ьу WШiams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2013. ISBN 978-5-8459-0986-4 (рус .) ISBN 0-672-32696-5 (англ.) © И:щательский дом "Вильяме", 2013 © Sams PuЫishing, 2005 Оглавление глава 1. Предварительные сведения 23 глава 2. введение в язык с 49 глава 3. Представление данных в языке с 75 глава 4. символьные строки и форматированный ввод-вывод 121 глава 5. операции, выражения и операторы 163 глава 6. Управляющие операторы: циклы 205 глава 7. Управляющие операторы: ветвление и безусловные переходы 259 глава 8. символьный ввод-вывод и верификация ввода 311 глава 9. Функции 347 глава 1 о. массивы и указатели 393 глава 11. символьные строки и строковые функции 449 глава 12. классы памяти, компоновка и управление памятью 505 глава 13. Файловый ввод-вывод 555 глава 14. структуры и другие формы данных 593 глава 15. операции с разрядами 657 глава 16. Препроцессор и библиотека языка с 691 глава 17. Расширенное представление данных 743 Приложение А. ответы на вопросы для самоконтроля 823 Приложение Б. справочный раздел 861 Приложение в. Набор символов ASCll 946 Предметный указатель 950 Соде ржани е Предисловие Об авторе Пос вящение Благодарности От издательства глава 1. Предварительные сведения Как появился язык С? Поче му язык С? Конструктивные особенности Эффе ктивность Переносимость Мощь и гибкость Ориентация на программистов Недостатки Откуда пошел язык С? Как раб отают компьютеры Языки программирования выс окого уровня и компиляторы Использование языка С : с е мь этапов Этап 1 : определе ние целей программы Этап 2: проектирование программы Этап 3: написание кода Этап 4: компиляция Этап 5: запус к программы на выполнение Этап 6: тестирование и отладка программы Этап 7: с о провожде ние и модификация программы Комментарий Механика программирования Файлы объе ктного кода, ис полняе мые файлы и библиотеки Операционная система U nix Редактирование в системе U nix Компиляция в системе Unix Операционная система Linux Инте грированная среда разработки (Window s ) Компиляторы DOS для пе рсональных компьютеров IBM РС Раб ота с языком С в системах Mac intosh Языковые стандарты Первый стандарт ANSI/ISO С Стандарт С99 Как организована эта книга 21 22 22 22 22 23 23 24 25 25 25 26 26 26 27 28 29 30 31 31 32 32 33 33 34 34 35 36 37 37 38 39 39 41 42 42 42 43 44 Содерж ание Соглашения , принятые в этой книге Ш р ифты и начертание Выходные данные программы Спе циальные клавиши Систе мы, ис пользованные при подготовке данной книги Тре бования к системе Спе циальные элеме нты Резю ме В опросы для с амоконтроля Упражнения по программированию глава 2. введение в язык с Простой приме р программы на языке С Пояснение к программе Проход 1: краткий обзор Проход 2 : детали программы Структура простой программы С оветы кас ательно удобства чтения программы Еще один шаг в использовании языка С Докуме нтирование Множе стве нные объявления Умноже ние Рас печатка нескольких значений Множество функций Предварительные сведения об отладке Синтакс иче ские ошибки Семантические ошибки Состояние программы Клю чевые слова и зарезервированные идентификаторы Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию глава 3. Представление данных в языке с Демонстрационная программа Что нового в этой программе? Пере менные и константы Клю чевые слова , обозначаю щие типы Целочисленные данные и данные с плавающей запятой Целые числа Числа с плавающей запятой Б азовые типы данных языка С Тип данных int Другие целочисленные типы 7 44 44 45 45 46 46 46 47 47 48 49 49 50 50 52 62 63 64 64 64 64 65 65 67 67 68 70 71 71 72 73 74 75 75 77 78 79 80 80 81 82 82 86 8 Содержание Ис пользование с имволов : тип c har Тип _В ооl Переносимые типы : int types.h Данные типа float, douЬ le и long douЬ le Комплексные и мнимые типы З а пределами б азовых типов Разме ры типов Использование типов данных Аргументы и ошибки при их ис пользовании Еще один приме р: управляющие последовательности Каким будет ре зультат выполне ния этой программы Сброс буфера выходных данных Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию 92 99 99 101 106 106 108 11О 111 113 113 114 115 115 116 118 глава 4. символьные строки и форматированный ввод-вывод 12 1 В водная программа Строки символов: введе ние Мас с ив значений типа c har и нулевой символ Ис пользование строк Функция strle n ( ) Константы и пре процессор С Модификатор const Раб ота с символичес кими константами Исследование и ис пользование функций printf() и sc anf( ) Функция printf() Ис пользование функции printf() Модификаторы с пецификации преобразования для функции printf( ) Что пре образует спе цификация преобразования Ис пользование функции sc anf( ) Модификатор* и е го использование в функциях printf( ) и sc anf( ) Советы п о ис пользованию функции printf( ) Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию 121 123 123 124 125 127 131 131 133 134 135 137 1 43 1 50 155 157 157 1 58 1 59 161 глава s. операции, выражения и операторы В веде ние в циклы Фундаментальные операции Операция присваивания : = Операция сложения : + Операция вычитания : - 163 163 166 166 168 168 Содерж ан и е Операции знака: - и + Операция умножения : * Операция деле ния : / Приоритеты операций Приоритеты и порядок вычисле ния Некоторые дополнительные операции Операция sizeof и тип size_t Операция деле ния по модулю : % Операции инкре мента и декре мента : ++ и Де кремент: - Приоритеты операций Не будьте слишком самоувере нными В ыражения и операторы Выраже ния Операторы Составные опе раторы (блоки) Пре об разования типов О пе рация приведения Функции с аргументами Демонстрационная программа Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию -­ r лава 6. Управляющие операторы: циклы Продолжение изучения цикла while Комментарии по программе Цикл считывания в стиле С О пе ратор while Заве ршение цикла while Когда цикл завершается? Оператор w hile : цикл с предусловием Синтакс иче ские особенности Что больше : использование операций и выражений отношения Что такое истина? Какой еще может быть истина? Трудности при употребле нии понятия "истина" Новый тип _Bool Приоритеты опе раций отношения Не определе нные циклы и циклы со счетчиком Цикл for Ис пользование цикла for с целью повышения гибкости Дополнительные операции прис ваивания : +=, - = , * = , / =, % = О пе рация запятой Гре ческий философ Зенон и цикл for 9 168 169 1 71 1 72 1 74 1 76 1 76 1 77 1 78 182 183 184 185 185 186 189 191 193 195 197 198 199 200 203 205 206 207 208 209 210 210 211 211 213 215 216 217 219 220 222 223 225 230 230 233 10 Содержание Цикл с постусловие м : do while Какой цикл выбрать? Вложенные циклы Анализ программы Вариации вложенных циклов В веде ние в мас сивы Ис пользование цикла for при раб оте с массивами Приме р цикла, использую щего возвращаемое значение функции Анализ программы Ис пользование функций с возвращаемыми значениями Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию глава 7. Управляющие операторы: ветвление и безусловные переходы О пе ратор if Добавление конструкции else к опе ратору if Еще один пример : знакомство с функциями getchar ( ) и putcha r ( ) Семе йство с имвольных функций ctype.h Множе стве нный выбор else if Объединение e lse и if в пары Больше е число вложений операторов if Давайте буде м логичными Альтернативное представление : заголовочный файл iso646.h Приоритеты опе раций Порядок вычисления выражений Диапазон значений Программа подсчета слов Условная операция: ? : Дополнительные средства организации цикла: continue и b reak Оператор c ontinue Оператор b reak Множественный выбор: операторы switch и break Ис пользование оператора switch Считывание только пе рвого с имвола строки Множе стве нные метки Операторы switch и if else О пе ратор goto Избе гайте использования опе ратора goto Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию 23 5 238 239 239 240 241 242 244 247 248 249 249 250 254 259 260 262 264 266 268 271 2 73 277 2 78 2 79 2 79 281 282 285 288 288 291 293 295 296 297 299 300 300 304 304 305 308 Содерж ание 11 глава 8. символьный ввод-вывод и верификация ввода 311 Односимвольные функции ввода·вывода: getchar ( ) и putcha r ( ) Буферы Завершение ввода с клавиатуры Файлы , потоки и ввод данных с клавиатуры Конец файла Перенаправление и файлы Перенаправле ние в Unix, Linux и DOS С оздание дружестве нного пользовательского интерфейса Раб ота с буферизованным вводом Смешивание числового и символьного ввода Прове рка допустимости ввода Анализ программы Поток ввода и числа Просмотр меню Задачи Обеспечение устойчивого выполнения программ Функция get_choice () Смешивание символьного и числового ввода Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию 312 313 315 315 316 320 320 325 325 327 330 335 336 336 337 337 338 339 3 42 3 43 3 43 3 44 глава 9. Функции О бзор функций Создание и использование простой функции Анализ программы Аргументы функции Определе ние функции с аргументами: формальные параметры Создание прототипа функции с аргументами Вызов функции с аргументом: фактичес кие аргументы Представление функции в виде черного ящика Возврат значе ния функцией с помощью опе ратора re turn Типы функций Прототипирование функций в стандарте ANSI С Решение проблемы Решение стандарта ANSI Отсутствие аргументов и не определенные аргументы Да здравствуют прототипы Рекурсия Рекурс ия в де йствии Ос новы рекурс ии Хвостовая ре курсия 347 347 3 49 3 49 3 52 354 355 355 3 56 357 360 361 361 363 364 365 365 366 367 369 12 Содержание Рекурс ия и обратный порядок Пре имущества и недостатки рекурсии Компиляция программ из двух или большего числа исходных файлов Unix Linux Компиляторы командной строки DOS Компиляторы Windows и Macintosh Ис пользование заголовочных файлов Поис к адресов: опе рация & Изменение переменных в вызывающей функции Указатели: первое знакомство Операция разыменования :* Объявление указателей Ис пользование указателей для обмена данными между функциями Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию глава 1 о. массивы и указатели Массивы Инициализация Выделе нные инициализаторы (стандарт С99) Прис ваивание значений массивам Границы массива Указание размера массива Многомерные масс ивы Инициализация двуме рного мас с ива Масс ивы с разме рностями больше двух Указатели и массивы Функции, мас сивы и указатели Ис пользование параметров типа указатель Комментарии: указатели и мас с ивы О пе рации с указателями Защита содержимого масс ива Ис пользование c onst с формальными параметрами Дополнительные сведе ния о клю чевом слове c onst Указатели и многоме рные масс ивы Указатели на многомерные масс ивы Совместимость указателей Функции и многоме рные масс ивы Массивы пере менной длины С оставные литералы Клю чевые понятия Резю ме 3 71 3 73 3 74 3 74 3 75 3 75 3 75 3 75 3 79 381 383 383 384 385 389 390 390 391 393 393 394 398 399 400 402 403 406 407 407 41 1 413 416 416 421 422 424 426 429 430 43 1 435 439 441 442 Содерж ание В опросы для с амоконтроля Упражнения по программированию 13 443 445 глава 1 1. символьные строки и строковые функции 449 В веде ние в строки и строковый ввод-вывод Определе ние строк в программе Указатели и строки В вод строк Выделе ние пространства памяти под строки Функция gets ( ) Функция fge ts ( ) Функция scanf( ) В ывод строк Функция puts ( ) Функция fputs ( ) Функция printf() В озможность создания с об стве нных функций Строковые функции Функция strle n ( ) Функция strcat ( ) Функция strncat ( ) Функция strcmp ( ) Возвращае мое значе ние функции strc m p ( ) Варианты функции strnc m p ( ) Функции strcpy ( ) и strncpy () Остальные с войства функции strcpy ( ) Тщательный выбор: функция strncpy () Функция sprintf( ) Другие строковые функции Приме р обработки строк: с о ртировка строк Сортировка указателей вме сто строк Выб ор алгоритма сортировки С имвольные функции c type .h и строки Аргументы командной строки Аргументы командной строки в интегрированных средах Аргументы командной строки в с реде Macintosh Пре об разование строк в числа Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию 449 451 459 460 460 460 463 464 465 466 467 468 468 4 71 471 4 73 474 4 75 476 479 480 482 482 484 485 488 489 489 491 493 495 495 496 499 499 500 503 глава 12. классы памяти, компоновка и управление памятью Кла с с ы памяти Область видимости sos 505 506 14 Содержание Связывание Продолжительность хране ния Автоматичес кие пе ременные Регистровые пе ременные Статические переме нные с областью видимости в пределах блока Статические переме нные с вне шним с вязыванием Статическая пе ременная с вне шним с вязыванием Множе стве нные файлы С пецификаторы классов памяти Кла с с ы памяти и функции Какой класс памяти следует выбрать? Функция ге не рации случайных чисел и статичес кая переменная Игра в кости Распределе ние памяти: функции malloc ( ) и free () Важность функции free ( ) Функция c a lloc ( ) Рас пределение динамической памяти и массивы пе ременной длины Клас сы памяти и динамическое распределе ние памяти Квалификаторы типов в стандарте ANSI С Квалификатор типа c onst Квалификатор типа volatile Квалификатор типа re stric t Новые места для старых клю чевых слов Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию глава 13. Файловый ввод-вывод Обмен данными с файлами Что такое файл? Текстовое и двоичное представление файлов Уровни ввода-вывода Стандартные файлы Стандартный ввод-вывод Проверка наличия аргументов командной строки Функция fopen ( ) Функции getc () и putc ( ) Признак конца файла Функция fc lose ( ) Указатели на стандартные файлы Простая программа с жатия файлов Функции ввода-вывода: fprintf( ) , fsc anf( ) , fgets ( ) и fputs ( ) Функции fprintf( ) и fscanf( ) Функции fge ts ( ) и fputs ( ) 508 508 510 514 514 516 52 1 52 1 522 525 526 526 53 0 534 53 8 53 9 53 9 541 541 542 544 545 547 54 7 548 549 551 sss 555 556 556 557 558 558 559 560 56 1 562 563 564 564 566 566 568 Содерж ание Комментарий: функции gets ( ) и fgets () Произвольный доступ: функции fse e k ( ) и ftell( ) Как работают функции fse e k ( ) и fte ll () Сравнение двоичного и текстового режимов Переносимость Функции fge tpos () и fse tpos ( ) За кулисами стандартного ввода-вывода Другие стандартные функции ввода-вывода Функция int ungetc (int с, FILE*fp ) Функция int fflus h ( ) Функция int setvb uf() Двоичный ввод-вывод: fread ( ) и fwrite ( ) Функция size_t fwrite ( ) Функция size_t fread ( ) Функции int fe of(FILE*fp ) и int fe rror (FILE*fp ) Пример ис пользования функций fread ( ) и fwrite ( ) Произвольный доступ с двоичным вводом-выводом Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию глава 14. структуры и другие формы данных Учебная задача : создание каталога книг О бъявле ние структуры О бъявле ние переменной типа структуры Инициализация структур Доступ к эле ментам структуры Выделе нные инициализаторы структур Массивы структур Объявление мас с ива структур Идентификация элементов массива структур Анализ программы Вложенные структуры Указатели на структуры Объявление и инициализация указателя на структуру Доступ к эле ментам структуры чере з указатели В заимоде йствие функций и структур Передача эле ментов структуры Ис пользование адре с а структуры Передача структуры в качестве аргуме нта Дальнейший анализ свойств структур Структуры или указатели н а структуры? Символьные массивы или указатели на с имволы в структурах Структура , указатели и функция m alloc ( ) 15 569 570 571 573 573 5 74 5 75 576 5 76 5 76 577 577 579 580 580 580 583 585 586 58 7 588 593 593 595 596 59 7 598 599 599 602 602 603 604 605 607 607 608 608 609 610 611 615 616 617 16 Содержание Составные литералы и структуры (С99) Элеме нты типа гибких мас с ивов (С99 ) Функции, ис пользую щие массив структур С охранение с одержимого структур в файле Пример сохране ния структуры Анализ программы Структуры : что дальше? О бъединения: краткое знакомство Пере числимые типы Константы типа e num Значения по умолчанию Прис ваивае мые значе ния Ис пользование клю чевого слова enum Совместно ис пользуемые пространства имен О пе ратор typedef: краткое знакомство Фиктивные объявления Функции и указатели Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию глава 1 s. операции с разрядами Двоичные числа , биты и байты Двоичная запись целочисленных значений Целочисленные значения со знаком Двоичное представле ние чисел с плавающей точкой Другие основания с истем счисления Восьмеричная система счисле ния Ш е стнадцатеричная с истема с числения Поразрядные операции Поразрядные логичес кие операции Область применения: маски Область применения: вклю чение разрядов Область применения: отклю чение разрядов Область применения: пере ключе ние разрядов Область применения: прове рка значения разряда Поразрядные операции сдвига Пример программы Еще один пример программы Разрядные поля Пример ис пользования разрядных полей Разрядные поля и поразрядные опе рации Клю чевые понятия Резю ме 619 621 623 625 626 628 629 630 633 634 634 634 635 636 63 7 639 641 648 649 650 653 657 657 658 659 660 660 661 661 662 663 664 666 666 666 667 667 669 671 6 73 6 74 6 78 685 685 Содерж ание 686 688 В опросы для с амоконтроля Упражнения по программированию глава 16. Препроцессор и библиотека языка с Первые шаги трансляции программы Именованные константы : # define Лексемы Переопределение констант Использование аргументов в дире ктиве # defi ne Создание строк из аргументов макроса: операция # Средство объедине ния препроцессора: операция ## Варьируе мые макро с ы : . . . и_VA ARGS Макрос или функция? В клю чение файлов : директива # include Пример заголовочного файла Область применения заголовочных файлов О стальные директивы Директива # undef Определе нность с точки зрения препроцесс ора Условная компиляция Предопределе нные макросы Директивы #J ine и # error Директива # pragm a В страивае мые функции Б иблиоте ка С Доступ к б иблиоте ке С Ис пользование опис аний библиотеки Б иблиоте ка математичес ких функций Б иблиоте ка утилит общего назначения Функции exit( ) и atexit( ) Функция q sort ( ) Б иблиоте ка assert Функции memcpy ( ) и memm ove ( ) из библиоте ки string.h Переменные аргументы: файл stdarg.h Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию _ 17 __ глава 17. Расширенное представление данных Исследование темы представления данных От мас с ива к связному списку Ис пользование с вязного спис ка Дополнительные соображения Абстрактные типы данных 69 1 691 692 696 697 697 700 701 702 703 704 705 707 708 709 709 710 715 716 716 71 7 720 720 721 722 725 725 727 732 734 736 738 738 739 740 743 744 747 750 754 755 18 Содержание Получение абстракции Построение интерфейса Ис пользование интерфейс а Реализация интерфейса С оздание очереди с помощью ADT Определе ние абстрактного типа данных очереди Определе ние инте рфейса Реализация представле ния данных интерфейса Тестирование очереди Имитация реальной очереди С равнение связного спис ка и мас с ива Деревья бинарного поиска ТипАDТ бинарного дере ва Инте рфе йс дере ва бинарного поиска Реализация бинарного дере ва Тестирование дерева Сооб ражения по поводу дере ва Другие направления Клю чевые понятия Резю ме В опросы для с амоконтроля Упражнения по программированию Приложение А. ответы на вопросы для самоконтроля Ответы на вопросы для с амоконтроля из главы 1 Ответы на вопросы для с амоконтроля из главы 2 Ответы на вопросы для с амоконтроля из главы 3 Ответы на вопросы для с амоконтроля из главы 4 Ответы на вопросы для с амоконтроля из главы 5 Ответы на вопросы для с амоконтроля из главы 6 Ответы на вопросы для с амоконтроля из главы 7 Ответы на вопросы для с амоконтроля из главы 8 Ответы на вопросы для с амоконтроля из главы 9 Ответы на вопросы для с амоконтроля из главы 1 0 Ответы на вопросы для с амоконтроля и з главы 1 1 Ответы на вопросы для с амоконтроля и з главы 1 2 Ответы на вопросы для с амоконтроля и з главы 1 3 Ответы на вопросы для с амоконтроля и з главы 1 4 Ответы на вопросы для с амоконтроля и з главы 1 5 Ответы на вопросы для с амоконтроля и з главы 1 6 Ответы на вопросы для с амоконтроля и з главы 1 7 Приложение Б. справочный раздел Раздел 1. Дополнительные источники информации Журнал 756 757 762 764 771 771 772 773 781 783 789 793 795 795 798 813 817 819 820 820 820 821 823 823 823 825 828 830 833 836 840 841 843 845 847 848 851 855 856 858 861 861 861 Содерж ание Сете вые ресурсы Книги по языку С Книги по программированию Справочные руководства Книги по С++ Раздел 11. О перации С Арифметические операции Операции отношений Операции прис ваивания Логические опе рации Условная операция Операции, связанные с указателями Операции знаков Операции структур и объединений Поразрядные операции Прочие операции Раздел 111. Б азовые типы и классы памяти Обзор: базовые типы данных Обзор: объявление простой пе ременной Обзор: квалификаторы Раздел IV. Выражения , операторы и поток управления программы Обзор: выраже ния и опе раторы Обзор: оператор while Обзор: оператор for Обзор: оператор do while О б з о р : ис пользование операторов if для реализации выбора Обзор: множественный выбор с помощью switc h Обзор: переходы в программе Раздел V. Стандартная библиотека ANSI С с дополне ниями С99 Диагностика : asse rt.h Комплексные числа : com plex.h (С 99) Об работка символов: c type .h Сообще ния об ошибках: e rrno.h Среда плавающей запятой: fenv.h (С99) Преобразование формата целочисленных типов: inttypes.h (С99) Локализация: locale .h Мате матиче ская библиотека: math.h Нелокальные пе реходы : setjmp.h Об работка сигналов: signal.h Переме нное количество аргументов: stdarg.h Поддержка булевс ких значений: stdb ool.h ( С 9 9 ) Об щие определе ния : stdd e f.h Целочисленные типы : stdint.h Стандартная библиотека ввода-вывода: stdio.h Об щие утилиты: stdlib .h 19 861 862 863 863 864 864 865 865 866 866 867 867 868 868 869 8 70 8 70 8 70 8 72 8 74 8 75 8 75 8 76 8 76 877 877 8 78 880 88 1 881 88 1 883 884 885 88 7 888 891 896 897 898 899 899 900 903 906 20 Содержание Об работка строк: string.h Мате матиче ские функции для общих типов : tgm ath.h (С99) Дата и время: time.h Утилиты для работы с мноrобайтными и рас ширенными с имволами: wc har.h (С99 ) Утилиты классификации и отображения рас ширенных символов: wctype.h (С99) Раздел VI. Расшире нные целочисленные типы Типы строгой ширины Типы минимальной ширины Быстрые типы минимальной ширины Типы макс имальной ширины Целые , которые могут хранить указатели Рас ширенные целочисленные константы Раздел VII. Расширенная поддержка символов Триrрафы Диграфы Альтернативная орфография : iso646 .h Многобайтные символы Универсальные имена символов (UCN) Рас ширенные символы Рас ширенные и многобайтные символы Раздел VIII. Расширенные средства вычислений С99 Стандарт плавающей запятой IEC Заголовочный файл fe nv.h Указание компилятору STDC FP _CO NTRACT Дополнения к библиотеке math.h Поддержка комплексных чисел Раздел IX. Различия между С и С++ Прототипы функций Константы char Модификатор const Структуры и объединения Перечисления Указатель на void Булевские типы Альтернативная орфография Поддержка расширенных символов Комплексные типы Встраиваемые функции Средства С99, которых нет в С++ 912 915 916 920 926 928 929 929 930 930 93 1 93 1 93 1 932 932 933 933 934 934 936 936 936 93 7 938 938 939 940 940 941 942 943 943 944 944 944 944 945 945 945 Приложение в. набор символов ASCll 946 Предметный указатель 950 Преди сл ов и е Когда в 1 984 году б ыло напис ано первое издание этой книги, язык С не был широ­ ко изве стным языком программирования. С тех пор началось бурное развитие этого языка , и многие люди изучали С , пользуяс ь именно этой книгой. По самым приблизи­ тельным данным данную книгу в различных редакциях приобрело свыше 500 ООО че­ ловек. По мере того , как этот язык развивался от ранней версии Кернигана-Р итчи к стан­ дарту ISO /ANSI 1 990 года , вместе с ним с о вершенствовалас ь и эта книга , и в настоя­ ще е время вышло в свет ее пятое издание . Как и во всех более ранних версиях этой книги, моей целью остается поучительное , четко изложенное и полезное введение в язык с. Подход и цели Эта книга предназначена служить друже стве нным , удобным в использовании и пригодным для самостоятельного изучения справочным пособие м . Чтобы соответст­ вовать этому назначению в книге применяются следую щие стратегии: • • • • • Наряду с описание м основных особенносте й языка С , излагаются основные принципы программирования ; изначально не предполагается , что читатель яв­ ляется профе с сиональным программистом. Множе ство приведенных в книге коротких примеров , которые легко ввести с клавиатуры, служат одновре менной иллю страцие й одного или двух понятий, иб о обучение путем выполнения представляет собой один из наиболее эффек­ тивных спос об ов освоения новой информации. Рисунки и иллю страции служат пояснениями к понятиям, которые трудно опи­ сать словами. Краткие описания ос новных свойств языка С помещаются в выделе нные рамки с те м, чтобы на них было ле гче с сылаться и отыскивать. В конце каждой главы приводится список вопросов и упражнений, которые пред· назначены для того, чтобы помочь вам проверить и закрепить знания языка С . Чтобы извлечь макс имальную пользу, в ы должны , насколько это возможно , играть активную роль при изучении мате риала данной книги. Не ограничивайтесь только чтением приме ров, введите их и попытайтес ь выполнить. Язык С является достаточно переносимым, но вы можете обнаружить различия между тем, как ведет с ебя та или иная программа в вашей системе и как она работает в системе автора . Не стесняйтесь экс периментировать, меняйте различные части программы, чтобы пос мотреть, к ка­ ким эффе ктам это приведет . Внос ите изме нения в программу, чтобы она выполняла работу, немного отличающую ся от первоначального варианта . Время от времени иг­ норируйте случайные предупреждаю щие сообщения и посмотрите , что случится , если вы будете поступать вопреки рекомендациям в книге . Попытайтесь найти ответы на вопро с ы и выполнить упражнения . Чем больше вы сделаете сами, тем большему вы научитес ь и больше запомните . Я наде юсь, что в ы найдете новейшее издание книги интересным и эффе ктивным введением в язык программирования С . 22 От издател ьства 06 авторе Стивен Прата (Stephen Prata ) преподает астрономию , физику и программирова­ ние в морс ком колледже города Ке нтфилд , штат Калифорния . Он получил диплом б а­ калавра в Калифорнийском технологичес ком институте и степень доктора филосо­ фии в Калифорнийс ком универс итете (в Беркли ) . Его увлече ние компьютерами нача­ лось с моделирования на компьютере зве здных скопле ний. Стивен является автором и соавтором более десятка книг, вклю чая С++ Primcr Plus и Unix Primer Plus. Посвящен ие С любовью В ики и Биллу Прата (Vicky and B ill Prata) , которые на протяже нии б о­ лее 69 лет показывали окружаю щим, каким благом может быть супружество. Стивен Прат а Благодарн ости Я хотел б ы побла годарить Лоретту Яте (Lore tta Ya te s ) из издательства Sams PuЬlishing за вклад в реализацию этого проекта и Сонглин Кию (Songlin Qiu) из того же издательства за просмотр рукописи. Благодарю также Рона Личти (Ron Liechty) из ком­ пании Metrowerks и Грега Комо (G reg Comeau) из компании Comeau Computing за по­ мощь в освоении новых свойств С99 и огромный вклад в службу работы с покупателями. От и здател ьства В ы , читатель этой книги, и есть главный ее критик и комме нтатор . Мы ценим ваше мне ние и хотим знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и любые другие заме чания, которые вам хотелось б ы высказать в наш адре с . Мы ждем ваших комментариев и надеемся н а них. Вы можете прислать нам бумаж­ ное или эле ктронное письмо , либо просто посетить наш WеЬ-с ервер и оставить свои заме чания там . Одним слово м , лю б ым удобным для вас способом дайте нам знать, нра­ вится или нет вам эта книга , а также выскажите свое мне ние о том, как сделать наши книги боле е интере сными для вас . Посылая письмо или сообщение , н е забудьте указать название книги и е е авторов, а также ваш обратный адрес . Мы внимательно ознакомимся с вашим мнением и обяза­ тельно учтем его при отборе и подготовке к изданию последую щих книг. Наши координаты: E-m ail: in fo@willi amspuЬ l i s hing . com WWW: http : / /www . willi amspuЫ i s hing . com Информация для писе м из: Росс ии: 1 1 54 1 9 , Мос ква , а/я 783 Украины : 03 1 50 , Киев, а/я 1 5 2 ГЛА ВА 1 Пред ва р ител ь н ые с веде н ия в этой главе: • • д Воз м ожности и и стория созд ания языка с Дей ствия. которые нужн о в ы п ол нить для написания п рограмм • • Немн ого о комп иляторах и компон овщиках ста ндарты языка с обро пожаловать в мир С мощного языка программирования для професс ио­ налов , который в не меньшей сте пени популярен и в с реде любителей, и в с реде программистов, пишущих программы для коммерче ского приме не ния . Эта гла­ ва подготовит вас к изучению и использованию этого мощного и широко рас простра­ не нного языка , она ознакомит вас с различными операционными средами, в которых вы, скорее всего, будете наращивать ваши знания языка С. Прежде вс его , мы ознако­ мимся с происхождением языка С и изучим некоторые его с войства , а также его с иль­ ные и слабые стороны . Затем мы изучим основы программирования и рас смотрим ос­ новные принципы программирования . В заверше ние мы обсудим, как выполнять про­ граммы на языке С в некоторых изве стных системах. - К ак п оявил ся язык С ? Деннис Ритчи (De nnis Ritchie ) из компании B ell Lab s , создал язык С в 1 9 72 году, ко­ гда он и Кен Томпсон (Ke n Thompson) работали над созданием операционной с исте­ мы Unix. Однако сам язык С не зародился просто так в голове Ритчи. Его предше ст­ венником был язык В, с о зданный Томпс оном, предшественником которого был . . . , но это уже другая история . Наиболее важным является тот факт, что С задумывался как инструментальное средство для программистов-практиков, следовательно , его глав­ ной целью в этом случае было создание поле зного языка программирования . Б ольшинство языков программирования создавались с целью быть полезными, но довольно-таки часто перед ними ставилис ь другие цели. Например, главной целью языка Pascal было создание базис а для изуче ния основных принципов программиро­ вания . С другой стороны , язык BASIC создавался как язык программирования , при­ ближе нный к е сте стве нному английс кому, чтобы обле гчить задачу изучения языков 24 Гл ава 1 программирования студентам, не знакомых с компьютерами. Это достаточно важные цели, однако , они не все гда содействуют достижению прагматичности и повседнев­ ной пригодности. Тем не менее , разработка С как языка , предназначе нного для про· граммистов, сделала его одним из наиболее востребованных в настояще е время . По чему язык С ? В те че ние трех последних десятилетий С стал одним из основных и наиболее ши­ роко распространенных языков программирования . Его популярность росла в связи с тем, что люди предпринимали попытки работать с ним, во вре мя которых он показы­ вал с е бя с лучшей стороны . За последне е десятилетия многие программисты пере шли с языка на более претенциозный язык С++, но язык С сам по себе все е ще остается важным языком, равно как и путем перехода к С + + . По мере изучения С вы убедитесь, что он обладает многими достоинствами (рис 1 . 1 ) . Некоторые из них мы отметим сейча с . М ощные управляющие струюуры Быстродействие Компактный программный код небольшие программы П ереносимость на другие комп ьютеры Ри с. 1 . 1 . Достоииства язъ�ка С Предварител ьные сведения 25 Конструкти вные особен ности С представляет собой с овременный язык программирования , вклю чаю щий управ­ ляю щие средства , которые теория и практика вычислительных систем рассматривает как поле зные и желательные . Его конструкция хорошо подходит для планирования сверху вниз, для структурного программирования и для модульного проектирования . Все это позволяет получать наде жные и понятные программы . Э ффекти вность С является эффективным языком программирования . Его конструкция продуктив­ но ис пользует возможности компьютеров, на которых он установлен. Программы на С отличаются компактностью и быстротой исполне ния . По сути дела С обладает не ко­ торыми с редствами точного управления, обычно характерными разве что для языка ассемблера. (Язъ�к ассемблера - это мнемониче ское представление множества инструк­ ций, ис пользуемых конкретным центральным процессором; различные семейства центральных проце ссоров имеют различные языки а с сембле ра . ) По желанию вы мо­ жете настроить ваши программы на максимальную с корость ис полнения или на более эффективное ис пользование памяти. Переносимость Язык С является пере нос имым языком, это означает, что программу, написанную на С для одной системы , можно выполнять на другой системе все го лишь с небольши­ ми изменениями, причем иногда удается вообще обходиться без измене ний. В тех слу­ чаях, когда изменения неизбежны, они ограничиваются простым редактированием не скольких запис ей в заголовочном файле , с опровождающем главную программу. Многие языки декларируются пе реносимыми, однако тот, кто преобразовывал про­ грамму на языке BASIC, предназначенном для перс онального компьютера (ПК) ком­ пании IВМ (IВМ РС ) в программу на языке BASIC для компьютера Apple (они являют­ ся близкими родственниками) , либ о предпринимал попытки выполнить в с реде Unix программу на языке FORTRAN, которая предназначена для мэйнфре йма IBM , знает, что такой пе ре нос - в лучшем случае очень трудоемкая операция . Язык С является ли­ дером в смысле переносимости. Компиляторы языка С (программы , преобразую щие ваш код на С в инструкции, ко­ торые компьютер использует для внутренних целей) доступны примерно для 40 систем, от 8-разрядных микропроцессоров до суперкомпьютеров Cray. Однако следует отметить, что фрагменты программы, написанной специально для доступа к конкретным аппарат­ ным устройствам, таким как монитор или специальные функции операционных систем, подобных Windows ХР или OS Х, обычно не принадлежат к числу переносимых. Поскольку язык С тес но с вязан с Unix, операционные системы с емейства Unix по­ ставляются с компилятором С в виде части соответствую щих пакетов. Инсталляция операционной с истемы Linux в об ще м случае также вклю чает компилятор языка С . Несколько компиляторов языка С предназначены для персональных компьютеров, вклю чая различные верс ии систем. Т аким образом, ис пользуете ли вы домашний ком­ пьютер, профе ссиональную рабочую станцию или мэйнфрейм, у вас высокие шансы получить компилятор языка С для вашей конкретной систе мы. 26 Гл ава 1 мощь и гибкость С это мощный и гибкий язык программирования (два наиболе е предпочитаемых определе ния в литературе компьютерной тематики) . Наприме р, большая часть про­ граммных кодов мощной гибкой операционной системы Unix написана на С . Многие компиляторы и инте рпретаторы других языков, таких как FORTRAN, Pe rl, Python, Pascal, LISP , Logo и BASIC, б ыли реализованы на С. В ре зультате, когда вы ис пользуете FORTRAN на Uniх-машине , то в конечном итоге программ а , написанная на С , выпол­ няет работу по созданию окончательной ис полняемой программы. Программы на С применялись для решения физичес ких и техничес ких задач, и даже для анимации спе циальных эффектов для множе ства фильмов, в числе которых "G ladiator" ("Гла­ диатор" ) . - Ориентация н а програ м м истов Язык С ориентирован на удовлетворе ние потре бностей программистов. О н пре­ доставляет вам доступ к об орудованию и позволяет манипулировать отдельными раз­ рядами памяти. О н также предоставляет богатый выбор операций, которые позволя­ ют вам кратко формулировать ваши задачи. Язык С обладает меньшей строгостью , чем, скажем, Pascal, в плане ограничения того , что вы сможете сделать. Такая гиб­ кость является достоинством и одновреме нно означает опасность. Достоинство за­ клю чается в том, что ре шение многих задач, таких как преобразование форматов данных, в С намного проще , чем в других языках. О пасность с о стоит в том , что вы можете допускать такие ошибки, которые просто невозможны в других языках. Язык С предоставляет вам большую свободу действий, но при этом накладывает и большую ответственность. Наряду с этим, большинство реализаций языка С сопровождаются обширными библиотеками полезных функций на С. Эти функции способны удовлетворить многие из потре бностей, с которыми сталкивается программист. Недостатки Язык С не лише н недостатков. Так же как в случае людей, недостатки и достоинст­ ва являются противоположными сторонами одного и того же свойства . Например, как мы уже упоминали, свобода выражений в языке С также требует дополнительной от­ ветственности. В частности, использование в С указателей (чтобы знать, что такое указатели, вы должны заглянуть в последую щие главы данной книги) означает , что возникают условия для появления программных ошибок, которые оче нь трудно от­ следить. Один из известных людей перефразировал данный комментарий следую щим образом: це ной свободы является постоянная бдительность. Выразительность языка С в сочетании с б огатством его опе раций позволяет пис ать такие программные коды, которые трудно понять. Мы отнюдь не настаиваем на том, чтобы вы пис али малопо­ нятные коды , но такая возможность суще ствует. В конце концов, для какого другого языка устраивается ежегодный конкурс на самый парадокс альный программный код? В языке С много достоинств, но, и, не сомненно , еще больше недостатков. Однако вместо того , чтобы погружаться в это дело глубже, перейде м к новой теме . Предварител ьные сведения 27 Откуда пош ел язык С ? В начале восьмиде сятых годов прошлого столетия С уже б ы л доминирую щим язы­ ком программирования в среде миникомпьютеров, функционировавших под управле­ нием опе рационной с истем Unix. С тех пор он распространился на перс ональные компьютеры (микрокомпьютеры) и мэйнфреймы (большие вычислительные маши­ ны ) . Обратите внимание на рис . 1 .2 . Многие компании по разработке и поставке про­ граммного обеспе че ния предпочитают использовать именно язык С при создании программ для те кстовых процессоров, крупномас штаб ных электронных таблиц, ком­ пиляторов и других программных продуктов . Эти компании убедилис ь в том, что с по­ мощью С можно создавать компактные и эффективные программы . Что важнее , они знают, что эти в программы ле гко вносить измене ния и ле гко адаптировать к новым моделям компьютеров . В с е , что хорошо для компаний и для ветеранов языка С , хорошо также и для других пользователей. В с е больше и больше пользователей компьютеров об ращаются к языку С, чтобы воспользоваться его преимуществами. Чтобы программировать на языке С , вовсе н е надо быть компьютерным профессионалом . Операционная система UNIX • Киностудия Lucas Film Комп ьютерные языки � "11111 П риложения для ПК � Язык с � Т Комп ьютерные � "'РЫ � ,,.. � Встроенные системы Фабрики роботов "Звездные войны" Р и с . 1 . 2 . Где исполъзу ется язъtх С 28 Гл ава 1 В девяностых годах прошлого столетия многие компании, изготавливающие и по­ ставляю щие программное обес пе че ние , стали пе реходить на язык С++ при реализа­ ции крупных программных проектов. Язык С++ добавляет к С инструментальные средства объе ктно-ориентированного программирования . ( Объектuо-{)рuеитирован:ное про?jюммироваиие ( object-{)rient,ed prograттiпg) представляет собой философию , которая пытается формировать язык таким образом, чтобы он соответствовал задаче , в отли­ чие от формулирования задачи таким образом, чтоб ы она соответствовала языку про­ граммирования . ) С++ в пе рвом приближении можно рас сматривать как надмноже ство языка С в том смысле , что программа на С также является или почти является про­ граммой на С + + . Изучая язык С , вы фактически изучаете некоторую часть языка С + + . Несмотря н а популярность б олее новых языков, таких к а к , например, С++ и Java , С сохраняет лидирую щее положе ние по с пособности решать задачи из области разра­ ботки программного об еспечения, неизменно завоевывая наивысший балл в рейтинге богатства возможностей языков программирования . В частности, С неизменно ис­ пользуется для программирования встрое нных систем. Иначе говоря, он вс е чаще приме няется для программирования обычных микропроце с с оров, встрое нных в ав­ то мо били , кам е р ы , DVD-проигрыватели и в другие с о вр е м е нные устройства , ис­ пользуе мые в быту. На ряду с этим С посягает на долговременное гос подство я зыка FORTRAN в области научного программирования . И, наконец, как язык, создавав­ шийся для разработки опе рационных систе м, он играет клю че вую роль в создании операционной системы Linux. Т аким образом , и в первой декаде двадцать первого ве­ ка С удерживает за с обой роль сильного языка . Короче говоря , С является одним и з наиболее важных языков программирования и надолго останется таковым. Если вы хотите заниматься разраб откой программ, то во­ прос , можете ли вы работать в языке С , вы непре менно должны ответить утве рди­ тельно. К ак работа ют ком п ьютеры Прежде чем вы приступите к изучению программирования в языке С , вы, очевид­ но , должны получить хотя бы самое приблизительное представление о том, как раб о­ тает компьюте р. Эти знания помогут вам понять, какая с вязь между написание м про­ граммы на С и что на самом деле происходит, когда вы исполняете эту программу. С овременные компьютеры состоят из нескольких компоне нтов . Цеитралъиое про 'Цессориое устройство (ЦП) выполняет основную вычислительную работу. Памятъ с про изволъиъ�м доступом, или оперативное запоминающее устройство (О ЗУ) , представляет собой рабочую область, в которой с одержатся программы и файлы. Постоянная па­ мять, обычно же сткий диск, запоминает эти программы и файлы и хранит их, даже когда компьютер выклю чен. Периферийные устройства различного назначения , та­ кие как клавиатура, мышь и монитор, об еспечивают обмен данными между вами и компьютером. ЦП обрабатывает ваши программы , поэтому рассмотрим более под­ робно его роль. Функции ЦП достаточно просты. Он извлекает команду из памяти и выполняет ее . Затем он извлекает следую щую команду и выполняет ее , и так дале е . (Центральный проце с с ор с тактовой частотой 1 ГГц выполняет порядка одного миллиарда таких операций в с е кунду, так что ЦП ведет монотонную жизнь в бешеном темпе . ) ЦП имеет Предварител ьные сведения 29 собстве нную рабочую область, с остоящую и з нескольких регистров, каждый и з них мо­ жет запоминать не которое число . Один регистр содержит адре с следую щей команды в памяти, а ЦП использует эту информацию для извлече ния следую ще й команды . После получения следую щей команды ЦП запоминает ее в другом ре гистре и обновляет пер­ вый регистр адре сом очередной команды. Центральный процессор выполняет огра­ ниченный набор команд (получивший название 'Набора и'Нсmру1сциu) . Наряду с этим, эти команды достаточно специфичны , многие из них требуют от ЦП пере местить число из одного места в другое, например, из ячейки памяти в тот или иной регистр. Здесь следует отметить два интерес ных факта . Во-первых, вс е , что хранится в ком­ пьютере, хранится в виде чисел. Числа сохраняются как числа . Символы , такие как буквы алфавита , которые вы ис пользуете в текстовых документах, с охраняются как числа , при этом каждый символ имеет свой числовой код . Команды, которые компью­ тер загружает в свои регистр ы , сохраняются как числа , каждая команда из системы команд имеет свой числовой код . Во-вторых, компьютерная программа в коне чном итоге должна быть выражена в этом числовом коде, или, другими словами, с помощью ма ШU'Н'НОго ЯЗ'ЬLКа. Одним из последствий такого принципа работы компьюте ра является то , что если вы хотите , чтобы компьютер выполнил какую-то раб оту, вы должны ввести конкрет­ ный список инструкций (программу) , в котором подробно расписано , что и как нужно сделать. Вы должны создать программу на языке , который понятен непосредственно компьютеру (на машинном языке ) . Это кропотливая и утомительная работа , требую­ щая б ольшой точности. Такая простая операция , как сложение двух чисел, должна быть разбита на несколько действий, примерно следую щим образом: 1. Скопировать число из ячейки памяти 2000 в регистр 1 . 2 . Скопировать число из ячейки памяти 2004 в регистр 2 . 3 . Сложить содержимое ре гистра 2 с с одержимым ре гистра 1 и оставить ре зультат сложе ния в регистре 1 . 4 . Скопировать с одержимое ре гистра 1 в ячейку памяти 2008. Каждую из этих инструкций вы должны представить в числовом коде ! Если напи­ сание программ в таком стиле вам нравится , вынужден огорчить вас , сообщив, что зо­ лотой ве к программирования в машинных кодах давно ушел в прошлое . Однако если вы все-таки предпочитаете что-нибудь б олее инте ре сное , добро пожаловать в языки программирования высокого уровня . Я зыки п рограмм и рования высокого уровня и ком п иляторы Языки программирования выс окого уровня , такие как С , существе нно упрощают вашу жизнь как программиста несколькими с пособами. Во-пе рвых, вы не должны представлять команды в числовом коде . Во-вторых, команды, которые вы ис пользуете , намного ближе к тому, как вы думаете о задаче , нежели к тому, как она представле на в рамках детализированного подхода , который использует компьютер. В место того чтобы обре менять себя мыслями о том, какие действия конкретный ЦП должен пред­ принять, чтобы решить конкретную задачу, вы можете выразить свои пожелания на 30 Гл ава 1 более абстрактном уровне . Чтобы сложить два числа , вы можете , например, напис ать следую щую конструкцию : total = mine + your s ; Увиде в код , подобный этому, вы сразу же догадываетесь, что он делает , в то же вре мя , просматривая эквивалентный код на машинном языке , содержащий несколько команд , выраженных в числовой форме , вы не сразу поймете , о чем идет речь. К со­ жалению , для компьютера это верно с точностью до наоборот, для не го команда на языке высокого уровня - непонятная бесс мыслица . Именно в этот момент на перед­ ний план выступают компиляторы . Компилятор - это программ а , которая пере водит программу, представленную на языке высокого уровня , в детальный набор команд на машинном языке , понимае мых компьютером . На вашу долю приходится творческое мышление в командах языка высокого уровня , а вс ю трудоемкую детализацию компи­ лятор бе рет на с ебя . Подход с использованием компилятора дает е ще одно преимущество . В общем слу­ чае с каждой моделью компьютера связан собстве нный уникальный машинный язык. Следовательно, программа, написанная на машинном языке , скажем, для ЦП Intel Pentium ниче го не говорит проце с сору Motorola PowerPC . В то же время вы можете прис пособить компилятор для конкретного машинного языка . По этой причине, рас­ полагая нужным компилятором или набором компиляторов, вы можете преобразовать одну и ту же программу на языке высокого уровня в различные программы на разных машинных языках. Вы решаете задачу программирования только один раз , после чего вы предоставляете возможность вашим компиляторам транслировать ее решение на множество различных машинных языков. Короче говоря , языки высокого уровня , такие как язык C , Java и Pascal, опис ывают действия в более абстрактной форме и привязаны к конкретному ЦП или к конкрет­ ной с истеме команд . К тому же , языки высокого уровня значительно легче изучать, на не м намного проще программировать, чем на машинных языках. Исп ол ьзовани е языка С: семь этап ов Язык С , как уже говорилось, является транслируе мым языком. Если вы привыкли работать с транслируе мым языком, например, с языком Pascal или FORTRAN, вам из­ вестны основные действия , выполняе мые для сборки программы , написанной на С . Тем не менее , если вы имели дело с интерпретируе мым языком, например, BASIC , или графичес ким интерфейсно-ориентированным языком , таким как, например, Visual Basic , или если у вас вообще нет программистского опыта , вы должны ознакомиться с особенностями компиляции. Мы вскоре рас смотрим этот проце с с , и вы с можете убе­ диться сами, что он достаточно прост и практичен. Во-первых, чтобы дать вам общее предоставление о программировании, разобьем процедуру написания программы на языке С на семь этапов (рис . 1 .3 ) . О братите внимание на то обстоятельство , что это идеализация. На практике , особенно в случае крупных проектов, вы должны пере ме­ щаться назад и вперед, используя то , че му вы научилис ь на более позднем этапе , для уточне ния результатов, полученных на более ранне й стадии. Предварител ьные сведения 31 Сопровождение и модификация программы � � 1:1 $1:1 �l:J �l:J ® l:J Тестирование и отладка программы Запуск программы на выполнение Компиляция Написание кода П роектирование программы 1:1 Определение целей программы Ри с. 1 . 3 . Семъ этапов программирова'/1,UЯ э тап 1 : определен ие целей п рограммы В полне естественно, в ы должны начинать с четкого видения того, что, по вашему мне нию , программа должна делать. В пе ре воде на информацию , которая нужна вашей программе , обдумайте вычисле ния и манипуляции, которые программа должна вы­ полнить, а также информацию , которую она должна возвратить. На этом уровне пла­ нирования вы должны мыслить в общих те рминах, а не в те рминах не которого кон­ кретного компьюте рного языка . э тап 2 : п роектирова ние п рогра м м ы После того , как станет ясна концептуальная картина того , что ваша программа должна сделать, вы должны ре шить, как она должна это сделать. Каким долже н быть пользовательский интерфейс ? Как должна быть организована эта программа? Како­ выми будут целе вые пользователи? Сколько времени потребуется для заверше ния раз­ работки программы? В ы также должны решить, как представлять данные в программе и, возможно , во вспомогательных файлах, а также какие методы следует ис пользовать для обработки данных. На начальном этапе изуче ния программирования в С ответы на эти вопрос ы не вызовут у вас затруднений, но если вы окажетесь в б олее сложной ситуации, то поймете , что эти решения потребуют от вас учета множества обстоятельств. Пра­ вильный выбор спос об а представления информации может существе нно облегчить разработку программы и обработку данных. 32 Гл ава 1 Подчеркнем еще раз , вы должны мыслить общими кате гориями и не думать о кон­ кретном коде , однако некоторые из ваших решений могут быть основаны на общих характеристиках языка . Наприме р, программист, работаю щий на С , имеет гораздо больше вариантов представления данных, че м, с каже м , программист, имеющий дело с языком Pascal. Э тап 3 : на писа н ие кода Теперь, когда вы создали проект ваше й программы , вы можете приступать к ее ре ализации, для че го необходимо написать программный код . Иначе говоря , вы пе ре­ водите проект программы на язык С . Именно на этой стадии потребуются все ваши знания языка С. В ы можете наброс ать решения на бумаге , но в конечном итоге вы должны будете ввести с оставленный вами код в компьютер. Механика этого процесса зависит от среды программирования , в которой вы раб отаете . Вскоре мы ознакомим вас с характеристиками не которых опе рационных сред. В об ще м случае вы используе­ те те кстовый редактор для построения так называемого файла исходного кода . Этот файл с одержит интерпретацию проекта вашей программы на языке С. В листинге 1 .1 показан пример исходного кода на С . листинг 1 .1 . Пример исходного кода на языке с #incl ude <s tdio . h > int main ( vo i d ) { int dogs ; printf ( " Cкoль к o у в а с со б ак ? \ n " ) ; s c an f ( " % d" , & собак) ; printf ( " Следов атель но , у в а с % d собак ( а , и ) ! \ n " , dogs ) ; r e turn О ; К числу работ, которые вы должны выполнить на этом этапе , относится докумен­ тирование ваших де йствий. Просте йшим способом докуме нтирования является ком­ ментарий, которым с набжается программный код на С , и в который вы помещаете не обходимые поясне ния . В главе 2 подробно опис ано , как следует употреблять ком­ ментарии в ваше м программном коде. Э тап 4 : ком п иля ция Следую щим этапом разработки является компиляция исходного кода . И в этом слу­ чае детали зависят от среды программирования , поэтому мы вскоре рас смотрим не ко­ торые из распространенных с ред. А пока мы расс мотрим концептуальное представле­ ние того , что происходит на этом этапе . Напомним, что компилятор представляет собой программу, в обязанности которой входит преобразование исходного кода в исполняемый код . Исполняемъ�u ход это соб­ стве нный язык машины, или маши11.11.ъ�u язъ�х ваше го компьюте ра . Этот язык из под­ робных команд, представленных в числовом коде. Как вы уже прочли выш е , разные компьютеры име ют разные машинные языки, а компилятор языка С транслирует код С в конкретный машинный язык. - Предварител ьные сведения 33 Компиляторы языка С вставляют также коды и з библиоте к программ н а С в окон­ чательный вариант программы; упомянутые библиотеки с одержат комплект стан­ дартных программ, например, printf ( ) и s c an f ( ) , дабы вы , при не обходимости, мог­ ли ими воспользоваться . (Если говорить точне е , то библиотечные программы в вашу программу вклю чает инструме нт , получивший название компонов щика, или редактор а связей, те м не менее , в большинстве систе м его запускает компилятор.) В коне чном итоге получается исполняемый файл, который понимает компьюте р, и который мож­ но запус кать на выполнение . Компилятор проверяет также , не с одержит ли ошибок ваша программа на С . Когда компилятор находит ошибки, он уведомляет об их наличии и не создает исполняе мый файл. Понимание "жалоб" компилятора - это еще одна обязанность, которую вам придется ос воить. э тап 5: за пуск програ м м ы на в ы полнение К а к правило, ис полняемый файл представляет собой программу, которую в ы мо­ жете запус кать на выполнение . Чтобы запустить программу, во многих изве стных сре­ дах, включая консоли MS-D O S , Unix, Linux , не обходимо ввести с клавиатуры имя ис­ полняемого файла. Другие среды , такие как система VМS на миникомпьютерах VAX, могут потребовать ввода команды запуска или использования какого-либо другого меха­ низма. Среды IDE (In tegrated developтen t en vironтen ts - интегрированная среда разработки), подобные те м, что поставляются для Windows и Mac intosh, позволяют редактировать и выполнять программы на С внутри с реды, выбирая соответствую щие пункты ме ню или нажимая с пециальные клавиши. Получе нная программа может быть запуще на на выполнение не посредственно из операционной с истемы путем одиночного или двой­ ного щелчка на имени файла или на с о ответствую щей пиктограмме . э тап 6: тести рова ние и отладка п рогра м м ы Тот факт , что ваша программа работает - хороший знак, тем не менее , есть веро­ ятность, что она работает неправильно. Отсюда следует, что вы должны уб едиться, что ваша программа делает именно то , что и должна. Достаточно часто в своих про­ граммах вы будете об наруживать ошибки. Отладка - это проце с с обнаружения и ис­ правле ния программных ошибок. Допущение ошибок является естественной состав­ ляю щей проце с с а обучения . О ни, по-видимому, присущи программированию , так что когда вы с о четаете программирование с обуче ние м, лучше быть готовым к частым на­ поминаниям об ошиб ках. По мере того , как вы становитес ь все более квалифициро­ ванным и проницательным программистом, ваши ошибки также становятся вс е более разрушительными и трудно об наруживае мыми. У вас есть много возможносте й с овершить ошибку. Вы можете с о вершить принци­ пиальную ошибку в проекте программы . Вы можете некорректно реализовать хоро­ шую идею . Вы можете упустить из виду недопустимые входные данные, которые иска­ зят вашу программу. Вы можете неправильно использовать конструкции самого языка С . Вы можете допус кать ошибки при наборе кода с клавиатур ы . Вы можете неправильно расставить скобки и так далее . Самостоятельно дополните этот печальный спис ок приме рами из своей практики. 34 Гл ава 1 К счастью , ситуация небезнадежна , хотя могут наступить такие моме нты , когда вам покажется , что это именно так. Компилятор отсле живает многие виды ошибок, кроме того , можно предпринять определенные ус илия , дабы помочь самому себе в поиске ошибок, которые не отловил компилятор . По мере изучения данной книги вы найдете в ней множе ство с о ветов по практической отладке программ. Э тап 7: соп ровожден ие и модифи ка ция п рогра м м ы Когда в ы создаете программу для себя или для кого-нибудь е ще , то , скорее всего , предполагаете , что о н а будет ис пользоваться достаточно часто . Если это так, возмож­ но , появятся причины для внесения в нее измене ний. Может быть, существует какой­ то незначительный дефе кт, который проявляется при вводе имени, начинающегося с букв "Zz" , либо возникает желание улучшить что-либо в программе . Вы можете доба­ вить в не е новую функциональную возможность. Вы можете адаптировать программу для выполнения в различных компьюте рных системах. Решение задач подобного рода существенно упрощается , если вы четко документируете программу и следуете прове­ ренным на практике рекомендациям. Ко м мента р и й Программирование обычно н е является таким последовательным процессом, ка­ ковым является описанный выше проце с с . Время от време ни вам приходится пере ме­ щаться туда и обратно по этапам. Например, когда вы пишете программный код , вы можете прийти к заклю чению , что намече нный ране е план неосуществим. Вы можете увидеть лучший с пособ реше ния задачи. Возможно , после анализа выполнения про­ граммы возникнет желание изменить проектное решение . Докуме нтирование совер­ шае мых действий поможет пе ремещаться по этапам туда и обратно . Многие из изучающих программирование пренебрегают этапами 1 и 2 (определе­ ние целей и построение проекта программы ) и пе реходят не посредственно к этапу 3 (напис ание программы ) . Первые написанные вами программы будут достаточно про­ стыми, чтобы вы могли "прокрутить" весь проце с с разработки в голове . Если вы до­ пустите ошиб ку, ее ле гко найти. По ме ре того как ваши программы становятся все крупнее и сложнее , мысле нное представление программы начинает подводить, а на выявление ошибок уходит все больше вре мени. В конечном итоге те , кто пренебрега­ ет стадие й планирования , обре че ны на бесполе зную поте рю вре мени, на долгие часы заме шательства и путаницы , к тому же получая уродливые , плохо функционирую щие и трудные для понимания программы . Чем крупнее и сложнее задача , тем б ольше вре­ мени приходится затрачивать на планирование ее решения. В ывод , который следует из вс его вышесказанного , заклю чается в том, что вы должны выработать в себе привычку составлять планы, пре жде чем приступать к на­ писанию собстве нно кода . Вос пользуйте сь старой, доброй и вполне оправданной тех­ нологией "карандаша и бумаги" , чтобы сформулировать цели вашей программы и на­ бро с ать эс киз е е проекта . Если вы это сделаете , то в коне чном итоге получите боль­ шую экономию времени и останетесь довольны ре зультатами. 35 Предварител ьные сведения М еханика п рограм м и рования Действия, которые в ы должны выполнить, чтобы получить программу, зависит от среды ваше го компьюте ра . Поскольку С - переносимый язык, с ним можно раб отать в различных средах, вклю чая операционные системы Unix, Linux, MS-DOS (вы не ошиблись, некоторые все еще пользуются этой операционной с истемой) , Windows и Mac intosh. В этой книге не хватит ме ста , чтобы рассмотреть все эти опе рационные среды , в частности, в силу того , что отдельные программные продукты развиваются, умирают и заменяются другими. Однако, прежде вс его , рассмотрим не которые аспе кты, которыми обладают все среды языка С , в том числе и указанные выш е . В ообще говоря, вам вовс е не нужно знать, по каким правилам выполняется С-программа , но это полезные знания. Это по­ может понять, почему для с оздания программы на С необходимо пройти через опре­ деле нные этапы . Когда вы пишете программу на языке С , вы с охраняете то , что написано, в тексто­ вом файле , который называется файлом исходного текста . Большинство С-с исте м , в том числе и упомянутые выш е , тре буют, чтобы имя файла заканчивалось на с (на­ приме р, wordcount . c или budget . c) . Часть имени, находящаяся перед точкой, назы­ вается базов'Ым именем, а часть, следую щая за точкой, - расширением. Следовательно , budg e t - это б азовое имя , а с - расширение . Сочетание b udget . с образует имя файла . Это имя должно также удовлетворять требованиям конкретной операционной с исте­ мы компьютера. Например, MS-D OS представляет собой операционную с истему для персональных компьютеров производства компании IBM и совме стимых с ними. О на тре бует , чтобы базовое имя содержало не более вос ьми символов, и в силу этого об­ стоятельства упомянутое выше имя файла wor dcount _ с не будет допустимым именем файла в D O S . Некоторые с истемы Unix ограничивают с овокупную длину имени файла 1 4-ю с имволами, вклю чая рас ширение ; другие системы Unix допускают длинные име­ на порядка 255 символов. О перационные системы Linux, Windows и Mac intosh также разре шают ис пользование длинных имен. Итак, дабы иметь что-то конкретное , на что можно было бы с сылаться, рассмот­ рим файл с име не м concr ete . c, с одержащий исходный код на С , представле нный в листинге 1 .2 . - листинг 1 .2. Программа concret e . с #incl ude <s tdio . h > int main ( vo i d ) { printf ( " Бe тoн содержит пе сок и цемент . \ n " ) ; r e turn О ; Пусть вас пока не беспокоят детали соде ржимого файла исходного кода, представ­ ленного в листинге 1 .2 , мы вернемся к ним в главе 2 . 36 Гл ава 1 Ф айлы объектного кода. исполняем ые фа йл ы и библ иотеки Базовая стратегия программирования на С заклю чается в том, чтобы использовать программы, которые преобразуют ваш исходный код в исполняемый файл, содержащий готовый к выполнению программный код на машинном языке . Эта работа выполняется в два этапа: компиляция и компоновка. Компилятор преобразует ваш исходный код в промежуточный код, а компоновщик комбинирует этот код с другим кодом, в результате получается исполняемый файл. В С используется такой двухэтапный подход для модуль­ ной организации программ. Вы можете компилировать индивидуальные модули по от­ дельности, а затем с помощью компоновщика объединить скомпилированные модули. Таким образом, если потребуется изменить какой-то один модуль, не нужно будет по­ вторно компилировать остальные модули. Кроме того, компоновщик связывает вашу программу с за ранее откомпилированным библиотечным кодом. Суще ствует не сколько вариантов форматов промежуточных файлов. Наиболее предпочтительным является вариант , выбранный для реализаций, описанных в дан­ ной книге , который предус матривает преобразование исходного кода в код на ма­ шинном языке , после че го ре зультат помещается в файл обиктиого кода, или, сокра­ ще нно , в обиктиъ�u файл. (При этом предполагается , что ваш исходный код хранится в одном файле . ) И хотя объе ктный файл содержит коды в машинном языке , он еще не готов к выполнению . О бъе ктный файл содержит трансляцию ваше го исходного кода, но это незаконченная программа . Первый эле мент, которого не хватает в файле объектного кода - это код запуска, представляющий с обой код, который де йствует в каче стве интерфе йс а между ваше й программой и операционной с истемой. Наприме р, вы можете запускать программу на одинаковых пе рсональных компьютерах, один из которых функционирует под управ­ лением D O S , а другой - под управлением Linux. В обоих случаях об орудование одно и то же , так что используется один и тот же объектный код, в то же время нужны раз­ ные коды запуска для DOS и для Linux , пос кольку эти системы подде рживают про­ граммы по-разному. Вторым отсутствую щим элементом является коды библиотечных программ. Практи­ чески все С-программы используют стандартные программы (именуемые фуикv,иями) , которые являются частью библиотеки С . Например, concr e t e . с использует функцию print f ( ) . Объектный файл не с одержит код этой функции, он просто содержит ко· манду, тре бую щую ис пользования функции printf ( ) . Фактиче ский код хранится в файле , получившем название библиотеки. Библиотечный файл содержит объе ктные коды для множества функций. Роль компоновщика заключается в том , чтобы с обрать воедино эти три эле мента ваш объектный код, стандартный код запус ка и библиоте чный код - с последую щим запоминанием в отдельном файле , который называется ис полняемым. Что касается библиотечного кода , то компоновщик извлекает только код , необходимый для функ· ций, вызываемых из библиоте ки, как показано на рис . 1 .4. Короче , как объектный, так и исполняемый файлы содержат команды на машинном языке . В то же время, объектный файл содержит только результат трансляции ваших программных кодов, в то время как исполняемый файл - также и машинные коды ис· пользованных вами стандартных библиотечных программ и код инициализации. Предварител ьные сведения 37 Компилятор Объектный код о Комп оновщик о Код запуска Исполняемый код о Ри с. 1 . 4. Компилятор и компонов щик В некоторых систе мах вы должны запускать транслятор и компоновщик отдельно . В других системах компилятор запускает компоновщик автоматиче ски, так что вам ос­ тается только выдать команду на компиляцию . Теперь рассмотрим не сколько кон­ кретных систе м. опера цион ная система Unix Поскольку популярность языка С начала с ь с систем на базе Unix, мы начне м име н­ но с этой операционной системы . Р едакти рован ие в систем е U n ix Язык С в с истеме Unix не имеет собственного редактора. В этом случае использу­ ется один из редакторов Unix общего назначе ния , например, e m a c s , j ove , vi или те к­ стовый редактор с истемы Х Window System . В ам достаточно добросовестно выполнить две процедуры: правильно ввести про­ грамму с клавиатуры и выбрать имя для файла, в котором будет храниться эта про­ грамма . Как уже говорилось выш е , это имя должно оканчиваться на . с. О братите вни­ мание , что система Unix различает буквы верхне го и нижнего регистров. Поэтому budg e t . с, BUDGE T . с и Budget . с три различных допустимых име ни исходных фай­ лов , в то же вре мя B UDGET . С таковым не является , так как рас шире ние . С представле­ но в верхнем, а не нижнем регистре . С помощью редактора vi мы ввели приведе нную ниже программу и с охранили ее в файле i n form . с . - Гл ава 1 38 #i nclude < s tdi o . h> int main ( void) { print f ( " . c з ав ерш а е т имя ф айла с прогр аммой н а C . \ n " ) ; r etur n О ; Этот текст представляет собой исходный код, а in form . с - исходный файл. Здесь важно отметить, что исходный файл - это начало процесс а , но не его конец. Ком пиляция в системе U nix Наша программа, хотя совершенная во всех других отноше ниях, вс е же непонятна компьютеру. Компьюте р не понимает таких выражений, как #include и print f. (На этой стадии вы , скорее все го , тоже , однако у вас е сть наде жда вс коре узнать, что это такое , в то время как у компьютера нет никаких шансов.) Как уже было отмечено вы· ше , мы нуждаемся в помощи компилятора при трансляции программного кода (исход· ного кода ) в код компьютера (машинный код ) . Результатом этих усилий будет испол· няемый файл, который содержит все машинные коды , которые нужны компьюте ру, чтобы выполнить работу. Компилятор языка С в опе рационной системе Unix называется се. Чтобы скомпи· лировать программу i n fo rm . с, вы должны ввести с клавиатуры следую щую команду: се i n form . c Через несколько секунд вновь отобразится подсказка системы Unix, уведомляю щая о том, что дело сделано . Вы можете получить предупреждающее сообщение или сообще· ние об ошибке, если программа написана неправильно , однако предположим, что все прошло удачно . (Если компилятор жалуется, что не понимает слова void, это означает, что ваша система не имеет компилятора ANSI С . Более подробно о стандартах мы пого· ворим далее . На данный момент имеет смысл просто удалить слово void.) Если восполь· зоваться командой l s для получения списка файлов, обнаружится файл с именем a . out (рис . 1 . 5 ) . Это исполняемый файл, содержащий оттранслированную (или скомпилиро· ванную ) программу. Чтобы запустить его, достаточно ввести с клавиатуры команду a . out в ответ выдается следую щее сообщение : .с з ав ершает имя ф айла с прогр аммой н а С . Если вы хотите иметь исполняемый файл ( a . o ut) , вы должны его пе ре именовать. В противном случае данный файл будет заменяться новым файлом а . out всякий раз, когда вы компилируете какую-либо программу. А что можно сказать об объе ктном коде? Компилятор создает файл объе ктного ко­ да , имею щий то же базовое имя, что и исходный файл, но с расширением . о . В нашем примере файл объектного кода получает имя inform . o , но вы его не найдете , по­ скольку компоновщик удалит е го , как только построение ис полняемой программы бу· дет заверше но . Однако если первоначальная программа использует более одного ис· ходного файла , файлы объектного кода будут с охранены . Когда дале е в этой книге мы будем рассматривать программы с множеством исходных файлов, вы убедитес ь , что это была неплохая идея. Предварител ьные сведения опера цион ная система Lin ux Linux представляет с о б о й широко рас простране н­ ную Uniх-подоб ную операцио нную с истему с откры­ тым исходным кодо м , которая работает на различных платформах, вклю чая IBM и M a c into s h . Подготовка С-программы в с реде Linux мало в чем отличается от подготовки в с реде с истемы Unix, за исклю че ние м то­ го , что вам придется вос пользоваться общедоступным и б е с платны м компилятором языка С с име н е м gc c, предо ставляе мым G NU . Команда компиляции имеет следую щий вид : g c c inform . c О б р атите внимание на то, что инсталляция g c c производится п о желанию пользо вателя во вре мя уста­ новки с истемы Linux, таким образом, вам (или кому-то другому) придется устанавливать компилятор gc c , е сли он не был до этого инсталлирован. Как пра вило , при инсталляции с оздается и пс евдоним с е , ука зываю щий на компилято р gcc, так что в командной строке можно использовать с е вместо gcc. Более подроб ная инфор­ мация о g c c , вклю чая с ведения о новых ве рсиях, дос­ тупна по адресу: http : / /www . gnu . org/ s o ftwar e / g c c / gcc . html 39 Введите исходный код Текстовый редактор Компилятор За пустите программу, введя с клавиатуры имя файла а . out Ри с. 1 . 5 . Подготовка про­ граммъ� на мъ�ке С в среде опера-ционной системъt Unix Интегр и рованная среда разработки ( Windows ) Компилятор языка С не является частью стандартного пакета операционной сис­ темы Window s , так что , возможно , у вас появится необходимость получить и устано­ вить этот компилятор. Всего лишь нес колько поставщиков, в числе которых такие компании, как Microsoft, Borland , Me trowerks и D igital Mars, могут предложить с реды IDE (интегрированная среда разработки) для Window s . (В настоящее время б ольшин­ ство из них представляют собой комб инированные компиляторы языков С и С++.) В с е они имеют в своем составе быстродействую щие инте грированные среды , позволяю­ щие компилировать С-программы. Клю чевая особенность заключается в том, что каж­ дая из этих сред имеет встроенный редактор , которым можно пользоваться для напи­ сания программ на С . Каждая ШЕ-среда предлагает систему ме ню , которые позволяют именовать и сохранять файлы исходных кодов, а также компилировать и запускать на выполнение программы, не покидая для этого с реду. Каждая IDE-c peдa возвращает вас обратно в редактор, е сли компилятор обнаруживает какие-либо ошиб ки, при этом указываются строки программы , содержащие ошиб ки. С реды ШЕ для Windows поначалу могут показаться устрашающими в силу того , что предлагают целый набор -целей, то е сть операционных сред, в которых программа бу­ дет ис пользоваться . Например , они могут предложить следую щий выбор: 1 6-разряд­ ная программа для Windows , 3 2-разрядная программа для Windows , файл библиоте ки 40 Гл ава 1 DLL (Dynamic-Link Library - динамически подклю чаемая библиотека ) и так далее. Мно­ mе из целей предусматривают использование графического интерфейса Windows. Что­ бы осуществить эти (а также и другие) выб оры, обычно с о здается проект, куда добав­ ляются имена исходных файлов, которые должны использоватьс я . Конкретные де й­ ствия зависят от ис пользуе мого программного продукта. Как правило, вы сначала используете меню File ( Файл) или Project (Прое кт ) для создания проекта . При этом важно выбрать правильную форму прое кта. Приме ры , приводимые в этой книге , но­ сят общий характер и служат иллю страцией выполне ния программы в среде команд­ ной строки. Различные среды IDE для Windows предлагают один или не сколько вари­ антов, удовлетворяющих этому не слишком требовательному условию . Например, Mi­ crosoft Visual С 7 . 1 предлагает вариант Win3 2 Console Application. Для Metrowerks CodeWarrior 9.0 выбирайте с начала Win3 2 С Stationery, а зате м С Console Арр или WinSIOUX С Арр (последний вариант подде рживает качественный пользовательс кий инте рфе йс ) . Что кас ается других систе м, то желательно найти вариант, использую­ щий такие термины , как D O S ЕХЕ , Console или Character Mode exec utaЬ le . В этих ре­ жимах ваша исполняемая программа будет выполняться в консольном окне . После создания проекта нужного типа вос пользуйте сь меню IDE , чтобы открыть новый файл с исходным кодом . В б ольшинстве программных продуктов это делается через меню File. Возможно , для добавле ния исходного файла в проект потребуется выполнить до­ полнительные действия. Поскольку среды IDE для Windows обычно рассчитаны на работу с языками С и С++, вы должны указать, что хотите выбрать С . В не которых программных продуктах, например, Metrowerks CodeWarrior, для этого ис пользуется тип прое кта. В других продуктах, таких как Microsoft Visual С + + , для этого служит расшире ние файла с . В то же вре мя большая часть программ на С работают и как программы на языке С + + . В ы можете столкнуться е ще с одной проблемой: окно , в котором отображается процесс выполнения , исчезает с экрана сразу после того , как программа завершаетс я . Если это имеет место , можете заставить программу остановиться до тех пор, пока не будет нажата клавиша <Enter>. Чтобы сделать это , поместите следую щую строку в ко­ не ц программы непосредственно перед оперетором r eturn: . ge tchar ( ) ; Эта строка считывает нажатие клавиши, поэтому программа будет ожидать нажа­ тия клавиши <Enter>. Иногда , в зависимости от того , как функционирует программа , она может ожидать нажатие любой клавиши. В этом случае следует воспользоваться функцией getch ar ( ) дважды : ge tchar ( ) ; ge tchar ( ) ; Например, е сли последне е , что сделала программа , было приглашение ввести свой ве с , вы набираете на кла виатуре с вои кило граммы, а затем нажимаете клавишу <Enter>, чтобы ввести эти данны е . Программа считывает значение ваше го вес а , пер­ вая функция getchar ( ) прочитает нажатие клавиши <Ente r>, а вторая getch ar ( ) за­ ставит программу остановиться до тех пор , пока снова не будет нажата <Enter>. Если сейчас вы еще не можете оце нить вс ех пре имуще ств этого приема , вы непре менно сделаете это после того , как более подробно изучите ввод данных в С . Предварител ьные сведения 41 И хотя различные среды IDE в принципиальных вопросах мало чем отличаются друг от друга , детали меняются от одного программного продукта к другому, а в рамках линейки одного программного продукта - от версии к версии. Вам придется не много поэкс периментировать, чтоб ы изучить, как работает ваш компилятор . Кроме того, возможно , придется обратиться за советами к с правочникам или поработать с онлай­ новым руководством . Компиляторы DOS для персональных ком пьютеров IBM РС По мнению многих, работать в DOS на ПК сегодня не модно , те м не менее, это один из возможных вариантов для тех, кто владеет компьютерами с ограниченными ресурсами и ограниченным бюджето м , а также для тех, кто предпочитает раб отать с более простой операционной с истемой, без крас очных эффе ктов оконной с реды . Многие среды IDE для Windows предоставляют инструментальные средства команд­ ной строки, об еспечивая возможность программировать в среде командной строки DOS. Компилятор Comeau С/С++, который доступен во многих опе рационных с исте­ мах, в том числе и в не которых вариантах Unix и Linux , имеет версию командной строки D O S . Кроме того , существуют компиляторы языка С, используе мые в бесплат­ ных и платных программных средствах, которые работают под D O S . Например , в прое кте G NU существует верс ия компилятора gcc для D O S . Файлы исходных кодов должны быть те кстовыми файлами, н о не докуме нтами текстового процес сора. (Документы текстового процессора с одержат дополнитель­ ную информацию о шрифтах и форматировании. ) Вы должны ис пользовать тексто­ вый редактор, например, Windows Notepad или программу ED IT , которая поставляет­ ся вместе с некоторыми верс иями DOS. Можно пользоваться и текстовым процессо­ ром, если с помощью пункта меню Save As (С охранить как) сохранять файл как текстовый. Файл долже н иметь расширение _ с . Некоторые текстовые процес соры ав­ томатически добавляют расшире ние - txt к именам текстовых файлов . Если это про­ изойдет с вашим файлом, вам придется поменять е го имя , заменив txt на с. Компиля­ торы языка С для ПК об ычно , но не вс егда, с оздают промежуточный объектный файл с рас ширение м - ob j . В отличие от компиляторов С в Unix, компиляторы С об ычно не удаляют этих файлов после того , как закончат работу. Существуют компиляторы , ко­ торые генерируют файлы на языке ассе мблера с рас ширением . asm или используют свой собственный формат. Некоторые компиляторы автоматичес ки запускают компоновщик по окончании компиляции; другие же могут потребовать, чтоб ы вы запускали компоновщик вруч­ ную . Компоновка завершается создание м ис полняемого файла , при этом к первона­ чальному базовому име ни файла с исходным кодом доб авляется рас ширение . Е ХЕ . Например, компиляция и компоновка файла исходного кода с именем concr e t e . с порождают файл с именем concr et e . е х е . Н е которые компиляторы предлагают воз­ можность построе ния исполняемого файла с именем concr et e . com. В любом случае вы можете запустить программу на выполнение , введя с клавиатуры б азовое имя фай­ ла в командной строке: C : >concr ete 42 Гл ава 1 Работа с язы ком с в системах Maci ntosh Наиболее широко известным компилятором языков С/С++ в системах Macintosh яв­ ляется компилятор Metrowerks CodeWarrior. (Версии CodeWarrior в системах Windows и Macintosh имеют очень похожие интерфе йсы . ) О н предоставляет среду ШЕ, в основу которой положена концепция проектов, подобная той, какую вы найдете в компиляторе для Windows. Начните с выбора пункта New Project (Новый проект) в меню F i le. Вам бу­ дет предоставлена возможность выбора типа проекта. Для недавних версий компилято­ ра CodeWarrior используйте вариант Std С Console . (В различных версиях CodeWarrior имеются разные пути для этого выбора.) Вам, возможно, придется выбирать между вер­ сией 68КВ (для процессоров типа Motorola 680х0 ) , версией РРС (для процессоров PowerPC ) и версией Carb on (для OS Х) . В новом проекте присутствует не большой файл исходного кода как часть началь­ ного проекта . Можете откомпилировать и выполнить эту программу, чтобы уб едиться в корректности установки с истемы . Я зыковые стандарты В настояще е вре мя доступно множество реализаций языка С . В идеальном случае , когда в ы пишете программу н а С , о н а должна работать одинаково н а любой реализа­ ции при условии, что в ней не используется код, спе цифичный для конкретной маши­ ны . Чтобы добиться этого на практике , различные ре ализации должны соответство­ вать обще признанному стандарту. С начала для языка С не было никакого стандарта . С другой стороны , обще при­ знанным стандартом служило первое издание книги Брайана Кернигана и Денниса Ритчи Язъtк программирова'Ния С (в настоящее вре мя доступно второе издание этой кни­ ги, выпуще нное издательс ким домом "В ильяме" в 2006 году) ; этот стандарт получил обозначе ние K&R С или classU: С (клас сичес кий С ) . Приложе ние Б настоящей книги можно рассматривать как руководство по реализациям языка С . Создатели компиля­ торов, например, утверждают, что предлагают полную ре ализацию K&R. Однако, хо­ тя в упомянутом приложении дано определение языка С , в нем не описана стандарт­ ная библиотека С . Язык С завис ит от с воей библиотеки в б ольшей степени, не жели другие языки, поэтому возникает необходимость также и в разработке стандарта на библиотеку. В условиях отсутствия какого-либ о официального стандарта , библиоте ка , поставляемая вместе с реализацией С для Unix, становится стандартом де-факто . Первый станда рт ANSI/ ISO с По мере того как язык С развивался и получал все более широкое применение в раз­ личных системах, сообщество пользователей языка С ощутило острую потребность во всеобъемлю щем, современном и строгом стандарте . Чтобы удовлетворить эту потреб­ ность институт ANSI (American National Standards Institute - Национальный институт стандартизации США) образовал в 1 983 году специальный комитет (X3J l l ) , целью ко­ торого была разработка нового стандарта , который формально был принят в 1 989 году. Этот новый стандарт (ANSI С) определяет как сам язык, так и стандартную библиотеку С . О рганизация ISO (International O rganization for Standardization - Международная ор­ ганизация по стандартизации) приняла стандарт языка С (ISO С ) в 1 990 году. Предварител ьные сведения 43 ISO С и ANSI С по сути дела явля ю тся одним и тем же стандартом. О кончатель­ ную версию стандарта ANSl/ISO часто называют С89 (именно в этом году институт ANSI утве рдил этот стандарт) или С90 (поскольку в этом году данный стандарт был утве ржде н ISO ) . Пос кольку версия ANSI появилас ь первой, часто ис пользуется тер­ мин АNSI С. Комитет X3Jl 1 выдвинул несколько руководящих принципов. Возможно , с амым инте ре сным был принцип, гласящий: " сохраняйте дух языка С" . Комитет пе речислил следую щие иде и, которые выступают в качестве выражений этого духа : • Дове рять программисту. • Не мешать программисту делать то , что он считает необходимым . • Не увеличивать язык и сохранять его простоту. • Предусматривать только один с пособ выполнения операции. • Делать операцию б ыстроде йствую щей, даже если при этом не гарантируется переносимость. С оглас но последнему пункту, комитет имел в виду, что реализация должна опреде­ лить конкретную опе рацию через действия , которые проявляют себя наилучшим об­ разом на целевом компьютере , а не пытаться любой ценой навязать абстрактное уни­ версальное определение . Вы будете сталкиваться с приме рами этой философии по мере изучения языка . ста ндарт С99 В 1 994 году начались раб оты по пе рес мотру этого стандарта , в ре зультате че го поя­ вился стандарт С99. О бъедине нный комитет ANSI/ISO , известный в те време на как комитет С9Х, подтве рдил базовые принципы стандарта С 9 0 , в том числе принцип ма­ лых размеров и простоты языка С . Цель, озвученная комитетом, с остояла в том, что­ бы не добавлять в язык новые свойства за исклю чением тех, которые не обходимы для достижения новых целей, поставленных пе ред языком . Одной из этих целей была поддержка интернационализации, например, разработка спос обов раб оты с наборами интернациональных символов. Второй целью была " кодификация существую щих ме­ тодов устране ния очевидных дефектов" . Таким образом, при не обходимости переноса С на 64-разрядные процессоры комитет положил в основу дополнений к стандарту опыт тех, кто решал эту задачу в ре альных условиях. Третьей целью было повышение пригодности языка С для выполнения критичес ких вычислений в рамках научных и техничес ких проектов. Три указанных выше момента - инте рнационализация , исправле ние дефе ктов и повыше ние вычислительной поле зности - были основными причинами, которые обу­ словили внесение изменений. О стальные планы, предус матривавшие изме нения , бы­ ли консервативными по своей природе , например, минимизация несоответствий стандарту С90 и языку С++ и сохране ние конце птуальной простоты языка . В формули­ ровке документа, принятого комитетом, с казано: " . . . комитет будет удовлетворен, если С++ станет болъшим и амб ициозным языком" . В результате изме не ния стандарта С99 позволяют сохранить естественную суть языка С , а с а м язык С остается экономным, чистым и эффективным . В этой книге рас­ сматриваются многие изменения, вне сенные в С 9 9 . 44 Гл ава 1 Поскольку в настоящее вре мя в большинстве компиляторов не ре ализованы все изме не ния стандарта С99, может случиться , что вы не найдете не которых из них в своей с истеме. Либо вы , возможно , обнаружите , что некоторые свойства С99 станут доступными только тогда, когда вы измените настройки компилятора . Н а зам етку ! В этой книге испол ьзуется термин ISOIANSI С, обознача ющи й сво йства, общие для обоих стандартов, и С99 для ссыл ки на новые свойства. Иногда будут встречаться ссыл ки на станда рт С90 (например, при обсуждении, сопровожда ющем первое по­ явление того ил и иного свойст ва в языке С). К ак орган и зована эта книга Суще ствует множество способов организации информации. Один из наиболее простых подходов заклю чается в том , что сначала представляется все , что касается темы А, затем все , что имеет отношение к теме В , и так дале е . Такой подход суще ст­ венно облегчает с с ылки, поскольку вы можете найти всю информацию , кас аю щую с я данной темы , в одном ме сте . В т о ж е время это не самый лучший вариант при изуче­ нии предмета . Наприме р, если вы начали изучать английский язык с запоминания всех существительных, ваши возможности выражать мысли будут жестко ограничены . Разумеется, вы можете указывать на объе кт и выкрикивать его название , но в то же вре мя вас будут значительно лучше понимать окружающие , если вы выучите не сколь­ ко суще ствительных, глаголов, прилагательных и проче го , а также нес колько правил, показываю щих, как эти элементы языка соотносятся друг с другом. Чтобы сбаланс ировать подачу материала , в данной книге используется спирале­ видный подход, который состоит в том, что в начальных главах начинается изучение сразу нескольких тем с последую щим возвратом к более полному их обсуждению в по­ следую щих главах. Например , понятие функции играет важную роль в понимании все­ го языка С. Таким образом, не сколько начальных глав с одержат краткие обсуждения функций, поэтому, когда вы будете читать полное описание функций в главе 9, вам бу­ дет значительно легче осваивать тонкости применения функций. Аналогично , в на­ чальных главах дается упрощенное предварительное опис ание строк и циклов, так что вы с можете пользоваться этими полезными инструментальными средствами еще до того , как вы изучите их во всех подробностях. Соглаш ения, п ринятые в этой кн и ге Теперь мы готовы приступить к изуче нию самого языка С . В этом разделе рассмат­ риваются некоторые соглашения , используе мые для представле ния мате риала книги. Ш рифты и на черта н ие Для текстов программ, входных и выходных данных используется моноширинный шрифт, который приблизительно напоминает то , что вы можете увидеть на экране или в листингах выходных данных. Ниже показан пример : #i nclude < s tdi o . h> int main ( void) Предварител ьные сведения 45 print f ( " Б e тo н содержит п е сок и цеме нт . \ n " ) ; r eturn О ; Тот же моноширинный шрифт применяется для представления терминов, связан· ных с кодом, например, main ( ) , и име н файлов, таких как s tdio . h. В книге также ис· пользуется моноширинный шрифт с курсивным начертанием для обозначения запол· нителей, которые должны заме няться конкретными значениями. Примером может служить модель объявления: имя типа имя переменной; _ _ В данном случае вы можете , например, заменить имя_ типа на int, а имя_ переменной ­ на z eb r a count. выходн ые да нные п рограммы В ыходные данные компьютера представляются в том же самом формате , а входные данные пользователя выделяются полужирным наче ртанием. Иллю страцией могут служить следую щие выходные данны е : Пожалуй с т а , вв едите название книги . Нажмите [ enter ] в начале строки для о станов а . Му Li fe as а B udgi e Т е перь в в едите имя а в тор а . масk: Zack1es Строки, представленные моноширинным шрифтом, являются выходными данны· ми программы, а строка , выделенная полужирным начертание м - это данны е , вве· денные пользователем. Суще ствует множество способов обмена данными между вами и компьютером . Тем не мене е , мы будем полагать, что вы вводите команды с клавиатуры , а ответ компью· тера вы читаете с экрана . специальные клавиши Как правило, в ы отс ылаете строку команд , нажимая клавишу, обозначенную как <Enter>, <c/r>, <Re turn> или похожим образом. В тексте мы с с ылаемся на не е как на клавишу <Enter>. Об ычно, в данной книге считается само собой разуме ю щимся, если вы нажимаете клавишу <Enter> в конце каждой строки ввода. Тем не мене е , чтоб ы за· острить ваше внимание на не которых моментах, рассмотрим не сколько приме ров, в которых клавиша <Enter> упоминается напрямую (для ее представле ния используется обозначе ние [ e nter ] ). Квадратные с кобки означают, что вы нажимаете одну клавишу, а не вводите с клавиатуры слово en f;er. Мы также пользуемся управляю щими символа· ми, например, <Ctrl+D>. Таким способом обозначается нажатие клавиши <D> при уде ржании в нажатом состоянии клавиши <C trl> (или, возможно , <Control> ) . 46 Гл ава 1 системы. использова н н ые при подготовке да нной книги Некоторые ас пекты языка С , такие как объе м памяти, отводимый для хранения числа, зависят от с истемы . Когда речь идет о примерах, и мы с с ылаемся на "нашу сис­ тему" , мы имеем в виду ПК с процес сором Pentium , функционирую щий под управле­ нием операционной системы Windows ХР Professional и использую щий компилятор Me trowerks CodeWarrior D evelopm e nt Studio 9 . 2 , Microsoft Visual С ++ 7.1 (верс ия, ко­ торая поставляется вместе с Mic rosoft Visual Studio .NET 2003 ) или gcc 3 .3 . 3 . На мо­ мент напис ания данной книги поддержка стандарта С99 была неполной, и ни один из этих компиляторов не поддерживал возможности С 9 9 . Тем не менее , эти компилято­ ры отве чали большинству тре бований нового стандарта . Б ольшая часть примеров бы­ ла отлажена с помощью Metrowerks CodeWarrior D evelopment Studio 9 . 2 , установлен­ ного на машине Mac intosh G 4 . В ремя о т време ни в данной книге делаются ссылки н а выполне ние программ в сис­ теме , функционирую щей под управлением Unix. Таковой является ве рс ия Berkeley ' s BSD 4 .3 системы Unix, функционирую щая на компьюте ре VAX 1 1 / 750. Кроме того, не сколько программ прошли отладку на ПК Pe ntium , работающем под управлением Linux и использую щем компиляторы gc c 3 .3 . 1 и Comeau 4.3 .3 . Коды демонстрацион­ ных программ , описанных в данной книге , вы можете найти на WеЬ-сайте издательст­ ва Sams по адресу www s amspuЫ i s hing com и на WеЬ-с айте издательского дома "В иль­ яме" по адресу www . wi l l i amspuЫ i s hing . com. Введите номер ISBN книги (без дефи­ сов) в окне поиска и активизируйте поиск. Перейдите на страницу этой книги и загрузите код. _ _ Требования к системе У вас должен быть установлен компилятор языка С либ о в ы должны иметь доступ к такому компилятору. С раб отает на огромном множе стве различных компьютерных систе м, так что перед вами большой выбор. Убедите сь в том , что вы используете ком· пилятор языка С, предназначенный для вашей конкретной системы . Не которые из примеров в этой книге требуют подде ржки нового стандарта С99, однако большинст­ во примеров будут работать с компиляторо м , поддерживаю щим стандарт С90. Если компилятор , который вы используете , был разработан до появле ния стандартов ANSI/ISO , вам, возможно , придется достаточно часто внос ить поправки, что должно побудить вас к обновлению компилятора . Б ольшинство поставщиков компиляторов делают с кидки для студентов и препода­ вателей, и если вы попадаете в эту кате горию клиентов, внимательно изучите WеЬ­ сайты поставщиков . специа л ьн ые элементы В данной книге используются несколько специальных эле ментов, которые позво­ ляют подчеркнуть важность того или иного вопроса. Ниже показан их внешний вид и описано то , как они используются . Предварител ьные сведения 47 В рез к а Врезка содерж ит более глубокий анал из ил и дополнител ьную информа цию, которая по­ зволяет подробнее освет ить тему. Со вет Советы содержат краткие полезные рекомендации, касающиеся разрешения кон­ кретных ситуаций в программировании. Вн и м ан и е ! П редупреждения о потенциал ьных ловушках. Н а зам етку ! Нечто вроде вмест ил ища разнообразных комментариев, кото рые не подпада ют ни под одну из указанных выше категори й. Р езюм е С - это мощный и компактный язык программирования . Его широкое рас простра­ не ние объяс няется тем, что он предлагает полезные инструментальные с редства и обеспечивает эффективное управление оборудованием , а также тем , что программы на этом языке легче переносятся с одной с истемы на другую . С принадлежит к числу транслируемых языков. Компиляторы и компоновщики ( редакторы с вязей ) языка С - это программы , которые пе ре водят исходные коды на языке С в исполняе мые коды . Программирование на языке С может тре бовать значительных ус илий, может ока­ заться обременительным и приносить одни лишь разочарования, но в то же вре мя оно может стать увлекательным и захватываю щим занятием и приносить удовлетворе ние . Мы надеемся, что язык С станет для вас источником творческого удовлетворе ния , ка­ ковым он стал для нас . В оп росы для сам оконтроля Ответы на эти вопросы находятся в приложении А. 1 . Что означает пе-реиосимостъ в конте ксте программирования? 2. Объяс ните , какое различие существует между файлом исходного кода, файлом объектного кода и исполняемым файлом. 3. Что собой представляют с е мь основных этапов программирования? 4. Что делает компилятор? 5. Что делает компоновщик? 48 Гл ава 1 Уп ражн ен и я по програм м ирован и ю Мы не предполагаем, чго вы уже готовы писать программный код на С , поэтому дан­ ное упражнение концентрируется на начальных этапах процесса программирования. 1 . Вы только что были приняты на работу в компанию Mac roMuscle, Inc . (Про­ граммное обес пече ние для крупных организаций ) . Компания выходит на евро­ пейский рынок и желает иметь в с воем распоряжении программу, которая пе­ реводит дюймы в сантиметры ( 1 дюйм = 2 , 5 4 см) . Компания хочет, чгоб ы про­ грамма выдавала пользователю приглашение на ввод значения в дю ймах. Ваша задача заклю чается в том, чтобы определить цели программы и разработать проект программы (этапы 1 и 2 проце с с а программирования ) . ГЛА ВА 2 в веде н и е в яз ы к с в этой главе: • • Опера ция: • Ф ункци и : пain < > prin tf ( > • • = • • Н а п исание п ростой п рогра м м ы н а языке с созд ание ц елочисленных п еременных. присваивание и м з н а ч ен и й и отображен и е этих з н а ч ен и й н а экра н е • Сим вол н овой строки включение комм ентариев в програ м м ы . созда н и е програ м м . содержащих бол ее од н ой функции. поиск п рогра м м н ых оши бок Что такое кл ю ч ев ы е сл ова к ак выглядит программа на языке С? Пролистав эту книгу, вы найдете множе ст­ во приме ров. Возможно , вы сочтете , что программа на С выглядит нес колько странно , будучи ус ыпанной такими с имволами, как { , cp - >tort и *ptr++. Од­ нако по мере того, как вы будете углубляться в соде ржимое книги, они, а также другие ха ракте рные для С с имволы пере станут казаться странными, они станут для вас более привычными, вам даже станет трудно обходиться без них! Эту главу мы на чнем с того, что рассмотрим простую демонстрационную программу и объясним, что она делает . В то же вре мя мы уделим основное внима ние некото рым б азовым с войствам языка С . П ростой п ри м ер п рограм мы н а язы ке с Расс мотрим простой пр имер про гра ммы на языке С . Эта програ мма , представле н­ ная в листинге 2 . 1 , служит для того , чтобы заострить ваше внима ние на некоторых особенностях про гра ммирования на С. Прежде че м вы про чтете построчное пояс не­ ние к программе , ознакомьтесь с начала с листингом 2 . 1 и по пробуйте выяс нить без по мо щи ко ммента риев, что делает эта про гра мма. Л исти н г 2.1 . П рограмма fir s t _ с #incl ude <s tdio - h > i n t main ( vo i d ) { int num ; num 1; = / * простая про г р амма * / / * определить п ер еме нную с именем num * / / * присвоить з н ач ени е переменной num * / 50 Гл ава 2 printf ( " Я про с той " ) ; / * исполь зовать функцию printf ( ) * / рrintf ( " компью тер . \ n " ) ; p r i nt f ( " Mo eй любимой цифр ой являе тся % d , так как она п epвaя . \ n " , num) ; r e turn О ; Если вы думаете , что программа что·то отобразит на экране, то вы не ошиблись ! Что конкретно будет отображено н а экране, может быть н е очевидно , поэтому вы­ полните программу и ознакомьтесь с ее ре зультатами. Пре жде всего, воспользуйтесь услугами вашего любимого редактора (или "лю бимым" редактором вашего компиля­ тора ) , чтобы с о здать файл, соде ржащий текст листинга 2 . 1 . Назначьте этому файлу имя, оканчивающееся на . с и удовлетворяющее требованиям , предъявляемым к име­ нам файлов в ваше й локальной системе . Например , вы можете присвоить программе имя f i r s t . с. Т епе рь откомпилируйте и выполните программу. (См. главу 1, в которой соде ржатся об щие с веде ния по этому проце ссу.) Если все пойдет хорошо, выходные данные программы примут следую щий вид: Я про с той комп ьютер . Мо ей любимой цифрой явля е тся 1 , так к ак она перв ая . В конечном итоге этот результат не является не ожиданным , однако что с об ой представляют символы \ n и % d в программе ? Кроме того , не которые строки програм­ мы выглядят довольно странно . Самое время для поясне ний. Поясн ен ие к п рограмм е Давайте с овершим два прохода по исходному коду программы. Пе рвый проход ("Проход 1 : краткий об зор") ос вещает значе ние каждой строки и поможет вам полу­ чить общее представле ние о том , что происходит. На втором проходе ("Проход 2: де­ тали программы" ) исследуются конкретные ре зультаты и подробности, дабы вы могли глубже понять особенности программы. На рис . 2.1 обобщены все части программы на С ; он соде ржит б ольше элементов, чем использует наша первая программ а . Проход 1 : краткий обзор В этом разделе представлена каждая строка приведенной выше программы, за ко­ торой следует ее краткое описание ; в следую щем разделе (посвященном проходу 2 ) тема , поднятая в этом разделе, рассматривается б олее полно . #i nclude < s tdi o . h> +-включить другой ф айл Эта строка тре бует от компилятора вклю чить информацию , хранящую ся в файле s tdio . h, который является стандартной частью всех пакетов компилятора языка С ; этот файл обес печивает поддержку ввода с клавиатуры и отображения вывода. int maiп ( void) +-имя функции Программа на языке С с о стоит из одной или больше го числа фу'Нк1J,ий б азовых модулей программ на С . Расс матривае мая программа с о стоит из одной функции с именем main. - Введение в язык С 51 Круглые скобки показывают, что main ( ) е сть имя функции, int указывает н а то, что функция mai n ( ) возвращает некоторое целое число , а void говорит о том, что функция mai n ( ) не принимает никаких аргументов. Это детали, которые мы будем рассматривать дале е . А с е йчас просто примем i nt и void как часть способа определе­ ния функции mai n ( ) языка С, соответствую ще го стандарту ISO /ANSI. (Если в вашем распоряже нии имеется компилятор языка С , разработанный до появле ния стандарта ISO /ANSI, опустите void; чтобы в дальнейшем избе жать не соответствий, вам потре­ буется более современное программное об еспечение . ) Типичная программа на языке С � #include � int rnain (void) -- Команды препроцесс ора -- rnain () всегда является первой вы зываемой функцие й 1 � О ператоры Функция а ( ) 1 � О ператоры Функция Ь ( ) 1 1 О ператоры Функции представляют собой строительные блоки языка С ,. Функции всегда состоят из операторов 5 типов операторов языка С Объявления � П рисваивания Функции Управления П устой Язык е Ри с_ 2 _ 1 . Анатомия программъ1 на мъ1ке С 52 Гл ава 2 / * про с т ая прогр амма * / �комментарий С имволы / * и * / заклю чают в себе комме нтарии, то есть примечания, которые помогают понять с мысл программы . О ни предназначены исклю чительно для читателя и компилятором игнорируются . �нач ало тела функции Эта открывающаяся фигурная скобка обозначает начало оператора , составляю щего функцию . Определение функции заканчивается закрываю щей фигурной скобкой ( J ) . int num ; �оп ер а тор о бъяв ления Этот опе ратор объявляет пе ременную с именем num и уведомляет , что переме нная num имеет тип int (целое число ) . num = 1 ; О пе ратор num �оп ер а тор присв аив ания 1 ; присваивает значение 1 переменной с име не м num. printf ( " Я про с той " ) ; �оп ер а тор вызов а функции Первый оператор, использую щий функцию print f ( ) , отображает на экране фразу Я про с той, оставляя курс ор в той же строке. Используемая зде с ь функция printf ( ) является частью стандартной библиотеки С . О на носит название ФУ'НЖ'ЦUU, а ис пользо· вание функции в программе называется в'Ызовом фуuк'Ции. printf ( " компью тер . \n " ) ; �еще один опера тор в ызова функции Следую щий вызов функции print f ( ) припис ывает слово компьютер в конец напе· чатанной предыдущей фраз ы . \ n - это код , указываю щий компьютеру начать новую строку, то есть переме стить курсор в начало следую ще й строки. printf ( " Мо ей любимой цифр ой являе тся % d , так как она п ервая . \ n " , num) ; Последне е использование функции printf ( ) приводит к печати значе ния пе ре­ менной num (которое равно 1 ) , вставленной во фразу, заключенную в кавычки. Код % d указывает компьютеру, где и в какой форме пе чатать значение num. r e turn О ; �оп ер а тор во звр ата Функция С может предоста вить , или в()Звратитъ, число в объект, который ее вызвал. П о ка что расс матривайте эту строку как одно из треб о ваний стандарта ISO /ANSI С , регламе нтирую щих ис пользование функции main ( ) . �ко нец пр огр аммы Как мы и обещали, программа оканчивается закрывающей фигурной скобкой. Проход 2 : детал и п рогра м м ы Теперь, когда в ы вкратце ознакомились с листингом 2 . 1 , расс мотрим представлен· ную в нем программу подробнее . Мы с нова будем расс матривать отдельные строки программы , но на этот раз используем каждую строку кода в качестве отправной точ· ки для боле е глубокого ис следования деталей, об означае мых соответствую щими ко­ дами, и как основу для того , чтобы выработать более общий взгляд на особе нности программирования на С . Введение в язык С 53 Директивы #include и заголовочные файлы #i nclude < s tdi o . h> Именно с этой строки начинается программа. Результат выполне ния строки #incl ude < s tdi o . h> такой же, как если бы вы ввели с клавиатуры содержимое файла s tdio . h в ваш файл в той точке , в которой появляется строка #include. По сути дела , это операция вырезания и вставки. Директива incl ude (вклю чить файлы ) представ­ ляет собой удобный спос об с о вместного использования информации, который при­ меняется во многих программах. О ператор #incl ude представляет собой пример директивъ; препро'Цессара в С. В общем случае компиляторы языка С выполняют некоторую подготовительную работу над ис­ ходным кодом перед компиляцией; это называется предварительной обработкой. Файл s tdio . h поставляется как часть всех пакетов компиляторов С. Он содержит информацию о функциях ввода и вывода , таких как print f ( ) , и предназначен для ис­ пользования компилятором . Его имя происходит от "stnndard input/ou tpu t header" (заго­ ловочный файл стандартного ввода-вывода ) . Разработчики языка С называют сово­ купность информации, которая помещается в верхней части файла заголовком ( header) , а реализации С обычно поставляются с множеством заголовочных файлов. По большей части заголовочные файлы содержат информацию , ис пользуемую компилятором для с о здания конечных ис полняе мых программ. Наприме р, они могут определять константы или указывать имена функций и способы их ис пользования . Однако фактический программный код функции представляет собой библиотечный файл предварительно откомпилированного кода , а не заголовочный файл. В обязан­ ности компоновщика, который является компонентом компилятора, входит поиск не­ обходимого библиотечного кода. Короче говоря, заголовочный файл оказывает по­ мощь в правильной компоновке вашей программы . Стандарт ISO /ANSI С сформулировал тре б о вания , какие заголовочные файлы должны быть предоставлены . Для одних программ необходимо вклю чать файл s tdio . h, для других программ он не нужен. Документация конкретной реализации языка С должна вклю чать описание функций из библиоте ки программ на С . Такие описания функций показывают, какие заголовочные файлы нужны . Например , описание функ­ ции printf ( ) требует использования файла s tdio . h. Пропуск заголовочного файла может вообще не повлиять на выполнение не которой конкретной программы , однако не нужно на это надеяться . Каждый раз, когда в этой книге ис пользуются библиотеч­ ные функции, вы будете применять включае мые файлы , определе нные стандартом ISO /ANSI для этих функций. Почему ввод и вывод не являются встроенными В озможно , вас удивит, почему такая важная информация , как ввод и вывод, не вклю чаются в программу автоматиче ски. Один из ответов с остоит то , что не все про­ граммы используют пакет ввода-вывода , а один из принципов языка С запрещает пе­ регружать программу ненужными функциями. Этот принцип экономного ис пользова­ ния ресурсов делает язык С особо удобным для написания встроенных программ, на­ приме р, программного кода для проце с с ора, управляющего автоматизированной подачей топлива . Гл ава 2 54 Между прочим , строка с директивой #i nclude вообще не является опе ратором языка С ! Символ # в первой строке означает, что эта строка должна обрабатываться препроцес сором до передачи ее компилятору. Далее вы столкнете с ь с различными приме рами команд пре процессора, а в главе 16 эта тема будет рас сматриваться более подробно . Функция main () int main ( void) В этой строке программы объявляется функция с именем main. Действигельно , main - более чем простое имя , однако это был единстве нно возможный выбор . Про­ грамма на языке С (с некоторыми исклю чениями, на которых мы с е йчас не будем ос­ танавливаться ) всегда начинается с выполнения функции main ( ) . Вы вс егда можете выб рать имя для лю б ой другой функции, однако, чтобы начать выполне ние програм­ мы, в ней обязательно должна присутствовать функция mai n ( ) . А для чего нужны скобки? Они идентифицируют main ( ) как функцию . Вскоре мы приступим к изуче нию функций. Сейчас просто следует знать, что функции представляют собой базовые моду­ ли программы на языке С. int - это возвращаемый тип функции main ( ) . Это значит, что тип значения , которое может вернуть функция main ( ) , является целочисленным. Ве р­ нуть куда? Да в операционную систему (этот вопрос мы рассмотрим в главе 6 ) . В круглых скобках, которые следуют за именем функции, обычно находится ин­ формация , передавае мая функциям. В условиях рассматриваемого простого примера ничего не передается , поэтому в скобках содержится слово void. (В главе 1 1 вводится еще один формат, позволя ю щий передавать информацию в функцию main ( ) из опе­ рационной с истемы . ) Если просмотреть старые программные коды н а С , довольно-таки часто можно встретить программы, которые начинаются со следую ще го формата : main ( ) Стандарт С90 неохотно смирился с этой формой, а стандарт С99 вовсе ее не при­ знает. Следовательно , даже если имеющийся у вас компилятор позволяет вам сделать это , у вас ничего не получится . В ы можете также столкнуться с о следую ще й формой: void mai n ( ) Некоторые компиляторы допускают такую форму, но ни один стандарт не упоми­ нает их даже в каче стве возможного варианта . В с илу этого обстоятельства компиля­ торы не должны вос принимать эту форму, и не которые из них не воспринимают. Т а­ ким образом, ориентируйте сь на стандартную форму, и вы не будете иметь проблем при перенос е с одного компилятора на другой. Комментарии /* пр о с т ая пр о г р амма */ Части программы , заклю ченные в символы / * * / , представляют собой комме нта­ рии. Комментарии существе нно обле гчают понимание ваше й программы всеми, кто ее изучает (в том числе и вам ) . Одно из полезных свойств комментарие в в языке С за­ клю чается в том, что они могут быть поме ще ны в любом ме сте программы , вклю чая ту Введение в язык С 55 ж е строку, где находится код , для пояс не ния которого они предназначаются. Более пространный комментарий может рас полагаться в собственной строке и даже зани· мать несколько строк. В с е , что находится между открывающей ( / * ) и закрываю щей ( * /) последовательностями, компилятором игнорируется . Ниже представлены при· меры правильных и неправильных форм комментариев: /* /* Э то к оммент арий н а С . */ Э то т комм е н т арий р а спо ложен в двух стр о к ах . */ /* Вы также мож е т е с д е л а т ь э то . */ /* Т акой комм е н т арий недопустим ввиду о т с у т с твия мар к е р а око нч ания . Стандарт С99 позволяет использовать еще один стиль комментариев, а именно тот, который предлагают языки С++ и Java . Новый стиль предполагает применение символов / / для представле ния комментария , умещающегося в одной строке: // Данный комм е н тарий умеща е т ся в о д н о й стр о к е . i n t r i gu e ; / / К оммент арий можно т акже поме стить сю да . Поскольку конец строки означает конец комментария , этот стиль требует маркера комментария только в начале . Новая форма комментариев решает потенциальную проблему, характер ную для старой формы комментария . Предположим, что имеется следую щий программный код : /* Я н адеюс ь , ч то э то т в ариант р а бота е т . */ х = 100; у = 200; / * Т е п ер ь попр о бу ем сд е л а т ь ч то -нибуд ь еще . */ Предположим , что вы решили удалить четвертую строку, но случайно стерли так· же и третью строку (марке р * / ) . В результате получаем такой код : /* Я н адеюс ь , ч то э то т в ариант р а бота е т . у = 200; / * Т е п ер ь попр о бу ем сд е л а т ь ч то -нибуд ь еще . */ Теперь компилятор с о единяет в пару марке р / * из первой строки и маркер * / в четвертой строке, объединяя все четыре строки в один комментарий, вклю чая строку, которая, по предположению , была частью программного кода. Поскольку форма / / охватывает не более одной строки, это н е приводит к проблеме "исче зновения кода". Некоторые компьютеры не подде рживают эту возможность, предус мотре нную стандартом С99, другие могут потре бовать изме нить параметры компилятора , чтобы стали доступными функции, предусмотренные стандартом С99. Мы считаем, что излишняя принципиальность может оказаться обременительной, поэтому в книге ис пользуются обе формы комментарие в. Гл ава 2 56 скобки, тело функции и блоки { В листинге 2.1 фигурные с кобки определяют границы функции main ( ) . В общем случае все функции языка С ис пользуют фигурные с кобки для обозначе ния начала и, соответстве нно , конца тела функции. Их наличие обязательно, так что не заб ывайте их проставить в нужных ме стах. Для этих целей можно применять только фигурные скобки ( { } ) , но не круглые ( ( ) ) или квадратные ( [ ] ) . Фигурные скобки также могут использоваться для организации операторов в блоки или модули в рамках функции. Если вам приходилось раб отать с языками Pascal, ADA, Modula-2 или Algol, вы заметите , что фигурные с кобки подобны операторам b e g i n и end в упомянутых языках. Об ъявления i n t num ; Эта строка программы называется оператором обт;.явлеиия. О пе ратор объявления от· носится к числу наиболее важных возможностей языка С . В рас сматриваемом примере объявлены два объе кта. Во-первых, где·то в функции у вас имеется перемснн,ая с именем num. В о-вторых, int объявляет num как целое число , то есть число без десятичной точ· ки, или без дробной части. ( i n t представляет собой пример типа да11,11,ъ1х.) Компиля· тор ис пользует эту информацию для того, чтоб ы выделить в памяти подходящее про· странство для переменной num. Точка с запятой в конце строки показывает, что дан· ная строка является оператором, или командой, языка С . Точка с запятой является частью этого оператора, а не просто разделителем между операторами, как, напри· мер, в языке Pascal. Слово int представляет собой хлю-чевое слово языка С, обозначающее один из ос нов· ных типов С . Клю че вые слова - это слова , используе мые для построения языковых конструкций, и вы не можете употре блять их в других целях. Например, вы не можете использовать int в качестве имени функции или пе ременной. Однако эти ограниче· ния , налагаемые на применение клю че вых слов , не выходят за рамки конкретных языков, и вы вполне можете дать имя "int" своему домашнему питомцу. (Правда, мест· ные обычаи или законы могут запрещать подобное . ) Слово num в данном примере является иден,тификатоfJом, т о е сть име не м , которое вы выбираете для переменной, функции или какого-то другого логического объе кта. Таким образом, объявле ние с о единяет конкретный иде нтификатор с конкретной яче йкой в памяти компьютера и при этом устанавливает тип информации или тип данных, которые будут храниться в этой ячейке . В языке С все пе ременные должны быть объявлены до того, как они будут ис пользо· ваны . Это значит, что вы должны составить с писки всех пе ременных, которые вы ис· пользуете в программе, и указать, к какому типу данных принадлежит каждая пе ре· менная . О бъявление переменных считается хорошим тоном в программирования , а в языке С оно обязательно. Введение в язык С 57 Традиционно , язык С требует, чтоб ы переменные были объявлены в начале того или иного блока , при этом объявлениям не могут предшествовать никакие другие ти­ пы операторов. Таким образом, тело функции mai n ( ) может иметь следую щий вид : int main ( ) / / тр адици о нные пр авил а { int do or s ; int do gs ; doo r s = 5 ; dogs = 3 ; / / др у г и е о п ер а тор ы Стандарт С99, обобщая опыт работы с языком С + + , теперь позволяет поме щать объявления в лю б ом месте блока. Тем не мене е , вы все еще должны объявлять пе ре­ менную , прежде чем ее использовать в пе рвый раз . Следовательно, е сли ваш компиля­ тор подде рживает эту возможность, ваш программный код может принять следую щий вид: int main ( ) 1 1 пр авила стандарта С 9 9 { 1 1 н е скол ь ко опер а торов int do or s ; doo r s = 5 ; / / п е р в о е и спо л ь з о в ани е п ер еменной door s / / дал ь н ейши е опер аторы int do gs ; dogs = 3 ; 1 1 п е р в о е и спо л ь з о в ани е п ер еменной dogs // о с т ал ь ные опер а торы В целях большей совместимости с более ранними системами эта книга будет следо­ вать первоначальным соглашениям. (Некоторые из современных компиляторов поддер­ живают возможности, определенные стандартом С99 , только если вы задействуете их . ) В этом ме сте у вас , по-видимому, возникнут три вопроса. Пе рвый: что такое тип данных? Второй: какие у вас имеются варианты выбора конкретного име ни? Третий: а почему, собственно говоря , нужно объявлять пере менные ? Рассмотрим ответы на не­ которые вопросы. Ти п ы дан н ых Язык С использует несколько видов (или типов) данных: например, целые числа , символы и числа с плавающей запятой. О бъявление той или иной переменной как имеющей целочисленный или символьный тип позволяет компьютеру должным обра­ зом хранить, осуще ствлять выборку и инте рпретировать данны е . Множество возмож­ ных типов данных вы изучите в следую ще й главе . Вь 1 6 ор и м ен Для каждой пере менной вы должны выбрать имена , име ю щие содержательный смысл (например, chip_count вме сто х 3 , если ваша программа подсчитывает количе­ ство микросхем) . Если имени недостаточно , воспользуйтесь комментарием , чтобы объяснить, чr о представляет данная переме нная. Докуме нтирование программы в та­ ком стиле считается в программировании хорошим тоном. Гл ава 2 58 Количество символов, которые вы можете использовать для именования перемен· ной, завис ит от конкретной реализации. Стандарт С99 разре шает ис пользовать до 63 символов, если речь не идет о внешних идентификаторах (см. главу 1 2 ) , в которых распознается только 31 символ. По сравнению с тре бованиями стандарта С90, кото· рый разре шал ис пользовать, соответстве нно , 3 1 и шесть символов, это заметный про· гре с с . Более ранние версии компиляторов часто позволяли с этой целью применять максимум восемь символов. По сути дела, вы можете ис пользовать больш е е число символов, чем ука занный макс имум , однако компилятор просто проигнорирует из· быточные с имволы . В с илу этого обстоятельства, в системах с вос ьмис имвольным ограничением имена s h a k e s p e a r e и s ha k e sp en c i l расс матриваются как одно и то же имя , пос кольку первые восемь символов обоих имен с овпадают. (Если вы хотите поупражняться с име не м , состоящим из 63 символов , составьте его с ами.) В вашем распоряжении имеются буквы нижнего и верхнего ре гистров , цифры и знак подчеркивания ( ) Первым символом должна быть буква или знак подчеркива· ния . Несколько примеров допустимых и недопустимых име н представлены в табл. 2 . 1 . _ . Таблица 2.1 . допустимые и недопустимые имена Допустим ое имя Недоnус11ШМ о е имя wiggl e s $Z] ** cat2 2 cat Hot T ub Hot-Tub taxRate t ax r a t e kcab don ' t О пе рационная система и библиотека С часто ис пользуют идентификаторы с од· ним или двумя символами подче ркивания , например kcab , так что лучше избегать употреблять эти символы . Стандартные идентификаторы , начинаю щиеся с одного или двух с имволов подчеркивания , такие как библиотечные иде нтификаторы, явля· ются зарезервироваи'li:ыми. Это означает, что хотя их ис пользование не является синтак· сической ошибкой, оно, тем не менее, приводит к конфликту имен. Имена в С -чувст вителъи'Ы к регистру в том с мысле , что буквы верхнего ре гистра рас сматриваются как отличные от соответствую щих букв нижнего регистра. По этой причине иде нтифика· тор s tar s отличается от Star s и STAR S . Для упрощения интернационального использования языка С стандарт С 9 9 обес пе· чивает возможность работы с расширенным набором символов с помощью механизма UCN (Universal Character Nam e s - имена в универсальных символах) . За с правками обращайтесь в приложение Б . _ Ч еты р е п ри ч и н ы объяв лен и я п ерем ен н ых Некоторые более ранние языки программирования , как, например , пе рвоначаль· ные варианты языков FORTRAN и BASIC, позволяют использовать переменные без их объявления . Почему нельзя приме нять этот упрощенный подход в С? На то существует целый ряд причин, часть из которых приведена ниже : • Разме ще ние объявлений всех переме нных в одном месте упрощает читателю понимание того , для чего предназначена данная программа . Это особенно важ· Введение в язык С 59 но в тех случаях, когда в вашей программе используются с мысловые име на (на­ приме р, taxr at e вме сто , с каже м , r ) . Если для раскрытия смысла име ни не дос­ таточно , вос пользуйте сь комме нтарием, поясня ю щим , что представляет с обой конкретная переменная . Документирование программы в подоб ном стиле с чи­ тается хорошим тоном в программировании. • • Мысли о том, какую переменную следует объявить, заставляют вас провести не­ которое планирование , прежде чем погружаться в проце с с написания кода. Ка­ кая информация нужна для того , чтобы начать писать программу? Какие вы­ ходные данные должна выдавать программа? Как наилучшим образом предста­ вить данные? Объявление переменных позволяет избежать одной из наиболее тонких и труд­ ных для обнаружения программных ошибок, а име нно - не правильно написан­ ного имени переменной. Например, предположим, что во время ис пользования одного из языков, в котором отсутствуют объявления, вы употребили следую­ щий опе ратор: RAD I US l = 2 0 . 4 ; и в каком-то другом месте программы вы не правильно ввели с клавиатуры опе­ ратор C I RCUМ = 6 . 2 8 * RADI USl ; Вы не заметили, как вместо цифры 1 вы проставили букву 1 . Язык создает новую переме нную с име не м RADIUSl и ис пользует случайное значение (ноль или про­ сто "мусор" ) . Переменная C I RCUМ получит не правильное значение , и вы потра­ тите кучу време ни, чтобы выяснить поче му. В С это не может случиться (если, разумеется , у вас хватит ума объявить два таких похожих друг на друга имени ) , поскольку компилятор выдаст сообщение об ошибке , когда не объявленная пе­ ременная RADI USl появится в программе . • Ваша программа на С не скомпилируется , если вы не объявите переменные . Ес­ ли пе речисленные выше аргументы не возыме ют действия , вас , возможно , убе­ дит следую щая серьезная мысль. Предположим, что вам необходимо объявить переме нные , тогда где это сделать? Как уже говорилось выш е , язык С до появ­ ле ния стандарта С99 требовал, чтобы все объявления выполнялис ь в начале блока . Один из доводов в пользу этого метода состоит в том, что группирование объявлений в каком-либо одном месте облегчает понимание того , что делает программа . Разуме ется, суще ствуют аргументы и в пользу распределения объяв­ лений по всей программе , что разрешает делать стандарт С99. Идея заклю чает­ ся в том, чтобы объявлять переменную в моме нты , когда вы будете готовы при­ своить ей конкретное значение . В таких ситуациях вы едва ли забудете присво­ ить ей с о ответствую щее значение . На практике , однако, многие компиляторы еще не подде рживают упомянутое нововведе ние стандарта С99. Присваивание num = 1; В следую щей строке программы представлен оператор присваивания - одна из ос­ новных операций языка С . 60 Гл ава 2 В рассматриваемом приме ре эта опе рация тре бует " прис во­ ить значе ние 1 пе ременной num" . Предшествую щая е му строка int num ; резе рвирует в памяти компьютера пространство для хране ния переменной num, а строка с опе ратором присваива­ ния записывает значение в эту яче йку. Позже вы можете при­ своить переменной num другое значе ние , е сли возникнет такая не обходимость; вот поче му num называется переменной. Об ра­ тите внимание , что опе ратор присваивания назначает кон­ кретное значе ние с права налево . Кроме того , этот оператор также оканчивается точкой с запятой, как показано на рис . 2 . 2 . Функция prin tf () num = 1 ; "' s а. :i: о \V t- 111 \V s а. \V ф 111 с () о � с Ри с. 2 . 2 . OncparrwjJ присваивапия являет­ ся одпой из базовъ;х опсршций язъtка С printf ( " Я про с той " ) ; рrintf ( " компью тер . \ n " ) ; printf ( " Мо ей любимой цифр ой являе тся % d , так как она п ервая . \ n " , num) ; В о вс ех этих строках используется стандартная функция С с именем printf ( ) . Круглые скобки показывают, что print f является именем функции. Материал, заклю­ ченный в круглые с кобки, представляет с обой информацию , передавае мую из функ­ ции main ( ) в функцию printf ( ) . Например, пе рвая строка передает функции printf ( ) фразу Я про стой. Такая ин­ формация называется аргумептом или, б оле е точно , фактичес ким аргументом функции (рис . 2 .3 ) . Что делает функция print f ( ) с этим аргументом? О на просматривает все , что заклю чено в двойные кавычки, и выводит этот текст н а экран. printf ( ) printf ( " Дa эт о о бычно е уnрянств о ! \ n " ) ; Р и с . 2.3. Фупк-ция pr i n t f ( ) с аргумептом Первая строка с функцией printf ( ) являет собою приме р того, как въ�зватъ или как обратитъся к функции в С. Для этого достаточно назвать имя функции и поместить нужный аргуме нт (аргуме нты ) в круглые скобки. Когда дойдет дело до этой строки, управление передается указанной функции (p rint f ( ) в рассматриваемом случае ) . Как только функция выполнит свою задачу, управление возвращается в исходную , то есть в въ1зъ1вающую функцию , в данном примере - mai n ( ) . Чем отличается следую щая строка printf ( ) ? В этом случае в кавычках присутст­ вуют с имволы \ n, однако они не выводятся на печать ! Почему так происходит? Сим­ волы \ n означают начало новой строки. Комбинация \ n (вводится как два с имвола ) представляет один символ, получивший название символа новой строки. Для функции print f ( ) она означает " начать новую строку с крайне й левой позиции" . Введение в язык С 61 Другими словами, пе чать с имвола новой строки выполняет ту ж е операцию , что и нажатие клавиши <Ente r> на стандартной клавиатуре. Поче му не используется клави­ ша <Enter> при пе чати аргументов функции priпt f ( ) ? Да потому, что это будет вос­ принято как непосредстве нная команда вашему редактору, но не как команда, кото­ рую не обходимо сохранить в ваше м исходном коде . Другими словами, когда вы на­ жмете клавишу <Ente r>, редактор покинет строку, с которой вы работаете, и начнет новую строку. В то же время символ новой строки определяет, какой вид примут вы­ ходные данные программы при отображении на экране . Символ новой строки представляет с обой пример управляющей последовательно­ сти. Управляющая последовательность используется для представле ния с имволов , ко­ торые трудно или просто невозможно ввести с клавиатуры . Примерами таких после­ довательносте й могут служить символы \ t для представления нажатия клавиши <ТаЬ > и \ Ь - для <B ac kspace>. В любом случае управляю щая последовательность начинается с косой черты с левым уклоном ( " слеша") \ . Мы вернемся к этой теме в главе 3 . Таким образом, все это объясняет, почему три опе ратора priпtf ( ) вывели только две , а не три строки: первый опе ратор не содержит символа новой строки, зато вто­ рой и третий - содержат. Завершаю щая строка priпt f ( ) привносит еще одну странность: что будет напе ча­ тано вме сто комб инации символов % d? Вс помните , что выходные данные этой строки выглядят следую щим образом: Мо ей любимой цифрой явля е тся 1 , так к ак она перв ая . В от именно ! При печати этой строки вме сто группы символов % d появилась циф­ ра 1 , а 1 - это значе ние переме нной пum. Комбинация % d представляет собой заполни­ тель, который показывает, где должно быть распе чатано значение пе ременной пum. Эта строка подоб на следую ще му опе ратору BASIC: PR INТ "Мо ей лю бимой цифрой явля е тся " ; пum ; " , т ак как она п ервая . " Верс ия С делает немного б ольше , чем это . С имвол % уведомляет программу, что в этом месте будет напечатана переме нная, а d указывает на то , что пе ременная должна быть напечатана как десятичное целое число . Функция priпt f ( ) предлагает на выбор не сколько вариантов, вклю чая и шестнадцатеричные целые числа , и числа с плаваю­ ще й запятой. Действительно , f в priпtf ( ) является напоминанием о том , что это форматирую щая функция пе чати. Каждый тип данных имеет собственный специфи­ катор ; по мере того, как в данной книге будут вводиться все новые типы , будут также вводиться и с о ответствую щие с пецификаторы. оператор возврата r e turп О ; О пе ратор возврата является завершающим оператором рассматриваемой про­ граммы . iпt в конструкции iпt maiп ( void) означает, что функция maiп ( ) возвращает целое значе ние . Стандарт языка С требует, чтобы поведение функции maiп ( ) было именно таким. Функции С, возвращаю щие значения, делают это с помощью опе ратора возврата , состоящего из клю чевого слова r etur п , за которым следует возвращаемое значение и точка с запятой. Если вы не укажете в функции maiп ( ) оператор возврата , большинство компиляторов уведомит вас о его отсутствии, но при этом компиляция программы прерываться не будет. 62 Гл ава 2 На этом этапе вы можете считать оператор возврата в функции main ( ) че м-то не­ обходимым для обес печения логичес кой заверше нности, однако в не которых опера­ ционных с истемах, в том числе DOS и Unix, он имеет практиче ское применение . В главе 1 1 эта тема рассматривается более подроб но . С труктура простой п рограм м ы Теперь, после ознакомления с конкретным примером, вы готовы к изучению не­ скольких общих правил написания программ на языке С. Пр о qюмма состоит из с овокуп­ ности одной или нескольких функций, одна из которых обязательно должна быть назва­ на main ( ) . О писание ФУ'Н//С 'ЦUU состоит из заголовка и тела функции. Заголовок содержит операторы препроцессора, такие как #include , а также имя функции. Вы можете распо­ знать имя функции по круглым скобкам, внугри которых может быть пусто. Тело функ­ ции заклю чено в фигурные скобки ( { } ) и состоит из некоторой последовательности операторов, при этом каждый оператор завершается точкой с запятой (рис . 2 .4) . --- Команды препроцессора Имя фун кции с аргументами -­ -- � � : � � � : � : : � : � - d n int main (void) � - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 Тело Оператор объявления Оператор присваивания Оператор вызова функции -­ -­ -- int q ; q 1; printf ( " % d is neat. \ n " , q) ; return О ; = } Р и с . 2.4. У фу 'Н,К 'Ц UU имеется заголовок и тело В приме ре , приведе нном в данной главе , использовался оп ер атор о бъявлтия, объяв­ ляю щий имя и тип переменной. В нем также присугствовал оп ер атор присваива'Н,UЯ, за­ давая значение переменной. Кроме того , в не м ис пользовались три опе ратора пе чати, каждый из которых вызывал функцию printf ( ) . Эти опе раторы печати представляют собой примеры оп ер атор ов в'Ызова фу 'Н,К 'Ц UU. И , наконец, main ( ) заве ршается операто­ ром возврата. Короче говоря , простая стандартная программа на С должна быть пред­ ставлена в следую ще м формате : #i nclude < s tdi o . h> int main ( void) { опера торы r eturn О ; Введение в язык С 63 Советы касательно удобства чтения программы Написание удобочитаемых программ является хорошим тоном в программирова­ нии. Удобочитае мую программу легче понять, ее проще корректировать и модифици­ ровать. Процесс придания программе удоб очитаемого вида также помогает прове рить вашу собстве нную конце пцию того , что делает программ а . В ы уже ознакомилис ь с двумя методами повышения удоб очитаемой программы : выбор с мысловых име н пе ременных и ис пользование комме нтариев. Обратите вни­ мание на то , что оба эти метода дополняют друг друга . Если вы присвоите пере менной имя width, тогда не нужен комментарий, который говорит, что эта переме нная пред­ ставляет ширину, в то же время переменная с име не м video_routine_4 (" видеопро­ грамма 4" ) тре бует пояснения , для че го она предназначена . Суще ствует также метод, использую щий пустые строки для отделения одного конце птуального раздела функ­ ции от другого . Наприме р, в простой демонстрационной программе присутствует пус­ тая строка , отделяю щая раздел объявле ний от раздела де йствий. В языке С пустые строки не обязательны, в то же время они повышают удобочитаемость программы . Четвертый метод использует одну строку на каждый опе ратор. И снова это вопрос соглашений, повышающих удобочитаемость программ , но отнюдь не требование язы­ ка С . В С принят так называемый формат свободной формы. В ы можете разме щать не сколько опе раторов в одной строке или расположить один опе ратор в нескольких строках. Приведенный ниже программный код вполне допустим , но оче нь неудобен: int main ( void ) { i nt four ; four 4 p r i nt f ( " % d\ n " , four ) ; r eturn О ; } Точка с запятой уведомляет компилятор , где заканчивается один опе ратор и начи­ нается другой, в то же время программная логика становится более понятной, если вы будете соблюдать с о глаше ние , действую ще е в рамках примера, рассматриваемого в настоящей главе (рис . 2 .5 ) . int main (void) / * converts 2 fathoms to feet * / - И спользуйте комментарии i n t fee t , fathoms ; ------ ------ fathoms=2 ; feet=6*fathoms ; ------ Выбирайте смысловые имена И спользуйте пустые строки Один оператор на строку printf ( " There are %d feet in %d fathoms ! \n" , feet, fathoms ) ; return О ; Р и с . 2 . 5 . Приведеиие про?/юмм'ы х удобочитаемому виду 64 Гл ава 2 Ещ е оди н ш аг в исп ол ьзовани и языка С Первая демонстрационная программа была с овсем простой, и следующий приме р, представленный в листинге 2.2, ничуть не труднее . Листинг 2.2. Программа fathm_ft . с 1 1 fathm_ft . c - пр еобр а зует 2 мор ских сажени в футы #incl ude <s tdio . h > int main ( vo i d ) { int feet , fathoms ; fathoms = 2 ; fe et = 6 * fathoms ; printf ( " B % d мор ских саже нях содержи т ся % d футов ! \ n " , fathoms , f e e t ) ; printf ( " Дa , я сказал % d ф утов ! \ n " , 6 * fathoms ) ; r e turn О ; Что нового в этой программе? Код предоставляет описание программы , объявляет не сколько переменных, выполняет операции умножения и распечатывает значения двух переменных. Рассмотрим все это б оле е подробно. До ку ментирование В о·первых, программа начинается с комментария (ис пользуется новый стиль) , в котором указано имя файла программы и назначе ние программы . Этот вид докумен· тирования не требует много вре мени, зато он принесет большую помощь позднее, ко· гда вы будете просматривать сразу не сколько файлов или распе чатывать их. множественные объявления Обратите внимание , что программа объявляет две переменных, а н е использует оператор объявления для каждой переменной. Чтобы сделать это , в операторе объяв· ления отделяйте переменные друг от друга ( feet и fathoms ) запятыми. То е сть: int feet , fathoms ; и int feet ; int fathoms ; эквивалентны . Ум ножен ие В·третьих, данная про грамма выполняет умножение . О на ис пользует огромную вычислительную мощь ко мпьюте рной с исте м ы с тем, чтобы умножить 2 на 6 . В С , как и в о многих других языках програ ммирования , * представляет собой с имвол ум· ножения . Введение в язык С 65 Поэтому опе ратор fe et = 6 * fathoms ; означает " получите значение переменной fathoms , умножьте его н а 6 и прис войте ре­ зультат вычисле ния пере менной f e et" . Распечатка нескол ьких значений И , наконец, эта программа оригинальным образом использует функцию printf ( ) . Если вы откомпилируете и выполните пример , выходные результаты будут иметь примерно следую щий вид : В 2 мор ских саженях содержится 1 2 футов ! Да , я ск а з ал 1 2 футо в ! На этот раз программа делает две подстановки при пе рвом вызове функции print f ( ) . Пе рвая комбинация символов %d в кавычках была заме не на на значение первой пе ременной ( fathoms ) в списке , который следует за с е гментом в кавычках, а вторая такая комбинация была заме не на значе ние м второй переменной ( f eet) из это­ го с писка . Обратите внимание на то, что с писок переме нных, предназначенных для печати, находится в хвостовой части этого оператора , непосредстве нно за частью , за­ клю ченной в кавычки. Отметим также , что каждый эле мент спис ка отделен от ос­ тальных запятой. Второе ис пользование функции printf ( ) показывает, что распечатывае мое зна­ че ние не обязательно должно б ыть переменной, оно вполне может быть выражением наподобие 6 * fathoms , которое приводится к с о ответствую щему типу. Эта программа достаточно проста , однако она может служить ядром программы перевода морских саженей в футы . Все что для этого нужно - это прис ваивание до­ полнительных значе ний в интерактивном ре жиме ; далее в этой главе мы покажем, как это сделать. М н ожество фун кций До сих пор в программах использовались стандартные функции printf ( ) . В лис­ тинге 2.3 демонстрируется возможность внедрения в программу наряду с функцией main ( ) вашей собственной функции. Листинг 2.3. Программа two_func . с / * two_func . c - прогр амма , и споль з ующая дв е функции в одном ф айле * / #incl ude <s tdio . h > / * про т о тип ф ункции в стандар т е I SO/ANSI С * / void butl er ( void) ; int main ( vo i d ) { printf ( " Я вызыв аю двор ецкого . \ n " ) ; butler ( ) ; printf ( " Дa . Прине си т е мне ч ай и з аписыв аемые компакт-диски . \ n " ) ; r e turn О ; 66 Гл ава 2 void butl e r ( void) / * нач ало опр е деления функции * / { printf ( " Вы зво нили , сэр ? \ n " ) ; В ыходные данные будут иметь следую щий вид : Я вызыв аю двор ецког о . Вы звонили , сэр ? Да . Прин е сите мне ч ай и з аписыв аемые компакт-диски . Функция b utler ( ) трижды появляется в расс матривае мой программе . В первый раз она появляется в виде прототипа , передающего компилятору информацию о функциях, которые будут ис пользованы в данной программ е . Во второй раз она появ­ ляется в mai n ( ) в форме вызова функции. И , наконец, в данной программе представ­ лено определение функции, которое является исходным кодом самой функции. Рас­ смотрим по очереди каждое из этих трех появлений. Прототипы были введены стандартом С90, поэтому более ранние компиляторы их не распознают. (Вс коре будет дано объяс не ние , что делать, когда приходится раб о­ тать с такими компиляторами . ) Прототип - это форма объявления, которая уведом­ ляет компилятор о том , что вы используете конкретную функцию . О н также опреде­ ляет свойства этой функции. Например , пе рвое ключевое слово voi d в прототипе функции butl er ( ) указывает на то , что but l e r ( ) не имеет возвращаемого значения . (В общем случае функция может возвратить значе ние в вызываю щую функцию для по­ следую щего его использования , но but l e r ( ) этого не делает .) Второе void, то , что в указано в функции butler ( void) , означает, что функция butl er ( ) не принимает ар­ гументов. Поэтому, когда достигается место в main ( ) , в котором вызывается butl er ( ) , выполняется проверка корректности использования функции butl e r ( ) . Обратите внимание на тот факт, что клю че вое слово void употребляется в смысле " пусто" , а не в смысле "неправильно" . Ранние версии С подде рживали более ограниченную форму объявле ния функции, в которой вы могли определить только возвращаемый тип, о пуская при этом описания аргументов: void but l e r ( ) ; В боле е ранних программных кодах на С использовались объявления функций, по­ добные представленным выше , но не прототипы функций. Стандарты С90 и С99 рас­ познают ранние верс ии, но в то же время указывают на то , что их употре бление по­ сте пенно сворачивается , и с оветуют их больше не применять. Если вы имеете дело с какой-то старой программой, возможно , потребуется привести объявления старого типа к прототипам. В последую щих главах настоящей книги мы вернемся к изуче нию прототипов, объявлениям функций и возвращаемым значе ниям. Далее , вы вызываете функцию butler ( ) внутри mai n ( ) , просто указав ее имя, вклю чая круглые с кобки. Когда функция butl er ( ) завершит с вою работу, программа переходит к выполнению следую щего оператора в main ( ) . И , наконец, функция butl er ( ) объявлена точно так же , как и функция main ( ) , с за­ головком и телом, заклю ченным в фигурные с кобки. Заголовок повторяет информа­ цию , заданную прототипом: butler ( ) не принимает никаких аргументов и не возвра­ щает значения. Для компиляторов ранних версий о пустите второе void. Введение в язык С 67 Еще один момент, на который следует обратить внимание , заклю чается в том, что место вызова функции b utler ( ) в функции mai n ( ) не является ме стом определения butl e r ( ) в файле , ме сто вызова указывает, когда функция but l e r ( ) должна быть вы­ полне на . В этой программе вы могли б ы , например, поместить определение butl er ( ) над определением функции mai n ( ) , и программа выполнялась так же, то е сть функция butl e r ( ) будет выполнена между двумя обращениями к printf ( ) в функции main ( ) . Напомним, что вс е программы на С начинаются с выполне ния функции main ( ) , при этом не имеет значения , в каком месте программного файла эта функция находится . В т о ж е время на практике , как правило, в с писке функций программы н а С первой идет main ( ) , поскольку она об ычно играет роль базового каркаса программы. Стандарт С рекомендует, чтобы вы предус матривали в программе прототипы для всех функций, которые используете . Стандартные файлы in clude берут на с ебя эту обязанность в отноше нии стандартных библиоте чных функций. Например , в с оответ­ ствии с о стандартом языка С , файл s tdio . h соде ржит прототип функции printf ( ) . Заклю чительный пример в главе 6 демонстрирует способ расширения прототипиро­ вания на функции без клю чевого слова void, а в главе 9 эти функции расс матриваются более подробно . Предварител ьн ые сведения об отладке Теперь, когда в ы научились пис ать простые программы н а С , у в а с появился "шанс " допускать простые ошибки. Программная ошиб ка в английском языке обозначается словом " bug' ("дефект" ) , а выявление и исправление ошиб ок называется отл,адхой (" d е Ь ugging" ) . В листинге 2.4 представлена программа , в которой допуще но не сколько программ­ ных ошибок. Проверьте , с колько программных ошибок вы сможете обнаружить. листинг 2.4. Программа noqood . с / * nogood . c - прогр амма с ошибками * / #incl ude <s tdio . h > int main ( vo i d ) ( int n , i nt n2 , int n З ; / * В э той прогр амме до пущено н е ско л ь ко ошибок n = 5; n2 = n * n; n З = n 2 * n2 ; printf ( " n = % d , n в кв адр ате % d , n в кубе % d \ n " , n , n2 , nЗ ) r e turn О ; си нта кси ческие ошибки Код в листинге 2 .4 с одержит не сколько синтаксических ошибок. В ы совершаете синтакс иче ские ошибки, когда не следуете правилам языка С. Это можно сравнить с грамматичес кими ошибками в обычном тексте . В качестве примера рассмотрим сле­ дую щее предложение : "Быть программные ошиб ки катастрофичес кие могут" . Гл ава 2 68 В этом предложении используются правильные слова , однако порядок их следова· ния нарушен. Синтакс иче ские ошибки в языке С заклю чаются в том, что символы это­ го языка размещаются не в тех местах. Итак, какие синтакс ические ошиб ки допущены в программе n o g o o d . c ? Прежде всего , для отмечания тела функции в ней ис пользуются круглые с кобки вместо фигур­ ных, то есть правильные , в общем-то, символы языка С расставлены в неве рных мес· тах. В о-вторых, объявления должны быть такими: int n, n 2 , nЗ ; или, по крайне й мере, такими: int n ; int n2 ; int nЗ ; Далее , в этом примере опущена пара с имволов * / , не обходимая для заве ршения комментария . (Также можно было бы заменить пару / * новой формой комме нтария / / . ) И , наконец, пропуще на точка с запятой, необходимая для завершения опе ратора print f ( ) . Как обнаружить синтакс иче ские ошиб ки? Во-пе рвых, перед компиляцией вы мо­ жете просмотреть исходный код - возможно , вы обнаружите какие-либо очевидные ошибки. Во-вторых, вы можете проанализировать ошибки, найденные компилятором, так как одной из е го обязанносте й является име нно об наруже ние синтакс иче ских ошибок. В о время компиляции такой программы компилятор сообщает об обнару· же нных им ошибках, а также указывает природу и ме стоположение каждой ошибки. Однако и сам компилятор может ошибаться . Истинная синтакс иче ская ошибка может ввести компилятор в заблуждение и заставить "думать" , что он нашел другие ошибки. Например, поскольку в примере неправильно объявлены пе ременные n2 и n З , компилятор может подумать, что он нашел еще нес колько ошибок в тех местах, в которых эти пе ременные используются. Фактичес ки вместо того , чтобы пытаться ис· правлять вс е обнаруженные им ошибки сразу, вам следует исправить с начала одну или две ошибки, после чего выполнить повторную компиляцию ; вполне возможно , что при этом исчезнет и ряд других ошибок. Продолжайте в том же духе, пока программа не заработает. Еще одна распространенная причуда компилятора состоит в том, что он обнаруживает ошиб ку на строку ниже. Наприме р, компилятор может не догадать· ся , что не хватает точки с запятой, пока не наступит очередь компиляции следую щей строки. Таким образом, если компилятор жалуется о пропаже точки с запятой в стро· ке , в которой уже есть один такой знак, прове рьте предыдущую строку. Сема нтические ошибки Семантиче ские ошибки - это с мысловые ошибки. В каче стве примера рассмотрим следую щее предложение : "Б ешеная инфляция думает зеленые мысли" . С интаксиче· ских ошибок оно не соде ржит, ибо прилагательные , существительны е , глаголы и на· ре чия находятся в нужных местах, тем не мене е , само предложе ние не имеет с мысла . В языке С вы совершаете се мантиче скую ошибку, если соблюдаете все требования языка , но получаете не верный результат. В рассматриваемом примере присутствует одна такая ошибка: nЗ = n2 * n2 ; Введение в язык С 69 В данном случае предполагается , что nЗ представляет куб числа n, в то время как код вычисляет четвертую сте пень n. Компилятор не обнаруживает с е мантиче ских ошибок, поскольку правила языка С не нарушены . Компилятор не с пособен предуга­ дывать ваши истинные намерения . Так что обнаруживать ошибки такого вида придет­ ся самому. Один из с пособов заклю чается в сравне нии того , что программа делает, с тем, что вы хотите от нее получить. Например, предположим, что вы исправили син­ такс иче ские ошибки в рас сматриваемом приме ре , так что теперь программа приоб ре­ тает вид , представле нный в листинге 2 . 5 . листинг 2.5. Программа sti11Ьad . с / * stillb ad . c - прогр амма с устраненными синтаксич е скими ошибками * / #incl ude <s tdio . h > int main ( vo i d ) { int n , n 2 , nЗ ; / * В э той прогр амме е с ть семантич е ская ошибка * / n = 5; n2 = n * n; n З = n 2 * n2 ; printf ( " n = % d , n в кв адр ате % d , n в кубе % d \ n " , n , n2 , nЗ ) ; r e turn О ; В ыходные данные этой программы имеют следую щий вид : n = 5 , n в квадрате = 2 5 , n в кубе = 6 2 5 Ле гко заметить, чт о 625 н е является правильным результатом возведения числа в третью степень, стало быть, этот результат неправильный. Следую щий этап преду­ сматривает отсле живание того , как вы получили такой ре зультат. В условиях данного примера вы , возможно, с можете выявить ошибку путем инспе кции кода. В общем слу­ чае , однако, вы должны вос пользоваться более систематизированным подходом. Один из методов заклю чается в том, что вы берете на себя роль компьютера и шаг за шагом проходите по вс ей программ е . Воспользуе мся этим методом и в данном случае. Программа начинает с вое выполнение с объявления трех переменных: n, n2 и nЗ . Вы можете смоделировать эту ситуацию , нарисовать три прямоугольника и присвоить им имена переменных (рис . 2 .6 ) . Далее программа прис ваивает переменной n значе­ ние 5. С моделируйте это действие , запис ав 5 в прямоугольник n. Зате м программа ум­ ножает n на n и присваивает результат переменной n2 ; посмотрев в прямоугольник n , вы увидите, что в нем находится значе ние 5 , умножьте 5 на 5 и получите 2 5 , после че го поместите 25 в прямоугольник n 2 . Чтоб ы вос произвести следую щий оператор С ( nЗ = n2 * n2 ; ) , загляните в прямоугольник n2 и там найдете 25. Умножьте 25 на 25, получите 625, и поместите его n З . Вот оно что ! В ы возводите в квадрат n2 вместо того, чтобы умножить его на n. Итак, возможно , эта процедура избыточна для данного приме ра , однако подобного рода пошаговое выполне ние этой программы часто является наилучшим с пособом по­ смотреть, что в конечном итоге произойдет. 70 Гл ава 2 Выполнение строки в программе stillbad . с Состояние переменных П еременные распределены int n , n2 , nЗ ; n Переменной n присвоено значение 5 5; = n2 = n*n ; nЗ = n2 * n2 ; Переменной n2 присвоено значение n в квадрате Переменной nЗ присвоено значение n2 в квадрате, в то время как он должна иметь значение n * n2 � [2J [2J c:J n n2 nЗ n n2 nЗ � 0 D D � � 0 � D n n2 nЗ 0 � 1625 1 n n2 nЗ Ри с. 2.6. Трассировка программъ� состоя н ие програ м м ы Выполняя пошаговый просмотр программы вручную , отслеживая каждую перемен· ную , вы осуществляете мониторинг состояния программы . Состояние программы - это просто набор значений всех переменных в заданной точке выполнения программы. Другими словами, это моментальный снимок текущего состояния вычислений. Мы обсудили один из методов отслеживания с о стояния : самостоятельно осуще ст· вить пошаговое выполне ние программы . В программе , которая делает, с кажем, 1 0 ООО итераций, вы не суме ете справиться с этой задачей. Тем не менее, вы можете выпол· нить не сколько ите раций, чтоб ы узнать, сможет ли программа сделать то , что от нее хотите . В то же время , всегда существует возможность, что вы выполните эти шаги, как вам это нужно, а не так, как вы ре ализовали их в программе , так что попытайтесь неукоснительно приде рживаться фактического кода. Еще один подход при выявлении с е м а нтичес ких о ш и б о к заключается в р а з м е щ е ние до полнительных операторов print f ( ) в разных ме стах программы с целью текущего контроля избранных пе ре· менных в клю чевых точках программы. Наблюдение за тем , как меняются эти значе· ния , возможно , подскажет вам, что происходит. После того , как вы добьете сь, чтобы работа программы вас удовлетворила, вы можете уб рать дополнительные операторы и выполнить повторную компиляцию . Третий метод изучения состояний программы предусматривает использование от­ ладчиков. Отладчик представляет собой программу, которая позволяет выполнять другую программу в пошаговом режиме и ис следовать значения программных пе ре· менных. Отладчики характеризуются различными уровнями удобства ис пользования и сложности. Наиболее с овершенные отладчики показывают исполняемую строку ис­ ходного кода. Это о с обенно удобно при отладке программ с альтернативными путями выполнения , пос кольку легко видеть, по какому конкретному пути продвигается вы­ полне ние программы . Если ваш компилятор был установлен с отладчиком, не пожа­ лейте с е йчас вре мени на его изуче ние . Например, опробуйте его на программ е , пред· ставленной в листинге 2.4. Введение в язык С 71 К л юч евые слова и з арез ервирован н ые иденти фи каторы Ключевые слова образуют словарь языка С . Поскольку они играют в С особую роль, вы не можете их использовать, например , в качестве идентификаторов или имен пере­ менных. Многие из этих ключевых слов описывают различные типы данных, например, int. Другие , такие как i f, служат для управления порядком исполнения операторов про­ граммы. В приведенном ниже перечне клю чевых слов языка С (табл. 2.2) полужирным выделены ключевые слова , введенные в употребление стандартом ISO /ANSI С90, а кур­ сивом показаны клю чевые слова , введенные стандартом С99. ТабJJица 2_2_ КJJючевые cJJoвa языка с Клю'Ч евы е сло ва стандар та ISO/ANSI С auto break cas e char const conti nue de fault do douЫ e el s e enum e xtern f l o at for goto if i n l i ne i nt l ong r egi s t er r e s tr i ct r e turn short uns igned void signed whi l e s i zeo f s t ati c s truct switch typ ede f union Bo ol volatile Compl ex Ima gi n a ry Если вы попытаетес ь использовать какое-либо клю чевое слово, скажем, для име ни переме нной, компилятор воспримет это как синтаксическую ошибку. Суще ствуют и другие идентификаторы , так называемые зарезервированнъtе идентифик.аторъt, которые вы также не должны использовать для этих целей. О ни не вызывают синтакс иче ских ошибок, пос кольку являются допустимыми именами. Однако язык уже использует их или заре зе рвировал за собой право на их использование , так что у вас могут возник­ нуть проблемы в случае , если вы воспользуетесь ими для каких-то других целей. Заре­ зе рвированными идентификаторами являются идентификаторы , начинаю щиеся с символа подчеркивания , а также име на стандартных библиотечных функций, такие как, например, print f ( ) . К л юч евые п онятия Программирование представляет собой довольно трудное занятие . О но требует аб страктного мышле ния на уровне идей и вместе с тем учета необходимых детале й. Вскоре вы убедитес ь в том, что компилятор требует внимательного отношения к дета­ лям . Когда вы говорите с хорошо знакомыми вам людьми, вы можете использовать то или иное слово не по правилам , с овершить пару грамматических ошибок, возможно , не которые предложения оставить неоконченными, тем не менее , ваши знакомые поймут, что вы хотите с казать. Но компилятор не допускает подобных вольностей, он придерживается принципа "почти правильно - это тоже неправильно" . 72 Гл ава 2 Компилятор бессилен вам чем-нибудь помочь в конце птуальных вопросах, каковы­ ми являются указанные выш е , поэтому в данной книге мы стремимся заполнить этот проб ел, выделяя основные идеи в каждой главе . В этой главе основной вашей задачей должно быть понимание того , что с обой представляет программа на языке С . В ы можете расс матривать программу как описа­ ние желаемого поведения компьютера , которое вы формулируете сами. Компилятор выполняет поистине кропотливую работу пре образования этого ваше го описания в базовый машинный язык. (Для того чтобы вы оце нили, какую огромную раб оту выполня ет компилятор, отметим , что он с оздает ис полняемый фа йл размером в 6 0 Кбайт на базе исходно го файла размером 1 Кбайт; б ольшие объемы машинных кодов затрачиваются на пред­ ставление в машинном языке даже простейших программ на С . ) Пос кольку у компью­ тера истинный интелле кт отсутствует , вы должны представить свое опис ание в выра­ жениях , понятных компилятору; такими выраже ниями являются формальные пра­ вил а , установле нные стандартом языка С. (И хотя данный подход накладывает достаточно жесткие ограничения , тем не менее , это лучше , чем с оставлять такое опи­ сание непосредстве нно на машинном языке ! ) Компилятор принимает только т е команды, которые представлены в спе циальном формате, который мы подробно описали в данной главе . Ваша обязанность как про­ граммиста заклю чается в том, чтобы выразить свое видение того, как программа должна работать, используя средства , которые компилятор , направляе мый стандар­ том языка С , может успе шно обработать. Р езюм е Программа на языке С состоит из одного или большего числа функций С . Каждая про­ грамма на С должна содержать функцию с именем main ( ) , поскольку именно эта функция вызывается в момент запуска программы. Простая функция состоит из заголовка , за ко­ торым следует открываю щая фигурная скобка, далее следуют операторы, образующие те­ ло функции, за которой следует завершающая или закръ1ва10щая фигурная скобка. Каждый опе ратор языка С является инструкцией компьютеру и обязательно закан­ чивается точкой с запятой. О ператор объявления назначает переменной имя и опре­ деляет тип данных, которые будет храниться в этой пе ременной. Имя пере менной может служить примером идентификатора . О ператор присваивания устанавливает значение пере менной или, используя более об щий термин, выделяет ей пространство в памяти. Вызов функции запус кает на выполнение функцию , имя которой указано в обращении. По выполнении вызванной функции программа пе реходит к выполне нию оператора , следую щего за вызовом функции. Функция printf ( ) может использоваться для печати фраз и значений пе ременных. Синтакс ис языка - это набор правил, определяющий способ, пос редством которого допустимые опе раторы в этом языке группируются в единую последовательность. Се­ мантика опе ратора е сть его с мысловое значение . Компилятор позволяет вам обнару­ живать синтакс иче ские ошибки, однако с интаксичес кие ошибки проявляются в про­ грамме лишь после того , как эта программа будет откомпилирована . О бнаружение се­ мантичес ких ошибок предусматривает мониторинг состояния программы , то есть, значений всех переме нных на каждом шаге выполне ния программы. И, наконец, клю­ че вые слова образуют словарь языка С . Введение в язык С 73 В оп росы для сам оконтроля Ответы на эти вопросы находятся в приложении А. 1 . Как называются б азовые модули программы н а языке С ? 2 . Что такое синтаксическая ошиб ка? Приведите примеры синтакс иче ской ошиб­ ки в конте ксте ваш е го родного языка и языка С . 3 . Что такое семантичес кая ошибка? Приведите примеры семантичес кой ошиб ки в конте ксте ваше го родного языка и языка С . 4. Ленивец и з Индианы написал и представил вам н а одобрение следую щую про­ грамму. Пожалуйста , помогите ему исправить допуще нные в ней ошибки. include s tudio . h int main { voi d } / * прогр амма п еч атае т колич ество недель в г оду / * int s s := 56; print ( B г оду s недель . ) ; r eturn О ; 5 . Предположим, что каждый из приведе нных ниже примеров является частью не которой законче нной программы . Что рас печатает каждая такая часть? а. print f ( " Бe - e , Б е - е , Ч ерная Овечка . " ) ; print f ( "Y тебя найд е т ся шер сть для меня ? \ n " ) ; б . рrint f ( "Убир айся ! \ Н е т , тол с то е со здание ! " ) ; в. print f ( " Ч тo ? \ nH e т/ nC ума сошел ? \ n " ) ; г. int num ; num = 2 ; printf ( " % d + % d = % d " , num , num , num + num) ; 6 . Какие из следую щих слов являются ключевыми в С : main, int, function, char , =? 7. Как вы будете пе чатать значе ния words и l i n e s в форме Было 3 0 2 0 слов и 3 5 0 строк? В данном случае 3 0 2 0 и 3 5 0 представляют значения двух указанных выше переме нных. 8 . Рас смотрим следую щую программу: #include < s t dio . h> int main ( vo i d ) { int а , Ь ; а = 5; 2 ; / * строка 7 * / ь а ; / * строка 8 * / Ь ; / * строка 9 * / а printf ( " % d % d \ n " , ь , а ) ; r eturn О ; ь Каково с остояние программы после выполне ния строки 7? Строки 8? Строки 9? 74 Гл ава 2 Уп ражн ен и я по програм м ирован и ю Для изучения языка С одного чтения книг не достаточно . В ы должны попытаться написать не сколько простых программ , чтобы посмотреть, так ли гладко идет напис а­ ние программы , как это описано в данной главе . Мы дадим вам нес колько советов, од­ нако вы сами должны продумать ре шение суще ствую щих задач. Ответы на избранные упражнения по программированию вы найдете на WеЬ-с айте издательства этой книги. 1 . Напишите программу, которая использует вызов функции print f ( ) для пе чати ваше го имени и фамилии в одной строке, ис пользует второй вызов функции print f ( ) , чтобы напечатать ваше имя и фамилию в двух строках, и использует два вызова функции printf ( ) для печати вашего имени и фамилии в одной строке . Выходные данные должны иметь следую щий вид (при этом используют­ ся ваши персональные данны е ) : Ив ан Ив анов �Первый опер атор п еч а ти Ив ан �В торой опер атор п еч а ти Ив анов �В се еще в торой оператор печ а ти �Тр е тий и ч е твер тый операторы печ а ти Ив ан Ив анов 2 . Напишите программу, печатаю щую ваше имя и адрес . 3 . Напишите программу, которая пре образует ваш возраст в годах в количе ство дней и отображает на экране оба значения . На этой стадии можно учитывать только прожитые годы и не учитывать високос ные года . 4. Напишите программу, печатаю щую следую щие выходные данны е : Наш Билли хороший пар ень ! Наш Билли - хороший пар ень ! Наш Билли - хороший пар ень ! Наш Билли лучше в сех ! В этой программе в дополнение к функции main ( ) следует приме нять функции, определенные пользователем: одна из них один раз пе чатает сообщение о хо­ рошем парне, вторая печатает один раз завершающую строку. 5. Напишите программу, которая создает целочисле нную переме нную с именем to e s . Программа должна присвоить пере менной to e s значение 1 0 . Наряду с этим , программа должна вычислить, чему равно значе ние удвоенного t o e s и че му равен квадрат to e s . Программа должна напечатать все три значе ния , сде­ лав соответствую щие пометки. 6. Напишите программу, которая выдает следую щие выходные данные : Улыбай ся ! Улыбайся ! Улыбайся ! Улыбай ся ! Улыбайся ! Улыбай ся ! В программе должна быть определена функция, которая отображает строку Улыбай ся ! один раз, в то же вре мя программа может использовать эту функцию столько раз , сколько надо . 7. Напишите программу, которая вызывает функцию с именем one_thr e e ( ) . Эта функция должна напечатать слово один в одной строке , вызвать функцию two ( ) , а затем напечатать слово три в одной строке. Функция two ( ) должна отобразить слово дв а в одной строке . Функция main ( ) должна вывести фразу начать сейчас: пе ред вызо вом функции o n e _ thr e e ( ) и напе чатать порядок ! после е е вызова. Таким образом, выходные данные должны иметь следую щий вид : нач ать сейч а с : О ДИН дв а три порядо к ! ГЛА ВА 3 П редста вл е н и е да н н ых в яз ы ке с в этой главе: • Кл ю ч ев ы е сл ова: int, short, 1onq, un s iqned, cha r, f1oat, douЬ 1e B oo1 C oпp 1e x Ima qinary • , _ , _ , _ • Операция : s iz еоf • Ф ункция: s canf О • Базовые типы данных в языке с • • Отл ичия м ежд у цел оч ислен н ы м и д а н н ы м и и д а н н ы м и с плаваю щей з апятой Написание кон стант и объявлен и е перем енных из вестных ти пов Использование функций printf О и s canf О для чтен ия и з а писи значений разл и ч ных типов п рограммы раб отают с данными. В ы вводите числа , буквы и слова в компью­ те р для того , чтоб ы о н вы полнил ка кие-нибудь действия над это й информа­ цие й. Н а пр им е р , вам может потре б о ваться , чтоб ы ко мпьюте р посчитал при­ б ыль и ото б р азил на э кране отс ортиро ванный с писок виноторговце в. В это й гла ве вы будете не просто читать о данных - вы будете на практике производить с данными различные де йствия , что намного инте рес не е . В это й гла ве м ы расс матрива е м два больших с е ме йства данных: целые числа и чис­ ла с пла ва ю ще й за пято й. В языке С имеется не с колько разно видносте й этих данных. В ы уз наете о том, какие типы данных существуют, как объя влят ь данные различных типов, как и когда их пр именять. Кроме того, в ы поймете , чем ко нста нты отлича ются от пе реме нных, а приложе нные вами ус илия принесут пе рвый ус пе х - вы сможете на­ писать с во ю пе р вую инте ра ктивную про грамму. дем онстраци онная п р огра м м а И снова мы на чинае м с де м о нстрационной про гра ммы . Как и пре жд е , вы найдете не с колько новых, незнакомых детале й, назначе ние кото р ых б удет вс коре объя с нено . О б щий смысл программы долже н б ыть яс ен, поэто му по пробуйте с ко м пилиро вать и выполнить исходный код , представле нны й в листинге 3 . 1 . Для э кономии вре мени пр и вводе про граммы комме нта р ии можно о пустить. 76 Гл ава 3 Листинг 3.1 . Программа rhodium. c / * rhodium . c -- с тоимо с ть родия , в е с которого равен в ашему в е су * / #incl ude <s tdio . h > int main ( vo i d ) { / * в е с пол ь з ователя * / fl o at weight ; / * родиевый эквив алент поль зов ателя * / fl o at value ; printf ( " Xo титe узнать свой роди евый э квив алент ? \ n " ) ; printf ( " Дaвaй т e подсчитаем . \ n " ) ; рrintf ( " Пожалуйста , вв еди т е свой в е с , выр аженный в фунтах : " ) ; / * по лучить входные данные о т поль зователя * / s c an f ( " % f " , &weight ) ; / * считаем , ч то цена родия р авна $ 7 7 0 з а тройскую унцию * / / * 1 4 . 5 8 3 3 к о эффициент для пер е в од а в е с а , выр аженного в ф ун т ах , в тр ойские унции* / value = 7 7 0 . О * weight * 1 4 . 5 8 3 3 ; p r i nt f ( " B aш родиевый экви в алент составля е т $ % . 2 f . \ n " , value ) ; printf ( "Bы легко можете стать достойным этого ! Е сли цена родия падает , \ n " ) ; printf ( " ешь те бол ь ш е для поддержания сво ей стоимо сти . \ n " ) ; r e turn О ; Сообщен и я о б о ши бк ах и п р едупрежден и я Если вы введете программу непра вильно и, скажем, пропустите точку с запятой, компипя­ тор выдаст сообщение о синта ксичес кой ошибке. Однако даже при правильном вводе про­ граммы компилятор может выдать предупреждение, подобное следующему: "Предупреж­ дение - преобразование данных типа 'douЫe' в тип 'float', возможна потеря данных". Со­ общение об ошибке означает, что вы сделали что-то неправильно, и программа дальше компил ироваться не будет. Предупрежден ие (warning) означает, что вы ошибку не совер­ шили, но сделали, возможно, не то, что намеревались. П редупреждение не приводит к прекращению компипяции. Данное конкретное предупреждение связано с тем, как в язы ке С обрабаты ваются числа, подобные числу 770.0. В данном примере этот вопрос не рас­ сматривается, а смысл этого предупреждения объясняется далее в этой главе. Когда вы будете вводить эту программу, вам , возможно , потребуется заменить чис· ло 7 7 0 . О текущей ценой на родий. Однако не нужно делать никаких манипуляций с о значение м 1 4 . 5 8 3 3 , представляющим собой количество тройских унций в одном фун· те . (Именно тройские унции используются в качестве ме ры веса драгоценных метал· лов; для измере ния ве са вс ех остальных товаров, в том числе и для измерения веса людей, как простолюдинов, так и вельможных персон, применяются фунты . ) Обратите внимание н а то , что фраза, приглашаю щая " ввести" ваш вес , означает напечатать на клавиатуре цифры, составляющие значе ние вашего ве с а , и зате м на· жать клавишу <Ente r> или <Return>. (Бе сполезно просто напе чатать цифры и ждать.) Нажатие клавиши <Ente r> информирует компьютер о завершении ввода. Данная про· грамма полагает , что на приглашение указать вес вы введете некоторое число , напри· мер, 1 5 0 , а не слова , с каже м , оч ень бо льшой. Ввод букв вме сто цифр служит источни· ком различных проблем , для устранения которых не обходимо использовать опе ратор i f ( с м . главу 7 ) , поэтому вводите соответствующее число . Представл ение данных в языке С 77 Ниже показан пример выходных данных расс матривае мой программы : Хо тите у знать свой родиевый эквив ален т ? Давайте подсчи таем . Пожалуй с т а , вв едите свой в е с , в ыр аженный в фунтах : 15 0 В аш роди евый э квив алент с о с тавл я е т $ 1 6 8 4 3 7 1 . 1 2 . Вы легко може т е стать до с тойным э того ! Е сли цена родия падае т , еш ь те бо льше для по ддержания св о ей стоимо с ти . Что нового в этой п рогра мме ? В этой программе появилос ь не сколько новых эле ментов языка С : • • • • Об ратите внимание , что в программе ис пользуется объявле ние переменной но­ вого типа . В предыдущих программах объявлялис ь только целочисленные пе· ременные (тип int ) , а зде с ь добавился тип переме нной с IИавающей запятой (тип fl o at) , так что теперь вы можете обрабатывать более широкий спектр данных. Переменной типа fl o at могут присваиваться веще стве нные числа . В программе демонстрируется новый спос об записи констант. Теперь в роли констант выступают веще стве нные числа . Чтоб ы выве сти значение переменной нового типа (то е сть число с плавающей запятой) , употребляйте в функции printf ( ) спецификатор % f. Кроме того, в спе цификаторе % f следует использовать модификатор . 2 для точной настройки вне шнего вида выходных данных, который определяет, что данные , выводимые на экран, будут с одержать два знака после десятичной точки. Для ввода данных в программу с клавиатуры используется функция s c an f ( ) . Спе цификатор % f в функции s can f ( ) означает, что с клавиатуры с читывается число с плавающей запятой, а &weight что вводимое число присваивается пе· ременной с име не м weight. Функция s can f ( ) использует амперсанд ( & ) , чтобы указать, где она может найти пе ременную weight . В следую щей главе это рас· сматривается б олее подробно , а пока просто поверьте на слово , что он здесь не обходим . - • Возможно , самая главная о с обенность этой программы заклю чается в том, что она является интерактивной. Компьюте р просит вас ввести конкретную ин· формацию , а затем использует введе нное вами число . Работать с интерактив­ ными программами намного инте ре снее. Но гораздо важнее то , что интерак· тивный подход делает программу более гибкой. Например, приведе нная выше демонстрационная программа может б ыть использована для пересчета люб ого разумного ве с а , а не только конкретного вес а , равного 1 5 0 фунтам. Эту про­ грамму не приходится пе реписывать каждый раз, когда она потребуется новому пользователю . Интерактивность обеспечивают функции s ca n f ( ) и printf ( ) . Функция s c an f ( ) считывает данные с клавиатуры и делает их доступными для программы , а функция printf ( ) принимает данные от программы и выводит их на экран. В месте обе эти две функции позволяют вам установить двусторонний обмен данными с компьютером (рис . 3 . 1 ) , что делает работу с компьютером го­ раздо более интере сной. 78 Гл ава 3 Тело программы / * rhodiwn. c * / int main (void) ( Прием в одных данных с клавиатуры scanf ( " - - - - - ) <111111 //ЬSтRfi!j!,E\\ printf ( " Are you- - ) Оюбр""' ""'° ""' """"" " " "'"" " " Р Р printf ( - - - - - ) return О ; � 1 д" уш 1 Ри с. 3 . 1 . Фун:1сции s ca n f ( ) и pr i n t f ( ) в работе Из приведе нного выше с писка новых свойств в настоящей главе расс матриваются два эле мента : переменные и константы различных типов. О стальные три с войства ис­ следуются в главе 4; тем не мене е , в данной главе мы продолжим в ограниченных мас­ штаб ах использовать функции s can f ( ) и print f ( ) . Перем енн ые и кон станты Компьютер под управлением программы может делать многое . Он может с клады· вать числа , сортировать имена, вос производить аудио· и видеоклипы, вычислять ор­ биты комет , с оставлять спис ки адрес атов почтовых отправле ний, набирать телефон­ ные номера, рисовать картинки, делать логичес кие выводы и многое другое из того, что придет вам в голову. Для решения этих задач программа должна раб отать с даннъ� ми: числа и с имволы являются той информацие й, которую вы ис пользуете . Значения не которых данных устанавливаются до начала выполнения программы и сохраняются не изме нными в течение все го време ни работы программы . Такие данные называются константами. Значения других данных могут изменяться (в частности, путем присваи­ вания новых значений) в ходе выполнения программы . Т акие данные называются пе· ременнъ�ми. В приведенной выше уче бной программе weight представляет собой пе ре­ менную , а 1 4 . 5 8 3 3 константу. А к какому виду данных следует отнести число 7 7 О . О , к переме нным или к константам? И в с амом деле , в обычной жизни це на на родий не является постоянной величиной, но в данной программе она рас сматривается как константа . Различие между пере менной и константой состоит в том, что пере менной можно присваивать значение или изме нять его во вре мя выполне ния программы , а с константой так поступать нельзя . - Представл ение данных в языке С 79 К л юч евые слова , обо з н а ч а ю щие ти пы Помимо различий между переме нными и константами, существуют также различия между данными разных типов. Одни данные являются числами. Другие данные пред­ ставляют собой буквы или, в общем случае , с имволы. Компьюте р должен каким-то способом различать их и использовать соответствую щими спос обами. Язык С разли­ чает несколько основных типов да'l/:н:ых. Если данные представляют с об ой константы, то компилятор может, как правило, определять их тип по тому, как они выглядят: 4 2 есть целое число , а 4 2 . 1 0 0 это число с плавающей запятой. Однако тип пере менной должен быть указан в операторе объявле ния . Чуть позже мы более подробно остано­ вимся на том , как объявлять переменные . Но сначала рас смотрим фундаментальные типы данных, суще ствую щие в языке С. В стандарте K&R С существовало с е мь клю че­ вых слов, определяющих типы данных. В стандарте С90 к этому с писку доб авилось еще два клю че вых слова. В стандарте С99 этот список пополнился е ще тремя словами (табл. 3 . 1 ) . - ТабJJица 3 .1 . КJJючевые cJJoвa языка с Клю'Ч евы е сл о ва исходноz о стандар та K&R Клю'Ч евы е сл ова, добавленные стандар том С90 Клю'Ч евы е сл ова, добавленные стандар том С99 int s igned Bool long void Comp l e x s hort Imagin ary unsigned char float douЫ e Клю чевое слово int обозначает основной класс целых чисел, используе мых в язы­ ке С . Т ри последую щих слова (long, short и uns igned), а также добавленное стандар­ том ANSI С клю чевое слово s igned представляют собой разновидности этого ос нов­ ного типа данных. Дале е , ключевое слово char обозначает символьные данные , к ко­ торым относятся буквы алфавита и другие символы , такие как #, $ , % и * . Т ип данных char можно также ис пользовать для представления небольших целых чисел. Типы данных f l o at, douЫ e и float douЫ e служат для представления веще стве нных чисел, которые в языке С называются числами с плавающей запятой (из-за формы их пред­ ставления ) . Тип данных _Bool используется для логических значений (tr u e и fal s e ) , типы данных _Complex и _Imaginary представляют, соответственно , комплексные и мнимые числа. Все типы данных, обозначаемые этими клю че выми словами, можно разделить на два семе йства в завис имости от того , как они хранятся в памяти компьютера: -цело-чис· лснu·ые типы данных и типы данных с плаваю щей запятой. 80 Гл ава 3 Б иты . байты и с лова Термины бит (bit), ба йт (byte) и слово (word) могут испол ьзоваться для описания эле­ ментов компьютерн ых данных ил и элементов компьютерной памяти . Основное внима­ ние мы удел им второму виду описания . М инимальны й элемент памяти назы вается битом . Он может хранить одно из двух значе­ ний: о или 1 . (Говорят таюке, что бит "сброшен" или "установлен". ) Конечно, в одном бите много информации не запомнишь, но в компьютере имеется огромное число битов. Бит пре­ дставляет собой базовый строительны й блок, из которых построена память компьютера. Байт это наиболее часто испол ьзуем ы й элемент памяти ко мпьютера. Почти во всех компьютерах байт образован из вос ьм и бито в, и это является станда ртны м определе­ нием ба йта, по крайней мере, когда реч ь идет об измерении объема памяти. (Однако , как будет показано в разделе "Испол ьзование символов: т и п char" далее в этой главе , в языке С при меняется другое определение .) Поскол ьку бит может принимать значение о ил и 1, байт обеспечи вает 256 (то есть 2 8 ) возможных комбина ций нулей и единиц. Эти комбинации могут испол ьзо ваться , например, для предста вления цел ых ч исел от О до 255 или для представления набора символов. Такое представление может быть реал и­ зовано путем перевода числа в двоичную систему сч исления , в котором для предста в­ ления чисел испол ьзуются только две цифры : О и 1 (счастл ивое сов падение). (Двоич­ н ы й код подробно расс матривается в главе 1 5, но вы можете в данно й главе озна ко­ миться с вводн ы м материалом по этим вопросам уже сейчас.) Слово представляет собой естественны й элемент памяти для компьютеров конкретного типа. В В-разрядных микрокомпьютерах, таких как первые компьютеры Apple, слово со­ стояло из 8 битов. Ранние модели компьютеров, совместимые с компьютерами I B M , в ко­ торых использовался микропроцессо р 80286, были 1 6-разрядными. Это значит, что раз­ мер слова этих машин был равен 1 6 битам. Такие машин ы , как персонал ьные компьютеры с микропроцессорами типа Pentium и Роwег Macintosh, работают с 32-битовыми словами. Более мощные компьютеры могут иметь слова длиной 64 и более битов. - Ц ело ч исленн ы е д ан н ые и д а н ные с пла вающей запятой В ам непонятно, что такое цело численные данные или данные с плава ю щ е й запя­ той? Если эти тер мины вам незнакомы , не о горчайтесь. Мы с е йчас в о б щ их чертах объя с ни м , что они о з начают. Если вы не зна комы с б ита ми, б а йтами и сло ва ми, пре ж­ де всего, в нимательно изучите предыдущее приме чание . Нужно ли вам знать о них все до последней детали? Вовсе нет. Вам ведь не обязательно изучать принцип работы двигателя внутре ннего с го р а ния для то го , чтоб ы водить автомобиль; но с о в с е м непло­ хо знать немного о то м , что про ис ходит внутри компьютера или двигателя - такие знания иногда оказываются очень поле зными. Для чело века различие между целыми числами и числа ми с пла ва ю ще й запятой проя вляется в с пособе их написа ния. Для ко мпьюте ра это р а зличие проявляется в с по с о б е их хра не ния в па мяти. Рас с мотр им по очереди оба эти клас с а данных. Ц елые ч и сла Целое -число - это число без дробной части. В языке С целое число нико гда не за пис ывается с десятичной то чко й. Целыми числами явля ю тс я , на приме р , 2, - 2 3 и 2 4 5 6 . Т акие числа, ка к 3 . 1 4 , 0 . 2 2 и 2 . 0 0 0 , не принадлежат к кла с су целых чисел. Целые числа хранятся в дво ично й форме . Целое число 7, на приме р , записы вается в двоично й с исте ме ка к 1 1 1 . Поэтому, чтоб ы запомнить это число в б а йте памяти, уста новите пер вые 5 разрядо в в О, а последние три разряда - в 1 ( рис . 3 .2 ) . Представл ение данных в языке С о о о о 1 1 1 1 1 о ---- 4 + 2 + 1 =7 -- 81 8-битное слово Целое число 7 Ри с_ 3 . 2 . Хранеиие в памяти -це.мго 'Числа 7 в двои'Чном ходе Числа с пла вающей запятой Число с плавающей запятой б олее или менее соответствует тому, что мате матики называют веществеинЪtм 'Числом. К вещественным числам относятся числа , находящиеся между целыми числами. Примерами чисел с rmавающей запятой могут служить: 2 . 7 5 , 3 . 1 6Е 7 , 7 . 0 0 и 2 е - 8 . Обратите внимание , что добавле ние десятичной точки превра· щает целое число в число с плавающей запятой. Таким образом, число 7 принадле жит к типу целых чисел, а в записи 7 . 0 0 оно переходит в класс чисел плавающей запятой. Существует несколько форм записи чисел с rmавающей запятой. Боле е подробно экс­ поне нциальную форму записи чис ел мы обсудим позже . Вообще говоря , запис ь 3 . 1 6Е 7 означает, что число 3 . 1 6 необходимо умножить н а 1 0 7 , т о есть н а число , с остояще е из единицы с с емью последую щими нулями. Число 7 называется порядком числа 1 0 . Клю чевым моментом зде с ь является то , что схема, используемая для хранения чис­ ла с плаваю щей запятой, отличается от той, которая применяется для хранения цело­ го числа . Число с rmавающей запятой разделяется на дробную часть и порядок, хра· нящие ся в памяти отдельно . Поэтому число 7.00 будет храниться в памяти не в том ви­ де , в каком хранится целое число 7, хотя оба они име ют одно и то же значе ние . Десятичным аналогом этого способа хранения будет запис ь числа 7.00 в виде 0.7E l . Зде с ь 0 . 7 это дроб ная часть числа , а 1 порядок. На рис . 3 .3 представлен е ще один пример хране ния в памяти числа с rmавающей запятой. Разумеется , для хранения в памяти компьютера используются двоичные числа и степени числа 2 , а не степени числа 1 0 . Дополнительный материал по этой теме вы найдете в главе 1 5 . - - 1 + . 3 14 1 59 Знак П орядок Дробная часть + . 3 14 1 59 1 х 1 01 1 --- 3 . 14 1 59 Р и с . 3.3. Хранеиие 'Числа 7f в формате 'Чи('Jl.а с плавающей запятой (десяти'Чная версия) А теперь обратим внимание на различия , имею щие практическое значение : • • Целое число не имеет дробной части; число с rmавающей запятой может иметь дробную часть. Диапазон допустимых чисел с плавающей запятой гораздо шире диапазона до­ пустимых целых чисел. С м . табл. 3 .2 в конце данной главы . 82 Гл ава 3 • • • При выполнении некоторых арифметических операций с плаваю щей запятой, например, опе рации вычитания одного б ольшого числа из другого , возможна существе нная потеря точности. Поскольку в лю б ом диапазоне чисел имеется бесконечное количество ве щест­ венных чисел, например, в диапазоне между 1 .0 и 2 . 0 , используемые в компью­ тере числа с плавающей запятой не могут представлять все числа этого диапа­ зона . Числа с плавающей запятой часто являются приближе ниями истинных значений. Наприме р, число 7.0 может быть запис ано как значение с плавающей запятой 6 .99999 (более подробно вопрос о точности будет рассмотрен ниже ) . Обычно операции над числами плавающей запятой выполняются медленнее , чем операции над целыми числами. Однако доступны микропроцессоры , разработан­ ные специально для выполнения операций над числами с плавающей запятой, и по быстродействию они сравнимы с операциями над целыми числами. Базовы е ти пы дан ны х языка С Расс мотрим теперь характерные ос обенности основных типов данных, используе­ мых в языке С . Мы покажем , как объявлять пе ременные и записывать константы всех основных типов, а также каких случаях и для каких целей применять данные того или иного типа . Некоторые компиляторы языка С распознают не все возможные типы данных, поэтому выясните в с оответствую щей документации, какие типы данных под­ де рживает ваш компилятор. Тип да н ных int Язык С предлагает множе ство целочисле нных типов данных, и вы , скоре е всего , хотите узнать, почему одного типа оказывается н е достаточно . Дело в том, что язык С дает программисту возможность выбора с о ответствую щего типа данных для исполь­ зования в каждом конкретном случае. В частности, предлагаемые языком С целые ти­ пы отличаются друг от друга диапазонами представляе мых значений, а также возмож­ ностью представления отрицательных чис ел. Базовым типом целочисленных данных является int, но если вам потребуются другие целочисленные типы , отвечающие тре­ бованиям конкретной задачи или компьютера, вы можете выбрать их. Число типа int это целое число со знаком. Это значит, что число должно быть целым, а также что оно может быть положительным, отрицательным или нулем. Диа­ пазон возможных значе ний завис ит от компьютерной системы . Обычно для хранения данных типа i nt используется одно машинное слово . Поэтому в компьютерах, с овмес­ тимых со старыми моделями IВМ РС с 1 6-битовыми словами, для хранения данных ти­ па int выделяется 1 6 битов. В этих условиях значения целых чисел находятся в диапа­ зоне от - 3 2 7 6 8 до + 3 2 7 6 7 . Совреме нные персональные компьютеры опе рируют 3 2разрядными целыми числами и данные типа int с о ответствуют этому размеру. При­ меры можно найти в табл. 3 .3 дале е в этой главе . В настояще е время производство пе рс ональных компьютеров с о риентировалось на выпуск 64-разрядных проце ссоров, которые могут с вободно манипулировать еще б ольшими целыми числами. Стандарт ANSI С требует, чтобы минимальным диапазо­ ном возможных значений данных типа int являлся диапазон от - 3 2 7 6 7 до + 3 2 7 6 7 . - Представл ение данных в языке С 83 Обычно для представления знака целого числа в вычислительной с истеме отво­ дится один разряд . Наиболее распространенные способы представле ния в памяти це­ лых чисел со знаком рассматриваются в главе 1 5 . Об ъявление переменных типа int Как было показано в главе 2 , для объявления целочисленных переме нных служит клю че вое слово int. Сначала идет ключевое слово int, зате м выбранное вами имя пе­ ременной, после которого ставится точка с запятой. Несколько переменных можно объявлять либо по отдельности, либо в одном операторе, в этом случае после клю че­ вого слова int поме щается спис ок имен, причем имена отделяются друг от друга запя­ тыми. Ниже показаны примеры допустимых объявлений пе ременных: int erns ; int hogs , cows , goat s ; Можно объявить каждую переменную отдельно или же , наоборот, все четыре пере­ менные объявить в одном операторе - это дело вкус а . Результат будет тот же : выделен­ ные области памяти с соответствую щими именами для четырех переменных типа int. Эти объявления создают переменны е , но не присваивают им никаких значений. Как переменные получают значения? Вы уже видели два способа, с помощью которых пере­ менные могут получать значения в программе . Во-первых, это оператор присваивания: COWS = 1 12 ; В о-вто рых, пе реме нная может получить з начение от функции , наприм е р , от s can f ( ) . Теперь рассмотрим третий способ. Инициализация переменных Ипи-циализироватъ переме нную - это значит прис во ить е й пачалъпое значе ние . В языке С это можно сделать в операторе объявления . Просто после име ни перемен­ ной поставьте знак операции присваивания (=) и значение , которое необходимо при­ своить пе ременной. В от не сколько приме ров: int hogs = 2 1 ; int cows = 3 2 , goats int dogs , cats = 9 4 ; 14 ; / * пр авил ь н ая , но пло х ая форма * / В последне й строке инициализируется только переменная cats . При беглом чте­ нии может показаться, что пе ременная dog s также инициализируется значе ние м 9 4 , поэтому лучше избегать указания инициализированных и не инициализированных пе­ ременных в одном операторе объявле ния . Короче говоря, эти объявления выделяют для пе ременных области памяти, име­ нуют их и прис ваивают им начальные значения (рис . 3 .4 ) . Ввод с клавиатуры констант типа int Различные целые числа ( 2 1 , 3 2 , 1 4 и 94) в последнем примере представляют с обой -цмо'Числеппъ;е копстаптъ;. Когда вы вводите число без де сятичной точки и без порядка , компилятор С вос принимает е го как целое . Поэтому числа 22 и -44 являются целочис­ ленными константами, а числа 22.0 и 2 .2El - нет. 84 Гл ава 3 int sows ; sows Выделить область памяти j 2 int boars=2 ; boars Выделить область памяти и записать в нее значение Ри с. 3 . 4 . Определепие и ипи-циализа-ция псремеппой Большинство целочисленных констант рассматриваются языком С как данные типа int. Очень большие целые числа могут трактоваться иначе (см. раздел "Константы типа long и long long" , в котором рассматриваются данные типа long int ) . Вывод на печать значений типа in t Для вывода на пе чать данных типа int вы можете воспользоваться функцией print f ( ) . Как уже говорилось в главе 2, с имволы % d служат для указания в строке мес· та , где будет пе чататься целое число . Символы %d называются спщификатором фоfJма· та, поскольку они определяют формат, используемый функцией print f ( ) для ото­ бражения конкретного значе ния . Каждому с пецификатору %d в строке формата долж· но быть поставлено в соответствие значе ние типа int из спис ка выводимых на пе чать элементов. Это может быть значе ние пе ременной типа i nt, константа типа int или любое другое выражения типа i nt. Программист должен следить за те м, чтобы коли· чество с пецификаторов формата равнялось числу значе ний типа int, так как комли· лятор не обнаруживает подобного рода ошибок. В листинге 3 .2 представлена простая программа , которая сначала выполняет ини· циализацию пе ременной, а затем выводит на пе чать значение этой переменной, зна· че ние константы и простого выраже ния . Программа также демонстрирует, к каким последствиям может привести не внимательность в этих вопрос ах. листинг 3.2. Программа printl . с / * printl . c - - демонс трир у е т некоторые свой с тв а ф ункции print f ( ) * / #incl ude <s tdio . h > int main ( void) int ten int two 10; 2; printf ( " Выполн я е тся пр ави л ь но : " ) printf ( " % d минус % d р авно % d\ n " ten , 2 , t e n - two ) ; рrintf ( " Выполн я е тся непр а вильно : " ) ; printf ( " % d минус % d i s % d \ n " , t e n ) ; / / пр опущены 2 ар г умента r e turn О ; Представл ение данных в языке С 85 После компИJiяции и выполне ния программа выведет на экран следую щую информацию : Выполня е тся пр авил ь н о : 1 0 минус 2 р ав но 8 Выполня е тся н е пр авил ь но : 1 0 минус 1 0 р авно 2 В первой строке выходных данных первый спецификатор % d представляет пе ре­ менную t e n типа int, второй - константу 2 типа int и третий - значение выражения ten - two , также типа int. Во второй строке первому спе цификатору %d также соот­ ветствует значение переменной ten, зато для двух последую щих спе цификаторов % d соответствую щих значе ний нет, и программа использует случайные значе ния , нахо­ дящие ся в близле жащей памяти ! (На своем компьютере вы можете получить совс ем не те числа , которые использует рассматриваемая программ а . Однако не только содер­ жимое памяти может быть ошибочным, дело еще в том , что различные компиляторы по-разному манипулируют яче йками памяти.) В ас может огорчить тот факт , что компилятор не способен обнаружить столь оче­ видную ошиб ку. Причина заклю чается в функции printf ( ) . Большинство функций принимает четко определе нное количе ство аргументов, и компилятор может прове­ рить, правильное ли число аргументов вы указали. В то же вре мя функция printf ( ) может принимать один, два , три и большее число аргуме нтов, и это обстоятельство не позволяет компилятору ис пользовать обычные методы обнаружения такого рода ошибок. Необходимо тщательно проверять, равно ли количе ство спе цификаторов формата в функции print f ( ) количеству значений, которые подлежат отображе нию на экране. Восьмеричные и шестнадцатеричные числа Обычно в языке С предполагается , что целочисле нная константа представляет с о­ бой десятичное число (основание м системы с числения является 1 0 ) . Однако в про­ граммировании широко используются и восьме ричные (ос нование - число 8 ) , и ше­ стнадцате ричные (основание - число 1 6 ) числа. Пос кольку 8 и 16 - это сте пени числа 2, а не 1 0 , вос ьмеричная и шестнадцатеричная системы счисления более удобны для представления чис ел, используемых в компьютерах. Например , число 6553 6 , которое часто всплывает в 1 6-разрядных компьютерах, в шестнадцатеричной с истеме выгля­ дит как 1 0000. Наряду с этим , каждая цифра ше стнадцате ричного числа соответствует в точности 4 разряда м . Например , шестнадцатеричная цифра 3 - это 001 1 , а шестна­ дцате ричная цифра 5 - это 0 1 0 1 . Таким образом, ше стнадцатеричному значению 3 5 соответствует битовая комбина­ ция 001 1 0 1 0 1 , а шестнадцатеричному значению 53 - битовая комбинация 0 1 01 001 1 . Это с оответствие позволяет облегчить переход от шестнадцатеричного представле­ ния числа к двоичному представле нию (основание - число 2) и обратно . Но каким об­ разом компьютер может определить, является ли число 1 0000 десятичным , шестна­ дцате ричным или восьме ричным? На используемую систему с числения в языке С ука­ зывают специальные префикс ы. Префикс О х ИJIИ ОХ означает, что вы определяете ше стнадцате ричное число , поэтому 16 в шестнадцатеричной системе записывается как O x l O или O X l O . Аналогично , пре фикс О означает, что задается восьме ричное чис­ ло. Например, в языке С де сятичное число 16 записывается в восьме ричной системе как 0 2 0 . Б олее полно эти альтернативные системы счисле ния (восьмеричная и шест­ надцатеричная ) обсуждаются в главе 1 5 . 86 Гл ава 3 В ы должны понимать, чго различные системы с числения применяются для удобст­ ва программистов. О ни не влияют на то , как числа хранятся в памяти компьютера. Иначе говоря , вы можете написать 1 6 , 0 2 0 или O x l O , тем не менее , это число в любом случае будет храниться в памяти в одном и том же виде - в двоичном коде, который используется внутри компьюте ра . отображение восьмеричных и шестнадцатеричных чисел Язык С предоставляет вам возможность не только записывать число в любой из опи­ санных выше трех систем счисления , но и отображать его в любой из этих трех систем. Чтобы отобразить на экране целое число в восьмеричном, а не десятичном виде , вместо спецификатора %d необходимо применять спецификатор %0. Для отображения целого числа в шестнадцатеричном виде служит спецификатор %х. Если вы хотите вывести на экран префикс языка С, воспользуйтесь спецификаторами %#0, % #х и % #Х, чго позволит вывести, соответственно, префиксы О, Ох и О Х . В листинге 3 .3 показан небольшой при­ мер. (Напомним, чго вам, возможно, придется вставить в программу оператор вызова функции getchar ( ) ; при работе в некоторых интегрированных средах разработки он предотвращает немедленное закрытие активного окна программы . ) листинг 3.3. Программа Ьa ses . с / * b a s e s . c - распеч атыв ае т число 1 0 0 в д е с я тич ной , восьмеричной и ш е с тнадцатеричной системах счисления * / #incl ude <s tdio . h > int main ( void) int х = 1 0 0 ; printf ( " десятично е х, х, х) ; printf ( " десятично е х, х, х) ; r e turn О ; % d ; восьмеричное % о ; шестнадцат ерично е = % x \ n " , % d ; восьмеричное % #0 ; ш е с тнадцатерич н о е = % #x \ n " , В результате компиляции и выполне ние этой программы были получены следую­ щие выходные данные : д е с я тичн о е д е с я тичн о е 1 0 0 ; во сьмеричное = 1 4 4 ; ш е с тн адцат ерично е = 6 4 1 0 0 ; во сьмеричное = 0 1 4 4 ; шестнадца т ерич н о е = О х 6 4 Одно и то ж е значение отображается в трех различных систе мах счисления . В с е преобразования выполняет функция printf ( ) . О братите внимание , что выводимые на экран данные отображаются без префикс ов О или Ох, если вы не вклю чили знак # как часть спе цификатора. Другие целоч ислен н ые тип ы Если вы просто изучаете язык С , то при работе с целыми числами в большинстве слу­ чаев вам, скорее всего, вполне достаточно будет типа int. Однако для полноты картины мы рассмотрим сейчас и другие формы целых чисел. При желании вы можете пропус· тить этот раздел и сразу перейти к изучению данных типа char в разделе "Использова­ ние символов: тип char", возвращаясь к данному разделу по мере необходимости. Представл ение данных в языке С 87 В языке С используются три клю чевых слова-прилагательных, обозначающих мо­ дификации основного типа целочисле нных данных (то есть типа int) : short, long и unsigned. • • • • • • Данные типа short int (или, короче, типа short) могут занимать ме ньший объем памяти, чем данные типа i nt, и таким образом экономить память в слу­ чае , когда используются только небольшие числа . Данные типа s hort, равно как и данные типа int, являются данными со знаком. Данные типа l o ng int (или long) могут занимать больший объем памяти, чем данные типа int, и те м самым обеспечивать вам возможность представлять большие целочисле нные значения. Данные типа long, так же как и данные типа int, являются данными со знаком. Данные типа long l ong int , или l o ng long (оба типа введены стандартом С99 ) , могут ис пользовать больше памяти, чем тип long , тем самым об еспечивая возможность представлять е ще б ольшие целочисленные значения . Как и тип int, тип long long является типом со знаком . Данные типа unsigned int (или просто uns igned), ис пользуется только для представления не отрицательных чисел. Этот тип смещает диапазон представ­ ляе мых чисел в сторону положительных чис ел. Например, 1 6-разрядный тип unsigned int обес пе чивает диапазон представляемых значений от О до 6 5 5 3 5 вместо диапазона о т - 3 2 7 6 8 до 3 2 7 6 7 . Разряд, который использовался для пред­ ставления знака, теперь становится е ще одной двоичной цифрой, благодаря че му расширяется диапазон представляемых не отрицательных чисел. Данные типа un signed long int , или unsi gned long, а также unsigned s hort int, или unsigned short, признаются стандартом С90 как допустимые . К этому списку стандарт С99 добавляет тип unsigned l ong long int, или unsi gned long l ong. Клю че вое слово s igned может ис пользоваться с лю б ыми типами со знаком с тем, чтобы ваши намере ния стали прозрачными. Например, short, s hort int, s igned s hort и signed short int представляют собой имена одного и того же типа данных. Об ъявление целочисленных данных различных типов Другие типы целых чисел объявляются так же , как и тип int. Приведе нный ниже список с одержит не сколько приме ров. Не вс е ранние компиляторы языка С распо­ знают последние три типа ; завершающий приме р допустим только в рамках стандарта С99. l o ng int e s tin e ; l o ng j ohns ; short int erns ; short r i b s ; un s i gned int s _count ; un s i gned p l aye r s ; un s i gned long he adcount ; un s i gned s hort y e s vo te s ; l o ng long ago ; 88 Гл ава 3 зачем нужно так много целочисленных типов ? Поче му мы говорим, что типа long и short "могут" ис пользовать больше или меньше памяти, чем тип int? Потому что язык С гарантирует только то , что тип s hort не длинне е типа i nt и что тип long не короче, чем тип int. Идея заклю чается в том, чтобы подоб рать тип данных для конкретной машины . На пе рсональном компьютере IBM РС, работающем под управлением операционной с истемы Windows 3 . 1 , напри­ мер, как тип int, так и тип s hort являются 1 6-разрядными типами данных, в то время как long представляет собой 3 2-разрядный тип данных. С другой стороны, на машине с Windows ХР или на Macintosh Powe rPC тип s hort соде ржит 16 разрядов, а оба типа int и long являются 3 2-разрядными. Естественный разме р слова, которыми опериру­ ют микропроцес соры Pentium , Powe rPC G3 или G 4 составляет 3 2 разряда . Поскольку такое слово обес печивает представление чисел, превышаю щих два миллиарда (как показано в табл. 3 .3 ) , разраб отчики языка С для различных комбинаций этих процес­ соров и опе рационных систем не видели необходимости в чем-то б олее крупном, в связи с чем тип l ong оказался таким же, как и int. Для многих приложений такие большие целые числа вовсе не нужны , поэтому б ыл предложе н тип short, обеспе чи­ ваю щий экономию памяти. С другой стороны , первые модели пе рсонального компью­ тера IBM РС поддерживали только 1 6-б итовое слово , и поэтому возникла не обходи­ мость в большем типе целочисленных данных, в частности, в типе long. В настояще е время, когда все более широкое распростране ние получают 64-разряд­ ные процессоры , такие как IBM Itanium , АМD O pteron и PowerPC G 5 , ощущается не­ обходимость в 64-разрядных целых числах, и име нно это об стоятельство стало при­ чиной для использования типа long long. Сегодня тип l o ng l ong предназначается для представления 64-разрядных чисел, тип l ong для 3 2-разрядных чисел, тип s hort для 1 6-разрядных чисел, а тип int для представления как 1 6-разрядных, так и 3 2-разрядных чисел, в зависимости от ес­ тественного размера слова машины. В принципе , однако, указанные выше четыре ти­ па данных могут представлять четыре различных размера. Стандарт языка С устанавливает ориентиры, определяющие минимально допусти­ мый размер каждого базового типа данных. Минимальный диапазон возможных зна­ че ний как типа s hort, так и типа int, находится в пределах от -3 2 767 до 3 2 76 7, что соответствует 1 6-разрядному слову, а минимальный диапазон типа l ong находится в пределах от -2 1 4 7 483 647 до 2 1 4 7 483 6 4 7 , что соответствует 3 2-разрядному слову. Что кас ается типов un signed short и uns igned int, то минимальный диапазон охва­ тывает числа от О до 65 53 5 , а для типа unsi gned long минимальный диапазон нахо­ дится в пределах от О до 4 294 967 295. Тип long l o ng предназначен для поддержки 64разрядных объектов данных. Минимальный диапазон чисел представляе мых этим ти­ пом данных достаточно внушителен и простирается от -9 223 3 72 036 854 775 807 до 9 223 3 72 036 854 775 807, а минимальный диапазон типа unsi gned long long охваты­ вает числа от О до 18 446 744 0 73 709 551 6 1 5 . В каких случаях в ы используете конкретные варианты типа int? Сначала рассмот­ рим типы без знака . Прежде вс его , они ис пользуются для подсчета , поскольку в этих случаях нет необходимости в отрицательных числах, беззнаковые типы могут прини­ мать большие положительные значе ния , чем данные с о знаком . Данные типа long приме няйте в тех случаях, когда тип long предоставляет вам возможность раб оты с нужными числами, а тип int не способен этого сделать. - - - Представл ение данных в языке С 89 В то же вре мя, в тех системах, в которых тип long больше , чем тип int , исполь­ зование типа l o ng может замедлить вычисления , в силу этого обстоятельства не сле­ дует употреблять тип l ong, если в этом нет необходимости. Еще один момент: е сли вы пишете программу для машины , у которой типs int и long одного и того же разме ра , а вам нужно работать с 3 2-разрядными целыми числами, вам следует выбирать тип long вместо int, чтобы ваша программа работала корректно , будучи пе рене сенной на 1 6разрядную машину. Аналогично , применяйте тип long long, если вам приходится иметь дело с 64разрядными целочисленными значениями. В некоторых компьютерах уже исполь­ зуются 64-разрядные проце с с оры , а 64-разрядная обработка в с ерверах, в рабочих станциях и даже в настольных компьюте рах вскоре может стать обычной практикой. Пользуйтес ь типом short в целях экономии памяти, например, в тех случаях, когда вам нужна 1 6-разрядная величина в систе ме, в которой типом int является 3 2-раз­ рядный тип данных. Обычно задача экономии памяти возникает только в тех случаях, когда ваша программа использует массивы целых чис ел, которые можно расс матри­ вать как крупные по сравнению с доступной памятью с истемы. Еще одна причина в использовании типа s hort состоит в том, что по своим размерам он соответствует размерам ре гистров, используемых конкретными компонентами с истемы. Ц елоч и слен н ое п ереполнен и е Что произойдет, есл и целое число окажется бол ьше, чем допускает выбранн ы й для это­ го числа тип? Дава йте присвоим целочисленно й переменной максимал ьно возможное целое значение, приба вим к нему какое-либо целое ч исло и посмотрим, чем это за кон­ чится . Вы полним эту операцию как над цел ы м и со знако м , так и с цел ы м и без знака . (В вызове функции printf ( ) для отображения значений типа uns igned int применя­ ется специфи катор % u . ) / * toobi g . c - пр евьШJ ение максимально во зможного знач ения int в в аш ей сис теме * / #i nclude < s tdi o . h> int main ( void) { int i = 2 1 4 7 4 8 3 6 4 7 ; unsigned int j = 4 2 9 4 9 6 7 2 9 5 ; print f ( " % d % d % d\ n " , i , i+ l , i+ 2 ) ; print f ( " % u % и % u\ n " , j , j + l , j + 2 ) ; r etur n О ; В нашей системе был получен следующий резул ьтат: 2 1 4 7 4 8 3 6 4 7 - 2 1 4 7 4 8 3 6 4 8 -2 1 4 7 4 8 3 6 4 7 4 2 9 4 9 67 2 95 о 1 Целое ч исло без знака j выступает в рол и счетчика расстояния , пройденного автомо­ билем. Когда этот счетч ик достигает максимального значени я , он сбрасы вает это зна­ чение и начинает новы й отсчет с нуля . Целое ч исло i ведет себя аналогично . Основное разл ич ие заключается в том, что переменная j типа unsigned int, подобно счетчику расстояния , начинается с О, в то вре мя как пере менная i типа int с -2 1 47 483 648. Обратите внимание, что вы ниче го не знал и о том, что значение i превзошло макси­ мал ьно возможное значение (произошло переполнение). - 90 Гл ава 3 Чтобы составить соответствующие табл и ц ы , вам придется предусмотреть в этой про­ гра м ме специал ьны й код. Описанное здесь по ведение програ м м ы определяется правилами языка С, регла менти­ рующи м и работу с типами без знака . Станда рт не указы вает, как должны вести себя ти­ пы со знако м . Описанное в рамках рассмотренного примера поведение является обыч­ н ы м , однако вам , возможно, еще придется стол кнуться с по ведением другого рода . Константы типа long и long long Обычно , когда вы используете в коде программы число , подобное , например, 2345, оно обычно сохраняется в памяти как относящееся к типу int . Что произойдет , если вы используете , например, число 1 ООО ООО, в с воей с истеме , в которой тип i nt не мо­ жет запомнить столь большое число? В этом случае компилятор рассматривает его как число типа l ong i nt, предполагая , что этого типа будет достаточно . Если число пре­ восходит максимально возможное значе ние типа l o ng, С рас сматривает его как зна­ че ние типа unsi gned long . Если и этого оказывается не достаточно, С интерпретиру­ ет е го как значение типа long l o ng или un s i gned l ong long, если, разуме ется , упо­ мянутые типы доступны . Восьмеричные и шестнадцатеричные константы интерпретируются как значения типа int, пока это значе ние слишком велико. Затем компилятор примеривает к этому числу тип un s i gned int . Если и это не помогает, компилятор последовательно пробу­ ет приме нить типы l o ng, un s i gned long, long l ong и unsigned long long. Иногда вы хотите , чтоб ы компилятор сохранил не большое число как целое значе­ ние типа long. Подоб ная необходимость может возникнуть, например, во время про­ граммирования на IBM РС, когда явно ис пользуются адре с а памяти. Наряду с этим, не которые стандартные функции языка С требуют значений типа long. Чтобы не­ большая константа инте рпретировалась как значение типа long, вы можете с о прово­ дить ее буквой 1 (буква "L" в нижнем регистре) или 1 в качестве суффикс а . Вторая форма предпочтительнее , поскольку она не выглядит похоже на цифру 1. В с илу этого обстоятельства система с 16-разрядным типом int и 3 2-разрядным типом long интер­ претирует целое число 7 как 1 6-разрядное , а целое число 7 1 как 3 2-разрядное . Суф­ фиксы 1 и 1 также могут ис пользоваться в вос ьмеричных и шестнадцатеричных чис­ лах, например, 0 2 0 1 и О х 1 0 1. Аналогично , в с истемах, поддерживающих тип long long, вы можете воспользоваться суффиксами 11 и 11, дабы указать значение типа long long, например , 3 11. Добавьте и или U к суффиксу в случае использования типа unsigned l o ng long, как случае 5 ull или 1 0 1 1U или 611U или 9Ul l . - Печать типов short, long, long long и unsigned Для вывода чисел типа unsigned int на печать приме няется спецификатор % u . Чтобы выве сти значе ние типа long, ис пользуется с пецификатор формата % ld. Если типы int и long имеют в ваше й системе один и тот же разме р, вполне достаточно од­ ного спе цификатора % d, однако ваша программа не будет работать должным образом, если ее пе ренести в систе му, у которой два эти типы име ют разные разме ры , поэтому для типа long следует пользоваться с пецификатором % ld. Вы можете также указывать пре фикс 1 вместе с префиксами х и о . Благодаря этому вы можете примерить с пеци­ фикатор % 1х для пе чати целого числа типа long в ше стнадцате ричном формате и с пе­ цификатор % 1о для печати в вос ьмеричном формате . - Представл ение данных в языке С 91 Обратите внимание , что если язык С позволяет использовать к а к буквы верхнего , так и буквы нижнего регистров в каче стве суффикс ов констант, то в с пецификаторах формата допускаются только буквы нижне го ре гистра. В языке С доступны дополнительные форматы функции printf ( ) . Во-пе рвых, вы можете использовать префикс h для значе ний типа s hort. По этой причине специфи· катор % hd отображает целое число типа s hort в де сятичной форме , а спе цификатор % ho отображает это же число в вос ьмеричной форме . Префиксы h и 1 могут ис пользо· ваться с пре фиксом u для типов без знака . Наприме р, вы могли бы вос пользоваться обозначе ние м % l u для печати значений типов unsigned long. В листинге 3 .4 пред· ставлен с оответствую щий пример . Системы , поддерживающие типы long long, ис· пользуют с пецификаторы % l l d и %11 u для версий со знаком и версий без знака . В гла· ве 4 изучение спе цификаторов формата будет продолже но . листинг 3.4. Программа print2 . с / * pr int2 . c - дал ь н ейш е е изуч ение свойств функции printf ( ) * / #incl ude <s tdio . h > int main ( vo i d ) { un s i gned int un = 3 0 0 0 0 0 0 0 0 0 ; / * Система с 3 2 -р азря дным типом i nt * / / * и 1 6 -р а зрядным типом short * / short end = 2 0 0 ; l o ng big = 6 5 5 3 7 ; l o ng long verybig 12345678908 642 ; p r i nt f ( " un = % u , но не % d \ n " , un , un ) ; p r i nt f ( " end = % hd и % d\ n " , end , end) ; p r i nt f ( "big = % l d , но не % hd\ n " , big , b i g ) ; printf ( " veryЬ i g = % l l d , но не % ld\ n " , veryЬ i g , v eryЬig ) ; r e turn О ; В от как выглядят выходные данные на одной из систем: un = 3 0 0 0 0 0 0 0 0 0 , но не - 1 2 9 4 9 6 7 2 9 6 end = 2 0 0 и 2 0 0 Ь i g = 6 5 5 3 7 , но не 1 ve ryЬig = 1 2 3 4 5 6 7 8 9 0 8 6 4 2 , но не 1 9 4 2 8 9 9 9 3 8 Этот пример показывает, что использование неправильной с пецификации может приве сти к непредсказуе мым ре зультатам . Прежде вс его , обратите внимание на то, что использование с пецификатора %d для переме нной без знака un вызывает появле· ние отрицательного числа ! Это объяс няется тем, что значе ние 3 ООО ООО ООО без знака и значе ние -129 496 296 со знаком имеют одно и то же внутреннее представление в памяти наше й системы . (В главе 1 5 это с войство рас сматривается подробно . ) Таким образом, е сли вы сообщи· те функции printf ( ) о том, что значе ние является числом без знака, она напечатает одно значение , а если вы сообщите ей, что значе ние представляет собой число со зна· ком - то другое значе ние . Подоб ное поведение имеет место в тех случаях, когда вели· чина со знаком превосходит макс имально допустимое значение . Небольшие положительные значения , например, 9 6 , с охраняется и отображается так же , как и типы со знаком и без знака. 92 Гл ава 3 Далее , об ратите внимание на то обстоятельство , что пе ременная e nd типа s hort отоб ражается одинаково в тех случаях, когда вы указываете функции print f ( ) , что переме нная end имеет тип s hort (спецификатор % hd) и когда задаете тип int (с пеци­ фикатор % d) . Это объясняется тем , что во время передачи аргумента функции С авто­ матически расширяет значение типа s h o r t до значе ния типа int. При этом у вас могут возникнуть два вопроса: почему это пре образование имеет ме сто , и для чего использу­ ется модификатор h? Ответ на первый вопрос выглядит следую щим образом : тип int выб ирался с целью об еспечения наиб оле е эффективной работы компьюте ра . Следо­ вательно, на компьютере, для которого типы s hort и int имеют разные разме ры , пе­ редача значе ния как типа int может выполняться быстре е . Ответ на второй вопрос звучит так: вы можете использовать модификатор h, чтобы показать, какой вид при­ мет целое значе ние , будучи ус еченным до типа s h o r t . Третья строка выходных дан­ ных может служить иллюстрацией этого момента . Число 6553 7, записанное в двоичном формате как 32-разрядное число , имеет такой вид: 0000000000000001 000000000000000 1 . Применив спецификатор % hd, мы заставля­ ем функцию p r i n t f ( ) просматривать только 16 последних разрядов числа , поэтому данное число она отображает как 1 . Аналогично , завершающая строка выходных дан­ ных показывает полное значе ние величины veryb i g , после чего это значение сохра­ няется в последних 32 разрядах, на что указывает спе цификатор % ld. Как упоминалось ранее , программист отвечает за то , чтобы количество специфи­ каторов с оответствовало количеству отображае мых значе ний. Из сказанного выше следует , что программист несет ответстве нность также и за правильный выбор с пе­ цификаторов, с оответствую щих типу отображаемых значений. Соотв етств и е тип ам с пеци фи к аторов printf о Не забы вайте убедиться в том, что на каждую отображаемую вел ичину в ва шем опера­ торе p r i n t f ( ) имеется один спе цификато р формата . П ро верьте также , чтобы тип каж­ дого спецификатора формата соответствовал типу отображаемо го значения. Использование си м волов: тип char Тип данных char применяется для хранения с имволов , таких как буквы и знаки препинания , однако в техничес ком ас пекте он является также целочисле нным. Поче­ му? Да потому, что тип char фактически хранит целые числа , а не символы . При раб о­ те с символами компьютер использует числовые коды , то есть определенные целые числа представляют определе нные символы. В США наиболее часто используемым кодом является код ASC II (American Standard Code for Inform ation Interchange - аме­ риканс кий стандартный код обмена информацие й ) . Именно этот код принят в данной книге . В нем , например, целое значение 65 представляет букву А верхнего регистра. Т а­ ким образом, чтобы сохранить букву А , вы фактически должны записать целое число 65. (Многие мэйнфре ймы IВМ ис пользуют другой код, получивший название EBCDIC (Extended B inary Coded D e c imal Interc hange Code - рас ширенный двоично-десятич­ ный код обмена информацие й ) , хотя в принципе это одно и то же. В компьютерных систе мах за пределами США могут приме няться сове ршенно другие коды.) Представл ение данных в языке С 93 Стандартный код ASC II воспринимает числовые значения в диапазоне от О до 1 2 7. Этот диапазон достаточно мал, и чтобы охватить е го , достаточно все го лишь 7 разря­ дов. Тип char об ычно определяется как 8-разрядная единица памяти, следовательно , ее боле е чем достаточно для того , чтобы вме стить стандартный код ASC II. Многие систе мы, такие как, например, IBM РС и Apple Macintosh, используют рас ширенные коды ASCII (разные для этих двух систе м ) , которые не выходят за пределы 8 разрядов . В общем с мысле , язык С гарантирует, что тип char достаточно большой, чтобы пред­ ставлять базовый набор символов для с исте м , в которых ре ализован С . Многие наб оры символов с остоят и з б оле е ч е м 1 2 7 или даже 2 5 5 значе ний. Напри­ мер, существует набор символов "Japanese kanji" для японских иероглифов . В рамках коммерче ской инициативы Unicode б ыл создана с истема, представляющая различные наборы символов, ис пользуемых в различных частях мира , которая в настояще е время содержит более 96 ООО с имволов. О рганизация ISO и комис с ия IEC (International Ele ctrote chnical Commission - Международная электротехническая комис с ия ) вместе разработали стандарт наборов символов, получивший название ISO /IEC 1 0646. К с ча­ стью , с помощью стандарта ISO /IEC 1 0646 удалось сохранить совме стимость стандар­ та U nicode с более широким стандартом ISO /IEC 1 0646. Платформа, использую щая один из этих наборов в качестве с воего базового наб о­ ра с имволов , могла употреблять 1 6-разрядное и даже 3 2-разрядное представление типа char . В языке С байт определяется как число разрядов, используемых для представле· ния типа cha r . В документации по языку С сказано , что в таких системах байт долже н соде ржать 1 6 или 3 2 разряда, но не 8 разрядов . Об ъявление переменных типа char Как и следовало ожидать, пере менные типа char объявляются так же, как и другие переме нные . Вот нес колько примеров таких объявле ний: char r e s pons e ; char itaЫ e , l atan ; Приведенный код создает три переменных типа char : r e s pons e , itaЫ e и l atan. символьные константы и инициализация Предположим , что вы хотите инициализировать с имвольную константу буквой А . Компьютерные языки должны облегчить решение этой задачи, по этой причине вы не должны помнить вс е коды ASCII, да вам это и не нужно . В ы можете присвоить символ А пе ременной gr ade с помощью следую щей процедуры инициализации: char gr ade = 'А' ; Одиночная буква , заклю ченная в одиночные кавычки, представляет собой символъ­ ную константу языка С . Когда в поле зрения компилятора попадает конструкция ' А ' , он переводит е е в соответствую ще е кодовое значение . Одиночные кавычки здесь оче нь важны. Расс мотрим еще один пример : char b r o i l e d ; 'Т ' ; broiled broiled Т; broiled "Т" ; / * о бъявл ение п ер еменной типа c h a r * / / * Пр авил ь но * / / * Непр авил ь но ! Компилят ор д умае т , ч т о Т явля е т ся переменн ой * / / * Неправильно ! Компилятор думает, что "Т" является С'Iрокой * / 94 Гл ава 3 Если опустить кавычки, то компилятор подумает , что Т представляет имя перемен­ ной. Если вы примените двойные кавычки, он подумает, что вы используете строку. Мы обсудим строки в главе 4. Поскольку символы на самом деле хранятся как числовые значения , вы можете также употреблять числовые коды для присваивания значений: char gr ade = 6 5 ; /* пр ави л ь но в контексте ASCI I , но это плохой стиль * / В данном примере 6 5 имеет тип i nt, однако, поскольку это значе ние меньше мак­ симального значения типа cha r , оно может быть присвоено пе ременной gr ade без ка­ ких-либо осложнений. Поскольку 65 представляет с об ой АSСП-код буквы А , в условиях данного примера пе ременной gr ade прис ваивается значе ние А. Тем не менее , обрати­ те внимание , что в расс матривае мом примере предполагается использование в с исте­ ме кодировки ASC II. Указание ' А ' вме сто 65 позволяет получить программный код, который раб отает на любой системе . Поэтому намного проще употреблять символь­ ные константы, че м значения числовых кодов. Н е с колько странным является тот факт, что С рассматривает с имвольные кон­ станты как тип i n t , а не c h a r . Н априм е р , в с истеме с 3 2-ра зряды м типом i nt и с 8-разрядным типом char , код char gr ade = ' В ' ; представляет ' В ' как числовое значе ние 6 6 , хранящееся в 3 2-разрядной ячейке памя­ ти, но при этом переменная gr ade с трудом умещает значе ние 66 в 8-разрядную яче йку. Эта характеристика символьных констант позволяет определять такую константу в виде ' FATE ' , при этом четыре отдельных 8-разрядных АSСП-кода помещаются в 3 2разрядную ячейку. В то же время, попытка прис воить такую символьную константу переменной типа char приводит к тому, что используются только последние 8 разря­ дов, в силу чего данная пе ременная получает значение ' Е ' . Непечатаемые символы Технология, ис пользую щая одиночные кавычки, хороша для символов, цифр и знаков препинания , однако е сли внимательно просмотреть таблицу кодов ASC II, мож­ но найти непе чатае мые с имволы. Например , некоторые из них представляют с обой такие действия , как возврат на одну позицию влево , переход на следую щую строку или выдачу звукового сигнала терминалом или встроенным динамико м . Как все это можно представить? Язык С предлагает три с пособа. Первый способ уже был опис ан, это использование АSС П-кодов. Наприме р, значе­ нием АSСП-кода для символа звукового сигнала является 7, так что вы можете исполь­ зовать следую щий оператор: char Ь е е р = 7 ; Второй с пособ представления неудобных с имволов в языке С предполагает приме­ не ние спе циальных последовательностей символов. Такие последовательности назы­ ваются управляющими последователъност.я.ми. В табл. 3.2 приводится перечень управ­ ляю щих последовательносте й вместе с их описаниями. Представл ение данных в языке С 95 Таб.nица 3 .2. Уnрав.nяющие пос.nедовате.nьности Последовательно сть Описание \а Предупреждение (стандарт ANSI С ) . \Ь Возврат на одну позицию влево . \f Перевод страницы. \n Новая строка. \r Возврат каретки. \t Горизонтальная табуляция. \v Вертикальная табуляция. \\ Обратная косая черта (\ ) . \' Одиночная кавычка ( ' ) . \ tl Двойная кавычка ( " ) . \? Знак вопроса ( ? ) . \ О оо Восьмеричное значение ( о представляет восьмеричную цифру) . \ xhh Ш естнадцатеричное значение (о представляет шестнадцате­ ричную цифру) . Когда с имвольным переме нным присваиваются управляющие последовательности, последние должны б ыть заклю чены в одиночные кавычки. Наприме р, можно запи· сать такой оператор: char ner f = ' \ n ' ; а затем распечатать переменную n e r f , что обеспе чит пе редвижение на следую щую строку на принтере или на экране монитора . Теперь более подроб но изучим , что делает каждая управляю щая последователь· ность. Символ предупрежде ния ( \ а ) , введенный стандартом С90, вызывает появление звукового или визуального предупреждающего с игнала. Природа предупреждающего сигнала завис ит от оборудования, чаще других ис пользуется звуковой сигнал. (В не ко· торых с истемах предупреждающий с имвол не никак не проявляется . ) Стандарт ANSI тре бует , чтобы предупреждающий сигнал не менял активной позиции. Под активной пози-цией в стандарте понимается место в устройстве отображения (экран, телетайп, печатающее устройство и так дале е ) , в котором должен появиться следую щий символ, если бы не было предупреждающего символа . Короче говоря , активная позиция есть об об ще ние понятия экранного курс ора , с которым вы , с корее всего , привыкли рабо­ тать. Использование предупреждающего с имвола в программе , отоб раженной на эк­ ране , должно вызвать звуковой с игнал без перемеще ния экранного курсора. Далее, управляю щие последовательности \ Ь , \ f, \n, \r, \ t и \ v представляют собой обычные символы управления выходным устройством. Их проще всего описывать с уче­ том их воздействия на активную позицию . Возврат на одну позицию влево ( \Ь ) переме· щает активную позицию назад на один символ текущей строки. Символ перевода стра­ ницы ( \ f) переносит активную позицию в начало следую щей страницы . Символ новой строки ( \ n ) перемещает активную позицию в начало следующей строки. 96 Гл ава 3 С имвол во3врата каретки ( \ r ) переносит активную по3ицию в начало те кущей строки. Символ гори3онтальной табуляции ( \ t ) пере мещает активную по3ицию в еле· дую щую точку гори3онтальной табуляции (обычно эти точки находятся в по3ициях 1 , 9 , 1 7, 2 5 и так далее ) . Символ вертикальной табуляции ( \ v ) перемещает активную по3ицию в следую щую точку вертикальной табуляции. Эти управляю щие последовательности не обя3ательно работают на вс ех устройст· вах отоб ражения . Например, с имволы перевода страницы и ве ртикальной табуляции не приводят к пе реме ще нию курсора и вы3ывают появление на экране случайных символов, однако , они раб отают в соответствие с описанием , будучи переданными на принте р. Следую щие три управляю щие последовательности ( \ \, \ ' и \ " ) об еспечивают во3можность ис полЬ3ования символов \ , ' , и " в качестве символьных констант. (Так как эти с имволы служат для определе ния символьных констант как части команды print f ( ) , то может во3никнуть путаница , е сли вы восполЬ3уетесь ими обычным спо­ собом.) Предположим, что вы хотите распечатать следую щую строку: Gr amp s s e z , " а \ is а b a c k s l a s h . " Необходимо подготовить следую щий программный код: printf ( " Gr amp s s e z , \ " а \ \ is а backs l a s h . \ " \ n " ) ; Две последних формы ( \ О оо и \ xhh) - это специальные представления АSС П-кода . Чтобы представить тот или иной с имвол в виде его восьме ричного АSСП·кода , нужно поставить перед ним обратную косую че рту (\ ) и 3аклю чить всю эту конструкцию в одиночные кавычки. Наприме р, е сли ваш компилятор не распо3нает символ преду­ пре ждения ( \ а ) , вы можете восполЬ3оваться соответствую щим е му АSСП·кодом: Ь е ер = ' \ 0 0 7 ' ; В ы можете опустить нулевые старшие ра3ряды , так что 3апись ' \ 0 7 ' или даже ' \ 7 ' будет правильной. Такая форма 3аписи 03начает, что числа должны интерпретиро­ ваться как восьмеричные , даже если отсутствует ведущий О . Начиная с о стандарта С90, С предлагает третий вариант - исполЬ3ование шестна· дцате ричной формы для представления с имвольных констант. В этом случае 3а об· ратной косой чертой следуют с имволы х или Х и от одной до трех шестнадцатеричных цифр . Например, символу <C trl+P> с оответствует ше стнадцате ричный АSС П·код 1 0 ( 1 6 в де сятичной системе с числения ) , следовательно, его можно представить как ' \ x l O ' или ' \ X O l O ' . На рис . 3 .5 пока3аны некоторые И3 представительных целых ти· пов. Когда вы исполЬ3уете код ASC II, обращайте внимание на ра3личие между числами и символами чисел (цифр ы ) . Наприме р, символ 4 представлен в коде ASC II 3начением 52. Запис ь ' 4 ' представляет символ 4, но не числовое 3наче ние . В этом месте у вас могут во3никнуть три вопрос а : • Почему в послед'Нем примере управляю щие последователъ'Ности 'Не заключе'Нъt в оди'НО'Ч'НЪtе кaвъ;чки (print f ( " Gr amp s s e z , \ " а \ \ i s а b a c k s l a s h . \ " \ n " ) ; ) ? Koгдa кoн· кретный символ (не имеет 3начения, является ли он управляющей последова· тельностью или нет) есть часть строки символов, 3аключенной в двойные ка· вычки, не помещайте его в одиночные кавычки. Об ратите внимание , что ни один И3 символов, исполЬ3ованных в этом примере (G, r, а, m, р, s и так далее ) , н е 3аключены в одиночные кавычки. Строка с имволов, 3аклю че нная в двойные Представл ение данных в языке С 97 кавычки, называется символъ'Ной строкой. (Строки изучаются в главе 4.) Анало­ гично, оператор printf ( " Hello ! \ 0 0 7 \ n " ) ; рас печатает Hello ! и выдаст звуко­ вой сигнал, в то же время оператор printf ( " Hello ! 7 \ n " ) ; распечатает Hello ! 7 . Цифры, н е являю щиеся частью управляющей последовательности, расс матри­ ваются как об ычные символы , которые нужно рас печатать. • • Когда исполъзу10тся АSСП-кодъt, а когда управляю щие последователъ'Ности ? Если у вас имеется возмо жность выбора между приме нением одно й из с пе циальных управляющих последовательностей, с кажем ' \ f ' , и эквивалентного АSС П-кода , например, ' \ О 1 4 ' , отдавайте предпочтение ' \ f ' . В о-первых, при таком пред­ ставлении легче понять ее с мысл. Во-вторых, такая запись обладает лучшей пе­ реносимостью . Если вы работаете с системой, в которой не используется коди­ ровка ASCII, последовательность ' \ f ' , тем не менее, будет работать должным образом . Ее.ли 'НJЖ'НО исполъзоватъ 'Цифровой код, по'Чему 'Необходимо указъtватъ, скажем, ' \ 0 3 2 ' вместо 0 3 2 ? В о-первых, использование запис и ' \ 0 3 2 ' вместо 0 3 2 позволит дру­ гому пользователю , читаю щему ваш программный код, понять, что вы хотите представить код с оответствую ще го символа . Во-вторых, управляющая последо­ вательность, такая как \ 0 3 2 , может быть встроена в некоторую часть строки на С таким же с пособом, как и \ О О 7 в первом пункте . Примеры це.nочис.nенных констант Тип Шестиадцатеричиый Вось.меричиый Деся mич1U>l й char \ Ох4 1 \0101 Отсутствует int Ох4 1 0101 65 uns igned int O x 4 1u O l O lu 65u long Ох4 11 01011 651 uns igned long O x 4 1U1 0 1 0 1 U1 6 5U1 long long Ох4 111 0 1 0 1 11 6511 uns igned long l ong O x 4 1U11 0 1 0 1 U11 6 5U11 Ри с. 3.5. Видъt записи 'ЦМО'Числе'Н'НъtХ ко'Нста'Нт семейства i n t Печатаемые символы Функция printf ( ) использует спецификатор % с, чтобы показать, что должен рас­ печатываться с имвол. В с помните, что символьная переме нная хранится как одноб ай­ товое целое значение . По этой причине, е сли вы печатаете значе ние переме нной ти­ па char с обычным спе цификатором % d, вы получите целое число . Спе цификатор формата %с заставляет функцию print f ( ) отобразить символ, который имеет в каче­ стве кодового значение это целое число . В листинге 3 .5 приводится программа , кото­ рая представляет переменную типа char двумя с пособами. 98 Гл ава 3 Листинг 3.5. Программа charcode . с / * char code . c - о то бр аж а е т кодовое знач е ние символа * / #incl ude <s tdio . h > int main ( vo i d ) { char ch ; printf ( " Bв eди т e какой-нибудь символ . \ n " ) ; s c an f ( " % c " , & c h ) ; / * по ль зов атель вводит симво л * / p r i nt f ( " Koд символа % с р а в е н % d . \ n " , ch , ch ) ; r e turn О ; В от как выглядит приме р выполнения этой программы : Вв едите какой - нибудь символ . с Код симв ола С равен 6 7 . При работе с программой не забывайте нажать клавишу <Ente г> или <Retuгn> по­ сле ввода с имвола. Функция s can f ( ) зате м выбирает символ, который был введен с клавиатуры , а амперсанд ( & ) означает, что этот с имвол прис ваивается пере менной ch . Затем функция print f ( ) дважды рас печатывает значе ние переменной ch, первый раз как символ (на что указывает код спе цификатора % с ) , а второй раз как де сятичное це· лое число (на что указывает код спе цификатора % d ) . Об ратите внимание на то обстоя· тельство , что спе цификаторы функции printf ( ) определяют , как будут отображаться данны е , но не то , как они хранятся в памяти (рис . 3 .6 ) . 1 о ...._ о_._ ch .... .. . . _....о_ .. о...._ .. о__........ .. _ 1 " %с " 11 \ d " Память (ASC ll-кoд) Код Отображение Р ис. 3.6. Отображение даннъ1х на экране и хранение их в памяти Со знаком или без знака ? В некоторых реализациях языка С данные типа char имеют тип со знаком. Это зна· чит, что данные типа char могут принимать значения в диапазоне от -1 28 до 1 27. В других реализациях данные типа char выступают как данные без знака и могут прини· мать значения в диапазоне от О до 255 . В описании вашего компилятора должно быть указано, к какой разновидности принадлежит тип char , либо вы это можете узнать, изу· чив заголовочный файл limi ts . h, который будет рассматриваться в следую щей главе . Представл ение данных в языке С 99 В рамках стандарта С90 язык С позволяет ис пользовать клю чевые слова signed и unsigned с типом ch ar . Далее , независимо от того , какими являются данные тип char по умолчанию , данные со знаком будут иметь тип s i gned char , а данные без знака тип unsigned cha r . Эти вере ии типа char полезны, е сли упомянутый тип применяется для работы с не большими целыми числами. При работе с символами используйте стандартный тип char без модификаторов. тип Bool Т и п _Bool б ы л добавлен стандартом С 9 9 и он ис пользуется для представления бу­ левских значений, то есть, логичес ких значений tru e (истина ) и fal s e (ложь) . По­ скольку язык С ис пользует значение 1 для представления значения true и О - для представления значе ния fal s e , тип _Bool по суще ству есть целый тип, однако такой целый тип, который в принципе требует 1 бит памяти, поскольку этого достаточно , чтобы охватить ве сь диапазон от О до 1 . Переносимые ти пы: inttypes . h Суще ствуют ли е ще какие-то целочисленные типы? Таких типов больше нет, в то же вре мя возможны другие названия существую щих целочисле нных типов, которыми вы можете пользоватьс я . Можно подумать, что эти названия б оле е чем аде кватно представляют существую щие типы целочисле нных данных, однако с основными на­ званиями типов у вас могут возникнуть проблемы . Тот факт , что переменная име ет тип i nt, ничего не говорит о том, сколько разрядов он содержит, пока вы не ознако­ митесь с документацией по ваше й системе . Чтобы обойти эту проблему, стандарт С99 предлагает альте рнативный набор имен, который точно определяет, с какими данны­ ми вы имеете дело. Наприме р, имя i n t l б _t представляет 1 6-разрядный целочисле н­ ный тип со знаком, в то время как имя uint 3 2 _t - 3 2-разрядный целочисленный тип без знака. Чтобы эти имена можно было использовать в программах, вклю чите в нее заголо­ вочный файл inttyp e s . h . (Следует отметить, что во время подготовки данного изда­ ния некоторые компиляторы е ще не поддерживали эту возможность.) Этот файл ис­ пользует средство typ ede f (его краткое описание впервые дается в главе 5) для созда­ ния имен новых типов . Например, он делает имя uint 3 2 _t с инонимом, или альтернативным именем, стандартного типа с нужными характеристиками, возможно , это unsigned int в од­ ной системе и unsi gned long - в другой. Ваш компилятор обеспечит совме стимость любого заголовочного файла с вычислительной системой, на которой вы работаете . Эти новые обозначения называются типами с mО'Ч'Ной шири'Ной. О братите внимание , что в отличие от типа int, тип uint 3 2 _t не является клю чевым словом, таким обра­ зом, компилятор не распознает его , пока вы не вклю чите в программу заголовочный файл inttyp e s . h . Одна и з возможных проблем, возникаю щих при попытке установить типы с точ­ ной шириной, заклю чается в том, что та или иная конкретная система может не под­ держивать не которые выбранные вами типы , так что нельзя гарантировать доступ­ ность, с каже м , типа i n t 8 _t (8-разрядное целое со знако м ) . Чтобы избежать этой про­ блемы , стандарт С99 определяет второй набор имен, который гарантирует, что тип 1 00 Гл ава 3 достаточно велик, чтобы соответствовать требованиям с пецификации, и что нет дру­ гих типов, которые также могут обеспечить выполнение с оответствую щей раб оты и которые б ыли бы меньш е . Эти типы называются типами с мипималъпой ширипой. На­ пример, int_l e a s t B _ t представляет собой альтернативное имя наименьше го доступ­ ного типа , который может принять 8-разрядное целое значение с о знаком. Если бы наиме ньший тип в той или иной конкретной системе был бы 8-разрядным, то объяв­ лять тип int B_t не имеет смысла . В то же время , может оказаться доступным тип int _l e a s t B _ t и, возможно , он ре ализован как 1 6-разрядное целое. Разумеется , некоторых программистов больше интересует быстродействие , чем расход памяти. Для них стандарт С99 определяет набор типов, которые позволяют достичь максимальной скорости вычислений. Эти типы называются высокоскоростпъ1 · ми типами с мипималъпой ширипой. Например, i nt_ fas t B _t может быть объявле но как альте рнативное имя целочисле нного типа данных ваше й систе мы, который обеспе чи­ вает высокос коростные вычисле ния для 8-разрядных значе ний со знаком. И , наконец, не которых программистов может устроить только макс имально возможный в системе целочисленный тип; этот тип представляет имя in tmax_ t и он может принять любое допустимое значе ние целого числа со знаком. Аналогично, ui ntmax_t представляет тип наибольшего допустимого целочисленного значения без знака. Ф актичес ки эти типы должны быть больше , чем l o ng l o ng и unsigned l o ng, пос кольку реализациям С предоставляется возможность определять типы , которые не относятся к числу обяза­ тельных. Стандарт С99 вводит не только эти новые , пе реносимые имена типов, но также оказывает помощь при вводе и выводе . Например , функция printf ( ) требует с пеци­ альных спе цификаторов для конкретных типов. Таким образом, что вы должны сде­ лать, чтобы отоб разить значе ние типа i n t 3 2 _t, если она может потребовать с пеци­ фикатора %d для одного определения и спе цификатора % l d для другого? Стандарт С99 предлагает несколько макрокоманд (их опис ание приводится в главе 4), которые ис­ пользуются для отображения пе ре носимых типов. Наприме р, заголовочный файл inttyp e s . h определяет PRi d l б как строку, представляю щую соответствую щий с пеци­ фикатор (наприме р, hd или d) для 1 6-разрядного значения с о знаком. В листинге 3.6 представлен краткий пример, демонстрирую щий использование пе ре носимых типов и соответствую щих им спецификаторов . Листинг 3.6. Программа a1tnanes . с / * altname s . c - n ер ено симые имена для це лочисленных типов * / #incl ude <s tdio . h > #incl ude <i nttyp e s . h> 1 1 поддержив а е т перено симые типы int main ( vo i d ) { i n t l б_t me l б ; 1 1 me l б - э то 1 6 -р азря дная пер еме нная с о знаком me l б = 4 5 9 3 ; printf ( " Cнaч a л a пр е дположим , что int l б t име е т тип short : " ) ; p r i nt f ( " me l б = % hd\ n " , me l б ) ; printf ( " Дaлee не будем делать никаких пр едположе ний . \ n " ) ; printf ( "Bмecтo этого восполь зуйтесь \ "макрокомандой\ " из файла inttypes . h : " ) ; p r i nt f ( " me l б = % " PR i d l б " \ n " , me l б ) ; r e turn О ; Представл ение данных в языке С 1 01 В заве ршающем аргументе функции printf ( ) , PRi d1 6 замещается своим определе­ нием " hd" из файла inttyp e s . h , в ре зультате чего соответствую щая строка програм­ мы принимает такой вид : p r i nt f ( " me 1 6 = % " " h d " " \ n " , me 1 6 ) ; В то же вре мя С формирует из нескольких последовательных строк, заклю че нных в двойные кавычки, одну строку в кавычках, в результате чего эта строка программы принимает следую щий вид : p r i nt f ( " me 1 6 = % hd\ n " , me 1 6 ) ; Ниже показаны выходные данные программы ; об ратите внимание на то , что в рас­ сматриваемом примере также ис пользуется управляю щая последовательность \ " для вывода двойных кавыче к: С н ач ала nр едnо ложим , ч то int 1 6_t име е т тип s hort : me 1 6 = 4 5 9 3 Далее н е будем делать ник аких пр едположений . Вместо этого восполь зуйте сь "макрокомандой" из ф айла inttype s . h : me 1 6 = 4 5 9 3 В справочном разделе VI (приложе ние Б ) приводится полное описание доб авлений заголовочного файла inttyp e s . h, а также список вс ех макрокоманд с пецификаторов. П оддержк а станд арта С99 Поставщики компиляторов язы ка С подходил и к реал изаци и средств, введенных нов ы м стандартом С 9 9 , с разной степенью готовности и с разной оперативностью. В пе риод подготовки данной кни ги некоторые ко мпилято ры пока не поддерживал и заголо вочн ы й файл i nttyp e s .h и соответствующие средства. д а н н ы е типа fl oat, douЫe и long douЫe Разнообразие целочисленных типов в большинстве случаев облегчает разработку программного обеспечения и способствует повышению его качества. В то же время про­ граммы, ориентированные на работу в таких предметных областях, как математика и финансы , часто оперируют числами с плавающей запятой. В языке С такие числа имеют тип f l o at, do uЫ e или l o ng douЫ e . О ни соответствуют данным типа r e a l в таких язы­ ках программирования, как FORTRAN и Pascal. Как уже говорилось выш е , применение чисел с плаваю щей запятой дает вам возможность работать с гораздо большими диапа­ зонами чисел, вклю чая десятичные дроби. Представление чисел с плаваю щей запятой во многом подобно научной форме записи, которая используется учеными для записи очень больших и очень маленьких чисел. Рассмотрим эту форму записи. В научной форме записи числа представлены как десятичные числа, умноженные на с оответствую щую степе нь числа 1 0 . Рассмотрим несколько примеров. Число Научиал ф орма записи Экспоиенц иальиал форма з аписи 1 ООО ООО ООО 1 . Ох 1 0 9 1 . Ое9 123 О О О 1 . 2 3х 1 0 5 1 . 23е5 322 . 5 6 3 . 2256х102 3 . 2 2 5 6е2 0 . 000 056 5 . 6х 1 0 - 5 5 . 6 е- 5 1 02 Гл ава 3 В первом столбце показана обычная форма записи чис· ла, во втором столбце - научная форма записи, а в треть· ем - экспоненциальная форма записи, которая представля· ет собой научную форму запис и, обычно используемую при работе с компьютерами, при этом за обозначе нием е следу· ет показатель степени 1 0 . На рис . 3 . 7 приводится е ще не· сколько примеров чисел с плавающей запятой. Стандарт языка С требует, чтобы значе ния типа float имели, по меньше й мере, ше сть значащих цифр после точки и представляли диапазон чисел, по меньшей мере, от 1 0- э7 до l о•э 1• Пе рвое тре бование , например, означает, что тип float долже н представлять, по меньшей мере , первые ше сть цифр такого числа , как 3 3 .3 3 3 3 3 3 . Второе тре бование по достоинству оце нят те , кто опе рирует такими величина· ми, как масса солнца (2 .Ое3 0 килограмм ) , электричес кий за· Р ис. 3.7 . Примеръ� 'Чисел с плавающей запятой ряд протона (1 .бе-1 9 кулона ) или сумма государственного долга . Для хранения чисел с плавающей запятой системы используют 3 2 разряда. Восемь разрядов отводятся под значение экспоненты и е е зна· ка , остальные 24 разряда служат для представления неэкспоненциальной части числа , называемой мантиссой или зншчащей 'Частъю 'Числа, и е е знака . Для представления чисел с плавающей запятой в языке С имеется также тип douЫ e (он обеспечивает двойную точность) . Минимальный диапазон возможных зна· че ний для данных типа do uЫ e тот же , что и для типа fl o at, а минимальное количе ст· во значащих цифр увеличено до 1 0 . Для представле ния данных типа douЫ e обычно используется 64 разряда вме сто 3 2 . В некоторых с истемах используются все 32 допол· нительных разряда неэкспоненциальной части. Это увеличивает количе ство знача· щих цифр и уменьшает ошибку округления. В других системах часть этих разрядов применяется для увеличения количества значащих цифр экспоненты , благодаря чему расширяется диапазон возможных значений этого типа . При каждом из этих подходов количество значащих цифр не бывает меньше 1 3 , что с избытком удовлетворяет ми· нимальным тре бованиям стандарта . Язык С допускает третий тип данных с плавающей запятой: long douЫ e . Этот тип вводит с целью достижения еще большей точности, чем точность, обеспе чивае мая типом douЫ e . Однако С гарантирует только то , что точность типа l ong douЬl e , по меньше й мере, не уступает точности типа douЫ e . Об ъявление переменных с плавающей запятой О бъявле ние и инициализация пе ременных с плавающей запятой осуществляется так же , как и родственных элементов из с емейства целочисленных типов. Расс мотрим не сколько приме ров: fl o at no ah , j o nah ; do uЫ e trouЫ e ; fl o at pl anck = 6 . б З е - 3 4 ; l o ng douЫ e gnp ; Представл ение данных в языке С 1 03 Константы с плавающей запятой При запис и константы с Шiавающей запятой вы можете выбрать одну из несколь­ ких возможносте й. В основной форме записи константа с плаваю щей запятой пред­ ставляет собой некоторую последовательность цифр со знако м , вклю чаю щая и деся­ тичную точку, за которой следует буква е или Е , а за ними следует экс поне нта со зна­ ком , представляющая целую степень числа 1 0 . Вот два приме ра правильной записи констант с Шiавающей запятой: - 1 . 5 6Е+ 1 2 2 . 8 7 е-3 Знак Шi ю с вы ве зде можете опускать. Вы можете также опустить де сятичную точку ( 2 Е 5 ) или экс поне нциальную часть ( 1 9 . 2 8 ) , но не то и другое одновременно. Вы мо­ жете обойтис ь без дробной части ( 3 . Е l б ) или целой части ( . 4 5Е- 6 ) , но не без обоих компоне нтов с разу (это уже чере счур ! ) . В от е ще нес колько допустимых констант с Шiавающей запятой: 3 . 1 4 159 .2 4е16 . S E-5 10 0 . В константах с плавающей запятой не должны присутствовать пробелы . Например, вот недопустимая константа: 1 . 5 6 Е + 1 2 По умолчанию компилятор полагает, что константы с Шiавающей запятой имеют двойную точность. Предположим , например, что s ome представляет с обой перемен­ ную с плаваю щей запятой, и у вас имеется следую щий оператор: s ome = 4 . 0 * 2 . 0 ; Тогда значения 4 . О и 2 . О запоминаются как данные типа douЬl e , при этом под ка­ ждое из них отводятся (обычно) 64 бита памяти. Для вычисления произведение при­ меняется арифметика с двойной точностью , и только после выполне ния операции умножения результат приводится к размеру do uЫ e . Это обес печивает более высокую точность вычислений, однако замедляет вычисле ния . В языке С име ются с редства , которые позволяют изменить такое поведение ком­ пилятора , установленное по умолчанию , путем использования суффикс а f или F, что заставит компилятор расс матривать константу с плавающей запятой как тип; приме­ рами могут служить 2 . 3 f и 9 . l 1 E 9 F . Суффикс ы 1 или 1 определяют константу как чис­ ло типа l ong douЫ e , например, 5 4 . 31 и 4 . 3 2 е 4 1. Об ратите внимание на то , что букву 1 трудне е пе репутать с 1 (единица ) , чем букву 1 . Если число с Шiавающей запятой не соде ржит суффикса , это число имеет тип douЫ e . Стандарт С99 ввел новый формат для представле ния констант с Шiавающей запя­ той. Он ис пользует шестнадцатеричный префикс ( О х или О Х ) с ше стнадцате ричными цифрами, р или Р вместо е или Е и экспоне нту, которая является сте пенью числа 2, а не 1 0 . Такое число может принимать следую щий вид: O x a . l fp l O . l f - 1 / 1 6-ая плю с 1 5 / 2 5 6-ая и p l O - 2 10 или 1 024, что в сумме дает 1 0364.0 в де сятичном эквиваленте . а - это 1 0 , 1 04 Гл ава 3 Не все компиляторы С поддерживают это средство , введенное стандартом С99. Печать значений с плавающей запятой Функция printf ( ) использует спе цификатор формата % f для пе чати чис ел типа float и douЫ e в десятичном представле нии и спе цификатор % е для пе чати в экспо­ не нциальном представлении. Если ваша с истема поддерживает шестнадцатеричный формат чисел с плавающей запятой, введенный стандартом С99, вы можете ис пользо­ вать а или А вместо е или Е . Тип long douЫ e требует с пецификаторов % L f, % 1е и % 1а для печати данных этого типа . Об ратите внимание , что как тип f l o at, так и тип douЫ e используют спецификаторы % f, % е или % а для вывода . Это объясняется тем, что язык С автоматиче ски расширяет значения типа fl o at до типа douЫ e , когда они передаются в качестве аргументов любой функции, такой как, например, print f ( ) , в прототипе которой тип аргумента явно не определе н. Код в листинге 3 . 7 служит ил­ лю страцией такого поведения . листинг 3.7. Программа showf_pt . c / * s howf_pt . c - о то бр аж а е т з н ач ени е типа float двумя спо собами * / #incl ude <s tdio . h > int main ( vo i d ) { fl o at ab o at = 3 2 0 0 0 . О ; do uЫ e abet = 2 . 1 4 е 9 ; l o ng douЫ e dip = 5 . 3 2 е - 5 ; printf ( " % f може т быть з аписано как % e \ n " , aboat , abo at ) ; printf ( " % f може т быть з аписано как % e \ n " , abet , ab e t ) ; printf ( " % f може т быть з аписано как % e \ n " , dip , dip ) ; r e turn о ; В ыполнение этой программы дает следую щие ре зультаты : 3 2 0 0 0 . 0 0 0 0 0 0 може т быть з аписано как 3 . 2 0 0 0 0 0 е+ 0 4 2 1 4 0 0 0 0 0 0 0 . 0 0 0 0 0 0 може т быть з а писано как 2 . 1 4 0 0 0 0 е+ О 9 0 . 0 0 0 0 5 3 может быть з апис ано как 5 . 3 2 0 0 0 0 е - 0 5 Этот пример может служить иллю страцией вывода данных по умолчанию . В сле­ дую щей главе мы об судим , как управлять внешним видом этих выходных данных за счет установки ширины поля и количества позиций справа от десятичной точки. Переполнение и потеря значимости в операциях с плавающей запятой Предполагая , что наибольшее возможное значе ние типа float равно примерно 3 .4Е3 8 , выполните следую щие операции: fl o at toobig = 3 . 4 Е 3 8 * 1 0 0 . 0 f ; printf ( " % e \ n " , toobi g ) ; Что произойдет в данном случае? Это пример перепол11,е11,ия (ove rflow ) , когда в ре­ зультате вычислений получается слишком б ольшое число , чтобы его можно было пра- Представл ение данных в языке С 1 05 вильно представить. Поведе ние системы в таких случаях обычно не определено, но в рассматриваемом случае С присваивает пе ременной tooЬig с пециальное значение , которое обозначает беско'Не-ч'Ностъ (infinity ) , и функция printf ( ) в качестве значения отоб ражает либо in f, либо in fini ty (либо другие разновидности) . Что можно с казать о деле нии оче нь малых чисел? В этом случае ситуация более сложная . Вс помните , что число типа float хранится в виде сочетания показателя сте­ пени и значащей части, или ма'Нmиссъ1. В рассматриваемом случае это число, имеющее минимально возможный показатель степени, а также наиме ньшее значение , которое использует все доступные биты , отведенные для представления мантис сы. Именно это число является наиме ньшим числом, представленным с наибольшей точностью , кото­ рая достижима для значения типа float. Теперь разделим его на 2 . В общем случае это приводит к уменьше нию показателя степени, но в данном случае показатель уже дос­ тиг своего нижне го предела. В такой ситуации компьютер сдвигает разряды мантисс ы вправо, освобождая пе рвую позицию и теряя последню ю двоичную цифру. Аналогич­ ная картина возникает, если выбрать 10 в качестве основания с истемы с числения , взять число с четырьмя значащими цифрами, например, О . 1 23 4Е-1 0 , и разделить его на 1 0 , получив при этом 0 . 0 1 23Е- 1 0 . Вы получите ответ, но в проце с с е деления вы по­ теряете цифру. Т акая ситуация называется потерей з'Ншчимости (underflow ) , а язык С рассматривает значе ния с плавающей запятой, которые утратили полную точность типа , как суб'Нормалъ'Н'Ые. Таким образом, деление наиме ньшего положительного значе­ ния с плаваю щей запятой на 2 дает в результате субнормальное значение . Если вы вы­ полните деление на достаточно большое значе ние , вы поте ряете все цифры и получи­ те в результате О. В настояще е время библиотека С может предоставить в ваше распо­ ряже ние функции, которые дают вам возможность проверить, не дают ли ваши вычисле ния результаты в виде субнормальных значе ний. Суще ствует еще одно с пециальное значение с плавающей запятой: NaN ("not-a­ numb er" - "не число" ) . Например, вы пе редаете функции a s i n ( ) некоторое значение , а она возвращает угол, для которого пе реданное значе ние является синусом. Однако значение синуса не может быть б ольше 1 , в с илу чего функция не определена для зна­ че ний, превышающих 1 . В таких случаях функция возвращает значение NaN, которое функция print f ( ) отображает как nan, NaN или как-то похоже. Ош и бк и округл ен и я дан н ых с п лавающей зап ятой Возьмем некото рое число, прибавим к нему 1 и вычтем из сум м ы исходное число. Что мы получ им в резул ьтате? М ы получим 1 . Вычисл ител ьные операции, вы полняемые над числами с плава ющей за пятой, как показы вает приведенн ы й ниже пример, могут дать другой результат: / * floaterr . c - служит иллюстр а цией ошибки округ ления * / #i nclude < s tdi o . h> int main ( void) { float а , Ь ; Ь = 2 . Ое2 0 + 1 . 0 ; а = Ь - 2 . О е2 0 ; print f ( " % f \ n " , а ) ; r etur n О ; 1 06 Гл ава 3 В резул ьтате вы полнения этой програ м м ы получены следующие результаты : 0 . 0 0 0 0 0 0 +- стар ая версия компиля тор а gcc в опер ационной системе Linux - 1 3 5 8 4 0 1 0 5 7 5 8 7 2 . 0 0 0 0 0 0 +- Turbo С 1 . 5 4 0 0 8 1754 6854 4 . 0 0 0 0 0 0 +- CodeWarrior 9 . 0 , MSVC++ 7 . 1 П рич ина поя вления таких странных резул ьтатов состо ит в то м, что ко мпьютер не сле­ дит за тем, чтобы под числа с плавающей запятой было отведено стол ько десятичных позиций, скол ько нужно для правил ьного в ы полнен ия а рифметических операций. Ч исло 2. Ое20 представлено цифро й 2, за кото рой следует 20 нулей, прибавляя 1 , вы пытае­ тесь изменить 2 1 -ю цифру. Чтобы эта операция была вы полнена правил ьно , програ м ма должна иметь возможност ь сохранять ч исло , состоящее из 2 1 цифры . Обычно ч исло с плавающей запятой представлено шестью ил и семью цифра ми, умноженных на осно­ вание системы сч исления в соответствующей степени, увел ичивающе й ил и уменьшаю­ щей это числовое значение . Эта попытка обречена на неудачу. С другой сторон ы , есл и вы используете число 2 . Ое4 вместо 2.Ое20, вы получите правил ьн ы й ответ, поскол ьку вы пытаетесь изменить пятую цифру, а ч исла с плавающей запятой обладают доста­ точной точностью для правил ьно го вы полнения такой опера ции . Ко м плексные и м н и мые ти п ы В о многих научных и технических рас четах используются компле кс ные и мнимые числа. Стандарт С99 подде рживает эти числа , правда с не которыми оговорками. В рамках автономных реализаций, например, при реализации встроенных процесс оров, нет необходимости во всех этих типах. (Микропроцес с ор видеомагнитофона , воз­ можно , не нуждается в комплекс ных числах во вре мя выполнения своих функций.) В свою очередь мнимые типы не относятся к числу обязательных. Существуют три комплексных типа , получивших названия float _Complex, douЫ e _ Comp l e x и long douЫ e _Compl ex. Переме нная типа float _Comp l e x , например, будет содержать два значения типа flo at, одно из них представляет действительную часть комплекс ного числа, а другая представляет мнимую его часть. Аналогично , существуют три мнимых типа , названные float Imaginary, douЫ e Imaginary и long douЫ e Imaginary . В клю чение заголовочного файла compl ex . h позволяет вам делать подстановки слова complex (компле ксное ) вме сто _Complex и слова imaginary (мнимое ) вместо Imaginary, и появляется возможность использования символа I для представления квадратного корня из -1 . за п редела ми базов ых ти пов С писок фундаментальных типов данных завершен. Для одних из вас этот спис ок может показаться слишком длинным. В то же время многие другие могут подумать, что не обходимы новые типы . Что можно с казать о типе символьной строки? В языке С нет такого типа , в то же время он обеспечивает успешную раб оту с о строками. Впер­ вые со строками вы ознакомитесь в главе 4 . В С присутствуют также и другие типы , явля ю щиеся производными о т базовых ти­ пов. Эти типы включают мас с ивы, указатели, структуры и объединения. И хотя все они являются предметом обсужде ния в других главах, тем не мене е , некоторые указа­ тели просочилис ь в примеры, рассмотренные в настоящей главе . ( Указател.ъ (pointe r ) указывает н а место в памяти, в котором хранится переме нная или какой-то другой объект данных. Префикс &, который ис пользуется в функции s c an f ( ) , с о здает указа­ тель, сообщаю щий этой функции, куда следует поме стить информацию . ) Представл ение данных в языке С 1 07 Свод к а: баз овые ти п ы дан ных Кл ючевые слова: Названия основных типов данн ых образуются с помощью 1 1 ключе вых слов: int, long , short, uns igned, char , fl o at, douЬ l e , signed, B o o l , Complex и Imagi nary. Цел ые числа со знаком: Это могут быть как положител ьны е , так и отри цател ьные числа : • • • • int основной тип целочисленны й тип данных в конкретной выч ислительной сис­ те ме. Язык С отводит под тип i nt не менее 1 6 разрядов. - s hort ил и short int максимальное целое ч исло типа short не превосходит наибольшего целочисленно го значения типа int. Язык С гарантирует, что под тип s hort будут отведены , по меньшей мере, 16 разрядов. - long ил и l ong int может хранить целое число, которое, по меньше й мере, не меньше наибол ьшего числа типа int или больше е го. Язык С гарантирует, что под тип long отводятся 32 разряда . - long l o ng ил и long long int этот тип может быть целы м числом, которое, по меньше й ме ре, не меньше наибол ьшего ч исла типа l ong, а, возможно, и больше его . Под тип l ong l o ng отводятся, по меньшей мере , 64 разряда памят и. - Обычно тип long имеет бол ьшую дл ину, чем тип short, а дл ина типа int совпадает с дл иной одного из этих т и пов. На пример, системы на персонал ьных ком пьюте рах, функ­ ционирующих под управлением DOS, имеют 1 6- разрядные типы s hort и int и 32разрядны й тип long , систем ы , работа ющие на базе Windows 95, предлага ют 1 6- разряд­ н ы й тип s hort и 32-разрядные ти пы int и long. П ри желании вы можете испол ьзовать ключе вое слово signed с л юбы м из типо в со знако м , тем самым я вно указы вая на то , что этот типы со знаком. Цел ые без знака: Эти типы могут хранить только нулевое ил и положительные значения. В силу этого об­ стоятельства увеличивается диапазон наибольшего возможного положител ьного числа. Ставьте ключевое слово uns i gned перед нужн ы м типом: uns igned int, unsigned long, unsigned s hort. Одно кл ючевое uns igned означает то же, что и unsigned int. С и мвол 1:о1 : Существуют типографские символ ы , такие как А, & и + . По определению тип char ис­ пол ьзует 1 байт па мяти дп я представления конкретного символа . Исто рически сложи­ лось так, что этот байт символа в бол ьшинстве случаев имеет дл ину 8 разрядов, но она может быть 1 6 разрядов и даже бол ьше , е сл и возникнет необходи мост ь представить базовы й набор символо в. • char кл ючевое слово для этого типа данных. В одних реализа циях испол ьзуется тип char со знако м , в то время ка к в других реал изациях испол ьзуют тип char без зна ка. Язык С предоставляет возможность испол ьзовать кл ючевые слова s igned и unsigned с тем, чтобы вы могл и задать нужную форму данных. - Булевские значения : Булевский тип представляет значения true (истина) и fal s e (ложь); язык С испол ьзует 1 для предста вления true и О для представления fal s e . • кл ючевое слово для этого типа . Это значение int без знака, оно должно быть достаточно бол ьшим, чтобы соответст вовать диапазону от О до 1 . _Bool - 1 08 Гл ава 3 В ещественные ч исла с плава ющей запято й : Ч исла с плавающей запятой могут принимать положительные и отрицател ьные значения : • • float - основной тип данных с плавающе й запятой в выч ислительной систе ме; он может х ранить, по меньшей ме ре, шесть значащих цифр. douЫ e - этот тип (возможно) служит предста вления больших, чем тип float, чисел с плавающей запятой. Он допускает больше значащих цифр (по меньшей мере, 1 0 , но обычно их бол ьше) и, возможно, большие значения показателя степени. • long douЫ e - этот тип (возможно) служит для представления еще бол ьших чисел с плава ющей запято й. Он допускает бол ьшее число значащих цифр и, возможно , бол ьших значений показателе й степени, чем тип do uЫ e . Комплексн ь1е и мни м ы е ч исла с плава ющей запято й : М нимые типы не принадлежат к ч ислу базовых. В основу вещественных и мним ых ком­ понентов положены соответствующие вещественные типы: • • • float _Compl ex douЫ e -Comp l ex long douЫ e Compl ex • float Imagi nary • douЫ e Imaginary • long douЫ e Imagi nary Свод к а: объяв лен и е п р остой перем енн ой 1. 2. Выберите нужн ы й вам тип данных. Выберите и мя для этой переменно й, испол ьзуя с этой цел ью все допустимые сим­ вол ы . 3. Дп я объя вления переменной испол ьзуйте следующий формат: специфика тор - типа имя-переменной; Ко мпонент спе цифика тор - типа образуется из одного ил и большего ч исла клю­ чевых сло в типо в; вот пример та кого объя вления : int er e s t ; unsigned short cas h ; 4 . Вы можете объя вить сразу нескол ько пере менных одного и то го же типа, отделяя имена переменных друг от друга запятыми. Н иже приведен при мер та кого объя в­ лен ия : char ch , ini t , ans ; 5. Вы можете инициал изировать переменную в операторе объя вления : float mas s = 6 . О Е 2 4 ; Размеры ти пов В таблицах 3 .3 и 3 .4 приведе ны размеры данных различных типов в некоторых широко используе мых опе рационных с редах языка С. (В не которых таких средах вам предоставляется выбор размеров данных.) Каков разме р данных в вашей с истеме? Попробуйте выполнить программу, код которой пока зан в листинге 3 .8 , чтобы по· лучить ответ на этот вопрос . Представл ение данных в языке С 1 09 Таб.nица 3 .3 . Ра3меры це.nочис.nенных типов (в битах) д.nя некоторых И3вестных вычис.nите.nьных систем Тип Macintosh Мetrowerks CW (По уммчаиию ) LiJШx ua ПK ШМ РС с Windows ХР и Windows NT ANSI C Minimum char 8 8 8 8 int 32 32 32 16 s hort 16 16 16 16 long 32 32 32 32 long long 64 64 64 64 Таб.nица 3 .4. Информация. х арактери3ующая типы с п.nавающей 3апятой. испо.nЬ3уемые в некоторых И3вестных системах Macintosh Мetrowerks CW (По ум ол1lаиию) Linux ua ПK ШМ РС с Windows ХР ANSI C Minimиm и Windows NT flo at 6 цифр от -37 до 38 6 цифр от -37 до 38 6 цифр от -37 до 38 6 цифр от -37 до 37 douЫ e 18 цифр от -493 1 до 4932 15 цифр от -307 до 308 15 цифр ОТ -307 ДО 308 10 цифр от -37 до 37 long douЫe 18 цифр от -4931 до 4932 ОТ -493 1 ДО 4932 18 цифр 18 цифр ОТ -493 1 ДО 4932 lО цифр от -37 до 37 Тип Для каждого типа верхняя строка показывает количество значащих цифр, а вторая строка - диапазон, в котором показатель степени принимает значения (по основанию 10). листинг 3.8. Программа type size . с / * typ e s i z e . c - р аспеч а тка р азмеров типов * / #incl ude <s tdio . h > int main ( vo i d ) { / * С т андар т с 9 9 предусматрив а е т специфик атор % zd для р а змеров типов * / printf ( " Tип int име е т р азмер % u байт ( а , ов ) . \ n " , s i zeo f ( int ) ) ; printf ( " Tип char име е т р а змер % u байт ( а , ов ) . \ n " , s i z eo f ( char ) ) ; printf ( " Tип l o ng име е т р а змер % u байт ( а , ов ) . \ n " , s i zeo f ( long ) ) ; printf ( " Tип do uЫ e име е т р а змер % u байт ( а , ов ) . \ n " , s i z eo f ( do uЬl e ) ) ; r e turn О ; В языке С имеется встрое нная операция s i zeo f , которая возвращает размер типа в байтах. (Не которые компиляторы тре буют спецификатора % l u вместо %u для вывода значений, возвращаемых s i zeo f . Это объяс няется тем, что С оставляет определе нную свободу в отношении фактического целочисленного типа без знака, который исполь­ зует функция s i zeo f при возврате ре зультатов . Стандарт С99 предлагает ис пользовать спе цификатор % zd для этого типа , и вы должны пользоваться именно указанным с пе­ цификатором в тех случаях, когда ваш компилятор его поддерживает.) 110 Гл ава 3 В ыходные данны е , полученные в ре зультате выполне ния программы, представлен· ной в листинге 3 .8 , имеют следую щий вид: Тип int име е т р а змер 4 байт ( а , о в ) . Тип char име е т р а змер 1 б айт ( а , ов ) . Тип long име е т р а змер 4 б айт ( а , ов ) . Тип douЫ e име е т р а змер 8 байт ( а , ов ) . Эта программа определяет разме ры только четырех типов, но вы ле гко можете прис пособить ее с таким рас четом, чтобы она определяла размеры любого другого инте ресующего вас типа . Обратите внимание на тот факт, что тип char обязательно должен иметь разме р 1 байт, поскольку С определяет размер одного байта через тип char . Следовательно , в системе с 1 6-разрядным типом char и 64·разрядным типом douЫ e функция s i z e o f сообщит, что тип douЫ e имеет разме р 4 байта . Вы можете прос мотреть заголовочные файлы limi ts . h и fl o at . h, чтоб ы получить боле е под· робную информацию о пределах типов . (В следую ще й главе мы продолжим анализ этих двух файлов.) Между прочим, обратите внимание на то , что последний оператор printf ( ) про­ граммы занимает две строки. В ы можете разб ить этот оператор на любое количе ство частей при условии, что разбиение не проходит внутри раздела оператора, заключен· ного в кавычки, или в середине какого-то слова . Исп ол ьзовани е тип ов дан н ы х При разработке программы обращайте внимание на то , какие переме нные вам тре буются, и какие типы должны иметь эти пере менные . Скорее все го , для представ· ления чисел вы выбе рете int или, возможно , float, и char для с имволов . О бъяв· ляйте их в начале функций, которые их используют. Выбирайте такие имена пе ре· менных, которые в той или иной степени отражают назначе ние хранящейся в них информации. Инициализируя ту или иную пе ременную , следите за тем, чтобы тип прис ваиваемого значения соответствовал типу пе ременной. Ниже представлены со· ответствую щие примеры: - int appl e s = 3 ; int oranges = 3 . 0 ; / * Пр авиль но * / / * Непр авиль ная форма * / В отношении не совпадения типов С более либерале н, че м, скажем, такой язык программирования , как Pascal. Компиляторы С допускает второй вид инициализации, но при этом они выдают с ообщения об ошиб ке , особенно в тех случаях, когда вы уста· новили высокий урове нь предупреждений. Не следует потакать плохим привычкам. Когда вы инициализируете пе ременную одного числового типа значением другого числового типа , С пре образует тип этого значе ния в тип пе ременной. Это означает, что может произойти потеря не которых данных. В каче стве примера рас смотрим еле· дую щие инициализации: int co s t = 1 2 . 9 9 ; fl o at pi / * пер еменная int иници ализир уе тся значением типа douЫ e * / 3 . 1 4 1 5 9 2 6 5 3 6 ; / * пер еменная типа float инициализируе тся значением типа douЫ e * / 111 Представл ение данных в языке С Первое объявле ние прис ваивает значение 1 2 переме нной co s t ; при преобразова­ нии значений с плавающей запятой в целые значе ния компилятор С вместо округле­ ния просто отбрас ывает дробную часть числа (выполняет усечепие) . Во втором объяв­ лении происходит некоторая потеря точности, поскольку для типа fl o at гарантиру­ ется только точность в пределах шести цифр. Компиляторы могут (но не обязаны ) выдавать предупреждаю щие с ообщения , когда вы выполняете такую инициализацию . Вы можете столкнуться с такой проблемой во вре мя компиляции программы , пока­ занной в листинге 3 . 1 . Многие программисты и организации придерживаются систематических соглаше­ ний, регламентирую щих прис воения имен пе ременным, соглас но которым имя указы­ вает на тип переменных. Например , вы можете воспользоваться пре фиксом i для указания типа int, и префиксом us чтобы указать тип unsigned short, откуда следу­ ет, что i s mart не медленно рас познается как пе ременная типа int , а us verysmart как переменная типа unsigned s hort. _ _, _ _ - Аргументы и ош ибки п ри и х исполь з овани и Ранее в этой главе уже отме чалас ь необходимость корректного ис пользования функции printf ( ) , и с ейчас мы еще раз подчеркиваем важность такого подхода . Как уже говорилось выш е , элеме нты информации, передаваемые функции, называются аргумептами. Н априм е р , вызов функции p r i nt f ( " До бр о пожало в а т ь . " ) с одержит один аргумент " До бро пожалов ать . " . Последовательность с имволов в кавычках, на­ приме р, " До бро пожалов ать . " , называется строкой. Мы буде м изучать строки в гла­ ве 4. В настоящее время важным моментом является то, что строка , даже если она со­ держит несколько слов и знаков пре пинания, рассматривается как один аргуме нт . Аналогично , вызов функции s c an f ( " % d" , &weight ) содержит два аргуме нта: " % d" и &weight. Для отделения аргументов функции друг от друга в С используются запя­ тые . Функции printf ( ) и s ca n f ( ) отличаются от других функций тем, что они не ог­ раничиваются заране е установленным количеством аргуме нтов . Наприме р, мы вызы­ вали функцию printf ( ) с одним , двумя и даже тремя аргументами. Чтобы программа работала должным образом, она должна знать, сколько аргументов получает функция . Функции printf ( ) и s c an f ( ) используют пе рвые аргументы для того , чтобы указать, сколько дополнительных аргументов будет пе редаваться . Дело в том , что каждая с пе­ цификация формата в начальной строке указывает на наличие еще одного дополни­ тельного аргумента . Например , показанный ниже опе ратор имеет два спецификатора формата, %d и % d: - printf ( " % d котов съедают % d банок тунца\ n " , cats , cans ) ; В этом случае наличие двух спе цификаторов означает, что функция должна при­ нять два аргумента , и в с амом деле , дальше следуют два эти аргумента cat s и can s . О бязанность программиста заклю чается в том , чтобы убедиться , что количе ство спе цификаций формата с оответствует числу дополнительных аргументов, и что тип спе цификатора с оответствует типу значения. В настояще е время в языке С имеется механизм прототипирования функций, который прове ряет , правильно ли задано чис­ ло аргументов функции и имеют ли эти аргументы соответствую щий тип, однако он не - 112 Гл ава 3 работает в случае функций print f ( ) и s can f ( ) , так как при каждом вы3ове они при· нимают ра3ное число аргументов. А что прои30Йдет, если программист не с правится с этой обя3анностью ? В каче стве примера рас смотрим программу, представле нную в листинге 3 .9 . листинг 3.9. Программа badcowtt . с / * b a dcount . c н е в ерно е число арг ументо в * / #incl ude <s tdio . h > int main ( vo i d ) { int f 4; int g 5; fl o at h = 5 . 0 f ; printf ( " % d\ n " , f , g ) ; / * и з быточ ное чи сло аргумен тов * / printf ( " % d % d\ n " , f ) ; / * н едо с т а точно е число арг ументов * / printf ( " % d % f\ n " , h , g ) ; / * н епр авиль ные типы спецификатор ов * / r e turn О ; - В ыходные данны е , полученные в ре 3ультате выполне ния этой программы в с исте· ме Microsoft Visual С++ 7 . 1 (под управление м Windows ХР ) , имеют следующий вид : 4 4 34603777 о 0 . 000000 Далее , выходные данные , полученные в ре3ультате выполнения этой программы в системе D igital Mars (под управлением Windows ХР ) , имеют такой вид: 4 4 423947 6 о 0 . 000000 И , наконец, выходные данные , получе нные в ре 3ультате выполнения этой про­ граммы в с реде ра3работки Me trowerks CodeWarrior D evelopment Studio 9 (под управ· лением Macintosh OS Х ) , имеют вид: 4 4 3327456 10 750525 4 4 0 . 0 0 0 0 0 0 Обратите внимание , что исполЬ3ование спе цификатора % d для отображе ния 3На· че ния f l o at не приводит к пре обра3ованию 3начения float в ближайшее 3начение int; наоборот , то , что при этом отображается, похоже на информационный мусор. Аналогично , применение спе цификатора % f для отображения 3наче ния i nt не приводит к пре обра3ованию целочисленного 3начения в 3наче ние с плавающей 3апя· той. Наряду с этим, ре3ультаты , которые вы получаете в случае недостаточного коли· чества аргументов или в случае неправильно ука3анных типов аргументов, отличаются от платформы к платформе. Ни один И3 компиляторов , которые мы исполЬ3овали для выполнения данной про­ граммы, не предъявил никаких претен3иЙ к программному коду. Не б ыло также ника· ких с ообщений об ошибках в проце с с е выполне ния программы. Некоторые компиля· Представл ение данных в языке С 113 торы вполне могут обнаружить подобного рода ошибки, однако стандарт языка С от них этого не тре бует . В силу этого об стоятельства , компьютер может и не выявить та­ ких ошибок, но поскольку во вс ех других отношениях программа ведет себя безупреч­ но , вы , возможно , и с ами не заметите этих ошибок. Если программа не печатает ожи­ даемое число значе ний или печатает не ожиданные значения , прове рьте , ис пользова­ ли ли вы нужное количе ство аргументов в вызове функции printf ( ) . (Между прочим, программа l int, осуще ствляю щая проверку с интаксиса в системе Unix, которая намного привередливее, чем компилятор Unix, отметила наличие оши­ бочных аргументов функции printf ( ) . ) Ещ е оди н п ри м ер : уп равля ю щие посл едовател ьности В качестве примера выполним еще одну программу печати, в которой используют­ ся некоторые специальные управляющие последовательности символов языка С . В ча­ стности, программа, представленная в листинге 3 . 1 0 , показывает, как работают воз­ врат на одну позицию влево ( \Ь ) , табуляция ( \ t ) и возврат каретки ( \ r ) . Эти понятия возникли с тех времен, когда компьютеры использовали для вывода телетайпы, а се­ годня они не все гда успе шно приживаются в с о временных графиче ских инте рфе йсах. Наприме р, представленная ниже программа не работает так, как зде с ь опис ано , в не­ которых реализациях языка С на компьютерах Mac intosh. листинг 3.1 0. Проrрамма e s cape _ c / * e s cap e . c - исполь зов ание символов упр авляющих по следов ательно стей * / #incl ude <s tdio . h > int main ( vo i d ) { fl o at s al ary ; printf ( " \ aBв eдитe предпочитаемую в ами сумму месячного жалования : " ) ; / * 1 */ printf ( " $ \ Ь \ Ь \ Ь \ Ь \ Ь \Ь \ Ь " ) ; / * 2 * / s c an f ( " % f " , & s al ary ) ; printf ( " \ n \ t $ % . 2 f в месяц соо тв етств у е т $ % . 2 f в г од . " , s al ary , s al ary * 1 2 . 0 ) ; / * 3 * / p r i nt f ( " \ Oг o ! \ n " ) ; / * 4 * / r e turn О ; ____ Ка к и м будет резул ьтат в ы полнения э той п рограммы Пройде мся п о этой программе и пос мотрим, как она будет раб отать в реализации языка С, соответствующей стандарту ANSI С. Первый оператор print f ( ) (идет под номером 1 ) подает звуковой с игнал (вызванный последовательностью \ а ) , а зате м пе­ чатает следую щую фразу: Вв едите пр едпочитаемую в ами сумму месячного жало в ания : 114 Гл ава 3 Поскольку в конце строки отсутствует последовательность \ n , курсор устанавлива­ ется в позиции, следую щей за двоеточием . Второй оператор printf ( ) продолжает пе чать с того ме ста , где закончил работу первый, таким образом , после того , как он завершит с вою работу, на экране будет отоб ражена следую щая фраза: Вв едите желаемую в ами сумму месячного жалов ания : $ ���- Пробел между двоеточием и знаком доллара появился зде сь в связи с тем, что строка во втором опе раторе начинается с пробела . Результатом семи символов воз­ врата на одну позицию будет перемещение курсора на с емь позиций влево . Курсор проходит че ре з с емь с имволов подчеркивания и располагается непос редстве нно по­ сле знака доллара. Как правило, возврат на одну позицию влево не приводит к удале­ нию символа , чере з который проходит курс ов , но в некоторых систе мах может быть примене н деструктивный возврат на одну позицию (забой ) , и результат выполнения этой простой программы окажется другим. В этом ме сте вы вводите с клавиатуры свой ответ , с кажем 2000.00. Теперь строка принимает следую щий вид : Вв едите пр едпочитаемую в ами сумму месячного жало в ания : $ 2 0 0 0 . 0 0 С имволы, которые вы вводите с клавиатуры, заме няют символы подчеркивания , после чего вы нажимаете клавишу <Enteг> (или <Retuгn>) , чтоб ы закончить ввод ва­ шего ответа, после че го курс ор переместится в начало следую щей строки. В ыходные данные третьего опе ратора printf ( ) начинаются с последовательности \n \ t. Символ перевода строки пе ремещает курсор в начало следую ще й строки. Сим­ вол табуляции пе ремещает курс ор в следую щую позицию табуляции в этой строке, об ычно , но не обязательно, в столб ец 9 . Затем пе чатается остальная строка. По за­ верше нии выполнения этого оператора на экране появляются следую щие данные : Вв едите пр едпочитаемую в ами сумму месячного жало в ания : $ 2 0 0 0 . 0 0 $ 2 0 0 0 . 0 0 в месяц соотв е тству е т $ 2 4 0 0 0 . 0 0 в год . Поскольку опе ратор printf ( ) не использует с имвол пере вода строки, курсор оста­ ется в позиции не посредственно после завершающей точки. Четвертый оператор pr int f ( ) начинается с последовательности \ r . О на помещает курсор в начало текущей строки. С этой позиции отоб ражается фраза Ог о ! , а \n пе ре­ водит курс ор на следую щую строку. В окончательном варианте выходные данные на экране имеют вид: Вв едите пр едпочитаемую в ами сумму месячного жало в ания : $ 2 0 0 0 . 0 0 Ог о ! $ 2 0 0 0 . 0 0 в месяц р авно $ 2 4 0 0 0 . 0 0 в год . Сброс буфера выходн ых да н ных Когда фактичес ки функция printf ( ) отсылает выходные данные на экран? Перво­ начально операторы printf ( ) пе ре сылают выходные данные в промежуточную об­ ласть хранения данных, называемую буфером. Вре мя от вре мени материал, хранящийся в буфере, перес ылается на экран. Правила стандартного С, определя ю щие , когда вы­ ходные данные пересылаются из буфера на экран, понятны : они пересылаются на эк­ ран, как только буфе р будет заполнен, когда появляется с имвол новой строки или ко­ гда предполагается ввод данных. (Перес ылка выходных данных из буфера на экран Представл ение данных в языке С 115 или в файл называется сбросом буфера. ) Например, два первых оператора не заполняют буфе р и не содержат с имвола новой строки, но непосредственно за ними следует опе­ ратор s can f ( ) , который запрашивает ввод . Это инициирует пересылку выходных данных оператора printf ( ) на экран. В ы мо жете столкнуться с б олее ранними реализациями , в которых оператор s can f ( ) не вызывает очистки буфера, что, в с вою очередь, приводит к тому, что про­ грамма начинает поиск ваших входных данных, не выводя на экран приглашения . В этом случае вы можете воспользоваться символом новой строки для очистки буфера. Программный код можно изменить следую щим образом: printf ( " Bв eди т e пр едпочит аемую в ами сумму м е сячного жалов ания : \ n " ) ; s c an f ( " % f " , & s al ary ) ; Этот программный код работает независимо от того , производит ли предстоящий ввод данных очистку буфера или нет. Однако он устанавливает курсор на следую щей строке , благодаря чему входные данные не будут разме щаться в той же строке, в кото­ рой находится приглашение . Другим решением является использование функции f flus h ( ) , опис анной в главе 1 3 . К л юч евые п онятия В языке С реализовано удивительно большое количество числовых типов. Этот факт отражает наме рение языка С не с оздавать дополнительных трудностей на пути программиста. В место постулирования , скаже м , того факта, что одного целочисле н­ ного типа вполне достаточно , С пытает предоставить программисту возможность вы­ бора типа (с о знаком или б е з ) и размера , которые наилучшим образом с оответствуют потре бностям конкретной программы . Числа с плаваю щей запятой фундаментально отличаются от целых чисел на кон­ кретном компьюте ре . О ни хранятся и обрабатываются различными с пособами. Два 3 2-разрядных элемента памяти могут с одержать один и тот же набор битов, но если один из них будет инте рпретирован как значение с плаваю щей запятой, а другой как значение типа l ong, они будут представлять совершенно разные и абсолютно не свя­ занные между с об ой значения. Наприме р, на е сли перс ональном компьютере вы рас­ смотрите набор битов, представляющих число 256.0 с плаваю щей запятой и попытае­ тесь интерпретировать е го как значение типа long , вы получите 1 1 3 246208. Язык С позволяет вам писать выраже ния со сме шанными типами данных, но при этом он вы­ полняет автоматиче ское приведение типов с те м, чтобы фактические вычисления выполнялись над данными одного типа . В памяти компьютера символы представлены числовыми кодами. Код ASC II полу­ чил наибольшее распространение в США, однако язык С поддерживает ис пользова­ ние и других кодов. С имвольная константа - это символьное представление числовых кодов, используе мых в компьютерных систе мах - она состоит из символов, заключен­ ных в одиночные кавычки, например, ' А ' . Р езюм е В языке С имеется большое разнообразие типов данных. Основные типы данных подразделяются на две категории: целочисленные типы данных и данные с плавающей запятой. Двумя отличительными особенностями целочисленных типов являются объем 116 Гл ава 3 памяти, выделяемый для размещения данных того или иного типа , и знак числа, то есть, со знаком данный тип или без знака . Наименьшим целочисленным типом является char , который, в зависимости от реализации, может быть со знаком или без знака. Вы можете использовать s igned char и uns igned char , чтобы явно определить тот тип, какой вам нужен, однако обычно вы выбираете эти типы, когда работаете с небольшими целыми числами, а не с символьными кодами. К другим целочисленным типом относятся short, int, long и long long. Язык С гарантирует, что каждый из этих типов не меньше , чем предшествующий тип. Каждый из этих типов есть тип со знаком, но вы можете исполь­ зовать клю чевое слово uns i gned для создания соответствующего типа без знака : uns igned short, uns igned int, uns igned long и uns igned long long. Либо вы можете добавить мо­ дификатор s igned с тем, чтобы явно объявить, что данный тип является типом со знаком. Наконец, существует также тип _Bool, представляю щий собой тип без знака , способный принимать значения О и 1, которые представляют значения fal s e и true. Тремя типами с плавающей запятой являются float, douЫ e и новый для стандарта ANSI С тип long douЫ e . Каждый из них, по меньшей мере, не меньше предыдущего типа . При не обходимости та или иная реализация может поддерживать компле кс ные и мнимые типа, используя с этой целью клю че вые слова _Comp l ex и _ Imagi nary в с о­ четании с клю че выми словам типа с плаваю щей запятой. Например , могут суще ство­ вать типы douЫ e Complex и тип fl o at I maginary. Целые числа могут быть представлены в десятичной, восьмеричной и ше стнадца­ теричной форме . Ведущий символ О показывает, что число представлено в восьме­ ричной форм е , ведущие символы О х или О Х указывают, что это шестнадцатеричное число. Наприме р, 3 2 , 0 4 0 и О х 2 0 - это де сятичное , восьмеричное и шестнадцатерич­ ное представления одного и того же значения . Суффикс 1 или 1 указывает, что значе­ ние имеет тип l o ng, а 11 или 11 - тип long l ong. С имвольные константы представляются путем заключе ния с имвола в одиночные кавычки, например ' Q ' , ' В ' и ' $ ' . Управляющие последовательности в языке С , такие как ' \ n ' , представляют определенные непечатаемые символы. Вы можете ис пользо­ вать форму ' \ О О 7 ' для представления символа в коде ASCII. Числа с плавающей запятой могут быть записаны в форме с фикс ированной деся­ тичной точкой, например, 9 3 9 3 . 9 1 2 или в экспоненциальном представлении, напри­ мер, 7 . 3 8 Е 1 0 . Функция printf ( ) позволяет пе чатать различные типы значений, ис пользуя с пе­ цификаторы , которые в своей просте йше й форме состоят из знака проце нта и буквы, указывающей тип, например, %d или % f. В оп росы для са м окон троля Ответы на эти вопросы находятся в приложении А. 1 . Какие типы данных вы будете использовать для каждого из следую щих типов данных? а. Населе ние Парижа . б . Стоимость копии фильма на DVD-дис ке . в. Буква , которая чаще других встре чается в данной главе . г. Количе ство раз, сколько эта буква встречается в данной главе . 2 . Назовите причины , по каким вы будете применять тип l o ng вме сто типа int? Представл ение д анных в языке С 117 3 . Какие перенос имые типы вы можете ис пользовать, чтобы получить 32-разрядное целое число со знаком? Приведите а р гуме нты в пользу ваше го выбора . 4. О пределите тип и зна че ние каждой из следую щих констант : а. ' \Ь ' б. 1 0 6 6 в. 99 . 4 4 г. О ХАА д. 2 . О е З О 5 . Не кто на писал програ мму с о ш иб ками. П о мо гите найти эти о ш иб ки. include <stdio . h > main flo at g; h ; flo at tax , r ate ; g е2 1 ; tax r at e * g ; = = 6 . О пределите тип данных (каким о н ис пользуется в о пе рато рах объя вления ) и формат с пе цификатора функции p r i nt f ( ) для каждо й из следую щих ко нстант: Констант а а. 12 б. о хз в. 'С' г. 2 . 3 4Е07 д. ' \040 ' е. 7 о ж. 61 з. 6 . 0f Тип Спец ификат &J! - 7. О пределите тип данных (каким он ис пользуется в о пе рато рах объя вления ) и формат с пе цификатора функции print f ( ) для каждо й из следую щих констант (предполагается , что ис пользуется 1 6-ра з рядный тип int ) : Константа а. 12 б. 2 . 9е 0 51 в. 1 г. 100000 д. ' \n ' е. 20 . 0 f ж. Ох4 4 s 1 Тип Спец ификат &J! 118 Гл ава 3 8 . Предположим, что программа начинается с о следую щих объявлений: int imate = 2 ; long s hot = 5 3 4 5 6 ; char gr ade = ' А ' ; float log = 2 . 7 1 8 2 8 ; Вставьте нужный тип с пецификатора в следую щие операторы print f ( ) : print f ( " C тaвкa на % р авна % к l . \ n " , imate , s hot ) ; рrint f ( " Рейтинг % не соотв е тству е т % по зиции . \ n " , log , grade ) ; 9 . Предположим, что ch представляет с обой переме нную типа char . Покажите , как присвоить этой пере менной символ возврата каретки, ис пользуя для этой цели соответствую щую управляю щую последовательность, де сятичное значе­ ние , восьме ричную с имвольную константу и шестнадцате ричную с имвольную константу (предполагается использование значе ний в коде ASCII ) . 10. Ис правьте приведенную ниже довольно-таки глупую программу. (С имволом / в языке С обозначается операция деле ния . ) void main ( i n t ) / Эта пр огр амма не содержи т оши бок / { cows , l e g s integer ; p r i nt f ( " C кoлькo коров ьих ног вы насчитали ? \ n ) ; s ca n f ( " % c " , l e g s ) ; COWS = l e g s / 4 ; printf ( " О тсюда следу е т , ч т о име е тся % f коро в . \ n " , cows ) 11. Определите , что представляют с об ой каждая из следую щих управляющих по­ следовательностей: а. б. в. г. \n \\ \" \t Уп ражн ен и я по програм м ирован и ю 1 . Экспе риме нтальным путем определите , как ваша с истема решает проблему пе­ реполне ния при выполне нии операций над целыми числами, над числами с плавающей запятой и проблему потери значимости при выполне нии операций над числами с плавающей запятой, то е сть, напишите программу, которая стал­ кивается такими проблемами. 2 . Напишите программу, которая приглашает ввести не которое значение в коде ASC II, например, 66, а затем печатает символ, которому соответствует введе н­ ный код . 3 . Напишите программу, которая выдает предупредительный звуковой сигнал, а зате м выводит на пе чать следую щий текст: Испуг анная внезапно раздавшимся звуком, Салли воскликнула : "Что это было ? " Представл ение данных в языке С 119 4. Напишите программу, которая с читывает некоторое число с плавающей запя­ той и печатает его с начала в десятичном представлении, а затем в экспоненци­ альном представлении. В ыходные данные должны быть отображены в следую­ ще м формате (фактиче ское число отображаемых цифр зависит от конкретной вычислительной системы ) : Введено число 2 1 . 2 9 0 0 0 0 или 2 . 1 2 9 0 0 0 е + 0 0 1 . 5 . В году примерно 3 . 1 56 x l 0 7 секунд. Напишите программу, которая приглашает ввести возраст в годах, а затем выводит на экран эквивалентное значение в се­ кундах. 6 . Мас с а одной молекулы воды приблизительно равна 3 .0x l 0-23 грамм. Кварта во­ ды вес ит примерно 950 грамм. Напишите программу, которая приглашает вве­ сти не которое значение объе ма воды в квартах и отображает количе ство моле· кул воды в этом объеме . 7. В дю йме 2 .54 сантиметра . Напишите программу, которая приглашает вас ввести рост в дюймах, после чего выводит на экран этот рост в с а нтиметрах. Либо, ес­ ли вам так б ольше нравится , запрашивает рост в сантиметрах и пере водит его в дюймы . ГЛА ВА 4 с и м вол ь н ые ст ро ки и фо р м а т и ро ва н н ы й ввод- в ы вод в этой главе: • Ф ункция: s trlen О • Кл ю ч ев ы е сл ова: const • символьные строки • • созд ание и хранен и е символьных строк • • Испол ьзование фун кц ии s trlen O для из м ерен ия длины строки Ди ректива # define п реп роцессора с и м одифи катор c onst стандарта ANSI с. испол ьзуем ы е для с озд а н ия символьных констант Испол ьзова н и е фун кций printf О и s canf О для чтен ия и отображен ия символьных строк о с новное в нимание в этой главе будет уделе но вопросам ввода и вы вода . Изу­ чив да нную гла ву, вы с можете наделить свои про гра ммы индивидуальными сво йства ми, сделав их интерактивными и вос пользовавшись с имвольными стро ками. Кроме того , будут более детально рас с мотре ны такие функции ввода-вы вода языка С, как p r i nt f ( ) и s canf ( ) . Эти две функции представля ют собой программные инст руме нты, предназначенные для с вязи с пользо вателя ми и для фор матирова ния выходных данных в соответствие с существую щими потреб ностями и вкус ами. На ко­ не ц, вы вкратце ознако мите с ь с таким важным с р едством яз ыка С , как препроце с с о р ( процессор предва р ительно й о б работки) , и узнаете , как следует о пределять и ис поль­ зо вать с имволичес кие ко нстанты. В водн ая п рограм м а С корее всего , с е йчас , подоб но то му, как в на чале других глав, вы ожидаете анализа о че редной простой учеб но й про гра ммы . В лист инге 4 . 1 представле на име нно такая про гра мма , которая ре ализует диало г с пользо вателе м. Чтоб ы в не сти некоторое раз­ нообразие , в данной про грамме использован новый стиль ко мментариев, рекоме н­ дуе мый стандартом С 9 9 . 122 Гл ава 4 Листинг 4.1 . Программа ta1kЬack. c 1 1 t a l kb a c k . c - довольно нас тырная информативн ая пр ограмма #incl ude < s tdio . h> #incl ude < s tring . h> 1 1 для про то типа функции strlen ( ) #de fi ne DEN S I T Y 6 2 . 4 1 1 пло тность ч елов е к а в ф унтах на кубич е ский фут int main ( ) { fl o at weight , volume ; int s i z e , l ett er s ; char name [ 4 0 ] ; 1 1 имя представля е т со бой массив из 4 0 симв олов рrintf ( " Здр авс твуйте ! Как вас зовут ? \ n " ) ; s c an f ( " % s " , name ) ; printf ( " % s , сколько вы в е сите в фунтах ? \ n " , name ) ; s c an f ( " % f " , &weight ) ; s i z e = s i zeo f name ; l e tt e r s = s trl en ( name ) ; volume = weight / DENS I T Y ; printf ( " Xopoшo , % s , в аш о бъем с о с тавл я е т % 2 . 2 f кубич е с ких фу тов . \ n " , n ame , volume ) ; printf ( " К тому же в аше имя состоит из % d символо в , \ n " , l etter s ) ; printf ( " и мы р аспо л аг аем % d бай т ами для ег о сохр анения . \ n " , s i z e ) r e turn О ; ,· Запустив на выполне ние программу tal kb a ck, получаем следую щий ре зультат : Здр авствуйте ! Как в ас зовут? ша риа Шарла , сколько вы в е сите в фунтах? 13 9 Хорошо , Шарла , в аш о бъем составля е т 2 . 2 3 ку бич е с ких фу тов . К тому же в аше имя состои т из ш е с ти символо в , и мы р а с полаг а ем 4 0 байтами для его сохранения . Отметим ос новные особенности этой программы , с которыми вы , возможно , сталкиваетес ь впе рвы е : • • • • Для хране ния символьных строк в программе используется массив, в котором хранится символъная строка. Имя пользователя с читывается в мас сив, который в этом случае представляет собой набор из 40 последовательных байтов памяти, причем каждый из них способен запомнить одно символьное значение . Рас сматриваемая программа использует спецификшцию преобразования % s для об· работки ввода и вывода строки. О братите внимание , что переменная name , в отличие от пере менной wei ght, не использует префикс & в тех случаях, когда она указывается в функции s can f ( ) . (Далее вы увидите , что как значение &weight, так и значение n ame представляют с обой адре с а . ) В программе используется пре процессор С , который дает возможность опреде· лить символьную константу DENS I T Y для представления значе ния 6 2 . 4 . Для вычисления длины строки в данной программе применяется функция s tr len ( ) . Символ ьные строки и фор матированный ввод-вы вод 1 23 Подход языка С к процедурам ввода-вывода может показаться не сколько услож­ не нным по сравнению , скажем, с языком Basic . Однако благодаря этой сложности достигается более сове ршенное управление вводом-выводом и б олее высокая эффек­ тивность программы. Как только вы привыкнете к этому, новые возможности пока­ жутся просто удивительными. Проведем исследование этих новых идей. Строки си м вол ов: введени е С имвольная строка представляет собой последовательность из одного или б ольше­ го числа символов, например : " Э то длинная с трока симво лов . " Двойные кавычки не являются частью строки. О ни сооб щают компилятору, что охватывают строку, в то вре мя как одиночные кавычки идентифицируют конкретный символ. М асси в зна ч ен и й типа char и ну левой си мвол В языке С нет никакого специального типа для представления строк. Вместо этого строки хранятся в массивах значений типа char . Символы , образующие строки, хранят­ ся в смежных ячейках памяти, по одному символу в ячейке , массив состоит из смежных ячеек памяти, так что строка размещается в массиве естественным образом (рис . 4 . 1 ) . lzl фlg l lwl e l n l t l ltlh l e l lslt l r l iln l g l s l lol t l lml yl l hl e l ф l ф l\ol .А. Кажда я я чейка имеет размер 1 байт Ри с. 4.1 . Строка в массиве .А. Нулевой сим вол Обратите внимание , что на рис . 4 . 1 в последней позиции массива показан символ \ О . Это иулевой ( nи ll) символ, и язык С использует е го для того, чтобы отметить конец строки. Нулевой символ - это не цифра нуль; это непечатаемый символ, значение ко­ торого в кодировке ASC II (или эквивалентной) равно О . Все строки языка С всегда с о­ храняются с этим символом в конце . То, что в конце массива всегда проставляется ну­ левой символ, означает, что масс ив долже н иметь, по крайне й мере, на одну яче йку больше , чем количество символов, которое вы хотите сохранить. Что же представляет собой масс ив? Вы можете рас сматривать массив как не сколь­ ко ячеек памяти, рас положе нных в ряд. Если вы предпочитаете б оле е формальную и точную формулировку, то мас с ив - это упорядоченная последовательность элементов данных одного типа . В расс матривае мом приме ре создается мас сив из 40 ячеек памя­ ти, или элемеитов, каждый из которых может хранить одно значение типа char . При этом используется следую щее объявле ние : char name [ 4 0 ] ; Квадратные скобки после name говорят о том , что это мас с ив . Число 4 О внутри с ко­ бок показывает число элементов в мас с иве. Клю чевое слово char иде нтифицирует тип каждого эле мента (рис . 4 . 2 ) . 124 Гл ава 4 Распределить 1 байт char ch ; ch Распределить 5 байт char name [ S ] ; name Р и с . 4. 2 . Обмвление переменной и обмвление массива Использование символьных строк тепе рь кажется очень сложным ! Вам нужно соз· дать мас с ив, разместить символы строки друг за другом , к тому же не забыть доб авить символ \ О в конец строки. К счастью , компьютер и с ам может выполнить большинст­ во этих деликатных действий. Использова ние строк На примере программы , представленной в листинге 4.2, можно убедиться , как просто на самом деле пользоваться строками. листинг 4.2. Программа praisel . с / * pr ai s e l . c - - и спол ь з у е т р азличные пр е дставл ения строк * / #incl ude <s tdio . h > #de fi ne PRAI SE " К акое пр екр а сное имя ! " int main ( vo i d ) { char name [ 4 О ] ; p r i nt f ( " Kaк в а с зовут ? \ n " ) ; s c an f ( " % s " , name ) ; рrintf ( " Здр ав с твуйте , % s . % s \ n " , name , PRAI SE ) ; r e turn О ; С пецификатор % s дает задание функции print f ( ) рас печатать строку. С пецифика­ тор % s появляется дважд ы , поскольку программа печатает две строки: одна хранится в массиве name , а другая представлена константой PRAI SE. Выполне ние программы p r ai s el . с дает на выходе следую щий результат: Как вас зовут? Хи пари Б абб :п с Здр авствуйте , Хилари ! Какое пр е кр а сно е имя ! 125 Символ ьные строки и фор матированный ввод-вы вод В ам не нужно самому помещать нулевой с имвол в масс ив nam e . Эту задачу для вас выполняет функция s c an f ( ) при вводе. При этом нет необходимости доб авлять нуле­ вой символ в строковую константу PRAI SE . Ниже мы рассмотрим действия опе ратора #de fi n e , а пока просто обратите внимание на то , что двойные кавычки, в которые за­ клю чается текст, следую щий непосредстве нно за именем PRAI SE , идентифицируют этот текст как строку. Компилятор сам позаботится о добавлении нулевого с имвола . Обратите внимание (и это важно ) на то обстоятельство , что функция s c an f ( ) чи­ тает только имя Хилари, а не Хилари Бабблс. После того, как функция s can f ( ) начи­ нает с читывать входные данные , она останавливает чтение на первом же из встреч­ ных так называемых " пробелънъ�х " символов, то е сть, символов пробела , табуляции и но­ вой строки. Следовательно , рассматриваемая программа останавливает прос мотр имени в тот момент, когда наталкивается на символ пробела между словами Хилари и Баббл с. В ообще говоря , функция s c an f ( ) используется со спе цификатором % s только для чте ния одиночных слов , а не целых фраз, таких как строка. В языке С доступны и другие функции, предназначенные для ввода данных, например, функция g ets ( ) , вы­ полняю щая обраб отку строк. В последую щих главах мы подвергне м эти функции б о· лее глубоким исследования м . Различия между строками и символами Строковая константа " х " - это отнюдь не то же с амое, что и символьная константа ' х ' . Одно из различий заклю чается в том, что ' х ' принад· лежит к базовому типу ( char ) , а " х " - это произ· водный тип, мас с ив значе ний типа char . Второе различие с о стоит в том, что " х " на с амом деле представляет собой строку, состоящую из двух символов, име нно , из ' х ' и ' \ О ' , как показано на рис . 4.3 . Символ 'х' Строка "х" �� �1 1 х \О 1 " Нулевой символ завершает строку Ри с. 4.3. Символ ' х ' и строка "х " Фун кция s trlen ( ) В предыдущей главе кратко затрагивалас ь опе рация s i z e o f, которая определяет размеры объектов в байтах. Функция s tr l e n ( ) определяет длину строки в символах. Поскольку для размещения одного символа требуется один байт, можно предполо· жить, что приме нительно к строке обе операции дадут одинаковый результат, но это не так. В качестве приме ра рассмотрим код в листинге 4.3 . Листинг 4.3. Программа praise2 . с / * pr ai s e 2 . c * / #incl ude <s tdio . h > / * предо ст авля е т про т о тип s tr l e n f ( ) * / #incl ude <s tring . h> #de fi ne PRAI SE " К акое пр екр а сное имя ! " int main ( vo i d ) c h a r name [ 4 0 ] ; p r i nt f ( " Kaк в а с зовут ? \ n " ) ; s c an f ( " % s " , name ) ; рrintf ( " Здр ав с твуйте , % s . % s \ n " , name , PRAI SE ) ; 126 Гл ава 4 printf ( " B aшe имя со стоит из % d символов и з анима е т % d яч еек памя ти . \ n " , s t r l e n ( name ) , s i zeo f name ) ; рrintf ( " Хв але бная ф р а з а содержи т % d символо в " , s t r l e n ( PRAI SE ) ) ; p r i nt f ( " и з анимае т % d яч е ек памя ти . \ n " , s i z е о f PRAI SE ) ; r e turn О ; Если вы используете версию компилятора, не подде рживаю щую ANSI С , вам по­ тре буется убрать строку: #i nclude < s tr i ng . h> Заголовочный файл s tring . h соде ржит прототипы функций для нескольких функций обработки строк, в числе которых и s t r l e n ( ) . Этот файл мы еще буде м рас· сматривать в главе 1 1 . (Ме жду прочим, некоторые системы UNIX, разработанные до появления стандарта ANSI, используют заголовочный файл s tr i ngs . h вместо s tring . h, в котором содержатся объявле ния строковых функций.) В общем случае С делит библиотеку функций на семейства логически связанных функций и предоставляет отдельный заголовочный файл для каждого с емейства . На· приме р, функции print f ( ) и s can f ( ) принадлежат семейству стандартных функций ввода·вывода и ис пользуют s t dio . h в качестве своего заголовочного файла . Функция s trl e n ( ) объединяет вокруг себя нес колько других функций обработки строк, таких как функции копирования строк, поиск по строкам, и для этого семейства отводится заголовочный файл s tring . h. Следует обратить внимание на то , что в листинге 4 .3 используются два метода об­ работки длинных операторов printf ( ) . Первый метод распространяет действие од­ ного оператора printf ( ) на две строки (можно разбить строку на два аргумента , но не в се редине строки; то е сть, не между кавычками) . Второй метод подразуме вает приме­ не ние двух опе раторов printf ( ) , чтобы пе чатать только одну строку. С имвол новой строки ( \ n ) присутствует только во втором операторе. В результате выполнения про­ граммы получается следую щий обмен данными: Как вас зовут? Иор r ав Б аттеркап Здр авствуйте , Морг ан . Какое пр е кр а сно е имя ! В аше имя состоит из ш е с ти букв и з анимае т 2 9 яч е ек памя ти . Хв алебная фр а з а сод ержит 1 6 символов и з анимае т 2 1 яч е ек памя ти . Посмотрите, что происходит . Мас с ив name содержит 40 ячеек памяти, и операция s i zeo f сообщает об этом факте . Однако для размещения имени Морг ан необходимо только первых ше сть ячеек, и об этом сообщает функция s t r l e n ( ) . С едьмая яче йка в массиве name с одержит нуле вой символ, и об этом также сообщает функция s trl en ( ) после заве ршения подс чета символов. Иллюстрацией этих идей служит рис . 4.4. Когда вы приступаете к манипуляциям с PRAI SE, вы обнаруживаете , что функция s trl e n ( ) снова подсчитывает точное количество символов в строке (вклю чая пробелы и знаки препинания) . Операция s i z e o f выдает количество символов на единицу боль­ ше, так как она учитывает нулевой символ, проставленный в конце строки. Вы не задае­ те компьютеру, какой объем памяти нужно заре зе рвировать для разме ще ния фразы. О н с а м должен подсчитывать количество символов , заклю ченных в двойные кавычки. Символ ьные строки и фор матированный ввод-вы вод 1 27 Завершающий нулевой символ 6 символов м g а n 1 "Мусор" (в общем случае ) 1 1 \0 Ри с. 4.4. Фу 'Н:к-ция s t r l e n () зн,ает, когда остан,овитъся С другой стороны , в предыдущей главе операция s i zeo f употреблялась с круглыми скобками, а в этой главе мы их не видим . Ис пользуете ли вы круглые скобки или нет, зависит от того , хотите ли вы получить размер типа или конкретное числовое значе­ ние . Круглые скобки требуются для определе ния разме ра типов, но не обязательны для подсчета размеров конкретных переменных. Иначе говоря , допускается конст­ рукция s i z eo f ( ch ar ) или s i z e o f ( fl o at ) , но также разрешается s i zeo f name или s i zeo f 6 . 2 8 . Тем не менее , в этих случаях правильным будет и применение круглых скобок, например, si z ео f ( 6 . 2 8 ) . В последне м примере функции s t r l e n ( ) и s i zeo f ис пользовались с довольно три­ виальной целью , а именно , с целью удовлетворить возможное любопытство пользова­ телей. На самом деле функции s t r l e n ( ) и s i zeo f - это важные инструментальные средства программирования . Наприме р, функция s trl e n ( ) весьма полезна во всех программах, работающих с символами и строками, как будет показано в главе 1 1 . Теперь пе рейдем к рассмотрению оператора #de fi n e . К онс танты и препроцессор с Иногда вам приходится ис пользовать константы в программе . Например, длину окружности можно вычислить по формуле : ci r cum f e r ence = 3 . 1 4 1 5 9 * di ame ter ; Здесь константа 3 . 1 4 1 5 9 представляет собой общеизвестную константу 11. Чтобы воспользоваться этой константой, просто введите е е фактичес кое значе ние , как в приведе нном выше примере. Однако существуют се рьезные основания для того , что­ бы ис пользовать вместо числа символън,у10 кон,стан,ту. Другими словами, вы можете вос­ пользоваться оператором, подобным показанному в приведе нном ниже примере, и за­ ставить компьюте р подставлять фактическое значе ние позже : ci r cum f e r ence = pi * di ame ter ; В чем заклю чаются пре имущества ис пользования символичес кой константы? В о­ первых, имя является б олее информативным , нежели число. С равните два следую щих утве рждения : owed = 0 . 0 1 5 * hou s e value ; 1 1 з адолженно с т ь = 0 . 0 1 5 * рыночная - с тоимо с ть -дома ; owed = t axrate * hou s evalu e ; 1 1 з адолженность = ставка-налог ового-о бложения * рыночная - стоимость -дома ; Если вы читаете длинную программу, то вторая версия лучше вос принимается , чем первая . 128 Гл ава 4 Кроме того , предположим, что вы используете констангу в нескольких ме стах про­ граммы , и возникает не обходимость изме нить ее значение . В конце концов, даже на­ логовые отчисле ния иногда меняются . В таком случае достаточно всего лишь изме­ нить определение символической константы, а не искать и менять соответствую щим образом значение этой констангы в разных местах программы . В с е это так, но как установить символьную константу? Один из способов заклю ча­ ется в том, чтоб ы объявить переменную и присвоить ей значе ние , равное требуемой константе . Вы можете сделать это следую щим образом: fl o at taxrate ; taxrate = 0 . 0 1 5 ; В этом случае мы имеем символьное имя, но при этом taxr ate остается перемен­ ной, и ваша программа может случайно изменить ее значение . К счастью , в языке С доступны и б оле е удачные реализации. Намного более оригинальная идея предус матривает ис пользование препроце ссора С. В главе 2 вы уже видели, как препроцессор использует директиву #include для включе ния информации из другого файла . Наряду с этим препроцессор позволяет оп­ ределять константы . Просто добавьте в верхню ю часть файла , с одержащего вашу про­ грамму, строку, подоб ную следую щей: #de fine TAXRAT E 0 . 0 1 5 После компиляции вашей программы значение О . 0 1 5 будет подставлено повсюду, где вы указываете TAXRATE. Это называется подстаповкой во врем.я комnил.яt&ии. К моменгу вы­ полнения программы все подстановки уже выполнены (рис . 4.5 ) . Констангы, опреде­ ленные таким образом, часто называются символU'ческими копстаптами или ли тер алами. Обратите внимание на формат . Сначала идет директива #de fine. За не й следует символическое имя (TAXRATE) констангы, после чего идет значе ние ( О . 0 1 5 ) констан­ ты. О братите внимание на то , что рас сматривае мая конструкция не использует знак = . О боб ще нный формат выглядит следую щим образом: #de fine NАМЕ v alue В ы можете заме нить NАМЕ на символичес кое имя , выбранное по с об стве нному ус­ мотре нию , и указать с о ответствую щее значение для val u e . Точка с запятой в этом случае не используется, поскольку это механизм замены, а не опе ратор языка С. По­ че му имя TAXRATE представлено заглавными буквами? Представление имен констант в символах верхнего регистра - это давно сложившаяся традиция в языке С . Если вы встречаете такое имя в недрах программы , вы сразу же ос ознаете , что пе ред вами кон­ станта , а не переме нная . Представление имен констант заглавными буквами - еще один спос об повысить удоб очитаемость программы . Разумеется, ваши программы бу­ дут работать, даже если вы не воспользуетес ь заглавными буквами для представления констант, но лучше вс е-таки взять этот прием на вооружение . Другой, менее распространенный с пособ выделения име н констант заклю чается в использовании префикс ов с_ или k _ для выделения констант, при этом возникают имена наподобие c_l evel или k_line . Имена , выбирае мые для символических констанг, должны удовлетворять тем же правилам, что и име на переменных. Вы можете использовать символы верхнего и нижне го ре гистров, цифры и знак подчеркивания . Первый с имвол не может быть цифрой. В листинге 4 .4 показан простой пример . Символ ьные строки и фор матированный ввод-вы вод #define taxrate 0 . 0 15 int main (void) ( Ьill=taxrate * sum; ( То, что вы ввели с клавиатуры 1 int main (void) Результаты работы препроцессора Ьill=0 . 0 15 * swn; l КО МП ИЛЯТО Р Ри с. 4. 5 . То, что в·ы ввели с клавиатуръt, и то, что бъtло откомпилировано 1 29 130 Гл ава 4 Листинг 4.4. Программа piz z a . с / * pi z z a . c - - исполь зуе т кон станты , опр е деленные в контексте пиццы * / #incl ude <s tdio . h > #de fi ne PI 3 . 1 4 1 5 9 int main ( vo i d ) { fl o at ar e a , ci r cum , r adius ; printf ( " Kaкoв р адиус в ашей пиццы? \ n " ) ; s c an f ( " % f " , &r adius ) ; ar e a = P I * r a dius * r adiu s ; ci r cum = 2 . 0 * PI * r adius ; printf ( " Базовые пар аме тры в ашей пиццы : \ n " ) ; printf ( " длинa окружно сти = % 1 . 2 f , площадь = % 1 . 2 f\ n " , cir cum , ar e a ) ; r e turn О ; С пецификатор % 1 . 2 f в операторе printf ( ) требует округления до двух десятич· ных позиций при выводе . Конечно , эта программа может не отражать основные па· раметры пиццы, вызываю щие ваш интерес , но она заполняет небольшую нишу в мире программ, касающихся пиццы . Рассмотрим приме р выполнения этой программы : Каков р а диус в ашей пиццы? 6.0 Ба зовые пар аме тры в ашей пиццы : длина окружно с ти = 3 7 . 7 0 , площадь = 1 1 3 . 1 0 О пе ратор #de fi ne также может приме няться для объявления с имволических и строчных констант. С этой целью достаточно воспользоваться одиночными кавычка· ми для первых и двойными кавычками для последних. Ниже приводятся примеры до­ пустимого объявления констант: #de fine #de fine #de fine #de fine ВЕЕР ' \ а ' ТЕЕ ' Т ' ESC ' \ 0 3 3 ' OOPS " Наконец-то вы сделали э то ! " Следует е ще раз подче ркнуть, что вс е то , что следует за с имволическим именем, замещает его . Не допускайте следую щую распространенную ошибку: / * следующе е о бъявление н екорр е к тно * / #de fine TOE S = 2 0 Если вы сделали это объявле ние , константа TOE S примет значе ние = 2 О , а не про­ сто 2 О . В этом случае такой оператор, как di gits = fing e r s + T OE S ; получит неправильное представление : di gits = fing e r s + = 20; Символ ьные строки и фор матированный ввод-вы вод 131 М одифи катор cons t Стандарт С90 об еспечивает е ще один с пособ с о здания символических констант, который предусматривает ис пользование клю чевого слова const для преобразования объявления переменной в объявление константы: cons t int MONTHS = 1 2 ; // MONTHS - символич е ская константа со знач ением 12 Благодаря такому объявлению константа MONTH S становится значение м только для чтения. Иначе говоря , вы можете вывести на экран значе ние MONТHS и задействовать его в вычислениях, но вы не можете изменять значение MONТ H S . Этот новый подход более гибок, чем использование конструкции #de fine; в главе 13 об суждается этот, а также и другие с пособы использования констант. На самом деле в языке С имеется е ще и третий способ с о здания с имволиче ских констант - ис пользование с редства enurn, которое рас сматривается в главе 1 4 . Работа с си мволически м и константа м и Заголовочные файлы l irni ts . h и fl o at . h содержат подробную информацию отно­ сительно ограниче ний размеров, соответстве нно , для целочисленных типов и типов с плавающей запятой. Каждый файл определяет ту или иную последовательность сим­ воличе ских констант, которые вы можете использовать в с воих программах. Напри­ мер, файл lirni ts . h содержит строки, подобные следую щим: #de fine INТ МАХ #de fine INТ MI N +3 2 7 67 -3 2 7 6 8 Эти константы представляют максимально и минимально возможные значе ния ти­ па i nt. Если ваша с истема использовала 3 2-разрядное значение int, этот файл обес­ печивает различные значения таких символических констант. Этот файл определяет минимальные и макс имальные значе ния для вс ех целочисле нных типов. Если вы включаете файл lirnits . h в программу, то можете пользоваться следую щим кодо м : рrintf ( "Максимально е знач ени е типа i n t для данной системы = %d\ n " , INТ_МАХ) ; Если ваша система использовала четырехб айтное значе ние int, то файл lirni ts . h, который поставляется в комплекте с этой системой, предоставляет определения зна­ че ний I NТ _МАХ и INТ _MIN, соответствую щих пределам четырехбайтного типа int . В табл. 4 . 1 приводится спис ок не которых констант, определения которых содержатся в файле lirni ts . h. Подобным образом в файле flo at . h определяются константы типа FLT_D I G и DB L_D I G, которые представляют собой количе ство значащих чис ел, об еспечиваемых типами f l o at и douЫ e . В табл. 4.2 перечислены некоторые константы , которые мож­ но найти в файле f l o at . h . (Вы можете воспользоваться текстовым редактором, чтобы открыть и ознакомиться с содержимым заголовочного файла float . h, который при­ сутствует в ваше й системе .) Рас сматривае мый пример относ ится к типу flo at. Э кви­ валентные константы определены для типов douЫ e и l ong douЫ e , при этом DB L и LDB L заме не ны в име ни на F LT . (Таблица предполагает , чт о в системе числа с плавающей запятой представлены в терминах степе ни 2 . ) 132 Гл ава 4 Таб.nица 4.1 . некоторые симво.nические константы. опреде.nения которых содержатся в фай.nе 1imi.ts . h Символическая 'Константа Чт о представля ет CHAR B I T Количество битов в типе ch ar . CHAR МАХ Максимальное значение типа char . CHAR MIN Минимальное значение типа char . SCHAR МАХ Максимальное значение типа signed char . SCHAR MIN Минимальное значение типа s igned char . UCHAR МАХ Максимальное значение типа unsigned ch ar . SHRT МАХ Максимальное значение типа short. SHRT MIN Минимальное значение типа s hort. USHRT МАХ Максимальное значение типа unsigned short. INT МАХ Максимальное значение типа int. INT MIN Минимальное значение типа int. UINT МАХ Максимальное значение типа unsigned int. LONG МАХ Максимальное значение типа long. LONG MlN Минимальное значение типа long. ULONG МАХ Максимальное значение типа unsigned l o ng. LLONG МАХ Максимальное значение типа long long. LLONG MIN Минимальное значение типа long l ong. ULLONG МАХ Максимальное значение типа unsigned l o ng long. Таб.nица 4 . 2 . некоторые симво.nические константы И 3 фай.nа 1imi.t s . h Символическая 'Константа Что представля ет F LT MANT DI G Число разрядов в мантиссе типа fl o at. F LT D I G Минимальное число значащих десятичных цифр типа fl o at. F LT M I N 1 0 ЕХР Минимальное значение отрицательного десятичного поряд­ ка числа для типа float с полным набором значащих цифр. F LT МАХ 10 ЕХР Максимальное значение положительного десятичного по­ рядка числа для типа flo at. F LT MIN Минимальное значение для положительного числа типа float , сохраняющее полную точность. F LT МАХ Максимальное значение для положительного числа типа float . F LT E PS I LON Различие между 1 . О О и наименьnмм значением f l o at, пре· вышающим 1 . 0 0 . Символ ьные строки и фор матированный ввод-вы вод 1 33 Листинг 4.5 служит иллю страцией использования данных из заголовочных файлов float . h и limit s . h. (Обратите внимание , что многие с о вре менные компиляторы поддерживают стандарт С99 е ще не в полной мере, поэтому могут не распознать иде н­ тификатор LONG_МIN.) Листинг 4 . 5 . Программа defines _ с ! / de fine s . c -- исполь зует определенные константы из файла limit . h и тип flo at . #incl ude <s tdio . h > / / целочисленные пр ед елы #incl ude <l imits . h> // пределы для чисел с плав ающей з апя той #incl ude < f l o at . h > int main ( vo i d ) { printf ( " Heкoтopыe пределы для д анной системы : \ n " ) ; printf ( " Наибол ьшее знач ение тип а int : % d\ n " , INТ_МАХ ) ; рrintf ( " Наиме н ь ш е е знач ение тип а long long : % l l d\ n " , L LONG_MIN) ; printf ( " Oдин б айт = % d р а зрядов в данной си стеме . \ n " , CHAR_B I T ) ; рrintf ( " Наибол ьшее знач ение тип а douЫ e : % e \ n " , DB L_МAX ) ; рrintf ( " Наиме н ь ш е е нормал ьное з н ач ени е типа float : % e \ n " , FLT_MIN) ; printf ( " Toчнo c т ь значений типа float = % d з н аков \ n " , F LT_D I G ) ; pr int f ( " Различие между 1 . О О и н аимень шим з н ач ени ем flo at , пр евьШJающим 1 . 0 0 = % e \ n " , F LT_E P S I LON) ; r etur n О ; В от как выглядят выходные данные программы de fi n e s . с: Некоторые пр ед елы для данной си стемы : Наибольш е е знач ение типа int : 2 1 4 7 4 8 3 6 4 7 Наименьш е е знач ение типа long l ong : - 9 2 2 3 3 7 2 0 3 6 8 5 4 7 7 5 8 0 8 Один бай т = 8 р а зрядов в данной системе . Наибольш е е знач ение типа douЫ e : 1 , 7 9 7 6 9 3 е+ 3 0 8 Наименьш е е нормал ь но е знач ение типа f l o at : 1 , 1 7 5 4 9 4 е - 3 8 Точность знач е ний типа fl o at = 6 знаков Различие между 1 . 0 0 и наименьшим знач ением float , пр евьШJающим 1 . 0 0 1 . 1 92 0 93 е- 0 7 Препроце с с ор С очень поле зное и удобное инструментальное с редство, поэтому приме няйте е го везде , где возможно . В этой книге вы ознакомите сь с другими случая­ ми его ис пользования . - Исследован ие и и спользован и е фун кций prin t f ( ) и scan f ( ) Функции print f ( ) и s can f ( ) обес пе чивают возможность общения пользователя с программой. О ни называются функциями ввода-въtвода. Эти функции не единственны е , которые в ы можете использовать для этих целей в языке С , в т о же вре мя они облада­ ют множеством дополнительных возможносте й. Историчес ки эти функции, подобно другим функциям в библиотеке С , ие бъ�ли частью определения языка С . Пе рвоначаль­ но С предоставлял ре ализацию процедур ввода-вывода разработчикам компилятора ; 134 Гл ава 4 такой подход давал возможность учитывать особенности конкретных машин при вы­ полне нии операций ввода-вывода . В интересах с охранения с овместимости различные ре ализации выполнялись с вклю ченными в них функциями s c an f ( ) и printf ( ) . Одна­ ко между верс иями встре чались и случайные нес оответствия. Стандарты С90 и С99 описывает стандартные версии этих функций, и мы будем следовать требованиям этих стандартов. Нес мотря на то что printf ( ) - это функция вывода, а s can f ( ) - функция ввода, в их работе много общего; так, например, каждая из них использует управляю щую стро­ ку и список аргуме нтов . Далее мы покажем, как они раб отают, сначала рассмотрим функцию printf ( ) , а зате м и функцию s can f ( ) . Фун кция printf ( ) Инструкции, которые вы даете print f ( ) , обращаясь к ней с требованием пе чати переменной, зависят от типа этой пе ременной. Например, ране е мы использовали форму записи %d при печати целого числа и %с - при пе чати символа . Эти указания называются сnе-цифиха-циями преобразования, пос кольку они определяют, каким образом пре образуются данные в форму, пригодную для вывода . Мы приведем список с пеци­ фикаций пре образования , которые стандарт ANSI С предусматривает для функции print f ( ) , и затем покажем, как следует ис пользовать наиболее употре бляе мые из них. В табл. 4.3 пе ре числены спецификаторы преобразования и типы вывода , который они обес пе чивают. Таб.nица 4.3 . спецификаторы преобра3ования и ре3у.nьтат вывода на печать Символическая константа Выходные данные %а Число с плавающей запятой, шестнадцатеричные цифры и р-представление (стандарт С99). %А Число с плавающей запятой, шестнадцатеричные цифры и Р-представление (стандарт С99) . %с Одиночный символ. %d Десятичное целое число со знаком. %е Число с плавающей запятой, экспоненциальное представление ( е-представление) . %Е Число с плавающей запятой, Е-представление . %f Число с плавающей запятой, десятичное представление. %g Используется спецификатор % f или % е, в зависимости от значения. Стиль % е используется, если показатель степени меньше 4 либо боль­ ше или равен заданной точности. - %G Используется спецификатор % f или % е, в зависимости от значения. Стиль % е используется, если показатель степени меньше -4 либо боль­ ше или равен заданной точности. %i Десятичное целое число со знаком (то же , что и спецификатор % d ) . Символ ьные строки и фор матированный ввод-вы вод 135 Охон:чаиие табл. 4.3 Символическая кон.ст аита Выходиьt е даииые %о Восьмеричное целое число без знака. %р Указатель. %s Строка символов. %u Десятичное целое число без знака. %х Ш естнадцатеричное целое число без знака, используются шестнадца­ теричные цифры О f. %Х Ш естнадцатеричное целое число без знака, используются шестнадца­ теричные цифры О f. %% Печать знака процента. Использование фун кци и printf ( ) В листинге 4.6 представлена программа, в которой ис пользуются не которые рас­ смотренные выше с пецификаторы. листинг 4.6. Программа printout . с / * p r i ntout . c -- исполь зует спецификатор ы пр ео бр а з о в ания * / #incl ude < s tdio . h> #de fi ne PI 3 . 1 4 1 5 9 3 i n t main ( vo i d ) { int numЬ er = 5 ; fl o at e s pr e s s o = 1 3 . 5 ; int co s t = 3 1 0 0 ; printf ( " % d администр аторов выпили % f ч ашек кофе э спр е с со . \ n " , numЬ er , e s pr e s s o ) ; рrintf ( " Знач ение pi р авно % f . \ n " , PI ) ; p r i nt f ( " Дo свидания ! В аше иску с с тво слишком дор о г о мне о бходится , \ n " ) ; printf ( " % c % d \ n " , ' $ ' , 2 * co s t ) ; r e turn О ; В ре зультате выполне ния этой программы были получе ны следую щие выходные данные: 5 админи стр а торов в ыпили 1 3 . 5 0 0 0 0 0 ч ашек э с пр е с с о . Зн ач ение pi р авно 3 . 1 4 1 5 9 3 . До свидания ! В аше и скусство слишком дорого мне о бходится , $6200 Формат использования функции printf ( ) имеет вид : р r i nt f ( управляющая- строка , элементl , элемент2 , . . . ) ; 136 Гл ава 4 Где элементl , элемент2 и так далее - это элеме нгы , которые нужно напе чатать. О ни могут б ыть переменными либ о константами, либ о даже выражениями, значение которых вычисляется при выводе на печать, а управляющая - строка - это строка сим­ волов , описываю щая , каким образом не обходимо выполнить печать эле ментов. Как уже говорилось в главе 3, управляющая строка должна с одержать спецификатор пре­ образования для каждого выводимого элемента . Наприме р, рассмотрим следую щий оператор: printf ( " %d администр аторов выпили % f ч ашек кофе э спр е с со . \ n " , numЬ er , e sp r e s s o ) ; Здесь управ ляющая - строка представляет собой фразу, заклю ченную в двойные ка­ вычки. О на содержит два спе цификатора преобразования , соответстве нно , для пе ре­ менных numb er и espr e s s o - двух выводимых на пе чать элементов. На рис . 4.6 показан другой пример применения оператора print f ( ) . printf ( Управляющий оператор Список переменных "You look great in %s\n" color ) ; Р ис. 4.6. Аргумептъt фупк-ции pri n t f ( ) В от еще одна строка из того же примера: рrintf ( " Знач ение pi р авно % f . \ n " , PI ) ; В этот моме нг времени с писок с остоит только из одного эле мента - символиче­ ской констангы PI . Как вы можете видеть на рис . 4 . 7 , управ ляющая - строка содержит два различных вида информации: • Символы, которые пе чатаются на с амом деле . • Спе цификаторы преобразования . " The value of pi is Л итеральные сим в олы Спецификация преобра з о в ания Р ис. 4.7 . Апатомия управляю щей строки Вн и м ан и е ! Не забудьте, что необходимо предусмот реть по одной спецификации преобразования дп я каждого элемента в приведенной ниже управляющей- строке. Будет очень печал ьно, есл и вы не выполните это основное требование ! Никогда не поступайте так: print f ( " Выпало Squids % d из % d . \ n " , s cor e l ) ; Отсутствует значение для второго специфи катора % d. То , к чему приведет подобная небрежность, зависит от вашей систем ы , но в луч шем случае вы получите на экране абра кадабру. Символ ьные строки и фор матированный ввод-вы вод 1 37 Если необходимо выве сти на печать только какую-то фразу, вам не нужны никакие спе цификации преобразования. Если вы хотите распечатать только данные, вы може­ те обойтис ь те кущим комментарием . Для этого вполне достаточно одного из двух сле­ дую щих операторов из листинга 4.6: p r i nt f ( " Дo свидания ! В аше искус ство слишком дор о г о мне о бходится , \ n " ) ; printf ( " % c % d \ n " , $ 2 * co s t ) ; ' ' , Обратите внимание на второй оператор , в котором первый элемент в с писке для печати представляет собой константу, а не переменную , в то же время второй эле­ мент - операция умножения . Это иллюстрирует тот факт, что функция print f ( ) ис­ пользует значе ния независ имо от того , представляют ли они собой переме нные , кон­ станты или выражения . Поскольку функция printf ( ) использует с имвол % для идентификации с пецифика­ ции преобразования , то возникает небольшая проблем а , когда вы захотите распе ча­ тать с ам символ % . Если вы просто укажете одиночный знак % , компилятор подумает, что вы не верно задали спе цификацию преобразования. В ыход из этой с итуации дос­ таточно прост: в таких случаях вы должны вос пользоваться двумя символами % , как показано ниже: ре = 2 * 6 ; printf ( " Toлькo % d % % припа сов Мэри были съедо бными . \ n " , р е ) ; В результате выполнения этого фрагмента программы получим следующий результат: Т о л ь ко 1 2 % припасов Мэри были съедо бными . М одифи каторы спецификации п реобразован и я для фун кци и printf ( ) В ы можете изменить базовую спецификацию пре образования , вставляя модифика­ торы между знаком % и символом, определя ю щим преобразования. В таблицах 4.4 и 4.5 приводятся с писки символов , которые можно разме щать в этих местах. Если вы используете более одного модификатора , они должны располагаться в том же поряд­ ке, в каком они представлены в табл. 4.4. Не вс е комб инации допустимы . Таблицы от­ ражают стандарт С 9 9 ; ис пользуе мая вами ре ализация , возможно , еще не поддержива­ ет все представле нные в этих таблицах варианты . ТабJJица 4.4. Модификаторы функции printf О Moi>uфuxamop Зиа-ч ение фла г Пять флагов ( - , +, пробел, # и О) описаны в табл. 4.5 . В функции могут быть представлены несколько флагов или же вообще ни одного. Пример: " % - l O d" цифра (ы) Минимальная IIШрина поля. Если выводимое на печать число или строка не поме�цаются таком поле , используется поле большей IIШрины. Пример: " % 4 d " 138 Гл ава 4 Око11:чаиие табл. 4. 4 Моди фикатор Значение . цифра (ы) Точность. Для преобразований % е, %Е и % f указывается количество цифр, которые будут распечатаны справа от десятичного числа. Для преобразований % g и % G задается максимальное количество значащих цифр. Для преобразования % s определяется максимальное количество символов, которое может быть распечатано . Для целочисленных пре· образований указывается минимальное количество воспроизводимых при печати цифр, ведущие нули используются в случае необходимости установки соответствия с этим минимумом. Если используется только точка ( . ) , то имеется в виду, что далее следует нуль, следовательно, % . f - то же , что и % . O f. Пример: " % 5 . 2 f " выводит значение типа f l o at в поле пшриной пять символов и двумя цифрами после десятичной точки. h Используется со спецификатором целочисленного преобразования i nt или unsigned short int . для отображения значений типа short Примеры: " % hu " , " % hx " и " % 6 . 4 hd" . hh Используется со спецификатором целочисленного преобразования для отображения значений типа s i gned char или unsi gned char . Примеры: " % hhu " , " % hhx " и " % 6 . 4 hhd" . j Используется при целочисленном преобразовании со спецификато· ром для отображения значений i ntmax _t или uintmax_t . Примеры: " % j d" и " % 8 j X " . 1 Используется с о спецификатором целочисленного преобразования in t или unsigned long int. для отображения значений типа l o ng Примеры: " % ld" и " % 8 l u " . 11 Используется со спецификатором целочисленного преобразования для отображения значений типа l o ng l o ng int или un s i gned long long int (стандарт С99) . Примеры: " % lld" и " % 8 l lu " . 1 Используется со спецификатором преобразовании значений с пла· вающей запятой для отображения значений типа l ong do uЫ e . Примеры: " % L f " и " % 1 0 . 4 Le " . t Используется с о спецификатором целочисленного преобразования для отображения значений p trdi f f _ t. Этот тип соответствует разли· чию между двумя указателями (стандарт С99) . Примеры: " % td" и " % 1 2 ti " . z Используется с о спецификатором целочисленного преобразования для отображения значений s i z e _t . Этот тип возвращается функцией s i z e o f (стандарт С99). Примеры: " % zd" и " % 1 2 zx " . Символ ьные строки и фор матированный ввод-вы вод 1 39 Прео бр аз ован и е ар гум ентов ти п а f1oat Сущест вуют с пецификато ры преобразова ния для вы вода на печат ь типов douЫ e и l o ng douЫ e . В то же время тако й спецификатор для т и па f l o at отсутст вует . П рич ина состоит в том, что в кпасс ическом я з ы ке С ил и K&R С , зна чения т и па flo at а втомати­ чески преобразов ы вал ись к т и пу douЫ e пе ред использованием в вы ражениях ил и в виде а рrу мента. В общем случае ANSI С не предус матрива ет а втомат ического преоб­ разования float в douЫ e . Одна ко, для того, чтобы обес печит ь пра в ил ьную работу о г­ ро много кол ичества сущест ву ющих програ м м , которые разрабаты вал ись с расчето м на то, что а ргумент ы т и па f l o at могут быть преобразованы в т и п douЫ e , все а ргу мент ы fl o at, ис пол ьзуе мые функцией printf ( ) , ра вно ка к и в другими функция ми С, для ко­ торых не созда ются явные протот и п ы , а втоматически преобразуются к т и пу douЫ e . Поэтому в каждом и з двух случа ев, ка к в классическом K&R С , так и в ANSI С , нет с пе­ циал ьного с пецификатора преобразования , которы й требуется для вы вода на печат ь элементов т и па fl o at. Таблица 4 . 5 . Фпаги функции printf О Флаz Зн.а-чен.ие Элеменг выравнивается влево, то есть содержимое будет напечатано, начиная с левого края. Пример: " % - 2 0 s " . + Значения с о знаком печатаются с о знаком + , если они положительные , и со знаком - , если отрицательные. Пример: " % + 6 . 2 f " . Про бел Значения с о знаком печатаются с ведущим пробелом (но без знака), если они положительны, и со знаком - , если они отрицательные . Флаг + пере­ крывает пробел. Пример: " % 6 . 2 f " . # Использует альтернативную форму для спецификации преобразования. Выводит ведущий О для формы %о и ведущий Ох или ОХ для форм %х и % Х . Для всех форм с плавающей запятой флаг # гарангирует, что символ деся­ тичной точки будет напечатан, даже если за ним не следуют цифры. Для форм % g и % G это предотврашает удаление завершающих нулей. Примеры: " % #о " , " % # 8 . O f " и " % + # 1 0 . З Е " . о Для числовых форм функция заполняет поле по всей ширине ведущими ну­ лями вместо пробелов. Этот флаг игнорируется, если присутствует флаг или если указана точность для целочисленной формы. Примеры: " % 0 1 0 d" и " % 0 8 . З f " . Примеры использования модификаторов и флагов Расс мотрим, как работают описанные выше модификатор ы . Начнем с того, что проанализируем, как влияет модификатора ширины поля на отображение целого чис­ ла. Рас смотрим программу, показанную в листинге 4 . 7 . 1 40 Гл ава 4 листинг 4.7. Программа width . c / * wi dth . c - - ширина поля * / #incl ude <s tdio . h > #de fi ne PAGES 9 3 1 int main ( vo i d ) { printf ( " * % d* \ n " , PAGE S ) ; printf { " * % 2 d* \ n " , PAGE S ) ; printf ( " * % 1 0 d* \ n " , PAGE S ) ; printf ( " * % - l O d* \ n " , PAGE S ) ; r e turn О ; Программа из листинга 4 . 7 выводит на пе чать одно и то же число четыре раза, но с приме не ние м четырех различных спецификаций пре образования . Звездочка ( * ) слу­ жит для уазания, где начинается и где заканчивается каждое поле . Выходные данные имеют следую щий вид : * 9 3 1* * 9 3 1* * *931 93 1 * * Первая с пецификация пре образования представлена конструкцией % d без моди­ фикаторов. О на ге не рирует поле с той же шириной, какую имеет рас печатываемое целое число . Этот вариант принимается по умолчанию , то есть число будет напе чата­ но именно в таком виде, е сли не представле ны дальнейшие инструкции. Вторая с пе· цификация преобразования представлена конструкцией % 2 d. О на устанавливает ши· рину поля равной 2 , но, пос кольку в рассматриваемом примере целое число имеет три значащих цифры , поле расширяется автоматиче ски, чтобы уместить это число . Сле· дую щая спе цификация преобразования - % 1 0 d. О на генерирует поле шириной 10 про­ белов, при этом мы име ем с емь пробелов и три цифры между звездочками с число м , а само число смещено в правый конец поля. Последне й спе цификацией является % - l O d . О на также генерирует поле шириной в 1 0 с имволов, а з н а к - означает, что число по­ мещается с ле вого края, в с оответствии со с казанным выш е . После того , как вы при­ обретете необходимые навыки раб оты, вы поймете , насколько удоб на такая с истема. Наряду с этим она обес печивает высокую степень контроля над вне шним видом ваше· го вывода . Попробуйте изменить значение PAGE S , чтобы пос мотреть, как пе чатаются числа с различным количеством цифр. Теперь расс мотрим некоторые форматы чисел с плавающей запятой. Набе рите , откомпилируйте и выполните программу, представленную на листинге 4.8. листинг 4.8. Программа f1oa ts . с / * Пр огр амма floats . c - - некоторые комбинации типов с плав ающей з апя той #incl ude <s tdio . h > int main ( vo i d ) { co n s t do uЫ e RENT = 3 8 5 2 . 9 9 ; 1 1 константа в стиле con s t printf ( " * % f* \ n " , RENT ) ; Символ ьные строки и фор матированный ввод-вы вод 1 41 printf ( " * l e * \ n " , RENT ) ; printf ( " * % 4 . 2 f * \ n " , RENТ ) ; printf ( " * % 3 . l f * \ n " , RENТ ) ; printf ( " * % 1 0 . 3 f* \ n " , RENТ ) ; printf ( " * % 1 0 . 3 e * \ n " , RENТ ) ; printf ( " * % + 4 . 2 f* \ n " , RENТ ) ; printf ( " * % 0 1 0 . 2 f* \ n " , RENТ ) ; r e turn О ; На с е й раз программа использует клю чевое слово con s t , чтоб ы создать символиче· скую констангу. Получается следую щий вывод : * 3 8 52 . 9 9 0 0 0 0 * * 3 . 8 5 2 9 9 0 е+ 0 3 * * 3 8 52 . 9 9 * * 3 8 53 . 0 * • 3 8 52 . 9 9 0 * * 3 . 853е+03* *+ 3 852 . 9 9* * 0 0 0 3 8 52 . 9 9* Начне м с варианта , заданного по умолчанию , а именно - с % f. В этом случае име ет· ся два значения, принятые по умолчанию : ширина поля и количе ство цифр справа от десятичной точки. Второе значе ние , принятое по умолчанию , - это ше сть цифр и ширина поля , необходимая для того, чтоб ы вместить число . Далее в программе идет значение , принятое по умолчанию для % е . В условиях этого варианга печатается одна цифра слева от десятичной точки и резервируются шесть позиций справа от нее. Получается довольно·таки много цифр ! Чтобы исправить это положе ние , нужно задать количество десятичных позиций с права от десятичной точ· ки, и следую щие четыре примера в этом разделе служат иллю страцией этого решения . О братите внимание н а то , как в четвертом и ше стом примере выполняется округле· ние выходных данных. Наконец, флаг + означает, что числовые данные будут выводиться вместе с их алгеб· раическими знаками, которым в данном случае является знак + , а флаг О приводит к за· полнению ведущими нулями на всю ширину поля. О братите внимание , что в специфика· торе % О 1 О первый О - это флаг, а остальные цифры ( 1 О ) - ширина поля. Можете изменить значение RENТ , чтобы посмотреть, как пе чатаются значения , для которых задаются различные размеры поля . Программ а , показанная в листинге 4 .9 , демонстрирует е ще несколько возможных комбинаций. листинг 4.9. Программа f1aqs . с / * fl ags . c - - иллюстр ация применения неко торых форматирующих флагов • / #incl ude <s tdio . h > int main ( vo i d ) { printf ( " l x % Х % #x \ n " , 3 1 , 3 1 , 3 1 ) ; printf ( " * * % d* * % d* * % d* * \ n " , 4 2 , 4 2 , - 4 2 ) printf ( " * * % 5d* * % 5 . 3 d* * % 0 5d* * % 0 5 . 3 d* * \ n " , 6 1 6 1 6 1 6 ) ; r e turn О ; ,· 1 42 Гл ава 4 Получае м следую щий вывод: l f lF O x l f ** 42** 4 2**-42** ** 6* * 0 0 6 * * 0 0 0 0 6* * 0 0 6* * Прежде все го примем к сведению , что l f представляет с обой шестнадцатеричный эквивалент десятичного числа 3 1 . Спе цификатор х об еспечивает формат l f, а с пеци­ фикатор Х l F . Использование флага # приводит к тому, что на выходе мы получим исходные данные в формате О х . Вторая строка выходных данных программы служит иллюстрацией того факта , что использование пробела в спецификаторе приводит к появле нию ведуще го пробела для положительных, но не для отрицательных значений. Это об стоятельство можно ис­ пользовать выравнивания выходных данных, пос кольку положительные и отрица­ тельные значения с одинаковым количеством значащих цифр будут напе чатаны в по­ лях одинаковой ширины . Третья строка служит иллю страцией того факта , что использование с пецификато­ ра точности ( % 5 . З d) в целочисленной форме приводит к пе чати такого количества ве­ дущих нулей, которое достаточно для представления числа минимальным количе ст­ вом цифр (три в расс матривае мом случае ) . Однако применение флага О приводит к наполнению представления числа ведущими нулями, количество которых достаточно для заполнения числом всей ширины поля . Наконец, если вы используете вместе флаг О и спецификатор точности, то флаг О игнорируется . Теперь исследуем не которые из параметров строки. Рас смотрим пример, показан­ ный в листинге 4 . 1 0 . - Листинг 4.1 0. Проrрамма strinqs . с / * s trings . c - - ф орматиров ание строки * / #incl ude <s tdio . h> #de fi ne B LURB "Authenti c imit ation ! " int main ( vo i d ) { printf { " / % 2 s / \ n " , BLURB ) ; printf ( " / % 2 4 s / \ n " , B LURB ) ; printft" / % 2 4 . S s / \ n " , B LURB ) ; printf ( " / % - 2 4 . S s / \ n " , B LURB ) ; r e turn О ; В ыходные данный этой программы имеют вид: /Authent i c imi tation ! / Authenti c imitation ! / / Authe / / /Authe / Обратите внимание, что поле расширено настолько, чтобы вместить все опис ан­ ные с имволы . О братите также внимание на то , как спецификатор точности ограни­ чивает выводимое количество символов. Запис ь . 5 в спе цификаторе формата сооб­ щает функции printf ( ) о том, что нужно выводить только пять символов. И снова на­ помним , что модификатор - выравнивает текст по левому краю . Символ ьные строки и фор матированный ввод-вы вод 1 43 Применение полученных знаний на практике В ы только что прос мотрели не сколько приме ров. Каким должен быть опе ратор вывода на печать те кста в следую ще й форм е : Семья NAME може т стать бо г ач е н а ХХХ . ХХ долларов ! Здесь NAME и ХХХ . ХХ представляют собой значения, для которых в программе будут зарезервированы пере менные , скаже м , name [ 4 0 ] и cash. Предлагаем одно из возможных ре шений: printf ( " С емья %s може т с тать бог ач е на % . 2 f дoллapoв ! \ n " , name , cas h ) ; Что п реобразует специфика ция преобразова ния Теперь боле е подроб но расс мотрим, что име нно пре образует спе цификация пре­ образования . О на преобразует значе ние , представленное в компьютере в некотором двоичном формате , в последовательность символов (строка ) , предназначенных для отоб ражения . Например, число 76 может б ыть представле но в компьютере как дво­ ичное число 0 1 0 0 1 1 00 . С пе цификатор пре образования %d превращает эту двоичную последовательность в символы 7 и 6, тем с амым отображая число 7 6 . Пре об разование %х превращает ту же двоичную последовательность ( 0 1 00 1 1 0 0 ) в шестнадцатеричное представление 4 с. А спе цификатор %с приводит то же значение к символьному пред­ ставлению L. Те рмин преобраJоваиие иногда приводит к недоразуме ниям, поскольку может воз­ никнуть неправильное предположение , будто исходное значение заменяется преобра­ зованным значе нием . Преобразую щие спецификации по суще ству являются специфи­ кациями пе ревода; %d означает, что нужно "перевести данное значение в де сятичное целочисленное те кстовое представление и вывести это представление на печать" Несовпадающие преобразования Естественно , необходимо с о глас овать спе цификацию преобразования с типом данных, выводимых на печать. Часто в этих случаях доступно не сколько вариантов. Наприме р, если вы хотите распе чатать тип int, вы можете использовать либо с пеци­ фикатор % d, либо % х , либо % 0 . Все эти с пецификаторы предполагают, что вы выводите на печать значение типа int ; сами же они просто обес печивают различные представ­ ления этого значения . Точно так же вы можете использовать % f, % е или % g , чтобы представить тип douЫ e . А что произойдет, е сли в ы н е согласовали спецификацию пре образования с ти­ пом? Вы уже видели в предыдуще й главе , что такие нес оответствия могут приве сти к возникнове нию проблем . О б этом никогда не следует заб ывать, поэтому программа, представленная в листинге 4 . 1 1 , предлагает е ще несколько примеров несоответствий при работе с се мейством целочисле нных типов. Листинг 4.1 1 . Программа intconv . с / * intconv . c -- некоторые несогласованные пр еобразования целочисленных типов * / #incl ude <s tdio . h > #de fi ne PAGES 3 3 6 #de fi ne WORDS 6 5 6 1 8 1 44 Гл ава 4 int main ( vo i d ) { short num = PAGE S ; short mnum = - PAGE S ; printf ( " num как тип s hort и тип unsigned short : % hd % h u\ n " , num , num) ; print f ( " -num как тип s hort и тип uns igned s hort : %hd % hu\ n " , mnum, mnum) ; printf ( " num как тип int и тип char : % d % c\ n " , num , num ) ; printf ( "WORDS как тип int , short и char : %d %hd % с \n" , WORDS, WORDS, WORDS ) ; r e turn О ; В нашей с истеме были получены следую щие ре зультаты: num как тип short и тип unsigned s hor t : 3 3 6 3 3 6 -num как тип s hort и тип unsigned short : - 3 3 6 6 5 2 0 0 num как тип int и тип char : 3 3 6 Р WORDS тип int , s hort и char : 6 5 6 1 8 8 2 R Посмотрев на первую строку, вы можете заметить, что спецификаторы % hd и % hu приводят к выводу числа 3 3 6 в качестве значения переменной num; тут нет никаких пробле м. Вариант ni num со с пецификатором % u (без знака) приводит к выводу значе­ ния 6 5 2 0 0 , а не 3 3 6 , как можно было ожидать. Т акой ре зультат был получен вследст­ вие спос об а представле ния значений типа s hort int со знаком , реализованного в ва­ шей системе . Во-пе рвых, они име ют разме р 2 б айта . В о-вторых, система использует метод , называемый поразрядн:ым доnол'Не'Нием до двойки для представле ния целых чисел со знаком. В этом методе числа от О до 3 2 76 7 представляют сами себя, а числа от 3 2 768 до 65535 отрицательные числа , причем 6553 5 представляет число -1 , 65534 число -2 и так дале е . Поэтому - 3 3 6 представляется как 6 5 5 3 6 - 3 3 6 , или 6 5 2 0 0 . Итак, число 65200 представляет -3 3 6 , когда инте рпретируется как тип signed i nt, и 65200, когда инте рпретируется как unsi gned i nt. Будьте осторожны ! Одно число может интер­ претироваться как два различных значения. Не все системы используют этот метод для представления отрицательных целых чис ел. Тем не менее, мы приходим к сле­ дую щему выводу: не рас считывайте на то , что в результате пре образования %u просто исчезнет знак у числа без каких-либо других последствий. Вторая строка показывает , что может случиться , если вы пытаете с ь преобразовать целое значе ние , превышающее 255, в с имвол. В данной с истеме тип s hort int зани­ мает 2 байта, а тип char 1 байт. Когда функция pr int f ( ) печатает 3 3 6 с указанием спе цификатора % с, она обращает внимание только на пе рвый байт из двух байтов, хранящих число 3 3 6 . Подобное усече ние (рис . 4 .8 ) равнозначно деле нию целого числа на 256 с с охранением остатка . В этом случае появляется остаток 80, который пред­ ставляет с об ой АSСП-значение символа Р . Выражаяс ь формально , число инте рпрети­ руется по модулю 256, что означает ис пользование остатка от деле ния числа на 256. В заключе ние мы попытались распечатать целое число (656 1 8 ) , пре вышающее максимально допустимое для типа s hort int ( 3 2 76 7 ) , ре ализованное в наше й системе . И снова компьютер применяет операции деле ния по модулю . Число 65616 в силу сво­ его разме ра сохраняется в наше й системе как 4-байтовое значение int. Когда мы пе­ чатае м это число , используя спе цификатор % hd, функция printf ( ) ис пользует только последние 2 байта . Это равносильно использованию остатка от деле ния этого числа на 6553 6 . В этом случае получаем остаток, равный 82. - - - Символ ьные строки и фор матированный ввод-вы вод ASC l l-кoд числа 80 символа ' Р ' ---- о о о о о о о 1 45 о ........ . � .. --�--�--------�--�� Число 336 в двоичном представлении о 1 о о о о о о о о о Р и с _ 4_8_ Иитеfmретшция -числа 336 как символа О статок, находящийся между 3 2 76 7 и 655 3 6 , был бы напечатан как отрицательное число в с илу соответствую щего способа хране ния отрицательных чисел. С истемы с другими размерами целых чис ел придерживалис ь бы той же линии поведения , но чи­ словые значения были бы другими. Когда вы начинаете смешивать значения, имею щие тип целого числа и тип числа с плавающей запятой, то результаты оказываются еще б олее причудливыми. Рассмот­ рим, например , код в листинге 4 . 1 2 . Листинг 4_1 2_ Программа f1oatcnv . с / * fl o atcnv . c - - несогл асованные пр еобр а зов ания с плавающей з апя той * / #iпcl ude <s tdio . h > iпt main ( vo i d ) { fl o at n l = 3 . О ; do uЫ e n 2 = 3 . 0 ; l o ng n3 = 2 0 0 0 0 0 0 0 0 0 ; l o ng n 4 = 1 2 3 4 5 6 7 8 9 0 ; printf ( " % . l e % . l e % . l e % . l e \ n " , п l , n2 , n3 , п 4 ) ; printf ( " % l d % ld\ n " , п3 , п 4 ) ; printf ( " % l d % ld % l d % l d\ n " , п l , п 2 , n3 , п 4 ) ; r e turn О ; В нашей с истеме код из листинга 4 . 1 2 с генерировал следую щий вывод: 3 . О е+ О О З . О е+ О О 3 . l e+ 0 4 6 1 . 7 е+ 2 6 6 2 0 0 0 0 0 0 0 0 0 123 4 5 67 8 9 0 о 1074266112 о 10742 66112 Первая строка выходных данных этой программы показывает , что при ис пользо­ вании спецификатора % е не происходит преобразование целого числа в число с пла­ вающей запятой. Посмотрим, например, что случается , е сли вы пробуете пе чатать значение nЗ (типа l o ng) с указанием спе цификатора % е . Во-первых, спе цификатор % е настраивает функцию priпtf ( ) на число типа douЫ e , которое является 8-байтовым значение м в нашей с истеме . Когда функция printf ( ) сталкивается с пере менной nЗ, которая представлена 4-байтовым значением в наше й системе , эта функция рассмат­ ривает также смежный блок памяти длиной 4 б айта . Таким образом, функция рас­ сматривает 8-байтовый модуль, в который фактически вложе но значение nЗ. В о­ вторых, она интерпретирует биты этого модуля как число с плавающей запятой. Не­ которые биты , например, интерпретируются как показатель степени. 1 46 Гл ава 4 Поэтому, даже е сли бы nЗ имел правильное число битов, они бы интерпретирова­ лис ь по-разному при использовании спецификаторов % е и % ld. Итоговый результат является бессмысленным . Первая строка также иллюстрирует то , о чем мы говорили выш е : тип flo at преоб­ разовывается к типу douЫ e , когда передается в качестве параметра функции print f ( ) . В этой с истеме тип с плаваю щей запятой занимает 4 байта , в то же время тип переменной nl был расширен до 8 байтов, чтобы функция print f ( ) могла ото­ бражать правильный результат. Вторая строка вывода показывает, что функция printf ( ) может выводить значе­ ния nЗ и n4 правильно , е сли использован корре ктный спецификатор. Третья строка выходных данных показывает, что даже правильно выб ранный с пе­ цификатор может приводить к неве рным ре зультатам , если функция print f ( ) име ет не соответствия где-нибудь в другом месте. Как и следовало ожидать, попытка вывода на печать значения с плавающей запятой с ис пользованием с пецификатора % l d при­ водит к неудаче, однако и в данном случае попытка вывода на пе чать значе ния типа long с указание м спецификатора % l d не удается ! Проблема связана с тем, как С пе ре­ дает информацию той или иной функции. Конкретные подроб ности этих неудач зави­ сят от реализации; на врезке "Передача аргументов" эти вопросы будут рас смотрены более подробно . Передач а аргументо в М еханизм пе редачи а ргументов зависит от реал иза ции. Вот ка к передача а ргу ментов происходит в нашей систе ме. Вызов функции имеет следующий вид: printf ( " % l d % l d %ld % l d\ n " , n l , n2 , n З , n 4 ) ; Показанны й вызов сообщает ко мпьютеру о том, что ему переда ются значения пере мен­ ных n l , n 2 , nЗ и n 4 . Это делается путем помещения их в область памят и, называемую ст еком . Когда ко мпьютер помещает эти значения в сте к, он ру ководст вуется т и па м и переменных, а н е специфи катора ми преобра зо вания. Соот ветст венно, для n l о н отво­ дит 8 ба йтов стека (т ип flo at преобразован в т и п douЫ e). Точно так же он отводит еще 8 ба йтов для переменной n 2 , после чего выделяет по 4 байта для переменных nЗ и n 4 . Затем у п равление передается функции printf ( ) . Эта функция читает значения из стека , причем в соот ветствии со спецификатора м и преобразования . Спецификатор % l d у казы вает , что функция printf ( ) должна читать 4 байта , т а к что print f ( ) ч итает первые 4 байта в стеке ка к с вое первое значение . Это тол ько первая половина значе­ ния n l , и она инте рпрет и руется ка к целочисленное значение т и па long. Следующий спецификатор %ld задает чтение еще 4 байтов; это тол ько вторая половина значения nl и она интерпрет ируется ка к второе целочисленное значение т и па long (рис. 4 . 9 ). Точно так же трети й и четверт ы й с пе цификаторы % l d приводят к тому, что перва я и вторая половин ы зна чения n2 читаются и интерпрет иру ются ка к очередные два числа т и па l ong, таким образом, хотя мы и испол ьзуем пра вил ьные с пе цификаторы для пе­ ре менных nЗ и n 4 , функция print f ( ) ч итает не те ба йт ы , что надо. Символ ьные строки и фор матированный ввод-вы вод 1 47 float nl ; / * Передаются хах тип douЬle * / douЬle n2 ; long nЗ , n4 ; printf ( " %ld %1d %ld %ld\n" , nl , n2 , nЗ , n4 ) ; 4 байт { } ' ' '"' ..______ n4 ..______ nЗ ..______ n2 ..______ nl Функция printf ( ) извлекает значения из стека как значения типа long Аргументы nl и n2 помещаются в стек как значения типа douЬle, а nЗ и n4 как значения типа long - Ри с. 4. 9 . Перед шч а аргуме'/1,тов Возвращаемое значение функции printf () Как было сказано в главе 2, функция в языке С в общем случае имеет возвращаемое значение - то , что она вычисляет и возвращает в вызываю щую программу. Наприме р, в библиотеке С доступна функция sqrt ( ) , которая принимает число в каче стве аргу­ мента и возвращает е го квадратный корень. В озвращаемое значе ние может быть при­ своено не которой переме нной, может ис пользоваться в вычислениях, может переда­ ваться как аргумент, короче говоря , им можно манипулировать как любым другим значение м. Функция printf ( ) также имеет возвращаемое значение : в С она возвраща­ ет количе ство символов, выведенных на печать. Если произошла ошибка вывода , функция printf ( ) возвращает то или иное отрицательное значение . (Некоторые вер­ сии printf ( ) име ют другие возвращаемые значения . ) В озвращаемое значение для функции p r i n t f ( ) н е является е е главной целью , с вя­ занной с печатью выходных данных, и обычно не используется . Одна причина, по ко­ торой вы могли бы использовать возвращаемое значе ние , состоит в том, чтобы про- 1 48 Гл ава 4 верить наличие ошибки вывода . Контроль ошиб ок чаще осуще ствляется при запис и в файл, чем при выводе на экран. Например, если запись на гибкий диск не возможна по причине нехватки ме ста , можно было в этом случае запустить программу, которая вы· полнила бы какое-то действие , скажем, выдала звуковой сигнал в те че ние 3 0 секунд . Для начала вы должны научиться работать с условным опе ратором i f. Простой при· мер, представленный в листинге 4 . 1 3 , показывает, как можно работать с возвращае· мым значением. листинг 4.1 3. Программа prntval . с / * pr ntval . c - - о бнаружение во звр ащаемог о знач ения print f ( ) * / #incl ude <s tdio . h > int main ( vo i d ) { int bph 2 o = 2 1 2 ; int rv ; rv printf ( " % d г р адусо в по Ф ар енг ейту со о тв е т ствуют точке кипения воды . \ n " , bph2 o ) ; рrintf ( " Функция printf ( ) выводи т % d символо в . \ n " , rv) ; r e turn О ; В ыходные данные этой программы имеют вид : 2 1 2 гр адусов по Ф ар е нг ейту соо тве тствуют точке кипения воды . Функция print f ( ) выводит 3 2 символов . С начала программа ис пользовала форму rv = printf ( . . . ) ; для присвое ния воз· вращаемого значения пере менной r v . Этот оператор выполняет две задачи: выводит информацию и присваивает значе ние переменной. Во-вторых, обратите внимание , что итоговый результат вклю чает вс е напечатанные символы , в том числе проб елы и не видимый символ новой строки. Печать длинных строк Иногда приходится сталкиваться с достаточно длинными опе раторами printf ( ) , которые не уме щаются в одной строке. Пос кольку С игнорирует пробельные с имволы (пробелы, символы табуляции и символы новой строки ) , кроме случаев, когда они ис· пользуются внутри строк, длинные опе раторы можно размещать в нес кольких стро· ках. Например, в листинге 4.1 3 оператор печати размещается в двух строках: рrintf ( " Функция printf ( ) печ а т а е т %d символов . \ n " , rv) ,· Строка разорвана между запятой и переменной rv. Чтобы показать читателю , что строка имеет продолжение , в этом примере rv представле на с отступом вправо . Язык С игнорирует избыточные пробелы . В то же вре мя вы не можете разрывать строку, заклю ченную в кавычки. П редпо· ложим, что вы пытаетесь сделать что-то в этом роде : printf ( " Функция printf ( ) печ а т а е т % d симво лов . \ n " , rv ) ; Символ ьные строки и фор матированный ввод-вы вод 1 49 В этом случае компилятор сообщит о том, что вы используете недопустимый сим­ вол в строковой константе . Вы можете включить в строку символ \ n , чтобы обозна­ чить с имвол новой строки, но это не будет фактичес кий с имвол новой строки, порож­ денный нажатие м клавиши <Enter> (<Re turn>) пос реди строки. Если необходимо разбить строку на части, вы можете действовать тремя способа­ ми, как показано в листинге 4.1 4 . листинг 4.1 4. Программа 1onqstrq . с / * l o ng s trg . c - - печ ать длинных строк * / #incl ude <s tdio . h > int main ( void) printf ( " Bo т один из спо со бов вывода " ) ; рrintf ( " длинных строк . \ n " ) ; printf ( " В торой спо со б вывода \ длинных стр ок . \ n " ) ; рrintf ( " Нов ейший спо соб в ывода " " длинных стро к . \ n " ) ; r e turn О ; / * ст андар т ANSI С * / В ре зультате выполне ния программы получается следую щий результат: Во т один из способов вывода длинных с трок . Второй способ вывода длинных строк . Нов ейший спо со б вывода длинных строк . Первый метод предусматривает использование нескольких опе раторов printf ( ) . Поскольку пе рвая напечатанная строка не заканчивается символом \ n , вторая строка продолжается с конца первой. Второй метод состоит в том, чтобы заве ршить первую строку комбинацие й обрат­ ной кос ой черты и нажатия клавиши <Ente r>. Это приводит к тому, что текст на экра­ не начинается с новой строки, но без включения символа новой строки в саму строку. Другими словами, эффект состоит в том, что предыдущая строка не прерывается , а продолжается на следую щей строке . Однако следую щая строка должна начинаться с крайне й левой позиции, как показано в листинге 4 . 1 4 . Если вы сдвинете эту строку вправо , скаже м , на пять пробелов, то эти пять пробелов станут частью строки. Третий метод, введенный стандартом ANSI С, называется конкате нацией или с це­ плением строк. Если одна строковая константа, заклю ченная в кавычки, следует за другой такой константой и обе строки разделе ны пробелом , С рассматривает эту ком­ бинацию как единую строку, таким образом, следую щие три формы эквивалентны : printf ( " Пpив e т юным влюбл енным , где бы они ни были . " ) ; printf ( " Пpив e т юным " " , г де бы они ни были . " ) ; " влюбле нным" printf ( " Пpив e т юным влюбл енным" " , где бы они ни были . " ) ; В о всех этих методах вы должны включить любые требуе мые пробелы в строки: "юным " " влюбл енным" приводит к строке "юнымвлюбле нным" , а комбинация "юным " " влюбленным " дает в результате "юным влюбл е нным " . 1 50 Гл ава 4 Использование фун кци и s canf ( ) Теперь пе ре йдем от вывода к вводу и исследуе м функцию s can f ( ) . Библиотека С соде ржит не сколько функций ввода, и s can f ( ) является самой универсальной из них, поскольку она может читать несколько форматов. Разумеется , результат ввода с кла­ виатуры - это текст, поскольку клавиши ге не рируют текстовые символы : буквы, циф­ ры и знаки препинания . Когда вы хотите вве сти, с каже м , целое число 2004 , вы вводи­ те с клавиатуры символы 2 О О и 4 . Если вы хотите с охранить его как числовое значение , а не как строку, ваша про­ грамма должна выполнить посимвольное преобразование строки в числовое значе­ ние - име нно это функция s can f ( ) и делает ! О на преобразует строковый ввод в раз­ личные формы: целые числа , числа с плавающей запятой, с имволы и строки С . Эта операция об ратная по де йствию функции printf ( ) , которая преобразует целые чис­ ла, числа с плаваю щей запятой, с имволы и строки С в текст, который затем отобража­ ется на экране. Подобно функции print f ( ) , s can f ( ) использует управляю щую строку, сопровож­ даемую списком параметров . Управляющая строка указывает, к каким типам данных должен быть преобразован вводимый поток символов . Главное различие между ними заклю чается в списке параметров. Функция printf ( ) использует имена пе ременных, константы и выраже ния , а функция s can f ( ) - указатели на переме нные . По счастли­ вому стечению обстоятельств, для использования этой функции не тре буется ничего знать об указателях. Помните только следую щие два простых правила: • • Если вы ис пользуете функцию s can f ( ) для чтения значения одного из б азовых типов, пе ред име не м такой переменной ставьте знак & . Если вы ис пользуете функцию s can f ( ) для чтения строки в символьный массив, не знак & не нужен. В листинге 1 4 . 1 5 показана короткая программа, служащая иллюстрацие й упомяну­ тых правил. листинг 4.1 5. Проrрамма input . с ког да исполь зовать з нак & 1 1 input . c #incl ude <s tdio . h > -- int main ( vo i d ) { int age ; fl o at as s ets ; char pet [ 3 0 ] ; 1 1 пер еме нная 11 пер еме нная 11 строка printf ( " Bв eди т e инф ормацию о св о ем во зр асте , о сумме в банке и о любимом живо тном . \ n " ) ; s c an f ( " % d % f " , & age , & a s s ets ) ; 1 1 исполь зуйте знак & / / не и споль з уйте & для строко вого массив а s c an f ( " % s " , p e t ) ; printf ( " % d $ % . 2 f % s \ n " , ag e , as s ets , p e t ) ; r e turn О ; Символ ьные строки и фор матированный ввод-вы вод 1 51 Диалог с программой выглядит примерно так: Вв едите информацию о своем возр асте , о сумме в б анке и о любимом живо тном . 38 92 3 6 0 . 8 8 11/арии 3 8 $ 9 2 3 6 0 . 8 8 Шарик Функция s c an f ( ) использует символы , называемые пробельными (символы новой строки, табуляции и пробела ) , чтобы решить, как разделить ввод на отдельные поля . Это соответствует последовательным приме не ниям спецификаций пре образования к последовательным полям , с пропуском проб ельных символов, которые находятся ме­ жду ними. Обратите внимание на то , как ввод распространяется на две строки. Вы могли точно так же использовать одну или пять строк, пока в вашем рас поряжении имеется , по меньшей ме ре , один символ пере вода строки, пробела или табуляции ме­ жду каждым элементом. Единстве нное исклю чение представляет собой спе цификатор преобразования % с, который читает каждый следую щий с имвол, даже если этот сим­ вол является пробельным. Вскоре мы вернемся к этой те ме. Функция s c an f ( ) ис пользует в осно вном тот же набор с пецификаторов прео бра­ зова ния , что и функция p r i n t f ( ) . Главное различие с остоит в том , что функция print f ( ) ис пользует спе цификаторы % f , % е , % Е , %g и %G для об оих типов fl o at и douЬl e , тогда как функция s can f ( ) применяет их только для типа с плавающей запя­ той fl o at, тре буя модификатор 1 для типа douЫ e . В табл. 4.6 перечислены основные спе цификаторы преобразования , как это описано в стандарте С99. Таблица 4.6. спецификаторы преобра3ования стандарта ANSI с д.nя функции scanf О Спецификатор пр ео бр аз о вания Значение %с Интерпретирует ввод как символ. %d Интерпретирует ввод как десятичное целое число со знаком. %е, % f, %g, %а Интерпретирует ввод как число с плавающей запятой ( % а определено стандартом С99 ) . % Е , % F , % G, % а Интерпретирует ввод как число с плавающей запятой ( % а определено стандартом С99 ) . %i Интерпретирует ввод как десятичное целое число со знаком. %о Интерпретирует ввод как восьмеричное целое число со знаком. %р Интерпретирует ввод как указатель (адре с ) . %s Интерпретирует ввод как строку; ввод начинается с первого символа, не являющегося пробельным, и включает все символы до следующего пробельного символа. %u Интерпретирует ввод как десятичное целое число без знака. %х, %Х Интерпретирует ввод как шестнадцатеричное целое число со знаком. В ы также можете ис пользовать модификаторы в спецификаторах преобразования, перечисленных в табл. 4 .6 . Модификаторы размещаются между знаком процента и 1 52 Гл ава 4 символом преобразования . Если вы ис пользуете в с пецификаторе б оле е одного моди­ фикатора , они должны появляться в том же самом порядке, как показано в табл. 4 . 7 . Таблица 4.7. Модификаторы преобра3ований функции s canf О МоUи фихатор * Зиа-чеJШ е Подавигь присваивание (см. текст). Пример: " % * d " . цифра (ы) Максимальная ПIИрина поля; ввод останавливается, когда достигнута максимальная ПIИрина поля или если обнаружен первый служебный символ, каким бы он ни был. hh Читает целое число как s i gned char или unsi gned char . Пример: " % 1 0 s " . Примеры: " % hhd " , " % hhu " . 11 Читает целочисленное число как l o ng l o ng или как l ong l o ng без знака (стандарт С99). Примеры: " % lld " , " % l l u " . h, 1 или L Спецификаторы " % hd" и " % hi " указывают, чт о значение будет сохра­ нено с типом short int . Спецификаторы " % ho " , " % hx " и " % hu " опре­ деляют, что значение будет сохранено с типом unsigned s hort i nt. Спецификаторы " % ld" и " % li " указывают, что значение будет сохра­ нено с типом long. " % 1о " , " % 1х " и " % l u " определяют, что значение будет сохранено с типом uns igned lo ng. Спецификаторы " % 1 е " , " % 1 f " и " % l g " указывают, что значение будет сохранено с типом douЫ e . Использование модификатора 1 вместо 1 в комбинациях с е , f и g обеспечивает то , что значение будет сохранено с типом long douЫ e . В отсутствие этих модификаторов d, i, о и х указывают на тип int , а е , f и g - на тип flo at. Как видите , вполне возможно использование спецификаторов преобразования, при этом в таблицах опуще ны некоторые их свойства . Эти свойства , прежде всего , об­ легчают чте ние выбранных данных из источников , имеющих же сткий формат, таких как перфокарты или другие запис и данных. Пос кольку в этой книге функция s c an f ( ) используется , в первую очередь, как удобное с редство для ввода данных в программу в инте рактивном режим е , мы не будем обсуждать экзотические особенности, понятные только профессионалам . Ввод с помощью функции scaпf () Расс мотрим более внимательно, как функция s can f ( ) читает с имволы входного потока данных. Предположим, что вы используете спецификатор % d, чтобы чигать целое число . Функция s can f ( ) начинает читать поток ввода в посимвольном режиме . О на пропускает проб ельные символы (проб елы, символы табуляции и символы пе ре­ вода строки) до тех пор, пока не столкнется с символом, отличным от проб ельного . Символ ьные строки и фор матированный ввод-вы вод 1 53 Поскольку функция предпринимает попытку читать целое число , функция s c an f ( ) надеется получить символ цифры или, возможно, символ знака (+ или ) . Если она встречает цифру или знак, то запоминает знак и читает следую щий с имвол. Если это цифра , она сохраняет цифру и читает следую щий символ . Функция s can f ( ) продол­ жает читать и сохранять символы , пока не столкнется с нецифровым символом. Тогда функция приходит к заключению , что она достигла конца очередного целого числа. Функция s can f ( ) возвращает этот нецифровой с имвол обратно в поток ввода . Это оз­ начает, что в следую щий раз , когда программа начнет читать поток ввода , она начнет его с ранее отвергнутого нецифрового символа . Наконец, функция s ca n f ( ) вычисляет числовое значение , с о ответствую щее цифрам , которые она считала, и помещает это значение в с пециально предназначенную переменную . Если вы используете всю ширину поля, функция s can f ( ) прекращает чтение при достижении конца поля или на первом пробельном с имволе , в зависимости от того, что встретится раньш е . А чт о случится , если первый символ, отличный о т пробельного , не является цифрой, а скажем, представляет собой символ А? Тогда функция s ca n f ( ) тут же останавливается и помещает символ А (или что бы там ни было ) обратно в поток ввода . Специально предназначенной переменной никакое значение не присваивается, и в следую щий раз , когда программа возобновит чтение потока ввода , она начнет его с символа А. Если ваша программа работает только со с пецификаторами % d, функция s c an f ( ) никогда не пропустит этот символ А. Кроме того , е сли вы используете s ca n f ( ) с не­ сколькими с пецификаторами, ANSI С требует, чтобы функция пре кращала чтение ввода при первой же ошибке . Чте ние потока ввода с использованием других числовых спе цификаторов проис­ ходит так же, как в случае применения с пецификатора %d. Главное различие между ними заклю чается в том , что функция s can f ( ) может распознавать больше символов как часть числа . Например, спе цификатор %х требует, чтобы функция s can f ( ) распо­ знавала символы a-f и A-F как ше стнадцате ричные цифры . С пецификаторы с пла­ вающей запятой требуют, чтобы функция s can f ( ) распознавала десятичные точки, экс поне нциальную форму запис и и новую р-нотацию . Если вы используете спецификатор % s , то допускается любой символ, отличный от пробельного , поэтому функция s can f ( ) пропус кает пробельные символы до появле­ ния первого не проб ельного символа , после чего с охраняет вс е не служебные с имволы вплоть до следую щего появления проб ельного символа . Это означает, что с пецифика­ тор % s заставляет функцию s can f ( ) читать одиночные слова , то есть строки, которые не с одержат пробельных символов. Если вы используете всю ширину поля , функция s can f ( ) пре кращает чтение в конце поля или на пе рвом проб ельном символе . Вы не можете использовать вс ю ширину поля , чтобы заставить функцию s ca n f ( ) читать б о­ лее одного слова при наличии одного спецификатора % s . Завершающий штрих: когда функция s c an f ( ) помещает строку в с пециально предназначе нный для этой цели мас­ сив, она то добавляет завершающий с им вол ' \ О ' с те м, чтобы сделать содержимое массива строкой С . Если в ы указываете спецификатор % с, т о вс е вводимые с имволы запоминаются в исходном виде. Если следую щим вводимым с имволом является пробел или символ но­ вой строки, то пробел или символ новой строки присваивается указанной перемен­ ной; пробельный символ не опускается. - 1 54 Гл ава 4 По сути дела функцию s ca n f ( ) нельзя с читать наиб оле е часто используемой функ­ цией ввода в С. О на рассматривается здесь в силу своей униве рс альности (она может читать вс е многооб разие типов данных ) , тем не менее , в С доступны нес колько других функций ввода, такие как ge tchar ( ) и g e t s ( ) , которые больше подходят для выпол­ не ния ряда конкретных задач, например, чте ния одиночных символов или чтения строк, содержащих пробелы. Мы рассмотрим некоторые из этих функций в главах 7, 1 1 и 1 3 . В то же вре мя , если вам нужно ввести целое число или десятичную дробь, символ либо строку, вы можете воспользоваться услугами функции s ca n f ( ) . Обычные символы в форматирующей строке Функция s can f ( ) предоставляет возможность помещать обычные с имволы в фор­ матирую щую строку. Об ычные с имволы, отличные от пробела , должны быть согласо­ ваны с форматом вводимой строки. Например, предположим, что вы случайно поме­ щаете запятую между двумя с пецификаторами: s c an f ( " % d , % d " , &n , &m) ; Функция s can f ( ) интерпретирует эту строку следую щим образом : нужно напе ча­ тать число , затем напечатать запятую , а затем второе число . То есть вы должны вве­ сти два целых числа следую щим образом: 88 121 1 Поскольку запятая стоит в форматирую щей строке непосредственно за специфика­ тором % d, вы должны напечатать ее сразу после числа 8 8 . Однако, так как функция s can f ( ) пропускает пробельный символ, предшествую щий целому числу, вы могли на­ печатать пробел или символ новой строки после запятой при вводе . То есть, варианты 88 121 1 и 88 12 1 1 также были б ы приемлемыми. Пробел в форматирую щей строке означает, что нужно пропускать любой пробель­ ный символ перед следую щим элементом ввода . Например, оператор s c an f ( " % d , % d " , &n , &m) ; принял бы лю бую из следую щих входных строк: 88 , 121 88 121 88 121 1 1 Обратите внимание , что понятие "любой пробельный символ" вклю чает в с ебя от­ сутствие пробельного символа как частный случай. За исклю чение м % с, все остальные спецификаторы автоматически пропускают пробельный с имвол, предшествую щий вводимому значению , так что опе ратор s can f ( " % d % d " , &n , &m) ведет себя так же, как и s саn f ( " % d %d " , & n , &m) . В случае спе цификатора %с включение пробела в форматирую щую строку изменяет это пове­ дение . Например, если в форматирую щей строке спецификатору % с предшествует проб ел, то функция s can f ( ) пропускает все до появления первого не проб ельного символа . Таким образом, опе ратор s can f ( " % с " , & c h ) читает первый значащий сим- Символ ьные строки и фор матированный ввод-вы вод вол, с которым сталкивается при вводе , а s ca n f ( " % с " , че нный ею непробельный символ. 1 55 & c h ) читает первый встре­ Возвращаемое значение функции scanf () Функция s c an f ( ) возвращает количество элеме нтов , которые она успешно прочи­ тала . Если она не прочитала никаких элементов, что бывает в тех случаях, когда вы печатаете не числовую строку, а s can f ( ) ожидает число , она возвращает О . s c an f ( ) возвращает EOF , когда обнаруживает условие , известное как "конец файла" . (EOF - это спе циальное значе ние , определе нное в файле s tdio . h. Как правило, дире ктива #de fi ne присваивает EOF значе ние 1 ) Мы рас смотрим признак конца файла в главе 6, а вопросы использования возвращаемого значения функции s can f ( ) - далее в этой главе . После того как вы изучите операторы if и whi l e , вы с можете использовать воз­ вращаемое значение функции s c an f ( ) , чтобы обнаруживать и обрабатывать несогла­ сованный ввод. - . М одифи катор * и его использован и е в фун кциях printf ( ) и s canf ( ) Модификатор * ис пользуется в функциях printf ( ) и s can f ( ) для изме не ния зна­ че ния спецификатора , причем вес ьма несхожими с пособами. Сначала посмотрим, что модификатор * может сделать для функции printf ( ) . Предположим , что вы не хотите фиксировать ширину поля заранее, но хотите , чтобы е е определила сама программа. Вы можете сделать это , воспользовавшись мо­ дификатором * вместо числа, задающего ширину поля , но при этом вам придется с помощью аргумента функции с ооб щить, какой должна быть ширина поля . Иначе го­ воря , если вы работаете со с пецификатором пре образования % * d, с писок аргументов должен вклю чать значе ние для * и значе ние для d. Эта методика также может быть использована применительно к значениям с плавающей запятой с тем , чтобы задавать точность, а также ширину поля. В листинге 4 . 1 6 показан не большой пример , демонст­ рирую щий, как все это раб отает. листинг 4.1 6. Проrрамма va rwid . e / * varwid . e - - исполь з о в ание поля вывода переменной ширины * / #incl ude <s tdio . h > int main ( vo i d ) { un s i gned width , pr e ci s ion ; int numЬ er = 2 5 6 ; do uЫ e weight = 2 4 2 . 5 ; printf ( " Kaкoвa ширина поля ? \ n " ) ; s c an f ( " % d" , &width ) ; printf ( " Знач ение р авно : % * d : \ n " , width , numb er ) ; printf ( " T eпepь вв едите ширину и точно сть : \ n " ) ; s c an f ( " % d % d " , &width , &p r e ci s ion ) ; p r i nt f ( " B e c = % * . * f\ n " , width , p r e ci s ion , weight ) ; printf ( " Го тово ! \ n " ) ; r e turn О ; 1 56 Гл ава 4 Переменная width определяет ширину поля, а переменная numЬ er это число , ко­ торое должно быть напечатано. Поскольку модификатор * предшествует d в специфи­ каторе, значение width предшествует значению numЬ er в спис ке параметров функции print f ( ) . Точно так же значения width и p r e ci s ion предоставляют необходимую информацию для форматирования при пе чати значения weight. Ниже показан ре­ зультат выполнения этой учебной программы : - Какова ширина поля ? 6 Зн ач ение р авно : 2 5 6 : Т е перь в в едите ширину и точно с т ь : 8 3 Вес = 2 42 . 50 0 Го тово ! В рассматриваемом случае ответ на первый вопрос был 6 , так что ширина исполь­ зованного поля задана равной 6. Т очно так же в ре зультате второго ответа б ыла уста­ новлена ширина поля 8 с 3 цифрами с права от де сятичной точки. В общем случае про­ грамма с ама может принять ре шение в отноше нии значений этих переменных после того , как ей становится известным значе ние weight . В случае функции s ca n f ( ) модификатор * выполняет совс ем другую работу. Когда модификатор * поме ще н между знаком % и с имволом спецификатора , это заставляет функцию пропустить с о ответствую щий ввод. В листинге 4 . 1 7 представле н соответст­ вую щий приме р. листинг 4.1 7. Проrрамма sld.p2 . c / * s kip2 . c - - игнорируе т первые дв а целых числ а из потока ввода * / #incl ude <s tdio . h > int main ( vo i d ) { int n ; printf ( " Bв eди т e три целых числа : \ n " ) ; s c an f ( " % * d % * d % d " , f i n ) ; рrintf ( " По следним ц елым числом было % d\ n " , n ) ; r e turn О ; Функция s ca n f ( ) в листинге 4 .1 7 выполняет следую ще е : пропус кает два целых числа и копирует третье целое число в переменную n. В результате выполнения про­ граммы получаются такие выходные данные : Вв едите три це лых числа : 2 0 04 2 0 0 5 2 0 0 6 По следн е е цело е число буд е т 2 0 0 6 Это средство пропус ка полезно в тех случаях, когда , например, не обходимо про­ честь конкретный столбец файла , в котором данные упорядочены в виде однородных столбцов. Символ ьные строки и фор матированный ввод-вы вод 1 57 Советы по испол ьзова н и ю фун кции printf ( ) О пределение фикс ированной ширины поля поле зно , когда вы хотите выводить данные в форме столбцов. Пос кольку заданная по умолчанию ширина поля е сть всего лишь ширина числа , выраже нная в числах, повторное ис пользование , скаже м , такого оператора printf ( " % d %d % d\ n " , val l , val 2 , val 3 ) ; приводит к пе чати данных в виде выровне нных столбцов , даже если числа в столб це имели различные размеры . Например, вывод может выглядеть следую щим образом: 12 4 22334 234 5 2322 1222 23 10001 (В данном случае предполагается , что значе ние переменных подвергалос ь измене­ ниям в проме жутке между выполнение м операторов пе чати. ) В ывод может б ыть упорядочен, если ис пользовать достаточно большую фиксиро­ ванную ширину поля . Например, использование оператора printf ( " % 9d % 9 d % 9d\ n " , val l , v al 2 , v al 3 ) ; позволило б ы получить следую щие выходные данные : 12 4 22334 234 5 2322 1222 23 10001 В клю чение пробела между одним спе цификатором преобразования и следую щим спе цификатором гарантирует, что одно число никогда не будет наложено на другое, даже если оно выйдет за границы своего с об ственного поля . Это происходит потому, что в управляющей строке пе чатаются обычные символы , включая пробелы. С другой стороны , е сли число должно быть вклю чено в фразу, часто б ывает удобно определить поле таким же или меньшим по разме ру, чем ширина ожидаемого числа. Благодаря такому подходу число размещается правильно , без ненужных пробелов . На­ приме р, оператор printf ( " Kayнт Беппо про бежал % _ 2 f мил ь за 3 ч а с а _ \ n " , di stance ) ; печатает следую щую фразу: Каунт Бе ппо пр обежал 1 0 . 2 2 миль за 3 ч а с а . Изменение с пецификации преобразования на % 1 О . 2 f привело бы к такому ре зуль­ тату: Каунт Бе ппо пр обежал 1 0 . 2 2 миль за 3 ч а с а . К л юч евые п онятия В языке С тип char представляет единичный символ . Для представления последо­ вательности с имволов в С используются символьные строки. Одной из форм строки является символичес кая константа , в которой с имволы заклю чены в двойные кавыч­ ки; примером может служить строка "Удачи , дру з ь я ! " . Вы можете хранить строку в массиве символов, который разме щается в с межных байтах памяти компьютера. 1 58 Гл ава 4 С имвольные строки, выраженные как символичес кие константы либо хранящиеся в мас сиве символов, оканчиваются символом, который не выводится на печать и на­ зывается нулевъ;м ( пиlО с имволом. Плодотворной оказалась идея представлять числовые константы в программе сим­ волическими, либо посредством директивы #de fin e , либо с помощью клю чевого сло­ ва con s t . Символические константы делают программу удоб очитаемой и легкой для сопровождения и внесения изме не ний. Стандартные функции ввода и вывода s can f () и print f () языка С используют сис­ тему, в которой вы должны согласовать спецификаторы в первом аргументе со значе­ ниями в последую щих аргументах. Согласование , скажем, спецификатора типа int, та­ кого как % d, со значением flo at приводит к непредсказуемым результатам. Вы должны внимательно следить за тем, чтобы количество и типы спецификаторов были согласо­ ваны с остальными аргументами функций. Что касается функции s canf ( ) , то не забы­ вайте проставить перед именем переменной префикс в виде адресной операции ( & ) . Пробельные символы (с имволы табуляции, пробела и новой строки) играют решаю щую роль в том, как s can f ( ) видит данные ввода. За исклю чением ре жима ввода , задаваемого с пецификатором % с (который читает только следую щий символ ) , при чтении входных данных функция s c an f ( ) пропус кает все символы пробела д о первого не пробельного символа . Далее она продолжает чте ние с имволов до тех пор, пока не об наружит проб ельный с имвол либо пока не об наружит символ, имеющий тип, отличный от заданного . Теперь посмотрим, что происходит , если мы подадим на ввод одну и ту же строку, но при различных режимах работы функции s c an f ( ) . Начнем со следую щей входной строки: -13 . 45е12# О Прежде вс его , предположим , что используется режим % d; в этом случае функция s can f ( ) прочтет три символа ( - 1 3 ) и остановится на точке , рассматривая ее как сле­ дую щий входной символ. Зате м функция s can f ( ) пре образует последовательность символов - 1 3 в соответствую щее целочисленное значе ние и сохраняет его в перемен­ ной назначения типа int. Далее , в режиме % f функция s can f ( ) читает ту же строку как последовательность символов - 1 3 . 4 5Е 1 2 и останавливает чтение на с имволе #, ос­ тавляя его для следую ще й операции ввода. Затем она пре образует последовательность символов - 1 3 . 4 5Е 1 2 в с о ответствую щее значение с плавающей запятой и с охраняет его в пере менной типа f l o at. Читая ту же строку в ре жиме % s , функция s can f ( ) про­ читает последовательность с имволов - 1 3 . 4 5Е 1 2 # и останавливается на проб еле , ос­ тавляя его для следую ще й операции ввода. Затем она сохраняет коды вс ех этих десяти символов в мас сиве назначения, добавив в коне ц масс ива нулевой символ. Наконец, при чтении этой же строки в режиме %s функция s ca n f ( ) прочтет и сохранит первый символ, в данном случае это пробел. Р езю м е Строка - это последовательность с имволов, рас сматривае мая как единый блок. В языке С строки представле ны последовательностями с имволов , заве ршаю щимися нуле вым символом, АSС П-код которого раве н О. Строки могут сохраняться в символь­ ных масс ивах. Массив представляет собой последовательность элементов, имеющих ОДИН и тот же тип. Символ ьные строки и фор матированный ввод-вы вод 1 59 Чтобы объявить массив с именем name , содержащий 3 0 элементов типа char , вос­ пользуйте сь следую ще й конструкцией: char name [ З O ] ; Позаботьте с ь о том, чтобы система выделила такое количество элеме нтов , кото­ рое достаточно для того , чтобы вме стить строку целиком , включая нулевой символ. Строковые константы создаются путем заклю чения строки в двойные кавычки: " Э то пример строки " . Функцию s trl e n ( ) можно ис пользовать для определения длины строки (без нуле­ вого символа в конце ) . Функция s ca n f ( ) , будучи вызванной вме сте с о с пецификато­ ром % s , может применяться для чте ния строк, состоящих из одного слова. Препроце с с ор языка С осуществляет поиск в исходном тексте программы дирек­ тив для препроцес сора, которые начинаются с с имвола #, и действует в соответствии с этими дире ктивами до компиляции программы . Директива #include заставляет проце с с ор доб авлять с одержимое другого файла в ваш файл в то ме сто , где располо­ же на эта дире ктива. Директива #de fine позволяет определять явные константы, то есть задавать символичес кие представления констант. Заголовочные файлы limi ts . h и float . h используют директиву #de fin e , чтобы определить набор констант, пред­ ставляю щих различные свойства цело численных типов и чисел с плавающей запятой. Для с оздания символических констант вы также можете использовать модификатор con s t . Функции p r i n t f ( ) и s can f ( ) обеспечивают универсальную подде ржку ввода и вы­ вода . Каждая из них использует управляю щую строку, содержащую вложе нные с пеци­ фикаторы преобразования , которые указывают количество и типы элементов данных для чтения или вывода на печать. Вы можете также использовать с пецификаторы преобразования , чтобы управлять внешним видом выходных данных: шириной поля, количеством десятичных позиций и размещением в пределах поля . В оп росы для са м окон троля Ответы на эти вопросы находятся в приложении А. 1 . Запустите программу из листинга 4.1 еще раз и введите имя и фамилию , когда программа предложит ввести имя. Что происходит в этом случае? Поче му? 2. Предположим, что каждый из следую щих примеров является частью законче н­ ной программы. Что печатать будет каждая такая часть? а. print f ( " Oн продал э ту кар тину з а $ % 2 . 2 f . \ n " , 2 . 3 4 5 е 2 ) ; б. print f ( " % c % c % c\ n " , в. 'Н' , 105, '\41 ' ) ; #de fi ne Q " Ег о Г амле т был хорош и без н амека на вул ь г арно сть . " print f ( " % s \ ncoдepжит % d симв олов . \ n " , Q , s trl en ( Q ) ) ; � рrint f ( "Являе тся ли % 2 . 2 е тем же , ч то и % 2 . 2 f ? \ n " , 12 0 1 . 0 , 12 0 1 . 0 ) ; 3 . Какие изменения необходимо сделать в пункте 2.в, чтобы строка Q была напе ча­ тана без кавычек? 4. Попытайтесь найти ошиб ку в следую щем коде: 1 60 Гл ава 4 de fine В ЬооЬоо de fine Х 1 0 main ( i nt ) { int age ; char name ; printf { " B в eдитe свое имя , " ) ; s ca n f ( " l s " , name ) ; p r i nt f ( " X opoшo , % С , а ско л ь ко в ам ле т ? \ n " , name ) ; s ca n f ( " I E " , age ) ; хр = age + Х ; printf ( " Неужели , % s ! В ам должно быть , по меньшей мер е , l d л е т . \ n " , В , хр ) ; r e r un О ; 5 . Предположим, что программа начинается так: #de fin e ВООК " Война и мир " int main ( vo i d ) { flo at co s t = 1 2 . 9 9 ; flo at per cent = 8 0 . 0 ; Напишите оператор print f ( ) , который использует ВООК, co s t и p er cent , чтобы напе чатать следую щее: Этот э к з емпляр книги " В ойна и мир " стоит $ 1 2 . 9 9 . Э то 8 0 % о т ц ены прайс-листа . 6 . Какие спе цификаторы преобразования вы бы использовали, чтобы напе чатать: а. Десятичное целое число с шириной поля , равной количеству цифр этого числа . б . Ш е стнадцате ричное целое число в форме 8А в поле шириной 4 с имвола . в. Число с плавающей запятой в форме 2 3 2 . 3 46 с шириной поля 1 0 с имволов . r. Число с плавающей запятой в форме 2 . 3 3 е+ОО2 с шириной поля 1 2 символов . д. Строку, выровненную п о ле вому краю в поле шириной 3 0 символов. 7. Какие спе цификаторы преобразования вы бы использовали, чтобы напе чатать перечисленные ниже выражения? а. Целое число типа unsigned long в поле шириной 15 с имволов. б. Ш е стнадцате ричное целое число в форме Ох8а в поле шириной 4 с имвола . в. Число с nпавающей запятой в форме 2.3 3Е+О2, выровненное по левому краю в поле шириной 12 символов. r. Число с плавающей запятой в форме + 2 3 2 .3 46 в поле шириной 10 с имволов. д. Пе рвые 8 символов строки в поле шириной 8 символов. 8 . Какие спе цификаторы преобразования вы бы ис пользовали, чтобы пе чатать перечисленные ниже выражения? а. Десятичное целое число, имеющее минимум 4 цифры в поле шириной 6 символов. Символ ьные строки и фор матированный ввод-вы вод 1 61 б . Восьме ричное целое число в поле, ширина которого будет задаваться в спи­ ске параметров. в. Символ в поле шириной 2 символа . г. Число с плавающей запятой в форме + 3 . 1 3 в поле шириной, которая равна количе ству символов в этом числе . д. Пе рвые пять символов в строке, выровненной по ле вому краю поля шири­ ной 7 символов. 9 . Для каждой из следую щих входных строк напишите оператор s can f ( ) , чтобы прочитать их. О бъявите также пе ременные или массивы , используемые в опе­ раторе . а. 1 0 1 б . 2 2 . 3 2 и 8 . 3 4Е-09 в. linguini г. catch 22 д. catch 22 (но пропустить catch) 10. Что такое проб ельный символ? 1 1 . Предположим, что вы предпочитаете пользоваться круглыми скобками вместо фигурных с кобок в своих программах. Насколько хорошо бы раб отали следую­ щие конструкции? #de fin e #de fin e ) } Уп ражн ен и я по програм м ирован и ю 1 . Напишите программу, которая запрашивает имя и фамилию , а затем печатает их в формате " фамилия, им.Я' . 2 . Напишите программу, которая запрашивает имя и выполняет с ним следую щие действия: а. Пе чатает его заклю ченным в двойные кавычки. б. Пе чатает е го в поле шириной 20 символов , при этом все поле заклю чается в кавычки. в. Пе чатает его с левого края поля шириной 20 символов , при этом вс е поле заклю чается в кавычки. г. Пе чатает его в поле шириной, на три с имвола превышающем длину имени. 3. Напишите программу, которая читает число с плавающей запятой и печатает его сначала в де сятичной, а затем в экс поненциальной форме . Предусмотрите вывод в следую щих формах (количество цифр показателя степени в вашей сис­ теме может быть другим) . а. Вводом является 2 1 . З или 2 . 1 е + О О 1 . б . Вводом является + 2 1 . 2 9 0 или 2 . 1 2 9Е+ 0 0 1 . 1 62 Гл ава 4 4. Напишите программу, которая запрашивает рост в дю ймах и имя , после чего отоб ражает получе нную информацию в следую ще й форм е : Л арри , в аш р о с т с о с тавл я е т 6 . 2 0 8 фу тов Ис пользуйте тип float, а также операцию деления / . Запросите рост в санти­ метрах и выразите в метрах, если вам так удоб не е . 5 . Напишите программу, которая запрашивает имя пользователя и затем е го фа­ милию . Сделайте так, чтобы она пе чатала введе нные имена в одной строке и количество с имволов в каждом слове в следую щей строке. Выровняйте каждое количество символов по концу соответствующего имени, как показано ниже: Meli s s a Honeyb e e 7 8 Зате м сделайте так, чтобы она печатала ту же самую информацию , но с количе­ ством с имволов, выровненным по началу каждого слова : Meli s s a Honeyb e e 7 8 6 . Напишите программу, которая присваивает пере менной типа douЫ e значение 1 . 0 / 3 . 0 и пе ременной типа float значение 1 . 0 / 3 . 0 . В ыведите на экран каж­ дый ре зультат три раза - в первом случае с четырьмя цифрами справа от деся­ тичной точки, во втором случае - с двенадцатью цифрами и в третьем случае с шестнадцатью цифрами. Вклю чите также в программу заголовочный файл float . h и выведите на экран значе ния F LT_DI G и DBL_D I G . Совместимы ли вы­ веденные значе ния со значе нием 1 . 0 / О . 3 ? 7. Напишите программу, которая прос ит пользователя ввести количество преодо­ ле нных миль и количе ство галлонов израсходованного бензина . Затем эта про­ грамма должна рассчитать и отобразить на экране количе ство миль, пройде н­ ных на одном галлоне горючего , с одним знаком после десятичной точки. Да­ лее, используя тот факт, что один галлон приблизительно равен 3 . 785 литра и одна миля - 1 .609 километра , ваша программа должна пе ревести значение в милях на галлон в литры на 1 0 0 километров (обычную е вропейскую меру изме­ рения потребления горю чего ) и выве сти ре зультат с одним знаком после деся­ тичной точки. (Об ратите внимание , что аме риканс кая схема измеряет затраты горючего , не обходимого для преодоле ния заданного рас стояния , тогда как ев­ ропе йс кая схема измеряет пройденный путь на единицу горю чего.) Ис пользуй­ те с имволиче ские константы (определенные с помощью cons t или #de fin e ) для этих двух параметров преобразования. ГЛА ВА 5 О п е ра ц и и , в ы раже н ия и о п е р ато р ы в этой главе: • кл ю ч ев ы е сл ова: whi1e, t:yp edef • Опера ции: • • = + - * / % ++ -- (тип) М н ожествен ные опера ц и и языка С, в том ч и сл е и те. которые испол ьз уются для реал иза ц и и обы чных ар ифм етич еских опера ций При оритеты опера ций и знач ен и е терм инов " оператор" и "выражение " • • • Удобн ы й в использова н и и оп ератор цикла whi1е Соста в н ы е оп ераторы, а втоматич еское преобразов ание ти пов и при в ед ен и е типов Н а п исание функций, испол ьз ующих арrvменты с ейча с , когда вы уже о з нако милис ь со с пособами представле ния данных, прове­ де м а нализ способов обработки данных. Для этих целе й язык С предлагает множество различных опе раций. В ы можете выполнять ар ифметиче с кие о пе­ рации, с ра внивать з начения , обновлят ь значе ния пе реме нных, ло гичес ки ко мб иниро­ вать отноше ния и делать многое другое . Н а чнем с а рифметичес ких опера ций - сло­ же ния , вычитания , умноже ния и деления. Другим аспе ктом обработки да нных является организа ция ваших программ с тем, чтобы о ни вы полняли правильные действия в нужном порядке . С обладает несколькими языковыми ср едства ми, кото рые по мо гают решить эту задачу. Одним из этих средств явля ются циклы, и в это й главе вы получите первое представле ние о них. Цикл позволя­ ет по вторять де йствия, делая программу более инте рес ной и повышая ее возможности. Введение в ци кл ы В листинге 5 . 1 представлена де м о нстрацио нная про гра мма, выполня ю щая не­ сложные а риф метичес кие о пе р а ции по вычисле нию длины ступни в дю ймах, для ко­ торой подходит обувь ( мужс кая) размера 9. Чтоб ы вы в полно й мере с мо гли о це нить пре имущества циклов, эта ве рсия про гра ммы иллю ст рирует огра ниче ния про грамми­ рова ния , не ис пользую ще го циклов. 1 64 Гл ава 5 Листинг 5.1 . Программа shoe s l . c / * s h o e s l . c - - пр еобр а з у е т р азмер о буви в дюймы * / #incl ude <s tdio . h > #de fi ne ADJUST 7 . 6 4 #de fi ne SCALE 0 . 3 2 5 int main ( vo i d ) { do uЫ e s ho e , foot ; shoe = 9 . 0 ; foot = S CALE * shoe + ADJUST ; длина ступни\ n " ) ; printf ( " Paзмep о буви (мужской ) printf ( " % 1 0 . l f % 2 0 . 2 f дюймо в \ n " , shoe , foot ) ; r e turn О ; Мы, наконец, получили желанную программу с опе рациями умножения и сложе­ ния . Она берет разме р вашего ботинка (если вы носите разме р 9) и сообщает вам, ка­ ков размер вашей ступни в дю ймах. Вы можете сказать: "Я вполне могу решить эту программу вручную быстрее, чем вы введете эту программу с клавиатуры" . Правиль­ ное замечание . Одноразовая программа , которая пе реводит в дюймы только один размер, представляет собой напрасную трату времени и усилий. Вы можете сделать программу более полезной, если ре ализуете в ней интерактивность, но и в этом случае могучий потенциал компьютера не будет задействован в полной мере. В ам нужно заставить компьютер выполнять повторные вычисления для некоторой последовательности размеров обуви. В конце концов, это одна из причин применения компьютеров для арифметиче ских вычислений. Язык С предлагает не сколько методов выполнения повторных вычислений, и зде с ь мы рас смотрим один из них. Этот метод, получивший название ?&Uкла whi l e , позволяет обес пе чить более эффективное исполь­ зование операций. В листинге 5.2 показан усовершенствованный вариант программы определе ния длины стопы по размеру обуви. листинг 5.2. Программа shoes2 . с / * s h o e s 2 . c - - вычисля е т длину стопы для н е ско л ь ких р а змеров о буви * / #incl ude <s tdio . h > #de fi ne ADJUST 7 . 6 4 #de fi ne SCALE 0 . 3 2 5 int main ( vo i d ) { do uЫ e s ho e , foot ; printf ( " Paзмep о буви (мужской ) длина ступни\ n " ) ; shoe = 3 . 0 ; whi l e ( s ho e < 1 8 . 5 ) / * нач ало цикла whi l e * / / * нач ало блока */ { foot = SCALE * shoe + ADJUST ; print f ( " % 1 0 . l f % 2 0 . 2 f дюймов \ n " , s ho e , foot) ; shoe = shoe + 1 . 0 ; */ / * коне ц бло ка printf ( " E cли о бувь подходи т , но сите e e . \ n " ) ; r e turn О ; Операции, вы ражения и оператор ы 1 65 В от как выглядит сжатая верс ия выходных данных программы s ho e s 2 . с: Ра змер о буви ( мужской ) длин а ступни 8 . 6 2 дюймов 3.0 4.0 8 . 9 4 дюймов 1 3 . 1 6 дюймов 17 . 0 18 . 0 1 3 . 4 9 дюймов Е с ли обувь подходи т , носи т е е е . (По сути, значе ния констант для этого преобразования были получены во время не официального визита в обувной магазин. Единстве нная метка для обуви, которая лежала на прилавке , б ыла предназначе на для мужской обуви. Тем, кого интере суют размеры же нс кой обуви, придется с амим отправиться в обувной магазин. Кроме того, программа делает нереалистичные предположе ния о том, что существую щая с истема размеров обуви рациональна и единообразна . ) В от как работает цикл whi l e . Когда программа впе рвые попадает н а опе ратор цик­ ла whi l e , она прове ряет , принимает ли условие в круглых скобках значе ние tru e . В этом случае выражение этого условия имеет вид : shoe < 1 8 . 5 С имвол < означает "меньше чем" . Переменная shoe инициализирована значением 3 . О, что, естественно , меньше 1 8 . 5. В силу этого обстоятельства условие принимает значение tr u e , и программа переходит к следую ще му оператору, который преобразу­ ет размер в дюймы . Зате м она пе чатает результат. Следую щий оператор увеличивает значение s ho e на 1 . О, в результате чего эта переменная получает значение 4 . О : s h o e = s ho e + 1 . 0 ; В этом месте программа возвращается к порции кода whi l e , чтобы проверить ус­ ловие . Но почему именно в этом месте? Потому, что в следую щей строке находится закрывающая фигурная скобка (J ) , а код ис пользует пару скобок ( { } ) для обозначения границ цикла whi l e . О пе раторы , которые находятся между двумя фигурными скобка­ ми, ис пользуются повторно . Раздел программы , расположенный внутри фигурных скобок, и сами фигурные скобки, называются блоком. Однако вернемся к программе . Значение 4 меньше 1 8 . 5 , следовательно , вся совокупность команд, заклю ченных в фи· гурные скобки (блок) , следую щая за whi l e , повторяется. (Пользуясь компьютерным жаргоном, программа "прос матривает эти операторы в цикле " . ) Это продолжается до тех пор , пока пе ременная s ho e не достигнет значе ния 1 9 . 0 . Тогда условие shoe < 1 8 . 5 получает значе ние fal s e , так как 1 9 .0 не меньше 1 8 . 5 . Как только это произойдет, управление пе редается первому оператору, следую щему за циклом whi l e . В рассмат­ риваемом случае это завершающий опе ратор print f ( ) . Вы легко можете приспособить эту программу для выполнения других преобразова· ний. Например, назначьте константе SCALE значение 1 . 8 , а константе ADJUST 3 2 . О, и вы получите программу, которая преобразует значение температуры по Цельсию в зна· чение по Фаренгейту. Назначьте константе SCALE значение О . 6 2 1 4 , а ADJUST О , и вы получите преобразование километров в мили. Внося описанные модификации, естест· венно , измените содержание выходных сообщений, дабы избежать путаницы. Цикл whi l e предоставляет в ваше распоряже ние удобное и гибкое с редство управ· ления ходом выполнения программы . - - 1 66 Гл ава 5 А с е йчас перейде м к изуче нию фундаментальных опе раций, которыми вы можете воспользоваться в своих программах. Фундам ентал ьн ые операци и Язык С ис пользует опе рации для представления арифметиче ских действий. На­ приме р, операция + означает , что две величины, находящиеся по обе стороны знака +, складываются . Если те рмин опера'ЦUЯ покажется вам странным, согласите сь с те м, что вещи подобного рода нужно каким-то образом называть. " Операция" выглядит ничуть не хуже , чем , скажем, " эта ве щь" или " арифметиче ский транзактор". Т епе рь рассмот­ рим базовые арифметические опе рации: =, + , - , * и / . (В языке С операция возведения в степе нь отсутствует. Однако библиотека стандартных математических функций С предлагает для этих целей функцию pow ( ) . Например , роw ( З . 5 , 2 . 2 ) возвращает значение 3 .5 , возведенное в степе нь 2 . 2 . ) операция п рисваивания: В языке С знак раве нства не означает "равно" . Это операция прис ваивания значе­ ния . Наприме р, оператор bmw = 2 0 0 6 ; прис ваивает значе ние 2006 переменной с име не м bmw. Иначе говоря , элемент, распо­ ложенный слева от знака = представляет с обой имя, пе ременной, а эле мент с права значение , присваиваемое этой переменной. С имвол = называется опера'Цией присваива ния. С нова хотим вас предупредить, не думайте , что эта строка означает "переменная bmw равна 2006" . Напротив, она означает "присвоить переменной bmw значе ние 2006" . В этой опе рации действие направлено справа нале во . Возможно , это различие между именем пере менной и значением переменной покажется непринципиальным, однако , рассмотрим следую щий довольно-таки общий оператор: i = i + 1; С мате матиче ской точки зрения этот оператор не имеет смысла . Если вы прибави­ те 1 к конечному числу, ре зультат не может быть " равен" числу, к которому вы прибав­ ляете 1 , однако как компьютерный опе ратор i i прис ваивания он имеет смысл и пре красно i=i+l ; работает . О н означает следующее : "Извлечь i=22+1 ; значение пе ременной с именем i, добавить к i=23 ; этому значению 1 , а затем назначить это но­ вое значение пере менной i " , как показано на i + 1; Р и с. 5 . 1 . Оператор i рис . 5 . 1 . О пе ратор наподобие 2 0 0 6 = bmw ; в языке С не имеет смысла (и, разумеется , не является правильным ) , так как 2006 это константа . Вы не можете присвоить значение константе , она сама всегда являtш1ся. значение м. Имея дело с компьютером, всегда помните , что элемент слева от знака = должен быть именем какой-то пе ременной. По сути, слева должна находиться с сылка на ячейку памяти. Простейший спос об предполагает ис пользование имени перемен- Операции, вы ражения и оператор ы 1 67 ной, однако, как вы увидите далее , для определения с о ответствую щей яче йки памяти можно применять "указатели" . В обще м случае С употребляет термин модифи'Цируемое l-з'Ншче'Ние для выделения тех логических сущностей, которым вы можете присваивать значения . Фраза " модифицируемое !-значение", возможно , не несет пока четкого с о­ держательного смысла , поэтому рассмотрим нес колько определений. Немного терминологии: объекты данных, /-значения, r-зна чения и операнды Об'Ы!кm да'Н'НЪtх это общий термин, обозначающий область хранения данных, кото­ рая может быть использована для удержания значений. Например, область хранения данных для переменных или массивов представляет собой объект данных. В С использу­ ется понятие l-з'Ншче'Ние для обозначения имени или выражения, которое определяет конкретный объе кт данных. Имя переменной, например, является !-значением, следо­ вательно, объе кт означает фактическую область хранения данных, однако , !-значение есть метка , которая служит для определе ния ме стоположения этого участка памяти. Не вс е объекты могут менять свои значе ния , поэтому в С имеется те рмин "моди­ фицируемое l-з'На'Че'Ние' для обозначения тех объе ктов , которые могут менять свое зна­ че ние . По этой причине левая часть оператора присваивания должна б ыть модифи­ цируе мым !-значением. На с амом деле , " l " в !-значении происходит от слова " left" ("ле­ вое" ) , пос кольку модифицируемые !-значения могут указываться в левой стороне операторов присваивания . Те рмин " r-з'На'Че'Ние' означает числа , которые могут б ыть прис вое ны модифицируе­ мым !-значе ниям. Наприме р, рассмотрим следую щий оператор: - bmw = 2 0 0 6 ; В данном случае bmw это модифицируе мое !-значе ние , а 2006 r-значение . Как вы уже , должно б ыть, догадались, " r " в r-значении происходит от слова " right" ("правое" ) . R-значение может б ыть константой, переменной или любым другим выражением , ко­ торое дает в результате значе ние . По мере изучения име н ве ще й вырис овывается под­ ходящий термин для того , что мы называе м "эле ментом" (приме ром может служить фраза "элемент слева от знака = " ) , этот те рмин об означает операнд . О пе рандом явля­ ется то , чем опе рирует опе рация . Например, поедание бутерброда вы можете описать как выполнение операции "поедать" к операнду "бутерброд"; точно так же вы можете сказать, что ле вый операнд операции = должен быть модифицируе мым !-значе ние м. Б азовая опе рация прис ваивания в языке С отличается оригинальностью на фоне других его опе раций. Выполним программу, показанную в листинге 5.3 . - - Листинг 5_3_ Программа qo1f _ с / * go l f . c - - кар т а итог овых р е зуль татов игры в голь ф * / #incl ude <s tdio . h > int main ( vo i d ) { int j ane , tar z an , ch eeta ; ch eeta = tar z a n = j ane = 6 8 ; printf ( " чита тар з ан джейн\ n " ) ; pr int f ( " Сч е т п ервого р аунда % 4 d % B d % 7 d\ n " , cheeta , tar z an , j an e ) ; r e turn О ; 1 68 Гл ава 5 Многие языки программирования не допускают тройного прис ваивания значений, сделанного в этой программе , но С считает это обычным делом . Присваивания вы­ полняются справа нале во . Прежде всего, значение 68 получает переменная j an e , за­ тем tar z an и в заве ршение это значение прис ваивается переменной ch eeta. Получаем следую щие выходные данные : Сч е т первого р аунда чита 68 тар з а н 68 джейн 68 Операция сложения: т О пе рация сложения приводит к тому, что два значения с обеих сторон знака + об­ разуют сумму. Например, оператор printf ( " % d" , 4 + 2 0 ) ; выводит число 24, но не выражение 4 + 20 Складываемые значе ния (операнды) могут быть как переменными, так и констан­ тами. В с илу этого , оператор in come = s al ary + b r ib e s ; заставляет извлечь значе ния двух переменных в правой части оператора , выполнить их сложение , а результат сложения присвоить пере менной i n come . Операция в ы ч ита н ия: О пе рация вычитания состоит в том, что число , стояще е в опе раторе после знака - , вычитается и з числа , стоящего перед этим знаком. О пе ратор t a kehome = 2 2 4 . 0 0 - 2 4 . 0 0 ; прис ваивает пере менной takehome значение 2 0 0 . О . О пе рации + и - называются б инарными, или двухместными, это означает, что они выполняются над двумя опе рандами. опера ци и знака: - и т Знак "минус" может использоваться для указания или изменения алге браичес кого знака конкретного значения . Например, следую щая последовательность: ro cky = - 1 2 ; smo key = -ro cky ; заве ршается тем, что переменная s mo k ey получает значе ние 1 2 . Когда подобным образом используется знак " минус " , о н называется унарной (одно· местной) операцией, это означает, что она выполняется над одним операндом (рис . 5.2 ) . Стандарт С90 вводит в язык С унарную опе рацию + . О на н е меняет значе ния или знака операнда, она просто позволяет использовать такие операторы , как do zen = + 1 2 ; и при этом не получать сообщений об ошибке . Р аньше такая конструкция не допус­ калась. Операции, вы ражения и оператор ы Бинарная операция 36 - 1 2 1 -- ��-- -1 6 __ �-- Обе операции - ( 1 2 - 20) Значение равно 24 Два операнда -I __. _ _ Унарная операция ....._ 1 69 Значение равно - 1 6 Один операнд 1 -- Значение равно 8 1 l_I� Два операнда L__ Один операнд Ри с. 5 . 2 . Yuap'li:ыe и бииари'Ые опера-ции операция умножен ия: * Умножение обозначается символом * . О ператор cm = 2 . 5 4 * i n ch ; умножает переменную inch на 2 . 5 4 и присваивает результат умножения переменной cm. Между прочим, не желаете ли вы с оставить таблицу квадратов значе ний? В языке С отсутствует функция возведения в квадрат , но как показано в листинге 5.4, для этого вы можете воспользоваться операцией умножения и вычислить квадраты соответст­ вую щих величин. Листинг 5.4. Программа squares . с / * s q u ar e s . c -- г енерир у е т т аблицу кв адр атов 2 0 значений * / #incl ude <s tdio . h > int main ( vo i d ) { int num = 1 ; whi l e ( n um < 2 1 ) { print f ( " % 4 d % 6d\ n " , num , num * num ) ; num = num + 1 ; r e turn О ; Эта программа пе чатает первые 20 целых чисел и их квадраты , в чем вы можете убедиться сами. Расс мотрим более интерес ный пример. 170 Гл ава 5 экспоненциальный рост В ы , скорее вс его , слышали историю о могущественном правителе, который хотел вознаградить мудреца , оказавшего ему б ольшую услугу. Когда мудре ца спросили, чr о он желает получить, он указал на шахматную дос ку и попросил положить одно пше­ ничное зернышко на пе рвую клетку, два зернышка на вторую клетку, четыре - на тре­ тью , восемь - на четвертую и так далее . Правитель, не име ю щий понятия в математи­ ке, был пораже н с кромностью притязаний мудре ца , ибо б ыл готов предложить ему большие богатства . Мудрец сыграл с правителем злую шутку, как показывает програм­ ма в листинге 5 .5 . О на вычисляет, сколько зернышек приходится на каждую клетку и подс читывает об щую сумму. Т ак как вы вряд ли следите за объемами е же годных уро­ жае в пшеницы в С оединенных Штатах, программа сравнивает промежуточные суммы с урожаем, собирае мым каждый год в этой стране. листинг 5.5. Программа wheat . с / * wh e at . c - - экспоненциал ь н ый р о с т * / #incl ude <s tdio . h > #de fi ne SQUARES 6 4 / * колич е с тво кв адр атов шахматной до ски * / #de fi ne CROP 1 Е 1 5 / * урожай пше ницы в США в з ерньШJках * / i n t main ( vo i d ) { do uЫ e curr ent , total ; int count = 1 ; ") ; итого до бавлено printf ( " кв aдp a т printf ( " пpoцeн т о т \ n " ) ; pr int f ( " з ерен з ерен ") ; printf ( " ypoжaя в США\ n " ) ; / * н ачинаем с о дного з ерньШJка * / total = current = 1 . 0 ; printf ( " % 4 d % 1 3 . 2 е % 1 2 . 2 е % 1 2 . 2 e \ n " , count , cur r e nt , total , total / CROP ) ; whi l e ( count < SQUAR E S ) { count = count + 1 ; cur r e nt = 2 . 0 * curr ent ; / * удвоить колич е с тво зерен на следующем квадр а те * / total = total + curr ent ; / * о бновить и тоговую сумму * / printf ( " % 4d % 13 . 2 е % 1 2 . 2 е % 12 . 2 e \ n " , count , current , total , total /CROP) ; printf ( " Bo т и в се . \ n " ) ; r e turn О ; В начале выходные данные не должны были вызывать у правителя беспокойство : кв адр а т 1 2 3 4 5 до бавлено з ерен 1 . О О е+ О О 2 . О О е+ О О 4 . О О е+ О О 8 . О О е+ О О 1 . б О е+ О l ито г о з ер ен 1 . О О е+ О О 3 . О О е+ О О 7 . О О е+ О О l . 5 0 e+ O l 3 . l O e+ O l процент о т урожая в США 1 . О О е-15 3 . О О е-15 7 . О О е-15 1 . 5 0 е- 1 4 3 . l O e- 1 4 Операции, вы ражения и оператор ы 6 7 8 9 10 3 . 2 0 e+ O l 6 . 4 0 e+ O l 1 . 2 8е+О2 2 . 56е+ О2 5 . 12е+О2 6 . 3 0 e+ O l 1 . 27е+О2 2 . 55е+ О 2 5. lle+02 1 . 02е+О3 171 6 . 3 0 е- 1 4 1 . 27 е-13 2 . 55е-13 5 . l l e-13 1 . 02 е-12 На четырех клетках мудрец получил немного б олее тысячи зере н пшеницы , одна­ ко , посмотрите , что получилось на клетке 50 ! 50 5 . 63е+ 14 1 . 13е+15 1 . 1 3 е+ О О Добыча мудре ца превосходит вес ь ежегодный урожай США! Если в ы хотите , что произойдет на 64-й ячейке , выполните эту программу сами. Приведенный пример служит крас норечивой иллю страцией явления экспоненци­ ального роста . Население мира и использование энергетичес ких ресурс ов растет по тому же закону. Операция делен ия: / В языке С символ / используется для обозначе ния деления. Значение слева от сим­ вола / делится на значе ние , указанное с права . Наприме р, следую щее выраже ние дает в результате 4 . О : four = 1 2 . 0 / 3 . О ; Действия операции деле ния на целочисле нных типах отличаются от действий на типах с плавающей запятой. При деле нии типов с плавающей запятой получаем ответ в виде числа с плаваю щей запятой, в то время как деление целых чисел дает целочис­ ленный ре зультат . Целое число не может иметь дробной части, и это об стоятельство делает деление 5 на 3 неточным, пос кольку ответ не с одержит дроб ной части. В С лю· бая дробная часть, полученная при делении двух целых чисел, отбрасывается . Этот процесс называется усечепием. В ыполните программу, представле нную в листинге 5.6, чтобы пос мотреть, как ра­ ботает усе чение и чем отличается деление целых чисел от деле ния чис ел с плавающей запятой. листинг 5.6. Программа divide . с / * di vide . c - - деление , каким мы е г о з н а ем * / #incl ude <s tdio . h > int main ( vo i d ) { рrintf ( " Целочи сленно е дел ение : 5/ 4 р авно рrintf ( " Целочи сленно е дел ение : 6/3 р авно 7/4 рrintf ( " Целочи сленно е деление : р авно printf ( " Делени е с плавающей з апя той : 7 . / 4 . р авно 7 . / 4 р авно printf ( " Смешанно е деление : r e turn о ; %d \ n " , 5 / 4 ) ; %d \ n " , 6 / 3 ) ; %d \ n " , 7 / 4 ) ; % 1 . 2 f \n" , 7 . / 4 . ) ; % 1 . 2 f \n" , 7 . / 4 ) ; Листинг 5.6 вклю чает случай "смешанных типов" , когда значение с плаваю щей за­ пятой делится на целое число . С является б олее либеральным языком программиро­ вания по сравне нию с не которыми другими и позволяет вам выполнять такие опера- 172 Гл ава 5 ции, однако в обычных ситуациях желательно избегать с мешивания типов. Теперь расс мотрим результаты выполне ния этой программы: Це лочисленное делени е : Це лочисленное делени е : Це лочисленное деление : Деление с плав ающей з апя той : Смешанно е деление : 5/ 4 6/ 3 7/ 4 7 . /4 . 7 . /4 р авно 1 р авно 2 р авно 1 р авно 1 . 7 5 р авно 1 . 7 5 Обратите внимание , что деле ние целых чис ел не выполняет округления до бли· жайшего целого , оно в любом случае отбрас ывает дробную часть (то е сть выполняет усече ние ) . Если вы с мешиваете в одной операции целые числа и числа с плавающей запятой, вы получаете такой же ответ, как и в случае деления чисел с плавающей запя· той. На с амом деле компьютер не спос обен делить число с плавающей запятой на це· лое число , и в силу этого обстоятельства компилятор приводит оба операнда к едино· му типу. В рассматриваемом случае целое число преобразуется в число с плавающей запятой до того , как будет выполнена операция деле ния . До появления стандарта С99 язык С предоставлял разработчикам ре ализаций не· которую с вободу при решении вопрос а , как выполняется деление с ис пользованием отрицательных чис ел. Следует иметь в виду, что процедура округления предус матри· вает определение наибольше го целого значения, ме ньшего или равного числу с пла· вающей запятой. Разуме ется , 3 удовлетворяет этому требованию , если его сравнивать с числом 3 .8 . Но как быть в случае -3 .8? Метод наиб ольшего целого числа предполага· ет е го округле ние до -4, поскольку -4 меньше , чем -3 .8. Однако в другой трактовке процесс округле ния просто отб расывает дробную часть, то есть происходит усе-чеиие в паправлснии пуля, в результате которого число -3 .8 преобразуется в -3 . До появления стандарта С99 одни ре ализации ис пользовали пе рвый подход , другие - второй. Одна· ко стандарт С99 однозна чно требует ус е че ния в на правлении нуля , следовательно , -3 .8 преобразуется к -3 . С войства опе рации деле ния целых чис ел оказываются очень удобными для реше· ния некоторых классов задач, и вы вс коре увидите соответствую щий пример . В о­ первых, суще ствует еще один важный аспе кт: что произойдет , е сли вы включите сразу не сколько операций в один оператор? Это наша следую щая тема. Приоритеты опера ци й Расс мотрим следую щую строку кода : butter = 2 5 . 0 + 6 0 . 0 * n / SCALE ; В этом операторе присутствуют операции сложения, умножения и деления. Какая из них выполнится первой? Возможно , следует к 2 5 . О прибавить 6 0 . О , полученный резуль· тат 8 5 . О , умножить на n, после чего полученное произведение разделить на SCALE? Или же 6 О О необходимо умножить на n, к полученному произведению прибавить 2 5 . О , после чего результат сложения разделить на SCALE? Возможно, существует какой-то другой по· рядок выполнения арифметических операций? Пусть n равно 6 . О , а SCALE равно 2 . О . Ее· ли вы выполните рассматриваемый оператор с упомянутыми значениями, то в условиях первого подхода вы получите результат 2 5 5 . Второй подход даст в результате 1 92 . 5 . Программа н а С , по·видимому, использует какой·то другой порядок следования опера· ций, ибо она присваивает переменной butter значение 2 0 5 . О . • Операции, вы ражения и оператор ы 1 73 Разумеется , порядок выполнения различных опе раций приводит к различиям, сле­ довательно, в языке С должны быть определе ны однозначные правила, и выбор того, что должно делаться в первую очередь, должен осуществляться в соответствие с этими правилами. Язык С решает эту проблему, устанавливая порядок выполнения опера­ ций. Каждой операции назначается соответствую щий приоритет. Как и в об ычной арифметике , умножение и деле ние име ют б оле е выс окий приоритет, чем сложение и вычитание , поэтому они выполняются первыми. Но что делать, если две операции имеют один и тот же приоритет? Если они совме стно используют один и тот же опе­ ранд, они выполняются в порядке , в каком они следуют в операторе . Для большинства операций определяется порядок следования слева направо . ( О пе рация = является в этом случае ис клю че ние м . ) По этой причине в опе раторе butter = 2 5 . 0 + 6 0 . 0 * n / SCALE ; устанавливается следую щий порядок выполне ния операций: 60 . 0 * n Первая операция * или / в выражении (исходя из предположения, что n равно 6, так что 6 О . О * n дает в результате 3 6 О . О ) . 3 6 0 . О / SCALE Затем идет вторая операция * или / в выражении. 25 . 0 + 180 В завершение (поскольку SCALE равно 2 . О ) первая операция + или - в выражении дает в результате 2 О 5 . О . Многие предпочитают представлять порядок вычислений в виде диаграммы, полу­ чившей название дерева выраже ния . На рис . 5.3 представлен пример такой диаграм­ мы. Диаграмма показывает, как исходное выраже ние посте пенно с водится к одному значению . SCALE=2 ; n=6 ; butter=2 5 . 0 + 6 0 . 0 * n / SCALE ; 25 . 0 1 SCALE * 60 . 0 n � " �, � � 25 . о 360 . 0 180 � 205. 0 Ри с. 5 . 3 . Деревъя въtраже'Ний, показъtвающие опершции, опера'Ндъ� и порядок 6ЪlnОЛ'Не'НUЯ 6'Ьt'ЧUСЛе'НUU А что если вы захотите , чтобы операция сложения выполнялась до операции деле­ ния? Тогда вы должны поступить так, как это сделано в следую ще й строке: flour = ( 2 5 . 0 + 6 0 . 0 * n ) / SCALE ; То, что заклю чено в круглые скобки, выполняется первым . Внутри круглых скоб ок де йствуют обычные правила. Наприме р, сначала выполняется умноже ние , а затем 174 Гл ава 5 сложе ние . Эти операции позволяют вычислить выражение в круглых скобках. Теперь ре зультат этих вычислений делится на SCALE . Использованные нами на текущий момент опе рации с ведены в табл. 5 . 1 . ТабJJица 5.1 . Операции в порядке уменьшения приоритетов Опер ации Ассоциативно сть () Слева направо + - (унарные ) Справа налево * / Слева направо + - (бинарные ) Слева направо Справа налево Обратите внимание , что две формы ис пользования знака "минус" имеют различ­ ные приоритеты, это же справедливо и для знака " плю с " . В столбце ассоциативности показано, как операции связаны со с воими операндами. Наприме р, унарная опера­ ция - ассоциируется с числовым значением, стоящим справа , в то время как при деле­ нии опе ранд слева делится на опе ранд с права . Приоритеты и порядок в ы ч ислен ия Приоритеты операций являются жизненно важным правилом для определения порядка вычисле ния выраже ния , в то же время нет необходимости в определении всей последовательности вычислений. Язык С предоставляет возможность выбора при реализации. Рас смотрим следую щий опе ратор: у = 6 * 12 + 5 * 2 0 ; Приоритет устанавливает порядок вычислений в тех случаях, когда две операции совме стно используют один и тот же операнд. Например , 1 2 является операндом как для опе рации * , так и для операции + , а приоритеты опе раций говорят, что первым должно выполняться умножение . Аналогично , приоритет показывает, что 5 использу­ ется в опе рации умножения, но не в операции сложе ния . Короче говоря , операции умножения 6 * 1 2 и 5 * 2 0 выполняются до того, как начнут выполняться операция сложе ния . При этом приоритет не устанавливает , какая из двух операций умножения выполняется первой. Язык С оставляет выбор за реализацие й, поскольку один выбор может оказаться б олее эффективным для одного типа аппаратных средств, а другой выбор обеспе чивает б олее высокую производительность на другом оборудовании. В любом случае , рассматриваемое выражение сводится к вычисле нию 72 + 10 О, следо­ вательно, выбор не отражается на конечном ре зультате в условиях этого конкретного примера . Однако вы можете возразить: "Умноже ние выполняется слева направо . Не означает ли это , что самое левое умноже ние ис полняется первым?" (Возможно , вы этого и не с кажете, но кто-нибудь когда-нибудь обязательно это скажет.) Правило ас­ социативности применяется к операциям , которые с о вместно используют конкрет­ ный операнд . Наприме р, в выраже нии 1 2 / 3 * 2 опе рации / и * , обладаю щие одним и тем же приоритетом, с о вместно ис пользуют операнд 3 . Операции, вы ражения и оператор ы 175 В с илу этого обстоятельства применяется правило " сле ва направо", и выражение сводится к вычислению произведения 4 * 2 , что в результате дает 8. (О бход с права налево дает 12 / 6 или 2. В этом случае выбор имеет существенное значение . ) В пре­ дыдущем приме ре две опе рации * не претендуют на общий операнд, в с вязи с чем пра­ вило "слева направо" не применяется . Проверка приоритета операций Попытаемся применить эти правила в условиях более сложного примера (листинг 5.7). Листинг 5.7. Программа ru1e s _ с / * r u l e s . c - - про в ерка , как р а бо тают приоритеты * / #incl ude <s tdio . h > int main ( vo i d ) { int top , s cor e ; top = s co r e - (2 + 5) * 6 + ( 4 + 3 * (2 + 3) ) ; printf ( " top = % d \ n " , top ) ; r e turn О ; Какое значение распе чатает эта программа? С начала вычислите его вручную , а за­ тем выполните программу или прочитайте приведенное ниже пояс не ние , дабы про­ верить свой ответ. Прежде вс его , наивысший приоритет присваивается вычисле нию в круглых скоб­ ках. Какое выражение в с коб ках, - (2 + 5) * 6 или ( 4 + 3 * (2 + 3 ) ) вычисляется первым , зависит , как мы только что установили, от конкретной ре ализации. Любой выбор в этом примере приводит к одному и тому же результату, так что пусть выраже­ ние слева вычисляется первым. Более высокий приоритет выражения в с кобках озна­ чает, что в подвыражении - ( 2 + 5 ) * 6 вы с начала вычисляете сумму ( 2 + 5 ) , кото­ рая равна 7 . Далее вы применяете к 7 унарную опе рацию минус , получая при этом - 7 . Теперь данное выражение принимает вид top = s co r e = - 7 * 6 + ( 4 + 3 * ( 2 + 3 ) ) На следую щем шаге следует вычислить 2 + 3 . Выраже ние принимает вид top = s co r e = - 7 * 6 + ( 4 + 3 * 5 ) Поскольку приоритет операции * выш е , че м у + , выраже ние сводится к top = s co r e = - 7 * 6 + ( 4 + 1 5 ) а зате м к top = s co r e = - 7 * 6 + 1 9 Умножим - 7 на 6 и получим следую ще е выражение : top = s co r e = - 4 2 + 1 9 В ыполнив сложение , получим top = s co r e = - 2 3 Теперь переменной s co r e прис ваивается значение - 2 3 , и, наконец, top получает значение - 2 3 . Вспомните , что операция = выполняется справа налево . 176 Гл ава 5 Н екоторые допол н ител ьн ые операци и В языке С имеется около 40 операций, и не которые из них употре бляются гораздо чаще, чем другие. Т е , с которыми мы уже ознакомились, относятся к наиболее часто используемым операциям , однако к этому спис ку можно добавить еще четыре очень полезных операции. Операция s i z eof и ти п s i z e t С операцией s i zeo f мы уже имели дело в главе 3 . Вс помните , что операция s i zeo f возвращает размер ее операнда в байтах. (Вс помните также , что байт в языке С по оп­ ределе нию является размерностью типа cha r . В прошлом в качестве такой размерно­ сти чаще все го использовалась порция в 8 бит, в то же вре мя для представления не ко­ торых наборов символов требовалось более одного байта.) О пе рандом может быть конкретный объект данных, такой как имя переменной, либ о это может быть тип. Ес­ ли это тип, например, float, операнд долже н быть закл ю че н в круглые скобки. В примере, представленном в листинге 5.8, показаны обе формы опе рандов. Листинг 5.8. Программа sizeof . с 1 1 s i z eo f . c -- исполь з о в ание опер а ции s i zeo f 1 1 Применение модификатор а % z стандарта С 9 9 1 1 % u или % l u , е с ли в ам н е достаточно % z d #incl ude <s tdio . h > int main ( vo i d ) { int n = О ; s i z e_t i nt s i z e ; попытайте с ь восполь зов ать ся ints i z e = s i z e o f ( i n t ) ; printf ( "n = %d, n состоит из % zd байтов ; все значения int имеют % zd байтов . \n " , n , s i zeo f n , i nts i z e ) ; r e turn О ; Язык С утверждает, что опе рация s i z eo f возвращает значение типа s i z e t. Это тип целого числа без знака, но не абс олютно новый тип. В место этого, как и перено· симые типы ( in tЗ 2 _t и прочие ) , он определяется в терминах стандартных типов. В С имеется механизм typede f (подробно обсуждается в главе 1 4 ) , который позволяет создавать альтернативные име на для существую щих типов. Наприме р, typ ede f douЫ e r e al ; делает r e a l еще одним име не м для do uЫ e . Теперь вы можете объявить пе реме нную типа r e a l : r e al deal ; 1 1 испол ь з у е тся typ ede f Компилятор , который сталкивается со словом r e a l , вс поминает, что опе ратор typede f сделал r e al альтернативным именем для do uЫ e и с о здает de al как перемен­ ную типа douЫ e . Аналогично , система заголовочных файлов языка С может исполь· зовать typ ede f, чтобы сделать s i z e t синонимом uns igned int в одной с истеме и Операции, вы ражения и оператор ы 1 77 unsigned l ong в другой. Таким образом, когда вы используете тип s i z e_t, компиля­ тор выполняет подстановку стандартного типа , который раб отает в вашей с истеме. Стандарт С99 идет на шаг дальше и представляет % zd в каче стве спецификатора функции printf ( ) для вывода значения s i z e _t . Если в вашей с истеме % zd не реализо­ ван, можно попытаться использовать вме сто него % и или % l u . Операция делен ия по модулю: % О пе рация деления по модулю применяется в целочисленной арифметике . Ее ре­ зультатом является остаток от деления целого числа , стояще го слева от знака опера­ ции, на число, расположенное справа от него . Наприме р, 1 3 % 5 (читается как " 1 3 по модулю 5" ) дает в ре зультате 3 , поскольку 5 с одержится в 1 3 дважды с остатком, рав­ ным 3 . Не пытайтес ь выполнять эту операцию над числами с плавающей запятой. О на просто не раб отает. На первый взгляд эта опе рация может кому-то показаться экзотическим инстру­ ментальным с редством, предназначе нным только для математиков, но, по сути дела , это очень удобная и поле зная опе рация . Обычно она ис пользуется , чтобы помочь управлять ходом выполнения программы. Предположим, например, вы работаете над программой подготовки счетов, которая начисляет дополнительную плату каждые третий ме сяц. Для этого достаточно разделить номер месяца по модулю 3 (то есть, month % 3 ) и проверить, не равен ли результат О. Если равен, то программа вклю чает дополнительную плату. После того , как в главе 7 вы изучите , как работает опе ратор i f, вы оце ните все преимущества этой операции. В листинге 5 .9 предлагается еще один пример применения операции % . В нем так­ же показан еще один способ использования цикла whil е . листинг 5.9. Программа min_ sec _ с 1 1 mi n_s e c . c - - п ер еводит се кунды в мину ты и с екунды #incl ude <s tdio . h > #de fi ne SEC PER MIN 6 0 / / число секунд в минуты int main ( vo i d ) { int s e c , min , l e ft ; printf ( " Пepeвoд секунд в минуты и секунды ! \ n " ) ; printf ( " Bв eди т e колич ество секунд ( <= О для выхо д а ) : \ n " ) ; 1 1 читать колич ество секунд s c an f ( " % d" , & s e c ) ,- whi l e ( s e c > О ) { min = s e c / SEC PER_MIN ; / / усеч е нное колич е ство минут l e ft = s e c % SEC PER_MI N ; // число колич е с тво в о с т атке print f ( " % d секунд - это % d минут %d секунд . \ n " , s e c , min , l e ft ) ; print f ( " B в e дитe следующе е з н ач ение ( < = О для в ыхода) : \ n " ) ; s can f ( " % d" , & s e c ) ; printf ( " Cдeлaнo ! \ n " ) ; r e turn О ; 178 Гл ава 5 В от как выглядит приме р выходных данных: Перевод секунд в минуты и секунды ! Вв едите колич е ство с екунд ( < = О для выхода) : 15 4 1 5 4 секунд - э то 2 минут 3 4 секунд . Вв едите следующе е з н ач ени е ( < = О для в ыхода) 567 5 6 7 секунд - э то 9 минут 2 7 секунд . Вв едите следующе е з н ач ени е ( < = О для в ыхода) о Сделано ! В коде из листинга 5.2 используется счетчик для управления циклом whi l e . Как только счетчик превысит заданное значе ние , цикл завершается . Однако код в листин· ге 5.9 использует функцию s can f ( ) , чтобы назначить новые значения пере менной s e c. Цикл продолжается до тех пор , пока это значение положительно. Когда пользо· ватель вводит ноль или отрицательное значе ние , цикл завершается . Важной особен· ностью программы в обоих случаях является то , что каждая итерация цикла обновляет значение проверяе мой пе ременной. Что произойдет, е сли будет введено отрицательное значение? До того, как стан· да рт С99 установил для целочисленного деле ния правило "ус ечения в направлении ну· ля" , существовало несколько возможностей. В случае ре ализации этого правила вы получаете отрицательное значе ние при деле нии по модулю , если пе рвых опе ранд от· рицательный, во всех остальных случаях вы получаете положительное значе ние : 1 1 1 5 равно 2 и 1 1 % 5 равно 1 1 1 1 - 5 равно 2 и 1 1 % - 2 равно 1 - 1 1 1 - 5 равно 2 и - 1 1 % - 5 равно - 1 - 1 1 1 5 равно - 2 и - 1 1 % 5 равно - 1 Если поведение ваше й с истемы будет другим, значит, она не с оответствует требо· ваниям стандарта С 9 9 . В любом случае , стандарт фактиче ски утверждает, что если а и Ь являются целочисленными, то вы можете вычислить а % Ь путем вычитания ( а /Ь ) *Ь из а . Например , таким способом вы можете вычислить значение - 1 1 % 5 : - 1 1 - ( - 1 1/ 5 ) * 5 = - 1 1 - ( -2 ) * 5 = - 1 1 - ( - 1 0 ) = - 1 Опера ци и и н кремента и декремента: ++ и - О пе рация и'Ккреме'Кmа решает простую задачу; она увеличивает (икреме нтирует ) значение с воего операнда на 1 . Существует две разновидности этой операции. В пер­ вом случае символы ++ идут не посредственно перед переменной, это так называе мая префикс'Кая форма . Во втором случае с имволы ++ следуют сразу за переменной; это постфикс'Кая форма . Эти две формы отличаются друг от друга по моменту выполнения инкре мента . С начала мы рассмотрим, в чем эти формы совпадают, а затем обратимся к различиям . Короткий пример , представленный в листинге 5 . 1 0 , демонстрирует, как работает операция инкре мента . Операции, вы ражения и оператор ы 1 79 Листинг 5.1 0. Проrрамма add_one . с / * add_one . c - - инкр еме нт : префиксный и постфиксный * / #incl ude <s tdio . h > int main ( vo i d ) { int ultr a = О , s uper О; whi l e ( s uper < 5 ) { s up er + + ; ++ultr a ; print f ( " s up er % d \ n " , s up e r , ult r a ) ; % d , ult r a r e turn О ; В результате выполне ния программы add one . с получе ны следую щие выходные данны е : super 1 1 , ult r a super 2 2 , ult r a super 3 3 , ult r a super 4 , ult r a 4 super 5 5 , ult r a Программа дважды просчитала до пяти в одно и то же время. В ы могли бы полу­ чить тот же результат, заме нив две операции инкремента следую щими опе раторами прис ваивания : super = s uper + 1 ; ul tr a = ultr a + 1 ; Это очень простые опе раторы. Зачем с оздавать е ще один такой же оператор , не говоря уже о двух, пусть даже в боле е компактном виде? Одна из причин заклю чается в том, что компактная форма позволяет создавать программы удобочитаемыми и более понятными. Такие опе раторы придают программе изящество и делают ее элегантной, а с такой программой вс егда приятно иметь дело . Например, можно представить часть программы s ho e s 2 . с (листинг 5.2) в следую щем виде: shoe = 3 . 0 ; whi l e ( s ho e < 1 8 . 5 ) { foot = SCALE * s i z e + ADJUST ; print f ( " % 1 0 . l f % 2 0 . 2 f дюймов \ n " , s ho e , foot) ; + + s ho e ; Тем не менее, и в этом случае вы не д о конца вос пользовались всеми пре имущест­ вами опе рации инкремента. Вы можете сделать этот фрагмент программы еще более компактным: shoe = 2 . 0 ; whi l e ( + + s ho e < 1 8 . 5 ) { foot = SCALE * s hoe + ADJUST ; print f ( " % 1 0 . l f % 2 0 . 2 f дюймов \ n " , s ho e , foot) ; 1 80 Гл ава 5 В данном случае проце с с инкремента и сравне ния цикла whil e объединены в од· ном выражении. Конструкции этого типа так часто используются в С , что заслужива· ют б олее пристального внимания. Во-пе рвых, как раб отает такая конструкция? Доста· точно просто . Значение пе ременной shoe каждый раз увеличивается на 1 , а затем сравнивается с 1 8 . 5. Если ее величина меньше 1 8 . 5, то операторы, заклю ченные в фигурные с кобки, выполняются один раз . Затем shoe опять увеличивается на 1 , и цикл продолжается до тех пор, пока значение s ho e не станет достаточно б ольшим. Потре· буется уменьшить начальное значе ние s ho e с 3 . О до 2 . О, чтобы скомпенсировать ин· кремент переменной s ho e , сделанный перед те м, как пе рвый раз б ыло вычисле но зна· че ние пе ременной foot (см. рис . 5.4) . Цикл whil е shoe = 2 О ; · --------+- while {++shoe < 1 8 . 5 ) �------+- foot=SCALE*shoe + ADJUST ; @ П роверить условие (true - продолжить цикл) ® Выполнить эти операторы printf { " - - - - - - " , shoe , foot) ; --------<f- Р и с. 5.4. ф И нкрементировать значение shoe до 3 @ Вернуться в начало цикла Одпа итсршция -цикла В о-вторых, в чем преимущества такого подхода? Прежде вс его , в компактности программы . Но что важне е - он объединяет в одном месте два проце с с а , которые управляют выполне нием цикла. Пе рвичный проце с с - это прове рка конца цикла: нужно ли продолжать выполнение цикла? В рас сматриваемом случае проверка выяс· няет , меньше ли значе ние переменной s i z e , чем 1 8 . 5. Вторичный процесс меняет значение объе кта проверки, в данном случае разме р обуви увеличивается на единицу. Предположим, что вы не предусмотрели изменение размера обуви. Тогда значение переменной shoe всегда будет меньше 1 8 . 5, а цикл никогда не завершится. Угодивший в бесконечный цикл компьютер раз за разом будет выводить одну и ту же строку. В конце концов, вы потеряете интерес к выходным данным и будете вынуждены каким-нибудь способом прервать выполнение программы. То обстоятельство, что и проверка условия цикла , и изменение параметра цикла находятся в одном, а не в разных местах, лишний раз напоминает о том, что необходимо изменить значение параметра цикла. Недостаток сочетания двух операций в одном выражении проявляется в том , что восприятие программного кода в этом случае затрудняется , а вероятность возникно· вения программных ошибок возрастает. Еще одно достоинство опе рации инкремента состоит в том, что при ее использо· вании не сколько возрастает эффективность кода в машинном языке , поскольку она подобна фактичес ким командам машинного языка . Однако, по мере того , как постав· щики программного об еспечения создают все более эффективные компиляторы язы· ка С , это преимущество постепе нно сходит на нет. Сообразительный компилятор мо­ жет рас познавать, что операцию х = х + 1 следует трактовать как ++х. Операции, вы ражения и оператор ы 1 81 И , наконец, эти операции име ют дополнительную особенность, которая может оказаться полезной в некоторых деликатных с итуациях. Чтобы узнать, чго это за о с о­ бенность, попытайтес ь выполнить программу, представле нную в листинге 5 . 1 1 . Листинг 5.1 1 . Программа postyre . c / * po s t_pr e . c - - постфиксная и пр е фиксная формы * / #incl ude <s tdio . h > int main ( vo i d ) { int а = 1 , Ь = 1 ; int aplu s , plusb ; ap lus = а++ ; / * постфиксная форма * / plusb = ++Ь ; / * пр ефиксная форма * / printf ( " a ap lus Ь plusb \ n " ) ; printf ( " % ld % 5 d % 5d % 5d\ n " , а , aplus , Ь , p l u sb ) ; r e turn О ; Если вы и ваш компилятор все выполнили правильно, то должны получить сле­ дую щий ре зультат : а 2 apl u s 1 Ь 2 plusb 2 Как и предполагалос ь, значения пе ременных а и Ь увеличилис ь на 1 . В то же время, значение пе ременной apl u s - это значение а, которое эта переменная имела до того, как произошло изменение а, а значение пе ременной plusb - это значение Ь после того, как произошло изме нение Ь. Именно в этом и состоит различие между префиксной и постфикс ной формами опе рации инкремента ( рис . 5 .5 ) . ap lus plusb а++ ; / * постфиксная форма : з н ач ение а меня е тся после тог о , как оно и споль з о в ано * / ++Ь ; /* пр е фиксная форма : знач ение а мен я е тся до тог о , как оно и споль з о в ано * / r--------------- 1 --------------r--------------- 1 --------------Префиксная форма q = 2 * ++а ; Сначала а увеличивается на 1 , затем умножается н а 2, после чего результат присваивается переменной q П остфиксная форма q = 2 * а++ ; Сначала а умножается на 2, результат присваивается переменной q, после чего а увеличивается на 1 1 82 Гл ава 5 В тех случаях, когда одна из этих операций инкреме нта использовалась сама по се­ бе, как, например , в одиночном опе раторе ego ++ ; , не имеет значения , какой е е форме вы отдали предпочтение . Однако выбор имеет смысл , когда сама операция и ее опе­ ранд являются частью некоторого выражения, например, такого , какое вы только что видели в операторах присваивания . В с итуациях подобного рода вы должны иметь четкое представление о том , какой ре зультат вы хотите получить. В каче стве примера напомним, что мы намеревались ис пользовать следую щую конструкцию : whi l e ( + + s ho e < 1 8 . 5 ) Такая проверка условия конца цикла позволяет получить таблицу до разме ра 1 8 . Если в ы воспользуетесь операцие й s h o e + + вме сто + + s ho e , размер таблицы возрастет до 1 9 , поскольку значение shoe должно быть увеличе но после сравнения , но никак не до сравне ния . Разумеется , вы все гда можете возвратиться к менее элегантной форме s h o e = s ho e + 1 ; но тогда никто не поверит, что вы являетес ь истинным приверже нцем языка С . Читая эту книгу, вы должны обратить ос обое внимание на примеры операции ин­ креме нта. Спро с ите с ебя , использовали ли вы где-нибудь префикс ную и постфиксную формы попеременно или обстоятельства ставили вас пе ред конкретным выбором . Возможно , более разумной является политика вообще избегать кода, в котором имеет смысл, какую форму вы изберете , пре фиксную или постфиксную . Например, вместо Ь = ++i ; // по лучим друг о е знач ение Ь , е сли испо л ь зовать i++ использовать ++i ; ь = i; / / строка 1 / / Ь получи т то же значение , как е сли бы в строке 1 было i++ С другой стороны , иногда ради шутки можно позволить себе немного безрас судст­ ва , так что данная книга не все гда следует этому благоразумному с о вету. д екремент: - Для каждой формы опе рации инкремента существует соответствую щая операция де креме нта . При этом вместо ++ применяется обозначе ние - - . - - count ; count- - ; / * пр ефик сная форма опер ации декремента * / / * постфиксная форма опер а ции декр еме нта * / Листинг 5 . 1 2 служит иллю страцией того , что иногда компьюте р может быть безна­ дежным лириком . листинг 5.1 2. Программа bottles . с #incl ude <s tdio . h > #de fi ne МАХ 1 0 0 int main ( vo i d ) { int count = МАХ + 1 ; whi l e ( - - count > 0 ) print f ( " % d бутылок мин ер альной воды н а полке , " " % d бутылок мин ер альной воды ! \ n " , count , count ) ; Операции, вы ражения и оператор ы 1 83 рrint f ( " В о з ьмите одну из них и пус тите по кру г у , \ n " ) ; print f ( " % d бутылок минерал ь н ой воды ! \ n\ n " , co unt 1) ; - r e turn О ; В ыходные данные начинаются со следую щих фраз: 1 0 0 бутылок минераль ной в оды на полке , 1 0 0 бутылок минерал ь н ой воды ! Во з ьмите одну из них и пу стите по кру г у , 9 9 бутылок минерал ь н ой воды ! 9 9 бутылок минерал ь н ой воды на полке , 9 9 бутылок минер аль ной воды ! Во з ьмите одну из них и пу стите по кру г у , 9 8 бутылок минераль ной воды ! В с е это продолжается не которое время и закачивается следую щим образом: 1 бутылок минераль ной воды на полке , 1 бутылок минер ал ь ной в оды ! Во з ьмите одну из них и пу стите по кру г у , О бутылок минераль ной воды ! Как вы можете убедиться с ами, у нашего бе знаде жного лирика не все в порядке с грамматикой, но это можно исправить, е сли воспользоваться уловными опе раторами, которые рас сматриваются в главе 7. Кстати, операция > означает "больше чем" . Как и операция < (" меньше че м" ) , она является операцией отношения . Более подроб но опе рации отношений расс матрива­ ются в главе 6 . Приоритеты опера ци й О пе рации инкре мента и декремента име ют очень высокий приоритет; только скобки обладают более высоким уровнем приоритета . В силу этого , х*у++ означает ( х ) * ( у++ ) , но никак не ( х* у ) + + , что весьма кстати, ибо последне е выражение не име­ ет смысла . О перации инкре мента и декреме нта применяются только к перемсн'Н:ым (или, в обще м случае, к модифицируемым 1-значениям) , а произведение х*у с амо по себе не является переме нной, хотя с омножители таковыми являются. Не путайте приоритеты этих двух операций с порядком вычисления. Предположим, мы име ем следую щую последовательность операторов: у = 2; n = 3; ne xtnum = (у + n++ ) * 6 ; Какое значение получит переме нная nextnum? Подстановка значений дает сле­ дую щее ne xtnum = (2 + 3 ) * 6 = 5 * 6 = 3 0 Значе ние n инкреме нтируется и получает значение 4 только после того , как эта переме нная будет использована в выражении. Приоритет операции говорит о том, что операция ++ приме няется только к n, но не к у + n. Кроме того , он указывает, когда значение n используется для вычисле ния выражения , но природа операции прираще­ ния определяет момент измене ния значе ния n. 1 84 Гл ава 5 Когда n++ присугствует в выражении как его часть, можно считать, что она означа­ ет " использовать n, а затем увеличить его значе ние на единицу" . С другой стороны, ++n означает "увеличить значение n на единицу, а затем е го использовать" . Не будьте сл и ш ком самоуверен н ы м и В ы можете попасть в вес ьма неловкое положение , если попытаете с ь применять операцию инкремента там, где надо и где не надо . Например , вы, возможно , подумае­ те , что можно улучшить программу s q u ar e s . с (листинг 5.4) для вывода на пе чать це­ лых чисел, заме нив цикл whil e в указанной программе следую щим циклом: whi l e ( n um < 2 1 ) { print f ( " % 1 0 d % 1 0 d \ n " , num , num* num++ ) ; } На первый взгляд это вполне оправдано. Вы печатаете число num, умножаете его само на себя, чтобы получить квадрат этого числа, а затем увеличиваете значе ние num на 1 . По суги, эта программа может даже работать в не которых систе мах, однако не на всех. Пробле ма заклю чается в том, что когда функция printf ( ) готова принять оче­ редные значе ния для последую щего вывода на печать, она может вычислить сначала последний аргуме нт и увеличигь значение num на единицу, пре жде чем переходить к следую щему аргуме нту. Поэтому, вместо того , чтобы печатать 5 25 она может напе чатать 6 25 О на даже может работать справа нале во , используя 5 в каче стве крайне го правого num и 6 в каче стве следую щих двух, в результате чего получатся следую щие выходные данны е : 6 3о Компилятор языка С может выбирать с ам, какой аргумент функции должен быть вычисле н пе рвым. Такая свобода выбора повышает производительность компилято­ ра , но в то же время она может стать причиной проблем , если операция инкреме нта применяется к аргументу функции. Другим возможным источником неприятностей может стать оператор следую щего вида : an s = num/ 2 + 5 * ( 1 + num++ ) ; И с нова пробле ма состоиг в том , что компилятор может выполнять де йствия не в том порядке , в каком вы предполагаете . В ы , возможно , подумаете , что он сначала найдет выраже ние num/ 2 и только потом пе рейдет к вычислению другой части выра­ же ния , однако он вполне может первым вычислить последний эле мент, увеличить значение num, и использовать это новое значение для вычисления num/ 2 . На этот счет не существует никаких гарантий. Еще одним возможным источником проблем может стать и такая конструкция : n у 3; n++ + n++ ; Операции, вы ражения и оператор ы 1 85 Разумеется , после выполнения этого опе ратора значение пере менной n увеличится на 2 , зато значе ние у определяется неоднозначно . Компилятор может ис пользовать старое значение n дважды при вычисле нии значе ния у, а зате м дважды увеличить зна­ че ние n на единицу. При этом у принимает значение 6 , а n - значе ние 5 , либо он может ис пользовать старое значе ние один раз , увеличить значе ние n один раз , ис пользовать это значение для второго n в выражении, а затем инкре ментировать n второй раз . В этом случае у принимает значение 7, а n - значение 5 . Возможе н и тот, и другой вариант. А е сли на­ зывать вещи своими именами, то результат этих вычислений не определен, и, следо­ вательно, это означает, что стандарт языка С не спос об ен определить, каким долже н быть результат. Вы можете легко избежать описанных выше проблем: • • Не применяйте опе рации инкре мента и декремента к переменной, которая яв­ ляется частью боле е одного аргумента функции. Не приме няйте операции инкре мента и декремента к переменной, которая по­ является в выражении более одного раза. С другой стороны, в языке С имеются средства , которые обе спечивают правильное выполнение операций инкремента и декреме нта . Мы вернемся к этой те ме, когда бу­ де м об суждать точки оце нки в разделе "Побочные эффе кты и точки оценки" далее в этой главе . В ы ражен ия и операторы Мы использовали термины " в'Ыражеииi' и " оператор" на протяжении нескольких первых глав , а теперь настало вре мя глубже обсудить смысл этих терминов. О перато­ ры образуют основные эле менты программы на С , а большая часть опе раторов состо­ ит из выражений. Отсюда следует , что в первую очередь нам нужно подробно изучить выраже ния . Вы ражения В'Ыражеиие представляет собой некоторую комбинацию опе раций и операндов. (Вспомните , что операнд е сть то , над че м выполняется операция . ) Простейшим вы­ ражение м является отдельный операнд, он может служить отправной точкой для по­ строения более сложных выражений. Ниже представлено не сколько примеров выра­ же ний: 4 -6 4+21 а * (Ь + c / d ) / 2 0 5*2 q х ++q % 3 q > 3 Ле гко заметить, что операндами могут б ыть константы , пе ременные и их сочета­ ния . Некоторые выражения представляют с об ой комбинации выражений ме ньших 1 86 Гл ава 5 размеров, которые мы назовем подвъ1раже11,иями. Например , в четве ртом примере c/d выступает в качестве подвыраже ния . Каждое выражение имеет значение В ажное с войство языка С состоит в том, что каждое выражение в этом языке имеет значение . Чтобы найти это значение , нужно выполнить операции в порядке, опреде­ ленном приоритетами выражений. Значения не скольких первых приведе нных выше выраже ний оче видны, однако что можно сказать о выражениях со знаком =? Эти вы· ражения просто принимают те же значения , что и пе ременные слева от знака = . По­ этому выражение q=5 * 2 как единое целое получает значе ние 1 0 . А что можно сказать о выраже нии q > З ? Такие выражения отношения получают значение 1 , е сли выраже­ ние истинно, и О , если оно ложно . Рассмотрим нес колько выражений и их значе ния : Выраж ение Зиач ешt е -4 + 6 2 с = 3 + 8 11 5 > 3 1 6 + (с 3 + 8) 17 Последне е выраже ние н е может н е показаться странным ! Однако оно вполне до­ пустимо в С (тем не менее , ис пользовать подобные выражения не ре комендуется ) , и представляет собой сумму двух выражений, каждое из которых имеет с обственное значение . операторы Операторъ; служат основными строительными блоками программы . Программа это последовательность опе раторов с необходимыми знаками пунктуации. О пе ратор есть законченная команда компьютеру. В языке С операторы распознаются по точке с запятой в конце . В силу этого обстоятельства legs = 4 это всего лишь выраже ние (которое может быть частью другого выраже ния ) , в то же вре мя legs = 4 ; является опе ратором. Какой будет команда в завершенном виде ? Во-первых, С трактует любое выраже­ ние как оператор, е сли только оно оканчивается точкой с запятой. По этой причине С не отвергает строки, подобные показанным ниже : 8; 3 + 4; Однако эти операторы не производят никаких де йствий в программе и, по сути де­ ла, не могут расс матриваться как опе раторы , имею щие хоть какой-нибудь смысл. Операции, вы ражения и оператор ы 1 87 Обычно опе раторы меняют значения переменных и вызывают функции: х = 25; ++ х ; у = s qrt ( x ) ; Хотя опе ратор (Иll и , по меньшей мере, оператор, име ющий смыл) - это заверше н­ ная команда, не вс е завершенные команды являются опе раторами. Рассмотрим сле­ дую щий опе ратор: х = 6 + (у = 5 ) ; В него входит подвыраже ние у = 5 , представляющее завершенную команду, но это только часть оператора. Поскольку заве ршенная команда не обязательно есть опера­ тор, для распознавания команд , которые на самом деле представляют с об ой операто­ ры , необходима точка с запятой. До сих пор мы сталкивалис ь с четырьмя видами опе раторов. В листинге 5 . 1 3 пред­ ставлен короткий пример , в котором используются вс е эти четыре вида операторов. листинг 5.1 3. Проrрамма addeпup . с / * addemup . c - - ч е тыр е вида операторов * / #incl ude <s tdio . h > / * находит сумму п ервых 2 0 на тур аль ных чисел * / int main ( vo i d ) { */ / * оператор о бъявл ения int count , s um ; / * оператор присв аив ания co unt = О ; / * то же само е s um = О ; / * оператор цикла whil e whi l e ( count++ < 2 0 ) / * операторы s um = s um + count ; printf ( " sum = %d\n" , s um) ; / * опер атор в ызова функции */ */ */ */ */ r e turn О ; Расс мотрим листинг 5 . 1 3 боле е внимательно . На данный момент вы должны быть достаточно хорошо знакомы с опе ратором объявления . Тем не менее , следует вспом­ нить, что он вводит в употребле ние име на и типы пе ременных, а также выделяет под них память. О братите внимание , что оператор объявле ния не является опе ратором выраже ния . То есть, если вы удалите из объявле ния точку с запятой, вы получите не­ что такое , что не является выражением и не имеет значения: int port /* э то не выр ажение , оно не име е т з н ач е ния * / Оператор присваивапия - это "рабочая лошадка" многих программ; он присваивает значения переменной. Этот оператор состоит из имени переменной, за которым сле­ дует операция прис ваивания (=) , далее идет выражение , сопровождаемое точкой с за­ пятой. Об ратите внимание , что оператор whi l e в примере с одержит в себе опе ратор прис ваивания . О пе ратор присваивания представляет собой приме р оператора выра­ же ния . Оператор въtзова фуnк'Ции заставляет функцию делать то , для че го она предназначе­ на . В рас сматриваемом примере функция print f ( ) вызывается для того , чтобы рас пе­ чатать не которые ре зультаты . 1 88 Гл ава 5 В опе раторе whil e имеются три различные части, как показано на рис . 5 . 6 . Первой частью является клю че вой слово whi l e . Далее , в круглые скобки заклю чено прове· ряемое условие . В заверше ние имеется оператор, который выполняется , если условие истинно. В цикле присутствует только один оператор. Это может быть простой опе· ратор, как это имеет ме сто в рассматриваемом приме ре , в таком случае фигурные скобки не нужны для его обозначе ния , либо это может быть составной оператор , как в не которых приме рах, рассмотренных выш е , в которых фигурные скобки были нужны . О составных операторах речь пойдет нес колько позже. 1 while false Переход на новый оператор Возврат в начало цикла true printf ( " Будь моим В алентином ! \ n " ) ; Р и с. 5.6. Структура простого t,_{uкла whi l e О пе ратор whil e принадле жит к кла с су операторов, который иногда называют структурированными опе раторами, поскольку они обладают боле е сложной структу· рой, чем простой опе ратор прис ваивания . В последую щих главах вы столкнете сь с множеством других структурированных опе раторов. Побочные эффекты и точки оценки В веде м не сколько новых терминов языка С . ПобочиЪtй эффект представляет с обой модификацию некоторого объекта данных или файла . Наприме р, поб очный эффект оператора s t at e s = 5 0 ; заклю чается в том , что он присваивает пе ременной s tates значение 5 0 . Но поче му " побочный эффе кт"? Это , с корее, является основным назна· че ние м этого опе ратора ! Тем не менее , что кас ается языка С, то основная цель опера· торов состоит в вычислении выражений. Введите в С выражение 4 + 6 , и С вычислит его значение , равное 1 0 . Введите выражение s tates = 5 0 , и С вычислит его значе· ние , которое равно 50. В ычисление этого выраже ния имеет своим побочным эффек· том то , что переме нная s tates изменяет свое пре жнее значение на 5 0 . О пе рации ин· креме нта и декремента , как и операция присваивания , име ют с вои побочные эффек· ты и ис пользуются , прежде все го , из·за наличия этого побочного эффекта . Точка ot,_{euкu это точка в ходе выполнения программы , в которой производится вычисле ние всех побочных эффектов, прежде чем пе реходить к следую щему дейст· вию . В языке С точку оценки обозначает точка с запятой в опе раторах. Это означает, что все изменения , вызванные опе рациями присваивания , инкремента и де креме нта в не котором операторе должны быть выполне ны до того , как программа пе рейдет к следую щему оператору. Не которые опе рации, которые будут рассматриваться в еле· дую щих главах, также имеют точки оце нки. Кроме того , коне ц любого полного опера· тора также является точкой оце нки. - Операции, вы ражения и оператор ы 1 89 Что такое полное выражение? Полпое въ�ражепие - это выражение , которое не явля­ ется подвыражением более крупного выражения . Примерами полных выраже ний мо­ гут служить выраже ния опе ратора присваивания, а также выраже ния , используемые в условии проверки цикла whi l e . Точки оценки позволяют выяснить, когда происходит инкре ментирование в постфиксной форме . Рассмотрим в качестве примера следую щий код: whi l e ( g u e s t s + + < 1 0 ) printf ( " % d \ n " , gue s ts ) ; Иногда начинаю щие программисты полагают, что фраза " использовать значение , а затем инкреме нтировать его" означает в данном контексте увеличение значения пе­ ременной после того , как она будет использована в операторе print f ( ) . Однако вы­ ражение g u e s t s + + < 1 0 представляет собой полное выраже ние , так как оно является прове ряемым условие м цикла whi l e , поэтому конец этого выраже ния является точкой оценки. Следовательно, язык С гарантирует, что побочный эффект (прираще ние зна­ че ния gu e s t s ) произойдет до того , как программа пере йдет к выполнению printf ( ) . Однако , использование постфиксной формы обес печивает то , что пе ременная g u e s t s получит приращение после того , к а к произойдет сравне ние ее со значе нием 1 0 . Теперь рас смотрим следую щий оператор: у = ( 4 + х++ ) + ( 6 + х++ ) ; В ыражение 4 + х++ не является полным выражением , следовательно , язык С не га­ рантирует, что значе ние х будет инкреме нтировано сразу после того , как будет вы­ числено подвыражение 4 + х++ . В данном случае полное выраже ние представлено це­ лым опе ратором присваивания , при этом точка с запятой отме чает точку оце нки, так что С может гарантировать только то , что значе ние х будет увеличено на единицу дважды к моменту перехода программы к выполнению следую щего опе ратора . Язык С не уточняет, будет ли значение х инкрементировано после вычисления каждого под­ выраже ния или после вычисления всех выражений, именно поэтому вы должны избе­ гать операторов подобного рода. Составные операторы (блоки) Составпой оператор - это два или б ольшее число операторов, объедине нных в еди­ ную группу с помощью фигурных с кобок; его еще называют блоком. В программе s h o e s 2 . с блок ис пользуется для того , чтобы опе ратор whil e мог выполнить сразу не­ сколько операторов как один. Рас смотрим следую щие фрагме нты программ: /* фр агмент 1 * / index = О ; whi l e ( i ndex++ < 1 0 ) s am = 1 0 * index + 2 ; p r i nt f ( " s am % d\ n " , s am) ; / * фр агмент 2 * / index = О ; whi l e ( i ndex++ < 1 0 ) { s am = 1 0 * index + 2 ; print f ( " s am = % d\ n " , s am) ; 1 90 Гл ава 5 В нутри фрагмента 1 в цикл whi l e входит только опе ратор присваивания . В отсут­ ствие фигурных с кобок область действия опе ратора whi l e распространяется от клю­ че вого слова whil e до следующей точки с запятой. Функция printf ( ) вызывается только один раз - по завершении цикла. В рамках фрагме нта 2 наличие фигурных скобок гарантирует, что оба опе ратора являются частью цикла whi l e , а функция printf ( ) вызывается при ка жд ом выполне­ нии цикла. Что касается структуры оператора whi l e , то ве сь составной оператор рас­ сматривается как единый оператор (рис . 5 . 7 ) . l while false Обратите внимание на префиксную форму, значение переменной fish инкрементируется перед каждым вычислением условия Возврат в начало цикла true food=quota *fish ; printf ( " %d- - - - - " , food , fish) ; Ри с. 5.7 . Цикл whi l e с составнъш оператором Со веты к а сатель н о сти ля Еще раз рассмотрим оба фра гмента, в которых присутст вует цикп whi l e , и вни мател ь­ но присмотримся к то му, ка к ис пол ьзуются отступы от левого поля в каждом из этих цикпов. Дпя компилятора отступы в строке не и меют ника кого значени я , прини мая ре­ шение относ ител ьно того, ка к интерпрети ровать зада нную команду, он учиты вает тол ь­ ко фигурные с кобки и знания о структуре цикпа whi l e , которые в него заложен ы . Оrсту­ пы в данном случае служат для то го, чтобы обл е гч ит ь зрительное восприятие органи­ зации програ м м ы . П риведенны й в ы ш е п р и м е р демонстрирует широко ра спро ст ра ненны й с пособ расста­ новки фигурных с кобок в случае ис пол ьзования оператора блока ил и, иначе говоря , со­ ста вного о пе ратора. Другой, не менее распространенн ы й способ вы глядит следующи м образом : whi l e ( i ndex++ < 1 0 ) { s am = l O * index + 2 ; print f ( " s am = % d\ n " , s am) ; Этот стиль а кценти рует внимание на принадлежност и блока к ци кпу whi l e . П редыду­ щий ст ил ь а кцент и рует внимание на том, что нескол ько операторов образу ют блок. По­ вторим еще ра з: что касается ко мпилятора, то оба с по соба идент ичн ы . Подводя ито ги, с кажем: ис пользуйте отступы ка к инстру мент , позвол я ющий сделат ь ст руктуру про гра ммы более понятной для читател я . Операции, вы ражения и оператор ы 1 91 Св одка; выражен и я и операто ры В ь1 ражения : В ыражен ие представпяет собой некоторую комбина цию опе ра ц и й и операндов. П ро­ стейшим вы ражением я вп я ется константа ил и переменная без операции, на пример, 2 2 ил и Ь е еЬор. Более сложные в ы ражения ми я вп я ются 5 5 + 22 и vap = 2 * ( vip + ( vup = 4 ) ) . О п ератор ы: Операт ор это команда компьютеру. Операторы бы ва ют прост ыми и составны ми. Про­ стые операторы завершаются точ кой с запятой, ка к показано в следу ющих примерах : - int to e s ; to e s = 1 2 ; Опе ратор вызова функци и : printf ( " % d\ n " , to e s ) ; Ст руктурированны й оператор: whi l e ( to e s < 2 О ) to e s = to e s + 2 ; Пусто й ( N U L L ) операто р: ; / * нич е г о не делае т * / Опе ратор объя впения: Опе ратор присва ивания : Составные операт оры , ил и блоки, состоят и з одного ил и бол ьшего числа операторов ( кото рые са ми могут быть соста вны м и операторами), заключенных в фигурные с кобки . П р и веденны й ниже пример операто ра whi l e содерж ит составной оператор: whi l e ( y e a r s < 1 0 0 ) { wi s dom = wi s dom * 1 . 0 5 ; print f ( " % d % d\ n " , y e ar s , wi s dom) ; years = y e a r s + 1 ; Преобра зования тип ов О пе раторы и выражения в общем случае должны использовать одни и те же типы выраже ний и констант. Если вы , однако, в одном и том же выражении употре бляете разные типы, то выполнение программы на С не останавливается , как это имеет ме­ сто , с кажем, с программами на языке Pascal. Вме сто этого используется набор правил, об еспечиваю щих автоматиче ское преобразование типов данных. С одной стороны, это очень удоб но , но в то же вре мя это может стать источником проблем, ос обенно если вы допус каете смешивание типов по причине невнимательности. (Программа lint, входящая в состав многих Uniх-с истем, выполняет проверку на наличие кон­ фликтов между типами. Многие компиляторы С , не предназначе нные для раб оты под управление м Unix, будут с ооб щать о возможных проблемах, если вы выбе рете высо­ кий уровень защиты от ошибок. ) Программистам рекомендуется хотя бы в общих чер­ тах ориентироваться в правилах преобразования типов. Ниже описаны базовые правила преобразования типов данных. 1 . Если в выражение входят типы char и s hort, причем оба они могут быть как s igned (со знаком ) так и unsigned (без знака ) они автоматически преобразуют­ ся в тип int или, при не обходимости, в unsigned int . (Если тип s hort име ет тот же размер, что и int, то размер типа unsi gned short больше , чем разме р int; в этом случае unsigned s hort пре образуется в un s i gned i nt . ) С оглас но 1 92 Гл ава 5 правилам K&R С , но не в текущей версии языка С , тип float автоматически пре образуется в do uЫ e . Поскольку эти преобразования приводят к появле нию более крупного размера данных, они называются пов'Ы шеиием типа. 2. Если какая-либо опе рация выполняется над данными двух типов, оба типа приводятся к выс шему из двух этих типов. 3. Последовательность типов, упорядоченных по принципу от выс шего к низшему, выглядит так: l o ng douЫ e , douЫ e , float, unsi gned long long, long long, unsigned long, long, unsigned i nt и int. Возможно одно ис клю че ние , когда long и int имеют один и тот же размер, в этом случае unsigne d int превосходит long. Типы short и char не присутствуют в этом спис ке , пос кольку они уже могли быть повыше ны до i nt или, возможно , до unsigned int . 4. В операторе прис ваивания окончательный результат вычисле ний пре образует­ ся к типу пе ременной, которой присваивается этот результат . Проце с с может приве сти к повышению типа в соответствии с правилом 1 или к поиижеиию ти­ па, вследствие чего значение приводится к б олее низкому типу. 5. При использовании в качестве аргументов типов char и s hort пре образуются к int, а float к do uЫ e . Это автоматическое повышение типов может б ыть от­ менено с помощью прототипирования функций, как будет показано в главе 9 . - Повышение в об ще м случае представляет с обой гладкий процесс , протекающий без осложнений, в то вре мя как понижение типа может привести к серьезным послед­ ствиям. Причина проста : тип более низкого типа может оказаться недостаточным, чтобы содержать в памяти число полностью . 8-разрядная пе ременная типа char может принимать целочисленное значение 1 О 1 , но не целое число 2 2 3 3 4 . Когда типы с пла­ вающей запятой приводятся с пониже нием к целому типу, они ус екаются либо округ­ ляются до нуля . Это означает, что два разных числа 2 3 . 1 2 и 2 3 . 9 9 в результате ус ече­ ния принимают одно и то же значение 23 и что - 2 3 . 5 ус екается до - 2 3 . В листинге 5 . 1 4 показано, как работают эти правила. листинг 5.1 4. Проrрамма convert . с / * convert . c - - ав томатич е ск о е пр е о бр а з о в ание типов * / #incl ude <s tdio . h > int main ( vo i d ) { char ch ; int i ; fl o at fl ; fl = i = ch = ' С ' ; printf ( " ch = % с , i % 2 . 2 f\ n " , ch , i , fl ) ; % d , fl ch = ch + 1 ; i = fl + 2 * ch ; fl = 2 . 0 * ch + i ; printf ( " ch = % с , i = % d , fl = % 2 . 2 f\ n " , ch , i , fl ) ; ch = 5 2 1 2 2 0 5 . 1 7 ; / * строка 1 5 * / printf ( " T eпepь ch = % c\ n " , ch ) ; r e turn О ; /* /* /* /* /* /* строка 9 * / строка 1 0 * / строка 1 1 * / строка 12 * / строка 13 * / строка 1 4 * / Операции, вы ражения и оператор ы 1 93 В ыполнив программу convert . с , получим следую щие результаты : ch = С , i ch = D , i Т е перь ch 6 7 , fl = 6 7 . О О 2 0 3 , fl = 3 3 9 . 0 0 В этой с истеме , в которой реализованы 8-разрядный тип char и 3 2-разрядный тип int, происходит следую ще е : • • • • • Строки 9 и 1 0 символ ' С ' запоминается как одноб айтное АSСП·значение в переменной ch. Целочисле нная переменная i получает целое значение , кото­ рое является преоб разованием символа ' С ' в целое число , равное 6 7 , и хранит· ся в 4 байтах памяти. И , наконец, переменная fl получает значе ние 6 7 . 0 0 , ко­ торое является ре зультатом преобразования целого числа 6 7 в число с плаваю· ще й запятой. - Строки 1 1 и 1 4 - значение ' С ' символьной пере менной преобразуется в целое число 6 7 , к которому затем прибавляется 1. Полученное в результате 4-байтовое целое число 68 усекается до 1 байта и запоминается в пе ременной ch . При пе· чати с использование м спецификатора % с число 6 8 инте рпретируется как АSС П·код символа ' D ' . Строки 12 и 1 4 - при умноже нии на 2 значение пе ременной ch пре образуется в 4-байтовое целое ( 6 8 ) . Полученное при этом целое значение ( 1 3 6 ) пре образует· ся в число с плавающей запятой, чтобы затем можно было сложить с fl . Ре зуль· тат ( 2 0 3 . О О f) преобразуется к типу int и запоминается в i . Строки 1 3 и 1 4 - значе ние переменной ch ( ' D ' , или 6 8 ) пре образуется в тип с плавающей запятой, после чего становится возможным е го умноже ние на 2 . О . Значение i ( 2 0 3 ) пре образуется в значение с плаваю щей запятой, чтоб ы можно было выполнить сложение , а результат ( 3 3 9 . О О ) запоминается в пере менной f l . Строки 1 5 и 16 - в этих строках предпринимается попытка понижения типа , ко· гда переменной ch присваивается очень большое значение . После усечения ch получает значение, которое в коде ASCII интерпретируется как символ дефис а . О п ерация при ведения По мере возможностей в ы должны избегать автоматического преобразования ти· пов, особенно пониже ния типов, но иногда такие преобразования оправданы, есте ст· венно , при условии, что принимаются все меры предосторожности. Преобразования типов, которые мы обсуждали до сих пор, выполняются автоматичес ки. В то же время вы можете потребовать выполнения име нно того типа преобразований, какой вам нужен, либо документировать факт , что вы знаете , что выполняете пре образование типа . Метод выполнения таких преобразований известен как приведепие типов и преду· сматривает выполнение следую щей процедуры : пе ред заданной величиной в круглых скобках проставляется имя требуемого типа данных. Скобки и имя в совокупности представляют собой операцию приведения типа . В об ще м случае операция приведе· ния типа записывается в следую щем виде : ( тип) Вместо слова тип указывается фактичес ки не обходимый тип, например, l o ng. 1 94 Гл ава 5 Расс мотрим две приведенные ниже строки кода, в которых mi ce - это переме нная типа int . Вторая строка соде ржит два приведения к типу int . mi ce 1.6 + 1.7; mi ce = ( int ) 1 . 6 + ( int ) 1 . 7 ; В первом примере применяется автоматичес кое преобразование типов. Сначала складываются числа 1 . 6 и 1 . 7 , что в сумме дает 3 . 3 . Это число затем пре образуется за счет усе че ния в целое число 3 с целью согласования с пере менной типа int. Во втором примере число 1 . 6, равно как и 1 . 7 , перед сложе ние м преобразуются в целое число ( 1 ) , в результате чего переме нной mi ce прис ваивается значение 1+ 1 , или 2 . По сути дела , ни одна из форм ничем не лучше другой, вам следует выб рать соответствую щую форму, исходя из тре бований контекста программируе мой задачи. В повседне вной практике старайте с ь избегать пе ремешивания типов (по этой причине в некоторых языках программирования оно не допускается ) , но встречаются случаи, когда оно поле зно . Философия языка С заклю чается в том , чтобы не устанав­ ливать дополнительных барьеров на своем пути, возложив на вас вс ю ответственность за злоупотребле ние предоставляемой вам с вободой. Сводка: оп ерац и и и операто ры яз ы к а С Н иже переч ислены операци и , которые обсуждал ись выше: О пера ция прис ваи ван ия : П рисва ивает значение с п рава от зна ка о пе ра ц и и переменной , указа нной слева от знака . А рифмети ческие опера ц ии : + П риба вляет значение справа о т зна ка операции к значени ю слева о т зна ка . Выч итает значение с пра ва о т зна ка о пе ра ц и и из значения слева о т знака. * Уна рная опера ция ; изменяет знак значения с п рава от знака операции. У множает значение с п рава от зна ка опера ц и и на значение слева от знака . / Делит значение слева о т знака опера ц и и н а значение с п рава от зна ка . Резул ьтат усекается , есл и оба операнда я вл я ются целочисленны м и . % Резул ьтат этой о перации представляет собой остаток от деления значения слева от знака на зна чение с п рава от зна ка (вы полняется только над цел ы м и ч ислами). ++ П риба вляет 1 к значению переменной с п рава от зна ка опера ц и и (префиксная форма) ил и к значению слева от зна ка операции ( постфиксная форма). Аналогична опера ции + + , но не доба вляет, а вычитает 1 . О пера ц и и разл ичного назначения : s i zeo f Возвра щает разме р в ба йтах операнда с п рава от знака операции. Таки м о пе ратором может быть спецификатор типа, закп юченн ы й в кру гл ы е с кобки , на пример, s i zeo f ( fl o at ) , ил и это может быть имя конкретно й пере мен­ но й, массива и тому подобное, например s i z e o f foo . ( тип) Ка к оператор приведения т и па , преобразует соот ветст ву ющее зна чение , стоя щее с п рава, к т и пу, о пределенному ключе вы м словом в кру глых с кобках . Н а пример, ( flo at ) 9 преобразует целое число 9 в число с плавающей за­ пятой 9 . О . Операции, вы ражения и оператор ы 1 95 Функци и с аргументами К настоящему моменту вы уже знаете , чг о такое аргументы функций и как они ис· пользуются . Следую щий шаг на пути к мастерству в работе с функциями заклю чается в том, чтоб ы научиться писать свои собственные функции, которые используют аргу· менты . Рассмотрим, в чем состоит это ис кусство . (На этой стадии, возможно , имеет смысл ос вежить в памяти пример функции butler ( ) , расс мотре нный в конце главы 2 ; этот пример показывает , как нужно писать функции б е з аргументов.) Программа , представленная в листинге 5 . 1 5 , с одержит функцию pound ( ) , которая печатает задан· ное количе ство знаков фунта ( # ) . Этот приме р также проливает свет на некоторые ас пекты преобразования типов данных. Листинг 5.1 5. Проrрамма pound . c / * pound . c - - опр еделя е т функцию с одним арг ументом * / #incl ude <s tdio . h > / * про т о тип ANS I * / void pound ( int n ) ; int main ( vo i d ) { int time s 5; '!'; char ch fl o at f 6.0; po und ( times ) ; po und ( ch ) ; po und ( ( i nt ) f ) ; r e turn О ; void pound ( int n ) / * ASC I I -кoд равен 3 3 */ / * арг умент типа int /* тип char ав томатич е ски прив одится к типу int /* прив едение типа f - > i nt */ */ */ / * з а г о ловок функции в стиле ANS I */ / * г овори т , ч то она принима е т один аргуме н т типа int* / whi l e ( n - - > О ) print f ( " # " ) ; printf ( " \ n " ) ; В ре зультате выполне ния этой программы получе ны следую щие выходные данные : ##### ################################# ###### С начала расс мотрим заголовок функции: void pound ( int n ) Если для выполнения функции аргументы н е требуются , в круглых скобках заго­ ловка функции будет присутствовать клю че вое слово void. Поскольку рассматривае· мая функция принимает один аргумент типа int, в круглых с кобках содержится объ· явление переменной типа int с именем n. Вы можете воспользоваться лю б ым именем, которое с оответствует правилам именования языка С. О пе рация объявле ния аргуме нта приводит к созданию пе ременной, называе мой фо-р мааъ'Нъtм аргумттом или формааъ'Нъtм параметром. В данном случае формальным па· 1 96 Гл ава 5 раметром является переменная типа i nt с име не м n. Вызов функции, такой как, на­ приме р, po und ( 1 О ) , приводит к тому, что переменной n присваивается значе ние 1 О . В рас сматриваемой программе вызов po und ( time s ) присваивает значение time s ( 5 ) переменной n. М ы говорим, что вызов функции (или обращение к функции) передает значение , а само это значе ние называется фактU'ческим аргументом или факти'Ческим параметром, следовательно, вызов функции pound ( 1 0 ) передает фактичес кий аргу­ мент 1 0 данной функции, при этом 1 0 присваивается формальному аргументу (пе ре­ менной n ) . Иначе говоря , значение пе ременной tim e s в функции main ( ) копируется в новую переме нную n функции pound ( ) . Аргум енты и п араметры И хотя термины аргумент и параметр часто употребл я ются в одно м и том же с м ы сл е , стандарт С 99 рекомендует пол ьзоваться термино м аргумент вместо фа ктических а р­ гументов ил и фа кт ических параметров, а терм ином параметр - вместо формальных пара метров и формальных а ргументов. Учит ы ва я это согла шение, мы можем утвер­ ждать, что пара метры - это пере менные, а а ргументы - это значения , котор ы м и снабжа ются вызовы функций и которые присва ива ются соответствующим пара м ет ра м. Имена переменных являются локальными по отношению к функции. Это значит, что имя, объявленное в какой-либо функции, не вступает в конфликт с таким же име­ не м, объявленным в любом другом ме сте . Если вы использовали переменную time s вместо n в функции p ound ( ) , то в функции main ( ) будет создана переменная, отличная от time s . То е сть, у вас появляются две пе ременных с одним именем, но программа хорошо знает, к какой функции отно с ится та или иная пе ременная . Теперь рассмотрим вызовы функций. Пе рвым вызовом является pound ( time s ) , и как мы уже отмечали, данный вызов приводит к тому, что значе ние пере менной time s , равное 5, присваивается пе ременной n. В результате эта функция выводит на печать пять знаков фунта и знак новой строки. Второй вызов этой функции выглядит как pound ( ch ) . В данном случае пе ременная ch имеет тип char . О на инициализируется символом ! , который в с истемах с кодировко й ASCII оз начает, что ch получила чис­ ловое значение 3 3 . Автоматиче ское повыше ние типа char до i nt преобразует его в данной с истеме с о значения 3 3 , которое размещается в одном байте , до значе ния 3 3 , размеще нно го в четырех байтах, та к что теперь значение 3 3 приобрело нужную фо рму, чтобы е го можно б ыло использовать в данной функции . Последний вызов, pound ( ( int ) f ) , использует пре образование типов для приведения типа пере менной f типа f l o at к типу, соответствую ще му назначению этого аргумента . Предположим , что вы опустили приведе ние типа . В современном варианте С про­ грамма выполнит приведе ние типа автоматиче ски. Это объясняется те м, что в начале файла находится прототип ANSI: void pound ( int n ) ; / * п р о т о ти п ANSI * / Прототип это объявление функции, в котором дается описание возвращаемого значения функции и вс ех е е аргументов. Рассматривае мый прототип сообщает сле­ дую щие с ведения о функции pound ( ) : - • У рассматриваемой функции возвращаемое значение отсутствует . • Эта функция принимает один аргумент, каковым является значение типа int. Операции, вы ражения и оператор ы 1 97 П о с кольку компилятор про с матривает этот прототип ра ньш е , ч е м функция pound ( ) будет использована в функции main ( ) , он к тому моме нту будет знать, какой вид аргумента должна иметь функции pound ( ) , и он вставляет в программу преоб разо· ванне типов, если в этом есть не обходимость, чтобы фактичес кий аргуме нт с оответ· ствовал прототипу в плане типов. Например, вызов функции pound ( 3 . 8 5 9 ) будет пре· образован в pound ( 3 ) . Д ем он страци он ная програм м а В листинге 5 . 1 6 представлена полезная программа (для ограниченной, но активной части человечества ) , которая служит иллю страцией нескольких иде й, рассматривае· мых в данной главе . Она выглядит достаточно длинной, однако все вычисления вы· полняются в шести строках кода, расположе нных ближе к концу. О сновной объем программы предназначен для обмена информацией между компьютером и пользова· теле м. Мы снабдили ее множеством комментариев, чтобы сделать ее действия оче· видными. Внимательно ознакомьтесь с этой программой, после чего мы поможе м вам снять не сколько вопросов. листинг 5.1 6. Проrрамма ruJUti.nCJ . с 1 1 running . c - - прогр амма , полезная для тех , к то з а нима е т ся бег ом #incl ude <s tdio . h > cons t int S PER М 60 ; 1 1 колич е с тво с екунд в мину те cons t i n t S P E R Н 360 0 ; 11 колич е с тво с екунд в ч а с е const douЫe M_PER_K 0 . 62 13 7 ; 1 1 колич е с тво ми л ь в киломе тр е int main ( vo i d ) { do uЫ e di s t k , di s tm ; 1 1 дистанция пр обег а в киломе тр ах и милях do uЫ e r ate ; 11 средняя скоро сть в милях в ч а с int min , s e c ; 1 1 вр емя про бег а в минутах и секундах int time ; 11 вр емя про бег а тол ь ко в с екундах do uЫ e mtime ; 11 вр емя про бег а одной мили в секундах int mmin , ms e c ; 11 время пробег а одной мили в минутах и секундах = = printf ( " Э тa пр огр амма пер е считыв а е т вр емя пробег а дист анции в ме трич е ской системе \ n " ) ; p r i nt f ( " вo вр емя пр обег а одной мили и вычисля е т в ашу среднюю \ n " ) ; printf ( " cкop o c т ь в милях в ч ас . \ n " ) ; printf ( " Bв eди т e дис танцию про бе г а в киломе трах . \ n " ) ; s c an f ( " % l f " , & di s t k ) ; 1 1 % l f для тип а douЫ e printf ( " Дaлee вв еди т е вр емя в минутах и се кундах . \ n " ) ; printf ( " Haчни т e с в вода минут . \ n " ) ; s c an f ( " % d" , &mi n ) ; printf ( " T eпepь вв едите се кунды . \ n " ) ; s c an f ( " % d" , & s e c ) ; 1 1 пер еводи т вр емя в с е кунды time S PER_M * min + s e c ; 1 1 пер еводи т киломе тры в мили di s tm M_PER_K * di s t k ; колич е с тво миль в ч а с 1 1 мили в с е кунду умножить н а секунды в ч а с rate di s tm / time * S_PER_H ; = = = 1 98 Гл ава 5 1 1 вр емя / р а сстояние = вр емя про бег а одной мили mtime = ( douЬl e ) time / di s tm ; mmin = ( int ) mtime / S_PER_M ; / / вычи сление полного колич е ства минут ms e c = ( int ) mtime % S_PER_M ; / / вьruи сление о с т а тка в секундах p r i nt f ( " Bы про бежали % 1 . 2 f км ( % 1 . 2 f мили ) з а % d мин , % d сек . \ n " , di s t k , di s t m , min , s e c ) ; printf ( " T aкaя скоро сть со о тв е т с твует про бег у одной мили з а % d мин , " , mmin ) ; printf ( " % d s e c . \ nB aшa ср е дняя скор о с т ь р ав нялась % 1 . 2 f миль в ч а с . \ n " , ms e c , r ate ) ; r e turn О ; В программ е , представленной в листинге 5 . 1 6 , используется тот же подход, кото­ рый применялся ранее в программе min_s e c для завершающего перевода результата в минуты и секунды, при этом данная программа приме няет преобразования типов. С какой целью ? Да потому, что той части программы , которая выполняет пересчет се· кунд в минуты , тре буются целочисленные аргументы, а при преобразовании метриче· ских данных в мили применяются числа с плавающей запятой. Мы использовали опе· рацию приведения , чтобы сделать эти преобразования явными. По правде говоря , эту программу можно было написать, используя только автомати· ческие преобразования типов. Фактически мы так и поступили, применив опе рацию приведения переменной mtime к типу int с тем, чтобы при вычислении времени все операнды имели целый тип. Тем не менее, эту версию программы не удалось выполнить на одной из 1 1 систем, на которых мы пытались ее запускать. Компилятор той системы (устаревшая и вышедшая из употребления версия) оказался неспособным следовать пра· вилам языка С. Использование приведения типов делают ваши намерения более понят· ными не только для читателей, но , по-видимому, и для компиляторов тоже . В от один из результатов ис полне ния рас сматриваемой программы: Эта программа пер есчитывает время про бег а дистанции в ме трич еской системе во вр емя про бе г а одной и мили в ьruисля е т в ашу ср е днюю скорость в милях в ч а с . Вв едите дистанцию про бег а в киломе тр а х . 10 . 0 Далее вв едите вр емя в минутах и секундах . Начните с ввода минут . 36 Т е перь в в едите секунды . 23 Вы пробежали 1 0 . 0 0 к м ( 6 . 2 1 мили ) з а 3 6 мин , 2 3 сек . Т акая скорость соо тве тств у е т пр обегу одной мили з а 5 мин , 5 1 сек . В аша ср е дняя скор о с т ь р ав нялась 1 0 . 2 5 миль в ч а с К л юч евые п онятия Язык С использует операции с целью оказания различного рода услуг. Каждую опе· рацию можно характе ризовать не которым количеством операндов, необходимых для ее выполнения , ее приоритетом и ассоциативностью . Два последних каче ства опреде· Операции, вы ражения и оператор ы 1 99 ляют , какие опе рации применяются пе рвыми и когда две операции с о вместно исполь­ зуют тот или иной опе ранд. О перации в комбинациях с конкретными значениями об­ разуют выражения, и каждое выражение в языке С имеет значение . Если вы не учиты­ ваете приоритет и ассоциативность операции, вы можете построить недопустимые выраже ния или такие выражения, которые дают результаты , отличные от тех, кото­ рые вы ожидаете , что отнюдь не содействует вашей репутации как квалифицирован­ ного программиста . Язык С позволяет вам запис ывать выражения , комбинируя различные типы дан­ ных. В то же время арифметические опе рации требуют, чтобы операнды были одного и того же типа , и в силу этого С выполняет автоматиче ское преобразование типов . Тем не менее , хороший тон в программировании гласит: не следует полагаться на ав­ томатическое пре образование типов. В место этого осуществляйте явный выбор ти­ пов, ис пользуя пе реме нные необходимых типов, либ о применяйте опе рации приведе­ ния типов. Поступая подобным образом, вы избежите неприятных сю рпризов со сто­ роны автоматического преобразования типов данных. Р езюм е В языке С существует множество операций, таких как операции присваивания и арифметиче ские опе рации, расс мотре нные в данной главе . В общем случае , опершция выполняется над одним или б ольшим числом опе рандов с целью получить соответст­ вую щее значе ние . О пе рации, которые выполняются в отноше нии одного операнда , подобные знаку " минус " или s i z eo f, называются унарнъ1ми (или одноместнъtми) опера -ци.ями. О перации, использую щие два опе ранда, такие как операции сложения и умно­ же ния , называются бинарнъtми (или двухместнъtми) опера-циями. Въtражения представляют собой комбинации операций и операндов. В С каждое выраже ние имеет значение , в том числе выражения прис ваивания и выраже ния срав­ не ния . Правила приоритетов опера-ций помогают определить, в каком порядке группи­ руются элеме нты при вычисле нии выражений. Когда две операции претендуют на ис­ пользование какого-либо опе ранда , опе рация , име ю щая боле е высокий приоритет, выполняется над ним первой. Если эти операции обладают равными приоритетами, какая них применяется первой, определяет ассоциативность (слева направо или с пра­ ва нале во ) . Операторъt это заве шенные команды компьютеру, они рас познаются в С п о точке с запятой, проставленной в конце . До сих пор вам приходилос ь иметь дело с операто­ рами объявлений, операторами прис ваивания , операторами вызова функций и управ­ ляю щими операторами. О ператоры, заклю ченные в фигурные с кобки, образуют со ставной оператор, или блок. Конкретным примером управляющего оператора является цикл whi l e , который повторно выполняет операторы до тех пор , пока проверяемое условие остается истинным . В языке С многие пре образования типов выполняются автоматиче ски. Типы char и s hort повышаются до типа douЫ e всякий раз , когда ис пользуются в аргументах функций. В версии K&R С (но не в версии ANSI С) тип fl o at повышается до do uЫ e , когда присутствует в выражении. Когда значение одного типа прис ваивается пе ре­ менной другого типа , то эта величина приводится к тому же типу, что и переме нная . Когда б ольшие типы (в смысле занимаемой памяти) приводятся к меньшим типам - 200 Гл ава 5 (например , l o ng к s hort или douЫ e к fl o at ) , возможна поте ря данных. В случае вы­ полне ния арифметичес ких опе раций над опе рандами разных типов меньшие типы приводятся к большим в соответствии с правилами, изложе нными в данной главе . Когда вы даете определение функции с одним аргументом, вы объявляете перемеп пую, или формалъжый аргумепт, в определе нии функции. Зате м значе ние , пе редаваемое в вызове функции, присваивается этой переменной, которая может теперь ис пользо­ ваться внутри данной функции. В оп росы для сам оконтроля 1 . Предположим, что вс е переменные имеют тип int . Найдите значения каждой из следующих пе ременных: L Х = (2 + 3 ) * 6 ; б . х = ( 12 + 6) /2*3 ; х = (2 + 3) / 4 ; в. у г. у 3 + 2* (х = 7 / 2 ) ; 2 . Предположим, что вс е переменные имеют тип int . Найдите значения каждой из следующих пе ременных: L Х = ( int ) 3 . 8 + 3 . 3 ; б. х = ( 2 + 3 ) * 1 0 . 5 ; в. х = 3 / 5 * 2 2 . 0 ; г. х = 2 2 . о * 3 / 5 ; 3 . У вас возникли подозрения , что в приведенной ниже программе присутствуют ошибки. Сможете ли вы их обнаружить? int main ( vo i d ) { int i = 1 , flo at n ; printf ( "Бyдь тe о с торожны ! Далее иде т последовател ь но с ть дробей ! \n " ) ; whi l e ( i < 3 0 ) n = 1/i ; print f ( " % f " , n ) ; printf ( " B o т и в се ! \ n " ) ; r eturn ; 4. Ниже приводится альтернативный вариант программы из листинга 5 . 9 . О н от­ личается б олее простым кодом, пос кольку два опе ратора s can f ( ) из листинга 5.9 в не м заме не ны одним опе ратором s c an f ( ) . Почему этот вариант програм­ мы хуже, чем исходный? #include < s t dio . h> #de fin e S ТО М 6 0 int main ( vo i d ) { int s e c , min , l e ft ; Операции. вы ражения и оператор ы 201 printf ( " Э тa пр ограмма пере водит секунды в минуты и " ) ; p r i nt f ( " c eкyнды . \ n " ) ; p r i nt f ( " B в eдитe колич е с тво секунд . \ n " ) ; p r i nt f ( " Для з а в ершения про г р аммы вв еди т е 0 . \ n " ) ; whi l e ( s e c > 0 ) { s canf ( " % d" , & s e c ) ; min s e c/ S_TO_M ; l e ft s e c % S_TO_M ; printf ( " % d секунд равно %d минут, %d секунд . \ n " , sec , min , l e ft) ; рrint f ( " Сле дующий ввод данных ? \ n " ) ; = = printf ( " P aбo тa з ав ершена . \ n " ) ; r eturn О ; 5 . Что распечатает следую щая программа? #include < s t dio . h> #de fin e FORMAT " % s ! С - э то круто ! \ n " int main ( vo i d ) { int num 10; = p r i nt f ( FORМAT , F ORМAT ) ; printf ( " % d\ n " , ++num ) ; printf ( " % d\ n " , num++ ) ; printf ( " l d\ n " , num-- ) ; printf ( " l d\ n " , num) ; r eturn О ; 6 . Что распечатает следую щая программа? #include < s t dio . h> int main ( vo i d ) { char c l , с2 ; int di f f ; flo at num ; cl 'S' ; с2 'О' ; di f f c l - с2 ; num di f f ; printf ( " % c % c % c : % d % 3 . 2 f\ n " , c l , с2 , c l , di f f , num) ; r eturn О ; = = = = 7. Что распечатает эта программа? #include < s t dio . h> #de fin e TEN 1 0 int main ( vo i d ) { int n О; = 202 Гл ава 5 whi l e ( n + + < TEN) print f ( " % 5d" , n ) ; printf ( " \ n " ) ; r eturn О ; 8 . Вне с ите в последню ю программу такие измене ния , чтобы она вме сто цифр пе­ чатала буквы от а до g. 9. Если приведе нные ниже фрагменты были частью единой программы , что бы они пе чатали? а. int х О; whil e ( + + х < 3 ) print f ( " % 4 d " , х ) ; = б . int х 10 0 ; whil e ( х++ < 1 0 3 ) print f ( " % 4 d \ n " , x ) ; print f ( " % 4 d \ n " , x ) ; = в. char ch 's ' ; whil e ( ch < ' w ' ) { print f ( " % c " , ch ) ; ch++ ,= print f ( " % c\ n " , ch ) ; 1 0 . Что будет пе чатать следую щая программа? #de fin e ME SG "COMPUTER BYTES DOG" #include < s t dio . h> int main ( vo i d ) { int n О; whi l e ( n < 5 ) printf ( " % s \ n " , ME S G ) ; n++ ; printf ( " B o т и в с е . \ n " ) ; r eturn О ; = 1 1 . Напишите оператор ы , которые выполняют следую щие де йствия (или, другими словами, имеет следую щие побочные эффе кты ) : а. Увеличивает значе ние переменной х на 1 0 . б . Увеличивает значе ние переменной х на 1 . в . Присваивает удвое нную сумму а и Ь пере менной с. г. Присваивает а плюс дважды Ь переменной с. 12. Напишите операторы , которые выполняют следую щие де йствия : а. Уменьшает значение пере менной х на 1 . б . Присваивает m остатов от деле ния n на k. в. Делит q на Ь , вычитает а присваивает результат р . г . Присваивает пе ременной х результат деления суммы значений пе ременных а и Ь на произведе ние с и d. Операции, вы ражения и оператор ы 2 03 Уп ражн ен и я по програм м и рован и ю 1 . Напишите программу, которая переводит время в минутах во время в часах и минутах. Воспользуйтесь инструкциями #de fine или con s t для создания сим­ вольной константы со значение м 60. Используйте цикл whi l e , чтобы обес пе­ чить пользователю возможность повторного ввода значений и для прекраще­ ния цикла, если вводится значение времени, меньшее или равное нулю . 2 . Напишите программу, которая запрашивает ввод целого числа , а зате м пе чата­ ет все целые числа , начиная (и вклю чая ) с этого числа и до числа , большего введенной величины на 1 0 включительно . (Иначе говоря , е сли вводится число 5, то выходными данными являются числа от 5 до 1 5 включительно . ) О бяза­ тельно отделите друг от друга значения выходных данных пробелом либо сим­ волами табуляции или новой строки. 3. Напишите программу, которая требует от пользователя ввести количе ство дней, а затем пе реводит это значение в количество недель и дней. Наприме р, она переводит 1 8 дней в 2 недели и 4 дня. Отобразите результаты в следую щем формате : 1 8 дней сост авляют 2 не дели и 4 дня . Ис пользуйте цикла whi l e , чтобы дать пользователю возможность многократно­ го ввода количества дне й и закончить цикл, для чего пользователь долже н вве­ сти неположительное значение , например , О или - 2 0 . 4 . Напишите программу, которая просит пользователя ввести высоту в сантимет­ рах, после чего отоб ражает высоту в сантиметрах, а также в футах и дю ймах. Разрешается также выводить дробные части сантиметров и дюймов, а програм­ ма должна обеспечить пользователю возможность продолжать ввод значений выс оты до тех пор, пока не будет введе но неположительное значе ние . Приме р выполнения этой программы должен иметь следую щий вид: Вв еди т е высо ту в сантим е тр ах : 1 8 2 1 8 2 . 0 см = 5 футо в , 1 1 . 7 дюймов Вв еди т е высо ту в сантим е тр ах (<=О для выхода и з прогр аммы) 1 6 8 . 0 см = 5 футо в , 6 . 1 дюймо в Вв еди т е высо ту в сантим е тр ах ( < = О для выхода и з прогр аммы) Работа з ав ершена . 168 О 5 . Вне с ите изменения в программу addemup . c (листинг 5. 1 3 ) , которая вычисляет сумму 20 первых целых чисел. (При желании вы можете расс матривать addemup . с как программу, которая вычисляет, какую сумму денег вы получите за 20 дней, если в пе рвый день вы получаете $ 1 , во второй день - $2 , в третий день - $3 и так дале е . ) Вне с ите в эту программу изменения , благодаря которым вы можете в интерактивном ре жиме указать, насколько далеко зайдут вычисле­ ния . Другими словами, заме ните целое число 20 пе ременной, значение которой программа может вводить. 6. Теперь внесите такие изменения в программу из упражне ния 5, чтоб ы она мог­ ла вычислять сумму квадратов целых чисел. (Или, е сли вам так больше нравит­ ся , программа вычисляет, какую сумму денег вы получите , если в первый день 204 Гл ава 5 вам заnпатят $1 , во второй день $4 , в третий день $9 и так далее. Такая ин­ терпретация намного симпатичнее ! ) В языке С нет функции возведения в квад­ рат, но в этом случае вам поможет сознание того , что квадратом числа n являет­ ся n * n . - - 7. Напишите программу, которая запрашивает ввод числа типа float и распе ча­ тывает значение куба этого числа . С этой целью вос пользуйте сь функцией, ко­ торая возводит заданное значение в куб и распечатывает результат, но эту функцию вы должны реализовать самостоятельно . Программа mai n ( ) должна передать вводимое значение этой функции. 8 . Напишите программу, которая тре бует от пользователя ввести значе ние те мпе­ ратуры по Фаренгейту. Программа должна считывать значение темпе ратуры как число типа douЫ e и передать его как аргумент пользовательской функции с именем T emper atur e s ( ) . Эта функция должна вычислять эквивале нтные значе­ ния температуры по Цельсию и по Кельвину и отображать на экране все три значения те мпе ратуры с точностью до двух позиций справа от десятичной точ­ ки. Функция должна сопоставлять каждое значе ние с температурной шкалой, которую оно представляет. Формула пере вода те мпе ратуры по Фаре нге йту в температуру по Цельс ию имеет вид: Т емпер атур а - по-Цель сию 1 . 8 * Т емп ер а тур а-по - Ф ар енг ейту + 3 2 . 0 = В шкале Кельвина , которая обычно приме няется в научных применениях, О представляет абсолютный нуль, минимальный предел возможных те мператур . Формула перевода температуры по Цельс ию в температуру по Фаре нге йту име­ ет вид : Т емпер атур а - по - К е л ь вину = Т емпер атур а-по -Цель сию + 2 7 3 . 1 6 Функция T emper atur e s ( ) должна использовать con s t для с о здания символиче­ ских представлений трех констант, которые используются для перевода . Функ­ ция main ( ) должна использовать цикл, чтобы предоставить пользователю воз­ можность многократного ввода значений темпе ратуры. Программа заве ршает работу, когда будет введе н символ q или какое-то другое нечисловое значе ние . ГЛА ВА 6 Уп р а вл я ю щ и е о п е р ато р ы : ц и кл ы в этой главе: • • • • • Кл ю ч евые сл ова: for, while, do while Опера ции : <. >. >= . <=. ! = . *= . - = . / = . %= =. += , • • Ф ункци и : fabs О Т РИ СТРУКТУРЫ Ц И КЛОВ В С while, for и do while - • Использова н и е опера ций отношения для созд ания в ы ражен и й . уп равляющих этими ц и кл а м и Др угие опера ц и и М асси в ы . которые ч асто используются с ц и клам и н а п исание функций. им еющ их в озв ращаем ы е знач ения м ощный, инrеллектуальный, универсальный и полезный ! Многие , несомне н­ но , хотели бы заслужить такие эпитеты. При наличии языка С существует, по меньшей мере, определенный шанс , что наши программы заслужат по­ добную оце нку. В с е дело заключается в правильном управле нии ходом выполнении программы . В соответствии с положе ниями теории вычислительных с истем (это нау­ ка о компьютерах, но не наука, развиваю щаяся благодаря компьютерам . . . пока что ) , хороший язык программирования должен обес печивать следую щие три формы управ­ ления ходом выполнения программы : • • • Выполнение последовательности операторов . Многократное выполнение заданной последовательности операторов, пока не будет удовлетворено некоторое условие (цикл ) . Ис пользование прове рок с целью выбора одной из нескольких альтернативных последовательностей действий (условный переход ) . Первая форма вам хорошо знакома ; в с е рассмотре нные выше программы пред­ ставляли с об ой те или иные последовательности операторов. Цикл whi l e является одним из примеров второй формы . В этой главе вы боле е подробно ознакомитесь с циклом whil e наряду с двумя другими циклическими структурами for и do whi l e . Третья и последняя форм а , осуще ствляя выбор и з нес кольких возможных последова­ тельностей действий, делает программу намного б олее " инrеллектуальной" и суще ст­ венно повышает эффективность ис пользования компьютера. Как ни пе чально, но вам - 206 Гл ава б придется подождать, пока не прочитаете целую главу, прежде чем вам будет довере но такое могущество. Эта глава служит также введением в мас с ивы , поскольку име нно к ним вы сможете приме нить приобретенные знания о циклах. Наряду с этим, в этой главе продолжается наращивание ваших знаний, кас ающихся функций. Начнем с того, что возобновим изучение цикла whi l e . Продолжен ие и зу ч ен ия ци кл а whi le В ы уже немного знакомы с циклом whi l e , и с е йчас повторим то , что уже известно , на приме ре программы , которая суммирует целые числа , вводимые с клавиатуры ( с м . листинг 6 . 1 ) . В этом примере для прекраще ния ввода данных используется возвра· щаемое значение функции s can f ( ) . листинг 6.1 . Программа sumninq . с / * s umming . c - - суммиру е т це лые числа , вводимые в интер а к тивном р ежиме * / #incl ude <s tdio . h > int main ( vo i d ) { l o ng num ; l o ng s um = 0 1 ; / * инициализ ация переменной s um нулем * / int s tatus ; printf ( " Bв eди т e целое число для после дующег о суммирования " ) ; printf ( " (или q для з ав ерш ения программы) : " ) ; s t atus = s can f ( " % l d " , &num ) ; whi l e ( s tatus == 1 ) / * == обознач а е т равенство * / { s um = s um + num ; printf ( "Bвeдитe следующее целое число (или q для завершения программы ) : " ) ; s tatu s = s c an f ( " % ld" , & num) ; printf ( " Сумма введенных целых чисел р авна % l d . \ n " , s um ) ; r e turn О ; Программа, показанная в листинге 6 . 1 , ис пользует тип l o ng, что позволяет вводить большие числа . Во избежание противоречий программа инициализирует пе реме нную s um значением 0 1 (нуль типа long ) , а не значе ние м О (нуль типа int ) , даже не смотря на то , что свойство автоматичес кого преобразования типов в языке С позволяет ис· пользовать просто О . Ниже показан пример выполнения этой программы: Вв едите целое число для п о следующег о суммир ов ания (или q для з ав ершения пр огр аммы) : 44 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : 3 3 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : 8 8 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : 1 2 1 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : q Сумма вв еденных целых чисел равна 2 8 6 . Управля ющие операторы: цикл ы 2 07 Ко м мента р и и по п рогра мме Расс мотрим сначала цикл whi l e . Проверяемым условие м этого цикла является: s t atus = = 1 В языке С с помощью == представляется операция равенства , то есть это выраже­ ние проверяет, равно ли 1 значе ние переменной s tatus . Не путайте это условие с вы­ ражение м s tatu s = 1, которое присваивает 1 пе ременной s t atus . Если прове ряемым условием является s tatus == 1, то цикл повторяется до тех пор, пока переменная s tatu s сохраняет значе ние 1 . На каждой итерации цикл приб авляет значе ние num к значению переменной s um, благодаря чему в переме нной s um накапливается текущее значение суммы. Когда переме нная s tatus получит значение , отличное от 1, цикл за­ вершается и программа выводит окончательное значение s um. Чтобы программа работала правильно , на каждой итерации цикла она должна по­ лучать для переменной num новое значение и на каждой итерации она должна устанав­ ливать новое значение переменной s tatus . Программа решает эту задачу, ис пользуя два свойства функции s ca n f ( ) . Во-пе рвых, программа вызывает s c an f ( ) для считыва­ ния нового значе ния пе ременной num. В о-вторых, она использует значение , возвра­ щаемое функцией s c an f ( ) , чтоб ы сообщить, была ли попытка считывания ус пе шной. Как отмечалось в главе 4, s can f ( ) возвращает количе ство успе шно считанных эле­ ментов. Если s c an f ( ) успешно считывает целое число , она помещает е го в перемен­ ную num и возвращает значе ние 1 , которое прис ваивается пе ременной s t atus . (Обра­ тите внимание , что входное значение передается пе ременной num, но не пере менной s tatu s . ) Это приводит к об новлению как значе ния num, так и значе ния s tatu s , и цикл whil e выходит на очередную ите рацию . Если вы ответите на приглаше ние вводом не­ числовой величины, такой как, например, символ q, то функция s can f ( ) не обнару­ жит целого числа при вводе , следовательно , возвращаемое е ю значение и значение s tatu s будут равны О. На этом выполне ние цикла завершается . Входной символ q, в силу того факта, что он не является числом, возвращается во входную очередь, он во­ обще не читается . (По сути дела , любой нечисловой ввод, а не только символ q, вызы­ вает заве ршение цикла, те м не менее, пользователю предлагается ввести q, поскольку это проще, че м просить ввести лю б ое нечисловое значе ние .) Если перед выполне нием преобразования значения функция s can f ( ) столкнется с какой-то пробле мой (наприме р, обнаружит символ конца файла или случится сбой оборудования ) , она возвращает спе циальное значение EOF ("End O f File" - конец фай­ ла ) , которое обычно определяется как - 1 . Это значе ние также вызовет завершение цикла. Такое двойное ис пользование функции s ca n f ( ) позволяет избежать трудно ре­ шаемой проблемы инте рактивного ввода в цикле : как с ооб щить циклу о том, когда он должен прекратиться? Предположим , например, что функция s ca n f ( ) не имеет воз­ вращаемого значения. В таком случае единственное , что подвергается изме не нию на каждой ите рации - это значе ние переменной num. В ы можете ис пользовать значение num, чтоб ы пре кратить выполнение цикла, указав, например, выражение num > О или num ! = О в каче стве проверяемого условия , но в этом случае вы лишаете сь возможно­ сти использовать отдельные вводимые значения, такие как -3 или О . Вместо этого вы могли бы добавить в цикл новый код, например запрос "Намерены ли вы продолжать? <да/нет>" на каждой итерации, а зате м проверять, ввел ли пользователь ответ "да" на 208 Гл ава б этот вопро с . Это выглядит несколько неуклюже, к тому же, замедляется ввод . Исполь­ зование возвращаемого значе ния s c an f ( ) позволяет избе жать этих проблем . Теперь подроб не е рассмотрим структуру этой программы . Кратко описать е е можно следую щим образом : инициали зировать пер еменную s um знач е нием О выдач а поль зов ателю приг л ашения на ввод считыв ание вхо дных данных пока вхо дно е з н ач ени е пр е дставл я е т со бой целое число , прибавить в ходно е знач ение к знач е нию s u m , выдать поль зователю приглаш е ние на ввод , з атем прочи тать следующий ввод по завершении ввода печ а т ь знач ения п ер еменной s um Фактичес ки это описание служит примером приме не ния псевдокода, который пред­ ставляет собой спос об упроще нного описания программы на естественном языке , ко­ торый в какой-то мере моделирует формы машинного языка . Псевдокод вес ьма поле­ зе н при разработке логики конкретной программы. Как только логика покажется вам безошибочной, можете приступать к переводу пс евдокода в программный код. Одно из пре имуществ пс евдокода заклю чается в том, что он позволяет с конце нтрировать усилия на отработке логики и организации программы, не беспокоясь до поры о том, как выразить иде и на языке программирования . Наприме р, в приведенном выше псе вдокоде блоки выделяются с помощью отступов, при этом неважно , что синтаксис языка С требует применения фигурных скобок. Еще одно достоинство псе вдокода с о­ стоит в том, что псе вдокод не привязан к конкретному языку программирования , бла­ годаря чему один и тот же псе вдокод может быть пе реведе н на различные языки. Как бы то ни б ыло, но пос кольку whi l e является циклом с предусловием, програм­ ма должна получить входное величину и проверить значение пере менной s tatus до того, как она войдет в тело цикла. Вот поче му функция s can f ( ) стоит в программе пе­ ред whi l e . Чтобы цикл продолжался , необходимо , чтобы внутри не го б ыл опе ратор чтения , который мог бы определить с остояние следую щей входной величины . Поэто­ му в конец цикла whi l e поставлен опе ратор s can f ( ) ; он подготавливает цикл к сле­ дую щей итерации. Вы можете рассматривать приведенный ниже псе вдокод как стан­ дартный формат цикла: получ ени е перв ого з н ач ения и выполнение его пров ерки пока про в ерка проходит успешно о брабо тать э то з н ач ени е получить сл едующе е знач ение Цикл с ч иты ва ния в стиле с Код в листинге 6 . 1 мог быть написан на языке программирования Pascal, BASIC или FORTRAN в соответствии с замыслом, сформулированном в приведенном выше псев­ докоде . Язык С , однако, обес пе чивает более короткую запись. Конструкция s t atus s can f ( " % l d " , &num ) ; whi l e ( s tatus 1) { / * действия , выпо лня емые в цикле * / s tatu s s c an f ( " l ld" , & num) ; = == = Управля ющие операторы: цикл ы 2 09 может быть заменена следующей : whi l e ( s can f ( " % l d " , &num) == 1 ) { / * действия , выпо лня емые в цикле * / Вторая форма ис пользует функцию s can f ( ) одновременно двумя различными спо­ собами. В о-первых, в результате вызова функции, если он заве ршился ус пешно, вход­ ное значение помещается в переме нную num. Во-вторых, значе ние , возвращаемое этой функцией (оно равно 1 или О и не является значе нием переменной num) управляет циклом . Поскольку условие цикла проверяется на каждой итерации, то и функция s can f ( ) вызывается на каждой ите рации, при этом будет введе но новое значение num и проведена новая прове рка . Другими словами, с войства синтаксиса языка С позволя­ ют заменить стандартный формат цикла следую щей компактной версией: пока получ ение и про в ерка знач е ния з а в ершае тся у спешно выполнить о бр а бо тку этого з н ач е ния А теперь рассмотрим оператор whil е более формально. О п ератор while О бщая форма оператора whil e имеет следую щий вид: whi l e ( выражение ) опера тор Часть опера тор может б ыть простым выраже нием , завершаю щимся точкой с запя­ той, либ о представлять собой составной оператор, заклю ченный в фигурные с кобки. В примерах, которые рассматривались до с их пор, в качестве выражения ис поль­ зовалис ь условные выражения , то е с ть выражением служило сравнение значений. В общем случае вы можете ис пользовать лю б ое выражение . Если выражение дает в ре­ зультате истину (или, в об ще м случае, принимает ненулевое значение ) , опера тор вы­ полняется один раз , после чего выраже ние проверяется снова . Цикл проверок и вы­ полне ний повторяется до тех пор , пока выражение не станет ложным (нуле м ) . Каждая последовательность проверки и выполне ния называется итерацие й (рис . 6 . 1 ). l while Следующий оператор true р rintf ( " Тр а -ля-ля-ля ! \n" ) ; Р и с . 6.1 . Структура -цикла whi l е 21 0 Гл ава 6 за вершен ие ци кла while Мы подошли к наиболе е существенному моменту, характеризующему циклы whi l e : при создании цикла whi l e должно быть предусмотрено нечто , что меняет значение прове ряемого выражения таким образом, чтоб ы оно в коне чном итоге становилось ложным . В противном случае цикл никогда не заканчивается . (На самом деле вы мо­ жете вос пользоваться операторами b r e a k и i f, чтобы завершить цикл, но пока о них ничего не знаете . ) Рас смотрим следую щий пример : index = 1 ,whi l e ( i ndex < 5 ) print f ( " Дo бpoe утро ! \ n " ) ; Предыдущий фрагмент программы печатает это бодрое сообщение бесконе чное число раз . Поче му так получается? Да потому, что ничто в цикле не меняет первона­ чального значения индекс а , равного 1 . Т епе рь рас смотрим такой фрагмент: index = 1 ; whi l e ( --index < 5 ) print f ( " Дo бpoe утро ! \ n " ) ; О н оказывается не намного лучше первого . Значе ние пе ременной index изме няет­ ся , но не в том направле нии ! Эта версия кода в конечном итоге приведет к окончанию цикла, но только после того, когда значение пере менной index упадет ниже мини­ мального отрицательного числа и станет максимальным положительным значением . (Программа tooЬig . с в главе 3 служит иллюстрацией того , что сложение макс ималь­ ного положительного числа с 1 обычно в результате приводит к появле нию отрица· тельного числа ; аналогично , вычитание 1 из минимального отрицательного числа да­ ет в ре зультате положительное значение . ) Когда цикл завершается ? В ажно понимать, что решение прервать выполнение цикла или продолжить его, принимается только в моме нт , когда заве ршена прове рка условия цикла. В качестве примера рассмотрим программу, представле нную в листинге 6 . 2 . листинг 6.2. Программа when . с 1 1 wh en . c - - ког д а цикл завершается ? #incl ude <s tdio . h > int main ( vo i d ) { int n = 5 ; whi l e ( n < 7 ) print f ( " n = % d\ n " , n ) ; n++ ; print f ( " T e n ep ь n = % d\ n " , n ) ; printf ( " Цикл з ав ерше н . \ n " ) ; r e turn О ; 1 1 строка 7 1 1 строка 1 0 1 1 строка 1 1 Управля ющие операторы: цикл ы 21 1 В результате выполне ния программы , показанной в листинге 6 . 2 , получаются следую щие выходные данны е : n = 5 Т е перь n = 6 n = 6 Т е перь n = 7 Цикл з ав ершен . Переменная n сначала получает значение 7 в строке 1 0 во время выполнения вто­ рой ите рации цикла. Однако программа на этом не завершается . О на все го лишь за­ вершает очередную итерацию (строка 1 1 ) и выходит из цикла только после того , как условное выражение в строке 7 будет прове ре но в третий раз. (Переменная n прини­ мает значе ние 5 во время первой прове рки и 6 во вре мя второй.) - оператор while: цикл с п редусловием Цикл whil e это условный цикл, использующий входное условие (или предусловие ) . Цикл называется "условным", поскольку выполнение его операторной части зависит от условия, представленного условным выражением, таким как, например, ( i ndex < 5 ) . Это выражение представляет собой предусловие , поскольку оно должно быть выполне­ но , прежде чем управление будет передано телу цикла. В ситуациях подобного рода управление никогда не войдет в тело цикла, так как условие ложно с самого на чала: index = 1 0 ; whi l e ( i ndex++ < 5 ) print f ( "Удачного дня ! \ n " ) ; - Поменяе м пе рвую строку на index = З ; и цикл начнет выполняться . си нта кси ческие особен ности При использовании оператора whi l e все гда следует иметь в виду, что в цикл в ка­ честве е го с оставной части, расположенной не посредственно за проверяемым усло­ вием, может входить только один оператор, простой или составной. Отступы от нача­ ла строки предназначены для помощи читателю , а не для компьютера . Листинг 6 .3 демонстрирует, что может произойти, если вы забудете об этом. Листинг 6 . 3 . Программа whi1el . с / * whi l e l . c -- пр авил ь н о р а с ставляйте фиг урные ско бки * / / * Неправильное кодировани е може т стать причиной появления бесконечных циклов* / #incl ude <s tdio . h > int main ( vo i d ) { int n = О ; whi l e ( n < 3 ) print f ( " n р авно % d\ n " , n ) ; n++ ; p r i nt f ( " Э тo в с е , на ч то способн а данная про г р амма\ n " ) ; r e turn О ; 21 2 Гл ава б Программа, представленная в листинге 6.3, генерирует следующие выходные данные: n р авно n р авно n р авно n р авно n р авно о о о о о ( . . . и так далее , пока вы каким-то радикальным способом не остановите ее выполнение . ) Хотя в этом примере опе ратор n++ ; вынесен в отдельную строку, он не заключен в фигурные скобки вме сте с предшествую щим оператором. Поэтому в с остав цикла вхо­ дит только оператор печати, который следует не посредственно за проверяемым усло­ вием. Переменная n никогда не изменитс я , условие n < 3 навс е гда останется истин­ ным, и вы получите цикл , который будет продолжать пе чатать известие о том, что n равно О , пока вы не прервете выполнение программы . Это пример бес конечного цик­ ла, из которого нельзя выйти без постороннего вмешательства . Всегда помните , что сам по себе оператор whi l e , даже е сли в нем используется с о­ ставной оператор, с позиций с интаксиса расс матривается как один оператор. Такой оператор выполняет все , что находится справа от клю чевого слова whi l e до первой точки с запятой, или в случае ис пользования составного опе ратора, до закрываю щей фигурной с кобки. Расставляя точки с запятой, соблюдайте осторожность. В качестве приме ра рас­ смотрим программу в листинге 6.4. листинг 6.4. Программа whi1e2 _ с / * whi l e 2 . c - - nр авил ь н о р а с ставляйте точки с з апя той * / #incl ude <s tdio . h > int main ( vo i d ) { int n = О ; whi l e ( n + + < 3 ) ; / * с трока 7 * / / * с трока 8 * / print f ( " n р авно % d\ n " , n ) ; p r i nt f ( " Э тo в с е , ч т о може т сделать данная прогр амма . \ n " ) ; r e turn О ; В результате выполнения программы , показанной в листинге 6.4, получены следую щие результаты : n р авно 4 Это в с е , ч то може т сделать данная прогр амма . Как уже было сказано выш е , цикл заканчивается пе рвым оператором, простым или составным , который следует не посредственно за проверяемым условием. Поскольку в строке 7 точка с запятой следует с разу за прове ряемым условие м, цикл заканчивается в этом месте , поскольку отдельно стоящая точка с запятой расс матривается как опе­ ратор. О ператор пе чати в строке 8 не относ ится к циклу, следовательно , значение n обновляется на каждой итерации, однако печать выполняется только по выходу из цикла. Управля ющие операторы: цикл ы 213 В расс матривае мом примере после проверя е мого условия следует пустой опе ратор, то есть оператор, который не выполняет никаких действий. Время от вре мени про­ граммисты наме ре нно используют оператор whi l e с пустым оператором как време н­ ную задержку или в с вязи с тем , что вся поле зная работа выполняется во вре мя про­ верки условия . Наприме р, предположим, что вы хотите пропустить ввод до первого символа , который не является пробелом или цифрой. Вы можете вос пользоваться та­ ким циклом : whi l e ( s can f ( " % d" , & num) == 1 ) / * пропустить ввод целого числа * / Поскольку функция s can f ( ) считывает целое число , она возвращает 1 , и цикл про­ должается . О братите внимание , что для наглядности мы поместили точку с запятой (пустой опе ратор) не в первой строке, а в строке ниже . Это повышает удобочитае­ мость те кста программы и в то же время говорит о том , что пустой опе ратор вклю чен в программу не случайно . Еще лучше пользоваться в таких случаях опе ратором conti nue, который будем рассматриваться в следую щей главе . Ч то бол ь ш е: и сп ол ьзован ие операци й и выражен ий отнош ен ия Поскольку правильность выполнения цикла whi l e часто зависит от проверяемых выражений, с помощью которых выполняются сравнения , эти выражения заслужива­ ют пристального внимания . Эти выражения получили название выражений отноше­ ния , а опе рации, в которых они появляются , называются опе рациями отношения . Вы уже пользовались нес колькими такими опе рациями, а в табл. 6.1 приводится полный список опе раций отношения в С. Эта таблица охватывает фактически все возможные числовые отношения . (Числа, даже если это комплексные числа , не так сложны , как люди со всеми их комплексами . ) Таб.nица 6.1 . операции отношений Опер ац ия Значение < Меньше <= Меньше или равно Равно >= Больше или равно > Больше != Не равно О пе рации отноше ний используются для построения выраже ний отноше ния , упот­ ребляе мых в операторах whi l e и в других операторах языка С , которые мы будем изу­ чать позже . Эти операторы проверяют , является ли конкретное выражение истинным или ложным. В от три не с вязанных между с обой опе ратора , соде ржащие примеры выраже ний отноше ний. Мы наде емся, что их с мысл должен быть понятен. 21 4 Гл ава б whi l e ( n umЬ er < 6 ) { print f ( " B в e дeннo e число оч е н ь мало . \ n " ) ; s can f ( " % d" , &numЬ e r ) ; whi l e ( ch ! = $ ) { count++ ; s can f ( " % c " , & c h ) ; ' ' whi l e ( s can f ( " % f " , & num) s um = s um + num ; 1) Обратите внимание на второй цикл - в условных выражениях моrуг ис пользовать­ ся также и символы . Для сравнений применяются их машинные коды (которыми, как мы полагаем, были коды ASC II) . Однако вы не можете ис пользовать операции отно­ ше ний для сравнения строк. В главе 1 1 вы узнаете , какие языковые средства использу­ ется для сравнения строк. О пе рации отношений моrуг также приме няться и к числам с плавающей запятой. Однако не обходимо иметь в виду, что при с равнении чисел с плавающей запятой можно пользоваться только операциями < и >. Это объяс няется тем , что ошиб ки ок­ ругления моrуг приве сти к тому, что числа окажутся не равными, хотя по логике про­ граммы они должны быть равны . Например, вполне очевидно , что произведение чи­ сел 3 и 1 /3 равно 1 .0 . Но если представить 1 / 3 в виде де сятичной дроби с шестью значащими числами, то произведением будет .999999, что в точности не равно 1 . Функция fab s ( ) , объявле нная в заголовочном файле math . h, может оказаться весьма полезной при проверке условий, в которых используются числа с плаваю щей запятой. Эта функция возвращает аб солютное значение величины с плавающей запятой, то есть, значение без алге браического знака . Например, вы можете проверить, насколь­ ко некоторое число близко к желаемому результату, используя код , подобный пока­ занному в листинге 6 . 5 . листинг 6.5. Программа cnpflt . с 1 1 cmp flt . c - - ср авнени е чисел с плавающей з апя той #incl ude <math . h> #incl ude <s tdio . h > int main ( vo i d ) { co n s t do uЫ e ANSWER = 3 . 1 4 1 5 9 ; do uЫ e r e spons e ,· printf ( " Kaкoвo знач е ние числа p i ? \ n " ) ; s c an f ( " % l f " , & r e spons e ) ; whi l e ( f ab s ( r e spons e - ANSWE R ) > 0 . 0 0 0 1 ) { print f ( " В в е дите знач ение пов торно ! \ n " ) ; s can f ( " % l f " , & r e s pons e ) ; printf ( " Tp e бyeмaя точно с т ь достигнута ! \ n " ) ; r e turn О ; Управля ющие операторы: цикл ы 21 5 Этот цикл продолжает уточнять ответ до тех пор , пока разность между ответом и правильным значением не окажется в пределах 0.000 1 : Каково з н ач ени е числ а pi ? 3 . 14 Вв едите знач ение пов торно ! 3 . 1416 Тр е буемая точность достиг нута ! Каждое условное выражение получает оценку "истина" (true) или "ложь" ( fal s e ) (но никогда " может быть" ) . При этом возникает инте ре сный вопро с . Что та кое исти на ? Ответ на этот извечный вопрос вы можете получить, по крайне й мере , когда дело кас ается языка С . Напомним , что выраже ние в С всегда имеет значе ние . Это утве р­ ждение остается в с иле и для выраже ний отношения , как показывает пример , пред· ставленный в листинге 6 . 6 . В этом примере на печать выводятся значения двух выра· же ний отноше ния , одно из которых истинное , а другое - ложное . листинг 6.6. Программа t_and_f . с / * t апd f . c и с тинные и ложные выр аже ния в я зыке С * / #incl ude <s tdio . h > int main ( vo i d ) { int true val , fal s e val ; -- tr ue_val = ( 1 0 > 2 ) ; / * знач ение истинного о тношения * / / * знач ения ложног о о тношения * / fal s e val = ( 1 0 == 2 ) ; p r i nt f ( " true = % d ; f al s e = % d \ n " , tr ue val , fal s e val ) ; r e turn О ; Код в листинге 6 .6 прис ваивает значе ния двух выраже ний отноше ния двум пе ре· менным. Во изб ежание недоразумений переменной true _ val прис ваивается значение истинного отношения , а переменной fal s e_val - значение ложного отношения . Вы· полнив эту программу, получаем следую щий простой результат: tr ue = 1 ; fal s e = О Так вот в чем дело ! В языке С истинное выраже ние имеет значе ние 1 , а ложное выраже ние - О . И действительно , некоторые программы на С ис пользуют следую щую конструкцию для циклов, которые по замыслу их авторов должны выполняться , по­ скольку 1 вс егда означает tru e : whi l e ( 1 ) { 21 6 Гл ава б ка кой еще может быть истина ? Если вы можете использовать 1 или О в каче стве проверяе мого условия в операторе whi l e , можете ли вы приме нять для этих целей другие числа? Если да , то что при этом происходит? Давайте немного поэкс периментируе м с программой из листинга 6 . 7 . листинг 6.7. Программа truth . c 1 1 tr uth . c - - какие з н ач е ния обознач ают истину ? #incl ude <s tdio . h > int main ( vo i d ) { int n = 3 ; whi l e ( n ) print f ( " % 2 d есть true \ n " , n- - ) ; printf ( " % 2 d е с ть fal s e \ n " , n ) ; n = -3 ; whi l e ( n ) print f ( " % 2 d есть true \ n " , n+ + ) ; printf ( " % 2 d е с ть fal s e \ n " , n ) ; r e turn О ; Получае м следую щие результаты : 3 2 1 о -3 -2 -1 о есть true есть true есть true есть fal s e есть true есть true есть true есть fal s e Первый цикл выполняется , когда пе ременная n принимает значения 3 , 2 и 1 , но прекращает выполняться , как только n становится равной О . Аналогично , второй цикл выполняется, когда переме нная n принимает значения - 3 , -2 и - 1 , но прекращает вы· полняться , как только n становится равной О . В общих словах, все ненулевые значения расс матриваются как истинные (tru e ) , и только О с читается ложным ( fal s e ) . Язык С предлагает довольно-таки широкое толкование истины ! С другой стороны , можно утверждать, что цикл whil e выполняется до тех пор , по­ ка вычисле ние е го проверяе мого условия дает в результате не нулевое значение . Это обстоятельство позволяет пере вести проверяе мое условие на числовую основу вместо использования значений " истина" и "ложь" . Не упускайте из виду, что условное выра· же ние принимает значение 1 , если оно истинно , и О , если ложно , таким образом , все выраже ния этого типа являются числовыми. Многие программисты, работающие на С, ис пользуют это свойство условий про­ верки. Например, конструкцию whil e ( go ats ! = О ) можно заменить на whi l e ( go ats ) , поскольку как выраже ние ( go a t s ! = О ) , так и выражение ( go ats ) принимают значе· ние О, или fal s e , только когда пе ременная goats равна О . Управля ющие операторы: цикл ы 217 Первая форма, по-видимому, б олее понятна тем, кто только что приступил к изу­ че нию языка , в то время как вторая форма представляет собой идиому, которая ис­ пользуется профе ссиональными программистами, работаю щими на С. В ам придется не много поэкс периментировать с формой whi l e ( go a t s ) , чтобы она впоследствии казалас ь вполне есте стве нной. Трудности при употреблен и и понятия ·· исти на ·· Достаточно либеральное толкование языком С понятия истины чревато осложне­ ниями. Например, внесем одно малозаметное на первый взгляд изме не ние в програм­ му, представле нную в листинге 6 . 1 , и получим программу, показанную в листинге 6 . 8 . Листинг 6.8. Программа t:rouЫe . с 1 1 trouЫ e . c - - н епр авильное испол ь зов ание знака 11 приводит к в о з никно в е нию бе скон ечных циклов #incl ude <s tdio . h > int main ( vo i d ) { l o ng num ; l o ng s um = 0 1 ; int s tatus ; printf ( " Bв eди т e целое число для после дующег о суммирования " ) ; printf ( " (или q для з ав ерш ения программы) : " ) ; s t atus = s can f ( " % l d " , &num ) ; whi l e ( s tatus = 1 ) { s um = s um + num ; printf ( "Bвeдитe следующее целое число (или q для завершения программы ) : " ) ; s tatu s = s c an f ( " % ld" , & num) ; printf ( " Cyммa введенных целых чисел р авна % ld . \ n " , s um ) ; r e turn О ; В ыполнение программы из листинга 6.8 дает следую щие выходные результаты: Вв едите целое число для п о следующег о суммир ов ания (или q для з ав ершения пр огр аммы) : 2 0 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : 5 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : 3 0 Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : q Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : Вв едите следующе е целое число ( или q для з ав ерше ния пр огр аммы) : ( . . . и так далее , пока вы каким-то радикальным с пособом не остановите выполне­ ние программы . ) Столь неутешительные результаты вызвало изменение , внесенное в проверяемое условие оператора whi l e , когда выраже ние s t atus == 1 было заме не но выражением 21 8 Гл ава б s tatu s = 1 . Второй оператор - это опе ратор прис ваивания , который назначает пе­ ременной s tatus значение 1 . Более того , значе ние оператора присваивания пред­ ставляет с обой значе ние его левой части, так что s tatus = 1 имеет то же числовое значение , равное 1 . Следовательно, для вс ех практиче ских целей ис пользование этого цикла whi l e дает тот же результат, что и оператор whi l e ( 1 ) ; то е сть, это бесконеч­ ный цикл. Когда вы вводите q, пе ременная s tatus получает значение О , однако во вре мя прове рки конца цикла переме нная s tatus получает значение 1 и инициирует выполнение оче редной ите рации. Вы, скорее вс его , захотите узнать, поче му из-за того , что программа выполняется в цикле, пользователь не имеет возможности ввести дальнейшие входные данные после того , как он ввел с имвол q. Когда функции s c an f ( ) не удается прочитать входные дан­ ные в той форм е , на которую она настроена, она оставляет этот не с о ответствую щий ее требованиям ввод на ме сте , чтобы прочитать е го в следую щий раз . Когда s c an f ( ) предпринимает попытку читать символ q как целое число и терпит при этом неудачу, она оставляет q на месте . Во время выполнения следую щей итерации цикла функция s can f ( ) пытается читать в том ме сте , где она читала последний раз , а име нно , там, где остался с имвол q. И снова s can f ( ) не может прочитать символ q как целое число , так что этот приме р не только устанавливает бесконечный цикл, он также демонстрирует цикл бесконе чных неудач, словом, печальный результат. Хорошо еще, что компьюте­ ры пока еще лише ны чувств. Использовать глупые команды компьютеру не лучше и не хуже попыток успе шно предсказывать положение на фондовой б ирже в течение сле­ дую щих 10 лет. Не употребляйте знак = вместо ==. В некоторых языках программирования (напри­ мер, в BASIC ) используется один и тот же символ для представления операции присваи­ вания и проверки на равенство , хотя эти операции совершенно разные (рис . 6.2) . r---------------1 --------------r 1 Сравнение canoes = 5 == проверяет, равно значение переменной canoe s величине 5 Присваивание значений : ��·::� �:·: : : : : : : ;.:==�:::"" 5 Ри с. 6. 2 . Опсра'Ция отношения == и опсра'Ция присваивания = О пе рация присваивания устанавливает значение переменной, указанной слева от знака опе рации. В то же время операция проверки на раве нство прове ряет, существу­ ет ли раве нство между левой и правой частями операции. О на не меняет значения пе­ ременной, указанной в левой части операции, если таковая там име ется . Расс мотрим соответствую щий пример : cano e s = 5 cano e s == 5 �Присв аив а е т переменной cano e s знач ение 5 �Провер я е т , р авно ли знач ение переменной cano e s величине 5 Управля ющие операторы: цикл ы 219 С облюдайте осторожность при выборе операции. Компилятор разре шит вам ис­ пользовать неправильную форму, но тогда вы получите совс е м не тот результат, на который рассчитываете . (Тем не менее , такое множество программистов так часто не правильно применяло опе рацию что б ольшая часть компиляторов выводит на эк­ ран предупреждающие сообщения - вполне достаточно , чтобы отбить охоту е ю поль­ зоваться . ) Если одна из сравниваемых величин является константой, вы можете по­ местить ее слева от знака операции сравнения , чтобы помочь выявить ошибку: = = cano e s 5 5 can o e s == , �Синтак сич е ская ошибка �Пров еря е т , р авно ли знач ение переменной cano e s величине 5 Дело в том, что нельзя присваивать значение константе , и в с илу этого компилятор рассматривает такую опе рацию присваивания как синтаксическую ошибку. Многие професс иональные программисты первой ставят константу в выраже ниях проверки на предмет раве нства . Подводя итоги, отметим , что опе рации отношения используются для создания ус­ ловных выражений. Выражения отношения получают значение 1, е сли они истинны, и О, если ложны. В опе раторах (таких как whi l e и i f ) , в которых обычно присутству­ ют условные выражения для определения условий проверки, могут ис пользоваться любые выражения , при этом ненулевые выражения интерпретируются как " истина" , а нуле вые - как "ложь" . Нов ы й ти п Bool Пере менные , предназначенные для представления значе ний tru e и fal s e , тради­ ционно в языке С имеют тип int. Специально для переме нных этого вида стандарт С99 вводит тип _Bool . Тип получил свое название в честь Джорджа Буля (George Boole ), английского математика , который разработал алгебраиче с кую с истему, позво­ ляю щую формулировать и ре шать логичес кие задачи. В программировании перемен­ ные , представляю щие значе ния tru e и fal s e , известны как булевские (Boole a n ) , таким образом _ Bool в языке С является име не м типа для упомянутых переме нных. Пе ре­ менная _Bool может принимать только значе ния 1 (истина ) и О (ложь). Если вы попы­ таете с ь присвоить ненулевое числовое значение пе ременной типа _B o o l , эта пе ре­ менная получит значение 1, которое отражает тот факт , что С трактует любое ненуле­ вое значе ние как tr u e . В программе , показанной в листинге 6 . 9 , исправлено условие проверки, ис пользо­ ванное в программе из листинга 6 . 8 , и переменная s tatus типа i nt заме не на пе ре­ менной input_i s_good типа _Boo l . Присвоение булевским пе ре менным имен, из ко­ торых ясно, какое значение эта переменная принимает, пре вратилось в установив­ шуюся практику. Листинг 6.9. Программа Ьоо1еаn . с 1 1 bool e an . c и споль з о в ани е переменной типа #incl ude <s tdio . h > int main ( vo i d ) { l o ng num ; l o ng s um 01; Bool input_i s _good; - - = Bool 220 Гл ава б printf ( " Bв eди т e цело е число для после дующег о суммирования " ) ; printf ( " (или q для з ав ерш ения программы) : " ) ; input_i s _good = ( s ca n f ( " % l d " , & num) == 1 ) ; whi l e ( i nput_i s_goo d ) { s um = s um + num ; рriпtf ( "Введите следующее целое число (или q для завершения программы ) : " ) ; input_i s_good = ( s caп f ( " % ld" , &пum ) == 1 ) ; printf ( " Cyммa введенных целых чисел р авна % ld . \ п " , s um ) ; r e turn О ; Обратите внимание на то , как программа прис ваивает результат с равнения пе ре· менной: input_i s _good = ( s ca n f ( " % l d " , & num) == 1 ) ; Это присваивание имеет смысл, поскольку опе рация == возвращает либо 1 , либо О . По сути дела , круглые с кобки, заклю чаю щие в себе выражение == , не нужны, посколь­ ку опе рация == имеет более высокий приоритет, чем операция =; тем не менее , они способны повысить удобочитаемость кода. Заметьте также , насколько изме нение имени переменной делает проверку цикла б олее понятной: whi l e ( i nput_i s_goo d ) Стандарт С99 предлагает для использования заголовочный файл s tdЬ ool . h. В этом заголовочном файле bool определяется как альтернативное имя для типа _Bool , а также tru e и fal s e как символичес кие константы со значе ниями 1 и О . Вклю чение этого заголовочного файла позволяет писать код , совме стимыq с программами на языке С + + , в котором b o o l , true и fal s e представляют собой клю че вые слова . Если ваша система не поддерживает тип _Boo l , можете заменить _Bool типом int, и этот пример будет работать так же. Приоритеты опера ци й отношен ия Приоритет операций отношения ниже приоритета арифметиче ских операций, + и - , но выш е , че м у операции присваивания . Это значит, что , например, х > у + 2 означает то же с амое, что и х > (у + 2 ) Это также значит, что х = у > 2 эквивале нтно х = (у > 2 ) Иначе говоря , пе ременной х присваивается 1 , е сли у больше 2 , и О случае; переменной х не присваивается значение у . - в противном Управля ющие операторы: цикл ы 221 О пе рации отношения имеют б ольший приоритет , чем опе рации присваивания . Поэтому x_bigger = х > у ; означает х bigger = (х > у) ; О пе рации отноше ния по своему приоритету делятся на две группы. • Группа с выс оким приоритетом: < <= > >= • Группа с низким приоритето м : == ! = Подобно большей части других опе раций, опе рации отношения выполняется слева направо . В с вязи с этим конструкция ех ! = wy e == z e e эквивалентна ( е х ! = wye ) == z e e С начала С прове ряет, имеет л и ме сто неравенство значений переме нных е х и wye . Затем полученное значе ние , которое может быть равно 1 или О ("истина" или "ложь" ) , сравнивается со значением z e e . М ы н е намерены использовать конструкции подобно­ го рода, но с читае м своим долгом подчеркнуть, что они возможны . В табл. 6.2 перечислены приоритеты операций, изученных к данному моме нту. В справочном разделе П (приложение Б) приведены вс е опе рации, упорядоченные в порядке убывания приоритетов . ТабJJица 6.2. Приоритет операций Опер ации (в tЮрядке убьt вапия при1'f'и тетов) Ассоциативность () Слева направо - + ++ -- s i zeo f ( тип) (все унарные ) Справа налево " / % Слева направо +- Слева направо < > <= >= Слева направо != Слева направо Справа налево Свод к а; оп ерато р whi le Кл ючевое слово: whi l e Комментарии о бще го характера: Опе ратор whi l e определяет опе рации, которые повторя ются до тех пор, пока прове­ ряемое вы ражение не станет ложны м или ра вны м нулю. Оператор whi l e представляет собой цикл с предусло вие м ; это знач ит , что решение еще одной итера ц и и цикла прини­ мается перед очередны м проходом цикла . Следо вател ьно , вполне возможно, что не 222 Гл ава 6 будет вы пол нен ни од ин проход цикла. Операторная часть ц и кл а может быть л ибо про­ ст ы м , л ибо соста вн ы м операторо м. Форма за п ис и: wh i l e ( выражение ) опера тор Ч аст ь опера тор по вто ряется до тех пор, пока выражение не станет ложн ы м ил и рав­ ным О . П ример ы : wh i l e ( n ++ < 1 0 0 ) print f ( " % d % d\ n " , n , 2 • n + 1 ) ; wh i l e ( f argo < 1 0 0 0 ) { fargo = far go + s tep ; s tep = 2 • s tep ; / • одиноч ный опер атор • / / • со ставной оператор • / Свод к а: о п ераци и отн ошен и я и услов н ые выражен и я О п ера ц и и отно ш ения : Каждая опера ция отно шения сравни вает значение в ее лево й част и со зна чением в ее правой част и: < <= >= > != меньше меньше ил и равно ра вно бол ьше ил и равно бол ьше не равно Усл овнь 1 е выраже ния : П ростое усло вное вы ражение состоит из зна ка о пе рации отно ше ния и операндо в, рас­ пол оженных сл ева и спра ва от нее. Есл и вы ражение ист инно, то значен ие усло вного вы ражения равно 1 . Есл и отно шение ложно, то значение усл овного вы ражения ра вно о . П ример ы : 5 > 2 (2 + а) == а истинно и принимает значен ие 1 . ложно и при ни мает значен ие О . Н еоп ре дел ен н ы е ц и к л ы и ц и к л ы со с ч ет ч и ком Некото рые из примеров цикла whil e представля ют собой образцы так называемых не определе нных циклов. Это означает , что вы заранее не можете знать, сколько раз цикл будет выполнен, пре жде че м выраже ние станет ложным. Наприме р, в про гра м­ ме, представленной в листинге 6 . 1 , использован инте рактивный цикл для суммиро ва­ ния целых чисел, и вы не знаете , с колько чис ел будет введено. В то же вре мя примеры представляют собой 'ЦUКЛъt со счетчих:ом. Эти циклы выполня ют заранее известное ко­ личе ство ите раций. Управля ющие операторы: цикл ы 223 В листинге 6 . 1 0 приводится пример оператора цикла whi l e со с четчико м . листинг 6.1 О . Проrрамма sweetiel . с / / sweeti e l . c - - цикл со сч е тчиком #incl ude <s tdio . h > int main ( vo i d ) { co n s t int NUМB ER 22; int count = 1 ; whi l e ( count <= NUМB E R ) { print f ( " Буд ь те моим Валентином ! \ n " ) ; count++ ; 1 1 инициализ ация / ! про в ерка 1 1 дей ствие / ! о бновлени е знач ения count r e turn О И хотя форма , использованная в листинге 6 . 1 0 , работает прекрасно, те м не менее, это не с амый лучший выбор в подоб ной ситуации, поскольку действия , определяющие цикл, не собраны воедино . Рассмотрим этот вопрос боле е подробно. Чтобы организовать цикл, который должен быть повторе н заданное количе ство раз , необходимо выполнить следую щие три действия : 1 . Инициализировать счетчик. 2. Сравнить показание счетчика с некоторой ограниченной величиной. 3. Об новить значе ние счетчика после каждого прохода цикла. Условие цикла whil e обес пе чивает выполнение сравне ния . О пе рация инкреме нта приводит к обновле нию значения счетчика . В программе из листинга 6 . 1 0 инкремен­ тирование счетчика выполняется по заве ршении ите рации. Такой вариант исклю чает возможность случайного увеличения значе ния счетчика . Следовательно , желательно совме стить де йствия по прове рке и об новле нию значения счетчика в одном выраже­ нии, воспользовавшись конструкцией count++ <= NUМBER, однако инициализация счет· чика все е ще проводится за пределами цикла, при этом сохраняется ве роятность того, что мы забудем инициализировать с четчик. О пыт нас учит: то , что может случиться, в конечном итоге когда·нибудь случается , поэтому мы должны хорошо изучить управ· ляю щий оператор, лишенный этих проблем. Ц и кл f or Цикл f o r с обирает вс е три указанных выше де йствия (инициализация , прове рка и об новле ние ) в одном месте. В ос пользовавшись циклом for , вы с можете заменить пре· дыдущую программу кодо м , показанным в листинге 6 . 1 1 . Листинг 6.1 1 . Проrрамма sweetie2 . с 1 1 swe eti e 2 . c - - цикл со сч е тчиком , испо л ь зующий for #incl ude <s tdio . h > int main ( vo i d ) 224 Гл ава б co n s t int NUМB ER int count ; 22; for ( count = 1 ; count <= NUМBER ; count++ ) print f ( " Буд ь те моим Валентином ! \ n " ) ; r e turn О ; В круглых с коб ках, следую щих за ключевым словом for , содержатся три выраже­ ния , отделенные друг от друга двумя точками с запятой. В пе рвом выраже нии выпол­ няется инициализация . Это делается только один раз , когда цикл for начинает вы­ полняться . Второе выражение - прове ряемое условие , оно вычисляется перед каж­ дым возможным выполнением цикла. Когда выражение получает значе ние fal s e (а именно , когда значе ние счетчика count становится больше величины NUМBER) , вы­ полне ние цикла пре кращается . Третье выражение , осуществляющее изменение или об новле ние , вычисляется в конце каждой итерации. Программа из листинга 6 . 1 0 ис­ пользует его для увеличе ния значе ния пе ременной count, однако, этим его ис пользо­ вание не ограничивается . О пе ратор for завершается выполне ние м простого или с о­ ставного оператора , следую щего за клю чевым словом for . Каждое из трех выражений является полным, так что любой побочный эффе кт в управляющем выражении, такой как увеличение значения той или иной пе ременной, происходит до вычисле ния дру­ гого выраже ния . На рис . 6.3 показана структура цикла for . yl. l for Инициализир овать выражение один раз перед тем , как начнется выполнение цикла --- count- ' Это выражение вычисляется в конце каждой итерации цикла false count++ ; printf { "Бyдьтe моим Валентином ! \n" ) ; Ри с. 6.3. Структура ?&Uкла for В качестве еще одного приме ра программа, представленная в листинге 6 . 1 2 , ис­ пользует цикл for для печати таблицы кубов целых чис ел. листинг 6.1 2. Проrрамма for_сuЬе . с / * for cub e . c - - исполь зов ание цикла for для постро ения т аблицы кубов целых чисел * / #incl ude <s tdio . h > int main ( vo i d ) Управля ющие операторы: цикл ы 225 int num ; printf ( " n n в кубе \ n " ) ; 1 ; num <= 6 ; num++ ) for ( num print f ( " % 5d % 5d\ n " , num , num* num* num) ; r e turn О ; Программа в листинге 6 . 1 2 пе чатает целые числа от 1 до и их куб ы . n 1 2 3 4 5 6 n в кубе 1 8 27 64 125 216 Первая строка цикла for сразу предоставляет всю информацию о параметрах цик­ ла: начальное значе ние переме нной num, конечное значение пе ременной num и вели­ чину, на которую num увеличивается при каждом проходе цикла. Использование ци кла for с целью пов ы ше н ия гибкости Хотя цикл for во многом похож на цикл DO в языке FORTRAN, на цикл FOR в языке Pasc al и на цикл F OR . . . NEXT в языке BASIC, он обладает гораздо большей гибкостью , чем любой из выше перечисленных циклов. Эта гиб кость обусловлена возможностями использования трех выраже ний в спе цификации цикла for . В приведенных выше приме рах пе рвое выраже ние служило для инициализации с четчика , следую ще е выра­ же ние - для задания граничных значений счетчика и третье выражение - для увели­ че ния значе ния счетчика на 1 . Когда оператор for языка С используется подобным образом, он во многом аналогичен упомянутым выше операторам. В то же время он обладает многими другими возможностями, укажем девять из них: • Вы можете вос пользоваться операцией де креме нта для отсчета цикла в порядке убывания , а не в порядке возрастания значе ний с четчика : / * Про г р амма for_down . c * / #include < s t dio . h> int main ( vo i d ) { int s e cs ; for ( s e c s = 5 ; s e cs > О ; s e cs - - ) print f ( " % d секунд ! \ n " , s e cs ) ; p r i nt f ( " E cть з ажиг ание ! \ n " ) ; r eturn О ; Ниже показан результат выполнения этой программы : 5 секунд ! 4 секунд ! 226 Гл ава б 3 секунд ! 2 секунд ! 1 секунд ! Есть з ажиг ание ! • При желании вы можете считать двойками, десятками и так дале е : / * Про г р амма f o r 1 3 s . c * / #include < s t dio . h> int main ( vo i d ) { int n ; / * кр атность сч ета 1 3 * / for ( n = 2 ; n < 6 0 ; n = n + 1 3 ) print f ( " % d \ n " , n ) ; r eturn О ; Значение пе ременной n на каждой ите рации увеличивается на 1 3 , на печать вы­ водятся следую щие выходные данные : 2 15 28 41 54 • Можно с читать по символам, а не по числам: /* Про г р амма for_char . c * / #include < s t dio . h> int main ( vo i d ) { char ch ; for ( ch = ' а ' ; ch <= ' z ' ; ch++ ) print f ( " Знач ение ASC I I для % с р авно % d . \ n " , ch , ch ) ; r eturn О ; Фрагме нт выходных данных имеет вид: Знач ение ASC I I для а р авно 9 7 . Знач ение ASC I I для ь р авно 9 8 . Знач ение ASC I I для х р авно 1 2 0 . Знач ение ASC I I для у р авно 1 2 1 . Знач ение ASC I I для z р авно 1 2 2 . Эта программа работает , так как с имволы хранятся в памяти в виде целых чи­ сел, так что ите рации во всех случаях используют целые числа . • Можно выполнять прове рку некоторого произвольного условия , а не только количества итераций. В программе for _ cub e вы можете заменить for ( n um = 1 ; num <= 6 ; num++ ) на for ( n um 1 ; num* num* num <= 2 1 6 ; num++ ) Управля ющие операторы: цикл ы 227 Вы можете воспользоваться этим прове ряемым условием, если больше озабоче­ ны тем, чтобы величина куба того или иного целого числа не превосходила не­ которого конкретного значения, а не подс четом числа ите раций. • Вы можете сделать так, чтоб ы некоторая величина возрастала не в арифмети­ че ской, а в ге ометриче ской прогрессии, то есть, вместо того, чтобы на каждом этапе цикла прибавлять фиксированную величину, вы можете умножать на фиксированное значение : / * Про г р амма for_g eo . c * / #include < s t dio . h> int main ( vo i d ) { douЫ e deЬt ; for ( debt = 1 0 0 . 0 ; debt < 1 5 0 . 0 ; debt = debt * 1 . 1 ) print f ( " T e п ep ь в аш долг равен $ % . 2 f . \ n " , deb t ) ; r eturn О ; В этом фрагменте программы значение пере менной deЬt умножается на 1 . 1 на каждом проходе цикла, что эквивалентно его увеличению каждый раз на 1 0 % . Выходные данные этой программы имеют вид: Т еперь Т еперь Т еперь Т еперь Т еперь • в аш Д О ЛГ р авен $ 1 0 0 . 0 0 . в аш Д О ЛГ р авен $ 1 1 0 . 0 0 . в аш Д О ЛГ р авен $ 1 2 1 . 0 0 . в аш Д О ЛГ р авен $ 1 3 3 . 1 0 . в аш Д О ЛГ р авен $ 1 4 6 . 4 1 . В качестве третьего выраже ния можно использовать любое корре ктно состав­ ле нное выраже ние . Какое значение вы бы не ввели, оно будет обновляться на каждой ите рации. /* Про г р амма for_wild . c * / #include < s t dio . h> int main ( vo i d ) { int х ; int у = 5 5 ; for ( х = 1 ; у < = 7 5 ; у = ( ++х * 5 ) + 5 0 ) print f ( " % 1 0 d % 1 0 d \ n " , х , у ) ; r eturn О ; В результате выполне ния этого цикла на печать выводятся значения перемен­ ной х и алге браичес кого выражения ++х * 5 + 5 0 . В ыходные данные имеют следую щий вид: 1 2 3 4 5 55 60 65 70 75 228 Гл ава б Об ратите внимание на то , что проверяются значе ния у, а не х. В каждом из трех выражений, управляющих раб отой цикла for , могут использоваться раз­ личные пере менные . (Следует также отметить, что хотя этот пример и состав­ лен правильно, он не может служить образом хорошего стиля программирова­ ния . Программа только выиграла бы в наглядности, если бы мы не с мешивали процесс обновле ния значе ний с алгебраичес кими вычисле ниями.) • Вы даже можете о пустить одно или не сколько выражений (но не забудьте при этом проставить точки с запятыми) . Не забудьте также включить в цикл тот или иной опе ратор, который в конечном итоге приведет к завершению цикла. /* Про г р амма for_none . c * / #include < s t dio . h> int main ( vo i d ) { int ans , n ; ans = 2 ; for ( n = 3 ; ans <= 2 5 ; ) ans = ans * n ; p r i nt f ( " n = % d ; ans = % d . \ n " , n , ans ) ; r eturn О ; В результате выполнения программы получаем следую щий результат: n = 3 ; ans = 5 4 . При выполнении этого цикла значение переме нной n остается равным 3 . Зна­ че ние переменной ans вначале равно 2 , затем оно увеличивается до 6 и 1 8 и в конечном итоге становится равным 5 4 . (Значение 1 8 меньше 2 5 , следовательно , в цикле for выполняется еще одна итерация, а 1 8 умножается 3 , что дает в ко­ не чном итоге 5 4 . ) Между прочим, пустое управляющее выраже ние в средней части спецификации цикла рас сматривается как истинное , следовательно , при­ водимый ниже цикл будет выполняться бесконечно : for ( ; ; ) printf ( " Я хочу , ч тобы ч то - то было сде лано . \ n " ) ; • Первое выражение не тре бует инициализации переменной. Это может быть одна из разновидносте й опе ратора print f ( ) . Необходимо только помнить, что первое выражение вычисляется или выполняется вс его лишь один раз , до того как начнут выполняться другие части цикла. /* Про г р амма for s how . c * / #include < s t dio . h> int main ( vo i d ) { int num = О ; for (рrint f ( " Продолжайте в водить числа ! \ n " ) ; num ! = 6 ; ) s can f ( " % d" , &num) ; printf ( " B o т то число , которое было нужно ! \ n " ) ; r eturn О ; Управля ющие операторы: цикл ы 229 В этом фрагменте первое число выводится н а печать все го лишь один раз , далее он продолжает принимать числа до тех пор, пока вы не введете число 6 : Продолжайте вводить числа ! 3 5 8 6 Вот то число , которое было нужно ! • Параметры выражений, входящих в спецификацию цикла, могут б ыть измене­ ны с помощью де йствий, выполняе мых внутри тела цикла. Наприме р, предпо­ ложим, что цикл определе н следую щим образом: for (n = 1 ; n < 1 0 0 0 0 ; n = n + delt a ) И если после нес кольких ите раций ваша программа придет к выводу, что зна­ че ние del ta слишком мало или слишком велико, оператор i f (см. главу 7) внут­ ри цикла может изме нить величину del t a . В интерактивной программе пользо­ ватель может изменить значение del ta в проце с с е выполнения цикла. Этот вид регулировки таит в себе определенную опасность, например, если пере менной del t a прис воить значение О , то это приведет вас (равно как и цикл) в никуда. Короче говоря , своб ода , которая предоставлена вам при выборе выражений, управляющих работой цикла for , позволяет этому циклу делать гораздо больше , чем просто выполнять фиксированное число опе раций. Эти возможности цикла for могут быть е ще б ольше рас ширены благодаря ис пользованию операций, которые мы вскоре обсудим. Св одка: оператор for Кл ючевое слово: for Комментарии о бще го характера: Опе ратор for ис пол ьзует три вы ражени я , упра вля ющие работой цикпа, которые отде­ ля ются друг от друга точ ка м и с запятой. В ы ражение иници ализ ация выч исляется тол ько один раз, до т о го, ка к будет вы полнен хотя бы один из операторов цикпа. Зате м вычисляется вы ражение проверка, и есл и оно истинно ( ил и не ра вно нул ю). то один ра з вы полняется тело цикпа. Далее вычисляется вы ражение о бновл ени е , после чего снова вычисляется вы ражение пров ерк а. Операто р for предст а вл я ет собой цикп с предусловием - решение , вы полнять в очередно й раз цикп ил и нет , принимается пе­ ред проходом цикпа. В с илу этого обстоятел ьства сущест вует вероятност ь того, что те­ ло ци кпа не будет вы полнено ни разу. Операторная част ь фо рмы может быть ка к про­ ст ы м , так и соста вны м оператором. Форма: for ( инициализ а ция; проверка ; о бновление ) опера тор Цикп повторяется до тех по р, пока вы ражение пров ерка не получит значение f al s e ил и не станет нул е м . П ример: for (n = О ; n < 1 0 ; n++ ) p r i nt f ( " % d % d \ n " , n, 2 • n + 1) ; 230 Гл ава 6 Д оп ол нительны е оп ераци и Q... _ при сваи ван ия · += -= *= /= '() • 1 1 1 1 В языке С имеется несколько операций присваивания. Важне йше й из них, несо­ мненно , является операция =, которая просто прис ваивает значение выражения, стояще го справа от знака операции, переме нной, стоящей слева от этого знака . Дру­ гие опе рации присваивания обновляют значения переменных. В запис и каждой такой операции имя пе ременной стоит слева от знака операции, а выражение - справа от знака. Переменной присваивается новое значе ние , равное ее старому значе нию , скор­ ректированному на величину выражения , стояще го справа . Точный результат коррек­ тировки завис ит от опе рации. Например: s co r e s + = 2 0 то же, что и s cor e s = s co r e s + 2 0 dime s = dimes - 2 dimes -= 2 то же, что и bunni e s * = 2 то же, что и time /= 2 . 7 3 то же, что и bunn i e s = bunni e s * 2 time = time / 2 . 7 3 r e duce % = 3 то же, что и r edu ce = r educe % 3 В представле нном выше пе речне использовалис ь простые числа, однако вполне можно применять и более сложные выражения: х *= 3 * у + 12 то же, что и х = х * (3 * у + 12) О пе рации присваивания , которые мы только что обсудили, обладают такими же приоритетами, что и операция =, то есть, меньшими, чем приоритет опе рации + или * . Низкий приоритет этих операций наглядно демонстрирует последний приме р, в котором 1 2 прибавляется к 3 * у до того , как результат сложения будет умножен на х . В овс е не обязательно пользоваться всеми этими формами. В то же время они от­ личаются большей компактностью и позволяют получать более эффективный машин­ ный код , чем боле е громоздкие формы записи. Сочетания различных операций при­ сваивания особенно полезны в тех случаях, когда вы пытаете сь "втис нуть" какие-то сложные выражения в спецификацию цикла f o r . О п ерация запято й О пе рация запятой повышает гиб кость цикла for , позволяя вклю чить в его с пеци­ фикацию сразу несколько инициализирую щих или корректирую щих выражений. На­ приме р, в листинге 6 .1 3 представлена программа, которая выводит на печать тарифы почтового обслуживания первого класса. (На моме нт написания этой книги тарифы составляли 37 центов за первую унцию и 23 це нта за каждую последую щую унцию пе­ ре сылаемого груза . ) Листинг 6.1 3. Программа po staqe . с 1 1 po s tage . c -- т арифы поч то вого о бслужи в ания перв о г о кла с с а #incl ude <s tdio . h > int main ( vo i d ) { 37 ; co n s t int F I R S T OZ Управля ющие операторы: цикл ы 231 co n s t int NEXT OZ = 2 3 ; int ounc e s , co s t ; printf ( " унции тари ф \ n " ) ; f o r ( oun ce s = l , co st=FIRST_OZ ; ounces < = 1 6 ; ounce s + + , co s t += NEXT_O Z ) print f ( " % 5d $ % 4 . 2 f\ n " , ounce s , co s t/ 1 0 0 . 0 ) ; r e turn О ; Первые пять строк выходных данных этой программы имеют вид : унции 1 2 3 4 т ариф $ 0 . 37 $ 0 . 60 $ 0 . 83 $1.06 В этой программе операция запятой используется в инициализирую щем и об нов­ ляю щем выражениях. Ее наличие в первом выраже нии приводит к инициализации переме нных oun c e s и co s t . Ее второе появление приводит к тому, что значение ounce s увеличивается на 1, а значение co s t увеличивается на 23 (значение константы NEXT _ OZ) на каждой ите рации. Все эти вычисления выполняются в с пецификации цикла for ( с м . рис 6.4) . i for ounces=l , cost=FIRST_02 ; ounces++ , false �4---� cost+=NEXT 1 02 true Сделать это Р ис. 6.4. Опсршция запятой и t& U КЛ for Приме нение опе рации запятой не ограничивается только циклами for , однако , именно здесь она чаще всего используется . Эта операция имеет еще два характерных свойства . Во-пе рвых, она гарантирует , что выраже ния , которые она отделяет одно от другого , вычисляются в порядке слева направо . (Другими словами, запятая является точкой последовательности, это значит, что вс е побочные эффе кты слева от запятой проявятся до того , как программа перейдет вправо от запятой.) Отсюда следует , что переме нная ounces инициализируется раньше пе ременной c o s t . В рассматриваемом примере порядок не имеет значе ния , однако он важен в тех случаях, когда выражение для co s t содержит переменную oun c e s . 232 Гл ава б Расс мотрим, например, такое выражение : ounces++ , co s t = oun c e s * F I RST_OZ В данном случае значение переменной ounce s увеличивается на 1, после чего но­ вое значе ние oun c e s ис пользуется во втором подвыражении. Запятая, будучи точкой последовательности, гарантирует, что поб очные эффекты ле вого подвыраже ния про­ явятся перед тем, как будет вычисле но правое подвыражение . В о-вторых, значе ние все го выраже ния , с одержащего операцию запятой, является значение м выражения в правой части. Результат опе ратора х = (у = З, (z = ++у + 2 ) + 5 ) ; состоит в том, что сначала 3 присваивается пере менной у, после чего значение у уве­ личивается до 4 , а зате м 2 прибавляется к 4 , получающееся при этом значе ние 6 при­ сваивается переменной z, далее 5 прибавляется к z, и в завершение значе ние пе ре­ менной z, равное к этому моменту 1 1 , прис ваивается пере менной х. Объяс не ние , за­ чем вс е это надо делать, не входит в задачу данной книги. С другой стороны, предположим, что вы по не осторожности использовали запятую в запис и числа : hous epr i c e = 249, 500 ; Это уже не синтакс ическая ошибка . Наоборот , язык С инте рпретирует правую часть как выражение с запятой, при этом hou s epri c e 2 4 9 представляет с об ой левое подвыражение , а 5 0 0 расс матривается как правое подвыраже ние . Следовательно, зна­ че ние все го выражения с запятой - это выражение из правой части, а ле вый подопе­ ратор присваивает значе ние 2 4 9 переменной hou s epri c e . Следовательно , результат этой операции совпадает с ре зультатом выполнения следую щего программного кода: = hous epr i c e 50 0 ; = 249; В с помните , что любое выражение становится опе ратором, если ему в конец доба­ вить точку с запятой; следовательно , 5 О О ; является оператором, который не произво­ дит никаких действий. С другой стороны , опе ратор hous epr i c e = (249, 50 0) ; прис ваивает пере менной hou s ep r i c e значе ние 5 0 0 . Запятая применяется также в качестве разделителя, так чт о запятые в выраже нии char ch , date ; и в опе раторе printf ( " % d % d\ n " , chimp s , chump s ) ; представляют собой разделители, но не опе рации запятой. Управля ющие операторы: цикл ы 233 Св од к а: н ов ы е операц и и О пера ци и присваивания : Каждая из приведенных ниже опера ц и й обновляет пере менну ю, стоя щу ю слева от зна­ ка опера ции , зна чение м , стоящим с п рава от знака : П риба вляет вел ич ину справа о т зна ка опера ц и и к значению слева от знака . += Вычитает вел ич ину с п рава о т знака опера ц и и из значения слева о т знака . *= /= %= Умножает значение переменной слева от знака операции на величину справа от знака. Дел ит значение переменной слева от знака операции на величину с пра ва от зна ка . Возвра щает остаток от деления зна чения переменной слева о т зна ка опера ц и и на вел ичину справа о т зна ка . П ример: r abЬits *= 1 . 6 ; это то же самое, что и r abЬits = r abЬ its * 1 . 6 ; Эт и комбинированные опера ц и и прис ваивания имеют такой же низкий приоритет , что и т радиционная операция присва ивани я , то ест ь меньш и й , чем приоритет арифметиче­ с ких операций. В с илу этого обстоятел ьства , следу ющий операто р contents * = o l d r ate + 1 . 2 ; дает тот же резул ьтат , что и приведенн ы й ниже операто р: contents = contents * ( old_r ate + 1 . 2 ) ; О пера ц ия запято й : Опера ция запятой связы вает два вы ражения в одно и гарант ирует , что выражение, нахо­ дящееся слева от знака операции, вычисляется первы м. Обычно она испол ьзуется для то­ го, чтобы включить ка к можно бол ьше информации в управляющее выражение цикпа for . Значение всего вы ражения есть значение выражения с пра ва от зна ка операции. П ример: for ( s tep = 2 , fargo = О ; fargo < 1 0 0 0 ; step *= 2 ) fargo += step ; Гречески й философ Зенон и цикл for Посмотрим , как с помощью цикла for и операции запятой можно разре шить древ­ ний парадокс . Грече ский философ Зенон утверждал, что стрела никогда не поразит цели. С начала , говорил он, стрела пролетает половину пути до цели. Зате м она проле­ тает половину оставшегося пути, затем она пролетит половину того пути, что остался, и так до бесконечности. Поскольку ве сь путь стрелы разбит на бесконечное количе ст­ во частей, утверждал Зенон, стреле потребуется бесконе чно большой проме жуток вре мени для достижения конца пути. Однако мы сомневае мся в том, что Зенон добро­ вольно с оглас ился бы стать живой мишенью , чтоб ы доказать свою правоту. Приме ним количе стве нный подход и предположим, что стреле потребуется одна секунда, чтоб ы пролететь пе рвую половину пути, зате м ей потребуется 1 / 2 с е кунды, чтобы пролететь половину пути, который остался , еще 1 /4 секунды, чтобы преодо· леть половину пути, который остался после этого , и так дале е . Полное время пролета стрелы можно представить в виде следую щей бесконе чной последовательности: 1 + 1/2 + 1/4 + 1/8 + 1/16 + . . . 234 Гл ава б Короткая программа, показанная в листинге 6 . 1 4 , вычисляет сумму нескольких первых эле ментов этой последовательности. Листинг 6.1 4. Проrрамма zeno . с / * z e no . c - - сумм.а посл едов а тельно сти * / #incl ude <s tdio . h > int main ( vo i d ) { int t_ct ; do uЫ e time , х ; int limi t ; 1 1 сч е тчик элемен тов по следо в атель ности printf ( " Bв eди т e необходимое колич е ство элементов последов а т е л ь но с ти : " ) ; s c an f ( " % d" , &l imit ) ; for ( time= O , x = l , t_ct= l ; t ct <= limit ; t_ct++ , х * = 2 . 0 ) { time += 1 . 0 / х ; print f ( " Bpeмя % f , ко гда колич е ство элементо в % d . \ n " , time , t_ct ) ; r e turn О ; Ре зультат выполне ния программы, в ходе которого суммируются первые 15 элементов последовательности, выглядит следую щим образом: Вв едите необходимо е колич е с тво элемен тов по следо в атель ности : 1 5 Вр емя 1 . 0 0 0 0 0 0 , ко гда колич е с тво эл ементов 1. Вр емя 1 . 5 0 0 0 0 0 , когда колич е с тво эл ементов 2. Вр емя 1 . 7 5 0 0 0 0 , когда колич е с тво эл ементов 3. Вр емя 1 . 8 7 5 0 0 0 , когда колич е с тво эл ементов 4. Вр емя 1 . 9 3 7 5 0 0 , когда колич е с тво эл ементов 5. Вр емя 1 . 9 6 8 7 5 0 , когда колич е с тво эл ементов 6. 7. Вр емя 1 . 9 8 4 3 7 5 , когда колич е с тво эл ементов Вр емя 1 . 9 9 2 1 8 8 , когда колич е с тво эл ементов 8. Вр емя 1 . 9 9 6 0 9 4 , когда колич е с тво эл ементов 9. Вр емя 1 . 9 9 8 0 4 7 , когда колич е с тво эл ементов 10. Вр емя 1 . 9 9 9 0 2 3 , когда колич е с тво эл ементов 11. Вр емя 1 . 9 9 9 5 1 2 , когда колич е с тво эл ементов 12 . Вр емя 1 . 9 9 9 7 5 6 , когда колич е с тво эл ементов 13 . Вр емя 1 . 9 9 9 8 7 8 , когда колич е с тво эл ементов 14 . Вр емя 15. 1 . 9 9 9 9 3 9 , когда колич е с тво эл ементов Ле гко заметить, что хотя мы и приб авляем все новые элементы, общая сумма , по­ видимому, не пре высит некоторой величины . И в самом деле , математики доказали, что сумма этой последовательности стремится к 2.0 по мере того , как число просум­ мированных элементов последовательности стремится к бесконе чности, на что ука­ зывают и результаты выполнения этой программы. Рассмотрим следую щие математи­ ческие выкладки. Предположим , что S представляет собой сумму последовательности : s = 1 + 1/2 + 1/ 4 + 1/ 8 + . . . Управля ющие операторы: цикл ы 235 Здесь многоточие означает " и так дале е" . Разделив S на 2 , получае м S/ 2 = 1 / 2 + 1/ 4 + 1/ 8 + 1/ 1 6 + . . . В ычитание второго выражения из первого дает S - S/2 = 1 +1/2 -1/2 + 1/ 4 -1/ 4 + . . . За исклю чением первого элемента , равного 1 , все остальные значения образуют пары , в которых одно значе ние положительное , а равное ему второе значение отри­ цательное, так что эти элементы уничтожают друг друга , и в результате получае м S/ 2 = 1 . И , наконец, умноже ние обеих сторон на 2 дает s = 2. Отсюда можно сделать поле зный вывод: прежде чем пускаться в трудоемкие вы· числе ния , прове рьте , не нашли ли математики более простого с пособа получить тот же ре зультат . Что можно сказать о самой программе ? О на показывает, что вы можете в выраже· ниях использовать более одной операции запятой. В ней вы инициализировали пе ре· менные time , х и count. После того , как вы определили условия выполнения цикла, программа оказалась очень короткой. Ц и кл с постуслови ем : do whi le Циклы whi l e и for представляют собой циклы с предусловиями. Проверяемые ус­ ловия вычисляются перед каждой итерацией цикла, таким образом, существует веро· ятность, что оператор ы , заложенные в цикл, никогда не будут выполнены. В С име ет· ся также цикл, в котором прове рка условия осуществляется на выходе из каждой ите­ рации цикла, благодаря чему гарантируется выполнение опе раторной части цикла, по меньше й мере , один раз. Эта разновидность цикла называется циклом do whi l e . Лис· тинг 6 . 1 5 являет собой пример такого цикла. Листинг 6.1 5. Программа d o whi1e . с / * do whil e . c - - цикл с постуслови ем * / #incl ude <s tdio . h > int main ( vo i d ) { co n s t int s e cr et code int code enter e d ; 13 ; do { print f ( " Что бы войти в клуб л еч е ния шпиономании , \ n " ) ; рrint f ( " пожалуйст а , вв едите секр е тный код : " ) ; s can f ( " % d" , & code_enter ed) ; whil e ( code enter e d ! = s e cret_code ) ; рrintf ( " По здр авля ем ! Вы уже здоровы ! \ n " ) ; r e turn О ; 236 Гл ава б Программа в листинге 6 . 1 5 читает входные значения до тех пор , пока пользователь не введет число 1 3 . Ниже показан результат выполнения этой программы: Чтобы войти в клуб леч ения шпио номании , пожалуй с т а , вв едите секр е тный код : 12 Чтобы войти в клуб леч ения шпио номании , пожалуй с т а , вв едите секр е тный код : 14 Чтобы войти в клуб леч ения шпио номании , пожалуй с т а , вв едите секр е тньп.1 код : 13 По здравляем ! В ы уже здоро вы ! Э квивалентная программа , в которой используется цикл whi l e , будет несколько длинне е , как можно видеть в листинге 6 . 1 6 . листинг 6.1 6. Проrрамма entry . c / * entry . c - - цикл с п о с тусловием * / #incl ude <s tdio . h > int main ( vo i d ) { co n s t int s e cr et code 13 ; int code enter e d ; printf ( " Чтo бы войти в клу б леч е ния шпиономании , \ n " ) ; рrintf ( " пожалуйста , вв еди т е секр е тный код : " ) ; s c an f ( " % d" , & c ode_entered) ; whi l e ( code_entered ! = s e cr et_code ) { print f ( " Что бы войти в клуб л еч е ния шпиономании , \ n " ) ; print f ( " пожалуйст а , вв едите секр е т ньп.1 код : " ) ; s can f ( " % d" , & code enter ed) ; printf ( " По здр авля ем ! Вы уже здоровы ! \ n " ) ; r e turn О ; О бщая форма цикла do whi l e имеет вид : do опера тор ( выражение whi l e ) ; О пе ратор может б ыть простым и с о ставным. Обратите внимание на то , что цикл do whil e считается оператором и тре бует, чтобы после него стояла точка с запятой, как показано на рис . 6 . 5 . Цикл d o whil e выполняется , по меньшей мере , один р а з , поскольку проверка ус· ловия производится только после того как тело цикла выполнено . С другой стороны, может случиться так, что циклы for и whi l e не будут выполне ны ни разу, поскольку проверка условия цикла производится пе ред выполне ние м операторной части цикла. Цикл do whil e следует ис пользовать только в тех случаях, когда требуется , по мень· шей мере, одна ите рация . Управля ющие операторы: цикл ы 237 р rintf ( " Тра-ля-ля-ля ! \n" ) ; Следующий оператор Р и с. 6.5. Структура 'Цикла do whi 1 е Например, программа проверки пароля наряду с приведенными ниже строками псе вдокода должна содержать и такой цикл: do { приг л ашение вве сти пароль ч тени е данных , вв еденных пол ь зоват елем whil e ( ввод не совпада е т с паролем) ; Избегайте приме не ния структур do whi l e , которые име ют тип, демонстрируе мый следую щим пс евдокодо м : do { з апро с поль зователю , н амер ен ли он продо лжать неко торые умные действия whil e ( о тв е том являе тся ' да ' ) ; В данном случае , после того как пользователь ответит "нет" , умные действия с о стороны пользо вателя непреме нно последуют, пос кольку проверка выполняется слишком поздно. Св од к а: оператор do whi le Кл ючевые слова: do whil e Комментарии о бще го характера: Опе ратор do whi l e создает ци кп , которы й повторяется до тех пор, пока проверяемое выражение не станет ложны м или ра вны м нулю. Оператор do whil e я вляется цикло м с постусловием, то ест ь решение о необходимост ь в ы полнения о пе раторной части цик­ ла еще раз, принимается после очередного прохождения цикпа. Поэтому цикп будет вы полнен, по меньше й мере, один раз. Часть опера тор форм ы может быть л ибо про­ ст ы м , л ибо соста вны м операторо м. Форма: do опера тор whi l e ( выражение ) ; 238 Гл ава б Часть опера тор гю вторяется до тех пор, пока выра жение не станет ложн ы м ил и не по­ лучит нулевое значение. П ример: do s can f ( " % d" , &nurnЬ er ) ; whi l e ( nurnЬ er ! = 2 0 ) ; К акой цикл выбрать ? Когда вы приходите к выводу, что без цикла никак не обойтись, перед вами встает вопрос , каким циклом следует вос пользоваться? Во-пе рвых, определите сь с тем, како­ му типу условия должен отвечать ваш цикл - предусловию или постусловию . В боль­ шинстве случаев выбирается цикл с предусловие м. Суще ствует целый ряд причин, в силу которых квалифицированные программисты обычно отдают предпочтение цик­ лам с предусловием. Во-вторых, программа становится б олее понятной, е сли условие цикла находится в начале цикла. И, наконец, во многих случаях нужно полностью пропустить цикл, если условие на входе в цикл не выполняется . Допустим, что вам нужен цикл с предусловием. Долже н ли это б ыть цикл for или цикл whil e ? Частично это дело вкус а , поскольку то , что вы можете сделать с помощью одного типа цикла, вы можете сделать и с помощью другого . Чтобы сделать цикл for подобным циклу whi l e , вы можете опустить пе рвое и третье выражения. Например: for ( ; проверк а ; то же самое , что и whi l e ( проверк а ) Чтобы придать циклу whil e такой ж е вид, как цикл fo r , выполните перед началом цикла инициализацию и вклю чите опе раторы, осуществляю щие обновления значе­ ний. Например: инициализа ция ; whi l e ( проверк а ) { тело - цикла ; о бновление - зна чений; то же самое , что и for ( инициализ а ция ; проверка ; о бновление ) тело - цикла ; С точки зре ния преобладающего стиля , цикл for подходит больше в тех случаях, когда цикл предусматривает инициализацию и обновление пе ременной, а цикл whil e предпочтительне е , когда этого делать н е нужно . Цикл whi l e целес ооб разно ис пользо­ вать в следую щих условиях: whi l e ( s can f ( " % l d " , &num) == 1 ) Цикл for представляется более есте стве нным выбором, когда в теле цикла произ­ водится вычисление значе ния инде кс а : f o r ( count = 1 ; count < = 1 0 0 ; count++ ) Управля ющие операторы: цикл ы 239 В л ожен ные ци кл ы Вложенн'ЫU -цикл - это цикл внутри другого цикла. О чень часто вложенные циклы используются для упорядоче нного отображе ния данных в форме строк и столбцов. Один цикл может взять на себя обработку, с каже м , всех столбцов в строке , в то время как второй цикл занимается обработкой строк. В листинге 6 . 1 7 представлен простой пример. Листинг 6.1 7. Программа rows l . c / * rows l . c - - исполь зов ание вложенных циклов * / #incl ude <s tdio . h > #de fi ne ROWS 6 #de fi ne CHARS 1 0 int main ( vo i d ) { int row ; char ch ; О ; row < ROWS ; row++ ) for ( row { for ( ch = ' А ' ; ch < ( ' А ' + C HAR S ) ; ch++ ) printf ( " % c " , c h ) ; print f ( " \ n " ) ; / * строка 1 0 * / / * строка 1 2 * / r e turn О ; В ре зультате выполне ния этой программы получе ны следую щие результаты : AB CDEFGH I J AB CDEFGH I J AB CDEFGH I J AB CDEFGH I J AB CDEFGH I J AB CDEFGH I J Анал из п рогра м м ы Цикл for , начинаю щийся в строке 1 0 , называется внешним, а цикл, который берет начало в строке 12, называется внутренним. Внешний цикл начинается при значении О переменной row и завершается , когда ее значение становится равным 6 . В силу этого об· стоятельства внешний цикл проходит через шесть итераций, при этом переменная row последовательно получает значения от О до 5 . Первый оператор в каждой итерации яв· ляется внутренним для цикла for . Этот цикл выполняет 10 итераций, печатая символы от А до J в одной строке . Вторым оператором внешнего цикла является print f ( " \ n " ) ; . Этот оператор начинает новую строку, это значит, что в следую щий раз , когда начинает работу внутренний цикл, выходные данные будут печататься с новой строки. О братите внимание , что в условиях вложе нного цикла внутренний цикл проходит ве сь диапазон итераций для каждой итерации внешнего цикла . В последнем примере внутренний цикл печатает 10 символов в строке , а внешний цикл печатает 6 таких строк. 240 Гл ава 6 Ва риаци и вложенных ци клов В предыдущем примере внутре нний цикл выполнял одни и те же действия для каж­ дой ите рации вне шнего цикла. Вы можете сделать так, чтобы внутре нний цикл изме­ нял свое поведе ние в завис имости от вне шнего цикла. Например, программа в листинге 6.18 является незначительной модификацией предыдущей программы, она выбирает начальный символ последовательности симво­ лов, распе чатываемых внутренним циклом в завис имости от номе ра ите рации вне ш­ не го цикла. К тому же в ней присутствует комментарий нового стиля и cons t вместо #de fi n e , чтобы можно было удобно работать в условиях обоих подходов. листинг 6_ 1 8_ Проrрамма rows2 _ с исполь зов ание з ависимых вложенных циклов 1 1 rows 2 . c #incl ude <s tdio . h > int main ( vo i d ) { co n s t int ROWS = 6 ; co n s t int CHAR S = 6 ; int row ; char ch ; - - О ; row < ROWS ; row++ ) for ( row { for ( ch = ( ' А ' + row) ; ch < ( ' А ' + CHARS ) ; ch+ + ) printf ( " % c " , c h ) ; print f ( " \ n " ) ; r e turn О ; На этот раз выходные данные принимают следую щий вид : AB CDEF BCDEF CDEF DE F EF F Поскольку значение переменной row добавляется к символу ' А ' во вре мя каждой итерации вне шнего цикла, пе ременная ch инициализируется в каждой строке симво­ лом , следую щим в алфавите за с имволом , который использовался для предыдущей инициализации. Проверяе мое условие , однако, не меняется , так что каждая новая строка заканчивается символом F. В силу этого обстоятельства , в каждой строке выво­ дится на один символ меньше , чем в предыдуще й. Управля ющие операторы: цикл ы 241 В веден ие в м асси вы Массивы играют важную роль во многих программах. О ни позволяют хранить не· которое количество эле ментов однотипной информации в удоб ной форме . Массивам полностью посвящена глава 1 0 , но пос кольку для работы с масс ивами оче нь часто ис· пользуются циклы, имеет смысл ознакомиться с их характерными о с обенностями. Массив - это некоторая совокупность значе ний одного и того же типа , например, 1 0 значений типа char или 1 5 значений типа int, хранящихся в памяти в виде непре· рывной последовательности. Мас с ив имеет с вое с об ственное имя, а доступ к отдель· ным его элементам осуществляется с помощью целочисленного инде кс а . Наприме р, объявление fl o at deЬts ( 2 0 ] ; уведомляет о том, что deЬ ts является массивом, состоящим из 20 элементов, каждый из которых может с одержать в себе значе ние типа float . Пе рвый элемент массива получает имя deЬts [ О ] , второй эле мент - deЬ ts [ 1 ] и так далее , вплоть до deЬ t s [ 1 9 ] . О братите внимание , что нумерация элементов мас с ива начинается с О , а не с 1 . Каж· дому элементу данного массива может быть присвое но значение типа float. Напри· мер, допустимы опе раторы следую щего вида : deЬts [ 5 ] 32 . 54 ; deЬts [ б ] = 1 . 2 е+ 2 1 ; По сути дела , вы можете использовать тот или иной эле мент мас сива так же, как и вы использовали бы переменную того же типа. Наприме р, вы можете читать значение в конкретный эле мент масс ива : s c an f ( " % f " , &deЬts [ 4 ] ) ; / / читать знач ение в пятый элемент массива Один из источников возможных ошибок с остоит в том, что язык С для повышения скорости вычисле ний не проверяет, правильно ли выбран инде кс элемента мас с ива. Наприме р, каждый из приведенных ниже операторов является ошибочным: deЬts [ 2 0 ] 88 .32; deЬts [ 3 3 ] = 8 2 8 . 1 2 ; 1 1 в данном массиве тако г о элемента н е т 1 1 в данном массиве тако г о элемента н е т В т о ж е время компилятор не обнаруживает ошибок подоб ного рода . В ходе вы· полне ния программы такие операторы размещают данные в ячейках памяти, кото­ рые , возможно , используются другими данными, они спос об ны запортить выходные данные программы и даже вызвать ее аварийное заве ршение . Массивы могут быть однородными последовательностями данных любого типа : int nann i e s [ 2 2 ] ; char actor s [ 2 6 ] ; l o ng Ьig [ 5 0 0 ] ; / * ма ссив для хр анения 2 2 це лых чисел * / / * ма ссив для хр анения 2 6 символов * / / * ма ссив для хр анения 5 0 0 целых чисел типа l o ng * / Например, ранее в книге речь шла о строках, представляющих собой с пециальный случай данных, которые можно разме щать в масс ивах типа char . (В общем случае мае· сив типа char - это такой мас сив, элементы которого имеют тип char . ) Содержимое массива тип char образуют строку в том случае, когда этот массив с одержит пустой символ \ О , который обозначает коне ц строки (рис . 6 .6 ) . 242 Гл ава 6 Символьный массив, но не строка у u о 1 с а n 1 s е е 1 i s е е 1 i Символьный массив и строка у u о 1 с а n 1 Нулевой символ Ри с. 6.6. Си.мволън:ые .массивъt и строки Числа , используемые для идентификации эле ментов мас с ива, называются указате л.ями, индекса.ми или с.мещени.ями. Индексами могут быть целые числа , и, как б ыло сказа­ но выш е , индексы начинаются с О . Эле менты мас сива хранятся в памяти, непосредст­ венно примыкая друг к другу, как показано на рис . 6 . 7. int Ьоо [4 ] (два байта на элемент inl) 1 980 46 481 6 3 Ьоо [ О ] boo [ l ] Ьоо [2 ] Ьоо [З] char foo [4 ] (один байт на элемент char) h е foo [ O ] foo [ l ] р foo [2 ] fоо [З] Р ис. 6.7 . Массивъt cha r и i n t в памяти Использование ци кла f or п р и работе с масси ва м и Массивы очень широко используются в программах. В листинге 6 . 1 9 представлен один из простейших примеров применения этой структуры данных. Программа с чи­ тывает результаты 1 0 игр в гольф , которые позже подве ргаются дальнейше й обработ­ ке. Используя мас с ив, вы изб егаете необходимости объявлять 1 0 переменных под раз­ личными именами, по одной переменной для результата каждой игры . О пять-таки, вы можете воспользоваться циклом for для считывания входных данных. Программа предназначена для подс чета общей суммы очков , их среднего значения и гандикапа , который представляет с обой разность между с редним и стандартным числом очков, или пар. листинг 6.1 9. Проrрамма scores_in . c 1 1 s co r e s i n . c испол ь зование циклов для о бр або тки массивов #incl ude <s tdio . h > #de fi ne S I Z E 1 0 #de fi ne PAR 7 2 - - Управля ющие операторы: цикл ы 2 43 int main ( vo i d ) { int inde x , s co r e [ S I Z E ] ; int s um = О ; fl o at av e r age ; printf ( " Bвeди т e % d р е зуль татов игры в г о л ь ф : \ n " , S I Z E ) ; for ( index = О ; inde x < S I ZE ; i ndex++ ) s can f ( " l d" , & s cor e [ inde x ] ) ; / / пр очитать 1 0 р е зуль татов игры в голь ф printf ( " Bв eдeны сле дующие р е зул ь таты : \ n " ) ; for ( index = О ; inde x < S I ZE ; i ndex++ ) print f ( " % 5 d " , s co r e [ index ] ) ; / / пр оверит ь вве денную информацию printf ( " \ n " ) ; for ( index = О ; inde x < S I ZE ; i ndex++ ) s um + = s cor e [ inde x ] ; / / сложить р е зул ь таты av e r age = ( fl o at ) s um / S I ZE ; / / р а спределение во вр емени рrintf ( " Оконч а тельная сумма очков = % d , ср е дн е е знач ение = % . 2 f\ n " , s um , av e r age ) ; рrintf ( " Получ е нный г андикап р а в е н % . O f . \ n " , aver age PAR ) ; - r e turn О ; С начала мы пос мотрим , как работает программа из листинга 6 . 1 9 , а после этого прокомме нтируем ее действия . Ниже показаны выходные данные : Вв едите 1 0 р е з уль татов и г р ы в г ол ь ф : 102 98 99 101 112 108 105 9 6 1 0 2 10 0 10 3 Вв едены следующие р е зуль т аты : 102 9 8 112 108 105 103 99 1 0 1 96 102 Оконч атель ная сумма очков = 1 0 2 6 , ср е дн е е з н ач ение Получ енный г андикап равен 3 1 . 102 . 60 В общем, программа раб отает, а теперь рас смотрим некоторые е е особенности. В о­ первых, обратите внимание на тот факт , что, хотя вы и напечатали 1 1 чисел, считаны только 10 из них, так как цикл, выполняющий чтение , вводит только 10 значений. По­ скольку функция s can f ( ) игнорирует пробелы , вы можете ввести вс е 1 0 чисел в одной строке , поместить каждое число в отдельную строку, либо, как в рассматриваемом случае, ис пользовать смес ь символов новой строки и пробелов, чтоб ы отделить вход· ные значе ния одно от другого . (Т ак как входные данные запоминаются в буфе ре , эти числа передаются в программу только после нажатия клавиши <Ente r>. ) Далее , работать с мас сивами и циклами гораздо удобнее, ч е м использовать 1 0 от· дельных операторов s can f ( ) и 1 0 опе раторов printf ( ) для того, чтобы прочитать 1 0 ре зультатов игры. Цикл f o r предлагает простой способ ис пользования индексов мае· сивов. О братите внимание , что программа манипулирует элементами мас с ива типа int так же , как и пе реме нными типа int. Чтобы прочитать пе ременную fue типа int, вы должны прибе гнуть к помощи функции s can f ( " l d " , & fu e ) . Программа из листин· га 6 . 1 9 производит с читывание элемента s co r e [ index ] типа int, следовательно, она использует выражение s can f ( " l d" , & s co r e [ index ] ) . 244 Гл ава 6 Этот пример является иллю страцие й не скольких моме нтов , характе ризую щих стиль программирования . Во-пе рвых, удачной является идея ис пользования дире кти­ вы #de fine для создания именованной константы ( S I ZE ) , описываю щий размер мас­ сива . Вы используете эту константу при описании массива и при выборе пределов циклов . Если вам в дальнейшем потребуется рас ширить программу до 20 счетов, дос­ таточно просто пере определить константу S I ZE, установив ее равной 2 0 . При этом отпадает необходимость менять каждую часть программы, в которой используется размер массива . Стандарт С99 позволяет ис пользовать значения типа con s t для опре­ деле ния размеров мас с ива, в то вре мя как в стандарте С90 это невозможно , зато ди­ ре ктива #de fin e разре шена и в том, и в другом стандартах. В о-вторых, конструкция for ( index = О ; inde x < S I ZE ; i ndex++ ) удобна для обработки мас сива размером S I ZE . О чень важно задать разме ры мас с ива. Пе рвый эле мент массива имеет индекс О, поэтому цикл начинается с того , что уста­ навливается значе ние пе ременной index равным О . Поскольку отсчет инде кс ов начи­ нается с О , индекс последне го элеме нта принимает значе ние S I ZE 1 . То есть, деся­ тый элемент масс ива - это s co r e [ 9 ] . Проверяе мое условие index < S I Z E учитывает это об стоятельство, благодаря чему последним значе нием переме нной i ndex, исполь­ зованным в цикле, является S I ZE 1. В-третьих, хороший тон в программировании требует о т программы проверять значения , которые б ыли только что введены (эхо-контроль) . Это позволяет обрести уверенность в том, что программа выполняет обработку именно тех данных, которые вы ввели. И, наконец, обратите внимание на то , что в программе, представле нной в листин­ ге 6 . 1 9 , используются три отдельных цикла fo r . У вас могут возникнуть сомнения, нужно ли столько циклов на самом деле . Нельзя ли совместить хотя бы некоторые операции в одном цикле? Ответом будет "да", то есть вы можете некоторые из опера­ ций выполнить в одном цикле. Это сделало бы программу боле е компактной. Однако этому мешает принцип модулъности. Идея, которая стоит за этим те рмином, состоит в том, что программа должна б ыть разбита на отдельные фрагме нты , при этом каждый фрагмент должен решать конкретную задачу. Это облегчает чте ние программы. Но что , возможно , еще важнее - модульность обле гчает реше ние задачи обновления или модификации программы , если ее фрагменты взаимно не переплетаются . Когда вы узнаете больше о функциях, вы сможете превратить каждый такой фрагме нт в от­ дельную функцию , увеличивая тем самым степе нь модульности конечной программы . - - При м ер ци кла, и сп ол ьзующего возвращаем ое з н а ч ен ие функци и Последний приме р в этой главе использует функцию , которая вычисляет результат возведе ния чисел в заданную целую степень. (Для решения б олее с е рьезных задач по обработке чисел в библиотеке math h предусмотрена б олее мощная функция pow ( ) , которая позволяет возводить веще стве нные числа в степень, также представле нную вещественным числом.) Тре мя основными задачами, которые решаются в этом при­ мере, являются : разработка алгоритма вычисления ответа , представление этого алго_ Управля ющие операторы: цикл ы 245 ритма в виде функции, которая возвращает ответ, и разраб отка удобного метода тес­ тирования этой функции. С начала рас смотрим алгоритм . Упростим задачу, ограничившись вычислением только положительных целых степеней. В таком случае , е сли вы захотите возвести n в сте пень р , вы должны умножить n с амо на с е бя р раз . Эта задача ле гко решается с по­ мощью цикла f o r . Вы можете прис воить пе ременной pow значение 1 , а затем много­ кратно умножать ее на n: for ( i = 1 ; i <= р; i + + ) pow * = n ; Напомним, что в ре зультате выполнения операции * = левая часть выражения ум­ ножается на правую . После выполне ния первой опе рации цикла значение перемен­ ной pow равно 1 , умноже нное на n, то есть n. По заверше нии второго цикла значение переменной pow равно ее предыдущему значению (n) , умноже нному на n, или n в квад­ рате , и так далее . В этом контексте цикл for вполне обоснован, пос кольку цикл вы­ полняется заране е известное количество раз (после того, как станет известным р ) . Теперь, когда разработан алгоритм, вы должны решить, какой тип данных не обхо­ димо ис пользовать. Показатель степе ни р, будучи целым числом, должен иметь тип int. Чтобы обеспечить достаточно широкий диапазон значений пе ременной n и ее сте пеней, для переме нных n и pow выбирается тип douЫ e . Далее , нужно подумать, к а к написать функцию . Вы должны пе редать функции два значения , а функция должна ве рнуть одно значение . Чтобы передать функции необ­ ходимую информацию , вы можете воспользоваться двумя аргументами, один из кото­ рых имеет тип douЫ e , а другой - тип int; эти аргуме нты определяют, какое число возводится в какую степень. Как устроить, чтобы функция возвращала значение в вы­ зываю щую программу? Чтобы написать функцию с возвращаемым значе ние м, выпол­ ните следую щие действия : 1 . При определе нии функции установите тип значения, которое она возвращает. 2 . Ис пользуйте клю чевое слово r eturn, чтобы указать возвращаемое значение . Например, вы можете поступить так: do uЫ e p ower ( douЫ e n , int р) { douЫ e pow = 1 ; int i ; for ( i = 1 ; i <= р ; i++ ) pow * = n ; r etur n pow ; / / в о звр ащает з н ач ени е типа douЫ e / / в о звр ащает з н ач ени е переменной pow Чтобы объявить тип функции, укажите тип перед имене м функции точно так же, как это делается при объявлении переменной. Клю чевое слово r eturn заставляет функцию вернуть следую щее за ним значение в вызываю щую функцию . В расс матри­ ваемом приме ре вы возвращаете значе ние переме нной, вы также можете возвращать значения выражений. Например, приведенная ниже конструкция является допусти­ мой: r e turn 2 * х + Ь ; 246 Гл ава б Функция вычисляет значение выраже ния и возвращает его . В вызывающей функ­ ции возвращаемое значение может быть присвое но другой пе ременной, оно может быть использовано как значе ние выраже ния , как аргумент другой функции, например, print f ( " % f " , powe r ( 6 . 2 8 , 3 ) ) или может быть вообще проигнорировано . Давайте вос пользуе мся такой функцией в программе . Чтобы проте стировать функцию , желательно иметь возможность передать этой функции несколько значений и посмотреть, как она на них прореагирует. Для этого потребуется организовать цикл ввода . Вполне е стественно выбрать для этой цели цикл whi l e . Вы можете вос пользо­ ваться функцией s ca n f ( ) , чтобы прочитать одновре менно два значения. Если функ­ ция s can f ( ) ус пешно прочтет два значе ния , она возвратит значение 2 , следовательно , вы можете управлять выполнением цикла, сравнивая значе ние , которое возвращает функция s can f ( ) , со значение м 2. Еще одна деталь: чтобы воспользоваться функцией power ( ) в ваше й программе , ее следует объявить подобно тому, как объявляются пе­ ременны е , используемые в программ е . В листинге 6 .20 показана ре зультирую щая про­ грамма . листинг 6.20. Программа power _ с 1 1 power . c - - в о з в едени е чисел в целую с тепень #incl ude <s tdio . h > douЫ e powe r ( douЫ e n , int р ) ; 1 1 про т о тип ANS I int main ( vo i d ) { do uЫ e х , xpow ; int ехр ; printf ( " Bв eди т e число и положитель ную целую степень , " ) ; printf ( " в которую\ nчисло будет возве дено . Для з ав ерше ния пр ограммы" ) ; printf ( " вв еди т е q . \ n " ) ; whi l e ( s can f ( " % l f % d" , &х , & е хр ) == 2 ) { xpow = powe r ( x , exp ) ; 1 1 вызов функции print f ( " % . 3 g в с тепени % d р авно % . 5g\ n " , х , е хр , xp ow) ; print f ( " В в е дите следующую пару чисел или q для завершения . \ n " ) ; print f ( " Haдeeмcя , в а с удовле творило кач е с тво прогр аммы - до свидания ! \n " ) ; r e turn О ; douЫ e powe r ( douЫ e n , int р ) { do uЫ e p ow = 1 ; int i ; for ( i = 1 ; i <= р ; i++ ) pow * = n ; r e turn pow; 1 1 опр е делени е функции 11 возврат з н ач ения переменной pow Управля ющие операторы: цикл ы 2 47 Ниже показаны ре зультаты выполнения программы : Вв едите число и положител ь ную целую с тепень , в ко торую чи сло будет во зведено . Для завершения прогр аммы вв еди т е q . 1 . 2 12 1 . 2 в степени 12 р авно 8 . 9 1 6 1 Вв едите следующую п ару чисел или q для з ав ершения . 2 16 2 в степ ени 16 р авно 6 5 5 3 6 Вв едите следующую п ару чисел или q для з ав ершения . q Наде емся , в а с удовле творило кач е с тво прогр аммы - до свидания ! Анал из п рогра м м ы Программа mai n ( ) представляет с об ой приме р драйвера, т о есть короткой про­ граммы , предназначенной для тестирования конкретной функции. Цикл whi l e является обобщением формы, которой мы пользовались раньше . Ввод чис ел 1 . 2 12 заставляет функцию s can f ( ) успе шно читать эти два значения и вернуть значение 2 , после чего цикл продолжает выполнятьс я . Поскольку s c an f ( ) игнорирует проб елы, входные данные могут быть представлены в нес кольких строках, как пока· зывают выходные данные демонстрационного приме ра , однако ввод символа q вызы· вает возврат значе ние О , пос кольку с имвол q не может быть считан при наличии с пе· цификатора % l f. Это обстоятельство заставляет функцию s ca n f ( ) вернуть О и тем с а· мым пре кратить выполне ние цикла. Аналогично, ввод значе ния 2 . 8 q вызывает появление возвращаемого значе ния , равного 1, которое также приводит к заверше· нию цикла. Теперь рассмотрим все , что связано с использованием функции в программе . Функция p ower ( ) появляется в данной программе три раза. Первый раз она встре ча· ется в следую щей конструкции : do uЫ e p ower ( douЫ e n , int р ) ; 1 1 прототип ANS I Этот оператор опис ывает, или обмвляет, что в программе будет ис пользоваться функция с именем power ( ) . Пе рвое клю чевое слово douЫ e показывает , что функция power ( ) возвращает значе ние типа douЫ e . Компилятору нужно знать, какое значение возвращает функция power ( ) , чтоб ы определить, сколько байтов данных следует ожи· дать на входе и как их следует интерпретировать ; име нно по этой причине и следует объявлять функцию . О бъявления douЫ e n , int р, заклю ченные в круглые с кобки, означают, что функции power ( ) требуются два аргуме нта. Первый из них долже н иметь тип douЫ е , а второй - тип int . Второй раз эта функция появляется в следую щем операторе : xp ow = p ower ( x , exp ) ; 1 1 вызов функции На этот раз программа вызывает функцию и передает ей два значения. Сама функ· ция вычисляет значе ние х в степени е хр и возвращает результат вызывающей про­ грамме , в которой возвращаемое значение присваивается пе ременной xp ow. 248 Гл ава 6 Третий раз расс матривае мая функция встречается в заголовке определения функ­ ции: do uЫ e p ower ( douЫ e n, int р ) 1 1 о пр едел ение функции В данном случае функция powe r ( ) принимает два параметра, один типа douЫ e и второй типа i nt, представленные значениями переменных n и р . О братите внимание , что после конструкции powe r ( ) не стоит точка с запятой, когда она появляется в оп­ ределе нии функции, зато точка с запятой следует за ней в объявлении функции. За за­ головком расположен программный код , который и определяет, что должна делать функция power ( ) . Напоминаем, что эта функция ис пользует цикл for для вычисле ния значе ния n в сте пени р , а затем прис ваивает это значе ние переменной pow. Следую щая строка де­ лает это значе ние pow возвращаемым значе ние м функции: r e turn pow; / / возврат з н ач ения переменной pow И спользова ние функци й с возвра щаем ы м и зна чен и я м и О бъявление функции, вызов функции, определение функции, использую щей клю­ че вое слово r eturn это основные действия при определе нии и использовании функций с возвращаемыми значе ниями. На этом этапе у вас может возникнуть ряд вопросов. Наприме р, если от вас требу­ ется объявить функции до того , как вы вос пользуетес ь их возвращае мыми значения­ ми, тогда поче му можно пользоваться возвращаемыми значениями функции s c an f ( ) без объявле ния самой функции s can f ( ) ? Почему нужно объявлять функцию power ( ) отдельно , если в ее определении сказано , что она имеет тип douЫ e? Расс мотрим сначала ответ на второй вопро с . Компилятор должен знать, какой тип имеет функция p ower ( ) , когда он впе рвые сталкивается с не й в программе . В этот мо­ мент компилятор пока не вышел на определе ние функции power ( ) , поэтому он е ще не знает, что, как с казано в определении, типом возвращаемого функцие й значения яв­ ляется douЫ e . Чтобы помочь компилятору, вы упре ждаете неизбежное появление у компилятора с о ответствую щих вопрос ов путем ис пользования предварителън,ого (или упреждаю щего) объявлен,ия. Т акого рода объявле ния уведомляют компилятор о том, что где-то в программе будет объявлена функция powe r ( ) и что она возвращает тип douЫ e . Если вы поме стите в файле определение функции power ( ) раньше функции main ( ) , вы можете опустить предварительное объявление , пос кольку компилятор уже будет знать все , что ему нужно о функции p ower ( ) , прежде чем он попадет на функцию main ( ) . Однако, такой стиль в языке С не является стандартным . В силу того , что main ( ) об ычно представляет с об ой базовую структуру все й программы , лучше , чтобы main ( ) была представлена первой. Кроме того , функции часто размещаются в отдельных файлах, и в этих случаях их предварительные объявления просто необходимы . Дале е , почему вы не объя вили функцию s ca n f ( ) ? А ведь вы е е фактиче с ки объя­ вили. В заголовочном файле s tdio . h с одержатся объявления функций s can f ( ) , print f ( ) и других функций ввода-вывода . В объявле нии функции s can f ( ) указано , что она возвращает значение типа i nt. - Управля ющие операторы: цикл ы 2 49 Кл юч евые п онятия Цикл представляет с обой мощное средство программирования . При организации цикла вы должны обращать особое внимание на три следую щих ас пекта : • • • Четко определять условия прекращения цикла. Убедиться в том, что значе ния , используемые в прове рке условия цикла, ини­ циализированы перед пе рвым их использованием . Убедиться в том, что цикл выполняет конкретные действия для обновления прове ряемого условия на каждой итерации. Язык С вычисляет числовое значе ние прове ряемого условия . Результат, равный О , расс матривается как ложное значение , любое другое числовое значение трактуется как истинное. Выражения , использую щие опе рации отношения, часто выступают в качестве проверяемых условий, они не сколько с пецифичны . Результатом вычисления выраже ния отноше ния является 1, если оно истинно , и О , е сли оно ложно , что согла­ суется со значе ниями, допустимыми для нового типа _Bool . Массив представляет собой совокупность рас положенных рядом ячеек памяти с величинами одного и того же типа. Вы не должны забывать, что нумерация элементов массива начинается с О, и в силу этого об стоятельства последний эле мент массива имеет индекс на единицу меньший, чем количе ство эле ментов мас с ива . С не выполня­ ет проверку на правильность использования значе ний инде кс ов, следовательно , эта обязанность целиком ложится на ваши плечи. Использование функций состоит из трех отдельных этапов: 1. Объявление функции с указание м е е прототипа . 2 . Обращение к функции из программы пос редством ее вызова . 3 . Определе ние функции. Прототип позволяет компилятору прове рять, правильно ли вы используете функ­ цию , а определе ние функции показывает, как эта функция должна работать. Прото­ тип и определение функции - это примеры современного стиля программирования , когда программный эле мент делится н а интерфе йс и реализацию . Интерфейс описы­ вает, как ис пользуется то или иное средство , и именно для этой цели и предназначе н прототип функции, а ре ализация детально расписывает конкретные действия про­ граммного элемента , что входит в обязанности определе ния функции. Р езюм е О сновной темой настояще й главы было обсужде ние возможностей управле ния хо­ дом выполнения программы. Язык С предлагает различные виды помощи в структури­ зации ваших программ. Построе ние циклов с предусловиями производится с помо­ щью опе раторов whi l e и for . О ператоры for особенно удобны для напис ания циклов, выполняю щих инициализацию и обновле ние значе ний пе ременных. О пе рация запя­ той позволяет выполнять инициализацию и обновление сразу нескольких перемен­ ных в цикле f o r . Для тех редких случае в, когда требуется цикл с постусловие м, С предлагает опе ратор do whi l e . 250 Гл ава б Типичная конструкция цикла whi l e имеет следую щий вид : по лучить перво е знач ение whi l e ( з нач е ни е удо в л е творя е т пров еря емому условию ) { о брабо тать знач ение получить сл едующе е знач ение Цикл for , выполняющий те же де йствия , имеет следую щий вид: for ( получить первое знач ение ; знач ение удо в л е тв оря е т проверя емому условию ; получить следующее значени е ) о брабо тать знач ение В о всех этих циклах ис пользуется условие проверки для определения, нужно ли выполнять еще одну итерацию цикла. Другими словами, цикл продолжается , если прове ряемое выражение дает в ре зультате ненулевое значе ние , в противном случае цикл заве ршается. Довольно часто проверяе мым условием является выражение отноше ния , которое представляет собой выражение , построенное на б азе некоторой операции отноше­ ния . Такое выраже ние получает значе ние 1 , е сли отноше ние истинно , и О - во всех остальных случаях. Пе реме нные типа _Boo l , введенные в употребле ние стандартом С99, могут принимать только значения 1 или О, с о ответствую щие tru e или fal s e . В дополнение к операциям отношения в этой главе б ыли расс мотре ны несколько арифметиче ских операций прис ваивания языка С, таких как += и * = . Эти операции модифицируют значения опе ранда слева от знака операции путем выполне ния над ним соответствую щей арифметичес кой операции. Следую щим предмето м обсужде ний были массивы. М а с с ивы объявля ю тся путе м ис пользования квадратных ско б о к , в которых указано количе ство элементов ма с с и· ва . Первый элемент произвольного мас с ива получает номе р О ; второй - номер 1 и так дале е . Например, объявление do uЫ e hippos [ 2 0 ] ; создает массив из 20 элементов, а отдельные элементы мас с ива получают имена в диа· пазоне от hippos [ О ] до hippos [ 1 9 ] вклю чительно. Манипулировать инде ксами, ис· пользуе мыми для нумерации элементов массива , удобно с помощью циклов. И, наконец, в этой главе было показано, как с о здавать и использовать функции с возвращае мыми значениями. В оп росы для сам оконтроля 1 . Найдите значения пе ременной qua c k в каждой строке. int qu ack = 2 ; quack += 5 ; quack * = 1 0 ; 6; quack quack / = 8 ; quack % = 3 ; Управля ющие операторы: цикл ы 251 2 . При условии, чr о переменная val ue имеет тип int, определите , какие выход­ ные данные будут получе ны в результате выполне ния следую щего цикла: for ( value = 3 6 ; value > О ; value / = 2 1 ) printf ( " % 3 d" , value ) ; Какие при этом могут возникнуть проблемы , если переме нная value будет иметь тип douЫ e вместо int? 3. Запишите выраже ние для каждого из следую щих проверяемых условий: а. х больше 5 . б . Функция s ca n f ( ) предпринимает неудачную попытку прочитать одно зна­ чение типа douЫ e (с именем х ) . в. х имеет значе ние 5 . 4. Запишите выраже ние для каждого и з следую щих проверяемых условий: а. функция s can f ( ) ус пешно читает одно целое число . б. х не равно 5 . в. х равно 2 О или больше . 5 . Приведе нная ниже программа далека от идеала . Какие ошибки вы можете в не й найти? #include < s t dio . h> int main ( vo i d ) { int i , j 1 l i s t ( l O ) ; for ( i = 1 , i <= 1 0 , i++ ) list [ i ] = 2 * i + 3 ; for ( j = 1 , j > = i , j + + ) printf ( " % d " 1 l i s t [ j ] ) ; print f ( " \ n " ) ; /* /* /* /* /* /* /* /* /* строка 3 * / строка 4 * / строка 6 * / строка 7 * / строка 8 * / строка 9 * / строка 1 0 * / строка 1 1 * / строка 1 2 * / 6 . Воспользуйтесь вложе нными циклами, чтобы создать программу, которая вы­ водит на пе чать следую щую фигуру: $$$$$$$$ $$$$$$$$ $$$$$$$$ $$$$$$$$ 7. Что выведет на печать каждая из следую щих программ? а. #include < s tdio . h> int main ( void) { i nt i = О ; whi l e ( ++i < 4 ) рrint f ( " Здр авствуйте ! " ) ; do print f ( " Дo свидания ! " ) ; whi l e ( i++ < 8 ) ; r eturn О ; 252 Гл ава 6 б . #include < s tdio . h> int main ( void) { i nt i ; char ch ; for ( i = О , ch = ' А ' ; i < 4 ; i + + , ch += 2 * i ) printf ( " l c " , ch ) ; r eturn О ; 8 . Если на вход каждой из приведенных ниже программ подается фраза " Go we s t , young man ! " какие выходные данные они выведут н а печать? (Восклицатель· ный знак ! следует с разу за символом пробела в последовательности ASC II. ) , а. #include < s tdio . h> int main ( void) { char ch ; s can f ( " % с " , & ch ) ; whi l e ( ch ! = ' g ' { printf ( " % c " , ch ) ; s can f ( " l c " , & ch ) ; r eturn О ; б . #include < s tdio . h> int main ( void) { char ch ; s can f ( " % с " , & ch ) ; whi l e ( ch ! = ' g ' { printf ( " l c " , ++ ch ) ; s can f ( " % с " , & ch ) ; r eturn О ; в. #include < s tdio . h> int main ( void) { char ch ; do { s can f ( " % с " , & ch ) ; printf ( " l c " , ch ) ; whil e ( ch ! = ' g ' ) ; r eturn О ; Управля ющие операторы: цикл ы r. #include < s tdio . h> int main ( void) { char ch ; s can f ( " % с " , & ch ) ; for ( ch = ' $ ' ; ch ! = putchar ( ch ) ; r eturn О ; 'g ' ; 2 53 s ca n f ( " % c " , & ch ) ) 9 . Что выведет на печать следую щая программа? #include < s t dio . h> int main ( vo i d ) { int n , m ; n = 30; whi l e ( + + n <= 3 3 ) print f ( " % d l " , n ) ; n = 30; do print f ( " % d l " , n ) ; whi l e ( + + n <= 3 3 ) ; printf ( " \ n* * * \ n " ) ; for ( n = 1 ; n * n < 2 0 0 ; n + = 4 ) print f ( " % d\ n " , n ) ; printf ( " \ n* * * \ n " ) ; for ( n = 2 , m = 6 ; n < m ; n * = 2 , m+= 2 ) print f ( " % d % d\ n " , n , m) ; printf ( " \ n* * * \ n " ) ; for ( n = 5 ; n > О ; n - - ) { for (m = О ; m <= n ; m++ ) printf ( " = " ) ; print f ( " \ n " ) ; r eturn О ; 1 0 . Рас смотрим следую щее объявление : douЫ e mint [ l O ] ; а. Какое имя прис воено масс иву? 6. Сколько эле ментов в массиве ? в. Какие виды значений могут храниться в каждом элементе мас с ива? r. Какие из примеров ис пользования функции s can f ( ) применительно к этому масс иву правильны? i. s can f ( " % l f " , mi nt [ 2 ] ) ii. s can f ( " % l f " , &mint [ 2 ] ) iii. s can f ( " % l f " , &mint ) 254 Гл ава 6 1 1 . Мистер Но предпочитает иметь дело с четными числами, поэтому он напис ал программу, которая создает мас сив и заполняет его четными числами 2 , 4 , 6 , 8 и так далее . Есть ли ошибки в этой программе , а если есть, то что это за ошибки? #include < s t dio . h> #de fin e S I ZE В int main ( vo i d ) { int by_two s [ S I Z E ] ; int index ; for ( inde x = 1 ; inde x <= S I ZE ; i ndex++ ) by_two s [ inde x ] = 2 * index ; for ( inde x = 1 ; inde x <= S I ZE ; i ndex++ ) print f ( " % d " Ьу two s ) ; printf ( " \ n " ) ; r eturn О ; 1 2 . Вы хотите написать программу, которая возвращает значения типа long . Что должно вклю чать описание функции в этом случае? 13. Напишите функцию , которая принимает аргуме нт типа i nt и возвращает квад­ рат этого значе ния как тип l o ng. 1 4 . Что напе чатает следую щая программа? #include < s t dio . h> int main ( vo i d ) { int k ; for ( k = 1 , printf ( " % d : Здр авствуйте ! \ n " , k ) ; printf ( " k k* k < 2 6 ; k+ =2 , p r i nt f ( " T enep ь k р авно % d\ n " , k ) ) printf ( " k р авно % d н а итер ации\ n " , k ) ; %d\n " , k ) , r eturn О ; Уп ражн ен и я п о програм м ирован и ю 1 . Напишите программу, которая с о здает мас с ив из 26 элементов и помещает в не­ го 26 букв нижне го ре гистра. Заставьте ее вывести содержимое мас с ива . 2 . Воспользуйтесь вложе нными циклами, чтобы написать программу, которая вы· водит на пе чать следую щую фигуру: $ $$ $$$ $$$$ $$$$$ Управля ющие операторы: цикл ы 255 3 . Воспользуйтесь вложе нными циклами, чтобы написать программу, которая вы­ водит на пе чать такую фигуру: F FE FED FEDC FEDCB FEDCBA Примсчапие: если система не использует ASC II или некоторые другие кодировки, которые кодируют буквы в числовом порядке, то для инициализации символь­ ного массива буквами алфавита вы можете воспользоваться следую щим опера­ тором: char l ets [ 2 6 ] = "ABCDEFGНI JKLМNOPQR STUVWX YZ " ; Зате м вы можете использовать инде кс ы мас с ива для выбора конкретных букв, например, l ets [ О ] равно ' А ' и так далее. 4. Напишите программу, которая запрашивает у пользователя ввод буквы верхне­ го регистра. Вос пользуйте сь вложенными циклами, чтобы написать программу, которая выводит на пе чать фигуру в виде пирамиды , подобную изображенной ниже: А АВА АВСВА ABCDC DA ABCDEDCBA Эта фигура должна разворачиваться в зависимости от того , какой символ был введен. Наприме р, представленная выше фигура должна быть получена в ре­ зультате ввода символа Е. Совет: для об работки строк воспользуйтес ь внешним циклом . Т ри внутренних цикла используйте для работы со строкой: один - для манипуляции пробелами, второй - для печати букв в порядке возрастания и еще одни - для пе чати букв в порядке убывания . Если ваша система не использует кодировку ASC II или подобную , в которой буквы представлены в строгом чи­ словом порядке, с м . примечание в упражне нии 3 . 5 . Напишите программу, печатаю щую таблицу, в каждой строке которой пред­ ставлено целое число , е го квадрат и его куб . Запросите у пользователя верхний и нижний пределы таблицы . Используйте цикл for . 6 . Напишите программу, которая читает слово в с имвольный мас с ив , а затем пе­ чатает это слово в обратном порядке. Совет : вос пользуйтесь функцией strlen ( ) (глава 4) для вычисле ния индекс а последнего с имвола массива . 7. Напишите программу, которая запрашивает два числа с плавающей запятой и печатает значе ние их разности, деленной на их произведе ние . Программа об­ рабатывает пары вводимых чисел до тех пор , пока пользователь не введет не­ числовое значение . 8 . Модифицируйте упражнение 7 таким образом, чтобы программа использовала функцию для возврата ре зультатов вычислений. 256 Гл ава б 9 . Напишите программу, которая запрашивает ввод ве рхне го и нижне го пределов последовательности целых чисел, вычисляет сумму вс ех квадратов целых чисел, начиная с квадрата нижнего целочисле нного предела и заканчивая квадратом верхнего целочисле нного предела , после чего отображает ре зультат на экране . Далее программа запрашивает ввод следую щих предельных значений и ото­ бражает ответ, пока пользователь не введет значе ние ве рхне го предела , кото­ рый меньше или равен нижнему пределу. Ре зультаты выполнения программы могут выглядеть следую щим образом: Вв еди т е в ерхний и нижний целочисленные пр еделы : 5 9 Сумма квадр а тов целых чисел о т 2 5 до 8 1 р авна 2 5 5 Вв еди т е следующую комбинацию пр еделов : 3 2 5 Сумма квадр а тов целых чисел о т 9 до 6 2 5 р авна 5 5 2 0 Вв еди т е следующую комбинацию пр еделов : 5 5 Работа з ав ершена 10. Напишите программу, которая читает восемь целых чисел в мас с ив , а затем вы­ водит их в обратном порядке . 1 1 . Рас смотрим две следую щих бес конечных последовательности: 1 . 0 + 1 . 0/2 . 0 + 1 . 0/3 . 0 + 1 . 0/4 . 0 + 1 . 0 - 1 . 0/2 . 0 + 1 . 0/3 . 0 - 1 . 0/4 . 0 + . . . Напишите программу, которая подсчитывает текущие суммы этих двух последо­ вательностей до тех пор , пока не будет обработано заданное количество эле­ ментов последовательностей. Это количе ство пользователь вводит в программу в инте рактивном ре жиме . Программа отображает значения те кущих сумм 20 эле ментов, 1 0 0 эле ментов, 500 элементов. Сходятся ли эти последовательности к какому-либо значе нию ? Совет: - 1 , умноженная с ама на с ебя нечетное число раз , дает в результате - 1 , а четное число раз - 1 . 1 2 . Напишите программу, которая создает восьмиэлементный мас сив значений ти­ па int и поме щает в него эле менты вос ьми первых степеней числа 2 , а затем выводит получе нные значения на печать. Используйте цикл for для вычисле­ ния эле ментов массива , и для разнообразия воспользуйтес ь циклом do whi l e для отображения значений. 1 3 . Напишите программу, которая создает два вос ьмиэлементных массива значе­ ний типа douЫ e и ис пользует цикл для ввода значе ний восьми элементов пер­ вого массива . Программа должна накапливать в элементах второго массива суммы первого массива с нарастаю щим итогом . Например, четвертый элемент второго мас с ива долже н быть равен сумме первых четырех элементов первого массива , а пятый эле мент второго мас с ива - сумме пяти первых элементов пер­ вого массива . (Это можно сделать с помощью вложе нных циклов, однако если учесть тот факт, что пятый элемент второго мас сива раве н четве ртому эле менту второго массива плюс пятый эле мент пе рвого массива , можно избежать вло­ же нных циклов и ис пользовать для решения задачи единстве нный цикл. ) В за­ вершение вос пользуйтесь циклом для отображе ния на экране с одержимого об оих массивов, при этом пе рвый мас с ив должен отоб ражаться в пе рвой стро­ ке , а каждый элемент второго массива долже н помещаться непос редственно под с о ответствую щим элементом первого масс ива . Управля ющие операторы: цикл ы 2 57 1 4 . Напишите программу, которая читает строку входных данных, а затем печатает эту строку в обратном порядке. Вы можете запоминать входные данные в мас­ сиве значений типа ch ar ; предполагается , что строка состоит не б оле е чем из 255 символов. Напоминаем о том , что вы можете вос пользоваться функцией s can f ( ) со с пецификатором % с, чтобы выполнять посимвольное считывание с устройства ввода , а также о том, что после каждого нажатия клавиши <Enteг> генерируется символ новой строки ( \ n ) . 1 5 . Дафна делает вклад в сумме $ 1 0 0 и е же годной процентной ставкой (простой процент) 1 0 % . (То есть, е же годный прирост вклада с оставляет 1 0 % от перво­ начальной суммы . ) Дейдра вкладывает $100 с ежегодной проце нтной ставкой 5%, но процент сложный. (Это значит, что ежегодное увеличение вклада дости­ гает 5% от текущего баланса , включая предыдущий прирост вклада.) Напишите программу, которая вычисляет, сколько нужно лет, чтобы сумма на счету Дейд­ ры превзошла сумму на счету Дафны . Выведите также разме ры обоих вкладов на тот момент. 1 6 . Чаки Лаки выиграл миллион долларов , которые он кладет на счет со ставкой 8% годовых. В последний день каждого года Чаки снимает со счета по 100 тысяч долларов. Напишите программу, которая может вычислить, сколько лет прой­ дет до того , как на счету Чаки не останется денег. ГЛА ВА 7 У п р а вл я ю щие о п е р а то р ы : вет вл е н и е и безусл о в н ы е п е реходы в этой главе: • кл ю ч ев ы е сл ова: if, else, switch, continue , Ьreak, c a s e, default, qoto • Опера ции: & & 1 1 ? : • Ф ункци и : qe tchar О . putchar О . семейство ctyp e . h • • и спользование операторов if и if e l s e и вл ожен и е их д руг в друга Испол ьзование л огич еских операторов для ком б и н и рования вы ражен и й отношения в бол ее сложн ы е в ы ражен ия проверки п • Усл о в н ы е операторы • Оп ератор swit ch • • • Оп ераторы Ьreak, continue и qoto Испол ьзование фун кций символьн ого в в ода-вывод а : qetchar О и putchar О Семей ство фун кций анализа символов. п редоставляем ых з а гол овоч ным фа йлом c t:ype . h о мере обрете ния уве ренности в о б р а ще нии с опе раторами яз ыка С , вы, с ко· рее все го , захотите решать вс е более сложные задачи, и тогда вам по надо· бятся методы о рганизации и управления про е ктами. В языке С имеются не· обходимые инструме нтальные с редства . В ы уже научились пользо ватьс я циклами для выполнения многократно повторяю щихся де йствий. В этой главе мы будем изучать структуры ветвле ния , такие как i f и swi tch, которые позволя ют про грамме б а зиро· вать с во и де йствия усло виями, кото рые она проверяет. Кроме того , вы получите на· чальные с веде ния о логиче ских о пе р а циях С , кото рые позволя ют прове рять более одного отнош е ния в условных выраже ниях операторов whi l e или i f, а та кже ознако· митес ь с о пер аторами б езусловного переход а , с по мощью которых можно изме нять естестве нны й ход выполне ния программы. В заве рше ние этой главы вы получите в кратких фор мулировках всю ос но вную инфо рмацию , не обходимую для разраб отки про гра ммы , по ведение кото рой отве чает всем предъя вляемым вами тре бова ния м. 260 Гл ава 7 О п ератор i f Начне м с того , что расс мотрим простой пример использования оператора i f, по­ казанный в листинге 7 . 1 . Эта программа считывает с писок ежедневных минимальных температур (по шкале Цельсия ) в специальный с писок и выдает общее количе ство за­ писей, а также процент значе ний ниже точки замерзания воды (то е сть ниже нуля по Цельс ию ) . Для ввода этих значений программа использует в цикле функцию s c an f ( ) . В процессе каждой итерации она увеличивает значение счетчика на единицу, чтобы отслеживать количество записей. О ператор i f рас познает случаи, когда те мпература о пускается ниже нуля по Цельсию , и отдельно подсчитывает число таких случаев. Листинг 7.1 . Программа co1dda:ys . с 1 1 colddays . c - - вьNисля е т процент случ а ев , ко гда т емпер а тур а 1 1 опуск а е т ся ниже нуля #incl ude <s tdio . h > int main ( vo i d ) { co n s t int FREE Z I NG О; fl o at temper atur e ; int cold_days О; int all days О; = = printf ( " Bв eди т e список дн евных темпер атур . \ n " ) ; р r i nt f ( " Исполь зуйте шкалу Цель сия ; для з ав ершения вв едите q . \ n " ) ; whi l e ( s can f ( " % f " , & t emp er atur e ) 1) { all_day s + + ; i f ( t emp er atur e < FREEZ ING) cold_day s + + ; == i f ( all days ! 0 ) printf ( " % d - общее количество дней : % . l f % % с температурой ниже нуля . \n " , al l_days , 1 0 0 . 0 * ( float) cold_days / all_days ) ; i f ( all_days 0) print f ( " Дaнныe н е введены ! \ n " ) ; = == r e turn О ; Ниже показан результат выполнения этой уче бной программы: Вв едите список дневных темпер атур . Исполь зуйте шк алу Цель сия , для з ав ерш ения в в еди т е q . 12 5 -2 . 5 о 6 8 - 3 - 10 5 1 0 q 10 - о бщее колич е ство дней : 3 0 . 0 % дней с темпера турой ниже нуля . Проверяемое усло вие цикла whi l e использует значение , возвращаемое функцией s c an f ( ) , для пре краще ния выполне ния цикл а , когда функция s ca n f ( ) сталкивается с не числовым значени е м . Применяя тип fl o a t вместо int для значений температу­ ры, про грамма получает во зможность принимать такие показания те мпературы , как 2 . 5 и 8. - Управля ющие оператор ы : ветвление и безусловные переходы 261 В блоке whi l e появляется новый оператор: i f ( temp er atur e < FREEZ ING ) cold_days ++ ; Этот о п е р атор i f дает компьютеру ко м а нду увеличить значе ние с четчика cold_days на 1 , е сли только что считанное значение (те мпе ратуры) меньше нуля . Что произойдет , если значе ние пе ременной temp e r atur e не меньше нуля? Т огда опе ратор cold_ days ++ ; пропускаетс я , а выполнение цикла whi l e продолжается , и далее читает­ ся следую щее значение пере менной t emper atur e . В программе е щ е дважды используется оператор i f для управления циклом. Если имеются какие-либо данные , программа пе чатает результаты . Если данных нет, про­ грамма с ооб щает об этом . (В скоре вы ознакомите с ь с более эле гантным способом ре а­ лизации этой части программы.) Чтобы избежать целочисленного деления при вычислении процентного отношения , в данном примере выполняется приведение к типу float. На самом деле приведение ти­ пов здесь не требуется, поскольку входящее в выражение 1 0 О . О * cold_days / all _days подвыражение 1 О О . О * cold_days сначала вычисляется , а затем приводится к типу с пла­ ваю щей запятой в соответствии с правилом автоматического приведения типов. В то же время использование операции приведения типов документирует ваши намерения и служит защитой программы от ошибок в случае неудачных версий компиляторов. О пе ратор i f называется оператором ветвлmия или оператором въt бор а, поскольку он представляет собой узловой пункт, по достижении которого программа оказывается перед выбором, по какому из двух возможных путей следовать дальше . О бобщенная форма оператора имеет вид : i f ( выражение ) опера тор Если в ре зультате вычисле ний выражение принимает истинное (не нулевое) значе­ ние , опера тор выполняется. В противном случае он пропус кается . Как и в цикле whi l e , опера тор может быть как одиночным оператором , так и блоком опе раторов (составным опе раторо м ) . Его структура во многом похожа на структуру опе ратора whi l e . О сновное различие заклю чается в том, что в операторе i f проверка условия и (возможно ) выполнение производится все го лишь один раз , в то время как в цикле whil e проверка условия и выполнение могут повторяться многократно . Как правило, выражение является выражением отноше ния , то е сть в не м сравни­ ваются две количе стве нных величины, как это имеет ме сто в выраже ниях х > у и с == 6 . Если выражение истинно ( х больше у , либо с равно 6 ) , оператор выполняетс я . В противном случае оператор игнорируется. В об ще м случае можно ис пользовать лю­ бое выражение , при этом выражение , принимающее значение О, дает в результате "ложь" . О пе раторная часть может быть простым выражением, как, например, в следую щем программном коде , либо она может б ыть с оставным оператором или блоком, заклю­ ченным в фигурные скобки: if ( s cor e > Ь i g ) print f ( " Джe кпo т ! \ n " ) ; 1 1 пр о с той опера тор 262 Гл ава 7 i f ( j oe > ron) 1 1 сложный опера тор { j o e c a s h++ ; print f ( " Tы проигр ал , Ron . \ n " ) ; Обратите внимание , что вся эта структура i f рас сматривается как один опе ратор, даже если в ней используется составной оператор. Добавле ние к онструк ции el se к о пе ратору i f Просте йшая форма оператора i f предоставляет вам возможность выполнить опе· ратор (возможно , составной) или пропустить его . Язык С предлагает также возмож· ность выб ора одного из двух операторов, для чего служит форма i f e l s e . Воспользу­ емся формой i f el s e , чтобы заменить громоздкие сегме нты программы, показанной на листинге 7 . 1 . i f ( all_days ! = О ) printf ( " % d - общее количество дней : % . l f % % с температурой ниже нуля . \n " , all_days , 1 0 0 . О * ( fl o at ) cold_day s / al l_days ) ; i f ( all days == О ) print f ( " Дaнныe н е введены ! \ n " ) ; Если программа обнаруживает , что значение all_days не равно О , она должна знать, когда значение пе ременной all_day s должно стать равным О без дальне йшей прове рки, и она об этом знает . Располагая формой if el s e , вы можете извлечь пользу из этих сведе ний, пере писав данный фрагмент в следую ще м виде: if ( all days ! = О ) printf ( " %d - общее количе ство дней : % . l f % % с темпер атурой ниже нуля . \ n" , all_days , 1 0 0 . 0 * ( fl o at ) cold_day s / al l_days ) ; el s e print f ( " Дaнныe н е введены ! \ n " ) ; Производится только одна проверка . Если проверяемое выражение оператора i f принимает значение tru e , данные о темпе ратуре выводятся на пе чать. Если оно лож­ но , печатается предупреждаю щее сообщение . Обратите внимание на общую форму опе ратора i f el s e : i f ( выражение ) опера торl el s e опера тор2 Если выражение истинно (не равно нулю ) , выполняется опера торl . Если выражение ложно или равно нулю , выполняется один оператор , следую щий за el s e . О ператоры могут быть простыми либ о составными. Язык С не требует использования отступов, однако, это стало обще принятой практикой. Отступы позволяют визуально различать операторы , выполне ние которые зависят от ре зультатов проверки. Если вы поме стить между if и e l s e более одного оператора , вы должны восполь­ зоваться фигурными скобками, позволяю щими образовать отдельный блок операто­ ров. В приведенной ниже конструкции нарушен синтакс ис языка С , поскольку компи­ лятор ожидает обнаружить только один опе ратор между i f и el s e : Управля ющие оператор ы : ветвление и безусловн ы е переходы 263 if ( х > О ) рrint f ( "Инкр емент знач ения x : \ n " ) ; х++ ; / / приводи т к во зникно вению ошибки el s e print f ( " x <= О \ n " ) ; Компилятор трактует оператор printf ( ) как часть оператора i f, оператор х++ ; он рассматривает как отдельный опе ратор, а не как с оставную часть оператора i f. По­ этому он с читает, что el s e не принадлежит оператору i f, что является ошибкой. Вме­ сто этой конструкции оператора i f воспользуйтесь следую ще й формой: if (х > О ) { рrint f ( "Инкр емент знач ения x : \ n " ) ; х++ ; el s e print f ( " x <= О \ n " ) ; О пе ратор i f предоставляет возможность выбора - выполнять или не выполнять какое-то одно конкретное де йствие . О ператор i f el s e предлагает на выбор возмож­ ность выполнять одно из двух действий. На рис . 7 . 1 сравниваются эти два оператора . fal s e num.=2 * num ; printf ( " % d\ n " , num) ; �н el s e pri ntf ( " %d \n" , num) ; num=2 * num ; Сл едующи й операто р Р и с. 7 .1 . Операторъt i f u i f el s e 264 Гл ава 7 Е ще оди н п ример: знакомство с фун кция м и getchar ( ) и pu tchar ( ) В большинстве рас смотре нных выше примеров использовалис ь числовые входные данны е . Чтобы попрактиковаться с данными других типов, рассмотрим пример , ори­ ентированный на обработку с имволов . Вы уже знаете , как используются функции s can f ( ) и printf ( ) со спе цификатором % с, обес печиваю щие чте ние и вывод с имво­ лов, однако в этом примере вам придется иметь дело с двумя другими функциями языка С, с пе циально предназначе нными для ввода-вывода символов: getchar ( ) и putchar ( ) . Функция ge tchar ( ) не имеет аргументов, она возвращает очередной с имвол из входного потока. Наприме р, показанный ниже оператор читает следую щий входной символ и присваивает его значе ние переменной ch: ch = get char ( ) ; В ыполнение этого оператора дает тот же результат, что и выполнение опе ратора s c an f ( " % с " , & c h ) ; Функция putchar ( ) выводит на печать свой аргумент. Например, следую щий опе­ ратор выводит в виде с имвола значение , присвое нное пере менной ch: putchar ( ch ) ; Этот оператор выдает тот же ре зультат, что и оператор: printf ( " % c " , c h ) ; Поскольку эти функции предназначены только для раб оты с символами, они вы­ полняются быстрее и характеризуются большей компактностью , чем универсальные функции s can f ( ) и pr int f ( ) . Кроме того , обратите внимание , что для них не нужны спе цификаторы формата; это объяс няется те м, что они предназначены для работы только с символами. Определения этих функций об ычно с одержатся в заголовочном файле s tdio . h. (Следует отметить, что они с корее являются макросами препроце ссо­ ра , а не функциями в полном смысле этого слова ; о функционально-подоб ных макро­ сах ре чь пойдет в главе 1 6 . ) Теперь рассмотрим, как работают эти функции, н а примере программы, которая повторяет строку входных символов, но при этом заме няет каждый отличный от про­ бела символ на следую щий за ним с имвол в последовательности кодов ASCII. Пробелы в выходной последовательности сохраняются. Вы можете сформулировать требуе мый выходной ре зультат следую щим образом: "Если с имвол является пробелом, он выво­ дится на пе чать, в противном случае печатается символ, следую щий за ним в последо­ вательности кодов ASCII" . С оответствую щий программный код представлен в листинге 7.2. Листинг 7_2_ Программа cypherl _ с / * cypher l . c - - вносит изменения в о входные данные , сохр аняя пр о белы * / #incl ude <s tdio . h > / * кавычка , пр обел , кавычка * / #de fi ne S PACE ' ' int main ( vo i d ) { char ch ; Управля ющие оператор ы : ветвление и безусловн ы е переходы 265 / * читать симв ол * / / * е сли э то н е симво л конца строки * / c h = get char ( ) ; whi l e ( ch ! = ' \ n ' ) { S PACE ) i f ( ch putchar ( ch ) ; el s e putchar ( ch + 1 ) ; ch = getchar ( ) ; / * изменить др угие символы * / / * в з я т ь следующий символ * / putchar ( ch ) ; / * печ атать символ новой строки * / / * о с тавить пр обел н е тронутым * / / * симво л не меня ется * / r e turn О ; В ре зультате выполне ния этой программы получаем следую щий ре зультат : CALL ИЕ ВАL . DВММ NF I BM/ Сравним этот цикл с циклом из листинга 7 . 1 . С целью определить момент прекра­ ще ния выполнения цикла программа в листинге 7 . 1 использует бит состояния , воз· вращаемый функцией s c an f ( ) , а не значение эле мента ввода . Программа из листинга 7.2, однако, ис пользует значение с амого эле мента входных данных для определения, когда нужно пре кратить выполнение цикла. Это обстоятельство определяет не боль­ шое различие структур расс матривае мых циклов , когда в одном случае оператор с чи­ тывания предше ствует циклу, а в другом случае оператор считывания находится в конце цикла. В то же вре мя гиб кий синтаксис языка С позволяет эмулировать про­ грамму листинга 7 . 1 за счет совмещения операций считывания и проверки условия в одном выражении. То есть, вы можете заменить цикл вида ch = get char ( ) ; whi l e ( ch ! = ' \ n ' ) ch = getchar ( ) ; / * читать симв ол * / / * е сли э то не конец строки * / / * о брабо тать символ * / / * в з я т ь следующий символ * / на цикл, который выглядит следую щим образом: whi l e ( ( ch = g etchar ( ) ) ! = ' \n ' ) /* о брабо тать символ * / Интерес вызывает приведенная ниже строка: whi l e ( ( ch = g etchar ( ) ) ! = ' \n ' ) О на демонстрирует стиль программирования , характе рный для программирова­ ния на языке С: сочетание двух де йствий в одном выражении. Механизм свободного форматирования языка С можно использовать для того , чтобы ле гче было определить назначение отдельных компонентов строки: whi l e ( ( ch = getch ar ( ) ) ! = ' \n ' ) 1 1 при свои т ь знач ение п ер еменной ch 11 ср авнить ch с \n 266 Гл ава 7 Действиями являются опе рация присваивания конкретного значе ния пере менной ch и с равнение этого значе ния с символом новой строки. Круглые скобки, охваты­ ваю щие конструкцию ch = getch ar ( ) , делают ее ле вым опе рандом операции ! =. Что­ бы вычислить это выраже ние , сначала вызывается функция get char ( ) , после чего возвращае мое ею значе ние прис ваивается пере менной ch . Поскольку значе ние м вы· ражения присваивания является значение пе ременной в левой части операции, зна­ че ние вс ей операции ch = g etchar ( ) е сть именно новое значение переменной ch. По­ этому, после того , как значение ch считано , проверяемое условие сводится к отноше· нию ch ! = ' \ n ' (то е сть, к утверждению , что значение переменной ch 'Нерав'Но символу новой строки ) . Языковая конструкция подоб ного рода очень часто применяется в программиро· вании на С , и вы должны быть с не й знакомы. Вы должны также твердо знать, как пользоваться круглыми скобками, чтобы правильно объединять подвыражения в группы . Ни одну пару с кобок опустить нельзя, все скобки необходимы . Предположим, что вы по ошибке использовали следую ще е выражение : whi l e ( ch = ge tchar ( ) ! = ' \n ' ) О пе рация ! = имеет более высокий приоритет, чем =, следовательно , пе рвым вы· числяется выражение g etchar ( ) ! = ' \ n ' . Пос кольку это условное выражение , оно принимает значение 1 или О. Затем это значение прис ваивается переменной ch. От· сутствие скобок означает, что пере менной ch прис воено значение О или 1 , а не воз· вращаемое значение функции ge tchar ( ) ; но это не то , что нам нужно . О пе ратор putchar ( ch + 1 ) ; /* изменить др угие символы * / служит еще одной иллюстрацией того, что символы хранятся как целые числа . В вы· ражении ch + 1 тип пере менной ch расширяется до int с тем, чтобы можно выполнить вычисле ния , а получе нный ре зультат пе редается в функцию p utchar ( ) , которая при­ нимает аргумент типа int , но при этом ис пользует только завершающий байт , чтобы определить, какой символ необходимо выве сти на экран. Семейство си м вол ьных фун кций ctype . h Обратите внимание , что из выходных данных программы, представленной в лис­ тинге 7.2, следует, что точка преоб разуется в косую черту; это объясняется те м, что в клас сификации ASC II код символа косой че рты на единицу больше , чем код символа точки. Однако если ос новная цель программы с о стоит в том, чтобы выполнять преоб· разование только букв, б ыло бы неплохо оставлять неизме нными все символы , отлич­ ные от букв, а не только пробелы . Логические операции, которые обсуждаются ниже, дают нам в распоряже ние средства , с помощью которых можно проверить, не являет· ся ли с имвол пробелом , запятой и так дале е , но если пере числять вс е возможные ва­ рианты, то это будет довольно громоздкая процедура. К с частью , в ANSI С имеется стандартный набор функций для анализа символов; заголовочный файл ctyp e . h с о­ держит соответствую щие прототипы. Эти функции принимают символ в качестве ар­ гуме нта и возвращают ненулевое значение (tru e ) , если символ принадлежит не кото· рой конкретной категории, и ноль ( fal s e ) в противном случае. Управля ющие оператор ы : ветвление и безусловн ы е переходы 267 Например, функция i s alph a ( ) возвращает ненулевое значение , если е е аргуме н· том является буква . Листинг 7.3 становится обобщение м листинга 7.2 благодаря ис· пользованию этой функции; она также соде ржит укороче нную структуру цикла, кото­ рый мы только что рассмотрели. листинг 7 .3 Программа cypher2 . с 1 1 cypher 2 . c - - меня е т симво лы вхо дных д анных , о ставляя н еизменными символы, 11 не являющи е ся букв ами #incl ude <s tdio . h > #incl ude <ctyp e . h > 1 1 для функции i s alpha ( ) int main ( vo i d ) { char ch ; whi l e ( ( ch = g etchar ( ) ) i f ( i s alpha ( ch ) ) putchar ( ch + 1 ) ; el s e putchar ( ch ) ; ! = ' \n ' ) ! / е сли э то букв а , ! / изменить е е 1 1 в противном случ ае 11 выв е сти символ таким , каким он е сть 1 1 печ атать символ новой строки putchar ( ch ) ; r e turn О ; Ниже показаны ре зультаты выполнения программы ; обратите внимание , что строчные и прописные буквы изменяются , а проб елы и знаки препинания - нет: Look! lt ' s а proqranпer ! Mppl ! Ju ' t Ь q sphsbn n f s ! В таблицах 7 . 1 и 7.2 представлен перечень нес кольких функций, использование ко­ торых становится возможным в результате включе ния в программу заголовочного файла ctyp e . h. В некоторых записях упоминается локализация , под которой понима· ются средства языка С, позволяю щие учитывать ре гиональные настройки и модифи· цирую щие или расширяю щие б азовое использование С . (Наприме р, во многих стра· нах используется запятая вместо де сятичной точки при записи дробных частей, кон· кретные местные условия могут указать, что язык С использует запятую в выходных данных с плаваю щей запятой, отоб ражая, с каже м , 1 2 3 . 4 5 как 1 2 3 , 4 5 . ) О братите вни· мание , что функции отображе ния не изме няют исходный аргумент, а вместо этого возвращают модифицированное значение . То есть, tolower ( ch ) ; 1 1 не вызыв а е т изменений ch не меняет значе ние пе ременной ch. Чтобы изменить ch, выполните следующее : ch = tol ower ( ch ) ; / / пр еобр азовать ch к нижн ему р е гистру 268 Гл ава 7 Таблица 7.1 . Функции проверки символьных значений из заголовочного файла ctype . h Имя функц ии Ис11Шиио, если ар zу.меит i s alnum ( ) Алфавитно-цифровой (буквенный или цифровой) . i s alpha ( ) Алфавитный. i sЬlank ( ) Стандартный пробельный символ (пробел, горизонтальная табуляция или символ новой строки) или любой дополнительный местный сим· вол, описанный подобным способом. i s cntrl ( ) Управляющий символ, такой как <Ctrl+B> . i s digit ( ) Цифра. i s gr aph ( ) Любой печатный символ, отличный от пробела. i s lower ( ) Символ ЮIЖнего регистра. i sp r i nt ( ) Печатный символ. i spunct ( ) Знак пунктуации (любой печатный символ, отличный от пробела и алфавитно-цифрового символа) . i s sp a c e ( ) Пробельный символ (пробел, символы новой строки, перевода стра­ ницы, возврата каретки, вертикальной табуляции, горизонтальной табуляции и, возможно, другие локально определенные символы) . i s upp er ( ) Символ верхнего регистра. i s xdi git ( ) Символ шестнадцатеричной цифры. Таблица 7.2. Функции отображения символов из файла ctype . h Имя функц ии Действие tolower ( ) Если аргумент является символом верхнего регистра, эта функция возвращает его версию для нижнего регистра ; иначе возвращает ис­ ходное значение аргумента. toupp er ( ) Если аргумент является символ нижнего регистра, эта функция воз· вращает его версию для верхнего регистра ; иначе возвращает исход­ ное значение аргумента. М ножествен н ы й выбор el se if Жизнь часто предоставляет вам выбор и з более ч е м двух вариантов. Можно рас­ ширить структуру i f el s e путем включения конструкции el s e i f, чтобы быть гото· вым к ситуациям подобного рода . Рассмотрим конкретный пример. Компании, пре­ доставляю щие коммунальные услуги, часто выставляют тарифы за использование эле ктроэнергии, которые зависят от количества электроэнергии, потребленного кли­ ентом. Ниже приводятся данные о тарифах на оплату потребле нной электроэнергии в киловатт-час ах (кВт/ч) одной из таких компаний: Первые 3 6 0 кВ т/ч : Следующи е 3 2 0 кВт/ч : Свыше 6 8 0 кВт/ ч : $ 0 . 1 1 4 3 9 з а 1 кВ т/ч $ 0 . 1 3 2 9 0 з а 1 кВ т/ч $ 0 . 1 4 0 2 2 з а 1 кВ т/ч Управля ющие оператор ы : ветвление и безусловн ы е переходы 269 Если вы намерены ве сти учет расхода электроэнергии , вам , возможно , понадобит· ся программ а , подсчитывающая стоимость потребленной электроэнергии. Програм· ма, представленная в листинге 7.4, может стать первым шагом в этом направлении. Листинг 7.4. Программа e1ectric . с / * el e ctri c . c - - подсчи тыв а е т сумму для сч ета з а электроэнергию * / #incl ude <s tdio . h > #de fi ne RAT E l 0 . 1 2 5 8 9 / * тариф з а первые 3 6 0 кВ т/ч * / #de fi ne RAT E 2 0 . 1 7 9 0 1 / * тариф з а следующи е 3 2 0 кВт/ч * / #de fi ne RAT E 3 0 . 2 0 9 7 1 / * тариф , ког да р асход пр евьШJает 6 8 0 кВт/ч * / / * первая точ к а р а зр ыв а тарифов * / #de fi ne BREAK l 3 6 0 . О / * в торая точ к а р а зр ыв а тарифов * / #de fi ne BREAK2 6 8 О . О #de fi ne BAS E l ( RATE l * BREAK l ) / * стоимо сть 3 6 0 кВ т/ч * / #de fi ne BAS E 2 ( BASE l + ( RATE 2 * ( B REAK2 - BREAK l ) ) ) / * стоимо сть 6 8 0 кВ т/ч * / int main ( vo i d ) { / * изр а сходов анные киловат т-ч асы * / do uЫ e kwh ; / * сумма к опл ате * / do uЫ e Ь i l l ; printf ( " Bв eди т e колич е ство изра сходов анной электроэнер гии в кВт/ч . \ n " ) ; / * % l f для тип а douЫ e * / s c an f ( " % l f " , & kwh ) ; i f ( kwh <= BREAK l ) bill = RATE l * kwh ; el s e i f ( kwh <= BREAK2 ) / * колич ество кВ т/ч в промежутке о т 3 6 0 до 6 8 0 * / BASE l + ( RATE 2 * ( kwh - BREAK l ) ) ; bill el s e / * колич е с тво кВ т/ч пр евьШJ а е т 6 8 0 * / bill BASE 2 + ( RATE 3 * ( kwh - BREAK2 ) ) ; printf ( " Cyммa к опл ате з а % . l f кВт/ч составля е т $ % 1 . 2 f . \ n " , kwh , Ь i ll ) ; r e turn О ; Ниже показаны выходные данные этой программы : Вв едите колич е ство изр а сходов анной электроэнергии в кВ т/ч . 580 Сумма к оплате з а 5 8 0 . 0 кВт/ч с о с тавл я е т $ 8 4 . 7 0 . В программе из листинга 7.4 для представления тарифов применяются символьные константы , при этом для удоб ства они с об раны в одном месте . Если электриче ская компания меняет свои тарифы (что вполне возможно ) , то пос кольку они определены в одном месте, задача их обновления суще ственно облегчается . В этом листинге также используются символьные обозначения значений расхода, в которых тарифы меняют свои значения (так называе мые точки разрыва ) . Их тоже время от времени меняют . Константы BASE l и BASE2 выраже ны через тарифы и точки разрыва. Далее , если та· рифы и точки разрыва прете рпе вают изме не ния , значения BASE l и BASE2 обновляют· ся автоматически. Зде с ь полезно напомнить, что пре процессор не выполняет вычис· лений. Там, где программе появляется константа BASE l , она заменяется произведением О . 1 2 5 8 9 * 3 6 0 . О . Можете не беспокоиться , компилятор вычислит числовое значение этого выражения ( 4 5 . 3 2 О 4 ) , так что в окончательной редакции программы использу· ется уже число 4 5 . 3 2 О 4 , а с оответствую щие вычисле ния не выполняются . 270 Гл ава 7 Ход выполне ния этой программы достаточно прост. О на выбирает одну из трех формул в зависимости от количества кВт/ч израсходованной электроэнергии. Поток управления этой программы показан на рис . 7.2. О собое внимание следует обратить на то, что программа может выйти на первый el s e , если значение пе ременной kwh равно или б ольше 3 6 0 . По этой причине строка el s e i f ( kwh <= BREAK2 ) фактичес ки эквивале нтна тре бованию , чтобы значение kwh находилось в пределах от 3 6 0 до 6 8 0 , как указано в комментарии к программ е . Аналогично , завершающий el s e может быть достигнут , только когда значе ние kwh превысит 68 О. И, наконец, обратите внимание на то , что константы BAS E l и BASE 2 представляют с обой общую стоимость, соответст­ венно , пе рвых 360 и 680 киловатт-часов. Поэтому тре буется только приб авить допол­ нительную плату за количе ство потре бленной энергии, превышающее эти величины . �н else if true else Ьill=RAТEl *kwh Ьill=BASE2+ RАТЕЗ* ( kwh-BREAК2 ) Ьill=BASEl+ RАТЕ2 * ( kwh-BREAКl) Р и с. 7 . 2 . Поток управлеиия программъ; el e c t r i с . с из листиига 7. 4 . Фактичес ки конструкция e l s e i f является видоизме не нным с пособом задания ус­ ловного опе ратора , с которым вы ознакомились раньше . Например , ядро расс матри­ ваемой программы представляет с обой другую форму запис и следую щей последова­ тельности опе раторов: if ( kwh <=BREAK l ) bill = RATE l * kwh ; el s e i f ( kwh <=B REAK2 ) bill BASE l + RATE2 * ( kwh - B REAK l ) ; el s e b i l l = BASE2 + RATE3 * ( kwh - B REAK2 ) ; То есть программа состоит из оператора i f els e , часть el s e которого представляет собой другой оператор i f el s e . Про второй оператор i f el s e говорят, что он вложеи в первый. Напомним, что вся структура i f el s e считается одним оператором. Управля ющие оператор ы : ветвление и безусловн ы е переходы 271 Вот почему не обязательно заклю чать вложенную конструкцию i f e l s e в фигурные скобки. В то же время использование скобок проясняет назначение этого конкретного формата. Эти две формы практически эквивалентны . Единственное различие между ними заклю чается в использовании пробелов и символов новой строки, в то же вре мя эти различия компилятором игнорируются . Тем не менее , первая форма предпочтитель­ не е , поскольку из не е сразу видно, что необходимо делать выбор одной из трех воз­ можносте й. Кроме того, она облегчает просмотр программы и понимание е е се манти­ ки. Сохраняйте все отступы для вложенных форм, так как они могут понадобиться, например, когда у вас возникнет не обходимость проверки двух разных величин. При­ мером могла бы служить 1 0 %-ная дополнительная плата за потре бление энергии с вы­ ше 680 киловатт-часов в летние месяцы. В одном опе раторе можно использовать столько опе раторов el s e i f , сколько вам нужно (разумеется , в пределах возможностей компилятора ) , как показывает следую­ щий фрагме нт: if ( s cor e < 1 0 0 0 ) bonus О; el s e i f ( s co r e < 1 5 0 О ) 1; bonus el s e i f ( s co r e < 2 0 0 О ) bonus 2; el s e i f ( s co r e < 2 5 0 О ) bonus 4; el s e bonus 6; = = = = (Этот фрагме нт может быть взят из игровой программы, в которой переме нная bonus представляет собой количе ство дополнительных " фотонных бомб" или "пита­ тельных таблеток", которые получает игрок, чтобы продолжить игру в следую щем ра­ унд е . ) Что касается возможностей компилятора , то стандарт С99 требует, чтобы компи­ лятор поддерживал не менее 1 2 7 уровне й вложения . Объединение el se и if в па ры Когда в программе присутствует множе ство операторов i f и el s e , к а к удается ком­ пилятору разобраться , какой i f какому e l s e соответствует? В качестве примера рас­ смотрим следую щий фрагмент программы: if ( nurnЬ er > 6 ) i f ( nurnЬ er < 1 2 ) p r i nt f ( " Bы з акончили игру ! \ n " ) ; el s e print f ( " K сожалению , в ы потеряли право хода ! \ n " ) ; В каком случае фраза К сожалению , вы пот еряли пр аво хода ! будет напе чатана? Когда значе ние переменной nurnЬ er меньше или равно 6, или когда значение nurnЬer больше 1 2 ? Другими словами, чему соответствует el s e , пе рвому if или второму? Правильный ответ такой: e l s e относ ится ко второму i f . То есть, вы получите сле­ дую щие ответы : 272 Гл ава 7 Число Результат 5 Нет ответа 10 Вы з акончили игру ! 15 К сожале нию , в ы потеряли пр аво хода ! С оглас но суще ствую ще му правилу, el s e соответствует ближайшему i f, кроме тех случаев, когда с помощью фигурных с кобок задается другой порядок (рис . 7.3 ) . [ if { условие) Сделать это ; if { условие) Сделать это ; else �������-+-­ Сделать это ; Иначе else относится к ближ айшему if if { условие) { Сделать это ; if { условие) Сдела ть это ; else Сделать это ; Иначе else относится к первому if, поскольку внутренний оператор закл ю чен в фигурные скоб ки Ри с. 7 . 3 . Правило обr.е(}uнения i f и e l s e в пары Мы сознательно расставили отступы таким образом , как будто el s e соответствует первому i f, при этом еще раз обращаем ваше внимание на то , что компилятор игно­ рирует отступы. Если вы на с амом деле хотите , чтобы el s e относ ился к пе рвому i f, вы могли бы запис ать этот фрагмент следую щим образом : i f ( nurnЬ er > 6 ) { i f ( nurnЬ er < 1 2 ) p r i nt f ( " Bы з акончили игру ! \ n " ) ; el s e print f ( " K сожалению , в ы потеряли право хода ! \ n " ) ; Управля ющие оператор ы : ветвление и безусловн ы е переходы 273 Теперь в ы получите следую щие ответы: Число Результат 5 К сожале нию , в ы потеряли пр аво хода ! 10 Вы з акончили игру ! 15 Нет ответа Большее ч и сло вложен и й операторов if В ы уже имели возможность убедиться в том, что последовательность i f . . . el s e i f . . . el s e является одной и з форм вложе нного оператора i f , который производит выбор из некоторого набора вариантов. Другой вид вложе нного опе ратора i f исполь· зуется в тех случаях, когда сделанный конкретный выбор приводит к дополнительно· му варианту. Например, программа может использовать оператор i f el s e для выбора между мужчинами и женщинами. Каждая ветвь в рамках оператора i f el s e может с о­ держать другой опе ратор i f el s e , чтобы, с каже м , провести различие между группами лиц с разным ГОД ОВЫМ доходом. Применим эту форму вложе нного опе ратора if для реше ния следую щей задачи: для заданного целого числа рас печатать вс е целые числа, на которые заданное число делится без остатка, а если таких делителей нет, выве сти сообщение о том , что задан· ное число является простым. Такая задача тре бует реше ния некоторых принципиальных вопросов, прежде чем приступать к написанию собственно кода. Прежде вс его , требуется разработать об· щую схему программы . Для удобства программа должна использовать цикл для ввода числа, подлежащего ис следованию на предмет его делителе й. Благодаря циклу нет не· обходимости запускать программу всякий раз , когда вы хотите проверить новое чис· ло. Ниже представлена модель такого цикла: приглаш е ние по ль зова телю на ввод числ а пока функция s caп f ( ) возвращае т знач е ние 1 выполнить анализ числа и соо бщить р е зуль таты приг л ашение пол ь з ов а т е лю на ввод числа В с помните , что с помощью функции s c an f ( ) в условии проверки цикла программа пытается прочитать число и выполнить проверку с те м, чтобы определить, необходи· мо ли прекратить работу цикла. Далее потре буется выраб отать план определения делителе й. Возможно, наиболее оче видным подходом является нечто в этом роде : for ( div = 2 ; div < num ; div++ ) i f ( n um % div == О ) printf ( " % d делится н а % d\ n " , num , div ) ; В этом цикле проверяются все числа в промежутке между 2 и num для нахождения тех из них, которые делят значение num без остатка . К сожалению , такой подход тре· бует больших затрат машинного времени. Можно найти гораздо б оле е экономное ре· ше ние . Расс мотрим, например, процедуру поиска делителей числа 1 44. Выясняется, что 1 4 4 % 2 равно О , что означает, что 1 4 4 делится на 2 без остатка . Если вы теперь выполните обычную операцию деления 1 44 на 2, вы получите 72, это число также яв­ ляется делителем числа 1 44, следовательно, в случае ус пешной проверки num % div, вы получите с разу два делителя вместо одного . 274 Гл ава 7 Однако главное достоинство такого подхода с о стоит в том, что изменяются преде­ лы при проверке условия конца цикла. Чтобы понять, как раб отает этот подход , ис­ следуйте пары делителей, полученных в проце с с е выполнения цикла: 2 , 72 , 3 , 48, 4 , 3 6 , 6 , 24, 8 , 1 8 , 9 , 1 6 , 1 2 , 1 2 , 1 6 , 9 , 1 8 , 8 и т а к дале е . Т а к вот в ч е м дело ! После пары 1 2 , 1 2 в последовательности встре чаются т е ж е де­ лители (в обратном порядке ) , которые уже были найдены . В место того чтобы про­ должать цикл до 1 4 3 , вы можете завершить его сразу после того , как достигли 1 2 . Это делает ненужным выполнение большого количества итераций ! О бобщая это открытие , вы убе ждаетесь, что для проверки, достиг ли цикл значе­ ния , равного квадратному корню из num, а не значения num. Для таких чисел, как 9 , выигрыш н е слишком велик, н о для чисел порядка 1 0000 и б олее он огромен. При этом вместо того , чтобы извлекать квадратные корни, вы можете сформулировать прове ряемое условие следую щим образом: for ( div = 2 ; ( div * div ) <= num ; div++ ) i f ( n um % div == О ) printf ( " % d д е л и т с я н а % d и % d . \ n " , num , div , num / div ) ; Если переме нная num принимает значе ние 1 4 4 , цикл выполня ется вплоть до div = 1 2 . Если num принимает значе ние 1 45 , цикл выполняется вплоть до div = 1 3 . Такая прове рка предпочтительне е проверки с вычислением квадратных корней, по меньшей мере, по двум причинам. В о-первых, компьюте р выполняет умножение целых чисел гораздо быстрее, чем извлече ние квадратного корня . В о-вторых, фор­ мально мы еще не знакомы с функцией вычисления квадратного корня . Н а м надо найти подход к ре шению е ще двух проблем , и только после этого м ы бу­ де м готовы приступить к напис анию кода. Пе рвая из них: что делать, если прове ряе­ мое число представляет собой точный квадрат? С ообщение о том, что число 1 44 де­ лится на два числа 12 и 12 не сколько ис кусстве нно , однако можно предус мотреть вло­ же нный оператор i f, в котором прове рить, равно ли div значе нию num / div. Если это так, программа должна рас печатать один делитель вместо двух. for ( div = 2 ; ( div * div ) <= num ; div++ ) { i f ( n um % div == О ) { i f ( div * div ! = num ) print f ( " % d д е ли т ся на % d и % d . \ n " , num , div , num / div ) ; el s e print f ( " % d д е ли т ся н а % d . \ n " , num , div ) ; Н а зам етку ! С технической точ ки зрения оператор i f el s e рассматривается ка к отдепьны й оператор, спедовател ьно, за кп ючать е го в фигурные с кобки н е т ребуетс я . Вне шний оператор if таюке представпяет собой отдеп ьны й о ператор, поэтом у с кобки для не­ го не нужн ы . Тем не менее, когда операторы становятся сл ишком дл инн ы м и , скобки по мога ют понять, что про и сходит, они таюке служат за щитой, е сл и вы в дал ьнейше м добавите е ще один о пе ратор в i f ил и в ц и кп . Управля ющие оператор ы : ветвление и безусловн ы е переходы 275 С другой стороны , как в ы можете установить, что число простое? Если значение переменной num является простым, то поток управления программы никогда не попа· дет внутрь оператора i f . Чтобы решить эту проблему, можно присвоить некоторой переменной конкретное значение , скаже м , 1, за пределами цикла и присвоить ей зна· че ние О внутри опе ратора i f. Затем, после того как цикл будет завершен, можно про­ верить, сохранила ли эта переменная значе ние 1 . Если сохранила, то управле ние ни разу не передавалось этому оператору i f, и в с илу этого об стоятельства число являет· ся простым. Часто такая переме нная называется флагом. Обычно в языке С для флагов ис пользуется тип int, в то же время новый тип _Bool наилучшим образом подходит для этой цели. Более того , вклю чив в программу заголо· вочный файл stdЬool . h, вы можете воспользоваться клю чевым словом b o o l , вместо _Bool для обозначения этого типа и использовать идентификаторы true и fal s e вме· сто , с о ответственно , 1 и О . В программе , представленной в листинге 7.5, реализованы все упомянутые идеи. Чтобы расширить область приме не ния , программа использует тип long вместо int. (Если ваша система подде рживает тип _Bo o l , вы можете выбрать тип int для пере менной i s Prime и применять 1 и О вместо true и f al s e . ) листинг 7.5. Программа divisors . с 1 1 di vi s or s . c - - вложенные о п ер а торы i f отобр ажают на экр ане делители 1 1 з аданног о числ а #incl ude <s tdio . h > #incl ude <s tdЬool . h> int main ( vo i d ) { un s i gned long num ; / / анализируемо е число un s i gned long div ; / / в о зможные делители bool i s Prime ; / / ф лаг пр о стог о числ а printf ( " Bв eди т e цело е число для анали з а ; " ) ; printf ( " для з а в ершения вв едите q . \ n " ) ; whi l e ( s can f ( " % l u " , &num) == 1 ) { for ( div = 2 , i s Prime= true ; ( div * div ) <= num ; div++ ) { i f ( num % div == 0 ) { i f ( ( div * div ) ! = num ) print f ( " % lu дели тся на % l u и % lu . \ n " , num , div , num / div ) ; el s e printf ( " % l u д елится н а % l u . \ n " , num , div ) ; / / число н е явля е тся про стым i s Prime= fal s e ; i f ( i s Prime ) printf ( " % l u я вля ется про с тьПvl. чи слом . \ n " , num) ; print f ( " В в е дите следующе е чи сло для анали з а ; " ) ; print f ( " для з ав ершения вв еди т е q . \ n " ) ; p r i nt f ( " B ceгo хорош е г о . \ n " ) ; r e turn О ; Гл ава 7 276 Обратите внимание на то , что программа ис пользует опе рацию запятой в управ­ ляю щем выражении цикла for , чтобы предоставить вам возможность инициализации переменной i s Pr ime значе нием true при каждом вводе нового числа. Ниже показан пример выполнения этой программы: Вв едите целое число для анали з а ; для з ав ерш ения вв еди т е q . 36 3 6 дели т ся на 2 и 1 8 . 3 6 дели т ся на 3 и 1 2 . 3 6 дели т ся на 4 и 9 . 3 6 дели т ся на 6 . Вв едите следующе е число для анали з а ; для з а в ерше ния вв едите q . 14 9 1 4 9 явля е тся про стым числом . Вв едите следующе е чи сло для анали з а ; для з а в ерше ния вв едите q . 30077 3 0 0 7 7 делится на 1 9 и 1 5 8 3 . Вв едите следующе е чи сло для анали з а ; для з а в ерше ния вв едите q . q Данная программа рассматривает 1 как простое число, которое в техничес ком ас· пекте таковым не является. Логические опе рации, обсуждае мые в следую щем разделе , позволят вам исклю чить 1 из спис ка простых чисел. Сводка: и сп ольз ован и е операторов if для п р и н яти я р ешен и й Кл ючевь1е слова: i f , el s e Комментарии о бще го характера: В каждой из привод и м ых н иже форм опера тором может быть либо простой, л ибо со­ ста вной о пе ратор. Истинн ы м я вл я ется вы ражение, прин имающее ненулевое значение. Форма 1 : if ( выражение ) опера тор опера тор вы полняется , ко гда выражение принимает истинное значение. Форма 2 : if ( выражение ) опера торl el s e опера тор2 Есп и выражение и ст инно , вы полняется опера торl , в противно м случае Форма З : if ( выражениеl ) опера торl el s e i f ( выражение 2 ) опера тор2 else опера торЗ - опера тор2. Управля ющие оператор ы : ветвление и безусловн ы е переходы 277 Есл и выражениеl ист инно , вы полняется опера торl . Если выр ажение l ложно, н о вы­ ражение2 ист инно, вы полняется опера тор2. Если оба вы ражения ложны , вы полняется опера тор З. П ример: i f ( l egs == 4 ) print f ( " Пo в с ей видимо сти , э то лош адь . \ n " ) ; el s e i f ( l egs > 4 ) print f ( " Э тo не лошадь . \ n " ) ; / * случ ай , когда ног мен ьше 4 * / el s e l e gs + + ; print f ( " T e п ep ь у н е е н а одну ногу больше . \ n " ) ; Д авайте будем л оги ч н ы ми Как вы уже имели возможность убедиться, в операторах i f и whil е в качестве уело· вий проверки часто ис пользуются условные выражения. В ремя от време ни вы находи· те поле зным приме нять сочетание двух или большего количе ства выражений. Напри· мер, предположим, что вы хотите иметь программу, которая подсчитывает количе ст· во символов, отличных от одиночных и двойных кавычек, во входном предложении. Для этой цели вы можете использовать логичес кие опе рации и символ точки ( . ) для обозначе ния конца предложе ния . В листинге 7.6 показана короткая программа , ре· шаю щая эту задачу. Листинг 7.6. Программа chcount . c 1 1 ch count . c - - и споль з о в ани е логич е ског о опер атор а AND #incl ude <s tdio . h > #de fi ne PER I OD ' int main ( vo i d ) { int ch ; int char count = О ; whi l e ( ( ch = g etchar ( ) ) ! = PERI OD ) i f ( ch ! = & & ch != ' \ " ) char count++ ; "" printf ( " B данном пр е дложе нии со держат ся % d симво лов , о тличных о т кавыч ек . \ n " , char count ) ; r e turn О ; Так выглядит приме р выполнения этой программы : я ве чита .в б е с тс е .в.в ер "Я вич е r о ве с•ис.вю в про r ра1!11!1Ир овавии " . В данном пр едложении содержатся 6 0 символов , о тличных о т кав ычек . Гл ава 7 278 В ыполнение программы начинается со считывания символа и проверки, не явля­ ется ли этот символ точкой, пос кольку точка обозначает конец предложе ния . Далее в программе появляется нечто новое - логическая опе рация "И" , представляемая с по­ мощью & & . Вы можете истолковать оператор i f следую щим образом : е сли символ не является двойной кавычкой И не является одиночной кавычкой, увеличить значение переменной char count на 1 . Чтобы выраже ние б ыло истинным, необходимо , чтобы истинными были оба усло­ вия. Логиче ские операции имеют более низкий приоритет, чем условные операции, так что нет необходимости в ис пользовании дополнительных круглых скобок для формирования подвыраже ний. В языке С име ются три логических операции: Опер ац ия Зиа-ч ение && и 1 1 Или Не Предположим , что exp l и ехр 2 - два простых условных выражения, такие как, на­ приме р, cat > r at и deЬt == 1 0 0 0 . Т огда вы можете утве рждать: • • • exp l & & ехр 2 истинно тогда и только тогда, когда оба выражения exp l и ехр 2 истинны . exp l 1 1 ехр 2 истинно, когда одно из выраже ний exp l и ехр 2 истинно либ о об а выраже ния истинны . ! ехр 1 истинно, если ехр 1 ложно , и ложно , если ехр 1 истинно. Расс мотрим не сколько конкретных примеров: 5 > 2 && 4 > 7 ложно , поскольку истинно только одно из подвыражений. 5 > 2 1 1 4 > 7 ! (4 > 7) истинно , ибо , по меньшей мере , одно из подвыражений истинно . истинно , пос кольку 4 не больше 7. Последне е выражение фактичес ки эквивалентно следую ще му выражению : 4 <= 7 Если вы не знакомы с логическими операциями или недостаточно преуспели в их примене нии, не забывайте простой истины : ( пр актик а & & вр емя ) == со в ершен ство Альтернати вное предста влен ие: заголовоч н ы й файл i s o 6 4 6 . h Язык С разраб атывался в США на с истемах, использую щих клавиатуры, построе н­ ные по стандартам, действую щим в США. Однако в других странах мира не во всех клавиатурах присутствуют с имволы, принятые в США. В с илу этого об стоятельства стандарт С99 вводит альтернативные формы написания логических операций. О ни определяются в заголовочном файле i s o 6 4 6 . h. Если вы вклю чили этот файл в свою программу, то можете ис пользовать and вме сто & & , or вместо 1 1 и not вместо ! . Управля ющие оператор ы : ветвление и безусловн ы е переходы 279 Например, фрагмент i f ( ch ! = " ' & & ch ! = char count++ ; ' ' \' ') можно пе ре писать следую щим образом: i f ( ch ! = " ' and ch ! = ' \ ' ' ) char count++ ; ' В ы можете сделать свой выбор с помощью таблицы 7.3 ; представления нетрудно запомнить. По сути дела у вас может возникнуть вопрос , почему в языке С не исполь­ зуется новая терминология . Ответ, скорее все го , заклю чается в том , что в историче­ ском плане язык С все гда пытался обходиться минимальным количе ством клю чевых слов. В с правочном разделе VII (приложение Б) приводится список альтернативных форм запис и не которых операций, с которыми вам е ще не приходилось сталкиваться . ТабJJица 7_3_ АJJьтернативное представJJение JJогических операций Предлаzаемая заzоловочиьLМ файлом iso6§6 . h Традиционная форма && and 1 1 or Not Приоритеты опера ци й О пе рация ! имеет очень высокий уровень приоритета - выш е , чем операция ум­ ножения, - который раве н приоритету операции инкремента ; выше его только при­ оритет круглых с кобок. О пе рация & & имеет более выс окий приоритет, чем 1 1 , и обе они по приоритету уступают условным операциям , но превосходят опе рацию при­ сваивания . В связи с этим выражение а > Ь && Ь > с 1 1 Ь > d инте рпретируется следую щим образом: ( ( а > Ь) && (Ь > с ) ) 1 1 (Ь > d) Иначе говоря , значе ние Ь находится между а и с или Ь больше d. Многие программисты используют, как и во второй версии, скобки даже там, где в них нет необходимости. В этом случае смысл ясен, даже е сли читатель не очень хоро­ шо помнит приоритеты конкретных логических опе раций. Порядок в ы ч ислен ия вы ражений Помимо случаев, когда две опе рации с о вместно ис пользуют один и тот ж е операнд, язык С в общем случае не устанавливает , какие части сложного выраже ния вычисля­ ются первыми. Наприме р, в приведе нном ниже выражении подвыраже ние 5 + 3 мо­ жет быть вычислено раньше , чем будет вычисле но подвыражение 9 + 6 , но может быть вычислено и после: appl e s = (5 + 3 ) * ( 9 + 6 ) ; 280 Гл ава 7 Такая не однозначность оставлена в С с таким рас четом, чтобы разработчики ком· пилятора могли сделать наиболее оптимальный выбор для конкретной системы . Единстве нным исклю чением из этого правила является порядок выполнения логиче· ских опе раций (или отсуrствие такого правила ) . С устанавливает порядок вычисления логических выражений слева направо . О перации & & и 1 1 это точки последователь· ности, так что все побочные эффе кты проявятся до того , как программа пе рейдет от одного опе ранда к другому. Более того , компилятор действует таким образом, что как только будет найден эле мент, из-за которого вс е выраже ние становится ложным, вы· числе ния прекращаются . Эти с войства компилятора позволяют использовать конст· рукции, подобные следую щим: - whi l e ( ( c = ge tchar ( ) ) ! = ' ' & & с ! = ' \n ' ) Эта конструкция образует цикл, который считывает символы до пе рвого пробела или до символа новой строки. Пе рвое подвыражение присваивает значе ние перемен· ной с, которая затем используется во втором подвыражении . Если порядок вычисле· ний не установлен, компьютер может сделать попытку вычислить значение второго подвыражения , прежде чем выяс нит , какое значение имеет пе ременная с. Расс мотрим еще один пример : i f ( numЬ er ! = О & & 1 2 / numЬ er = = 2 ) print f ( " Знач ение пер еме нной numЬer р авно 5 или 6 . \ n " ) ; Если переме нная numЬ e r имеет значение О , то первое подвыраже ние ложно , и вы· числе ние условного выражения дальше не продолжается . Это позволяет компьюте ру избе жать ошиб ки деления на ноль. Уб едившись, что значение м переменной numЬer является О, он пе реходит к вычисле нию следую ще го условия . И , наконец, рас смотрим такой приме р: whi l e ( х++ < 10 && х + у < 2 0 ) Тот факт, что операция & & представляет собой точку последовательности, служит гарантией того , что значение х будет увеличено на 1 , пре жде чем будет вычисле но выраже ние справа . Свод к а: логи ч еск и е оп ера ци и и выражен и я Л огические опера ц ии : В логичес ких опера циях в качестве операндов выступа ют условные вы ражения . Опера­ ция ! вы полняется над одним опера ндо м. Остал ьные ло гичес кие опера ц и и вы полняют­ ся над двумя операнда ми, один из них ра сположен слева от зна ка опера ции , дру го й с п рава . Опер ац ия && 1 1 Зиа-ч ение и или НЕ Л огические вы ражения : Логичес кое про изведение выражениеl & & выражение2 истинно тогда и только тогда , когда оба вы ражение истинны ; выражениеl 1 1 выражение2 и ст инно , если одно ил и оба вы ражения ист инны . ! выражение ист инно, е сл и вы ражение ложно, и наоборот . Управля ющие оператор ы : ветвление и безусловн ы е переходы 2 81 Порядок вь 1 числения : Логические вы ражения вычисля ются слева на право . Вычисление прекра щается, ка к тол ько обна руж ива ется подвы ражение, которое делает ложны м все вы ражение. П римеры : 6 > 2 & & з == з (6 > 2 && з з) х ! = О & & ( 2 О / х) < 5 Истинно Ложно Вто рое вы ражение вычисляется тол ько при условии, что х имеет ненулевое значение д иапазон значен и й В ы можете воспользоваться опе рацией & & для проверки н а вхождение в диапазон значений. Например, чтоб ы проверить, находится ли значение переменной s co r e в диапазоне от 9 0 до 1 0 0 , вы можете предусмотреть следую щий оператор : i f ( r ang e >= 9 0 & & r ange <= 1 0 0 ) print f ( " Henлoxoй р е зул ь тат ! \ n " ) ; Следует раз и навсегда отказаться от стере отипов математичес ких обозначений, как показано в следую щем приме ре : i f ( 9 0 <= r ang e <= 1 0 0 ) / / Не поступайте так ! print f ( " Heплoxoй р е зул ь тат ! \ n " ) ; Проблема закл ю чается в том , что в этот программный код вкралась семантиче­ ская, а не с интаксичес кая ошибка , поэтому компилятор не способен ее об наружить (хотя он может выдать какое-то предупреждающее сообщение ) . Пос кольку для выпол­ не ния операции <= принят порядок слева направо , проверяемое выражение инте р­ претируется следую щим образом : ( 9 0 <= r ange ) <= 1 0 0 Подвыраже ние 9 0 <= r ange получает либо значе ние 1 (истина ) , либо О (ложь) . И то и другое значение меньше 1 0 0 , поэтому все выраже ние все гда истинно , не зави­ симо от значения r ange. Следовательно , для проверки на вхожде ние в диапазон следу­ ет пользоваться опе рацией & & . В о многих программах проверка н а вхождение в диапазон используется с целью определить, с каже м , представлен ли конкретный символ в нижне м ре гистре. Напри­ мер, предположим, что переме нная ch имеет тип char : i f ( ch >= ' а ' & & ch <= ' z ' ) print f ( " Э то симво л нижнего р егистр а . \ n " ) ; Представле нный метод подходит для таких символьных кодов, как ASCII, в этом случае последовательность кодов представляет собой последовательность возрастаю­ щих целых чисел. Однако, этот метод не приме ним к не которым другим видам коди­ ровки с имволов, в том числе к EBCD IC. Менее зависимым от выбора вида кодировки является ис пользование функции i s lower ( ) из заголовочного файла ctyp e . h ( с м . табл. 7 . 1 ) : i f ( i s lower ( ch ) ) print f ( " Э то симво л нижнего р егистр а . \ n " ) ; 282 Гл ава 7 Функция i slower ( ) работает независимо от используемой кодировки символов. (Од­ нако в некоторых устарелых реализациях семейство функций ctyp e . h отсутствует.) Програм м а подс ч ета сл ов Теперь у нас е сть вс е необходимые инструментальные средства для программы подс чета слов (то есть программы , которая читает входной текст и сообщает количе­ ство содержащихся в не м слов ) . Вы можете с тем же успехом подсчитать число симво­ лов и число строк. С начала определимся с тем, что должно быть вклю чено в эту про­ грамму. Прежде все го , эта программа должна выполнять посимвольный ввод , при этом тем или иным образом знать, когда следует остановиться . В о-вторых, она должна быть способна распознавать такие элеме нты , как символы , строки и слова . Представление этой программы в пс евдокоде имеет вид : читать символ пока ввод не з аконч е н , инкр ементир овать сч е тчик символов е сли считан а стро ка , инкр еме нтировать сч е тчик строк е сли считано слово , инкреме н тировать сч е тчик слов читать следующий симво л В от как выглядит модель цикла ввода с имволов : whi l e ( ( ch = g etchar ( ) ) { ! = STOP ) В данном случае STOP представляет собой некоторое значение пере менной ch, сиг­ нализирую щее о конце ввода . В рассмотренных ранее примерах мы использовали для этой цели с имвол новой строки и точку, однако ни тот ни другой с имвол не подходит для универс альной программы подсчета слов. На текущий моме нт выберем символ (такой как, например, 1 ) , который не встречаются в тексте . В главе 8 будет показано более удачное ре шение, которое позволит использовать программу как для работы с текстовыми файлами, так и с данными, которые вводятся с клавиатуры . Теперь рас смотрим тело цикла. Поскольку для ввода программа ис пользует функ­ цию getchar ( ) , она может вести подсчет символов, инкрементируя с четчик при каж­ дом проходе цикла. Чтобы определить количество строк, программа может подс чи­ тать количество с имволов в новой строке. Если программа сталкивается с символом новой строки, она должна увеличить значение счетчика строк на 1 . Потребуется дать ответ еще на один вопрос : что делать, если символ STOP встретился в середине строки. Нужно ли с читать это строкой или нет? Один из ответов гласит: это нужно расс матри­ вать как частичную строку, то есть строку, в которой с одержатся различные с имволы, но нет с имвола конца строки. В этом случае вы можете сохранять значение предыду­ ще го с читанного символа . Если считанный символ, предшествую щий символу STOP, не является символом новой строки, то вы имеете дело с не полной строкой. Наиболее сложной является та часть программы , которая распознает слова . В о­ первых, необходимо дать определение , что понимается под словом . Выберем сравни­ тельно простой подход и определим слово как последовательность символов, которая Управля ющие оператор ы : ветвление и безусловные переходы 2 83 не с одержит с имволов форматирования те кста (пробелов , символов табуляции и сим­ волов новой строки) . В этом смысле " glymxc k" и " r2d2" также являются словами. Сло­ во начинается в том случае , когда программа в первый раз встре чает символ, отлич­ ный от пробельных символов, и кончается, когда появляется следую щий пробельный символ. Ниже показан пример простейше го те стового выраже ния , обеспе чивающего об наруже ния с имволов , отличных от проб ельных: с ! = ' ' && с ! = ' \ n ' && с ! = ' \ t ' / * истинно , е с ли с не являе тся про б е л ь нь11v1. символом * / С амое простое прове ряемое выражение , обес печивающее выявление пробельных символов, имеет следую щий вид : с == ' ' 1 1 с == ' \ n ' 1 1 с == ' \ t ' / * истинно , е с ли с явля е т ся про бель ным симв олом * / Однако проще вос пользоваться функцие й i s sp ace ( ) из заголовочного файла ctyp e . h, которая возвращает значение true, если аргумент представляет собой про­ бельный с имвол. Следовательно, функция i s space ( с ) возвращает значение tru e , если с - пробельный с имвол, и ! i s s p a c e ( с ) будет истинным , если с таковым не является . Чтобы определить, входит ли тот или иной символ в конкретное слово, вы должны установить флаг (назовем е го inword) в 1, когда с читывается первый с имвол слова. В этой точке вы можете также инкреме нтировать значение с четчика слов. Далее , пока значение флага inword равно 1 (или true ) , последую щие пробельные символы не оз­ начают начало следую ще го слова . При появле нии следую щего символа , не являю ще­ гося пробельным, вы должны с брос ит ь флаг в О (или f al s e ) , а программа будет готова к выявлению следую щего слова . Все сказанное выше можно представить в виде псев­ докода : е с ли с н е есть про б е л и i nword есть f al s e , установить флаг inword р авным true и подсчитать сло во if с есть про б е л и inwo r d есть true установить флаг inword р авным fal s e При таком подходе флаг inwo r d устанавливается в 1 (true) в начале каждого слова и в О ( fal s e ) в конце каждого слова. Подс чет слов производится только в те моменты, когда установка влага меняется с О на 1. Если в ваше й системе ре ализован тип _Bool, вы можете включить заголовочный файл s tdЬ ool . h и использовать клю чевое слово bool для обозначе ния типа флага inword, принимающего значения true и fal s e . В противном случае ис пользуйте тип int, принимаю щий значе ния 1 и О . Если вы работаете с булевс кой переменной, проще всего использовать значение самой этой переменной в качестве проверяемого условия. В этом случае употребляйте i f ( i nwo rd) вместо i f ( i nwo rd tru e ) а также i f ( ! inword) вместо i f ( i nwo rd fal s e ) 284 Гл ава 7 О сновная идея с остоит в том, чго выражение inwo r d true принимает значение true, если флаг inword уже имеет значе ние true, и fal s e , если i nword принимает значение fal s e , отс юда следует, что вы можете ис пользовать флаг inwo rd в качестве прове ряемого условия . Аналогично, ! inword принимает те же значе ния , что и выра­ же ние inwo r d f al s e (not tru e есть fal s e , а not f al s e - true ) . В программе , показанной в листинге 7 . 7 , описанные идеи (распознавание строк, распознавание частичных строк и распознавание слов) реализованы. == == листинг 7.7. Пporpaммa wordcnt . c 1 1 wo rdcnt . c производит подсч е т симво лов , слов , строк #incl ude <s tdio . h > #incl ude <ctyp e . h > 1 1 ф айл , сод ержащий функцию i s sp a c e ( ) #incl ude <s tc!Ьool . h> 1 1 ключ евые слова bool , true , fal s e #de fi ne STOP ' 1 ' int main ( vo i d ) { char с ; 1 1 считанный символ char pr e v ; 1 1 пр едыдущий считанный символ 01; l o ng n_char s 1 1 сч е тчик символов О; int n l i n e s 1 1 сч е тчик строк О; int n wo rds 1 1 колич е с тво слов int p_l i n e s О; 1 1 колич е с тво ч а с тич ных слов bool inword fal s e ; tr ue е сли с в нутри слова 11 - - = == printf ( " Bв eди т e текст для анали з а ( 1 для з ав ерше ния ) : \ n " ) ; pr ev ' \n ' ; / / используется для распо знавания полноценных строк = whi l e ( ( с ge tchar ( ) ) ! STOP) { n_char s + + ; / / подсч е т символов if (с ' \n ' ) n_lin e s + + ; 1 1 подсч е т строк i f ( ! i s sp a c e ( c ) & & ! inword) { inword true ; 1 1 нач ало ново г о слова n_words + + ; 11 подсч е т сло в = = == = i f ( i s sp a c e ( c ) & & inwo r d ) inword fal s e ; / / до сти г нут к о н е ц слова prev с; / / сохр а ня е т символьное з н ач ени е = = i f (prev ! = ' \ n ' ) p_l i n e s 1; = p rintf ( "количество симв ол ов % ld, количество слов %d, количе ств о с трок n_cha r s , n_words , n_l i n e s ) ; printf ( " колич е ство ч а с тич ных строк % d\ n " , p_li n e s ) = = = r e turn О ; ,· %d, ", 2 85 Управля ющие оператор ы : ветвление и безусловн ы е переходы В от ре зультат выполнения этой программы : Вв едите текст для анали з а ( 1 для завершения ) : Reason is а powerful servant but an inade quate пвster . 1 колич е ство символов = 5 5 , колич е с тво слов колич е ство ч а с тичных строк = О 9 , колич е с тво строк з, Данная программа ис пользует логичес кие операции для перевода псевдокода в язык С . Наприме р, псевдокод е сли с е сть про бел и i nword принима е т знач ение fal s e транслируется в следую щее выраже ние : i f ( ! i s s p a c e ( c ) & & ! inwo r d ) Обратите внимание на то , чго ! inword эквивалентно выражению inword fal s e . Все выражение проверки легче понять, чем выраже ние для проверки каждого пробельного с имвола по отдельности: if (с ! = ' ' & & с ! = ' \ n ' && с ! = ' \ t ' && ! inwo r d ) Обе эти формы означают: " если с не есть пробел и е с л и в ы не внутри слова" . Если оба условия выполняются , это означает , что вы начинаете новое слово , а значение переменной n _words увеличивается на 1 . Если вы находите сь в се редине слова , то вы­ полняется пе рвое условие , в то же время значение флага inword будет true, а счетчик n_wor ds не увеличивается . Как только вы выйдете на следую щий пробельный символ, inwo r d снова получает значение fal s e . Проверьте, правильно ли работает программа в ситуации, когда между двумя словами следуют несколько пробелов подряд . В главе 8 будет показано, какие изменения потребуется вне сти в эту программу, чтобы она мог­ ла считывать слова из файла . Условн ая операция: ? : Язык С предлагает с окращенный способ представления одной из форм опе ратора i f el s e . Он называется условным выражением и использует условную опе рацию ? : . Эта операция с о стоит из двух частей и работает с тремя опе рандами. Вспомните , чго операция с одним операндом называется унарной, а с двумя опе рандами - бинарной. Соблюдая эту традицию , назовем операции с тремя операндами тернарными, а рас­ сматриваемая условная операция является в С единственным примером опе раций та­ кой категории. Примером этой операции может служить получение аб солютного зна­ чения числа : х = (у < О) ? -у : у; В с е , что находится между знаком = и точкой с запятой, представляет с обой услов­ ное выражение . С мысл этого опе ратора можно выразить следую щим образом : "е сли у меньше нуля , то х = -у , в противном случае х = у " . В терминах оператора i f el s e это можно выразить следую щим образом: (у < О ) х -у ; el s e х = у; if 286 Гл ава 7 В обще м виде условное выраже ние можно записать так: выражение l ? выражение2 : выражение З Если выражениеl дает в результате true (ненулевое значение ) , то все условное вы­ ражение принимает то же значение , что и выражение 2. Если выражениеl равно fal s e (ноль) , все условное выражение получает т о ж е значение , что и выражение З. Вы можете использовать условное выражение в ситуации, когда пе ременной нужно прис воить одно из двух возможных значе ний. Типичным примером может служить прис ваивание конкретной переменной наибольше го из двух значе ний: max = (а > Ь) ? а : Ь ; Этот оператор присваивает переменной max значе ние а , если оно больше Ь , и Ь в противном случае. Обычно оператор i f e l s e делает то же самое, что и условная операция . Однако версия условной операции отличается компактностью и в зависимости от типа ком­ пилятора может обеспечить генерацию более компактного кода. В качестве примера рассмотрим программу, показанную в листинге 7.8. Эта про­ грамма вычисляет, сколько банок крас ки не обходимо для того , чтобы покрас ить за­ данное число квадратных футов пове рхности. О сновной алгоритм достаточно прост: разделить число квадратных футов, которые нужно покрасить, на число квадратных футов, которые можно покрас ить содержимым одной банки. Однако предположим, что ответом будет 1 . 7 банки. В магазине вам не продадут такого количе ства кра с ки, там краска продается целыми банками, так что вам придется приобре сти две банки. Следовательно, программа должна округлить ответ до следую щего целого числа. С этой целью программа использует условную операцию , эта же операция применяет­ ся для печати слова " банка" в соответствую ще м числе и паде же . листинг 7.8. Программа paint . c / * p aint . c - - исполь зов ание условной опер ации * / #incl ude <s tdio . h > #de fi ne COVERAGE 2 0 0 / * число кв адр атных футов на о дну банку кр аски * / int main ( vo i d ) { int s q_f eet ; int cans ; print f ( " Введите число квадратных футов , которые нео бходимо покр асить : \ n " ) ; whi l e ( s can f ( " % d" , & s q_ f e e t ) 1) { cans s q_ f e e t / COVERAGE ; cans + ( ( s q_feet % COVERAGE О) ) ? О : 1; == = = == print f ( " Для э тог о потр е буется % d % s кр а ски . \ n " , can s , cans 1 ? " банк а " : " банки " ) ; print f ( " B в e дитe следующе е з н ач ение (или q для з ав ершения ) : \ n " ) ; == r e turn О ; Управля ющие оператор ы : ветвление и безусловн ы е переходы 2 87 Ниже показаны ре зультаты выполнения этой программы: Вв едите число кв адр а тных футов , ко тор ые нео бходимо покр асить : 200 Для э тог о потр е буется 1 б анка краски . Вв едите следующе е з н ач ени е (или q для з ав ершения ) : 215 Для э тог о потр е буется 2 б анки краски . Вв едите следующе е з н ач ени е (или q для з ав ершения ) : q Поскольку в программе ис пользуется тип i nt, дробная часть ре зультата от деления отбрасывается , то е сть, 2 1 5/200 дает 1 . Следовательно, количество банок округляется до ближайшего меньшего целого . Если s q _ f e e t % COVERAGE равно О , то COVERAGE делит s q_ fe et без остатка, и значение cans остается без изме нений. В противном случае по­ является остаток, и значение cans увеличивается на 1. Это достигается с помощью следую щего оператора: cans += ( ( s q_ f e et % COVERAGE == 0 ) ) ? О : 1 ; О н прибавляет значение выражения, стоящее справа от знака + = , к значе нию cans . Выраже ние с права есть условное выражение , принимающее значение О или 1 в зави­ симости от того , делится ли s q_ f e e t на COVE RAGE без остатка . О кончательное значение аргумента функции p r i n t f ( ) также есть условное выра­ же ние : cans == 1 ? " б анка " : " банки " ) ; Если значе ние переме нной cans равно 1 , употребляется строка " банк а " , в против­ ном случае - строка " банки " . Это показывает, что в условной операции могут исполь­ зоваться строки в качестве второго и третье го операндов. Св од к а: условн ая оп ерац и я Условная опера ц ия: ?: Комментарии о бще го характера: Эта операция прини мает три о пе ранда, кажды й из которых я вляется в ы ражение м: выражение l ? выражение2 : выражение З Значение всего выражения равно значени ю выражение2, есл и выражениеl прини мает значение true. В противном случае оно равно значению выражение З. П римерь � : (5 > 3) ? 1 (3 > 5) ? 1 (а > Ь) ? а 2 2 ь получает значение 1 получает значение 2 получает значение большего из значений а ил и Ь 288 Гл ава 7 Д оп ол нительны е средства организаци и ци кл а : continue и break Как правило, после того как управление передается телу цикла, программа выпол­ няет все операторы тела цикла, прежде чем будет осуществлена очередная проверка конца цикла . Операторы continue и b r e a k дают возможность пропустить часть цикла и даже пре кратить его выполне ние в зависимости от результатов прове рок, выпол­ няемых в теле цикла. Оператор continue Этот опе ратор может использоваться во всех трех формах циклов. Когда ему пе ре­ дается управление , остальная часть цикла пропускается и начинается новая итерация . Если оператор continue с одержится во вложенной структуре , е го действия рас про­ страняются только на самую внутренню ю структуру, с одержащую этот опе ратор. По­ пробуем воспользоваться опе ратором continue в короткой программе , показанной в листинге 7.9. листинг 7.9. Программа skippart . c / * s kippart . с - - исполь зует опер атор continu e , что бы пропустить ч асть цикла * / #incl ude <s tdio . h > int main ( vo i d ) { co n s t fl o at МI N O . Of; 10 0 . 0f; co n s t fl o at МАХ fl o at s cor e ; fl o at total O . Of; int n О; МАХ ; fl o at mi n fl o at max МI N ; = = printf ( " Bв eдитe р е зуль тат первой игры (или q для з ав ершения ) whi l e ( s can f ( " % f " , & s cor e ) 1) ") ; == i f ( s co r e < MIN 1 1 s cor e > МАХ ) { print f ( " % 0 . l f - недопустимое значение . Пов торите попытку : " , s cor e ) ; continue ; рrint f ( " Во с приня то % 0 . l f : \ n " , s cor e ) ; min ( s cor e < mi n ) ? s co r e : min ; max ( s cor e > max ) ? s co r e : max ; total += s cor e ; n++ ; print f ( " В в е дите р е з у ль тат сл едующей игры (или q для з ав ершения ) : " ) ; = = i f (n > О ) { print f ( " Cpeднe e значение %d ре зуль татов равно % 0 . l f . \n " , n , total / n ) ; Управля ющие оператор ы : ветвление и безусловн ы е переходы print f ( "Минималь н о е % 0 . l f , максималь но е 2 89 % 0 . l f\ n " , min , max ) ; el s e print f ( " Допустимые р е з уль таты игр н е вве дены . \ n " ) ; r e turn О ; В программе из листинга 7.9 в цикле whi l e с читываются входные данные до тех пор, пока не будет введе но нечисловое значение . О ператор i f, вклю ченный в этот цикл, выводит на экран недопустимые результаты . Если вы , скаже м , вводите число 1 8 8 , программа сообщает, что это недопустимое значение . Затем оператор continue заставляет программу пропустить остальную часть цикла, которая предназначена для обработки допустимых входных значений. Вместо того чтобы продолжать те кущую итерацию , программа инициирует новую итерацию , считывая оче редное входное значение . Обратите внимание на то , что существует два спос об а избе жать ис пользования оператора conti nue. Один из таких спос обов заключается в том , чтобы опустить опе· ратор conti nue и представить оставшую ся часть итерации как блок el s e : i f ( s cor e < О 1 1 s co r e > 1 0 0 ) / * оператор print f ( ) * / el s e / * оп ер атор ы * / С другой стороны , вы можете воспользоваться следую щим форматом: if ( s cor e >= О && s co r e <= 1 0 0 ) { / * оп ер атор ы * / Преимуще ство применения оператора continue в рас сматриваемом случае связано с тем, что вы можете отказаться от одного уровня отступов в главной группе операто­ ров. Б олее компактная форма повышает удоб очитаемость программы , когда операто· ры громоздки и обладают большой глубиной вложения . Наряду с этим опе ратор continue может использоваться в качестве заполнителя . Наприме р, следую щий цикл с читывает и пе редает в программу входную последова· тельность символов, пока не встретит символ конца строки (вклю чительно ) : whi l e ( g etchar ( ) ! = ' \n ' ) Такой метод удобен, когда программа уже прочитала некоторые входные с имволы из конкретной строки, и ей необходимо пропустить оставшиеся символы те кущей строки и перейти в начало следую ще й строки. Проблема заклю чается в том, что оди· ночный символ точки с запятой об наружить достаточно трудно . Программный код становится более удобочитаемым, е сли вы воспользуетесь оператором continue: whi l e ( g etchar ( ) continue ; != ' \ n ' ) 290 Гл ава 7 Не следует ис пользовать оператор continu e , если он усложняет, а не упрощает код . В качестве примера рас смотрим следую щий фрагмент: whi l e ( ( ch = g etchar ( ) ! = ' \n ' ) i f ( ch ' \t ' ) continue ; putchar ( ch ) ; == В этом цикле пропускаются символы табуляции, а с ам цикл завершается только в случае, когда встре чается символ новой строки. Такой цикл можно записать в более компактной форме : whi l e ( ( ch g etchar ( ) ) i f ( ch ! ' \t ' ) putchar ( ch ) ; = ! = ' \n ' ) = Довольно часто, как, впроче м, и в данном случае , изменение порядка прове рки в операторе i f устраняет необходимость приме не ния continu e . Как уже было показано , оператор continue обес печивает игнорирование остав· ше йся части тела цикла. В какой точке возобновляется выполне ние цикла? Если речь идет о циклах whil e и do whi l e , то следую щим де йствием , выполняемым сразу после conti nue, будет оце нка условия выполнения цикла. Расс мотрим, например, следую· щий цикл: co unt О ,· whi l e ( count < 1 0 ) { ch getchar ( ) ; i f ( ch ' \n ' ) continue ; putchar ( ch ) ; count++ ; = = == Цикл считывает 1 0 символов (ис клю чая символы новой строки, поскольку опера· тор count++ ; пропускается , когда значе ние ch соответствует с имволу новой строки) и осуществляет их эхо-вывод на экран, ис ключая с имвол новой строки. Когда выполня· ется опе ратор continue, следую щим вычисляется выражение проверки окончания цикла. Для цикла for следую щим действием является вычисле ние значения корректи· рую щего выражения, за которым следует вычисление проверка конца цикла. Для примера рассмотрим такой цикл: for ( count О ; count < 1 0 ; count++ ) { ch getchar ( ) ; i f ( ch ' \n ' ) continue ; putchar ( ch ) ; = = == Управля ющие оператор ы : ветвление и безусловн ы е переходы 291 Когда в этом случае выполняется оператор co ntinu e , сначала инкрементируется значение count, которое затем с равнивается с 1 0 . Таким образом, поведе ние этого цикла несколько отличается от поведения цикла whil e из рассмотренного выше при­ мера. Как и раньше , на экране отображаются только символы , отличные от символа новой строки. Но на этот раз счетчик count учитывает с имволы новой строки, поэто­ му цикл с читывает ровно 1 0 символов , включая с имвол новой строки. Оператор break О пе ратор b r e a k , вклю ченный в цикл, заставляет программу прервать выполнение цикла, выйти из не го и пере йти к выполне нию следую щей стадии программы. В про­ грамм е , представленной в листинге 7.9, замена опе ратора conti nue оператором b r e a k вызывает выход программы из цикла, когда на входе появляется , скажем, число 1 8 8 , но не пропуск следую щих з а ним опе раторов цикла и переход к выполне нию следую­ ще й итерации. На рис . 7.4 сравниваются опе раторы b r e a k и conti nue. Если опе ратор b r e a k содержится внутри вложенных циклов, е го де йствие распространяется только на с амый внутренний цикл , где он присутствует. while { { ch = getchar О ) ! =EOF) { ЫahЬlah {ch) ; if {ch ' \n ' ) break ; yakyak {ch) ; = Ыunder { n , m) ; :J while { {ch = getchar { ) ) ! =EOF) { ЫahЬlah {ch) ; if {ch ' \n ' ) continue ; �������---<� yakyak {ch) ; = Ыunder { n , m) ; Р и с . 7 .4. Gр ав'Не'Ние операторов brea k и соп t inue 292 Гл ава 7 Иногда опе ратор b r e a k используется для выхода из цикла, когда для такого выхода имеются две разные причины . Программа, показанная в листинге 7.1 0 , соде ржит цикл для вычисления площади прямоугольника . Цикл прекращается , когда вы вводите не­ числовое значение в каче стве длины или ширины прямоугольника . листинг 7.1 0. Программа breaJc. c / * b r e a k . c - - исполь зуе т оператор b r e a k для выхода из цикла * / #incl ude <s tdio . h > int main ( vo i d ) { fl o at l e ngth , width ; printf ( " Вв еди т е длину прямоуг о л ь ника : \ n " ) ,­ whi l e ( s can f ( " % f " , & l ength ) == 1 ) { print f ( " Длинa = % 0 . 2 f : \ n " , l ength ) ; print f ( " B в e дитe ширину прямоугольника : \ n " ) ; i f ( s can f ( " % f " , &width ) ! = 1 ) br e a k ; print f ( "Шиpинa = % 0 . 2 f : \ n " , width ) ,­ рrint f ( " Площадь = % 0 . 2 f : \ n " , l ength * wi dth ) ; print f ( " B в e дитe длину прямоугольника : \ n " ) ; printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; В ы могли б ы организовать цикл следую щим образом: whi l e ( s can f ( " % f % f " , &l ength , &width ) == 2 ) В то же время использование опе ратора b r e a k существенно упрощает эхо-вывод вводимых значений. Как и в случае с continue, опе ратора b r e a k следует избегать, если он усложняет код программы : whi l e ( ( ch = g etchar ( ) ) { i f ( ch == ' \ t ' ) br e a k ,­ putchar ( ch ) ; ! = ' \n ' ) Логика программы становится прозрачнее , если обе прове рки выполняются в од­ ном и том же ме сте : whi l e ( ( ch = g etchar ( ) ) putchar ( ch ) ; ! = ' \ n ' & & ch ! = ' \ t ' ) О пе ратор b r e a k представляет собой полезное дополне ние опе ратора swi t ch, к изучению которого мы вскоре перейде м. О пе ратор b r e a k передает управление опе ратору, который непос редстве нно следу­ ет за циклом, и в отличие от continu e , включе нного в цикл f o r ; в этом случае пропус­ кается та часть, в которой производится обновление цикла. О пе ратор b r e a k , вклю- Управля ющие оператор ы : ветвление и безусловн ы е переходы 293 ченный во вложенный цикл, выводит программу только из этого внутренне го цикла, чтобы выйти из внешне го цикла, требуется еще один оператор b r e ak: int р, q ; s c an f ( " % d" , &р ) ; whi l e ( р > О ) { print f ( " % d\ n " , р ) ; s can f ( " % d" , & q ) ; whil e ( q > О ) { printf ( " % d\ n " , p * q ) ; if (q > 1 0 0 ) break ; 1 1 опер атор b r e a k в о вну тр еннем цикл е s c an f ( " % d" , &q ) ; if (q > 1 0 0 ) br e a k ; s can f ( " % d" , &р ) ; / / опер атор b r e a k в о внешнем цикле М н ожественн ый выбор: оп ераторы swi tch и break Условная операция и конструкция i f el s e существенно облегчают написание про­ грамм, в которых производится выбор из двух возможных вариантов. Однако в некото­ рых случаях должен делаться выбор одного конкретного варианта из нескольких воз­ можностей. Вы можете реализовать это с помощью конструкции if el s e i f . . . el s e . В т о же время в о многих случаях может оказаться гораздо сподручнее вос пользоваться оператором switch языка С . Программа в листинге 7 . 1 1 представляет с об ой приме р примене ния оператора s witch. Эта программа с читывает букву и отве чает тем, что выводит на печать название животного , которое начинается с этой буквы. листинг 7.1 1 . Проrрамма an:imals . с / * animal s . c -- и споль з о в ани е опер атор а switch * / #incl ude <s tdio . h > #incl ude <ctyp e . h > int main ( vo i d ) { char ch ; printf ( " Дaйтe мне букву алфави т а , и я укажу в ам " ) ; рrintf ( " название живо тног о , \ nначинающ е е ся с э той буквы . \ n " ) ; printf ( " Bв eди т e букву или # для з ав ершения . \ n " ) ; whi l e ( ( ch = g etchar ( ) ) ! = ' # ' ) { i f ( ' \ n ' == ch ) continue ; / * только строчные буквы * / i f ( i s lower ( ch ) ) switch ( ch ) 294 Гл ава 7 cas e ' а ' : printf ( " apг aли , дикий г ор ный азиатский бар ан \ n " ) ; br e a k ,· cas e ' б ' : printf ( " бaбиpycca , дикая малайская свинь я \ n " ) ; br e a k ; cas e ' к ' : printf ( " кo a ти , носуха о быкновенная \ n " ) ; br e a k ; cas e ' в ' : printf ( " выxyxoль , в одопл а в ающе е суще ство\ n " ) ; br e a k ; cas e ' е ' : printf ( " exидн a , иг о л ь ч а тый мур а в ь ед\ n " ) ; br e a k ; cas e ' р ' : p r i nt f ( " pыбoлoв , св е тло-корич н е в ая куница\ n " ) ; br e a k ; de fault : p r i nt f ( " Э тo трудная з адач а ! \ n " ) ; / * кон ец оператор а выбо р а * / el s e printf ( " Распо з н аются тол ь ко стр очные буквы . \ n " ) ; whil e ( getchar ( ) ! = ' \ n ' ) continue ; / * пропустить оставшуюся ч асть входной строки * / print f ( " B в e дитe следующую букву или # для з ав ершения . \ n " ) ; / * коне ц цикл а whil e * / printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; Мы немного рас слабились и предусмотрели варианты далеко не для вс ех букв, но можно было вполне продолжить в том же духе . Сначала расс мотрим приме р выполне· ния этой программы , а затем проанализируе м ее структуру: Дайте мн е букв у алф авита , и я укажу в ам н а з в ание животног о , начинающ е е ся с э той буквы . Вв едите букву или # для з ав ерше ния . а [enter ] ар г али , дикий г орный ази а т ский бар ан Вв едите следующую букву или # для з ав ершения . вап [enter] выхухоль , водо плав ающе е суще ство Вв едите следующую букву или # для з ав ершения . ф [enter ] Это трудная задач а ! Вв едите следующую букву или # для з ав ершения . Е [enter ] Ра спо знаются только строч ные буквы . Вв едите следующую букву или # для з ав ершения . # [enter ] Пр ограмма з ав ершена . Управля ющие оператор ы : ветвление и безусловн ы е переходы 295 Две основных ос обенности программы состоят в использовании опе ратора s witch и в том, как она манипулирует входными данными. Для начала рассмотрим, как рабо­ тает оператор swi t ch. Использова ние оператора swi tch С начала вычисляется выраже ние в круглых с кобках, следую ще е за словом s witch . В этом случае оно получает выражение , которое переме нная ch получила в результате последнего ввода. Зате м программа просматривает с писок меток (в рассматриваемом случае это c a s e ' а ' : , cas e ' б ' : и так дале е ) , пока не найдет подходящее значе ние . Программа пе реходит на эту строку. А что произойдет, если она не найдет соответст­ вую щей метки? Если в операторе имеется строка, помеченная как de faul t : , про­ грамма переходит на эту строку. Если такой строки нет, программа пе реходит к вы­ полне нию оператора, следую щего не посредственно за switch . Как в подоб ной ситуации ведет с ебя оператор b r e ak? О н заставляет программу выйти из оператора s wi tch и пере йти к оператору, непосредственно следую щему за switch (рис . 7.5 ) . Если бы не было опе ратора br e a k , выполнялся б ы каждый опе ратор в промежутке от оператора , помеченного совпадающей с входным с имволом меткой, до конца опе ратора swi tch. Наприме р, если вы удалите из программы все операторы b r e a k , а зате м запустите е е и введете букву в , будет иметь место следую щий диалог: Дайте мн е букв у алф авита , и я укажу в ам н а з в ание живо тног о , начинающ е е ся с э той буквы . Вв едите букву или # для з ав ерше ния . в [enter ] выхухоль , водо плав ающе е суще ство ехидна , игольч атый мур ав ь ед рыболов , св е тло -коричневая куница Это трудная задач а ! Вв едите следующую букву или # для з ав ершения . # [enter ] Пр огр амма з ав ершена . Б ыли выполнены все операторы , начиная с cas e ' в ' : и до конца опе ратора switch. Между прочим , опе ратор break работает и с циклами и с оператором выбора, в то вре мя как опе ратор continue работает исклю чительно с циклами. Однако continue может ис пользоваться как часть оператора swi tch, если swi tch присутствует в цикле . В такой с итуации, как, впроче м, и в других циклах, оператор conti nue заставляет про­ грамму пропустить остальные операторы цикла, в том числе и другие части опе ратора switch. Если вы знакомы с языком программирования Pascal, то вы можете отметить большое сходство оператора swi tch с оператором cas е языка Pascal. О с новное разли­ чие между ними заклю чается в том, что опе ратор s wi tch тре бует использования опе­ ратора br e a k , если вы хотите , чтобы обраб отке подве ргались только помеченные операторы . Наряду с этим, вы не можете использовать диапазон так, как это можно делать в cas e . Заклю ченное в круглые скобки прове рочное выраже ние оператора swi tch должно иметь целочисле нный тип (вклю чая и тип char ) . Метки операторов также должны 296 Гл ава 7 быть константами целочисле нного типа (вклю чая тип char ) или выражениями цело­ численного типа (выражениями, содержащими только целочисленные константы ) . Переменные в качестве меток операторов c a s e ис пользовать нельзя . В общем случае структура оператора swi t ch имеет следую щий вид : switch ( цело численно е выр ажение ) cas e конста нта l : �не о б я з ател ь ные опера торы cas e конста нта 2 : �не о б я з ател ь ные �не о б я з ател ь но �не о б я з ател ь ные опера торы de fault : опера торы [ switch ( чяcлo) �ase 1 : case оператор 1 ; break ; 2 : оператор 2 ; ,.--���� break ; case 3 : оператор 3 ; break default : оператор 4 ; ) [ [ statement 5 ; �witch ( чяcлo) case 1 : оператор 1 ; case 2 : оператор 2 ; case 3 : �efault : оператор 3 ; оператор 4 ; оператор 5 ; Р и с . 7 . 5 . Поток управле'Ния программъ; в операторах s wi t ch с и без операторов break сч итыва н ие тол ько первого си мвола строки Еще одно новое свойство, присущее программе animal s . с , связано с те м, как она считывает входные данные . Как вы, должно быть, уже заметили во время выполнения этой программы , когда была введена последовательность символов в а л , обраб отался только пе рвый символ. Часто такое поведение интерактивных программ, принимаю- Управля ющие оператор ы : ветвление и безусловн ы е переходы 297 щих однос имвольные запро с ы , предпочтительнее любых других. Такое поведение реализует приведе нный ниже программный код: whi l e ( g etchar ( ) conti nue ; ! = ' \n ' ) / * про пусти т ь осталь ную ч асть входной строки * / Этот цикл читает входную последовательность с имволов , вклю чая символ новой строки, который ге не рируется нажатие м клавиши <Enter>. Обратите внимание , что значение , возвращаемое функцие й getch ar ( ) , не присваивается переменной ch, сле­ довательно, программа читает символы и просто их отбрас ывает за не надоб ностью . Так как последним отброше нным с имволом будет с имвол новой строки, то следую щий считывае мый символ принадлежит уже новой строке. Функция getchar ( ) читает его и прис ваивает е го значение пере менной ch во внешнем цикле whi l e . Предположим , что пользователь начинает работу с программой, нажав <Enter>, так что пе рвый прочитанный программой символ является символом новой строки. Следую щий далее программный код учитывает такую возможность: if ( ch == ' \ n ' ) conti nue ; М ножествен н ые метки В ы можете использовать не сколько меток оператора cas e для данного опе ратора swi tch, как показано в листинге 7.1 2 . листинг 7.1 2. Проrрамма vowels . c / * vowel s . c исполь з о в ание множе ства м е ток * / #incl ude <s tdio . h > int main ( vo i d ) { char ch ; int a_ct , e_ct , i_ct , o_ct , и ct ; а ct = е ct = i ct = о ct = и ct = О ; printf ( " Вв еди т е текст или # для з ав ершения прогр аммы . \ n " ) ; whi l e ( ( ch = g etchar ( ) ) ! = ' # ' ) { switch ( ch ) { case , а , case 'А ' а ct++ ; br e a k ; case ' е ' case ' Е ' е ct++ ; br e a k ; case ' i ' i ct++ ; case , I , br e a k ; case ' о ' case ' О ' о ct++ ; br e a k ; case ' и ' case ' U ' и ct++ ; ьre a k ; - - 298 Гл ава 7 de faul t br e a k ; / * конец опера тор а switch * / / * конец цикла whil e * / рrintf ( " Колич е ство г л а сных : А Е I О U \ n " ) ; printf ( " % 4 d % 4 d % 4 d % 4 d % 4 d\ n " , a_ct , e_ct , i ct , o_ct , u_ct ) ; r e turn О ; Если ch раве н, скажем, букве i , опе ратор swi t ch переходит в точку, поме че нную как cas e ' i ' : . Поскольку нет оператора b r e a k , связанного с этой меткой, поток управления программы переходит к следую ще му оператору, а таковым в расс матри­ ваемом случае является оператор i _ ct ++ ; . Если значение ch равно I , поток управле­ ния программы выходит непос редственно на этот опе ратор. По сути дела , обе метки ссылаются на один и тот же оператор. Строго говоря , оператор break для case ' U ' не нужен, так как при его отсутствии поток управления программы выходит на следую щий оператор в рассматриваемом операторе swi tch , каковым является b r e a k для случая по умолчанию . Следовательно , оператор b r e a k для cas e ' U ' можно было о пустить, тем самым сократив программ­ ный код . Однако с другой стороны, если позже могут быть добавлены другие случаи, то наличие оператора b r e a k там, где он должен быть, всегда будет напоминать о том, что его надо добавлять. Ниже показан пример выполнения этой программы: Вв едите текст или # для з ав ерше ния пр ограммы . I see under the over seer . # Колич е ство г л а сных : А Е I О U о 7 1 1 1 В рас сматриваемом случае вы можете отказаться от использования множе стве н­ ных меток, прибе гнув к помощи функции toupper ( ) из семейства ctyp e . h ( см. табл. 7 . 2 ) , чтоб ы перед прове ркой условия преобразовать вс е строчные буквы в прописные : whi l e ( ( ch = g etchar ( ) ) ! = ' # ' ) { ch = to upp er ( ch ) ; s witch ( ch ) { cas e ' А ' а- ct++ ; break ; cas e ' Е ' е - ct++ ; break ; cas e , I , i ct++ ; break ; cas e ' О ' о - ct++ ; break ; cas e ' U ' u- ct++ ,· break ; de fault : break ; / * конец о пер атора swi tch * / } / * конец о пер атора whi l e * / } Управля ющие оператор ы : ветвление и безусловные переходы 299 Либ о , если вы хотите оста вить значе ние ch неизменным, вос пользуйтесь этой функцией следую щим образо м : switch ( toupp er ( ch ) ) Св од к а: м ножествен н ы й выбор с п ом ощью операто ра swi tch Кл ючевое слово: switch Комментарии о бще го характера: Поток управления програ м м ы пе редается на метку о пе ратора c a s e , которая по значе­ нию совпадает со значение м выр ажения. Зате м поток управпения проходит через все оставшиеся операторы , по ка не вы йдет на о пе ратор b r e ak. Как выражение, так и метки операторов cas e должны быть целочисленны м и значен ия ми (вкп ючая тип char), а са­ ми метки должны быть константами и вы ражениями, образованны м и искпюч ител ьно из констант. Есл и ни одна из меток операторов cas e не совпадает со значением вы раже­ ния , упра впение пе редается оператору с меткой de faul t, е сл и та ковой имеется. В противном случае управпение переходит к оператору, спедующему непосредственно за switch. Форма: switch ( выражение ) cas e ме тка l опера торl / * испо ль зуй т е br e a k , ч т о бы пр опусти т ь остальные опера торы * / cas e ме тка 2 опера тор2 de fault : опера торЗ В операто ре выбора могут быть два помеченных оператора, а нал ичие c a s e с меткой de faul t необязательно. П ример: switch ( choi c e ) { cas e 1 cas e 2 р r i nt f ( " Мер зоп ако стная пог ода ! \ n " ) ; b r e a k ; printf ( " He так уж и плохо на дв ор е ! \ n " ) ; cas e 3 printf ( " Xopoший день ! \ n " ) ; b r e a k ; cas e 4 de faul t : printf ( " Желаем хорошего о тдыха . \ n " ) ; } Есл и переменная choi c e принимает целое значение 1 ил и 2 , печатается пе рвое сооб­ щение, е сл и 3 второе и третье сообщения . (Поток управпения переходит на следую­ щий оператор, поскольку после cas e 3 нет о пе ратора b r e a k . ) Есл и она принимает зна­ чение 4 , то печата ется третье сообщение. Другие значения приводят к вы воду тол ько - последнего сообщения . Операторы swi tch и if el se Когда следует использовать опе ратор swi t c h , а когда i f el s e ? Часто у вас просто нет выбора . Вы не можете использовать опе ратор switch, если ваш выбор основан на оценке переме нной или выраже ния типа fl o at. Возникают определенные трудности 300 Гл ава 7 при использовании оператора swi tch, е сли переме нная проверяется на принадле ж­ ность к тому или иному диапазону. Легко написать следую щее выражение : i f ( integer < 1 0 0 0 & & integer > 2 ) К сожале нию , применение оператора switch требует назначе ния меток cas e для каждого целого числа в проме жутке от 3 до 9 9 9 . В то же время , если вы имеете воз­ можность воспользоваться опе ратором swi tch, программа будет выполняться не мно­ го быстрее и станет боле е компактной. О п ератор go to О пе ратор goto, одно важне йших с редств ранних версий языков программирова­ ния BASIC и FORTRAN, реализован также и в С. В то же вре мя язык С, в отличие от упомянутых языков, вполне может обойтись и без этого оператора . Керниган и Ритчи отзываются об операторе goto как о потенциальном источнике ошиб ок и советуют использовать его как можно реже, а е ще лучше вообще отказаться от его применения . Далее мы объясним, почему нам не следует прибегать к его помощи. О пе ратор goto состоит из двух часте й: клю чевого слова goto и имени метки. Име­ нование меток производится по те м же правилам, которые соблюдаются при име но­ вании переменных, как в показанном ниже приме ре : goto part2 ; Чтобы этот оператор раб отал правильно, в программе должен присутствовать дру­ гой оператор с присвое нной ему меткой p ar t 2 . В этом случае этому второму оператору предшествует метка , за которой идет двоеточие : part2 : рrintf ( "Уточн енный анали з : \ n " ) ; И збега й те использова ния оператора go to По сути дела, в программе на языке С вы вполне можете обойтись и опе ратора goto , однако е сли ранее вы имели дело с языками FORTRAN или BASIC, оба из кото­ рых требуют использования этого оператора, у вас , по-видимому, выработалис ь навы­ ки программирования , предполагаю щие применение оператора goto . Чтобы помочь вам преодолеть эту привычку, рас смотрим не сколько типовых ситуаций, в которых используются операторы goto , а зате м покаже м, как эти проблемы решаются в языке С другими средствами. • Ис пользование опе раторов goto в ситуации, когда в опе раторе i f требуется выполнить сразу несколько операторов: if ( s i z e > 1 2 ) goto а ; goto Ь ; а : co s t co s t * 1 . 0 5 ; fl ag 2; co s t * f l ag ; Ь : bill В ранних версиях языков программирования BASIC и FORTRAN к оператору i f относился только один оператор, непосредственно следую щий за условием . Управля ющие оператор ы : ветвление и безусловн ы е переходы 301 Никаких средств для об разования блоков и составных операторов в этих языках не б ыло. Мы перевели этот пример на язык С. Стандартный для языка С подход с ис пользование м составных операторов или блоков существе нно облегчает восприятие смысла программы: if ( s i z e > 1 2 ) { co s t co s t * 1 . 0 5 ; fl ag 2; bill = co s t * flag ; • Выб ор одного из двух возможных вариантов: if (ibex > 1 4 ) goto а ; s heds = 2 ; goto Ь ; а : s h e ds = 3 ; Ь : help = 2 * sheds ; Наличие в языке С структуры i f el s e позволяет с формулировать такой выбор более наглядно : i f (ibex > 1 4 ) s h e ds 3; el s e s h e ds 2; help = 2 * s heds ; На с амом деле , в более поздних версиях BASIC и FORTRAN появилась конст­ рукция el s e . • Организация бес конечного цикла: r e adiп : s caп f ( " % d" , & s cor e ) ; i f ( s co r e < О ) goto s t ag e 2 ; множе с тво о п ер а торов ; goto r e adiп ; stage2 : дополнитель ная о бр а бо тка ; Благодаря использованию цикла whi l e , этот фрагме нт программы приобретает следую щий вид : s caп f ( " % d" , & s cor e ) ; whil e ( s co r e <= 0 ) { множество опер аторов ; s ca п f ( " % d " , & s cor e ) ; дополнител ь н ая о бработк а ; • Пропус к о пе раторов до ко нца тела цикла и начало следую щ е й ите раци и . Ис пользуйте для этой цели опе ратор coпtiпue. 302 • • Гл ава 7 Выход из цикла. По сути, опе раторы b r e a k и continue представляют с об ой с пе· циализированные формы оператора goto. Пре имуще ство их ис пользования за­ клю чается в том, что их названия говорят, для чего они предназначены, и в том, что можно не опасаться , что метки будут расставлены не там, где надо , по­ скольку они вообще не имеют дела с метками. Беспорядочные пе реходы из одних частей программы в другие. И вс е-таки имеет ме сто случай, когда опытные программисты , имеющие солидный опыт работы на языке С, допускают употребление опе ратора goto : выход из вложе н­ ного набора циклов при возникнове нии ошибки (одиночный опе ратор b r e a k обес пе­ чивает выход только из с амого внутре ннего цикла) : whi l e ( funct > О ) { for ( i = 1 , i <= 1 0 0 ; i + + ) { for ( j = 1 ; j <= 5 0 ; j ++ ) { совокупно с т ь операторо в ; i f ( ошибка ) goto help ; опер а торы ; } некоторо е число операторо в ; } еще н еко тор о е число о п ер а торов ; } дальнейшие операторы ; h e lp : код устр анения оши бки ; Как легко убедиться на других приме рах, альтернативные формы представления программ более удобны для вос приятия , че м формы , в которых ис пользуются опера­ торы goto. Эти различия становятся е ще более заметными, когда имеет место сочета· ние не скольких ситуаций подобного рода . Какие операторы goto ус пешно использу­ ются в с очетании с операторами i f el s e , какие из них управляют циклами, а какие появилис ь в программе только потому, что вы столкнулись с затрудне ниями и не на­ шли другого выхода? Используя операторы там где надо и где не надо, вы загоняете в лаб иринт поток управле ния программы . Если вы не знаете , как правильно применять операторы goto , не тратьте сил на ее его изучение и вообще откажитес ь от его ис­ пользования. Если вы привыкли пользоваться этим оператором, постарайте с ь отвы­ кнуть. По иронии судьб ы , язык С, который вовсе не нуждается в операторе goto , луч­ ше всех языков программирования прис пособлен для его применения, поскольку в ка­ честве меток можно выбирать смысловые имена, а не числа . Св од к а: п ереход ы в п рограммах Кл ючевые слова: br e a k , conti nue , goto Управля ющие оператор ы : ветвление и безусловн ы е переходы 3 03 Комментарии о бще го характера: Вы полнение каждого из трех этих операторов вызывает преры вание естественного по­ тока управлен ия про гра мм ы , которое проя вляется в скачкообразной пе редаче упра вле­ ния из одного места программы в другое . О ператор Ьreak: Опе ратор b r e a k может испол ьзоваться с л юбой из трех фо рм цикла, а также внутри оператора swi tch. Он заста вляет программу пропустить все дал ьнейшие операторы ил и конструкцию swi tch, содержащую его, и перейти к команде, следующей за цикло м ил и за о пе ратором swi tch. П ример: switch ( numЬ er ) { cas e 4 : p r i nt f ( " Э тo лучший в ыбор . \ n " ) ; br e a k ; cas e 5 : printf ( " Э тo хороший выбор . \ n " ) ; br e a k ; de fault : p r i nt f ( " Э тo плохой выбор . \ n " ) ; О ператор continue : Опе ратор continue может испол ьзоваться с л юбо й из трех форм циклов, но не в опе­ рато ре swi tch. Его вы полнение при водит к тому, что следующие за ним в теле цикла операторы про пускаются . В циклах whil e и for после это го начинается новая итера­ ция . В цикле do whil e проверяется условие выхода из цикла, а затем, при необходи­ мости, стартует следующая итерация цикла. П ример: whi l e ( ( ch = g etchar ( ) ) { i f ( ch == ' ' ) continue ; putchar ( ch ) ; chcount++ ; ! = ' \n ' ) Этот фрагмент осуществляет эхо-печать символов, отличных от пробела, и ведет их подсчет . О ператор qoto : Опе ратор goto вызы вает передачу управления в программе о пе ратору, помеченному меткой, указанной в goto . Двоеточие испол ьзуется для отделения помеченного опера­ тора от его метки. И мена меток подч иня ются тем же правилам, что и и мена пере мен­ ных. Помеченн ы й оператор может находиться до ил и после оператора goto. Форма: goto ме тка ; ме тка : опера тор П ример: top : ch = get char ( ) ; i f ( ch ! = ' у ' ) goto top ; 304 Гл ава 7 К л юч евые п онятия Один из аспектов интеллекта состоит в способности выбирать реакцию в зависимо­ сти от ситуации. В силу этого обстоятельства операторы выбора представляют собой фундамент для разработки программ с элементами интеллекта. В языке С выбор реали­ зуется с помощью операторов i f, i f el s e и swi tch, а также условной операции ( ? : ) . О ператоры i f и i f e l s e используют условие проверки для определения того, какие операторы должны быть выполнены. Любое ненулевое значение рассматривается как истинное значение , в то время как ноль - как ложное . Обычно в проверках используют­ ся условные выражения, в которых сравниваются два значения , и логические выраже­ ния , в которых посредством логических операций создаются сложные выражения. Один из ос новных принципов , которые все гда следует иметь в виду, глас ит : если вы хотите прове рить два условия , вы должны использовать соответствую щую логиче­ скую операцию и два заверше нных выражения проверки. Наприме р, две следую щие попытки ошибочны: if ( а < х < z ) / / непр авил ь н о - о т сутствует логич е ская о п ер ация i f ( ch ! = ' q ' & & ! = ' Q ' ) / /непр авильно - отсутствует завершенное выражение Напомним, что правильный спос об заклю чается в соединении двух условных вы­ ражение в одно с помощью логической операции: if (а < х && х < z ) / / для о бъединения двух выр ажений 1 1 исполь зуе тся опер ация & & i f ( ch ! = ' q ' & & ch ! = ' Q ' ) / / для о бъединения двух выр ажений 1 1 исполь зуе тся опер ация & & Управляющие операторы, представленные в двух последних главах, позволяют с о­ ставлять программы , которые обладают большими возможностями и спос обны ре­ шать более сложные задачи, нежели те, которые рассматривались в начальных главах книги. Чтоб ы убедиться в этом, достаточно сравнить текущие примеры программ с программами из предше ствую щих глав. Р езю м е В данной главе б ыло рассмотрено несколько тем , и сейчас проведем их краткий обзор. О ператор i f использует условие прове рки для определения , когда программа должна выполнить какой-либо отдельный оператор или блок операторов. В ыполне­ ние происходит в том случае , когда проверочное выраже ние получает ненулевое зна­ че ние , и не происходит , если это значение равно нулю . О ператор i f el s e позволяет производить выбор из двух возможностей. Если проверяемое значение не равно нулю , выполняется опе ратор, предше ствующий el s e . Если про веряемое значение являет­ ся нуле вым , выполняется опе ратор, следую щ ий за el s e . Если воспользоваться дру­ гим опе ратором i f , который непосредстве нно следует за e l s e , можно построить структуру, которая производит выбор из некоторой последовательности возмо жных вариантов. Управля ющие оператор ы : ветвление и безусловные переходы 305 Часто в качестве выражения проверки приме няется услов'Ное въ�раже'Ние, то есть вы­ ражение , построе нное с ис пользованием одной условной операции, такой как, на­ пример, < или ==. С помощью логических опе раций языка С можно создавать различ­ ные комбинации условных выражений, представляю щие более сложные выражения . Услов'Ная опершция ( ? : ) позволяет создавать выражения , которые во многих случаях оказываются более компактной альтернативой операторам i f el s e . Семейство символьных функций ctyp e . h, в с остав которого входят, например, i s sp a c e ( ) и i s alpha ( ) , предлагает удобные инструментальные средства для построе­ ния выражений прове рки на базе класс ификации с имволов . О пе ратор switch предоставляет возможность выбора из заданной последователь­ ности операторов, помече нных целочисленными значе ниями. Если целое значение условия проверки, следую ще го непос редстве нно за клю чевым словом опе ратора swi tch, совпадает с одной из меток, управление передается оператору, с которым свя­ зана эта метка . Управление проходит чере з операторы , следую щие за помеченным оператором, пока не встретится оператор b r e a k . И , наконец, операторы b r e a k , continue и goto это операторы безусловного пере­ хода, которые заставляют программу изменить естественный ход процесса и передать управление в другое место. Оператор break вынуждает программу перейти к выполне­ нию оператора, который непосредственно следует за концом цикла или оператора swi tch, содержащего break. Оператор continue заставляет программу пропустить вы­ полнение опе раторов, следую щих за ним в теле цикла, и начать новую итерацию. - В оп росы для са м оконтроля 1 . Определите , какие выраже ния истинны, а какие ложны . L 100 > 3 && ' а ' > ' с ' б. 1 0 0 > 3 1 1 в. 'а'>'с' ! ( 1 0 0 >3 ) 2 . Напишите выраже ния , формулирую щие следую щие условия : а. Значение nurnЬer равно или больше 90, но меньше 1 0 0 . б . Значение переме нной c h н е является символом q ил и k . в. Значение пе ременной nurnЬer находится в промежутке между 1 и 9 (вклю чая концевые значения ) , но не равно 5 . г . Значение nurnЬer н е попадает в диапазон о т 1 до 9 . 3 . В приведенной ниже программе используются чрезмерно сложные выражения, в ней также присутствуют явные ошибки. Упростите программу и устраните ошибки. #include < s t dio . h> int rnain ( vo i d ) { int weight , height ; /* 1 */ /* 2 */ / * в е с в фунтах , р о с т в дюймах * / /* 4 */ /* 5 */ s ca n f ( " % d , wei ght , height ) ; /* 6 */ i f (weight < 1 0 0 & & height > 6 4 ) 306 Гл ава 7 /* 7 */ i f ( h e ight >= 7 2 ) p r i nt f ( " B aш в е с слишком мал для в ашег о рост а . \ n " ) ; / * 9 */ e l s e i f ( h e i ght < 7 2 & & > 6 4 ) /* 1 0 */ p r i nt f ( " B aш в е с мал для вашего роста . \ n " ) ; /* 1 1 */ e l s e i f ( weight > 3 0 0 & & ! (weight <= 3 0 0 ) /* 12 */ & & height < 4 8 ) /* 13 */ i f ( ! ( height >= 4 8 ) ) p r i nt f ( " B aш р о с т мал для в ашего в е са . \ n " ) ; /* 15 */ els e /* 1 6 */ print f ( "Y в ас иде ал ь ный в е с . \ n " ) ; /* 17 */ r eturn О ; 4. Каковы числовые значения кажд ого из следую щих выражений? а. 5 > 2 б. 3 + 4 > 2 & & 3 < 2 в. х >= у 1 1 у > х г. d 5 + ( 6 > 2 ) д. ' Х ' > ' Т ' ? 1 0 : 5 е. х > у ? у > х : х > у 5 . Что напе чатает следую щая программа? #include < s t dio . h> int main (void) { int num ; for ( num 1 ; num <= 1 1 ; num++ ) { i f ( num % 3 == 0 ) putchar ( ' $ ' ) ; else putchar ( ' * ' ) ; putchar ( ' # ' ) ; putchar ( ' % ' ) ; } putchar ( ' \ n ' ) ; r eturn О ; 6 . Что напе чатает следую щая программа? #include < s t dio . h> int main (void) { int i = О ; whi l e ( i < 3 ) switch ( i + + ) print f ( " fat " ) ; cas e О cas e 1 : print f ( " hat " ) ; cas e 2 : print f ( " cat " ) ; de f ault : print f ( " Oh no ! " ) ; } putchar ( ' \ n ' ) ; r eturn О ; Управля ющие оператор ы : ветвление и безусловные переходы 3 07 7. Какие ошибки допуще ны в следую ще й программе ? #include < s t dio . h> int main ( vo i d ) { char ch ; int l c О ; / * подсч е т символов нижнег о р е г истр а i n t uc О ; / * подсч е т символов в ерхн е г о р е гистр а int ос О ; / * подсч е т в с е х осталь ных симво лов whi l e ( ( ch ge tchar ( ) ) ! '#') = = = = i f ( ' а ' <= ch >= ' z ' ) l c+ + ; el s e i f ( ! ( ch < ' А ' ) 1 1 uc+ + ; о с++ ; ! ( ch > ' Z ' ) p r i nt f ( % d lower cas e , % d upp ercas e , % d other , l c , u c , о с ) ; r eturn О ; 8 . Что напе чатает следую щая программа? /* r e t i r e . c * / #include < s t dio . h> int main ( vo i d ) { int age 20; whi l e ( ag e++ <= 6 5 ) { i f ( ( age % 2 0 ) 0 ) / * Дели тся ли возр аст на 2 0 ? * / print f ( " Вам %d ле т . Получите заслуженную надбавку к зарплате . \n" , age ) ; i f ( ag e 65 ) printf ( "B aм are %d ле т . Получите в аши золотые ч асы . \ n " , age ) ; = == = r eturn О ; 9 . Что напе чатает в приведенная ниже программа в ответ на следую щий ввод? q с g ь #include < s t dio . h> int main ( vo i d ) { char ch ; whi l e ( ( ch ge tchar ( ) ) { i f ( ch ' \n ' ) continue ; print f ( "Шaг 1 \ n " ) ; == ! = '#') 308 Гл ава 7 i f ( ch == ' с ' ) continue ; q el s e i f ( ch 'Ь ' ) br e a k ; el s e i f ( ch = = ' g ' ) goto l a s t s tep ; print f ( "Шaг 2 \ n " ) ; l a s t s t ep : print f ( "Шaг 3 \ n " ) ; } p r i nt f ( " Г o тoвo \ n " ) ; r eturn О ; 1 0 . Перепишите программу из пункта 9 таким образом, чтобы она не использовала операторы continue и goto, но при этом не изменила поведение . Уп ражн ен и я по програм м ирован и ю 1 . Напишите программу, которая читает входные символы до тех пор, пока не встретится с имвол #, а затем отоб ражает количество считанных пробелов , ко­ личе ство с имволов новой строки и количе ство всех других с имволов. 2 . Напишите программу, которая читает входные символы до тех пор, пока не встретится с имвол #. Программа должна печатать каждый символ и е го АSС П­ код в десятичном представлении. Рас печатайте выходные данные по восемь пар символ - код в строке . Совет: ис пользуйте счетчик с имволов и операцию деле­ ния по модулю ( % ) для печати символа новой строки для каждых вос ьми итера­ ций цикла. 3. Напишите программу, которая читает целые числа до тех пор , пока при вводе не встретится О. Как только ввод прекратится , программа должна сообщить об ще е количество четных чисел во входных данных (за исклю че ние м О ) , сред­ не е количество четных чисел, общее количество нечетных чисел во входных данных и среднее значение нечетных чис ел. 4. Ис пользуя операторы if el s e , напишите программу, которая читает входную последовательность символов , пока не встретится символ #, заменяет каждую точку на вос клицательный знак, каждый восклицательный знак в исходном входном потоке - на два восклицательных знака, и сообщает в конце о количе­ стве выполне нных замен. 5 . Выполнить упражне ние 3 с ис пользованием оператора swi t ch. 6 . Напишите программу, которая читает входные символы до тех пор, пока не встретится символ # , и сообщает, сколько раз во входной последовательности символов встретило с ь сочетание ei . Н а зам етку ! Эта программа должна "помнить" предыдущий символ , равно как и те кущий си мвол . П рове рьте е е н а входной последовательности си мволов "Rece ive your eieio award''. Управля ющие оператор ы : ветвление и безусловн ы е переходы 3 09 7. Напишите программу, которая запрашивает ввод количества часов, отработан­ ных за неделю , а зате м выводит на печать заработную плату без вычетов, сумму налогов и зарплату после вычетов. Примите во внимание следую щие условия : а. Базовый почасовый та риф = $ 1 0 .00 /час б. Пе ре работка (при превыше нии 40 часов в неделю ) = в полтора раза в. Налоговая ставка : 1 5 % с первых $300 20% с о следую щих $ 1 50 5 % с остальной суммы Воспользуйтесь константами #de fine, и пусть вас не беспокоит тот факт , что рассматриваемый пример не с оответствует де йствую щему налоговому законо­ дательству. 8. В условие (а) упражне ния 7 внесите такие измене ния , чтобы программа предла­ гала меню для выбора ставки зараб отной платы. С этой целью используйте опе­ ратор swi t ch. Начало выполне ния программы может выглядеть приблизитель­ но так: ********************************************************************* Вв еди т е число , со ответс твующе е пр едпочита емой ставке з арабо тной платы или действию : 1 ) $ 8 . 7 5/час 2 ) $ 9 . 3 3 /ч а с 4 ) $ 1 1 . 2 0 /ч а с 3 ) $ 1 0 . 0 0 /ч а с 5 ) выход ********************************************************************* Если выбраны варианты 1 -4, программа должна потребовать ввода отработан­ ных за неделю часов. Программа должна выполнять цикл до тех пор, пока не будет введена цифра 5 . Если будет введе на цифра , отличная от цифр в диапазо­ не от 1 до 5, программа должна напомнить пользователю , каким долже н быть правильный ввод , после чего начать следую щий цикл ввода . Воспользуйтесь константами #de fine для представле ния различных налоговых ставок и ставок заработной платы . 9 . Напишите программу, которая в качестве входных данных принимает целое число, а зате м выводит на экран вс е простые числа , которые меньше или равны введенному числу. 1 О. В 1 988 году шкала федеральных налоговых ставок Соединенных Штатов за по­ следне е время б ыла одной из самых простых. О на разбита на четыре категории, каждая из которых соде ржит две ставки. Ниже приведены самые общие данные (суммы в долларах представляют собой налогооблагаемый доход ) : Категор ия На.лог Одинокий 1 5 % с первых $ 1 7 850 плюс 28% при превышении этой суммы Глава семьи 1 5 % с первых $23 900 плюс 28% при превышении этой суммы Состоит в браке , совме­ стное ведение хозяйства 1 5 % с первых $29 750 плюс 28% при превышении этой суммы Состоит в браке , раздель- 1 5 % с первых $ 1 4 8 75 плюс 28 % при превышении этой суммы ное ведение хозяйства 31 0 Гл ава 7 Наприм е р , одинокий ра ботник, получаю щий налогоо благае мую з араб отную плату $20 ООО, платит налоги в сумме 0 . 1 5 х $1 7 850 + 0.28 х ($20 ООО - $ 1 7 850 ) . Напишите программу, которая предоставляет пользователю возможность вы­ брать кате горию и налогооблагаемый доход, после чего вычисляет сумму нало­ гов. Воспользуйтес ь циклом с те м, чтобы пользователь мог рассмотреть различ­ ные варианты налогообложения. 1 1 . Компания АБС Mail O rder Groce гy , торгую щая б акалейными товарами по за­ казам, поступаю щим по электронной почте, продает артишоки по цене $ 1 .25 за фунт, с веклу по $0 .65 за фунт и морковь по $0 .89 за фунт . Она предоставляет 5-проце нтную с кидку на заказы на сумму $ 1 00 без учета затрат на транспорти­ ровку. О на назначает тариф в сумме $3 .50 за доставку и обраб отку заказа вес о м в 5 фунтов и ниже, $ 1 0 .00 за обработку и доставку заказа ве сом от 5 до 20 фунтов и $8 .00 плюс $0 . 1 0 за каждый фунт для при заказе с вес о м , превышающем 2 0 фун­ тов. Напишите программу, использую щую опе ратор swi tch в цикле, котором в ответ на ввод с имвола а пользователю предоставляется возможность указать вес заказываемых артишоков в фунтах, в ответ на ввод Ь - вес заказывае мой с веклы в фунтах, в ответ на ввод с - вес заказываемой моркови в фунтах и в ответ на ввод q - заве ршить проце с с формирования заказа . Затем программа вычисляет об щие издержки, с кидку, если таковая имеет место , расходы на доставку и окончательную сумму заказа. Затем программа должна отоб разить на экране всю информацию о покупке : стоимость фунта товара , количество заказанных фунтов, стоимость данного заказа в расчете на каждый овощ, общую стоимость заказа , скидку (е сли есть) , стоимость доставки и итоговую сумму заказа с учетом всех факторов. ГЛА ВА 8 С и м во л ь н ы й ввод - в ы вод и ве р и ф и ка ц и я ввода в этой главе: • • в дал ь н ей ш ее изуч ен и е воп росов в в ода-вывод а и разл ичия м ежд у буфериз ова н н ы м и н ебуферизован н ы м в в од ом д а нных М од ел и рование усло вия конца фа йла с кл ави атуры • • использование п ерена пра вления для установки соед и н ения программы с фа йлами созд а н ие дружеств енных пол ьз овательских и нтерфей сов вы числительном мире те рмины ввод и в'ывод употре бляются в не с кольких смыс­ лах. М ы го во р им об устро йствах в вода и вывод а , таких как клавиатур ы, дис­ ковые нако пители и лаз е р ные принте р ы . М ы говорим о данных, кото рые вводятся в систему и вы водятся из с истемы . Мы употребля ем эти слова в отно ше нии функций, кото рые выполняют ввод и вывод . О с новное в нимание в этой главе уделяет­ ся именно функциям ввода-вы вода . Функции ввода-вывода транс портируют инфо рмацию в ва шу про гра мму и из не е ; приме р а ми мо гут служит ь т а кие функции , как p r i n t f ( ) , s c an f ( ) , g e t c h a r ( ) и putchar ( ) . В ам уже приходилось встре чаться с этими функциями в предыдущих гла­ вах, а в этой главе вы изучите их ко нце птуальные о сно в ы. На ряду с этим вы уз наете , как можно улучш ить пользовательс кий инте рфе йс про гра мм. Первона чально функции ввода-вывода не входили в о пределе ние я зыка С . Их раз­ работка б ыла оставлена за ре ализа циями. На практике моделью для этих функций стала реализация языка С на б а зе о пе ра ционной с исте мы U nix. Б иблиотека функций языка ANSI С , призна вая вс ю важность функций, разработа нных в прошлом , содер­ жит большое количество функций ввода-вы вода , ориентирова нных на работу под управление м U nix, в том числе и не которые из тех, кото рые мы использовали выше . Пос кольку эти станда рт ные функции должны раб отат ь в широко м диапазоне компью­ терных с ред, они редко пользуются пре имуществами, характерными для ко нкретных с исте м. В с илу этого обстоятельства мно гие поставщики вар иантов яз ыка С предла га­ ют дополнительные функции ввода-в ывода, кото рые используют эти особые сво йства , такие как, на приме р , по рты ввода-вы вода микропро це с с о ро в Inte l или стандартные 31 2 Гл ава 8 программы ПЗУ Macintosh. Другие функции или семейства функций вклю чаются в конкретные опе рационные систе м ы , которые поддерживают, наприме р , с пе ци­ альные графиче с кие интерфейсы наподобие инте рфейсов о пера ционных с истем Windows и Macintosh OS. Эти с пециализированные, не стандартные функции предос­ тавляют возможность писать программы , которые используют конкретные компью­ теры с больше й эффе ктивностью . К сожале нию , они зачастую не могут ис пользовать­ ся в средах других компьютерных с исте м . Соответственно , мы сосредоточим ос нов­ ное внимание на стандартных функциях ввода-вывода, которые доступны на всех ваших с истемах, поскольку они позволяют с о здавать пе реносимые программы , кото­ рые ле гко можно пе ре носить с одной системы на другую . О ни также повышают уро­ вень универс альности программ, позволяя использовать файлы для ввода и вывода. Одна важная задача, с которой сталкиваются многие программы, связана с про· веркой допустимости входных данных, или ве рификацией данных, то есть в прове рке , соответствуют ли данные, вводимые пользователями, ожиданиям программы. В дан­ ной главе рас сматриваются не которые из проблем , связанные с проверкой допусти· мости данных, и предлагаются методы ее ре шения . Односи м вольн ые фун кц и и ввода-вывода: ge tchar ( ) и putchar ( ) К а к можно б ы л о убедитьс я во время изуче ния мате риала главы 7 , функции getch ar ( ) и put char ( ) выполняют посимвольный ввод и вывод . Такой метод решения задачи ввода-вывода может показаться вам нерациональным и неразумным . В конце концов, вы ле гко можете считывать группы символов, состоящих из б оле е чем одного символа , однако этот метод соответствует возможностям компьюте ра . Более того , та· кой подход служит ос новой б ольшей части программ, в задачу которых входит обра· ботка те кста, иначе говоря , об ычных слов. Чтобы вспомнить, как работают эти функ­ ции, рас смотрим очень простой пример , представленный в листинге 8 . 1 . Эта про· грамма принимает символ с клавиатуры и отображает е го на экране . Такой проце с с называется эхо-повтором ввода. В не й ис пользуется цикл whi l e , который заве ршается в случае ввода символа # . листинг 8.1 . Программа echo . с / * e cho . c повтор я е т вводимые символы • / #incl ude <s tdio . h > int main ( vo i d ) { char ch ; - - whi l e ( ( ch = g etchar ( ) ) putchar ( ch ) ; != '#') r e turn О ; Язык ANSI С ассоциирует заголовочный файл s tdi o . h с ис пользованием функций getch ar ( ) и put char ( ) , и мы вклю чили этот файл в программу именно по этой при· чине. (Как правило, функции get char ( ) и putchar ( ) не являются функциями в пол- Символьный ввод-вывод и верификация ввода 313 ном с мысле этого слова , они определяются пос редством макросов препроце с с ора; этой теме будет пос вяще на глава 16.) При выполне нии этой программы возможе н примерно такой диалог: З �рав ствуйте . я хоте• би [ enter] Здр авствуйте . Я хо тел бы пр вобре стк #3 с ети• к артофе .п я . [ enter] приобр е с ти Ознакомившись с тем , как работает эта программ а , вы , очевидно, захотите узнать, почему нужно ввести с клавиатуры вс ю строку, прежде чем входные с имволы будут отоб ражены на экране . В ы , возможно , также захотите знать, нет ли лучшего способа заве ршить ввод. Использование специального символа , такого как #, для заве ршения ввода , не позволяет указывать этот символ в тексте . Чтобы ответить на эти вопрос ы, рассмотрим, каким образом программы на языке С обрабатывают ввод с клавиатур ы . В частности, выясним, что такое буферизация , и ознакомимся с понятием стандарт­ ного ВХОД НОГО файла . Буферы Когда вы выполняете приведе нную выше программу на не которых с истемах, текст, который вводится с клавиатуры, немедленно отображается на экране. Выполняя эту программу, вы получите нечто , подобное следую щему ре зультату: ЗЗддрр ааввссттввууйй ттее . . ппррииоо ббрр е е ссттии # ЯЯ ххоо тте елл ббыы [ еnt е r ] Подобное поведение не является типичным. В большинстве систе м ничего не про· изойдет, если не нажать клавишу <Enter>, как и в пе рвом примере . Не медле нный эхо­ вывод символов входных данных на экране представляет собой приме р иебуфеjJизоваи иого , или прямого ввода, означаю щего , что символ, который вы ввели с клавиатуры, не медленно становится доступным для ожидаю щей его программы . С другой стороны, задержанный эхо-вывод характе ризует буфеjJизоваииъ�й ввод , когда введе нные вами символы накапливаются и хранятся во време нной области, называе мой буфеjJом. На­ жатие клавиши <Enter> приводит к тому, что введенный блок символов становятся доступными для программы . На рис . 8 . 1 с равниваются эти два вида ввода . Для чего нужны буферы? Во-первых, пе ресылка нескольких символов в виде блока требует меньше времени, чем посимвольная пересылка . Во-вторых, в случае опечатки можно выполнить необходимые исправления с клавиатуры. Когда вы в конечном итоге нажмете клавишу <Enter>, вы сможете передать откорректированную версию текста . С другой стороны, небуферизованный ввод может подходить для некоторых инте­ рактивных программ. Например, в играх вы хотите , чтобы действие команды испол­ нялось, как только нажимается соответствую щая клавиша. В результате как буферизо· ванный, так и не буфе ривованный ввод находят широкое приме не ние . Буферизация реализуется в двух видах: полиостъю буфеjJизоваииЪ1й ввод-в'Ьiвод и по стро'Чио буфеjJизоваии'Ьlй ввод-въ�вод. В рамках полностью буферизованного ввода буфе р очищается (содержимое посылается по ме сту назначе ния ) в момент его заполнения . Этот вид буфе ризации обычно осуществляется при вводе файла . Размер буфера зави­ сит от системы , но обычно их размеры с оставляют 5 1 2 или 4096 байтов . При исполь­ зовании построчно буфе ризованного ввода-вывода пос ылка его соде ржимого произ- 31 4 Гл ава 8 водится всякий раз , когда об наруживается символ новой строки. Ввод с клавиатуры представляет с об ой приме р обычного построчно буфе ризованного ввода, когда при нажатии клавиши <Enter> буфе р очищается . Небуферизованный ввод ! IH Содержимое немедленно становится доступным для программы ---��·_ ::j Буферизованный ввод buffer ---::,- :-1 �-J l�_J Символы посыла ются один за другим в буфер в том порядке, в каком они вводятся с клавиатуры Содержимое буфера становится доступным для программы Ри с. 8.1 . Буфсризован'Н:ый и небуфсризованнъtй ввод С каким типом ввода вы будете иметь дело - буферизованным или не буфе ризован· ным? Язык ANSI С тре бует , чтобы ввод был буфе ризованный, в то же вре мя K&R ос· тавляет право выбора за разработчиками компилятора. Тип ввода, используемый в систе ме, можно определить, запустив на выполнение программу e cho . с и проанали· зировав ее поведение . Причина выбора языком ANSI С буферизованного ввода в качестве стандарта за· клю чается в том, что не которые компьютерные системы не могут работать с небуфе· ризованным вводом. Если конкретный компьютер допускает небуферизованный ввод, то вполне вероятно , что компилятор С разрешает небуферизованный ввод в качестве дополнительной и необязательной возможности. Например, многие компиляторы для персональных компьютеров , совме стимых с IBM РС , предоставляют спе циальное се· мейство функций, подде рживаемых заголовочным файлом conio . h и предназначе н· ных для обеспе чения небуфферизованного ввода·вывода. К числу этих функций отно· сится функция ge tche ( ) , используемая для эхо·вывода символов (Ввод с эхо-въ1водом оз· начает, что вводимый символ отображается на экране , а ввод без эхо-въ1вода - что значения нажатых клавиш на экране не отображаются . ) В опе рационной системе Unix используется другой подход, поскольку буфе ризацией управляет сама система U nix. Что кас ается Unix, то в не й применяется функция io ctl ( ) (входит в состав библиоте· ки Unix, но не является частью стандарта С ) , которая устанавливает нужный тип вво· да , и функция g etchar ( ) , которая ведет себя соответствую щим образом. В языке ANSI С функции s etbu f ( ) и s etvb u f ( ) ( с м . главу 1 3 ) об еспечивают не который контроль над буфе ризацией, но характерные для ряда с истем ограниче ния с нижают их эффек· тивность. Символьный ввод-вывод и верифик ация ввода 31 5 Короче говоря , в стандарте ANSI не существует способа задания небуферизованно­ го ввода; с о ответствую щие с редства зависят от компьюте рной системы . В этой книге мы будем полагать, что вы используете буферизованный ввод, в связи с чем приносим свои извине ния приверже нцам небуфе ризованного ввода. З аверш ен и е ввода с клав и атуры При вводе символа # выполнение программы e cho с останавливается , что удобно до тех пор , пока этот с имвол исклю чен из обычных входных данных. Однако, как вы уже видели, с имвол # может встречаться при обычном вводе. В иде альном случае хо­ телось бы иметь с имвол заверше ния ввода , который в обычном тексте не появляетс я . Такой с имвол н е может неожиданно появиться в с е редине некоторого входного те к­ ста , в результате чего программа останавливается там, где это не ожидается. Язык С предоставляет такие возможности, однако, чтобы вос пользоваться ими, вы должны знать, как С работает с файлами. - Ф айлы. потоки и ввод да н н ых с кла в иатуры Файл - это область памяти, в которой хранится информация . Обычно файл раз­ мещается на не котором носителе постоянной памяти, например, гибком и жестком магнитном дис ке или магнитной ле нте. В ажность файлов для компьютерных с истем не сомненна . Например, программы , написанные на С , хранятся в файлах, программы, используемые для компиляции ваших программ, также хранятся в файлах. Последний пример указывает на то , что некоторые программы испытывают потребность доступа к конкретным файлам. При компиляции программы, сохране нной в файле e cho . с , компилятор открывает этот файл и читает его с одержимое . По окончании компиля­ ции он закрывает этот файл. Другие программы, такие как текстовые процессоры , не только открывают, читают и закрывают файлы , они также выполняют в них запись. С, будучи мощным, гибким языком программирования с множеством достоинств, обладает многими библиоте чными функциями, предназначенными для открывания, чтения, запис и и закрытия файлов. На одном уровне он может раб отать с файлами, используя с этой целью основные инструментальные средства манипулирования фай­ лами, которые имеет опе рационная система хоста . Эти инструментальные с редства получили название 11,изкоуровн,евого ввода-въtвода. В связи с тем, что между компьюте рны­ ми системами существуют очень важные различия, невозможно с оздать стандартную библиотеку универсальных функций низкоуровневого ввода-вывода , к тому же стан­ дарт ANSI С и не пытается делать это; в то же время язык С работает с файлами и на другом уровне , получившем название стан,дартн,ого пакета ввода-въtвода. При этом пред­ полагается с о здание стандартной модели и стандартного набора функций ввода­ вывода , предназначе нных для работы с файлами. На этом боле е высоком уровне раз­ личия между с истемами нивелируются специальными ре ализациями языка С , обес пе­ чиваю щими единый интерфейс , с которыми вы работаете . О каких различиях идет речь? Разные с истемы , например, сохраняют файлы раз­ ными спос обами. Н е которые из них хранят содержимое файла в одном ме сте , а ин­ формацию о не м - в другом ме сте . Другие с истемы встраивают опис ание файла в сам файл. При работе с текстами многие с истемы используют одиночный символ новой строки для обозначения конца строки. Иногда для представления конца строки при- 31 6 Гл ава 8 меняется комбинация с имволов возврата каретки и перевода строки. Есть систе мы, измеряю щие размер файлов до ближайше го байта , другие же измеряют файлы блока­ ми байтов. Использование стандартного пакета ввода-вывода позволяет с мягчить проблемы, порождаемые такими различиями. Благодаря этому пакету, вы можете выполнить прове рку на наличие с имвола новой строки, вос пользовавшись конс трукцие й ( ch == ' \ n ' ) . Если система фактически использует комбинацию с имволов возврата каретки и пере вода строки, то функция ввода-вывода осуществляет автоматическую трансляцию в прямом и об ратном направлениях между двумя этими представлениями. По иде е , программа на С имеет дело непосредстве нно с потоком, а не с файлом . Поток представляет с об ой идеализированный поток данных, н а который фактичес ки отоб ражаются входные и выходные данные . Это означает, что различные виды вход· ных данных с отличаю щимися с войствами представлены в виде потоков , с войства ко­ торых в значительной степени унифицированы . Проце с с открытия файла в этом слу· чае становится процессом ассоциирования конкретного потока с файлом, а операции чтения и записи осуществляются че ре з этот поток. В главе 1 3 представлено более подробное обсуждение файлов. В данной главе мы просто отметим, что С рассматривает устройства ввода и вывода как обычные файлы, размещенные на запоминающих устройствах. В частности, клавиатура и устройства отоб ражения расс матриваются как файлы, которые автоматичес ки открываются каж· дой программой на С . В вод данных с клавиатуры представлен потоком с име не м s tdi n , а данные , выво­ димые на экран (или на телетайп или другое устройство вывода ) , представлены пото­ ком с именем s tdo ut. Функции g etchar ( ) , putchar ( ) , printf ( ) и s can f ( ) - элементы стандартного пакета ввода-вывода , и они работают с этими двумя потоками. Одним из следствий рассмотренных выше подробносте й есть то , что вы можете использовать одну и ту же технологию как при вводе данных с клавиатуры , так и при работе с файлами. Например, программе , с читывающей файл, не обходим способ об­ наруже ния конца файла , чтоб ы знать, где закончить считывание . В силу этого обстоя· тельства функции ввода в языке С укомплектованы встроенным с редством обнаруже­ ния конца файла . Пос кольку данные , вводимые с клавиатуры, рассматриваются как файл, то вы также должны иметь возможность употребить это средство обнаружения конца файла для заве ршения ввода данных с клавиатур ы . Рассмотрим, как вс е это де­ лается с начала на приме ре работы с файлами. Конец файла В операционных систе мах возникает потре бность в т е х или иных способах, отме· чаю щих, где начинается и где кончается файл. Один из таких методов обнаружения конца файла тре бует помещения в файл спе циального символа , отмечающего его ко· не ц. Этот метод когда-то ис пользовался, например, в текстовых файлах операцион· ных с истем СР /М , IBM-D OS и MS-D O S . В настоящее время эти операционные с исте­ мы могут прибегать к помощи встроенного с имвола <Ctrl+Z> для отме чания конца файла . Было время , когда этот маркер был единстве нным средством, которое эти операционные системы употребляли с этой целью , однако с е йчас доступны и другие варианты, например , отслеживание размеров файла . Таким образом, в с овреме нных систе мах в текст может быть, а может и не быть встроен символ <Ctrl+Z>, но если он Символьный ввод-вывод и верификация ввода 317 встроен, то операционная с истема рассматривает его как марке р конца файла . Рису­ нок 8.2 служит иллю страцие й такого подхода . Второй подход заклю чается в том, что опе рационные системы хранят информа­ цию о размере файла. Если файл с одержит 3000 байтов, и программа прочитала 3000 байтов, значит, она достигла конца файла . О перационная система MS-D O S и ей по­ добные применяют этот подход для двоичных файлов, поскольку метод позволяет хранить все символы , вклю чая и <Ctгl+Z>. Более поздние верс ии системы DOS также используют этот подход при работе с текстовыми файлами. С истема Unix применяет этот подход в отноше нии вс ех файлов. Язык С управляет все м этим разнообразием методов с помощью функции getch ar ( ) , которая возвращает с пециальное значе ние при достижении конца файла не зависимо от того, как на с амом деле операционная с истема обнаружила этот конец файла . Это с пециальное значение получило имя EOF ("end of file" - " конец файла" ) . Следовательно, значением, которое возвращает функция ge tchar ( ) , когда об наружи­ вает конец файла , является EOF . Функция s can f ( ) также возвращает EOF при обнару­ же нии конца файла . Обычно EOF определяется в файле s tdio . h следую щим образом: #de fine EOF ( - 1 ) Поче му используется значение - 1? Обычно функция ge tchar ( ) возвращает значе­ ние в диапазоне от О до 1 2 7 , пос кольку таковыми являются значе ния , соответствую­ щие стандартному набору символов, в то же время она может возвратить значение в пределах от О до 2 5 5 , если с истема воспринимает расширенный набор символов . В любом случае значение - 1 н е с оответствует н и одному и з символов, следовательно , оно может ис пользоваться в каче стве признака конца файла . Некоторые с истемы могут определять значе ние E OF как величину, отличную от - 1 , но его определение всегда отличается о т возвращаемого значения , генерируемого до­ пустимым входным символом. Если вы вклю чаете в программу файл s tdio . h и исполь­ зуете с имвол EOF , вам не стоит беспокоиться относительно его числового значения . При этом необходимо отметить, что EOF представляет значе ние , которое сообщает о том, что обнаружен коне ц файла : это не е сть с имвол, обнаруженный в файле . В с е это хорошо, но как вы можете использовать маркер E OF в программе ? Сравни­ те значение , возвращаемое функцией getchar ( ) с EOF. Если они отличаются друг от друга , это означает, что вы е ще не достигли конца файла . Текст : В половине двен адцатого с северо-за п ада , со стороны деревни Ч м а ровки , в Ста ргород вошел мол одой человек лет дв адцати восьми . Текст в фа йл е: В nоловине двенадцатого с северо-заnада,\л со стороны дерев114 Чмаровки, в Старгород\л вошел молодой человек лет двадцати восьми\n'Z Р и с . 8 . 2 . Файл и маркер кон.-ца файла 31 8 Гл ава 8 Другими словами, вы можете использовать выражение наподобие приведенного ниже: whi l e ( ( ch = g etchar ( ) ) ! = EOF ) А что происходит, когда вы вводите не файл, а данные с клавиатуры? Б ольшая часть с истем (но далеко не все ) обладают возможностью имитировать условия конца файла при вводе с клавиатуры . Зная об этом, вы можете пере писать программу e cho базового ввода и эхо-вывода, как показано в листинге 8 . 2 . Листинг 8.2. Программа echo eof . с _ / * e cho eo f . c пов тор я е т ввод до момен т а до с тижения конца ф айла * / #incl ude <s tdio . h > int main ( vo i d ) { int ch ; -- whi l e ( ( ch = g etchar ( ) ) putchar ( ch ) ; ! = EOF ) r e turn О ; Обратите внимание на следую щие моме нты : • • Нет не обходимости отлавливать EOF , поскольку заголовочный файл s t dio . h берет на себя эту функцию . Вам не нужно б еспокоиться о фактическом значении маркера EOF , поскольку оператор #de fi ne в файле s tdi o . h позволяет ис пользовать символьное пред­ ставление маркера EOF . В ам не нужно пис ать программный код, которые тре­ буют знания конкретного значения EOF. Переменная ch поменяла тип с char на int, поскольку пе ременные типа char могут быть представлены целыми числа­ ми без знака в диапазоне от О до 2 5 5 , в то же время EOF может иметь числовое значение 1 Такое значе ние недопустимо для пе ременной типа char без знака , но вполне допустимо для типа int. К счастью , функция get char ( ) сама име ет тип int, следовательно, она может читать символ EOF . Реализации, которые ис­ пользуют тип char со знако м , могут об ойтись объявлением переменной ch типа char , но гораздо лучше воспользоваться более общей формой. - • • . Тот факт, что переменная ch есть целое число , никак не отражается на функ­ ции putchar ( ) . На печать она выводит его символьный эквивалент. Ис пользуя эту программу применительно к вводу с клавиатуры , вам каким-то образом нужно ввести символ EOF. Разумеется , вы не можете просто ввести бук­ вы Е О F, в то же вре мя вы не можете напе чатать - 1 . (Ввод с клавиатуры - 1 пред­ ставляет собой ввод двух символов: дефиса и цифры 1 . ) Вместо этого , вы долж­ ны найти то , что требует ваша с истема. В большинстве с истем на базе Unix, на­ приме р, нажатие клавиш <C trl+D > в начале строки вызывает пе редачу сигнала конца файла . Многие микрокомпьюте рные системы распознают комбинацию клавиш <C trl+Z> в начале строки как с игнал конца файла , некоторые системы инте рпретируют комбинацию <C trl+Z> в лю б ом месте строки как сигнал конца файла . Символьный ввод-вывод и верифик ация ввода 319 Н и ж е по к а з а н п р и м е р пр и м е н е ния буфе р и з о в а н н о го ввода в п р о гра м м е e cho_ eo f . с под управлением Unix: У ве r о ве бипо даже папьто . У него н е было даже паль то . В r ород иоподо й чеп овеи воше п в зепевои в тапию ио стюи е . В г ород молодой ч еловек в ошел в зеленом в талию костюме . И . Ипьф , Е . П е тр ов И . Ил ь ф , Е . П е тров [Ctrl+D] Каждый раз , когда вы нажимаете клавишу <Ente r>, символы , хранящиеся в буфе ре , подве ргаются обраб отке , а копия строки выводится на пе чать. Это продолжается до тех пор, пока вы не с моделируете конец файла . На персональном компьюте ре для этой цели вы можете нажать комбинацию <Ctrl+Z> . О становимся на минутку и задумае мся о возможностях программы e cho _ eo f . с. О на воспроизводит на экране любые входные данные , какие вы ей предоставите . Предпо­ ложим, что вы каким-то образом предоставили в ее распоряжение файл. Затем она выводит на экран содержимое этого файла и останавливается , когда достигает конца файла , обнаружив EOF . Предположим, что вместо этого вы нашли способ направить выходные данные этой программы в файл. Затем вы можете ввести данные с клавиа­ туры и использовать программу e cho _ ео f . с, чтобы сохранить в файле то , что вы вве­ ли с клавиатуры . Предположим, что вы можете сделать и то и другое одновре менно : направить входные данные из одного файла в программу e cho _ ео f . с и послать вы­ ходные данные в другой файл. После этого вы можете воспользоваться программой e cho _ ео f . с для копирования файлов. Эта не большая программа может применяться для просмотра с одержимого файлов, для создания новых файлов и для снятия копий существую щих файлов, что довольно не плохо для такой скромной программы ! Ее ос­ новой является умение управлять потоками входных и выходных данных, но это тема последую щих разделов. Э м уляци я EOF и графи чес к и е и н тер фей с ы Идея эмуляции символа EOF возникла в среде командной строки, испол ьзующей тек­ стовый интерфе йс. В среде такого рода пол ьзователь взаимоде йствует с программой посредство м нажатия кла виш, а о перационная система генерирует сигнал EOF . Не кото­ рые практичес кие методы не очень хорошо переносятся в среды графичес ких интер­ фейсов, подобные Windows и Macintosh , в которых реал изованы более сложные пол ь­ зовател ьские инте рфейс ы , вкпючающие переме щение мыши и щелчки на кнопках. По­ ведение програ м м ы , стал кива ющейся с эмуля цией EOF , зависит от компилятора и типа прое кта. Напри мер, одно временное нажатие кпавиш <Ctrl+Z>, в за вис имости от кон­ кретных установок, может заве ршить ввод данных, а может и завершить в ы полнение всей про гра мм ы . 320 Гл ава 8 Перен аправл ен ие и файл ы В вод и вывод выполняется с ис пользованием функций, данных и устройств. Рас­ смотрим , например, программу, e cho _ eo f _ с . В не й используется функция getchar ( ) . Входным устройством (по предположению ) является клавиатура , а поток входных данных состоит из отдельных символов. Предположим , что вы хотите сохранить ту же входную функцию и тот же тип данных, но хотите изменить источник, из которого программа черпает данны е . При этом возникает вполне резонный вопрос : "Как про­ грамма узнает, откуда нужно получить входные данные?" По умолчанию программа на С , ис пользую щая стандартный пакет ввода-вывода , рассматривает свое устройство ввода-вывода как источник входных данных. Это поток входных данных, идентифицированный выше как s tdin . Его можно расс матривать как обычный способ с читывания данных в компьютер. Им могут быть такие старо­ модные устройства , как магнитная лента , перфокарты или (далее мы буде м подразу­ мевать именно этот вариант) ваша клавиатура либо же современная технология напо­ добие голосового ввода . Однако в совреме нных компьютерах это настраиваемое ин­ струментальное средство , и вы можете обнаружить входные данные в различных средах и на различных нос ителях. В частности, вы можете потребовать от программы извле кать входные данные из файла , а не вводить их с клавиатуры . Суще ствуют два способа заставить программу работать с файлами. Один из них за­ клю чается в прямом ис пользовании с пециальных функций, которые открывают, за­ крывают, читают, запис ывают файлы и тому подобное . Изучение этого метода мы ос­ тавим до главы 1 3 . Второй с пособ предполагает ис пользование программы , предна­ значенной для работы с клавиатурой и экраном, но при этом входные и выходные данные переиаправляются в различные каналы, например, в файл и из файла . Другими словами, вы пе реадре суете поток s tdin в файл. Программа getchar ( ) продолжает по­ лучать данные из потока, ее соверше нно не интересует, откуда поток получает дан­ ные . Такой подход (пере направление ) в не которых ас пектах является б олее ограни­ ченным, чем пе рвый подход, однако им гораздо проще пользоваться , при этом он по­ зволяет ознакомиться с рас пространенными методами обработки файлов. Одна из главных проблем перенаправле ния с о стоит в том , что она с вязана с опе­ рационной систе мой, но не с языком С. Однако многие с реды языка С, вклю чая опе­ рационные системы Unix, Linux и MS-DOS (ве рсии 2.0 и более поздние ) , поддержи­ вают перенаправле ние , а некоторые реализации языка С моделируют свойство пе ре­ направления в с истемах, где оно отсутствует. Мы рассмотрим перенаправле ние в сре­ дах Unix, Linux и D O S . Перена пра влен ие в Unix . Lin ux и DOS О пе рационные с истемы Unix, Linux и те кущие версии DOS позволяют выполнять перенаправле ние ввода и вывода . Перенаправле ние ввода дает вашей программе воз­ можность использовать для ввода файл вме сто клавиатуры , а пе ренаправление выво­ да - применять для вывода файл вместо экрана. Символьный ввод-вывод и верификация ввода 321 Перенаправление ввода Предположим , что в ы откомпилировали программу e cho _ eo f . с и поме стили ис· полняемую версию в файл, получивший имя e cho_e o f (или e cho_e o f . exe в систе мах DOS) . Чтобы выполнить программу, введите имя файла : e cho eo f Эта программа выполняется так, как описано выш е , получая входные данные с клавиатуры . Теперь предположим, что вы хотите использовать эту программу приме· нительно к текстовому файлу с именем wor ds . Текстовъ�u файл это файл, содержащий текст , иначе говоря, файл, в котором данные хранятся в виде символов, восприни· маемых человеком. Наприме р, это может быть реферат или программа на языке С . Файл , с одержащий инструкции н а машинном языке , например, файл с ис полняемой версией программы, не относится к категории текстовых. Поскольку программа раб о· тает с символами, она должна использоваться с те кстовыми файлами. В этом случае вместо ранее указанной команды потребуется ввести следую щую команду: - e cho eo f < wor ds Здесь символ < является операцией перенаправле ния в операционных систе мах Unix и Linux (а также в D O S ) . При этом устанавливается связь между файлом wo rds и потоком s tdin, которая обеспе чивает перекачку содержимого файла в программу e cho e o f . Программа e cho_eo f с а м а п о с е б е н е знает (или н е желает знать), что входные данные поступают из файла , а не с клавиатуры . В с е , что она знает , это как поступает в не е поток символов, благодаря чему она осуще ствляет их посимвольное считывание и отоб ражение , пока не будет достигнут конец файла . Поскольку язык С рассматривает файлы и устройства ввода·вывода как эквивале нтные категории, то файл тепе рь ста· новится устройством ввода-въ�вода. Попробуйте им вос пользоваться ! До п о лн и тель н ы е св еден и я о п ерен аправ лен и и П ри работе в системах U nix, Linux и DOS пробел ы с обеих сторон знака < н е обязател ь­ н ы . Некоторые систе м ы , такие как Am iga DOS, поддерживают перенаправление, но не допускают употребления пробелов между символом перена правления и именем фа йла. Ниже приводится приме р выходных данных программы e cho_e o f , примененной к конкретному те кстовому файлу, при этом знак $ е сть одно из стандартных приглаше· ний операционных с истемы Unix и Linux . В опе рационной системе DOS, скорее все· го , приглашение будет выглядеть как А> или С >. $ echo_eof < words Пешеходо в надо люби т ь . Пешеходы составляют большую ч а с ть ч еловеч е с тв а . Мало тог о - лучшую его ч а сть . Пешеходы создали мир . $ Итак, пе ре йдем к сути дела . 322 Гл ава 8 Перенаправление вывода Теперь предположим, что вы хотите , чтобы программа e cho_eo f пе рес ылала ваши входные данные с клавиатуры в файл с именем mywords . В этом случае потребуется ввести приведе нную ниже команду и начать ввод с клавиатуры: e cho eo f > mywords Знак > представляет опе рацию пе ре направления . О н приводит к с озданию нового файла с именем mywords , который вы можете ис пользовать в с воих целях, а зате м пе­ реадре совать выходные данные программы e cho_eo f (то есть копии с имволов, вве­ денные с клавиатуры ) в этот файл. Пе ре направление пе ре назначает поток s tdout с устройства отображе ния (ваш экран) в файл mywords . Если файл с именем mywor ds уже существует , обычно он удаляется и заменяется новым содержимым. (Многие операци­ онные с истемы, однако , предлагают возможности защиты суще ствую щих файлов, объявляя их файлами только для чтения . ) Все , что появляется на вашем экране - это те буквы , которые вы вводите с клавиатуры, а их копии поступают в файл. Чтобы за­ вершить программу, нажмите комбинацию клавиш <C trl+D> (Unix) или <Ctrl+Z> (DOS) в начале строки. Проверьте это . Если не можете придумать, что вводить с кла­ виатуры , повторите приведенный ниже пример . В не м мы присутствует приглашение $ системы Unix. Напоминаем, что ввод каждой строки заканчивается нажатием кла­ виши <Ente r>, в результате че го содержимое буфе ра передается в программу. $ echo_eof > щywords У в а с не допжв: о бить В11и аиих про б пеи при э апоиив авии , что де в ает тот ипи ивой оператор переваправпеВ11я . З апо ИВ11те топьио , что опера тор уи а эив ает ваправпеВ11 е потоиа ив фориации . Представьте себе , ч то э то воровиа . [Ctrl+D] $ После того , как <C trl+D> или <Ctrl+Z> будут обработаны , программа заве ршает ра­ боту, и приглаше ние операционной с истемы возвращается на экран. Б ыла ли выпол­ не на программа? Команда ls с истемы Unix или команда dir операционной системы DOS, которые выводят на экран список име н файлов, должны подтвердить суще ство­ вание файла mywords . Вы можете также воспользоваться командой cat в Unix и Linux или typ e в D O S для прове рки соде ржимого либо снова запустить программу e cho _ e o f , н а этот р а з перенаправив файл в программу: $ e cho_e o f < mywords Комбинированное перенаправление Теперь предположим, что вы хотите с оздать копию файла mywords и присвоить е й имя s avewo rds . Для этого воспользуйтес ь следую щей командой: e cho eo f < mywords > s avewords и дело сделано . Можно также выдать и приведенную ниже команду, поскольку поря­ док выполне ния опе раций пере направления не имеет значе ния : e cho_eo f > s av ewords < mywords С облюдайте осторожность - не используйте один и тот же файл как для ввода, так и для вывода в рамках одной и той же команды. Символьный ввод-вывод и верификация ввода e cho eo f < mywords > mywor ds . . . . 323 <--НЕПРАВИЛ ЬНО Это объяс няется тем, что команда > mywo rds усе кает исходный файл mywords до нуле вой длины, пре жде чем он будет использован в качестве входного файла . Подводя итоги, можно отметить, что существуют правила, ре гламентирую щие ис· пользование указанных выше двух операций пере направления (< и >) в операционных систе мах Unix, Linux и D O S : • • • Операция пере направления ас социирует исполпяемую программу (вклю чая стан· дартные команды операционных с исте м ) с файлом данных. О на не может быть ис пользована для с о единения одного файла данных с другим, а также для с о­ единения одной программы с другой. Входные данные можно брать только из одного файла , но не из не скольких, это справедливо и в отношении выходных данных. Об ычно проб елы между именами и операциями не обязательны , бывают эпизо­ дические исклю чения, когда ис пользуются с пециальные символы , име ющие особый смысл для командных проце с с оров U nix, Linux или DOS. Можно было бы, например , применить команду e cho _ eo f < word s . В ыше в ы ознакомились с приме рами правильно с оставленных команд . Ниже пока· зано нес колько примеров неправильно сформулированных команд, в которых addup и count являются ис полняе мыми программами, а fi s h и b e e t s - текстовыми файлами: fi s h > b e ets � Нарушается пе рвое правило addup < count � Нарушается пе рвое правило addup < fi s h < b e ets � Нарушается второе правило co unt > b e ets fi s h � Нарушается второе правило В опе рационных системах Unix, Linux и DOS реализована также операция >>, ко­ торая предоставляет возможность доб авлять данные в конец существую щего файла , а также операция канала ( 1 ) , позволяю щая подклю чать вывод одной программы к вводу другой программы . За дополнительной информацией по все м этим операциям обра· щайте сь к книгам, посвященным опе рационной с истеме Unix. Комментарии Перенаправление позволяет использовать с файлами программы, предназначе н· ные для обработки ввода с клавиатуры . В этих условиях программа должна осуще ств· лять проверку на предмет конца файла . Например, в главе 7 расс матривалась про· грамма , подсчитываю щая количество слов для появления первого символа 1 . Поме· няйте тип char переме нной ch на тип int и замените 1 на EOF в проверочном выра· же нии цикла, и вы сможете пользоваться этой программой для подсчета слов в те к· стовых файлах. Перенаправление - это конце пция командной строки, пос кольку вы задаете е е пу· тем ввода с клавиатуры специальных символов в командной строке. Если вы не ис· пользуете с реду командной строки, у вас все еще остается возможность вос пользо· ваться этой технологией. В о-первых, в некоторых инте грированных с редах имеются пункты меню , позво­ ляю щие выполнить пе ренаправление . ' ' 324 Гл ава 8 В о-вто рых , в с р едах Windows вы можете открыть окно DOS и запустить испол­ няемый файл из командной строки. По умолчанию с истема Microsoft Visual С ++ 7.1 помещает ис полняе мый файл в подкаталог с именем Debug . Имя файла будет иметь то же базовое имя , что и имя прое кта и расшире ние . ех е . В случае отладчика Codewarrior воспользуйтесь ре жимом контрольного приложе­ ния Win 32 (Win 32 Console Арр ) , который присвоит исполняе мому файлу имя Cproj Debug . ехе по умолчанию (где Cproj будет заме не но именем ваше го прое кта ) , и помес­ тите е го в папку проекта . Если перенаправление не работает, можете попытаться заставить программу от­ крыть файл напрямую . Примером может служить программ а , показанная в листинге 8.3 и сопровождаемая минимальными пояснениями. Б оле е подробную информацию можно найти в главе 1 3 . Листинг 8.3. Программа fi1e_eof . с 1 1 fi l e_eo f . c - - о ткрыть ф айл и о т о бразить его #incl ude <s tdio . h > / / для функции exit ( ) #incl ude <s tdlib . h> int main ( ) { int ch ; F I LE * fp ; char fname [ 5 0 ] ; 1 1 для з апоминания имени ф айла printf ( " Bв eди т e имя ф айла : " ) ; s c an f ( " % s " , fn ame ) ; / / о ткрыть ф айл для ч тения fp = fop en ( fname , " r " ) ; // попытк а завершил а с ь неудач ей i f ( fp == NULL ) { print f ( " He удае т ся о ткрыть ф айл . Прогр амма з а в ерше н а . \ n " ) ; exit ( l ) ; 1 1 выйти из пр огр аммы 1 1 функция getc ( fp ) получ а е т симво л и з о ткрыто г о ф айла whi l e ( ( ch = g etc ( fp ) ) ! = EOF ) putchar ( ch ) ; / / з акрыть ф айл fclo s e ( fp ) ; r e turn О ; Свод к а: как перенап рав ить в в од и вывод В бол ьшинстве систем С вы можете испол ьзовать перенаправление л ибо для всех про­ гра м м с помощью операционной системы, л ибо тол ько для про гра мм на С, благодаря возможностя м компилятора языка С . Пусть prog я вляется и менем исполняемой про­ гра м м ы и пусть fil e l и fil e 2 - имена фа йлов. Перенап равление вь1 вода в фа йл : >prog >fil e l Перенап равление ввода из фа й ла: < prog < fi l e 2 Символьный ввод-вывод и верифик ация ввода 325 Комб и н и рован ное перенапра вл ение : p r o g < fi l e 2 >fil e l prog >fi l e l < f i l e 2 Обе формы испол ьзуют fi l e 2 дп я ввода и fil e l для вывода . П роб ел ь � : Некоторые системы требуют наличия пробела слева от знака о перации перенапра вле­ ния и не требуют пробела спра ва от знака. Другие системы (например, U n ix) до пускают нал ичие пробелов с обе их сторон, л ибо запре щают эти пробел ы . Соз дан и е дружествен н ого поль з овател ьского интерфей са Б ольшинству из нас доводилось пис ать программы , которыми неудобно было поль­ зоваться. К с частью , язык С предлагает инструментальные средства , с пособные пре­ вратить ввод в более предс казуе мый и приятный процесс . К с ожалению , изучение этих инструментальных средств на первых порах порождает новые проблемы . Цель данного раздела заключается в том , чтобы прове сти вас чере з не которые из этих про­ блем к получе нию более дружественного пользовательс кого интерфейс а , который об­ легчает задачу ввода интерактивных данных и минимизирует эффект от ошибочного ввода данных. Работа с буферизова н н ы м вводом Буферизованный ввод часто удобен для пользователя, пос кольку обес печивает возможность редактирования входных данных до пе редачи их в программу, однако для программиста с имвольный ввод служит источником дополнительных забот. Про­ блема, как вы могли убедиться во время изучения приведе нных выше примеров, за­ клю чается в том, что буферизованный ввод требует нажатия клавиши <Enter> для пе­ редачи введе нной информации. Это действие пересылает также с имвол новой стро­ ки, который программа должна обраб отать. Теперь проведем исследования этой и других проблем с помощью программы отгадывания чисел. В ы выб ираете число , а компьютер пытается е го отгадать. При этом используется достаточно сложный метод, однако ос новное внимание мы уделим вводу-выводу, а не собственно алгоритму. На­ чальная верс ия такой программы показана в листинге 8 .4 . листинг 8.4. Программа gues s _ с / * gu e s s . c - - неэфф ективное и ошибочно е о тг адывание числа * / #incl ude <s tdio . h > int main ( vo i d ) { int gue s s = 1 ; printf ( "Bыбepитe целое число в промежутке от 1 до 1 0 0 . Я попробую отг адать " ) ; printf ( " eг o . \ nHaжми т e клавишу у , е сли моя дог адк а в ерн а и " ) ; printf ( " \ n клавишу n в пр о тивном случ ае . \ n " ) ; printf ( " B aшим числом явля е тся % d ? \ n " , gue s s ) ; / * получить о тв е т , ср авнить с у * / whi l e ( g etchar ( ) ! = ' у ' ) print f ( "Итaк , это буде т % d? \ n " , + + gue s s ) ; 326 Гл ава 8 printf ( " Я знал , ч то у меня получится ! \ n " ) ; r e turn О ; Приводим ре зультат выполнения этой учебной программы : Выберите целое число в пр омежутке о т 1 до 1 0 0 . Я попро бую отг адать его . Нажмите клавишу у , е сли моя дог адка в ерна и кл авишу n в пр о тивном случ а е . В ашим чи слом я вля ется 1 ? n В ашим чи слом я вля ется 2 ? В ашим чи слом я вля ется 3 ? n В ашим чи слом я вля ется 4 ? В ашим чи слом я вля ется 5 ? у я знал , ч то у меня получи тся ! В опре ки ожиданиям амбициозного алгоритма, реализованного в программ е , мы выб рали небольшое число . О братите внимание на то , что программа выполняет два предположения каждый раз , когда вы вводите n. Программа читает ответ n и рассмат­ ривает его как отрицание того , что было загадано число 1 , и при этом считывает сим­ вол новой строки как отрицание того факта , что было загадано число 2 . Одно и з решений предусматривает использование цикла whi l e для игнорирования остальных символов входной строки, в том числе и символа новой строки. В ре зульта­ те ввод , например, no ("нет" ) или no way ("ни в коем случае " ) , можно также инте рпре­ тировать аналогично n. Версия программы, представленная в листинге 8.4, инте рпре­ тирует no как два ввода. Ниже показан приме р цикла, в котором эта проблема решена : whi l e ( g etchar ( ) ! = ' у ' ) / * получить о тв е т , ср авнить с у * / { print f ( " B aшим чи слом я вля ется % d? \ n " , + + gue s s ) ; whil e ( getchar ( ) ! = ' \ n ' ) / * про пусти т ь оставшуюся ч а с т ь входной строки* / continue ; При использовании этого цикла получается следую щий диалог: Выберите целое число в пр омежутке от 1 до 1 0 0 . Я попро бую отг адать его . Нажмите клавишу у , е сли моя дог адка в ерна и кл авишу n в пр о тивном случ а е . В ашим чи слом я вля ется 1 ? n В ашим чи слом я вля ется 2 ? no В ашим чи слом я вля ется 3 ? no sir В ашим чи слом я вля ется 4 ? forqet it В ашим чи слом я вля ется 5 ? у Я знал , ч то у меня получи тся ! Символьный ввод-вывод и верификация ввода 327 Проблема с символом новой строки ре шена . В т о ж е время, к а к борцу за чистоту нравов в программировании, вам вряд ли понравится , что f будет трактоваться так же, как n . Чтоб ы устранить этот дефе кт , вы можете вос пользоваться оператором i f , что­ бы отфильтровать другие ответы. Во-первых, добавьте переме нную типа char для за­ поминания ответа : char r e s pons e ; Затем вне сите измене ния в цикл , чтобы он приобрел следую щий вид: whi l e ( ( r e spon s e = g etchar ( ) ) ! = ' у ' ) / * получи ть о тв е т * / { i f ( r e spons e == ' n ' ) printf ( " B aшим числом явля е тся % d ? \ n " , ++gu e s s ) ; el s e printf ( " K сожалению , я по нимаю только у или n . \ n " ) ; whil e ( getchar ( ) ! = ' \ n ' ) / * пр опусти т ь о с тавшую ся ч а с ть строки * / continue ; Теперь ответ программы имеет следую щий вид: Выберите целое число в пр омежутке от 1 до 1 0 0 . Я попро бую отг адать его . Нажмите клавишу у , е сли моя дог адка в ерна и кл авишу n в пр о тивном случ а е . В ашим чи слом я вля ется 1 ? n В ашим чи слом я вля ется 2 ? no В ашим чи слом я вля ется 3 ? no sir В ашим чи слом я вля ется 4 ? forqet it к сожале нию , я понимаю то л ь ко у или n . В ашим чи слом я вля ется 5 ? у Я знал , ч то у меня получи тся ! Когда вы пишете интерактивные программы, вы должны предусмотреть случаи, когда пользователи могут нарушать инструкции. В таких с итуациях вы должны по­ строить свою программу таким образом, чтобы она помогала пользователям исправ· лять свои ошибки. О на уведомляет их о том, где они допустили ошибку, и предоставляет им дополни­ тельный шанс . Разумеется , вы должны предоставить пользователю четкие инструкции, однако не­ зависимо от их четкости и полноты , обязательно найдется кто·то , кто поймет их не· правильно, а потом вас же обвинит в том , что вы составили непонятные инструкции. смешивание ч и слового и сим вольного ввода Предположим , что ваша программа требует ввода с имвольных данных с помощью функции getchar ( ) и числовых данных с помощью функции s can f ( ) . Каждая из этих функций по отдельности доброс овестно выполняет свою задачу, однако их сочетание 328 Гл ава 8 работает не корре ктно . Это объясняется те м, что функция get char ( ) читает каждый символ, в том числе пробелы , символы табуляции и новой строки, в то время как s can f ( ) при считывании чис ел пропускает пробелы , символы табуляции и новой строки. Чтобы продемонстрировать пробле мы, которые при этом возникают , в листинге 8.5 представлена программа , которая считывает символ и два числа в качестве ввода. Затем она печатает с имвол, ис пользуя при этом номер строки и столбца , указанные во входных данных. Листинг 8.5. Прорамма showchar l . с / * showchar l . c -- программа с большой про блемой , связ анной с вводом-вьmодом * / #incl ude <s tdio . h > void di spl ay ( char cr , i nt l i n e s , i nt width ) ; int main ( vo i d ) { / * символ , выво димый на печать * / int ch ; / * колич е ство с трок и стол бцов * / int rows , col s ; printf ( " Bв eди т e симв ол и дв а це лых числа : \ n " ) ; whi l e ( ( ch = g etchar ( ) ) ! = ' \ n ' ) { s can f ( " l d l d" , &r ows , & col s ) ; di spl ay ( ch , rows , col s ) ; print f ( " В в е дите еще один символ и дв а целых числа ; \ n " ) ; print f ( " в в е дите символ новой строки для з ав ершения прогр аммы . \ n " ) ; printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; void di spl ay ( char cr , i nt l i n e s , i nt width ) int row , col ; for ( row = 1 ; row <= line s ; row++ ) for ( col = 1 ; col <= width ; col++ ) putchar ( cr ) ; / * з акончить с троку и нач а ть новую * / putchar ( ' \ n ' ) ; Обратите внимание на то , что программа читает символ как тип int, чтобы обес· печить прове рку на EOF . В то же вре мя она пе редает символ как тип char в функцию di spl ay ( ) . Поскольку тип char меньше , чем тип int, некоторые компиляторы выдают предупре ждаю щие с ооб ще ния о возможных ошиб ках при преобразовании типов. В данном случае , вы можете игнорировать это предупрежде ние . Программа написана таким образом, что функция mai n ( ) получает данные , а функция di spl ay ( ) выполня· ет печать. Расс мотрим ре зультат выполне ния программы, что позволит определить, в чем суть проблемы : Символьный ввод-вывод и верификация ввода 329 Вв едите символ и дв а целых числ а : с 2 3 ссс ссс Вв едите еще один символ и дв а целых числа ; вв едите символ новой строки для з ав ершения прогр аммы . Пр огр амма з ав ершена . С начала программа раб отает хорошо. Вы вводите с 2 3 , и она печатает два ряда по три символа с, как и ожидалось. Затем программа обращается к вам с предложе ни­ ем ввести следую щий набор данных и заве ршает работу, прежде чем вы сможете отве­ тить ! Что случилос ь? И опять все дело в символе новой строки, на этот раз он следует не посредственно за цифрой 3 в первой строке ввода . Функция s ca n f ( ) оставляет его во входной очереди. В отличие от s can f ( ) , функция getch ar ( ) не пропускает симво· лов новой строки, поэтому такой символ читается функцией g etchar ( ) на следую щей итерации цикла, пре жде че м вы получите возможность ввести что·либо еще. Это зна­ че ние присваивается переме нной ch, а е сли ch получает символ новой строки в каче· стве своего значения, удовлетворяется условие выхода из цикла. Чтобы устранить эту проблему, программа должна пропус кать любые символы но­ вой строки или пробелы между последним числом, набранным в одном цикле ввода, и символом, идущим первым в следую щей строке. Кроме того , было бы неплохо , если выполнение программы можно было бы прекратить на стадии выполне ния функции s can f ( ) в дополнение к прове рке функции get char ( ) . Все это ре ализовано в следую· ще й версии программы , текст которой приведен в листинге 8 . 6 . Листинг 8.6. Программа showchar2 . с / * s h owchar 2 . c печ а т а е т символы в стр оках и стол бцах * / #incl ude <s tdio . h > void di spl ay ( char cr , i nt l i n e s , i nt width ) ; int main ( vo i d ) { / * символ , выво димый на печать * / int ch ; / * колич е ство с трок и стол бцов * / int rows , col s ; -- printf ( " Bв eди т e симв ол и дв а це лых числа : \ n " ) ; whi l e ( ( ch = g etchar ( ) ) ! = ' \ n ' ) { i f ( s can f ( " % d % d " , &rows , & co l s ) br e a k ; != 2) di spl ay ( ch , rows , col s ) ; whil e ( getchar ( ) continue ; ! = ' \n ' ) print f ( " В в е дите еще один символ и дв а це лых числа ; \ n " ) ; print f ( " в в е дите символ новой строки для з ав ершения прогр аммы . \ n " ) ; printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; 330 Гл ава 8 void di spl ay ( char cr , i nt l i n e s , i nt width ) { int row , col ; for ( row = 1 ; row <= lines ; row++ ) for ( col = 1 ; col <= wi dth ; col++ ) putchar ( cr ) ; putchar ( ' \ n ' ) ; / * з акончить строку и нач а ть новую * / О пе ратор whil e заставляет программу пропускать все символы, следую щие за вво­ дом s can f ( ) , включая с имвол новой строки. Это подготавливает цикл для чте ния пер­ вого символа в начале следую ще й строки. Это означает, что вы можете вводить дан­ ные без каких-либо ограниче ний: Вв едите символ и дв а целых числ а : с 1 2 се Вв едите еще один символ и дв а целых числа ; вв едите символ новой строки для з ав ершения прогр аммы . ! 3 6 !! !!!! !! !!!! !! !!!! Вв едите еще один символ и дв а целых числа ; вв едите символ новой строки з ав ершения прогр аммы . Пр огр амма з ав ершена . Используя опе ратор i f с овместно с оператором br e a k , вы заве ршаете выполнение программы , е сли значе ние , возвращаемое функцией s can f ( ) не равно 2 . Это име ет место в тех случаях, когда одно или оба входных значе ния не являются целыми числа· ми или если встретился символ конца файла . Проверка допусти м ости ввода На практике пользователи программ не всегда соблюдают тре бования инструк­ ций , и вполне возмо жно расхожде ние между тем, что про грамма ожидает, и тем, что фактиче с ки получает. В этих условиях могут возникнуть проблемы с выполне ние м программы . В то же вре мя , о сознавая возможность во зникнове ния ошибок, вы мо­ жете вклю чить в с во ю программу дополнительный код, помогающий избе жать этих ошибок. Предположим , например , что вы разрабатываете программу, которая приглашает пользователя ввести не отрицательное целое число . Другим видом ошиб ки является ввод значений, недопустимых для конкретной задачи, которую решает программа . Предположим , например , что у вас име ется цикл, выполняющий обработку неот­ рицательных чисел. Одним из видов ошибок, которые может совершить пользователь в этом случае , является ввод отрицательного числа . Вы можете воспользоваться ус­ ловным выражением и прове сти с оответствую щую проверку: Символьный ввод-вывод и верификация ввода int n ; s c an f ( " % d" , &n ) ; whi l e ( n >= 0 ) { 1 1 о бработать n s can f ( " % d" , & n ) ; 331 1 1 получит ь перв о е знач ение 11 о бнаружить значение , выходящее за пр еделы диапазона 11 получит ь следующе е знач е ние Еще одна потенциальная ошибка состоит в том , что при вводе пользователь может выб рать неправильный тип входного значения, например , ввести символ q. Один из способов обнаружения такого вида ошибок предус матривает проверку значе ний, воз­ вращаемых функцией s can f ( ) . Эта функция , как вы знаете , возвращает количе ство элементов, которые были ус пешно прочитаны, поэтому выражение s c an f ( " % d" , &n ) == 1 принимает истинное значение , только когда пользователь вводит целое число . Это тре бует внесе ния в код следую щего изменения: int n ; whi l e ( s can f ( " % d" , & n ) == 1 & & n >= О ) 1 1 о бр або тк а n Другими словами, условие цикла whil e звучит так: " если вводится целое значение и это целое значение больше нуля" . Последняя версия программы прерывает ввод , ко­ гда пользователь вводит значе ние не правильного типа . В то же время вы можете сде­ лать программу не сколько б олее дружественной по отношению к пользователю и пре­ доставить ему возможность исправить ошибку при вводе и ввести правильное значе­ ние . В этом случае вы, прежде все го , должны удалить те входные данные , которые стали источником проблем . Если функция s c an f ( ) не смогла прочитать ввод , она ос­ тавляет его во входной очереди. В этом случае тот факт, что ввод представляет с обой поток символов, благоприятствует ус пеху дела , поскольку вы можете вос пользоваться функцией g etchar ( ) для посимвольного чтения входных данных. В ы можете реализо­ вать все эти иде и в рамках функции, подобной представленной ниже : int get_int ( vo i d ) { int i nput ; char ch ; whil e ( s can f ( " % d" , &inp u t ) ! = 1 ) { whi l e ( ( ch = g etchar ( ) ) ! = ' \ n ' ) putchar ( ch ) ; 1 1 осво божд ение о т непр авиль ного символа printf ( " не я вля ется целочисленным . \ nПожалуйста , вв еди т е " ) ; printf ( " цeлoe число , тако е как 2 5 , - 1 7 8 или 3 : " ) ; r etur n input ; Эта функция предпринимает попытку прочитать значение типа int в пе реме нную input . Если ей это не удастся , функция входит в тело внешнего цикла whi l e . 332 Гл ава 8 Затем внуrре нний цикл whi l e выполняет посимвольное чтение неправильного ввода . Обратите внимание , что эта функция предпочитает удалить вс е , что осталос ь в о входной строке. Другими возможными вариантами остаются удале ние следую щего символа или слова. Затем функция приглашает пользователя осуществить еще одну попытку ввода . Внешний цикл продолжает выполняться до тех пор , пока пользователь не завершит ус пешно ввод целого числа и функция s can f ( ) вернет значе ние 1 . После того , как пользователь устранит все препятствия для ввода целых чисел, программа может выполнить просмотр введе нных данных на предмет их допустимо­ сти. Рассмотрим приме р, по условиям которого требуется, чтобы пользователь ввел значения ве рхне го и нижнего пределов, определяю щих диапазон допустимых значе­ ний. В этом случае вы , возможно , захотите , чтобы программа проверяла, чтобы пер­ вое значение не было больше второго (об ычно при задании диапазонов полагают, что первое значение меньше второго ) . Может быть, придется проверить, что обе величи­ ны находятся в приемлемых пределах. Наприме р, поиск в архиве , возможно , не следу­ ет проводить, если значе ния даты, которые принимают соответствую щие перемен­ ные , меньше 1 958 или больше 2005. Эту прове рку также должна выполнять с пециаль­ ная функция . Ниже предлагается одна из возможностей, рассматриваемая далее функция пред­ полагает, что в программу был вклю чен заголовочный файл s tc!Ьool . h. Если в вашей системе не используется тип _B ool, вы можете вос пользоваться типом i nt вместо bool , 1 вместо tru e и О вместо fal s e . О братите внимание , что эта функция возвраща­ ет значение true, если ввод был выполне н неправильно , откуда , впрочем, и следует ее название b ad_ limi ts ( ) : bool b ad_limi t s ( int b egin , int end , i nt low , int high ) { bool not_go od f al s e ; i f (b egin > end) { printf ( " l d не мень ш е ч ем l d . \ n " , begi n , end) ; no t_good tru e ; = i f (b egin < low 1 1 end < low ) { pr int f ( n Знач ения ДО ЛЖНЫ быть р авными l d или боль ш е . \ n n no t_good tru e ; 1 low) ; = i f (b egin > high 1 1 end > hi gh ) { pr int f ( " Знач ения до лжны быть р авными % d или мень ш е . \ n " , high ) ; no t_good tru e ; = r e turn not_go o d ; Э т и д в е функции используются в программе, показанной в листинге 8 . 7 , для того, чтобы снабжать целыми числами арифметичес кую функцию , которая вычисляет сум­ му квадратов всех целых чисел из заданного диапазона. Программа ограничивает верхние и нижние границы диапазона , с о ответственно , значениями 1 000 и - 1 0 0 0 . Символьный ввод-вывод и верификация ввода 333 листинг 8.7. Программа checkinq . с / * c h e c king . c - - проверка до пустимо с ти в вода * / #incl ude <s tdio . h > #incl ude <s tc!Ьool . h> 1 1 пр о в ерка , явля е тся ли вводимо е знач ение целым чи слом int g e t_int ( void) ; 1 1 пр о в ерка , являются ли границы диапазона допустимыми bool b ad_limits ( i nt begin , int end , int low , i nt hig h ) ; 1 1 вычисление суммы кв адратов целых чисел // О Т а Д О Ь douЫ e s um_ s quar e s ( int а , int Ь ) ; int main ( vo i d ) { co n s t int MIN -10 0 0 ; 1 1 нижняя гр аница диапазона co n s t int МАХ +100 0 ; 11 в ерхняя гр аница ди апазона int s t ar t ; 11 н ач ало диапазона int s top ; 11 конец диапазона do uЫ e answe r ; printf ( " Э тa пр ограмма вычисля е т сумму кв адр атов " " ц елых чисел в з аданном диапазоне . \ nНижняя гр аница не должна " быть меньше - 1 0 0 0 , \ n a в ерхняя - " " больше + 1 0 0 0 . \ nВ в е дите з н ач ения гр аниц ди апазона ( в в е дите О для " " о беих г р аниц для з ав ерше ния пр ограммы) : \ nнижняя гр аница : " ) ; s t art = get_int ( ) ; printf ( " в epxня я гр аница : " ) ; s top = g e t_int ( ) ; whi l e ( s tart ! = О 1 1 s top ! = 0 ) i f ( b ad_limits ( s t art , s top , MI N , МАХ ) ) рrintf ( " Пожалуйста , пов торите попытку . \ n " ) ; else { answe r = s um_s quar e s ( s tar t , s top ) ; printf ( " Cyммa кв адр атов ц елых чисел " ) ; printf ( " o т l d до l d р авна l g \ n " , s t ar t , s top , answer ) ; print f ( " Вв е дите знач ения границ ди апазона ( в в едите " о беих гр аниц для завершения прогр аммы) : \ n " ) ; print f ( " нижняя г р аница : " ) ; s tart = get_int ( ) ; print f ( " в ep xняя гр аниц а : " ) ; s top = get_int ( ) ; printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; int g e t_int ( void) { int input ; char ch ; О для " 334 Гл ава 8 wh i l e ( s can f ( " % d " , & i nput ) != 1) != ' \n ' ) { whi l e ( ( ch = g e t c h a r ( ) ) p u t char ( ch ) ; / / удалени е н е пр а вил ь н ых вхо дных д анных p r i nt f ( " н е я в л я е тся ц елочи с л е нным . \ nПожалуй с т а , p r i n t f ( " цe л o e число , т акое к ак 2 5 , - 1 7 8 или 3 : в в едите ") ; ") ; r e turn i nput ; douЫ e s um_ s q u ar e s ( i nt а , int Ь ) { О; do uЫ e total = int i ; fo r ( i = а ; i <= Ь ; i + + ) total += i * i; r e turn total ; bool b ad_l i mi t s ( i nt b e g i n , { i n t end , int low , i nt hi g h ) b o o l not_good = f al s e ; if ( b egi n { > end) p r i nt f ( " % d не ме н ь ш е % d . \ n " , not_good if begin , end) ; true ; ( b egi n < l o w 1 1 e nd < l ow ) { р r i nt f ( " Зн а ч е ния должн ы быть р а в ными %d или б о л ь ш е . \ n " , l ow ) ; not_good = true ; if ( b egi n { > high 1 1 end > high ) р r i nt f ( " Зн а ч е ния должн ы быть р а в ными %d или м е н ь ш е . \ n " , high ) ; not_good = true ; r e turn n o t_go o d ; Н и ж е показан пример выполнен ия этой программы: Эта про г р амма в ычисл я е т с умму к в адр атов це л ых ч и с е л в з аданном ди апазон е . Нижняя г р аница н е должна быть м е н ь ш е а в ерхня я - бо л ь ш е -1000, +1000 . В в едите з н ач е ния г р а ниц диап а з о н а ( в в едите О для о беих г р ани ц для з а в ершения про г р аммы ) : нижняя г р аница : 1ow l o w не я вл я е т с я целочисл е нным . Пожалуй с т а , в в едите целое чи сло , в е рхняя г р аниц а : тако е как 2 5 , - 1 7 8 или 3 : 3 а biq nuпЬer а Ь i g numЬ e r н е я в л я е тся целочи сле нным . Пожалуй с т а , в в едите целое чи сло , тако е как 2 5 , - 1 7 8 или 3 : 12 Символьный ввод-вывод и верификация ввода 335 Сумма кв адратов целых чисел о т 3 д о 1 2 равна 6 4 5 Вв едите знач ения гр аниц диапазона ( в в едите О для о беих гр аниц для з а в ершения про г р аммы ) : нижняя г р аница : 8 0 в ерхняя гр аниц а : 1 0 8 0 не меньше 1 0 . Пожалуй с т а , по в тори т е попытку . Вв едите знач ения гр аниц диапазона ( в в едите О для о беих гр аниц для з а в ершения про г р аммы ) : нижняя г р аница : О в ерхняя гр аниц а : О Пр ограмма з ав ершена . Анал из п рогра м м ы В ычислительное ядро (функция s um_ s quar e s ( ) ) программы che ckiпg . с занимает не много места , в то же время поддержка прове рки допустимости ввода ис пользует его более интенсивно , чем в примерах, приведенных выш е . Рас смотрим некоторые его элементы , обратив внимание в первую очередь на об щую структуру программы . Мы придерживалис ь модульного подхода , используя отдельные функции (модули) для проверки допустимости ввода и для управле ния отображением данных. Чем боль· m e программа , тем важнее ис пользование модульного программирования. Функция maiп ( ) управляет потоко м , рас пределяя задачи на выполне ние другими функциями. О на ис пользует фукнцию get_int ( ) , чтобы получать значения , цикл whil e для их обработки, функцию b adlimi ts ( ) для проверки допустимости входных данных и функцию s um_ s qua r e s ( ) , чтоб ы производит фактичес кие вычисле ния : s tart = get_int ( ) ; print f ( " в ep xняя гр аниц а : " ) ; s top = get_int ( ) ; whil e ( s tar t ! = О 1 1 s top ! = 0 ) { i f (b ad_limits ( s tart , s top , MIN , МАХ ) ) рrint f ( " Пожалуй с т а , по в тори т е попытку . \ n " ) ; el s e { answe r = s um_s quar e s ( s tart , s top ) ; print f ( " Сумма кв адратов целых чисел " ) ; print f ( " o т % d до % d равна % g \ n " , s tart , s top , answe r ) ; printf ( " Bв eди т e знач ения гр аниц диапа зона ( в в еди т е О для " " о беих г р аниц для з ав ерше ния пр ограммы) : \ n " ) ; рrintf ( " нижняя гр аница : " ) ; s t art = get_int ( ) ; printf ( " в epxня я граница : " ) ; s t op = g e t_int ( ) ; 336 Гл ава 8 Поток ввода и ч и сла При написании программного кода, который выполняет обработку неправильного ввода , такого как в листинге 8 . 7 , вы должны иметь четкое представление от том, как осуществляется ввод в языке С. Рассмотрим следую щий ввод : is 2 8 12 . 4 М ы воспринимаем этот ввод как строку символов, за которой идет целое число , а за ним следует значение с плавающей запятой. В то же вре мя программа на С вос при­ нимает эту последовательность как поток байтов. Первый байт есть символьный код буквы i , второй байт - символьный код буквы s , третий байт - символьный код про­ бела , четвертый байт - символьный код цифры 2 и так далее. Следовательно, если функция get _int ( ) столкнется с этой строкой, представленный ниже код читает и отвергает всю строку, вклю чая числа , которые в строке являются такими же символа­ ми, как и остальны е : whi l e ( ( ch = g etchar ( ) ) putchar ( ch ) ; ! = ' \n ' ) 1 1 удал ение н епр ави л ь ног о ввода И хотя входной поток с о стоит из символов, функция s can f ( ) может преобразовать их в числовые значения, если вы попрос ите ее сделать это . Наприме р, рассмотрим следую щий ввод: 42 Если вы используете функцию s can f ( ) со спецификатором % с, она читает только символ 4 и запоминает его в переменной типа ch ar . Если вы указываете спе цификатор % s , она читает два символа , символ 4 и символ 2, и сохраняет их в строке символов . Если в ы используете спе цификатор % d , функция s can f ( ) читает эти ж е два символа , однако затем далее она посчитает, что целочисленное значение , с о ответствую щее этим символам, есть 4 х 1 0 + 2 , или 42. Затем она запоминает целочисленное двоичное представление этого значе ния в переменной типа int . Если вы задаете спе цификатор % f, функция s can f ( ) читает два символа , выполняет вычисле ния , показывающие, что они соответствуют числовому значе нию 42, выражает это значение во внутреннем представлении в виде значения с плаваю щей запятой и сохраняет этот результат в пе­ ременной fl o at. Короче говоря , ввод состоит из символов, в то же время функция s can f ( ) может преобразовать входные данные в целое значе ние или значение с плавающей запятой. Прибегая к помощи таких спе цификаторов, как % d или % f, мы можем ограничить ко­ личе ство приемлемых типов при вводе , в то же вре мя функции getch ar ( ) и s ca n f ( ) , использую щие спе цификатор % с, могут принимать любые с имволы. Просм отр м ен ю Многие компьюте рные программы используют меню как часть пользовательского инте рфе йса. Меню делают программу более удобной, в то же вре мя они ставят опре­ деле нные проблемы перед программистом. Посмотрим, в чем они с о стоят. Меню предлагают пользователю ответы на выб ор . Ниже показан гипотетичес кий пример: Символьный ввод-вывод и верификация ввода 337 Вв едите любую букву на в аш выбор : с . сов е т з . з вонок п . подсч е т в . в ыход В идеальном случае пользователь вводит одну из представленных в примере букв, и программа функционирует в соответствии с этим выбором . Будучи программистом , вы хотите , чтобы этот проце с с протекал как можно боле е гладко . Первая цель состоит в том, чтобы программа работала гладко , когда пользователь придерживается инструк­ ций по ее эксплуатации . Вторая цель предполагает устойчивую раб оту программы и в тех случаях, когда пользователь нарушает эти инструкции. Как нетрудно догадаться, второй цели достичь гораздо труднее, чем первой, поскольку трудно предусмотреть все возможные наруше ния , с которыми может столкнуться программа на протяжении все го срока служб ы. зада чи Будем конкретными и рассмотрим задачи, которые должна решать программа с ре ализованным меню . Прежде все го , необходим ответ пользователя , получив кото­ рый, программа выбе рет совокупность действий, с оответствую щих этому ответу. На­ ряду с этим, программа должна обес печить способ возврата меню в состояние , при­ годное для последую щего выб ора вариантов. О ператор выбора языка С представляет собой естественный механизм выбора де йствий, пос кольку каждый выбор пользова­ теля долже н соответствовать конкретной метке c a s e . Вы можете вос пользоваться оператором whi l e , чтобы обес печить многократный доступ. С помощью псе вдокода можно описать этот процесс следую щим образом: сделать выбор пока не выбр а н а букв а ' в ' переключить ся на нужный в ари ант и выполнить е г о сделать сле дующий выбор Обеспечен ие устой ч и вого вы пол не н ия програ мм Задача устойчивого ( б е з сбоев) выполнения программы (корре ктное функциони­ рование программы при обработке б езошибочного ввода и при обработке входных данных, содержащих ошибки) выходит на передний план, когда вы принимаете реше­ ние , каким образом реализовать представленный выше псевдокод . Наприме р, одно из де йствий, которое вы можете предпринять - это ис клю чить на стадии "сделать вы­ бор" не правильные отклики с тем, чтобы оператору s wi tch передавались только пра­ вильные отклики. Это предполагает представление процесса ввода в виде функции, которая может возвращать только правильные отклики. Комбинация этого с циклом whil e и оператором swi tch порождает следую щую структуру программы: #i nclude < s tdi o . h> char get_choi c e ( void ) ; void count ( vo i d ) ; int main ( void) { int choi ce ; 338 Гл ава 8 whi l e ( ( choi c e = g e t_choi ce ( ) ) ! = ' в ' ) { switch ( cho i c e ) { рrintf ( " Покупайте дешево , продавайте дорого . \ n " ) ; case ' с ' br e a k ,· put char ( ' \ a ' ) ; / * AN S I * / case ' з ' br e a k ; case ' п ' count ( ) ; br e a k ; de fault : рrintf ( " Прогр аммная ошибка ! \ n " ) ; br e a k ; r etur n О ; Функция get_ choi ce ( ) определе на таким образо м , что она может возвращать только значе ния ' с ' , ' з ' , ' п ' и ' в ' . Вы ис пользуете ее примерно так же, как и функ· цию g etchar ( ) - она получает конкретное значение и сравнивает его с символом за· верше ния программы (в рассматриваемом случае это ' в ' ) . Мы сохранили выбор из меню на достаточно простом уроне с тем, чтобы уделить основное внимание структу­ ре программы ; дале е мы рассмотрим функцию count ( ) . Вариант de f aul t удобно ис­ пользовать во время отладки. Если функция g e t_cho i c e ( ) не с может ограничить свои возвращае мые значе ния до заранее заданных, вариант de fault дает вам понять, что происходит не что , вызывающее опасения . Фун кция get_choice ( ) В рассматриваемом случае одна из возможных структур этой функции имеет в псе вдокоде следую щий вид: по казать во зможные в ариан ты по лучить о тклик пока о тклик н е приемл ем приг л ашение на ввод дальнейших о ткликов получить отклик Ниже показана простая , и в то же вре мя неудобная ре ализация : char get choi c e ( vo i d ) { int ch ,· print f ( " B в e дитe букву выбр анного в ариан т а : \ n " ) ; з . звонок\ n " ) ; print f ( " c . сов е т print f ( " п . подсч е т в . выхо д\ n " ) ; ch = getchar ( ) ; whil e ( ch ! = ' с ' & & ch ! = ' з ' & & ch ! = ' п ' & & ch ! = ' в ' ) { printf ( " Bыбepи тe с , з , п или в . \ n " ) ; ch = get char ( ) ; r etur n ch ; Символьный ввод-вывод и верификация ввода 339 Проблема заклю чается в том, что в условиях буферизованного ввода каждый сим­ вол новой строки, порожденный нажатием <Enteг>, рассматривается как ошиб очный отклик. Чтобы сделать программный интерфейс устойчивым, данная функция должна пропускать символы новой строки. Суще ствует не сколько способов сделать это . Один из них предусматривает замену функции g etchar ( ) новой функцией с именем get_f i r s t ( ) , которая с читывает пер­ вый символ строки и игнорирует все остальны е . Преимущество такого метода состоит в том, что он трактует введенную строку, например , ела, как просто символ с, но не рассматривает ее как правильный вариант с, за которым следует е ще один правиль­ ный вариант в виде буквы л, означающей подсч е т. С учетом вс его этого , функцию вво­ да можно перепис ать в следую щем виде : char get choi c e ( vo i d ) { int ch ; print f ( " В в е дите букву выбр анного в ариант а : \ n " ) ; з . звонок\ n " ) ; print f ( " c . сов е т print f ( " n . подсч е т в . выхо д\ n " ) ; ch = get fi r s t ( ) ; whil e ( ch ! = ' с ' & & ch ! = ' з ' & & ch ! = ' л ' & & ch ! = ' в ' ) { printf ( " Bыбepи тe с , з , л или в . \ n " ) ; ch = get fir s t ( ) ; r etur n ch ; char get fir s t ( void) int ch ; ch = getchar ( ) ; whil e ( getchar ( ) continue ; r etur n ch ; / * считыв ание следующего символа * / ! = ' \n ' ) / * пропустить о с тал ь н ую ч а с ть строки * / смешивание сим вольного и ч и слового ввода С оздание меню является еще одной иллюстрацией того , какие проблемы порожда­ ет сме шивание ввода символов и чис ел. Предположим, например, что функция count ( ) (выбор л) имеет следую щий вид : v o i d count ( vo i d ) { int n , i ; рrint f ( " Считать до какого пр едел а ? Вв еди т е целое число : \ n " ) ; s can f ( " % d" , & n ) ; for ( i = 1 ; i <= n ; i++ ) printf ( " % d\ n " , i ) ; 340 Гл ава 8 Если в ответ вы введете 3 , функция s can f ( ) прочтет 3 и оставит символ новой строки в каче стве следую ще го символа во входной оче реди. Результат следую щего вы­ зова get _ cho i c e ( ) будет состоять в том, чго функция get _ fir st ( ) вернет этот символ новой строки, что приведет к нежелательному поведе нию . Один из спос обов уладить эту проблему заклю чается в том, чтобы перепис ать функцию get _ fi r s t ( ) таким обра­ зом, чтобы она возвращала следую щий не проб ельный символ, а не любой следую щий встреченный е ю символ. Мы оставим эту задачу в качестве упражне ния для самостоя­ тельного выполнения . Второй подход заставить с аму функцию count ( ) следить за по­ рядком и удалять символы новой строки. Именно этот подход применяется в следую­ ще м примере: void count ( vo i d ) { int n , i ; рrint f ( " Считать до какого пр едел а ? Вв еди т е целое число : \ n " ) ; n = g e t_int ( ) ; for ( i = 1 ; i <= n ; i++ ) printf ( " % d\ n " , i ) ; whil e ( get char ( ) ! = ' \ n ' ) continue ; Эта функция ис пользует также функцию get_int ( ) из листинга 8 . 7 ; вс помните , чго она выполняет проверку допустимости ввода и предоставляет пользователю возмож­ ность повторного ввода данных. В листинге 8.8 показан окончательный вариант про­ граммы, в которой ис пользуется меню . Листинг 8.8. Программа пenuette . с / * menuette . c - - технология меню * / #incl ude <s tdio . h > char get_cho i c e ( void) ; char get_fi r s t ( vo i d ) ; int g e t_int ( void) ; void count ( void) ; int main ( vo i d ) { int choi ce ; void count ( vo i d ) ; whi l e ( ( choi c e = g e t_choi ce ( ) ) ! = ' в ' ) { switch ( cho i c e ) { рrintf ( " Покупайте дешево , продавайте дорого . \ n " ) ; case ' с ' br e a k ; put char ( ' \ a ' ) ; / * AN S I * / case ' з ' br e a k ; case ' п ' count ( ) ; br e a k ; de fault : рrintf ( " Прогр аммная ошибка ! \ n " ) ; br e a k ; Символьный ввод-вывод и верификация ввода printf ( " В с е г о хорош е г о . \ n " ) ; r e turn О ; void count ( void) { int n , i ; рrintf ( " Считать до како г о пр еде л а ? Вв едите целое число : \ n " ) ; n = get_int ( ) ; fo r ( i = 1 ; i <= n ; i++ ) print f ( " % d\ n " , i ) ; whi l e ( getchar ( ) ! = ' \ n ' ) conti nue ; char get_choi c e ( void) int ch ; printf ( " Bв eди т e букв у выбранно г о в ари анта : \ n " ) ; printf ( " с . сов е т з . з вонок\ n " ) ; p r i n t f ( " п . подсч е т в . в ыход\ n " ) ; ch = get fir s t ( ) ; whi l e ( ch ! = ' с ' & & ch ! = ' з ' & & ch ! = ' п ' & & c h ! = ' в ' ) { print f ( "Bыбepитe с , з , п или в . \ n " ) ; ch = get_fi r s t ( ) ; r e turn c h ; char g et_fi r s t ( vo i d ) { int ch ; ch = get char ( ) ; whi l e ( g etchar ( ) conti nue ; r e turn c h ; ! = ' \n ' ) int g et_int ( void) { int input ; char ch ; whi l e ( s canf ( " % d " , & input ) ! = 1 ) { whil e ( ( ch = getchar ( ) ) ! = ' \ n ' ) putchar ( ch ) ; / / удали т ь непр авил ь ный вывод print f ( " не явля е тся ц елочис ленным . \ nПожалуй с т а , вв едите " ) ; print f ( "цeлoe чи сло , т акое к ак 2 5 , - 1 7 8 или 3 : " ) ; r e turn i nput ; 341 342 Гл ава 8 В от как выглядит приме р выполнения этой программы : Вв едите букву выбр анного в ариан та : с . сов е т з . зво нок п . подсч е т в . выход с Покупай т е деше во , пр одавайте дорог о . Вв едите букву выбр анного в ариан та : с . сов е т з . зво нок п . подсч е т в . выход под счет Считать до какого пр едела ? Вв едите целое чи сло : ;iuзa дв а не я вля ется целочисле нным . Пожалуй с т а , вв едите целое число , тако е как 2 5 , - 1 7 8 или 3 : 5 1 2 3 4 5 Вв едите букву выбр анного в ариан та : с . сов е т з . зво нок п . подсч е т в . выход а Выберите с , з , п или в . в В с е г о хорошег о . Иногда довольно-таки трудно добиться того, чтобы интерфейс , ис пользую щий ме­ ню , работал без с боев, то есть настолько гладко , нас колько это возможно , однако по­ сле разработки жизнеспособного подхода вы сможете приме нять е го во множестве ситуаций. Следует также обратить внимание на то , как каждая функция , столкнувшись с не обходимостью выполнить слегка усложненную задачу, передает эту задачу другой функции, тем самым повышая уровень модульности программы . К л юч евые п онятия Программы н а языке С рас сматривают входные данные как поток байтов. Функция getch ar ( ) интерпретирует каждый байт как символьный код. Функция s can f ( ) вос­ принимает ввод аналогично , в то же время , с помощью спе цификаторов преобразова­ ния она может пе ревести символьный ввод в числовое значе ние . Многие операцион­ ные с истемы предлагают механизм пе ренаправления, которые позволяет вам сме нить клавиатуру на файл при вводе и направлять выходные данные в файл. Часто программы ожидают входные данные , представленные в спе циальной фор­ ме. В ы можете существе нно повыс ить надежность программы и сделать ее боле е дру­ же ственной по отноше нию к пользователям, выявляя ошибки, которые может допус­ тить пользователь при вводе, и снаб жая программу средствами, способными с пра­ виться с этими ошибками. В случае небольшой программы проверка допустимости ввода может оказаться наиболее интенсивно используемой частью программы. О на предлагает несколько ва- Символьный ввод-вывод и верификация ввода 3 43 риангов. Наприме р, если пользователь вводит неправильную информацию , вы може­ те завершить программу, предоставить пользователю фиксированное количе ство по­ пыток для ис правления входных данных либ о предложить неограниченное число та­ ких попыток. Р езю м е Многие программы используют функцию ge tchar ( ) для пос имвольного ввода входных данных. Обычно с истемы используют постро'Чnо буферизоваппъtй ввод в том смысле , что входные данные передаются в программу всякий раз , когда нажимается клавиша <Ente r>. Нажатие клавиши <Ente r> ге не рирует с имвол новой строки, и этому явлению не обходимо уделять внимание при разработке программ. Стандарт ANSI С тре бует применения буферизованного ввода . Язык С располагает се мейством функций, получившим название стандартного па­ кета ввода-вывода , который позволяет применять унифицированный подход при ра­ боте с различными формами файлов в различных систе мах. Функции getchar ( ) и s can f ( ) принадлежат этому с емейству. Обе функции возвращают значение EOF (опре­ деле ние этого знака с одержится в заголовочном файле s t dio . h) , когда обнаруживают конец файла . С истемы Unix обес пе чивают возможность моделировать условие конца файла с клавиатуры , для этого нужно нажать <C trl+D > в начале каждой строки; с исте­ мы DOS ис пользуют для этой цели клавиши <Ctrl+Z>. Во многих опе рационных систе мах, в том числе в Unix и D O S , реализован меха­ низм перепаправлепия, который позволяет использовать для входных и выходных дан­ ных файлы вместо клавиатуры и экрана . Программы , которые читают ввод до тех пор, пока не встретится EOF , могут использоваться как при вводе данных с клавиатуры с эмулированными сигналами конца файла , так и при перенаправле нии ввода в файлы . Использование вызовов функции s can f ( ) при обращениях к функции getch ar ( ) порождает проблемы в тех случаях, когда функция s can f ( ) оставляет с имволы новой строки во входных данных, перед тем как вызывать функцию ge tchar ( ) . Тем не ме­ не е , с о знавая важность таких пробле м, вы можете разрабатывать свои программы с их учетом. При напис ании с об ственной программы тщательно планируйте пользовательс кий инте рфе йс . Постарайтесь предусмотреть вс е виды ошибок, которые могут совершить пользователи, чтобы с о здавать такие программы, которые могли бы их исправлять. В оп росы для са м оконтроля 1 . Выраже ние p u t c h ar ( ge t ch a r ( ) ) является допустимы м ; что о но оз нача ет? Допустимо ли также и выражение g etchar (putchar ( ) ) ? 2 . Какие действия выполняют следую щие операторы? а. putchar ( ' Н ' ) ; б . putchar ( ' \ 0 0 7 ' ) ; в. putchar ( ' \ n ' ) ; г. putchar ( ' \ Ь ' ) ; 344 Гл ава 8 3 . Предположим, что имеется ис полняемая программа с именем count, которая подсчитывает количество с имволов во входных данных. Придумайте команду для с реды командной строки, которая ис пользует программу count для подсче­ та количе ства символов в e s s ay и для запоминания ре зультата в файле с именем e s s ayct. 4. Пусть заданы программа и файлы , описанные в пункте 3, какие из приведе нных ниже команд являются допустимыми? а. e s s ayct <e s s ay б . count e s s ay в. e s s ay >count 5 . Что такое EOF? 6 . Какими являются выходные данные каждого из следую щих ниже фрагментов в условиях указанных ниже входных данных (предполагается , что переме нная ch имеет тип int и ввод буфе ризованный) ? а. Задан следую щий ввод: If you quit , I wi11 . [ enter] Фрагмент программы имеет вид : whil e ( ( ch = get char ( ) ) ! = ' i ' ) p utchar ( ch ) ; б . Задан следую щий ввод: Barhar [enter] Фрагмент программы имеет вид : whil e ( ( ch = get char ( ) ) ! = ' \ n ' ) { p utchar ( ch++ ) ; p utchar ( + + ch ) ; 7. Какой подход применяет язык С к разным компьютерным с истемам с разными соглаше ниями относительно файлов и символов новой строки? 8. С какой поте нциальной проблемой вы столкнетес ь при смешивании символь­ ного ввода и ввода чис ел в систе мах с буферизованным вводом? Уп ражн ен и я по програ м м ирован и ю Некоторые из описанных ниже программ требуют, чтобы ввод прекращался в ре­ зультате появления символа EOF . Если в ис пользуемой вами опе рационной системе процедура перенаправления неудобна или вообще невозможна , вос пользуйте сь какой­ то другой прове ркой для пре краще ния ввода , такой как, например, с читывание сим­ вола & . 1 . Напишите программу, которая подсчитывает количество символов при их вво­ де до достиже ния конца файла . 2 . Напишите программу, которая воспринимает входные данные как поток сим­ волов и читает их до тех пор, пока не встретит символ EOF. Заставьте программу Символьный ввод-вывод и верификация ввода 345 распечатывать каждый входной символ и е го десятичное значение . Обратите внимание на то , что в последовательности ASCII символу пробела предшеству­ ют непе чатае мые символы . Примените к ним с пециальную обработку. Если не· печатаемым символом является символ новой строки или с имвол табуляции, печатайте , с о ответственно , \ n или \ t. В противном случае, вос пользуйтесь для обозначе ния с имволами управления . Например, ASCII 1 это <Ctгl+A>, кото­ рый может отображаться как лА. О братите внимание , что АSСП-значение для символа А представляет собой значе ние <C tгl+A> плюс 64. Аналогичное отно· ше ние выполняется и для других непе чатае мых символов. Печатайте по 10 пар в строке , но начинайте печать с новой строки всякий раз , когда встречается символ новой строки. - 3. Напишите программу, которая с читывает входные данные как поток символов, пока не встретит символ EOF . Сделайте так, чтоб ы программа отдельно сообща· ла о количестве букв ве рхне го регистра и количе стве букв нижне го ре гистра. Можете предположить, что числовые значения букв нижне го регистра образу· ют не пре рывную последовательность, это же вы можете предположить и в от· ношении букв верхнего ре гистра . Либо можете воспользоваться функциями из библиотеки ctyp e . h, выполняю щими соответствую щую кла с сификацию симво· лов , в этом случае уровень переносимости программы будет увеличен. 4. Напишите программу, которая с читывает входные данные как поток символов, пока не встретит символ EOF . Сделайте так, чтоб ы программа отдельно сообща· ла среднее количество букв на слово . Пробелы не должны трактоваться как бук­ вы слова . Фактически, знаки пре пинания также можно подсчитать, но в данном конкретном случае этого пока делать не следует . (Если вас заинтерес овала эта пробле м а , попробуйте вос польз о ваться функцие й i s p u n c t ( ) из с е мейства ctyp e . h . ) 5 . Вне с ите в программу угадывания чисел, представленную в листинге 8.4, такие изме не ния , которые реализуют б оле е интелле ктуальную страте гию . Наприме р, пусть программа сначала предложит число 50, и спросит, отклонился ли этот вариант от задуманного числа в большую сторону, в ме ньшую сторону или же число угадано. Если, скаже м , предположение меньше задуманного числа , еле· дую щая попытка угадать число производится в диапазоне от 50 до 1 0 0 , то есть 75. Если же предположе ние больше задуманного числа , следую щая попытка производится в се редине диапазона чисел от 75 до 50 и так далее . Используя стратегию бинарного поиска, программа быстро находит правильный ответ, ко­ не чно , если пользователь не вводит ее в заблуждение . 6 . Вне с ите изме не ния в функцию get fi r s t ( ) , представленную в листинге 8 . 8 , с таким рас четом, чтобы она возвращала пе рвый встреченный ею непробельный символ. Проверьте ее в какой-нибудь простой программ е . _ 7. Видоизмените упражнение 8 из главы 7 таким образом , чтобы варианты меню были помечены буквами, а не номерами. 8 . Напишите программу, которая выводит на экран меню , предлагая выб рать ари­ фметичес кую операцию сложе ния , вычитания , умноже ния или деления. Полу· чив выбор, программа приглашает ввести два числа , а зате м выполняет зака· занную арифметичес кую опе рацию . Программа должна принимать только пред- 346 Гл ава 8 ложенный выбор из меню . О на должна использовать тип float для чисел и предоставлять пользователю возможность делать повторные попытки, если с первого раза ему не удается ввести число . В случае вычитания программа долж­ на предложить пользователю ввести новое значение , е сли в каче стве второго операнда вычитания был выб ран О . Выполнение такой программы должно иметь следую щий вид : Выбери те жел аемую опер а цию : в . вычитание с . сложение у . умножение д . дел ение к . выход из прогр аммы с Вв еди т е перв о е чи сло : 2 2 . 4 Вв еди т е в тор о е чи сло : о дин Один не явля е тся числом . Пожалуйста , вв еди т е число , такое как 2 . 5 , - 1 . 7 8 Е 8 или 3 : 1 22 . 4 + 1 23 . 4 Выбери те жел аемую опер а цию : в . вычитание с . сложение д . дел ение у . умножение к . выход из прогр аммы д Вв еди т е перв о е чи сло : 1 8 . 4 Вв еди т е в тор о е чи сло : о Вв еди т е число , отличное о т О : 0 . 2 18 . 4 / о . 2 92 Выбери те жел аемую опер а цию : в . вычитание с . сложение у . умножение д . дел ение к . выход из прогр аммы к В с е г о хорош е г о . = = ГЛА ВА 9 Фу н к ц и и в этой главе: • Кл ю ч ев ы е сл ова: return • Опера ции: * (уна рная), & (ун а рная) • Функции и их оп ред еление • и спользование аргументов и воз в ращаемых з н а ч ен и й • Использование п еременных типа указ ател ь в качеств е а ргум ентов фун кций • ти п ы фун кций • Прототипы A N SI с • Рекурсия к акова организация ваш е й программы? Принципы построе ния программ на языке С рас сматривают функции как строительные блоки. В аши программы широко ис пользуют стандартную б иблиоте ку С для работы с такими функция­ ми, как print f ( ) , s can f ( ) , getchar ( ) , putchar ( ) и s t r l e n ( ) . Теперь вы созрели для более активных де йствий - для создания собстве нных функций. В ранних главах этой книги были заложены основы этого процесса, а в этой главе вся информация будет систе матизирована . Об з ор фун кций Прежде все го , что такое функция? Функ-ция представляет собой самодостаточную единицу программного кода, разраб отанную для решения конкретной задачи. Функ­ ция в языке С играет ту же роль, какую играют функции, подпрограммы и процедуры в других языках программирования , хотя в деталях эти роли могут быть различными. В результате выполне ния не которых функций происходит то или иное событие . На­ пример, в результате выполнения функции printf ( ) на ваше м экране появляются конкретные данны е . Другие функции возвращают значения для их последую щего ис­ пользования в программе. Например, функция s t r l e n ( ) сообщает программе длину заданной строки. В обще м случае функция может одновреме нно выполнять действия и возвращать значения . Почему в ы должны пользоваться функциями? Во-первых, они снимают с вас обреме­ нительную обязанность многократного повторения в программе одних и тех же кодовых последовательностей. Если в программе приходится решать одну и ту же задачу несколь­ ко раз, вам достаточно написать соответствую щую функцию всего лишь один раз. Про­ грамма использует эту функцию там, где необходимо , а вы можете использовать одну и 348 Гл ава 9 ту же функцию в нескольких программах, ведь ранее вы вызывали функцию putchar ( ) в нескольких программах, не так ли? Даже в тех случаях, когда задача в программе pema· ется всего лишь один раз , использование функции целесообразно , поскольку при этом увеличивается уровень модульности, благодаря чему программа становится более по· нятной при чтении, к тому в нее легче вносить изменения и исправления . Предположим , например, что вы хотите написать программу, которая выполняет следую щие действия : • Считывает спис ок чис ел. • Сортирует эти числа. • Вычисляет среднее значение . • Выче рчивает гистограмму. С этой целью вы можете воспользоваться следую щей программой: #i nclude < s tdi o . h> #de fine S I ZE 5 0 int main ( void) { float l i s t [ S I ZE ] ; r e adl i s t ( li s t , S I ZE ) ; sort ( li s t , S I Z E ) ; aver age ( l i s t , S I Z E ) ; b argr aph ( li s t , S I ZE ) ; r etur n О ; Разумеется , вам также придется написать четыре функции r e adl i s t ( ) , s o r t ( ) , aver age ( ) и b argr ap h ( ) , но это уже детали. Описательные имена функций позволяют определить, что делает программа и как она организована . Далее вы можете автоном· но работать с каждой функцией, и если вы придадите функциям более общий харак· тер , то сможете использовать их в других программах. Многие программисты предпочитают думать о функции как о " че рном ящике " , представленном в те рминах информации, которая поступает н а вход этого ящика (ввод) , и значением или действие м, которое он производит (вывод ) . В ас не должно инте рес о вать, что происходит внутри че рного ящика , если, конечно , вы не програм· мист, занимаю щийся разработкой этой функции. Например , когда вы пользуетесь функцией printf ( ) , вы знаете , что е й нужно передать управляю щую строку и, воз· можно , некоторые аргументы. В ы также знаете , какой выход должна ге не рировать функция printf ( ) . Вам также вовсе не нужно знать программный код реализации print f ( ) . Такой подход к использованию функций позволяет сос редоточить вс е уси· лия на создании общей структуры программы и не отвлекаться на отдельные детали. Тщательно продумайте , что должна выполнять функция и какое место она занимает в программе , прежде че м приступать к написанию е е программного кода. Что вы должны знать о функциях? Прежде всего , вы должны знать, как их пра· вильно определять, как их вызывать для последую ще го ис пользования и как наладить их взаимодействие . Чтобы освежить эти моме нты в памяти, начнем с рас смотрения оче нь простого примера, а затем будем добавлять в него все новые возможности, пока не получим полное представление о функциях. Функции 3 49 создан и е и использование п ростой функци и Нашей первой скромной целью является с о здание функции, которая пе чатает 40 зве здочек в строке. Чтобы придать этой функции конкретный смысл, мы вклю чим е е в программу, которая печатает простой заголовок письма . В листинге 9 . 1 программа показана полностью . О на с о стоит из функций main ( ) и s t arbar ( ) . Листинг 9.1 . Программа 1etheadl . c / * l e th e adl . c * / #incl ude <s tdio . h > #de fi ne NАМЕ " GI GATHINK , INC . " #de fi ne ADDRE S S " 1 0 1 Megabuck Pl a z a " #de fi ne PLACE "Me gapoli s , С А 9 4 9 0 4 " #de fi ne WIDTH 4 0 void s tarb ar ( vo i d ) ; / * пр ототип функции * / int main ( vo i d ) { s t arbar ( ) ; printf ( " % s \ n " , NАМЕ ) ; printf ( " % s \ n " , ADDRE S S ) ; printf ( " % s \ n " , PLACE ) ; s t arbar ( ) ; / * исполь з о в ание функции * / r e turn О ; void s tarb ar ( vo i d ) { int count ; / * опр еделение функции * / for ( count = 1 ; count <= W I DTH ; count++ ) putchar ( ' * ' ) ; putchar ( ' \ n ' ) ; В ывод программы выглядит следую щим образом: **************************************** GI GATHINK , INC . 1 0 1 Megabuck P l a z a Megapoli s , С А 9 4 9 0 4 **************************************** Анал из п рогра м м ы Следует отметить несколько важных о с обенностей этой программы : • Она использует иде нтификатор s tarb ar в трех контекстах: в nporrюmune фу'Нк· t,tии, который с ообщает компилятору о том, какого рода функцией является s tarb ar ( ) , в въtзове фу'Нкt,tии, благодаря которому происходит выполнение функ­ ции, и в onpeдeлrnuu фу'Нкt,tии, которое описывает все , что делает функция . 3 50 • Гл ава 9 Подобно переменным, функции имеют типы . Любая программа , которая ис­ пользует функцию , должна объявить тип этой функции, прежде чем она ею вос­ пользуется . В силу этого обстоятельства прототип, отве чаю щий требованиям стандарта ANSI С , предшествует объявлению функции mai n ( ) : void s tarbar ( void) ; Круглые скобки указывают, что s tarb ar является именем функции. Пе рвое ключевое слово void это тип функции; тип void указывает, что данная функ­ ция не возвращает значения . Второе слово void (которое заклю чено в круглые скобки) показывает, что у функции нет аргументов. Точка с запятой означает, что вы объявляете функцию , а не определяете ее . То е сть эта строка извещает о том, что программа ис пользует функцию типа void под именем s t arbar ( ) , и что компилятор должен искать ее определение в другом ме сте программы . В случае компиляторов, которые не распознают прототипов ANSI С , просто объявите тип функции следую щим образом: - void s tarbar ( ) ; Следует отметить, что некоторые оче нь старые компиляторы не рас познают тип void. В этом случае ис пользуйте тип int для функций, которые не возвра­ щают значе ния . • • Программа помещает прототип функции s tarb ar ( ) перед функцией mai n ( ) ; вместо этого она может быть поме ще на внутри функции main ( ) в том месте , в каком находятся объявления любых других переме нных. Годится любой из этих способов. Программа въ�зъtвает функцию (обращается к функции) s tarbar ( ) из функции main ( ) , используя ее имя , за которым следуют круглые скобки и точка с запя­ той, тем с амым, создавая оператор: s tarb ar ( ) ; Это форма вызова функции типа void. Каждый раз , когда компьютер выходит на опе ратор s tarb ar ( ) ; , он ищет функцию s tarbar ( ) и выполняет с одержа­ щиеся в ней команды. Заве ршив выполне ние команд функции s tarbar ( ) , ком­ пьютер возвращается к следую щей строке въtзъ�ваю щей фуuк'Ции, в рассматривае­ мом случае это main ( ) (рис . 9 . 1 ) . • • Для определения функции s tarbar ( ) программа выбирает ту же форму, что и для определения функции main ( ) . О но начинается с типа , име ни и круглых скобок. Далее следует открывающая фигурная с кобка, объявление используе­ мых переменных, определение операторов функции и закрываю щая фигурная скобка (рис . 9 .2 ) . О братите внимание , что за име не м функции s tarbar ( ) не следует точка с запятой. Отсутствие точки с запятой говорит компилятору о том, что в данном случае вы объявляете функцию s tarbar ( ) , но не вызываете ее и не создаете ее прототип. Программа вклю чает функции s t arbar ( ) и main ( ) в один и тот же файл. В то же время вы можете использовать два отдельных файла . В ариант с одним фай­ лом легче компилировать. Вариант с двумя файлами упрощает использование одной и той же функции в различных программах. Если вы храните функцию в Функции 351 отдельном файле , в ы должны поме стить в этот ж е файл не обходимые дире кти­ вы #de fi ne и #include. Вариант с двумя или большим числом файлов мы рас­ смотрим позднее . А пока мы поместим вс е функции в один файл. Закрываю щая скобка функции main ( ) указывает компилятору, где эта функция оканчивается, а следую щий за не й заголовок функции s tarb ar ( ) уведомляет компилятор о том, что s tarbar ( ) является функцией. • Переме нная co unt в функции starbar ( ) является локалъной. Это означает, что она известна только функции s tarb ar ( ) . В ы можете использовать имя count в других функциях, вклю чая main ( ) , и в этом случае конфликта удается избежать. Просто в программе применяются отдельные независимые друг от друга пе ре­ менны е , получившие одно и то же имя . Если представить себе функцию s tarb ar ( ) как че рный ящик, то его де йствие за­ клю чается в том, чтобы печатать строку звездочек. О на вообще не выполняет ввод, поскольку ей не нужна никакая информация от вызывающей функции. main { ) { � starbar ( ) 1 � putchar ( ) 1 printf () Каждая функция может "вь� звать" другие функции � printf О � printf О � starbar ( ) ,. } 1 -- Все функции "выполн яются'' поочередно putchar ( ) Р и с. 9 .1 . Поток управлеиия программъ1 l e the a d l . с (листинг 9. 1 ) 3 52 Гл ава 9 Заголовок #include <stdio . h> ----+=- Инструкции препроцессора #define WIDTH 4 0 void starbar (void) ---+-- Имя функции Тело int count ; -------+- Оператор объявления for ( count=l ; - - - ) Управляющий оператор цикла putchar ( ' * ' ) ; Оператор функции putchar ( ' \n ' ) ; Оператор функции 1 Р и с . 9 . 2 . Структура простой ФУ'Н-К'ЦUU О на не предоставляет (то есть не возвращает) никакой информации в функцию main ( ) , следовательно, функция s tarb ar ( ) не имеет возвращаемого значе ния . Короче говоря , функция s tarbar ( ) не нуждается ни в какой связи с вызывающей функцие й. А теперь с о здадим функцию , для которой подобный обмен данным необходим. Аргументы функци и Показанный выше заголовок письма выглядел намного лучше , если бы текст рас· полагался по центру. Вы можете поместить текст в центре , поме стив требуе мое коли· чество ведущих пробелов пе ред те м, как начать печать текст. Такое поведение анало­ гично функции s tarb ar ( ) , которая печатала заданное число звездочек, но в данный момент вы хотите заданное число пробелов. Вместо того чтобы с о здавать отдельные функции для каждой задачи, мы , соблюдая принципы языка С , напише м одну, более универс альную функцию , которая решает обе эти задачи. Мы назове м эту новую функцию s how_n_char ( ) (имя означает, что конкретный символ отображается n раз ) . Единстве нное измене ние заклю чается в том , что вме сто ис пользования встрое нных значений отображаемого символа и количе ства повторе ний функция s how_n_char ( ) будет использовать аргументы для получе ния соответствую щих значе ний. А теперь займемся б олее конкретными делами. Предположим , что доступное про­ странство имеет ширину в 40 символов. Строка из звездоче к содержит 40 символов, расположенных вплотную друг к другу, а обращение к функции s how_ n_ char ( ' * ' , 4 О ) должна печатать эту строку точно так же, как это делала функция s tarbar ( ) раньш е . Что можно сказать о пробелах, используемых для центрирования строки GI GAT H I NK , INC? Строка GI GAT H I NK , I NC . имеет ширину, содержащую 1 5 пробелов, поэтому в пер­ вой версии программы за заголовком следовало 25. Чтобы поме стить строку в центр, нужно включить в начало строки 12 пробелов , в результате получим 12 пробелов с од· ной стороны фразы и 1 3 пробелов с другой стороны . Следовательно , сначала не обхо· димо выполнить такой вызов функции: s h ow_n_char ( ' ' , 1 2 ) Функции 3 53 Если не учитывать наличие аргумента , то это обраще ние к функции s how_n_ char ( ) во многом похоже на вызов функции s t arbar ( ) . Одно из различий заклю чается в том, что функция s how_n_ char ( ) не добавляет символа новой строки, как это делает функ­ ция s tarbar ( ) , так как, с корее все го , в эту строку потребуется добавить текст. В лис­ тинге 9.2 показан модифицированный вариант рассматриваемой программы. Чтобы продемонстрировать, как раб отают аргументы, программа использует различные их формы. Листинг 9.2. Программа 1ethead2 . с / * l e the ad2 . c * / #incl ude <s tdio . h > #incl ude <s tring . h> / * для функции s t r l e n ( ) * / #de fi ne NАМЕ " GI GATНINK , INC . " #de fi ne ADDRE S S " 1 0 1 Megabuck Pl a z a " #de fi ne PLACE "Me gapoli s , С А 9 4 9 0 4 " #de fi ne WIDTH 4 0 #de fi ne S PACE ' 1 void s how_n_char ( char ch , int num) ; int main ( vo i d ) { int s p a c e s ; s how_n_char ( ' * ' , WI DTH) ; putchar ( ' \ n ' ) ; s how_n_char ( S PACE , 1 2 ) ; / * испол ь зов ание констант в кач естве арг ументов * / / * испол ь зов ание констант в кач естве арг ументов * / printf ( " % s \ n " , NАМЕ ) ; s p a c e s = (WIDTH - s t r l e n (ADDRE S S ) ) / 2 ; / * Пусть прогр амма вь�исли т , * / / * сколь ко про белов пропу стить * / s how_n_char ( S PACE , s p a c e s ) ; / * испол ь зов ание пер еменной в кач естве арг умента * / printf ( " l s \ n " , ADDRE S S ) ; s how_n_char ( S PACE , ( W I DTH - s t r l e n ( PLACE ) ) / 2 ) ; / * выр ажение в кач е стве аргумента * / printf ( " % s \ n " , PLACE ) ; s how_n_char ( ' * ' , WI DTH) ; putchar ( ' \ n ' ) ; r e turn О ; / * опр еделение функции s how_n_char ( ) * / void s how_n_char ( char ch , int num) { int count ; for ( count = 1 ; count <= num ; count++ ) putchar ( ch ) ; 3 54 Гл ава 9 Ниже показаны ре зультаты выполнения программы : **************************************** GI GATHINK , INC . 1 0 1 Megab u c k Pl a z a Megapol i s , С А 9 4 9 0 4 **************************************** А теперь расс мотрим , как можно построить ф ункцию , которая принимает аргу· менты . После этого вы узнаете , как эта функция используется. Определен ие фун кци и с а р гументами: формальн ые пара метры О пределение ф ункции начинается со следую щего заголовка функции, соответст· вую щего стандарту ANSI С : v o i d s how_n_char ( char ch , i n t num) Эта строка с ооб щает компилятору о том, что функция s how_n_ char ( ) использует два аргумента под име нами ch и num, а также о том, что пе ременная ch имеет тип char , а переменная num тип int. Обе переме нных ch и num называются форма.л,-ь11,-ыми аргу ме'Нmами или, как на них ссылаются се йчас , форма.лъ'Нъtми параметрами. Подобно пе ре· менным, определенным внутри ф ункции, ф ормальные параметры представляют с о­ бой локальные пе ременные , действую щие в рамках только данной функции. Это озна· чает, что вы можете не беспокоиться , если дубликаты имен пе реме нных будут использованы и в других ф ункциях. Этим переме нным назначаются конкретные зна· чения при каждом обращении к функции. Обратите внимание , что ф орма ANSI С тре бует , чтоб ы каждой переменной пред· ше ствовал ее тип. То есть, в отличие от случая с обычными объявлениями, вы не мо­ жете ис пользовать с писок пе ременных одного типа : - void dib s ( int х , у , z ) void dub s ( int х , int у , int z ) / * непр авил ь ный з а г оловок функции * / / * пр авиль ный з а г о ловок функции * / Стандарт ANSI С признает также ф орму, которая применялас ь до появления ф ормы ANSI, однако характе ризует ее как устаревшую и выходящую из употребле ния : void s how_n_char ( ch , num) char ch ; int num ; В рассматриваемом случае в круглые скобки заключен список имен аргументов, но их типы объявляются позже . О братите внимание на то , что аргументы объявляются перед ф игурной с кобкой, которая обозначает начало тела функции, но обычные ло­ кальные переменные объявляются после ф игурной скобки. Такая ф орма позволяет использовать списки имен переменных, разделенных запятыми, если эти пере менные имеют один и тот же тип, как показано в приме рах ниже : void dib s ( x , у , z ) int х , у , z ; / * пр ави л ь но * / Назначение стандарта ANSI с остоит в том, чтобы постепе нно прекратить исполь· зование ф орм , употре блявшихся до е го появления . Эти ф ормы следует знать, чтобы Функции 355 можно было понимать старые программы, но в т о ж е вре мя в новых программах вы должны использовать совреме нные формы. И хотя функция s how_n_char ( ) принимает значения от функции main ( ) , она ниче­ го не возвращает. Поэтому функция s h ow_n_ char ( ) имеет тип void. Теперь посмот­ рим, как используется эта функция . создан и е п рототипа функци и с а р гумента м и М ы приме няем прототип ANSI для объявления функции перед тем, как она будет использоваться: void s how_n_char ( char ch , int num) ; Когда функция принимает аргументы, ее прототип задает их количество и типы, при этом употребляются спис ки по типам, в которых имена переменных отделяются друг от друга запятыми. При желании вы можете опустить имена пе ременных в про­ тотипах: void s how_n_char ( char , int ) ; На самом деле ис пользование имен в прототипах функции не приводит к созданию переме нных. О но просто подчеркивает тот факт, что char в прототипе означает пе­ ременную типа char и так дале е . С нова напомним, что стандарт ANSI С также признает старые формы объявления функции, в которой с писок аргументов не употре бляется : void s how_n_ch ar ( ) ; Эта форма фактиче ски выпадает из указанного выше стандарта . Если б ы даже это было не так, все равно формат прототипа намного лучше , в чем вы вс коре убедитесь сами. О с новная причина того, что вы должны знать эту форму, заклю чается в том, что вам приходится с ними сталкиваться в старых программах. Вызов фун кци и с а р гументом: факти ческие аргументы Значе ния пере менным ch и num назначаются с помощью фахти11.есхих аргументов в вызове функции. Рас смотрим пе рвый случай использования функции s how_n_ char ( ) : s h ow_n_char ( S PACE , 1 2 ) ; Фактичес кими аргументами являются символ пробела и число 1 2 . Эти значение прис ваиваются соответствую щим формальным параметрам функции s how_n_ char ( ) , то есть переменным ch и num. Короче говоря , формальный параметр есть переме нная в вызывае мой функции, а фактиче ским аргументом является конкретное значение , прис ваивае мое пе ременной функции в момент вызова функции. Как показывает при­ мер, фактичес ким аргументом может быть константа , переме нная и даже б олее слож­ ное выражение . Не зависимо от того, что представляет собой фактиче ский аргумент, он вычисляется , и результат вычислений копируется в соответствую щий формальный параметр функции. Например, рассмотрим последний случай ис пользования функции s how_n_char ( ) : s h ow_n_char ( S PACE , ( W I DTH - s t r l e n ( PLACE ) ) / 2 ) ; 3 56 Гл ава 9 В ычисление длинного выраже ния , образующего второй актуальный аргумент , дает в ре зультате 1 0 . Значение 1 0 присваивается пере менной num. Функция не знает , да и не пытается узнать, откуда происходит это число, от константы , пе ременной или б о­ лее общего выраже ния . Еще раз подчеркне м, что фактический аргумент представляет собой конкретное значе ние , присваиваемое пе ременной, известной как формальный параметр (рис . 9.3 ) . Поскольку вызывае мая функция работает с данными, скопиро­ ванными из вызывающей функции, исходные данные в вызываю щей функции защи­ ще ны от любых манипуляций , которым вызываемая функция подвергает их копии. int main (void) ( Фактическим аргументом является число 25, это значение передается в функцию main ( ) функцией space ( ) и присваивается переменной nwnЬer space (25) ; Формальным параметром является nwnЬer п еременная , объявленная в заголовке функции - llllii..._ ,,..­ void space (int nwnЬer) Р и с . 9 .3. ФормаЛЪ'/1,Ъtе параметръ� и факти-ческие аргумmтъ� Факти ч еск и е аргум енты и фор м ал ьн ы е п араметры Фактически й аргумент - это в ы ражение, которое поя вляется в круглых скобках в вызо­ ве функци и . Формал ьн ы й пара метр - переменная, объя вленная в заголовке объя вле­ ния функции. Когда вы полняется вызов функци и, пере менные, объя вленные как фор­ мальные параметры, создаются и инициал изируются значения м и , полученны м и при вычислении фактичес ких а ргументов. В листинге 9.2 ' * ' и W I DTH я вля ются фактиче­ ски м и а ргументами, когда функция s how_n_ char ( ) вызы валась первы й раз, а S PACE и 1 1 - фактически м и аргументами второго вызова этой функции. В определении функ­ ции рассматриваемо й функции ch и num - это формальные па раметры. Предста вле н ие фун кции в виде черного ящика В представлении функции s how_ n_ char ( ) в виде черного ящика входом является символ, подлежащий отображе нию , и количество пробелов, которые нужно пропус­ тить. Ре зультатом является печать заданного с имвола указанное число раз . Т акой ввод привязан к функции посредством аргументов . Функции 3 57 Этой информации вполне достаточно , чтобы продемонстрировать, как эта функ­ ция используется внутри main ( ) . Кроме того , она вполне может служить в качестве спе цификации для написания функции. Тот факт, что ch, num и co unt локальные пе ременные , не видимые за пределами функции s h ow_n_ char ( ) , хорошо впис ывается в концепцию че рного ящика . Если бы вы ис пользовали пере менные с теми же именами в функции main ( ) , то это были бы другие, независимые переме нные . То е сть, если в функции main ( ) была бы перемен­ ная count , то изменение ее значения не привело б ы к изменению значе ния count в функции s how_n_char ( ) и наоборот . Все , что происходит внутри че рного ящика , не­ доступно вызывающей функции. - Возврат зна чения функцией с помощью оператора return Вы уже видели, как передается информация из вызывае мой функции в вызываю­ щую . Чтоб ы передать информацию в противоположном направлении используется возвращае мое значение . Чтобы напомнить, как это работает, создадим функцию , ко­ торая возвращает ме ньший из двух аргуме нтов. Дадим этой функций имя imin ( ) , по­ скольку она предназначена для манипулирования значе ниями тип int. Кроме того, напишем простую функцию main ( ) , единственная цель которой закл ю чается в про­ верке работоспособности функции imi n ( ) . Программу, разработанная для те стирова­ ния функций таким спос об ом , иногда называют драйвером. Драйвер бе рет функцию для прове рки. Если функция проходит проверку ус пешно, ее можно использовать в более ответственной программе . В листинге 9.3 показан драйвер и функция выбора мини­ мального значе ния . Листинг 9.3. Программа 1es s er . с / • l e s s er . c - - из двух зол о н а выбир а е т меньшую • / #incl ude <s tdio . h > int imin ( i n t , int ) ; int main ( vo i d ) { int evil l , evi l 2 ; printf ( " Bв eди т e дв а целых числа (или q для з ав ершения прогр аммы) : \ n " ) ; whi l e ( s can f ( " % d % d " , & e vi l l , & evil 2 ) 2) { рrint f ( "Мен ьшим из двух чисел % d и % d я вля ется % d . \ n " , evil l , e vil 2 , imin ( e vil l , evil 2 ) ) ; print f ( "Bв eдитe дв а целых числа (или q для з авершения прогр аммы) : \ n " ) ; printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; int imin ( i n t n , int m) { int min ; 3 58 Гл ава 9 i f ( n < m) min n; el s e min m; = r e turn min ; Ниже показан пример выполнения этой программы: Вв едите дв а це лых числа ( или q для з а в ершения пр ограммы) : 509 333 Меньшим из двух чисел 5 0 9 и 3 3 3 явля е тся 3 3 3 . Вв едите дв а це лых числа ( или q для з а в ершения пр ограммы) : -9393 6 Меньшим из двух чисел - 9 3 9 3 и 6 явля е тся - 9 3 9 3 . Вв едите дв а це лых числа ( или q для з а в ершения пр ограммы) : q Пр ограмма з ав ершена . Клю чевое слово r e turn об еспечивает то , что следую ще е за ним выраже ние стано­ вится возвращаемым значением функции. В рас сматриваемом случае функция воз· вращает значение , присвоенное переменной min . Поскольку min имеет тип int, сле­ довательно, функция imi n ( ) также имеет этот тип. Переменная min это приватная пе ременная , принадлежащая функции imin ( ) , однако значение min передается обратно в вызываю щую функцию с помощью r etur n . Смысл опе ратора , подобного приведенному ниже , заклю чается в том, чтобы присво­ ить значение min пе ременной l e s s er : - l e s s er = imin ( n , m) ; Нельзя ли было вместо этого написать следую щие команды? imin ( n , m ) l e s s er min ; ,· = Нет, нельзя, пос кольку вызываю щая функция ниче го не знает о существовании пе­ ременной min. В с помните , что пе ременные функции imin ( ) являются локальными по отношению к imin ( ) . При вызове функции imin ( evil l , evil 2 ) производится копиро­ вание одного набора данных в другой. В озвращаемое значение не только может быть присвоено той или иной перемен­ ной, оно может также быть использовано как часть выражения . Например , можно по­ ступить следую щим образом: answer 2 * imin ( z , z s tar ) + 2 5 ; printf ( " % d\ n " , imin ( - 3 2 + answe r , LIMI T ) ) ; = В озвращаемое значе ние может быть представлено любым выраже нием , а не только переменной. Например , вы можете сократить разме ры программы: / * функция , определяющая минималь н о е значение , в тор ая в ер сия * / imin ( int n , int m) { r etur n ( n < m) ? n m; Функции 3 59 В результате вычислений данное условное выраже ние принимает меньшее из зна­ че ний n или m и оно же возвращается вызывающей функции. Если вы предпочитаете для ясности или для с облюде ния стиля заклю чить возвращаемое значение в круглые скобки, можете это сделать, хотя круглые скобки в этом случае не нужны. А что случится , е сли функция возвращает тип, отличный от объявленного? int what_i f ( i n t n ) { douЫ e z = 1 0 0 . 0 / ( douЬl e ) n ; r etur n z ; 1 1 ч то произ ойде т ? В этом случае фактическое значе ние есть то , что вы получите , е сли присвоите ука­ занное возвращаемое значе ние переменной, имеющей объявленный тип возвращае­ мого значения . Следовательно, в рассматриваемом примере конечный итог будет тем же , как если бы вы прис воили значение z пе ременной типа i nt, а затем возвратили бы это значение . Наприме р, предположим, что функция вызвана следую щим образо м : r e s ult = what_i f ( 6 4 ) ; Тогда пере менной z присваивается значение 1 . 5 6 2 5 . Однако оператор r e turn воз­ вращает значе ние 1 . Использование оператора r eturn дает другой эффе кт. О н завершает функцию и возвращает управление следую ще му оператору вызывающей функции. Это происхо­ дит даже в тех случаях, когда опе ратор r e turn в функции не последний. Поэтому вы можете написать функцию imin ( ) так, как показано ниже: / * функция , определяющая минимальное знач е ние , тр е т ь я версия * / imin ( int n , int m) { i f ( n < m) r e turn n ; el s e r e turn m ; Многие , хотя и не все , программисты-практики, раб отающие с языком С , считают, что оператор r e turn желательно ис пользовать только один раз и только в конце функции, поскольку так легче отсле живать прохожде ние потока управле ния чере з функцию . В то же время не будет большим грехом приме нять нес колько опе раторов r etur n в функции, даже такой короткой, как рассматриваемая . В лю б ом случае, для пользователя все три версии - это одно и то же , ибо все они принимают один и тот же ввод и генерируют один и тот же вывод . О ни отличаются только внутренним устрой­ ством. Даже приведенная далее верс ия дает тот же результат: / * функция , определяющая минимальное знач е ние , ч е тв ер т ая в ер сия * / imin ( int n , int m) { i f ( n < m) r e turn n ; el s e r eturn m ; print f ( " Пр о ф е ссор Флеппард - напьnценно е нич тоже ство . \ n " ) ; 360 Гл ава 9 О пе раторы r etur n расставле ны таким образом, что управле ние никогда не попа­ дет на оператор printf ( ) . Профессор Фле ппард может пользоваться откомпилиро­ ванной версие й этой функции в своей программе , так и не узнав истинного к не му от­ ношения со стороны студентов, изучающих программирование . Вы можете употре­ бить следую щий оператор: r e turn ; О н вызывает прекращение выполне ния функции и возвращает управле ние вызы­ вающей функции. Поскольку за оператором r eturn нет никаких выражений, то ника­ кое значение не возвращается , а эта форма может использоваться только в функциях типа void. тип ы функци й Функции должны объявляться с указание м типов. Функция с возвращаемым значе­ нием должна быть объявлена с те м же типом, что и у возвращаемого значения . Функ­ ции, которые не возвращают никаких значений, должны иметь тип void. Если функ­ ции тип не назначается , в боле е ранних версиях С предполагается , что такая функция имеет тип int. Это с оглашение восходит к тем ранним временам суще ствования языка С , когда большая часть функций, так или иначе, имела тип i nt. Тем не менее , стан­ дарт С99 уже не поддерживает это неявное предположе ние в отноше нии int. О бъявле ние типа является частью определения функции. Следует иметь в виду, что это положе ние имеет отношение к возвращаемому значению , но не к аргументам функции. Например, заголовок приведенной ниже функции указывает на то , что вы определяете функцию , которая принимает два аргумента типа int, но при этом воз­ вращает тип douЫ e : do uЫ e klink ( i nt а , i n t Ь ) Чтобы правильно использовать функцию , программа должна знать тип функции. Один из с пособов достижения этой цели требует разме ще ния полного определения функции в тексте программы до ее первого приме не ния . Однако такой метод делает программу более трудной для понимания . К тому же функции могут б ыть частью биб­ лиотеки С или храниться в каком-то другом файле . В силу этого обстоятельства вы об ычно сообщаете компилятору о наличии функций, объявляя их заране е . Наприме р, функция main ( ) в листинге 9.3 содержит следую щие строки: #i nclude < s tdi o . h> int imin ( int , int ) ; int main ( void) { int e vil l , evil 2 , l e s s e r ; Вторая строка устанавливает , что imin является име не м функции, возвращаю щей значение типа i nt. Теперь компилятор будет знать, как обращаться с функцией imin ( ) , когда она позже появится в программ е . Мы поместили предварительные объ­ явления функций за пределами функций, которые их ис пользуют. О ни также могут быть помещены внутри функции. Например, вы можете представить начало програм­ мы l e s s er . с в таком виде: Функции 361 #i nclude < s tdi o . h> int main ( void) { int imin ( i n t , int ) ; / * о бъявление функции imin ( ) * / int e vil l , evil 2 , l e s s e r ; В любом случае главное заключается в том, что объявление функции должно появ· ляться до фактичес кого е е ис пользования . В стандартной библиотеке ANSI С функции сгруппированы в с емейства , при этом каждое се мейство имеет свой заголовочный файл. Такие заголовочные файлы содер· жат среди всего проче го и объявления функций из таких семейств. Например, заголо· вочный файл s tdio . h с одержит объявления функций для стандартных библиоте чных функций ввода·вывода , таких как print f ( ) и s can f ( ) . Заголовочный файл math . h с о· держит объявления множества мате матиче ских функций, например, объявле ние do uЫ e s qrt ( do uЬl e ) ; которое уведомляет компилятор о том, что функция s qrt ( ) возвращает значение типа douЫ e . Не путайте эти объявления с определе ниями функций. О бъявле ние функции сообщает компилятору о том, что какой тип имеет функция , в то время как программ· ный код приводится в определении функции. Вклю чение заголовочного файла math . h уведомляет компьютер о том, что возвращае мым типом функции s q r t ( ) является douЫ e , и что программный код функции s q r t ( ) соде ржится в отдельном файле биб· лиоте чных функций. Прототи п ирован ие фун кций в стандарте AN SI с Традиционная , применявшаяся еще до появления стандарта ANSI С схема объявле· ния функций обладала тем недостатком, что предусматривала указание типа возвращае· мого значения функции, но не типов ее аргументов. Рассмотрим, какие проблемы воз· никают в тех случаях, когда используется старая форма объявления функций. Представле нное ниже объявление , сделанное в форме , применявшейся до появле· ния стандарта ANSI, уведомляет компилятор о том, что функция imin ( ) возвращает значение типа i nt: int imin ( ) ; В то же время , оно ничего не говорит о количестве и типах аргументов imin ( ) . По­ этому, в случае применения функции imi n ( ) с неправильным количеством или типами аргументов компилятор не выявит ошибку. Решение проблемы Расс мотрим не сколько примеров использования функции imax ( ) , которая во мно· гом напоминает функцию imin ( ) . В листинге 9.4 показана программа , которая объяв· ляет функцию imax ( ) старым с пособом, а затем не правильно ее использует. 362 Гл ава 9 Листинг 9.4. Программа mi.suse . с / * mi s us e . c - - н е пр авил ьное исполь зовани е функции * / #incl ude <s tdio . h > int imax ( ) ; / * о бъяв ление в стар ом стиле * / int main ( vo i d ) printf ( " Наибольшим знач е нием из % d и % d являе тся % d . \ n " , 3 , 5 , imax ( 3 ) ) ; рrintf ( " Наибольшим знач е нием из % d и % d являе тся % d . \ n " , 3 , 5 , imax ( 3 . 0 , 5 . 0 ) ) ,· r e turn О ; int imax ( n , m) int n , m; int max ; i f ( n > m) max n; el s e max m; = r e turn max ; В первом вызове функции printf ( ) о пущен аргумент функции imax ( ) , а во втором вызове ис пользуются аргументы с плавающей запятой вместо целочисленных значе· ний. Несмотря на эти ошибки, компиляция и выполнение программы заве ршается ус· пешно. Ниже показан пример выходных данных, полученных при использовании отлад· чика Metrowe rks Codewarrior в среде D evelopment Studio 9 : Наибольшим значением из 3 и 5 я вля ется 1 2 4 5 1 2 0 . Наибольшим значением из 3 и 5 я вля ется 1 0 7 4 2 6 6 1 1 2 . Компилятор D igital Mars 8.4 генерирует значения 4 2 0 2 8 3 7 и 1 0 7 4 2 6 6 1 1 2 . Оба эти компилятора работают хорошо; оба они становятся жертвами неправильного исполь· зования программой прототипов функций. Что происходит в данном случае? Механизмы в различных с истемах могут быть различными, но вот что происходит в перс ональных компьюте рах IBM или в компью· терах VAX. Вызываю щая функция помещает аргументы во време нную память, полу· чившую название стека, а вызываемая функция читает их оттуда. Оба эти проце с с а не скоординированы между собой. Вызывающая функция решает, какие типы переда· вать, исходя из значений фактических аргументов в вызове , а вызываемая функция читает значения на ос нове своих формальных аргументов. Поэтому вызов imax ( 3 ) помещает одн.о целое значение в стек. Как только начинается выполнение функции imax ( ) , она читает два целых значений из стека. В сте к был помещен только один ар­ гумент, следовательно, в каче стве следую щего значения читается то , что случайно оказалось в стеке в этот моме нт. Функции 363 Второй раз , когда в примере используется функция imax ( ) , функции imax ( ) пе ре­ дается значение f l o at. Это означает помещение в стек двух значений douЫ e . (Вспом­ ните , что значение типа float повышается до douЫ e , когда пе редается в каче стве ар­ гумента . ) В наше й системе это два 64-разрядных значения , таким образом, в сте к по­ мещаются данные в количестве 1 2 8 битов. Когда функция imax ( ) считывает два значения типа i nt из стека , она читает пе рвые 64 бита из стека , пос кольку в нашей системе каждое значение типа int занимает 3 2 бита . Случаю было угодно, чтобы эти биты соответствовали двум конкретным целочисленным значениям, наибольшим из которых было 1 0 74266 1 1 2 . Решение станда рта ANSI Подход к ре шению задачи несоответствия аргументов, предложенный стандартом ANSI, с остоит в том, чтобы разре шить в объявле ниях функции также и объявление типов. Результатом становится прототип фуuк-ции, то е сть объявление , которое уста­ навливает тип возвращаемого значе ния , количество аргументов и типы этих аргуме н­ тов. Чтобы указать, что функции imax ( ) тре буются два аргумента , можете объявить ее со следую щими прототипами: int imax ( int , int ) ; int imax ( int а , int Ь ) ; Первая форма использует список типов, разделенных запятыми. Во второй форме к типам добавлены имена переме нных. Помните , что име на пе ременных на с амом де­ ле являются фиктивными и не обязательно должны соответствовать именам, исполь­ зуемым в определении функции. Располагая этой информацией, компилятор может проверить, соответствует ли вызов функции прототипу. Задано ли правильное количество аргументов? Правильно ли выбраны их типы? Если имеет ме сто несовпадение типов и если оба типа представ­ ляют собой числа , компилятор пре образует значения фактичес ких аргументов в тот же тип, что и формальные аргументы. Например, imax ( 3 . О , 5 . О ) становится imax ( 3 , 5) . Мы внесли изме не ния в листинг 9.4, чтобы ис пользовать прототип функции. Ре зуль­ тат показан в листинге 9 . 5 . листинг 9.5. Программа proto . с / * pr oto . c - - исполь зуе т про то типы функции * / #incl ude <s tdio . h > int imax ( i n t , int ) ; / * про то тип * / int main ( vo i d ) { printf ( " Наибольшим знач е нием из % d и % d являе тся % d . \ n " , 3 , 5 , imax ( 3 ) ) ; рrintf ( " Наибольшим знач е нием из % d и % d являе тся % d . \ n " , 3 , 5 , imax ( 3 . О , 5 . О ) ) ; r e turn О ; int imax ( i n t n , i nt m) { int max ; 364 Гл ава 9 i f ( n > m) max n; el s e max = m ; r e turn max ; При попытке скомпилировать программу, представленную в листинге 9 . 5 , наш компилятор выдает сообщение об ошиб ке , утверждающе е , чго вызов функции imax ( ) соде ржит меньше параметров, чем нужно. А что можно сказать об ошибках при выборе типов? Чтоб ы провести их исследова­ ние , мы заменили вызов imax ( 3 ) на imax ( 3 , 5) и предприняли очередную попытку компиляции. На этот раз с ообщений об ошибках не последовало, и мы запустили про­ грамму на выполнение . В от как выглядят результаты : Наибольшим значением из 3 и 5 я вля ется 5 . Наибольшим значением из 3 и 5 я вля ется 5 . Как и ожидалось, 3 . О и 5 . О во втором вызове были пре образованы в 3 и 5 , чтобы функция смогла обработать ввод должным образом. И хотя программа не выдала ни одного сообщения об ошиб ке , наш компилятор вы­ дал предупреждающее сообщение о том, что тип douЫ e был преобразован в тип i nt и что при этом возможна поте ря данных. Наприме р, вызов imax ( 3 . 9 , 5 . 4 ) становится эквивалентным следую ще му вызову: imax ( 3 , 5 ) Различие между сообщением о б ошиб ке и предупреждаю щим сообщением состоит в том, чго ошибка не позволяет выполнить компиляцию , а предупреждение компиля­ цию допускает. Н е которые компиляторы выполняют приведение типов, не уведомляя вас об этом. Это объясняется тем, что стандарт не требует предупреждения . В то же вре мя многие компиляторы позволяют выбирать уровень предупрежде ний, который управляет, насколько "крас норечивым" будет компилятор , выводящий на экран пре­ дупреждение . Отсутствие а р гументов и неопределенные аргументы Предположим , что в ы создали прототип, подобный следую щему: void p r i nt name ( ) ; Компилятор ANSI С полагает, что вы приняли ре шение заране е построить прото­ тип функции, и не станет проверять аргуме нты . Чтобы указать, что функция на самом деле не имеет аргументов, в круглые скобки заклю чается клю чевое слово void: void p r i nt_name ( vo i d ) ; Функции 365 Компилятор ANSI С интерпретирует предыдущее выражение следую щим образо м : функция p rint_n ame ( ) не имеет аргуме нтов. Затем проверяется , ис пользуете ли вы аргументы при вызове этой функции. Некоторые функции, такие как printf ( ) и s c an f ( ) , принимают пе ременное коли· чество аргументов. В функции printf ( ) , например, в качестве пе рвого аргумента ис· пользуется строка, в то же время остальные аргументы не фиксированы ни по типу, ни по числу. ANSI С в таких случаях допускает построе ние частичных прототипов. Наприме р, вы можете использовать такой прототип функции printf ( ) : int print f ( char * , . . . ) ; Этот прототип показывает, что первым аргументом является строка (в главе 1 1 эта тема рас сматривается более подроб но ) и что функция может принимать другие аргу· менты , природа которых на текущий момент не уточнена . Б иблиоте ка функций на языке С с помощью заголовочного файла s t darg . h пред· лагает стандартный спос об для определе ния функции с переменным количеством па· раметров; в главе 1 6 можно найти соответствую щие подробности. д а здравствуют прототи п ы Прототипы являются весомым дополнением к языку. О ни предоставляют компи· лятору возможность выявлять многие ошибки или упущения, которые были допущены при использовании функций. Если они не будут с воевременно обнаружены, они станут источником проблем, для решения которых потре буются немалые усилия. О бязатель· но ли ими пользоваться? Нет, вы можете применять старый с пособ определения функций (спос об, в котором параметры не указываются ) , но у этого спос оба нет пре· имуществ , зато для не го характерно множество недостатков. Суще ствует один способ избежать прототипов, но в то же время сохранить пре· имущества прототипирования . Назначение прототипа заклю чается в том , чтоб ы по­ казать компилятору, как должна ис пользоваться функция , прежде чем компилятор выйдет на первый случай фактического вызова этой функции. Вы достигните того же ре зультата , е сли поместите полное определе ние функции раньш е , чем она будет ис· пользована . В этом случае определение де йствует как свой собственный прототип. Обычно такой прием применяется в отношении коротких функций: 11 nрив е денно е ниже описание являе тся одновр еменно и о пр едел ением , / / и про то типом int imax ( int а , int Ь) { r eturn а > Ь ? а : Ь ; } int main ( ) z = imax ( x , 5 0 ) ; Р екурсия Язык программирования С позволяет функции вызывать саму себя. Этот проце с с называется рекурсией. Иногда рекурсия бывает удобным инструме нтальным средством, но время от вре мени она пре подносит сю рпризы . 366 Гл ава 9 Рекурсию трудно дове сти до конца , пос кольку функция , которая вызывает сама се­ бя , с пособна делать это до бес конечности, если в программе не предусмотрены с пеци­ альные средства , включаю щие проверку условия заверше ния рекурсии. Рекурсия часто может использоваться там, где применяется цикл. Иногда более предпочтительным решение м является цикл, а иногда - ре курсия . Рекурс ивное реше­ ние изящно, в то вре мя как решение с использованием циклов обладает больше й эф­ фективностью . Рекурсия в действи и Чтобы посмотреть, что собой представляет рекурсия , обратимся к подходящему приме ру. Функция main ( ) в листинге 9.6 вызывает функцию up_and_down ( ) . Мы назо­ вем это " первым уровнем рекурсии" . Затем функция up_and_down ( ) вызывает с аму се­ бя ; назовем это " вторым уровне м рекурсии" . Второй уровень вызывает третий урове нь ре курсии и так далее. В приведенном ниже примере ис пользуются четыре уровня ре­ курсии. Чтоб ы получить представле ние о рекурсии изнутри, рассматриваемая про­ грамма не только воспроизводит значение пе ременной n , она также вос производит значение &n, представляющее собой адрес ячейки памяти, где хранится пе реме нная n . ( Операция & , используемая в этой программе, более подробно расс матривается далее в этой главе . Функция printf ( ) для адресов предусматривает с пецификатор %р. ) Листинг 9.6. Программа recur . с / * r e cur . c - - иллюстр ация р е кур сии * / #incl ude <s tdio . h > void up_and_down ( int ) ; int main ( vo i d ) { up and_down ( l ) ; r e turn О ; void up_and_down ( int n ) { printf ( " Уровень % d : яч ейк а n %p \ n " , n , & n ) ; i f (n < 4 ) up and_down ( n+ l ) ; p r i nt f ( " YPOBEHЬ % d : яч ейк а n %p \ n " , n , & n ) ; В ыходные данные принимают следую щий вид: Ур овень Ур овень Ур овень Ур овень УРОВЕНЬ УРОВЕНЬ УРОВЕНЬ УРОВЕНЬ 1: 2: 3: 4: 4: 3: 2: 1: ячейка n ячейка n ячейка n ячейка n ячейка n ячейка n ячейка n ячейка n Ox0 0 1 2 ff4 8 Ox0 0 1 2 ff3c O x 0 0 1 2 f f3 0 O x 0 0 1 2 f f2 4 O x 0 0 1 2 f f2 4 O x 0 0 1 2 f f3 0 Ox0 0 1 2 ff3c Ox0 0 1 2 ff4 8 /* 1 */ /* 2 */ Функции 367 Пройде мся по данной программ е , чтобы выяс нить, как работает ре курсия . Прежде всего , функция mai n ( ) вызывает функцию up _ and_ down ( ) с аргументом 1 . В результате формальный параметр n функции up _ and_down ( ) получает значение 1, поэтому пер­ вый оператор пе чати выводит Уров ень 1 . Далее , пос кольку n меньше 4 , функция up _ and_down ( ) (урове нь 1 ) вызывает функцию up _ and_down ( ) (уровень 2) с фактиче­ ским аргументом n + 1, или 2 . Этот вызов приводит к тому, что пере менной n в вызо­ ве на уровне 2 присваивается значение 2 , следовательно , первый опе ратор печати вы­ водит Level 2 . Аналогично, в ре зультате вызова следую щих двух вызовов пе чатаются Level 3 и Level 4 . При достижении уровня 4 переменная n имеет значение 4 , следовательно , прове р­ ка i f заканчивается неудачей. В ызовы функции up_and_down ( ) прекращаются . Вместо этого вызов на уровне 4 передает управле ние второму опе ратору пе чати, который вы­ водит УРОВЕНЬ 4 , поскольку переменная n имеет значение 4 . Зате м управление пе ре­ дается оператору r etur n . В этой точке заканчивается вызов уровня 4, и управление возвращается функции, которая его вызвала (вызов уровня 3 ) . Последним операто­ ром, выполненным на уровне 3 , б ыло обращение к уровню 4 в операторе i f. По этой причине выполнение уровня 3 возоб новляется со следую щ е го оператора, таким опе­ ратором является второй опе ратор печати. При этом пе чата ется УРОВ ЕНЬ 3 . По окончании уровня 3 управление передается на уровень 2, на котором пе чата ется УРОВЕНЬ 2, и так далее. Обратите внимание на то , что каждый уровень рекурс ии использует свою собст­ венную пе ременную n. Этот факт легко установить, если проанализировать адре с ные значения . (Разумеется , в общем случае различные системы с ообщают различные адре­ с а , возможно , в разных форматах. Одним из важных моментов является то , что адрес в строке Ур овень 1 тот же, что и адре с в строке УРОВЕНЬ 1 и так дале е . ) Если это в а м покажется слишком сложным , представьте с ебе, что в ы имеете не ко­ торую це почку вызовов функций, при этом функция fun 1 ( ) вызывает функцию fun2 ( ) , функция fun2 ( ) вызывает funЗ ( ) , а funЗ ( ) - fun 4 ( ) . Как только заканчивает­ ся выполнение функции fun 4 ( ) , управле ние передается функции funЗ ( ) . Как только заканчивается выполнение funЗ ( ) , управле ние возвращается функции fun2 ( ) . А когда заве ршается выполне ние fun2 ( ) , управление возвращается fun l ( ) . Ре курсия работает точно так же, за исклю чением того факта , что все функции fun l ( ) , fun2 ( ) , funЗ ( ) и fun 4 ( ) представляют собой одну и ту же функцию . основы рекурси и На пе рвый взгляд рекурсия может показаться оче нь сложным процессом, поэтому стоит расс мотреть три базовых положе ния , которые существенно обле гчат е е пони­ мание . В о-первых, каждый урове нь вызова функции имеет собственные пере менные . То есть пе ременная n уровня 1 отлична от переме нной n уровня 2 , следовательно , программа создает четыре разных пе ременных, каждая из которых имеет имя n, и ка­ ждая имеет с вое собственное значение , отличное от других. Когда в коне чном итоге программа возвратится к вызову функции up _ and_ down ( ) , выполненному на пе рвом уровне , исходная переменная n все еще будет иметь значе ние 1 (рис . 9.4) . 368 Гл ава 9 П еременные : После вызова на уровне 1 После вызова на уровне 2 После вызова на уровне 3 После вызова на уровне 4 После возврата из уровня 4 После возврата из уровня 3 После возврата из уровня 2 После возврата из уровня 1 n n n n 2 2 2 2 2 3 3 3 4 ( Все вызовы завершены ) Р и с . 9 .4. Переме11,11,ъ1е рекурсии Во·вторых, каждому вызову функции соответствует возврат на предыдущий уровень. Когда поток управления программы достигает оператора r eturn в конце последнего уровня рекурсии, управление передается предыдущему уровню рекурсии. Программа не возвращается каждый раз к исходному вызову в функции rnain ( ) . Вместо этого програм· ма должна пройти через каждый уровень рекурсии при возврате с одного уровня функ· ции up _ and_down ( ) на уровень, на котором функция up _ and_down ( ) ее вызвала . В·третьих, операторы ре курсивной функции, которые предшествую ре курсивному вызову, выполняются в том же порядке, в каком были вызваны эти функции. Напри· мер, в листинге 9.6 пе рвый опе ратор пе чати находится пе ред рекурсивным вызово м . О н б ы л выполне н четыре раза в порядке следования рекурсивных вызовов: уровень 1 , урове нь 2 , урове нь 3 и уровень 4. В-четвертых, операторы в рекурс ивных функциях, которые следуют после ре кур­ сивных вызовов, выполняются в порядке , обратном тому, в каком эти функции вызы· валис ь. Например, второй оператор печати располагается после рекурсивного вызо· ва , и выполнялся в следую ще м порядке: урове нь 4, уровень 3, уровень 2, урове нь 1 . Это свойство рекурсии полезно при написании программ , решаю щих задачи изменения порядка следования на обратный. Соответствую щий пример будет представлен позже. В·пятых, нес мотря на то , что каждый уровень рекурс ии имеет собственный набор переме нных, сам по себе код не дублируется. Программный код представляет с обой последовательность команд , а вызов функции е сть команда перехода в начало этой последовательности. Потому рекурсивный вызов возвращает программу в начало этой последовательности команд . Наряду с те м, что рекурсивные вызовы создают новые переменные при каждом вызове , они во многом напоминают цикл. В самом деле , в не· которых случаях ре курс ия может быть ис пользована вме сто цикла и наоборот. И, наконец, оче нь важно , чтобы рекурсивная функция с одержала что-то, что могло бы остановить последовательность рекурс ивных вызовов. Обычно рекурсивная функ· ция использует проверки i f или эквивалентные с целью пре кратить рекурсию , когда со ответствую щий параметр функции достигает конкретного значения . Чтобы это работало , необходимо , чтобы каждый вызов использовал различные значе ния этого па раметра. Например, в последне м приме ре функция up_and_down ( n ) вызывает up_and_down ( n+ l ) . В конечном итоге фактичес кий аргумент получает значение 4, при этом проверка показывает, что условие i f (n < 4 ) не выполняется . Функции 369 Х востовая рекурсия В рекурсии простейшей формы рекурсивный вызов расположе н в конце функции, не посредственно пе ред опе ратором r etur n. Такая ре курс ия называется хвостовой или ко�щевой, поскольку рекурсивный вызов выполняется в конце . Хвостовая ре курсия яв­ ляется простейшей формой рекурс ии, поскольку она действует подобно циклу. Расс мотрим как циклическую , так и ре курсивную версию функции, вычисляю щей факториал, при этом будем иметь в виду хвостовую рекурс ию . Фактоfrиал целого чис­ ла - это произведение вс ех целых чисел в промежутке от 1 до заданного числа вклю­ чительно . Например , факториал 3 (запис ывается как 3 ! ) раве н 1 * 2 * 3 . При этом О ! полагается равным 1 , а факториалы отрицательных чисел не определе ны . В листинге 9 . 7 показана одна функция , которая использует цикл для вычисления факториала, и вторая функция , которая для этой цели использует ре курсию . листинг 9.7. Программа factor . с 1 1 fa ctor . c - - испол ь з у е т циклы и рекур сию для вычисления ф акториала #incl ude <s tdio . h > long fact ( i nt n ) ; long r fact ( int n ) ; int main ( vo i d ) { int num ; printf ( " Э тa пр огр амма вычисля е т ф акториалы . \ n " ) ; printf ( "Bвeдитe значение в диапазоне 0-12 (или q для завершения программы ) : \n" ) ; whi l e ( s can f ( " % d" , & num) == 1 ) { i f ( n um < 0 ) рrintf ( " Пожалуйста , не вв одите о трица тельные числа . \ n " ) ; el s e i f ( num > 1 2 ) printf ( " Bxoднo e знач ение должно быть мень ш е 1 3 . \ n " ) ; el s e { pr int f ( " цикл : ф акториал % d = % l d\ n " , num , f act ( num) ) ; p r i nt f ( " p eкyp cия : ф актори ал % d = % l d\ n " , num , r fact ( num) ) ; print f ( " B в e дитe знач ение в диапазоне 0 - 1 2 (или q для завершения прогр аммы) : \ n " ) ; } p r i nt f ( " B ceгo хорош е г о . \ n " ) ; r e turn О ; long fact ( i nt n ) { l o ng ans ; for ( ans = 1 ; n > 1 ; n - - ) ans *= n; r e turn ans ; 1 1 функция на б а з е цикла 370 Гл ава 9 long r fact ( int n ) { l o ng ans ; 1 1 рекур сивная в ер сия i f (n > О ) ans= n * r f act ( n - 1 ) ; el s e ans = 1 ; r e turn ans ; Программа тестового драйвера ограничивает входные данные целыми значениями в диапазоне от О до 1 2 . Как показывают вычисления , значе ние 1 2 ! немного меньше полумиллиарда, так что для размещения ре зультата 1 3 ! требуется больше памяти, чем может предложить тип long в нашей систе ме. Чтобы вычислять факториалы , превос­ ходящие 1 2 ! , не обходимо ис пользовать типы больших размеров, такие как douЫ e или long long. Ниже показаны ре зультаты выполнения демонстрационной программы: Эта про г р амма вычисл я е т ф актори алы . Вв едите знач ение в диапазоне 0 - 1 2 (или q для з ав ершения про г р аммы) 5 цикл : ф а к ториал 5 = 1 2 0 р е кур сия : ф акториал 5 = 1 2 0 Вв едите знач ение в диапазоне 0 - 1 2 (или q для з ав ершения про г р аммы) 10 цикл : ф а к ториал 10 = 3 6 2 8 8 0 0 р е кур сия : ф акториал 1 0 = 3 6 2 8 8 0 0 Вв едите знач ение в диапазоне 0 - 1 2 (или q для з ав ершения про г р аммы) q В с е г о хорошег о . Верс ия с циклом инициализирует переме нную ans значением 1 , а зате м умножает ее на целые значе ния в диапазоне от n до 2. В техничес ком плане необходимо умно­ жать также и на 1 , однако результат от этого не изменится . Теперь рас смотрим рекурс ивную версию . Клю че вым фактором является уравнение n ! = n х ( n - 1 ) ! . О но следует из того факта , что ( n - 1 ) ! представляет с об ой произве­ дение всех положительных целых чисел от 1 до n - 1 . Т аким образом, умножение этого ре зультата на n дает произведение целых чисел от 1 до n. Эта особенность позволяет приме нить рекурсивный подход. Если соответствую щую функцию назвать r fa ct ( ) , то r fact ( n ) есть n * r fact ( n - 1 ) . Следовательно, вы можете вычислить значение r fact ( n ) , выполнив вызов r fa ct ( n - 1 ) , как это делает программа , показанная в лис­ тинге 9 . 7 . Разумеется , вы обязательно должны прервать рекурс ию в той или иной точ­ ке, при этом вы можете сделать это , установив возвращаемое значение равным 1 , ко­ гда значе ние м n является О . Рекурсивная версия программы в листинге 9 . 7 позволяет получить тот ж е ре зуль­ тат, что и версия с циклом. О братите внимание на тот факт, что несмотря на то, что вызов функции r fact ( ) не является последней строкой в функции, это последний оператор, выполняемый в условиях, когда n > О , откуда следует, что мы имеем дело с хвостовой ре курсией. Функции 371 Учитывая тот факт, что при написании кода в ы можете использовать как цикл, так и ре курсию , возникает вопрос, какой из этих вариантов выб рать? В большинстве слу­ чае в предпочтение отдается циклу. Во-первых, пос кольку рекурс ивный вызов создает свой собстве нный набор пе ременных, вариант с рекурсией использует больше памя­ ти, при этом каждый рекурсивный вызов размещает новый набор пе ременных в стеке . Во-вторых, рекурс ия выполняется медленне й, пос кольку на каждый вызов функции тре буется определенное время . В таком случае, какой смысл уделять этому приме ру столько внимания? Дело в том, что хвостовая рекурсия наиболее доступна для пони­ мания , а рекурс ия в целом достойна внимательного изучения , так как на практике встречаются случаи, когда с помощью простого цикла задача не решается. Рекурсия и обратны й порядок Теперь рассмотрим задачу, при решении которой очень удобно воспользоваться ре­ курсией для изменения порядка на обратный. (Это один из тех случаев, когда реализа­ ция рекурсии проще , нежели цикла.) Необходимо решить следую щую задачу: написать функцию , которая печатает двоичный эквивалент целого числа . В двоичной системе счисления числа представлены в виде суммы степеней 2. Подобно тому, как в десятич­ ной системе число 234 означает 2 х 1 0 2 + 3 х 1 0 1 + 4 х 1 0 ° , число 1 0 1 в двоичной системе означает 1 х 2 2 + О х 2 1 + 1 х 2 ° . В двоичных числах используются только цифры О и 1 . В ам не обходим метод , или алгоритм. Как вы можете , скажем, найти двоичный эк­ вивалент числа? Понятно, что нечетные числа должны иметь двоичное представле­ ние , оканчивающееся цифрой 1 . Четные числа оканчиваются цифрой О, следователь­ но , вы можете определить, является ли последняя цифра 1 или О , вычислив выраже­ ние 5 % 2 . Если результатом является 1 , то число 5 нечетное , и последней цифрой будет 1 . В обще м случае, е сли n есть число , то его последняя цифра есть n % 2, следо­ вательно, первая цифра , которую вы найдете , будет последней цифрой, которую должны напечатать. Это предполагает использование рекурсивной функции, которая вычисляет выраже ние n % 2 до рекурсивного вызова , но которая печатает е го ре зуль­ тат после рекурс ивного вызова . Таким образом, выраже ние , вычисленное первым, печатается последним. Чтобы получить следую щую цифру, разделите исходное число на 2. Это двоичный эквивалент сдвига де сятичной точки на одну позицию вле во , благодаря которому вы можете определить следую щую двоичную цифру. Если значение четное , следую щей двоичной цифрой является О , а если нечетное 1. Наприме р, 5/2 равно 2 (целочис­ ленное деле ние ) , таким образом, следую щая цифра О. В ре зультате имеем 0 1 . Про­ должим этот проце с с . Делим 2 на 2 и в ре зультате получаем 1 . В ычисляем 1 % 2 и по­ лучаем 1 , следовательно, следую щей цифрой будет 1 . Имеем 1 0 1 . Когда процесс дол­ же н остановиться? Он останавливается в том случае, когда ре зультат деления на 2 меньше 2 , поскольку пока он остается равным 2 или больше , для представле ния числа используются , по меньшей ме ре , еще одна цифра . Каждое деление на 2 приводит к уменьшению количества цифр на единицу, пока процесс не достигнет конца . (Если есть затруднения в освоении этого алгоритма , перейдите к де сятичной аналогии. О с­ татком от деления 628 на 1 0 является 8 , следовательно , 8 последняя цифра. Деление на 10 дает 62, а остаток от деле ния 62 на 10 есть 2, следовательно , таковой является следую щая цифра и так далее . ) Этот подход реализован в программе, показанной в листинге 9 . 8 . - - - 372 Гл ава 9 листинг 9.8. Программа binary . с / * Ь i n ary . c - - nечатает целые числ а в дв оичной форме * / #incl ude <s tdio . h > void to_Ьin ary ( u n s igned long n ) ; int main ( vo i d ) { un s i gned long numЬ e r ; printf ( " Bв eди т e цело е число (или q для з ав ершения про г р аммы) : \ n " ) ; whi l e ( s can f ( " % ul " , &numЬ e r ) 1) { print f ( " Двоичный эквив алент : " ) ; to_Ь i n ary ( numЬ e r ) ; putchar ( ' \ n ' ) ; print f ( " B в e дитe целое число (или q для з ав ерш ения прогр аммы) : \ n " ) ; printf ( " Пpoгp aммa з ав ерш е н а . \ n " ) ; r e turn О ; void to_Ьin ary ( u n s igned long n ) { int r ; / * р екур сивная функция * / r = n % 2; i f ( n >= 2 ) to_Ь i n ary ( n / 2 ) ; putchar ( ' O ' + r ) ; r e turn ; В листинге 9.8 выражение ' О ' + r дает в результате символ ' О ' , если r равно О , и символ ' 1 ' , если r равно 1 . Отс юда следует , что числовой код символа ' 1 ' на единицу больше , чем с имвола ' О ' . Как коды ASCII, так и коды EBCDIC удовлетворяют этому условию . В об ще м случае , вы можете использовать следую щий подход : putchar ( r ? ' 1 ' : ' О ' ) ; Ниже показан пример выполнения этой уче бной программы: Вв едите целое число (или q для з ав ерш ения прогр аммы) : 9 Дв оичный эквив алент : 1 0 0 1 Вв едите целое число (или q для з ав ерш ения прогр аммы) : 255 Дв оичный эквив алент : 1 1 1 1 1 1 1 1 Вв едите целое число (или q для з ав ерш ения прогр аммы) : 1024 Дв оичный эквив алент : 1 0 0 0 0 0 0 0 0 0 0 Вв едите целое число (или q для з ав ерш ения прогр аммы) : q Пр ограмма з ав ершена . Функции 373 Можно л и было использовать этот алгоритм для вычисления двоичных представ­ лений числа без приме не ния рекурс ии? Можно , однако, пос кольку этот алгоритм пер­ вой вычисляет последню ю цифру, вы должны где-то сохранить вс е цифры (наприме р, в массиве ) , прежде чем отображать результат на экране . В главе 1 5 будет представлен приме р не рекурсивного подхода к ре шению этой задачи. Преимущества и недостатки рекурси и Рекурсия обладает с воими преимуще ствами и недостатками. Одно из преимуще ств ре курсии с остоит в том, что она предлагает простейшее решение некоторых задач программирования . Один из недостатков рекурсии заклю чается в том, что некоторые ре курсивные алгоритмы могут довольно-таки быстро исчерпать ресурс памяти ком­ пьютера. Наряду с этим ре курсию трудно документировать и поддерживать. Рассмот­ рим приме р, который иллю стрирует как преимущества , так и недостатки рекурсии. Числа Фибоначчи могут быть определены следую щим образом: первое число Ф и­ боначчи есть 1 , второе число Фибоначчи также е сть 1 , а каждое следую щее число Ф и­ боначчи е сть сумма двух предшествую щих чисел. Отсюда следует , что первые не­ сколько чисел последовательности Фибоначчи выглядят так: 1 , 1 , 2, 3 , 5, 8, 1 3 . Числа Фибоначчи пользуются особой любовью в с реде математиков, издается даже с пеци­ альный журнал, посвяще нный этим числам . Однако в данный моме нт нас интересует отнюдь не это . С ейчас мы напише м функцию , которая для заданного положительного целого значе ния n возвращает соответствующее число Фибоначчи. Прежде всего , посмотрим, как работает ре курс ия в данном случае: рекурс ия зада­ ется в виде простого определе ния . Если мы дадим этой функции имя Fibon acci ( ) , то Fibonacci ( n ) должна вернуть значение 1, если n равно 1 или 2 , и сумму Fibonacci ( n - 1 ) + Fibon acci ( n - 2 ) в любом другом случае: l o ng Fib onacci ( int n ) { if (n > 2 ) r e turn F ibonacci ( n- 1 ) + F ibonacci ( n - 2 ) ; el s e r e turn 1 ; Рекурсивная функция в С просто повторяет математическое определение ре кур­ сии. (Чтоб ы не усложнять проблему, функция не применяется к значе ниям n мень­ ше 1 . ) Эта функция использует двой'Ную рекурсию, то есть функция вызывает с ебя дваж­ ды . Это обстоятельство является источником ее слабости. Что б ы ис следо вать эту слабость, предположим, что вы ис пользуете вызов Fibon acci ( 4 О ) . Это будет пе рвый уровень рекурс ии , и он распределяет переменную с именем n. Затем выполняются два вызова функции Fibonacci ( ) , создавая еще две пе­ ременных с име не м n на втором уровне рекурсии. Каждый из этих двух вызовов гене­ рирует еще два вызова , которые , в свою очередь, требуют еще четырех переменных с именем n на третье м уровне рекурсии, что в итоге составляет с емь переме нных. На каждом уровне количество переме нных удваивается по сравне нию с предыдущим уровнем , то есть число переменных возрастает по экс поненте ! Как вы могли уб едить­ ся на примере с пшеничными зернами в главе 5, экспоненциальное возрастание быст­ ро приводит к огромным значе ниям. В расс матривае мом случае экс поненциальное 374 Гл ава 9 возрастание быстро приводит к тому, что компьютеру потребуются огромные объе мы памяти, что , скорее вс его , приведет к аварийному заве ршению программы. Как мы убедились, это экстре мальный пример , в то же время он показывает, что следует соблюдать осторожность при использовании ре курсии, ос об енно в тех случа­ ях, когда к предъявляются повышенные тре бования к эффе ктивности программы . Все фун к ц и и С с озданы н а од н ом и том же уровн е Каждая функция в програ м ме на С находится на одном уровне с остал ьными функция­ м и . Каждая из них может вызвать л юбую другую функцию ил и быть вызванной други ми функция м и . Эти функции в язы ке С отл ичаются от про цедур в таких язы ках програ м м и­ рования , как Pascal и M odula-2 , поскол ьку в них про цедуры могут быть вложенными друг в друга. П роцедуры в одном вложении не знают, какие процедуры находятся в друго м вложении. Я вляется ли функция main ( ) особой? И менно та к, она нескол ько отличается от ос­ тальных в том плане, что когда програ м ма составляется из нескольких функций, вы пол­ нение этой програ ммы начинается с первого оператора функции main ( ) , но этим и о г­ ран ичивается ее отличие от других функций. Функция main ( ) может вызы ват ь са ма се­ бя в рамках некото рой рекурсии ил и быть вызванной из других функций, хотя подобное встречается достаточно редко. К ом пиля ция п рогра мм и з двух ил и бол ь ш его ч и сл а и сх одны х файлов Простейший метод использования нескольких функций требует их размещения в од­ ном и том же файле . Затем выполняется компиляция этого файла, как если бы он со­ держал единственную функцию. Другие подходы к решению этой проблемы существен­ но зависят от конкретной системы, как показано в нескольких следую щих разделах. U nix В этом случае предполагается , что в с истеме Unix установлен стандартный для U nix компилятор С с е . Предположим, что fil e 1 . с и fil e 2 . с два файла , с одержа­ щие функции языка С. Тогда следую щая команда с компилирует об а файла и создаст исполняе мый файл с именем a . out: - се - fil e l . c fil e 2 . c Кроме того , при это м с озда ются два о бъектных файла с именами fi l e l . о и fil e 2 . о . Если позже вы изме ните с одержимое файла fil e 1 . с, оставив fil е 2 . с не из­ менным, вы можете откомпилировать первый файл и объединить е го с объектной версией второго файла , с помощью такой команды: се fil e l . c fil e 2 . o В системе U nix имеется команда make, которая автоматизирует управление много­ файловыми программами, но эта тема выходит за пределы данной книги. Функции 375 Lin ux В данном случае предполагается , что в с истеме Linux установлен компилятор gcc языка GNU С . Предположим, что fi l e l . с и fil e 2 . с - два файла , соде ржащие функ­ ции языка С. Т огда следую щая команда выполнит компиляцию обоих файлов и соз­ даст исполняе мый файл с име нем a . out: g c c fil e l . c fi l e 2 . c Кроме того, при этом с о здаются два объектных файла fil e l . о и fil e 2 . о . Если позже вы изме ните с одержимое файл fil e l . с , оставив файл f i l e 2 . с неизменным, вы можете откомпилировать первый файл и объединить его с объектной версией второ­ го файла , воспользовавшис ь следую щей командой: g c c fil e l . c fi l e 2 . o Компиляторы командной строки DOS Б ольшинство компиляторов командной строки операционной системы DOS рабо­ тают аналогично команде с е системы Unix. Единственное различие заклю чается в том, что объе ктные файлы получают рас ширение . obj вме сто расшире ния . о . Не ко­ торые компиляторы создают вместо объе ктных кодов промежуточные файлы с ас­ сембле рным или каким-то другим кодом. Компиляторы Windows и Maci ntosh Компиляторы опе рационных систе м Windows и Macintosh представляют с обой компиляторъ1, орие'/1,тирова11:нъ1е '/1,а проектъ1. Проект описывает ресурсы, используемые программой. Эти ресурсы вклю чают ваши файлы исходного кода. Если вы когда-либо работали с такими компиляторами, то , скорее вс его , должны были с о здавать проекты для выполне ния однофайловых программ. Что касается многофайловых программ, потребуется найти в меню команду, которая позволяет включить в проект файл ис­ ходного кода . Вы должны убедиться в том , что вс е ваши файлы исходных кодов (то есть файлы с расширением . с ) являются частью прое кта . Однако будьте осторожны, не помещайте в проект заголовочные файлы (файлы с расшире ние м . h) . Идея заклю­ чается в том , что прое кт управляет ис пользование м файлов исходного кода, а дирек­ тивы #include в файлах исходного кода управляют использованием заголовочных файлов . Использование за головоч ных фа йлов Если вы поме стили функцию main ( ) в один файл, а определе ния ваше й с обствен­ ной функции во второй файл, то первому файлу все е ще нужны прототипы функций. Вме сто того чтобы вводить их с клавиатуры каждый раз, когда вы используете файл с кодом функции, вы можете хранить прототипы функций в одном из заголовочных файлов . Име нно эту задачу выполняет стандартная библиотека С , разме щая , напри­ мер, прототипы функций ввода·вывода в файле s tdio . h, а математических функций в файле math . h. Вы можете поступить точно так же в отноше нии файлов с обстве нных функций. 376 Гл ава 9 Кроме того , вы часто будете ис пользовать препроцессор С для определения кон­ стант, используемых в программ е . Т акие определения возможны только для файла, соде ржаще го директивы #de fin e . Если вы поместите функции, используе мые в про­ грамме , в отдельные файлы , вам также придется обеспечить их доступность, указывая директиву #de fine для каждого файла . Наиб олее прямой путь заклю чается в повтор­ ном вводе дире ктив для каждого файла, но этот процесс тре бует б ольших време нных затрат и увеличивает ве роятность ошибок. Наряду с этим возникает проблема сопро­ вождения: если вы намерены изменить значе ние директивы #de fin e , то должны пом­ нить, что это нужно сделать для каждого файла . Более предпочтительное решение предусматривает размещение дире ктивы #de fine в заголовочном файле с последую­ щим ис пользованием дире ктивы #include для каждого файла исходного кода . Таким образом, хороший тон в программировании рекомендует размещать прото­ типы функций и объявлять константы в заголовочном файле . Рассмотрим соответст­ вую щий пример . Предположим, что вы управляете с етью из четырех отелей. В каж­ дом отеле действуют различные рас ценки платы за номер, в то же время плата за все номе ра в одном и том же отеле одинакова . Для постояльцев, которые зарезервирова­ ли за собой номера на не сколько суток, разме р платы за вторые сутки с оставляет 95 % от тарифа за первые сутки, третьи сутки оплачивается в размере 95% от платы за вто­ рые сутки и так далее. (Пусть вас не беспокоит вопрос о целесообразности упомянутой ценовой политики.) Вам нужна программа, которая позволила бы выбрать отель, ука­ зать срок проживания в этом отеле в сутках и рас считать суммарную стоимость пре­ бывания в нем в течение указанного с рока. Желательно наличие в программе меню , которое позволило бы вводить данные до тех пор , пока вы не пожелаете выйти. В листингах 9 . 9 , 9 . 1 0 и 9.1 1 показано, как можно решить эту задачу. Пе рвый лис­ тинг с одержит функцию main ( ) , которая определяет об щую организацию программы . Второй листинг содержит функции подде ржки, которые , как мы полагаем, хранятся в отдельном файле . И , наконец, в листинге 9 . 1 1 показан заголовочный файл, в котором соде ржатся определения констант и прототипы функций для всех исходных файлов программы . Напоминае м, что в с редах Unix и DOS двойные кавычки в директиве #incl ude " hotel s . h" указывают на то , что вклю чаемый файл хранится в текущем ра­ бочем каталоге (обычно в этом каталоге хранится исходный код ) . Листинг 9.9. Управляющий модуль usehote1 . c / * us ehotel . c - - прогр амма о пр едел ения класса г о с тиничных номер ов * / / * компилир уе тся вме сте с ли стинг ом 9 . 1 0 * / #incl ude <s tdio . h > #incl ude "hotel . h " / * определя е т констан ты , о бъявля е т функции * / int main ( vo i d ) { int nights ; do uЫ e hotel_r ate ; int code ; whi l e ( ( code = menu ( ) ) ! = QUI T ) { switch ( code ) { cas e 1 : hotel r a t e HOTE L l ; Функции br e a k ; hotel r a t e НОТЕ12 ; br e a k ,· cas e 3 hotel r a t e НОТЕLЗ ; br e a k ; hotel r a t e НОТЕ 1 4 ; cas e 4 br e a k ; de fault : hotel r a t e о .о; printf ( " Oшибк a ! \ n " ) ; br e a k ; cas e 2 - - night s ge tnight s ( ) ; s howp r i c e ( hotel_r ate , nights ) ; рrintf ( " Благ о д арим з а исполь зов ание и желаем усп ехов . " ) ; r e turn О ; листинг 9.1 0. Модуль функций поддержки hote l . c / * hotel . c - - функции упр авл ения о телем * / #incl ude <s tdio . h > #incl ude "hotel . h " int menu ( vo i d ) { int code , s tatus ; p r i n t f ( " \ n % s % s \ n " , S TARS , STARS ) ; printf ( " Bв eди т e число , со ответс твующе е выбр анному отелю : \ n " ) ; printf ( " l ) F a i r field Arms 2 ) Hotel Olympi c \ n " ) ; printf ( " З ) Chertworthy Pl a z a 4 ) T h e Sto ckton\ n " ) ; printf ( " 5 ) выход\ n " ) ; printf ( " % s % s \ n " , STARS , STARS ) ; whi l e ( ( s tatus s ca n f ( " % d " , & code ) ) ! = 1 1 1 ( code < 1 1 1 code > 5 ) ) i f ( s tatus ! = 1 ) s c an f ( " % * s " ) ; рrint f ( " Пожалуйст а , вв едите целое число о т 1 до 5 . \ n " ) ; r e turn code ; int g etnights ( vo i d ) { int nights ; printf ( " Ha ско л ь ко суток вы р е з ервир у е т е номер ? " ) ; whi l e ( s can f ( " % d" , & nights ) ! = 1 ) { s can f ( " % * s " ) ; рrint f ( " Пожалуйст а , вв едите целое число , тако е как 2 . \ n " ) ; r e turn nights ; 377 378 Гл ава 9 void s howp r i c e ( do uЫ e r ate , i nt nights ) { int n ; do uЫ e total = О . О ; do uЫ e f a ctor = 1 . 0 ; fo r ( n = 1 ; n <= nights ; n++ , f a ctor * = DI S COUNT ) total += r a t e * f a ctor ; printf ( " Oбщaя с тоимо сть с о с тавл я е т $ % 0 . 2 f . \ n " , total ) ; листинг 9.1 1 . ЗагоповочныА файп hote1 . h / * hotel . h - - кон с танты и о бъявления для прогр аммы hotel . c * / #de fi ne QUI T 5 #de fi ne HOT E L l 80 .00 #de fi ne НОТ Е12 125 . 0 0 #de fi ne НОТ Е13 155 . 0 0 #de fi ne НОТ Е 1 4 200 . 0 0 #de fi ne DI S COUNT 0 . 95 #de fi ne STARS " * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * " 1 1 показыв а е т спи сок в о зможных в ариантов int menu ( vo i d ) ; 1 1 во звр аща е т колич е ство суток , на ко тор о е р е з ервир уе тся номер int g etnights ( vo i d ) ; 1 1 вычисля е т с тоимо с т ь с уч е том тарифов и колич е с тв а суто к 1 1 и о то бр ажает р е зуль т а т вычислений void s howp r i c e ( do uЫ e r ate , i nt ni ghts ) ; Ниже показан пример выполнен ия : ******************************************************************** Вв едите число , соо тв етств ующе е выбр анному о телю : 1 ) Fair f i eld Arms 2 ) Hotel Olymp i c 3 ) Chertworthy Pl a z a 4 ) T h e S to c kton 5) выход ******************************************************************** 3 На сколь ко суток вы р е з ер вируе т е номер ? 1 Общая с тоимо с т ь сос тавля е т $ 1 5 5 . 0 0 . ******************************************************************** Вв едите число , соо тв етств ующе е выбр анному о телю : 1 ) Fair f i eld Arms 2 ) Hotel Olymp i c 3 ) Chertworthy Pl a z a 4 ) T h e S to c kton 5) выход ******************************************************************** 4 На сколь ко суток вы р е з ер вируе т е номер ? 3 Общая с тоимо с т ь сос тавля е т $ 5 7 0 . 5 0 . Функции 379 ******************************************************************** Вв едите число , соо тв е тствующе е выбр анному о телю : 1 ) F air field Arms 2 ) Hotel Olymp i c 4 ) T h e S to ckto n 3 ) Chertworthy Pl a z a 5 ) выход ******************************************************************** 5 Бл агодарим з а исполь зов ание и желаем успехо в . Между прочим, для с амой этой программы характерны не которые инте рес ные особенности. В частности, функция menu ( ) и getnight s ( ) пропускают нецифровые данны е , и с этой целью они проверяют возвращаемое значение функции s ca n f ( ) и выполняют вызов s ca n f ( " % * s " ) с те м, чтобы пропустить следую щий пробел. О брати­ те внимание на то , как приведе нный ниже фрагмент функции menu ( ) осуществляет прове рку на наличие как не цифровых данных, так и числовых данных, выходящих за допустимые пределы : whi l e ( ( s tatus s ca n f ( " % d " , & code ) ) ( code < 1 1 1 code > 5 ) ) != 1 1 1 В этом фрагменте кода используется особенность языка С , обеспечивающая вычис· ление логических выражений слева направо и завершение вычислений в тот момент, когда становится ясно, что выражение принимает ложное значение . В рассматриваемом примере значение переменной проверяется только после того , как подтвердится тот факт, что функции s can f ( ) удалось прочитать целочисленное значение. Назначение отдельных задач отдельным функциям спос обствует улучшениям про­ граммы . При первом проходе функции menu ( ) или getnights ( ) можно ис пользовать простую функцию s can f ( ) без добавленного средства проверки допустимости данных. Затем, после того , как основная версия заработает, можно приступать к раб оте по со­ верше нствованию каждого модуля . Поиск адресов : операция & Одним из наиболее важных понятий языка С (иногда самым трудным для понима­ ния ) является указателъ, представляю щий собой адрес , по которому хранится с оответ­ ствую щая переме нная . В ы уже видели, что функция s ca n f ( ) использует адре са аргу­ ментов. В об ще м случае любая функция С , которая вносит изме не ния в значе ния вы­ зывающей функции без использования возвращае мых значений, использует адрес а . Далее м ы рас смотрим функции, использую щие адреса, и начне м с унарной операции &. (В следую ще й главе мы продолжим исследование указателе й и рас смотрим, как они используются . ) Унарная операция & выдает адре с , п о которому хранится соответствую щая пе ре­ менная . Если pooh является именем пе ременной, то &pooh это адре с переменной. Вы можете представлять адре с как некоторую ячейку в памяти. Предположим, что име ет­ ся следую щий оператор: - pooh = 2 4 ; Предположим , что адре с , по которому хранится переме нная pooh, выглядит как О В 7 6 (адре са в пе рсональных компьютерах часто задаются в ше стнадцате ричной форм е ) . 380 Гл ава 9 Тогда оператор printf ( " % d %p\ n " , pooh , &p ooh) ; генерирует следую щий ре зультат ( %р - спецификатор вывода адрес а) : 2 4 ОВ7 6 Код в листинге 9 . 1 2 использует эту опе рацию , чтоб ы узнать, где хранятся перемен­ ные с одним и те м же имене м , но ис пользуемые в различных функциях. Листинг 9.1 2. Программа 1occheck. c / * lo ccheck . c - - проверка с целью выя сн е ния , г де хр аня тся пер еменные • / #incl ude <s tdio . h > / * о бъявлени е функции • / void mi kado ( int ) ; int main ( vo i d ) { 2 , b ah 5; / * пер еменные , локальные в функции main ( ) • / int pooh = = printf ( " В функции main ( ) pooh pooh , &poo h ) ; printf ( " В функции main ( ) b ah b ah , &bah) ; mi kado ( p o o h ) ; % d и &pooh = = % d и & b ah = = %p\ n " , %p\n" , r e turn О ; / * о бъя вление функции • / void mi kado ( int b ah ) { int pooh 10; / * переменные , локаль ные в функции mi kado ( ) • / = printf ( " В функции mi kado ( ) pooh printf ( " В функции mi kado ( ) b ah = = % d и &pooh % p \ n " , pooh , &poo h ) ; % d и &bah %p\ n " , b a h , &b a h ) ; = = Программа в листинге 9 . 1 2 использует формат %р стандарта ANSI С с целью вывода адре с а . Для этого небольшого приме ра наша программа генерирует следую щие вы­ ходные данные : В функции main ( ) p o o h 2 и &pooh Ox0 0 12 f f 4 8 В функции main ( ) b ah 5 и &bah Ox0 0 12 ff4 4 В функции mi kado ( ) pooh 1 0 и &pooh O x 0 0 1 2 f f3 4 В функции mi kado ( ) b ah 2 и &b ah Ox0 0 12 f f 4 0 = = = = = = = = Способ представле ния адре с а с использование м спецификатора % р отличается в различных системах. В то же вре мя многие ре ализации, такие как используе мые в данном приме ре , отображают адрес в шестнадцатеричной форме . О чем свидетельствуют эти выходные данные? Во-первых, обе переменных pooh имеют различные адреса. То же можно сказать и о двух переменных b ah. Следовательно , как говорилось выш е , компьютеры рассматривают их как четыре различных перемен­ ных. Во-вторых, вызов функции mi kado (pooh) передает значение ( 2 ) фактического ар­ гумента (значение pooh из main ( ) ) формальному аргументу (значение b ah из mi kado ( ) ) . Обратите внимание на то , что было передано только значение . Обе эти переменные (pooh функции main ( ) и b ah функции mi kado ( ) ) сохраняют свою идентичность. Функции 3 81 Второй вопрос состоит в том, что это условие выполняется не для всех языков про­ граммирования . В языке FORTRAN, например, подпрограмма может затронуть ис­ ходную пе ременную в вызывающей функции. Переменная подпрограммы может иметь другое имя , но тот же адрес . Язык С этого не допус кает. Каждая функция ис­ пользует с вои собственные пе ременны е . Такой подход предпочтительне е , поскольку он пре пятствует непонятным изме не ниям, производимым некоторыми побочными эффектами вызываю щей функции. Тем не мене е , он также может породить опреде­ ленные трудности, как показано в следую щем разделе . Из м ен ен и е перем ен ны х в вызываю щей фун кции Иногда возникает не обходимость изме нить значения пе ременных других функций. Наприме р, распростране нная задача алгоритмов сортировки связана с обменом зна­ че ниями двух переменных. Предположим , что суще ствуют две переменные с име нами х и у , и вы хотите , чтобы они обменялис ь значениями. Простая последовательность операторов х = у; х; у не достигает цели, поскольку в тот моме нт , когда управле ние будет пе редано второй строке , исходное значение пе ременной х уже будет заменено исходным значением переменной у . Потре буется дополнительный оператор для време нного запоминания исходного значения х. temp = х ; х у; у = temp ; Теперь, когда этот метод заработал, вы можете положить е го в основу соответст­ вую щей функции и подготовить драйве р для ее тестирования . Чтоб ы выяснить, какая переме нная принадле жит функции main ( ) , а какая inter change ( ) , в программе , представленной в листинге 9 . 1 3 , пе ре менные х и у используются в main ( ) , а u и v в inter change ( ) . - - листинг 9.1 3 . Программа swapl . c / * swap l . c - - пер в ая попытка создания функции о бмен а знач ениями * / #incl ude <s tdio . h > void inter change ( int u , int v ) ; / * о бъяв ление функции * / int main ( vo i d ) { int х = 5 , у = 1 0 ; рrintf ( " Первонач ал ь н о х = % d и у = % d . \ n " , х , inter change ( x , у ) ; printf ( " T eпepь х = % d и у = % d . \ n " , х , у ) ; r e turn О ; у) ; 382 Гл ава 9 void inter change ( int u , int v ) { int temp ; / * о бъя влени е функции * / temp = и ; v; и v = temp ; При выполнении этой программы получены следую щие результаты Первонач ально х = 5 и у = 1 0 . Т е перь х = 5 и у = 1 0 . Ну и дела ! Значения не поменялись! Чтобы выяс нить, в чем дело , поместим в функцию inter change ( ) операторы печати (см. листинг 9 . 1 4 ) . листинг 9.1 4. Программа swap2 . с / * swap 2 . c - - пер е смо тр енный в ариант прогр аммы swap l . c * / #incl ude <s tdio . h > void inter change ( int u , int v ) ; int main ( vo i d ) { int х = 5 , у = 1 0 ; рrintf ( " Первонач ал ь н о х %d и у = %d . \n" , х , inter change ( x , у ) ; printf ( " T eпepь х = % d и у = % d . \ n " , х , у ) ; у) ; r e turn О ; void inter change ( int u , int v ) { int temp ; %d и v рrintf ( " Первонач ал ь н о и %d . \n" , и , v ) ; temp = и ; и = v; v = temp ; printf ( " T eпepь и = % d и v %d. \n" , u , v) ; В ыполнение этой программы привело к получению следую щих выходных данных: Первонач ально х 5 и у 10 . Первонач ально и = 5 и v = 1 0 . Т е перь и = 1 0 и v = 5 . Т е перь х = 5 и у = 1 0 . В функции inter chang e ( ) ошибок нет - она меняет ме стами значе ния и и v . Про· блема закл ю чается в пе редаче результатов в функцию main ( ) . Как уже отме чалось выш е , функция inter change ( ) ис пользует другие переме нные из функции main ( ) , так что обмен значениями между переменными и и v никак не сказывается на пе ременных х и у! Функции 3 83 Может б ыть, следует воспользоваться оператором r eturn? Итак, вы можете за­ вершить выполнение функции inter change ( ) строкой r e turn ( u ) ; а зате м завершить вызов в функции main ( ) следую щим оператором: х = inter chang e ( x , y ) ; Такая замена приводит к тому, что х получает новое значение , но у при этом оста­ ется неизменной. С помощью опе ратора r e turn вы можете пе ре слать обратно в вызы­ ваю щую функцию вс его лишь одно значение , а вам надо передать два значе ния . И все­ таки их можно передать! Нужно только воспользоваться указателями. Ука зател и : п ервое з нако м ство Указатели? А что это такое? По сути дела , ухазатмъ представляет с об ой перемен­ ную (или, в обще м случае , объект данных ) , значением которой является адрес . Подоб­ но тому, как пе ременная типа char в каче стве значения имеет с имвол, а переме нная типа int - целое число , указатель принимает значение адре с а . Указатели име ют мно­ гочисленные применения в языке С ; в данной главе будет показано , как и почему они используются в качестве параметров функций. Если конкретной пере менной типа указателя прис воить имя p t r , можно будет пользоваться такими операторами, как показанный ниже: ptr = &pooh ; / * прис в аив а е т адр е с переменной pooh переменной ptr * / Мы говорим, что переменная p tr "указывает на" pooh. Различие между ptr и &pooh состоит в том, что ptr является пе ременной, а &pooh - константой. При необходимо­ сти вы можете сделать так, чтобы ptr указывал в любое место памяти: ptr = &b ah ; / * пер еменная ptr указыв а е т на b ah вме сто pooh * / В данном случае значе нием пе ременной ptr является адрес переменной b ah. Чтобы создать переме нную-указатель, вы должны объявить ее тип. Предположим, что вы хотите объявить пе ременную ptr с таким расчето м , чтобы она могла хранить адре с значе ния типа int . Чтобы сделать такое объявление , вы должны ис пользовать новую операцию . Рассмотрим эту операцию . Операция разыменования: * Предположим , что вы знаете , что ptr указывает на переменную b ah, как показано ниже: ptr = &b ah ; Теперь вы можете воспользоваться операцией разыменования * (она еще называ­ ется операцие й снятия косвенности ) , чтобы найти значе ние , сохраняемое в перемен­ ной b a h (не путайте эту унарную операцию с бинарной операцией умножения * ) : val = *p tr ; / * опред елить , н а к акое з н ач ени е указыв а е т ука з а тель p tr * / О пе раторы p tr = &bah ; и val = *ptr ; , взятые вместе, эквивалентны следую щему оператору: val = b a h ; 384 Гл ава 9 Использование операции адресации и разыменования представляют с об ой кос­ венный путь достижения этого ре зультата , отсюда и происходит название " операция разыме нования" . Свод к а: оп ерац и и с ук азателям и О пера ция адресации : & Комментарии о бще го характера: Знак &, за которы м следует имя переменной , представляет адрес этой пе ременно й. П ример: &nur s e явпяется адресом переменной nur s e . О ператор разь 1 менован ия : * Комментарии о бще го характера: Есл и за знаком * следует имя ип и адрес , то он пе редает значение, которое х ранится по указанно му адресу. П ример: nur s e = 2 2 ; ptr &nur s e ; val = * p tr ; / * указатель на переменную nur s e * / / * присв аив а е т пер еме нной v al з н ач е ние , хр анящ е е ся в яч ейк е ptr * / В конечном итоге переменная val получает значение 2 2 . Объявление указателей В ы уже знаете, как объявлять пере менные типа int и других фундаментальных ти­ пов. Как объявляются переме нные типа указатель? В ы , должно быть, подумали, чго эта форма должна иметь вид: pointer ptr ; / * Непр авил ь н ый спо соб о бъявля ть ука з а тели * / Поче му не может? Поскольку недостаточно объявить, что та или иная переме нная является указателем. Вы также должны указать вид переменной, на которую указывает указатель. Причина состоит в том, что различные типы переме нных требуют для сво­ его размещения различные объемы памяти, а не которые опе рации над указателями тре буют сведе ний о разме ре памяти, отведенной под пере менные . Наряду с этим, про­ грамма должна знать, какой вид данных хранится по указанному адресу. Типы long и float могут использовать один и тот же объе м данных, однако способы хранения этих чис ел суще ственно различаются . Вот как выполняется объявление указателей: int * pi ; char * р е ; float * p f , * pg ; / * pi - ука з а тель н а целочисле нную п ер еменную * / / * р е - ука з а тель н а симв оль ную переменную * / / * p f , pg - указатели на переменные с плавающей запятой* / С пецификация типа задает тип переменной, на которую указывает указатель, а зве здочка ( * ) означает, что пе ременная сама по себе является указателем. О бъявление int * pi ; говорит о том, чго значение *pi имеет тип int (рис . 9 .5 ) . Функции 3 85 Операции над адресами &feet 'surunas s 52009 52000 52001 52002 52ооз 52004 52005 . . . 52010 -- Машинные адреса 1 Байт 1 Байт !Байт 1 Байт !Байт 1 Байт 1 Байт 1 Байт 1 Байт 1 Байт 1 Байт G ch 777 6 feet 30 1 94 2 2 . 0 1 Sxlo date surunass Значение в памяти int *pfeet ; float *psun ; Объявление указателя pfeet &feet ; psun &sunmass ; -- Пр исваивание значений *pfeet *psun тип а указатель (адресов ) L Операции __J ----i____ П рисваивание значений , хранящихся разыменования по конкретным адресам = = Р и с . 9 . 5 . Обмвде'/1,ие и исполъзова'/1,ие указателей Пробел между знаком " и именем указателя не обязателе н. Часто программисты используют пробел при объявле нии и опускают его при разыменовании переме нной. Значе ние ( " р е ) , на которое указывает указатель р е , имеет тип char . Что можно сказать о самом указателе р е? Мы описываем его как имею щий тип "указателя на зна­ че ние типа char". Значение ре - это адрес, который имеет внутреннее представление во многих системах в виде целого числа без знака . Однако вы не должны расс матри­ вать указатель как целочисленное значение . Существуют операции, которые можно выполнять над целыми числами, но нельзя над указателями и наоборот. Например , вы можете умножать одно целое число на другое , но вы не можете умножать указатели. Следовательно, указатель и в самом деле представляет собой новый тип, отличный от целочисленного . Таким образом, как уже было указано выше , стандарт ANSI С преду­ сматривает форму % р специально для указателей. Использование указателей для обмена да н н ы м и между фун кциями Мы лишь слегка коснулись многообразного и удивительного мира указателей, в т о же время нас , пре жде всего , инте ресует использование указателей для решения про­ блемы обмена данными. В листинге 9 . 1 5 представлена программа , которая использует указатели с тем, чтобы заставить раб отать функцию i nt e r ch ange ( ) . О знакомимся с этой программой, выполним ее и разбере мся , как она работает. листинг 9.1 5. Программа swapЗ . с / " swap З . c -- исполь зов ание указат елей для о бмена з нач ениями пер еменных " / #incl ude <s tdio . h > void inter change ( int " u , int " v ) ; int main ( vo i d ) { int х = 5 , у = 1 0 ; рrintf ( " Первонач ал ь н о х inter change ( &x , &у ) ; %d и у %d . \n" , х , у) ; / " пер едач а адр е сов в функцию " / 386 Гл ава 9 printf ( " T eпepь х = % d и у r e turn О ; %d. \n" , х , у) ; void inter change ( int * и , int * v ) { int temp ; temp = * и ; / * п ер еменная t emp получ а е т знач е ние , на которое указыв а е т u* / *u *v; * v = temp ; Будет ли программа из листинга 9 . 1 5 раб отать после сборки? Первонач ально х 5 и у = 10 . Т е перь х = 1 0 и у = 5 . Да, она работает. Теперь посмотрим, как раб отает программа из листинга 9 . 1 5 . Во·пе рвых, вызов функции имеет вид: inter change ( &x , &у ) ; Вместо пе редачи зна'Ченuй переме нных х и у в функцию передаются их адреса. Это означает, что формальные аргуме нты и и v, появля ю щиеся в прототипе и определе­ нии функции inter change ( ) , ис пользуют адреса в качестве с воих значений. В силу этого они должны быть объявлены как указатели. Поскольку х и у - целые числа , и и v - указатели на целые значе ния , они должны быть объявлены следую щим образом: void inter change (int * и, int * v ) Далее , в теле функции с одержится объявление int temp ; которое отражает потребность во временной памяти. Чтобы с охранить значение х в переменной temp , воспользуйтес ь оператором temp = * и ; Напомним, что и имеет значение &х, так что и указывает на х . Это означает, что * и предоставляет вам значение х, именно это и б ыло нужно . Не следует использовать та­ кой оператор temp = и ; / * Непр авил ь но * / так как в этом случае переменной temp присваивается адрес переменной х, а не ее значе­ ние , а наша задача состоит в том, чтобы осуществить обмен значениями, а не адресами. Аналогично , чтобы назначить переменной значение х, воспользуйтесь оператором *u = *v; который, по сути дела , дает следую щий результат: х = у; Подведем итоги этого примера. Нам нужна была функция, которая меняет значе­ ния пе ременных х и у. Передавая функции адреса х и у, мы предоставляем функции inter change ( ) доступ к этим пе ременным. Ис пользуя указатели и опе рацию * , эта функция может проверить, какие значения хранятся в этих ячейках, и поменять их. Функции 3 87 В прототипе ANSI вы можете опустить имена пе ременных. Т огда объявление в прототипе принимает следую щий вид : void inter change ( int * , int * ) ; В обще м случае вы можете передать в функцию два вида информации о перемен­ ной. Если вызов имеет вид : function l ( х ) ; то вы передаете значения пере менной х. Если вы вызов имеет вид functi o n 2 ( &x ) ; вы пе редаете адрес пере менной х. Первая форма требует , чтобы определение функ­ ции включало формальный аргуме нт того же типа , что и тип пе ременной х: int function l ( int num) Вторая форма требует, чтобы определение функции включало формальный пара­ метр , который является указателем на правильный тип: int function2 ( int * ptr ) Если функции нужно не которое значение для с о ответствую щих вычисле ний или де йствий, ис пользуйте первую форму. Если тре буется , чтобы вызываемая функция из· менила значения пе реме нных вызывающей функции, вос пользуйте сь второй формой. Вы выполняли подобного рода операции с помощью функции s c an f ( ) . Когда вы хоти­ те прочитать значение той или иной переменной (например , переменной num) , вы используете вызов s can f ( " % d " , & num) . Эта функция читает значение , а зате м исполь· зует указанный адре с для сохране ния значения . Указатели позволяют обойти тот факт, что переме нные функции inter change ( ) являются локальными. О ни позволяют этой функции изменять то , что хранится внут­ ри main ( ) . Пользователи, работаю щие с языками Pascal и Modula-2 , могут обратить внимание на тот факт, что пе рвая форма аналогична параметру-значению в Pascal, а вторая форма подобна (но не иде нтична ) параметру-пере менной того же языка . Поль­ зователям , работаю щим с языком BASIC, все эти построе ния могут показаться не­ сколько искус ственными. Если материал, изложе нный в данном разделе , покажется не понятным, не сомневайте с ь в том, что после непродолжительной практики исполь­ зование указателей станет с амым обычным делом, простым и удобным (рис . 9 .6 ) . 52000 52001 52002 52ооз 52004 52005 . . . 52009 52010 --- М ашинный адрес I Б• l �� l �� I Б• l�� 1 �� 1 �� 1 �� I Б• l �� I Б• ЗO G 7776 1 94 2 2 . O l Sxl O ch feet date sun111a s s &ch = 52 0 0 0 &feet = 52 0 0 1 &date = 52 0 0 3 &sunmass = 52 0 0 5 &quit = 52 0 0 9 Значение в памяти Для размещения в п амяти переменной с плава ющей запятой отводятся 4 байта L Адресная операция Р и с. 9 .6. Имена, адреса и З'l/.а'Ч,СНUЯ в системе байтовой адресшции, такой как псрсон,алън·ый компъютеjJ IBM РС 388 Гл ава 9 Перем ен н ы е: и м ена. ад реса и зн ачен и я Изучение указателей было прервано на анал изе отношений между именами, адресами и значениями переменных. Сейчас изучение будет продолжено. Когда вы пишете програ м му, вы сч итаете, что переменная имеет два атрибута: имя и значение. (Существуют также и другие атрибуты , в том числе тип, но это отдел ьная те­ ма.) После того, как програ м ма будет отко мпил и рована и загружена, компьютер воспри­ нимает ту же пере менную как сущность, име ющую два аргумента: адрес и значение . Адрес представляет собой компьютерную версию имен и . В о мно гих языках программирования вопросы адресации реша ются ком пьютером, и к этому процессу программист не имеет доступа. Однако в языке С вы получаете доступ к адреса м с помощью о пе рации & . На пример, & b arn - это адрес переменной barn. Вы можете получить значение и з имен и , для этого достаточно просто вос пол ьзоваться именем. Н апример, print f ( " % d\ n " , b a r n ) вы водит значение пе ременно й barn. Вы можете получить значение переменной п о е е адресу, воспол ьзовавшись операцией * . В о пе раторе pbarn = &barn ; конструкция *pb ar n - это значение, хранимое по адре­ су &barn. Короче говоря, обычная пере менная в качестве первичной кол ичественной вел ичины испол ьзует значение, в то время как адрес через опера цию & становится производной вел ичино й . Переменная типа указател ь делает адрес первичной кол ичественной вел и­ чиной, а производно й вел ичино й с помощью о пе рации * становится адрес . И хотя ради любопытства вы можете распечаты ват ь адреса, опе рация & предназначена в основно м не для этого . Гораздо важнее, что использо вание операций &, * и указате­ лей позволяет символ ически ман ипул ировать адресами и их содерж и м ы м , как это и ме­ ет место в программе swap З . с (листинг 9. 1 5). Св одка: Фvн к ци и Форма: Типичное определение функции в стандарте ANSI С и меет следующую фо рму: имя ( список о бъявлений аргументов ) тело функции Список объя влен и й а ргументо в - это список переменных, в котором имена пере мен­ ных отделя ются друг от друга запят ы м и . Переменные, отличные от параметров функ­ ций, объявл я ются в теле функции, ограниченном фигурными скобка ми. П ример: int di f f ( int х , int у ) { int z ; z = х у; r etur n z ; - 1 1 стандар т ANS I С 1 1 нач ало тела функции 1 1 о бъявление локаль ных пер еме нных 1 1 возвращени е значений 1 1 коне ц тела функции Передача значени й : А ргументы испол ьзуются для передачи значений из вызы вающе й функции в вызы вае­ мую. Если переменные а и Ь имеют значения , соответственно, 5 и 2 , вызов с = di f f ( а , Ь ) ; Ф ункции 3 89 передает 5 и 2 переменным х и у . Значения 5 и 2 назы ваются фактическим и аргумен­ тами, а переменные х и у функции di f f ( ) - формальным и параметрами. Кл ючевое слово r etur n передает одно значение из функции в вызы вающую функцию. В рассмат­ ри ваемом примере с получает значение переменной z, кото рое равно 3 . Обычно функ­ ция не вл ияет на переменную вызыва ющей функци и . Чтобы непос редственно менять значения переменных вызыва ющей функции, испол ьзуйте указател и в качестве а ргу­ ментов. Это может оказаться необходимым, есл и вы хотите возвратить бол ьше одного значения в вызывающую функцию. В озвра щае мы й тип функ ц и и: Возвращаемый тип функции указы вает тип значения , возвращаемо го функцией. Есл и возвращаемое значение имеет тип, отл ичный от объя вленного возвращаемого типа , это значение преобразуется к объя вленному типу. П ример: int main ( void) { douЫ e q , х , du f f ( ) ; int n ; / * о бъя вления в вызывающей функции * / q = du f f ( х , n ) ; do uЫ e du f f ( u , k ) do uЫ e u ; int k ; / * о бъя вления в опр еделении функции * / douЫ e tor ; r etur n tor ; / * возврат з н ач ения типа douЫ e * / К л юч евые п онятия Если вы хотите успешно и эффективно программировать в языке С , то обязательно должны знать, как работают функции. О чень полезно, и даже не обходимо, организо­ вывать крупные программы в виде с овокупности нескольких функций. Если вы будете следовать правилу, назначаю щему одну функцию одной задаче, вашу программу легче будет понять и отладить. Выяс ните во всех подробностях, как функции обме ниваются информацией, то есть убедитесь, что вы понимаете , как работают аргументы и воз­ вращаемые значе ния функции. Кроме того , убедитес ь в том, насколько параметры функции и другие локальные пе ре менные приватны для функции, то есть объявление двух пе ременных под одним и тем же именем в разных функциях создает две разных пе реме нных. Наряду с этим дна функция не имеет пря мого доступа к пе ременным , объявленным в другой функции . Такой о граниченный доступ позволяет сохранить цело стность данных. В то же вре мя , е сли вам нужно, чтобы одна функция имела доступ к данным другой функции, вы можете использовать аргуме нты функции с ти­ по м указатель. 39 0 Гл ава 9 Р езюм е Используйте функции как строительные блоки крупных программ. Каждая функ­ ция должна ре шать единственную , четко определенную задачу. Воспользуйтес ь аргу­ ментами для пе редачи значений функции, а для возвращения результата выполнения функции применяйте клю чевое слово r etur n . Если функция возвращает значение , тип которого не i nt, вы должны опис ать тип функции в описании этой функции в разделе объявлений вызывающей функции. Если вы хотите , чтобы функция оказывала воздей­ ствие на переменные вызывающей функции, используйте адре са и указатели. Стандарт ANSI С предлагает прототипирован,ие фуюс'Ций, мощное усовершенствова­ ние языка С, позволяющее компиляторам прове рять, правильно ли указано количе ст­ во и типы аргументов в вызове функции. Функция С способна вызывать сама себя, это свойство называется рекурсией. Некоторые задачи программирования можно ре шать методом ре курсии, но этот метод может оказаться неэффективным с точки зрения использования памяти и време ни. В оп росы для сам оконтроля 1 . В чем закл ю чается отличие между фактичес ким аргументом и формальным па­ раметром? 2. Напишите заголовок функции на ANSI С для опис анных ниже функций. Обра­ тите внимание , что речь сейчас идет о заголовках, а не о теле функций. а. Функция donut ( ) принимает аргумент типа int и выводит на печать количе­ ство нуле й, равное значе нию этого аргумента. б . Функция gear ( ) принимает два аргумента типа int и возвращает значение типа int. в. Функция stuff _i t ( ) принимает значение тип douЫ e и адрес пере менной типа do uЫ e и запоминает первое значение в заданной яче йке . 3 . Напишите заголовок функции на ANSI С для опис анных ниже функций. Обра­ тите внимание на то , что речь сейчас идет о заголовках, а не о теле функций. а. Функция n_to _ char ( ) принимает аргумент типа int и возвращает значение типа ch ar . б . Функция di gits ( ) принимает значение тип douЫ e и аргумент типа int и возвращает значе ние типа int . в. Функция r andom ( ) не имеет аргументов и возвращает значе ние типа int . 4. Создайте функцию , которая возвращает сумму двух целых чисел. 5 . Что изменится и изменится ли что-нибудь, если вы потребуете , чтобы функция, описанная в пункте 4, с кладывала вме сто двух целых чисел два числа типа douЫ e ? 6 . Создайте функцию с именем a l t e r ( ) , которая принимает две переменные х и у типа int и присваивает этим переменным, с о ответственно , значе ния их суммы и разности. 7. Нет ли ошиб ок в следую щем определе нии функции? Функции 391 void s al ami ( num) { int num , count ; for ( count = 1 ; count <= num ; num++ ) print f ( " О s al ami mio ! \ n " ) ; 8 . Напишите функцию , которая возвращает наибольший и з трех аргуме нтов . 9 . Заданы следую щие выходные данны е : Выбери те один из следующих в арианто в : 1 ) копировать ф айлы 2 ) переме сти т ь ф айлы 4 ) выйти из прогр аммы 3 ) удалить ф айлы Вв еди т е номер выбранног о в ари анта : а. Напишите функцию , которая выводит на экран меню из четырех пронуме­ рованных вариантов выбора и предлагает вам выбрать один из них. (В ыход­ ные данные должны иметь показанный выше вид . ) б . Напишите функцию , которая имеет два аргумента типа int : значе ния верх­ ней и нижней границы . Функция должна читать целое число из входных данных. Если это число выходит за пределы указанных границ, функция снова должна выве сти меню на экран (воспользовавшись функцие й из пунк­ та а) выш е ) с целью выдачи повторного приглашения пользователю ввести новое значение . Если будет введено новое значе ние , попадающее в диапа­ зон, заданный граничными значениями, функция должна возвратить это значение . в. Напишите минимальную программу, используя функции из пунктов а ) и б ) данного вопроса. Под минималъной программой м ы подразуме вае м то , что она по сути дела не должна выполнять действия, объявленные в меню , дос­ таточно предложить пользователю сделать выбор в меню и получить пра­ вильный ответ . Уп ражн ен и я по програм м и рован и ю 1 . Напишите функцию с именем min ( х , у ) , которая возвращает меньшее из двух зна­ чений типа douЫe . Протестируйте эту функцию с помощью простого драйвера . 2 . Напишите функцию с именем chline ( ch , i , j ) , которая печатает требуе мый символ в столб цах от i до j . Протестируйте эту функцию с помощью простого драйве ра . 3 . Напишите функцию , которая принимает три аргумента : символ и два целых числа. С имвол должен быть рас печатан в строке. Первое целое значение опре­ деляет, с колько раз символ печатается в строке, а второе целое число задает, сколько строк должно быть распечатано . Напишите программу, которая ис­ пользует эту функцию . 4. Среднее гармониче ское значе ние двух чисел может быть получе но , е сли взять об ратные величины этих двух чис ел, вычислить их средне е значение и взять его об ратную величину. Напишите функцию , которая берет два аргумента типа douЫ e и возвращает с реднее гармониче ское значение этих двух чисел. 392 Гл ава 9 5 . Напишите и протестируйте функцию с именем l arger_o f ( ) , которая заменяет содержимое двух пе ременных типа douЫ e большим из двух этих значений. На­ приме р, функция l arger_o f ( x , y ) присвоит обеим переме нным х и у большее из двух этих значений. 6. Напишите программу, которая считывает символы из стандартного устройства ввода до тех пор, пока не встретится символ конца файла . Программа должна сообщить, когда вводимый символ является буквой. Когда вводимый символ есть буква , она сообщает также ее порядковый номер в алфавите . Наприме р, буквы с и С идут под номером 3. Вклю чите также функцию , которая принимает символ в каче стве аргумента и возвращает его номер в алфавите , если этот сим­ вол является буквой, и 1 в противном случае. 7. В главе 6 рас сматривается функция power ( ) (листинг 6.20 ) , которая возвращает результат возведения чисел типа douЫ е в положительную целую степень. Вне­ сите в эту функцию такие изменения, которые позволили бы использовать ее для возведения чисел в отрицательные степени. Наряду с этим, внесите в эту функцию такие изме нения , которые при возведении О в лю бую сте пень давали бы в ре зультате О, а при возведении любого числа в степе нь О выдавали бы в ка­ че стве ре зультата 1 . В ос пользуйтесь циклом. Протестируйте эту функцию на примере конкретной программы . 8 . Еще раз выполните упражне ние 7, но с использование м рекурс ивной функции. 9 . Раширьте функцию to_Ьinary ( ) , представле нную в листинге 9 . 8 , до функции to_b a s e_n ( ) , которая принимает второй аргумент в диапазоне от 2 до 1 0 . О на должна выводить на печать число , которое является ее пе рвым аргументом , в системе счисления , основание которой задается ее вторым аргуме нтом . На­ приме р, вызов to _b as e _n ( 1 2 9 , 8 ) выведет на экран 20 1 , эквивалент числа 1 2 9 в восьмеричной системе . Протестируйте получе нную функцию в составе завер­ шенной программы . 1 0 . Напишите и протестируйте функцию Fibona cci ( ) , в которой для вычисления чис ел Фибоначчи используется цикл, а не рекурсия . ГЛАВА 1 0 м а с с и вы и ука зател и в этой главе: • Кл ю ч евое сл ово: • s t atic • • Опера ции: & * (ун а р н ы е) • Созд ание и и н и циализация м а ссивов • Указател и (на основе сведений, которые вам уже известны) и какое отношение они и м еют к м а ссивам Написание функций, обрабатывающих массивы дв умерные массивы л юди обращаются за помощью к компьютеру при реше нии таких задач, как под­ с чет е же месячных расходов, расчет ежедне вного количества осадков , еже­ квартальных продаж, е же недельного прироста веса и так дале е . Предприятия обращаются к помощи компьюте ра при составлении плате жных ведомосте й, при уче­ те мате риально-производстве нных запасов и при рас четах с заказчиками. Будучи про­ граммистом, вы неизб ежно имеете дело с большими объемами соответствую щих дан­ ных. Довольно часто масс ивы предлагают наилучшие способы манипулирования та­ кими данными - удобные и эффе ктивные . В главе 6 было введено понятие масс ива , а в этой главе мы изучим масс ивы более подробно. В частности, мы рассмотрим функции, выполняю щие обраб отку масс ивов . Такие функции предоставляют вам возможность распространить преимуще ства модульного программирования на мас с ивы. Изучая ма­ териал этой главы , вы с ами можете убедиться в том, насколько тесно связаны между собой массивы и указатели. М ассивы В с помните , что .массив образует не которая последовательность эле ментов одного и того же типа данных. Вы используете обмвлен,ия с целью уведомить компилятор о том, что вам необходим мас сив. Обмвлепие .массива сообщает компилятору, с колько элемен­ тов содержит мас с ив и какой тип имеют е го эле менты . Рас полагая такого рода ин­ формацие й, компилятор может правильно с о здать массив. Элементы мас с ива могут иметь те же типы , что и обыкновенные переме нные . Рассмотрим следую щий приме р объявления мас с ива : 394 Гл ава 10 / * н е ско л ь ко о бъявлений массивов * / int main ( void) { float candy [ 3 6 5 ] ; / * ма ссив и з 3 6 5 знач е ний типа flo at * / char code [ l 2 ] ; / * ма ссив и з 1 2 знач е ний тип а char * / int s tates [ 5 0 ] ; / * ма ссив и з 5 0 знач е ний тип а int * / Квадратные скобки ( [ ] ) свидетельствуют о том, что candy и другие такие же струк­ туры данных являются масс ивами, а число, заклю ченное в квадратные скобки, задает количество элементов в масс иве . Чтобы получит доступ к эле ментам мас с ива, вы должны указать отдельный эле­ мент, используя для этой цели его номер, который также называется иидексом. Нуме­ рация элементов мас с ива начинается с О. Следовательно, candy [ О ] - это первый эле­ мент мас сива candy, а candy [ 3 6 4 ] - 3 65-й, он же последний, эле мент мас сива . В с е это нам уже изве стно ; продолжим дале е изучение мас сивов. и н ициа л изация Массивы часто используются для хранения данных, необходимых программе . На­ приме р, каждый элемент 1 2-элементный мас с ива может содержать количество дне й в соответствую щем месяце . В случаях, подобных данному, удобно выполнить инициали­ зацию мас сива в начале программы. Посмотрим, как это делается. Вы уже знаете , как выполняется инициализация однозначных переме нных (иногда они называются ска ляри'Ыми) в объявлениях с выражениями наподобие int fix 1; fl o at fl ax PI * 2; = = в которых, как можно надеяться , константа б ыла определена ране е . Язык С рас ширя­ ет инициализацию на массивы с помощью новых с интаксиче с ких средств, как показа­ но ниже : int main ( void) { int p ower s [ 8 ] { 1 , 2 , 4 1 61 8 1 1 61 32 , 64 } ; / * то л ь ко в станд арте ANS I * / Нетрудно видеть, что вы инициализируете мас с ив с приме не нием списка элемен­ тов, отделенных друг от друга запятыми, заклю ченного в квадратные скобки. Между запятыми и значе ниями при желании можно вставлять пробелы . Первому эле менту ( powe r s [ О ] ) присваивается значе ние 1 и так далее. (Если ваш компилятор отказывает­ ся выполнять такую форму инициализации, рассматривая ее как синтаксическую ошибку, значит, компилятор был разработан до появле ния стандарта ANSI. Проблема решается путем помещения перед объявлением мас с ива клю че вого слово s tati c . В главе 1 2 значе ние этого клю чевого слова обсуждается более подробно . ) В листинге 1 0 . 1 показана короткая программа , которая выводит на печать количе ство дней каж­ дого месяца . М ассивы и указатели 395 Листинг 1 0.1 . Программа day_ПDnl . с выводи т н а печ ать колич е с тво дней каждог о месяца * / / * day_mon l . c #incl ude <s tdio . h > #de fi ne MONTHS 1 2 int main ( vo i d ) { int days [ MONTH S ] = { 3 1 , 2 8 , 3 1 , 3 0 , 3 1 , 3 0 , 3 1 , 3 1 , 3 0 , 3 1 , 3 0 , 3 1 ) ; int inde x ; for ( index = О ; inde x < MONTHS ; index++ ) print f ( "Me cяц % d име е т % 2 d дней ( д ень ) . \ n " , i ndex + 1 , days [ index ] ) ; r e turn О ; - - В ыходные данные име ют следую щий вид : Ме сяц 1 име е т 3 1 дн ей ( д е нь ) . Ме сяц 2 име е т 2 8 дн ей ( д е нь ) . Ме сяц 3 име е т 3 1 дн ей ( д е нь ) . Ме сяц 4 име е т 3 0 дн ей ( д е нь ) . Ме сяц 5 име е т 3 1 дн ей ( д е нь ) . Ме сяц 6 име е т 3 0 дн ей ( д е нь ) . Ме сяц 7 име е т 3 1 дн ей ( д е нь ) . Ме сяц 8 име е т 3 1 дн ей ( д е нь ) . Ме сяц 9 име е т 3 0 дн ей ( д е нь ) . Ме сяц 1 0 име е т 3 1 дней ( д ень ) . Ме сяц 11 име е т 3 0 дней ( д ень ) . Ме сяц 1 2 име е т 3 1 дней ( д ень ) . Не ахти какая программа , однако, она ошиб ается только один раз в четыре года . Программа инициализирует мас сив days [ ] с ис пользованием списка значе ний, отде­ ленных друг от друга запятыми, заключе нного в квадратные скобки. Обратите внимание на то , что в этом примере присутствует с имволическая кон­ станта MONTHS для представления разме ра мас сива . Это распространенная и рекомен­ дованная практика . Например , если вдруг мир перейдет на 1 3 -месячный календарь, достаточно будет внести соответствую щее измене ние в опе ратор #de fi n e , и не нужно будет выискивать в программе все места , в которых используется разме р мас сива . И сп ольз ован и е кон стант в м асси в ах Иногда вам приходится использовать массив, предназначенны й только для чтения . То есть программа извлекает значения из массива, но не предприни мает попыток записы­ вать новые значения в этот массив. В та ких случаях вы можете и должны испол ьзовать ключе вое слово cons t во время объя вления и инициал изации масс ива . С учетом ска­ занного выше, в программе, представленной в л истинге 1 0. 1 , лучше вос пол ьзоваться следующей конструкцие й: co n s t int days [ MONTH S ] = { 31, 28 , 31, 30 , 31, 30 , 31,31, 30, 31 , 30, 31) ; Это позволяет про гра мме рассматривать кажды й элемент массива как константу. Ка к и в случае обычных пере менных, вы должны использовать объявление для инициал иза­ ции данные типа con s t , поскол ьку есл и они объя влены как const, вы не можете впо­ следствии присва и вать им новые значения. Теперь, когда мы знаем об это м, мы може м испол ьзовать константы в последующих при мерах. 396 Гл ава 10 Что произойдет, е сли вы не сможете инициализировать мас с ив? О б этом мы узна· ем после ис следования программы из листинга 1 0 . 2 . Листинг 1 0.2. Проrрамма no_data . c / * no_data . c н еиници ализиров анный мас сив * / #incl ude <s tdio . h > #de fi ne S I Z E 4 -- int main ( vo i d ) { int no data [ S I ZE J ; int i ; / * неинициали зированный массив * / printf ( " % 2 s % 1 4 s \ n " , " i " , " n o_data [ i ] " ) ; for ( i = О ; i < S I ZE ; i++ ) print f ( " % 2 d% 1 4 d\ n " , i , no dat a [ i ] ) ; r e turn О ; В от один из примеров выходных данных этой программы (результаты могут ме· няться ) : i о 1 2 3 no_data [ i ] 16 4 2 0 4 93 7 4 219854 2147348480 Элементы массива мало чем отличаются от обычных переменных, и е сли вы не инициализируете их, они могут принимать произвольные значения . Компилятор ис· пользует те значе ния , которые уже были записаны в соответствую щих ячейках памя· ти, вот поче му ваши ре зультаты могут отличаться от представленных выше . Учет различных классов памяти Массивы , как и вс е другие переме нные , можно создавать с использованием классов памя,ти. Эта тема рассматривается в главе 1 2 , однако, с ейчас вам достаточно знать, что в текущей главе описаны массивы , которые относятся к автоматическому клас су памяти. Это означает, что они объявлены внутри функции без употре бления клю чево· го слова s t ati c. Все переме нные и мас с ивы, использовавшиеся до сих пор в этой кни· ге , относятся к автоматическому классу. Мы упомянули здесь клас сы памяти по той простой причине , что вре мя от времени различные кла с с ы памяти проявляют раз· личные с войства , так что вы не должны автоматически переносить все сказанное в этой главе на другие кла с с ы памяти. В частности, пере менные и мас с ивы некоторых не рас смотренных зде с ь классов памяти, не будучи инициализированными, получают значение О. Количество элементов в спис ке должно соответствовать разме ру мас с ива . Но что будет, е сли вы ошибете сь при подсчете? Попробуем еще раз выполнить последний пример, как показано в листинге 1 0 . 3 , вклю чив с писок, который в два раза короче объявленного размера массива . М ассивы и указатели 397 Листинг 1 0.3. Проrрамма soпedata . с / * s ome dat a . c ч а с тично инициализиров анный массив * / #incl ude <s tdio . h > #de fi ne S I Z E 4 -- int main ( vo i d ) { int s ome_data [ S I ZE ] int i ; { 1492 , 1066) ; printf ( " % 2 s % 1 4 s \ n " , " i " , " s ome_data [ i ] " ) ; for ( i = О ; i < S I ZE ; i++ ) print f ( " % 2 d % 1 4 d \ n " , i , s ome_data [ i ] ) ; r e turn О ; На этот раз выходные данные приобретают следую щий вид : i о 1 2 3 s ome_data [ i ) 1 4 92 10 66 о о Нетрудно убедиться в том, что у компилятора проблем не б ыло. Как только значе­ ния в спис ке закончатся , остальные элементы он инициализирует нулями. Иначе го­ воря , если вы вообще не выполняете инициализацию массива , е го элеме нты , подобно об ычным переменным, получают произвольные значе ния мус ора в памяти, в то же вре мя , е сли вы выполняете частичную инициализацию мас с ива, оставшимся неини­ циализированным эле ментам присваиваются нуле вые значения . Но компиляторы не простят вам ошибку, е сли вы укажете с писок, в котором зна­ че ний больше , чем размер объявле нного массива . Такая чре змерная щедрость рас­ сматривается как ошибка . Тем не менее , нет не обходимости выставлять с ебя мише· нью для насмешек со стороны собственного компилятора . Вместо этого вы можете предоставить компилятору возможность привести в соответствие разме р мас с ива с о списком, ис ключив размер и з квадратных с кобок (листинг 1 0 .4 ) . листинг 1 0.4. Проrрамма day_JD)n2 . с / * day_mon2 . c компиля тор сам подсчитыв а е т колич е ство э л ементов * / #incl ude <s tdio . h > - - int main ( vo i d ) { co n s t int days [ ] int inde x ; = { 31, 28, 31,30, 31, 30 , 31,31, 30, 31) ; for ( index = О ; inde x < s i zeo f days / s i zeo f days [ O ] ; i ndex++ ) print f ( "Me cяц % 2 d име е т % d дней ( д ень ) . \ n " , i ndex + 1 1 days [ index ] ) ; r e turn О ; 398 Гл ава 10 В листинге 1 0 .4 необходимо отметить два моме нта : • • Когда вы освобождаете квадратные с кобки с целью инициализации массива , компилятор проводит подсчет эле ментов списка и устанавливает разме р масси­ ва равным этому числу. Об ратите внимание на то , как был скорректирован управляю щий опе ратор цикла for . Не имея уверенности в том, что нам удастся выполнить этот подсчет правильно (что вполне объяснимо ) , мы предоставляем право компьютеру само­ стоятельно определить размер мас сива . О перация s i z eo f возвращает размер в байтах объекта , или типа, следую ще го за ним. Таким образом, s i zeo f days это размер в байтах вс его масс ива , а s i zeo f days [ О ] разме р в байтах одного эле мента этого массива . Выполнив деление разме ра всего массива на размер одного эле мента , мы узнаем, сколько элементов содержится в мас сиве . - - Ниже показан результат выполнения этой программы: Ме сяц 1 име е т 3 1 дн ей ( д е нь ) . Ме сяц 2 име е т 2 8 дн ей ( д е нь ) . Ме сяц 3 име е т 3 1 дн ей ( д е нь ) . Ме сяц 4 име е т 3 0 дн ей ( д е нь ) . Ме сяц 5 име е т 3 1 дн ей ( д е нь ) . Ме сяц 6 име е т 3 0 дн ей ( д е нь ) . Ме сяц 7 име е т 3 1 дн ей ( д е нь ) . Ме сяц 8 име е т 3 1 дн ей ( д е нь ) . Ме сяц 9 име е т 3 0 дн ей ( д е нь ) . Ме сяц 1 0 име е т 3 1 дней ( д ень ) . В от как ! Вывело с ь лишь 1 0 значе ний, а ис пользованный нами метод , позволяю щий программе самостоятельно определить размер масс ива , не предоставил возможности распечатать мас с ив до конца . Это подче ркивает потенциальный недостаток автомати­ зированного подс чета : ошибочное количество элементов может оказаться необнару­ же нным . Суще ствует еще один более короткий метод инициализации массивов. Однако, по­ скольку е го применение возможно только в отноше нии символьных строк, мы отло­ жим его рас смотрение до следую щей главы . Выделенные и н ициал изаторы (ста нда рт С99) Стандарт С99 добавляет в язык С новую возможность въtделеииъtе иии'Циадизаторъ� . Это с войство позволяет выбирать, какие элементы должны быть инициализированы . Предположим, например, что вы хотите инициализировать только последний эле­ мент мас с ива . Полагаясь только на традиционные синтакс иче ские с редства инициа­ лизации языка С, вы должны также инициализировать и все элементы , предшествую­ щие последнему: - int arr [ б ] = { О , О , О , О , 0 , 212 ) ; / / тр адицио нный синтаксис В условиях действия стандарта С99 вы можете ис пользовать инде кс в квадратных скобках в спис ке инициализации при описании некоторого конкретного эле мента : int arr [ б ] = { [5] = 2 1 2 ) ; / / инициализировать элемент arr [ 5 ] значени ем 2 12 М ассивы и указатели 399 Как и при обычной инициализации , после того , как вы выполните инициализа· цию , по меньше й мере , одного эле мента , инициализированные эле менты получают значение О . В листинге 1 0 .5 представлен б оле е сложный приме р. Листинг 1 0.5. Программа de siCJDate . c 1 1 de s ignat e . c - - испол ь зование выделенных инициали з а торо в #incl ude <s tdio . h > #de fi ne MONTHS 1 2 int main ( vo i d ) { int days [ MONТH S ] int i ; { 31, 28, [4] 31, 30, 31, [ 1] 29} ; for ( i = О ; i < MONT H S ; i+ + ) print f ( " % 2 d % d\ n " , i + 1 , days [ i ] ) ; r e turn О ; Ниже приведены выходные данные для случая , когда компилятор подде рживает это свойство стандарта С99: 1 2 3 4 5 6 7 8 9 10 11 12 31 29 о о 31 30 31 о о о о о Эти выходные данные отражают два важных свойства выделе нных инициализато· ров. Во-первых, если за выделе нным инициализатором следует код с дальнейшими значениями, как, например, в последовательности [ 4 ] = 3 1 , 3 0 , 3 1 , при этом приве· денные значения используются для инициализации последую щих элементов. То есть, после инициализа ции day s [ 4 ] значением 3 1 этот код инициализирует эле м е нты days [ 5] и days [ 6 ] , с о ответственно , значениями 3 0 и 3 1 . Во-вторых, если этот код инициализирует конкретный эле мент некоторым значение м б олее одного раза , в де й· ствие вступает последняя инициализация . Например, в листинге 1 0 .5, начало спис ка инициализации инициализирует элемент days [ 1 ] значе нием 2 8 , но это значение бу· дет позже пе рекрыто выделенным инициализатором [ 1 ] = 2 9 . Присва ива н и е значений масси ва м После того , как массив будет объявлен, вы можете присвоитъ значения элементам массива , используя для этого и'Ндекс элемента массива . Например, следующий фрагмент программного кода присваивает (или назначает) массиву четные числовые значения: 400 Гл ава 1 0 / * присв аив ани е знач ений массив у * / #i nclude < s tdi o . h> #de fine S I ZE 5 0 int main ( void) { int counter , evens [ S I ZE ] ; for ( count e r О ; count er < S I ZE ; counter++ ) evens [ counter ] 2 * counter ; = = Обратите внимание, что в этом коде используется цикл для поэлеме нтного при· сваивания значе ний. Язык С не позволяет прис ваивать один массив в каче стве значе· ния другого как эле мента данных. Нельзя также использовать форму с писка , заклю· ченного в фигурные с кобки, нигде , кроме как в опе раторе инициализации. В показанном ниже фрагменте программного кода демонстрируются некоторые формы недопустимых методов присваивания значе ний. / * Недопустимые методы присв аив ания з н ач е ний элементам масси в а * / #de fine S I ZE 5 int main ( void) { int oxen [ S I ZE ] / * здесь в с е в порядке * / {5, 3, 2 , 8) ; int y a k s [ S I ZE ] ; yaks oxen ; yaks [ S I Z E ] oxen [ S I Z E ] ; yaks [ S I ZE ] {5, 3 , 2, 8) ; = = / * недопу стимый опер атор * / / * непр авильно * / / * этот м е тод не работает * / Гра н и цы масси ва В ы должны уб едиться в том, что ис пользуемые инде ксы элементов масс ива не вы· ходят за допустимые пределы, другими словами, вы должны убедиться в том, что они имеют значе ния , разре шенные для данного массива . Наприме р, предположим, что вы сделали следую щее объявления : int doo fi [ 2 0 ] ; В этом случае вы должны следить за тем, чтоб ы программа ис пользовала индексы в диапазоне от О до 1 9 , поскольку компилятор не станет делать эту прове рку за вас . Расс мотрим программу в листинге 1 0 . 6 . О на создает мас сив из четырех элементов, а зате м бе зответстве нно использует значения индексов в диапазоне от -1 до 6. Листинг 1 0.6. Проrрамма bounds . с 1 1 bo unds . c - - выход з а гр аницы ма ссива #incl ude <s tdio . h > #de fi ne S I Z E 4 int main ( vo i d ) { int valu e l 44; int arr [ S I ZE ] ; int valu e 2 88; int i ; = = М ассивы и указатели 401 p r i nt f ( " valu e l = % d , valu e 2 = % d\ n " , value l , valu e 2 ) ; for ( i = - 1 ; i <= S I ZE ; i+ + ) arr [ i ] = 2 * i + 1 ; for ( i = - 1 ; i < 7 ; i++ ) print f ( " % 2 d % d\ n " , i , arr [ i ] ) ; p r i nt f ( " valu e l = % d , valu e 2 = % d\ n " , value l , valu e 2 ) ; r e turn О ; Компилятор не выполняет проверку правильности использования инде ксов в мас­ сиве . В стандартном языке С ре зультат неправильного ис пользования индекс а не оп­ ределен. Это означает, что когда вы инициируете программу, может показатьс я , что она работает правильно , вести с ебя странным образом или аварийно завершитьс я . Ниже показан пример выходных данных этой программы, откомпилированной с по­ мощью D igital Mars 8 .4: value l = 4 4 , v al u e 2 88 -1 -1 о 1 1 3 2 5 3 7 4 9 5 5 6 12 4 5 1 2 0 value l = - 1 , v al u e 2 = 9 Обратите внимание , что компилятор сохранил значение valu e 2 непос редственно после мас сива , а значение valu e l - непос редстве нно пе ред ним. (Другие компилято­ ры могут хранить данные в памяти в другом порядке . ) В данном случае arr [ - 1 ] соот­ ветствует той же ячейке памяти, что и valu e l , а arr [ 4 ] - той же ячейке памяти, что и value 2 . В с илу этого об стоятельства , ис пользование индексов эле ментов масс ива , вы­ ходящих за допустимые пределы, приводит к тому, что программа меняет значения других переменных. Другой компилятор может выдать другие результаты, и одним из них может б ыть аварийное завершение программы . В ы , должно быть, хотели бы знать, поче му язык С допус кает такие вещи. В с е это является следствием философии языка С , которая предоставляет программистам большую свободу в принятии решений. Отказ от прове рки выхода за допустимые пре­ делы ускоряет выполнение программы на С. Компилятор не вс егда способен отлавли­ вать все индексные ошиб ки, поскольку значе ние индекса может оставаться не опреде­ ленным до тех пор, пока не начнется выполнение результирую щей программы. В силу этого обстоятельства , чтобы обес пе чить б езопасность, компилятор долже н добавить в программу дополнительный код для прове рки каждого инде кс а во время выполнения, но от этого замедлится само выполне ние программы . По этой причине С оставляет за программистом задачу правильного кодирования , и вознаграждает е го за это увеличе­ нием быстродействия программы . Разумеется , не все программисты заслуживают та­ кого доверия, вот тогда-то и начинаются настоящие проблемы. 402 Гл ава 1 0 Следует все гда помнить одну простую истину - нумерация элементов массива на­ чинается с О. Нужно также выработать в с ебе привычку ис пользовать символические константы в объявле ниях массивов и других местах, где ис пользуется разме р массива : #de fine S I ZE 4 int main ( void) { int arr [ S I Z E ] ; for ( i О ; i < S I ZE ; i + + ) = Это позволит обрести уверенность в том, что вы последовательно ис пользуете один и тот же разме р массива в программе . Указа н и е размера масси ва До сих пор при объявлении массивов ис пользовались целочисленные константы : #de fine S I ZE 4 int main ( void) { int arr [ S I Z E ] ; douЫ e lots [ 1 4 4 ] ; 1 1 символич е ская целочисленн ая константа 11 ли тер аль ная це лочисленная константа Что е ще разрешено программисту? До появления стандарта С99 на этот вопрос можно было ответиrь, что при объявлении массива вы можете вос пользоваться кон стантнъ1м 'Цело-численнъtм въtражением, заклю чив его в квадратные скобки. Константное выраже ние - это выраже ние , сформированное из целочисле нных констант. В этом смысле выражение s i zeo f рассматривается как целочисле нная константа, в то же вре мя (в отличие от cas e в языке С + + ) значение м типа con s t не является . Кроме того, значение такого выражения должно быть больше О : int n 5 ,· int m 8; fl o at a l [ 5 ] ; fl o at а2 [ 5 * 2 + 1 ] ; fl o at аз [ s i z eo f ( int ) + 1 ] ; fl o at а 4 [ - 4 ] ; fl o at а5 [ О ] ; fl o at а 6 [ 2 . 5 ] ; fl o at а7 [ ( int ) 2 . 5 ] ; fl o at а 8 [ n ] ; fl o at а 9 [ m ] ; = = 1 1 да 1 1 да 1 1 да 1 1 н е т , р азмер долже н быть > о 1 1 н е т , р азмер долже н быть > о 1 1 н е т , р азмер долже н быть целым числом 1 1 да, преобразование типа из float int constant 1 1 н е р а з р ешало сь Д О появл ения с т андар та С 9 9 1 1 н е р а з р ешало сь до появл ения с т андар та С 9 9 Как показывают комментарии, компиляторы языка С , действую щие в соответствие с требованиями стандарта С90, не разре шают двух последних объявлений. В то же вре мя стандарт С99 их допускает, благодаря чему создается новый тип мас сивов, по­ лучивший название массива переменной длинъ1. Стандарт С99 вводиr в употребление массивы переме нной длины главным образом для того , чтобы увеличить возможности языка С в плане приме не ния численных ме­ тодов. Наприме р, масс ивы пере менной длины значительно облегчают преобразова­ ние существую щих б иблиоте к программ на языке FORTRAN, выполняю щих цифро- М ассивы и указатели 403 вые расчеты, в программы на языке С. На массивы переменной длины накладываются определе нные ограничения , например, вы не можете выполнять инициализацию мас­ сива переменной длины в его объявлении. Мы е ще вернемся к мас сивам пере менной длины в этой главе несколько позже , после того , как изучим ограничения , которым в языке С подве ргается клас сический мас сив. М н огом ерн ые м асси вы Мисс Т емпе ст Клауд (Tempest Cloud буквально, "грозовая туча" ) , специалист­ мете оролог, исклю чительно серье зно относящаяся к своим профе ссиональным обя­ занностям , наме ревается прове сти анализ данные о еже месячных ос адках за послед­ ние пять лет. Одно из ее пе рвых ре шений касается выбора подходяще й формы пред­ ставления данных. Один из возможных вариантов состоит в использовании 60 пе ре­ менных, по одной для каждого эле мента данных. (Мы уже обсуждали этот вариант, нам он и сейчас кажется таким же бессмысле нным, каким он казался и раньш е . ) Ис­ пользование масс ива , соде ржаще го 60 эле ментов, намного лучше , в то же вре мя гораз­ до удобне е держать отдельно все данные , относящиеся к тому или иному конкретному году. Можно ис пользовать пять масс ивов по 1 2 эле ментов, но такая попытка имеет свои недостатки и перерастает в трудно решаемую проблему, если мисс Темпест Клауд ре шит изучить данные о е жемесячных осадках за период длиной в 50 лет вместо пяти лет. Для этого ей потребуется более подходящая форма представления данных. Б олее рациональный подход предусматривает ис пользование мас сива массивов. Главный масс ив должен содержать пять элеме нтов , по одному на каждый год. Каждый из этих элементов, в свою очередь, представляет собой 1 2-элементный масс ив, по од­ ному элементу на каждый месяц. Такой мас сив объявляется следую щим образом: - fl o at r a i n [ 5 ] [ 1 2 ] ; 1 1 массив , состо ящий и з 5 массиво в , каждый из 11 ко торых состоит из 1 2 пер еменных тип а float Один из подходов к с о стоит в том, что сначала рассматривается внутре нняя часть объявления (выделенная полужирным ) : float rain [5] [ 12 ] ; / / rain - э то массив и з 5 пока еще н е изв естных объектов Это говорит о том , что r ain является мас сивом, с о стоящим из пяти элеме нтов . Но что представляет собой каждый из этих элементов? Теперь рассмотрим другую часть этого объявления (выделенную полужирным ) : fl o at r a i n [ 5 ] [ 12 ] ; / / массив из 1 2 знач е ний типа flo at Это говорит о том , что каждый элемент мас с ива имеет тип float [ 1 2 ] , то е сть каж­ дый из этих пяти эле ментов мас сива r ain сам по себе является мас сивом из 1 2 значе­ ний типа float. В соответствие с этой логикой элемент r ain [ О ] , будучи первым элементом массива r ain, представляет собой мас с ив из 12 значений типа flo at. Это справедливо и в от­ ношении элементов r ain [ 1 ] , r ain [ 2 ] и так далее. Если r ain [ О ] е сть масс ив, его пер­ вым элементом будет r a i n [ О ] [ О ] , вторым эле ментом r ain [ О ] [ 1 ] и так далее . Коро­ че говоря , r a i n это пятиэле ментный масс ив 1 2-элементных масс ивов значений типа float , r a i n [ O ] масс ив из 12 эле ментов типа float, а r ain [ O ] [ О ] значение типа float . Для получе ния доступа , с кажем, к значе нию , находящемуся в строке 2 и столб- - - - 404 Гл ава 1 0 це 3 , используется конструкция r ain [ 2 ] [ 3 ] . (Напоминаем, что отсчет эле ментов мас­ сива начинается с О, так что строка с номером 2 будет третьей.) В ы можете рассматривать этот массив r ain как двумерный мас с ив , состоящий из пяти строк, при этом в каждой строке име ется 1 2 столбцов , как показано на рис . 1 0 . 1 . Изме няя второй инде кс , в ы продвигаете с ь п о строке, месяц за месяцем. Изме няя пер­ вый инде кс , вы пе ремещаете с ь вертикально вдоль столбца , год за годом. 12 5 const float rain [ S ] ( 1 2 ] Р и с. 1 0.1 . Двумерн:ый массив Двумерное представление - это вс его лишь удобный способ просмотра масс ива с двумя индексами. В памяти компьютера такой мас сив хранится последовательно, на­ чиная с пе рвого 1 2-элементного мас сива , за которым следует второй 1 2-элементный массив, и так далее . В ос пользуе мся таким двуме рным мас сивом в программе обработки погодных дан­ ных. Цель этой программы с остоит в подс чете осадков за год, в вычисле нии средних ежегодных осадков и средних еже месячных осадков . Чтобы вычислить общие осадки за год, нужно просуммировать все данные конкретной строки. Чтобы вычислить осад­ ки за конкретный месяц, потребуется сложить все значения в заданном столбце . Дву­ мерный мас с ив упрощает визуализацию и выполнение этих действий. В листинге 1 0 .7 показана соответствую щая программа . листинг 1 0.7. Проrрамма rain . с / * rain . c вычисля е т итог овые д анные по г о д ам , ежег одные ср е дние з н ач ения и ежемесячные ср едние значения осадков за период в несколь ко лет* / #incl ude <s tdio . h > #de fi ne MONTHS 1 2 / / колич е ство м е сяцев в году #de fi ne YEARS 5 / / количе ство лет, в теч ение которых проводились наб.mодения int main ( vo i d ) { 1 1 иници ализ ация ма ссива данными о б о садках з а п ериод с 2 0 0 0 по 2 0 0 4 co n s t fl o at r a i n [ YEAR S ] [ MONTHS ] { { 4 .3, 4 .3, 4 . 3, 3 . 0, 2 . 0 , 1.2, 0 . 2 , 0.2, 0 . 4 , 2 . 4 , 3 .5, 6 . 6} , { 8 .5, 8.2, 1.2, 1 . 6, 2 . 4 , 0 . 0, 5 .2 , 0 . 9, 0 .3, О . 9 , 1 . 4 , 7 .3} , { 9 . 1, 8 .5, 6.7, 4 .3, 2 . 1, 0 . 8, 0 . 2 , 0.2, 1 . 1, 2 .3 , 6 .1, 8 . 4} , -- = М ассивы и указатели 405 {7.2, 9.9, 8 . 4 , 3 .3, 1.2, 0 . 8 , 0 . 4 , 0 . О , О . 6, 1.7, 4 .3, 6 .2} , {7 . 6, 5 . 6, 3 . 8, 2 . 8, 3 . 8 , 0 . 2 , 0 . О , О . О, О . 0 , 1.3 , 2 . 6, 5 .2} }; int y e ar , month ; fl o at s ubtot , total ; printf ( " ГОД КОЛИЧЕСТВО ОСАДКОВ ( в дюймах ) \ n " ) ; for (year = О , total = О ; y e ar < YEAR S ; year++ ) / / для каждог о г о д а суммарное колич е ство о с адко в з а к аждый месяц { for ( month = О , s ubtot = О ; month < MONT H S ; mo nth++ ) s ubtot + = r ain [ y e ar ] [ month ] ; print f ( " % 5d % 1 5 . l f\ n " , 2 0 0 0 + y e ar , s uЬtot ) ; total += s uЬtot ; / / о бщая сумма з а в с е г о ды printf ( " \ nCp eднeгoдo в o e колич е с тво о с адков составля е т % . l f дюймов . \ n \ n " , total / YEARS ) ; printf ( " C PEДHEMECЯЧHOE КОЛИЧЕСТВО OCAДKOB : \ n\ n " ) ; printf ( " Янв Фев Мар Апр Май Июн Июл Авг Сен Окт " ) ; printf ( " Ноя Дек\ n " ) ; for (month = О ; month < MONTHS ; month++ ) / / суммарные о с адки по к аждому меся цу на про тяжении в сего период а { f o r ( y e ar = О , s uЬtot = О ; y e ar < YEARS ; y e ar++ ) s uЬtot + = r ain [ y e ar ] [ month ] ; print f ( " % 4 . l f " , s uЬto t/ YEAR S ) ; printf ( " \ n " ) ; r e turn О ; Ниже показаны выходные данные этой программы : ГОД КОЛИЧЕСТВО ОСАДКОВ ( в дюймах ) 2000 32 . 4 2001 37 . 9 2 0 02 49. 8 2 0 03 44 . 0 2004 32 . 9 Ср едн е г о довое колич е ство о с адко в сост авля е т 3 9 . 4 дюймо в . MONTHLY AVERAGE S : Янв 7.3 Фев 7.3 Мар 4.9 Апр 3.0 Май 2.3 Июн 0.6 Июл 1.2 Ав г 0.3 Сен 0.5 Окт 1.7 Ноя 3.6 Дек 6.7 В о вре мя изучения этой программы обратите особое внимание на инициализации и на схему вычислений. При этом инициализация является достаточно сложной про­ цедурой, поэтому сначала рассмотрим б оле е простую часть (вычисле ния ) . Чтобы вычислить итоговую сумму з а год , значение y e ar остается не изме нным, в то вре мя как значе ние month пробегает весь диапазон значений. Это внутре нний цикл for первой части программы. Зате м этот процесс выполняется для следую ще го значе­ ния переменной year. Это внешний цикл первой части программы. Структура вло­ же нного цикла вполне е стественна при работе с двумерными циклами. Один цикл вы­ полняют обработку первого индекс а , а второй цикл обраб атывает второй инде кс . 406 Гл ава 1 0 Вторая часть программы имеет аналогичную структуру, н о н а этот р а з значение y e ar меняется во внутреннем цикле , а значение mo nth - во вне шне м . В с помните , что каждый раз, когда внешний цикл выполняет одну итерацию , внутре нний цикл пробе· гает вес ь диапазон значений. По этой причине, при такой организации цикл пробега­ ет все года, пре жде чем изме нится ме сяц. Сначала вы получаете средне годовое значе­ ние осадков для первого ме сяца , затем для второго и так далее. И н и циал иза ция двумерного массива В основу инициализации двуме рного мас с ива положена методика инициализации одномерного массива . В о-первых, вспомните , что инициализация одномерного масси­ ва выполняется следую щим образом: sometyp e ar l [ 5 ] = { v al l , v al 2 , val 3 , val 4 , val 5 } ; В рас сматриваемом случае значения val l , val 2 и так дале е с оответствуют не кото· рому типу s ometyp e . Например, если бы s ometyp e был типом i nt, значение v al l могло бы быть 7 , или если бы sometyp e был бы типом douЫ e , значение val l могло бы быть 1 1 . 3 4 . Однако r ain - это мас с ив , состоящий из пяти элементов, причем этими эле· ментами являются эле менты в виде массивов, каждый из которых содержит 12 значе· ний типа fl o at. Следовательно, что кас а ется масс ива r ain, то в качестве val l должно быть значе ние , пригодное для инициализации одномерного масс ива значений типа float , например, следую щий мас с ив : { 4 .3, 4 .3 , 4 .3 , 3 . 0 , 2 . О , 1.2, О .2, О . 2 , О . 4 , 2 . 4 , 3 . 5, 6. 6} То есть, если s ometyp e - массив, состоящий из 1 2 значений типа do uЬ l e , то значе­ ние val l представляет собой с писок 12 значе ний типа douЫ e . Следовательно, для инициализации двумерного массива , такого как r ai n , нам нужен с писок из пяти таких объектов, отделенных друг от друга запятыми: co n s t fl o at r a i n [ YEAR S ] [ MONTHS ] = { { 4 .3, 4 .3, 4 . 3, 3 . 0, 2 . 0 , 1.2, О . 2 , О.2, О . 4 , 2 . 4 , 3 .5, 6 . 6} , { 8 .5, 8.2, 1.2, 1 . 6, 2 . 4 , 0 . О, 5 .2 , О . 9, О .3, О . 9 , 1 . 4 , 7 .3} , { 9 . 1, 8 .5, 6.7, 4 .3, 2 . 1, О . 8, О . 2 , О.2, 1 . 1, 2 .3 , 6 .1, 8 . 4} , {7.2, 9 . 9, 8 . 4 , 3 .3, 1.2, 0 . 8 , О . 4 , О . О , О . 6, 1. 7 , 4 .3, 6 .2} , {7 . 6, 5 . 6, 3 . 8, 2 . 8, 3 . 8 , 0 . 2 , О . О , О . О, О . О , 1.3 , 2 . 6, 5 .2} }; Такая инициализация требует задания пяти спис ков числовых значе ний, заклю· ченных в фигурные скобки. Данны е , с одержащиеся в пе рвой паре фигурных с кобок, прис ваиваются пе рвой строке мас с ива , данные , содержащие ся во второй паре фигур­ ных скобок, - второй строке массива и так далее. Расс мотре нные выше правила, ка· саю щие ся не соответствия размеров данных и мас сивов, применимы к каждой строке . Иначе говоря, е сли внутренняя пара фигурных скобок содержит 1 0 чисел, только 1 0 элементов получат соответствую щие значения . В этом случае два последних элемента в этой строке инициализируются нулями. Если в спис ке слишком много чисел, это оз· на чает ошибку, и эти числа не будут распределе ны в следую щей строке. Можно опустить внутренние фигурные скобки и оставить две вне шних с кобки. Ее· ли в этом случае в скобках будет представлено корре ктное количество элеме нтов , ре­ зультат будет таким же. Однако, е сли элементов меньш е , чем необходимо , мас с ив за· полняется последовательно, строка за строкой, пока не закончатся данные . М ассивы и указатели 407 После этого оставшие ся эле менты инициализируются нулями. На рис . 1 0 .2 показа­ ны оба способа инициализации масс ива . Поскольку мас с ив r a i n с одержит данные , которые не могут быть изменены, про­ грамма ис пользует модификатор co n s t в объявлении массивов. int sq [2 ] [3] = { 5 , б , 7 , В } ; New : int sq [2 ] [3] }; {5 , б} , {7 , В} Р и с . 1 0. 2 . Два метода U'нu-циализа-ции массива масси вы с размерностя м и бол ьше двух В с е , что б ыло сказано о двуме рных мас сивах, можно рас пространить на трехме р­ ные масс ивы и на мас с ивы б ольших разме рностей. Вы можете объявить трехме рный массив следую щим образом: int box [ l O ] [ 2 0 ) [ 3 0 ) ; В ы можете расс матривать одномерный массив как строку данных, двумерный мас­ сив - как таблицу данных, а трехмерный массив - как набор таблиц данных. Напри­ мер, вы можете рассматривать многоме рную таблицу как совокупность 1 0 двумерных массивов (размером 20х30 кажды й ) , помещенных друг поверх друга . Другим способом восприятия Ьох является масс ив массивов, состоящих из масси­ вов. Иначе говоря, это мас сив, элеме нты которого представляют собой 20-элемент­ ный мас с ив . Каждый элемент этого 20-элементного мас сива представляет с об ой 3 0элементный мас с ив. Либо вы просто можете рассматривать мас сивы в терминах коли­ чества не обходимых индексов. Как правило, для обработки трехмерных масс ивов используется три вложе нных цикла, для обраб отки четырехмерных масс ивов - циклы с глубиной вложе ния , равной четырем , и так далее. В наших примерах в основном мы буде м пользоваться двуме р­ ными масс ивами. Ука зател и и м асси вы Указатели, к а к следует и з главы 9 , представляют собой с имволиче ский с пособ ис­ пользования адре сов. Поскольку аппаратные инструкции вычислительных машин в значительной степени зависят от адресов, указатели позволяют формулировать свои наме рения достаточно близко к тому, как машина выражает свои задачи. Это соответ­ ствие повышает эффективность программ, использую щих указатели. В частности, указатели позволяют эффективно раб отать с масс ивами. В с амом деле , как вы увидите далее , с истема обозначения массивов просто представляет собой особый вид исполь­ зования указателей. 408 Гл ава 1 0 Примером такого особого использования может служить тот факт, что имя масси­ ва является также адре сом первого эле мента массива . Другими словами, если fli zny есть массив, то приведе нное ниже выражение будет истинным: fl i zny == 1 1 именем массив а является адр е с перв ого э л емент а & fli zny [ O ] ; Оба имени, fl i zny и & fli zny [ О ] представляют адре с в памяти первого элемента массива . (Вспомните , что & это опе рация адре сации. ) Оба име ни являются констан­ тами, поскольку остаются фиксированными в тече ние всего време ни де йствия про­ граммы . В то же время они могут быть присвоены в виде значений перемен:ной типа ука­ затель, и вы можете менять значение переменной, как показано в листинге 1 0 . 8 . О б­ ратите внимание на то , что происходит с о значением указателя , когда вы прибавляете к не му число. (Как говорилось ране е , с пецификатор %р , предназначенный для указа­ телей, об ычно отоб ражает шестнадцатеричные значе ния . ) - листинг 1 0.8. Программа pnt_add . c сложени е ука з ателей 1 1 pnt_add . c #incl ude <s tdio . h > #de fi ne S I Z E 4 -- int main ( vo i d ) { short dates [ S I ZE ] ; short * pti ; short index ; do uЫ e b i l l s [ S I ZE ] ; do uЫ e * pt f ; pti date s ; / / на знач ение ука з ателю адр е са мас сива pt f Ь i ll s ; printf ( " % 2 3 s % 1 0 s \ n " , " s hort " , " douЬl e " ) ; for ( index О ; inde x < S I ZE ; i ndex + + ) рrint f ( "указ атели + % d : % 1 0р % 1 0p \ n " , index , pti + index , p t f + inde x ) ; = = = r e turn О ; Ниже показаны выходные данные этой программы : ук аз атели + о : ук аз атели + 1 : ук аз атели + 2 : ук аз атели + 3 : s hort O x 0 0 6 4 fd2 0 O x 0 0 6 4 fd2 2 O x 0 0 6 4 fd2 4 O x 0 0 6 4 fd2 6 do uЫ e O x 0 0 6 4 fd2 8 O x 0 0 6 4 fd3 0 O x 0 0 6 4 fd3 8 O x 0 0 6 4 fd4 0 В о второй строке печатаются начальные адре са двух мас сивов, в следую щей строке показан результат от прибавления к адресу 1 и так далее . Не забывайте , что адреса представлены в шестнадцатеричной форме , поэтому 3 0 на 1 больше , чем 2 f, и на 8 больше , чем 28. Почему так? O x 0 0 6 4 fd2 0 + 1 дает в р е з уль тате O x 0 0 6 4 fd2 2 ? O x 0 0 6 4 fd3 0 + 1 дает в р е з уль тате O x 0 0 6 4 fd3 8 ? М ассивы и указатели 409 В ам вс е е ще это не понятно? О бъяс не ние достаточно простое ! В наше й системе ре ализована поб айтная адре сация памяти, в то время как тип s hort использует 2 бай­ та , а тип douЫ e - 8 байтов. Происходит вот что : когда вы говорите " прибавить 1 к указателю " , С доб авляет одну едини-цу хранения. В случае мас с ивов это означает, что ад­ ре с увеличивается до адреса следую щего элемента, но не до следую щего байта (рис . 1 0 . 3 ) . Это одна из причин того, почему вы должны объявлять вид объекта , на который указывает указатель. Адреса недостаточно , пос кольку компьютер должен знать, сколь­ ко нужно байтов для хранения объекта . (Это с праведливо и в отношении указателей на скалярные переме нные , в противном случае операция *p t , выбираю щая значение , не будет работать правильно . ) Значение указателя в результате сложения указателей увеличивается на 2, поскольку переменная pti имеет тип int ... 56014 pti + 1 ... ... dates [ l ] int dates [y] , *pti ; pti dates ; (or pti 1 pti + 2 56015 56016 56017 56018 dates [ O ] = 1 1 pti pti + з ... 56019 56020 dates [2 ] 5 6 0 2 1 - Машинный адрес dates [З ] - Элементы массива & dates [ O ] ; ) ... П еременной pti типа указателя присваивается адрес первого элемента массива dates Р и с. 1 0. 3 . Массив'Ы и сложение указатемй Сейчас необходимо дать более четкое определение , что означают выраже ния "ука­ затель на i nt", "указатель на float" или "указатель на любой тип" . • • • Значение указателя - это адре с объекта , на который он указывает. То, как адрес представлен в памяти машины, зависит от конструктивных ос обенностей аппа­ ратных средств. Многие компьютеры , в том числе и пе рс ональные компьютеры IВМ РС и Macintosh, имеют байтовую адреса-цию, это значит, что б айты памяти пронуме рованы последовательно. В подобных случаях адре с крупного объе кта, такого как переме нная типа douЫ e , как правило, представляет собой адрес первого байта объекта . Приме няя операцию * к указателю , получае м значе ние , которое хранится в объекте , на который нацелен указатель. Добавле ние к указателю 1 увеличивает его значение на величину разме ра типа в байтах переменной, на которую он указывает. 41 0 Гл ава 1 0 Благодаря интеллектуальному характе ру языка С , имеем следую щие равенства : dates + 2 &date [ 2 ] * ( dates + 2 ) date s [ 2 ] == = = */ / * тот же адр е с / * то же з н ач ени е * / Представле нные отношения подводят итог тес ной зависимости между массивами и указателями. Это означает, что вы можете использовать указатели для иде нтификации конкретного эле мента масс ива и получения его значе ния . По сути, в вашем распоря­ же нии имеются два различных обозначе ния одного и того же объе кта . В с амом деле , стандарт языка С описывает массивы через указатели. То есть, он определяет, что ar [ n ] означает * ( ar + n ) . В ы можете инте рпретировать второе выраже ние как " пе­ ре йти к ячейке памяти ar , пройти через n единиц и там найти нужное значе ние " . В т о ж е время , н е путайте * ( date s + 2 ) с * date s + 2 . О пе рация разыменования (* ) имеет боле е выс окий приоритет, чем + , следовательно, последнее выражение означа­ ет ( * date s ) + 2 : * ( dates + 2 ) * dates + 2 / * знач е ние тр е т ь е г о элеме нта массива dat e s */ / * 2 при бавля е тся к знач е нию первого элемента * / Такая зависимость между мас сивами и указателями означает, что вы часто можете пользоваться любым из подходов при написании программы. Программа в листинге 1 0 . 9 , например, после компиляции генерирует те же выходные данные , что и про­ грамма в листинге 1 0 . 1 . Листинг 1 0.9. Проrрамма dау_пюn3 . с / * day_mon3 . c исполь зуются обоз нач е ния ч ер е з ука з атели * / #incl ude <s tdio . h > #de fi ne MONTHS 1 2 - - int main ( vo i d ) { int days [ MONTH S ] int inde x ; = { 31, 28, 31, 30 , 31, 30 , 31,31, 30, 31 , 30 , 31) ; for ( index О ; inde x < MONTHS ; index++ ) print f ( "Me cяц % 2 d име е т % d дней ( l tym) . \ n " , i ndex + 1 , * ( d ays + inde x ) ) ; / / то же , ч то и day s [ inde x ] r e turn О ; = В расс матривае мом случае days представляет собой адрес пе рвого элемента масси­ ва , индекс days + index - адре с эле мента days [ index ] , а * ( day s + inde x ) - значение этого эле мента , так же как и days [ index ] . Цикл пооче редно прос матривает элементы массива и выводит на печать значе ния , которые в них находит . О бладает ли какими­ либ о пре имуще ствами программа , написанная подобным образом? По сути, никаких пре имуществ она не предоставляет . О сновное назначение программы в листинге 1 0 .9 состоит в том , чтобы показать, что подходы с ис пользованием запис и через мас с ивы и чере з указатели являются эквивалентными. Этот приме р демонстрирует , что вы мо­ жете ис пользовать для масс ивов с истему обозначения с помощью указателей. О брат· ное утве рждение также верно - вы можете использовать систему обозначе ния масси­ вов в отношении указателей. Это имеет значение в случае работы с функцией, аргу­ ментом которой является мас с ив. М ассивы и указатели 41 1 Функци и, м асси вы и ука з ател и Предположим , что вы хотите написать функцию , которая выполняет опе рации над массивами. Например, нужна функция , возвращаю щая сумму эле ментов мас с ива. Предположим, что marЫ e s имя массива значе ний типа int. Какой вид будет иметь вызов такой функции? Здравый смысл подсказывает, что он должен иметь вид: - total = s um (marЫ e s ) ; / / в о зможный вызов функции Каким долже н быть прототип этой функции? Вс помните , что именем массива яв­ ляется адре с его первого элемента , так что фактический аргумент marЬl e s , будучи ад­ ре сом значе ния int , должен б ыть присвое н формальному параметру, каковым являет­ ся указатель на тип int: int s um ( int * ar ) ; / / соотв е т с твующий пр ототип Какую информацию получает функция s um ( ) благодаря этому аргументу? О на по­ лучает адрес первого элемента массива , она также узнает, как найти значение int в этой яче йке . Обратите внимание на то , что эта информация ничего не говорит о ко­ личе стве эле ментов в мас с иве. Мы поставле ны пе ред выб ором одного из двух вариан­ тов продолже ния определения функции. Пе рвый вариант заклю чается в том, чтобы каким-то образом закодировать фиксированный разме р мас с ива в функцию : int s um ( int * ar ) { int i ; int total = О ; for ( i = О ; i < 1 0 ; i++ ) total += ar [ i ] ; r etur n total ; / / соотв е т с твующе е опр еделение / / пр едпол аг а е т ся наличие 1 0 элементов // ar [ i ] то же , ч то и * ( ar + i ) В данном случае используется тот факт, что аналогично возможности применения системы обозначе ний через указатели к мас сивам, можно употре блять с истему об о­ значений мас с ивов к собственно указателям . Кроме того , вспомните , что операция += добавляет значение операнда, стоящего с права от знака операции, к операнду, стоя­ ще му слева от знака операции. Следовательно, итоговое значение представляет с обой сумму элементов массива . О пределение этой функции ограниче но ; она может работать только с массивами, соде ржащими 10 элеме нтов . Б олее гибкий подход состоит в том, что разме р массива передается в качестве второго аргумента: int s um ( int * ar , int n) { int i ; int total О; for ( i = О ; i < n ; i++ ) total += ar [ i ] ; r etur n total ; 1 1 более о бщий подход 11 и споль з уются n элементов // ar [ i ] э то то же , ч то и * ( ar + i ) - В данном случае первый параметр говорит функции о том, где можно найти мас­ сив, и какой тип данных элементов мас с ива , а второй параметр уведомляет функцию о том, сколько элементов содержится в мас сиве . 41 2 Гл ава 1 0 О параметрах функции необходимо сказать следую ще е . В контексте прототипа функции или заголовка определения функции, и толъхо в этом контексте , можно по­ ставить int ar [ ] вместо int * ar : int s um ( int ar [ ] , i nt n ) ; Форма i nt * ar все гда означает, что ar является типом указателя на значе ние int . Форма int ar [ ] также означает, то ar это тип указатель на int, однако только в тех случаях, когда он используется толъхо для объявле ния формальных параметров. Идея заклю чается в том, что вторая форма напоминает, что ar не просто указывает на зна­ че ние типа int, он указывает на значение i nt, которое является элементом массива . - Объяв лен и е п араметров м асси в а Поскол ьку и мя масси ва - это адрес его перво го элемента , фактический а ргумент в ви­ де имени массива требует, чтобы соответствующий формал ьны й аргумент был указа­ телем. В этом контексте, и только в нем, С интерпрет ирует int ar [ ] ка к int * ar , то есть ar является указателем на тип i nt. Поскол ьку протот ипы позвол я ют опускать имя, все чет ы ре показанных ниже прототипа экви валентны : i n t s um ( int * ar , int n ) ; int s um ( int * , int ) ; int s um ( int ar [ ] , int n ) ; int s um ( int [ ] , int ) ; Вы не можете опускать имена в определен иях функций, следовател ьно , с точ ки зрения определений, следующие две форм ы эквивалентны : int s um ( int * ar , int n ) { 1 1 з д е с ь находится прогр аммный КО Д int s um ( int ar [ ] , int n ) ; 1 1 з д е с ь находится прогр аммный код Вы должны и меть возможность испол ьзовать л юбой из указанных в ы ше прототипов с л юбы м из двух определений, при веденных выше. В листинге 1 0 . 1 0 представле на программа, ис пользую щая функцию s um ( ) . Чтобы продемонстрировать интерес ные о с обенности аргументов типа мас сива , программа распечатывает также разме р исходного файла и размер параметра функции, пред­ ставля ю щего мас с ив. (Ис пользуйте спецификатор % u или, возможно , % l u , если ваш компилятор не подд е рживает с пе цификатор % zd для печати з на че ний функции s i zeo f . ) листинг 1 0.1 0. Программа swn_arrl . с 1 1 s um arr l . c сумма элеме нтов массива 11 исполь зуйте специфик аторы % u или % l u , е сли спецификатор % zd не работает #incl ude <s tdio . h > #de fi ne S I Z E 1 0 int s um ( int ar [ ] , int n ) ; - - М ассивы и указатели 41 3 int main ( vo i d ) { int marЫ e s [ S I ZE ] = { 2 0 , 1 0 , 5 , 3 9 , 4 , 1 6 , 1 9 , 2 6 , 3 1 , 2 0 ) ; l o ng answer ; answer = s um (marЬl e s , S I ZE ) ; printf ( " Общая сумма элеме нтов массива marЬl e s равна % l d . \ n " , answe r ) ; printf ( "Oбъeм памяти, отведенной под массив marЫ es , составляет % zd байт . \n" , s i zeo f marЫ e s ) ; r e turn О ; int s um ( int ar [ ] , int n ) int i ; int total 1 1 каков р азмер массив а ? О; for ( i = О ; i < n ; i + + ) total += ar [ i ] ; printf ( " Paзмep переменной a r со ставля е т % zd байт . \ n " , s i zeo f ar ) ; r e turn total ; В ыходные данные нашей системы имеют следую щий вид : Ра змер п ер еменной ar сост авля е т 4 бай т . Общая сумма эл ементов мас сива marЬ l e s р авна 1 9 0 . Объем памя ти , о тведе нной под ма ссив marЫ e s , сос тавля е т 4 0 б айт . Обратите внимание на то , чго размер массива marЬl e s раве н 40 байтов. Это име ет смысл, поскольку мас с ив marЫ e s соде ржит 1 0 значе ний типа i nt, каждое из которых занимает 4 байта , что в сумме составляет 40 байт. В то же время размер ar равен всего 4 байта . Это объяс няется тем, что ar не является массивом, это указатель на первый элемент массива marЬl e s . Наша с истема использует четырехбайтовый адрес, таким образом, разме р пе ременной типа указатель с оставляет 4 байта . (Другие с истемы мо­ гут ис пользовать для этой цели другое число байтов.) Короче говоря , в программе из листинга 1 0 . 1 0 marЬ l e s - это мас с ив , ar - указатель на пе рвый элемент массива marЫ e s , а связь в С между массивами и указателями позволяет ис пользовать систему обозначе ний мас с ива для указателя ar . И спользование пара метров ти па указатель Функция , раб отаю щая с мас с ивом, должна знать, где начинать и где заканчивать свои действия . Функция s um ( ) использует параметр типа указатель с те м, чтобы рас· познать начало масс ива и целочисленный параметр, задающий количество элементов массива , подлежащих обработке . (Параметр типа указатель также описывает тип дан­ ных в масс иве . ) Но это не единственный путь с ооб щить функции то , чго она должна знать. Другой с пособ с о стоит в том, чтобы описать мас с ив , пе редавая функции два па­ раметра , при этом пе рвый из них указывает, где начинается масс ив (как и раньш е ) , а второй - где мас с ив заканчивается . Программа в листинге 1 0 . 1 1 служит иллюстрацией этого подхода . Он также ис пользует тот факт, что параметр типа указатель является переменной, а это означает, что вме сто ис пользования инде кс а для указания , к какому 41 4 Гл ава 1 0 элементу массива осуществлять доступ, эта функция может менять само значе ние ука­ зателя, заставляя его поочередно указывать на каждый элемент мас с ива. Листинг 1 0.1 1 . Проrрамма swn_arr2 . с / * s um_arr 2 . c суммир у е т э л ементы массив а * / #incl ude <s tdio . h > #de fi ne S I Z E 1 0 int s ump ( i n t * s t art , i nt * e nd) ; int main ( vo i d ) { int marЫ e s [ S I ZE ] = { 2 0 , 1 0 , 5 , 3 9 , 4 , 1 6 , 1 9 , 2 6 , 3 1 , 2 0 ) ; l o ng answer ; - - answer = s ump ( marЬl e s , marЬl e s + S I ZE ) ; printf ( " Oбщe e колич е ство элемен тов marЬ l e s р авно % ld . \ n " , answer ) ; r e turn О ; / * исполь з о в ание арифме тики указат елей * / int s ump ( i n t * s t art , i nt * e nd) { int total О; whi l e ( s tart < end) { total += * s tart ; s tart++ ; / * добавить з н ач ени е к total * / / * переме сти т ь указ атель на сл едующий элемент * / r e turn total ; Указатель s tart в начале указывает на пе рвый элемент масс ива marЫ e s , таким об­ разом, выражение total + =* start добавляет значение первого эле мента мас сива ( 2 0 ) к значению пе ременной total . Зате м выражение s tart++ увеличивает значе ние пе­ ременной start, благодаря чему она указывает на следую щий элемент мас с ива. По­ скольку s tart указывает на тип int, С увеличивает значение s t art на размер типа int. Обратите внимание на то , что функция s ump ( ) использует различные методы функции s um ( ) для окончания цикла суммирования. Функция s um ( ) ис пользует коли­ чество элементов в качестве второго аргумента , а в цикле это значение применяется как часть прове рки конца цикла: for ( i = О ; i < n; i + + ) Однако функция s ump ( ) для проверки окончания цикла использует второй указа­ тель: whi l e ( s tart < end) Поскольку выполняется прове рка на не раве нство , последним элементом массива , подвергнутым обработке , будет элеме нт, непосредстве нно предшествую щий элемен­ ту, на который указывает end. Это означает , что end указывает на ячейку, которая на­ ходится с разу же за последним элементом мас с ива . С гарантирует , что когда он рас­ пределяет пространство памяти для мас с ива, указатель на первую яче йку после конца М ассивы и указатели 41 5 массива будет допустимым. Благодаря этому обстоятельству, конструкция, подобная данной, также допустим а , так как последним значением, которое s tart получает в цикле, будет end. Обратите внимание на то , что использование указателя , нацеленно­ го "за пределы конца мас с ива" , позволяет осуществить такой вызов: answer = s ump ( marЬl e s , marЬl e s + S I ZE ) ; Поскольку индексирование начинается с О , marЬ l e s + S I ZE указывает на элемент, следую щий за концом массива . Если бы end указывал на последний элемент вместо элемента , следую ще го за концом масс ива , вы должны бы были воспользоваться сле­ дую щим кодом: answer = s ump ( marЫ e s , mar Ы e s + S I ZE - 1) ; Этот код не только менее элегантен по внешнему виду, его , к тому же , труднее запом­ нить, следовательно , из-за чего в программе появляется тенденция к возникновению ошибок. Между прочим, хотя язык С гарантирует допустимость применения указателя marЬ l e s + S I ZE, тем не менее, нет таких гарантий в отношении marЬ l e s [ S I ZE ] , значе­ ния , хранимого в этой ячейке . В ы можете также сжать тело цикла в следую щую строку: total += * s tart++ ; Унарные операции * и + + име ют один и тот же приоритет, но они выполняются справа налево . Это означает , что операция ++ применяется к start, но не к * start. Иначе говоря, увеличивается указатель, но не значение , на которое он указывает . Ис­ пользование постфиксной формы ( s tart++ вме сто + + s tart) означает, что значение показателя не увеличивается , пока значение , на которое нацелен указатель, не будет добавлено к tot a l . Если бы программа использовала конструкцию * + + s tart, порядок был бы следую щий: увеличе ние значе ния указателя с последую щим ис пользованием значения , на которое нацелен указатель. Однако если в программе используется кон­ струкция ( * start) + + , она использует значение s tart, после чего увеличивается зна­ че ние , но не указатель. При этом указатель будет нацелен на тот же элемент, но этот элемент содержит новое число . И хотя запись вида * s t art++ используется достаточно широко , мы рекомендуем более удоб очитаемую форму записи: * ( s tart++ ) . Програм­ ма, показанная в листинге 1 0 . 1 2 , служит иллю страцие й всех этих " прелестей" приори­ тетов операций. Листинг 1 0.1 2. Проrрамма order . с / * or der . c приоритеты опер аций с указ ателями * / #incl ude <s tdio . h > int data [ 2 ] = { 1 0 0 , 2 0 0 ) ; int moredat a [ 2 ] = { 3 0 0 , 4 0 0 ) ; int main ( vo i d ) -- int * p l , * р 2 , * р З ; p l = р 2 = data ; р З = mor edata ; *р2 printf ( " * p l %d, *р2 *pl printf ( " *p l++ % d , * ++р 2 *p l++ 1 * ++р 2 %d, * %d, рЗ = % d \ n " , *р З ) ; ( *р З ) ++ = % d\ n " , ( *р З ) ++ ) ; 41 6 Гл ава 1 0 printf ( " *p l *pl %d, *р 2 *р 2 %d, * р 3 = % d\ n " , *р3 ) ; r e turn О ; В от как выглядят выходные данные этой программы : *pl *p l++ *pl *р 2 100, 1 0 0 , * ++р2 *р 2 2001 100, 2001 2001 *р3 ( *р 3 ) ++ *р3 300 300 301 Единственная операция, которая меняет значе ние мас с ива - ( *р 3 ) + + . Две другие операции приводят к тому, что указатели р 1 и р2 переме щаются на следую щий эле­ мент мас сива . Ком мента р и и: указатели и массивы Как в ы уже убедились, функции, выполняю щие обраб отку мас с ивов, по сути, ис­ пользуют указатели в качестве аргуме нтов, однако при с оздании функций обработки массивов потре буется выбрать форму записи - с помощью массива или с помощью указателе й. Приме нение формы записи с использованием мас с ива , как в листинге 1 0 . 1 0 , по­ зволяет ле гче определять, что данная функция работает с масс ивами. Наряду с этим, запис ь в форме мас с ива более привычна для программистов, работаю щих в других языках программирования , та ких как, наприме р , FORTRAN , Pascal, Mod ula-2 или BASIC. Другие программисты , возможно, предпочитают работать с указателями, и для них форма запис и с использование указателей, подобная продемонстрированной в листинге 1 0 . 1 1 , является более привычной. Что касается языка С , то два выраже ния ar [ i ] и * ( ar+i ) эквивалентны по смыслу. Оба раб отают в тех случаях, когда ar есть имя мас с ива, оба они также работают, если ar - пе ременная типа указатель. Тем не менее , использование такого выражения , как ar+ + , работает только в тех случаях, когда ar представляет собой переменную типа указатель. Запись с применением указателей, в частности, когда они сопровождается опера­ цией инкремента , ближе к машинному языку, а при ис пользовании некоторых компи­ ляторов дает более эффективный программный код. В то же вре мя , многие разделяют мне ние о том, что ос новная задача программиста - с облю сти правильность и яс ность программного кода , а оптимизация кода вме няется в обязанности компилятора . О п ерации с ука зателя ми Так что же можно делать с указателями? Язык С предлагает множество б азовых операций, которые можно выполнять над указателями, а приведенная ниже програм­ ма демонстрирует восемь из существую щих возможностей. Чтобы показать результаты каждой операции, программа выводит на пе чать значение указателя (таковым являет­ ся адрес , на который он указывает) , значе ние , хранящееся по адресу, на который на­ целен указатель, а также адрес с а мого указателя . (Если ваш компилятор не поддержи­ вает с пецификатор %р, попытайтесь воспользоваться для вывода адресов с пецифика­ тором %u или, возможно , % l u . ) В листинге 1 0 . 1 3 показаны восемь базовых операций, М ассивы и указатели 41 7 которые можно выполнять над пе ременными типа указатель. В дополнение к этим операциям вы можете использовать опе рации отноше ний при сравнении указателей. листинг 1 0.1 3 . Проrрамма ptr_op s . с 1 1 ptr_op s . c - - о п ер ации над указа телями #incl ude <s tdio . h > int main ( vo i d ) { int urn [ 5 ] = { 1 0 0 , 2 0 0 , 3 0 0 , 4 0 0 , 5 0 0 ) ; int * ptr l , * ptr 2 , *ptr 3 ; ptr l urn ; 1 1 присв аив ание ука з ателю адр е с а p t r 2 = & urn [ 2 ] ; 1 1 в торой экз емпляр 11 р азыменовани е ука з ателя и взя тие 11 адр е с а указа теля printf ( " значение указа тел я, разыменов анный указатель , адр ес указателя : \ n " ) p r i nt f ( " ptr l = %р , * ptr l = % d , &ptr l = % p \ n " , ptr l , *ptr l , &ptr l ) ; / / сложение ука з ателей p t r 3 = p tr l + 4 ; printf ( " \ ncлoжeниe з н ач ения int с ука з ателем : \ n " ) ; p r i nt f ( " ptr l + 4 = % р , * ( p tr 4 + 3 ) = % d\ n " , ptr l + 4 , * ( ptr l + 3 ) ) ; ptr l++ ; / / увелич ение з н ач ение ука з ателя на 1 рrintf ( " \ nзнач ения п о сле выполн ения о п ер ации ptr l++ : \ n " ) ; p r i nt f ( " ptr l = %р , * ptr l = % d , &ptr l = % p \ n " , ptr l , *ptr l , &ptr l ) ; ptr 2 - - ; / / уменьш ение з н ач ение ука з ателя на 1 рrintf ( " \ nзнач ения п о сле выполн ения о п ер ации - -p tr 2 : \ n " ) ; printf ( " ptr 2 = %р , * ptr 2 = % d , &ptr 2 = % p \ n " , ptr 2 , *ptr 2 , &ptr 2 ) ; --ptr l ; / / восстановление исходног о знач ения ++ptr 2 ; / / восстановление исходног о знач ения рrintf ( " \ nвосс тановление исходных значений ука з а телей : \ n " ) ; p r i nt f ( " ptr l = %р , p t r 2 = % p \ n " , ptr l , ptr 2 ) ; / / вьNитание одного указат еля из друг о г о p r i n t f ( " \ nвьNи тание одног о указ ателя из дру г ог о : \ n " ) ; p r i nt f ( "ptr2 = %р , p tr l = %р , p t r 2 - ptr l = % d\ n " , ptr 2 , ptr l , ptr 2 - ptr l ) ; 1 1 вьNитание целого знач ения из указат еля printf ( " \ nвьNи тaниe из ук а з а теля знач ения типа i nt : \ n " ) ; p r i nt f ( "ptr3 = %р , p t r 3 - 2 = % p \ n " , ptr 3 , ptr 3 - 2 ) ; r e turn О ; Ниже показаны выходные данные этой программы : з н ач ение указа теля , р азыменов анный ук азател ь , адр е с ук а з а теля : ptr l = O x 0 0 1 2 f f3 8 , * ptr l = 1 0 0 , &ptr l = O x 0 0 1 2 f f3 4 сложение знач е ния int с указате лем : ptr l + 4 = O x 0 0 1 2 f f 4 8 , * ( p tr 4 + 3 ) = 4 0 0 ,· 41 8 Гл ава 1 0 з н ач е ния после выполнения опер а ции ptr l++ : ptr l = O x 0 0 1 2 f f3 c , * ptr l = 2 0 0 , &ptr l = O x 0 0 1 2 f f3 4 з н ач е ния после выполнения опер а ции --ptr 2 : p t r 2 = O x 0 0 1 2 f f3 c , * ptr 2 = 2 0 0 , &ptr 2 = O x 0 0 1 2 f f 3 0 во сстано вление исходных з н ач е ний указ ателей : ptr l = O x 0 0 1 2 f f3 8 , p t r 2 = O x 0 0 1 2 f f 4 0 вычитани е одно г о ука з ателя из друг ог о : p t r 2 = O x 0 0 1 2 f f 4 0 , p tr l = O x 0 0 1 2 f f3 8 , ptr 2 - ptr l 2 вычитани е из указателя з н ач е ния типа int : ptrЗ = O x 0 0 1 2 f f 4 8 , ptrЗ - 2 = O x 0 0 1 2 f f 4 0 В представленном ниже перечне опис аны базовые опе рации, которые могут быть выполнены над указателями. • Присваивание значений . Указателю можно прис воить адрес. Обычно это де­ лается путем использования имени мас с ива или опе рации адресации ( & ) . В рас­ сматриваемом примере пе ременной ptr l прис ваивается адрес начала массива urn. Этим адресом оказался номер яче йки памяти O x 0 0 1 2 f f3 8 . Переменная ptr 2 получает в качестве с воего значения адрес третьего , и последнего, элемента urn [ 2 ] . О братите внимание , что этот адрес должен быть совместим с типом указателя . Иначе говоря , вы не можете прис ваивать адрес типа douЫ e указате­ лю , сс ылающемуся на значение типа int, по крайней мере, без приведе ния ти­ пов, которое в такой ситуации чре вато пагубными последствиями. Это правило было введе но стандартом С99. • • Определение значения (разыменование) . О пе рация * возвращает значение , хранящееся в ячейке , на которую с сылается указатель. По этой причине перво­ начальным значением * ptr l является 1 0 О , это значение хранится в ячейке Ox0 0 12 ff3 8 . Адрес указателя . Подобно всем переменным, пере менные типа указатель име­ ют адре с и значение . Операция & определяет, где хранится с а м указатель. В рас­ сматриваемом примере ptr l хранится в ячейке O x 0 0 1 2 f f3 4 . Содержимое этой ячейки памяти - O x 0 0 1 2 f f3 8 , что является адре сом мас сива urn. • • Сложение целочисленного значения с указателем . С помощью опе рации + можно сложить целое число с указателем или указатель с целым числом. В лю­ бом из этих случае в целое число умножается на количе ство б айт, представляю­ ще е тип данных, на который ссылается указатель, после чего ре зультат добав­ ляется к пе рвоначальному адресу. Результат операции p tr l + 4 тот же, что и ре­ зультат опе рации &urn [ 4 ] . Ре зультат сложения не определен, если он выходит за пределы мас сива , на который с сылается исходный указатель, за ис клю че ни­ ем сс ылки на адрес , следую щий за концом массива ; в этом случае значе ние с чи­ тается допустимым. Инкремент значения указателя. Увеличение значения указателя на величину эле мента масс ива заставляет указатель сс ылаться на следую щий элемент масси­ ва . По этой причине операция ptr 1 ++ увеличивает значение указателя ptr 1 на 4 (в наше й системе под каждое значение типа int отводится 4 байта ) , в ре зульта- М ассивы и указатели 41 9 те указатель ptr l сс ылается на urn [ l ] (рис . 1 0 .4) . Теперь ptr l имеет значение O x 0 0 1 2 f f 3 c (адрес следую ще го эле мента массива ) , а *p tr l значе ние 2 0 0 (зна­ че ние элеме нта urn [ 1 ] ) . Обратите внимание , что сам адрес ptr 1 не меняется и остается равным О х О О 1 2 f f З 4 . В конце концов, пе ременная не перемещается в памяти по причине того , что она поменяла с вое значе ние ! - urn [ O J 1 1 urn [ l ] � � O ODC O ODD 100 ... ptrl 1 1 O ODE 1 O ODF 200 � O O FO 1 O O Fl 30 0 Элемент массива ptrl urn [ 2 ] 1 1 1 Е �1 � осо о 1 OCOl O ODC ... Адрес в памяти 1 1 Значения массива Адрес хранится здесь *ptrl представляет собой значение, храня щееся по адресу OOD C , которое на текущий момент равно 1 00 ptrl=urn ; ptrl set to O ODC then ptrl++ sets ptrl to O ODE и т.д. Р и с . 1 0.4. Увели-чепие указателя па зпа-чепие типа in t • • • С помощью опе рации - можно вычи­ тать целое число из указателя ; указателем должен быть пе рвый опе ранд или указатель на целое число. Целое число умножается на количество б айт в типе , на который сс ылается указатель, а ре зультат умножения вычитается из перво­ начального адре с а . Ре зультат опе рации вычитания p t r З - 2 аналогиче н ре зуль­ тату опе рации & urn [ 2 ] , пос кольку p t r З указывает на &urn [ 4 ] . Результат сложе· ния не определен, если он выходит за пределы массива , на который сс ылается исходный указатель, за исклю чением ссылки на адрес, следую щий за концом массива ; в этом случае значение с читается допустимым. Вычитание целочисленных значений . Декремент значения указателя . Разуме ется, значение указателя можно де кре­ ментировать. В рассматриваемом приме ре уме ньшение значение p t r 2 на еди­ ницу приводит к тому, что он ссылается не на третий, а на второй элемент мас­ сива . Обратите внимание , что вы можете пользоваться префиксными и пост­ фиксными формами опе раций инкремента и де креме нта. Также об ратите внимание на то , что оба указателя ptr 1 и ptr 2 указывали на один и тот же эле· мент, в данном случае это urn [ 1 ] перед тем, как их значение было измене но . Вычис ление разн о с ти указателей. Имеется возможность определить разность между двумя указателями. Обычно это делается для двух указателей на элемен­ ты, принадлежащие одному и тому же масс иву, с тем , чтобы определить, на­ сколько далеко они отстоят друг от друга . Результат представляется в тех же единицах, что и размер типа . Наприме р, в выходных данных программы из листинга 1 0 . 1 3 выраже ние p t r 2 - p tr l имеет значе ние 2 , что означает , что эти 42 0 Гл ава 1 0 указатели ссылаются н а объе кты, разделе нные двумя значе ниями int, н о не двумя байтами. Вычитание является гарантировано допустимой операцией, ес­ ли только оба указателя с с ылаются на один и тот же массив (или, возможно , на позицию , следую щую непосредственно за концом массива ) . Приме не ние этой операции к указателям в двух различных мас с ивах может дать какой-то ре зуль­ тат, но может привести и к ошибке во вре мя выполне ния программы. • Сравнения указателей . Для сравнения значений двух указателей могут использо­ ваться операции отношений в случае , если указатели имеют один и тот же тип. Обратите внимание на существование двух форм вычитания. Вы можете вычесть один указатель из другого и получить целое число , в то же время вы может вычесть целое число из указателя и получить указатель. При выполнении операций инкре мента и декремента указателя , следует соблюдать определе нные ме ры предосторожности. Компьютер не отслеживает, с с ылается ли полученный в результате указатель на какой-то элемент массива . Язык С гарантирует только то, что для заданного мас сива указатель на лю бой из элементов этого масс ива или на позицию , не посредстве нно следую щую за последним элементом массива , явля­ ется допустимым . Однако результат инкремента или декремента указателя , выходя­ щий за эти пределы , не определен. Наряду с этим, вы можете разыменовать указатель для люб ого элемента масс ива . Тем не менее, несмотря на то , что указатель, с с ылаю­ щийся на эле мент, следую щий за концом масс ива , допустим, нет гарантии того , что этот указатель может быть разыменован. Р аз ым енован и е неи н и ци ализи р о ван ного ук азателя Говоря об осторожност и, существует пра випо, которое должно прочно засесть в вашей памяти: ни когда не разы меновывайте неинициал изированные указатели . На пример, рассмотрим следующий програ м мн ы й код: int * pt ; *pt = 5 ; 1 1 неинициализиров анный ука з а тель 11 к а т астрофич е ск ая оши бка Почему это настол ько плохо? Вто рая строка означает необходимост ь сохранить значе­ ние 5 в ячейке, на которую указы вает p t . Одна ко p t , будуч и инициал изированн ы м , имеет случайное значение , следовательно, неизвестно , куда будет записано значе­ ние 5. Оно может не вызвать каких-л ибо пагубных последст вий, но оно может затереть какой-то код ил и данные, а может вообще привести к аварийному завершению про­ гра м м ы . Вспомните, что при создании указателя память выделяется под сам указател ь, но для хранения данных память не распределяется. Поэтому, прежде чем вы воспол ь­ зуетесь указателем, ему должен быть присвоен адрес ячейки па мяти, которая уже была распределена. На пример, вы можете назначить указател ю адрес существующей пере­ менной. (Именно это и происходит, когда вы испол ьзуете функцию с параметром типа указател ь. ) Л ибо вы можете воспол ьзоваться функцией mal l o c ( ) , которая расс матри­ вается в главе 1 2, чтобы сначала зарезервировать нужную память. В л юбом случае, чтобы не допускать ошибо к, не разы меновы вайте неинициал изиро­ ванные указатели ! do uЫ e * p d ; *pd = 2 4 ; - 1 1 неинициализиров анный ук азатель 1 1 НЕ ДЕЛ АЙТЕ э того ! М ассивы и указатели 42 1 Пусть дано int urn [ З ] ; int * ptr l , * ptr 2 ; ниже приведены примеры допустимых и недопустимых операторов: Допустимый onepam&jJ ptr l++ ; Недопустимый опер атор urn+ + ; ptr2 p tr l + 2 ; ptr 2 ptr 2 + ptr l ; ptr2 urn + 1 ; ptr 2 urn * ptr l ; О писанные ранее операции открывают множе ство возможностей. Программисты, работаю щие на языке С, создают мас с ивы указателей, указатели на функции, масс ивы указателе й на указатели, мас с ивы указателей на функции и так далее. Однако пусть вас это не волнует , мы ограничимся лишь теми основными видами использования указа­ телей, которые рассматривалис ь выш е . Пе рвый основной вид использования указате­ лей с остоит в передаче информации из функций и в функции. Вы уже знаете , что вы должны ис пользовать указатели, е сли хотите , чтобы та или иная функция меняла зна­ че ния пе ременных в вызывающей функции. Второй вид использования касается функций, разработанных для манипулирования мас с ивами. Расс мотрим еще один пример программы , использую ще й функции и масс ивы. З ащита содержи м ого м асси ва Когда в ы пишете функцию , которая выполняет обраб отку данных фундаме нталь­ ного типа , такого как int , пе ред вами встает выбор: пе редать данные типа int по зна­ че нию или передать указатель на значение типа int . О бычное правило предус матри­ вает передачу числовых данных по значению до тех пор, пока в программе не появит­ ся потре бность изменить это значение , в этом случае вы передаете указатель. Мас сивы не предоставляют такой возможности, и вы обязателъ'Но должны передавать указатель. Это делается с целью обес пе чить эффективность программы . Если функция передает массив по значению , она должна иметь в своем рас поряжении достаточное пространство , чтобы с охранить копию исходного массива , после чего скопировать все данные из исходного массива в новый массив. Гораздо быстрее пе редать адрес массива , чтобы функция работала с исходным массиво м . П р и использовании такого метода возникает ряд проблем. Причиной того , что С об ычно передает данные по значению , является стремле ние сохранить целостность данных. Если функция работает с копией исходных данных, она не сможет случайным образом исказить эти данные . В то же вре мя, поскольку функции, выполняю щие обра­ ботку мас с ива , работают с исходными данными, они могут исказить массив. Иногда это желательно. Наприме р, рассмотрим функцию , которая прибавляет одно и то же значение к каждому элементу мас сива : void add to ( do uЫ e ar [ ] , i nt n , douЫ e val ) { int i ; for ( i О ; i < n ; i++ ) ar [ i ] += val ; 42 2 Гл ава 1 0 Следовательно , вызов функции add_to ( p r i c e s , 1 0 0 , 2 . 5 0 ) ; приводит к тому, что каждый эле мент мас с ива p r i c e s получает величину, превосхо­ дящую е го исходное значение на 2 . 5 ; эта функция изме няет содержимое мас с ива. Функция может сделать это , поскольку, раб отая с указателями, она использует исход­ ные данные . Другие функции, однако, н е должны изменять данные . Следую щая функция, на­ пример, предназначена для подс чета суммы содержимого мас с ива, и она не меняет массива . Однако, поскольку ar е сть фактический указатель, программная ошибка мо­ жет привести к искаже нию исходных данных. Зде с ь, например, выражение ar [ i ] ++ приводит к тому, что значение каждого элемента увеличивается на 1 : int s um ( int ar [ ] , int n ) / / ошибочный прогр аммный код { int i ; int total = О ; for ( i = О ; i < n ; i++ ) total += ar [ i ] ++ ; / /по ошибке увеличивае тся знач ени е каждого элемента r etur n total ; Использование cons t с формальн ы м и пара метра ми П р и ис пользовании компилятора K&R С единственный с пособ избежать ошибок такого вида - быть постоянно бдительным. В стандарте ANSI С существует альтерна­ тива . Если функция не предназначена для того , чтоб ы менять содержимое массива , используйте клю че вое слово cons t при объявлении формального параметра в прото­ типе и в определении функции. Например , прототип и определе ние функций s um ( ) должны иметь следую щий вид : int s um ( cons t int ar [ J , int n ) ; int s um ( cons t int ar [ J , int n ) { int i ; int total О; for ( i = О ; i < n ; i++ ) total += ar [ i ] ; r etur n total ; / * пр ототип * / / * определ ение * / Это клю чевое слово с ообщает компилятору о том, что функция должна расс матри· вать мас сив, на который ссылается указатель a r , как мас с ив , содержащий констант­ ные данные . В этом случае , если вы случайно ис пользуете такие выражения , как ar [ i ] ++, компилятор может отловить этот случай и выдать сообщение об ошибке , уведомляющее о том, что функция предпринимает попытку изме нить константные данны е . В ажно понимать, что использование клю чевого слова con s t в этом плане не требу­ ет, чтобы все эле менты исходного масс ива б ыли константами, оно просто говорит о том, что функция должна обращаться с элементами массива так, как если бъ; они были константами. М ассивы и указатели 42 3 Использование клю чевого слова con s t подобным образом обес печивает защиту массивов, какую передача по значению обеспечивает для фундаментальных типов . В общем случае, если вы пишете функцию , предназначенную для модификации масси· ва , не используйте ключе вое слово con s t при объявлении параметров мас сива . Если вы пишете функцию , не предназначенную для модификации мас сива , вы можете ука· зать клю чевое слово co n s t при объявле нии параметров мас с ива. В программе, показанной в листинге 10.14, одна функция отображает мас сив, а другая - умножает каждый элемент мас с ива на заданное значение . Поскольку первая функция не должна менять значения элементов массивов, она использует клю чевое слово cons t . Пос кольку вторая функция имеет намерение модифицировать мас с ив , в не й словj cons t не используется. листинг 1 0.1 4. Программа arf . c / * ar f . c - - функции , манипулирующи е массив ами * / #incl ude <s tdio . h > #de fi ne S I Z E 5 void s how_ar r ay ( const douЫ e ar [ ] , int n ) ; void mult_ar r ay ( douЫ e ar [ ] , int n , douЫ e mul t ) ; int main ( vo i d ) { do uЬ l e dip [ S I Z E ] = { 2 0 . 0 , 1 7 . 6 6 , 8 . 2 , 1 5 . 3 , 2 2 . 2 2 ) ; р r i nt f ( " Исходный мас сив dip : \ n " ) ; s how_arr ay ( dip , S I ZE ) ; mult_arr ay ( dip , S I ZE , 2 . 5 ) ; p r i nt f ( " Maccив dip после вызова функции mul t_arr ay ( ) : \ n " ) ; s how_arr ay ( dip , S I ZE ) ; r e turn О ; / * выводит содержимо е массив а * / void s how_ar r ay ( const douЫ e ar [ ] , int n ) { int i ; for ( i = О ; i < n ; i + + ) print f ( " % 8 . 3 f " , ar [ i ] ) ; putchar ( ' \ n ' ) ; / * умножает каждый элемент массива на один и тот же множи тель * / void mult_ar r ay ( douЫ e ar [ ] , int n , douЫ e mul t ) { int i ; for ( i О ; i < n ; i++ ) ar [ i ] * = mult ; Ниже приводятся выходные данные этой программы: Исходный массив dip : 2 0 . 0 0 0 1 7 . 660 8 . 2 0 0 15 . 3 0 0 22 . 2 2 0 42 4 Гл ава 1 0 Ма ссив dip после выз о в а ф ункции rnult arr ay ( ) : 5 0 . 0 0 0 4 4 . 15 0 2 0 . 5 0 0 3 8 . 2 5 0 55 . 550 О б рат ите вним а ние , что типом о б е их функции я вля ется v o i d . Ф ункция rnul t_ arr ay ( ) присваивает новые значения эле ментам мас с ива dip , однако при этом механизм возврата значений не используется . Допол нительн ые сведения о кл ючевом слове cons t Как вы убедились выш е , клю чевое слово co n s t можно использовать для создания символических констант: co n s t do uЫ e P I = 3 . 1 4 1 5 9 ; В этих случаях, наряду с прочим, вы могли кое·что сделать в отноше нии директивы #de fi n e , в то же время клю чевое слово con s t дополнительно позволяет создать мае· сивы констант, константные указатели, а также указатели на константы. В листинге 1 0 .4 показано, как используется клю чевое слово const для защиты массива от моди· фикации: #de fine MONTHS 1 2 co n s t i n t days [ MONTH S ] = { 31, 28 , 31, 30 , 31, 30 , 31,31, 30, 31 , 30, 31) ; Если в дальнейше м программный код попытается изменить мас с ив, вы получите сообщение об ошиб ке на этапе компиляции: days [ 9 ] = 44; / * ошибка на э тапе компиляции * / Указатели на константы не могут использоваться для изменения значе ний. Рас· смотрим следую щий программный код : do uЫ e r at e s [ 5 ] = { 8 8 . 9 9 , 1 0 0 . 1 2 , 5 9 . 4 5 , 1 8 3 . 1 1 , 3 4 0 . 5 ) ,· co n s t do uЫ e * pd = r at e s ; / / p d указыв а е т на нач ало массива Вторая строка кода объявляет, что значение типа douЬ l e , на которое указывает pd, есть cons t . Это означает, что вы не можете использовать pd для изменения значений, на которые ссылаются указатели: *p d = 2 9 . 8 9 ; pd [ 2 ] = 2 2 2 . 2 2 ; rates [ O J = 9 9 . 9 9 ; 1 1 н е допускае тся 11 не допускае тся 11 допускается , поскольку rates не являе тся константой Независимо от того , употребляете ли вы форму записи с использованием указате· лей или с использованием мас с ива , вы не можете использовать pd для изме не ния дан· ных, на которые указывают указатели. Однако, обратите внимание на то , что массив r at e s не был объявлен как константа , вы можете продолжать ис пользовать r a t e s с тем, чтобы менять его значения. В то же время , вы можете ис пользовать указатель pd для сс ылки на какой·то другой объект: pd++ ; / * з аставл я е т p d указывать на r a t e s [ l ] - допустимо * / Указатель на константу об ычно применяется в каче стве параметра функции, чтобы указать, что функция не использует указателей для изменения данных. Например, для функции s how_arr ay ( ) из листинга 1 0 . 1 4 можно предус мотреть следую щий прототип: void s how_arr ay ( cons t douЫ e * ar , int n ) ; М ассивы и указатели 42 5 Суще ствует несколько правил, кас аю щихся назначе ния указателей и употре бления клю че вого слова con s t , которые вы должны твердо знать. Во-пе рвых, допускается прис ваивание адре с а как константных, так и не константных данных указателю на константу: do uЫ e r at e s [ 5 ] = { 8 8 . 9 9 , 1 0 0 . 1 2 , 5 9 . 4 5 , 1 8 3 . 1 1 , 3 4 0 . 5 ) ; co n s t do uЫ e l o cked [ 4 ] = { 0 . 0 8 , 0 . 0 7 5 , 0 . 0 7 2 5 , 0 . 0 7 ) ; co n s t do uЫ e * р е = r at e s ; 1 1 доnу стимо ре locked; 1 1 доnу стимо ре = &rates [ 3 ] ; 1 1 доnу стимо В то же вре мя обычным указателям могут быть присвое ны только адреса некон­ стантных данных: do uЫ e r at e s [ 5 ] = { 8 8 . 9 9 , 1 0 0 . 1 2 , 5 9 . 4 5 , 1 8 3 . 1 1 , 3 4 0 . 5 ) ; co n s t do uЫ e l o cked [ 4 ] = { 0 . 0 8 , 0 . 0 7 5 , 0 . 0 7 2 5 , 0 . 0 7 ) ; do uЫ e * pnc = r at e s ; 1 1 допу стимо p n c = lo cked; / ! не допустимо p n c = &r ates [ 3 ] ; 1 1 допу стимо Это разумное правило. В противном случае вы можете использовать указатель для изме не ния данных, которые рассматривались как константные . Последствия применения этих правил на практике состоят в том, что функция , на­ приме р, s how_arr ay ( ) , может принимать имена обычных масс ивов и постоянных массивов в качестве фактиче ских аргуме нтов, пос кольку каждый из них может быть прис воен указателю на константу: s h ow_arr ay ( r at e s , 5 ) ; s h ow_arr ay ( l o c ke d , 4 ) ; 1 1 допу стимо 1 1 допу стимо В то же время функция , подобная mult_arr ay ( ) , не может принимать име ни кон· стантного массива в качестве аргумента : mult_arr ay ( r at e s , 5 , 1 . 2 ) ; mult_arr ay ( l o c ke d , 4 , 1 . 2 ) ; 1 1 допу стимо / ! не допустимо В силу этого обстоятельства использование клю чевого слова в определении пара· метра функции не только обеспе чивает защиту данных, он также позволяет функции работать с массивами, которые были объявлены как con s t . Суще ствуют и другие варианты использования клю чевого слова con s t . Наприме р, вы можете объявить и инициализировать указатель, который не может указывать на что угодно . Все завис ит от того, где размещается клю чевое слово const: do uЫ e r at e s [ 5 ] = { 8 8 . 9 9 , 1 0 0 . 1 2 , 5 9 . 4 5 , 1 8 3 . 1 1 , 3 4 0 . 5 ) ; do uЫ e * cons t р е = r at e s ; / / р е указыв а е т на нач ало массива ре = &rates [ 2 ] ; 11 н е допу стимо *р с = 9 2 . 9 9 ; / / пр авил ь но - изменя е т элемент rates [ O ] Такой указатель все еще может ис пользоваться для изменения значений, но он мо­ жет указывать только на ячейку, первоначально присвое нную ему. И, наконец, вы можете воспользоваться клю чевым словом дважды для создания указателя , который не может изменить ни адрес , на который он указывает, ни значе· ние , на которое он указывает: 42 6 Гл ава 1 0 do uЫ e r at e s [ 5 ] = { 8 8 . 9 9 , 1 0 0 . 1 2 , 5 9 . 4 5 , 1 8 3 . 1 1 , 3 4 0 . 5 ) ; co n s t do uЫ e * cons t р е = r at e s ; ре = &rates [ 2 ] ; 1 1 н е допус тимо *р с = 9 2 . 9 9 ; 1 1 не допус тимо Ука зател и и м ногомерные м ассивы Как указатели связаны с многомерными мас сивами? И почему это необходимо знать? Функции, которые работают с многоме рными мас сивами, ис пользуют с этой целью указатели, поэтому вы должны продолжить изучение указателей, прежде чем переходить к работе с такими функциями. А что касается первого вопрос а , то рас­ смотрим нес колько примеров и постараемся найти на не го ответ. Чтобы упростить этот проце с с , будем работать с мас с ивами небольших размеров. Предположим, что имеется следую щее объявление : int zipp o [ 4 ] [ 2 ] ; / * массив массив ов з н ач е ний int * / В этом случае zippo, будучи именем массива , представляет собой адрес первого эле­ мента массива . Тогда первый элемент массива zippo сам является массивом, состоящим их двух значений типа int, следовательно , zippo - это адрес массива, состоящего из двух значений типа int. Проведем дальнейший анализ с учетом свойств указателей: • • • Поскольку z ippo - адре с первого элемента масс ива , zippo имеет то же значе­ ние , что и & zippo [ О ] . Далее , zippo [ О ] с ам по себе есть мас с ив из двух целых чи­ сел, следовательно , zippo [ О ] имеет то же значение, что и & zippo [ О ] [ О ] , адрес его первого элемента , то есть int. Короче говоря , zippo [ О ] есть адрес объекта , размер которого выражается через величину типа int, а zippo - адрес объекта , имею щего размер, равный размеру двух типов int. Поскольку и целое значение, и массив, состоящий из двух целочисленных значений, начинаются в одной и той же ячейке , zippo и zippo [ О ] имеют одно и то же числовое значение. Добавле ние 1 к указателю или адресу дает значение , которое больше исходного на размер объекта ссылки. В этом отношении zippo и zippo [ О ] отличаются друг от друга , пос кольку zippo ссылается на объект, име ю щий размер двух ти­ пов int, а zippo [ О ] ссылается на объект размером в один тип int. Следователь­ но , zippo + 1 имеет значение , отличное от zippo [ О ] + 1 . Разыменование указателя или адреса (применение опе рации * или опе рации [ ] с индексом) позволяет получить значение , представляе мое объектом ссылки. Поскольку zippo [ О ] является адресом его первого эле мента , ( zippo [ О ] [ О ] ) , * ( zipp o [ О ] ) представляет значе ние , хранящееся в zipp o [ О ] [ О ] , то есть значе­ ние типа int. Аналогично , * z ippo представляет значение е го первого элеме нта , то е сть zippo [ О ] , в то же время zippo [ О ] сам по себе е сть адрес значения типа int. Это адре с & zippo [ O ] [ О ] , следовательно, * z ippo есть & zippo [ O ] [ О ] . При­ менение оператора разыменования к обоим выражениям , приводит к тому, что * * zipp o эквивалентно * & zippo [ О ] [ О ] , при этом оба эти выражения приводятся к z ippo [ О ] [ О ] , представляю щему собой значение типа int. Короче говоря, zippo - это адре с адре с а , к которому операция разыменования должна быть приме не на дважды , чтоб ы получить обычное значение . Адрес адреса или указа­ тель указателя представляют собой примеры двойиого раз'Ымсноваиия. М ассивы и указатели 42 7 Разумеется , возрастание размерности массива увеличивает сложность представле­ ния в виде указателей. В этой точке большинство изучающих язык С начинают пони­ мать, почему указатели считаются одним из наиболее трудных аспе ктов языка . Воз­ можно , вам потребуется е ще раз почитать о свойствах указателей, которые описаны выш е , и только после этого ве рнуться к расс мотре нию программы в листинге 1 0 . 1 5, где выводятся значения не которых адре сов и содержимое массивов. Листинг 1 0.1 5. Проrрамма zippol . c / * zippo l . c - - информация о массив е zipp o * / #incl ude <s tdio . h > int main ( vo i d ) { int zipp o [ 4 ] [ 2 ] = { { 2 , 4 ) , { 6 , 8 ) , { 1 , 3 ) , { 5 , 7 ) } ; zippo + 1 = % p \ n " , zippo %р , zippo + 1 ) ; zippo , printf ( " zippo [ O ] % р , zippo [ O ] + 1 = % p \ n " , zippo [ O ] , zippo [ O ] + 1 ) ; * zippo + 1 = % p \ n " , printf ( " * zippo = % р , * zippo , * zippo + 1 ) ; % d \ n " , zippo [ O ] [ 0 ] ) ; printf ( " zippo [ O ] [ О ] % d \ n " , * z ippo [ O ] ) ; printf ( " • zippo [ O ] % d \ n " , * * zippo ) ; • • zippo printf ( " zippo [ 2 ] [ 1 ] % d\ n " , zippo [ 2 ] [ 1 ] ) ; printf ( " printf ( " * ( * ( zippo+ 2 ) + 1 ) = % d\ n " , * ( * ( zipp o+ 2 ) + 1 ) ) ; r e turn О ; printf ( " Ниже показаны ре зультаты выполнения этой программы в одной из с исте м : O x 0 0 6 4 fd3 8 , zippo + 1 zippo zippo [ O ] = O x 0 0 6 4 fd3 8 , zippo [ O ] + 1 * zippo + 1 • zippo = O x 0 0 6 4 fd3 8 , 2 zippo [ O ] [ О ] • zippo [ O ] = 2 * * zippo = 2 zippo [ l ] [ 2 ] 3 * ( * ( zipp o+ l ) + 2 ) 3 O x 0 0 6 4 fd4 0 O x 0 0 6 4 fd3 c O x 0 0 6 4 fd3 c Другие системы могут отображать различные адресные значения, однако отноше­ ния будут такими же , что и описанные в этом разделе . Эти выходные данные показы­ вают, что адрес двумерного масс ива zipp o и адре с одномерного массива zippo [ О ] одни и те же адреса. Каждый из них представляет с обой адре с первого эле мента соот­ ветствую щего мас с ива , что в числовом виде то же самое , что и & zippo [ О ] [ О ] . Тем не менее, здесь име ются определенные различия . В нашей системе тип int за­ нимает 4 байта. Как уже говорилось выш е , адре с zippo [ О ] указывает на 4-байтный объект. Добавляя 1 к zippo [ О ] , мы должны получить адре с , больший исходного на 4 . Имя zippo представляет собой адре с масс ива , состояще го и з двух значений типа int, таким образом, он идентифицирует 8-б айтовый объект данных. По этой причине до­ бавление 1 к z ippo должно иметь с воим результатом адрес , превышающий исходный на 8 байтов , что и происходит на самом деле . - 42 8 Гл ава 1 0 Приведенная выше программа показывает, что zippo [ О ] и * z ippo идентичны, ка­ кими они и должны быть. Далее , она показывает , что имя двумерного мас с ива должно быть разыме новано дважды, чтобы можно было получить доступ к значению , храня­ ще муся в мас сиве . Это можно сделать, применив дважды опе рацию разыменования (* ) или дважды выполнив операцию квадратных скобок ( [ ] ) . (Это можно сделать, если выполнить один раз опе рацию * и один раз приме нив квадратные скобки [ ] , однако , рассматривая вс е эти возможности, давайте не буде м отклоняться от главной цели. ) В частности, обратите внимание , что выражение zippo [ 2 ] [ 1 ] в системе записи с использованием указателей эквивалентно выражению * ( * ( zippo+ 2 ) + 1 ) в системе запис и с использование м указателей. Вы, должно быть, хотя б ы раз в с воей жизни пы­ талис ь нарушить это равенство . Будем строить это выражение постепенно , выполняя указанную ниже последовательность шагов: zippo � адрес первого элеменга, состоящего из двух значений типа int. zippo+2 � третий элеменг массива, состоящий из двух значений типа int. * ( zippo+ 2 ) � третий элеменг, представляющий собой массив из двух значений, следовательно, это адрес его первого элеменга, имеющего значение типа i nt. * ( zippo+ 2 ) + 1 � адрес второго элемента двухэлеменгного массива, также имею­ щего значение типа int. * ( * ( zippo+ 2 ) + 1) � значение второго объекта ти m int в третьей строке (zippo [ 2 ] [ 1] ). Цель этой причудливой формы записи с использование м указателей с остоит не в том, чтобы продемонстрировать только то , что вы овладели е ю и можете ис пользо­ вать ее вместо намного б олее простого выражения zipp o [ 2 ] [ 1 ] , но и то, что вы впол­ не можете пользоваться б олее простой формой записи в виде мас с ива, чтобы извлечь значение , когда встречается указатель на двумерный мас с ив . На рис 1 0 .5 показана е щ е одна точка зре ния н а завис имость между адре сами масси­ ва , соде ржимым мас с ива и указателями. zippo zippo+l zippo+2 zippo+3 ! zippo ( 1 ) ! zippo ( 2 ) ! zippo (3) zippo [ О ] zippo [О] [О] Адреса i 0BF2 * z ippo zippo [О] (1) zippo (1) [О] 0BF4 0BF6 r * z ippo+l 1 zippo (1) (1) zippo (2) [ О] OBFS OBFA * z ippo+2 Р и с . 1 0. 5 . Массив массивов zippo (2) ( 1 ) zippo (3) [ О ] OBFC OBFE ! zippo (3) ( 1 ) ос о о М ассивы и указатели 42 9 Указатели на многомерные массивы Как в ы объявите пе ременную p z типа указатель, которая может с сылаться на такой двумерный массив как zipp o? Такой указатель может использоваться , например, при напис ании функции, которая выполняет обработку масс ивов, подобных zipp o . Доста­ точно ли для этого указателя на значе ние типа int? Нет, не достаточно. Этот тип с о­ вместим с zipp o [ О ] , который указывает на какое-то одно значение типа int. Однако zippo е сть адрес е го первого эле мента , который сам представляет собой мас с ив из двух значений типа int. Отсюда следует, что p z должен указывать на мас с ив, содер­ жащий два эле мента типа i nt, а не на какое-то одно значение типа i nt. В от как можно поступить в таком случае : int ( * p z ) [ 2 ] ; 1 1 p z указыв а е т на массив из 2 знач ений типа int Этот опе ратор говорит, что pz является указателем на массив из двух значе ний ти­ па i nt. Зачем в этом случае нужны круглые скобки? Скобки [ ] имеют более высокий приоритет, чем * . Следовательно, в случае, например, такого объявления int * р ах [ 2 ] ; сначала используются квадратные скобки, благодаря которым р ах рассматривается как мас сив двух каких-то объе ктов данных. Далее , применяется опе рация * , в ре зуль­ тате которой мас с ив р а х превращается в мас сив из двух указателей. В заклю чение ис­ пользуется значение int, превращающее р ах в масс ив из двух указателей на значения типа int. Это объявление создает два указателя на значения типа int, тем не менее, первоначальная версия использует круглые скобки с те м, чтобы сначала приме нить операцию * , создавая одип указатель на два значения типа int. Код в листинге 1 0 . 1 6 показывает, к а к можно ис пользовать такой указатель в качестве исходного мас сива . Листинг 1 0.1 6. Проrрамма zippo2 . c / * zippo2 . c - - получ ени е инф ормации о ма ссиве zippo с помощью пер еменной типа ука з атель * / #incl ude <s tdio . h > int main ( vo i d ) { int zipp o [ 4 ] [ 2 ] { {2, 4) , { 6, 8 ) , { 1, 3) , {5, 7) } ; int ( *p z ) [ 2 ] ; p z = zippo ; printf ( " pz %р , p z + 1 = %p\n" , pz, pz + 1) ; printf ( " p z [ O ] = %р , p z [ O ] + 1 = % p \ n " , pz [ O ] , pz [ O ] + l) ; *p z + 1 = % p \ n " , printf ( " *p z = %р , *p z , *p z + 1 ) ; printf ( " p z [ O ] [ О ] % d\ n " , p z [ O ] [ О ] ) ; % d\ n " , *p z [ O ] ) ; printf ( " *p z [ O ] % d\ n " , * * p z ) ; **pz printf ( " pz [2] [ 1] % d\ n " , p z [ 2 ] [ 1 ] ) ; printf ( " printf ( " * ( * ( p z + 2 ) + 1 ) = % d\ n " , * ( * (p z+ 2 ) + 1 ) ) ; r e turn О ; Гл ава 1 0 43 0 Ниже представле ны новые выходные данные : pz O x 0 0 6 4 fd3 8 , pz + 1 O x 0 0 6 4 fd 4 0 pz [ O ] O x 0 0 6 4 fd3 8 , p z [ O ] + 1 O x 0 0 6 4 fd3 c *p z O x 0 0 6 4 fd3 8 , *pz + 1 O x 0 0 6 4 fd3 c pz [О] [О] 2 *pz [ O ] 2 * *p z 2 pz [2] [ 1] 3 * ( * (pz+2 ) + 1 ) 3 = = = = = = = И снова вы можете получить различные адрес а , однако отноше ния остаются пре жни м и . К а к мы о б е ща л и , вы можете ис польз овать такую фо рму запи с и , к а к p z [ 2 ] [ 1 ] , даже е с л и р z является указателем, н о н е имене м массива . В более общих случаях вы можете представлять отдельные эле менты с помощью формы записи с ис­ пользованием мас сива или с использованием указателей применительно либо к имени массива , либо к указателю : zippo [ m ] [ n ] * ( * ( z ippo + m) + n ) p z [m] [ n ] * ( * ( p z + m) + n ) == = = Совмести мость указателей Правила присвое ния одного указателя другому обладают более высоким приорите­ том, чем правила для числовых значе ний. Наприме р, вы можете присвоить значение типа int переменной типа douЫ e без приме не ния пре образования типов , в то же вре мя вы не можете поступить подобным образом в отношении указателей на два эти типа : int n 5 ,­ do uЫ e х ; int * p l &n ; do uЫ e * pd &х ; х n; pd pl; = = = = 1 1 неявное nр еобр а зовани е тип а / / оши бка э т апа компиляции Эти ограничения распространяются на более сложные типы. Предположим, что мы имеем следую щие объявления : int * pt ; int ( *р а ) [ 3 ] ; int ar 1 [ 2 ] [ 3 ] ; int ar 2 [ 3 ] [ 2 ] ; int * *р 2 ; 1 1 ука з атель на ук азатель Тогда мы получаем следую щие раве нства : pt pt pt ра ра р2 *р 2 р2 = = & ar l [ O ] [ O ] ; ar l [ О ] ; ar l ; ar l ; ar 2 ; &pt ; ar 2 [ О ] ; ar 2 ; 1 1 оба - ука з атели на з н ач ени е типа int / / оба - ука з атели на з н ач ени е типа int / / не допустимо 1 1 оба - ука з атели на int [ 3 ] 1 1 н е допустимо 1 1 оба - ука з атели на з н ач ени е типа int * 1 1 оба - ука з атели на з н ач ени е типа int 1 1 н е допустимо М ассивы и указатели 43 1 Обратите внимание , что для всех недопустимых случаев прис ваивания характерно прежде все го то , что используются два указателя, которые указывают на разные типы данных. Наприме р, pt указывает на одно значение int, в то же вре мя ar l - на массив из трех значений типа int. Аналогично , ра указывает на мас с ив из двух значений типа int, следовательно , он с овместим с ar 1, но не с ar 2 , который указывает на масс ив из двух значе ний int. Два последних примера не сколько искусственны . Переменная р 2 представляет с о­ бой указатель на указатель на значе ние типа int, в то время как ar 2 есть указатель на массив, состоящий из двух значений типа int (или, короче, указатель на массив int [ 2 ] ) . Таким образом, р 2 и ar 2 - разные типы , и вы не можете присвоить ar 2 указа· телю р 2 . В то же время *р 2 имеет тип указателя на тип int, что делает е го с овмести· мым с ar 2 [ О ] Напомним, что ar 2 [ О ] является указателем на его первый элеме нт , в данном случае, ar 2 [ О ] [ О ] , что также делает a r 2 [ О ] типом указателя на значение int . В общем случае многократные опе рации разыме нования также носят искус стве н· ный характер. Например , рассмотрим следую щий фрагмент кода: • int "' p l ; co n s t int " р 2 ; co n s t int н рр2 ; pl = р 2 ; 1 1 недопустимо - при сваивание const з н ач е нию н е const р2 = p l ; 1 1 допус тимо - присв аив ани е не cons t знач е нию con s t рр 2 = &p l ,· 1 1 недопустимо - при сваивание н е con s t знач ению const Как вы убедились выше , присваивание указателя типа con s t указателю типа не cons t не допус кается , поскольку вы можете использовать новый указатель для изме· не ния данных типа con s t . В то же вре мя указатель типа не con s t на указатель cons t допустим при условии, что в ы выполняете всего лишь один уровень разыменования : р2 = pl; / / допустимо - присв аив ание н е const з н ач е нию con s t Однако такие присваивания теперь чреваты ошибками, если вы пе рейдете н а два уровня опе рации разыменования . Если бы они были допустимы, вы бы получили воз­ можность выполнить одну из следую щих операций: co n s t int * *рр 2 ; int *p l ; co n s t int n = 1 3 ; рр 2 = &p l ,· 1 1 недопустимо , но пр едполаг ае тся допустимо сть *рр2 = & n ; 1 1 допустимо , оба con s t , однако устанавлива е т pl ссылку на n *p l = 1 0 ; 1 1 допустимо , но при э том изме ня ется con s t Функции и м ногомерные масси вы Если в ы хотите написать функцию , которая выполняет обраб отку двуме рных мае· сивов, то должны иметь достаточно четкое представление об указателях, чтобы де· лать правильные объявле ния , касаю щиеся аргументов функции. В теле самой функции вполне достаточно приме нять форму записи с помощью мас с ивов . Напишем функцию , манипулирую щую двумерными мас с ивами. Одной и з возмож­ ностей является использование цикла for для применения функций об работки одно· мерного мас с ива к каждой строке двумерного массива . 43 2 Гл ава 1 0 Другими словами, в ы можете делать не что подоб ное : in t j u n k [ 3 ] [ 4 ] = { { 2 , 4 , 5 , 8 } , { 3 , 5 , 6 , 9 } , { 1 2 , 1 О , 8 , 6 } } ; int i , j ; int total = О ; for ( i = О ; i < 3 ; i++ ) total += s um ( j unk [ i ] , 4 ) ; / / j un k [ i ] одномерный массив - В с помните , что если j unk е сть двумерный мас с ив , то j unk [ i ] - одномерный мас­ сив, который вы можете рассматривать как строку двуме рного массива . В этом случае функция s um ( ) вычисляет проме жуточную сумму для каждой строки двумерного мас­ сива , а в цикле for выполняется суммирование этих промежуточных сумм. Однако в условиях этого подхода теряется с вязь с информацией, кас аю щейся строк и столб цов. В этом приложении (суммирование вс ех значений) такая информа­ ция не имеет решающего значе ния , однако предположим , что каждая строка пред­ ставляет год , а каждая строка - месяц. В этом случае вам, возможно, понадобится функция , которая , с кажем, суммирует значения, содержащие ся в не котором конкрет­ ном столб це . В этом случае функция должна иметь в своем рас поряжении информа­ цию о столбцах и строках. Этот вопрос можно решить путем объявления правильного вида формальной переменной, благодаря чему функция могла б ы правильно переда­ вать масс ивы. В этом случае мас с ив j unk представляет собой мас с ив трех массивов, каждый из которых соде ржит четыре значе ния типа int. Как показывают ранее про­ веденные обсуждения , массив j unk - это указатель на мас с ив из четырех значений ти­ па int . Вы можете объявить параметр функции этого типа следую щим образом: void s ome function ( i nt ( * pt) [ 4 ] ) ; С другой стороны, если (и только е сли) pt является формальным параметром функции, вы можете объявить его следую щим образом: void s ome function ( i nt pt [ ] [ 4 ] ) ; О братите внима ние на то, что первый набор квадратных с кобок пус т . Пустые квадратные с кобки показывают, что p t является указателем. Такого рода переме нная может быть ис пользована в этом случае так же, как и массив j unk. Вот что мы сделали в следую щем примере, представленном на листинге 1 0 . 1 7. В этом листинге представ­ лены три эквивалентных формы синтаксис а прототипов. Листинг 1 0.1 7. Проrрамма arra_y2 d . c ф ункции для двумер ных ма ссивов 1 1 ar r ay 2 d . c #incl ude <s tdio . h > #de fi ne ROWS 3 #de fi ne COLS 4 void s um_rows ( int ar [ ] [ COLS ] , int rows ) ; void s um co l s ( int [ ] [ COLS ] , i nt ) ; / / можно опустить имена int s um2 d ( i nt ( * ar ) [ COL S ] , int rows ) ; // дру г ой вид син такси с а i n t m a i n ( vo i d ) { int j unk [ ROWS ] [ COLS ] {2, 4, 61 8} 1 {3, 5, 7 1 9) 1 { 12 , 1 0 , 8 , 6 ) }; - - М а ссивы и ука з а те л и s um_rows ( j un k , ROWS ) ; s um_co l s ( j un k , ROWS ) ; p r i nt f ( " C yммa в с е х элеме н то в r e turn % d\ n " , s um 2 d ( j unk , О; void s um_rows ( i nt ar [ ] [ CO LS ] , int rows ) { int r ; int с ; i n t tot ; fo r { О; (r < rows ; r tot О; for (с = О; r++ ) < COLS ; с с++ ) t o t += ar [ r ] [ с ] ; p r i nt f ( " c тp o к a % d : сумма = % d\ n " , void s um_co l s ( i nt ar [ ] [ CO LS ] , r, to t ) ; int rows ) int r ; int с ; i n t tot ; fo r { О; (с = tot = for с < COLS ; с++ ) О; (r = О; r < rows ; r++ ) tot += ar [ r ] [ с ] ; p r i n t f ( " cтoлбeц % d : int s um 2 d ( i nt ar [ ] [ CO LS ] , с умма = % d \ n " , с, tot) ; int rows ) { int r ; int с ; О; О ; r < rows ; r + + ) ( с = О ; с < COLS ; с++ ) i n t tot = fo r (r = for tot += ar [ r ] [ с ] ; r e turn t o t ; Ниже показаны выход ные д анные этой программы : строка о : сумма 20 1: сумма = 2 4 строка 2 : сумма = 3 6 строка столбец о : 1: сумма 17 19 столбец 2 : сумма 21 столбец 3 : сумма 23 столбец сумма Сумма в с е х элементов = 80 ROWS ) ) ; 43 3 43 4 Гл ава 1 0 Программа и з листинга 1 0 . 1 7 пе редает в качестве аргумента имя j unk, которое представляет указатель на пе рвый элемент, каковым является подмассив, а также сим­ воличе скую константу ROWS со значением 3 , то е сть количество строк массива . После этого каждая функция расс матривает ar как мас с ив мас с ивов, с одержащих четыре значения типа int . Количе ство столбцов встраивается в функцию , но количе ство строк остается неуказанным. Эта же функция будет работать, скажем, с мас с ивом раз­ мерности 1 2 х 4 , если пе редается число 1 2 в качестве числа строк. Это объясняется тем, что rows это количе ство эле ментов; в то же время , пос кольку каждый элемент представляет с об ой мас с ив, или строка, rows становится количе ством строк. Обратите также внимание и на то , что ar ис пользуется так же, как j un k в функции main ( ) . Это становится возможным, поскольку ar и j un k имеют один и тот же тип: указатель на мас сив из четырех значе ний типа int. Имейте в виду, что следующее далее объявление не будет работать должным образом: - int s um2 ( int ar [ ] [ ] , int r ows ) ; 1 1 ошибоч н о е о бъявлени е В с помните , что компилятор пе реводит форму записи в стиле массива , в форму за­ писи в стиле указателя. Это значит, например, что ar [ 1 ] преобразуется в ar+ l . Чтобы компилятор мог вычислить эти выражения , он должен знать размер объе кта, на кото­ рый указывает a r . О бъявление int s um2 ( int ar [ ] [ 4 ] , int rows ) ; 11 пр авил ь но е о бъявление говорит о том, что ar указывает на массив, с о стоящий из четырех значений типа int (в наше й систе ме, на объект длиной 16 байтов) , следовательно, ar+ l означает " приба­ вить 16 байтов к адре су" . В условиях варианта с пустыми квадратными скобками ком­ пилятор не будет знать, что делать дальше . В ы можете также заклю чить размер в другую пару квадратных скобок, к а к показа­ но выш е , однако компилятор их проигнорирует : int s um2 ( int ar [ 3 ] [ 4 ] , int rows ) ; / / до пустимо е о бъявление , 1 1 однако 3 иг норир у е т ся Это удобно в тех случаях, когда используются typ ede f : typ ede f int ar r 4 [ 4 ] ; 1 1 массив arr 4 , состо ящий и з 4 з н ач е ний int typ ede f arr 4 arr 3 x 4 [ 3 ] ; / / массив arr 3 x 4 , со стоящий из 3 массивов arr 4 int s um2 ( ar r 3 x 4 ar , int rows ) ; / / то же , ч то и следующе е о бъявл ение int s um2 ( int ar [ 3 ] [ 4 ] , int rows ) ; / / то же , ч то и следующе е о бъявл ение int s um2 ( int ar [ ] [ 4 ] , int rows ) ; 1 1 стандартная форма В общем случае , чтобы объявить указатель, соответствую щий Nме рному массиву, вы должны снабдить значениями вс е комплекты квадратных скобок, кроме с амой ле­ вой пары : int s um4 d ( int ar [ ] [ 1 2 ] [ 2 0 ] [ 3 0 ] , int r ows ) ; Это объясняется тем , что пе рвый комплект квадратных скобок указывает на нали­ чие указателя , в то вре мя как остальные квадратные с кобки описывают типы объектов данных, на которые с с ылаются указатели, как показано в следую ще м эквиваленте прототипа : int s um4 d ( int ( * ar ) [ 1 2 ] [ 2 0 ] [ 3 0 ] , int rows ) ; э то ук азатель 11 ar - В данном случае ar представляет собой указатель на мас с ив 1 2х20х 3 0 значений ти­ па int . М ассивы и указатели 43 5 М ассивы п ерем ен н ой дл ин ы В ы , должно быть, уже заметили некоторую странность, характерную для функций, выполняю щих обработку двумерных мас сивов: вы можете описать количество строк посредством параметра функции, однако количество столбцов встроено в функцию . Наприме р, пос мотрите на это определение : #de fine COLS 4 int s um2 d ( int ar [ ] [ C OLS ] , int r ows ) { int r ; int с ; int tot = О ; for ( r = О ; r < r ows ; r + + ) for ( с = О ; с < COLS ; с++ ) tot += ar [ r ] [ с ] ; r etur n tot ; Далее , предположим, что б ыли объявлены следую щие мас сивы: int arr a y l [ 5 ] [ 4 ] ; int array2 [ 1 0 0 ] [ 4 ] ; int arr ay3 [ 2 ] [ 4 ] ; В ы можете использовать функцию s um2d ( ) для обраб отки следую щих масс ивов: tot s um2d ( ar r ay l , 5 ) ; tot = s um2 d ( ar r ay2 , 1 0 0 ) ; tot = s um2 d ( ar r ay3 , 2 ) ; 1 1 сумма э л еме н то в массив а 5 х 4 1 1 сумма э л еме н то в массив а 1 0 0 х 4 1 1 сумма э л еме н то в массив а 2 х 4 Это объясняется тем , что количество строк передается в параметр rows , представ­ ляю щий собой переменную . Однако если вы захотите просуммировать мас с ив размер­ ности бх 5 , вам придется вос пользоваться другой функцией, той, в которой COLS при­ нимает значение 5. Т акое поведение е сть результатом того факта , что для определе­ ния размерности массива вы должны воспользоваться константами; в силу этого обстоятельства , вы должны заменить параметр COLS переменной. Если вы на с амом деле хотите с о здать отдельную функцию , способную работать с любым двумерным масс ивом, вы сможете сделать это , но при этом получите оче нь не­ уклю жий результат. (Вам придется пе редать массив в виде одноме рного мас с ива и иметь под рукой функцию , чтобы вычислять, с какого места начинается каждая стро­ ка.) Б олее того, этот метод недостаточно гладко вписывается в подпрограммы на язы­ ке FORTRAN, который позволяет программисту задавать в вызове функции оба раз­ мера массива . FORTRAN можно с читать древним языком программирования , но в те­ чение многих де сятилетий эксперты в области численных методов разраб отали множество поле зных вычислительных библиоте к на языке FORTRAN. О жидалось, что С станет наследником FORTRAN, таким образом, возможность корректного пе­ реноса б иблиоте к FORTRAN на С представляется вес ьма полезной. Эта потре бность для стандарта С99 б ыла главным импульс ом , побудившим введе­ ние концепции мас с ивов переменной длины , которые позволяют использовать пе ре­ менные для указания размеров мас с ива . 43 6 Гл ава 1 0 Например, в ы можете сделать следую ще е : int quar t e r s = 4 ; int r egions = 5 ; do uЫ e s al e s [ r egions ] [ quar t e r s ] ; 1 1 мас сив nер еменной длины Как говорилось выше , на использование мас сивов пе ременной длины накладыва­ ются определенные ограниче ния . Для них должен быть предусмотрен класс автома­ тиче ской памяти, это означает, что они объявляются либо в функции, либо как пара­ метры функции. Кроме того , вы не можете их инициализировать в объявлении. М асси в ы перем енн ой дли н ы н е м ен яют размера Термин переменный в отношении массивов переменной дп ины не означает, что можно менять дп ину массива после того, как вы его создадите. Будучи созданны м , массив пе­ ре менной дп ины сох раняет свои размеры . Термин переменный означает, что вы може­ те испол ьзовать пере менные при описании размерности массива . Поскольку массивы переменной длины представляют собой новое языковое средст· во , поддержка его в настояще е время пока не отличается полнотой и стабильностью . Рассмотрим простой пример, демонстрирую щий, как следует писать функцию , которая выполняет суммирование содержимого любого двумерного массива значений int. Прежде все го , покажем , как объявлять функцию с аргументом в виде двумерного массива переменной длины: int s um2 d ( int rows , int co l s , i nt ar [ rows ] [ col s ] ) ; / / ar - массив пер еменной длины Обратите внимание на то , что два первых параметра (строки и столбцы) исполь­ зуются как размерность для объявления параметра массива a r . Пос кольку в объявле­ нии масс ива ar присутствуют строки и столбцы , они должны быть объявлены до того, как ar появится в спис ке параметров. В с илу этого обстоятельства следую щий прото­ тип является ошибочным: int s um2 d ( int ar [ rows ] [ col s ] , int rows , int col s ) ; / / неправиль ный порядок Стандарт С99 утве рждает , что вы можете в прототипе опускать имена , но в этом случае вы должны заме нить опуще нные разме рности звездочками: int s um2 d ( int , int , int ar [ * ] [ * ] ) ; 1 1 a r - массив пер еменной длины , имена опущены В о-вторых, посмотрите, как определять функцию : int s um2 d ( int rows , int co l s , i nt ar [ rows ] [ col s ] ) { int r ; int с ; int tot = О ; for ( r = О ; r < r ows ; r + + ) for ( с = О ; с < col s ; с+ + ) tot + = ar [ r ] [ с ] ; r etur n tot ; М ассивы и указатели 43 7 Без учета заголовка новой функции, единственное отличие от клас сической ве р­ сии этой функции на языке С (листинг 1 0 . 1 7) состоит в том, что константа COLS была заме не на пере менной col s . Мас с ив переменной длины в заголовке функции является именно тем с редством , которое позволяет проводить такие изменения . Кроме того, наличие пе ременных, которые представляют как количество строк, так и количе ство столбцов массива , позволяет использовать новую функцию s um 2 d ( ) с любыми разме­ рами двуме рного мас с ива значений int. Листинг 1 0 . 1 8 служит иллюстрацие й этого ут­ верждения. В то же время , для этого требуется компилятор языка С , в котором это но­ вое с войство реализовано . Программа в листинге 1 0 . 1 8 также показывает, что эта функция , построенная на основе свойств мас с ивов пере менной длины, может исполь­ зоваться как с любыми традиционными мас с ивами языка С , так и с массивами пе ре­ менной длины . Листинг 1 0.1 8. Проrрамма vararr2d . c / / var arr 2 d . c - - ф ункции , исnоль зующие ма ссивы переменной длины #incl ude <s tdio . h > #de fi ne ROWS 3 #de fi ne COLS 4 int s um2 d ( i nt rows , int col s , int ar [ rows ] [ col s ] ) ; i nt mai n ( vo i d ) { int i , j ; int r s = 3 ; int cs = 1 0 ; int j unk [ ROWS ] [ COLS ] {2, 4 1 6, в} 1 {31 5 , 71 9) 1 { 12 , 1 0 1 8 1 6 ) }; int mor e j unk [ ROWS - 1 ] [ COLS+ 2 ] { 2 0 , 3 0 , 4 0 , 50 , 60 , 70 ) 1 { 5, 6 , 7 , 8 , 9 , 10 ) }; int varr [ r s ] [ c s ] ; 1 1 массив пер еменной длины for ( i = О ; i < r s ; i++ ) for ( j = О ; j < cs ; j ++ ) v arr [ i ] [ j ] = i * j + j ; p r i nt f ( " Maccив р а змерно сти 3 x5 \ n " ) ; printf ( " Cyммa всех элемен тов = % d\ n " , s um2 d ( ROWS , COLS , j un k ) ) ; p r i nt f ( " Maccив р а змерно сти 2 х 6 \ n " ) ; printf ( " Cyммa всех э л емен тов = % d\ n " , s um2 d ( ROWS - l , COLS+ 2 , mor ej unk ) ) ; p r i nt f ( " Maccив переменной длины р а змерно сти 3 x l 0 \ n " ) ; printf ( " Cyммa всех э л емен тов = % d\ n " , s um2d ( r s , cs , varr ) ) ; r eturn О ; 43 8 Гл ава 1 0 1 1 функция с пар аметром в виде мас сива п ер еменной длины int s um2 d ( i nt rows , int col s , int ar [ rows ] [ col s ] ) { int r ; int с ; int tot = О ; for ( r = О ; r < rows ; r++ ) for ( с = О ; с < col s ; с++ ) tot += ar [ r ] [ с ] ; r e turn tot ; В ыходные данные этой программы имеют следую щий вид : Ма ссив р азмерности З х5 Сумма в с ех элементов = 8 0 Ма ссив р азмерности 2 х 6 Сумма в с ех элементов = 3 1 5 Ма ссив п ер еменной длины р азмерности З х l О Сумма в с ех элементов = 2 7 0 Следует отметить тот факт , что объявление массива в спис ке параметров объявле­ ния функции на с амом деле не приводит к с о зданию массива . Как и в случае старого синтакс ис а , фактическое имя массива пе ременной длины является указателе м . Это значит, что функция с параметром в виде массива переменной фактиче ски раб отает с данными исходного мас с ива, и в силу этого обстоятельства способна вносить измене­ ния в масс ив, переданный ей в качестве аргумента. Следую щий фрагмент кода пока­ зывает, когда необходимо объявлять указатель, а когда - фактичес кий мас с ив: int thing [ l O ] [ 6 ] ; two s et ( l 0 , 6 , thing ) ; void two s et ( i nt n , int m , int ar [ n ] [ m] ) 1 1 ar пр едст авля е т собой указ атель на мас сив и з m з н ач е ний типа int int t emp [ n ] [ m ] ; temp [ O ] [ О ] = 2 ; ar [ О ] [ О ] = 2 ; 1 1 temp - массив р а змер ности n х m знач е ний типа int 11 установить знач ение элемента массив а temp р авным 2 1 1 ус танови ть знач ение элеме нта thing [ O ] [ О ] р авным 2 Когда вызывается функция two s et ( ) , как показано , ar становится указателем на элемент thing [ О ] , а temp с оздается как мас с ив размерности l Охб . Поскольку как ar , так и thi ng являются указателями на thi ng [ О ] , то ar [ О ] [ О ] получает доступ к той же яче йке памяти, что и thing [ О ] [ О ] . Массивы переменной длины позволяют также ре ализовать динамиче ское распре­ деле ние памяти. Это означает, что вы можете задавать размеры массива во время вы­ полне ния программы . Для обычных мас с ивов выполняется статичес кое распределе­ ние памяти, означающее , что размер масс ив известен во вре мя компиляции. Именно по этой причине размеры массива , являющиеся константами, должны быть известны компилятору заранее . В опросы динамичес кого распределе ния памяти расс матрива­ ются в главе 1 2 . М ассивы и указатели 43 9 Составные литералы Предположим , что вы хотите передать в функцию некоторое значение с помощью параметра типа i nt; вы можете передать переменную типа int, но вы также можете передать константу типа int, такую как 5. До появления стандарта С99 возможности ре ализации функции с аргументом в виде массива были другими; вы могли передать массив, однако тогда не было ничего такого, что могло бы служить эквивалентом кон­ станты типа мас сив. Стандарт С99 изменил эту ситуацию , вводя составнъ�е литсралъ� . Литералы - это константы, которые не являются символическими константами. На­ пример, 5 е сть литеральная константа типа int , В 1 . 3 - литеральная константа типа douЫ e , ' У ' - литерал типа ch ar , а " el ephant " - строковая литеральная константа. Комитет, который разрабатывал стандарт С99, пришел к заклю чению , что удобне е бу­ дет иметь составные литеральные константы , которые могут представлять с одержи­ мое мас сивов и структур . С точки зре ния мас с ивов , составной литерал выглядит как список инициализации массивов, которому предшествует имя типа , заклю ченное в круглые скобки. Напри­ мер, ниже показано об ычное объявление мас с ива : int diva [ 2 ] = { 10, 20} ; Здесь ис пользуется составная литеральная константа , которая создает безымян­ ный мас с ив , соде ржащий те же значе ния типа int: ( i nt [ 2 ] ) { 1 0 , 2 0 } ! / со с т авная литер альная константа Обратите внимание, что именем типа е сть то , у вас остается , когда из удалите diva из предыдущего объявле ния , то е сть int [ 2 ] . Точно так же, как вы опускаете размерность мас с ива при инициализации име но­ ванного массива , вы можете опустить его в составной литеральной константе , а ком­ пилятор с ам подс читает, сколько имеется элементов в мас с иве: ( i nt [ ] ) { 5 0 , 2 0 , 9 0 } 1 1 сост авной литер ал с тр емя э л емент ами Поскольку эти с о ставные литеральные константы не имеют имени, вы можете соз­ давать их в каком-то одном операторе и использовать в дальне йшем по мере не обхо­ димости. С другой стороны , вы как-то должны их использовать в моме нт их создания . Один из спос обов состоит в приме не нии указателя для отслеживания местоположе­ ния с о ответствую щей ячейки памяти. То е сть, вы можете написать не что подоб ное показанному ниже: int * pt l ; pt l = ( i nt [ 2 ] ) { 1 0 , 2 0 } ; Следует также отметить, что эта литеральная константа идентифицируется как массив значений тип i nt. Подобно имени массива , она преобразуется в адре с первого элемента , таким образо м , она может быть присвое на указателю на значе ние типа int . Этот указатель вы можете использовать позже . Например , *p t l в этом случае будет иметь значе ние 1 0 , а ptl [ 1 ] - значе ние 2 0 . Другой прием, который становится возможным благодаря составным лите ральным константам, закл ю чается в том, что вы можете передать е го в качестве фактичес кого аргумента в функцию с соответствую щим формальным параметром: 440 Гл ава 1 0 int s um ( int ar [ ] , int n ) ; int total 3 ; total 3 = s um ( ( int [ ] ) { 4 , 4 , 4 , 5 , 5 , 5 } , 6 ) ; В данном случае пе рвый аргумент представляет собой массив, состоящий из шести элементов типа int, который действует как адре с первого эле мента , то есть так же, как и имя массива . Этот вид использования , в рамках которого вы передаете инфор­ мацию функции без необходимости заране е создавать мас с ив, типичен для составных литеральных констант. Вы можете рас пространить этот метод на двумерные и многоме рные мас с ивы. Вот как в этом случае создается двумерный массив значений типа int и с охраняется его адре с : int ( *pt2 ) [ 4 ] ; / /объявление указателя н а массив массивов 4 знач ений типа int pt2 = ( i nt [ 2 ] [ 4 ] ) { { 1 , 2 , 3 , - 9 } , { 4 , 5 , 6 , - 8 } } ; В данном случае типом является int [ 2 ] [ 4 ] , то есть мас с ив размерности 2 х 4 зна· че ний типа int. Листинг 1 0 . 1 9 объединяет все эти приме ры в одну полную программу. листинг 1 0.1 9. Программа flc . c 1 1 fl c . c - - стр анно выг лядящие кон станты #incl ude <s tdio . h > #de fi ne COLS 4 int s um2 d ( i nt ar [ ] [ COLS ] , int rows ) ; int s um ( int ar [ ] , int n ) ; int main ( vo i d ) int total l , total 2 , total 3 ; int * pt l ; int ( *pt 2 ) [ COL S ] ; pt l = ( i nt [ 2 ] ) { 1 0 , 2 0 } ; p t 2 = ( i nt [ 2 ] [ COLS ] ) { { 1 , 2 , 3 , - 9 } , { 4 , 5 , 6 , - 8 } } ; total l s um (p t l , 2 ) ; total 2 = s um2 d (pt2 , 2 ) ; total 3 = s um ( ( int [ ] ) { 4 , 4 , 4 , 5 , 5 , 5 } , 6 ) ; printf ( " total l % d\ n " , total l ) ; printf ( " total 2 % d\ n " , total 2 ) ; printf ( " total 3 % d\ n " , total 3 ) ; r e turn О ; int s um ( int ar [ ] , int n ) { int i ; int total = О ; for ( i = О ; i < n ; i ++ ) total += ar [ i ] ; r e turn total ; М ассивы и указатели 441 int s um2 d ( i nt ar [ ] [ COLS ] , int rows ) { int r ; int с ; int tot = О ; for ( r = О ; r < rows ; r++ ) for ( с = О ; с < COLS ; с++ ) tot += ar [ r ] [ с ] ; r e turn tot ; Для этой программы необходим компилятор, в котором реализовано расс матри­ ваемое дополнение к стандарту С99 (на текущий момент очень не многие компилято­ ры спос об ны делать это ) . Ниже показаны выходные данные этой программы: total l total 2 total З 30 4 27 К л юч евые п онятия Когда возникает необходимость хранить множество значений одного и того ж е типа , наиболее подходящим средством для решения этой задачи является массив. Язык С рас­ сматривает массивы как производн,ъ;е m:unъt, поскольку они построены на основе других типов. Другими словами, вы не просто объявляете массив значений типа int или типа float или какого-либо другого типа . Этот другой тип сам по себе является типом масси­ ва , и в конечном итоге получается массив массивов, или двумерный массив. Иногда б ывает полезно предусмотреть функции для обработки мас сивов, это по­ зволяет повысить модульность программы за счет реше ния конкретных задач в рам­ ках спе циализированных функций. Важно понимать, что когда вы ис пользуете имя массива в качестве фактичес кого аргумента , вы не пе редаете этой функции вес ь мас­ сив, вы просто пе редаете адре с мас с ива (следовательно, соответствую щий формаль­ ный параметр функции должен быть указателем ) . Чтобы обработать этот массив, функция должна знать, где хранится масс ив и с колько элементов содержит этот мас­ сив. Адрес массива указывает " где"; данные о том "с колько" должны либо быть встрое­ ны в функцию , либо пе редаваться ей в качестве отдельного аргумента . Второй подход носит более общий характер, что позволяет одной и той же функции работать с мас­ сивами различной размерности. С вязь между масс ивами и указателями настолько те сная, что вы часто можете представить одну и ту же опе рацию , употре бляя форму записи с использование м мас­ сивов и форму записи чере з указатели. Именно эта связь позволяет применять форму запис и мас с ивов в функции, обрабатывающей массивы даже в тех случаях, когда фор­ мальный параметр является указателем, а не массивом. В языке С вы должны задавать размерность обычного мас с ива константным выра­ же нием , благодаря чему размерность обычного массива становится известной во вре­ мя компиляции. Стандарт С99 предлагает альте рнативу в виде мас с ива переменной длины, когда спе цификатором размерности может быть пе ременная . Это позволяет задавать размеры массива пере менной длины во время выполнения программы . 442 Гл ава 1 0 Р езюм е Массив - это набор элементов одного и того же типа . Элементы хранятся в памяти в виде последовательности, а доступ к ним осуществляется с помощью целочисленного индекс а , или смещеиия. В языке С первый элемент массива имеет индекс О , следователь­ но , завершающий элемент массива , содержащего n элементов, имеет индекс n - 1 . Кон­ троль за правильным использованием индексов возлагается на программиста, поскольку ни компилятор, ни исполняемая программа не следят за этим процессом. Для объявления простого одиомериого масс ива используется следую щая форма : тип имн [ размер ] ; Здесь тип указывает тип данных каждого эле мента мас сива , имя - это имя массива , а разме р задает количе ство элементов. Традиционно язык С тре бовал, чтобы разме р был константным целочисле нным выражением. Стандарт С99 позволяет ис пользовать не константное целочисле нное выражение ; в расс матривае мом случае масс ив тракту­ ется как масс ив пе ременной длины . Язык С интерпретирует имя масс ива как адрес первого элемента этого мас с ива. В другой терминологии имя массива эквивалентно указателю на пе рвый элемент мас­ сива . В общем случае мас с ивы и указатели тес но с вязаны друг с другом. Если ar есть массив, то выраже ния ar [ i ] и * ( ar + i ) эквивалентны . Язык С не допускает передачу все го мас с ива в качестве аргумента функции, однако , вы можете пе редать в качестве аргумента адрес мас с ива. Далее функция может ис­ пользовать этот адрес для манипулирования исходным мас с ивом . Если функция не предназначается для модификации исходного масс ива , вы должны вос пользоваться клю че вым словом cons t при объявлении формального параметра, представляющего массив. В ы можете использовать в вызывающей функции как запись в форме массива , так и запис ь в форме указателей. В любом случае на практике ис пользуется перемен­ ная типа указатель. Добавление к указателю целого значе ния или инкремент указателя меняет значе­ ние указателя на число байтов, которые занимает в памяти объе кт , на который наце­ лен указатель. Иначе говоря , е сли pd указывает на 8-байтовое значение типа douЫ e в массиве , добавле ние 1 к указателю pd увеличивает его значе ние на 8 , следовательно , этот указатель будет с сылаться на следую щий элемент массива . Двумери'Ые массивы представляют с об ой мас с ивы массивов. Например , объявление do uЫ e s al e s [ 5 ] [ 1 2 ] ; создает массив с именем s al e s , состоящий из пяти элементов, каждый из которых представляет с об ой мас с ив из 12 значе ний типа douЫ e . На первый и з этих одноме рных массивов можно ссылаться как s al e s [ О ] , н а вто­ рой - s al e s [ 1 ] и так далее , при этом каждый из этих массивов с одержит 12 значений типа douЫ e . Второй индекс служит для доступа к конкретным элементам в этих мас­ сивах. Наприме р, s al e s [ 2 ] [ 5] - шестой эле мент мас сива s al e s [ 2 ] , а s al e s [ 2 ] - тре­ тий элемент мас с ива s al e s . Традиционный метод языка С передачи многомерного массива в функцию преду­ сматривает передачу име ни мас с ива , которое является адресом этого мас сива , пара­ метру в форме указателя на значе ние подходящего типа . М ассивы и указатели 443 О бъявле ние такого указателя должно описать все размерности мас сива , кроме первой; размерность первого параметра обычно передается во втором аргументе . На· приме р, чтобы обработать ранее упоминавшийся масс ив s al e s , прототип функции и вызов функции должны иметь вид : v o i d di s p l ay ( douЫ e ar [ ] [ 1 2 ] , i nt rows ) ; di splay ( s al e s , 5 ) ; Массивы переменной длины позволяют применять второй синтаксис , в рамках ко· торого обе разме рности пе редаются функции в качестве аргуме нтов. В этом случае прототип функции и вызов функции принимают следую щий вид : v o i d di s p l ay ( i nt rows , i n t col s , douЫ e ar [ rows ] [ col s ] ) ; di splay ( 5 , 1 2 , s al e s ) ; В ходе описанных выше рассуждений были использованы массивы значений типа int и массивы значений типа douЫ e , однако все , что было сказано выше , применимо и к массивам других типов. В то же время для символьных строк предусматривается целый набор специальных правил. Это объясняется тем фактом, что завершаю щий символ пробела в строке позволяет функции обнаружить конец строки без необходимости пе­ редачи ей размера. Символьные строки подробно рассматриваются в главе 1 1 . В оп росы для сам оконтроля 1 . Что выводит н а экран следую щая программа? #include < s t dio . h> int main ( vo i d ) { int r e f [ ] {В 4, О, 2} ; int *ptr ; int index ; for ( inde x О , ptr r e f ; index < 4 ; index+ + , ptr + + ) print f ( " % d % d\ n " , r e f [ i ndex ] , *ptr ) ; r eturn О ; = , = = 2 . Сколько элементов содержит мас с ив r e f из вопро с а 1 ? 3 . Адресом чего является r e f в вопрос е 1 ? Что можно сказать о r e f + 1? На чт о ссылается выражение + + r e f? 4. Какими являются значе ния *ptr и * (ptr + 2 ) в каждом из следую щих случаев? а. int *ptr ; int tor f [ 2 ] [ 2 ] { 12 , 1 4 1 1 6 } ; ptr tor f [ O J ; б . int * ptr ; int fort [ 2 ] [ 2 ] { { 12 } 1 { 1 4 , 16) } ; ptr fort [ О ] ; = = = 444 Гл ава 1 0 5 . Какие значения принимают выражения * *ptr и * * (ptr + 1 ) в каждом и з сле­ дую щих функций? а. int ( *ptr ) [ 2 ] ; int tor f [ 2 ] [ 2 ] ptr tor f ; { 12 , 1 4 , 1 6 } ; = б . int ( *ptr ) [ 2 ] ; int fort [ 2 ] [ 2 ] ptr fort ; = { { 12 } , { 1 4 , 16) } ; = 6 . Предположим, что имеет следую ще е выражение : int gr id [ З O ] [ 1 0 0 ] ; а. Выразите адрес gri d [ 2 2 ] [ 5 6 ] одним с пособом. б . Выразите адрес gri d [ 2 2 ] [ О ] двумя спос обами. в. Выразите адрес gri d [ О ] [ О ] тремя спос обами. 7. Напишите соответствую щее объявление для каждой из следую щих пе ременных: а. digi ts представляет собой мас с ив из 10 значений типа int. б. r ate s представляет собой массив из шести значений типа flo at. в. mat представляет с обой массив, с о стоящий из трех массивов, каждый из ко­ торых соде ржит 5 целых значе ний. г. psa представляет собой мас с ив, состоящий из 20 указателей, сс ылающихся на значение типа char . д. p s tr представляет собой указатель на мас с ив, состоящий из 20 значений ти­ па ch ar . 8. а. О бъявите массив, состоящий из шести значений типа int, и инициализи­ руйте его значениями 1 , 2, 4, 8, 16 и 3 2 . б. Используйте запис ь в форме мас с ива для представле ния третье го элемента (имеющего значе ние 4) мас с ива , заданного в пункте а ) . в. Предполагая, что правила стандарта С99 вступили в силу, объявите массив из 100 значе ний типа i nt и инициализируйте его таким образом, чтобы по­ следний элемент получил значение - 1 ; значе ния остальных элементов могут быть произвольными. 9 . Каким должен быть диапазон значений индексов мас сива , с о стояще го из 10 эле­ ментов? 1 0 . Предположим, что име ются следую щие объявле ния : float rootb e er [ l O ] , thi ngs [ l O ] [ 5 ] , *p f , v alue 2.2; int i 3; Назовите , какие из приведенных ниже операторов допустимы , а какие - нет: а. rootb eer [ 2 ] value ; б . s can f ( " % f " , &rootb e e r ) ; в. rootb eer value ; � printf ( " % f " , rootb e e r ) ; д. things [ 4 ] [ 4 ] r ootb e er [ З ] ; е . things [ 5 ] rootb e e r ; ж. p f value ; з. p f rootb e er ; = = = = = = = М ассивы и указатели 445 1 1 . О бъявите массив значений типа int размерности 800х600. 12. Пусть заданы объявления трех мас сивов: douЫ e trots [ 2 0 ] ; s hort clop s [ 1 0 ) [ 3 0 ) ; long s hots [ 5 ] [ 1 0 ) [ 1 5 ) ; а. Напишите прототип и оператор вызова для обычной функции типа void, которая обрабатывает элементы мас сива trots , и для функции, использую­ щей мас с ивы пере менной длины . б . Напишите прототип и оператор вызова для обычной функции типа void, которая обрабатывает элементы мас сива cl op s , и для функции, использую­ щей мас с ивы пере менной длины . в. Напишите прототип и оператор вызова для обычной функции типа void, которая обрабатывает элементы мас сива shots , и для функции, использую­ щей мас с ивы пере менной длины . 1 3 . Заданы два прототипа функций: void s how ( do uЫ e ar [ ] , int n ) ; 1 1 n - колич е ство э л ементов void s how2 ( douЫ e ar 2 [ ] [ 3 ] , i nt n ) ; 11 n - колич е ство с трок а. Напишите оператор вызова функции s how ( ) , который пе редает функции с о­ ставную лите ральную константу, содержащую значения 8 , 3 , 9 и 2 . б . Напишите оператор вызова функции s how ( ) , который пе редает функции с о­ ставную литеральную константу, содержащую 8 , 3 и 9 в качестве первой строки и значения 5, 4 и 1 в качестве второй строки. Уп ражн ен и я по програм м и рован и ю 1 . Вне с ите изменения в программу r ain, представленную в листинге 1 0 . 7 , с таким расчетом , чтобы она выполняла вычисления , используя указатели вместо ин­ дексов массивов. (В ам по-прежне му придется объявлять и инициализировать соответствую щий массив.) 2 . Напишите программу, которая инициализирует не который массив значений типа douЫ e , а затем копирует соде ржимое этого мас с ива в два других мас с ива. (В се три мас сива должны б ыть объявлены в основной программ е . ) Для созда­ ния первой копии вос пользуйтесь функцией, в которой приме няется запись в форме массива . Для создания второй копии вос пользуйте сь функцией, в кото­ рой применяется запись в форме указателя и инкрементирование указателя . Каждая функция должна принимать в качестве аргументов имя искомого (целе­ вого ) мас с ива и количество элеме нтов , подле жащих копированию . Иначе гово­ ря , с учетом соответствую щих объявлений, вызовы функций должны иметь сле­ дую щий вид: douЫ e sour c e [ 5 ] = { 1 - 1 , 2 . 2 , 3 _ 3 _ , 4 . 4 , 5 . 5 ) ; douЫ e targ e t 1 [ 5 ] ; douЫ e target2 [ 5 ] ; copy_arr ( s ource , t arget l , 5 ) ; copy_p tr ( s o u r c e , t arget l , 5 ) ; 446 Гл ава 1 0 3 . Напишите функцию , которая возвращает наибольшее значе ние и з массива зна­ че ний типа int. Протестируйте эту функцию с помощью простой программы. 4. Напишите функцию , которая возвращает индекс наибольшего значения из мас­ сива значе ний типа douЫ e . Проте стируйте эту функцию с помощью простой программы . 5 . Напишите функцию , которая возвращает разность между наибольшим и наи­ меньшим значениями из мас сива типа do uЫ e . Проте стируйте эту функцию с помощью простой программы . 6 . Напишите программу, которая инициализирует двумерный мас сив значений типа douЫ e и ис пользует одну из функций копирования из упражнения 2 для его копирования во второй двумерный массив. (Поскольку двуме рный массив представляет собой мас сив мас с ивов , функция , предназначенная для копирова­ ния одномерных массивов, может использоваться для работы с каждым из под­ массивов.) 7. Воспользуйтесь одной из функций копирования из упражнения 2 для копирова­ ния элементов с третьего по пятый семиэле ментного массива в мас с ив, состоя­ щий из трех элеме нтов . Саму функцию менять не надо, достаточно всего лишь правильно выбрать фактические аргументы . ( Фактиче скими аргументами не обязательно должны быть имя мас сива и размер мас с ива . Достаточно, чтобы такими аргументами были адре с элемента массива и количество обраб атывае­ мых элеме нтов . ) 8 . Напишите программу, которая инициализирует двумерный мас сив значений типа douЫ e размерности 3 х 5 и ис пользует функцию , ориентированную на ра­ боту с массивами пере менной длины , для копирования этого мас сива во второй двуме рный массив. Кроме того , напишите функцию , ориентированную на раб о­ ту с мас сивами пе ременной длины, для отображения содержимого этих двух массивов. В общем случае обе эти функции должны быть с пособны выполнять обработку произвольных мас с ивов размерности NxM. (Если вы не имеете дос­ тупа к компилятору, с пособному работать с мас с ивами пе ременной длины , вос­ пользуйте сь традиционным подходом языка С, применяе мым к функциям, ко­ торые могут выполнять обработку мас с ивов Nx 5 ) . 9 . Напишите функцию , которая устанавливает значение эле мента мас с ива равным сумме соответствую щих эле ментов двух других массивов. Иначе говоря , если массив 1 имеет значе ния 2, 4 , 5 и 8, а масс ив 2 значения 1, О , 4 и 6, эта функ­ ция присваивает массиву 3 значения 3 , 4 , 9 и 1 4 . Эта функция должна прини­ мать имена трех масс ивов и их разме рности в качестве аргуме нтов . Протести­ руйте эту функцию с помощью простой программы . - 1 0 . Напишите программу, которая объявляет мас сив размерностью 3 х 5 и выполня­ ет е го инициализацию значе ниями по ваше му выбору. Программа должна вы­ вести эти значения на экран, удвоить все значения, после чего вывести на экран новые значения. Напишите одну функцию для вывода значений на экран и дру­ гую функцию для удваивания значе ний. В качестве аргументов функции прини­ мают имя массивов и количество строк. М ассивы и указатели 447 1 1 . Перепишите программу r ain из листинга 1 О . 7 таким образом, чтобы основные задачи выполнялис ь с оответствую щими функция м и , но не в теле функции main ( ) . 1 2 . Напишите программу, которая предлагает пользователю ввести три набора , каждый из которых содержит по пять чисел типа douЫ e . Программа должна выполнять следую щие де йствия : а. Запоминать информацию в мас с иве размерности 3 х 5 . б . Вычислять среднее значение каждого набора и з пяти чисел. в. Вычислять среднее значение вс ех чисел. г. О пределять наибольшее из 15 значений. д. Выводить на экран сообщение с результатами вычислений. Каждая более-менее крупная задача должна решаться спе циальной функцией с ис пользованием традиционного для языка С подхода к обработке массивов. Выполните задачу б ) с помощью функции, которая вычисляет и возвращает среднее значение одномерного мас сива ; воспользуйте сь циклом для вызова этой функции три раза. Функции, реализую щие остальные задачи, должны по­ лучать в качестве аргумента вес ь мас сив, а функции, выполняющие задачи в ) и г ) должны возвращать ответ в вызываю щую программу. 1 3 . Выполните упражне ние 1 2 , но в каче стве параметров функции используйте массивы пе ременной длины . ГЛАВА 1 1 с и м вол ь н ые ст ро ки и ст ро ко в ы е ф ун кц и и в этой главе: • Ф ункци и : qet s О , put s О , • Испол ьзование строковых и сим вол ь н ых ФУН КЦ И Й из библ иотеки с и созд ание собственных строков ых фун кций • Использование а ргум ентов ком андной строки s t rcat ( ) , s trncat ( ) , s t r cnp ( ) , s t rncnp ( ) , s t rcpy ( ) , s t rncpy ( ) , sp rintf ( ) , s t rchr ( ) • Созд ание и испол ьзование строк с имвольная строка - это один из наиболее поле зных и важных типов данных в языке С . До с их по р вы постоя нно ис пользо вали с имвольные стро ки, и вам еще многое предстоит узнать о них. Б иблиоте ка функций С предлагает ш иро кий с пе кт р функций для чте ния и з а писи, копиро вания , сравнения , ко мб иниро вания , по­ иска и выполне ния других о пе р а ций со стро ками. Эта глава поможет вам вклю чить эт и функции в ар сенал ис пользуе м ых про гра ммных с редств. В веден ие в строки и строков ы й ввод-в ы вод Разумеется , вам уже известен наиболе е важный факт: символыи1я строка представ­ ляет собой массив з начений типа char, концевым эле ментом кото рого я вляется нуле­ вой с имвол (\ О ) . В с илу этого , все с веде ния о масс ивах и указателях, кото рые вы полу­ чили в предыдущих гла вах, мо гут б ыть пе рене с е ны и на с имвольные строки. Однако в с вяз и с инте нс ивны м ис пользо ванием с имвольных строк С предоставляет пользо вате­ лю множество функций, предназначе нных для работы со строками. В данно й главе расс матривается природа строк, с пособы объявле ния и инициализации строк, вклю­ че ние и исклю че ние их из про гра ммы , а та кже манипуляции со строками. В листинге 1 1 . 1 по казана довольно насыще нная про грамма , которая служит иллю­ стра цией нес кольких с пос об о в фор мирова ния , с читыва ния и пе чат и стро к. В ней ис­ пользуются две но вых функции - функция g ets ( ) , кото рая читает стро ку, и функция puts ( ) , которая выводит строку на пе чать. ( В ы , должно б ыть, об ратили внимание на 450 Гл ава 1 1 сходство с функциями g e t c h a r ( ) и put char ( ) . ) В остальном программа не содержит ничего необычного и незнакомого . их семейное листинг 1 1 .1 . Программа strinqs . c 11 s t r ings . c - - колле кцио нир о в ание пол ь з о в а т е л ей #incl ude < s tdio . h > #de f i n e MSG " в ы должны о бл а д а т ь мн о г ими талан т ами . #de f i n e L I M 5 #de f i n e L I N E LEN 81 Н а з о в и т е н е к о торые . " 11 константа символ ь ной с троки 11 максимал ь н ая дли н а с тр о ки + 1 int main ( vo i d ) { char name [ L I NE LEN] ; char tal ents [ L I NE LEN ] ; int i ; 1 1 иници али з а ция ма с си в а з н ач е ний 1 1 тип а char з аданной р а з мерно с ти co n s t c h a r m l [ 4 0 ] " По с т ар айте с ь уложи т ь ся в одну с тр о к у . " ; 1 1 пусть компиля тор 1 1 р а змеры ма с си в а с ам в ычи сли т const char m2 [ ] = "Если в ам ничего не приходит в гол ов у, придумайт е что-нибудь . " ; 1 1 иници али з а ция у к а з а т е л я co n s t c h a r *mЗ = " \ nBce , co n s t c h a r * my t a l [ L I M ] = о с е б е до ста точно , а в а с как з о в у т ? " ; 1 1 иници али з а ция ма с си в а 11 у к а з а телей н а строку / / массив из 5 у к а з ателей { " М г н о в е н н о е скл адыв ание ч и с ел " , " Т оч н о е умноже ни е " , " Н акаплив ание данных " , " И сполн е ни е и н с трукций с точно с т ь ю до посл едней букв ы " , " З нание я з ыка про г р аммир о в ания }; С" Я комп ь ю т ер по имени Кл айд . " " У меня ма с с а талантов . \ n " ) ; р r i nt f ( " Здр а в с твуйте ! p r i nt f ( " C eйч a c я р а с ск ажу ко е -ч то о них . \ n " ) ; p u t s ( " Чтo у меня за таланты? Во т тол ь ко ч а с тич ный их п ер еч е н ь . " ) ,· fo r (i = О; i < LIM; i++ ) p u t s ( my t a l [ i ] ) ; / / п еч а т ь талантов комп ь ю тера p u t s ( m3 ) ; g e t s ( n am e ) ; p r i nt f ( " Xopowo , ls , printf ( " % s \n % s \ n " , ls\n" , ml , n ame , MSG) ; m2 ) ; g e t s ( t al ent s ) ; р u t s ( " По смо трим , е с т ь ли у меня э т о т переч е н ь : " ) ; p u t s ( t al ents ) ; p r i n t f ( " Благ о д арю з а инфо рмацию , r e turn О; %s . \n" , name ) ; Символьные строки и строковые функции 451 Чтобы продемонстрировать, на что способна эта программ а , запустим ее на вы· полне ние : Здр авствуйте ! Я комп ьютер по имени Кл айд . У меня масса талан тов . С ейч ас я расскажу к о е -ч то о них . Что у меня з а таланты? Во т тол ь ко ч а с тичный их п еречен ь . Мг новенн о е скл адыв ание чисел Точное умножение Накаплив ание д анных Исполнение инс трукций с точно стью до последней буквы Зн ание я зыка прогр аммиров ания С В с е , о с е бе до статоч но , а в а с к ак зовут? Ск арт Ко !Шетев твиi Хорошо , Смар т Компе тентный , вы должны о бладать многими талан тами . На зовите неко торые . По стар ай т е с ь уложи т ь ся в одну с троку . Е с ли в ам нич е г о не приходит в г олову , придумайте ч то -нибудь . С ж овесвая перепапка , не с е ние чепухи , С](l![)'пяция , фапьшь • вз�о хи . По смо трим , е с т ь ли у меня этот переч е н ь : Слов е сная пер е палка , н е с е ние ч е пухи , симуля ция , ф альшь и вздохи . Бл агодарю з а информацию , Смар т Комп е т ентный . Мы с ейчас не будем изучать листинг 1 1 . 1 строка за строкой, а применим более об· щий подход . Прежде всего , рассмотрим способы определе ния строк в программах. За­ тем покажем, что необходимо для чтения строки в программу. И , наконец, рассмат· рим с пособы вывода строк. Определен ие строк в п рогра мме К а к вы , возможно , уже заметили, знакомясь с листингом 1 1 . 1 , существует множе ст· во способов определения строк. О сновные способы предполагают использование строковых констант, масс ивов типа cha r , указателей типа char и массивов символь· ных строк. При этом необходимо убедиться в наличии ме ста в памяти, где можно с о­ хранить строку, и позже этот вопрос еще будет рас сматриватьс я . Константы типа символьной строки (строковые литералы} Строковая коистаита, которую также называют строков'Ым литералом, представляет собой произвольную последовательность символов, заклю ченную в кавычки. Заклю