Każdy język programowania posiada zestaw instrukcji sterujących umożliwiających wielokrotne wykonywanie tego samego kodu (pętle), wybór odpowiedniego fragmentu kodu (warunki) oraz instrukcje umożliwiające opuszczenie bieżącego fragmentu kodu. ArduinoIDE przejęło z C/C++ większość niezbędnych elementów sterujących. Ich składnia jest identyczna z C. Poniżej przedstawimy w skrócie ich składnię. Ze względu na zgodność z C/C++ wyczerpujące informacje na temat instrukcji sterujących można znaleźć w kursie C++ na naszej stronie lub dowolnej książce opisującej język C++.
if
Polecenie if umożliwia uzależnienie wykonania fragmentu programu od wyniku sprawdzenia określonego warunku. Jeżeli warunek jest spełniony wykonany zostanie kod programu, jeżeli warunek nie jest spełniony kod programu zostanie pominięty. Składnia polecenia if jest następująca:
if(warunek)
{
instrukcja1;
instrukcja2;
}
Warunkiem może być dowolne przyrównanie zmiennych lub wartości zwracanych przez funkcję. Podstawowym kryterium warunku jest to, by odpowiedzią zawsze była prawda lub fałsz. Przykładowe warunki dla instrukcji if:
if(a!=2)
{
}
if(x<10)
{
}
if(znak=='B')
{
}
Wewnątrz nawiasu, w którym wpisujemy warunki możliwe jest wykonywanie kodu. Osoby, które rozpoczynają przygodę z programowaniem często popełniają błąd przyrównując wartość do określonej zmiennej za pomocą jednego znaku =. Taki zapis jest jednoznaczny z przypisaniem wartości do zmiennej, a co za tym idzie warunek będzie zawsze spełniony. Sprawdzając, czy jakaś zmienna równa się określonej wartości zawsze stosujemy podwójny znak = czyli ==. Możliwe jest wykorzystanie jako warunek funkcji np.:
if(init())
{
Serial.print("ok.");
}
Powyższy przykład wykona się w następujący sposób: w pierwszym kroku zostanie wywołana funkcja init(). Funkcja ta zwróci wartość, która zostanie zinterpretowana jako prawda lub fałsz. W zależności od interpretacji zostanie przesłany tekst ok. lub nic.
if...else
Rozszerzeniem polecenia if jest polecenie if....else. Umożliwia ono wykonanie jednego fragmentu kodu, gdy warunek zostanie spełniony, a drugiego fragmentu kodu, gdy warunek nie zostanie spełniony. Składnia polecenia jest następująca:
if (warunek)
{
// polecenia A
}
else
{
// polecenia B
}
Polecenia A zostaną wykonane tylko wtedy, gdy warunek zostanie spełniony, polecenia B wykonane zostaną, gdy warunek nie zostanie spełniony. Nie ma możliwości wykonania jednocześnie poleceń A i poleceń B. Poniższy przykład pokazuje sposób wykorzystania składni if..else:
if (init())
{
Serial.print("ok.");
}
else
{
Serial.print("Błąd");
}
W powyższy sposób można sprawdzić, czy funkcja wykonała się prawidłowo i poinformować o tym użytkownika. Często stosowaną praktyką jest negacja warunku. Wynika to z faktu, że zwyczajowo funkcja, która wykonała się poprawnie zwraca wartość 0, a funkcja, której wykonanie z jakiegoś powodu nie powiodło się zwraca wartość różną od zera. Uzasadnienie takiego "komplikowania sobie życia" jest proste. Jeżeli funkcja wykonała się prawidłowo, to jest to jedyna informacja, którą potrzebujemy. W przypadku błędu warto się czasem dowiedzieć co poszło źle, dlaczego funkcja nie wykonała się poprawnie. I tu przychodzą z pomocą liczby różne od zera, czyli możliwość określenia za pomocą kodów cyfrowych konkretnych błędów. Przykładowo 1 - problem z odczytem jakiejś wartości, 2 - brak miejsca w pamięci czy na dysku itp. Ostatni zmodyfikowany przykład pokazuje sposób wywołania funkcji, która zwraca wartość zero, gdy się prawidłowo wykona:
if (!init())
{
Serial.print("ok.");
}
else
{
Serial.print("Błąd");
}
switch case
Polecenie if umożliwia sprawdzenie jednego warunku. Czasami zdarza się, że trzeba wykonać jedną z akcji zależną od zwróconej, czy oczytanej wartości. Do tego idealnie nadaje się polecenie wyboru wielowariantowego switch. Poniżej pokazano składnię polecenia switch:
switch (var)
{
case 1:
//instrukcje dla var=1
break;
case 2:
//instrukcje dla var=2
break;
default:
// instrukcje domyślne (jeżeli var różne od 1 i 2)
}
W zależności od wartości zmiennej var wykonywane są instrukcje w określonych blokach. Słowo kluczowe case oznacza początek bloku dla podanej wartości. Przykładowo case 1: oznacza, że dany blok zostanie wykonany dla wartości zmiennej var równej jeden. Każdy blok należy zakończyć poleceniem break. Przerywa ono dalsze wykonywanie polecenia switch. Jeżeli polecenia break zabraknie kolejne instrukcje będą wykonywane nawet w kolejnych blokach. Klauzula default: jest opcjonalna tak jak else w poleceniu if. Instrukcje znajdujące się w bloku default są wykonywane, gdy wartość zmiennej var nie pasuje do żadnego wzorca. Często się zdarza, że te same instrukcje powinny zostać wykonane dla jednej z kilku wartości. Można to osiągnąć w następujący sposób:
switch (x)
{
case 1:
//instrukcje dla x=1
break;
case 2:
case 3:
case 5:
//instrukcje dla x=2 lub 3 lub 5
break;
case 4:
//instrukcje dla x=4
break;
case 6:
//instrukcje dla x=6
break;
default:
// instrukcje domyślne (jeżeli x różne od 1,2,3,4,5,6)
}
W zależności od wartości zmiennej x zostanie wykonany blok instrukcji. Powtórzenie case 2: case 3: case 5: informuje kompilator, że jeżeli zmienna x ma wartość 2 lub 3 lub 5 to ma zostać wykonany ten sam fragment kodu.
for
Polecenie for umożliwia wielokrotne wykonanie tego samego kodu. Często należy wykonać te same instrukcje zmieniając tylko wartość jakiejś zmiennej. Do tego idealnie nadaje się pętla for. Składnia polecenia jest następująca:
int i;
for(i=0;i<10;i++)
{
//instrukcje do wykonania w pętli
}
Pierwszym parametrem podawanym w instrukcji for jest wartość początkowa zmiennej. Kolejny element to sprawdzenie warunku, czy pętla ma się wykonywać nadal. Pętla wykonywana jest tak długo, jak długo jest spełniony warunek. Ostatni element to zmiana wartości zmiennej. Najczęściej zwiększamy lub zmniejszamy jej wartość (w zależności od potrzeb). W powyższym przykładzie instrukcje znajdujące się wewnątrz pętli wykonają się dla i=0 do 9. Często zmienną wykorzystywaną w pętli deklaruje się bezpośrednio:
for(int i=0;i<10;i++)
{
//instrukcje do wykonania w pętli
}
Zmienna, która służy do odliczania kolejnych kroków pętli może służyć wewnątrz jej do wywołania funkcji z odpowiednimi parametrami.
for(int i=10;i>0;i--)
{
Serial.print(i); //wyśle liczby 10,9,8,7,6,5,4,3,2,1
}
while
Pętla for jest idealna wszędzie tam, gdzie chcemy skorzystać z odliczania. W sytuacji, gdy chcemy wykonywać określone czynności w zależności od spełnienia warunku, który niekoniecznie jest przewidywalny (np. czekamy na naciśnięcie przycisku) możemy skorzystać z polecenia while, które wykonuje blok instrukcji tak długo, jak długo spełniony jest warunek. Składnię polecenia while przedstawiono poniżej:
while(warunek)
{
//blok instrukcji do wykonania
}
Istotny jest fakt, że sprawdzenie warunku następuje na początku wykonywania pętli. Może się zdarzyć, że instrukcje zawarte wewnątrz pętli while nie wykonają się nigdy. Możliwe jest również utworzenie pętli nieskończonej. Sprawdźmy dwa przykłady:
int x=2;
while(x>5)
{
Serial.print(x);
}
int y=5;
while(y>0)
{
Serial.print(y);
}
Pierwszy blok instrukcji znajdujący się wewnątrz while nie wykona się nigdy. Zmienna x ma wartość dwa i nie jest większa od 5. W drugim przykładzie mamy do czynienia z pętlą nieskończoną. Zmienna y została ustawiona na 5, czyli jest większa od zera. Wewnątrz pętli nie ma żadnej modyfikacji zmiennej y, więc pętla nigdy nie zostanie zakończona. Jest to częsty błąd programistyczny, gdzie zapominamy o modyfikacji parametru powodującego zakończenie pętli. Poniżej dwa prawidłowe przykłady zastosowania pętli while:
int x=0;
while(x<10)
{
//blok instrukcji;
x++;
}
while(true)
{
if(warunek) break;
//blok instrukcji
}
W pierwszym przykładzie zadbano o zmianę wartości zmiennej, która sprawdzana jest w warunku. W efekcie pętla kiedyś się zakończy. W drugim przykładzie utworzono celowo pętlę nieskończoną. Pętla taka jest odpowiednikiem funkcji loop() w ArduinoIDE. Dodatkowo wewnątrz pętli umieszczono sprawdzenie warunku, od którego uzależniono zakończenie jej wykonywania.
do... while
Odmianą pętli while jest pętla do..while. Poza składnią różni się ona miejscem sprawdzania warunku. W przypadku do...while sprawdzenie warunku następuje po wykonaniu bloku instrukcji. Oznacza to, że blok instrukcji w pętli wykona się przynajmniej raz. Poniżej przedstawiono składnię polecenia do...while:
do
{
//blok instrukcji do wykonania
}while(warunek)
Wszystko, co napisano o poleceniu while dotyczy również do...while. Poniżej przedstawiono przykładowe wykorzystanie pętli do...while:
int x=10;
do
{
//blok instrukcji
x--;
}while(x>0);
do
{
//blok instrukcji
if(warunek) break;
}while(true);
break
Polecenie break umożliwia opuszczenie pętli (do..while, for, while) oraz wyjście z opcji polecenia switch. Na poniższym przykładzie zostanie omówione działanie polecenia break:
for(i=0;i<10;i++)
{
if(i==5) break;
Serial.print(i);
}
Pętla powinna wykonać się dla liczb od 0 do 9, jednak dla liczby 5 spełniony zostanie warunek uruchamiający instrukcję break. Spowoduje to opuszczenie pętli. W efekcie poprzez port szeregowy (Serial.print) przekazane zostaną liczby 0,1,2,3,4.
continue
Polecenie continue powoduje przerwanie wykonywania instrukcji w pętli (do..while, for, while) dla bieżącej wartości i przejście do wykonywania kolejnego kroku pętli. Poniższy przykład pokazuje sposób działania polecenia continue:
for(i=0;i<10;i++)
{
if(i==5) continue;
Serial.print(i);
}
Jak nie trudno zauważyć pętla zostanie wykonana dla wartości od 0 do 9. Dla wartości 5 wykona się polecenie continue czyli instrukcje znajdujące się po tym poleceniu nie zostaną wykonane. W efekcie poprzez port szeregowy (Serial.print) przekazane zostaną liczby 0,1,2,3,4,6,7,8,9.
return
Polecenie return kończy wykonywanie wywołanej funkcji i zwraca wartość określonego typu. Jako parametr polecenia można podać liczbę, znak lub zmienną określonego typu. Istotne jest by zwracana wartość była zgodna z typem zadeklarowanej funkcji. Poniższy przykład pokazuje sposób zastosowania polecenia return.
int checkSensor(){
if (analogRead(0) > 400) { //odczyt wejścia analogowego
return 1; //dla wartości większej od 400 zwraca wartość 1
else{
return 0; //dla pozostałych zwraca wartość 0
}
}
Jak widać możliwe jest zastosowanie kilku poleceń return w jednej funkcji, lecz wykonane powinno być zawsze tylko jedno. Dopuszczalne jest użycie polecenia return bez parametrów. Umożliwi ono wcześniejsze zakończenie funkcji, która nie zwraca żadnej wartości.
void funkcja()
{
instrukcja1;
if(x==0) return;
instukcja2;
instrukcja3;
}
W powyższym przykładzie instrukcja1 wykona się zawsze, gdy wywołana zostanie funkcja. Wykonanie instrukcji2 oraz instrukcji3 uzależnione jest od wyniku polecenia if. Jeżeli warunek zostanie spełniony wykonane zostanie polecenie return, czyli funkcja zakończy działanie. W przypadku, gdy warunek nie zostanie spełniony polecenie return nie wykona się, natomiast wykonają się instrukcje 2 i 3 i wtedy funkcja zakończy działanie.
goto
Ze względów ideologicznych powinienem wyrzucić ten fragment... Polecenie goto jest poleceniem, którego nie powinno się używać w normalnym programowaniu. Powoduje zaciemnienie kodu i wprowadza złe nawyki programistyczne. Zdecydowanie odradzam stosowanie tego polecenia we własnych programach. Ze względu na fakt, że goto znajduje się w oficjalnej dokumentacji na stronie arduino.cc zostanie tu pobieżnie opisane.
Składnia polecenia goto:
....
goto etykieta; //skocz do miejsca oznaczonego nazwą "etykieta'
.....
....
....
etykieta: //etykieta od której będzie kontynuowany program
...
Polecenie umożliwia przeskok do oznaczonej etykiety, czyli miejsca w programie.