DERS: PROGRAMLAMA DİLLERİ 1 DÖNEM: 2002-2003 Güz yarı yılı KONU 7: DOSYA İŞLEME ( File Processing ) HEDEFLER: 1- Dosyaları yaratabilme, okuyabilme, yazabilme ve güncelleyebilme 2- Sıralı erişim dosyalarını tanıma 3- Rasgele (doğrudan) erişim dosyalarını tanıma 4- Bu dosyalar üzerinde G/Ç işlemleri yapma İÇERİK: Veri hiyerarşisi Sıralı erişim dosyası yaratma Sıralı erişim dosyasından okuma Sıralı erişim dosyalarını güncelleme Rasgele erişimli dosyalar Sıralı erişim dosyalarını yaratma Sıralı erişim dosyalarına yazma Sıralı erişim dosyasından sıralı okuma VERİ HİYERARŞİSİ Bilgisayarlarda her sayı ya da alfabetik karakterin 0 ve 1 lerle temsil edildiğini biliyorsunuz. Karakterlerin bitlerden oluşması gibi alanlar da karakter (veya baytlardan) oluşur. Alan bir anlamı olan bir grup karakterdir. Bir kayıt ise (C++ da class, C de struct ) birden fazla alandan meydana gelir. Örneğin bir muhasebe sisteminde bir çalışana ait bir kayıtta aşağıdaki alanlar bulunabilir. 1- Sicil no 2- Adı 3- Adresi 4- Ücreti 5- Vergi oranı Dolayısıyla bir kayıt birbiriyle alakalı alanlardan meydana gelir. 100000 kaydın olduğu bir dosyadan bir kayda erişmek istediğimizi düşünelim. Genellikle bu erişimi kolaylaştırmak için kaydın bir alanı anahtar ( record key ) olarak belirlenir ve erişim esnasında kullanılır. Yukardaki örnekte sicil no kayıt anahtarı olarak kullanılabilir. Şirketlerin birden fazla dosyaları vardır: muhasebe, alacak hesapları, stok, personel, vb gibi. Birbiriyle ilgili dosyaların bir veri tabanı programı kullanılarak veri tabanında tutulması pek çok kolaylık sağlar: veri bütünlüğü, veri tutarlılığı, bakım kolaylığı, mükerrer veri olmaması gibi. Veri tabanlarını yaratmak ve erişimi sağlamak, bakımlarını yapmnak için Veri Tabanı Yönetim Sistemi (DBMS) denen yazılımlar mevcuttur. 1
C++ bir dosyayı baytlar sırası ( sequence of bytes ) olarak görür. Her dosya, dosya sonunu belirten bir işaret ile biter. C++ da dosya işlemleri yapabilmek için <iostream> ve <fstream> başlık dosyaları dahil edilmelidir. <fstream> başlık dosyası ifstream (giriş işlemleri için) ve ofstream (çıkış işlemleri için) gerekli sınıf ( class ) tanımlarını içerir. SIRALI DOSYA YARATMA Örnek: Her müşteri için hesap no, müşter, adı, bakiyesi, vb. tutulacak. Program, kullanıcının kayıtları hesap no sırasında gireceğini varsaymaktadır. // fig. 14.4 s.761 // create a sequential file #include <iostream> using std::cout; using std::cin; using std::ios; using std::cerr; using std::endl; #înclude <fstream> using std::ofstream; #include <cstdlib> ofstream MusteriDosyasi( musteri.dat, ios::out ); // musteri.dat çıkış // için açılır (open) if (!MusteriDosyasi) // dosya düzgün açılırsa 0 döndürür cerr << Dosya açılamadı << endl; exit(1); // sıfır harici bir değer programın bir hata sonucu bittiğini gösterir. Bu değer op.sys e... cout << Hesap no, Ad, Bakiye bilgilerini giriniz.\n << Girişi bitirmek için EOF giriniz.\n? ; int hesap; char ad[20]; double bakiye; while (cin >> account >> ad >> bakiye ) //EOF olmadıkça bu koşul doğrudur MusteriDosyasi << hesap << << ad << << bakiye << \n ; cout <<? ; return 0; // EOF olunca main biter. Musteri.dat dosyası kapatılır. Hesap no, Ad, Bakiye bilgilerini giriniz. Girişi bitirmek için EOF giriniz.? 100 Jones 24.98? 200 Doe 345.67? 300 White 0.00? 400 Stone 42.16? 500 Rich 224.62? ^Z 2
Dosya açma durumları ( file open modes ) ios::app ios::in ios::out ios::trunc ios::binary dosyanın sonuna ekle dosyayı giriş için aç dosyayı çıkış için aç dosyanın içeriğini at dosyayı ikili (metin olmayan) giriş çıkış için aç SIRALI DOSYADAN OKUMA YAPMA ( reading from sequential access file ) // fig. 14.7 s. 766-767.. #include <fstream>.. void satiryazdir(int, const char * const, double); ifstream MusteriDosyasi( musteri.dat, ios::in ); da olur // ios::in konulmasa if (!MusteriDosyasi) cerr << Dosya açılamadı\n ; exit(1); int hesap; char ad[20]; double bakiye; cout << setiosflags( ios::left ) setw( 10) << HESAP NO << setw(13) << ADI << BAKİYE\n << setiosflags( ios::fixed ios::showpoint ); while ( MusteriDosyasi >> hesap >> ad >> bakiye ) satiryazdir(hesap, ad, bakiye); return 0; ad is a char pointer to a const char void satiryazdir(int hes, const char * char ad, double bak) cout << setiosflags( ios::left ) setw( 10) << hes << setw(13) << ad << setw(7) << setprecision(2) << resetiosflags( ios::fixed ) << bal << \n ; HESAP NO ADI BAKIYE 100 Jones 24.98 200 Doe 345.67 300 White 0.00 400 Stone -42.16 500 Rich 224.62 ----------------------------------------------------------------------------------------------------------- ss. 768-769-770-771 atlandı 3
SIRALI ERİŞİM DOSYALARININ GÜNCELLENMESİ Yukarıda yaratılmış olan musteri dosyasındaki bir veri değiştirilmek (güncellenmek) istendiğinde diğer verilerin bozulma riski vardır. Örneğin White ismini Worthington yapmak istersek, bu isim orijinal isim olan Whhite dan 6 karakter daha uzun olduğundan kendisinden sonra gelen kaydı bozar. Bunu önlemek için 300 White 0.00 a kadar olan kayıtlar yeni bir dosyaya kopyalanıp 300 Worthington 0.00 yazılıp geri kan kayıtlar da yeni dosyaya aktarılabilir. Bir seferde çok sayıda kayıt güncellenecekse bu yöntem kabul edilebilir. RASGELE ERİŞİMLİ DOSYALAR Belirli bir kaydın hemen bulunmasu gerektiği durumlarda kullanılır. Bankada belirli bir müşteri hesabına (dosyada daha önce gelen müşteri kayıtları okunmadan ) erişilmesi, kayıt sırasında belirli bir öğrenci numarasına daha önceki tüm numaralar okunmadan erişilmesi gibi. C++ dosyalar üzerinde bir yapı oluşturmadığı için bunu programcı yapmalıdır. Rasgele erişimli dosya yaratmak için pek çok teknik vardır. Bunların en kolayında, tüm kayıtların sabit uzunluklu olması tercih edilir / gerekir. Kayıtların sabit uzunluklu olması aranan bir kaydın dosyanın başına göre kaçıncı bayttan ( byte offset ) başladığını hesaplamamızı mümkün kılar. RASGELE ERİŞİMLİ DOSYALARI YARATMAK Bu tür dosyalara genellikle class veya struct ile tanımlanmış bir grup alanı bir anda yazdırırız, tek bir sayıyı değil. Örnek: 100 müşteri için 100 sabit uzunluklu kayıt tutulacak. Her kayıtta hesap no (kayıt anahtarı), ad, soyad, bakiye alanları bulunacak. Program, bir hesabı güncelleyebilmeli, yeni hesap ekleyebilmeli, hesap silebilmeli ve tüm hesapları listeleyebilmeli. Aşağıda rasgele erişimli bir dosyanın nasıl açıldığını, struct ile kayıt yapısının nasıl tanımlandığı (musveri.h adlı başlık dosyasında) gösterilmektedir. Program öncelikle 100 kayda boş struct lar yazarak kayıtların ik değer atamalarını yapmaktadır. Boş struct da hesap no için 0, ad ve soyad için null dizgi, ve bakiye için 0.0 vardır. // musveri.h // struct musteri nin tanımı #ifndef MUSVERI_H #define MUSVERI_H Header file musveri.h struct musteri aşağıdaki tüm programlarda int hesapno; bu başlık dosyası kullanılacak char soyad[15]; char ad[10]; double bakiye; ; #endif 4
// fig.14.11 s.774 // creating random-access file sequentially #include <iostream> using std::cerr; using std::endl; using std::ios; #include <fstream> using std::ofstream; #include <cstdlib> #include musveri.h ofstream kredi( kredi.dat, ios::binary); // kredi.dat file is associated with // ofstream object kredi if (!kredi ) cerr << kredi dosyası açılamadı << endl; exit(1); musteri bosmusteri = 0,,, 0.0 ; for ( int i = 0; i <100, i++ ) kredi.write( reinterpret_cast<const char *>( &bosmusteri ), sizeof(musteri)); return 0; kredi.write( reinterpret_cast<const char *>( &bosmusteri ), sizeof(musteri)); cümlesinin anlamı: kredi.dat ile ilişkilendirilmiş kredi nesnesine ( object ) musteri yapısı büyüklüğündeki bosmusteri yapısını yazar. NOTE THAT the first argument of write function must be of type const char *. However, the data type of &bosmusteri is musteri *. To convert &bosmusteri to the appropriate pointer type, the expression reinterpret_cast<const char *>( &bosmusteri ) uses the cast operator reinterpret_cast to convert the address of bosmusteri to a const char *, so the call to write compiles without issuing a syntax error. 5
RASGELE-ERİŞİMLİ DOSYAYA VERİLERİ RASGELE YAZMA Aşağıdaki program kredi.dat dosyasına veri yazar. Dosyaya verileri tam yerlerine yazmak için ofstream fonksiyonları write ve seekp kullanır. Fonksiyon seekp, dosyaya yazıacak kaydın tam yerini ayarlar ve write verileri yazar. // fig.14.12 s.775 // writing to a random access file #include <iostream> using std::cerr; using std::endl; using std::ios; using std::cin; using std::cout; #include <fstream> using std::ostream; #include <cstdlib> #include musveri.h ofstream kredi( kredi.dat, ios::binary); // kredi.dat file is associated with // ofstream object kredi if (!kredi ) cerr << kredi dosyası açılamadı << endl; exit(1); cout << Hesap numarasını ( 1 ile 100 arasında )giriniz. << Bitirmek için 0 giriniz.\n? ; musteri mus; cin >> mus.hesapno; while ( mus.hesapno > 0 && mus.hesapno <= 100 ) cout << Ad, soyad ve bakiye giriniz.\n? ; cin >> mus.ad >> mus.soyad >> mus.bakiye; // kredi dosyasında gerekli adrese konumlan kredi.seekp( ( mus.hesapno 1 ) * sizeof (musteri) ); kredi.write( reinterpret_cast<const char *>(&mus), sizeof(musteri) ); cout << Hesap numarasını giriniz:\n? ; cin >> mus.hesapno; return 0; 6
Hesap numarasını ( 1 ile 100 arasında )giriniz.bitirmek için 0 giriniz.? 37 Ad, soyad ve bakiyeyi giriniz:? Bakır Deniz 0.00 Hesap numarasını giriniz:? 29 Ad, soyad ve bakiyeyi giriniz:? Baki Naci 24.54 Hesap numarasını giriniz:? 96 Ad, soyad ve bakiyeyi giriniz:? Sarı Sami 34.98 Hesap numarasını giriniz:? 88 Ad, soyad ve bakiyeyi giriniz:? Simit Davut 258.34 Hesap numarasını giriniz:? 0 kredi.seekp( ( mus.hesapno 1 ) * sizeof (musteri) ); Parantez içindeki aritmetik ifade, yazılacak yadın, dosya içine baştan kaçıncı bayttan başlacağını hesaplar. Musteri 50 bayt ise, hesapno 4 ise, hesapno 4 olan kayıt 200.bayttan itibaren yazılacak demektir. Dosya konumlandırma işaretçisi ( file position pointer ) bu adrese konumlanır. RASGELE-ERİŞİMLİ BİR DOSYAYI SIRALI OKUMA s.777 Daha önce yarattığımız rasgele-erişimli dosyayı sıralı okuyacağız yani ilk kayıttan başlayarak sırayla her kaydı. Öncelikle birkaç önmeli konuya bakalım: ifstream fonksiyonu read belirlenen bayt uzunluğundaki veriyi dosyadan okur. Örneğin kredi.read(reinterpret_cast<char *>(?mus ), sizeof(musteri) ); = kredi dosyasından musteri uzunluğunda baytı okuyup mus yapısına koyar. NOT: read fonksiyonunun ilk argümanı char * olmak durumundadır. Aşağıdaki program kredi.dat dosyasındaki bütün kayıtları sırayla okur ve okunan her bir kaydın veri içerip içermediğine bakar. Döngü koşulunu inceleyecek olursak: while ( kredi &&!kredi.eof() ) dosya sonuna gelip gelinmediğini anlamak için eof fonksiyonunu kullanır. Dosya sonuna gelindiyse döngüden çıkılır. İkinci bir koşul da && işaretinin sol tarafında yer alan kredi sözcüğüyle belirtilmiştir. kredi dosyası okunurken bir hata oluşursa bu noktaya false döner ve yine döngüden çıkılır. OutputLine fonksiyonunun 2 argümanına dikkat edelim: 1- ostream objesi 2-musteri yapısı (structure). Görüldüğü gibi, cout gibi bir ostream objesini argüman olarak vermek mümkündür. 7
Program: Rasgele erişimli dosyasını sıralı okunması // fig. 14.14 reading a random access file sequentially #include <iostream> using std::cerr; using std::endl; using std::ios; using std::cout; #include <iomanip> using std::setprecision; using std::setiosflags; using std::resetiosflags; using std::setw; #include <fstream> using std::ifstream; using std::ostream; #include <cstdlib> #include musveri.h ; void outputline( ostream&, const clientdata & ); ifstream kredi( kredi.dat, ios::in); // kredi.dat file is associated with // ifstream object kredi if (!kredi ) cerr << kredi dosyası açılamadı << endl; exit(1); cout << setioflags( ios:: left ) << setw (10) << HESAP NO << setw(16) << SOYADI << setw(11) << ADI << resetiosflags(ios::left) << setw(10) << BAKİYE << endl; musteri mus; kredi.read(reinterpret_cast<char *>( &mus ), sizeof( musteri )); while ( kredi &&!kredi.eof() ) if (mus.hesapno!= 0 ) outputline ( cout, mus); kredi.read(reinterpret_cast<char *>( &mus ), sizeof( musteri )); return 0; void outputline( ostream &output, const musteri &c) output << setiosflags( ios::left ) << setw (10) << c.hesapno << setw(16) << c.soyad 8
<< setw(11) << c.ad << setw(10) << setprecision(2) << resetiosfalgs( ios::left) << setiosflags( ios::fixed ios::showpoint) << c.bakiye << \n ; HESAP NO SOYADI ADI BAKİYE 29 Baki Naci -24.54 33 Dakik Selin 314.33 37 Bakır Deniz 0.00 88 Simit Davut 258.34 96 Sarı Sami 34.98 9