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

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
24
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

ЭКСПЕРИМЕНТ. Введите строку символов вместо целого числа. Возможный результат:

z=gjytrv.oi866 <ENTER> x=sum=3630652

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

ЭКСПЕРИМЕНТ. Что будет, если программист использовал не адрес переменной, а ее имя в качестве аргумента функции scanf()?

В следующей программе сделана эта ошибка:

/* 04_23_1.c - ошибка в аргументе функции scanf() */ #include <stdio.h>

int main ()

{

int z, x, sum; printf("z="); scanf("%d",z); printf("x="); scanf("%d",x); sum=z+x; printf("sum=%d",sum); return 0;

}

Программа корректно транслируется. Результат выполнения программы:

z=3 <ENTER> x=4 <ENTER> sum=3630652

Очень плохо! Нет сообщений при компиляции или на этапе сборки исполнимой программы. Программа выполняется, но ошибочно.

121

ЭКСПЕРИМЕНТ. В обращении к функции scanf() укажите спецификацию преобразования, не соответствующую типу объекта, в который выполняется ввод.

/* 04_23_2.c - функция scanf() - ошибка в спецификации */

#include <stdio.h> int main ()

{

int z, x, sum; printf("z="); scanf("%f",&z); printf("x="); scanf("%f",&x); sum=z+x; printf("sum=%d",sum); return 0;

}

Корректная трансляция!!!

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

z=4 <ENTER> x=66 <ENTER> sum=-2096889856

Очень плохо! Успешная компиляция, но ошибочное исполнение.

Наиболее традиционный тип представления вещественных данных это double. Вещественные константы, в записи которых отсутствует суффикс (f или l), по умолчанию имеют тип double. При форматном выводе значения типа double обычно используют спецификации преобразования %e и %f. Но при вводе их применение в форматной строке функции scanf() недопустимо.

ЗАДАЧА 04-24. Определите в программе переменную типа double, введите с клавиатуры ее значение и выведите (напечатайте) значение переменной.

122

/* 04_24.c - функция scanf() для типа double */ #include <stdio.h>

int main ()

{

double real; printf("real="); scanf("%le",&real); printf("result=%e\n",real); printf("result=%f\n",real); return 0;

}

Корректная трансляция. Результат выполнения программы:

real=1234.4321 <ENTER> result=1.234432e+03 result=1234.432100

ЭКСПЕРИМЕНТ. В обращении к scanf() предыдущей программы замените спецификацию преобразования %le на %e.

/* 04_24_1.c - функция scanf(). Неверен формат для double */

#include <stdio.h> int main ()

{double real; printf("real="); scanf("%e",&real); printf("result=%e\n",real);

printf("result=%f\n",real); return 0;

}

Корректная трансляция. Результат выполнения программы:

real=1234.4321 <ENTER> result=2.951329e-310 result=0.000000

Результат неверен.

123

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

Есть особенности ввода последовательности символов с клавиатуры, когда после каждого символа нужно нажать клавишу ENTER.

ЗАДАЧА 04-25. Введите с помощью трех обращений к функции форматного ввода значения трех переменных типа char, а затем выведите полученные значения и напечатайте их коды.

При чтении символов из стандартного входного потока необходимо обращать внимание на тот факт, что каждое нажатие клавиши ENTER помещает во входной поток два кода – CR (возврат каретки, значение 15) и LF (переход к следующей строке, значение 10). Считывание очередного символа из входного потока функцией scanf() со спецификацией %c "оставляет" в буфере входного потока один из этих управляющих символов. Следующее обращение к функции scanf() приводит к тому, что из входного потока прочитывается только оставшийся управляющий символ LF и не вводится никакая информация. Для иллюстрации сказанного проведем перед решением поставленной задачи такой эксперимент.

ЗАДАНИЕ. Введите с помощью функции форматного ввода значение одной переменной типа char, а затем, повторно обратившись к той же функции, выведите десятичное значение полученного кода.

Заданию соответствует программа:

/* 04_25_1.c - коды от клавиши ENTER */ #include <stdio.h>

int main ()

{

char x, code; printf("x="); scanf("%c",&x); scanf("%c",&code);

124

printf("\nresult: x=%c, code=%d",x,code); return 0;

}

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

x=f<ENTER>

result: x=f, code=10

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

Одно из решений задачи 04-25:

/* 04_25.c - функция scanf() при чтении символов с клавиатуры */

#include <stdio.h> int main ()

{

char x1,x2,x3; printf("x1="); scanf("%c",&x1); printf("x2=");

scanf("%c",&x2); scanf("%c",&x2); printf("x3=");

scanf("%c",&x3); scanf("%c",&x3); printf("\nresult: x1=%c x2=%c x3=%c",x1,x2,x3); return 0;

}

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

x1=a <ENTER> x2=d <ENTER> x3=j <ENTER>

result: x1=a x2=d x3=j

Чтобы получить значения переменных x2 и x3, пришлось дважды обращаться к функции scanf(). В первом чтении вводится код LF, во втором – код символа, набранного пользователем.

125

ЭКСПЕРИМЕНТ. Удалите из программы 04_25.с "удвоение" обращений к функции scanf() для ввода значений x2 и x3. Оттранслируйте и выполните такой вариант программы.

Возможный результат:

x1=a <ENTER> x2=x3=b <ENTER>

result: x1=a x2= x3=b

Никак не удается ввести осмысленное значение для переменной

x2.

Другое более обоснованное и технически более корректное решение обеспечивает функция из стандартной библиотеки:

int fflush(FILE * stream);

Функция очищает буфер потока, указатель на который использован в качестве ее аргумента. В нашем случае после обращения к scanf() необходимо очищать буфер стандартного входного потока stdin. Решение задачи в этом случае будет таким:

/* 04_25_2.c - функция scanf() + функция fflush() */ #include <stdio.h>

int main ()

{

char x1,x2,x3; printf("x1=");

scanf("%c",&x1); fflush(stdin); printf("x2=");

scanf("%c",&x2); fflush(stdin); printf("x3=");

scanf("%c",&x3); fflush(stdin); printf("\nresult: x1=%c x2=%c x3=%c",x1,x2,x3); return 0;

}

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

126

x1=1<ENTER>

x2=2<ENTER>

x3=3<ENTER>

result: x1=1 x2=2 x3=3

К сожалению, компилятор DJGPP неверно выполняет эту программу. При использовании компиляторов Turbo C и Visual C программа работает правильно. Именно с их помощью получен приведенный результат.

Другое решение задачи – ввод трех (или более) символов в одном обращении к функции scanf().

/* 04_26.c - функция scanf() при чтении символов с клавиатуры */

#include <stdio.h> int main ()

{

char x1,x2,x3;

printf("Input x1, x2, x3:\n"); scanf("%c %c %c",&x1,&x2,&x3);

printf("\nresult: x1=%c x2=%c x3=%c",x1,x2,x3); return 0;

}

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

Input x1, x2, x3: q g I <ENTER>

result: x1=q x2=g x3=i

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

Коротко о важном

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

127

!Идентификаторы, выбираемые в программе в качестве имен, не должны совпадать со служебными словами (04_01.с).

!Использование идентификатора библиотечной функции в качестве имени объекта в программе "экранирует" доступ к библиотечной функции (04_02.с).

!Применение препроцессорного идентификатора из заголовочного файла в качестве имени объекта программы приводит к "загадочным" сообщениям об ошибках (04_02.с).

!Компилятор не "с первого раза" распознает все ошибки, присутствующие в программе (04_01.с, 04_02.с).

!Одна ошибка в тексте программы может привести к появлению многочисленных синтаксических ошибок, о которых информирует компилятор.

!typedef вводит не новый тип, а новое обозначение (название)

типа (04_03.с).

!Требования к инициализирующим выражениям внешних и внутренних объектов функции различны (04_04.с).

!Внешние объекты доступны в функции, если их имена не использованы в ее теле для обозначения внутренних объектов

(04_05.с).

!Унарные операции декремента и инкремента имеют приоритет над бинарными операциями суммирования и вычитания

(04_06.с, 04_08.с, 04_08_1.с).

!Операции декремента и инкремента применимы только к псевдодопустимым выражениям (04_06.с, 04_08.с).

!Присваивание является операцией (04_07.с).

!Следует понимать различия между постфиксными и префиксными операциями декремента и инкремента (04_08.с, 04_08_1.с).

!При делении целочисленных операндов учитывайте округление результата (04_09.с, 04_10.с).

!При умножении целочисленных операндов не забывайте о возможном переполнении (04_09.с, 04_10.с).

!Значение выражения с операцией "запятая" определяет правый операнд (04_11.с, 04_15.с).

!Положительное целое трактуется как "истинное" значение

(04_12.с, 04_13.с).

!Цепочка одноранговых логических операций выполняется слева направо только до тех пор, пока не станет очевиден результат

(04_14.с, 04_15.с).

128

!В ряде случаев условное (тернарное) выражение может заменить условный оператор (04_16.с, 04_17.с).

!В условном (тернарном) выражении никогда не вычисляются значения всех трех операндов (04_18.с).

!Битовые представления позволяют иллюстрировать различие между знаковыми и беззнаковыми целыми, между операциями "~" и "!" (04-19.с), между логическими и поразрядными логическими операциями (04_21.с).

!Сдвиг на N разрядов влево эквивалентен умножению целого чис-

ла на 2N (04_20.с).

!Поразрядный сдвиг вправо знакового целого сохраняет значение знакового разряда и делает его нулевым для беззнакового значе-

ния (04-22.с, 04-22_1.с).

!Функция scanf() не "сообщает" о несоответствии вводимых данных спецификациям преобразования (04_23.с).

!Компилятор не распознает несоответствие спецификаций из форматной строки последующим аргументам (04_23_1.с, 04_23_2.с, 04_24_1.с).

!При вводе значения типа double нужна спецификация преобра-

зования %le (04_24.с, 04_24_1.с).

!Нажатие клавиши ENTER приводит к появлению во входном потоке двух служебных символов, только один из которых "извлекает" функция scanf() со спецификацией (04_25_1.с).

!Для корректного чтения из входного потока символов, код каждого из которых завершается нажатием клавиши ENTER, следует либо использовать пары вызовов функции scanf(), либо после каждого обращения к scanf() вызывать функцию fflush(stdin) для очистки буфера входного потока (04_25.с, 04_25_2.с).

Тема 5

Управление

последовательностью

вычислений

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

Э. Дейкстра. Заметки по структурному программированию

Основные вопросы темы

!Формы условных операторов.

!Условное выражение как альтернатива условного оператора.

!Использование операторов return совместно с условными операторами.

!Переключатели и роль операторов перехода при ветвлениях.

!Особенности меток и управляющих выражений переключателей.

!Применимость и возможности операторов циклов.

!Ввод-вывод символов средствами форматного обмена.

!Специализированные функции для ввода-вывода символьных данных.

!Переназначение стандартных потоков ввода-вывода.

!Символьные данные в переключателях.

!Признак EOF.

Текст программы – это статический, вневременной объект. Программа, как писал Э.Дейкстра [16], приобретает смысл только во время ее выполнения. Линейные участки текста программы содержат операторы преобразования данных в том порядке, в котором они должны исполняться. Для изменения естественной последовательности исполнения в языке Си используются:

!операторы выбора (if, switch);

!операторы циклов (do, while, for);

!операторы переходов (goto, break, continue, return).

130