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

4.2 Розгалуження

Розгалуженою називається програма, яка має переходи. Порядок виконання її операторiв визначається поставленими умовами.

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

В графічному алгоритмі розгалуження позначається за допомогою символа, який має вигляд установленого горизонтально ромба. Всередині ромба записується вираз, а два виходи супроводжуються написами, як правило, це “так” і “ні” або цілі числа: 0 – неправда та 1 – правда.

Для органiзацiї розгалужень мова С має такi засоби:

  • умовний оператор: if(вираз)оператор; Вiн працює так: якщо значення виразу не дорiвнює нулю, то виконується оператор, який знаходиться в складі оператора if, iнакше – наступний оператор програми. Тут і далі під поняттям оператор будемо розуміти один оператор або блок – декілька операторів, обрамлених фігурними дужками;

  • альтернативний оператор: if(вираз)оператор1; else оператор2; Його виконання буде таким: якщо значення виразу не дорiвнює нулю, то далi виконується оператор1, iнакше – оператор2;

  • оператор безумовного переходу: goto мiтка; При його виконаннi вiдбувається безумовний перехiд на оператор з вiдповiдною мiткою. Мiтка являє собою iдентифiкатор, він ставиться перед оператором, на який відбувається перехід, і відділяється від нього двокрапкою. Зауважимо, що широке застосування цього оператора не вважається хорошим тоном програмування. Існують мови програмування, наприклад, FoxPro, які взагалі не мають подібного оператора;

  • оператор припинення: break; При його виконанні відбувається вихід за межi блока;

  • оператор-перемикач: switch(вираз)

{оголошення локальних змінних;

case константа: оператори1;

case константа: оператори2;

... ...

default: операторих;

}

Тiло перемикача являє собою блок, він може мати власнi оголошення даних. Його виконання починається з обчислення виразу, який може мати тiльки тип int. (Нагадаємо, що лiтернi константи типу char можуть сприйматися як коди лiтер, тому вони теж мають тип int). Виконання операторiв починається вiд того мiсця, де константа спiвпадає зi значенням виразу, до слова default або, якщо його немає, то до кiнця блока, незважаючи на розташовані нижче оператори з iншими константами-мiтками. Операторих виконуються, якщо значення виразу не спiвпадає нi з однiєю константою;

  • описані в розділі 2.6 логічні операції та операції відношення.

Продемонструємо застосування оператора типу if на прикладі. Нехай для заданого діаметра долота d (мм) необхідно знайти діаметр обважнених бурильних труб D, використавши таку залежність [6]:

На рисунку 4.2 показана числова вісь, на якій відображені області існування функції D.

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

Графічний алгоритм для даної задачі подано на рисунку 4.3. Аналізуючи його, можна зразу сказати, що для його програмування слід застосувати умовний альтернативний оператор. Нижче в прикладі 4.2.1 подано варіант програми, яка реалізовує цей алгоритм. У ній позначено: D = d1, d = d.

Приклад 4.2.1 – Розгалужена програма

#include<stdio.h> /* Розгалуження */

int main(void)

{

float d, d1;

printf("Введіть діаметр долота: ");scanf("%f", &d);

if(d>295.3)d1=0.7*d;else d1=0.8*d;

printf("D=%6.1f\td=%6.1f\n", d1, d);

getch();

return(0);

}

Випробуємо цю програму на всіх діапазонах існування функції D, для цього запустимо її на виконання два рази: при d=320 (тобто при d>295,3) і при d=190,5 (d≤295,3). Отримаємо такі результати:

Введіть діаметр долота: 320

D= 224.0 d= 320.0

Введіть діаметр долота: 190.5

D=152.4 d=190.5

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

Програму прикладу 4.2.1 можна спростити, застосувавши тренарну операцію, замість оператора if запишемо вираз: d1=d>295.3?0.7*d:0.8*d; Результати виконання програми будуть такими ж. Можна також внести цю тренарну операцію в параметри функції printf(). Тодi й змінна d1 буде зайвою, а програма прийме такий вигляд (приклад 4.2.2):

Приклад 4.2.2 – Застосування тренарної операції

#include<stdio.h> /* Розгалуження, варіант */

int main(void)

{

float d;

printf("Введіть діаметр долота: ");scanf("%f", &d);

printf("D=%6.1f\td=%6.1f\n", d>295.3?0.7*d:0.8*d, d);

getch();

return(0);

}

Наведемо ще один приклад. Нехай потрібно обчислити коефіцієнт λ гідравлічного опору нафтопровідного колектора нафтопроводу в залежності від числа Рейнольдса за однією з таких формул [11]:

при Re≤2320 (формула Стокса);

при 2320 < Re ≤ 10 (формула Блазіуса);

при 10 < Re ≤ 500 (формула Альтшуля);

при Re > 500 (формула Шифринсона),

де Re – число Рейнольдса; Δ – шорсткість труб, Δ = 0,15 мм; d – діаметр колектора, d = 400 мм.

Число Рейнольдса залежить від технічних характеристик колектора та швидкості протікання рідини, воно може бути лише додатнім. Покажемо області існування функції λ на числовій осі Re (рисунок 4.4). З метою спрощення рисунка скоротимо вигляд формул, за якими обчислюється λ, зашифруємо їх першими буквами авторів відповідно С, Б, А і Ш.

Аналізуючи рисунок 4.4 з метою побудови графічного алгоритму, бачимо, що на числовій осі Re знаходиться 4 вузли, в яких мають відбуватися розгалуження. Відповідно до них будемо мати 5 гілок. Для скорочення запису умов зашифруємо їх так же, як і на числовій осі, але додамо слово “Умова”. Зрозуміло, що для забезпечення “читабельності” алгоритму слід обходити вузли осі Re та ставити умови в якомусь порядку. Побудуємо графічний алгоритм для цієї задачі, він показаний на рисунку 4.5, де вузли розглядаються зліва направо.

Для зображення розгалужень в алгоритмі використані спецiальні знаки (блоки 4, 6, 8,10), які мають вигляд поверненого горизонтально ромба. Всерединi кожного знака записана в зашифрованому вигляді умова, в залежностi вiд якої вибирається потрібна гiлка подальшого виконання алгоритму. Гілки підписані словами “Так” або “Ні”, які означають відповідно задовільнення або незадовільнення умов.

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

Побудова розгалуженої програми – досить складне завдання. Аналізуючи рисунок 4.5, бачимо, що він має місцями по два символи (12-й і 13-й) у ширину, ніяк між собою безпосередньо не зв’язані, а програма повинна складатися з операторів, записаних у один стовпчик у певній послідовності.

Тут можна запропонувати такий прийом: пристосувати графічний алгоритм до вимог програми. Для цього вишикуємо всі символи алгоритму в один ряд, не змінюючи зв’язків між ними, тобто всі лінії повинні потягнутися за символами. В нашому прикладі всі блоки можна залишити на місці, лише блок за номером 13 помістимо (уявно) між 12-тим і 14-тим. Тоді оператори програми можна буде написати в тій же послідовності, що й відповідні їм символи графічного алгоритму, це спростить процес її побудови.

Вищенаведені формули для обчислення величини λ містять операцію добування кореня 4-го степеня. Їх реалізацію в програмі можна виконати за допомогою раніше вже розгляненої в розділі 4.1 функції pow() з показником степеня 1/4 або дворазового кореня квадратного. Використаємо в програмі функцію sqrt() – корінь квадратний, тому за допомогою директиви препроцесору #include підключимо файл math.h, де вона описана.

Виконаємо ідентифікацію змінних, діаметр колектора позначимо буквою латини d, а число Рейнольдса – re. Оскільки мова С не має букв грецького алфавіту, назвемо змінні λ і Δ латиною відповідно lambda i delta. Іменованим константам d i delta надамо відповідні значення за допомогою директив #define. Значення константи d запишемо як дійсне число, тобто 400.0, це попередить спробу компілятора приводити вирази дійсного типу, в яких вона використовується, до цілого типу. Змінним lambda і re надамо тип float, у даній задачі він задовільний.

Ввід змiнної re забезпечимо за допомогою функцiї scanf(), а вивід результату – printf(), тому за допомогою директиви препроцесору #include підключимо файл stdio.h.

Ці розмірковування реалізовані в програмі прикладу 4.2.3.

Приклад 4.2.3 – Програма з декількома розгалуженнями

#include<stdio.h> /* Варіант goto */

#include<math.h>

#define d 400.0

#define delta 0.15

int main(void)

{

float lambda, re;

printf("Введіть число Рейнольдса: ");scanf("%f", &re);

if(re>0 && re<=2320){lambda=64/re; goto a;}

if(re>2320 && re<=10*d/delta){lambda=0.3164/sqrt(sqrt(re)); goto a;}

if(re>10*d/delta && re<=500*d/delta){lambda=0.11*sqrt(sqrt(delta/d+68/re)); goto a;}

if(re>500*d/delta){lambda=0.11*sqrt(sqrt(delta/d)); goto a;}

printf("Функція не існує, Re= %7.0f\n",re); goto b;

a:printf(" Re=%7.0f Lambda= %5.3f\n", re, lambda);

b:getch();

return(0);

}

Цю програму слід перевірити на правильність не лише обчислення виразів, але й логіки переходів. Випробуємо її на всіх діапазонах, які показані на рисунку 4.4, результати будуть такими:

Введіть число Рейнольдса: 0

Функція не існує, Re= 0

Введіть число Рейнольдса: 1001

Re= 1001 Lambda=0.064

Введіть число Рейнольдса: 2500

Re= 2500 Lambda=0.045

Введіть число Рейнольдса: 1e5

Re= 100000 Lambda=0.020

Введіть число Рейнольдса: 1.5e6

Re=1500000 Lambda=0.015

Виконавши паралельні обчислення вручну, переконаємося в тому, що вони вірні.

Попри працездатність програма прикладу 4.2.3 має суттєві недоліки. По-перше, дублюються логічні вирази, наприклад, у виразі першого оператора if (при обчислені lambda = С) ставиться умова re<=2320, а в другому – re>2320, що по суті одне й те ж. Подібні повторення виразу мають також решта операторів if. Це сталося внаслідок того, що вирази операторів if не по порядку розглядають області існування функції, показані на рисунку 4.4. Першою областю, яка повинна бути розглянена і відібрана (виключена з розгляду в наступних операторах if) повинна бути крайня: ліва або права, в нашому випадку – ліва, де re<=0.

По-друге, вона має багато операторів goto. З них перші чотири забезпечують перехід на мітку a:, встановлену перед функцією виводу re i lambda. П’ятий – здійснює перехід на мітку b:, тобто на кінець програми. (З метою підвищення “читабельності” програми в графічний алгоритм варто вносити всі мітки, що й показано на рисунку 4.5.)

Перенасиченість програми мітками та операторами goto, як було вже сказано вище, не є хорошим тоном програмування. Одним із способів зменшення їх числа є застосування альтернативного умовного оператора і безперечно, що він буде використаний, але спочатку розглянемо сам графічний алгоритм. Аналізуючи його, бачимо, що він має два символи, де збігаються гілки розгалужень: 13-й (мітка a:) і 14-й (мітка b:). Це є ознакою того, що алгоритм складається з двох розгалужень, вкладених одне в друге, тобто зовнішнє або першого ступеня вкладеності і внутрішнє – другого. Зовнішнє розгалуження має лише дві гілки, з яких ліва служить для виводу повідомлення про те, що функція не існує, а права містить всі решта розгалужень (другого ступеня вкладеності) і вивід значень λ та Re. Нижче у прикладі 4.2.4 подано змінений варіант програми, де ці зауваження враховані.

Приклад 4.2.4 – Вкладені розгалуження

#include<stdio.h> /* Варіант else */

#include<math.h>

#define d 400.0

#define delta 0.15

int main(void)

{

float lambda, re;

printf("Введіть число Рейнольдса: ");scanf("%f", &re);

if(re<=0)printf("Функція не існує, Re= %7.0f\n", re);

else{if(re<=2320) lambda=64/re;

else if(re<=10*d/delta) lambda=0.3164/sqrt(sqrt(re));

else if(re<=500*d/delta)lambda=0.11*sqrt(sqrt(delta/d+68/re));

else lambda=0.11*sqrt(sqrt(delta/d));

printf("Re= %7.0f Lambda= %5.3f\n", re, lambda);

}

getch();

return(0);

}

Цей приклад показує чому наявність у програмі операторів goto небажана. Зрозуміло, що графічний алгоритм обчислювального процесу мусить правильно відображати його хід. Його не можна ніяк змінити, хіба що інакше розкласти символи (не змінюючи суть алгоритму). Проте, якщо на стадії програмування застосовується спрощений підхід, як у прикладі 4.2.3, або, хоча й використовується альтернативний оператор if-else подібно до програми прикладу 4.2.4, але не враховується вкладеність розгалужень, то матимемо небажані безумовні переходи.

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

ввід(re); /* Схема Lambda */

if(умова С)lambda=С;

if(умова Б)lambda=Б;

if(умова А)lambda=А;

if(умова Ш)lambda=Ш;

вивід(re, lambda);

getch();

Ця схема буде занадто недосконалою, бо відбудеться перехід на вивід у всіх випадках, у тому числі тоді, коли функція не існує і значення λ не обчислювалося, не задавалося. Подібна ситуація недопустима. Різні системи по-різному ініціалізовують незадані змінні, найчастіше – нулем, який у нашому випадку був би невірним результатом. Деякі інші поміщають в неініціалізовану змінну невизначеність, що при виводі може спричинити помилку, тобто аварійне завершення програми, або буде виведено щось незрозуміле.

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

Покажемо варiант розгалуженого алгоритму з перемикачем switch на розгляненому вже вище прикладі обчислення коефіцієнта λ. Нагадаємо, що константами-мiтками перемикача можуть бути лише цiлi числа або букви, тому в якості виразу не можна використовувати багато умов, хіба що одну з виходами 0 або 1. Для багатьох умов можна застосувати такий прийом: перетворити їх так, щоб результатом обчислення були різні цілі числа. Скористаємося тим, що при задовільненні умови значення логічного виразу дорівнює одиниці, а при незадовільненні – нулю. Якщо помножимо різні умови на різні цілі числа, то при задовільненні умов отримаємо не одиниці, а ті числа. При незадовільненні умов отримаємо нулі. Для різних областей існування функції λ сума цих чисел буде різною.

На рисунку 4.6 показано графічний розгалужений алгоритм з перемикачем. Оскільки, як уже було вище сказано, маємо два вкладені розгалуження, то в алгоритмі застосовуються два вкладені один у другий відповідні їм перемикачі. Умова-вираз перемикача записується в кружок, від якого відходять гілки, що визначають напрямок подальшого ходу обчислювального процесу відповідно до умови. З метою скорочення виразів вони позначені для зовнішнього розгалуження за умовою Re<=0 буквою N, а для внутрішнього, яке має умову (Re ≤ 2320) + 2(Re ≤ 10 ) + 3(Re ≤ 500 ), – буквою H.

Нижче поданий приклад 4.2.5 програми для цього алгоритму.

Приклад 4.2.5 – Застосування перемикача

#include<stdio.h> /* Варіант switch */

#include<math.h>

#define d 400.0

#define delta 0.15

int main(void)

{

float lambda,re;

int h;

printf("Введіть число Рейнольдса: ");scanf("%f", &re);

switch(re<=0)

{case 1: printf("Функція не існує, Re= %7.0f\n",re); break;

default: h=(re<=2320) + 2*(re<=(10*d/delta)) + 3*(re<=(500*d/delta));

switch(h)

{case 6: lambda=64/re; break;

case 5: lambda=0.3164/sqrt(sqrt(re)); break;

case 3: lambda=0.11*sqrt(sqrt(delta/d+68/re)); break;

default: lambda=0.11*sqrt(sqrt(delta/d));

}

printf("Re=%7.0f Lambda= %5.3f\n", re, lambda);

}

getch();

return(0);

}

Основою програми прикладу 4.2.5 є два вкладені перемикачі. Вираз зовнішнього перемикача re<=0 може приймати два значення: 0 або 1. Відповідно до цього його блок має мітки: константу 1 та default. При одиниці виводиться повідомлення про те, що функція не існує (ліва гілка рисунка 4.6). Якщо значення виразу дорівнює 0 (мітка default:), то відбувається виконання трьох таких операторів: обчислення допоміжної змінної h, виконання внутрішнього оператора switch та вивід результатів обчислень re і lambda (права гілка рисунка 4.6). Обидві гілки зовнішнього перемикача виводять обчислювальний процес за межі його блока в одне місце – на кінець програми.

Перед блоком внутрішнього перемикача обчислюється значення h типу int. Ця змінна служить для скорочення довжини логічного виразу H, їй присвоюється значення цього виразу. Вираз h внутрішнього перемикача побудований так, що в залежності від числа Рейнольдса він прийматиме значення цілого числа 6 (6=1+2+3), якщо будуть задовільнятися всі три логічні вирази, 5 – будуть задовільнятися два останні вирази або 3 – буде задовільнятися лише один останній вираз. Тоді h=6 лише в тому випадку, коли число Re задовільняє умову С. При h=5 задовільняється лише умова Б, а при h=3 – лише умова А. Умову Ш не було необхідності включати в вираз, зрозуміло, що вона буде задовільнятися в усіх інших випадках.

Вираз h можна було побудувати й так, щоб результатами його обчислення були більш зрозумілі числа 0, 1, 2, 3. Тоді він мав би такий вигляд:

h=(re<=2320) + 2*(re>2320 && re<=(10*d/delta)) + 3*(re>(10*d/delta) &&

re<=(500*d/delta));

А як мітки-константи внутрішнього блока перемикача служили б числа 1:, 2:, 3: і default:. Зразу видно, що він більш складний і що в ньому повторюються умовні вирази, тому він невигідний.

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

Результати виконання програми прикладу 4.2.5 такi ж, як i в розглянених вище прикладах 4.2.3 та 4.2.4.

Оператор switch вигідно застосовувати в тому випадку, коли результатами обчислення його виразу (умови) є цілі числа, букви або коди клавіш, зокрема при ручному управлінні ходом виконання програми (рухомі фігури на екрані в іграх, управління курсором та ін.). Тоді в якості виразу оператора switch варто використовувати функцію bioskey(0), яка описана в файлі bios.h. Вона служить для вводу кодів клавіш клавіатури і їх комбінацій з клавішами Ctrl, Alt та Shift. Коди клавіш можуть служити як мітки операторів блока перемикача.

Переглянути коди клавіш можна за допомого функції:

printf("Код=%d\n", bioskey(0));

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

  1. Дайте визначення розгалуженого алгоритму.

  2. Відомо, що десяткове число 5 у двійковій системі числення дорівнює 101. Чому n=0 після виконання операцій: int n; n=5 & 101;?

  3. Чому n=1, але не 5, якщо n=5 && 101;?

  4. Перелiчiть усi можливi види виразу оператора if, наведiть приклади, пояснiть виконання.

  5. Порівняйте програми 4.2г та 4.2д. Яка з них складніша? В яких випадках доцільно застосовувати перемикач?

  6. Чому дорiвнюватиме k пiсля виконання операторів: if(i=j) k=1; else k=2; при а) int k, i=2, j=2; б) int k, i, j=0; в) int k, i=2, j=0;?

  7. Чи можлива ситуацiя, коли розгалужена програма закiнчена, проте знайдуться її оператори, якi нi разу не виконувались?

  8. Задано зміну: int mis; (порядковий № місяця). Що одержимо в результаті обчислення виразу: mis<4?1:mis<7?2:mis<10?3:4?

  9. Для чого служить оператор break у програмі з перемикачем? Чи можна його застосувати в блоці альтернативного оператора типу if-else?

  10. Чому в програмі прикладу 4.2.5 маємо два перемикачі? Чи не можна було обійтися одним і, якщо можна, то як?

  11. Чи можна назвати застосування альтернативного оператора типу if-else універсальним способом уникнення оператора goto?

  12. Чому в результаті виконання операторів: float x=-3.2; printf("%d", x+3.2==0); одержимо нуль, а не одиницю, як можна було б очікувати?

  13. Що одержимо в результаті виконання операторів: float x=1; printf("%d", x==0);? printf("%d", x);? printf("%d", x=0);?

  14. Як визначити код клавіші F1? Як можна застосувати цей код у виразі перемикача?

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