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

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

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

real=23<ENTER>

zero=2.300000e+01

estimate=4.600000e+01

estimate=3.450000e+01

zero=1.150000e+01

estimate=2.875000e+01

zero=5.750000e+00

.........

zero=1.021405e-14

estimate=2.300000e+01

estimate=2.300000e+01

zero=5.107026e-15

estimate=2.300000e+01

zero=2.553513e-15

Впрограмме вводимое число представлено переменной real, текущая оценка машинного нуля – переменная zero, сумма значения real и монотонно убывающего значения zero представлена переменной estimate.

Впрограмме циклы двух видов do и for. В цикле do проверяемое условие всегда истинно while(1), поэтому цикл может быть завершен только принудительно – за счет выполнения оператора break. Если введено положительное значение переменной real, цикл завершает-

ся.

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

ЗАДАНИЕ. Напишите варианты программы оценки машинного нуля для данных типа float и long double.

ЗАДАЧА 05-12. Вычислите с точностью до "машинного нуля" приближенное значение суммы ([13], стр. 42)

1

+

 

1

+

 

1

+

 

1

L

 

 

 

 

 

 

5 6

1 2 3 2

3 4 3

4 5 4

 

(Точное значение приведенной бесконечной суммы равно 0.25.)

Задачу решает следующая программа:

/* 05_12.c - оценка бесконечной суммы (double) */ #include <stdio.h>

int main ()

{

double sumOld=0.0, sumNew=0.0, denom;

151

int k=1;

denom=k*(k+1)*(k+2); /* знаменатель */ do { sumOld=sumNew;

sumNew+=1.0/denom;

denom=denom/k*(k+3);

k++;

}

while(sumOld < sumNew); printf("\nsum=%e",sumNew); return 0;

}

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

sum=2.500000e-01

В программе denom – знаменатель очередного слагаемого. Важно обратить внимание, что при переходе от k-го слагаемого к (k+1)-му нужно разделить его знаменатель на k и затем умножить на (k+3). Сумма вычисляется как значение переменной sumNew. Ее предыдущее значение сохраняется в переменной sumOld. Так как приближенное значение с добавлением неотрицательных слагаемых не может уменьшаться, то условием продолжения цикла служит отношение sumOld<sumNew. Слагаемые монотонно убывают (растет denom). Когда при добавлении очередного слагаемого значение суммы остается неизменным (за счет конечности разрядной сетки для представления вещественных чисел), то нарушается условие sumOld<sumNew и цикл прекращается.

ЗАДАНИЕ. При решении предыдущей задачи выведите последовательности: номеров слагаемых, их значений и соответствующих приближенных значений суммы.

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

ЗАДАНИЕ. Решите задачу с переменными типа float и long double.

152

5.4. Циклы при работе с символами

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

Для представления "граничных" символов определим в программе две переменных beg и end типа char. Чтобы не усложнять задачу дополнительными проверками, будем считать, что задача решается при условии, когда значение (код) переменной beg не превышает значения (кода) переменной end.

/* 05_13.c - символы и их коды из диапазона */ #include <stdio.h>

#define READC(VARIABLE) \

{ printf(#VARIABLE"="); scanf("%c",&VARIABLE); } int main ()

{

char z, beg, end; int i; READC(beg);

scanf("%c",&z); /* вспомогательный ввод */ READC(end);

for (z=beg, i=1; z <= end; z++,i++) printf("%c - %o(8)%c",z,z,i%3?'\t':'\n');

return 0;

}

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

beg=d<ENTER>

end=v<ENTER> d - 144(8)

g - 147(8) j - 152(8) m - 155(8) p - 160(8) s - 163(8) v - 166(8)

e - 145(8) h - 150(8) k - 153(8) n - 156(8) q - 161(8) t - 164(8)

f - 146(8) i - 151(8) l - 154(8) o - 157(8) r - 162(8) u - 165(8)

153

Для ввода символа "с подсказкой" в программе определен макрос READC. Однако между двумя обращениями к нему помещен вспомогательный вызов функции scanf("%c",&z). С помощью этого обращения в переменную char z считывается из буфера входного потока не нужный для нашей задачи код LF перевода строки 12(8), оставшийся во входном потоке после ввода значения переменной char beg. Эту тонкость, а также возможность применения функции fflush() для очистки буфера мы уже рассмотрели в теме 4 (задача

04-25).

Еще одна особенность состоит в использовании условного выражения в качестве аргумента функции printf(). С его помощью в выходной поток помещается либо код табуляции '\t', либо код перехода на новую строку '\n'. "Регулирует" выбор значения выражения i%3, равное 0 при i кратном трем. Здесь i – вспомогательная переменная – счетчик обработанных символов.

ЭКСПЕРИМЕНТ. Удалите (например, поместите в скобки комментариев) вспомогательное обращение к функции scanf(), оттранслируйте и выполните программу (см. 05_13_1.с).

Так как программа выдает коды символов при условии, что beg<=end, то для получения на экране хоть каких-нибудь результатов необходимо ввести такой код для переменой beg, чтобы его значение не превышало 12(8). Варианты результатов следующие.

Первое исполнение:

beg=<ENTER>

end=<ENTER>

- 12(8)

Второе исполнение:

beg=<TAB><ENTER> end= - 11(8)

- 12(8)

Код 12(8) обозначает переход к следующей строке (LF), код 11(8) обозначает горизонтальную табуляцию. Изображения оба кода не имеют.

154

ЗАДАЧА 05-14. Модифицируйте программу 05_13.с таким образом, чтобы она выдавала символы (и их коды) из диапазона от beg и end независимо от соотношения значений beg и end.

Задачу решает следующая программа:

/* 05_14.c - символы и их коды из диапазона */ #include <stdio.h>

#define READC(VARIABLE) \

{ printf(#VARIABLE"="); scanf("%c",&VARIABLE); } int main ()

{

char z, beg, end; int i; READC(beg);

scanf("%c",&z); /* вспомогательный ввод */ READC(end);

for (z = beg <= end ? beg:end, i=1;

z <= (beg <= end ? end:beg); z++,i++) printf("%c - %o(8)%c",z,z,i%3?'\t':'\n');

return 0;

}

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

beg=a<ENTER>

 

end=k<ENTER>

b - 142(8)

a - 141(8)

d - 144(8)

e - 145(8)

g - 147(8)

h - 150(8)

j - 152(8)

k - 153(8)

c - 143(8) f - 146(8) i - 151(8)

Второе исполнение:

beg=B<ENTER>

 

 

 

 

end=5<ENTER>

6

- 66(8)

7

- 67(8)

5

- 65(8)

8

- 70(8)

9

- 71(8)

: - 72(8)

; - 73(8)

< - 74(8)

= - 75(8)

> - 76(8)

? - 77(8)

@ - 100(8)

A - 101(8)

B - 102(8)

 

 

155

Обратите внимание на необходимость скобок в выраженииусловии заголовка цикла for.

ЭКСПЕРИМЕНТ. Удалите из программы 05_14.с вспомогательное обращение к функции scanf() и выполните программу (см. 05_14_1.с).

Результаты без функции scanf():

beg=%<ENTER>

end=

- 12(8)

 

 

 

- 13(8)

- 16(8)

¤ - 17(8)

 

- 15(8)

 

- 20(8)

- 21(8)

- 22(8)

 

- 23(8)

¶ - 24(8)

§ - 25(8)

 

- 26(8)

- 27(8)

♦ - 30(8)

▲ - 36(8)

- 31(8)

- 34(8)

- 35(8)

- 37(8)

- 40(8)

! - 41(8)

 

" - 42(8)

# - 43(8)

$ - 44(8)

 

% - 45(8)

 

 

 

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

ЭКСПЕРИМЕНТ. Уберите скобки в выражении-условии из цикла for программы 05_14.с, т.е. запишите его в таком виде:

z<=beg<=end?end:beg

Выполните программу (см. 05_14_2.с) и объясните результаты. Вероятно, придется останавливать исполнение комбинацией клавиш

"Ctrl+C".

ЗАДАЧА 05-15. Решите предыдущую задачу (программа 05_14.с) при следующем дополнительном условии – при выводе пропускайте символы, в битовом представлении кода которых

156

присутствуют две единицы в правых (младших) разрядах. Используйте оператор continue.

// 05_15.c - символы с особыми кодами из диапазона

#include <stdio.h> #define READC(VARIABLE) \

{printf(#VARIABLE"="); scanf("%c",&VARIABLE); } int main ()

{

char z, beg, end; int i; READC(beg);

scanf("%c",&z); /* считывание символа LF */ READC(end);

for (z=beg<=end?beg:end, i=1;

z <= (beg<=end?end:beg); z++)

{if (z & '\001' && z & '\002') continue; printf("%c - %o(8)%c",z,z,i%3?'\t':'\n');

i++;

}

return 0;

}

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

beg=d

end=v

d - 144(8) h - 150(8) l - 154(8) p - 160(8) t - 164(8)

e - 145(8) i - 151(8) m - 155(8) q - 161(8) u - 165(8)

f - 146(8) j - 152(8) n - 156(8) r - 162(8) v - 166(8)

В проверяемом выражении условного оператора дважды используется побитовая конъюнкция (операция &). Восьмеричные коды '\001' и '\002' служат масками для оценки кода символа z:

00…001

битовый код маски '\001'

хх…ххх

битовый код символа z

00…00w

результат поразрядной конъюнкции

00…010

битовый код маски '\002'

157

хх…ххх

битовый код символа z

00…0b0

результат поразрядной конъюнкции

Конъюнкция && результатов истинна, если и w, и b равны 1. В этом случае выполняется оператор continue и переход к следующей итерации цикла.

ЗАДАНИЕ. Предложите и реализуйте в программе другие правила выбора игнорируемых либо, наоборот, выводимых символов из заданного диапазона.

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

Для ввода и вывода символьных данных мы использовали достаточно универсальные функции форматного обмена scanf() и printf() со спецификацией преобразования %c. Имеются специализированные библиотечные функции:

int getchar() - чтение одного кода из стандартного входного по-

тока stdin;

int putchar(int) – запись значения кода аргумента в стандартный выходной поток stdout.

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

При запуске программы на исполнение чтение от клавиатуры можно заменить чтением из текстового файла. Для этого используется команда вида:

>имя_программы.exe < имя_файла_исходных_данных

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

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

>имя_программы.exe > имя_файла_результатов

158

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

ЗАДАЧА 05-16. Напишите программу, читающую посимвольно входной файл и выводящую его текст в файл результатов.

/* 05_16.c - вход на выход */ #include <stdio.h>

int main ()

{

char z;

while ((z=getchar()) != EOF) putchar(z);

return 0;

}

Выполнение программы, созданной в файле test.exe:

test.exe < 05_16.c > res

В файле res будет создана копия текста из файла 05_16.с. Обратите внимание на скобки в заголовке цикла. Если их убрать, т.е. записать

while(z=getchar()!= EOF),

то переменной z будет присвоено значение (отношения) сравнения результата выполнения функции getchar() с препроцессорной константой EOF.

ЭКСПЕРИМЕНТ. Запустите программу на исполнение, не указав входной и выходной потоки, т.е. планируя ввод из стандартного входного потока, настроенного на клавиатуру, а вывод – на экран дисплея.

>test.exe

Завершить исполнение можно, нажав сочетание клавиш "Ctrl+Z". Во входном потоке stdin, настроенном на клавиатуру, признак окончания файла EOF формируется при одновременном нажатии двух клавиш "Ctrl+Z".

159

ЗАДАЧА 05-17. Подсчитайте во входном потоке: общее количество символов; количество строк; количество открывающихся фигурных скобок '{'; количество открывающихся круглых скобок '('; количество пробелов. Используйте переключатель.

/* 05_17.c - анализ символов текстового файла */ #include <stdio.h>

#define PRINTI(EXPRESSION) \

int

printf(#EXPRESSION"=%d\n",EXPRESSION)

main ()

 

 

 

{

 

 

 

 

char z;

 

 

 

int

symbls=0, brace=0, string=0, parenthesis=0,

 

blank=0;

 

 

{

while ((z=getchar()) != EOF)

 

symbls++;

 

 

 

 

switch(z) {

brace++;

break; /* скобка */

 

case '{' :

 

case '(' :

parenthesis++;

break; /* скобка */

 

case '\n':

string++;

break; /* строка */

 

case ' ' :

blank++;

break; /* пробел */

}

}

 

 

 

PRINTI(brace);

 

 

 

PRINTI(parenthesis);

PRINTI(string);

PRINTI(blank);

PRINTI(symbls); return 0;

}

Директива запуска на исполнение:

>test.exe < 05_17.c > res

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

race=4

parenthesis=13

string=29

blank=127

symbls=684

160