Neler yeni

Yeni mesajlar Yeni konular En çok mesaj En çok tepki En çok görüntülenen

C++ Hakkında Bilgiler

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
C++ NASIL BİR PROGRAMLAMA DİLİDİR?

C++ nesne yönelimli programlama tekniğinin uygulanabilmesi için C'nin genişletilmiş bir biçimidir. Nesne yönelimli programlama(object oriented programming) tekniği ve C++ B.Stroustroup tarafından geliştirilmiştir. Tasarım 70'li yılların ikinci yarısından başlanmış olsa da bütün dünyada yaygınlaşması ve kabul görmesi 80'li yılların sonlarına doğru mümküm olmuştur. Nesne yönelimli programlama tekniği(NYP) özellikle büyük kodların üstesinden gelebilmek amacıyla tasarlanmıştır. Tasarımı C++ üzerinde yapılmış olmasına karşın bugün pek çok yüksek seviyeli programlama dilleri bu tekniği desteklemektedir. C++ ve nesne yönelimli programlama tekniğinin en belirgin uygulama alanlarından birisi WINDOWS altında programlamadır. WINDOWS karmaşık ve yüksek yüksek seviyeli bir işletim sistemidir. WINDOWS altında program geliştirebilmek için uzun kodlar yazmak gerekir. Bu nedenle WINDOWS altında C ile değil C++ ile ve NYP tekniğini kullanarak program yazmak daha etkin bir çözümdür. NYP tekniğinin uygulanabilmesi için çalıştığımız sistemin kaynaklarının yeterince geniş olması gerekir. (Yani hızlı bir mikro işlemci, büyük RAM ve DISK ve iyi bir işletim sistemi)

C++'IN C'DEN FARKLILIKLARI

NYPT İLE DOĞRUDAN SINIF YAPISI
İLİŞKİSİ OLMAYAN
FARLILIKLARI VE FAZLALIKLARI

İki düzeyde değerlendirilebilir.
1-)NYPT ile doğrudan ilişkisi olayan farkılılıklar ve fazlalıklar
2-)Sınıf yapısı

Sınıf(class) C'deki yapı(struct)'lara benzer bir veri yapısıdır. NYPT sınıflar kullanılarak program yazılması tekniğidir. Kursun %80'i sınıf yapısının yapısı ve kullanılması üzerine ayrılmıştır.

C++'IN NYPT İLE DOĞRUDAN İLİŞKİSİ OLMAYAN FARLILIKLARI VE FAZLALIKLARI

C++ derleyicileri C derleyicisini de içermek zorundadır. Yani C++ derleyicisi demek hem C hem de C++ derleyicisi demektir. Derleyici dosyanın uzantısına bakarak kodun C'de mi yoksa C++'ta mı yazılmış olduğuna karar verir. C'de ise uzantısı c, C++'ta yazılmışsa uzantısı cpp'dir.

1-)C++'ta yerel değişkenlerin bildirimleri blokların başında yapılmak zorunda değildir. Standart C'de yerel değişkenler blokların başında bildirilmek zorundadır. Yani küme parantezi açıldıktan sonra daha hiçbir fonksiyon çağırılmadan ve işlem yapılmadan yapılmalıdır. Bu tasarımın nedeni programcının bildirimin yerini kolay bulabilmesini sağlamaya yöneliktir. Oysa C++'ta terel değişklenler bloğun herhangi bir yerinde bildirilebilir. Bir değişkenin kullanıma yakın bir bölgede bildirilmesi C++ tasarımcılarına göre daha okunabilirdir. (Değişken kavramı nesne isimlerini, struct, union ve enum isimlerini ve enum sabitlerini, typedef isimlerini içeren genel bir terimdir.) O halde C++'ta yerel değişkenin faaliyet alanı bildirim noktasından blok sonuna kadar olan bölgeyi kapsar. Ne olursa olsun bir blok içerisinde aynı isimli birden fazla değişken bildirimi yapılamaz.

C++'da for döngüsünün birinci kısmında bildirim yapılabilir. Örnek olarak:

for(int i = 0,j = 20; i + j < 50; ...){ }

Tabii while döngüsünün ve if deyiminin içerisinde bildirim yapılamaz.

#include <stdio.h>
#define SIZE 100

void main(void)
{
for(int i = 0; i < SIZE; ++i)
printf("%d\n", i);
}

Böyle for döngüsünün içerisinde bildirilmiş değişkenlerin faaliyet alanları bildirildiği yerden for döngüsünün içinde bulunduğu bloğun sonuna kadar etkilidir. if, for, switch, while gibi deyimlerden sonra blok açılmamış olsa bile gizli bir bloğun açıldığı düşünülmelidir.

{
for (int i = 0; i < 100; ++i) {
for (int j = 0; j < 100; ++j) {
}
printf(%d\n", j); /*geçerli*/
}
printf("%d\n" ,i); /*geçerli*/
printf("%d\n", j); /*geçersiz*/
}

{
for (int i = 0; i < 100; ++i)
for (int j = 0; j < 100; ++j) {
}
j = 10; /*geçersiz*/
i = 10; /*geçerli*/
}

2-)C++'ta // ile satır sonuna kadar yorumlama yapılabilir.
C++'ta /* */ yorumlama biçiminin yanı sıra kolaylık olsun diye // ile satır sonuna kadar yorumlama biçimi de eklenmiştir. Son senelerde böyle bir yorumlama biçimi standart C'de de kullanılmaya başlanmıştır. Ancak ANSI C standartlarında tanımlı değildir. Taşınabilirlik bakımından bu yorumlama biçimini standart C'de kullanmak tavsiye edilmez.
3-)C++'ta çağırılan fonksiyon eğer çağıran fonksiyonun yukarısında tanımlanmamışsa fonksiyon prototipi zorunludur.
C 'de bir fonksiyonun çağırıldığını gören derleyici fonksiyonun çağırılma noktasına kadar fonksiyonun tanımlamasıyla ya da prototipi ile karşılaşmamışsa geri dönüş değerini int olarak varsayar ve kod üretir. Dolayısıyla aşağıdaki örnek C'de geçerlidir.



void main(void)
{
int x;

x = fonk();
}

int fonk() /*Bu durum C'de sorun olmaz ama C++'ta error verir.*/
{

}

Oysa C++'ta derleyicinin çağırılma noktasına kadar fonksiyonun tanımlamasıyla ya da prototipiyle karşılaşması gerekir. Dolayısıyla yukarıdaki kod C++'ta error'dür. (NOT: CV++ ve nesne yönelimli programlama tekniği bug oluşturabilecek kodlardan kaçınılması temeline dayandırılmıştır. Yani garanti yöntemler kullanılmalıdır. Bu sebeple C'deki pek çok uyarı C++'ta error'e dönüştürülmüştür.)
4-)C++'ta farklı parametre yapılarına sahip aynı isimli birden fazla fonksiyon tanımlanabilir.

void fonk(void)
{
}

void fonk(int x)
{
}

C'de ne olursa olsun aynı isimli birden fazla fonksiyon tanımlanamaz. Oysa C++'ta parametre yapısı sayıca ve/veya türce farklı olan aynı isimli birden fazla fonksiyon tanımlanabilir. Aynı isimli birden fazla fonksiyon varsa ve o fonksiyon çağırılmışsa gerçekte hangi fonksiyon çağırılmış olduğu çağırılma ifadesindeki parametre yapısı incelenerek belirlenir. Yani çağırılma ifadesindeki parametre sayısı ve türü hangisine uygunsa o çağırılmış olur. Geri dönüş değerinin farklı olması aynı isimli fonksiyon yazmak için yeterli değildir. Yani geri dönüş değerleri farklı fakat parametre yapısı aynı olan birden fazla fonksiyon tanımlanamaz.

#include <stdio.h>

void fonk(int x)
{
printf("int = %d\n", x);
}

void fonk(long x)
{
printf("long = %ld\n", x);
}

void fonk(void)
{
printf("void\n");
}

void fonk(char *str)
{
puts(str);
}

void main(void)
{
fonk(); /*parametresi void olan fonksiyonu çağırır*/
fonk(10); /*parametresi int olan fonksiyonu çağırır*/
fonk(100L); /*parametresi long olan fonksiyonu çağırır*/
fonk("merhaba"); /*parametresi karakter türünden gösterici olan fonksiyonu çağırır*/

İki anlamlılık hatası

C++'ta pek çok durumda derleyicinin birden çok seçenek arasında karar verememesinden dolayı error durumuyla karşılaşılır. Bu tür hatalara iki anlamlılık hataları denir. Yukarıdaki örnekte fonk(3.2); gibi bir çağırma yapılırsa "Ambiguity between 'fonk(int)' and 'fonk(long)'" hatasını verir. Aynı isimli birden fazla fonksiyon arasında seçme işlemi ancak parametre sayıları çağılma ifadesine uygun birden fazla fonksiyon varsa gerçekleşir. Parametre sayısı çağırılma ifadesine uygun tek bir fonksiyon varsa bu durumda tür uyuşmasına bakılmaz. C'de olduğu gibi otomatik tür dönüştürmesi yapılarak o fonksiyon çağırılır.
C++ derleyicisi aynı sayıda parametrelere sahip birden fazla aynı isimli fonksiyonun bulunması durumunda çağırılma ifadesine tür bakımından uygun bir fonksiyon bulamazsa bu durum iki anlamlılık hatasına yol açar. Bu durumun 3 istisnası vardır:

1. Fonksiyon char ya da short parametreyle çağırılmışsa char ya da short int parametreye sahip bir fonksiyon yok ancak int parametreye sahip bir fonksiyon varsa int parametreye sahip olan fonksiyon çağırılır.
2. Fonksiyon float parametreyle çağırılmışsa ancak float parametreye sahip bir fonksiyon yok double parametreye sahip bir fonksiyon tanımlanmışsa bu durumda double parametreye sahip olan fonksiyon çağırılır.
3. Fonksiyon aynı türden const olmayan bir ifadeyle çağırılmışsa ancak aynı türden const parametreye sahip bir fonksiyon y,tanımlanmışsa tür uyuşumunun sağlandığı kabul edilir ve const parametreye sahip olan fonksiyon çağırılır.

C'de ve C++'ta tanımlanan ve çağırılan bir fonksiyon ismi .obj modül içerisine yazılmak zorundadır. .obj modül standardına göre aynı isimli birden çok fonksiyon modül içerisine yazılamaz. Standart C derleyicileri fonksiyon isimlerinin başına bir _ ekleyerek obj modülün içerisine yazarlar. Oysa C++ derleyicileri fonksiyon isimlerini parametre türleriyle kombine ederek obj modül içerisine yazarlar. Bu durumda C++'ta aynı isimli farklı parametrelere sahip fonksiyonlar sanki farklı isimlere sahiplermiş gibi obj modüle yazılırlar.

5-)extern "C" ve extern "C++" bildirimleri
C++'ta normal olarak bütün standart C fonksiyonları çağırılabilir. Standart C fonksiyonları lib dosyalarının içerisine başında "_" bulunarak yani standart C kurallarıyla yazılmışlardır. Oysa bu fonksiyonların C++'tan çağırılmasıyla bir uyumsuzluk ortaya çıkar. Çünkü C++ derleyicisi çağırılan fonksiyonu obj modül içerisine başına "_" koyarak değil parametre türleriyle kombine ederek yani C++ kurallarıyla yazar. extern "C" bildirimi bir fonksiyonun prototipinin önüne ya da bir fonksiyonun tanımlamasının önüne getirilirse /*örneğin:

extern "C" double sqrt(double);

veya

extern "C" void fonk(void)
{
.........
}
*/
derleyici bu fonksiyonu obj modül içerisine C kurallarıyla yani başına "_" koyarak yazar. Böylece C'de yazılmış olan C++'tan kullanılması mümkün olur. Bir grup fonksiyon yazım kolaylığı sağlamak için extern "C" bloğu içine alınabilir.

extern "C" {
void fonk(void);
void sample(void);
....
}

Bloğun içerisinde başka bildirimler ve kodlar bulunabilir. Ancak derleyici yalnızca bu bloğun içerisindeki fonksiyonlarla ilgilenir. Bu durumda standart C başlık dosyalarının içerisinde fonksiyonların extern "C" bildirimiyle prototipleri yazılmış olması gerekir. Aynı dosya hem C hem C++'ta include edilip kullanılabildiğine göre ve extern "C" bildirimi sadece C++ için geçerliyse bir problem ortaya çıkmaz mı? Bu problem önceden tanımlanmış cplusplus sembolik sabitiyle çözümlenmiştir:

#ifdef cplusplus
extern "C" {
#endif
.....
.....
.....
.....
.....
.....
#ifdef cplusplus
}
#endif

Bir de extern "C++" bildirimi vardır. Bu bildirim fonksiyon isimlerinin C++ kurallarına göre obj modülün içerisine yazılacağını anlatır. Zaten fonksiyonlar default olarak bu kurala göre yazılırlar. Bu bildirim ileriye doğru uyumu sağlamak için düşünülmüştür. Şu anda bir kullanım gerekçesi yoktur.

6-)C++'ta dinamik bellek yönetimi new ve delete isimli iki operatörle yapılır.
Mademki C++ içerisinde bütün standart C fonksiyonları kullanılabiliyor, o halde dinamik bellek yönetimi malloc, claloc, realloc ve free fonksiyonlarıyla yapılabilir. Ancak bu fonksiyonlar nesne yönelimli programlama tekniğini uygulayabilmek için tasarlanmamıştır. Bu yüzden C++'ta yeni bir teknik kullanılmaktadır. C++'ta dinamik olarak tahsis edilme potansiyelindeki boş bölgelere free store denilmektedir(standart C'de heap denir).
 

Benzer konular

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
NEW Operatörü

Genel biçimi:

new <tür> [<[uzunluk]>]

new int
new char
new double [10]
new float[n]
new char[strlen(s) + 1]

Eğer köşeli parantez olmadan sadece tür ismi isle tahsisat yapılırsa o türden bir elemanlık yer tahsis edilmiş olur. Örneğin:
new int à1 int'lik yer tahsis edilmiştir.
Eğer köşeli parantez içerisine ifade yazılarak kullanılırsa bu durumda o ifade ile belirtilen sayıda elemanlık alan tahsis edilir. new operatörü türü belirli bir alan tahsis eder. Yani new operatörüyle elde edilen adresin tür bileşeni çağırılma ifadesindeki tür ile aynı olur.

int *p;
p = new int; /* Burada sizeof(int) kadar byte tahsis ediliyor ve tahsis edilen */ /* alanın başlangıç adresi elde ediliyor. Bu adres int türündedndir. */

char *p;
p = new int [10]; /* C++'ta hatadır. */
p = (char *)new int[10]; /* Hata değil. */

/*----------new1.cpp---------*/
#include <stdio.h>
#include <string.h>

void main(void)
{
char *p;

p = new char[30];
gets(p);
puts(p);
}
/*------------------------------*/

new bir operatördür. Ancak derleyici bu operatör kullanıldığında dinamik tahsisat işleminin yapılmasını sağlamak için dinamik tahsisat yapan bir fonksiyonun çağırma kodunu amaç koda ekler. Yani new bir operatör olmasına karşın tahsisat işlemi yerleştirilen bu fonksiyon sayesinde programın çalışma zamanı sırasında yapılmaktadır. Bu operatör öncelik tablosunun ikinci düzeyinde bulunmaktadır. Örneğin:
new int + n
gibi bir işlem geçerlidir. İşlemler:
İşlem 1 : new int
İşlem 2 : İşlem 1 + n

new operatörü tahsisat işlemini yapamazsa 0 değerini(xxxx gösterici) üretir.

/*-------freestor.cpp------*/
/*free store alanının hesaplanması*/
#include <stdio.h>

#define BLOCKSIZE 1024

void main(void)
{
long size = 0;
char *p;

for(;{
p = new char[BLOCKSIZE];
if(p == xxxx)
break;
size += BLOCKSIZE;
}
printf("Free store size = %ld\n", size);
}
/*---------------------------*/

Köşeli parantez içerisine yazılan ifade sabit ifadesi olmak zorunda değildir.

/*-----------new2.cpp---------*/
/*Tam olarak ad sosay uzunluğu kadar bellek tahsis eden fonksiyonun kullanılışı*/
#include <stdio.h>
#include <stdlib.h>

char *getname(void)
{
char *p;
char buf[80];

printf("Adı Soyadı;
gets(buf);
p = new char[strlen(buf) + 1)];
if(p == xxxx){
printf("Cannot allocate memory..\n");
exit(1);
}
strcpy(p, buf);
return p;
}

void main(void)
{
char *p;

p = getname();
puts(p);
}
/*--------------------------------*/
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
DELETE OPERATÖRÜ

delete operatöürü new operatörüyle tahsis edilmiş olan blokları serbest bırakmak için kullanılır. Genel biçimi:
1. delete p;
2. delete [] p;

Eğer tahsisat tek parça olarak yapılmışsa yani köşeli parantez kullanılmadan yapılmışsa silme işlemi köşeli parantez kullanılmadan yapılmalıdır. Örneğin:

int *p;
p = new int;
delete p;

Eğer tahsisat işlemi birden fazla eleman için yapılmışsa yani köşeli parantez kullanılarak yapılmışsa serbest bırakma işleminde de köşeli parantez kullanılmalıdır. Örneğin:

int *p;
p = new int[n];
delete [] p;

Burada köşeli parantez içerisine bir şey yazılmaz. delete operatörü unary prefix bir operatördür ve öncelik tablosunun ikinci düzeyinde bulunur.

delete p + 1; /*Hatalı*/
delete (p + 1);/*Doğru*/

delete operatörünün operandı daha önce tahsis edilmiş olan bloğun başlangıç adresi olmalıdır. Değilse beklenmeyen sonuçlar ortaya çıkabilir. Tabii derleici delete operatörüne karşılık amaç koda (object module'e) free gibi tahsis edilmiş bloğu serbest bırakan bir fonksiyon kodu yerleştirmektedir. new delete operatörlerinin tahsisat işlemlerinde kullandığı fonksiyon maloc, calloc, free fonksiyonları olmak zorunda değildir. Bu iki grup fonksiyon farklı tahsisat tabloları kullanıyor olabilir. Bu nedenle new delete operatörleriyle malloc, calloc, free gibi standart C fonksiyonlarını özel bir durum yoksa birlikte kullanmamak gerekir. Çünkü bir grup tarafından tahsis edilen alan diğer grup tarafından tahsis edilmemiş gibi gözükebilir.
Görüldüğü gibi C++'ta realloc fonksiyonun karşılığı bir operatör yoktur. Ancak böyle bir fonksiyon yazılabilir.

/*------------realloc.cpp---------------*/
void *Realloc(void *ptr, size_t newsize, size_t oldsize) /*size_t àunsigned int*/
{
void temp;

temp = new char [newsize];
memcpy(temp, ptr, oldsize);
delete [] ptr;
return temp;
}
/*----------------------------------------*/


Kullanımı:
p = new char [10]; /* 10 * sizeof(char) kadar bellek tahsis edildi */
p = Realloc(p, 20, 10); /* Tahsis edilmiş alan 20 * sizeof(char)'e büyütüldü */

SET_NEW_Handler FONKSİYONU

Normal olarak new oparetörü başarısızlıkla sonuçlandığında 0 adresine geri döner ve bu adresin test edilmesi gerekir. Ancak her new kullanımında bu adresin test edilmesi yerine daha etkin bir yöntem kullanılmaktadır. new operatörü başarısız olduğunda set_new_handler fonksiyonu ile belirlenen fonksiyonu çağırmaktadır. Böylece her defasında kontrol yapılmasına gerek kalmaz.

set_new_handler(void (*ptr)(void));

set_new_handler'a parametre olarak geri dönüş değeri void parametresi void olan bir fonksiyonun adresi verilir. Artık başarısızlık durumunda bu fonksiyon çağırılacaktır. new operatörü başarısızlık durumunda belirlenen fonksiyonu çağırır ve bu fonksiyon çağırıldıktan sonra tekrar tahsisat işlemini yapar. Yine başarısız olursa tekrar fonksiyonu çağırır ve bu böyle devam eder. Yani aşağıdaki algoritmadaki gib çalışır:
for(;{
if(boşyer var mı)
return boşyer;
else
set_new_handler();
}

/*-----------snhandle.cpp---------------*/
#include <stdio.h>
#include <new.h>
#include <stdlib.h>

long size = 0;

void myhandler(void)
{
printf("Free store size=%ld\n", size);
exit(1);
}
void main(void)
{
void *ptr;
void *oldhandler;

oldhandler = set_new_handler(myhandler);
for(;{
ptr = new char [1024];
size += 1024;
}
}
sen_new_handler(oldhandle); /*handler eski haline dönüştürüldü*/
/*------------------------------------------*/

set_new_handler'ın prototipi new.h içindedir.

7-)Bir adresin farklı türden bir göstericiye atanması ve adres olmayan bir bilginin bir göstericiye atanması durumu uyarı değil error olarak değerlendirilir.
Adres işlemlerinde tür uyuşmazlıkları C++'ta eror olarak değerlendirilir. Oysa standart C derleyicileri böyle durumlarda en fazla uyarı verirler. Ancak void göstericiye herhangi bir türden adres atanabilir. Fakat void bi adresin herhangi bir göstericiye atanması error olarak değerlendirlir(bu durum C'de en fazla uyarı olaak değerlendilir). Tabii tür dönüştürme operatörüyle her tür her türe atanabilir.

/*----------fark7.cpp----------*/
void main(void)
{
int s[100];
char *t;

t = s; /* "Cannot convert 'int *' to 'char *'" hatasını verir */
t = (char *)s; /* Hata vermez */
}
/*--------------------------------*/

Benzer biçimde const bir değişkenin adresi ancak const bir göstericiye atanmalıdır.

const int x;
int *y;
conts int *p;
y= &x; /* Hata verir */
p = &x; /* Hata vermez */

:cool:const bildirimi ile yaratılmış bir değişken sabit ifadesi gibi işlem görür.
C++'ta const bir değişken için yine bellekte yer ayrılır. Ancak const değişken kullanıldığında derleyici eğer const değişkene ilk değer sabit ifadesiyle verildiyse derleyici doğrudan o sabit ifadesini kullanır. Tabii const değişkene verilen ilk değer sabit ifadesi değilse bu consta değişken kullanıldığında derleyici doğrudan bir sayı yerleştiremez, const değişkenin kendisini yerleştirir.

const int MAX = a + 100;
const int MIN = 1;
y = MAX; /* Burada bir sayı yazamaz */
y = MIN; /* Burada MIN yerine 1 yazılabilir */
const int SIZE = 10;
int a[SIZE]; /* C++'ta geçerli C'de geçerli değil */

const değişken için yine de bellkte yer ayrılır. Bu durumda const değişkenin adresi alınabilir. Bu yolla const deişkenin içeriği de değiştirilebilir. Tabii bu değiştirme programın çalışma zamanı içerisinde olduğundan sonucu değiştirmez.

/*----------fark8.cpp------------*/
#include <stdio.h>

void main(void)
{
const int SIZE = 10;
int *p;

p = (int *)&SIZE;
*p = 20;
printf("%d\n", SIZE);
}
/*--------------------------------*/

9-)C++'ta statik ömürlü değişkenlere sabit ifadesiyle ilk değer verme zorunluluğu yoktur.
Global değişkenler ve statik yerel değişkenler gibi statik ömürlü değişkenlere ilk değer C'de sabit ifadesiyle verilmek zorundadır. Çünkü statik ömürlü değişkenler amaç kod içerisine ilk değerleriyle yazılırlar. Exe dosyasının içerisinde yer alırlar. Bunun mümkün olabilmesi için verilen ilk değerlerin derleme aşamasında belirlenmiş olması gerekir. Derleme aşamasında tespit edilmesi için ifadenin sabit ifadesi olması gerekir. Oysa C++'ta statik ömürlü değişkenlere her türden sıradan bir ifadeyle ilk değer verilebilir. Bu değişkenler 0 ilk değeriyle amaç koda yazılırlar. Programın çalışma zamanı sırasında ve main fonksiyonundan önce ilk değerini alırlar.

10-)Parametre değişkenlerinin default değerler alması(default function arguments)
C++'ta fonksiyon çağırılırken bir parametre belirtilmemişse ona ilişkin parametre değişkeni default bir değer alabilir. Böyle bir durum C'de yoktur. Bir parametre değişkeninin default değer alması durumu fonksiyon tanımlanırken ya da prototip bildiriminde paramere değişkeninden sonra eşittir operatörüyle belirtilmelidir.

/*---------fark10.cpp----------*/
#include <stdio.h>

void fonk(int x = 10, int y = 20)
{
printf("x = %d y = %d\n", x ,y);
}

void main(void)
{
fonk(100, 200); /* x = 100 y = 200 */
fonk(100); /* x = 100 y = 20 */
fonk(); /* x = 10 y = 20 */
}
/*--------------------------------*/

Bir parametre değişkeni default değer almışsa onun sağında bulunanların hepsi default değerler almak zorundadır.

void fonk(int x = 10, int y) /* Hata verir */
{
}

void fonk(int x, int y = 20) /* Hata vermez */
{
}

Default değer almamış olan bütün parametre değişkenleri için çağırılma ifadesinde parametre yazılmak zorundadır. Default değer alan parametre değişkenlerine sahip fonksiyonlarla aynı isimli başka fonksiyonların birlikte bulunması durumunda iki anlamlılık hataları oluşabilir. İki anlamlılık hataları fonksiyonların tanımlanması sonucunda değil çağırılması sonucunda ortaya çıkmaktadır.

/* İki anlamlılık hatası örneği */
#include <stdio.h>

void fonk(int x, int y = 20)
{
printf("%d %d\n", x, y);
}

void fonk(int x)
{
printf("%d\n", x);
}

void main(void)
{
fonk(100, 200); /* Hata vermez */
fonk(100); /* İki anlamlılık hatası verir */
}
/*------------------------------------------*/

Bir gösterici parametresi de default değer alabilir.

/* Göstericiye default değer */
#include <stdio.h>

void message(const char *p = "Success")
{
puts(p);
}

void main(void)
{
char *p = "Ali";

message(p);
message();
}
/*-------------------------------------------*/

Default Parametre Değişkenlerine Sahip Fonksiyonların Kullanılma Nedenleri

Çok sayıda parametrelere sahip fonksiyonlar söz konusu ise ve bu parametre değişkenlerinin belli bölümüne çağırma sırasında aynı değerler atanıyorsa default parametre değişkenlerinin kullanılması büyük bir yazım kolaylığı sağlar. Fazla sayıda parametrenin yazılmaması hem programcının iş yükünü azaltır, hem de okunabilirliği arttırır.

#include <stdio.h>
#include <stdlib.h>
void *myitoa(int n, char *str, int base = 10)
{
return itoa(n, str, base);
}

void main(void)
{
char s[100];

myitoa(123, s);
puts(s);
}
Default değer alan parametre değişkeni kullanılırken dikkat etmek gerekir. Bir fonksiyon % 90 aynı parametre değerleriyle çağırılıyorsa default parametre değişkeni kullanılmalıdır. "Hiçbir değer almayacağına bari şu değeri alsın" fikriyle kullanılmamalıdır. Böylesi kullanımlar kodu inceleyen kişiyi yanıltırlar. Bazen parametre değişkenine verilen default değerin özel bir anlamı olmaz. Bu default değer fonksiyonun default parametreyle çağırılıp çağırılmadını tespit etmek amacıyla kullanılır. Gerçek default değerler fonksiyonun içerisinde ve bir dizi işlemlerle elde edilir. Örneğin

#define DEFAULT_CALL (-1)

void writefile(void *ptr, unsigned size, long offset = DEFAULT_CALL)
{
if(offset != DEFAULT_CALL)
fseek(fp, offset, SEEK_SET);
fwrite(ptr, 1, size, fp);
}

void main(void)
{
double x = 10.2;

writefile(&x, sizeof(double));
}

Default Değer Alan Parametre Değişkenlerine Sahip Fonksiyonların Prototipleri

Böyle fonksiyonların prototiplerinde dafault parametre değerleri belirtilmelidir. Prototip yazma işlemi değişken isimlerini kullanarak ya da kullanmayarak yapılabilir. Örneğin aşağıdaki iki prototip de geçerlidir.

void sample(int = 10, int = 20);
void sample(int a = 10, int b = 20);

Prototipi yazılan fonksiyon aynı modül içerisinde tanımlanıyorsa(yani kütüphane içerisinde değilse) tanımlama sırasında bir daha bu default değerler yazılamaz. Yani default değerler ya prototipte ya da tanımlama sırasında belirtilmek zorundadır. Her ikisinde birden belirtilemezler. Tavsiye ediln kullanım prototipte belirtilmesi, tanımlama da belirtilmemesidir.

void sample(int x = 10, int y = 20);

void sample(int x =10, int y = 20) /* Hata verir */
{
}

void sample(int x, int y) /* Hata vermez */
{
}



11-)C++'ta göstericilere benzeyen ve ismine referans denilen ayrı bir tür vardır.
Referans Türünden Bir Göstericinin Tanımlanması

Genel biçimi:

<tür> &<referans_ismi> = <nesne>

Örnek:

int a = 10;
int &b = a;

double x;
..........
double &y = x;

Bir referans ilk değer verilerek tanımlanmak zorundadır. Örneğin:

int &r; /* hata */
double &r = 10.2; /* hata */

Referansa verilen ilk değer aynı türden bir nesne olmak zorundadır.

double x = 10 ;
int &r = x; /* Hata. Farklı türden bir nesneyle ilk değer verilmiş. */
int &r = a; /* Okunuşu: r int türünden bir referanstır */

Referanslar bir çeşit düzeyi yüksek göstericidir. Referansların içerisinde adres bilgisi bulunur. Derleyici bir referans tanımlandığında ilk değer olarak verilen nesnenin adresini referansın içerisine yerleştirir. Referansları iyi anlayabilmek için onların eşdeğer gösterici karşılıklarını düşünmek gerekir. Eş değer gösterici karşılığı referans yerine gösterici kullanıldığında elde edilecek eş değer kod anlamına gelir.

int a = 10;
int &b = a;

Eşdeğer karşılığı:

int a = 10;
int *b = &a;

Bir referans ilk değer verildikten sonra kullanıldığında artık referans içerisindeki adres değil referans içerisindeki adreste bulunan bilgi temsil edilir.






/*----------fark11.cpp--------------*/

#include <stdio.h>

#if 1

void main(void) /* referans kullanımı */
{
int a = 10;
int &b = a;

b = 50;
printf("%d %d\n", b, a);
}

#endif

#if 0

void main(void) /* referansın gösterici karşılığı */
{
int a = 10;
int *b = &a;

*b = 50;
printf("%d %d\n", *b, a);
}

#endif
/*-------------------------------------*/

int a = 10;
int &b = &a; /* Hata: &a int türünden değil adres türündendir */

Referansların Fonksiyon Parametresi Olarak Kullanılması

Referanslar fonksiyon parametresi olarak kullanılabilirler. Madem ki bir referans aynı türden bir nesneyle ilk değer verilerek tanımlanmak zorundadır, o halde parametresi referans olan fonksiyonlar aynı türden bir nesnenin kendisiyle çağırılmak zorundadır.

/* fonksiyon parametresi olan referans örneği */

#include <stdio.h>

#if 1 /* parametresi referans */
void fonk(int &a)
{
a = 20;
}

void main(void)
{
int x = 10;

fonk(x);
printf("%d\n", x);
}
#endif

#if 0 /* gösterici karşılığı */
void fonk(int *a)
{
*a = 20;
}

void main(void)
{
int x = 10;

fonk(&x);
printf("%d\n", x);
}
#endif
/*------------------------------------------------------------*/

Bir C programında fonk(a) gibi bir çağırma işlemiyle a değiştirilemez. Oysa C++'ta böyle bir çağırma fonksiyonun parametre değişkeni bir referans ise a paametresini değiştirebilir. Klasik bir C bakış açısıyla parametre olan a'nın değiştirilmeyeceği sanılabilir. Okunabilirliği kuvvetlendirmek için eğer parametreyi değiştirecek bir fonksiyon tasarlanacaksa bunun için referans değil gösterici kullanılmalıdır. Fonksiyonun parametre değişkeni referans ise derleyici tarafından otomatik olarak yapılan bir adres aktarımı söz konusudur.

Referans uygulaması Gösterici eşdeğeri
int a = 10;int &r1 = a;int &r2 = r1;r2 = 20;printf("%d\n", r1); int a = 10;int *r1 = &a;int r2 = &r1;*r2 = 20;printf("%d\n", *r1);

/*-----referans.cpp-----*/
#include <stdio.h>

#if 1 /* referans örneği */

void main(void)
{
int a = 10;
int &a1 = a;
int &a2 = a1;

a2 = 20;
printf("%d\n", a1);
}

#endif

#if 0 /*gösterici eşdeğeri */

void main(void)
{
int a = 10;
int *a1 = &a;
int *a2 = a1;

*a2 = 20;
printf("%d\n", *a1);
}

#endif
/*-------------------------*/

/*-----referan1.cpp-----*/
#include <stdio.h>

void main(void)
{
int a = 10;
int &b = a;


printf("%p %p\n", &a, &b);
}
/*--------------------------*/

Bir referans & operatörüyle adres alma işlemine sokulabilir. Bu durumda elde edilen değer referans içerisinde bulunan adreste bulunan nesnenin adresidir. Bu da referans içerisindeki adresle aynı olmak zorundadır. Bir referansın da bir adresi vardır. Ama o adres değeri geçerli bir ifdade ile elde edilemez. r bir referans olmak üzere & &r; ifadesi geçerli değildir. Çünkü bu ifadenin eşdeğer gösterici karşılığı & &*p;'dir ve &*p bir nesne değildir.

Yapı Değişkenlerinin Referans Yoluyla Fonksiyonlara Geçirilmesi

Bir yapı değişkeninin fonksiyona aktarılmasında doğru teknik yapı değişkeninin adresinin fonksiyona geçirilmesidir. Yani fonksiyon yapı değişkeninin adresiyle çağırılır, fonksiyonun parametre değişkeni o yapı türünden bir gösterici olur. Fonksiyonun içerisinde elemana ok(->) operatörüyle erişilir. Ancak C++'ta aynı etkinlikte olmak üzere referansla aktarım da söz konusudur. Yani fonksiyon yapı değişkeninin kendisiyle çağırılır. Fonksiyonun parametre değişkeni o yapı türünden bir referans olur. Fonksiyon içeriisnde elemana nokta operatörüyle erişilir.

/*----------referan2.cpp-------------*/
#include <stdio.h>

struct PERSON{
char *name;
int no;
};

void disp(struct PERSON &r)
{
printf("%s %d\n", r.name, r.no);
}

void main(void)
{
struct PERSON per = {"Ali Serçe", 123};

disp(per);
}
/*--------------------------------------*/

Yapıların referans ya da gösterici yoluyla fonksiyonlara aktarılması tamamen eşdeğer kullanımlardır.
const Referanslar

Bir referans da const olarak tanımlanabilir.

Referans örneği Gösterici eşdeğeri
int a = 10;const int &b = a;b = 20; /* Hata */ int a = 10;const int *p = &a;*p = 20; /* Hata */

const bir referans, gösterdiği yer const olan const bir göstericiye eşdeğerdir. Yani böyle referanslar sol tarafa değeri olarak kullanılamaz. Çünkü referans içerisinde bulunan adresteki bilgi const yapılmıştır. Const referanslar da okunabilirliği arttırmak amacıyla fonksiyon parametresi olarak kullanılırlar.

void disp(const struct PERSON &r);
Fonksiyonun referans olan parametresi de default argüman alabilir.

int x;

void fonk(int &a = x) /*fonksiyonun referans olan parametresi default değer almış*/
{
...
}

char &a = "Ali"; /* Doğru bir kullanımdır */

Fonksiyonun Geri Dönüş Değerinin Referans Olma Durumu

return ifadesiyle geri dönüş değerinin oluşturulması aslında derleyici tarafından tahsis edilen geçici bir bölgeye yapılan atama işlemidir. Yani return ifadesi önce geçici bir bölgeye yerleştirilir, sonra oradan alınarak kullanılır. Fonksiyonun geri dönüş değerinin türü bu geçici bölgenin türüdür. Bir fonksiyonun geri dönüş değeri referans olabilir. Bu durumda fonksiyonun geri dönüş değerine ilişkin geçici bölge referans türündendir. Bir referansa bir nesneyle ilk değer verileceğine göre böyle fonksiyonları return ifadelerinin de nesne olması gerekir.

Gösterici eşdeğeri Referans örneği
/*-----referan3.cpp-----*/#include <stdio.h>int a = 10;int *fonk(void){ return &a;}void main(void){ *fonk() = 20; printf("%d\n", a);} /*------referan4.cpp-----*/#include <stdio.h>int a = 10;int &fonk(void){ return a;}void main(void){ fonk() = 20; printf("%d\n", a);}


Artık bu fonksiyon kullanıldığında referans kullanılıyor gibi işlem göreceğinden return ifadesindeki nesne anlaşılır. Böyle fonksiyonların geri dönüş değeri nesne belirtir ve sol taraf değeri olarak kullanılabilir. Özetle referansa geri dönen bir fonksiyonun geri dönüş değeri kullanıldığında return ifadesindeki nesnenin kullanıldığı anlaşılır.

Bir Referansa Farklı Bir Türden Bir Nesneyle İlk Değer Verilmesi Durumu

Böyle bir durumda önce referansla aynı türden geçici bir değişken yaratılır. Verilen ilk değeri bu geçici değişkene atar, tabii otomatik tür dönüştürülmesi olur ve yaratılan bu geçici bölgenin adresi referansa aktarılır.

/*-----referan5.cpp-----*/
#include <stdio.h>

void main(void)
{ /* Eşdeğeri */
double x = 3.2; /* double x =3.2; */
int &r = x; /* int temp = x; */
/* int &r = temp; */
r = 5;
printf("%f\n", x);
}
/*--------------------------*/

Tabii böylesi bir durumda derleyiciler bir uyarıyla durumu bildirirler.


Bir Referansa Sabitle İlk Değer Verilmesi Durumu

Bir referansa bir sağ taraf değeriyle de ilk değer verilebilir. Bu durumda ilk değer olarak verilen sağ taraf değeri derleyici tarafından oluşturulan geçici bir bölgenin içerisine aktarılır. Geçici bölgenin adresi de referansa yerleştirilir.


Referans örneği Eşdeğeri
/*-----referan6.cpp-----*/#include <stdio.h>void main(void){ int &r = 10; r = 50; printf("%d\n", r);} int temp;int &r = temp;

Böyle iki problemli ilk değer verme durumlarından da kaçınmak gerekir. Her iki durumda da derleyici uyarı mesajı verecektir.

Göstericilerle Referanslar Arasındaki Benzerlikler ve Farklılıklar


- Göstericiler de referanslar da adres tutan nesnelerdir.
- Referansın içerisindeki adres bir daha değiştirilemez ama göstericinin içerisindeki adres değiştirilebilir.
- Diziler türü ne olursa olsun, referans yoluyla referanslara geçirilemezler. Çünkü dizi elemanlarına erişmek için adres arttırımı yapmak gerekir.
- Referanslar tek bir elemanı fonksiyona geçirmek için kullanılabilirler.

12-)C'de enum türü ile int türü tamamen aynıdır. Yani enum türünden bir değişkene int türünden bir değer atanabilir. Oysa C++'ta enum türü ayrı bir türdür ve enum türünden değişkenlere ancak enum türünden sabitler atanabilir.
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
SINIFLAR(classes)

Sınıflar nesne yönelimli programlama tekniğini uygulayabilmek için mutlaka gerekli olan C'deki yapılara benzeyen C++'a özgü veri yapılarıdır.
Tıpkı yapılarda olduğu gibi sınıflarla da çalışmadan önce bir sınıf bildirimi yapmak gerekir. Sınıf bildirimi bellekte yer kaplamaz(C++'ta nesne terimi daha çok bir sınıf türünden değişkeni anlatmakta kullanılır. Nesne yönelimli programlama tekniği sınıflar kullanılarak program yazma tekniğidir).
Sınıf Bildiriminin Genel Biçimi:

class [sınıf_ismi] {
[private:]
...
...
[protected:]
...
...
[public:]
...
...
};

Bir sınıf 3 bölümden oluşur:
1. Private
2. Protected
3. Public

Bir bölüm bölüm belirten anahtar sözcük ve iki nokta üst üste ile başlatılır, başka bir bölüm belirten sözcüğe kadar sürer. Birden fazla aynı bölüm belirten anahtar sözcük aynı sınıf bildirimi içerisinde kullanılabilir. Bölüm belirten anahtar sözcüklerin biri ya da hiçbirisi yazılmak zorunda değildir. Sınıf hiçbir bölüm belirten anahtar sözcükle başlatılmamışsa private bölüm anlaşılır. Okunabilirlik açısından sınıf isminin ilk harfi büyük geri kalan harfleri küçük yazılır. Bir yapı yalnızca veri elemanlarına sahiptir. Sınıflar hem veri hem fonksiyon içeren veri yapılarıdır. Yani normal yapılardan sınıfların fazlalıkları aynı zamanda fonksiyon da içermeleridir. Sınıf içerisinde bildirilen değişkenlere sınıfın veri elemanları(data member) sınıf içerisinde bildirilen fonksiyonlara ise sınıfın üye fonksiyonlar(member function) denir(daha yüksek seviyeli nesne yönelimli dilllerinde metod ismi de kullanılır). Veri elemanları ve üye fonksiyonları sınıfın herhangi bir yerinde yazılabilir. Üye fonksiyonların sadece prototipleri sınıf içerisine konur. Tanımlamaları sınıf bildiriminden sonra yapılır. Ancak genellikle protected bölümü pek kullanılmaz, sınıfın veri elemanları private bölüme üye fonksiyonları public bölüme yazılır.

Bir Sınıf Türünden Nesnenin Tanımlanması

Genel biçimi:

[class] <sınıf_ismi> <nesne_ismi>;

class Sample x;
Sample y;

class anahtar sözcüğü yazılmayabilir. C++'ta yapı türünden nesne tanımlarken struct anahtar sözcüğü de kullanılmayabilir. Bir sınıf nesnesi için sınıfın toplam veri elemanları kadar yer ayrılır.

/*-----class1.cpp-----*/
#include <stdio.h>

class Sample {
private:
int a, b;
public:
void fonk(void);
};

void main(void)
{
Sample x;
printf("%d\n", sizeof(x));
}
/*-----------------------*/

Üye Fonksiyonları Tanımlanması

Üye fonksiyonları prototipleri sınıf bildirimi içerisine yerleştirilir, tanımlamaları dışarıda aşağıdaki gibi yapılır.

[geri dönüş değerinin türü] <sınıf isim> :: <fonksiyon ismi> ([parametreler])

void Sample::fonk(void)
{

}

İki tane iki nokta üstüste C++'a özgü bir operatördür. Üye fonksiyonlar amaç koda parametre türleri ve sınıf isimleriyle kombine edilerek yazılırlar. Yani aynı isimli ve aynı parametre yapısına sahip bir üye fonksiyonu ve global bir fonksiyon tanımlanabilir. Hiçbir sınıfa ait olmayan fonksiyonlara global fonksiyon denir.

Sınıfın Veri Elemanlarına ve Üye Fonksiyonlarına Erişim

Sınıfın veri elemanlarına ve üye fonksiyonlarına nokta operatörüyle erişilir. Bir üye fonksiyonu ancak aynı sınıf türünden bir nesneyle çağırılabilir. Eğer nesne olmadan çağırılırsa global bir fonksiyonun çağırıldığı anlaşılır.

X.fonk(); /*üye fonksiyonu çağırılmış*/
fonk(); /*global fonkiyon çağırılmış*/

/*-----class2.cpp-----*/
#include <stdio.h>

class Sample {
public:
int a, b;
public:
void fonk(void);
};

void Sample::fonk(void)
{
printf("I'm sample fonk..\n");
}

void fonk(void)
{
printf("I'm global fonk..\n");
}

void main(void)
{
class Sample X;

X.a = 10;
X.b = 20;
X.fonk();
fonk();
}
/*-----------------------*/

Bir üye fonksiyon içerisinde sınıfın hangi bölümünde tanımlanmış olursa olsun bütün veri elemanları ve üye fonksiyonlarına doğrudan erişilebilir. Yani sınıfın veri elemanları sınıfın üye fonksiyonları arasında ortak olarak kullanılmaktadır. Bir üye fonksiyon içerisinde kullanılan üye fonksiyonları o üye fonksiyon hangi sınıf nesnesiyle çağırılmışsa o sınıf nesnesinin elemanları olur.
/*-----class3.cpp-----*/
#include <stdio.h>

class Sample {
public:
int a;
public:
void fonk1(int x);
void fonk2(void);
};

void Sample::fonk1(int x)
{
printf("I'm sample fonk1..\n");
a = x;
}

void Sample::fonk2(void)
{
printf("%d\n", a);
}
void main(void)
{
class Sample X;

X.fonk1(50);
Sample Y;

Y.fonk1(100);
X.fonk2();
Y.fonk2();
}
/*-----------------------*/

Bir üye fonksiyonu içerisinde sınıfın bir diğer üye fonksiyonu da doğrudan çağırılabilir. Sınıfın a üye fonksiyonu X nesnesiyle çağırılmış olsun, a üye fonksiyonu içerisinde b üye fonksiyonu doğrudan çağırılabilir. Bu durumda b üye fonksiyonu içerisinde kullanılan veri elemanları X sınıf nesnesine ilişkindir.

/*-----class4.cpp-----*/
#include <stdio.h>

class Sample {
public:
int a;
public:
void fonk1(int x);
void fonk2(void);
};

void Sample::fonk1(int x)
{
printf("I'm sample fonk1..\n");
a = x;
fonk2();
}

void Sample::fonk2(void)
{
printf("%d\n", a);
}

void main(void)
{
class Sample X;

X.fonk1(50);
}
/*-----------------------*/

Sınıf Faaliyet Alanı(class scope)

C'de dardan genişe doğru 3 tür faaliyet alanı vardır:
1. Blok faaliyet alanı
2. Fonksiyon faaliyet alanı
3. Dosya faaliyet alanı

C'de ve C++'ta aynı faaliyet alanına ilişkin birden fazla değişken aynı isimle tanımlanamaz. Ancak farklı faaliyet alanına ilişkin aynı isimli birden fazla değişken tanımlanabilir. Bir blok içerisinde birden fazla aynı isimli değişken faaliyet gösteriyorsa o blok içerisinde dar faaliyet alanına sahip olan erişilebilir.

C++'ta sınıf faaliyet alanı diye isimlendirilen ayrı bir faaliyet alanı daha tanımlanmıştır. Sınıf faaliyet alanı fonksiyon faaliyet alanı ile dosya faaliyet alanı arasında bir alana sahiptir. Sınıf faaliyet alanı yalnızca bir sınıfın tüm üye fonksiyonları arasında tanınma aralığıdır. Sınıfın veri elelamanları ve üye fonksiyon isimleri sınıf faaliyet alanına uyarlar. Bir sınıfın veri elemanıyla aynı isimli sınıfın üye fonksiyonu içerisinde aynı isimli bir yerel değişken tanımlanabilir. Bu durumda fonksiyon içerisindeki blokta yerel olana erişilir. Benzer biçimde bir üye fonksiyon içerisinde bir fonksiyon çağırılmışsa çağırılan fonksiyon ile aynı isimli hem global hem de bir üye fonksiyon varsa dar faaliyet alanı kuralına göre üye fonksiyon çağırıldığı varsayılır.

Çözünürlük Operatörü(scope resolution operator)

:: operatörüne çözünürlük operatörü denir. Bu opertörün hem binary-infix hem de unary-prefix olarak kullanılan tipleri vardır.

1. Binay infix resolution operatörü:
Bu kullanımda sol tarafındaki operandın bir sınıf ismi, sağ tarafındaki operandın ise veri elemanı ya da fonksiyon ismi olması gerekir. Bu operatör sınıfın faaliyet alanı probleminden dolayı gizlenmiş olan veri elemanına ya da üye fonksiyonuna erişimini sağlar.

void Sample::fonk1(int a)
{
printf("Sample fonk1..\n");
Sample::a = a; /*sınıfın veri elemanı olan a'ya parametre a'yı ata*/
}

2. Unary prefix resolution operatörü:
Bu durumda operand global bir değişken ya da fonksiyon ismi olabilir. Bu haliyel bu operatör faaliyet alanı probleminden dolayı global olana erişimi sağlar. Bu operatör öncelik tablosunun en yüksek düzeyinde bulunur.

Başlangıç ve Bitiş Fonksiyonları

1. Başlangıç Fonksiyonları(constructors)

Bir sınıf destesi tanımlandığında derleyici tarafından otomatik olarak çağırılan fonksiyona sınıfın başlangıç fonksiyonu denir. Yerel bir sınıf nesnesi programın akışı tanımlama noktasına geldiğinde, global bir sınıf nesnesiyse program belleğe yüklenir yüklenmez yaratılır. Başlangıç fonksiyonun ismi sınıf ismiyle aynı olmalıdır. Başlangıç fonksiyonlarının geri dönüş değeri gibi bir kavramı yoktur. Yani geri dönüş türü yerine bir şey yazılmaz. bu durum int ya da void anlamına gelmez. Başlangış fonksiyonları içerisinde return anahtar sözcüğü kullanılabilir, ancak yanına bir ifade yazılamaz. C++'ta farklı parametre yapısına sahip birden fazla başlangıç fonksiyonu olabilir. Parametresi olmayan(yani void olan) başlangış fonksiyonuna default başlangıç fonksiyonu(default constructor) denir. Eğer sınıf nesnesi nesne isminden sonra parantez açılmadan yani normal bir biçimde tanımlanmış ise (örneğin: X n bu durumda varsayılan başlangıç fonksiyonu çağırılır. Eğer nesne isminden sonra bir parantez açılır ve içerisine bir parametre listesi yazılırsa (örneğin: X n(10) parametre listesine uygun olan başlangıç fonksiyonu çağırılır.
Uyarı: Nesne isminden sonra parantez açılıp içine hiçbirşey yazılmazsa bu durumda varsayılan başlangıç fonksiyonu çağırılmaz. Bu ifade bir fonksiyon prototipi anlamına gelir. Örneğin:

X a(); /*parametresi olmayan, X türünden bir fonksiyonun prototipi*/

Global sınıf nesnelerine ait başlangıç fonksiyonları main fonksiyonundan önce çağırılır. Daha yukarıda tanımlanan daha önce çağırılacak bir biçimde sıralama söz konusudur.

2. Bitiş Fonksiyonu(destructor)

Bir nesne faaliyet alanını bitirmesiyle bellekten silinir. Yerel değişkenler programın akışı tanımlandıkları bloğun sonunda, global değişkenler ise programın bitimiyle bellekten silinirler. Bir sınıf nesnesi bellekten silineceği zaman otomatik olarak çağırılan fonksiyona bitiş fonksiyonu(destructor function) denir. Bitiş fonksiyonunun ismi sınıf ismiyle aynıdır, anck başına bir ~ sembolü getirilir. Bitiş fonksiyonunun da geri dönüş değeri gibi bir kavramı yoktur. Bitiş fonksiyonu en az ve en fazla bir tane olabilir. Parametresi void olmak zorundadır. Yani parametresi olmamak zorundadır. Varsayılan bitiş fonksiyonu diye bir kavram yoktur. Global bir sınıf nesnesine ait bitiş fonksiyonu programın sonucunda main bittikten sonra yani main'in sonunda çalıştırılır. Başlangıç ve bitiş fonksiyonlarının çağırılma sıraları her zaman terstir. a ve b herhangi türden iki sınıf nesnesi olmak üzere başlangıç fonksiyonları önce a sonra b olacak şeklinde çağırılıyorsa bitiş fonsiyonları önce b sonra a şeklinde çağırılır(LIFO sistemi).

Başlangıç ve Bitiş Fonksiyolarının Bulundurulma Kuralı

Sınıfın bitiş fonksiyonu olmak zorunda değildir. Yani varsa çağırılır yoksa çağırılmaz. Bir sınıf nesnesinin tanımlanma biçimine uygun bir başlangıç bir fonksiyonu olmak zorundadır. Ancak sınıfın hiçbir başlangıç fonksiyonu yoksa ve nesne varsayılan başlangıç fonksiyonu çağırılacak biçimde tanımlanmışsa bu durum istisna olarak hata oluşturmaz. Ancak sınıfın herhangi bir başlangıç fonksiyonu varsa fakat varsayılan başlangıç fonksiyonu yoksa varsayılan fonksiyonu çağıracak biçimde yapılacak bir tanımlama hata ile sonuçlanır.
Başlangıç ve Bitiş Fonksiyonlarının Kullanılma Nedenleri

Nesne yönelimli programlama da bir sınıf belirli bir amacı gerçekleştiren bir kütüphane olarak ele alınabilir. Örneğin seri port işlemlerini yapan bir sınıf tasarlanabilir. Fare işlemleri için ayrı bir sınıf yazılabilir. Bu sınıfların faydalı işlemleri yapan bir takım üye fonksiyonları olmalıdır. Bu üye fonksiyonlar sınıfın veri elemanlarını ortak olarak kullanırlar. Bir sınıf bir takım yararlı işleri yapmaya aday ise o yararlı işlemleri gerçekleştirmek için bazı hazırlık işlemleri gerekebilir. Örneğin seri port ile ilgili işlem yapan bir sınıfta seri portun set edilmesi, fare işlemleri yapan sınıfta farenin reset edilmesi dosya işlemleri yapan bir sınıfta dosyanın açılması bu tür hazırlık işlemleridir. Bu hazırlık işlemleri sınıfın başlangıç fonksiyonu içerisinde yapılırsa sınıfı kullanan kod küçülür, ayrıntılar göz ardı edilir ve algılama iyileştirilir(abstraction). Örneğin dosya işlemleri yapan sınıfın başlangıç fonksiyonu içerisinde dosya açılabilir. Nesne tanımlanır tanımlanmaz hazırlık işlemlerinin otomatik olarak yapılması sınıfı kullanan kişilerin de işlerini kolaylaştırır.
Bitiş fonksiyonu başlangıç fonksiyonuyla yapılan hazırlık işlemlerinin otomatik bir biçimde geri alınması için kullanılır. Örneğin dosya işlemlerini yapan sınıfın bitiş fonksiyonu otomatik olarak kapayabilir. Seri port işlemlerini yapan sınıfın bitiş fonksiyonu port ayarlarını eski durumuna getirebilir. Tabii bazı durumlarda hazırlık işlemlerinin geri alınması gerekmeyebilir. Yani başlangıç fonksiyonunun olması bitiş fonksiyonunun olmasını mantıksal bakımdan gerekli kılmaz.
Genel Biçi Sınıf Bildirimininmi:

class [sınıf_ismi] {
[private:]
...
...
[protected:]
...
...
[public:]
...
...
};

Bir sınıf 3 bölümden oluşur:
1. Private
2. Protected
3. Public

Bir bölüm bölüm belirten anahtar sözcük ve iki nokta üst üste ile başlatılır, başka bir bölüm belirten sözcüğe kadar sürer. Birden fazla aynı bölüm belirten anahtar sözcük aynı sınıf bildirimi içerisinde kullanılabilir. Bölüm belirten anahtar sözcüklerin biri ya da hiçbirisi yazılmak zorunda değildir. Sınıf hiçbir bölüm belirten anahtar sözcükle başlatılmamışsa private bölüm anlaşılır. Okunabilirlik açısından sınıf isminin ilk harfi büyük geri kalan harfleri küçük yazılır. Bir yapı yalnızca veri elemanlarına sahiptir. Sınıflar hem veri hem fonksiyon içeren veri yapılarıdır. Yani normal yapılardan sınıfların fazlalıkları aynı zamanda fonksiyon da içermeleridir. Sınıf içerisinde bildirilen değişkenlere sınıfın veri elemanları(data member) sınıf içerisinde bildirilen fonksiyonlara ise sınıfın üye fonksiyonlar(member function) denir(daha yüksek seviyeli nesne yönelimli dilllerinde metod ismi de kullanılır). Veri elemanları ve üye fonksiyonları sınıfın herhangi bir yerinde yazılabilir. Üye fonksiyonların sadece prototipleri sınıf içerisine konur. Tanımlamaları sınıf bildiriminden sonra yapılır. Ancak genellikle protected bölümü pek kullanılmaz, sınıfın veri elemanları private bölüme üye fonksiyonları public bölüme yazılır.

Bir Sınıf Türünden Nesnenin Tanımlanması

Genel biçimi:

[class] <sınıf_ismi> <nesne_ismi>;

class Sample x;
Sample y;

class anahtar sözcüğü yazılmayabilir. C++'ta yapı türünden nesne tanımlarken struct anahtar sözcüğü de kullanılmayabilir. Bir sınıf nesnesi için sınıfın toplam veri elemanları kadar yer ayrılır.

/*-----class1.cpp-----*/
#include <stdio.h>

class Sample {
private:
int a, b;
public:
void fonk(void);
};

void main(void)
{
Sample x;
printf("%d\n", sizeof(x));
}
/*-----------------------*/

Üye Fonksiyonları Tanımlanması

Üye fonksiyonları prototipleri sınıf bildirimi içerisine yerleştirilir, tanımlamaları dışarıda aşağıdaki gibi yapılır.

[geri dönüş değerinin türü] <sınıf isim> :: <fonksiyon ismi> ([parametreler])

void Sample::fonk(void)
{

}

İki tane iki nokta üstüste C++'a özgü bir operatördür. Üye fonksiyonlar amaç koda parametre türleri ve sınıf isimleriyle kombine edilerek yazılırlar. Yani aynı isimli ve aynı parametre yapısına sahip bir üye fonksiyonu ve global bir fonksiyon tanımlanabilir. Hiçbir sınıfa ait olmayan fonksiyonlara global fonksiyon denir.

Sınıfın Veri Elemanlarına ve Üye Fonksiyonlarına Erişim

Sınıfın veri elemanlarına ve üye fonksiyonlarına nokta operatörüyle erişilir. Bir üye fonksiyonu ancak aynı sınıf türünden bir nesneyle çağırılabilir. Eğer nesne olmadan çağırılırsa global bir fonksiyonun çağırıldığı anlaşılır.

X.fonk(); /*üye fonksiyonu çağırılmış*/
fonk(); /*global fonkiyon çağırılmış*/

/*-----class2.cpp-----*/
#include <stdio.h>

class Sample {
public:
int a, b;
public:
void fonk(void);
};

void Sample::fonk(void)
{
printf("I'm sample fonk..\n");
}

void fonk(void)
{
printf("I'm global fonk..\n");
}

void main(void)
{
class Sample X;

X.a = 10;
X.b = 20;
X.fonk();
fonk();
}
/*-----------------------*/

Bir üye fonksiyon içerisinde sınıfın hangi bölümünde tanımlanmış olursa olsun bütün veri elemanları ve üye fonksiyonlarına doğrudan erişilebilir. Yani sınıfın veri elemanları sınıfın üye fonksiyonları arasında ortak olarak kullanılmaktadır. Bir üye fonksiyon içerisinde kullanılan üye fonksiyonları o üye fonksiyon hangi sınıf nesnesiyle çağırılmışsa o sınıf nesnesinin elemanları olur.
/*-----class3.cpp-----*/
#include <stdio.h>

class Sample {
public:
int a;
public:
void fonk1(int x);
void fonk2(void);
};

void Sample::fonk1(int x)
{
printf("I'm sample fonk1..\n");
a = x;
}

void Sample::fonk2(void)
{
printf("%d\n", a);
}
void main(void)
{
class Sample X;

X.fonk1(50);
Sample Y;

Y.fonk1(100);
X.fonk2();
Y.fonk2();
}
/*-----------------------*/

Bir üye fonksiyonu içerisinde sınıfın bir diğer üye fonksiyonu da doğrudan çağırılabilir. Sınıfın a üye fonksiyonu X nesnesiyle çağırılmış olsun, a üye fonksiyonu içerisinde b üye fonksiyonu doğrudan çağırılabilir. Bu durumda b üye fonksiyonu içerisinde kullanılan veri elemanları X sınıf nesnesine ilişkindir.

/*-----class4.cpp-----*/
#include <stdio.h>

class Sample {
public:
int a;
public:
void fonk1(int x);
void fonk2(void);
};

void Sample::fonk1(int x)
{
printf("I'm sample fonk1..\n");
a = x;
fonk2();
}

void Sample::fonk2(void)
{
printf("%d\n", a);
}

void main(void)
{
class Sample X;

X.fonk1(50);
}
/*-----------------------*/
Sınıflarda Temel Erişim Kuralları

Temel erişim kuralı sınıf bölümlerinin ne anlama geldiğiyle ilgilidir. İki kural vardır:

1. Bir sınıf nesnesi yoluyla dışarıdan nokta ya da ok operatörünü kullanarak sınıfın yalnızca public bölümünde bildirilen veri elemanlarına ya da fonksiyonlarına erişilebilir. Private veya protected bölümlerine erişilemez.
2. Sınıfın üye fonksiyonu hangi bölümde bildirilmiş olursa olsun sınıfın her bölümündeki veri elemanlarına ve üye fonksiyonlarına erişebilir. Yani üye fonksiyonlar içerisinde sınıfın her bölümündeki veri elemanlarını kullanabilir ve üye fonksiyonlarını çağırabiliriz.

Genellikle sınıfın veri elemanları sınıfın rivate bölümünde üye fonksiyonları ise public bölümde tutulur. Böylece veri elemanlarına dışarıdan doğrudan erişilemez. Dışarıdan doğrudan üye fonksiyonlara erişilir. Üye fonksiyonları veri elemanlarına erişirler. Yani veri elemanlarına doğrudan değil üye fonksiyonlar yoluyla erişilmesi istenmiştir. Eğer private bölgedeki veri elemanlarının değerlerini almak ya da bunlara değer yerleştirilmek istenirse bunlarla ilişki kuran bir grup get ve set fonksiyonu yazmak gerekir.

Yeniden kullanılabilirlik(reusability) nesne yönelimli programlama tekniğinin anahtar kavramlarından birisidir. Bu kavram yazılmış olan bir kodun özellikle de bir sınıfın başka projelerde tekrar yazılmadan kullanılması anlamına gelir.

Veri Elemanlarının private, Üye Fonksiyonlarının public Kısmına Yazılması

Genellikle sınıflarda veri koruması istendiği zaman sınıfın veri elemanları private bölgeye üye fonksiyonları ise public bölgeye yazılırlar. Sınıfın veri elemanlarının private bölgeye yerleştirilmesi dışarıdan onlara doğrudan erişimi engeller. Onlara public bölgedeki bir grup üye fonksiyon ile erişiriz. Normalde tasarlanmış olan bir sınıf çok değişik ve uzun kodlarda kullanılabilir. Yani sınıfı kullanan kodlar sınıfın kendi kodlarından çok daha fazladır. Eğer veri elemanlarını private bölgeye yerleştirirsek o veri elemanlarının genel yapısında değişiklik olduğunda sınıfı kullanan kodları değiştirmek zorunda kalmayız. Yalnızca prototipleri aynı kalmak üzere sınıfın üye fonksiyonlarını yeniden yazmak zorunda kalırız. Oysa veri elemanları puıblic bölgeye yerleştirilseydi, dışarıdan bu elemanlara doğrudan erişilebilirdi ve veri yapısı değiştiğinde onu kullanan tüm kodları değiştirmek gerekirdi. Çeşitli veri elemanlarını ve üye fonksiyonları private bölgeye yerleştirmekle onları sınıfı kullanan kişinin algısından uzak tutarız. Kişiler erişemeyecekleri bilgileri incelemezler. Bu durumda nesne yönelimli programlama tekniğinde veri gizleme(data hiding) denir. Tabi veri elemanlarının private bölgeye yerleştirilmesi bunlara erişimi zorlaştırır. Çünkü erişim doğrudan değil, ara birim üye fonksiyonlarla yapılır. Sınıfın veri yapısı değiştirilmeyecekse veri elemanları doğrudan public bölgeye de yerleştirilebilir. Bu durumda onları doğrudan kullanmanın bir zararı olmaz.

/*----date2.cpp----*/
/*----date3.cpp----*/
Dinamik Tahsisat Yapan Sınıflar

Pek çok sınıf başlangıç fonksiyonu içerisinde bir veri elemanı için dinamik tahsisat yapar. Tahsis edilen bu dinamik bölgenin bitiş fonksiyonu içerisinde otomatik olarak sisteme iade edilmesi istenir.

/*----consdest.cpp----*/

Bir dosyanın başlangıç fonksiyonu içinde açılması ve bitiş fonksiyonunda otomatik olarak kapatılması gibi durumlara sıkça rastlanır.

/*----clasfile.cpp----*/

Bazen bitiş fonksiyonunda otomatik olarak yapılan geri alma işlemi bir üye fonksiyon ile bilinçli olarak da yapılabilir. Böylece bitiş fonksiyonundaki geri alma işlemi geçersiz hale gelir. Çünkü geri alma işlemi daha önce geçekleşmiştir. O halde bitiş fonksiyonu içerisinde geri alma işlemi daha önce yapılmışsa geri alma işlemini yapmamak gerekir. bunu sağlamak için çeşitli veri elamanlarından faydalanılabilir.

File::File(void)
{
f = xxxx;
}

File::~File(void)
{
if (f)
fclose(f);
}

Sınıf Türünden Göstericiler ve Adresler

Bir sınıf nesnesinin veri elamanları tıpkı bir yapı gibi belleğe adrışıl bir biçimde yerleştirilir. Sınıfın aynı bölümündeki veri elemanları o bölüm içerisinde ilk yazılan düşük anlamlı adreste olacak biçimde ardışıl olarak yerleştirilirler. Ancak bölümlerin sırası için herhangi bir zorunluluk standartlara konulmamıştır. Yani bölüm içleri ilk yazılan düşük anlamlı adreste olacak biçimde yerleştirilir, ancak bölümlerin kendi aralarında nasıl yerleştirileceği derleyiciden derleyiciye değişebilir. Bölümler arası yerleşim de ardışıl olmak zorundadır. Ancak derleyicilerin hemen hepsi bölüm farkı gözetmeksizin ilk yazılan elemanın düşük anlamlı adreste olacağı biçimde yerleşim kullanırlar. Bir sınıf nesnesinin adresi alınabilir. Elde edilen adresin sayısal bileşeni sınıf veri eleman bloğunun başlangıç adresidir. Bir sınıf türünden değişkenin adresi aynı türden bir sınıf göstericiye yerleştirilmelidir. Bir sınıf göstericisi yoluyla sınıfa ilişkin bir üye fonksiyon ok operatörüyle ya da * ve nokta operatörüyle çağırılabilir.

Date x(10, 10, 1999);
Date *p;

p = &x;
x.Disp(); /* Disp() fonksiyonu çağırılır */
p -> Disp(); /* Disp() fonksiyonu çağırılır */
(*p).Disp(); /* Disp() fonksiyonu çağırılır */

Bir üye fonksiyon sınıfa ilişkin bir gösterici kullnılarak ok operatörüyle çağırıldığında üye fonksiyon o göstericiyel belirtilen adreste bulunan veri elemanlarını kullanır.

p -> Disp(); Disp() üye fonksiyonu p göstericisinin içerisinde bulunan veri elemanlarını kullanır.

/*-----date4.cpp-----*/

Yine gösterici yoluyla sınıfın veri elemanlarına erişilir. Tabii bu elemanların public bölgede olması gerekir.
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
Sınıf Türünden Referanslar

Bir sınıf türünden referans aynı türden bir sınıf nesnesiyle ilk değer verilerek tanımlanabilir. Bu durumda derleyici ilk değer olarak verilen sınıf nesnesinin adresini referansa yerleştirir. Bir referans yoluyla sınıfın veri elemanlarına ve üye fonksiyonlarıyla nokta operatörü kullanılarak erişilir. Bir referans ile bir üye fonksiyonu çağırıldığında üye fonksiyon referansın içerisinde bulunan adresteki veri elemanlarını kullanır.

/*-----date5.cpp-----*/

Sınıf Türünden Değişkenlerin Fonksiyonlara Geçirilmesi

Bir sınıf tıpkı yapı gibi bileşik bir nesnedir. Bu durumda yapılarda olduğu gibi fonksiyona geçirmede iki teknik kullanılabilir. Sınıfın kendisinin geçirilmesi yöntemi özel durumlar dışında kötü bir tekniktir. Adres yöntemiyle fonksiyona geçirme tekniği tercih edilmelidir. Adresle geçirme işlemi C++'ta iki biçimde yapılabilir:
1. Gösterici kullanılarak: Yani fonksiyon bir sınıf nesnesinin adresiyle çağırılır. Fonksiyonun parametre değişkeni de aynı sınıf türünden bir gösterici olur. Bu durumda fonksiyon içerisinde sınıfn veri elemanlarına ve üye fonksiyonlarına ok operatörüyle erişilir.
/*-----date6.cpp-----*/
2. Referans kullanılarak: Bu durumda fonksiyon bir sınıf nesnesinin kendisiyle çağırılır. Fonksiyonun parametre değişkeni aynı türden bir referans olur. Fonksiyon içerisinde veri elemanlarına ve üye fonksiyonlarına nokta operatörüyle erişilir.
/*-----date7.cpp-----*/

Bu iki yöntem arasında etkinlik farkı yoktur.

Date *p;

p - > Disp();

İfadesi syntax bakımından geçerlidir. Ancak bir gösterici hatası söz konusudur. Disp() fonksiyonu p göstericisinin içerisindeki rastgele yerdeki veri elemanlarını kullanır. Bir sınıf türünden referans ya da gösterici tamnımlandığında başlangıç fonksiyonu çağırlmaz. Çünkü başlangıç fonksiyonu nesnenin kendisi tanımlandığında çağırılmaktadır.

Sınıf Türünden Dinamik Alan Tahsis Edilmesi

new operatörüyle bir sınıf türünden heap üzerinde dinamik bir alan tahsis edilebilir. Tahsis edilen alanın başlangıç adresi aynı türden bir göstericiye atanmalıdır. Madem ki new operatörüyle heap üzerinde bir nesne yaratılıyor o halde yaratılan nesne için başlangıç fonksiyonu çağırılır. Bir sınıf türünden dinamik alan tahsis edildiğinde tahsis edilme işleminin hemen ardından derleyici tarafından otomatik olarak tahsis edilen alan için başlangıç fonksiyonu çağırılır. Eğer sınıf isminden sonra bir parantez açılmazsa varsayılan başlangıç fonksiyonu, açılır ve bir parametre listesi yazılırsa parametre listesine uygun olan başlangıç fonksiyonu çağırılır.

X *p;
p = new X; /*default başlangıç fonksiyonu çağırılır*/
p = new X(a, b, c); /*parametreleri uyan başlangıç fonksiyonu çağırılır */
p = new X(); /*default başlangıç fonksiyonu çağırılır */

/*-----date8.cpp-----*/

C++'ta dinamik bellek yönetiminin dinamik bellek fonksiyonlarıyla değil de new ve delete ile yapılmasının nedeni başlangıç fonksiyonunun otomatik olarak çağırılmasını sağlamak içindir.

Sınıf Türünden Yaratılmış Dinamik Bir Alanın Boşaltılması

Dinamik olarak tahsis edilmiş bir sınıf delete operatörüyle sisteme iade edilebilir. delete operatörü alanı serbets bırakmadan önce bitiş fonksiyonunu otomatik olarak çağırmaktadır.

/*-----date9.cpp-----*/

new operatörüyle bir sınıf türünden bir dizi alan tahsis edilebilir.

p = new X[n];

Bu durumda derleyici n * sizeof(X) kadar alanı dinamik olarak tahsis eder ve yaratılmış olan n nesnenin her biri için default başlangıç fonksiyonunu çağırır. Bir dizi için alan tahsis edilmesi durumunda tahsis edilen sınıf nesneleri için başka bir başlangıç fonksiyonu çağırılamaz.

p = new X[n] (a, b, c); /*Geçerli değildir. Tür dönüşümü olarak düşünülür*/

Ancak bir dizi sınıf nesnesi için dinamik tahsisat işlemine çok nadir rastlanır. Bir dizi sınıf nesnesi için tahsis edilmiş olan alanın delete ile silinmesi durumunda her bir sınıf nesnesi için ayrı ayrı bitiş fonksiyonu çağırılır. Böle bir durumda eğer yanlışlıkla silme işlemi köşeli parantez kullanılmadan yapılırsa(örneğin: delete p tüm dinamik alan serbest bırakılır fakat yalnızca ilk yaratılmış nesne için bitiş fonksiyonu çağırılır.

/*-----date10.cpp-----*/

Sınıf Türünden Dinamik Tahsisat İşleminin Anlam ve Önemi

class X
{
public:
X(int n);
void Disp(void);
private:
int x;
};
X(int n)
{
x=n;
}

void X:isp(void)
{
printf("%d\n",x);
}
X::~X(void)
{
printf("I am a destructor\n");
}

X *p;

void main (void)
{
{

}

}

Normal bir sınıf nesnesi için başlangıç ve bitiş fonksiyonlarının çağrılacağı nokta, nesnenin tanımlandığı yere göre belirlenmektedir. Örneğin yerel bir nesne için bitiş fonksiyonu kesinlikle tanımlama bloğnun sonunda çağrılır. Oysa tahsisat işlemi dinamik olarak yapılırsa, nesnenin yaratılması ve bellekten silinmesi, başlangıç ve bitiş fonksiyonlarının çağrılması programcının istediği noktada yapılabilir.
Dinamik olarak tahsis edilmiş sınıf nesnesi için delete operatörü ile silme yapılmamışsa tahsis edilmiş olan alan normal olarak programın bitiminde sisteme iade edilir. Ancak bu iade edilme işlemi sırasında bitiş fonksiyonu çağrılmaz. Dinamik olarak tahsis edilmiş bir alan için bitiş fonksiyonunun çağrılması ancak delete işlemi ile mümkün olabilir.


sınıf nesnesi için dinamik tahsisat yapıldığı halde başlangıç fonksiyonun çağrılmaması aşağıdaki gibi sağlanabilir.

p= (X *) new char [sizeof(X)]

CONST üye fonksiyonlar

Standart C'de bir fonksiyonun const olması tanımlı ve geçerli değildir. Oysa C++'ds bir sınıfın üye fonksiyonu const olabilir. (C++'da global bir fonksiyon const olamaz, sadece sınıf üye fonksiyonları const olabilir)

Bir üye fonksiyonu const yapabilmek için, fonksiyon prototipinde ve tanımlama sırasında parametre parantezi kapatıldıktan sonra const anahtar sözcüğü yazılmalıdır. (Not const anahtar sözcüğünün hem prototipte hem de tanımlama cümlesinde yazılması zorunludur).
Const üye fonksiyon içerisinde sınıfın üye elemanları değiştirilemez.

Class X
{
public:
X(int n);
void set(int n);
void Disp (void) const;
~X(void);
private:
int x;
};

X:(int n)
{
x=n;
}

void X:isp(void) const
{
printf("%d\n",x);
x=10;

}

X::~X(void)
{
printf("I am a destructor\n");
}

void main(void)
{
X a(20); //hata verecek
a.disp();
}

Const üye fonksiyonlar, const üye fonksiyonlarda olduğu gibi okunabilirliği artırmak için kullanılabilir.


Class X
{
public:
X(int n);
void set(int n);
void Disp (void) const;
~X(void);
private:
int x;
};

X:(int n)
{
x=n;
}

void X::set(int n)
{
x=n;
}

void X:isp(void) const
{
printf("%d\n",x);
x=10;

}

X::~X(void)
{
printf("I am a destructor\n");
}

void main(void)
{
X a(20); //hata verecek
a.disp();
a.set(50);
a.disp();
}

Const bir fonksiyon içerisinde const olmayan bir fonksiyon çağrılamaz, çünkü const olmayan bir fonksiyon sınıfın veri elemanlarını kullanabilir.

Const bir üye fonksiyonun içerisinde sınıfın bütün üye elemanlarının const olduğu kabul edilir. Örneğin böyle bir üye fonksiyon içerisinde sınıfın bir veri elemanının adresi const olmayan bir göstericiye atanamaz.

Not: C++'da const bir değişkenin adresi const olmayan bir göstericiye atanamaz.

Sınıfın başlangıç ve bitiş fonksiyonları const üye fonksiyonu olarak tanımlanamaz. Yasak.

Const Sınıf Nesneleri

void main(void)
{
const X a(10);
}

Bir sınıf nesnesi const olabilir. Bu durumda sınıf nesnesinin veri elemanları her hangi bir biçimde değiştirilemez. Const bir sınıf nesnesi ile ancak const bir üye fonksiyon çağrılabilir, const olmayan bir üye fonksiyon çağrılamaz.

Not: Const tamamen derleyici için olan bir kavramdır.

Const bir sınıf göstericisi ya da referansı söz konusu olabilir. Tabii bu gösterici ya da referansla ancak const üye fonksiyonlar çağırılabilir. Özellikle bir fonksiyonun parametre değişkeninin const bir sınıf göstericisi ya da const bir sınıf referansı olma durumuna sıklıkla rastlanır.

String Sınıfı

C'de yazılar üzerinde işlem yapmak için karakter dizileri kullanılır. Ancak karakter dizilerinin normal bir dizi biçiminde tanımlanması genellikle kullanılan yöntem olsa da önemli problemleri vardır. Örneğin dizinin uzunluğu sabit ifadesi olmak zorundadır. Böylece yazıların büyütülmesi gibi işlemler verimsiz bir biçimde yapılır. Çünkü işlemler en kötü olasılıkla uzun diziler açılarak yürütülmek zorundadır. Bu da belleğin verimsiz kullanılması anlamına gelir. Tabii dinamik bellek yönetimiyle yer kaplama bakımından etkinlik problemi giderilebilir. Ancak işlemsel karmaşıklık artar, kod büyümesi oluşur. Ancak C++'ta yazı işlemleri için özel tasarlanmış string sınıfları kullanılabilir. Bu sınıflar yazıları dinamik olarak saklarlar. Dinamik bellek işlemleri üye fonksiyonlar tarafından otmatik olarak yapıldığı için algılsal düzeyde karmaşıklık oluşmaz. Bir sınıf kullanılarak yazılar üzerinde işlemler yapmak için MFC sınıf sisteminde Cstring isimli bir sınıf tasarlanmıştır. Yine son yıllarda ANSI C++ içerisine dahil edilen standart bir string sınıfı vardır. Standardizasyon konusundan problemleri olmasına karşın yazı işlemlerini yapan bir string sınıfı her türlü derleyici sisteminde ve sınıf kütüphanelerinde kullanılmak üzere hazır bulundurulmaktadır. Böyle bir sınıfın tasarımı için C++'ın pek çok konusundan faydalanılmaktadır. Kurs içerisinde çeşitli konular görülürken bu sınıf tekrar tekrar görülecektir.
String Sınıfın Tasarımı

String sınıfın en az iki private veri elemanı olmalıdır. Birincisi yazının başlangıç adresini tutan karakter türünden bir adrestir. Yazı için alan dinamik olarak tahsis edilerek başlangıç adresi bu göstericiye yerleştirilecektir. İkinci veri elemanı yazının uzunluğunu tutan int türünden bir değişken olabilir. Tabii aslında yazının sadece başlangıç adresinin bilinmesi uzunluğunun istenildiği zaman bulunabileceği anlamına gelmektedir. Ancak yazı uzunluğuna duyulan gereksinimin fazlalığı ve hızın yer kaplamaya göre tercih edilmesi böyle bir veri elemanının bulunmasını anlamlı hale getirmektedir. Böyle bir sınıftan ilk istenecek şey dinamik yapılan tahsisatların üye fonksiyonlar tarafından gizlice gerçekleştirilmesidir. Sınıfn başlangıç fonksiyonu new operatörüyle tahsisat yapabilir ve bitiş fonksiyonu bu tahsisatı serbest bırakabilir.

Bir Sınıfın Başka Bir Sınıf Türünden Veri Elemanına Sahip Olması Durumu

Bir sınıf başka bir sınıf türünden veri elemanına sahipse o sınıfın üye fonksiyonları içerisinde eleman olan sınıf nesnesine erişilebilir. Ancak o sınıf nesnesinin private ve protected bölümlerine erişilemez. Sınıfın başlangıç fonksiyonu bir üye fonksiyonuymuş gibi dışarıdan çağırılamaz, nesne yaratılırken otomatik olarak çağırılır. O halde bir sınıfın başka sınıf türünden veri elemanına sahip olması durumunda bu veri elemanına başlangıç fonksiyonu içerisine nasıl ilk değer atanacaktır? Bunu sağlamak için C++'ta eleman olan sınıfın başlangıç fonksiyonu eleman nesne için elemana sahip sınıfın başlangıç fonksiyonunun ana bloğunun başında gizlice çağırılır. Eğer ":" syntax'i ile bir belirleme yapılmamışsa eleman olan sınıfın default başlangıç fonksiyonu çağırılır. Eğer ":" ile veri elemanının ismi yazılıp parantez içerisine bir parametre listesi belirtilmişse, eleman b parametre listesine uygun başlangıç fonksiyonu ile ilk değe alır. X bir sınıf a ise başka bir sınıf türünden X sınıfının bir veri elemanı ise bu veri elemanı için istenilen bir başlangıç fonksiyonunun çağırılması aşağıdaki syntax ile yapılır.

X:(...) : a(..)
{
...
}

Bir parametre değişkeninin faaliyet alanı tanımlanma noktasından fonksiyonun sonuna kadar olan bölgedir. Yani elemana ilişkin ":" ifadesinde fonksiyon parametreleri doğrudan kullanılabilir.

/*-----claincla.cpp-----*/
#include <stdio.h>
#include <time.h>
#include <string.h>

class Date {
int day, month, year;
public:
Date(void);
Date(int d, int m, int y);
void Disp(void) const;
};

class Person {
char *name;
Date bdate;
public:
Person(const char *nm, int d, int m, int y);
void Disp(void) const;
~Person(void);
};
Date:ate(int d, int m, int y)
{
day = d;
month = m;
year = y;
}

Date:ate(void)
{
long t;

t = time(xxxx);
struct tm *p = localtime(&t);

day = p -> tm_mday;
month = p -> tm_mon + 1;
year = p -> tm_year + 1900;
}

void Date:isp(void) const
{
printf("%02d/%02d/%04d\n", day, month, year);
}

void Person:erson(const char *nm, int d, int m, int y) : bdate(d, m, y)
{
name = new char[strlen(nm) + 1];
strcpy(name, nm);
}

void Person:isp(void) const
{
printf("%s\n", name);
bdate.Disp();
}

Person::~Person(void)
{
delete[] name;
}

void main(void)
{
Person X("Ali Serçe", 10, 12, 1990);

X.Disp();
}
/*-------------------------*/

Başlangıç fonksiyonlarının çağırılma sıraları önce elemana ilişkin sınıfın başlangıç fonksiyonu sonra elemana sahip sınıfın başlangıç fonksiyonu biçimindedir. C++'ta her zaman başlangıç ve bitiş fonksiyonlarının çağırılıma sırası terstir. Bu durumda elemana ilişkin sınıfın bitiş fonksiyonu elemana sahip bitiş fonksiyon bloğunun sonunda otomatik olarak çağırılır. Yani elemana sahip sınıfın bitiş fonksiyonu daha önce çağırılmaktadır.
Bir sınıfın başka sınıf türünden birden fazla veri elemanı varsa bu veri elemanları için başlangıç fonksiyonları çağırılma sırası sınıf bildiriminde ilk yazılan önce olacak biçimde yapılır. ":" syntax'i ile belirtilen sıra öneli değildir.

class Y { ......... ........};class X { ......... ......... Y a, b;};X(....) : b(...), a(...) { ........ ........}
/*önce a'nın parametreleriyle belirtilen */ /*Y sınıfının başlangıç fonksiyonu çağırılır. */


Bitiş fonksiyonlarının çağırılma sırası başlangıç fonksiyonuna göre ters sırada olmak zorundadır. Yani örnekte önce b için bitiş fonksiyonu çağıılır.

Bir Sınıfın başka Bir Sınıf Türünden Gösterici Veri Elamanına Sahip Olması Durumu

Bu duruma diğer durumdan daha sıklıkla rastlanmaktadır. Tabii gösterici veri elemanı için başlangıç fonksiyonu çağırılmaz. Ancak elemana sahip sınıfın başlangıç fonksiyonu içerisinde bu gösterici için dinamik tahsisat yapılmalıdır. Elemana sahip bitiş fonksiyonu içerisinde de delete operatörüyle başlangıç fonksiyonunda tahsis edilen alan serbest bırakılmalıdır. Bu serbest bırakma ile bitiş fonksiyonu da kendiliğinden çağırılacaktır.

/*-----claincl2.cpp-----*/
#include <stdio.h>
#include <time.h>
#include <string.h>

class Date {
int day, month, year;
public:
Date(void);
Date(int d, int m, int y);
~Date(void);
void Disp(void) const;
};

class Person {
char *name;
Date *bdate;
public:
Person(const char *nm, int d, int m, int y);
void Disp(void) const;
~Person(void);
};

Date:ate(int d, int m, int y)
{
day = d;
month = m;
year = y;
}

Date:ate(void)
{
long t;

t = time(xxxx);
struct tm *p = localtime(&t);

day = p -> tm_mday;
month = p -> tm_mon + 1;
year = p -> tm_year + 1900;
}
void Date:isp(void) const
{
printf("%02d/%02d/%04d\n", day, month, year);
}

Date::~Date(void)
{
printf("I am a Date destructor..\n");

}

void Person:erson(const char *nm, int d, int m, int y)
{
name = new char[strlen(nm) + 1];
strcpy(name, nm);
bdate = new Date(d, m, y);
}
void Person:isp(void) const
{
printf("%s\n", name);
bdate -> Disp();
}

Person::~Person(void)
{
delete[] name;
delete bdate;
}

void main(void)
{
Person X("Ali Serçe", 1, 2, 1990);

X.Disp();
}
/*-------------------------*/

inline Fonksiyonlar

C++'ta bir fonksiyon tanımlarken geri dönüş değerinin önüne inline anahtar sözcüğü getirilebilir. Örneğin:

inline void fonk(void)
{
....
}

(inline anahtar sözcüğü prototipte değil tanımlama sırasında kullanılmalıdır.)

Bir inline fonksiyon tıpkı bir makro gibi işlem görür. Yani inline fonksiyon çağırıldığında fonksiyon çağırma kodu yerine fonksiyon kodunun kendisi yerleştirilir.

/*-----inline.cpp-----*/
#include <stdio.h>

inline double square(double x)
{
return x * x;
}

void main(void)
{
double n;
n = square(3);
printf("%lf\n", n);
}
/*----------------------*/

inline ile yapılmak istenen makro ile yapılmak istenenle aynıdır. Makro ön işlemci tarafından açılırken inline fonksiyonlar derleme modülü tarafından açılmak. inline fonksiyonlar ile makrolar aynı amaçlar için kullanılmalarına karşın inline fonksiyonlar makrolara göre çok daha kullanışlı ve sağlamdır. Örneğin: Makrolar ++ ve - operatörleriyle çağırılamadığı halde(şüpheli kod oluşabilir), inline fonksiyonlar güvenli bir biçimde çağırılabilir. Çünkü inline fonksiyonlarda önce parametre ifadesi değerlendirilir, sonra açma işlemi gerçekleştirilir. Tabii inline fonksiyonlar normal bir fonksiyon gibi yazılıp çağırılırlar. Operatörlerin önceliğinden oluşacak problemler derleyici tarafından zaten giderilirler. inline fonksiyon parametrelerini parantez içerisine almaya gerek yoktur. inline fonksiyonlar diğer fonksiyonlarda olduğu gibi tür dönüştürmesi ve tür bakımından derleyicinin denetiminden geçerler. Yani inline fonksiyonlarda error'ler ortaya çıkabilir, böylece kod açılmadan hatalar tespit edilir. Yani daha sağlamdır. Bu nedenlerden dolayı C++'ta neredeyse makro hiç kulanılmaz. Hep inline fonksiyonlar kullanılır.

Sınıfın normal türlerden veri elemanları da başlangıç fonksiyonunda ":" syntax'i ile ilk değer alabilirler. Örneğin:

Eşdeğeri
X:(void):a(10), b(20){ ....} X:(void){ a = 10; b = 20;}

Örnek bir sınıf:

CRect isimli sınıf MFC sınıf sistemi içerisinde kullanılan ve dörtgensel bölgeler üzerinde temel işleri yapmayı hedefleyen bir sınıftır. Sınıfın dörtgensel bölgeni sol üst ve sağ alt köşegenini belirleyen 4 veri elemanı vardır.

/*-----crect.cpp-----*/
#include <stdio.h>

class CRect {
int x1, y1, x2, y2;
public:
CRect(void);
CRect(int c1, int r1, int c2, int r2);
int Width(void) const;
int Height(void) const;
void InflateRect(int width, int height);
int IsEmpty(void) const;
void Disp(void) const;
};


CRect:Rect(void)
{
x1 = x2 = y1 = y2 = 0;
}

CRect:Rect(int c1, int r1, int c2, int r2)
{
x1 = c1;
y1 = r1;
x2 = c2;
y2 = r2;
}

int CRect:idth(void) const
{
return x2 - x1;
}

int CRect:eight(void) const
{
return y2 - y1;
}

void CRect:nflateRect(int width, int height)
{
x1 = x1 + width;
x2 = x2 - height;
y1 = y1 + width;
y2 = y2 - height;
}

int CRect:sEmpty(void) const
{
if(Width() && Height())
return 0;
return 1;
}

void CRect:isp(void) const
{
printf("[%d, %d; %d, %d]\n", x1, y1, x2, y2);
}

void main(void)
{
CRect a(5, 5, 20, 20);

a.Width();
a.Height();
a.Disp();
a.InflateRect(-6, 4);
a.Disp();
if(a.IsEmpty())
printf("boş işte\n");
else
printf("dolu\n");
}
/*---------------------*/
Sınıfın Veri Elemanlarına Erişim ve "this" Göstericisi

Bir sınıfın üye fonksiyonu bir sınıf nesnesiyle çağırıldığında aslında sınıf nesnesinin adresi gizlice üye fonksiyona geçirilir. Üye fonksiyon içerisinde sınıfın veri elemanlarına erişme bu gizlice geçirilmiş olan gösterici yluyla yapılır. Yani bir üye fonksiyonunun hiç parametresi yoksa aslında gizli bir parametresi vardır. Bu da çağırılan sınıf nesnesinin adresini tutan göstericidir. Sınıfın üye fonksiyonlarının, sınıfın veri elemanlarına erişmesi aslında adres yoluyla yapılmaktadır. Tabii kolaylık olması bakımından geçirilen bu gizli gösterici açıkça parametre listesinde görünmez. Erişim sırasında ok operatörü de kullanılmaz.

C++'ta üye fonksiyonunu çağırılması C'deki karşılığı
void Crect:isp(void) const{ printf("[%d, %d; %d, %d]\n", x1, y1, x2, y2);}void main(void){ CRect x(10, 10, 20, 20); x.Disp();} void Crect_Disp(const Crect *this){ printf("[%d, %d; %d, %d]\n", this -> x1, this -> y1, this -> x2, this ->y2);}void main(void){ CRect x = {10, 10, 20, 20}; Crect_Disp(&x);}

Üye fonksiyona gizlice geçirilen bu adres üye fonksiyon içerisinde açıkça "this" anahtar sözcüğüyle kullanılabilir. "this" parametre biçiminde programcı tarafından yazılamaz, ancak yazılmış gibi kullanılabilir. x bir sınıfın veri elemanı olmak üzere üye fonksiyon içerisinde x ile this -> x tamamen aynı anlamdadır. Bir üye fonksiyon başka bir üye fonksiyonu çağırıyorsa çağıran üye fonksiyona ilişkin this göstericisi doğrudan çağırılan fonksiyona geçirilir. "this" göstericisi global fonksiyonlarda kullanılmaz, sadece herhangi bir sınıfın üye fonksiyonu içerisinde kullanılabilir. "this" göstericisi hangi sınıfın üye fonksiyonunda kullanılırsa türü de o sınıf türünden gösterici olur. "this" gösterisinin değeri değiştirilemez. "this" göstericisi kendisi const olan const bir gösterici biçimindedir.

/*-----this.cpp-----*/
#include <stdio.h>

class X {
int a;
public:
void fonk(void);
};

void X::fonk(void)
{
printf("%p\n", this);
}

void main(void)
{
X n;
printf("%p\n", &n);
n.fonk();
}
/*-------------------*/
Sınıf İçi inline Fonksiyonlar

Sınıfın veri elemanları private ya da protected bölüme yerleştirilmişse bu veri elemanlarına çeşitli public üye fonksiyonlarıyla erişilir. Bu tür fonksiyonlara "Get ve Set" fonksiyonları denir. Sınıfın bu tür küçük fonksiyonları tipik inline fonksiyonu olarak yazılmalıdır. Bir sınıfın üye fonksiyonunun tanımlanması dışarıda değil sınıfın içerisinde de yapılabilir. Örneğin:

class X {
int a, b;
public:
X(int x, int y)
{
a = x;
b = y;
}
};

/*-----inline1.cpp-----*/
#include <stdio.h>
#include <conio.h>

class X {
int a;
public:
void fonk(void)
{
printf("%p\n", this);
}

};

void main(void)
{
X n;

printf("%p\n", &n);
n.fonk();
}
/*-----------------------*/

Bunun gibi sınıf bildirimi içinde tanımlanan fonksiyonlar otomatik olarak inline fonksiyon olarak kabul edilir. Sınıf içerisinde tanımlanmış fonksiyonların sırasının hiçbir önemi yoktur. Yani yukarıdaki fonksiyon aşağıdaki fonksiyonu çağırabilir. "inline" bildirimi (dışarıda ya da gizlice sınıf içinde) bir zorunluluk biçiminde değil bir istek biçimindedir. Yani derleyici bir inline fonksiyonu inline olarak açamayabilir. Eğer açamazsa onu normal bir fonksiyon gibi ele alır. Açamadığından dolayı herhangi bir error ya da warning mesajı oluşmaz(tıpkı register anahtar sözcüğünde olduğu gibi). Genel olarak uzun kod içeren, karmaşık döngü ve if değimleri içeren fonksiyonlar inline olarak açılamazlar.
Sınıfların Türetilmesi

Daha önce yazılmış olan bir sınıfa ekleme yapılması istendiğinde başvurulacak en iyi yöntem türetme işlemidir. Bir sınıfa ek yapmak sınıfa yeni veri ve üye fonksiyonu eklemek anlamındadır. Bir sınıfa ek yapmak için türetme dışında birkaç yöntem akla gelebilir:

1. Sınıfa doğrudan ekleme yapmak Bu durumda önceki sınıfın bir kopyası çıkartılır ve üzerinde eklemeler yapılırsa, gereksiz bir biçimde üye fonksiyon tekrarı yapılır.
2. Önceki sınıfı bir veri elemanı olarak kullanan yeni bir sınıf tanımlamak class A { //... }; class B { A a; //... }; Bu durumda veri elemanı public bölgeye yerleştirilirse genişletilecek sınıfın üye fonksiyonları bu nesne sayesinde çağırılır. Ancak bu yöntem de türetme yöntemine göre çeşitli kısıtlamaları olan bir yöntemdir.

Bir sınıfın işlevleri türetme yoluyla genişletilecekse türetmenin yapılavcağı sınıfa taban sınıf (base class), türetilmiş olan sınıfa da türemiş sınıf (derived class) denir. Şekilsel olarak türemiş sınıftan taban sınıfa bir ok olarak belirtilir.

Türetme işleminin genel biçimi:
Türetme biçimi

class <türemiş sınıf ismi>:[private/protected/public]<taban sınıf ismi> {

}

Örnek:

class A {
int a;
//...
};

class Bublic A {
int b;
//...
};

İki nokta üstüste ayıracından sonra isteğe bağlı olarak türetme biçimi yazılabilir. Yazılmazsa private yazılmış gibi işlem görür.
Türemiş sınıf türünden bir nesne tanımlandığında bu nesne hem taban sınıf veri elemanlarını hem de türemiş sınıf veri elemanlarını içerir.



/*-----turetme.cpp-----*/
#include <stdio.h>

class A {
public:
int a;
};

class Bublic A {
public:
int b;
};

void main(void)
{
B x;

printf("%d\n", sizeof(x));
}
/*------------------------*/

Türemiş Sınıf Nesnesinin Bellekteki Organizasyonu

Türemiş sınıf nesnesinin taban sınıf veri elemanları ve türemiş sınıfın kendi veri elemanları blok olarak ardışıl bir biçimde yerleştirilir. Ancak taban sınıf ve türemiş sınıf veri elemanlarının hangisinin daha düşük adres bölgesine yerleştirileceği ANSI standartlarında belirlenmemiştir, dolayısıyla derleyiciyi yazanlara bırakılmıştır.

A B
B A

Tabii derleyici organizasyonu hep aynı yapar. Yaygın kullanılan derleyicilerde taban sınıf veri elemanları düşük anlamlı adresi yerleştirilmektedir(Kursta bütün örneklerde önce taban sınıf veri elemanları düşük anlamlı adrese yerleştirilecektir).
Türemiş Sınıflarda Erişim Kuralı

Türemiş sınıflardaki erişim kuralı türetme biçimine bağlıdır.

Public Türetmesi

Public türetmesinde taban sınıfın public bölümü türemiş sınıfın public bölümüymüş gibi, taban sınıfın protected bölümü de türemiş sınıfın protected bölümüymüş gibi işlem görür. Taban sınıfın private bölümü erişime kapalıdır. Taban sınıfın private bölümüne türemiş sınıf tarafından erişilemez.

Taban Türemiş
private
protected protected
public public
Public Türetmesinden Çıkan Sonuçlar

1. Türemiş sınıf nesnesi yoluyla dışarıdan nokta ya da ok operatörü kullanılarak taban sınıfın public bölümüne erişilebilir. Ancak taban sınıfın protected ve private bölümüne erişilemez.
2. Türemiş sınıf üye fonkiyonları içerisinde taban sınıfın public ve protected bölümlerine doğrudan erişilebilir.ancak taban sınıfın private bölümüne erişilemez.

Protected Türetmesi

Bu türetme biçiminde taban sınıfın public ve protected bölümleri türemiş sınıfın protected bölümüymüş gibi işlem görür. Taban sınıfın private bölümü erişime kapalıdır. Türemiş sınıf tarafından erişilemez.

Taban Türemiş
private
protected protected
public

Protected Türetmesinden Çıkan Sonuçlar

1. Türemiş sınıf nesnesiyle dışarıdan nokta ya da ok operatörüyle taban sınıfın hiçbir bölümüne erişilemez.
2. Türemiş sınıf üye fonksiyonları içerisinde taban sınıfın public ve protected bölümlerine doğrudan erişilebilir. Ancak private bölümlerine erişilemez.

Private Türetmesi

Bu durumda taban sınıfın public ve protected bölümleri türemiş sınıfın private bölümüymüş gibi işlem görür. Taban sınıfın private bölümü erişime kapalıdır. Türemiş sınıf tarafından erişilemez.

Taban Türemiş
private
protected private
public

Private Türetmesinden Çıkan Sonuçlar

1. Türemiş sınıf nesnesi yoluyla dışarıdan nokta ya da ok operatörüyle taban sınıfın hiçbir bölümüne erişilemez.
2. Türemiş sınıf üye fonksiyonları içerisinde taban sınıfın public ve protected bölümlerine doğrudan erişilebilir. Ancak private bölümlerine erişilemez.
Türetme İşleminden Çıkan Ortak Sonuçlar

1. Türemiş sınıf nesnesi yoluyla dışarıdan nokta veya ok operatörü kullanılarak ancak taban sınıfın public bölümüne ancak public türetmesiyle erişilebilir. Hiçbir zaman dışarıdan taban sınıfın protected ya da private bölümüne erişilemez.
2. Türemiş sınıf üye fonksiyonları içerisinde türetme biçimi ne olursa olsun taban sınıfın public ve protected bölümlerine erişilebilir.
3. Taban sınıfın private bölümü tam olarak korunmuştur. Türemiş sınıf tarafından doğrudan hiçbir şekilde erişilemez.

En çok kullanılan türetme biçimi public türetmesidir.

Protected Bölümünün Anlamı

Protected bölüm dışarıdan doğrudan erişilemeyen, ancak türemiş sınıf üye fonksiyonları tarafından erişilebilen bir bölümdür. Protected bölümünün türetme dışında özel bir anlamı yoktur. Çünkü bu bölüm türemiş sınıfın çeşitli işlemlerini kolaylaştırmak için gereksinimlerini tutmakta kullanılır.

Private Bölüme Fonksiyonları Yerleştirilmesi

Bir sınıfın public bölümünde bulunan X() isimli fonksiyon işlemini gerçekleştirmek için işlemin belirli bölümlerini yapan, yani ara işlemleri yapan çeşitli fonksiyonlar kullanabilir. Bu ara işlemleri yapan fonksiyonların dışarıdan çağırılmasının hiçbir anlamı yoktur. Algıdan uzak tutmak amacıyla private bölüme yerleştirilebilirler. Sınıfı kullanacak kişi için private bölgenin incelenmesi gereksizdir.

Somut, Soyut ve Arabirim Sınıflar

Kendisinden başka bir sınıf türetilmeyecek biçimde tasarlanan sınıflara somut sınıflar (concreate cleass) denir. Somut sınıflar belirli bir konuda yararlı işlemleri yaparlar. Genellikle işlevlerinin genişletilmesi biçiminde bir istek söz konusu olmaz.
Soyut sınıflar (abstract class) kendi başına bir anlamı olmayan kendisinden türetme yapılarak kullanılması zorunlu olan sınıflardır. C++'ta soyut sınıflar derleyici tarafından belirli bir syntax içimiyle doğrudan desteklenirler.
Arabirim sınıflar (interface class) en çok rastlanan sınıflardır. Kendi başlarına yararlı işlemeleri yapabilen, ancak türetme işlemine de izin verebilecek biçimde tasarlanmış sınıflardır. Arabirim sınıfları tasarlayan kişi bunları türetme durumunu göz önüne alarak tasarlamalıdır.

Türetme İşlemine Birkaç Örnek



- Seri port işlemlerini yapan serial isimli bir sınıf olsun. Bu sınıfın üye fonksiyonları portu set etme, okuma ve yazma gibi temel işlemleri yapsın.
Modem seri porta bağlanarak kullanılan, ve iletişimi sağlayan bir araçtır. Modemi programlamak için seri porta bilgi göndermek gerekir. Modem işlemlerini yapmak için serial sınıfından türetme uygulanabilir. Benzer biçimde laplink kablosu kullanarak seri portlar arası bilgi transferi için laplink isimli ayrı bir sınıf türetilebilir.
- MFC sınıf sisteminde CWnd sınıfı genel olarak her türlü pencereyle ilgili işlem yapmak amacıyla kullanılır. Dialog pencereleri özel pencerelerdir. Yani bir pencerenin tüm özelliğini gösterir, ancak ek özelliklere de sahiptir. CDialog sınıfı CWnd sınıfından türetilmiştir.
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
PUBLİC







Ancak her dialog penceresi kendine özgü farklılıklara sahiptir. MFC'de ne zaman bir dialog penceresi açılacak olsa CDialog sınıfından bir sınıf türetilmeli ve dialog penceresi o sınıfla ilişkilendirilmelidir.


PUBLİC

PUBLİC




Bu durumda MyDialog sınıfına ilişkin bir nesne tanımlanırsa bu nesne yoluyla CWnd sınıfına ilişkin üye fonksiyonlar çağırıldığında; pencereye ilişkin temel işlemler, CDialog sınıfının üye fonksiyonları çağırıldığında dialog pencerelerine ilişkin genel işlemler ve nihayet MyDialog sınıfına ilişkin üye fonksiyonlar çağırıldığında kendi dialog penceremizle ilgili özel işlemler yapılacaktır.

Türemiş Sınıflarda Faaliyet Alanı

Sınıf faaliyet alanı bir sınıfın ve ondan türemiş olan sınıfların üye fonksiyonları arasında tanınabilme aralığıdır. Bir veri elemanı ya da üye fonksiyonu aynı isimle taban ve türemiş sınıflarda tanımlı olabilir. Bu durumda:
1. Türemiş sınıf üye fonksiyonu içerisinde ilgili isim doğrudan kullanılırsa dar faaliyet alanına sahip olan yani türemiş sınıfta tanılanmış olan anlaşılır. Eğer istenirse çözünürlük operatörüyle sınıf ismi belirtilerek (X::a = 10) taban sınıftaki isme erişilebilir.
2. Türemiş sınıf nesnesi yoluyla dışarıdan nokta ya da ok operatörüyle aynı isimli değişkene ya da fonksiyona erişildiğinde yine dar faaliyet alanına sahip olan türemiş sınıftaki isim anlaşılır. Ancak nokta ya da ok operatöründen sonra yine çözünürlük operatörü kullanılabilir (p->X::a).
3. Taban ve türemiş sınıf içerisinde aynı isimli fakat farklı parametre yapılarına sahip fonksiyonlar olsun. Eğer türemiş sınıfın başka birt üye fonksiyonunda aynı isimli fonksiyon çağırılmışsa bu fonksiyon yalnızca dar faaliyet alanında aranır. Yani parametre yapıları farklı bile olsa dar faaliyet alanındaki diğerini gizlemektedir. Aynı durum dışarıdan türemiş sınıf nesnesi yoluyla nokta ya da ok operatörüyle de söz konusu olur. Yani dar faaliyet alanındaki isim geniş faaliyet alanını gizleyerek bu ismin orada aranmamasını sağlar(Global fonksiyon çağırırken :: operatörü fonksiyon isminin önüne getirilir). Bir sınıfın üye fonksiyonu içerisinde bir fonksiyon çağırımış olsun. Derleyici bu ismi sırasıyla şuralarda arar(name lookup):
a. Üye fonksiyona ilişkin sınıf içerisinde,
b. Üye fonksiyonun taban sınıfları içerisinde,
c. Global faaliyet içerisinde aranır.

Bir üye fonksiyon içerisinde fonksiyon ismi, sınıf ismi ve çözünürlük operatörüyle çağırılmışsa isim sırasıyla şuralarda aranır:
a. İsim çözünürlük operatörüyle belirtilen sınıfta aranır.
b. Bulunamazsa çözünürlük operatörüyle belirtilen sınıfın taban sınıfınlarında aranır. Ancak global faaliyet alanında aranmaz.

Eğer unary çözünürlük operatörü ile çağırılmışsa :):fonk(); ) isim yalnızca global faaliyet alanında aranır. Bir sınıfın üye fonksiyonu içerisinde sınıfın ve taban sınıfların global fonksiyonla aynı isimli fonksiyonları olmasa bile çağırma işlemi okunabilirlik bakımından yine unary çözünürlük operatörü kullanılarak yapılmalıdır. Fonksiyonlar için belirtilen bu arama durumlarının hepsi normal değişkenler için de geçerlidir.

Türemiş Sınıfın Taban Sınıfı Çağırması İşlemleri

Normal olarak türemiş sınıf taban sınıfa erişebilir. Yani türemiş sınıf nesnesiyle ya da türemiş sınıf üye fonksiyonları içerisinde taban sınıf nesnesi ya da fonksiyonları kullanılabilir. ancak tersi durum mümkün değildir. Yani taban sınıf türemiş sınıfa erişemez. Taban sınıf derlenirken derleyici bundan bir sınıf türetileceğini bilmek zorunda değildir. Bu durumda türemiş sınıf nesnesi ile taban sınıf üye fonksiyonu çağırıldığında derleyici bu fonksiyona this göstericisi olarak türemiş sınıf nesnesinin taban kısmının adrsini geçirir. Uygulamada taban sınıf veri elemanları daha düşük adrese yerleştirildiğinden geçirilen adres nesnenin bütünsel başlangıç adresiyle aynı olur.

B x;
x.fonka();


Burada A taban sınıfının fonka üye fonksiyonuna this göstericisiyle 1FC0 adresi geçirilmektedir. Türemiş sınıf nesnesiyle türemiş sınıfın üye fonksiyonu çağırıldığında yine taban sınıf veri elemanlarının bulunduğu toplam veri adresi geçirilir. Çünkü türemiş sınıf üye fonksiyonu içerisinde taban sınıf üye fonksiyonu çağırıldığında derleyici taban sınıf veri bloğunun adresini tespit edebilmek zorundadır.

Bir Dizi Türetme Yapılması Durumu

Tüeretme işlemi birden fazla yapılabilir Bu durumda yukarıdaki sınıflar aynı kurallarla aşşağıdan erişilebilir. Bir sınıfın bir dizi türetme yapıldığında bir üsteki taban sınıfına doğrudan tan sınıf (direct base class) sonraki taban sınıflarına ise dolaylı taban sınıf (indirect base class) denir. Bir sınıf birden fazla sınofa taban sınıf olabilir. Bu durum çatışmaya yol açmayacak biçimde açık ve anlaşılır bir türetme durumudur.







Ancak bir sınıffın birden fazla taban sınıfa sahip olması ayrı bir bölümde incelenecek kadar karmaşık bir konudur. Bu duruma çoklu türetme denir. Çoklu türetme diğer bilgisayar dillerinde desteklenmemektedir.







Taban Sınıf Türünden Göstericilere Türemiş Sınıf Nesnelerinin Adreslerinin Atanması

C++'ta bir göstericiye farklı türden bir göstericinin atanması uyarı değil error gerektirir. Ancak istisna olarak taban sınıf türünden bir göstericiye türemiş sınıf türünden bir nesnenin adresi atanabilir. Ancak bunun tersi olan durum, yani türemiş sınıf türünden bir göstericiye taban sınıf türünden bir nesnenin adresinin atanması durumu yararlı ve geçerli bir işlem değildir.

class A {
..
..
};

class Bublic A {
..
..
};

void main(void)
{
A *p; /*Taban sınıf türünden bir gösterici*/
B x; /*Türemiş sınıf türünden nesne*/
A y; /*Taban sınıf türünden bir nesne*/
B *t; /*Türemiş sınıf türünden bir gösterici*/

p = &x; /*Geçerli*/
t = &y; /*Geçersiz*/
}

Türemiş sınıf nesnesi taban sınıf veri elemanlarını içerdiğine göre türemiş sınıf nesnesinin adresi taban sınıf göstericisine geçirildiğinde problemli ya da güvensiz bir durum oluşmaz. Derleyici bu durumda türemiş sınıf nesnesinin taban sınıf veri elemalarının adresini göstericiye atar. Taban sınıf göstericisiyle ancak taban sınıf üye fonksiyonları çağırılabileceğine göre bu göstericiyle tahsis edilmiş bir alana erişilir, yani uygunsuz bir durum oluşmaz. Mademki taban sınıf ve türemiş sınıf veri elemanlarının yerleşimi kesin bir standartla belirlenmemiştir, böyle bir atamada derleyici türemiş sınıf nesnesinin taban sınıf elemanlarının başlangıç adresini tespit edip o adresi atamalıdır. Tabii genellikle taban sınıf veri elemanları düşük anlamlı adrese yerleştirildiğinden geçirilen adres de nesnenin bütününün başlangıç adresi olur.

/*-----tabangos.cpp-----*/
#include <stdio.h>

class A {
int a;
public:
A(int x = 0)
{
a = x;
}
void DispA(void);
void SetA(int x)
{
a = x;
}
};

class B : public A {
int b;
public:
B(int x = 0, int y = 0): A(y)
{
b = x;
}
void DispB(void);
void SetB(int x)
{
b = x;
}
};

void A:ispA(void)
{
printf("%d\n", a);
}

void B:ispB(void)
{
printf("%d\n", b);
}

void main(void)
{
A *p;
B x(10, 20);

p = &x;
p -> DispA();
p -> SetA(50);
p -> DispA();
}
/*------------------------*/

Örnek B'den, B A'dan türemiş sınıflardır:

{
B *p;
C n;

p = &n; /*Geçerlidir, p'ye A'nın başlangıç adresi, geçirilir*/
}

Türemiş Sınıf Nesnesinin Adresinin Taban Sınıf Göstericisine Atanması İşleminin Program İçerisinde Karşılaşılabilen Çeşitli Durumları

Türemiş sınıf nesnesinin adresi açık ya da gizli çeşitli kodlarla taban sınıf göstericisine geçirilebilmektedir.

1. Açık bir biçimde yapılan atamalarla:

Yani taban sınıf türünden bir gösterici tanımlanır, türemiş sınıf türünden bir nesnenin adresi atanır.

{
A *p; /*Taban sınıf türünden bir gösterici*/
B x; /*Türemiş sınıf türünden nesne*/
p = &x; /*Geçerli*/
}

2. Fonksiyon çağırma yoluyla ve gösterici kullanılarak:

Bu durumda fonksiyonun parametre değişkeni taban sınıf türünden bir göstericidir. Fonksiyon türemiş sınıf nesnesinin adresiyle çağırılır. Bu biçimde atama işlemi en sık rastlanan atama işlemidir.

void fonk(A *p)
{
p -> DispA();
}

void main(void)
{
B n(10, 20);

fonk(&n);
}

3. Açıkça ve referans kullanılarak yapılan atamalar:

Taban sınıfa ilişkin bir referans türemiş bir sınıfa ilişkin bir nesneyle ilk değer verilerek tanımlanabilir. Derleyici bu durumda türemiş sınıf nesnesinin taban sınıf veri eleman bloğunun adresini taban sınıf referansına atar.

{
B n(10, 20);
A &r = n; /*Geçerli*/

r.Disp(); /*Geçerli*/
}

4. Fonksiyon çağırma yoluyla ve referans kullanılarak:

Bu durumda fonksiyonun parametre değişkeni taban sınıf türünden bir referanstır. Fonksiyon da türemiş sınıf nesnesinin kendisiyle çağırılır.

void fonk(A &p)
{
p.DispA();
}

void main(void)
{
B n(10, 20);

fonk(n);
}



Taban Sınıf Göstericisine Türemiş Sınıf Nesnesin Adresinin Atanmasının Faydaları

Bir dizi türetme söz konusu olduğunda türetme içerisindeki her sınıfın ortak veri elemanları söz konusu olabilir. Örneğin şöyle bir türetme söz konusu olsun:



(D B'den, E C'den, B ve C de A'dan türemiş sınıflar olsun). Örneğin burada türetilmiş sınıfların hepsi A sınıfın içermektedir. Yani bu sınıfların hepsi A sınıf gibi de davranabilmektedir. Burada A veri elemanları üzerine genel işlemler yapan bir fonksiyon söz konusu olsun:

void fonk(A *p)
{


}

Türemiş sınıfların herhangi birine ilişkin nesnenin adresiyle bu fonksiyon çağırılabilir. Yani böylece türden bağımsız olarak işlem yapabilen fonksiyonlar yazılabilmektedir.

Taban Sınıf Göstericisine Türemiş Sınıf Nesnesinin Atanmasına İlişkin Birkaç Örnek

1. Bir işletmede çalışan kişiler gruplanarak bir sınıf ile temsil edilmiş olsun.



Çalışan hangi gruptan olursa olsun onun genel özellikleri üzerinde işlem yapan ProcessPersonalInfo() fonksiyonu olsun:

ProcessPersonalInfo(Employee *p);

Bu fonksiyona hangi sınıf türünden nesne verirsek verelim o sınıfın genel çalışan özellikleri üzerinde işlemler yapılabilir.

{
Manager x(....);
Salesperson y(...);

ProcessPersonalInfo(&x);
ProcessPersonalInfo(&y);
}

2. MFC sınıf sisteminde her türlü pencere üzerinde işlem yapabilen bir CWnd sınıf vardır.

Editbox, puchbutton ve dialog pencereleri de bir çeşit penceredir. Bu özel pencereler üzerinde işlem yapabilen CWnd üzerinden türetilmiş ayrı sınıflar vardır.



Pencerenin türü ne olursa olsun, onun genel pencere özelliği üzerinde işlemler yapan global bir ProcessWnd() fonksiyonu yazıldığını düşünelim. Bu fonksiyon her türlü pencere üzerinde işlem yapabilecektir.

void ProcessWnd(CWnd *p)
{

}

{
CButton button;
CDialog dialog;

ProcessWnd(&button);
ProcessWnd(&dialog);
}

Sınıfın Static Veri Elemanları

Veri elemanları sınıf içerisinde static anahtar sözcüğüyle bildirilebilir. Böyle bildirilmiş static veri elemanları sınıfın kendi veri elemanlarına dahil edilmez.

/*-----static.cpp-----*/
#include <stdio.h>

class X {
static int x;
int a;
public:
void fonk(void) { }
};

void main(void)
{
X n;

printf("%d\n", sizeof(X)); /*2*/
}
/*--------------------*/

Sınıfın static veri elemanları aslında bir çeşit global değişkendir, yalnızca sınıf ile ilişkilendirilmiştir. Yani adeta yalnızca sınıfın erişebildiği global bir değişkendir. Bunlar C'ce normal global değişkenler gibidirler, yani static ömürlüdürler. Sınıfın static veri elemanlarından bir tane bulunur. Bu elemana bir sınıf elemanıymış gibi erişilir.

/*-----static1.cpp-----*/
#include <stdio.h>

class X {
public:
static int x;
int a;
void fonk(void) { }
};

int X:;

void main(void)
{
X n;
X z;

n.x = 50;
printf("%d\n", z.x); /*50*/
}
/*---------------------*/

Sınıfın static veri elemanı ayrıca dışarıda global bir biçimde tanımlanmak zorundadır. Bu tanımlama veri elemanı sınıfın hani bölgesinde olursa olsun tanımlanmak zorundadır. Sınıfın static veri elemanı normal bir veri elemanıymış gibi erişim kuralına uyar.

/*-----static2.cpp-----*/
#include <stdio.h>

class X {
private:
static int x;
int a;
public:
X(int r)
{
a = r;
++x;
}
int GetCount(void)
{
return x;
}
};

int X: = 0;

void main(void)
{
X a(10);
X b(20);

printf("%d\n", b.GetCount());
}
/*---------------------*/

Sınıfın static veri elemanına hangi sınıf nesnesiyle erişildiğinin hiçbir önemi yoktur.Bu nedenle sınıfın static veri elemanına doğrudan sınıf nesnesi olmadan sınıf ismi ve çözünürlük operatörüyle de erişilebilir.

Sınıf_ismi::static_veri_elemanı_ismi

Tabii bu erişimin geçerli olabilmesi için veri elemanının public bölgede olması gerekir. Genellikle sınıfın static veri elemanı public bölgeye yerleştirilir ve dışarıdan bu biçimde erişilir.
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
Static Veri Elemanları Neden Kullanılır?

Bazen bir sınıf global değişkene gereksinim duyabilir. Ama o global değişken yalnızca o sınıf için anlamlı olabilir. Eğer bu değişken sınıfın static veri elemanı yapılırsa yalnızca bir sınıfla ilişkilendirilmiş olur. Algılama iyileştirilir.
Sınıfın static veri elemanı bir dizi biçiminde olabilir. Örneğin tarihlerin yazdırılması için kullanılacak, ayların isimlerini tutacak gösterici dizisi global yerine sınıfın static veri elemanı biçiminde alınabilir. Böylece hem her sınıf nesnesi içierisinde ayrıca yer kaplamaz, hem de sınıfa ilişkilendirilmiş olur.

/*-----static4.cpp-----*/
class Date {
private:
static char *mon[12];
int day, month, year;
public:
/* ....
....
....
*/ void Disp(void);
};

char * Date::mon[12] = {"Ocak", "Şubat", "Mart", "Nisan",
"Mayıs", "Haziran", "Temmuz", "Ağustos",
"Eylül", "Ekim", "Kasım", "Aralık"};
/*---------------------*/

Sınıfın Static Üye Fonksiyonları

Sınıfın static üye fonksiyonları normal bir üye fonksiyonu gibidir. Ancak bu fonksiyonlara this göstericisi geçirilmez, yani bu fonksiyonlar içerisinden sınıfın veri elemanlarına erişilemez. Yani adeta sınıfla ilişkilendirilmiş global bir fonksiyon gibidirler. Yani sınıfın veri elemanlarını kullanmayan ancak mantıksal olarak sınıfa ilişkili olan global fonksiyonlar sınıfın static fonksiyonu yapılabilir. Sınıfın static üye fonksiyonuna this göstericisi geçirilmediğine göre bu fonksiyonun da özel olarak bir nesne ile çağırılmasının anlamı kalmaz. Sınıfın static üye fonksiyonu sınıf ismi belirtilerek çözünürlük operatörüyle doğrudan çağırılabilir. Tabii erişim kuralı bakımından public bölgede bildirilmiş olması gerekir. Sınıfın static üye fonksiyonu içerisinde sınıfın static olmayan bir üye fonksiyonu çağırılamaz, static olmayan bir veri elemanı kullanılamaz. Ama sınıfın static veri elemanları kullanılabilir ve static üye fonksiyonları çağırılabilir. Farklı sınıfların aynı isimli static üye fonksiyonları ya da veri elemanları bulunabilir.

/*-----static5.cpp-----*/
#include <stdio.h>

class X {
private:
int a;
static int b;
public:
X(int n)
{
a = n;
++b;
}
void Disp(void);
static void Fonk(void);
};

int X::b = 0;

void X:isp(void)
{
printf("%d\n", a);
}

void X::Fonk(void)
{
printf("%d\n" ,b);
}

void main(void)
{
X n1(50);
X n2(60);

X::Fonk();
}
/*---------------------*/
Arkadaş Fonksiyonlar

Global bir fonksiyonu bir sııfın arkadaş fonksiyonu yapabilmek için prototipinin önüne friend sözcüğü getirilmelidir. Arkadaş fonksiyonlar dışarıda normal global fonsiyonlardır. Ancak erişim bakımından ayrıcalıklı fonksiyonlardır. Arkadaş fonksiyonlar erişim bakımından ayrıcalıklıdır. Bir arkadaş fonksiyon içerisinde arkadaş olunan sınıfa ilişkin bir nesne tanımlanırsa o nesne yoluyla sınıfın her yerine erişilir. Bir fonksiyon birden fazla sınıfın arkadaş fonksiyonu olabilir. Arkadaş fonksiyon bildirimi sınıfın herhangibir bölümünde yapılabilir. Hangi bölümde yapıldığının hiçbir etkisi yoktur. Arkadaş fonksiyonlar sınıfın private ve protected bölümlerinin korunmasını zayıflatır. Yani sınıfın veri yapısı değiştirildiğinde bu fonksiyonların da yeniden yazılması gerekir.

/*-----static6.cpp-----*/
#include <stdio.h>

class X {
int a;
public:
X(int n)
{
a = n;
}
void Disp(void) const;
friend void fonk(void);
};

void X:isp(void) const
{
printf("%d\n", a);
}

void fonk(void)
{
X n(20);

printf("%d\n", n.a);
}

void main(void)
{
fonk();
}
/*-------------------------*/

Başka bir sınıfın bi üye fonksiyonu da bir sınıfın arkadaş fonksiyonu yapılabilir.

friend Y::sample(void);

Fonksiyonun parametre parantezi de fonksiyona dahildir. Yani genellikle arkadaş fonksiyonu parametresi arkadaş olunan sınıfına ilişkin bir gösterici ya da referans olur, fonksiyon içerisinde bu sınıfın her bölümüne erişilir.

class X {
private:
int a;
public:
friend void fonk(const X *p);
};

void fonk(const X *p)
{
printf("%d\n", p->a);
}

void main(void)
{
X n(20);

fonk(&n);
}

Arkadaş Fonksiyonlar Ne Zaman Kullanılır?

Bazı tasarımlarda az sayıda fonksiyon sınıfın private bölümü üzerinde yoğun işlemler yapıyor olabilir. Bu az sayıda fonksiyon için arabirim public üye fonksiyonlar yazmak etkin görünmeyebilir. İşte bu durumlarda arkadaş fonksiyonlarla erişim kuralı bozulabilir. Tabii arkadaş fonksiyonların aşırı ölçüde kullanılması korunmayı azaltarak private bölgeyi anlamasız hale getirebilir. Friend fonksiyon bildirimi global fonksiyonlar için prototip yerine de geçer.

Arkadaş Sınıflar

Bir sınıf bütün olarak arkadaş sınıf olarak bildirilebilir.

class X {
int a;
public:
X(int n) { a = n];
friend class Y;
};

Bu durumda sınıfın bütün elemanları arkadaş kabul edilir.Yani o sınıfın tüm üye fonksiyonları içerisinde arkadaş olunan sınıfa ilişkin nesne tanımlanırsa o nesne yoluyla sınıfın her tarafına erişilebilir.

class Node {
int data;
Node *next;
friend class LList;
};

class LList {
Node *head;
public:
void Add(int d);
void Delete(void);
};
Değişkenler ve Sınıf Faaliyet Alanı

C'de ve C++'ta aslında yalnızca nesnelerin değil her türden değişkenlerin faaliyet alanı vardır. Örneğin bir blok içerisinde bir typedef ismi bildirilirse o typdef ismi o bloğun dışından kullanılamaz. Bir sınıf içerisinde typedef, enum, struct, başka bir sınıf ya da union bildirimi yapılabilir. Bu bildirimere ilişkin isimlere ancak o sınıfların üye fonksiyonları içerisinden doğrudan erişilebilir(yani sınıf ismi belirtmeden). Ancak bu isimler sınıfn public bölümündeyse sınıf ismi ve çözünürlük operatörüyle erişim sağlanabilir. Örneğin:

class X {
public:
typedef int *PINT;
...
...
...
};

void main(void)
{
PINT n; /*Error*/
X:INT n; /*Doğru kullanım*/
}

Bir sınıfın içerisinde başka bir sınıf ya da yapı bildirimi de yapılabilir. Bu durumda içerde bildirilmiş olan sınıf yalnızca dışarıda bildirilmiş sınıf içerisinden doğrudan kulanılabilir. Örneğin:

class X {
public:
class Y {
...
...
};
...
...
};

void main(void)
{
Y n; /*Error*/
X: n; /*Doğru kullanım*/
}

Bir sınıf bir sınıfı içeriyorsa aralarında hiçbir veri elemanı içerme gibi bir durum yoktur(yani C'deki iç içe yapılarda olduğu gibi değildir). Aslında bu iki sınıf tamamen birbirlerinden farklı bağımsız olarak dışarıda bildirilebilecek iki sınıftır. İçeride bildirilmiş sınıf yalnızca dışarıdaki sınıf içerisinde isim bakımından doğrudan kullanılabilir.
Sanal Fonksiyonlar

Taban sınıf ve türemiş sınıflarda aynı isimli fonksiyonlar varsa, çağırma çözünürlük operatörüyle yapılmamışsa, eğer taban sınıf nesnesi ya da göstericisine ilişkin bir çağırma söz konusuysa taban sınıfın fonksiyonu doğal olarak çağırılacaktır.
Sınıfın bir üye fonksiyonu sanal fonksiyon yapılabilir. Bir üye fonksiyonu sanal fonksiyonu yapabilmek için fonksiyon prototipinin önüne virtual anahtar sözcüğü getirilir. virtual anahtar sözcüğü sadece prototip bildirirken kullanılır, fonksiyon tanımlanırken kullanılmaz. Bir üye fonksiyon sanal yapılırsa o sınıfın türemiş sınıflarında bulunan aynı isimli , aynı prototipe sahip tüm fonksiyonlar da sanal olur. Yani virtual anahtar sözcüğü yazılmasa da yazılmış gibi işlem görür. Aynı prototipe sahip olması demek geri dönüş değerlerinin parametre yapılarının ve fonksiyon isimlerinin aynı olması demektir(const'luk dahil).
Türemiş sınıf nesnesinin adresi taban sınıf göstericisine atanır ve bu gösterici yoluyla sanal fonksiyon çağırılırsa adresi alınan nesne hangi sınıfa aitse o sınıfın sanal fonksiyonu çağırılır. Taban sınıfın türemiş sınıfa erişmesi ancak bu koşullarda mümkün olmaktadır. Bir dizi türetme yapıldığında türemiş sınıflardan birine ilişkin nesnenin adresi taban sınıflardan birine ilişkin göstericiye geçirilebilir ve bu gösterici yoluyla sanal fonksiyon çağırılabilir. Sınıf ismi belirtilerek sanal fonksiyon çağırılırsa sanallık özelliği kalmaz.

/*-----virtual.cpp-----*/

Sanal Fonksiyonların Program İçerisindeki Çağırılma Biçimleri

Program içerisinde sanal fonksiyon şu biçimlerde çağırılabilir:

1. Türemiş sınıf nesnesinin adresinin açık bir biçimde taban sınıf nesnesine atanması yoluyla

void main(void)
{
B z(10, 20);
A *p;

p = &z;
p -> Disp();
}

2. Fonksiyonun parametre değişkeni taban sınıf türünden bir göstericidir. Fonksiyon da türemiş sınıf nesnesinin adresiyle çağırılır. Bu gösterici yoluyla sanal fonksiyon çağırılabilir.

void fonk(A *p)
{
p -> Disp();
}

void main(void)
{
B n(10, 20);

fonk(&n);
}

3. Taban sınıf türünden bir referans türemiş sınıf türünden nesneyle ilk değer verilerek tanımlanır. Bu referans yoluyla sanal fonksiyon çağırılabilir. Bu durumda türemiş sınıfa ilişkin sanal fonksiyon çağırılacaktır.

void main(void)
{
B n(10, 20);
A &r = n;

r.Disp();
}

4. Fonksiyonun parametre değişkeni taban sınıf türünden bir referans olur. Fonksiyon da türemiş sınıfın nesnesinin kendisiyle çağırılır. Fonksiyon içerisinde bu referans yoluyla türemiş sınıfa ilişkin sanal fonksiyon çağırılır.

void fonk(A &r)
{
r.Disp();
}

void main(void)
{
B n(10, 20);

fonk(n);
}

5. Türemiş sınıf türünden bir nesne ile taban sınıfa ilişkin bir üye fonksiyonu çağırılırsa, bu üye fonksiyon içerisinde de sanal fonksiyon çağırılırsa üye fonksiyon hangi sınıfa ilişkin nesne ile çağırılmışsa o sınıfa ilişkin sanal fonksiyon çağırılır.

void A::fonk(void)
{
Disp();
}

void main(void)
{
B n(10, 20);

n.fonk();
}

Tabii üye fonksiyon içerisinde çağırma işlemi çözünürlük operatörü ve sınıf ismi beliritilerek yapılırsa sanallık özelliği kalmaz.

Taban sınıf sanal fonksiyona sahip olduğu halde türemiş sınıf sanal fonksiyona sahip olmayabilir. Yani türemiş sınıf için sanal fonksiyonun tanımlanması zorunlu değildir. Sanal fonksiyona sahip olmayan türemiş sınıfa ilişkin bir sınıf nesnesinin adresi taban sınıf göstericisine atanır ve bu gösterici yoluyla sanal fonksiyon çağırılırsa türemiş sınıfın sanal fonksiyona sahip ilk taban sınıfının sanal fonksiyonu çağırılır.
Sanal fonksiyon çağırılabilmesi için türetme biçiminin public olması gerekir. Bir sanal fonksiyon çağırıldığında gerçekte çağırılacak olan türemiş sınıfın sanal fonksiyonu sınıfın herhangi bir bölümünde olabilir. Ancak çağırılma ifadesindeki nesneye ilişkin sınıfın sanal fonksiyonu public bölümde olmak zorundadır. Örneğin :

A *p;
C n;
p = &n;
p -> fonk();

Burada fonk sanal bir fonksiyon olsun, gerçekte çağırılacak olan fonk C sınıfının fonksiyonudur. C sınıfının fonk sanal fonksiyonu sınıfın herhangi bir bölümünde bildirilmiş olabilir, ancak çağırmanın mümkün olabilmesi için A sınıfının fonk sanal fonksiyonunun public bölümde bildirilmiş olması gerekir.

Sanal Fonksiyon Çağırmanın Nedenleri

Sanal fonksiyon çağırmanın iki faydalı nedeni vardır:

1. Bir sınıfın işlevini değiştirmek,
2. Türden bağımsız işlemler yapılmasına olanak sağlamak.

Örneğin A gibi bir sınıf varsa, bu sınıf belirli işlemleri yapıyorsa, bu sınıfa hiç dokunmadan sınıfın yaptığı işlemler üzerinde değişiklik yapılması sağlanabilir.

Sanal Fonksiyon Kullanılmasına İlişkin Örnekler

1. İngilizce yazılar üzerinde işlem yapan bir CString sınıfı olsun. Bu sınıfın yazıları karşılaştıran, büyük harf ya da küçük harfe dönüştüren üye fonksiyonları olsun. Yazıların karşılaştırılması ve harf dönüşümünün yapılması dile bağlı bir durumdur. CString sınıfının Cmp isimli karşılaştırma fonksiyonu işlemini yaparken iki karakteri karşılaştıran sanal Cmphr fonksionunu çağırıyor olsun. CString sınıfından bir sınıf türetilir, CmpChr sanal fonksiyonu bu sınıf için yeniden yazılırsa, artık Cmp fonksiyonu türemiş fonksiyonunun CmpChr fonksiyonunu çağıracak ve işlemler başka bir dile göre düzgün yapılabilecektir.

2. Bir dizinin Array isimli bir sınıfla temsil edildiğini düşünelim. Bu sınıfın sıraya dizme işlemini yapan sanal bir sort fonksiyonu olsun. Bazı üye fonksiyonlar da bu fonksiyonu çağrarak sort işlemini gerçekleştirsin. Sort algoritması çok çeşitli olabilir. Bu sınıftan bir sınıf türetilerek bu sanal fonksiyon başka bir sort algoritmasını kullancak biçimde yeniden yazılabilir. Bu durumda bizim istediğimiz sort algoritmasıyla işlemler yürütülecektir.

3. MFC sınıf sisteminde her türlü pencere işlemleri CWnd sınıfı tarafından yapılmaktadır. Dialog penceresi de özel bir tür penceredir. Dialog penceresi işlemleri CWnd sınıfından türetilen CDialog sınıfı ile yapılmaktadır. Her dialog penceresi diğerinden farklı özelliklere sahip olabilir. O işlemler de CDailog sınıfından türetilen sınıfla temsil edilir.



Dialog penceresini görünür hale getirmek için CDialog sınıfının DoModal() fonksiyonu çağırılır. CDialog sınıfının OnOk ve OnCancel sanal fonksiyonları vardır. CWnd sınıfından türetilen bir sınıfa ilişkin bir nesne tanımlandığında CWnd sınıfının başlangıç fonksiyonu ile yaratılan nesnenin adresi MFC sistemi tarafından global bir biçimde saklanır. Ne zaman bir dialog penceresinde OK ya da CANCEL tuşlarına basılırsa MFC saklamış olduğu adresle OnOk ya da OnCancel fonksiyonlarını çağırır. Eğer biz bu fonksiyonları yeniden yazarsak bizimki çağırılacaktır. Tabii orijinal OnOk ve OnCancel fonksiyonları kritik bazı işlemleri de yapmaktadır. Bu durumda bu fonksiyonların doğrıdan çağırmaları da gerekebilir.

void MyDialog:nOK(void)
{
....
....
CDialog:nOK();
}

Sanal Fonksiyonların Mekanizmasının Kurulması

Bir türemiş sınıf nesnesinin adresi taban sınıf göstericileriyel dolaştırılmış olabilir ve en sonunda sanal fonksiyon çağırılsa bile nesnenin orijinaline ilişkin sanal fonksiyon çağırılacaktır. peki derleyici bu olayı derleme zamanı sırasında belirleyebilir mi? Bu olayın derleme sırasında tespit edilmesi mümkün değildir. Gerçekte hangi sanal fonksiyonun çağırlıacağını belirlemek ancak runtime sırasında kurulacak bir mekanizmayla mümkün olabilir. Bu mekanizmanın runtime sırasında kurulmasına ingilizce late binding denilmektedir.

class A {
public:
virtual void fonk1(void);
virtual void fonk2(void);
};

class B : public A {
public:
virtual void fonk1(void);
virtual void fonk2(void);
};

class C : public B {
virtual void fonk1(void);
virtual void fonk2(void);
};

void sample(B *p)
{
sample2(p);
}

void sample2(A *p)
{
p -> fonk1();
}

void main(void)
{
C x;
B *t;

t = &x;
sample(t);/*C'nin sanal fonksiyonu çağırılacak*/
}

A Sınıfının Sanal Fonksiyon Tablosu
Sıra No Adres
1 &A::fonk1()
2 &A::fonk2()


B Sınıfının Sanal Fonksiyon Tablosu
Sıra No Adres
1 &B::fonk1()
2 &B::fonk2()


C Sınıfının Sanal Fonksiyon Tablosu
Sıra No Adres
1 &C::fonk1()
2 &C::fonk2()


Derleyici bu mekanizmayı kurabilmek için her sınıfa ilişkin bir sanal fonksiyon tablosu yaratır. Bu sanal fonksiyon tablolarında ilgili sınıfın sanal fonksiyonlarının adresleri bulunur. Sanal fonksiyona sahip bir sınıfa ilişkin bir nesne tanımlandığında o nesne için bir gizli gösterici kadar daha fazla yer ayrılır. Bu gizli göstericide nesne hangi sınıfa ilişkinse o sınıfa ilişkin sanal fonksiyona ilişkin tablonun adresi tutulur. Bu gizli göstericinin nesnenin neresinde tutulduğu standart olarak belirlenmemiştir. Ancak nesnenin en düşük anlamlı adreslerinde genel olarak tutulmaktadır. Bu durumda bir sanal fonksiyon çağırıldığında aşağı seviyeli şu işlemler yapılır:

1. Sanal fonksiyon tablo göstericisi alınır ve tablonun yeri bulunur.
2. Sanal fonksiyon tablosunda ilgili sanal fonksiyonun adresi bulunur.
3. Sanal fonksiyon çağırılır.

Sanal fonksiyon çağırmanın yer ve zaman maliyeti söz konusudur. Çünkü sanal fonksiyon tabloları ve nesne içerisindeki sanal fonksiyon tablo göstericisi ek yer kaplamaktadır. Ayrıca tablolara bakarak sanal fonksiyon çağırıldığı için ek makina komutlarına gereksinim duyulur. Bu da zaman kaybına sebep olur.
Operatör Fonksiyoları

C'de iki yapı değişkeni birbirleriyle aritmetik işlemlere ve karılaştırma işlemlerine sokulamaz. Ancak aynı türden iki yapı değişkeni birbirlerine atanabilir. C'de olmayan bir veri türü üzerinde işlem yapabilmek için o veri türü önce bir yapı ile temsil edilir. Sonra işlemler yapan arabirim fonksiyonlar yazılır.

typedef struct _COMPLEX {
double real, image;
} COMLEX;

void AddComp(COMPLEX *p1, COMPLEX *p2, COMPLEX *result);

COMPLEX a = {3, 4}, b = {8, 2}, c;

AddComp(&a, &b, &c);

C++'ta operatör fonksiyonları ya bir sınıfın üye fonksiyonu biçiminde yapılır, ya da global fonksiyon biçiminde olur.

Üye Fonksiyon Biçiminde Tanımlanan Operatör Fonksiyonları

Genel Biçimi:
[geri dönüş değeri] operator <operatör sembolü> ([parametreler]);

Operatör fonksiyonları aslında normal fonksiyonlardır. Yalnızca fonksiyon ismi olarak operator anahtar sözcüğü ile operatör sembolü gelir.

/*-----operfonk.cpp-----*/
#include <stdio.h>

class A {
int a;
public:
A(int x)
{
a = x;
}
int operator +(int x);
};

int Aerator +(int x)
{
return a + x;
}

void main(void)
{
A n(5);
int z;

z = n.operator +(2);
printf("%d\n", z);
}
/*------------------------*/

Operatör sembolüne ilişkin yazılan operatör fonksiyonu eğer binary bir operatöre ilişkinse bir parametreye sahip olması, unary bir operatöre ilişkinse parametreye sahip olmaması gerekir. Bir operatör fonksiyonunun kısa biçimde çağırılması da söz konusudur. "a.operator <sembol>(b)" ile "a <sembol> b" eşdeğerdir. Zaten operatör fonksiyonlarının kullanılma amacı onları kısa biçimde çağırarak sanki normal bir operatör işlemi yapılıyormuş gibi okunabilirliği arttırmaktır.

C++ Derleciyilerinin İfadeleri Yorumlama Biçimi

C++ derleyicileri bir operatörle karşılaştığında önce operandların türlerini araştırır. Eğer operandlar C'nin normal türlerine ilişkinse işlemi normal olarak gerçekleştirir. Eğer operandlardan biri bir sınıfa ilişkinse uygun bir operatör fonksiyonu araştırır. Öyle bir operatör fonksiyonu bulursa çağırır. Bulamazsa işlem error ile sonuçlandırılır. Farklı parametre yapılarına sahip aynı sembole ilişkin operatör fonksiyonları bulunabilir.

Sınıfın Dosya Organizasyonu

Normal olarak bir sınıf iki dosya halinde yazılır. Header dosyası içerisine sınıf bildirimi sembolik sabitler ve çeşitli bildirimler yerleştirilir. Bu başlık dosyasına tipik olarak

#ifndef _İSİM_H_
#define _İSİM_H_

#endif

biçiminde çeşitli bildirimler yerleştirilir. Bunun dışında inline fonksiyonlar da buraya yerleştirilmelidir. CPP dosyasının içerisine sınıfın bütün üye fonksiyonlarının tanımlamaları yazılır. Bu dosya içerisinden header file include edilir. CPP derlenerek library içerisine yerleştirilebilir. Ancak bu sınıf başka yerlerden kullanılacaksa header dosyasının kullanan kod içerisinde include edilmesi gerekir. Bir sınıfın üye fonksiyonlarının hepsi library içerisine yerleştirilebilir. Ancak derleme aşamasında header dosyasının derleyicinin bilgilendirilmesi için bulundurulması gerekir.

Karşılaştırma Operatörlerine İlişkin Operatör Fonksiyonlarının Yazılması

Karşılaştırma operatörlerine ilişkin operatör fonksionlarının parametreleri ya da geri dönüş değerleri herhangibir biçimde olabilir. Ancak geri dönüş değerlerinin int türünden olması ve koşul sağlanıyorsa 1 değerine, sağlanmıyorsa 0 değerine geri dönmesi en uygun olab durumdur.

/*-----date.h-----*/
/*-----date.cpp-----*/

CString Sınıfında Karşılaştırma Operatör Fonksiyonlarının Kullanılması

Yazı işlemlerini yapan CString sınıfı yazıların karşılaştırılması için bir grup karşılaştırma operatör fonksiyonuna sahip olabilir.

/*-----cstring.h-----*/
/*-----cstring.cpp-----*/

+, -, *, / Operatörlerine İlişkin Operatör Fonksiyonlarını

Bu operatör fonksiyonlarının parametreleri ve geri dönüş değerleri herhangi bir biçimde olabilir. Ancak en çok karşılaşılan durum fonksiyonun bir sınıfa geri dönmesi durumudur. Bir fonksiyonun bir sınıf nesnesiyle geri dönmesi C'de fonksiyonun bir yapıya geri dönmesi anlamına gelir. C'de fonksiyonlar yapıların kendisine geri dönebilirler. Bu durumda bu fonksiyonların geri dönüş değerlerinin aynı türden bir yapı değişkenine atanması gerekir. C'de bu durum çok rastlanan ve tavisye edilen bir durum değildir. Ancak C++'ta bu durum yani bir fonksiyonun bir sınıfın kendisine geri dönmesi durumu çok rastlanan bir durumdur. Böyle bir fonksiyonun geri dönüş değeri aynı türden bir sınıf nesnesine atanmalıdır. Çünkü C++'ta aynı türden iki sınıf nesnesi birbirine atanabilir.

/*-----complex.cpp-----*/

++ ve -- Operatörlerine İlişkin Operatör Fonksiyonları

Bu operatörler tek operandlı oldukları için bu operatörlere ilişkin operatör fonksiyonlarının da parametresiz yazılması gerekir. Geri dönüş değerleri herhangi bir türden olabilse de en çok rastlanan durum fonksiyonun *this ile geri dönmesi ve geri dönüş değerinin de kendi sınıf türünden bir referans olmasıdır. Bu operatörlerin prefix version'ları parametre parantezi içerisi void yazılarak veya boş bırakılarak yazılır. Postfix verison'u için parametre parantezinin içerisine int anahtar sözcüğü yazılarak bırakılır. Buradaki int anahtar sözcüğü parametre anlamına gelmez. Zaten yanına değişken ismi de yazılmaz. Operatörün postfix kullanılacağına ilişkin bir gösterim biçimidir.

++ ve -- Operatörlerinin Tarih Sınıfında Kullanılması

Bu operatör fonksiyonlarının yazımı için 1-1-1900'den geçen gün sayısını tarih bilgisine çeviren bir fonksiyona gereksinim vardır. ++ ve -- operatör fonksiyonlarını yazmak için RevDays() fonksiyonundan faydalanabiliriz. Eğer bu operator fonksiyonlarının yalnızca prefix version'ları yazılırsa postfix için de kullanılabilir. Yani postfix kullanımda aynı operatör fonksiyonu çağırılacaktır. Postfix verison'ları yazılsa bile arttırma işleminin ifadenin sonunda yapılmasını programcı sağlamak zorundadır.

/*-----date.h-----*/ /*Ekleme Revdays(), ++, -- */
/*-----date.cpp-----*/
new ve delete Operatör Fonksiyonlarının Yazımı

new ve delete operatör fonksiyonları bir sınıfın üye fonksiyonları biçiminde yazılabilir. Bu durumda bir sınıf türünden bir tahsisat yapıldığında ya da tahsis edilmiş bir alan serbest bırakıldığında bu sınıfın üye fonksiyonlarıyla ilgili işlemler yürütülür. new operatör fonksiyonu aşağıdaki gibi tanımlanmak zorundadır.

void * operator new(size_t size);

size_t yerine unsigned int de yazılabilir ama bu tür aslında derleyicileri yazanlar tarafından herhangi bir tür olarak tanımlanabilecek bir tür belirtmektedir. Hemen hemen bütün derleyicilerde size_t unsigned int anlamındadır.

Benzer biçimde delete operatör fonksiyonu da şu şekilde yazılmak zorundadır.

void operator delete(void *ptr);

/*-----opernew.cpp-----*/

Bir sınıfın new ve delete operatör fonksiyonları yazılmışsa bu sınıf türünden yapılan new ve delete işlemlerinde bu operatör fonksiyonları kullanılır. Ama yine normal olarak başlangıç ve bitiş fonksiyonları çağırılacaktır. Benzer biçimde sınıfa ilişkin birden fazla sınıf nesnesi için tahsisat yapabilmek için köşeli parantezli operatör fonksiyonları da yazılabilir. Köşeli parantezli new ve delete operatör fonksiyonları şöyle bildirilmek zorundadır.

void * operator new [](unsigned size);
void operator delete [](void *ptr);

new X[10];/*default constructor çağırılır*/
Global Operatör Fonksiyonları

Normal olarak x bir sınıf nesnesi n de C'nin normal türlerine ilişkin bir nesne olmak üzere x + n gibi bir işlem sınıfın uygun bir + operatör fonksiyonuyla gerçekleştirilebilir. Oysa n + x gibi bir işlem + operatörünün değişme özelliği olmasına rağmen gerçekleştirilemez. Çünkü bu işlem n.operator(x) anlamına gelir, bu da mümkün değildir. Global operatör fonksiyonları bu durumu mümkün hale getirmek için tasarlanmıştır ve üye operatör fonksiyonlarını işlevsel bakımdan kapsar. Global operatör fonksiyonu normal bir global fonksiyon gibi yazılır. Ama operatör sembolü binary bir operatçre ilişkinse iki parametre, unary bir operatöre ilişkinse tek parametre almak zorundadır.

Global Operatör Fonksiyonlarının Date Sınıfıyla İlişkin Uygulamaları

/*-----date.h-----*//*ekleme yapıldı*/
/*-----date.cpp-----*/

Derleyici bir kullanım biçimine uyguna hem üye operatör fonksiyonlarını hem de global operatör fonksiyonlarını araştırır. Eğer her ikisi de varsa bu durum bir iki anlamlılık hatası oluşturur. Üye operatör fonksiyonu olarak yazılabilen tüm operatör fonksiyonları global operatör fonksiyonu olarak da yazılabilir. Genellikle global operatör fonksiyonları kolay erişim bakımından arkadaş operatör fonksiyon biçiminde yazılır.

/*-----cstring.h-----*//*ekleme yapıldı*/
/*-----cstring.cpp-----*/

Global new ve delete Operatör Fonksiyonları

Aslında new ve delete işlemi yapıldığında çağırılacak global operatör fonksiyonu vardır ve bu fonksiyon derleyiciyi yazanlar tarafından kütüphane içerisine yerleştirilmiştir. Eğer programcı bu operatör fonksiyonlarını yeniden yazarsa kütüphanedeki değil programcının yazdığı çağırılacaktır. new ve delete operatör fonksiyonları aşağıdaki prototipe uygun yazılmak zorundadır.

void * operator new(unsigned size);
void * operator new [](unsigned size);
void operator delete(void *ptr);
void operator delete [](void *ptr);

/*-----glop_new.cpp-----*/

Derleyici global new operatör fonksiyonuna parametreyi new operatörünün kullanımındaki tahsisat miktarı olarak geçirir. Yani örneğin:

p = new int; ile p = operator new(sizeof(int)); arasında fark yoktur.

new operatörü kullanıldığında aslında derleyici ifadeyi global new operatörü fonksiyonunu çağırma ifadesine dönüştürür. Aslında new bir fonksiyon çağırma işlemi anlamına gelir.
Kütüphane içerisinde new ve delete operatör fonksiyonlarının köşeli parantezli fonksiyonları da vardır. Ancak bunlar global köşeli parantezsiz new ve delete operatör fonksiyonlarını çağırırlar.

void * operator [] (unsigned size)
{
....
p = operator new(sizeof(size));
....
}

Biz köşeli parantezli new ve delete operatör fonsksiyonlarını yazmasak bile yazdığımız new ve delete operatör fonksiyonları yine çağırılacaktır.

new int[n]; işlemi ile operator new [](sizeof(int) * n); aynıdır.

Eski derleyicilerde(borland v3.1 gibi) new ve delete operatörlerinin köşeli parantezli versionlarına ilişkin operatör fonksiyonlarını yazmak geçerli değildir.
Başlangıç Fonksiyonu İle Nesne Yaratılması

C++'ta X bir sınıf ismi olmak üzere başlangıç fonksiyonu çağırıyormuş gibi X(...) ifadesi ile derleyici X sınıfı türünden geçici bir nesne yaratır. Bu nesne için uygun constructor fonksiyonunu çağırır. İfade bitince bu nesneyi serbest bırakarak bitiş fonksiyonunu çağırır. Örneğin:

a = X( ) + n; işleminde önce X sınıfı türünden geçisi bir nesne tanımlanır. Sonra bu nesne + operatör fonksiyonuyla işlem görür ve a nesnesine atanır. Yaratılmış olan geçisi nesneye ilişkin bitiş fonksiyonu ifadenin bitmesiyle çağırılacaktır. Örneğin:

Create(...., CRect(10, 10, 20, 20), ....);/*Buradaki işlem Create fonksiyonunun çağırılmasıdır*/

Burada önce CRect türünden geçici bir sınıf nesnesi yaratılır, Create fonksiyonuna parametre olarak geçirilir, CReate fonksiyonu bitince yaratılan geçici nesne için destructor çağırılır.

Tür Dönüştürme Operatör Fonksiyonu

Bu fonksiyon yalnızca sınıfın üye fonksiyonu biçiminde yazılabilir. Global olarak yazılamaz. Genel biçimi:

operator <tür> (void);

Tür dönüştürme operatör fonksiyonlarının geri dönüş gibi bir kavramı yoktur(tıpkı başlangıç ve bitiş fonksiyonları gibi). Parametresi void olmak zorundadır. C'de ve C++'ta tür dönüştürme işlemi 3 biçimde yapılır:

1. Atama işlemi ile(sağ taraf değeri sol taraf değerinin türüne dönüştürülerek atama işlemi gerçekleştirilir),
2. Tür dönüştürme operatörüyle,
3. İşlem öncesi otomatik tür dönüştürmeleri biçiminde.

Bir sınıf başka bir sınıfa ya da C'nin normal türlerine dönüştürüleceği zaman sınıfın ilgili türe ilişkin tür dönüştürme operatörü varsa çağırılarak işlemler gerçekleştirilir.

/*-----complex.cpp-----*/ /*Ekleme yapıldı*/

Tür dönüştürme operatör fonksiyonlarının geri dönüş değerleri synatx içerisinde belirtilmemiş olsa da aslında geri dönüş değerleri vardır ve bu tür operatör fonksiyonunun ilişkin olduğu türdür. Bir sınıfın ifade içerisinde başka bir türe dönüştürülmesi gerektiğinde derleyici önce ilgili türe dönüştürme operatör fonksiyonunun olup olmadığını kontrol eder. Varsa işlemini bu operatör fonksiyonunu çağırarak yapar.

Tür dönüştürme operatör fonksiyonu ile sınıf herhangi bir türden herhangi bir adrese de dönüştürülebilir.

/*-----cstring.h-----*/ /*Ekleme yapıldı*/
/*-----cstring.cpp-----*/
Tür Dönüştürme Operatör Fonksiyonlarında İki Anlamlılık Durumu

C'nin normal türlerinde dönüşüm yapan tek bir dönüştürme yapan fonksiyonu varsa Bu fonksiyon kullanılarak her türe dönüşüm yapılması sağlanabilir. Örneğin Complex sınıfının yalnızca double türüne dönüşüm yapan bir operatör fonksiyonu varsa aşağıdaki kod hataya yol açmaz:

Complex b;
int a;
a = b;

Bu örnekte eğer Complex sınıfının hem double hem de int türüne dönüşüm uygulayan operatör fonksiyonları olsaydı int olan seçilecekti. Eğer bu sınıfın hem double hem de long türüne dönüşüm yapan operatör fonksiyonları olsaydı bu işlem iki anlamlılık hatasına yol açardı.
Bir sınıfın aynı işlemi yapabilecek hem normal bir operatör fonksiyonu hem de tür dönüştürme operatör fonksiyonu bir arada bulunabilir. Bu durumda normal operatör fonksiyonu öncelikli olarak ele alınır. Ancak bu tür durumlarda okunabilirlik gereği tür dönüştürme operatöründen faydalanılarak işlem gerekirse açıkça belirtilebilir.

class X {
public:
int operator +(int x);
operator int(void);
};

void main(void)
{
X a;

b = a + 10; /* + operatör fonksiyonu ile işlem yapılır*/
b = (int) a + 10; /* tür dönüştürme operatörüyle yapılır*
}
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
Başlangıç Fonksiyonu Yoluyla Dönüştürme Yapılması

C'nin normal türlerinden sınıf türlerine dönüşüm yapılabilir. Örneğin:

class X {
private:
int x;
public:
X (int n);
X operator +(X &r);
}

void main(void)
{
X n;

n + 10; /* eş değeri n + (X ) 10; veya n + X(10); */
}

Burada önce int türü X sınıfı türüne dönüştürülür, daha sonra n ile toplama işlemi yapılır. Bu dönüştürme işleminde sınıfın başlangıç fonksiyonu kullanılır. Yani bu başlangıç fonsiyonu ile geçici bir nesne yaratılacak, işlem bittikten sonra sınıfa ilişkin bitiş fonksiyonu çağırılacaktır. Aslında (X) 10; ile X(10); ifadeleri arasında fark yoktur. Tabii böyle bir dönüşümün mümkün olabilmesi için sınıfın tek parametreli başlangıç fonksiyonu bulunmak zorundadır. Özetle bir işlemin bir operandı bir sınıf nesnesiyse bu işlem derleyici tarafından 3 biçimde yapılabilir:

1. Sınıfın açık bir biçimde tanımlanmış normal ve uygun bir operatör fonksiyonu ile,
2. Tür dönüştürme operatörü fonksiyonu ile(yani sınıfın C'nin normal türlerine dönüştürülmesiyle)
3. C'nin normal türünün başlangıç fonksiyonu yoluyla geçici bir sınıf nesnesine dönüştürülmesiyle.

Bu yöntemlerin üçünün de aynı zamanda mümkün olması halinde yapılacak en iyi şey açık bir syntax ile hangi yöntemin tercih edildiğinin belirtilmesidir.

X a;
int n;

1. a.operator + (n);
2. (int ) a + n;
3. a + (X ) n;

Eğer özellikle böyle bir belirtme yapımamışsa yukarıdaki öncelik sıraları dikkate alınır.

X a = X(n); işlemi C++'ta geçerli bir işlemdir. Ancak burada iki kez nesne yaratılmaz. Yani geçici nesne kesinlikle yaratılmayacaktır. Bu işlem tamamen X a(n); işlemiyle eş değerdir.
Tek Parametreli Başlangıç Fonksiyonlarına Sahip Sınıflara İlişkin Nesnelerin İlk Değer Verilme Syntax'i İle Tanımlanması

X a = b; gibi bir ilk değer verme işlemi tamamen X a(b); ile eş değerdir. Bu eş değerlik şuradadan gelmektedir:

1. X a = b;
2. X a = X(b);
3. X a(b);

Örneğin:

CString x = "Ali"; ile CString x("Ali"); eşdeğerdir.
[ ] Operatörüne İlişkin Operatör Fonksiyonunun Yazımı

Bu operatör fonksiyonunun geri dönüş değeri herhangi bir biçimde olabilir, ancak geri dönüş değerinin referans olması en uygun durumdur. Fonksiyonun parametresi tamsayı türlerine ilişkin olmak zorundadır ve köşeli parantez içindeki sayıyı belirtir.

a bir sınıf nesnesi olmak üzere;
a[n] ile a.operator[](n) aynı anlama gelir.

Örnek: Sınıf kontrolünün yapıldığı diziyi temsil eden örnek bir sınıf.

/*-----kosedizi.cpp-----*/

Atama Operatör Fonksiyonları ve Kopya Başlangıç Fonksiyonu

Aynı türden iki sınıf nesnesi birbirlerine atanabilir. Böyle bir atama işleminde C'deki yapılarda olduğu gibi karşılıklı veri elemanları birbirlerine kopyalanır. Ancak bazı sınıflarda ve özellikle gösterici veri elemanına sahip sınıflarda karşılıklı veri elemanlarının atanması istenen bir durum değildir. Böyle sınıflarda iki sınıf nesnesi birbirine atandığında gösterici içerisindeki adresler birbirine atanır. Yani gösterici veri elemanları aynı yeri gösteriri hale gelirler. Dar faaliyet alanına ilişkin nesne iin bitiş fonksiyonu çağırıldığında geniş faaliyet alanına sahip nesnenin gösterdiği alan da silinecektir.

{
CString x("Ali");

{
CString y("Veli");
y = x;
}/*Bu aşamada x'in gösterdiği bilgi silindiğinden gösterici hatası ortaya çıkacaktır. */
}

Oysa böyle bir atama işleminde yapılacak en iyi işlem adreslerin değil adreslerin içeriğinin kopyalanması olacaktır. İşte bu tür uygulamalarda böyle atama işlemlerinin probleme yol açmaması için atama operatör fonksiyonu yazılmalıdır. Atama operatör fonksiyonu yalnızca sınıfın üye fonksiyonu olarak yazılabilir. Atama operatör fonksiyonunun parametresi herhangi bir türden olabilir. Ancak geri dönüş değerinin aynı sınıf türünden bir referans olması en uygun durumdur. tasarımı zorlaştırmamamk amacıyla void biçimde de alınabilir.

/*-----cstring.h-----*//*Ekleme yapıldı*/
/*-----cstring.cpp-----*/

Her sınıf için ayrıca atama operatör fonksiyonu yazılmasına gerek yoktur.

Kopya Başlangıç Fonksiyonu(copy constructor)

Bir sınıfın kendi sınıfı türünden bir referans parametresine sahip başlangıç fonksiyonuna kopya başlangıç fonksiyonu denir. Kopya başlangıç fonksiyonunun parametresi const bir referans da olabilir. Bu durumda C++'ta 3 tür başlangıç fonksiyonu vardır:

1. Default başlangıç fonksiyonu (parametresi void)
2. Kopya başlangıç fonksiyonu (parametresi kendi sınıfı türünden referans)
3. Sıradan başlangıç fonksiyonu (parametresi herhangi bir tür)

Kopya başlangıç fonksiyonu derleyici tarafından 3 durumda çağırılır:

1. Bir sınıf nesnesinin kendi türünden bir sınıf nesnesiyle ilk değer verilerek tanımlandığı durumlarda. Örneğin:

X n = a;/*a X türünden bir sınıf nesnesi*/

2. Bir fonksiyonun parametresi bir sınıf türünden nesnenin kendisiyse fonksiyon da aynı sınıf türünden başka bir sınıf nesnesinin kendisiyle çağırılmışsa parametre değişkeni kopya başlangıç fonksiyonu çağırılarak oluşturulur.

3. Fonksiyonun geri dönüş değeri bir sınıf türündense return ifadesiyle geçici bölgede nesne yaratılırken kopya başlangıç fonksiyonu çağırılır.

Bir sınfın atama fonksiyonu ve kopya başlangıç fonksiyonu yazılmak zorunda değildir. Eğer yazılmamışsa karşılıklı veri elemanları birbirine atanır. Kopya başlangıç fonksiyonunun yazılmasının gerekçesi atama operatör fonksiyonun yazılması gerekçesiyle aynıdır. Yani bir sınıf için atama fonksiyonunun yazılması gerekiyorsa mutlaka kopya başlangıç fonksiyonunun da yazılması gerekir.

Sanal Bitiş Fonksiyonları(virtual destructor)

Bir sınıfın bitiş fonksiyonu sanal olabilir. Aslında ne zaman bir türetme yapılacaksa taban sınıfın bitiş fonksiyonu sanal yapılmalıdır. Taban sınıfın bitiş fonksiyonu sanal yapılırsa o sınıftan türetilen tüm sınıfların bitiş fonksiyonları otomatik olarak sanal kabul edilir. Normal olarak delete operatörünün operandı hangi sınıfa ilişkin bir adres ise o sınıfa ilişkin bitiş fonksiyonu çağırılır. Ancak bazı durumlarda adrese ilişkin sınıfın bitiş fonksiyonu değil de nesnenin orijinaline ilişkin sınıfın bitiş fonksiyonunun çağırılması gerekir.





B sınıfı A sınıfından türetilmiş olsun;

{
A *p;

p = new B(n);
delete p;
}

Burada normal olarak p göstericisi A sınıfına ilişkin olduğu için delete p; işlemindeA sınıfının bitişi fonksiyonu çağırılır. Oysa B sınıfına ilişkin bitiş fonksiyonunun çağırılması uygun olan durumdur. İşte taban sınıf bitiş fonksiyonu sanal yapılırsa B sınıfına ilişkin bitiş fonksiyonu çağırılır. Bu bitiş fonksiyonu kendi içerisinde zaten A sınıfının bitiş fonksiyonunu da çağıracaktır. Bir sınıf kendisinden türetme yapılacak şekilde tasarlanıyorsa mutlaka bitiş fonksiyonu sanal yapılmalıdır.
iostream Sınıf Sistemi

Bu sınıf sistemi ekran, klavye ve dosya işlemleri için türetilmiş bir dizi sınıftan oluşur.



istream sınıfı klavye ve dosyadan okuma yapmak için gereken veri elemanlarına ve üye fonksiyolarına sahiptir. ostream sınıfı ise ekrana ve dosyaya bilgi yazmak için gereken veri elemanlarına ve üye fonksiyonlarına sahiptir. ios sınıfı okuma ve yazma işlemlerinde kullanılan temel ve ortak veri elemanlarını ve üye fonksiyonlarını bulunduran bir sınıftır. ostream sınıfının her türden parametreye sahip bir grup << (sola shift) operatör fonksiyonu vardır. Bu operatör fonksiyonları parametreleri ile belirtileni ekrana yazdırırlar. Yani ostream sınıfı türünden bir sınıf nesnesi tanımlanır ve bu operatör fonksiyonları kullanılırsa ekrana yazdırma yapılabilir.

ostream x;
x << 100;

Ancak zaten kütüphane içerisinde cout isimli bir nesne tanımlanmıştır. Yani bu nesne kullanılarak ekrana yazdırma yapılabilir. iostream sınıf sisteminin bütün bildirimler iostream.h içerisindedir. Sınıfların üye fonksiyonları kütüphane içerisindedir.

ostream sınıfının << operatör fonksiyonlarının geri dönüş değerleri yine ostream türünden bir referanstır. Böylece bu operatör fonksiyonu birden fazla eleman için kombine edilebilir.

/*-----cout.cpp-----*/
#include <iostream.h>

void main(void)
{
int a = 123;

cout << "Value=" << 20 << '\n';
}
/*-------------------*/

Sınıf İçerisinde Başka Bir Sınıf, Yapı, typedef ve enum Bildirimlerinin Bulunması

Bir sınıfın içerisinde yapı, başka bir sınıf, enum vs. bildirimleri yapılabilir. Genel olarak sınıf içerisinde bildirilen bütün değişken isimleri(yapı, sınıf, enum sabitleri gibi) dışarıdan ancak çözünürlük operatörü kullanılarak sınıf ismiyle erişilebilir. Tabii bu erişimin geçerli olabilmesi için bildirimin sınıfın public bölümde yapılmış olması gerekir. Yani sınıf içerisinde bildirilen bütün değişkenler sınıf faaliyet alanına sahip olur. Sınıfın üye fonksiyonları içerisinde doğrudan, dışarıdan ancak sınıf ismi ve çözünürlük operatörüyle çağırılabilir.

class X {
private:
int x;
public:
typedef unsigned int WORD;
void func(void);
};

void X::fonk(void)
{
WORD x; /*Doğru*/
}

void main(void)
{
WORD x; /*Yanlış*/
X:ORD x; /*Doğru*/
}

iostream Sınıf Sisteminde Formatlı Yazdırma İşlemleri

ios sınıfının protected bölümünde long bir x_flags isimli bir değişken vardır. ostream sınıfının << operatör fonksiyonları x_flags değişkeninin bitlerine bakarak yazdırma işleminin nasıl yapılacağını anlarlar. Bu değişkenin değerini alan ve değişkenin değerini değiştiren iki public üye fonksiyonu vardır:

long flags();
long flags(long);

x_flags değişkeninin ilgili bitlerini set edebilmek için ios sınıfı içerisinde bütün bitleri 0 yalnızca bir biti 1 olan çeşitli sembolik sabitler enum sabiti biçiminde tanımlanmıştır. Örneğin bitlerden birisi yazma işleminin hex sistemde yapılıp yapılmayacağını belirler. O bitin set edilip eski hale getirilmesi şöyle yapılabilir.

#include <iostream.h>

void main(void)
{
long x;
x = cout.flags();
cout.flags(x | ios::hex); /*hex biçimde yazılmasını sağlar*/
cout.flags(x & ~ios::hex); /*hex biçimde yazılmamasını sağlar*/
}

x_flags değişkeninin uygun bitleri 1 yapılarak yazdırma işlemi çeşitli biçimlere çekilebilir.

/*-----cout2.cpp-----*/
#include <iostream.h>

void main(void)
{
int a = 100;
long x;

cout << a << '\n';
x = cout.flags();
cout.flags(x & ~ios::hex | ios::hex);
cout << a << '\n';
}
/*--------------------*/

Burada yapılan işlemi tek aşamada yapan setf isimli bir üye fonksiyon da vardır.

long setf(long);
long unsetf(long);

setf fonksiyonu önce x_flags içerisindeki değeri alır, bunu parametresiyle or işlemine sokarak işlemi bir hamlede gerçekleştirir.

/*-----cout3.cpp-----*/
#include <iostream.h>

void main(void)
{
int a = 100;

cout << a << '\n';
cout.setf(ios::hex);
cout << a << '\n';
cout.unsetf(ios::hex);
cout << a << '\n';
}
/*--------------------*/

ios sınıfının protected x_width elemanı yazma işlemi için kaç karakter alanı kullanılacağını belirlemekte kullanılır. Bu elemanla ilişki kuran iki fonksiyon vardır.

int width(void);
int width(int w);

Benzer biçimde x_precision noktadan sonra kaç basamak yazılacağını belirlemekte kullanılır.

/*-----cout4.cpp-----*/
#include <iostream.h>

void main(void)
{
double x = 3.52534;

cout << x << '\n';
cout.precision(10);
cout << x << '\n';
}
/*--------------------*/
 

Dram-Like

🏅Acemi Tasarımcı🏅
Katılım
21 Şub 2009
Mesajlar
90
Tepkime puanı
3
Template Fonksiyonlar

Genel biçim:
template <class(typename) T> [geri dönüş değeri] <fonksiyon ismi>([parametreler])
{

}

Örnek:
template <class T>
void fonk(T a)
{

}

template bir anahtar sözcüktür. template anahtar sözcüğünden sonra < > gelmelidir. < > arasındaki class anahtar sözcüğünün normal sınıf işlemleriyle hiçbir ilgilisi yoktur. class anahtra sözcüğü yerine 1996 standardizasyonunda typename anahtar sözcüğü de kullanılabilir. class anahtar sözcüğünden sonra isimlendirme kuralına göre herhangi bir isim yazılır. Bu isim bir tür belirtir. Template fonksiyonlar ve sınıflar birer şablondur. Yani kendi başlarına kodda yer kaplamazlar. Bir template fonksiyon çağırıldığında derleyici önce çağırılma ifadesindeki parametre yapısını inceler. Bu türe uygun olarak şablonda belirtilen fonksiyondan programcı için yazar.

/*-----templat.cpp-----*/
#include <iostream.h>

template <class T>
void fonk(T a)
{
cout << a << endl;
}

void main(void)
{
fonk(3.5);
fonk(30L);
fonk(40L);
}
/*----------------------*/

Template fonksiyonlarda tür belirten sözcüğe template argümanı denir. Template argümanının template fonksiyon parametre yapısı içerisinde gözükmesi zorunludur. Zaten gözükmemesi durumu da anlamsız olur. Bu template argümanı geri dönüş değeri olarak ya da fonksiyon içerisinde tür belirten sözcük olarak kullanılabilir.

/*-----templat2.cpp-----*/
#include <iostream.h>

template <class T>
T abs(T a)
{
return a > 0 ? a : -a;
}

void main(void)
{
cout << abs(-3.5) << endl;
cout << abs(-3) << endl;
cout << abs(-10L) << endl;
}
/*------------------------*/

Bir template fonksiyon birden faazla farklı template argümanına sahip olabilir. Bu durumda pek çok kombinasyon söz konusu olabilir.

/*-----templat3.cpp-----*/
#include <iostream.h>

template <class A, class B>
void fonk(A a, B b)
{
cout << a << endl << b << endl;
}

void main(void)
{
fonk(20L, 100);
fonk(100, 20L);
fonk(1.3, 10L);
fonk('c', 12);
}
/*------------------------*/

Template fonksiyonlar çağırıldığında uygun parametre yapısı bulunamazsa error durumu oluşabilir.

/*-----templat4.cpp-----*/
#include <iostream.h>

template <class T>
void swap(T *a, T *b)
{
T temp;

temp = *a;
*a = *b;
*b = temp;
}

void main(void)
{
int x = 10, y = 20;
swap(&x, &y);
cout << "x = " << x << " " << "y = " << y << endl;
}
/*------------------------*/

Bir template fonksiyon içerisinde başka bir template fonksiyon çağırılmış olabilir. Template argümanı bir sınıf da olabilir. Bir template fonksiyonuyla aynı isimli normal bir fonksiyon bir arada bulunabilir. Bu durumda iki anlamlılık hatası oluşmaz. Normal fonksiyonun template fonksiyona karşı bir üstünlüğü vardır.

Sınıf Çalışması:

template <class T>
void sort(T *p, int size);

Biçiminde sort işlemi yapan bir template fonksiyonu yazınız.

/*-----templat5.cpp-----*/
#include <iostream.h>

template <class T>
void swap(T *a, T *b)
{
T temp;

temp = *a;
*a = *b;
*b = temp;
}

template <class T>
void sort(T *p, int size)
{
int i = 0, j = 0;

for (i = 0; i < size - 1; ++i)
for (j = i + 1; j < size; ++j)
if (p > p[j])
swap(p + i, p + j);
}

void main(void)
{
int a[5] = {3, 8, 4, 7, 6};
int i;

sort(a, 5);
for(i = 0; i < 5; ++i)
cout << a << endl;
}
/*-----------------------*/

Template fonksiyonlar bir şablon belirtir ve kütüphaneye yerleştirilemez. Bu yüzden header dosyalarının içerisine yazılmalıdır.

Template Sınıflar

Template sınıflarda derleyici nesne tanımlama biçimine uygun bir sınıfın üye fonksiyonlarının hepsini yazar. Genel biçimi:

template <class T>
class X {

};

Bu template argümanı sınıf bildiriminin içerisinde ve bütün üye fonksiyonların içerisinde tür belirten sözcük olarak kullanılabilir. Uygulamada ağırlıklı bir biçimde template sınıflar kullanılmaktadır. Bir tenplate sınıfa ilişkin üye fonksiyon sınıfın dışında aşağıdaki syntax biçimine uygun yazılmak zorundadır.

template <class T>
[geri_dönüş_değeri] sınıf_ismi<T>::fonksiyon_ismi(parametreler)
{

}

Örnek:

template <class T>
void X<T>::fonk(void)
{

}

Bir template sınıfa ilişkin sınıf nesnesi şöyle tanımlanır:

sınıf_ismi <tür ismi> nesne_ismi;

Örnek: Array <int> x;

/*-----templat6.cpp-----*/
#include <stdio.h>
#include <stdlib.h>

template <class X>
class Array {
private:
int size;
X *p;
public:
Array(void);
Array(int n);
X & operator [](int n);
};

template <class X>
Array<X>:rray(void)
{
size = 10;
p = new X[size];
}

template <class X>
Array<X>:rray(int n)
{
size = n;
p = new X[size];
}

template <class X>
X & Array<X>erator[](int n)
{
if (n >= size) {
printf("Out of range\n");
exit(1);
}
return p[n];
}

void main(void)
{
Array <float> n(5);

for (int k = 0; k < 5; ++k)
n[k] = k;
for (k = 0; k < 10; ++k)
printf("%f\n", n[k]);
}
/*---------------------*/
Stack Sistemine İlişkin Bir template Sınıf Örneği

Stack Sistemi Nedir?

Stack lifo sistemi ile çalışan bir kuyruk yapısıdır. Stack sisteminin ismine stack pointer denilen bir göstericisi vardır. Bu gösterici o anki stack pozisyonunu tutar. Stack sistemi ile ilgili iki temel işlem vardır:

1. Eleman Eklenmesi: Eleman eklenmesi durumunda önce stack gösterişcicsi azaltılır daha sonra stck göstericisinin gösterdiği yere eleman yerleştirilir. stack sistemini başlangıç konumunda sp tahsis edilmiş alanın en aşşasını gösterir. Stack'e eleman eklenmesine push denir. Stack sistemine fazla eleman yerleştirilmesi taşmaya yol açabilir. Bu işleme stack sisteminin üsten taşması denir(stack overflow)

2. Eleman Çekilmesi: Bu durumda stack göstericisinin gösterdiyi yerden eleman çekilir ve stack göstericisi bir artırılır. Stack'ten bilgi alınmasına pop işlemi denir. Eğer fazla sayıda eleman çekilme işlemi uygulanırsa stack aşşağıya doğru taşar. Bu işleme stack underflow denir.

Stack Sisteminin Uygulama Alanları

1. Ters düz etme işlemi. Bir yazıyı ters düz etmek için karakterler tek tek stack sistemine eklenir ve sonra çekilir.
2. Undo işlemleri: Stasck sisteminin her elemanı yapılan işlem hakkında bilgiyi tutan bir yapı şeklindedir. İşlemler yapıldıkça push işlemi ile stack sistemine yerleştirilir. daha sonra undo yapıldıkça pop işlemi ile geri alınır.
3. Aktif pencerenin bulunması işlemi: Üstüste açılan pencerelerde son açılan aktif penceredir o pencere kapatılırsa bir önceki pencere aktif pencere olur.

/*-----stack.h-----*/
/*-----stack.cpp-----*/

nesne tanımlanırken kullanılan template türü başka bir template sınıfı olabilir. X <Y<int>> Z;
Çoklu Türetme (multiple inherritence)

Bir sınıfın birden fazla taban sınıfa sahip olması durumudur. Çoklu türetme durumunda başlangıç fonksiyonlarının çağrılması bildirimde ilk yazılan taban sınıfın başlangıç fonksiyonu önce çağrılacak biçimdedir. Örneğin class Cublic A, public B { böyle bir bildirimde önce a, b, c şeklinde başlangıç fonksiyonları çağrılır. Bitiş fonksiyonlarıda c, b, a olacak şekilde tam tersdir.
Türemiş sınıf nesnesi bütün taban sınıf nesnelerinide içerir. Taban sınıf nesnelerinin tüenmiş sınıf içindeki organizasyonu standart olarak belirlenmemiştir. Ancak bildirimde genllikle bildirimde ilk yazılan taban sınıfın vweri elemanları düşük anlamlı adreste oacak biçimde ardışıl bir yerleşim söz konusudur.

/*-----multinhe.cpp-----*/

Çoklu Türetmede Faaliyet Alanı ve İsim Arama

Çoklu türetme urumunda türemiş sınıf bütün taban sınıflara erişebilir. taban sınıflara erişim konusunda hiç bir önceklik yoktur. Bütün taban sınıflar aynı faaliyet alanı içinde kabul edilir. Yani her iki taban sınıftada aynı isimli bir veri elemanı yada üye fonksiyonu varsa ve erişim çözünürlük operatörü ile yapılmamışsa bu durum iki anlamlılık hatasının oluşmasına yol açar.

int x; int x;


C n;
n.x = 10; /*Bu işlem iki anlamlılık hatasıdır*/

Yani türemiş sınıf üye fonksiyonu içinde bir isim kukllşanıldığında bu isim:

1. Türemiş sınıf faaliyet alanı içinde
2. Taban sınıfların her birinin faaliyet alanı içinde aranır ve bir sıra gözetilmez.

Çoklu Türetmede Türemiş Sınıf Adresinin Taban Sınıf Adresine Atanması Durumu:

Mademki türemiş sınıfı nesnesinin adesi herhangibir tabna sınıf nesnesinin adresine atana biliyor o halde adresteki işlemler geçerlidir.



C x;
A *pa;
B *pb;
pa = &x;
pb = &x;/*işlemleri geçerlidir*/


Çoklu türetme durumunda türemiş sınıf nesnesinin adresi, herhangi bir taban sınıf göstericisine atandığında derleyici türemiş sınıf nesnesinin taban sınıf veri elemanının adresini göstericiye atar.

Çeşitli Çoklu Türetme Şemaları

1. Bir sınıfın birden fazla doğrudan taban sınıfı olamaz. Başka bir deyimle bir sınıf başka bir sınıfa birden fazla taban sınıflık yapamaz.




/*Bu işlem olamaz.*/


class B: public A, public A {

};

Böyle bir bildirimde aynı sınıfın veri elemanlarından iki kez oluşturulur. Faaliyet alanı gereği aynı isimli iki taban sınıf veri elemanlarına çatışma çözülerek erişim sağlanamaz.
 
Üst