C ile oluşum (composition)

Yazan: Şadi Evren ŞEKER

Oluşum Composition, bir şeyin ufak parçaların bir araya getirilmesi ile oluşturulması anlamına gelmektedir. Buna göre örneğin bir araba, motor, koltuklar, tekerlekler vs. gibi parçalardan oluşmaktadır.

Programlama dillerinde modellenmek istenen varlık alt varlıklara bölünebiliyorsa, composition kullanılarak modellenebilir. Bu yaklaşım nesne yönelimli programlama dillerinin temelini oluşturmaktadır. Ancak nesne yönelimli olmayan C gibi dillerde de sınırlı da olsa destek bulunmaktadır.

Örneğin öğrenci veritabanı üzerinde çalışan bir C programı yazılmak istensin. Öğrenci tanımı muhtemelen insan (isim, soyisim, yaş vs.) ve ders (kredisi, dersin adı, bölümü vs) bilgilerini içeren bir tanım olacak. Bu durumda öğrenci ders ve insan bilgilerinden oluşacaktır.

C dilinde ilkel veri yapılarının (primitive data structures) üzerine kurulan ve kullanıcı tarafından tanımlanabilen yapılar için struct kelimesi kullanılır. Programcı tanımladığı yapıları (struct) daha ileride yeni yapılar (struct) içerisinde de ilkel veri yapısı gibi kullanabilir.

Aşağıda örnek yapı (struct) kullanımı verilmiştir:

typedef struct {

int yas;

char *isim;

enum { bay, bayan } cinsiyet;

} Insan;

Yukarıdaki örnekte bir insan tipi tanımlanmış ve bu tipin özellikleri olarak , yaş, isim ve cinsiyet belirtilmiştir. Artık insan yapısında olan değişkenlerin bu bilgilerine erişilip istenilen veriler atanabilir.

Örnek:


Insan ali;
ali.yas=18;

ali.isim=”Ali Yildiz”;

ali.cinsiyet=bay;

Yukarıda tanımlanan yapı için aynı zamanda pointer (gösterici) kullanmak da mümkündür. Bu durumda:

Insan *ali;

ali->yas=18;

ali->isim=”Ali Yildiz”;

ali->cinsiyet=bay;

şeklinde pointer erişimi yapmak gerekir. Bir yapı, fonksiyonlara parametre olarak geçirilirken de bu özelliğinden faydalanılır.

void yaslandir(Insan *yaslanacakInsan)

{

yaslanacakInsan->yas++;

}

Yukarıda referans ile çağırma (call by reference) kulanıldığı için ayrıca yapının dönmesi gerekememiştir.

Bir yapının içerisinde farklı yapılar da kullanılabilir. Örneğin yazının başlarında geçen öğrencinin insan ve ders bilgileri olması durumunu ele alalım ve eksik olan ders yapısını tanımlayalım:

typedef struct {

int kredi;

char *isim;

} Ders;

Şimdi yukarıdaki insan ve ders yapılarını birleştirerek öğrenci tanımlayabiliriz:


typedef struct {

Ders *alinanDers;

Insan *kisiselBilgi;

Char *bolumu;

} Ogrenci;

Yukarıdaki yapıda öğrenci tanımlanmış ve bir öğrencinin ders tipinden alinanders’i insan tipinden kisiselbilgileri ve ilkel tipten (char) bolumu tanımlanmıştır. Artık aşağıdaki erişimler mümkündür:

Ogrenci ali;
ali.alinanDers->kredi=3;

ali.alinanDers->isim=”C ile programlama”;

ali.kisiselBilgi->yas=18;

ali.bolumu=”Bilgisayar Muhendisligi”;

Yukarıdaki örnekte olduğu üzere bir yapıdan başka yapıların kullanılmasına nesne yönelimli programlamada bütünleştirme (aggregation) adı da verilmektedir.

Örnek bir linked list yapısı:

typedef struct{

int value;

node *next;

}node;

Örnek bir ağaç yapısı:

teypedef struct{

int value;

node *left;

node *right;

}node;

Bir linked list elemanlarına veri koyan ve verileri ekrana basan kod örneği:

#include <stdio.h>
#include <conio.h>

struct lnode{

int value;

lnode *next;

};

typedef lnode node;

int main(){

node root;

node *a;

a=&root;

for(int i = 0;i

a->value=i;

a->next= (node* ) malloc(sizeof(node));

a=a->next;

}

// ekrana basan kod:

a=&root;

for(int i = 0 ;i

printf(“%dn”,a->value);

a=a->next;

}

}

Nesne Yönelimli Programlama Dillerinde Oluşum (Composition)

Gelen talepler doğrultusunda, yukarıda anlatılan ve C dili gibi nesne yönelimli olmayan diller için anlatılan oluşum konusuna ilave olarak, nesne yönelimli diller için durumu anlatmaya karar verdim. Kısaca nesne yönelimli dillerde, oluşum kavramı sınıflar (class) tarafından çözülmektedir. Her sınıf aslında tanım itibariyle bir oluşumu ifade eder.

Örneğin JAVA dili için yukarıdakine benzer bir tanım yaptığımızı düşünelim:

class insan{
   String isim;
   int yaş;
   int kilo;
}

Yukarıdaki tanım itibariyle bir insanın ismi, yaşı ve kilosu bulunmaktadır. (müsvette olarak kod yazdığım için Türkçe karakter barındıran değişken tanımı kullandım ancak kodlamada bu konulara dikkat edilmesi gerekir)

Kabaca insan kavramının, 3 farklı veriden oluştuğunu söyleyebiliriz.

Yukarıdaki sınıftan bir nesne (object) tanımlamak ise aşağıdaki şekilde mümkündür:

insan ali = new insan ();

bu tanıma göre ali isimli bir nesne, yukarıdaki sınıf tanımından türetilmiş olup ali isimli nesnenin üyelerine (members) erişmek mümkündür:

ali.isim = "Ali Demir";

ali.yaş = 20;

ali.kilo=80;

Yukarıdaki kodlar, nesnemizin üyelerine (members) değer atamada kullanılan örnek satırlardır. Yukarıdaki bu satırlar daha dikkatli bir kodlamada kapsülleme (encapsulation) konusunda anlatıldığı üzere getter/setter fonksiyonları ile erişim sağlayabilirler.

Gelelim oluşum özelliği bulunan bu sınıflardan türetilen nesnelerin birbirine bağlanması. Veri yapıları konusunda en önemli kodlama ihtiyaçlarından birisi de bu veri ünitelerinin birbirine göstericiler aracılığı ile bağlanmasıdır. Klasik olarak C dilinde bulunan göstericiler (pointers) bu iş için biçilmiş kaftan olup yazının daha önceki bölümlerinde örnekler ile izah edilmişti. Sorumuz, nesne yönelimli dillerde bağlantının nasıl kurulacağıdır ve aşağıdaki örnek soruya cevap mahiyetindedir:

class insan{
   String isim;
   int yaş;
   int kilo;
   insan müdür; // bu satır aslında bir gösterici gibi düşünülebilir
}

Yukarıdaki kodda görüldüğü üzere insan sınıfının tanımında, yine insan sınıfından bir nesne atıfı (object referrer) bulunmaktadır. Nesne atıfları, nesne yönelimli dillerde kullanılan göstericilerdir (pointers) ve farklı bir nesneye atıfta bulunmak için kullanılır.

Örneğin yukarıdaki sınıfı aşağıdaki şekilde kullanabiliriz:

insan veli = new insan();

veli.müdür = ali;

Daha önceden tanımladığımız ali nesnesini, veli nesnesinin müdürü olarak belirttik. Bunun anlamı, veli nesnesinden, ali nesnesine erişen bir gösterici (pointer) tanımlamış olmamızdır.

Gelelim klasik veri yapılarına. Yukarıdaki yaklaşımı biraz daha ilerletirsek, veri yapılarının en klasik şekillerini kodlamamız mümkündür. Örneğin elemanlarının her biri insan olan basit bir bağlı liste yapmak için  aşağıdaki kodu kullanabiliriz:

class insan{
   String isim;
   int yaş;
   int kilo;
   insan next;
}

Yukarıdaki kodda bulunan next isimli nesne atıfının (object referrer) amacı, bağlı listeler (linked list) konusunda anlatılan bağlantıyı kurmaktır. Örneğin aşağıdaki bağlı listeyi artık kurmamız mümkündür:

insan head = new insan();
head.yaş = 0;
insan iter=head;
for(int i = 1;i<10;i++){
   iter.next = new insan();
   iter = iter.next;
   iter.yaş=i;
}

Yukarıdaki kod parçasında yapılmak istenen işlem, 10 elamanlı bir bağlı liste oluşturmaktır. Listenin her elamanı insan sınıfından türetilmiş olup sadece yaş değerlerine döngüdeki i değeri (yani ilk elemana da elle 0 atıldığı düşünülürse) 0’dan 9’a kadar atanmıştır.

Diğer değerler örnekte kullanılmamıştır ancak dilenirse bu değerlere de atama yapılabilir.

Buraya kadar anlatılan liste yapısında dikkat edileceği üzere veri yapısının bağlantısı için kullanılan değişkenler, veri ünitesi ile (yani veri yapımızdaki anlamlı en küçük varlık) beraber kullanılmıştır. Diğer bir deyişle, verilerimiz olan yaş, isim, kilo gibi bilgiler ile, veri yapımızı oluşturmak için kullandığımız next atıfı beraber tek bir sınıfta bulunmaktadır. Bu yapı yukarıdaki örnekte gördüğümüz üzere çalışmaktadır ve kullanabiliriz. Ancak daha doğru bir tasarım, veri ünitelerimiz ile veri yapımızı birbirinden ayırmak olacaktır.

class insan{
    String isim;
    int yaş;
    int kilo;
}

class liste{
   insan veri;
   liste next;
}

Yukarıdaki yeni tasarımımızda bulunan iki sınıf, bu ayırım açısından ele alındığında daha sağlıklıdır. Aynı veri atan örneği aşağıdaki şekilde yeniden yazabiliriz:

liste head = new liste();
head.veri = new insan();
head.veri.yaş = 0;
liste iter=head;
for(int i = 1;i<10;i++){
   iter.next = new liste();
   iter = iter.next;
   iter.veri.yaş=i;
}

Görüldüğü üzere veri yapısı işlemleri doğrudan liste sınıfını (class) hedef alırken, verinin içeriğine ait işlemler, veri ile gösterilen insan sınıfını hedef almaktadır.

Ayrıca yukarıdaki kullanım, veri yapısı ile veriyi birbirinden soyutlamaya (abstraction) bir adım daha yakındır ve şablon (template) kullanılması mümkündür.

Yorumlar

  1. fatih kabakci

    hocam ben structures ile ilgili bir sorum olucak.

    struct ogrenci {

    char *isim;
    char *soyisim;
    int numara;

    };

    int main() {
    struct ogrenci lokal;

    lokal.isim = "fatih";
    lokal.soyisim = "kabakci";
    lokal.numara =80502031;

    printf("Adi:%sn",lokal.isim);
    printf("Soyadi:%sn",lokal.soyisim);
    printf("%d",lokal.numara);
    getch();
    }

    ben bu kodda typedef i kullanmadim ve bu sekildede yapilar konusundaki yaptigim alistirmalarda bu sekildeki varyasyonlarla yaziyorum. 'typedef' in kullanmakla kullanmamak arasinda bir fark varmidir?

  2. Şadi Evren ŞEKER Article Author

    typedef komutu, bir yapıdan yeni bir tip tanımlamaya yarar. Örneğin sizin verdiğiniz kodda ogrenci yapısında tanım yapmak için her seferinde "struct ogrenci" yazmak gerekir. Nitekim kodunuzda bir değişken tanımlarken
    struct ogrenci lokal;
    şeklinde yazmışsınız. Oysaki typedef komutu ile bu tanımlama tek kelimeye indirilebilir.
    Bir kereye mahsus
    typedef ogrenci ogr;
    yazılması yeterlidiri. Artık bu satırdan sonra her tanımlamada sadece ogr tipi kullanılabilir ve
    ogr lokal;
    şeklinde tanım yapmak mümkündür.
    Kısacası typedef size yeni bir tip tanımlamaktan fazla birşey sunmaz. Kodlamada kolaylık sağlar ve istenirse kullanılmayabilir.

    başarılar

  3. muhendis_adayi

    mrb hocam asagıdaki kodu yazdım fakat bircok yerde hata alıyorum ama sebebini anlayamadım sizin yukarda kullandığınız yapının aynısını kullanmaya calıstım

    #include "stdio.h"
    #include "stdlib.h"
    #include "string.h"
    struct romaRakami{
    char rakam;
    romaRakami *sonraki;
    };
    typedef romaRakami tip;

    void yazdir (tip *ptr)
    {
    while (ptr != NULL)
    {
    printf("%c",ptr -> rakam);
    ptr = ptr -> sonraki;
    }
    }

    int main()
    {
    tip *ilk,*ilk1,*ilk2,*ilk3,*son;
    ilk = (tip *) malloc(sizeof(tip));
    ilk1 = (tip *) malloc(sizeof(tip));
    ilk2 = (tip *) malloc(sizeof(tip));
    ilk3 = (tip *) malloc(sizeof(tip));
    son = (tip *) malloc(sizeof (tip));
    ilk->rakam = 'Y'; ilk->sonraki = ilk1;
    ilk1->rakam = 'A'; ilk1->sonraki = ilk2;
    ilk2->rakam = 'S'; ilk2->sonraki = ilk3;
    ilk3->rakam = 'I'; ilk3->sonraki = son;
    son->rakam = 'N'; son->sonraki = NULL;
    yazdir (ilk);
    }

  4. muhendis_adayi

    typedef struct _Nokta {
    int x;
    int y;
    } Nokta;
    a) Yukarıdaki Nokta yapı tanımlamasını baz alarak.
    /* makepoint: x ve y bilesenlerinden bir nokta olusturur */
    Nokta makepoint(int x, int y) {

    }
    /* addpoint: iki noktayı topla */
    Nokta addpoint(Nokta n1, Nokta n2) {

    }

    hocam bu sekilde tanımlanan nokta olstur ve nokta tanımlayı gerceklestirmek istiyorum ama nasıl yapacagım konusunda aklıma bişey gelmedi yardımcı olursanız sevinirim

  5. gokhan.taskin

    Hocam lutfen yardımcı olurmusunuz 2 gun sonra sınavım var calısma sorularını cozmeye calısıyorum ama bu soruda takıldım

  6. Şadi Evren ŞEKER Article Author
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    Nokta makepoint(int x, int y){
       return (Nokta) malloc(sizeof(Node));
    }
     
    Nokta addpoint(Nokta n1, Nokta n2){
      Nokta gecici = (Nokta) malloc (sizeof(Nokta));
      gecici.x = n1.x + n2.x;
      gecici.y = n1.y + n2.y;
      return gecici;
    }

    Tam olarak ne istendiğini yazmamışsınız, isimlerinden tahmin üzerine yukarıdaki şekilde yazdım ancak yine de bu tür sorularınızı dersinizi veren hocanıza sormanız daha yerinde olur çünkü aynı kelimeler ile ifade edilebilen çok sayıda farklı kod çok sayıda farklı anlamda yazılabilir.

    Başarılar

  7. muhendis_adayi

    hocam yapmak istediğimi size tam olarak anlatamadım ama yazdıgınız koddan ilham olarak satenizde yaptım

    #include "stdio.h"

    typedef struct _nokta{
    int x;
    int y;
    }nokta;

    nokta makepoint (int x,int y){
    nokta node;
    node.x = x;
    node.y = y;
    return node;
    }

    nokta addpoint (nokta n1,nokta n2){
    n1.x+=n2.x;
    n1.y+=n2.y;
    return n1;
    }

    int main (void)
    {
    int x,y;
    nokta a = makepoint(2,3);
    nokta b = makepoint(1,4);
    nokta c = addpoint (a,b);
    x = c.x;
    y = c.y;
    printf("%d %d",x,y);
    }

    yapmak istediğim buydu

    simdi bu nokta yapıyı kullanarak dikdortgen gerceklemem lazım hocam verdiği ifade
    aynen asagıdaki sekilde

    Nokta yapısından yararlanılarak Dikdortgen yapısını gerçekleyin.
    /* Dikdortgen: iki Noktal ı dikdortgen temsili */
    typedef struct _ Dikdortgen {
    ? nSU; /* sol-ust kose noktasi */
    ? nSA; /* sag-alt kose noktasi */
    } Dikdortgen;

    burada takıldım dikdortgen gerceklemek derken neyi kastetmiş anlayamadım

  8. Recep

    Arkadaşlar aşagıdaki soru için yardımlarınızı bekliyorum
    Üç tane parametre almayan içi boş fonksiyon yaz.

    void init(void);
    void read(void);
    void write(void);
    

    bir struct yarat ve bu struct üç tane fonksiyon pointer tutsun.

    typedef struct
    {
    void (*init)(void);
    void (*read)(void);
    void (*write)(void);
    } FunctionStruct
    

    Bu fonksiyon pointerlara yukarda yazdığın fonksiyonları ata.
    Bir tane de fonksiyon yaz. Fonksiyonun imzaı şu şekilde olsun.
    void funcitonCaller(char* structPointer, int functionIndex);
    Bu fonksiyon şu işi yapsın. Ben Function Struct türünden bir değişkeni buraya char pointer olarak geçireyim.
    Fonksiyona geçirdiğim functionIndex eğer sıfır ise FunctionStruct değişkenindeki init fonksiyonunu, bir ise
    read, iki ise write fonksiyonu çağırsın.
    Ama functionIndex’i bir if ya switch ile kullanıp manuel fonksiyonları çağırmıycaz.
    structpointer ve functionIndex değerlerinden fonksiyonun adresini hesaplayıp, fonksiyonu çağıracağız.
    Yani

    switch (funcitonIndex)
    {
    case 1:
    ((FunctionStruct*)structPointer)->init();
    break;
    case 2:
    ((FunctionStruct*)structPointer)->read();
    break;
    case 3:
    ((FunctionStruct*)structPointer)->write();
    break;
    }
    

    olarak değil!

    void (*functionPtr)(void);
    functionPtr = { structPtr ve functionIndex değerleri ile adresi bulup };
    functionPtr(); // Fonksiyonu Çağır.
    

    Şeklinde olmalı

  9. Recep

    Soru çözüldü belki birisini yardımı olur diye paylaşıyorum

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    #include <stdio.h>
    #include <conio.h>
     
    typedef struct
    {
    	void (*init)(void);
    	void (*read)(void);
    	void (*write)(void);
    } FunctionStruct;
     
    void init(void) { puts("init"); }
    void read(void) { puts("read"); }
    void write(void){ puts("write"); }
     
    void funcitonCaller(char* structPointer, int functionIndex)
    {
         void (*p)() = ( void (*)() ) ((int*)structPointer)[functionIndex];
         (p)();     
    }
     
    static FunctionStruct fs = { init, read, write };
     
    int main()
    {
        funcitonCaller((char*)&fs, 0);
        funcitonCaller((char*)&fs, 1);
        funcitonCaller((char*)&fs, 2);
        funcitonCaller((char*)&fs, 0);
        funcitonCaller((char*)&fs, 1);
        funcitonCaller((char*)&fs, 2);
        getch();
    }
  10. Ozan

    sayın hocam sımdı burada tabi siz soruyu anlatırken ilk soruyu (bu dosyaya veri ekleme ve ada ve ya baska bir krıtere gore yazdırırken) siz degisken olarak structa "xyz" adlı bir değişken verdiniz ve struct uyelerine bu yolla ulastımız.sorum su:burada xyz[10] adlı bir dizi struct ı kullansaydınız ne farkı olurdu.2.sorum yoksa orada xyz[10] yapmayıp bunu örneğin xyz.isim=(char*)malloc(sizeof(char)) ile aynı hesaba mı getırdınız?soruyu umarım anlamıs olursunuz.biraz anlatmak istedıımı karısık yazdım galıba.

  11. Şadi Evren ŞEKER Article Author

    Yapılarda (struct) dizi kullanımı ile bir üyenin dizi olması farklı durumlardır. Örneğin yukarıdaki yapıyı ele alalım:

    1
    2
    3
    4
    5
    
    typedef struct {
       Ders *alinanDers;
       Insan *kisiselBilgi;
       char *bolumu;
    } Ogrenci;

    Buradaki öğrenci yapısından bir öğrenci dizisi tanımlayalım, örneğin 10 öğrenci tutmak için:

    1
    
    Ogrenci ogr[10];

    Şimdi herhangi bir öğrencinin bölümünü yazmadan önce bu bölüm değişkeninin hafızada yer ayrılarak tutulması gerekir:

    1
    2
    
    ogr[3].bolumu = (char *)malloc(sizeof(char) * 10);
    strcp(ogr[3].bolum, "Bilgisayar");

    Bu şekilde içine değer konulabilir. Yani aslında ikişeyi ayırmak gerekir birisi yapının elemanı, diğeri yapının elemanının elemanı. Aşağıdaki kodu çalıştırıp deneyebilirsiniz:

    1
    
    printf("%c", ogr[3].bolumu[4]);

    Bu kod ekrana "g" harfini basar çünkü 3. öğrencinin bölümünün 4. elemanı g harfidir.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir


× sekiz = 8