Yeni Sitemize Yönlendiriliyorsunuz !

Join the forum, it's quick and easy

Yeni Sitemize Yönlendiriliyorsunuz !

Would you like to react to this message? Create an account in a few clicks or log in to continue.

    STRUCT YAPIMI

    TURQUD
    TURQUD


    Mesaj Sayısı : 66 Aldığı teşekkürler : 2419 Nereden : IMISLI Kayıt Tarihi : 10/02/11

    STRUCT YAPIMI  Empty STRUCT YAPIMI

    Mesaj tarafından TURQUD Perş. Mayıs 05, 2011 6:35 am

    1 . BÖLÜM : YAPILAR
    Yapılar (structures) da diziler gibi birden fazla nesneyi içlerinde tutabilen bir veri türürdür. Yapıların da elemanları bellekte ardışıl (contigious) bir biçimde bulunur. Fakat yapıların dizilerden temel farkı şudur: Diziler aynı türden nesneleri içinde tutabilirken, yapılar farklı türlerden nesneleri tutabilirler. Yapıların kullanılmasının ana nedeni budur. Çoğu zaman, türleri farklı bir takım nesneler, mantıksal olarak bir bütün oluşturabilirler. İsim, yaş, departman, ücret, öğrenim durumu gibi bilgileri farklı türden nesneler içinde saklayabiliriz ve bunların tamamı çalışan bir personele ait bilgiler olabilir. Bu gibi aynı konu ile ilgili farklı türden veriler yapılar içinde saklanır.

    Yapı Bildiriminin Genel Şekli
    [yapı ismi] {
    ;
    ;
    ;
    ...
    };

    Yukarıdaki gösterimde struct bir anahtar sözcüktür. Bildirimde mutlaka yer alması gerekmektedir. Yapı ismi (structure tag) C dilinin isimlendirme kurallarına uygun olarak seçilmiş bir isimdir.

    Örnek bildirimler:

    struct SAMPLE {
    int a;
    long b;
    char ch;
    };

    struct DATE {
    int day, month, year;
    };

    struct POINT {
    int x, y;
    };

    Yapı isimleri (structure tags) geleneksel olarak büyük harfle yazılır. Bu bir zorunluluk değildir.

    Yapı bildiriminin yapılması bellekte derleyici tarafından bir yer ayrılmasına neden olmaz. Yani bir tanımlama (definition) söz konusu değildir. Bu bildirimle (declaration) programcının yarattığı yeni bir veri türü hakkında derleyiciye bilgi verilmektedir.

    Yapı bildiriminin yapılmasından sonra artık bildirimi yapılmış yapı türünden nesneler tanımlanabilir. Yapı bilidiriminin yapılması ile yeni bir veri türü yaratılmıştır. Derleyici artık bu tür hakkında bilgi sahibi oldugundan, bu yeni veri türünden, nesneler tanımlanabilir. Yeni veri türünden nesnelerin tanımlanması durumunda artık derleyici bu nesneler için bellekte ne kadar bir alan ayırması gerektiği bilgisine sahip olacaktır.
    Yapı Türünden Nesne Tanımlaması
    ;

    Örnekler :

    struct DATE x; /* x DATE yapısı türünden bir nesnedir. */
    struct POINT p1, p1; /* p1 ve p2 POINT yapısı türünden nesnelerdir. */
    struct SAMPLE sample; /* sample SAMPLE yapı türünden bir nesnedir. */

    Yapı değişkenleri bileşik nesnelerdir. Yani parçalardan oluşurlar. Zaten yapı bildirimlerinin yapılmasının amacı da bu parçaların isimleri ve türleri hakkında derleyiciye bilgi vermektir. Bir yapı bildirimini gören derleyici, daha sonra bildirimi yapılan yapı türünden bir değişken tanımlanması durumunda, bu değişken için bellekte ne kadar yer ayıracağını, ve ayırdığı yeri ne şekilde organize edeceğini bilir.

    Bir yapı değişkeni (nesnesi) için, yapı bildiriminde belirtilen elemanların toplam uzunluğu kadar (byte olarak) yer ayrılır.

    struct SAMPLE {
    int a;
    long b;
    char ch;
    };

    void main()
    {
    struct SAMPLE x;

    printf("%d\n", sizeof(x));
    }

    Yukarıdaki program parçasında ekrana (DOS altında) 7 sayısı yazdırılacaktır. Çünkü yapı nesnesi olan x nesnesinin bellekte kapladığı yer üç parçasının kapladığı uzunluğun toplamıdır. Bunu aşağıdaki şekilde de ifade edebiliriz:

    sizeof(x) == sizeof(int) + sizeof(long) + sizeof(char)

    (Hizalama [alignment] konusuna geldiğimizde bu durumu daha detaylı olarak inceleyeceğiz.)
    Yapı Elemanlarına Erişim
    Yapıların dizilerden önemli bir farkı da elemanlara erişim konusundadır. Dizi elemanlarına erişim, dizi ismi (aslında bir adres bilgisidir) ve indis operatörü [ ] (index operator - subscript operator) yoluyla yapılırken, yapı elemanlarına erişim doğrudan yapı nesnesinin ve elemanın isimleriyle olur. Yapı elemanlarına erişimde nokta operatörü kullanılır.

    Nokta operatörü (.) iki operand alan araek konumunda (binary infix) bir operatördür. Bu operatörün sol tarafındaki operand bir yapı türünden değişken olmalıdır. Sağ tarafındaki operand ise ilgili yapı türününün (yani yapı bildiriminde önceden belirlenmiş) bir üyesi olmak zorundadır. Nokta operatörü, operatör öncelik tablosunun en yüksek düzeyinde bulunur.

    Yapı nesneleri de yerel ya da global olabilir. Diğer nesnelerde olduğu gibi yapı nesneleri içinde faaliyet alanı (scope) kavramı söz konusudur. Tüm blokların dışında tanımlanan yapı nesneleri global iken blokların içlerinde tanımlanan yapı değişkenleri yereldir. Global yapı nesneleri diğer türden global nesneler gibi statik ömür karakterine sahiptir ve dosya faaliyet alanı kuralına uyarlar. Yerel yapı nesneleri ise dinamik ömürlüdür ve blok faaliyet alanı kuralına uyarlar.

    Yapı değişkenlerine programcı tarafından değer verilmemişse, yapı değişkeni yerel (local) ise tüm elemanlarında rasgele değerler (garbage value) bulunur. Yapı nesnesi global ise tüm elemanlarında 0 değeri bulunur.

    Bir yapı nesnesi tanımlanarak, bu yapı nesnesinin elemanlarına nokta operatörü ile ulaşıldığında artık bu elemanların her biri ayrı bir nesne olarak ele alınır. Bu nesnelerin yapı dışında tanımlanan nesnelerden herhangi bir farkı yoktur, nesnelerin tüm özelliklerine sahiptirler.

    Örnek:

    struct SAMPLE {
    int a;
    long b;
    char c;
    };

    int main()
    {
    struct SAMPLE x;

    x.a = 10;
    x.b = 200000;
    x.c = 'M';
    printf("x.a = %d\nx.b = %ld\nx.c = %c\n", x.a, x.b, x.c);
    return 0;
    }

    Yukarıdaki örnekte görüldüğü gibi x.a, x.b ve x.c yapı elemanları ayrı birer nesne özelliği gösterirler. Bu elemanlara ayrı ayrı ulaşabilir ve ayrı ayrı atamalar yapabiliriz. Bu nesneleri ++ ve -- operatörlerine ya da & operatörüne operand yapabiliriz.
    Yapı Bildirimlerinin Yapılış Yerleri
    Yapı bildirimi global ya da yerel olarak yapılabilir. Eğer yerel olarak yapılırsa yalnızca bildirimin yapıldığı blokta o yapı türünden nesne tanımlanabilir. Bildirim global olarak yapılırsa her yerde o yapı türünden değişken tanımlanabilir.

    Uygulamalarda hemen hemen her zaman yapı bildirimleri programın tepesinde global olarak yapılır. (Yapı bildirimi başlık dosyalarının içinde de yapılabilir. Bu durumu ileride detaylı olarak inceleyeceğiz.)

    Yapı bildiriminde yer alan yapı elemanlarının faaliyet alanı yapı bildirim bloğuyla sınırlıdır. Yani yapı bildirimi bloğu dışında, yapı elemanı ile aynı isimli bir değişken tanımlanabilir. Yapı ismi (structure tag) ile aynı isimli değişkenler de tanımlanabilir.

    Aşağıdaki program parçasında isimlendirme açısından bir hata yok. Okunabilirlik açısından iyi bir uygulama değil.

    struct SAMPLE {
    int sample;
    long x;
    };

    int main()
    {
    int sample, SAMPLE;
    ...
    return 0;
    }
    Yapı Elemanlarının Bellekteki Yerleşimi
    Yapı elemanları belleğe, bildirimde ilk yazılan eleman küçük adreste olacak biçimde, ardışıl olarak yerleştirilir.

    Örnek:

    struct SAMPLE {
    int a;
    long b;
    char c;
    };

    int main()
    {
    struct SAMPLE x;

    printf("x.a adresi = %p\nx.b adresi = %p\n x.c adresi = %p\n", &x.a, &x.b, &x.c);
    }
    Yapı Değişkenlerine İlk Değer Verilmesi (initialization)
    Yapı değişkenlerine küme parantezleri içerisinde ilk değer verilebilir. Bu durumda verilen ilk değerler sırası ile yapı elemanlarına yerleştirilir. Daha az sayıda yapı elemanına ilk değer verilebilir, bu durumda ilk değer verilmemiş yapı elemanları 0 değeri alırlar.

    struct DATE {
    int day, month, year;
    };

    int main()
    {
    struct DATE x = {10, 12, 1999};
    struct DATE y = {10, 12};

    printf("%d %d %d\n", x.day, x.month, x.year};
    printf("%d %d %d\n", y.day, y.month, y.year};
    }

    Dizilerde olduğu gibi, yapılarda da yapı elemanlarından daha fazla sayıda elemana ilk değer vermek derleme zamanında hata oluşumuna neden olacaktır.
    Yapı Elemanı Olarak Göstericilerin Kullanılması
    Yapının bir elemanı herhangi türden bir gösterici olabilir. Bu durumda bu yapı elemanına ulaşıldıdığında, bu gösterici de yapı elemanı olmayan göstericiler gibi değerlendirilir

    Örnek:

    struct PERSON {
    char *name;
    int no;
    };

    int main()
    {
    struct PERSON x;

    x.name = "Necati";
    x.no = 125;
    printf("%s %d\n", x.name, x.no);
    return 0;
    }

    Yukarıdaki örnekte yapı elemanlarına ilk değer verme sentaksıyla da (initialization) değer atanabilirdi:

    ....
    struct PERSON x = {"Necati", 125};
    ...
    Yapı Elemanı Olarak Dizilerin Kullanılması
    Yapının bir elemanı herhangi türden bir dizi olabilir. Dizilerde olduğu gibi, bu durumda da yapının dizi elemanının ismi nesne belirtmez. (adres bilgisi belirtir).

    Örnek:

    struct PERSON {
    char name[30];
    int no;
    };

    int main()
    {
    struct PERSON x;
    gets(x.name);
    puts(x.name);
    putchar(x.name.[3]);
    x.name++ /* error. dizi ismi nesne değildir. */
    return 0;
    }
    Yapı Nesneleri Üzerinde Yapılabilecek İşlemler
    Yapı değişkenleri bir bütün olarak aritmetik operatörlerle ve karşılaştırma operatörleri ile işleme sokulamaz. Yapı nesneleri üzerinde şu işlemler yapılabilir:

    Bir yapı değişkeninin adresi alınabilir.
    Aynı türden iki yapı değişkeni birbirine atanabilir.
    sizeof operatörü ile bir yapı nesnesinin bellekte kapladığı alan bulunabilir.

    Aynı türden iki yapı değişkeninin birbirine atanmasında yapı elemanları karşılıklı olarak birbirlerine atanır. Yani bir blok kopyalanması söz konusudur. Atama işlemi için kesinlikle iki yapı değişkeninin de aynı türden olması gerekmektedir. İki yapı değişkeni de aynı türden değilse bu durum derleme aşamasında hata oluşturur. İki yapının aynı türden olmaları için aynı yapı ismi ile tanımlanmış olmaları gerekir. Aşağıdaki iki yapı, elemanlarının türleri ve dizilişleri aynı olduğu halde birbirleri ile aynı değildir ve birbirlerine atanamazlar.

    struct POINT_1 {
    int x, y;
    };

    struct POINT_2{
    int x, y;
    };

    ...
    struct POINT_1 a;
    struct POINT_2 b;

    b.x = 10;
    b.y = 20;

    a = b /* derleme zamanında hata. a ve b yapıları aynı türden değil. */
    Yapı Türünden Adresler ve Göstericiler
    Bir yapı değişkeninin adresi alınabilir. Bu durumda elde edilen adresin sayısal bileşeni yapının bellekteki başlangıç adresi, tür bileşeni ise yapı ile aynı türden adrestir. Bir yapı türünden göstericiler de tanımlanabilir. Yapı türünden göstericilere aynı yapı türünden bir adres atanmalıdır.
    Örnek:

    struct POINT_1 *p = &a;
    Göstericiler Yapı Elemanlarına Erişim
    p yapı türünden bir gösterici ve elm de o yapının bir elemanı olmak üzere erişim şu biçimde yapılır:

    (*p).elm

    Burada öncelik operatörünün kullanılması zorunludur. Kullanılmazsa önce p.elm ele alınır daha sonra * operatörü işleme sokulurdu. Bu durum da derleme zamanında hata oluşumuna neden olurdu.
    Yapıların Fonksiyonlara Parametre Olarak Geçirilmesi
    Yapıları fonksiyonlara geçirebilmek için iki yöntem kullanılabilir:

    Yapının kendisinin fonksiyona parametre olarak geçirilmesi.
    Aynı türden yapı nesnelerinin birbirlerine atanabilmesi özelliğinden faydalanılır. Bu yöntemde fonksiyonun parametre değişkeni bir yapı değişkeni olur. Fonksiyon da aynı yapı türünden bir yapı değişkeni ile çağırılır. Aynı türden iki yapı değişkeninin birbirine atanması geçerli olduğuna göre bu işlem de geçerlidir. Bu işlem bir blok kopyalaması gerektirdiği için hem bellek hem de zaman açısından göreli bir kayıba neden olur. Üstelik bu yöntemle, fonksiyon kendisine gönderilen argumanları değiştiremez. Yani fonksiyon değerle çağırılmıştır. (call by value)

    Örnek :

    struct PERSON {
    char name[30];
    int no;
    };

    void disp(struct PERSON y)
    {
    printf("%s %d\n", y.name, y.no);
    }

    int main()
    {
    struct PERSON x = {"Necati ERGIN", 125};
    disp(x);
    }

    Yapı değişkenlerinin adreslerinin geçirilmesi.
    Bu yöntemde fonksiyonun parametre değişkeni yapı türünden bir gösterici olur. Fonksiyon da bu türden bir yapı değişkeninin adresi ile çağırılır. Bu yöntem iyi bir tekniktir. Hemen her zaman bu yöntem kullanılmalıdır. Bu yöntemde yapı ne kadar büyük olursa olsun aktarılan yalnızca bir adres bilgisidir. Üstelik bu yöntemde fonksiyon kendisine adresi gönderilen yapı değişkenini değiştirebilir. Şüphesiz böyle bir aktarım işleminin mümkün olabilmesi yapı elemanlarının bellekteki ardışıllık özelliğinden faydalanılmaktadır. Örnek :

    struct PERSON {
    char name[30];
    int no;
    };

    void disp(struct PERSON *p)
    {
    printf("%s %d\n", (*p).name, (*p).no);
    }


    int main()
    {
    struct PERSON x = {"Necati Ergin", 156};

    disp(&x);
    }
    Yapıların Kullanım Yerleri
    Yapılar temel olarak 3 nedenden dolayı kullanılırlar.

    Birbirleri ile ilişkili olan değişkenler yapı elemanları olarak bir yapı içerisinde toplanırsa algısal kolaylık sağlanır. Örneğin düzlemde bir nokta, bir tarih bilgisi, bir depoda bulunan mamüllere ilişkin özellikler bir yapı ile temsil edilebilir.

    C dilinde bir fonksiyon en fazla 8 - 10 parametre almalıdır. Daha fazla parametreye sahip olması kötü bir tekniktir. Bir fonksiyon çok fazla parametrik bilgiye gereksinim duyuyorsa, bu parametrik bilgiler bir yapı biçiminde ifade edilmelidir. O yapı türünden bir değişken tanımlanmalı, bu değişkenin adresi fonksiyona parametre olarak gönderilmelidir. Örneğin bir kişinin nüfus cüzdan bilgilerini parametre olarak alıp bunları ekrana yazdıracak bir fonksiyon tasarlayacak olalım. Nüfus cüzdanı bilgilerinin hepsi bir yapı biçiminde ifade edilebilir ve yalnızca bu yapının adresi fonksiyona gönderilebilir.

    C dilinde fonksiyonların tek bir geri dönüş değeri vardır. Oysa fonksiyonların çok değişik bilgileri çağıran fonksiyona iletmesi istenebilir. Bu işlem şöyle yapılır: İletilecek bilgiler bir yapı biçiminde ifade edilir. Sonra bu türden bir yapı değişkeni tanımlanarak adresi fonksiyona gönderilir. Fonksiyon da bu yapı değişkeninin içini doldurur. Yani fonksiyon çıkışında bilgiler yapı değişkeninin içinde olur.

    C dilinin tasarımında belirlenmiş veri türleri yalnızca temel türleri kapsayacak şekildedir. Örneğin tarihler ya da karmaşık sayılar için C dilinde belirlenmiş temel bir tür yoktur (default veri tipi yoktur.) Yapıları bu gibi veri türlerini oluşturmak için kullanıyoruz.
    Ok Operatörü
    OK operatörü - ve > karakterlerinin yanyana getirilmesiyle oluşturulur. İki operand alan araek konumunda bir operatördür. (Binary infix ) Ok operatörü öncelik tablosunun en yüksek öncelik seviyesindedir. -> operatörünün sol tarafındaki operand yapı türünden bir adres olmalıdır. -> operatörünün sağ tarafındaki operand ise ilgili yapının bir elemanı olmalıdır. Sol tarfındaki operand ile belirtilen yapının (o adresteki yapının) sağ tarafında belirtilen elemanına ulaşmak için kullanılır.

    p, yapı türünden bir nesnenin adresini tutuyor olsun. Aşağıdaki iki ifade eşdeğerdir.

    (*)p.a p->a

    Yani nokta ve ok operatörlerinin her ikisi de elemana erişmek için kullanılır. Ancak nokta operatörü yapı değişkeninin kendisiyle ok operatörü ise adresiyle erişim sağlar. Okunabilirlik açısından ok operatörünün solunda ve sağında boşluk bırakılmamasını tavsiye ediyoruz.

    &p->a ifadesiyle p'nin gösterdiği yapının a elemanının adresi alınır. (Ok operatörü adres operatörüne göre daha önceliklidir.)
    Bir Yapı Türüne Geri Dönen Fonksiyonlar
    Bir fonksiyonun geri dönüş değeri bir yapı türünden olabilir.

    Örnek:

    struct POINT {
    int x, y;
    };

    struct POINT make_point(int x, int y);

    int main()
    {
    struct POINT a;

    a = make_point(3, 5);
    ...
    return 0;
    }

    struct POINT make_point(int x, int y)
    {
    struct POINT temp;

    temp.x = x;
    temp.y = y;
    return temp;
    }

    Bu durumda geri dönüş değerinin aktarıldığı geçici bölge de struct POINT türünden olur. Böyle bir fonksiyonun geri dönüş değeri aynı türden bir yapı değişkenine atanabilir.

    Ancak böyle fonksiyonlar küçük yapılar için kullanılmalıdır. Çünkü return ifadesiyle geçici bölgeye geçici bölgeden de geri dönüş değerinin saklanacağı değişkene atamalar yapılacaktır. Bu da kötü bir teknik olmaya adaydır. Küçük yapılar için tercih edilebilir. Çünkü algısal karmaşıklığı daha azdır.

    make_point fonksiyonunun parametre değişkenleri ile POINT yapısının üyeleri aynı isimde oldukları halde faaliyet alanları farklıdır.
    Yapı Türünden Bir Alanın Dinamik Olarak Tahsis Edilmesi
    Nasıl bir dizi için bellek alanı dinamik olarak tahsis edilebiliyorsa bir yapı için de böyle bir tahsisat yapılabilir. Aşağıdaki örnekte DATE türünden bir yapı için dinamik tahsisat yapılmıştır:

    struct DATE {
    int day, month, year;
    };

    int main()
    {
    struct DATE *p;

    p = (struct DATE *) malloc (sizeof(struct DATE));
    p->day = 10;
    p->month = 12;
    p->year = 2000;
    printf("%d %d %d\n", p->day, p->month, p->year);
    free(p);
    return 0;
    }

    Bir Yapı Adresine Geri Dönen Fonksiyonlar
    Bir fonksiyonun geri dönüş değeri bir yapı türünden adres de olabilir.

    Örneğin:

    struct POINT {
    int x, y;
    };

    struct POINT *dump_point(struct POINT *p);

    int main()
    {
    struct POINT *p;
    struct POINT a;

    a.x = 10;
    b.x = 20;
    p = dump_point(&a);
    return 0;
    }

    struct POINT dump_point(struct POINT *p)
    {
    struct POINT *retval;

    if ((retval = (struct POINT *) malloc(sizeof(struct POINT))) == NULL) {
    printf("not enough memory");
    exit(EXIT_FAILURE);
    }

    *retval = *p;
    return retval;
    }

    Tabi böyle bir fonksiyon yerel bir yapı değişkeninin adresine geri dönemez. Global bir yapı değişkeninin adresine de geri dönmesinin bir anlamı yoktur. (Statik bir yerel yapı nesnesinin adresiyle geri dönülmesinde hiçbir sakınca yok. Statik yerel değişkenler konusundaki bilgilerinizi hatırlayınız.) En iyi tasarım, içeride dinamik olarak tahsis edilmiş bir yapının adresine geri dönmektir.

    Tahsis edilen alanın "heap" bellek alanına iade edilmesi, fonksiyonu çağıranın sorumluluğundadır.
    Yapı Bildirimi İle Değişken Tanımlamasının Birlikte Yapılması
    Bir yapı bildirimi noktalı virgül ile kapatılmayıp, yapı bildiriminin kapanan blok parantezinden hemen sonra bir değişken listesi yazılırsa yapı bildirimi ile değişken tanımlaması bir arada yapılmış olur.

    struct DATE {
    int day, month, year;
    } bdate, edate;

    Bu tanımlamada struct DATE türünden bir yapının bildirimi ile birlikte bu yapı türünden bdate ve edate değişkenleri de tanımlanmıştır. Bu durumda yapı değişkenlerinin faaliyet alanları yapı bildiriminin yerleştirildiği yere bağlı olarak belirlenir. Yani söz konusu yapı değişkenleri yerel ya da global olabilir.

    Yapı nesnelerinin hemen yapı bildiriminden sonra tanımlanması durumunda yapı ismi kullanma zorunluluğu da bulunmamaktadır. Örneğin yukarıdaki bildirim aşağıdaki şekilde de yapılabilir.

    struct {
    int day, month, year;
    } bdate, edate;

    Burada bdate ve edate yukarıda bildirilen (ama isimlendirilmeyen) yapı türünden değişkenlerdir. Bu yöntemin dezavantajı artık aynı türden başka bir yapı değişkeninin tanımlanmasının mümkün olmayışıdır. Örneğin yukarıdaki kod parçasından sonra aynı yapı türünden bir yapı nesnesi daha tanımlamak istediğimizi düşünelim. Bu tanımlama

    struct {
    int day, month, year;
    } cdate;

    şeklinde yapılsa bile artık derleyici, eleman yapısı ve elemanların türleri aynı olsa bile bu yapıyı ayrı bir yapı türü olarak ele alacaktır. Dolayısıyla

    cdate = bdate;

    gibi bir atama yapı türlerinin farklı olması nedeniyle geçerli değildir.

    Böyle bir tanımlamanın başka bir dezavantajı da bu yapı türüyle ilgili bir fonksiyon yazılamayışıdır. Çünkü yapı türünün bir ismi yoktur, ve bir fonksiyonun parametre değişkeni tür bilgisi olmadan tanımlanamaz.
    İçiçe Yapılar (nested structures)
    Bir yapının elemanı başka bir yapı türünden yapı değişkeni ise bu duruma içiçe yapılar denir. İçiçe yapı bildirimi iki biçimde yapılabilir :

    Önce eleman olarak kullanılan yapı bildirilir. Onun aşağısında diğer yapı bildirimi yapılır. Örnek:

    struct DATE {
    int day, month, year;
    };

    struct PERSON {
    char name[30];
    struct DATE birthdate;
    int no;
    };

    Bu durumda içteki yapıya ulaşmak için 2 tane nokta operatörü kullanmak gerekir.

    int main()
    {
    struct PERSON employee;

    employee.birthdate.day = 10;
    employee.birthdate.month = 12;
    employee.birthdate.year = 2000;
    strcpy(employee.name, "Necati Ergin");
    employee.no = 1472;
    printf("%d %d %d - %s - %d\n", employee.birthdate.day, employee.birthdate.month,
    employee.birthdate.year, employee.name, employee.no);
    return 0;
    }


    Bu yöntemde eleman olan yapı değişken tanımlaması ile birlikte elemana sahip yapının içerisinde bildirilir.
    Örnek:

    struct PERSON {
    char name[30];
    struct DATE {
    int day, month, year;
    } birthdate;
    int no;
    };

    Burada içte bildirilen yapı da sanki dışarıda bildirilmiş gibi işlem görür. yani içeride bildirilen yapı türünden değişkenler tanımlanabilir. Burada dikkat edilmesi gereken bir noktada içiçe yapı bildiriminin yapılmasına rağmen bir değişken tanımlamasının yapılmamış olmasıdır. Yani birthdate bir nesne değildir. Ancak struct PERSON türünden bir değişken tanımlandığında, bu yapı değişkeninin bir alt elemanı olacaktır:


    struct PERSON X;

    X.birthdate.day = 20;
    İçiçe Yapılara İlk Değer Verilmesi
    Normal olarak ilk değer vermede elemanlar sırasıyla, içteki yapı da dikkate alınacak biçimde, yapı üyelerine atanır. Ancak içteki yapının ayrıca küme parantezleri içerisine alınması okunabilirliği artırdığı için tavsiye edilir. Örnek:

    struct PERSON per = {"Necati Ergin", {10, 10, 1967}, 123};

    Eğer içteki yapı ayrıca küme parantezi içine alınmışsa içteki yapının daha az sayıda elemanına ilk değer vermek mümkün olabilir. Örnek :

    struct PERSON per = {"Necati Ergin", {10, 10}, 123};

    Burada doğum tarihinin year elemanına ilk değer verilmemiştir. Derleyici bu elemana otomatik olarak 0 değeri yerleştirecektir. Ancak burada içteki küme parantezleri kullanılmasaydı 123 ilk değeri year elemanına verilmiş olacak, no elemanına otomatik olarak 0 değeri verilmiş olacaktı. Aynı durum yapı içinde dizi bildirimlerinde de söz konusudur. Örnek :

    struct SAMPLE {
    int a[3];
    long b;
    char c;
    };

    struct SAMPLE x = {{1, 2, 3}, 50000L, 'x'};

    Aşağıdaki gibi bir bildirim de geçerlidir :

    struct SAMPLE {
    int a[3];
    long b;
    char c;
    } x = {{1, 2, 3}, 50000L, 'A}, *p;


    İçiçe Yapı Kullanımının Avantajları
    Aşağıdaki gibi bir yapı bildiriminin yapıldığını düşünelim :

    struct ISIM {
    char ad[ADUZUNLUK + 1];
    char soyad[SADUZUNLUK + 1];
    };

    İsim yapısını daha büyük bir yapının parçası olarak kullanabiliriz.

    struct OGRENCI {
    struct ISIM isim;
    int no, yas;
    char cinsiyet;
    } ogrenci1, ogrenci2;

    Bu durumda ogrenci1 degiskeninin ad ve soyad'ına ulaşmak icin iki kez nokta operatörü kullanılacaktır:

    strcpy(ogrenci1.isim.ad, “Hakan”);

    Yukarıdaki örnekte OGRENCI yapısının elemanı olarak ISIM yapısını kullanmak yerine, dogrudan ad[ADUZUNLUK + 1] ve char soyad[SADUZUNLUK + 1] dizilerini OGRENCI yapısının elemanları yapabilirdik. Ama isim yapısını doğrudan bir eleman olarak kullanırsak bazı işlemleri daha kolay yapabiliriz. Örneğin öğrencilerin ad ve soyadını yazan bir fonksiyon yazacağımızı düşünelim. Bu durumda yazılacak fonksiyona 2 arguman göndermek yerine yalnızca 1 arguman gönderilecektir.

    isim_yaz(ogrenci1.isim);

    Benzer şekilde isim türünden bir yapı elemanından ögrencinin ad ve soyad bilgilerini OGRENCI yapısı türünden bir değişkenin alt elemanlarına kopyalama daha kolay yapılacaktır :

    struct isim yeni_isim;
    ...
    ogrenci1.isim = yeni_isim;
    Yapı Dizileri
    Yapılar da bir tür belirttiğine göre yapı türünden de diziler söz konusu olabilir. Yapı dizilerine de normal dizilere ilk değer verilmesine benzer şekilde ilk değer verilebilir. İlk değer verme sırasında kullanılan, içteki küme parantezleri okunabilirliği artırır. Örnek:

    struct DATE {
    int day, month, year;
    };

    int main()
    {
    struct DATE birthday[5] = {{10, 10, 1958}, {04, 03, 1964},
    {21, 6.1967}, {22. 8, 1956},
    {11, 3, 1970}};
    struct DATE *pdate;
    int i;

    pdate = birthdate;
    for (i = 0; i < 5; ++i) {
    printf("%02d / %02d / %04d\n", pdate->day, pdate->month, pdate->year);
    ++pdate;
    }
    return 0;
    }

    Program parçasını şu şekilde de yazabilirdik:

    int main()
    {
    ...
    for (i = 0; i < 5; ++i)
    disp(&birthdate[i]);
    return 0;
    }

    void Disp(struct DATE *p)
    {
    printf("%02d / %02d / %04d\n", pdate->day, pdate->month, pdate->year);
    }
    Yapılara İlişkin Karmaşık Durumlar
    Bir yapının elemanı başka bir yapı türünden yapı göstericisi olabilir. Örneğin:

    struct DATE {
    int day, month, year;
    };

    struct PERSON {
    char name[30];
    struct DATE *bdate;
    int no;
    };

    struct PERSON x; şeklinde bir tanımlamanın yapıldığını düşünelim.

    x.bdate ifadesinin türü struct DATE türünden bir adrestir ve nesne belirtir. (Nesne belirtir çünkü bdate bir gösterici değişkendir.)
    x.bdate->day ifadesinin türü int dir ve nesne belirtir.
    &x.bdate->day ifadesinin türü int türden bir adrestir.

    Tabi bu örnekte, bir değer ataması yapılmamışsa, x.bdate ile belirtilen gösterici içerisinde rasgele bir adres vardır. (garbage value) Bu göstericinin kullanılabilmesi için tahsis edilmiş bir alanı göstermesi gerekir. Örneğin bu alan dinamik olarak tahsis edilebilir.

    x.bdate = (struct DATE *) malloc (sizeof(struct DATE));

    Yukarıdaki örnekte elimizde yalnızca struct PERSON türünden bir gösterici olduğunu düşünelim.

    struct PERSON *person;

    person->bdate ifadesinin türü struct DATE türünden bir adrestir.
    person->bdate->day ifadesinin türü int dir.

    Bu örneklerde henüz hiçbir yer için bilinçli bir tahsisat yapılmamıştır. Hem person göstericisi için hem de person->date göstericisi için, dinamik bellek fonksiyonlarıyla bellekte yer tahsisatlarının yapılması gerekir:

    person = (struct PERSON *) malloc(sizeof(struct PERSON));
    person->bdate = (struct DATE *) malloc(sizeof(struct DATE));

    Burada dinamik olarak tahsis edilen alanlar ters sırada iade (free) edilmelidir.

    free(person->bdate);
    free(person);

    Bir yapının elemanı kendi türünden bir yapı değişkeni olamaz. Örneğin:

    struct SAMPLE {
    struct SAMPLE a;
    }; /* hata */

    Çünkü burada SAMPLE yapısının uzunluğu belirlenemez. Ancak bir yapının elemanı kendi türünden bir gösterici olabilir. Örneğin:

    struct LLIST {
    int val;
    struct LLIST *next;
    };

    Bu tür yapılar özellikle bağlı liste ve ağaç yapılarını (algoritmalarını) gerçekleştirmek amacıyla kullanılabilir. (Bu konu detaylı olarak bağlı listeler dersinde ele alınacaktır.
    Bağlı Liste Nedir
    Bellekte elemanları ardışıl olarak bulunmayan listelere bağlı liste denir. Bağlı listelerde her eleman kendinden sonraki elemanın nerede olduğu bilgisini de tutar. İlk elemanın yeri ise yapı türünden bir göstericide tutulur. Böylece bağlı listenin tüm elemanlarına ulaşılabilir. Bağlı liste dizisinin her elemanı bir yapı nesnesidir. Bu yapı nesnesinin bazı üyeleri bağlı liste elemanlarının değerlerini veya taşıyacakları diğer bilgileri tutarken, bir üyesi ise kendinden sonraki bağlı liste elemanı olan yapı nesnesinin adres bilgisini tutar. Örnek:

    struct LLIST {
    int val;
    struct LLIST *next;
    };

    Bağlı listenin ilk elemanının adresi global bir göstericide tutulabilir. Son elemanına ilişkin gösterici ise NULL adresi olarak bıraklılır. Bağlı liste elemanları malloc gibi dinamik bellek fonksiyonları ile oluşturulur.
    Bağlı Listelerle Dizilerin Karşılaştırılması
    Dizilerde herhangi bir elemana çok hızlı erişilebilir. Oysa bağlı listelerde bir elemana erişebilmek için, bağlı listede ondan önce yer alan bütün elemanları dolaşmak gerekir.

    Dizilerde araya eleman ekleme ya da eleman silme işlemleri için blok kaydırması yapmak gerekir. Oysa bağlı listelerde bu işlemler çok kolay yapılabilir.

    Diziler bellekte ardışıl bulunmak zorundadır. Bu durum belleğin bölünmüş olduğu durumlarda belirli uzunlukta dizilerin açılmasını engeller. Yani aslında istenilen toplam büyüklük kadar boş bellek vardır ama ardışıl değildir. İşte bu durumda bağlı liste tercih edilir.

    Bağlı liste kullanımı sırasında eleman ekleme, eleman silme, bağlı listeyi gezme (traverse), vb. işlemler yapılır.

      Forum Saati Cuma Mayıs 10, 2024 1:10 am