Programowanie zaawansowane

Adam Boduch

Zaawansowane aspekty programowania w C++ by Asmie (c) 2001

Spis treści :

  1. Wstęp

  2. Co to są klasy i co mogę z nimi zrobić ?

  3. Co to jest dziedziczenie ? Jak to wykorzystać ?

  4. Overloading funkcji

  5. Overloading operatorów - podstawy

  6. Tworzenie list łączonych

  7. Zakończenie

  8. Wstep

Yo all ! Witajcie w pierwszej czesci mojego tekstu dotyczacego zaawansowanego programowania w C++. Skoro pisze, ze to jest pierwsza, to pewnie domyslacie sie, ze beda nastepne :) Jak dobrze pojdzie to wydam jeszcze kilka takich odcinkow, w ktorych bedzie m.in. programowanie przy uzyciu gniazdek siecowych dla systemow Unix, kurs programowania w C++ dla Windows, OWL, pisanie skryptow CGI w C++ i wiele innych. Kontakt ze mna jest podany na koncu tego tekstu. Prosze o przesylaniu kazdych uwag, bez wzlgledu na to jakie sa ;)

  1. Co to sa klasy i co moge z nimi zrobic ?

Kazdy zna na pewno pojecie struktury. Deklarujemy ja w ten sposob:

struct nazwa_struktury 
{
  skladniki ...
  ....
} nazwa_obiektu;

Jak juz zapewne wiecie nazwe struktury mozna pominac. Nazwa obiektu jest to nazwa zmiennej jakiegos typu, a w naszym przykladzie jest to typ nazwa_struktury. Czyli :

struct urzadzenie 
{
  float cena;
  int pobor_mocy;
} komputer;

to mozemy powiedziec, ze komputer jest obiektem struktury urzadzenie. Obiekt komputer mozemy takze zadeklarowac:

struct urzadzenie komputer;

I takze to oznacza, ze komputer jest obiektem struktury urzadzenie. A teraz wyobrazmy sobie, ze chcemy rozbudowac jakas strukture o dodatkowe funkcje np.:

#include <stdio.h>
struct clip 
{
  private:
  int wartosc;
  public:
  void ustaw_wartosc(int liczba);
  int oddaj_wartosc(void);
};

void clip::ustaw_wartosc(int liczba)
{
  wartosc = liczba;
}

int oddaj_wartosc(void)
{
  return (wartosc);
}

int main(void)
{
  struct clip schowek1, schowek2;

  schowek1.ustaw_wartosc(20);
  schowek2.ustaw_wartosc(50);
  printf("\\nW schowku pierwszym jest: %d .", schowek1.oddaj_wartosc());
  printf("\\nW schowku drugim jest: %d .",schowek2.oddaj_wartosc());
  return 0;
}

W powyzszym programie zastosowalem pewna hermetyzacje danych. Do liczby wartosc maja dostep tylko dwie funkcje: oddaj_wartosc i ustaw_wartosc. Jakiekolwiek dzialanie z funkcji main jest niemozlie do wykonania na tej liczbie bez uzycia tych dwoch funkcji. Np.: przy probie dostepu:

printf("%d", schowek1.wartosc);

od razu kompilator wykryje blad i stwierdzi, ze funkcja main nie jest autoryzowana do dostepu do danych znajdujacych sie wewnatrz struktury schowek1. I tutaj mam dla was zaskoczenie, poniewaz to co przed chwila stworzyliscie praktycznie jest juz klasa :O. Wystarczy zmienic tylko slowko struct na class i pominac struct przy definiowaniu obiektow schowek1 i schowek2. Teraz clip jest klasa objektow, a schowek1 jest obiektem klasy clip. Ogolnie rzecz biorac klasy sluza do tworzenia formalnego typu danych. W przypadku klas wiadomo jednak, ze bedzie to dosc zlozony typ danych, zawierajacy takze funkcje. W klasach funkcje nazywane sa metodami, poniewaz sa one metodami dostepu do prywatnych czesc klasy. Jezeli nie napiszesz inaczej wszystkie zadeklarowane pola klasy maja status private, czyli po zadeklarowaniu takiej klasy:

class Klasa 
{
  int w1;
  float w2;
  int func(float);
};

nic z nia nie mozesz zrobic, poniewaz wszystkie elementy tej klasy sa dla niej prywatne, a wiec niedostepne z zewnatrz. Taka klasa na nikomu nic sie nie przyda, wiec nie ma sensu robic czegos takiego. Kolejna wazna rzecza dotyczaca klas jest to, ze nie mozemy w jej wnetrzu zainicjowac zadnych danych, czyli deklaracja:

class Klasa 
{
  int w1 = 0; // ZLE !!
  float w2 = 3.14; // ZLE !!
};

jest calkowicie zla. Jezeli chcemy nadac poczatkowe wartosci danym, to musimy skorzystac z konstruktora, o ktorym pozniej. Teraz pokaze wam jak tworzyc obiekt oraz odwolywac sie do niego. Zacznijmy od deklaracji klasy:

class Klasa 
{
  public:
  int liczba_uczniow;
  char **nazwiska;
  char *wychowawczyni;
};

Oukej, mamy klase, a teraz trzeba zadeklarowac obiekt tej klasy:

Klasa _1A;

Zadeklarowalismy obiekt _1A (przypominam, ze zmienna nie moze rozpoczynac sie od liczby, dlatego wprowadzilem na poczatek znak podkreslenia) nalezacy do klasy obiektow nazywajacej sie Klasa ;)Jak sie odwolac do takiego obiektu ? Normalnie, tak samo jak to bylo w przypadku struktur:

_1A.liczba_uczniow = 23;

itp.Jezeli chcemy wprowadzic nazwisko wychowawczyni to mozemy to zrobic np.: w taki sposob:

int dl;
char bufor[50];
scanf("%s", bufor);
dl = strlen(bufor);
_1A.wychowawczyni = (char *) calloc(dl + 1, sizeof(char));
strncpy(_1A.wychowawczyni, bufor, dl);

I juz w lancuchu wychowawczyni jest nazwisko. Zastanawiasz sie pewnie po co mi ten bufor, czemu od razu nie zadeklarowac w klasie: char wychowawczyni[50]; ? Otoz, nie kazde nazwisko ma 50 znakow (malo ktore ;)) dlatego tez gdyby uzytkownik chcial wprowadzic np.: 50 klas (cala szkola ;)) to byloby to ogromne marnotrawstwo pamieci. A taki bufor tworzy sie tylko jeden (a nie 50) oraz bufor, mozna wykorzystac przy wszystkich operacjach we/wy. Wskazniki, ktore odwoluja sie do poszczegolnych zmiennych danego obiektu tworzymy tak samo jak w przypadku struktur:

Klasa *wsk;
wsk = (Klasa *) calloc(1, sizeof(Klasa));
printf("%d", wsk->liczba_uczniow);

Dobrze, znamy podstawy. Teraz pora przejsc dalej. Doszlismy do tematu konstruktorow. Co to jest ? Konstruktor jest funkcja wywolywana podczas inicjowania obiektu danej klasy. Np.:

class Klasa 
{
  private:
  int liczba_uczniow;
  public:
  void zmien_liczbe(int liczba);
  int oddaj_liczbe(void);
  Klasa(void); // To jest konstruktor !
};

Klasa::Klasa(void)
{
  liczba_uczniow = 0;
}

I oto nasz pierwsz gotowy konstruktor. Kiedy tylko w kodzie pojawi sie linia:

Klasa _1A;

zostanie automatycznie wywolany konstruktor, ktory nada 0 zmiennej liczba_uczniow. Oto cala idea konstruktorow. Z konstruktorami wiaze sie jeszcze jedno zagadnienie: przekazywanie do nich argumentow. Skoro konstruktor przypomina zwykla funkcje (ktora zawsze nic nie zwraca) to moze da sie do niego przekazac argument ? Otoz da sie !! Przyklad:

class Klasa 
{
  private:
  int liczba_uczniow;
  public:
  void zmien_liczbe(int liczba);
  int oddaj_liczbe(void);
  Klasa(int x); // To jest konstruktor !
};

Klasa::Klasa(int x)
{
  liczba_uczniow = x;
}

int main(void)
{
  Klasa _1A(25);
  ...
}

Co oznacza zapis _1A(25) ? Oznacza tyle co: utworz obiekt klasy Klasa i przekaz wartosc x do wywolywanego konstruktora. Dzieki temu, kazdy obiekt danej klasy moze miec inne dane wejsciowe. Jest jednak male ograniczenie dotyczace konstruktorow. Nie mozemy zadeklarowac tablicy zlozonej z obiektow posiadajacych konstruktory, chyba ze wszystkie konstruktory sa bezparametrowe. Skoro jezyk C++ posiada konstruktory, musi posiadac takze destruktory. Destruktor nazywa sie tak samo jak konstruktor, tyle ze jest poprzedzony znakiem tyldy (~). Jest on wywolywany, kiedy obiekt danej klasy jest likwidowany z pamieci. Do powyzszego przykladu pasuje destruktor:

Klasa::~Klasa(void)
{
  liczba_uczniow = 0;
}

chociaz zerowanie liczby uczniow jest bezcelowe, poniewaz zaraz obiekt przestanie istniec to jednak do celow dydaktycznych podany przyklad moze sie nadac :) Do klas nalezy zaliczyc takze dziedziczenie, ktore zaraz omowie w nastepnym punkcie. Jak sam zapewne zauwazyles, nie ma az tak wielkiej roznicy pomiedzy klasami, a strukturami. Jednakze, jezeli tworzysz bardziej zlozone typy danych, ktore zawieraja tez w sobie funkcje to lepiej jest uzyc do tego celu klasy niz struktury. Oprocz tego uzywajac klas, spelniamy wymogi programowania obiektowego :)

  1. Co to jest dziedziczenie ? Jak to wykorzystac ?

Dziedziczenie jest to przekazywanie pewnych cech przez rodzicow, swojemu potomstwu. Jest to bardzo wazny proces zachodzacy zarowno w naturze jak i w C++. Dziedziczenie polega na przekazywaniu cech z klasy bazowej do klasy pochodnej. Wyjasnie to na przykladzie:

#include <stdio.h>

class Zwierze // Klasa bazowa
{
  public:
  int nogi;
  void jedz();
  void oddychaj();
};

void Zwierze::jedz(void) { printf("\\nJem sobie co nie co ...");}
void Zwierze::oddychaj(void) { printf("\\nOddycham sobie ...");}

class Krowa : public Zwierze //Klasa pochodna
{
  public:
  void muczy();
  void chodzi();
};

void Krowa::muczy(void) {printf ("\\nMuuuu !!!");}
void Krowa::chodzi(void) {printf ("\\nJa chodze !!\\n");}

int main(void)
{
  Krowa Mucka;
  Mucka.nogi = 4; // Nogi Krowa odziedziczyla po klasie Zwierze
  Mucka.jedz();
  Mucka.oddychaj();
  Mucka.muczy();
  Mucka.chodzi();
  return 0;
}

A wiec, klasa bazowa jest klasa Zwierze. W deklaracji klasy Krowa, jest napis deklarujacy, ze podana klasa dziedziczy "geny" od klasy Zwierze. Dlatego tez, klasa Krowa ma dostep do wszystkich funkcji i zmiennych jakie posiada klasa Zwierze. Taki schemat jest bardzo przydatny jezeli mamy funkcje i zmienne, ktore powtarzaja sie we wszystkich klasach, dlatego tez mozemy je dac w jedno miejsce, po czym utworzyc klasy potomne na bazie klasy podstawowej. So, pora na cos powazniejszego. Dziedziczyc moga takze struktury. Przebiega to identycznie jak w przypadku klas, tylko przy definicji struktury pochodnej nie wystepuje slowo public, gdyz w przypadku struktur nie ma problemow z prawami dostepu. Przedstawiam takze pare zasad dotyczacych dziedziczenia mieszanego:

  • Klasa pochodna moze dziedziczyc po strukturze bazowej
  • Struktura moze dziedziczyc po klasie
  • W dziedziczeniu nie uczestnicza unie
  • Definicje klas wraz z implementacja funkcji moga byc umieszczone w plikach naglowkowych

Hmmm... Pora na jeszcze jeden aspekt dziedziczenia. Jest nim dziedziczenie zlozone. Polega ono na utworzeniu kilku pokolen. To znaczy sa dwie klasy pochodne i klasa bazowa. Przy czym jedna klasa pochodna jest klasa bazowa dla innej klasy pochodnej. Przedstawie to na przykladzie:

#include <stdio.h>

class Zwierze
{
  public:
  void oddychaj();
  void jedz();
};

class Gad : public Zwierze
{
  public:
  void linieje();
};

class Jaszczurki : public Gad
{
  public:
  int nogi;
  void wygrzewam_sie();
};

void Zwierze::oddychaj(void) {printf("\\nOddycham ...");}
void Zwierze::jedz(void) {printf("\\nJem sobie ...");}
void Gad::linieje(void) {printf("\\nO! Zrzucilem skore !!!");}
void Jaszczurki::wygrzewam_sie(void) {printf("\\nGrzeje sie na sloncu...");}

int main(void)
{
  Jaszczurki Micki;
  printf("\\nJestem jaszczurka Micki. Oto co moge:");
  Micki.nogi = 4;
  printf("\\nMam takze %d nogi.", Micki.nogi);
  Micki.oddychaj();
  Micki.jedz();
  Micki.linieje();
  Micki.wygrzewam_sie();
  return 0;
}

Jak zauwazyliscie obiekt Micki ma dostep do wszystkich funkcji klas nadrzednych. Obiekt Micki korzysta z funkcji klas Zwierze, Gad i Jaszczurki. Za pomoca takiego dziedziczenia mozemy opracowac niezwykle bogate pokolenia. Mozemy np.: rozwinac jakas klase do 20 pokolenia i taka klasa w 20 pokoleniu bedzie miala niezwykle duzo funkcji oraz zmiennych. To jest wlasnie ten mechanizm... Ciekawy ... nie ? ;) Aha ! Jeszcze jedno ! W naturze dziedziczy sie geny po obu rodzicach, dlatego tez w C++ takze jest to mozliwe. Wtedy funkcja pochodna musi posiadac deklaracje na wzor takiej:

class POCHODNA : public BAZOWA1, BAZOWA2 itd.

Wtedy klasa pochodna odziedziczy wszystko po OBU klasach bazowych. W takim dziedziczeniu zlozonym biora udzial takze struktury. So, dotarlismy do konca zagadnienia dziedziczenia... :) Jezeli wszystkiego nie zrozumiales, to przeczytaj to jeszcze raz, ewentualnie walnij sie z tym do lozka, a rano powinno Ci sie to rozjasnic...

  1. Overloading funkcji

Overloading funkcji mozna przetlumaczyc jako przeciazanie funkcji. Polega to na napisaniu kilku funkcji o takiej samej nazwie, a przyjmujace rozne liczby argumentow i dzieki temu, jezyk C++ stal sie duzo bardziej elastyczny. Uwaga ! Overloadingowi nie mozna poddac funkcji piszac program w klasycznym C. Wyobrazmy sobie sytuacje, ze musimy napisac funkcje przeksztalcajaca kod ASCII na znak i znak na kod ASCII. Jak to zrobic ? W klasycznym C trzeba bylo napisac dwie rozne funkcje, ktore posiadaly inne nazwy, a nastepnie napisac w nich odpowiednie instrukcje. Natomiast w C++ da sie to zrobic w duzo madrzejszy sposob. Otoz mozna poddac funkcje overloadingowi. Np.: nasza funkcja nazywa sie zamien i pobiera argument w postaci dodatniej liczby calkowitej, a zwraca znak.

char zamien(unsigned int kod_ASCII)

A jezeli chcemy aby istniala zamiana takze w druga strone to musimy napisac druga funkcje:

unsigned int zamien(char znak)

A jezeli piszemy funkcje dla kogos, kto nie ma ochoty wnikac w szczegoly techniczne funkcji, tylko chce miec mozliwosc latwego z niej korzystania to powinnismy napisac takie dwie funkcje i umiescic je np.: w klasie. So oto przyklad:

#include <stdio.h>
class zamiany
{
  public:
  unsigned int zamien(char znak);
  char zamien(unsigned int liczba);
};

unsigned int zamiany::zamien(char znak)
{
  return (znak);
}

char zamiany::zamien(unsigned int liczba)
{
  return (liczba);
}

int main(void)
{
  zamiany change;
  unsigned int x;
  char y;
  y = change.zamien(40);
  x = change.zamien('o');
  return 0;
}

Jak widzisz ta sama nazwa funkcji moze posiadac zupelnie inne argumenty. Dzieki temu zjawisku, mozemy pisac biblioteki funkcji dla innych osob, ktore beda mogli spokojnie z nich korzystac. Takie funkcje sa przygotowane na przyjecie naprawde roznorodnej gamy argumentow. Mozna przygotowac funkcje obslugujace wszystkie znane typy danych. A teraz wrocmy na kfile do punktu drugiego do klas i przyjrzyjmy sie blizej konstruktorom. Tak ! To tez przeciez sa funkcje ! Czyli mozemy je takze poddac overloadingowi aby podczas inicjacji obiektu mozna bylo obslugiwac wiele typow danych. Tutaj takze mam maly przyklad:

#include <stdio.h>
class Klasa
{
  char znak;
  int liczba;
  public:
  Klasa();
  Klasa(int x);
  Klasa(char y);
}

Klasa::Klasa()
{
  znak = 'A';
  liczba = 0;
}

Klasa::Klasa(int x)
{
  liczba = x;
}

Klasa::Klasa(char y)
{
  znak = y;
}

int main(void)
{
  Klasa obiekt1;
  Klasa obiekt2(22); 
  Klasa obiekt3('r');
  return 0;
}

Troche denny ten programik ale nie mam sily wymyslec lepszego :P So, co on robi ? Ano tworzy trzy obiekty, z ktorych kazdy posiada odmienny sposob inicjacji. Obiekt1 jest inicjowany prze uzyciu konstruktora domyslnego (default). Obiekt2 jest inicjowany poprzez konstruktor pobierajacy jedna liczbe. Natomiast obiekt3 obsluguje konstruktor pobierajacy jeden znak. W kazdym przypadku (i funkcji i konstruktorow) C++ wie, ktora funkcja ma zostac wybrana, poniewaz dopasowuje typ argumentow do odpowiedniej funkcji. Jeszcze tylko bardzo wazna uwaga: NIE MOZNA DOKONAC OVERLOADINGU DESTRUKTOROW !!!

  1. Overloading operatorow - podstawy

Jak jush wiemy overloading to jest przeciazanie, czyli zmienianie przeznaczenia jakiejs funkcji aby obslugiwala kilka zdarzen. W C++ mozemy poddac overloadingowi takze operatory. Jako, ze jest to w miare trudne to przedstawie tu tylko overloading operatorow dwuargumentowych + i -. Jak to dziala ? Kiedy chcemy obliczyc wartosc wyrazenia:

int x,y;
x = y = 2
x = 3 + y;

to operator + sam wie jak dodac te liczby, bo to zostalo mu juz zaprogramowane. Ale jezeli utworzymy wlasna klase np.:

class schowek 
{
  public: 
  int x;
  float y;
  char z;
};

to skad operator ma wiedziec jak potraktowac takie wyrazenie:

schowek num1, num2, num3;
num1.x = 10;
num1.y = 3.89;
num1.z = 'a';
num2.x = 80;
num2.y = 5.3;
num2.z = 'd';
num3 = num1 + num2;

Wtedy musimy uzupelnic wiadomosci operatora + o to w jaki sposob zachowac sie przy takim wyrazeniu. Chcemy aby num3 zawieral pola obu tych klas (num1 i num2) po dodaniu. Wbrew pozorom nie jest to takie trudne :) Zaczynam od razu od przykladu:


/* Uwaga ! Przyklad moze nie dzialac na kompilatorze firmy Borland ! Powodem bledu jest nieodpowiednia wersja biblioteki z typami ale jezeli u Ciebie dziala to sie ciesz ;) ! Polecam kompilator Microsoft Visual C++ v.4.0 i wyzej ! */

#include <stdio.h>
#include <iostream.h>

class schowek 
{
  int numer;
  float zmienna;
  char znak;
  public:
  schowek() {};
  schowek(int x, float y, char z) {numer = x; zmienna = y; znak = z;};
  void pokaz_wynik() {cout << numer << " " << zmienna << " " << znak << '\\n';}
  schowek operator+(schowek);
};

schowek schowek::operator+(schowek dod)
{
  schowek kopia = *this;
  kopia.numer += dod.numer;
  kopia.zmienna += dod.zmienna;
  kopia.znak += dod.znak;
  return (kopia);
}

int main(void)
{
  schowek num1 (12, 3.1, 'a'), num2 (20, 3.2, 'b'), num3;
  printf("\\nOto poczatkowe wyniki zmiennej num1: \\n");
  num1.pokaz_wynik();
  printf("Oto poczatkowe wartosci zmienneg num2: \\n");
  num2.pokaz_wynik();
  printf("A oto wartosc num3 po dodaniu num1 i num2: \\n");
  num3 = num1 + num2;
  num3.pokaz_wynik();
  return 0;
}

So, omowie to z lekka, ale chyba nie mam za bardzo co omawiac :) Aby dokonac overloadingu w strukturze schowek deklarujemy funkcje operator+ , ktora jest wlasnie przeznaczona do dokonywania overloadingu. Zwraca ona wartosc typu schowek i dziala tylko pomiedzy tymy wartosciami. Specjalny operator *this sluzy do pobrania obiektu stojacego po lewej stronie znaku:

num3 = num1 + num2;

Operator *this wskazuje na num1. Zawsze nalezy tworzyc kopie tego obiektu, poniewaz nikt nie chce abysmy mu grzebali po num1. Mamy dodac te klasy zapisujac wynik w obiekcie num3 ale inne obiekty maja pozostac nienaruszone. Znak minus poddajemy overloadingowi w ten sam sposob, jednak tutaj trzeba byc troche bardziej uwaznym, bo wszystko musi sie miescic w odpowiednich zakresach ! Sa jednak operatory, ktorych nie mozna poddac overloadingowi. Sa to:

. - kropka
:: - operator widocznosci
?: - operator warunkowy
.* - operatora wskazania czlonka klasy

Reszte mozna spokojnie overloadingowac !

  1. Tworzenie listy laczonej

Kolejnym dzisiejszym zagadnieniem bedzie utworzenie ATD w postaci listy laczonej. Wyobrazmy sobie sytuacje, ze musimy opracowac projekt managera kontaktow. Uzytkownik moze wprowadzac dowolna ilosc kontaktow itp. Jak to wykonac ? Hmm.. na mysl przychodzi stworzyc jedna wielka tablice o 500 elementach i uzytkownik by ja sobie wypelnial. Tak, ale jezeli uzytkownik bedzie chcial wpiac tylko 10 osob na liste to kolejne 490 pozycji bedzie wielkim marnotrawstwem. A jezeli znajdzie sie osoba, ktora ma 501 kontaktow to nasz program sie od razu "wysypie". Hmm.. Zacznijmy od napisania struktury jednego wpisu:

#define MAX_NAM 30
struct osoba 
{
  char imie[MAX_NAM];
  char nazwisko[MAX_NAM];
  char adres[100];
  struct osoba *nast;
};

Do tej pory wszystko powinno byc zrozumiale poza ostatnia linijka, czyli: struct osoba *nast;. Co ona daje ? Jest to wskaznik do nastepnej struktury tego samego typu co bierzaca. W liscie laczonej chodzi o to, ze kazdy element zawiera adres nastepnego elementu, a ostatni element listy zawiera wskaznik NULL. Jest jeszcze jeden wskaznik: wskaznik glowny posiadajacy adres pierwszego elementu. So, ponizej przedstawie taki podstawowy szkielet listy laczonej (rozbudujemy ja w trzecim odcinku tego cyklu ale to co jest ponizej jest pelna lista laczone, tyle ze uboga w funkcje):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_POLE 30
struct osoba 
{
  char imie[MAX_POLE];
  char nazwisko[MAX_POLE];
  char numer_tel[20];
  char adres[200];
  struct osoba *nast;
};

int main(void)
{
  struct osoba *glowny = NULL;
  struct osoba *poprz, *bierz;
  char bufor[210];
  int dl;

  puts("Witaj w programie zarzadzajacym Twoimi kontaktami !");
  puts("Podaj imie pierwszej osoby, ktora chcesz zapisac: ");
  while (gets(bufor) != NULL && bufor[0] != '\\0')
  {
    bierz = (struct osoba *) calloc (1, sizeof(struct osoba));
    if (glowny == NULL)
    glowny = bierz;
    else
    poprz->nast = bierz;
    bierz->nast = NULL;
    dl = strlen(bufor);
    strncpy(bierz->imie, bufor, dl);
    puts("Wprowadz nazwisko: ");
    dl = 31;
    while (dl < 31)
    {
      gets(bufor);
      if ((dl = strlen(bufor)) > 30)
      {
        puts("Zbyt dlugie nazwisko !");
        continue;
      }
    }
    strncpy(bierz->nazwisko, bufor, dl);
    ....
    ....
    puts("Podaj nastepne imie: ");
    poprz = bierz;
  }
  if (glowny == NULL)
  {
    puts("Nie wprowadzono zadnych danych!");
    puts("Do widzenia !");
    exit(EXIT_SUCCESS);
  }
  else
  {
    puts ("Oto lista osob:");
    bierz = glowny;
    while (bierz != NULL)
    {
      printf("\\nImie: %s, nazwisko: %s, adres: %s, numer telefonu: %s\\n",bierz->imie, bierz->nazwisko,  bierz->adres, bierz->numer_tel);
      bierz = bierz->nast;
    }
      puts("Na razie !");
      return 0;
    }

Jak zapewne zauwazyliscie nie napisalem wszystkiego, bo po prostu chcialem oszczedzic czas. Adres i numer telefonu program pobieralby analogicznie jak nazwisko. Jeszcze tylko dlugosc imienia powinna byc sprawdzana ale to juz zostawiam jako prace domowa :) Jak to dziala ? Otoz zauwazyliscie w strukturze wskaznik do nastepnej struktury. W funkcji main zadeklarowalem trzy obiekty o strukturze osoba: glowny, bierz, poprz. Glowny jest pierwsza osoba, od ktorej wszystko sie zaczyna. Program przydziela pamiec dla struktury bierz, po czym wypelnia ja danymi i przypisuje wskaznik do pola nast struktury poprz. Dzieki temu zawsze wiemy jaka byla struktura poprzednia, jaka jest bierzaca, gdzie jest poczatek i gdzie jest koniec. Potem przypisujemy do pola bierz->nast wartosc NULL, ktora oznacza, ze jest to ostatnie pole listy laczonej. Na koniec adres obecnej struktury jest nadawany adresowi struktury poprzedniej. Lista jest wyswietlana poprzez przechodzenie wskaznika po polach nast struktury. To jest cala filozofia listy laczonej. Proste, nieprawdaz ? Nasz program zbyt madry nie jest, nie posiada oblsugi bledow typu brak pamieci ale przeciez to nie jest problem. Najgorsza robota, czyli szkielet listy jest jush zrobiony.

  1. Zakonczenie

Pisalem co bedzie w nastepnych czesciach. Nie wiem jeszcze dokladnie co bedzie ciekawego w nastepnym odcinku ale cos wymysle :) Na pewno zaprezentuje wam technike drzew binarnych. Bedzie tez pare innych rzeczy ale to jush sami zobaczycie. So, kolejna wersja dostepna bedzie pod adresem:

http://www.asmie.prv.pl (jak zrobie te strone ale to juz niedlugo :)) a jakbym jeszcze jej nie zrobil to mail-me a wysle nowy text...
Asmie out !

Gr33tz 4:
Furiza (essshhh te Deamonz ;)), Neo, Boogie (chcesz kicka ? ;)), deefes, requill, Bartaz, Perry, Mazik (#pojebani rullezzz ;)), Deamonz of The Net oraz:
#deamonz, #polska (IRC-zone), #hakcing (IRC-zone), #coolest, #cipisparty, #dg, #hackpl i dla calej reszty.

Kontakt:

[email protected]

7 komentarzy

dzięki wielkie, proste i pomocne przykłady

Heh, to raczej nie jest programowanie zaawansowanie lecz PODSTAWY!!! :/

a tym bardzie biblioteki <stdio.h> ( jak już to <cstdio> ) tylko <iostream>

Dodałem wcięcia i kolorowanie składni.

art ok ale ja bym ują kod w tagi < cpp > oraz < / cpp >. Poprawiłoby to wygląd :) Ale tak to spoko :)

Ale co tu jest zaawansowanego? Przecież to podstawy wykorzystania klas. Tym bardziej, w C++ nie wykorzystuje się już funkcji z rodziny malloc.

Trochę się rozpisałeś, ale jest spox.