Struktury

Z RNO-Wiki

Spis treści

Podstawy

Jak już wiemy, w języku C/C++ zdefiniowanych jest wiele typów danych, np. int, long long, char, string, void, float, double. Jeśli jest to dla nas zbyt mało, to możemy zdefiniować swój własny typ złożony. Nazywamy go strukturą. Na przykład typ będący parą dwóch zmiennych int-a i string-a, w języku C++ definiujemy następująco:

struct para
  {
    int n;
    string s;
  };

Teraz możemy deklarować zmienne typu para (nazwę można było wpisać prawie dowolną) w następujący sposób:

 para X;

Mamy wówczas zmienną X.

Pola struktury

Do pól struktury możemy dostać się po kropce, mianowicie (kontynuujemy poprzedni przykład)

int a;
  a = X.n;
  cin >> X.s; // wczytywanie string-a ze standardowego wejścia

Możemy także napisać własną funkcję, która zwraca zmienną typu para, np.

para funkcja(string argument)
  {
    para wynik;
    wynik.n = argument.length();
    wynik.s = argument;
    return wynik;     // ważne jest, że wynik jest typu 'para'
  }

Metody

Struktura może mieć nie tylko pola (zmienne pewnego typu); można także dla struktry zdefiniować tzw. 'metody, czyli funkcje obsługujące interfejs struktury. Na przykład

struct punkt
  {
    int x; // to jest pole
    int y; // to jest pole
 
    bool czyLezyNaOsi() // metoda, a wiec funkcja, ktora nie pobiera zadnych argumentow; odpowiada czy punkt lezy na jednej z osi OX lub OY
    {
       if (x==0) return true;
       if (y==0) return true;
       return false;
    }
 
    int ktoraCwiartka() // funkcja zwraca numer cwiartki, do ktorej nalezy punkt
    {
       if (czyLezyNaOsi()) return 0;
       if (x>0 && y>0) return 1;
       if (x<0 && y>0) return 2;
       if (x<0 && y<0) return 3;
       if (x>0 && y<0) return 4;
    }
  }; // koniec definicji struktury musi zawierać średnik

Teraz możemy poszaleć:

punkt tablica[100]; // deklarujemy tablice struktur punkt
  for (int i=0; i<100; i++) // wczytujemy 100 punktow
    cin >> tablica[i].x >> tablica[i].y;
 
  int ile[5] = { 0,0,0,0,0 }; // deklarujemy tablice pięciu zer
  for (int i=0; i<100; i++)
  {
    int cw = tablica[i].ktoraCwiartka(); // do metody struktury także dostajemy się po kropce
    ile[cw] = ile[cw]+1; // zwiekszamy liczbe punktów należących do ćwiartki nr 'cw'
  }
 
  cout << " W cwiartce nr 1 jest " << ile[1] << " punktow." << endl;
  cout << " W cwiartce nr 2 jest " << ile[2] << " punktow." << endl;
  cout << " W cwiartce nr 3 jest " << ile[3] << " punktow." << endl;
  cout << " W cwiartce nr 4 jest " << ile[4] << " punktow." << endl;

Po co to? Otóz metody są bardzo przydatne. Mogą coś dla nas zrobić tak jak funkcje. Należy rozumieć, że tutaj każda zmienna (struktura) ma swoją własną funkcję. Jeśli w definicji metody używamy zmiennych o nazwach takich, jak jej pola to wówczas odwołujemy się do pól struktury. Metody mogą więc nieźle namieszać w strukturze. Kolejny przykład:

struct piwo
  {
    int ileProcent;
    int ileChmielu;
    bool czyCiemne;
    bool czyDobre;
 
    void dobre(bool czy)
    {
      czyDobre = czy;
    }
  };
 
  int main(void)
  {
    piwo P;
    p.dobre(true); // używamy metody dobre, aby ustawić pole 'czyDobre' na true;
    // ten sam efekt moglibyśmy uzyskać pisząc
    p.czyDobre = true; 
  }

Metody są fajne. Mogą dużo zrobić, a my dzięki ich wywołaniom możemy łatwo nimi zarządzać. Zauważ, że np. string jest czymś podobnym do struktury. Ciężko jest dostać się do jego pól, ale za to ma wiele metod, np: length(), size(), erase(...), insert(...). Jeśli chcesz poczytać więcej o stringach, to zapraszam tutaj.

Parametry const

W strukturach można zagwarantować, że metoda nic nie namiesza. Robi się to dopisując const pzed definicją metody. Zobacz:

struct wektor
  {
    int x,y;
 
    float length() const         // dopisanie const powoduje, że kompilator nie dopuści np do czegoś takiego : x = 5;
                                 // po prostu nie wolno nam zmieniac wartości wszystkich pól struktury!
    {
      return sqrt( x*x + y*y );
    }
  };

Konstruktory

Konstruktorem struktury jest metoda wywoływana przy kontrukcji struktury (deklaracji). Na przykład pisząc

punkt Q;

Tworzy się punkt i wywołuje się domyślny konstruktor. Czasem chciałoby się powiedzieć jakie mają dostać wartości poszególne pola strktury. Dlatego w tym celu należy napisać własny konstruktor (niekoniecznie domyślny):

struct pralka
  {
    int ileObrotow;
    bool czyNowa;
    int cena;
 
    pralka() // konstruktor domyślny
    {
      czyNowa = true;
      ileObrotow = 0;
      cena = 0;
    }
 
    pralka(int obroty) // knstruktor pobierajacy jednego int-a
    {
      czyNowa = true;
      ileObrotow = obroty;
    }
  };

Teraz w funkcji np. main mozemy konstruować zmienne typu 'pralka' za pomoca naszych konstruktorów:

pralka P; //tutaj wywołuje sie domyślny konstruktor.
  pralka T(800); // teraz T.ileObrotow == 800  :-)
 
  pralka tablica[1000]; // konstruktor domyślny wywołuje się aż 1000 razy

Operatory

Mam nadzieję, że spodobały Ci się nowe możliwości, jakie dają Ci struktury w C/C++. Ale to nie wszystko. Przypomnij sobie co można było robić więcej ze stringami :-). Można było zrobić coś takiego

string A = "ala ";
  string B = "ma kota";
  string C = A+B+A+A+B;

Czy to nie jest super? Jacyś kolesie napisali operator dodawania dla stringów. Czemu by nie napisać własny operator dla swojej struktury? Robi się to tak:

struct wektor
  {
    int x, y; // wspołrzędne wektora
    wektor(int xx, int yy) { x = xx; y = yy; } // wygodny konstruktor, np.  wektor W(4,5);
    wektor() { x = 0; y = 0; }                 // domyślny konstruktor
 
    wektor operator* (int skalar) // operator mnożenia wektor przez liczbę
    {
      return wektor(x*skalar, y*skalar); // zwracamy to, co skonstruuje konstruktor
    }
 
    wektor operator+ (wektor W) // operator dodawania wektora W do wektora
    {
      wektor wynik; // nie potrzeba, ale mozemy skonstruowac zmienną, którą potem zwrócimy jako wynik funkcji
      wynik.x =  x + W.x;
      wynik.y =  y + W.y;
      return wynik;
    }
  }

Zobaczmy, jak działa to w praktyce:

wektor A(1,2);
  wektor B(3,4);
 
  wektor C = A+B;
  wektor D = A*7; // niestety nie można pisać  7*A, bo int-y nie mają operatora '*' mnożenia przez wektor.

Operatory porównywania

Znowy odwołam się do stringów, bo to naprawdę świetnie napisana struktura. Nie wiem, czy wiecie, ale stringi można porównywać

string A = "ala ma", B = "aleksander";
  if (A < B) { ... } // sprawdzenie, czy A jest mniejszy leksykograficznie niż B

To naprawdę wygodne narzędzie No więc do dzieła. Napiszmy własny operator<

struct wektor
  {
    int x, y; // wspołrzędne wektora
    wektor(int xx, int yy) { x = xx; y = yy; } // wygodny konstruktor, np.  wektor W(4,5);
 
    bool operator< (wektor W)   // "porównanie dwóch wektorych według porządku na współrzędnych (leksykograficznie)
    {
      if (x<W.x) return true;
      if (x>W.x) return false;
      return y<W.y;  // tak też można; w końcu y<W.y ma jakąś wartość logiczną, którą zwracamy tutaj jako wynik operatora<
    }
  }

Sortowanie

Szerzej o sortowaniu mówimy w arytkule Sortowanie. Tutaj poświęcimy uwage, jak napisać operator porównywania, aby móc sortować tablice naszych własnych struktur.

Ten przykład wyjaśnia jak należy pisać operator mniejszości, aby móc potem sortować za pomocą funkcji sort z bilbioteki STL:

struct wektor
  {
    int x, y; // wspołrzędne wektora
    wektor(int xx, int yy) { x = xx; y = yy; } // wygodny konstruktor, np.  wektor W(4,5);
    wektor() { x = 0; y = 0; }                 // domyślny konstruktor
 
    wektor operator* (int skalar) // operator mnożenia wektor przez liczbę
    {
      return wektor(x*skalar, y*skalar); // zwracamy to, co skonstruuje konstruktor
    }
 
    wektor operator+ (wektor W) // operator dodawania wektora W do wektora
    {
      wektor wynik; // nie potrzeba, ale mozemy skonstruowac zmienną, którą potem zwrócimy jako wynik funkcji
      wynik.x =  x + W.x;
      wynik.y =  y + W.y;
      return wynik;
    }
 
    bool operator< (const wektor &W) const  // takiego operatora wymaga funkcja sort z biblioteki algorithms
    {
      if (x<W.x) return true;
      if (x>W.x) return false;
      return y<W.y;
    }
  };
 
  int main void()
  {
    wektor tab[1000];
    for (int i=0; i<1000; i++) cin >> tab[i].x >> tab[i].y; // wczytywanie wektorów (każdy dany jako dwie liczby  int)
 
    sort(tab, tab+1000); // sortowanie :-) (wszystko układa się od najmniejszego do największego za pomocą operatora <
    return 0;
  }
Osobiste