Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Posibnyk_C.doc
Скачиваний:
23
Добавлен:
03.11.2018
Размер:
1.56 Mб
Скачать

4.3 Цикли

Циклом називається обчислювальний процес, який багаторазово повторюється. Основною його характеристикою є аргумент або параметр циклу. Це змiнна, яка перед циклом одержує початкове значення, а в циклi нарощується i перевiряється на досягнення кiнцевого значення, перевищення якого є умовою закiнчення циклу. В залежності від характеру зміни параметра розрiзняють цикл з рiвномiрним приростом аргумента, який називають ще циклом типу арифметичної прогресiї, та iтерацiйний або перерахунковий.

Для програмування циклів служать такі спеціальні оператори:

  • for(вираз1; вираз2; вираз3)оператор;

Тут: вираз1 – присвоєння параметру циклу початкового значення;

вираз2 – умова, перевiрка на досягнення кiнцевого значення;

вираз3 – нарощення параметра циклу на заданий крок;

оператор – тiло циклу, один оператор або блок;

  • while(вираз)оператор; Це оператор циклу з передумовою, цикл виконується поки значення виразу не дорівнює нулю;

  • do оператор; while(вираз); Оператор циклу з постумовою, від попереднього відрізняється тим, що спочатку цикл виконується, а потім превіряється умова виходу з циклу, тому незалежно від задовільнення умови він буде виконаний хоча б один раз;

  • continue; Продовження, його дiя полягає в переходi на кiнець циклу, тобто на нарощення параметра i перевiрку умови закiнчення циклу. Якщо ця умова ще не задовільняється, то цикл продовжує виконуватися спочатку з нарощеним значенням параметра;

  • break; Виконується подібно до вже розгляненого в розділі 4.2, він служить для передчасного виходу з циклу (за межі блоку).

Слід зауважити, що подібні програмні засоби мають практично всі мови програмування високого рівня. Проте, мова С має свої особливості, які полягають у тому, що вирази в операторах усіх видів можуть мати будь-який вигляд. В заголовку оператора for деякі вирази можуть бути відсутніми, допустима й така конструкція: for(x=0;y<5;z++), тобто вигляд виразів строго не регламентується.

Цикл типу арифметичної прогресії. Цикл з рiвномiрним приростом аргумента є найбiльш вживаним. Число його виконань n можна обчислити за формулою:

де xп, xк, ∆x – вiдповiдно початкове i кiнцеве значення та прирiст (крок нарощення) параметра циклу.

В класичному варіанті, тобто такому, як цей цикл був започаткований, його параметр змінюється за законом арифметичної прогресії, що й надало йому відповідну назву. Проте, мова С надає низку можливостей для його модифікації.

В графічному алгоритмі символ заголовка цього циклу має вигляд двох паралельних ліній, замкнених з боків прямими кутами. Всередині символа пишуть ім’я параметра циклу, знак =, після якого записують початкове і кінцеве значення параметра циклу та його приріст.

Наведемо приклад. Нехай необхідно візуально переконатися в тому, що функція

y=f(z)=3z2-2z+k

при k=3,2 на відрізку [0,2] не змінює свого знака, тобто приймає лише додатні або лише від’ємні значення. Ця функція являє собою параболу, отже змінюється монотонно, тому достатньо дослідити її в декількох точках зміни аргумента. Обчислимо всі її значення на заданому відрізку з кроком 0,2 та представимо результати на екрані у вигляді таблиці.

Графічний алгоритм для даної задачі показаний на рисунку 4.7. Він складається з 6 блоків, з яких перший і останній служать для визначення відповідно початку і кінця його виконання. У другому блоці змінній k присвоюється значення 3,2. Зрозуміло, що перш, ніж виконувати обчислення виразу, необхідно задати значення всіх його змінних. Третій блок містить заголовок циклу, тут сказано, що параметром циклу є змінна z, яка змінюється від 0 до 2 з кроком 0,2. Четвертий і п’ятий блоки складають тіло циклу, де відбувається відповідно обчислення значень функції y=f(z) та вивід результату.

Нижче подана програма Цикл мовою С яка виконує цей алгоритм. На її початку оголошені змінні z, y, k, всі вони мають тип float, який достатній для нашої задачі. Змінна k ініціалізована під час оголошення константою 3.2. Далі йде заголовок циклу, звернемо увагу на те, що наприкінці його немає крапки з комою, тут цикл лише починається. Тіло циклу взято в фігурні дужки, бо воно являє собою блок, який має два оператори: обчислення значення y і вивід його та аргумента z.

#include<stdio.h> /* Цикл */

int main(void)

{

float z, y, k=3.2;

for(z=0; z<=2; z+=0.2)

{

y=3*z*z-2*z+k;

printf("y=%5.2f z=%4.1f\n", y, z);

}

getch();

return(0);

}

Результати виконання програми Цикл отримаємо в такому вигляді:

y= 3.20 z = 0.0

y= 2.92 z = 0.2

y= 2.88 z = 0.4

y= 3.08 z = 0.6

y= 3.52 z = 0.8

y= 4.20 z = 1.0

y= 5.12 z = 1.2

y= 6.28 z = 1.4

y= 7.68 z = 1.6

y= 9.32 z = 1.8

Аналізуючи їх, бачимо, що вони невірні і нас не задовільняють, програма не видала значення y при z=2, хоча в заголовку циклу задана чітка і однозначна умова виконання циклу: z<=2.

Причина цього недоліка полягає в неточному представленні дійсних чисел у пам’яті, через що накопичується похибка обчислень. З метою його усунення в даному випадку можна врахувати похибку та змінити умову, збільшити число 2 на якусь величину, не більшу за один крок, наприклад, на пів-кроку. Тоді умова виконання циклу прийме вигляд: z<=2.1, а до вже показаних результатів додасться ще й такий:

y= 11.20 z = 2.0

Цей підхід не вважається хорошим тоном програмування. Крім того, може відбутися зайве виконання циклу, а існують задачі, в яких кількість виконання циклу строго регламентується (наприклад, при накопиченні суми з відносно малим кроком), що взагалі недопустимо.

Тоді напрошується висновок про те, що в якості параметра циклу варто застосовувати тільки змінну цілого типу. Навіть маємо мови програмування, наприклад, Паскаль, де реалізовано саме такий цикл. Треба, однак, сказати, що існує певний клас задач, де застосування змінної дійсного типу в якості параметра циклу є оправданим, тому оператор циклу типу for мови С розширює її можливості.

Нижче подано приклад варіанту Цикл_1 даної програми з параметром циклу цілого типу. В її текст внесено дві зміни, а саме: додано змінну n типу int та змінено заголовок циклу. В цьому заголовку початкове значення та нарощення приймають дві змінні – це n i z, але на досягнення кінця циклу перевіряється лише змінна n. Саме вона є параметром, який змінюється від 0 до 10 з кроком 1, тобто приймає 11 значень, оскільки відповідно до наведеної на початку цього розділу формули кількість виконань нашого циклу дорівнює (2-0)/0,2+1=11. Змінена програма видасть усі потрібні результати.

У заголовку циклу програми Цикл_1 двічі використано операцію , (кома) – послідовне виконання: при набутті початкових значень та при нарощенні змінних n i z. Відомо, що параметр циклу оператора for (в даному випадку це змінна n) компілятор розміщує в регістровій пам’яті (клас register), яка відзначається високою швидкодією. Це вигідно, бо економиться час виконання програми, адже параметр циклу багаторазово змінюється. За рахунок операції послідовного виконання на цей привілей має шанс і змінна z, тобто не відбулося ніякого програшу від того, що вона перестала бути параметром циклу.

#include<stdio.h> /* Цикл_1 */

int main(void)

{

float z, y, k=3.2;

int n;

for(n=0, z=0; n<=10; n++, z+=0.2)

{

y=3*z*z-2*z+k;

printf("y=%5.2f z=%4.1f\n", y, z);

}

getch();

return(0);

}

Цикл типу арифметичної прогресії можна побудувати й за допомогою оператора типу while. Нижче подано приклад програми Цикл_while його застосування для розв’язування поставленої вище задачі.

#include<stdio.h> /* Цикл_while */

int main(void)

{

float z=0, y, k=3.2;

int n=0;

while(n<=10)

{

y=3*z*z-2*z+k;

printf("num=%2d y=%5.2f z=%4.1f\n", n, y, z);

z+=0.2;

n++;

}

getch();

return(0);

}

Результати виконання програми Цикл_while будуть такими:

n= 0 y= 3.20 z = 0.0

n= 1 y= 2.92 z = 0.2

n= 2 y= 2.88 z = 0.4

n= 3 y= 3.08 z = 0.6

n= 4 y= 3.52 z = 0.8

n= 5 y= 4.20 z = 1.0

n= 6 y= 5.12 z = 1.2

n= 7 y= 6.28 z = 1.4

n= 8 y= 7.68 z = 1.6

n= 9 y= 9.32 z = 1.8

n=10 y=11.20 z = 2.0

За змістом вони не відрізняються від виданих попередньою програмою, але з метою підвищення їх “читабельності” тут додано ще порядковий номер виведеного рядка.

У програмі Цикл_while параметром циклу є змінна n, яка змінюється від 0 до 10, тому приймає 11 значень. В тілі циклу, обрамленому фігурними дужками, відбувається обчислення зачення y, вивід результатів і нарощення двох змінних: z – на величину 0,2 та n – на 1.

Останній варіант програми можна вдосконалити – скоротити і текст, і час її виконання. Нижче подано фрагмент програми Цикл_while, де ці скорочення відображені.

n=11;z=0;

while(n--)

{

y=3*z*z-2*z+k;

printf("num=%2d y=%5.2f z=%4.1f\n", 10-n, y, z);

z+=0.2;

}

У цьому фрагменті змінна n змінюється не від 0 до 10, як у попередньому прикладі, а навпаки – від 10 до 0. В даному випадку не має значення в якій послідовності змінюється параметр циклу, головне лише, щоб кількість його виконань не змінилася. За рахунок цього вдалося вилучити з тіла циклу його нарощення. Правда, для того, щоб результати виглядали так само, на екран виводиться не значення n, а – 10-n, тобто замість просто імені змінної маємо цілий вираз, тому може статися, що цей виграш буде знівельований.

Важливо знати, що після закінчення виконання циклу, тобто після виходу з циклу, значення його параметра залишається нарощеним на один крок. Наприклад, у розгляненому вище фрагменті, де параметром циклу є змінна n, після його завершення n=0. Після досягнення значення n=1 відбудеться його перевірка на нуль, а оскільки воно не дорівнює нулю, цикл буде підготовлений до виконаня. Потім за рахунок операції декременту (n--) n стане дорівнювати нулю, але це вже не вплине на хід роботи оператора циклу, він виконається ще один останній раз при n=0. Воно ж і залишиться в пам’яті, бо в результаті наступної перевірки на нуль відбудеться завершення циклу без чергового виконання операції декременту.

Запитання для самоперевiрки

  1. Пояснiть термiни: цикл, параметр циклу, тiло циклу, кiлькiсть виконань циклу.

  2. Що видаcть фрагмент: m=0; for(k=1;m++;)printf("%d\n",k);?

  3. Що видаcть фрагмент: m=0; for(k=1;m<0;)printf("%d\n",k);?

  4. Що видасть фрагмент: n=1; d=0; for(k=1;n<2;d++)printf("%d",k);?

  5. Що видасть фрагмент: n=0; for(k=1,s=0;n<2;)printf("%d",k);?

  6. Як змiняться результати, якщо в програмi прикладу опустити фiгурнi дужки, в якi взято тiло циклу?

  7. Якi значення будуть мати змiннi z та i пiсля останнього їх виводу?, пiсля виходу з циклу за межі блока?

  8. Складiть графiчний алгоритм i програму для поставленої в прикладi задачi, використавши оператори if та goto.

  9. Як можна припинити виконання частини операторів блока циклу до досягнення його параметром кінцевого значення і почати цикл заново після нарощення параметра?

  10. Як можна припинити виконання частини операторів і вийти за межі блока циклу до досягнення його параметром кінцевого значення?

  11. Обгрунтуйте перевагу оператора циклу типу for перед while для циклу типу арифметичної прогресії.

  12. Чи можна параметр циклу типу for змінювати за іншим законом, відмінним від арифметичної прогресії?

  13. Чи можна використовувати параметр циклу у виразах блока циклу?

  14. Чи можна змінювати значення параметра в тілі циклу помимо його нарощення в заголовку типу for? в тілі циклу типу while?

  15. Складіть програму для розв’зання задачі прикладу цього розділу за допомогою оператора типу do ... while.

  16. Що станеться, якщо в тіло циклу прикладу додати оператор z-=0.2?

  17. Перелічіть можливі способи попередження зациклення при використанні оператора while(1){тіло циклу;}. Застосуйте цей оператор у якості блока для уникнення застосування оператора goto в задачі попереднього розділу.

Ітераційний цикл. Циклiчний iтерацiйний алгоритм характерний тим, що нi кiлькiсть його виконань, нi кінцеве значення параметра циклу заздалегiдь невiдомi. Його суть полягає в тому, що цикл виконується за формулою qi+1 = φ(qi) (i=0, 1, 2, ...), починаючи з деякого наперед заданого числа q0, поки |qi+1-qi| > ε, де ε – задана похибка, а формула φ(q) відома з постановки задачі Як правило, останнє значення змінної qi є результатом виконання циклу.

Цей цикл порівняно простий, що зумовило його поширення в програмуванні. Однією із областей його застосування є розв’язуваня алгебраїчного нелінійного рівняння методом простої ітерації, звідки й походить його назва. Оскільки помимо власне ітераційного процесу в цьому циклі відбувається перерахунок (те, що було qi+1 стає qi), його ще можна назвати перерахунковим. Треба сказати, що існує певний клас задач, зокрема вищезгаданий метод простої ітерації, де використання циклу цього виду є найбільш вдалим.

Попри привабливість, ітерацiйний циклiчний процес має свої недоліки. Продемонструємо хід ітераційного процесу графічно, він показаний на рисунку 4.8. Аналізуючи його, бачимо, що процес обчислень може бути збіжним (варіанти а, б), розбiжним (варіанти в, г) або зацикленим (варіант д). Звичайно, що користувача програми цікавить лише збіжний процес. Тоді існує певна межа q*, до якої він наближається, тобто можна сказати, що

Вважають, що, якщо вiдстань |qi+1 - qi| стане меншою за деяку, наперед задану малу величину (похибку ε), то вона також менша за похибку обчислення, тобто |qi+1 - q*| < ε, тодi iтерацiйний процес припиняється.

Зауважимо, що існують також задачі, для яких розбіжність або зацикленість вважається нормою. Тоді, як правило, кількість виконань циклу обмежують певним числом, достатнім для коректного завершення програми. Їх можна вважати спеціалізованими, тому тут вони не розглядаються.

Для розв’язування нелінійного алгебраїчного рівняння виду f(x) = 0 застосовують відому формулу Ньютона, яка має такий вигляд:

Вона забезпечує збіжність ітерації на певному відрізку [a, b], де задовільняються вимоги: sign(f′(x)) = const та sign(f(a)) ≠ sign(f(b)), тобто зберігається незмінність знака першої похідної функції f(x) (вона монотонно або зростає, або спадає), а також на кінцях відрізка ця функція має різні знаки. Тоді x0 вибирають з діапазону [a,b]. Цю формулу можна поширити й на інші подібні процеси.

Покажемо цикл цього типу на прикладі розв’язування рівняння

f(z)=z3-z2+kz-c=0,

де z – корінь рівняння, k=3,2, c=1,4.

Це відоме в нафтогазовій справі рівняння Редліха-Квонга [8], яке застосовують для обчислення коефіцієнта надстислості газу z при різних значеннях температури і тиску. З його фізичного змісту відомо, що значення z може знаходитися в межах [a, b], де a=0, b=2.

Спробуємо його розв’язати методом простої ітерації. Для цього перетворимо рівняння до вигляду вигідного для ітерації якимось способом, наприклад, додамо до лівої і правої його частин z. Зауважимо, що від цього його корінь не зміниться. Тоді воно прийме такий вигляд: z=z3-z2+(k+1)z-c. За початкове наближення кореня візьмемо значення 1 – середину відрізка [0,2]. Тоді z0=1, а ітераційна формула матиме такий вигляд:

zi+1=zi3-zi2+(k+1)zi-c.

Нам немає необхідності зберігати в пам’яті всі значення z, лише два сусідні наближення: zi+1 і zi, які потрібні для перевірки умови завершення циклу так, як про це сказано на початку цього розділу. Позначимо їх відповідно p= zi+1, а z=zi, похибку ε візьмемо рівною 10-5. Тоді ітераційну формулу одержимо в такому вигляді: p=z3-z2+(k+1)z-c при початковому наближенні z=1, а умову виконання (продовження) циклу – в такому: |z-p|>ε при ε=10-5.

Графічний алгоритм для цієї задачі показаний на рисунку 4.9.

В його 2-му блоці початкове значення присвоєно змінній p=1, а не z, як сказано вище, це зроблено для того, щоб цикл почав працювати. В блоці 3 це та наступні значення p будуть переприсвоєні змінній z, а далі – використовуватися для обчислення за ітераційною формулою.

Нижче показано текст програми do_whil, яка реалізовує цей алгоритм за допомогою оператора циклу типу do ... while. ЇЇ ідентифікатор eps означає ε, а решта мають таку ж назву, як у графічному алгоритмі. Операцію взяття за модулем у виразі оператора while реалізовано за допомогою функції fabs(), яка призначена для обробки чисел типу float. Інший її варіант abs() використовується для чисел типу int. Наприкінці програми передбачено вивід результату p. Зауважимо, що замість нього можна було б вивести z, обидві ці змінні відрізняються між собою не більше, ніж на похибку eps, але значення p – останнє наближення, тому воно точніше.

#include<stdio.h> /* do_whil */

#include<math.h>

int main(void)

{

float z, p=1, k=3.2, c=1.4, eps=1e-5;

do

{

z=p;

p=z*z*z-z*z+(k+1)*z-c;

}

while(fabs(z-p)>eps);

printf("Korin= %f \n", p);

getch();

return(0);

}

Запустивши програму do_whil на виконання ми, однак, не одержимо потрібного результату, натомість вона завершиться некоректно і видасть таке повідомлення:

Floating point error: Overflow.

Воно означає, що відбулося переповнення пам’яті, тому робимо висновок про те, що ітераційний процес був розбіжним. Цього можна було й сподіватися, досвід програмування показує, що в приблизно 90 випадках зі 100 матимемо саме розбіжний ітераційний процес.

Спробуємо знайти причини неполадків і усунути їх. Спочатку перевіримо чи задовільняються вищеперечислені умови збіжності. Нагадаємо, що першою з них була незмінність знака першої похідної функції f(z) на відрізку [0, 2]. Візьмемо похідну, вона матиме вигляд:

f′(z)=3z2-2z+k.

Результати її обчислення вже були видані програмою Цикл_while попереднього розділу. Аналізуючи їх, бачимо, що функція монотонно зростає і всі значення додатні, отже, її знак не змінюється,.

Друга умова вимагає різних знаків функції f(z)=z3-z2+kz-c на кінцях відрізка [0,2]. Виконавши необхідні обчислення, знайдемо, що f(0)=-1,4 – від’ємне число, а f(2)=8-4+3,2*2-1,4=9 – додатнє, тобто, що ця умова теж задовільняється. Тоді залишається такий вихід: застосувати вищенаведену формулу Ньютона, що й зроблено в програмі Njuton.

#include<stdio.h> /* Njuton */

#include<math.h>

int main(void)

{

float z, p=1, k=3.2, c=1.4, eps=1e-5;

do

{

z=p;

p=z-(z*z*z-z*z+k*z-c)/(3*z*z-2*z+k);

}

while(fabs(z-p)>eps);

printf("Корінь рівняння= %f \n", p);

getch();

return(0);

}

Вона видасть такий результат:

Корінь рівняння= 0.474471

Підставивши це значення кореня в наше рівняння, переконуємося в тому, що воно вірне, f(0,474471)=-0,0000001, тобто одержане значення дорівнює нулю з незначною похибкою.

Ітераційний цикл можна побудувати й за допомогою оператора типу for, нижче показано цей варіант програми (for_njuton).

#include<stdio.h> /* for_njuton */

#include<math.h>

int main(void)

{

float z, p, k=3.2, c=1.4, eps=1e-5;

for(z=1, p=10; fabs(z-p)>eps; p=z-(z*z*z-z*z+k*z-c)/(3*z*z-2*z+k))z=p;

printf("Корінь рівняння= %f \n", p);

getch();

return(0);

}

Вона видасть той же вірний результат. У списку її оператора for змінній p присвоєно число 10 – формальне значення, яке перевищує змінну z на число, більше за похибку. Це зроблено для забезпечення виконання циклу за першим разом (для початку ітераційного процесу).

Запитання для самоперевiрки

  1. Який цикл названо тут ітераційним і чому? Поясніть його суть.

  2. Поясніть поняття: збіжний, розбіжний, зациклений процеси.

  3. Поясніть умову забезпечення збіжності ітераційного процесу.

  4. Чи можна розбіжний ітераційний процес зробити збіжним?

  5. Назвiть оператори циклу з передумовою та постумово. Чим вони вiдрiзняються мiж собою?

  6. Чи можна використати оператор типу for для складання програми ітераційного типу?

  7. Вдоскональте перший варіант програми do_whil прикладу (розбіжного процесу), забезпечте перегляд на екрані перших 20 результатів ітерації і коректне її завершення.

  8. У програмі другого Njuton і третього for_njuton (з циклом типу for) варіанту прикладу забезпечте перегляд на екрані загальної кількості ітерацій, затрачених на обчислення кореня.

  9. Складiть програму для задачi прикладу з використанням оператора типу while.

  10. Чому в програмах, як результат, передбачено вивід значення змiнної p? Чи не краще виводити z? Яка різниця між цими змінними?

  11. Як буде виконуватись оператор do...while, якщо в його дужках замiсть виразу fabs(z-p)>e буде стояти число 1?, число 0?, інше число?, буква?

  12. Алгоритми якого типу реалізовані вищенаведеними програмами? Чи має цикл з оператором for тип арифметичної прогресії?

  13. Які типи алгоритмів, крім циклічних, маємо в програмах прикладу?

  14. Складiть графiчний алгоритм i програму для уточнення квадратного кореня x з числа a за формулою: x=(x+a/x)/2.

  15. Знайдiть iтерацiйну формулу добування кореня будь-якого степеня з довiльного числа a.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]