Bu sayfadaki yazılar
Sayfanın metni
Sayfa 20
Daha önce bir takım değişkenleri tanımlamaktan ve kullanmaktan bahsettik. Bu değişkenlerin hepsinin de bellekte durduğunu biliyoruz. İşte bir değişkenin bellekte durduğu yere o değişkenin “adresi” denir. Bu adres genelde platformun mimarisine bağlı büyüklükte bir sayıdır. Örneğin 32 bitlik bir sistemde bir değişkenin adresi de 32 bitlik bir sayıdır.
Bellek denilen şey art arda dizilmiş baytlardan oluşur. Bu baytlara istediğimiz bilgileri yazarız. Zaten siz bir değişken tanımladığınızda, o değişkenin tipine bağlı olarak bellekte bir grup bayt değişken için ayrılır. Örneğin çoğu 32 bitlik sistemde siz int tipinden bir değişken tanımladığınızda bellekte 4 baytlık bir alan ayrılır.
Bir değişkenin adresini almaya yarayan operatör & operatörüdür. Örneğin
in as 4 std::cout << da;
dediğiniz zaman sisteminizin a için ayırdığı baytlardan ilkinin adresini ekrana yazdırmış olursunuz.
bu tip değişken adresleri ile çeşitli işlemler yapabilmeniz için “işaretçileri” destekler. Bir işaretçi kendisi de bir değişkendir. İçerisinde başka bir değişkenin “adresini” taşır. Başka bir deyişle başka bir değişkene “işaret eder”. Bir nevi “şurada falanca tipte bir değişken var” der. Bunu biraz açalım.
İşaretçiler gösterdikleri değişkenin tipi ile bağlantılıdırlar örneğin
int a - 5;
int* pa &a;
Burada önce a adında tamsayı tipinde bir değişken tanımladık ve o değişkeni hemen 5 ile yükledik. Ardından “int tipinde bir değişkene bakan”, pa adında bir işaretçi tanımladık. Ve ona a değişkeninin adresini yükledik. İkinci satır bu demektir.
İşaretçiler aracılığıyla onların gösterdikleri değişkenlere erişebiliriz. Bunun için *operatörünü aşağıdaki gibi kullanırız std::cout<<*pa; bu komut a'daki değeri yani 5'i yazdırır. Aynı şekilde *pa=10;
Diyerek a değişkeninin değerini değiştirebiliriz.
İşaretçiler ile diziler arasında da akrabalık vardır diyebiliriz. Bir dizi tanımladığımız zaman dizinin adını aynı zamanda dizinin ilk elemanına bakan bir işaretçi gibi kullanabiliriz. Örneğin
int dizi(3) * (1, 2, 3); *dizi - 5; std: dizilÖl,;
dediğimizde 5 sonucu yazdırılır. Ancak dizilerin işaretçilerden farkı başka bir int değişkeninin adresini dizi'ye atayamamanızdır (oysa dizi bir dizi değil de işaretçi olsaydı atayabilirdiniz)
Değişkenlerin nerelerde ne amaçla kullanıldığını merak ediyor olabilirsiniz. Bu kullanımları yeri geldikçe birlikte göreceğiz.
Giriş
Bellek Yönetimine İşaretçilerin kullanıldığı yerlerden biri dinamik bellek yönetimidir. Şu ana kadar bütün gördüğümüz değişken tanımlamalarında, derleyici programın derlenişi esnasında ihtiyaç duyulan bellek miktarını bilebilir ve bu belleğin ayrılmasını dolaylı olarak sağlar.
Oysa bazen programcı, bellek tasarrufu için ya da başka sebeplerle çalıştığı sistemden bellek isteyecek şekilde programı yazmaya ihtiyaç duyar. Bu durumlarda programımıza bazı durumlarda sistemden bir miktar bellek isteyen komutlar yazarız.
İşte “bana biraz bellek ver” diye konuştuğumuz şahsiyete aslında “C++ runtime” deniyor. Bu programınıza eklenen bir kod bütünü. İşletim sistemi ile sizin programınız arasındaki bazı bağlantıları yaparak, programınızın çalışması için zemin hazırlıyor. Runtime'ın yaptığı işler arasında programınızın ihtiyacı olan belleğin bir kısmını siz istedikçe size sağlamak da var.
Aynı şekilde kullanıp artık ihtiyacınız kalmayan belleği de yine sisteme iade etmek gerekir. Siz bir bellek parçasını sisteme iade ettiğinizde o bellek sonraki isteklerde size verilebilecek bir kaynak olarak müsait hale gelir.
Eğer hep sistemden bellek alır ve hiç iade etmezseniz, eninde sonunda sistemde bellek kalmaz ve programınızın başına kötü şeyler gelir. Bu şekilde iade edilmesi gereken belleği iade etmeyi unutmak programlarda sık yapılan bir hata türüdür ve özel bir adı vardır: Bellek Sızdırma.
Şimdi sistemden nasıl bellek istediğimize bakalım: int* pa © new int();
Sistemden bellek isterken new komutunu kullanırız. Bu esnada o bellekte saklayacağımız şeyin tipini de söyleriz. Ve bu istek bize bir işaretçi döndürür. Bu işaretçi yeni oluşturulan değişkenin adresini taşımaktadır. Örneğin yukarıda bir tamsayı saklayacak kadar belleği sistemden istiyoruz. Eğer runtime istediğimiz belleği bulabilir ise (bellek bitmemişse bulur) uygun büyüklükte bir bellek parçasını ayırır ve o parçanın adresini döndürür.
Dönen şey bir adres olduğu için onu bir işaretçide saklamak zorundayız. İşte bu işaretçilerin en çok kullanıldığı yerlerden biridir. Bu tamsayı değişkenini hep işaretçi üstünden kullanırız.
Klavyeyle gezinmek için ← ve → tuşları.
Sayfa 21
İşimiz bittikten sonra da bu belleği delete komutu ile sisteme iade ederiz
delete pa;
böylece bellek sızıntısı olmamış olur.
Benzer şekilde new ve delete kullanarak bir dizi saklayacak kadar bellek de sistemden isteyebiliriz. Üstelik new ile bu şekilde bellek isterken dizilerden farklı olarak değişken boylarda dizileri saklamaya yetecek kadar bellek alabiliriz. Bunu biraz açalım. Şu koda bakın:
Std: sinav”; int kacSinav; std: :cin>>kacSinav; int // malesef calismaz!!!
Burada dikkat ederseniz bir dizi oluşturmak istiyoruz. Ama dizinin kaç elemanı olacağını programı yazarken bilmiyoruz. Çünkü bu değer program çalışırken kullanıcı tarafından giriliyor. Kullanıcı belki 4 diyecek belki 500. Bu yüzden kaçSınav değişkeni büyüklüğünde dizi tanımlamaya çalışıyoruz. Ancak bu kod derleyicide hata verir.
C++'da dizilerin bellek yönetimi derleyiciler tarafından ayarlanır. Derleyici bir dizi tanımı gördüğünde o dizi için gereken belleği alan kodu siz bir şey yapmadan programınıza ekler. Derleyicinin bunu yapabilmesi için derleme anında gereken belleğin miktarını kestirebilmesi gerekir. Bu yüzden dile şu kural konmuştur: Dizilerin ebatları değişken olamaz, sabit olmak zorundadır.
Ancak new kullanarak derleme zamanı yerine çalışma zamanında bellek istemini yaparsak önümüz açılıyor. Bu durumlar için new komutunu şöyle kullanabiliriz.
Int* sinavNotlari new intlkacSinavl;
Tıpkı dizilerdeki gibi köşeli parantezleri kullanıyoruz. Bu ifadede parantezin içine değişken gelebilir. Çünkü istek program çalışırken yapılıyor. Programımız bu satıra geldiğinde kaçSınav adet tamsayıyı saklayabilecek büyüklükte bir bellek parçasını sistemden ister. Sistem bulduğu belleğin adresini sınavNotları işaretçisine yükler.
İşaretçiler ve diziler akraba demiştik. Bu noktada [] indeksleme operatörünü tıpkı dizilerde olduğu gibi işaretçilerle de kullanabiliriz. Yani
30;
gibi bir ifade geçerlidir.
ile alınan belleği sisteme iade ederken delete komutu da biraz farklı yazılarak kullanılır:
delete (|
sinavNotlari;
Bu şekilde ayrı bir delete kullanılmasının sebebi şu: Derleyici sınavNotları adlı int* işaretçisinin bir int'lik bir bölgeye mi yoksa n int'lik bir bölgeye mi baktığını bilmiyor. Burada delete[] kullandığımızda sisteme, “bu adresten başlayan daha önce new[] ile aldığım bütün belleği iade ediyorum” demiş oluyoruz.
Bu bilgileri kullanarak geçen sayıdaki örnek programımızı biraz daha geliştirelim. Bu sefer sınav notlarını iki boyutlu bir dizide tutmak yerine tek boyutlu “öğrenci sayısı x sınav sayısı” kadar tamsayı içeren bir bellek parçasında tutacağız. m'inci öğrencinin n'inci sınavına ulaşmak için de “(m x sınav sayısı) + n” formülü ile indeksleme yapacağız.
#include <iostream> int main()
// kac ogrenci ogrenci?”; int kacOgrenci; std::cin>>kacOgrenci;
// kac sinav std: sinav?”; int kacSinav;
// dongu ile notlari al #nt* sinavNotlari new intlkacOgrenci * kacSinavl;
for(int o<kacOgrenci;
for(int iskacSinav;
int index o * kacSinav * iş; std: :cout << “sinav notu; std::cin >> )
// sonuclari hesapla ve goster int sinifToplam 0; £orl(int ö€lÜş (
int ogrenciToplam -— O; forl(int i<3; int index © * kacSinav * İ; ogrenciToplam *- sinavNotlarilindex|;
int ogrenciOrtalama öogrenciToplam / kacSinav; "<< o <<” ortalamasi: ” << ogrenciOrtalama <<
sinifToplam *t- ogrenciOrtalama;
)
int sinifOrtalama std::cout << “Sinif ortalamasi: <2
sinifToplam / kacOgrenci; # << sinifOrtalama
Aslında asıl zevkli bölümlere yeni geliyoruz. Şimdi programcılıktaki en önemli kavramlardan birinden bahsetme zamanı.
Fonksiyonlar