C/Instrukcje sterujące: Różnice pomiędzy wersjami

Z Wikibooks, biblioteki wolnych podręczników.
< C
Usunięta treść Dodana treść
m Update syntaxhighlight tags - remove use of deprecated <source> tags
Linia 7: Linia 7:
=== if ===
=== if ===
Użycie instrukcji if wygląda tak:
Użycie instrukcji if wygląda tak:
<source lang="C">
<syntaxhighlight lang="C">
if (wyrażenie) {
if (wyrażenie) {
/* blok wykonany, jeśli wyrażenie jest prawdziwe */
/* blok wykonany, jeśli wyrażenie jest prawdziwe */
}
}
/* dalsze instrukcje */
/* dalsze instrukcje */
</syntaxhighlight>
</source>
Istnieje także możliwość reakcji na nieprawdziwość wyrażenia - wtedy należy zastosować słowo kluczowe '''else''':
Istnieje także możliwość reakcji na nieprawdziwość wyrażenia - wtedy należy zastosować słowo kluczowe '''else''':
<source lang="C">
<syntaxhighlight lang="C">
if (wyrażenie) {
if (wyrażenie) {
/* blok wykonany, jeśli wyrażenie jest prawdziwe */
/* blok wykonany, jeśli wyrażenie jest prawdziwe */
Linia 21: Linia 21:
}
}
/* dalsze instrukcje */
/* dalsze instrukcje */
</syntaxhighlight>
</source>
Przypatrzmy się bardziej "życiowemu" programowi, który porównuje ze sobą dwie liczby:
Przypatrzmy się bardziej "życiowemu" programowi, który porównuje ze sobą dwie liczby:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 38: Linia 38:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>


Stosowany jest też krótszy zapis warunków logicznych, korzystający z tego, jak C rozumie '''[[C/Operatory#"Prawda" i "fałsz" w języku C|prawdę i fałsz]]''', tzn.:
Stosowany jest też krótszy zapis warunków logicznych, korzystający z tego, jak C rozumie '''[[C/Operatory#"Prawda" i "fałsz" w języku C|prawdę i fałsz]]''', tzn.:
Linia 44: Linia 44:
* liczba całkowita równa zero oznacza fałsz.
* liczba całkowita równa zero oznacza fałsz.


Jeśli zmienna <tt>a</tt> jest typu <tt>integer</tt>, zamiast: <source lang="C"> if (a != 0) b = 1/a;</source> można napisać: <source lang="C"> if (a) b = 1/a;</source>
Jeśli zmienna <tt>a</tt> jest typu <tt>integer</tt>, zamiast: <syntaxhighlight lang="C"> if (a != 0) b = 1/a;</syntaxhighlight> można napisać: <syntaxhighlight lang="C"> if (a) b = 1/a;</syntaxhighlight>


a zamiast <source lang="C"> if (a == 0) b = 0; </source> można napisać: <source lang="C"> if (!a) b = 0;</source>
a zamiast <syntaxhighlight lang="C"> if (a == 0) b = 0; </syntaxhighlight> można napisać: <syntaxhighlight lang="C"> if (!a) b = 0;</syntaxhighlight>






Czasami zamiast pisać instrukcję <tt>if</tt> możemy użyć operatora wyrażenia warunkowego (patrz [[C/Operatory#Operator wyrażenia warunkowego|Operatory]]).
Czasami zamiast pisać instrukcję <tt>if</tt> możemy użyć operatora wyrażenia warunkowego (patrz [[C/Operatory#Operator wyrażenia warunkowego|Operatory]]).
<source lang="C">
<syntaxhighlight lang="C">
if (a != 0)
if (a != 0)
b = 1/a;
b = 1/a;
else
else
b = 0;
b = 0;
</syntaxhighlight>
</source>
ma dokładnie taki sam efekt jak:
ma dokładnie taki sam efekt jak:
<source lang="C">
<syntaxhighlight lang="C">
b = (a !=0) ? 1/a : 0;
b = (a !=0) ? 1/a : 0;
</syntaxhighlight>
</source>


=== switch ===
=== switch ===
Aby ograniczyć wielokrotne stosowanie instrukcji if możemy użyć '''switch'''. Jej użycie wygląda tak:
Aby ograniczyć wielokrotne stosowanie instrukcji if możemy użyć '''switch'''. Jej użycie wygląda tak:
<source lang="C">
<syntaxhighlight lang="C">
switch (wyrażenie) {
switch (wyrażenie) {
case wartość1: /* instrukcje, jeśli wyrażenie == wartość1 */
case wartość1: /* instrukcje, jeśli wyrażenie == wartość1 */
Linia 74: Linia 74:
break;
break;
}
}
</syntaxhighlight>
</source>
Należy pamiętać o użyciu <tt>break</tt> po zakończeniu listy instrukcji następujących po <tt>case</tt>. Jeśli tego nie zrobimy, program przejdzie do wykonywania instrukcji z następnego <tt>case</tt>. Może mieć to fatalne skutki:
Należy pamiętać o użyciu <tt>break</tt> po zakończeniu listy instrukcji następujących po <tt>case</tt>. Jeśli tego nie zrobimy, program przejdzie do wykonywania instrukcji z następnego <tt>case</tt>. Może mieć to fatalne skutki:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 92: Linia 92:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
A czasami może być celowym zabiegiem (tzw. "fall-through") - wówczas warto zaznaczyć to w komentarzu. Oto przykład:
A czasami może być celowym zabiegiem (tzw. "fall-through") - wówczas warto zaznaczyć to w komentarzu. Oto przykład:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 113: Linia 113:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
Przeanalizujmy teraz działający przykład:
Przeanalizujmy teraz działający przykład:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 135: Linia 135:
printf ("Do zapłaty: %d\n", podatek);
printf ("Do zapłaty: %d\n", podatek);
}
}
</syntaxhighlight>
</source>


== Pętle ==
== Pętle ==
Linia 141: Linia 141:
=== while ===
=== while ===
Często zdarza się, że nasz program musi wielokrotnie powtarzać ten sam ciąg instrukcji. Aby nie przepisywać wiele razy tego samego kodu można skorzystać z tzw. '''pętli'''. Pętla wykonuje się dopóty, dopóki prawdziwy jest warunek.
Często zdarza się, że nasz program musi wielokrotnie powtarzać ten sam ciąg instrukcji. Aby nie przepisywać wiele razy tego samego kodu można skorzystać z tzw. '''pętli'''. Pętla wykonuje się dopóty, dopóki prawdziwy jest warunek.
<source lang="C">
<syntaxhighlight lang="C">
while (warunek) {
while (warunek) {
/* instrukcje do wykonania w pętli */
/* instrukcje do wykonania w pętli */
}
}
/* dalsze instrukcje */
/* dalsze instrukcje */
</syntaxhighlight>
</source>
Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie. Załóżmy, że mamy obliczyć kwadraty liczb od 1 do 10. Piszemy zatem program:
Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie. Załóżmy, że mamy obliczyć kwadraty liczb od 1 do 10. Piszemy zatem program:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 160: Linia 160:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
Po analizie kodu mogą nasunąć się dwa pytania:
Po analizie kodu mogą nasunąć się dwa pytania:
* Po co zwiększać wartość a o jeden? Otóż gdybyśmy nie dodali instrukcji zwiększającej a, to warunek zawsze byłby spełniony, a pętla "kręciłaby" się w nieskończoność.
* Po co zwiększać wartość a o jeden? Otóż gdybyśmy nie dodali instrukcji zwiększającej a, to warunek zawsze byłby spełniony, a pętla "kręciłaby" się w nieskończoność.
Linia 170: Linia 170:
Instrukcję for stosuje się w następujący sposób:
Instrukcję for stosuje się w następujący sposób:


<source lang="C">
<syntaxhighlight lang="C">
for (wyrażenie1; wyrażenie2; wyrażenie3) {
for (wyrażenie1; wyrażenie2; wyrażenie3) {
/* instrukcje do wykonania w pętli */
/* instrukcje do wykonania w pętli */
}
}
/* dalsze instrukcje */
/* dalsze instrukcje */
</syntaxhighlight>
</source>
Jak widać, pętla for znacznie różni się od tego typu pętli, znanych w innych językach programowania.
Jak widać, pętla for znacznie różni się od tego typu pętli, znanych w innych językach programowania.


Linia 184: Linia 184:


Jeżeli wewnątrz pętli nie ma żadnych instrukcji '''continue''' (opisanych niżej) to jest ona równoważna z:
Jeżeli wewnątrz pętli nie ma żadnych instrukcji '''continue''' (opisanych niżej) to jest ona równoważna z:
<source lang="C">
<syntaxhighlight lang="C">
{
{
wyrażenie1;
wyrażenie1;
Linia 193: Linia 193:
}
}
/* dalsze instrukcje */
/* dalsze instrukcje */
</syntaxhighlight>
</source>
Ważną rzeczą jest tutaj to, żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla for. Początkującym programistom nieznajomość tego faktu sprawia wiele problemów.
Ważną rzeczą jest tutaj to, żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla for. Początkującym programistom nieznajomość tego faktu sprawia wiele problemów.


Linia 203: Linia 203:




<source lang="C">
<syntaxhighlight lang="C">
for(i=1; i<=10; ++i){
for(i=1; i<=10; ++i){
Linia 220: Linia 220:
printf(" i = %d", i ); // wyrażenie3 i++ zostanie wykonane, nawet jeżeli był to już ostatni obieg pętli
printf(" i = %d", i ); // wyrażenie3 i++ zostanie wykonane, nawet jeżeli był to już ostatni obieg pętli


</syntaxhighlight>
</source>
Dwa pierwsze przykłady korzystają z własności [[C/Podstawy#Struktura_blokowa|struktury blokowej]], kolejny przykład jest już bardziej wyrafinowany i korzysta z tego, że jako <tt>wyrażenie3</tt> może zostać podane dowolne bardziej skomplikowane wyrażenie, zawierające w sobie inne podwyrażenia. A oto kolejny program, który najpierw wyświetla liczby w kolejności rosnącej, a następnie wraca.
Dwa pierwsze przykłady korzystają z własności [[C/Podstawy#Struktura_blokowa|struktury blokowej]], kolejny przykład jest już bardziej wyrafinowany i korzysta z tego, że jako <tt>wyrażenie3</tt> może zostać podane dowolne bardziej skomplikowane wyrażenie, zawierające w sobie inne podwyrażenia. A oto kolejny program, który najpierw wyświetla liczby w kolejności rosnącej, a następnie wraca.
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
int main()
int main()
Linia 237: Linia 237:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
Po analizie powyższego kodu, początkujący programista może stwierdzić, że pętla wypisze <tt>123454321</tt>. Stanie się natomiast inaczej. Wynikiem działania powyższego programu będzie ciąg cyfr <tt>12345654321</tt>. Pierwsza pętla wypisze cyfry "12345", lecz po ostatnim swoim obiegu pętla for (tak jak zwykle) [[w:inkrementacja|zinkrementuje]] zmienną <tt>i</tt>. Gdy druga pętla przystąpi do pracy, zacznie ona odliczać począwszy od liczby i=6, a nie 5. By spowodować wyświetlanie liczb od 1 do 5 i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli for a pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej <tt>i</tt> o 1.
Po analizie powyższego kodu, początkujący programista może stwierdzić, że pętla wypisze <tt>123454321</tt>. Stanie się natomiast inaczej. Wynikiem działania powyższego programu będzie ciąg cyfr <tt>12345654321</tt>. Pierwsza pętla wypisze cyfry "12345", lecz po ostatnim swoim obiegu pętla for (tak jak zwykle) [[w:inkrementacja|zinkrementuje]] zmienną <tt>i</tt>. Gdy druga pętla przystąpi do pracy, zacznie ona odliczać począwszy od liczby i=6, a nie 5. By spowodować wyświetlanie liczb od 1 do 5 i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli for a pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej <tt>i</tt> o 1.


Niech podsumowaniem będzie jakiś działający fragment kodu, który może obliczać wartości kwadratów liczb od 1 do 10.
Niech podsumowaniem będzie jakiś działający fragment kodu, który może obliczać wartości kwadratów liczb od 1 do 10.
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 252: Linia 252:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
{{Porada|W kodzie źródłowym spotyka się często [[C/Operatory#Inkrementacja_i_dekrementacja|inkrementację]] <tt>i++</tt>. Jest to '''zły zwyczaj''', biorący się z wzorowania się na nazwie języka [[C++]]. Post-inkrementacja <tt>i++</tt> powoduje, że tworzony jest obiekt tymczasowy, który jest zwracany jako wynik operacji (choć wynik ten nie jest nigdzie czytany). Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie, ale w pętli "for" takie kopiowanie odbywa się po każdym przebiegu pętli. Dodatkowo, w C++ podobną konstrukcję stosuje się do obiektów - kopiowanie obiektu może być już czasochłonną czynnością. Dlatego w pętli "for" należy stosować wyłącznie <tt>++i</tt>.}}
{{Porada|W kodzie źródłowym spotyka się często [[C/Operatory#Inkrementacja_i_dekrementacja|inkrementację]] <tt>i++</tt>. Jest to '''zły zwyczaj''', biorący się z wzorowania się na nazwie języka [[C++]]. Post-inkrementacja <tt>i++</tt> powoduje, że tworzony jest obiekt tymczasowy, który jest zwracany jako wynik operacji (choć wynik ten nie jest nigdzie czytany). Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie, ale w pętli "for" takie kopiowanie odbywa się po każdym przebiegu pętli. Dodatkowo, w C++ podobną konstrukcję stosuje się do obiektów - kopiowanie obiektu może być już czasochłonną czynnością. Dlatego w pętli "for" należy stosować wyłącznie <tt>++i</tt>.}}


=== do..while ===
=== do..while ===
Pętle while i for mają jeden zasadniczy mankament - może się zdarzyć, że nie wykonają się ani razu. Aby mieć pewność, że nasza pętla będzie miała co najmniej jeden przebieg musimy zastosować pętlę do while. Wygląda ona następująco:
Pętle while i for mają jeden zasadniczy mankament - może się zdarzyć, że nie wykonają się ani razu. Aby mieć pewność, że nasza pętla będzie miała co najmniej jeden przebieg musimy zastosować pętlę do while. Wygląda ona następująco:
<source lang="C">
<syntaxhighlight lang="C">
do {
do {
/* instrukcje do wykonania w pętli */
/* instrukcje do wykonania w pętli */
} while (warunek);
} while (warunek);
/* dalsze instrukcje */
/* dalsze instrukcje */
</syntaxhighlight>
</source>
Zasadniczą różnicą pętli do while jest fakt, iż sprawdza ona warunek pod koniec swojego przebiegu. To właśnie ta cecha decyduje o tym, że pętla wykona się co najmniej raz. A teraz przykład działającego kodu, który tym razem będzie obliczał trzecią potęgę liczb od 1 do 10.
Zasadniczą różnicą pętli do while jest fakt, iż sprawdza ona warunek pod koniec swojego przebiegu. To właśnie ta cecha decyduje o tym, że pętla wykona się co najmniej raz. A teraz przykład działającego kodu, który tym razem będzie obliczał trzecią potęgę liczb od 1 do 10.
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 276: Linia 276:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
Może się to wydać zaskakujące, ale również przy tej pętli zamiast bloku instrukcji można zastosować pojedynczą instrukcję, np.:
Może się to wydać zaskakujące, ale również przy tej pętli zamiast bloku instrukcji można zastosować pojedynczą instrukcję, np.:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
Linia 287: Linia 287:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>


=== break ===
=== break ===
Instrukcja '''break''' pozwala na opuszczenie wykonywania pętli w dowolnym momencie. Przykład użycia:
Instrukcja '''break''' pozwala na opuszczenie wykonywania pętli w dowolnym momencie. Przykład użycia:
<source lang="C">
<syntaxhighlight lang="C">
int a;
int a;
for (a=1 ; a != 9 ; ++a) {
for (a=1 ; a != 9 ; ++a) {
Linia 297: Linia 297:
printf ("%d\n", a);
printf ("%d\n", a);
}
}
</syntaxhighlight>
</source>
Program wykona tylko 4 przebiegi pętli, gdyż przy 5 przebiegu instrukcja break spowoduje wyjście z pętli.
Program wykona tylko 4 przebiegi pętli, gdyż przy 5 przebiegu instrukcja break spowoduje wyjście z pętli.


==== Break i pętle nieskończone ====
==== Break i pętle nieskończone ====
W przypadku pętli for nie trzeba podawać warunku. W takim przypadku kompilator przyjmie, że warunek jest stale spełniony. Oznacza to, że poniższe pętle są równoważne:
W przypadku pętli for nie trzeba podawać warunku. W takim przypadku kompilator przyjmie, że warunek jest stale spełniony. Oznacza to, że poniższe pętle są równoważne:
<source lang="C">
<syntaxhighlight lang="C">
for (;;) { /* ... */ }
for (;;) { /* ... */ }
for (;1;) { /* ... */ }
for (;1;) { /* ... */ }
Linia 308: Linia 308:
while (1) { /* ... */ }
while (1) { /* ... */ }
do { /* ... */ } while (1);
do { /* ... */ } while (1);
</syntaxhighlight>
</source>
Takie pętle nazywamy '''pętlami nieskończonymi''', które przerwać może jedynie instrukcja '''break'''<ref>Tak naprawdę podobną operacje, możemy wykonać za pomocą polecenia <tt>goto</tt>. W praktyce jednak stosuje się zasadę, że <tt>break</tt> stosuje się do przerwania działania pętli i wyjścia z niej, <tt>goto</tt> stosuje się natomiast wtedy, kiedy chce się wydostać z kilku zagnieżdżonych pętli za jednym zamachem. Do przerwania pracy pętli mogą nam jeszcze posłużyć polecenia <tt>exit()</tt> lub <tt>return</tt>, ale wówczas zakończymy nie tylko działanie pętli, ale i całego programu/funkcji.</ref>(z racji tego, że warunek pętli zawsze jest prawdziwy) <ref>Żartobliwie można powiedzieć, że stosując pętlę nieskończoną to najlepiej korzystać z pętli <tt>for(;;){}</tt>, gdyż wymaga ona napisania najmniejszej liczby znaków w porównaniu do innych konstrukcji.</ref>.
Takie pętle nazywamy '''pętlami nieskończonymi''', które przerwać może jedynie instrukcja '''break'''<ref>Tak naprawdę podobną operacje, możemy wykonać za pomocą polecenia <tt>goto</tt>. W praktyce jednak stosuje się zasadę, że <tt>break</tt> stosuje się do przerwania działania pętli i wyjścia z niej, <tt>goto</tt> stosuje się natomiast wtedy, kiedy chce się wydostać z kilku zagnieżdżonych pętli za jednym zamachem. Do przerwania pracy pętli mogą nam jeszcze posłużyć polecenia <tt>exit()</tt> lub <tt>return</tt>, ale wówczas zakończymy nie tylko działanie pętli, ale i całego programu/funkcji.</ref>(z racji tego, że warunek pętli zawsze jest prawdziwy) <ref>Żartobliwie można powiedzieć, że stosując pętlę nieskończoną to najlepiej korzystać z pętli <tt>for(;;){}</tt>, gdyż wymaga ona napisania najmniejszej liczby znaków w porównaniu do innych konstrukcji.</ref>.


Wszystkie fragmenty kodu działają identycznie:
Wszystkie fragmenty kodu działają identycznie:
<source lang="C">
<syntaxhighlight lang="C">
int i = 0;
int i = 0;
for (;i!=5;++i) {
for (;i!=5;++i) {
Linia 328: Linia 328:
++i;
++i;
}
}
</syntaxhighlight>
</source>


=== continue ===
=== continue ===
W przeciwieństwie do break, która przerywa wykonywanie pętli instrukcja '''continue''' powoduje przejście do następnej iteracji, o ile tylko warunek pętli jest spełniony. Przykład:
W przeciwieństwie do break, która przerywa wykonywanie pętli instrukcja '''continue''' powoduje przejście do następnej iteracji, o ile tylko warunek pętli jest spełniony. Przykład:
<source lang="C">
<syntaxhighlight lang="C">
int i;
int i;
for (i = 0 ; i < 100 ; ++i) {
for (i = 0 ; i < 100 ; ++i) {
Linia 339: Linia 339:
printf ("Koniec\n");
printf ("Koniec\n");
}
}
</syntaxhighlight>
</source>
Dla wartości i większej od 40 nie będzie wyświetlany komunikat "Koniec". Pętla wykona pełne 100 przejść.
Dla wartości i większej od 40 nie będzie wyświetlany komunikat "Koniec". Pętla wykona pełne 100 przejść.




Oto praktyczny przykład użycia tej instrukcji:
Oto praktyczny przykład użycia tej instrukcji:
<source lang="C">
<syntaxhighlight lang="C">
#include <stdio.h>
#include <stdio.h>
int main()
int main()
Linia 355: Linia 355:
return 0;
return 0;
}
}
</syntaxhighlight>
</source>
Powyższy program generuje liczby z zakresu od 1 do 50, które nie są podzielne przez 4.
Powyższy program generuje liczby z zakresu od 1 do 50, które nie są podzielne przez 4.


Linia 361: Linia 361:


Istnieje także instrukcja, która dokonuje skoku do dowolnego miejsca programu, oznaczonego tzw. '''etykietą'''.
Istnieje także instrukcja, która dokonuje skoku do dowolnego miejsca programu, oznaczonego tzw. '''etykietą'''.
<source lang="C">
<syntaxhighlight lang="C">
etykieta:
etykieta:
/* instrukcje */
/* instrukcje */
goto etykieta;
goto etykieta;
</syntaxhighlight>
</source>
'''Uwaga!''': kompilator GCC w wersji 4.0 i wyższych jest bardzo uczulony na etykiety zamieszczone przed nawiasem klamrowym, zamykającym blok instrukcji. Innymi słowy: niedopuszczalne jest umieszczanie etykiety zaraz przed klamrą, która kończy blok instrukcji, zawartych np. w pętli for. Można natomiast stosować etykietę przed klamrą kończącą daną funkcję.
'''Uwaga!''': kompilator GCC w wersji 4.0 i wyższych jest bardzo uczulony na etykiety zamieszczone przed nawiasem klamrowym, zamykającym blok instrukcji. Innymi słowy: niedopuszczalne jest umieszczanie etykiety zaraz przed klamrą, która kończy blok instrukcji, zawartych np. w pętli for. Można natomiast stosować etykietę przed klamrą kończącą daną funkcję.


Linia 372: Linia 372:


Przykład uzasadnionego użycia:
Przykład uzasadnionego użycia:
<source lang="C">
<syntaxhighlight lang="C">
int i,j;
int i,j;
for (i = 0; i < 10; ++i) {
for (i = 0; i < 10; ++i) {
Linia 381: Linia 381:
koniec:
koniec:
/* dalsza czesc programu */
/* dalsza czesc programu */
</syntaxhighlight>
</source>


Zobacz również : [[Programowanie:C:Biblioteka_standardowa:Indeks_tematyczny#setjmp.h|obsługa nielokalnych skoków]]
Zobacz również : [[Programowanie:C:Biblioteka_standardowa:Indeks_tematyczny#setjmp.h|obsługa nielokalnych skoków]]
Linia 387: Linia 387:
==Natychmiastowe kończenie programu - funkcja exit==
==Natychmiastowe kończenie programu - funkcja exit==
Program może zostać w każdej chwili zakończony - do tego właśnie celu służy funkcja '''exit'''. Używamy jej następująco:
Program może zostać w każdej chwili zakończony - do tego właśnie celu służy funkcja '''exit'''. Używamy jej następująco:
<source lang="C">
<syntaxhighlight lang="C">
exit (kod_wyjścia);
exit (kod_wyjścia);
</syntaxhighlight>
</source>
Liczba całkowita ''kod_wyjścia'' jest przekazywana do procesu macierzystego, dzięki czemu dostaje on informację, czy program w którym wywołaliśmy tą funkcję zakończył się poprawnie lub czy się tak nie stało. Kody wyjścia są nieustandaryzowane i żeby program był w pełni przenośny należy stosować makra <tt>EXIT_SUCCESS</tt> i <tt>EXIT_FAILURE</tt>, choć na wielu systemach kod 0 oznacza poprawne zakończenie, a kod różny od 0 błędne. W każdym przypadku, jeżeli nasz program potrafi generować wiele różnych kodów, warto je wszystkie udokumentować w ew. dokumentacji. Są one też czasem pomocne przy wykrywaniu błędów.
Liczba całkowita ''kod_wyjścia'' jest przekazywana do procesu macierzystego, dzięki czemu dostaje on informację, czy program w którym wywołaliśmy tą funkcję zakończył się poprawnie lub czy się tak nie stało. Kody wyjścia są nieustandaryzowane i żeby program był w pełni przenośny należy stosować makra <tt>EXIT_SUCCESS</tt> i <tt>EXIT_FAILURE</tt>, choć na wielu systemach kod 0 oznacza poprawne zakończenie, a kod różny od 0 błędne. W każdym przypadku, jeżeli nasz program potrafi generować wiele różnych kodów, warto je wszystkie udokumentować w ew. dokumentacji. Są one też czasem pomocne przy wykrywaniu błędów.



Wersja z 00:14, 20 kwi 2020

C jest językiem imperatywnym - oznacza to, że instrukcje wykonują się jedna po drugiej w takiej kolejności w jakiej są napisane. Aby móc zmienić kolejność wykonywania instrukcji potrzebne są instrukcje sterujące.

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory, że wyrażenie jest prawdziwe wtedy i tylko wtedy, gdy jest różne od zera, a fałszywe wtedy i tylko wtedy, gdy jest równe zeru.

Instrukcje warunkowe

if

Użycie instrukcji if wygląda tak:

 if (wyrażenie) {
   /* blok wykonany, jeśli wyrażenie jest prawdziwe */
 }
 /* dalsze instrukcje */

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia - wtedy należy zastosować słowo kluczowe else:

 if (wyrażenie) {
   /* blok wykonany, jeśli wyrażenie jest prawdziwe */
 } else {
   /* blok wykonany, jeśli wyrażenie jest nieprawdziwe */
 }
 /* dalsze instrukcje */

Przypatrzmy się bardziej "życiowemu" programowi, który porównuje ze sobą dwie liczby:

 #include <stdio.h>
 
 int main ()
 {
   int a, b;
   a = 4;
   b = 6;
   if (a==b) {
     printf ("a jest równe b\n");
   } else {
     printf ("a nie jest równe b\n");
   }
   return 0;
 }

Stosowany jest też krótszy zapis warunków logicznych, korzystający z tego, jak C rozumie prawdę i fałsz, tzn.:

  • liczba całkowita różna od zera oznacza prawdę
  • liczba całkowita równa zero oznacza fałsz.

Jeśli zmienna a jest typu integer, zamiast:

 if (a != 0) b = 1/a;

można napisać:

  if (a) b = 1/a;

a zamiast

  if (a == 0) b = 0;

można napisać:

  if (!a)  b = 0;


Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego (patrz Operatory).

 if (a != 0)
   b = 1/a;
 else
   b = 0;

ma dokładnie taki sam efekt jak:

 b = (a !=0) ? 1/a : 0;

switch

Aby ograniczyć wielokrotne stosowanie instrukcji if możemy użyć switch. Jej użycie wygląda tak:

 switch (wyrażenie) {
   case wartość1: /* instrukcje, jeśli wyrażenie == wartość1 */
     break;
   case wartość2: /* instrukcje, jeśli wyrażenie == wartość2 */
     break;
   /* ... */
   default: /* instrukcje, jeśli żaden z wcześniejszych warunków nie został spełniony */
     break;
 }

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case. Jeśli tego nie zrobimy, program przejdzie do wykonywania instrukcji z następnego case. Może mieć to fatalne skutki:

 #include <stdio.h>
 
 int main ()
 {
   int a, b;
   printf ("Podaj a: ");
   scanf ("%d", &a);
   printf ("Podaj b: ");
   scanf ("%d", &b);
   switch (b) {
     case  0: printf ("Nie można dzielić przez 0!\n"); /* tutaj zabrakło break! */
     default: printf ("a/b=%d\n", a/b);
   }
   return 0;
 }

A czasami może być celowym zabiegiem (tzw. "fall-through") - wówczas warto zaznaczyć to w komentarzu. Oto przykład:

 #include <stdio.h>
 
 int main ()
 {
   int a = 4;
   switch ((a%3)) {
     case  0:
       printf ("Liczba %d dzieli się przez 3\n", a);
       break;
     case -2:
     case -1:
     case  1:
     case  2:
       printf ("Liczba %d nie dzieli się przez 3\n", a);
       break;
   }
   return 0;
 }

Przeanalizujmy teraz działający przykład:

 #include <stdio.h>
 
 int main ()
 {
   unsigned int dzieci = 3, podatek=1000;
   switch (dzieci) {
      case  0: break; /* brak dzieci - czyli brak ulgi */ 
      case  1: /* ulga  2% */
        podatek = podatek - (podatek/100* 2); 
        break;
      case  2: /* ulga  5% */
        podatek = podatek - (podatek/100* 5);
        break;
      default: /* ulga 10% */
        podatek = podatek - (podatek/100*10);
        break; 
   }
   printf ("Do zapłaty: %d\n", podatek);
 }

Pętle

while

Często zdarza się, że nasz program musi wielokrotnie powtarzać ten sam ciąg instrukcji. Aby nie przepisywać wiele razy tego samego kodu można skorzystać z tzw. pętli. Pętla wykonuje się dopóty, dopóki prawdziwy jest warunek.

 while (warunek) {
   /* instrukcje do wykonania w pętli */
 }
 /* dalsze instrukcje */

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie. Załóżmy, że mamy obliczyć kwadraty liczb od 1 do 10. Piszemy zatem program:

 #include <stdio.h>
 
 int main ()
 {
   int a = 1;
   while (a <= 10) { /* dopóki a nie przekracza 10 */
     printf ("%d\n", a*a); /* wypisz a*a na ekran*/
     ++a; /* zwiększamy a o jeden*/
   }
   return 0;
 }

Po analizie kodu mogą nasunąć się dwa pytania:

  • Po co zwiększać wartość a o jeden? Otóż gdybyśmy nie dodali instrukcji zwiększającej a, to warunek zawsze byłby spełniony, a pętla "kręciłaby" się w nieskończoność.
  • Dlaczego warunek to "a <= 10" a nie "a!=10"? Odpowiedź jest dość prosta. Pętla sprawdza warunek przed wykonaniem kolejnego "obrotu". Dlatego też gdyby warunek brzmiał "a!=10" to dla a=10 jest on nieprawdziwy i pętla nie wykonałaby ostatniej iteracji, przez co program generowałby kwadraty liczb od 1 do 9, a nie do 10.

for

Od instrukcji while czasami wygodniejsza jest instrukcja for. Umożliwia ona wpisanie ustawiania zmiennej, sprawdzania warunku i inkrementowania zmiennej w jednej linijce co często zwiększa czytelność kodu.

Instrukcję for stosuje się w następujący sposób:

 for (wyrażenie1; wyrażenie2; wyrażenie3) {
   /* instrukcje do wykonania w pętli */
 }
 /* dalsze instrukcje */

Jak widać, pętla for znacznie różni się od tego typu pętli, znanych w innych językach programowania.

Opiszemy więc, co oznaczają poszczególne wyrażenia:

  • wyrażenie1 - jest to instrukcja, która będzie wykonana przed pierwszym przebiegiem pętli. Zwykle jest to inicjalizacja zmiennej, która będzie służyła jako "licznik" przebiegów pętli.
  • wyrażenie2 - jest warunkiem trwania pętli. Pętla wykonuje się tak długo, jak prawdziwy jest ten warunek.
  • wyrażenie3 - jest to instrukcja, która wykonywana będzie po każdym przejściu pętli ( także po ostatnim). Zamieszczone są tu instrukcje, które zwiększają licznik o odpowiednią wartość.

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest ona równoważna z:

 {
   wyrażenie1;
   while (wyrażenie2) {
     /* instrukcje do wykonania w pętli */
     wyrażenie3;
   }
 }
 /* dalsze instrukcje */

Ważną rzeczą jest tutaj to, żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla for. Początkującym programistom nieznajomość tego faktu sprawia wiele problemów.

W pierwszej kolejności w pętli for wykonuje się wyrażenie1. Wykonuje się ono zawsze, nawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy.

Po wykonaniu wyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2, jeżeli jest on prawdziwy ( inny niż zero), to wykonywana jest treść pętli for, czyli najczęściej to co znajduje się między klamrami, lub gdy ich nie ma, następna pojedyncza instrukcja. W szczególności musimy pamiętać, że sam średnik też jest instrukcją - instrukcją pustą.

Gdy już zostanie wykonana treść pętli for, następuje wykonanie wyrażenie3. Należy zapamiętać, że wyrażenie3 zostanie wykonane, nawet jeżeli był to już ostatni obieg pętli. Poniższe 4 przykłady pętli for w rezultacie dadzą ten sam wynik. Wypiszą na ekran liczby od 1 do 10.


 
 for(i=1; i<=10; ++i){
  printf("%d", i);
 }

 for(i=1; i<=10; ++i){
  printf("%d", i);
}

 for(i=1; i<=10; printf("%d", i++ ) );

 
 // 4 przykład 
 for(i=1; i<10; printf("i = %d", i++ ) ); 
 printf(" i = %d", i ); // wyrażenie3 i++ zostanie wykonane, nawet jeżeli był to już ostatni obieg pętli

Dwa pierwsze przykłady korzystają z własności struktury blokowej, kolejny przykład jest już bardziej wyrafinowany i korzysta z tego, że jako wyrażenie3 może zostać podane dowolne bardziej skomplikowane wyrażenie, zawierające w sobie inne podwyrażenia. A oto kolejny program, który najpierw wyświetla liczby w kolejności rosnącej, a następnie wraca.

 #include <stdio.h>
 int main()
 {
  int i;
  for(i=1; i<=5; ++i){   
    printf("%d", i);
    }  

  for( ; i>=1; --i){
    printf("%d", i);
    }
 
  return 0;
 }

Po analizie powyższego kodu, początkujący programista może stwierdzić, że pętla wypisze 123454321. Stanie się natomiast inaczej. Wynikiem działania powyższego programu będzie ciąg cyfr 12345654321. Pierwsza pętla wypisze cyfry "12345", lecz po ostatnim swoim obiegu pętla for (tak jak zwykle) zinkrementuje zmienną i. Gdy druga pętla przystąpi do pracy, zacznie ona odliczać począwszy od liczby i=6, a nie 5. By spowodować wyświetlanie liczb od 1 do 5 i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli for a pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o 1.

Niech podsumowaniem będzie jakiś działający fragment kodu, który może obliczać wartości kwadratów liczb od 1 do 10.

   
 #include <stdio.h>
 
 int main ()
 {
   int a;
   for (a=1; a<=10; ++a) {
     printf ("%d\n", a*a);
   }
   return 0;
 }

do..while

Pętle while i for mają jeden zasadniczy mankament - może się zdarzyć, że nie wykonają się ani razu. Aby mieć pewność, że nasza pętla będzie miała co najmniej jeden przebieg musimy zastosować pętlę do while. Wygląda ona następująco:

 do {
   /* instrukcje do wykonania w pętli */
 } while (warunek);
 /* dalsze instrukcje */

Zasadniczą różnicą pętli do while jest fakt, iż sprawdza ona warunek pod koniec swojego przebiegu. To właśnie ta cecha decyduje o tym, że pętla wykona się co najmniej raz. A teraz przykład działającego kodu, który tym razem będzie obliczał trzecią potęgę liczb od 1 do 10.

 #include <stdio.h>
 
 int main ()
 {
   int a = 1;
   do {
     printf ("%d\n", a*a*a);
     ++a;
   } while (a <= 10);
   return 0;
 }

Może się to wydać zaskakujące, ale również przy tej pętli zamiast bloku instrukcji można zastosować pojedynczą instrukcję, np.:

 #include <stdio.h>
 
 int main ()
 {
   int a = 1;
   do printf ("%d\n", a*a*a); while (++a <= 10);
   return 0;
 }

break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie. Przykład użycia:

 int a;
 for (a=1 ; a != 9 ; ++a) {
   if (a == 5) break;
   printf ("%d\n", a);
 }

Program wykona tylko 4 przebiegi pętli, gdyż przy 5 przebiegu instrukcja break spowoduje wyjście z pętli.

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku. W takim przypadku kompilator przyjmie, że warunek jest stale spełniony. Oznacza to, że poniższe pętle są równoważne:

 for (;;) { /* ... */ }
 for (;1;) { /* ... */ }
 for (a;a;a) { /* ... */} /*gdzie a jest dowolną liczba rzeczywistą różną od 0*/
 while (1) { /* ... */ }
 do { /* ... */ } while (1);

Takie pętle nazywamy pętlami nieskończonymi, które przerwać może jedynie instrukcja break[1](z racji tego, że warunek pętli zawsze jest prawdziwy) [2].

Wszystkie fragmenty kodu działają identycznie:

 int i = 0;
 for (;i!=5;++i) {
   /* kod ... */
 }

 int i = 0;
 for (;;++i) {
   if (i == 5) break;
 }

 int i = 0;
 for (;;) {
   if (i == 5) break;
   ++i;
 }

continue

W przeciwieństwie do break, która przerywa wykonywanie pętli instrukcja continue powoduje przejście do następnej iteracji, o ile tylko warunek pętli jest spełniony. Przykład:

 int i;
 for (i = 0 ; i < 100 ; ++i) {
   printf ("Poczatek\n");
   if (i > 40) continue ;
   printf ("Koniec\n");
 }

Dla wartości i większej od 40 nie będzie wyświetlany komunikat "Koniec". Pętla wykona pełne 100 przejść.


Oto praktyczny przykład użycia tej instrukcji:

 #include <stdio.h>
 int main()
 {
   int i;
   for (i = 1 ; i <= 50 ; ++i) {
     if (i%4 == 0) continue ;
     printf ("%d, ", i);
   }
   return 0;
 }

Powyższy program generuje liczby z zakresu od 1 do 50, które nie są podzielne przez 4.

goto

Istnieje także instrukcja, która dokonuje skoku do dowolnego miejsca programu, oznaczonego tzw. etykietą.

 etykieta:
 /* instrukcje */
 goto etykieta;

Uwaga!: kompilator GCC w wersji 4.0 i wyższych jest bardzo uczulony na etykiety zamieszczone przed nawiasem klamrowym, zamykającym blok instrukcji. Innymi słowy: niedopuszczalne jest umieszczanie etykiety zaraz przed klamrą, która kończy blok instrukcji, zawartych np. w pętli for. Można natomiast stosować etykietę przed klamrą kończącą daną funkcję.

Przykład uzasadnionego użycia:

 int i,j;
 for (i = 0; i < 10; ++i) {
   for (j = i; j < i+10; ++j) {
     if (i + j % 21 == 0) goto koniec;
   }
 }
 koniec:
 /* dalsza czesc programu */

Zobacz również : obsługa nielokalnych skoków

Natychmiastowe kończenie programu - funkcja exit

Program może zostać w każdej chwili zakończony - do tego właśnie celu służy funkcja exit. Używamy jej następująco:

 exit (kod_wyjścia);

Liczba całkowita kod_wyjścia jest przekazywana do procesu macierzystego, dzięki czemu dostaje on informację, czy program w którym wywołaliśmy tą funkcję zakończył się poprawnie lub czy się tak nie stało. Kody wyjścia są nieustandaryzowane i żeby program był w pełni przenośny należy stosować makra EXIT_SUCCESS i EXIT_FAILURE, choć na wielu systemach kod 0 oznacza poprawne zakończenie, a kod różny od 0 błędne. W każdym przypadku, jeżeli nasz program potrafi generować wiele różnych kodów, warto je wszystkie udokumentować w ew. dokumentacji. Są one też czasem pomocne przy wykrywaniu błędów.

Uwagi

  • W języku C++ można deklarować zmienne w nagłówku pętli "for" w następujący sposób: for(int i=0; i<10; ++i) (więcej informacji w C++/Zmienne)

Przypisy

  1. Tak naprawdę podobną operacje, możemy wykonać za pomocą polecenia goto. W praktyce jednak stosuje się zasadę, że break stosuje się do przerwania działania pętli i wyjścia z niej, goto stosuje się natomiast wtedy, kiedy chce się wydostać z kilku zagnieżdżonych pętli za jednym zamachem. Do przerwania pracy pętli mogą nam jeszcze posłużyć polecenia exit() lub return, ale wówczas zakończymy nie tylko działanie pętli, ale i całego programu/funkcji.
  2. Żartobliwie można powiedzieć, że stosując pętlę nieskończoną to najlepiej korzystać z pętli for(;;){}, gdyż wymaga ona napisania najmniejszej liczby znaków w porównaniu do innych konstrukcji.