> 'operator overloading' : Aslında bir fonksiyon çağrısıdır. Eğer çağrılan bu fonksiyon; >> Bir 'global namespace functions' ise bu duruma 'global operator functions' denir. >> Bir 'class member functions' ise de bu duruma 'member operator functions' denir. Fakat bu fonksiyonlar 'static' olamıyorlar. 'non-static' OLMAK ZORUNDALAR. >> Derleyici, statik olarak, yani derleme zamanında koda bakarak, hangi fonksiyonun çağrılacağına karar veriyor. Tıpkı 'FO' gibi. Dolayısıyla 'Çalışma Zamanına' doğrudan bir etkisi de yoktur. >> BU ARAÇIN EN ÖNEMLİ FAYDASI, 'client programmer' IN SİZİN KÜTÜPHANENİNİZİ KULLANIRKEN İŞİNİ KOLAYLAŞTIRMAKTIR. >> 'STL' isimli kütüphanedekilerin bir çoğu bu MEKANİZMAYI KULLANDIĞI İÇİN İYİ ÖĞRENMELİYİZ. >> Temel türler için kullanılamazlar. Çünkü bu mekanizmadan yararlanılması için en az bir operandın 'user-defined type' OLMASI GEREKMEKTEDİR. >> Bazı operatörler için bu mekanizma kullanılamıyor, dilin kuralları gereği iptal edildiler. Örneğin, ':?', 'sizeof()', '::', '.', '.*', 'typeid' gibi operatörler için bu mekanizma kullanılamıyor. >> Ya 'global namespace function' ya da 'non-static member function' olacak iş bu mekanizma fonksiyonları. >> Olmayan operatörler(yani C++ operatör setinde olmayan operatörler) için böyle bir mekanizma kullanılamaz. Örneğin, 'x @ y' şeklinde bir ifade için bu mekanizma işlemez. Çünkü '@' şeklinde bir operatör mevcut değildir. >> Bazı operatörler için bu mekanizma kullanılırken, arka plandaki fonksiyonun 'non-static member functions' OLMASI ZORUNLU. Bunlar '[]', '->', '()', 'tür-dönüştürme' ve '='. >> Arka plandaki fonksiyonlara keyfî isim veremiyoruz. Onların isimleri önceden belirlenmiştir. Örneğin, '+' operatörü için arka plandaki fonksiyon 'operator+' şeklinde. >> Biri hariç, arka plandaki fonksiyonlar varsayılan argüman ALAMIYORLAR. '()' operatörü için arka plandaki fonksiyon varsayılan argüman alır. >> Arka plandaki fonksiyonları kullanıcı olarak bizler de çağırabiliriz, yani isimleri ile çağırabiliriz. >> Operatörlerin 'operant sayıları(arity)' DEĞİŞTİRİLEMEZ. Örneğin, >>> Unary Opt. : Bir operand alan operatörlerdir. Bunlar, >>>> '!' operandı : Eğer bu operand için iş bu mekanizmayı kullanacaksak, arka plandaki fonksiyon hem 'global namespace function' hem de 'non-static member function' olabilir. Eğer ÜYE FONKSİYON olursa, sınıf nesnesinin kendisi için çağrılacağı için, harici bir parametre almayacaktır. Fakat 'global function' olursa bir adet parametre alması gerekmektedir. * Örnek 1, '!x' için; x.operator!(); // Üye fonksiyon olması durumunda. Çünkü sınıf nesnesi için çağrıldığından, gizli parametre // olarak ilgili nesnenin adresini almaktadır. '*this = x'. x.operator!(int); // Üye fonksiyon olması durumunda. ARTIK BU ÇAĞRI SENTAKS HATASIDIR. operator!(x); // Global fonksiyon olması durumunda. Çünkü artık sınıf nesnesi için çağrılmadıklarından, // sadece bir adet parametre alabilirler. operator!(x, y); // Global fonksiyon olması durumunda. ARTIK BU ÇAĞRI SENTAKS HATASIDIR. operator!(); // Global fonksiyon olması durumunda. ARTIK BU ÇAĞRI SENTAKS HATASIDIR. >>> Binary Opt. : İki operand alan operatörlerdir. Bunlar, >>>> '<' operandı : Eğer bu operand için iş bu mekanizmayı kullanacaksak, arka plandaki fonksiyon hem 'global namespace function' hem de 'non-static member function' olabilir. Eğer ÜYE FONKSİYON olursa, sınıf nesnesinin kendisi için çağrılacağı için, harici bir adet parametre alabilir. Bu durumda soldaki operand için bu fonksiyon çağrılacak olup, sağdaki operand ise argüman olarak geçilecektir. Fakat 'global function' olursa iki adet parametre alması gerekmektedir. * Örnek 1, ' x> C ve C++ dilinde iki anlam taşıyan operatörler '+', '-', '&' ve '*' operatörleridir. Bunlardan, >>> '+' operatörü 'Binary Opt.' olarak kullanıldığında TOPLAMA İŞLEMİ, 'Unary Opt.' olarak kullanıldığında İŞARET DEĞİŞTİRME İŞLEMİ olarak çalışır. >>> '-' operatörü 'Binary Opt.' olarak kullanıldığında ÇIKARMA İŞLEMİ, 'Unary Opt.' olarak kullanıldığında İŞARET DEĞİŞTİRME İŞLEMİ olarak çalışır. >>> '&' operatörü 'Binary Opt.' olarak kullanıldığında 'Bit-wise AND', 'Unary Opt.' olarak kullanıldığında 'Address of' İŞLEMİ olarak çalışır. >>> '*' operatörü 'Binary Opt.' olarak kullanıldığında ÇARPMA İŞLEMİ, 'Unary Opt.' olarak kullanıldığında 'derefence' İŞLEMİ olarak çalışır. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, //.. class Data{ public: Data operator+(); // İŞARET DEĞİŞTİRME OLARAK KULLANILDIĞINDA. Data operator+(Data); // TOPLAMA İŞLEMİ İÇİN KULLANILDIĞINDA. Data operator*(); // Dereference İşlemi Data operator*(Data); // ÇARPMA İŞLEMİ İÇİN KULLANILDIĞINDA. }; >> Bu mekanizma ile OPERATÖRLERİN ÖNCELİK SEVİYESİNİ VE ÖNCELİK YÖNÜNÜ DEĞİŞTİREMEYİZ. * Örnek 1, Bu operatörlerin fonksiyonlarının 'global namespace functions' şeklindedir. //.. class Data{ //.. }; Data& operator++(Data&); Data& operator*(const Data&, const Data&); Data& operator/(const Data&, const Data&); Data& operator-(const Data&, const Data&); Data& operator>(const Data&, int); int main() { Data a,b,x,y; auto f1 = ++a * b - x / y > 10; // Derleyici, yukarıdaki ifadeyi şu şekilde ele alacaktır; // i. (++a).... // ii. ((++a) * b)... // iii. ((++a) * b) - (x/y)... // iiii. (((++a) * b) - (x/y)) > 10; // Peki, yukarıdaki bu ifadeyi fonksiyonları biz çağırarak yazarsak, // i. operator++(a)... // ii. operator*(operator++(a), b)... // iii. ............operator/(x, y)... // iiii. operator-(operator*(operator++(a), b), operator/(x, y))... // iiiii. operator>(operator-(operator*(operator++(a), b), operator/(x, y)), 10); } * Örnek 2, Bu operatörlerin fonksiyonları 'non-static member functions' şeklindedir. //.. class Data{ public: Data& operator++(); Data operator*(const Data&); Data operator-(const Data&); Data operator/(const Data&); Data operator>(int); }; int main() { Data a,b,x,y; auto f1 = ++a * b - x / y > 10; // Derleyici, yukarıdaki ifadeyi şu şekilde ele alacaktır; // i. (++a)... // ii. ((++a) * b)... // iii. ((++a) * b) - (x/y)... // iiii. (((++a) * b) - (x/y)) > 10; // Peki, yukarıdaki bu ifadeyi fonksiyonları biz çağırarak yazarsak, // i. a.operator++()... // ii. a.operator++().operator*(b)... // iii. a.operator++().operator*(b)...x.operator/(y)... // iiii. a.operator++().operator*(b).operator-(x.operator/(y))... // iiiii. a.operator++().operator*(b).operator-(x.operator/(y)).operator>(10); } >> Geri dönüş değerinin türü tamamiyle kodu yazan programcıya bağlıdır. Dilin sentaksı bir zorlamada bulunmuyor. Fakat lojik açıdan bir takım deyimleri takip etmek de güzeldir. Bir diğer deyişle 'primitive' türlerdeki davranışlara ne kadar çok yaklaşırsak o kadar iyi. * Örnek 1, //.. 'a > b' ifadesinin döndürdüğü değer genel olarak 'true/false' şeklinde 'bool' türden bir değişken şeklinde olması beklenir. Çünkü ilgili operatörü bir 'user-defined' tür ile kullandığımız zaman 'a.operator>(b)' fonksiyonu çağrılacak ve bu fonksiyon çağrısının geri döndürdüğü değer, yukarıdaki ifadenin geri döndürdüğü değer olacak. Buradan hareketle bu fonksiyonun 'bool' türden bir değer döndürmesi akla mantığa daha yakındır. Fakat 'int' türden veya 'string' türden veya 'user-defined' türden bir değişken döndürmemiz SENTAKS HATASINA YOL AÇMAZ. >>> Geri dönüş değerinin referans mı olması gerek yoksa o türden 'call-by-value' şeklinde mi olması gerek sorusuna cevap olarak; " 'primitive' türlerdeki davranışlara ne kadar çok yaklaşırsak o kadar iyi", şeklinde verebiliriz. Buradan hareketle iş bu operator 'L-Value' değer döndürüyor ise iş bu fonksiyon da referans yoluyla değer döndürmeli. 'PR-Value' değer döndürüyorsa, fonksiyonu da 'call-by-value' şeklinde değer döndürmeli. >>>> 'primitive' türlerde '++' operatörü 'L-Value' değer döndürmektedir. Dolayısıyla buna ait olan fonksiyon da referans döndürmelidir ki taklit edebilsin. >>>> 'primitive' türlerde '+' operatörü 'Binary Opt.' olarak kullanıldığında 'PR-Value' değer döndürmekte. Dolayısıyla buna ait fonksiyon da 'call-by-value' döndürmelidir ki doğal kullanıma uygun olsun. >>>> 'primitive' türlerde '*' operatörü 'Unary Opt.' olarak kullanıldığında 'L-Value' değer döndürmekte. Dolayısıyla buna ait fonksiyon da referans döndürmeli ki doğal kullanıma uygun olsun. >> Peki "iş bu fonksiyonların parametreleri nasıl olmalı", sorusuna cevap olarak şunu diyebiliriz: 'primitive' türlerdeki kullanımlarda operantlar değişiyor mu değişmiyor mu, ona bakmalıyız. Bir nevi "Yan etki var mı yok mu" sorusunun cevabıdır. * Örnek 1, '+' operatörünün 'Binary Opt.' olarak kullanılması, //.. Data operator+(Data other, Data otherTwo); // I Data operator+(const Data& other, const Data& otherTwo); // II class Data{ public: Data operator+(const Data& other)const; // III }; // Yukarıdaki ilgili operatör fonksiyonları, '+' operatörünün 'Binary Opt.' olarak kullanılması için // yazılmıştır. Çünkü bu şekildeki kullanımda, geri döndürülen değer 'PR-Value' şeklinde olduğundan, // fonksiyonumuz 'call-by-value' şeklinde değer döndürmüştür. // 'I' numaralı fonksiyon şeklinde tanımlayabiliriz çünkü '+' operatörünün operantları TOPLAMA İŞLEMİNDEN // sonra değerlerini korumaktadır. // 'II' numaralı fonksiyonu da 'I' numaralı fonksiyona alternatif olarak yazabiliriz. // 'III' numaralı fonksiyonu da 'I' ve 'II' numaralılara alternatif olarak yazabiliriz. Fakat burada dikkat // edilmesi gereken nokta, iş bu fonksiyonun 'const' bir üye fonksiyon olması. Çünkü TOPLAMA İŞLEMİ // sonrasında operantlar değişmiyor. Ama bu fonksyion da bir sınıf nesnesi için çağrılıyor. Dolayısıyla bu // fonksiyonu çağıran nesnenin de korunması gerekiyor. Eğer biz bu fonksiyonu 'const' olarak betimlemeseydik, // 'this' göstericisi üzerinden nesneyi değiştirme imkanı verirdik ki bu da FELSEFİ OLARAK YANLIŞ. Zaten // 'other' isimli ikinci operandı 'const' olarak aldığımız için onu da korumuş oluyoruz. /* # ÖZETLE # * İlgili üye fonksiyonumuz 'call-by-value' değer döndürdü, çünkü 'primitive' türlerde '+' operatörü 'Binary Opt.' olarak kullanıldığında 'PR-Value' döndürmekte. * İlgili üye fonksiyonumuz 'const' bir nesneyi argüman olarak almakta, çünkü 'primitive' türlerde '+' operatörü 'Binary Opt.' olarak kullanıldığında operantlar değişmemekte. * İlgili üye fonksiyonumuz 'const' bir üye fonksyion, çünkü 'primitive' türlerde '+' operatörü 'Binary Opt.' olarak kullanıldığında operantlar değişmemekte. Dolayısıyla bu fonksiyonu çağıran nesnenin de değişmemesi lazım. */ * Örnek 2, '+=' operatörünün kullanılması, //.. class Data{ public: Data& operator+=(const Data& other); // I }; /* # ÖZETLE # * İlgili üye fonksiyonumuz 'call-by-reference' değer döndürdü, çünkü 'primitive' türlerde '+=' operatörü 'L-Value' döndürmekte. * İlgili üye fonksiyonumuz 'const' bir nesneyi argüman olarak almakta, çünkü 'primitive' türlerde '+=' operatörü sağ taraftaki operandı değiştirmemekte. * İlgili üye fonksiyonumuz 'const' bir üye fonksyion değil, çünkü 'primitive' türlerde '+=' operatörü sol taraftaki operandı DEĞİŞTİRMEKTE. */ * Örnek 3, '<' operatörünün kullanılması, //.. class Data{ public: bool operator<(const Data& other)const; // I }; /* # ÖZETLE # * İlgili üye fonksiyonumuz 'bool' değer döndürdü, çünkü 'primitive' türlerde '<' operatörü 'bool' döndürmekte. * İlgili üye fonksiyonumuz 'const' bir nesneyi argüman olarak almakta, çünkü 'primitive' türlerde '<' operatörü iki operandı da değiştirmez. * İlgili üye fonksiyonumuz 'const' bir üye fonksyion, çünkü 'primitive' türlerde '<' operatörü iki operandı da değiştirmez. */ >> 'std::cout' nesnesinin 'operator' notasyonu ile kullanımı: * Örnek 1, //.. int main() { int x = 10; double dval = 4.5; std::cout << x << " " << dval << "\n"; // Bu ifadeyi 'operator' notasyonu ile yazalım. std::cout.operator<<(x).operator<<(" ").operator<<(dval).operator<<("\n"); // BU YANLIŞ, AYNI ÇIKTIYI VERMEZ. Çünkü ilgili sınıf nesnesi için 'string' türden argüman alan // 'operatör' fonksyionları 'GLOBAL OPERATOR FONKSYİON'. Fakat aynı zamanda ilgili sınıf nesnesinin // 'void*' türden argüman alan 'MEMBER OPERATOR FONKSİYONLARI' DA VAR. Dolayısıyla bizler o fonksiyonları // çağırmış olduk. Haliyle ilgili 'string' türden değişkenlerin ADRESLERİNİ yazdırmış olduk. /* # PROGRAM ÇIKTISI # 10 4.5 100x5645711990084.50x56457119900a */ // Ayıklamak gerekirse, // std::cout.operator<<(x) // => [10] // std::cout.operator<<(x).operator<<(" ") // => 10[0x564571199008] // std::cout.operator<<(x).operator<<(" ").operator<<(dval) // => 100x564571199008[4.5] // std::cout.operator<<(x).operator<<(" ").operator<<(dval).operator<<("\n") // => 100x5645711990084.5[0x56457119900a] // DOĞRU ŞEKİLDEKİ ÇEVRİMİ, operator<<(operator<<(std::cout.operator<<(x), " ").operator<<(dval), "\n"); // i. ...std::cout.operator<<(x)... // ii. ...operator<<(std::cout.operator<<(x), " ")... // iii. ...operator<<(std::cout.operator<<(x), " ").operator<<(dval)... // iiii operator<<(operator<<(std::cout.operator<<(x), " ").operator<<(dval), "\n"); /* # PROGRAM ÇIKTISI # 10 4.5 10 4.5 */ } * Örnek 2, 'std::ostream' sınıfının temsili örneklemesi, //.. Ostream& operator<<(Ostream& os, const char* p); // Birinci parametreye geçilen tekrar geri döndürülüyor. class Ostream{ public: Ostream& operator<<(int); // '*this' geri döndürülüyor. Ostream& operator<<(double); // '*this' geri döndürülüyor. Ostream& operator<<(long); // '*this' geri döndürülüyor. Ostream& operator<<(void*); // '*this' geri döndürülüyor. //.. }; // BURADAN DA ANLAŞILABİLECEĞİ GİBİ 'operator overloading functions' can be overlaoded!. // BURADAN DA GÖRÜLECEĞİ ÜZERE 'referans' SEMANTİĞİ İŞİMİZİ ÇOK KOLAYLAŞTIRMAKTADIR, KOD YAZARKEN DE KOD // OKURKEN DE. >> BU MEKANİZMANIN NİMETLERİNDEN YARARLANIRKEN, 'client' LARIN SEZGİSEL OLARAK, AKLA MANTIĞA UYGUN ŞEKİLDE, DOĞRU ÇIKARIMLAR YAPMALARINI SAĞLAYIN. Örneğin, 'string' sınıf türünden iki nesnemiz olsun. s1 ve s2 isimleri olsun. Genel olarak 's1 + s2' deyimi iki ismi birleştirileceği mesajı verir. İşte bu mesaja uygun fonksiyon yazmalıyız. Aynı sınıf türünden iki nesnemiz olsun. myFighter ve yourFighter isimleri olsun. 'myFighter >> yourFighter' deyimi akla ilk olarak bir şey getirmemektedir. Üçüncü bir göz koda bakar bakmaz ne yaptığını anlamaz. Dolayısıyla 'operator overloading' yerine kendimiz bir isimli fonksiyon yazmalıyız ki okunması daha da rahat olsun. >> STL, Standart Template Library, içerisindeki Containers ve Algorithms, BU MEKANİZMAYI KULLANDIĞINDAN, sınıflarımızda da bu operatör overload sınıflarını yazmalıyız. * Örnek 1, //.. class Fighter{}; int main() { vector fVec(1000); // 1000 tane savaşçımız var. Fighter myF; // Bu savaşçıyı, yukarıdaki vektör içerisinde arayacağım. find(fVec.begin(), fVec.end(), myF); // BU ÇAĞRI SENTAKS HATASINA NEDEN OLACAKTIR. Çünkü 'find()' fonksiyonu arama yaparken bizim sınıfımızın // 'operator==' fonksiyonunu çağırmakta. Öyle bir fonksiyon yazmadığımız için SENTAKS hatası aldık. } >>> 'operator==' ve 'operator<' çok sık çağrılanlar olduklarından, bunların yazılma ihtimali bir hayli yüksek. >> Hangi operatör için 'member function' hangi operatör için 'global function' yazmamıza karar veremiyorsa aşağıdaki genel konvensiyona uyabiliriz. >>> Sınıf nesnesini değiştiren fonksiyonları ÜYE FONKSİYON yapın. >>> Binary Simetric Operatörleri GLOBAL FONKSİYON yapın. >>>> 'Binary Simetric' : Burada kastedilen operantların birbirleri ile yer değiştirmesi durumunda aynı işlemin yapılması, sonuca bakılmıyor. * Örnek 1, //.. 'a > b' deyimi ile 'b > a' deyimleri sonucunda aynı işlemler yapılmakta fakat sonuçları farklı. * Örnek 2, //.. 'mydate' isminin bir sınıf türden değişken ismi, 'ndays' isminin de 'int' türden isim olduğunu varsayalım. 'mydate-ndays' işlemi demek BİR TARİHTEN BİR GÜN ÇIKARILMASI DEMEKTİR. İŞLEM SONUCUNDA BİR TARİH BİLGİSİ ELDE EDERİZ. 'ndays-mydate' işlemi demek BİR GÜNDEN BİR TARİH BİLGİSİ ÇIKARILMASI DEMEKTİR. MANTIKSIZ BİR İŞLEMDİR. Dolayısıyla yukarıdaki senaryo için 'operator-' fonksiyonunu 'member functions' olarak YAZMALIYIZ. 'mydate+ndays' işlemi demek BİR TARİHE BİR GÜN EKLEMEK DEMEKTİR. İŞLEM SONUCUNDA BİR TARİH BİLGİSİ ELDE EDERİZ. 'ndays+mydate' işlemi demek BİR GÜNE BİR TARİH BİLGİSİ EKLEMEK DEMEKTİR. İŞLEM SONUCUNDA BİR TARİH BİLGİSİ ELDE EDERİZ. Dolayısıyla yukarıdaki senaryo için 'operator+' fonksiyonunu 'global functions' olarak YAZMALIYIZ. Çünkü bu deyim bir 'Binary Simetric'. >> AYNI OPERATÖR FONKSİYONLARINI BİRİ ÜYE OPERATOR FONKSİYONU BİRİ GLOBAL OPERATÖR FONKSYİONU ŞEKLİNDE YAZMAK 'ambiguity' HATASINA NEDEN OLUR, BUNDAN KAÇINMALIYIZ. >> İsimlendirilmiş bir fonksiyon aynı işi 'operator overloading' mekanizmasından daha sezgisel ise o fonksiyon ile işimizi görmeliyiz. >> '[]', subscript operatörünün 'overload' edilmesi: '[]' operatörü operand olarak 'pointer-like' ve/veya 'array-like' objeler alırlar. Dolayısıyla bu operatörü 'overload' ederken de bunu gözetmeliyiz. 'Global Operator Function' şeklinde 'overload' EDİLEMEZLER. * Örnek 1, //.. #include #include int g = 100; class Myclass { public: int& operator[](int n) { std::cout << "operator[](" << n << ") was called. The address of the object: " << this << "\n"; return g; } }; int main() { /* # OUTPUT # [g | &g] : [100 | 0x5641a5100010] [ | &m] : [ | 0x7ffe3e0599d7] operator[](31) was called. The address of the object: 0x7ffe3e0599d7 [g | &g] : [123321 | 0x5641a5100010] */ std::cout << "[g | &g] : [" << g << " | " << &g << "]\n"; Myclass m; std::cout << "[ | &m] : [" << " " << " | " << &m << "]\n"; std::cout << "\n\n"; m[31] = 123321; // m.operator[](31) = 123312; std::cout << "\n"; std::cout << "\n[g | &g] : [" << g << " | " << &g << "]\n"; } * Örnek 2, //.. #include class Darray { public: Darray() = default; Darray(int size) : msize{size}, mp{ new double[msize]{} } { std::cout << "Values in the occupied memory block before assignment:\n"; for(int i = 0; i < msize; ++i) std::cout << mp[i] << " "; std::cout << "\n"; } ~Darray() { delete[] mp; } double& operator[](int index) // 'non-const' olan nesneler tarafından çağrılacak. { return mp[index]; } const double& operator[](int index)const // 'const' olan nesneler tarafından çağrılacak. { return mp[index]; } friend std::ostream& operator<<(std::ostream& os, const Darray& d) { os << "["; for(int i = 0; i < d.msize - 1; ++i) os << d.mp[i] << ","; os << d.mp[d.msize - 1] << "]"; return os; } Darray(const Darray&) = delete; Darray& operator=(const Darray&) = delete; private: int msize{}; double* mp{ nullptr }; }; int main() { /* # OUTPUT # Values in the occupied memory block before assignment: 0 0 0 0 0 0 0 0 0 0 Values in the occupied memory block after assigning new ones : 0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 [0,1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9] */ Darray a(10); for(int i = 0; i < 10; ++i) a[i] = i * 1.1; std::cout << "Values in the occupied memory block after assigning new ones :\n"; for(int i = 0; i < 10; ++i) std::cout << a[i] << " "; std::cout << "\n"; std::cout << a << "\n"; const Darray ca(5); ca[2] = 4.4; // SENTAKS HATASI. auto y = ca[3]; // LEGAL } // YUKARIDAKİ ÖRNEKTEN DE GÖRÜLDÜĞÜ ÜZERE, EĞER '[]' OPERATÖRÜ 'overload' EDİLECEKSE, İKİ TANE 'operator[]' // FONKSİYONU YAZMALIYIZ. BİR TANESİ 'const' SINIF NESNELERİ İÇİN, DİĞERİ DE 'non-const' SINIF NESNELERİ İÇİN. >>> FAKAT BU OPERATÖRÜN 'overload' EDİLMESİ AŞAĞIDAKİ ÖRNEKTEKİ GİBİ KULLANIMA OLANAK SAĞLAMAZ!!! * Örnek 1, //.. #include int main() { /* # OUTPUT # a[i] | i[a] | *(a+i) | *(i+a) 1 | 1 | 1 | 1 2 | 2 | 2 | 2 3 | 3 | 3 | 3 4 | 4 | 4 | 4 5 | 5 | 5 | 5 */ int a[] = {1,2,3,4,5}; std::cout << "a[i] | " << "i[a] | " << "*(a+i) | " << "*(i+a)\n"; for(int i = 0; i < 5;++i) std::cout << a[i] << " | " << i[a] << " | " << *(a+i) << " | " << *(a+i) << "\n"; } >> '->' ('arrow operator') ve '*' ('derefencing operator' ) 'overload' edilmesi: 'unique_ptr' sınıf nesnesinin ilkel hali aklımıza gelsin. Burada, '->' operatörünün sol tarafındaki operand sınıf nesnesi ile derleyici bu operandın 'overload' edilip edilmediğinde bakıyor. Dolayısıyla aslında 'a->b' şeklindeki ifadeyi 'a.operator->()->b' haline getiriyor. Yani aslında 'a' nın 'operator->()' fonksiyonunu çağır. Geri dönüş değerini de '->' operatörünün operandı yaparak, tekrardan çağrıda bulun. Dolayısıyla ilgili operatörün O SINIF TÜRÜNDEN 'pointer' DÖNDÜRMESİ GEREKİYOR. * Örnek 0, #include class A { public: void func() { std::cout << "void func() was called..\n"; } }; A ga; class B { public: A* operator->() { std::cout << "operator->() was called.\n"; return &ga; } }; int main() { /* # OUTPUT # operator->() was called. void func() was called.. operator->() was called. void func() was called.. */ B bx; bx->func(); std::cout << "\n\n"; bx.operator->()->func(); } * Örnek 1, #include class Myclass { public: Myclass() { std::cout << "An object has been created at the address of : " << this << "\n"; } ~Myclass() { std::cout << "An object has been destroyed at the address of : " << this << "\n"; } void func() { std::cout << "void func() was called for the object at the address of : " << this << "\n"; } private: char myArray[4096]{}; }; class MyclassPtr { public: MyclassPtr(Myclass* p) : mp{p} { } ~MyclassPtr() { if(mp) delete mp; } Myclass& operator*() { return *mp; } Myclass* operator->() { return mp; } // Our class is 'non-copiable' now. MyclassPtr(const MyclassPtr& other) = delete; MyclassPtr& operator=(const MyclassPtr& other) = delete; // Our class is only 'movable' now. MyclassPtr(MyclassPtr&& other) : mp{other.mp} { other.mp = nullptr; } MyclassPtr& operator=(MyclassPtr&& other) { if(mp) delete mp; mp = other.mp; other.mp = nullptr; } private: Myclass* mp{nullptr}; }; int main() { /* # OUTPUT # // 'p' isimli değişken. An object has been created at the address of : 0x55f37379beb0 // 'x' isimli değişken. An object has been created at the address of : 0x7ffdbaffeef0 void func() was called for the object at the address of : 0x55f37379beb0 main started // Blok içerisindeki 'p' isimli değişken. An object has been created at the address of : 0x55f37379d2d0 // Blok içerisindeki 'x' isimli değişken. An object has been created at the address of : 0x7ffdbafffef0 void func() was called for the object at the address of : 0x55f37379d2d0 // Blok içerisindeki 'x' isimli değişken. An object has been destroyed at the address of : 0x7ffdbafffef0 // Blok içerisindeki 'p' isimli değişken. An object has been destroyed at the address of : 0x55f37379d2d0 main ended // 'x' isimli değişken. An object has been destroyed at the address of : 0x7ffdbaffeef0 */ Myclass* p = new Myclass; Myclass x; *p = x; p->func(); /*== 'unique_ptr' sınıf nesnesinin ilkel hali ==*/ std::cout << "main started\n"; { MyclassPtr p = new Myclass; Myclass x; *p = x; p->func(); } std::cout << "main ended\n"; } * Örnek 2, SİZİN DE GÖRDÜĞÜNÜZ GİBİ İLGİLİ 'operator->' VE 'operator*' FONKSİYONLARININ GERİ DÖNDÜRDÜĞÜ DEĞERLER SINIF TÜRLERİNDE. BİNLERCE FARKLI SINIF TÜRÜ HALİHAZIRDA OLDUĞUNDAN, ŞİMDİ DE 'template' KULLANARAK AYNI SINIFI YAZALIM; #include class Myclass { public: Myclass() { std::cout << "An object has been created at the address of : " << this << "\n"; } ~Myclass() { std::cout << "An object has been destroyed at the address of : " << this << "\n"; } void func() { std::cout << "void func() was called for the object at the address of : " << this << "\n"; } private: char myArray[4096]{}; }; template class UniquePtr { public: UniquePtr(T* p) : mp{p} { } ~UniquePtr() { if(mp) delete mp; } T& operator*() { return *mp; } T* operator->() { return mp; } // Our class is 'non-copiable' now. UniquePtr(const UniquePtr& other) = delete; UniquePtr& operator=(const UniquePtr& other) = delete; // Our class is only 'movable' now. UniquePtr(UniquePtr&& other) : mp{other.mp} { other.mp = nullptr; } UniquePtr& operator=(UniquePtr&& other) { if(mp) delete mp; mp = other.mp; other.mp = nullptr; } private: T* mp{nullptr}; }; int main() { /* # OUTPUT # main started An object has been created at the address of : 0x555a090d02c0 // 'p' nesnesi için. An object has been created at the address of : 0x7ffcb44e20b0 // 'x' nesnesi için. void func() was called for the object at the address of : 0x555a090d02c0 An object has been destroyed at the address of : 0x7ffcb44e20b0 // 'x' nesnesi için. An object has been destroyed at the address of : 0x555a090d02c0 // 'p' nesnesi için. main ended */ std::cout << "main started\n"; { UniquePtr p = new Myclass; Myclass x; *p = x; p->func(); } std::cout << "main ended\n"; } >> '()', function call operatörünün 'overload' edilmesi: Her gördüğümüz 'func();' deyimini saf bir fonksiyon çağrısı olarak ele almamalıyız. Çünkü C ve C++ dilinde ilgili ifade aşağıdaki anlamlara gelebilir; >>> 'func' bir fonksiyon ismi olabilir. Bu durumda da 'func()' ifadesi 'func' isimli fonksiyonu çağıracaktır. * Örnek 1, //.. #include void func() { std::cout << "func() was called.\n"; } int main() { func(); // 'func' ismi Fonksiyon Çağrı Operatörünün operandı oldu. } >>> 'func' bir fonksiyon-göstericisi ismi de olabilir. Bu durumda 'func()' ifadesi, ilgili göstericinin göstermiş olduğu fonksiyonu çağıracaktır. * Örnek 1, //.. #include void func() { std::cout << "func() was called.\n"; } void foo() { std::cout << "foo() was called.\n"; } int main() { // void (*fp)() = func; // auto fp = &func; auto fp = func; fp(); // 'func' isimli fonksiyon çağrılacaktır. fp = foo; fp(); // 'foo' isimli fonksiyon çağrılacaktır. } >>> 'func' bir fonksiyonel makro ismi de olabilir. Bu durumda derleme aşamasından evvel bu makro açılacaktır. Fakat C++ dilinde bu şekildeki yaklaşımdan, yani fonksiyonel makro kullanımından, kaçınmalıyız. Çünkü çok daha gelişmiş ve aynı işi gören araçlar mevcuttur. * Örnek 1, //.. #include #include #include #define randomize() srand((unsigned)time(0)) int main() { randomize(); // Ön işlemci tarafından yukarıdaki ifade açılacaktır. Yani o ifade yerine // " srand((unsigned)time(0)) " ifadesi yazılacaktır. } Yukarıdaki üç kullanıma ek olarak C++ dilinde 'func' ismi aşağıdaki anlamlara da gelmektedir: >>> 'func' ismi bir "Function Object / Functor / (Fonksiyon çağrı operatörünü 'overload' eden sınıflar)" olabilir ki bu durumda iki farklı seçeneceğimiz vardır: >>>> 'func' ismi bir 'std::bind' sınıfından alınmış bir nesne olabilir. >>>> 'func' ismi bir "Closure Object / Lambda Expression" olabilir. >>> 'func' ismi bir 'std::function' sınıf türünden nesne olabilir. Buradan da hareketle buradaki 'func' ismi için, C++ dilinde, herhangi bir 'callable' diyebiliriz. >>> Ayrıca bu operatörü 'overload' eden fonksiyon GLOBAL OPERATÖR FONKSİYONU olamaz. * Örnek 1, //.. #include class Myclass { public: void operator()(int x = 100) const { std::cout << "void Myclass::operator()(int " << x << ") was called.\n"; std::cout << "this points to : " << this << "\n"; } }; int main() { /* # OUTPUT # &m : 0x7fff00b83ab7 void Myclass::operator()(int 31) was called. this points to : 0x7fff00b83ab7 */ Myclass m; std::cout << "&m : " << &m << "\n"; m(31); // 'operator' notasyonu ile : m.operator()(31); // Yukarıdaki 'operator()(int x)' fonksiyonu da 'overload' edilebilir. // 'const' veya 'non-const' olabilir. // Duruma göre istediği kadar parametre alabilir, duruma göre uygun geri dönüş değer türüne // sahip olabilir vs. VARSAYILAN DEĞER ALAN TEK 'operator overloading' FONKSİYONDUR. } >>> Bu operatörün 'overload' edilmesi daha çok 'generic' programlamada görülmektedir. * Örnek 1, //.. #include #include #include // İlkel kısma ait fonksiyonlar. bool isEven(int x) { return x % 2 == 0; } bool isDividableByFive(int x) { return x % 5 == 0; } bool isDividableBySeven(int x) { return x % 7 == 0; } // Functor yazılan kısma ait. class DivPred{ public: DivPred(int x) : m_val{x} {} bool operator()(int x) const { return x % m_val == 0; } private: int m_val{}; }; int main() { /* # OUTPUT # 1383 886 777 915 1793 335 1386 492 649 1421 362 27 690 59 1763 // İlkel kısma ait. 5 adet oge 2 rakamina tam bolunmekte. 3 adet oge 5 rakamina bolunmekte. 3 adet oge 7 rakamina bolunmekte. Kaca tam bolunenleri istersiniz? : 3 // Functor yazılan kısma ait. 7 adet oge 3 rakamina tam bolunmekte. Kaca tam bolunenleri istersiniz? : 11 // Lambda Expressions kısmına ait. 3 adet oge 11 rakamina tam bolunmekte. */ // İlkel Yöntem std::vector iVec(15); std::generate(iVec.begin(), iVec.end(), [](){ return rand() % 2000; }); for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; std::cout << std::count_if( iVec.begin(), iVec.end(), isEven ) << " adet oge 2 rakamina tam bolunmekte.\n"; std::cout << std::count_if( iVec.begin(), iVec.end(), isDividableByFive ) << " adet oge 5 rakamina bolunmekte.\n"; std::cout << std::count_if( iVec.begin(), iVec.end(), isDividableBySeven ) << " adet oge 7 rakamina bolunmekte.\n"; // Görüldüğü üzere her bir rakam için ayrı ayrı fonksiyon yazmak durumundayız. Çok daha iyi bir // yöntem olarak bir sınıf(Functor) yazabiliriz: std::cout << "Kaca tam bolunenleri istersiniz? : "; int m{}; std::cin >> m; std::cout << std::count_if( iVec.begin(), iVec.end(), DivPred{ m } ) << " adet oge " << m << " rakamina tam bolunmekte.\n"; // 'DivPred{m}' ifadesi ilgili sınıf türünden geçici bir nesne oluşturmakdır. // Peki çok daha iyi bir yöntem daha yazamaz mıyız? Tabii ki yazabiliriz. (Bkz. 'Lambda Expressions') // Lambda Expressions ile derleyici bir sınıf kodunu YAZIYOR ve ifadeyide O SINIF TÜRÜNDEN GEÇİCİ BİR // NESNEYE dönüştürmektedir. Tıpkı bizim yukarıda yaptığımız gibi. std::cout << "Kaca tam bolunenleri istersiniz? : "; int k{}; std::cin >> k; std::cout << std::count_if( iVec.begin(), iVec.end(), [k](int x) { return x % k == 0; } ) << " adet oge " << k << " rakamina tam bolunmekte.\n"; } * Örnek 2, Temsili bir 'std::count_if' fonksiyon şablon yazımı: //.. template int Count_If(Iter beg, Iter end, F pred) { int cnt{}; while(beg != end) { if(pred(*beg)) ++cnt; ++beg; } return cnt; } int main() { /* # OUTPUT # 1383 886 777 915 1793 335 1386 492 649 1421 362 27 690 59 1763 0 adet oge 13 rakamina tam bolunmekte. */ std::vector iVec(15); std::generate(iVec.begin(), iVec.end(), [](){ return rand() % 2000; }); for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; std::cout << Count_If( iVec.begin(), iVec.end(), [](int x) { return x % 13 == 0; } ) << " adet oge " << 13 << " rakamina tam bolunmekte.\n"; } >>> İş bu operatörlerin fonksiyonlarını çağıran şeyler birer nesne olduklarından, sınıfların 'private' kısımlarına erişebilirler. Birden fazla ilgili sınıf türünden nesne olduklarında, her birinin durumu da birbirinden farklı olabilir. * Örnek 1, //.. #include #include class Random { public: Random() = default; Random(int min, int max) : m_min{min}, m_max{max} {} int operator()() { ++m_count; return rand() % (m_max - m_min + 1) + m_min; } int get_count()const { return m_count; } private: int m_count{}; int m_min{}; // İlgili 'm_max' isimli değişkenimiz, 'signed int' türünün taşıyabileceği maksimum değer ile hayata // gelecektir. int m_max{ std::numeric_limits::max() }; }; int main() { /* # OUTPUT # 49 46 48 55 44 5 kez sayi uretildi. 5746 48309 10280 53079 12340 34023 33769 28077 8 kez sayi uretildi. */ Random randOne{30, 56}; for(int i = 0; i < 5; ++i) std::cout << randOne() << " "; std::cout << "\n" << randOne.get_count() << " kez sayi uretildi.\n\n"; Random randTwo{100, 56000}; for(int i = 0; i < 8; ++i) std::cout << randTwo() << " "; std::cout << "\n" << randTwo.get_count() << " kez sayi uretildi.\n"; } >> 'type-cast' operatörünün 'overload' edilmesi : >>> Bizim sınıfımız türünden olan bir nesneyi bizim sınıf türünden olmayan başka bir türe dönüştürmektedir; "(static_cast(MyClassObject)" / "(int)MyClassObject)" gibi. Bu yönüyle 'Conversion Ctor' a çok benzemektedir. 'Conversion Ctor' ise, sınıf türünden olmayanı sınıf türüne döndüştürmektedir. 'type-cast' operatörü ise bu işin tam tersini yapmaktadır. >>> BU OPERATÖR FONKSİYONU DA SINIFIN ÜYE FONKSİYONU OLMAK ZORUNDADIR. >>> C dilinde 'type-casting' operatörleri ile elde edilen değerlerin kategorisi 'R-Value' olduğundan, C++ dilinde de bu özelliği korumalıyız. Yani bu operatör fonksiyonunu 'overload' ederken, geri döndürülen değer 'call-by-value' şeklinde olmalı. >>> Sentaks açısından bir zorunluluk olmamasına rağmen, bu operatör fonksiyonu da bir 'const' üye fonksiyon olmalıdır. >>> İlgili operatör fonksiyonu ile sadece 'primitive' türlere dönüşüm değil, başka sınıf türlerine de dönüşüm yaptırabiliriz. * Örnek 1, //.. // A.hpp #ifndef A_H #define A_H #include class A{ public: A() { std::cout << "A::A() was called.\n"; } }; #endif // main.cpp #include #include "A.hpp" class B{ public: B() { std::cout << "B::B() was called.\n"; } explicit operator A()const { std::cout << "B::operator A()const was called.\n"; return A{}; } }; int main() { /* # OUTPUT # A::A() was called. // 'ax' B::B() was called. // 'bx' B::operator A()const was called. A::A() was called. // 'B::operator A()const' içerisindeki 'Geçici Nesne' */ A ax; B bx; ax = static_cast(bx); // Çünkü 'B::operator A()const' is 'explicit'. } >>> İlgili operatör fonksiyonu ile dönüştürülen tür 'Referans Tür' olabilir. STL içerisindeki 'ReferenceWrapper' sınıfı buna bir örnek olarak verilebilir. Yani 'rebindable references'. * Örnek 1, #include class iRef{ public: iRef(int& r) : mp{&r} { std::cout << "iRef::iRef(int& r) was called.\n"; } iRef& operator=(int& x) { std::cout << "iRef& operator=(int& x) was called.\n"; mp = &x; return *this; } operator int&() // I { std::cout << "operator int&() was called.\n"; return *mp; } private: int* mp; }; int main() { /* # OUTPUT # x : 34 y : 71 -------------- iRef::iRef(int& r) was called. operator int&() was called. -------------- x : 35 -------------- iRef& operator=(int& x) was called. operator int&() was called. y : 70 -------------- */ int x{34}, y{71}; std::cout << "x : " << x << "\n"; std::cout << "y : " << y << "\n"; std::cout << "--------------\n"; iRef r = x; // 'r' demek 'x' demektir. ++r; // LEGAL. Çünkü bizler 'I' numaralı fonksiyonu bildirdik. Aksi halde SENTAKS HATASI oluşacaktı. // Fakat 'I' yerine bizler 'operator++' operatörünü de bildirebilirdik. 'x' in değeri bir artmıştır. std::cout << "--------------\n"; std::cout << "x : " << x << "\n"; std::cout << "--------------\n"; r = y; // 'r' demek 'y' demektir. --r; // 'y' nin değeri bir azalmıştır. std::cout << "y : " << y << "\n"; std::cout << "--------------\n"; } >> C dilinde lojik ifade alan deyimlere, örneğin '||' ve '&&' operatörlerinin operandlarına veya 'if' deyiminin parantezi içerisine, aşağıdaki türler geçilirse; >>> Eğer bu ifadeler Aritmetik Türler('int', 'float', 'double' vs.) ise 'Non-Zero' değerler 'true' olarak yorumlanırken, 'Zero' değerler ise 'false' olarak yorumlanır. >>> Eğer bu ifadeler bir gösterici ise 'nullptr' değerindeki göstericiler 'false' olarak yorumlanırken, diğer göstericiler 'true' olarak yorumlanır. >>> Peki bu kural sınıf nesneleri için de geçerli midir? El-cevap: duruma göre geçerli, duruma göre değil. * Örnek 1, //.. Yukarıdaki 'Mint' sınıfını ele alalım. // Mint.hpp class{ public: //.. // (i)... iş bu operatörün 'overload' edilme biçimi. 'explicit' OLARAK NİTELENDİĞİNE // DİKKAT EDİNİZ...(ii) explicit operator bool()const { return mval != 0; } private: int mval; }; // main.cpp int main() { Mint mx; std::cout << "Bir tam sayi giriniz: "; std::cin >> mx; if(mx) std::cout << "evet, dogru.\n"; // Eğer 'Mint' sınıfında tanımlı bir 'non-explicit operator int()' fonksiyonu olsaydı, // bu kod bloğu ÇALIŞTIRILACAKTI. Çünkü ilgili fonksiyonumuz 'explicit' olmadığından // 'Mint' türünden 'int' dönüşüm ilgili o fonksiyon üzerinden gerçekleşecek, 'int' // türünden 'bool' türüne de 'Standar Conversion' gerçekleşecektir. // From 'Mint' to 'int' : 'User Defined Conversion' // From 'int' to 'bool' : 'Standart Conversion' // Velevki iş bu fonksiyonumuz tanımlı olmasaydı, o zaman bu kod bloğu SENTAKS HATASINA // neden olacaktı. // Peki bizim niyetimiz ilgili sınıf nesnemizi böyle bir deyim içerisinde kullanmaksa? // El-cevap : 'operator bool()' fonksiyonunu 'overload' etmeli ve onu yine 'explicit' olarak // NİTELEMELİYİZ. Çünkü nesnemiz 'logic-control' istenen bir yerde kullanılırsa harici // dönüşüm istememektedir...(i) // if(mx) // std::cout << "evet, dogru.\n"; // (ii)... Yukarıdaki ifade hala geçerli. Çünkü sınıf nesnemiz 'logic-control' istenen bir // yerde kullanılmıştır. Eğer sınıf nesnemizi başka bir 'primitive' türe atamaya çalışırsak // SENTAKS HATASI alacağız. Çünkü ilgili operatörümüz hala 'explicit'. // double dval{12.23}; // dval = mx; // SENTAKS HATASI. // dval = static_cast(mx); // GEÇERLİ. // YUKARIDAKİ BU SENARYO SADECE VE SADECE 'operator bool()' FONKSİYONUN ÖZGÜDÜR. } * Örnek 2, //Mint.hpp #ifndef MINT_H #define MINT_H #include class Mint{ public: Mint() = default; explicit Mint(int val) : mval{val} { std::cout << "Mint::Mint(int " << val << ") was called.\n"; } // Semantik açıdan da doğru olması için bu fonksiyonu 'const' bir üye fonksiyon yaptık. explicit operator int()const { std::cout << "operator int() const was called.\n"; return mval; } explicit operator bool()const { return mval != 0; } friend std::ostream& operator<<(std::ostream& os, const Mint& mint) { return os << "(" << mint.mval << ")"; } friend std::istream& operator>>(std::istream& is, Mint& mint) { return is >> mint.mval; } private: int mval{}; }; #endif // main.cpp #include #include "Mint.hpp" int main() { /* # OUTPUT # Mint::Mint(int 24) was called. true */ Mint mx{24}, my{42}; if(mx) std::cout << "true\n"; else std::cout << "false\n"; // double dval = 23.32; // dval = mx; // 'operator bool()' fonksiyonumuz 'explicit' olduğundan GEÇERSİZ. // Eğer hem 'operator int()' hem de 'operator bool()' fonksiyonlarımız 'explicit' OLMASAYDI, // 'ambiguity' tip SENTAKS HATASI OLUŞACAKTIR. // Aşağıdaki ifadeler de 'boolean context' olduğundan, yani 'logic-control' istenen yer // olduğundan, geçerlidir: auto f = mx && my; // auto f = mx.operator bool() && my.operator bool(); auto fNot = mx || !my; // auto fNot = mx.operator bool() || !(my.operator bool()); } * Örnek 3, //.. int main() { int x; while(std::cin >> x) std::cout << x << "\n"; // # YUKARIDA GERÇEKLEŞEN ŞEYLER # // i. 'while(std::cin >> x)' ifadesi aslında 'while(std::cin.operator>>(x))' ifadesine // dönüştürülmekte. İş bu fonksiyon çağrısının geri dönüş değeri de, tıpkı bizim 'Mint' sınıfında // yazdığımız gibi, '*this'. Yani 'std::cin' nesnesinin kendisi. 'while' parantezi içerisinde // 'std::cin' nesnesi olduğundan ve o sınıfın da bir 'operator bool()' fonksiyonu olduğundan, // derleyici o fonksiyonu çağırmakta. Bir diğer deyişle 'while(std::cin >> x)' ifadesi // 'while(std::cin.operator>>(x).operator bool())' ifadesi haline geliyor. Bu giriş işleminin // yapılmasından sonra 'std::cin' nesnesi hata durumunda değilse, bizim 'operator bool()' // fonksiyonumuz 'true' değer döndürecektir. Hata durumundaysa 'false' değerini döndürecektir ve // artık 'std::cin' nesnesi yeni bir giriş için kullanılamaz hale geliyor. x = std::cin; // 'cin' nesnesine ait olan sınıfın 'operator bool()' fonksiyonu 'explicit' olarak // nitelendiğinden bu atama SENTAKS HATASI. x = static_cast(std::cin); // GEÇERLİ. } * Örnek 4, 'std::unique_ptr' sınıfı içerisindeki 'operator bool()' fonksiyonunun kullanımı, //.. int main() { std::unique_ptr up{new string{"Neco"} }; if(up) // if(up.operator bool()) std::cout << "I am not 'nullptr' \n"; else { std::cout << "I am 'nullptr' \n"; } } >> 'operator overload' mekanizmasını, Numaralandırma Sabitleri için de kullanabiliriz. Şöyleki; * Örnek 1, //.. #include enum class Weekday{ Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, }; // Bir 'class-type' olmadığından tek alternatifimiz 'Global Operator Overload' şeklinde kullanılmaları. //Prefix Weekday& operator++(Weekday& theDay) { return theDay = theDay == Weekday::Saturday ? Weekday::Sunday : static_cast( static_cast(theDay) + 1 ); } // Postfix Weekday operator++(Weekday& theDay, int) { Weekday temp{theDay}; ++theDay; return temp; } std::ostream& operator<<(std::ostream& os, const Weekday& theDay) { // A look-up table: an array of pointers. static const char* const pdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; return os << "[" << pdays[static_cast(theDay)] << "]\n"; } int main() { /* // Postfix // Prefix # OUTPUT # # OUTPUT # [Sunday] [Monday] [Monday] [Tuesday] [Tuesday] [Wednesday] [Wednesday] [Thursday] [Thursday] [Friday] [Friday] [Saturday] [Saturday] [Sunday] [Sunday] [Monday] */ Weekday wd = Weekday::Sunday; for(int i = 0; i < 8; ++i) std::cout << ++wd; // Prefix // std::cout << wd++; // Postfix } >> Pekiştirici Örnekler, * Örnek 1.1, // main.hpp // #pragma once // Standart Değil #ifndef MINT_H #define MINT_H class Mint{ // SINIFLARIMIZI DIŞARIDAN İÇERİYE DOĞRU TASARLAMALIYIZ. // ÖNCE 'interface' SONRA 'implementation'. public: // Mint() = default; // 'Default Constructable' olması için derleyiciye yazdırdık. Mint(); // 'Default Constructable' olması için tanımını biz yazacağız. Mint(int); // Yukarıdaki iki tane 'Ctor' yerine aşağıdaki gibi bir tane 'default ctor' da yazabilirdik. // Mint(int x = 0){} // Sınıfımız bir kaynak kullanmayacağı için 'rule of zero' deyimini takip etmeliyiz. Yani 'special // member functions' ları derleyiciye yazdırmalıyız. // Karşılaştırma Operatörlerinin 'overload' edilmeleri; friend bool operator<(const Mint&, const Mint&); // İlgili operatör 'Binary Simetric' olduğu için Global Fonksiyon olarak bildirdik. Sınıfımızın // 'private' kısmıne erişmesi gerektiğinden, 'friend' lik verdik. Ayrıca, Fonksiyon bildirimlerinde // değişkenlere isim vermek değişkenlik gösteren bir durumdur. İlgili fonksiyonun ismine bakarak // hangi parametrenin ne için kullanılacağı açık ve net değilse isim kullanmalıyız. Örneğin, bizim bu // fonksiyonumuz için isme gerek yok. Sonuçta bu bir Global Fonksiyon ve parametrenin sıralaması o // kadar da önemli değil. Ek olarak, // Bu fonksiyonu sınıf bildirimi içerisinde 'inline' olarak da tanımlayabiliriz. // Bu fonksiyonun geri dönüş değerini 'bool' harici bir türden yapmak da lojik açıdan pek mantıklı // değil. // Eşitlik operatörünün 'overload' edilmeleri; friend bool operator==(const Mint&, const Mint&); // 'Karşılaştırma' operatöründeki açıklamalar // burada da geçerlidir. // Genel konvensiyon gereği, ekseriyet ile sadece '<' ve '==' operatörleri 'overload' edilir. Geri // kalan diğer karşılaştırma ve eşitlik kontrolü sağlayan fonksiyonlar ise bizim bu fonksiyonlarımızı // çağırır...(i) // Aritmetik operatörlerin 'overload' edilmeleri; Mint& operator+=(const Mint&); }; //(i)... örneğin, aşağıdaki kullanım; // İlgili fonksiyonumuz, sınıf içerisinde tanımladığımız fonksiyonları çağırmaktadır. // 'inline' olarak tanımladık çünkü hala başlık dosyası içerisindeyiz fakat artık sınıf bloğu içerisinde değiliz. // Karşılaştırma Operatörlerinin 'overload' edilmeleri; inline bool operator>(const Mint& x, const Mint& y) { return y < x; } inline bool operator>=(const Mint& x, const Mint& y) { return !(x < y); } inline bool operator<=(const Mint& x, const Mint& y) { return !(y < x); } // Eşitlik operatörünün 'overload' edilmeleri; inline bool operator!=(const Mint& x, const Mint& y) { return !(x == y); } // Aritmetik operatörlerin 'overload' edilmeleri; inline Mint operator+(const Mint& x, const Mint& y) { /* # Uzun Hali # Mint temp(x); temp += y; return temp; */ /* # Uzun Hali # */ return Mint(x) += y; // 'Mint' türünden geçici bir nesne oluşturduk ve '+=' operatörü ile 'y' değerini ekledik. İlgili // operatör fonksiyonu bize referans döndürdüğünden, direkt olarak onu 'call-by-value' olarak döndürdük. } #endif // İŞ BU DERSİN DEVAMININ KAYDI OLMADIĞINDAN, GERİ KALAN KISMI BİLİNMİYOR. BİR SONRAKİ DERSTE YAZILAN KOD AŞAĞI // TEKRAR YAZILACAKTIR. BU KOD ASLINDA SON HALİ GİBİ. * Örnek 1.2, önceki derste yazılan 'Mint' sınıf türünün son hali, // mint.hpp #ifndef MINT_H #define MINT_H #include class Mint{ public: Mint() = default; explicit Mint(int val) : mval{val} {} Mint& operator++() // prefix { ++mval; return *this; } Mint operator++(int) // postfix { Mint temp{*this}; ++* this; return temp; } // '++' ve '--' operatörlerinin 'Global Operatör Fonksiyon' olma durumları, // friend Mint& operator++(Mint& r); // prefix // friend Mint operator++(Mint& r, int); // postfix Mint& operator--(); // prefix Mint operator--(int); // postfix Mint& operator+=(const Mint& other) { mval += other.mval; return *this; } Mint& operator-=(const Mint& other) { mval -= other.mval; return *this; } Mint operator+()const { return *this; } Mint operator-()const { return Mint{-mval}; } friend bool operator<(const Mint& mx, const Mint& my) { return mx.mval < my.mval; } friend bool operator==(const Mint& mx, const Mint& my) { return mx.mval == my.mval; } friend std::ostream& operator<<(std::ostream& os, const Mint& mint) { return os << "(" << mint.mval << ")"; } friend std::istream& operator>>(std::istream& is, Mint& mint) { return is >> mint.mval; } private: int mval{}; }; inline Mint operator+(const Mint& x, const Mint& y) { return Mint(x) += y; } inline Mint operator-(const Mint& x, const Mint& y) { return Mint(x) -= y; } inline bool operator>(const Mint& x, const Mint& y) { return y < x; } inline bool operator<=(const Mint& x, const Mint& y) { return !(y < x); } inline bool operator>=(const Mint& x, const Mint& y) { return !(x < y); } inline bool operator!=(const Mint& x, const Mint& y) { return !(x == y); } /* '++' ve '--' operatörlerinin 'Global Operatör Fonksiyon' olma durumları, Mint& operator++(Mint& r) // prefix { ++r.mval; return r; } Mint operator++(Mint& r, int) // postfix { Mint temp{r}; ++r; return temp; } */ #endif // main.cpp //.. int main() { std::ofstream ofs{"out.txt"}; if(!ofs) exit(EXIT_FAILURE); srand(static_cast(time(nullptr))); for(int i = 0; i < 100; ++i) { Mint x(rand() % 250); Mint y(rand() % 500); ofs << x << " + " << y << " = " << x + y << "\n"; // Çıktıyı ilgili dosyaya yazdı. // std::cout << x << " + " << y << " = " << x + y << "\n"; // Çıktıyı ekrana yazdı. } } * Örnek 2, 'type-cast' operatörünün 'overload' edilmesi: // Mint.hpp #ifndef MINT_H #define MINT_H #include class Mint{ public: Mint() = default; explicit Mint(int val) : mval{val} {} // 'type-cast operator functions' İÇİN GERİ DÖNÜŞ DEĞERİ KAVRAMI VARDIR FAKAT ONU YAZMIYORUZ, // SENTAKS HATASI. // Semantik açıdan da doğru olması için bu fonksiyonu 'const' bir üye fonksiyon yaptık. operator int()const { return mval; } Mint& operator++() // prefix { ++mval; return *this; } Mint operator++(int) // postfix { Mint temp{*this}; ++* this; return temp; } Mint& operator--(); // prefix Mint operator--(int); // postfix Mint& operator+=(const Mint& other) { mval += other.mval; return *this; } Mint& operator-=(const Mint& other) { mval -= other.mval; return *this; } Mint operator+()const { return *this; } Mint operator-()const { return Mint{-mval}; } friend bool operator<(const Mint& mx, const Mint& my) { return mx.mval < my.mval; } friend bool operator==(const Mint& mx, const Mint& my) { return mx.mval == my.mval; } friend std::ostream& operator<<(std::ostream& os, const Mint& mint) { return os << "(" << mint.mval << ")"; } friend std::istream& operator>>(std::istream& is, Mint& mint) { return is >> mint.mval; } private: int mval{}; }; inline Mint operator+(const Mint& x, const Mint& y) { return Mint(x) += y; } inline Mint operator-(const Mint& x, const Mint& y) { return Mint(x) -= y; } inline bool operator>(const Mint& x, const Mint& y) { return y < x; } inline bool operator<=(const Mint& x, const Mint& y) { return !(y < x); } inline bool operator>=(const Mint& x, const Mint& y) { return !(x < y); } inline bool operator!=(const Mint& x, const Mint& y) { return !(x == y); } #endif // main.cpp #include #include "Mint.hpp" int main() { /* # OUTPUT # (31) + (13) = (44) ival : 31 */ Mint mx{31}, my{13}; std::cout << mx << " + " << my << " = " << mx + my << "\n"; int ival{mx}; // int ival = mx.operator int(); // İlgili bu operatör fonksiyonunun geri döndürdüğü değer, aslında bir nevi 'mx' nesnesinin // içerisindeki 'int' türden değişkenin değeri olmalıdır. std::cout << "ival : " << ival << "\n"; // İLGİLİ OPERATÖR FONKSİYONUNU 'overload' ETTİĞİMİZ İÇİN, SINIF TÜRÜNDEN 'int' TÜRÜNE DÖNÜŞÜM // LEGAL HALE GELDİ. // 'mx + 4' ifadesinin geçerli olması için, // i. ya sadece 'operator int()' fonksiyonu tanımlı olması lazım ki 'mx' türünden nesne 'int' // türüne dönüştürülsün // ii. ya sadece 'Conversion Ctor' un 'explicit' OLMAMASI lazım ki 'int' türünden obje, 'Mint' // türünden bir nesneye dönüştürülsün. // iii. ya da UYGUN BİR 'operator+(int)' ŞEKLİNDE '+' OPERATÖRÜNÜ 'overload' EDEN BİR FONKSİYON // OLMALIDIR ki 'Mint' türünden bir obje ile 'int' türünden bir nesneyi toplayabilelim. // BURADAN DA BİR HATIRLATMA DAHA YAPMALIYIZ Kİ // 'FO' mekanizmasındaki 'User-Defined Conversion' şeklindeki dönüşümler iki şekilde // gerçekleşmektedir. Bunlar, // i. 'Conversion Ctor' ile // ii. 'type-cast' operatörlerinin 'overload' edilmeleriyle. // BURADAN BİR KEZ DAHA 'Conversion Sequence' GÖNDERME YAPALIM: // i. Önce 'User Defined Conversion', sonra 'Standart Conversion' var ise derleyici bu // dönüşümleri OTOMATİK olarak gerçekleştirir. // ii. Önce 'Standart Conversion', sonra 'User Defined Conversion' var ise derleyici bu // dönüşümleri OTOMATİK olarak gerçekleştirir. // iii. Eğer iki defa 'User Defined Conversion' var ise derleyici OTOMATİK OLARAK GERÇEKLEŞTİRMEZ. // Programcı olarak bizler dahil olmalıyız. Bu açıklamayı baz alarak aşağıdaki örnekleri de // inceleyelim: // double dval; // dval = mx + my; // Yukarıdaki atama geçerlidir. Çünkü eşitliğin sağ tarafındaki nesnelerin türü 'Mint'. Eşitliğin // sol tarafındaki tür ise 'double'. Bu durumda, // i. 'operator int()' fonksiyonu ile 'Mint' türünden 'int' türüne 'User Defined Conversion', // gerçekleşecek. // ii. 'int' türünden de 'double' türüne 'Standart Conversion' gerçekleşecek. // 'User Defined Conversion' + 'Standart Conversion' gerçekleşmiştir. Peki biz bu dönüşümü // yanlışlıkla yapmışsak? El-cevap : FELAKET OLURDU. // Peki biz bu dönüşümü nasıl engelleriz? El-cevap : 'operator int()' fonksiyonunu da 'explicit' // OLARAK NİTELEMELİYİZ. Artık yukarıdaki 'int' türünden 'double' türüne gerçekleşen // 'Standart Conversion' iptal edilecek. Bizler 'static_cast<>()' ile dönüşüm gerçekleştirmeliyiz. // # ÖZETLE # // i. Eğer 'type-cast' operatör fonksiyonlarını 'overload' etmişsek onları 'explicit' olarak // nitelemeliyiz. Eğer o fonksiyonları kullanmak istiyorsak, 'static_cast<>()' ile kullanmalıyız. // ii. 'Conversion Ctor' u da 'explicit' olarak bildirmeliyiz ki 'int' türünden 'Mint' türüne // otomatik dönüşüm olmasın. } * Örnek 3.1, 'Conversion Ctor' ve 'operator int()' fonksiyonlarının 'explicit' edilmesi; //Mint.hpp #ifndef MINT_H #define MINT_H #include class Mint{ public: Mint() = default; explicit Mint(int val) : mval{val} { std::cout << "Mint::Mint(int " << val << ") was called.\n"; } // Semantik açıdan da doğru olması için bu fonksiyonu 'const' bir üye fonksiyon yaptık. explicit operator int()const { std::cout << "operator int() const was called.\n"; return mval; } Mint& operator+=(const Mint& other) { mval += other.mval; return *this; } friend std::ostream& operator<<(std::ostream& os, const Mint& mint) { return os << "(" << mint.mval << ")"; } int getMember()const { return mval; } private: int mval{}; }; inline Mint operator+(const Mint& x, const Mint& y) { std::cout << "Mint operator+(const Mint& x, const Mint& y) was called.\n"; std::cout << "mval : " << x << "\n"; std::cout << "y : " << y << "\n"; return Mint(x) += y; } inline int operator+(const Mint& x, int y) { std::cout << "Mint operator+(const Mint& x, int y) was called.\n"; std::cout << "mval : " << x << "\n"; std::cout << "y : (" << y << ")\n"; return x.getMember() + y; } #endif // main.cpp #include #include "Mint.hpp" int main() { /* # OUTPUT # Mint::Mint(int 31) was called. Mint::Mint(int 13) was called. Mint operator+(const Mint& x, const Mint& y) was called. mval : (31) y : (13) (31) + (13) = (44) ----------------- Mint operator+(const Mint& x, const Mint& y) was called. mval : (31) y : (13) operator int() const was called. temp : 44 ----------------- Mint operator+(const Mint& x, const Mint& y) was called. mval : (31) y : (13) Mint operator+(const Mint& x, int y) was called. mval : (44) y : (44) tempTwo : 88 */ Mint mx{31}, my{13}; Mint mz {mx + my}; std::cout << mx << " + " << my << " = " << mz << "\n"; std::cout << "-----------------\n"; // Tek parametre alan 'Ctor', yani 'Conversion Ctor', 'explicit' olduğundan SENTAKS HATASI. // mx = 56; // 'operator int()' fonksiyonu 'explicit' olduğundan SENTAKS HATASI. // int temp = mx + my; // 'operator int()' fonksiyonu 'explicit' olduğundan dolayı, O fonksiyonu SADECE BU ŞEKİLDE // KULLANABİLİRİZ. int temp = static_cast(mx + my); std::cout << "temp : " << temp << "\n"; std::cout << "-----------------\n"; int tempTwo = mx + my + 44; std::cout << "tempTwo : " << tempTwo << "\n"; } * Örnek 3.2, Artık 'Conversion Ctor' ve 'operator int()' fonksiyonları 'explicit' olduğundan, derleyici tarafından otomatik olarak çağrılmayacaklar. Bizler 'static_cast' gibi Tür Dönüştürme operatörlerini harici olarak çağırarak, gerek 'Mint => int' şeklinde gerek 'int => Mint' şeklinde dönüşümler yaptırabiliriz. //.. Yukarıdaki 'Mint' sınıfının da burada olduğunu varsayalım, #include #include "Mint.hpp" int main() { /* # OUTPUT # Mint::Mint(int 56) was called. (56) Mint::Mint(int 100) was called. (100) operator int() const was called. ival : 100 operator int() const was called. dval : 100 */ Mint mq{56}; std::cout << mq << "\n"; // Explicit Only Cast from 'int' to 'Mint' // 'Conversion Ctor', 'explicit' olduğundan dolayı, sadece bu şekilde dönüşüm yapabiliriz. Eğer bu // 'Ctor' olmasaydı, bu satır SENTAKS HATASI verecekti. mq = static_cast(100); std::cout << mq << "\n"; // Explicit Only Cast from 'Mint' to 'int' // 'operator int()' fonksiyonu 'explicit' olduğundan dolayı, sadece bu şekilde dönüşüm yapabiliriz. // Eğer bu fonksiyon da olmasaydı, bu satır SENTAKS HATASI verecekti. int ival = static_cast(mq); std::cout << "ival : " << ival << "\n"; // Explicit Only Cast from 'Mint' to 'int', then Standart Conversion from 'int' to 'double'. double dval = static_cast(mq); std::cout << "dval : " << dval << "\n"; }