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ı 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 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.