WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     || 2 | 3 | 4 |
-- [ Страница 1 ] --

МИНИСТЕРСТВО ОБРАЗОВАНИЯ РЕСПУБЛИКИ БЕЛАРУСЬ УЧРЕЖДЕНИЕ ОБРАЗОВАНИЯ «БЕЛОРУССКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ИНФОРМАТИКИ И РАДИОЭЛЕКТРОНИКИ» Кафедра информатики А.А. Мелещенко Основы

программирования на языке С Учебное пособие по курсу «Конструирование программ и языки программирования» для студентов специальности 31 03 04 “Информатика” дневной формы обучения Минск 2004 УДК 004.434 (075.8) ББК 32.973-018.1я73 М 47 Рецензент:

зав. кафедрой математики и информатики факультета информационных технологий Европейского гуманитирного университета кандидат технических наук В.И. Романов Мелещенко А.А.

М 47 Основы программирования на языке С: Учеб. пособие по курсу «Конструирование программ и языки программирования» для студ.

спец. 31 03 04 «Информатика» дневной формы обуч. / А.А. Мелещенко.

– Мн.: БГУИР, 2004. – 232 с.: ил.

ISBN 985-444-696-4 Пособие является учебным материалом по курсу КПиЯП (1 семестр обучения, специальность «Информатика»). Будет полезно студентам, изучающим язык программирования С. Материал пособия может использоваться в качестве теоретической части лабораторных работ.

УДК 004.434 (075.8) ББК 32.973-018.1я ISBN 985-444-696-4 © Мелещенко А.А., © БГУИР, Содержание Предисловие............................................................................. 1. Данные программы............................................................. Анатомия С-программы.................................................................................... Функция main().............................................................................................. Сообщения об ошибках................................................................................ Заголовочные файлы..................................................................................... Комментарии................................................................................................. Отладка с комментариями......................................................................... Переменные и типы переменных................................................................... Ключевые слова............................................................................................. Идентификаторы........................................................................................... Целые типы.................................................................................................... Знаковые и беззнаковые значения............................................................ Множественные объявления переменных............................................... Литеральные целые значения.................................................................... Ключевое слово const................................................................................. Типы данных с плавающей запятой............................................................ Точность....................................................................................................... Символьные типы.......................................................................................... Использование отдельных символов........................................................ Работа со строками..................................................................................... Размеры переменных.................................................................................... Символические константы............................................................................. Определение собственных символических констант................................ Предопределенные символические константы.......................................... Перечисления................................................................................................... Преобразование типов..................................................................................... Использование операции приведения типа................................................ Резюме............................................................................................................... Обзор функций................................................................................................. Функция printf() (stdio.h).............................................................................. Прототип и краткое описание................................................................... Спецификаторы преобразования.............................................................. Задание ширины поля и точности представления................................... Флаги............................................................................................................ Функция scanf() (stdio.h)............................................................................... Прототип и краткое описание................................................................... 2. Действия программы....................................................... Выражения........................................................................................................ Операторы........................................................................................................ Арифметические операторы........................................................................ Операторы отношений.................................................................................. Логические операторы.................................................................................. Оператор отрицания...................................................................................... Операторы инкремента и декремента......................................................... Оператор присваивания................................................................................ Множественное присваивание.................................................................. Сокращенный оператор присваивания..................................................... Приоритеты операторов............................................................................... Оператор if..................................................................................................... Оператор else................................................................................................. Условные выражения.................................................................................... Оператор switch............................................................................................. Оператор while............................................................................................... Оператор do-while......................................................................................... Оператор for................................................................................................... Бесконечный цикл...................................................................................... Оператор break.............................................................................................. Оператор continue.......................................................................................... Оператор goto................................................................................................ Резюме.............................................................................................................. Обзор функций................................................................................................. 3. Функции.............................................................................. Нисходящее программирование.................................................................... Функции, которые возвращают пустоту....................................................... Локальные и глобальные переменные.......................................................... Область видимости переменных................................................................. Область видимости локальных переменных........................................... Область видимости глобальных переменных.......................................... Функции, которые возвращают значение..................................................... Целые функции.............................................................................................. Функции с плавающей запятой................................................................... Другие типы функций................................................................................... Распространенные ошибки в функциях........................................................ Параметры и аргументы функций................................................................. Безымянные параметры................................................................................ Рекурсия: то что сворачивается, должно разворачиваться......................... Резюме............................................................................................................... Обзор функций................................................................................................. 4. Массивы.............................................................................. Введение в массивы......................................................................................... Инициализация массивов............................................................................. Использование sizeof с массивами............................................................... Использование массивов констант.............................................................. Символьные массивы.................................................................................. Многомерные массивы................................................................................. Двумерные массивы.................................................................................... Трехмерные массивы.................................................................................. Инициализация многомерных массивов................................................... Передача массивов функциям...................................................................... Передача многомерных массивов функциям........................................... Указатели........................................................................................................ Введение в указатели.................................................................................... Объявление и разыменование указателей................................................ Указатели в качестве псевдонимов........................................................... Нулевые указатели...................................................................................... Указатели типа void.................................................................................... Указатели и функции.................................................................................... Указатели и динамические переменные..................................................... Резервирование памяти в куче................................................................... Удаление памяти в куче.............................................................................. Указатели и массивы..................................................................................... Динамические массивы.............................................................................. Резюме............................................................................................................. Обзор функций............................................................................................... 5. Строки............................................................................... Что такое строка............................................................................................. Строковые литералы................................................................................... Строковые переменные.............................................................................. Строковые указатели.................................................................................. Нулевые строки и нулевые символы......................................................... Строковые функции...................................................................................... Отображение строк..................................................................................... Чтение строк................................................................................................ Преобразование строк в значения............................................................. Определение длины строк.......................................................................... Копирование строк...................................................................................... Дублирование строк.................................................................................... Сравнение строк.......................................................................................... Конкатенация строк.................................................................................... Поиск элементов строк............................................................................... Поиск символов........................................................................................ Поиск подстрок......................................................................................... Разложение строк на подстроки................................................................ Резюме............................................................................................................ Обзор функций............................................................................................... 6. Структуры........................................................................ Сравнивание и присваивание структур.................................................... Инициализация структур............................................................................ Использование вложенных структур........................................................ Структуры и функции................................................................................... Структуры и массивы.................................................................................... Массивы структур....................................................................................... Структуры с членами, являющимися массивами.................................... Динамические структуры данных................................................................ Самоссылочные структуры.......................................................................... Cтеки............................................................................................................. Очередь......................................................................................................... Списки.......................................................................................................... Двунаправленные списки......................................................................... Резюме............................................................................................................ 7.Сортировка и поиск данных.......................................... Методы и алгоритмы сортировки................................................................ Прямые сортировки.................................................................................... 1. Сортировка методом обмена............................................................... 2. Сортировка методом выбора............................................................... 3. Сортировка методом вставки.............................................................. Усовершенствованные методы сортировки............................................. 4. Сортировка методом Шелла................................................................ 5. Быстрая сортировка.............................................................................. Выбор метода сортировки.......................................................................... Поиск данных................................................................................................. Линейный поиск.......................................................................................... Бинарный поиск........................................................................................... Поиск строки в тексте................................................................................. Прямой поиск строки............................................................................... Алгоритм Боуера–Мура........................................................................... Какой метод выбрать?................................................................................ Резюме............................................................................................................. Обзор функций............................................................................................... 8. Файлы................................................................................ Что такое файл?............................................................................................. Текстовые файлы........................................................................................... Чтение в посимвольном режиме................................................................ Чтение в построчном режиме.................................................................... Посимвольная запись.................................................................................. Построчная запись...................................................................................... Функция printf() и родственные ей функции............................................ Функция scanf() и родственные ей функции............................................ Двоичные файлы............................................................................................ Обработка двоичных файлов..................................................................... Файлы с последовательным доступом...................................................... Файлы с произвольным доступом............................................................. Программирование баз данных.................................................................... Проектирование баз данных...................................................................... Создание файла базы данных..................................................................... Добавление записей в базы данных.......................................................... Редактирование записей базы данных...................................................... Создание отчетов о содержимом базы данных........................................ Резюме............................................................................................................. Обзор функций............................................................................................... Приложение 1: Таблицы кодов ASCII............................ Приложение 2: Хороший стиль программирования.... Приложение 3: Рекомендуемая литература................... Предисловие Научиться программировать – непростое дело;

в какой-то момент программирование может показаться вам утомительным и «неподъемным».

Но его освоение принесет вам ни с чем не сравнимое удовлетворение, а кроме того, возможность стабильного заработка во взрослой части вашей жизни.

Это пособие – для тех, кто хочет научиться программировать на С.

Последовательно и подробно рассматриваются основные конструкции языка, функции, указатели и массивы, строки, структуры и файлы. Большое внимание уделяется алгоритмам сортировки и поиска данных, разработке баз данных средствами языка С.

Основная цель пособия – научить основам программирования на С, поэтому некоторые элементы языка, например, бинарные операции, объединения, классы переменных, макросы – не рассматриваются. При необходимости обратитесь к приложению 3 «Рекомендуемая литература» для их самостоятельного изучения.

Вряд ли вы всю жизнь будете программировать в одиночку – вы будете работать в коллективе программистов. Хорошо, если с самого начала вы привыкните писать программы в общепринятом стиле программирования, что облегчит их понимание другими людьми. Этому посвящено приложение 2 «Хороший стиль программирования».

Хотелось бы выразить благодарность студентам, внесшим существенный вклад в эту работу:

Болбас Татьяне и Мазохе Ольге, которые помогли перевести материалы пособия в электронную форму;

Прудникову Михаилу, который подготовил материал, вошедший в главы 6 и 7;

Михасеву Артему, который помог протестировать примеры программ, входящие в пособие.

Сотрудники кафедры информатики В.Л. Балащенко и С.И. Сиротко консультировали меня во время работы над рукописью. Нашли время прочесть рукопись перед ее сдачей в печать и внесли много ценных замечаний Карасик Е.А. и Зиссер Ю.А.

Самая большая благодарность – Тому Свану, американскому программисту, известному лектору, на работах которого основана большая часть материала данного пособия.

А. Мелещенко 1. Данные программы Данные – это факты, которые известны компьютерной программе.

Запомнив данные в программе, вы передаете эти знания, например, номер телефона или адрес вашего друга, компьютеру.

Как вы узнаете из этой главы, данные С-программы могут быть либо целыми числами, либо значениями с плавающей запятой, либо же символьными строками. Однако перед тем как начать исследование этих и других типов данных, давайте поближе познакомимся с составными частями типичной С-программы.

Анатомия С-программы Лучший способ изучить язык программирования – это ввод, компилирование и запуск на выполнение листингов небольшого размера.

Введите листинг 1.1 (исключая номера строк с двоеточиями слева), сохраните его под именем welcome.c, затем скомпилируйте и, наконец, выполните программу.

Листинг 1.1. welcome.c (пример простой программы на С) 1: #include 2:

3: main() 4: { 5: printf(Welcome to C Programming!\n);

6: return 0;

7: } Рассмотрим, из чего состоит программа welcome.c.

Строка 1 – это директива включения. Имя директивы (#include – «включить») указывает компилятору на чтение текста из заданного файла.

Используемый в листинге 1.1 включаемый файл stdio.h называется заголовочным и обычно имеет расширение “.h”. Файл stdio.h содержит различные, необходимые для вашей программы стандартные объявления, связанные с вводом и выводом.

Строка 2 – пустая. Пустые строки также выполняют свою роль в программировании, – например, помогают выделить ключевые разделы программ. Компилятор «не обращает внимания» на пустые строки.

Открывающая и закрывающая фигурные скобки в строках 4 и образуют блок – группу элементов, с которой компилятор будет обращаться как с единым целым. Блок также называется составным оператором. Блок из листинга 1.1 содержит два оператора (строки 5 и 6). Операторы представляют собой действия, которые программа должна выполнить.

Первый оператор (строка 5) вызывает стандартную функцию форматированного вывода printf() (произносится принт-эф;

функция объявлена в файле stdio.h), чтобы отобразить на экране строку заключенного в кавычки текста. Этот текст заканчивается управляющим символом ‘\n’, который обозначает переход на новую строку.

Строка 6 содержит оператор возврата return. Он завершает выполнение программы и возвращает некоторое значение операционной системе. Обычно ненулевые значения свидетельствуют об ошибках, нулевые – об успешном завершении программы.

В этой короткой программе лишь одна строка 3 осталась без объяснения. В ней расположен один из самых важных элементов каждой С программы – функция main(). Она заслуживает более пристального внимания.

Функция main() С-программы выполняются последовательно. Они начинаются с одного оператора, выполняют ряд других и, если не ударит молния и не отключится питание, благополучно завершаются в запланированное время.

Все С-программы начинаются одинаково: первый оператор содержит функцию, называемую main(). (Вы узнаете больше о функциях и о том, как их писать, в главе 3). Именно с этой функции начинает выполняться любая скомпилированная С-программа, независимо от ее размера.

Листинг 1.2 представляет собой минимально полную С-программу.

Несмотря на то что эта программа – «лилипут», она все-таки имеет функцию main().

Листинг 1.2. smallest.c (минимально возможная С-программа) 1: main() 2: { 3: return 0;

4: } Функции начинаются с имени, за которым следует пара круглых скобок. За именем функции следует блок операторов (строки 2-4). Все, что находится внутри блока, принадлежит функции. В данном случае, у функции есть только один оператор return (строка 3), хотя их может быть и больше.

Каждый оператор должен заканчиваться точкой с запятой (;

), так что компилятор легко может найти их концы.

Выполнение этой суперпростой программы под неусыпным оком отладчика показывает, как программа начинается, выполняется и заканчивается (рис. 1.1).

Код Код запуска main() завершения Рис. 1.1. Выполнение типичной С-программы Введите листинг 1.2 и вместо обычной компиляции выполните следующие шаги.

1. Нажмите (если вы работаете в среде Borland C++) или (Visual C++) для компиляции и пошагового выполнения программы. По первому нажатию выделится оператор main(). Программа замирает на старте, подобно гонщику, напряженно ожидающему зеленого света.

2. Нажмите снова. Выделится оператор return. Вы только что выполнили невидимую секцию, называемую кодом запуска. Код запуска программы выполняет различные инициализации, которые обычно нас мало интересуют.

3. Нажмите еще раз, чтобы выполнить оператор return. Сейчас выделена последняя скобка и программа готова выполнить другую невидимую секцию, называемую кодом завершения. Подобно коду запуска, код завершения выполняет разнообразную вспомогательную работу, которую вы можете пока игнорировать.

4. Нажмите в последний раз, чтобы завершить программу.

Сообщения об ошибках Не удивляйтесь, если при написании и вводе текста программы вы делаете много ошибок. Программирование требует совершенства.

Пропустите хотя бы один, казалось, незначительный символ – и компилятор запищит от недовольства.

Существует две категории «жалоб» компилятора.

• Errors (Ошибки) означают серьезные ошибки, которые мешают компилятору закончить его работу.

• Warnings (Предупреждения) обращают ваше внимание на потенциальные проблемы, которые могут вызвать сбой в работе программы. Вы, конечно, можете запустить программу, для которой компилятор выдал предупреждения, но в этом случае результаты будут непредсказуемы.

Если вы получили от компилятора предупреждение, отнеситесь к нему со вниманием и ликвидируйте его причину как можно скорее.

Поскольку в ходе деятельности на ниве программирования вы, несомненно, будете допускать бесчисленное количество ошибок, сейчас будет полезно специально сделать несколько ошибок, чтобы знать, как исправлять их в дальнейшем. Скомпилируйте листинг 1.3, который содержит несколько грубых ошибок.

Листинг 1.3. bad.c (хорошая программа с большим количеством ошибок) 1: include

3: main{} 4: ( 5: printf(Problems...\n);

6: printf(Problems, problems\n);

7: printf(Problems all day long!\n) 8: ) Во время компиляции листинга компилятор сообщит вам, что программа bad.c имеет несколько ошибок:

Error bad.c 1: Declaration syntax error Error bad.c 6: ) expected Error bad.c 6: Unterminated string or character constant Каждое сообщение об ошибке содержит имя файла (что важно для многофайловых программ), номер строки и короткое объяснение. Как видите, проблемы гнездятся в строках 1 и 6. Теперь исправьте ошибки в указанных строках. Замените строку 1 на #include Строка 6 упоминается сразу в двух сообщениях. Первое предполагает, что пропущена круглая скобка. Это понятно. Следующее сообщение «Unterminated string or character constant» («Незаконченная строковая или символьная константа») говорит о том, что строка символов в операторе printf() осталась без заключающих кавычек. Замените строку 6 на printf(Problems, problems\n);

и скомпилируйте измененную программу. И снова компилятор открывает свою «жалобную книгу»:

Error bad.c 3: Declaration syntax error Error bad.c 6: ) expected Error bad.c 7: ) expected Теперь уже строка 3 попала в «черный список». Это обычное дело: вы исправляете одни ошибки и возникают другие. Постарайтесь исправить остальные ошибки самостоятельно (используйте листинг 1.1 в качестве образца). Листинг 1.4 демонстрирует правильную программу.

Листинг 1.4. good.c (исправленная версия bad.c) 1: #include 2:

3: main() 4: { 5: printf(Problems...\n);

6: printf(Problems, problems\n);

7: printf(Problems all day long!\n);

8: return 0;

9: } Заголовочные файлы Такие директивы, как #include , дают указание компилятору включить текст заданного файла в вашу программу. Просмотрите файл stdio.h (его легко найти с помощью средств поиска на вашем компьютере).

При обработке директивы #include компилятор ищет заданные файлы в соответствии со следующими правилами:

• если имя файла заключено в угловые скобки (#include ), компилятор осуществляет поиск в стандартных каталогах, заданных в настройках среды разработки программ;

• если имя файла заключено в кавычки (#include “anyfile.h”), компилятор ищет файл в текущем каталоге;

• если компилятор не может найти файл, имя которого заключено в кавычки, то он обращается с ним так, как будто его имя окружено угловыми скобками (другими словами, если файла “anyfile.h” нет в текущем каталоге, компилятор ищет его в стандартных каталогах);

• при указании имен включаемых файлов регистр букв не имеет никакого значения. Все имена MyFile.H, MYFILE.H и myfile.h относятся к одному и тому же файлу. Сама же директива #include вводится на нижнем регистре (строчными буквами).

Имена файлов могут включать и маршруты, как, например, в директиве #include c:\mydir\myfile.h. Имена каталогов отделяются обратной косой чертой.

Комментарии Комментарии – это данные, предназначенные только для вас.

Компилятор их полностью игнорирует. Цель комментариев – пояснить содержание программы.

Заключайте комментарии в двухсимвольные ограничители /* и */. Вот пример:

/* Моя первая программа */ Компилятор полностью игнорирует все символы в комментариях, включая ограничители. Комментарии могут стоять в конце оператора:

printf(Print this text);

/* отображение текста на экране */ Однако их нельзя ставить внутри текста, заключенного в кавычки, или внутри идентификаторов:

printf(Not /* комментарий */);

/* ??? */ int bad/* комментарий */Identifier;

/ *??? */ Мы будем использовать комментарии /* ??? */ для обозначения неправильных конструкций или сомнительных действий. Приведенные выше две строки никуда не годятся.

Вы можете писать комментарии, которые занимают одну или несколько строк, используя одну пару ограничителей. Следующие три строки текста представляют собой один комментарий:

/* Моя вторая С-программа Написана 10.09. Автор: имя */ Как и в случае однострочных комментариев, компилятор игнорирует весь текст, включая ограничители. Пользуясь этим, программисты часто пишут причудливые комментарии, аналогичные следующему:

/* ** Программа: Моя третья С-программа ** Дата: 12.09. ** Автор: имя */ Дополнительные звездочки здесь только для красоты оформления.

Отладка с комментариями Комментарии часто бывают полезны для отладки отдельных фрагментов программы. Иногда, удалив один или два оператора и выполнив программу без них, можно выявить причину возникшей проблемы. Этот метод можно сравнить с ситуацией, когда воображаемый врач удаляет у пациента сердце и, убедившись, что тот остался больным, делает вывод, что с сердцем как раз было все в порядке (не следуйте этому примеру в жизни).

Предположим, вы хотите узнать, что случится, если в листинге 1. пренебречь оператором возврата. Вместо удаления строки, запретите ее выполнение с помощью комментария.

main() { printf(Welcome to C Programming!\n);

/* return 0;

*/ } Компилятор теперь проигнорирует этот оператор, который можно легко восстановить, убрав ограничители.

Замечание. Пользуйтесь комментариями умеренно. Хорошие программы удобочитаемы сами по себе, и вам следует привыкать к чтению программного кода, а не только комментариев. Используйте комментарии, чтобы прояснить значение ключевых мест в ваших программах или для сообщения информации (как правило, в начале файла) о назначении программы, параметрах, авторе и т.п.

Переменные и типы переменных С помощью переменных происходит запоминание информации в программе. Переменная представляет собой имя, связанное с областью памяти, отведенной для хранения значения переменной.

Перед тем как использовать переменные, программа должна объявить их. Это правило помогает компилятору быстро справляться со своими обязанностями и заставляет программистов тщательнее планировать свою работу. Листинг 1.5 демонстрирует, как нужно объявлять, инициализировать и использовать простую переменную, позволяющую хранить целые числа. В языке С целые переменные обозначаются сокращением int.

Листинг 1.5. integer.c (пример использования целой переменной) 1: #include 2:

3: main() 4: { 5: int value;

6:

7: value = 1234;

8: printf(Value = %d\n, value);

9: return 0;

10: } Строка 5 объявляет переменную int с именем Память value. Объявление переменной заканчивается …… точкой с запятой. Слово int является именем типа данных, оно встроено в язык С. Слово value является идентификатором, т.е. именем, которое вы value придумали сами. По договоренности, пустая строка (строка 6) отделяет объявления переменных программы от ее операторов.

Строка 7 присваивает переменной value значение 1234. При выполнении оператора присваивания программа сохраняет двоичное представление числа 1234 в области памяти, принадлежащей value (рис. 1.2).

…… Строка 8 отображает значение переменной value с помощью оператора Рис. 1.2. Значение printf(Value = %d\n, value);

1234 сохраняется в памяти Рассмотрим оператор printf() подробнее.

Внутри строки находится спецификатор %d – специальная команда, которая «объясняет» функции printf(), где и в каком виде должна появиться переменная value. Функция printf() заменяет спецификатор значением переменной, записанной после строки, заключенной в кавычки. Символ, стоящий после знака процента, указывает, какого типа должна быть выведена переменная. В нашем случае – это d, значит, вывести нужно целую переменную.

Листинг 1.6. отображает значения переменных различных типов данных.

Листинг 1.6. variable.c (переменные различных типов) 1: #include 2:

3: main() 4: { 5: char slash = /;

6: short month = 1;

7: int year = 2010;

8: long population = 10000000L;

9: float pi = 3.14159;

10: double velocity = 299791336.2;

11: long double lightYear = 9.5e15;

12:

13: printf("Date = %02d%c%d\n", month, slash, year);

14: printf("Population of the Belarus = %ld\n", population);

15: printf("Pi = %f\n", pi);

16: printf("Velocity of light = %13.2lf meter/sec\n",velocity);

17: printf("One light year = %.0Lf meters\n", lightYear);

18: return 0;

19: } Далее в этой главе мы рассмотрим все типы данных, представленные в листинге 1.6 (строки 5-11).

Чтобы отобразить значения переменных, функция printf() использует множество спецификаторов (%d, %c, %f и т.д.), каждый из которых вставляет в отображаемые строки значение соответствующего типа. Раздел «Обзор функций» в конце этой главы содержит подробное описание этой могущественной функции.

Строка 7 демонстрирует удобный способ объявления и инициализации переменной за один прием. Вы также могли бы записать эту строку в виде двух строк:

int year;

...

year = 2010;

Конечный результат тот же, но перед тем, как число 2010 будет присвоено переменной year, значение переменной имеет непредсказуемое значение, равное комбинации нулей и единиц, оставленной в ее области памяти от предыдущих операций. Во избежание проблем всегда инициализируйте свои переменные перед их использованием (компилятор предупредит вас, если вы забудете об этом).

Упражнение. Сократите листинг 1.5, объявив и инициализировав переменную value за один прием.

Ключевые слова Ключевые слова, например int в листинге 1.5, являются встроенными символами языка. Вы не должны их использовать для своих собственных идентификаторов. Ключевые слова нужно вводить строчными буквами. В табл. 1.1 перечислены ключевые слова языка C.

Таблица 1. Ключевые слова ANSI C asm default for short Union auto do goto signed Unsigned break double if sizeof void case else int static volatile char enum long struct while const extern register switch continue float return typedef Идентификаторы Слово value, встретившееся в листинге 1.5, называется идентификатором. Идентификаторы – это слова, которые вы придумываете для обозначения своих собственных переменных и функций.

Идентификаторы должны начинаться с буквы английского алфавита и могут содержать только английские буквы, цифры и символы подчеркивания. Они могут быть практически любой длины, но должны различаться в первых 32 символах, чтобы компилятор воспринимал их как разные идентификаторы (количество различаемых символов может быть задано с помощью настроек вашей среды разработки).

Замечание. Идентификаторы также могут начинаться с одного или нескольких знаков подчеркивания, например, _myIdentifier или systemVar. Обычно идентификаторы такого вида зарезервированы для системного уровня. Если вы не будете начинать свои идентификаторы со знаков подчеркивания, у вас никогда не будет конфликтов с предопределенными системными идентификаторами.

Язык С чувствителен к регистру букв, то есть имеет значение, в каком регистре – верхнем или нижнем – написаны буквы. MyVar, myvar и MYVAR – это совершенно разные идентификаторы. Обычно программисты, пишущие на С, начинают идентификаторы переменных и стандартных функций со строчных букв. Свои собственные функции они пишут с прописной буквы, поэтому их легко отличить от библиотечных. Например, вы можете объявить несколько переменных с именами value balance result name и вызывать функции с именами printf() MyFunc() getchar() GetUp() Помните, что все это соглашения, а не правила. В своих идентификаторах вы можете использовать буквы любого регистра и в любом порядке. Но стандартные функции (такие, как printf() или getchar()) необходимо писать строчными буквами – точно так, как они объявлены.

Данные соглашения помогут вам разрабатывать свои и понимать чужие программы.

Неправильно записанные идентификаторы являются причиной «жалоб» компилятора типа «Declaration terminated incorrectly». Подобное сообщение об ошибке могут вызвать следующие примеры «горе идентификаторов»:

3rdValue $balance my-Var Mistake! /* ??? */ Идентификаторы никогда не должны начинаться с цифры и не должны содержать никаких знаков препинания, кроме символа подчеркивания.

Иногда программисты используют символы подчеркивания в идентификаторах, состоящих из нескольких слов. В этом случае значение идентификаторов становится очевидным, и они легко читаются:

Balance_of_power speed_of_light Существует другое популярное соглашение, называемое «верблюжий горб». Идентификатор, начинающийся со строчной буквы и имеющий прописную где-то в середине, действительно напоминает горб верблюда. С учетом этого соглашения два предыдущих идентификатора примут следующий вид:

balanceOfPower speedOfLight В своих программах вы можете использовать любой из этих стилей.

Целые типы Целые типы данных (такие как int) занимают фиксированное число ячеек памяти, что ограничивает диапазон значений, которые они могут представлять.

Переменные типа int обычно занимают два байта и могут хранить значения в диапазоне от –32 768 до +32 767 (включая 0).

Если вам нужен больший диапазон целых чисел, можно объявить переменные типа long int, например:

long int bigValue;

Объявленная таким образом переменная обычно занимает четыре байта и может хранить значения от -2 147 483 648 до +2 147 483 647, включая 0. Вы можете сократить запись long int до long:

long bigValue;

Также вы можете объявить значения типа short int как short int smallValue;

и сократить их до short smallValue;

Как правило, тип short int аналогичен типу int и представляет тот же диапазон значений, поэтому используется редко.

Замечание. Точные диапазоны и размеры памяти для разных типов данных изменяются от компилятора к компилятору, особенно если компиляторы работают в различных операционных системах. ANSI С гарантирует, что значение int занимает не меньше памяти, чем short int, и что long int занимает не меньше памяти, чем int.

Для очень маленьких значений используйте тип char. Несмотря на то что переменные char предназначены для представления символов, они могут также хранить значения, которые помещаются в одном байте, т.е. в диапазоне от -128 до +127 (включая 0), например:

char oneByte;

Знаковые и беззнаковые значения Все целые типы – char, short, int и long – являются знаковыми по умолчанию. Другими словами, переменные этих типов могут иметь положительные и отрицательные значения. Вы можете использовать ключевое слово signed, чтобы явно указать на это:

signed int plusOrMinus;

Простой тип int означает то же самое, слово signed обычно не пишут.

Если вам не нужно хранить отрицательные значения, вы можете перед любым целым типом использовать ключевое слово unsigned. Вот несколько примеров беззнаковых переменных:

unsigned char oneChar;

unsigned short oneShort;

unsigned int oneInt;

unsigned long oneLong;

Поскольку в беззнаковых переменных отрицательные значения не запоминаются, они могут представлять вдвое больше положительных значений, чем их знаковые эквиваленты. В табл. 1.2 сведены знаковые и беззнаковые целые типы данных с указанием размера занимаемой памяти и диапазонов значений.

Загляните в стандартный библиотечный заголовочный файл limits.h, чтобы узнать точные диапазоны значений целых типов данных для вашего типа компилятора.

Таблица 1. Целые типы данных Тип данных Размер в Размер в Минимальное Максимальное байтах битах значение значение signed char 1 8 -128 unsigned char 1 8 0 signed short 2 16 -32768 unsigned short 2 16 0 signed int 2 16 -32768 unsigned int 2 16 0 signed long 4 32 -2147483647 unsigned long 4 32 0 Поскольку переменные целых типов занимают фиксированный объем памяти, при использовании в выражениях они, подобно автомобильному одометру, могут переполняться, т.е. доходить до максимума и затем сбрасываться в нуль. Например, если беззнаковая целая переменная (unsigned int) i равна 15000, то выражение j = i + 60000;

присвоит беззнаковой целой переменной j значение 9464, а не 75000, которое лежит за границами представления значений типа unsigned int, т.е. результат «сворачивается», чтобы поместиться в меньшее пространство. Это не ошибка, а специальный эффект, позволяющий избежать затрат времени на проверку возможного переполнения. Однако рассчитывать на эффект «сворачивания» значения - это плохая практика. Ведь ваши программы могут работать и в других системах, где используется другой объем памяти для представления значений типа int.

Замечание. При переполнении беззнаковые целые переменные обнуляются.

Знаковые переменные обычно приводятся к максимальному в их диапазоне отрицательному числу.

Упражнение. Если k является переменной типа int, то чему будет равна k после выполнения выражения k = 1000 * 2000? А если k объявить как переменную типа long, то чему будет равно значение переменной в этом случае? (Постарайтесь угадать ответ, а затем напишите программу, чтобы проверить свои предположения.) Множественные объявления переменных При объявлении нескольких переменных одного и того же типа вы можете записать их либо одну за другой:

int v1;

int v2;

int v3;

int v4;

либо отдельными строками:

int v1;

int v2;

int v3;

int v4;

Гораздо удобнее вместо бесконечных повторений int ограничиться короткой записью:

int v1, v2, v3, v4;

Во всех трех случаях переменные vl, v2, v3 и v4 объявлены переменными типа int.

Литеральные целые значения Литеральными являются значения, которые вы вводите непосредственно в текст программы. Поскольку после компиляции программы вы не можете изменить их значения, литералы также называются константами. Чтобы обработать такие константы, как 1234 и –96, компилятор использует типы данных с наименьшим возможным диапазоном значений.

Иногда необходимо заставить компилятор хранить константы как данные определенного типа. Например, чтобы хранить константу 1234 не как int, а как long, добавьте букву L после последней цифры:

long bigValue = 1324L;

Чтобы задать беззнаковое значение, добавьте U. Константа 1234U запоминается как значение типа unsigned int. Константа 1234UL будет храниться как значение типа unsigned long int.

Вы можете также задавать литеральные значения в шестнадцатеричном и восьмеричном форматах. Константы, которые начинаются с цифры 0, интерпретируются как восьмеричные числа (десятичные константы не должны начинаться с нуля). Константы, начинающиеся с символов 0х, являются шестнадцатеричными. Листинг 1.7 отображает шестнадцатеричные, восьмеричные и десятичные числа в различных форматах.

Листинг 1.7. hexoct.c (шестнадцатеричные и восьмеричные константы) 1: #include 2:

3: main() 4: { 5: int hexValue = 0xf9a;

6: int octalValue = 0724;

7: int decimalValue = 255;

8:

9: printf(As decimal integers: \n);

10: printf( hexValue = %d\n, hexValue);

11: printf( octalValue = %d\n, octalValue);

12: printf( decimalValue = %d\n, decimalValue);

13:

14: printf(\nAs formatted integers:\n);

15: printf( hexValue = %x\n, hexValue);

16: printf( octalValue = %o\n, octalValue);

17: printf( decimalValue = %#x\n, decimalValue);

18: return 0;

19: } Выполнив программу, приведенную в листинге 1.7, вы увидите на экране следующие строки:

As decimal integers:

hexValue = octalValue = decimalValue = As formatted integers:

hexValue = f9a octalValue = decimalValue = 0xff Форматирование шестнадцатеричных и восьмеричных значений с помощью команд %d в операторах printf() отображает их эквивалентные десятичные значения (строки 10-12). Задавайте %о для получения восьмеричного формата (строка 16). Чтобы отобразить шестнадцатеричные значения, используйте формат %х (для вывода строчных букв от a до f, используемых в шестнадцатеричных числах) или %Х (для вывода прописных букв от A до F).

Значок # перед символами x или X позволяет выводить шестнадцатеричные числа в альтернативном формате. Например, строка 17 использует %#x для отображения десятичного значения 255 в виде шестнадцатеричного 0xff.

Ключевое слово const Как вам уже известно, вы можете запоминать значения в целых переменных с помощью операторов типа intValue = 1234;

Любой другой оператор присваивания может изменить значение intValue.

Например, оператор intValue = 4321;

присваивает 4321 переменной intValue, заменяя ее текущее значение. Если существует другая целая переменная newValue, то оператор intValue = newValue;

делает значение переменной intValue равным значению переменной newValue.

Чтобы запретить операторам изменять значения переменных, предварите их объявления ключевым словом const. Например, попробуйте заменить строку 5 листинга 1.7 на const int hexValue = 0xf9a;

и затем добавить оператор hexValue = 0xa9b;

Компилятор выдаст сообщение об ошибке «Cannot modify a const object» («Нельзя изменять константу»). Переменным, объявленным как константы, можно присваивать начальные значения, но другие операторы не могут изменять их – мера безопасности, гарантирующая, что эти значения останутся постоянными на протяжении всего времени работы программы.

Типы данных с плавающей запятой Существуют три типа данных с плавающей запятой: float, double и long double. (Типа long float не существует). В табл. 1.3 представлены размеры памяти и диапазоны значений для типов данных с плавающей запятой.

Таблица 1. Типы данных с плавающей запятой Размер в Размер в Минимальное Максимальное Тип данных байтах битах значение значение float 4 32 -3.4Е+38 3.4Е+ double 8 64 -1.7Е+308 1.7Е+ long double 10 80 -3.4Е+4932 1.1Е+ Замечание. Обратитесь к стандартному библиотечному заголовочному файлу float.h для уточнения диапазонов представления и других деталей, касающихся чисел с плавающей запятой.

Инициализируйте значения с плавающей запятой, присваивая им литеральные константы в объявлении double balance = 525.49;

либо используя отдельный оператор присваивания balance = 99.99;

Константы с плавающей запятой, такие как 525.49 или 99.99, имеют тип double, если за ними не следуют буквы f или F. Значение 3.14159F имеет тип float. Используйте букву l или L для получения значения типа long double, например 3.14159L.

Листинг 1.8 демонстрирует типичное применение переменных с плавающей запятой. Эта программа решает простую математическую задачу:

вычисляет площадь и длину окружности по заданному радиусу.

Листинг 1.8. circle.c (вычисление площади и длины окружности) 1: #include 2: const double PI = 3.14159;

3:

4: void main() 5: { 6: double r, s, l;

7: printf("\nEnter radius: ");

8: scanf("%lf", &r);

9: s = PI * r * r;

10: l = 2 * PI * r;

11: printf("Square of circle = %lf, circuit = %lf", s, l);

12: } В строке 2 объявляется глобальная константа PI, которая будет использоваться в вычислениях. Хороший прием – записывать константы прописными буквами, что явно указывает на то, что их значение не может быть изменено.

Строка 6 объявляет 3 переменных с плавающей запятой типа double.

Оператор printf() на следующей строке предлагает ввести радиус. За ввод данных в программе отвечает функция scanf(), объявленная в файле stdio.h и по форме аналогичная функции printf(). Оператор scanf("%lf", &r);

ожидает, пока вы не введете значение радиуса, после чего сохраняет введенное значение в переменной r. Оператор содержит один или несколько спецификаторов, заключенных в кавычки. В нашем случае это “%lf”, т.к. мы хотим ввести одно значение типа double.

За строкой формата следует переменная, предназначенная для приема данных. Выражение &r передает адрес переменной r функции scanf().

Подробнее тема адресов и указателей будет раскрыта в главе 4, а пока просто запомните, что перед именами всех переменных (за исключением строк) в функции scanf() необходимо поставить знак взятия адреса (&).

Листинг 1.8 содержит еще один новый элемент – выражение. В строках 9 и 10 вычисляется площадь и длина окружности по формулам: S = r2, L = 2r. Обратите внимание, что все переменные в программе circle имеют тип double. Это не случайно. Целые переменные плохо подходят для использования в арифметических выражениях. Особенно «опасны» для них выражения, в которых используются операции умножения и деления.

Так как целые переменные имеют относительно небольшой диапазон значений, умножение даже небольших чисел друг на друга может привести к переполнению и эффекту «сворачивания» целой переменной. Например, следующий «безобидный» оператор int i = 170 * 200;

на самом деле содержит потенциальную ошибку, т.к. переменная типа int не может вместить значение 34 000.

При делении целых чисел дробная часть результата отбрасывается и, если вы не будете внимательны, то можете допустить ошибку, подобную следующей:

float f = 170 / 200;

/* f равно 0, а не 0.85 */ Чтобы не потерять значащую информацию, используйте в арифметических выражениях переменные и константы с плавающей запятой.

Точность Использование значений с плавающей запятой можно сравнить с измерениями, выполняемыми с помощью линейки. В отличие от целочисленных вычислений, представляющих точные расчеты, точность вычислений значений с плавающей запятой зависит от размера переменной этого типа данных в памяти. Чем больший размер имеет переменная в памяти, тем ближе друг к другу расположены деления воображаемой линейки и тем с большей точностью происходят вычисления.

Значения типа float имеют около шести или семи значащих цифр (т.е.

различаемых цифр после десятичной точки);

значение типа double – 15 или 16, а long double – 19. Ошибка округления, вышедшая за пределы установленных значащих цифр, может привести к потере информации.

Концептуально существует бесконечное число значений между двумя вещественными числами. Между 0.0 и 1.0 находится значение 1.5;

между 1. и 1.5 есть 1.25 и т.д. Однако, поскольку для представления значения с плавающей запятой используется ограниченное количество битов, некоторые значения не могут быть точно представлены в памяти. Значения с плавающей запятой в компьютере лишь аппроксимируют истинные вещественные числа в математике. Вы можете представить любое значение в пределах диапазона выбранного типа данных с плавающей запятой, но не можете представить каждое значение с абсолютной точностью.

Листинг 1.9 демонстрирует особенности выражений с плавающей запятой.

Листинг 1.9. precise.c (точность вычислений значений с плавающей запятой) 1: #include 2:

3: main() 4: { 5: double d1 = 4.0 / 2.0;

6: double d2 = 1.0 / 3.0;

7: double d3 = 2.0E40 * 2.0E30 + 1;

8:

9: printf(d1 = %lf\n, d1);

10: printf(d2 = %lf\n, d2);

11: printf(d3 = %le\n, d3);

12: return 0;

13: } Строки 5-7 присваивают значения результатов трех выражений переменным типа double. Деление 4.0 на 2.0 дает результат 2.0, который может точно представлен. Однако при делении 1.0 на 3.0 получается нечто вроде 0.33333 – близко, но не точно. Десятичная цифра повторяется до бесконечности, и любое отсечение уменьшает общую точность. Третье выражение прибавляет 1 к произведению двух очень больших чисел.

Поскольку результат требует более 64 битов для его представления, полученное значение d3 (4Е+70) будет на 1 меньше, чем истинный ответ.

Пусть это не удивляет вас. Несколько простых советов помогут вам правильно использовать значения с плавающей запятой.

• Всюду, где только можно, объявляйте вместо 32-битовых переменных типа float 64-битовые переменные типа double. Тип данных double может безопасно представлять больше значащих цифр, чем float, и в большинстве случаев дает более точные результаты.

• Не используйте long double, если вы в самом деле не нуждаетесь в дополнительной точности, обеспечиваемой этим 80-битовым значением с плавающей запятой, которое требует больше памяти и времени на обработку. Для большинства приложений простые значения типа double позволяют достичь идеального компромисса между размером и точностью.

• Не используйте значения с плавающей запятой для хранения важных финансовых данных. Ошибки округления здесь неприемлемы. Для работы с финансовой и другой важной информацией существуют специальные классы чисел.

Символьные типы Для работы с символами и строками предназначен символьный тип char.

Переменные этого типа занимают в памяти компьютера один байт и этого как раз достаточно, чтобы хранить номер символа в стандартной для всех моделей PC таблице символов ASCII (см. прил. 1). Таким образом, в памяти компьютера все символы хранятся как числа, что облегчает работу с ними.

Использование отдельных символов Итак, переменные типа char могут запоминать небольшие значения в диапазоне от –128 до +127. Например, объявление char c = S;

инициализирует переменную с типа char ASCII-значением символа S (равным 83). Односимвольные константы заключаются в апострофы (одинарные кавычки).

Листинг 1.10 отображает символы в различных форматах.

Листинг 1.10. ascii.c (символы в десятичном и шестнадцатеричном форматах) 1: #include 2:

3: main() 4: { 5: char c;

6:

7: printf(Enter character: );

8: c = getchar();

9: printf(Character = %c\n, c);

10: printf(ASCII (dec) = %d\n, c);

11: printf(ASCII (hex) = %#x\n, c);

12: return 0;

13: } В строке 5 объявляется переменная типа char. В строке 8 вызывается описанная в файле stdio.h библиотечная функция getchar() для чтения символа с клавиатуры. Функция ожидает, пока вы не введете символ и не нажмете , затем возвращает значение символа, присвоенное здесь переменной с. После этого три оператора printf() отображают значение переменной в символьном (%c), десятичном (%d) и шестнадцатеричном (%#x) форматах.

Поскольку литеральные символы хранятся в памяти как значения int, а не как данные типа char, что можно было бы предположить, вы можете заменить строку 5 на int c;

после чего программа продолжает прекрасно работать. Действительно, многие библиотечные функции (и многие программы) разработаны в расчете на сохранение одиночных символов как значений int – это дань ранним версиям языка С.

Работа со строками Последовательность символов образует строку. В этой главе вы уже видели много примеров строк. Строка в С хранится как последовательность значений типа char, которые заканчиваются нулевым символом ‘\0’. Нулевой символ позволяет функциям, работающим со строками, обнаруживать концы строк.

Самое простое, что можно сделать, чтобы создать строковую переменную, это добавить к объявлению char заключенную в квадратные скобки максимальную длину строки. Например, запись char string[80];

объявляет переменную с именем string, которая может запоминать от 0 до символов, включая завершающий нулевой символ.

Чтобы прочитать строку символов с клавиатуры, выполните оператор типа scanf(%s, string);

Спецификатор %s указывает, что будет читаться строка. Операция взятия адреса перед именем строки в данном случае не нужна. Затем вы можете отобразить результат с помощью функции printf():

printf(Вы ввели %s, string);

Мы остановимся подробнее на строках и строковых функциях в главе 5.

Упражнение. Напишите программу, которая предлагала бы ввести имя, затем выводила бы сообщение «Привет, Х», где Х должен быть заменен на введенный текст.

Размеры переменных Компиляторы ANSI C могут отличаться друг от друга способом хранения данных различных типов. Вместо того чтобы строить предположения о количестве байтов, занимаемых конкретной переменной, воспользуйтесь услугами оператора sizeof().

Листинг 1.11 является удобной тестовой программой, которая с помощью оператора sizeof() отображает размер всех основных типов данных компилятора ANSI C.

Листинг 1.11. sizeof.c (применение оператора sizeof()) 1: #include 2:

3: main() 4: { 5: char c;

6: short s;

7: int i;

8: long l;

9: float f;

10: double d;

11: long double ld;

12:

13: printf(Size of char...... = %2d byte(s)\n, sizeof(c));

14: printf(Size of short..... = %2d byte(s)\n, sizeof(s));

15: printf(Size of int....... = %2d byte(s)\n, sizeof(i));

16: printf(Size of long...... = %2d byte(s)\n, sizeof(l));

17: printf(Size of float..... = %2d byte(s)\n, sizeof(f));

18: printf(Size of double.... = %2d byte(s)\n, sizeof(d));

19: printf(Size of long double = %2d byte(s)\n,sizeof(ld));

20: return 0;

21: } Замечание. Компиляция программы sizeof.c может вызвать многочисленные предупреждения о переменных, которые были объявлены, но не использовались («declared but never used»). Это предупреждение поможет вам избавиться от переменных, которые стали ненужными, и вы про них забыли. В данном случае можете не обращать на него внимания.

Программа объявляет переменные семи типов (строки 5-11), а затем отображает размер элемента каждого типа в байтах (строки 13-19).

Например, оператор printf(..., sizeof(c));

отображает размер переменной с типа char. Компилятор преобразует выражение sizeof(элемент) в целое значение, равное размеру элемента в байтах. Этот элемент может быть именем типа данных вроде int или float либо именем любой вашей переменной.

Символические константы Некоторые С-программы можно сравнить с криптограммами. Другие кажутся легкими для анализа и понимания. А разница между ними состоит не столько в сложности программы, сколько в том, какие идентификаторы, комментарии и символические константы использует программист.

Символическая константа представляет собой идентификатор, который что-нибудь означает. Например, в автоматах (большинство из которых представляет собой хитро замаскированные компьютеры) идентификатор JACKPOT может означать максимальную сумму выигрыша. Такой идентификатор обычно объявляется в начале программы с помощью директивы #define по следующему образцу:

#define JACKPOT Для удобства символические константы вводятся прописными буквами, чтобы их легко было различить в сложной программе. Вы должны понимать, что JACKPOT – это не переменная. Директива #define связывает некоторый символ (в данном случае JACKPOT) с заданным текстом (например, 45000). Другими словами, везде, где в тексте программы встречается слово JACKPOT, при запуске программы препроцессор заменит его на текст 45000.

Вы можете использовать символические константы в любом месте, где связанные с ними литералы имеют смысл. Например, если в программе объявлена символическая константа #define NAME Jack Pot то компилятор будет интерпретировать оператор printf(NAME);

так, как если бы вы написали printf(Jack Pot);

Очень часто совершают ошибку, заканчивая определение символической константы точкой с запятой. Следующая запись неверна:

#define JACKPOT 45000;

/* ??? */ Когда вы попытаетесь использовать этот неудачный символ, например в операторе printf(JackPot = $%d\n, JACKPOT);

/* ??? */ компилятор попытается скомпилировать эту строку как printf(Jack Pot = $%d\n, 45000;

);

/* ??? */ что вызовет ошибку неверного синтаксиса.

Определение собственных символических констант Определение символических констант в начале программы или в заголовочном файле создает набор легко модифицируемых символов.

Листинг 1.12 демонстрирует типичные способы использования директивы #define.

Листинг 1.12. define.c (использование символических констант) 1: #include 2:

3: #define CHARACTER @ 4: #define STRING Mastering ANSI C\n 5: #define OCTAL 6: #define HEXADECIMAL 0x9b 7: #define DECIMAL 8: #define FLOATING_POINT 3. 9:

10: main() 11: { 12: printf(CHARACTER = %c\n, CHARACTER);

13: printf(STRING = STRING);

14: printf(OCTAL = %o\n, OCTAL);

15: printf(HEXADECIMAL = %#x\n, HEXADECIMAL);

16: printf(DECIMAL = %d\n, DECIMAL);

17: printf(FLOATING_POINT = %f\n, FLOATING_POINT);

18: return 0;

19: } Если у вас большая программа, то сохранение многочисленных символических констант в одном месте облегчит изменение параметров программы и вам не придется делать изменения во всех операторах, которые их используют.

Рассмотрите внимательно операторы printf() в строках 12-17. Строка 13 не является ошибочной. При обработке строки printf(STRING =, STRING);

константа STRING заменяется на соответствующий текст:

printf(STRING = Mastering ANSI C\n);

При компиляции программы эти смежные строки соединяются в одну длинную строку. Таким образом, этот оператор отобразит одну строку:

STRING = Mastering ANSI C В качестве полезного упражнения перепишите каждый из операторов printf() (строки 12-17), заменив вручную символические константы соответствующими им значениями. Это поможет вам разобраться, как обрабатываются операторы подобного рода.

Предопределенные символические константы Кроме ваших собственных, вы можете использовать предопределенные символические константы из различных заголовочных файлов. Например, в заголовочном файле math.h есть несколько очень полезных констант:

#define M_E 2. #define M_LOG2E 1. #define M_PI 3. #define M_PI_2 1. #define M_PI_4 0. #define M_SQRT2 1. Если вы включите в программу директиву #include , то вам станут доступны эти и другие математические константы. Например, если вам потребуется использовать значение в выражении, вы можете записать:

result = M_PI * value;

Это поможет вам избежать возможных опечаток при самостоятельном вводе числа.

Упражнение. Модифицируйте листинг 1.8, используя в выражениях более точное значение числа, представленное константой из файла math.h.

Замечание. Директива #define позволяет создавать текстовые макроопределения (или макросы), представляющие собой вид мини-языка, встроенного во все компиляторы ANSI C. Некоторые библиотечные функции низкого уровня определяются как макросы, чтобы избежать многочисленных вызовов подпрограмм для таких простых операций, как, например, чтение одного символа с клавиатуры. Может быть, в свое время и вы захотите узнать, как строить свои собственные макросы, но сейчас благоразумнее отложить эту задачу до тех пор, пока вы не почувствуете себя увереннее. Многие опытные программисты за свою жизнь не написали ни одного макроса, и вы ничего не потеряете, если присоединитесь к их числу.

Перечисления Предположим, вашей программе необходимо работать с цветами радуги. Вы могли бы представить их в виде символических констант следующим образом:

#define RED #define ORANGE #define YELLOW #define GREEN #define BLUE #define INDIGO #define VIOLET Это удобный способ представления названия цветов в виде уникальных целых значений. После определения этих символических констант программа может объявить, например, целую переменную int color;

и затем присвоить ей любой из «цветов» вашей «радуги»:

color = GREEN;

Компилятор заменит символическую константу GREEN связанным с ней текстом, в данном случае цифрой 3. В результате будет скомпилирован оператор color = 3;

Но, как правило, символ GREEN воспринимается лучше литерального значения 3.

Ввод длинной последовательности названий цветов и других относительно устойчивых имен (например названий месяцев) – это довольно нудная работа. Кроме того, подобное определение символических констант никак не отражает их родства. Чтобы облегчить процесс создания подобных списков, ANSI C имеет полезное средство, называемое перечислимым типом.

Используя ключевое слово enum, вы можете создать семь упомянутых выше констант цвета следующим образом:

enum {RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET};

За ключевым словом enum следует заключенный в фигурные скобки список идентификаторов (обычно прописными буквами), разделенных запятыми. Список завершает обязательная точка с запятой. Каждому символу последовательно присваивается целое значение, начиная с нуля. RED получает значение 0, ORANGE – 1, YELLOW – 2 и т.д.

После сделанного с помощью enum объявления можно объявить переменную с именем color и присвоить ей значение BLUE:

int color = BLUE;

Перечислимые типы данных имеют внутреннее представление с минимально возможным объемом памяти – в знаковых либо беззнаковых типах char или int – и, следовательно, всегда могут быть присвоены целым переменным.

С помощью другого ключевого слова – typedef (сокращение от «Type definition» – «Определение типа») – можно определить новое, более информативное имя типа данных. Используя typedef, более формализованное перечисление цветов радуги можно записать следующим образом:

typedef enum { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET } Colors;

Вопреки распространенному мифу (и звучанию ключевого слова – оно как раз и вводит в заблуждение), typedef не определяет новый тип данных, а лишь создает для него псевдоним (Colors). Псевдоним обычно начинается с большой буквы, хотя это не является жестким правилом.

После определения псевдонима вы можете объявить переменную как Colors oneColor;

Теперь oneColor является переменной типа Colors, и ей можно присваивать символические константы цветов с помощью оператора, аналогичного следующему:

oneColor = INDIGO;

Но так как Colors является перечислимым типом, вы также можете присваивать переменным этого типа целые значения:

oneColor = 5;

Как вы уже знаете, компилятор автоматически нумерует элементы в объявлении enum. Но иногда нужно изменить эту нумерацию и присвоить заданные значения одному или нескольким символам. Например, рассмотрим проблему создания перечислимого типа для названий месяцев. Вы могли бы сделать следующее объявление:

enum {JAN, FEB, MAR, APR, JUN, JUL, AUG, SEP, OCT, NOV, DEC};

В этом объявлении нет ошибки. Но так как первому символу перечислимого списка присваивается значение 0, элемент JAN получает значение 0, а не 1, которое принято связывать с первым месяцем года.

Эту проблему можно решить, явно присвоив значение любому элементу перечислимого списка. Например, чтобы установить JAN равным 1, вы можете заменить предыдущее объявление на enum {JAN = 1, FEB, MAR, APR, JUN, JUL, AUG, SEP, OCT, NOV, DEC};

Выражение JAN = 1 присваивает единицу символу JAN. Следующие символы продолжают эту последовательность с этого места, присваивая FEB значение 2, MAR – 3, APR – 4 и т.д. Вы можете поступить аналогичным образом с любым элементом перечислимого списка. Можно даже пронумеровать месяцы десятками:

enum {JAN = 10, FEB = 20,..., DEC = 120};

но такое насилие над элементами списка вряд ли когда-либо потребуется.

Лучше всего предоставить работу по присваиванию значений компилятору, а самим пользоваться преимуществом легко узнаваемых имен символических констант.

Замечание. Использование перечислимых типов делает более наглядным текст вашей программы, но не текст, который программа отображает на экране. Вы не сможете отобразить на экране символы JAN, FEB или INDIGO только благодаря использованию перечислимых типов. Хранясь в памяти как целые типы данных, на экране перечисления будут отображаться так же, как значения int или char.

Упражнение. Создайте перечислимый тип данных для дней недели.

Преобразование типов Хотя значения int и long являются целыми, их не назовешь лепестками одного и того же цветка. Нам осталось разобраться в том, что случится, если в выражении смешаются значения различных типов данных.

Не забывайте, что все переменные занимают в памяти фиксированное количество байтов. Если проигнорировать этот факт, то программа может преподнести неприятный сюрприз. Рассмотрим следующие объявления:

long aLong = 12345678;

int anInt;

Что же произойдет, если использовать следующий оператор присваивания:

anInt = aLong;

/* ??? */ Поскольку переменная anInt может запоминать значения только в диапазоне от –32 768 до +32 767 (табл. 1.2), она не сможет вместить значение переменной aLong. Несмотря на очевидную ошибку, компилятор позволит свершиться этой «несправедливости», присвоив anInt значение 24 910 – совсем не то, что вы ожидали.

Оказывается, число 24 910 – это остаток от деления числа 12 345 678 на число 65 536, последнее является наибольшим беззнаковым значением, которое может содержаться в двух байтах. Это отнюдь не случайно. Значение long, сохраняемое в переменной aLong, усекается, чтобы поместиться в меньшей области памяти, отведенной для переменной anInt.

Присваивание значений больших переменных меньшим может вызвать аналогичную потерю информации, и компилятор может отреагировать предупреждающим сообщением, а может и «закрыть на это глаза».

Присваивание меньших значений большим обычно не вызывает никаких неприятностей. Например, если значение переменной anInt равно 4321, то оператор aLong = anInt;

присвоит переменной aLong то же самое значение 4321, которое великолепно вписывается в диапазон типа long int.

В приведенном примере компилятор выполнил расширение двухбайтового значения int до четырехбайтового значения long. Расширения также встречаются в выражениях, включающих значения с плавающей запятой. Если fp является переменной типа float, а db – типа double, то оператор db = fp;

расширит fp до db без потери информации. Однако обратное присваивание fp = db;

может вызвать проблемы, так как значение типа float не в состоянии охватить тот же диапазон, что и double.

Встречается и другой вид расширения, когда целым присваиваются значения с плавающей запятой. Такое присваивание разрешается, но, конечно, теряется дробная часть. Обратная операция – присваивание целых переменным с плавающей запятой – также разрешена.

Смешение целых значений и значений с плавающей запятой вызывает преобразование типов, результаты которого не всегда легко предсказать.

Рассмотрим следующие объявления:

int i = 4;

double d = 2.8;

Затем выполним оператор i = d * i;

Чему будет равно i? В смешанных выражениях значения с меньшим диапазоном расширяются до значений с большим диапазоном, и, чтобы обработать выражение d * i, компилятор сначала преобразует целую переменную i (равную 4) в значение типа double, умножит это значение на d (4.0 * 2.8 = 11.2), а затем присвоит результат переменной i. На последней стадии значение с плавающей запятой 11.2 сужается до типа int, присваивая в итоге переменной i значение 11.

Использование операции приведения типа В наших силах отменять или, по крайней мере, более гибко управлять действующими по умолчанию правилами компилятора, касающимися расширения типов данных. Возьмем объявления из предыдущего примера:

int i = 4;

double d = 2.8;

Если вы хотите умножить неокругленный целый эквивалент значения переменной d на i (т.е. в итоге получить 8 вместо 11), используйте операцию приведения типов:

i = (int)d * i;

Выражение (int)d заставляет компилятор преобразовать переменную d в значение типа int (при этом дробная часть числа 2.8 отбрасывается и получается целое значение 2). После этого 2 умножается на значение переменной i (равное 4), в результате чего получается 8.

Совет. Проявляйте осторожность при использовании смешанных выражений в ваших программах. Чтобы избежать потери информации, не присваивайте значения больших переменных меньшим. Также не рекомендуется смешивать в выражениях знаковые и беззнаковые переменные.

Резюме • Все С-программы должны иметь функцию main(). С этой функции начинается выполнение программы.

• Сообщения об ошибках (Errors) указывают на серьезные промахи, которые не позволяют компилятору завершить работу. Предупреждения (Warnings) свидетельствуют о менее серьезных ошибках и разрешают программам работать, но их следует исправить как можно скорее.

• Включение в программу заголовочных файлов с помощью директивы #include позволяет вам получить доступ к объявлениям стандартных (или написанных вами) функций, константам и другим элементам.

• Комментарии заключаются в ограничительные символы /* и */.

Компилятор игнорирует эти ограничители и весь текст между ними.

• Ключевые слова являются зарезервированными символами языка.

Идентификаторы – это слова, которые вы придумываете сами.

Идентификаторы должны начинаться с буквы или символа подчеркивания и могут включать только буквы английского алфавита, цифры и символы подчеркивания.

• Язык С чувствителен к регистру букв. Идентификаторы myVar и MYVAR рассматриваются как разные слова.

• Целыми типами данных являются char, short, int и long, которые считаются знаковыми по умолчанию. Этим типам могут предшествовать ключевые слова signed или unsigned. Само по себе слово unsigned является синонимом типу unsigned int.

• Типами данных с плавающей запятой являются float, double и long double. Используя их, вы не можете представить вещественные числа с абсолютной точностью. Для большинства приложений тип double является удачным компромиссом между размером и точностью.

• Литеральные символы записываются подобно ‘a’. Литеральные строки записываются аналогично “abcdef”.

• Используйте оператор sizeof для определения объема памяти, занимаемого типами данных и переменными.

• Используйте директивы #define для определения символических констант.

• Используйте ключевое слово enum для определения перечисляемых символических констант.

• Будьте осторожны в использовании выражений, в которых смешиваются типы данных.

Обзор функций Функция printf() (stdio.h) Прототип и краткое описание int printf(const char *format,...);

Функция записывает форматированную строку в стандартный поток вывода и возвращает количество выводимых на печать байтов информации.

Если произошла ошибка вывода, printf() возвращает значение EOF.

Спецификаторы преобразования C помощью спецификаторов преобразования функция printf() может выводить на печать значения различных типов данных. В табл. 1. представлены наиболее употребительные спецификаторы.

Таблица 1. Спецификаторы преобразования функции printf() Спецификатор Тип данных Описание %d int Десятичное целое число со знаком %u unsigned int Десятичное целое число без знака %ld long int Длинное десятичное целое число со знаком %lu unsigned long Длинное десятичное целое число без int знака %o целые типы Восьмеричное целое число без знака %x или %X целые типы Шестнадцатеричное целое число без знака. Спецификатор X используется для вывода числа цифрами 0-9 и буквами A-F, а х – для вывода числа цифрами 0-9 и буквами a-f %f или %e float Число с плавающей точкой, десятичное или экспоненциальное представление %lf или %le double Число с плавающей точкой двойной точности, десятичное или экспоненциальное представление %Lf или %Le long double Число long double, десятичное или экспоненциальное представление %с char Одиночный символ %s char * Строка символов %% Печать знака процента Задание ширины поля и точности представления Точный размер поля, в котором печатаются данные, задается шириной поля. Если ширина поля больше, чем необходимо для печати данных, то данные обычно выравниваются внутри поля по его правому краю. Целое число, задающее ширину поля, может быть вставлено между знаком процента (%) и спецификатором преобразования.

Пример: %4d – печать целого значения в поле шириной 4.

Функция printf() дает также возможность задать точность представления, с которой будут напечатаны данные. Для этого поместите между знаком процента и спецификатором преобразования десятичную точку (.) с последующим числом.

Пример: %.4f Точность имеет различный смысл для различных типов данных. Если она используется при выводе чисел с плавающей запятой, то точность – это количество цифр, которые будут напечатаны после десятичной точки.

Пример: %5.2Lf – вывод значения типа long double в поле шириной символов с двумя цифрами после десятичной точки.

Если точность используется со спецификаторами преобразования целых чисел, то она обозначает минимальное количество цифр, которое должно быть выведено. Если выводимое значение содержит меньше цифр, чем задано точностью, то перед ним будут напечатаны префиксные нули, так, чтобы общее количество цифр стало равно заданной точности. Для спецификатора %s точность – это максимальное число символов строки, которое будет напечатано.

Флаги Функция printf() предусматривает использование флагов, дополняющих возможности форматирования. Чтобы использовать в спецификации флаг, поместите его непосредственно справа от знака процента. В одной спецификации могут быть объединены несколько флагов.

Таблица 1. Флаги функции printf() Флаг Описание 1 – Выравнивание выводимой строки по левому краю в пределах заданной ширины поля.

Пример: %-20s + Значения со знаком печатаются со знаком «плюс», если они положительны, и со знаком «минус», если они отрицательны.

Пример: %+6.2f Окончание табл. 1. 1 Пробел Значения со знаком «плюс» печатаются с пробелом (но без знака), если они положительны, и со знаком «минус», если они отрицательны.

Пример: % 6.2f # Альтернативный формат. Выводит в начале 0 для спецификатора %o и 0х или 0Х для спецификаторов %x и %X.

Пример: %#o 0 Дополняет поле до заданной ширины нулями слева.

Пример: %04d Функция scanf() (stdio.h) Прототип и краткое описание int scanf(const char *format,...);

Считывает форматированный ввод со стандартного потока ввода (как правило, с клавиатуры);

ввод завершается на первом встретившемся символе пробела, табуляции или при нажатии . Для ввода значений с помощью scanf() вы можете применять те же спецификаторы преобразования, что и в printf() (табл. 1.4).

2. Действия программы Компьютерные программы, написанные на С, могут сортировать базы данных, делать вычисления по заданным формулам, распечатывать таблицы и многое другое. Разве можно представить себе кино с неподвижными и немыми актерами? Точно так же программа без действий – это уже не программа, а нечто застывшее и скучное. Как действия в кино оживляют неподвижные картинки, так и программы обрабатывают данные, вычисляя выражения и выполняя операторы.

Выражения Выражения – это операции, которые выполняют программы.

Выражения бывают разные – от очень простых до невероятно сложных, но все они имеют свои значения. Например, переменная А типа int является простейшим выражением, которое представляет значение переменной А.

Выражение (А + В) равно значению суммы слагаемых А и В. Обычно результат выражения присваивается какой-нибудь переменной с помощью оператора присваивания:

С = А + В;

Значение С теперь равно сумме А и В.

Операторы Язык С включает несколько операторов, без которых не обходится практически ни одна программа. В следующих разделах вы познакомитесь с арифметическими и логическими операторами, а также операторами отношений, отрицания, инкремента и декремента.

Арифметические операторы Еще сидя за школьной партой, вы использовали арифметические операторы +, –, * и / (табл. 2.1). В языке С эти операторы работают аналогичным образом. Оператор % используется для вычисления остатка от деления целых чисел. Например, значение выражения 24 % 11 (читается « по модулю 11») равно 2, т.е. остатку от целочисленного деления 24 / 11.

Значение выражения 8 % 2 равно 0, так как 8 делится на 2 без остатка.

Таблица 2. Арифметические операторы Оператор Описание Пример * Умножение a * b / Деление a / b + Сложение a + b - Вычитание a – b % Деление по модулю a % b Листинг 2.1 содержит пример выражений, использующих арифметические операторы. В примере выполняется преобразование значений градусов по Фаренгейту в градусы по Цельсию.

Листинг 2.1. celsius.c (преобразование градусов) 1: #include 2: #include 3:

4: main() 5: { 6: double fdegrees, cdegrees;

7:

8: printf("Fahrenheit to Celsius conversion\n\n");

9: printf("Degrees Fahrenheit? ");

10: scanf("%lf", &fdegrees);

11: cdegrees = ((fdegrees - 32.0) * 5.0) / 9.0;

12: printf("Degrees Celsius = %lf\n", cdegrees);

13: return 0;

14: } Программа использует две переменные с плавающей запятой типа double – fdegrees и cdegrees. После ввода количества градусов по Фаренгейту с помощью функции scanf() выражение ((fdegrees – 32.0) * 5.0) / 9. вычисляет эквивалентную температуру в градусах по Цельсию. При этом наличие круглых скобок в выражении влияет на порядок выполнения операций: прежде всех выполняются операции, заключенные во «внутренние» круглые скобки. В этом примере выражение вычисляется в такой последовательности:

1) 32.0 вычитается из fdegrees;

2) результат первого шага умножается на 5.0;

3) результат второго шага делится на 9.0.

Совет. Всегда используйте столько круглых скобок, сколько нужно для того, чтобы сделать ясной структуру выражения. Круглые скобки совершенно не влияют на быстродействие, с их помощью вы просто задаете желаемый порядок вычисления.

Операторы отношений Операторы отношений обрабатывают свои операнды таким образом, чтобы получить либо истинный (ненулевой), либо ложный (нулевой) результат. В языке С ложный результат соответствует нулю, а истинный – ненулевому значению. Выражение (A < B) истинно только в том случае, если А меньше В. Переменные А и В должны принадлежать к таким типам данных, которые можно сравнивать. Обычно это целые или значения с плавающей запятой. (Для сравнения строк операторы отношений использовать нельзя;

в главе 5 объясняется, как сравнивать строки с учетом порядка букв.) В табл. 2.2 перечислены все операторы отношений языка С.

Таблица 2. Операторы отношений Оператор Описание Пример < Меньше a < b <= Меньше или равно a <= b > Больше a > b >= Больше или равно a >= b == Равно a == b != Не равно a != b Замечание. Оператор равенства в С представлен двойным знаком равенства, таким образом, выражение (А == В) будет истинно, только если А равно В. Не путайте такие выражения с операторами типа А = В, которые присваивают значение В переменной А. Для операторов присваивания всегда используйте одиночный знак равенства (=), а для сравнения – двойной (==).

Логические операторы Логические операторы && и || объединяют выражения отношений в соответствии с правилами логического И и ИЛИ. Используйте логический оператор И (&&) в сложных выражениях отношений, аналогичных следующему:

(A < B) && (B < C) которое будет истинным, только если А меньше В и В меньше С.

Таким же образом используйте оператор ИЛИ (||). Выражение (A < B) || (B < C) будет истинным, только если А меньше В или В меньше С.

Сложные логические выражения вычисляются рациональным способом. Например, если при вычислении выражения (A <= B) && (B == C) оказалось, что А больше В, то все выражение примет значение «ложь», и вторая часть (В == С) вычисляться не будет. Такая сокращенная обработка сложных логических выражений способствует быстрому выполнению программы.

Оператор отрицания Вы можете инвертировать результат любого логического выражения с помощью унарного (т.е. имеющего только один операнд) оператора отрицания ‘!’. Выражение !(A < B) («неверно, что А меньше В») эквивалентно выражению (A >= B).

Оператор неравенства != связан с оператором отрицания. Выражение (A != B) («А не равно В») эквивалентно выражению !(A == B) («не верно, что А равно В»).

Операторы инкремента и декремента Двумя самыми интригующими операторами языка С являются ++ (инкремент) и –– (декремент). Оператор ++ («плюс плюс») прибавляет к операнду единицу. Оператор -- («минус минус») вычитает единицу.

Несколько примеров проясняют, как работают эти операторы.

Следующих две строки дают идентичные результаты:

i = i + 1;

/* прибавление единицы */ i++;

/* то же самое */ Следующие два выражения также дают одинаковые результаты:

i = i – 1;

/* вычитание единицы из i */ i--;

/* то же самое */ В работе с операторами инкремента и декремента есть одна существенная деталь. Значения выражений, подобных i++ или i––, зависят от расположения операторов ++ и ––. Если оператор следует за своим операндом, то значение выражения i++ или i–– равно значению i до модификации. Другими словами, оператор j = i++;

присваивает переменной j первоначальное значение переменной i. Например, если значение i равно 7, то после выполнения оператора инкремента значение i будет равно 8, а j получит значение 7.

Если же операторы инкремента и декремента предшествуют своим операндам, значение выражения будет равно значению операнда после модификации, т.е. оператор j = ++i;

присваивает переменной j инкрементированное значение i. Если значение i равно 7, то после выполнения этого оператора обе переменные j и i станут равными 8.

Чтобы лучше понять эти различия, можно обратиться к длинным формам записи выражений инкремента и декремента. Действие оператора j = i++;

аналогично действию двух следующих операторов:

j = i;

i++;

А оператор j = ++i;

действует подобно такой паре операторов:

++i;

j = i;

Если вы просто хотите прибавить или отнять от переменной единицу:

i++;

расположение операторов инкремента и декремента не имеет значения.

Оператор присваивания Вы уже знакомы с оператором присваивания, а теперь, пора узнать поближе этот важный символ. Рассмотрим следующий простой оператор, который присваивает значение В переменной А:

A = B;

/* присвоить значение В переменной А */ В результате выполнения этого оператора значение В не изменяется, а прежнее значение А исчезает, как утренний туман в лучах солнца.

Выражение A = 1234;

вполне законно при условии, что переменная А может хранить числовые значения. Выражение 1234 = A;

/* ??? */ не имеет смысла и не будет компилироваться, потому что литеральная константа (1234) не является переменной и не может принимать значения.

Множественное присваивание Как вы уже знаете, каждое выражение имеет свое значение. Выражение (А = В) имеет значение, равное значению присваиваемого операнда.

Следовательно, выражение С = (А = В);

полностью допустимо. Сначала заключенное в круглые скобки подвыражение (А = В) присваивает значение В переменной А. Затем значение этого подвыражения (равное В) присваивается переменной С.

Таким образом, как переменная С, так и А, сейчас равна В. Можно даже обойтись без круглых скобок. Выражение С = А = В;

присваивает значение В переменным А и С.

Принимая это во внимание, возьмите на вооружение один из способов инициализации многих переменных. Выражение С = А = В = 451;

присваивает число 451 переменным А, В и С.

Сокращенный оператор присваивания Операторы присваивания иногда выполняют больше работы чем нужно. В операторе А = А + 45;

компилятор сначала генерирует код для вычисления выражения А + 45, результат которого затем присваивается опять переменной А. То же самое выражение можно записать проще:

А += 45;

Эта запись помогает компилятору сгенерировать программный код более рационально. Двойной символ += называется сокращенным оператором присваивания. Вот полный набор сокращенных операторов:

*=, /=, +=, –=, %=, <<=, >>=, &=, ^=, |=.

Следующие несколько примеров демонстрируют, как работает сокращенная форма операторов (переменная count имеет тип int).

count += 10;

/* count = count + 10;

*/ count *= 2;

/* count = count * 2;

*/ count /= 3;

/* count = count / 3;

*/ count %= 16;

/* count = count % 16;

*/ Большинство современных компиляторов способны генерировать эффективный программный код и для полных форм операторов присваивания, но в сложных выражениях, где участвуют не такие простые переменные, как count, множественные ссылки на переменные могут снизить быстродействие программы. Используйте сокращенные операторы присваивания, где только это возможно. Они ускорят выполнение программы.

Приоритеты операторов В сложных выражениях операторы с более высоким приоритетом выполняются в первую очередь. Причем одни операции выполняются слева направо, а другие – справа налево. Это свойство операторов называется ассоциативностью.

Для получения точных результатов, т.е. для указания нужного порядка выполнения операций, используйте круглые скобки, например:

y = ((a * x) + b) / (x + c);

Так как операторы * и / имеют более высокий приоритет, чем + и –, то представленное выше выражение можно записать проще и получить при этом правильный результат, а именно:

y = (a * x + b) / (x + c);

Изучите приоритеты операторов и правила ассоциативности, приведенные в табл. 2.3. Но чем полагаться исключительно на приоритет операторов, добавьте лучше лишнюю пару круглых скобок – они не повлияют на быстродействие, а программа станет более понятной.

Таблица 2. Приоритет операторов и порядок вычисления (ассоциативность) Уровень Порядок Операторы приоритета вычислений 1 (). [] –> Слева направо 2 * & ! ~ ++ -- + – (тип) sizeof Справа налево 3 * / % Слева направо 4 + – Слева направо 5 << >> Слева направо 6 < <= > >= Слева направо 7 == != Слева направо 8 & Слева направо 9 ^ Слева направо 10 | Слева направо 11 && Слева направо 12 || Слева направо 13 ? : Справа налево 14 = *= /= += –= %= <<= >>= &= ^= |= Справа налево 15, Слева направо Комментарий. Унарный плюс (+) и унарный минус (–) находятся на уровне 2, и их приоритет выше, чем у арифметических плюса и минуса, находящихся на уровне 4. Символ & на уровне 2 является оператором взятия адреса, а символ & на уровне 8 представляет поразрядный оператор И. Символ * на уровне 2 – это оператор разыменования, а символ * на уровне 3 означает оператор умножения. При отсутствии круглых скобок операторы одного уровня вычисляются в соответствии с порядком вычислений.

Назначение некоторых операторов сейчас может показаться вам непонятным. Мы подробно рассмотрим их в следующих главах.

Оператор if Оператор if («если») действует точно так, как ожидается. Если выражение истинно, оператор выполнит действие;

в противном случае оператор выполняться не будет.

В общем виде оператор if можно записать так:

if (выражение) оператор;

Выражением может служить любое выражение, которое в результате дает значение «истина» или «ложь». Если выражение истинно (ненулевое), то оператор выполняется. Если выражение ложно (нуль), то оператор пропускается.

Оператор if может быть составным:

if (выражение) { оператор1;

оператор2;

} Расположение фигурных скобок в составном операторе if строго не регламентируется, однако правила хорошего стиля написания программ (см.

прил. 2) рекомендуют выравнивать скобки, как в предыдущем фрагменте, либо следующим образом:

if (выражение) { оператор1;

оператор2;

} Для записи сравниваемых выражений вы можете использовать оператор отношения:

if (выражение == значение) оператор;

Оператор будет выполнен только в том случае, если выражение равно значению. Не следует записывать оператор if в таком виде:

if (выражение = значение) /* ??? */ оператор;

в противном случае вы получите предупреждение компилятора о неверном, возможно, присваивании: вы, вероятно, не хотели присваивать значение выражению, а скорее всего случайно ввели один знак равенства вместо двух – это одна из самых распространенных ошибок.

Листинг 2.2 показывает, как использовать оператор if. Скомпилируйте и запустите программу, затем введите число от 1 до 10. Если вы введете число, выходящее за пределы этого диапазона, программа выдаст сообщение об ошибке.

Листинг 2.2. choice.c (использование оператора if) 1: #include 2:

3: main() 4: { 5: int number;

6: int okay;

7:

8: printf(Enter a number from 1 to 10: );

9: scanf(%d, &number);

10: okay = (number >= 1) && (number <= 10);

11: if (!okay) 12: printf(Incorrect answer!\n);

13: return okay;

14: } Строка 10 присваивает переменной okay типа int истинное или ложное значение результата выражения отношения (number >= 1) && (number <= 10);

Результат выражения представляет собой целое значение – ноль, если вычисление даст «ложь», или ненулевое значение в случае «истины». Это значение присваивается переменной okay, после чего строка 11 проверяет выражение !okay («не о’кей»). Если okay ложно (в случае ошибки), то !okay даст значение «истина», и тогда строка 12 выведет сообщение об ошибке.

Строка 13 возвращает значение переменной okay («истину» или «ложь») обратно в операционную систему, хотя в данном случае в этом нет необходимости – это просто демонстрация такой возможности.

Вы можете также записывать сложные логические выражения непосредственно в операторах if. Например, вы могли бы записать:

if ((number < 1) || (number > 10)) printf(Incorrect answer!\n);

Выражения (number < 1) и (number > 10) заключены в скобки лишь для удобства. Следующая запись вполне корректна.

if (number < 1 || number > 10) printf(Incorrect answer!\n);

Оператор else Оператор else («иначе») является расширением оператора if. После любого оператора if вы можете вставить оператор else для выполнения альтернативного действия. Используйте else следующим образом:

if (выражение) оператор1;

else оператор2;

Если выражение истинно (ненулевое), выполняется оператор1;

в противном случае – оператор2. Операторы могут быть простыми или составными. Вы можете записать:

if (выражение) { оператор1;

оператор2;

} else { оператор3;

оператор4;

} Составные операторы могут иметь любую глубину вложенности, поэтому оператору1 разрешено иметь еще один оператор if, который, в свою очередь, может выполнять другие операторы if. Логика глубоко вложенных операторов if вряд ли будет прозрачной, но компилятор не налагает никаких ограничений на их количество. Лучше всего ограничиться двумя уровнями вложения.

Как и в простых операторах if, расположение фигурных скобок в сложных конструкциях if-else жестко не регламентируется. Некоторые программисты предпочитают выравнивать фигурные скобки так:

if (выражение) { оператор1;

оператор2;

} else { оператор3;

оператор4;

} С помощью конструкций if-else вы можете создать многовариантный выбор:

if (выражение1) оператор1;

else if (выражение2) оператор2;

else if (выражениеN) операторN;

else /* необязательно */ оператор_умолчания;

/* необязательно */ Давайте разберемся, как работает такая конструкция. Если выражение истинно, то выполняется оператор1;

если истинно выражение2, то выполняется оператор2 и т.д. Во всех остальных случаях (т.е. если ни одно из выражений не имеет значения «истина») выполняется оператор_умолчания.

Листинг 2.3, используя вложенные операторы if-else, определяет високосные годы. Скомпилируйте и запустите программу, затем введите год, например 2004. Год, открывающий новое столетие, является високосным, если без остатка делится на 400. Промежуточные годы будут високосными, если они без остатка делятся на 4.

Листинг 2.3. leap.c (определение високосного года) 1: #include 2:

3: main() 4: { 5: int leapYear;

6: int year;

7:

8: printf(Leap Year Calculator\n);

9: printf(Year? );

10: scanf(%d, &year);

11: if (year > 0) { 12: if ((year % 100) == 0) 13: leapYear = ((year % 400) == 0);

14: else 15: leapYear = ((year % 4) == 0);

16: if (leapYear) 17: printf(%d is a leap year\n, year);

18: else 19: printf(%d is not a leap year\n, year);

20: } 21: return 0;

22: } Оператор if в строке 11 помогает избежать ошибки. Если введенный год оказался меньше нуля, вычисления производиться не будут. Оператор if в следующей строке определяет, является ли введенный год первым годом столетия. Если да, то строка 13 определяет, делится ли год на 400 без остатка.

Если нет, то нужно определить, делится ли год без остатка год на 4 (строка 15).

Оператор if-else, содержащий ошибку (чаще всего это неправильное расположение фигурных скобок или их отсутствие), может привести к неправильной работе всей программы. Листинг 2.4 демонстрирует испорченную программу.

Листинг 2.4. badif.c (неправильная вложенность операторов if-else) 1: #include 2:

3: main() 4: { 5: int value;

6:

7: printf(Value (1..10)? );

8: scanf(%d, &value);

9: if (value >= 1) 10: if (value > 10) 11: printf(Error: value > 10 \n);

12: else 13: printf(Error: value < 1 \n);

14: return 0;

15: } Запустите программу badif и введите число от 1 до 10. Предполагалось, что числа, не входящие в этот диапазон, будут отброшены, но эта программа бракует числа именно из заданного диапазона и некоторые, лежащие за его пределами. Оператор if в строке 9 сравнивает введенное число с единицей, и если оно больше или равно 1, то внутренний оператор if сравнивает его с 10.

Если ваше число больше 10, будет выдано сообщение об ошибке. Проблема в том, что код не работает так, как ожидается.

Во всем виноват оператор else на строке 12. Несмотря на то что он выравнивается с оператором if в строке 9, логически он связан с ближайшим оператором if, находящимся на строке 10. Здесь и заключается ошибка.

Теперь ваша задача – исправить программу так, чтобы оператор else работал в одной связке с нужным оператором if.

Совет. Чтобы проверить логику программы, никогда не полагайтесь на сделанные вами отступы. Они только для вас, компилятор не обращает на них никакого внимания. А вот фигурные скобки заставят его прислушаться к вашим пожеланиям относительно вложенных операторов if-else.

Листинг 2.5 демонстрирует исправленный вариант программы.

Листинг 2.5. goodif.c (правильная вложенность операторов if-else) 1: #include 2:

3: main() 4: { 5: int value;

6:

7: printf(Value (1..10)? );

8: scanf(%d, &value);

9: if (value >= 1) { 10: if (value > 10) 11: printf(Error: value > 10 \n);

12: } else 13: printf(Error: value < 1 \n);

14: return 0;

15: } Условные выражения Сокращенный оператор if-else, называемый условным выражением, иногда очень полезен. В общем виде его можно записать так:

выражение1 ? выражение2 : выражение 3;

Программа вычисляет выражение1. Если оно истинно, то результат всего выражения равен выражению2. Если же выражение1 ложно, результат равен выражению3. Условное выражение в точности эквивалентно оператору if-else:

if (выражение1) выражение2;

else выражение3;

Обычно вы присваиваете результат условного выражения какой-нибудь переменной. Предположим, вы пишете программу, управляющую меню.

Если переменная типа int menuChoice будет равна ‘Y’, вы хотите установить другую переменную testValue равной 100;

в противном случае – равной нулю. Одно из решений использует оператор if-else:

if (menuChoice == Y) testValue = 100;

else testValue = 0;

Эта конструкция достаточно ясна, но данный оператор требует двух ссылок на переменную testValue. Чтобы обратиться к testValue только один раз и выполнить ту же самую работу, вы можете записать:

testValue = (menuChoice == Y) ? 100 : 0;

Если выражение (menuChoice == ‘Y’) будет истинным, то оператор присвоит переменной testValue значение 100, если ложным – значение 0.

Не следует использовать условные выражения только потому, что они занимают одну строку вместо четырех. В большинстве случаев компилятор генерирует аналогичный код как для оператора if-else, так и для эквивалентного условного выражения. С другой стороны, чтобы избежать двух ссылок на одно и то же сложное выражение (особенно, если оно имеет сложную и громоздкую форму), предпочтительнее использовать условное выражение.

Оператор switch Вложенный набор операторов if-else может выглядеть как запутанный водопровод старого дома. Система работает, но трудно понять, какая труба куда ведет. Рассмотрим следующую серию операторов if-else, каждый из которых сравнивает выражение с определенным значением:

if (выражение == значение1) оператор1;

else if (выражение == значение2) оператор2;

else if (выражение == значениеN) операторN;

else /* необязательно */ оператор_умолчания;

/* необязательно */ Эту конструкцию можно сократить с помощью более простого оператора switch:

switch (выражение) { case значение1:

оператор1;

/* выполняется, если выражение == значение1 */ break;

/* выход из оператора switch */ case значение2:

оператор2;

/* выполняется, если выражение == значение2 */ break;

/* выход из оператора switch */ case значениеN:

операторN;

/* выполняется, если выражение == значениеN */ break;

/* выход из оператора switch */ default:

оператор_умолчания;

/*выполняется, если не было совпадений*/ } На первый взгляд эта запись может показаться более длинной, но на практике оператор switch использовать проще, чем эквивалентную конструкцию if-else.

За ключевым словом switch следует выражение, которое будет сравниваться с заданными значениями внутри оператора. Строка case значение1:

сравнивает значение1 с результатом вычисления выражения оператора switch. Если сравнение даст значение «истина», то будет выполнен оператор (или блок операторов), следующий за строкой case. Если же результатом сравнения будет «ложь», аналогичным образом будет обрабатываться следующая строка case. Последняя строка оператора switch (default) задает действия, выполняемые в случае, если ни одно из значений case не совпало со значением выражения.

Оператор break в каждом блоке выбора осуществляет немедленный выход из оператора switch. Операторы switch разработаны таким образом, что если вы напишите их без оператора break switch (выражение) { case значение1:

оператор1;

/* ??? */ case значение2:

оператор2;

/* ??? */ case значение3:

оператор3;

} и выражение будет равно значению1, то после выполнения оператора последует выполнение оператора2 и оператора3, хотя вряд ли именно это входило в ваши планы. Первый же случай сравнения, давший в результате «истину», приведет к выполнению всех последующих операторов, вплоть до конца оператора switch.

Замечание. Некоторые программисты иногда намеренно опускают операторы break из операторов switch, чтобы позволить одному блоку case выполнять действия, записанные и в других блоках. Технически это разрешено, но затрудняет процесс отслеживания результатов и создает трудности при внесении изменений. Лучше всего завершать каждый блок case своим оператором break.

Одним из самых распространенных программных интерфейсов является меню команд. Листинг 2.6 демонстрирует, как писать программу, обслуживающую меню, используя оператор switch. Скомпилируйте и запустите программу, затем введите букву A, D, S или Q, чтобы выбрать команду. (Команды не делают ничего полезного, и вы можете развить эту программу по своему вкусу.) Введите букву, которая не входит в этот маленький список, и вы увидите, как оператор switch обнаружит ошибку ввода.

Листинг 2.6. menu.c (демонстрация оператора switch) 1: #include 2: #include 3:

4: main() 5: { 6: int choice;

7:

8: printf(Menu: A)dd, D)elete, S)ort, Q)uit: );

9: choice = toupper(getchar());

10: switch (choice) { 11: case A:

12: printf(You selected Add\n);

13: break;

14: case D:

15: printf(You selected Delete\n);

16: break;

17: case S:

18: printf(You selected Sort\n);

19: break;

20: case Q:

21: printf(You selected Quit\n);

22: break;

23: default:

24: printf(\nIllegal choice!\n);

25: } 26: return 0;

27: } Чтобы прочитать символ, введенный с клавиатуры, строка 9 вызывает стандартную библиотечную функцию getchar(). Функция toupper() переводит этот символ в верхний регистр (делает прописным), упрощая таким образом определение нажатой клавиши.

Оператор switch начинается на строке 10. Выбранный символ рассматривается в качестве анализируемого выражения. Последующие блоки case сравнивают переменную choice с символами ‘A’, ‘D’, ‘S’ и ‘Q’, выдавая подтверждающее сообщение в случае, если сравнение было удачным. Если совпадения с заданными буквами не произошло, блок default (строки 23-24) выведет сообщение об ошибке.

Оператор while Оператор while – один из трех операторов цикла языка С. Программы используют его для повторного выполнения операторов в течение всего времени, пока заданное условие истинно.

while (выражение) оператор;

В переводе с английского эту запись можно прочитать так: «Пока выражение истинно, выполнять оператор». Как обычно, оператор может быть простым или составным:

while (выражение) { оператор1;

оператор2;

.........

} Листинг 2.7 использует цикл while для счета от 1 до 10.

Листинг 2.7. wcount.c (счет от 1 до 10 с помощью цикла while) 1: #include 2:

3: main() 4: { 5: int counter;

6:

7: printf(while count\n);

8: counter = 1;

9: while (counter <= 10) { 10: printf(%d\n, counter);

11: counter++;

12: } 13: return 0;

14: } Строка 8 инициализирует целую переменную counter, называемую управляющей переменной цикла. Оператор while в строках 9-12 сравнивает значение переменной counter с числом 10. Пока counter меньше или равно 10, будут выполняться операторы на строках 10-11. Строка 11 увеличивает counter на единицу на каждой итерации цикла, гарантируя таким образом, что цикл в конце концов закончится. (Цикл обычно должен выполнять хотя бы один оператор, который влияет на его условие, в противном случае он превратится бесконечный цикл.) Чему будет равно значение переменной counter после окончания работы оператора while? Проверьте свою догадку, вставив следующий оператор между строками 12 и 13:

printf(counter = %d\n, counter);

Почему переменная counter имеет конечное значение 11? Чему оно будет равно, если заменить выражение в строке 9 на (counter < 10)? Что произойдет, если в строке 8 присвоить переменной counter значение, равное 11? Будет ли цикл выполняться?

Ответив на эти вопросы, вы определите важное свойство цикла while:

если анализируемое выражение с самого начала ложно, ни один из его операторов не будет выполнен.

Упражнение. Модифицируйте листинг 2.7 в направлении обратного счета от 10 до 1.

В условии цикла while можно использовать и другие виды переменных.

Листинг 2.8 выводит английский алфавит с помощью символьной переменной.

Листинг 2.8. walpha.c (вывод алфавита с помощью цикла while) 1: #include 2:

3: main() 4: { 5: char c;

6:

7: printf(while alphabet\n);

8: c = А;

9: while (c <= Я) { 10: printf(%c, c);

11: c++;

12: } 13: return 0;

14: } Выражение в строке 9 (с <= ‘Z’) истинно, если ASCII-значение символа (т.е. его номер в таблице ASCII – см. приложение 1) меньше или равно ASCII-значению буквы ‘Z’.

Упражнение. Измените листинг 2.8 таким образом, чтобы программа выводила на экран русский алфавит.

Оператор do-while Оператор do-while («делай – пока») является в некотором роде перевернутым циклом while.

do { оператор;

} while (выражение);

Выполняется оператор, затем проверяется выражение. До тех пор пока значение выражения будет истинным, оператор будет выполняться.

Сравните эту схему работы с рассмотренным выше оператором while, который вычисляет свое выражение прежде, чем выполнить какой-нибудь оператор. Цикл while вообще может не выполнить ни одного оператора, если анализируемое выражение с самого начала было ложным. Однако do-while всегда выполняет свои операторы, по крайней мере, один раз, т.к. проверка выражения осуществляется в конце каждой итерации цикла. Из этого следует основное правило, необходимое при выборе между while и do-while.

• Спросите себя: «Существуют ли такие условия, при которых операторы цикла не должны выполняться ни разу?» Если ответом будет «да», то вам, скорее всего, следует выбрать цикл while.

• Если ответ на предыдущий вопрос будет «нет», то вам, вероятно, подойдет цикл do-while.

Листинг 2.9 аналогичен приведенной выше программе wcount, однако использует для счета от 1 до 10 цикл do-while.

Листинг 2.9. dwcount.c (счет от 1 до 10 с помощью цикла do-while) 1: #include 2:

3: main() 4: { 5: int counter;

6:

7: printf(do-while count\n);

8: counter = 0;

9: do { 10: counter++;

11: printf(%d\n, counter);

12: } while (counter < 10);

13: return 0;

14: } Задание. Кроме выполнения программы dwcount на компьютере, попытайтесь выполнить ее вручную, записывая значения переменной counter на бумаге. Выясните, почему в строке 12 используется выражение (counter < 10), а не (counter <= 10), как это было в программе wcount. Проверьте свои предположения, вставив операторы printf() для отображения значения переменной counter в интересующие вас места программы (это неплохое средство отладки).

Условием цикла do-while может быть любое логическое выражение.

Чтобы продемонстрировать такую многогранность, листинг 2.10 использует цикл do-while для вывода на экран алфавита.

Листинг 2.10. dwalpha.c (вывод алфавита с помощью цикла do-while) 1: #include 2:

3: main() 4: { 5: char c;

6:

7: printf(do-while alphabet\n);

8: c = A - 1;

9: do { 10: c++;

11: printf(%c, c);

12: } while (c < Z);

13: return 0;

14: } Оператор for Когда вам точно известно, сколько раз должны выполняться операторы цикла, лучше всего использовать третий оператор цикла языка С – for.

for (выражение1;

выражение2;

выражение3;

) { оператор;

} На первый взгляд, оператор for может показаться сложным, но он станет понятнее, если его представить в виде эквивалентного ему оператора while:

выражение1;

while (выражение2;

) { оператор;

выражение3;

} Например, для того чтобы с помощью цикла for посчитать от 1 до 10, можно взять переменную i типа int и записать:

for (i = 1;

i <= 10;

i++) /* выражения 1, 2, и 3 */ printf(i = %d\n, i);

/* оператор */ Первое выражение цикла for в этом примере присваивает начальное значение переменной i, равное 1. Это действие выполняется только один раз, перед самым началом цикла. Второе выражение обычно является выражением отношения. В нашем случае оно будет равно «истине», если значение i меньше или равно 10. Третье, и последнее, выражение увеличивает на единицу значение переменной i, приближая, таким образом, цикл к концу.

Предыдущий оператор for можно изобразить в виде эквивалентного цикла while:

i = 1;

/* выражение 1 */ while (i <= 10) { /* выражение 2 */ printf(i = %d\n, i);

/* оператор */ i++;

/* выражение 3 */ } Листинг 2.11 использует цикл for для отображения набора видимых символов из основной части таблицы ASCII (это символы со значениями от 32 до 127).

Листинг 2.11. fascii.c (отображение ASCII-символов с помощью цикла for) 1: #include 2:

3: main() 4: { 5: char c;

6:

7: for (c = 32;

c < 128;

c++) { 8: if ((c % 32) == 0) printf(\n);

9: printf(%c, c);

10: } 11: printf(\n);

12: return 0;

13: } Когда вы запустите программу fascii, она отобразит на экране следующую 96-символьную таблицу:

!”#$%&’()*+,-./0123456789:;

<=>?

@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ ‘abcdefghijklmnopqrstuvwxyz{|}~ Цикл for (строки 7-10) первоначально устанавливает управляющую переменную с типа char равной ASCII-значению символа пробела (32).

Управляющее выражение (с < 128) завершает цикл после отображения последнего символа, т.е. когда переменная с станет равной 128. Выражение с++ увеличивает управляющую переменную на каждом проходе цикла на единицу.

Оператор if, находящийся внутри оператора for, сравнивает выражение (с % 32), равное остатку от деления с на 32, с нулем. Если результат сравнения – значение «истина», то оператор printf() начинает новую строку.

Замените 32 на 16, и вы получите вывод по 16 символов в строке.

Упражнение. Замените цикл for в листинге 2.11 эквивалентным циклом while.

Бесконечный цикл Выражения оператора for в предыдущем примере можно было бы опустить, но в этом случае мы получаем бесконечный цикл:

for ( ;

;

) ;

/* бесконечный цикл */ Этот цикл не инициализирует управляющую переменную, не содержит управляющего выражения и не выполняет никаких действий, чтобы завершиться. Он также не имеет никаких операторов. Во время выполнения программа «зависает», пока не произойдет какое-нибудь внешнее событие:

например, пропадет электропитание или сигнал прерывания заставит процессор начать выполнение другой программы.

Пробел между закрывающей круглой скобкой и завершающей точкой с запятой поставлен не случайно. Без этого пробела некоторые компиляторы С выдают предупреждение, которое помогает избежать следующей ошибки:

for (i = 0;

i < 100;

i++);

/* ??? */ оператор;

Внимательно рассмотрите этот фрагмент. Цикл не выполняет никаких полезных действий – сто раз выполняется «пустой» оператор (;

), следующий после закрывающей круглой скобки for;

оператор выполнится только один раз.

Оператор break Иногда бывает нужно прервать выполнение цикла while, do-while или for. Листинг 2.12 демонстрирует, как оператор break может прервать программу подобно тому, как срочный выпуск новостей прерывает телевизионную передачу.

Листинг 2.12. breaker.c (действие оператора break) 1: #include 2:

3: main() 4: { 5: int count;

6:

7: printf(\n\nfor loop:\n);

8: for (count = 1;

count <= 10;

count++) { 9: if (count > 5) break;

10: printf(%d\n, count);

11: } 12:

13: printf(\n\nwhile loop:\n);

14: count = 1;

15: while (count <= 10) { 16: if (count > 5) break;

17: printf(%d\n, count);

18: count++;

19: } 20:

21: printf(\n\ndo/while loop:\n);

22: count = 1;

23: do { 24: if (count > 5) break;

25: printf(%d\n, count);

26: count++;

27: } while(count <= 10);

28:

29: return 0;

30: } Программа breaker выполняет операторы for (строки 8-11), while (строки 15-19) и do-while (строки 23-27). Каждый оператор считает от 1 до 10, используя переменную count. Каждый цикл также выполняет оператор break до того, как переменная count достигнет своего конечного значения (строки 9, 16, 24), поэтому эти циклы заканчиваются преждевременно.

Данный метод очень полезен, особенно, когда нужно в спешке «спасаться бегством» из цикла: например, для того, чтобы отреагировать на ошибочную информацию.

Операторы break можно использовать и в других случаях (вы уже встречали их раньше внутри оператора switch). Например, широко распространен метод программирования, при котором бесконечный цикл использует оператор break, чтобы выйти из «вечности»:

for ( ;

;

) { /* выполнять цикл вечно */............. /* различные операторы */ if (выражение) /* если выражение окажется истинным */ break;

/* прервать цикл */ } Так обычно пишется главный цикл приложений для Windows, программируются контроллеры, программы с пользовательским интерфейсом. Многие программисты предпочитают организовывать бесконечный цикл с помощью ы while:

while (1) { /* бесконечный цикл while */.............

if (выражение) break;

} Упражнение. В листинге 2.1, чтобы преобразовать не одно значение температуры, а несколько, вам приходится каждый раз заново запускать программу. Добавьте в программу цикл для повторения преобразований до тех пор, пока вы не захотите выйти из программы.

Оператор continue Оператор continue похож на break, но он заставляет цикл прервать текущую итерацию и начать следующую. Листинг 2.13 демонстрирует различие между операторами break и continue.

Листинг 2.13. continue.c (действие оператора continue) 1: #include 2:

3: main() 4: { 5: int count;

6:

7: printf(\nStarting for loop with continue...\n);

8: for (count = 1;

count <=10;

count++) { 9: if (count > 5) continue;

10: printf(%d\n, count);

11: } 12: printf(After for loop, count = %d\n, count);

13:

14: printf(\n\nStarting for loop with break...\n);

15: for (count = 1;

count <= 10;

count++) { 16: if (count > 5) break;

17: printf(%d\n, count);

18: } 19: printf(After for loop, count = %d\n, count);

20: return 0;

21: } Чтобы посчитать от 1 до 10, программа continue использует два цикла for (строки 8-11 и 15-18). Строка 9 первого цикла for выполнит оператор continue, если переменная count будет больше 5. Строка 16 выполнит оператор break при том же условии. За исключением этого различия приведенные циклы идентичны.

Оператор continue прерывает последовательное выполнение программы и передает управление в начало цикла. Оператор printf() в строке 10, таким образом, пропускается. Оператор break немедленно осуществляет выход из цикла, передавая управление следующему после цикла оператору (строка 19).

Программа отображает значение переменной count внутри и снаружи циклов. После выхода из первого цикла оно равно 11, после выхода из второго – 6.

Оператор goto Оператор goto позволяет перейти в указанное место программы, и начиная с этой позиции, продолжить выполнение операторов программы.

Поскольку goto позволяет «прыгать» в любое место программы, его использование напоминает желание оставить проторенную дорогу ради замеченной вами извилистой тропинки в поле.

На первый взгляд, goto кажется очень полезным оператором. Однако на практике он дает программистам слишком большую свободу «скакать» по программе куда угодно. В лучшем случае, результаты программы, которая имеет несколько goto операторов, просто трудно понять, в худшем – такая программа вообще работает плохо.

Если вы не можете обойтись без goto, поставьте метку (неиспользованный ранее идентификатор и двоеточие) в нужном месте программы (перед оператором, на который вы хотите «прыгнуть»).

Выполните goto МЕТКА, и «течение» вашей программы будет направлено «по новому руслу». Листинг 2.14 продемонстрирует, как использовать goto для счета от 1 до 10.

Листинг 2.14. gcount.c (счет от 1 до 10 с помощью оператора goto) 1: #include 2:

3: main() 4: { 5: int count = 1;

6:

7: printf(\ngoto count\n);

8: TOP:

9: printf(%d\n, count);

10: count++;

11: if (count <= 10) goto TOP;

12: return 0;

13: } Метка TOP в строке 8 помечает «пункт назначения» для оператора goto. Оператор if на строке 11 проверяет значение целой переменной count.

Если оно меньше или равно 10, goto передает управление на метку TOP, чтобы снова выполнить операторы printf() и count++, пока переменная count не станет больше 10.

Программа, конечно, работает, но ей не хватает наглядности циклов while, do-while и for, описанных в этой главе. Понимание goto-варианта требует отслеживания вручную работы каждого оператора. Отладка сложных программ с оператором goto – это кропотливый и неблагодарный труд. Тем не менее вы должны знать, как использовать операторы goto, т.к. можете встретиться с ними в чужой программе. Но не используйте их в своих собственных – это тот оператор, без которого можно обойтись.

Резюме • Программы выполняют действия, вычисляя выражения и выполняя операторы.

• Все выражения имеют свои значения. Например, значение выражения (А = В) равно значению, присвоенному переменной А.

• Операторы языка С *, /, +, – и % выполняют арифметические операции над операндами. Операторы отношений <, <=, >, >=, == и != сравнивают два операнда между собой. Логические операторы && и || объединяют выражения в соответствии с правилами логического И и ИЛИ. Оператор отрицания ! инвертирует значение логического выражения.

• Оператор инкремента ++ прибавляет единицу к своему операнду.

Оператор декремента –– вычитает единицу из своего операнда. Эти операторы могут стоять как перед, так и после своих операндов.

• Сокращенные операторы присваивания позволяют сократить операторы типа value = value + 10 до value += 10. Наиболее часто в языке С используются сокращенные операторы: *=, /=, +=, –= и %=.

• Все операторы имеют приоритеты. Выражения, использующие операторы с более высоким приоритетом, вычисляются раньше выражений, которые используют операторы с низким приоритетом.

• Для управления порядком вычисления в выражениях всегда можно использовать круглые скобки, изменяя, таким образом, действующие по умолчанию приоритеты операторов и их ассоциативность.

• Операторы if и if-else позволяют принимать решения в зависимости от того, является ли анализируемое выражение истинным или ложным.

• Используйте операторы switch для упрощения длинных конструкций if else. Убедитесь, что каждый блок case заканчивается оператором break.

• Используйте операторы while для создания циклов, которые выполняются, пока проверяемое условие остается истинным. В цикле while условное выражение проверяется в самом начале и, следовательно, если оно изначально ложно, то операторы цикла выполняться не будут.

• Используйте операторы do-while для создания циклов, выполняющихся до тех пор, пока проверяемое условие не станет ложным. В циклах do while условное выражение вычисляется в конце, и, следовательно, их операторы выполняются, по крайней мере, один раз.

• Оператор for, возможно, самый популярный оператор в языке С.

Используйте его, когда вы знаете или можете вычислить заранее, сколько итераций должен иметь цикл.

• Оператор break немедленно завершает цикл.

• Оператор continue заставляет цикл начать следующую итерацию.

• Оператор goto передает управление на любой помеченный оператор.

Pages:     || 2 | 3 | 4 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.