Değişkenler ve Bellek Modeli
Bir değişken, bellekte bir adrese takılan isim etiketidir. Tıpkı bir otelin odalarına numara vermesi gibi — her oda (bellek adresi) bir konuğu (veri) barındırır ve sen o odayı ismiyle (değişken adı) çağırırsın. Bu derste değişkenlerin nasıl tanımlandığını, bellekte nasıl yaşadığını ve C++'ın bellek modelini öğreneceksin.
Değişken Tanımlama
C++'ta bir değişken tanımlarken önce tipini, sonra ismini yazarsın:
#include <iostream>
int main() {
int yas; // Tanimlama (declaration) — deger atanmadi!
yas = 25; // Deger atama (assignment)
int boy = 175; // Tanimlama + baslangic degeri (initialization)
double kilo = 70.5;
char kan_grubu = 'A';
bool ogrenci = true;
std::cout << "Yas: " << yas << std::endl;
std::cout << "Boy: " << boy << std::endl;
std::cout << "Kilo: " << kilo << std::endl;
return 0;
}İlk satırda yas tanımlandı ama değer verilmedi. Bu durumda yas içinde "çöp değer" (garbage value) bulunur — bellekte o an ne varsa o. Bu, C++'ın en yaygın hatalarından biridir.
⚠️ Dikkat: Başlatılmamış (uninitialized) bir değişkeni okumak "tanımsız davranış" (undefined behavior) oluşturur. Program çökebilir, yanlış sonuç verebilir veya tamamen farklı davranabilir. Her değişkene mutlaka başlangıç değeri ver!
Başlatma Yöntemleri (Initialization)
C++'ta bir değişkene başlangıç değeri vermenin birden fazla yolu vardır. Modern C++'ta tercih edilen yöntemler farklıdır:
#include <iostream>
int main() {
// 1. Copy initialization (C tarzı)
int a = 42;
// 2. Direct initialization
int b(42);
// 3. Uniform (brace) initialization — C++11, TERCIH EDILEN
int c{42};
// 4. Value initialization (sifirla baslatir)
int d{}; // d = 0
double e{}; // e = 0.0
bool f{}; // f = false
std::cout << "a: " << a << std::endl;
std::cout << "b: " << b << std::endl;
std::cout << "c: " << c << std::endl;
std::cout << "d: " << d << std::endl;
std::cout << "e: " << e << std::endl;
std::cout << std::boolalpha << "f: " << f << std::endl;
return 0;
}Neden Süslü Parantez {} Tercih Edilmeli?
Süslü parantez ile başlatma (uniform initialization) iki büyük avantaj sağlar:
Narrowing conversion engeller: Veri kaybına yol açacak dönüşümleri derleyici hata olarak yakalar
Tutarlı söz dizimi: Her tip için aynı şekilde çalışır
#include <iostream>
int main() {
// int x = 3.14; // Sessizce 3'e keser — tehlikeli!
// int y{3.14}; // DERLEME HATASI — veri kaybi var!
int z = 3.14; // Uyari verir ama derler
// int w{3.14}; // Hata verir, derlemez
std::cout << "z: " << z << std::endl; // z = 3
return 0;
}💡 İpucu: Modern C++ projelerinde
{}ile başlatma standart pratiktir. Bu alışkanlığı baştan edin — ileride seni birçok bug'dan kurtaracak.
Değişken İsimlendirme Kuralları
C++'ta değişken isimleri için bazı kurallar ve gelenekler var:
Zorunlu kurallar:
Harf veya alt çizgi (
_) ile başlamalı, rakamla başlayamazSadece harf, rakam ve alt çizgi içerebilir
C++ anahtar kelimeleri (keywords) kullanılamaz (
int,class,returnvb.)Büyük/küçük harf duyarlıdır (
yasveYasfarklı değişkenlerdir)
Gelenekler (convention):
snake_case:ogrenci_sayisi,toplam_fiyat— C++ dünyasında yaygıncamelCase:ogrenciSayisi,toplamFiyat— bazı projelerde tercih edilirİsimlendirme açıklayıcı olsun:
xyerinesicaklik,nyerineogrenci_sayisi
#include <iostream>
int main() {
// IYI isimler
int ogrenci_sayisi = 45;
double ortalama_not = 78.5;
bool sinav_gecti = true;
// KOTU isimler
int x = 45; // Ne oldugu belli degil
double on = 78.5; // "on" = ortalama not? Belirsiz
bool b = true; // Hic bir sey anlatmiyor
// GECERSIZ isimler
// int 2sayi = 5; // Rakamla baslayamaz
// int class = 10; // Anahtar kelime
// int top lam = 20; // Bosluk icermez
return 0;
}Sabitler (Constants)
Değeri hiç değişmeyecek bir değişken tanımlamak istiyorsan const kullanırsın:
#include <iostream>
int main() {
const double PI = 3.14159265358979;
const int MAX_OGRENCI = 50;
const std::string OKUL_ADI = "Bilgi Universitesi";
std::cout << "Pi: " << PI << std::endl;
std::cout << "Max ogrenci: " << MAX_OGRENCI << std::endl;
// PI = 3.14; // DERLEME HATASI! const degistirilemez
return 0;
}Sabitler genelde BUYUK_HARF ile yazılır — bu bir zorunluluk değil ama yaygın bir gelenektir. const konusunu ilerideki derslerde çok daha detaylı göreceğiz.
Bellek Modeli — Analoji
Bilgisayarın belleğini devasa bir apartman olarak düşün. Her daire bir byte'lık bir hücre ve her dairenin bir adresi var (0, 1, 2, 3...). Bir değişken tanımladığında, bu apartmandan bir veya birkaç daireyi "kiralıyorsun" ve o dairelere bir isim etiketi takıyorsun.
Bellek Adresi: [1000] [1001] [1002] [1003] [1004] [1005] [1006] [1007]
|-------- int yas --------| |------ int boy ----------|
| 4 byte = 32 bit | | 4 byte = 32 bit |int yas = 25; dediğinde, derleyici bellekten 4 byte'lık bir alan ayırır ve 25 değerini bu alana yazar. Sen yas dediğinde, aslında derleyici arka planda o bellek adresine gider.
Stack ve Heap — İki Farklı Bellek Bölgesi
C++ programlarında bellek iki ana bölgeye ayrılır: Stack (yığın) ve Heap (öbek). Şimdilik temel farkı anlamak yeterli.
Stack — Otomatik ve Hızlı
Stack, fonksiyonlar içinde tanımlanan yerel değişkenlerin yaşadığı alandır. Çok hızlıdır ve otomatik yönetilir.
#include <iostream>
void fonksiyon() {
int x = 10; // Stack'te olusturulur
double y = 3.14; // Stack'te olusturulur
std::cout << "x: " << x << ", y: " << y << std::endl;
} // Fonksiyon bitince x ve y otomatik olarak yok edilir
int main() {
int a = 5; // Stack'te
fonksiyon();
// x ve y artik yok — erisemezsin
std::cout << "a: " << a << std::endl;
return 0;
}Stack bir tabak yığınına benzer: en son koyduğun tabağı ilk alırsın (LIFO — Last In, First Out). Fonksiyon çağrıldığında değişkenler yığına eklenir, fonksiyon bitince otomatik kaldırılır.
Stack'in özellikleri:
Çok hızlı (bellek ayırma/geri verme anlık)
Boyutu sınırlı (genelde 1-8 MB)
Otomatik yönetim — sen bir şey yapmazsın
Fonksiyon bitince değişkenler yok olur
Heap — Dinamik ve Esnek
Heap, senin elle yönettiğin bellek alanıdır. new ile ayırır, delete ile geri verirsin. Daha büyük ve esnektir ama yönetmek senin sorumluluğundadır.
#include <iostream>
int main() {
// Heap'te bir int olustur
int* p = new int{42};
std::cout << "Deger: " << *p << std::endl;
std::cout << "Adres: " << p << std::endl;
delete p; // Bellegi geri ver — UNUTMA!
p = nullptr; // Guvenlik icin nullptr yap
return 0;
}Heap'i bir depo kiralama gibi düşün: istediğin kadar alan kiralayabilirsin ama işin bitince geri vermezsen, başkası kullanamaz (bellek sızıntısı — memory leak).
⚠️ Dikkat: Heap yönetimi C++'ın en zor ve hata yapılmaya müsait konusudur. Modern C++'ta
new/deleteyerine akıllı işaretçiler (smart pointers) kullanılır. Bu konuyu ilerleyen derslerde detaylı göreceğiz. Şimdilik stack/heap farkını kavramak yeterli.
Heap'in özellikleri:
Stack'e göre daha yavaş
Boyutu çok büyük (sistemin RAM'i kadar)
Manuel yönetim gerektirir
Program bitene kadar veya
deleteedilene kadar yaşar
Stack vs Heap Özet Tablosu
| Özellik | Stack | Heap |
|---|---|---|
| Hız | Çok hızlı | Daha yavaş |
| Boyut | Sınırlı (1-8 MB) | Büyük (RAM kadar) |
| Yönetim | Otomatik | Manuel |
| Ömür | Fonksiyon bitince biter | delete edilene kadar |
| Kullanım | Yerel değişkenler | Dinamik boyutlu veriler |
Değişkenlerin Bellekteki Yerleşimi
Değişkenlerin bellekte nasıl sıralandığını görmek istersen, adres operatörü & ile bakabilirsin:
#include <iostream>
int main() {
int a = 10;
int b = 20;
int c = 30;
double d = 3.14;
std::cout << "a adresi: " << &a << " (boyut: " << sizeof(a) << ")" << std::endl;
std::cout << "b adresi: " << &b << " (boyut: " << sizeof(b) << ")" << std::endl;
std::cout << "c adresi: " << &c << " (boyut: " << sizeof(c) << ")" << std::endl;
std::cout << "d adresi: " << &d << " (boyut: " << sizeof(d) << ")" << std::endl;
return 0;
}Stack'teki değişkenler genelde ters sırada yerleşir (yüksek adresten düşük adrese doğru). Ama derleyici optimizasyonlara göre sırayı değiştirebilir — bu detay hakkında varsayım yapma.
Hizalama (Alignment)
İşlemciler belleğe erişirken "hizalanmış" adresleri tercih eder. Yani 4 byte'lık bir int genelde 4'ün katı olan bir adreste durur.
#include <iostream>
struct Ornek {
char a; // 1 byte
int b; // 4 byte
char c; // 1 byte
};
int main() {
std::cout << "Ornek boyutu: " << sizeof(Ornek) << " byte" << std::endl;
// Beklenen: 1 + 4 + 1 = 6 byte
// Gercek: muhtemelen 12 byte (padding yuzunden)
Ornek o;
std::cout << "a adresi: " << (void*)&o.a << std::endl;
std::cout << "b adresi: " << &o.b << std::endl;
std::cout << "c adresi: " << (void*)&o.c << std::endl;
return 0;
}1 + 4 + 1 = 6 byte beklersin ama derleyici performans için araya "dolgu" (padding) ekler. Bu yüzden struct boyutları bazen beklediğinden büyük olur. İleri seviye bir konu ama bilmende fayda var.
Kapsam (Scope) ve Ömür (Lifetime)
Bir değişkenin "kapsamı" onun erişilebilir olduğu kod bölgesidir. Süslü parantezler {} bir kapsam oluşturur.
#include <iostream>
int global = 100; // Global kapsam — her yerden erisilebilir
int main() {
int yerel = 50; // main kapsaminda
{
int ic = 30; // Ic kapsam
std::cout << "global: " << global << std::endl; // OK
std::cout << "yerel: " << yerel << std::endl; // OK
std::cout << "ic: " << ic << std::endl; // OK
}
// ic burada artik yok!
std::cout << "global: " << global << std::endl; // OK
std::cout << "yerel: " << yerel << std::endl; // OK
// std::cout << ic; // HATA! ic bu kapsamda tanimli degil
return 0;
}Değişkenler tanımlandıkları süslü parantez kapanınca otomatik olarak yok olur. Bu, C++'ın güçlü yanlarından biridir — kaynak yönetimi (RAII) bu prensibe dayanır.
İsim Gölgeleme (Shadowing)
İç kapsamda dış kapsamla aynı isimde bir değişken tanımlayabilirsin. Bu durumda iç kapsamdaki değişken dıştakini "gölgeler":
#include <iostream>
int main() {
int x = 10;
std::cout << "Dis x: " << x << std::endl; // 10
{
int x = 20; // Dis x'i golgeler
std::cout << "Ic x: " << x << std::endl; // 20
}
std::cout << "Dis x hala: " << x << std::endl; // 10
return 0;
}💡 İpucu: Gölgeleme yasal olsa da kafa karıştırıcıdır. Derleyici uyarıları açıksa (
-Wshadowbayrağı) seni uyarır. Mümkünse farklı isimler kullan.
auto ile Tür Çıkarımı (Type Deduction)
C++11'den itibaren auto anahtar kelimesiyle derleyiciye "tipini sen bul" diyebilirsin:
#include <iostream>
#include <string>
int main() {
auto sayi = 42; // int
auto pi = 3.14; // double
auto harf = 'A'; // char
auto isim = std::string{"Ahmet"}; // std::string
auto aktif = true; // bool
std::cout << "sayi: " << sayi << std::endl;
std::cout << "pi: " << pi << std::endl;
std::cout << "harf: " << harf << std::endl;
std::cout << "isim: " << isim << std::endl;
std::cout << std::boolalpha << "aktif: " << aktif << std::endl;
return 0;
}auto tipi sağ taraftaki ifadeden çıkarır. 42 yazarsan int, 3.14 yazarsan double, 'A' yazarsan char olur.
auto Ne Zaman Kullanılmalı?
Kullan:
Tip çok uzun ve karmaşıksa (özellikle iterator'larda)
Tip sağ taraftan açıkça belliyse:
auto x = 42;Lambda ifadelerinde
Kullanma:
Tip belli değilse ve okunabilirlik düşüyorsa
Tipin ne olduğu önemli ve açıkça belirtilmesi gerekiyorsa
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> isimler = {"Ali", "Veli", "Ayse"};
// auto olmadan — uzun ve zahmetli
std::vector<std::string>::iterator it1 = isimler.begin();
// auto ile — temiz ve okunabilir
auto it2 = isimler.begin();
std::cout << *it1 << std::endl;
std::cout << *it2 << std::endl;
return 0;
}Iterator gibi uzun tip isimlerinde auto gerçekten hayat kurtarır. Ama basit tiplerde (int, double) açıkça yazmak genelde daha okunabilirdir.
decltype — Bir İfadenin Tipini Al
decltype bir ifadenin tipini "kopyalar" ama değerini almaz. auto ile birlikte kullanışlıdır:
#include <iostream>
int main() {
int x = 42;
decltype(x) y = 100; // y'nin tipi x ile ayni: int
double pi = 3.14;
decltype(pi) e = 2.71; // e'nin tipi double
std::cout << "y: " << y << " (int)" << std::endl;
std::cout << "e: " << e << " (double)" << std::endl;
return 0;
}decltype şu an çok kullanmayacaksın ama template ve generic programlamada önemli bir araç. Var olduğunu bilmen yeterli.
Statik Değişkenler (static)
static yerel değişken, fonksiyon her çağrıldığında yeniden oluşturulmaz — değerini korur:
#include <iostream>
void sayac() {
static int count = 0; // Sadece ilk cagride olusturulur
count++;
std::cout << "Cagri sayisi: " << count << std::endl;
}
int main() {
sayac(); // 1
sayac(); // 2
sayac(); // 3
return 0;
}Normal bir yerel değişken her fonksiyon çağrısında sıfırdan oluşturulur ve fonksiyon bitince yok olur. Ama static değişken programın ömrü boyunca yaşar, sadece tanımlandığı kapsamdan erişilebilir.
Birden Fazla Değişken Tanımlama
Aynı tipte birden fazla değişkeni tek satırda tanımlayabilirsin:
#include <iostream>
int main() {
int x = 1, y = 2, z = 3;
double a{1.1}, b{2.2}, c{3.3};
std::cout << x << " " << y << " " << z << std::endl;
std::cout << a << " " << b << " " << c << std::endl;
return 0;
}Bu söz dizimi pratik görünür ama okunabilirliği azaltabilir. Özellikle pointer tanımlarken tehlikelidir:
int* p1, p2; // DIKKAT: p1 pointer, p2 normal int!
int *p3, *p4; // Ikisi de pointer⚠️ Dikkat: Pointer tanımlarken
*değişkene yapışır, tipe değil.int* p1, p2;yazdığında sadecep1pointer olur. Kafa karışıklığını önlemek için her değişkeni ayrı satırda tanımla.
Özet
Değişkenler bellekteki verilere isim etiketleridir; her zaman başlangıç değeri ver
Süslü parantez `{}` ile başlatma (uniform initialization) en güvenli yöntemdir — narrowing conversion'ı engeller
Stack hızlı ve otomatik yönetilir, Heap büyük ve esnek ama manuel yönetim gerektirir
Değişkenler tanımlandıkları kapsam `{}` dışına çıkınca otomatik yok olur
auto karmaşık tiplerde okunabilirliği artırır ama basit tiplerde açık yazmak daha iyidir
İsimlendirme açıklayıcı olsun:
xyerinesicaklik,nyerineeleman_sayisi
AI Asistan
Sorularını yanıtlamaya hazır