zeyunal.sitemynet.com
Anasayfa C++ sayfa 1 C++ sayfa 2 İletişim

C++ sayfa 2


buss6.gif

Sabit Nesneler ve Sabit Fonksiyonlar
Diğer veri tiplerinde olduğu gibi bir nesne de sabit (const) olarak tanımlanabilir. Bunun anlamı nesnenin veri alanlarının program boyunca doğrudan ya da dolaylı olarak (fonksiyon çağırarak) değiştirilemeyeceğidir.
const Nokta sn(10,20); // Sabit nokta
Derleyiciler sabit olarak tanımlanan nesnelerin içeriklerinin değişmemesi için bu nesnelerin üye fonksiyonlarının çağırılmasına izin vermezler. Sınıfın yazarları üye veriler üzerinde değişiklik yapmayan fonksiyonları da sabit (const) olarak bildirmelidirler. Sabit nesneler için sadece sabit fonksiyonlar çağırılabilirler.
class Nokta{ // Nokta Sınıfı
int x,y; // Nitelikler: x ve y koordinatları
public:
Nokta(); // Kurucu fonksiyon bildirimi
bool git(int, int); // Noktanın hareket etmesini sağlayan fonksiyon
void goster() const; // sabit fonksiyon, koordinatları ekrana çıkartır
};

void Nokta::goster() const
{
cout << "X= " << x << ", Y= " << y << endl;
}
Sınıfın verileri üzerinde değişiklik yapmayan fonksiyonların sabit olarak tanımlanmaları hata olasılığını ve hata çıktığında incelenmesi gereken fonksiyonların sayısını azaltmaktadır. Bu nedenle bu özelliğe uyan tüm üye fonksiyonlar sabit olarak tanımlanmalıdır.
// Sabit nesneler ve sabit fonksiyonlar

#include <iostream.h>
//using namespace std;

class Nokta{ // Nokta Sınıfı
int x,y; // Nitelikler: x ve y koordinatları
public:
Nokta(int,int); // Kurucu fonksiyon bildirimi
int git(int, int); // Noktanın hareket etmesini sağlayan fonksiyon
void goster() const; //sabit fonksiyon, koordinatları ekrana çıkartır
};

// ***** Üye Fonksiyonların Gövdeleri *****

// Parametreli Kurucu Fonksiyon
Nokta::Nokta(int ilk_x, int ilk_y)
{
cout << "Kurucu fonksiyon calisiyor..." << endl;
if ( ilk_x < 0 ) // Verilen değer negatifse
x = 0; // Koordinat sıfırlanıyor
else
x = ilk_x;
if ( ilk_y < 0 ) // Verilen değer negatifse
y = 0; // Koordinat sıfırlanıyor
else
y = ilk_y;
}

// Noktanın hareket etmesini saglayan fonksiyon
int Nokta::git(int yeni_x, int yeni_y)
{
if (yeni_x >=0 && yeni_y>=0){
x = yeni_x; // x koordinatına yeni değer atandı
y = yeni_y; // y koordinatına yeni değer atandı
return 1;
}
return 0;
}

// Noktanın koordinatlarını ekrana çıkaran fonksiyon (sabit)
void Nokta::goster() const
{
cout << "X= " << x << ", Y= " << y << endl;
}

// -------- Ana Program -------------
int main()
{
const Nokta sn(10,20); // sabit nokta
Nokta n(0,50); // sabit olmayan nokta
sn.goster(); // Dogru
//sn.git(30,15); // HATA
sn.goster();
n.git(100,45); // Dogru
n.goster();
return 0;
}
/*
Kurucu fonksiyon calisiyor...
Kurucu fonksiyon calisiyor...
X= 10, Y= 20
X= 10, Y= 20
X= 100, Y= 45 */

Statik Üyeler
Bir sınıftan tanımlanan her nesne için bellekte farklı veri alanları yaratılır. Ancak bazı durumlarda tüm nesnelerin ortak bir veriyi (bellek gözünü) paylaşmaları gerekli olabilir. Bellekte sadece tek kopyasının yaratılması istenen üye veriler static olarak tanımlanmalıdırlar.

class A{
char c;
static int i;
};
int main()
{
A p,q,r;
:
}
Statik üyeler nesne tanımlanmadan önce bellekte yaratılırlar. Statik üyeler de diğerleri gibi özel (private) veya açık (public) olabilirler. Açık statik üyeler, global veriler gibi programın tüm alanlarından erişilebilirler. Bunun için sınıfın ismi ve &#8216;scop&#8217; operatörü (::) kullanılır. A::i= 5;
Statik üyeler özel (private) olarak tanımlanırsa bu üyelere doğrudan erişmek mümkün olmaz. Henüz nesne yaratılmadan önce bu verilere başlangıç değeri atamak için statik fonksiyonlar tanımlanır.
// static veriler ve fonksiyonlar

#include <iostream>
using namespace std;

class A{
char c;
static unsigned int sayac; // Sınıftan yaratılan nesnelerin sayısı
public:
static void sifirla(){sayac=0;} // Statik metot, sayacı sıfırlamak için
A(){sayac++; cout<< "\n"<< "Kurucu "<< sayac;} //Kurucu
~A(){sayac--; cout<< "\n"<< "Yok edici "<< sayac;} //Yok edici
};

unsigned int A::sayac; // sayac bellekte tanımlanıyor

//----- Ana program -------
int main()
{
cout<<"\n 1. BLOK Giris ............";
A::sifirla(); // Statik fonksiyon çağrılıyor
A a,b,c;
{
cout<<"\n 2. BLOK Giris ............";
A d,e;
cout<<"\n 2. BLOK Bitisi ............";
}
cout<<"\n 1. BLOK Bitisi ............";
return 0;
}

/*
1. BLOK Giris ............
Kurucu 1
Kurucu 2
Kurucu 3
2. BLOK Giris ............
Kurucu 4
Kurucu 5
2. BLOK Bitisi ...........
Yok edici 4
Yok edici 3
1. BLOK Bitisi ...........
Yok edici 2
Yok edici 1
Yok edici 0
*/
18/10/2004
Nesne Atamak
Bir nesne aynı tipten başka bir nesneye atanabilir. Normalde, nesne diğer nesneye atandığında tüm veri üyelerinin bit bit kopyası alınmaktadır. Örnek olarak ol adındaki nesne o2 adında diğer bir nesneye atandığında ol&#8217;e ait verilerin tümü o2&#8217;nin eşdeğer üyelerinin içi ne kopyalanır.
// Nesne atamalarına bir örnek.
#include <iostream>
using namespace std;

class myclass {
int a, b;
public:
void set(int i, int j) { a = i; b = j; }
void show() { cout << a << ' ' << b << "\n"; }
};

int main()
{
myclass o1, o2;

o1.set(10, 4);

//o1&#8217; o2&#8217;ye ata
o2 = o1;

o1.show();
o2.show();

return 0;
}

Burada ol nesnesi, kendisine ait olan a ve b üye değişkenlerine 10 ve 4 değerlerini koyar. Daha sonra, ol, o2&#8217;ye atanır. Bu, ol.a&#8217;nın o2.a&#8217;ya atanmasını ve ol.b&#8217;nin de o2.b&#8217;ye atanmasına neden olur. Program çalıştırıldığında, ekranda aşağıdaki sonuçlar görülür.
10 4
10 4
Bir nesne diğerine atadığında sadece bu nesnelerin içerisindeki veriler eşitlenir. İki nesne halen birbirinden ayrı durumdadır. Örneğin atama işleminden sonra ol.a&#8217;ya değer atamak için ol.set( )&#8217;in çağrılması, o2 ve ona ait a değeri üzerinde bir etki yapmaz.
Örnekler
Sadece aynı tipteki nesneler arasında atama işlemleri yapılabilir. Eğer nesneler aynı tipte değilse bu bir derleme hatasına neden olur. Üstelik tiplerin fiziksel olarak aynı olmaları da yeterli değildir, tip adları da aynı olmak zorundadır. Örneğin şu program doğru değildir:
// This program has an error.
#include <iostream>
using namespace std;

class myclass {
int a, b;
public:
void set(int i, int j) { a = i; b = j; }
void show() { cout << a << ' ' << b << "\n"; }
};

/* Bu sınıf myclass a benzemektedir, fakat farklı bir sınıf adı
Kullanmaktadır ve bu yüzden derleyiciye farklı bir tip olarak görünür. class yourclass {
int a, b;
public:
void set(int i, int j) { a = i; b = j; }
void show() { cout << a << ' ' << b << "\n"; }
};

int main()
{
myclass o1;
yourclass o2;

o1.set(10, 4);

o2 = o1; // HATA, nesneler aynı tipte değil

o1.show();
o2.show();

return 0;
}

Bu durumda, myclass ve yourclass fiziksel olarak aynı olmasına rağmen tip adları farklı olduğundan, derleyici onlara farklı tipteymişler gibi davranır.
Gerçek bir program içerisinde meydana gelen bu tip problemler dinamik bellek ayırma sistemini bozacaktır ve hatta büyük olasılıkla programın kilitlenmesine neden olacaktır. Bir önceki örnekteki gibi, bir nesne diğerine atanırken daha sonra ihtiyacınız olabilecek bilgileri yok etmediğinizden emin olmalısınız.
Fonksiyonlara Nesne Aktarmak
Nesneler, tıpkı diğer veri tiplerinin aktarılmasında olduğu gibi argümanlar halinde fonksiyonlara aktarılabilir. Fonksiyonun parametresi sınıf tipinde bildirilir ve sonra fonksiyonun çağrılışında bu sınıfa ait bir nesne argüman olarak kullanılır. Fonksiyona diğer veri tiplerin de olduğu gibi varsayılan olarak nesnelerin değerleri aktarılır.
Örnekler
1. Fonksiyonlara nesne aktarılmasıyla ilgili kısa bir örnek:
#include <iostream>
using namespace std;

class samp {
int i;
public:
samp(int n) { i = n; }
int get_i() { return i; }
};

// Return square of o.i.
int sqr_it(samp o)
{
return o.get_i() * o.get_i();
}

int main()
{
samp a(10), b(2);

cout << sqr_it(a) << "\n";
cout << sqr_it(b) << "\n";

return 0;
}
/*
100
4 */
Bu program, i tamsayısını içeren, samp adlı bir sınıf oluşturur. sqr_it() fonksiyonunun samp tipinde bir argümanı vardır ve bu fonksiyon nesneye ait i değerinin karesini döndürür. Bu program, ekranda 4 ve 100 değerlerini gösterecektir.
2. Belirtildiği gibi, C + + &#8216;da nesneler de dahil olmak üzere tüm parametrelerin aksi söylenmediği sürece değerleri aktarılır. Bunun anlamı şudur: argümanın bit bit kopyası yapılır ve bu kopya, fonksiyon tarafından kullanılan kopyadır. Sonuç olarak, fonksiyonun içerisinde nesneye yapılan değişiklikler kopyası alınan nesneyi etkilemez.
/*
Unutmayın nesnelerin de diğer parametreler gibi değerleri aktarılır.
Böylece fonksiyonların içerisindeki parametrelerde meydana gelen
değişiklikler, çağırma işleminde kullanılan nesneler üzerinde bir etki
yapmaz.*/
#include <iostream>
using namespace std;

class samp {
int i;
public:
samp(int n) { i = n; }
void set_i(int n) { i = n; }
int get_i() { return i; }
};

/* o.i ye kendisinin karesi konulur. Fakat sqr_it()&#8217;yiçağırmakta kullanılan nesne bu değişiklikten etkilenmez.
*/
void sqr_it(samp o)
{
o.set_i(o.get_i() * o.get_i());

cout << "a &#8216;nın kopyası şu değerin i değerine sahiptir : " << o.get_i();
cout << "\n";
}

int main()
{
samp a(10);

sqr_it(a); // a &#8216;nındeğeri aktarılır

cout << "Fakat a.i main&#8217;in içerisinde değişikliğe uğramaz ";
cout << a.get_i(); // 10 gösterir

return 0;
}

Bu program şu şekilde bir çıkış verir:
a&#8217;nın kopyası şu değerin i değerine sahiptir : 100
Fakat, a.i main&#8217;in içerisinde değişikliğe uğramaz : 10
3. Tıpkı diğer değişken tipleri gibi, nesne adresleri de fonksiyonlara aktarılabilir, bu şekilde de çağırma işleminde kullanılan argüman, fonksiyon tarafından değiştirilebilir. Aşağıdaki program bir önceki örneğin bunu yapacak şekilde değiştirilmesiyle oluşturulmuştur, bu program hatta sqr_it( )&#8216;nin çağrılışında adresi kullanılan nesnenin değerini değiştirmektedir.
/*
Şimdi, nesnenin adresi sqr_it() aktarılıyor, fonksiyon, çağırma işleminde adresi kullanılan argümanın değerini değiştirebilir. */
listing 11
/*
Şimdi nesnenin adresi sqr_it()&#8217;e aktarılıyor, fonksiyon, çağırma
işleminde adresi kullanılan argümanın değerini değiştirebilir.*/

#include <iostream>
using namespace std;

class samp {
int i;
public:
samp(int n) { i = n; }
void set_i(int n) { i = n; }
int get_i() { return i; }
};

/* o.i ye kendisinin karesi konuluyor. Bu, çağıran argümanıetkiliyor. */

void sqr_it(samp *o)
{
o->set_i(o->get_i() * o->get_i());

cout << " a&#8217;nın kopyası şu değerin i değerine sahiptir :" << o->get_i();
cout << "\n";
}

int main()
{
samp a(10);

sqr_it(&a); // a&#8217;nın adresi to sqr_it()&#8217;e gönderiliyor

cout << "Şimdi main&#8217;in içindeki a değişti: ";
cout << a.get_i(); // 100 gösterir

return 0;
}
Bu program aşağıdaki sonucu verir:
a&#8217;nın kopyası şu değerin i değerine sahiptir : 100
Şimdi, main&#8217;in içerisindeki a değişti : 100
4. Nesneler fonksiyonlara gönderilirken bu nesnelerin kopyalarının yapılması, yeni nesnelerin oluşturulması anlamına gelir. Nesnenin gönderildiği fonksiyon sonlandığında argümanın kopyası yok edilir. Bu durumda aklımıza iki soru gelmesi gerekiyor: Nesnenin yapıcısı kopyanın yapılışı sırasında çağrılıyor mu? Nesnenin yıkıcısı kopya yok edilirken çağrılıyor mu? Cevap ilk başta sizi biraz şaşırtabilir.
Nesnenin kopyası oluşturulurken yapıcı fonksiyon çağrılmaz. Üzerinde biraz düşünürsek bunun nedenini kolayca anlayabiliriz. Yapıcı fonksiyonu, genelde nesneyi hazır hale getirmek için kullanılır ve bu nedenle zaten mevcut bir nesnenin fonksiyona gönderilecek kopyasının oluşturulması sırasında çağrılmamalıdır. Bu fonksiyon çağrılırsa nesnenin içeriğini değişecektir. Oysa biz bu nesnenin o anki durumunun fonksiyona gönderilmesini istiyoruz, başlangıçtaki durumunun değil. Fakat fonksiyon sonlandığında ve kopya yok edildiğinde yıkıcı fonksiyonu çağrılır. Bunun nedeni, nesnenin yok olması sırasında yapılması gereken bazı işlemlerin gerçekleştirilmesi gerektiğidir. Örneğin, kopya için serbest bırakılması gereken bazı bellek yerleri ayrılmış olabilir.
Özetlemek gerekirse, nesnenin kopyası oluşturulurken bu kopya fonksiyonun bir argümanı olarak kullanıldığından, yapıcı fonksiyonu çağrılmaz. Fakat kopya yok edilirken (bu genellikle bir değer döndürdüğünde alandan çıkması şeklinde olur) yıkıcı fonksiyonu çağrılır..
Aşağıdaki örneği incelersek bunu daha iyi anlayacağız:
#include <iostream>
using namespace std;

class samp {
int i;
public:
samp(int n) {
i = n;
cout << "Oluşturuluyor\n";
}
~samp() { cout << "Yok ediliyor\n"; }
int get_i() { return i; }
};

// o.i nin karesi döndürülür.
int sqr_it(samp o)
{
return o.get_i() * o.get_i();
}

int main()
{
samp a(10);

cout << sqr_it(a) << "\n";

return 0;
}
Bu fonksiyon aşağıdaki sonucu verir:
Oluşturuluyor
Yok ediliyor
100
Yok ediliyor
Sizin de göreceğiniz gibi, yapıcı fonksiyonu sadece bir kere, o da a&#8217;nın oluşturulması sırasında çağrılır. Fakat yıkıcı iki kere çağrılır. Bu çağrılardan bir tanesi a, sqr_it( )&#8216;ye gönderildiğinde oluşturulan kopya içindir. Diğeri ise a&#8217;nın kendisi içindir.
Argümanın kopyası olan nesneye ait yıkıcının fonksiyon bittiğinde çalıştırılması bazı problemlere yol açabilir. Örneğin, argüman olarak kullanılan nesne dinamik olarak bellekte yer kaplıyor ve yok edildiğinde bu belleği serbest bırakıyorsa, bu nesnenin kopyasına ait yıkıcı çağrıldığında aynı bellek bölgesini serbest bırakmaya çalışacaktır. Bu da kopyası oluşturulan nesneye zarar verecektir ve onu kullanılamaz hale getirecektir. (Örnek olarak bu bölümün en başındaki 2. Alıştırmayı inceleyebilirsiniz.) Bu hataya karşı tedbir alın ve kopya nesneye ait yıkıcı fonksiyonunun orijinal nesneyi etkileyecek yan etkilere yol açmadığına emin olun.
Tahmin edeceğiniz gibi, kopyaya ait yıkıcının gerçek nesnenin gerek duyabileceği verilen yok etmesi problemini, nesnenin kendisini göndermek yerine adresini göndererek çözebilirsiniz Adres gönderildiğinde yeni bir nesne oluşturulmaz ve bunun sonucu olarak da fonksiyon sona erdiğinde yıkıcı çağrılmaz. (Bir sonraki bölümde göreceğimiz gibi, C+ + bu konuda çok çeşitli alternatifler sunmaktadır. Fakat bu soruna getirilebilecek en iyi çözümü, copy constructor (kopya yapıcısı) adındaki özel yapıcı tipini öğrendikten sonra kullanabileceğiz. Kopya yapıcıları kullanarak nesnelere ait kopyaların tam olarak nasıl oluşturulacağını tanımlayabilirsiniz. (Kopya yapıcılar konusu Bölüm 5&#8217;de ele alınmıştır.)
Fonksiyonlardan Nesne Döndürmek
Tıpkı fonksiyonlara nesne gönderebildiğimiz gibi, fonksiyonlardan nesne de döndürebiliriz. Bunun için öncelikle fonksiyonu sınıf tipi döndürür şekilde bildirmeniz gerekir. İkinci olarak da bu tipte bir nesneyi normal return deyimini kullanarak döndürürüz.
Fakat fonksiyonlardan nesne döndürülmesi konusunda anlaşılması gereken önemli bir nokta daha var: Bir nesne fonksiyon tarafından döndürüldüğünde, otomatik olarak, döndürülen değeri saklayan geçici bir nesne oluşturulur. Gerçekte fonksiyon tarafından döndürülen nesne budur. Değer döndürüldükten sonra bu nesne yok edilir. Bu geçici nesnenin yok edilmesi bazı durumlarda Örnek 2&#8217;de olduğu gibi beklenmedik yan etkilere neden olabilir.
Örnekler
1. Nesne döndüren fonksiyonlara bir örnek:
// Nesne döndürülüyor
#include <iostream>
#include <cstring>
using namespace std;

class samp {
char s[80];
public:
void show() { cout << s << "\n"; }
void set(char *str) { strcpy(s, str); }
};

// samp tipinde bir nesne döndürülüyor
samp input()
{
char s[80];
samp str;

cout << " Bir katar girin: ";
cin >> s;

str.set(s);

return str;
}

int main()
{
samp ob;

// döndürülen nesne ob&#8217;a atanıyor
ob = input();
ob.show();

return 0;
}
/*Bir katar girin: bu_bir_denemedir
bu_bir_denemedir */

Bu örnekte input() fonksiyonu, str adında yerel bir nesne oluşturur ve sonra da klavyeden bir katar okur. Bu katar str.s&#8217;in içine kopyalanır ve sonra str, fonksiyon tarafından döndürülür. Nesne, döndürüldükten sonra main( )&#8216;in içerisindeki ob&#8217;a atanır.
2. Fonksiyonlardan nesne döndürürken eğer bu nesnelerin yıkıcı fonksiyonları varsa dikkatli olmalısınız.Çünkü bu değer döndürülür döndürülmez, döndürülen nesne erim alanının dışına çıkar. Mesela diyelim ki, fonksiyon tarafından döndürülen nesnenin dinamik ayrılmış belleğini serbest bırakan bir yıkıcısı var. Bu bellek, döndürülen değerin atandığı nesne onu hala kullanıyor da olsa serbest bırakılacaktır. Örnek olarak bir önceki programın hatalı hale getirilmiş şeklini inceleyelim:
// Nesnenin döndürülmesi ile ortaya çıkan bir hata.
//Nesnenin döndürülmesi ile ortaya çıkan bir hata.
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

class samp {
char *s;
public:
samp() { s = '\0'; }
~samp() { if(s) free(s); cout << "s serbest bırakılıyor\n"; }
void show() { cout << s << "\n"; }
void set(char *str);
};

// Load a string.
void samp::set(char *str)
{
s = new char[strlen(str)+1];
if(!s) {
cout << "Bellekte yer ayırma hatası\n";
exit(1);
}

strcpy(s, str);
}

// samp tipinde bir nesne döndürülüyor.
samp input()
{
char s[80];
samp str;

cout << " Bir katar girin: ";
cin >> s;

str.set(s);
return str;
}

int main()
{
samp ob;

// döndürülen nesne ob&#8217;a atanmaya çalışılıyor
ob = input(); // Bu bir hataya yol açar!!!!
ob.show();

return 0;
}
/* Bir katar girin: merhaba
s serbest b²rak²l²yor
HATA mesajı geliyor */
Program size şu ekran çıkışını verecektir: (HATALI)
Bir katar girin: Merhaba
s serbest bırakılıyor
s serbest bırakılıyor Buradan sonrası gelmiyor
Merhaba
s serbest bırakılıyor
Null pointer assignınent (Sıfır gösterici)
samp&#8217;in yıkıcı fonksiyonunun üç kere çağrıldığına dikkat edin. Bu fonksiyon ilk olarak input() geri döndürüldüğünde str yerel nesnesinin erim alanından çıkmasıyla çağrılır.
~samp( )&#8216;in ikinci çağrılışı input() tarafından döndürülen geçici nesnenin yok edilmesinde meydana gelir. Unutmayın, nesneler fonksiyondan döndürüldüğünde, otomatik olarak, döndürülen değeri saklayan (size) görünmeyen geçici bir nesne oluşturulur. Bu durumda nesne, fonksiyonun döndürdüğü değer olan str&#8217;nin bir kopyasıdır. Sonuç olarak, fonksiyon sona erdikten sonra geçici nesnenin yıkıcısı çalıştırılır. Son olarak da main( )&#8216;in içerisindeki ob nesnesinin yıkıcısı program sonlandığında çağrılır.
Bu durumda sorun şudur: Yıkıcının ilk çalıştırılışında, input() tarafından alınan katar için ayrılan bellek yeri serbest bırakılır. Böylece samp&#8217;ın yıkıcısına yapılan diğer iki çağrı, zaten serbest bırakılmış olan bir dinamik bellek parçasını serbest bırakmaya çalışmakla kalmaz, aynı zamanda bellekte dinamik yer ayırma sistemini de yok eder; bunu, çıkan &#8220;Null pointer assignment.&#8221; (Sıfir gösterici ataması) mesajın dan da anlayabiliriz. Bu programı denediğinizde, derleyicinize, deneme için kullanılan bellek modeline vb göre hata mesajını görebilirsiniz veya göremeyebilirsiniz. Bu örnekten öğrenmemiz gereken şu: Nesneler fonksiyonlardan döndürüldüğün de, dönüş değerini etkilemekte kullanılan geçici nesnenin kendi yıkıcı fonksiyonu da çağrılır. Böylelikle bu durumun tehlikeli olduğu nesneleri döndürmekten kaçınmalısınız. Bölüm 5&#8217;te öğreneceğiniz gibi, bu durumu halletmek için bir kopya yapıcı kullanmanız mümkündür.

Nesnelerin Fonksiyonlara Parametre Olarak Aktarılması
Aksi zorunlu olmadıkça, nesneler fonksiyonlara referanslar yoluyla aktarılmalı. Benzer şekilde fonksiyonlardan geri döndürülen nesneler için de eğer mümkünse referanslar kullanılmalıdır. Parametre aktarımında referanslar kullanılmazsa nesnelerin içerdiği değerler
yığına kopyalanır ( call by value). Eğer sınıfın içinde bir kopyalama fonksiyonu varsa yığına kopyalama işi için de programcının yazdığı bu fonksiyon canlanacaktır.
// Nesnelerin fonksiyonlara değer olarak aktarılması
// Uygun Yöntem DEĞİLDİR!

Örnek 1
#include <iostream>
using namespace std;
// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
float re,im; // reel ve sanala kısımlar
static unsigned int sayac; // Bu sınıftan yaratılan nesne sayisinı tutar
public:
ComplexT(float re_in=0,float im_in=1); // Kurucu
ComplexT(const ComplexT &); // Kopyalama kurucusu
ComplexT topla(ComplexT) const; // Parametre olarak nesne alan metot
void goster() const; // Bilgileri ekrana çıkarır
static void sifirla(){sayac=0;} // Sayacı sıfırlayan static metot
~ComplexT(); // Yok edici
};

//Parametrsiz çağırılabilen kurucu
// İlk değerleri atar. Yaratılan nesne sayisinı arttırır.
ComplexT::ComplexT(float re_in,float im_in)
{
re=re_in;
im=im_in;
sayac++; // Nesne sayisi arttırıldı
cout<< endl << "Kurucu calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}

// Kopyalama Kurucusu
// Nesnenin verilerini kopylar
// Yaratılan nesne sayisinı arttırır
ComplexT::ComplexT(const ComplexT &c)
{
re=c.re; // Elemanlar kopyalanıyor
im=c.im;
sayac++; // Nesne sayisi arttırıldı
cout<< endl << "Kopyalama kurucusu calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}

// İki karmaşık sayıyı toplayıp üçüncü bir sayı elde eden metot
// Giriş parametresi değer olarak (call by value) alınan bir nesnedir.
// Parametrenin bu şekilde alınması uygun DEĞİLDİR!
ComplexT ComplexT::topla(ComplexT c) const
{
cout<< endl << "Toplama metodu calisiyor";
ComplexT sonuc; // Sonucun yazılacağı 3. nesne
sonuc.re=re+c.re; // Alt alanlar toplanıyor
sonuc.im=im+c.im;
return sonuc; // sonuc nesnesi geri döndürülüyor.
}

// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << endl << "re= " << re << " , im= " << im;
cout << " Nesne sayisi= " << sayac;
}

// Yok edici fonksiyon
// Yaratrılan nesne sayisinı bir azaltır
ComplexT::~ComplexT()
{
sayac--; // Nesne sayisi azaltıldı
cout<< endl << "Yok edici calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}


unsigned int ComplexT::sayac; // static sayac icin bellekte yer ayrıldı

//------ Ana program -------
int main()
{
ComplexT::sifirla(); // sayac sıfırlandı
ComplexT z1,z2,z3; // Üç adet nesne yarıtıldı
z3=z1.topla(z2); // z3 = z1 + z2 işlemi yapılıyor.
z3.goster();
return 0;
}
/*
Kurucu calisti. Nesne sayisi = 1
Kurucu calisti. Nesne sayisi = 2
Kurucu calisti. Nesne sayisi = 3
Kopyalama kurucusu calisti. Nesne sayisi = 4
Toplama metodu calisiyor
Kurucu calisti. Nesne sayisi = 5
Kopyalama kurucusu calisti. Nesne sayisi = 6
Yok edici calisti. Nesne sayisi = 5
Yok edici calisti. Nesne sayisi = 4
Yok edici calisti. Nesne sayisi = 3
re= 0 , im= 2 Nesne sayisi= 3
Yok edici calisti. Nesne sayisi = 2
Yok edici calisti. Nesne sayisi = 1
Yok edici calisti. Nesne sayisi = 0 */

Nesnelerin fonksiyonlara değer olarak aktarılması hem bellekte daha fazla yer harcanmasına neden olur hem de programın çalışmasını yavaşlatır. Bu nedenle, eğer aksi zorunlu değilse, fonksiyonlara nesnelerin değerleri değil referansları aktarılmalıdır. Referans yoluyla aktarılan nesnelerin içeriklerinin fonksiyon içinde değiştirilmesi istenmiyorsa bu aktarım sabit referanslar ile yapılmalıdır.

Örnek 2
// Nesnelerin fonksiyonlara referans olarak aktarılması
#include <iostream>
using namespace std;
// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
float re,im; // reel ve sanala kısımlar
static unsigned int sayac; // Bu sınıftan yaratılan nesne sayisinı tutar
public:
ComplexT(float re_in=0,float im_in=1); // Kurucu
ComplexT(const ComplexT &); // Kopyalama kurucusu
ComplexT topla(const ComplexT &) const; // Parametre olarak nesneye referans alan metot
void goster() const; // Bilgileri ekrana çıkarır
static void sifirla(){sayac=0;} // Sayacı sıfırlayan static metot
~ComplexT(); // Yok edici
};

//Parametrsiz çağırılabilen kurucu
// İlk değerleri atar. Yaratılan nesne sayisinı arttırır.
ComplexT::ComplexT(float re_in,float im_in)
{
re=re_in;
im=im_in;
sayac++; // Nesne sayisi arttırıldı
cout<< endl << "Kurucu calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}

// Kopyalama Kurucusu
// Nesnenin verilerini kopylar
// Yaratılan nesne sayisinı arttırır
ComplexT::ComplexT(const ComplexT &c)
{
re=c.re; // Elemanlar kopyalanıyor
im=c.im;
sayac++; // Nesne sayisi arttırıldı
cout<< endl << "Kopyalama kurucusu calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}

// İki karmaşık sayıyı toplayıp üçüncü bir sayı elde eden metot
// Giriş parametresi referans olarak (call by address) alınan bir nesnedir.
ComplexT ComplexT::topla(const ComplexT & c) const
{
cout<< endl << "Toplama metodu calisiyor";
ComplexT sonuc; // Sonucun yazılacağı 3. nesne
sonuc.re=re+c.re; // Alt alanlar toplanıyor
sonuc.im=im+c.im;
return sonuc; // sonuc nesnesi geri döndürülüyor.
}

// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << endl << "re= " << re << " , im= " << im;
cout << " Nesne sayisi= " << sayac;
}

// Yok edici fonksiyon
// Yaratrılan nesne sayisinı bir azaltır
ComplexT::~ComplexT()
{
sayac--; // Nesne sayisi azaltıldı
cout<< endl << "Yok edici calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}


unsigned int ComplexT::sayac; // static sayac icin bellekte yer ayrıldı

//------ Ana program -------
int main()
{
ComplexT::sifirla(); // sayac sıfırlandı
ComplexT z1,z2,z3; // Üç adet nesne yarıtıldı
z3=z1.topla(z2); // z3 = z1 + z2 işlemi yapılıyor.
z3.goster();
return 0;
}
/*
Kurucu calisti. Nesne sayisi = 1
Kurucu calisti. Nesne sayisi = 2
Kurucu calisti. Nesne sayisi = 3
Toplama metodu calisiyor
Kurucu calisti. Nesne sayisi = 4
Kopyalama kurucusu calisti. Nesne sayisi = 5
Yok edici calisti. Nesne sayisi = 4
Yok edici calisti. Nesne sayisi = 3
re= 0 , im= 2 Nesne sayisi= 3
Yok edici calisti. Nesne sayisi = 2
Yok edici calisti. Nesne sayisi = 1
Yok edici calisti. Nesne sayisi = 0 */
Hatırlatma: Yerel değişkenler referans yolu ile geri döndürülemez. Bu nedenle aşağıdaki fonksiyon hatalıdır.

ComplexT & ComplexT::topla(const ComplexT & c) const
{
ComplexT sonuc; // Yerel nesne
sonuc.re=re+c.re; // Alt alanlar toplanıyor
sonuc.im=im+c.im;
return sonuc; // HATA!
}

Fonksiyonlardaki Geçici Nesnelerin Azaltılması
Örnek 2 de toplama fonksiyonunda iki nesneyi toplamak için geçici bir nesne yaratılmaktadır. Bu işlem kurucu ve yok edici fonksiyonların çalışmasına neden olmaktadır.
Ardından bu geçici nesne fonksiyondan geri gönderileceği için (return gecici;) yığına kopyalanmaktadır. Bu işlemleri azaltmak için geçici bir nesne yaratmak yerine, işlemler geçici değişkenler üzerinde yapılır. Daha sonra fonksiyondan değer döndürülürken nesne yaratılır.
}

Örnek3
// Fonksiyonlardaki yerel nesnelerin azaltılması
#include <iostream>
using namespace std;

// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
float re,im; // reel ve sanal kısımlar
static unsigned int sayac; // Bu sınıftan yaratılan nesne sayisinı tutar
public:
ComplexT(float re_in=0,float im_in=1); // Kurucu
ComplexT(const ComplexT &); // Kopyalama kurucusu
ComplexT topla(const ComplexT &) const; // Parametre olarak nesneye referans alan metot
void goster() const; // Bilgileri ekrana çıkarır
static void sifirla(){sayac=0;} // Sayacı sıfırlayan static metot
~ComplexT(); // Yok edici
};

//Parametrsiz çağırılabilen kurucu
// İlk değerleri atar. Yaratılan nesne sayisinı arttırır.
ComplexT::ComplexT(float re_in,float im_in)
{
re=re_in;
im=im_in;
sayac++; // Nesne sayisi arttırıldı
cout<< endl << "Kurucu calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}

// Kopyalama Kurucusu
// Nesnenin verilerini kopylar
// Yaratılan nesne sayisinı arttırır
ComplexT::ComplexT(const ComplexT &c)
{
re=c.re; // Elemanlar kopyalanıyor
im=c.im;
sayac++; // Nesne sayisi arttırıldı
cout<< endl << "Kopyalama kurucusu calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}

// İki karmaşık sayıyı toplayıp üçüncü bir sayı elde eden metot
// Giriş parametresi referans olarak (call by address) alınan bir nesnedir.
ComplexT ComplexT::topla(const ComplexT & z) const
{
cout<< endl << "Toplama metodu calisiyor. ";
float gecici_re, gecici_im; // Bütün bir nesne yerine sadece geçici değişkenler
gecici_re = re + z.re; // Toplama işlemi yapılıyor
gecici_im = im + z.im;
return ComplexT(gecici_re,gecici_im); // Nesne yartılıyor, kurucu canlanır
}


// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << endl << "re= " << re << " , im= " << im;
cout << " Nesne sayisi= " << sayac;
}

// Yok edici fonksiyon
// Yaratrılan nesne sayisinı bir azaltır
ComplexT::~ComplexT()
{
sayac--; // Nesne sayisi azaltıldı
cout<< endl << "Yok edici calisti. ";
cout<< "Nesne sayisi = "<< sayac;
}


unsigned int ComplexT::sayac; // static sayac icin bellekte yer ayrıldı

//------ Ana program -------
int main()
{
ComplexT::sifirla(); // sayac sıfırlandı
ComplexT z1,z2,z3; // Üç adet nesne yarıtıldı
z3=z1.topla(z2); // z3 = z1 + z2 işlemi yapılıyor.
z3.goster();
return 0;
}
/*
Kurucu calisti. Nesne sayisi = 1
Kurucu calisti. Nesne sayisi = 2
Kurucu calisti. Nesne sayisi = 3
Toplama metodu calisiyor.
Kurucu calisti. Nesne sayisi = 4
Yok edici calisti. Nesne sayisi = 3
re= 0 , im= 2 Nesne sayisi= 3
Yok edici calisti. Nesne sayisi = 2
Yok edici calisti. Nesne sayisi = 1
Yok edici calisti. Nesne sayisi = 0 */

İç içe nesneler: Nesnelerin başka sınıfların üyesi olması
Bir sınıfın üyeleri sadece hazır veri tipleri (char, int, double....) olmak zorunda değildir. Bir sınıfın üyeleri başka sınıftan tanımlanmış nesneler de olabilirler. Sınıflar arası bu ilişkiye sahip olma ilişkisi ( has a relation) adı verilir. Aşağıdaki örnekte karmaşık sayıları tanımlamak üzere bir sınıf (ComplexKes)oluşturulmuştur. Bu sınıfın reel ve sanal kısımları bayağı kesirlerden oluşacaklardır. Bayağı kesirler ise (Kesir) sınıfından tanımlanan nesneler ile oluşturulacaktır.


Burada ComplexKes sınıfının kurucusunu yazan programcı, kullandığı nesnelerin (Kesir sınıfından re ve im) kurucularına uygun parametreleri göndermekle yükümlüdür.
Örnek 1
// Nesnelerin başka bir sınıfın üyesi olması

#include<iostream.h>
//using namespace std;

class Kesir{ // Bayağı kesirleri tanımlamak için
int pay,payda;
public:
Kesir(int, int); // Kurucu
void goster() const;
};

Kesir::Kesir(int py, int pyd) // KURUCU
{
pay=py;
if (pyd==0) payda=1; // Payda olarak 0 verilirse 1 yazar.
else payda=pyd;
cout << "Kesir'in kurucusu" << endl;
}

// Bayağı kesiri ekrana çıkaran fonksiyon
void Kesir ::goster() const
{
cout << pay << "/" << payda << endl;
}

// Elemanları bayağı kesir olan karmaşık sayıları tanımlayan sınıf
class ComplexKes{
Kesir re,im; // üye nesneler
public:
ComplexKes(int,int); // Kurucu
void goster() const;
};

// Kurucu fonksiyon
// Önce üye nesnelerin kurucularına gerekli parametreler gönderiliyor
ComplexKes::ComplexKes(int re_in,int im_in) :re(re_in,1),im(im_in,1)
{
cout << "ComplexKes'in kurucusu" << endl;
}

// Karmaşık sayı ekrana çıkarılıyor
// Bayağı kesirlerin goster fonksiyonu kullanılıyor
void ComplexKes::goster() const
{
re.goster();
im.goster();
}

//---- Ana program ----
int main()
{
ComplexKes ck(2,5); // Bir karmaşık sayı tanımlanıyor
ck.goster(); // Karmaşık sayı ekrana çıkarılıyor
return 0;
}

/*Kesir'in kurucusu
Kesir'in kurucusu
ComplexKes'in kurucusu
2/1
5/1 */

Örnek2
// İç içe nesnelerde kurucu ve yok edicilerin çalışma sırası
// Önce içteki nesnelerin kurucuları çalışır
// Yok edicilier kurucuların ters sırasında çalışır

#include <iostream>
using namespace std;

// İçteki sınıf
class A{
public:
A(){ cout << endl<< "Kurucu A";}
~A(){ cout << endl<< "Yok edici A";}
};

// Dıştaki sınıf, iki tane nesneyi üye olarak içerir.
class B{
A m1,m2; // A sınıfından iki üye nesne
public:
B(){ cout << endl<< "Kurucu B";}
~B(){ cout << endl<< "Yok edici B";}
};

//--- Ana program ---
int main()
{
B b; // Bsınıfından bir nesne yaratılıyor
return 0;
}

/*
Kurucu A
Kurucu A
Kurucu B
Yok edici B
Yok edici A
Yok edici A */

25.10.2004
Arkadaş Fonksiyonlara Giriş
Bir fonksiyonun üyesi olmadığı bir sınıfa ait private üyelere erişim hakkı olmasını istediğiniz zamanlar olacaktır. Bunun için C + + size arkadaş (friend) fonksiyonlarını sunmaktadır. Arkadaş fonksiyonları sınıfa ait değildir, ama bu fonksiyonların o sınıfa ait private elemanlara erişim hakkı vardır.
Arkadaş fonksiyonlarının işimize yaradığı iki durum vardır: Operatörlerin üst üste yüklenmesi ve belli tiplerde 1/0 fonksiyonlarının oluşturulması. Arkadaş fonksiyonlarının bu iki şekilde nasıl kullanıldığını görmek için bir süre daha beklemeniz gerekecek. Fakat, burada ele alacağımız üçüncü bir kullanım şekli daha var Bir fonksiyonun iki veya daha fazla sınıfın prıvate üyelerine erişmesinin istendiği durum.
Bir arkadaş fonksiyonu düzgün ama üye olmayan bir fonksiyon olarak tanımlanır. Fakat bu fonksiyonun, kendisine arkadaş olacağı sınıf bildiriminin içerisinde friend anahtar kelimesi ile belirtilen bir prototipi mevcuttur. Bunun nasıl çalıştığını anlamak için aşağıdaki bu kısa örneği inceleyelim:
// Arkadaş fonksiyonlarına bir örnek.
// A example of a friend function.
#include <iostream>
using namespace std;

class myclass {
int n, d;
public:
myclass(int i, int j) { n = i; d = j; }
// myclass&#8217;ın arkadaş fonksiyonunun deklare edilmesi
friend int isfactor(myclass &);
};

/* Arkadaş fonksiyonunun tanımı. Eğer n d&#8217;nin tam katı isetrue değerini
döndürür.friend anahtar kelimesinin isfactor()&#8217;ün tanımlanması sırasında
kullanılmadığına dikkat edin.*/
int isfactor(myclass &ob)
{
if(!(ob.n % ob.d)) return 1;
else return 0;
}

int main()
{
myclass ob1(10, 2), ob2(13, 3);

if(isfactor(ob1)) cout << "10 2&#8217;nin katıdır\n";
else cout << "10 2&#8217;nin katı değildir\n";

if(isfactor(ob2)) cout << "13 3&#8217;ün katıdır\n";
else cout << "13 3&#8217;ün katı değildir\n";

return 0;
}

Bu örnekte myclass kendi yapıcı fonksiyonunu ve sınıf bildirimi içerisindeki arkadaş isfactor( )&#8216;ı bildirmektedir. isfactor(), myclass&#8217;ın arkadaşı olduğundan isfactor( )&#8216;un onun private üyelerine erişim hakkı vardır. Bu, isfactor( )&#8216;un içerisinde ob.n ve ob.d&#8217;ye doğrudan gönderme yapabilmesinin nedenidir.
Bir arkadaş fonksiyonunun arkadaş olduğu sınıfa ait olmadığının anlaşılması önemlidir. Böylece bir arkadaş fonksiyonunu nesne adını ve sınıf üyesi erişim operatörünü (nokta ve ya ok operatörü) kullanarak çağırmamız mümkün değildir. Örneğin bir önceki örnek için aşağıdaki deyim yanlıştır:
obl.isfactorO; // yanlış; isfactor() üye bir fonksiyon değildir.
Halbuki arkadaşlar normal fonksiyonlar gibi çağrılırlar.
Bir arkadaş fonksiyonu her ne kadar arkadaş olduğu sınıfın private elemanlarının bilgisine sahip olsa da onlara sadece bu sınıfa ait bir nesne üzerinden erişebilir. Yani myclass&#8217;ın n veya d&#8217;ye doğrudan gönderme yapabilen üye fonksiyonlarının aksine, bir arkadaş fonksiyonu bu değişkenlere sadece, arkadaş fonksiyonu içerisinde bildirilmiş veya arkadaş fonksiyonuna gönderilmiş bir nesne aracılığıyla erişebilir.
NOT Bir önceki paragraf önemli bir meseleyi gözlerimizin önüne seriyor. Üye fonksiyonlar private elemanlara doğrudan gönderme yaparlar, çünkü üye fonksiyonlar sadece, bu sınıfa ait bir nesneyle bağlantılı olarak çalıştırılırlar. Böylece, üye fonksiyon private elemana gönderme yaptığında, derleyici bu private elemanın hangi nesneye ait olduğunu bilir. Bu bilgiyi üye fonksiyonun çağrılışı sırasında fonksiyona bağlanan nesneden alır. Fakat arkadaş fonksiyonu herhangi bir nesneye bağlı değildir, sadece sınıfın private elemanlarına erişim hakkına sahiptir. Böylece, arkadaş fonksiyonunun içerisinde, belirli bir nesne referans alınmadan private bir üyeye gönderme yapmanın bir anlamı yoktur.
Arkadaşlar sınıflara ait değillerdir, bu yüzden arkadaş oldukları sınıfa ait bir veya daha fazla nesne kendilerine gönderilecektir. Aynı şey isfactor() için de geçerlidir. Bu fonksiyona ob adında myclass&#8217;a ait bir nesne gönderiyoruz. Fakat isfactor myclass&#8217;ın arkadaşı olduğundan, ob&#8217;nin private elemanlarına erişebilir. isfactor myclass&#8217;a arkadaş olmasaydı n ve d myclass&#8217;ın private üyeleri olduğu sürece ob.d veya ob.n&#8217;ye erişemeyecekti.
HATIRLATMA Arkadaş fonksiyonları üye değildir ve bir nesne adı tarafından nitelenemez. Normal bir fonksiyonlu aynı şekilde çağrılmalıdır.
Arkadaş fonksiyonlarının miras yoluyla aktarılmaları da mümkün değildir. Yani temel sınıfın arkadaş fonksiyonu türetilmiş sınıfın da arkadaşı değildir.
Bu fonksiyonlar hakkındaki bir diğer önemli nokta da arkadaş fonksiyonlarının birden fazla sınıfla arkadaş olabilmesidir.
Örnekler
Arkadaş fonksiyonunun sık karşılaşacağınız (ve aynı zamanda oldukça iyi) bir başka kullanım şekli de farklı tipte iki sınıfın çeşitli değerlerinin karşılaştırılmasıdır. Örnek olarak aşağıdaki programı inceleyelim. Burada car (araba) ve truck (kamyon) adında iki sınıf oluşturuluyor. Bu sınıfların her biri, private değişkenlerinde gösterdikleri aracın hızını saklamaktadır:
//listing 18
#include <iostream>
using namespace std;

class truck; // forward bildirim

class car {
int passengers;
int speed;
public:
car(int p, int s) { passengers = p; speed = s; }
friend int sp_greater(car c, truck t);
};

class truck {
int weight;
int speed;
public:
truck(int w, int s) { weight = w, speed = s; }
friend int sp_greater(car c, truck t);
};

/* Eğer car&#8217;ın (araba) hızı truck&#8217;tan (kamyon) fazlaysa pozitif gönderir.
Hızlar aynıysa 0 döndürür. Eğer truck&#8217;ın hızı car&#8217;dan büyükse negatif
döndürür.
*/
int sp_greater(car c, truck t)
{
return c.speed - t.speed;
}

int main()
{
int t;
car c1(6, 55), c2(2, 120);
truck t1(10000, 55), t2(20000, 72);

cout << "c1 ve t1 karsilastiriliyor:\n";
t = sp_greater(c1, t1);
if(t<0) cout << "Truck daha hizli.\n";
else if(t==0) cout << "Car ve truck&#8217;in hizlari ayni.\n";
else cout << "Car daha hizli.\n";

cout << "\n c2 ve t2 karsilastiriliyor:\n";
t = sp_greater(c2, t2);
if(t<0) cout << "Truck daha hizli.\n";
else if(t==0) cout << "Car ve truck&#8217;ın hizlari ayni.\n";
else cout << "Car daha hizli.\n";

return 0;
}
/* c1 ve t1 karsilastiriliyor:
Car ve truck'in hizlari ayni.

c2 ve t2 karsilastiriliyor:
Car daha hizli. */

Bu program, car ve truck sınıflarının arkadaş fonksiyonu olan sp_ greater() fonksiyonunu içermektedir. Belirtildiği gibi, bir fonksiyon iki veya daha fazla sınıfa arkadaş olabilir. Bu fonksiyon, car nesnesi truck nesnesinden daha hızlı gidiyorsa pozitif, hızları eşitse 0 ve truck daha hızlı gidiyorsa negatif döndürür.
Bu program C + + &#8216;ın önemli bir yazım elemanını göstermektedir: forward bildirim (bu aynı zamanda forward referans şeklinde de adlandırılır). sp_ greater( ), hem car hem de truck sınıfının parametrelerini aldığından, sp_ greater( )&#8216;i bu iki sınıfa dahil etmeden önce sınıfları deklare etmek mantıken imkansızdır. Sonuç olarak derleyiciye sınıfı gerçekte deklare etmeden sınıf adını söylemenin bir yolu olmalıdır. Buna forward bildirim adı verilir. C + + &#8216;da derleyiciye tanımlayıcının sınıf adı olduğunu söylemek için, sınıf adının ilk kullanımından önce aşağıdaki gibi bir satır kullanın:
class sınıf&#8212;adı;
Örneğin bir önceki programda forward bildirimi şu şekildeydi:
class truck;
Şimdi truck, sp_greater( )&#8216;ın arkadaş bildiriminde bir deneme hatası meydana getirmeden kullanılabilir.
2. Bir fonksiyon bir sınıfın üyesi ve bir diğerinin de arkadaşı olabilir. Örneğin burada bir önceki örnek, sp_greater car&#8217;ın üyesi ve truck&#8217;ın arkadaşı olacak şekilde yeniden yazılmıştır:
//listing 20
#include <iostream>
using namespace std;

class truck; // bir forward bildirim

class car {
int passengers;
int speed;
public:
car(int p, int s) { passengers = p; speed = s; }
int sp_greater(truck t);
};

class truck {
int weight;
int speed;
public:
truck(int w, int s) { weight = w, speed = s; }

// kapsam çözümleme operatörünün yeni kullanımına dikkat edin.
friend int car::sp_greater(truck t);
};

/* Eğer car&#8217;ın (araba) hızı truck&#8217;tan (kamyon) fazlaysa pozitif gönderir.
Hızlar aynıysa 0 döndürür. Eğer truck&#8217;ın hızı car&#8217;dan büyükse negatif
döndürür.*/
int car::sp_greater(truck t)
{
/* sp_greater() car&#8217;a ait olduğu sürece, sadece bir truck nesnesi ona
gönderilmelidir.*/

return speed-t.speed;
}

int main()
{
int t;
car c1(6, 55), c2(2, 120);
truck t1(10000, 55), t2(20000, 72);

cout << " c1 ve t1 karsilastiriliyor :\n";
t = c1.sp_greater(t1); // car&#8217;a ait bir fonksiyonmuş gibi uyandırılıyor
if(t<0) cout << " Truck daha hizli.\n";
else if(t==0) cout << " Car ve truck&#8217;in hizlari ayni.\n";
else cout << " Car daha hizli.\n";

cout << "\n c2 ve t2 karsilastiriliyor:\n";
t = c2.sp_greater(t2); // car&#8217;a ait bir fonksiyonmuş gibi uyandırılıyor
if(t<0) cout << " Truck daha hizli.\n";
else if(t==0) cout << " Car ve truck&#8217;in hizlari ayni.\n";
else cout << " Car daha hizli.\n";

return 0;
}
/* c1 ve t1 karsilastiriliyor :
Car ve truck'in hizlari ayni.

c2 ve t2 karsilastiriliyor:
Car daha hizli. */

Erim alanı çözümleme operatörünün (scope resolutıon operator) truck sınıfının bildirimindeki arkadaş bildiriminde ortaya çıkan yeni kullanım şekline dikkat edin. Burada derleyiciye sp_greater() fonksiyonunun car sınıfına ait olduğunu söylemek için bu operatörü kullanıyoruz.
Erim alanı çözümleme operatörünün nasıl kullanılacağını kolayca hatırlamanın yolu şu: Üye adı, onu takip eden erim alanı çözümleme operatörü ve onu takip eden sınıf adı sınıfa ait bu üyeyi tam olarak belirler.
Hatta, sınıfa ait bir üyeye gönderme yaparken bu üyenin adını tam olarak belirtmemiz yanlış olmaz. Fakat nesne, üye fonksiyonları çağırmak veya üye değişkenlere erişmek için kullanıldığında tam adın kullanılması gereksizdir ve bu çok seyrek yapılır. Örneğin,
t = cl.spgreater(tl);
aşağıdaki gibi, (gereksiz olan) erim alanı çözümleme operatörü ve car sınıf adı kullanılarak yazılabilir:
t = c1.car::sp_greater(t1);

Fakat d, car tipinde bir nesne olduğu sürece derleyici zaten sp )&#8216;ın car sınıfina ait olduğunu bilir. Bu da sınıfın tam olarak tanımlamamızı gereksiz hale getirir. SCs Sayfa 84

Sınıflarda Arkadaşlık (friend) İlişkisi
Bir A sınıfı oluşturulurken B sınıfı A'nın arkadaşı olarak bildirilebilir. Bu durumda B'nin tüm üye fonksiyonları erişim tipleri ne olursa olsun A'nın tüm üyelerine (public, private,protected) erişebilirler.
class A{
friend class B; // B sınıfı A'nın arkadaşıdır
private: // A'nın özel üyeleri
int i;
float f;
public: // A'nın açık üyeleri
void fonk1(char *c);
};
class B{ // B sınıfı
int j;
public:
void fonk2(A &s){cout << s.i;} // B, A'nın özel üyesi i'ye erişebiliyor
};

Sınıflar arası arkadaşlık ilişkisi tek yönlüdür. Yukarıdaki örnekte B, A'nın arkadaşıdır ve onun tüm üyelerine erişmektedir. Ancak A, B'nin arkadaşı değildir.

Bir fonksiyon da bir sınıfın arkadaşı olarak bildirilebilir. Fonksiyonlar arkadaşları oldukları sınıfların tüm üyelerine (public, private, protected) erişebilirler.
class Nokta{ // Nokta Sınıfı
friend void sifirla(Nokta &); // Nokta sınıfının arkadaşı olan sifirla fonksiyonu
int x,y; // özel (private) Nitelikler: x ve y koordinatları
public: // açık (public) üyeler
bool git(int, int); // Noktanın hareket etmesini sağlayan fonksiyon
void goster(); // Noktanın koordinatlarını ekrana çıkartır
bool sifir_mi(); // Noktanın (0,0) koordinatlarında olup olmadığı
};
void sifirla (Nokta &n) // Hiçbir sınıfın üyesi değil
{
n.x = 0; // n'nin x elemanı sıfırlanıyor
n.y = 0; // n'nin x elemanı sıfırlanıyor
}

buss7.gif

OPERATÖRLERİ AŞIRI YÜKLEMEK( Operator Overloading)

C++&#8217;da hazır olarak var olan operatörlere (+, -, *, / , ! , << , ++ vs.) ilişkin fonksiyonlar yazarak bu operatörlerin sizin belirlediğiniz işlemleri yapmasını sağlayabilirsiniz. Operatör fonksiyonları bir sınıfın üyesi de olabilirler. Böylece o sınıftan yaratılan nesneler üzerinde işlemler yapan operatörler tanımlanmış olur. C++&#8217;da operatör kullanımı fonksiyon çağrılarına karşı düşmektedir. Operatörlere yeni işlevler yükleyerek yapılabilecek her şey normal fonksiyonlar ile de yapılabilir. Fonksiyon isimleri yerine operatörleri kullanmak programın yazılmasını ve okunmasını kolaylaştırabilir. Bu nedenle bir operatöre işlev yüklemek, eğer program daha kolay okunur ve anlaşılır olacaksa tercih edilmelidir. Sınırlamalar: C++&#8217;da olmayan operatörlere işlev yüklenemez. Örneğin üs alma işlemi için &#8216;^&#8217;simgesine ya da &#8216;**&#8217; simgesine bir işlev yüklenemez. C++&#8217;da var olan operatörlerden bazılarına da yeni işlev yüklenemez. Bunlar: nokta operatörü &#8216;.&#8217; , yaşam alanı belirleme operatörü &#8216;::&#8217; , koşul operatörü &#8216;? :&#8217;ve boyut operatörüdür &#8216;sizeof&#8217; . C++ operatörleri birli ve ikili olmak üzere iki gruba ayrılabilir. Birli operatörler tek operand alırlar. Örneğin: -a, a++, !a . İkili operatörler ise iki operandalırlar; a+b, a/b gibi.
Operatörlere işlev yüklerken operand sayısı değiştirilemez.
Operatörlerin öncelikleri değiştirilemez.
Derleyicinin hazır veri tipleri üzerinde işlem yapan operatörlere yeni işlev yüklenemez. Örneğin iki tamsayıyı toplayan + operatörü değiştirilemez. Yeni oluşturulan operatörlerin en az bir operandının tipi bir sınıf olmalıdır.



Üye operatör fonksiyonlarının genel kullanım şekli aşağıdaki gibidir:
dönüş-tipi sınıf-adı: : operatör# (argüman-listesi)
{
Il gerçekleştirilecek işlem
}
Operatör fonksiyonlarının döndürme tipi, genelde fonksiyon hangi sınıf için tanımlandıysa o sınıftır. Fakat operatör fonksiyonları herhangi bir tip döndürmekte serbesttir. Aşırı- yüklenen operatör #&#8217;ın yerini alır. Örneğin eğer + operatörü aşırı-yükleniyorsa fonksiyonun adı operator+ olur. argüman-listesi&#8217;nin içeriği, operatör fonksiyonunun tanımlanma şekline ve aşırı-yüklenmekte olan operatörün tipine göre değişir.

Atama Operatörüne &#8220;=&#8220; Yeni Bir İşlev Yüklenmesi
Atama işlemi programlarda çok sık kullanıldığından C++ derleyicisi her sınıfa atama operatörü için bir fonksiyon yerleştirir. Derleyicinin yerleştirdiği fonksiyon bir nesnenin verilerini var olan başka bir nesnenin veri alanlarına bire bir kopyalar. Eğer bu bire bir atama işlemi o sınıf için yeterli ise programcının atama operatörü için bir fonksiyon yazmasına gerek kalmaz. İçinde gösterici olmayan sınıflar için genellikle derleyicinin sağladığı fonksiyon yeterlidir. Örneğin karmaşık sayılar için aşağıda gösterilen atama fonksiyonu gereksizdir.

Bu fonksiyon yazılmasaydı derleyicinin yerleştireceği fonksiyon da aynı işi yapardı. Eğer sınıfta bir atama fonksiyonu varsa, artık derleyici tüm atama işleri için bu fonksiyonu kullanır.

// Atama operatörüne yeni bir işlev yüklenmesi
// Karmaşık sayılar sınıfı ComplexT'ye = operatörü için bir metot
// eklenmiştir.
// Bu sınıf için bu metodun yazılması gereksizdir.
// Bu fonksiyon sınıftan kaldırılsa da ana program doğru olarak çalışır.
// Bu program sadece atama fonksiyonunun yazılımını göstermek için
// hazırlanmıştır.

#include <iostream>
using namespace std;

// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
double re,im; // reel ve sanal kısımlar
public:
ComplexT(double re_in=0,double im_in=1): re(re_in),im(im_in)
{}; // Kurucu (gövdesi boş)
void operator=(const ComplexT & ); // = Operatörü
void goster() const;
};

// = operatörü
void ComplexT::operator=(const ComplexT &c)
{
re = c.re; // Atamalar yapılıyor
im = c.im;
cout << "Atama fonksiyonu calisti" << endl; // Çalıştığını ekranda
//görmek için
}

// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << "re=" << re << " im=" << im << endl;
}

int main()
{
ComplexT z1(1,2),z2;
z2.goster(); // Atamadan önceki içerik
z2=z1;
z2.goster(); // Atamadan sonraki içerik
return 0;
}
/* re=0 im=1
Atama fonksiyonu calisti
re=1 im=2 */

Ancak her sınıf için derleyicinin sağladığı atama fonksiyonu yeterli olmayabilir. Özellikle içinde gösterici olan sınıflarda programcının atama operatörüne ilişkin fonksiyonu yazması gerekebilir. Kopyalanma kurucusundakine benzer bir problem burada da vardır. Aşağıda örnek String sınıfı için atama fonksiyonu yazılmıştır.
class String{ // Örnek (karakter katarı) String sınıfı
int boy; // Katarın boyu
char *icerik; // Katarın içeriği
public:
String(); // Parametresiz kurucu
String(const char *); // Kurucu
String(const String &); // Kopyalanma kurucusu
void operator=(const String &); // Atama operatörü
void goster(); // Katarları ekrana çıkaran üye fonksiyon
~String(); // Yok edici fonksiyon
};
// Atama Operatörü
void String::operator=(const String &gelen_nesne)
{
cout<< "Atama operatoru calisti" << endl;
boy = gelen_nesne.boy;
delete [] icerik; // Eski içerik belleğe iade ediliyor
icerik = new char[boy + 1]; // +1 null karakteri icin
strcpy(icerik, gelen_nesne.icerik);
}

Eğer operatör fonksiyonlarının geri dönüş değeri tipleri void olarak yazılırsa bu operatörler peş peşe bağlanamaz. Bir önceki örnekte gösterilen atama fonksiyonu geriye bir değer döndürmemektedir (void) . Bu nedenle bu operatörü kaskad olarak yazmak ( a = b = c gibi)
mümkün değildir. Operatörleri kaskad bağlayabilmek için operatöre ilişkin fonksiyon, üzerinde işlemyapılan nesnenin referansını geri göndermelidir.

Örnek
// Atama operatörünün "=" Katar dizileri (String) için yüklenmesi

#include <iostream>
#include <cstring> // string fonksiyonları için
using namespace std;

class String{ // Örnek (karakter katarı) String sınıfı
int boy; // Katarın boyu
char *icerik; // Katarın içeriği
public:
String(); // Parametresiz kurucu
String(const char *); // Kurucu
String(const String &); // Kopyalama kurucusu
const String& operator=(const String &); // Atama operatörü
void goster(); // Katarları ekrana çıkaran üye fonksiyon
~String(); // Yok edici fonksiyon
};

// Parmatresiz Kurucu Fonksiyon
// Sadece NULL içeren bir boş katar oluşturur
String::String()
{
cout<< "Parametresiz Kurucu calisti" << endl;
boy = 0; // boş katarın boyu sıfır
icerik = new char[1]; //icerik için yer ayrıldı, null icin
strcpy(icerik, ""); // boş katar
}

// Kurucu Fonksiyon
// Parametre olarak aldığı katarı nesnenin içeriğine kopyalar
String::String(const char *gelen_veri)
{
cout<< "Kurucu calisti" << endl;
boy = strlen(gelen_veri); // gelen katarın boyu hesaplandı
icerik = new char[boy +1]; // icerik için yer ayrıldı, +1
//null icin
strcpy(icerik, gelen_veri); // gelen veri icerik'in gosterdigi
//yere kopyalanıyor
}

// Kopyalama kurucusu
String::String(const String &gelen_nesne)
{
cout<< "Kopyalama Kurucusu calisti" << endl;
boy = gelen_nesne.boy;
icerik = new char[boy + 1]; // +1 null karakteri icin
strcpy(icerik, gelen_nesne.icerik);
}

// Atama Operatörü
const String& String::operator=(const String &gelen_nesne)
{
cout<< "Atama operatoru calisti" << endl;
boy = gelen_nesne.boy;
delete [] icerik; // Eski içerik belleğe iade ediliyor
icerik = new char[boy + 1]; // +1 null karakteri icin
strcpy(icerik, gelen_nesne.icerik);
return *this; // Kendi adresini geri gönderiyor
}


void String::goster()
{
cout<< icerik << ", " << boy << endl; // Katar ve boyu ekrana
// yazılıyor
}

// Yok edici Fonksiyon
// icerik tarafından isaret edilen bellek geri veriliyor
String::~String()
{
cout<< "Yok edici calisti" << endl;
delete[] icerik;
}

//-------- Ana Program ----------------
int main() // Ana fonksiyon
{
String s1("Katar 1");
String s2 = s1; // Kopyalama kurucusu çalışır
s2.goster();
String s3 , s4;
s3 = s4= s2; // Atama operatörü 2 defa çalışır
s3.goster();
s4.goster();
return 0;
}
/*
Kurucu calisti
Kopyalama Kurucusu calisti
Katar 1, 7
Parametresiz Kurucu calisti
Parametresiz Kurucu calisti
Atama operatoru calisti
Atama operatoru calisti
Katar 1, 7
Katar 1, 7
Yok edici calisti
Yok edici calisti
Yok edici calisti
Yok edici calisti */
Atama operatörü ile kopyalanma kurucusu benzer işler yapmakla beraber farklı zamanlarda canlanırlar. Kopyalanma kurucusu yeni bir nesne yaratılırken canlanır ve eski bir nesnedeki bilgileri yeni nesneye kopyalar. Atama operatörü ise var olan bir nesneye başka bir nesnedeki değerler atanırken canlanır.

6.2. İkili Operatörlerin Aşırı Yüklenmesi
Bir üye operatör fonksiyonu bir ikili operatörü aşırı-yüklediğinde, bu fonksiyonun sadece tek parametresi olacaktır. Bu parametre operatörün sağ tarafındaki nesneyi alacaktır. Sol taraftaki nesne ise operatör fonksiyonunu çağıran nesnedir ve üstü kapalı olarak this tarafından gönderilir.
Operatör fonksiyonlarını çeşitli şekillerde yazabiliriz. Burada verdiğimiz örnekler pek ayrıntılı değiller, ama sık karşılaşacağınız çeşitli teknikleri göstermekteler.

Örnekler
1. Aşağıdaki programda coord sınıfıyla ilişkili olan + operatörü aşırı-yüklenmektedir. Bu sınıfı X,Y koordinatlarını saklamak için kullanıyoruz.
// coord sınıf ı içerisinde + operatörü aşırı-yükleniyor
#include <iostream>
using namespace std;

class coord {
int x, y; // koordinat değerleri
public:
coord() { x=0; y=0; }
coord(int i, int j) { x=i; y=j; }
void get_xy(int &i, int &j) { i=x; j=y; }
coord operator+(coord ob2);
};

// + operatörü coord sınıfı için aşırı-yüklenmektedir.
coord coord::operator+(coord ob2)
{
coord temp;
temp.x = x + ob2.x;
temp.y = y + ob2.y;

return temp;
}

int main()
{
coord o1(10, 10), o2(5, 3), o3;
int x, y;

o3 = o1 + o2; // nesne toplanmaktadır, bu deyim operator+()&#8217;yı
// çağırır.


o3.get_xy(x, y);
cout << "(o1+o2) X: " << x << ", Y: " << y << "\n";

return 0;
}
Bu program size aşağıdaki sonucu verir:
(o1+o2) X: 15, Y: 13

NOT: Aşağıdaki program yukarıdaki programın daha az nesne yartan ve daha
Hızlı çalışan şeklidir. Yazılımlarda aşağıdaki yazım şekli tercih
Edilmelidir.
// coord sınıf ı içerisinde + operatörü aşırı-yükleniyor
#include <iostream>
using namespace std;

class coord {
int x, y; // koordinat değerleri
public:
// coord() { x=0; y=0; }
// coord(int x=0, int y=0);
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
void goster();
coord operator+(const coord &) const;
};
// + operatörü coord sınıfı için aşırı-yüklenmektedir.
coord coord::operator+(const coord &ob2) const
{
// coord temp;
int x_yeni, y_yeni;
x_yeni = x + ob2.x;
y_yeni = y + ob2.y;
return coord(x_yeni,y_yeni);
}
void coord::goster()
{
cout << "(o1+o2) X: " << x << ", Y: " << y <<endl;
}
int main()
{
coord o1(10, 10), o2(5, 3), o3;
o3 = o1 + o2; // nesne toplanmaktadır, bu deyim operator+()'yı
// çağırır.
o3.goster();
return 0;
}
// (o1+o2) X: 15, Y: 13

Programı daha yakından inceleyelim. operator+() fonksiyonu, terimlerinin X koordinatlarının toplamını x&#8217;te, Y koordinatlarının toplamını da y&#8217;de bulunduran coord tipinde bir nesne döndürür. operator+() içerisinde bulunan temp adında geçici bir nesneyi, sonucu saklamak için kullanıyoruz. Terimlerden hiçbirinde bir değişiklik meydana gelmemekte, buna dikkat edin. temp nesnesinin kullanılma nedeni basittir. Verdiğimiz örnekte + operatörü normal aritmetik kullanım amacına benzer bir şekilde aşırı-yükleniyor. Sonuç olarak terimlerden hiçbirinin değişmemesini sağlıyoruz. Örneğin, 10&#8217;a 4 eklediğimizde sonuç 14&#8217;dür, fakat ne 10 ne de 4&#8217;de bir değişiklik olmaz. Bu yüzden sonucun saklanması için geçici bir nesne kullanılması gerekir.
operator+() fonksiyonumuz coord tipinde bir nesne döndürüyor, bunun nedeni coord tipindeki nesnelerin toplanmasından elde ettiğimiz sonucu daha geniş ifadelerde kullanabilmemizdir. Örneğin,
o3 = o1 + o2;
deyiminin geçerli olmasının tek nedeni o1+o2&#8217;nin sonucunun o3&#8217;e atanabilen bir coord nesnesi olmasıdır. Farklı bir tip döndürseydik bu deyim doğru sayılmayacaktı. Üstelik coord tipinde bir nesne döndürerek toplam operatörünün ikiden fazla terimi toplamasını da sağlarız. Örneğin şu geçerli bir deyimdir:
o3 = ol + o2 + ol + o3;
Operatör fonksiyonunun tanımlandığı nesneden farklı bir nesne döndürmesini isteyeceğimiz bazı durumlar olacaktır, fakat oluşturduğumuz fonksiyonlar genellikle kendi sınıflarından bir nesne döndüreceklerdir. Bu kuralı bozan en büyük istisna, karşılaştırma ve mantık operatörlerinin aşırı-yüklenmesi durumudur. Bunu konuyu 6.3&#8217;de inceleyeceğiz.
Bu örnek için son bir noktaya daha değinelim. coord tipinde nesne döndürülmesi sayesinde aşağıdaki deyim de tamamen geçerlidir:
(ol+o2).get_xy(x, y);
Burada operator+() tarafından döndürülen geçici nesne doğrudan kullanılmaktadır. Elbette bu deyim işletildikten sonra geçici nesne yok ediliyor.
2. Yukarıdaki programı aşağıda başka bir şekliyle ele alalım. Bu programda - ve = operatörleri coord sınıfı için aşırı-yüklenmektedir.
// coord sınıfıyla ilişkili olarak +, &#8212; ve = operatörleri aşırı&#8212; yüklenmektedir.
#include <iostream>
using namespace std;
class coord {
int x, y; // koordinat değerleri
public:
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
void goster();
coord operator+(const coord &) const;
coord operator-(const coord &) const;
};
// + operatörü coord sınıfı için aşırı-yüklenmektedir.
coord coord::operator+(const coord &ob2) const
{
int x_yeni, y_yeni;
x_yeni = x + ob2.x;
y_yeni = y + ob2.y;
return coord(x_yeni,y_yeni);
}
void coord::goster()
{
cout << X: " << x << ", Y: " << y <<endl;
}
// - operatörü coord sınıfı için aşırı-yüklenmektedir.
coord coord::operator-(const coord &ob2) const
{
int x_yeni, y_yeni;
x_yeni = x - ob2.x;
y_yeni = y - ob2.y;
return coord(x_yeni,y_yeni);
}
int main()
{
coord o1(10, 10), o2(5, 3), o3;
o3 = o1 + o2; // iki nesne toplanıyor - bu deyim, operator+()'yı çağırır.
o3.goster();
o3 = o1 - o2; // birinci nesneden ikinci çıkarılıyor
o3.goster();
return 0;
}
/* X: 15, Y: 13
X: 5, Y: 7 */
operator-() fonksiyonu operator+()&#8217;ya benzer bir şekilde oluşturulur. Fakat yukarıdaki örnekte, terimlerin sırasının önemli olduğu bir operatörü aşırı-yüklerken dikkat etmemiz gereken ciddi bir noktaya değiniliyor. operator+()fonksiyonu oluşturulurken terimlerin sırası önemli değildi. Yani, A+B, B+A ile aynı şeydi. Fakat çıkarma işleminde sıra önemlidir. Bu yüzden çıkarma operatörü nü doğru bir şekilde aşın-yüklememiz için soldaki terimden sağdaki terimin çıkarılmasını sağlamalıyız. Çünkü soldaki terim operator+()&#8217;yi çağırır ve çıkarma şu sırayla yapılmalıdır:
x - ob2.x;
HATIRLATMA İkili operatörlerin aşırı-yüklenmesi sırasında, soldaki terim fonksiyona argüman olarak değil, üstü kapalı olarak gönderilir. Sağdaki terim de argüman olarak gönderilir.
Şimdi atama operatörü fonksiyonuna bakalım. İlk olarak dikkatinizi çekmesi gereken şey soldaki terimin (yani değer atanan nesnenin) işlem sonunda değişikliğe uğramasıdır. Bu, atama işleminin normal anlamına uygundur. Dikkatinizi çekecek ikinci şey ise fonksiyonun *this&#8217;i döndürmesidir. Yani operator=() fonksiyonu atama yapılan nesneyi döndürür. Bu sayede ardışık atama yapılmasını sağlarız. Sizin de bilmeniz gerektiği gibi C++&#8217;da aşağıdaki deyim geçerlidir (ve hatta çok sık kullanılır):
a = b = c = d = 0;
*this&#8217;in döndürülmesiyle aşırı-yüklenen atama operatörü, coord tipinde nesnelerin benzer şekilde kullanılmasına imkan verir. Örneğin aşağıdaki deyim tamamen geçerlidir:
o3 = o2 = ol;
Şunu aklınızda tutun: Aşırı-yüklenmiş bir atama fonksiyonunun atama yapılan nesneyi döndürmesini gerektiren bir kural yoktur. Fakat eğer aşırı-yüklenmiş = operatörünün kendi sınıfı için hazır tiplere davrandığı gibi davranmasını istiyorsanız bu operatör *this döndürmelidir.

3. Bir operatörü sağ taraftaki terim, operatör fonksiyonunun sınıfına ait olmak yerine, tamsayı gibi mevcut tiplerde bir nesne olacak şekilde aşırı-yüklememiz mümkündür. Örneğin burada + operatörünü, bir tamsayı değerini coord tipinde bir nesneyle toplayacak şekilde aşırı-yüklüyoruz.
// + operatörü, ob + ob için olduğu gibi ob + int için aşırı-yükleniyor.
#include <iostream>
using namespace std;
class coord {
int x, y; // koordinat değerleri
public:
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
void goster() const ;
coord operator+(const coord &) const;
coord operator+(const int) const;
};
// + operatörü coord sınıfı için aşırı-yüklenmektedir.
coord coord::operator+(const coord &ob2) const
{
int x_yeni, y_yeni;
x_yeni = x + ob2.x;
y_yeni = y + ob2.y;
return coord(x_yeni,y_yeni);
}
// + operatörü ob + int için aşırı-yükleniyor
coord coord::operator+(int i) const
{
int x_yeni, y_yeni;
x_yeni = x + i;
y_yeni = y + i;
return coord(x_yeni,y_yeni);
}
void coord::goster()const

{
cout << X: " << x << ", Y: " << y <<endl;
}
int main()
{
coord o1(10, 10), o2(5, 3), o3;
o3 = o1 + o2; // iki nesne toplanıyor - operator+(coord) çağrılıyor
o3.goster();
o3 = o1 + 100; // + int toplanıyor - operator+(int) çağrılıyor
o3.goster();
return 0;
}
/* X: 15, Y: 13
X: 110, Y: 110 */

Nesnelerin diğer mevcut tiplerle işleme tabi tutulabilmesi için üye operatör fonksiyonlarını aşırı-yüklüyorsak mevcut tipin operatörün sağ tarafında bulunmasına dikkat etmeliyiz. Bunun nedeni basittir: Operatör fonksiyonu soldaki nesneyi çağırır. Örneğin, derleyici aşağıdaki satın gördüğünde ne olur?
o3 = 19 + ol; // int + ob
Bir tamsayı ile bir nesneyi toplamak için tanımlanmış mevcut bir işlem yoktur. Aşırı-yüklenmiş operator+(int i) fonksiyonu sadece nesne soldayken çalışır. Sonuç olarak, bu satırda bir deneme hatasıyla karşılaşırız. Çok kısa bir süre sonra bu kısıtlamayı aşmanın yolunu öğreneceğiz.
4. Operatör fonksiyonlarında bir referans parametresi kullanabiliriz. Örneğin aşağıdaki yöntemle + operatörünü coord sınıfı için aşırı-yüklemek mümkündür:
// + operatörü coord sınıfı için referans kullanılarak aşırı-yükleniyor.
coord coord::operator+(coord &ob2)
{
coord temp;

temp.x = x + ob2.x;
temp.y = y + ob2.y;

return temp;
}

Operatör fonksiyonlarında referans parametrelerini kullanmamızın nedenlerinden biri verimliliği sağlamaktır. Nesnelerin parametre olarak fonksiyonlara gönderilmesi çok maliyetlidir ve önemli miktarda CPU çevrimi tüketir. Fakat nesnelerin adreslerinin gönderilmesi daima daha hızlı ve verimli bir yoldur. Eğer operatörü sık kullanacaksanız, referans parametresini kullanarak genellikle performansı önemli bir şekilde arttırabiliriz.
Referans parametrelerinin kullanılmasının bir diğer nedeni de, terimlere ait kopyalarının yok edilmesi sonucu ortaya çıkan sorunları engellemektir. Önceki bölümlerden bildiğimiz gibi, değeri gönderilen argümanın kopyası oluşturulur. Eğer bu nesneye ait bir yıkıcı fonksiyonu varsa, fonksiyon sona erdiğinde kopyanın yıkıcısı çağrılır. Bazı durumlarda yıkıcının çağıran nesne tarafından gerek duyulan bazı şeyleri yok etmesi mümkündür. Eğer durum böyleyse, değer parametresi yerine referans parametresini kullanarak problemi kolay (ve etkili) bir şekilde halledebiliriz. Tabii bu problemi her durumda engelleyecek bir kopya yapıcı da tanımlayabilirdik.
Örnek 5 (daha önce yazılan örneğe Operatör + eklenmiş)
Aşağıdaki örnekte ComplexT sınıfına + operatörü için bir metot eklenecektir. Böylece + operatörünün karmaşık sayıları toplaması sağlanacaktır.
// + operatörüne yeni bir işlev yüklenmesi
#include <iostream>
using namespace std;

// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
double re,im; // reel ve sanal kısımlar
public:
ComplexT(double re_in=0,double im_in=1); //Kurucu
ComplexT operator+(const ComplexT & ) const; // + operatörünün
//fonksiyonu
void goster() const;
};

//Parametrsiz çağırılabilen kurucu
ComplexT::ComplexT(double re_in,double im_in)
{
re=re_in;
im=im_in;
cout<< endl << "Kurucu calisti. ";
}

// + operatörü
ComplexT ComplexT::operator+(const ComplexT &c) const // + operatörünün
//fonksiyonu
{
double yeni_re, yeni_im;
yeni_re=re+c.re;
yeni_im=im+c.im;
return ComplexT(yeni_re,yeni_im);
}

// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << endl << "re=" << re << " im=" << im;
}

int main()
{
ComplexT z1(1,1),z2(2,2),z3;
z3=z1+z2; // z3=z1.operator+(z2)
z3.goster();
return 0;
}
/*
Kurucu calisti.
Kurucu calisti.
Kurucu calisti.
Kurucu calisti.
re=3 im=3 */

Bir Operandlı ( Unary) Operatörlere Yeni Bir İşlev Yüklenmesi
Birli operatörlere örnekler: arttırma (++), azaltma(--), eksileme(-5), mantıksal tümleme (!) vs. Bu operatörlere ilişkin fonksiyonlar bir sınıfın üyesi olarak yazıldıklarında hiç parametre almazlar. Çünkü üzerinde çağırıldıkları nesne üzerinde işlem yaparlar. Bu operatörler normalde nesnenin solunda yer alırlar: !n, -n, ++n gibi.

Örnekler
1. Aşağıdaki programda coord sınıfı için + + operatörü aşırı-yüklenmektedir:
// coord sınıfı için ++ operatörü aşırı- - yükleniyor.
#include <iostream>
using namespace std;

class coord {
int x, y; // koordinat değerleri
public:
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
coord coord::operator++();
void goster() const;
};
// coord sınıfı ile ilişkili olarak ++ aşırı-yükleniyor.
coord coord::operator++()
{
x++;
y++;
return *this;
}
void coord::goster() const
{
cout << "(++o1) X: " << x << ", Y: " << y <<endl;
}
int main()
{
coord o1(10, 10);
++o1; // nesne bir arttırılıyor
o1.goster();
return 0;
}
//(++o1) X: 11, Y: 11
Arttırma operatörü, terimini 1 arttırmak için tasarlandığından aşırı-yüklenmiş olan ++ işlem yaptığı nesneyi değiştirir. Fonksiyon, arttırdığı nesneyi aynı zamanda döndürür. Bu sayede arttırma operatörünü aşağıdaki gibi daha geniş deyimlerde kullanabiliriz:
o2 = ++o1;
Ikili operatörlerde olduğu gibi, bini operatörlerin de normal işlevini yerine getirmesi için aşırı-yüklenmesi gerektiğini söyleyen bir kuralımız yok. Fakat, çoğu zaman bunu yapmak isteyeceksiniz.
2. C++&#8216;ın eski sürümlerinde, arttırma veya eksiltme operatörü aşırı-yüklendiğinde, aşırı-yüklenmiş ++ veya --&#8217;nin terimden önce mi sonra mı geldiğine karar vermek imkansızdır. Yani bir önceki programı düşünecek olursak bu iki deyim birbirinin aynı olurdu:
ol++;
++o
Fakat, C++&#8217;ın modern özellikleri sayesinde derleyici bu iki deyimi birbirinden ayırt edebilmektedir. Bunu gerçekleştirmek için iki farklı operator++() fonksiyonu oluşturmamız gerekir. Bu fonksiyonlardan ilkini yukarıdaki örnekle aynı şekilde tanımladık. İkincisini ise aşağıdaki gibi deklare ettik:
coord coord: :operator++(int notused);
Eğer ++ terimden önceyse operator++() fonksiyonu çağrılır. Fakat eğer ++ terimden sonraysa operator++(int notused) fonksiyonu kullanılır. Bu durumda notused&#8217;a her zaman 0 değeri gönderilecektir. Sonuç olarak eğer ++&#8217;nın terimin başında veya sonunda olması sınıfımızın nesneleri için önemliyse her iki operatör fonksiyonunu da kullanmamız gerekir.
3. Bildiğiniz gibi C++&#8217;da eksi operatörünü hem ikili hem de bini operatör olarak sayıyoruz. Bu operatörü her iki şekilde birden nasıl yükleyeceğinizi merak ediyor olabilirsiniz. Bunun cevabı aslında çok basit: Tek yapmanız gereken, bu operatörü iki kere aşırı-yüklemek (bir kere ikili operatör olarak, bir kere de birli operatör olarak). Aşağıdaki programı inceleyerek bunu nasıl yapacağımızı açıklığa kavuşturalım:
// coord sınıfı için -'nin aşırı-yüklenmesi.
#include <iostream>
using namespace std;
class coord {
int x, y; // koordinat değerleri
public:
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
void goster() const ;
coord operator-(const coord &) const;
coord operator-();
};
// coord sınıfı için -'nin aşırı-yüklenmesi.
coord coord::operator-(const coord &ob2) const
{
int x_yeni, y_yeni;
x_yeni = x - ob2.x;
y_yeni = y - ob2.y;
return coord(x_yeni,y_yeni);
}

// birli -'nin coord sınıfı için aşırı-yüklenmesi.
coord coord ::operator-()
{
x=-x;
y=-y;
return *this;
}
void coord::goster()const
{
cout << "X: " << x << ", Y: " << y <<endl;
}
int main()
{
coord o1(10, 10), o2(5, 7);
o1 = o1 - o2; // çıkarma
o1.goster();
o1 = -o1; // negatif yapılıyor
o1.goster();
return 0;
}
/* X: 5, Y: 3
X: -5, Y: -3 */
Gördüğünüz gibi eksi operatörünü ikili operatör olarak aşırı-.yüklediğimizde bu operatör bir parametre alır. Birli operatörü olarak aşırı-yüklediğimizde ise hiç parametre almaz. Eksiyi her iki işlem için ayrı ayrı aşırı-yükleyebilmemize imkan veren şey parametre sayısındaki bu farktır. Programdan da göreceğiniz gibi eksi operatörü ikili operatörü olarak kullanıldığında operator-(coord ob2) fonksiyonu çağrılır. Bini eksi olarak kullanıldığında ise operator-() fonksiyonu çağrılır.
Örnek 4: Aşağıdaki örnekte ++ operatörüne, karmaşık sayıların reel kısmını 0.1
arttırma işlevi yüklenmiştir.
void ComplexT::operator++()
{
re=re+0.1;
}
int main()
{
ComplexT z(1.2, 0.5);
++z; // operator++ fonksiyonu canlanir
z.goster();
return 0;
}

Eğer bu operatörün bir atama deyiminde kullanılması isteniyorsa operatöre ilişkin fonksiyon nesnenin referansını geri döndürülmelidir.
Örnek
// ++ operatörüne yeni bir işlev yüklenmesi
// Operatör karmaşık sayıların reel kısmını 0.1 kadar arttırmaktadır.

#include <iostream.h>
//using namespace std;

// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
double re,im; // reel ve sanal kısımlar
public:
ComplexT(double re_in=0,double im_in=1): re(re_in),im(im_in)
{}; // Kurucu (gövdesi boş)
const ComplexT& operator++( ); // ++ Operatörü
void goster() const;
};

// ++ operatörü
// Karmaşık sayıların reel kısmını 0.1 kadar arttırmaktadır.
const ComplexT & ComplexT::operator++()
{
re=re+0.1; // reel kısım arttırılıyor
return *this; // Nesnenin referansı döndürülüyor
}

// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << "re=" << re << " im=" << im << endl;
}

//---- Ana Program -----
int main()
{
ComplexT z1(1.2,0.5),z2;
z2= ++z1;
z1.goster();
z2.goster();
return 0;
/* re=1.3 im=0.5
re=1.3 im=0.5 */

// ++ operatörü
// Karmaşık sayıların reel kısmını 0.1 kadar arttırmaktadır.
const ComplexT & ComplexT::operator++()
{
re=re+0.1; // reel kısım arttırılıyor
return *this; // Nesnenin referansı döndürülüyor
}
// ---- Ana Program -----
int main()
{
ComplexT z1(1.2,0.5),z2;
z2= ++z1;
z1.goster();
z2.goster();
return 0;
}

Bilindiği gibi ++ ve &#8211; operatörleri operandların hem soluna hem de sağına yazılabilirler.Bir atama deyimi ile birlikte kullanıldıklarında operatörlerin yazılma şekli önemli olur. Buna göre önceden arttırma (azaltma) ya da sonradan arttırma (azaltma) anlamına gelirler.
z2= ++ z1; // önceden arttırma
z2 = z1++; // sonradan arttırma
Operatör fonksiyonu operator++() şeklinde parametresiz olarak yazılırsa, önceden arttırma operatörüne bir işlev yüklenmiş olur. Sonradan arttırma operatörüne bir işlev yüklemek için fonksiyon bir parametreli olarak yazılır operator++(int). Buradaki parametrenin amacı sadece iki fonksiyonu birbirinden ayırmaktır. Fonksiyon çağrılırken herhangi bir parametre gönderilmez.

Örnek
//++ operatörünün hem önceden hem de sonradan arttırma için kullanılması
// Operatör karmaşık sayıların reel kısmını 0.1 kadar arttırmaktadır.
#include <iostream>
using namespace std;

// Karmaşık (Kompleks) sayıları tanımlamak için oluşturulan sınıf
class ComplexT{
double re,im; // reel ve sanal kısımlar
public:
ComplexT(double re_in=0,double im_in=1): re(re_in),im(im_in)
{}; // Kurucu (gövdesi boş)
const ComplexT& operator++(); // önceden arttırma ++ operatörü
ComplexT operator++(int); // sonradan arttırma ++ operatörü
void goster() const;
};

// Önceden arttırma ++ operatörü
// Karmaşık sayıların reel kısmını 0.1 kadar arttırmaktadır.
const ComplexT & ComplexT::operator++()
{
re=re+0.1; // reel kısım arttırılıyor
return *this; // Nesnenin referansı döndürülüyor
}

// Sonradan arttırma ++ operatörü
// Karmaşık sayıların reel kısmını 0.1 kadar arttırmaktadır.
ComplexT ComplexT::operator++(int) // sonradan arttırma operatörü
{
ComplexT gecici;
gecici = *this; // nesnenin orijinal değeri
re=re+0.1; // reel kısım arttırılıyor
return gecici; //eski(artmamış)değer döndürülüyor
}


// Nesne ile ilgili bilgileri ekrana çıkaran metot
void ComplexT::goster() const
{
cout << "re=" << re << " im=" << im << endl;
}

//---- Ana Program -----
int main()
{
ComplexT z1(1.2,0.5),z2;
z2= ++z1; // operator ++() çalışır
z1.goster();
z2.goster();
z2= z1++; // operator ++(int) çalışır
z1.goster();
z2.goster();
return 0;
}
/* re=1.3 im=0.5
re=1.3 im=0.5
re=1.4 im=0.5
re=1.3 im=0.5 */

Arkadaş Operatör Fonksiyonların Kullanımı
Bölümün başında da belirttiğimiz gibi, operatörleri arkadaş fonksiyonlarını kullanarak da aşırı-yüklememiz mümkündür. Arkadaş fonksiyonlarının this göstericileri olmadığını daha önce konuşmuştuk. İkili operatörlerle uğraşıyorsak bu, arkadaş operatör fonksiyonuna her iki terimin de açık bir şekilde gönderildiği anlamına gelir. Birli operatörler için ise tek bir terim gönderilir. Üye fonksiyon yerine arkadaş fonksiyonunun kullanmamızın tek bir önemli nedeni vardır, bu da Orneklerde anlattığımız nedendir.
HATIRLATMA Bir arkadaş fonksiyonunu, atama operatörünü aşırı-yüklemek için kullanamazsınız. Atama operatörü sadece üye operatör fonksiyonu tarafından aşırı yüklenebilir.
Örnekler
1. Burada operator+() fonksiyonu ile coord sınıfı bir arkadaş fonksiyonu kullanılarak aşın-yüklenmiştir:
// Arkadaş fonksiyonu kullanılarak + operatörü coord sınıfı için aşırı- yükleniyor.
#include <iostream>
using namespace std;

class coord {
int x, y; // koordinat değerleri

public:
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
void goster();

friend coord operator+(coord &,coord &);
};

// Bir arkadaş fonksiyonu kullanılarak + operatörü aşırı-yükleniyor.
coord operator+(coord &ob1, coord &ob2)
{
int x_yeni, y_yeni;
x_yeni = ob1.x + ob2.x;
y_yeni = ob1.y + ob2.y;
return coord(x_yeni,y_yeni);

}


void coord::goster()
{
cout << "(o1+o2) X: " << x << ", Y: " << y <<endl;
}

int main()
{
coord o1(10, 10), o2(5, 3), o3;

o3 = o1 + o2; // nesne toplanıyor - bu deyim, operator+() çağırıyor
o3.goster();
return 0;
}

// (o1+o2) X: 15, Y: 13
Sol terimi ilk parametreye, sağ terimi ise ikinci parametreye gönderiyoruz, dikkat edin.

2. Arkadaş fonksiyonu ile operatörü aşırı-yüklediğimizde bu bize üye fonksiyonların sağlamadığı çok önemli bir özellik sağlar. Arkadaş operatör fonksiyonu kullanarak tamsayı gibi mevcut tipleri operatörün sol tarafına da koyabiliriz, bu şekilde nesnelerimizi bu tür işlemlere de sokabiliriz. İçeren işlemlerde kullanılmasını sağlayabiliriz. Daha önce sol terimin nesne, sağ terimin mevcut tiplerden olduğu ikili üye operatör fonksiyonunu aşırı-yüklemiştik. Fakat mevcut tiplere sahip değişkenlerin operatörün sol tarafında bulunması için üye fonksiyon kullanmamız mümkün değildir. Örneğin, aşırı-yüklenmiş bir üye operatör fonksiyonunu düşünelim, verilen ilk deyim kurallara uygundur, fakat ikincisi değildir:
ob1 = ob2 + 10; // kurala uygun
obl = 10 + ob2; // kurala uygun değil
Programımızda ilk deyime benzeyen deyimleri kullanabiliriz, fakat her seferinde nesnenin terimin sol tarafında ve diğer tipin terimin sağ tarafında olduğundan emin olmak zorunda olmamız bir engel teşkil edebilir. Çözüm, aşırı-yüklenmiş operatör fonksiyonlarını arkadaş haline getirmek ve her iki olası durumu da tanımlamaktır.
Bildiğiniz gibi, her iki terim de açık bir şekilde arkadaş operatör fonksiyonlarına gönderilir. Böylece, aşırı-yüklenmiş arkadaş fonksiyonunu sol terim nesne ve sağ terim de diğer tipte olacak şekilde tanımlamak mümkündür. O halde operatörü yine sol terim hazır tipte ve sağ terim nesne olacak şekilde de aşırı-yükleyebiliriz. Aşağıdaki programda bu metodu inceleyelim:
// Programa esneklik katmak için arkadaş operatör fonksiyonları kullanılıyor.
#include <iostream>
using namespace std;
class coord {
int x, y; // koordinat değerleri
public:
coord(int i=0,int j=0): x(i), y(j) // kurucu
{};
void get_xy(int &i, int &j) { i=x; j=y; }
friend coord operator+(coord &, int i);
friend coord operator+(int i, coord &);
};
// ob + int için aşırı-yükleniyor.
coord operator+(coord &ob1, int i)
{
int x_yeni, y_yeni;
x_yeni = ob1.x + i;
y_yeni = ob1.y + i;
return coord(x_yeni,y_yeni);
}
// int + ob için aşırı-yükleniyor.
coord operator+(int i, coord &ob1)
{
int x_yeni, y_yeni;
x_yeni = i + ob1.x ;
y_yeni = i + ob1.y ;
return coord(x_yeni,y_yeni);
}
int main()
{
coord o1(10, 10);
int x, y;
o1 = o1 + 10; // nesne + tamsayı
o1.get_xy(x, y);
cout << "(o1+10) X: " << x << ", Y: " << y << "\n";
o1 = 99 + o1; // tamsayı + nesne
o1.get_xy(x, y);
cout << "(99+o1) X: " << x << ", Y: " << y << "\n";
return 0;
}
/* (o1+10) X: 20, Y: 20
(99+o1) X: 119, Y: 119 */

Arkadaş operatör fonksiyonlarını her iki durum için de aşırı-yüklememiz sayesinde bu deyimlerin her ikisi de geçerlidir:
o1 = o1 + 10;
o1 = 99 + o1;

Eğer arkadaş operatör fonksiyonlarını ++ veya -- birli operatörlerinden birini aşırı-yüklemek için kullanmak istiyorsanız, terimi fonksiyona bir referans parametresi olarak göndermeniz gerekir. Bunun nedeni arkadaş fonksiyonlarının this göstericilerinin olmamasıdır. Artırma ve eksiltme operatörlerinin terimde değişiklik yapacağını unutmayın. Fakat eğer bu operatörleri değer parametresi olan bir arkadaş fonksiyonu kullanarak aşırı-yüklerseniz, arkadaş operatör fonksiyonunun içerisindeki parametreye yapılan değişiklikler çağırma işlemini yapan nesneyi etkilemeyecektir. Nesneye herhangi bir gösterici üstü kapalı olarak gönderilmediği sürece (yani this göstericisi yokken) arkadaş kullanıldığında terimi etkileyecek bir arttırma veya eksiltme yapılması mümkün değildir.
Fakat eğer terimi arkadaşa bir referans parametresi olarak gönderirseniz, arkadaş fonksiyonunun içerisinde meydana gelen değişiklikler çağrıyı yapan nesneyi etkiler. Örneğin aşağıdaki program ++ operatörünü bir arkadaş fonksiyonu kullanarak aşırı-yüklemektedir:
// ++, arkadaş fonksiyonu kullanılarak aşırı-yükleniyor.
#include <iostream>
using namespace std;

class coord {
int x, y; // koordinat değerleri
public:
coord() { x=0; y=0; }
coord(int i, int j) { x=i; y=j; }
void get_xy(int &i, int &j) { i=x; j=y; }
friend coord operator++(coord &ob);
};

// ++, bir arkadaş fonksiyonu kullanılarak aşırı&#8212;yükleniyor.
coord operator++(coord &ob) // referans parametre kullanılıyor
{
ob.x++;
ob.y++;

return ob; // çağrıyı yapan nesne döndürülüyor
}

int main()
{
coord o1(10, 10);
int x, y;

++o1; // ol referans tarafından gönderiliyor
o1.get_xy(x, y);
cout << "(++o1) X: " << x << ", Y: " << y << "\n";

return 0;
}
// (++o1) X: 11, Y: 11
Eğer modern bir derleyici kullanıyorsanız, tıpkı üye fonksiyonlar gibi arkadaş fonksiyonlarını kullanırken de arttırma ve eksiltme operatörlerinin terimin başında veya sonunda bulunduğunu anlayabilirsiniz. Nesne operatörün sağındaysa bu durumu belirtmek için bir tamsayı parametre eklersiniz. Aşağıda örnekte coord sınıfı için arttırma operatörünün nesnenin solda ve sağda bulunma durumları için prototipi verilmektedir:
coord operator++(coord &ob); // başında
coord operator++(coord &ob, int notused); // sonunda

Eğer ++ terimden önce geliyorsa operator++(coord &ob) fonksiyonu çağrılır. Fakat, sonra geliyorsa, operator++(coord &ob, int notused) fonksiyonu kullanılır. Bu durumda notused&#8217;a 0 değeri gönderilecektir.

İndis Operatörüne ( Subscript Operator) &#8220;[]&#8220; Yeni Bir İşlev Yüklenmesi
Tüm operatör fonksiyonları için aynı kurallar geçerli olduğundan hepsini ayrı ayrı anlatmaya gerek yoktur. Ancak ilginç ve yararlı olabilecek bazı operatörler açıklanmıştır. Bunlardan biri de indis operatörüdür. Bu operatöre ilişkin fonksiyon iki farklı yapıda olabilir:
class C{
dönüş tipi & operator [] (parametre tipi);
ya da
const dönüş tipi & operator [] (parametre tipi) const;
};
Birinci yazım şekli, eğer bu operatör ile nesnenin verileri değiştirilecekse kullanılır. Bu durumda operatör bir atama operatörünün solunda yer alabilir. İkinci yazım şeklinde ise fonksiyon sabit olarak tanımlanmıştır. Bu durumda operatör ile nesnenin verileri sadece okunabilir. c bir nesne olmak üzere
c[i] ifadesi c.operator[](i) anlamına gelir.

cesitli06.gif

Örnek:
İndis operatörüne String sınıfında kullanılmak üzere bir işlev yüklenecektir. Bu operatör bir katardaki i. karaktere erişilmesini sağlayacaktır. Eğer i sıfırdan küçük verilirse katardaki ilk elemana, eğer i katarın boyundan büyük verilirse katardaki son elemana erişilmiş olacaktır.
// İndis operatörünün "[]" Katar dizileri (String) için yüklenmesi

#include <iostream>
#include <cstring> // string fonksiyonları için
using namespace std;

class String{ // Örnek (karakter katarı) String sınıfı
int boy; // Katarın boyu
char *icerik; // Katarın içeriği
public:
String(const char *); // Kurucu
char & operator[](int i); // İndis operatörü
void goster(); // Katarları ekrana çıkaran üye fonksiyon
~String(); // Yok edici fonksiyon
};


// Kurucu Fonksiyon
// Parametre olarak aldığı katarı nesnenin içeriğine kopyalar
String::String(const char *gelen_veri)
{
cout<< "Kurucu calisti" << endl;
boy = strlen(gelen_veri); // gelen katarın boyu hesaplandı
icerik = new char[boy +1]; // icerik için yer ayrıldı,+1 null ic.
strcpy(icerik, gelen_veri); // gelen veri icerik'in gos.yere kopy.
}


// İndis Operatörü
char& String::operator[](int i)
{
cout << "I