Fonksiyonlar

C'de fonksiyonlar programı oluşturan yapılardır ve tüm aktivite fonksiyonlarda gerçekleştirilir. Bir fonksiyonun genel şekli şudur:

dönüş_veri_tipi fonksiyon_ismi (formal parametreler){
kod parçası
}

Dönüş veri tipi olarak normal veri tiplerinden biri yazılır ve fonksiyon çalıştırıldıktan sonra bu tipte bir değer verir.

Formal parametreler fonksiyona argüman olarak verilecek verilerin yerlerine geçeceği parametrelerdir ve parantez içine "veri_tipi değişkenin_ismi" formatında birbirlerinden virgülle ayrılarak yazılmalıdır.

Kod parçası ise fonksiyon çağrıldığında çalışacak olan kısımdır.

Fonksiyonlar içinde ilan edilen değişkenler de yerel değişkenlerdir ve sadece kendi blokları içinde bilinirler. Ayrıca fonksiyon içinde fonksiyon tanımlanamaması da C'nin kurallarından biridir. Şimdi tek tek yukarıdaki yapıdaki kısımları inceleyelim.


Formal Parametreler

Bunlar fonksiyona verilecek argümanların yerine geçeceği değişkenlerdir ve şu formatta yazılmalıdır:

(veri_tipi değişken1, veri_tipi değişken2, ...)

Örnek verecek olursak:

(int a, char b)

Ayrıca bir tipten birden fazla formal parametre olsa bile hepsine tip girilmelidir:

(int a, int b, char c)
(int a, b, char c)

Yukarıdaki iki örnekten üstteki doğru alttaki yanlıştır.

#include <stdio.h>

int f1(int x, int y);

int main(void){
    int a,b;
    printf("Enter values of a and b\n");
    scanf("%d",&a);
    scanf("%d",&b);
    int c=f1(a,b);
    printf("%d",c);
    return 0;
}

int f1(int x, int y){
    return (x+y)*(x+y);
}

Yukarıdaki örneği inceleyelim. Bir fonksiyonun değeri kullanılacaksa öncesinde fonksiyon ilan edilmelidir. İlan etme "dönüş_tipi fonksiyon_ismi(formal parametreler);" şeklinde olmalıdır. Yani fonksiyonun tanımlanmasındaki parantezler dışındaki kısımla aynıdır. Örnekte bu "int f1(int x, int y);" şeklinde yapılmıştır.

Daha sonra a ve b değişkenleri tanımlanmış ve bu değişkenlerin değerleri f1 fonksiyonuna argüman olarak verilmiştir. Çıkan sonuç da c'ye atanmıştır. Şimdi fonksiyon çağrıldığında yapılan işlemleri inceleyelim:

Fonksiyona a ve b argümanları verilmiştir. Bu da demektir ki fonksiyondaki x formal parametresinin değeri a'nın değeri, y formal parametresinin değeri b'nin değeri olacaktır. Daha sonra da fonksiyon "(x+y)*(x+y)" ifadesinin değerini verecektir.

Daha sonra main fonksiyonunda bu değer c'ye atanacak sonra da c'nin değeri yazdırılacaktır.

Yukarıdaki örnekte fonksiyonun değişkenlerle tanımlandığını ve fonksiyona da değişkenlerin argüman olarak verildiği görülmektedir. Burada fonksiyon argüman olarak verilen değişkenlerin kendilerini değil değerlerini kullanmıştır. Bu nedenle fonksiyon içinde yapılan değişiklikler değişkenleri etkilemeyecektir.

#include <stdio.h>

int f(int x);

int main(void){
    int a=6;
    int c=f(a);
    printf("%d\n",c);
    printf("%d",a);
    return 0;
}

int f(int x){
    int t=1;
    for(;x>0;x--){
        t=x*t;
    }
    return t;
}

Misalen yukarıdaki örnekte a'nın değeri fonksiyona argüman olarak verilmiştir. Fonksiyonun çalışması a'da herhangi bir değişikliğe neden olmamıştır. Faktöriyel alan fonksiyon çalıştırıldıktan sonra c'nin değeri 720 a'nın değeri 6 olacaktır.

Fonksiyonları çalıştırmanın bir başka yolu ise fonksiyona argüman olarak değişkenlerin adresini vermektir. Bu durumda fonksiyonda yapılan işlemler değişkenin adresi üzerinde yapılacağı için değişkenin değeri de değişebilir. Örnek olarak yukarıdaki kodu fonksiyona argüman olarak değişken yerine değişken adresi vererek yapalım:

#include <stdio.h>

int f(int *x);

int main(void){
    int a=6;
    int c=f(&a);
    printf("%d\n",c);
    printf("%d",a);
    return 0;
}

int f(int *x){
    int t=1;
    for(;*x>0;(*x)--){
        t=(*x)*t;
    }
    return t;
}

Yukarıdaki örnekte yine aynı işlem yapılmıştır fakat bu sefer fonksiyon işaretçi ile tanımlanmış ve fonksiyona argüman olarak değişkenin adresi verilmiştir. Bu nedenle x'de yapılan değişiklikler a'ya da yansımıştır. x'in son değeri 0 olduğu için a'nın da son değeri 0 olacaktır. Sonuç bu sefer 720 ve 6 değil 720 ve 0'dır.

Dizilerin fonksiyonlara nasıl argüman olarak verilebileceğini ve dizilerle fonksiyonların nasıl tanımlanacağını daha önceden diziler konusunda söylemiştik. Hatırlanacağı gibi bu durumlarda da fonksiyona dizilerin adresi verilmektedir ve bu nedenle fonksiyon içerisinde yapılacak değişiklikler de diziye yansımaktadır.

#include <stdio.h>

void f(int x[3][3]);
int i,j;

int main(void){
    int a[3][3]={(1,1,1),(2,2,2),(3,3,3)};
    f(a);
    for(i=0;i<3;i++){
        for(j=0;j<3;j++){
            printf("%d\t",a[i][j]);
        }
    printf("\n");
    }
    return 0;
}

void f(int x[3][3]){
    for(i=0;i<3;i++){
        for(j=0;j<3;j++){
            x[i][j]=x[i][j]*x[i][j];
            printf("%d\t",x[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

Misalen yukarıdaki örnekte fonksiyonda diziye yapılan değişiklikler dışarıdaki a dizisine de yansımıştır. Bu nedenle fonksiyon çalıştırıldıktan sonra a'nın değerleri yazdırıldığında fonksiyonda yazdırılanlarla aynı sonuç görülmüştür.

Bunun dışında main fonksiyonuna da argüman verilebilir. Bu şekilde programın çalışması için program çalıştırılırken komut satırına ek bilgi girilmesi gerekecektir. main fonksiyonu ise şu şekilde tanımlanmalıdır:

int main(int argc, char *argv[ ])

argc ve argv isimleri geleneksel olmakla birlikte bunları kullanmak zorunlu değildir. Bu şekilde yazıldığında argv programın ismini ve girilen komutu tutar. argc ise argv[ ] içinde tutulan dizgi sayısını depolar. argv[ ] dizisinin ilk elemanı daima programın ismidir.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]){
    if (argc<2){;
        printf("Please run the program with a password");
        getchar();
        exit(1);
    }
    char password[]="Test";
    if (!strcmp(argv[1],password)){
        printf("Welcome\n");
        getchar();
    }
    else{
        printf("You have written a wrong password");
        getchar();
        exit(1);
    }
    return 0;
}

Yukarıdaki örnek kod derlendikten sonra oluşan .exe dosyası direkt açılırsa "Please run the program with a password" yazısı çıkacaktır ve bir karakter girilir girilmez program sonlanacaktır. Çünkü argv dizgisi sadece programın ismini tutmaktadır ve bu nedenle eleman sayısı, yani argc değişkeninin değeri 1'dir. Kodda bu değerin 2'den küçük olması durumunda uygulanacaklar belirtilmiştir.

Eğer bu .exe dosyası bulunduğu konumdan komut satıırı ile misalen "C:\Users\...\program_ismi.exe Test" şeklinde açılırsa bu durumda karşımıza "Welcome" yazısı çıkacaktır. Başka değerler girilmesi dışında ise "You have written a wrong password" ifadesi ile karşılaşılacaktır. Bunun mantığı şudur: Girdiğimiz komut da argv dizisinde tutulacaktır, bu nedenle argc değeri 1'den büyük olacak ve ilk if komutu çalıştırılmayacaktır; daha sonra, ileride değinilecek olan strcmp fonksiyonu ile girilen komutun "Test" dizgisiyle aynı olup olmadığı kontrol edilecek ve aynı ise "Welcome" yazısı yazdırılacak, aksi durumda yanlış bir şifre girildiği uyarısı verilecektir.

argv dizgisine birden fazla dizgi sabiti atamak da mümkündür. Ayrıca bu dizgilerin içerisindeki tüm karakterlere de erişilebilir. Eğer birden fazla dizgi sabiti girilmesi isteniyorsa dizgi sabitleri arasında boşluk bırakılmalıdır.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]){
    if (argc<3){;
        printf("Please enter a name and a surname");
        getchar();
        exit(1);
    }
    int i,j;
    for (i=1;i<3;i++){
        for (j=0;argv[i][j];j++){
            printf("%c\n",argv[i][j]);
        }
        printf("\n");
    }
    getchar();
    return 0;
}

Misalen yukarıdaki örnek "C:\Users\...\program_ismi.exe Gokhan Keskin" şeklinde çalıştırıldığında çıktı şu olacaktır:

G
o
k
h
a
n

K
e
s
k
i
n

Görüldüğü gibi tek bir karaktere erişmek için argv dizisinde iki indis kullanılmalıdır. Birinci indis dizgiyi, ikinci indis de dizginin içindeki karakteri seçecektir.

Şimdiye kadar olan kısımda tanımlamadaki formal parametreleri inceledik. Geriye iki kısım kaldı: Dönüş tipi ve fonksiyonun içerisindeki kod parçası. Fonksiyonun içerisindeki kod parçası genel hatlarıyla yapılacak işlemler olduğundan ve daha önce main fonksiyonunda kullandığımız kod parçalarından farklı olmadığından burada incelenmesi gereken tek kısım fonksiyondaki return komutudur. Bu komut ile dönüş tipi birbirleriyle ilişkili olduğundan ikisini birlikte işlemek yararlı olacaktır.


return Komutu ve Dönüş Tipi

Bir fonksiyonun nasıl tanımlandığını daha önceden görmüştük:

dönüş_tipi fonksiyonun_ismi (formal parametreler){
Kod parçası
}

Bazı fonksiyonlar çalışmalarından sonra bir değer oluştururlar. Hatta eğer dönüş tipi void dışındaki bir veri tipiyse oluşturmalıdırlar. Dönüş tipi oluşacak değerin hangi tipte olacağını belirler. Oluşturulacak değer return komutu ile belirlenir. Bu komuta daha önceden sıçrama komutlarında kısaca değinmiştik ve en baştan beri örneklerimiz içerisinde de bu komuta yer vermiştik. Bu komuta bu kısımda daha detaylı bakalım.

return komutundan sonra gelen ifadeden oluşacak değer çağrılan fonksiyonun değeri olur.

#include <stdio.h>

int f1(int x);
int f;

int main(void){
    printf("Enter a value for f\n");
    scanf("%d",&f);
    int a=f1(f);
    printf("%d",a);
    return 0;
}

int f1(int x){
    int t;
    int sum=1;
    for(t=1;t<=x;t++){
        sum=sum*t;
    }
    return sum;
}

Yukarıdaki örnekte en üstte fonksiyon prototipi yazılmıştır. Bu bir fonksiyonu kullanmadan önce yapılan bir uygulamadır ve daha önceki örneklerimizde de bu mevcuttur. Bunun detayları yazının ilerleyen kısımlarında anlatılacaktır.

Daha sonra bir f değişkeni ilan edilmiş, bu değişken için kullanıcıdan değer istenmiş, ardından bir a değişkeni ilan edilmiş ve f1 fonksiyonuna f argümanı verildiğinde oluşacak değer bu değişkene atanmıştır. Fonksiyon çalışırken f değişkeninin değeri, fonksiyonun argümanı olduğu için, x'e atanır ve fonksiyon gerekli işlemlerle faktöriyel hesabı yapar. Son adımda faktöriyel değerini tutan değişken sum değişkenidir. return komutunun karşısına bu değişken yazılır ve fonksiyonun değeri bu değişkenin değeri olur. Buna fonksiyon bu değere döner de denir.

Sonuç olarak karşımıza f'ye verdiğimiz değerin faktöriyeli çıkacaktır.

C89'da return komutunun sonrası boş da bırakılabilir. Bu durumda fonksiyon bilinmeyen bir değere dönecektir. C99 standartlarında bu duruma izin verilmemektedir. Ayrıca fonksiyon içerisinde return komutu kullanılmazsa fonksiyon fonksiyonun son parantezine ulaşıldığında sonlanacaktır. Bu durumda da fonksiyon bilinmeyen bir değere dönecektir.

Fonksiyonlar çağrıldıklarında değerleri bir değişkene atanabilir veya fonksiyon tek başına çalıştırılabilir. Fakat fonksiyon çağrısı bir atama işleminin sol tarafında bulunamaz. Üstteki örnek fonksiyonun değerinin bir değişkene atanmasına örnektir. Diğer iki durum için aşağıdaki örnekleri inceleyelim:

#include <stdio.h>

int f1(int x);
int f;

int main(void){
    printf("Enter a value for f\n");
    scanf("%d",&f);
    int a=f1(f);
    return 0;
}

int f1(int x){
    int t;
    int sum=1;
    for(t=1;t<=x;t++){
        sum=sum*t;
    }
    printf("%d",sum);
    return sum;
}


#include <stdio.h>

int f1(int x);
int f;

int main(void){
    printf("Enter a value for f\n");
    scanf("%d",&f);
    int a;
    f1(f)=a;
    return 0;
}

int f1(int x){
    int t;
    int sum=1;
    for(t=1;t<=x;t++){
        sum=sum*t;
    }
    printf("%d",sum);
    return sum;
}

Üstteki örnekte fonksiyon çalıştırılmış fakat değeri herhangi bir değişkene atanmamıştır. Değerin yazdırılması fonksiyon içinde yapılmıştır. Alttaki örnek ise çalışmayacaktır. Çünkü fonksiyon çağrısı bir atama işleminin sol tarafındadır.

Fonksiyonlar sonuç olarak bir işaretçi de oluşturabilirler. Bunun için fonksiyonun dönüş tipi bir işaretçi tipi olarak girilmelidir.

#include <stdio.h>

int *func(int x[]);

int main(void){
    int a[8]={1,2,3,4,5,6,7,8};
    int *b=func(a);
    printf("%d",(*b)*(*b));
    return 0;
}

int *func(int x[]){
    int y;
    scanf("%d",&y);
    int i;
    for(i=1;i<=7;i++){
        if(y==x[i]){
            return &x[i];
        }
    }
}

Misalen yukarıdaki örnekte bir dizi tanımlanmış ve bu dizi fonksiyona verilmiştir. Fonksiyon bir sayı girilmesini istemekte ve eğer girilen sayı dizinin elemanlarından biriyse dizinin o elemanının adresi fonksiyonun değeri olacaktır. Daha sonra bu adres b işaretçisine atanmakta ve b adresindeki değerin karesi yazdırılmaktadır.

void tipindeki fonksiyonlar ise herhangi bir değere dönmezler. Sadece içerisindeki kod parçası çalışır. Bu nedenle bu fonksiyonların çağrıları bir değişkene de atanamaz. Bilindiği gibi main fonksiyonu da bir fonksiyondur ve bu fonksiyon da tanımlanış itibariyle bir int değerine döner. Bu nedenle bu fonksiyon 0 değerine döndürülür. main fonksiyonunu bir değere döndürmek aynı argümanla exit fonksiyonunu çağırmaya eşdeğerdir.

C'de bir fonksiyon kendini de çağırabilir. Fakat bu durumda bu döngüye belli bir yerde son verecek bir mekanizmanın oluşturulmasına dikkat edilmelidir.


Fonksiyon Prototipi

C'de zorunlu olmasa da bir programda fonksiyon, çağrılmadan önce ilan edilmelidir. Bu derleyicinin çalışmasını iyileştirir. İlan etme işlemi fonksiyon prototipi ile yapılır. Fonksiyon prototipi fonksiyon tanımlamasının kod parçası olmayan halidir. Aşağıdaki örnekler birer fonksiyon prototipidir:

int f(int x, int y);
void function(char *p);

Eğer bir fonksiyonun tanımlanması kullanılmasından önce yapılmışsa bu durumda fonksiyon prototipine gerek yoktur. Fakat aynı anda farklı kişiler tarafından belli parçaları yazılarak oluşturulacak programlar göz önüne alındığında her zaman fonksiyon tanımını kullanılmasından önce yapmak mümkün olmamaktadır.

Kütüphanelerde yer alan fonksiyonları kullanmak için ise fonksiyon prototipine kod içinde gerek yoktur. Koda kütüphanenin eklenmesi yeterlidir.


inline

C99 ile birlikte inline ismi verilen bir kelime eklenmiştir. inline kelimesi ile ilan edilen fonksiyonlar çalışırken çağrılmazlar. Bunun yerine fonksiyon kod içerisine yerleştirilir. inline kelimesinin daha iyi anlaşılması için aşağıdaki iki eşdeğer örnek incelenebilir:

#include <stdio.h>
int func(int x,int y);

int main(void){
    int a,b;
    printf("Enter values for x and y\n");
    scanf("%d",&a);
    scanf("%d",&b);
    int c=func(a,b);
    printf("%d",c);
    return 0;
}

inline int func(int x, int y){
    return (x+y)*(x+y);
}


#include <stdio.h>

int main(void){
    int a,b;
    printf("Enter values for x and y\n");
    scanf("%d",&a);
    scanf("%d",&b);
    int c=(a+b)*(a+b);
    printf("%d",c);
    return 0;
}

Görüldüğü gibi inline kelimesi fonksiyonun kodun içine yayılmış gibi çalışmasını sağlamaktadır. Bu fonksiyon çağrılmasını ve yeni formal parametreler oluşturulmasını gerektirmediği için kodu daha verimli kılar, fakat kodun boyutu büyüyecektir. Bu nedenle bu kelime gerçekten kodun hızlı çalışmasına etkisi büyük olan yerlerde ve küçük fonksiyonlarda kullanılmalıdır.

inline kelimesi sadece derleyiciye iletilen bir talebi ifade eder. Derleyici bu talebi yok sayabilir. Unutulmaması gereken bir nokta da fonksiyon prototipi veya tanımından sadece birinde inline kelimesinin kullanılması gerektiğidir. İkisinde birden kullanılması kodun çalışmasını engelleyecektir.





İşaretçiler <<<<< Temel C >>>>> Yapılar