Hyrje C++pc-sys-albania.weebly.com/uploads/8/1/0/3/8103491/hyrje... · 2018. 10. 4. · Title:...
Transcript of Hyrje C++pc-sys-albania.weebly.com/uploads/8/1/0/3/8103491/hyrje... · 2018. 10. 4. · Title:...
-
Dr. Artan Boriçi
Lenda: Programim
Hyrje ne C++
1 Prej C tek C++
Nuk eshte qellimi te behet histori ne kete paragraf, por per te vendosur urat ndermjet
ketyre dy gjuheve. Ne fakt, emri C++ ka kuptimin zhvillimi i gjuhes C. Ne se do te
krahasonim keto emertime me ate perkatese te Fortranit nuk do te kuptonim se cfare ka
mbetur prej Fortran77 ne Fortran2003. Ndryshe qendron puna me C++: gjuha C eshte
nje nenbashkesi e saj; pra me pak ndryshime te vogla nje kod C mund te kompilohet prej
C++. Atehere cila eshte nevoja te kemi nje gjuhe te re, ose me sakte cfare ka te re ne
C++ qe nuk gjendet ne C?
Nese me ane te C mund te programohet me modelin procedurial C++ ka ndertime qe
lejojne programimin me objekte. Pra, C++ lejon te dy tipet e programimit, por duhet
thene qe programimi procedurial ne C++ do te ishte keqperdorim i kesaj gjuhe, nderkohe
qe kete pune mund ta kryeje vete C. Nga ana tjeter kombinimi i dy tipeve te programimit
e ben C++ nje gjuhe teper te zhdervjellet ne krahasim me gjuhe si Java qe ka hapsire
shume te ngushte (ne fakt nuk i hyn ne pune) per programim procedurial.
1.1 Programi i pare ne C++
// my first program in C++
#include
using namespace std;
int main ()
{
1
-
cout i;
cout
-
2 Ndryshoret dhe tipet e te dhenave
Edhe ne C++ identifikuesit nuk duhet te jene fjale kyce te gjuhes ose te percaktuara si
te tilla prej kompilatorit. Fjalet kyce te rezervuara jane:
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
Nuk lejohen qe operatoret te parqiten si identifikues meqe ato jane fjale te rezervuara:
and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor, xor_eq
3 Tipet themelore te te dhenave
Nje byte eshte sasia minimale e kujteses ne C++. Ne nje byte mund te vendoset nje
sasi relativisht e vogel te dhenash: nje shenje ose nje numer te plote te vogel (zkonisht
prej 0 deri ne 255). Nga ana tjeter, kompjuteri mund te manipuloje tipe te dhenash me
komplekse qe formohen duke u grupuar ne disa byte, sic jane numrat e gjate ose numrat
jo te plote. Me poshte jepet nje liste me tipet ekzisuese themelore te te dhenave ne C++
se bashku me kufinjte e vlerave qe ato mund te marrin:
Tipet themelore te te dhenave
Emri Pershkrimi Madhesia Kufinjte
char Karakter ose i plote i vogel 1byte me shenje: -128 deri 127
pa shenje: 0 deri 255
3
-
int I plote 1fjale me shenje:
-2147483648 deri 2147483647
pa shenje: 0 deri 4294967295
short int
dhe short I plote i shkurter 2bytes me shenje: -32768 deri 32767
pa shenje: 0 deri 65535
long int
dhe long I plote i gjate 4bytes me shenje:
-2147483648 deri 2147483647
pa shenje: 0 deri 4294967295
bool Vlere Booleane 1byte true ose false
float Numer me pike notuese 4bytes 3.4e +/- 38 (7 shifra)
double Numer me pike notuese
me precizion te dyfishte 8bytes 1.7e +/- 308 (15 shifra)
long double Numer i gjate me pike rrjedhese
me precizion te dyfishte 8bytes 1.7e +/- 308 (15 shifra)
wchar\_t Karakter i gjere 2bytes 1 karakter i gjere
Vlerat ne shtyllat Madhesia dhe Kufinjte varen prej arkitektures se sistemit ne te cilin
kompilohet dhe ekzekutohet programi. Vlerat e dhena ketu jane ato qe ndeshen me shpesh
ne sistemet me 32 bit. Po keshtu short dhe long jane praktikisht te njevlereshme me
short int dhe long int.
3.1 Deklarimi i ndryshoreve
Deklarim i eshte i ngjashem si ne C: qe te perdorim nje ndryshore ne C++, do te duhet
qe ta deklarojme me pare ate duke treguar se cfare tipi te dhenash duam qe ajo te jete.
Tipet ‘char, short, long, int’ mund te jene me shenje ose pa shenje. Default per
shumicen e kompilatoreve te C++ eshte deklarimi me shenje. Perjashtim ben ‘char’ qe
konsiderohet i ndryshem prej ‘signed char’ dhe ‘unsigned char’, te cilave u caktohen vlera
4
-
numerike.
Po keshtu ‘signed’ dhe ‘unsigned’ mund te perdoren te vetem me nenkuptimin ‘int
signed’ dhe ‘int unsigned’. Keshtu, deklarimet
unsigned NextYear;
unsigned int NextYear;
jane te njevlefshme.
3.2 Fusha e veprimit te ndryshoreve
Ndryshret mund te jene globale nese ato deklarohen ne fillim te programit perpara cdo
funksioni dhe lokale nese ato deklarohen brenda trupit te nje funksioni:
#include
// Ndryshore Globale
int Integer;
char aCharacter;
char string [20];
unsigned int NumberOfSons;
int main ()
{
// Ndryshore Lokale
unsigned short Age;
float ANumber, AnotherOne;
// Urdhera
cout > Age;
5
-
...
}
Ndryshoreve globale mund tu referohet kudo ne kod madje edhe brenda funksioneve, gjith-
nje pas deklarimit te tyre. Ndresa fusha e ndryshoreve lokale kufizohet brenda blloqeve
te perfshira ne kllapa gjarperueshe.
3.3 Inicializimi i ndryshoreve
Nese duam te inicializojme nje ndryhsore te plote ne castin e deklarimit, atehere do te
shkruajme:
int a = 0;
ose
int a (0);
3.4 Kordat
Kordat ne C++ nuk jane tip themelor por nje klase. Per deklaruar dhe perdorur kordat
duhet te perfshijme nje skedar koke te quajtur ”string” i cili i referohet namespace std,
p.sh.
// shebull kordash
#include
#include
using namespace std;
int main ()
{
string korde = "Kjo eshte nje korde";
cout
-
return 0;
}
3.5 Konstantet
Konstantet ne C++ u nenshtrohen te njejta rregullave si ne. Ne vecanti mund te percak-
tojme konstante me ane te direktives se preprocesorit #define. Po keshtu ato mund te
deklarohen me ane te prefiksit const perpara tipit, si p.sh.
const float pi=3.1415926;
Ne rast se tipi nuk jepet, atehere ai nenkuptohet int.
4 Operatoret
Operatoret ne C++ jane thuajse ato te gjuhes C. Me poshte po japim tabelen permbled-
hese te tyre:
Perparesia Operatori Pershkrimi Grupimi
1 :: fusha Djathtas
2 () [] . -> ++ --
dynamic_cast
static_cast
reinterpret_cast
const_cast typeid prapashtesa Djathtas
3 ++ -- ~ ! sizeof
new delete unar (parashtese)
* & ridrejtimi dhe referneca (treguesit)
+ - operator unar te shenjes Majtas
4 (tipi) ndryshim tipi Majtas
5 .* ->* tregues per tek anetar Djathtas
7
-
6 * / % shumezues Djathtas
7 + - mbledhes Djathtas
8 > zhvendoses Djathtas
9 < > = krahasues Djathtas
10 == != barazisese Djathtas
11 & bit AND Djathtas
12 ^ bit XOR Djathtas
13 | bit OR Djathtas
14 && logjik AND Djathtas
15 || logjik OR Djathtas
16 ?: kushtezues Majtas
17 = *= /= %= += -=
>>=
-
e njejte per çdo shkronje.
6 Tabelat
Ne C, si ne Fortran mund te ndertohen tabela me elemente homogjene te tipave baze.
Keshtu, per te deklaruar nje tabele me 10 elemente ”int” mund te shkruajme:
int x[10];
Te vihet re se ketu perdoren kllapat katrore. Indekset e elementeve do te fillojne nga 0,
ne 9 dhe elementet e ”x”-it do te shkruhen:
x[0], x[1], x[2], ..., x[9]
Ne pergjithesi, nese nje tabele ka ”n” elemente, indekset do te shkojne nga 0, ne ”n-1”.
Mund te ndertohen edhe tabela shumepermasore. P.sh.:
int x[10] [15] [20];
Indekset mund te jene shprehje me vlera te plota. Keshtu mund te kete kuptim pohimi:
n = name[i+j] [1] + name[k] [2];
7 Strukturat e Kontrollit
C++ ka te njejtat struktura kontrolli si ato te gjuhes C:
- kushtezuese: if (condition) statement1 else statement2
- iterative: while (expression) statement
do statement while (condition);
for (initialization; condition; increase) statement;
- perzgjedhese:
switch (expression)
9
-
{
case constant1:
group of statements 1;
break;
case constant2:
group of statements 2;
break;
.
.
default:
default group of statements
}
- pohimet break, continue dhe goto
- funksionin exit te percaktuar ne librarine cstdlib
7.1 Pohimi ”if”; operatoret e krahasimit; pohimet e perbera
Pohimi baze kushtezues ne C/C++ eshte pohimi if:
c = getchar ( );
if ( c == ’?’ )
cout
-
== e barabarte me;
!= e ndryshme nga;
> me e madhe se;
< me e vogel se ;
>= me e madhe e barabarte me;
-
7.2 Pohimi ”while”; Kalimi i vleres brenda nje shprehjeje; po-
himi zero
Mekanizmi baze i ciklimit ne C eshte pohimi ”while”. Ja nje shembull qe kopjon hyrjen e
vet tek dalja e vet duke kojuar karakteret nje e nga nje. Mbani mend se ’\0’ eshte fundi
i skedarit.
main ( ) {
char c;
while ( (c=getchar( )) != ’\0’ )
putchar(c);
}
Sintaksa e pergjithshme eshte:
while ( shprehje ) pohim
Kuptimi i saj eshte:
(a) Vlereso shprehjen.
(b) Nese vlera e saj eshte e vertete, pra jo zero, ekzekuto pohimin dhe kthehu tek (a).
Meqenese shprehja kontrollohet perpara ekzekutimit te pohimit, pjesa e pohimit mud te
ekzekutohet zero here, gje qe eshte shpesh e deshirueshme. Ashtu si tek ”if”, shprehja
dhe pohimi mund te jene sa te duam te nderlikuara. Shembulli yne merr nje karakter, e
kalon tek ”c” dhe pyet nese ai eshte ’\0’. Ne qofte se jo, ekzekutohet pjesa e pohimit, qe
shtyp karkterin. Pastaj ”while” perseritet. Kur me ne fund, karakteri hyres eshte ’\0’,
”while” perfundon, keshtu ben edhe ”main”.
Verejme se eshte perdorur pohimi i kalimt te vleres,
c = getchar( )
brenda nje shprehjeje. Kjo praktike shkurton kodin dhe e ben ate me te qarte (mundohuni
p.sh. te rishkruani kopjimin e skedarit pa perdorur kalimin e vleres brenda nje shprehjeje).
12
-
Kjo menyre funksionon meqe pohimi i kalimit te vleres merr vlere njelloj si çdo shprehje
tjeter. Vlera e saj eshte ajo anes se djathte. Kjo nenkupton se mund te bejme kalime te
shumefishta vlere si me poshte:
x = y = z = 0;
Vleresimi behet nga e djathta ne te majte.
Verejme qe kllapat ne pohimin e kalimit te vleres brenda kushtezimit jane te nevojshme:
ne qofte se do te shkruanim
c = getchar( ) != ’\0’
”c” do te jete 0 ose 1 ne varesi te kapjes ose jo te karakterit tr̈ fundit te skedarit. Kjo, pe
arsye se, ne mungese te kllapave, operatori i kalimit te vleres, ‘=’ vleresohet pas operatorit
krahasues ‘!=’. Ne rast dyshimi, madje edhe nese jo, vendos klappat.
Meqe putchar(c) kthen ”c” si vlere te vet te funksionit, ne edhe mund te kopjojme hyrjen
tek dalja duke nderfutur thirjet per tek getchar dhe puchar:
main( ) {
while( putchar(getchar( )) != ’\0’ ) ;
}
Çfare pohimi eshte duke u perseritur? Asnje, ose me sakte, pohimi zero, meqe e gjithe
puna eshte bere brenda pjeses testuese te ”while”. Ky version eshte pak i ndryshem prej
te meparshmit meqe ‘0” perfundimtare kpjohet tek dalja perpara se ne te vendosim te
ndalojme.
7.3 Klauzola ”else”; Shprehjet kushtezuese
Ne sapo perdorem nje ”else” pas nje ”if”. Forma e pergjithshme e ”if” eshte:
if (shprehje) pohim1 else pohim2
ku pjesa ”else” eshte fakultative por shpesh e dobishme. Shembulli kanonik i jep ”x”-it
vleren e minimumit ndermjet ”a” dhe ”b”:
13
-
if ( a < b )
x = a;
else
x = b;
C jep nje forme kushtezuese alternative qe eshte shpesh me e permbledhur. Quhet “sh-
prehje e kushtezuar” sepse eshte nje kushtezues qe ne te vertete merr nje vlere dhe mund
te perdoret kudo ku mund te perdoret nje shprehje. Vlera e
a
-
Kushtet verifikohen me rradhe dhe vetem nje bllok pohimesh dhe vetem nje ekzekutohet:
ose i pari per te te cilin kenaqet ”if”-i, ose ai i ”else”-it te fundit. Kur perfundon ky
bllok, pohimi tjeter qe ekzekutohet eshte ai pas ”else”-it te fundit. Ne qofte se nuk duhet
ndermarre asnje veprim per rastin default, atehere mos vendos ”else”-in e fundit.
7.4 Pohimi ”for”
Ne nje fare menyre pohimi ”for” eshte nje pohim i pergjithesuar ”while”, qe na lejon qe
te vendosim pjesen inicializuese dhe inkrementuese te nje cikli ne nje pohim te vetem se
bashku me testin. Forma e pergjithshme eshte:
for( inicializim; shprehja; inkrementim )
pohim
Kuptimi eshte sikur te shkruanim:
inicializim;
while( shprehje ) {
pohim
inkrementim;
}
Keshtu, programi i meposhtem mbledh elementet e nje tabele:
sum = 0;
for( i=0; i
-
for( ; ; ) ...
dhe
while( 1 ) ...
jane te dy cikle te pafundem.
Cikli ”for” eshte me kompakt se ”while” sepse e mban kodin aty ku perdoret dhe ngan-
jehere eleminon nevojen e perdorimit te pohimeve te perbera, si ne kete shembull ku
elementet e nje tabele 2-permasore behen zero:
for( i=0; i
-
case ’b’:
bflag++;
break;
case ’c’:
cflag++;
break;
default:
cout
-
case ’a’:
aflag++;
break;
case ’b’:
bflag++;
break;
...
}
// pohimet break na sjellin direkt ketu
Pohimi ‘break’ funksionon edhe per pohimet ‘while’; ai shkakton daljen e menjehershme
prej ciklit.
Pohimi ‘continue’ ecen vetem brenda ‘for’ dhe ‘while’; ai ben qe te filloje iteracioni
i radhes ne cikel. Ne mund te kishim perdorur nje ‘continue’ ne shmebullin tone per te
vazhduar iteracionin tjeter te ‘for’, por perdorimi i ‘break’ ne vend te tij duket me i qarte.
8 Operatoret e rritjes dhe zvogelimit
Gjuhet C/C++ permbajne operatoret ”++” (rritja me 1) dhe ”- -” (zvogelimi me 1).
Supozojme se duam te numerojme rreshtat ne nje skedar.
main( ) {
int c,n;
n = 0;
while( (c=getchar( )) != ’\0’ )
if( c == ’\n’ )
++n;
cout
-
++n eshte ekuivalente me n=n+1 por eshte me e qarte, veçanerisht kur n eshte shprehje e
nderlikuar. Duhet thene veç se keto operatore vlejne vetem per tipat ”int” dhe ”char”
(dhe per treguesit, qe do te shqyrtohen me tej).
Operatoret e ketij tipi mund te vihen si para, ashtu edhe prapa ndryshores qe do t’i
ndryshohet vlera. Ndryshimi vihet re nese kalojme kete e vlere tek nje ndryshore tjeter.
Keshtu,
x = ++k;
do te thote qe ne fillim ”k”-se do t’i rritet vlera me 1, dhe pastaj kjo vlere do t’i jepet
”x”-it. Nese shkruajme:
x = k++;
atehere, ne fillim ”x”-it i jepet vlera e ”k”-se dhe pastaj ”k”-se i rritet vlera me 1. Ne te
dy rastet, per variablen ”k” nuk ka rendesi nese operatori i qendron para apo pas. Dallimi
behet ne vleren qe merr ”x”-i.
9 Funksionet
Funksionet bejne te mundur strukturimin e nje programi ne forme modulare. Ashtu si ne
C argumentat pasohen me vlere ose me reference kur perdorim treguesit. Nje funksion
duhet te jete percaktuar perpara se te mund t’i referohemi perndryshe ata duhet qe
patjeter te deklarohen me para dhe te percaktohen pas main. Argumentave mund t’i
caktojme edhe vlera default si ne shembullin e meposhtem:
// vlerat default ne funksione
#include
using namespace std;
int divide (int a, int b=2)
{
19
-
int r;
r=a/b;
return (r);
}
int main ()
{
cout
-
{
int x=5,y=2;
float n=5.0,m=2.0;
cout
-
return (a * factorial (a-1));
else
return (1);
}
int main ()
{
long number;
cout > number;
cout
-
b = &a;
vendos adresen e ‘a’ tek ‘b’. Ne nuk mund te bejme shume me kaq pervec se ta printojme
ate ose t’ia kalojme nje rutine tjeter. Kjo per aresye se ‘b’-se nuk i kemi dhene deklarimin
e duhur. Por ne qofte se deklarojme ‘b’ si tregues tek nje numer i plote, atehere jemi ne
gjendje te mire:
int a, *b, c;
b = &a;
c = *b;
Tani ‘b’ permban adresen e ‘a’ dhe ‘c = *b’ do te thote te perdoret vlera e ‘b’-se si nje
adrese, d.th. si nje tregues. Efekti eshte qe ne te marrim perseri permbajtejen e ‘a’-se,
ndonese ne menyre indirekte. (Eshte gjithnje e vertete se ‘*&x’ eshte e njejte me ‘x’ ne
rast se ’x’ ka nje adrese.)
Perdorimi me i shpeshte i treguesve ne C eshte shetitja e shpejte pergjate tabelave.
Ne fakt, emri i nje tabele ne C perfqason adresen e elementit zero te saj, prandaj ajo
nuk mund te vendoset ne anen e majte te nje shprehjeje. (Adresa e diçkaje nuk mund te
ndryshohet duke kaluar vlera tek ajo.) Ne se shkruajme:
char *y;
char x[100];
‘y’ eshte i tipit tregues tek karakter (megjithese nuk tregon ende ne ndonje vend). Ne
mund ta bejme ‘y’ te tregoje tek nje element i ‘x’-it me dy menyra:
y = &x[0];
y = x;
Meqe ‘x’ eshte adresa e ‘x[0]’ menyra e dyte eshte legale dhe konsistene. Nga ana tjeter
‘*y’ jep ‘x[0]’. Per me teper,
*(y+1) /* jep x[1] */
*(y+i) /* jep x[i] */
23
-
ndersa vargu i pohimeve,
y = &x[0];
y++;
ben qe ‘y’ te tregoje tek ‘x[1]’.
Le te perdorim treguesit ne nje funksion ‘length’ qe llogarit gjatesine e nje tabele
karakteresh. Rikujtojme qe me mareveshje te gjithe tabelat e karaktereve mbarojne me
‘\0’. (Ne qofte se jo programi do te rrezohet patjeter). Menyra e vjeter eshte:
length(s)
char s[ ]; {
int n;
for( n=0; s[n] != ’\0’; )
n++;
return(n);
}
Duke e rishkruar me tregues:
length(s)
char *s; {
int n;
for( n=0; *s != ’\0’; s++ )
n++;
return(n);
}
Tani mund te kuptohet perse duhet te percaktojme ne cfare gjeje ‘s’ tregon: nese do ta
shtonim ate me ane te ‘s++’ atehere do ta shtonim me sasine e duhur.
Versioni me tregues eshte me eficient (kjo eshte thuajse gjithnje e vertete), ndersa edhe
me kompakt eshte:
24
-
for( n=0; *s++ != ’\0’; n++ );
‘*s’ kthen nje karakter; ‘++’ shton treguesin me nje, ne kete menyre marrim karakterin
e radhes heren tjeter. Sic mund te shihet, duke i bere gjerat me eficiente i bejme ato
njekohesiht me pak te qarta. Por ‘*s++’ eshte nje idom kaq e perdorur saqe duhet te
mesohet patjeter.
Duke shkruar nje hap perpara, ketu eshte funksioni ‘strcopy’ qe kopjon tabelen e
karaktereve ‘s’ tek nje tjeter ’t’:
strcopy(s,t)
char *s, *t; {
while(*t++ = *s++);
}
Testi perkundrejt ‘\0’ eshte lene menjeane, meqe ‘\0’ eshte identikisht zero; nje kodim te
tille ndeshet shpesh. (Duhet vendosur hapsire prapa ‘=’: shih paragrafet qe vijojne).
Per argumentet e nje funksioni dhe vetem per to deklarimet
char s[ ];
char *s;
jane te njevlefshme: nje tregues tek nje tip ose nje tabele me madhesi te papercaktuar
jane e njejta gje.
Nese e gjitha kjo duket misterioze, eshte mire te mesohen keto forma permendesh deri
sa te behen natyre e dyte. Shpesh nuk duhet dicka me e nderlikuar.
10.1 Treguesit tek Funksionet
Tregimi tek funksionet perdoret kryesisht per te pasuar nje funksion si argument te nje
funksioni tjeter. Per te deklaruar nje tregues tek nje funksion duhet te deklaruar prototipi
i funksionit me emrin e funksionit te futur brenda kllapave dhe te filluar me nje yll:
// tregues tek funksionet
25
-
#include
using namespace std;
int mbledhje (int a, int b)
{ return (a+b); }
int zbritje (int a, int b)
{ return (a-b); }
int veprimi(int x, int y, int (*funksioni_qe_thirret)(int,int))
{
int g;
g = (*funksioni_qe_thirret)(x,y);
return (g);
}
int main ()
{
int m,n;
int (*minus)(int,int) = zbritje;
m = veprimi(7, 5, mbledhje);
n = veprimi(20, m, minus);
cout
-
11 Kujtesa dinamike
Kujtesa dinamike sherben per te ndryshuar kerkesen per kujtese gjate ekzekutimit te
programit. Per te bere te mundur kete perdoret operatori new pasuar prej tipit, p.sh.
udhezimet:
int * ndim;
ndim = new int [5];
bejne qe te caktohet kujtese ne menyre dinamike per pese elemente te plote e te kthehet
nje tregues, ndim, qe tregon tek elementi i pare i plote ne kujtese. Ne qofte se sistemi nuk
ka kujtese te lire atehere programi ndalon automaikisht. Ne rast se duam qe kjo te mos
ndodhe shkruajme:
int * ndim;
ndim = new (nothrow) int [5];
if (ndim == 0) {
// tregues zero: programi nuk mund te caktoje kujtese; merr masa te tjera
};
Pra, vendosja e (nothrow) pas new ben qe ne rast se nuk ka kujtese te lire, ndim i caktohet
treguesi zero, pra nje tregues qe nuk tregon ne asnje vend ne kujtese.
Per te kthyer mbrapsht veprimin e krijimit te kujteses perdoret operatori delete,
p.sh.:
// kujtesa dinamike
#include
using namespace std;
int main ()
{
int i,n;
27
-
int * p;
cout > i;
p= new (nothrow) int[i];
if (p == 0)
cout
-
typedef char fushe [50];
Ne kete rat kemi kater tipe te dhenash, C, FJALE, tregues_char, fushe perkatesisht
si char, unsigned int, char*, char[50] te cilet mund te perdoren per deklarime.
typedef perdoret per shkurtime te tipeve qe perdoren shpesh ne nje program, ose kur
duam qe me vone te ndryshojme tipin dhe te evitojme redaktimin e tekstit.
12.2 Strukturat
Perdorimi kryesor i strukturave eshte per te grupuar nje sere ndryshoresh te tipeve te
ndryshem qe te mund te trajtohen si nje njesi e vetme. P.sh. nese do te shkruanim nje
kompilator ose asembler, do te mund te na duhet per cdo identifikues nje infomacion se
emri (tabele karakteresh), numri i rreshtit burimor (i plote), nje lloj informacioni (ndoshta
nje karakter) dhe me gjase nje numerator perdorimi (nje tjeter i plote):
char id[10];
int line;
char type;
int usage;
Me to ne shume thjeshte mund te bejme nje strukture si me poshte. Fillimisht i tregojme
C si do te dukej nje strukture, pra cfare lloj gjerash ajo permban; me pas ne mund
te rezervojme kujtese per te ose ne te njejtin pohim ose veçmas. Me e thjeshta eshte
percaktimi dhe rezerivimi i kujteses e gjitha njeheresh:
struct {
char id[10];
int line;
char type;
int usage;
} sym;
29
-
Ketu perkufizohet struktura ‘sym’ me formen e dhene; id, line, type, usage jane
pjestare te struktures. Ne mund t’i referohemi nje pjestari si me poshte:
emer-strukture . emer-pjestari
Keshtu p.sh. mund te shkruajme:
sym.type = 077;
if( sym.usage == 0 ) ...
while( sym.id[j++] ) ...
etj.
Megjithese emrat e pjestareve pasojne emrin e struktures, ato duhet te jene te vetem dhe
nuk mund te perdoren ne ndonje strukture tjeter.
Deri tani nuk kemi ndonje avantazh te perdorimit te strukturave. Ai duket kur kemi
tabela strukturash, apo kur deshirojme te kalojme te dhena te komplikuara ndermjet
funksionesh. Supozojme se duam te ndertojme nje tabele 100 vendeshe ku elementet te
jene bashkesi vlerash te ndryshoreve p.sh. char[10], line, type, usage. Pa perdorur
struktura, do te ishim te detyruar t’i shtonim te gjitha ndryshoreve edhe nje permase me
100 vende:
char id[100][10];
int line[100];
char type[100];
int usage[100];
Nese ndryshoret fillestare vihen ne strukture, kjo na lejojn qe te riorganizojme informa-
cionin ne menyre qe te gjitha te dhenat rreth nje emri te vetem (atij te struktures), te
jene bashke:
struct {
char id[10];
int line;
30
-
char type;
int usage;
} sym[100];
Ky deklarim ben qe ”sym” te jete nje tabele strukturash (identike ne forme) me 100
elemente;çdo element i tabeles i ka te gjithe pjesetaret e struktures (por vlerat e tyre
mund te jene te ndryshme). Ne mund t’i referohemi elementeve te veçante si me poshte:
sym[i].usage++; /* inkremento ‘usage’ te elementit te i-te te ‘sym’ */
for( j=0; sym[i].id[j++] != ’\0’; ) ...
etj.
Keeshtu per te printuar nje liste identifikuesish qe nuk jane perdorur se bashku me numrin
e tyre te rreshtit,
for( i=0; i
-
main( ) {
...
if( (indeks = shiko(emer)) >= 0 )
sym[indeks].usage++; /* rrit me 1 "usage" te "sym[indeks]" */
else
install(emer, newline, newtype);
...
}
shiko(s)
char *s; {
int i;
extern struct {
char id[10];
int line;
char type;
int usage;
} sym[ ];
for( i=0; i 0 )
return(i);
return(-1);
}
krahaso(s1,s2) /* kthe 1 nese s1==s2, perndryshe kthe 0 */
char *s1, *s2; {
while( *s1++ == *s2 )
if( *s2++ == ’\0’ )
32
-
return(1);
return(0);
}
Ne fakt deklarimi i struktures ne funksionin ”shiko” s’eshte i nevojshem, nese deklarimi i
saj i jashtem gjendet para perdorimit te saj ne kodin burimor te skedarit, siç do te shohim
me tej.
Tani le te shohim versionin me tregues:
struct symtag {
char id[10];
int line;
char type;
int usage;
} sym[100], *psym;
psym = &sym[0]; /* inicializim i "psym" */
Kjo e ben ‘psym’ nje tregues strukture te te njejtit lloj me tabelen ‘sym’ dhe e inicializon
ate qe te tregoje adresen e elementit te pare te ‘sym’.
E njejta gje mund te behej ne dy hapa. Ne te parin:
struct symtag {
... trupi i struktures
};
dhe pastaj te shkruanim:
struct symtag sym[100];
struct symtag *psym;
qe e deklaron tabelen dhe treguesin dhe qe mund te shkruhet edhe:
struct symtag sym[100], *psym;
33
-
Menyra me te cilen i referohemi nje pjestari te nje strukture me tregues eshte si me poshte:
treg -> pjestar-strukture
Simboli ‘-¿’ tregon qe po tregojme tek nje anetar i nje strukture; ‘-¿’ perdoret ne ate
kontekst. treg eshte tregues tek (baza e) struktures qe permban anetain e struktures.
Keshtu mund te kemi konstruksione si:
psym->type = 1;
psym->id[0] = ’a’;
etj.
Per shprehje me te komplikuara eshte mire qe te vendosen kllapat. P.sh.:
struct { int x, *y; } *p;
p->x++ inkrementon x
++p->x e njejta gje behet edhe ketu!
(++p)->x inkrementon p para se te merret x
*p->y++ perdor y si tregues, dhe pastaj e inkrementon ate
*(p->y)++ e njejta gje behet edhe ketu
*(p++)->y perdor y si tregues, dhe pastaj inkrementon p
Menyra e mbajtjes mend te ketyre eshte se -¿, ., ( ) dhe [ ] jane te lidhura ngushte.
Nje shprehje qe permban nje prej tyre trajtohet si nje njesi. p-¿x, a[i], y.x dhe f(b) jane
ekzaktesisht emra sic eshte ‘abc’.
Nese ‘p’ eshte tregues i nje strukture, veprimet aritmetike mbi ‘p’ marrin ne kon-
siderate madhesine struktures. Per shmbull, ‘p++’ inkrementon ‘p’ ne menyre qe te
merret elementi pasardhes i tabeles se strukturave. Duhet pasur parasysh se madhesia e
struktures NUK eshte shuma e madhesive te elementeve te saj: per aresye te shtrirjes se
objekteve te permasave te ndryshme, ne struktura mund te kete ”hapsira boshe”.
Me sa u tha, jemi ne gjendje te kalojme ne versionin me trregues te shembullit te
meparshem.
34
-
struct symtag {
char id[10];
int line;
char type;
int usage;
} sym[100];
main( ) {
struct symtag *shiko( );
struct symtag *psym;
...
if( (psym = shiko(emer)) ) /* tregues jo-zero */
psym -> usage++; /* tregon se gjendet */
else
install(emer, newline, newtype);
...
}
struct symtag *shiko(s)
char *s; {
struct symtag *p;
for( p=sym; p < &sym[nsym]; p++ )
if( krahaso(s, p->id) > 0)
return(p);
return(0);
}
Funksioni ‘krahaso’ nuk ndryshon. ‘p -> id’ i referohet nje korde karakteresh.
Ne ‘main’ testohet treguesi i kthyer prej ‘shiko’ perkundrejt zeros, duke u bazuar
ne faktin se nje tregues eshte me perkufizim gjithnje jozero kur ai tregon tek diçka.
35
-
Manipulimet e tjera me tregues jane triviale.
I vetmi kompleksitet eshte bashkesia e rreshtave si:
struct symtag *shiko( );
Kjo na sjell ne nje fushe qe do e trajtojme nxitimthi; çeshtja e tipeve te funksionit.
Deri tani gjithe funksionet e trajtuara na kthenin numra te plote (ose karaktere qe eshte
thuajse e njejta gje). Si te bejme kur funksioni kthen diçka tjeter, p.sh. nje tregues te nje
strukture? Rregulli eshte qe funksioni qe s’kthen nje numer te plote, duhet ta deklaroje
se çfare do ktheje. Informacioni per tipin shkon para emrit te funksionit (qe e ben emirn
te veshtire per tu pare).
Shembuj:
char f(a)
int a; {
...
}
int *g( ) { ... }
struct symtag *shiko(s) char *s; { ... }
Funksioni ‘f’ kthen nje karakter, ‘g’ kthen nje tregues mbi nje te plote dhe ‘shiko’ kthen
nje tregues mbi nje strukture te se njejtes forme me ”symtag”. Nese do t’i perdorim keto
funksione, duhet te bejme nje deklarim aty ku do t’i perdorim ato (siç u be ne funksionin
kyesor te shembullit me lart).
Te vihet re paralelizmi ndermjet deklarimeve
struct symtag *shiko( );
struct symtag *psym;
Kjo tregon se ‘shiko’ dhe ‘psym’ perdoren te dy ne te njejten menyre - si treguesa te nje
strukture, paçka se njeri eshte funksion dhe tjetra ndryshore.
36
-
12.3 Bashkimet
Bashkimet lejojne referimin e se njejtes pjese kujtese me ane te tipeve te ndryshme te te
dhenave. Deklarimi i tyre eshte i ngjashem me strukturat megjithese funksionaliteti eshte
krejt i ndryshem:
union emer_bashkimi {
tipi_1 anetar_1;
tipi_2 anetar_2;
tipi_3 anetar_3;
.
} emer_objekti;
Anetaret e nje deklarimi union zene te njejten hapsire fizike ne kujtese. Madhesia e saj
eshte ajo e anetarit me te madhe branda deklarimit, p.sh.:
union tipet {
char c;
int i;
float f;
} mytypes;
percakton tre elemente:
mytypes.c
mytypes.i
mytypes.f
secili me tipe te ndryshme te dhenash. Meqe ata i referohen te njetit vend nje kujtese,
ndryshimi i permbajtjes se njerit do te sjelle ndryshimin e gjithe te tjereve. Nje nga per-
dorimet e union eshte bashkimi i nje tipi elementar me nje tabele strukturash elementesh
me te vegjel, p.sh.:
37
-
union mix_t {
long l;
struct {
short lart;
short poshte;
} s;
char c[4];
} mix;
percakton tri emra qe lejojne referimin e te njejtit grup prej 4 byte: mix.l, mix.s,
mix.c. Keto mund te perdoren sipas menyres qe ne duam t’i referohemi ketyre 4 byte:
sikur te ishin nje tip i vetem long, sikur te ishin dy elemente short apo nje tabele
char. Rreshtimi real dhe rendi i anetareve te nje bashkimi varet prej platofrmes, prandaj
problemi i portabilitetit te kodit duhet patur parasysh kur perdorim union.
12.4 Bashkimet Anonime
Ne rast se nje bashkim deklarohet pa emer, ate here ai eshte anonim. Kjo ben qe te
mund t’i referohemi anetareve te tij ne menyre te drejtperdrejte. Ndryshimi i vetem ne
deklarimet
// strukture me bashkim te zakonshem
struct {
char titulli[50];
char autori[50];
union {
float dollare;
int euro;
} cmimi;
} liber;
dhe
38
-
// strukture me bashkim anonim
struct {
char titulli[50];
char autori[50];
union {
float dollare;
int euro;
};
} liber;
eshte fakti qe emri i bashkimit mungon ne rastin e dyte. Kjo ben qe referimi i anetareve
dollare dhe euro te nje objekti te ketij tipi ne rastin e pare te behet:
liber.cmimi.dollare
liber.cmimi.euro
ndersa ne rastin e dyte behet:
liber.dollare
liber.euro
Rikujtojme se dollare dhe euro jane anetare te nje bashkimi qe zene te njejten hapsire
fizike ne kujtese, prandaj nuk mund te perdoren per te vendosur vlera te ndryshme ne te
njejten kohe.
12.5 Numratoret (enum)
Numratoret krijojne nje tip te ri te dhenash qe nuk kufizohet ne tipet themelore, pra eshte
nje tip ktejt i ri. Forma e tyre eshte:
enum emer_numratori {
vlera1,
vlera2,
39
-
vlera3,
.
} emer_objekti;
P.sh. duam te krijojme nje tip te ri ndryshoreje qe permban ngjyrat:
enum ngjyre {gjelber, kuq, blu};
Verjem se nuk kemi asnje tip themelor ne kete deklarim. Vlerat e mundshme qe merr
ky tip jane ato konstante te vendosura brenda kllapave gjarperueshe. Keshtu, me ty
deklaruar ky tip pohimet e meposhteme jane legale:
ngjyre ngjyra1;
ngjyra1 = blu;
if (ngjyra1 == gjelber) ngjyra1 = red;
Numratoret jane kompatibel ne tip me ndryshoret numerike; kjo do te thote qe konstan-
teve u vihet ne korrespondence nje vlere e plote. Ne rast se nuk jepet, nenkuptohet qe
vlera e pare shorerohet me 0, e dyta me +1, etj. Po ne kemi ne dore te ndryshojme keto
vlera te plota si p.sh.:
enum muaji { janar=1, shkurt, mars, prill, maj, qershor, korrik, gusht, shtator, tetot,
Ne kete rast ndryshorja viti i tipit numrator muaji mund te permbaje nje prej vlerave
te mesiperme qe jane ekuivalente me vlerat prej 1 deri ne 12 (dhe jo prej 0 deri ne 11
sepse ne e vendosem janarin te barabarte me 1).
13 Hyrje tek Klasat
Klasa eshte nje koncept i zgjeruar i nje strukture te dhenash: ajo mban jo vetem te dhena
por edhe funksione. Nje objekt eshte konkretizimi i nje klase. Ne analogji me ndryshoret,
klasa do te ishte tipi ndersa objekti do te ishte ndryshorja. Ato deklarohen si me poshte:
40
-
class emri_i_klases {
fusha_e_veprimit_1:
anetar_1;
fusha_e_veprimit_2:
anetar_2;
...
} emrat_e_objekteve;
Anetaret jane te dhena ose funksione, ndersa fusha e veprimit eshte tri llojesh:
private, referohet vetem prej anetareve te se njejtes klase ose miqve te tyre;
protected, referohet prej anetareve te se njejtes klase ose miqve te tyre si edhe prej
anetareve te klasave qe rrjedhin prej tyre;
public, referohet prej secilit vend ku shfaqet objekti.
Kur nuk jepet fusha e veprimit, ajo nenkuptohet te jete private, p.sh.
class Klase_Drejtkendeshash {
int x, y;
public:
void vendos_vlerat (int,int);
int siperfaqe (void);
} drejtkendesh;
deklaron ‘drejtkendesh’ si objekt te klases ‘Klase Drejtkendeshash’. Anetaret e kesaj klase
jane: ndryshoret ‘x’ dhe ‘y’ me fushe referimi private, meqe fusha e veprimit nuk jepet;
funksionet publike ‘vendos vlerat’ dhe ‘siperfaqe’.
Anetaret publike te objekteve mund te referohen kudo ne program njelloj si te dhenave
ne nje strukture te dhenash, p.sh.:
drejtkendesh.vendos_vlerat(3,4);
siperfaqja_ime=drejtkendesh.siperfaqe();
41
-
Te dhenat ‘x’ dhe ‘y’ nuk mund te referohen jashte trupit te klases.
Me poshte kemi shembullin e plote te klases ‘Klase Drejtkendeshash’:
// Shembull klasash 1
#include
using namespace std;
class Klase_Drejtkendeshash {
int x, y;
public:
void vendos_vlerat (int,int);
int siperfaqe () {return (x*y);}
};
void Klase_Drejtkendeshash::vendos_vlerat (int a, int b) {
x = a;
y = b;
}
int main () {
Klase_Drejtkendeshash drejtkendesh;
drejtkendesh.vendos_vlerat(3,4);
cout
-
ndryshoret brenda funksionit kane te njejten fushe veprimi sikur funksionit te jete perku-
fizuar brenda klases. Keshtu, ndryshoret ‘x’ dhe ‘y’ jane ndryshore private te klases
‘Klase Drejtkendeshash’ dhe si te tilla mund te referohen vetem brenda klases, pra edhe
ne funksionin ‘vendos vlerat’, anetar i kesaj klase.
Deklarimi jashte klases behet shpesh per ta bere kodin me te lexueshem. Nga ana
tjeter, nese deklarimi brenda klases e ben nje funksion inline, deklarimi jashte eshte si cdo
deklarim tjeter i zakonshem funksioni.
Per nje klase ne mund te deklarojme nje ose me shume objekte, p.sh.:
// Shembull klasash 2
#include
using namespace std;
class Klase_Drejtkendeshash {
int x, y;
public:
void vendos_vlerat (int,int);
int siperfaqe () {return (x*y);}
};
void Klase_Drejtkendeshash::vendos_vlerat (int a, int b) {
x = a;
y = b;
}
int main () {
Klase_Drejtkendeshash drejtkendesh, drejtkendesh1;
drejtkendesh.vendos_vlerat(3,4);
drejtkendesh1.vendos_vlerat(5,6);
cout
-
cout
-
class Klase_Drejtkendeshash {
int gjeresi, gjatesi;
public:
Klase_Drejtkendeshash (int,int);
int siperfaqe () {return (gjeresi*gjatesi);}
};
Klase_Drejtkendeshash::Klase_Drejtkendeshash (int a, int b) {
gjeresi = a;
gjatesi = b;
}
int main () {
Klase_Drejtkendeshash drejtkendesh (3,4);
Klase_Drejtkendeshash drejtkendesh1 (5,6);
cout
-
caktohet ne menyre dinamike dhe leshohet duhet perdorur fshirjen e nje operatori.
Desktruktori duhet te kete te njetin emer te klases, te filluar me shenjen gjarperueshe
‘ ’. Po keshtu ai nuk duhet te kete asnje tip kthimi.
Perdorimi i destruktoreve eshte veçanerisht i pershtshem kur nje objekt cakton kujtese
dinamike gjate kohes se tij te ekzistences dhe ne momentin e shkaterimit te tij ne duam
te leshojme kujtesen qe i ishte zene objektit.
// Shembull klasash 4: konstruktoret dhe destruktoret
#include
using namespace std;
class Klase_Drejtkendeshash {
int *gjeresi, *gjatesi;
public:
Klase_Drejtkendeshash (int,int);
~Klase_Drejtkendeshash ();
int siperfaqe () {return (*gjeresi * *gjatesi);}
};
Klase_Drejtkendeshash::Klase_Drejtkendeshash (int a, int b) {
gjeresi = new int;
gjatesi = new int;
*gjeresi = a;
*gjatesi = b;
}
Klase_Drejtkendeshash::~Klase_Drejtkendeshash () {
delete gjeresi;
delete gjatesi;
}
46
-
int main () {
Klase_Drejtkendeshash drejtkendesh (3,4), drejtkendesh1 (5,6);
cout
-
gjeresi = 5;
gjatesi = 5;
}
Klase_Drejtkendeshash::Klase_Drejtkendeshash (int a, int b) {
gjeresi = a;
gjatesi = b;
}
int main () {
Klase_Drejtkendeshash drejtkendesh (3,4);
Klase_Drejtkendeshash drejtkendesh1;
cout
-
int a,b,c;
void multiply (int n, int m) { a=n; b=m; c=a*b; };
};
kompilatori nenkupton se ‘CExample’ ka nje konstruktor default. Ne kete menyre objektet
e kesaj klase mund te deklarohen pa argumenta:
CExample ex;
Por sapo te jete deklaruar prej vete nesh nje konstruktor per klasen, kompilatori nuk na
me asnje konstruktor default nga ane e tij. Keshtu nqse shkruajme:
class CExample {
public:
int a,b,c;
CExample (int n, int m) { a=n; b=m; };
void multiply () { c=a*b; };
};
do duhet te deklarojme objektet si p.sh.:
CExample ex (2,3);
Ndersa, deklarimi:
CExample ex;
do te ishte i gabuar ne kete rast, meqenese ne kemi deklaruar konstruktorin ne menyre
eksplicite per klasen ne fjale.
Pervec krijimit te nje konstruktori default, kompilatori deklaron ne menyre implicite
edhe tre anetare te tjere, te cilet jane funksionet e veçanta: konstruktori i kopjimit,
operatori i caktimit per kopjimin dhe destruktori default.
Konstruktori i kopjimit dhe operatori i caktimit per kopjimin kopjojne te gjitha
te dhenat qe ndodhen ne nje objekt tjeter tek te dhenat e objektit aktual. Prandaj,
deklarimet e meposhteme te dy objekteve do te jene korrekte:
49
-
CExample ex (2,3);
// konstruktori implicit i kopjimit (te dhenat kopjohen prej ex):
CExample ex2 (ex);
Treguesit tek Klasat
Treguesit mund te tregojne edhe tek klasat. Kjo per aresyen e thjeshte se, me tu deklaruar,
klasat perbejne nje tip me vete. Keshtu, p.sh.
Klase_Drejtkendeshash *tregues_drejtkendesh
eshte nje tregues tek nje objekt i klases ‘Klase Drejtkendeshash’. Ashtu si me strukturat
e te dhenave, per t’iu referuar drejtperdrejt nje anetari te nje objekti te treguar prej nje
treguesi ne mund te perdorim operatorin shigjete ’−¿’. Le te japim nje shembull me disa
kombinime:
// Klasa Shembull 6: tregues tek klasat
#include
using namespace std;
class Klase_Drejtkendeshash {
int gjeresia, gjatesia;
public:
void vendos_vlerat (int, int);
int siperfaqe (void) {return (gjeresia * gjatesia);}
};
void Klase_Drejtkendeshash::vendos_vlerat (int a, int b) {
gjeresia = a;
gjatesia = b;
}
50
-
int main () {
Klase_Drejtkendeshash a, *b, *c;
Klase_Drejtkendeshash * d = new Klase_Drejtkendeshash[2];
b= new Klase_Drejtkendeshash;
c= &a;
a.vendos_vlerat (1,2);
b->vendos_vlerat (3,4);
d->vendos_vlerat (5,6);
d[1].vendos_vlerat (7,8);
cout
-
shprehja lexohet
*x tregohet prej x
&x adresa e x
x.y anetari y i objektit x
x->y anetari y i objektit te treguar prej x
(*x).y anetari y i objektit te treguar prej by x (ekuivalent me paraardhesin)
x[0] objekti i pare i treguar prej x
x[1] objekti i dyte i treguar prej x
x[n] objekti i (n+1)-te i treguar prej x
Klasat e perkufizuara me struct dhe union
Klasat mund te perkufizohet edhe prej fjaleve kyçe struct dhe union.
Konceptet e klases dhe struktures se te dhenave jane aq te ngjashme sa qe te dy keto
fjale kyçe kane ne C++ te njejtin funksionalitet perveç se anetaret e klases se deklaruar
me struct kane fushe veprimi publike ne vend te asaj private qe kane ato te deklaruar
me class. Ky eshte ndryshimi i vetem, perndryshe per çdo qellim tjeter te dyja fjalet
kyçe jane ekuivalente.
Koncepti brenda union eshte i ndryshem prej atij te stuct dhe class. Kjo per aresyen
se union i vendos te dhenat, anetare te saj, nje e nga nje ne kujtese. Por ato jane edhe
klasa dhe si te tilla mund te permbajne funksione si anetare te tyre. Refernca deafault ne
union eshte publike.
52
-
14 Klasat II
14.1 Operatoret e Mbingarkuar
C++ permban mundesine e perdorimit te operatoreve standarte per te kryer veprime me
klasa perveç atyre me tipet themelore. P.sh.
int a, b, c;
a = b + c;
eshte kod i vlefshem ne C++ meqe ndryshoret e ndryshme te mbledhjes jane te gjitha
te tipeve themelore. Kjo nuk do te thote qe kjo te shtrihet menjehere edhe per raste si i
meposhtemi:
struct {
string product;
float price;
} a, b, c;
a = b + c;
Rreshti i fundit do te shkaktoje gabim ne kompilim meqe nuk kemi treguar si do te sillet
klasa ne lidhje me operatorin e mbledhjes. Kjo arrihet me ane te mbingarkimit te opera-
toreve, nje tipar i C++, me ane te te cilit ne mund te pershkruajme klasa qe te kryejne
veprime me operatoret standarte. Me poshte jepet lista e operatoreve te mbingarkueshem:
Overloadable operators
+ - * / = < > += -= *= /= >
= == != = ++ -- % & ^ ! |
~ &= ^= |= && || %= [] () , ->* -> new
delete new[] delete[]
Per te mbingarkuar nje operator deklarohen funksionet operatoriale, te cilet jane funk-
sione te zakonshme emrat e te cileve jane fjala kyce operator e ndjekur prej shenjes se
operatorit qe duam te mbingarkojme:
53
-
tipi operator shenja (parametra) { /*...*/ }
Me poshte jepet nje shembull i mbingarkimit te operatorit te mbledhjes (+) per klasen e
vektoreve me dy permasa:
// Vektoret: shmebull i mbingarkimit te operatoreve
#include
using namespace std;
class CVector {
public:
int x,y;
CVector () {};
CVector (int,int);
CVector operator + (CVector);
};
CVector::CVector (int a, int b) {
x = a;
y = b;
}
CVector CVector::operator+ (CVector param) {
CVector temp;
temp.x = x + param.x;
temp.y = y + param.y;
return (temp);
}
int main () {
54
-
CVector a (3,1);
CVector b (1,2);
CVector c;
c = a + b;
cout
-
i perfshire ne main() nuk do te ishte i vlefshem nese nuk do te kishim kontruktorin bosh,
pra ate pa parametra.
Ne fakt, me mire do te ishte qe konstruktori te kishte brenda bllokut nje percaktim te
ndryshoreve te anetarit:
CVector () { x=0; y=0; };
Kjo nuk u be me lart per te thejshtuar leximin e kodit ne ndihme te ilustrimit te konceptit
te mbingarkimit.
Pervec konstruktorit deafult dhe konstruktorit te kopjimit, ne rastin kur keto nuk
deklarohen, klasat perfshijne edhe nje percaktim deafult te operatorit te caktimit (=) me
klasen vete si parameter:
CVector d (2,3);
CVector e;
e = d; // operatori i caktimit per kopjimin
Operatori i caktimit per kopjimin eshte i vetmi anetar funksion operatorial i ndertuar ne
menyren deafult. Natyrisht, ai mund te ripercaktohet per caredo funksionaliteti tjeter,
p.sh. per kopjimin e anetareve te vecante te klases ose per te kryer procedura shtese
inicializuese.
Mbingarkimi i operatoreve nuk detyron veprimin perkates te kete lidhje me kuptimin
matematik ose ate te zakonshem te operatorit, megjithese kjo rekomandohet. P.sh. kodi
mund te mos jete shume intuitiv nese perdoret (+) per te zbritur dy klasa.
Megjithese prototipi i nje funksioni operator+ trajton si parameter ate qe qendron
djathtas tij, kjo mund te mos jete gjithnje e vertete per operatore te tjere. Me poshte
jepet nje tabele me menyren e deklarimit te funksioneve operatoriale (zevendeso kudo @
me operatorin perkates):
Shprehje Operatori Funksioni Anetar Funksioni Global
@a + - * & ! ~ ++ -- A::operator@() operator@(A)
56
-
a@ ++ -- A::operator@(int) operator@(A,int)
a@b + - * / % ^ & | < > ==
!= = > && || , A::operator@ (B) operator@(A,B)
a@b = += -= *= /= %= ^=
&= |= = [] A::operator@ (B) -
a(b, c..) () A::operator() (B, C...) -
a->x -> A::operator->() -
ku a eshte nje objekt i klases A, b objekt i klases B dhe c objekt i klases C.
Verejme qe disa operatore mund te mbingarkohen me dy menyra: si nje funksion
anetar ose si nje funksion global. Megjithese perdorimi i tyre nuk ndryshon, duhet patur
parasysh se funksionet te cilat nuk jane anetare te nje klase nuk mund t’i referohen
anetareve private ose te mbrojtur te asaj klase pervecse ne rastin kur funksioni global
eshte mik i saj (miqesia shpjegohet me poshte).
14.2 Fjala kyçe this
Fjala kyçe this paraqet nje tregues tek nje objekt, funksioni anetar i te cilit eshte duke
u ekzekutuar. Ai eshte tregues tek vete objekti.
Nje prej perdorimeve te tij eshte te kontrolloje nese nje parameter i pasuar tek nje
funksion anetar eshte vete objekti, p.sh.:
// Shembull me ‘this’
#include
using namespace std;
class CDummy {
public:
int is_it_me (CDummy& param);
};
57
-
int CDummy::is_it_me (CDummy& param)
{
if (¶m == this) return true;
else return false;
}
int main () {
CDummy a;
CDummy* b = &a;
if ( b->is_it_me(a) )
cout
-
14.3 Anetaret Statike
Nje klase mund te permbaje anetare statike, te dhena ose funksione.
Te dhenat, anetare statike te nje klase, njihen gjithashtu si ”ndryshore te klases”,
meqe ekziston nje velre e vetme per cdo objekt te se njejtes klase. Permbajtja e tyre nuk
ndryshon prej nje objekti te klases tek tjetri.
Nje anetar statik mund te perdoret p.sh. si nje ndryshore brenda nje klase qe mund
te permbaje nje numerues te objekteve te krijuar te klases:
// Anetaret statike tek klasat
#include
using namespace std;
class CDummy {
public:
static int n;
CDummy () { n++; };
~CDummy () { n--; };
};
int CDummy::n=0;
int main () {
CDummy a;
CDummy b[5];
CDummy * c = new CDummy;
cout
-
}
Cilat do te ishin rezultet? Te verifikohen me ane te kodit.
Ne fakt, anetaret statike kane te njejtat veti si ndryshoret globale qe gezojne edhe
fushen e veprimit ne klase. Per kete aresye, si dhe per te shmangur deklarimin e tyre disa
here, ne mund te deklarojme prototipin ne klase por jo percaktimin se bashku me inicial-
izimin. Ne menyre qe te inicializojme nje anetar statik te dhenash ne duhet te perfshijme
nje percaktim formal jashte klases, ne fushe globale, si ne shembullin e mesiperm:
int CDummy::n=0;
Meqe kemi te bejme me nje vlere te vetme te ndryshores per te gjithe objektet e se njejtes
klase, atij mund t’i referohemi si nje anetar te nje objekti cfaredo te asaj klase ose ne
menyre te drejtperdrejte me ane te emrit te klases (por kujdes, kjo e fundit vlen vetem
per anetaret statike):
cout
-
15 Miqesia dhe Trashegimia
15.1 Funksionet Miq
Anetaret priavte dhe te mbrojtur te nje klase nuk mund te referohen jashte klases ku jane
deklaruar. Prej ketij rregulli nejne perjashtim miqte.
Miqte jane funksione ose klasa te deklaruara si te tilla.
Ne qofte se duam te deklarojme nje funksion te jashtem si mik te nje klase, pra
te lejojme kete funksion t’i referohet anetareve private dhe te mbrojtur te kesaj klase,
atehere do duhet te deklarojme nje prototip te ketij funksioni te jashtem brenda klases
duke vendosur perpara tij fjalen kyce friend:
// Funksionet Miq
#include
using namespace std;
class K_Drejtkendesh {
int gjeresi, lartesi;
public:
void vendos_vlerat (int, int);
int siperfaqe () {return (gjeresi * lartesi);}
friend K_Drejtkendesh drejtk_dyfishuar (K_Drejtkendesh);
};
void K_Drejtkendesh::vendos_vlerat (int a, int b) {
gjeresi = a;
lartesi = b;
}
K_Drejtkendesh drejtk_dyfishuar (K_Drejtkendesh param_drejtk)
61
-
{
K_Drejtkendesh drejtk;
drejtk.gjeresi = param_drejtk.gjeresi*2;
drejtk.lartesi = param_drejtk.lartesi*2;
return (drejtk);
}
int main () {
K_Drejtkendesh drejtk_1, drejtk_2;
drejtk_1.vendos_vlerat (2,3);
drejtk_2 = drejtk_dyfishuar (drejtk_1);
cout
-
15.2 Klasat Mike
Ashtu sic eshte e mundur per te percaktuar nje funksion mik, ne mund te percaktojme
nje klase si nje mike te nje tjetre. Ne kete menyre klasa mike ka te drejte t’i referohet
anetareve private dhe te mbrojtur te klases tjeter:
// Klasa mike
#include
using namespace std;
class K_Katror;
class K_Drejtkendesh {
int gjeresi, lartesi;
public:
int siperfaqe ()
{return (gjeresi * lartesi);}
void kthe_ne_katror (K_Katror a);
};
class K_Katror {
private:
int brinje;
public:
void vendos_brinjen (int a)
{brinje=a;}
friend class K_Drejtkendesh;
};
void K_Drejtkendesh::kthe_ne_katror (K_Katror a) {
63
-
gjeresi = a.brinje;
lartesi = a.brinje;
}
int main () {
K_Katror katror;
K_Drejtkendesh drejtk;
katror.vendos_brinjen(4);
drejtk.kthe_ne_katror(katror);
cout
-
15.3 Trashegimia ndermjet klasave
Nje tipar thelbesor i klasave ne C++ eshte trashegimia. Trashegimia lejon krijimin e
klasava te cilat rrjedhin prej klasave te tjera; kjo ben qe ato te perfshijne automatik-
isht disa prej anetareve te tyre ”prinder”. P.sh. supozojme se duam te deklarojme nje
sere klasash qe pershkruajne shumekendesha si K_Drejtkendesh, K_Trekendesh. Ato
kane veti te perbashketa sic jane baza dhe lartesia. Keto veti mund te pershkruhen ne
boten e klasave me ane te klases K_Shumkendesh, prej se ciles mund te dreivojme klasat
K_Drejtkendesh dhe K_Trekendesh.
Klasa K_Shumkendesh do te permbaje anetare qe jane te perbashket per te dy tipet
e shumekendeshave. Ne rastin tone: baza dhe lartesia, por me tipare te vecanta qe
ndryshojne prej njerit shumekendesh tek tjetri.
Klasat qe rrjedhin prej te tjerave trashegojne te gjithe anetaret e referueshem te klases
baze. Kjo do te thote se ne qofte nje klase baze perfshin nje anetar A dhe ajo e derivuar
nje anetar B, atehere kjo e fundit do te permbaje edhe anetarin A dhe ate B.
Per te derivuar nje klase prej nje tjetre perdoret shenja ‘:’ ne deklarimin e clases se
rrjedhur sipas formatit te meposhtem:
class emer_i_klases_se_rrjedhur: public emer_i_klases_baze
{ /*...*/ };
ku public mund te zevendesohet me nje prej referencave te tjera si protected ose
private. Referenca pershkruan nivelin minimal te referueshmerise se anetareve qe trashe-
gohen prej klases baze.
// Klasat e derivuara
#include
using namespace std;
class K_Shumkendesh {
protected:
65
-
int baze, lartesi;
public:
void vendos_vlerat (int a, int b)
{ baze=a; lartesi=b;}
};
class K_Drejtkendesh: public K_Shumkendesh {
public:
int siperfaqe ()
{ return (baze * lartesi); }
};
class K_Trekendesh: public K_Shumkendesh {
public:
int siperfaqe ()
{ return (baze * lartesi / 2); }
};
int main () {
K_Drejtkendesh drejtk;
K_Trekendesh trek;
drejtk.vendos_vlerat (4,5);
trek.vendos_vlerat (4,5);
cout
-
baze, lartesi dhe vendos_vlerat().
Rikujtojme se anetaret e mbrojtur jane te ngjashem me ata private. I vetmi ndryshim
ka te beje me trashegimine: kur nje klase trashegon prej nje tjetre, anetaret e rrjedhur
mund t’i referohen atyre te mbrojtur te trasheguar nga klasa baze, por jo atyre private.
Keshtu, meqe donim baze dhe lartesi te referohen prej anetareve te klasave te rrjedhura
K_Drejtkendesh dhe K_Trekendesh dhe jo vetem prej anetareve te K_Shumkendesh, kemi
perdorur referimin e mbrojtur ne vend te atij privat.
Tipet e ndryshme te referimit mund te permblidhen si me poshte:
Referimi public protected private
anetare te se njejtes klase po po po
anetare te klasave te rrjedhura po po jo
jo anetare po jo jo
ku me ”jo anetare” nenkuptojme cdo referim jashte klases si ato prej main(), prej nje klase
ose funksioni tjeter. Ne shmembullin tone anetare e trasheguar prej K_Drejtkendesh dhe
K_Trekendesh kane te njejtat leje per tu referuar ashtu sic kishin brenda klases baze
K_Shumkendesh:
K_Shumkendesh::baze // referim i mbrojtur
K_Drejtkendesh::baze // referim i mbrojtur
K_Shumkendesh::vendos_vlerat() // referim publik
K_Drejtkendesh::vendos_vlerat() // referim publik
Kjo per aresye se kemi perdorur fjalen kyçe public per te caktuar mardhenien e trashegimise
se seciles prej klasave te rrjedhura:
class K_Drejtkendesh: public K_Shumkendesh { ... }
Ne kete rast public pas shenjes ‘:’ tregon nivelin minimal te referimit te te gjithe
anetareve te trasheguar prej klases qe ndjek ate (ne kete rast K_Shumkendesh). Meqe
67
-
public eshte niveli me i larte i referimit, duke shkruar kete fjale, klasa e rrjedhur do te
trashegoje tek gjithe anetare e saj te njetat nivele qe ata kishin ne klasen baze.
Ne rast se japim referim me te kufizuar si p.sh. protected, te gjithe anetare publike
te klases baze trashegohen si te mbrojtur ne klasen e rrjedhur. Ne rast se shkruajme
private, atehere te gjithe anetaret e klases baze trashegohen si private. P.sh. ne rast se
bije eshte nje klase e rrjedhur prej klases nene:
class bije: protected nene;
kjo ben qe protected te jete niveli maksimal i referimit per anetaret e klases bije qe
trashegohen prej klases nene. Kjo do te thote se te gjithe anetaret publike tek nene behen
te mbrojtur tek bije. Sigurisht, kjo gje nuk kufizon klasen bije te deklaroje anetaret
e vet publike. Pra, niveli maksimal i referimit vendoset per anetaret e trasheguar prej
klases nene.
Ne rast se nuk japim ne menyre eksplicite nivelin e referimit per trashegimin, kompi-
latori do ta nenkuptoje ate private per klasat e deklaruar me class dhe public per ato
te deklaruara me struct.
15.4 Çfare trashegohet prej klases baze?
Ne parim, nje klase e rrjedhur trashegon cdo anetar te klases baze me perjashtim te:
• konstruktorin dhe desktruktorin e tij
• anetaret e tij operator=()
• miqte e tij
Megjithse konstructoret dhe destruktoret e klases baze nuk trashegohen ne vetvete, kon-
struktori i saj default (pra, konstruktori pa parametra) dhe deskturktori i vet thirren
gjithnje kur krijohet ose shkaterrohet nje objekt i ri i klases se rrjedhur. Ne rast se klasa
baze nuk ka konstruktor default ose ne rast se duam te thirret nje konstruktor i mbin-
garkuar kur krijohet nje objekt i klases se rrjedhur, kete mund ta japim ne cdo perkufizim
te konstruktorit te klases se rrjedhur:
68
-
emer_konstruktori_te_rrjedhur (parametra) : emer_konstruktori_baze (parametra) {...}
Per shembull:
// konstruktoret e klasave te rrjedhura
#include
using namespace std;
class mother {
public:
mother ()
{ cout
-
son daniel(0);
return 0;
}
mother: no parameters
daughter: int parameter
mother: int parameter
son: int parameter
Verejme ndryshimin ndermjet kontruktorit mother qe thirret kur krijohet nje objekt i ri
daughter dhe atij te nje objekti son. Kjo per aresye te deklarimit te daughter dhe son:
daughter (int a) // nuk jepet asgje: therritet default
son (int a) : mother (a) // jepet konstruktori: therritet pikirisht ky
15.5 Trashegimia e shumefishte
Eshte e mundur qe nje klase te trashegoje anetare prej me shume se nje klase. Kjo
behet duke ndare me presje klasat e ndryshme baze ne deklarimin e klases se rrjedhur.
Per shembull, ne rast se na duhet nje klase qe printon ne ekran, K_Dalje, dhe duam qe
klasat K_Drejtkendesh dhe K_Trekendesh te trashegojne anetaret e saj pervec te atyre
te K_Shumkendesh, ne mund te shkruajme:
class K_Drejtkendesh: public K_Shumkendesh, public K_Dalje;
class K_Trekendesh: public K_Shumkendesh, public K_Dalje;
Me poshte jepet shembulli i plote:
70
-
// Trashegimia e shumfishte
#include
using namespace std;
class K_Shumkendesh {
protected:
int baze, lartesi;
public:
void vendos_vlerat (int a, int b)
{ baze=a; lartesi=b;}
};
class K_Dalje {
public:
void dalje (int i);
};
void K_Dalje::dalje (int i) {
cout
-
int siperfaqe ()
{ return (baze * lartesi / 2); }
};
int main () {
K_Drejtkendesh drejtk;
K_Trekendesh trek;
drejtk.vendos_vlerat (4,5);
trek.vendos_vlerat (4,5);
drejtk.dalje (drejtk.siperfaqe());
trek.dalje (trek.siperfaqe());
return 0;
}
72