C de Veri Tipleri

Endorfin35+

Kayıtsız Üye
Katılım
1 Mayıs 2020
Mesajlar
4,193
Bu başlık altında C dilince dizi tanımlama, pointerlar ve ilişkileri hakkında öğrendiğim bilgileri toparlayarak paylaşacağım. Hatalı bilgiler görürseniz bildiriniz.


1622378826209.png

Kaynak : https://www.geeksforgeeks.org/

Öncelikle yukarıdaki tabloyu paylşamak istiyorum. Tablo C dilinde değer (veri) taşıyan öğeleri gruplandırarak göstermekte. Soldaki birinci grup temel değişkenleri kapsamakta ve bu gurupta hepimizin bir şekilde bildiği ineteger, float, boolean gibi değişkenler yer almakta. Ortadaki grup fonksiyonlar, diziler, pointerlar ı kapsamakta ve son grup ise kullanıcı tarafından oluşturulan veri türlerini kapasamakta. Şimdi düşündüğüm zaman soldaki temel değişkenler diğer grupların içerisinde de yer almakta. Değişken var ise veri vardır. Veri var ise bellek kullanımı vardır...​


Bellek (Ram) :

Programın çalışma zamanında veriler rem üzerinde oluşturulur ve saklanır. Rem 8 bit/1 bayt kapasiteli depolama alanlarından oluşur.

1622379833693.png


Bu veri depoalama alanlarına ulaşabilmek için her alan bir adrese sahiptir. Rem adresleri genellkile 16 sayı tabanında gösterilirler. Sisteminde ram kapasitesi kadar adres bulunur.

1622380525828.png


Değişkenler:

Herhangi bir değişken tanımlandığı anda c dili bizim için rem üzerinde tanımlanan türün kapasitesi kadar yer ayırır.

Örneğin integer türünde bir değişken tanımladığımızda,

C:
int a;

1622380884313.png


int a; tanımlamasını (declaretion) yaptığımızda, C dili (aslında işletim sistemi) Ram üzerinde şu an bilmediğimiz herhangi bir yerde (adreste) bizim için int türünün kapasitesi kadar bellek alanı ayırır. int türü genellikle 4 byte kapasiteli olmakla beraber bu durum kullanılan sisteme göre değişiklik gösterebilir. Tanımladığımız değişkenin kapasiteni öğrenmek için sizeof() fonksiyonu kullanılabilir.
C:
#include <stdio.h>
int main()
{
    int a;
    printf("%lu \n",sizeof(a));
}
Program Çıktısı:
4

1622381328792.png


int türü için 4 byte kullanıldığını doğruladık ve int a tanımlaması için ramde yukarıda turuncu renki alanın bizim a değişkenimiz içn ayrıldığını söyeleyebiliriz.


5 adet in türü değişken tanımlayarak değer atamadan mevcut değerlerine bakalım;
C:
#include <stdio.h>

int main()
{
    int a,b,c,d,e;
    printf("%d \n",a);
    printf("%d \n",b);
    printf("%d \n",c);
    printf("%d \n",d);
    printf("%d \n",e);
}
Program Çıktısı:
21845
-8608
32767
0
0

Değer olarak anlık hafızanın ayrılan bölümünde ne değer var ise onu alıyoruz. Tesadüfen o alan boş ise veriler sıfır olarakta dönebilir...


Peki birde a ya değer atayalım... (Değişkenlere ilk defa değer atanmasına ilklendirme / initializing denir.)
C:
int a=65;
1622385840625.png


Bize ayrılan hafıza alanında artık sadece atadığımız 65 değeri var. artık verileri silerek üzerine 65 yazdık...

Peki sistem a değişkenin remin neresinde olduğunu nereden biliyor..? a değişkenin bulunduğu pozisyonun bir adresi var. a değişkenimiz bu adresi temsil ediyor. Biz a üzerinde bir işlem yaptığımızda aslında a nın sahip olduğu adreste işlem yapıyoruz.

a nın adresini öğrenelim...

C:
#include <stdio.h>

int main()
{
    int a=65;
  
    printf("%d \n",a);
    printf("%p \n",&a);  // değişkenin adresini "&" işareti ile öğreniyoruz.
}
Program Çıktısı:
65
0x7fffffffdd64

1622386322795.png


a değişkenimiz aslında 0x7fffffffdd64 adresini temsil ediyor...

Normalde program içersinde birden çok farklı türlerde değişkenlerimiz bulunur. Sistem bu değişkenler kendi uygun gördüğü (bize göre rastegele görünen) adreslerde depolar. Biz sadece o alandaki içeriği değiştiririz.

birkaç farklı değişken tanımlayalaım.
C:
#include <stdio.h>

int main()
{
    int   a=65;
    bool  b=false;
    char  c='A';
    int   d=66;
    int   e=67;
    float f=3.12;
  
    printf("a değişkenin adresi : %p \t kapladigi alan : %lu byte \n",&a,sizeof(a));
    printf("b değişkenin adresi : %p \t kapladigi alan : %lu byte \n",&b,sizeof(b));
    printf("c değişkenin adresi : %p \t kapladigi alan : %lu byte \n",&c,sizeof(c));
    printf("d değişkenin adresi : %p \t kapladigi alan : %lu byte \n",&d,sizeof(d));
    printf("e değişkenin adresi : %p \t kapladigi alan : %lu byte \n",&e,sizeof(e));
    printf("f değişkenin adresi : %p \t kapladigi alan : %lu byte \n",&f,sizeof(f));
}
Program Çıktısı:
a değişkenin adresi : 0x7fffffffdd58     kapladigi alan : 4 byte
b değişkenin adresi : 0x7fffffffdd56     kapladigi alan : 1 byte
c değişkenin adresi : 0x7fffffffdd57     kapladigi alan : 1 byte
d değişkenin adresi : 0x7fffffffdd5c     kapladigi alan : 4 byte
e değişkenin adresi : 0x7fffffffdd60     kapladigi alan : 4 byte
f değişkenin adresi : 0x7fffffffdd64     kapladigi alan : 4 byte

1622390853790.png

Değişkenlerimizin rem üzerine yukarıdaki tablodaki gibi yerleştiğini görüyoruz. Buradaki yerleşim düzeninden bir anlam çıkarmamalıyız bu yerleşim düzeni bizim kontrolümüzün dışında...


Birde dizi tanımlayarak dumuna bakalım.
C:
#include <stdio.h>

int main()
{
    int   a[3]={10,11,12};

    printf("a dizisinin  adresi : %p \t kapladigi alan : %lu byte \n\n",a,sizeof(a));
    printf("a dizisinin 1.elemanın adresi : %p \n", &a[0]);
    printf("a dizisinin 2.elemanın adresi : %p \n", &a[1]);
    printf("a dizisinin 3.elemanın adresi : %p \n", &a[2]);

}
Program Çıktısı:
a dizisinin  adresi : 0x7fffffffdd5c     kapladigi alan : 12 byte

a dizisinin 1.elemanın adresi : 0x7fffffffdd5c
a dizisinin 2.elemanın adresi : 0x7fffffffdd60
a dizisinin 3.elemanın adresi : 0x7fffffffdd64
1622391436840.png


Burada ise bizim için önemli bir detay var. Dizi tanımlandığı zaman dizi elemanları ram üzerinde sıralı olarak yerleştiriliyor. Bu yerleşimlerin faydalarını pointer konusunda daha detaylı anlayacağız. şimdilik yerleşimleri zihnimizde görselleştirmek adına bir aşama sağladık...
 

Ekler

  • 1622386331866.png
    1622386331866.png
    15.5 KB · Görüntüleme: 146
Son düzenleme:
Pointer Kavramı :

Önceki mesajda değişkenlerimizi ram üzerinde belirli adreslerde tutulduğundan ve bu adresleri nasıl öğreneceğimizden bahsetmiştim. Şimdi ise bu adreslere göre nasıl işlem yapıyoruz ona bakalım.

Ram adreslerini saklayan değişkenlere pointer denir. Yani pointerda bilgi olarak sadece ram adresi saklamaya yarayan bir değişken türüdür. Pointer değişkenler tanımlanır iken mutlaka "*" karakteri ile adlandırma yapılır. İkinci ve önemli diğer bir detay ise pointer değişkeni saklayacağı adreste bulunan veri türü ile aynı türde tanımlanır. Pointer başka bir değişkenin adresini sakladığı/göstertiği için pointerlara gösterici de denir.
C:
#include <stdio.h>

int main()
{
    int  a=5;  // int türünde a değişkenini tanımladık.
    int *p;    // int türü için p adında pointer tanımladık.
    p = &a;    // a nın adresini p pointerine atadık.


    printf("a nin degeri \t\t:  %d \n", a);
    printf("a nin adresi \t\t:  %p \n", &a);
    printf("p nin gosterdigi adres \t:  %p \n", p);
    printf("p nin gosterdigi adresdeki veri :  %d \n", *p);
}
Program Çıktısı:
a nin degeri            :  5
a nin adresi            :  0x7fffffffdd5c
p nin gosterdigi adres  :  0x7fffffffdd5c
p nin gosterdigi adresdeki veri :  5

Ne yaptığımızı tekrar özetleyerek pekiştimeye çalışayım;
1. Pointer tanımlarken mutlaka hedef değişken türünde tanımlama ve * karakteri ile başlayarak adlandırma yapıyoruz.
C:
 int *p;    // int türü için p adında pointer tanımladık.
2. Pointer sadece bir değişkenin adresini saklar. Pointer a değer atanırken "*" karakteri olmadan değer atanır. adresi alınacak değişkenin ise önüne "&" karakteri eklenir.
C:
 p = &a;    // a nın adresini p pointerine atadık.
3. Pointerin sakladığı/gösterdiği adresi görüntülemek/kullanmak için başında "*" karakteri olmadan işlem yapılır.
C:
printf("p nin gosterdigi adres \t:  %p \n", p);
4. Pointerin sakladığı/gösterdiği adresteki veriyi görüntülemek/kullanmak için "*" karakteri ile birlikte işlem yapılır.
C:
printf("p nin gosterdigi adresdeki veri :  %d \n", *p);

int *p; ile pointer ımızı tanımladığımız anda bir değer atamadığımız için bu aşamada ramdeki artık verilerden dolayı kontrolümüz dışında bir adresi gösteriyor olabilir. Kullanmadan önce mutlaka pointera bir adres atamalıyız. Ayrıca pointerın adresini göstereceği değişken hali hazırda tanımlanmış ise pointer tanımlanırken de değer ataması şu şekilde yapılabilir.
C:
int *p=&a;
Sonradan değer atanacak ise "*" kullanılmadan atama yapılması gerektiğini tekrar hatırlatayım...

Pointer ı gösterdiği değişkenin yerine kullanmak :

Normalde a değişkenine değer atadığımız zaman aslında a nın temsil ettiği adrese değer ataması yapıyoruz. Peki biz pointerda bu adrese sahip isek doğrdan o adrese değer atayarak a nın değerinide değiştirmiş olumuyuz? Evet aynen a yı değiştirmiş oluyoruz.

"p" başında "*" olamadan yalın kullanımda adres bilgisine ulaşıyoruz.
"*p" başına "*" ekleyerek kullanımda doğradan o adrese ulaşıyoruz. Dolayısı ile o adrese okuma yazma yapabiliyoruz.

Örnek ile görelim.
C:
#include <stdio.h>

int main()
{
    int  a=5;  // int türünde a değişkenini tanımladık.
    int *p;    // int türü için p adında pointer tanımladık.
    p = &a;    // a nın adresini p pointerine atadık.

    printf("a nin degeri :  %d \n", a);

    *p=6;   // a nın sahip olduğu adrese doğrudan atama yaptık.

    printf("a nin degeri :  %d \n", a);
}
Program Çıktısı:
a nin degeri :  5
a nin degeri :  6
En temel hali ile pointer kullanımının özü bu kadardır. Bu aşamaya kadar olan işlemleri iyi sindirmek gerekli...

Pointer nerede?

Pointer ında bir değişken/veri türü olduğundan bahsetmiştim. O halde bir pointer tanımladığında pointerın kendiside ram üzerinde bulunarak bir alan işgal etmektedir. Bu alan ram üzerinde herhangi bir yerde olabilir. Yani pointerın kendiside bir adrese ve boyuta sahiptir.

1622395701306.png


C:
#include <stdio.h>

int main()
{
    int  a=5;  // int türünde a değişkenini tanımladık.
    int *p;    // int türü için p adında pointer tanımladık.
    p = &a;    // a nın adresini p pointerine atadık.

    printf("a degeri :  %d \n", a);
    printf("a boyutu :  %lu \n",sizeof(a));
    printf("a adresi :  %p \n\n", &a);

    printf("p gosterdiği adresteki veri :  %d \n", *p);
    printf("p gosterdiği adresteki boyut:  %lu \n", sizeof(*p));
    printf("p gosterdiği adres          :  %p \n\n", p);

    printf("p nin kendi adresi :  %p \n", &p);
    printf("p nin kendi boyutu :  %lu \n", sizeof(p));
}
Program Çıktısı:
a boyutu :  4
a adresi :  0x7fffffffdd5c

p gosterdiği adresteki veri :  5
p gosterdiği adresteki boyut:  4
p gosterdiği adres          :  0x7fffffffdd5c

p nin kendi adresi :  0x7fffffffdd60
p nin kendi boyutu :  8

Yukarıdaki kod ve çıktısı incelendiğinde görüleceği üzere pointerın kendisi de ram üzerinde bir adreste saklanıy ve 8 bayt yer kaplıyor...


Dıdının dıdısının dıdısı...

Pointer da bir adrese sahip ise bu adreside başka bir pointerda saklamak mümkündür. Hatta bu başka pointerın adresini bir başkasında saklmak bir başkasının adresini bir başkasında saklamak ve işlem yapılmak istendiğinde esas ana değişkenin sahip olduğu veriye bu pointerlar üzerinden erişerek okuma/yazma yapmak mümkündür. Tam kimin eli kimin cebinde durumu... :)

C:
#include <stdio.h>

int main()
{
    int  a=5;
    int *p=&a;      // p : a nın adresini gösteriyor
    int **q=&p;     // q : p nin adresini gösteriyor
    int ***r=&q;    // r : q nun adresini gösteriyor

    printf("a degeri :  %d \n", a);
    printf("a degeri :  %d \n", ***r);

    ***r=27;  // adresler takip edilerek a nın adresine atama yapılıyor.

    printf("a degeri :  %d \n", ***r);
    printf("a degeri :  %d \n", a);
}
Program Çıktısı:
a degeri :  5
a degeri :  5
a degeri :  27
a degeri :  27

Her pointer ın pointeri tanımlanır/kullanılır iken "*" karakteri sayısı bir artırılır...


Evet sanırım pointer ın temeli bu kadar. Ne işimize yarar derseniz onuda takip eden konularda aktarmaya çalışacağım....
 
Son düzenleme:
Dizilerin Pointer ile ilişkisi:

Bir dizi tanımlandığı zaman dizi elemanları için ramde sıralı bir düzende yer ayrıldığından bahsetmiştim.

1622397235544.png


pointer yardımı ile dizinin elemanlarına erişmek mümkündür.

C:
#include <stdio.h>

int main()
{
    int  dizi[5] = {10,11,12,13,14};   
    int *p=&dizi[0]; // dizinin ilk elemanın adresini atadık.

    
    printf("Dizinin 1. elemanı %d \n",dizi[0]);
    printf("Dizinin 2. elemanı %d \n",dizi[1]);

    printf("Dizinin 1. elemanı %d \n",*p);
    printf("Dizinin 2. elemanı %d \n",*p+1);

    printf("Dizinin 1. elemanının adresi %p \n",p);
    printf("Dizinin 2. elemanının adresi %p \n",p+1);

}
Program Çıktısı:
Dizinin 1. elemanı 10
Dizinin 2. elemanı 11
Dizinin 1. elemanı 10
Dizinin 2. elemanı 11
Dizinin 1. elemanının adresi 0x7fffffffdd50
Dizinin 2. elemanının adresi 0x7fffffffdd54

Pointer dizinin ilk elemanını gösterdiği için pointer adresi ile doğrudan dizinin ilk elemanına ulaşılır.
Pointer adresine 1 eklenerek dizinin ikinci elemanına ulaşılır.

Dizimiz integer türünde ve her bir eleman 4 bayt yer kapladığı için dizinin bir sonraki elemanın adresi +4 daha fazla bir değere sahiptir. Ancak biz pointera +4 yerine +1 yazıyoruz. Bunun nedeni pointer ımız int türünde tanımlandı ve kendisine 1 eklendiğinde +4 yapması gerektiğini biliyor... İşin özü bu konuda biz sadece ulaşmak istediğimiz elemanın kaçıncı eleman olduğunu pointer a ekleyerek doğrudan erişim sağlıyoruz. Pointer a "=" operatörü ile bir atama yapmadığımızda pointer değerimiz sabit kalıyor. 4. elemanı istiyorsam *p+4 ...


Dizi = Pointer :

Şimdi biraz kafa karıştırmaya başlayalım...

C:
int main()
{
    int  dizi[5] = {10,11,12,13,14};   
    int *p;
    int *q;
    p = &dizi[0]; // dizinin ilk elemanın adresini atadık.
    q = p;

    printf("Dizinin 3. elemanı %d \n",*q+3);
}
Program Çıktısı:
Dizinin 3. elemanı 13

Ne yaptık... iki ayrı pointer tanımladık. ilk pointer a dizinin ilk elemanın adresini atadık. Sonra ikinci pointer a " q = p " ifadesi ile ilk pointer ın değerini atadık. Pointerlara sadece adres atayabiliyorduk. p zaten bir adres değeri taşıdığında p yi q ya atarken ayrıca "&" karakteri kullanmadık. Eğer "&" karakteri kullansaydık dizinin değilde p nin kendi adresini atamaya çalışacaktık.... Neyse kodumuz doğru ve çalışıyor dikkat çekmek istediğim nokta bir pointerdan diğerine atama işlemi idi...

şimdi şu koda bakalım;
C:
#include <stdio.h>

int main()
{
    int  dizi[5] = {10,11,12,13,14};   
    int *p;
    p = dizi;

    printf("Dizinin 3. elemanı %d \n",*p+3);
}
Program Çıktısı:
Dizinin 3. elemanı 13

pointer a dizinin adresini tanımlarken & işaretini kullanmadım ve kodumu sorunsuz çalıştı. Aksine p=&dizi şeklinde atama yapmamada derleyicim izin vermedi...

bir önceki örnekte pointer a pointer ı, q = p şeklinde atamıştık. şimdide p = dizi şeklinde bir atama yapıyoruz. Bundan çıkan sonuç dizi aslında bir pointer dır. Diziyi ifade eden [ ] köşeli parantez görüldüğünde aslında gizli bir pointer ifadesi yer almaktadır.

şimdi şu koda bakalım;
C:
#include <stdio.h>

int main()
{
    int  dizi[5] = {10,11,12,13,14};   
    int *p = dizi;
    
    p[2]=55;

    printf("Dizinin 3. elemanı %d \n",dizi[2]);
    printf("Dizinin 4. elemanı %d \n",p[3]);
}
Program Çıktısı:
Dizinin 3. elemanı 55
Dizinin 4. elemanı 13

p pointerımızı diziymiş gibi [ ] köşeli parantez ile kullanarak diziye değer atadık, diziden değer okuduk...

Sonuç : dizi = pointer

Dinamik Diziler:

Bir dizi tanımlanırken dizinin eleman sayısı bir değişken ile belirlenebilir. Dizinin kaç elemanlı olacağı klavye/kullanıcı girişi ile tespit edilebilir. Bölyece dizinin boyutu çalışma zamanında dinamik olarak belirlenir. Bu durum C ye sonradan eklenmiş bir özellik olmakla beraber bazı kaynaklada standart dışı olarak nitelendirilir...

C:
#include <stdio.h>

int main()
{
    int x = 0;
    printf ("Dizi icin eleman sayisi giriniz :");
    scanf ("%d",&x);
    int dizi[x];

    for (int k=0;k<x;k++)
    {
        dizi[k]=11+k;
        printf("Dizinin %d. elamanın degeri : %d \n",k+1,dizi[k]);
    }
}
Program Çıktısı:
Dizinin 1. elamanın degeri : 11
Dizinin 2. elamanın degeri : 12
Dizinin 3. elamanın degeri : 13
Dizinin 4. elamanın degeri : 14
Dizinin 5. elamanın degeri : 15
Dizinin 6. elamanın degeri : 16
Dizinin 7. elamanın degeri : 17
Dizinin 8. elamanın degeri : 18
Dizinin 9. elamanın degeri : 19
Dizinin 10. elamanın degeri : 20
Dizinin 11. elamanın degeri : 21
Dizinin 12. elamanın degeri : 22

Bu kaynak kodu dizinin eleman sayısını bilmiyor. Remde ne kadar alan ayırlacak bilmiyor. Dizinin eleman sayısı çalışma zamanı içerisinde belli oluyor. Ve bu nasıl oluyor emin değilim. Hafızada yeterli alan yoksa ne olacak..? Bir karambol durumu olabilir....

Daha düzgün bir yöntem ise hafıza ayırarak dinamik dizilerin oluşturulması. Bunun için stdlib.h kütühanesi içerisindeki malloc(), calloc(), realloc() ve free() fonksiyonlarından yararlanıyoruz.

malloc : hafızada yer ayırır.
calloc : hafıza yer ayırır ve bu yerin içini temizler
realloc : hafızada ayrılan yerin boyutunu değiştirir
free : hafıza da ayrıan yeri iptal eder. (boşa çıkartır)

Örnekleyelim;
C:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int e = 0;
    printf ("Dizi icin eleman sayisini giriniz :");
    scanf ("%d",&e);
    
    int *dizi;
    dizi = (int*) malloc (e*sizeof(int));
    
    if (dizi==NULL) { printf ("Yeterli Bellek Yok"); return 0; }

    printf ("Dizi icin eleman sayisi %d  \n",e);
    
    printf ("Dizi icin yeni eleman sayisini giriniz :");
    scanf ("%d",&e);

    dizi=(int*) realloc (dizi,(e*sizeof(int)));
    if (dizi==NULL) { printf ("Yeterli Bellek Yok"); return 0; }

    printf ("Dizi icin eleman sayisi %d  \n",e);

    free(dizi);
}
Program Çıktısı:
Dizi icin eleman sayisini giriniz :10
Dizi icin eleman sayisi 10 
Dizi icin yeni eleman sayisini giriniz :20
Dizi icin eleman sayisi 20
 
C:
int main()
{
    int *p;

    *p=5;

   printf("%d \n",*p);
}

Çalışmayan bir kod... ( en azından bende çalışmıyor )

Sadece bir pointer tanımlayıp bir adres tanımlamaz isek pointerı kullanamayız. Pointer hiçbir yeri veya saçma bir yeri gösteriyor olabilir. Daha doğrusu nereyi göstereceğini bilmiyor...

Ancak Char türü bir pointer tanımlayıp içerisine bir string atadığımda;
C:
int main()
{
    char *ptr="Merhaba";
    printf("%s \n",ptr);
}
Kod sorunsuz(?) çalışıyor gibi görünüyor. Birkaç deneme yaptım. Bu durumun sadece karakter dizilerine özgü olduğunu gördüm. Ve yine sadece çift tırnak ile string atandığınında gerçekleşen durum. Hehangi bir adres atamadığımız pointer ımız (") çift tırnak ın kerameti ile ram üzerinde işlevsel bir konuma yerleşiyor...

Önceki mesajda köşeli parantez ile tanımladığımız dizilerin aslında bir pointer olduğunu kod örnekleri ile beraber açıklamıştık. O Halde aşağıdaki gibi bir karakter dizisi tanımlarken köşeli parantez ve pointer kullanımı arasında ne fark var anlamaya çalışalım....

C:
int main()
{
    char metin[]="Merhaba";
    char *yazi ="Merhaba";
    printf("%p \n",metin);
    printf("%p \n",yazi);
}

metin içeriğinde dizinin hafıza üzerinde nerede olduğunu saklar ve bu sabit (const) bir değerdir. metin in gösterdiği adres değiştirilemez...
yazi da aynı görevi yapar ancak yazıya başka çalışma zamanı içesinde başka bir adres atanabilir. Bu durumla ilgili zaten derleyici uyarıyı yapıştırıyor.

1622977697345.png


program içerisinde yazi pointerinin adresi değiştirilecek ise uyarı görmezden gelinir. Adres sabit kalıcak ise uyarıdan kurtulmak için pointer const olarak tanımlanır.
C:
  const char *yazi ="Merhaba";


Pointer Dizileri:

pointerları dizi olarak tanımlamak mümkündür...

C:
#include <stdio.h>
int main()
{
    const char *bolum[3]={"elektrik","elektronik","mekanik"};
    printf("%s \n",bolum[0]);
    printf("%s \n",bolum[1]);
    printf("%s \n",bolum[2]);
    printf("Dizinin adresi     : %p \n",bolum);
    printf("1. elemanın adresi : %p \n",&bolum[0]);
    printf("2. elemanın adresi : %p \n",&bolum[1]);
    printf("3. elemanın adresi : %p \n",&bolum[2]);
}
Program Çıktısı:
elektrik 
elektronik 
mekanik 
Dizinin adresi     : 0x7fffffffdd50 
1. elemanın adresi : 0x7fffffffdd50 
2. elemanın adresi : 0x7fffffffdd58 
3. elemanın adresi : 0x7fffffffdd60
 
Son düzenleme:
enum (numaralandırma) :

Değişkenin alacağı değerler ve tam sayı ve daha önceden belirli değerler ise enum kullanılabilir. Enum rakamlar üzerinde rakamlar yerine anlamlı kelimeler çalışma imkanı sağlar.

Örnekler ile konuyu pekiştirelim;
işci = 0, şef =1, mudur=2 kodlarına sahip olsun.
C:
#include <stdio.h>

int main()
{
    enum yetki {isci,sef,mudur} personel1;

    printf("Yetki Kodu : %d \n",personel1);

}
Program Çıktısı:
Yetki Kodu : 0

enum ile yetki listesini oluşturarak personel1 adlı değişkeni tanımladık... Herhangi bir değer atamadığımız için değişkenimiz 0 değerini aldı. Listenin sıralamadı dizilerde olduğu gibi sıfırdan başlar. Perseonel1 için mudur ataması yaparak aldığı değeri görelim.
C:
#include <stdio.h>
int main()
{
    enum yetki {isci,sef,mudur} personel1;

    personel1=mudur;

    printf("Yetki Kodu : %d \n",personel1);
}
Program Çıktısı:
Yetki Kodu : 2

Aynı türden bir değişken daha tanımlamal için aşağıdaki gibi yazım kullanılabilir.
C:
    enum yetki {isci,sef,mudur} personel1;
    enum yetki personel2;

    personel1=mudur;
    personel2=sef;

liste elemanlarının sıra numarası değeri istenildiği gibi değiştirilebilir.
C:
    enum yetki {isci=1,sef,mudur=5,patron=25} personel1;

    personel1=sef;  // 2 değerini alır.

Typedef :

bir veri türünü belirlenen bir takma isim ile tanımlama için kullanılır.
Örneğin,
C:
int main()
{
    typedef int rakam;

    rakam sayi1, sayi2;

    sayi1=5;
}
int türü typedef ile rakam olarak adlandırılmış ve sonrasında int türünden sayi1 ve sayi2 değişkenini tanımlamak için artık rakam türünden sayi1,sayi2 tanımlanmıştır...

Ornek Kullanım:
C:
#include <stdio.h>
int main()
{
    typedef enum {otomobil=120,otobus=105,kamyon=90} limit;

    limit arac1=otobus;
    limit arac2=otomobil;

    printf("Arac1 Hız limiti : %d 'km/h dir.  \n",arac1);
}
Program Çıktısı:
Arac1 Hız limiti : 105 'km/h dir.
 
Struct :

Struck bir grup değişkeni tek bir grup altında toplayan kullanıcı (programcı) tanımlı bir veri türüdür.

C:
#include <stdio.h>

struct personel
{
    int yas;
    char *unvan;
};


int main()
{
    personel ahmet;
    ahmet.yas=18;
    ahmet.unvan="isci";

    printf ("ahmetin yasi   : %d \n",ahmet.yas);
    printf ("mehmetin unvanı: %s \n",ahmet.unvan);
    
}
personel adında bir tür tanımladık. Personel adlı türümüz yas ve unvan olmak üzere iki ayrı değişkeni bunyasinde barınrıyıor. Bu veri türümüzün adı personel olduğu için bu türden bir değişken(nesne) tanımlar iken personel ahmet; yazdık. personel turunden ahmet adlı bir nesnemiz( değişkenimiz) oldu. Alt öğelerine (üyelerine) ulaşmak için ise "." nokta kullanıyoruz. ahmet.yas hasan.yas=24 gibi...


struct tanımlanır iken yapıyı kullanacak değişkenlerde yapının sonuna eklenebilir.
Örnek:
C:
#include <stdio.h>

struct personel
{
    char *isim;
    int yas;
    char *unvan;
} kisi1, kisi2;
// kisi1 ve kisi2 şimdiden tanımlandı.


int main()
{
    kisi1.isim="Hasan";
    kisi2.yas= 34;

    personel kisi3;  // ihtiyaç oldu kisi3 tanımlandı...
    kisi3.yas=41;

    printf (" %d \n",kisi3.yas);

}

Yapı Kurulurken değişkenlere değer atanır ise bu değerler varsayılan değerleri oluştururur.

C:
#include <stdio.h>

struct personel
{
    char *isim ="isimsiz";
    int yas=18;
    char *unvan="isci";
} ;


int main()
{
    personel kisi1;
    printf (" %s \n",kisi1.isim);
    kisi1.isim="serafettin";

    printf (" %s \n",kisi1.isim);

}
Program Çıktısı:
 isimsiz
 serafettin

Varsayılan değerler için şuda mümkündür;
C:
struct personel
{
    char *isim ="isimsiz";
    int yas=18;
    char *unvan="isci";
} kisi1 = {"Ahmet",22,"sofor"};

isimsiz yapılar da karşımıza çıkabilir. Dez avantaj olarak bu yapılardan sonradan nesne oluşturamayız.

C:
#include <stdio.h>

struct
{
    char *isim;
    int yas;
    char *unvan;
} kisi1;


int main()
{   
    kisi1.isim="ahmet"
}

typedef ile struck kullanımı...
C:
#include <stdio.h>

typedef struct
{
    char *isim;
    int yas;
    char *unvan;
} personel;


int main()
{   
    personel kisi1,kisi2;

    kisi1.isim="Ahmet";
}
 
Struck Pointer :
C:
#include <stdio.h>
#include <stdlib.h>

typedef enum
{
    kursun,
    kurukalem,
    tukenmez,
    dolma
} kalem_t;


typedef struct
{
    kalem_t tipi;
    const char *rengi;
    float kalinlik;
} Kalemler;


int main()
{   
    Kalemler kalem1;
    kalem1.tipi=kursun;
    kalem1.rengi="kara";
    kalem1.kalinlik=0.7;

    Kalemler *kalem2; // struck turunden pointer tanımladık.
    kalem2 = (Kalemler*) malloc(sizeof(Kalemler)); // pointer için hafızamızı ayarladık.

    kalem2->kalinlik=1.2;
    kalem2->rengi="KIRMIZI";
    kalem2->tipi=kurukalem;

printf("%s \n",kalem2->rengi);
printf("%s \n",kalem1.rengi);

}
 
Fonksiyonlar :

1. Parametre almayan değer döndürmeyen fonksiyon:


değer döndürmeyen fonksfiyonlar void türündendir.

C:
#include <stdio.h>

void selamla()
{
    printf("Selam \n");
}

int main()  // Ana fonksiyon...
{
    selamla();
    selamla();
}
Program Çıktısı:
Selam
Selam

selamla adında bir fonksiyon tanımladık. Main fonksiyonumuzdan her selamla fonksiyonu çağrıldığında ekrana selam yazıyor. Burada dikkat edilmesi gereken husus selamla fonksiyonunun main fonksiyonundan önce yazılmış olmasıdır. Bu durum herzaman mümkün olmaz. Bazen bir foknsiyonun içinden bir başkası bir başkasının içinden bir başka fonksiyonu çağırmak gerekir. Böyle durumlarda fonksiyonları doğru sıralama ile tanımlamak mümkün olmayabilir. Bu durumlarda fonksiyon prototipi kullanılarak kodun içinde bu fonksiyonun yer alacağı derleyiciye bildirilir.

Örnek:
C:
#include <stdio.h>

void selamla(); // fonksiyon prototipi...

int main()  // Main/ana fonksiyon...
{
    selamla();
    selamla();
}

void selamla() // fonksiyon...
{
    printf("Selam \n");
}

Yeri gelmişken static değişken kullanımını görelim. Local bir değişken olmasına rağmen son değerini koruyan bir değişken türü...
C:
#include <stdio.h>

void selamla(); // fonksiyon prototipi...

int main()  // Main/ana fonksiyon...
{
    selamla();
    selamla();
    selamla();
}

void selamla() // fonksiyon...
{
    static int x = 0;
    x++;
    printf("%d. Selam \n",x);

}
Program Çıktısı:
1. Selam
2. Selam
3. Selam
 
Son düzenleme:
2. Parametre alan değer döndürmeyen fonksiyon:

C:
#include <stdio.h>

void selamla(char *isim, int yas); // fonksiyon prototipi...

int main()  // Main/ana fonksiyon...
{
    selamla("Ahmet",25);
    selamla("Mehmet",28);
}

void selamla(char *isim, int yas) // fonksiyon...
{
    printf("Selam %s senin yasin tahminimce %d olmalı... \n",isim,yas);
}
Program Çıktısı:
Selam Ahmet senin yasin tahminimce 25 olmalı...
Selam Mehmet senin yasin tahminimce 28 olmalı...
 
3. Değer döndüren fonksiyonlar :

Fonksiyonların aslında iki temel olayı vardır. birincisi değer alma, ikincisi değer döndürme. Fonksiyonun çağırıldığı yerden nasıl değer aldığını gördük.
Şimdi nasıl değer döndürdüğümüze bakalım.

Fonksiyon tanımlanırken döndürülecek değer türünde tanımlanır.

int hesapla() / float sicaklik olc() / char *kelime () gibi...

Parametre olarak aldığı sayılarda işlem yapan fonksiyon;
C:
#include <stdio.h>

int topla(int a,int b); // fonksiyon prototipi...

int main()  // Main/ana fonksiyon...

{

    printf("Sayıların Toplamı : %d \n", topla(2,3));

}

int topla(int a,int b) // fonksiyon...

{

    return a+b;

}
Program Çıktısı:
Sayıların Toplamı : 5
 
Fonksiyon jargonu ile ilgili şu iki kelimeyi açıkladıktan sonra bir örnek daha yapalım.

Argüman : Fonksiyon çağrılır iken fonksiyona gönderilen değer/değerler.

Parametre :
Fonksiyon çalıştığında aldığı değer/değerler

Alında kabaca ikisi aynı şey denilebilir....


Örnek: Verilen karakter dizisindeki virgül karakterilerini boşluk karakteri ile değiştiren fonksiyon yazalım...

C:
#include <stdio.h>


void duzenle (char *metin)
{
    int k=0;
    while (metin[k]!='\0') k++; // dizi eleman saydık strlen de olur.
    for (int i=0;i<k;i++)
    {   
        if (metin[i]==',') metin[i]=' ';
    }
}

int main()
{
    char yazi_1[]="ben,sen,o,biz,siz,onlar";

    duzenle(yazi_1);
 
    printf("%s \n",yazi_1);

}
Program Çıktısı:
ben sen o biz siz onlar

fonksiyonumuza karakter diziminizin adresini argüman olarak geçirip doğrudan rem üzerinde işlem yaptığımız için geriye bir değer döndürmeye gerek kalmadı...


string döndüren fonksiyon örneği :
C:
#include <stdio.h>

const char *metin ()
{
    return "bıldirki hurmalar...";
}

int main()
{
    printf("%s",metin());
}
Program Çıktısı:
bıldirki hurmalar...
 
Main Fonksiyonu nun aldığı parametreler :

Program komnut satırından çağrılır iken program ismi ile birlikte verilen argumanlar main fonksiyonuna iletilir.
int argc iletilen argüman sayısını verir.
char *argv[] karakter pointer dizisidir.

C:
int main (int argc, char* argv[])
{
    if (argc > 1) // argüman var ise
       {
        ...=argv[1]; // argüman ile birşeyler yap
    }
}
 
Template :

C:
#include <stdio.h>

template <typename Tipitip>
Tipitip buyukolaniAL(Tipitip x, Tipitip y)
{
    if (x>y)
        return x;
    else
        return y;
}

int main()
{
    printf("%d \n", buyukolaniAL<int>(3, 7));
    printf("%c \n", buyukolaniAL<char>('a','b'));

  return 0;
}

büyük olanı tespit eden fonksiyon tür tanımınından bağımsız çalışır...


 
Aşırı Yüklenmiş Fonksiyonlar (Fonction overloading):

Fonksiyonların aldığı parametrelerin farklı olması kaydı ile aynı isimde birden çok fonksiyon tanımlanması durumudur.

C:
#include "stdio.h"

void bilgi_goster(char* ad)
{
    printf("%s \n",ad);
}

void bilgi_goster(char* ad, char* soyad)
{
    printf("%s %s \n",ad,soyad);
}

int main()
{
char *ad    = "Halil";
char *soyad = "Dagli";

bilgi_goster(ad);
bilgi_goster(ad,soyad);
}

Program Çıktısı:
Halil
Halil Dagli

örnekteki bilgi_goster fonksiyonu çağırıldığı zaman argüman-parametre eşleşmesine bakılarak aynı isimli fonksiyonlardan uygun olan fonksiyon çalışır.


Fonksiyonlarda varsayılan değerler :

konunun aşırı yüklenmiş fonksiyonla alakası olmamasına rağmen fonksiyon çağırmada kısmında benzerlik olması nedeni ile bu konudan burada bahsetmek istedim. Bir fonksiyon tanımlanırken parametrelerin varsayılan değerleri belirlenebilir. Fonksiyon çağrısında argüman kullanılmına göre varsayılan değerler kullanılır veya kullanılmaz...


C:
#include <stdio.h>

void topla (int a=5, int b=10, int c=15)
{
    printf("%d \n",a+b+c);
}

int main()
{
    topla();  // varsayılan değerler çalışır.
    topla(10); // a 10 değerini alır. (b ve c varsayılan değerleri alır.)
    topla(10,15); //a 10 , b 15 değerini alır.
    topla(1,2,3); // varsayışan değerler kullanılmaz.
}
Program Çıktısı:
30
35
40
6

Bir parametre değişkeni için varsayılan bir değer belirlenmişse, bu parametre değişkeninin daha sağında bulunan parametre değişkenlerinin hepsi varsayılan değerler almak zorundadır:

C:
void func(int x = 10, int y); //geçersiz!
void foo(int x, int y = 20); //Geçerli

Kaynak : https://necatiergin2019.medium.com/...argüman-alması-default-arguments-1be6aec48f03
 
Son düzenleme:
Fonksiyonlara Değer Atama (Call by value & Call by referance):

Aslında neden pointer kullanmak mantıklı veya değil konusu ile doğrudan ilişkili bir konuya geldik. Önce "call by value" örneğimizi inceleyelim;

C++:
#include "stdio.h"


void katla(int a)
{
    printf("a nın ilk değeri : %d \n",a);
    a=a*2;
    printf("a nın son değeri : %d \n",a);
}

int main()
{
    int x=2;

    katla(x),

    printf("x in    değeri : %d \n",x);
}
Program Çıktısı:
a nın ilk değeri : 2
a nın son değeri : 4
x in    değeri : 2

Aldığı değeri iki ile çarpıp çıktı veren "katla" adlı bir fonksiyonumuz var. ilk başta int x=2; tanımlamasını yapıyoruz. x hafızada 4byte (int kadar) yer kapılıyor. Sonra bu değeri fonksiyonumuza a değişkeni olarak gönderiyoruz. x in 2 değeri a nın değerine kopyalanmış oluyor. Yani hafızada a için bir 4byte daha yer kaplamış oluyoruz. Sonrasında işlemi a üzerinde yaptığımız için x bu durumlardan etkilenmiyor.

şimdi aynı işlemi "call by referance" yöntemi ile yapalım;

C:
#include "stdio.h"


void katla(int *a) // aldığımız parametre bir adres...
{
    printf("a nın ilk değeri : %d \n",*a);
    // adresteki değer üzerinde işlem için *a kullanıyoruz.
    *a=*a * 2;
    printf("a nın son değeri : %d \n",*a);
}

int main()
{
    int x=2;

    katla(&x); //  x in değerini değil adresini gönder.

    printf("x in    değeri : %d \n",x);
}
Program Çıktısı:
a nın ilk değeri : 2
a nın son değeri : 4
x in    değeri : 4

Bu örnekte katla fonksiyonunun aldığı parametre için *a kullandık. Yani sen bir adres değeri alacaksın dedik. ve main fonksiyonunda x in adresini (referance) katla fonksiyonuna gönderdik. Sonra fonksiyon içinden x in adresine ulaşarak doğrudan x üzerinde işlem yaptık.

Bu yöntemin "call by value" yöntemine göre en önemli farkı fonksiyona gönderdiğimiz değerin kopyasını oluşturmadık. Dolayısı ile hafızayı daha az meşgul etmiş olduk. Burada x değerimiz sadece 4 byte olduğu için önemsiz gibi görünebilir ancak gönderilen değerin yüzlerce byte olduğu durumlarda oldukça önem kazanın bir konu. Ayrıca bu yöntem tüm veri tipleri için kullanılabilir.

Özetleyelim;

call by value : değeri kopyalayarak kopyalanmış değer üzerinde işlem yapıyoruz. Orijinal değer durumdan etkilemiyor. geriye değer döndürmek istersek sadece 1 adet (return) değerini döndürebiliyoruz. kopya değerler için fazladan hafıza alanı kullanıyoruz.

call by referance : pointer adresleri marifeti ile orijinal değerler üzerinde işlem yapıyoruz. Pointerların harcadığı alan dışında ek hafıza kullanımı yok. Yine pointerlar sayesinde fonksiyonlardan çoklu değer döndürmek mümkün...
 
Fonksiyon Pointer ı :

Fonksiyonlarda hafızada bir adrese sahiptirler ve bu adres üzerinden fonksiyonlara erişmek mümkündür.

Bir fonksiyonun pointer adresini saklayacak pionter değişkenini tanımlamak için fonksiyon yapısına uygun bir söz dizimi kullanılmalıdır.

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

f1 fonksiyonun adresini bir pointerda saklamak istiyorsak, f1 fonksiyonun dönüştipi ve aldığı parametre tiplerine uygun bir söz dizimi ile pointer değişkenini tanımlamamız gerekmektedir. eğer f1 fonksiyonun adresini saklamak için tanımlayacağımız pointer değişkenin isminin f1_p olmasını istiyorsak söz dizimi şöyle olmalıdır;
C:
int (*f1_p)(int,int);

Hemen başka bir örnek ile pekiştirelim. Fonksiyonumuz şöyle ise;
C:
void harf (char a,int b)
{
  ...
  ...
}
fonksiyon pointerımız şu şekilde tanımlanmalıdır;
C:
void (*harf_p)(char,int);

harf fonksiyonumuzun adresini harf_p değişkenine aktarmak için ise;
C:
harf_p = &harf;
şeklinde atama yapılır. Dizilerde dizinin ilk elemanı ile pointer adresi aynı değeri ifade ediyordu. Bu durum fonksiyonlarda da mevcuttur. Fonksiyonun adı bir zaten bir pointer adresini ifade ettiğinden atama sırasında "&" karakteri kullanılmayabilir.
C:
harf_p = harf;
Bu şekilde yapılan atamada da harf_p aynı adres değerini alır.

Basit kullanım örneği ;
C:
#include "stdio.h"

int topla (int a ,int b)
{
  return a+b;
}

int main()
{
// topla fonksiyonun için bir pointer değişkeni tanımlayalım.
int (*topla_p)(int,int);

// topla fonksiyonun adresini pointer değişkenine aktaralım.
topla_p = topla;

// adres üzerinden fonksiyonu çağıralım.
printf("Sonuç : %d",topla_p(2,3));
}

Typedef yardımı ile fonksiyon pointer ı tanımlama:

Birden çok yerde fonksiyon pointer ı tanımlanacak kullanılacak ise anlaşılırlık açısından typedef ile fonksiyon pointeri tanımlamak faydalı olabilir. yukarıdaki örnekte toplama fonksiyonu için pointer i typedef ile şu şekilde tanımlayabiliriz.
C:
// topla fonksiyonun için bir pointer değişkeni için fp türünü tanımlayalım.
typedef int (*fp)(int,int);
// fp türünden topla_p adında fonksiyon pointerımızı oluşturalım.
fp topla_p;

Örnek kullanım;
C:
#include "stdio.h"

int topla (int a ,int b)
{
  return a+b;
}

int carp (int a ,int b)
{
  return a*b;
}

int main()
{
// topla fonksiyonun için bir pointer değişkeni için fp türünü tanımlayalım.
typedef int (*fp)(int,int);
// fp türünden islem adında fonksiyon pointerımızı oluşturalım.
fp islem;

int secim=0;
printf("Çarpma için 1, toplama için 2 giriniz: \n");
scanf("%d",&secim);

if (secim==1) islem=carp; // carpma fonksiyonun adresini al.
if (secim==2) islem=topla; // toplama fonksiyonun adresini al.

// adres üzerinden fonksiyonu çağıralım.
printf("Sonuç : %d",islem(2,3));
}


Fonksiyon pointerı ile fonksiyon çağırma :

C:
int main()
{
int (*islem)(int,int);
 
islem=topla; // toplama fonksiyonun adresini al.

printf("Sonuç : %d",islem(2,3));
}

islem(2,3) yazarak adres üzerinden fonksiyonu doğrudan çağırabiliyoruz. islem ifadesi bir fonksiyon pointerı olmasına rağmen kodun tamamı incelenmeden bakıldığında normal bir fonksiyon gibi görünmektedir. islem in bir pointer olduğu vurgulanmak istenir ise aşağıdaki yazım şekli kullanılabilir.
C:
printf("Sonuç : %d",(*islem)(2,3));


Belki de en başta söylenmesi gerekeni söylemenin zamanı geldi. Ne işe yarar bu fonksiyon pointer ı..? Bir fonksiyona başka bir fonksiyon parametre olarak gönderilmek istendiğinde fonksiyon pointer ı kullanılır.

şöyle bir sorumuz olsun;

g(x) = x^2 ,
f(x) = x + g(x) ise,
f(5) = ?

Nasıl kodlayacağımızı görelim;
C:
#include <stdio.h>

// fonksiyon adresi ve int parametresi al...
int f(int (*g_p)(int), int x)
{
    // adresini bildiğimiz g fonksiyonuna x gönder sonucu x e topla ve geri döndür.
    return x + g_p(x);

}

// sadece int parametresi al...
int g(int x)
{
    return x * x; //x²
}

int main()
{
    // g fonksiyonunun adresini al.
    int (*gp)(int);
    gp=g;

    int sonuc = f(gp,5);
    printf("Sonuc : %d",sonuc);
}
Program Çıktısı:
Sonuc : 30



aynı örneği typedef kullanarak yazalım;
C:
#include <stdio.h>

// fonksiyon pointer için bir typedef tanımı yapalım.
typedef int (*funcp)(int);

// f parametre olarak fonksiyon pointerı ve int alıyor.
int f(funcp gp, int x)
{
    // adresini bildiğimiz g fonksiyonuna x gönder sonucu x e topla ve geri döndür.
    return x + gp(x);
}

int g(int x)
{
    return x * x; //x²
}

int main()
{
    // fonksiyon adı aynı zamanda adres olduğu için ayrıca adresini almadan doğrudan kullanıyorum.
    int sonuc = f(g,5);
    printf("Sonuc : %d",sonuc);
}

Kaynaklar :

 
Son düzenleme:
Nesne Yönelimli Programla (object oriented programming) :

Bu konu c++ konusu olmakla beraber c kütüphane/kullanım olarak c++ 'ın alt kümesi olduğundan c mümkün olduğunca kütüphanelerini kullanmaya devam ederek bu konuyu aktarmaya çalışacağım. Bununla beraber OOP nin temeli sınıflar üzerine kurulu ve sınıf tanımla yeteneği c++ tarafından desteklenen bir özellik. Yani sadece bir C derleyici ile sınıf yazmak (şimdilik) mümkün değil gibi... Neyse daha fazla kafa karıştırmadan konuya dalalım...

Elimizde bir plan/şema var ve bu planın kendisi sınıfı (class) oluşturuyor. Bu plandan faydalanarak ürettiklerimiz ise nesnelerimiz olacak. Önce planla sonra üret mantığı... OOP nin temeli en basit ifade ile sanırım bu şekilde. Bunun dışında OOP nedir hakkında bir sürü makale var. Bunları incelemenizi tavsiye ederim. Ben OOP ne olduğundan çok nasıl oluşturur nasıl kullanırız kısmını anlatmaya çalışacağım.

Struct (yapı) konusunu hatırlayalım; struck tanımlayarak birden çok aynı veya farklı değişken türünü bir arada tutan bir veri türü tanımlıyorduk. Class tanımlayınca da aynı şeyi yapıyoruz ancak değişken tanımlarına ek olarak class içinde dahili fonksiyonlarda tanımlayabiliyoruz. Struct kullanarak tanımadığımız veri yapılarına sadece değer atayabilirken, class kullanarak bu verilere işlevde kazandırmış oluyoruz. Ve artık hem değerlere hem de işlevlere sahip verilerimize nesne (object) diyoruz.

Hemen basit bir sınıf tanımlayalım;
C++:
#include <stdio.h>

class personel
{
  public:
    int yas;
    int per_no;


};  // noktalı virgüle tikkat...

int main()
{
    personel ahmet;
    personel mehmet;
    ahmet.yas=25;
    ahmet.per_no=1345;
    mehmet.yas=28;
    mehmet.per_no=1346;

    printf("%d \n", ahmet.per_no);

   
}

personel adında bir sınıf tanımladık. Sınıf içerisinde yas ve per_no değişkenlerini oluşturduk. Sonrasında ise bu sınıftan ahmet ve mehmet i oluşturduk. ahmet ve mehmet sınıftan üretilen nesneler oldu ve bu nesnelerin yas ve per_no adı aıltında iki adet değişkeni oldu. Buraya kadar herşey struct kullanımı ile aynı şekilde ilerledi... Struct tan farklı olarak sadece public: ifadesini kullandık ki bu konuya daha sonra geleceğiz.

Hadi birde sınıfımıza bir fonksiyon ekleyelim;
C++:
#include <stdio.h>

class personel
{
  public:
    int yas;
    int per_no;

    void bilgi_goster()
    {
        printf("%d nolu personelin yası : %d 'dir \n",per_no,yas);
    }

};  // noktalı virgüle tikkat...

int main()
{
    personel ahmet;
    personel mehmet;
    ahmet.yas=25;
    ahmet.per_no=1345;
    mehmet.yas=28;
    mehmet.per_no=1346;

    mehmet.bilgi_goster();
    ahmet.bilgi_goster();  
}
Program Çıktısı:
1346 nolu personelin yası : 28 'dir
1345 nolu personelin yası : 25 'dir

Sınıf içerisinde tanımladığımız değişkenlere sınıfın niteliği (attribute) denilirken sınıf içinde tanımlanan fonksiyonlara sınıfın methot/ları denilmektedir. Nitelik ve methotların tamamı ise sınıfın üyelerini (members) oluşturur.

Class içerisinde tanımlanan fonksiyonların prototip olarak tanımlanıp daha sonra fonksiyon içeriği class bloğunun dışında yazılabilir. Bu yöntem ile yukarıdaki örnekte bulunan bilgi göster fonksiyonu aşağıdaki örnekte prototip olarak tanımlanıp daha sonra fonksiyon içeriği oluşturulmuştur.
C++:
class personel
{
  public:
    int yas;
    int per_no;

    // methot (fonksiyon) prototip olarak tanımlandı.
    void bilgi_goster();
};

// daha önce prototip olan fonksiyonun içeriği class bloğu dışında oluşturuluyor.
// fonksiyonun personel sınıfına ait olduğu belirmek için "::" bağlacı ile
// personel::bilgi_goster() ifadesi ile fonksiyon oluşturulur.
void personel::bilgi_goster()
{
    printf("%d nolu personelin yası : %d 'dir \n",per_no,yas);
}
 
Erişim denetimi :

Yukarıdaki örneklerde struct tan farklı olarak karşıma public: ifadesi çıkmıştı. Şimdi bu ifadenin ne olduğuna ve başka hangi ifadelerin kullanıldığına bakalım.

Bir class ın üyeleri (members) public, protected veya private denetleyicileri altında yer alır. Bu denetleyiciler sayesinde class ı olurşturan blok dışından üyelere erişim ihtiyaca göre kısıtlanır.
C++:
class personel
{
 
  public:
    int per_no;
    ...
  protected:
    int maas:
    ...
  private:
    int yas;
    ...
};

1631559164316.png

public denetleyicisi altında yer alan sınıf üyelerine dışarıdan doğrudan erişmek mümkünken private altındaki üyelere dışarıdan erişim sağlanmaz. Sınıf içerisinde eğer bir erişim belirteci tanımlanmaz ise varsayılan olarak erişim türü private olur. Şimdilik public ve private erişim denetleyicilerini kullanacağız...

Kapsülleme (Encapsulation) :


Kapsülleme sınıf içerisine dışarıdan erişimi kısıtlayarak veri güvenliği sağlayan bir yöntemdir. Örneği personel sınıfında personelin yaşı asla negatif değer almamalıdır. Personelin yaşını tutan değişken public denetleyicisi altında olur ise class dışından bu değişkene istenilen değer atanabilir. Bu durum programın hatalı çalışmasına neden olacaktır. Kapsülleme kullanılarak bu durumun önüne geçilir. Hemen örnekle konuyu anlayalım.

C++:
#include "stdio.h"

class personel
{
    private:
        // isim private dışardan erişelemez.
        const char* name;
        int age;
  
    public:
        // isime fonksiyonlar üzerinden dışarıdan dolaylı erişim.
        void setName(const char* isim)
        {
            name=isim;
        }

        const char* getName()
        {
            return name;
        }

        void setAge(int yas)
        {
            if (yas<0)
            {
                printf("Hata..! Yas 0 dan küçük olmaz... \n");
                age=1;
                return;
            }
            age=yas;
        }
        int getAge()
        {
            return age;
        }
};



int main()
{

    personel per1;

    per1.setName("Mehmet ");

    printf("%s \n",per1.getName());

    per1.setAge(-3);

    printf("Personel yasi : %d \n",per1.getAge());

}
Program Çıktısı:
Mehmet
Hata..! Yas 0 dan küçük olmaz...
Personel yasi : 1

private bir değer olan yaş niteliğine public olan setAge fonkisyonu üzerinden dolaylı erişim sağladık. Bu esnada set etmek istediğimiz değerin uygunluk kontrolünü sağladık. Örnekte yaş -3 girildiğinden bir hata çıktısı verdik... dışarıdan değer atamak için set, değer okumak için get önekli fonksiyonlar yaygın olarak kullanılır...
 
Son düzenleme:
Pointer ile Class kullanımı :

yukarıdaki örnekte personel sınıfından bir nesne üretmek şu ifadeyi kullanmıştık;
C++:
personel per1;

Şu ana kadar öğrendiğimiz pointer mantığı ile;
C++:
    personel *per1; // personel türünden bir pointer oluştur.
    personel adam;    // personel türünden nesne oluştur.
    per1=&adam;        // nesnenin adresini pointer değişkenine aktar.

ifadesi kullanılarak pointer değişkeni üzerinden nesnenin öğelerine erişmek mümkündür. Struct pointerı kullanımında olduğu gibi öğelere erişmek için "." yerine "->" ifadesi kullanılır. (örneğin: per1->getName() )

Alternatif olarak hafızanın dinamik bölümünü kullanarakta bir pointer tanımlaması yapabiliriz. Bu durumda;
C++:
// per1 adında bir pointer oluştur ve hafızada personel sınıfından
// nesne üretecek şekilde yer ayır...

personel *per1= new personel();
ifadesi ile pointer kullanılarak bir nesne oluşturulur. Dinamik hafıza konusu ilerde ayrıca ele alınacaktır...

Şimdilik bir önceki mesajdaki ile aynı işleve sahip aşağıdaki örneği inceleyelim;

C++:
#include "stdio.h"

class personel
{
    private:
        // isim private dışardan erişelemez.
        const char* name;
        int age;
    
    public:
        // isime fonksiyonlar üzerinden dışarıdan dolaylı erişim.
        void setName(const char* isim)
        {
            name=isim;
        }

        const char* getName()
        {
            return name;
        }

        void setAge(int yas)
        {
            if (yas<0)
            {
                printf("Hata..! Yas 0 dan küçük olmaz... \n");
                age=1;
                return;
            }
            age=yas;
        }
        int getAge()
        {
            return age;
        }
};



int main()
{
    // per1 adında bir pointer oluştur ve hafızada personel sınıfından nesne üretecek şekilde yer ayır...
    personel *per1= new personel();

    per1->setName("Hasan");

    printf("Personel : %s",per1->getName());

}
 
Dinamik Hafıza Kullanımı (Stack & Heap):

Klasik yöntemler ile tanımlanan tüm veri türleri (değişken, dizi, nesne vs) için ne kadar hafıza alanına ihtiyaç olduğu sabit olarak bellidir. Bu durumda dinamik hafıza kullanmayız ve bu verilerin saklandığı hafıza bölümüne "stack" denir. Örneğin int a=5; dediğimizde a nın saklandığı yer stack bölümüdür. Hafıza alanı ihtiyacı derleme sırasında kesin olarak belli olduğundan programımız çalıştığı anda gerekli alan hafızada tahsis edilir ve bu alan program sonlanıncaya kadar tahsisli olarak kalır.

Bazı durumlarda programında çalışma zamanında ortaya çıkacak yeni veya değişken hafıza alanları tahsisi gerekebilir. Bu konu dinamik hafıza kullanımı olarak adlandırılır ve hafızanın "heap" bölümünü kullanır. C de malloc fonksiyonu ile C++ da new komutu ile çalışma zamanında yeni hafıza alanları tahsisi sağlanır. Dinamik hafıza kullanımının sağladığı avantajların yanında dezavantaj olarak tahsis edilen alanlara ihtiyaç ortadan kalkınca programcı tarafında boşa çıkartılması gerekir. Yine bu işlem için C de free fonksyionu , C++ da delete komutu kullanılır.

Malloc kullanımı daha önce gösterilmiştir;

New kullanarak da basit bir örnek yazalım;
C++:
int main()
{
    int e = 0;
    printf ("Dizi icin eleman sayisini giriniz :");
    scanf ("%d",&e);
 
    int *dizi;
    dizi = new int[e]; // veya int(e);
 
        printf ("Dizi icin eleman sayisi %d  \n",e);
 

    delete dizi;

}

alternatif olarak
C++:
    delete[] dizi;
aynı işleve sahiptir.

Aşağıdaki kodda personel sınıfından üretilen nesne hafızanın heap bölümünde oluşturulur. Sınıflar bir veri türü olduğundan ayrılması gereken alan sınıf "personel()" kadardır.

C++:
// per1 adında bir pointer oluştur ve hafızada personel sınıfından
// nesne üretecek şekilde yer ayır...

personel *per1= new personel();

Notlar:
- dinamik hafıza (heap) kullanımında pointer kullanımı zorunludur. Komutlar sadece pointer türünü destekler.
- C de bulunan realloc fonksiyonunun C++ da doğrudan alternatif bir karşılığı yoktur.
- Çalışma zamanında tahsis edilen alana ihtiyaç ortadan kalkınca alan tahsisi sonlandırılmalıdır.
- malloc ile ayrılmış alan delete ile silinebilir...
- new ve delete komutları (sınıf/nesne kullanımında) ayrıca yapıcı ve yıkıcı fonksiyonları tetikler.
- heap bölümü stack bölümünden görece daha yavaş çalışır. (çalışma zamanında yer tahsisi, ilgili yere ulaşma vs zaman alır. stack da her şey önceden hazırdır)
 
Son düzenleme:

Çevrimiçi personel

Forum istatistikleri

Konular
5,791
Mesajlar
99,037
Üyeler
2,464
Son üye
s4met

Son kaynaklar

Son profil mesajları

cemalettin keçeci wrote on HaydarBaris's profile.
barış kardeşim bende bu sene akıllı denizaltı projesine girdim ve sensörleri arastırıyorum tam olarak hangi sensör ve markaları kullandınız yardımcı olabilir misin?
m.white wrote on Altair's profile.
İyi akşamlar.Arabanız ne marka ve sorunu nedir.Ben araba tamircisi değilim ama tamirden anlarım.
* En mühim ve feyizli vazifelerimiz millî eğitim işleridir. Millî eğitim işlerinde mutlaka muzaffer olmak lâzımdır. Bir milletin hakikî kurtuluşu ancak bu suretle olur. (1922)
Kesici/Spindle hızı hesaplamak için SpreadSheet UDF'leri kullanın, hesap makinesi çok eski kalan bir yöntem :)
Dr. Bülent Başaran,
Elektrik ve Elektronik Mühendisi
Yonga Tasarım Özdevinimcisi
Üç güzel "çocuk" babası
Ortahisar/Ürgüp/Konya/Ankara/Pittsburgh/San Francisco/Atlanta/Alaçatı/Taşucu...

Back
Top