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
[IMG]file:///C:/DOCUME%7E1/SA%28%5e_%5e%7E1/LOCALS%7E1/Temp/msohtml1/01/clip_image001.jpg[/IMG][IMG]file:///C:/DOCUME%7E1/SA%28%5e_%5e%7E1/LOCALS%7E1/Temp/msohtml1/01/clip_image002.jpg[/IMG]
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
…..
….. [IMG]file:///C:/DOCUME%7E1/SA%28%5e_%5e%7E1/LOCALS%7E1/Temp/msohtml1/01/clip_image003.jpg[/IMG]
….. [IMG]file:///C:/DOCUME%7E1/SA%28%5e_%5e%7E1/LOCALS%7E1/Temp/msohtml1/01/clip_image004.jpg[/IMG]
…..[IMG]file:///C:/DOCUME%7E1/SA%28%5e_%5e%7E1/LOCALS%7E1/Temp/msohtml1/01/clip_image005.jpg[/IMG]
…..
…..
#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).
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(NULL 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 == NULL)
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 == NULL){
printf("Cannot allocate memory..\n");
exit(1);
}
strcpy(p, buf);
return p;
}
void main(void)
{
char *p;
p = getname();
puts(p);
}
/*——————————–*/
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 */
8-)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.
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.