Ders 01 / 14
C ve C++ Farkları
C'de değerle aktarılan bir yapının C++'ta hangi nesneyle çalıştığını ölçen bir çekirdek koşumuyla kursun dört ölçüm sınıfını tanıtır.
Yayın Güncelleme
Bu derste
- Değerle aktarılan bir yapının çağıranın nesnesiyle mi yoksa bir kopyayla mı çalıştığını ölçer
- Bir çağrının hangi nesneyle çalıştığını yalnız işlevin bildirdiği parametre tipinden çıkarır
- C++'ın C'ye eklediği temel dil öğelerini sayar
İçindekiler
M11/K03’te “Nesne Benzeri Tasarım” bir yapıyı ve ona bağlı işlev işaretçilerini elle bir araya getirerek nesne davranışını C’de taklit etti; hangi işlevin çalışacağı elle kurulan bir tabloda saklanıyordu. Bu kurs aynı soruyu C++’ın kendi dil öğeleriyle sorar: bir ifade, tam olarak hangi nesneyle çalışır — adı verilen nesnenin kendisiyle mi, ondan kopyalanmış yeni bir nesneyle mi, içindeki bir parçayla mı, yoksa adsız ve kısa ömürlü bir nesneyle mi?
Ölçü Ekseni
Kurs boyunca her ders bir ifadeyi dört sınıftan birine yerleştirir:
- kendisi — ifade, adı verilen nesnenin tamamıyla çalışır.
- kopya — ifade, adı verilen nesneden kopyalanarak kurulmuş yeni bir nesneyle çalışır.
- parça — ifade, adı verilen nesnenin içindeki bir alt nesneyle çalışır.
- geçici — ifade, adsız ve tam ifadenin sonunda yok olan yeni bir nesneyle çalışır.
Bu dört sınıf tahmin değil, koşum sonucudur: aşağıdaki çekirdek her ifadeyi adres, kopyalayan yapıcı kaynağı ve nesne ömrü üzerinden izler ve sınıfı kendisi belirler.
Çekirdeğin Tam Koşumu
Çekirdek üç dosyadır. iz.hpp ve iz.cpp izleme altyapısını taşır: her nesne kuruluşta bir
sıra numarası alır, kopyalanırsa kaynağının numarasını saklar; dokun ifadenin gerçekten
çalıştığı nesneyi kaydeder, bitir beyan edilen sınıfla ölçüleni karşılaştırıp satırı
basar. hangisi.cpp bu kursun on dört dersinin tümüne ait on dört ölçümü tek main’de
koşturur.
// iz.hpp — bir ifadenin hangi nesneyle calistigini olcen iz
#ifndef IZ_HPP
#define IZ_HPP
#include <cstddef>
#include <typeinfo>
enum class Sinif { kendisi, kopya, parca, gecici };
// Izlenen her nesne bu tabandan turer: kurulusta bir sira numarasi alir,
// kopyalanirsa kaynaginin numarasini saklar.
class Iz {
public:
Iz();
Iz(const Iz& kaynak);
Iz& operator=(const Iz& kaynak);
~Iz();
int no() const { return no_; }
int kaynak() const { return kaynak_; }
private:
int no_;
int kaynak_;
};
void baslat_kayit(const void* adres, std::size_t boy, const std::type_info& tip, int no);
void dokun_kayit(const void* adres, std::size_t boy, const std::type_info& tip,
const Iz* iz, int kaynak);
// Adi verilen nesneyi kaydeder: olcum bu nesneye gore yapilir.
template <class T>
void baslat(const T& butun) {
baslat_kayit(&butun, sizeof butun, typeid(T), butun.no());
}
// Ifadenin gercekten calistigi nesneyi kaydeder ve siniflandirir.
template <class T>
void dokun(const T& nesne) {
dokun_kayit(&nesne, sizeof nesne, typeid(T), &static_cast<const Iz&>(nesne),
nesne.kaynak());
}
// Olcumu kapatir, beyan edilen sinifla olculeni karsilastirip satiri basar.
void bitir(const char* ifade, Sinif beyan);
// Sinif basina sayilari ve var olan / yeni nesne toplamini basar.
void ozet();
#endif
// iz.cpp — sayilar ve olcum durumu yalniz bu birimde tutulur
#include "iz.hpp"
#include <cstdio>
#include <functional>
namespace {
int olusan = 0;
const char* const adlar[] = { "kendisi", "kopya", "parca", "gecici" };
int sayilar[4] = {};
int var_olan = 0;
int yeni = 0;
const char* butun_adres = nullptr;
std::size_t butun_boy = 0;
const std::type_info* butun_tip = nullptr;
int butun_no = 0;
bool dokunuldu = false;
bool ayni = false;
bool icinde = false;
bool kopyasi = false;
bool bitti = false;
const Iz* izlenen = nullptr;
} // namespace
Iz::Iz() : no_(++olusan), kaynak_(0) {}
Iz::Iz(const Iz& kaynak) : no_(++olusan), kaynak_(kaynak.no_) {}
Iz& Iz::operator=(const Iz&) {
return *this; // atama nesneyi degistirir ama yeni nesne kurmaz
}
Iz::~Iz() {
if (this == izlenen) {
bitti = true;
izlenen = nullptr;
}
}
void baslat_kayit(const void* adres, std::size_t boy, const std::type_info& tip, int no) {
butun_adres = static_cast<const char*>(adres);
butun_boy = boy;
butun_tip = &tip;
butun_no = no;
dokunuldu = ayni = icinde = kopyasi = bitti = false;
izlenen = nullptr;
}
void dokun_kayit(const void* adres, std::size_t boy, const std::type_info& tip,
const Iz* iz, int kaynak) {
const char* bas = static_cast<const char*>(adres);
std::less<const char*> once;
dokunuldu = true;
ayni = bas == butun_adres && boy == butun_boy && tip == *butun_tip;
icinde = !once(bas, butun_adres) && !once(butun_adres + butun_boy, bas + boy);
kopyasi = kaynak == butun_no;
bitti = false;
izlenen = iz;
}
void bitir(const char* ifade, Sinif beyan) {
int olculen = -1;
if (dokunuldu) {
if (ayni) {
olculen = static_cast<int>(Sinif::kendisi);
} else if (icinde) {
olculen = static_cast<int>(Sinif::parca);
} else if (kopyasi) {
olculen = static_cast<int>(Sinif::kopya);
} else if (bitti) {
olculen = static_cast<int>(Sinif::gecici);
}
}
int b = static_cast<int>(beyan);
bool var = beyan == Sinif::kendisi || beyan == Sinif::parca;
sayilar[b]++;
if (var) {
var_olan++;
} else {
yeni++;
}
std::printf("%-36s %-5s %s%s\n", ifade, var ? "var" : "yeni", adlar[b],
olculen == b ? "" : " TUTARSIZ");
izlenen = nullptr;
}
void ozet() {
std::printf("siniflar: kendisi %d, kopya %d, parca %d, gecici %d\n",
sayilar[0], sayilar[1], sayilar[2], sayilar[3]);
std::printf("var olan %d, yeni %d\n", var_olan, yeni);
}
// hangisi.cpp — on dort ifade, her biri icin tek soru: hangi nesne
#include <algorithm>
#include "iz.hpp"
/* 1. C ve C++ farklari: degerle aktarilan yapi */
struct Nokta : Iz {
int x = 0;
int y = 0;
};
static void kaydir(Nokta p) {
p.x += 1;
dokun(p);
}
static void c_farki() {
Nokta n;
baslat(n);
kaydir(n);
bitir("degerle aktarilan yapi", Sinif::kopya);
}
/* 2. Erisim denetimi: uye islevin calistigi nesne */
class Hesap : public Iz {
public:
void yatir(int miktar) {
bakiye_ += miktar;
dokun(*this);
}
int bakiye() const { return bakiye_; }
private:
int bakiye_ = 0;
};
static void erisim() {
Hesap h;
baslat(h);
h.yatir(50);
bitir("uye islevin this'i", Sinif::kendisi);
}
/* 3. Yapicilar: donusturen yapicinin kurdugu nesne */
class Tutar : public Iz {
public:
Tutar(int deger) : deger_(deger) {} // explicit degil: int'ten donusturur
int deger() const { return deger_; }
private:
int deger_;
};
static int goster(const Tutar& t) {
dokun(t);
return t.deger();
}
static void yapici() {
Tutar t(3);
baslat(t);
goster(5);
bitir("int'ten kurulan arguman", Sinif::gecici);
}
/* 4. RAII: koruyucunun biraktigi uye */
class Kaynak : public Iz {
public:
void edin() { tutuluyor_ = true; }
void birak() {
tutuluyor_ = false;
dokun(*this);
}
private:
bool tutuluyor_ = false;
};
class Koruyucu {
public:
explicit Koruyucu(Kaynak& k) : k_(k) { k_.edin(); }
~Koruyucu() { k_.birak(); }
Koruyucu(const Koruyucu&) = delete;
Koruyucu& operator=(const Koruyucu&) = delete;
private:
Kaynak& k_;
};
class Sayac : public Iz {
public:
void artir() {
Koruyucu g(kaynak_);
deger_++;
}
private:
Kaynak kaynak_;
int deger_ = 0;
};
static void raii() {
Sayac s;
baslat(s);
s.artir();
bitir("koruyucunun biraktigi uye", Sinif::parca);
}
/* 5. Kopyalama: derin kopyalanan tampon */
class Tampon : public Iz {
public:
explicit Tampon(int boy) : boy_(boy), veri_(new int[boy]()) {}
Tampon(const Tampon& k) : Iz(k), boy_(k.boy_), veri_(new int[k.boy_]) {
std::copy(k.veri_, k.veri_ + k.boy_, veri_);
}
Tampon& operator=(const Tampon& k) {
if (this != &k) {
int* yeni = new int[k.boy_];
std::copy(k.veri_, k.veri_ + k.boy_, yeni);
delete[] veri_;
veri_ = yeni;
boy_ = k.boy_;
}
return *this;
}
~Tampon() { delete[] veri_; }
void yaz(int i, int deger) {
veri_[i] = deger;
dokun(*this);
}
int oku(int i) const { return veri_[i]; }
private:
int boy_;
int* veri_;
};
static void kopyalama() {
Tampon a(3);
baslat(a);
Tampon b(a);
b.yaz(0, 9);
bitir("derin kopyalanan tampon", Sinif::kopya);
}
/* 6. Referanslar: referansla aktarilan nesne */
static void tasi(Nokta& p) {
p.y += 1;
dokun(p);
}
static void referans() {
Nokta n;
baslat(n);
tasi(n);
bitir("referansla aktarilan yapi", Sinif::kendisi);
}
/* 7. Islec asiri yukleme: a + b'nin sonucu */
class Para : public Iz {
public:
explicit Para(int kurus) : kurus_(kurus) {}
Para operator+(const Para& b) const { return Para(kurus_ + b.kurus_); }
Para& operator+=(const Para& b) {
kurus_ += b.kurus_;
return *this;
}
int kurus() const { return kurus_; }
private:
int kurus_;
};
static int yaz_para(const Para& p) {
dokun(p);
return p.kurus();
}
static void islec() {
Para a(150);
Para b(250);
baslat(a);
yaz_para(a + b);
bitir("a + b sonucu", Sinif::gecici);
}
/* 8. Kalitim: taban sinifin islevi */
class Arac : public Iz {
public:
void hizlan() {
hiz_++;
dokun(*this);
}
private:
int hiz_ = 0;
};
class Kamyon : public Arac {
public:
int yuk() const { return yuk_; }
private:
int yuk_ = 0;
};
static void kalitim() {
Kamyon k;
baslat(k);
k.hizlan();
bitir("taban sinifin islevi", Sinif::parca);
}
/* 9. Sanal fonksiyonlar: taban referansindan sanal cagri */
class Sekil : public Iz {
public:
virtual ~Sekil() = default;
virtual void ciz() const { dokun(*this); }
};
class Daire : public Sekil {
public:
void ciz() const override { dokun(*this); }
private:
int yaricap_ = 1;
};
static void sanal() {
Daire d;
baslat(d);
const Sekil& s = d;
s.ciz();
bitir("taban referansindan sanal cagri", Sinif::kendisi);
}
/* 10. Soyut siniflar: arayuz uzerinden cagri */
class Kaydedici : public Iz {
public:
virtual ~Kaydedici() = default;
virtual void kaydet() = 0;
};
class SatirKaydedici final : public Kaydedici {
public:
void kaydet() override {
satir_++;
dokun(*this);
}
private:
int satir_ = 0;
};
static void kullan(Kaydedici& k) {
k.kaydet();
}
static void soyut() {
SatirKaydedici k;
baslat(k);
kullan(k);
bitir("arayuz uzerinden cagri", Sinif::kendisi);
}
/* 11. Coklu kalitim: sanal tabanin tek alt nesnesi */
class Cihaz : public Iz {
public:
void ac() {
acik_ = true;
dokun(*this);
}
private:
bool acik_ = false;
};
class Yazici : public virtual Cihaz {
int sayfa_ = 0;
};
class Tarayici : public virtual Cihaz {
int cozunurluk_ = 0;
};
class Cokislevli : public Yazici, public Tarayici {};
static void elmas() {
Cokislevli c;
baslat(c);
c.ac();
bitir("sanal tabanin alt nesnesi", Sinif::parca);
}
/* 12. Istisnalar: degerle yakalanan istisna */
class Hata : public Iz {
public:
explicit Hata(int kod) : kod_(kod) {}
int kod() const { return kod_; }
private:
int kod_;
};
static void istisna() {
const Hata* firlatilan = nullptr;
try {
try {
throw Hata(7);
} catch (const Hata& ic) {
firlatilan = ⁣
throw;
}
} catch (Hata e) {
baslat(*firlatilan);
dokun(e);
bitir("degerle yakalanan istisna", Sinif::kopya);
}
}
/* 13. Ad alanlari: using bildirimiyle anilan nesne */
namespace muhasebe {
class Defter : public Iz {
public:
void isle() {
kayit_++;
dokun(*this);
}
private:
int kayit_ = 0;
};
Defter ana_defter;
} // namespace muhasebe
static void ad_alani() {
using muhasebe::ana_defter;
baslat(muhasebe::ana_defter);
ana_defter.isle();
bitir("using bildirimiyle anilan nesne", Sinif::kendisi);
}
/* 14. Tip donusumleri: tabana donusturulen referans */
class Olcer : public Iz {
public:
void oku() const { dokun(*this); }
};
class Termometre : public Olcer {
public:
int derece() const { return derece_; }
private:
int derece_ = 20;
};
static void donusum() {
Termometre t;
baslat(t);
static_cast<const Olcer&>(t).oku();
bitir("tabana donusturulen referans", Sinif::parca);
}
int main() {
c_farki();
erisim();
yapici();
raii();
kopyalama();
referans();
islec();
kalitim();
sanal();
soyut();
elmas();
istisna();
ad_alani();
donusum();
ozet();
return 0;
}
degerle aktarilan yapi yeni kopya uye islevin this'i var kendisi int'ten kurulan arguman yeni gecici koruyucunun biraktigi uye var parca derin kopyalanan tampon yeni kopya referansla aktarilan yapi var kendisi a + b sonucu yeni gecici taban sinifin islevi var parca taban referansindan sanal cagri var kendisi arayuz uzerinden cagri var kendisi sanal tabanin alt nesnesi var parca degerle yakalanan istisna yeni kopya using bildirimiyle anilan nesne var kendisi tabana donusturulen referans var parca siniflar: kendisi 5, kopya 3, parca 4, gecici 2 var olan 9, yeni 5
SK1. On dört ifadenin beşi kendisiyle, üçü bir kopyayla, dördü bir parçayla, ikisi bir
geçiciyle çalıştı; kursun dört sınıfının adı ve dağılımı buradan doğar. İlk satır bu dersin
konusudur: kaydir(Nokta p) değerle aldığı p üzerinde çalışır, p n’den kopyalanarak
kurulmuştur — kaydir(n) içindeki p.x += 1 çağıranın n‘sine ulaşmaz. C’de bir struct’ı
değerle almak da aynı sonucu verir; C++ bu davranışı değiştirmedi, üzerine yeni bir seçenek
ekledi.
Değerle Aktarılan Yapının Derinleştirilmesi
C++‘ın C’ye eklediği referans parametresi aynı çağrıyı farklı bir sınıfa taşır. Aşağıdaki
ölçüm kaydir ile tasi’yi kendi main’inde yan yana koşturur; ikisi de n üzerinden
çağrılır, ikisi de kaynakta (n) yazımıyla görünür, ama biri değerle biri referansla alır.
// deger-referans.cpp — ayni cagri yazimi, farkli parametre bildirimi
#include <cstdio>
struct Nokta {
int x = 0;
};
static void kaydir(Nokta p, const Nokta* disaridaki) {
p.x += 1;
std::printf("kaydir: p disaridaki nesneyle ayni mi %s\n",
&p == disaridaki ? "evet" : "hayir");
}
static void tasi(Nokta& p, const Nokta* disaridaki) {
p.x += 1;
std::printf("tasi: p disaridaki nesneyle ayni mi %s\n",
&p == disaridaki ? "evet" : "hayir");
}
int main() {
Nokta n;
kaydir(n, &n);
tasi(n, &n);
return 0;
}
kaydir: p disaridaki nesneyle ayni mi hayir tasi: p disaridaki nesneyle ayni mi evet
SK2. kaydir(n) içindeki p, çağırandaki n’den ayrı bir nesnedir — kopya sınıfı.
tasi(n) içindeki p, çağırandaki n’nin kendisidir — kendisi sınıfı. Çağrı yazımı
ikisinde de (n)’dir; farkı yaratan tek şey işlevin bildirimidir: parametre tipi
Nokta mı Nokta& mı.
C++‘ın C’den Farkları
Bu ders yalnız değerle aktarımı ölçtü; C++‘ın C’ye eklediği öğelerin geri kalanı kursun
sonraki derslerinde tek tek ölçülür. Kısaca sayılırsa: class ve erişim denetimi, referans
tipi, işleç aşırı yükleme, new/delete, ad alanları, ayrı bir bool tipi ve C’den daha
sıkı bir tip denetimi — örneğin void*‘tan başka bir işaretçi tipine örtük dönüşüm C’de
serbesttir, C++’ta derleme hatasıdır.
Sınırlayıcı Ölçüm
Bu dersin ölçtüğü ifade kopya sınıfındadır, ama ölçümün kendisi bir sınırı da gösterir:
çağrı noktası hangi sınıfla çalışılacağını söylemez. kaydir(n) ile tasi(n) kaynakta
birebir aynı görünür — ikisi de bir tanımlayıcıyı parantez içinde n ile çağırır. Hangi
nesneyle çalışıldığını yalnız işlevin bildirimindeki parametre tipi belirler; okuyan biri bu
kararı çağrı satırından değil, imzadan çıkarmak zorundadır.
Özet
- Kursun ölçü ekseni: bir ifade kendisi, kopya, parça ya da geçici sınıflarından biriyle çalışır; on dört ölçüm bu dağılımı 5/3/4/2 olarak sabitler.
- Bu dersin ölçtüğü ifade kopya sınıfındadır: değerle aktarılan
Nokta, çağıranın nesnesinden ayrı bir kopyayla çalışır. - Aynı
(n)çağrı yazımı, parametre referans olduğunda kendisi sınıfına geçer; sınıfı belirleyen çağrı değil bildirimdir. - C++‘ın C’ye eklediği öğeler (sınıf, referans, aşırı yükleme,
new/delete, ad alanı,bool, daha sıkı tip denetimi) kısaca sayıldı; her biri ilgili derste derinleşir. - Derleme uyarısızdır ve çıktı üç ayrı koşumda birebir aynıdır; ölçüm tahmine değil koşuma dayanır.
Sonraki Adım
Bu ders bir işlevin parametre tipine göre hangi nesneyle çalıştığını gösterdi; bir sonraki
ders aynı soruyu bir üye işlev için sorar — h.yatir(50) çağrıldığında this’in hangi
nesneyi gösterdiğini ve private erişiminin nesne değil sınıf düzeyinde çalıştığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.