Standart C Programlama Dili


7.7. Bir Örnek—Öğrenci Veritabanı

Öğrenci kayıtlarını tutup bunlar üzerinde ekleme, silme ve görüntüleme işlemlerini yapan bir program yazalım. Program kullanıcıya bir menü sunacak ve kullanıcının seçimine göre ilgili seçeneği çalıştıracaktır. C’nin bu tür uygulama için en uygun dil olduğunu veya bunun en iyi ve kısa program olduğunu savunmuyoruz; amacımız sadece, bu bölümde anlatılmış olan C ile ilgili bazı konuları göstermektir. Programın listesinden sonra, kodun detaylı bir açıklaması verilmektedir. Devam etmeden önce, programı bilgisayara girip çalıştırmanız önerilir.

  1. #include <conio.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5.
  6. /* Degismezler */
  7. char const  DOSYA_ADI[] = "OGRENCI.DAT";
  8. char const  BOS[]       = "\r\r\r\r\r\r\r";
  9. #define     ANHTR_UZ    7
 10. char const  AKD[]       = "%7s";
 11. #define     ISIM_UZ     15
 12. #define     KAYIT_BOYU  ((long)(ANHTR_UZ+ISIM_UZ+4))
 13. char const  KKD[]       = "%7s%15s%4d";
 14.
 15. struct kayit {
 16.   char anhtr[ANHTR_UZ+1], isim[ISIM_UZ+1];
 17.   int yil;
 18. };
 19.
 20. /* Her fonksiyonda bastan tanimlamak yerine bu
 21.  * degiskenler burada tanimlanmislardir.
 22.  */
 23. char  satir[128];
 24. FILE  *dg;
 25.
 26. void dizioku            /* d icine en fazla u karakter oku */
 27.   (char d[], int u)
 28. {
 29.   gets(satir);
 30.   strncpy(d, satir, u);
 31.   d[u] = '\0';
 32. }  /* dizioku */
 33.
 34. long bul                /* Dosya icindeki ANHTR'in yerini; */
 35.   (char const anhtr[])  /* aksi takdirde -1L dondur.  */
 36. {
 37.   int   drm, kar = 1;
 38.   long  dns;
 39.   char  d1[ANHTR_UZ+1], d2[ANHTR_UZ+1];
 40.
 41.   sprintf(d2, AKD, anhtr); /* soldan bosluk doldur */
 42.   dg = fopen(DOSYA_ADI, "rb");
 43.   if (dg == NULL)
 44.     return -1L;
 45.   drm = fseek(dg, -KAYIT_BOYU, SEEK_END);
 46.   while (drm == 0 && fgets(d1, ANHTR_UZ+1, dg) != NULL  &&
 47.     (kar=strcmp(d2,d1)) != 0)
 48.       drm = fseek(dg, -KAYIT_BOYU-ANHTR_UZ, SEEK_CUR);
 49.   if (kar == 0)
 50.     dns = ftell(dg)-ANHTR_UZ;
 51.   else
 52.     dns = -1L;
 53.   fclose(dg);
 54.   return dns;
 55. }  /* bul */
 56.
 57. void ekleme (void)
 58. {
 59.   long          konum;
 60.   struct kayit  k;
 61.   int           scd;
 62.
 63.   puts("Eklenecek ogrenci numarasini girin:");
 64.   dizioku(k.anhtr, ANHTR_UZ);
 65.   if (bul(k.anhtr) == -1L) {
 66.     puts("Ogrenci adini girin:");
 67.     dizioku(k.isim, ISIM_UZ);
 68.     do {
 69.       puts("Kayit yilini girin:");
 70.       scd = scanf("%d", &k.yil);
 71.       gets(satir);
 72.     } while (1!=scd || k.yil<1980 || k.yil>9999);
 73.     konum = bul(BOS);
 74.     dg = fopen(DOSYA_ADI, "ab");  /* dosyayi olustur  */
 75.     fclose(dg);
 76.     dg = fopen(DOSYA_ADI, "r+b");
 77.     if (konum == -1L)
 78.       fseek(dg, 0L, SEEK_END);    /* dosya sonuna git */
 79.     else
 80.       fseek(dg, konum, SEEK_SET); /* bos kayida git */
 81.     fprintf(dg, KKD, k.anhtr, k.isim, k.yil);
 82.     fclose(dg);
 83.     puts("Ekleme islemi tamamlandi.");
 84.   }
 85.   else
 86.     fprintf(stderr, "Cift anahtar!\n\a");
 87. }  /* ekleme */
 88.
 89. void silme (void)
 90. {
 91.   long  konum;
 92.   char  anhtr[ANHTR_UZ+1];
 93.
 94.   puts("Silinecek ogrenci numarasini girin:");
 95.   dizioku(anhtr, ANHTR_UZ);
 96.   if ((konum = bul(anhtr)) != -1L) {
 97.     dg = fopen(DOSYA_ADI, "r+b");
 98.     fseek(dg, konum, SEEK_SET);
 99.     fprintf(dg, AKD, BOS);
100.     fclose(dg);
101.     puts("Silme islemi tamamlandi.");
102.   }
103.   else
104.     fprintf(stderr, "Boyle anahtar yok!\n\a");
105. }  /* silme */
106.
107. void goruntuleme (void)
108. {
109.   long          konum;
110.   struct kayit  k;
111.
112.   puts("Goruntulenecek ogrenci numarasini girin:");
113.   dizioku(k.anhtr, ANHTR_UZ);
114.   if ((konum = bul(k.anhtr)) != -1L) {
115.     dg = fopen(DOSYA_ADI, "rb");
116.     fseek(dg, konum+ANHTR_UZ, SEEK_SET);
117.     printf("Ogrenci adi: %s\n",  fgets(k.isim,ISIM_UZ+1,dg));
118.     fscanf(dg, "%d", &k.yil);
119.     fclose(dg);
120.     printf("Kayit yili : %d\n", k.yil);
121.   }
122.   else
123.     fprintf(stderr, "Boyle anahtar yok!\n\a");
124. }  /* goruntuleme */
125.
126. int menu (void)
127. {
128.   int  secenek;
129.
130.   do {
131.     puts("\n\t1.EKLEME\n\t2.SILME\n\t3.GORUNTULEME\n\t0.CIKIS");
132.     printf("Seciminizi yapin:");
133.   } while ((secenek=_getche()) < '0' || secenek >  '3');
134.   putchar('\n');
135.   return secenek - '0';
136. }  /* menu */
137.
138. int main (void)
139. {
140.   static void (*fd[])(void) =
141.     { (void (*)(void)) exit, ekleme, silme, goruntuleme };
142.
143.   while (1) (*fd[menu()])();
144.   return 0;
145. }  /* main */

Programda, her öğrenci için bir sayı, isim ve kayıt yılı saklanır. Bu bilgiyi birleştirmek için Satır 15’te kayit yapısı tanımlanmıştır. Öğrenci numarası “anahtar”dır, yani her öğrencinin ayrı numarası vardır ve öğrencinin bilgisine ulaşmak için bu numarayı kullanırız.

menu fonksiyonu (Satır 126-136) kullanıcıya seçimleri sunar ve neyi seçtiğini gösteren bir sayı (aslında bir char) girmesini ister. 0’dan küçük veya 3’ten büyük seçimler kabul edilmez. Geçerli bir seçenek girildiğinde, fonksiyon tarafından secenek değişkeninin tamsayı eşdeğeri döndürülür. Bu fonksiyonda puts kullanımına dikkat edin. Herhangi bir değişkenin değerinin basılmasına gerek olmadığından, printf kullanmanıza gerek yoktur. Programda, böyle durumlar için puts kullandık.

main’deki while deyimi biraz karmaşık gözükebilir. Satır 140-141’de exit, ekleme, silme ve goruntuleme fonksiyonlarına göstergeler içerecek şekilde ilklenmiş bulunan, fd dizisinin menu’ncü elemanını çağırır. Gördüğünüz gibi, fonksiyonlara göstergeler için bir dizi gibi, karmaşık veri yapılarının kullanımı küçük bir yerde çok şey yapabilecek kod yazmamızı sağlar. Dikkat: stdlib.h’de tanımlanmış olan exit fonksiyonu bir int argüman beklemektedir; diğer fonksiyonlara uyması için, bir kalıp aracılığı ile, bu fonksiyonu argümansız hale çevirdik.

ekleme, silme ve goruntuleme fonksiyonları, argüman olarak verilmiş anahtarı içeren kaydı taramak için bul adı verilen bir fonksiyonu çağırırlar. bul (Satır 34-55) kaydın dosyanın başına göre konumunu bir long sayı şeklinde verir; herhangi bir hata olursa -1L döndürür. bul’un başındaki sprintf, anhtr’ın sağa dayanmış bir uyarlamasını d2’ye koyar. Daha sonra, öğrenci dosyasını okumak için dosyayı açmaya çalışırız. Eğer açılamazsa, hata döndürülür. Eğer dosya açılırsa, dosyadaki son kaydın başına konumlanırız. Satır 46-48’deki while şu anda konumlandırılan kaydın anahtarını d2 ile karşılaştırır ve eğer eşitseler veya dosyanın başına ulaştıysak, sona erer. Satır 48 dosyayı bir önceki kaydın başına konumlandırır. Dosya taramasını geriye doğru yapmamızın nedeni, fseek fonksiyonunun, dosyanın sonundan ileri doğru gitmeyi bir hata şeklinde değerlendirmemesinden kaynaklanır. Satır 53’te dosyayı kapattıktan sonra, bul’un sonunda, (Satır 50’de ftell kullanılarak uygun değer verilen) dns döndürülür.

Üç “büyük” fonksiyon tarafından çokça kullanılan başka bir fonksiyon da dizioku’dur. Bu “kolaylık” fonksiyonu iki argüman kabul eder: İçine bir şey okunacak karakter dizisi ile okunacak dizinin uzunluğunu gösteren bir tamsayı. Baştaki gets girdiden bir satır okur. Geri kalan deyimler, gerekli sayıda karakteri d’ye aktarıp sonuna bir boş karakter koyarlar.

ekleme fonksiyonu dosyaya yeni bir öğrenci kaydı yerleştirmeye çalışır. Yeni öğrencinin numarasını okur ve bu anahtarla bul’u çağırır. Eğer bul -1L’den farklı bir şey döndürürse, dosya içinde bu numaradan bir öğrenci kaydı vardır demektir, onun için ekleme uygun bir uyarı mesajı ile sona erer. Eğer bu numara dosyada yeni ise, öğrencinin ismi ve kayıt yılı sorulur. Yıl için dört rakamlı bir sayı girinceye kadar devam etmeyi engelleyen do deyimine dikkat ediniz. scanf’in gerçekleştirilen başarılı dönüşüm sayısını döndürdüğünü anımsayın.

Öğrenci hakkında bütün bilgileri elde ettiğimize göre, onu dosyaya yazmamız gerekir. Satır 73’te BOS anahtarı içeren bir kayıt ararız. BOS, bir öğrenci numarası olarak, kullanıcı ne kadar çalışırsa çalışsın, giremeyeceği özel bir karakter dizisidir. (Satır 8’deki BOS’un tanımına bakıp bunun neden böyle olduğunu söyleyiniz.) BOS anahtarı daha önce eklenmiş, fakat sonra silinmiş kayıtları gösterir. Satır 74-75, dosya yoksa, yeni bir dosya açar; aksi takdirde bir değişiklik yapmaz. Satır 76, okuma-yazma erişimi için dosyayı açar. Eğer BOS bir kayıt yoksa, yeni kayıt dosyanın sonuna eklenir (Satır 78). Aksi takdirde, adresi konum içinde bulunan BOS kayda yazılır (Satır 80). Asıl yazma işlemi Satır 81’de fprintf ile yapılır. Dosyayı kapattıktan sonra ekleme sona erer.

silme silinecek öğrenci numarasını okuyarak başlar. Böyle bir öğrencinin dosya içinde olup olmadığını araştırmak için bul çağrılır. Eğer kayıt varsa, anahtarına BOS yazılır ve silme biter. Eğer yoksa, dönmeden önce, uygun bir hata mesajı basılır. Hata mesajlarının stderr’e gönderildiğinde dikkat edin.

goruntuleme, öğrenci numarasını okur ve silme’ye benzer bir şekilde kaydın bulunup bulunmadığını kontrol eder. Kayıt bulunursa, anahtar dışındaki bilgiler görüntülenir.