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
tipinden struct yk
yd
değişkenini tanımlamış bulunuyoruz.
tipi iki üye içerir,
bir tamsayı, bir de gerçek sayı. struct yk
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
tipindeki bir yapının
saklandığı bir yere bir gösterge olan
struct yk
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 kunyeolan 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.