6.1 Definiowanie złożonych typów danych

Przez typy „złożone” rozumiemy takie, o których można powiedzieć, że są złożeniami typów różnego rodzaju: na przykład tablica wskaźników, referencja do tablicy albo wskaźnik do wskaźnika do tablicy wskaźników itp. Określanie i zrozumienie tego rodzaju typów sprawia często wiele kłopotu nawet osobom dobrze znającym C/C++.

Definiowanie typów pochodnych może czasem prowadzić do skomplikowanych wyrażeń. Czym na przykład są x, y, z, f po następujących deklaracjach/definicjach:

       int tab[] = {1,2,3};
       int (&x)[3] = tab;
       int *y[3] = {tab,tab,tab};
       int *(&z)[3] = y;
       int &(*f)(int*,int&);
Linia 1 określa oczywiście, że tab jest typu 'trzyelementowa tablica zmiennych typu int', co w wyrażeniach w sposób naturalny konwertowane jest do typu int*. Pozostałe deklaracje mogą sprawiać kłopoty. Ogólne zasady są następujące:
  1. zaczynamy od nazwy deklarowanej zmiennej,
  2. patrzymy w prawo: jeśli jest tam nawias otwierający okrągły, to będzie to funkcja (odczytujemy liczbę i typ parametrów); jeśli będzie tam nawias otwierający kwadratowy, to będzie to tablica (odczytujemy rozmiar),
  3. jeśli po prawej stronie nic nie ma lub jest nawias okrągły zamykający, to przechodzimy na lewo i czytamy następne elementy kolejno od prawej do lewej aż do końca lub do napotkania nawiasu otwierającego,
  4. jeśli napotkaliśmy nawias okrągły otwierający, to wychodzimy z całego tego nawiasu i kontynuujemy znów od jego prawej strony,
  5. gwiazdkę ('*') czytamy jest wskaźnikiem do,
  6. ampersand ('&') czytamy jest referencją do,
  7. po odczytaniu liczby i typu parametrów funkcji dalszy ciąg procedury określa typ zwracany tej funkcji,
  8. po odczytaniu wymiaru tablicy dalszy ciąg procedury określa typ elementów tablicy.
Rozpatrzmy po kolei linijki naszego przykładu:


       int (&x)[3] = tab;
x jest: Ponieważ x jest referencją, musieliśmy od razu dokonać inicjalizacji — widać, że jest ona prawidłowa, bo tab właśnie jest trzyelementową tablicą int-ów.


       int *y[3] = {tab,tab,tab};
y jest: Tu nie musieliśmy od razu dokonywać inicjalizacji, ale ta której dokonaliśmy jest prawidłowa, bo tab standardowo jest konwertowana do typu int*. W tym przykładzie wszystkie elementy tablicy y wskazują na tę samą liczbę całkowitą, a mianowicie na pierwszy element tablicy tab.


       int *(&z)[3] = y;
z jest: Tu znów musieliśmy od razu dokonać inicjalizacji — do jej wykonania użyliśmy tablicy y z poprzedniego przykładu.


       int &(*f)(int*,int&);
f jest: O wskaźnikach do funkcji będziemy jeszcze mówić szczegółowo w rozdziale im poświęconym . Na razie przykład programu z powyższymi deklaracjami:


P30: dekl.cpp     Złożone deklaracje

      1.  #include <iostream>
      2.  
      3.  int& fun(int *k, int &m) {
      4.      return *k > m ? *k : m;
      5.  }
      6.  
      7.  int main() {
      8.      using std::cout; using std::endl;
      9.      int tab[]{1,2,3};
     10.  
     11.      int (&x)[3] = tab;
     12.      cout << "x[2]    = " << x[2]    << endl;
     13.  
     14.      int *y[3] = {tab,tab,tab};
     15.      cout << "y[2][0] = " << y[2][0] << endl;
     16.  
     17.      int *(&z)[3] = y;
     18.      cout << "z[2][0] = " << z[2][0] << endl;
     19.  
     20.      int &(*f)(int*,int&);
     21.      f = fun;
     22.      int v1 =  f(&tab[1], tab[2]);            
     23.      int v2 = (*f)(&tab[1], tab[2]);          
     24.      cout << "v1      = " << v1      << endl;
     25.      cout << "v2      = " << v2      << endl;
     26.  }

W świetle powyższych rozważań powinien być zrozumiały wynik

    x[2]    = 3
    y[2][0] = 1
    z[2][0] = 1
    v1      = 3
    v2      = 3
Zauważmy, że obie formy wywołania funkcji, z linii , są równoważne: f jest wskaźnikiem do funkcji, ale przy wywołaniu można, choć nie trzeba, używać operatora dereferencji (więcej szczegółów w rozdziale o wskaźnikach funkcyjnych ).

T.R. Werner, 23 lutego 2019; 23:59