Программирование >>  Динамические структуры данных 

1 2 3 4 [ 5 ] 6 7 8 ... 38


иметь в виду, что в нем всегда явно или неявно присутствуют четыре элемента: начальные установки, тело цикла, модификация параметра цикла и проверка условия продолжения цикла (рис. 2.4). Начинающие чаще всего забывают про первое и/или третье.

Задача 2.4. Таблица значений функции

Написать программу печати таблицы значений функции

/, JC < О tx,0<x<lO 2/, хЮ

для аргумента, изменяющегося в заданных пределах с заданным шагом. Если t > 100, должны выводиться целые значения функции.

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

В словесной форме алгоритм можно сформулировать так:

1. Ввести исходные данные.

2. Взять первое из значений аргумента.

3. Определить, какому из интервалов оно принадлежит.

4. Вычислить значение функции у по соответствующей формуле.

5. Если t > 100, преобразовать значение у в целое.

6. Вывести строку таблицы.

7. Перейти к следующему значению аргумента.

8. Если оно не превышает конечное значение, повторить шаги 3-7, иначе закончить выполнение.

В каждый момент времени требуется хранить одно значение функции, поэтому для него достаточно завести одну переменную вещественного типа. Шаги 3-7 повторяются многократно, поэтому для их выполнения надо организовать цикл. В приведенном ниже варианте используется цикл whi 1е:

#include <stdio.h> #include <niath.h> int main(){

double Xn. Xk. dX. t. y:

printf( Enter Xn. Xk. dX. t \n ):

scanf( !i;lf lf3!lf3!lf .&Xn. &Xk. &dX. &t):

printf( -.........------...........\n );

printf( I X I Y \n ):

printf( ...........................\n ):

double X = Xn:

while (X <- Xk){ if ( X < 0 ) у t: if ( X >= 0 && X < 10) у - t * x: if ( X >= 10 ) у - 2 * t: if (t > 100) printf( !i:9.21f \%9(i else printf( a:9.21f 1X9.21f X +- dX:

printf( ................-..........\n ):

return 0: }

A вот та же программа с использованием оператора for:

#include <stdio.h>

#include <math.h>

int main(){ double Xn. Xk. dX. t. y: printf( Enter Xn. Xk. dX. t \n ): scanf( !i;if!i;ifa:if!i;if .&xn. &xk. &dx. &t):

printf( ...........................\n )

printf( I X I Y \n )

printf( ......................-.....Vn )

for ( double X - Xn: X <- Xk: x += dX){

if ( X < 0 ) у - t:

if ( x 0 && X < 10) у - t * x:

if ( X >- 10 ) у - 2 * t:

if (t > 100) printf( a:9.21f \%9(1

else

Начальные установки

\n . X. (int)y): \n . X. y):

Модификация параметра цикла

printf( return 0:

printf( !i;9.21f 1X9.21f

X. (int)y): X. y):

\n ):

В программу введена вспомогательная переменная х, которая последовательно принимает значения от Хп до Хк с шагом dX. Она определена непосредственно перед использованием. Это является хорошим стилем, поскольку снижает вероятность ошибок (например, таких, как использование неинициализированной переменной).

СОВЕТ -

В общем случае надо стремиться к минимизации области видимости переменных.

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

В старых версиях компиляторов (и даже в VC!) переменная видна и после цикла.



говоря, в условии цикла while допускается описывать и инициализировать переменную таким же образом, как в операторе i f, но при этом синтаксис не допускает ее сравнения с Хк. Другим преимуществом второго варианта программы является то, что все управление циклом for сосредоточено в его заголовке. Это делает программу более читаемой.

Для преобразования значения функции к целому в программе использовалась конструкция (int )у, унаследованная из языка С. Строго говоря, в данном случае лучше применить операцию преобразования типа static cast, введенную в С++, но в старых компиляторах она может не поддерживаться:

printfC!i;9.21f 3;9d \n . x. static cast<int>(y)): Выполните программу несколько раз, задавая различные значения исходных данных. С помощью ручного просчета убедитесь в правильности вычислений.

Рассмотрим теперь пример цикла, количество итераций которого заранее подсчитать невозможно.

Задача 2.5. Вычисление суммы ряда

Написать программу вычисления значения функции Ch х (гиперболический косинус) с помощью бесконечного ряда Тейлора с точностью е по формуле:

, jc jc JC* jc

v = l+- +-+ -+... +-

2! 4! 6! 2л!

+ ..

Этот ряд сходится при I Jr I < оо. Для достижения заданной точности требуется суммировать члены ряда, абсолютная величина которых больше е. Для сходящегося ряда модуль члена ряда при увеличении п стремится к нулю. При некотором п неравенство С > е перестает выполняться, и вычисления прекращаются. Общий алгоритм решения этой задачи очевиден: требуется задать начальное значение суммы ряда, а затем многократно вычислять очередной член ряда и добавлять его к ранее найденной сумме. Вычисления заканчиваются, когда абсолютная величина очередного члена ряда станет меньше заданной точности. До выполнения программы предсказать, сколько членов ряда потребуется просуммировать, невозможно. В цикле такого рода есть опасность, что он никогда не завершится - как из-за возможных ошибок в вычислениях, так и из-за ограниченной области сходимости ряда (данный ряд сходится на всей числовой оси, но существуют ряды Тейлора, которые сходятся только для определенного интервала значений аргумента). Поэтому для надежности программы необходимо предусмотреть аварийный выход из цикла с печатью предупреждающего сообщения по достижении некоторого максимально допустимого количества итераций. Прямое вычисление члена ряда по приведенной выше общей формуле, когда х возводится в степень, вычисляется факториал, а затем числитель делится на зна-

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

Для уменьшения количества выполняемых действий следует воспользоваться рекуррентной формулой получения последующего члена ряда через предыдущий С - С X Г, где Г - некоторый множитель. Подставий в эту формулу С и С получим выражение для вычисления Г:

2п!.х<-> С х (2(п + 1))! (2/1 + 1Х2Я+2) Ниже приведен текст программы с комментариями. Обратите внимание на заголовок цикла: в нем, кроме части начальных установок, заданы условие выхода из цикла и модификация параметра цикла, то есть в данной программе все составные части цикла присутствуют в явном виде.

#include <iostream.h> #include <math.h> int main(){

const int Maxlter - 500: /* максимально допустимое количество итераций */ double X. eps:

cout ХпВведите аргумент и точность: : cin X eps:

bool done - true: признак достижения точности

double ch - 1. у - ch: первый член ряда и нач. значение суммы for (int n - 0: fabs(ch) > eps: n++) { ch *- X * X /((2 * n + 1)*(2 * n + 2)): очередной член ряда у += ch: добавление члена ряда к сумме

if (п > Maxlter){ cout \пРяд расходится! : done - false: break:}

if (done){

cout ХпЗначение функции: у для x = х endl: cout вычислено после n итераций endl:

return 0:

Об этой операции рассказывается в Учебнике на с. 237. Аналогичный пример приведен в Учебнике на с. 50.

Кроме того, большие числа могут переполнить разрядную сетку.



Первый член ряда равен 1, поэтому чтобы при первом проходе цикла значение второго члена вычислялось правильно, п должно быть равно О, Максимально допустимое количество итераций удобно задать с помощью именованной константы. Для аварийного выхода из цикла применяется оператор break (см. Учебник, с. 50), который выполняет выход на первый после цикла оператор. Поскольку выход как в случае аварийного, так и в случае нормального завершения цикла происходит в одну и ту же точку программы, вводится булева переменная done, которая предотвращает печать значения функции после выхода из цикла в случае, когда точность вычислений не достигнута. Создание подобных перемен-ных-< флагов , принимающих значение истина в случае успешного окончания вычислений и -*ложь в противном случае, является распространенным приемом программирования.

Измените программу так, чтобы она печатала не только значения аргумента и функции, но и количество просуммированных членов ряда, и выполните программу несколько раз для различных значений аргумента и точности. Выявите зависимость между этими величинами.

Можно реализовать ту же логику и без специальной булевой переменной, объединив проверку обоих вариантов выхода из цикла в его заголовке:

#include <iostream.h>

#include <math.h>

#include <float.h>

int main(){

const int Maxlter - 500: /* максилально допустиное количество

итераций */

double X. eps - DBL EPSILON:

cout ХпВведите аргумент : cin x :

double ch - 1. у = ch: первый член ряда и нач. значение суммы for (int n - 0: fabs(ch) > eps && n < Maxlter: n++) {

ch *= X * X /((2 * n + 1)*(2 * n + 2)): очередной член ряда

у +- ch:

if (n < Maxlter) {

cout ХпЗначение функции: у для х - х endl: cout вычислено после п итераций endl:

else cout \пРяд расходится! :

return 0:

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

в библиотеке есть функция cosh(x). вычисляющая гиперболический косинус. Ее прототип находится в файле <math.h>. Измените программу так, чтобы она рядом

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

Итак, мы рассмотрели две задачи с использованием циклов. Циклы встречаются в программах повсеместно, и строятся они по одним и тем же принципам. Чтобы избежать ошибок при программировании, рекомендуется:

□ проверить, всем ли переменным, встречающимся в правой части операторов присваивания в теле цикла, присвоены до этого начальные значения, а также возможно ли выполнение других операторов;

□ проверить, изменяется ли в цикле хотя бы одна переменная, входящая в условие выхода из цикла;

□ предусмотреть аварийный выход из цикла по достижении некоторого количества итераций;

□ не забывать заключать в фигурные скобки тело цикла, если в нем требуется выполнить более одного оператора.

Операторы цикла в языке С++ взаимозаменяемы, но можно привести некоторые рекомендации по выбору наилучшего в каждом конкретном случае. Оператор do whi 1е обычно используют, когда цикл требуется обязательно выполнить хотя бы один раз, - например, если в цикле производится ввод данных. Оператором whi 1 е удобнее пользоваться в тех случаях, когда либо число итераций заранее неизвестно, либо очевидных параметров цикла нет, либо модификацию параметров удобнее записывать не в конце тела цикла.

Оператор for предпочтительнее в большинстве остальных случаев. Однозначно - для организации циклов со счетчиками, то есть с целочисленными переменными, которые изменяют свое значение при каждом проходе цикла регулярным образом (например, увеличиваются на 1).

Давайте повторим наиболее важные моменты этого семинара.

1. Выражение, стоящее в круглых скобках операторов i f, whi 1 е и do whi 1 е, вычисляется в соответствии с приоритетами операций и преобразуется к типу bool.

2. Если в какой-либо ветви вычислений условного оператора или в цикле требуется выполнить более одного оператора, то они объединяются в блок.

3. Проверка вещественных величин на равенство опасна.

4. Чтобы получить максимальную читаемость и простоту структуры программы, надо правильно выбирать способ реализации ветвлений (с помощью if, switch или условной операции), а также наиболее подходящий оператор цикла.

5. Выражение, стоящее в скобках после ключевого слова switch, и константные выражения в case должны быть целочисленного типа.

6. Рекомендуется всегда описывать в операторе switch ветвь defaul t.

7. После каждой ветви для передачи управления на конец оператора switch используется оператор break.



1 2 3 4 [ 5 ] 6 7 8 ... 38

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика