Лабораторная работа №4 ПРОГРАММИРОВАНИЕ ЦИКЛИЧЕСКИХ АЛГОРИТМОВ Цель лабораторной работы: изучить простейшие средства отладки программ в среде C++ Builder. Составить и отладить программу циклического алгоритма. 1. Операторы организации циклов do..while, while, for языка C++ Под циклом понимается многократное выполнение одних и тех же операторов при различных значениях промежуточных данных. Число повторений может быть задано в явной или неявной форме. Для организации повторений в языке C++ предусмотрены три различных оператора цикла. 1. Цикл с предусловием: while (<выражение-условие>) <тело_цикла>; В качестве <выражения-условия> чаще всего используется отношение или логическое выражение. Если оно истинно, т.е. не равно 0, то тело цикла выполняется до тех пор пока <выражение-условие> не станет ложным. 2. Цикл с постусловием: do <тело_цикла>; while (<выражение-условие>); Тело цикла выполняется до тех пор, пока <выражение-условие> истинно. Замечание: Если в цикле с предусловием (while) <выражение-условие> будет равно 0 (т.е. ложно) при первом входе, то тело цикла не выполнится ни разу, в отличие от цикла с постусловием (do..while), в котором тело цикла всегда выполнится хотя бы один раз. 3. Цикл с параметром: for (<выражение_1>;<выражение-условие>;<выражение_3>) <тело_цикла>; <Выражение_1> и <выражение_3> могут состоять из нескольких выражений, разделенных запятыми. <Выражение_1> - задает начальные условия для цикла (инициализация). <Выражениеусловие> определяет условие выполнения цикла, если оно не равно 0, цикл выполняется, а затем вычисляется значение <выражения_3>. <Выражение_3> - задает изменение параметра цикла или других переменных (коррекция). Цикл продолжается до тех пор, пока <выражение-условие> не станет равно 0. Любое выражение может отсутствовать, но разделяющие их « ; » должны быть обязательно. Примеры использования цикла с параметром. 1) Уменьшение параметра: for ( n=10; n>0; n--) { <тело цикла>}; 2) Изменение шага корректировки: for ( n=2; n>60; n+=13) { <тело цикла>}; 3) Возможность проверять условие отличное от условия, которое налагается на число итераций: for ( num=1;num*num*num<216; num++) { <тело цикла>}; 4) Коррекция может осуществляться не только с помощью сложения или вычитания: for ( d=100.0; d<150.0;d*=1.1) { <тело цикла>}; for (x=1;y<=75;y=5*(x++)+10) { <тело цикла>}; 5) Можно использовать несколько инициализирующих или корректирующих выражений: for (x=1, y=0; x<10;x++;y+=x); 1 2. Средства отладки программ в C++ Builder Практически в каждой вновь написанной программе после запуска обнаруживаются ошибки. Ошибки первого уровня (ошибки компиляции) связаны с неправильной записью операторов (орфографические, синтаксические). При обнаружении ошибки компилятор C++ Builder останавливается напротив первого оператора, в котором обнаружена ошибка. В нижней части экрана появляется текстовое окно, содержащее сведения обо всех ошибках, найденных в проекте. Каждая строка этого окна содержит имя файла, в котором найдена ошибка, номер строки с ошибкой и характер ошибки. Для быстрого перехода к интересующей ошибке необходимо дважды щелкнуть по строке с ее описанием. Для получения более полной информации о характере ошибки необходимо обратиться к справке нажатием клавиши F1. Следует обратить внимание на то, что одна ошибка может повлечь за собой другие, которые исчезнут при ее исправлении. Поэтому следует исправлять ошибки последовательно, сверху вниз и, после исправления каждой ошибки компилировать программу снова. Ошибки второго уровня (ошибки выполнения) связаны с ошибками выбранного алгоритма решения или с неправильной программной реализацией алгоритма. Эти ошибки проявляются в том, что результат расчета оказывается неверным либо происходит переполнение, деление на ноль и др. Поэтому перед использованием отлаженной программы ее надо протестировать, т.е. сделать просчеты при таких комбинациях исходных данных, для которых заранее известен результат. Если тестовые расчеты указывают на ошибку, то для ее поиска следует использовать встроенные средства отладки среды C++ Builder. В простейшем случае для локализации места ошибки рекомендуется поступать следующим образом. В окне редактирования текста установить курсор в строке перед подозрительным участком и нажать клавишу F4 (выполнение до курсора). Выполнение программы будет остановлено на строке, содержащей курсор. Теперь можно увидеть, чему равно значение интересующей переменной. Для этого можно поместить на нужную переменную курсор (на экране будет высвечено ее значение) либо нажать Ctrl+F7 и в появившемся диалоговом окне указать интересующую переменную (с помощью данного окна можно так же изменить значение переменной во время выполнения программы). Нажимая клавишу F7 (пошаговое выполнение), можно построчно выполнять программу, контролируя изменение тех или иных переменных и правильность вычислений. Если курсор находится внутри цикла, то после нажатия F4 расчет останавливается после одного выполнения тела цикла. Для продолжения расчетов следует нажать <Run> меню Run. 2 3. Выполнение индивидуального задания №1 3.1. Задание (вариант 15) Написать и отладить программу, которая выводит таблицу значений суммы ряда S(x) и контрольной функции Y(x) для х, изменяющегося в интервале от a до b c шагом h = (b – a) / 10. N S ( x) (1) k 1 k 1 x 2k 2k 2k 1 Y ( x) x arctg x ln 1 x 2 3.2. Общий метод решения Очевидно, что процесс построения таблицы значений должен быть итерационным, представляющим собой два цикла: 1. Изменение текущего значения аргумента x; 2. Вычисление суммы ряда. Второй цикл вложен в первый. Для подсчета суммы членов ряда следует повторять вычисления по одной и той же формуле при значениях k= 1, 2, ...N . В каждой итерации цикла следует подсчитывать очередной член ряда. Полученное значение следует прибавлять к переменной, которая представляет сумму. Эта переменная в каждой итерации будет содержать в себе сумму всех уже обработанных членов ряда, следовательно, нужно определить ее начальное значение. После завершения вычисления суммы ряда необходимо вычислить значение контрольной функции, напечатать значение суммы и функции. Близость значений S(x) и Y(x) во всем диапазоне значений x указывает на правильность вычисления S(x) и Y(x). 3.3. Построение рекуррентной формулы Для подсчета очередного члена ряда во избежание переполнения данных и уменьшения погрешности вычислений в большинстве случаев удобно воспользоваться рекуррентной формулой. n 1 Требуется найти сумму ряда, общий член которого an (1) x 2n . 2n2n 1 Для получения рекуррентной формулы необходимо вычислить отношение a n 1 : an ( n 1) 1 2 ( n 1) n 11 2 n 2 a n 1 1 x 2n(2n 1) 1 x 2n(2n 1) x 2 n(2n 1) an 2n 12(n 1) 1 1n 1 x 2 n 2n 12n 1 1n 1 x 2 n (n 1)( 2n 1) Откуда получаем: a n1 an x 2 n(2n 1) (n 1)( 2n 1) 3.4. Определение переменных программы Для реализации алгоритма понадобятся такие переменные: x1, x2 – границы интервала изменения аргумента: double x1,x2; h – шаг изменения аргумента. double h; x – текущее (изменяющееся) значение аргумента: double x; N – количество элементов ряда, которые нужно просуммировать: int N; a – значение текущего члена ряда: double a; s – сумма членов ряда: double s; k – параметр внутреннего цикла – номер члена ряда : int k; 3 y – значение контрольной функции от текущего аргумента: double y; 3.5. Разработка текста программы Начинаем разработку текста программы с заголовка главной функции: void main() Далее открывается тело функции, и в него включаются объявления переменных. Кодовая часть программы начинается с ввода границ интервала изменения аргумента и количества суммируемых элементов ряда. cin >> x1; cin >> x2; cin >> N; Далее вычисляется шаг изменения аргумента и определяется начальное значение аргумента? h=(x2-x1)/10.0; x=x1; Открываем цикл перебора значений аргумента – цикл с предусловием: пока текущее значение аргумента меньше конечного значения интервала. Для учета машинной погрешности усилим условие, увеличив на небольшое число правую часть: while (x<x2+0.001) Далее реализуем тело внешнего цикла. Сначала вычислим значение первого элемента ряда и добавим его к сумме: double a=pow(x,2.0)/2.0; double s=a; Далее организуем вычисление суммы ряда с помощью цикла с параметром. Параметр цикла – номер элемента ряда, изменяющийся от 1 до N. for(int k=1;k<=N;k++) Внутри цикла вычислим значение следующего элемента ряда, используя построенную рекуррентную формулу, добавим полученное значение к сумме и завершим цикл: a=a*(-x*x*k*(2.0*k-1)/((k+1)*(2.0*k+1))); s+=a; // Запись эквивалентна s=s+a; Продолжим реализацию внешнего цикла. Вычислим значение контрольной функции: double y=x*atan(x)-log(sqrt(1+pow(x,2.0))); Выведем текущее значение аргумента, суммы и контрольной функции. cout << x << s << y; Вычислим следующее значение аргумента и завершаем внешний цикл while: x+=h; Полный текст программы приведен ниже. /****************************************************/ /* Лабораторная работа 4 */ /* Вычисление суммы ряда */ /* Пример выполнения. Вариант 15. */ /****************************************************/ #include <vcl.h> #include <iostream.h> #include <iomanip.h> #include <conio.h> #include <math.h> char* Rus(const char* text); void main() { double x1,x2,x,h; int N; cout << Rus("Введите начало интервала X1: "); cin >> x1; cout << Rus("Введите конец интервала X2: "); cin >> x2; cout << Rus("Введите количество суммируемых элементов ряда N: "); cin >> N; 4 h=(x2-x1)/10.0; x=x1; cout << "|-------|-------------|-------------|" << endl; cout << "| X | S | Y |" << endl; cout << "|-------|-------------|-------------|" << endl; cout << setiosflags(ios::fixed); while (x<x2+0.001) { double a=pow(x,2.0)/2.0; double s=a; for(int k=1;k<=N;k++) { a=a*(-x*x*k*(2.0*k-1)/((k+1)*(2.0*k+1))); s+=a; // Запись эквивалентна s=s+a; } double y=x*atan(x)-log(sqrt(1+pow(x,2.0))); cout << setprecision(3) << "|" << setw(7) << x << "|" << setprecision(10) << setw(13) << s << "|" << setw(13) << y << "|" << endl; x+=h; // Запись эквивалентна x=x+h; } cout << "|-------|-------------|-------------|" << endl; getch(); } char bufRus[256]; char* Rus(const char* text) { CharToOem(text,bufRus); return bufRus; } 3.6. Отладка программы Форма вывода результатов программы столь наглядна, что по результатам можно убедиться в правильном функционировании программы: значения суммы ряда и контрольной функции для каждого значения аргумента должны быть почти равны. При неправильном функционировании необходимо: убедиться в правильном построении рекуррентной формулы; убедиться в правильном вычислении первого элемента ряда; убедиться в правильной организации внешнего и внутреннего циклов; убедиться в правильном вычислении контрольной функции. 3.7. Результаты работы программы Результат работы программы приведен ниже: 5 3.8. Индивидуальные задания В заданиях с 1 по 15 (табл. 1) необходимо вывести на экран таблицу значений функции Y(x) и ее разложения в ряд S(x) для x, изменяющегося от a до b с шагом h=(b-a)/10. n – количество элементов ряда, которые необходимо суммировать. Близость значений S(x) и Y(x) во всем диапазоне значений x указывает на правильность вычисления S(x) и Y(x). Таблица 1 6 4. Выполнение индивидуального задания №2 4.1. Задание (вариант 30) Написать и отладить программу, которая выводит таблицу значений суммы ряда S(x) и контрольной функции Y(x) для х, изменяющегося в интервале от a до b c шагом h = (b – a) / 10. sin( 2k 1) x 2k 1 k 1 sign x Y ( x) 4 S ( x) 4.2. Общий метод решения Очевидно, что процесс построения таблицы значений должен быть итерационным, представляющим собой два цикла: 1. Изменение текущего значения аргумента x; 2. Вычисление суммы ряда. Второй цикл вложен в первый. В каждой итерации цикла следует подсчитывать очередной член ряда по одной и той же формуле пока очередной член ряда не станет достаточно малым. Полученное значение следует прибавлять к переменной, которая представляет сумму. Эта переменная в каждой итерации будет содержать в себе сумму всех уже обработанных членов ряда, следовательно, нужно определить ее начальное значение. После завершения вычисления суммы ряда необходимо вычислить значение контрольной функции, напечатать значение суммы и функции. Близость значений S(x) и Y(x) во всем диапазоне значений x указывает на правильность вычисления S(x) и Y(x). 4.3. Построение рекуррентной формулы Для подсчета очередного члена ряда во избежание переполнения данных и уменьшения погрешности вычислений в большинстве случаев удобно воспользоваться рекуррентной формулой. Требуется найти сумму ряда, общий член которого a n sin( 2n 1)n . 2n 1 В данном случае формула вычисления члена ряда довольно простая и построение рекуррентной формулы не требуется. 4.4. Определение переменных программы Для реализации алгоритма понадобятся такие переменные: x1, x2 – границы интервала изменения аргумента: double x1,x2; h – шаг изменения аргумента. double h; x – текущее (изменяющееся) значение аргумента: double x; eps – точность вычисления суммы членов ряда: int N; a – значение текущего члена ряда: double a; s – сумма членов ряда: double s; 7 k – параметр внутреннего цикла – номер члена ряда : int k; y – значение контрольной функции от текущего аргумента: double y; 4.5. Разработка текста программы Начинаем разработку текста программы с заголовка главной функции: void main() Далее открывается тело функции, и в него включаются объявления переменных. Кодовая часть программы начинается с ввода границ интервала изменения аргумента и погрешности вычисления суммы ряда. cin >> x1; cin >> x2; cin >> eps; Далее вычисляется шаг изменения аргумента и определяется начальное значение аргумента? h=(x2-x1)/10.0; x=x1; Открываем цикл перебора значений аргумента – цикл с предусловием: пока текущее значение аргумента меньше конечного значения интервала. Для учета машинной погрешности усилим условие, увеличив на небольшое число правую часть: while (x<x2+0.001) Далее реализуем тело внешнего цикла. Сначала вычислим значение первого элемента ряда и добавим его к сумме: double a=sin(x); double s=a; int k=1; Далее организуем вычисление суммы ряда с помощью цикла с предусловием. В условии цикла будем проверять достигли ли мы необходимой точности вычисления суммы ряда. while(fabs(a)>eps) Внутри цикла увеличим номер следующего члена ряда и вычислим его значение, добавим полученное значение к сумме и завершим цикл: k++; a=sin((2*k-1)*x)/(2*k-1); s+=a; Продолжим реализацию внешнего цикла. Вычислим значение контрольной функции. Выведем текущее значение аргумента, суммы и контрольной функции. cout << x << s << y; Вычислим следующее значение аргумента и завершаем внешний цикл while: x+=h; Полный текст программы приведен ниже. /****************************************************/ /* Лабораторная работа 4 */ /* Вычисление суммы ряда */ /* Пример выполнения. Вариант 30. */ /****************************************************/ #include <vcl.h> #include <iostream.h> #include <iomanip.h> #include <conio.h> #include <math.h> char* Rus(const char* text); void main() { double x1,x2,x,h,eps; cout << Rus("Введите начало интервала X1: "); cin >> x1; cout << Rus("Введите конец интервала X2: "); cin >> x2; 8 cout << Rus("Введите точность вычисления суммы ряда: "); cin >> eps; h=(x2-x1)/10.0; x=x1; cout << "|-------|-------------|-------------|" << endl; cout << "| X | S | Y |" << endl; cout << "|-------|-------------|-------------|" << endl; cout << setiosflags(ios::fixed); while (x<x2+0.001) { double a=sin(x); double s=a; int k=1; while(fabs(a)>eps) { k++; a=sin((2*k-1)*x)/(2*k-1); s+=a; } double y; if(x<0)y=-M_PI/4; else if (x>0)y=M_PI/4; else y=0; cout << setprecision(3) << "|" << setw(7) << x << "|" << setprecision(10) << setw(13) << s << "|" << setw(13) << y << "|" << endl; x+=h; // Запись эквивалентна x=x+h; } cout << "|-------|-------------|-------------|" << endl; getch(); } char bufRus[256]; char* Rus(const char* text) { CharToOem(text,bufRus); return bufRus; } 4.6. Результаты работы программы Результат работы программы приведен ниже: 4.8. Индивидуальные задания В заданиях с № 16 по 30 (табл. 2.) необходимо вывести на экран таблицу значений функции Y(x) и ее разложения в ряд S(x) с точностью ε. Близость значений S(x) и Y(x) во всем диапазоне значений x указывает на правильность вычисления S(x) и Y(x). Вывести число итераций, необходимое, для достижения заданной точности. При определении суммы членов ряда следует использовать рекуррентную формулу для получения следующего члена ряда. При составлении программы считать, что точность достигнута, если |аn| < 9 Таблица 2 10