C++

Funksiyalar

Funksiyalar

C++ dilinin proqramçılar arasında ən məşhur dil olmasında rol oynayan iki ən güclü imkanından biri funksiyalardır. Funksiyalar bizə proqramın istənilən yerindən digər hissəsinə (funksiyaya) müraciət etməyə imkan verir. Proqramda funksiyadan istifadə etmək üçün biz əvvəlcə funksiyanı elan etməliyik. Daha sonra isə, funksiyanın proqram kodunu tərtib etməliyik.

5.1 Funksiyanın elanı

C++ dilində funksiya aşağıdakı kimi elan olunur:

Nəticənin_tipi funksiyanın_adı (tip1 argument1, tip2 argument2, ...);

Burada nəticənin_tipi funksiyanın qaytaracağı nəticənin tipini göstərir. Əgər funksiya heç bir nəticə qaytarmırsa, onda nəticənin_tipi olaraq void yazırıq.

funksiyanın_adı olaraq ingilis əlifbasının hərflərindən, rəqəmlərdən, _ simvolundan istifadə edə bilərik. Funksiya adı mütləq ingilis əlifbası hərfi ilə başlamalıdır və operator adları ilə üst-üstə düşməməlidir. Funksiyanın adından sonra mötərizə daxilində funksiyanın qəbul edəcəyi arqumentlərin siyahısı verilir. Arqumentlər bir-birindən vergüllə ayrılır. Arqumentlərin əsas tipi önəmlidir. Funksiyanın elanında arqumentlərə verilən adlar heç bir əhəmiyyət daşımır və onlar buraxıla bilər.

Aşağıdakı kimi:

Nəticənin_tipi funksiyanın_ad (tip1, tip2, ...);

Nümunə:

int cem (int x, int y);

Burada biz int tipli nəticə qaytaran və int tipli iki arqument qəbul edən cem funksiyası elan elədik. Biz bunu aşağıdakı kimi də yaza bilərik, harada ki, arqumentlərin adları göstərilmir.

int cem (int, int );

 

5.2 Funksiyanın mətn kodunun tərtibi

Funksiyanı elan etməklə biz kompilyatora funksiya (ad, tip, arqumentlər) barəsində məlumat veririk.Funksiyanın mətn kodunu tərtib etməklə biz onun görəcəyi işi proqramlaşdırmış oluruq. Bunun üçün aşağıdakı qaydadan istifadə edirik:

Nəticənin_tipi funksiyanın_ad (tip1 arg1, tip2 arg2) {

proqram kodu

return nəticə;

}

Burada ilk sətir funksiyanın elanı sətridir. Fərq yalnız odur ki, mötərizədən sonra ; deyil { simvolu gəlir. { simvolu funksiyanın proqram kodu blokunun başlanğıcını bildirir. { simvolundan sonra funksiyanın proqram kodu yerləşdirilir. Burada biz adi halda olduğu kimi, istənilən proqram kodu yerləşdirə bilərik və hətta digər funksiyalara müraciət də edə bilərik. Bundan əlavə, biz funksiyanın öz kodu daxilində onun özünə müraciət də edə bilərik. Buna proqramlaşdırmada rekursiya deyirlər.

Gəlin, yuxarıda elan etdiyimiz cem funksiyasının proqram kodunu tərtib edək:

int cem (int x, int y)

{

int z;

z = x + y;

return z;

}

Burada funksiyanın daxilində int tipli z dəyişəni elan etdik. Daha sonra, z dəyişəninə funksiyanın arqumentlərinin (x və y) cəmini mənimsətdik və alınmış qiyməti nəticə olaraq qaytardıq.

 

5.3 Return əmri, funksiyadan geri qayıtma

Proqramda hər- hansı funksiyaya müraciət aşağıdakı şəkildəki kimi baş verir:

 

 

Proqram kodu icra olunur və hansısa yerdə funksiyaya müraciət olunur. Bu zaman proqramın hal-hazırda icra olunan instruksiyasının ünvanı yadda saxlanılır və icraolunma çağırılan funksiyanın kodu yerləşən hissəyə ötürülür. Funksiya öz işini yekunlaşdırdıqdan sonra isə, icraolunma yenidən proqramın funksiya çağırılan yerinə qaytarılır (həmin yerin ünvanı yaddaşa yerləşdirilmişdi).

Funksiyanı çağırmaq üçün biz onun adından istifadə edirik (aşağıdakı proqram nümunəsinə bax). Bəs icraolunma funksiyadan onu çağıran kod hissəsinə geri necə ötürülür? Funksiyanı çağırmaq və ondan geri qayıtmaq üçün prosessorun call və ret assembler instruksiyalarından istifadə olunur, lakin mən bu məsələdə çox dərinə getmək istəmirəm. Sadəcə olaraq, onu bilməyimiz kifayətdir ki, funksiyanın daxilində istənilən yerdən geri qaytarmaq istəyiriksə (funksiyadan çıxmaq) return operatorundan istifadə edirik.

Funksiya daxilində return operatoru icra olunan yerdən sonra gələn hissələr yerinə yetirilmir. Biz dedik ki, funksiyanın tipi ya hər hansı tip, ya da void ola bilər (funksiya heç bir nəticə qaytarmır). Əgər funksiya icra olunduqdan sonra hər hansı nəticə qaytarmalıdırsa, bu da return operatoru vastəsilə həyata keçirilir. Bu zaman funksiyanın qaytaracağı məlumatı return operatoruna arqument kimi vermək lazımdır.

Aşağıdakı kimi:

return netice;

 

5.4 Funksiyalardan istifadə

Biz funksiyaların elanı, mətn kodunun tərtibi və funksiyadan qayıtmanın qaydalarını öyrəndik. İndi isə, gəlin, funksiyalardan istifadə olunan proqram nümunələri ilə tanış olaq.

Proqram nümunəsi:

Funksiyadan istifadə etməklə, iki ədədin cəmini hesablayan proqram:


//prg_5_1.cpp

#include <iostream>

/* cəm funksiyanın elanı */ 
int cem (int x, int y);

int main (int argc, char *argv[])
{ 
int x,y,z;
std::cout<<"x-i daxil edin \n"; 
std::cin>>x;
std::cout<<"y-i daxil edin \n"; 
std::cin>>y;
/* cəm funksiyasını çağırırıq */ 
z = cem(x,y);
std::cout<<"x ilə y-in cəmi = "<<z<<"\n";
return 0; 
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
/* cəm funksiyasının kodu */ 
int cəm ( int dey1, int dey2) 
{
int dey3; 
dey3 = dey1 + dey2;
return dey3; 
}

Nümunə

Proqramı kompilyasiya edib yerinə yetirək:


C:\cpp\prog2\Debug> 
C:\cpp\prog2\Debug>prog2.exe 
x-i daxil edin 7 
y-i daxil edin 789 
x ilə y-in cəmi = 796 
C:\cpp\prog2\Debug> 
C:\cpp\prog2\Debug>

İzahı:

Baxdığımız proqramda əvvəlcə cem funksiyasının elanı sətri yerləşir, daha sonra isə, proqramın əsas funksiyası main funksiyası gəlir. Burada istifadəçi x və dəyişənlərinin qiymətlərini daxil edir. Daha sonra bu dəyişənlər cem funksiyasına ötürülür, cem funksiyası bu dəyişənlərin cəmini hesablayır və nəticəni qaytarır.

 

5.5 Lokal və Qlobal dəyişənlər

Funksiyalardan istifadə edərkən bilməli olduğumuz vacib anlayışlardan biri də lokal və qlobal dəyişənlər anlayışıdır. Nədir lokal və qlobal dəyişənlər? lokal və qlobal dəyişənlərin nə olduğunu bilmək üçün biz blok anlayışını daxil etməliyik. C++ dilində { və } mötərizələri arasında qalan hissə blok adlanır.

Əgər diqqət yetirsəniz, görərsiniz ki, funksiyanın mətn kodu bütövlükdə bir blok-dan ibarətdir. blok daxilində blok elan edə bilərik və bu zaman "içəridə" yerləşən blok–lar "üst" blok-lardakı dəyişənləri görür, "üst" blok-lar isə, "içəri" blok-larda elan olunan dəyişənləri görmür.

Aşağıdakı kimi:


{ 
/* blok A */
int x; 
/* x y-i görmür*/
  {
  /* blok B */int y;
  /* y isə x-i görür, ona görə yaza bilərəm. */
  y = x;
    {
    /* blok C */
    int z;
    /* z-i nə blok A, nə də blok B görmür. */
    /* z isə x və y-i görür, ona görə yaza bilərəm. */
    z = x + y;
    /* blok C-nin sonu */
    }
  /* blok B-nin sonu */
  }
/* blok A-nın sonu */
}

 

Bir az əvvəl daxil etdiyimiz proqrama nəzər salsaq, görərik ki, cem funksiyası daxilində biz int tipli dey3 dəyişəni elan etmişik. Aydındır ki, bu dəyişən main funksiyası üçün lokaldır, yəni biz main funksiyasından və ümumiyyətlə, proqramın cəm funksiyasından başqa heç bir yerindən dey3-ə müraciət edə bilmərik.

 

5.6 Dəyişənlərin Ünvana və Qiymətə görə ötürülməsi

Yuxarıda baxdığımız proqramda biz cem funksiyasına arqument olaraq int tipli iki dəyişən ötürdük. Funksiya bu dəyişənlərin cəmini hesablayıb nəticə olaraq qaytardı. Bu zaman biz dəyişənlərin funksiyaya qiymətə görə ötürülməsi qaydasından istifadə etdik. Bir çox hallarda isə bizə nəticə ilə yanaşı, funksiyanın ona ötürülən parametrlərin də qiymətlərini dəyişdirməsi tələb olunur.

Bu zaman isə, biz dəyişənlərin funksiyaya ünvana görə ötürülməsi qaydasından istifadə etməliyik.

Fərq nədədir?

 

Dəyişənlərin qiymətə görə ötürülməsi.

Dəyişəni qiymətə görə ötürəndə (indiyə kimi baxdığımız hal), dəyişənlərin nüsxələri (kopiya) yaradılır və funksiyaya bu nüsxələr ötürülür. Aydın məsələdir ki, bu zaman nüsxə üzərində aparılan heç bir əməliyyat dəyişənlərin orijinal qiymətlərinə təsir etmir. Bir tərəfdən bu, yaxşıdır, çünki bu zaman biz dəyişənləri mühafizə etmiş oluruq. Amma pis cəhət odur ki, dəyişənlərin nüsxəsinin yaradılmasına həm əlavə vaxt, həm də yaddaşda əlavə yer ayrılır və iri həcmli dəyişənlər olanda, bu qayda sərfəli olmur. Həm də, əgər məsələnin tələbi ilə funksiya parametrlərinin qiymətlərinin dəyişdirilməsi lazım olsa, qiymətə görə ötürmədə biz bunu edə bilmərik.

 

Dəyişənlərin ünvana görə ötürülməsi.

Ünvana görə ötürülmə zamanı isə funksiyaya ötürülən dəyişənlərin heç bir nüsxəsi yaradılmır, funksiyaya dəyişənlərin yaddaşdakı ünvanları ötürülür. Bu zaman funksiya daxilində dəyişən üzərində aparılan bütün əməlyyatlar funksiya bitdikdən sonra qüvvədə qalır. Dəyişənləri ünvana görə ötürmək üçün biz funksiyanı aşağıdakı kimi elan etməliyik:

Nəticənin_tipi funksiyanın_adı (tip1 *arg1, tip2 *arg2, ...);

Dəyişənləri bu funksiyaya parametr kimi ötürəndə isə, onların əvvəlinə ünvan - & operatoru əlavə etməliyik.

 

Nümunə:

Ünvana görə ötürülmə zamanı funksiyanın elanı:

int funk (int *, int *);

Funksiyaya müraciət:

int x,y;

funk(&x, &y);

Gəlin, arqumentlərin funksiyaya qiymətə və ünvana görə ötürülməsinin fərqinə baxmaq üçün proqram tərtib edək.


// prg_5_2.cpp

#include<iostream>

 void funk1 (int);
 void funk2 (int*);

 int main(){
  int x=45;
  std::cout<<"x-in ilkin qiyməti "<<x<<"\n";
  funk1(x);
  std::cout<<"x-in funk1-dən sonrakı qiyməti "<<x<<"\n";
  funk2(&x);
  std::cout<<"x-in funk2-dən sonrakı qiyməti "<<x<<"\n";
  return 0;
}
   
  // qiymətə görə ötürülmə
 void funk1 (int x){
  x=90;
  std::cout<<"x-in funk1 daxilində qiyməti "<<x<<"\n";
 }

  // ünvana görə ötürülmə
 void funk2 (int *x){
  *x=200;
  std::cout<<"x-in funk2 daxilində qiyməti "<<*x<<"\n";
 }
 
Nümunə

Proqramı icra edək:


C:\cpp\prog2\Debug> 
C:\cpp\prog2\Debug>prog2.exe 
x-in ilkin qiyməti 45
x-in funk1 daxilində qiyməti 90
x-in funk1-dən sonrakı qiyməti 45
x-in funk2 daxilində qiyməti 200
x-in funk2-dən sonrakı qiyməti 200
C:\cpp\prog2\Debug> 
C:\cpp\prog2\Debug>

İzahı:

funk1-də arqument funksiyaya qiymətə görə, funk2-də isə ünvana görə ötürülür. Başqa

sözlə, funk1-də arqumentin nüsxəsi yaradılır və funksiyaya nüsxə ötürülür, funk2-də isə arqumentin ünvanı funksiyaya ötürülür. funk1 daxilində ona ötürülən dəyişən üzərində aparılan bütün dəyişikliklər funksiya qayıtdıqdan sonra itir. funk2-də bütün dəyişikliklər birbaşa dəyişənə aid yaddaş sahəsi üzərində aparıldığından, funksiya qayıtdıqdan sonra ona ötürülən parametr üzərində aparılmış bütün dəyişikliklər qalır.

 

*5.7 Ünvan Dəyişənlərinin Funksiyaya Ünvana və Qiymətə görə ötürülməsi

(*Çətinliyi artırılmış mövzudur)
Biz adi dəyişənlərin funksiyaya qiymətə və ünvana görə ötürülməsi ilə tanış olduq. İndi isə, gəlin, ünvan dəyişənlərinin funksiyaya parametr kimi ötürülməsi və bu zaman qiymətə və ünvana görə ötürülmənin fərqi ilə tanış olaq.


// prg_5_3.cpp

#include<iostream>

 void funk1(int *);
 void funk2(int **);

 int x,y,*z;

int main(){
x=35;
y=68;
std::cout<<"x-in və y-in qiymətləri "<<x<<" , "
     <<y<<"\n";
std::cout<<"x-in və y-in ünvanları "<<&x<<" , "
     <<&y<<"\n";
// z-ə x-in ünvanını mənimsədək
z=&x;
std::cout<<"z-in ilkin qiyməti "<<z<<"\n"
     <<"bu ünvanda olan məlumat "<<*z<<"\n";
// ünvan dəyişəninin qiymətə görə ötürülməsi
funk1(z);
std::cout<<"z-in funk1-dən sonrakı qiyməti "<<z<<"\n"
     <<"bu ünvanda olan məlumat "<<*z<<"\n";
// ünvan dəyişəninin ünvana görə ötürülməsi
funk2(&z);
std::cout<<"z-in funk2-dən sonrakı qiyməti "<<z<<"\n"
     <<"bu ünvanda olan məlumat "<<*z<<"\n";
return 0;
}

Nümunə

Proqramı icra edək:


C:\cpp\prog2\Debug> 
C:\cpp\prog2\Debug>prog2.exe 
x-in və y-in qiymətləri 35 , 68
x-in və y-in ünvanları 0x6013a0 , 0x6013a4
z-in ilkin qiyməti 0x6013a0
bu ünvanda olan məlumat 35
z-in funk1 daxilində qiyməti 0x6013a4
bu ünvanda olan məlumat 68
z-in funk1-dən sonrakı qiyməti 0x6013a0
bu ünvanda olan məlumat 35
z-in funk2 daxilində qiyməti 0x6013a4
bu ünvanda olan məlumat 68
z-in funk2-dən sonrakı qiyməti 0x6013a4
bu ünvanda olan məlumat 68
C:\cpp\prog2\Debug>
C:\cpp\prog2\Debug>

İzahı:

Proqramda qlobal x,y dəyişənləri və z ünvan dəyişəni elan edirik. Əvvəlcə x və y-in qiymətlərini və ünvanlarını çap edirik. Daha sonra, z-ə x-in ünvanın mənimsədirik. funk1-ə z-i qiymətə görə parametr kimi ötürürük. funk1-in daxilində z-in qiymətin dəyişib y-in ünvanına mənimsədirik. funk1-dən sonra z-in qiyməti dəyişməmişdir, o əvvəlki kimi x-ə istinad edir. funk2-yə isə z-i ünvana görə ötürürük, buna görə funk2 daxilində z üzərində etdiyimiz dəyişiklik funksiya qayıtdıqdan, olduğu kimi saxlanılır.