Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык Си - Уэйт, Прата, Мартин

.pdf
Скачиваний:
583
Добавлен:
01.06.2015
Размер:
4.92 Mб
Скачать

2.

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

Резюме: операции в языке Си

Ниже перечислены операции, которые мы уже обсудили.

=Присваивает величину справа от знака переменной слева от него

+Прибавляет величину справа от знака к величине слева от него

-Вычитает величину справа от знака из величины слева от него

-Унарная операция, изменяет знак величины справа от знака

*Умножает величину справа от знака на величину слева от него

/Делит величину слева от знака на величину справа от него. Результат усекается, если оба операнда целые числа

%Дает остаток при делении величины слева от знака на величину справа от него (только для целых чисел)

++Прибавляет 1 к значению переменной слева от знака (префиксная форма)

или к значению переменной справа от знака (постфиксная форма)

--Аналогичная операции ++, но вычитает 1

sizeof Дает размер операнда, стоящего справа, в байтах.

Операнд может быть спецификацией типа, заключенного в круглые скобки, как, например, sizeof (float), или именем конкретной переменной, массива и т. п., например sizeof foo

(тип) Операция приведения: приводит величину, стоящую справа, к типу, определяемому ключевым словом (или словами) в скобках. Например,

91

(float)9 преобразует целое число 9 в число с плавающей точкой 9.0.

ПРИМЕР ПРОГРАММЫ

Далее Содержание

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

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

/* бег */

 

 

# define SM

60 /* число секунд в минуте */

# define SH

3600 /* число секунд в часе */

# define МК 0.62137 /* число миль в километре */

main()

 

 

{

 

 

float distk, distm;

/* дистанция в км и милях */

float rate;

/* средняя скорость в милях в час */

int min, sec;

/* время бега в минутах и секундах */

int time; /* время бега в секундах */

float mtime;

/* время пробега одной мили в секундах */

int mmin, msec;

/* время пробега одной мили в минутах и секундах */

printf(" Эта программа пересчитывает ваше время пробега дистанции, выраженной в км, \n"); printf(" во время, требуемое для пробега одной мили, и вашу среднюю \n");

printf(" скорость в милях в час,\n");

printf(" Укажите, пожалуйста, дистанцию в километрах.\n"); scanf(" %f ", &distk);

printf(" Введите затем время в минутах и секундах. \n "); printf(" Начните с ввода минут. \n");

scanf(" %d", &min);

printf(" Теперь вводите секунды. \n"); scanf(" %d", &sec);

time = SM * mm + sec; /* переводит время в секунды */ distm = MK * distk; /* переводит километры в мили */ rate = distm / time*SH; /* число миль в сек * число

сек в час = число миль в час */

mtime = (float)time / distm; /* время/дистанция = время на милю */ mmin = (int)mtime / SM; /* находит целое число минут */

msec = (int)mtime % SM; /* находит остаток в секундах */ printf("Bы пробежали %1.2f KM (%1.2f мили) за %d мин %d с \n", distk, distm, mm, sec);

printf(" Эта скорость соответствует пробегу мили за %d : мин", mmin); printf("%d c.\n Ваша средняя скорость %l.2f миль/ч \n", msec, rate);

}

РИС. 5. 8. Программа, полезная для тех, кто занимается бегом

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

Честно говоря, нашу программу можно было бы написать, используя только автоматическое преобразование типов. Мы так и делали, применяя операцию приведения переменной mtime к типу int, чтобы при вычислении времени все операнды были целого типа. Однако такая версия

92

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

Вот результат работы данной программы.

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

10,0.

Введите затемвремя в минутах и секундах. Начните с ввода минут.

36.

Теперь введите секунды. 23

Вы пробежали 10,00 км (6,21 мили) за 36 мин. 23 с. Эта скорость соответствует пробегу мили за 5 мин 51 с. Ваша средняя скорость 10.25 миль/ч

ЧТО ВЫ ДОЛЖНЫ БЫЛИ УЗНАТЬ В ЭТОЙ ГЛАВЕ Далее Содержание

Как использовать операции: +,-,*,/,%,++, --, (type).

Что такое операнд: это - величина, над которой выполняется операция. Что такое выражение: совокупность операций и операндов.

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

Виды операторов: операторы описания, присваивания, while, составной.

Как сформировать составной оператор: заключить последовательность операторов в фигурные скобки {}.

Как сформируется оператор while: while (проверка условия) оператор.

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

ВОПРОСЫ И ОТВЕТЫ

Далее Содержание

1.Предположим, все переменные имеют тип int. Определите значение каждой из последующих переменных:

а. х = (2+3)*6, б. х = (12+6)/2*3,

в. y = x = (2+3)/4,

г. y = 3 + 2*(x = 7/2 ), д. x = (int)3.8 + 3.3,

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

main( )

{

int i = 1, float n;

printf(" Внимание! Сейчас появится несколько дробей. \n"); while (i < 30)

n = 1/ i; printf(" %f", n);

printf(" Вот и все! Конец! \n"),

}

93

3. Ниже приведена первая попытка сделать программу "секунды в минуты" диалоговой. Программа нас не удовлетворяет. Почему? Как ее улучшить?

#define SM 60 main( )

{

int sec, mm, left,

printf( Эта программа переводит секунды в минуты и секунды \n ); printf( 'Укажите число секунд \n ),

printf( Для окончания работы программы необходимо ввести 0

\n);

while (sec < 0){

 

scanf( %d", &sec),

 

mm = sec/SM,

 

left = sec % SM,

 

printf("%d с это % d мин %d с \n", sec, mm, left),

 

printf(" Введите следующее значение \n"),

 

}

 

printf( До свидания!\n ),

 

}

 

Ответы

1.а. 30

б. 27(а не 3). Результат 3 можно получить в случае (12 + 6)/(2*3) в. х = 1, у = 1 (деление целых чисел)

г. х = 3 (деление целых чисел) и у = 9

д. х = 6, так как (int)3.8=3.3 + 3.3 = 6.3, это число будет преобразовано в число 6, поскольку х имеет тип int

2.Строка 3: должна оканчиваться точкой с запятой, а не запятой.

Строка 7: оператор while представляет собой бесконечный цикл, потому что величина переменной i остается равной 1 и всегда будет меньше 30. По всей видимости, мы собирались

написать while(i+ + < 30).

Строки 7-9: отступы в строках подразумевают, по видимому, что из операторов, расположеных в строках 8 и 9, мы намеревались сформировать блок, но отсутствие фигурных скобок означает, что

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

Строка 8: поскольку 1 и i - оба целого типа, результат деления будет равен 1 при i, равном 1, и 0 - для всех больших значений. Необходимо писать так n = 1.0/i; перед делением значение переменной i будет приведено к типу данных с плавающей точкой и будет получен ненулевой результат.

Строка 9: мы опустили символ "новая строка" в управляющей строке; это приведет к тому, что числа будут печататься на одной строке, если так допускается устройством вывода.

3. Основная трудность лежит в согласовании между оператором, выполняющим проверку (величина переменной sec больше 0 или нет?), и оператором scanf( ), осуществляющим ввод значения переменной sec. В частности, когда проверка выполняется первый раз, переменная sec в программе еще не получает своего значения, и поэтому сравнение будет производиться с некоторой случайной величиной (мусором"), которая может оказаться в соответствующей ячейке

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

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

чтобы оператор scanf( ) выполнялся перед тем, как осуществляется проверка в операторе while.

94

Этого можно достичь путем следующей модификации средней части программы

scanf(" %d," &sec); while(sec > 0){

mm = sec / SM; left = sec % SM;

printf(" %d с это %d мин %d с \n", sec, mm, left); printf(" Введите следующее значение \n");

scanf(" %d ", &sec);

}

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

scanf в конце цикла (и, следовательно, как раз перед тем, как начнется выполнение очередного шага цикла). Этот подход является общим способом решения проблем подобного сорта.

УПРАЖНЕНИЯ

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

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

2.А теперь модифицируйте свою программу так, чтобы она вычисляла сумму квадратов целых чисел (Или, если вам так больше нравится, сколько вы всего получите денег, если в первый день вам заплатят 1 долл , во второй - 4, в третий - 9 и т. д. Это гораздо более прибыльное дело!) Учтите, что в языке Си нет функции возведения в квадрат, но вы можете использовать тот факт, что квадрат числа n - это просто n*n.

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

1) Гамбургер - булочка с котлетой - Прим перев.

ввод - вывод

ФУНКЦИИ getchar( ) и putchar( )

КОНЕЦ ФАЙЛА ПЕРЕКЛЮЧЕНИЕ < И >

СИСТЕМНО ЗАВИСИМЫЙ ВВОД-ВЫВОД ЦИКЛЫ РЕАЛИЗУЮЩИЕ ЗАДЕРЖКУ ПО ВРЕМЕНИ

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

95

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

Под функциями ввода-вывода подразумеваются функции, которые выполняют транспортировку данных в программу и из нее. Мы уже использовали две такие функции: printf( ) и scanf( ). Теперь же рассмотрим несколько других возможностей, предоставляемых языком Си.

Функции ввода-вывода не входят в определение языка Си; их разработка возложена на программистов, реализующих компилятор с языка Си. Если вы являетесь проектировщиком такого компилятора, то можете реализовать любые функции ввода-вывода. Если вычислительная система, для которой вы его создаете, обладает той или иной особенностью, например тем, что каналы ввода-вывода построены на основе портов микропроцессора INTEL 8086, вы можете встроить в нее специальные функции ввода-вывода, ориентированные на эту особенность. Мы рассмотрим пример применения такого подхода в конце данной главы. С другой стороны, выгода использования стандартного набора функций ввода-вывода на всех системах очевидна. Это дает возможность писать "переносимыe" программы, которые легко можно применять на разных машинах. В языке Си имеется много функций ввода-вывода такого типа, например printf( ) и scanf( ). Ниже мы рассмотрим функции getchar( ) и putchar( ).

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

96

РИС. 6.1. Функции getchar( ) и putchar( ) рабочие лошади программы обработки текстов

ВВОД И ВЫВОД ОДНОГО СИМВОЛА:

Далее Содержание

ФУНКЦИИ getchar( ) И putchar( )

 

Функция getchar() получает один символ, поступающий с пульта терминала (и поэтому имеющий название), и передает его выполняюшейся в данный момент программе. Функция putchar( ) получает один символ, поступающий из программы, и пересылает его для вывода на экран. Ниже приводится пример очень простой программы. Единственное, что она делает, это принимает один символ с клавиатуры и выводит его на экран. Мы будем постепенно модифицировать данную программу до тех пор, пока она не приобретет ряд полезных возможностей. Из дальнейшего вы узнаете, что представляют из себя эти возможности, но сначала давайте посмотрим на наш скромный первый вариант

/* ввод-вывод1 */ #include

main( )

{

char ch;

ch = getchar( ); /* строка 1 */ putchar (ch); /* строка 2 */ }

Для большинства систем спецификации функций getchar и putchar содержатся в системном файле stdio.h, и только по этой причине мы указали данный файл в программе. Использование такой программы приводит к следующему:

g [ввод] g

или, возможно, к

gg

Обозначение [ввод] служит указанием, что вы должны нажать клавишу [ввод]. В любом случае, первый символ g вы набираете на клавиатуре сами, а второй выводится компьютером.

Результат зависит от того, есть в вашей системе "буферизованный" ввод или нет. Если перед тем как получить на экране ответ, вы должны нажать клавишу [ввод], то буферизация в вашей

системе имеется. Давайте закончим рассмотрение функций getchar( ) и putchar( ) перед тем, как приступить к обсуждению понятия буферов.

Функция getchar( ) аргументов не имеет (т. е. при ее вызове в круглые скобки не помещается никакая величина). Она просто получает очередной поступающий символ и сама возвращает его

значение выполняемой программе. Например, если указанная функция получает букву Q, ее значением в данный момент будет эта буква. Оператор, приведенный в строке 1, присваивает

значение функции getchar( ) переменной ch.

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

putchar( ) является указание любого из этих аргументов при ее вызове.

putchar ('S');

/* напомним, что символьные */

putchar ('\n');

/* константы заключаются в апострофы */

putchar ('\007');

 

putchar (ch);

/* ch - переменная типа char */

putchar (getchar ( ));

97

Форму записи, приведенную в последнем примере, мы можем использовать для того, чтобы представить нашу программу в следующем виде:

#include main( )

{

putchar (getchar( ));

}

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

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

БУФЕРЫ

Далее Содержание

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

применяется только первый символ, поскольку функция getchar( ) вызывается в ней один раз. Например, работа нашей программы в системе, использующей буферизованный ввод, будет выглядеть следующим образом:

Вот длинная входная строка. [ввод] В

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

ВВот длинная входная строка

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

функция getchar( ) вызывается лишь один раз.

98

РИС. 6.2. Схема буфернзованного и небуферизованного ввода

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

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

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

СЛЕДУЮЩИЙ ШАГ

Далее Содержание

Теперь возьмемся за что-нибудь несколько более сложное чем чтение и вывод на печaть oднoгo cимвола - например за вывод на печать групп символов. Жeлaтeльнo также, чтобы в любой момент можно было остановить работу программы; для этого спроектируем ее так, чтобы она прекращала

работу при получении какого-нибудь специального символа, скажем *. Поставленную задачу можно решить, используя цикл while:

/*ввод-вывод2 */ /*ввод и печать символов до поступления завершающего символа*/ #include

#define STOP *

/*дает символу * символическое имя STOP*/

main()

 

 

 

{

 

 

 

char ch;

 

 

 

ch = getchar;

/* строка 9 */

 

 

while(ch!= STOP){ /* строка 10

/

 

putchar (ch);

/ * строка

11

*/

ch=getchar ();

/ * строка 12

*/ }

}

 

 

 

99

В данном примере была использована структура программы, обсуждавшаяся нами в конце гл. 5

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

программе функцией getchar( ), расположенной в строке 12. Мы ввели новую операцию отношения !=, смысл которой выражается словами "не равно". В результате всего этого цикл while будет осуществлять чтение и печать символов до тех пор, пока не поступит признак STOP. Мы могли бы

опустить в программе директиву #define и использовать лишь символ * в операторе while, но наш способ делает смысл данного знака более очевидным.

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

/* ввод-выводЗ */ #include

#define STOP * main( ) { char ch;

while ((ch=getchar( )) != STOP) /* строка 8 */ putchar (ch);

}

Одна строка 8 этой программы заменяет строки 9, 10 и 12 программы ввод-вывод2. Как же работает этот оператор? Начнем с того, что рассмотрим содержимое внутренних скобок:

ch = getchar( )

Это - выражение. Его смысл заключается в вызове функции getchar( ) и присваивании полученного значения переменной ch. Одним таким действием мы выполним то, чему в программе ввод-вывод2 были посвящены строки 9 и 12. Далее напомним, что любое выражение имеет значение и что значение выражения, включающего в себя операцию присваивания, совпадает со значением переменной, расположенной слева от знака = . Следовательно, значение выражения (ch = getchar( )) - это величина переменной ch, так что

(ch = getchar( )) ! = STOP

имеет то же действие, что и

ch != STOP

Тем самым выполняется проверка, которую в программе ввод-вывод2 осуществлял оператор, расположенный в строке 10. Конструкции подобного сорта (объединение в одном выражении операций присваивания и сравнения) довольно часто используются при программировании на языке Си:

100