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

C++ sayfa 1


C++ : Daha iyi bir C

C++ programlama dili ,C diline bazı özelliklerin eklenmesiyle oluşturulmuştur.
Yapılan eklemeler üç grupta toplanabilir:
1.Programcıların kodlama aşamasında kullanabilecekleri teknik özellikler.
Bunlar programlama yöntemiyle ilgili değildir.
2.Nesneye dayalı programlama yöntemini destekleyen özellikler.
3.Üretken programlamayı(generic programming) destekleyen özellikler.
C++ , C dilinin bir üst kümesi olduğundan bu dilin hemen hemen tüm özelliklerini içerir.



Nesneye dayalı programlama ile ilgili olmayan ,ancak kodlama aşamasında C diline göre üstünlükler sağlayan eklentiler:

Tek satırlık açıklamalar

C de kullanılan /* */ sembolleri arasına yazılan ve birden fazla satıra yayılabilen açıklamalara ek olarak , C++ da sadece tek satır uzunluğunda olabilen ve // simgesiyle başlayan açıklamalar yazmak mümkündür.

z=x/y // açıklama satırı

Tanımlar(definition)

Değişkenler C dilinde blokların başlarında tanımlanırken C++ dilinde değişkenlerin kullanıldığı satırdan bir önce veya aynı satırda olabilir.
Bir veri ilk tanımlandığı yerden itibaren geçerli olup, tanımlandığı bloktan çıkınca geçerliliği sona erer. Bir verinin geçerli olduğu bloğa o verinin yaşam alanı (scope )denir.

Örnek
C’de C++ ‘da
{ {
int a; int a,b,c;
float b; c=a*b;
: int d;
: d=d/2;
} }
Burada a ve b geçerliliğini kaybeder. Burada da a, b, c,d geçerliliğini kaybeder.
C++ a diğer bir örnek
for (int i=0; i<n; ++i)

Bildirimler(Declarations)

Bir bildirim yapıldığında, derleyiciye o program dosyasında kullanılacak olan verilerin yada fonksiyonların sadece adı ve tipleri belirtilmiş olur. Bildirimler veriler veya fonksiyonlar
için bellekte yer ayırtmazlar.

Örnek
void fonksiyon(float,int); // burada bellekte yer ayrılmaz
void fonksiyon(float a,int b); // burada bellekte yer ayrılır


struct Date { // bu bildirimde de bellekte yer ayrılmaz
int day, month, year;
};

Date d1,d2; // bellekte d1 ve d2 için ayrı ayrı 6 bytlık yer ayrılacaktır.

Yaşam alanı (scope)

Bir C++ programında iç içe yer alan bloklarda, aynı isimde veriler tanımlanabilir. Böyle bir durumda iç bloktaki (yerel)veri, aynı isme sahip olan dış bloktakini(global) örter(değeri bozulmaz).
Yaşam alanı belirtme (scope Resolution)operatörü(::) kullanılarak örtülmüş olan verilere de ulaşmak mümkündür.

int a=3;
int b=2;
void fonk( ) {
int a=1;
::a++; //global a=4
a++; //yerel a=2
b++; // global b=3
}

İsim uzayı (Name Space)

Program boyları büyüdükçe yazılan programlarda isim çakışması olabildiği gibi, nesne veya
fonksiyon arşivlerinin kullanımlarında da çakışmalar olabilir . Bunu önlemek için C++ da
isim uzayı kavramı kullanılmaktadır

namespace programcı1 {
int no;
:
}
namespace programcı2 {
int no;
void fonk(char);
:
}

İsim uzayında verilere erişilmesi:
Programcı2::no=1234;
Programcı1::no= 78;
Programcı2::fonk(&#8216;a&#8217;)

using Bildirimi:

İsim uzaylarındaki verilere her erişimde uzayında isminin yazılması gereğini ortadan kaldırarak erişimi kolaylaştırmak için using bildirimi kullanılmaktadır.
using programcı2::no; //isim uzayında bir değişken için geçerli
no=1234;
Programcı1::no= 78;
Programcı2::fonk(&#8216;a&#8217;);

Bu bildirim istenirse tek bir veri yerine isim uzayının tamamı için yapılabilir. Bunu için using namespace sözcükleri birlikte kullanılır.

using programcı2; //isim uzayının tamamı için geçerli
no=1234;
fonk(&#8216;a&#8217;)
Programcı1::no= 78; //diğer isim uzayı için isim belirtmek gerekli

Standart C++ Başlık(Header )Dosyaları

C++ ın ilk sürümlerinde başlık dosyalarında .h uzantıları kullanılmıştır.
Değişik işletim sistemi ve derleyicilerde değişik uzantılar olduğu için uzantılar tamamen kaldırılmıştır. Standart arşivler düzenlenirken tüm veriler std adındaki bir isim uzayı içinde tanımlanmıştır. C den gelen başlık dosyaları da başına c harfi koyarak yeni standarda uydurulmuştur.
Yeni derleyici Eski derleyici
#include<iostream> #include<iostream.h>
using namespace std;

C&#8217; de C++&#8217; da

#include <stdio.h> #include <cstdio>
#include <stdlib.h> #include <cstdlib>

Giriş/ Çıkış İşlemleri

C++ da C den farklı olarak giriş/çıkış fonksiyonlarının yerine giriş /çıkış nesneleri kullanılır.

Cin : Standart giriş birimi. Çoğunlukla tuş takımı işlemlerinde kullanılır. Cin >>
Cout : Standart çıkış birimi. Çoğunlukla ekran işlemlerinde kullanılır. Cout<<

Örnek 1
#include<iostream>
using namespace std;
int main( ) {
int a,b;
float c;
a=150;
b=305;
c=25.125;
cout<<&#8221;değerler: &#8220;;
cout <<a;
cout <<&#8217; &#8216;;
cout<<b;
cout <<&#8217; &#8216;;
cout<<c;
return 0;
}

programın sonucu:

değerler: 150 305 25.125

Örnek 1
#include<iostream>
using namespace std;
int main( ) {
int a,b;
float c;
a=150;
b=305;
c=25.125
cout<<&#8221;değerler: &#8220;;
cout <<a<<&#8217; &#8216;<<b<<&#8217; &#8216;<<c;
return 0;
}

programın sonucu:

değerler: 150 305 25.125

Mantıksal Değişkenler

C dilinde, doğru(true),yanlış(false) gibi mantıksal değerleri tutmak için özel değişkenler yoktur.Bu işlemler için de tamsayılar kullanılır. Sıfırdan farklı değerler doğru(true) olarak kabul edilirken sıfır değeri yanlış (false) olarak kabul edilir.

C++ da C deki durum korunurken ayrıca bool ve true, false sözcükleri de derleyiciye eklenmiştir.
Örnek
Bool esit; //mantıksal değişken
esit= false ;
int x=0;
int y=0;

esit = x = = y;
if(esit)---

Sabitler
Standart C dilinde sabitler tanımlamak için önişlemcinin #define direktifi kullanılmaktadır.
#define PI 3.14
C++ da ise const sözcüğü kullanılır.

const int i = 100; //i sabittir ve değeri 100 dür.
........
i=5; // Derleme hatası i sabit olarak tanımlanmıştır, değiştirilemez.
Diğer bir gösteriliş şekli

int const i = 100; //const int i =100; ile aynı

Dinamik Bellek Yönetimi

C dilinde bellekte yer alma gerektiğinde malloc( ),ayrılan yeri geri vermek için de free( )
fonksiyonları kullanılıyordu. C++ da ise dinamik bellek işlemlerini gerçekleştirmek için C ye göre kullanımı daha kolay olan ve daha çok yeteneğe sahip operatörler tanımlanmıştır. Bu operatörlerden new bellek ten yer almak için, delete ise alınan yerleri geri vermek için kullanılır. Bu operatörlerin genel kullanım formu aşağıdaki gibidir.
p_var = new type;
delete p_var;
type, bellekte yer ayrılan nesnenin türü, p_var ise göstericinin türüdür. new operatörü ,type ile türü belirtilen nesneyi alacak kadar büyüklüğü olan belleğe bir gösterici(pointer) döndürür. delete ise iş bittiğinde belleği geri verir. delete sadece daha önce new ile ayrılmış belleğin göstericisi ile çağrılır. delete başka gösterici ile çağrılırsa program kilitlenebilir.

Yer ayırma işleminin gerçekleşmesi için yeteri kadar bellek bulunmuyorsa , ya boş bir gösterici döndürür veya bir ayrıcaklı durum oluşur. (Bu hatalar çalışma anında ortaya çıkar)
Uygulamada hata durumunda new &#8216;ün göstericisinin boş dönüp dönmediğini kontrol etmek yeni derleyiciye zarar vermeyeceğinden şimdilik kontrol etmekte yarar vardır. En iyisi derleyicinin dokümanına bakmaktır. New ve delete&#8217;in malloc() ve free&#8217;ye göre aşağıdaki üstünlükleri vardır.

1) new size of komutunu kullanmadan nesnenin tipine göre yeterli miktarda yer ayırır.
2) Kendiliğinden belirtilen tip için bir gösterici döndürür.
3) new ve delete komutlarının her ikisi de yeniden yüklenebilir. Bu sayede kendi özel yer ayırma sisteminizi oluşturma olanağı tanınır.
4) Dinamik olarak oluşturulmuş bir nesneye hazırlık amacıyla başlangıç öncesi iş yaptırmak mümkündür.
5) <cstdlib> satırını da program içerisinde bulundurulması gerekmez.

örnek:
#include<iostream>
using namespace std;
int main() {
int *p;
p=new int; // tamsayı için bellekte yer ayırır
if(!p){
cout<<&#8221;bellekte yer ayırma hatası&#8221;\&#8221;;
return 1;
}
*p=100;
cout<<&#8221;p&#8217; deki tamsayı: &#8220;<<*p<<&#8221;\n&#8221;;
delete p;
return 0;
}
dinamik olarak yer ayrılan belleğe başlangıç değeri genel formülü aşağıda gösterildiği gibi başlangıç değeri atanabilir.

p_var=new type (ilk_değer);
Yukarıdaki örneği

örnek:
#include<iostream>
using namespace std;
int main() {
int *p;
p=new int(100);
if(!p){
cout<<&#8221;bellekte yer ayırma hatası&#8221;\&#8221;;
return 1;
}
cout<<&#8221;p&#8217; deki tamsayı: &#8220;<<*p<<&#8221;\n&#8221;;
delete p;
return 0;
}

new kullanarak tek boyutlu diziye dinamik bellek ayrılmak istenirse aşağıdaki genel formül kullanılır.

p_var=new type[eleman sayısı]

bu komut çalıştırıldığında p_var&#8217;a dizinin ilk elemanının adresi atanacaktır. Bazı teknik sebeplerden diziye ilk değer atmak mümkün değildir. Ayrılan yeri yok etmek için delete[]p; şekli kullanılır.
Örnek;
------
------
int *p=new int[10];
if(!p){
cout<<&#8221;bellekte yer ayırma hatası&#8221;\&#8221;;
return 1;
for(int k=0; k<10; k++) // Tanıtım ortalarda olabilir
p[k] = 0;
delete [ ] p;
----
----

Fonksiyon Bildirimleri Tanımları

1) C++ dilinde hata olasılığını azaltmak için fonksiyon çağrılırken tür kontrolleri yapmak zorunludur. Fonksiyon çağrıldığı programda(dosyada ) bildirimi yani prototipi olmalıdır. Prototipte parametrelerin sadece türleri belirtilirken , fonksiyonun çağrıldığı yerde gerçek argümanların isimleri bulunur(sabit veya değişken).

char basari(int, int, int); // basari adli fonksiyonun prototipi (bildirimi)
Fonksiyonun tanımında parametrelerin gerçek adları yer alacaktır:
char basari (int vize1, int vize2, int final) // basari adli fonksiyonun tanımı
{
char sonuc;
&#8230;&#8230;&#8230;&#8230; // Fonksiyonun gövdesi, sonuc uygun şekilde hesaplanacak
return sonuc;
}

2) Bir fonksiyonun parametresi yoksa ,C de parametre olarak void yazılmalıdır. C++ da ise hiç bir şey yazılmaz.
İnt fonk(void) // C de
int fonk( ) // C++ da
3) C++ da fonksiyon geriye bir değer gönderecek şekilde tanıtılmışsa mutlaka o değeri göndermelidir. Yani return deyimi mutlaka bir değer içermelidir. C de fonksiyonun döndüreceği sonucun türü tam olarak belirlenmezse bu türün tamsayı olduğu varsayılır. C++ da böyle bir durum olmadığı için fonksiyonların return türü tam olarak belirlenmelidir.
4) C de yerel değişkenler sadece blokların başında tanımlanırken , C++ da ilk kullanıldıkları yere yakın bir yerde tanımlanabilirler.

İnline fonksiyonlar(Makrolar)

C dilinde makroları tanımlamak için önişlemcinin #define direktifi kullanılmaktadır. C++ da ise makrolar normal fonksiyonlar gibi yazılabilmektedir.

Fonksiyonların gövdeleri bir kez derlenir ve makine dilindeki kod belleğe yerleştirilir. Bu fonksiyon her çağrıldığında çağıran programdan fonksiyonun koduna gidilir ve bu kod yürütüldükten sonra yine çağıran programa dönülür.
Makrolarda ise, çağrının yapıldığı her yere makronun kodu eklenir. Yani her makro çağrısı programın boyunun uzamasına neden olur. Burada başka bir koda gitme, geri gelme ve parametre aktarımı olmadığından makro çağrıları daha hızlı gerçekleşirler. Özellikle hızlı çalışmasını istediğimiz ve kodu kısa olan program parçalarını makro olarak oluşturmak doğru olacaktır.

//bir inline fonksiyon örneği
#include<iostream>
using namespace std;
inline int tek(int x)
{
return (x%2);
}
int main() {
int y;
if (tek(y))
cout <<y<< &#8220; tek sayıdır \n&#8221;;
else
cout<<y << &#8220; cift sayıdır \n&#8221;;
return 0;
}
(inline fonksiyonlar class lar içerisinde değişik şekillerde kullanılacaktır.)
Fonksiyon Parametrelerine Başlangıç Değerlerinin Verilmesi

Örnek olarak aşağıda üç adet parametre alan bir fonksiyon tanımlanmıştır. Birinci parametrenin ( char c, int i1 = 0 , int i2 = 1) // i1 ,i2 nin başlangıç değerleri var
{
........ //fonksiyonun gövdesi
........
}

Bu fonksiyon aşağıdaki gibi üç farklı şekilde çağrılabilir:

fon(&#8216;A&#8217;,4,6); // c=&#8217;A&#8217;, i1= 4, i2=6
fon(&#8216;B&#8217;,3 ); // c=&#8217;B&#8217;, i1= 3, i2=1
fon(&#8216;C&#8217;); // c=&#8217;C&#8217;, i1= 0, i2=1
Bir fonksiyon çağrılırken parametrelere karşı düşecek olan argümanlar soldan sağa doğru verilmelidir.
fon(&#8216;C&#8217;,,7 ); //HATA! İkinci parametre eksik bırakılmış, üçüncü verilmiş

fonksiyonlar yazılırken de parametrelere başlangıç değerleri sağdan başlanarak ve parametre atlamadan verilmelidir.
void fon(char c= &#8216;A&#8217;, int i1, int i2 =1) // HATA ! Aradaki parametre atlanmış

Fonksiyonların Aşırı Yüklenmesi( Function overloading)
C++ derleyicileri fonksiyonları birbirinden ayırırken sadece isimlerini değil parametre listelerini de dikkate alırlar. Buna göre iki farklı fonksiyon, parametrelerinin tipleri, sayıları veya sıraları farklı olmak koşulu ile aynı isme sahip olabilirler. Bir fonksiyonun ismi ve parametre listesi o fonksiyonun imzasını ( signature) oluştururlar.
İki veya daha fazla fonksiyon aynı adı paylaşıyorsa onlara aşırı yüklenmiş denir. Aşırı yüklenmiş fonksiyonlar, birbiriyle alakalı işlemlerin aynı adla çağrılmasına izin vererek programın karmaşıklığını azaltabilirler.

PROGRAM 1
// Programın normal yazılmış hali
#include<iostream.h>
//using namespace std;
int absi(int n){
return n<0 ? -n :n;}

float absf(float n){
return n<0 ? -n :n;}

double absd(double n){
return n<0 ? -n :n;}

main()
{
int j=5;int l=-6; float r=-10.5;double x=-12.1234567898;

cout<<endl;
cout<<"k="<<absi(j)<<endl;
cout<<"lk="<<absi(l)<<endl;
cout<<"s="<<absf(r)<<endl;
cout<<"y="<<absd(x)<<endl;
return 0;
}
Çıktılar

j=5, l=6, r=10.5, x=12.1234567898

PROGRAM 2
// parametre türleri farklı aynı isimde kullanılmaları
#include<iostream.h>
//using namespace std;
int abs(int n){
return n<0 ? -n :n;}

float abs(float n){
return n<0 ? -n :n;}

double abs(double n){
return n<0 ? -n :n;}

main()
{
int j=5;int l=-6; float r=-10.5;double x=-12.1234567898;

cout<<endl;
cout<<"j"<<abs(j)<<endl;
cout<<"l="<<abs(l)<<endl;
cout<<"r="<<abs(r)<<endl;
cout<<"x="<<abs(x)<<endl;
return 0;
}
Çıktılar

j=5, l=6, r=10.5, x=12.1234567898

PROGRAM 3
// Parametre sayıları farklı
#include<iostream.h>
using namespace std;
int topla(int x,int y)
{
int tp1;
tp1=x+y;
return tp1;
}
int topla(int a,int b,int c)
{
int tp2;
tp2=a+b+c;
return tp2;
}
main(){
int a,b,c,t,p;
a=5; b=3; c=4; t=2; p=6;
int r,s,q;
r=topla(a,t);
s=topla(c,b,p);
q=topla(9,b);
cout<<"r="<<r<<" s="<<s<<" q="<<q<<endl;
return 0;
}
Çıktılar
r=7, s=13, q=12

PROGRAM 4
// parametre sayıları ve türleri farklı
#include<iostream.h>
using namespace std;
int topla(int x,int y)
{
int tp1;
tp1=x+y;
return tp1;
}
float topla(int a,float b,float c)
{
float tp2;
tp2=a+b+c;
return tp2;
}
main(){
int a,t,p;
float b,c;
a=5; b=6.4; c=7.2; t=2; p=6;
int r,q;
float s;
r=topla(a,t);
s=topla(p,b,c);
q=topla(9,b);
cout<<"r="<<r<<" s="<<s<<" q="<<q<<endl;
return 0;
}
Çıktılar
r=7, s=19.6, q=15

PROGRAM 5
#include <iostream>
using namespace std;
struct ComplexT{ // Karmaşık sayıları için bir yapı
float re,im; // reel ve sanal kısımlar
};
void print (float deger){ // reel sayılar için
cout <<" deger = " << deger << endl;
}
void print (ComplexT c){ // Karmaşık sayılar için
cout <<"reel= " << c.re << " , sanal= " << c.im << endl;
}
void print (float deger,char c){ // reel sayılar ve char için
cout << "deger= " << deger << " , c= " << c << endl;
}
int main()
{
ComplexT z; Çıktılar
z.re=0.5;
z.im=1.2; reel=0.5, sanal=1.2
print(z); değer=4.2
print(4.2); değer=2.5, c=A
print(2.5,'A');
return 0;
}

Referans
C++'da ampersan (&) simgesini kullanarak değişkenler için referanslar tanımlamak mümkündür. Bir değişken için referans tanımlanması aynı bellek gözüne ikinci bir isim vermek demektirDiğer taraftan referansa bir değişkenin diğer ismi gibi davranan bir göstericidir de denilebilir. Referanslar, bağımsız bir referans oluşturularak, bir fonksiyona geçirilerek, ve bir fonksiyon tarafından döndürülerek üç farklı şekilde kullanılırlar.

Örnek1
// gösterici
int b; // b bir tamsayı değişkeni
int *a = &b; // a bir gösterici olarak tanımlanmış ve b nin adresi a ya aktarılmış
* a = 10; // a ya 10 değeri aktarılır. Bu b nin değerini de 10 yapar

// referans
int b; // b bir tamsayı değişkeni
int &a = b; // burada a bir referans olarak b nin adresini göstermektedir
a = 10; // a ya 10 değeri aktarılır. Bu b nin değerinide 10 yapar.

örnek 2
int i = 5;
int &j = i; // j, i'ye bir referanstır. j ve i aynı adrese sahipler.
j++; // i = 6 olmuştur.

Fonksiyonlara Parametrelerin Referans Olarak Gönderilmesi
C'de eğer parametre olarak gönderilen bir verinin orijinal değerinin fonksiyon
içinde değişmesi isteniyorsa bu durumda o verinin adresinin parametre olarak
fonksiyona gönderilmesi gerekir.

Örnek 1
//gösterici ile
#include <iostream>
using namespace std;
void f(int *n); // gösterici(gösterici) parametresi kullanma işlemi
int main()
{
int i = 0;
f(&i);
cout << &#8220;i&#8217;nin yeni değeri: &#8220; << i << &#8216;\n&#8217;;
return 0;
}
void f(int *n)
{
*n = 100; // n tarafından gösterilen argümana 100 yerleştirme
}

//referans ile
#include <iostream>
using namespace std;
void f(int &n); // referans parametresi bildirme
int main(){
int i = 0;
f(i);
cout << &#8220;i&#8217;nin yeni değeri: &#8220; << i << &#8216;\n&#8217;;
return 0;
}
void f(int &n) // f( ) artık referans parametresi kullanıyor
n = 100; // şimdiki komut için * operatörüne gerek kalmamıştır
} // f() çağırmak için kullanılan argümana 100 koy

Örnek 2
/* C'deki Çözüm: Göstericiler*/
void hesap(int *j) {
*j = *j**j/2; /* Okuması ve anlaması zor */
}
int main()
{
int i=5;
hesap(&i); /* i'nin adresi gönderiliyor */
return 0;
}
Örnekte kullanılan & simgesi adres operatörüdür (referans değildir).

Aynı fonksiyonun C++'da referanslar kullanılarak yazılması
// C++'da referanslar ile çözüm
void hesap(int &j) // j gelecek olan veriye bir referanstır, aynı adrese sahiptir
{ j = j*j/2; } // Fonksiyonun gövdesi, j normal bir değişken gibi
int main( )
{
int i=5;
hesap(i);
return 0;
}
Büyük boyutlu veriler bir fonksiyona parametre olarak aktarılırken değerleri yerine adreslerinin aktarılması tercih edilir.Çünkü değer olarak aktarılması durumunda büyük boyutlu verinin tamamı yığına kopyalanacaktır.Bu da hem bellekte fazla yer harcanmasına hem de programın yavaşlamasına neden olacaktır. Eğer bir veri içeriğinin değişmesi için değil, sadece aktarım zamanını kısaltmak için referans olarak aktarılacaksa fonksiyondaki referans parametresi sabit (const) olarak tanımlanmalıdır.

Örnek 3
#include <iostream> // Giriş/çıkış için
#include <cstring> // Karakter katarları için
using namespace std;
struct Kisi{ // Kişileri tanımlamak için bir yapı
char isim [40]; // İsim alanı 40 sekizli
int sicil_no; // Sicilno alanı 4 sekizli
}; // Toplam 44 sekizli
void goster (const Kisi &k) // parametre sabit referans
{
cout << "İsim: " << k.isim << endl; // isim ekrana
cout << "No: " << k.sicil_no << endl; // sicil_no ekrana
}
int main(){
Kisi ahmet; // Kisi tipinden bir veri ahmet
strcpy(ahmet.isim,"Ahmet Bilir"); // isim = "Ahmet Bilir"
ahmet.sicil_no=324; // sicil_no= 324
goster(ahmet); // Fonksiyon çağırılıyor.
return 0;
}

Fonksiyonlardan Verilerin Referans Olarak Döndürülmesi
C++'da ise gerekli olduğu durumlarda geri döndürülen veri bir referans olarak tanımlanabilir ve bu durumda değer aktarımı adresler ile yapılır. Bu tür aktarım iki yarar sağlar:
&#8226; Büyük boyutlu veriler yığına kopyalanmamış olur
&#8226; Geri döndürülen adrese çağıran program içinde atama yapılabilir
// max fonksiyonu dizinin başlangıç adresini ve uzunluğunu
// alıyor en büyük elemana bir referansı geri döndürüyor
int& max(int a[], int uzunluk)
{
int i=0; // i en büyük elemanın indisi olacak
for (int j= 0 ; j<uzunluk ; j++) // En büyük eleman aranıyor
if (a[j] > a[i]) i = j;
return a[i]; // Eleman geri döndürülüyor (referans)
}

int main()
{
int dizi[] = {12, -54 , 0 , 123, 63}; // Bir dizi tanımlanıyor, değerler atanıyor
max(dizi,5) = 0; // En buyuk eleman bulundu ve sıfırlandı
for(int i=0; i<5; i++)
cout << array[i] << " "; // Dizi ekranda görüntüleniyor
return 0;

Böyle durumlarda geri döndürülen verinin ana programda değiştirilmemesi istenebilir. Bunu sağlamak için geriye sabit (const) referanslar döndürülür. Büyük boyutlu verileri geri döndürmek için de referanslar kullanılır.
// max fonksiyonu dizinin başlangıç adresini ve uzunluğunu alıyor
// en büyük elemana bir sabit referansı geri döndürüyor
const int& max(int a[], int uzunluk)
{
int i=0; // i en büyük elemanın indisi olacak
for (int j= 0 ; j<uzunluk ; j++) // En büyük eleman aranıyor
if (a[j] > a[i]) i = j;
return a[i]; // Eleman geri döndürülüyor (referans)
}
Bu şekilde yazılan fonksiyon atama deyimlerinin sadece sağında yer alabilir.
int main()
{
int buyuk; // En büyük elemanı tutacak değişken
int dizi[] = {12, -54 , 0 , 123, 63}; // Bir dizi tanımlanıyor, değerler atanıyor
buyuk = max(dizi,5); // En buyuk eleman buyuk'e atandı
return 0;
}
Yerel değişkenler referans ile döndürülemez!
Yerel değişkenler sadece tanımlandıkları bloğun içinde geçerlidirler. Benzer
şekilde bir fonksiyonun içinde tanımlanan yerel değişkenler de fonksiyondan
çıkıldıktan sonra bellekten kaldırılırlar.
int& f( ) // Referans ile veri geri döndürülecek
{
int i; // Yerel değişken. Bir çok sistemde yığında yaratılır
:
return i; // Hata! i'nin adresi artık geçerli değildir.
}
Doğru olan yerel değişkenlerin değer olarak döndürülmeleridir.
int f( ) // Değer döndürülecek
{
int i; // Yerel değişken. Bir çok sistemde yığında yaratılır
:
return i; // Doğru, çünkü değer döndürülüyor.
}
Operatörlerin Aşırı Yüklenmeleri( Operator Overloading)

Genel formülü
Dönüş tipi sınıf adı::operatör#(argüman_listesi)
{
// gerçekleştirilecek işlem
}
C++'da, + , - , *, !, ++ gibi operatörlere ilişkin fonksiyonlar yazılarak bu operatörlere yeni işlevler yüklenebilir.
&#8226; Sadece C++'da zaten var olan operatörler için fonksiyon yazılabilir. Örneğin C++'da var olmayan '^' operatörü için üs almak üzere bir fonksiyon yazılamaz.
&#8226; Operatörlerin orijinalindeki operand sayısı değiştirilemez. Örneğin '+' operatörü için mutlaka iki operandlı (parametreli) bir fonksiyon, '!' operatörüiçin tek operandlı bir fonksiyon yazmak gerekir.
&#8226; Operatörlerin orijinal öncelikleri değiştirilemez. Örneğin yeni bir işlev yüklense bile '*' operatörü her zaman '+' operatöründen daha önceliklidir. Bir operatöre işlev yükleyen fonksiyonların adı operator özel sözcüğünü içerir. Örneğin '+' operatörüne işlev yükleyecek olan fonksiyonun adı operator+ ve '*' operatörüne işlev yükleyecek olan fonksiyonun adı da operator* olmalıdır.

İ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.

// Example for opertor overloading
struct ComplexT{ // Karmaşık sayıları tanımlamak için bir yapı
float re,im; // Reel ve sanal kısımlar
};
// + operatörünün karmaşık sayıları toplamak için yüklenmesi
ComplexT operator+ (const ComplexT &v1, const ComplexT &v2)
{
ComplexT sonuc; // sonucun yazılacağı yerel değişken
sonuc.re=v1.re+v2.re; // reel kısımlar toplanıyor
sonuc.im=v1.im+v2.im; // sanal kısımlar toplanıyor
return sonuc; // sonuç çağıran programa döndürülüyor
}
void print (ComplexT c){ //kompleks sayıları yazan print fonksiyonu
cout<< "real= " << c.re << " im= " << c.im << endl;
}
int main()
{
ComplexT c1,c2,c3; // Üç adet karmaşık sayı tanımlanıyor
c1.re= 0.5; // Sayıların alanları dolduruluyor
c1.im= -1;
c2.re= 1.5;
c2.im= 0.5;
c3= c1 + c2;
// Yukarıdaki operator+ fonksiyonu
print(c3); // c3= operator+(c1,c2); aynı işi yapar
return 0;
}

NESNE MODELLERİ : SINIFLAR 4/10/2004
Bu bölümünden itibaren C++ programlama dilinin nesneye dayalı programlamaya yönelik özellikleri tanıtılacaktır. C++ ın en önemli özelliği sınıflardır. Bu özellikler yazılımların kalitesini yükseltmek amacıyla geliştirilmişlerdir.Bilgisayar programı yazmanın, aslında gerçek dünyadaki unsurların bilgisayarda birer modellerinin oluşturulması anlamına gelir.
Gerçek dünya nesnelerden oluşmaktadır. Bu nedenle bilgisayar programları da bu nesnelerin modellerinden oluşturulmaktadır.

Nesne Nedir?
Gerçek dünyada bir nesne iki kısımdan oluşur:
1. Nitelikler. Buna durumlar ya da özellikler de denir.
2. Davranışlar (yetenekler).
Nesnelerin bilgisayarda kurulan modellerinde ise aşağıda gösterildiği gibi nitelikleri belirtmek için veriler, davranışları belirtmek için de fonksiyonlar kullanılacaktır.
Gerçek dünyada nesne = Nitelikler + Davranışlar
Yazılım dünyasında nesne = Veriler + Fonksiyonlar

Sınıflar ve Nesneler
C++'da nesnelerin modellerini oluşturmak için sınıf ( class) adı verilen bir yapı kullanılmaktadır. class&#8217;ların bildirimi yapılarınkine benzer bir şekilde yapılır. Bu bildirimin genel formatı şu şekildedir.
Class sınıf _adı {
// private (özel) fonksiyonlar ve değişkenler
public:
// public (genel) fonksiyonlar ve değişkenler

Örnek olarak grafik programlarında kullanılacak nokta nesnelerini tanımlamak üzere bir model tasarlansın. Noktalar iki boyutlu düzlemde yer alacağından özellik olarak iki adet koordinat bilgisine sahiptirler:
&#8226; x ve y koordinatları. Bu özellikler tamsayı değişkenler ile ifade edilebilirler.
Örnek programımızda noktaların sahip olması gereken yetenekler (davranışlar) ise şunlardır:
&#8226; Noktalar, düzlemde herhangi bir yere konumlanabilmeli: git fonksiyonu
&#8226; Noktalar bulundukları koordinatları ekrana çıkartabilmeli: goster fonksiyonu
&#8226; Noktalar, sıfır (0,0) koordinatında olup olmadıkları sorusunu yanıtlayabilmeli: sifir_mi
fonksiyonu
Bu yapı bir nesneyi oluşturacak olan verileri ve fonksiyonları birlikte barındırmaktadır.
Sınıflar, nesnelerin modeli diğer bir deyişle şablonudur. Programda bir kez sınıf yazılıp şablon oluşturulduktan sonra o sınıftan gerektiği kadar nesne yaratılabilir.
Nokta sınıfı:
class Nokta{ // Nokta Sınıfı
int x,y; // Nitelikler: x ve y koordinatları
public:
void 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ığı
};

Bildirim class sözcüğü ile başlar, daha sonra sınıfın ismi gelir (Nokta).
Yukarıdaki örnekte önce veriler sonra fonksiyonlar yazılmıştır. Bu sıra ters de olabilir.
Sınıfın içindeki veri ve fonksiyonlara o sınıfın üyeleri ( member) denir. Sınıf bildirimi noktalı virgül (;) ile bitirilir. Örnek sınıf bildiriminin içinde fonksiyonların sadece prototipleri yer
almaktadır. Fonksiyonların gövdeleri ise ayrı bir yerde tanımlanabilir. Sınıf class kelimesi kullanılarak bildirimi yapılır. Üye fonksiyonlara o sınıfın metotları da ( method) denilmektedir.

// ***** Üye Fonksiyonların Gövdeleri *****
Sınıfa ait fonksiyonların genel formatı aşağıda şekilde olduğu gibidir.

döndürme_ tipi sınıf_adı::fonksiyon_ adı (parametre_listesi)
{
// fonksiyon yordamı(gövdesi)
}

Burada sınıf_ adı fonksiyonların ait olduğu sınıfın adıdır. Üye fonksiyonları tanımlamak için , sınıfın tip adını fonksiyon adıyla bağlamak gerekir. Bu işlem , sınıf adı ve iki nokta üst üste işaretlerinin ardından fonksiyonun adı yazılarak yapılabilir. Yan yana yazılmış olan iki tane iki nokta üst üste işaretine kapsam çözümleme operatörü adı verilir.

// Noktanın hareket etmesini sağlayan fonksiyon
void Nokta::git(int yeni_x, int yeni_y)
{
x = yeni_x; // x koordinatına yeni değer atandı
y = yeni_y; // y koordinatına yeni değer atandı
}
// Noktanın koordinatlarını ekrana çıkaran fonksiyon
void Nokta::goster()
{
cout << "X= " << x << ", Y= " << y << endl;
}
// Noktanın (0,0) koordinatlarında olup olmadığını belirten fonksiyon
bool Nokta::sifir_mi()
{
return (x == 0) && (y == 0); // x=0 VE y=0 ise doğru
}

Üye fonksiyonların gövdeleri de yazıldıktan sonra Nokta modeli (şablon) tamamlanmıştır. Artık bu sınıftan gerektiği kadar nokta nesnesi yaratılabilir. Sınıfa ait bir nesne oluşturulduğunda , yapıya ait üyelere erişildiği gibi, nokta(dot, periyot) operatörünü kullanarak bu nesnelere de erişilebilir.

int main()
{
Nokta n1,n2; // 2 adet nesne tanımlandı n1 ve n2
n1.git(100,50); // n1 (100,50)'ye gönderiliyor
n1.goster(); // n1'in koordinatları ekrana çıkartılıyor
n1.git(20,65); // n1 (20,65)'e gönderiliyor
n1.goster(); // n1'in koordinatları ekrana çıkartılıyor
if(n1.sifir_mi()) // n1 sıfır da mı?
cout << "n1 şu anda sıfır noktasındadır." << endl;
else
cout << "n1 şu anda sıfır noktasında değildir." << endl;
n2.git(0,0); // n2 (0,0)'a gönderiliyor
if(n2.sifir_mi()) // n2 sıfır da mı?
cout << "n2 şu anda sıfır noktasındadır." << endl;
else
cout << "n2 şu anda sıfır noktasında değildir." << endl;
return 0;
}
// ekran cıktıları
// X=100, Y=50
// X=20, Y=65
// n1 şu anda sıfır noktasında değildir.
// n2 şu anda sıfır noktasında değildir.

Programda iki adet nesne (n1 ve n2) tanımlanmıştır. Bu nesnelerin üye fonksiyonları çağırılarak nesnelerin belli davranışlarda bulunmaları sağlanmıştır. Nesnelerin metotlarının canlandırılmasına o nesneye mesaj göndermek denir.

Değerlendirme:
Sınıflar ve nesneler hakkında bu aşamaya kadar öğrenilenler değerlendirildiğinde şu sonuçlara varılabilir:
Sınıflar 'aktif' veri tipleridir. Bu tiplerden nesneler tanımlanır. Nesnelere mesajlar gönderildiğinde nesneler bir davranışta bulunarak cevap verirler. Sınıflar hem verileri hem de bu veriler üstünde işlem yapan fonksiyonları aynı paket içinde barındırırlar ( DEPOLAMA) (ENCAPSULATION). Depolama, kodu ve kodun işlediği verileri bir araya getiren ve onları dış etkilerden , yanlış kullanımlardan koruyan mekanizmadır. Nesneye dayalı dillerde kod ve veri, bir &#8220;kara kutu&#8221; oluşturacak şekilde bir araya getirilir. Kod ve verinin bu şekilde birbirine bağlanmasıyla da nesne meydana getirilmiş olur. Diğer bir deyişle nesne depolama yaptığımız yerdir. Bunun sonucu olarak:
&#8226; Nesneler gerçek dünyadakine benzer şekilde modellenebilir,
&#8226; Program daha kolay okunabilir, çünkü birbirleriyle ilgili olan veriler ve fonksiyonlar bir
aradadır,
&#8226; Hataları bulmak kolay olur, çünkü bu veriler üzerinde işlem yapabilen fonksiyonlar bellidir.

Sınıf Üyelerine Erişimin Denetlenmesi
Programcılar, sınıfların oluşturulması ve kullanılması açısından ikiye ayrılırlar.Birincisi sınıfları yazan programcılardır. İkincisi ise sınıfları kullanarak nesneler yaratan ve bu nesnelere mesajlar göndererek onların canlandıran programcılardır. Sınıfı oluşturan programcı sınıf yapısının doğru çalışmasından sorumludur. Sınıfı nesne tanımlamak için kullanan programcı sınıfın iç yapısını bilmek zorunda değildir. Bilmesi gereken tek şey nesnelere nasıl mesaj yollanacağıdır. Bu özellikleri sağlamak için, sınıfı yazan programcı, sınıfın bazı üyelerini gizleyerek ( data hiding) onlara sınıf dışından erişilmesini engelleyebilir. Bir sınıfın içindeki üyelere erişimi denetleyen üç farklı etiket bulunmaktadır: public (açık), private (özel) ve protected (korumalı). Açık (public) olarak tanımlanan üyelere, bu sınıftan yaratılan tüm nesneler yoluyla erişilebilir.
Özel (private) olarak tanımlanan üyelere (veri ve fonksiyon) ise sadece sınıfın içindeki fonksiyonlar erişebilir. Bu sınıftan yaratılan nesneler üzerinden özel üyelere erişmek mümkün değildir.
Korumalı (protected) üyeler ise kalıtım ( inheritance) özelliği ile birlikte kullanılmaktadır. Kalıtım konusu anlatılırken korumalı erişim yöntemi de açıklanacaktır.

Bir sınıfın içindeki açık elemanlar o sınıfın dışarıya verdiği hizmetleri ( services) tanımlarlar. Bunların tümüne sınıfın arayüzü ( interface) denir. Özel üyeler sınıfın gerçeklenmesiyle ( implementation) ilgili olduklarından sınıfın kullanıcılarını ilgilendirmezler. Bir sınıftaki üyelerin erişim tipi normalde özeldir (private). Sınıfın yazarı dışarıya açmak istediği üyelerin tanımından önce public: etiketini koyar. Bu nedenle örnekteki Nokta sınıfındaki ilk iki üye (x , y) özeldir ve dışarıya kapalıdır. Ana programda yaratılan nesneler üzerinden bu verilere erişmek mümkün değildir.





Örnek olarak Nokta sınıfından üretilen noktaların hareket alanlarının x ekseninde 0-500, y ekseninde ise 0-300 aralığında olması ve noktalar bu alanın dışına çıkamamaları istensin.
class Nokta{ // Nokta Sınıfı
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ığı
};
// Noktanın hareket etmesini sağlayan fonksiyon
bool Nokta::git(int yeni_x, int yeni_y)
{
if( yeni_x > 0 && yeni_x < 500 && // yeni_x 0-500 aralığında ise
yeni_y > 0 && yeni_y < 300) // yeni_y 0-300 aralığında ise
{
x = yeni_x; // x koordinatına yeni değer atandı
y = yeni_y; // y koordinatına yeni değer atandı
return true; // atama yapıldı, hata yok
}
return false; // Giriş değerleri kabul edilmedi
}
Yeni yazılan git fonksiyonu geriye bir mantıksal değer döndürmektedir. Bu değer sayesinde fonksiyona gönderilen koordinatların kabul edilip edilmediği anlaşılmaktadır.

int main()
{
Nokta n1; // n1 nesnesi yaratıldı
int x,y; // Tuş takımından değer okumak için iki değişken tanımlandı
cout << "Noktanın gideceği koordinatları veriniz "
cin >> x >> y; // Tuş takımından iki değer okunuyor
if( n1.git(x,y) ) // git fonksiyonu çağırılıyor ve sonuç değerlendiriliyor
n1.goster(); // Atama yapıldıysa ekrana yeni koordinatlar çıkacak
else
cout << "\nGirilen değerler kabul edilmemiştir";
}
Erişim Etiketlerinin Geçerlilik Alanları:
Bir erişim etiketi yazıldığı yerden aşağıya doğru geçerli olur. Bu etiketin altında yer alan tüm veriler ve fonksiyonlar etikette belirtilen erişim türüne sahip olurlar. Bir etiketin geçerlilik alanı, başka bir etiket yazıldığında ya da sınıfın sonuna gelindiğinde sona erer.

C++'da struct Yapısı:
C++'da struct yapısı da class ile aynı işlevi yerine getirmekte ve sınıf bildirimleri için kullanılmaktadır. struct ile class arasındaki tek fark üyelerin erişim haklarında ortaya çıkmaktadır. class bildirimlerinde aksi bildirilmedikçe üyelerin erişim tipi özeldir (private)
struct bildirimlerinde ise aksi bildirilmedikçe üyelerin erişim tipi açıktır (public).

Metotların inline Fonksiyon (Makro) Olarak Tanımlanmaları:
Nokta sınıfının bildiriminde metotların (üye fonksiyonların) sadece prototipleri yer almaktadır. Fonksiyonların gövdeleri ise sınıfın dışında tanımlanmıştı. Metotların gövdeleri de sınıf bildiriminin içine yazılabilir. Bu durumda o fonksiyon makro (inline) olarak tanımlanmış olur İnline fonksiyonu sınıf bildiriminin içinde tanımlanmışsa inline anahtar kelimesi artık gerekli değildir.( Fakat kullanılmasında da bir sakınca yoktur.)

Aşağıdaki örnekte sifir_mi adlı üye fonksiyon sınıfın içine yazılarak makro
olarak tanımlanmıştır.
class Nokta{ // Nokta Sınıfı
int x,y; // Nitelikler: x ve y koordinatları
public:
void 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ığı (makro)
{
return (x == 0) && (y == 0);
}
};

buss10.gif

buss5.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.

DEVAMI SAYFA 2'DE >>

Kurucu Fonksiyonlar(Constructors)

Yazılan programların bazı elemanları için hazırlık yapılması gerekir. Nesnelerde bu duruma daha sık karşılaşılır. Hatta gerçek problemler düşünülürse, her nesne bu tür hazırlığa gereksinim duyar. C++ da bu gereksinim , sınıf bildirimi içerisine constructer fonksiyonlarının (kurucu fonksiyonların) konulmasıyla karşılanır. Bir sınıfın kurucu (constructer) fonksiyonları, global nesneler için bir kere program başladığında, yerel nesneler için ise, bildirim deyiminin her işletilişinde çağrılır, üyesi oldukları sınıftan bir nesne yaratılırken kendiliğinden canlanırlar. Bu tür fonksiyonlar bir nesnenin kurulması aşamasında yapılması gereken işleri, örneğin verilere uygun başlangıç değerleri atamak için kullanılırlar Böylelikle bir nesne için gerçekleştirilmesi gereken tüm hazırlıklar kurucu fonksiyonu tarafından otomatik olarak yapılırlar. Kurucu fonksiyonlar üyesi oldukları sınıf ile aynı ismi taşırlar, parametre alırlar, ancak geri dönüş değerleri yoktur. Geri dönüş tipi olarak herhangi bir tip (void bile) yazılmaz. Kurucu fonksiyonlar nesne yaratılırken sınıfın dışından sınıfın açık (public) üyeleri arasında yer almalıdırlar.
Bu fonksiyonlar işlevlerine ve yapılarına göre bazı alt gruplara ayrılırlar. Bunlar

Parametresiz Kurucu Fonksiyonlar ( Default Constructor)
Bu tür kurucu fonksiyonların ya parametre listeleri boştur, ya da tüm parametrelerin bir başlangıç değeri vardır. Ana programda kurucu fonksiyonların çağrılması için özel bir deyim yazılmaz. Nesnelerin yaratıldığı satırlarda kurucu fonksiyon her nesne için bir defa çalışır.

Örnek 1
#include <iostream>
using namespace std;
class myclass {
int a;
public: myclass(); // kurucu
void show();
};
myclass::myclass()
{
cout << "Kurucu fonksiyon çalışıyor\n";
a = 10;
}
void myclass::show()
{
cout << a;
}
int main()
{
myclass ob;
ob.show();
return 0;
}
Örnek2
// Paremtresiz Kurucu Fonksiyon

#include <iostream>
using namespace std;

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(); // Noktanın koordinatlarını ekrana çıkartır
};

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

// Parametresiz Kurucu Fonksiyon
Nokta::Nokta()
{
cout << "Kurucu fonksiyon calisiyor..." << endl;
x = 0; // Koordinatlar sıfırlanıyor.
y = 0;
}

// Noktanın hareket etmesini saglayan fonksiyon
bool 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 true;
}
return false;
}

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

// -------- Ana Program -------------
int main()
{
Nokta n1,n2; // Kurucu 2 kez çalışır
Nokta *pn = new Nokta; // Kurucu 1 kez çalışır.
n1.goster(); // n1'in koordinatları ekrana çıkartılıyor
n2.goster(); // n2'nin koordinatları ekrana çıkartılıyor
pn->goster(); // pn2'nin işaret ettiği nesnenin koord.ekr.çıkar.
return 0;
}
/*Kurucu fonksiyon calisiyor...
Kurucu fonksiyon calisiyor...
Kurucu fonksiyon calisiyor...
X= 0, Y= 0
X= 0, Y= 0
X= 0, Y= 0 */


Parmetre Alan Kurucu (Constructor) Fonksiyonlar
Kurucu fonksiyonlar da diğer üye fonksiyonlar gibi gerektiğinde parametre alacak şekilde tanımlanabilirler. Bu durumda sınıftan nesne yaratan programcılar, nesneleri tanımladıkları satırlarda kurucu fonksiyonlara uygun tipte ve sayıda argümanı vermek zorundadırlar.

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

Örnek 1
#include <iostream>
using namespace std;

class myclass {
int a;
public:
myclass(int x); // kurucu
void show();
};

myclass::myclass(int x)
{
cout << "kurucu fonksiyon çalışıyor\n";
a = x;
}
void myclass::show()
{
cout << a << "\n";
}

int main()
{
myclass ob(4);

ob.show();

return 0;
}

Gerçekte parametreli bir constructor&#8217;a argüman gönderme işlemi şu notasyonla yapılmalıdır.
myclass ob = myclass(4);
Fakat daha çok kısa şekil tercih edilir. Bu iki yazım şekli arsında kurucuların kopyalanmasıyla ilgili teknik açıdan ufak bir fark bulunmaktadır. Kuruculara birden fazla argüman göndermek mümkündür.
Örnek 2
#include <iostream>
using namespace std;

class myclass {
int a, b;
public:
myclass(int x, int y); // kurucu
void show();
};
myclass::myclass(int x, int y)
{
cout << " kurucu da\n";
a = x;
b = y;
}
void myclass::show()
{
cout << a << ' ' << b << "\n";
}
int main()
{
myclass ob(4, 7);
ob.show();
return 0;
}

Örnek 3

// Paremtreli Kurucu Fonksiyon
#include <iostream>
using namespace std;

class Nokta{ // Nokta Sınıfı
int x,y; // Nitelikler: x ve y koordinatları
public:
Nokta(int,int); // Kurucu fonksiyon bildirimi
bool git(int, int); // Noktanın hareket etmesini sağlayan fonksiyon
void goster(); // Noktanın koordinatlarını 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
bool 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 true;
}
return false;
}
// Noktanın koordinatlarını ekrana çıkaran fonksiyon
void Nokta::goster()
{
cout << "X= " << x << ", Y= " << y << endl;
}

// -------- Ana Program -------------
int main()
{
Nokta n1(20,100), n2(-10,45); // Kurucu 2 kez çalışır
Nokta *pn = new Nokta(10,50); // Kurucu 1 kez çalışır.
// Nokta n3; // HATA! Parametresiz kurucu yok
n1.goster(); // n1'in koordinatları ekrana çıkartılıyor
n2.goster(); // n2'nin koordinatları ekrana çıkartılıyor
pn->goster(); // pn2'nin işaret ettiği nesnenin koord.ekr.çık.
return 0;
}
/*Kurucu fonksiyon calisiyor...
Kurucu fonksiyon calisiyor...
Kurucu fonksiyon calisiyor...
X= 20, Y= 100
X= 0, Y= 45
X= 10, Y= 50 */


Kurucu Parametrelerine Başlangıç Değeri Verilmesi:
Diğer fonksiyonlarda olduğu gibi kurucu fonksiyonların parametrelerine de başlangıç değeri verilebilir. Bu durumda nesne yaratılırken verilmeyen argümanların yerine parametrelerin başlangıç değerleri kullanılacaktır.
// Kurucu Fonksiyon
Nokta::Nokta(int ilk_x = 0, int ilk_y = 0)
{
cout << "Kurucu fonksiyon çalışıyor..." << 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;
}
Bu sınıftan aşağıdaki gibi nesneler yaratılabilir.
Nokta n1(15,75); // x=15, y=75
Nokta n2(100); // x=100, y=0
Bu fonksiyon parametresiz bir kurucu olarak da kullanılabilir.
Nokta n3; // x=0, y=0

Kurucu Fonksiyonlarda İlk Değer Atama ( Constructor Initializers)
class C{ // Örnek C sınıfı
const int ci; // sabit üye veri
int x; // sabit olmayan üye veri
public:
C() { // Kurucu fonksiyon
x = -2; // Doğru, çünkü x sabit değil
ci = 0; // HATA! Çünkü ci sabit
}
};
Aşağıdaki gibi bir yazım da derleme hatasına neden olur.
class C{ // Örnek C sınıfı
const int ci = 10; // HATA! sabit üye veri
int x; // sabit olmayan üye veri
};
Kurucu fonksiyonlarda nesnelerin verilerine ilk değerlerini atamak için C++'nın atama deyiminden daha farklı bir yapı da kullanılabilmektedir. Özellikle sabit verilere ilk değerlerini atamak için bu yapının kullanılması zorunludur. Bir sınıfın kurucu fonksiyonunda sabit veriye başlangıç değeri atanmaya çalışılırsa derleme hatası oluşur, çünkü sabit bir veri bir atama işleminin solunda yer alamaz.

class C{ // Örnek C sınıfı
const int ci; // sabit üye veri
int x; // sabit olmayan üye veri
public:
C():ci(0) { // Kurucu fonksiyon. ci'ye sıfır atanıyor.
x = -2; // Doğru, çünkü x sabit değil
}
};

Tüm üye verilere değer atamak için bu yapı kullanılabilir. Eğer kurucu fonksiyonun değer atamaktan başka bir görevi yoksa gövdesi boş kalabilir.
class C{ // Örnek C sınıfı
const int ci; // sabit üye veri
int x; // sabit olmayan üye veri
public:
C():ci(0), x(-2) // Kurucu fonksiyon, ilk değerler atanıyor
{ } // Gövde boş
};
Bu problemin kurucu fonksiyonlarda ilk değer atama yapısı ( constructor initializer) kullanılarak çözülür. Kurucularda verilere ilk değer atamak için kurucu fonksiyonun imzasından sonra iki nokta üst üste (:) konur, ardından ilk değer atanacak verinin adı gelir ve parantez içinde atanacak değer yazılır.

Yok Edici Fonksiyonlar ( Destructors)
Bu fonksiyonlar üyesi oldukları sınıftan yaratılmış olan bir nesne bellekten kaldırılırken kendiliğinden çalışırlar. Bir nesnenin bellekten kaldırılması için ya nesnenin yaşam alanı sona ermelidir (tanımlandığı blok sona ermiştir) ya da dinamik bellekte tanımlanmış olan bir nesne delete operatörü ile bellekten silinmelidir. Yerel nesneler bulundukları alandan çıkılınca yok edilirken, global nesneler program sona erdiğinde yok edilirler. Bir kurucunun (constructor) veya yok edicinin(destructer) adresinin alınması imkansızdır. Yok edici fonksiyonlar da kurucular gibi sınıf ile aynı ismi taşırlar, ancak isimlerinin önünde 'tilda' simgesi (~) yer alır. Yok ediciler parametre almazlar ve geriye değer döndürmezler. Bir sınıfta sadece bir adet yok edici fonksiyon olabilir.

Örnek 1
#include <iostream>
using namespace std;

class myclass {
int a;
public:
myclass(); // kurucu
~myclass(); // yok edici
void show();
};

myclass::myclass()
{
cout << " kurucu da\n";
a = 10;
}
myclass::~myclass()
{
cout << "Yokediliyor...\n";
}

void myclass::show()
{
cout << a << "\n";
}

int main()
{
myclass ob;

ob.show();

return 0;
}

Örnek 2

C++ standart arşivinde katarları tanımlamak için string adında hazır bir sınıf vardır.
// Yok Edici Fonksiyonlar
// Karakter katarı (String)

#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
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 icin
strcpy(icerik, gelen_veri); //gelen veri icerik'in gos.yere kopy.
}

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

// Yok edici Fonksiyon
// icerik tarafından isaret edilen bellek geri veriliyor
String::~String()
{
cout<< "Yok edici calisti" << endl;
delete[] icerik;
}
//-------- Ana Program ----------------
int main()
{
cout << "--------- 1. Blok Basliyor ------" << endl;
String string1("katar 1");
String string2("katar 2");
{
cout << "--------- 2. Blok Basliyor ------" << endl;
string1.goster();
string2.goster();
String string3("katar 3");
cout << "--------- 2. Blok Bitiyor ------" << endl;
}
cout << "--------- 1. Blok Bitiyor ------" << endl;
return 0;
}
/*--------- 1. Blok Basliyor ------
Kurucu calisti
Kurucu calisti
--------- 2. Blok Basliyor ------
katar 1, 7
katar 2, 7
Kurucu calisti
--------- 2. Blok Bitiyor ------
Yok edici calisti
--------- 1. Blok Bitiyor ------
Yok edici calisti
Yok edici calisti */
11.10.2004
Nesnelerin Dizi Olarak Yaratılması
Nesneler de , diğer tüm değişkenlerle aynı özellikleri ve yetenekleri olan bir tür değişkendir. Bu nedenle nesneler de diziler halinde kullanılabilirler. Bir dizi nesnenin bildirim şekli, herhangi bir değişken dizinin bildirim şekli gibidir. Nesne dizilerine erişim şekli , diğer tipteki değişken dizilerininki ile tamamen aynıdır.
Örnek 1:

#include <iostream>
using namespace std;
class samp {
int a;
public:
void set_a(int n) { a = n; }
int get_a() { return a; }
};
int main()
{
samp ob[4];
int i;
for(i=0; i<4; i++) ob[i].set_a(i);
for(i=0; i<4; i++) cout << ob[i].get_a( );
cout << "\n";
return 0;
}
/*
0123 */

Örnek 2:
İki boyutlu dizi için örnek
// İki boyutlu dizi için yer ayır.
#include <iostream>
using namespace std;

class samp {
int a;
public:
samp(int n) { a = n; }
int get_a() { return a; }
};
int main()
{
samp ob[4][2] = {
1, 2,
3, 4,
5, 6,
7, 8
};
int i;
for(i=0; i<4; i++) {
cout << ob[i][0].get_a() << ' ';
cout << ob[i][1].get_a() << "\n";
}
cout << "\n";
return 0;
}
Bu program aşağıdaki sonuçları gösterir.
1 2
3 4
5 6
7 8

Örnek 3:
Aşağıdaki örnekte Nokta sınıfından, adı dizi olan 10 elemanlı bir nesne dizisi yaratılmış ve kullanılmıştır.
int main()
{
Nokta dizi[10]; // 10 elemanlı dizi yaratılıyor.
dizi[0].git(15,40); // dizinin ilk elemanına (0 indisli) git mesajı
dizi[1].git(75,35); // dizinin ikinci elemanına (1 indisli) git mesajı
: // Diğer elemanlar da konumlandırılır
for (int i= 0; i < 10; i++) // dizideki tüm nesneler ekrana çıkartılıyor
dizi[i].goster();
return 0;
}

Nesnelere Gösterici Kullanmak
Nesnelere göstericiler vasıtasıyla da erişilebilir. Gösterici kullanıldığında, nesnenin üyelerine referanslar(.) ile değil (->) operatörü ile gerçekleştirilir. Diğer veri tipleri için geçerli olan gösterici işlemleri nesne göstericileri için de geçerlidir. Bu nesnenin tipine bağlı olarak gerçekleştirilir. Örneğin bir nesne göstericisi bir arttırıldığında bir sonraki nesneyi gösterir. Yine bir nesne göstericisi bir eksiltildiğinde bir önceki nesneyi gösterir.

// Nesnelere Gösterici kullanmak.
#include <iostream>
using namespace std;

class samp {
int a, b;
public:
samp(int n, int m) { a = n; b = m; }
int get_a() { return a; }
int get_b() { return b; }
};

int main()
{
samp ob[4] = {
samp(1, 2),
samp(3, 4),
samp(5, 6),
samp(7, 8)
};
int i;

samp *p;
p = ob; // dizinin başlangıç adresini al

for(i=0; i<4; i++) {
cout << p->get_a() << ' ';
cout << p->get_b() << "\n";
p++; // bir sonraki nesnenin adresini al
}

cout << "\n";

return 0;
}
// 0123
Çıkıştan görüldüğü gibi p her arttırıldığında dizideki bir sonraki nesneyi gösteriyor.

Nesnelerin Dinamik Olarak Yaratılması:
Derleyicinin hazır veri tipleri (int, float, char vs.) veri tanımlamak için nasıl kullanılıyorsa sınıflar da nesne tanımlamak için aynı şekilde kullanılabilir. Örneğin nesnelere işaret edebilen göstericiler tanımlanabilir ve bu göstericiler için dinamik bellekten yer alınabilir ve daha önce alınmış olan yerler geri verilebilir.
Örnek 1
Nesneler için bir örnek.
// Nesnelere dinamik olarak yer ayrılması.
#include <iostream>
using namespace std;

class samp {
int i, j;
public:
void set_ij(int a, int b) { i=a; j=b; }
int get_product() { return i*j; }
};

int main()
{
samp *p;
p = new samp; // nesneye yer ayır
if(!p) {
cout << "Yer ayırma hatası\n";
return 1;
}

p->set_ij(4, 5);

cout << "Product is: " << p->get_product() << "\n";

return 0;
}
// Product is: 20
Aşağıdaki programda nesneleri gösteren iki gösterici (pn1 ve pn2) tanımlanmakta, dinamik bellekte bu göstericiler tarafından gösterilen nesneler yaratılmaktadır.
Örnek 2
int main()
{
Nokta*pn1 = new Nokta; //pn1'in gösterdiği nesne için bellekten yer alındı
Nokta*pn2 = new Nokta; //pn2'nin gösterdiği nesne için bellekten yer alındı
pn1->git(50,50); //pn1'in gösterdiği nesneye git mesajı
pn1->goster(); //pn1'in gösterdiği nesneye goster mesajı
pn2->git(100,150); //pn2'nin gösterdiği nesneye git mesajı
if(pn2->sifir_mi()) // pn2'nin gösterdiği nesne sıfır da mı?
cout << "pn2'nin nesnesi şu anda sıfır noktasındadır." << endl;
else
cout << "pn2'nin nesnesi şu anda sıfır noktasında değildir." << endl;
delete pn1; // Dinamik bellekten alınan yerler geri veriliyor
delete pn2;
return 0;
}








this Göstericisi
Bir sınıftan yaratılan her nesnenin sadece verileri için bellekte ayrı yerler ayrılır. Sınıfın fonksiyonları ise bir kere derlenir ve tüm nesneler aynı fonksiyonları paylaşırlar.
Örnek Nokta sınıfından n1 ve n2 nesneleri yaratıldığında bu nesneler bellekte verileri kadar yer kaplarlar. Nokta sınıfında iki adet tamsayı veri (x ve y) bulunduğundan bu sınıftan tanımlanan nesneler de sadece iki tamsayı içerirler.



Fonksiyonlar tüm nesneler tarafından ortak olarak kullanıldığında göre bir fonksiyon çağırıldığında hangi nesnedeki veriye erişeceği nasıl belirlenmektedir? Aynı fonksiyonun farklı nesneler üzerinde çalışmasını sağlamak için C++ derleyicileri tarafından her programda this adında bir gösterici tanımlanmaktadır. Bir sınıfın üye fonksiyonu hangi nesne için çağırılırsa this göstericisine o nesnenin adresi atanır. Örneğin n1.goster(); çağrısı yapıldığında this, n1'in adresini taşır, n2.goster(); çağrısı yapıldığında ise n2'nin adresini taşır.
Programcılar da gerek duyduklarında this göstericisini programlarında kullanabilirler.
Aşağıda Nokta sınıfına uzak adında bir fonksiyon eklenmiştir. Bu fonksiyon parametre olarak Nokta tipinden başka bir nesne almakta ve hangi nesnenin (noktanın) sıfır noktasına (0,0) daha uzak olduğunu hesaplamaktadır. Daha uzak olan nesnenin adresi geriye gönderilmektedir.

// Örnek this göstericisinin kullanımı
// Nokta sınıfına uzak fonksiyonu eklenmiştir.
#include <iostream>
using namespace std;

class Nokta{ // Nokta Sınıfı
int x,y; // Nitelikler: x ve y koordinatları
public:
bool git(int, int); // Noktanın hareket etmesini saglayan fonksiyon
void goster(); // Noktanın koordinatlarını ekrana çıkartır
bool sifir_mi(); // Noktanın (0,0) koordinatlarinda olup olmadığı
Nokta *uzak(Nokta &); // Hangi nokta (0,0)'a daha uzak?
};
// ***** Üye Fonksiyonların Gövdeleri *****
// Noktanın hareket etmesini saglayan fonksiyon
bool Nokta::git(int yeni_x, int yeni_y)
{
if( yeni_x > 0 && yeni_x < 500 && // x_yeni 0-500 araliginda ise
yeni_y > 0 && yeni_y < 300) // y_yeni 0-300 araliginda ise
{
x = yeni_x; // x koordinatina yeni deger atandi
y = yeni_y; // y koordinatina yeni deger atandi
return true; // atama yapildi, hata yok
}
return false; // Giris degerleri kabul edilmedi
}
// Noktanın koordinatlarını ekrana ekrana çıkaran fonksiyon
void Nokta::goster()
{
cout << "X= " << x << ", Y= " << y << endl;
}
// Noktanın (0,0) koordinatlarinda olup olmadığını belirten fonksiyon
bool Nokta::sifir_mi()
{
return (x == 0) && (y == 0);
}
// Hangi noktanın (0,0)'a daha uzak olduğunu bulan fonksiyon
Nokta *Nokta::uzak(Nokta &n)
{
unsigned long x1 = x*x;
unsigned long y1 = y*y;
unsigned long x2 = n.x * n.x;
unsigned long y2 = n.y * n.y;
if ( (x1+y1) > (x2+y2) ) return this; // Kendi nesnesinin adresi
else return &n; // Parametre olarak gelen nesne
}

int main()
{
Nokta n1,n2; // 2 adet nesne tanımlandı: n1 ve n2
n1.git(100,50); // n1 (100,50)'ye gönderiliyor
n2.git(20,65); // n1 (20,65)'e gönderiliyor
Nokta *np;
np=n1.uzak(n2);
np->goster(); // (0,0)'a uzak olan ekrana çıkartılıyor
return 0;
}
// sonuc
/*X= 100, Y= 50 */

Kurucu (Constructor) Fonksiyonların Aşırı Yüklenmesi
Bir sınıfın kurucu fonksiyonunu aşırı yüklenebilir. İsimleri aynı olan bu fonksiyonların bir belirsizlik olmadan çağrılabilmeleri için parametrelerinin tipleri ve/veya sayıları farklı olmalıdır. Bir kurucuyu aşırı yüklemenin üç ana nedeni vardır: esneklik kazanmak, dizileri desteklemek, ve kopya kurucuları yaratmak. Sınıfların tüm oluşturuluş şekilleri için ayrı kurucu (constructor) fonksiyonları bulunması gerekir. Eğer karşılık gelen hiçbir kurucu fonk siyonu yokken program, nesne yaratmaya çalışırsa deneme sırasında hata çıkar.
(Ancak yok edici aşırı yüklenemez.)
Nokta::Nokta() // Parametresiz kurucu fonksiyon
{
............... // Gövdesi önemli değil
}
Nokta::Nokta(int ilk_x, int ilk_y) // Parametreli kurucu fonksiyon
{
................. // Gövdesi önemli değil
}
İki kurucuya sahip bir Nokta sınıfından farklı şekillerde nesneler yaratılabilir.
Nokta n1; // Parametresiz kurucu çalışır
Nokta n2(30,10); // Parametreli kurucu çalışır
Örnek sınıfta tek parametreli bir kurucu olmadığından aşağıdaki satır hatalıdır.
Nokta n3(10); // HATA! Bir parametreli kurucu yok

Örnekler
1. Aşırı yüklenmiş kurucuların en sık kullanımı belki de bir nesneyi hazırlayıp, hazırlamama seçeneğini sağlamak içindir. Örneğin aşağıdaki programda o1&#8217;e bir başlangıç değeri verilmiş, fakat o2&#8217;ye verilmemiştir. Eğer argüman listesi boş olan kurucuyu kaldırırsak program derlenemeyecektir. Çünkü samp tipinden hazırlanmamış nesneye uygun bir kurucu yoktur. Bunun tersi de doğrudur: eğer parametrelendirilmiş kurucuyu kaldırırsanız, hazırlanmış nesne ile bağlantı kurulamayacağından, program yine derlenemeyecektir. Programın doğru şekilde derlenebilmesi için her ikisi de gereklidir.
#include <iostream>
using namespace std;

class myclass {
int x;
public:
// iki yolla kurucuyu aşırı yükle
myclass() { x = 0; } // hazırlayıcı yok
myclass(int n) { x = n; } // var
int getx() { return x; }
};

int main()
{
myclass o1(10); // ilk değerle bildir
myclass o2; // hazırlayıcı olmadan bildir.

cout << "o1: " << o1.getx() << '\n';
cout << "o2: " << o2.getx() << '\n';

return 0;
}
/*
o1: 10
o2: 0 */
2. Kurucu fonksiyonların aşırı yüklenmesinin gerekliliğine bir diğer sebep de, hem tek tek nesnelerin hem de nesne dizilerinin, bir program içerisinde ortaya çıkmasına olanak sağlamaktır. Belki tecrübelerinizden biliyorsunuz, tek bir değişkeni hazırlamak alışılmış olup, bir diziyi hazırlamak pek yaygın değildir. (Genellikle, dizilere program işletilirken bilinen değerler atanır.) Böylelikle, hazırlanmış nesne dizileriyle hazırlanmamış nesne dizilerine ortam sağlamak için , hazırlamayı gerçekleştiren ve de gerçekleştirmeyen birer kurucuyu dahil etmeliyiz.
Mesela, Örnek l&#8217;deki myclass sınıfını düşünelim, bu bildirimlerin her ikisi de geçerlidir.
myclass ob(10);
myclass ob[5];

Hem parametrelendirilmiş hem de parametresiz kurucuları kullanarak, programınız isteğe bağlı olarak hazırlanmış veya hazırlanmamış nesnelerin yaratılmasına imkan sağlar. Tahmin edeceğiniz gibi, bir kere parametrelendirilmiş veya parametresiz kurucular tanımlanırsa, bunları hazırlanmış yada hazırlanmamış dizileri yaratmakta kullanabilirsiniz.
Örnek vermek gerekirse, aşağıdaki program biri hazırlanmış diğeri hazırlanmamış myclass tipinde iki diziyi belirtmektedir.
#include <iostream>
using namespace std;

class myclass {
int x;
public:
// iki yolla kurucuyu aşırı yükle
myclass() { x = 0; } // hazırlayıcı yok
myclass(int n) { x = n; } // var
int getx() { return x; }
};

int main()
{
myclass o1[10]; //diziyi ilk değerler olmadan bildir

// ilk değerlerle bildir
myclass o2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

int i;

for(i=0; i<10; i++) {
cout << "o1[" << i << "]: " << o1[i].getx();
cout << '\n';
cout << "o2[" << i << "]: " << o2[i].getx();
cout << '\n';
}

return 0;
}
/*
o1[0]: 0
o2[0]: 1
o1[1]: 0
o2[1]: 2
o1[2]: 0
o2[2]: 3
o1[3]: 0
o2[3]: 4
o1[4]: 0
o2[4]: 5
o1[5]: 0
o2[5]: 6
o1[6]: 0
o2[6]: 7
o1[7]: 0
o2[7]: 8
o1[8]: 0
o2[8]: 9
o1[9]: 0
o2[9]: 10 */
Bu örnekte o1in bütün elemanları kurucu fonksiyonu tarafından 0&#8217;a ayarlanmıştır. o2&#8217;nin elemanları programda gösterildiği gibi hazırlanmıştır.
3. Kurucu fonksiyonlarının aşırı yüklenmesinin bir başka sebebi programcıya bir nesneyi başlangıç durumuna getirmekte en uygun metodu seçebilmesine olanak sağlamak içindir. Nasıl olduğunu anlamak için öncelikli olarak takvim tarihini tutabilen bir sınıf yaratan sonraki örneği incelemeniz faydalı olacaktır. date( ) kurucusını iki şekilde aşırı yükler. Bir formda tarihi bir karakter dizisi şeklinde kabul eder. Diğer formda tarih üç tam sayı şeklinde gönderilir.
#include <iostream>
#include <cstdio> //sscanf() için dahil edildi
using namespace std;

class date {
int day, month, year;
public:
date(char *str);
date (int m, int d, int y) {
day = d;
month = m;
year = y;
}
void show() {
cout << month << '/' << day << '/';
cout << year << '\n';
}
};

date::date(char *str)
{
sscanf(str, "%d%*c%d%*c%d", &month, &day, &year);
}

int main()
{
// katar kullanarak date nesnesini yapılandır
date sdate("12/31/99");

// tamsayılar kullanarak date nesnesini yapılandır
date idate(12, 31, 99);

sdate.show();
idate.show();
return 0;
}
/* 12/31/99
12/31/99 */
Bu programda da gösterildiği gibi date( ) kurucusunın aşırı yüklenmesinin avantajı, hangi seçenek durumumuza en uygun ise onu seçebilme ve kullanma rahatlığı sağlamasıdır. Örneğin eğer bir date nesnesi kullanıcı tarafından girilen değere göre yaratılıyorsa, programın katarlı sürümü, kullanılabilecek en kolay yoldur. Yine de, eğer nesne birtakım içsel hesaplamalar sonrasında yapılandırılıyorsa, üç tamsayı parametreli sürümü daha etkili olabilir.
Her ne kadar bir kurucuyu istenildiği kadar aşırı yüklemek mümkün olsa da, bu işlemi gereğinden çok yapmak sınıf üzerinde yıkıcı bir etki yapar. Teknik olarak, en iyisi bir kurucuyu, sıklıkla meydana gelebilecek durumlar için aşırı yüklemektir. Örneğin, date( )&#8216;in bir üçüncü kez aşırı yüklenmesi, tarihin milisaniyeler mertebesinde girilebilmesi gibi ufak bir etki yapar. Ancak, time_t tipinde (sistemin tarihini ve zamanını tutan bir tip ) bir nesnenin kabulü için aşırı yüklemek çok değerli olabilir. (Pekiştirme Testi&#8217;ne bakınız, örnek olarak bu tip alıştırmalara göz atınız.)
4. Bir sınıfın kurucu fonksiyonunun aşırı yüklenmesini gerektirecek bir durum daha vardır ki bu durum o sınıftan bir dinamik dizi için bellekte yer ayrılmasıdır. İlerdeki bölümlerde de, göreceğimiz gibi dinamik bir dizi hazırlanamaz. Böylelikle eğer sınıf hazırlayıcı alan bir kurucuyu içeriyorsa, hazırlayıcı almayan aşırı yüklenmiş sürümü eklemelisiniz. Örneğin şimdi dinamik olarak bir nesne dizisi için yer ayıran bir program yazalım.
#include <iostream>
using namespace std;

class myclass {
int x;
public:
// iki yolla kurucuyu aşırı yukle
myclass() { x = 0; } // hazırlayıcı yok
myclass(int n) { x = n; } // var
int getx() { return x; }
void setx(int n) { x = n; }
};

int main()
{
myclass *p;
myclass ob(10); // tek değişkeni hazırla

p = new myclass[10]; // burada hazırlayıcıları kullanılamaz
if(!p) {
cout << "Allocation error\n";
return 1;
}

int i;

// bütün eıemanları ob&#8217;a ayarla
for(i=0; i<10; i++) p[i] = ob;

for(i=0; i<10; i++) {
cout << "p[" << i << "]: " << p[i].getx();
cout << '\n';
}

return 0;
}
/*
p[0]: 10
p[1]: 10
p[2]: 10
p[3]: 10
p[4]: 10
p[5]: 10
p[6]: 10
p[7]: 10
p[8]: 10
p[9]: 10
*/
Hazırlayıcısı olmayan, myclass( )&#8217;ın aşırı yüklenmiş sürümü olmadıkça, new, bir derleme aşamasında hatası doğuracaktır ve program derlenmeyecektir.
Kurucuların Nesne Dizileri ile Kullanılması
Bir sınıfta parametresiz bir kurucu fonksiyon varsa bu sınıftan bir nesne dizisi yaratıldığında dizinin her elemanı için kurucu fonksiyon kendiliğinden canlanır.
Nokta dizi[10]; // Parametresiz kurucu 10 defa çalışır
Eğer bir parametre alan kurucu fonksiyona sahip bir sınıftan nesne dizisi yaratılacaksa, kurucuya gerekli argümanları göndermek için başlangıç değerleri listesi kullanılır. Bu listenin başı ve sonu kıvırcık parantezler ({ }) ile belirlenir.
Ayrıca kurucuya gönderilecek her değer de kıvırcık parantezler içine yazılır.
// Kurucu Fonksiyon
Nokta::Nokta(int ilk_x, int ilk_y = 0)
Nokta dizi[]= { {10} , {20} , Nokta(30,40) }; // 3 elemanlı nesne dizisi
Eğer Nokta sınıfında yukarıdaki kurucu fonksiyona ek olarak parametresiz bir kurucu fonksiyon da yer alsaydı aşağıda gösterildiği gibi 5 elemanlı bir dizi yaratılabilirdi.
Nokta dizi[5]= { {10} , {20} , Nokta(30,40) }; // 5 elemanlı nesne dizisi
Yukarıda başlangıç değerleri listesine sadece üç değer vardır. Bu durumda dizinin ilk üç elemanına listedeki değerler sırasıyla gönderilecektir. Son iki eleman için ise parametresiz kurucu fonksiyon çalıştırılacaktır.

Kopyalanma kurucusu ( Copy Constructor)
Kopyalanma kurucusunun en genel formu aşağıda gösterilmiştir.
classname (const classname &obj){
//kurucunın içi
}
Burada obj, bir nesnenin hazırlamak için kullanılan nesneye referanstır.

Kopyalanma kurucusu özel bir kurucu fonksiyondur. Diğer kurucu fonksiyonlar gibi bir nesne yaratılırken kendiliğinden canlanırlar. Var olan bir nesnedeki verileri yeni yaratılan nesnenin içine kopyalarlar. Böylece yeni yaratılan nesne var olan eski bir nesnenin kopyası olur. Bu fonksiyonlar giriş parametresi olarak aynı sınıftan bir nesneye referans alırlar. Bu kopyası çıkarılacak olan nesneye bir referanstır.Eğer programcılar sınıflarının içine bir kopyalanma kurucusu koymazlarsa, derleyici standart bir kurucuyu sınıfa yerleştiriri. Standart kopyalanma kurucusu bir nesnenin elemanlarını bire bir yeni nesnenin veri alanlarına kopyalar. İçinde gösterici olmayan nesneler için bu genellikle yeterlidir. Örneğin bir önceki örnekteki String sınıfı için derleyicinin yerleştireceği kopyalanma kurucusu aşağıdaki işlemleri yapacaktır.


String sınıfı için verilen örnekte de görüldüğü gibi derleyicinin sağladığı kopyalama fonksiyonu sadece nesnenin elemanlarını kopyalamaktadır. İşaretçilerin işaret ettiği veriler kopyalanamaz. Bu alanların da kopyalanması isteniyorsa programcı kendi kopyalama fonksiyonunu yazmalıdır.

// Kopyalama Kurucusu (Copy Constructor)

#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
String(const String &); // Kopyalama kurucusu
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 çalıştı" << 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 çalıştı" << endl;
boy = gelen_nesne.boy;
icerik = new char[boy + 1]; // +1 null karakteri icin
strcpy(icerik, gelen_nesne.icerik);
}

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 çalıştı" << endl;
delete[] icerik;
}
//-------- Ana Program ----------------
int main() // Ana fonksiyon
{
String string1("Katar 1");
string1.goster();
String diger = string1; // Kopyalanma kurucusu çalisir
String baska(string1); // Kopyalanma kurucusu çalisir
diger.goster();
baska.goster();
return 0;
}
/*
Kurucu calisti
Katar 1, 7
Kopyalama Kurucusu calisti
Kopyalama Kurucusu calisti
Katar 1, 7
Katar 1, 7
Yok edici calisti
Yok edici calisti
Yok edici calisti */

zeyunal@hotmail.com

Bana ulaşmak için yukarıdaki e-mail adresini kullanın