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

Основы программирования. Борисенко

.pdf
Скачиваний:
1534
Добавлен:
09.04.2015
Размер:
9.31 Mб
Скачать

3.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-й множитель