Zagadnienia

4. Instrukcje złożone

Instrukcje złożone

4.1. Instrukcje pętli

4.1.1. Pętle while i do

. instrukcja_pętli:
. . while (warunek) instrukcja
. . do instrukcja while (warunek);
. . pętla_for
. warunek:
. . wyrażenie

Podstawowym narzędziem do opisywania powtarzania wykonywania operacji jest iteracja. W C++ do jej zapisu służą aż trzy instrukcje pętli. Są one takie same jak w C, dwie pierwsze z nich są podobne do pętli (odpowiednio) while i repeat z Pascala.

Instrukcja while

  • Dopóki warunek jest spełniony, wykonuje podaną instrukcję.

  • Warunek wylicza się przed każdym wykonaniem instrukcji.

  • Może nie wykonać ani jednego obrotu.

Instrukcja do

  • Powtarza wykonywanie instrukcjiwarunek przestanie być spełniony.

  • Warunek wylicza się po każdym wykonaniu instrukcji.

  • Zawsze wykonuje co najmniej jeden obrót.

Jak widać semantyka pętli while i do w C++ jest typowa. Znającym Pascala warto zwrócić uwagę, że dozór w pętli do oznacza warunek kontynuowania pętli (a nie kończenia jak w repeat z Pascala).

Oto przykłady użycia tych pętli.

Obliczanie największego wspólnego dzielnika dwu liczb naturalnych większych od zera za pomocą odejmowania.

 while (m!=n)
  if(m>n)
   m=m-n;
 else
   n=n-m;

Wypisywanie (od tyłu) cyfr liczby naturalnej nieujemnej.

 do{
  cout << n%10;
  n=n/10;
 }
 while (n>0);

  • Warunek musi być typu logicznego, arytmetycznego lub wskaźnikowego.

  • Jeśli wartość warunku jest liczbą lub wskaźnikiem, to wartość różną od zera uważa się za warunek prawdziwy, a wartość równą zeru za warunek fałszywy.

  • Wartość warunku typu innego niż logiczny jest niejawnie przekształcana na typ bool.

  • Warunek w pętli while może być także deklaracją (z pewnymi ograniczeniami), której zasięgiem jest ta pętla,

  • Ta deklaracja musi zawierać część inicjującą.

  • Zauważmy, że w pętli do warunek nie może być deklaracją.

  • Instrukcja może być deklaracją (jej zasięgiem zawsze jest tylko ta instrukcja). Przy każdym obrocie pętli sterowanie wchodzi i opuszcza ten lokalny zasięg, z wszelkimi tego konsekwencjami.

Zaskakującą cechą pętli w C++ (odziedziczoną po C) jest to, że warunek nie musi mieć typu logicznego (w pierwszych wersjach języków C i C++ w ogóle nie było takiego typu). Składnia języka C była tak tworzona, by łatwo zapisywało się w niej typowe programy, natomiast twórcy C nie przykładali dużej wagi do czytelności programów w tym języku. Język był przeznaczony dla bardzo zaawansowanych programistów, np. tworzących systemy operacyjne, w związku z tym twórcy języka uznali, że nie warto udawać przed użytkownikami tego języka, że w pamięci komputera są inne rzeczy niż liczby i adresy — rzeczy takie jak na przykład wartości logiczne. Stąd reguła, że każda wartość (liczba lub wskaźnik) różna od zera będzie oznaczała prawdę, a wartość zero fałsz. W wielu przypadkach taka składnia okazywała się poręczna, na przykład przeglądanie listy za pomocą wskaźnika p można zapisać używając dozoru pętli o postaci po prostu p, czyli:

  while (p)
   p=p->next;
 
(Wskaźniki będą omówione w kolejnych wykładach). Chcąc użyć wartości logicznych należałoby zapisać tę pętlę tak:
  while (p != NULL)
   p=p->next;
 
Niestety za tę kuszącą zwięzłość zapisu płaci się zmniejszoną czytelnością i większym ryzykiem popełniania błędów. Na przykład pominięcie części warunku przy przeglądaniu listy cyklicznej:
  while (p) // pominięta część != start
   p=p->next;
 
zamiast
  while (p != start)
   p=p->next;
 
nie spowoduje żadnego komunikatu ani ostrzeżenia ze strony kompilatora. Niestety nie wszystko to co daje się łatwo zapisać, daje się ławo odczytać.

Deklarowanie zmiennych w dozorach pętli while nie ma większego praktycznego znaczenia.

Należy pamiętać, że jeśli w treści pętli będzie zadeklarowana (niestatyczna) zmienna, to przy każdym obrocie pętli będzie ona od nowa tworzona (i usuwana na koniec obrotu pętli). Zatem na przykład nie można w kolejnym obrocie pętli odwołać się do wartości nadanej tej zmiennej w poprzednich obrotach pętli. Na przykład program:

  i=0;
  while(i<n){
   int j = 0;
   j++;
   cout << "* " << j << endl;
   i++;
  }
 
Wypisze liczby: 1 1 1 1 1 Ten sam program po dodaniu słowa static przy deklaracji zmiennej j:
  i=0;
  while(i<n){
   static int j = 0;
   j++;
   cout << "* " << j << endl;
   i++;
  }
 
Wypisze liczby: 1 2 3 4 5.

4.1.2. Instrukcja pętli - for

. pętla_for:
. . for(inst_ini_for warunek_{{opc}};wyrażenie_{{opc}}) instrukcja
. inst_ini_for:
. . instrukcja_wyrażeniowa
. . prosta_deklaracja
  • Warunek jak w poprzednich pętlach,

  • Pominięcie warunku jest traktowane jako wpisanie true,

  • Jeśli instrukcja instr_inic jest deklaracją, to zasięg zadeklarowanych nazw sięga do końca pętli,

  • Zasięg nazw zadeklarowanych w warunku jest taki sam, jak zasięg nazw zadeklarowanych w inst_ini_for,

  • Instrukcja może być deklaracją (jej zasięgiem zawsze jest tylko ta instrukcja). Przy każdym obrocie pętli sterowanie wchodzi i opuszcza ten lokalny zasięg.

4.1.3. Semantyka pętli for

Instrukcja for jest (praktycznie) równoważna instrukcji:

{
 inst_ini_for
 while ( warunek ) {
   instrukcja
   wyrażenie ;
 }
}

Różnica: jeśli w instrukcji wystąpi continue, to wyrażenie w pętli for będzie obliczone przed obliczeniem warunku. W pętli while nie można pominąć warunku.

4.2. Dalsze instrukcje zmieniające przepływ sterowania

4.2.1. Instrukcje skoku

  • break;

  • continue;

  • return\mbox{wyrażenie}_{{opc}};

  • goto identyfikator ;

W C++ zawsze przy wychodzeniu z zasięgu widoczności następuje niszczenie obiektów automatycznych zadeklarowanych w tym zasięgu, w kolejności odwrotnej do ich deklaracji.

4.2.2. Instrukcja break

  • Może się pojawić jedynie wewnątrz pętli lub instrukcji wyboru i powoduje przerwanie wykonywania najciaśniej ją otaczającej takiej instrukcji,

  • Sterowanie przechodzi bezpośrednio za przerwaną instrukcję.

4.2.3. Instrukcja continue

  • Może się pojawić jedynie wewnątrz instrukcji pętli i powoduje zakończenie bieżącego obrotu (najciaśniej otaczającej) pętli.

4.2.4. Instrukcja return

  • Służy do kończenia wykonywania funkcji i (ewentualnie) do przekazywania wartości wyniku funkcji.

  • Każda funkcja o typie wyniku innym niż void musi zawierać co najmniej jedną taką instrukcję.

  • Jeśli typem wyniku funkcji jest void, to funkcja może nie zawierać żadnej instrukcji return, wówczas koniec działania funkcji następuje po dotarciu sterowania do końca treści funkcji.

4.2.5. Instrukcja goto

  • Nie używamy tej instrukcji.

4.3. Pozostałe konstrukcje

4.3.1. Instrukcja deklaracji

. instrukcja_deklaracji:
. . blok_deklaracji
  • Wprowadza do bloku nowy identyfikator.

  • Ten identyfikator może przesłonić jakiś identyfikator z bloku zewnętrznego (do końca tego bloku).

  • Inicjowanie zmiennych (auto i register) odbywa się przy każdym wykonaniu ich instrukcji deklaracji. Zmienne te giną przy wychodzeniu z bloku.

4.3.2. Deklaracje

  • Każdy identyfikator musi być najpierw zadeklarowany.

  • Deklaracja określa typ, może też określać wartość początkową.

  • Zwykle deklaracja jest też definicją (przydziela pamięć zmiennej, definiuje treść funkcji).

  • Deklarując nazwę w C++ można podać specyfikator klasy pamięci:

    • auto prawie nigdy nie stosowany jawnie (bo jet przyjmowany domyślnie),

    • register tyle co auto, z dodatkowym wskazaniem dla kompilatora, że deklarowana zmienna będzie często używana,

    • static to słowo ma kilka różnych znaczeń w C++, tu oznacza, że identyfikator będzie zachowywał swoją wartość pomiędzy kolejnymi wejściami do bloku, w którym jest zadeklarowany,

    • extern oznacza, że identyfikator pochodzi z innego pliku, czyli w tym miejscu jest tylko jego deklaracja (żeby kompilator znał np. jego typ, a definicja (czyli miejsce gdzie została przydzielona pamięć) jest gdzie indziej.

4.3.3. Komentarze

W C++ mamy dwa rodzaje komentarzy:

  • Komentarze jednowierszowe zaczynające się od //.

  • Komentarze (być może) wielowierszowe, zaczynające się od /* i kończące */. Te komentarze nie mogą się zagnieżdżać.

4.4. Literały

4.4.1. Literały całkowite

  • Dziesiętne (123543). Ich typem jest pierwszy z wymienionych typów, w którym dają się reprezentować: int, long int, unsigned long int (czyli nigdy nie są typu unsigned int!).

  • Szesnastkowe (0x3f, 0x4A). Ich typem jest pierwszy z wymienionych typów, w którym dają się reprezentować: int, unsigned int, long int, unsigned long int.

  • Ósemkowe (0773). Typ j.w.

  • Przyrostki U, u, L i l do jawnego zapisywania stałych bez znaku i stałych long, przy czym znów jest wybierany najmniejszy typ (zgodny z przyrostkiem), w którym dana wartość się mieści.

  • Stała 0 jest typu int, ale można jej używać jako stałej (oznaczającej pusty wskaźnik) dowolnego typu wskaźnikowego,

4.4.2. Literały zmiennopozycyjne

  • Mają typ double (o ile nie zmienia tego przyrostek)

  • 1.23, 12.223e3, -35E-11,

  • Przyrostek f, F (float), l, L (long double),

4.4.3. Literały znakowe (typu char)

  • Znak umieszczony w apostrofach ('a'),

  • Niektóre znaki są opisane sekwencjami dwu znaków zaczynającymi się od \. Takich sekwencji jest 13, oto niektóre z nich:

    • \n (nowy wiersz),

    • \\ (lewy ukośnik),

    • \' (apostrof),

    • \ooo (znak o ósemkowym kodzie ooo, można podać od jednej do trzech cyfr ósemkowych),

    • \xhhh (znak o szesnastkowym kodzie hhh, można podać jedną lub więcej cyfr szesnastkowych),

    Każda z tych sekwencji opisuje pojedynczy znak!

4.4.4. Literały napisowe (typu const char[n])

  • Ciąg znaków ujęty w cudzysłów (”ala\n”).

  • Zakończony znakiem '\0'.

  • Musi się zawierać w jednym wierszu, ale …

  • … sąsiednie stałe napisowe (nawet z różnych wierszy) są łączone.

4.4.5. Literały logiczne (typu bool)

  • true,

  • false.

4.4.6. Identyfikatory

  • Identyfikator (nazwa) to ciąg liter i cyfr zaczynający się od litery (_ traktujemy jako literę),

  • Rozróżnia się duże i małe litery.

  • Długość nazwy nie jest ograniczona przez C++ (może być ograniczona przez implementację),

  • Słowo kluczowe C++ nie może być nazwą,

  • Nazw zaczynających się od _ i dużej litery, bądź zawierających __ (podwójne podkreślenie) nie należy definiować samemu (są zarezerwowane dla implementacji i standardowych bibliotek).

  • Nazwa to maksymalny ciąg liter i cyfr.

Treść automatycznie generowana z plików źródłowych LaTeXa za pomocą oprogramowania wykorzystującego LaTeXML.

Projekt współfinansowany przez Unię Europejską w ramach Europejskiego Funduszu Społecznego.

Projekt współfinansowany przez Ministerstwo Nauki i Szkolnictwa Wyższego i przez Uniwersytet Warszawski.