Making Class Objects Act Like Values

Kategori: C++ , 16 Kasım 2019 , JanFranco


Çoğu dilde (C++ dahil) built-in tipler değer gibi davranır. Örneğin bir tipten obje kopyaladığımızda, orjinal ve kopyalanmış obje aynı değere sahiptir. Bu özelliği bir önceki bölümde kendi sınıfımızı yazarken kullandık. Bu bölümde ise yine built-in tiplerde gördüğümüz önemli bir özelliği kendimiz yapmaya çalışacağız: otomatik tür dönüşümleri. Örneğin int ve double tipte iki değeri toplamaya çalışırsak, compiler otomatik olarak int tipini double tipe dönüştürür. Bir önceki bölümde kendi vektör sınıfımızı yazmıştık. Bu bölümde yeni öğreneceğimiz özellikleri string sınıfını baştan yazarak göreceğiz. Str isimli basit bir sınıf oluşturalım:


class Str {
    public:
	typedef Vec<char>::size_type size_type;
	Str() { }
	Str(size_type n, char c): data(n, c) { }
	Str(const char* cp) {
	    std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
	}
	template<class In> Str(In b, In e) {
	    std::copy(b, e, std::back_inserter(data));
	}
    private:
	Vec<char> data;
};
Burada bir önceki bölümde yazdığımız Vec sınıfını kullandık. private alanda Vec sınıfından data isimli bir obje ürettik. public alanda da 4 farklı constructor tanımladık. İlk constructor boş bir string oluşturuyor. İkinci constructor n kadar c karakteri depoluyor. Üçüncü constructor gönderilen bir karakter dizisini kopyalayarak string oluşturuyor. Dördüncü constructor, bir önceki constructor ile aynı işi yapıyor fakat burada karakter dizisi yani string değil iki iterator alıyor. Şimdi string operasyonlarını implement etmek için önce bu operasyonları görelim:


cin >> s
cout << s
s[i]
s1 + s2
İlk satırda string okunuyor, ikinci satırda string yazılıyor, üçüncü satırda stringin bir karakterine erişiliyor, dördüncü satırda iki string birleştiriliyor. Karakter okuma basit bir operasyon, bu operasyonu implemente ederek başlayabiliriz:


class Str {
    public:
	...
	char& operator[](size_type i) { return data[i]; }
	const char& operator[](size_type i) const { return data[i]; }
    private:
	Vec<char> data;
};
[] operatörlerini overload ettik. Böylelikle Str sınıfından ürettiğimiz objelerde bu operatörleri kullanarak belirli karakterlere erişebiliriz. << operatörünü de overload edelim. Bunun için ilk olarak size() fonksiyonu oluşturalım:


class Str {
    public:
	size_type size() const { return data.size(); }
	...
};
size() fonksiyonu bize stringin boyutunu verecek. Şimdi operatörü overload edebiliriz:


ostream& operator<<(ostream& os, const Str& s){
    for (Str::size_type i = 0; i != s.size(); ++i)
	os << s[i];
    return os;
}
<< operatörü kullanıldığında objedeki datanın karakterleri sırasıyla os'a yazılacak. Şimdi de >> operatörünü tanımlayalım:


istream& operator>>(istream& is, Str& s){
    s.data.clear();
    char c;
    while (is.get(c) && isspace(c))
	;
    if(is) {
	do s.data.push_back(c);
	while (is.get(c) && !isspace(c));
	if (is)
 	    is.unget();
    }
    return is;
}
İlk olarak objede herhangi bir veri varsa temizledik. Eğer girilen karakter boşluk değilse karakter direk data'da depolanacak. Eğer karakter boşluksa ilk while döngüsü aktif olacak ve alt satıra geçilemeyecek. Eğer girilen karakter boşluk değil fakat sonradan girilen karakterler boşluksa ikinci while döngüsündeki koşul sağlanmamış olacak ve unget() fonksiyonu ile alınan boşluk karakteri geri gönderilecek. Fonksiyonu yazdık ve çalışma mantığı bu şekilde fakat bu fonksiyon bu halde compile edilemez. Bunun sebebi data'nın private alanda tanımlanmış olması. data'nın dışarıdan erişilememesi fakat aynı sınıf içerisinden erişilebilmesi gerekli. Bu durumda friend anahtar kelimesini kullanabiliriz:


class Str {
    friend std::istream& operator>>(std::istream&, Str&);
    public:
	...
    ...
};
+ operatörünü tanımlayalım. Sınıfımızdan oluşan objeyi yani bir string ifadeyi başka bir string ifade ile birleştirmek s = s + t işlemi ile ifade edilebilir. Bu da s += t işlemidir aslında. Bu durumda ilk olarak += operatörünü tanımlayalım:


Str& operator+=(const Str& s) {
    std::copy(s.data.begin(), s.data.end(), std::back_inserter(data));
    return *this;
}
Şimdi sınıfın dışında + operatörünü tanımlayalım:


Str operator+(const Str& s, const Str& t) {
   Str r = s;
   r += t;
   return r;
}
Son özelliklerle birlikte Str sınıfının son hali:


class Str {
    friend std::istream& operator>>(std::istream&, Str&);
    public:
        Str& operator+=(const Str& s) {
            std::copy(s.data.begin(), s.data.end(), std::back_inserter(data));
            return *this;
        }

        typedef Vec<char>::size_type size_type;
        Str() { }
        Str(size_type n, char c): data(n, c) { }
        Str(const char* cp) {
            std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
        }
        template<class In> Str(In i, In j) {
            std::copy(i, j, std::back_inserter(data));
        }

        char& operator[](size_type i) { return data[i]; }
        const char& operator[](size_type i) const { return data[i]; }
        size_type size() const { return data.size(); }
    private:
        Vec<char> data;
};

ostream& operator<<(ostream& os, const Str& s){
    for (Str::size_type i = 0; i != s.size(); ++i)
	os << s[i];
    return os;
}

istream& operator>>(istream& is, Str& s){
    char c;
    while (is.get(c) && isspace(c))
	;
    if(is) {
	do s.data.push_back(c);
	while (is.get(c) && !isspace(c));
	if (is)
 	    is.unget();
    }
    return is;
}

Str operator+(const Str& s, const Str& t) {
   Str r = s;
   r += t;
   return r;
}
Otomatik tür dönüşümünü görmek adına Student_info sınıfımıza aşağıdaki satırı ekleyelim:


class Student_info {
    public:
	operator double();
	// ...
};
Şimdi Student_info sınıfından objeler tutan bir vektör oluşturalım:


vector<Student_info> vs;
Bu vektörü Student_info sınıfından objeler ile doldurduğumuzu düşünelim. Şimdi o objelerdeki bilgileri çekelim ve bir double değişkene atalım:


double d = 0;
for (int i = 0; i != vs.size(); ++i)
    d += vs[i];

cout << "Average grade: " << d / vs.size() << endl;
Student_info sınıfında operator double(); satırını yazdığımız için vs[i] değeri otomatik olarak double tipine çevrilmiş oldu. Aynısını Str sınıfı için yapalım:


class Str {
    public:
	operator char*(); // added
	operator const char*() const; // added
	// as before
    private:
	Vec<char> data;
};
Otomatik tür dönüşümünü anlamak için bir örnek daha yapalım:


#include <iostream>
#include <cmath>

using namespace std;

class My_Complex {
   private:
      double real, imag;
   public:
      My_Complex(double re = 0.0, double img = 0.0) : real(re), imag(img) //default constructor{}
      double mag() {    //normal function to get magnitude
         return getMagnitude();
      }
      operator double () { //Conversion operator to gen magnitude
         return getMagnitude();
      }
   private:
      double getMagnitude() { //Find magnitude of complex object
         return sqrt(real * real + imag * imag);
      }
};

int main() {
   My_Complex complex(10.0, 6.0);
   cout << "Magnitude using normal function: " << complex.mag() << endl;
   cout << "Magnitude using conversion operator: " << complex << endl;
}
Bu örnekte mag() fonksiyonu ile sonuca ulaşabileceğimiz gibi operator double() tanımı sayesinde direk complex diyerek de sonuca ulaşabiliriz.


Sonraki Yazı: Using Inheritance
Yorumlar

Henüz bir yorum bulunmuyor.
Yorum bırakın