Ana sayfa > Programlama > C++ Programlama > C dosya Giriş/Çıkış (I/O)

C dosya Giriş/Çıkış (I/O)

► Detaylı anlatım

Bu bölümde, C temelli dosya giriş ve çıkış işlemlerini incelemeye çalışacağız. Bu işlemler üzerinde çalışmaya başlamadan önce, akış ve dosya kavramlarını ayrı ayrı ele alalım:

Akış

Dosya sistemi farklı donanım elemanları ile çalışma olanağı sağlar. Bu donanım elemanı bir klavye, ekran, disk sürücü veya bilgisayarda kullanılan bir port olabilir.

Dosya giriş çıkış işlemlerinin temelini, bilgisayarı oluşturan aygıtlar için mantıksal bir arabirim görevi yapan, Akış adı verilen bir kavram oluşturur. Genel bir ifade ile, akış bir dosyaya ulaşmak için kullanabileceğiniz mantıksal bir ara birimdir. Ancak burada kullanılan dosya ifadesi, diske kaydedilebilen bir dosyayı ifade etmekle birlikte, ekran ve klavye gibi donanım elemanlarından birini de ifade edebilir. Dosyaların yapıları veya kapasiteleri farklı olabilir, ancak akışların tamamı aynıdır. Bu özellik, donanım elemanlarını benzer şekilde tanımlama olanağı sağlar. Yani, dosya sistemi bu farklı donanım elemanlarını akış vasıtası ile benzer elemanlar gibi kullanabilir. Böylece, disk üzerindeki bir dosyaya yazı yazmak için kullandığınız fonksiyonları, aynı zamanda ekrana ve yazıcıya yazı yazmak için de kullanabiliriz.

Akış, stdio.h başlık dosyası içinde tanımlı FILE adlı yapı veri türünden bildirimi yapılan bir işaretçi değişkeni ile kullanılan bir dosya veya fiziksel bir cihazdır.

Bir akışın dosya (fiziksel aygıtlar dahil) ile bağlantısını sağlamak için açma işlemi, dosyadan bağlantısını kesmek için kapama işlemi uygulanır. Dosya açıldıktan sonra, dosyadan programımıza bilgi aktarabilir veya dosya içeriğini değiştirebiliriz.

2 tür akış vardır:

1. Metin akışı
2. İkili sistem akışı

Metin Akışı

Metin akışı, herhangi bir metin editörü ile oluşturulan DOS metin dosyaları ile kullanılır. Yani, metin akışı ASCII karakterlerle birlikte kullanılır. Metin akışı, metin dosyalarını satırları tek bir yeni satır karakteri (ASCII 10 = Line feed = LF = \n) ile ayrılmış olarak dikkate alır. Ancak, DOS metin dosyaları, her satırı arasında bir yeni satır (ASCII 10 = Line feed = LF = \n) ve bir satır başı (ASCII 13 = Carriage return = CR = \r) karakteri olmak üzere toplam iki karakter ile, diske kaydedilir. Metin akışına diskten bir dosya okurken yeni satır ve satır başı karakterleri tek bir yeni satır karakterine çevrilir. Metin akışından diske bir dosyayı kaydederken ise, tek bir yeni satır karakteri, yeni satır ve satır başı karakterlerine çevrilir. Bu nedenle, metin akışına gönderilen ifadelerle dosyaya yazılan ifadeler birebir aynı olmayabilir.

İkili sistem akışı

İkili sistem akışında ise, herhangi bir karakter dönüşümü yapılmaz. Yani, ikili akıştan diske bir dosya kaydederken veya diskten ikili bir dosya aktarırken, dosya içinde yer alan karakterlerde herhangi bir değişiklik yapılmaz. İkili sistem akışına gönderilen ifadelerle dosyaya gönderilen ifadeler tamamen birbirinin aynıdır.

Aktif konum

Aktif konum, bir dosyaya yapılacak bir sonraki girişin yapılacağı konumdur. Örneğin, 80 byte uzunluğunda bir dosyanın ilk erişimde 20 byte'ının okunduğunu kabul edelim. Bir sonraki dosya okuma işlemi 21 nci byte dan başlayacaktır. Bu durumda, 21 nci byte aktif konum olarak kabul edilir. Başka bir ifade ile, dosya işlemlerinde yapılan bir sonraki işlem dosya başından değil kalınan yerden devam eder.

Dosya sistemi

Dosya sisteminde kullanılan dosya kavramı, normal bir disk dosyası için kullanılabileceği gibi, ekran, klavye gibi bilgisayar parçaları içinde kullanılabilir.

Bir dosya açıldığında, aktif konum göstergesi dosya başlangıcını gösterir. Dosyadan her okuma ve yazma işleminde, aktif konum göstergesi otomatik olarak bir sonraki konuma geçer.

Standart kütüphane fonksiyonlarını kullanarak, dosya aktif konumunu başa alabilir veya belirli bir konuma taşıyabiliriz.

Standart akışlar

Bir programı çalıştırdığımızda, program aşağıda gösterilen standart metin akışlarını otomatik olarak açar. Bu metin akışları yoluyla, program bilgisayara bağlı aygıtlara (Klavye, ekran, yazıcı gibi...) tıpkı bir dosya gibi erişebilir:

stdin  : Klavyeden veri okur.

stdout : Verileri ekrana yollar.
stderr : Verileri ekrana yollar.

stdaux : Seri port'tan veri alışverişi yapar.

stdprn : Verileri yazıcıya yollar.

Bu standart akışlar FILE işaretçileri olduğundan, FILE değişkenini kullanan bütün fonksiyonlarla kullanılabilir.

Standart akışlar birer değişken olarak kabul edilmezler. fopen() ve fclose() fonksiyonları tarafından işleme tabi tutulamazlar. Bu akışları kullanabiliriz, ama değiştiremeyiz.

Standart akışlar yoluyla konsol ve dosya giriş/çıkış fonksiyonlarını birbirinin yerine kullanabiliriz. Aslında temel olarak bu fonksiyonlar arasında pek büyük bir fark yoktur.

Şimdi öğrendiklerimizi bir örnek üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstring>

int main(void)
{
  char cdizi[40];

  strcpy(cdizi, "Bilgisayar")
  printf(cdizi);
  
  return 0;
}

Program, aşağıdaki karakter dizisini ekrana yazar:

Bilgisayar

Yukarıdaki programın derlenmiş adının deneme.exe olduğunu kabul edelim. Bu durumda, komut satırında aşağıdaki satırı yazarsak, çıkış (stdout) deneme.txt adlı dosyaya yönlendirildiğinden, program çıktısı deneme.txt adlı dosyaya aktarılır:

deneme > deneme.txt

Komut satırında elde ettiğimiz .exe dosyayı > işareti yoluyla stdout akışı ile kullandığımızda, .exe dosyanın ekrana yazacağı değerler bir dosyaya yazılır.

Komut satırında elde ettiğimiz .exe dosyayı < işareti yoluyla stdin akışı ile kullandığımızda, .exe dosyaya klavyeden girilmesi beklenen değerler bir dosyadan okunur.

Dosya açma

Şimdi, dosya açma ve kapama işlemleri ile dosyadan veri okunması ve dosyaya veri yazılması işlemlerini incelemeye çalışacağız.

Bir dosyayı açmak ve bu dosyayı ilgili akışla birleştirmek için aşağıda genel yapısı verilen fopen() fonksiyonu kullanılır:


FILE *fopen(char *dosya-adı, char *mod);

dosya-adı ifadesi açılacak dosya adını ve mod ifadesidosya açılış şeklini gösterir.

fopen() fonksiyonu stdio.h başlık dosyasını kullanır. dosya-adı ifadesi ile gösterilen ve fopen() fonksiyonuna argüman olarak geçirilen dosya adı işletim sisteminde kullanılan geçerli bir dosya adı olmalıdır. fopen() fonksiyonuna argüman olarak geçirilen ve mod ile gösterilen karakter dizisi ise dosyanın ne şekilde açılacağını belirler. Burada, mod ifadesi yerine kullanılabilecek değerler aşağıdaki tabloda yer almaktadır:

Mod Anlamı
r Okuma için bir metin dosyası açar. Dosya mevcut olmalıdır.
w Yazma için bir metin dosyası oluşturur. Aynı isimde bir dosya zaten mevcut ise, içeriği silinir.
a Bir metin dosyasını ekleme yapmak için açar. Dosya yok ise oluşturulur.
r+ Okuma ve yazma için bir metin dosyası açar. Dosya mevcut olmalıdır.
w+ Okuma ve yazma için bir metin dosyası oluşturur.
a+ Okuma ve ekleme için bir metin dosyası açar.
rb Okuma için bir ikili sistem dosyası açar. Dosya mevcut olmalıdır.
wb Yazma için bir ikili sistem dosyası oluşturur. Aynı isimde bir dosya zaten mevcut ise, içeriği silinir.
ab Bir ikili sistem dosyasını ekleme yapmak için açar. Dosya yok ise oluşturulur.
r+b Okuma ve yazma için bir ikili sistem dosyası açar. Dosya mevcut olmalıdır.
w+b Okuma ve yazma için bir ikili sistem dosyası oluşturur.
a+b Okuma ve ekleme için bir ikili sistem dosyası açar.

Eğer dosya açma işlemi başarılı olarak sonuçlanırsa, fopen() fonksiyonu geçerli bir dosya işaretçisi geri döndürür. FILE, stdio.h dosyası içinde tanımlanmış olup dosyanın boyutu, aktif konumu ve giriş modları gibi dosya hakkındaki değişik değerleri içeren bir yapıdır. Yapı tek bir isimle giriş yapılan bir grup değişkenden oluşur. fopen() fonksiyonu dosya açma işlemi ile birlikte dosya ile ilgili yapıya bir işaretçi geri döndürür. Bu işaretçi diğer fonksiyonlarla dosya üzerinde işlem yapmak için kullanılır.

Eğer fopen() fonksiyonu normal olarak çalışmazsa NULL bir işaretçi geri döndürür. NULL stdio.h dosyasında null bir işaretçi olarak tanımlanmıştır. fopen() fonksiyonunun geçerli bir dosya işaretçisi geri döndürmesi gerekir. Döndürülen değerin NULL olmadığından emin olmak için döndürülen değeri kontrol etmek gerekir.

Bir dosyanın açılmasını bir örnek üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>

int main(void)
{
  FILE *fp;

  if ((fp = fopen("dosya.txt", "r")) == NULL) {
      printf("Dosya açma hatası!\n");
      exit(1);
  }
  
  return 0;
}

Program, aşağıdaki satırı ekrana yazar:

Dosya açma hatası!

Programda kullanılan fopen() fonksiyonu NULL bir değer döndürür (dosya.txt adlı bir dosya olmadığından). Böylece, if koşulu sağlandığından, printf() işlem satırında yer alan karakter dizisini ekrana yazar. exit() fonksiyonu programın sona ermesini sağlar.

• Mevcut olmayan bir dosyayı sadece okuma (r) için açmak istediğimizde, fopen() fonksiyonu hata verir.

• Mevcut olmayan bir dosyayı ekleme yapmak (a) için açmak istediğimizde, program bir dosya oluşturur. Dosya zaten mevcut ise girdiğimiz bilgiler dosyanın sonuna eklenir. Dosyada önceden yer alan bilgiler ise değişmez.

• Mevcut olmayan bir dosyayı yazma işlemi (w) için açmak istediğimizde, program bir dosya oluşturur. Dosya zaten mevcutsa, içindeki bilgilerin tamamı silinir ve tamamen yeni bir dosya oluşturulur.

• r+ modunu kullandığınızda, dosya mevcut değilse, yeni bir dosya oluşturulmaz. Aynı koşullarda w+ modu ise yeni bir dosya oluşturur. Dosya mevcut ise, dosyayı w+ modu ile açmamız dosyanın içeriğini tamamen siler, fakat r+ modu ile açmak silmez.

Dosya kapatma

Bir dosyayı kapatmak için aşağıda genel yapısı verilen fclose() fonksiyonu kullanılır:


int fclose (FILE *fp);

fp ifadesi fopen() ile kullanılan dosya işaretçisini gösterir. fclose() fonksiyonu fp ile bağlantılı dosyayı kapatır ve dosyanın akış ile olan bağlantısını keser.

Sistemin etkinliğini artırmak için dosya sistem uygulamalarının çoğu veriyi diske her defasında bir sektör olmak üzere yazarlar. Bu durumda, bir sektör değere erişilene dek, veri tampon belleğe aktarılır daha sonra diske yazılır. fclose() fonksiyonunu çağırdığımızda, fclose() fonksiyonu tam dolmamış tampon bellek içeriğini otomatik olarak diske yazar. Bu işleme TAMPON BELLEK BOŞALTMA adı verilir.

Eğer fclose() fonksiyonunu uygun olmayan argümanlarla çağırırsak, dosya sistemimiz zarar görebilir, tekrar elde edilmesi mümkün olmayan veri kayıpları meydana gelebilir.

fclose() fonksiyonu normal olarak çalışırsa 0, bir hata meydana gelirse EOF değerini geri döndürür.

fgetc() ve fputc() fonksiyonları

Bir dosya açıldıktan sonra, açılış moduna bağlı olarak, aşağıda genel yapısı verilen iki fonksiyonu kullanarak dosyaya bir karakter yazabilir veya dosyadan bir karakter okuyabiliriz:


int fgetc (FILE *fp);
int fputc (int id, FILE *fp);

fgetc() fonksiyonu, fp ile tanımlanan dosyadaki bir sonraki byte'ı unsigned char olarak okur ve int bir değer olarak geri döndürür. fgetc() fonksiyonu bir hata durumunda ve dosya sonuna geldiğinde int bir değer olan EOF değerini döndürdüğü için, geri döndürülen değerin kontrolü amacıyla fgetc() fonksiyonu int bir değer geri döndürür. Ancak, fgetc() fonksiyonunun geri döndürdüğü değeri int bir değişkene atamamız şart değildir. Geri döndürülen değeri bir karakter değişkene de atayabiliriz.

fputc() fonksiyonu id değişken değeri olan byte'ı fp ile gösterilen dosyaya unsigned char olarak yazar. id değişkenini, int bir değer olarak tanımlandığı halde, char bir değer olarak çağırabiliriz. Dosyaya yazma işlemi başarılı olursa, fputc() fonksiyonu yazılan karakteri aksi takdirde EOF değerini geri döndürür.

fgetc() yerine getc() ifadesini, fputc() fonksiyonu yerine ise putc() ifadesini kullanabiliriz. Sonuçta, fonksiyonlar tarafından yapılan işlem tamamen birbirinin aynıdır.

Şimdi, öğrendiklerimizi bir örnek üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>

int main(void)
{
  FILE *fp;
  int id;

  if ((fp = fopen ("dosya.txt", "w")) == NULL) {
      printf("Dosya açma hatası!");
      exit(1);
  }

  for (id=0; id<10; id++) fputc ('a', fp);
  fclose(fp);

  if ((fp = fopen ("dosya.txt", "r")) == NULL) {
      printf("Dosya açma hatası!");
      exit(1);
  }

  for (id=0; id<10; id++) printf("%c", fgetc(fp));
  
  fclose(fp);
  
  return 0;
}

Program, aşağıdaki satırı ekrana yazar:

aaaaaaaaaa

Program, fputc() fonksiyonunu kullanarak, her defasında bir karakter olmak üzere, 10 adet 'a' harfini w modunda açılan dosyaya yazar. Bu işlemi bitirdikten sonra, dosyayı kapatır. Dosyayı r modunda açtıktan sonra, her karakteri birer birer dosyadan okuyarak ekrana yazar.

feof() ve ferror() fonksiyonları

fgetc() fonksiyonu, aşağıda belirtilen 2 farklı durumda, EOF değerini geri verir:

1. Bir hata meydana geldiğinde
2. Dosya sonuna gelindiğinde

fgetc() fonksiyonu EOF değerini geri verdiğinde, yukarıdaki durumlardan hangisine göre bu değeri geri verdiğini bilemeyiz. EOF değerinin fgetc() fonksiyonu tarafından hangi nedenle geri verildiğini anlayabilmek amacıyla aşağıda ana yapıları verilen feof() ve ferror() fonksiyonlarını programlarımızda kullanabiliriz:


int feof (FILE *fp);
int ferror (FILE *fp);

Eğer fp ile gösterilen dosyanın sonuna gelinmişse, feof() fonksiyonu 0 olmayan bir değer, aksi takdirde 0 değerini geri verir. feof() fonksiyonunu hem ikili sistem hem de metin dosyaları ile birlikte kullanabiliriz.

ferror() fonksiyonu fp ile gösterilen dosyada bir hata bulursa 0 olmayan bir değer, aksi takdirde 0 değerini geri verir.

ferror() fonksiyonu sadece yaptığınız en son dosya girişinden sonraki hata kontrolünü sağladığı için, her dosya işleminden sonra bu fonksiyonu tekrar çağırmanız gerekir.

Şimdi, feof() ve ferror() fonksiyonlarının kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>

int main(int argc, char *argv[])
{
  FILE *fp1, *fp2;
  char cd;

  if (argc!=3) {
      printf("Kullanım: deneme <Kaynak dosya> <Hedef dosya>");
      exit(1);
  }

  if ((fp1=fopen(argv[1], "rb")) == NULL) {
      printf("Kaynak dosya açılamadı!\n");
      exit(1);
  }
  if ((fp2=fopen(argv[2], "wb")) == NULL) {
      printf("Hedef dosya açılamadı!\n");
      exit(1);
  }

  while (!feof(fp1)) {
     cd = fgetc(fp1);
     if (ferror(fp1)) {
         printf("Kaynak dosyadan okuma hatası!\n");
         exit(1);
     }
     if (!feof(fp1)) fputc (cd, fp2);
     if (ferror(fp2)) {
         printf("Hedef dosyaya yazma hatası!\n");
         exit(1);
     }
  }

  fclose (fp1);
  fclose (fp2);
  
  return 0;
}

Program, komut satırından argüman olarak verilen kaynak dosyanın içeriğini yine argüman olarak verilen hedef dosyaya yazar. Hedef dosya mevcut değilse oluşturur, mevcut ise önceki içeriğini tamamen siler. Program içinde tam bir hata kontrolü sağlanır.

fputs(), fgets(), fprintf() ve fscanf() fonksiyonları

Metin dosyaları ile çalışırken aşağıda genel yapıları verilen dört fonksiyonla farklı veri türleri ile işlemler gerçekleştirebiliriz:


int fputs (char *cdizi, FILE *fp);
char *fgets (char *cdizi, int id, FILE *fp);

int fprintf (FILE *fp, char *kontrol dizisi,...);
int fscanf (FILE *fp, char *kontrol dizisi,...);

fputs() fonksiyonu, cdizi ifadesi ile gösterilen karakter dizisini fp ile gösterilen dosyaya yazar. Normal olarak çalışırsa 0 olmayan bir değer, aksi takdirde EOF değerini geri verir. cdizi karakter dizisini sona erdiren NULL değeri dosyaya yazmaz.

fgets() fonksiyonu, fp ile gösterilen dosyadan okuduğu karakterleri cdizi ifadesi ile gösterilen karakter dizisine atar. Normal olarak çalışırsa cdizi karakter dizisini, aksi takdirde NULL bir işaretçi geri verir. Fonksiyon prototipi içinde verilen id değişkeni ise, dosyadan okunacak karakter sayısını belirler. fgets() fonksiyonu, id-1 kadar karakter okuduğu veya bir yeni satır karakteri ile karşılaştığı zaman, okuma işlemine son verir.

fprintf() fonksiyonu tıpkı printf() fonksiyonu gibi çalışır. Tek fark fprintf() fonksiyonunun ekran yerine dosyalarla çalışmasıdır. Normal olarak çalıştığında dosyaya yazdığı byte sayısını, hata durumunda ise EOF değerini geri verir.

fscanf() fonksiyonu ise tıpkı scanf() fonksiyonu gibi çalışır. Tek fark fscanf() fonksiyonunun ekran yerine dosyalarla çalışmasıdır. Normal olarak çalıştığında, okuduğu ve değişkenlere yüklediği alan sayısını geri verir. Geri verdiği değer değişkenlere yüklenmeyen alanları içermez. Eğer fscanf() fonksiyonu dosya sonunu okursa EOF değerini, hiç bir alan yüklemezse 0 değerini geri verir.

Şimdi, bu dört fonksiyonun kullanılmasını örnekler üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>
#include <cstring>

char* bg_fgets(char *str, int count);

int main(int argc, char *argv[])
{
  FILE *fp;
  char cdizi[80];

  if (argc!=2) {
      printf("Kullanım: deneme <Dosya adı>");
      exit(1);
  }

  if ((fp=fopen(argv[1], "w")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  do {
     printf("Bir karakter dizisi giriniz: ");
     bg_fgets(cdizi, 80); // fgets() fonksiyonu ile klavyeden karakter okuma
     strcat(cdizi, "\n");
     if (*cdizi!='\n') fputs(cdizi, fp);
  } while (*cdizi!='\n');

  fclose (fp);
  if ((fp=fopen(argv[1], "r")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  do {
     if (fgets(cdizi, 80, fp)) printf(cdizi); // fgets() fonksiyonu ile dosyadan karakter okuma
  } while (!feof(fp));

  fclose (fp);

  return 0;
}

// fgets() fonksiyonu ile klavyeden okunan karakter dizisi girilebilecek maksimum karakter sayısından az olduğunda 
// karakter dizisine '\0' karakterinden hemen önce otomatik olarak eklenen '\n' karakteri yerine '\0' karakteri koyar.
char* bg_fgets(char *str, int count)
{
  const char *s;

  fgets(str, count, stdin);

  for (s=str; *s && *s!='\n'; ++s);

  if ((s-str) < (count-1)) *(str+(s-str)) = '\0';

  return str;
}

Pprogram, fputs() ve fgets() fonksiyonlarının çalışma şeklini göstermektedir. Klavyeden girilen karakter dizilerini, komut satırından argüman olarak verilen dosyaya yazar. Bir karakter dizisi girilirken ENTER tuşuna basıldığında, dosyaya yazma işlemi sona erer ve dosya kapanır. Sonra, program dosyayı tekrar açar. Girdiğiniz karakter dizilerini sıra ile okuyarak ekrana yazar. bg_fgets() fonksiyonu, fgets() fonksiyonu ile klavyeden okunan karakter dizisi girilebilecek maksimum karakter sayısından az olduğunda karakter dizisine '\0' karakterinden hemen önce otomatik olarak eklenen '\n' karakteri yerine '\0' karakteri koymak için kullanılır.


#include <cstdio>
#include <cstdlib>

int main(int argc, char *argv[])
{
  FILE *fp;
  char cdizi[40];
  double dd;
  int id;
  char cd;

  if (argc!=2) {
      printf("Kullanım: deneme <Dosya adı>");
      exit(1);
  }

  if ((fp=fopen(argv[1], "w")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  fprintf (fp,"%f %d %s %c", 654.123, 9852, "Bilgisayar", 'A');
  fclose (fp);

  if ((fp=fopen (argv[1], "r")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  fscanf (fp,"%lf%d%s %c", &dd, &id, cdizi, &cd);
  printf("%lf %d %s %c", dd, id, cdizi, cd);
  fclose (fp);
  
  return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar:

654.123000 9852 Bilgisayar A

Program, fprintf() ve fscanf() fonksiyonlarının çalışmasını gösterir. Program, komut satırından verilen bir dosyaya bir int, bir double, bir karakter dizisi ve bir char olmak üzere toplam dört farklı veri yazar ve dosyayı kapatır. Sonra, dosyayı tekrar açar ve dosyadan okuduğu verileri ekrana yazar.

fread() ve fwrite() fonksiyonları

Verileri dosyaya yazma ve dosyadan okuma işlemlerinde aşağıda genel yapıları verilen fread() ve fwrite() fonksiyonlarını da kullanabiliriz:


size_t fread (void *tampon-bellek, size_t boyut, size_t id, FILE *fp);
size_t fwrite (void *tampon-bellek, size_t boyut, size_t id, FILE *fp);

Daha önce kullandığımız fprintf() ve fscanf() fonksiyonları dosyadan veri okuma ve dosyaya veri yazma işlemlerinde veriler üzerinde değişim yaparak çalıştığından, fread() ve fwrite() fonksiyonlarını kullanmak daha pratiktir. fprintf() fonksiyonunu kullanarak bir dosyaya sayı yazarken, sayının dosyanın ASCII metnine çevrilmesi gerekir. Yine fscanf() fonksiyonu ile bir dosyadan bir sayı okurken, sayının fscanf() fonksiyonunun dahili format yapısına çevrilmesi gerekir.

fread() fonksiyonu fp ile gösterilen dosyadan okunan boyut yapısında id kadar değeri tampon-bellek ile gösterilen belleğe atar. Burada, boyut ifadesi okunan verinin byte olarak değerini, id ifadesi ise kaç adet veri okunduğunu belirler. fread() fonksiyonu okunan veri sayısını geri verir. Bu değer 0 ise, herhangi bir veri okunmamış demektir. Bu durumda, ya bir hata olmuştur ya da dosya sonu gelmiştir.

fwrite() fonksiyonu, fread() fonksiyonunun yaptığı işlemin tam tersini yapar. fwrite() fonksiyonu, tampon-bellek ile gösterilen bellekte bulunan boyut yapısında id kadar değeri fp ile gösterilen dosyaya yazar. Burada, boyut ifadesi yazılan byte olarak değerini, id ifadesi ise kaç adet veri yazıldığını belirler. fwrite() fonksiyonu yazılan veri sayısını geri verir. Sadece bir hata meydana geldiğinde bu değer id değerinden az olur.

fread() ve fwrite() fonksiyonları ile birlikte void işaretçiler kullanılmaktadır. Bu işaretçilerin her tür veriyi göstermesi mümkündür. void işaretçilerini, fread() ve fwrite() fonksiyonlarında olduğu gibi, herhangi bir veri türü ile birlikte kullanabiliriz. Bu durumda, fread() ve fwrite() fonksiyonları her türlü veriye işlem yapabilir.

fread() ve fwrite() fonksiyonları ile birlikte kullanılan size_t ifadesi stdio.h başlık dosyası içinde tanımlanmıştır. Bu veri türünden bir değişken derleyicinin desteklediği azami değerin boyutuna eşit bir değer içerecek şekilde ANSI standartları tarafından tanımlanmıştır. Bu durumda, size_t ifadesini unsigned ve unsigned long ifadelerine benzetebiliriz. Bu ifadenin kullanılmasının temel nedeni derleyicilerin her türlü çalışma ortamına uyumlu olarak çalışmasını sağlamaktır.

Şimdi öğrendiklerimizi bir örnek üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>

int main(void)
{
  FILE *fp;
  int id;

  if ((fp=fopen("deneme.txt", "w")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  printf("Bir int değer giriniz: ");
  scanf("%d", &id);

  if (fwrite(&id, sizeof(int), 1, fp) != 1) {
      printf("Yazma hatası!\n");
      exit(1);
  }

  fclose(fp);

  if ((fp=fopen("deneme.txt", "r")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  if (fread(&id, sizeof(int), 1, fp) != 1) {
      printf("Okuma hatası!\n");
      exit(1);
  }

  printf("Dosyadan okunan değişken değeri: %d", id);
  fclose(fp);
  
  return 0;
}

Program, fread() ve fwrite() fonksiyonlarının çalışmasını göstermektedir. Program, klavyeden bir int değer girilmesini ister. Girilen değeri id değişkenine atar. Sonra, fwrite() fonksiyonunu kullanarak id değişken değerini deneme.txt dosyasına yazar. Daha sonra, fread() fonksiyonu ile aynı değeri dosyadan okutarak ekrana yazar.

Yukarıdaki örnekte, int değişken boyutunu belirlemek için sizeof ifadesi kullanılmaktadır. Aşağıda kullanım şekli verilen sizeof ifadesi derleme zamanında devreye girer ve kendisinden sonra gelen veri türünün veya değişkenin boyutunu geri verir:

sizeof (veri-türü)
sizeof değişken-adı;

sizeof ifadesi veri türü ile kullanıldığında parantezlerle birlikte tanımlanmasına rağmen, değişken adı ile kullanıldığı zaman, parantezlere gerek yoktur.

Rastgele erişim

Şimdiye kadar incelediğimiz bütün örneklerde, dosyadan yaptığımız okuma işlemlerini dosyanın başından sonuna doğru bir sıra dahilinde yaptık. Bunun yanında, aşağıda ana yapısı verilen fseek() fonksiyonunu kullanarak bir dosyanın herhangi bir yerindeki bir bilgiyi okuyabilir veya değiştirebiliriz:


int fseek (FILE *fp, long ara, int yer);

Burada, fp ifadesi işlem yapılan dosyayı, ara ifadesi yer ifadesinin gösterdiği değerin tanımladığı dosya konumundan, işlem yapılmak istenen yerin byte olarak uzaklığını verir. yer ifadesi dosyada arama işleminin başlayacağı yeri gösterir. yer ifadesinin yerine aşağıdaki makrolardan birini kullanmanız gerekir:

SEEK_SET (0) Aramayı dosya başından başlatır.
SEEK_CUR (1) Aramayı aktif konumdan başlatır.
SEEK_END (2) Aramayı dosya sonundan başlatır.

Yukarıdaki makrolar stdio.h başlık dosyasında tanımlanmıştır.

fseek() fonksiyonu normal olarak çalıştığında 0 değerini, aksi takdirde 0 olmayan bir değer geri döndürür.

Ayrıca, aşağıda ana yapısı verilen ftell() fonksiyonunu kullanarak bir dosyanın aktif konumunu belirleyebiliriz:


long ftell (FILE *fp);

Normal olarak çalıştığında, fp ile gösterilen dosyanın aktif konumunu, aksi takdirde -1L değerini geri verir.

Rastgele erişim genellikle ikili sistem dosyalarında kullanılır. Metin dosyalarında bu özelliğin kullanılmamasının nedeni, bu dosyalarda karakter değişimlerinin yer almasıdır. fseek() fonksiyonunu metin dosyaları ile kullanmak için, daha önce ftell() fonksiyonu ile dosyanın aktif konumunu belirlememiz ve fseek() fonksiyonunu SEEK_SET ile birlikte tanımlamamız gerekir.

Rastgele erişim metodunu ikili sistem dosyası olarak açılan metin dosyalarına uygulayabiliriz, ancak metin dosyası olarak açılan dosyalara uygulayamayız.

Şimdi, öğrendiklerimizi bir örnek üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>

int main(int argc, char *argv[])
{
  long int lid1, lid2;
  int id;
  FILE *fp;

  if (argc!=2) {
      printf("Kullanım : deneme <Dosya adı>");
      exit(1);
  }

  if ((fp=fopen(argv[1], "rb")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  /* Dosya sonuna ulaşıp dosya boyutunu kaydeder. */
  fseek (fp, 0L, SEEK_END); 
  lid2 = ftell(fp);
  
  for ( ; ; ) {
       printf("Byte sıra no.sunu giriniz: ");
       scanf("%ld", &lid1);
       if (lid1>=lid2) break;
       if (fseek(fp, lid1, SEEK_SET)) {
           printf("Arama hatası!");
           exit(1);
       }
       id = getc(fp);
       printf("%ld konumundaki değer ASCII %d : %c\n", lid1, id, id);
  }

  fclose(fp);
  
  return 0;
}

Program, komut satırından argüman olarak girilen dosyayı açar. Girilen byte'da yer alan karakteri ve ASCII kodunu ekrana yazar. Dosya boyutundan fazla bir değer girerseniz program sona erer.

remove(), rewind() ve fflush() fonksiyonları

Aşağıda ana yapısı verilen remove() fonksiyonunu bir dosyayı silmek için kullanabiliriz:


int remove(char *dosya-adı);

remove() fonksiyonu dosya-adı ifadesi ile gösterilen dosyayı siler. Başarılı bir şekilde sona erdiğinde 0 değerini, aksi takdirde 0 olmayan bir değer geri döndürür.

Bir dosyanın aktif konumunu dosya başına almak için aşağıda genel yapısı verilen rewind() fonksiyonunu kullanabiliriz:


void rewind(FILE *fp);

fp ifadesi ile gösterilen dosyanın aktif konum göstergesini dosyanın başına alır. Bu fonksiyon herhangi bir değer geri vermez. Çünkü, başarılı bir şekilde açılan dosyanın aktif konumu başa alınabilir.

Bir dosya ile ilgili tampon belleği boşaltmak için aşağıda ana yapısı verilen fflush() fonksiyonunu kullanabiliriz:


int fflush(FILE *fp);

fflush() fonksiyonu, fp ifadesi ile gösterilen dosyanın tampon belleğini boşaltır. Fonksiyon başarılı olduğu zaman 0 değerini, aksi takdirde EOF değerini geri verir. fflush() fonksiyonunu argüman olmadan kullandığımızda, mevcut bütün disk tampon bellekleri silinir.

Şimdi, öğrendiklerimizi örnekler üzerinde incelemeye çalışalım:


#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <conio.h>

int main(int argc, char *argv[])
{
  if (argc != 2) {
      printf("Kullanım : deneme <Dosya adı>");
      exit(1);
  }

  printf("Dosya silinecek! Devam edecek misiniz (E/H)?");
  if (toupper(getche())=='E') remove(argv[1]);
  
  return 0;
}

Program, komut satırından argüman olarak girilen dosyayı siler.


#include <cstdio>
#include <cstdlib>

int main(int argc, char *argv[])
{
  FILE *fp;

  if (argc!=2) {
      printf("Kullanım : deneme <Dosya adı>n");
      exit(1);
  }
  if ((fp=fopen(argv[1], "r")) == NULL) {
      printf("Dosya açılamadı!\n");
      exit(1);
  }

  while (!feof(fp)) putchar(getc(fp));
  rewind(fp);
  printf("\n");
  while (!feof(fp)) putchar(getc(fp));

  fclose(fp);
  
  return 0;
}

Program, komut satırından argüman olarak verilen dosya içeriğini ekrana yazar. Aynı dosyanın aktif konumunu rewind() fonksiyonu ile başa aldıktan sonra dosya içeriğini bir kez daha ekrana yazar.