Иванова Г.С. - Основы программирования
.pdf2. Простейшие конструкции языка
Для корректного выполнения операции присваивания результат выраже ния и переменная, записанная в правой части оператора присваивания, должны иметь одинаковые или совместимые типы.
Совместимыми считаются:
•все целые типы;
•все вещественные типы;
•отрезок некоторого базового типа и базовый тип;
•два отрезка одного базового типа;
•символ и строка.
При несовпадении типов правой и левой частей оператора присваивания для совместимых типов происходит неявное преобразование результата вы ражения к типу переменной, указанной в правой части. Например:
Var
L:longint; {переменная типа longint}
Е,х: extended; {переменные типа extended} Linteger; {переменная типа integer} R:real; {переменная типа real}
Begin...
R:=I*E/(x-^L);... {результат выражения, записанного в правой части оператора присваивания, будет иметь тип extended, однако, так как переменная R типа real, то результат будет преобразован в этот тип}
End,
Если типы правой и левой частей оператора присваивания не совмести мы, то необходимо использовать явное преобразование типов.
Явное преобразование обычно выполняют посредством использования специальных функций:
Тгипс(х) - преобразует значение вещественного типа в значение целого типа, отбрасывая дробную часть;
Round(x) - преобразует значение вещественного типа в значение целого типа, округляя его до ближайшего целого;
Ord(x) - преобразует значение порядкового типа в его номер;
Cltr(x) - преобразует номер символа по таблице ASCII в сам символ. Например:
а) Varn,nl:integer;xn,xk,h:real; Begin
xn:=I;xk:=5.7; h:=OJ;
n:'=Round((xk'Xn)/h); |
{n получит значение 16} |
nl:=Trunc((xk'Xn)/h); |
... {nlполучит значение 15} |
41
Часть 1. Основы алгоритмизации и процедурное программирование
б) Var c:char; x,y:integer: Begin x:=3;
y:= Ord(*A*);{y получит значение 65 - код символа А
по таблице ASCII} с. = Chr(Ord('A*)-^x); ... {с получит значение 'D'}
Кроме того, для явного преобразования типов можно использовать функции, имена которых соответствуют идентификаторам стандартных или определенных пользователем типов. Этот вид преобразования иногда назы вают автоопределеиным, например:
Var h'.char;
... h:=Char(65); ... {h получит значение 'А'}
Следует отметить, что при данном виде преобразования изменения зна чения не происходит, выполняется просто изменение типа, связанного с дан ным значением. В результате может произойти усечение или увеличение раз мера значения по сравнению с исходным. В случае усечения значения (при ведение к меньшему по размеру типу) возможно изменение знака преобразу емого числа. В случае же расширения значения (приведение к большему по размерам типу) знак числа всегда сохраняется.
Например:
Туре Month=(Jan,FabMcir,ApKMayJun,JulAug,Sep, Oct,Nov,Dec);
Var M:Month; |
|
|
A,B:mteger; |
|
|
C'char; |
|
|
L:longint; |
|
|
Begin |
|
|
A:=10; |
C-'E'; |
|
B:==Integer(C); |
{число 69 - код символа E - длиной 2 байта} |
|
М: =Month(A'2); |
{значение Sep} |
|
L:=Longint(M);.... |
{значение 8} |
2.6. Процедуры ввода-вывода
Ввод значений. Для ввода значений с клавиатуры используют специаль ные процедуры Read и ReadLn (рис. 2.10). Эти процедуры позволяют вводить значения стандартных типов, кроме boolean, и строки (string).
Вводимые значения (кроме значений символов и строк) разделяют про белами или записывают на разных строках. Отдельные символы и строки символов при вводе записывают подряд, так как пробел в этом случае также считается символом.
42
2. Простейшие конструкции языка
rU Read Vi
|
Идентификатор |
|
переменной |
L^/ReadLnVI |
О |
|
Рис. 2.10. Синтаксическая диаграмма <Процедуры ввода с клавиатуры>
Физически операции ввода выполняются с использованием буферОуТ, е. вводимая с клавиатуры последовательность символов сначала помещается в память, а затем, уже из памяти, читается программой. Последовательность передается в буфер ввода по нажатию клавиши Enter. При этом в буфер вме сте с кодами введенных символов помещается и код Enter, состоящий из двух символов «#13, #10».
Если ввод осуществляется процедурой ReadLn, то буфер ввода после выполнения операции очищается, причем символы, оставшиеся не обрабо танными, игнорируются. Если ввод осуществляется процедурой Read, то очистка не выполняется и, следовательно, следующий оператор ввода начнет читать символы из той же строки. Последнее cyщectвeннo только, если вво дятся значения типа char (или string, см. параграф 4.5), так как при вводе чи
сел пробелы и переход на следующую строку |
игнорируются. |
||||
Например: |
|
|
|
|
|
а) |
Var a,b,c:real; |
п:integer; |
|
|
|
|
Begin |
|
|
|
|
|
Read(a,b); |
{числа могут быть введены в одной строке или в |
|||
|
|
|
разных} |
|
|
|
ReadLn(c,n);,,. {числа |
могут быть введены в той же строке, что и |
|||
|
|
|
предыдущие числа} |
|
|
б) |
Var a:real; |
c:char; |
|
|
|
|
Begin ... |
|
|
|
|
|
Read(a); ... |
|
|
|
|
|
Write('Продолжить? |
(y/n) |
*); |
||
|
Read(c); |
{приводит к тому, что после запроса компьютер не |
|||
|
переходит в ожидание ввода, как мы предполагали, а вводит |
||||
|
следующий символ из буфера ввода, т.е. символ #13 (рис. 2.11)} |
Чтобы избежать «игнорирования ввода», необходимо для выполнения предыдущей операции ввода использовать вместо процедуры Read процеду ру ReadLn:
43
Часть L Основы алгоритмизации и процедурное программирование
Местоположение указателя ввода перед
чтением символа
Число а
гл/
8 5 #13 #10
Рис. 2.11. Ситуация «игнорирования» ввода
Var a:real; c:char; Begin...
ReadLn(a);... {очистим буфер ввода после выполнения операции}
Write('Продолэюитъ? (у/п) *);
Read(c); {в данном случае все в порядке: после вывода запроса программа ожидает ввода символа}
Вывод значений. Для вывода значений на экран используют процедуры Write и WriteLn (рис. 2.12). Эти процедуры предназначены для вывода значе ний стандартных типов и строк.
Целочисленный литерал <Целое 1> интерпретируется как ширина поля, в которое выводится значение, причем выводимые значения прижимаются к правой границе. Если указанной ширины поля недостаточно, то она автома тически увеличивается до нужного значения. Если <Целое 1> не указано, то его значение определяется количеством выводимых символов.
Целочисленный литерал <Целое 2> указывается только для веществен ных чисел: он определяет количество цифр дробной части числа. Если <Целое 2> указано равным О, то ни дробная часть числа, ни десятичная точка не выводятся. Если <Целое1> и <Целое 2> не указаны, то веш[ественные числа выводятся в виде мантиссы и порядка, причем ширина поля вывода по умол чанию принимается равной 23, а количество дробных цифр - 14.
Логические значения выводятся как TRUE или FALSE.
Символы и строки выводятся без изменения, но дополняются пробела ми, если ширина поля вывода больше, чем необходимо.
г*^ Write \h0^Выражение |
Ч1ЯЦелое1 |
Г0- |
U/writeLn\j |
<1ЯЦелое2 И |
Рис. 2.12. Синтаксическая диаграмма <Процедуры вывода на экран>
44
2. Простейшие конструкции языка
После вывода значений процедурой WriteLn курсор переводится на сле дующую строку.
Пример 2.1. Разработать программу вычисления корней уравнения Ах2-1-Вх+С=0 при условии, что дискриминант - неотрицательное число.
Формула корней уравнения известна:
-b±Vd
х,2 = |
' |
|
2а |
где d = Ь^ - 4ас.
Алгоритм программы выглядит следующим образом:
Корни уравнения:
Ввести а, Ь, с
d:= b^ - 4ас
е:= Ь / (2а)
X, := - е + V d / (2а) Х2 := - е - V^d / (2а) Вывести Х|, Х2
Конец алгоритма.
Ниже приведен текст программы.
Program ex;
Var a,bx,xl,x2,e,d:real; {описываем переменные} Begin
WriteLnCВведите коэффициенты уравнения: '); ReadLn(a,b,c); {вводим параметры} d:=b*b'4*a*c; {определяем дискриминант}
е:=Ь/(2*а); {определяем значение вспомогательной переменной} xl:=-e^sqrt(d)/(2'^a): {определяем xj}
x2:^'e'Sqrt(d)/(2^a); {определяем Х2}
WriteLn('xl= \х1:6:2, *х2= \х2:б:2); {выводим результаты} End
2.7. Практикум. Оценка точности результатов
При программировании вычислений необходимо помнить о том, что во многих случаях результат этих вычислений является числом приблиэюенным.
Пусть «А» - точное значение числа, а «а» - его приближенное представ ление, тогда ошибкой или абсолютной погрешностью приближенного пред ставления числа А называют значение
Л = |А - а|.
45
Часть 1. Основы алгоритмизации и процедурное программирование
Обычно при оценке точности полученных результатов точное значение неизвестно. Поэтому для оценки погрешности используют ее приближение «сверху», т.е. максимально возможное значение погрешности, которое назы вают предельным значением абсолютной погрешности
Аа>А =|А-а|.
Абсолютная погрешность не является единственной характеристикой ошибки. Сравним два варианта результата с одинаковой погрешностью: 100±1 и 1±1. Очевидно, что с точки зрения практики необходимо иметь ха рактеристику, позволяющую при оценке ошибки учитывать само значение. Такой характеристикой является относительная погрешность.
Относительной погрешностью называют отношение абсолютной по грешности числа к его модулю (А;»^0):
5 = А /А.
С учетом того, что точное значение А обычно не известно, в качестве
предельной относительной погрешности или «оценки сверху» относитель ной погрешности можно использовать значение
5, = Д,/(а-Д,).
Погрешность результата вычислений складывается из погрешностей:
•допущенных при постановке задачи за счет ее упрощения (погрешно сти задачи);
•связанных с использованием приближенных методов решения задачи (погрешности метода);
•связанных с использованием приближенных значений параметров, на пример, любых физических констант (начальные погрешности);
•связанных с ограниченным количеством разрядов, используемых для представления чисел (погрешности округления);
•возникающих при выполнении операций над приближенными числа ми (погрешности операций).
Примечание. При программировании следует помнить, что относительная погрешность вычислений резко возрастает при вычитании двух близких чисел. Это связано с тем, что при этом резко уменьшается значение результата и соответственно также резко возрастает относительная погрешность.
Естественно, при решении конкретной задачи какие-то погрешности могут отсутствовать или быть несущественными.
Пример 2.2. Выполнить оценку погрешности представления числа 1/3 и вычислений над числами типа real.
46
2. Простейшие конструкции языка
Program ex;
Var у,у1у2,уЗ,у4,у5,у6,р:геа1; Begin
y-l; yl-y/3;
WriteLnCyl = ',yl:16:14); {выводит yl=0.33333333333348} y2:=sqrt(yl};
y3:=sqr(y2);
у4:=уЗ/14;
у5:=у4*]4;
WriteLnCy6^ \у6:16:14): {выводит у6=1.00000000000182} WriteLn(y = \y:16:14); {выводит у =1.06000000000000}
End
Откуда погрешности представления числа 1/3 в формате real:
А,/3 = |1/3 - у1| = I 0.33333333333333 - 0.333333333333481 = 0.15-10-'2,
5,/з = 0.15 10-12/(1/3) = 0.45-10-10
а погрешность выполнения операций над числами, представленными в формате типа real, в конкретном случае:
Дуб = 1У - Уб1 = 11 - 1.00000000000182| = 0.182.1 о-1', 5уб = Дуб 'у = 0.182.10-11/1= 0.182.10-11.
Пример 2.3. Из математики известно, что ch^ х - sh^ х = 1. Разработать программу, «проверяющую» это равенство.
Наша программа должна вводить значение х и для него считать
у, = (ех +е-х)/2, У2 = (е>^-е-^)/2,
У = У1^-У2^-
Полученные значения У], У2 и у выведем на экран.
Program ex;
Var X, у, yly y2:real; Begin
Write('Введите значение x: *); ReadLn(x);
yl: ='(exp(x)-^exp('X))/2;
47
Часть 1. Основы алгоритмизации и процедурное программирование
у2:=(ехр(х) - ехр('Х))/2; y:=^sqr(yl) - sqr(y2): WriteLnCyl^\yl:13:ll): WnteLn(y2=\y2:13:ll); WriteLn(y=^\y:13:ll);
End.
Последовательно вводя x = 5, 6, 7, ..., 14, получаем, что у = 1, хотя по грешность результата растет. Однако при х=15 у=0 (!!!). Попробуем понять, почему такое происходит, для чего сведем результаты при х = О, 5, 10, 14, 15, 20 в табл. 2.4 и рассчитаем абсолютную и относительную погрешности ре зультата. Из таблицы видно, что значения функций ch х и sh х с увеличени ем X быстро растут. А чем больше число, тем длиннее запись его мантиссы. И, наконец, при х=15 разрядной сетки для записи мантиссы числа перестает хватать. При этом младшие разряды мантиссы, которые различны для ch х и sh X, отбрасываются, и при возведении чисел в квадрат мы получаем одина ковые результаты.
Для того чтобы избежать подобных ситуаций, в каждом конкретном слу чае используют разные приемы, например, в данном примере можно исполь зовать тип данных с большим количеством значащих цифр (см. табл. 2.2) или вычислять результат преобразованного выражения:
ch^ X - sh^ X = (ch X - sh x)(ch x + sh x),
в котором значения уменьшаемого и вычитаемого растут не так быстро.
|
|
|
Т а б л и ц а 2.4 |
X |
У1,У2 |
У |
А, 5 |
0 |
у1=1.00000000000 |
1.00000000000 |
А=0, |
|
у2=0.00000000000 |
6=0% |
|
|
|
||
5 |
у 1=74.2099485248 |
0.99999999989 |
Л=0.00000000011 |
|
у2=74.2032105778 |
5=0.000000011% |
|
|
|
||
10 |
у 1=11013.2329201 |
1.00008608813 |
А=0.00008608813 |
|
у2=11013.2328747 |
|
6=0. 008608813 % |
14 |
у 1=601302.142083 |
1.14689281583 |
А=0.14689281583 |
|
у2=601302.142082 |
|
6=14.689281583% |
15 |
у 1=1634508.68623 |
0.00000000000 |
А=1 |
|
у2= 1634508.68623 |
|
6=100 % |
20 |
у 1=242582597.704 |
0.00000000000 |
А=1 |
|
у2=242582597.704 |
|
6=100 % |
48
2 Простейшие конструкции языка
Тема оценки погрешностей вычислений будет продолжена в параграфе
3.5.
Задания для самопроверки
Задание 1. Измените в программе примера 2.3 тип х на double. Объясните полученные результаты.
Задание 2. Разработайте программу, которая «проверяет» формулу sin^ X + cos2x= 1.
Убедитесь, что при любых допустимых значениях х мы получаем правильные результаты. Почему?
3. УПРАВЛЯЮЩИЕ ОПЕРАТОРЫ ЯЗЫКА
Программы, содержащие в разделе операторов только операторы ввода-вывода и операторы присваивания, выполняются последовательно оператор за оператором. Такие программы ндзыътогт линейными, они реализуют линейный процесс вычислений. Для ор ганизации разветвленных и циклических процессов вычислений используют управляю щие операторы языка, определяющие последовательность выполнения операторов про граммы. В данной главе мы рассмотрим управляющие операторы языка Borland Pascal, к которым относятг опсратор условной передачи управления, оператор выбора, операторы организации циклов, а также неструктурные операторы и процедуры передачи управле ния.
3.1. Оператор условной передачи управления
Оператор условной передачи управления (рис. 3.1) используют для про граммирования ветвлений, т. е. ситуаций, когда возникает необходимость при выполнении условия реализовывать одни действия, а при нарушении - другие. Условие записывают в виде логического выражения, в зависимости от результата которого осуществляется выбор одной из ветвей: если резуль тат true, то выполняется оператор, следующий за служебным словом then, иначе - оператор, следующий за служебным словом else.
В каждой ветви допускается запись одного оператора (в том числе и дру гого jf) или составного оператора.
Составным оператором в Borland Pascal называют последовательность операторов, заключенную в операторные скобки begin...end. Операторы по следовательности отделяют друг от друга точкой с запятой «;». Перед end точку с запятой можно не ставить. Перед else точка с запятой не ставится никогда, так как в этом случае запись условного оператора продолжается.
if |
Логическое |
then |
Оператор |
Оператор г |
|
выражение |
|||||
|
|
|
|||
|
|
|
else |
Рис. 3.1. Синтаксическая диаграмма <Оператор условной передачи управления>
50