C++'IN C'DEN FARKLILIKLARI
-NYPT İLE DOĞRUDAN İLİŞKİSİ OLMAYAN FARLILIKLARI VE FAZLALIKLARI
- SINIF YAPISI
İ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 yerel değişkenler 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
#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’dur. (NOT: C++ 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
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:
· Fonksiyon char ya da short parametreleri ile ç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.
· 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.
· Fonksiyon aynı türden const olmayan bir ifadeyle çağırılmışsa ancak aynı türden const parametreye sahip bir fonksiyon 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 ile 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
..... Fonksiyon prototipleri
.....
.....
.....
.....
.....
#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, calloc, 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
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 ile tahsisat yapılırsa o türden 1 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ündendir.
char *p;
p = new int [10]; /* C++’ta hatadır. */
p = (char *)new int[10]; /* Hata değil. */
/*----------new1.cpp---------*/
#include
#include
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
#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 soyad uzunluğu kadar bellek tahsis eden fonksiyonun kullanılışı */
#include
#include
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;
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 derleyici 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 gibi çalışır:
for(; {
if(boşyer var mı)
return boşyer;
else
set_new_handler();
}
/*-----------snhandle.cpp---------------*/
#include
#include
#include
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;
}
}
set_new_handler(oldhandle); /*handler eski haline dönüştürüldü*/
/*------------------------------------------*/
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 error 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 bir adresin herhangi bir göstericiye atanması error olarak değerlendirilir (bu durum C’de en fazla uyarı olarak değerlendirilir). 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 */
}
/*--------------------------------*/
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 const’a 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 bellekte 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
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 parametre değişkeninden sonra eşittir operatörüyle belirtilmelidir.
/*---------fark10.cpp----------*/
#include
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
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
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
#include
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 default 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 edilen 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 BİR GÖSTERİCİNİN TANIMLANMASI
Genel biçimi;
Ö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
#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
#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 parametresini 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
#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
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 ifade 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
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ın return ifadelerinin de nesne olması gerekir.
Gösterici eşdeğeri
Referans örneği
/*-----referan3.cpp-----*/
#include
int a = 10;
int *fonk(void)
{
return &a;
}
void main(void)
{
*fonk() = 20;
printf("%d\n", a);
}
/*------referan4.cpp-----*/
#include
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
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.
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.






0 Yorum:
Yorum Gönder