Standart C Programlama Dili


5.2. Yapılar

Yapılar ve birlikler C programları için karmaşık veri yapılarının tanımlanmasında anahtar rolü oynarlar. Bu kısımda yapılar anlatılmaktadır. Daha sonraki bir kısım ise birlikleri kapsamaktadır.

Pascal’la tanışıklığı olan okuyucularımız, herhalde C dilindeki yapıların Pascal’daki record’lara karşılık olduğunu fark edeceklerdir. Aynı tipte belirli sayıdaki değişkenleri saklamak için bir dizi kullanılabilir. Diğer taraftan, bir yapı, genelde değişik tipte olan bir veya daha fazla değişkeni bir araya getirmek için kullanılabilir. Niye, ayrı ayrı değişkenler kullanmak yerine bunları bir yapı içinde toplamaya gerek duyarız? Temel neden kolaylıktır. Örneğin, bir fonksiyona çok sayıda argüman geçirmek yerine, bazılarını yapılar içinde gruplayıp yapıları geçirebiliriz.

Şimdi yapıları nasıl tanımlayabileceğimizi ve kullanacağımızı görelim. Yapı tanımlamasının genel şekli şöyledir:

struct tanıtıcı_sözcükopt { yapı_bild_listesi }opt bild_belirteçleriopt ;

struct anahtar sözcüğü bir yapının tanımlanmakta olduğunu göstermektedir. Her zaman olduğu gibi, tanımlanmakta olan değişken(ler)in bellek sınıfını göstermek için bu anahtar sözcüğün önüne uygun herhangi bir bellek sınıfı belirteci (örneğin, static, extern) konulabilir.

İsteğe bağlı olan tanıtıcı_sözcük’e yapı künyesi adı verilir. Tanımlanmakta olan yapı tipine isim verir ve daha sonra benzer yapı değişkenleri tanımlamak için bir kısaltma işlevi görür. Eğer bu yapı tipi bütün program içinde bir defa kullanılıyorsa, daha sonra kullanılmayacağı için yapı künyesini atlayabiliriz.

Yapı_bild_listesi her bildirimin bir noktalı virgülle bitirildiği bir dizi bildirim içerir. Bu bildirimler (ilkleyen içerememeleri dışında) normal değişken tanımlamalarına benzer; fakat bu durumda değişkenler bildirimi yapılan yapı altında gruplanacaklar ve yapının üyeleri olacaklardır. Bir yapının üyeleri diziler veya yapılar gibi karmaşık tipler olabileceği için, C dilinde istediğimiz kadar karmaşık veri yapıları tanımlayabiliriz.

Bild_belirteçleri belirtilen yapı tipinden değişkenler tanımlayan isteğe bağlı bir listedir. Bunlar basit (yapı) değişkenleri, yapılara göstergeler, yapılardan oluşan diziler, yapı döndüren fonksiyonlar veya bunların herhangi bir birleşimi olabilir.

Bir örnek:

struct yk {
  int i;
  float f;
} yd;

Burada, yk yapı künyesini ve struct yk tipinden yd değişkenini tanımlamış bulunuyoruz. struct yk tipi iki üye içerir, bir tamsayı, bir de gerçek sayı. yk yapı künyesi daha sonra aynı tipten başka değişkenlerin bildirimini yapmak için kullanılabilir. Örneğin,

struct yk yd1, *yg;

(yd’ye benzeyen) bir yd1 değişkenini ve struct yk tipindeki bir yapının saklandığı bir yere bir gösterge olan yg değişkenini tanımlayacaktır.

Gelin birkaç örneğe daha bakalım. Bu kısmın başında verilen sözdizimde üç tane isteğe bağlı bölüm görmekteyiz. Herhangi bir zamanda bunlardan en çok bir tanesi belirtilmeyebilir. Böylece,

struct {
  int i;
  float f;
} yd2;

tanımlamasında bir yapı değişkeni, yd2, tanımlanmakta, fakat yapı künyesi tanımlanmamaktadır;

struct yk {
  int i;
  float f;
};

tanımlamasında ise bir yapı künyesi, yk, tanımlanmakta, fakat değişken tanımlamaları bulunmamaktadır; yani sadece bir tip bildirimi vardır. Daha sonra, bu ismi kullanarak, aşağıda yapıldığı gibi değişkenler tanımlayabiliriz:

struct yk ydz[5], yf(float), (*ydgf(int))[8];

Burada yapı_bild_listesi’ni tekrar belirtmeye gerek yoktur. ydz böyle 5 tane yapıdan oluşan bir dizidir; yf bir float argüman alan ve böyle bir yapı döndüren bir fonksiyondur (bazı eski derleyiciler böyle bir bildirimi kabul etmeyebilirler, çünkü, eski Standarda göre, fonksiyonlar dizi veya yapı gibi karmaşık tipler döndürmezler); ydgf böyle 8 yapıdan oluşan bir diziye bir gösterge döndüren ve bir int argüman alan bir fonksiyondur. Son örneği anlamak için, işleçleri, uygulandıkları sırayı dikkate alarak (tersten) okuyun: (), *, []; yani yapı dizisi göstergesi fonksiyonu.

Şimdi de daha karmaşık bir örnek:

struct kisi {
  char *isim;
  int dogumtarihi;
  enum cinsiyet_tipi { erkek, kadin } cinsiyet;
  struct kisi *baba, *ana, *es,
    *cocuklar[MAKSCCK];
} kisiler[MAKSKS];

Yukarıdaki tanımda gördüğünüz kadarıyla, tıpkı diğer dillerde olduğu gibi, C dilinde de karmaşık veri yapıları tanımlanabilir. Burada, basit değişkenler, göstergeler, sayım tipinden değişkenler, yapılara göstergelerden oluşan diziler vs. olan üyeleri içeren bir yapı dizisi görmekteyiz. Yapının bazı üyelerini tanımlarken yapının kendi tanımını da kullanmakta olduğumuza dikkat edin; tanımlamakta olduğumuz üyeler gösterge oldukları için bu herhangi bir problem oluşturmaz. Aslında, gösterge tanımlarken daha ileride tanımlanmış yapıları da kullanabiliriz. Yukarıda tanımlanan kisiler değişkeni, kolayca erişilip işlenebilen en fazla MAKSKS kadar kişinin bilgisini saklayabilir.

Bir yapının üyelerine ulaşmak için, bir nokta (.) ile gösterilen, yapı üyesi işlecini kullanırız. Bu işleç, yapı değişkeni ile üyesi arasına konulur. Örneğin,

yd.i = (int) yd.f;

yd yapı değişkeninin f üyesi içindeki tamsayıyı i üyesine atayacaktır. . işleci en yüksek önceliği olan işleçlerden biridir, böylece, örneğin yd.i genelde sanki tek bir değişkenmiş gibi, etrafına parantez koymaya gerek duymadan kullanılabilir.

Şimdi, bir yapı göstergesi değişkeninin üyesine erişirken karşılaşabileceğimiz küçük bir problemi inceleyelim. Örneğin, yg bir yapıya bir göstergedir; *yg yapıya erişmek için kullanılır, ancak onun üyesine erişmek için *yg.i kullanılamaz. Bunun nedeni önceliklerdir; .nin önceliği *’dan yüksektir, böylece *yg.i aslında *(yg.i) anlamına gelir, bu da yg’nin bir üyesi olan i’nin bir gösterge olduğunu varsayar. Doğru sonucu elde etmek için parantez kullanmak zorundayız: (*yg).i. Ancak bu yöntemin de kullanımı biraz zor olabilir. Bunun üstesinden gelmek için, C’ye başka bir işleç (->) eklenmiştir. yg->i tamamen (*yg).i ile eşdeğerdir ve daha kısa ve anlamlıdır.

Dört işleç, (), [], . ve ->, öncelik tablosunda en yüksek düzeyi oluştururlar ve referans oluşturmak için kullanıldıklarından diğer işleçlerden farklıdırlar.

Bazı eski C uygulamalarında, yapılarla yapabileceğimiz şeyler, & kullanarak bir yapının adresini almak ve üyelerinden birisine erişmekle sınırlıdır. Örneğin, yapılar fonksiyonlara geçirilip geri döndürülemez;

yapi1 = yapi2;

şeklinde her iki tarafında yapı değişkeni olan bir atama gerçekleştirilemez. Bu sınırlamaların üstesinden gelmek için göstergeler kullanılabilir. Buna rağmen, C Standardını izleyen yeni C derleyicilerinde böyle sınırlamalar yoktur. Örneğin, bizim sistemde aşağıdaki program kabul edilmektedir:

#include <stdio.h>

struct kunye {
  int i;
  char *c;            /* y1 yapı değişkeninin */
} y1 = { 1, "bir" };  /* tanımlanması ve ilklenmesi */

struct kunye f        /* parametresi ve dönüş tipi */
 (struct kunye y)     /* struct kunye olan fonksiyon */
{
  struct kunye z;
  z.i = y.i * 2;      /* üyelere değerler ata */
  z.c = "iki kere";
  return z;           /* bir yapı döndür */
}  /* f */

int main (void)
{
  struct kunye y2, f(struct kunye);
  y2 = f(y1);         /* yapıyı başka bir yapıya ata */
  printf("%d %s\n%d %s\n", y1.i, y1.c, y2.i, y2.c);
  return 0;
}  /* main */

ve çıktı şöyledir:

1 bir
2 iki kere

Burada yapılarla yapılacak üç şeyi görmekteyiz: Bir yapıyı bir argüman olarak geçirmek, fonksiyondan bir yapı döndürmek ve bir yapıyı başka bir yapıya atamak. Yapı ataması, kaynak yapının her üyesinin hedef yapının ilgili üyesine atanması demektir ve yapının boyuna göre bir miktar zaman alabilir.

Yukarıdaki örnekte, yapı değişkenlerini ilkleme yöntemini de görmekteyiz. Sözdizimi diziler için kullanılana benzemektedir. İlklenecek değişken, = atama simgesinden sonra virgülle ayrılıp çengelli parantezler içine alınmış değişmez ifadelerden oluşan bir liste ile izlenir. Her üye sırayla ilgili ifadenin değerine ilklenir. Eğer ifadelerden daha fazla üye varsa, geri kalan üyeler C dilindeki varsayılan değere, yani sıfıra ilklenir.

Daha yeni C Standardında yapı üyelerini ilklemek için kullanılacak başka bir yöntem daha bulunmaktadır. Belirlenmiş ilkleyen kullanarak sadece istediğimiz üyeyi ve tanımlama sırasına bağlı olmadan ilkleyebiliriz. Bu yöntemde eksik verilen ilkleyenler için ilgili üyeler sıfıra ilklenir ve belirlenmiş bir ilkleyenden sonra normal bir ilkleyen verilirse, sıradaki üyeye uygulanır. Örneğin:

struct yapi {
  int i, j;
  char *c;
  float x, y;
} y5 = { .i=1, 2, .x=3, .c="bir" };

Burada 2 değeri i üyesinden sonra gelen j üyesine atanmakta; x üyesine 3.0 değeri atanırken, y üyesine 0.0 atanmaktadır.