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

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

C++ akışlar (streams)

C++ I/O sistemi akışlar aracılığıyla çalışır. Akış, bilgi üreten veya kullanan mantıksal bir aygıt olarak tanımlanabilir. Bir akış, I/O sistemi tarafından fiziksel bir cihaza bağlanır. Bağlı oldukları gerçek fiziksel cihazlar farklı olsa bile tüm akışların çalışma şekli aynıdır. Tüm akışlar aynı şekilde çalıştığından, aynı I/O fonksiyonları hemen hemen her tür fiziksel aygıtta çalışabilir. Örneğin, bir dosyaya veri yazmak için kullandığımız bir fonksiyonu bir yazıcıya veya ekrana yazmak için de kullanabiliriz.

Akış sınıfları

C++ I/O sistemi fonksiyonları ile sınıf bildirimleri <iostream> başlık dosyasında yer alır.

I/O sınıf bildirimleri şablon sınıflarla başlar. Bir şablon sınıf tanımlandıktan sonra, biri 8 bitlik karakterler ve diğeri geniş karakterler olmak üzere iki farklı şablon fonksiyonu için yeniden bildirim yapılır.

C++ I/O sistemi, birbiriyle ilişkili ancak farklı iki şablon sınıfı hiyerarşisi üzerine kurulmuştur.

Birincisi, basic_streambuf adı verilen düşük seviyeli I/O sınıfından türetilmiştir. Bu sınıf, temel, düşük seviyeli giriş ve çıkış işlemlerini ve tüm C++ I/O sistemi için temel desteği sağlar. Gelişmiş I/O programlama yapmadığımız sürece, doğrudan basic_streambuf kullanmamıza gerek kalmayacaktır.

En sık çalışılan sınıflar ise, basic_ios sınıfından türetilmiştir. Bu, akış I/O ile ilgili biçimlendirme, hata kontrolü ve durum bilgileri sağlayan üst düzey bir I/O sınıfıdır. Basic_ios sınıfı, basic_istream, basic_ostream ve basic_iostream gibi türetilmiş sınıfların türetilmesinde ana sınıf olarak kullanılır. Bu sınıflar sırasıyla giriş, çıkış ve giriş/çıkış yapabilen akışlar oluşturmak için kullanılır.

Aağıdaki listede 8 bit ve geniş karakterler için kullanılan şablon sınıf isimleri yer almaktadır:

Şablon sınıfı 8 bit karakter şablon sınıfı Geniş karakter şablon sınıfı
basic_streambuf streambuf wstreambuf
basic_ios ios wios
basic_istream istream wistream
basic_ostream ostream wostream
basic_iostream iostream wiostream

Ön tanımlı akışlar

Bir C++ programı çalışmaya başladığında, dört adet akış otomatik olarak açılır.

C++ tabanlı nesneye yönelik I/O sistemi şablon sınıfları
Akış Görevi Aygıt
8 bit karakter Geniş karakter
cin win Standart veri girişi Klavye
cout wout Standart çıkış Ekran
cerr werr Standart hata çıkışı Ekran
clog wlog cerr tamponlanmış sürümü Ekran

Normal olarak, standart akışlar klavye ve ekran ile işlem yapar. Ancak, ihtiyaç duyulduğunda, dosya veya diğer aygıtlara yönlendirilebilir.

Yapılandırılmış I/O

C++'da, giriş ve çıkış işlemlerinde verileri, alan genişliklerini ayarlayarak, sayıların hangi sayı sistemine göre gösterileceğini belirleyerek veya ondalık sayılarda noktadan sonra kaç rakam görüntüleneceğini belirleyerek, yapılandırabiliriz.

C++'da, verileri yapılandırmak için iki farklı yöntem izleyebiliriz.

1. ios sınıfının üyelerine doğrudan erişerek, sınıf içindeki üye fonksiyonları ve değişkenleri kullanabiliriz.

2. Manipülatörler adı verilen özel fonksiyonları kullanabiliriz.

ios sınıfı ile veri yapılandırma

Her akış, verinin yapılandırma şeklini kontrol eden bir dizi format işaretiyle ilişkilendirilmiştir. ios sınıfı içinde, aşağıdaki değerlerin tanımlandığı fmtflags adlı bir numaralandırma yer alır.

CodeBlocks 17.12 sürümünde ios_base.h başlık dosyasının içinde _Ios_Fmtflags isimli bir numaralandırma yapılmış ve typedef anahtar kelimesi ile fmtflags adlı bir veri türü tanımlanmıştır. Sonra, _Ios_Fmtflags içinde yer alan her bir değer için fmtflags veri türünden (numaralandırma) statik sabit bir değer oluşturulmuştur.


enum _Ios_Fmtflags 
{ 
  _S_boolalpha 		  = 1L << 0,
  _S_dec 			  = 1L << 1,
  _S_fixed 			  = 1L << 2,
  _S_hex 		      = 1L << 3,
  _S_internal 	      = 1L << 4,
  _S_left 		      = 1L << 5,
  _S_oct 		      = 1L << 6,
  _S_right 		      = 1L << 7,
  _S_scientific 	  = 1L << 8,
  _S_showbase 	      = 1L << 9,
  _S_showpoint 	      = 1L << 10,
  _S_showpos 	      = 1L << 11,
  _S_skipws 		  = 1L << 12,
  _S_unitbuf 	      = 1L << 13,
  _S_uppercase 	      = 1L << 14,
  _S_adjustfield 	  = _S_left | _S_right | _S_internal,
  _S_basefield 	      = _S_dec | _S_oct | _S_hex,
  _S_floatfield 	  = _S_scientific | _S_fixed,
  _S_ios_fmtflags_end = 1L << 16 
};


typedef _Ios_Fmtflags fmtflags;


static const fmtflags boolalpha = _S_boolalpha;
static const fmtflags dec = _S_dec;
static const fmtflags fixed = _S_fixed;
static const fmtflags hex = _S_hex;
static const fmtflags internal = _S_internal;
static const fmtflags left = _S_left;
static const fmtflags oct =  _S_oct;
static const fmtflags right = _S_right;
static const fmtflags scientific = _S_scientific;
static const fmtflags showbase = _S_showbase;
static const fmtflags showpoint = _S_showpoint;
static const fmtflags showpos = _S_showpos;
static const fmtflags skipws = _S_skipws;
static const fmtflags unitbuf = _S_unitbuf;
static const fmtflags uppercase = _S_uppercase;
static const fmtflags adjustfield = _S_adjustfield;
static const fmtflags basefield = _S_basefield;
static const fmtflags floatfield = _S_floatfield;

Başlık dosyasında (ios_base.h) yer alan sabit değerlerin kullanım amaçları aşağıdaki tabloda yer almaktadır:

Şablon sınıfı Kullanım amacı
adjustfield Hizalama özelliğini ayarlayan left, right ve internal değerlerinin her üçünü birden ayarlar.
basefield Sayı sistemlerini temsil eden dec, oct ve hex değerlerinin her üçünü birden ayarlar.
boolalpha Ayarlandığında, ikili sayı sistemi değerleri true ve false anahtar kelimleri ile giriş yapılabilir veya çıktı alınabilir.
dec Sayısal değer çıkışını ondalık sayı sistemine ayarlar.
fixed Ayarlandığında, float değerler normal yöntemle görüntülenir.
floatfield Float değerlerin görüntülenmesini belirleyen scientific ve fixed değerlerinin her ikisini birden ayarlar.
hex Sayısal değer çıkışını onaltılık sayı sistemine ayarlar.
internal Ayarlandığında, herhangi bir işaret veya temel karakter arasına boşluklar ekleyerek bir alanı doldurmak için sayısal bir değer doldurulur.
left Ayarlandığında, çıktı sola hizalanır.
oct Sayısal değer çıkışını sekizlik sayı sistemine ayarlar.
right Ayarlandığında, çıktı sağa hizalanır (Ön tanımlı).
scientific Ayarlandığında, float değerler bilimsel gösterim ile görüntülenir.
showbase Ayarlandığında, sayısal değerlerin sayı sisteminin gösterilmesini sağlar.
showpoint Ayarlandığında, gerekli olsun veya olmasın tüm float değerler için ondalık noktası ve sondaki sıfırların görüntülenmesini sağlar.
showpos Ayarlandığında, pozitif değerlerden önce bir artı işareti görüntülenir.
skipws Ayarlandığında, bir akışa giriş yapılırken başta yer alan boşluk, sekme ve yeni satır gibi beyaz boşluk karakterleri atılır. Devre dışı bırakıldığında, beyaz boşluk karakterleri kabul edilir.
unitbuf Ayarlandığında, tampon her ekleme işleminden sonra boşaltılır.
uppercase Ayarlandığında, bilimsel gösterim için kullanılan e ve onaltılık değerlerin gösterimi için kullanılan karakterler büyük harf olarak görüntülenir.

Bu listede yer alan değerler veri yapılandırma flag değerlerini ayarlamak için kullanılır.

Şimdi, veri yapılandırma flag değerlerinin ön tanımlı değerlerini yazdıracağımız bir örneği incelemeye çalışalım:


#include <iostream>
#include <stdlib.h>

using namespace std;

void deger_yaz(const char *cdeger, long int val)
{
  int bitsayi = 32;
  char *cdizi = (char*) malloc(bitsayi+1);
  cdizi[bitsayi] = '\0';

  unsigned long int u = *(unsigned long int*)&val;
  unsigned long int mask = 1 << (bitsayi-1);
  int id;

  for (id=0; id<bitsayi; id++, mask >>= 1) {
       cdizi[id] = (u & mask) ? '1' : '0';
  }

  printf("%-15s %5ld : %s\n", cdeger, val, cdizi);
  free(cdizi);
}

int main(void)
{
  deger_yaz((const char*) "boolalpha", ios::boolalpha);
  deger_yaz((const char*) "dec", ios::dec);
  deger_yaz((const char*) "fixed", ios::fixed);
  deger_yaz((const char*) "hex", ios::hex);
  deger_yaz((const char*) "internal", ios::internal);
  deger_yaz((const char*) "left", ios::left);
  deger_yaz((const char*) "oct", ios::oct);
  deger_yaz((const char*) "right", ios::right);
  deger_yaz((const char*) "scientific", ios::scientific);
  deger_yaz((const char*) "showbase", ios::showbase);
  deger_yaz((const char*) "showpoint", ios::showpoint);
  deger_yaz((const char*) "showpos", ios::showpos);
  deger_yaz((const char*) "skipws", ios::skipws);
  deger_yaz((const char*) "unitbuf", ios::unitbuf);
  deger_yaz((const char*) "uppercase", ios::uppercase);
  deger_yaz((const char*) "adjustfield", ios::adjustfield);
  deger_yaz((const char*) "basefield", ios::basefield);
  deger_yaz((const char*) "floatfield", ios::floatfield);

  return 0;
}

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

boolalpha           1 : 00000000000000000000000000000001
dec                 2 : 00000000000000000000000000000010
fixed               4 : 00000000000000000000000000000100
hex                 8 : 00000000000000000000000000001000
internal           16 : 00000000000000000000000000010000
left               32 : 00000000000000000000000000100000
oct                64 : 00000000000000000000000001000000
right             128 : 00000000000000000000000010000000
scientific        256 : 00000000000000000000000100000000
showbase          512 : 00000000000000000000001000000000
showpoint        1024 : 00000000000000000000010000000000
showpos          2048 : 00000000000000000000100000000000
skipws           4096 : 00000000000000000001000000000000
unitbuf          8192 : 00000000000000000010000000000000
uppercase       16384 : 00000000000000000100000000000000
adjustfield       176 : 00000000000000000000000010110000
basefield          74 : 00000000000000000000000001001010
floatfield        260 : 00000000000000000000000100000100

Program, deger_yaz() fonksiyonunu kullanarak ios_base.h dosyasındaki veri yapılandırma değerlerinin önce isimlerini sonra onluk ve ikili sayı sistemine göre değerlerini ekrana yazar.

Veri yapılandırma değerlerine doğrudan erişim sağlandığından, değer isimlerinden önce ios:: ön eki kullanılmaktadır.

Veri yapılandırma flag değerlerini ayarlama ve devre dışı bırakma

Fmtflags içinde yer alan flag değerlerini ayarlamak ve devre dışı bırakmak için ios sınıfının elemanları olan setf() ve unsetf() fonksiyonları kullanılır. Fonksiyonların genel yapısı aşağıda gösterilmektedir.

fmtflags setf(fmtflags flags);

void unsetf(fmtflags flags);

Setf() fonksiyonu önceki veri yapılandırma flag değerlerini geri döndürür ve flags ile tanımlanan değerleri devreye sokar. Unsetf() fonksiyonu flags ile tanımlanan değerleri devreden çıkarır ve herhangi bir değer geri döndürmez.

Sayısal değer çıkışını onaltılık sayı sistemine ayarlamak için aşağıdaki işlem satırı kullanılır.

akış-türü.setf(ios::hex);

Burada, akış-türü veri yapılandırmasını ayarlamak istediğimiz akışı göstermektedir.

Şimdi, setf() ve unsetf() fonksiyonlarının çalışmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

int main(void)
{
  cout << 795248 << endl;

  cout.unsetf(ios::dec);
  cout.setf(ios::hex);
  cout.setf(ios::showbase);

  cout << 795248 << endl;

  return 0;
}

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

795248
0xc2270

Program, önce long int bir değeri onlu sayı sisteminde ekrana yazar. Sonra, unsetf() fonksiyonunu kullanarak onluk sayı sisteminin devreden çıkarır, setf() fonksiyonunu kullanarak onaltılık sayı sistemini ve sayı sistemini gösterme özelliğini devreye sokar. Son olarak, long int değeri onaltılık sayı sisteminde ekrana yazar.

Yukarıdaki programın yaptığı işlemin aynısını sadece setf() fonksiyonu kullanarak yapabiliriz. Çoklu görev tanımlanarak iki parametre alacak şekilde düzenlenmiş setf() fonksiyonunun genel yapısı aşağıda gösterilmektedir:

fmtflags setf(fmtflags flag1, fmtflags flag2);

Bu durumda, sadece flag2 ile gösterilen flag değerleri etkilenir. Bu parametrede gösterilen flag değerleri tamamen devre dışı bırakılır, sonra flag1 parametresi ile gösterilen flag değerleri devreye sokulur. Flag2 parametresinde yer almayan flag değerlerine flag1 parametresinde tanımlansa bile herhangi bir işlem yapılmaz. Önceki veri yapılandırma flag değerleri geri döndürülür.

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


#include <iostream>

using namespace std;

int main(void)
{
  cout << 795248 << endl;

  cout.setf(ios::hex, ios::basefield);
  cout.setf(ios::showbase);

  cout << 795248 << endl;

  return 0;
}

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

795248
0xc2270

Program, önce long int bir değeri onlu sayı sisteminde ekrana yazar. Sonra, sadece setf() fonksiyonunu kullanarak ikinci parametre ile tüm sayı sistemlerini devreden çıkarır, birinci parametre ile onaltılık sayı sistemini devreye sokar. Yine setf() fonksiyonunu kullanarak sayı sistemini gösterme özelliğini devreye sokar. Son olarak, long int değeri onaltılık sayı sisteminde ekrana yazar.

Veri yapılandırma flag değerlerini okuma

Veri yapılandırma flag değerlerini okumak için ios sınıfı içinde yer alan flags() üye fonksiyonunu kullanabiliriz. Her bir flag değerinin aktif ayarını geri döndüren flags() fonksiyonunun genel yapısı aşağıdaki şekildedir:

fmtflags flags();

Şimdi, flags() fonksiyonunun çalışmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

void deger_yaz(void)
{
  ios::fmtflags fmt = cout.flags();   // flag değerlerini okuma
  int bitsayi = 32;

  unsigned long int mask = 1 << (bitsayi-1); // 2.147.483.648

  cout << fmt << " = ";

  for ( ; mask; mask >>= 1) {
       cout << ((fmt & mask) ? "1" : "0");
  }

  cout << endl;
}

int main(void)
{
  deger_yaz();               // Mevcut flag değerini ekrana yazar.

  cout.setf(ios::basefield); // dec, hex ve oct flag değerlerini aktif hale getirir.

  deger_yaz();               // Değişiklik sonrası flag değerini ekrana yazar.

  return 0;
}

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

4098 = 00000000000000000001000000000010
4170 = 00000000000000000001000001001010

Program, önce deger_yaz() fonksiyonu içinde flags() fonksiyonunu kullanarak mevcut veri yapılandırma flag değerlerini ekrana yazar. Sonra, setf() fonksiyonu ile basefield flag değerini devreye sokarak dec, hex ve oct flag devrelerini aktif hale getirir. Daha sonra, deger_yaz() fonksiyonu içinde flags() fonksiyonunu kullanarak veri yapılandırma flag değerlerinin son halini tekrar ekrana yazar.

Veri yapılandırma flag değerlerini değiştirme

Bir akış ile ilgili veri yapılandırma flag değerlerini değiştirmek için ios sınıfı içinde yer alan flags() üye fonksiyonunu parametre atayarak kullanabiliriz. Önceki ayarları geri döndüren flags() fonksiyonunun genel yapısı aşağıdaki şekildedir:

fmtflags flags(fmtflags flags);

Şimdi, flags() fonksiyonunun bir parametre değeri alarak çalışmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

void deger_yaz(void)
{
  ios::fmtflags fmt = cout.flags();   // flag değerlerini okuma
  int bitsayi = 32;

  unsigned long int mask = 1 << (bitsayi-1); // 2.147.483.648

  cout << fmt << " = ";

  for ( ; mask; mask >>= 1) {
       cout << ((fmt & mask) ? "1" : "0");
  }

  cout << endl;
}

int main(void)
{
  deger_yaz();                 // Mevcut flag değerini ekrana yazar.

  // dec, hex ve oct flag değerlerini aktif hale getirir. Diğer tüm flag değerlerini devre dışı bırakır.
  cout.flags(ios::basefield);  

  deger_yaz();                 // Değişiklik sonrası flag değerini ekrana yazar.

  return 0;
}

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

4098 = 00000000000000000001000000000010
74 =   00000000000000000000000001001010

Program, önce deger_yaz() fonksiyonu içinde flags() fonksiyonunu kullanarak mevcut veri yapılandırma flag değerlerini ekrana yazar. Sonra, flags() fonksiyonunu ios::basefield parametresi ile kullanarak, dec, hex ve oct flag değerlerini aktif hale getirir. Diğer tüm flag değerleri devreden çıkarılır. Daha sonra, deger_yaz() fonksiyonu içinde flags() fonksiyonunu kullanarak veri yapılandırma flag değerlerinin son halini tekrar ekrana yazar.

Bir önceki programda, setf() fonksiyonu ile kullanılan ios::basefield parametresi sadece dec, hex ve oct değerlerini aktif hale getirirken, burada flags() fonksiyonu ile kullanıldığında, dec, hex ve oct değerlerini aktif hale getirirken diğer tüm değerleri devreden çıkarır.

Veri genişliğini ayarlama

Bir akış ile ilgili verinin çıkışını almak istediğimizde, veri içinde yer alan karakter sayısı kadar yer kaplar. İhtiyaç duyulduğunda, ios sınıfı içinde yer alan width() üye fonksiyonunu kullanabiliriz. Fonksiyonun genel yapısı aşağıdaki şekildedir:

streamsize width(streamsize width);

streamsize width();

Burada width alan genişliğini gösterir. Fonksiyon önceki alan genişliğini geri döndürür. Streamsize veri türü int veri türünden tanımlanmış bir değerdir.

Fonksiyon parametresiz olarak kullanıldığında, aktif alan genişliğini verir.

Minimum alan genişliği ayarlandıktan sonra, bir değer belirtilen genişlikten daha az karakter içeriyorsa, boş kalan alan aktif doldurma karakteriyle (varsayılan değer boşluk karakteridir) doldurulur. Değer minimum alan genişliğinden daha fazla karakter içeriyorsa, alan otomatik olarak genişletilecek ve herhangi bir değer iptal edilmeyecektir.

Şimdi, width() fonksiyonunun kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

int main(void)
{
  cout << 21.754f << endl;
  cout.width(10);
  cout << 21.754f << endl;

  cout << 526 << endl;
  cout.width(10);
  cout << 526 << endl;
  
  return 0;
}

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

21.754
    21.754
526
       526

Program, önce float bir değeri ekrana yazar. Veri genişliğini width() fonksiyonu ile 10 değerine ayarladıktan sonra aynı bir değeri tekrar ekrana yazar. Toplam genişlik 10 ve veri genişliği 6 karakter olduğundan verinin sol tarafına 4 adet boşluk karakteri eklenir. Aynı işlemler int bir değer için tekrarlanır.

Ondalıklı sayıların ondalık bölüm karakter sayısını belirleme

Bir akış ile ilgili ondalıklı sayıların çıkışını almak istediğimizde, ondalık bölüm karakter sayısını belirlemek için, ios sınıfı içinde yer alan precision() üye fonksiyonunu kullanabiliriz. Fonksiyonun genel yapısı aşağıdaki şekildedir:

streamsize precision(streamsize pre);

streamsize precision();

Burada pre ondalık sayı karakter sayısını gösterir. Fonksiyon önceki karakter sayısını geri döndürür. Ön tanımlı değer 6'dır.

Aslında pre ondalıklı bir sayıda yer alan tüm rakamların sayısını belirlemektedir. Nokta işaretinin sol tarafında kalan tamsayı rakam sayısını pre değerinden çıkardığımız için, nokta işaretinin sağ tarafında kalan ondalıklı bölüm rakam sayısı dolaylı olarak belirlenmektedir.

Fonksiyon parametresiz olarak kullanıldığında, aktif ondalık sayı karakter sayısını verir.

Şimdi, precision() fonksiyonunun kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

int main(void)
{
  cout << 125.83541f << endl;
  cout.precision(8);
  cout << 125.83541f << endl;
  
  return 0;
}

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

125.835
125.83541

Program, önce 3 basamaklı tamsayı ve 5 basamaklı olmak üzere toplam 8 basamaklı float bir değeri ekrana yazar. Ancak, ön tanımlı basamak sayısı 6 olduğundan, ondalık kısmının son iki rakamı yazılmaz. Ondalık sayı basamak sayısını, precision() fonksiyonu ile 8 değerine ayarladıktan sonra, aynı değeri tekrar ekrana yazar. Bu kez sayı normal bir şekilde ekrana yazılır.

Boşluk karakterini belirleme

Bir akış ile ilgili verinin çıkışını almak istediğimizde, verilen değer ön tanımlı veya width() fonksiyonu tarafından belirtilen genişlikten daha az karakter içeriyorsa, boş kalan alan, varsayılan değer olan boşluk karakteri ile doldurulur. Ön tanımlı boşluk karakterini değiştirmek için, ios sınıfı içinde yer alan fill() üye fonksiyonunu kullanabiliriz. Fonksiyonun genel yapısı aşağıdaki şekildedir:

char fill(char cd);

char fill();

Burada, cd yeni boşluk doldurma karakteri olarak belirlenir. Fonksiyon önceki boşluk doldurma karakterini geri döndürür.

Fonksiyon parametresiz olarak kullanıldığında, aktif boşluk doldurma karakterini verir.

Şimdi, fill() fonksiyonunun kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

int main(void)
{
  cout.width(10);
  cout << 7452 << endl;

  cout.width(10);
  cout.fill('&');
  cout << 7452 << endl;

  return 0;
}

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

      7452
&&&&&&7452

Program, veri genişliğini 10 karakter olarak ayarlayarak 4 basamaklı bir int değeri ekrana yazar. Ön tanımlı boşluk doldurma karakteri boşluk karakteri olduğundan sayının sol tarafına 4 adet boşluk karakteri ekler. Veri genişliğini tekrar 10 karakter olarak ayarladıktan ve fill() fonksiyonu ile boşluk doldurma karakterini & olarak ayarladıktan sonra, int değeri tekrar ekrana yazar. Bu kez sayının sol tarafına 4 adet & karakteri ekler.

Manipülatörlerle veri yapılandırma

Bir akışın veri yapılandırma parametrelerini değiştirmek için manipülatörler adı verilen özel fonksiyonlar da kullanabiliriz. Sistemde tanımlı olan manipülatörler aşağıdaki tabloda yer almaktadır:

Manipülatör Kullanım amacı
boolalpha Boolalpha flag değerini devreye sokar.
dec Dec flag değerini devreye sokar.
endl Çıkışa bir yeni satır karakteri gönderir ve akışı temizler.
ends Çıkışa boş bir değer gönderir.
fixed Fixed flag değerini devreye sokar.
flush Bir akışı temizler.
hex Hex flag değerini devreye sokar.
internal Internal flag değerini devreye sokar.
left Left flag değerini devreye sokar.
nobooalpha Boolalpha flag değerini devreden çıkarır.
noshowbase Showbase flag değerini devreden çıkarır.
noshowpoint Showpoint flag değerini devreden çıkarır.
noshowpos Showpos flag değerini devreden çıkarır.
noskipws Skipws flag değerini devreden çıkarır.
nounitbuf Unitbuf flag değerini devreden çıkarır.
nouppercase Uppercase flag değerini devreden çıkarır.
oct Oct flag değerini devreye sokar.
resetiosflags Kendisine parametre olarak geçirilen fmtflags içinde yer alan flag değerlerini devreden çıkarır.
right Right flag değerini devreden çıkarır.
scientific Scientific flag değerini devreye sokar.
setbase Sayı sistemini kendisine parametre olarak geçirilen değere ayarlar.
setfill Doldurma karakterini kendisine parametre olarak değere ayarlar.
setiosflags Kendisine parametre olarak geçirilen fmtflags içinde yer alan flag değerlerini devreye sokar.
setprecision Ondalık sayı rakam sayısını kendisine parametre olarak değere ayarlar.
setw Veri genişliğini kendisine parametre olarak değere ayarlar.
showbase Showbase flag değerini devreye sokar.
showpoint Showpoint flag değerini devreye sokar.
showpos Showpos flag değerini devreye sokar.
skipws Skipws flag değerini devreye sokar.
unitbuf Unitbuf flag değerini devreye sokar.
uppercase Uppercase flag değerini devreye sokar.
ws Verinin önünde yer alan beyaz boşluk karakterlerini dikkate almaz.

Parametre alan manipülatörleri kullanmak için, <iomanip> başlık dosyasını programlarımızın başına eklememiz gerekir.

Veri yapılandırmasında ios sınıfı içinde yer alan fonksiyonlar ayrı bir işlem satırında kullanılırken, manipülatör fonksiyonları akışların yer aldığı satırlarda kullanılabilmektedir.

Şimdi, akış veri genişliğini ayarlama işleminin iki farklı yöntemle yapılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>
#include <iomanip>

using namespace std;

int main(void)
{
  // ios sınıfı width() fonksiyonu ile akış veri genişliğini ayarlama
  cout.width(10);
  cout << 142.843f << endl;

  // Manipülatör ile akış veri genişliğini ayarlama
  cout << setw(10) << 142.843f << endl;

  return 0;
}

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

   142.843
   142.843

Program, akış veri genişliğini önce ios sınıfı width() fonksiyonu ile yaparak float bir değeri ekrana yazar. Sonra, aynı işlemi manipülatör setw() ile yaparak değeri tekrar ekrana yazar.

Şimdi, akış verisini onaltılık sayı sistemine geçirme işleminin iki farklı yöntemle yapılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>
#include <iomanip>

using namespace std;

int main(void)
{
  cout << 46325 << endl;

  // ios sınıfı setf() fonksiyonu ile onaltılık sayı sistemine geçiş
  cout.setf(ios::hex, ios::basefield);
  cout.setf(ios::showbase);

  cout << 46325 << endl;

  // Manipülatör ile onaltılık sayı sistemine geçiş
  cout << hex << showbase << 46325 << endl;

  return 0;
}

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

46325
0xb4f5
0xb4f5

Program, akış veri genişliğini önce ios sınıfı setf() fonksiyonu ile yaparak int bir değeri ekrana yazar. Sonra, aynı işlemi hex() ve showbase() manipülatörleri ile yaparak değeri tekrar ekrana yazar.

<< ve >> işlemcilerine çoklu görev tanımlama

C++'da, << ve >> işlemcilerini mevcut ve kullanıcı tanımlı veri tipleri üzerinde giriş ve çıkış işlemleri yapabilmek için çoklu görev tanımlama işlemi uygulayabiliriz.

Akışlara veri gönderen << işlemcisine çoklu görev tanımlama (Overloading)

Oluşturduğumuz bir sınıf için aşağıdaki genel yapı ile bir << işlemcisine çoklu görev tanımlama işlemi uygulayabiliriz:

ostream &operator<<(ostream &stream, const sınıf_adı &nesne)
{
  // Kod bloğu
  return stream;
}

İlk parametre çıkış akışını gösteren bir akış olmalıdır.

İkinci parametre çıkış işlem yapılacak sınıftan oluşturulan bir nesne referansı olmalıdır.

Çoklu görev tanımlama işlemi ile elde edilen << fonksiyonu mutlaka stream değeri geri döndürmelidir.

Şimdi, çoklu görev tanımlama işlemi yapılmış bir << işlemcisinin kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

class sinif {
  public:
    int pubid1, pubid2, pubid3;
    sinif(int pid1, int pid2, int pid3)
    {
      pubid1 = pid1; pubid2 = pid2; pubid3 = pid3;
    }
    void deger_goster()
    {
      cout << "Değişken değerleri (Sınıf fonksiyonu ile): " << pubid1 << " " << pubid2 << " " << pubid3 << endl;
    }
};

// Çoklu görev tanımlanmış << işlemci fonksiyonu
ostream &operator<<(ostream &stream, const sinif &nes)
{
  stream << "Değişken değerleri (<< fonksiyonu ile): " << nes.pubid1 << " " << nes.pubid2 << " " << nes.pubid3 << endl;

  return stream;
}

int main(void)
{
  sinif nes(7, 21, 143);

  nes.deger_goster(); // Değerlerin sınıf fonksiyonu ile gösterilmesi

  cout << nes;        // Değerlerin çoklu görev tanımlanmış << işlemci fonksiyonu ile gösterilmesi

  return 0;
}

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

Değişken değerleri (Sınıf fonksiyonu ile): 7 21 143
Değişken değerleri (<< fonksiyonu ile): 7 21 143

Program, üç adet public int değer, bu int değerlere birer değer atayan constructor fonksiyonu ve bu değerleri ekranda gösteren bir fonksiyon içeren bir sınıf oluşturur. Ayrıca, << işlemcisine çoklu görev tanımlayarak, kendisine geçirilen nesne yoluyla bir sınıf kopyası içindeki değişken değerlerini ekranda gösteren, bir fonksiyon oluşturur. Sonra, oluşturduğu sınıftan bir nesne bildirimi yapar. Nesne yoluyla, sınıf kopyası içinde yer alan değişken değerlerini önce sınıf fonksiyonu ile sonra da çoklu görev tanımlama ile oluşturulan fonksiyon ile ekrana yazar.

Programda, << işlemcisine çoklu görev tanımlama ile oluşturulan fonksiyon sınıfın bir elemanı olarak tanımlanmamıştır.

  • Herhangi bir operatör fonksiyonu bir sınıfın üyesi olduğunda, ilk parametresi işlemci fonsiyonuna yapılan çağrıyı oluşturan nesnedir. Ayrıca bu nesne, operatör fonksiyonunun üyesi olduğu sınıfın bir nesnesidir. Herhangi bir işlemci için yapılan çoklu görev tanımlama ile oluşturulan bir fonksiyon bir sınıfın üyesi ise, en soldaki parametre bu sınıfın bir nesnesi olmalıdır.
  • veri-türü sınıf-adı::operator<<(sınıf_adı nesne)
    
  • Ancak, << işlemcisi ile yapılan çoklu görev tanımlama ile oluşturulan bir fonksiyonda, en solda yer alan ilk parametre bir stream değeri, ikinci parametre ise sınıftan oluşturulmuş bir nesne olmalıdır.
  • ostream &operator<<(ostream &stream, const sınıf_adı &nesne)
    

Bu nedenlerle, << işlemcisi ile yapılan çoklu görev tanımlama ile oluşturulan fonksiyonlar işlem yaptıkları sınıfın üyesi olamazlar.

<< işlemcisine çoklu görev tanımlama uygulanarak oluşturulan fonksiyonlar işlem yaptıkları sınıfın bir üyesi olamadığından, sınıfın private elemanlarına erişim sağlamaları için, bu fonksiyonlar friend fonksiyon olarak tanımlanır.

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


#include <iostream>

using namespace std;

class sinif {
  private:
    int priid1, priid2, priid3;

  public:
    sinif(int pid1, int pid2, int pid3)
    {
      priid1 = pid1; priid2 = pid2; priid3 = pid3;
    }
    void deger_goster()
    {
      cout << "Değişken değerleri (Sınıf fonksiyonu ile): " << priid1 << " " << priid2 << " " << priid3 << endl;
    }

    friend ostream &operator<<(ostream &stream, const sinif &nes);
};

// Çoklu görev tanımlanmış << işlemci fonksiyonu
ostream &operator<<(ostream &stream, const sinif &nes)
{
  stream << "Değişken değerleri (<< fonksiyonu ile): " << nes.priid1 << " " << nes.priid2 << " " << nes.priid3 << endl;

  return stream;
}

int main(void)
{
  sinif nes(7, 21, 143);

  nes.deger_goster(); // Değerlerin sınıf fonksiyonu ile gösterilmesi

  cout << nes;        // Değerlerin çoklu görev tanımlanmış << işlemci fonksiyonu ile gösterilmesi

  return 0;
}

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

Değişken değerleri (Sınıf fonksiyonu ile): 7 21 143
Değişken değerleri (<< fonksiyonu ile): 7 21 143

Program, üç adet private int değer, bu int değerlere birer değer atayan constructor fonksiyonu ve bu değerleri ekranda gösteren bir fonksiyon içeren bir sınıf oluşturur. Ayrıca, << işlemcisine çoklu görev tanımlayarak, kendisine geçirilen nesne yoluyla bir sınıf kopyası içindeki değişken değerlerini ekranda gösteren, friend bir fonksiyon oluşturur. Sonra, oluşturduğu sınıftan bir nesne bildirimi yapar. Nesne yoluyla, sınıf kopyası içinde yer alan değişken değerlerini önce sınıf fonksiyonu ile sonra da çoklu görev tanımlama ile oluşturulan fonksiyon ile ekrana yazar.

Akışlardan veri alan >> işlemcisine çoklu görev tanımlama (Overloading)

Oluşturduğumuz bir sınıf için aşağıda genel yapı ile bir >> işlemcisine çoklu görev tanımlayabiliriz:

istream &operator>>(istream &stream, sınıf_adı &nesne)
{
  // Kod bloğu
  return stream;
}

İlk parametre giriş akışını gösteren bir akış olmalıdır.

İkinci parametre giriş işlemi yapılacak sınıftan oluşturulan bir nesne referansı olmalıdır.

Çoklu görev tanımlama ile elde edilen >> fonksiyonu mutlaka stream değeri geri döndürmelidir.

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


#include <iostream>

using namespace std;

class sinif {
  private:
    int priid1, priid2, priid3;

  public:
    sinif() {
      priid1=0; priid2=0; priid3=0;
    }
    sinif(int pid1, int pid2, int pid3)
    {
      priid1 = pid1; priid2 = pid2; priid3 = pid3;
    }
    void deger_goster(void)
    {
      cout << "Değişken değerleri (Sınıf fonksiyonu ile): " << priid1 << " " << priid2 << " " << priid3 << endl;
    }
    friend ostream &operator<<(ostream &stream, const sinif &nes);
    friend istream &operator>>(istream  &stream, sinif &nes);
};

ostream &operator<<(ostream &stream, const sinif &nes)
{
  stream << "Değişken değerleri (<< fonksiyonu ile): " << nes.priid1 << " " << nes.priid2 << " " << nes.priid3 << endl;

  return stream;
}

istream &operator>>(istream &stream, sinif &nes)
{
  cout << "1.değişken değerini giriniz: ";
  stream >> nes.priid1;

  cout << "2.değişken değerini giriniz: ";
  stream >> nes.priid2;

  cout << "3.değişken değerini giriniz: ";
  stream >> nes.priid3;

  return stream;
}

int main(void)
{
  sinif nes;

  cin >> nes;         // Değerlerin çoklu görev tanımlanmış >> işlemci fonksiyonu ile girilmesi

  nes.deger_goster(); // Değerlerin sınıf fonksiyonu ile gösterilmesi

  cout << nes;        // Değerlerin çoklu görev tanımlanmış << işlemci fonksiyonu ile gösterilmesi

  return 0;
}

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

1.değişken değerini giriniz: 7
2.değişken değerini giriniz: 21
3.değişken değerini giriniz: 35
Değişken değerleri (Sınıf fonksiyonu ile): 7 21 35
Değişken değerleri (<< fonksiyonu ile): 7 21 35

Program, üç adet private int değer, bu int değerlere parametre almadan sıfır değeri atayan bir constructor fonksiyonu, birer int değer atayan parametre alan bir constructor fonksiyonu ve bu değerleri ekranda gösteren bir fonksiyon içeren bir sınıf oluşturur. Sonra, >> işlemcisine çoklu görev tanımlayarak, kendisine geçirilen nesne referansı yoluyla bir sınıf kopyası içindeki değişken değerlerine klavyeden değer alan friend bir fonksiyon oluşturur ve << işlemcisine çoklu görev tanımlayarak, kendisine geçirilen nesne yoluyla bir sınıf kopyası içindeki değişken değerlerini ekranda gösteren, friend bir fonksiyon oluşturur. Oluşturduğu sınıftan bir nesne bildirimi yapar. Nesne yoluyla, sınıf kopyası içinde yer alan değişken değerlerine çoklu görev tanımlama ile oluşturulan fonksiyon ile değer atar. Sonra, değişken değerlerini önce sınıf fonksiyonu ile sonra da çoklu görev tanımlama ile oluşturulan fonksiyon ile ekrana yazar.

Manipülatör fonksiyonları oluşturma

C++'da, kendi manipülatör fonksiyonlarımızı oluşturabiliriz. Özel manipülatör fonksiyonlar oluşturmak, birden fazla giriş/çıkış işlemini tek bir fonksiyonda birleştirme ve giriş/çıkış işlemlerini daha pratik bir şekilde kodlamada fayda sağlar.

Özel manipülatör fonksiyonları giriş ve çıkış akışları için ayrı ayrı olmak üzere tanımlanır. Giriş ve çıkış manipülatör fonksiyonları için kullanılan genel yapı aşağıda gösterilmektedir:

ostream &manipülatör-adı(ostream &stream)
{
  // Kod bloğu
  return stream;
}

istream &manipülatör-adı(istream &stream)
{
  // Kod bloğu
  return stream;
}

Şimdi, veri çıkış işlemleri için bir manipülatör fonksiyonun kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

// Özel manipülatör fonksiyonu
ostream &bg_hex(ostream &stream)
{
  stream.setf(ios::showbase);
  stream.setf(ios::hex, ios::basefield);

  return stream;
}

int main(void)
{
  int id = 21864;

  cout << id << endl;
  cout << bg_hex << id; // Özel manipülatör fonksiyonu kullanımı

  return 0;
}

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

21864
0x5568

Program, ios sınıfı içinde yer alan setf() fonksiyonunun iki farklı kullanımını tek bir fonksiyon içinde yaparak, çıkış işlemi için bir manipülatör fonksiyon oluşturur. Bildirimini yaptığı int bir değeri önce onluk sayı sisteminde sonra manipülatör fonksiyonunu kullanarak onaltılık sayı sisteminde ekrana yazar.

Şimdi, veri giriş ve çıkış işlemleri için manipülatör fonksiyonlarının kullanılmasını bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

// Giriş özel manipülatör fonksiyonu
istream &deger_al(istream &deger)
{
  cout << "Bir değer giriniz: ";

  return deger;
}
// Çıkış özel manipülatör fonksiyonu
ostream &deger_goster(ostream &deger)
{
  deger << "Girilen değer: ";

  return deger;
}

int main(void)
{
  string str, id;

  cin >> deger_al >> str;
  cout << deger_goster << str;

  cout << endl;

  cin >> deger_al >> id;
  cout << deger_goster << id;

  return 0;
}

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

Bir değer giriniz: Program
Girilen değer: Program
Bir değer giriniz: 21
Girilen değer: 21

Program, deger_al() isimli giriş manipülatör fonksiyonu ve deger_goster() isimli çıkış manipülatör fonksiyonu oluşturur. Bu fonksiyonları sırasıyla çalıştırarak önce bir karakter dizisi sonra int bir değer için işlem yapar ve girilen değerleri ekrana yazar.