Struktury i klasy w języku C++

Struktury

W języku C++ możemy tworzyć dowolnie skomplikowane struktury danych za pomocą słowa kluczowego struct.

 

struct nazwa_struktury
{
    pole danych;
    pole danych;
    ...
};

 

pole danych określa element składowy struktury. Definiujemy go podobnie do zmiennej:

 

typ nazwa_pola;

 

Po zdefiniowaniu struktury możemy na jej podstawie tworzyć zmienne strukturalne jak każde inne zmienne. Typem będzie nazwa struktury.

 

nazwa_struktury  nazwa_zmiennej;

 

Jeśli zmienna strukturalna jest zmienną statyczną, to dostęp do pól danych uzyskujemy za pomocą operatora kropka.

 

nazwa_zmiennej.nazwa_pola

 

Poniżej mamy prosty przykład struktury, która jest stosem.

 

// Struktura - stos
// (C)2011 I LO w Tarnowie
// KOŁO INFORMATYCZNE
//------------------------

#include <iostream>
#include <cstdlib>
#include <time.h>

using namespace std;

const int LIMIT = 5; // długość stosu

// Tutaj deklarujemy typ struktury

struct stack
{
    int w,s[LIMIT];
};

// Funkcja zapisuje na stos

void Zapisz(stack & st, int x)
{
    if(st.w < LIMIT) st.s[st.w++] = x;
    else cout << "STOS PELNY!!!" << endl;
}

// funkcja odczytuje ze stosu

int Czytaj(stack & st)
{
    if(st.w) return st.s[--st.w];
    cout << "STOS PUSTY!!!" << endl;
    return -1;
}

// Program główny

int main()
{
  stack stos;
  int i,x;

  srand(time(NULL)); // inicjujemy generator pseudolosowy

  stos.w = 0;        // inicjujemy wskaźnik stosu

  cout << "ZAPIS 7 LICZB NA STOS\n";

  for(i = 1; i <= 7; i++)
  {
     x = rand();
     cout << x << endl;
     Zapisz(stos,x);
  }

  cout << "\nODCZYT 7 LICZB ZE STOSU\n";

  for(i = 1; i <= 7; i++)
  {
     x = Czytaj(stos);
     cout << x << endl;
  }

  return 0;
}
ZAPIS 7 LICZB NA STOS
24789
30828
19286
5754
30359
10767
STOS PELNY!!!
2799
STOS PELNY!!!

ODCZYT 7 LICZB ZE STOSU
30359
5754
19286
30828
24789
STOS PUSTY!!!
-1
STOS PUSTY!!!
-1

 

Strukturę możemy utworzyć jako obiekt dynamiczny, podobnie jak tablicę. W tym celu tworzymy wskaźnik do struktury:

 

nazwa_struktury * wskaźnik_struktury;

 

W pamięci rezerwujemy odpowiedni blok pamięci i adres tego bloku umieszczamy we wskaźniku:

 

wskaźnik_struktury = new nazwa_struktury;

 

Dostęp do pól danych uzyskujemy za pomocą operatora ->.

 

wskaźnik_struktury -> nazwa_pola

 

Gdy struktura przestanie być potrzebna, usuwamy ją z pamięci:

 

delete wskaźnik_struktury;

 

Poniżej nasz program ze stosem przerobiony na strukturę dynamiczną:

 

// Struktura dynamiczna - stos
// (C)2011 I LO w Tarnowie
// KOŁO INFORMATYCZNE
//------------------------

#include <iostream>
#include <cstdlib>
#include <time.h>

using namespace std;

const int LIMIT = 5; // długość stosu

// Tutaj deklarujemy typ struktury

struct stack
{
    int w,s[LIMIT];
};

// Funkcja zapisuje na stos

void Zapisz(stack * st, int x)
{
    if(st -> w < LIMIT) st -> s[st -> w++] = x;
    else cout << "STOS PELNY!!!" << endl;
}

// Funkcja odczytuje ze stosu

int Czytaj(stack * st)
{
    if(st -> w) return st -> s[--st -> w];
    cout << "STOS PUSTY!!!" << endl;
    return -1;
}

// Program główny

int main()
{
  stack * stos; // wskaźnik
  int i,x;

  stos = new stack;   // tworzymy strukturę dynamiczną

  srand(time(NULL));  // inicjujemy generator pseudolosowy

  stos -> w = 0;      // inicjujemy wskaźnik stosu

  cout << "ZAPIS 7 LICZB NA STOS\n";

  for(i = 1; i <= 7; i++)
  {
     x = rand();
     cout << x << endl;
     Zapisz(stos,x);
  }

  cout << "\nODCZYT 7 LICZB ZE STOSU\n";

  for(i = 1; i <= 7; i++)
  {
     x = Czytaj(stos);
     cout << x << endl;
  }

  return 0;
}

 

Zwróć uwagę, iż funkcje obsługujące operacje na strukturze wymagają przekazania parametru będącego strukturą. W pierwszym przykładzie strukturę przekazywaliśmy przez referencję, dzięki czemu funkcje miały bezpośredni dostęp do pól struktury. W drugim przykładzie przekazywany był wskaźnik do struktury. Chociaż technicznie funkcje te wyglądają inaczej, to w rzeczywistości w obu przypadkach kompilator utworzył identyczne funkcje. W pierwszym referencja jakby ukryła przed nami fakt, iż faktycznie funkcja otrzymała wskaźnik do struktury. W drugim przypadku wskaźnik ten przekazaliśmy jawnie.

 

Klasy

Klasa jest obiektem, z którym, oprócz danych, jak w strukturze, skojarzono funkcje operujące na tych danych. Funkcje klasy nazywamy funkcjami składowymi (ang. member functions). Stanowią one integralną część definicji klasy.

Powodem wprowadzenia klas było w pewnym sensie uproszczenie programowania. Wyobraźmy sobie telewizor. Chcę oglądać jakiś program - czy muszę koniecznie znać w najdrobniejszych szczegółach jego budowę? Oczywiście nie, wystarczy znać funkcje klawiszy sterujących, a to co w środku, to dla inżynierów. Podobnie jest z klasami. Klasa udostępnia programowi tzw. interfejs, poprzez który program może się komunikować z klasą. Cechy implementacyjne mogą być ukrywane. W tym celu definicja klasy zawiera tzw. część publiczną - dostępną dla programu, oraz część prywatną - na użytek samej klasy, do której zewnętrzne funkcje nie posiadają dostępu.

Oprócz pól danych klasa może zawierać funkcje składowe, będące jej integralną częścią. Funkcje te mogą być publiczne - wtedy program może z nich korzystać, lub prywatne - na potrzeby wewnętrzne klasy.

Klasa może zawierać specjalne funkcje zwane konstruktorami i destruktorami. Funkcja konstruktor ma taką samą nazwę jak nazwa klasy. Jej zadaniem jest odpowiednie zainicjowanie pól danych w czasie tworzenia klasy - w przypadku struktury inicjowaniem pól musiał się zajmować program. Konstruktorów może być kilka, ale muszą się różnić parametrami. Destruktor jest funkcją o nazwie klasy poprzedzoną tyldą ~. Zadaniem destruktora jest posprzątanie po klasie, gdy będzie ona usuwana z pamięci. Jest to istotne, jeśli klasa sama zawiera dynamiczne struktury - wtedy destruktor zwalnia wcześniej przydzieloną na nie pamięć. Konstruktor i destruktor jest wywoływany automatycznie, programista nic nie musi specjalnie robić w tym celu. Jeśli w klasie nie zdefiniujemy swojego konstruktora lub destruktora, to zostanie utworzony standardowy konstruktor i destruktor.

Definicja klasy wygląda następująco:

 

class nazwa_klasy
{
   public:                // deklaracja pól i funkcji publicznych
       pole_danych;       // pola dostępne dla programu
       pole_danych;
       ...
       nazwa_klasy();     // konstruktor
       ~nazwa_klasy()     // destruktor

       funkcja_składowa;  // funkcje dostępne dla programu
       funkcja_składowa;
       ...
   private:               // deklaracja pól i funkcji prywatnych
       pole_danych;       // pola dostępne tylko dla funkcji składowych klasy
       pole_danych;
       ...
       funkcja_składowa;  // funkcje dostępne tylko wewnątrz klasy
       funkcja_składowa;
};

 

Jeśli funkcje klasy są krótkie, to można je zdefiniować bezpośrednio w definicji klasy. W przypadku dłuższych funkcji definiujemy je na zewnątrz w sposób następujący:

Konstruktor - funkcja nic nie zwraca, brak w niej polecenia return:

 

nazwa_klasy::nazwa_klasy(ewentualne_parametry)
{
   treść konstruktora
}

 

Destruktor - funkcja nic nie zwraca, brak w niej polecenia return:

 

nazwa_klasy::~nazwa_klasy()
{
   treść destruktora
}

 

Funkcja składowa:

 

typ_wyniku nazwa_klasy::funkcja_składowa(ewentualne_parametry)
{
   treść funkcji składowej;
   return wynik;
}

 

Funkcje składowe posiadają bezpośredni dostęp do danych klasy - jawnie nie musimy do nich przekazywać parametru będącego klasą. Niejawnie taki parametr jest zawsze przekazywany - wewnątrz każdej funkcji składowej nazywa się on this, lecz nie ma go na liście parametrów. Poniżej mamy nasz program ze stosem przerobiony na klasę. Sam stos jest tablicą dynamiczną, której rozmiar przekazujemy w trakcie tworzenia zmiennej - parametr ten trafia do konstruktora klasy.

 

// Klasa - stos
// (C)2011 I LO w Tarnowie
// KOŁO INFORMATYCZNE
//------------------------

#include <iostream>
#include <cstdlib>
#include <time.h>

using namespace std;

// Tutaj definiujemy klasę

class stack
{
    public:               // interfejs klasy dla programu

      stack(int n);       // konstruktor stosu n-elementowego
      ~stack();           // destruktor
      void Zapisz(int x); // funkcja składowa
      int  Czytaj();      // funkcja składowa

    private:              // elementy prywatne do użytku wewnętrznego

      int w, * s, limit;
};

// Definicja konstruktora

stack::stack(int n)
{
    limit = n;            // zapamiętujemy rozmiar stosu
    s = new int[n];       // tworzymy tablicę dynamiczną
    w = 0;                // zerujemy wskaźnik stosu
    cout << "KONSTRUKTOR - STOS UTWORZONY\n";
}

// Definicja destruktora

stack::~stack()
{
    delete [] s;          // usuwamy tablicę dynamiczną
    cout << "DESTRUKTOR - STOS USUNIETY\n";
}

// Funkcja zapisuje na stos

void stack::Zapisz(int x)
{
    if(w < limit) s[w++] = x;
    else cout << "STOS PELNY!!!" << endl;
}

// Funkcja odczytuje ze stosu

int stack::Czytaj()
{
    if(w) return s[--w];
    cout << "STOS PUSTY!!!" << endl;
    return -1;
}

// Program główny

int main()
{
  stack stos(5);      // zmienna klasy, stos o rozmiarze 5
  int i,x;

  srand(time(NULL));  // inicjujemy generator pseudolosowy

  cout << "ZAPIS 7 LICZB NA STOS\n";

  for(i = 1; i <= 7; i++)
  {
     x = rand();
     cout << x << endl;
     stos.Zapisz(x);
  }

  cout << "\nODCZYT 7 LICZB ZE STOSU\n";

  for(i = 1; i <= 7; i++)
  {
     x = stos.Czytaj();
     cout << x << endl;
  }

  return 0;
}

 

Klasy również mogą być tworzone dynamicznie. Procedura jest następująca:

 

nazwa_klasy * wskaźnik; // zmienna typu wskaźnik do klasy

 

W pamięci rezerwujemy odpowiedni blok pamięci i adres tego bloku umieszczamy we wskaźniku:

 

wskaźnik = new nazwa_klasy;

 

Dostęp do pól danych uzyskujemy za pomocą operatora ->.

 

wskaźnik -> nazwa_pola

 

Dostęp do funkcji składowych również uzyskujemy za pomocą operatora ->:

 

wskaźnik -> funkcja_składowa(parametry);

 

Gdy klasa dynamiczna przestanie być potrzebna, usuwamy ją z pamięci:

 

delete wskaźnik;

 

Poniżej nasz program z klasą dynamiczną:

 

// Klasa dynamiczna - stos
// (C)2011 I LO w Tarnowie
// KOŁO INFORMATYCZNE
//------------------------

#include <iostream>
#include <cstdlib>
#include <time.h>

using namespace std;

// Tutaj definiujemy klasę

class stack
{
    public:               // interfejs klasy dla programu

      stack(int n);       // konstruktor stosu n-elementowego
      ~stack();           // destruktor
      void Zapisz(int x); // funkcja składowa
      int  Czytaj();      // funkcja składowa

    private:              // elementy prywatne do użytku wewnętrznego

      int w, * s, limit;
};

// Definicja konstruktora

stack::stack(int n)
{
    limit = n;            // zapamiętujemy rozmiar stosu
    s = new int[n];       // tworzymy tablicę dynamiczną
    w = 0;                // zerujemy wskaźnik stosu
    cout << "KONSTRUKTOR - STOS UTWORZONY\n";
}

// Definicja destruktora

stack::~stack()
{
    delete [] s;          // usuwamy tablicę dynamiczną
    cout << "DESTRUKTOR - STOS USUNIETY\n";
}

// Funkcja zapisuje na stos

void stack::Zapisz(int x)
{
    if(w < limit) s[w++] = x;
    else cout << "STOS PELNY!!!" << endl;
}

// Funkcja odczytuje ze stosu

int stack::Czytaj()
{
    if(w) return s[--w];
    cout << "STOS PUSTY!!!" << endl;
    return -1;
}

// Program główny

int main()
{
  stack * stos;         // wskaźnik do klasy
  int i,x;

  stos = new stack(5);  // tworzymy klasę dynamicznie

  srand(time(NULL));    // inicjujemy generator pseudolosowy

  cout << "ZAPIS 7 LICZB NA STOS\n";

  for(i = 1; i <= 7; i++)
  {
     x = rand();
     cout << x << endl;
     stos -> Zapisz(x); // wywołanie funkcji składowej
  }

  cout << "\nODCZYT 7 LICZB ZE STOSU\n";

  for(i = 1; i <= 7; i++)
  {
     x = stos -> Czytaj(); // wywołanie funkcji składowej
     cout << x << endl;
  }

  delete stos;         // usunięcie klasy dynamicznej z pamięci

  return 0;
}

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.