Book: Язык программирования C. Лекции и упражнения. 6-е издание



Язык программирования C. Лекции и упражнения. 6-е издание

Язык программирования C. Лекции и упражнения. 6-е издание
Язык

программирования

ЛЕКЦИИ И УПРАЖНЕНИЯ

6-е издание





C Primer Plus

Sixth Edition

Stephen Prata


▼▼

Addison

Wesley

Upper Saddle River, NJ • Boston • Indianapolis • San Francisco


New York • Toronto • Montreal • London • Munich • Paris • Madrid


Cape Town • Sydney • Tokyo • Singapore • Mexico City


Язык

Язык программирования C. Лекции и упражнения. 6-е издание
программирования

ЛЕКЦИИ И УПРАЖНЕНИЯ

6-е издание

Стивен Прата


швей

Москва • Санкт-Петербург • Киев


2015


ББК 32.973.26-018.2.75


П70

Язык программирования C. Лекции и упражнения. 6-е издание
УДК 681.3.07



Зав. редакцией С.Н. Тригуб


Перевод с английского Ю.Н. Артеменко


Под редакцией Ю.Н. Артеменко

По общим вопросам обращайтесь в Издательский дом “Вильямс” по адресу:


[email protected]://www.winiamspublishing.com

Прата, Стивен.

П70 Язык программирования С. Лекции и упражнения, 6-е изд. : Пер. с англ. —М : ООО “И.Д. Вильямс”, 2015. — 928 с. : ил. - Парал, тит. англ.

ISBN 978-5-8459-1950-2 (рус.)

Язык программирования C. Лекции и упражнения. 6-е издание


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

Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Addison-Wesley Publishing Company, Inc.

Authorized translation from the English language edition published by Addison-Wesley Publishing Company, Inc, Copyright © 2014 by Pearson Education, Inc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the publisher.

Russian language edition is published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2015.

Научно-популярное издание


Стивен Прата

Язык программирования С. Лекции и упражнения

6-е издание

Верстка Т.Н. Артеменко Художественный редактор В.Г. Пашютин

Подписано в печать 24.02.2015. Формат 70x100/16.


Гарнитура Times.

Уел. печ. л. 74,82. Уч.-изд. л. 54,2.

Тираж 500 экз. Заказ № 867.

Отпечатано способом ролевой струйной печати


в АО «Первая Образцовая типография»

Филиал «Чеховский Печатный Двор»

142300, Московская область, г. Чехов, ул. Полиграфистов, д. 1


Сайт: www.chpd.iu. E-mail: [email protected], тел.: 8(499)270 73 59

ООО “И. Д. Вильямс", 127055, г. Москва, ул. Лесная, д. 43, стр. 1

Язык программирования C. Лекции и упражнения. 6-е издание
© Издательский дом “Вильямс", 2015 © Pearson Education, Inc., 2014


Оглавление

Глава 1. Предварительные сведения                                                                                              25

Глава 2. Введение в язык С                                                                                                               51

Глава 3. Данные в языке С                                                                                                                77

Глава 4. Символьные строки и форматированный ввод-вывод                                             П7

Глава 5. Операции, выражения и операторы                                                                              157

Глава 6. Управляющие операторы С: циклы                                                                              199

Глава 7. Управляющие операторы С: ветвление и переходы                                                247

Глава 8. Символьный ввод-вывод и проверка достоверности ввода 293 Глава 9. Функции  325

Глава 10. Массивы и указатели                                                                                                     367

Глава 11. Символьные строки и строковые функции                                                                419

Глава 12. Классы хранения, связывание и управление памятью                                          479

Глава 13. Файловый ввод-вывод                                                                                                    531

Глава 14. Структуры и другие формы данных                                                                           565

Глава 15. Манипулирование битами                                                                                            627

Глава 16. Препроцессор и библиотека С                                                                                      661

Глава 17. Расширенное представление данных                                                                        717

Приложение А. Ответы на вопросы для самоконтроля                                                            791

Приложение Б. Справочные материалы                                                                                      829

Приложение В. Набор символов ASCII                                                                                        917

Язык программирования C. Лекции и упражнения. 6-е издание
Предметный указатель


Содержание

Об авторе                                                                                                                                                  21

Благодарности                                                                                                                                        21

Предисловие                                                                                                                                            23

Глава 1. Предварительные сведения                                                                                                25

Появление языка С                                                                                                                                 26

Причины популярности языка С                                                                                                       26

Конструктивные особенности                                                                                                     26

Эффективность                                                                                                                                27

Переносимость                                                                                                                                27

Мощь и гибкость                                                                                                                             28

Ориентация на программистов                                                                                                   28

Недостатки                                                                                                                                       28

Происхождение языка С                                                                                                                      29

Особенности функционирования компьютеров                                                                            30

Языки программирования высокого уровня и компиляторы                                                    31

Стандарты языка С                                                                                                                               32

Первый стандарт ANSI/ISO С                                                                                                     33

Стандарт С99                                                                                                                                   33

Стандарт C11                                                                                                                                  34

Использование языка С: семь этапов                                                                                               35

Этап 1: определение целей программы                                                                                    35

Этап 2: проектирование программы                                                                                         35

Этап 3: написание кода                                                                                                                36

Этап 4: компиляция                                                                                                                        36

Этап 5: запуск программы на выполнение                                                                              37

Этап 6: тестирование и отладка программы                                                                          37

Этап 7: сопровождение и модификация программы                                                            38

Комментирование                                                                                                                           38

Механика программирования                                                                                                            38

Файлы объектного кода, исполняемые файлы и библиотеки                                            39

Операционная система Unix                                                                                                        41

Коллекция компиляторов GNU и проект LLVM                                                                     43

Системы Linux                                                                                                                                 43

Компиляторы командной строки для РС                                                                                 44

Интегрированные среды разработки (Windows)                                                                    44

Опция Windows/Linux                                                                                                                    46

Работа с языком С в системах Macintosh                                                                                 46

Как организована эта книга                                                                                                               47

Соглашения, принятые в этой книге                                                                                                 47

Шрифты и начертание                                                                                                                   47

Вывод программы                                                                                                                           48

Специальные элементы                                                                                                                 49

Резюме                                                                                                                                                       49

Вопросы для самоконтроля                                                                                                                50

Упражнения по программированию                                                                                                50


Содержание 7

Глава 2. Введение в язык С                                                                                                                 51

Простой пример программы на языке С                                                                                         52

Пояснение примера                                                                                                                               53

Проход 1: краткий обзор                                                                                                              54

Проход 2: нюансы программы                                                                                                   55

Структура простой программы                                                                                                         63

Советы по обеспечению читабельности программ                                                                     64

Еще один шаг в использовании языка С                                                                                         65

Документирование                                                                                                                         65

Множественные объявления                                                                                                       66

Умножение                                                                                                                                        66

Вывод нескольких значений                                                                                                        66

Множество функций                                                                                                                             66

Знакомство с отладкой                                                                                                                        68

Синтаксические ошибки                                                                                                               68

Семантические ошибки                                                                                                                 69

Состояние программы                                                                                                                   70

Ключевые слова и зарезервированные идентификаторы                                                          71

Ключевые понятия                                                                                                                                72

Резюме                                                                                                                                                       73

Вопросы для самоконтроля                                                                                                                73

Упражнения по программированию                                                                                                74

Глава 3. Данные в языке С                                                                                                                  77

Демонстрационная программа                                                                                                          78

Что нового в этой программе?                                                                                                    79

Переменные и константы                                                                                                                    80

Ключевые слова для типов данных                                                                                                 81

Сравнение целочисленных типов и типов с плавающей запятой                                    82

Целые числа                                                                                                                                     82

Числа с плавающей запятой                                                                                                        83

Базовые типы данных языка С                                                                                                          84

Тип int                                                                                                                                                 84

Другие целочисленные типы                                                                                                       88

Использование символов: тип char                                                                                            92

Тип_Bool                                                                                                                                           98

Переносимые типы: stdint.h и inttypes.h                                                                                   98

Комплексные и мнимые типы                                                                                                   105

За пределами базовых типов                                                                                                    105

Размеры типов                                                                                                                               108



Использование типов данных                                                                                                          108

Аргументы и связанные с ними ловушки                                                                                      109

Еще один пример: управляющие последовательности                                                            111

Результаты выполнения программы                                                                                       111

Сброс буфера вывода                                                                                                                 112

Ключевые понятия                                                                                                                              113

Резюме                                                                                                                                                    113

Вопросы для самоконтроля                                                                                                             114

Упражнения по программированию                                                                                              116


8 Содержание

Глава 4. Символьные строки и форматированный ввод-вывод                                                m

Вводная программа                                                                                                                            118

Введение в символьные строки                                                                                                       119

Массив типа char и нулевой символ                                                                                       119

Использование строк                                                                                                                   120

Функция strlen()                                                                                                                             121

Константы и препроцессор С                                                                                                          123

Модификатор const                                                                                                                      127

Работа с символическими константами                                                                                127

Исследование и эксплуатация функций printf() и scanf()                                                         129

Функция printf()                                                                                                                             130

Использование функции printf()                                                                                               130

Использование функции scanf()                                                                                               144

Ключевые понятия                                                                                                                              151

Резюме                                                                                                                                                    152

Вопросы для самоконтроля                                                                                                             153

Упражнения по программированию                                                                                              155

Глава 5. Операции, выражения и операторы                                                                              157

Введение в циклы                                                                                                                                158

Фундаментальные операции                                                                                                           160

Операция присваивания: =                                                                                                        160

Операция сложения: +                                                                                                                 163

Операция вычитания: -                                                                                                               163

Операции знака: - и +                                                                                                                  163

Операция умножения: *                                                                                                              164

Операция деления: /                                                                                                                     166

Приоритеты операций                                                                                                                167

Приоритет и порядок вычисления                                                                                           169

Некоторые дополнительные операции                                                                                         170

Операция sizeof и тип size_t                                                                                                       170

Операция деления по модулю: %                                                                                            171

Операции инкремента и декремента: ++ и --                                                                         172

Декрементирование: --                                                                                                                176

Приоритеты операций                                                                                                                177

Не умничайте                                                                                                                                177

Выражения и операторы                                                                                                                   178

Выражения                                                                                                                                     179

Операторы                                                                                                                                      179

Составные операторы (блоки)                                                                                                 182

Преобразования типов                                                                                                                       184

Операция приведения                                                                                                                  187

Функции с аргументами                                                                                                                    188

Демонстрационная программа                                                                                                       190

Ключевые понятия                                                                                                                              191

Резюме                                                                                                                                                    192

Вопросы для самоконтроля                                                                                                             193

Упражнения по программированию                                                                                              196


Содержание 9

Глава 6. Управляющие операторы С: циклы                                                                              199

Повторный обзор цикла while                                                                                                          200

Комментарии к программе                                                                                                        201

Цикл чтения в стиле С                                                                                                                 202

Оператор while                                                                                                                                     203

Завершение цикла while                                                                                                              204

Когда цикл завершается?                                                                                                           204

Оператор while: цикл с предусловием                                                                                    205

Особенности синтаксиса                                                                                                            205

Сравнение: операции и выражения отношений                                                                         207

Что такое истина?                                                                                                                        208

Что еще является истинным?                                                                                                     209

Затруднения с понятием истины                                                                                              210

Новый тип _Bool                                                                                                                           212

Приоритеты операций отношений                                                                                          213

Неопределенные циклы и циклы со счетчиком                                                                          215

Цикл for                                                                                                                                                  216

Использование цикла for для повышения гибкости                                                           217

Дополнительные операции присваивания: +=, -=, * = , / = , %=                                              221

Операция запятой                                                                                                                                222

Греческий философ Зенон и цикл for                                                                                      225

Цикл с постусловием: do while                                                                                                         226

Выбор подходящего цикла                                                                                                               229

Вложенные циклы                                                                                                                               230

Анализ программы                                                                                                                       230

Изменение поведения вложенного цикла                                                                              230

Введение в массивы                                                                                                                            231

Использование цикла for с массивами                                                                                   233

Пример цикла, использующего возвращаемое значение функции                                       235

Анализ программы                                                                                                                       237

Использование функций с возвращаемыми значениями                                                   238

Ключевые понятия                                                                                                                              238

Резюме                                                                                                                                                    239

Вопросы для самоконтроля                                                                                                             240

Упражнения по программированию                                                                                              243

Глава 7. Управляющие операторы С: ветвление и переходы                                                247

Оператор if                                                                                                                                            248

Добавление к оператору if конструкции else                                                                              250

Еще один пример: знакомство с функциями getchar() и putchar()                                   251

Семейство функций для работы с символами с type.h                                                      254

Множественный выбор else if                                                                                                   255

Образование пар else и if                                                                                                            258

Другие вложенные операторы i f                                                                                             259

Давайте будем логичными                                                                                                               263

Альтернативное представление: заголовочный файл iso64 6.h                                     264

Приоритеты операций                                                                                                                265

Порядок вычисления выражений                                                                                             265


10 Содержание

Диапазон значений                                                                                                                      266

Программа подсче та слов                                                                                                               267

Условная операция ? :                                                                                                                        270

Вспомогательные средства для циклов: continue      и break                                                  272

Оператор continue                                                                                                                        272

Оператор break                                                                                                                             275

Выбор из множества вариантов: операторы switch и break                                                   277

Использование оператора switch                                                                                             278

Пение только первого символа строки                                                                                   280

Множество меток                                                                                                                         280

Операторы switch и if else                                                                                                           283

Оператор goto                                                                                                                                       283

Избегайте goto                                                                                                                               283

Ключевые понятия                                                                                                                              286



Резюме                                                                                                                                                    287

Вопросы для самоконтроля                                                                                                             288

Упражнения по программированию                                                                                              290

Глава 8. Символьный ввод-вывод и проверка достоверности ввода 293

Односимвольный ввод-вывод: getchar() и putchar                ()                                                   294

Буферы                                                                                                                                                   295

Завершение клавиатурного ввода                                                                                                  297

Файлы, потоки и ввод данных с клавиатуры                                                                       297

Конец файла                                                                                                                                  298

Перенаправление и файлы                                                                                                               301

Перенаправление в Unix, Linux и командной строке Windows                                       302

Создание дружественного пользовательского интерфейса                                                    306

Работа с буферизированным вводом                                                                                      306

Смешивание числового и символьного ввода                                                                      308

Проверка допустимости ввода                                                                                                        310

Анализ программы                                                                                                                       315

Поток ввода н числа                                                                                                                    315

Просмотр меню                                                                                                                                    316

Задачи                                                                                                                                             316

На пути к более гладкому выполнению                                                                                317

Смешивание символьного и числового ввода                                                                      319

Ключевые понятия                                                                                                                              321

Резюме                                                                                                                                                    322

Вопросы для самоконтроля                                                                                                             322

Упражнения по программированию                                                                                              323

Глава 9. Функции                                                                                                                                325

Обзор функций                                                                                                                                     326

Создание и использование простой функции                                                                      327

Анализ программы                                                                                                                       328

Аргументы функции                                                                                                                     330

Определение функции с аргументами: формальные параметры                                   331

Создание прототипа функции с аргументами                                                                     332

Вызов функции с аргументами: фактические аргументы                                                 333


Содержание 11

Представление в виде черного ящика                                                                                     334

Возврат значения из функции с помощью return                                                                 334

Типы функций                                                                                                                                337

Создание прототипов функций в ANSI С                                                                                      338

Суть проблемы                                                                                                                              338

Решение стандарта ANSI С                                                                                                       339

Отсутствие аргументов и неопределенные аргументы                                                     340

Преимущество прототипов                                                                                                        341

Рекурсия                                                                                                                                                 341

Рекурсия в действии                                                                                                                     342

Основы рекурсии                                                                                                                          343

Хвостовая рекурсия                                                                                                                     344

Рекурсия и изменение порядка на противоположный                                                       346

Преимущества и недостатки рекурсии                                                                                   348

Компиляция программ, состоящих из двух и более файлов исходного кода                    349

Unix                                                                                                                                                   349

Linux                                                                                                                                                 349

Компиляторы командной строки DOS                                                                                    350

Компиляторы интегрированных сред разработки в Windows и Apple                          350

Использование заголовочных файлов                                                                                    350

Выяснение адресов: операция &                                                                                                     353

Изменение переменных в вызывающей функции                                                                        355

Указатели: первое знакомство                                                                                                         357

Операция разыменования: *                                                                                                      357

Объявление указателей                                                                                                               358

Использование указателей для обмена данными между функциями                            359

Ключевые понятия                                                                                                                              363

Резюме                                                                                                                                                     363

Вопросы для самоконтроля                                                                                                              364

Упражнения по программированию                                                                                              365

Глава 10. Массивы и указатели                                                                                                      367

Массивы                                                                                                                                                 368

Инициализация                                                                                                                              368

Назначенные инициализаторы (С99)                                                                                     372

Присваивание значений элементам массива                                                                        373

Границы массива                                                                                                                          374

Указание размера массива                                                                                                        376

Многомерные массивы                                                                                                                      377

Инициализация двумерного массива                                                                                      379

Большее количество измерений                                                                                               380

Указатели и массивы                                                                                                                          381

Функции, массивы и указатели                                                                                                        384

Использование параметров типа указателей                                                                       386

Комментарии: указатели и массивы                                                                                       388

Операции с указателями                                                                                                                    389

Защита содержимого массива                                                                                                         393

Использование const с формальными параметрами                                                          394

Дополнительные сведения о ключевом слове const                                                           395


12       Содержание

Указатели и многомерные массивы                                                                                               397

Указатели на многомерные массивы                                                                                     400

Совмести мость указателей                                                                                                      401

Функции и многомерные массивы                                                                                           403

Массивы переменной длины                                                                                                            406

Составные литералы                                                                                                                          410

Ключевые понятия                                                                                                                              412

Резюме                                                                                                                                                     412

Вопросы для самоконтроля                                                                                                              414

Упражнения по программированию                                                                                              416

Глава 11. Символьные строки и строковые функции                                                                419

Введение в строки и строковый ввод-вывод                                                                                420

Определение строк в программе                                                                                              421

Указатели и строки                                                                                                                      429

Ввод строк                                                                                                                                             430

Создание пространства под строку                                                                                       430

Неудачливая функция gets()                                                                                                      430

Альтернативы функции gets()                                                                                                   432

Функцияscanf()                                                                                                                              438

Вывод строк                                                                                                                                          440

Функция puts()                                                                                                                               440

Функция fputs()                                                                                                                             441

Функция printf()                                                                                                                             442

Возможность самостоятельного создания функций                                                                  442

Строковые функции                                                                                                                            445

Функция strlen()                                                                                                                             445

Функция strcat()                                                                                                                             446

Функция strncat()                                                                                                                          447

Функция strcmp()                                                                                                                          449

Функции strcpy() и strncpy()                                                                                                       454

Функция sprintf()                                                                                                                           459

Другие строковые функции                                                                                                       460

Пример обработки строк: сортировка строк                                                                               462

Сортировка указателей вместо строк                                                                                    464

Алгоритм сортировки выбором                                                                                               465

Символьные функции ctype.h и строки                                                                                          465

Аргументы командной строки                                                                                                         467

Аргументы командной строки в интегрированных средах                                             469

Аргументы командной строки в Macintosh                                                                          469

Преобразования строк в числа                                                                                                        470

Ключевые понятия                                                                                                                              473

Резюме                                                                                                                                                     473

Вопросы для самоконтроля                                                                                                              474

Упражнения по программированию                                                                                              477

Глава 12. Классы хранения, связывание и управление памятью                                          479

Классы хранения                                                                                                                                 480

Область видимости                                                                                                                      481


Содержание     13

Связывание                                                                                                                                     483

Продолжительность хранения                                                                                                  484

Автоматические переменные                                                                                                    486

Регистровые переменные                                                                                                            490

Статические переменные с областью видимости в пределах блока                             491

Статические переменные с внешним связыванием                                                             492

Статические переменные с внутренним связыванием                                                       496

Множество файлов                                                                                                                       497

Спецификаторы классов хранения                                                                                                 498

Классы хранения и функции                                                                                                            501

Выбор класса хранения                                                                                                              501

Функция генерации случайных чисел и статическая переменная                                        502

Игра в кости                                                                                                                                           505

Выделенная память: malloc()  и free()                                                                                            509

Важность функции free()                                                                                                             513

Функция calloc()                                                                                                                             514

Динамическое распределение памяти и массивы переменной длины                          514

Классы хранения и динамическое распределение памяти                                              515

Квалификаторы типов ANSI С                                                                                                         517

Квалификатор типа const                                                                                                           517

Квалификатор типа volatile                                                                                                       519

Квалификатор типа restrict                                                                                                         520

Квалификатор типа Atomic (C11)                                                                                            521

Новые места для старых ключевых слов                                                                              522

Ключевые понятия                                                                                                                              523

Резюме                                                                                                                                                     523

Вопросы для самоконтроля                                                                                                              525

Упражнения по программированию                                                                                              526

Глава 13. Файловый ввод-вывод                                                                                                     531

Взаимодействие с файлами                                                                                                              532

Понятие файла                                                                                                                               532

Текстовый режим и двоичный режим                                                                                     532

Уровни ввода-вывода                                                                                                                  534

Стандартные файлы                                                                                                                    534

Стандартный ввод-вывод                                                                                                                  535

Проверка наличия аргумента командной строки                                                               536

Функция fopen()                                                                                                                             537

Функции getc() nputc()                                                                                                                  538

Конец файла                                                                                                                                   538

Функция fclose()                                                                                                                             540

Указатели на стандартные файлы                                                                                          540

Бесхитростная программа уплотнения файла                                                                     540

Файловый ввод-вывод: fprintf(), fscanf() , fgets ( ) и fputs()                                                      542

Функцииfprintf() иfscanf()                                                                                                           542

Функции fgets() и fputs()                                                                                                              544

Произвольный доступ: f seek() Hftellt)                                                                                           544

Работа функций fseek() nftell()                                                                                                  545

Сравнение двоичного и текстового режимов                                                                       547


14 Содержание

Переносимость                                                                                                                              547

Функции fgetpos() иfsetpos()                                                                                                       548

“За кулисами” стандартного ввода-вывода                                                                                548

Другие стандартные функции ввода-вывода                                                                              549

Функция int ungetc ( int с, FILE * fр)                                                                                        549

Функция int fflush()                                                                                                                       550

Функция int setvbuf()                                                                                                                    550

Двоичный ввод-вывод: fread() и fwrite ( )                                                                               551

Функцияsize_t fwrite()                                                                                                                   552

Функция size_t fread()                                                                                                                  553

Функции int feoff FILE * f p) Hint ferror(FILE * f p)                                                            553

Пример использования fread() и fwrite()                                                                                 553

Произвольный доступ с двоичным вводом-выводом                                                         556

Ключевые понятия                                                                                                                              558

Резюме                                                                                                                                                     558

Вопросы для самоконтроля                                                                                                              559

Упражнения по программированию                                                                                              561

Глава 14. Структуры и другие формы данных                                                                           565

Учебная задача: создание каталога книг                                                                                     566

Объявление структуры                                                                                                                       567

Определение переменной типа структуры                                                                                   568

Инициализация структуры                                                                                                        570

Доступ к членам структуры                                                                                                      570

Инициализаторы для структур                                                                                                 571

Массивы структур                                                                                                                               571

Объявление массива структур                                                                                                  574

Идентификация членов в массиве структур                                                                          574

Анализ программы                                                                                                                       575

Вложенные структуры                                                                                                                       576

Указатели на структуры                                                                                                                    577

Объявление и инициализация указателя на структуру                                                     579

Доступ к членам но указателю                                                                                                 579

Сообщение функциям о структурах                                                                                               580

Передача членов структуры                                                                                                     580

Использование адреса структуры                                                                                           581

Передача структуры в качестве аргумента                                                                          582

Дополнительные возможности структур                                                                               583

Символьные массивы или указатели на char в структурах                                             587

Структура, указатели и malloc()                                                                                              588

Составные литералы и структуры (С99)                                                                               591

Члены с типами гибких массивов (С99)                                                                                 592

Анонимные структуры (C11)                                                                                                     594

Функции, использующие массив структур                                                                            595

Сохранение содержимого структур в файле                                                                               596

Пример сохранения структуры                                                                                                597

Анализ программы                                                                                                                       600

Структуры: что дальше?                                                                                                                   601

Объединения: краткое знакомство                                                                                                 602


Содержание     15

Использование объединений                                                                                                    603

Анонимные объединения (C11)                                                                                                604

Перечислимые типы                                                                                                                           605

Константы enum                                                                                                                           606

Стандартные значения                                                                                                               606

Присвоенные значения                                                                                                               606

Использование enum                                                                                                                   606

Совместно используемые пространства имен                                                                     608

Средство typedef: краткое знакомство                                                                                         609

Причудливые объявления                                                                                                                 611

Функции и указатели                                                                                                                          612

Ключевые понятия                                                                                                                              619

Резюме                                                                                                                                                    620

Вопросы для самоконтроля                                                                                                             620

Упражнения по программированию                                                                                              623

Глава 15. Манипулирование битами                                                                                            627

Двоичные числа, биты и байты                                                                                                       628

Двоичные целые числа                                                                                                               629

Целые числа со знаком                                                                                                               629

Двоичные числа с плавающей запятой                                                                                 630

Другие основания систем счисления                                                                                             631

Восьмеричная система счисления                                                                                           631

Шестнадцатеричная система счисления                                                                               631

Побитовые операции                                                                                                                          632

Побитовые логические операции                                                                                            633

Случай применения: маски                                                                                                       634

Случай применения: включение (установка) битов                                                           635

Случай применения: выключение (очистка) битов                                                            636

Случай применения: переключение битов                                                                           636

Случай применения: проверка значения бита                                                                     637

Побитовые операции сдвига                                                                                                     637

Пример программы                                                                                                                      639

Еще один пример                                                                                                                          640

Битовые поля                                                                                                                                        642

Пример с битовыми полями                                                                                                       644

Битовые поля и побитовые операции                                                                                     647

Средства выравнивания (C11)                                                                                                         653

Ключевые понятия                                                                                                                              655

Резюме                                                                                                                                                    655

Вопросы для самоконтроля                                                                                                             656

Упражнения по программированию                                                                                              658

Глава 16. Препроцессор и библиотека С                                                                                     661

Первые шаги в трансляции программы                                                                                        662

Символические константы: #define                                                                                               663

Лексемы                                                                                                                                           666

Переопределение констант                                                                                                       667

Использование аргументов в директиве #define                                                                        667


16 Содержание

Создание строк из аргументов макроса: операция #                                                         670

Средство слияния препроцессора: операция # #                                                                671

Макросы с переменным числом аргументов: ... и_____ VA_ARGS_                            672

Выбор между макросом и функцией                                                                                              673

Включение файлов: директива #include                                                                                        674

Пример заголовочного файла                                                                                                   675

Случаи применения заголовочных файлов                                                                          677

Другие директивы                                                                                                                                678

Директива # unde f                                                                                                                       678

Определение с точки зрения препроцессора                                                                        678

Условная компиляция                                                                                                                  679

Предопределенные макросы                                                                                                     684

Директивы #line и #error                                                                                                              685

Директива #pragma                                                                                                                      685

Обобщенный выбор (C11)                                                                                                          686

Встраиваемые функции (С99)                                                                                                          688

Функции Noreturn (С11)                                                                                                                     690

Библиотека С                                                                                                                                        690

Получение доступа к библиотеке С                                                                                        691

Использование описаний библиотеки                                                                                    692

Библиотека математических функций                                                                                          693

Немного тригонометрии                                                                                                             694

Варианты типов                                                                                                                            695

Библиотека tgmath.h (С99)                                                                                                        697

Библиотека утилит общего назначения                                                                                        698

Функции exit() и atexit()                                                                                                               698

Функция qsort ()                                                                                                                             700

Библиотека утверждений                                                                                                                  704

Использование assert()                                                                                                                 704

_Static_assert(Cl1)                                                                                                                         706

Функции memcpy() Hmemmove() избиблиотеки string.h                                                          707

Переменное число аргументов: файл stdarg.h                                                                             709

Ключевые понятия                                                                                                                              711

Резюме                                                                                                                                                     711

Вопросы для самоконтроля                                                                                                              712

Упражнения по программированию                                                                                              713

Глава 17. Расширенное представление данных                                                                         717

Исследование представления данных                                                                                           719

От массива к связному списку                                                                                                          721

Использование связного списка                                                                                               725

Дополнительные соображения                                                                                                 728

Абстрактные типы данных                                                                                                               729

Получение абстракции                                                                                                               730

Построение интерфейса                                                                                                              731

Использование интерфейса                                                                                                       735

Реализация интерфейса                                                                                                              737

Создание очереди с помощью ADT                                                                                                744

Определение абстрактного типа данных для представления очереди                        744


Содержание     17

Определение интерфейса                                                                                                           744

Реализация представления данных интерфейса                                                                 745

Тестирование очереди                                                                                                                753

Моделирование реальной очереди                                                                                                755

Сравнение связного списка и массива                                                                                          761

Двоичные деревья поиска                                                                                                                 764

Создание абстрактного типа данных для двоичного дерева                                           765

Интерфейс двоичного дерева поиска                                                                                     766

Реализация двоичного дерева                                                                                                  768

Тестирование пакета для древовидного представления                                                  782

Соображения по поводу дерева                                                                                               786

Другие направления                                                                                                                           787

Ключевые понятия                                                                                                                              788

Резюме                                                                                                                                                    788

Вопросы для самоконтроля                                                                                                             788

Упражнения по программированию                                                                                              789

Приложение А. Ответы на вопросы для самоконтроля                                                            791

Ответы на вопросы для самоконтроля      из главы 1                                                                792

Ответы на вопросы для самоконтроля      из главы 2                                                                792

Ответы на вопросы для самоконтроля      из главы 3                                                                794

Ответы на вопросы для самоконтроля      из главы 4                                                                796

Ответы на вопросы для самоконтроля из главы      5                                                                798

Ответы на вопросы для самоконтроля из главы 6                                                                     801

Ответы на вопросы для самоконтроля из главы      7                                                                804

Ответы на вопросы для самоконтроля из главы      8                                                                807

Ответы на вопросы для самоконтроля из главы 9                                                                     808

Ответы на вопросы для самоконтроля      из главы 10                                                              810

Ответы на вопросы для самоконтроля      из главы 11                                                              812

Ответы на вопросы для самоконтроля из главы     12                                                               816

Ответы на вопросы для самоконтроля из главы     13                                                               817

Ответы на вопросы для самоконтроля из главы     14                                                               820

Ответы на вопросы для самоконтроля из главы     15                                                               823

Ответы на вопросы для самоконтроля из главы     16                                                               824

Ответы на вопросы для самоконтроля из главы     17                                                               826

Приложение Б. Справочные материалы                                                                                      829

Раздел I. Дополнительные источники информации                                                                  830

Онлайновые ресурсы                                                                                                                  830

Книги по языку С                                                                                                                          831

Книги по программированию                                                                                                   831

Справочные руководства                                                                                                          832

Книги по C++                                                                                                                                 832

Раздел II. Операции в языке С                                                                                                         832

Арифметические операции                                                                                                        833

Операции отношений                                                                                                                  834

Операции присваивания                                                                                                             834

Логические операции                                                                                                                  835

Условная операция                                                                                                                      835


18 Содержание

Операции, связанные с указателями                                                                                       836

Операции со знаком                                                                                                                     836

Операции структур и объединений                                                                                         836

Побитовые операции                                                                                                                   837

Прочие операции                                                                                                                          838

Раздел III. Базовые типы и классы хранения                                                                              838

Сводка: базовые типы данных                                                                                                 838

Сводка: объявление простой переменной                                                                             840

Сводка: квалификаторы                                                                                                             842

Раздел IV. Выражения, операторы и поток управления программы                                    843

Сводка: выражения и операторы                                                                                             843

Сводка: оператор while                                                                                                               844

Сводка: оператор for                                                                                                                   844

Сводка: оператор do while                                                                                                         845

Сводка: использование операторов        i f для реализации выбора                               845

Сводка: множественный выбор с помощью switch                                                             846

Сводка: переходы в программе                                                                                                847

Раздел V. Стандартная библиотека ANSI С с дополнениями С99 и C11                            848

Диагностика: assert.h                                                                                                                   848

Комплексные числа: complex.h (С99)                                                                                     849

Обработка символов: сtype.h                                                                                                    851

Сообщение об ошибках: errno.h                                                                                               851

Среда плавающей запятой: fenv.h (С99)                                                                               852

Характеристики среды плавающей запятой: float.h                                                          854

Преобразование формата целочисленных типов: inttypes.h (С99)                                856

Альтернативное написание: iso646.h                                                                                     857

Локализация: locale.h                                                                                                                  857

Математическая библиотека: math.h                                                                                     860

Нелокальные переходы: sеtjmp.h                                                                                            864

Обработка сигналов: signal.h                                                                                                    865

Выравнивание: stdlign.h (C11)                                                                                                   866

Переменное количество аргументов: stdarg.h                                                                      866

Поддержка атомарности: stdatomic.h (C11)                                                                         867

Поддержка булевских значений: stdbool.h (C99)                                                                867

Общие определения: stddef.h                                                                                                    868

Целочисленные типы: stdint.h                                                                                                   868

Стандартная библиотека ввода-вывода: stdio.h                                                                 871

Общие утилиты: stdlib.h                                                                                                              874

_Noreturn:stdnoreturn.h                                                                                                                879

Обработка строк: string.h                                                                                                            879

Математические функции для обобщенных типов: tgmath.h (С99)                               882

Потоки: threads.h (C11)                                                                                                               883

Дата и время: time.h                                                                                                                     883

Утилиты Unicode: uchar.h (C11)                                                                                               887

Утилиты для работы с многобайтными и широкими символами: wchar.h (С99) 887 Утилиты классификации и отображения широких символов: wctype.h (С99) 893 Раздел VI. Расширенные целочисленные типы              895

Типы с точной шириной                                                                                                             895

Тины с минимальной шириной                                                                                                 896


Содержание 19

Самые быстрые типы с минимальной шириной                                                                  896

Типы максимальной ширины                                                                                                    897

Целые, которые могут хранить указатели                                                                            897

Расширенные целочисленные константы                                                                             898

Раздел VII. Расширенная поддержка символов                                                                          898

Триграфы                                                                                                                                        898

Диграфы                                                                                                                                          899

Альтернативное написание: i sо 6 4 6.h                                                                                 899

Многобайтные символы                                                                                                             899

Универсальные имена символов (UCN)                                                                                 900

Широкие символы                                                                                                                        901

Широкие и многобайтные символы                                                                                        903

Раздел VIII. Расширенные вычислительные средства С99/С11                                            903

Стандарт плавающей запятой IEC                                                                                         903

Заголовочный файл f env.h                                                                                                        907

ПрагмаSTDC FP_CONTRACT                                                                                                   908

Дополнения библиотеки math.h                                                                                               908

Поддержка комплексных чисел                                                                                                909

Раздел IX. Отличия между С и C++                                                                                                911

Прототипы функций                                                                                                                     911

Константы char                                                                                                                             912

Модификатор const                                                                                                                      913

Структуры и объединения                                                                                                         914

Перечисления                                                                                                                                 914

Указатель Havoid                                                                                                                         915

Булевские типы                                                                                                                             915

Альтернативное написание                                                                                                       915

Поддержка широких символов                                                                                                 915

Комплексные типы                                                                                                                       915

Встраиваемые функции                                                                                                              916

Средства С99/С11, которых нетвС++11                                                                                916

Приложение В. Набор символов ASCII                                                                                        917

Предметный указатель                                                                                                                       922



Памяти моего отца, Уильяма Прата.


Об авторе

Стивен Прата, в настоящее время отошедший от дел, преподавал астрономию, физику и программирование в Колледже Марин в Кентфилде, штат Калифорния. Он получил диплом бакалавра в Калифорнийском технологическом институте и степень доктора философии в Калифорнийском университете в Беркли. Его увлечение компьютерами началось с компьютерного моделирования звездных скоплений. Стивен является автором и соавтором более десятка книг, включая C++ Primer Plus (Язык программирования C++. Лекции и упражнения, 6-е изд, ИД "Вильяме", 2012 г.) и Unix Primer Plus.

Благодарности

Я хотел бы поблагодарить Марка Табера за продолжение этого проекта и доведение его до конца. Также я благодарен Денни Калев за техническую помощь и за предложенный им термин “область действия программы”.

От издательства

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

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

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

Наши координаты:

E-mail:            [email protected]

http://www.williamspublishing.com

Информация для писем из:

России:           127055, i: Москва, ул Лесная, д. 43, стр. 1

Украины:       03150, Киев, а/я 152



Язык программирования C. Лекции и упражнения. 6-е издание


Бейоннский мост, соединяющий Бейонн, штат Нью- Джерси, со Статен-Айлендом, Нью-Йорк, был самым длинным в мире стальным арочным мостом, когда его открыли в 1931 году, и удерживал эту позицию на протяжении 45 лет. В наши дни многие по-прежнему считают его значительным эстетическим и техническим достижением.

С пролетом в 511 метров грациозная арка моста вздымается на высоту 69 метров над проливом Килл-Ван-Кул и позволяет беспрепятственно проходить судам по бухте Ньюарк, главном судоходном канале к островным портам Ньюарка и Элизабета, штат Нью-Джерси.

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

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

В 1931 году Американский институт стальных конструкций присудил этому мосту приз как самому красивому стальному арочному мосту, а в “Нью-Йорк тайме”, запоздало отдавая дань, отметили, что Бейоннскому мосту присуща симметрия и плавность деталей, которые производят глубокое впечатление и надолго западают в память.

В 2013 году портовые власти Нью-Йорка и Нью-Джерси запустили проект реконструкции моста на сумму 1,3 миллиарда долларов для увеличения высоты пролета в пределах существующей структуры арки, чтобы крупные контейнеровозы могли проходить иод мостом транзитом в порты Ньюарка и Элизабета.


Предисловие

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

С развитием языка от раннего неформального стандарта K&R, через стандарт 1990 года ISO/ANSI и стандарт 1999 года ISO/ANSI, до появления в 2011 году стандарта ISO/IEC, обретала зрелость и данная книга, добравшись до своего шестого издания. Как и во всех предшествующих изданиях, моей целью было создание поучительного, ясного и полезного введения в язык С.

Подход и цели

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

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

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

•   Рисунки и иллюстрации проясняют концепции, которые трудно описать только одними словами.

•   Главные средства языка С подытожены во врезках, на которые легко ссылаться и пересматривать.

•   Вопросы для самоконтроля и упражнения по программированию в конце каждой главы позволяют проверять и закреплять понимание языка С.

Чтобы извлечь максимальную пользу, при изучении материала данной книги вы должны играть как можно более активную роль. Не ограничивайтесь одним лишь чтением примеров —вводите и опробуйте их в своей системе. Язык С является в высокой степени переносимым, но вы можете обнаружить отличия между тем, каким образом программа ведет себя в вашей системе и как она работает у нас. Смело экспериментируйте, изменяя разные части программы, чтобы посмотреть, к чему это приведет. Модифицируйте программу, чтобы она делала что-то другое. Ищите возможность разработки альтернативного подхода. Игнорируйте появляющиеся временами предупреждения и исследуйте, что произойдет, если вы будете поступать некорректно. Попытайтесь ответить на вопросы и выполнить упражнения. Чем больше вы сделаете самостоятельно, тем большему вы научитесь и больше запомните.

Я надеюсь, что вы найдете это новое издание книги интересным и эффективным введением в язык программирования С.




Язык программирования C. Лекции и упражнения. 6-е издание


 ЭТОЙ ГЛАВЕ...

• Возможности и история создания языка С

• Действия, которые нужно выполнить для

написания программ

• Немного о компиляторах и компоновщиках

• Стандарты языка С


Глава 1

Язык программирования C. Лекции и упражнения. 6-е издание
Добро пожаловать в мир С —мощного языка программирования для профессиона- лов, который в равной степени популярен как в среде любителей, так и в среде программистов, пишущих программы для коммерческого применения. Данная глава подготовит вас к изучению и использованию этого мощного и широко распространенного языка и ознакомит с различными операционными средами, в которых, скорее всего, вам придется совершенствовать свои знания языка С.

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

Появление языка С

Деннис Ритчи из компании Bell Labs создал язык С в 1972 году, когда они вместе с Кеном Томпсоном работали над созданием операционной системы Unix. Однако сам язык С зародился в голове Ритчи не просто так. Его предшественником был язык В, созданный Томпсоном на основе..., впрочем, это уже другая история. Наиболее важным является тот факт, что С задумывался как инструментальное средство для про- граммистов-практиков, следовательно, его главной целью в этом случае было создание полезного языка программирования.

Большинство языков программирования создавались с целью быть полезными, но довольно часто перед ними ставились другие вопросы. Например, изначально язык Pascal предназначался для облегчения изучения принципов программирования. С другой стороны, язык BASIC создавался как язык программирования, приближенный к естественному английскому языку, чтобы облегчить задачу изучения языков программирования для студентов, не знакомых с компьютерами. Это были важные цели, но они не всегда соответствовали прагматичном' подходу к решению повседневных задач. Тем не менее, разработка С как языка, предназначенного для программистов, сделала его одним из наиболее востребованных в настоящее время.

Причины популярности языка С

В течение последних четырех десятилетий С стал одним из основных и наиболее широко распространенных языков программирования. Его популярность росла потому, что люди предпринимали попытки работать с ним и убеждались в его достоинствах. За последнее десятилетия или два многие программисты перешли на такие языки, как C++, Objective С и Java, но язык С вес еще остается важным и сам по себе, и как путь перехода на указанные языки. По мере изучения С вы убедитесь, что он обладает многими достоинствами (рис 1.1). Некоторые из них мы отметим сейчас.

Конструктивные особенности

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


Язык программирования C. Лекции и упражнения. 6-е издание
Язык программирования C. Лекции и упражнения. 6-е издание


Предварительные сведения


Эффективность

С является эффективным языком программирования. Его конструкция продуктивно использует возможности компьютеров, на которых он установлен. Программы на С отличаются компактностью и быстротой исполнения. По сути дела С обладает некоторыми средствами точного управления, обычно характерными разве что для языка ассемблера. (Язык ассемблера -- это мнемоническое представление множества инструкций, используемых конкретным центральным процессором; различные семейства центральных процессоров имеют разные языки ассемблера.) При желании программы можно настроить на максимальную скорость выполнения или на более эффективное использование памяти.

Переносимость

С является переносимым языком, и это означает, что программу, написанную на С для одной системы, можно выполнять на другой системе всего лишь с небольшими изменениями, а иногда удается обойтись вообще без модификаций. В тех случаях, когда изменения неизбежны, они ограничиваются простым редактированием нескольких записей в заголовочном файле, сопровождающем главную программу Многие языки декларируются как переносимые, однако тем, кто преобразовывал программу на языке BASIC, предназначенном для ПК компании IBM в программу на языке BASIC для компьютера Apple (они были близкими родственниками), либо предпринимал попытки выполнить в среде Unix программу на языке FORTRAN, которая предназначена для


28 Глава 1 мэйнфрейма IBM, хорошо известно, что такой перенос — в лучшем случае весьма трудоемкая операция. Язык С является лидером в смысле переносимости. Компиляторы языка С (программы, преобразующие код на С в инструкции, которые компьютер использует для внутренних целей) доступны для многих компьютерных архитектур, от 8-разрядных микропроцессоров до суперкомпьютеров Cray. Однако следует отметить, что фрагменты программы, написанной специально для доступа к конкретным аппаратным устройствам, таким как монитор или специальные функции операционных систем, подобных Windows 8 или OS X, обычно не принадлежат к числу переносимых.

Поскольку язык С тесно связан с Unix, операционные системы семейства Unix поставляются с компилятором С в виде части соответствующего пакета. Установка операционной системы Linux также обычно включает компилятор языка С. Доступно несколько компиляторов языка С, предназначенных для персональных компьютеров, в том числе для работающих под управлением различных версий ОС Windows и Macintosh. Таким образом, используете вы домашний компьютер, профессиональную рабочую станцию или мэйнфрейм, у вас высокие шансы получить компилятор языка С для вашей конкретной системы.

МОЩЬ и гибкость

Язык С является мощным и гибким (это два наиболее предпочитаемых определения в литературе компьютерной тематики). Например, большая часть кода мощной и гибкой операционной системы Unix была написана на С. На языке С были реализованы многие компиляторы и интерпретаторы для других языков, таких как FORTRAN, Perl, Python, Pascal, LISP, Logo и BASIC. В результате, когда вы используете FORTRAN на машине Unix, в конечном итоге именно программа, написанная на С, выполняет работу по созданию окончательной исполняемой программы. Программы на С применялись для решения физических и инженерных задач и даже для анимации специальных эффектов для множества фильмов.

Ориентация на программистов

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

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

Недостатки

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


Предварительные сведения 29

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

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

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

Происхождение языка С

В начале восьмидесятых годов прошлого столетия С уже был доминирующим языком программирования в среде миникомпьютеров, функционировавших под управлением операционных систем Unix. С тех пор он распространился на персональные компьютеры (микрокомпьютеры) и мэйнфреймы (большие вычислительные машины). Взгляните на рис. 1.2. Многие компании по разработке и поставке программного обеспечения предпочитают использовать именно язык С при создании программ для текстовых процессоров, крупномасштабных электронных таблиц, компиляторов и других программных продуктов. Эти компании убедились в том, что с помощью С можно создавать компактные и эффективные программы. А еще важнее то, что эти в программы легко вносить изменения и легко адаптировать к новым моделям компьютеров.

Язык программирования C. Лекции и упражнения. 6-е издание

Pic. 1.2. Где используется язык С



30 Глава 1

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

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

Несмотря на популярность более новых языков вроде C++ и Java, язык С сохраняет лидирующее положение по способности решать задачи из области разработки программного обеспечения, обычно входя в десятку наиболее востребованных языков программирования. В частности, С неизменно используется для программирования встроенных систем. Иначе говоря, он все чаще применяется для программирования обычных микропроцессоров, встроенных в автомобили, камеры, DVD-проигрыватели и другие современные бытовые устройства. Наряду с этим С посягает на долговременное господство языка FORTRAN в области научного программирования. И, наконец, как язык, создававшийся для разработки операционных систем, он играет ключевую роль в построении операционной системы Linux. Таким образом, и во второй декаде двадцать первого века С продолжает удерживать за собой сильные позиции. Короче говоря, С является одним из наиболее важных языков программирования и надолго останется таковым. Если вы хотите заниматься разработкой программ, то на вопрос, можете ли вы работать на языке С, вы непременно должны ответить утвердительно.

Особенности функционирования компьютеров

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

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

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


Предварительные сведения 31

рабочую область, состоящую из нескольких регистров, каждый из которых может запоминать какое-то число. Один регистр содержит адрес памяти следующей команды, и ЦП использует эту информацию для извлечения следующей команды. После извлечения следующей команды ЦП запоминает ее в другом регистре и обновляет первый регистр адресом очередной команды. Центральный процессор выполняет ограниченный набор команд (получивший название набора инструкции). Наряду с этим, его команды достаточно специфичны; многие из них требуют от ЦП перемещения числа из одного места в другое — например, из ячейки памяти в регистр.

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

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

1.  Скопировать число из ячейки памяти 2000 в регистр 1.

2.   Скопировать число из ячейки памяти 2004 в регистр 2.

3.  Сложить содержимое регистра 2 с содержимым регистра 1 и оставить результат сложения в регистре 1.

4.   Скопировать содержимое регистра 1 в ячейку памяти 2008.

И каждую из этих инструкций придется представить в числовом коде!

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

Языки программирования высокого уровня и компиляторы

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

total = mine + yours;


32 Глава 1

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

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

Подход с использованием компилятора дает еще одно преимущество. В общем случае каждый компьютер обладает собственным уникальным машинным языком. Поэтому программа, написанная на машинном языке, например, для ЦП Intel Core i7, совершенно бессмысленна для процессора с ARM-архитектурой Cortex-A57. В то же время компилятор можно приспособить для конкретного машинного языка. Следовательно, располагая нужным компилятором или набором компиляторов, можно преобразовывать одну и ту же программу на языке высокого уровня в разнообразные программы на разных машинных языках. Вы решаете задачу программирования только один раз, после чего предоставляете компиляторам возможность транслировать ее решение на множество различных машинных языков.

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

Этапы компьютерной эры

В 1964 году корпорация Control Data Corporation объявила о создании компьютера CDC 6600. Эта занимающая целую комнату машина считается первым суперкомпьютером, и ее начальная стоимость составляла около 6 миллионов долларов США. Этот компьютер был основным вычислительным инструментом при исследованиях в ядерной физике высоких энергий. Современный смартфон превосходит его в несколько сотен раз по вычислительной мощности и объему памяти. Вдобавок он может воспроизводить видео и музыку. Причем это всего лишь телефон.

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

Стандарты языка С

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

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


Предварительные сведения 33

и Денниса Ритчи Язык программирования С (в настоящее время доступно второе издание этой книги, выпущенное издательским домом “Вильямс”); этот стандарт получил обозначение К&Л Сили Classic С (классический С). Приложение Б настоящей книги можно рассматривать в качестве руководства по реализациям языка С. Например, создатели компиляторов утверждают, что предлагают полную реализацию K&R. Однако, хотя в упомянутом приложении дано определение языка С, в нем не описана стандартная библиотека С. Язык С зависит от своей библиотеки в большей степени, нежели другие языки, поэтому возникает необходимость также и в разработке стандарта для библиотеки. При отсутствии какого-либо официального стандарта библиотека, поставляемая вместе с реализацией С для Unix, стала стандартом де-факто.

Первый стандарт ANSI/ISO С

По мере того как язык С развивался и получал все более широкое применение в различных системах, сообщество пользователей С ощутило острую потребность во всеобъемлющем, современном и строгом стандарте. Чтобы удовлетворить эту потребность, институт ANSI (American National Standards Institute — Национальный институт стандартизации США) образовал в 1983 году специальный комитет (X3J11), целью которого была разработка нового стандарта, и он формально был принят в 1989 году. Этот стандарт (ANSI С) определяет как сам язык, так и стандартную библиотеку С. Организация ISO (International Organization for Standardization — Международная организация по стандартизации) приняла стандарт языка С (ISO С) в 1990 году. По существу ISO С и ANSI С являются одним и тем же стандартом. Окончательную версию стандарта ANSI/ISO часто называют С89 (именно в этом году институт ANSI утвердил данный стандарт) или С90 (т.к. в этом году данный стандарт был утвержден ISO). Поскольку версия ANSI появилась первой, часто используется термин ANS1 С.

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

•   Доверять программисту.

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

•   Не увеличивать язык и сохранять его простоту.

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

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

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

Стандарт С99

В 1994 году объединенный комитет ANSI/ISO, получивший название комитета С9Х, начал работу по пересмотру существующего стандарта, результатом которой стал стандарт С99. Комитет подтвердил базовые принципы стандарта С90, в том числе принцип малого размера и простоты языка С. Цель, озвученная комитетом, состояла в том, чтобы не добавлять в язык новые свойства за исключением тех, которые необходимы для достижения новых целей, поставленных перед языком. Одной из этих


34 Глава 1 целей была поддержка интернационализации, например, создание способов работы с наборами интернациональных символов. Второй целью была “кодификация существующих методов устранения очевидных дефектов”. Таким образом, при необходимости переноса С на 64-разрядные процессоры комитет положил в основу дополнений к стандарту опыт тех, кто решал эту задачу в реальных условиях. Третьей целью было повышение пригодности языка С для выполнения критических вычислений в рамках научных и технических проектов, что делало С более привлекательной альтернативой языку FORTRAN.

Три указанных выше момента — интернационализация, исправление дефектов и повышение вычислительной полезности — были основными причинами, которые обусловили внесение изменений. Остальные планы, предусматривавшие изменения, были более консервативными по своей природе, например, минимизация несоответствий стандарту С90 и языку C++ и сохранение концептуальной простоты языка. В формулировке документа, принятого комитетом, сказано: ”... комитет голосует за предоставление C++ возможности стать большим и амбициозным языком”.

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

Стандарт С11

Поддержка стандарта — процесс бесконечный, и в 2007 году комитет по стандартам приступил к созданию следующей версии стандарта, СIX, которая была выпущена как С11. Комитет выдвинут ряд новых руководящих принципов. Одним из них стало некоторое смягчение цели “доверия программисту” с учетом современной заботы о защищенности и безопасности программного кода. Комитет сделал также ряд важных наблюдений. Одно из них заключалось в том, что стандарт С99 был не настолько хорошо принят и поддержан поставщиками, как С90. В результате некоторые функциональные возможности С99 стали необязательными для С11. Одна из причин состояла в признании комитетом того, что от поставщиков, обслуживающих рынок малых компьютеров, не следует требовать поддержки функциональных возможностей, которые не используются в целевых средах. Другое наблюдение заключалось в том, что пересмотр стандарта был обусловлен не его нарушением, а потребностью следования в русле новых технологий. Один из примеров этого — добавление необязательной поддержки параллельного программирования в ответ на тенденцию применения нескольких процессоров в компьютерах. Мы кратко рассмотрим данный вопрос, но его глубокое исследование выходит за рамки данной книги.

На заметку!

В этой книге термины ANSI С, или в более интернациональном дуxe ANSI/ISO С либо просто ISO С, служат для указания функциональных возможностей, общих для С89/90 и последующих стандартов, а С99 и С11 — для указания новых функциональных возможностей. Иногда будут встречаться ссылки на стандарт С90 (например, при обсуждении первого добавления того или иного свойства в язык С).


Предварительные сведения 35

Использование языка С: семь этапов

Как уже говорилось, язык С является компилируемым языком. Если вы привыкли работать с компилируемым языком, например, с Pascal или FORTRAN, то вам известны основные действия, выполняемые для сборки программы, написанной на С. Тем не менее, если вы имели дело с интерпретируемым языком, например, BASIC, либо графическим интерфейсно-ориентированным языком, таким как Visual Basic, или если у вас вообще нет опыта программирования, тогда вы должны ознакомиться с особенностями компиляции. Мы вскоре рассмотрим этот процесс, и вы сами сможете убедиться, что он достаточно прост и практичен. Прежде всего, чтобы дать вам общее предоставление о программировании, разобьем процесс написания программы на языке С на семь этапов (рис. 1(3). Имейте в виду, что это идеализация. На практике, особенно в случае крупных проектов, вы должны перемещаться назад и вперед, используя то, чему вы научились на более позднем этапе, для уточнения результатов, которые были получены на более ранней стадии.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 1.3. Семь этапов программирования


Этап 1: определение целей программы

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

Этап 2: проектирование программы

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


36 Глава 1 быть пользовательский интерфейс? Как должна быть организована эта программа? Каковыми будут целевые пользователи? Сколько времени потребуется для завершения разработки программы?

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

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

Этап 3: написание кода

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

Листинг 1.1. Пример исходного кода на языке С

Язык программирования C. Лекции и упражнения. 6-е издание


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

Этап 4: компиляция

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

Вспомните, что компилятор представляет собой программу, задачей которой является преобразование исходного кода в исполняемый код. Исполняемый код -- это код на


Предварительные сведения 37

собственном языке компьютера, или на машинном языке. Такой язык состоит из подробных инструкций, представленных в числовом коде. Как уже упоминалось, разные компьютеры имеют разные машинные языки, а компилятор языка С транслирует код С в конкретный машинный язык. Компиляторы языка С вставляют в финальную программу также код из библиотек С; эти библиотеки содержат комплект стандартных подпрограмм, например, printf() и scanf(), которыми можно пользоваться. (Если говорить точнее, то библиотечные подпрограммы в программу включает инструмент, называемый компоновщиком или редактором связей, но в большинстве систем его запускает компилятор.) Конечным результатом является исполняемый файл, который понимает компьютер, и который можно запускать на выполнение.

Компилятор также проверяет, не содержит ли ошибки программа на языке С. При обнаружении ошибок компилятор сообщает о них и не создает исполняемый файл. Понимание “жалоб” компилятора — это еще один навык, которым вам придется овладеть.

Этап 5: запуск программы на выполнение

Как правило, исполняемый файл представляет собой программу, которую можно запускать на выполнение. Чтобы запустить программу во многих распространенных средах, включая режим командной строки Windows, режим терминала Unix и режим терминала Linux, достаточно ввести имя исполняемого файла. Другие среды, такие как система VMS на миникомпьютерах VAX, могут потребовать ввода команды запуска или применения какого-то другого механизма. Среды IDE (Integrated Development Environment — интегрированная среда разработки), подобные тем, что поставляются для Windows и Macintosh, позволяют редактировать и выполнять программы на С внутри среды, выбирая соответствующие пункты меню или нажимая специальные клавиши. Полученную программу можно также запустить непосредственно из операционной системы, выполнив одиночный или двойной щелчок на имени файла или на соответствующем значке.

Этап 6: тестирование и отладка программы

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

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

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


38 Глава 1

Этап 7: сопровождение и модификация программы

Когда вы создаете программу для себя или кого-то другого, то, возможно, планируете ее широкое применение. Если это так, могут появиться причины для внесения в нее изменений. Вполне вероятно, что обнаружится незначительный дефект, проявляющийся при вводе имени, которое начинается с букв “Zz", либо возникнет желание улучшить что-либо в программе. Вы можете добавить в нее новую функциональную возможность. Программу можно адаптировать для выполнения в различных компьютерных системах. Решение задач подобного рода существенно упрощается, если четко документировать программу и придерживаться проверенных на практике рекомендаций.

Комментирование

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

Многие из изучающих программирование пренебрегают этапами 1 и 2 (определение целей и проектирование программы) и переходят непосредственно к этапу 3 (написание кода). Первые написанные вами программы будут достаточно простыми, чтобы весь процесс разработки можно было “прокрутить” в голове. Если вы допустите ошибку, то найти ее будет довольно легко. По мере того как ваши программы становятся все крупнее и сложнее, представление программы в уме начинает подводить, а на выявление ошибок уходит все больше и больше времени. В конечном итоге те, кто пренебрегает стадиями планирования, обречены на бесполезную потерю времени, на путаницу и разочарование из-за громоздких, плохо функционирующих и трудных для понимания программ. Чем масштабнее и сложнее задача, тем более тщательного планирования она требует.

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

Механика программирования

Точные действия, которые нужно выполнить, чтобы получить программу, зависят от компьютерной среды. Поскольку С — переносимый язык, с ним можно работать в различных средах, включая операционные системы Unix, Linux, MS-DOS (да, некоторые все еще пользуются этой операционной системой), Windows и Macintosh. В этой книге не хватит места, чтобы рассмотреть все эти операционные среды, в частности потому, что отдельные программные продукты развиваются, умирают и заменяются другими.

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


Предварительные сведения 39

При написании программы на языке С код сохраняется в текстовом файле, который называется файлом исходного кода. Большинство систем С, в том числе упомянутые выше, требуют, чтобы имя файла заканчивалось на .с (например, wordcount.c или budget.с). Часть имени, находящаяся перед точкой, называется базовым именем, а часть, следующая за точкой — расширением. Таким образом, budget — это базовое имя, а с — расширение. Сочетание budget.с образует имя файла. Это имя должно также удовлетворять требованиям конкретной операционной системы компьютера. Например, MS-DOS представляет собой операционную систему для персональных компьютеров производства IBM и совместимых с ними. Она требует, чтобы базовое имя содержало не более восьми символов, и в силу этого обстоятельства указанное выше имя файла wordcount.с не является допустимым именем файла в DOS. Некоторые системы Unix ограничивают совокупную длину имени файла 14 символами, включая расширение; другие системы Unix допускают длинные имена вплоть до 255 символов. Операционные системы Linux, Windows и Macintosh также разрешают использование длинных имен.

Итак, для определенности, рассмотрим файл с именем concrete.с, который содержит исходный код на С, представленный в листинге 1.2.

Листинг 1.2. Программа concrete.с

Язык программирования C. Лекции и упражнения. 6-е издание


Пока не беспокойтесь о деталях содержимого файла исходного кода, приведенного в листинге 1.2; мы вернемся к ним в главе 2.

Файлы объектного кода, исполняемые файлы и библиотеки

Базовая стратегия программирования на С предусматривает применение программ, которые преобразуют исходный код в исполняемый файл, содержащий готовый к выполнению код на машинном языке. Реализация программы на С обычно осуществляется в два этапа: компиляция и компоновка. Компилятор преобразует исходный код в промежуточный код, а компоновщик объединяет этот код с другим кодом, создавая исполняемый файл. В С используется такой двухэтапный подход для поддержки модульной организации программ. Индивидуальные модули можно компилировать но отдельности, а затем позже с помощью компоновщика объединять скомпилированные модули. Таким образом, если потребуется изменить какой-то один модуль, не нужно будет повторно компилировать остальные модули. Кроме того, компоновщик связывает программу с заранее скомпилированным библиотечным кодом.

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


40 Глава 1

Первый элемент, которого не хватает в файле объектного кода — это код запуска, представляющий собой код, который действует в качестве интерфейса между программой и операционной системой. Например, программу можно запускать на одинаковых персональных компьютерах, один из которых функционирует под управлением Microsoft Windows, а другой — под управлением Linux. В обоих случаях оборудование одно и то же, поэтому применяется один и тот же объектный код, в то же время для Windows и для Linux нужен разный код запуска, поскольку эти системы обрабатывают программы по-разному.

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

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

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 1.4. Компилятор и компоновщик


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

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


Предварительные сведения 41

Теперь рассмотрим несколько конкретных систем.

Операционная система Unix

Поскольку язык С появился и обрел популярность в системах Unix, мы начнем именно с этой операционной системы. (Обратите внимание: под “Unix” подразумеваются и такие системы, как FreeBSD, которая была создана на основе Unix, но не могла использовать это название по правовым причинам.)

Редактирование в системе Unix

Язык С в системе Unix не имеет собственного редактора. В этом случае применяется один из редакторов Unix общего назначения, например, einacs, jove, vi или текстовый редактор системы X Window System.

Вы отвечаете за выполнение двух процедур: корректный ввод кода программы с клавиатуры и выбор имени для файла, в котором будет храниться введенный код. Как обсуждалось ранее, это имя должно заканчиваться на .С. Обратите внимание, что система Unix различает прописные и строчные буквы. Поэтому budget, с, BUDGET.с и Budget.с — три разных допустимых имени исходных файлов, в то же время BUDGET. С таковым не является, т.к. в расширении .С используется прописная, а не строчная буква.

С помощью редактора vi мы подготовили приведенную ниже программу и сохранили ее в файле inform.с.

Приведенный текст представляет собой исходный код, a inform.с — исходный файл. Здесь важно отметить, что создание исходного файла — это начало процесса, но не его конец.

Компиляция в системе Unix

Наша программа, хотя и совершенна во всех других отношениях, она все же непонятна компьютеру. Компьютер не понимает таких выражений, как #include и printf. (На этой стадии, возможно, вы тоже не особо понимаете, однако у вас есть надежда вскоре узнать, что это такое, тогда как у компьютера нет никаких шансов.) Как отмечалось выше, мы нуждаемся в помощи компилятора при трансляции написанного кода (исходного кода) в код компьютера (машинный код). Результатом этих усилий будет исполняемый файл, который содержит весь машинный код, который необходим компьютеру для выполнения работы.

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

Чтобы скомпилировать программу inform, с, введите следующую команду:

сс inform.с


42 Глава 1

Спустя момент приглашение командной строки Unix отобразится снова, уведомляя о том, что дело сделано. Вы можете получить предупреждения или сообщения об ошибках, если программа написана неправильно, однако предположим, что все прошло удачно. (Если компилятор жалуется, что не понимает слова void, это означает, что данная система еще не имеет компилятора ANSI С. Более подробно о стандартах речь пойдет немного позже. Пока что просто удалите слово void из текста примера.) Если воспользоваться командой Is для вывода списка файлов, обнаружится новый файл с именем a.out (рис. 1.5). Это исполняемый файл, содержащий транслированную (или скомпилированную) программу. Чтобы запустить его, достаточно ввести

а. out

и в ответ будет выдано следующее сообщение:

Конструкция .с завершает имя файла с программой на С.

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

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 1.5. Подготовка программы на языке С в среде Unix


А что можно сказать об объектном коде? Компилятор создает файл объектного кода, имеющий то же базовое имя, что и исходный файл, но с расширением .о. В нашем примере файл объектного кода получает имя inform, о, но вы его не найдете, поскольку компоновщик удалит его, как только построение исполняемой программы будет завершено. Однако если первоначальная программа использует более одного исходного файла, файлы объектного кода будут сохранены. При последующем рассмотрении многофайловых программ вы убедитесь, что это была здравая идея.


Предварительные сведения 43

коллекция компиляторов GNU и проект LLVM

Проект GNU, запущенный в 1987 году, является проектом массового сотрудничества, в рамках которого было разработано множество бесплатных Unix-подобных программ. (GNU представляет собой аббревиатуру от “GNU’s Not Unix” (“GNU — это не Unix”).) Одно из его детищ — коллекция компиляторов GNU, или GCC, в число которых входит и компилятор GCC для языка С. Проект GCC пребывает в состоянии постоянной разработки, которая ведется под руководством координационного комитета, и в его компиляторе С точно отслеживаются изменения стандартов языка С. Версии GCC доступны для широкого множества аппаратных платформ и операционных систем, включая Unix, Linux и Windows. Компилятор GCC С может быть вызван командой gcc. При этом многие системы, использующие команду gcc, создадут для нее псевдоним сс.

Проект LLVM предоставляет еще одну замену для команды сс. Этот проект представляет собой коллекцию связанного с компилятором программного обеспечения с открытым кодом, разработка которого началась в 2000 году с исследовательского проекта в Иллинойском Университете. Его компилятор Clang выполняет обработку кода С и может быть вызван с помощью команды clang. Доступный на нескольких платформах, включая Linux, в конце 2012 года Clang стал стандартным компилятором системы FreeBSD. Как и GCC, в компиляторе Clang достаточно оперативно отслеживаются изменения в стандарте С.

Оба компилятора принимают флаг -v для отображения информации о версии, поэтому в системах, использующих псевдоним сс для команды gcc или clang, следующая команда отображает сведения об используемом компиляторе и его версии:

СС -V

В зависимости от версии, как gcc, так и clang могут требовать указания параметров времени выполнения для вызова более новых стандартов С:

gcc -std=c99 inform.с

gcc -std=clx inform.с

gcc -std=c11 inform.с

Первый пример вызывает стандарт С99, второй — черновой стандарт С11 для версий GCC, разработанных до принятия стандарта, а третий — стандарт С11 для версий GCC, которые были разработаны после его принятия. В компиляторе Clang применяются те же самые флаги.

Системы Linux

Linux является широко распространенной Unix-подобной операционной системой с открытым кодом, которая работает на различных платформах, включая РС и Мае. Подготовка программы С в среде Linux мало чем отличается от подготовки в среде системы Unix, за исключением того, что вам придется воспользоваться общедоступным и бесплатным компилятором GCC С, предоставляемым GNU. Команда компиляции имеет следующий вид:

gcc inform.с

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


44 Глава 1

Дополнительная информация о GCC, включая сведения о новых версиях, доступна но адресу: http://www.gnu.org/software/gcc/index.html.

Компиляторы командной строки для РС

Компилятор языка С не является частью стандартного пакета Windows, поэтому может возникнуть необходимость в получении и установке этого компилятора.cygwin и MinGW — бесплатные загружаемые файлы, которые делают компилятор GCC доступным для использования в командной строке на ПК.cygwin запускается в собственном окне, которое выглядит подобно окну командной строки, но имитирует среду командной строки Linux. С другой стороны, MinGW выполняется в режиме командной строки Windows. Эти программы поставляются с новейшей (или почти самой новой) версией GCC, которая поддерживает стандарт С99 и, по меньшей мере, часть функциональных возможностей СП. Компилятор Borland C++ Compiler 5.5 — еще одна бесплатная загружаемая программа, которая поддерживает стандарт С90.

Файлы исходного кода должны быть текстовыми файлами, а не документами текстового процессора. (Документы текстового процессора содержат дополнительную информацию о шрифтах и форматировании.) Для работы с ними нужно применять текстовый редактор, такой как Windows Notepad. Можно воспользоваться и текстовым процессором, если с помощью пункта меню Save As (Сохранить как) сохранять файл как текстовый. Файл должен иметь расширение .с. Некоторые текстовые процессоры автоматически добавляют расширение . txt к именам текстовых файлов. Если это произойдет с вашим файлом, придется поменять его имя, заменив txt на с.

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

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

С:>concrete

Интегрированные среды разработки (Windows)

Немало поставщиков, в числе которых такие компании, как Microsoft, Einbarcadero и Digital Mars, предлагают среды 1DE (integrated development environments — интефи- рованная среда разработки) для операционной системы Windows. (В настоящее время большинство из них представляют собой комбинированные компиляторы языков С и C++.) Бесплатные загружаемые пакеты включают Microsoft Visual Studio Express и Pelles С. Все они имеют в своем составе быстродействующие интегрированные среды, позволяющие собирать программы на языке С. Ключевой аспект в том, что каждая из этих сред имеет встроенный редактор, которым можно пользоваться для написания программ на С. Каждая IDE-среда предлагает меню, которые позволяют сохранять файлы исходного кода, а также компилировать и запускать программы, не покидая среду. Каждая IDE-среда возвращает вас обратно в редактор, если компилятор обнаруживает какие-то ошибки, при этом сопоставляя строки программы с соответствующими сообщениями об ошибках.


Предварительные сведения 45

Среды IDE для Windows поначалу могут показаться устрашающими в силу того, что предлагают целый набор целевых платформ, т.е. операционных сред, в которых программа будет использоваться. Например, они могут предложить следующий выбор: 16-разрядная программа для Windows, 32-разрядная программа для Windows, файл библиотеки DLL (Dynamic-Link Library — динамически подключаемая библиотека) и т.д.

Многие целевые платформы предусматривают применение графического интерфейса Windows. Чтобы управлять этими (а также и другими) вариантами, обычно создается проект, куда добавляются имена файлов исходного кода, которые должны использоваться. Конкретные действия зависят от применяемого программного продукта. Как правило, сначала нужно воспользоваться меню File (Файл) или Project (Проект) для создания проекта. При этом важно выбрать правильную форму проекта. Примеры, приводимые в этой книге, носят общий характер и служат иллюстрацией выполнения программы в среде командной строки. Разнообразные IDE-среды для Windows предлагают один или несколько вариантов, чтобы соответствовать этому нетребовательному предположению.

Например, в Microsoft Visual Studio имеется вариант Win32 Console Application. В других системах ищите вариант, в котором присутствуют такие термины, как DOS EXE, Console или Character Mode executable. В этих режимах исполняемая программа будет выполняться в консольном окне. После создания проекта подходящего типа воспользуйтесь меню IDE-среды, чтобы открыть новый файл с исходным кодом. В большинстве программных продуктов это делается через меню File. Возможно, для добавления исходного файла в проект понадобится выполнить дополнительные действия.

Поскольку IDE-среды для Windows обычно рассчитаны на работу с языками С и C++, необходимо указать, что требуется создание программы на С. В некоторых интегрированных средах язык С указывается с помощью типа проекта. В других продуктах, таких как Microsoft Visual C++, для этого служит файловое расширение .с. В то же время большая часть программ на С работают и как программы на языке C++. Различия между языками С и C++ приведены в справочном разделе IX приложения Б.

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

getchar();

Эта строка считывает нажатие клавиши, поэтому программа будет ожидать нажатия клавиши <Enter>. Иногда, в зависимости от того, как функционирует программа, она уже может ожидать нажатие любой клавиши. В такой ситуации следует вызвать функцию getchar() два раза:

getchar();

getchar();

Например, если последнее, что сделала программа, было приглашение ввести ваш вес, вы набираете его на клавиатуре и нажимаете клавишу <Enter>, чтобы ввести эти данные. Программа считывает значение вашего веса, первый вызов функции getchar() прочитает нажатие клавиши <Enter>, а второй вызов getchar() заставит программу остановиться до тех пор, пока снова не будет нажата <Enter>. Если вы пока что не видите в этом большого смысла, то поймете сказанное после того, как освоите ввод данных в С. Позже мы еще папомним об этом подходе.


46 глава 1

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

Microsoft Visual Studio и стандарт С

Среда Microsoft Visual Studio и бесплатная версия Microsoft Visual Studio Express занимают наибольшую нишу в разработке программного обеспечения для Windows, поэтому их взаимосвязь со стандартами С весьма важна. Говоря кратко, политика Microsoft всячески поощряет программистов переходить от С к C++ или С#. Среда Visual Studio поддерживает стандарт С89/90, но ее поддержка более поздних стандартов заключается в поддержке тех новых функциональных возможностей, которые присущи также C++, таких как тип long long. Кроме того, начиная с версии Visual Studio 2012, среда не предлагает С в качестве одного из доступных для выбора типов проекта. Тем не менее, Visual Studio по-прежнему можно использовать с подавляющим большинством программ, описанных в этой книге. Одна из возможностей предусматривает просто выбор в настройках Application settings (Настройки приложения) опции C++, затем Win32 Console и далее Empty Project (Пустой проект). Практически все версии С совместимы с C++, поэтому большинство программ на С в этой книге также работают и как протраммы C++. Или же, выбрав опцию C++, для файла исходного кода можно применять расширение .с вместо используемого по умолчанию расширения .срр, и компилятор будет работать с правилами языка С, а не C++.

Опция Windows/Linux

Многие пакеты Linux можно устанавливать из среды Windows для создания системы с двойной загрузкой. Часть дискового пространства будет выделена для системы Linux, после чего можно будет загружать либо Windows, либо Linux. Программу для Linux нельзя запускать под управлением Windows или наоборот, и к файлам Linux нельзя получать доступ из системы Windows, но к документам Windows можно обращаться из среды Linux.

Работа с языком с в системах Macintosh

В настоящее время компания Apple предоставляет свою систему разработки XCode в виде бесплатного загружаемого пакета. (В прошлом этот пакет иногда был доступен бесплатно, а иногда за умеренную плату.) Эта система позволяет работать с несколькими языками программирования, в числе которых С.

Система XCode, с ее способностями поддержки нескольких языков программирования, ориентации на множество целевых платформ и разработки крупномасштабных проектов, может казаться пугающе сложной. Но для создания простых программ на С достаточно овладеть лишь необходимым минимумом знаний. В системе XCode 4.6 воспользуйтесь меню File, чтобы выбрать опции New (Создать), Project (Проект), OS X Application Command Line Tool (Средство командной строки приложения OS X), после чего введите имя программного продукта и выберите С в качестве типа (Туре). Для компиляции кода на языке С система XCode применяет компилятор Clang или GCC С. Раньше по умолчанию использовался компилятор GCC, но теперь — Clang. В настройках XCode можно указать необходимый компилятор и поддерживаемый стандарт С. (Из-за особенностей лицензирования версия Clang, доступная вместе с XCode, является более новой, чем версия GCC.)


Предварительные сведения 47

Mac OS X построена на основе Unix, и утилита Terminal открывает окно, которое позволяет запускать программы в среде командной строки Unix. Компания Apple не предоставляет компилятор командной строки в составе своего стандартного пакета, но если загрузить XCode, можно также загрузить дополнительные инструменты командной строки, которые позволяют применять команды clang и gcc для выполнения компиляции в режиме командной строки.

Как организована эта книга

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

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

Соглашения, принятые в этой книге

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

шрифты и начертание

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

Тот же самый моноширинный шрифт применяется для представления терминов, связанных с кодом, например, main(), и имен файлов, таких как stdio.h.


48 Глава 1

Курсивный моноширинный шрифт используется для терминов-заполнителей, которые нужно заменять конкретными терминами, как в следующей модели объявления:

имя типа имя_переменной;

В данном случае можно, например, вместо имя_типа указать int, а вместо имя_ переменной — zebra_count.

Вывод программы

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

Пожалуйста, введите название книги.

Нажмите [enter] в начале строки для останова.

Язык программирования С

Теперь введите имя автора.

Стивен Прата

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

Существует множество способов обмена данными между вами и компьютером. Тем не менее, мы будем полагать, что вы вводите команды с клавиатуры, а ответ компьютера читаете с экрана.

Специальные клавиши

Как правило, вы отправляете строку инструкций, нажимая клавишу, которая обозначена как <Enter>, <c/r>, <Return> или похожим образом. В тексте мы ссылаемся на нее как на клавишу <Enter>. Обычно в данной книге считается само собой разумеющимся нажатие клавиши <Enter> в конце каждой вводимой строки. Тем не менее, чтобы заострить внимание на некоторых моментах, в некоторых примерах кода клавиша <Enter> указывается явно как [enter]. Квадратные скобки означают, что вы нажимаете одну клавишу <Enter>, а не вводите с клавиатуры слово enter.

Мы также пользуемся управляющими символами, например, <Ctrl+D>. Таким способом обозначается нажатие клавиши <D> при удержании в нажатом состоянии клавиши <Ctrl> (или, возможно, <Control>).

Системы, использованные при подготовке данной книги

Некоторые аспекты языка С, такие как объем памяти, отводимый для хранения числа, зависят от системы. Когда при описании примеров мы упоминаем “наша система", обычно речь идет о компьютере iMac, работающем под управлением OS X 10.8.4 и применении системы разработки XCode 4.6.2 с компилятором Clang 3.2. Большинство программ были также скомпилированы с помощью Microsoft Visual Studio Express 2012 и Pelles С 7.0 в системе Windows 7 и GCC 4.7.3 в системе Ubuntu 13.04 Linux.

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


Предварительные сведения 49

Требования к системе

Вы должны располагать компилятором С либо иметь к нему доступ. Компиляторы С имеются на огромном множестве различных компьютерных систем, так что перед вами богатый выбор. Удостоверьтесь в том, что используете компилятор С, предназначенный для вашей конкретной системы. Некоторые примеры в этой книге требуют поддержки стандарта С99 или C11, однако большинство примеров будут работать с компилятором, поддерживающим стандарт С90. Если применяемый компилятор был разработан до появления стандартов ANSI/ISO, возможно, придется достаточно часто вносить правки в код, поэтому компилятор имеет смысл обновить.

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

Специальные элементы

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

Врезка

Врезка содержит более глубокий анализ или дополнительную информацию, которая

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

Совет

Советы содержат краткие полезные рекомендации, касающиеся разрешения конкретных

ситуаций в программировании.

Внимание!

Здесь даются предупреждения о потенциальных ловушках.

На заметку!

Нечто вроде вместилища разнообразных комментариев, которые не подпадают ни под одну

из указанных выше категорий.

Резюме

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

Язык С принадлежит к числу компилируемых. Компиляторы и компоновщики (редакторы связей) языка С — это программы, которые переводят исходный код С в исполняемый код.

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


50 глава 1

Вопросы для самоконтроля

Ответы на вопросы для самоконтроля приведены в приложении А.

1.  Что означает переносимость в контексте программирования?

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

3.  Назовите семь основных этапов программирования.

4.  Что делает компилятор?

5.  Что делает компоновщик?

Упражнения по программированию

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

1. Вы только что были приняты на работу в компанию MacroMuscle, Inc. Компания выходит на европейский рынок и желает иметь в своем распоряжении программу, которая переводит дюймы в сантиметры (1 дюйм составляет 2,54 см). Компания хочет, чтобы программа выдавала пользователю приглашение на ввод значения в дюймах. Ваша задача заключается в том, чтобы определить цели программы и разработать проект программы (этапы 1 и 2 процесса программирования).



2

Введение в язык С

В ЭТОЙ ГЛАВЕ...

•    Операция: =

•    Функции: main(), printf()

•    Написание простой программы на языке С

•   Создание целочисленных переменных, присваивание им значений и отображение этих значений на экране

•    Символ новой строки

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

•    Что такое ключевые слова





52 Глава 2

Н

а что похожа программа на языке С? Пролистав эту книгу, вы найдете множество примеров. Возможно, вы сочтете, что программа на С выглядит несколько странно, будучи усыпанной такими символами, как {, cp->tort и *ptr++. Однако по мере чтения книги, как они, так и другие характерные для С символы, уже не покажутся странными, станут более привычными и, возможно, вам даже будет трудно обходиться без них! Те читатели, которые уже знакомы с одним из множества языков, построенных на основе С, могут ощутить себя так, словно они возвратились в отчий дом к истокам детства. Эту главу мы начнем с того, что рассмотрим простую демонстрационную программу и объясним, что она делает. Одновременно мы уделим особое внимание некоторым базовым свойствам языка С.

Простой пример программы на языке С

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

Листинг 2.1. Программа first.с

Язык программирования C. Лекции и упражнения. 6-е издание


Если вы думаете, что программа что-то отображает на экране, то вы не ошиблись! Однако конкретная информация, которая будет отображена на экране, может быть не очевидной, поэтому запустите программу и ознакомьтесь с ее результатами. Прежде всего, воспользуйтесь услугами своего любимого редактора (или “любимым” редактором вашего компилятора), чтобы создать файл с текстом листинга 2.1. Назначьте этому файлу имя, которое оканчивается на .с и удовлетворяет требованиям, предъявляемым к именам файлов в вашей локальной системе. Например, в качестве имени можно выбрать first.с. Теперь скомпилируйте и выполните программу. (Общие сведения по этому процессу приведены в главе 1.) Если все прошло хорошо, выходные данные программы будут иметь следующий вид:

Я простой компьютер.

Моей любимой цифрой является 1, так как она первая.

В целом результат не является неожиданным, однако что случилось с конструкциями \n и %d из программы? Кроме того, некоторые строки программы выглядят довольно странно. Самое время для пояснений.


Введение в язык С 53

Настройка программы

Возможно, вывод этой программы быстро мелькает на экране, а затем исчезает. Некоторые оконные среды запускают программу в отдельном окне и автоматически закрывают его после завершения программы. В таком случае в программу можно вставить дополнительный код, чтобы окно оставалось открытым до нажатия какой-либо клавиши. Один из возможных способов достижения этой цели — добавление перед оператором return следующей строки: getchar();

Этот код вынуждает программу дожидаться нажатия клавиши, в результате чего окно остается открытым до ее нажатия. Функция getchar() более подробно описана в главе 8.

Пояснение примера

Давайте совершим два прохода по исходному коду программы. Первый проход (“Проход 1: краткий обзор”) освещает значение каждой строки и поможет получить общее представление о том, что происходит. На втором проходе (“Проход 2: нюансы программы”) исследуются конкретные результаты и подробности, чтобы можно было глубже понять особенности программы. На рис. 2.1 обобщены все части программы на С; на нем показано больше элементов, чем использует наша первая программа.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 2.1. Структура программы на языке С



54 Глава 2

Проход 1: краткий обзор

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

#include <stdio.h>     <-включить другой файл

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

int main(void)         <-имя функции

Программа на языке С состоит из одной или большего количества функций — базовых модулей любой программы С. Рассматриваемая программа состоит из одной функции по имени main. Круглые скобки идентифицируют main() как имя функции, int указывает на то, что функция main() возвращает целое число, a void — о том, что функция main() не принимает аргументов. Эти подробности будут рассмотрены позже. А сейчас просто примем int и void как часть способа определения функции main() в стандарте ANSI С. (Если в вашем распоряжении находится компилятор языка С, разработанный до появления стандарта ANSI С, удалите слово void; чтобы избежать несоответствий в дальнейшем, вам потребуется найти более новый компилятор.)

/* простая программа */ <-комментарий

Символы /* и */ заключают в себе комментарии, т.е. примечания, которые помогают понять смысл программы. Они предназначены исключительно для читателя кода и компилятором игнорируются.

{                      <-начало тела функции

Эта открывающая фигурная скобка обозначает начало оператора, образующего функцию. Определение функции заканчивается закрывающей фигурной скобкой (}).

int num;               <-оператор объявления

Этот оператор объявляет переменную с именем num и уведомляет, что она имеет тип int (целочисленный).

num = 1;               <-оператор присваивания

Оператор num = 1; присваивает значение 1 переменной по имени num.

printf("Я простой     ");     <-оператор вызова функции

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

printf("компьютер.\n"); <-еще один оператор вызова функции

Следующий вызов функции printf() дописывает слово “компьютер” в конец предыдущей выведенной фразы. \n — это код, указывающий компьютеру начать новую строку, т.е. переместить курсор в начало следующей строки.

printf("Моей любимой цифрой является %d, так как она первая.\n",num);


Введение в язык С 55

Последнее использование функции printf() приводит к выводу значения переменной num (равного 1), которое вставляется внутрь фразы, заключенной в двойные кавычки. Код %d указывает компьютеру, где и в какой форме вывести значение num.

return 0; <-оператор возврата

Функция С может предоставить, или возвратить, число объекту, который ее вызвал. Пока что рассматривайте эту строку как корректный способ завершения функции main().

}         <-конец программы

Как уже было сказано, программа оканчивается закрывающей фигурной скобкой.

Проход 2: нюансы программы

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

Директивы  #include и заголовочные файлы

#include <stdio.h>

С этой строки начинается программа. Результат выполнения #include <stdio.h> оказывается таким же, как если бы вы ввели с клавиатуры содержимое файла stdio.h в своем файле там, где находится строка # include. В сущности, это операция вырезания и вставки. Директива include (включить файлы) представляет собой удобный способ совместного использования информации, который применяется во многих программах.

Оператор #include представляет собой пример директивы препроцессора в С. В общем случае компиляторы языка С выполняют некоторую подготовительную работу над исходным кодом перед компиляцией; это называется предварительной обработкой.

Файл stdio.h поставляется как часть всех пакетов компиляторов С. Он содержит информацию о функциях ввода и вывода, таких как printf(), и предназначен для использования компилятором. Его имя происходит от standard input/output header (заголовочный файл стандартного ввода-вывода). Разработчики языка С называют совокупность информации, которая помещается в верхней части файла заголовком, а реализации С обычно поставляются с множеством заголовочных файлов.

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

В ISO/ANSI С стандартизировано то, какие заголовочные файлы компилятор С должен делать доступными. Для одних программ необходимо включать файл stdio.h, для других программ — нет. Документация по конкретной реализации языка С должна содержать описание функций из библиотеки С. Эти описания функций идентифицируют, какие заголовочные файлы нужны. Например, в описании функции printf() говорится о необходимости применения файла stdio.h. Пропуск подходящего зато-


56 Глава 2 ловочного файла может и не повлиять на какую-то конкретную программу, однако лучше на это не рассчитывать. Каждый раз, когда в приводимых здесь примерах используются библиотечные функции, будут применяться включаемые файлы, определенные стандартом ISO/ANSI для этих функций.

НА ЗАМЕТКУ! Почему ввод и вывод не являются встроенными

Может возникнуть вопрос, почему настолько базовые возможности, как ввод и вывод, не включены автоматически. Одна из причин связана с тем, что не все программы используют пакет ввода-вывода, а философия языка С запрещает перегружать программу ненужными функциями. Этот принцип экономного использования ресурсов делает язык С особо удобным для написания встроенных программ, например, кода для процессора, управляющего автоматизированной подачей топлива, или для проигрывателя Blu-гау-дисков. Кстати, строка с директивой # include вообще не является оператором языка С! Символ # в первой строке означает, что до передачи компилятору она должна обрабатываться препроцессором. Позже вы столкнетесь с различными примерами команд препроцессора, а в главе 16 эта тема рассматривается более подробно.

Функция main()

int main (void)

В этой строке программы объявляется функция по имени main. Действительно, main — более чем простое имя, однако это был единственно возможный выбор. Программа на языке С (с некоторыми исключениями, на которых мы сейчас не будем обращать внимание) всегда начинается с выполнения функции main(). Для других функций вы можете выбирать имена, однако, чтобы можно было запустить программу, в ней должна присутствовать функция main(). А для чего нужны скобки? Они идентифицируют main() как функцию. Вскоре вы узнаете больше сведений о функциях, а пока просто запомните, что функции представляют собой базовые модули программы С.

Возвращаемый тип функции main() определен как int. Это означает, что значения, которые может возвращать main(), являются целочисленными. Куда они возвращаются? В операционную систему — в главе 6 мы еще вернемся к этому вопросу.

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

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

main()

Стандарт С90 неохотно смирился с этой формой, а стандарты С99 и С11 ее вообще не признают. Так что даже если компилятор позволяет делать это, лучше так не поступать.

Можно также столкнуться со следующей формой:

void main()

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


Введение в язык С 57

Комментарии

/* простая программа */

Части программы, заключенные в символы /* */, представляют собой комментарии. Комментарии существенно облегчают понимание программы всеми, кто ее изучает (в том числе и вам). Одно из полезных свойств комментариев в языке С заключается в том, что они могут быть размещены в любом месте программы, даже в той же строке, где находится поясняемый код. Более длинный комментарий может располагаться в собственной строке или занимать несколько строк. Все, что находится между открывающей (/*) и закрывающей (*/) последовательностями, компилятор игнорирует. Ниже представлены примеры правильных и неправильных форм комментариев:

/* Это комментарий на С. */

/* Этот комментарий, будучи несколько многословным, размещен в двух строках. */

/*

Допустим также и такой комментарий.

*/

/* Такой комментарий недопустим ввиду отсутствия маркера окончания.

В стандарте С99 появился еще один стиль комментария, который был популяризирован языками C++ и Java. Новый стиль предполагает применение символов // для представления комментария, ограниченного одной строкой:

// Данный комментарий умещается в одной строке.

int rigue;     // Комментарий можно также поместить сюда.

Поскольку конец строки означает конец комментария, этот стиль требует маркера только в начале комментария.

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

/*

Я надеюсь, что этот вариант работает.

*/

х = 100;

у = 200;

/* Теперь попробуем сделать что-нибудь еще. */

Предположим, что вы решили удалить четвертую строку, но случайно удалили также и третью строку (*/). В результате получился такой код:

/*

Я надеюсь, что этот вариант работает.

у = 200;

/* Теперь попробуем сделать что-нибудь еще. */

Теперь компилятор соединяет в пару маркер /* из первой строки и маркер * / в четвертой строке, объединяя все четыре строки в один комментарий, в том числе и строку, которая по предположению была частью программного кода. Поскольку форма // не распространяется на более чем одну строку, не возникает проблема “исчезновения кода”.

Некоторые компиляторы не поддерживают э ту возможность, другие могут потребовать изменить параметры компилятора, чтобы стали доступными функции, предусмотренные стандартом С99 или С11.

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


58 Глава 2

Язык программирования C. Лекции и упражнения. 6-е издание

В листинге 2.1 фигурные скобки определяют границы функции main (). В общем случае все функции языка С используют фигурные скобки для обозначения начала и конца своего тела. Наличие скобок обязательно, так что не забывайте о них. Для этой цели допускается применять только фигурные скобки ({ }), но не круглые (( )) или квадратные ([ ]).

Фигурные скобки можно также использовать внутри функции для организации операторов в модуль или блок. Если вам приходилось работать с языками Pascal, ADA, Modula-2 или Algol, то вы заметите, что фшурные скобки подобны операторам begin и end в упомянутых языках.

Объявления

int num;

Эта строка программы называется оператором объявления. Оператор объявления является одной из наиболее важных возможностей языка С. В рассматриваемом примере объявляются два аспекта. Во-первых, где-то в функции имеется переменная по имени num. Во-вторых, с помощью int переменная num объявлена как целочисленная, т.е. число без десятичной точки, или без дробной части, (int представляет собой пример типа данных.) Компилятор применяет эту информацию для того, чтобы выделить в памяти для переменной num пространство подходящего размера. Точка с запятой в конце строки показывает, что данная строка является оператором или инструкцией языка С. Точка с запятой является частью этого оператора, а не просто разделителем между операторами, как, например, в языке Pascal.

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

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

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

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

int main() // традиционные правила

{

int doors;


Введение в язык с 59

int dogs; doors = 5; dogs = 3;

// другие операторы

}

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

int main()      // действующие в настоящее время правила С

{

// какие-то операторы int doors;

doors =5;    // первое использование переменной doors

// еще какие-то операторы int dogs;

dogs =3;     // первое использование переменной dogs

// другие операторы

}

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

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

Типы данных

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

Выбор имени

Для переменных следует выбирать осмысленные имена (или идентификаторы), например, sheep_count вместо хЗ, если программа занимается подсчетом овец. Если имен недостаточно, добавьте комментарии с объяснениями того, какие данные эти переменные представляют. Документирование программы в подобное манере считается хорошим тоном в программировании.

Стандарты С99 и C11 разрешают использовать имена идентификаторов любой желаемой длины, но компилятор должен рассматривать в качестве значащих только пе[> вые 63 символа. В случае внешних идентификаторов (глава 12) распознаваться будут только 31 символ. Это заметное увеличение по сравнению с требованиями стандарта С90, составляющими 31 и 6 символов, соответственно, а более старые компиляторы часто останавливались на максимум 8 символах. В действительности можно использовать больше символов, чем указанный максимум, но компилятор просто не обязан обращать внимание на дополнительные символы. Что это значит? При наличии двух идентификаторов длиной по 63 символа, отличающихся только одним символом, компилятор должен распознать их как разные идентификаторы. Если же два идентификатора длиной по 64 символа имеют отличие только в последнем символе, то компилятор может распознать их как разные, а может и не распознать; в стандарте ничего не определено относительно того, что должно происходить в таком случае.


60 глава 2

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

Язык программирования C. Лекции и упражнения. 6-е издание


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

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

Чтобы придать языку С более высокую интернациональность, стандарты С99 и С99 обеспечивают доступность обширного набора символов посредством механизма UCN (Universal Character Names — имена в универсальных символах). Подробное описание этого расширения приведено в приложении Б. Эта возможность позволяет использовать символы, не входящие в английский алфавит.

Четыре веских причины объявления переменных

Некоторые ранние языки программирования, такие как первоначальные формы FORTRAN и BASIC, позволяли применять переменные без их объявления. А почему нельзя использовать такой упрощенный подход в С? Это обусловлено рядом причин.

•   Размещение объявлений всех переменных в одном месте упрощает читателю кода уловить назначение программы. Это особенно справедливо, когда вы назначаете переменным осмысленные имена (например, taxrate вместо г). Если имени недостаточно, предусмотрите в комментарии объяснение, что конкретно представляют объявленные переменные. Документирование программы в таком стиле считается хорошим тоном в программировании.

•   Обдумывание того, какие переменные объявить, способствует проведению определенного планирования, прежде чем приступать к написанию кода. Какая информация нужна для того, чтобы начать писать программу? Какой вывод должна производить программа? Как лучше всего представить данные?

•   Объявление переменных помогает избежать одной из наиболее тонких и трудных для обнаружения ошибок программирования — некорректно написанного имени переменной.

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

RADIUS1 = 20.4;


Введение в язык С 61

Затем в другом месте программы был введен оператор с неправильно указанным именем переменной:

CIRCUM = 6.28 * RADIUS1;

Вы не заметили, как вместо цифры 1 ввели букву 1 (строчную латинскую букву “С). Согласно правилам этого языка будет создана новая переменная с именем RADIUS1, которая получит случайное значение (возможно ноль, а возможно какой-то мусор). Переменная CIRCUM получит неправильное значение, и придется потратить немало времени, чтобы выяснить причину. В С это невозможно (если только вы не окажетесь достаточно неосмотрительными, объявив два настолько похожих имени), т.к. компилятор выдаст сообщение об ошибке, когда в коде встретится необъявленная переменная RADIUS1.

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

Учитывая необходимость объявления переменных, где это следует делать? Как упоминалось ранее, до появления стандарта С99 требовалось размещать все объявления в начале блока. Одна из причин следования этой рекомендации заключается в том, что группирование объявлений в едином месте облегчает понимание назначения программы. Разумеется, существуют аргументы и в пользу распределения объявлений по всей программе, как теперь разрешает делать стандарт С99. Идея в том, чтобы объявлять переменные непосредственно перед тем, когда вы готовы присвоить им значения. Это позволит не забыть присвоить переменным начальные значения. Однако на деле многие компиляторы пока не поддерживают такое правило стандарта С99.

Присваивание

Язык программирования C. Лекции и упражнения. 6-е издание
num = 1;

В следующей строке программы находится оператор присваивания, в котором применяется одна из основных операций языка С. В рассматриваемом примере это означает “присвоить значение 1 переменной num”. Предшествующая ему строка int num; резервирует в памяти компьютера пространство для переменной num, а строка с оператором присваивания сохраняет значение в этой ячейке. Позже при желании переменной num можно присвоить другое значение; вот почему num называется переменной.

Обратите внимание, что оператор присваивания назначает значение, указанное справа знака операции, переменной, указанной слева. Кроме того, оператор завершается точкой с запятой (рис. 2.2).

Функция printf() printf("Я простой "); printf("компьютер.\n"); printf ("Моей любимой цифрой является %d, так как она первая. \n" ,num);

Во всех этих строках используется стандартная функция С по имени printf(). Круглые скобки указывают, что printf является именем функции. То, что содержится внутри круглых скобок — это информация, передаваемая из функции main() в функцию printf(). Например, первая строка передает фразу “Я простой ” в функцию printf(). Такая информация называется аргументам или, более точно, фактическим


62 Глава 2

printf()                                    аргументом функции (рис. 2.3). (Для различения

Язык программирования C. Лекции и упражнения. 6-е издание
конкретного значения, переданного функции, и переменной в функции, используемой для хранения значения, в языке С используются термины фактический аргумент и формальный аргумент. Более подробно об этом пойдет речь в главе 5.) Что делает функция printf() с этим аргументом? Она просматривает все, что заключено в двойные кавычки, и выводит этот текст на экран.

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

Чем отличается следующая строка с printf() ? Она содержит символы \n, заключенные в кавычки, и они не выводятся! В чем же дело? Символы \n означают начало новой строки. Комбинация \n (вводится как два символа) представляет один символ, получивший название символы новой строки. Для функции printf() эта комбинация означает “начать новую строку с крайней левой позиции”. Другими словами, вывод символа новой строки выполняет ту же операцию, что и нажатие клавиши <Enter> на стандартной клавиатуре. А почему бы просто не нажать клавишу <Enter> при наборе этого аргумента printf() ? Потому что это будет воспринято как непосредственная команда редактору, но не как инструкция, которая должна быть помещена в исходном коде. В результате при нажатии клавиши <Enter> редактор прейдет с текущей строки на следующую. Тем не менее, символ новой строки влияет на то, как будет отображаться вывод программы.

Символ новой строки является примером  управляющей последовательности. Управляющая последовательность применяется для представления символов, которые трудно или просто невозможно ввести с клавиатуры. Примерами таких последовательностей могут служить символы \t для представления нажатия клавиши <Таb> и \b — для <Backspace>. В любом случае управляющая последовательность начинается с обратной косой черты (\). Мы вернемся к этой теме в главе 3.

Таким образом, все это объясняет, почему три оператора printf() вывели только две строки: первый оператор не содержал символа новой строки, но он был включен во второй и третий операторы.

Финальная строка с printf() привносит еще одну странность: что случилось с %d при выводе строки? Вспомните, что вывод этой строки выглядел так:

Моей любимой цифрой является 1, так как она первая.

Итак, при выводе этой строки вместо группы символов %d появилась цифра 1, и 1 — это значение переменной num. Комбинация %d представляет собой заполнитель, который показывает, где должно быть выведено значение переменной num. Эта строка подобна следующему оператору BASIC:

PRINT "Моей любимой цифрой является "; num; ", так как она первая."

Версия С фактически делает немного больше. Символ % уведомляет программу, что в этом месте будет выведено значение переменной, a d указывает на то, что перемен-


Введение в язык С 63

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

Оператор возврата

return 0;

Оператор возврата является завершающим оператором программы, int в конструкции int main (void) означает, что функция main() возвращает целочисленное значение. Стандарт языка С требует, чтобы поведение функции main() было именно таким. Функции С, возвращающие значения, делают это с помощью оператора возврата, состоящего из ключевого слова return, за которым следует возвращаемое значение и точка с запятой. Если в функции main() опустить оператор возврата, по достижении закрывающей фигурной скобки } программа возвратит значение 0. Таким образом, оператор возврата в конце функции main() можно не указывать. Однако для других функций это не разрешено, поэтому ради единообразия рекомендуем использовать оператор возврата также и в main(). На этом этапе вы можете считать оператор возврата в функции main() чем-то необходимым для обеспечения логической согласованности, но в некоторых операционных системах, включая Linux и Unix, он имеет практическое применение. В главе 11 эта тема рассматривается более подробно.

Структура простой программы

Теперь, когда вы видели конкретный пример, вы готовы к ознакомлению с несколькими общими правилами для программ на С. Программа состоит из коллекции одной или нескольких функций, одна из которых обязательно должна иметь имя main(). Описание функции включает заголовок и тело функции. Заголовок функции содержит имя функции и сведения о типе информации, передаваемой в функцию и возвращаемой из нее. Имя функции можно опознать по круглым скобкам, которые могут быть пустыми. Тело функции заключено в фигурные скобки ({ }) и состоит из последовательности операторов, каждый из которых завершается точкой с занятой (рис. 2.4). В примере, приведенном в настоящей главе, использовался оператор объявления, определяющий имя и тип переменной. В нем также присутствовал оператор присваивания, устанавливающий значение переменной. Кроме того, в нем применялись три оператора вывода, в каждом из которых вызывалась функция printf(). Эти операторы вывода представляют собой примеры операторов вызова функции. И, наконец, функция main() завершается оператором возврата.

Короче говоря, простая стандартная программа на С должна иметь следующий формат:

#include <stdio.h>

int main(void)

{

операторы return 0;

}

(Помните, что каждый оператор завершается символом точки с запятой.)


64 глава 2

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 2.4. Функция имеет заголовок и тело


Советы по обеспечению читабельности программ

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

Вы уже видели два приема улучшения читабельности: выбор осмысленных имен для переменных и использование комментариев. Обратите внимание на то, что оба эти приема дополняют друг друга. Если назначить переменной имя width (ширина), то в комментарии с пояснением, что эта переменная представляет ширину, нет необходимости; в то же время переменная по имени video_routine_4 (видеопрограмма 4) требует объяснения, для чего она предназначена.

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

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


Введение в язык С 65

Точка с запятой сообщает компилятору, где заканчивается один оператор и начинается другой, но логика программы будет намного яснее, если следуете соглашениям, используемым в примере настоящей главы (рис. 2.5).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 2.5. Придание программе удобочитаемого вида


Еще один шаг в использовании языка С

Первая демонстрационная программа была совсем простой, и следующий пример, представленный в листинге 2.2, не намного труднее.

Листинг 2.2. Программа fathm ft.с

Язык программирования C. Лекции и упражнения. 6-е издание


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

Документирование

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


66 Глава 2

Множественные объявления

Во-вторых, в программе объявлены сразу две переменные без использования отдельного оператора объявления для каждой переменной. Для этого в операторе объявления переменные (feet и fathoms) должны разделяться запятыми. Это значит, что

int feet, fathoms; и

int feet; int fathoms;

эквивалентны.

Умножение

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

feet = 6 * fathoms;

означает “получить значение переменной fathoms, умножить его на 6 и присвоить результат вычисления переменной feet”.

Вывод нескольких значений

Наконец, в-четвертых, в этой программе довольно необычно применяется функция printf(). Если скомпилировать и выполнить этот пример, вывод будет выглядеть примерно так:

В 2 морских саженях содержится 12 футов!

Да, именно 12 футов!

На этот раз в первом вызове printf() сделано две подстановки. Первая комбинация %d в кавычках была заменена значением первой переменной (feet) в списке, который следует за сегментом в кавычках, а вторая такая комбинация заменена значением второй переменной (fathoms) из этого списка. Обратите внимание, что список переменных, предназначенных для вывода, находится в хвостовой части этого оператора после части, заключенной в кавычки. Также следует отметить, что каждый элемент списка отделяется от других запятой.

Второй случай использования printf() демонстрирует тот факт, что выводимое значение не обязательно должно быть переменной; оно вполне может быть чем-то вроде выражения 6 * fathoms, которое приводится к соответствующему типу.

Эта программа имеет ограниченную область применения, однако она может служить ядром программы для преобразования морских саженей в футы. Все что для этого понадобится — способ интерактивного присваивания дополнительных значений переменной feet; далее в этой главе будет показано, как это делать.

Множество функций

До сих пор в программах использовалась стандартная функция printf(). В листинге 2.3 показано, как можно внедрить в программу собственную функцию помимо

main().


Введение в язык С 67

Листинг 2.3. Программа two func.c

Язык программирования C. Лекции и упражнения. 6-е издание


Вывод будет иметь следующий вид:

Я вызываю дворецкого.

Вы звонили, сэр?

Да. Принесите мне чай и записываемые DVD-диски.

Функция butler() встречается в этой программе трижды. В первый раз она появляется в виде прототипа, который информирует компилятор о функциях, которые будут применяться. Во второй раз она присутствует внутри main() в форме вызова функции. В третий раз в программе представлено определение функции, которое является исходным кодом самой функции. Рассмотрим по очереди каждое из этих трех появлений.

Прототипы были добавлены в стандарте С90, поэтому более старые компиляторы их не распознают. (Вскоре будет дано объяснение, что делать, когда приходится работать с такими компиляторами.) Прототип объявляет компилятору о том, что вы применяете конкретную функцию, поэтому он и называется объявлением функции. Он также определяет свойства этой функции. Например, первое ключевое слово void в прототипе для функции butler() указывает на то, что butler() не имеет возвращаемого значения. (В общем случае функция может возвращать значение в вызывающую функцию для последующего его использования, но в случае butler() это не так.) Второе void — то, которое в butler (void) — означает, что функция butler() не принимает аргументов. Поэтому когда компилятор достигает места в main(), где вызывается butler(), он может проверить, корректно ли применяется эта функция. Обратите внимание, что ключевое слово void употребляется в смысле “пусто”, а не в смысле “недействительно”.

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

void butler();

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


68 глава 2

Далее функция butler() вызывается внутри main() путем указания ее имени и круглых скобок. Когда функция butler() завершит свою работу, управление переходит на следующий оператор внутри main().

Наконец, функция butler() определена точно так же, как main(), с заголовком и телом, заключенным в фигурные скобки. Заголовок повторяет информацию, указанную в прототипе: функция butler() не принимает аргументов и ничего не возвращает. Для компиляторов ранних версий второе вхождение void понадобится удалить.

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

Стандарт языка С рекомендует предоставлять прототипы для всех используемых функций. Стандартные файлы include позаботятся о решении этой задачи для стандартных библиотечных функций. Например, согласно стандарту языка С, файл stdio.h содержит прототип функции printf(). В заключительном примере главы 6 демонстрируется способ распространения прототипов на функции не void, а в главе 9 такие функции рассматриваются более подробно.

Знакомство с отладкой

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

Листинг 2.4. Программа nogood.с

Язык программирования C. Лекции и упражнения. 6-е издание


Синтаксические ошибки

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


Введение в язык с 69

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

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

int n, n2, nЗ;


или, возможно, так:

int n;

int n2;

int nЗ;

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

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

Однако и сам компилятор может ошибаться. Настоящая синтаксическая ошибка в одном месте может ввести компилятор в заблуждение и заставить его предполагать, что он нашел другие ошибки. Например, поскольку в примере неправильно объявлены переменные n2 и n3, компилятор может посчитать, что он обнаружил еще несколько ошибок там, где используются эти переменные. В действительности, если не удается разобраться во всех обнаруженных ошибках, то вместо того, чтобы пытаться исправлять сразу все ошибки, сначала следует исправить первые одну или две ошибки, после чего выполнить повторную компиляцию; вполне возможно, что какие-то другие ошибки исчезнут. Продолжайте в том же духе, пока программа не заработает. Еще одна распространенная особенность компилятора заключается в том, что он сообщает об ошибке на одну строку позже. Например, компилятор может не догадаться, что не хватает точки с запятой, пока не наступит очередь компиляции следующей строки. Таким образом, если компилятор жалуется на отсутствие точки с запятой в строке, в которой этот символ имеется, проверьте предыдущую строку.

Семантические ошибки

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

nЗ = n2 * n2;


70 глава 2

В данном случае предполагается, что n3 представляет куб числа n, в то время как код вычисляет четвертую степень n.

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

Листинг 2.5. Программа stillbad.c

Язык программирования C. Лекции и упражнения. 6-е издание


Вывод программы выглядит следующим образом:

n = 5, n в квадрате = 25, n в кубе = 625

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

Тело программы начинается с объявления трех переменных: n, n2, и n3. Эту ситуацию можно смоделировать, нарисовав три прямоугольника и пометив их именами переменных (рис. 2.6). Далее программа присваивает переменной n значение 5. Смоделируйте это действие, записав 5 в прямоугольник n. Затем программа умножает n на n и присваивает результат переменной n2. Посмотрев в прямоугольник n, вы увидите, что в нем находится значение 5. Умножьте 5 на 5 и получите 2 5, после чего поместите 25 в прямоугольник n2. Чтобы воспроизвести следующий оператор С (n3 = n2 * n2;), загляните в прямоугольник n2; вы там найдете 25. Умножьте 25 на 25, получите 625 и поместите это значение в прямоугольник n2. Итак, вы возводите n2 в квадрат вместо того, чтобы умножить его на n.

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

Состояние программы

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


Введение в язык С 71

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 2.6. Трассировка программы


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

Мы обсудили только один метод отслеживания состояния: самостоятельное пошаговое выполнение программы. В программе, которая делает, скажем, 10 000 итераций, вы не справитесь с такой задачей. Но можно выполнить несколько итераций, чтобы узнать, делает ли программа то, что от нее ожидается. Тем не менее, всегда существует возможность, что вы выполните эти шаги, как задумали, а не так, как действительно реализовали их в программе, поэтому старайтесь неукоснительно придерживаться фактического кода.

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

Третий метод исследования состояний программы предусматривает применение отладчика. Отладчик — это программа, которая позволяет выполнять другую программу в пошаговом режиме и просматривать значения переменных в этой программе. Отладчики характеризуются различными уровнями удобства использования и сложности. Наиболее совершенные отладчики отображают строку исходного кода, выполняемую в текущий момент. Это особенно удобно при отладке программ с альтернативными путями выполнения, поскольку легко видеть, по какому конкретному пути продвигается выполнение. Если ваш компилятор снабжен отладчиком, уделите время на его изучение. Например, опробуйте его на программе, приведенной в листинге 2.4.

Ключевые слова и зарезервированные идентификаторы

Ключевые слова образуют словарь языка С. Поскольку они играют в С особую роль, их нельзя применять, например, в качестве идентификаторов либо имен переменных. Многие из этих ключевых слов описывают разнообразные типы данных, скажем, int.


72 Глава 2

Другие, такие как if, служат для управления порядком выполнения операторов программы. В приведенном ниже перечне ключевых слов языка полужирным выделены ключевые слова, добавленные стандартом С90, курсивом показаны ключевые слова, введенные стандартом С99, а полужирным курсивом — появившиеся в стандарте C11.

Ключевые слова ISO С

Язык программирования C. Лекции и упражнения. 6-е издание

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

Ключевые понятия

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

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

В этой главе основной целью должно быть понимание того, что собой представляет программа на языке С. Программу можно считать подготовленным вами описанием желаемого поведения компьютера. Компилятор выполняет по-настоящему кропотливую работу по преобразованию такого описания в базовый машинный язык. (Для того чтобы вы оценили, насколько огромную работу делает компилятор, отметим, что он создает исполняемый файл размером 60 Кбайт из исходного файла размером всего 1 Кбайт; для представления даже простой программы на С требуется большой объем кода на машинном языке.) Поскольку истинный интеллект у компилятора отсутствует, вы должны представить свое описание в понятных ему терминах, и эти термины


Введение в язык С 73

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

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

Резюме

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

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

Функция printf() может применяться для вывода фраз и значений переменных.

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

И, наконец, ключевые слова образуют словарь языка С.

Вопросы для самоконтроля

Ответы на эти вопросы находятся в приложении А.

1.  Как называются базовые модули программы на языке С?

2. Что такое синтаксическая ошибка? Приведите примеры синтаксической ошибки в контексте своего родного языка и языка С.

3. Что такое семантическая ошибка? Приведите примеры в контексте своего родного языка и языка С.

4. Джо из Индианы написал и представил вам на утверждение следующую программу. Помогите ему исправить ошибки.

Язык программирования C. Лекции и упражнения. 6-е издание



74 Глава 2

5.  Предположим, что каждый из приведенных ниже примеров является частью завершенной программы. Что выведет каждая такая часть?

а. printf ("Бе, бе, Черная Овечка .");

printf("У тебя найдется шерсть для меня?\n");

б. printf("Прочь!\nВот наглая свинья!\n");

в. printf ("Что?\nНе/nклюет?\n");

г. int num; num = 2;

printf ("%d + %d = %d", num, num, num + num);

6.    Какие из следующих слов являются ключевыми в С? main, int, function, char, =

7.  Как вывести значения переменных words и lines, чтобы они отобразились в следующей форме:

Текст содержал 3020 слов и 350 строк.

Здесь 3020 и 350 представляют значения этих двух переменных.

8.  Рассмотрим следующую программу:

Язык программирования C. Лекции и упражнения. 6-е издание


Каким будет состояние программы после выполнения строки 7? Строки 8? Строки 9? 9. Взгляните на следующую программу:

Язык программирования C. Лекции и упражнения. 6-е издание


Каким будет состояние программы после выполнения строки 7? Строки 8? Строки 9?

Упражнения по программированию

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


Введение в язык С 75

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

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

Иван Иванов   <-Первый оператор вывода

Иван          <-Второй оператор вывода

Иванов        <-По-прежнему второй оператор      вывода

Иван Иванов   <-Третий и четвертый операторы вывода

2.  Напишите программу, выводящую ваше имя и адрес.

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

4.  Напишите программу, которая производит следующий вывод:

Он веселый молодец!

Он веселый молодец!

Он веселый молодец!

Никто не может это отрицать!

Вдобавок к функции main() в программе должны использоваться две определенные пользователем функции: jolly(), которая выводит сообщение “Он веселый молодец!” один раз, и deny(), выводящая сообщение в последней строке.

5.  Напишите программу, которая производит следующий вывод:

Бразилия, Россия, Индия, Китай Индия, Китай,

Бразилия, Россия

Вдобавок к функции main() в программе должны использоваться две определенные пользователем функции: br(), выводящую строку “Бразилия, Россия” один раз, и 1с(), которая один раз выводит строку “Индия, Китай”. Функция main() должна позаботиться о любых дополнительных задачах вывода.

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

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

Улыбайся!Улыбайся!Улыбайся!

Улыбайся!Улыбайся!

Улыбайся!

В программе должна быть определена функция, которая отображает строку “Улыбайся!” один раз. Эта функция должна вызываться столько раз, сколько необходимо.


Глава 2

Язык программирования C. Лекции и упражнения. 6-е издание
8.  В языке С одна функция может вызывать другую. Напишите программу, которая вызывает функцию по имени one three(). Эта функция должна вывести слово “один” в одной строке, вызвать функцию two(), а затем вывести слово “три” тоже в одной строке. Функция two() должна отобразить слово “два” в одной строке. Функция main() должна вывести слово “начинаем:” перед вызовом функции one_three() и слово “порядок!” после ее вызова. Таким образом, выходные данные должны иметь следующий вид:

начинаем:

один

два

три

порядок!



Язык программирования C. Лекции и упражнения. 6-е издание


Данные в языке С

в этой ГЛАВЕ...

•     Ключевые слова:

•  int,short,long, unsigned, char, float, double, _Bool, _Complex, _Imaginary

•     Операция:

•  sizeof

•     Функция:

•  scanf()

•     Базовые типы данных в языке С

•     Различия между целочисленными данными и данными с плавающей запятой

•     Написание констант и объявление переменных известных типов

•     Использование функций printf() и scanf() для чтения и записи значений различных типов



78 Глава 3

П

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

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

Демонстрационная программа

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

Листинг 3.1. Программа platinum.с

Язык программирования C. Лекции и упражнения. 6-е издание


СОВЕТ. Сообщения об ошибках и предупреждения

Если вы введете код программы некорректно, скажем, пропустив точку с запятой, компилятор выдаст сообщение о синтаксической ошибке. Однако даже при правильном вводе программы компилятор может выдать предупреждение, подобное следующему: “Преобразование из double в float может привести к потере данных". Сообщение об ошибке означает, что вы сделали что-то неправильно, и программа компилироваться не будет. С другой стороны, предупреждение означает, что введенный код является допустимым, но может привести не к тому результату, который ожидался. Предупреждение не вызывает прекращение компиляции. Это конкретное предупреждение связано с тем, как в языке С обрабатываются числа, подобные 1700.0. В данном примере это не проблема, и позже в главе будет объяснен смысл такого предупреждения.


Данные в языке С 79

При вводе этой программы, возможно, потребуется заменить число 1700.0 текущей ценой платины. Однако не следует каким-либо образом изменять значение 14.5833, представляющее число тройских унций в одном фунте. (В качестве меры веса для драгоценных металлов используются тройские унции; для измерения веса всего остального применяются фунты.)

Обратите внимание, что ввод веса означает набор на клавиатуре числа, представляющего значение веса, и затем нажатие клавиши <Enter> или <Return>. Нажатие клавиши <Enter> информирует компьютер о завершении ввода. В программе предполагается, что для указания веса будет введено некоторое число, например, 15 6, а не слова вроде очень большой. Ввод букв вместо цифр вызывает проблемы, для устранения которых должен применяться оператор if (описанный в главе 7), поэтому вводите подходящее число. Ниже приведен пример вывода программы:

Хотите узнать свой вес в платиновом эквиваленте?

Давайте подсчитаем.

Пожалуйста, введите свой вес, выраженный в фунтах: 156 Ваш вес в платиновом эквиваленте составляет $3867491.25.

Вы легко можете стать достойным этого! Если цена платины падает, ешьте больше для поддержания своей стоимости.

Настройка программы

Если вывод программы быстро мелькает на экране, а затем исчезает даже после добавления строки getchar();, как было описано в главе 2, вызов этой функции нужно использовать дважды:

getchar(); getchar();

Функция getchar() считывает следующий введенный символ, поэтому программа вынуждена дожидаться ввода. В данном случае мы предоставили ввод, набрав число 156 и затем нажав клавишу <Enter> (или <Return>), что приводит к передаче символа новой строки. Таким образом, функция scanf() считывает число, первая функция getchar() считывает символ новой строки, а вторая функция getchar() вынуждает программу приостановить выполнение, дожидаясь дальнейшего ввода.

Что нового в этой программе?

В этой программе появилось несколько новых элементов языка С.

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

•   В программе демонстрируются новые способы записи констант. Теперь в роли констант выступают числа с десятичной точкой.

•   Для вывода значения переменной нового типа в функции printf() должен использоваться спецификатор %f. Модификатор .2 в спецификаторе %f служит для настройки внешнего вида вывода, так что после десятичной точки будут отображаться два знака.

•   Для ввода данных в программу с клавиатуры применяется функция scanf(). Спецификатор %f в scanf() означает, что с клавиатуры должно считываться число с плавающей запятой, a &weight — что введенное число будет присвоено переменной по имени weight. В функции scanf() используется амперсанд (&) для указания на то, где можно найти переменную weight. В следующей главе это рассматривается более подробно, а пока просто поверьте, что он здесь необходим.


Глава 3

Язык программирования C. Лекции и упражнения. 6-е издание
Язык программирования C. Лекции и упражнения. 6-е издание


• Вероятно, главной новой характеристикой этой программы является то, что она интерактивна. Компьютер запрашивает у вас информацию и затем задействует введенное вами число. Работать с интерактивными программами намного интереснее, чем с их неинтерактивными разновидностями. Но важнее то, что интерактивный подход делает программы более гибкими. Например, показанная выше демонстрационная программа может применяться для пересчета любого разумного веса, а не только 156 фунтов. Такую программу не придется переписывать каждый раз, когда она потребуется новому пользователю. Эта интерактивность обеспечивается функциями scanf() и printf(). Функция scanf() читает данные с клавиатуры и делает их доступными в программе, а функция printf() принимает данные от программы и выводит их на экран. Вместе эти две функции позволяют установить двухсторонний обмен данными с компьютером (рис. 3.1), что делает работу с компьютером гораздо более увлекательной.





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

Переменные и константы

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


Данные в языке С 81

нения программы и их значения сохраняются неизменными в течение всего времени ее работы. Такие данные называются константами. Другие виды данных могут изменяться в ходе выполнения программы. Они называются переменными. В приведенной выше демонстрационной программе weight является переменной, а 14.5833 — константой. А что можно сказать о числе 177 0.0? Конечно, в реальности цена на платину не является постоянной величиной, но в этой программе она считается константой. Различие между переменной и константой состоит в том, что переменной можно присваивать значение либо изменять его во время выполнения, а с константой так поступать нельзя.

Ключевые слова для типов данных

Помимо отличий между переменными и константами, существует также разница между разных типами данных. Одни данные являются числами. Другие данные представляют собой буквы или, в общем случае, символы. Компьютеру необходим способ идентификации и использования этих разных видов данных. В языке С для этого предусмотрено несколько базовых типов данных. Если данные представляют собой константы, то обычно компилятор может выяснить их тип по внешнему виду: 42 — это целое число, а 42.100 — число с плавающей запятой. Тем не менее, тип переменной должен быть указан в операторе объявления. Позже вы узнаете, как объявлять переменные, но сначала давайте рассмотрим ключевые слова для базовых типов данных, распознаваемые языком С. В стандарте K&R С существовало семь ключевых слов, относящихся к типам. В стандарте С90 к этому списку были добавлены два ключевых слова. В стандарте С99 список пополнился еще тремя ключевыми словами (табл. 3.1).

Таблица 3.1. Ключевые слова для типов данных в языке С

Язык программирования C. Лекции и упражнения. 6-е издание


Ключевым словом int обозначается основной класс целых чисел, применяемых в С. Следующие три ключевых слова (long, short и unsigned) и добавленное стандартом С90 ключевое слово signed используются для указания вариаций этого базового типа, например, unsigned short int и long long int. С помощью ключевого слова char определяются символьные данные, к которым относятся буквы алфавита и другие символы, такие как #, $, % и *. Тип данных char можно также применять для представления небольших целых чисел. Типы float, double и long double служат для представления чисел с плавающей запятой. Тип данных Bool используется для булевских значений (true и false), а типы данных Complex и Imaginary представляют, соответственно, комплексные и мнимые числа.


82 Глава 3

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

Биты, байты и слова

Для описания элементов компьютерных данных или элементов компьютерной памяти могут применяться термины бит, байт и слово. Второму термину уделяется основное внимание.

Минимальная единица памяти называется битом, который может хранить одно из двух значений: 0 или 1. (Иногда говорят, что бит “включен” или “выключен”.) Конечно, водном бите много информации сохранить не получится, но в компьютере их имеется огромное количество. Бит является базовым строительным блоком для памяти компьютера.

Байт — это наиболее часто используемая единица памяти компьютера. Практически на всех машинах байт состоит из 8 битов, и это является стандартным определением байта, по крайней мере, когда речь идет об измерении объема памяти. (Однако, как будет показано в разделе “Использование символов: тип char" далее в главе, в языке С имеется другое определение.) Поскольку бит может принимать значение 0 или 1, байт обеспечивает 256 (т.е. 28) возможных комбинаций нулей и единиц. Эти комбинации могут использоваться, например, для представления целых чисел от 0 до 255 или набора символов. Числа можно записывать посредством двоичного кода, в котором для представления чисел применяются только нули и единицы. (Двоичный код подробно рассматривается в главе 15, и при желании можете ознакомиться с начальными сведениями из указанной главы прямо сейчас.)

Слово — это естественная единица памяти для компьютера конкретного типа. В 8-разрядных микрокомпьютерах, таких как первые машины Apple, слово состояло из 8 битов. С тех пор персональные компьютеры перешли на 16-битные, 32-битные, а в настоящее время и 64-битные слова. Большие размеры слова позволяют быстрее передавать данные и делают доступным больший объем памяти.

Сравнение целочисленных типов и типов с плавающей запятой

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

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

Целые числа

Целое число — это число без дробной части. В языке С целое число никогда не записывается с десятичной точкой, например, 2, -23 и 2456. Числа наподобие 3.14, 0.22 и 2.000 целыми не являются. Целые числа хранятся в двоичной форме. Например, целое число 7 записывается в двоичной форме как 111. Следовательно, чтобы сохранить это число в 8-битном байте, нужно просто установить первые 5 битов в 0, а последние три бита — в 1 (рис. 3.2).


Язык программирования C. Лекции и упражнения. 6-е издание
Язык программирования C. Лекции и упражнения. 6-е издание


Данные в языке С





Числа с плавающей запятой

Число с плавающей запятой более или менее соответствует тому, что математики называют вещественным числам. К вещественным числам относятся числа, находящиеся в промежутках между целыми числами. Примерами чисел с плавающей запятой могут служить 2.75, 3.16Е7, 7.00 и 2е-8. Обратите внимание, что добавление десятичной точки превращает целое число в число с плавающей запятой. Таким образом, 7 имеет целочисленный тип, но 7.00 — тип с плавающей запятой. Очевидно, что существует более одной формы записи числа с плавающей запятой. Более подробно экспоненциальную форму записи чисел мы обсудим позже, но если кратко, то запись 3.16Е7 означает, что число 3.16 необходимо умножить на 107, т.е. на число, состоящее из единицы с последующими семью нулями. Число 7 называется порядкам числа 10.

Ключевым моментом здесь является то, что схема, используемая для хранения числа с плавающей запятой, отличается от схемы, которая применяется для хранения целого числа. Число с плавающей запятой разделяется на дробную часть и порядок, которые хранятся отдельно. Таким образом, 7.00 будет храниться в памяти не в том виде, в каком хранится целое число 7, хотя оба они имеют одно и то же значение. Десятичным аналогом записи 7.00 может быть 0.7Е1. Здесь 0.7 — дробная часть числа, а 1 — порядок. На рис. 3.3 показан еще один пример хранения числа с плавающей запятой. Разумеется, для хранения компьютер использует двоичные числа и степени 2, а не степени 10. Дополнительный материал по этой теме вы найдете в главе 15. А теперь сосредоточим внимание на практических различиях.

•   Целое число не имеет дробной части; число с плавающей запятой может иметь дробную часть.

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

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

•   Поскольку в любом диапазоне чисел имеется бесконечное количество вещественных чисел, например, в диапазоне между 1.0 и 2.0, применяемые в компьютере числа с плавающей запятой не могут представить все числа этого диапазона. Числа с плавающей запятой часто являются приближениями настоящих значений. Например, 7.0 может быть сохранено как значение с плавающей запятой 6.99999 (вопросы точности более подробно рассматриваются позже).

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


84 Глава 3

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 3.3. Хранение числа Pi в формате числа с плавающей запятой (десятичная версия)


Базовые типы данных языка с

Давайте взглянем на особенности базовых типов данных, используемых в языке С. Для каждого типа мы покажем, как объявлять переменные и представлять константы с фиксированными значениями наподобие 5 или 2.78, а также продемонстрируем типичные случаи их применения. Некоторые старые компиляторы С поддерживают не все эти типы данных, поэтому выясните в документации, какие типы данных доступны в компиляторе.

ТИП int

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

Тип int nредставляет целое число со знаком. Это значит, что оно должно быть целым и может иметь положительную, отрицательную или нулевую величину. Диапазон возможных значений зависит от компьютерной системы. Обычно для хранения данных типа int используется одно машинное слово. Поэтому в компьютерах, совместимых со старыми моделями IBM PC с 16-битными словами, для хранения данных типа int выделялось 16 битов. Это позволяло иметь диапазон значений от -32 7 68 до 327 67. Современные персональные компьютеры обычно оперируют 32-битными целыми числами и данные типа int соответствуют такому размеру. В настоящее время индустрия персональных компьютеров сориентировалась на выпуск 64-разрядных процессоров, которые могут свободно манипулировать еще большими целыми числами. В стандарте ISO С указано, что минимальным диапазоном для типа int должен быть от -32767 до 32767. Обычно системы представляют целые числа со знаком за счет использования значения определенного бита. Распространенные способы представления рассматриваются в главе 15.

Объявление переменной типа int

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

int erns;

int hogs, cows, goats;


Данные в языке С 85

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

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

cows = 112;

Второй способ предусматривает получение переменной значения из функции, например, из scanf(). А теперь рассмотрим третий способ.

Инициализация переменных

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

int hogs =21;

int cows = 32, goats = 14;

int dogs, cats = 94;    /* допустимая, но неудачная форма */

В последней строке инициализируется только переменная cats. Однако по невнимательности может показаться, что переменная dogs также инициализируется значением 94, поэтому лучше избегать использования в одном операторе объявления инициализированных и неинициализированных переменных.

Выражаясь кратко, эти объявления выделяют и помечают для переменных области хранения, а также присваивают им начальные значения (рис. 3.4).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 3.4. Определение и инициализация переменной


Константы типа int

Различные целые числа (21, 32, 14 и 94) в последнем примере являются целочисленными константами, также называемыми целочисленными литералами. Когда вы записываете число без десятичной точки и порядка, компилятор С распознает его как целое. Следовательно, числа 22 и -44 являются целочисленными константами, а числа 22 . О и 2.2Е1 — нет. Большинство целочисленных констант трактуются в С как принадлежащие к типу int. Очень большие целые числа могут трактоваться иначе; в разделе “Константы long и long long” далее в главе рассматриваются данные типа long int.


86 Глава 3

Вывод значений типа int

Для вывода значений типа int можно применять функцию printf(). Как уже упоминалось в главе 2, конструкция %d служит для указания в строке места, где будет выводиться целое число. Конструкция %d называется спецификатором формата, поскольку она определяет формат, используемый функцией printf() для отображения конкретного значения. Каждому спецификатору %d в строке формата должно соответствовать значение int в списке выводимых элементов. Таким значением может быть переменная int, константа int или любое другое выражение int. Программист должен следить за тем, чтобы количество спецификаторов формата соответствовало числу значений, потому что компилятор не обнаруживает ошибки подобного рода. В листинге 3.2 представлена простая программа, которая инициализирует переменную, а затем выводит значение этой переменной, значение константы и значение простого выражения. Вдобавок она демонстрирует, что происходит в случае невнимательности.

Язык программирования C. Лекции и упражнения. 6-е издание


Компиляция и запуск этой программы ведет к получению следующего вывода:

В первой строке вывода первый спецификатор %d представляет переменную ten типа int, второй — константу 2 типа int и третий — значение выражения ten - two типа int. Однако во второй строке переменная ten применяется для предоставления значения только первому спецификатору %d, а для последующих двух спецификаторов %d значений не предусмотрено, поэтому программа использует для них случайные значения, находящиеся в памяти! (На своем компьютере вы можете получить результат, сильно отличающийся от полученного в этом примере. Может отличаться не только содержимое памяти, но также разные компиляторы будут управлять ячейками памяти по-разному.)

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


Данные в языке С 87

Восьмеричные и шестнадцатеричные числа

Обычно в языке С предполагается, что целочисленные константы являются десятичными числами (по основанию 10). Однако у многих программистов пользуются популярностью восьмеричные (по основанию 8) и шестнадцатеричные (по основанию 16) числа. Поскольку 8 и 16 представляют собой степени числа 2, а 10 — нет, восьмеричная и шестнадцатеричная системы счисления более удобны для представления чисел, связанных с компьютерами. Например, число 65536, которое часто встречается в 16-разрядных машинах, в шестнадцатеричной форме записывается как 10000. Кроме того, каждая цифра шестнадцатеричного числа соответствует в точности 4 битам. Например, шестнадцатеричная цифра 3 — это 0011, а шестнадцатеричная цифра 5 — это 0101. Таким образом, шестнадцатеричному значению 35 соответствует битовая комбинация 0011 0101, а шестнадцатеричному значению 53 — 0101 0011. Такое соответствие позволяет облегчить переход от шестнадцатеричного представления числа к двоичному представлению (по основанию 2) и обратно. Но каким образом компьютер может определить, в какой форме записано число 10000 — в десятичной, шестнадцатеричной или восьмеричной? В языке С система счисления задается с помощью специального префикса. Префикс 0x или 0x означает, что вы указываете шестнадцатеричное число, поэтому 16 в шестнадцатеричном виде записывается как 0x10 или 0X10. Аналогично, префикс 0 (ноль) означает, что задается восьмеричное число. Например, в С десятичное число 16 в восьмеричном виде записывается как 020. Более подробно эти альтернативные системы рассматриваются в главе 15. Вы должны понимать, что возможность применения разных систем счисления является лишь удобством для программистов. Это не влияет на способ хранения числа в памяти. Другими словами, вы можете написать 16, 020 или 0x10, и это число в каждом случае будет храниться в памяти одинаковым образом — в двоичном коде, используемом внутри компьютера.

Отображение восьмеричных и шестнадцатеричных чисел

Язык С позволяет не только записывать число в любой из трех систем счисления, но и отображать его во всех них. Чтобы вывести на экран целое число в восьмеричном, а не десятичном виде, вместо %d применяйте спецификатор %o. Для отображения целого числа в шестнадцатеричном виде служит спецификатор %х. Если вы хотите вывести на экран префиксы языка С, воспользуйтесь спецификаторами %#о, %#х и %#Х, которые позволяют отображать префиксы 0, 0x и 0x. В листинге 3.3 приведен небольшой пример. (Вспомните, что в некоторых интегрированных средах разработки может потребоваться вставить в программу оператор getchar();, чтобы предотвратить немедленное закрытие окна выполнения программы.)

Язык программирования C. Лекции и упражнения. 6-е издание


После компиляции и выполнения этой программы получен следующий вывод:


88 Глава 3

Одно и то же значение отображается в трех различных системах счисления. Все преобразования выполняет функция printf(). Обратите внимание, что префиксы О или 0x не отображаются в выводе до тех пор, пока в спецификаторе не будет указан символ #.

Другие целочисленные типы

Если вы просто изучаете язык С, то в большинстве случаев вам, скорее всего, вполне достаточно будет типа int. Однако для полноты картины мы рассмотрим и другие формы целых чисел. При желании вы можете пропустить этот раздел и перейти к обсуждению типа char в разделе “Использование символов: тип char”, а затем возвратиться к данному разделу в случае необходимости.

В языке С применяются три ключевых слова, модифицирующих базовый целочисленный тип: short, long и unsigned. Примите во внимание следующие аспекты.

•   Тип short int, или short, может использовать меньший объем памяти, чем int, и тем самым экономить память в случае, когда требуются только небольшие числа. Подобно int, short является типом со знаком.

•   Тип long int, или long, может занимать больший объем памяти, чем int, позволяя представлять крупные целочисленные значения. Подобно int, long является типом со знаком.

•   Тип long long int, или long long (введен стандартом С99), может занимать больше памяти, чем long. Для этого типа используются минимум 64 бита. Подобно int, long long является типом со знаком.

•   Тип unsigned int, или unsigned, применяется для переменных, которые принимают только неотрицательные значения. Этот тип сдвигает диапазон хранимых чисел. Например, 16-битный тип unsigned int имеет диапазон значений от 0 до 65535 вместо диапазона от -32768 до 32767. Бит, который использовался для представления знака, теперь становится еще одной двоичной цифрой, делая возможным представление большего числа.

•   Типы unsigned long int, или unsigned long, и unsigned short int, или unsigned short, распознаются как допустимые стандартом С90. В стандарте С99 к ним добавлен тип unsigned long long int, или unsigned long long.

•   Ключевое слово signed может применяться с любыми типами со знаком, чтобы явно указать свое намерение. Например, short, short int, signed short и signed short int являются именами одного и того же типа.

Объявление переменных других целочисленных типов

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

long int estine; long johns; short int erns; short ribs; unsigned int s_count; unsigned players; unsigned long headcount; unsigned short yesvotes; long long ago;


Данные в языке С 89

Почему доступно так много целочисленных типов?

Почему мы говорим, что типы long и short “могут” использовать больше или меньше памяти, чем int? Дело в том, что язык С гарантирует только то, что тип short не будет длиннее типа int, а тип long не будет короче типа int. Идея заключается в том, чтобы согласовать типы с конкретной машиной. Например, во времена операционной системы Windows 3 типы int и short были 16-битными, a long — 32-битным. Позже системы Windows и Apple перешли на использование 16 битов для типа short и 32 битов для типов int и long. Применение 32 битов расширяет диапазон допустимых целых чисел до более 2 миллиардов. В настоящее время, когда распространенными стали 64-разрядные процессоры, возникла потребность в 64-битных целых числах, что и стало причиной появления типа long long. Чаще всего в наши дни тип long long устанавливается как 64-битный, long — 32-битный, short — 16-битный, а int — либо 16-битный, либо 32-битный, в зависимости от естественного размера машинного слова. В принципе эти четыре типа могли бы представлять четыре разных размера, но на практике, по меньшей мере, некоторые из них перекрываются.

В стандарте языка С предоставлены указания по минимально допустимому размеру для каждого базового типа данных. Минимальный диапазон значений для типов short и int составляет от -32 767 до 32 767, соответствуя 16-битной единице памяти, а минимальный диапазон для типа long — от -2 147 483 647 до 2 147 483 647, что соответствует 32-битной единице. Для типов unsigned short и unsigned int минимальный диапазон охватывает числа от 0 до 65 535, а для типа unsigned long он находится в пределах от 0 до 4 294 967 295. Тип long long предназначен для поддержки 64-битных данных. Его минимальный диапазон довольно внушителен и простирается от -9 223 372 036 854 775 807 до 9 223 372 036 854 775 807. Минимальный диапазон для типа unsigned long long охватывает числа от 0 до 18 446 744 073 709 551 615.

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

Тип long должен использоваться, когда необходимы числа, которые он позволяет поддерживать, а int — нет. Тем не менее, в системах, где тип long длиннее int, применение типа long может замедлить вычисления, поэтому его не стоит использовать без крайней необходимости. Еще один момент: если вы пишете код для машины, на которой типы int и long имеют один и тот же размер, а вам нужны 32-битные целые числа, то выбирайте тип long, а не int, чтобы программа функционировала корректно в случае переноса на 16-разрядную машину. Аналогично, применяйте тип long long, если требуются 64-битные целочисленные значения.

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

Целочисленное переполнение

Что произойдет, если целое число окажется больше, чем допускает выбранный для него тип? Давайте присвоим целочисленной переменной максимально возможное целое значение, добавим к нему еще какое-то целое число и посмотрим, к чему это приведет. Мы выполним это действие над типами со знаком и без знака. (В вызове функции printf() для отображения значений типа unsigned int nрименяется спецификатор %u.)


90 Глава 3

Язык программирования C. Лекции и упражнения. 6-е издание


В нашей системе был получен следующий результат:

2147483647 -2147483648 -2147483647 4294967295 0 1

Целочисленная переменная без знака j действует как счетчик пробега автомобиля. Когда достигается максимальное значение, оно сбрасывается, и подсчет начинается с начала. Целочисленная переменная i ведет себя аналогично. Главное различие между ними заключается в том, что значения переменной j типа unsigned int, подобно счетчику пробега, начинаются с 0, в то время как значения переменной 1 типа int — с -2 147 483 648. Обратите внимание, что о превышении максимального значения (переполнении) переменной i ничего не сообщается. Чтобы отслеживать это, вам придется самостоятельно предусмотреть подходящий код.

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

Константы long и long long

Обычно, когда в коде программы используется число вроде 2345, оно хранится в памяти как относящееся к типу int. А что произойдет, если указать число, такое как 1 000 000, в системе, где тип int не способен хранить настолько большое значение? В этом случае компилятор трактует его как число типа long int, предполагая, что этого типа окажется достаточно. Если число превосходит максимально возможное значение типа long, оно будет рассматриваться как значение типа unsigned long. Если и этого не достаточно, оно интерпретируется как значение типа long long или unsigned long long, если данные типы доступны.

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

Иногда необходимо, чтобы компилятор сохранил небольшое число как целое значение типа long. Например, это может потребоваться при явном использовании в коде адресов памяти в IBM PC. Кроме того, некоторые стандартные функции С требуют значений типа long. Чтобы небольшая константа интерпретировалась как значение типа long, к ней можно дописать букву 1 (строчная буква L) или L. Вторая форма предпочтительнее, поскольку она не выглядит похожей на цифру 1. Следовательно, система с 16-битным типом int и 32-битным типом long трактует целое число 7 как 16-битное, а целое число 7L — как 32-битное. Суффиксы 1 и L можно также применять с восьмеричными и шестнадцатеричными числами, например, 020L и 0xlOL. Аналогично, в системах, поддерживающих тип long long, можно использовать суффикC11 или LL для указания значения типа long long, например, 3LL. Чтобы задать тип unsigned long long, добавьте к суффиксу букву и или U, как в bull, 10LLU, 6LLU и 9U11.


Данные в языке С 91

Вывод значений типов short, long, long long и unsigned

Для вывода чисел типа unsigned int nрименяйте спецификатор %u. Чтобы вывести значение типа long, используйте спецификатор формата %ld. Если типы int и long в вашей системе имеют один и тот же размер, вполне достаточно спецификатора %d, однако ваша программа не будет корректно работать при переносе в систему, где эти два типа обладают разными размерами, поэтому для long лучше применять спецификатор %ld. Вместе с префиксами х и о можно также указывать префикс 1. Таким образом, вы можете использовать спецификатор %1х для вывода целого числа типа long в шестнадцатеричном формате и спецификатор %1о — для его вывода в восьмеричном формате. Обратите внимание, что хотя язык С позволяет применять в качестве суффиксов констант и прописные, и строчные буквы, в этих спецификаторах формата используются только строчные буквы.

В языке С доступны дополнительные форматы для printf(). Первым делом, можно применять префикс h для значений типа short. Следовательно, спецификатор %hd отображает целое число типа short в десятичной форме, а спецификатор %ho отображает это же число в восьмеричной форме. Префиксы h и 1 можно использовать вместе с префиксом и для типов без знака. Например, для вывода значений типов unsigned long можно было бы указать %lu. В листинге 3.4 приведен пример. В системах, поддерживающих типы long long, для версий со знаком и без знака применяются спецификаторы %lld и %llu. Более полное обсуждение спецификаторов формата можно найти в главе 4.

Листинг 3.4. Программа print2 .с

Язык программирования C. Лекции и упражнения. 6-е издание


Ниже показан вывод в конкретной системе (результаты могут варьироваться):

un = 3000000000, но не -1294967296

end = 200 и 200

big = 65537, но не 1

verybig = 12345678908642, но не 1942899938

Этот пример демонстрирует, что использование неправильных спецификаторов может привести к неожиданным результатам. Прежде всего, обратите внимание, что применение спецификатора %d для беззнаковой переменной un выдает отрицательное число! Причина в том, что значение 3 000 000 000 без знака и значение -129 496 296 со знаком имеют одно и то же внутреннее представление в памяти нашей системы. (В главе 15 это свойство объясняется более подробно.) Таким образом, если указать функции printf(), что значение является числом без знака, она выведет одно зна-


92 Глава 3 чепие, а если указать, что значение представляет собой число со знаком, то другое значение. Подобное поведение происходит для значений, которые превышают максимально допустимое значение для типа со знаком. Небольшие положительные значения, такие как 96, сохраняются и отображаются одинаково как для типов со знаком, так и для типов без знака.

Далее отметим, что переменная end типа short отображается одинаково независимо от того, указываете вы функции printf() принадлежность end к типу short (спецификатор %hd) или к типу int (спецификатор %d). Это объясняется тем, что при передаче аргумента функции С значение типа short автоматически расширяется до типа int. Здесь могут возникнуть два вопроса: почему предпринимается указанное преобразование, и для чего используется модификатор h? Ответ на первый вопрос прост: для типа int выбирался такой размер, чтобы обеспечить наиболее эффективную его обработку компьютером. Следовательно, на компьютере, в котором типы short и int имеют разные размеры, передача значения как int может осуществляться быстрее. Ответ на второй вопрос выглядит так: модификатор h можно применять, чтобы продемонстрировать, какой вид примет целое значение, будучи усеченным до типа short. Иллюстрацией этого утверждения может служить третья строка вывода. Число 65537, записанное в двоичном формате как 32-битное число, имеет вид 00000000000000010000000000000001. С помощью спецификатора %hd мы заставляем функцию printf() просматривать только последние 16 битов числа, поэтому она отображает в результате 1. Аналогично, финальная строка вывода показывает полное значение verybig, после чего это значение сохраняется в последних 32 битах, на что указывает спецификатор %ld.

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

СОВЕТ. Соответствие типов и спецификаторов в printf()

Не забывайте проверять, что для каждого отображаемого значения в операторе printf()

предусмотрен один спецификатор формата. Кроме того, проверяйте, что тип каждого спецификатора формата соответствует типу отображаемого значения.

Использование символов: тип char

Тип данных char применяется для хранения символов, таких как буквы и знаки препинания, однако формально он также является целочисленным. Почему? Причина в том, что тип char в действительности хранит целые числа, а не символы. Для поддержки символов компьютер использует числовой код, в котором определенные целые числа представляют определенные символы. В США наиболее часто применяется код ASCII (приложение В), и он как раз принят в настоящей книге. К примеру, целое значение 65 в нем представляет прописную букву A. Таким образом, чтобы сохранить букву А, фактически нужно записать целое число 65. (Во многих мэйнфреймах IBM используется другой код, EBCDIC, но принцип остается тем же. В компьютерных системах, эксплуатируемых в других странах, могут применяться совершенно другие коды.)

Стандартный код ASCII состоит из последовательности чисел от 0 до 127. Этот диапазон достаточно мал, чтобы для значения хватило 7 битов. Тип char обычно определяется как 8-битная единица памяти, поэтому ее более чем достаточно, чтобы уместить стандартный код ASCII. Во многих системах, таких как IBM PC и Apple Macintosh, используются расширенные коды ASCII (разные для этих двух систем), которые по-прежнему не выходят за пределы 8 битов. В общем случае язык С гаранти-


Данные в языке С 93

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

Многие наборы символов содержат более 127 или даже 255 значений. Например, существует набор символов Japanese kanji для японских иероглифов. В рамках коммерческой инициативы Unicode был создана система для представления широкого разнообразия наборов символов, применяемых в различных частях мира, которая в настоящее время содержит более 110 000 символов. Организация ISO и комиссия IEC (International Electrotechnical Commission — Международная электротехническая комиссия) вместе разработали для наборов символов стандарт, получивший название ISO/IF.C 10646. К счастью, стандарт Unicode сохранил совместимость с более широким стандартом ISO/IF.C 10646.

Язык С определяет байт как несколько битов, используемых типом char, поэтому может быть система с 16- или 32-битным байтом и типом char.

Объявление переменных типа char

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

char response; char itable, latan;

В этом коде создаются три переменных типа char: response, itable и latan.

Символьные константы и инициализация

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

char grade = 'А';

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

char broiled; /* объявление переменной типа char                    */

broiled = 'Т'; /* правильно                                         */

broiled = Т; /* Неправильно! Компилятор считает, что Т является переменной */ broiled = "Т"; /* Неправильно! Компилятор считает, что "Т" является строкой */

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

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

char grade = 65; /* правильно в контексте ASCII, но стиль неудачен */

В данном примере 65 имеет тип int, но поскольку это значение меньше максимального значения типа char, оно может быть присвоено переменной grade без каких-либо проблем. Так как 65 представляет собой ASCII-код буквы A, в этом примере переменной grade присваивается значение А. Тем не менее, обратите внимание, что в примере предполагается использование в системе кодировки ASCII. Указание 'А' вместо 6 5 дает в результате код, который работает в любой системе. Таким образом, применять символьные константы намного лучше, чем значения числовых кодов.


94 Глава 3

Несколько странно, однако С трактует символьные константы как тип int, а не char. Например, в системе с 32-битным типом int и с 8-битным типом char следующий код представляет ' В' как числовое значение 66, хранящееся в 32-битной единице памяти, но переменная grade в итоге получает значение 66 в 8-битной единице памяти:

char grade = 'В';

Эта характеристика символьных констант делает возможным определение символьной константы вида 'FATE', с четырьмя отдельными 8-битными ASCII-кодами, хранящимися в 32-битной единице памяти. Тем не менее, попытка присвоить такую символьную константу переменной типа char приводит к тому, что используются только последние 8 битов, так что переменная получает значение ' Е '.

Непечатаемые символы

Прием с одиночными кавычками хорош для символов, цифр и знаков препинания, однако если просмотреть таблицу кодов ASCII, в ней можно обнаружить также непечатаемые символы. Например, некоторые из них представляют собой такие действия, как возврат на одну позицию влево, переход на следующую строку или выдачу звукового сигнала терминалом либо встроенным динамиком. Как их можно представить? В языке С предлагаются три способа. Первый способ уже упоминался — применение ASCII-кода. Например, ASCII-кодом для символа звукового сигнала является 7, так что можно использовать следующий оператор:

char beep = 7;

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

Таблица 3.2. Управляющие последовательности

Язык программирования C. Лекции и упражнения. 6-е издание



Данные в языке С 95

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

char nerf = '\n';

а затем вывести переменную nerf, что обеспечит перемещение на следующую строку на принтере или на экране монитора.

Теперь давайте более пристально взглянем, что делает каждая управляющая последовательность. Символ предупреждения (\а), введенный стандартом С90, вызывает появление звукового или визуального предупреждающего сигнала. Природа предупреждающего сигнала зависит от оборудования; чаще других используется звуковой сигнал. (В некоторых системах предупреждающий символ не оказывает никакого действия.) Стандарт С требует, чтобы предупреждающий сигнал не изменял активную позицию. Под активной позицией в стандарте понимается место в устройстве отображения (экран, телетайп, принтер и т.д.), в котором иначе появился бы следующий символ. Выражаясь кратко, активная позиция — это обобщение понятия экранного курсора, с которым вы наверняка хорошо знакомы. Применение предупреждающего символа в программе, выводящей на экран, должно вызвать звуковой сигнал без перемещения экранного курсора.

Управляющие последовательности \b, \f, \n, \r, \t и \v представляют собой обычные символы управления выходным устройством. Их проще всего описывать в терминах того, как они влияют на активную позицию. Символ возврата на одну позицию влево (\b) перемещает активную позицию назад на один символ текущей строки. Символ перевода страницы (\f) переносит активную позицию в начало следующей страницы. Символ новой строки (\n) перемещает активную позицию в начало следующей строки. Символ возврата каретки (\r) переносит активную позицию в начало текущей строки. Символ горизонтальной табуляции (\t) перемещает активную позицию в следующую точку горизонтальной табуляции (обычно эти точки находятся в позициях 1,9, 17, 25 и т.д.). Символ вертикальной табуляции (\v) переносит активную позицию в следующую точку вертикальной табуляции.

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

Следующие три управляющие последовательности (\\, \' и \") обеспечивают возможность использования символов \, ' и " в качестве символьных констант. (Поскольку эти символы служат для определения символьных констант как части команды printf(), буквальное их указание может вызвать путаницу.) Предположим, что вы хотите вывести следующую строку:

Джо сказал: "символ \ является символом обратной косой черты."

Необходимо использовать такой код:

printf("Джо сказал: \"символ \\ является символом обратной косой черты.\"\n");

Две последних формы (\0оо и \xhh) — это специальные представления ASCII-кода. Чтобы представить символ его восьмеричным ASCII-кодом, предварите код обратной косой чертой (\) и поместите всю конструкцию в одиночные кавычки. Например, если ваш компилятор не распознает символ предупреждения (\а), вы можете воспользоваться его ASCII-кодом:

beep = '\0 0 7 ';


96 Глава 3

Ведущие нули можно не указывать, так что запись ‘\07 ' или даже ‘\ 7 ' будет правильной. Эта запись вызывает интерпретацию чисел в качестве восьмеричных, даже при отсутствии начального 0.

Начиная со стандарта С90, в С доступна и третья возможность — применение шестнадцатеричной формы для символьных констант. В этом случае за символом обратной косой черты следует символ х или X и от одной до трех шестнадцатеричных цифр. Например, символу <Ctrl+P> соответствует шестнадцатеричный ASCII-код 10 (16 в десятичной форме), следовательно, его можно выразить как ‘\х10’ или ‘\X010'.

На рис. 3.5 приведены примеры целочисленных констант.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 3.5. Виды запит целочисленных констант семейства int


При использовании кода ASCII обращайте внимание на различие между числами и символами чисел. Например, символ 4 представлен в коде ASCII значением 52. Запись ‘4 ' представляет символ 4, но не числовое значение 4.

На этом этапе у вас могут возникнуть три вопроса.

•   Почему в последнем примере управляющие последовательности не заключены в одиночные кавычки (printf("Джо сказал: \"символ \\ является символом обратной косой черты. \ "\n"); )? Когда символ, будь он управляющей последовательностью или нет, является частью строки символов, которая заключена в двойные кавычки, не помещайте его в одиночные кавычки. Обратите внимание, что ни один из символов, использованных в этом примере (Д, ж, о и т.д.), не заключен в одиночные кавычки. Строка символов, помещенная в двойные кавычки, называется символьной строкой. (Строки рассматриваются в главе 4.) Аналогично, оператор printf ("Здравствуй, мир!\007\n" ); выведет строку Здравствуй, мир! и вызовет выдачу звукового сигнала, а оператор printf ("Здравствуй, мир! 7\n"); выведет строку Здравствуй, мир! 7. Цифры, не являющиеся частью управляющей последовательности, считаются обычными символами, подлежащими выводу.

•   Когда должен использоваться ASCII-код, а когда — управляющие последовательности? Если у вас есть возможность выбора между применением одной из специальных управляющих последовательностей, скажем '\f', и эквивалентного ASCII-кода, например, '\014', отдавайте предпочтение '\f'. Во-первых, при таком представлении легче понять смысл. Во-вторых, такая запись обладает лучшей переносимостью. Если вы работаете с системой, в которой не используется код ASCII, последовательность ‘\ f' по-прежнему будет работать.


Данные в языке С 97

• Если нужно применять цифровой код, почему необходимо указывать, скажем,

‘\032 ', а не 032? Во-первых, использование записи ‘\032 ' вместо 032 позволит другому программисту, читающему код, понять, что вы намереваетесь представить код символа. Во-вторых, управляющая последовательность, такая как \ 0 3 2, может быть встроена в часть строки С тем же способом, что и \ 0 0 7 в первом вопросе.

Печатаемые символы

Для указания на то, что должен быть выведен символ, в функции printf() используется спецификатор %с. Вспомните, что символьная переменная хранится как однобайтовое целочисленное значение. Следовательно, при выводе значения переменной типа char с обычным спецификатором %d будет получено целое число. Спецификатор формата %с сообщает функции printf() о необходимости отобразить символ с кодовым значением, равным этому целому числу. В листинге 3.5 приведен код, в котором переменная char выводится обоими способами.

Листинг 3.5. Программа charcode.c

Язык программирования C. Лекции и упражнения. 6-е издание


Вот пример выполнения этой программы:

Введите какой-нибудь символ.

С

Код символа С равен 67.

При работе с программой не забывайте нажимать клавишу <Enter> или <Return> после ввода символа. Функция scanf() затем извлекает символ, введенный с клавиатуры, а амперсанд (&) означает, что этот символ присваивается переменной ch. Далее с помощью функции printf() значение переменной ch выводится два раза, сначала как символ (на что указывает спецификатор %с), а потом как десятичное целое число (на что указывает спецификатор %d). Обратите внимание, что спецификаторы функции printf() определяют способ отображения данных, но не то, как они хранятся в памяти (рис. 3.6).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 3.6. Опюбражение данны х на экране н их хранение в памяти



98 Глава 3

Со знаком или без знака?

В некоторых реализациях С тип char является типом со знаком. Это значит, что переменная типа char может принимать значения из диапазона от -128 до 127. В других реализациях тип char сделан беззнаковым и может иметь значения из диапазона от 0 до 255. В описании вашего компилятора должно быть явно указано, к какой разновидности принадлежит тип char, либо вы это можете узнать, заглянув в заголовочный файл limits.h, который рассматривается в следующей главе.

Согласно стандарту С90, язык С позволяет использовать ключевые слова signed и unsigned с типом char. Затем, независимо от того, какими являются данные типа char по умолчанию, тип signed char будет со знаком, а тип unsigned char — без знака. Такие версии типа char удобны, если этот тип применяется для обработки небольших целых чисел. Для собственно символов используйте стандартный тип char без модификаторов.

Тип _Bool

Тип _Bool, появившийся в стандарте С99, применяется для представления булевских значений, т.е. логических значений true (истина) и false (ложь). Поскольку в языке С для представления true используется значение 1, а для представления false — значение 0, тип Bool по существу является целочисленным типом, но таким, который в принципе требует всего 1 бит памяти, поскольку этого достаточно, чтобы охватить весь диапазон от 0 до 1.

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

Переносимые типы: stdint.h И inttypes.h

К этому моменту вы, скорее всего, уже обратили внимание, что язык С предлагает широкое разнообразие целочисленных типов, и это можно только приветствовать. И, вероятно, вы также заметили, что одно и то же имя типа не обязательно означает одно и то же в разных системах, что не столь отрадно. Было бы замечательно, если бы язык С располагал типами, смысл которых не зависит от системы. Начиная со стандарта С99, нечто подобное было достигнуто.

Это было сделано в языке за счет создания дополнительных имен для существующих типов. Секрет в том, что эти новые имена определены в заголовочном файле stdint.h. Например, int32_t представляет тип для 32-битного целого значения со знаком. В системе, в которой используется 32-битный тип int, указанный заголовочный файл может определять int32_t в качестве псевдонима типа int. В другой системе, где применяется 16-битный тип int и 32-битный тип long, это же имя, int32_t, может быть определено как псевдоним для типа long. Тогда при создании программы с использованием int32_t в качестве типа и включении заголовочного файла stdint.h компилятор будет заменять тип int или long так, как это подходит для конкретной системы.

Рассмотренные альтернативные имена являются примерами целочисленных типов с точной шириной. Тип int32_t содержит в точности 32 бита — ни больше и ни меньше. Не исключено, что целевая система не поддерживает эти варианты, так что целочисленные типы с точной шириной следует считать необязательными.

А что, если система не способна поддерживать типы с точной шириной? Стандарты С99 и С11 предоставляют вторую категорию альтернативных имен, которые являются


Данные в языке С 99

обязательными. Этот набор имен гарантирует, что тип достаточно велик, чтобы удовлетворять спецификации, и нет других типов, которые могут выполнить нужную работу, но имеют меньший размер. Эти типы называются типами c минкмальпай шириной. Например, int_least8_t представляет собой псевдоним наименьшего доступного типа, который может хранить 8-битное целочисленное значение со знаком. Если бы наименьший тип в конкретной системе был 16-битным, то тип int8_t не определялся бы. Однако тип int_least8_t был бы доступным и, скорее всего, реализованным как 16-битный целочисленный тип.

Конечно, есть программисты, которых больше заботит быстродействие, чем расход памяти. Для них стандарты С99 и C11 определяют набор типов, которые обеспечат максимально быстрые вычисления. Эти типы называются высокоскоростными ти- нами с минимальной шириной. Например, тип int_fast8_t может быть определен как альтернативное имя для целочисленного типа данных вашей системы, который обеспечивает высокоскоростные вычисления с участием 8-битных значений со знаком.

Наконец, некоторых программистов устраивает только максимально возможный в системе целочисленный тип; такому типу соответствует имя intmax_t и он может хранить любое допустимое целочисленное значение со знаком. Аналогично, uintmax t представляет тип наибольшего допустимого целочисленного значения без знака. Кстати, указанные типы могут быть больше, чем long long и unsigned long, т.к. реализациям С разрешено определять типы, выходящие за рамки обязательных. Например, некоторые компиляторы ввели тип long long еще до того, как он стал частью стандарта.

Стандарты С99 и С11 не только предоставляют эти новые переносимые имена типов, но также содействуют с вводом и выводом значений таких типов. Например, функция printf() требует определенных спецификаторов для конкретных типов. Так что нужно сделать, чтобы отобразить значение int32_t, когда для одного определения может требоваться спецификатор %d, а для другого — %ld? Текущий стандарт предоставляет строковые макросы (этот механизм описан в главе 4), предназначенные для отображения переносимых типов. Например, файл заголовка inttypes.h определит PRId32 в качестве строки, представляющей подходящий спецификатор (скажем, d или 1) для 32-битного значения со знаком. В листинге 3.6 приведен краткий пример, иллюстрирующий применение переносимого типа и связанного с ним спецификатора. Заголовочный файл inttypes.h включает и файл заголовка stdint .h, поэтому в программе придется включать только файл inttypes.h.

Листинг 3.6. Программа altnames.c

Язык программирования C. Лекции и упражнения. 6-е издание



100 Глава 3

В финальном вызове функции printf() аргумент PRId32 заменяется своим определением "d" из файла inttypes.h, в результате чего строка принимает такой вид:

printf("me32 = %" "d" "\n", me32);

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

printf("mel6 = %d\n", mel6);

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

Сначала предположим, что int32_t является int: me32 = 45933945 Далее не будем делать никаких предположений.

Вместо этого воспользуемся "макросом" из файла inttypes.h: me32 = 45933945

В этом разделе не ставится цель изучить все расширенные целочисленные тины. Намерение скорее состоит в демонстрации наличия этого уровня управления типами на тот случай, если он потребуется. Подробное описание заголовочных файлов inttypes.h и stdint.h приведено в справочном разделе VI приложения Б.

НА ЗАМЕТКУ! Поддержка С99/С11

Несмотря на то что язык С перешел на стандарт С11, даже средства стандарта С99 разработчики компиляторов внедряли в разном темпе и с отличающимися приоритетами. На момент написания этой книги в некоторых компиляторах еще не были реализованы заголовочный файл inttypes.h и связанные с ним возможности.

Типы float, double И long double

Разнообразные целочисленные типы нормально подходят для большинства проектов по разработке программного обеспечения. Тем не менее, ориентированные на математику и финансы программы часто оперируют числами с плавающей запятой. В языке С такие числа имеют тип float, double или long double. Они соответствуют данным вещественного типа в языках программирования FORTRAN и Pascal. Как упоминалось ранее, числа с плавающей занятой позволяют представлять намного больший диапазон чисел, включая десятичные дроби. Представление чисел с плавающей запятой подобно научной форме записи, которая применяется для выражения очень больших и очень маленьких чисел. Давайте рассмотрим такую форму записи.

В научной форме записи числа представляются в виде десятичных чисел, умноженных на степень числа 10. Рассмотрим несколько примеров.

Язык программирования C. Лекции и упражнения. 6-е издание


В первом столбце показана обычная форма записи числа, во втором столбце — научная форма записи, а в третьем — экспоненциальная форма записи, которая представляет собой научную форму записи, обычно используемую при работе с компьютерами, при этом за обозначением е следует показатель степени 10. На рис. 3.7 приведено еще несколько примеров чисел с плавающей запятой.


Данные в языке С 101

Язык программирования C. Лекции и упражнения. 6-е издание
Стандарт языка С требует, чтобы тип float был способен представлять минимум шесть значащих цифр и охватывал диапазон значений, по меньшей мере, от 10~эт до 10+:Н Первое требование означает, что тип float должен представлять, как минимум, первые шесть цифр такого числа, как 33.333333. Второе требование по достоинству оценят те, кто оперирует такими величинами, как масса Солнца (2.0е30 килограмм), электрический заряд протона (1.6е-19 кулона) или сумма государственного долга. Часто для хранения чисел с плавающей запятой системы используют 32 бита. Восемь битов отводятся под значение экспоненты и ее знака, а остальные 24 бита служат для представления не- эксионенциальной части числа, которая называется мантиссой или значащей частью числа, и ее знака.

Язык программирования C. Лекции и упражнения. 6-е издание
Для представления чисел с плавающей запятой язык С предлагает также тип double (обеспечивающий двойную точность). Тип double имеет те же требования к минимальному диапазону возможных значений, что и float, но поддерживает более высокое минимальное количество значащих цифр — 10. В типичных представлениях типа double применяются 64 бита, а не 32. В некоторых системах все 32 дополнительных бита используются для неэкспоненциальной части. Это приводит к увеличению количества значащих цифр и сокращению ошибок, связанных с округлением. В других системах часть этих битов используется для размещения большей экспоненты, благодаря чему расширяется диапазон возможных значений. Любой из этих подходов обеспечивает, как минимум, 13 значащих цифр, что более чем удовлетворяет минимальному требованию стандарта.

Язык С допускает третий тип данных с плавающей запятой: long double. Цель этого типа — достижение большей точности, чем у типа double. Однако С гарантирует только то, что точность типа long double, по меньшей мере, не уступает точности типа double.

Объявление переменных с плавающей запятой

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

float noah, jonah;

double trouble;

float planck = 6.63e-34;

long double gnp;

Константы с плавающей запятой (литералы)

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

-1.56Е + 12

2.87e-З

Знак “плюс” можно не указывать. Можно также опустить десятичную точку (2Е5) или экспоненциальную часть (19.28), но не то и другое одновременно. Можно обой-


102 Глава 3 тись без дробной части (З.Е16) или целой части (.45Е-6), но не без обоих компонентов сразу. Ниже показано еще несколько допустимых констант с плавающей запятой:

3.14159

.2

4е16

. 8Е-5

100.

Не применяйте пробелы в константах с плавающей запятой. Например, эта константа является недопустимой:

1.56 Е+12

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

some = 4.0 * 2.0;

Тогда значения 4.0 и 2.0 сохраняются как данные типа double с использованием для каждого (обычно) 64 бита. Произведение вычисляется с применением арифметики с двойной точностью, и только после этого результат усекается к обычному типу float. Это гарантирует более высокую точность вычислений, но замедляет выполнение программы.

Язык С позволяет переопределить такое стандартное поведение компилятора за счет использования суффикса f или F, который заставляет компилятор трактовать константу с плавающей запятой как тип float, например, 2.3f и 9.11E9F. Суффикс 1 или L определяет число типа long double, например, 54.31 и 4.32e4L. Отметим, что букву L труднее перепугать с 1 (единица), чем букву 1. Если число с плавающей запятой не содержит суффикса, оно относится к типу double.

Начиная со стандарта С99, в языке С имеется новый формат для выражения констант с плавающей запятой. В нем применяется шестнадцатеричный префикс (0x или 0Х) с шестнадцатеричными цифрами, р или Р вместо е или Е и экспонента, которая является степенью 2, а не 10. Такое число может выглядеть следующим образом:

0xа.lfplO

Язык программирования C. Лекции и упражнения. 6-е издание


Вывод значений с плавающей запятой

Функция printf() использует спецификатор формата %f для вывода чисел типа float и double в десятичном представлении и спецификатор %е для вывода в экспоненциальном представлении. Если ваша система поддерживает шестнадцатеричный формат чисел с плавающей запятой, то вместо е или Е можно применять а или А. Для вывода данных типа long double требуются спецификаторы %Lf, %Le и %La. Обратите внимание, что для вывода как float, так и double используется спецификатор %f, %е или %а. Причина в том, что язык С автоматически расширяет значения float до типа double, когда они передаются в качестве аргументов любой функции, такой как printf() , в прототипе которой тип аргумента не определен явным образом. Это поведение демонстрируется в листинге 3.7.


Данные в языке С 103

Листинг 3.7. Программа showf pt. с

Язык программирования C. Лекции и упражнения. 6-е издание

Ниже приведен вывод, при условии, что компилятор совместим со стандартом С99/С11:

32000.000000 может быть записано как 3.200000е+04

И его 0xl.f4p+14 в шестнадцатеричной, представляющей степени 2, форме записи

2140000000.000000         может быть записано как 2.140000е+09

0.000053 может быть записано как 5.320000е — 05


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

Переполнение и потеря значимости в операциях с плавающей запятой

Предположим, что наибольшее возможное значение типа float равно примерно 3.4Е38, и нужно выполнить следующие операции:

float toobig = 3.4Е38 * 100.0f;

printf("%e\n", toobig);

Что произойдет? Это пример переполнения, когда в результате вычислений получается слишком большое число, чтобы его можно было правильно представить. Поведение системы в таких случаях обычно не определено, но в рассматриваемой ситуации переменной toobig присваивается специальное значение, которое обозначает бесконечность, и функция printf() отображает либо inf, либо infinity (или какую- то другую вариацию на эту тему).

А что можно сказать о делении очень малых чисел? Здесь ситуация более сложная. Вспомните, что число типа float хранится в виде сочетания показателя степени и значащей части, или мантиссы. В рассматриваемом случае это число, имеющее минимально возможный показатель степени, а также наименьшее значение, которое использует все доступные биты, отведенные для представления мантиссы. Это будет наименьшее число, представленное с наибольшей точностью, доступной для типа float. Теперь разделим его на 2. Обычно это приводит к уменьшению показателя степени, но в данном случае показатель уже достиг своего нижнего предела. Происходит сдвиг битов мантиссы вправо, с освобождением первой позиции и потерей последней двоичной цифры. Аналогичная картина возникает, если выбрать 10 в качестве основания системы счисления, взять число с четырьмя значащими цифрами, например,

0.1234Е-10, и разделить его на 10, получив в итоге 0.0123Е-10. Вы получите результат,


104 Глава 3 но в процессе деления потеряете цифру. Эта ситуация называется потерей значимости, а значения с плавающей запятой, которые утратили полную точность типа, в языке С называются субнормальными. Таким образом, деление наименьшего положительного значения с плавающей запятой на 2 дает субнормальное значение. Деление на достаточно большое значение приведет к потере всех цифр, и вы получите в результате 0. В настоящее время библиотека С предоставляет функции, которые позволяют проверить, не приведут ли вычисления к субнормальным значениям.

Существует еще одно специальное значение с плавающей запятой: NaN (not-a-num- ber — не число). Например, вы передаете функции asin() некоторое значение, а она возвращает угол, для которого переданное значение является синусом. Однако значение синуса не может быть больше 1, поэтому функция не определена для значений, превышающих 1. В таких случаях функция возвращает значение NaN, которое функция printf() отображает в виде nan, NaN или каким-то похожим образом.

Ошибки округления данных с плавающей запятой

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

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

Язык программирования C. Лекции и упражнения. 6-е издание

Вывод выглядит следующим образом:

0.000000       к- старая версия компилятора gcc в операционной системе Linux

-13584010575872.000000 +-Turbo С 1.5

4008175468544.000000 +-XCode 4.5, Visual Studio 2012,

текущая версия компилятора gcc

Причина получения таких странных результатов состоит в том, что компьютер не следит за тем, чтобы под числа с плавающей запятой было отведено столько десятичных позиций, сколько нужно для правильного выполнения операции. Число 2.0е20 представлено цифрой 2, за которой следует 20 нулей, и за счет прибавления 1 вы пытаетесь изменить 21-ю цифру. Чтобы эта операция выполнилась корректно, программа должна иметь возможность хранить число, состоящее из 21 цифры. Число типа float — это обычно шесть или семь цифр, масштабированных при помощи показателя степени до большего или меньшего числа, так что такая попытка сложения обречена на неудачу. С другой стороны, если вместо 2.0е20 вы укажете 2.0е4, то получите правильный ответ, поскольку вы пытаетесь изменить пятую цифру, а числа типа float обладают достаточной для этой операции точностью.

Представление значений с плавающей запятой

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


Данные в языке С 105

В 2011 году этот стандарт был принят в качестве международного стандарта ISO/IEC/IEEE 60559:2011. Он вошел в качестве необязательной части в стандарты С99 и С11, исходя из предположения, что его будут поддерживать платформы с соответствующим оборудованием. Последний пример вывода программы floaterr.с получен в системе, которая поддерживает этот стандарт для представления чисел с плавающей запятой. Поддержка со стороны языка С включает инструменты для выявления описанной проблемы. Более подробные сведения приведены в разделе V приложения Б.

Комплексные и мнимые типы

Во многих научных и технических расчетах используются комплексные и мнимые числа. Стандарт С99 поддерживает эти числа, но с некоторыми оговорками. В автономной реализации, такой как применяемая для встроенных процессоров, необходимость в наличии этих типов отсутствует. (Заметим, что микропроцессор видеомагнитофона при выполнении своей задачи вряд ли нуждается в комплексных числах.) Также в общем случае мнимые типы являются необязательными. В стандарте С11 весь пакет поддержки комплексных чисел сделан необязательным.

Существуют три комплексных типа, названные float _Complex, double Complex и long double _Complex. Переменная типа float _Complex, к примеру, будет содержать два значения float, одно из которых представляет действительную часть комплексного числа, а другое — его мнимую часть. Аналогично, существуют три мнимых типа: float _Imaginary, double _Imaginary и long double _Imaginary.

Включение заголовочного файла complex.h делает возможной подстановку слова complex взамен _Complex и слова imaginary взамен _Imaginary, а также применение символа I для представления квадратного корня из -1.

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

Например, до появления стандарта С99 многие программисты уже использовали struct complex для представления комплексных чисел или, возможно, психологических состояний. (Ключевое слово struct, как объясняется в главе 14, служит для определения структур данных, способных хранить более одного значения.) Превращение слова “complex” в ключевое слово превратило бы предшествующие случаи его применения в синтаксические ошибки. С другой стороны, использование сочетания struct Complex значительно менее вероятно, особенно с учетом того, что идентификаторы с начальным символом подчеркивания считаются зарезервированными. Таким образом, комитет остановился на _Complex в качестве ключевого слова и сделал вариант complex доступным для тех, кому не нужно беспокоиться по поводу конфликтов с предшествующими применениями.

За пределами базовых типов

На этом список фундаментальных типов данных завершен. Одним он может показаться слишком длинным. Другие могут посчитать, что необходимы дополнительные типы. Как насчет типа символьной строки? В языке С нет такого типа, но он мог бы обеспечить удобную работу со строками. Первое представление о строках вы получите в главе 4.


106 Глава 3

В С имеются другие типы, производные от базовых типов. Они включают массивы, указатели, структуры и объединения. Хотя эти типы являются предметом обсуждения последующих глав, кое-что уже было вскользь затронуто в примерах настоящей главы. Например, указатель указывает на место в памяти, в котором хранится переменная или другой объект данных. Префикс &, используемый в функции scanf(), создает указатель, который сообщает функции, куда помещать информацию.

Сводка: базовые типы данных Ключевые слова

Базовые типы данных устанавливаются с применением 11 ключевых слов: int, long, short, unsigned,char, float, double,signed,_Bool, _Complex И _Imaginary.

Целые числа со знаком

Они могут иметь как положительные, так и отрицательные значения.

•   int — базовый целочисленный тип в заданной системе. Язык С гарантирует для int не менее 16 битов.

•   short или short int — максимальное целое число типа short не превосходит наибольшего целочисленного значения типа int. Язык С гарантирует для short не менее 16 битов.

•   long или long int — может хранить целое число, которое, как минимум, не меньше наибольшего числа типа int или больше его. Язык С гарантирует для long не менее 32 битов.

•   long long или long long int — этоттип может быть целым числом, которое, как минимум, не меньше наибольшего числа типа long, а, возможно, и больше его. Для long long гарантируются не менее 64 битов.

Обычно тип long имеет большую длину, чем short, а длина типа int совпадает с длиной одного из этих типов. Например, старые основанные на DOS системы для IBM PC предоставляли 16-битные типы short и int и 32-битный тип long, а позже системы, основанные на Windows 95, предлагали 16-битный тип short и 32-битные типы int и long.

При желании для любого типа со знаком можно указывать ключевое слово signed, делая явным тот факт, что этот тип поддерживает знак.

Целые без знака

Такие типы хранят только нулевое или положительные значения. Это расширяет диапазон до большего возможного положительного числа. Указывайте ключевое слово unsigned перед желаемым типом: unsigned int, unsigned long, unsigned short. Одиночное ключевое unsigned означает то же, что и unsigned int.

Символы

Ими являются типографские символы, такие как а, & и +. По определению тип char для представления символа использует 1 байт памяти. Исторически сложилось так, что байт символа чаще всего имеет длину 8 битов, но он может быть длиной 16 битов или больше, если это необходимо для представления базового набора символов.

•   char — ключевое слово для этого типа. В одних реализациях применяется тип char со знаком, в других он без знака. Язык С позволяет использовать ключевые слова signed и unsigned для указания нужной формы.


Данные в языке С 107

Булевские значения

Булевский тип представляет значения true (истина) и false (ложь); в языке С для представления true применяется 1, а для представления false — 0.

•   _Bool — ключевое слово для этого типа. Он является типом int без знака и должен быть настолько большим, чтобы обеспечить хранение значений из диапазона от 0 до 1.

Вещественные числа с плавающей запятой

Эти типы могут иметь как положительные, так и отрицательные значения.

•   float — базовый тип данных с плавающей запятой в системе; он может представлять, по меньшей мере, шесть значащих цифр с заданной точностью.

•   double — (возможно) большая единица для хранения чисел с плавающей запятой. Этот тип может разрешать большее количество значащих цифр (минимум 10, но обычно больше) и, возможно, большие значения показателя степени, чем тип float.

•   long double — (возможно) еще большая единица для хранения чисел с плавающей запятой. Этот тип может разрешать большее количество значащих цифр и, возможно, большие значения показателя степени, чем тип double.

Комплексные и мнимые числа с плавающей запятой

Мнимые типы являются необязательными. Вещественные и мнимые компоненты основаны на соответствующих вещественных типах:

•    float _Complex

•    double _Complex

•    long double _Complex

•    float _Imaginary

•    double _Imaginary

•    long double _Imaginary

Сводка: объявление простой переменной

1.  Выберите необходимый тип данных.

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

3.  Применяйте следующий формат для оператора объявления:

специфика тор-типа имя-переменной;

Компонент спецификатор-типа образуется из одного или большего количества ключевых слов для типов; вот примеры объявлений:

int erest;

unsigned short cash;

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

char ch, init, ans;

5.  Вы можете инициализировать переменную в операторе объявления:

float mass = 6.0Е24;


108 Глава 3

Размеры типов

Какие размеры типов используются в вашей системе? Чтобы выяснить это, попробуйте выполнить программу, показанную в листинге 3.8.

Листинг 3.8. Программа typesize.c

Язык программирования C. Лекции и упражнения. 6-е издание


В языке С имеется встроенная операция sizeof, которая возвращает размер типа в байтах. Для такого применения sizeof в стандартах С99 и C11 предоставляется спецификатор %zd. Компиляторы, несовместимые с этими стандартами, могут потребовать вместо него спецификатор %u или %lu. Ниже показан пример вывода программы

typesize.c:

Тип int имеет размер 4 байт(ов).

Тип char имеет размер 1 байт(ов) .

Тип long имеет размер 8 байт(ов) .

Тип long long имеет размер 8 байт(ов) .

Тип double имеет размер 8 байт(ов).

Тип long double имеет размер 16 байт(ов).

Эта программа находит размеры только шести типов, но вы легко можете ее модифицировать, чтобы она определяла размер любого другого интересующего типа. Обратите внимание, что размером типа char обязательно будет 1 байт, потому что в языке С размер одного байта определяется в терминах char. Таким образом, в системе с 16-битным типом char и 64-битным double операция sizeof сообщит, что тип double имеет размер 4 байта. Для получения более подробной информации о предельных размерах типов можете просмотреть заголовочные файлы limits.h и float.h. (Эти два файла обсуждаются в следующей главе.)

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

Использование типов данных

При разработке программы обращайте внимание на то, какие переменные необходимы, и какие типы они должны иметь. Скорее всего, для чисел вы выберете int или, возможно, float, а для символов — тип char. Объявляйте переменные в начале фун-


Данные в языке С 109

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

int apples =3;     /* правильно */

int oranges = 3.0; /* плохая форма */

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

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

int cost = 12.99;  /* инициализация переменной типа int значением double */

float pi = 3.1415926536; /* инициализация переменной типа float значением double */

В первом объявлении переменной cost присваивается значение 12; при преобразовании значений с плавающей запятой в целочисленные компилятор С вместо округления просто отбрасывает дробную часть числа (выполняет усечение). Во втором объявлении происходит некоторая потеря точности, поскольку для типа float точность гарантируется только в пределах шести цифр. Когда вы делаете такую инициализацию, компиляторы могут (но не обязаны) выдавать предупреждающее сообщение. Подобная проблема могла возникнуть при компиляции программы, представленной в листинге 3.1.

Многие программисты и организации придерживаются систематических соглашений по назначению имен переменным, согласно которым имя отражает тип переменной. Например, можно было бы воспользоваться префиксом i_ для указания типа int и префиксом us_ для отражения типа unsigned short, так что i smart немедленно опознается как переменная типа int, a us verysmart — как переменная типа unsigned short.

Аргументы и связанные с ними ловушки

Полезно еще раз повторить и акцентировать внимание на сделанном ранее в этой главе предупреждении, касающемся использования функции printf(). Как вы можете помнить, элементы информации, передаваемой функции, называются аргументами. К примеру, вызов функции printf ("Здравствуй, мир. " ) содержит один аргумент: "Здравствуй, мир. ". Последовательность символов в кавычках вроде "Здравствуй, мир. " называется строкой. Строки будут обсуждаться в главе 4. Пока что важным моментом является то, что строка, даже если она содержит несколько слов и знаков препинания, считается одним аргументом.

Аналогично, вызов функции scanf ("%d", &weight) содержит два аргумента: "%d" и &weight. Для отделения аргументов друг от друга в языке С применяются запятые. Функции printf() и scanf() необычны в том, что они не ограничены конкретным количеством аргументов. Например, мы вызывали printf() с одним, двумя и тремя аргументами. Чтобы программа работала должным образом, она должна знать, сколько аргументов получает функция. Функции printf() и scanf() используют первый аргумент для указания количества дополнительных аргументов, который будут переданы. Дело в том, что каждая спецификация формата в первой строке говорит о наличии дополнительного аргумента.


110 Глава 3

Например, приведенный ниже оператор содержит два спецификатора формата,

%d и %d:

printf("%d котов съедают %d банок тунца\n", cats, cans);

Это сообщает о том, что функция должна ожидать еще два аргумента, и действительно, дальше следуют два аргумента — cats и cans.

Как программист, вы отвечаете за гарантию того, что количество спецификаций формата соответствует числу дополнительных аргументов, а типы спецификаторов соответствуют типам значений. В настоящее время язык С располагает механизмом прототипирования функций, который проверяет правильность количества и типов аргументов в вызове функции, однако он не работает в случае функций printf() и scanf(), т.к. они принимают переменное число аргументов. Что случится, если программист не справится со своей обязанностью по отношению к аргументам? Предположим, вы написали программу, показанную в листинге 3.9.

Листинг 3.9. Программа badcount.с

Язык программирования C. Лекции и упражнения. 6-е издание


Ниже приведен пример вывода, полученный в случае применения компилятора XCode 4.6 (OS X 10.8):

4

4 1 -706337836

1606414344 1

А так выглядит вывод этой же программы при использовании Microsoft Visual Studio Express 2012 (Windows 7):

4

4 0 0

0 1075576832

Обратите внимание, что применение спецификатора %d для отображения значения float не приводит к преобразованию значения float в ближайшее значение int. Кроме того, результаты, которые вы получаете при недостаточном количестве аргументов или в случае указания некорректных их типов, отличаются от пла тформы к платформе и от запуска к запуску.

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


Данные в языке C111

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

printf().

Еще один пример: управляющие последовательности

Давайте рассмотрим еще один пример, связанный с выводом, в котором используются специальные управляющие последовательности для символов языка С. В частности, программа, представленная в листинге 3.10, демонстрирует работу символов возврата на одну позицию влево (\b), табуляции (\t) и возврата каретки (\r). Их концепции существуют со времен, когда компьютеры применяли для вывода телетайпы, и они не всегда успешно транслируются в современных графических интерфейсах. Например, код в листинге 3.10 не работает описанным здесь образом в некоторых реализациях для компьютеров Macintosh.

Листинг 3.10. Программа escape.с

Язык программирования C. Лекции и упражнения. 6-е издание


Результаты выполнения программы

Давайте пошагово пройдемся по этой программе и посмотрим, как она будет работать в системе, где управляющие последовательности ведут себя описанным образом. (Фактическое поведение может отличаться. Например, XCode 4.6 отображает символы \а, \b и \r в виде перевернутых вопросительных знаков!)

Первый оператор printf() (помечен номером 1) воспроизводит звуковой сигнал (вызванный последовательностью \а), а затем выводит следующую фразу:

Введите желаемую сумму месячной зарплаты:

Поскольку в конце строки отсутствует последовательность \n, курсор устанавливается в позицию, следующую за двоеточием.

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

Введите желаемую сумму месячной зарплаты: $_

Пробел между двоеточием и знаком доллара появился в связи с тем, что строка во втором операторе начинается с пробела. Результатом семи символов возврата на


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

В этом месте вы вводите с клавиатуры свой ответ, скажем, 4 000.00. Теперь строка принимает следующий вид:

Введите желаемую сумму месячной зарплаты: $4000.00

Символы, которые вы набираете на клавиатуре, заменяют символы подчеркивания, и после нажатия клавиши <Enter> (или <Return>), чтобы ввести ответ, курсор переместится в начало следующей строки.

Вывод третьего оператора printf() начинается с \n\t. Символ новой строки перемещает курсор в начало следующей строки. Символ табуляции перемещает курсор в следующую позицию табуляции в этой строке — обычно, но не обязательно, в позицию 9. Затем выводится оставшаяся часть строки. После выполнения этого оператора экран выглядит так:

Введите желаемую сумму месячной зарплаты: $4000.00

$4000.00 в месяц соответствует $48000.00 в год.

Поскольку в этом операторе printf() символ новой строки не используется, курсор остается непосредственно после завершающей точки.

Четвертый оператор printf() начинается с последовательности \r. Она помещает курсор в начало текущей строки. Затем отображается строка “Ого!” и последовательность \n переводит курсор на следующую строку.

Окончательный вывод на экране имеет следующий вид:

Введите желаемую сумму месячной зарплаты: $4000.00

Ого! $4000.00 в месяц соответствует $48000.00 в год.

Сброс буфера вывода

Когда функция printf() действительно отправляет вывод на экран? Первоначально операторы printf() пересылают выходные данные в промежуточную область хранения, называемую буфером. Время от времени данные, находящиеся в буфере, отправляются на экран. Стандартные правила С относительно того, когда пересылать вывод из буфера на экран, довольно очевидны: он передается на экран, когда буфер заполнен, когда встречается символ новой строки или когда наступает время ввода данных. (Отправка вывода из буфера на экран или в файл называется сбросом буфера.) Например, первые два оператора printf() не заполняют буфер и не содержат символа новой строки, но непосредственно за ними следует оператор scanf(), который запрашивает ввод. Это инициирует отправку вывода printf() на экран.

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

printf("Введите желаемую сумму месячной зарплаты:\n");

scanf("%f", ssalary);


Данные в языке C113

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

Ключевые понятия

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

В компьютере числа с плавающей запятой фундаментально отличаются от целых чисел. Они хранятся и обрабатываются по-разному. Две 32-битных единицы памяти могут содержать идентичные наборы битов, но если одна из них интерпретируется как float, а другая как long, то они будут представлять совершенно разные и не связанные между собой значения. Например, если взять в IBM PC последовательность битов, представляющую число 256.0 типа float, и интерпретировать его как значение типа long, вы получите 113246208. Язык С позволяет записывать выражения со смешанными тинами данных, но будет выполнять автоматические преобразования, чтобы в действительном вычислении участвовал только один тин данных.

В памяти компьютера символы представлены числовым кодом. В США наибольшее распространение получил код ASCII, но язык С поддерживает использование и других кодов. Символьная константа — это символьное представление числового кода, применяемого в компьютерной системе; она состоит из символов, заключенных в одиночные кавычки, например, 'А'.

Резюме

В языке С имеется большое разнообразие типов данных. Базовые типы данных разделены на две категории: целочисленные типы данных и данные с плавающей запятой. Двумя отличительными особенностями целочисленных типов являются объем памяти, выделяемой для типа, и наличие или отсутствие знака. Наименьший тип целочисленных данных — char, который в зависимости от реализации может быть со знаком или без знака. Можно использовать signed char и unsigned char, чтобы явно указать, какой вариант нужен, однако обычно это делается в случае использования тина char для хранения небольших целых чисел, а не символьных кодов. К другим целочисленным типам относятся short, int, long и long long. В языке С гарантируется, что каждый из этих типов имеет, по крайней мере, такой же размер, как предшествующий тип. Все они являются типами со знаком, но можно применять ключевое слово unsigned для создания соответствующих типов без знака: unsigned short, unsigned int, unsigned long и unsigned long long. Или же можно добавить модификатор signed, чтобы явно указать, что тип имеет знак. Наконец, существует еще тип Bool — тип без знака, который способен принимать значения 0 и 1, представляющие false и true.

Существуют три типа с плавающей занятой: float, double и, начиная со стандарта С90, long double. Каждый из них минимум не меньше предыдущего тина. Дополнительно реализация может поддерживать комплексные и мнимые типы за счет использования ключевых слов Complex и Imaginary в сочетании с ключевыми ело-


114 Глава 3.

вами для типов с плавающей запятой. Например, можно работать с типами double _Complex и float _Imaginary.

Целые числа могут быть выражены в десятичной, восьмеричной и шестнадцатеричной форме. Префикс 0 указывает на восьмеричное число, а префикс 0x пли 0x — на шестнадцатеричное. Например, 32, 040 и 0x2 0 — это десятичное, восьмеричное и шестнадцатеричное представление одного и того же значения. Суффикс 1 или L указывает, что значение имеет тип long, all или LL — что оно относится к типу long long.

Символьные константы представляются путем помещения символа в одиночные кавычки, например, 'Q', '8' и '$'.С помощью управляющих последовательностей, таких как '\n', задаются определенные непечатаемые символы. Вы можете применять форму '\007' для представления символа в коде ASCII.

Числа с плавающей запятой могут быть записаны в форме с фиксированной десятичной точкой, например, 9393.912, или в экспоненциальном представлении, например, 7.38Е10. Стандарты С99 и С11 предоставляют третью форму экспоненциальной записи с использованием шестнадцатеричных цифр и степеней 2, подобно 0xа.lfpl0.

Функция printf() позволяет выводить значения различных типов с применением спецификаторов, которые в своей простейшей форме состоят из знака процента и буквы, указывающей тип, например, %d или %f.

Вопросы для самоконтроля

Ответы на эти вопросы находятся в приложении А.

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

а.   Население Москвы.

б.   Стоимость копии фильма на DVD-диске.

в.   Буква, которая чаще других встречается в данной главе.

г.   Количество раз, сколько эта буква встречается в данной главе.

2.   В каких случаях следует использовать переменную типа long вместо int?

3.  Какие переносимые типы можно использовать, чтобы получить 32-битное целое число со знаком? Приведите аргументы в пользу своего выбора.

4.   Идентифицируйте тип и назначение каждой из следующих констант:

Язык программирования C. Лекции и упражнения. 6-е издание


5.   Кое-кто написал программу с ошибками. Найдите эти ошибки.

include <stdio.h> main

{

float g; h; float tax, rate; g = e21; tax = rate*g;

}


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

Язык программирования C. Лекции и упражнения. 6-е издание

7.  Определите тип данных (по тому, как он используется в операторах объявления) и спецификатор формата printf() для каждой из следующих констант (предполагая, что тип int является 16-битным).

Язык программирования C. Лекции и упражнения. 6-е издание

8. Предположим, что программа начинается со следующих объявлений:

Язык программирования C. Лекции и упражнения. 6-е издание


9. Предположим, что ch является переменной типа char. Покажите, как присвоить ей символ возврата каретки, используя управляющую последовательность, десятичное значение, восьмеричную символьную константу и шестнадцатеричную символьную константу. (Предположите, что применяются значения кода ASCII.)

10. Исправьте следующую нелепую программу. (В языке С символом / обозначается операция деления.)

Язык программирования C. Лекции и упражнения. 6-е издание


Глава 3

Язык программирования C. Лекции и упражнения. 6-е издание
Язык программирования C. Лекции и упражнения. 6-е издание


11. Определите, что представляет каждая из следующих управляющих последовательностей:



Язык программирования C. Лекции и упражнения. 6-е издание



Упражнения по программированию

1.  Экспериментальным путем выясните, как ваша система обрабатывает переполнение при выполнении операций над целыми числами и над числами с плавающей запятой, а также потерю значимости при выполнении операций над числами с плавающей запятой; т.е. напишите программу, в которой присутствуют такие проблемы. (Для получения сведений о наибольших и наименьших значениях просмотрите обсуждение limits.h и float.h в главе 4.)

2.  Напишите программу, которая приглашает ввести некоторое значение в коде ASCII, например, 66, а затем выводит символ, которому соответствует введенный код.

3.  Напишите программу, которая выдает предупредительный звуковой сигнал, а затем выводит следующий текст:

Напуганная внезапным звуком, Вика вскрикнула:

"Во имя всех звезд, что это было!"

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

Введите значение с плавающей запятой: 64.25 Запись с фиксированной запятой: 64.250000 Экспоненциальная форма записи: 6.425000е+01 Двоично-экспоненциальное представление: 0xl.dp+6

5.  В году содержится примерно 3.156х107 секунд. Напишите программу, которая предлагает ввести возраст в годах, а затем выводит на экран эквивалентное значение в секундах.

6.  Масса одной молекулы воды приблизительно составляет 3.0x10-23 грамм. Кварта воды весит примерно 950 грамм. Напишите программу, которая предлагает ввести значение объема воды в квартах и отображает количество молекул воды в этом объеме.

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

8.  В американской системе единиц измерений объема пинта равна 2 чашкам, чашка — 8 унциям, унция — 2 столовым ложкам, а столовая ложка — 3 чайным ложкам. Напишите программу, которая предлагает ввести объем в чашках и отображает эквивалентные значения в пинтах, унциях, столовых ложках и чайных ложках. Почему для этой программы тип с плавающей запятой подходит больше, чем целочисленный?


4

Символьные строки и форматированный

ввод-вывод

В ЭТОЙ ГЛАВЕ...

•    Функция: strlen()

•    Ключевое слово: const

•    Символьные строки

•    Создание и храпение символьных строк

•    Использование функций printf() и scant() для чтения и отображения символьных строк

•    Использование функции st rle n() для измерения длины строки

•    Использование директивы #define препроцессора С и модификатора const стандарта ANSI С

для создания символических констант



118 Глава 4

В

 этой главе основное внимание сосредоточено на вводе и выводе. После изучения всего предлагаемого здесь материала вы сможете придать своим программам индивидуальность, сделав их интерактивными и использующими символьные строки. Кроме того, более подробно рассматриваются две удобные функции ввода-вывода — printf() и scanf(). Эти функции являются программными инструментами для взаимодействия с пользователями и форматирования выходных данных в соответствие с конкретными потребностями и предпочтениями. Наконец, вы вкратце ознакомитесь с таким важным средством языка С, как препроцессор, и узнаете, каким образом опре делять и применять символические константы.

Вводная программа

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

Листинг 4.1. Программа talkback.с

Язык программирования C. Лекции и упражнения. 6-е издание


Запустив на выполнение программу talkback.с, получаем следующий результат:

Здравствуйте! Как вас зовут?

Кристина

Кристина, сколько вы весите в фунтах?

154

Хорошо, Кристина, ваш объем составляет 2.47 кубических футов.

К тому же ваше имя состоит из 8 букв, и мы располагаем 40 байтами для его сохранения.


Символьные строки и форматированный ввод-вывод 119

Эта программа отличается следующими новыми особенностями.

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

•   В рассматриваемой программе для обработки ввода и вывода строки используется спецификатор преобразования %s. Обратите внимание, что с переменной name, в отличие от weight, префикс & не указывается, когда она применяется в вызове функции scanf(). (Позже вы увидите, что как &weight, так и name являются адресами.)

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

•   В рассматриваемой программе для выяснения длины строки применяется функция strlen().

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

Давайте ознакомимся с этими новыми идеями.

Введение в символьные строки

Символьная строка — это последовательность из одного или большего количества символов, например:

"Это длинная строка символов."

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

Массив типа char и нулевой символ

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

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.1. Строка в массиве

На рис. 4.1 обратите внимание, что в последней позиции массива находится символ \0. Он представляет собой нулевой символ, который в языке С служит для пометки конца строки. Нулевой символ — это не цифра ноль, а непечатаемый символ, кодовое значение которого в кодировке ASCII (или эквивалентной) равно 0. Строки в С всегда сохраняются с завершающим нулевым символом.


120 Глава 4

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

Что же такое массив? Массив можно представить как несколько ячеек памяти, рас положенных подряд. Если вы предпочитаете более формальный стиль, то массив - это упорядоченная последовательность элементов данных одного типа. В рассмат риваемом примере создается массив из 40 ячеек памяти, или элементов, каждый и: которых может хранить одно значение типа char, для чего используется следующее объявление:

char name[40];

Квадратные скобки после имени name идентифицируют его как массив. Число 4 С внутри скобок указывает количество элементов в этом массиве, char идентифицирует тип каждого элемента (рис. 4.2).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.2. Сравнение объявлений простой переменной и массива


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

Использование строк

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

Листинг 4.2. Программа praisel .с

Язык программирования C. Лекции и упражнения. 6-е издание



Символьные строки и форматированный ввод-вывод 121

Спецификатор %s сообщает функции printf() о необходимости вывода строки. Он встречается дважды, т.к. программа выводит две строки: одна хранится в массиве name, а другая представлена PRAISE. Выполнение программы praisellc дает пример но такой результат:

Как вас зовут? Мария Иванова

Здравствуйте, Мария. Вы - выдающаяся личность.

Вам не придется самостоятельно помещать нулевой символ в массив name. Эту задачу решает функция scanf(), когда считывает входные данные. Точно так же нет необходимости во включении нулевого символа в строковую символьную константу PRAISE. Действия оператора #define мы рассмотрим позже, а пока просто запомните, что двойные кавычки, в которые заключается текст, следующий за PRAISE, идентифицируют данный текст как строку. Компилятор сам позаботится о добавлении нулевого символа.

Обратите внимание (и это важно) на то, что функция scanf() читает только имя Мария, а не имя и фамилию. После того, как функция scanf() начинает считывать входные данные, она останавливает чтение на первом встреченном пробельном символе (символе пробела, табуляции или новой строки). Таким образом, считывание для массива name прекращается, когда появляется символ пробела между словами “Мария” и “Иванова”. В принципе функция scanf() применяется со спецификатором %s только для чтения одиночного слова, а не целой фразы, которая может находиться в строке. В языке С доступны другие функции ввода данных, такие как fgets(), поддерживающая общие строки. Эти функции подробно рассматриваться в последующих главах.

Различия между строками и символами

Строковая константа "х" — вовсе не то же самое, что и символьная константа ' х'. Одно из различий связано с тем, что ' х' имеет базовый тип (char), но "х" — это производный тип, представляющий собой массив значений char. Второе различие заключается в том, что "х" на самом деле состоит из двух символов — ' х' и ‘\0’ (рис. 4.3).

Язык программирования C. Лекции и упражнения. 6-е издание

РИс. 4.3. Символ 'х' и строка "х"


Функция strlen()

В предыдущей главе кратко затрагивалась операция sizeof, которая предоставляет размер типа в байтах. Функция strlen() возвращает длину строки в символах. Поскольку для размещения одного символа требуется один байт, можно было бы предположить, что применительно к строке sizeof и strlen() дадут один и тот же результат, однако это не так. Добавьте в пример кода несколько строк, как показано в листинге 4.3, и вы поймете причину.


122 Глава 4

Листинг 4.3. Программа praise2.с

Язык программирования C. Лекции и упражнения. 6-е издание


Если вы используете версию компилятора, не поддерживающую ANSI С, придется удалить следующую строку:

#include <string.h>

Заголовочный файл string.h содержит прототипы для нескольких функций обработки строк, включая strlen(). Более подробно этот заголовочный файл обсуждается в главе 11. (Кстати, в некоторых системах UNIX, разработанных до появления стандарта ANSI, вместо string.h применяется заголовочный файл strings.h, содержащий объявления строковых функций.)

В общем случае С разделяет библиотеку функций на семейства связанных функций и предоставляет отдельный заголовочный файл для каждого семейства. Например, функции printf() и scanf() принадлежат семейству стандартных функций ввода- вывода и имеют свой заголовочный файл stdio.h. Функция strlen() объединяет вокруг себя ряд других функций обработки строк, таких как функции для копирования и поиска в строках, и это семейство обслуживается заголовочным файлом string.h.

Следует отметить, что в листинге 4.3 длинные операторы printf() представлены с использованием двух методов. Первый метод предусматривает разнесение оператора printf() на две строки. (Вызов можно разделять в промежутках между аргументами, но не в середине строки; т.е. не между кавычками.) Второй метод предполагает применение для вывода одной строки двух операторов printf(). Символ новой строки (\n) присутствует только во втором операторе. После запуска программы возникает следующее взаимодействие с пользователем:

Как вас зовут? Васисуалий Лоханкин

Здравствуйте, Васисуалий. Вы - выдающаяся личность.

Ваше имя состоит из 10 букв и занимает 40 ячеек памяти.

Хвалебная фраза содержит 31 символов и занимает 32 ячеек памяти.

Давайте взглянем, что происходит. Массив name имеет 40 ячеек памяти, и именно об этом сообщает операция sizeof. Однако для размещения имени Васисуалий необходимы только первые 10 ячеек, и об этом информирует функция strlen().


Символьные строки и форматированный ввод-вывод 123

Одиннадцатая ячейка в массиве name содержит нулевой символ, и его присутствие сообщает функции strlen(), когда она должна остановить подсчет. На рис. 4.4 эта концепция иллюстрируется на примере более короткой строки.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.4. Функции strlen() известно, когда остановить подсчет символов


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

Как упоминалось в главе 3, стандарты С99 и С11 предполагают использование спецификатора %zd для типа, указываемого в операции sizeof. Это также относится и к типу, возвращаемому функцией strlen(). Для более ранних версий С необходимо знать действительный тип, возвращаемый операцией sizeof и функцией strlen(); обычно им будет unsigned int или unsigned long.

Еще один момент: в предыдущей главе операция sizeof применялась с круглыми скобками, но в этом примере их нет. Используете вы круглые скобки или нет, зависит от того, хотите вы получить размер типа или конкретной величины. Круглые скобки обязательны для типов, но необязательны для отдельных величин. Это значит, что вы будете применять sizeof (char) или sizeof ( float), но также можете использовать sizeof name или sizeof 6.28. Тем не менее, в этих случаях также допускается указание круглых скобок, например, sizeof (6.28).

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

Давайте перейдем к рассмотрению оператора #define.

Константы и препроцессор С

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

circumference = 3.14159 * diameter;

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

circumference = pi * diameter;


124 Глава 4

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

owed = 0.015 * housevalue;

owed = taxrate * housevalue;

В длинной программе понять второй оператор гораздо проще, чем первый.

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

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

float taxrate;

taxrate = 0.015;

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

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

#define TAXRATE 0.015

После компиляции программы значение 0.015 будет подставлено повсюду, где использовалась константа TAXRATE. Это называется подстановкой во время компиляции. К моменту запуска программы все подстановки уже сделаны (рис. 4.5). Константы, определенные подобным образом, часто называются символическими константами или литералами.

Взгляните на формат. Сначала идет директива #define. За ней следует символическое имя (TAXRATE) для константы и затем ее значение (0.015). (Обратите внимание, что в этой конструкции отсутствует знак =.) Общая форма выглядит так:

#define ИМЯ значение

Здесь вы должны заменить конструкцию ИМЯ желаемым символическим именем, а конструкцию зна чение соответствующим значением. Точка с запятой в этом случае не указывается, т.к. это механизм замены, поддерживаемый препроцессором, а не оператор языка С. Почему имя TAXRATE записано прописными буквами? По сложившейся традиции имена констант в С представляются прописными буквами. Если где- то в недрах программы встречается имя подобного рода, то сразу становится ясно, что оно определяет константу, а не переменную. Представление имен констант прописными буквами является еще одним способом улучшения читабельности программ. Программы сохранят работоспособность и без представления констант прописными буквами, но разумнее взять этот прием на вооружение.

Другое менее распространенное соглашение по именованию констант предусматривает предварение имени префиксом с_ или к_ для указания на то, что оно представляет константу, в результате чего появляются имена, подобные c level или k_line.

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



Язык программирования C. Лекции и упражнения. 6-е издание




126 глава 4

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

Листинг 4.4. Программа pizza.с

Язык программирования C. Лекции и упражнения. 6-е издание


Спецификатор %1.2f в операторе printf() вызывает округление при выводе до двух десятичных позиций. Конечно, эта программа может не отражать все характе ристики пиццы, представляющие для вас интерес, но она заполняет небольшую нишу в мире программ, имеющих отношение к пицце. Вот пример выполнения этой программы:

Каков радиус вашей пиццы?

6.0

Основные параметры вашей пиццы:

длина окружности = 37.70, площадь = 113.10

Директиву #define можно использовать также для объявления символьных и строковых констант. Достаточно указать одиночные кавычки для символьных и двойные кавычки для строковых констант. Ниже приведены допустимые объявления констант:

#define ВЕЕР ‘\а '

#define TEE 'Т'

#define ESC '\033 '

#define OOPS "Теперь вы сделали это!"

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

/* следующее определение некорректно */

#define TOES = 20

Если поступить так, то константа TOES будет заменена последовательностью =2 0, а не просто 20. В этом случае оператор следующего вида:

digits = fingers + TOES; преобразуется в такое ошибочное представление:

digits = fingers + = 20;


Символьные строки и форматированный ввод-вывод 127

модификатор const

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

const int MONTHS = 12;  // MONTHS является символической константой для 12

Такое объявление делает MONTHS значением только для чтения. Это означает, что вы можете отображать MONTHS на экране и применять его в вычислениях, но не модифицировать значение MONTHS. Новый подход более гибок, чем прием с директивой #define; он позволяет объявить тип и обеспечивает больший контроль над тем, в каких частях программы может использоваться константа. В главе 12 обсуждается этот и другие способы применения модификатора const.

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

Работа с символическими константами

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

#define INT_MAX +32767

#define INT_MIN -32768

Эти константы представляют наибольшее и наименьшее возможные значения для типа int. Если в вашей системе используется 32-битный тип int, то данный файл предоставит другие значения для таких символических констант. В файле limits.h определены минимальные и максимальные значения для всех целочисленных типов. После включения файла limits.h вы можете применять такой код:

printf("Максимальное значение типа int в этой системе составляет %d\n", INT_MAX);

Если в системе используется четырехбайтный тип int, то файл limits.h, который поступает с этой системой, предоставит определения для INT MAX и INT MIN, соответствующие пределам четырехбайтного типа int. В табл. 4.1 приведен список некоторых констант, находящихся в файле limits.h.

Таблица 4.1. Некоторые символические константы из файла limits.h

Язык программирования C. Лекции и упражнения. 6-е издание



128 Глава 4

Окончание табл. 4.1

Язык программирования C. Лекции и упражнения. 6-е издание


Аналогично, в файле float.h определены такие константы, как FLT_DIG и DBL_DIG, которые представляют количество значащих цифр, поддерживаемое типами float и double. В табл. 4.2 перечислены некоторые константы, определенные в файле float.h. (Можете открыть в текстовом редакторе заголовочный файл float.h, доступный в вашей системе, и ознакомиться с его содержимым.) Здесь приводятся только данные для типа float. Эквивалентные константы определены для типов double и long double; в их именах вместо FLT применяются строки DBL и LDBL. (В табл. 4.2 предполагается, что в системе числа с плавающей запятой представлены степенями 2.)

Таблица 4.2. Некоторые символические константы из файла float.h

Язык программирования C. Лекции и упражнения. 6-е издание


В листинге 4.5 демонстрируется использование данных из float.h и limits.h. (Следует отметить, что компилятор, который не полностью поддерживает стандарт С99, может не принять идентификатор LONG_MIN.)


Символьные строки и форматированный ввод-вывод 129

Листинг 4.5. Программа defines. с

Язык программирования C. Лекции и упражнения. 6-е издание


Ниже показан пример вывода:

Некоторые пределы чисел для данной системы:

Наибольшее значение типа int: 2147483647

Наименьшее значение типа long long: -9223372036854775808

В данной системе один байт = 8 битов.

Наибольшее значение типа double: 1, 797693е + 308

Наименьшее нормализованное значение типа float: 1,175494е-38

Точность значений типа float = 6 знаков

Разница между 1.00 и минимальным значением float, которое больше 1.00 =

1.192093е-07

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

Исследование и эксплуатация

ФУНКЦИЙ printf() И scanf()

Функции printf() и scanf() позволяют организовать взаимодействие с программой и называются функциями ввода-вывода. В языке С доступны и другие функции ввода-вывода, но printf() и scanf() являются наиболее универсальными. Исторически сложилось так, что они, как и все остальные функции в библиотеке С, не были частью определения языка. Первоначально язык С оставлял реализацию средств ввода-вывода разработчикам компиляторов; это делало возможным лучшее соответствие функций ввода-вывода конкретным машинам. В интересах совместимости различные реализации поставлялись со своими версиями функций scanf() и printf(). Тем не менее, между реализациями встречались некоторые расхождения. В С90 и С99 описаны стандартные версии этих функций, и именно их мы будем придерживаться.

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


130 Глава 4

ФУНКЦИЯ printf()

Инструкции, которые вы даете функции printf(), запрашивая у нее вывод пере менной, зависят от типа этой переменной. Например, ранее мы применяли форму записи %d при выводе целого числа и %с при выводе символа. Эти обозначения называются спецификаторами преобразования, поскольку они определяют, каким образом данные преобразуются в отображаемую форму. Мы приведем список спецификаторов преобразования, которые стандарт ANSI С предоставляет для функции printf(), и затем покажем, как использовать наиболее общие из них. В табл. 4.3 перечислены спецификаторы преобразования и показан вывод, к которому они приводят.

Таблица 4.3. Спецификаторы преобразования и результирующий вывод

Язык программирования C. Лекции и упражнения. 6-е издание


Использование функции printf()

В листинге 4.6 представлена программа, в которой применяются некоторые спецификаторы преобразования.


Символьные строки и форматированный ввод-вывод 131

Листинг 4.6. Программа printout, с

Язык программирования C. Лекции и упражнения. 6-е издание


Вывод программы выглядит вполне ожидаемо:

7 участников соревнований съели 12.750000 пирожков с вишнями.

Значение pi равно 3.141593.

До свидания! Ваше искусство слишком дорого обходится,

$15600

Формат использования функции printf() имеет вид:

printf(управляющая-строка, элемент1, элемент2, ...);

Здесь элемент 1, эмемент2 и т.д. — это элементы, которые нужно вывести. Ими могут быть переменные, константы или даже выражения, которые вычисляются до того, как значение будет выведено. Далее, управляющая-строка представляет собой символьную строку, описывающую способ вывода элементов. Как упоминалось в главе 3, управляющая строка должна содержать спецификатор преобразования для каждого выводимого элемента. Например, рассмотрим следующий оператор:

printf("%d участников соревнований съели %f пирожков с вишнями.\n", number, pies);

В этом операторе управляющая-строка — это фраза, заключенная в двойные кавычки. Она содержит два спецификатора преобразования, соответствующие number и pies — двум выводимым элементам. На рис. 4.6 показан другой пример применения оператора printf().

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.6. Аргументы функции printf()


Вот еще одна строка из примера:

printf("Значение pi равно %f.\n", PI);

На этот раз список элементов состоит только из одного элемента — символической константы PI.


132 Глава 4

Как можно видеть на рис. 4.7, управляющая строка содержит два разных вида информации:

•    символы, которые в действительности выводятся;

•    спецификаторы преобразования.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.7. Структура управляющей стракн


Внимание!

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

printf("Выпало %d очков из %d.\n", scorel);

Здесь отсутствует значение для второго спецификатора %d. Результат такой небрежности зависит от системы, но в лучшем случае на экране отобразятся бессмысленные символы.

Если вы хотите вывести только фразу, то не нуждаетесь в каких-либо спецификаторах преобразования. Чтобы вывести только данные, вы можете обойтись традиционным вызовом. Оба следующих оператора из листинга 4.6 вполне приемлемы:

printf("До свидания! Ваше искусство слишком дорого мне обходится,\n"); printf("%c%d\n",    2 * cost);

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

Поскольку функция printf() использует символ % для идентификации спецификаторов преобразования, то возникает небольшая проблема, когда требуется вывести сам символ %. Если просто указать одиночный знак %, компилятор посчитает, что вы некорректно задали спецификатор преобразования. Выход из этой ситуации прост: достаточно указать два символа %:

рс = 2*6;

printf("Только %d%% припасов Мэри были пригодными в пищу.\n", рс);

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

Только 12% припасов Мэри были пригодными в пищу.

Модификаторы спецификаторов преобразования для функции printf()

Базовый спецификатор преобразования можно изменять, вставляя модификаторы между знаком % и символом, который определяет преобразование.


Символьные строки и форматированный ввод-вывод 133

В табл. 4.4 и 4.5 перечислены символы, которые можно здесь размещать. При указании более одного модификатора они должны располагаться в том же порядке, в каком они представлены в табл. 4.4. Не все возможные комбинации допустимы. В таблице отражены дополнения стандарта С99; ваша реализация может не поддерживать все показанные варианты.

Таблица 4.4. Модификаторы функции printf()

Язык программирования C. Лекции и упражнения. 6-е издание



134 Глава 4

НА ЗАМЕТКУ! Переносимость типов

Вспомните, что операция si zeof возвращает размер типа или значения в байтах. Это значение должно быть какой-либо формой целого числа, но стандарт допускает только целое значение без знака. Следовательно, им может быть unsigned int, unsigned long или даже unsigned long long. Таким образом, в случае применения функции printf() для отображения выражения sizeof можно было бы использовать спецификатор %u в одной системе, %lu — в другой и %llu — в третьей. Это значит, что нужно выяснить правильное применение в конкретной системе, и что в случае переноса в другую систему может потребоваться изменить программу.

Итак, помимо всего прочего, язык С предоставляет поддержку для обеспечения более высокой переносимости типов. Во-первых, заголовочный файл stddef.h (включаемый в результате включения заголовочного файла stdio.h) определяет, что типом sizet будет тип, используемый в системе для возвращаемого значения операции sizeof. Этот тип называется основополагающим типом. Во-вторых, в функции printf() применяется модификатор z для указания соответствующего типа при выводе. Аналогично в языке С определен тип pt rdi f f_t и модификатор t для указания основополагающего целочисленного типа со знаком, используемого системой для представления разницы между двумя адресами.

НА ЗАМЕТКУ! Преобразование аргументов типа float

Существуют спецификаторы преобразования для вывода типов double и long double. В то же время такой спецификатор для типа float отсутствует. Причина в том, что в классическом языке K&R С значения типа float автоматически преобразовывались в тип double перед использованием в выражении или до передачи в качестве аргумента. В общем случае в стандарте ANSI С (или последующих реализациях) не предусматривается автоматическое преобразование float в double. Однако для того, чтобы обеспечить правильную работу огромного количества существующих программ, которые разрабатывались с расчетом на то, что аргументы типа float преобразуются в double, все аргументы float для функции printf() — и других функций С, не использующих явные прототипы — автоматически преобразуются в тип double. Поэтому ни в K&R С, ни в ANSI С специальный спецификатор преобразования для отображения типа float не требуется.

Таблица 4.5. Флаги функции printf <)

Язык программирования C. Лекции и упражнения. 6-е издание



Символьные строки и форматированный ввод-вывод 135

Примеры использования модификаторов и флагов

Давайте посмотрим на описанные выше модификаторы в действии. Мы начнем с оценки влияния модификатора, устанавливающего ширину поля, на вывод целого числа. Рассмотрим программу, показанную в листинге 4.7.

Листинг 4.7. Программа width.с

Язык программирования C. Лекции и упражнения. 6-е издание


Программа из листинга 4.7 выводит одно и то же число четыре раза, применяя четыре разных спецификатора преобразования. Звездочка (*) служит для обозначения начала и конца каждого поля. Вывод выглядит следующим образом:

*959*

*959*

*     959*

*959     *

Первым спецификатором преобразования является %d без модификаторов. Он производит поле с шириной, которую имеет выводимое целое число. Этот вариант принят но умолчанию, т.е. если не предоставлены дальнейшие инструкции, то число будет выведено именно в таком виде. Второй спецификатор преобразования — %2d. Он устанавливает ширину поля равной 2, но поскольку в рассматриваемом примере целое число имеет три значащих цифры, ноле автоматически расширяется, чтобы уместить это число. Следующий спецификатор преобразования — %10d. Он генерирует поле шириной 10 символов, при этом в итоге получаются семь пробелов и три цифры между звездочками, а число смещено к правой границе поля. Последним спецификатором является %-10d. Он также производит ноле шириной 10 символов, а знак - означает, что число начинается с левого края, как и было заявлено. Привыкнув к этой системе, вы убедитесь, что она проста в применении и обеспечивает высокий контроль над внешним видом вывода. Попробуйте изменить значение PAGES, чтобы посмотреть, как выводятся числа с различным количеством цифр.

Теперь рассмотрим форматы чисел с плавающей запятой. Введите, скомпилируйте и запустите программу, показанную в листинге 4.8.

Листинг 4.8. Программа floats.с

Язык программирования C. Лекции и упражнения. 6-е издание



136 Глава 4

Язык программирования C. Лекции и упражнения. 6-е издание


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

*3852.990000*

*3.852990е+03*

*3852.99*

*3853.0*

* 3852.990*

* 3.853Е+03*

*+3852.99*

*0003852.99*

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

Затем используется еще одна версия спецификатора, принятая по умолчанию — %е. Она выводит одну цифру слева от десятичной точки и резервирует шесть позиций справа от нее. Получается довольно много цифр. Чтобы исправить это положение, нужно указать количество десятичных позиций справа от десятичной точки, и следующие четыре примера служат иллюстрацией такого решения. Обратите внимание на то, что в четвертом и шестом примере при выводе происходит округление. Вдобавок в шестом примере вместо спецификатора е применяется Е.

Наконец, флаг + приводит к выводу результата с его алгебраическим знаком, которым в данном случае является “плюс”, а флаг 0 обеспечивает дополнение до полной ширины поля ведущими нулями. Следует отметить, что в спецификаторе %010.2 f первый 0 — это флаг, а остальные цифры до десятичной точки (10) указывают ширину ноля.

Можете модифицировать значение RENT, чтобы посмотреть, как выводятся значения разнообразной длины. Программа в листинге 4.9 демонстрирует еще несколько возможных комбинаций.

Листинг 4.9. Программа flags.с

Язык программирования C. Лекции и упражнения. 6-е издание



Символьные строки и форматированный ввод-вывод 137

Вывод программы показан ниже:

If IF 0xlf

42** 42**

** 6** 006**00006** 006**

Первым делом отметим, что If — это шестнадцатеричный эквивалент десятичного числа 31. Спецификатор х выдает результат If, а спецификатор X — 1F. Использование флага # обеспечивает вывод ведущих символов 0x.

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

В третьей строке показано, что использование спецификатора точности (%5.3d) с целочисленной формой дополняет число ведущими нулями до получения минимального количества цифр (трех в данном случае). Однако применение флага 0 приводит к дополнению представления числа ведущими нулями, которых достаточно для заполнения всей ширины поля. Наконец, при одновременном указании флага 0 и спецификатора точности флаг 0 игнорируется.

Теперь исследуем некоторые варианты со строкой. Рассмотрим программу в листинге 4.10.

Листинг 4.10. Программа stringf. с

Язык программирования C. Лекции и упражнения. 6-е издание



Язык программирования C. Лекции и упражнения. 6-е издание

Обратите внимание, что спецификатор %2s расширяет поле настолько, чтобы уместить все символы строки. Кроме того, спецификатор точности ограничивает количество выводимых символов. Конструкция . 5 в спецификаторе формата сообщает функции printf() о том, что нужно вывести только пять символов. Опять-таки, модификатор - выравнивает текст по левому краю.

Использование полученных знаний на практике

Итак, вы ознакомились с несколькими примерами. Как должен выглядеть оператор для вывода текста в следующей форме:

Семья NAME может стать богаче на $ХХХ.ХХ!


138 Глава 4

Здесь NAME и XXX. XX представляют значения, которые будут предоставляться в программе переменными, скажем, name [40] и cash.

Одно из возможных решений выглядит гак:

printf("Семья %s может стать богаче на $%.2f!\n", name, cash);

Что поеобоазует спецификатоо поеобоазования?

Теперь более подробно рассмотрим, что именно преобразует спецификатор преобразования. Он преобразует значение, хранящееся в памяти компьютера в двоичном формате, в последовательность символов (строку) с целью отображения. Например, число 76 может быть представлено в памяти компьютера в двоичном виде как 01001100. Спецификатор преобразования %d превращает его в символы 7 и 6, отображая 76. Преобразование %х превращает это же двоичное значение (01001100) в шестнадцатеричное представление 4с, а спецификатор %с преобразует его в символьное представление L.

Термин преобразование, возможно, в чем-то неточен, т.к. можно предположить, что исходное значение заменяется преобразованным. Спецификаторы преобразования по существу являются спецификаторами трансляции; к примеру, %d означает “транслировать заданное значение в десятичное целочисленное текстовое представление и затем вывести его”.

Несовпадающие преобразования

Естественно, спецификатор преобразования должен соответствовать типу выводимого значения. Часто вам доступно несколько вариантов. Например, для вывода значения типа int можно применять спецификатор %d, %х или %o. Все эти спецификаторы предполагают, что вы выводите значение типа int; они просто предоставляют различные представления этого значения. Аналогично, спецификаторы %f, %е или %д можно использовать для представления типа double.

Что произойдет, если спецификатор преобразования не соответствует типу? В предыдущей главе вы уже видели, что несоответствия могут вызвать проблемы. Это очень важный аспект, который следует иметь в виду, так что в листинге 4.11 приведено еще несколько примеров несоответствия при работе с семейством целочисленных типов.

Листинг 4.11. Программа intconv.c

Язык программирования C. Лекции и упражнения. 6-е издание



Символьные строки и форматированный ввод-вывод 139

В нашей системе были получены следующие результаты:

num как тип short и тип unsigned short: 336 336 -num как тип short и тип unsigned short: -336 65200 num как тип int и тип char: 336 Р WORDS тип int, short и char: 65618 82 R

Взглянув на первую строку, вы можете заметить, что спецификаторы %hd и %hu выдают 336 в качестве вывода для переменной num; туг нет никаких проблем. Однако во второй строке версия %u (без знака) для mnum выглядит как 65200, а не как ожидаемое значение 336; это вытекает из способа представления значений типа short int со знаком в нашей системе. Во-первых, они имеют размер 2 байта. Во-вторых, для представления целых чисел со знаком система использует метод, называемый поразрядным дополнением до двойки. При таком методе числа от 0 до 32767 представляют сами себя, а числа от 32768 до 65535 представляют отрицательные числа, причем 65535 соответствует -1, 65534       2 и т.д. Следовательно, -336 представлено как 65536 - 336, или

65200. Таким образом, число 65200 представляет -336, когда интерпретируется как int со знаком, и 65200, когда интерпретируется как int без знака. Поэтому будьте осторожны! Одно число может интерпретироваться как два разных значения. Описанный метод представления отрицательных целых чисел применяется не во всех системах. Тем не менее, мораль этого примера: не рассчитывайте на то, что преобразование %u просто отбросит знак числа.

Третья строка демонстрирует, что происходит при попытке преобразования в символ значения, которое больше 255. В нашей системе тип short int занимает 2 байта, а тип char — 1 байт. Когда функция printf() выводит 336 с использованием спецификатора %с, она просматривает только один байт из двух, задействованных для хранения 336. Такое усечение (рис. 4.8) равнозначно делению целого числа на 256 с сохранением только остатка. В этом случае остаток равен 80, что представляет собой ASCII-значение символа Р. Формально можно сказать, что число интерпретируется как результат деления по модулю 256, что означает использование остатка от деления числа на 256.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.8. Интерпретация числа 336 как символа


В заключение мы попытались вывести в своей системе целое число (65618), превышающее максимально допустимое значение типа short int (32767). И снова компьютер применил деление по модулю. Число 65618 в силу своего размера хранится в нашей системе как 4-байтовое значение int. Когда оно выводится с применением спецификатора %hd, функция printf() использует только последние 2 байга, которые равносильны остатку от деления на 65536. В этом случае остаток равен 82. Остаток, находящийся между 32767 и 65536, с учетом способа хранения отрицательных чисел выводился бы как отрицательное число. В системах с другими размерами целых чисел общее поведение было бы таким же, но с другими числовыми значениями.


140 Глава 4

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

Листинг 4.12. Программа floatcnv.c

Язык программирования C. Лекции и упражнения. 6-е издание


В нашей системе код из листинга 4.12 сгенерировал следующий вывод:

3.0е+00 3.0е+00 3.1е+46 1.7е+266

2000000000 1234567890

0 1074266112 0 1074266112

Первая строка вывода показывает, что применение спецификатора %е не вызывает преобразование целого числа в число с плавающей запятой. Давайте посмотрим, что происходит при попытке вывода переменной n3 (типа long) с использованием спецификатора %е. Во-первых, спецификатор %е заставляет функцию printf() ожидать значение типа double, которое в нашей системе является 8-байтовым. Когда функция printf() исследует переменную n3, представленную в нашей системе 4-байтовым значением, она просматривает также смежные 4 байта памяти. Таким образом, функция анализирует 8-байтовый блок, в котором содержится действительное значение n3. Во-вторых, она интерпретирует биты этого блока как число с плавающей запятой. Например, некоторые биты, будут трактоваться в качестве показателя степени. Поэтому, даже если бы значение n3 содержало правильное количество битов, для спецификаторов %е и %ld они бы интерпретировались по-разному. Конечный результат оказывается бессмысленным.

Первая строка вывода также иллюстрирует то, что упоминалось ранее — при передаче в виде аргумента функции printf() значение float преобразуется в тип double. В данной системе тип float занимает 4 байта, но переменная nl была расширена до 8 байтов, чтобы функция printf() смогла корректно отобразить ее значение.

Вторая строка вывода показывает, что функция printf() может правильно выводить значения n3 и n4, если указан корректный спецификатор.

Третья строка вывода демонстрирует, что даже правильный спецификатор может приводить к ложным результатам, если оператор printf() содержит несоответствия где-то в другом месте. Как и можно было ожидать, попытка вывода значения с плавающей запятой с применением спецификатора %ld оказывается неудачной, однако в данном случае неудачу терпит и попытка вывода значения типа long с использованием спецификатора % &ld! Проблема кроется в способе передачи информации функции. Точные детали отказа при выводе зависят от реализации, но во врезке “Передача аргументов” обсуждается поведение в типичной системе.


Символьные строки и форматированный ввод-вывод 141

Передача аргументов

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

printf ("%ld %ld %ld %ld\n", nl, n2, n3, n4);

Этот вызов сообщает компьютеру о том, что ему передаются значения переменных nl, n2, n3 и n4. Ниже описан один из распространенных способов обработки этой ситуации. Программа помещает значения в область памяти, которая называется стеком Когда компьютер помещает эти значения в стек, он руководствуется типами переменных, а не спецификаторами преобразования. Следовательно, для nl он выделяет в стеке 8 байтов (float преобразуется в double). Подобным же образом для переменной n2 отводится еще 8 байтов, после чего по 4 байта выделяется для переменных n3 и n4. Затем управление передается функции printf(). Эта функция читает значения из стека, но делает это согласно спецификаторам преобразования. Спецификатор %ld указывает, что функция printf() должна прочитать 4 байта, поэтому она считывает первые 4 байта в стеке в качестве своего первого значения. Прочитанные 4 байта представляют собой первую половину nl, которая интерпретируется как целочисленное значение long. Следующий спецификатор %ld обеспечивает чтение еще 4 байтов; это вторая половина nl, и она интерпретируется как второе целочисленное значение long (рис. 4.9). Аналогично третий и четвертый спецификаторы %ld приводят к чтению первой и второй половины n2 с последующей их интерпретацией в качестве еще двух целочисленных значений long, так что, хотя для переменных n3 и n4 указаны корректные спецификаторы, функция printf() читает не те байты, которые нужны.

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 4.9. Передача аргументов



142 Глава 4

Возвращаемое значение функции printf()

Как упоминалось в главе 2, функция в языке С в общем случае имеет возвращаемое значение — это то, что она вычисляет и возвращает в вызывающую программу. Например, в библиотеке С содержится функция sqrt(), которая принимает число в качестве аргумента и возвращает его квадратный корень. Возвращаемое значение может быть присвоено переменной, участвовать в вычислениях, передаваться как аргумент — словом, им можно манипулировать подобно любому другому значению. Функция printf() также имеет возвращаемое значение — количество выведенных символов. Если произошла ошибка вывода, printf() возвратит отрицательное значение. (Некоторые старые версии printf() имели другие возвращаемые значения.)

Возвращаемое значение функции printf() является побочным эффектом ее главной задачи вывода данных и обычно не используется. Единственной причиной работы с возвращаемым значением printf() является необходимость проведения проверки на предмет наличия ошибок вывода. Чаще всего это делается при записи в файл, а не при выводе на экран. Например, если запись на CD- или DVD-диск невозможна из-за его переполнения, программа могла бы предпринимать подходящее действие, такое как выдача звукового сигнала в течение 30 секунд. Тем не менее, прежде чем можно будет реализовать это, необходимо изучить условный оператор if. Простой пример в листинге 4.13 демонстрирует работу с возвращаемым значением.

Листинг 4.13. Программа prntva1.с

Язык программирования C. Лекции и упражнения. 6-е издание


Вот вывод этой программы:

Вода закипает при 212 градусах по Фаренгейту.

Функция printf() вывела 4 6 символов.

Во-первых, для присваивания возвращаемого значения переменной rv в программе применяется оператор вида rv = printf (...);. Он решает две задачи: выводит информацию и присваивает значение переменной. Во-вторых, обратите внимание, что итоговый результат включает все выведенные символы, в том числе пробелы и невидимый символ новой строки.

Вывод ДЛИННЫХ строк

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


Символьные строки и форматированный ввод-вывод 143

Например, в листинге 4.13 оператор printf() находится в двух строках:

printf("Функция printf() вывела %d символов.\n", rv);

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

Однако нельзя разрывать строку внутри кавычек. Предположим, что вы пытаетесь сделать что-то в этом роде:

printf("Функция printf() вывела %d символов.\n", rv);

В таком случае компилятор сообщит об использовании недопустимого символа в строковой константе. Вы можете включить в строку символ \n, чтобы обозначить символ новой строки, но не можете иметь внутри строки действительный символ новой строки, сгенерированный нажатием клавиши <Enter> (<Return>).

Когда требуется разбить строку, для этого существуют три возможности, продемонстрированные в листинге 4.14.

Листинг 4.14. Программа longstrg.c

Язык программирования C. Лекции и упражнения. 6-е издание

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

Вот один из способов вывода длинной строки.

Вот второй способ вывода длинной строки.

А вот самый новый способ вывода длинной строки.


Первый метод предусматривает применение более одного оператора printf(). Поскольку первая выведенная строка не заканчивается символом \n, вторая строка продолжается с конца первой.

Второй метод предполагает завершение первой строки комбинацией обратной косой черты и нажатия клавиши <Enter>. Это приводит к тому, что текст на экране начинается с новой строки, но не помещает символ новой строки внутрь строки. В результате строка продолжается на следующей строке экрана. Однако, как видно в листинге, следующая строка экрана должна начинаться с крайней левой позиции. Если вы добавите отступ длиной, скажем, пять пробелов, то эти пять пробелов станут частью строки.

Третий метод, введенный в стандарте ANSI С, называется конкатенацией строк. Если одна строковая константа, заключенная в кавычки, следует за другой такой константой, и они разделены только пробельными символами, то эта комбинация трактуется языком как единая строка. Таким образом, следующие три формы эквивалентны:


144 Глава 4

printf("Привет юным влюбленным, где бы они ни были.");

printf("Привет юным" "влюбленным" ", где бы они ни были.");

printf("Привет юным влюбленным"

", где бы они ни были.");

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

Использование функции scanf()

Теперь давайте перейдем от вывода к вводу и исследуем функцию scanf(). Библиотека С содержит несколько функций ввода, и scanf() является наиболее универсальной из них, т.к. она способна считывать в разных форматах. Разумеется, вводимые с клавиатуры данные являются текстом, поскольку нажатие клавиш приводит к генерации текстовых символов: букв, цифр и знаков препинания. Когда вы хотите ввести, скажем, целое число 2014, вы вводите с клавиатуры символы 2, 0, 1 и 4. Если вы хотите сохранить его как числовое, а не строковое значение, то программа должна выполнить посимвольное преобразование строки в числовое значение — именно это и делает функция scanf(). Она преобразует строковый ввод в разнообразные фор мы: целые числа, числа с плавающей занятой, символы и строки С. Ее действие противоположно действию функции printf(), которая преобразует целые числа, числа с плавающей запятой, символы и строки С в текст, который затем отображается на экране.

Подобно printf(), в функции scanf() используется управляющая строка, за которой следует список параметров. Управляющая строка указывает целевые типы данных для потока вводимых символов. Главное различие между ними связано со списком аргументов. В функции printf() применяются имена переменных, константы и выражения, а в scanf() — указатели на переменные. К счастью, для использования этой функции знание указателей не требуется. Достаточно запомнить следующие простые правила.

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

•   Если вы применяете функцию scanf() для чтения строки в символьный массив, символ & не нужен.

В листинге 14.15 показана короткая программа, иллюстрирующая эти правила. Листинг 4.15. Программа input.с

Язык программирования C. Лекции и упражнения. 6-е издание



Символьные строки и форматированный ввод-вывод 145

Ниже приведен пример взаимодействия с программой:

Введите информацию о своем возрасте, сумме в банке и любимом животном.

38

92360.88 лама

38 $92360.88 лама

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

Введите информацию о своем возрасте, сумме в банке и любимом животном.

42

2121.45

гуппи

42 $2121.45 гуппи

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

В scanf() применяется в основном тот же набор спецификаторов преобразования, что и в printf(). Главное отличие в том, что в функции printf() используются спецификаторы %f, %е, %Е, %д и %G для типов float и double, тогда как в scanf() они применяются только для типа float, требуя указания модификатора 1 для типа double. В табл. 4.6 перечислены основные спецификаторы преобразования, как они описаны в стандарте С99.

Таблица 4.6. Спецификаторы преобразования ANSI С для функции scanf()

Язык программирования C. Лекции и упражнения. 6-е издание



146 Глава 4

В спецификаторах преобразования можно также использовать модификаторы, перечисленные в табл. 4.6. Модификаторы размещаются между знаком % и буквой преобразования. В случае указания в спецификаторе нескольких модификаторов они должны появляться в том же самом порядке, как они описаны в табл. 4.7.

Таблица 4.7. Модификаторы преобразования функции scanf()

Язык программирования C. Лекции и упражнения. 6-е издание


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


Символьные строки и форматированный ввод-вывод 147

Обработка ввода функцией scanf()

Давайте более подробно рассмотрим, как функция scanf() считывает поток вводимых данных. Предположим, что вы применяете спецификатор %d, чтобы прочитать целое число. Функция scanf() начинает читать поток ввода по одному символу за раз. Она пропускает пробельные символы (символы пробела, табуляции и новой строки) до тех пор, пока не натолкнется на символ, отличный от пробельного. Поскольку функция scanf() пытается прочитать целое число, она ожидает обнаружить цифровой символ или, возможно, знак (+ или -). Встретив цифру или знак, она запоминает этот символ и считывает следующий. Если это цифра, она сохраняет ее и читает следующий символ. Функция scanf() продолжает чтение и сохранение символов, пока не столкнется с нецифровым символом. Тогда функция приходит к заключению, что она достигла конца очередного целого числа. Функция scanf() помещает этот нецифровой символ обратно в поток ввода. Это означает, что в следующий раз, когда программа приступит к чтению потока ввода, она начнет его с ранее отклоненного нецифрового символа. Наконец, функция scanf() вычисляет числовое значение, соответствующее считанным ею цифрам (и, возможно, знаку), и заносит это значение в указанную переменную.

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

А что случится, если первый отличный от пробельного символ представляет собой, скажем, символ А, а не цифру? В таком случае функция scanf() тут же останавливается и помещает символ А (или другой) обратно в поток ввода. Указанной переменной значение не присваивается, и в следующий раз, когда программа будет читать поток ввода, она снова начнет его с А. Если вы применяете в программе только спецификаторы %d, то функция scanf() никогда не продвинется дальше этого символа А. Кроме того, если вы используете scanf() с несколькими спецификаторами, то язык С требует, чтобы функция прекращала чтение потока ввода при первом же отказе.

Чтение потока ввода с применением других числовых спецификаторов происходит так же, как в случае спецификатора %d. Главное различие между ними заключается в том, что функция scanf() может распознавать больше символов в качестве части числа. Например, спецификатор %х требует, чтобы функция scanf() распознавала символы a-f и А-F как шестнадцатеричные цифры. Спецификаторы с плавающей запятой требуют, чтобы функция scanf() распознавала десятичные точки, экспоненциальную форму записи и новую р-нотацию.

Если вы используете спецификатор %s, то допускается любой символ, отличный от пробельного, поэтому функция scanf() пропускает пробельные символы до появления первого непробельного символа, после чего сохраняет все неиробельные символы вплоть до следующего появления пробельного символа. Это означает, что спецификатор %s заставляет функцию scanf() читать одиночное слово, т.е. строку, которая не содержит пробельных символов. В случае указания ширины поля scanf() прекращает чтение при достижении конца поля или на первом пробельном символе, в зависимости от того, что произойдет раньше. С помощью ширины поля нельзя заставить функцию scanf() читать более одного слова для одного спецификатора %s. И последний момент: когда функция scanf() помещает строку в назначенный массив, она добавляет завершающий символ ‘\0’ с тем, чтобы сделать содержимое массива строкой С.

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


148 глава 4

В действительности функция scanf() не относится к числу наиболее часто используемых функций ввода в С. Она рассматривается здесь по причине своей универсальности (т.к. умеет читать все многообразие типов данных). В языке С доступно несколько других функций ввода вроде getchar() и fgets(), которые лучше подходят для решения специфичных задач, например, чтения одиночных символов или чтения строк, содержащих пробелы. Некоторые из этих функций будут рассмотрены в главах 7, 11 и 13. А пока для ввода целого числа или десятичной дроби, символа или строки можете применять функцию scanf().

Обычные символы в строке формата

Функция scanf() позволяет помещать в строку формата обычные символы. Вводимая с трока должна обеспечивать точное совпадение для обычных символов, отличных от пробельных. Например, предположим, что вы непредумышленно поместили занятую между двумя спецификаторами:

scanf("%d,%d", &n, &m);

Функция scanf() интерпретирует эту строку так, что вам придется набрать число, затем запя тую и, наконец, второе число. То есть вы должны вводить два целых числа следующим образом:

88, 121

Поскольку в строке формата запятая находится непосредственно после спецификатора %d, ее требуется набирать сразу после числа 88. Тем не менее, с учетом того, что scanf() пропускает пробельные символы, предшествующие целому числу, при вводе можно было бы набрать пробел или символ новой строки. Другими словами, показанные ниже варианты также будут приемлемыми:

88, 121

и

88,

121

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

scanf("%d,%d", &n, &m); принял бы любую из следующих входных строк:

88,121

88 ,121

88 , 121

Обратите внимание, что концепция “любые пробельные символы” охватывает также специальный случай отсутствия пробельных символов.

За исключением %с все остальные спецификаторы автоматически пропускают пробельный символ, предваряющий вводимое значение, так что оператор scanf ("%d%d", &n, &m) ведет себя точно так же, как scanf ("%d %d", &n, &m). Для спецификатора %c наличие или отсутствие символа пробела в строке формата не вносит никакой разницы. Например, если в строке формата спецификатору %с предшествует пробел, то функция scanf() пропускает все до появления первого непробельного символа. Таким образом, оператор scanf ("%с" , &ch) читает первый значащий символ, с которым сталкивается во введенных данных, a scanf (" %с", &ch) читает первый встреченный непробельный символ.


Символьные строки и форматированный ввод-вывод 149

Возвращаемое значение функции scanf()

Функция scanf() возвращает количество элементов, которые она успешно прочитала. Если не прочитано ни одного элемента, как бывает в случае набора нечисловой строки, в то время когда scanf() ожидает число, возвращается 0. При обнаружении условия, называемого “конец файла” (“end of file”), функция возвращает EOF. (EOF — это специальное значение, определенное в файле stdio.h. Обычно с помощью директивы #define константе EOF присваивается значение -1.) Мы рассмотрим признак конца файла в главе 6, а вопросы использования возвращаемого значения функции scanf() — позже в этой главе. После изучения операторов if и while вы сможете задействовать возвращаемое значение scanf() для обнаружения и обработки несогласованного ввода.

Модификатор * в функциях printf() и scanf()

И в printf(), и в scanf() модификатор * можно применять для изменения значения спецификатора, но делается это по-разному. Для начала давайте рассмотрим использование модификатора * в функции printf().

Предположим, что вы не хотите фиксировать ширину поля заранее, но желаете, чтобы ее определила сама программа. Это можно сделать, указав вместо числа, задающего ширину поля, модификатор *, но понадобится также добавить аргумент для сообщения функции, какой должна быть ширина поля. То есть при наличии спецификатора преобразования %*d список аргументов должен содержать значение для модификатора * и значение для d. Такой метод можно применять также со значениями с плавающей запятой, чтобы указывать точность и ширину поля. В листинге 4.16 приведен небольшой пример, демонстрирующий, как все это работает.

Листинг 4.16. Программа varwid.e

Язык программирования C. Лекции и упражнения. 6-е издание


Переменная width определяет щирицу поля, а переменная number — это число, которое должно быть выведено. Поскольку модификатор * предшествует d в спецификаторе, значение width находится раньше значения number в списке параметров функции printf(). Подобным образом значения width и precision предоставляют необходимую информацию для форматирования для вывода значения weight. Взгляните на пример выполнения этой программы:


150 Глава 4

Введите ширину поля:

6

Значение равно: 256:

Теперь введите ширину и точность:

8 3

Вес = 242.500 Готово!

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

В случае функции scanf() модификатор * служит совершенно другой цели. Когда он помещен между символом % и буквой спецификатора, модификатор * вынуждает функцию пропускать соответствующий ввод. В листинге 4.17 предоставлен пример.

Листинг 4.17. Программа skip2.c

Язык программирования C. Лекции и упражнения. 6-е издание


Оператор scanf() в листинге 4.17 указывает программе на необходимость пропуска двух целых чисел и копирования третьего целого числа в переменную n. Ниже показан пример выполнения этой программы:

Введите три целых числа:

2013 2014 2015

Последним целым числом было 2015

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

Советы по использованию функции printf()

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

printf("%d %d %d\n", vail, val2, val3);

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

12 234 1222 4 5 23

22334 2322 10001

(Здесь предполагается, что значения переменных изменялись между выполнением операторов printf().)

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


Символьные строки и форматированный ввод-вывод 151

Например, применение оператора

printf("%9d %9d %9d\n", vail, val2, val3); дает следующий вывод:

12    234   1222

4       5     23

22334   2322  10001

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

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

printf("Каунт Беппо пробежал %.2f миль за 3 часа.\n", distance); выводит следующую фразу:

Каунт Беппо пробежал 10.22 миль за 3 часа.

Изменение спецификатора преобразования на % 10.2 f привело бы к такому результату: Каунт Беппо пробежал 10.22 миль за 3 часа.

Выбор локалн

В США и многих других странах мира для отделения целочисленной части от дробной используется точка, как в 3.14159. В то же время во множестве других стран для этого применяется запятая, как в 3,14159. Вы могли заметить, что спецификаторы функций printf() и scanf() не предусматривают формат с использованием запятой. Однако в языке С не забыли о других странах. Как описано в разделе V приложения Б, язык С поддерживает понятие покали. Это предоставляет программе С возможность выбора конкретной покали. Например, можно было бы указать локаль Нидерландов, тогда функции printf() и scanf() использовали бы локальное соглашение (в данном случае запятую) при отображении и чтении значений с плавающей запятой. Кроме того, после указания данной среды соглашение в отношении запятой применялось бы для чисел, появляющихся в коде:

double pi = 3,14159;    // локаль Нидерландов

Стандарт С требует использования одной из двух локалей: "С" и По умолчанию программы применяют локаль "С", что по существу соответствует принятому в США способу представления чисел. Локаль "" подразумевает локаль, используемую в конкретной системе. В принципе, она может совпадать с локалью "С". На практике операционные системы, такие как Unix, Linux и Windows, предоставляют обширные списки вариантов локалей, однако эти списки могут различаться.

Ключевые понятия

В языке С тип char представляет одиночный символ. Для представления последовательности символов в С применяется символьная строка. Одной из форм строки является символьная константа, в которой символы заключены в двойные кавычки, например, "Удачи, друзья! ". Вы можете хранить строку в массиве символов, который состоит из смежных байтов памяти. Символьные строки, выраженные в виде символьной константы или сохраненные в символьном массиве, завершаются скрытым символом, который называется нулевым символом.


152 Глава 4

Числовые константы целесообразно представлять в программе символически, либо посредством директивы #define, либо с помощью ключевого слова const. Символические константы делают программу более читабельной и легкой для сопровождения и модификации.

Стандартные функции ввода и вывода scanf() и printf() языка С используют систему, при которой вы должны сопоставлять спецификаторам внутри первого аргумента значения в последующих аргументах. Сопоставление, скажем, спецификатора int, такого как %d, со значением float приведет к непредсказуемым результатам. Необходимо внимательно следить за тем, чтобы количество и типы спецификаторов соответствовали остальным аргументам функции. Для scanf() не забывайте предварять имена переменных операцией взятия адреса (&).

Пробельные символы (символы табуляции, пробела и новой строки) играют критически важную роль в том, как scanf() просматривает вводимые данные. За исключением режима, устанавливаемого спецификатором %с (который читает только следующий символ), при чтении входных данных функция scanf() пропускает пробельные символы вплоть до первого непробельного символа. Затем она продолжает чтение символов до тех пор, пока не встретит пробельный символ либо символ, не подходящий для типа, для которого считывается значение. Давайте посмотрим, что происходит при вводе одной и той же информации в разных режимах ввода функции scanf(). Начнем со следующей входной строки:

-13.45е12# О

Сначала предположим, что применяется режим %d; функция scanf() прочитает три символа (-13) и остановится на точке как на следующем входном символе. Затем scanf() преобразует последовательность символов -13 в соответствующее целочисленное значение и сохранит его в целевой переменной типа int. В режиме %f функция scanf() прочитает символы -13.45Е12 и остановится на символе #, оставив его для последующего ввода. Далее она преобразует последовательность символов -13.45Е12 в соответствующее значение с плавающей запятой и сохранит его в целевой переменной типа float. В случае режима %s функция scanf() прочитает последовательность символов -13.45E12# и остановится на пробеле как на следующем символе для ввода. Затем она сохранит коды всех этих десяти символов в целевом символьном массиве, добавив в конец нулевой символ. При чтении этой же строки в режиме %с функция scanf() прочитает и сохранит первый символ, т.е. пробел.

Резюме

Строка — это последовательность символов, трактуемая как отдельная единица. В языке С строка представлена последовательностью символов, завершающейся нулевым символом, ASCH-код которого равен 0. Строки могут храниться в символьных массивах. Массив — это последовательность элементов, имеющих один и тот же тип. Чтобы объявить массив name, содержащий 30 элементов типа char, используйте следующий оператор:

char name [30];

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

Строковые константы создаются путем заключения строки в двойные кавычки:

"Это пример строковой константы"


Символьные строки и форматированный ввод-вывод 153

Язык программирования C. Лекции и упражнения. 6-е издание
Функцию strlen() (объявленную в заголовочном файле string.h) можно применять для выяснения длины строки (без учета завершающего нулевого символа). Функция scanf(), будучи вызванной вместе со спецификатором %s, может использоваться для чтения строк, состоящих из одного слова.

Препроцессор языка С ищет в исходном тексте программы директивы препроцессора, которые начинаются с символа #, и действует согласно им до начала процесса компиляции программы. Директива #include заставляет препроцессор добавить содержимое другого файла в текущий файл там, где эта директива находится. Директива #define позволяет определять символические константы. В заголовочных файлах limits.h и float.h директива #def ine применяется для определения набора констант, представляющих разнообразные свойства целочисленных типов и типов с плавающей запятой. Для создания символических констант можно также использовать модификатор const.

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

Вопросы для самоконтроля

Ответы на эти вопросы находятся в приложении А.

1.  Запустите программу из листинга 4.1 еще раз, и когда программа запросит ввод имени, введите имя и фамилию. Что происходит? Почему?

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

а. printf("Он продал эту картину за $%2.2f.\n", 2.345е2);

б. printf ("%c%c%c\n", 'Н', 105, '\41');

в. #define Q "Его Гамлет был хорош, и без намека на вульгарность." printf("%s\nсодержит %d символов.\n", Q, strlen(Q));

г. printf("Является ли %2.2е тем же, что и %2.2f?\n", 1201.0, 1201.0);

Язык программирования C. Лекции и упражнения. 6-е издание


3.  Какие изменения необходимо сделать в пункте в) второго вопроса, чтобы строка Q была выведена в двойных кавычках?


154 Глава 4

5.  Предположим, что программа начинается так:

#define BOOK "Война и мир" int main(void)

{

float cost = 12.99; float percent = 80.0;

Напишите оператор printf(), который использует BOOK, cost и percent для следующего вывода:

Данный экземпляр книги "Война и мир" стоит $12.99.

Это 80% от цены в прайс-листе.

6.  Какие спецификаторы преобразования вы бы использовали, чтобы вывести следующие данные?

а.   Десятичное целое число с шириной поля, равной количеству цифр этого числа.

б.   Шестнадцатеричное целое число в форме 8А с шириной поля 4 символа.

в.    Число с плавающей запятой в форме 232.346 с шириной поля 10 символов.

г.    Число с плавающей запятой в форме 2.33е+002 с шириной поля 12 символов.

д.   Строку, выровненную по левому краю внутри поля шириной 30 символов.

7.  Какие спецификаторы преобразования вы бы использовали, чтобы вывести следующие данные?

а.   Целое число типа unsigned long в поле шириной 15 символов.

б.   Шестнадцатеричное целое число в форме 0x8а в поле шириной 4 символа.

в.  Число с плавающей запятой в форме 2.33Е+02 в поле шириной 12 символов с выравниванием по левому краю поля.

г.    Число с плавающей запятой в форме +232.346 в поле шириной 10 символов.

д.   Первые 8 символов строки в поле шириной 8 символов.

8.  Какие спецификаторы преобразования вы бы использовали, чтобы вывести следующие данные?

а.  Десятичное целое число, имеющее минимум 4 цифры, в поле шириной 6 символов.

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

в.    Символ в поле шириной 2 символа.

г.  Число с плавающей запятой в форме +3.13 в поле с шириной, которая равна количеству символов в этом числе.

д.  Первые пять символов в строке, выровненной по левому краю поля шириной 7 символов.

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

а.   101

б. 22.32 8. 34Е-09

в. linguini

г. catch 22

д. catch 22 (но пропустить catch)


Символьные строки и форматированный ввод-вывод 155

10.  Что такое пробельный символ?

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

printf("Тип double состоит из %z байтов..\n", sizeof (double));

12. Предположим, что в своих программах вы хотели бы использовать круглые скобки вместо фигурных. Насколько хороню бы работали следующие конструкции?

#define ( (

#define ) }

Упражнения по программированию

1. Напишите программу, которая запрашивает имя и фамилию, а затем выводит их в формате фамилия, имя.

2. Напишите программу, которая запрашивает имя и выполняет с ним следующие действия.

а.   Выводит его заключенным в двойные кавычки.

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

в.  Выводит его с левого края поля шириной 20 символов, при этом все поле заключается в кавычки.

г.   Выводит его в поле шириной, на три символа превышающем длину имени.

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

а.   Вводом является 21.3 или 2.1е+001.

б.   Вводом является +21.290 или 2.129Е+001.

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

Ларри, ваш рост составляет 6.208 футов

Используйте тип float, а также операцию деления /. Если хотите, можете запрашивать рост в сантиметрах и отображать его в метрах.

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

При скорости загрузки 18.12 мегабит в секунду файл размером 2.20 мегабайт загружается за 0.97 секунд(ы).

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

Иван Петров 4  6


Глава 4

Язык программирования C. Лекции и упражнения. 6-е издание
Затем сделайте так, чтобы программа выводила ту же самую информацию, но с количеством символов, выровненным по началу каждого слова:

Иван Петров 4  6

7.  Напишите программу, которая присваивает переменной типа double значение 1.0/3.0 и переменной типа float значение 1.0/3.0. Отобразите каждый результат три раза: в первом случае с четырьмя цифрами справа от десятичной точки, во втором случае с двенадцатью цифрами и в третьем случае с шестнадцатью цифрами. Включите также в программу заголовочный файл float.h и выведите значения FLT_DIG и DBL DIG. Согласуются ли выведенные значения со значением 1.0/0.3?

8.  Напишите программу, которая предлагает пользователю ввести количество преодоленных миль и количество галлонов израсходованного бензина. Затем эта программа должна рассчитать и отобразить на экране количество миль, пройденных на одном галлоне горючего, с одним знаком после десятичной точки. Далее, учитывая, что один галлон равен приблизительно 3.785 литра, а одна миля составляет 1.609 километра, программа должна перевести значение в милях на галлон в литры на 100 километров (обычную европейскую меру измерения потребления горючего) и вывести результат с одним знаком после десятичной точки. Обратите внимание, что в США принято измерять пробег на единицу горючего (чем выше, тем лучше), в то время как в Европе принято измерять расход топлива на единицу расстояния (чем ниже, тем лучше). Применяйте для этих двух коэффициентов преобразования символические константы (определенные с помощью const или t#define).



5

Операции, выражения и операторы

В ЭТОЙ ГЛАВЕ...

•    Ключевые слова: while, typedef

•    Операции: = - * / % ++ — (тип)

•     Разнообразные операции языка С, включая используемые для распространенных арифметических действий

•     Приоритеты операций и значение терминов оператор и выражение

•    Удобный цикл while

•     Составные операторы, автоматическое преобразование типов и приведение типов

•    Написание функций, принимающих аргументы



158 Глава 5

Т

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

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

Введение в циклы

В листинге 5.1 показана демонстрационная программа, выполняющая несложные арифметические действия для вычисления длины ступни в дюймах, для которой подходит мужская обувь размера 9 (применяемого в США). Чтобы вы лучше смогли оценить преимущества циклов, в этой первой версии программы иллюстрируются ограничения программирования без использования циклов.

Листинг 5.1. Программа shoes1.c

Язык программирования C. Лекции и упражнения. 6-е издание


Ниже приведен вывод:

Размер обуви (мужской) длина ступни 9.0     10.31 дюймов

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


Операции, выражения и операторы 159

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

Листинг 5.2. Программа shoes2. с

Язык программирования C. Лекции и упражнения. 6-е издание



Вот как выглядит сжатая версия вывода программы shoes2.с:

Язык программирования C. Лекции и упражнения. 6-е издание

Если обувь подходит, носите ее.

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

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

shoe < 18.5

Символ < означает “меньше чем”. Переменная shoe инициализирована значением 3. О, что определенно меньше чем 18.5. Поэтому условие равно true и управление переходит на следующий оператор, который преобразует размер в дюймы. Затем программа выводит результат. Следующий оператор увеличивает значение shoe на 1.0, делая его равным 4.0:

shoe = shoe + 1.0;


160 глава 5

В этой точке управление возвращается к порции while, чтобы проверить условие. Но почему именно в этой точке? Причина в том, что в следующей строке находится закрывающая фигурная скобка (}), а код использует пару таких скобок (( }) для обозначения границ цикла while. Операторы, находящиеся между двумя фигурными скобками, повторяются. Раздел программы внутри фигурных скобок и сами фигурные скобки называются блоком. А теперь вернемся к программе. Значение 4 . О меньше 18.5, поэтому все операторы, заключенные в фигурные скобки (блок), следующие за while, повторяются. (На компьютерном жаргоне можно сказать, что программа “проходит в цикле” по этим операторам.) Это продолжается до тех пор, пока переменная shoe не достигнет значения 19.0. Тогда условие

shoe <18.5

получает значение false, потому что 19.0 не меньше 18.5. Как только это произойдет, управление передается первому оператору, следующему за циклом while. В данном случае им является финальный оператор printf().

Рассмотренную программу можно легко модифицировать для выполнения других преобразований. Например, установив SCALE в 1.8 и ADJUST в 32.0, вы получите программу, которая преобразует значение температуры по Цельсию в значение но Фаренгейту. Присвоив SCALE значение 0.6214 и ADJUST — 0, вы реализуете преобразование километров в мили. Естественно, понадобится также соответствующим образом изменить выводимые сообщения. Цикл while предоставляет в ваше распоряжение удобное и гибкое средство управления внутри программы. Теперь давайте перейдем к ознакомлению с фундаментальными операциями, которые вы можете применять в своих программах.

Фундаментальные операции

Для представления арифметических действий в языке С используются операции. Например, операция + вызывает сложение двух значений, находящиеся по обе стороны символа операции. Если термин операция кажется вам странным, подумайте о том, что вещи такого рода должны как-то называться. “Операция” представляется более удачным вариантом, чем, скажем, “эта вещь” или “арифметический транзактор”. Теперь рассмотрим операции, применяемые для базовой арифметики: =, +, -, * и /. (В языке С операция возведения в степень отсутствует. Тем не менее, библиотека стандартных математических функций С предлагает для этих целей функцию pow(). Например, pow (3.5, 2.2) возвращает значение 3.5, возведенное в степень 2.2.)

Операция присваивания: =

В языке С знак = не означает “равно”. Вместо этого им обозначается операция присваивания значения. Например, следующий оператор присваивает значение 2 002 переменной по имени bmw:

bmw = 2002;

То есть элемент, расположенный слева от знака =, представляет собой имя переменной, а элемент справа — значение, присваиваемое этой переменной. Символ = называется операцией присваивания. Еще раз: ни в коем случае не думайте, что эта строка гласит: “переменная bmw равна 2 002”. Взамен читайте ее так: “присвоить переменной bmw значение 2 002". Для этой операции действие происходит справа налево.

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

i = i + 1;


Операции, выражения и операторы 161

С точки зрения математики этот оператор не имеет смысла. После прибавления 1 к конечному числу результат не может быть “равен” исходному числу, однако как компьютерный оператор присваивания он совершенно корректен. Его смысл таков: “извлечь значение переменной по имени i, добавить 1 к этому значению и затем присвоить новое значение переменной i" (рис. 5.1).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.1. Оператор i = i + 1;


Оператор наподобие

2002 = bmw;

в С не имеет смысла (и, разумеется, не является допустимым), т.к. 2 002 — это то, что в языке С называется r-значением, которое в данном случае представляет собой просто литеральную константу. Присвоить значение константе невозможно; она уже является эквивалентом значения. Следовательно, не забывайте, что элемент слева от знака = должен быть именем переменной. В действительности левая сторона должна ссылаться на ячейку памяти. Простейший способ предполагает использование имени переменной, но как вы увидите позже, для этого можно применять “указатель”. В общем случае для пометки сущностей, которым можно присваивать значения, в С используется термин модифицируемое I-значение. Возможно, данный термин не особенно понятен, поэтому давайте введем некоторые определения.

Немного терминологии: объекты данных,

I-значения, r-значения и операнды

Рассмотрим оператор присваивания. Его назначение заключается в том, чтобы сохранить значение в ячейке памяти. Объект данных — это общий термин для обозначения области хранения данных, которая может применяться для удержания значений. Для обозначения этой концепции в стандарте языка С используется термин объект. Один из способов идентификации объекта предполагает применение имени переменной. Но, как со временем вы узнаете, для идентификации объекта существуют и другие способы. Например, вы могли бы указать элемент массива, член структуры или воспользоваться выражением указателя, которое включает в себя адрес объекта. Термин /-значение в С применяется для обозначения любого имени или выражения подобного рода, идентифицирующего конкретный объект данных. Объект относится к фактической области хранения данных, но 1-значение — это метка, которая используется для определения местоположения этой области памяти.

На заре развития языка С именование чего-либо 1-значением происходило при двух обстоятельствах.

1.  Оно указывало объект, тем самым ссылаясь на адрес в памяти.

2.  Оно могло применяться слева от символа операции присваивания; отсюда и буква “Г (от “left” — “левая часть”) в названии (значение.

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


162 Глава 5

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

В современном стандарте предлагается более точный термин: значение локатора объекта.

Термин r-значение относится к величинам, которые могут быть присвоены модифицируемым 1-значениям, но которые сами не являются 1-значениями. Например, рассмотрим следующий оператор:

bmw = 2002 ;

Здесь bmw — это модифицируемое l-значение, а 2002 — r-значение. Как вы уже, вероятно, догадались, “r” в r-значении происходит от слова “right” — “правая часть”). R-значениями могут быть константы, переменные или любое другое выражение, которое в результате дает значение, например, вызов функции. И действительно, в современном стандарте вместо термина r-значение применяется термин значение выражения.

Ниже приведен небольшой пример:

int ех;

int why;

int zee;

const int TWO = 2;

why = 42;

zee = why;

ex = TWO * (why + zee);

В этом примере ex, why и zee — модифицируемые l-значения (или значения локаторов объектов). Они могут использоваться слева или справа от символа операции присваивания. Здесь TWO — не модифицируемое 1-значение; оно может указываться только в правой части. (В контексте установки TWO в 2 операция = представляет инициализацию, а не присваивание, поэтому правило не нарушается.) В то же время 42 — это r-значение. Оно не ссылается на какую-то конкретную ячейку памяти. Кроме того, хотя why и zee — модифицируемые 1-значения, выражение (why + zee) является r-значением. Оно не представляет конкретную ячейку памяти и ему нельзя присваивать значение. Это всего лишь временное значение, которое программа вычисляет, а затем отбрасывает по завершении работы с ним.

По мере ознакомления с этими понятиями вырисовывается подходящий термин для того, что мы называем “элементом” (примером может служить фраза “элемент слева от знака =”); таким термином является операнд. Операнды — это то, чем оперируют операции. Например, процесс поедания гамбургера можно описать применением операции “поедание” к операнду “гамбургер”. Аналогично можно сказать, что левым операндом операции = должно быть модифицируемое 1-значение.

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

Листинг 5.3. Программа golf .с

Язык программирования C. Лекции и упражнения. 6-е издание




Операции, выражения и операторы 163

Язык программирования C. Лекции и упражнения. 6-е издание


Многие языки программирования не разрешают тройное присваивание значений, сделанного в этой программе, но в С это считается обычным делом. Присваивание выполняется справа налево. Вначале значение 68 получает переменная jane, затем tarzan и, наконец, это значение присваивается переменной cheeta. В результате получается следующий вывод:

чита  тарзан   джейн

Счет первого раунда 68     68    68

Операция сложения: +

Операция сложения приводит к суммированию двух значений с обеих сторон знака +. Например, оператор

printf("%d", 4 + 20);

выводит число 2 4, но не выражение

4 + 20

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

income = salary + bribes;

Напомним еще раз, что income, salary и bribes — это модифицируемые 1-значения, поскольку каждое из них идентифицирует объект данных, которому может быть присвоено значение, но выражение salary + bribes является r-значением, т.е. вычисленным значением, не идентифицируемым конкретной областью памяти.

Операция вычитания: -

Операция вычитания вызывает вычитание числа, следующего за знаком -, из числа, находящегося перед этим знаком. Например, приведенный ниже оператор присваивает переменной takehome значение 200.0:

takehome = 224.00 - 24.00;

Операции + и - называются бинарным и, или двухместными, т.е. они требуют указания двух операндов.

Операции знака: - и +

Знак “минус” может использоваться для указания или изменения алгебраического знака значения. Например, следующие операторы приводят к присваиванию переменной smokey значения 12:

rocky = -12; smokey = -ocky;

Когда знак “минус” применяется подобным образом, он называется унарной операции, которая выполняется над одним операндом (рис. 5.2).


164 Глава 5

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.2. Унарные и бинарные операции


Стандарт С90 вводит в язык С унарную операцию +. Она не меняет значения или знака операнда, но просто позволяет использовать такие операторы, как

dozen = +12;

и при этом не получать сообщений об ошибке. Раньше такая конструкция не допускалась.

Операция умножения: *

Умножение обозначается символом *. Например, следующий оператор умножает значение переменной inch на 2.54 и присваивает результат умножения переменной cm:

cm = 2.54 * inch;

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

Листинг 5.4. Программа squares.с

Язык программирования C. Лекции и упражнения. 6-е издание


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


Операции, выражения и операторы 165

Экспоненциальный рост

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

Листинг 5.5. Программа wheat.с

Язык программирования C. Лекции и упражнения. 6-е издание



Сначала выходные данные не должны были вызывать у правителя беспокойство:

Язык программирования C. Лекции и упражнения. 6-е издание


166 Глава 5

На десяти клетках мудрец получил чуть больше тысячи зерен пшеницы, но взгляните на результат для 55 клетки:

55    1.80е+16   3.60е+16     1.80е+00

Плата мудреца превысила весь мировой урожай! Если хотите выяснить, что произойдет к 64-й клетке, выполните программу самостоятельно.

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

Операция деления: /

В языке С символ / используется для обозначения деления. Значение, находящееся слева от символа /, делится на значение, указанное справа. Например, следующий оператор присваивает переменной four значение 4.0:

four = 12.0/3.0;

Деление работает по-разному для целочисленных типов и типов с плавающей запятой. В результате деления с плавающей запятой получается число с плавающей запятой, а целочисленное деление дает целое число. Так как целое число не может иметь дробной части, деление 5 на 3 не является точным, поскольку результат не содержит дробной части. В языке С любая дробная часть, полученная при делении двух целых чисел, отбрасывается. Этот процесс называется усечением.

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

Листинг 5.6. Программа divide.с

Язык программирования C. Лекции и упражнения. 6-е издание


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

Целочисленное деление: 5/4 равно 1 Целочисленное деление: 6/3 равно 2 Целочисленное деление: 7/4 равно 1 Деление с плавающей запятой: 7./4. равно 1.75 Смешанное деление: 7./4 равно 1.75

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


Операции, выражения и операторы 167

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

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

нахождение наибольшего целого значения, которое меньше или равно числу с плавающей запятой. Естественно, число 3 удовлетворяет этому требованию, если его сравнивать с 3.8. Но как быть в случае -3.8? Метод нахождения наибольшего целого числа предполагает его округление до -4, поскольку -4 меньше, чем -3.8. Однако другая точка зрения на процесс округления состояла в том, что дробная часть просто отбрасывается; при такой интерпретации, называемой усечением в направлении нуля, предполагается преобразование числа -3.8 в -3. До выхода стандарта С99 в одних реализациях применялся первый подход, а в других — второй. Но в стандарте С99 определено усечение в направлении нуля, следовательно, -3.8 преобразуется в -3.

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

приоритеты операций

Рассмотрим следующую строку кода:

butter =25.0+60.0 * n/ SCALE;

В этом операторе присутствуют операции сложения, умножения и деления. Какая из них выполнится первой? Будет ли 25.0 суммироваться с 60.0, полученный результат 85.0 умножаться на n, после чего произведение делиться на SCALE? Или же 60.0 умножится на n, к полученному произведению прибавится 2 5.0, после чего результат сложения разделится на SCALE? А, может быть, будет использоваться вообще другой порядок выполнения операции? Предположим, что n равно 6,0, a SCALE 2,0. При таких значениях первый подход даст результат 25 5, а второй — 192.5. По всей видимости, в программе на С задействован другой порядок, поскольку переменная butter в итоге получает значение 2 05.0.

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

butter =25.0+ 60.0 *n/ SCALE; операции выполняются в следующем порядке:

60.0 * n

Сначала выполняется первая операция * или / в выражении (при условии, что п равно 6, 60.0 * n дает 360.0).

360.0 / SCALE


168 Глава 5

Затем выполняется вторая операция * или / в выражении.

25.0 + 180

В завершение (поскольку SCALE равно 2.0) выполняется первая операция + в выражении, давая результат 205.0.

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

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.1. Деревья выражений, показывающие операции, операнды и порядок вычиелтия


А что, если нужно обеспечить выполнение операции сложения раньше операции деления? Тогда можно поступить так, как это сделано в следующем операторе:

flour = (25.0 + 60.0 * n) / SCALE;

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

Правила для рассмотренных операций обобщены в табл. 5.1.

Таблица 5.1. Операции в порядке снижения приоритета

Язык программирования C. Лекции и упражнения. 6-е издание


Обратите внимание, что две формы использования знака “минус” имеют разные приоритеты, как и две формы применения знака “плюс”. В столбце “Ассоциативность” указано, как операция связывается с ее операндами. Например, унарная операция - ассоциируется с величиной, находящейся справа от нее, а в случае операции деления операнд слева делится на операнд справа.


Операции, выражения и операторы 169

приоритет и порядок вычисления

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

у = 6 * 12 + 5 * 20;

Приоритеты диктуют порядок вычисления, когда две операции применяются к одному операнду. Например, 12 является операндом для двух операций, * и +, и приоритет указывает, что умножение должно выполняться первым. Аналогично, согласно приоритетам, операнд 5 должен сначала умножаться, а не суммироваться. Короче говоря, операции умножения 6*12и5*20 выполняются до того, как начнет выполняться операция сложения. При этом приоритеты не устанавливают, какая из двух операций умножения выполняется первой. Язык С оставляет этот выбор за реализацией, поскольку один выбор может оказаться более эффективным для одного оборудования, а другой выбор обеспечивает более высокую производительность на другом оборудовании. В любом случае выражение сводится к 72 + 100, так что последовательность выполнения операций умножения в этом конкретном примере не оказывает влияния на окончательный результат. Однако у вас может возникнуть вопрос: поскольку умножение выполняется слева направо, не означает ли это, что самое левое умножение выполнится первым? Правило ассоциативности применяется к операциям, которые совместно используют операнд. Например, в выражении 12 / 3 * 2 операции / и *, обладающие одинаковыми приоритетами, совместно используют операнд 3. Таким образом, в этом случае применяется правило “слева направо”, и выражение сводится к 4 * 2, т.е. 8. (Выполнение справа налево привело бы к 12 / 6, или 2. В этом случае выбор имеет значение.) В предыдущем примере две операции * не имеют общего операнда, так что правило “слева направо” не применяется.

Исследование правил

Давайте испытаем эти правила в более сложном примере (листинг 5.7).

Листинг 5.7. Программа rules .с

Язык программирования C. Лекции и упражнения. 6-е издание


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

Первым делом, вычислению в круглых скобках назначается наивысщий приоритет. Какое выражение в скобках, - (2 + 5) * 6 или (4 + 3* (2 + 3)), вычисляется первым, зависит, как мы только что обсудили, от конкретной реализации. В данном примере любой выбор приводит к получению одного и того же результата, поэтому предположим, что выражение слева вычисляется первым. Более высокий приоритет выраже-


170 Глава 5 ния в скобках означает, что в подвыражении - (2 + 5) * 6 сначала вычисляется сумма (2 + 5), которая равна 7. Далее к 7 применяется унарная операция минус, что дает -7. Теперь данное выражение принимает такой вид:

top = score =-7*6+ (4 + 3 * (2 + 3))

На следующем шаге необходимо вычислить 2 + 3. Выражение принимает вид

top = score =-7*6+ (4+3*5)

Поскольку приоритет операции * выше, чем у операции +, выражение сводится к

top = score =-7*6+ (4 + 15) а затем и к

top = score = -7 * 6 + 19

Умножаем -7 на 6 и получаем следующее выражение:

top = score = -42 + 19 Выполнив сложение, получим

top = score = -23

Теперь переменной score присваивается значение -23, после чего и top получает значение -23. Вспомните, что операция = выполняется справа налево.

Некоторые дополнительные операции

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

Операция sizeof И ТИП size_t

С операцией sizeof мы уже имели дело в главе 3. Вспомните, что она возвращает размер своего операнда в байтах. (Вспомните также, что байт в языке С определен как размер, используемый для типа char. В прошлом байт чаще всего состоял из 8 битов, но некоторые наборы символов использовали байты большего размера.) Операндом может быть конкретный объект данных, такой как имя переменной, либо им может быть тип. Если это тип, скажем, float, то операнд должен быть помещен в круглые скобки. В примере, представленном в листинге 5.8, показаны обе формы операндов.

Листинг 5.8. Программа sizeof .с

Язык программирования C. Лекции и упражнения. 6-е издание



Операции, выражения и операторы 171

В языке С указано, что операция sizeof возвращает значение типа size_t. Это целочисленный тип без знака, но не совершенно новый тип. Напротив, как вы можете вспомнить из предыдущей главы, он определен в терминах стандартных типов. В С имеется механизм typedef (обсуждаемый в главе 14), который позволяег создавать псевдонимы для существующих типов. Например, следующее определение делает real еще одним именем для типа double:

typedef double real;

Теперь можно объявить переменную типа real:

real deal; // использование typedef

Компилятор обнаружит слово real, учтет, что оператор typedef сделал real псевдонимом для double, и создаст переменную deal как относящуюся к типу double. Аналогично, в системе заголовочных файлов С оператор typedef может использоваться для того, чтобы сделать size_t синонимом unsigned int в одной системе и unsigned long в другой. Таким образом, когда вы применяете тип size_t, компилятор подставит стандартный тип, предназначенный для вашей системы.

Стандарт С99 идет на шаг дальше и предлагает %zd в качестве спецификатора функции printf() для вывода значения size_t. Если спецификатор %zd в вашей системе не реализован, вместо него можно попробовать %u или %lu.

Операция деления по модулю: %

Операция деления по модулю применяется в целочисленной арифметике. Ее результатом является остапюк от деления целого числа, стоящего слева от знака операции, на число, расположенное справа от него. Например, 13 % 5 (читается как “13 по модулю 5”) дает в результате 3, поскольку 5 умещается в 13 дважды с остатком, равным 3. Не пытайтесь выполнять эту операцию над числами с плавающей запятой. Она просто не работает.

На первый взгляд эта операция может показаться экзотическим инструментом, предназначенным только для математиков, но по существу она очень удобна и полезна. Обычно она используется, чтобы помочь управлять ходом выполнения программы. Предположим, например, что вы работаете над программой подготовки счетов, предназначенной для начисления дополнительной платы каждый третий месяц. Для этого достаточно разделить номер месяца по модулю 3 (т.е. month % 3) и проверить, не равен ли результат 0. Если равен, программа включает дополнительную плату. Это станет более понятным после ознакомления с оператором if в главе 7.

В листинге 5.9 приведен еще один пример применения операции %. В нем также демонстрируется еще один способ использования цикла while.

Листинг 5.9. Программа min sec.с

Язык программирования C. Лекции и упражнения. 6-е издание



Язык программирования C. Лекции и упражнения. 6-е издание
Язык программирования C. Лекции и упражнения. 6-е издание


Глава 5





Вот как выглядит пример вывода:

Перевод секунд в минуты и секунды!

Введите количество секунд (<=0 для выхода):

154

154 секунд - это 2 минут(ы) 34 секунд.

Введите следующее значение (<=0 для выхода) :

567

5 67 секунд - это 9 минут (ы) 27 секунд.

Введите следующее значение (<=0 для выхода) :

О

Готово!

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

Язык программирования C. Лекции и упражнения. 6-е издание


Что произойдет, если будет введено отрицательное значение? До того, как в стандарте С99 было установлено для целочисленного деления правило “усечения в направлении нуля”, существовало несколько возможностей. Но когда действует это правило, вы получаете отрицательный результат деления по модулю, если первый операнд отрицателен, и положительный результат во всех остальных случаях:



Если поведение в вашей системе оказывается другим, значит, она не поддерживает стандарт С99. В любом случае, стандарт фактически утверждает, что если а и b являются целочисленными, то вы можете вычислить а%b путем вычитания (а/b) *b из а. Например, значение -11% 5 можно вычислить следующим образом:

-11 - (-11/5) * 5 = -11 -(-2) *5 = -11 -(-10) = -1

Операции инкремента и декремента: ++ и --

Операция инкремента решает простую задачу: она увеличивает (инкрементирует) значение своего операнда на 1. Существуют две разновидности этой операции. В первом случае символы ++ располагаются перед изменяемой переменной; это префиксная форма.


Операции, выражения и операторы 173

Во втором случае символы ++ следуют сразу за переменной; это постфиксная форма. Эти две формы отличаются друг от друга по моменту выполнения инкрементирования. Сначала мы объясним подобные черты этих форм, а затем обратимся к различиям. Короткий пример, представленный в листинге 5.10, демонстрирует работу операции инкремента.

Листинг 5.10. Программа add_one.c

Язык программирования C. Лекции и упражнения. 6-е издание


Выполнение программы add_one.c генерирует следующий вывод:

super = 1, ultra = 1 super = 2, ultra = 2 super = 3, ultra = 3 super = 4, ultra = 4 super = 5, ultra = 5

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

super = super + 1; ultra = ultra + 1;

Это достаточно простые операторы. Зачем создавать еще одно сокращение, не говоря уже о двух? Одна из причин заключается в том, что компактная форма позволяет улучшить читабельность и упростить программы. Данные операции придают программам изящество и элегантность, радуя глаз. Например, часть программы shoes2.c из листинга 5.2 можно переписать так:

shoe = 3.0; while (shoe < 18.5)

{

foot = SCALE * size + ADJUST;

printf("% 10.If %20.2f дюймов\n", shoe, foot);

++shoe;

}

Тем не менее, вы по-прежнему не до конца задействовали преимущества операции инкремента. Фрагмент программы можно еще больше сократить, как показано ниже:

shoe = 2.0;

while (++shoe < 18.5)

{

foot = SCALE*shoe + ADJUST;

printf("%10.If %20.2f дюймов\n", shoe, foot);

}


174 Глава 5

Здесь процесс инкрементирования и сравнения из цикла while объединены в одно выражение. Конструкции такого типа настолько часто встречаются в программах на С, что заслуживают более пристального внимания.

Во-первых, как работает такая конструкция? Все довольно просто. Значение переменной shoe увеличивается на 1, а затем сравнивается с 18.5. Если оно меньше 18.5, то операторы, заключенные в фигурные скобки, выполняются один раз. Затем shoe снова увеличивается на 1 и цикл продолжается до тех пор, пока значение shoe не станет достаточно большим. Чтобы скомпенсировать инкремент переменной shoe, выполненный перед первым вычислением значения переменной foot, мы уменьшили начальное значение shoe с 3.0 до 2.0 (рис. 5.4).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.4. Одна итерация цикла


Во-вторых, в чем преимущества такого подхода? Программа становится компактной. Но более важно то, что при этом объединяются в одном месте два процесса, которые управляют циклом. Первичным процессом является проверка того, продолжать ли выполнение цикла? В данном случае проверка заключается в выяснении, меньше ли значение переменной size, чем 18.5. Вторичный процесс изменяет значение проверяемого объекта; в данном случае размер обуви увеличивается на единицу.

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

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

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

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


Операции, выражения и операторы 175

Листинг 5.11. Программа post_pre.c

Язык программирования C. Лекции и упражнения. 6-е издание


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

a a_post b pre_b

2 12 2

Как и было задумано, значения переменных а и b увеличились на единицу. Однако a post содержит значение переменной а перед изменением, а b prе — значение переменной b после изменения. Именно в этом заключается отличие между префиксной и постфиксной формами операции инкремента (рис. 5.5):

a_post = а++; // постфиксная форма: переменная а меняется после

// использования ее значения

b_pre= ++b;  // префиксная форма: переменная b меняется до использования

// ее значения

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

while (++shoe < 18.5)

Такая проверка условия завершения цикла позволяет получить таблицу значений вплоть до размера 18. Если вы укажете shoe++ вместо ++shoe, то таблица расширится до размера 19, т.к. значение shoe будет увеличиваться после сравнения, а не до него.

Конечно, вы могли бы возвратиться к менее элегантной форме:

shoe = shoe + 1;

но тогда никто не поверит, что вы настоящий программист на языке С.

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

b = ++i; // если используется i++, значение переменной b будет другим

применяйте

++i;  // строка 1

b = i; // переменная b получит то же значение, даже если в строке 1 указать i++


176 Глава 5

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.5. Префиксная и постфиксная формы инкремента


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

декрементирование: —

Для каждой формы операции инкремента имеется соответствующая форма операции декремента. Вместо ++ указывайте —:

--count; // префиксная форма операции декремента count--; // постфиксная форма операции декремента

Листинг 5.12 служит иллюстрацией того, что компьютер может быть опытным поэтом.

Листинг 5.12. Программа bottles. с

Язык программирования C. Лекции и упражнения. 6-е издание

Вывод начинается примерно так:

100 бутылок родниковой воды на полке, 100 бутылок родниковой воды! Возьмите одну из них и пустите по кругу,

99 бутылок минеральной воды!

99 бутылок родниковой воды на полке, 99 бутылок родниковой воды! Возьмите одну из них и пустите по кругу,

98 бутылок родниковой воды!

Все это продолжается некоторое время и закачивается следующим образом:

1 бутылок родниковой воды на полке, 1 бутылок родниковой воды!

Возьмите одну из них и пустите по кругу,

0 бутылок родниковой воды!


Операции, выражения и операторы 177

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

Кстати, операция > означает “больше чем”. Как и < (“меньше чем”), она является операцией оттаения. Более подробно операции отношений рассматриваются в главе 6.

Приоритеты операций

Операции инкремента и декремента имеют очень высокий приоритет; выше по приоритету только скобки. Поэтому х*у++ означает (х) * (у++), но не (х*у)++, что благоприятно, т.к. последняя конструкция не имеет смысла. Операции инкремента и декремента применяются только к переменным (или, в общем случае, к модифицируемым 1-значепиям), а само произведение х*у не является модифицируемым 1-значением, хотя его части таковыми являются.

Не путайте приоритеты этих двух операций с порядком их вычисления. Предположим, мы имеем такой код:

у = 2;

n = 3;

nextnum = {у + n++) * 6;

Какое значение получит nextnum? Подстановка значений дает следующее:

nextnum = (2 + 3)*6 = 5*6 = 30

Значение переменной n увеличивается до 4 только после ее использования. Приоритет операции говорит о том, что операция ++ применяется только к n, но не к у + n. Он также указывает, когда значение n используется для вычисления выражения, но момент изменения значения n определяется природой операции инкремента.

Когда n++ является частью выражения, можно считать, что это означает “использовать переменную n, а затем увеличить ее значение на единицу”. С другой стороны, ++п означает “увеличить значение переменной n на единицу, а затем использовать ее”.

Не умничайте

Слишком частое применение операции инкремента быстро приводит к путанице. Например, может показаться, что программу для вывода целых чисел и их квадратов squares.с (листинг 5.4) удастся улучшить, заменив в ней цикл while таким циклом:

while (num < 21)

{

printf("%10d %10d\n", num, num*num++);

}

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

В результате вместо того, чтобы вывести

5                 25 она может вывести

6       25


178 глава 5

Она даже может работать справа налево, используя 5 в качестве значения крайнего правого аргумента num и 6 в качестве значения следующих двух аргументов, приводя к такому выводу:

6                 30

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

ans = num/2 + 5*(1 + num+ + );

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

Еще одним источником проблем может стать такая конструкция:

n = 3;

у = n++ + n++;

Действительно, после выполнения второго оператора значение n увеличивается на 2, но значение у будет неопределенным. Компилятор может применить старое значение n дважды при вычислении значения у, а затем дважды увеличить значение п на единицу. При этом у принимает значение 6, a n  значение 5, либо он может использовать старое значение один раз, увеличить значение n один раз, задействовать это значение во втором экземпляре n в выражении, а затем инкрементировать n во второй раз. В таком случае у принимает значение 7, а n — значение 5. Возможен как первый, так и второй вариант. Точнее говоря, результат не определен, т.е. стандарт С не регламентирует, каким должен быть результат.

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

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

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

С другой стороны, в языке С имеются определенные гарантии относительно того, когда выполняется инкрементирование. Мы вернемся к этой теме, когда будем рассматривать точки следования в разделе “Побочные эффекты и точки следования” далее в главе.

Выражения и операторы

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


Операции, выражения и операторы 179

Выражения

Выражение состоит из комбинации операций и операндов. (Вспомните, что операнд — это то, над чем выполняется операция.) Простейшим выражением является отдельный операнд, и он может служить отправной точкой для построения более сложных выражений. Ниже приведено несколько примеров выражений:

4

-6

4 + 21

a*(b + c/d)/20

q = 5*2

х = ++q % 3

q > 3

Как видите, операндами могут быть константы, переменные и их сочетания. Некоторые выражения представляют собой комбинации выражений меньших размеров, называемых подвыражениями. Например, в четвертом примере c/d является подвыражением.

Каждое выражение имеет значение

Важное свойство языка С заключается в том, что каждое выражение имеет значение. Чтобы найти это значение, нужно выполнить операции в порядке, определенном приоритетами операций. Значения нескольких первых приведенных выше выражений очевидны, но что можно сказать о выражениях со знаком =? Эти выражения просто принимают те же значения, что и переменные, находящиеся слева от знака =. Поэтому выражение q=5*2 как единое целое получает значение 10. А что можно сказать о выражении q>3 ? Такие выражения отношения получают значение 1, если выражение истинно, и 0, если оно ложно. Ниже приведены несколько выражений и их значения.

Язык программирования C. Лекции и упражнения. 6-е издание

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

Операторы

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

legs = 4

является всего лишь выражением (которое может быть частью другого выражения), но

legs = 4;

становится оператором.


180 Глава 5

Простейшим из возможных считается пустой оператор:

; // пустой оператор

Он ничего не делает и является особым случаем инструкции.

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

8;

3 + 4;

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

х = 25;

++х;

у = sqrt(х);

Хотя оператор (во всяком случае, осмысленный оператор) — это завершенная инструкция, не все завершенные инструкции являются операторами. Рассмотрим следующий оператор:

х = 6 + (у = 5) ;

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

Язык программирования C. Лекции и упражнения. 6-е издание


До сих пор мы уже встречались с пятью видами операторов (не считая пустого оператора). В листинге 5.13 показан короткий пример, в котором применяются все эти пять видов операторов.





Давайте обсудим код в листинге 5.13. К этому моменту вы должны быть хорошо знакомы с оператором объявления. Тем не менее, напоминаем, что он устанавливает имена и типы переменных и выделяет для них пространство в памяти. Обратите внимание, что оператор объявления не является оператором выражения. То есть удаление символа точки с запятой ведет к чему-то, что не является выражением и не имеет значения:

Язык программирования C. Лекции и упражнения. 6-е издание
/* это не выражение, оно не имеет значения */


Операции, выражения и операторы 181

Оператор присваивания — это “рабочая лошадка” многих программ; он присваивает значение переменной. Данный оператор состоит из имени переменной, за которым следует операция присваивания (=), а за ней — выражение, сопровождаемое точкой с запятой. Обратите внимание, что оператор while в примере содержит в себе оператор присваивания. Оператор присваивания представляет собой пример оператора выражения.

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

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.6. Структура проспюго цикла while


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

Оператор return завершает выполнение функции.

Побочные эффекты и точки следования

Введем еще несколько терминов языка С. Побочный эффект — это модификация объекта данных или файла. Например, побочный эффект оператора

states = 50;

заключается в том, что переменная states устанавливается в 50. Но почему “побочный эффект’? Это больше похоже на основную цель оператора! Однако с точки зрения языка С основная цель состоит в вычислении выражений. Покажите С выражение 4 + 6, и С вычислит его значение Ю. Покажите выражение states = 50, и С вычислит его значение 50. С вычислением этого выражения связан побочный эффект, заключающийся в изменении значения переменной states на 50. Операции инкремента и декремента, подобно операции присваивания, имеют свои побочные эффекты и используются главным образом из-за этих побочных эффектов.


182 Глава 5

Аналогично, при вызове функции printf() факт отображения ею информации считается побочным эффектом. (Вспомните, что значением функции printf() является количество отображенных элементов.)

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

Что такое полное выражение? Полное выражение — это выражение, которое не является подвыражением более крупного выражения. Примерами полных выражений могут служить выражение в операторе присваивания, а также выражение, применяемое в условии проверки для цикла while.

Точки следования позволяют прояснить, когда происходит постфиксное инкрементирование. Рассмотрим в качестве примера следующий код:

while (guests++ < 10)

printf("%d \n", guests);

Иногда начинающие программисты на С полагают, что фраза “использовать значение и затем инкрементировать его” означает в данном контексте увеличение значения переменной guests после ее использования в операторе printf(). Однако guests++< 10 — это полное выражение, поскольку оно представляет собой условие проверки цикла while и, следовательно, конец этого выражения является точкой следования. Таким образом, язык С гарантирует, что побочный эффект (инкрементирование guests) произойдет до того, как программа перейдет к выполнению printf(). Тем не менее, применение постфиксной формы гарантирует то, что переменная guests будет инкрементирована после ее сравнения со значением 10.

Теперь рассмотрим следующий оператор:

у = (4 + х++) + (6 + х++);

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

Составные операторы (блоки)

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

/* фрагмент 1 */

index = 0;


Операции, выражения и операторы 183

while (index++ < 10)

sam = 10 * index + 2;


printf("sam= %d\n", sam);

/* фрагмент 2 */ index = 0;

while (index++ < 10)

{

sam = 10 * index + 2; printf("sam = %d\n", sam);

}

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

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

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 5.7. Цикл while с составным оператором


СОВЕТ. Советы касательно стиля

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

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

while (index++ < 10) { sam = 10*index + 2; printf ("sam = %d \n", sam) ;

}


184 Глава 5

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

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

Сводка: выражения и операторы Выражения

Выражение представляет собой комбинацию операций и операндов. Простейшим выражением является константа или переменная без операции, например, 2 2 или beebop. Более сложные выражения выглядят подобно 55 + 22nvap = 2 * (vip + (vup = 4)).

Операторы

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

Язык программирования C. Лекции и упражнения. 6-е издание
int toes; toes = 12;

printf("%d\n", toes); while (toes < 20) toes = toes + 2; return 0;

; /* ничего не делает */

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

while (years < 100)

{

wisdom = wisdom * 1.05;

printf("%d %d\n", years, wisdom);

years = years + 1;

}

Преобразования типов

Операторы и выражения обычно должны использовать одни и те же типы выражений и констант. Однако если вы смешиваете типы, то язык С этому не препятствует, как это делает, скажем, Pascal. Взамен применяется набор правил, обеспечивающих автоматическое преобразование типов данных. Это может быть удобным, но также и опасным, особенно если смешивание типов происходит неумышленно. (Программа lint, входящая в состав многих систем Unix, проверяет наличие “конфликтов” между типами. Многие компиляторы С, которые ориентированы на системы, отличные от Unix, информируют о возможных проблемах с типами, если выбран высокий уровень сообщений об ошибках.) Неплохо иметь хотя бы общее представление о правилах преобразования типов.

Ниже описаны базовые правила преобразования типов данных.

1. Находясь в выражении, типы char и short (как signed, так и unsigned) автоматически преобразуются в int или при необходимости в unsigned int. (Если тип short имеет такой же размер, как у int, то размер типа unsigned short больше, чем int; в этом случае unsigned short преобразуется в unsigned int.) В K&R С, но не в текущей версии языка тип float автоматически преобразует-


Операции, выражения и операторы 185

ся в double. Поскольку они являются преобразованиями в большие по размеру типы, они называются повышением.

2.  Если в любую операцию вовлечены два типа, оба значения приводятся к более высокому из этих двух типов.

3.  Порядок типов от высшего к низшему выглядит так: long double, double, float, unsigned long long, long long, unsigned long, long, unsigned int и int. Возможно одно исключение, когда long и int имеют одинаковые размеры; в этом случае unsigned int nревосходит long. Типы short и char в этом списке отсутствуют, т.к. они уже должны были повыситься до int или, возможно, до unsigned int.

4.  В операторе присваивания финальный результат вычислений преобразуется к типу переменной, которой присваивается значение. Процесс может привести к повышению типа, как описано в правиле 1, или к понижению типа, при котором значение преобразуется в более низкий тип.

5.  При передаче в качестве аргументов функции типы char и short преобразуются в int, a float — в double. Это автоматическое повышение переопределяется прототипированием функций, как будет показано в главе 9.

Повышение обычно представляет собой гладкий процесс без особых происшествий, но понижение может привести к реальной проблеме. Причина проста: типа более низкого уровня может оказаться недостаточно для сохранения полного числа. Например, 8-битная переменная char может хранить целочисленное значение 101, по не 22334. Что происходит, когда преобразованное значение не умещается в целевой тип? Ответ зависит от задействованных типов. Ниже приведены правила для случаев, когда присвоенное значение не помещается в конечном типе.

1.  Когда целевым является одна из форм целочисленного типа без знака, а присвоенное значение представляет собой целое число, лишние биты, делающие значение слишком большим, игнорируются. Например, если целевой тип — 8-битный unsigned char, то присвоенным значением будет результат деления исходного значения по модулю 256.

2.  Если целевым типом является целый тип со знаком, а присвоенное значение — целое число, то результат зависит от реализации.

3.  Если целевой тип является целочисленным, а присвоенное значение представляет собой значение с плавающей запятой, то поведение не определено.

А что, если значение с плавающей запятой умещается в целочисленный тип? Когда типы с плавающей запятой понижаются до целочисленного типа, они усекаются или округляются в направлении нуля. Это означает, что и 23.12, и 23.99 усекаются до 23, а -23.5 усекается до -23.

В листинге 5.14 иллюстрируется работа некоторых из описанных правил.

Листинг 5.14. Программа convert.с

Язык программирования C. Лекции и упражнения. 6-е издание



186 Глава 5

Язык программирования C. Лекции и упражнения. 6-е издание

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

ch = С, i = 67, fl = 67.00

ch = D, i = 203, fl = 339.00

Теперь ch = S

Теперь ch = P


Вот что происходит в системе, в которой реализованы 8-битный тип char и 32битный тип int.

•   Строки 9 и 10. Символ 'С сохраняется как однобайтовое ASCII-значение в переменной ch. Целочисленная переменная i получает результат целочисленного преобразования символа 'С, который представляет собой число 67, сохраненное в 4 байтах памяти. И, наконец, переменная fl получает результат преобразования с плавающей запятой значения 67, которым является 67.00.

•   Строки 11 и 14. Значение ' С' символьной переменной преобразуется в целое число 67, к которому затем добавляется 1. Результирующее 4-байтовое целое число 68 усекается до 1 байта и сохраняется в переменной ch. В случае вывода с использованием спецификатора %с число 68 интерпретируется как ASCII-код символа 1 D'.

•   Строки 12 и 14. При умножении на 2 значение переменной ch преобразуется в 4-байтовое целое (68). Результирующее целое значение (136) преобразуется в число с плавающей запятой, чтобы его можно было добавить к fl. Результат (203.0Of) преобразуется в тип int и сохраняется в i.

•   Строки 13 и 14. Значение переменной ch (' D ', или 68) преобразуется в тип с плавающей запятой для его умножения на 2.0.Значение i (203) преобразуется в значение с плавающей запятой для выполнения сложения, и результат (339.00) сохраняется в переменной fl.

•   Строки 15 и 16. Здесь предпринимается попытка понижения, когда переменной ch присваивается значение, выходящее за диапазон допустимых значений. После игнорирования лишних разрядов переменная ch в итоге получает значение, равное ASCII-коду символа 'S'. Точнее говоря, 1107 % 256 равно 83, что является кодом 'S'.

•   Строки 17 и 18. Это еще один пример попытки понижения типа, при котором значение ch устанавливается равным числу с плавающей запятой. После усечения ch получает значение, равное ASCII-коду символа ' Р'.


Операции, выражения и операторы 187

Операция приведения

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

( ТИП)

Вместо слова тип указывается необходимый тип, например, long.

Рассмотрим две приведенные ниже строки кода, в которых mice — это переменная типа int. Вторая строка содержит два приведения к типу int.

mice = 1.6 + 1.7;

mice = (int) 1.6 + (int) 1.1;

В первой строке применяется автоматическое преобразование типов. Сначала суммируются числа 1.6 и 1 Л, что дает значение 3.3. Затем это число преобразуется путем усечения в целое число 3, чтобы соответствовать переменной типа int. Во второй строке числа 1.6 и 1.1 перед сложением преобразуются в целочисленный вид (1), в результате чего переменной mice присваивается значение 1 + 1, или 2. По существу ни одна из форм не считается более корректной, чем другая; при выборе более подходящей необходимо учитывать контекст программируемой задачи.

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

Сводка: операции в С

Ниже перечислены операции, которые обсуждались выше.

Операция присваивания

= Присваивает переменной, указанной слева от знака операции, значение, заданное справа от него.

Арифметические операции

+ Добавляет значение справа от знака операции к значению слева от знака.

-      Вычитает значение справа от знака операции из значения слева от знака.

-      Как унарная операция, изменяет знак значения, указанного справа от знака операции.

* Умножает значение справа от знака операции на значение слева от знака.

/ Делит значение слева от знака операции на значение справа от знака.

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

%    Выдает остаток от деления значения слева от знака на значение справа от знака

(только для целочисленных значений).

++ Добавляет 1 к значению переменной справа от знака операции (префиксная форма) или к значению слева от знака операции (постфиксная форма).

-- Подобна++, но вычитает 1.


188 Глава 5

Операции различного назначения

sizeof Возвращает размер в байтах операнда, указанного справа. Операндом может быть спецификатор типа в круглых скобках, например, sizeof (float), либо имя конкретной переменной, массива и т.д. без скобок, например, sizeof foo.

(тип) Как операция приведения, преобразует следующее за ней значение в тип, указанный внутри круглых скобок. Например, (float) 9 преобразует целочисленное значение 9 в число с плавающей запятой 9.0.

Функции с аргументами

К этому времени вы уже знакомы с использованием аргументов функций. Следующий щаг на пути к мастерству работы с функциями — научиться писать собственные функции, принимающие аргументы. Давайте кратко взглянем, в чем заключается этот навык. (Возможно, сейчас имеет смысл просмотреть еще раз пример функции butler(), приведенный в конце главы 2; он демонстрировал написание функции без аргументов.) Код в листинге 5.15 включает функцию pound(), которая выводит указанное количество знаков фунта (#). (Этот символ называют также знаком номера и хещ-символом.) Пример также иллюстрирует некоторые аспекты преобразования типов.

Листинг 5.15. Программа pound.с

Язык программирования C. Лекции и упражнения. 6-е издание


Выполнение программы дает следующий вывод:

#####

#################################

######

Первым делом посмотрим на заголовок функции:

void pound(int n)

Если функция не принимает аргументов, в круглых скобках заголовка функции будет указано ключевое слово void. Поскольку функция pound() принимает один аргумент int, в круглых скобках содержится объявление переменной типа int nо имени n. Здесь можно указывать любое имя, соответствующее правилам именования языка С.


Операции, выражения и операторы 189

Объявление аргумента создает переменную, которая называется формальным аргументом или формальным параметром. В этом случае формальным параметром является переменная int с именем n. Вызов функции, такой как pound (10), приводит к присваиванию переменной n значения 10. В программе вызов pound (times) присваивает n значение переменной times (5). Мы говорим, что вызов функции передает значение, которое называется фактическим аргументом или фактическим параметром, так что вызов функции pound (10) передает функции фактический аргумент 10, при этом значение 10 присваивается формальному параметру (переменной n). Другими словами, значение переменной times в функции main() копируется в новую переменную п внутри функции pound().

НА ЗАМЕТКУ! Аргументы или параметры

Хотя термины аргумент и параметр часто применяются взаимозаменяемо, в стандарте С99 решено использовать термин аргумент для фактического аргумента или фактического параметра и термин параметр для формального параметра и формального аргумента. С учетом этого соглашения можно сказать, что параметры — это переменные, а аргументы — это значения, которые предоставляются вызовом функции и присваиваются соответствующим параметрам. Таким образом, в листинге 5.15 аргументом функции pound() является times, а ее параметром — n. Подобным же образом в вызове функции pound (times + 4) значение выражения times + 4 представляет собой аргумент.

Имена переменных являются закрытыми внутри функции. Это значит, что имя, определенное в одной функции, не будет конфликтовать с таким же именем, объявленным в каком-то другом месте. Если бы в функции pound() вместо n использовалась переменная times, это привело бы к созданию переменной, отличной от times в функции main(). То есть вы получили бы две переменных с одним и тем же именем, но программе хорошо известно, к какой функции относится та или иная переменная.

Теперь взглянем на вызовы функций. Первым вызовом является pound (times) и, как уже было сказано, он приводит к присваиванию переменной n значения times, равного 5. В результате эта функция выводит пять знаков фунта и знак новой строки.

Второй вызов функции — pound (ch). В данном случае переменная ch имеет тип char. Она инициализируется символом !, который в системах с кодировкой ASCII имеет числовое значение 33. Но char является неподходящим типом для функции pound(). Именно здесь вступает в действие прототип функции, расположенный в верхней части программы. Прототип — это объявление функции, в котором дается описание возвращаемого значения функции и всех ее аргументов. Рассматриваемый прототип сообщает следующие сведения о функции pound():

•   функция не возвращает никакого значения (часть void);

•   функция принимает один аргумент, которым является значение типа int.

В этом случае прототип информирует компилятор о том, что функция pound() ожидает передачи ей аргумента типа int. В ответ компилятор, достигая выражения pound (ch), автоматически применяет к аргументу ch приведение типа, преобразуя его в аргумент int. В данной системе аргумент преобразуется из значения 33, хранящегося в одном байте, в значение 33, размещенное в четырех байтах, в результате чего значение 33 приобретает корректную форму, чтобы его можно было использовать в данной функции. Аналогично в последнем вызове, pound (f), приведение типа применяется для преобразования переменной f типа float в тип, подходящий этому аргументу.


190 Глава 5

До выхода стандарта ANSI С в языке использовались объявления функций, которые не были прототипами — они только указывали имя функции и тип возвращаемого значения, но не типы аргументов. В целях обратной совместимости в С по-прежнему допускается применение такой формы:

void pound();    /* объявление функции в стиле, предшествующем ANSI */

А что случится, если в программе pound.с вместо прототипа использовать такую форму объявления? Первый вызов функции, pound (times), будет работать, поскольку типом аргумента times является int. Второй вызов, pound(ch), также будет работать, т.к. при отсутствии прототипа компилятор С автоматически повышает типы аргументов char и short до int. Однако третий вызов, pound(f), оказывается неудачным, потому что в условиях отсутствия прототипа тип float автоматически повышается до double, а от этого, в действительности, мало пользы. Программа по-прежнему будет выполняться, но ее поведение окажется некорректным. Это можно было бы исправить, используя явное приведение типа в вызове функции:

pound ((int) f); // принудительное использование нужного типа

Обратите внимание, что это может не помочь, если значение переменной f слишком велико, чтобы уместиться в тип int.

Демонстрационная программа

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

Листинг 5.16. Программа running.с

Язык программирования C. Лекции и упражнения. 6-е издание




Операции, выражения и операторы 191

Язык программирования C. Лекции и упражнения. 6-е издание


В программе из листинга 5.16 применяется тот же подход, который использовался ранее в программе min_sec.c для преобразования финального времени в минуты и секунды, но здесь также выполняются преобразования типов. Почему? Причина в том, что для части программы, реализующей пересчет секунд в минуты, требуются целочисленные аргументы, а при преобразовании данных метрической системы в мили применяются числа с плавающей запятой. Чтобы сделать эти преобразования явными, мы использовали операцию приведения.

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

Ниже показан пример вывода:

Эта программа преобразует время пробега дистанции в метрической системе

во время пробега одной мили и вычисляет вашу среднюю

скорость в милях в час.

Введите дистанцию пробега в километрах.

10.0

Введите время в минутах и секундах.

Начните с ввода минут.

36

Теперь введите секунды.

23

Вы пробежали 10.00 км (6.21 мили) за 36 мин, 23 сек.

Такая скорость соответствует пробегу одной мили за 5 мин, 51 сек.

Ваша средняя скорость составила 10.25 миль в час

Ключевые понятия

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


192 Глава 5 приоритетом и ассоциативностью. Два последних качества определяют, какая операция применяется первой, когда две операции совместно используют один операнд. Операции комбинируются со значениями для построения выражений, и с каждым выражением в С связано значение. Если не учитывать приоритет и ассоциативность операций, Moiyr получиться выражения, которые не являются допустимыми или дают значения, отличные от ожидаемых; вряд ли это будет содействовать вашей репутации как программиста.

Язык С позволяет записывать выражения, объединяя разные числовые типы. Но арифметические операции требуют, чтобы операнды принадлежали одному и тому же типу, поэтому С выполняет автоматические преобразования. Тем не менее, рекомендуемая практика программирования — не полагаться на автоматические преобразования. Вместо этого делайте выбор типов явным, либо объявляя переменные необходимого типа, либо используя приведения типов. При таком подходе вам не придется сталкиваться с неожиданными автоматическими преобразованиями.

Резюме

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

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

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

В языке С многие преобразования типов происходят автоматически. Типы char и short повышаются до типа int всякий раз, когда используются в выражениях или в качестве аргументов функции, не имеющей прототипа. Тип float в случае применения в аргументах функции повышается до типа double. В версии K&R С (но не в ANSI С) тип float повышается до double, если используется внутри выражения. Когда значение одного типа присваивается переменной второго типа, это значение преобразуется в тип, который имеет переменная. В случае преобразования больших типов в меньшие (например, long в short или double в float) возможна потеря данных. При смешивании типов в арифметических выражениях меньшие типы преобразуются к большим согласно правилам, изложенным в настоящей главе.

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


Операции, выражения и операторы 193

Вопросы для самоконтроля

Ответы на вопросы для самоконтроля приведены в приложении А.

1. Предположим, что все переменные имеют тип int. Найдите значение для каждой из следующих переменных:

а. х = (2 + 3) * 6;

б. х = (12 + 6) /2*3;

в. у = х = (2 + 3) / 4;

г. у = 3 + 2* (X = 7/2);

2. Предположим, что все переменные имеют тип int. Найдите значение для каждой из следующих переменных:

а. х = (int) 3.8 + 3.3;

б. х = (2 + 3) * 10.5;

В. х = 3 / 5 * 22.0;

Г. х = 22.0 * 3 / 5;

3. Вычислите каждое из следующих выражений:

а. 30.0 / 4.0 * 5.0;

б. 30.0 / (4.0 * 5.0);

в.    30 / 4 * 5;

г. 3 0 * 5 / 4;

д. 30 / 4 .0 * 5; е. 30 / 4 * 5.0;

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

int main(void)

{

int i = 1, float n;

printf("Будьте внимательны! Далее идет последовательность дробей!\n"); while (i < 30) n = 1/i;

printf (" %f ", n); printf("На этом все!\n"); return;

}

5. Ниже приведен альтернативный вариант программы из листинга 5.9. Выглядит так, будто преследовалась цель упростить код путем замены двух операторов scanf() из листинга 5.9 единственным оператором scanf(). Почему этот вариант программы хуже исходного?

#include <stdio.h>

#define S_TO_M 60 int main(void)

{

int sec, min, left;



Язык программирования C. Лекции и упражнения. 6-е издание
Язык программирования C. Лекции и упражнения. 6-е издание




Операции, выражения и операторы 195

9. Модифицируйте последнюю программу так, чтобы вместо чисел она выводила буквы алфавита от а до g.

10.  Если бы следующие фрагменты кода были частью завершенной программы, тогда что они выводили бы?

а.    int х = 0;

while (++х < 3)

printf("%4d", х);

б.    int х = 100;

while (х++ < 103) printf("%4d\n",x); printf("%4d\n",x);

в.    char ch = ' s ';

while (ch < 'w')

{

printf("%c", ch); ch++;

}

printf("%c\n",ch);

11. Что выведет следующая программа?

Язык программирования C. Лекции и упражнения. 6-е издание

12.  Напишите операторы, которые выполняют перечисленные ниже действия (или, другими словами, имеют следующие побочные эффекты).

а.    Увеличивает значение переменной х на 10.

б.    Увеличивает значение переменной х на 1.

в.    Присваивает переменной с удвоенную сумму а и b.

г.     Присваивает переменной с сумму а и удвоенного значения b.

13.   Напишите операторы, которые выполняют перечисленные ниже действия.

а.    Уменьшает значение переменной х на 1.

б.    Присваивает m остаток от деления n на к.

в.    Делит q на b минус а и присваивает результат р.

г Присваивает переменной х результат деления суммы а и b на произведение с и d.


196 глава 5

Упражнения по программированию

1.  Напишите программу, которая преобразует время в минутах в часы и минуты. Для значения 60 создайте символическую константу посредством t#define или const. Используйте цикл while, чтобы обеспечить пользователю возможность повторного ввода значений и для прекращения цикла, если вводится значение времени, меньшее или равное нулю.

2.  Напишите программу, которая запрашивает у пользователя ввод целого числа, а затем выводит все целые числа, начиная с этого числа (и включая его) и заканчивая числом, которое больше введенного значения на 10 (включая его). (То есть, если вводится число 5, то в выводе должны присутствовать числа от 5 до 15.) Обеспечьте разделение выводимых значений друг от друга пробелами, символами табуляции или символами новой строки.

3.  Напишите программу, которая запрашивает у пользователя ввод количества дней и затем преобразует это значение в количество недель и дней. Например, 18 дней программа должна преобразовать в 2 недели и 4 дня. Отображайте результаты в следующем формате:

18 дней составляют 2 недели и 4 дня.

Чтобы пользователь мог многократно вводить количество дней, используйте цикл while. Цикл должен завершаться при вводе пользователем неположительного значения, например, 0 или -20.

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

Введите высоту в сантиметрах: 182

182.0 см = 5 футов, 11.7 дюймов

Введите высоту в сантиметрах (<=0 для выхода из программы):   168.7

168.0 см = 5 футов, 6.4 дюймов

Введите высоту в сантиметрах (<=0 для выхода из программы): 0 Работа завершена.

5.  Внесите изменения в программу addemup.c (листинг 5.13), которая вычисляет сумму первых 20 целых чисел. (Если хотите, можете считать addemup. с программой, которая вычисляет сумму, которую вы будете иметь спустя 20 дней, если в первый день вы получаете $1, во второй день — $2, в третий день — $3 и т.д.) Модифицируйте программу так, чтобы можно было интерактивно указать, насколько далеко должно распространяться вычисление. Другими словами, замените число 2 0 переменной, значение которой вводится пользователем.

6.  Теперь модифицируйте программу из упражнения 5, чтобы она вычисляла сумму квадратов целых чисел. (Или, если вам так больше нравится, программа должна вычислять сумму, которую вы получите, если в первый день вам заплатят $1, во второй день — $4, в третий день — $9 и т.д.) В языке С отсутствует функция возведения в квадрат, но, как вы знаете, квадрат числа n равен п*п.


Операции, выражения и операторы 197

7.  Напишите программу, которая запрашивает ввод числа типа double и выводит значение куба этого числа. Для этого используйте собственную функцию, которая возводит значение в куб и выводит полученный результат. Программа main() должна передавать этой функции вводимое значение.

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

Эта программа вычисляет результаты деления по модулю.

Введите целое число, которое будет служить вторым операндом: 256 Теперь введите первый операнд: 438 438 % 256 равно 182

Введите следующее число для первого операнда (<= 0 для выхода из программы): 1234567 1234567 % 256 равно 135

Введите следующее число для первого операнда (<= 0 для выхода из

программы) : 0

Готово

9.  Напишите программу, которая запрашивает у пользователя ввод значения температуры но Фаренгейту. Программа должна считывать значение температуры как число типа double и передавать его в виде аргумента пользовательской функции по имени Temperatures(). Эта функция должна вычислять эквивалентные значения температуры но Цельсию и по Кельвину и отображать на экране все три значения температуры с точностью до двух позиций справа от десятичной точки. Функция должна идентифицировать каждое значение символом соответствующей температурной шкалы. Вот формула перевода температуры по Фаренгейту в температуру по Цельсию:

Температура по Цельсию = 5.0 / 9.0 х (температура по Фаренгейту - 32.0)

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

Температура по Кельвину = температура по Цельсию + 273.16

Функция Temperatures() должна использовать const для создания символических представлений трех констант, которые участвуют в преобразованиях. Чтобы предоставить пользователю возможность многократного ввода значений температуры, в функции main() должен быть организован цикл, который завершается при вводе символа q или другого нечислового значения. Воспользуйтесь тем фактом, что функция scanf() возвращает количество прочитанных ею элементов, поэтому она возвратит 1, если прочитает число, но не будет возвращать 1, когда пользователь введет q. Операция == выполняет проверку на равенство, так что ее можно применять для сравнения возвращаемого значения scanf() с 1.




6

У правляющие операторы С: циклы

В ЭТОЙ ГЛАВЕ...

•    Ключевые слова: for

while do while

•    Операции:

< > > =

<= != == + =

*= -= /= %=

•    Функции:

fabs()

•    Три структуры циклов в С: while, for и do while

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

•    Другие операции

•    Массивы, которые часто используются с циклами

•    Написание функций, имеющих возвращаемые значения





200 Глава 6

М

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

• Выполнение последовательности операторов.

• Повторение последовательности операторов до тех пор, пока удовлетворяется некоторое условие (цикл).

• Использование проверки для выбора между альтернативными последовательностями операторов (условный переход).

Первая форма вам хороню знакома; все приведенные ранее программы сос тояли из последовательностей операторов. Цикл while является одним из примеров второй формы. В этой главе мы более подробно рассмотрим цикл whi 1е , а также две других структуры циклов — for и do while. Третья форма, т.е. выбор между разными возможными последовательностями действий, делает программу намного “интеллектуальнее” и значительно увеличивает полезность компьютера как такового. К сожалению, вам придется дождаться следующей главы, прежде чем вы получите в свое распоряжение всю эту мощь. В настоящей главе также дано введение в массивы, поскольку именно к ним могут быть приложены новые .знания о циклах. В дополнение в главе вы продолжите изучение функций. Давайте начнем с повторного обзора цикла while.

Повторный обзор цикла while

Вы уже кое-что знаете о цикле while, но мы повторим то, что известно, на примере программы, которая суммирует целые числа, вводимые с клавиатуры (листинг 6.1). В этом примере для определения момента прекращения ввода данных используется возвращаемое значение функции scanf().

Листинг 6.1. Программа summing.с

Язык программирования C. Лекции и упражнения. 6-е издание



Управляющие операторы С: циклы 201

В программе из листинга 6.1 применяется тип long, что позволяет вводить большие числа. Для согласованности переменная sum инициализируется значением 0L (ноль типа long), а не 0 (ноль типа int), хотя автоматическое преобразование типов в С позволяет указывать просто 0.

Ниже показан пример выполнения этой программы:

Введите целое число для последующего суммирования (или q для завершения

программы): 44

Введите следующее     целое число (или q            для     завершения   программы):  33

Введите следующее     целое число (или q            для     завершения   программы):  ее

Введите следующее     целое число (или q            для     завершения   программы):  121

Введите следующее     целое число (или q            для     завершения   программы):  q

Сумма введенных целых чисел равна 286.

Комментарии к программе

Для начала взглянем на цикл while. Условием проверки этого цикла является следующее выражение:

status == 1

В языке С символы == представляют операцию равенства, т.е. это выражение проверяет, равно ли значение переменной status числу 1. Не путайте его с выражением status = 1, которое присваивает переменной status значение 1. Благодаря условию проверки status == 1, цикл повторяется до тех пор, пока переменная status имеет значение 1. На каждой итерации в цикле текущее значение num добавляется к значению переменной sum, так что sum хранит промежуточную сумму. Когда переменная status получит значение, отличное от 1, цикл завершается, и программа выводит финальное значение sum.

Чтобы программа работала корректно, на каждой итерации цикла она должна получать новое значение для переменной num и переустанавливать переменную status. Это достигается за счет использования двух свойств функции scanf(). Во-первых, функция scanf() вызывается с целью чтения нового значения для num. Во-вторых, возвращаемое значение scanf() применяется для выяснения, была ли попытка чтения успешной. Как отмечалось в главе 4, функция scanf() возвращает количество успешно прочитанных элементов. Если scanf() успешно считывает целое число, она помещает его в переменную num и возвращает значение 1, которое присваивается переменной status. (Обратите внимание, что входное значение попадает в num, а не в status.) В результате обновляются значения num и status, после чего цикл while переходит на следующую итерацию. Если вы введете нечисловое значение, такое как q, то функция scanf() не обнаружит целого числа при вводе и возвратит значение 0, которое затем получит переменная status. Входной символ q из-за того, что он не является числом, возвращается во входную очередь; он вообще не считывается. (В действительности цикл завершается по причине ввода любого нечислового значения, а не только q, но предложение ввести q является более простой инструкцией для пользователя, чем сообщение о вводе любого нечислового значения.)

Если перед попыткой преобразовать значение функция scanf() сталкивается с проблемой (например, возникает конец файла или сбой оборудования), она возвращает специальное значение EOF (end of file — конец файла), которое обычно определено как -1. Это значение также приводит к прекращению цикла.

Такое двойное использование функции scanf() позволяет избежать трудноразрешимого аспекта интерактивного ввода в цикле: каким образом сообщить циклу о том, когда он должен прекратиться? Предположим, например, что scanf() не имеет


202 Глава 6 возвращаемого значения. Тогда единственное, что изменяется на каждой итерации — это значение переменной num. Значение num можно было бы задействовать при определении момента завершения цикла, скажем, указав num > 0 (num больше 0) или num ! = 0 (num не равно 0) в условии проверки, но это воспрепятствовало бы вводу определенных значений, таких как -3 или 0. Взамен можно было бы добавить в цикл дополнительный код, например, выдающий на каждой итерации запрос “Намерены ли вы продолжать? <да/нет>”, после чего проверять, ввел ли пользователь утвердительный ответ. Однако такой подход выглядит несколько громоздким и замедляет ввод. Применение возвращаемого значения scanf() позволяет избежать этих проблем.

Теперь давайте подробнее рассмотрим структуру этой программы. Ее можно кратко описать следующим образом:

инициализировать переменную sum значением 0 выдать пользователю приглашение на ввод прочитать входные данные

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

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

В любом случае, поскольку while является циклом с предусловием, программа должна получить входные данные и проверить значение переменной status до того, как будет произведен вход в тело цикла. Именно по этой причине в программе имеется вызов функции scanf() перед while. Чтобы цикл мог продолжаться, внутри него должен присутствовать оператор чтения, который позволит определить значение переменной status для следующего входного значения. В связи с этим оператор scanf() присутствует также в конце цикла while; он подготавливает цикл к следующей итерации. Приведенный ниже псевдокод можно считать стандартным форматом цикла:

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

Цикл чтения в стиле С

Программу в листинге 6.1 можно было бы написать на Pascal, BASIC или FORTRAN с тем же самым проектом, представленным с помощью псевдокода. Тем не менее, язык С предлагает сокращение. Конструкция


Управляющие операторы С: циклы 203

status = scanf("%ld", &num);

while (status = 1)

{

/* действия, выполняемые в цикле */

^ status = scanf("%ld", &num);

может быть заменена следующей:

while (scanf("%ld", &num) == 1)

{

/* действия, выполняемые в цикле */

}

Во второй форме функция scanf() используется двумя разными способами одновременно. Во-первых, вызов функции в случае успешного завершения помещает значение в num. Во-вторых, возвращаемое значение этой функции (которое равно 1 или О и не является значением num) управляет циклом. Поскольку условие цикла проверяется на каждой итерации, то и функция scanf() вызывается на каждой итерации, предоставляя новое значение num и обеспечивая новую проверку. Другими словами, возможности синтаксиса С позволяют заменить стандартный формат цикла следующей компактной версией:

пока получение и проверка значения завершается успешно, обработать значение

А теперь рассмотрим оператор while более формально.

Оператор while

Общая форма цикла while имеет следующий вид:

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

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

До сих пор в примерах в качестве части выражение применялись выражения отношений, т.е. сравнивались значения. В общем случае здесь можно использовать любое выражение. Если выражение истинно (или в общем случае имеет ненулевое значение), то часть оператор выполняется один раз и затем выражение проверяется снова. Такой повторяющийся процесс проверки и выполнения повторяется до тех пор, пока выражение не станет ложным (получит нулевое значение). Каждый процесс проверки и выполнения называется итерацией (рис. 6.1).

Язык программирования C. Лекции и упражнения. 6-е издание

PHс. 6.1. С труктура цикла while



204 глава 6

Завершение цикла while

Мы подошли к ключевому моменту, связанному с циклами while: при построении цикла while должен быть предусмотрен код, который изменяет значение проверочного выражения, чтобы оно в конечном итоге стало ложным. В противном случае цикл никогда не закончится. (В действительности для завершения цикла можно применить операторы break и if, но об этом речь пойдет позже.) Рассмотрим следующий пример:

index = 1;

while (index < 5)

printf("Доброе утро!\n");

Предыдущий фрагмент программы выводит это ободряющее сообщение бесконечно. Почему? Причина в том, что внутри цикла нет ничего, что изменило бы первоначальное значение index, равное 1. Теперь взгляните на такой фрагмент кода:

index = 1;

while (--index < 5)

printf("Доброе утро!\n");

Он немногим лучше первого. Значение переменной index изменяется, но не в том направлении! Но во всяком случае эта версия кода обеспечит завершение цикла после того, как значение переменной index упадет ниже минимального отрицательного числа, поддерживаемого системой, и станет наибольшим из возможных положительным значением. (В программе toobig.с из главы 3 было продемонстрировано, что добавление 1 к максимальному положительному числу обычно дает отрицательное число, а вычитание 1 из минимального отрицательного числа, как правило, приводит к получению положительного числа.)

Когда цикл завершается?

Важно понимать, что решение прекратить или продолжить выполнение цикла принимается только после того, как вычислено условие проверки. В качестве примера рассмотрим программу, приведенную в листинге 6.2.

Язык программирования C. Лекции и упражнения. 6-е издание


Запуск программы из листинга 6.2 дает следующий вывод:


Управляющие операторы С: циклы 205

Переменная n впервые получает значение 7 в строке 10 во время второй итерации цикла. Однако цикл не прекращается немедленно. Вместо этого завершается текущая итерация (строка 11) и выход из цикла происходит только после того, как условное выражение в строке 7 будет вычислено в третий раз. (Во время первой проверки переменная n имела значение 5, а во время второй — значение 6.)

Оператор while: цикл с предусловием

Цикл while — это условный цикл, использующий входное условие (предусловие). Цикл называется “условным”, поскольку выполнение его операторной части зависит от условия, описанного условным выражением, таким как (index < 5). Это выражение представляет собой предусловие, поскольку оно должно быть удовлетворено, прежде чем произойдет вход в тело цикла. В ситуациях, подобных приведенной далее, управление никогда не войдет в тело цикла, т.к. условие ложно с самого начала:

index = 10; while (index++ < 5)

printf("Удачного дня!\n");

Стоит изменить первую строку следующим образом:

index = 3;

и цикл начнет выполняться.

особенности синтаксиса

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

Листинг 6.3. Программа while1.с

Язык программирования C. Лекции и упражнения. 6-е издание


Запуск программы из листинга 6.3 порождает следующий вывод:

n равно 0 n равно 0 n равно 0 n равно 0 n равно 0

и т.д., пока вы не прервете ее выполнение.


206 Глава 6

Хотя в этом примере оператор n++; набран с отступом, он не заключен в фигурные скобки вместе с предыдущим оператором. Таким образом, в состав цикла входит только один оператор вывода, находящийся непосредственно после условия проверки. Переменная n никогда не изменится, условие n < 3 навсегда останется истинным, и вы получите цикл, который будет продолжать выводить сообщение до тех пор, пока вы не прервете выполнение программы. Это пример бесконечного цикла, из которого нельзя выйти без внешнего вмешательства.

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

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

Листинг 6.4. Программа while2 .с

Язык программирования C. Лекции и упражнения. 6-е издание


Программа, представленная в листинге 6.4, генерирует следующий вывод:

п равно 4

Это все, что делает данная программа.

Как упоминалось ранее, цикл заканчивается первым оператором, простым или составным, который следует непосредственно за условием проверки. Поскольку в строке 7 за условием проверки находится точка с запятой, именно в этом месте цикл и заканчивается, т.к. отдельно стоящая точка с запятой считается оператором. Оператор вывода в строке 8 не является частью цикла, поэтому значение переменной n увеличивается на каждой итерации цикла, но выводится только после выхода из цикла.

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

while (scanf("%d", &num) == 1)

;   /* пропустить целочисленный ввод */

До тех пор, пока функция scanf() считывает целое число, она возвращает 1, и цикл продолжается. Обратите внимание, что для ясности точка с запятой (пустой оператор) должна размещаться в следующей строке, а не вместе с оператором while. Это упрощает распознавание пустого оператора, а также напоминает о том, что он включен преднамеренно. Но еще лучше применять оператор continue, который обсуждается в следующей главе.


Управляющие операторы С: циклы 207

Сравнение: операции и выражения отношений

Поскольку циклы while часто полагаются на проверочные выражения, которые делают сравнения, эти выражения сравнения заслуживают более детального рассмотрения. Такие выражения называются выражениями отношений, а операции, которые в них появляются — операциями птношений. Мы уже пользовались несколькими такими операциями; в табл. 6.1 приведен полный список операций отношений в С. Здесь раскрыты почти все возможности в плане взаимоотношений между числами. (Взаимоотношения между числами, даже комплексными, не так сложны, как между людьми.)

Таблица 6.1. Операции отношений

Язык программирования C. Лекции и упражнения. 6-е издание


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

while (number < 6)

{

printf("Число слишком мало.\n");

scanf ("%d", snumber);

}

while (ch != '$')

{

count++;

scanf("%c", &ch);

}

while (scanf("%f", &num) == 1)

sum = sum + num;

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

Операции отношений могут также применяться с числами с плавающей запятой. Однако имейте в виду, что при сравнении чисел с плавающей вы должны использовать только операции < и >. Это объясняется тем, из-за ошибок округления два числа могут оказаться неравными, хотя логически они должны быть равны. Например, совершенно очевидно, что произведение чисел 3 и равно 1.0. Но если выразить число]/3 в виде десятичной дроби с шестью значащими цифрами, то произведением


208 глава 6 будет .999999, что не равно в точности 1. Функция fabs(), объявленная в заголовочном файле math.h, может быть удобной при проверках, в которых участвуют числа с плавающей запятой. Эта функция возвращает абсолютное значение величины с плавающей запятой, т.е. значение без алгебраического знака. Например, с помощью кода, подобного показанному в листинге 6.5, можно проверить, насколько число близко к желаемому результату.

Листинг 6.5. Программа cmpflt.c

Язык программирования C. Лекции и упражнения. 6-е издание


В цикле продолжается уточнение ответа до тех пор, пока разница между ответом и корректным значением не окажется в пределах 0.0001:

Каково значение числа pi?

3.14

Введите значение еще раз.

3.1416

Достаточно близко!

Каждое условное выражение получает оценку “истина” или “ложь” (но не “может быть"). В результате возникает интересный вопрос, о котором речь пойдет в следующем разделе.

Что такое истина?

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

Листинг 6.6. Программа t and f.c

Язык программирования C. Лекции и упражнения. 6-е издание



Управляющие операторы С: циклы 209

Язык программирования C. Лекции и упражнения. 6-е издание

В листинге 6.6 двум переменным присваиваются значения двух выражений отношений. С целью простоты переменной true_val присваивается значение истинного выражения, а переменной false val — значение ложного выражения. Запуск этой программы дает следующий простой вывод:

true = 1; false = О

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

while (1)

{

}

Что еще является истинным?

Если 1 или 0 допускается использовать в качестве условия проверки в операторе while, то можно ли применять для этих целей другие числа? И если да, то что произойдет? Давайте поэкспериментируем на примере программы, показанной в листинге 6.7.

Листинг 6.7. Программа truth, с

Язык программирования C. Лекции и упражнения. 6-е издание


Получаются следующие результаты:

3 является истинным 2 является истинным 1 является истинным О является ложным -3 является истинным -2 является истинным -1 является истинным О является ложным


210 глава 6

Первый цикл выполняется, когда переменная n принимает значения 3, 2 и 1, но пре кращает выполнение, когда n равна 0. Подобным же образом второй цикл выполняется, когда переменная n принимает значения -3, -2 и -1, но завершается, как только n становится равной 0. В общем случае осе ненулевые значения рассматриваются как истинные, а ложным считается только 0. В языке С истина имеет очень широкое толкование!

Говоря по-другому, цикл while выполняется до тех пор, пока в результате вычисления его условия проверки получается ненулевое значение. Это обстоятельство перемещает условия проверки из рамок “истина/ложь” в числовую область. Имейте в виду, что условное выражение принимает значение 1, если оно истинно, и 0, если ложно, поэтому все выражения подобного рода в действительности являются числовыми.

Многие программирующие на С пользуются этим свойством условий проверки. Например, конструкцию while (goats ! = 0) можно заменить while (goats), поскольку выражения (goats !=0) и (goats) принимают значение 0, те. ложное, только когда переменная goats равна 0. Вы должны в достаточной мере попрактиковаться с формой while (goats), чтобы она стала привычной.

Затруднения с понятием истины

Довольно широкое толкование понятия истины в С может приводить к затруднениям. Например, давайте внесем одно тонкое изменение в программу из листинга 6.1, получив в результате программу, показанную в листинге 6.8.

Листинг6.8. Программа trouble.с

Язык программирования C. Лекции и упражнения. 6-е издание

Запуск программы из листинга 6.8 дает следующий вывод:

Введите целое число для последующего суммирования (или q для завершения программы) : 20 Введите следующее                                       целое число   (или  q для завершения программы): 5

Введите следующее           целое число (или                         q                для завершения   программы)        :              30

Введите следующее           целое число (или                         q                для завершения   программы)        :              q

Введите следующее           целое число (или                         q                для           завершения        программы):

Введите следующее           целое число (или                         q                для           завершения        программы):

Введите следующее           целое число (или                         q                для           завершения        программы):

Введите следующее           целое число (или                         q                для           завершения        программы):


и т.д. до тех пор, пока вы не прервете выполнение программы.


Управляющие операторы С: циклы 211

К такому плачевному результату привело изменение, внесенное в условие проверки оператора while, когда выражение status == 1 было заменено выражением status = 1. Второе выражение — это оператор присваивания, который устанавливает переменную status в 1. Более того, значением оператора присваивания является значение его левой части, поэтому status = 1 имеет то же самое числовое значение 1. Следовательно, с практической точки зрения этот цикл while дает такой же результат, как и while (1) — т.е. цикл никогда не завершится. В случае ввода q переменная status устанавливается в 0, однако при проверке условия цикла status получает значение 1 и инициирует следующую итерацию.

Может возникнуть вопрос, по какой причине, учитывая, что программа выполняется в цикле, пользователь лишен возможности ввести какие-то дополнительные данные после ввода символа q. Когда функция scanf() не может прочитать данные в указанной форме, она оставляет этот не соответствующий ее требованиям ввод на месте для его чтения в следующий раз. Следовательно, после неудавщейся попытки чтения символа q как целого числа функция scanf() оставляет q на месте. На следующей итерации цикла scanf() пытается выполнить считывание с того места, где оно было остановлено в последний раз — там, где остался символ q. Функция scanf() снова не может прочитать q как целое число, так что этот пример демонстрирует не только возникновение бесконечного цикла, но также и цикла безуспешных попыток чтения. Словом, результат получается довольно плачевный, и хорошо, что компьютеры пока еще лишены чувств. Слепое следование неразумным инструкциям для компьютера столь же бесперспективно, как и попытка предсказать ситуацию на фондовой бирже на ближайшее десятилетие.

Не используйте знак = вместо ==. В некоторых языках программирования (скажем, BASIC) для представления операции присваивания и операции проверки на равенство применяется один и тот же символ, однако это совершенно разные операции (рис. 6.2).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 6.2. Операция отношения == и операция пршдаивания =


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

canoes =5      <- Присваивает значение 5 переменной canoes

canoes == 5    <- Проверяет, имеет ли переменная canoes значение 5

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


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

5 = canoes     <- Синтаксическая ошибка

5 = canoes <- Проверяет, имеет ли переменная canoes значение 5

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

Итак, операции отношения используются для построения условных выражений. Выражения отношений имеют значение 1, если они истинны, и 0, если ложны. В операторах (таких как while и if), где обычно применяются выражения отношений в качестве условий проверки, могут использоваться любые выражения, при этом их ненулевые значения интерпретируются как “истина”, а нулевые — как “ложь”.

НОВЫЙ ТИП _Bool

Переменные, предназначенные для представления истинных и ложных значений, в языке С традиционно имеют тип int. В стандарте С99 для переменных такого вида был введен тип Bool. Тип получил свое название в честь Джорджа Буля (George Boole), английского математика, который разработал алгебраическую систему, позволяющую формулировать и решать логические задачи. В программировании переменные, представляющие истинные и ложные значения, известны как булевские, так что именем типа для этих переменных в языке С является Bool. Переменная типа Bool может иметь только значения 1 (“истина”) и 0 (“ложь”). Если вы попытаетесь присвоить переменной Bool ненулевое числовое значение, то она получит значение 1, отражая тот факт, что любое ненулевое значение в С трактуется как “истина”.

В листинге 6.9 исправлено условие проверки, указанное в листинге 6.8, и переменная status типа int заменена переменной input_is_good типа Bool. Назначение булевским переменным имен, но которым ясно, что они принимают истинные и ложные значения, является общей практикой.

Листинг 6.9. Программа boolean.с

Язык программирования C. Лекции и упражнения. 6-е издание



Управляющие операторы С: циклы 213

Обратите внимание на то, как в коде присваивает результат сравнения переменной:

input_is_good = (scanf("%ld", &num) == 1);

Такое присваивание имеет смысл, поскольку операция == возвращает значение 1 или 0. Кстати, круглые скобки, заключающие в себе выражение ==, не нужны, поскольку операция == имеет более высокий приоритет, чем =; тем не менее, они способствуют улучшению читабельности кода. Кроме того, взгляните, насколько изменение имени переменной делает проверку цикла while более понятной:

while (input_is_good)

Стандарт С99 также предлагает заголовочный файл stdbool.li, в котором bool сделан псевдонимом типа Bool и определены символические константы true и false для значений 1 и 0. Включение этого заголовочного файла позволяет писать код, совместимый с программами на языке C++, где bool, true и false являются ключевыми словами.

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

приоритеты операций отношений

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

х > у + 2 эквивалентно х > (у + 2)

И также значит, что х = у > 2 эквивалентно х = (у > 2)

Другими словами, переменной х присваивается значение 1, если у больше 2, и 0 — в противном случае; переменной х не присваивается значение у.

Операции отношений имеют больший приоритет, чем операции присваивания. Поэтому

x_bigger = х > у; эквивалентно

x_bigger = (х > у);

Операции отношений по своему приоритету делятся на две группы.

Группа с большим приоритетом:          <<=>>=

Группа с меньшим приоритетом:          == ! =

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

ex != wye == zee

эквивалентно

(ex != wye) == zee


214 Глава 6

Сначала осуществляется проверка на неравенство значений переменных ех и wye. Затем полученное значение, которое может быть равно 1 или 0 (“истина” или “ложь”), сравнивается со значением zee. Мы вовсе не предлагаем вам применять конструкции подобного рода, но считаем своим долгом дать соответствующее пояснение.

В табл. 6.2 показаны приоритеты операций, представленных до сих пор. В справочном разделе || приложения Б приведена информация но приоритетам всех операций.

Таблица 6.2. Приоритет операций

Язык программирования C. Лекции и упражнения. 6-е издание


Сводка: оператор while Ключевое слово

while

Общий комментарий

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

Форма записи

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

Часть оператор повторяется до тех пор, пока выражение не станет ложным или равным О,

Примеры

while (n++ < 100)

printf(" %d %d\n",n, 2 * n + 1); // одиночный оператор while (fargo < 1000)

{                               // составной оператор

fargo = fargo + step; step = 2 * step;

}


Управляющие операторы С: циклы 215

Сводка: операции отношений и условные выражения Операции отношений

Каждая операция отношения сравнивает значение в ее левой части со значением в ее правой части:

< меньше <= меньше или равно == равно

>= больше или равно > больше ! = неравно Условные выражения

Простое условное выражение состоит из знака операции отношения и операндов слева и справа. Если выражение истинно, то условное выражение имеет значение 1. Если отношение ложно, то условно! выражения получает значение 0.

Примеры

5 > 2 истинно и принимает значение 1.

(2 + а) == а ложно и принимает значение 0.

Неопределенные циклы и циклы со счетчиком

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

Листинг 6.10. Программа sweetie1.c

Язык программирования C. Лекции и упражнения. 6-е издание


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


216 Глава 6

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

1.  Инициализировать счетчик.

2.  Сравнить показание счетчика с некоторой граничной величиной.

3.  Инкрементировать значение счетчика на каждом проходе цикла.

О сравнении позаботится условие цикла while. Операция инкремента отвечает за увеличение значения счетчика. В листинге 6.10 инкрементирование делается в конце цикла. Такой подход устраняет возможность случайно пропустить действие инкрементирования. Следовательно, было бы лучше объединить действия по проверке и обновлению в одно выражение, применив конструкцию count++ <= NUMBER, но инициализация счетчика по-прежнему выполняется за пределами цикла, сохраняя вероятность забыть сделать это. Опыт нас учит: то, что может случиться, рано или поздно произойдет, поэтому давайте более подробно рассмотрим управляющий оператор, который позволяет избежать таких проблем.

Цикл for

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

Листинг 6.11. Программа sweetie2.c

Язык программирования C. Лекции и упражнения. 6-е издание


В круглых скобках, следующих за ключевым словом for, содержатся три выражения, разделенные двумя точками с запятой. Первое выражение — это инициализация. Она осуществляется только один раз при первом запуске цикла for. Второе выражение представляет собой условие проверки; оно вычисляется перед каждым потенциальным проходом цикла. Когда выражение имеет значение false (когда значение счетчика count больше, чем NUMBER), цикл завершается. Третье выражение, которое выполняет изменение или обновление, вычисляется в конце каждой итерации. В листинге 6.10 оно применяется для инкрементирования значения count, но этим его использование не ограничивается. Оператор for завершается следующим за ним простым или составным оператором. Каждое из трех выражений является полным, так что любой побочный эффект в управляющем выражении, такой как инкремент значения переменной, происходит до вычисления другого выражения. Структура цикла for иллюстрируется на рис. 6.3.

В качестве еще одного примера в листинге 6.12 показана программа, в которой цикл for применяется для вывода таблицы кубов.


Управляющие операторы С: циклы 217

Язык программирования C. Лекции и упражнения. 6-е издание



Рис. 6.3. Структура цикла for

Язык программирования C. Лекции и упражнения. 6-е издание


Программа в листинге 6.12 выводит целые числа от 1 до 6 и результат их возведения в куб.

n n в кубе

1               1

2           8

3     27

4     64

5    125

6     216

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

Использование цикла for для повышения гибкости

Хотя цикл for выглядит похожим па цикл DO в языке FORTRAN, цикл FOR в языке Pascal и цикл FOR... NEXT в языке BASIC, он обладает гораздо большей гибкостью, чем любой из них. Эта гибкость является результатом того, как могут применяться три выражения в спецификации оператора for. В примерах, приведенных до сих пор, первое выражение использовалось для инициализации счетчика, второе выражение — для установки предельного значения счетчика и третье выражение — для увеличения значения счетчика на 1. Применяемый подобным образом оператор for языка С во многом подобен упомянутым выше операторам. Тем не менее, он обладает и множеством других возможностей, девять из которых описаны далее.


218 Глава 6

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

/* for_down.c */

#include <stdio.h> int main(void)

{

int secs;

for (secs = 5; secs > 0; secs--) printf ("%d секунд(ы) !\n", secs);

printf("Ключ на зажигание!\n"); return 0;

}

Ниже показан вывод:

5 секунд(ы)!

4 секунд(ы)!

3 секунд(ы)!

2 секунд(ы)!

1 секунд(ы)!

Ключ на зажигание!

•      При желании можно считать двойками, десятками и т.д.

/* for_13s.c */

#include <stdio.h> int main(void)

{

int n;     // счет с интервалом 13, начиная с 2

for (n=2;n<60;n=n+13) printf("%d \n", n); return 0;

}

Значение переменной n на каждой итерации увеличивается на 13, давая следующий вывод:

2

15

28

41

54

•      Можно делать подсчет по символам, а не числам.

/* for_char.c */

#include <stdio.h> int main(void)

{

char ch;

for (ch = 'a'; ch <= 'z'; ch++)

printf("Значение ASCII для %c равно %d.\n", ch, ch); return 0;

}

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

Значение ASCII для а равно 97.

Значение ASCII для b равно 98.


Управляющие операторы С: циклы 219

Значение ASCII для х равно 120.

Значение ASCII для у равно 121.

Значение ASCII для z равно 122.

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

•    Можно выполнять проверку условия, отличного от количества итераций. В программе for cube.с строку

for (num = 1; num <= 6; num++)

можно заменить такой строкой:

for (num = 1; num*num*num <= 216; num++)

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

•    Можно позволить некоторой величине расти не в арифметической, а в геометрической прогрессии; т.е. вместо добавления каждый раз фиксированного значения можно умножать на фиксированное значение.

/* for_geo. с * /

#include <stdio.h> int main(void)

{

double debt;

for (debt = 100.0; debt < 150.0; debt = debt * 1.1)

printf("Теперь ваша задолженность составляет $%.2f.\n", debt); return 0;

}

В этом фрагменте кода значение переменной debt на каждом проходе цикла умножается на 1.1, что увеличивает его на 10%. Вывод программы имеет следующий вид:

Теперь ваша задолженность составляет $100.00.

Теперь ваша задолженность составляет $110.00.

Теперь ваша задолженность составляет $121.00.

Теперь ваша задолженность составляет $133.10.

Теперь ваша задолженность составляет $146.41.

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

/* for_wild.с */

#include <stdio.h> int main(void)

{

int x; int у = 55;

for (x = 1; у <= 75; у = ( ++x * 5) + 50) printf("%10d %10d\n", x, y); return 0;

}

Этот цикл выводит значения переменной х и алгебраического выражения ++х *5+50. Ниже показан вывод программы:

1      55

2       60

3      65

4      70

5      75


220 Глава 6

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

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

/* for_none.c */

#include <stdio.h> int main(void)

{

int ans, n; ans = 2;

for (n = 3; ans <=25; ) ans = ans * n;

printf("n = %d; ans = %d.\n", n, ans); return 0;

}

Вот вывод этой программы:

n = 3; ans = 54 .

Значение переменной n в цикле остается равным 3. Переменная ans начинается со значения 2, затем увеличивается до 6 и 18, а в конечном итоге получает значение 54. (Значение 18 меньше, чем 25, так что цикл for выполняет па одну итерацию больше, умножая 18 на 3 для получения 54.) Кстати, пустое управляющее выражение, находящееся посередине, считается истинным, поэтому приведенный ниже цикл выполняется бесконечно: for (;; )

printf("Требуется определенное действие.\n");

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

/* for_show.c */

#include <stdio.h> int main(void)

{

int num = 0;

for (printf("Продолжайте вводить числа!\n"); num != 6; ) scanf("%d", &num);

printf("Вот то число, которое было нужно!\n"); return 0;

}

Этот фрагмент кода один раз выводит первое сообщение, а затем продолжает принимать числа до тех пор, пока вы не введете 6:

Продолжайте вводить числа!

3

5 8

6

Вот то число, которое было нужно!


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

for (n = 1; n < 10000; n = n + delta)

Если после нескольких итераций программа решит, что значение delta слишком мало или слишком велико, то посредством оператора if (глава 7) внутри цикла величину delta можно изменить. В интерактивной программе значение delta может быть изменено пользователем в процессе выполнения цикла. С таким видом настройки связана и определенная опасность; к примеру, установка delta в 0 приведет к бесконечному циклу.

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

Сводка: оператор for Ключевое слово for

Общий комментарий

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

Форма записи

for (инициализация; проверка; обновление) оператор

Цикл повторяется до тех пор, пока выражение проверка не станет ложным или нулевым. Пример

for (n = 0; n < 10; n++)

printf(" %d %d\n", n, 2 * n + 1);

Дополнительные операции присваивания: +=, -=, *=, /=, %=

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


222 Глава 6

Например:

scores += 20 — то же, что и scores = scores + 20 dimes - = 2 — то же, что и dimes = dimes - 2 bunnies *= 2 — то же, что и bunnies = bunnies * 2 time /= 2.73 — то же, что и time = time /2.73 reduce %= 3 — то же, что и reduce = reduce % 3

В приведенных выше примерах применялись простые числа, но эти операции также работают и с более сложными выражениями:

х *= 3 * у + 12 — то же, что их = х* (3 * у + 12)

Только что рассмотренные операции присваивания имеют такой же низкий приоритет, как и операция =, и этот приоритет меньше приоритета операции + или *. Низкий приоритет отражен в последнем примере, в котором 12 суммируется с 3 * у и только затем результат умножается на х.

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

Операция запятой

Операция запятой повышает гибкость цикла for, позволяя включить в его спецификацию более одного выражения инициализации или обновления. В листинге 6.13 представлена программа, которая выводит тарифы почтового обслуживания первого класса. (В 2013 году тарифы составляли 46 центов за первую унцию и 20 центов за каждую последующую унцию пересылаемого груза.)

Листинг 6.13. Программа postage.с

Язык программирования C. Лекции и упражнения. 6-е издание

Первые пять строк вывода программы выглядят так:


унции тариф

1   $0.46

2   $0.66

3     $0.86

4  $1.06


Управляющие операторы С: циклы 223

Операция запятой в программе применяется в выражениях инициализации и обновления. Ее наличие в первом выражении приводит к инициализации переменных ounces и cost. Второе ее вхождение вызывает увеличение на 1 переменной ounces и увеличение на 20 (значение константы NEXT 0Z) переменной cost на каждой итерации. Все вычисления делаются в спецификациях цикла for (рис. 6.4).

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 6.4. Операция запятой и цикл for


Использование операции запятой не ограничивается циклами for, но именно здесь она применяется чаще всего. Эта операция обладает еще двумя свойствами. Во- первых, она гарантирует, что разделяемые ею выражения вычисляются в порядке слева направо. (Другими словами, запятая является точкой следования, поэтому все побочные эффекты слева от запятой учитываются до того, как происходит переход вправо от запятой.) Таким образом, переменная ounces инициализируется раньше переменной cost. В рассматриваемом примере порядок не имеет значения, однако он важен в ситуации, когда перменная ounces задействована в выражении для cost.. Для примера взгляните на следующее выражение:

ounces++, cost = ounces * FIRST_0Z

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

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

х = (у = 3, {z = ++у +2) + 5);

выглядит так: переменной у присваивается 3, значение у инкрементируется до 4, к этому значению 4 добавляется 2, результирующее значение 6 присваивается переменной z, к z добавляется 5 и, наконец, финальное значение переменной z, равное 11, присваивается переменной х. Объяснение, почему кто-то мог все это делать, выходит за рамки настоящей книги. С другой стороны, предположим, что вы по неосторожности указали запятую при записи числа:

houseprice = 249,500;

Здесь нет синтаксической ошибки. Взамен компилятор С интерпретирует это как выражение запятой, с houseprice = 249 в качестве левого подвыражения и 500 — в ка-


224 Глава 6

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

houseprice = 249;

500;

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

houseprice = (249,500);

присваивает переменной houseprice значение 500.

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

char ch, date;

и в операторе

printf("%d %d\n", chimps, chumps); представляют собой разделители, а не операции запятой.

Сводка: новые операции Операции присваивания

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

+= Добавляет величину справа от знака операции к значению слева от знака.

-= Вычитает величину справа от знака операции из значения слева от знака.

*= Умножает значение переменной слева от знака операции на величину справа от знака. /= Делит значение переменной слева от знака операции на величину справа от знака.

%= Возвращает остаток от деления значения переменной слева от знака операции на величину справа от знака.

Пример

rabbits *= 1.6; эквивалентна

rabbits = rabbits *  1.6;

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

contents *= old_rate + 1.2;

дает тот же результат, что и такой оператор:

contents = contents * (old_rate + 1.2);

Операция запятой

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

for (step = 2, fargo = 0; fargo < 1000; step *= 2) fargo += step;


Управляющие операторы С: циклы 225

греческий философ Зенон и цикл for

Давайте посмотрим, как с помощью цикла for и операции запятой можно разрешить древний парадокс. Гфеческий философ Зенон утверждал, что стрела никогда не поразит своей цели. Сначала, говорил он, стрела пролетает половину пути до цели. Затем она пролетает половину оставшегося пути, затем — половину того пути, который остался, и так до бесконечности. Поскольку весь путь стрелы разбит на бесконечное количество частей, утверждал Зенон, стреле потребуется бесконечно большой промежуток времени для достижения конца пути. Однако мы сомневаемся в том, что Зенон добровольно согласился бы стать живой мишенью, чтобы доказать свою правоту.

Применим количественный подход и предположим, что стреле требуется одна секунда, чтобы пролететь первую половину пути. Затем ей понадобится 1/2 секунды, чтобы пролететь половину оставшегося пути, еще 1/4 секунды, чтобы преодолеть половину пути, который остался после этого, и т.д. Полное время пролета стрелы можно представить в виде следующей бесконечной последовательности:

1 + 1/2 + 1/4 + 1/8 + 1/16 + ...

Короткая программа в листинге 6.14 вычисляет сумму нескольких первых элементов этой последовательности. Переменная power of two принимает значения 1.0, 2.0, 4.0, 8.0 и т.д.

Листинг 6.14. Программа zeno.c

Язык программирования C. Лекции и упражнения. 6-е издание


Результат выполнения программы, в ходе которого суммируются первые 15 элементов последовательности, выглядит следующим образом:

Введите желаемое количество элементов последовательности: 15 Время = 1.000000, когда количество элементов = 1.

Время = 1.500000, когда количество элементов = 2.

Время = 1.750000, когда количество элементов = 3.

Время = 1.875000, когда количество элементов = 4.

Время = 1.937500, когда количество элементов = 5.

Время = 1.968750, когда количество элементов = 6.

Время = 1.984375, когда количество элементов = 7.

Время = 1.992188, когда количество элементов = 8.

Время = 1.996094, когда количество элементов = 9.


226 Глава 6

Время = 1.998047, когда количество элементов = 10.

Время = 1.999023, когда количество элементов = 11.

Время = 1.999512, когда количество элементов = 12.

Время = 1.999756, когда количество элементов = 13.

Время = 1.999878, когда количество элементов = 14.

Время = 1.999939, когда количество элементов = 15.

Легко заметить, что хотя мы и добавляем все новые элементы, общая сумма, по-видимому, не превысит некоторой величины. И в самом деле, математики доказали, что сумма этой последовательности стремится к 2.0 по мере того, как количество просуммированных элементов стремится к бесконечности, на что указывают результаты выполнения программы. Ознакомьтесь со следующими математическими выкладками. Предположим, что S представляет собой такую сумму:

S   = 1 + 1/2 + 1/4 + 1/8 + ...

Здесь многоточие означает "и т.д.”. Разделив S на 2, получаем:

S/2 = 1/2 + 1/4 + 1/8 + 1/16 + ...

Вычитание второго выражения из первого дает:

S - S/2 = 1 +1/2 -1/2 + 1/4 -1/4 + .. .

За исключением начального значения 1 все остальные значения образуют пары, в которых одно значение положительное, а второе — отрицательное, так что эти элементы уничтожают друг друга, в результате оставляя:

S/2 = 1

И, наконец, умножение обеих сторон на 2 дает:

S = 2

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

Что можно сказать о самой программе? Она показывает, что в выражении можно использовать более одной операции запятой. Вы инициализировали переменные time, power of 2 и count. После того, как вы определили условия для цикла, программа оказалась совсем короткой.

Цикл с постусловием: do while

Циклы while и for являются циклами с предусловием. Условия проверки вычисляются перед каждой итерацией цикла, поэтому вполне возможно, что операторы, помещенные в цикл, никогда не выполнятся. В языке С имеется также цикл с постусловием, в котором проверка условия производится после прохода каждой итерации цикла, благодаря чему гарантируется выполнение операторной части цикла минимум один раз. Эта разновидность цикла называется циклом do while. В листинге 6.15 приведен пример.

Листинг 6.15. Программа do while.с

Язык программирования C. Лекции и упражнения. 6-е издание



Управляющие операторы С: циклы 227

Язык программирования C. Лекции и упражнения. 6-е издание

Программа в листинге 6.15 читает входные значения до тех пор, пока пользователь не введет 13. Ниже показан результат выполнения этой программы:

Чтобы войти в клуб лечения трискадекафобии, пожалуйста, введите секретный код: 12 Чтобы войти в клуб лечения трискадекафобии, пожалуйста, введите секретный код: 14 Чтобы войти в клуб лечения трискадекафобии, пожалуйста, введите секретный код: 13 Поздравляем! Вас вылечили!

Эквивалентная программа, в которой применяется цикл while, была бы несколько длиннее, как можно видеть в листинге 6.16.

Листинг 6.16. Программа entry.с

Язык программирования C. Лекции и упражнения. 6-е издание


Общая форма цикла do while имеет вид:

do

оператор

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

Оператор может быть простым или составным. Обратите внимание на то, что сам цикл do while считается оператором и таким образом требует наличия после него точки с запятой (рис. 6.5).


228 Глава 6

Язык программирования C. Лекции и упражнения. 6-е издание

Рис. 6.5. Структура цикла do while


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

do

{

запросить ввод пароля прочитать пользовательский ввод ) while (введенные данные не совпадают с паролем);

Избегайте применения структуры do while, которая имеет вид, подобный показанному ниже псевдокоду:

do

{

запросить у пользователя, желает ли он продолжить какие-то действия } while (ответом является 'да');

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

Сводка: оператор do while Ключевые слова

do while

Общий комментарий

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


Управляющие операторы С: циклы 229

Форма записи

do

опера тор

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

Часть оператор повторяется до тех пор, пока выражение не станет ложным или нулевым.

Пример:

do

scanf("%d", snumber);

while (number !< 20)

Выбор подходящего цикла

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

Предположим, что требуется цикл с предусловием. Это должен быть цикл for или же цикл while? Частично это дело вкуса, поскольку то, что возможно сделать с помощью одного цикла, можно достичь и посредством другого. Чтобы сделать цикл for похожим на while, можно не указывать первое и третье выражения. Так, цикл

for (; условие-проверки; ) эквивалентен циклу

while (условие-проверки)

Чтобы придать циклу while вид, подобный for, предварите его инициализацией и предусмотрите внутри тела операторы для обновления значений. Например:

инициализация;

while (условие-проверки)

{

тело-цикла; обновление;

}

эквивалентно

for (инициализация; условие-проверки; обновление) тело-цикла;

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

while (scanf("%ld", &num) == 1)

Цикл for является более естественным выбором, когда реализуется подсчет для какого-нибудь индекса:

for (count = 1; count <= 100; count++)


230 Глава 6

Вложенные циклы

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

Листинг 6.17. Программа rows1.с

Язык программирования C. Лекции и упражнения. 6-е издание


Выполнение этой программы дает следующий вывод:

ABCDEFGHIJ

ABCDEFGHIJ

ABCDEFGHIJ

ABCDEFGHIJ

ABCDEFGHIJ

ABCDEFGHIJ

Анализ программы

Цикл for,