[2] Sta posle skole - standard library

[2] Sta posle skole - standard library

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Sadržaj

Uvod
Standrad C++ Library
Usavršavanje digitrona
Odvajanje računanja u posebnu klasu
Odvajanje prikaza u posebnu klasu
CMake i kod u više datoteka
Zaključak

Uvod
Za razliku od C jezika, C++ pored proceduralnog programiranja možemo koristiti i objektno orientisano programiranje (OOP). Koristeći OOP imamo mogućnost da grupišemo kod sa pripadajućim promenljivama i time program razdelimo na manje, relativno samostojne delove tj. klase.

Standrad C++ Library
Standardna C++ biblioteka donosi brdo klasa koje nam omogućavaju brže i lakše pisanje koda. Određene stvari rade malo sporije u odnosu na korišćenje standardnog C koda (recimo ispis na ekran), ali je razlika u brzini relativno mala, a lakoća pisanja koda mnogo veća tako da se isplati koristiti C++ klase.
U prošlom projektu smo koristili scanf i printf funkcije za čitanje i pisanje podataka i te funkcije su zahtevale da u formatu upišemo tip podatka s kojim ćemo raditi što može dovesti do problema ako slučajno upišemo pogrešan tip. Kompajler i linker će bez problema kod prevesti u izvršnu datoteku, i grešku ćemo uočiti tek kada programpokrenemo i on se sruši prilikom izvršavanja sporne komande.
Korišćenjem C++ klasa možemo izbeći konkretno taj problem. Koristićemo cin i cout promenljive koje predstavljaju interfejs za čitanje i pisanje na ekran. One su zasnovane tako da podatke jednostavno šaljemo/čitamo iz njih, a one se brinu o tipovima. Primera radi, ako bi promenljiva t predstavljala neki tekst, a promenljiva b neki broj, ispis na starom načinu bi izgledao ovako:
printf("%s %d", t, b);
Nov način ne zahteva od nas da eksplicitno navodimo tipove promenljivih:
cout << t << " " << b;
Za one koji se prvi put sreću sa standardnom C++ bibliotekom i nije im potpuno jasno šta znači recimo using namespace std; ili <<), predlažem da prvo pročitaju nešto više o Standrad C++ Library, klasama koje nudi i načinu korišćenja: http://msdn.microsoft.com/en-us/library/cscc687y%28v=vs.100%29

Usavršavanje digitrona
Da bi malo dobili osećaj za korišćenje cin i cout prepravićemo prošli primer tako da koristi nov način za čitanje i pisanje:
#include <iostream> using namespace std; int main() {    int op = 0;    int p1 = 0, p2 = 0;    cout << "Digitron" << endl << endl;    cout << "Izaberite operaciju" << endl;    cout << "  1. Sabiranje" << endl;    cout << "  2. Oduzimanje" << endl;    cout << ": ";    cin >> op;    if (op != 1 && op != 2)    {       cout << "Ta operacija ne postoji!" << endl;       return 1;    }    cout << "Unesite prvi parametar: ";    cin >> p1;    cout << "Unesite drugi parametar: ";    cin >> p2;    cout << "Rezultat je: ";    switch(op)    {    case 1:       cout << p1 + p2;       break;    case 2:       cout << p1 - p2;       break;    default:       break;    }    cout << endl;    return 0; }
Kao što vidite, ništa specijalno nije promenjeno osim načina čitanja i pisanja.
Ceo ovaj kod je relativno mali da bi odvajali delove koda koji su zaduženi za logiku i delove za prikaz podataka... pa hajde da malo zakomplikujemo program. Recimo da želimo da omogućimo korisniku da upiše ceo izraz koji ćemo izračunati. Da ne bi otišli predaleko s komplikovanjem, reći ćemo da su dozvoljeni samo brojevi i znaci + i - da ne bi morali da se patimo oko redosleda operacija i oko zagrada... recimo: 12+20-45+6. Jednostavna, ne preterano dobra implementacija bi mogla da izgleda ovako (ko želi, može da proba da napiše bolji parser):
#include <iostream> #include <sstream> #include <string> using namespace std; int main() {    string f, buffer = "", lastOperation = "+";    string numbers = "0123456789", operations = "+-";    string::size_type i;    int b, r = 0;    cout << "Digitron" << endl << endl;    cout << "Unesite izraz: ";    getline(cin, f);    for(i = 0; i < f.length(); ++i)    {       if (numbers.find(f[i]) != string::npos)          buffer += f[i];       else if (operations.find(f[i]) != string::npos)       {          istringstream(buffer) >> b;          if (lastOperation == "+")             r += b;          else             r -= b;          buffer = "";          lastOperation = f[i];       }       else       {          cout << "Nedozvoljen znak '" << f[i] << "' na " << i + 1 << ". poziciji!" << endl;          return 1;       }    }    if (!buffer.empty())    {       istringstream(buffer) >> b;       if (lastOperation == "+")          r += b;       else          r -= b;    }    cout << f << "=" << r << endl;    return 0; }
Sada već ceo kod izgleda malo komplikovanije i kada bi neko morao da popravi samo deo za prikaz, morao bi malo da pripazi da slučajno ne promeni deo koda koji je zadužen za računanje. Kod većih projekata je taj problem još izraženiji, a jedno od rešenja je izdvajanje koda zaduženog za jedan određeni deo u klasu.
Za sve one koji do sad nisu pisali svoje klase, predlažem da pročitaju nešto o tome pre nego što krenu dalje. dobar tekst o klasama možete naći ovde: http://www.cplusplus.com/doc/tutorial/classes/ (pogledajte na levoj strani deo Object Oriented Programming)

Odvajanje računanja u posebnu klasu
Pre nego što napišemo klasu, moramo da odredimo na koji način ta klasa treba da komunicira sa glavnim delom. Sigurno će nam trebati jedna funkcija za prosleđivanje izraza i vraćanje rezultata. Pošto može da dođe i do greške prilikom obrade izraza, moramo omogućiti i način za vraćanje greške. Imajući to na umu, možemo da spremimo spremimo kod.
Pošto će kod za računanje biti odvojen, možemo još malo da ga proširimo... da lepše parsuje izraz, da podržava i realne brojeve i, recimo množenje i delenje (kod koji budem postavio neće uzimati u obzir prednost operacija):
class calculator { public:    void calculate(string func);    float getResult();    string getError(); protected:    enum tokenType    {       number,       operation,       end,       error    };    float strToNum(string num);    tokenType getNextToken();    void doLastOperation(float num);    string _func;    string::size_type _index;    string _validNumbers;    string _validOperations;    float _result;    char _lastOperation;    string _token;    string _error; }; void calculator::calculate(string func) {    _func = func;    _index = 0;    _validNumbers = "0123456789.";    _validOperations = "+-*/";    _result = 0;    _lastOperation = _validOperations[0];    _token = "";    _error = "";    bool done = false;    while(!done)    {       switch(getNextToken())       {       case calculator::number:          doLastOperation(strToNum(_token));          break;       case calculator::operation:          _lastOperation = _token[0];          break;       case calculator::end:          done = true;          break;       default:  // error          done = true;          _error = _token;          break;       }       _token = "";    } } float calculator::getResult() {    return _result; } string calculator::getError() {    return _error; } float calculator::strToNum(string num) {    float res;    istringstream(num) >> res;    return res; } calculator::tokenType calculator::getNextToken() {    if (_index == _func.length())       return calculator::end;    for(; _index < _func.length(); ++_index)    {       if (_validNumbers.find(_func[_index]) != string::npos)          _token += _func[_index];       else if (_validOperations.find(_func[_index]) != string::npos)       {          if (_token.empty())          {             _token = _func[_index];             ++_index;             return calculator::operation;          }          else             return calculator::number;       }       else       {          ostringstream error;          error << "Nedozvoljen znak '" << _func[_index] << "' na " << _index + 1 << ". poziciji!";          _token = error.str();          return calculator::error;       }    }    return calculator::number; } void calculator::doLastOperation(float num) {    switch(_lastOperation)    {    case '+':       _result += num;       break;    case '-':       _result -= num;       break;    case '*':       _result *= num;       break;    case '/':       _result /= num;       break;    default:       break;    } }
Ta klasa sada ne zavisi od eksternog koda, sve što joj treba ima u sebi. Ako neko želi da izračuna izraz, dovoljno je da pozove calculate metod i da proveri rezultat... nema potrebe da se zamara samom implementacijom.

Odvajanje prikaza u posebnu klasu
Naš kod za prikaz je prilično jednostavan i dovoljno je da napravimo jednu funkciju koja će pokrenuti ispis i čitanje:
class app { public:    int run(); }; int app::run() {    string func;    calculator calc;    cout << "Digitron" << endl << endl;    cout << "Unesite izraz: ";    getline(cin, func);    calc.calculate(func);    if(!calc.getError().empty())    {       cout << calc.getError() << endl;       return 1;    }    else    {       cout << func << "=" << calc.getResult() << endl;       return 0;    } }
Ovde vidimo i primer korišćenja klase za računanje i koliko sada ovaj kod izgleda jednostavniji i lakši za čitanje i održavanje.

Ostaje nam još da u glavnoj datoteci iskoristimo ovu klasu za prikaz:
int main() {    app calc;    return calc.run(); }

CMake i kod u više datoteka
Ceo kod za ovaj projekat možete skinuti odavde: https://www.mycity.rs/must-login.png
Ovaj projekat, za razliku od prethodnog, ima više datoteka za kompajliranje, ali to nije nikakav problem za CMake. Dovoljno je da samo nabrojimo sve cpp datoteke (h datoteke ne treba navoditi) i CMake će se snaći:
cmake_minimum_required (VERSION 2.6) project (calc) add_executable(calc calc.cpp app_console.cpp calculator.cpp)

Zaključak
Ako nemate jasnu sliku programa kojeg želite da pravite, ne morate žuriti s pisanjem klasa i odvajanjem koda. Kada primetite da jedna celina polako izdvaja i da u suštini može da radi i bez ostalog koda, to je već dobar znak da bi mogli da napravite klasu. Kad budete napravili par projekata, stećićete već osećaj.
Pošto smo sada odvojili kod za unos i prikaz podataka, sledećeg puta ćemo napraviti malo drugačiji kod za taj deo, napravićemo prozorčić sa digmenzima i poljem za unos. Naravno, klasa za izračunavanje će biti ista kao i u ovom primeru.

Dopuna: 22 Jun 2012 1:06

Da li je moguće da niko nije imao nikakvih problema s ovim što smo do sad probali da uradimo... ili niko ništa nije ni probao da odradi? Smile

Evo 2 sličice digitrona kojeg ćemo napraviti sledećeg puta:


Ovako to izgleda kod mene u KDE okruženju. U drugim okruženjima će aplikacija izgledati malo drugačije (imaće grafičnu temu koju ste izabrali kod vas, fontove koje koristite, itd...), ali što je za nas bitno, radiće svuda lepo i izgledaće kao da je napravljena baš za vaš računar Smile

Dok pišem sledeći tekst, možete se malo upoznati sa Qt bibliotekom, jer ćemo nju koristiti za pravljenje prozora:
http://doc.qt.nokia.com/4.7/gettingstartedqt.html
http://qt-project.org/videos



Registruj se da bi učestvovao u diskusiji. Registrovanim korisnicima se NE prikazuju reklame unutar poruka.
offline
  • Fil  Male
  • Legendarni građanin
  • Pridružio: 11 Jun 2009
  • Poruke: 16586

Super je napisano. Zeljno ocekujem deo i sa QT jer planiram da ga ucim Ziveli
Nisam jos probao da iskompajliran ovaj kod (digitron), ali isprobacu pre pravljenja drugog clanka o Cpp-u.



Ko je trenutno na forumu
 

Ukupno su 706 korisnika na forumu :: 32 registrovanih, 4 sakrivenih i 670 gosta   ::   [ Administrator ] [ Supermoderator ] [ Moderator ] :: Detaljnije

Najviše korisnika na forumu ikad bilo je 3466 - dana 01 Jun 2021 17:07

Korisnici koji su trenutno na forumu:
Korisnici trenutno na forumu: Apok, bokisha253, Boris90, comi_pfc, darkangel, darkstar101, Dimitrise93, dmdr, DonRumataEstorski, DPera, Džordžino, Excalibur13, Frunze, ILGromovnik, Instruktor 1223, jackreacher011011, Karla, Kibice, Litostroton, marsovac 2, Milometer, milutin134, moldway, nedeljkovici, raptorsi, Singidunumac, Sirius, ss10, Stoilkovic, vathra, wizzardone, ZetaMan