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.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Değişmezler */
char const  DOSYA_ADI[] = "OGRENCI.DAT";
char const  BOS[]       = "\r\r\r\r\r\r\r";
#define     ANHTR_UZ    7
char const  AKD[]       = "%7s";
#define     ISIM_UZ     15
#define     KAYIT_BOYU  ((long)(ANHTR_UZ+ISIM_UZ+4))
char const  KKD[]       = "%7s%15s%4d";

struct kayit {
  char anhtr[ANHTR_UZ+1], isim[ISIM_UZ+1];
  int  yil;
};

/* Her fonksiyonda baştan tanımlamak yerine,
 * bu değişkenleri burada tanımlıyoruz.
 */
char   satir[2];
FILE * dg;

int dizioku             /* d içine en fazla u karakter oku */
  (char d[], int u)
{
  int  i, c;

  for (i=0; (c=getchar())!='\n'; i++)
    if (i<u)
      d[i] = c;
  for (; i<u; i++)
    d[i] = '\0';
  d[u] = '\0';
  return d[0];
}  /* dizioku */

long bul                /* Dosya içindeki ANHTR’ın yerini; */
  (char const anhtr[])  /* aksi takdirde -1L döndür.  */
{
  int   drm, kar = 1;
  long  dns;
  char  d1[ANHTR_UZ+1], d2[ANHTR_UZ+1];

  sprintf(d2, AKD, anhtr); /* soldan boşluk doldur */
  dg = fopen(DOSYA_ADI, "rb");
  if (dg == NULL)
    return -1L;
  drm = fseek(dg, -KAYIT_BOYU, SEEK_END);
  while (drm == 0 && fgets(d1, ANHTR_UZ+1, dg) != NULL  &&
    (kar=strcmp(d2,d1)) != 0)
      drm = fseek(dg, -KAYIT_BOYU-ANHTR_UZ, SEEK_CUR);
  dns = (kar == 0) ? ftell(dg)-ANHTR_UZ : -1L;
  fclose(dg);
  return dns;
}  /* bul */

void ekleme (void)
{
  long          konum;
  struct kayit  k;
  int           scd;

  puts("Eklenecek ogrenci numarasini girin:");
  dizioku(k.anhtr, ANHTR_UZ);
  if (bul(k.anhtr) == -1L) {
    puts("Ogrenci adini girin:");
    dizioku(k.isim, ISIM_UZ);
    do {
      puts("Kayit yilini girin:");
      scd = scanf("%d", &k.yil);
      dizioku(satir, 0);   /* satır sonuna git */
    } while (1!=scd || k.yil<1980 || k.yil>9999);
    konum = bul(BOS);
    dg = fopen(DOSYA_ADI, "ab");  /* dosyayı oluştur */
    fclose(dg);
    dg = fopen(DOSYA_ADI, "r+b");
    if (konum == -1L)
      fseek(dg, 0L, SEEK_END);    /* dosya sonuna git */
    else
      fseek(dg, konum, SEEK_SET); /* boş kayıda git */
    fprintf(dg, KKD, k.anhtr, k.isim, k.yil);
    fclose(dg);
    puts("Ekleme islemi tamamlandi.");
  }
  else
    fprintf(stderr, "Ogrenci zaten eklenmis!\n\a");
}  /* ekleme */

void silme (void)
{
  long  konum;
  char  anhtr[ANHTR_UZ+1];

  puts("Silinecek ogrenci numarasini girin:");
  dizioku(anhtr, ANHTR_UZ);
  if ((konum = bul(anhtr)) != -1L) {
    dg = fopen(DOSYA_ADI, "r+b");
    fseek(dg, konum, SEEK_SET);
    fprintf(dg, AKD, BOS);
    fclose(dg);
    puts("Silme islemi tamamlandi.");
  }
  else
    fprintf(stderr, "Ogrenci bulunamadi!\n\a");
}  /* silme */

void goruntuleme (void)
{
  long          konum;
  struct kayit  k;

  puts("Goruntulenecek ogrenci numarasini girin:");
  dizioku(k.anhtr, ANHTR_UZ);
  if ((konum = bul(k.anhtr)) != -1L) {
    dg = fopen(DOSYA_ADI, "rb");
    fseek(dg, konum+ANHTR_UZ, SEEK_SET);
    printf("Ogrenci adi: %s\n", fgets(k.isim,ISIM_UZ+1,dg));
    fscanf(dg, "%d", &k.yil);
    fclose(dg);
    printf("Kayit yili : %d\n", k.yil);
  }
  else
    fprintf(stderr, "Ogrenci bulunamadi!\n\a");
}  /* goruntuleme */

void kayitsay (void)
{
  dg = fopen(DOSYA_ADI, "rb");
  if (dg == NULL) {
    fprintf(stderr, "%s bulunamadi!", DOSYA_ADI);
    return;
  }
  fseek(dg,0,SEEK_END);
  printf ("Kayit sayisi: %ld\n", ftell(dg)/KAYIT_BOYU);
  fclose(dg);
}  /* kayitsay */

int menu (void)
{
  int  secenek;

  do {
    puts("\n\t1.EKLEME\n\t2.SILME\n\t3.GORUNTULEME"
      "\n\t4.KAYIT SAYISI\n\t0.CIKIS");
    printf("Seciminizi yapin:");
  } while ((secenek=dizioku(satir,1))<'0' || secenek>'4');
  putchar('\n');
  return secenek - '0';
}  /* menu */

int main (void)
{
  static void (*fd[])(void) = { (void (*)(void)) exit,
    ekleme, silme, goruntuleme, kayitsay };

  while (1)
    (*fd[menu()])();
  return 0;
}  /* main */

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

menu fonksiyonu (Satır 140-151) 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 4’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 155-156’da exit, ekleme, silme, goruntuleme ve kayitsay 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 39-57) 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 51-53’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 53 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 55’te dosyayı kapattıktan sonra, bul’un sonunda, (Satır 54’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ı. getchar kullanılarak okunan karakterler diziye konulur; en fazla u sayısı kadar karakter okunur. Dizi dolmadan satır sonuna gelirsek, kalan karakterler '\0' ile doldurulur. Satır sonuna gelmeden dizi dolarsa, satırda kalan tüm karakterler satır sonu karakterine kadar okunup atlanır. Son olarak, d’nin sonuna en az bir adet '\0' konulur. Okunan dizinin ilk karakteri fonksiyonun değeri olarak döndürülür. Bu fonksiyon standart girdide dosya sonu (EOF) kontrolü yapmamaktadır. Eğer girdide dosya sonu karakteri (unix-benzeri sistemlerde Ctrl+D, Microsoft ortamlarında Ctrl+Z) girilirse, bu fonksiyon döngüye girebilir.

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. Döngü içindeki dizioku çağrısı ise scanf’ten arta kalan karakterlerin satır sonuna kadar okunup atlanması içindir.

Öğrenci hakkında bütün bilgileri elde ettiğimize göre, onu dosyaya yazmamız gerekir. Satır 75’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 7’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 76-77, dosya yoksa, yeni bir dosya açar; aksi takdirde bir değişiklik yapmaz. Satır 78, 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 80). Aksi takdirde, adresi konum içinde bulunan BOS kayda yazılır (Satır 82). Asıl yazma işlemi Satır 83’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.

kayıtsay fonksiyonu dosya içindeki kayıt sayılarını hesaplayıp yazar. Bu sayıya boş kayıtlar da dahildir. Boş olmayan kayıtların düşülmesi için tüm dosyanın baştan sona kadar okunması gerektiğine dikkat edin.