C++ Classes & Data Abstraction

  • C++ dilinin en güçlü olduğu alanlardan bir tanesi data abstraction.

  • Nesne yönelimli programlama data abstraction demek değil. Data abstraction anlamı veri soyutlama.

  • Problem domaininde ki bir veriyi yazılımsal olarak nasıl temsil edeceğim demek. Hikaye şöyle, user defined türlerimiz olmasa elimizde sadece primitive türler var. Bu türlerle modellenebilecek veriler var. Ama problem domaininde bizi ilgilendirecek verilerin çoğu daha komplex. a + bi gibi bir sayı gibi yada bir üniversitede okuyan bir öğrencinin bilgileri.(tüm bilgileri) En güzel örnek olarak programlama kitaplarında genelde tarih bilgisi veriliyor. Tarih bilgisi tipik olarak gün ay yıl. Dilin bize sunduğu avantajlar olmasaydı şöyle yapıcaktık, 3 tane değişken alacaktık, dilin kuralları gereği bu 3 değişken arasında bir bağlantı kurulmasa da dilin araçlarıyla logic olarak biz bir bağlantı kuracaktık. Bunun da getirdiği bir sürü problem olacaktı. Data abstraction yönünden güçlü olması demek daha komplex türleri primitive türleri kullanıyormuş gibi kolaylıkla kullanabilmektir. İşte bu konuda C++ dilinde class(sınıf)lar var.

  • Şimdi bahsedeceğim C++ dilinin sınıf kavramı. Nesne yönelimli programlamanın sınıfı değil.

    • Sınıf konusu C++ dilinde user defined bir tür.
    • class bir keyword.
    • Bu türü doğrudan Data ismi ile kullanılabilir
class Data{}; // semicolon unutma - noktalı virgül
Data x; // bir nesne tanimladik
Data *ptr = new Data; // bir dinamik nesne tanimladik
  • Blok içinde sınıfın öğelerini tanıtacağız

Sınıf Öğeleri

  • Veri öğeleri (Data member)

  • Fonksiyonlar öğeleri (Member function)

  • Tür öğeleri (Nested type)

  • C de birlikler ve yapılar artık c++ da sınıf oldu.

  • Sınıf üye fonksiyonları semantik bir maske, gizleme.

  • class değilde struct ile bir sınıf oluşturulsaydı default erişim bölümü public olucaktı, public class yerine struct kullanılır

  • class bir scope fakat public, private, protected birer scope değil.

  • C++ dilinde sınıf dışı gördüğümüz fonksiyonlara global fonksiyon diyeceğiz. Sınıf içi tanımlanan fonksiyonlara member function.

  • Structlardan farklı olarak, sınıfların erişim ayrıcalığı var.

  • Sınıfın public, protected ve private bölümleri var.

// ERISIM BELIRLEYICILERI
class Data
{
public:
/////
private:
/////
protected:
/////
}
  • private sınıfın kendi kodlarına açık fakat sınıfın müşterilerine kapalı. compile timeda kontrol edilecek.

  • protected, kalıtımla birlikte önem kazanacak(inheritance). Sınıfın clientlarına kapalı, inherit edilince açık.

// Class Scope - Duplicate member x
#include <iostream>

using namespace std;

// CLASS BIR SCOPE, Bir scope icinde bir ismi birden fazla varliga veremeyiz
class v
{
int x; // CLASS SCOPE
public:
double x; // CLASS SCOPE, AYNI ISIM VEREMEYIZ
}
  • .(nokta) operatörü

  • ->(ok) operatörü

  • :: (çözünürlük operatörü)

  • Aynen yapılarda olduğu gibi bir sınıf nesnesi yoluyla sınıfın öğesine nokta yoluyla ulaşabiliyoruz. Önemli olan burada öğe aramanın nasıl yapıldığı.

  • Derleyici . operatörünün önce sol operandına bakar ve bir sınıf türünden olduğunu anlarsa sağ operandını class scope’ta arar.

  • Bir sınıf türünden pointer ise ptr->x ok operandının solu sınıf mı, sağ operandını o class içinde arar

myclass.*ptr;
ptr->x;

Name Lookup

  • Derleyici aradığı ismi bulunamassa her zaman error. Bulursa geçerli diye birşey yok. Aramada 3 durum var

    1. nokta operatörü
    2. -> operatörü (o sınıf türünden adres olucak)
    3. :: operatörü (sınıf türünden olucak)
  • operatörlerinin sağ operantları class scope ta aranır

Myclass m;
m.x; // isim arama önce yapılıyor, sonra class private olursa x aradım buldum, private alanına erişim hatası
  • private kodlarını gizlemek için PIMPL idiomu kullanılır, bu idiomun hem compile time hemde run time maliyeti var.

  • En katı korunan kısım private, hem clientlara hem de türetilmiş sınıflara kapalı

  • protected clientlara kapalı, türetilmiş sınıflara açık

  • public herkese açık

  • C de şöyle diyebilirdik, client bunları bil ama kullanma, ben senin bu öğelere doğrudan erişmeni istemiyorum gibi.

PIMPL IDIOMU HAKKINDA

  • Madem amaç private bölümü gizlemek, engellemek o zaman neden private sınıf tanımında yazıyoruz ki? Muhtemelen başlık dosyasında olucak, eee madem clientların bilmesini istemiyorum başlık dosyasına koyarsam clientlar bunu görmeyecek mi? Malesef görecekler, böyle bir açık var ama teknik zorunluluktan oluyor. Neden? Çünkü client ın bunu kullanması yanında cpp dosyasıda bunu kullanıyor. Özel önlem alınabilir ama default durum client bunu görebiliyor, bunları göstermemenin yolu var buna pimpl idiomu deniyor. (Pointer Implemantatıon)

  • Compile time maliyeti var, runtime maliyeti var (pimpl idiomu)

  • Veri elemanları private bölümü, member functionlar sınıfın public bölümüne, sınıfın public bölümü genelde bu sınıf için işleri yapıcak fonksiyonlardan oluşacak

  • Pimpl idiomu için ayrıcı bir makale yazıcam. Konuyu dağıtmamak için devam ediyoruz….
    Bir önce ki makalede de belirttiğim gibi :: çözünürlük operatörü ile ilgili şöyle ufak bir örnek yapalım.

// :: operatoru
#include <iostream>


using namespace std;

int x = 56;

int main()
{
int x = 20;

cout << x << endl; // 20
cout << ::x << endl; // 56

return 0;
}
// Global alanda x isimli birsey bulunamadi

#include <iostream>


using namespace std;


int main()
{
int x = 20;

cout << x << endl;
cout << ::x << endl; // No member named 'x' in the global namespace; did you mean simply 'x'?

return 0;
}
  • Üye fonksiyon uydurmasının anlamı şu, daha önce global scope ta bulunan fonksiyonlar artık class scope içine dahil oluyorlar, amaç clienta daha iyi bir interface vermek, mydate.set() mydate in set fonksiyonu gibi.
    Artık üye fonksiyonlar geldi, global fonksiyonlar hiç kullanılmayacak mı? -Hayır alakası yok, C de zaten başka yolumuz yok ama C++ da sınıfla ilgili iş yapan fonksiyonların artık çoğu class içinde olucak ama global fonksiyonlarda olabilir
//Function Member

#include <iostream>

using namespace std;

int x = 9999;

class Data {
int x;
public:
void set(int val);
void display();
};

void Data::set(int i)
{
x = i;
}

void Data::display()
{
cout << "x = " << x << endl;
}

int main()
{
Data mydata;

mydata.set(100);
mydata.display();

return 0;
}
  • Uygulama pratiğinde her sınıf için bir header dosyası oluşturulur

  • Header dosyası oluştururken ilk yapacağımız multiple inclusion, amaç bir başlık dosyasını birden fazla import edilmesini engellemek. https://en.wikipedia.org/wiki/Include_guard

// Multiple Inclusion || Include Guard
#ifndef MYHEADER_H
#define MYHEADER_H
// header file contents go here..
#endif // MYHEADER_H
// Scope

#include <iostream>

using namespace std;

int x = 9999;

class Data {
int x;
public:
void set(int val);
void display();
};

void Data::set(int i)
{
x = i;
}

void Data::display()
{
int x = 3333;
cout << "x = " << x << endl; // x = 3333
cout << "x = " << Data::x << endl; // x = 100
cout << "x = " << ::x << endl; // x = 9999
}

int main()
{
Data mydata;

mydata.set(100);
mydata.display();

return 0;
}
#include <iostream>

using namespace std;


class Date {
char str[1000];
public:
void set(int d, int m, int y);
void display();
};



void Date::set(int d, int m, int y)
{
sprintf(str, "%d/%d/%d", d, m, y);
}

void Date::display()
{
cout << str << endl; // 5/12/1997
}



int main()
{
Date mydate;

mydate.set(5, 12, 1997);
mydate.display();

return 0;
}
#include <iostream>

using namespace std;

class Myclass {
int x, y;
public:
void func();
};



//////myclass.cpp


void Myclass::func()
{
cout << "this = " << this << endl; // this = 0x7fff5fbff760
}

int main()
{
Myclass m;

m.func();

cout << "&m = " << &m << endl; // // &m = 0x7fff5fbff760

return 0;
}

Adres Bilgisi

  • Sınıfta public veya private önce olacağı durumlar olucak, belli senaryolar durumda bunda özgürüz.

  • Global bir fonksiyon ile sınıfın üye fonksiyonu asla overload değildir.

  • Sınıfın üye fonksiyonu default u nonstatic

  • Eğer sınıf içinde bir fonksiyonda static keywordu varsa bunu C deki ile karıştırmayacağız. Bu sınıf nesnesinin adresine ihtiyaç duymayan demek

  • non static, nesneye ihtiyacı var(static keywordu almayanlar)


Sınıfın Üye Fonksiyonları Arasında İsim Arama (name lookup)

  • C dilinde block scope, global scope alanı vardı, şimdi class scope eklendi.(C++ için)

  • Block scope un class scopa önceliği var.

  • isim arama yapılır, eğer aranan isim bulunursa arama durur (javada böyle değil), yani isim 1 kez aranır, bulunan isimden sonra asla isim arama yapılmaz.

//Scope && Erişim Konusu

#include <iostream>

using namespace std;


class Data {
int x;
public:
void func();
};

Data g;


void Data::func()
{
Data y;
// sinifin uye fonksiyonu, private bolume erisebilir
x = 12; // class scope, bu fonksiyon hangi x icin cagirirsam onun ismi
g.x = 13; // global nesne, private erisimi var
y.x = 14; // yerel nesne, private erisimi var
}

Const Öğeler

  • Const öğelerin amacı nesneyi değiştirmek değil, bu bir get fonksiyonu, yani bu nesnenin varlık nedeni nesneyi değiştirmek değil

  • Const olmayan bir üye fonksiyon const üye fonksiyonu çağırabilir ama const üye fonksiyon const olmayan bir üye fonk çağıramaz.

  • Const overloading (yine function overloading tabi ama const)

  • Native erişim biçimi, this göstericisi ile ve çözünürlük operatörü ile

  • C++ dilinde this->x olarak kullanmayın… (java, c# taki gibi değil), normal koşullarda bunu kullanma

  • Sınıfın veri elemanlarına this gösterici ile erişilebiliriz


This Pointerı

  • Bu fonksiyon hangi nesne için çağırıldı ise o nesnenin adresi

  • *this demek o adresteki nesne

  • this kendisi const olan bir gösterici

// this

#include <iostream>

using namespace std;

class Myclass {
int x, y;
public:
void func(Myclass &r);

};

Myclass g;

void Myclass::func(Myclass &r)
{
*this = r;
////
}

int main()
{
Myclass m1, m2;
////

m1.func(m2); // m2'yi m1'e atiyoruz
// *this m1 demek zaten, *this nesnenin kendisi demek
return 0;
}
  • Nesnenin kendisi ise nokta operatörü

  • Pointer ise ok(->) operatörü kullan

// Genel Bir Ornek

#include <iostream>

using namespace std;

///date.h
class Date {
int md, mm, my;
public:
Date &set(int d, int m, int y);
void display()const;
int getMonthDay()const;
int getMonth()const;
int getYear()const;
};

//////////date.cpp
//include "date.h"

Date &Date::set(int d, int m, int y)
{
md = d;
mm = m;
my = y;
return *this;
}


int Date::getMonthDay()const
{
return md;
}

int Date::getMonth()const
{
return mm;
}

int Date::getYear()const
{
return my;
}

void Date::display()const
{
cout << md << "/" << mm << "/" << my << endl;
}

int main()
{
Date x;

x.set(12, 4, 1999).display(); // 12/4/1999

int val = x.getMonth();

Date y;

y.set(23, 5, 1987);


return 0;
}

Ufak bir tekrar

  • Sınıflarda .(nokta) operatörü

  • Pointerlarda ->(ok) operatörü

  • :: çözünürlük operatörü(erisim) Myclass::x

  • Sadece ::x dersek global değişken

  • :: 2 kullanımı var… SINIFLARDA ve NAMESPACE alanlarında

  • cons olan fonksiyon, cons olmayan üye fonksiyonu çağıramaz

  • cons olmayan bir fonksiyon, cons olan fonk çağırabilir


Üyeleri 3 ayrı erişim var

  • Native doğrudan erişim x = 45; y = 26; gibi

  • this ile erişim

  • çözünürlük operatörü(::) ile erişim

Dereference edersek;

    • this, Myclass m; m.func();
  • *this m’nin kendisi yani hangi nesne için çağırılırsa o nesnenin kendisi

  • this kendisi const olan bir göstericidir. Data *const this;

  • this atama yapamayız ama *this e atama yapabiliriz

this -> x++; ile (*this).x++; aynıdır