Kullanıcı Aletleri

Site Aletleri


tr:cs:cpp:common:fileio

Dosya Girdi ve Çıktı

Bu başlıktaki bilgiler C++17'den önce kullanılan dosya girdi/çıktı işlemleri ile ilgilidir. C++17 ile birlikte gelen filesystem kütüphanesi ile ilgili bilgiler için filesystem sayfasına bakınız.

C++'da dosya girdi ve çıktı işlemleri normal girdi çıktı işlemleri ile neredeyse aynıdır. 3 adet dosya G/Ç sınıfı bulunmaktadır.

  • istream sınıfından türetilen ifstream sınıfı
  • ostream sınıfından türetilen ofstream sınıfı
  • iostream sınıfından türetilen fstream sınıfı

Bu sınıfları kullanabilmek için; fstream başlık dosyasını dahil etmeniz gerekmektedir.

clog, cerr, cin ve cout gibi kullanıma hazır akışlardan farklı olarak dosya akışları bizim tarafımızdan ayarlanması gerekmektedir.

  • Bir dosyayı okuma ve/veya yazma amacıyla açmak için, parametre olarak dosyanın adını kullanarak uygun dosya G/Ç sınıfından bir nesne oluşturma.
  • Ardından dosyaya veri yazmak veya dosyadan veri okumak için ekleme («) veya çıkarma (») operatörünü kullanma.
  • İşlemleriniz bittiğinde, bir dosyayı kapatma.

Şeklinde özetlenebilir.

Dosya Çıktısı

Aşağıdaki örnekte dosya çıktısı almak için ofstream sınıfını kullanacağız.

main.cpp
#include <fstream>
#include <iostream>
 
int main()
{
    // ofstream dosya yazmak için kullanılır
    // Deneme.txt adında bir dosya oluşturacağız
    std::ofstream outf{ "Deneme.txt" };
 
    // Eğer çıktı dosya akışını yazmak için açamazsak
    if (!outf)
    {
        // Bir hata yazdırın ve çıkın
        std::cerr << "Siktir, Deneme.txt yazmak için açılamadı!\n";
        return 1;
    }
 
    // Bu dosyaya iki satır yazacağız
    outf << "Satir 1\n";
    outf << "Satir 2\n";
 
    return 0;
 
    // outf kapsam dışına çıktığında, ofstream
    // yıkıcı dosyayı kapatacaktır
}

Dosya Girdisi

Bir önceki başlıkta yazdığımız dosyayı okumak için ifstream sınıfını kullanacağız.

main.cpp
#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
    // ifstream dosyaları okumak için kullanılır
    // Deneme.txt adlı bir dosyadan okuyacağız
    std::ifstream inf{ "Deneme.txt" };
 
    // Eğer çıktı dosya akışını okumak için açamazsak
    if (!inf)
    {
        // Bir hata yazdırın ve çıkın
        std::cerr << "Siktir, Deneme.txt okunmak için açılamadı!\n";
        return 1;
    }
 
    // Hala okunacak şeyler varken
    while (inf)
    {
        // dosyadan bir dizeye bir şeyler oku ve yazdır
        std::string strInput;
        inf >> strInput;
        std::cout << strInput << '\n';
    }
 
    return 0;
 
    // inf kapsam dışına çıktığında, ifstream
    // yıkıcı dosyayı kapatacaktır
}

Yukarıdaki örnek tam istediğimiz sonucu elde etmemizi sağlamayabilir. Çünkü çıkarma operatörü bildiğimiz gibi boşluk karakterlerinde kesilir.

Bu yüzden getline() fonksiyonu kullanabiliriz.

main.cpp
#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
    // ifstream dosyaları okumak için kullanılır
    // Deneme.txt adlı bir dosyadan okuyacağız
    std::ifstream inf{ "Deneme.txt" };
 
    // Eğer çıktı dosya akışını okumak için açamazsak
    if (!inf)
    {
        // Bir hata yazdırın ve çıkın
        std::cerr << "Siktir, Deneme.txt okunmak için açılamadı!\n";
        return 1;
    }
 
    // Hala okunacak şeyler varken
    while (inf)
    {
        // dosyadan bir dizeye bir şeyler oku ve yazdır
        std::string strInput;
        std::getline(inf, strInput);
        std::cout << strInput << '\n';
    }
 
    return 0;
 
    // inf kapsam dışına çıktığında, ifstream
    // yıkıcı dosyayı kapatacaktır
}

Tampon Çıktı

C++'da çıktı tamponlanabilir. Bu, bir dosya akışına çıktı olarak verilen herhangi bir şeyin hemen diske yazılmayabileceği anlamına gelir. Bunun yerine, birkaç çıktı işlemi birleştirilebilir ve birlikte işlenebilir. Bu öncelikle performans nedenleriyle yapılır. Bir tampon diske yazıldığında, buna tamponun boşaltması denir. Tamponun boşaltılmasını sağlamanın bir yolu dosyayı kapatmaktır - tampunun içeriği diske yazılacak ve ardından dosya kapatılacaktır.

Tamponlama genellikle bir sorun değildir, ancak bazı durumlarda dikkatsiz kişiler için komplikasyonlara neden olabilir. Bu durumda ana suçlu, tamponda veri olması ve ardından programın hemen sonlanmasıdır (ya çökerek ya da exit() fonksiyonunu çağırarak). Bu durumlarda, dosya akışı sınıflarının yıkıcıları çalıştırılmaz, bu da dosyaların asla kapatılmadığı ve tamponların asla boşaltılmadığı anlamına gelir. Bu durumda, tampondaki veriler diske yazılmaz ve sonsuza kadar kaybolur. Bu nedenle exit() fonksiyonunu çağırmadan önce açık dosyaları kapatmak her zaman iyi bir fikirdir.

Tamponu ostream::flush() fonksiyonunu kullanarak ya da std::flush'ı çıktı akışına göndererek manuel olarak boşaltmak mümkündür. Bu yöntemlerden her ikisi de programın çökmesi ihtimaline karşı tamponun içeriğinin hemen diske yazılmasını sağlar.

std::endl çıktı akışını boşaltır. std::endl'in aşırı kullanımı (gereksiz tampon boşaltmalarına neden olur), boşaltmaların pahalı olduğu (bir dosyaya yazma gibi) tamponlu G/Ç yaparken performans etkilerine neden olabilir. Bu nedenle, performans bilincine sahip programcılar, tamponun gereksiz yere yıkanmasını önlemek amacıyla, çıktı akışına yeni bir satır eklemek için std::endl yerine genellikle \n kullanırlar.

Dosya Modları

Dosya akışı sınıflarının yapıcıları, dosyanın nasıl açılması gerektiğine ilişkin bilgileri belirtmemize olanak sağlayan opsiyonel dosya modu adı verilen ikinci bir parametreye sahiptir.

Bu parametrenin kabul ettiği bayraklar ios sınıfında bulunur.

ios dosya modu Açıklama
app Dosyayı ekleme modunda açar
ate Okumadan/yazmadan önce dosyanın sonuna kadar arar
binary Dosyayı ikili modda açar
in Dosyayı okuma modunda açar (ifstream için varsayılan)
out Dosyayı yazma modunda açar (ofstream için varsayılan)
trunc Dosya zaten mevcutsa siler

Birden fazla bayrağı, | operatörünü kullanarak belirtmek mümkündür.

fstream'in tasarlanma şekli nedeniyle, std::ios::in kullanılırsa ve açılan dosya mevcut değilse başarısız olabilir. Eğer fstream kullanarak yeni bir dosya oluşturmanız gerekiyorsa, sadece std::ios::out modunu kullanın.

Rastgele Dosya G/Ç

Tüm dosya akışları, bir dosya işaretçisine sahiptir. Bu işaretçi, geçerli dosya okuma/yazma konumunu takip eder.

Genel olarak bir dosya okuma/yazma için açıldığında bu işaretçi dosyanın başında bulunur. Eğer app dosya modu ile açtıysak, dosyanın sonuna taşınır ki ekleyeceğimiz veriler dosyanın sonuna eklensin.

Yazının bu kısmına kadarki kısımda yaptığımız g/ç işlemleri hep sıralı şekildeydi. İstersek rasgele şekilde dosyanın istediğimiz kısımlarına erişebiliriz. Bunun için işaretçiyi istediğimiz yere taşımamız gerekmektedir.

Bunu dosya işaretçisini seekg() ve seekp() fonksiyonları ile manipüle ederek gerçekleştiririz.

Normal akışlarda seekg() okuma pozisyonunu, seekp() yazma pozisyonunu ayrı olarak manipüle ederler. Ancak dosya g/ç söz konusu olduğunda okuma ve yazma pozisyonları ortaktır. Yani iki fonksiyonda birbirleri yerine kullanılabilir.

Bu iki fonksiyon iki parametreye sahiptir;

  • Birinci parametre, dosya işaretçisinin kaç bayt taşınacağını belirleyen bir ofsettir.
  • İkinci parametre, ofset parametresinin neye göre ofsetlenmesi gerektiğini belirten bir ios bayrağıdır.
ios bayrağı Açıklama
ios::beg Dosyanın başından itibaren ofsetlenir
ios::cur Dosyanın mevcut konumundan itibaren ofsetlenir
ios::end Dosyanın sonundan itibaren ofsetlenir

Pozitif ofset, dosya işaretçisini dosyanın sonuna doğru taşımak anlamına gelirken, negatif ofset dosya işaretçisini dosyanın başına doğru taşımak anlamına gelir.

main.cpp
#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
    std::ifstream inf{ "Deneme.txt" };
    // Giriş dosyası akışını okumak için açamadıysak
    if (!inf)
    {
        // Bir hata yazdırın ve çıkın
        std::cerr << "Siktir, Deneme.txt dosyası okunmak için açılamadı!\n";
        return 1;
    }
 
    std::string strData;
 
    inf.seekg(5); // 5. karaktere geç
    // Satırın geri kalanını alın ve 2. satıra geçerek yazdırın
    std::getline(inf, strData);
    std::cout << strData << '\n';
 
    inf.seekg(8, std::ios::cur); // dosyaya 8 bayt daha taşı
    // Satırın geri kalanını alın ve yazdırın
    std::getline(inf, strData);
    std::cout << strData << '\n';
 
    inf.seekg(-14, std::ios::end); // dosya sonundan 14 bayt önce taşı
    // Satırın geri kalanını alın ve yazdırın
    std::getline(inf, strData);
    std::cout << strData << '\n';
 
    return 0;
}

Bir diğer onemli fonksiyonlar ise tellg() ve tellp() fonksiyonlarıdır. Bu fonksiyonlar dosya işaretçisinin mutlak konumunu döndürürler.

Bu fonksiyonlar ile dosyanın boyutunu hesaplayabiliriz.

std::ifstream inf {"Deneme.txt"};
inf.seekg(0, std::ios::end); // dosya sonuna taşı
std::cout << inf.tellg();

Programlamada \n aslında soyut bir kavramdır. Bu karakteri biz satır sonu olarak yorumlarız.

  • Windows'ta satır sonu, sıralı şekilde CR (satır başı) ve LF (satır besleme) karakterleri ile gösterilir. (Yani 2 bayt depolama alanı kaplar.)
  • Unix'te satırsonu, sadece LF (satır besleme) karakteri ile gösterilir. (Yani 1 bayt depolama alanı kaplar)

Bu yüzden yukarıdaki örnek kod iki işletim sisteminde farklı sonuç verecektir.

fstream ile aynı anda okuma ve yazma

Bildiğimiz gibi fstream sınıfı yazma ve okuma işlemlerini birlikte destekler. Buradaki en büyük kusur, okuma ve yazma arasında keyfi olarak geçiş yapmanın mümkün olmamasıdır.

Okuma veya yazma işlemlerinden harhangi biri gerçekleşirken, diğerine geçmek istersek zorunlu olarak dosya işaretçisini modifiye etmemiz gerekir.

Eğer dosya işaretçisinin geçerli konumunu değiştirmek istemiyorsak, yani sadece diğer işleme geçmek istiyorsak, seek ile zaten bulunduğu konuma tekrar taşımamız gerekmektedir.

// iofile'ın fstream türünde bir nesne olduğunu varsayalım
iofile.seekg(iofile.tellg(), std::ios::beg); // geçerli dosya konumuna ara

Eğer bu işlemi yapmazsanız

Değişkenleri bir dosyaya aktarmak oldukça kolay olsa da, işaretçilerle söz konusu olduğunda işler daha karmaşık hale gelir.

Bildiğimiz gibi işaretçiler, bellek adresleri tutarlar ve programın her çalışmasında bu adresler değişebilir.

Eğer biz işaretçileri diske yazdırırsak bu o çalışma esnasında geçerli olan adresi yazdıracaktır.

Bellek adreslerini dosyalara yazmayın. Başlangıçta bu adreslerde bulunan değişkenler, değerlerini diskten geri okuduğunuzda farklı adreslerde olabilir ve adresler geçersiz olur.


UCH Viki'den alınmıştır. https://wiki.ulascemh.com/doku.php?id=tr:cs:cpp:common:fileio

tr/cs/cpp/common/fileio.txt · Son değiştirilme: 2025/05/02 21:47 Değiştiren: ulascemh