![](/user_photo/2706_HbeT2.jpg)
Основы программирования. Борисенко
.pdf3.5.4. Пример: решение |
квадратного уравнения |
141 |
|
i f (x < |
0.0) |
{ |
|
s = |
(-1.0); |
|
}
else i f (x > 0.0) { s = 1.0;
}
else {
s = 0.0;
}
При выполнении этого фрагмента сперва проверяется условие x < 0.0. Если оно истинно, то выполняется оператор s = (-1.0); иначе проверяется второе условие x < 0.0. В случае его истин¬
ности выполняется оператор s = |
1.0, иначе |
выполняется оператор |
|
s = 0.0. Фигурные скобки здесь |
добавлены |
для улучшения |
струк¬ |
турности текста программы. |
|
|
|
В любом случае, в результате |
выполнения конструкции |
выбора |
исполняется лишь один из операторов (возможно, составных). Усло¬ вия проверяются последовательно сверху вниз. Как только находится истинное условие, то производится соответствующее действие и вы¬ бор заканчивается.
3.5.4.Пример: решение квадратного уравнения
Рассмотрим простой пример, в котором применяется конструкция «если. . . иначе»: требуется решить квадратное уравнение
a x 2 + bx + c = 0
Программа должна ввести с клавиатуры терминала числа a, b, c и за¬ тем напечатать ответ. После ввода надо проверить корректность вве¬ денных чисел — коэффициент a должен быть отличен от нуля (ина¬ че уравнение перестает быть квадратным, тогда формула решения квадратного уравнения неприменима). В зависимости от знака дис¬ криминанта уравнение может не иметь решений. Программа должна напечатать либо сообщение об отсутствии решений, либо два корня уравнения (возможно, совпадающие в случае нулевого дискриминан¬ та).
142 |
|
3.5. Управляющие конструкции |
|
Д л я печати |
на экран терминала и ввода информации с клавиату¬ |
||
ры используются функции ввода-вывода |
из стандартной |
библиотеки |
|
Си. Отметим, |
что функции стандартного |
ввода-вывода |
не являются |
частью языка Си: Си не содержит средств ввода-вывода. Однако, лю¬ бой компилятор обычно предоставляет набор библиотек, в который входит стандартный ввод-вывод. Описания функций ввода-вывода содержатся в заголовочном файле stdio.h, который подключается с помощью строки
#include <stdio.h>
М ы используем две функции: функцию printf вывода по формату и функцию scanfввода по формату. У обеих этих функций число аргу¬ ментов переменное, первым аргументом всегда является форматная строка. В случае функции printf обычные символы форматной строки просто выводятся на экран терминала. Например, в рассмотренном ранее примере "Hello, World!" текст выводился на экран с помощью строки прграммы
printf("Hello, World!\n'');
(Здесь '\n' — символ конца строки, т.е. перевода курсора в нача¬ ло следующей строки.) Единственным аргументом функции printf в данном случае служит форматная строка.
Кроме обычных символов, форматная строка может включать символы формата, которые при выводе заменяются значениями остальных аргументов функции printf, начиная со второго аргумента. Д л я каждого типа данных Си имеются свои форматы. Формат начи¬ нается с символа процента '%'. После процента идет необязательный числовой аргумент, управляющий представлением данных. Наконец, далее идет одна или несколько букв, задающих тип выводимых на
печать |
данных. Д л я вывода |
чисел можно |
использовать следующие |
форматы: |
|
|
|
%d |
вывод целого числа типа int (d — от decimal) |
||
%lf |
вывод вещ. числа |
типа double (lf |
— от long float) |
Например, для печати целого числа n можно использовать строку
printf("n = °/„d\n", n);
3.5.4. Пример: решение квадратного уравнения |
143 |
Здесь формат |
"%d" будет заменен на значение переменной |
n. Пусть, |
к примеру, n |
= 15. Тогда при выполнении функции printf |
будет на¬ |
печатана |
строка |
n = |
15 |
При |
печати вещественного числа компьютер сам решает, сколь¬ |
ко знаков после десятичной точки следует напечатать. Если нужно |
повлиять на представление числа, следует использовать необязатель¬ ную часть формата. Например, формат
/ . 3 l f
применяется для печати значения вещественного числа в форме с тремя цифрами после десятичной точки. Пусть значение веществен¬ ной переменной x равно единице. Тогда при выполнении функции
printf("oTBex = / . 3lf\n", x);
будет напечатана строка
ответ = 1.000
При вызове функции форматного ввода scanf форматная строка
должна содержать только |
форматы. Этим функция scanf отлича¬ |
||||
ется от |
printf. |
Вместо значений |
печатаемых переменных |
или вы¬ |
|
ражений, |
как |
в функции |
printf, |
функция scanf должна |
содержать |
указатели |
на вводимые переменные! Д л я начинающих это постоян¬ |
ный источник ошибок. Необходимо запомнить: функции scanf нужно
передавать |
адреса |
переменных, в которые надо |
записать введенные |
|
значения. |
Если вместо |
адресов переменных передать их значения, |
||
то функция scanf |
все |
равно проинтерпретирует |
полученные значе¬ |
ния как адреса, что при выполнении вызовет попытку записи по некорректным адресам памяти и, скорее всего, приведет к ошибке типа Segmentation fault. Пример: пусть нужно ввести значения трех
вещественных переменных |
a, |
b, c. Тогда следует использовать фраг¬ |
мент |
|
|
s c a n f ( " / l f / l f / l f " , |
&a, |
&b, &c); |
Ошибка, которую часто совершают начинающие: передача функции scanf значений переменных вместо адресов:
144 |
3.5. Управляющие конструкции |
s c a n f ( " / l f / l f / l f " , a, b, c) ; // Ошибка! Передаются // значения вместо указателей
Помимо стандартной библиотеки ввода-вывода, в Си-программах широко используется стандартная библиотека математических функ ций. Ее описания содержатся в стандартном заголовочном файле math.h, который подключается строкой
#include <math.h>
Стандартная математическая библиотека содержит математические
функции |
|
sin, cos, exp, log (натуральный логарифм), fabs (абсолютная |
||||
величина |
вещ . числа) |
и многие другие. На м необходима функция |
||||
sqrt, вычисляющая квадратный корень вещественного числа. |
||||||
Итак, |
приведем полный |
текст программы, решающей квадратное |
||||
уравнение; он содержится в файле "squareEq.cpp". |
||||||
#include |
<stdio.h> |
// Описания стандартного ввода-вывода |
||||
#include |
<math.h> |
// Описания математической библиотеки |
||||
int main() |
{ |
|
|
|
||
double |
a, b, c; // Коэффициенты уравнения |
|||||
double |
d; |
// Дискриминант |
|
|||
double |
x1, x2; |
// Корни уравнения |
||||
printf("Ввeдитe |
коэффициенты a, b, c:\n"); |
|||||
s c a n f ( " / l f / l f / l f \ |
&a, &b, &c); |
|
||||
i f |
(a == 0.0) { |
|
|
|
||
|
|
printf("Кoэффициeнт a должен быть ненулевым.^"); |
||||
} |
|
return 1; |
// Возвращаем код некорректного |
|||
|
|
|
// |
|
завершения |
|
d = b*b - 4.0*a*c; |
// Вычисляем |
дискриминант |
||||
i f |
(d < 0.0) { |
|
н е т . ^ " ) ; |
|
||
} |
|
printf("Рeшeний |
|
|||
|
{ |
|
|
|
||
else |
// Квадр. корень |
из дискриминанта |
||||
|
|
d |
= sqrt(d); |
3.5.5. Цикл while |
|
|
|
|
145 |
|
x1 |
= |
(-b + d) / |
(2.0 * a); // Первый корень ур-я |
|||
x2 |
= |
(-b - d) / |
(2.0 * a); // Второй корень ур-я |
|||
// Печатаем |
ответ |
|
||||
p r i n t f ( |
|
уравнения: x1 = % l f , |
x2 = % l f \ n " , |
|||
|
"Решения |
|||||
); |
x1, |
x2 |
|
|
|
|
|
|
|
|
|
|
|
} |
0; |
// Возвращаем код успешного |
завершения |
|||
return |
}
Приведем пример выполнения программы:
Введите коэффициенты a, b, c:
1 2 -3
Решения уравнения: x1 = 1.000000, x2 = -3.000000
Здесь первая и третья строчки напечатаны компьютером, вторая строчка напечатана человеком (ввод чисел заканчивается клавишей перевода строки Enter).
3.5.5.Цикл while
Конструкция цикла "пока" соответствует циклу while в Си:
while (условие) действие;
Ц и к л |
while |
называют циклом с предусловием, |
поскольку |
условие |
проверяется |
перед выполнением тела цикла. |
|
|
|
Ц и к л while выполняется следующим образом: сначала проверя¬ |
||||
ется |
условие. |
Если оно истинно, то выполняется действие. |
Затем |
|
снова |
проверяется условие; если оно истинно, |
то снова повторяется |
||
действие , и так до бесконечности. Ц и к л завершается, когда |
условие |
|||
становится ложным . Пример: |
|
|
int n, p;
p = 1;
146 |
3.5. Управляющие конструкции |
while |
(2*p <= n) |
p |
*= 2; |
В результате выполнения этого фрагмента в переменной p будет вы¬ числена максимальная степень двойки, не превосходящая целого по¬ ложительного числа п.
Если условие ложно с самого начала, то действие не выполняется ни разу. Это очень облегчает программирование и делает программу более надежной, поскольку исключительные ситуации автоматиче¬ ски правильно обрабатываются. Так, приведенный выше фрагмент работает корректно при п = 1 (цикл не выполняется ни разу).
При ошибке программирования цикл может никогда не кончить ся. Чтобы избежать этого, следует составлять программу таким обра¬ зом, чтобы некоторая ограниченная величина, от которой прямо или косвенно зависит условие в заголовке цикла, монотонно убывала или возрастала после каждого выполнения тела цикла. Это обеспечивает завершение цикла. В приведенном выше фрагменте такой величиной является значение p, которое возрастает вдвое после каждого выпол¬ нения тела цикла.
Тело цикла может состоять из одного или нескольких операторов. В последнем случае их надо заключить в фигурные скобки. Советуем заключать тело цикла в фигурные скобки д а ж е в том случае, когда оно состоит всего из одного оператора, — это делает текст программы
более наглядным |
и облегчает его возможную модификацию. Напри¬ |
|
мер, приведенный |
выше фрагмент лучше было бы записать так: |
|
int n, p; |
|
|
p = 1; |
(2*p <= n) { |
|
while |
||
p |
*= 2; |
|
} |
|
|
Сознательное применение цикла "пока" всегда связано с явной формулировкой инварианта цикла, см. раздел 1.5.2.
Рассмотрим построение цикла "пока" на примере программы вы¬ числения квадратного корня методом деления отрезка пополам.
3.5.6. Пример: вычисление квадратного корня делением отрезка |
147 |
3.5.6.Пример: вычисление квадратного корня методом деления отрезка пополам
Метод вычисления корня функции с помощью деления отрезка пополам в общем случае у ж е был рассмотрен в разделе 1.5.2. Пусть надо найти квадратный корень из неотрицательного вещественного числа а с заданной точностью е. Задача сводится к нахождению корня функции
y = x 2 — а
на отрезке [0,6], где b = max(1,a). На этом отрезке функция имеет ровно один корень, поскольку она монотонно возрастает и на концах
отрезка принимает значения разных знаков (или нулевое |
значение |
при а = 0 или а = 1). |
|
Идея алгоритма состоит в том, что отрезок делится пополам и |
|
выбирается та половина, на которой функция принимает |
значения |
разных знаков. Эта операция повторяется до тех пор, пока длина от¬
резка |
не станет |
меньше, чем е. |
Концы |
текущего |
отрезка содержатся |
|
в переменных |
x0, x l . В данном |
случае функция монотонно возрас |
||||
тает |
при x > |
0. |
Инвариантом |
цикла |
является |
утверждение о том, |
что функция принимает отрицательное или нулевое значение в точ¬
ке x0 и положительное или нулевое значение в точке x 1 . Ц и к л |
рано |
или поздно завершается, поскольку после каждого выполнения |
тела |
цикла длина отрезка [x0, x1] уменьшается в два раза. Приведем полный текст программы:
#include <stdio.h> // Описания стандартного ввода-вывода
int main() |
{ |
|
|
|
|
double |
a; |
// Число, |
из которого извлекается |
корень |
|
double |
x, x0, x1; |
// [x0, x1] |
- текущий |
отрезок |
|
double |
y; |
|
// Значение ф-ции в точке x |
||
double |
eps = 0.000001; |
// Точность |
вычисления |
корня |
|
printf("Ввeдитe число |
a:\n"); |
|
|
||
scanf('7„lf", &a); |
|
|
|
||
i f (a < 0.0) |
{ |
|
|
|
printf("Числo должно быть неотрицательным.^");
148 |
|
|
|
|
|
|
|
|
|
|
3.5. Управляющие конструкции |
||
} |
return 1; |
// Возвращаем |
код |
|
завершения |
||||||||
|
|
|
|
// |
некорректного |
||||||||
// |
Задаем |
|
концы |
отрезка |
|
|
|
|
|
|
|||
x0 |
= 0.0; |
|
|
|
|
|
|
|
|
|
|
|
|
x1 |
= a; |
1.0) |
{ |
|
|
|
|
|
|
|
|
||
i f |
(a < |
|
|
|
|
|
|
|
|
||||
} |
x1 |
= |
1.0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// |
Утверждение: |
x0 |
* x0 - a <= |
0, |
|
|
|
||||||
// |
|
|
|
|
|
x1 |
* x1 |
- a |
>= |
0 |
|
|
|
while (x1 |
|
- x0 |
> eps) { |
|
|
|
0, |
|
|||||
|
// Инвариант: |
x0 * x0 - a <= |
|
||||||||||
|
// |
(x0 +x1) |
x1 * x1 - a |
>= |
0 |
|
|
||||||
|
x = |
/ 2.0; // середина отрезка [x0,x1] |
|||||||||||
|
y = x * x - a ; |
|
// значение ф-ции в точке x |
||||||||||
|
i f |
(y |
|
>= |
0.0) |
{ |
|
левую |
|
половину |
отрезка |
||
|
} |
x1 |
= x; |
// |
выбираем |
|
|||||||
|
|
{ |
|
|
|
|
|
|
|
|
|
||
|
else |
|
|
|
|
|
|
|
|
|
|||
|
} |
x0 |
= x; |
// |
выбираем |
правую |
половину |
отрезка |
|||||
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// |
Утверждение: |
x0 |
* x0 - a <= |
0, |
|
|
|||||||
// |
|
|
|
|
|
x1 |
* x1 |
- a |
>= |
0, |
|
|
|
// |
|
|
|
|
|
x1 |
- x0 |
<= |
eps |
|
|
|
|
x |
= (x0 + x1) |
/ 2.0; // |
Корень |
:= |
|
середина |
отрезка |
||||||
// |
Печатаем ответ |
корень = |
% l f \ n " , x); |
|
|||||||||
printf("Квадратный |
|
||||||||||||
return |
0; |
|
// Возвращаем |
код |
успешного завершения |
||||||||
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
3.5.7. Выход из цикла break, переход на конец цикла continue |
149 |
Отметим, что существует более быстрый способ вычисления квадратного корня числа — метод итераций Ньютона, или метод ка сательных к графику функции, но здесь мы его не рассматриваем.
3.5.7.Выход из цикла break, переход на конец цикла continue
Если необходимо прервать выполнение цикла, следует использо¬ вать оператор
break;
Оператор break применяется внутри тела цикла, заключенного в фигурные скобки. Пример: требуется найти корень целочисленной функции f(x), определенной для целочисленных аргументов.
int |
f ( i n t |
x); |
// Описание прототипа функции |
||
int |
x; |
|
|
|
|
// Ищем |
корень функции f(x) |
||||
x = 0; |
|
|
{ |
|
|
while (true) |
0) { |
||||
|
i f |
(f(x) == |
|||
|
} |
break; |
// Нашли корень |
||
|
|
|
|
|
|
|
// Переходим к следующему целому значению x |
||||
|
// |
|
в порядке 0, -1, 1, -2, 2, -3, 3, ... |
||
|
i f |
(x >= |
0) { |
||
|
} |
x = |
(-x - 1); |
||
|
|
{ |
|
|
|
|
else |
|
|
||
|
} |
x |
= (-x); |
||
|
|
|
|
|
}
// Утверждение: f(x) == 0
Здесь используется бесконечный цикл "while (true)". Выход из цикла осуществляется с помощью оператора "break".
150 |
3.5. Управляющие конструкции |
Иногда требуется пропустить выполнение тела цикла при какихлибо значениях изменяющихся в цикле переменных, переходя к сле¬ дующему набору значений и очередной итерации. Д л я этого исполь¬ зуется оператор
continue;
Оператор continue, так же , как и break, используется лишь в том случае, когда тело цикла состоит более чем из одного оператора и заключено в фигурные скобки. Его следует понимать как переход на фигурную скобку, закрывающую тело цикла. Пример: пусть за¬
дана n + |
1 точка на вещественной прямой xi, i = 0 , 1 , . . . , n; точки |
|||
Xi будут |
называться |
узлами интерполяции. Элементарный |
интерпо |
|
ляционный |
многочлен |
Лагранжа Lk (x) |
— это многочлен степени n , |
|
который |
принимает |
нулевые значения |
во всех узлах x i , |
кроме Xk. |
В k-ом узле Xfc многочлен |
L k ( x ) |
принимает значение 1. Многочлен |
|
L k ( x ) вычисляется по следующей |
формуле: |
||
L k |
( x ) = П |
^ |
Пусть требуется вычислить значение элементарного интерполяцион¬ ного многочлена L k ( x ) в заданной точке x = t. Это делается с помо¬ щью следующего фрагмента программы:
double |
x[100]; // Узл^г интерполяции (не более 100) |
|
int |
n; |
// Количество узлов интерполяции |
int |
k; |
// Номер узла |
double t ; // Точка, в которой вычисляется значение double L; // Значение многочлена L_k(x) в точке t int i ;
L |
= |
1.0; // Начальное значение |
произведения |
||
i |
= |
0; |
( i <= |
n) { |
|
while |
|
||||
|
|
i f |
( i == |
k) { |
|
|
|
|
++i; |
// К следующему узлу |
|
|
|
} |
continue; // Пропустить |
k-й множитель |
|
|
|
|
|
|