Professional Documents
Culture Documents
The C++ Standard Library-772-877-84-106 TR
The C++ Standard Library-772-877-84-106 TR
Okuma sırasında dosyanın sonuna ulaşmadığınız sürece, aynı dosyaya okuma ve yazma
işlemleri arasında bir arama yapmak önemlidir. Bu arama yapılmazsa, muhtemelen bozuk bir
dosyayla veya daha da ölümcül hatalarla sonuçlanırsınız.
Daha önce de belirtildiği gibi, karakter karakter işlemek yerine, << işlecine argüman olarak
dosyanın akış arabelleğine bir işaretçi geçirerek tüm içeriği tek bir deyimde yazdırabilirsiniz
(ayrıntılar için bkz. Bölüm 15.14.3, sayfa 846):
std::cout << input.rdbuf();
Akış arabelleği bunları tüketemediği sürece ikinci bağımsız değişken tarafından belirtilen
karakterler. (Sonlandırıcı) null karakterleri önemsemez. Bu fonksiyon yazılan karakter sayısını
döndürür. Akış tamponundan karakter okuma arayüzü biraz daha karmaşıktır (Tablo 15.43) çünkü
girdi için, bir karakteri tüketmeden ona bakmak gerekir. Ayrıca, ayrıştırma sırasında karakterlerin
akış tamponuna geri konulabilmesi istenir. Böylece, akış
tampon sınıfları ilgili işlevleri sağlar.
Bir dizenin bir ostreambuf_iterator kullanılarak bir akış tamponuna nasıl yazılabileceği aşağıda
açıklanmıştır:
// çıktı akışının tamponu için yineleyici oluşturun cout
std::ostreambuf_iterator<char> bufWriter(std::cout);
İfade Etki
ostreambuf_iterator<char>(ostream) için bir çıktı akışı tampon yineleyicisi
oluşturur.
ostreambuf_iterator<char>(buffer_ptr) ostream
buffer_ptr'nin başvurduğu tampon için bir
çıkış akışı tampon yineleyicisi oluşturur
*iter No-op (iter döndürür)
iter = c Çağırarak tampona c karakterini yazar
bunun için sputc(c)
++iter No-op (iter döndürür)
iter++ No-op (iter döndürür)
failed() Çıktı akışı yineleyicisinin artık yazıp
yazamayacağını döndürür
Tablo 15.46 giriş akışı tampon yineleyicilerinin tüm işlemlerini listeler. Uygulama, akış
yineleyicilerine benzerdir (bkz. Bölüm 9.4.3, sayfa 462). Ayrıca, yineleyiciyi bir tamponla
başlatabilirsiniz ve iki giriş akışı tampon yineleyicisinin eşit olup olmadığını döndüren equal()
üye işlevi sağlanır. İki giriş akışı tampon yineleyicisi, her ikisi de akış sonu yineleyicisi olduğunda
veya hiçbiri akış sonu yineleyicisi olmadığında eşittir.
İfade Etki
istreambuf_iterator<char>() Akış sonu yineleyicisi oluşturur
istreambuf_iterator<char>(istream) istream için bir giriş akışı tampon
yineleyicisi oluşturur ve sgetc()
işlevini kullanarak ilk karakteri
istreambuf_iterator<char>(buffer_ptr) okuyabilir
buffer_ptr öğesinin başvurduğu arabellek
için bir giriş akışı arabellek yineleyicisi
*iter oluşturur ve sgetc() işlevini kullanarak
ilk karakteri okuyabilir
Daha önce sgetc() ile okunan geçerli
++it karakteri döndürür (kurucu tarafından
yapılmadıysa ilk karakteri okur)
er Sonraki karakteri sbumpc() ile okur ve
konumunu döndürür
iter Sonraki karakteri sbumpc() ile okur, ancak *
önceki karakteri veren bir yineleyici (proxy)
++ döndürür
Her iki yineleyicinin eşit olup olmadığını
döndürür Eşitlik için iter1 ve iter2'yi test
iter1.equal(iter2) eder
iter1== iter2 Eşitsizlik için iter1 ve iter2'yi test eder
iter1!= iter2
Aşağıdaki örnek, okunan tüm karakterleri akış tampon yineleyicileri ile yazan klasik filtre
çerçevesidir. Bölüm 15.5.3, sayfa 772'deki örneğin değiştirilmiş bir versiyonudur:
// io/charcat2.cpp
#include <iostream>
#include <iterator>
using namespace
std;
int main()
{
// cin için giriş akışı tampon yineleyicisi
istreambuf_iterator<char> inpos(cin);
Ayrıca bir girdi akışından okunan tüm karakterleri işlemek için algoritmalara akış tampon
yineleyicileri de aktarabilirsiniz (tam bir örnek için io/countlines1.cpp dosyasına bakın):
int countLines (std::istream& in)
{
return std::count(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>(),
'\n');
}
Bir dizeyi başlatmak için standart girdiden okunan tüm karakterleri kullanan bir örnek için Bölüm
14.6, sayfa 732'ye bakın.
832 Bölüm 15: Akış Sınıflarını Kullanarak
Giriş/Çıkış
Karakter yazmak için kullanılan bir tampon, pbase(), pptr() ve epptr() fonksiyonları
tarafından erişilebilen üç işaretçi ile tutulur (Şekil 15.4). İşte bu işaretçiler neyi temsil eder:
1. pbase() ("put base") çıktı tamponunun başlangıcıdır.
2. pptr() ("put pointer") geçerli yazma konumudur.
3. epptr() ("end put pointer") çıktı tamponunun sonudur. Bu, epptr()'nin tamponlanabilecek
son karakterden bir sonrasına işaret ettiği anlamına gelir.
tarafından işaret edilen karakter hariç, pbase() ile pptr() arasındaki aralıktaki karakterler
pptr(), zaten yazılmış ancak henüz ilgili çıkış kanalına aktarılmamış veya temizlenmemiştir.
sputc() üye fonksiyonu kullanılarak bir karakter yazılır. Bu karakter, boş bir yazma konumu
varsa geçerli yazma konumuna kopyalanır. Daha sonra geçerli yazma konumunun göstericisi
artırılır. Tampon doluysa (pptr() == epptr()), çıkış tamponunun içeriği overflow() sanal
fonksiyonu çağrılarak ilgili çıkış kanalına gönderilir. Bu fonksiyon karakterleri, dize akışlarında
olduğu gibi dahili olabilen bazı "harici temsillere" göndermekten sorumludur. Temel
basic_streambuf sınıfındaki overflow() uygulaması sadece dosya sonu döndürür, bu da daha
fazla karakter yazılamayacağını gösterir.
sputn() üye fonksiyonu aynı anda birden fazla karakter yazmak için kullanılabilir. Bu
fonksiyon, işi daha verimli bir şekilde gerçekleştirilebilen xsputn() sanal fonksiyonuna
devreder.
15.13 Dere Tampon Sınıfları 833
Bölüm 16.1.4, sayfa 853'te tanıtılmıştır. Bu durumda, c'nin dosya sonu ile karşılaştırılması farklı
görünür: EOF yerine traits::eof() döndürülmelidir ve c argümanı EOF ise,
traits::not_eof(c) değeri döndürülmelidir, burada traits basic_streambuf'un ikinci şablon
argümanıdır. Bu aşağıdaki gibi görünebilir:
// io/outbuf1i18n.hpp
#include <streambuf>
#include <locale>
#include <cstdio>
typedef basic_outbuf<char>outbuf
;
typedef basic_outbuf<wchar_t> woutbuf;
Temel sınıf bir şablon parametresine bağlı olduğu için getloc() çağrısını artık this-> ile
nitelemeniz gerektiğini unutmayın. Ayrıca, putchar() işlevine aktarmadan önce karakteri
daraltmamız gerekir, çünkü putchar() yalnızca char kabul eder (bkz. Bölüm 16.4.4, sayfa
891).
15.13 Dere Tampon Sınıfları 835
int main()
{
outbuf ob; // özel çıktı tamponu oluştur
std::ostream out(&ob); // çıktı akışını bu çıktı tamponu ile başlat
out << "31 hexadecimal: " << std::hex << 31 << std::endl;
}
// write() için:
#ifdef
_MSC_VER
#include
<io.h> #else
#include <unistd.h>
#endif
Halka açık:
// kurucu
fdoutbuf (int _fd) : fd(_fd) {
}
korumalı:
// bir karakter yaz
virtual int_type overflow (int_type c) {
if (c != EOF) {
char z = c;
if (write (fd, &z, 1) != 1) {
return EOF;
}
}
c'yi döndür;
}
// birden fazla karakter yaz
virtual std::streamsize xsputn (const char* s,
std::streamsize num) {
return write(fd,s,num);
}
};
Bu akış tamponuna bir karakter dizisi gönderilirse, her karakter için overflow() işlevinin
çağrılmasını önlemek için bu akış tamponu xsputn() işlevini de uygular. Bu fonksiyon, fd
dosya tanımlayıcısı tarafından tanımlanan dosyaya tek bir çağrı ile tüm karakter dizisini yazar.
xsputn() fonksiyonu başarıyla yazılan karakter sayısını döndürür. İşte örnek bir uygulama:
// io/outbuf2.cpp
#include <iostream>
#include "outbuf2.hpp"
15.13 Dere Tampon Sınıfları 837
int main()
{
fdostream out(1); // dosya tanımlayıcısı 1'e yazan tamponlu akış
out << "31 hexadecimal: " << std::hex << 31 << std::endl;
}
Bu program, dosya tanımlayıcısı 1 ile başlatılan bir çıkış akışı oluşturur. Bu dosya tanımlayıcısı,
geleneksel olarak, standart çıkış kanalını tanımlar. Dolayısıyla, bu örnekte karakterler basitçe
yazdırılır. Başka bir dosya tanımlayıcısı mevcutsa - örneğin, bir dosya veya soket için - bu da
kurucu argüman olarak kullanılabilir.
Tamponlama yapan bir akış tamponu uygulamak için, yazma tamponunun aşağıdaki işlev
kullanılarak başlatılması gerekir
setp(). Bu, bir sonraki örnekte gösterilmektedir:
// io/outbuf3.hpp
#include <cstdio>
#include <streambuf>
// write() için:
#ifdef _MSC_VER
# include
<io.h> #else
# include <unistd.h>
#endif
Halka açık:
// kurucu
// - veri arabelleğini başlat
// - bufferSizeth karakterinin overflow() çağrısına neden olmasına izin vermek için bir
karakter daha az outbuf() {
setp (buffer, buffer+(bufferSize-1));
}
// yıkıcı
// - veri tamponunu yıka
virtual ~outbuf() {
sync();
}
838 Bölüm 15: Akış Sınıflarını Kullanarak
Giriş/Çıkış
korumalı:
// tampondaki karakterleri temizle
int flushBuffer () {
int num = pptr()-pbase();
if (write (1, buffer, num) != num) {
return EOF;
}
pbump (-num); // put işaretçisini uygun şekilde sıfırla
return num;
}
// arabellek dolu
// - c ve önceki tüm karakterleri yaz
virtual int_type overflow (int_type c) {
if (c != EOF) {
// tampona karakter ekle
*pptr() = c;
pbump(1);
}
// tamponu temizle
if (flushBuffer() == EOF) {
// HATA
EOF döndür;
}
c'yi döndür;
}
yazma konumuna yazılabilir, çünkü yazma konumunun işaretçisi bitiş işaretçisinin ötesine
artırılmaz. overflow() argümanı yazma konumuna yerleştirildikten sonra, tüm tampon
boşaltılabilir.
flushBuffer() üye fonksiyonu tam olarak bunu yapar. Karakterleri write() fonksiyonunu
kullanarak standart çıkış kanalına (dosya tanımlayıcı 1) yazar. Akış tamponunun üye işlevi
pbump(), yazma konumunu tamponun başına geri taşımak için kullanılır.
overflow() işlevi, EOF değilse overflow() çağrısına neden olan karakteri tampona ekler.
Daha sonra, pbump() işlevi yazma konumunu tampon karakterlerin yeni sonunu yansıtacak
şekilde ilerletmek için kullanılır. Bu, yazma konumunu geçici olarak bitiş konumunun (epptr())
ötesine taşır.
Bu sınıf ayrıca, akış tamponunun mevcut durumunu ilgili depolama ortamıyla senkronize
etmek için kullanılan sync() sanal fonksiyonuna sahiptir. Normalde, yapılması gereken tek şey
tamponu temizlemektir. Akış tamponunun tamponlanmamış versiyonları için, bu fonksiyonun
geçersiz kılınması gerekli değildi, çünkü temizlenecek bir tampon yoktu.
Sanal yıkıcı, akış arabelleği yok edildiğinde hala arabelleğe alınmış olan verilerin yazılmasını
sağlar.
Bunlar çoğu akış tamponu için geçersiz kılınan işlevlerdir. Harici gösterimin bazı özel yapıları
varsa, ek fonksiyonların geçersiz kılınması faydalı olabilir. Örneğin, seekoff() ve seekpos()
fonksiyonları yazma pozisyonunun manipülasyonuna izin vermek için geçersiz kılınabilir.
Girdi mekanizması temelde çıktı mekanizması ile aynı şekilde çalışır. Ancak, girdi için son
okumayı geri alma imkanı da vardır. Girdi akışının unget() işlevi tarafından çağrılan sungetc()
veya girdi akışının putback() işlevi tarafından çağrılan sputbackc() işlevleri, akış tamponunu
son okumadan önceki durumuna yeniden depolamak için kullanılabilir. Okuma konumunu bu
karakterin ötesine taşımadan bir sonraki karakteri okumak da mümkündür. Bu nedenle, bir akış
tamponundan okumayı uygulamak için, bir akış tamponuna yazmayı uygulamak için gerekenden
daha fazla işlevi geçersiz kılmanız gerekir.
Bir akış tamponu, eback(), gptr() ve egptr() üye fonksiyonları aracılığıyla erişilebilen
üç işaretçi içeren bir okuma tamponu tutar (Şekil 15.5):
1. eback() ("end back") giriş tamponunun başlangıcı ya da adından da anlaşılacağı gibi geri
koyma alanının sonudur. Karakter, özel bir işlem yapılmadan yalnızca bu konuma kadar geri
konulabilir.
2. gptr() ("get pointer") geçerli okuma konumudur.
3. egptr() ("end get pointer") girdi tamponunun sonudur.
Okuma konumu ile son konum arasındaki karakterler harici gösterimden programın belleğine
taşınmıştır, ancak hala program tarafından işlenmeyi beklemektedirler.
Tek karakterler sgetc() veya sbumpc() fonksiyonları kullanılarak okunabilir. Bu iki
fonksiyonun farkı, okuma işaretçisinin sgetc() tarafından değil, sbumpc() tarafından
artırılmasıdır. Tampon tamamen okunmuşsa (gptr() == egptr()), hiçbir karakter mevcut
değildir ve tampon, verilerin okunmasından sorumlu olan underflow() sanal fonksiyonunun
çağrılmasıyla yeniden doldurulmalıdır. Hiçbir karakter mevcut değilse, sbumpc() işlevi bunun
yerine uflow() sanal işlevini çağırır. Bu durumda
840 Bölüm 15: Akış Sınıflarını Kullanarak
Giriş/Çıkış
Yeni bir tampon okunmuşsa, başka bir sorun ortaya çıkar: Eski veri tampona kaydedilmezse bir
karakter bile geri konulamaz. Bu nedenle, underflow() uygulaması genellikle geçerli tamponun
son birkaç karakterini (örneğin dört karakter) tamponun başına taşır ve ardından yeni okunan
karakterleri ekler. Bu, pbackfail() çağrılmadan önce bazı karakterlerin geri taşınmasını sağlar.
Aşağıdaki örnek, böyle bir uygulamanın nasıl görünebileceğini göstermektedir. inbuf
sınıfında, on karakterli bir giriş tamponu uygulanmıştır. Bu tampon, geri koyma alanı için en fazla
dört karaktere ve "normal" giriş tamponu için altı karaktere bölünmüştür:
// io/inbuf1.hpp
#include <cstdio>
#include <cstring>
#include <streambuf>
// read() için:
#ifdef _MSC_VER
# include
<io.h> #else
# include <unistd.h>
#endif
protected:
// veri tamponu:
// - geri koyma alanında en fazla dört karakter artı
// - sıradan okuma tamponunda en fazla altı karakter
static const int bufferSize = 10; // veri tamponunun boyutu
char buffer[bufferSize]; // veri tamponu
Halka açık:
// kurucu
// - boş veri arabelleğini başlat
// - geri koyma alanı yok
// => force underflow()
inbuf() {
setg (buffer+4, // geri koyma alanının
başlangıcı buffer+4, // okuma
pozisyonu tampon+4); // son konum
}
842 Bölüm 15: Akış Sınıflarını Kullanarak
Giriş/Çıkış
korumalı:
// tampona yeni karakterler ekle
virtual int_type underflow () {
// okunan konum arabellek sonundan önce mi?
if (gptr() < egptr()) {
return traits_type::to_int_type(*gptr());
}
Kurucu, tampon tamamen boş olacak şekilde tüm işaretçileri başlatır (Şekil 15.6). Bu akış
tamponundan bir karakter okunursa, underflow() işlevi çağrılır. Sonraki karakterleri okumak
için her zaman bu akış tamponu tarafından kullanılan bu işlev, okunan karakterleri kontrol ederek
başlar.
15.13 Dere Tampon Sınıfları 843
giriş tamponu. Karakterler mevcutsa, memcpy() işlevi kullanılarak geri koyma alanına taşınırlar.
Bunlar, en fazla, giriş tamponunun son dört karakteridir. Daha sonra POSIX'in düşük seviyeli G/Ç
fonksiyonu read() standart giriş kanalından bir sonraki karakteri okumak için kullanılır. Tampon
yeni duruma göre ayarlandıktan sonra, okunan ilk karakter döndürülür.
eback()gptr()egptr()
Örneğin, 'H', 'a', 'l', 'l', 'o' ve 'w' karakterleri read() işlevine yapılan ilk çağrı ile
okunursa, giriş arabelleğinin durumu Şekil 15.7'de gösterildiği gibi değişir. Geri koyma alanı
boştur çünkü tampon ilk kez doldurulmuştur ve henüz geri koyulabilecek karakter yoktur.
eback()gptr() egptr()
H a l l o w
Bu karakterler çıkarıldıktan sonra, son dört karakter geri koyma alanına taşınır ve yeni karakterler
okunur. Örneğin, 'e', 'e', 'n' ve '\n' karakterleri bir sonraki read() çağrısı ile okunursa,
sonuç Şekil 15.8'de gösterildiği gibi olur.
l l o w e e n \n
int main()
{
inbuf ib; // özel akış tamponu oluştur
std::istream in(&ib); // giriş akışını bu tampon ile başlat
char c;
for (int i=1; i<=20; i++) {
// sonraki karakteri oku (tampondan)
in.get(c);
Program bir döngü içinde karakterleri okur ve dışarı yazar. Sekizinci karakter okunduktan sonra,
iki karakter geri konur. Böylece, yedinci ve sekizinci karakterler iki kez yazdırılır.
sayısal değerlerin biçimlendirilmiş olarak okunması gibi sık kullanılan işlemler için geliştirmeler.
Ancak, bu tür iyileştirmeler için akış tamponlarının tamponlu olması şarttır.
Bu nedenle, tüm G/Ç işlemleri tamponlama için bir mekanizma uygulayan akış tamponları
kullanılarak yapılır. Bununla birlikte, yalnızca bu tamponlamaya güvenmek yeterli değildir, çünkü
üç husus etkili tamponlama ile çelişir:
1. Akış tamponlarını tamponlama olmadan uygulamak genellikle daha basittir. İlgili akışlar sık
kullanılmıyorsa veya yalnızca çıktı için kullanılıyorsa, tamponlama muhtemelen o kadar
önemli değildir. (Çıkış için, akış tampon yineleyicileri ve işaretçiler arasındaki fark giriş için
olduğu kadar kötü değildir; asıl sorun akış tampon yineleyicilerini karşılaştırmaktır). Ancak,
yoğun olarak kullanılan akış tamponları için tamponlama kesinlikle uygulanmalıdır.
2. Unitbuf bayrağı, çıktı akışlarının her çıktı işleminden sonra akışı yıkamasına neden olur.
Buna paralel olarak, flush ve endl manipülatörleri de akışı temizler. En iyi performans için
muhtemelen üçünden de kaçınılmalıdır. Bununla birlikte, örneğin konsola yazarken, tam
satırları yazdıktan sonra akışı temizlemek muhtemelen hala mantıklıdır. Unitbuf, flush veya
endl'i yoğun olarak kullanan bir programa takılıp kaldıysanız, akış tamponunu temizlemek
için sync() kullanmayan ancak uygun olduğunda çağrılan başka bir işlev kullanan özel bir
akış tamponu kullanmayı düşünebilirsiniz.
3. Akışların tie() fonksiyonuyla bağlanması (bkz. Bölüm 15.12.1, sayfa 819) ayrıca akışların
daha fazla yıkanmasına neden olur. Bu nedenle, akışlar yalnızca gerçekten gerekliyse
bağlanmalıdır.
Yeni akış tamponlarını uygularken, bunları önce tamponlama olmadan uygulamak makul olabilir.
Ardından, akış tamponunun bir darboğaz olduğu tespit edilirse, uygulamanın geri kalanında
herhangi bir şeyi etkilemeden tamponlamayı uygulamak hala mümkündür.
int main ()
{
// tüm standart girdiyi standart çıktıya kopyala
std::cout << std::cin.rdbuf();
}
Burada rdbuf(), cin tamponunu verir (bkz. Bölüm 15.12.2, sayfa 820). Böylece, program
tüm standart girdiyi standart çıktıya kopyalar.
- Bir akış tamponuna bir işaretçiyi >> operatörüne geçirerek, doğrudan bir akış tamponuna
okuyabilirsiniz. Örneğin, tüm standart girdiyi standart çıktıya aşağıdaki şekilde de
kopyalayabilirsiniz:
// io/copy2.cpp
#include <iostream>
int main ()
{
// tüm standart girdiyi standart çıktıya kopyala
std::cin >> std::noskipws >> std::cout.rdbuf();
}
skipws bayrağını temizlemeniz gerektiğini unutmayın. Aksi takdirde, girdinin başındaki boşluk
atlanır (bkz. Bölüm 15.7.7, sayfa 789).
Biçimlendirilmiş G/Ç için bile, akış tamponlarını doğrudan kullanmak makul olabilir. Örneğin, bir
döngüde çok sayıda sayısal değer okunuyorsa, döngünün yürütüldüğü tüm süre boyunca var olan
tek bir sentry nesnesi oluşturmak yeterlidir. Daha sonra, döngü içinde boşluklar manuel olarak
atlanır - ws manipülatörünü kullanmak da bir sentry nesnesi oluşturacaktır - ve ardından
num_get faseti (bkz. Bölüm 16.4.1, sayfa 873) sayısal değerleri doğrudan okumak için kullanılır.
Bir akış tamponunun kendi hata durumu olmadığını unutmayın. Ayrıca, kendisine
bağlanabilecek giriş veya çıkış akışı hakkında hiçbir bilgisi yoktur. Bu nedenle, çağrı
// in içeriğini out'a kopyala <<
in.rdbuf();
in hata durumunu bir hata veya dosya sonu nedeniyle değiştiremez.
Bu sayfa kasıtlı olarak boş bırakılmıştır