TASODIFIY QIYMATLARNI KELTIRIB CHIQARISH Kompyuter faning ko'p sohalarida tasodifiy qiymatlar bilan ishlash kerak bo'ladi. Masalan o'yin tuzganda, ma'lum bir tabiiy hodisani modellashtirganda va hokazo. Bunda dasturlash tasodifiy qiymatni olishga asoslangan. Va o'sha tasodifiy qiymatga bog'langan holda biror bir funksiya bajariladi yoki ifoda hisoblanadi. Tasodifiy qiymatni standart kutubhonaning rand() funksiyasi bilan
olsa bo'ladi. rand() 0...RAND_NAX orasida yotgan butun son qiymatini qaytaradi. RAND_MAX da aniqlangan simvolik konstantadir. Uning kattaligi 32767 dan kichik bo'lmasligi kerak. rand() qaytaradigan qiymat
aslini olganda psevdo-tasodifiydir. Yani dastur qayta-qayta ishlatilganda ayni qiymatlarni beraveradi. To'la tasodifiy qilish uchun dastur har gal ishlatilganda ma'lum bir o'zgaruvchan qiymatga asoslanib boshlanishi kerak.Buning uchun srand() funksiyasi qo'llaniladi. srand() dasturda faqat bir marta chaqirilsa yetarlidir. Dastur ishlash davomida esa ehtiyojga qarab, rand() chaqirilaveradi. Tasodifiy qiymatlar bilan ishlash uchun ni include bilan e'lon qilishimiz kerak. Yuqoridagi funksiyalarning prototiplarini berib o'taylik:
void srand(unsigned int seed);
int rand();
Biz har gal dastur ishlaganda srand() ga seed o'zgaruvchisini klaviaturadan
kiritishimiz mumkin, yoki buni avtomatik tarzda bo'ladigan qilishimiz mumkin.
Buning bir yo'li
srand( time(NULL) );
deb yozishdir. Bunda kompyuter avtomatik ravishda o'z ichidagi soatning qiymatini time() funksiyasi yordamida o'qiydi va srand() ga parametr sifatida beradi. time() ni NULL yoki 0 argument bilan chaqirganimizda kompyuter
soatining joriy vaqti sekundlar ko'rinishida qaytaradi. Vaqt bilan ishlash uchun ni e'lon qilish kerak bo'ladi. Nardaning 6 lik toshidek ishlaydigan dastur yozaylik.
//rand()/srand() bilan ishlash.
# include # include # include int main()
{
srand( time(NULL) );
int k;
for (k = 1; k<11; k++){
cout << (1 + rand() % 6) << ( (k % 5 == 0) ? endl : " " );
}//end for(k...)
return (0);
}//end main()
Bu yerda amallar siklda bajarilmoqda. rand() % 6 ifodasi masshtablash (scaling) deyiladi. rand() dan qaytuvchi qiymat 0...RAND_MAX orasida bo'lishi mumkin, biz bu qiymatni 6 ga ko'ra modulini olsak, bizda javob sifatida faqat 0, 1, 2, 3, 4 va 5 qiymatlari bo'lishi mumkin. Keyin esa biz bu qiymatlarni siljitamiz, yani 1 raqamini qo'shamiz. Shunda javoblar bizga kerakli oraliqda
yotgan bo'ladi. ?: shart operatori bilan biz har bir raqamdan keyin ekranga nima bosilib chiqishini hal qilyapmiz, bu yoki endl bo'ladi, yoki bo'shliq bo'ladi. Tasodifiy qiymatlar bilan biz keyingi dasturlarimizda hali yana ishlaymiz.
C/C++ dagi enumeration (ketma-ketlik) tipini tanishtirib o'taylik. Enumeration butun sonli konstantalar yig'indi-sidir.Konstantalar qiymati agar boshlangich qiymat beril-
magan bo'lsa, noldan boshlanadi va 1 ga oshib boraveradi, yoki biz qiymatlarni o'zimiz berishimiz mumkin. Masalan:
enum MeningSinifim {AKMAL, TOHIR, NIGORA};
MeninigSinfim SinfA;
SinfA = TOHIR;
SinfA = AKMAL;
Bu yerdagi MeningSinfim qo'llanuvchi tarafidan tuzilgan yangi tipdir.Keyin esa ushbu tipda bo'lgan SinfA o'zgaruv
chisini e'lon qilamiz. Ushbu tipdagi o'zgaruvchi faqat uchta farqli qiymatni qabul qiladi. Bunda AKMAL 0 ga teng, TOHIR 1 ga va NIGORA 2 ga teng bo'ladi. Lekin biz enumeration tipida bo'lgan o'zgaruvchilarga faqat {} qavslar ichida berilgan simvolik ismlarni bera olamiz. Yani to'g'ridan-to'g'ri sonli qiymat bera olmaymiz. Boshqa bir ketma-ketlik:
enum Oylar {YAN = 1, FEV, MAR, APR, MAY, IYUN, IYUL, AVG, SEN, OKT, NOY, DEK};
Bunda qiymatlar birdan boshlanadi va birga ortib boradi. Enumeration
qiymatlari katta harflar bilan yozilsa, dastur ichida ajralib turadi. Va sonli
konstantalardan ko'ra ishlash qulayroqdir.
DASTUR BIRLIKLARINING SIFATLARI
O'zgaruvchilarning kattaligi, ismi va turidan tashqari yana bir necha boshqa
hossalari bor. Bulardan biri hotirada saqlanish tipidir. O'zgaruvchilar
hotirada ikki uslubda saqlanishi mumkin. Birinchisi avtomatik, ikkinchisi
statik yo'ldir. Avtomatik bo'lgan birlik u e'lon qilingan blok bajarilishi
boshlanganda tuziladi, va ushbu blok tugaganda buziladi, u hotirada egallagan
joy esa bo'shatiladi. Faqat o'zgaruvchilar avtomatik bolishi mumkin. Avtomatik
sifatini berish uchun o'zgaruvchi boshiga auto yoki register so'zlari
qo'yiladi. Aslida lokal o'zgaruvchilar oldiga hech narsa yozilmasa, ularga
auto sifati beriladi.
Dastur ijro etilganda o'zgaruvchilar markaziy prosessor registrlariga yuklanib
ishlov ko'radilar. Keyin esa yana hotiraga qaytariladilar. Agar register
sifatini qo'llasak, biz kompyuterga ushbu o'zgaruvchini ishlov ko'rish payti
davomida registrlarning birida saqlashni tavsiya etgan bo'lamiz. Bunda
hotiraga va hotiradan yuklashga vaqt ketmaydi. Albatta bu juda katta vaqt
yutug'i bermasligi mumkin, lekin agar sikl ichida ishlatilsa, yutuq sezilarli
darajada bo'lishi mumkin. Shuni etish kerakki, hozirgi kundagi kompilyatorlar
bunday ko'p ishlatiladigan o'zgaruvchilarni ajrata olishdi va o'zlari ular
bilan ishlashni optimizatsiya qilishadi. Shu sababli o'zgaruvchini register
deb e'lon qilish shart bo'lmay qoldi.
Hotirada boshqa tur saqlanish yo'li bu statik saqlanishdir. Statik sifatini
o'zgaruvchi va funksiyalar olishlari mumkin. Bunday birliklar dastur
boshlanish nuqtasida hotirada quriladilar va dastur tugashiga qadar saqlanib
turadilar. O'zgaruvchi va funksiyalarni statik qilib e'lon qilish uchun static
yoki extern (tashqi) ifodalari e'lon boshiga qo'yiladi. Statik o'zgaruvchilar
dastur boshida hotirada quriladilar va initsalizatsiya qilinadilar.
Fuksiyalarning ismi esa dastur boshidan bor bo'ladi. Lekin statik birliklar
dastur boshidan mavjud bo'lishi, ularni dasturning istalgan nuqtasida turib
qo'llasa bo'ladi degan gap emas. Hotirada saqlanish uslubi bilan qo'llanilish
sohasi tushunchalari farqli narsalardir. O'zgaruvchi mavjud bo'lishi mumkin,
biroq ijro ko'rayatgan blok ichida ko'rinmasligi mumkin.
Dasturda ikki hil statik birliklar bor. Birinchi hili bu tashqi
identefikatorlardir. Bular global sohada aniqlangan o'zgaruvchi va
funksiyalardir. Ikkinchi tur statik birliklar esa static ifodasi bilan e'lon
qilingan lokal o'zgaruvchilardir. Global o'zgaruvchi va funksiyalar oldida
extern deb yozilmasa ham ular extern sifatiga ega bo'ladilar. Global
o'zgaruvchilar ularning e'lonlarini funksiyalar tashqarisida yozish bilan
olinadi. Bunday o'zgaruvchi va funksiyalar o'zlaridan faylda keyin keluvchi
har qanday funksiya tomonidan qo'llanilishi mumkin.
Global o'zgaruvchilarni ehtiyotorlik bilan ishlatish kerak. Bunday
o'zgaruvchilarni harqanday funksiya o'zgartirish imkoniga ega. O'zgaruvchiga
aloqasi yo'q funksiya uning qiymatini bilib-bilmasdan o'zgartirsa, dastur
mantig'i buzilishi mumkin. Shu sababli global sohada iloji boricha kamroq
o'zgaruvchi aniqlanishi lozim. Faqat bir joyda ishlatilinadigan o'zgaruvchilar
o'sha blok ichida aniqlanishi kerak. Ularni global qilish noto'g'ridir.
Lokal o'zgaruvchilarni, yani funksiya ichida e'lon qilingan o'zgaruvchilarni
static so'zi bilan e'lon qilish mumkin. Bunda ular ikkinchi hil statik
birliklarni tashkil qilishgan bo'lishadi. Albatta ular faqat o'sha funksiya
ichida qo'llanishlari mumkin. Ammo funksiya bajarilib tugaganidan so'ng statik
o'zgaruvchilar o'z qiymatlarini saqlab qoladilar va keyingi funksiya
chaqirig'ida saqlanib qolingan qiymatni yana ishlatishlari yoki
o'zgartirishlari mumkin.
Statik o'zgaruvchilar e'lon paytida initsalizatsiya qilinadilar. Agar ularga
e'lon paytida ochiqchasiga qiymat berilmagan bo'lsa, ular nolga
tenglashtiriladi.
static double d = 0.7; // ochiqchasiga qiymat berish,
static int k; // qiymati nol bo'ladi.
Agar static yoki extern ifodalari global identefikatorlar bilan qo'llanilsa,
ushbu identefikatorlar mahsus ma'noga egadirlar. Biz u hollarni keyin ko'rib
o'tamiz.
QO'LLANILISH SOHASI (SCOPE RULES)
O'zgaruvchi dasturning faqat ma'lum sohasida ma'moga egadir. Yani faqat biror
bir blok, yoki bu blok ichida joylashgan bloklar ichida qo'llanilishi mumkin.
Bunday blokni soha (qo'llanilish sohasi - scope) deb ataylik. Identefikator
(oz'garuvchi yoki funksiya ismi) besh hil sohada aniqlanishi mumkin. Bular
funksiya sohasi, fayl sohasi, blok sohasi, funksiya prototipi sohasi va klas
sohasi.
Agar identefikator e'loni hech bir funksiya ichida joylashmagan bo'lsa, u fayl
sohasiga egadir. Ushbu identefikator e'lon nuqtasidan to fayl ohirigacha
ko'rinadi. Global o'zgaruvchilar, funksiya prototiplari va aniqlanishlari
shunday sohaga egadirlar.
Etiketlar (label), yani identefikatorlardan keyin ikki nuqta (:) keluvchi
ismlar, masalan:
chiqish:
mahsus ismlardir. Ular dastur nuqtasini belgilab turadilar. Dasturning boshqa
yeridan esa ushbu nuqtaga sakrashni (jump) bajarish mumkin. Va faqat etiketlar
funksiya sohasiga egadirlar. Etiketlarga ular e'lon qilingan funksiyaning
istalgan joyidan murojaat qilish mumkin. Lekin funksiya tashqarisidan ularga
ishora qilish ta'qiqlanadi. Shu sababli ularning qo'llanilish sohasi
funksiyadir. Etiketlar switch va goto ifodalarida ishlatilinadi. goto
qo'llanilgan bir blokni misol qilaylik.
int factorial(int k) {
if (k<2)
goto end;
else
return ( k*factorial(k-1) );
end:
return (1);
}
Bu funksiya sonning faktorialini hisoblaydi. Bunda 0 va 1 sonlari uchun
faktorial 1 ga teng, 1 dan katta x soni uchun esa x! = x*(x-1)*(x-2)...2*1
formulasi bo'yicha hisoblanadi. Yuqoridagi funksiya rekursiya metodini
ishlatmoqda, yani o'zini-o'zini chaqirmoqda. Bu usul dasturlashda keng
qo'llaniladi. Funksiyamiz ichida bitta dona etiket - end: qollanilmoqda.
Etiketlarni qo'llash strukturali dasturlashga to'g'ri kelmaydi, shu sababli
ularni ishlatmaslikga harakat qilish kerak.
Blok ichida e'lon qilingan identefikator blok sohasiga egadir. Bu soha
o'zgaruvchi e'lonidan boshlanadi va } qavsda (blokni yopuvchi qavs) tugaydi.
Funksiyaning lokal o'zgaruvchilari hamda funksiyaning kiruvchi parametrlari
blok sohasiga egadirlar. Bunda parametrlar ham funksiyaning lokal
o'zgaruvchilari qatoriga kiradilar. Bloklar bir-birining ichida joylashgan
bo'lishi mumkin. Agar tashqi blokda ham, ichki blokda ham ayni ismli
identefikator mavjud bo'lsa, dastur isjrosi ichki blokda sodir bo'layatgan
bir vaqtda ichki identefikator tashqi blokdagi identefikatorni to'sib turadi.
Yani ichki blokda tashqi blok identefikatorining ismi ko'rinmaydi. Bunda
ichki blok faqat o'zining o'zgaruvchisi bilan ish yuritishi mumkin. Ayni ismli
tashqi blok identefikatorini ko'rmaydi. Lokal o'zgaruvchilar static deya
belgilanishlariga qaramay, faqat aniqlangan bloklaridagina qo'llanila
oladilar. Ular dasturning butun hayoti davomida mavjud bo'lishlari ularning
qo'llanilish sohalariga ta'sir ko'rsatmaydi.
Funksiya prototipi sohasiga ega o'zgaruvchilar funksiya e'lonida berilgan
identefikatorlardir. Aytib o'tkanimizdek, funksiya prototipida faqat
o'zgaruvchi tipini bersak yetarlidir. identefikator ismi berilsa, ushbu ism
kompilyator tomonidan hisobga olinmaydi. Bu ismlarni dasturning boshqa yerida
hech bir qiyinchiliksiz qo'llash mumkin. Kompilyator hato bermaydi.
Klas sohasiga ega ismlar klas nomli bloklarda aniqlanadilar. Bizlar klaslarni
keyinroq o'tamiz.
Hozir soha va hotirada saqlanish tipi mavzusida bir misol keltiraylik.
//Qo'llanilish sohasi, static va auto
//o'zgaruvchilarga misollar.
# include long r = 100; //global o'zgaruvchi,
//funksiyalar tashqarisida aniqlangan
void staticLocal(); //funksiya prototipi yoki e'loni
void globalAuto(int k /* k funksiya prototipi
sohasiga ega */); //f-ya e'loni
int main ()
{
staticLocal();
staticLocal();
int m = 6;
globalAuto(m);
::r = ::r + 30;
cout "main da global long r: ";
cout << ::r << endl; //global long r to'liq aniqlangan
//ismi o'rqali qo'llanilmoqda
m++;//m = 7
globalAuto(m);
int r = 10; //tashqi sohadagi main ga nisbatan lokal o'zgaruvchi;
//long r ni to'sadi
cout << "tashqi sohadagi lokal r: " << r << endl;
{ //ichki blok
short r = 3; //ichki sohadagi lokal o'zgaruvchi;
//int r ni to'sadi
cout << "ichki sohadagi lokal r: " << r << endl;
}
cout << "tashqi sohadagi lokal r: " << r << endl;
return (0);
}
void staticLocal() {
static int s = 0; //statik o'zgaruvchi
cout << "staticLocal da: " << s << endl;
s++; //s = 1;
}
void globalAuto(int i) {
int g = 333; //avtomatik o'zgaruvchi
cout << "globalAuto da: " << i << " ";
cout << g << " ";
g++;
cout << r << endl; //global long r ekranga bosiladi
}
Ekranda:
staticLocal da: 0
staticLocal da: 1
globalAuto da: 6 333 100
main da global long r: 130
globalAuto da: 7 333 130
tashqi sohadagi lokal r: 10
ichki sohadagi lokal r: 3
tashqi sohadagi lokal r: 10
ARGUMENT OLMAYDIGAN FUNKSIYALAR
Agar funksiya prototipida () qavslar ichiga void deb yozilsa, yoki hech narsa
yozilmasa, ushbu funksiya kirish argument olmaydi. Bu qonun C++ da o'rinlidir.
Lekin C da bo'sh argument belgisi, yani () qavslar boshqa ma'no beradi.
Bu e'lon funksiya istalgancha argument olishi mumkin deganidir. Shu sababli
C da yozilgan eski dasturlar C++ kompilyatorlarida hato berishlari mumkindir.
Bundan tashqari funksiya prototipi ahamiyati haqida yozib o'taylik. Iloji
boricha har doim funksiya prototiplarini berib o'tish kerak, bu modulli
dasturlashning asosidir. Prototip va e'lonlar alohida e'lon fayllar ichida
berilishi mumkin. Funksiya yoki klas o'zgartirilganda e'lon fayllari
o'zgarishsiz qoladi. Faqat funksiya aniqlangan fayllar ichiga o'zgartirishlar
kiritiladi. Bu esa juda qulaydir.
inline SIFATLI FUNKSIYALAR
Funksiyalar dastur yozishda katta qulayliklar beradilar. Lekin mashina
saviyasida funksiyani har gal chaqirtirish qo'shimcha ish bajarilishiga olib
keladi. Registrlardagi o'zgaruvchilar o'zgartiriladi, lokal yozgaruvchilar
quriladi, parametr sifatida berilgan argumentlar funksiya stekiga o'ziladi.
Bu, albatta, qo'shimcha vaqt oladi. Umuman aytganda, hech funksiyasiz yozilgan
dastur, yani hamma amallari faqat main() funksiyasi ichida bajariladigan
monolit programma, bir necha funksiyalarga ega, ayni ishni bajaradigan
dasturdan tezroq ishlaydi. Funksiyalarning bu noqulayligini tuzatish uchun
inline (satr ichida) ifodasi funksiya e'loni bilan birga qo'llaniladi.
inline sifatli funksiyalar tanasi dasturdagi ushbu funksiya chaqirig'i
uchragan joyga qo'yiladi. inline deb ayniqsa kichik funksiyalarni belgilash
effektivdir. inline ning o'ziga yarasha kamchiligi ham bor, inline
qo'llanilganda dastur hajmi oshdi. Agar funksiya katta bo'lsa va dastur ichida
ko'p marotaba chaqirilsa, programma hajmi juda kattalashib ketishi mumkin.
Oddiy, inline sifati qo'llanilmagan funksiyalar chaqirilish mehanizmi
quyidagicha bo'ladi. Dastur kodining ma'lum bir yerida funksiya tanasi bir
marotaba aniqlangan bo'ladi. Funksiya chaqirig'i uchragan yerda funksiya
joylashgan yerga ko'rsatkich qo'yiladi. Demak, funksiya chaqirig'ida dastur
funksiyaga sakrashni bajaradi. Funksiya o'z ishini bajarib bo'lgandan keyin
dastur ishlashi yana sakrash joyiga qaytadi. Bu dastur hajmini ihchamlikda
saqlaydi, lekin funksiya chaqiriqlari vaqt oladi.
Kompilyator inline ifodasini inobatga olmasligi mumkin, yani funksiya oddiy
holda kompilyatsiya qilinishi mumkin. Va ko'pincha shunday bo'ladi ham.
Amalda faqat juda kichik funksiyalar inline deya kompilyatsiya qilinadi.
inline sifatli funksiyalarga o'zgartirishlar kiritilganda ularni ishlatgan
boshqa dastur bloklari ham qaytadan kompilyatsiya qilinishi kerak. Agar katta
proyektlar ustida ish bajarilayatgan bo'lsa, bu ko'p vaqt olishi mumkin.
inline funksiyalar C da qo'llanilgan # define makrolari o'rnida qo'llanilish
uchun mo'ljallangan. Makrolar emas, balki inline funksiyalar qo'llanilishi
dastur yozilishini tartibga soladi. Makro funksiyalarni keyinroq o'tamiz.
//inline ifodasining qo'llanilishi
# include inline int sum(int a, int b);//funksiya prototipi
int main() {
int j = -356,
i = 490;
cout << "j + i = " << sum(j,i) <return (0);
}
int sum(int a, int b){ //funksiya aniqlanishi
return( a + b );
}
Ekranda:
j + i = 134
KO'RSATKICHLAR VA FUNKSIYA CHAQIRIQLARIDA ULARNING QO'LLANILISHI
C/C++ da funksiya chaqirig'iga kirish parametrlarini berishning ikki usuli
bordir. Birinchi usul qiyamat bo'yicha chaqiriq (call-by-value) deyiladi.
Ikkinchi usul ko'rsatkich bo'yicha chaqiriq (call-by-reference) deb nomlanadi.
Hozirgacha yozgan hamma funksiyalar qiymat bo'yicha chaqirilardi. Buning
ma'nosi shuki, funksiyaga o'zgaruvchining o'zi emas, balki uning nushasi
argument sifatida beriladi. Buning afzal tomoni shundaki, o'zgaruvchi
qiymatini funksiya ichida o'zgartirish imkoni yo'qdir. Bu esa havfsizlikni
ta'minlaydi. Ammo, agar o'zgaruvchi yoki ifoda hotirada katta joy egallasa,
uning nushasini olish va funksiyaga argument sifatida berish sezilarli vaqt
olishi mumkin.
Ko'rsatkich bo'yicha chaqiriqda o'zgaruvchi nushasi emas, uning o'zi argument
sifatida funksiyaga uzatilinadi. Bu chaqiriqni bajarishning ikki usuli mavjud.
Bittasini biz hozir ko'rib chiqamiz, ikkinchi usulni esa keyinroq. Hozir
o'tadigan ko'rsatkichni o'zbekchada &-ko'rsatkich
(AND ko'rsatkich - reference) deb ataylik. Ikkinchi tur ko'rsatkichning esa
inglizcha nomlanishini saqlab qo'laylik, yani pointer (ko'rsatkich) deb
nomlaylik. Bu kelishishdan maqsad, inglizchadagi reference va pointer so'zlar
o'zbekchaga ko'rsatkich deb tarjima qilinadi. Bu ikki ifodaning tagida
yotuvchi mehanizmlar o'zhshash bo'lishlariga qaramay, ularning qo'llanishlari
farqlidir. Shuning uchun ularning nomlanishlarida chalkashlik vujudga
kelmasligi kerak.
Etkanimizdek, ko'rsatkich bo'yicha chaqiriqda o'zgaruvchining o'zi parametr
bo'lib funksiyaga beriladi. Funksiya ichida o'zgaruvchi qiymati
o'zgartirilishi mumkin. Bu esa havfsizlik muammosini keltirb chiqarishi
mumkin. Chunki aloqasi yo'q funksiya ham o'zgaruvchiga yangi qiyamat berishi
mumkin. Bu esa dastur mantig'i buzilishiga olib keladi. Lekin, albatta,
bilib ishlatilsa, ko'rsatkichli chaqiriq katta foyda keltirishi mumkin.
&-ko'rsatkichli parametrni belgilash uchun funksiya prototipi va aniqlanishida
parametr tipidan keyin & belgisi qo'yiladi. Funksiya chaqirig'i oddiy
funksiyaning ko'rinishiga egadir. Pointerlarni qo'llaganimizda esa funksiya
chaqirig'i ham boshqacha ko'rinishga egadir. Ammo pointerlarni keyinroq ko'rib
chiqamiz.
Bir misol keltiraylik.
//Qiymat va &-ko'rsatkichli chaqiriqlarga misol
# include int qiymat_10(int); //e'lon
int korsatkich_10(int &); //e'lon
int f, g;
int main(){
f = g = 7;
cout << f << endl;
cout << qiymat_10(f) << endl;
cout << f << endl << endl;
cout << g << endl;
cout << korsatkich_10(g) << endl; //chaqiriq ko'rinishi o'zgarmaydi
cout << g << endl;
return (0);
}
int qiymat_10(int k){
return ( k * 10 );
}
int korsatkich_10(int &t){
return ( t * 100 );
}
Ekranda:
7
70
7
7
700
700
Bu yerda g o'zgaruvchimiz korsatkich_10(int &) funksiyamizga kirib chiqqandan
so'ng qiymati o'zgardi. Ko'rsatkich bo'yicha chaqiriqda kirish argumentlaridan
nusha olinmaydi, shu sababli funksiya chaqirig'i ham juda tez bajariladi.
&-ko'rsatkichlarni huddi oddiy o'zgaruvchilarning ikkinchi ismi deb qarashimiz
mumkin. Ularning birinchi qo'llanilish yo'lini - funksiya kirish parametrida
ishlatilishini ko'rib chiqdik. &-ko'rsatkichni blok ichida ham ko'llasak
bo'ladi. Bunda bir muhim marsani unutmaslik kerakki &-ko'rsatkich e'lon
vaqtida initsalizatsiya qilinishi kerak, yani ayni tipda bo'lgan boshqa bir
oddiy o'zgaruvchi unga tenglashtirilishi kerak. Buni va boshqa tushunchalarni
misolda ko'rib chiqaylik.
//const ifodasi bilan tanishish;
//&-ko'rsatkichlarning ikkinchi qo'llanilish usuli
# include void printInt(const int &); //funksiya prototipi
double d = 3.999;
int j = 10;
int main()
{
double &rd = d; //d ga rd nomli &-ko'rsatkich
const int &crj = j; //const ko'rsatkich
const short int k = 3; //const o'zgaruvchi - konstanta
cout << rd << endl;
printInt(j);
printInt(crj);
return (0);
}
void printInt(const int &i) //...int& i... deb yozish ham mumkin;
{ //kirish argumenti const dir
cout << i << endl;
return;
}
Ekranda:
3.999
10
Ko'rganimizdek, rd ko'rsatkichimiz d o'zgaruvchi qiymatini ekranga bosib
chiqarish imkonini beradi. Ko'rsatkich o'rqali o'zgaruchining qiymatini ham
o'zgartirsa bo'ladi. &-ko'rsatkichning asosiy hususiyati shundaki mahsus
sintaksis - & belgisining qo'yilishi faqat ko'rsatkich e'lonida qo'llaniladi
halos. Dastur davomida esa oddiy o'zgaruvchi kabi ishlov ko'raveradi. Bu juda
qulaydir, albatta. Lekin ko'rsatkich ishlatilganda ehtiyot bo'lish kerak,
chunki, masalan, funksiya tanasi ichida argumentning nushasi bilan emas, uning
o'zi bilan ish bajarilayatganligi esdan chiqishi mumkin.
const (o'zgarmas) ifodasiga kelaylik. Umuman olganda bu nisbatan yangi
ifodadir. Masalan C da ishlagan dasturchilar uni ishlatishmaydi ham.
const ni qo'llashdan maqsad, ma'lum bir identefikatorni o'zgarmas holga
keltirishdir. Masalan, o'zgaruvchi yoki argument const bilan sifatlantirilsa,
ularning qiymatini o'zgartirish mumkin emas. Lekin boshqa amallarni bajarish
mumkin. Ularni ekranga chiqarish, qiymatlarini boshqa o'zgaruvchiga berish
ta'qiqlanmaydi. const ifodasisiz ham dasturlash mumkin, ammo const yordamida
tartibli, chiroyli va eng muhimi kam hatoli dasturlashni amalga oshirsa
bo'ladi. const ning ta'siri shundaki, const qilingan o'zgaruvchilar kerakmas
joyda o'zgarolmaydilar. Agar funksiya argumenti const deb belgilangan bo'lsa,
ushbu argumentni funksiya tanasida o'zgartirilishga harakat qilinsa,
kompilyator hato beradi. Bu esa o'zgaruvchining himoyasini oshirgan bo'ladi.
const ning qo'llanilish shakli ko'pdir. Shulardan asosiylarini ko'rib chiqsak.
Yuqoridagi misoldagi
const int &crj = j;
amali bilan biz &-ko'rsatkichni e'lon va initsalizatsiya qildik. Ammo crj
ko'rsatkichimiz const qilib belgilandan, bu degani crj ko'rsatkichi orqali
j o'zgaruvchisining qiymatini o'zgartira olmaymiz. Agar const ifodasi
qo'llanilmaganda edi, masalan yuqoridagi
double &rd = d;
ifodadagi rd ko'rsatkichi yordamida d ning qiymatini qiyinchiliksiz
o'zgartirishimiz mumkin. d ning qiymatini 1 ga oshirish uchun
d++;
yoki
rd++;
deb yozishimiz kifoyadir.
Yana bir marta qaytarib o'taylikki, &-ko'rsatkichlar e'lon vaqtida
initsalizatsiya qilinishi shartdir. Yani quyidagi ko'rinishdagi bir misol
hatodir:
int h = 4;
int &k; // hato!
k = h; // bu ikki satr birga qo'shilib yoziladi: int &k = h;
Ba'zi bir dasturchilar &-ko'rsatkich e'londa & belgisini o'zgaruvchi tipi
bilan birga yozadilar. Bunining sababi shuki, & belgisining C/C++ dagi
ikkinchi vazifasi o'zgaruvchi yoki ob'ektning adresini qaytarishdir. Unda
& ni o'zgaruvchiga yopishtirib yozish shartdir. Demak, & tipga yopishtirib
yozish & ning qo'llanishlarini bir-biridan farqlab turadi. Lekin & ni
ko'rsatkich e'lonida qo'llaganda, uning qanday yozilishining ahamiyati yo'q.
Adres olish amalini biz keyinroq ko'rib chiqamiz.
...
int H = 4;
int F;
int &k = H;
int& d = F; // yuqoridagi e'lon bilan aynidir
void foo(long &l);
void hoo(double& f); // yuqoridagi protopip bilan aynidir
...
Bir necha ko'rsatkichni e'lon qilish uchun:
int a, b , c;
int &t = a, &u = b, &s = c;
Bu yerda & operatori har bir o'zgaruvchi oldida yozilishi shart.
&-ko'rsatkich ko'rsatayotdan hotira sohasining adresini o'zgartirib bo'lmaydi.
Masalan:
int K = 6;
int &rK = K;
K ning hotiradagi adresi 34AD3 bolsin, rK ko'rsatkichning adresi esa 85AB4.
K ning qiymati 6, rK ning qiymati ham 6 bo'ladi.
Biz rK ga qaytadan boshqa o'zgaruvchini tenglashtirsak, rK yangi o'zgaruvchiga
ko'rsatmaydi, balki yangi o'zgaruvchining qiymatini K ning adresiga
yozib qo'yadi.
Masalan yangi o'zgaruvchi N bo'lsin, uning adresi 456F2, qiymati esa 3
bo'lsin. Agar biz
rK = N;
desak, rK N ga ko'rsatmaydi, balki K ning adresi - 34AD3 bo'yicha N ning
qiymatini - 3 ni yozadi. Yani K ning qiymati 6 dan 3 ga o'zgaradi.
Yuqoridagi dasturda:
const short int k = 3;
deb yozdik. Bu const ning ikkinchi usulda qo'llanilishidir. Agar o'zgaruvchi
tipi oldidan const qo'llanilsa, o'zgaruvchi konstantaga aylanadi, dastur
davomida biz uning qiymatini o'zgartira olmaymiz. Bu usul bilan biz Pascaldagi
kabi konstantalarni e'lon qilishimiz mumkin. Bu yerda bitta shart bor,
const bilan sifatlantirilgan o'zgaruvchilar e'lon davrida initsalizatsiya
qilinishlari shart. Umuman olganda bu qonun const ning boshqa joylarda
qo'llanilganida ham o'z kuchini saqlaydi. Albatta, faqat funksiya argumentlari
bilan qo'llanilganda bu qonun ishlamaydi. C/C++ da yana # define ifodasi
yordamida ham simvolik konstantalarni e'lon qilish mumkin. Ushbu usulni keyin
ko'rib chiqamiz. Undan tashqari enum strukturalari ham sonli kostantalarni
belgilaydi.
Dasturimizda printInt() funksiyamizga kiradigan int& tipidagi argumentimizni
const deya belgiladik. Buning ma'nosi shuki, funksiya ichida ushbu argument
o'zgartirilishga harakat qilinsa, kompilyator hato beradi. Yani funksiya
const bo'lib kirgan argumentlarni (hoh ular o'zgaruvchi nushalari bo'lsin,
hoh o'zgaruvchilarga ko'rsatkich yoki pointer bo'lsin) qiymatlarini o'zgartira
olmaydilar.
Funksiya faqat bitta qiymat qaytaradi dedik. Bu qaytgan qiymatni biz
o'zgaruvchiga berishimiz mumkin. Agar bittadan ko'proq o'zgaruvchini
o'zgartirmoqchi bo'lsak, o'zgaradigan ob'ektlarni ko'rsatkich yoki pointer
sifatida kiruvchi argument qilib funksiyaga berishimiz mumkin. Bundan tashqari
biz funksiyadan &-ko'rsatkichni qaytarishimiz mumkin. Lekin bu yersa ehtiyot
bo'lish zarur, ko'rsatkich funksiya ichidagi static o'zgaruvchiga ko'rsatib
turishi lozim. Chunki oddiy o'zgaruvchilar avtomatik ravishda funksiya
tugaganida hotiradan olib tashlanadi. Buni misolda ko'raylik.
...
int& square(int k){
static int s = 0; //s static sifatiga ega bo'lishi shart;
int& rs = s;
s = k * k;
return (rs);
}
...
int g = 4;
int j = square(g); // j = 16
...
FUNKSIYA ARGUMENTLARNING BERILGAN QIYMATLARI
Ba'zi bir funksiyalar ko'pincha bir hil qiymatli argumentlar bilan
chaqirilishi mumkin. Bu holda, agar biz funksiya argumentlariga ushbu ko'p
qo'llaniladigan qiymatlarni bersak, funksiya argumentsiz chaqirilganda bu
qiymatlar kompilyator tomonidan chaqiriqqa kiritiladi. Berilgan qiymatlar
funksiya prototipida berilsa kifoyadir.
Berilgan qiymatli argumentlar parametrlar ro'hatida eng o'ng tomonda yozilishi
kerak. Buning sababi shuki, agar argument qiymati tashlanib o'tilgan bo'lsa,
va u o'ng tomonda joylashmagan bo'lsa, biz bo'sh vergullani qo'yishimizga
to'g'ri keladi, bu esa mumkin emas. Agar bir necha berilgan qiymatli
argumentlar bor bo'lsa, va eng o'ngda joylashmagan argument tushurilib
qoldirilsa, undan keyingi argumentlar ham yozilmasligi kerak.
Bir misol keltiraylik.
//Berilgan qiymatli parametrlar bilan ishlash
# include int square(int = 1, int = 1); // ...(int a=1, int b=1)...
// yuqoridagi kabi o'zgaruvchilar otini ham
// berishimiz mumkin
int main()
{
int s = 3, t = 7;
cout << "Paremetrsiz: " << square()<< endl;
cout << "Bitta parametr (ikkinchisi) bilan:" << square(t) << endl;
cout << "Ikkita parametr bilan:" << square(s,t) << endl;
return (0);
}
int square(int k, int g){
return ( k * g );
}
Ekranda:
Parametrsiz: 1
Bitta parametr (ikkinchisi) bilan: 7
Ikkita parametr bilan: 21
FUNKSIYA ISMI YUKLANISHI
Bir hil ismli bir necha funksiya e'lon qilinishi mumkin. Bu C++ dagi juda
kuchli tushunchalardandir. Yuklatilgan funksiyalarning faqat kirish
parametrlari farqli bo'lishi yetarlidir. Qaytish parametri yuklatilishda
ahamiyati yo'qdir. Yuklangan funksiyalar chaqirilganda, qaysi funksiyani
chaqirish kirish parametrlarining soniga, ularning tipiga va navbatiga
bog'liqdir. Yani ism yuklanishida funksiyaning imzosi rol o'ynidi. Agar kirish
parametrlari va ismlari ayni funksiyalarning farqi faqat ularning qaytish
qiymatlarida bo'lsa, bu yuklanish bo'lmaydi, kompilyator buni hato deb e'lon
qiladi.
Funksiya yuklanishi asosan ayni ishni yoki amalni farqli usul bilan farqli
ma'lumot tiplari ustida bajarish uchun qo'llaniladi. Masalan bir fazoviy
jismning hajmini hisoblash kerak bo'lsin. Har bir jismning hajmi farqli
formula yordamida, yani farqli usulda topiladi, bir jismda radius tushunchasi
bor bo'lsa, boshqasida asos yoki tomon tushunchasi bor bo'ladi, bu esa farqli
ma'lumot tiplariga kiradi. Lekin amal ayni - hajmni hisoblash. Demak, biz
funksiya yuklanishi mehanizmini qo'llasak bo'ladi. Bir hil amalni bajaruvchi
funksiyalarni ayni nom bilan atashimiz esa, dasturni o'qib tushunishni
osonlashtiradi.
Kompilaytor biz bergan funksiya imzosidan (imzoga funksiya ismi va kirish
parametrlari kiradi, funksiyaning qaytish qiymati esa imzoga kirmaydi) yagona
ism tuzadi, dastur ijrosi davruda esa funksiya chaqirig'idagi argumentlarga
qarab, kerakli funksiyani chaqiradi. Yangi ismni tuzish operatsiyasi ismlar
dekoratsiyasi deb ataladi. Bu tushunchalarni misolda ko'rib chiqaylik.
// Yuklatilgan funksiyalarni qo'llash
# include # include // Yangi ismlar sohasini aniqladik
namespace
mathematics {
const double Pi = 3.14159265358979;
double hajm(double radius); // sharning hajmi uchun - 4/3 * Pi * r^3
double hajm(double a, double b, double s) // kubning hajmi uchun - abc
}
using namespace mathematics;
int main()
{
double d = 5.99; // sharning radiusi
int x = 7, y = 18, z = 43;
cout << "Sharninig hajmi: " << hajm(d) << endl;
cout << "Kubning hajmi: " << hajm(x,y,z) << endl;
return (0);
}
double mathematics::hajm(double radius) {
return ( (Pi * pow(radius,3) * 4.0) / 3.0 );
}
double mathematics::hajm(double a, double b, double c) {
return ( a * b * c );
}
Ekranda:
Sharning hajmi: 900.2623
Kubning hajmi: 5418
Yuqoridagi dasturda yangi ismlar sohasini aniqladik, unda Pi konstantasini
e'lon qildik. shaqning hajmini hisoblashda standart kutubhonadagi pow()
funksiyasini ishlatdik, shu sababli e'lon faylini # include ifodasi
bilan kiritdik. Ismlar sohasida joylashgan funksiyalarni aniqlash uchun, yani
ularning tanasini yozish uchun biz ilarning to'liq ismini berishimiz kerak.
Albatta, agar funksiya ismlar sohasining ichida aniqlangan bo'lsa, tashqarida
boshqattan yozib o'tirishning hojati yo'q. hajm() funksiyalarining to'liq
ismi mathematics::hajm(...) dir. :: operatori sohalarni bog'lovchi
operatordir. Yangi ismlar sohasini faqatgina misol tariqasida berdik, uni
funksiya yuklanishlari bilan hech qanday aloqasi yo'qdir. Funksiya ismlari
yuklanishi, ko'rib turganimizdek, juda qulay narsadir.
Funksiya yuklanishini qo'llaganimizda, funksiyalar argumentlarining berilgan
qiymatlarini ehtiyotkorlik bilan qo'llashimiz lozim. Masalan bizda ikkita
funksiyamiz bor bo'lsin.
foo(int k = 0); // berilgan qiymati 0
foo();
Bu ikki funksiya yuklatilgan. Lekin agar biz birinchi funksiyani dasturda
argumentsiz chaqirsak, kompilyator qaysi funksiyani chaqirishni bilmaydi, va
shu sababli hato beradi. Biroq bu deganimiz funksiya yuklanishi bilan berilgan
qiymatlar qo'llanilishi mumkin emas deganimiz emas, eng muhimi funksiya
chaqirig'ini ikki hil tushunish bo'lmasligi kerak.
FUNKSIYA SHABLONLARI
Funksiya shablonlari (function templates) ham funksiya yuklanishiga o'hshash
tushunchadir. Bunda eng asosiy farq funksiya shablonlarida amal ham bir hil
yo'l bilan bajariladi. Masalan bir necha sonlar ichidan eng kattasini topish
kerak bo'lsin. Sonlar to'plami faqat tipi bilan farqlanadi, int, double yoki
float. Ishlash algoritmi esa aynidir. Bu holda biz funksiyalarni yuklab
o'tirmasdan, shablon yozib qo'ya qolamiz.
Funkisya shabloni yoki yuklanishisiz ham bu masalani yechish mumkinku degan
savol paydo bo'ladi. Masalan, agar biz kiradigan parametrlarning hammasini
long double qilsak, istalgan sonli tipdagi argumentni bera olamiz, chunki
kompilyator o'zi avtomatik ravishda kirish tiplarini long double ga
o'zgartiradi. Lekin, agar biz bunday funksiya yozadigan bo'lsak, hotiradan va
tezlikdan yutqizamiz. Dasturimizda faqat char tipidagi, bir baytli qiymatlar
bilan ishlashimiz mumkin. long double esa 10 bayt, va eng katta sonni aniqlash
uchun sonlarni solishtirganimizda, long double qiymatlarni solishtirish
char tipidagi qiymatlarni solishtirishdan ko'ra ancha ko'p vaqt oladi.
Qolaversa, har doim ham kompilyator tiplarni biridan ikkinchasiga to'g'ri
keltira oladi.
Shablonlarning strukturasi bilan tanishaylik. Bizning funksiya ikkita kirish
argumentini bir biriga qo'shsin, va javobni qaytarsin.
template T summa(T a, T b) {
return ( a + b);
}
Shablon funksiya e'loni va aniqlanishidan oldin template <> ifodasi yoziladi,
<> qavslardan keyin nuqta-vergul (;) qo'yilmaydi. <> qavslar ichida
funksiya kirish parametrlari, chiqish qiymati va lokal o'zgaruvchilar tiplari
beriladi. Ushbu formal tiplarning har birining oldida class yoki typename
(tip ismi) so'zi qo'yilish kerak. Yuqoridagi misolda T ning o'rniga istalgan
boshqa identefikator qo'yish mumkin. Misollar beraylik.
template javob hajmKub(uzunlik a, englik b, balandlik c);
template T maximum(T k, T l);
Yuqorida yozgan shablonimizni qo'llagan holga bir misol keltiraylik.
// Shablonlar bilan ishlash
# include template T summa(T a, T b) {
return ( a + b );
}
int main()
{
int x = 22, y = 456;
float m = .01, n = 56.90; // kasrli sonda nuqtadan oldingi (butun qismdagi)
// nolni berish shart emas: ... m = .01 ...
cout << "int: 22 + 456 = " << summa(x,y) << endl;
cout << "float: 0.01 + 56.90 = " << summa(0.01,56.90) << endl;
return (0);
}
Ekranda:
int: 22 + 456 = 478
float: 0.01 + 56.90 = 56.91
Shablonlarni funksiyalardan tashqari klaslarga ham qo'llasa bo'ladi. Ko'rib
turganimizdek, shablonlar faqat bir marotaba yoziladi. Keyin esa mos
keladigan tiplar qo'yilib, yozilib ketilaveradi. Aslida shablonlar C++ ning
standartida juda ko'p qo'llanilgan. Agar bilib ishlatilsa, shablonlar
dasturchining eng kuchli quroliga aylanishi mumkin. Biz keyinroq yana
shablonlar mavzusiga qaytamiz.
4. MASSIVLAR
Bu qismda dasturdagi ma'lumot strukturalari bilan tanishishni boshlaymiz.
Dasturda ikki asosiy tur ma'lumot strukturalari mavjuddir. Birinchisi
statik, ikkinchisi dinamikdir. Statik deganimizda hotirada egallagan joyi
o'zgarmas, dastur boshida beriladigan strukturalarni nazarda tutamiz. Dinamik
ma'lumot tiplari dastur davomida o'z hajmini, egallagan hotirasini
o'zgartirishi mumkin.
Agar struktura bir hil kattalikdagi tiplardan tuzilgan bo'lsa, uning nomi
massiv (array) deyiladi. Massivlar dasturlashda eng ko'p qo'laniladigan
ma'lumot tiplaridir. Bundan tashqari strukturalar bir necha farqli tipdagi
o'zgaruvchilardan tashkil topgan bo'lishi mumkin. Buni biz klas
(Pascalda record) deymiz. Masalan bunday strukturamiz ichida odam ismi va
yoshi bo'lishi mumkin.
Bu bo'limda biz massivlar bilan yaqindan tanishib o'tamiz. Bu bo'limdagi
massivlarimiz C uslubidagi, pointerlarga (ko'rsatkichlarga) asoslan
strukturalardir. Massivlarning boshqa ko'rinishlarini keyingi qismlarda
o'tamiz.
Massivlar hotirada ketma-ket joylashgan, bir tipdagi o'zgaruvchilar guruhidir.
Alohida bir o'zgaruvchini ko'rsatish uchun massiv nomi va kerakli o'zgaruvchi
indeksini yozamiz. C/C++ dagi massivlardagi elementlar indeksi har doim
noldan boshlanadi. Bizda char tipidagi m nomli massiv bor bo'lsin. Va uning
4 dona elementi mavjud bo'lsin. Shemada bunday ko'rsataylik:
m[0] -> 4
m[1] -> -44
m[2] -> 109
m[3] -> 23
Ko'rib turganimizdek, elementga murojaat qilish uchun massiv nomi va []
qavslar ichida element indeksi yoziladi. Bu yerda birinchi element qiymati
4, ikkinchi element - 1 nomerli indeksda -44 qiymatlari bor ekan. Ohirgi
element indeksi n-1 bo'ladi (n - massiv elementlari soni).
[] qavslar ichidagi indeks butun son yoki butun songa olib keluvchi ifoda
bo'lmog'i lozim. Masalan:
...
int k = 4, l = 2;
m[ k-l ] = 77; // m[2] = 77
m[3] *= 2; // m[3] = 46
double d = m[0] * 6; // d = 24
cout << m[1]; // Ekranda: -44
...
Massivlarni ishlatish uchun ularni e'lon qilish va kerak bo'lsa massiv
elementlarini initsalizatsiya qilish kerak. Massiv e'lon qilinganda
kompilyator elementlar soniga teng hajmda hotira ajratadi. Masalan yuqorida
qo'llanilgan char tipidagi m massivini e'lon qilaylik.
char m[4];
Bu yerdagi 4 soni massivdagi elementlar miqdorini bildiradi. Bir necha
massivni e'londa bersak ham bo'ladi:
int m1[4], m2[99], k, l = 0;
Massiv elementlari dastur davomida initsalizatsiya qilishimiz mumkin, yoki
boshlang'ich qiymatlarni e'lon vaqtida, {} qavslar ichida ham bersak bo'ladi.
{} qavslardagagi qiymatlar massiv initsalizaytsiya ro'yhati deyiladi.
int n[5] = {3, 5, -33, 5, 90};
Yuqorida birinchi elementning qiymati 3, ikkinchiniki 5 ... ohirgi beshinchi
element qiymati esa 90 bo'ldi. Boshqa misol:
double array[10] = {0.0, 0.4, 3.55};
Bu yerdagi massiv tipi double bo'ldi. Ushbu massiv 10 ta elementdan iboratdir.
{} qavslar ichida esa faqat boshlangich uchta element qiymatlari berildi.
Bunday holda, qolgan elementlar avtomatik tarzda nolga tenglashtiriladi. Bu
yerda aytib o'tishimiz kerakki, {} qavslar ichida berilgan boshlangish
qiymatlar soni massivdagi elementlar sonidan katta bo'lsa, sintaksis hatosi
vujudga keladi. Masalan:
char k[3] = {3, 4, 6, -66, 34, 90}; // Hato!
Uch elementdan iborat massivga 6 dona boshlangich qiymat berilyapti, bu
hatodir. Boshqa misolni ko'rib chiqaylik:
int w[] = {3, 7, 90, 78};
w nomli massiv e'lon qilindi, lekin [] qavslar ichida massivdagi elementlar
soni berilmadi. Bunday holda necha elementga joy ajratishni kompilyator
{} qavslar ichidagi boshlangich qiymatlar miqdoriga qarab biladi. Demak,
yuqoridagi misolda w massivimiz 4 dona elementdan iborat bo'ladi.
E'lon davridagi massiv initsalizatsiya ro'yhati dastur ijrosi vaqtidagi
initsalizatsiyadan ko'ra tezroq ishlaydigan mashina kodini vujudga keltiradi.
Bir misol keltiraylik.
// Massivlar bilan ishlash.
# include # include const int massiv = 8; // massiv kattaligi uchun konstanta
int k[massiv];
char c[massiv] = {5,7,8,9,3,44,-33,0}; // massiv initsalizatsiya ro'yhati
int main()
{
for (int i = 0; i < massiv; i++) {
k[i] = i + 1; // dastur ichida inisalizatsiya
}
for (int j = 0; j < massiv; j++) {
cout << k[j]
<< setw(4)
<< c[j]
<< endl;
}
return (0);
}
Ekranda:
1 5
2 7
3 8
4 9
5 3
6 44
7 -33
8 0
Yuqorida faylini dasturimizga kiritdik. Bu e'lon faylida standart
kirish/chiqish oqimlari bilan ishlaydigan buyruqlar berilgan. Dasturimizda
qo'llanilgan setw() manipulyatori chiqish oqimiga berilayatgan ma'lumotlarning
eng kichik kengligini belgilaydi, biz setw() parametrini 4 deb berdik, demak
c[] massivi elementlari 4 harf kenglikda ekranga bosiladilar. Agar kenglik
kamlik qilsa, u kattalashtiriladi, agar bo'sh joy qolsa, elementlar chapga
yondashilib yoziladi. Biz va manipulyatorlarni keyinroq to'la
ko'rib chiqamiz.
Misolimizda massiv nomli konstantani qo'lladik. Uning yordamida massiv
chegaralarini va for strukturasidagi chegaraviy qiymatlarni berdik. Bunday
o'zgarmasni qo'llash dasturda hatoga yo'l qo'yishni kamaytiradi. Massiv
chegarasi o'zgarganda, dasturning faqat bir joyiga o'zgarish kiritiladi.
Massiv hajmi e'lonida faqat const sifatli o'zgaruvchilar qo'llanilishi mumkin.
Massivlar bilan ishlaganda eng ko'p yo'l qoyiladigan hato bu massivga
0 dan kichkina va (n-1) dan (n: massivdagi elementlar soni) katta indeks bilan
murojaat qilishdir. Bunday hato dastur mantig'i hatosiga olib keladi.
Kompilyator bu turdagi hatolarni tekshirmaydi. Keyinroq o'zimiza yozgan
massiv klaslarida ushbu hatoni tekshiriladigan qilishimiz mumkin.
10 ta sonning tushish ehtimilini ko'rsaturvchi dastur yozaylik.
// Ehtimollar va massivlar
# include # include # include # include int main ()
{
const int massivHajmi = 10;
int m[massivHajmi] = {0}; // hamma 10 ta element
// 0 ga tenglashtirildi
srand( time(NULL) );
for(int i = 0; i < 1000; i++) {
++m[ rand() % 10 ];
}
for(int j = 0; j < massivHajmi; j++) {
cout << j << setw(4) << m[j] << endl;
}
return (0);
}
Ekranda:
0 96
1 89
2 111
3 97
4 107
5 91
6 100
7 118
8 99
9 92
Ko'rib turganimizdek, sonlarning tushish ehtimoli nisbatan tengdir. Albatta,
bu qiymatlar dasturning har yangi ishlashida o'zgaradi.
++m[ rand() % 10 ];
Yozuvi bilan biz massivning rand() % 10 indeksli elementini birga
oshirmoqdamiz. Bunda rand () % 10 ifodasidan chiqadigan qiymatlar [0;9] ichida
yotadi.
Satrlar, yani harflar ketma-ketligi ("Toshkent", "Yangi yilingiz bilan!"...)
C/C++ da char tipidagi massivlar yordamida beriladi. Bunday satrlar bilan
islovlar juda tez bajariladi. Chunki ortiqcha tekshirishlar bajarilmaydi.
Bundan tashqari C++ da ancha rivojlangan String klasi mavjuddir, u oddiy char
bilan berilgan satrlardan ko'ra qulayroqdir. Lekin ushbu klas ko'proq joy
egallaydi va massivli satrlardan ko'ra sekinroq ishlaydi. String klasini
keyingi qismlarda o'tamiz. Qolaversa, satrlar bilan ishlash uchun biz o'zimiz
klas yoki struktura yozishimiz mumkin. C dan meros bo'lib qolgan satrlar
ustida amallar bajarish uchun biz dasturimizga (yangi ismi ) e'lon faylini kiritishimiz kerak. Ushbu e'lon faylida
berilgan funksiyalar bilan keyingi bo'limda ishlaymiz.
Harflar, yani literalar, aytib o'tganimizdek, C++ da char tipi orqali
beriladi. Literalar apostroflarga ('S', '*' ...) olinadi. Satrlar esa
qo'shtirnoqlarga olinadi. Satrlar e'loniga misol beraylik.
char string[] = "Malibu";
char *p = "Ikkinchi!?!";
Satrlarni yuqoridagi ikkita yo'l bilan initsalizatsiya qilsa bo'ladi.
Satrlar ikkinchi uslubda e'lon qilinganda, yani pointer mehanizmi
qo'llanilganda, ba'zi bir kompilyatorlar satrlarni hotiraning konstantalar
saqlanadigan qismiga qo'yadi. Yani ushbu satrlarga o'zgartirish kiritilishi
ta'qiqlanadi. Shu sababli satrlarni hardoim o'zgartirish imkoni bo'lishi
uchun ularni char tipidagi massivlar ko'rinishida e'lon qilish afzaldir.
Satrlar massiv yordamida berilganda, ularning uzunligi noma'lumdir.
Shu sababli satrning tugaganligini bildirish uchun satr ohiriga mahsus belgi
nol literasi qo'yiladi. Uning dastursa belgilanishi '\0' ko'rinishga ega.
Demak, yuqorida berilgan "Malibu" satriga ohiriga '\0' belgisi qo'shiladi, yani
massivning umumiy uzunligi "Malibu":6 + '\0':1 = 7 ta char tipidagi elementga
teng bo'ladi. Satrlarni massiv initsalizatsiya ro'yhati ko'rinishida ham
bersak bo'ladi:
char c[6] = {'A', 'B', 'C', 'D', 'E' , '\0'};
...
cout << c;
...
Ekranda:
ABCDE
Biz cout bilan c ning qiymati ekranga bosib chiqardik. Aynan shunday
klaviaturadan ham o'qib olishimiz mumkin:
char string[80];
cin >> string;
Eng muhimi satr bilan '\0' belgisi uchun yetarli joy bo'lishi kerak.
Satrlar massiv yordamida berilganligi uchun, alohida elementlarga indeks
orqali yetishish mumkin, masalan:
char k[] = "Bahor keldi, gullar ochildi.";
for (int i = 0; k[i] != '\0'; i++)
if ( (i mod 2) == 0 ) // juft sonlar uchun haqiqat bo'ladi
cout << k[i] << " ";
Ekranda:
B h r k l i u l r o h l i
Yuqoridagi misolda, sikl tugashi uchun k[i] element '\0' belgiga teng bo'lishi
kerak.
FUNKSIYALARNING MASSIV KIRISH PARAMETRLARI
Funksiyalarga massivlarni kirish argument sifatida berish uchun parametr
e'lonida [] qavslar qo'yiladi. Masalan:
...
void sortArray(int [], int ); // funksiya e'loni
void sortArray(int n[], int hajm) { // funksiya aniqlanishi
...
}
...
Dasturda esa, funksiya chaqirilganda, massivning faqat ishmi beriladi halos,
[] qavslarning keragi yo'q.
int size = 10;
int array[size] = {0};
...
void sortArray(array, size); // funksiya chaqirig'i,
// faqat massiv ismi - array berildi
...
Funksiyaga massivlarni berganimizda, eng katta muammo bu qanday qilib
massivdagi elementlari sonini berishdir. Eng yaxshi usul bu massiv kattaligini
qo'shimcha kirish parametri orqali funksiyaga bildirishdir. Bundan tashqari,
massiv hajmini global konstanta orqali e'lon qilishimiz mumkin. Lekin bu
ma'lumotni ochib tashlaydi, global sohani ortiqcha narsalar bilan to'ldirib
tashlaydi. Undan tashqari massiv hajmini funksiyaning o'ziga yozib qoyishimiz
mumkin. Biroq bunda bizning funksiyamiz faqat bitta kattalikdagi massivlar
bilan ishlaydigan bo'lib qoladi. Yani dasturimiz dimamizmni yo'qotadi.
Klaslar yordamida tuzilgan massivlar o'z hajmini biladi. Agar bunday
ob'ektlarni qo'llasak, boshqa qo'shimcha parametrlarni qo'llashimizning keragi
yo'q.
Funksiyalarga massivlar ko'rsatkich ko'rinishida beriladi. Buni C++, biz
ko'rsatmagan bo'lsak ham, avtomatik ravishda bajaradi. Agar massivlar qiymat
bo'yicha chaqirilganda edi, har bir massiv elementining nushasi olinishi kerak
bo'lardi, bu esa dastur ishlash tezligiga salbiy ta'sir ko'rsatar edi.
Lekin massivning alohida elementi argument o'rnida funksiyaga berilganda,
ushbu element, aksi ko'rsatilmagan bo'lsa, qiymat bo'yicha beriladi. Masalan:
...
double m[3] = {3.0, 6.88, 4.7};
void foo(double d){
...
}
...
int main()
{
...
void foo(m[2]); // m massivining uchinchi elementining qiymati - 4.7 berildi
...
return (0);
}
Agar kiritilayatgan massiv funksiya ichida o'zgarishi ta'qiqlansa, biz
funksiya massiv parametri oldiga const sifatini qo'ysak bo'ladi:
foo(const char []);
Bunda funksiyaga kiradigan massiv funksiya tomonidan o'zgartirilmaydi.
Agar o'zgartirishga urinishlar bo'lsa, kompilyator hato beradi.
Massivlar va funksiyalarning birga ko'llanilishiga misol beraylik.
// Massiv argumentli funksiyalar
# include const int arraySize = 10;
double ortalama(int m[], int size) {
double temp = 0;
for (int i = 0; i < size; i++) {
temp += m[i];
}
return ( temp / size );
}
void printArray(const int n[], int size, int ortalama) {
for (int i = 0; i < size; i++) {
cout << n[i]; << endl;
}
cout << "O'rtalama: " << ortalama << endl;
}
int main()
{
int m[10] = {89,55,99,356,89,335,78743,44,767,346};
printArray(m, arraySize, ortalama(m, arraySize)) ;
return (0);
}
Ekranda:
89
55
99
356
89
335
78743
44
767
346
O'rtalama: 8092.3
BIR NECHA INDEKSLI MASSIVLAR
Massivlar bir necha indeksga ega bo'lishlari mumkin. C++ kompilyatorlari
eng kamida 12 ta indeks bilan ishlashlari mumkin. Masalan, matematikadagi
m x n kattalikdagi matritsani ikkita indeksli massiv yordamida berisak
bo'ladi.
int matritsa [4][10];
Yuqorida to'rt satrlik, 10 ustunlik matritsani e'lon qildik. Bir indeksli
massivlar kabi ko'p indeksli massivlarni initsalizatsiya ro'yhati bilan birga
e'lon qilish mumkin. Masalan:
char c[3][4] = {
{ 2, 3,9, 5}, // birinchi satr qiymatlari
{-10, 77,5, 1}, // ikkinchi " "
{ 90,233,3,-3} // uchinchi " "
};
int m[2][2] = {56,77,8,-3}; // oldin birinchi satrga qiymatlar beriladi,
// keyin esa ikkinchi satrga
double d[4][3][6] = {2.55, -46,0988}; // birinchi satrning dastlabki ikkita
// elementi qiymat oladi,
// massivning qolgan elementlari esa
// nolga tenglashtiriladi
Massivning har bir indeksi alohida [] qavslar ichiga olinishi kerak.
Yuqoridagi c[][] massivining ikkinchi satr, birinchi ustunidagi elementi
qiymatini birga oshirish uchun
++c[1][0]; // yoki c[1][0]++;
// c[1][0] += 1;
// c[1][0] = c[1][0] + 1;
deb yozishimiz mumkin. Massiv indekslari 0 dan boshlanishini unutmaslik zarur.
Agar
++c[1,0];
deb yozganimizda hato bo'lar edi. C++ bu yozuvni
++c[0];
deb tushunar edi, chunki kompilyator vergul bilan ajratilgan ro'yhatning
eng ohirgi elementini qabul qilardi. Hullas, C++ dagi ko'p indeksli massivlar
dasturchiga behisob imkoniyatlar beradi. Undan tashqari, ular hotirada statik
joylashganligi uchun ularning ishlash tezligi kattadir.
C++ dagi ko'p indeksli massivlar hotirada ketma-ket joylashgandir. Shu sababli
agar massiv funksiyaga kirish parametri sifatida berilsa, faqat birinchi
indeks tushurilib qoldiriladi, qolgan indekslar esa yozilishi shartdir.
Aks taqdirda funksiya massiv kattaligini to'g'ri keltirib chiqarolmaydi.
Massiv parametrli bir funksiya e'lonini beraylik.
//Ko'p indeksli massivlar
# include int indeks = 3;
int intArray[indeks][4] = {}; // hamma elementlar 0 ga tenglashtirildi
void printArray(int mass[][4], int idx){ // funksiya e'loni
for (int i = 0; i < idx; i++) { // massivning birinchi indeksini
// o'zgartirsa bo'ladi
for (int k = 0; k < 4; k++){ // massivning ikkinchi indeksi o'zgarmaydi
cout << mass[i][k];
}
cout << endl;
}
return;
}
...
int main()
{
...
printArray(intArray); // funksiya chaqirig'i
...
return (0);
}
Massivning indekslarini funksiyaga bildirish yana muammoligicha qoladi.
Albatta, birinchi indeksdan tashqari qolgan boshqa indekslar kattaligini
funksiya ichida berish ma'noga egadir. Lekin birinchi indeks kattaligini
tashqaridan, qo'shimcha parametr sifatida bersak, funksiyamiz chiroyliroq
chiqadi, turli kattalikdagi massivlarni o'lish imkoniga ega bo'ladi.
5. POINTER (ko'rsatkich) VA SATRLAR
C++ da ikki ko'rinishdagi ko'rsatkichlar - &-ko'rsatkichlar va pointerlar
mavjuddir. Aslida bularning farqi faqat qo'llanilishi va ko'rinishida desak
ham bo'ladi. Bu qismda biz C dan meros qolgan pointerlar bilan yaqindan
tanishamiz. &-ko'rsatkichlarni biz o'tgan qismda ko'rgan edik.
Pointerlar C/C++ dasturlash tillarining eng kuchli qurollaridandir. Lekin
pointer tushunchasini anglash ham oson emas. Pointerlar yordamida
funksiyalarning ko'rsatkich bo'yicha chaqirish mehanizmini amalga oshirish
mumkin. Undan tashqari pointerlar yordamida dinamik strukturalar - stek
(stack), ro'yhat (list), navbat (queue) va darahtlar (tree) tuzish mumkin.
Undan tashqari pointer, satr va massivlar orasida yaqin aloqa bordir. Satr
va massivlarni pointerlar yordamida berish bizga C dan meros bo'lob qoldi.
Keyingi boblarda biz satr va massivlarni to'la qonli ob'ekt sifatida qo'lga
olamiz.
Pointerlar qiymat sifatida hotira adreslarini oladilar. Oddiy o'zgaruvchilar
ma'lum bir qiymatga ega bo'lgan bir paytda, pointerlar boshqa bir
o'zgaruvchining adresini o'z ichlariga oladilar. Shunda o'zgaruvchi bevosita
qiymatga ko'rsatib tursa, pointer qiymatga bilvosita ko'rsatadi.
Pointer e'lonida, pointer ismidan oldin '*' (yulduzcha, ko'paytiruv) belgisi
qo'yilishi kerak. Misolda ko'raylik:
char *charPtr, c = 8, *pc, ff[] = "IcyCool";
Bu yerda charPtr va pc lar char tipidagi ko'rsatkichlardir. Yani, charPtr ni
"char tipidagi oz'garuvchiga ga ko'rsatkich" deb o'qisak bo'ladi. Ko'rsatkich
sifatida e'lon qilinayatgan har bir o'zgaruvchi ismi oldida '*' bo'lishi
shartdir.
Pointerlarni boshqa o'zgaruvchilar kabi e'lon davrida, yoki dastur ichida
qiymat berish yordamida initsalizatsiya qilish mumkin. Pointerlar qiymat
sifatida faqat o'zgaruvchi yoki ob'ektlarning adreslarini va NULL yoki 0 ni
oladilar. NULL simvolik konstantasi va boshqa bir necha standart
e'lon fayllarida aniqlangan. Pointerga NULL qiymatini berish pointerni 0 ga
tenglashtirish bilan tengdir, ammo C++ pointerga to'g'ridan-to'g'ri 0
qiymatini berish afzalroqdir, chunki agar NULL emas, 0 qiymati berilsa,
ushbu qiymat avtomatik ravishda pointerning tipiga keltiriladi. Butun
sonlardan faqat 0 qiymati pointerga keltirilishsiz berilishi mumkin. Agar
ma'lum bir hotira adresini pointerga bermoqchi bo'lsak, quyidagicha yozishimiz
mumkin:
int *iPtr, address = 0x45ad7 ;
iPtr = (int *) address; // C uslubida tiplarni keltirish
iPtr = static_cast(address); // C++ " " "
Lekin, albatta, yuqoridagi yozganimizni kamdam-kam qo'llashga to'g'ri
kelsa kerak, chunki adres olishning soddaroq yo'llari bordir. Aslida, pointer
e'lon qilinsa-yu lekin hali qo'llanilmayatgan bo'lsa, unga 0 qiymatini berish
tavsiya etiladi. Chunki, agar biz bu 0 qiymatli pointerni bilmasdan
qo'llamoqchi bo'lsak, kompilyator hato beradi, bunga sabab odatda operatsiyon
sistemalar 0 adresli hotira maydoni bilan ishlashga ruhsat bermaydilar. Shu
tariqa qiymatsiz pointer qo'llanilganidan ogoh bo'lamiz.
POINTER OPERATORLARI
O'zgaruvchilarning (yani harqanday ob'ektning) adresini olishda biz &
operatorini - adres olish operatorini qo'llaymiz. Bu kontekstda & operatori
bir dona argument oladi. Undan tashqari & ikkili operatori bitli qo'shishda
qo'llaniladi. Adres olishga misol keltiraylik.
int *iPtr, var = 44;
iPtr = &var;
double d = 77.0, *dPtr = &d;
Bu yerda bir narsani o'tib ketishimiz kerak. C++ da identefikatorlar
(o'zgaruvchi va ob'ektlar) ikki turda bo'ladi. Birinchisi chap
identefikatorlar (lvalue - left value: chap qiymat), ikkinchi tur esa o'ng
identefikatorlardir (rvalue - right value: o'ng qiymat). Yani chap
identefikatorlar '=' (qiymat berish operatori) belgisining chap argumenti
sifatida qo'llanilishi mumkin. O'ng identifikatorlar esa '=' ning o'ngida
qo'yilishlari kerak. Bunda o'ng identefikatorlar har doim ham chap
identefikator o'rnida qo'llanila olomaydilar. Yani chap identefikatorlarning
qiymatlari '=' operatori yordamida o'zgartirilishi mumkin. Agar o'zgaruvchi
const sifati bilan e'lon qilingan bo'lsa, u normal sharoitda faqat o'ng
identefikatordir. Bu ma'lumotlarni keltirganimizning sababi, & adres olish
operatori faqat chap identefikator bo'la oladigan o'zgaruvchilarga nisbatan
qo'llanilishi mumkin. Agar o'zgaruvchi const sifatli konstantalarga, register
sifatli o'zgaruvchilarga va ko'rsatkich qaytarmaydigan (adres qaytarmaydigan)
ob'ektlarga nisbatan qo'llanilishi ta'qiqlanadi.
Faraz qilaylik, biz pointerimizga boshqa bir o'zgaruvchining adresini berdik.
Endi bizning pointerimiz ikki qiymatni ifoda etmoqda, biri bu o'zining
qiymati, yani boshqa o'zgaruvchining adresi, ikkinchi qiymat esa, bu boshqa
o'zgaruvchining asl qiymatidir. Agar biz pointerning o'zi bilan ishlasak,
biz hotiradagi adres bilan ishlagan bo'lamiz. Ko'p hollarda esa buning
keragi yo'q. Pointer ko'rsatayotgan o'zgaruvchining qiymati bilan ushbu
pointer yordamida ishlash uchun biz '*' belgisini, boshqacha qilib etganda,
ko'rsatish operatorini (indirection, dereferencing operator) qo'llashimiz
kerak bo'ladi. Bunga misol beraylik.
...
int k = 74;
int *kPtr = &k;
...
cout << k << " --- " << *kPtr << endl; // Ekranda:
// 74 --- 74
cin >> *kPtr; // 290 qiymatini kiritaylik...
cout << k << " === " << *kPtr << endl; // Ekranda:
// 290 === 290
*kPtr = 555;
cout << k << " ... "<< *Ptr << endl; // Ekranda:
// 555 ... 555
...
Ko'rib turganimizdek, biz kPtr ko'rsatkichi orqali k o'zgaruvchining ham
qiymatlarini o'zgartira oldik. Demak *kPtr chap identefikatordir, chunki
uning qiymatini qiymat berish operatori yordamida o'zgartira olar ekanmiz.
Agar pointerimiz 0 ga teng bo'lsa, uni ko'rsatish operatori - '*' bilan
ko'llash ko'p hollarda dastur ijrosi hatolariga olib keladi. Undan tashqari,
boshlangich qiymatlari aniqlanmagan pointerni qiymatiga ko'rsatish eng kamida
mantiqiy hatolarga olib keladi, bunung sababi, pointer ko'rsatayotgan hotira
qismida oldingi ishlagan dasturlar kodlari qolgan bo'lishi mumkin. Bu esa
bizga hech keragi yo'q. Pointer bo'lmagan o'zgaruvchilarga ko'rsatish
operatorini qo'llash ta'qiqlanadi.
Aslini olganda, & adres olish operatori va * ko'rsatish operatorlari,
bir-birining teskarisidir. Bir misol kertiraylik.
// Adres olish va ko'rsatish operatorlari
# include char c = 44; // char tipidagi o'zgaruvchi
char *pc = &c; // char tipidagi pointer
int main ()
{
cout << "&*pc ning qiymati: " << &*pc << endl;
cout << "*&pc ning qiymati: " << *&pc << endl;
cout << "c ning hotiradagi adresi: " << &c << endl;
cout << "pc pointerning qiymati: " << pc << endl;
cout << "c ning qiymati: " << c << endl;
cout << "*pc ning qiymati: " << *pc << endl;
return (0);
}
Ekranda:
&*pc ning qiymati: 0x4573da55
*&pc ning qiymati: 0x4573da55
c ning hotiradagi adresi: 0x4573da55
pc pointerning qiymati: 0x4573da55
c ning qiymati: 44
*pc ning qiymati: 44
Demak, &*pc va *&pc ayni ishni bajarar ekan, yani * va & operatorlari
bir-birining teskarisidir. Hotiradagi adres ekranga boshqa ko'rinishda
chiqishi mumkin. Bu mashina va kompilyatorga bog'liqdir.
POINTER ARGUMENTLI FUNKSIYALAR
Funksiylar ikki argumentlariga qarab ikki turga bo'linadi degan edik.
Argumentlar qiymat bo'yicha, yoki ko'rsatkich bo'yicha berilishi mumkin edi.
Qiymat bo'yicha berilgan argumentning funksiya chaqirig'iga nushasi beriladi.
Ko'rsatkich bo'yicha argument chaqirig'ida, funksiyaga kerakli argumentga
ko'rsatkich beriladi. Ko'rsatkich bo'yicha chaqiriqni ikki usulda bajarish
mumkin, birinchi usul &-ko'rsatkichlar orqali amalga oshiriladi. Ikkinchi
usulda esa pointerlar qo'llaniladi.
Pointerlar bilan chaqishning afzalligi (qiymat bo'yicha chaqiriq bilan
solishtirganda) shundagi, agar ob'ektlar katta bo'lsa, ulardan nusha olishga
vaqt ketqizilmaydi. Undan tashqari funksiya ob'ektning asl nushasi bilan
ishlaydi, yani ob'ektni o'zgartura oladi. Funksiya faqat bitta ob'ektni yoki
o'zgaruvchini return ifodasi yordamida qiytara olgani uchun, oddiy yol bilan,
qiymat bo'yicha chaqiriqda funksiya faqat bitta o'zgaruvchining qiymatini
o'zgartira oladi. Agar pointerlarni qo'llasak, bittadan ko'p ob'ektlarni
o'zgartirishimiz mumkin, huddi &-ko'rsatkichli chaqiriqdagi kabi.
Funksiya chaqirig'ida esa, biz o'zgaruvchilarning adresini qo'llashimiz kerak.
Buni & adres olish operatori yordamida bajaramiz. Massivni berayatganda esa
adresni olish kerak emas, chunki massivning ismining o'zi massiv birinchi
elementiga pointerdir.
Pointerlarni qo'llab bir dastur yozaylik.
// Pointer argumentli funksiyalar
# include int foo1(int k) {return (k * k);}
void foo2(int *iPtr) {*iPtr = (*iPtr) * (*iPtr);}
int main()
{
int qiymat = 9;
int javob = 0;
javob = foo1(qiymat); // javob = 81
cout << "javob = " << javob << endl;
foo2(&qiymat); // qiymat = 81
cout << "qiymat = " << qiymat << endl;
return (0);
}
Ekranda:
javob = 81
qiymat = 81
Yuqoridagi dasturimizda foo2() funksiya chaqirig'ida qiymat nomli
o'zgaruvchimizning adresini oldik (& operatori) va funksiya berdik.
foo2() funksiyamiz iPtr pointer argumentining qiymatini * operatori yordamida
o'zgartiryapti.
Funksiya e'lonida pointer tipidagi parametrlardan keyin o'zgaruvchi ismlarini
berish shart emas. Masalan:
int func(int * , char * ); // funksiya e'loni
int func(int *arg1, char *arg2); // funksiya e'loni
Yuqoridagi ikki e'lon aynidir.
Aytib o'tkanimizdek, massivlarning ismlari birinchi elementlariga
ko'rsatkichdir. Hatto, agar massiv bir indeksli bo'lsa, biz massivlar bilan
ishlash uchun pointer sintaksisini qo'llashimiz mumkin. Kompilyator
foo(int m[]);
e'lonini
foo(int * const m);
e'loniga almashtiradi. Yuqoridagi m pointerini "int tipiga o'zgarmas pointer"
deb o'qiymiz. const bilan pointerlarning qo'llanilishini alohida ko'rib
chiqamiz.
const SIFATLI POINTERLAR
const ifodasi yordamida sifatlantirilgan o'zgaruvchining qiymatini normal
sharoitda o'zgartira olmaymiz. const ni qo'llash dasturning hatolardan
holi bo'lishiga yordam beradi. Aslida ko'p dasturchilar const ni qo'llashga
o'rganishmagan. Shu sababli ular katta imkoniyatlarni boy beradilar. Bir
qarashda const ning keragi yo'qdek tuyuladi. Chunki const ni qo'llash
dasturning hech qaysi bir yerida majburiy emas. Masalan konstantalarni
belgilash uchun # define ifodasini qo'llasak bo'ladi, kiruvchi argumentlarni
ham const sifatisiz e'lon qilsak, dastur mantig'i o'zgarishsiz qoladi.
Lekin const kerak-kerakmas joyda o'zgaruvchi va ob'ektlarning
holat-qiymatlarini o'zgartirilishidan himoyalaydi. Yani ob'ekt qiymatini faqat
cheklangan funksiyalar va boshqa dastur bloklari o'zgartira oladilar.
Bu kabi dasturlash uslubi esa, yani ma'lumotni berkitish va uni himoya
qilish ob'ektli dasturlash falsafasiga kiradi.
Ko'rsatkich qo'llanilgan funksiyalarda, agar argumentlar funksiya tanasida
o'zgartirilmasa, kirish parametrlari const deb e'lon qilinishlari kerak.
Masalan, bir massiv elementlarini ekranga bosib chiqaradigan funksiya
massiv elementlarini o'zgartirishiga hojat yo'q. Shu sababli argumentdagi
massiv const sifatiga ega bo'ladi. Endi, agar dasturchi adashib, funksiya
tanasida ushbu massivni o'zgartiradigan kod yozsa, kompilyator hato beradi.
Yani bizning o'zgaruvchimiz himoyalangan bo'ladi. Bu mulohazalar boshqa
tipdagi const sifatli funksiya kirish parametrlariga ham tegishlidir.
Pointerlar bilan const ni to'rt hil turli kombinatsiya qo'llashimiz mumkin.
1. Oddiy pointer va oddiy o'zgaruvchi (pointer ko'rsatayatgan o'zgaruvchi).
2. const pointer va oddiy o'zgaruvchi.
3. Oddiy pointer va const o'zgaruvchi.
4. const pointer va const o'zgaruvchi.
Yuqoridagilarni tushuntirib beraylik. Birinchi kombinatsiyada o'zgaruvchini hech bir narsa himoya qilmаyapti. Ikkinchi holda esa o'zgaruchining qiymatini
o'zgartirsa bo'ladi, lekin pointer ko'rsatayоtgan adresni o'zgartirish ta'qiqlanadi. Masalan massiv ismi ham const pointerdir. Va u ko'rsatayatgan massiv birinchi elementi-ni o'zgartirishimiz mumkin. Endi uchinchi holda pointeri-
miz oddiy, lekin u ko'rsatayatgan o'zgaruvchi himoyalan
gandir. Va nihoyat, to'rtinchi variantda eng yuqori darajadagi o'zgaruvchi himoyasita'minlanadi.
Yuqoridagi tushunchalarga misol berib o'taylik.
// const ifodasi va pointerlar
# include # include int countDigits(const char *); // oddiy pointer va const o'zgaruvchi
void changeToLowerCase(char *); // oddiy pointer va oddiy o'zgaruvchi
int main()
{
char m[] = "Sizni 2006 yil bilan tabriklaymiz!";
char n[] = "TOSHKENT SHAHRI...";
cout << m << endl <<"Yuqoridagi satrimizda " << countDigits(m)
<< " dona son bor." << endl << endl;
cout << n << endl << "Hammasi kichik harfda:" << endl;
changeToLowerCase(n);
cout << n << endl;
return (0);
}
int countDigits(const char * cpc) { // satrdagi sonlar (0..9) miqdorini
// hisoblaydi
int k = 0;
for ( ; *cpc != '\0' ; cpc++){ // satrlarni elementma-element
// ko'rib chiqishning birinchi yo'li.
if ( isdigit(*cpc) ) // kutubhona funksiyasi
k++;
}
return (k);
}
void changeToLowerCase(char *pc) { // katta harflarni kichik harflarga
// almashtiruvchi funksiya
while( *pc != '\0'){ // satrlarni elementma-element
// ko'rib chiqishning ikkinchi yo'li.
*pc = tolower( *pc ); // kutubhona funksiyasi
++pc; // pc keyingi harfga siljitildi
}
return;
}
Ekranda:
Sizni 2006 yil bilan tabriklaymiz!
Yuqoridagi satrimizda 4 dona son bor.
TOSHKENT SHAHRI...
Hammasi kichik harfda:
toshkent shahri...
Yuqoridagi dasturda ikki funksiya aniqlangan. Change ToLowerCase()funksiyasining parametri juda oddiydir. Oddiy char tipidagi pointer. Ushbu pointer ko'rsatayotgan ma'lumot ham oddiydir. Ikkinchi funksiyamizda esa
(countDigits()), pointerimiz oddiy, yani uning qiymati o'zgarishi mumkin, u hotiraning turli adreslariga ko'rsatishi mumkin, lekin u ko'rsatayotgan
o'zgaruvchi const deb e'lon qilindi. Yani pointerimiz ko'rsatayotgan ma'lumot ayni ushbu pointer yordamida o'zgartirilishi ta'qiqlanadi.
Bizda yana ikki hol qoldi, ular quyida berilgan:
const pointer va const o'zgaruvchi
const pointer va oddiy o'zgaruvchi
Birinchi holga misol beraylik.
...
int m = 88, j =77;
const int * const pi = &m; // const pointer e'lon paytida
// initsalizatsiya qilinishi shartdir
...
m = 44; // To'g'ri amal
*pi = 33; // Hato! O'zgaruvchi const deb belgilandi; birinchi const
pi = &j; // Hato! Pointer const deb belgilandi; int * dan keyingi const
...
j = *pi; // To'g'ri. const o'zgaruvchilarning
// qiymatlari ishlatilinishi mumkin.
...
Yuqoridagi parchada const pointer va const o'zgaruvchili kombinatsiyani ko'rib chiqdik. Eng asosiysi, const pointer e'lon qilinganda initsalizatsiya bo'lishi shart. Bu qonun boshqa tipdagi const o'zgaruvchilarga ham tegishli.
Ko'rib turganimizdek,
*pi = 33;
ifodasi bilan m ning qiymatini o'zgartirishga intilish bo'ldi. Lekin bu hatodir. Chunki biz pi pointeri ko'rsatib turgan hotira adresini o'zgarmas
deb pi ning e'lonida birinchi const so'zi bilan belgilagan edik. Lekin biz o'zgaruvchining haqiqiy nomi bilan - m bilan o'zgaruvchi qiymatini o'zgartira
olamiz. Albatta, agar m ham o'z navbatida const sifatiga ega bo'lmasa. Yani hotira adresidagi qiymatga ikkita yetishish yo'li mavjud bo'lsa, bular o'zgaruvchining asl nomi - m, va pi pointeri, bulardan biri orqali ushbu qiymatni o'zgartirsa bo'ladi, boshqasi o'rqali esa bu amal ta'qiqlanadi.
Keyin,
pi = &j;
ifoda bilan esa pi ga yangi adres bermoqchi bo'ldik. Lekin pointerimiz o'zgarmas bo'lgani uchun biz biz bu amalni bajara olmaymiz.
Pointerlar va const ifodasining birga qo'llanilishining to'rtinchi holida
const pointer va oddiy hotira adresi birga ishlatilinadi. Bunga bir misol:
int j = 84, d = 0;
int * const Ptr = &j; // e'lon davrida initsalizatsiya shartdir
*Ptr = 100; // to'g'ri amal
Ptr = &d; // Hato! Ptr ko'rsatayatgan
// hotira adresi o'zgartilishi ta'qiqlanadi
Yuqorida Ptr ko'ratayatgan adresni o'zgartirsak, kompilyator hato beradi.Aslida, massiv ismlari ham ayni shu holga misol bo'la oladilar. Massiv ismi massivning birinchi elementiga const pointerdir. Lekin u ko'rsatayotgan massiv birinchi elementning qiymati o'zgartilishi mumkin.
POINTER VA ODDIY O'ZGARUVCHILARNING EGALLAGAN ADRES KATTALIGI
Pointerlar istalgan ichki asos tipga (char, int, double ...) yoki qollanuvchi belgilagan tipga (class, struct ...) ko'rsatishi mumkin. Albatta, bu turli tiplar hotirada turlicha yer egallaydilar. char bir bayt bo'lsa, double 8.
Lekin bu tiplarga ko'rsatuvchi pointerlarning kattaligi bir hil 4 bayt. Bu kattalik turli adreslash turlariga qarab o'zgarishi mumkin, lekin bitta sistemada turli tipdagi ko'rsatkichlar bir hil kattalikga egadirlar. Buning sababi shuki, pointerlar faqat hotiraning adresini saqlaydilar. Hozirgi sistemalarda esa 32 bit bilan istalgan adresni aniqlash mumkin. Pointerlar
oz'garuvchining faqat boshlangich baytiga ko'rsatadilar. Masalan, bizda double tipidagi o'zgaruvchi d bor bo'lsin. Va unga ko'rsatuchi pd pointerimiz ham e'lon qilingan bo'lsin. Pointerimiz d o'zgaruvchisining faqat birinchi
baytiga ko'rsatadi. Lekin bizning d o'zgaruvchimizda pointerimiz ko'rsatayotgan birinchi baytidan tashqari yana 7 dona qo'shimcha bayti mavjud.
Mana shunda pointerning tipi o'yinga kiradi. Kompilyator double tipi hotirada qancha joy egallishi bilgani uchun, pointer ko'rsatayotgan adresdan boshlab qancha baytni olishni biladi. Shuning uchun pointerlar hotirada bir hil joy egallasa ham, biz ularga tip beramiz. void * tipidagi pointerni ham e'lon qilish mumkin. Bu pointer tipsizdir. Kompilyator bu pointer bilan * ko'rsatish operatori qo'llanilganda necha bayt joy bilan ishlashni bilmaydi.
Shu sababli bu amal tayqiqlangandir. Lekin biz void * pointerini boshqa tipdagi pointerga keltirishimiz mumkin, va o'sha yangi tip bilan ishlay olamiz.
Masalan:
...
int i = 1024;
int *pi = &i, *iPtr;
void *pv;
pv = (void *) pi;
cout << *pv; // Hato!
iPtr = (int *) pv;
cout << *iPtr; // Ekranda: 1024
...
Tiplarning hotiradagi kattaligini kopsatadigan, bir parametr oladigan sizeof() (sizeof - ning kattaligi) operatori mavjuddir. Uning yordamida tiplarning,
o'zgaruvchilarning yoki massivlarning kattaliklarini aniqlash mumkin. Agar o'zgaruvchi nomi berilsa, () qavslar berilishi shart emas, tip, massiv va pointer nomlari esa () qavslar ichida beriladi. Bir misol beraylik.
// sizeof() operatori
# include int k;
int *pk;
char ch;
char *pch;
double dArray[20];
int main()
{
cout << sizeof (int) << " - " << sizeof k << " - " << sizeof (pk) << endl;
// tip nomi o'zgaruvchi pointer
cout <cout << "\nMassiv hotirada egallagan umumiy joy (baytlarda): "
<< sizeof (dArray) << endl;
cout << "Massivning alohida elementi egallagan joy: "
<< sizeof (double) << endl;
cout << "Massivdagi elementlar soni: "
<< sizeof (dArray) / sizeof (double) << endl;
return (0);
}
Ekranda:
4 - 4 - 4
1 - 1 - 4
Massiv hotirada egallagan umumiy joy (baytlarda): 160
Massivning alohida elementi egallagan joy: 8
Massivdagi elementlar soni: 20