STP
-
Upload
sasa-bajtl -
Category
Documents
-
view
5 -
download
0
description
Transcript of STP
Suvremene tehnike programiranja
Vježba 1
Ciljevi
� Uputiti studente u osnovne ideje i pojmove objektno orijentiranog programiranja
� Omogućiti služenje barem jednim objektno orijentiranim proramskim jezikom
� Upoznati studente s metodama, alatima i razvojnim okolinama za objektno programiranje
Literatura
� Osnovna:� Stanley B. Lippman, Josée Lajoie, Barbara E. Moo: C++ Primer,
Fourth Edition, Addison Wesley Professional, 2005.
� Dopunska:� Bjarne Stroustrup: The C++ Programming Language, Addison
Wesley, 2000.
Popis tema
� Uvod. Objektno orijentirano programiranje. Programski jezik C++
� Objekti, klase. Preopterećivanje. Enkapsulacija. Sučelja. Razdvajanje sučelja i implementacije
� Nasljeđivanje. Agregacija. Polimorfizam� Predlošci� Suvremeni alati i razvojne okoline: MS Visual
Studio.NET, Eclipse, Dev-C++, NetBeans, gcc
Osnovni elementi OOP
� Apstrakcija - Koncepti se reprezentiraju neposredno u programu, a izvedbeni detalji su skriveni iza sučelja (eng. interface) koje reprezentira koncept.
� Enkapsulacija - Sposobnost osiguravanja da se apstrakcija koristi prema svojim specifikacijama. Enkapsulacijom se sprečava narušavanje apstrakcije, odnosno prodiranje implementacijskih odluka izvan granica apstrakcije.
� Polimorfizam - Skrivanje različitih implementacija iza istog sučelja. Osigurava široku primjenjivost koda i reducira zavisnost o implementaciji.
� Nasljeđivanje - Konstrukcija novih apstrakcija polazeći od već postojećih.
Osnovni elementi OOP
� Apstrakcija
� Enkapsulacija
� Polimorfizam
� Nasljeđivanje
Programski jezik C++
asm auto bool break
case catch char class
const const_cast continue default
delete do double dynamic_cast
else enum explicit export
extern false float for
friend goto if inline
int long mutable namespace
new operator private protected
public register reinterpret_cast return
short signed sizeof static
static_cast struct switch template
this throw true try
typedef typeid typename union
unsigned using virtual void
volatile wchar_t while
Programski jezik C++
� Sveprisutan jezik opće namjene
� Visoke performanse
� Dobro prihvaćen i u akademskom i u industrijskom okruženju
� Strogo kontrolirani razvoj jezika: ISO C++ standard
� Višeparadigmatski jezik:� proceduralno programiranje
� objektno-temeljeno programiranje
� objektno-orijentirano programiranje
� generičko programiranje
� funkcionalno programiranje
Programski jezik C++
� Kada koristiti C++?� veliki projekti
� sistemske aplikacije
� grafika
� strukture podataka
� kad god je važna brzina
� Kada ne koristiti C++?� male aplikacije
� brzo prototipiranje aplikacija
� web aplikacije
Alati
� MS Visual Studio.NET
� Eclipse,
� Dev-C++
� NetBeans
� ...
C++ standardi
Godina C++ Standard Neformalno ime
1998 ISO/IEC 14882:1998 C++98
2003 ISO/IEC 14882:2003 C++03
2007 ISO/IEC TR 19768:2007 C++07/TR1
2011 ISO/IEC 14882:2011 C++11
2014 ISO/IEC 14882:2014 C++14
2017 još nije određeno C++17
C++ primjer promjena� Po uzoru na većinu ostalih programskih jezika u verziji C++11 uvedena je tzv
range-for petlja (pojednostavljena for-naredba) sljedećeg oblika:
for(deklaracija : izraz)naredba;
� Ovdje izraz predstavlja niz elemenata -- polje, višedimenzionalno polje, niz u vitičastim zagradama, string, vector ili bilo koji STL spremnik koji ima begin i endmetode.
� Deklaracija mora biti takva deklaracija varijable da se element niza može konvertirati u tu varijablu. Na primjer,
char slova[] = { 'a','b','c','d','e'};for(char x : slova) std::cout << x << ",";
� U svakom prolazu petlje, x dobiva novu vrijednost iz name. Petlja je ekvivalentna sa:
for(int i=0; i<5; ++i) cout<<slova[i]<< ",";
C++ primjer promjena� Kod deklaracije varijable u range-for petlji prirodno je koristiti auto i pustiti
prevodiocu da deducira tip elementa u spremniku: for(auto x : slova) std::cout << x << ",";
� Ukoliko mijenjamo elemente spremnika treba varijablu deklarirati kao referencu: for(auto& x : slova) x += 1;
� Svi STL spremnici koji imaju begin i end metodu mogu se koristiti u range-for petlji, kao i eksplitno zadani nizovi:
std::vector<double> podaci{1.1,2.2,3.3};for(double x : podaci) cout << x << ",";for(string x : {"aaa","bbb","ccc"}) cout<<x<< ",";
� Dinamički alocirano polje ne može se koristiti u range-for petlji.� Range-for petlja primijenjena na STL spremnike sprema end-iterator na
početku petlje pa se ne može koristiti za dodavanje/izbacivanje elemenata u/iz spremnik(a).
Kvalifikator const
� Pomoću kvalifikatora const možemo zaštiti rijednost od promjene
int broj=15;broj=30; //OK
const int broj=15;broj=30; //Greška
� Varijabla mora biti označena kao const već pri deklaraciji i pri tom mora biti i inicijalizirana
� const osim sa varijablama možemo koristiti i na druge načine
Kvalifikator const
� Primjeri:
const int broj = 100;
const double pi = 3.14159;
broj = 0; // greska
const string rijec = "hello!"; // ok
const int i, j = 0; // greska
Kvalifikator const
� Zadatak:Koje od slijedećih linija koda su legalne?
(a) const int buf;
(b) int cnt = 0;
const int sz = cnt;
(c) cnt++; sz++;
C++ auto� Kada želimo izračunatu vrijednost nekog izraza spremiti u varijablu trebamo odrediti tip te varijable. Taj posao
možemo prepustiti prevodiocu jer on ionako zna tip izraza; dovoljno je varijablu deklarirati s auto: int x = 1;
double y = 1.0;
auto z = x + y; // prevodilac ce u činiti z tipa double
� Taj je mehanizam najkorisniji s predlošcima, gdje stvarni tip ne znamo.template <typename T1, typename T2>
void f(T1 t1, T2 t2)
{
auto t = t1 + t2;
// ...
}
� Kada varijablu inicijaliziramo drugom varijablom koja je referenca na neki tip, auto u konstrukciji tipa ignorira referencu. Ako je referenca bila konstantna, auto ignorira i const.
int i = 1;
const int j = 2;
const int & k = i;
const int & l = j;
auto k1 = k; // daje: int k1 = k;
auto l1 = l; // daje: int l1 = l;
C++ auto� const se ne ignorira kada su objekti konstantni:
auto pk = &k; // const int * pk = &k; auto pi = &i; // int * pi = &i;
� Želimo li dobiti referencu trebamo ju eksplicitno zatražiti. Tada const neće biti ignoriran. Jednako tako možemo dodati const u auto deklaraciju.
auto & k2 = k; // const int & k2 = k;auto & l2 = l; // const int & l2 = l;const auto ii = i; // const int ii = i;
� Konzistentnih auto deklaracija možemo imati više u jednoj liniji, jednako kao i standardnih.
auto v = 0.0, *pv = &v;
Reference
� Referenca na neki objekt je novo ime (alias) za taj objekt.
� Osnovna uloga reference je omogućiti prijenos parametara po referenci, (prijenos u kojem nema kopiranja parametra, već funkcija dobiva referencu na objekt)
� Svaka promjena na parametru koja je napravljena unutar funkcije biti će vidljiva i nakon izlaza iz funkcije
� Referencu nikad ne možemo preusmjeriti na drugi objekt (pokazivače možemo)
Reference
� Budući da referenca uvijek mora referirati na neki objekt, slijedi pravilo:
� Prilikom deklaracije referenca mora biti inicijalizirana.
int n=100;
int &rn = n; // ok
float ℞ //Greška pri prevo đenju // Neinicijalizirana referenca
Reference� Na jedan objekt možemo imati više referenci, a kako je svaka referenca samo
novo ime za objekt, svaka promjena objekta putem jedne reference vidljiva je i kroz sve druge.
int n = 100; int &rn1 = n; int &rn2 = n;std::cout << “n= "<< n << std::endl ; //100std::cout << “rn1= "<< rn1 <<“, rn2=“<<rn2<< std::en dl; //100,100rn1++; std::cout << “n= "<< n << std::endl ; //101std::cout << “rn1= "<< rn1 <<“, rn2=“<<rn2<< std::en dl; //101, 101
Reference
� Referenca može biti konstantna i tada može referirati na konstantan objekt. Obična referenca ne može referirati na konstantu:
char &ra = 'a'; // greška pri kompilaciji.
// Nekonstantna referenca, konstantan objekt.
const char &rb = 'b'; // o.k. Konstantna referenca nam garantira da kroz nju ne možemo promijeniti objekt na koji referira.
� konstantnom referencom možemo referirati i nekonstantni objekt
Reference
� Primjer:int i = 1024;
int& refVal1 = i; // ok
int& refVal2; // greska
int& refVal3 = 42; // greska
� const reference:const int &ir = 1024;
double dval = 3.14159;
const int &ir2 = dval;
Reference
� Zadatak: Koje od slijedećih definicija su pogrešne?Kako biste ih ispravili?(a) int ival = 1.01;(b) int &rval1 = 1.01;(c) int &rval2 = ival;(d) const int &rval3 = 1;
� Zadatak:Koja od slijedećih pridruživanja su pogrešna?(a) rval2 = 3.14159;(b) rval2 = rval3;(c) ival = rval3;(d) rval3 = ival;
Reference
� Korištenjem referenci donekle ograničavamo prirodan način korištenja implicitnih konverzija. Na primjer, sljedeći kod javlja grešku pri kompilaciji.
double x = 2.71;
int &rx = x;
� S druge strane, sljedeći javlja samo upozorenje o konverziji double u int (jer je naravno moguć gubitak podataka):
double x = 2.71;
const int &rx = x;
Reference
� Kada prevoditelj treba referencu na jedan tip inicijalizirati objektom nekog drugog, kompatibilnog, tipa, on kreira privremeni objekt istog tipa kao i referenca, vrši konverziju i inicijalizira referencu tim privremenim objektom.
� Kod koji prevoditelj generira izgleda, dakle, ovako:
double x = 2.71;
int tmp = x;
const int &rx = tmp;
� Nekonstantna referenca nekog tipa može biti inicijalizirana jedino objektom egzaktno tog istog tipa.
Reference
� Referencu možemo definirati i na pokazivač.
int n = 100;
int *pn = &n;
int * &rpn = pn; // Referenca na pokaziva č
std::cout << "*rpn = "<< *rpn << std::endl;
� Nije dozvoljeno deklarirati pokazivač na referencu, a isto tako ni referencu na referencu.
� Polje referenci ne postoji.
� double &x[8]; // Polje referenci nije dozvoljeno – neispravno.� Razlog je taj što svaka referenca mora biti inicijalizirana nekim objektom,
što pri definiciji polja nije moguće.
Reference (primjer)� void f( int *p)
� {
� std::cout<<*p<<std::endl;
� p++;
� }
� void g( int * & p)
� {
� std::cout<<*p<<std::endl;
� p++;
� }
� ……..
� int a=3;
� int *ptr=&a;
� std::cout<<ptr<<std::endl;
� f(ptr);
� std::cout<<ptr<<std::endl;
� g(ptr);
� std::cout<<ptr<<std::endl;
Reference
� U C++ u koristimo reference kad: ne želimo prijenos parametara po vrijednosti. Ako je formalni argument funkcije deklariran kao referenca nekog tipa, onda prilikom poziva funkcije neće doći do kopiranja stvarnog argumenta u formalni, već će formalni argument (koji je referenca) biti inicijaliziran stvarnim. Formalni argument tako postaje alias za stvarni argument i kroz njega možemo dohvatiti stvarni argument. Na taj se način postiže prijenos parametara po referenci.
void swap( int &x, int &y) {
int tmp = x;
x = y;
y = tmp;
}
Reference
� Prijenos po referenci vršimo kad želimo: 1. mijenjati stvarni argument ili kad je
2. argument suviše velik za kopiranje (ubrzavamo poziv funkcije).
� U slučaju, kad samo želimo izbjeći kopiranje stvarnog argumenta, formalni argument treba deklarirati kao konstantnu referencu (const) jer tako osiguravamo da argument neće biti promijenjen
Reference
� void print(std::string& text) {
� std::cout << text << std::endl;
� }
� std::string a("..."); print(a); // ok
� print("..."); // greška
� Bolje je dakle pisati:
� void print( const std::string& text) {
� std::cout << text << std::endl;
� }
Reference
� Ako je argument funkcije deklariran kao nekonstantna referenca, onda pri pozivu funkcije nije dozvoljena konverzija tipova za taj argument. Formalni i stvarni argument moraju biti istog tipa.
� Kad je referenca konstantna, onda je, kao što smo vidjeli, konverzija dozvoljena. Treba imati pri tome na umu da će prevoditelj generirati privremeni objekt i da će formalni argument referirati na njega.
Polja kao argument funkcije
� Kako se polja ne mogu kopirati, ne možemo napisati funkciju koja ima parametar tipa polje. Kad koristimo polje u izrazima, ono se automatski konvertira u pokazivač na prvi član polja.
� Sljedeće deklaracije su ekvivalentne. Interpretiraju se kao funkcija koja kao parametar prima pokazivač tipa int*.
� void print( int * ){ …..}
� void print( int[ ] ){ …. }
� void print( int[100] ){ …… }
Polja kao argument funkcije
� Funkcija koja uzima polje kao argument dobiva pokazivač na prvi element polja. Takva funkcija može uzeti polje bilo koje dimenzije. S druge strane, moguće je deklarirati funkciju koja uzima referencu na polje:
�
int count( int (&arr)[4]) {
int x = 0;
for( int i=0; i< 4; ++i) x += arr[i];
return x;
}
� Funkcija count će uzimati samo polja dimenzije 4, jer nema implicitnih konverzija između polja različite veličine. Referenca na polje je dakle manje fleksibilna od pokazivača na polje, pa se stoga rijetko koristi kao parametar funkcije.
Reference
� Ako funkcija vraća referencu, tada ne dolazi do kopiranja vrijednosti, već samo do kopiranja reference. Za velike objekte to može biti velika ušteda.
� Referenca kao povratna vrijednost ima još i tu prednost da predstavlja vrijednost koja se može naći na lijevoj strani znaka jednakosti (lvalue).
char& get(std::string& s, unsigned i) {
return s[i];
}
std::string s1("abc");
get(s1,2)='z';
Reference
� Moramo paziti da nikad ne vratimo referencu na lokalnu varijablu jer će ona nakon vraćanja reference na nju biti uništena. Isto naravno vrijedi i za pokazivač na lokalnu varijablu.
// Neispravan kod
std::string& ccn( const std::string& s1, const std::string& s2){
std::string tmp = s1+s2;
return tmp; // greška
}
� Funkcija ne smije nikada vratiti referencu ili pokazivač na lokalnu varijablu.
Skraćeno označavanje izvedenih tipova
� Ako često koristimo neki složeniji tip (npr. pokazivač na char) onda tom tipu možemo dati novo ime pomoću ključne riječi typedef
typedef tip novo ime;
� Primjer:typedef char * pchar;//pchar je novo ime
� Korištenje novog imena:pchar rijec; //rijec je pokazivac na char
typedef (primjer)
Primjer. Pokazivač na double nazvat ćemo Pdouble
typedef double *Pdouble;
Pdouble postaje pokazivač na double, pa smijemo pisati:
Pdouble px; /* = double *px */
double x=3.11;
px=&x;
Pokazivači na funkcije
� Kao i ostali pokazivači, i pokazivač na funkciju pokazuje na točno određeni tip funkcije. Pritom se uzima u obzir i povratni tip i lista parametara.
� Primjer: Pokazivač na funkciju koja prima const string referencu i int, a vraća int, deklariramo na sljedeći način:
int (*fptr)( const string &, int ); //pok na funkciju
� Napomena: zagrade oko *fptr su nužne jer je
int *fptr(const string &, int ); //funkcija vra ća pok
� deklaracija funkcije koja ima jednaku listu parametara i vraća pokazivač na int.
Pokazivači na funkcije
� Zbog kompliciranosti sintakse, preporučljivo je uvesti novo ime za pokazivač na funkciju:
typedef int (*fptrType)( const string &, int );
� Sad je fptrType novo ime za pokazivač na funkciju
� Ukoliko koristimo ime funkcije za poziv funkcije, ona se i tretira kao funkcija. U ostalim slučajevima se automatski tretira kao pokazivač na funkciju pripadnog tipa.
Pokazivači na funkcije
� Neka je deklarirana odgovarajuća funkcija:int f( const string &, int );
� Pokazivač na funkciju može se inicijalizirati samo s funkcijom ili pokazivačem na funkciju jednakog tipa ili s konstantnim izrazom jednakim nula.
� Ne postoji konverzija između pokazivača na funkcije različitih tipova (moraju se slagati i lista parametara i povratni tip).
typedef int (*fptrType)(const string &, int );
fptrType ptr1=f;
ptr1=&f;
fptrType ptr2=0;
ptr2=ptr1;
Pokazivači na funkcije
� Možemo uvesti novo ime i za sam tip funkcije.
typedef int (*fptrType)( const string &, int );
typedef int fType( const string &, int );
int f( const string &, int );
fptrType fptr1=f;
fType *fptr2=fptr1;
Pokazivači na funkcije
� Pokazivač na funkciju koji nije inicijaliziran ili ima vrijednost nula ne može se koristiti za poziv funkcije. Pri pozivu funkcije možemo koristiti i pokazivač na funkciju i dereferencirani pokazivač.
int f( const string &s, int a){
cout << s;
return ++a;
}
fptrType fptr=&f;
int a=(*fptr)("Hello",1);
int b=fptr("Hello",1);
Pokazivači na funkcije
� Pokazivač na funkciju može biti i parametar neke funkcije. Pritom su ekvivalentne sljedeće deklaracije:
typedef int (*fptrType)( const string &, int );
void Test( int , int( const string &, int ) );
void Test( int , int (*) ( const string &, int ) );
void Test( int , fptrType );
Pokazivači na funkcije
� Funkcija može i vratiti pokazivač na funkciju. Pritom povratna vrijednost mora biti baš pokazivač, ne može biti sama funkcija.
� Preporučljivo je koristiti typedef za tip pokazivača. Ekvivalentno je:
int (*f( int ))( const string &, int );
fptrType f( int );
� Nije dozvoljeno deklarirati funkciju tipa
fType ff( int );
Pokazivači na funkcije
� Moguće je definirati i polje pokazivača na funkcije.
void f1( int i) { cout<<"Funkciji f1 proslijedili ste broj "<<i; }
void f2( int i ) { cout<<"Funkciji f2 proslijedili ste broj "<<i; }
void f3( int i ){ cout<<"Funkciji f3 proslijedili ste broj "<<i; }
void (*fp[3])( int )={ f1,f2,f3 };
fp[1](13);
Pokazivači na funkcije
� Zadatak:
� Definirane su funkcije
� bool ascending ( int a, int b ) { return a<b ; }
� bool descending ( int a, int b ) { return a>b ; }
� Napišite definiciju funkcije deklariranu s
� void Sort ( int [ ] , const int , bool(*) (int, int));
� koja sortira polje int-ova u rastućem ili padajućem poretku.
� Napišite main koji testira funkciju.
Konverzije
� Izrazi mogu biti sastavljeni od operanada različitih tipova ukoliko se svi mogu konvertirati u odgovarajući zajednički tip.
� Prevoditelj će prije izvršenja operacije izvršiti konverziju operanada u taj zajednički tip.
� Budući da se radi o konverzijama koje se rade bez eksplicitnog zahtjeva od programera govorimo o implicitnim konverzijama.
Konverzije
� Implicitne konverzije se dešavaju u ovim situacijama:
• Kod aritmetičkih, relacijskih i logičkih izraza.
• Kod testiranja u if, while, for i do while naredbama dolazi do konverzije u tip bool.
• Kod izraza pridruživanja (=) dolazi do konverzije u tip varijable na lijevoj strani. Ako pri tome dolazi do gubitka preciznosti, konverzija je svejedno legalna, jedino što će prevoditelj dati upozorenje.
• Kod poziva funkcije, ako stvarni i formalni argumenti nisu istog tipa.
Konverzije
� Najčešće su aritmetičke konverzije. Osnovno pravilo je da dolazi do konverzije u najširi tip u izrazu s ciljem da se sačuva preciznost rezultata.
� Integralna promocija: svi se integralni tipovi manji od int (char, signedchar, unsigned char, short, unsigned short) pretvaraju u int, ako je to moguće, a ako ne, onda u unsigned int.
� Kada se tip bool pretvara u int, onda se true pretvara u 1, a false u nulu.
Konverzije
� Ostale konverzije:
• U većini izraza polje se konvertira u pokazivač na prvi član polja. To se ne dešava kod primjene adresnog operatora i sizeof operatora te kad se poljem inicijalizira referenca na polje.
• U izrazima testiranja pokazivač se pretvara u bool tip: null-pokazivač se konvertira u false, svaki drugi u true.
• Svaki pokazivač se može konvertirati u void *.
• Nula (0) se može konvertirati u pokazivački tip.
• Enumeracija se konvertira u integralni tip koji je strojno zavisan.
• Nekonstantan objekt se može konvertirati u konstantan. Pokazivač na konstantan tip može se inicijalizirati adresom nekonstantnog objekta.
� Napomena: klase definiraju svoje vlastite konverzije.
Konverzije
� Od prevoditelja možemo eksplicitno zahtijevati da napravi konverziju tipova, ukoliko je takva konverzija dozvoljena. U C-u bismo to učinili izrazom
� (T) izraz;
� gdje je T tip u koji konvertiramo izraz.
� Jednako je dozvoljen izraz oblika T(izraz).
� Primjer: izbjegavanje cjelobrojnog dijeljenja.
int x = 3;
int y =4;
double z=3.24;
z = x/y; // cjelobrojno dijeljenje
z = double(x)/y; // realno dijeljenje ili
z = ( double) x/y; // realno dijeljenje
Konverzije (operatori pretvorbe)
� u C++-u, se uvode četiri specijalizirana operatora konverzije:
static_cast<T>(izraz); //ovo smo ve ć u čili
const_cast<T>(izraz);
dynamic_cast<T>(izraz);
reinterpret_cast<T>(izraz);
� Prednost specijaliziranih operatora je bolja dijagnostika grešaka, a ekspresivnija sintaksa omogućava lakše uočavanje eksplicitnih konverzija u kodu.
Konverzije
� static_cast<T> služi za statičku eksplicitnu promjenu tipa:
void *pv = &x;
int *pi;
pi = pv; // Greška pri kompilaciji
pi = static_cast<int *>(pv); // o.k.
� Ovaj je kod ispravan samo ako je varijabla x kojom smo incijaliziralipokazivač pi tipa int. Prevoditelj, općenito, nije u stanju detektirati stvarni tip varijable čija je adresa uzeta. Stoga se greške u uporabi eksplicitnih konverzija pokazuju za vrijeme izvršavanja programa.
Konverzije
� reinterpret_cast<T> služi također za statičku eksplicitnu promjenu tipa: kao i static_cast
� U većini slučajeva oba operatora dati će isti rezultat
� Razlika između ova dva tipa je u načinu kako se tretiraju klase prilikom nasljeđivanja
� Rijetko se koristi, a ima svoje mjesto u sistemskom programiranju.
Konverzije (promjena konstantnosti objekta)
� const_cast<T>(izraz) uklanja konstantnost izraza. Pretpostavimo, na primjer, da imamo funkciju koja uzima nekonstantnu referencu na tip, ali ne mijenja stvarni argument. Tu funkciju ne možemo pozvati s konstantnim stvarnim argumentom.
void f( int& i) { // ... } // ....
const int x = 3;
f(x); // Greška pri kompilaciji
f( const_cast<int &>(x)); // o.k
� const_cast<T> se može primijeniti samo na pokazivačima i referencama.
Konverzije
� dynamic_cast<T>(izraz) se koristi za konverziju pokazivača ili reference na baznu klasu u pokazivač ili referencu na izvedenu klasu.
� Eksplicitne konverzije su opasne jer dovode do grešaka za vrijeme izvršavanja koje nije moguće otkriti za vrijeme kompilacije. One često indiciraju grešku u dizajnu programa i najčašće se mogu izbjeći. Stoga ih treba koristiti u najmanjoj mogućoj mjeri