Защо ни е това
Операторът for вече го познаваш. Той е подходящ, когато тялото на цикъла трябва да се изпълни за определени стойности на някаква управляваща променлива или определен брой пъти.
Но много често не знаем предварително колко пъти ще се завърти цикълът. Колко години трябва да мине, докато продукцията падне под 10 тона? Колко деления прави алгоритъмът на Евклид? За такива случаи C# предлага две други възможности.
Ключово
Условието за край може да се провери преди тялото (предусловие — while) или след него (постусловие — do-while). Разликата личи само в един случай: когато условието е невярно от самото начало.
Операторът while
while (докато)
while (<условие>) { <тяло на цикъла> } — проверява стойността на логическия израз и ако тя е true, изпълнява тялото. Продължава да прави това, докато условието е изпълнено. Когато за пръв път стойността стане false, операторът прекратява работа и предава управлението на следващия оператор.
Тъй като в while условието се проверява преди да се изпълни тялото на цикъла, за оператора казваме, че цикълът е с предусловие.
Бележка
Възможностите на while не са различни от тези на for. Просто синтаксисът на for е усложнен от употребата на трите управляващи израза. Когато се проектира цикличен процес, се използва диаграмата от фиг. 1, а при реализацията се преценява дали да се използва while или for.
Операторът do-while
do-while
do { <тяло на цикъла> } while (<условие>); — условието за край на цикъла се проверява след всяко изпълнение на тялото му. Затова казваме, че този цикъл е с постусловие.
Внимание
Тялото на do-while се изпълнява поне веднъж, дори когато условието е невярно от самото начало. Това е цялата разлика с while — и точно тя е причината за най-неприятните грешки.
Всеки цикъл може да се напише с друг
Не е невъзможно да се моделира работата на кой да е оператор за цикъл с някой от другите, макар че понякога това ще бъде доста изкуствено и често ненужно. Ето еквивалентността на for и while:
| for | while | |
|---|---|---|
| Записва се | for (<инициализация>; <условие>; <обновяване>) { <тяло> } | <инициализация>; while (<условие>) { <тяло> <обновяване> } |
| Проверката е | Преди тялото | Преди тялото |
| Подходящ, когато | Броят повторения се знае предварително | Броят повторения зависи от данните |
Обратното — да получим постусловие от предусловие — става с break, който вече познаваш. Организираме безкраен цикъл с предусловие константата true, който да прекратим, когато постусловието вече не е истина. За целта на постусловието правим неговото отрицание:
while (true)
{
<тяло на цикъла>
if (!<условие>) break;
}Друг полезен оператор при организацията на цикли е continue. Когато се стигне до него, изпълнението на тялото се преустановява и се преминава към следваща стъпка на цикъла. Същото моделиране може да стане и така:
while (true)
{
<тяло на цикъла>
if (<условие>) continue;
else break;
}Задача 1 — фирмата
Условие. Фирма произвела през 2010 г. 130 тона продукция. Всяка от следващите години производството спадало с 10%. Определи през коя година то за първи път ще е под 10 тона.
Решение. Използваме две променливи — дробна x за продукцията и цяла g за поредната година. На всяка стъпка намаляваме x с 10% и увеличаваме g с 1. Изпълняваме цикъла, докато съдържанието на x стане по-малко от 10.
x = 130.0
g = 2010
докато x >= 10
повтори
x = 0.9 * x
g = g + 1
край
изведи gСъвет
Забележи, че броят стъпки не се знае предварително — той зависи от числата. Точно затова тук for е неудобен, а while пасва идеално.
Задача 2 — най-голям общ делител
Условие. По зададени две положителни цели числа a и b, a > b, намери техния най-голям общ делител.
Решение. Използваме бързата версия на алгоритъма на Евклид. Тя се основава на следното математическо свойство: нека a = k·b + r, където r е остатъкът при деление на a с b. Тогава:
НОД(a, b) = НОД(b, r)
Например: НОД(30, 12) = НОД(12, 6) = НОД(6, 0) = 6Свойството поражда цикличен процес: започва със зададените a и b, на всяка стъпка намира остатъка r при деление на a с b и замества a с b, а b с r. Процесът завършва, когато намереният остатък е 0 — тогава стойността на НОД е текущата стойност на a.
while (b != 0)
{
r = a % b;
a = b;
b = r;
}
// Отговорът е в a.Задача
Реализирай същия алгоритъм и с цикъл с постусловие. Двете решения трябва да дават един и същ резултат — но внимавай какво става, ако b е 0 още при въвеждането.
Задача 3 — квадратен корен по числен метод
Условие. Напиши приложение, което изчислява квадратен корен на зададено цяло число a.
Тук ще използваме изчислителна процедура, доста различаваща се от алгоритмите, които вече познаваш. Такава процедура се нарича числен метод. Пресмятаме последователно елементите на безкрайна редица по правилото:
x₁ = a / 2
x₂ = (x₁ + a/x₁) / 2
x₃ = (x₂ + a/x₂) / 2
...
xᵢ₊₁ = (xᵢ + a/xᵢ) / 2Такъв начин на задаване на елементите на безкрайна редица се нарича рекурентна зависимост. Доказано е, че всеки следващ елемент е все по-близо до квадратния корен на a.
Проблемът при такава процедура е кога да се прекрати изпълнението на цикъла. Тъй като стойностите на редицата непрекъснато намаляват, както и разликата между всеки два последователни елемента, цикълът може да спре, когато разликата между два съседни члена стане по-малка или равна на някакво много малко число. Това число се нарича приближение или точност на численя метод. По традиция се означава с гръцката буква ε (епсилон) — оттам и името на променливата eps.
0) нека eps = 0.001
1) въведи a
2) x = a / 2.0
3) повтаряй
4) xs = x
5) x = (x + a/x) / 2
6) докато xs - x >= eps
7) изведи xРолята на променливата xs е важна: в първата стъпка от тялото на цикъла в нея се съхранява предишната стойност на x, а в следващия оператор в x се изчислява новата стойност.
Проследяване за a = 9
| Стъпка | xs | новото x | xs − x | ≥ eps? |
|---|---|---|---|---|
| начало | — | 4,50 | — | — |
| 1 | 4,50 | 3,25 | 1,25 | да → продължава |
| 2 | 3,25 | 3,00961538… | 0,24… | да → продължава |
| 3 | 3,00961538… | 3,00001536… | 0,0096… | да → продължава |
| 4 | 3,00001536… | 3,00000000003932 | 0,00001536… | не → край |
Извежда се 3,00000000003932. Забележи колко бързо стойността се доближава до 3.
Внимание
Резултатът от численен метод е приблизителен, с точност eps. Дай по-малка стойност — например 0.0001 или 0.00001 — и виж как се променят резултатът и броят на стъпките.
Въпроси
Въпроси и задачи
1. Дай примери за изречения на български език, в които се използват предусловие и следусловие. Например „Докато вали дъжд, няма да излизам навън“.
2. Напиши логически израз в C#, който съответства на всяко от твърденията: а) a, b и c са положителни числа; б) числото x е в интервала [3;10]; в) числото p е четно или положително; г) числата a, b и c са равни помежду си; д) числата a, b и c са различни; е) поне две от числата a, b и c са положителни; ж) нито едно от числата a, b и c не е отрицателно; з) точно две от числата a, b и c са равни помежду си.
3. Колко пъти ще се изпълни тялото на цикъла от задача 1?
4. Промени програмата за квадратен корен така, че да показва и междинните резултати, както е в текста на урока.
Какво трябва да запомниш
whileе с предусловие — тялото може да не се изпълни нито веднъж.do-whileе с постусловие — тялото се изпълнява поне веднъж, каквото и да е условието.- Всеки цикъл може да се моделира с друг:
while (true)плюсbreakдава постусловие. - Алгоритъмът на Евклид: заменяш (a, b) с (b, a % b), докато b стане 0.
- Численият метод дава приблизителен отговор. Спира се, когато разликата между две последователни приближения падне под зададена точност
eps.