/*============================================================================================================*/ (01_12_09_2020) > Unspecified Behaviour: Derleyicinin ürettiği makine koduna bağlı. Çalışma zamanında farklı sonuçlar elde edebiliriz. Fakat derleyici,nasıl bir kod ürettiğini, belgelemek zorunda DEĞİL. > Implementation Defined : Derleyicinin ürettiği makine koduna bağlı. Fakat derleyici, nasıl bir kod ürettiğini, belgelemek zorunda. * Örnek #1, #include int f1() { return 5; } int f2() { return 10; } int main() { x = f1() + 5 * f2(); /* * Yukarıdaki kod çağrısında f1() fonksiyonunun mu önce yoksa f2() fonksiyonunun mu önce çağrılacağına dair bir garanti yok. * Derleyiciye göre değişmekte. Fakat fonksiyonlar çağrıldıktan sonra, '*' işleminin önceliği olduğundan dolayı, 'x' * değişkeninin değeri '55' olacaktır. */ } > C ve C++ nin içerisindeki C arasındaki farklar: >> C dilinde 'bool' veri tipi mevcut değil. C99 ile '_Bool' veri tipi eklenmiş fakat bu da arka planda 'int' veri tipini kullanmakta. Fakat C++ dilinde 'bool' bir veri tipi. Her iki dilde de bunlar bir byte yer kaplamaktalar. >> C dilinde karşılaştırma ve mantık operatörlerinin geri dönüş değerlerinin türleri 'int' veri tipiyken, C++ dilinde bunlar 'bool' veri tipindedirler. >> C dilinde kullanılan 'NULL' aslında bir makrodur ve '0' rakamına tekabül eder. C++ dilinde ise 'nullptr', 'nullptr_t' türündendir. >> C dilinde aritmetik türleri göstericilere atayabiliyoruz. Benzer şekilde göstericileri de aritmetik türlere atayabiliyoruz. >>> 'zero' değerine sahip bir değişkeni göstericilere atadığımız zaman, ilgili gösterici 'NULL' değerini almaktadır. >>> 'non-zero' değerine sahip bir değişkeni göstericilere atadığımız zaman, ilgili gösterici 'Non-NULL' değerini almaktadır. >> C++ dilinde ise aritmetik türleri göstericilere, göstericileri de aritmetik değerlere atayamıyoruz. Bunun tek istisnası, sadece Legacy C++ döneminde kullanılan, "int* ptr = 0" şeklindeki bir atamadır. Modern C++ döneminde ise bunun yerini 'nullptr' almıştır. Burada unutulmaması gereken, istisnai durumun sadece '0' rakamının göstericilere atanması içindir. '0' rakamı hariç diğer değişkenlerin göstericilere atanması hem legacy C++ hem de modern C++ döneminde sentaks hatasıdır. /*============================================================================================================*/ (02_13_09_2020) > C ve C++ nin içerisindeki C arasındaki farklar (devam): >> C dilinde '(void *)' türünden '(T *)' türüne otomatik dönüşüm vardır. Örtülü bir dönüşümdür(implicit conversion) (bkz. 'malloc()'). Fakat C++ dilinde böyle bir örtülü dönüş olmadığı için, kullanıcı, harici olarak ilgili '(void *)' türünden nesneyi '(T *)' türünden nesneye 'cast' etmelidir. Ama 'C-Style' cast işlemi ile ama C++ dilinde mevcut olan 'static_cast' vs. gibi diğer araçlar ile. * Örnek 1, #include using namespace std; int main() { int* ptr = nullptr; ptr = 0; // LEGAL, SADECE legacy C++ dönemindeki kodlarda kullanılmalıdır. cout << "ptr:" << ptr << "\n"; ptr = 200; // invalid conversion from ‘int’ to ‘int*’ [-fpermissive] void* vPtr = ptr; // LEGAL int* iPtr = vPtr; // invalid conversion from ‘void*’ to ‘int*’ [-fpermissive] return 0; // Örnektende görüldüğü üzere, // i. 'void*' -> 'T*' : Örtülü dönüşüm yoktur. // ii. 'T*' -> 'void*' : Örtülü dönüşüm VARDIR. } >> C++ dilinde farklı adres türleri arasında örtülü dönüşüm olmadığı için bu durum sentaks hatasıdır. Bu durumda kullanıcı, yukarıdaki 'casting' araçlarını kullanarak harici bir dönüşüm yapmalıdır. Fakat C dilinde böyle bir örtülü dönüşüm vardır. * Örnek 1, // Some codes here... int main() { int x = 10; int* xPtr = &x; char* cPtr = xPtr; // C++ dilinde bu çağrı sentaks hatasıyken, C dilinde geçerlidir. } >> 'const' anahtar sözcüğünün kullanımındaki farklılıklar: >>> C dilinde 'const' anahtar sözcüğü ile bildirilen fakat ilk değer verilmeyen, yani 'uninitialize' durumda olan, değişkenler iki farklı değer ile hayata gelirler; >>>> 'static' ömürlü değişkenler ise hayata '0' değeri ile gelirler. >>>> 'automatic' ömürlü değişkenler ise hayata 'garbage-value' ile gelirler. >>> C++ dilinde ise 'const' anahtar sözcüğü ile nitelenen değişkenler ilk değer almak zorundadır. Ömür kategorilerinin burada bir önemi yoktur. Aksi halde sentaks hatasına neden olur. Yani nesnenin kendisi 'const' ise ilk değer almak zorundadır. * Örnek 1, // Some codes here... int main() { const int* ptr; // C++ dilinde bu geçerlidir. Çünkü 'ptr' değişkeninin kendisi 'const' değildir. Sadece gösterdiği // değişken 'const' statüsündedir. // int* const ptr; // İşte bu durumda sentaks hatası olmuş olur. Çünkü 'ptr' in kendisi 'const' durumdadır. } >>> NE C DİLİNDE YAZARKEN NE DE C++ DİLİNDE YAZARKEN 'const' ANAHTAR SÖZCÜĞÜ İLE NİTELENDİRİLMİŞ BİR DEĞİŞKENİ / NESNEYİ, 'C-style Casting' ile DEĞİŞTİRME GİRİŞİMİNDE BULUNMAMALIYIZ. BÖYLE BİR GİRİŞİMDE BULUNMAK SENTAKS HATASIDIR. SENTAKS HATASINI BY-PASS EDEREK GİRİŞİMDE BULUNDUĞUMUZ ZAMAN, 'Tanımsız Davranış'A NEDEN OLMUŞ OLURUZ. >>> C dilinde 'const T*' türünden 'T*' türüne örtülü bir dönüşüm vardır, sentaks hatası değildir. Fakat C++ dilinde böyle bir örtülü dönüşüm olmadığı için sentaks hatasıdır. >>> C dilinde 'const int' türünden bir değişkeni, 'Constant Expressions' alan noktalarda kullanamıyoruz. Velev ki ilgili 'const int' türden değişkeni bir 'integral literals' ile hayata getirmiş olsak bile. Örneğin, bu türden bir değişkeni dizi boyutu olarak veya 'switch-case' merdiveninde kullanamıyoruz. Fakat C++ dilinde böyle bir kullanım legaldir, sentaks hatasına neden olmaz. * Örnek 1, // Some codes here... int foo(); int main() { const int x = 10; // Burada 'x' değişkeni, 'integral literals' ile hayata gelmiştir. const int y = foo(); // Derlenme sırasında 'y' değişkeninin değeri bilinemememektedir. Burada 'y' değişkeni, // 'integral literals' ile hayata gelmiyor. int a[x]; // C dilinde sentaks hatasıdır. (Variable Array Length konusu hariç) int b[x]{}; // C++ dilinde legaldir. } >>> Eğer bir isim, >>>> Farklı kaynak dosyalarında kullanılmasına rağmen, aynı varlığa hitap ediyorsa, bu isim 'external linkage' haldedir. >>>>> C ve C++ dilinde bir isim ve/veya fonksiyon 'global namespace' alanında kullanılmışsa, bu isim 'external linkage' durumundadır. İsmin 'const' ile nitelenmesi durumunda ilgili isim C++ dili kurallarına göre artık 'internal linkage' durumuna geçmiştir fakat C dili için böyle bir kural söz konusu değildir. >>>> Farklı kaynak dosyalarında kullanılmasına rağmen, birbirinden farklı varlıklara da hitap ediyorsa, bu isim 'internal linkage' haldedir. >>>>> C ve C++ dilinde bir isim ve/veya fonksiyon 'global namespace' alanında kullanılmışsa, bu isim 'external linkage' durumundadır. İsmin 'static' ile nitelenmesi durumunda ilgili isim her iki dil kurallarına göre artık 'internal linkage' durumuna geçmiştir. >> 'enums' kullanımındaki farklılıklar: >>> C dilinde numaralandırma sabitlerinin türleri 'int' türüne eşitken, C++ dilinde ya elemanların veri tiplerine göre yada kullanıcının atadığı 'T' veri tipi türündedir. >>> C dilinde aritmetik türlerden 'enum' türlere, 'enum' türlerden aritmetik türlere ve farklı 'enum' türleri arasında otomatik bir dönüşüm vardır. Fakat C++ dilinde sadece 'enum' türlerden aritmetik türlere otomatik bir dönüşüm vardır. Bu da beraberinde bir takım handikaplar getirmektedir. Bunları örtbas etmek için de Modern C++ ile dile 'enum class' türler eklendi. Artık 'enum class' türleri kullanacağız. >> 'user-defined types' kullanımlarındaki farklılıklar: >>> C dilinde bu tip türler, ki bunlar 'struct', 'unions' ve 'enum' şeklindedir, yalnız başına kullanılamıyorlar. Yani sadece etiket ismi ile birlikte kullanılamazlar. Fakat C++ dilinde sadece etiket ismi ile birlikte kullanılabilirler. * Örnek 1, // Some codes here... struct Data{ // Etiket ismi 'Data' int mx; }; typedef struct Data_{ int my; }DataTwo; int main() { Data myData; // C dilinde bu sentaks hatasıdır. Legal hale getirmek için ya 'struct' anahtar sözcüğü ile birlikte ya da // 'typedef' yaparak kullanmalıyız. Fakat C++ dilinde geçerlidir. Ne 'struct' anahtar sözcüğüne ne de // 'typedef' kullanımına gerek yoktur. struct Data myData; // C diline göre legal kullanım bu şekildedir. DataTwo myDataTwo; // C diline göre legal bir kullanım şeklidir. } * Örnek 2, // Some codes here... struct Data{ // Etiket ismi 'Data' int Data; // Veri elemanının ismi de 'Data' }; int main() { struct Data Data; // C dilinde bu tip bir kullanım geçerlidir. // C++ dilinde ise 'name look-up' kuralları gereği geçersizdir. Data: // goto- label // C dilinde bu tip bir kullanım da geçerlidir. // C++ dilinde ise 'name look-up' kuralları gereği geçersizdir. } >> C dilinde 'struct' yapı türünün en az bir elemanı olmak zorunda. Fakat C++ dilinde böyle bir zorunluluk yoktur. İçi boş bir 'struct' de kurabiliriz. >> Fonksiyon bildirimleri ve tanımlamalarındaki farklılıklar: >>> C dilinde 'implicit int' kullanımı C99 standartları ile sentaks hatasına neden olsa da, geçmişe dönük uyumluluktan dolayı, ana akım derleyiciler tarafından geçerli kabul edilmekte. Fakat C++ dilinde bu kullanım sentaks hatası. * Örnek 1, // Some codes here.. foo(); // Fonksiyonun bildiriminde geri dönüş değerinin türü yazılmamasına rağmen, 'implicit int' gereği, derleyici tarafından // 'int' türü yazılmakta. fuu() { } // Fonksiyonun tanımında geri dönüş değerinin türü yazılmamasına rağmen, 'implicit int' gereği, derleyici tarafından // 'int' türü yazılmakta. fpp(x,y) { return x+y; } // Fonksiyonun tanımında, geri dönüş değerinin ve fonksiyon parametrelerinin türleri yazılmamasına rağmen, // 'implicit int' gereği, derleyici tarafından 'int' türü yazılmakta. int main() { } * Örnek 2, #include fpp(x,y) { return x+y; } int main() { /* # OUTPUT # main.c: In function ‘fpp’: main.c:11:1: warning: type of ‘x’ defaults to ‘int’ [-Wimplicit-int] main.c:11:1: warning: type of ‘y’ defaults to ‘int’ [-Wimplicit-int] fpp(2,3) : 5 */ printf("fpp(2,3) : %d", fpp(2,3)); } >>> C dilinde yapılan fonksiyon bildirimlerinde/tanımlamalarında, parametre parantezinin içerisine 'void' anahtar sözcüğünün yazılması, ilgili fonksiyonun parametre almayacağını söylemektedir. Haliyle bu fonksiyonlara parametre geçmek sentaks hatasıdır. Fakat bu parantezlerinin içerisinin boş bırakılması, fonksiyon parametreleri hakkında bilgi verilmediği anlamına gelir ki bu tip fonksiyonlara parametre geçebiliriz. Sentaks hatası oluşturmaz. Ama C++ dilinde bu parantezlerinin içinin boş bırakılması veya 'void' anahtar sözcüğünün yazılması aynı anlama gelmekte olup, tercihen 'void' yazmalıyız. Yani bu parantezlerin içini boş bırakmamalıyız. İllaki bir şeyler yazmalıyız. >>> C dilindeki fonksiyon bildirimlerinde, fonksiyon parametrelerine isim vermek zorunda değiliz. Fakat fonksiyon tanımlamalarında isim vermek zorundayız. Ama C++ dilinde ne fonksiyon bildirimlerinde ne de fonksiyon tanımlamalarında, fonksiyon parametrelerine isim vermek zorunda değiliz. Sadece 'Function Overload Resolution' için kullanılır bu taktik. >>> C dilinde, geriye değer döndüren bir fonksiyonun bloğunda 'return' anahtar sözcüğü ile değer döndürmemek, legal bir davranıştır. Sentaks hatasına neden olmaz. Sadece bu fonksiyonu çağırıp da geri dönüş değerini kullanmamız 'Tanımsız Davranış' a neden olur. Fakat C++ dilinde bu tip fonksiyonların bloğunda 'return' kullanılmaması sentaks hatasıdır. NOT: C++ DİLİNDE 'main()' FONKSİYONUNUN GERİ DÖNÜŞ DEĞERİ 'int' OLMAK ZORUNDA FAKAT C DİLİNDE BÖYLE BİR ZORUNLULUK YOKTUR. 'void' DE OLABİLİR 'double' DA OLABİLİR. NOT: C++ DİLİNDE VE C99 STANDARTLARI SONRASINDA C DİLİNDE 'main()' FONKSİYONUNUN BLOĞU İÇERİSİNDE 'return 0;' DEYİMİ YAZILMAZ İSE DERLEYİCİ KENDİSİ YAZIYOR. >>> 'implicit-function-declaration' : aşağıdaki örneği inceleyin, * Örnek 1, // Some code here... // int func(); //(2) Derleyici tarafından varsayım üzerine yazılan fonksiyon bildirimi. int main() { func(); // (1) C derleyicisi burada 'func' ismini bulamayacağı için, kendisi varsayımda bulunarak, yukarıdaki fonksiyon // bildirimini yapıyor. func(2, 5); // (3) Bu 'func' fonksiyonuna parametre geçebiliriz. Çünkü fonksiyon parametleri hakkında bilgi verilmemiştir. double dVal = func(); // (4) Bu 'func' fonksiyonunun geri dönüş değerini kullanabiliriz fakat bu durum 'Tanımsız Davranış' oluşturur. // Yukarıdaki senaryo C99 ile sentaks hatası olmasına rağmen, geriye dönük uyumluluktan dolayı hala geçerlidir. // Fakat C++ dilinde bu durum direkt sentaks hatasıdır. } * Örnek 2, #include int main() { /* # OUTPUT # main.c:13:2: warning: implicit declaration of function ‘func’ [-Wimplicit-function-declaration] 13 | func(); | ^~~~ main.c: At top level: main.c:16:1: warning: return type defaults to ‘int’ [-Wimplicit-int] 16 | func() | ^~~~ func() was called. */ func(); } func() { printf("func() was called."); } >> 'string literals' kullanım farklılıklar: >>> Hem C dilinde hem de C++ dilinde 'string literals' değiştirme girişiminde bulunmak 'Tanımsız Davranışa' a neden olur. >>> C dilinde bir 'string literals' in türü 'char[n]' türünden.Fakat C++ dilinde ise bu türler, 'const char[n]' türünden. Eğer bir 'string literals' işleme sokarsak, örneğin bir fonksiyona argüman olarak geçersek, 'array-decay' den dolayı sırasıyla 'char[n]' => 'char*' ve 'const char[n]' => 'const char*' şeklinde bir dönüşüm gerçekleşir. Aşağıdaki örneği inceleyelim. * Örnek 1, // Some codes here... int main() { char* xPtr = "Ahmet"; // Bu ifade C diline göre legal, C++ diline göre illegal bir deyimdir fakat C dilinde de kullanılmamalıdır. const char* yPtr = "Ahmet"; // Bu ifade ise hem C dilinde hem de C++ dilinde olması gereken kullanımı göstermektedir. // Yukarıdaki her iki gösterici üzerinden "Ahmet" yazısını DEĞİŞTİRME GİRİŞİMİNDE BULUNMAMALIYIZ. 'Tanımsız Davranış' // meydana gelecektir. } >> 'character literals' kullanım farklılıkları: >>> C dilinde bu tip literaller, ki bunlar 'A' şeklindeki karakter sabitleridir, 'int' veri türündendir. Fakat C++ dilinde bu tip literallerin türü 'char' veri türündendir. >> 'char' türden veri tutan bir diziye 'string literal' atanması durumundaki farklılıklar: >>> Aşağıdaki örneği inceleyin, * Örnek 1, // Some codes here... int main() { char str[] = "mert"; // Normal şartlarda yukarıdaki dizinin türü 'char[5]' ve eleman sayısı ise beştir. char strTwo[4] = "memo"; // C++ diline göre illegal, C diline göre legal. // Fakat yukarıdaki gibi köşeli parantezlerin arasında, 'string literals' eleman sayısından daha düşük bir eleman // sayısı yazarsak, 'strTwo' isimli diziyi 'null-terminated-string' olarak ele alamayız. Artık 'strTwo' isimli // dizinin elemanları sırasıyla 'm', 'e', 'm' ve 'o' dan oluşmaktadır. C++ dilinde ise yukarıdaki gibi bir değer verme // sentaks hatasıdır. } >> 'Value-Category' konusundaki farklılıklar: >>> C++ dilinde '++' ve '--' operatörleri ön-ek olarak kullanıldığında, geri döndürdüğü değer 'L-Value Expression' kategorisine girer. İlgili operatörler son-ek olarak kullanılması durumunda, geri döndürdüğü değer 'R-Value Expression' kategorisindedir. Fakat C dilinde bu iki operatör her zaman için 'R-Value Expression' kategorisinde değer döndürür. >>> C++ dilinde ',' operatörü kullanıldığında, geri döndürdüğü değer 'L-Value Expression' kategorisine girer. Fakat C dilinde geri dönen değerin kategorisi 'R-Value Expression'. >>> C++ dilinde 'ternary operator' kullanıldığında, geri döndürdüğü değer 'L-Value Expression' kategorisine girer eğer bütün argümanları 'L-Value Expression' ise. Eğer bir tanesi bile 'R-Value Expression' ise döndürdüğü değerin kategorisi 'R-Value Expression' şeklindedir. Fakat C dilinde geri dönen değerin kategorisi 'R-Value Expression'. >>> Hem C++ dilinde hem de C dilinde '+' operatörünün geri dönüş değerinin kategorisi 'R-Value Expression'. > C++ dilinde değişkenlerin 'initialize' edilme şekilleri: >> 'statik' ömürlü bir değişken hayata gelirken 'value-initialize' edilmemiş ise 'zero-initialize' edilirler. >>> 'zero-initialize' : Değişkenimiz aritmetik türden ise '0' değerini, 'boolean' türden ise 'false' değerini ve 'pointer' tip ise 'nullptr' değerini alırlar. Dizilerin bildirilmesinde de aynı durum geçerlidir; bütün öğeleri yukarıdaki şekliyle hayata gelirler. >> 'automatic' ömürlü bir değişken hayata gelirken 'value-initialize' edilmemiş ise 'default-initialize' edilirler. >>> 'default-initialize' : Değişkenler 'garbage-value' tutarlar. Tuttukları bu değerleri değiştirmeden kullanmak ise 'Tanımsız Davranışa' neden olur. Bu şekilde hayata gelen değişkenlerimizi de tam olarak kullanılacakları yerlerde bildirmeliyiz ki 'scope leakage' a neden olmasın, gereksiz isim kalabalığı oluşturmasın. Aşağıdaki örneği inceleyin, * Örnek 1, // Some codes here... int main() { int x; // 'x' isimli değişken 'garbage-value' değer tutmaktadır. Çünkü 'default-initialize' edilmiştir. int y = x; // 'x' isimli değişkenin değerini değiştirmeden kullandığımız için bu satır 'Tanımsız Davranış'a sebebiyet vermektedir. } >> C++ dilinde olan ama C dilinde olmayan 'direct-initialize' ve 'brace-initialize veya uniform-initialize' açıklamaları için aşağıdaki örneği inceleyin. * Örnek 1, // Some Code here... int main() { int x(100); // C++ dilindeki 'direct-initialize' şekli. int y(); // Artık burada bir fonksiyon bildirimi söz konusudur. İsmi 'y', geri dönüş değeri 'int' türden ve parametre almayan bir // fonksiyon. int z{200}; // C++11 ile dile eklenen 'brace-initialize veya uniform-initialize' şekli. // Yukarıdaki gibi 'brace-initialize' şekli kullanıldığında, 'narrowing conversion' sentaks hatasına neden olur. Harici olarak // bizlerin 'casting' yapması gerekiyor. Fakat 'narrowing conversion', diğer 'initialize' şekillerinde sentaks hatasına neden // olmaz. Sadece, veri kaybına neden olabilir. Eğer eşitliğin sol tarafındaki (işaretli) tür, sağ tarafındaki değeri // taşıyamayacaksa, taşma meydana gelir ve 'Tanımsız Davranış'a neden olur. İşaretsiz türlerde ise taşma; rakam, bitlerin // taşıyabileceği maksimum sayıya bölünür. Geriye kalan sayı, taşma sonucunda elde edilendir. int q{}; // Eleman hayata '0' değeri ile gelmiş olur. C++ dilinde geçerlidir, C dilinde böyle bir kullanım yoktur. int t[200]{}; // C++ dilinde legal ve bütün elemanları 'zero-initialize' eder. int tt[200] = {}; // C dilinde bu sentaks hatası. Çünkü en az bir elemanı olmak zorunda. C++ dilinde legal ve bütün elemanları 'zero-initialize' // eder. } * Örnek 2, // Some codes here... class A{}; class B{ public: B(A); }; int main() { B bx(A()); // Bu deyim derleyici tarafından iki farklı şekilde yorumlanabilir. // Bunlardan birincisi; bu çağrı, geri dönüş değeri 'B' türünden, fonksiyon parametresi 'A' türünden 'function-pointer' ve ismi // 'bx' olan bir fonksiyonun bildirimi şeklinde. // Bunlardan ikincisi; bu çağrı, 'B' türünden ve ismi 'bx' olan bir nesneye, 'A' türünden 'geçici-nesne' ile ilk değer veriyorum. // Bu durumda derleyici, 'fonksiyon bildirimi' şeklinde yorumlayacaktır bu deyimi. 'Most Vexing Parse' işte bu durumu anlatmaktadır. B cx{ A() }; // Artık 'Most Vexing Parse' durumu da ortadan kalkmış oluyor. Bu çağrı, yukarıdaki senaryodaki ikinci anlama gelmektedir. // Yani; bu çağrı, 'B' türünden ve ismi 'bx' olan bir nesneye, 'A' türünden 'geçici-nesne' ile ilk değer veriyor. } > C++ dilinde 'namespace' konusuna kısa bir giriş; Üç farklı yöntem kullanılır. İsimleri ilgili alanlarda aratmak için. >> 'using declaration' yöntemi için, aşağıdaki örneği inceleyin, * Örnek 1, // Some code here... int main() { using std::cout; // 'using declaration' dan kastedilen kısım burasıdır. Blok-scope'da yapıldığından, bu fonksiyon bloğunun // sonuna kadar kapsar. cout << "..." << "\n"; // Artık 'cout' ismi için tekrardan 'std::' nitelemesinde bulunmaya gerek yoktur. // Fakat bu durum sadece 'cout' için geçerlidir. } >> 'using directive declaration' yöntemi için de aşağıdaki örneği inceleyin, * Örnek 1, // Some code here... using namespace std; // 'using directive declaration' dan kastedilen kısım burasıdır. Global-namespace alanında bildirildiğinden, // kaynak dosyanın sonuna kadar kapsar. int main() { cout << "..." << "\n"; // Artık 'std' isim alanındaki bütün isimleri, 'std::' nitelemesi yapmadan kullanabiliriz. } >> 'ADL : Arguman Dependent Look-Up' yöntemini ileride inceleyeceğiz. > C++ dilinde 'reference' semantiği: "Legacy C++ döneminde kullanılan referanslar ve Modern C++ ın getirdiği araçları kullanmak için oluşturulan referanslar", şeklinde kabaca iki gruba ayırabilir ve bunları en haliyle 'L-Value Reference' ve 'R-Value Reference' şeklinde isimlendirebiliriz. Bunlardan; >> Legacy C++ döneminden beri kullanılagelen referanslara kabaca 'L-Value Reference' denir. >>> 'L Value References', bir ismin yerine geçen referans isimlerdir. Hayata gelirken ilk değer vermek zorunludur. >>>> 'non-const L Value References', değişkenlerimize bağlarken tür uyuşmazlığı olmamalı. * Örnek 1, // Some codes here... int main() { int x = 100; int& rx = x; // 'rx' is a reference to 'x'. Yani, 'rx' demek 'x' demek. 'rx' ayrı bir nesne DEĞİLDİR. // Ama hafızada yer kaplayıp kaplamaması ayrı bir konu. Arka planda dönenler derleyiciye bağlı. rx = 1000; // x is 1000. int y = 31; rx = y; // x is 31 now. // int& rTemp; // Sentaks hatası. Çünkü ilk değer verilmedi. // double& dr = x; // Sentaks hatası. Çünkü tür uyuşmazlığı var. İlgili referansın kendisi 'const' olsaydı eğer geçerli olacaktı. } * Örnek 2, // Some codes here... int main() { int x{10}; int* xPtr{&x}; int& xRef{*xPtr}; // 'xRef' is a reference to 'x' now. ++xRef; // x is now '11'. } * Örnek 3, // Some codes here... int main() { int a[]{10, 20, 30, 40}; int* p{a}; // 'p' göstericisi, array-decay mekanizmasından dolayı, 'a' dizisinin ilk elemanını göstermekte. int* &r{p}; // 'r' is a reference to 'int*'. Yani 'r' demek 'p' demektir. ++r; // 'r'/'p' artık ilgili 'a' dizisinin ikinci elemanını gösteriyor. ++*r; // 'r'/'p' tarafından refere edilen/gösterilen o elemanın değeri bir arttırılıyor. } * Örnek 4, // Some codes here... int main() { int a[]{10, 20, 30, 40, 50}; // 'a' türü => int[5] int (&ra)[5] = a; // 'ra' artık 'a' dizisini refere etmektedir. Yani 'a' demek 'ra' demektir. // 'ra' türü => int (&)[5]. 'a' türü => int[5] // auto& ra = a; // Yukarıdaki bildirim şekli ile aynı anlamdadır. 'Type-deduction' mekanizmasını kullanır. // auto ra = a; // Bu şekilde kullanılırsa 'array-decay' mekanizması devreye girecektir. Yani 'a = &a[0]' şeklinde // olacaktır ki bu durumda 'ra' nın türü ise 'int*' olacaktır. std::cout << ra[3] << "\n"; // Ekrana '40' değerini yazdıracaktır. int* ptr = ra; // 'array-decay' mekanizmasından dolayı, 'ptr' göstericisi, ilgili dizinin ilk elemanını göstermektedir. } >> Modern C++ ile dile eklenen 'Move Semantics' ve 'Perfect Forwarding' mekanizmanlarında kullanılnan referanslara ise kabaca 'R-Value Reference' denir. > 'value category' : >> C dilinde ifadeler(expressions) ya 'L-Value Expression' ya da 'R-Value Expression' olabilir. Üçüncü bir kategori türü yoktur. Bir ifadenin; >>> 'L-Value Expression' olması : bir nesneye karşılık gelmesi, bellekte bir yeri olması gibi özellikleri barındırır. >>> 'R-Value Expression' olması : doğrudan bellek alanında bir karşılığının olmaması anlamına geliyor. Yani o ifade, bellek alanında özdeşleşmemiştir. >> Legacy C++ döneminde de yukarıdaki değer kategorileri mevcut iken, Modern C++ ile değer kategorileri üç kısma ayrılmışlardır. Bunlar; 'L-Value Expression', 'PR-Value Expression' ve 'X-Value Expression'. >> NOT : 'non-const' SOL TARAF REFERANSLARINA, SAĞ TARAF DEĞER KATEGORİSİ İLE DEĞER VEREMEYİZ. /*============================================================================================================*/ (03_19_09_2020) > 'value category' (devam): >> 'call-by-value' döndüren fonksiyonların geri dönüş değeri de 'PR-value Expression' kapsamına girmektedir. 'L-Value Referance' bağlanamazlar. > C++ dilinde 'reference' semantiği (devam): >> Fonksiyonlara da referanslar bağlanabilir. * Örnek 1, // Some code here... int foo(int, int); int main() { int (*fPtr)(int, int) = foo; // 'function-pointer conversion' mekanizmasından dolayı 'fPtr' isimli gösterici yukarıdaki 'int foo(int, int)' imzalı fonksiyonu // göstermektedir. // ... = &foo; // 'address-of' operatörü kullanıldığı için 'function-pointer conversion' mekanizması devreye girmiyor. // Fakat yine aynı kapıya çıkıyoruz. // Yukarıdaki her iki senaryoda da 'fPtr' nin gösterdiği değer 'foo()' fonksiyonunun adresidir. int (&rPtr)(int, int) = foo; // Referans semantiğini kullanırsak da bu şekilde bildirim yapacağız. Artık 'rPtr' demek 'foo()' demektir. } >> Dizilere de referanslar bağlanabilir. * Örnek 2, // Some code here... int main() { int a[10] = {0}; // 10 elemanlı, elemanları 'int' türden olan bir dizi. int* p = a; // Burada 'p' isimli gösterici, 'a' dizisinin ilk elemanını göstermekte (pointer-to-int). // Bu 'p' göstericisinin değerini bir arttırdığımız zaman, ilgili dizinin ikinci elemanını gösterecektir. int(*pArray)[10] = &a; // Burada 'pArray' isimli gösterici, bir dizi göstericisi. 10 elemanlı bir diziyi işaret eder. // Bu 'pArray' isimli göstericisinin değerini bir arttırdığımız zaman, bir sonraki 10 elemanlı diziyi gösterir. Eğer köşeli // parantez içerisine '10' rakamı yerine '11' rakamı yazsaydık, sentaks hatası oluşmuş olacaktı. int (&rArray)[10] = a; // Burada 'rArray' demek aslında 'a' dizisi demek. 'a' DİZİSİNİN İLK ELEMANI DEMEK DEĞİLDİR. // YANİ DİZİNİN GENELİNİ REFERANSTIR. 'array-decay' MEKANİZMASINDAN SÖZ EDİLEMEZ. // Eğer köşeli parantez içerisine '10' yerine '5' yazsaydık sentaks hatası alacaktık. int& r = a[0]; // Burada ise 'r' demek 'a' dizisinin ilk elemanı demek. int& rTwo = *a; // Burada da 'rTwo' demek 'a' dizisinin ilk elemanı demek. Çünkü 'array-decay' mekanizması devreye girmiştir. } >> USE 'references' WHEREVER YOU CAN; USE 'pointers' WHEREVER YOU MUST. >> Fonksiyonların geri dönüş değeri olarak 'reference' kullanımı: * Örnek 1, // Some code here... int g = 100; int& foo() { // some code here... return g; } int func() { return 31; } int main() { // foo(); // Artık bu ifadenin değer kategorisi 'L-Value Expression' kategorisindedir. Çünkü bize 'g' nesnesini referans yoluyla // döndürmektedir. Döndürülen nesnenin adresini alabiliriz de. // &foo(); // Doğrudan buna atama da yapabiliriz. // foo() = 32; // 'L-Value Referance' a da bağlayabiliriz. // int& r = foo(); // Artık 'r' demek 'g' demektir. // NOT: 'func()' fonksiyonun geri döndürdüğü değerin kategorisi ise hala 'R-Value Expression' kategorisindedir. } * Örnek 2, // Some code here... int* foo() { int x = 10; ++x; return &x; } int main() { int* y = foo(); // 'foo()' FONKSİYONU İÇERİSİNDE 'automatic ömürlü' BİR NESNE 'Call-By-Reference' YOLUYLA DÖNDÜRÜLDÜĞÜ İÇİN // 'Tanımsız Davranış' A NEDEN OLUR. } >>> Eğer bir fonksiyonun geri dönüş değerinin kategorisi 'L-Value Expression' ise; >>>> Bu fonksiyon statik ömürlü bir nesne döndürmektedir. Bu durumda bu nesne ya global isim alanındaki bir nesne yada fonksiyon bloğunda 'static' anahtar sözcüğü ile nitelenmiş bir nesnedir. >>>> Bu fonksiyon dinamik ömürlü bir nesne döndürmektedir ('malloc()' vs.). * Örnek 1, // Some code here... struct Data{ int x, y, z; }; // Pointer Semantics kullanıldı. Data* createData(int a, int b, int c) { Data* p = (Data*)malloc(sizeof(Data)); if(!p) { std::cerr << "Not Enough Memory\n"; exit(EXIT_FAILURE); } p->x = a; p->y = b; p->z = c; return p; } // Reference Semantics kullanıldı. Data& createDataR(int a, int b, int c) { Data* p = (Data*)malloc(sizeof(Data)); if(!p) { std::cerr << "Not Enough Memory\n"; exit(EXIT_FAILURE); } p->x = a; p->y = b; p->z = c; return *p; } int main() { Data* ptr = createData(10, 20, 30); // Pointer Semantics kullanıldı. Data& ptrR = createDataR(10, 20, 30); // Reference Semantics kullanıldı. } >>> Bu fonksiyon, çağıran koddan aldığı nesneyi, çağıran koda tekrar geri döndürmektedir. >> C++ dilinde 'const T*' türünden 'T*' türüne ve 'const T&' türünden 'T&' türüne otomatik dönüşüm olmadığından, böyle bir dönüşüm girişiminde bulunmak sentaks hatasıdır. >>> 'const T' türünden bir nesneyi, 'T&' türünden bir referansa bağlayamayız. Benzer şekilde 'const T' türden bir nesnenin adresini de 'T*' türden bir göstericiye, atayamayız/ilk değer olarak veremeyiz. Her iki durum da sentaks hatasıdır. * Örnek 1, // Some code here... int main() { const int x = 10; int* ptr = &x; // Bu kod parçası sentaks hatasına neden olur. int& ref = x; // Bu kod parçası da sentaks hatasına neden olur. } >>> OKUMA AMAÇLI KULLANILAN REFERANSLAR VE GÖSTERİCİLER 'const' OLMALI. YAZMA AMAÇLI KULLANILAN REFERANSLAR VE GÖSTERİCİLER 'non-const' OLMALI. >>> EĞER DİLİN ARAÇLARINI KULLANARAK 'const T' TÜRÜNDEN BİR NESNEYİ 'T' TÜRÜNDEN BİR NESNEYE CAST EDEREK(C-style), DEĞİŞTİRME GİRİŞİMİ DE 'Tanımsız Davranış' A NEDEN OLUR. * Örnek 1, // Some code here... int main() { const int x = 10; int* ptr = nullptr; // Some code here... ptr = (int*)&x; *ptr = 12; // 'Tanımsız Davranış'. } >> C++ dilinde 'R-Value Expression' kategorisinde olan nesneleri/değişkenleri, 'const T&' türünden 'L-Valur Referance' referanslara bağlayabiliriz. Fakat bu nesneleri, 'T&' türünden 'L-Value Referance' referanslara bağlamayamız. * Örnek 1, // Some code here... int main() { int& r = 100; // Sentaks hatasıdır. Çünkü 'R-Value Expression' olan bir ifade, 'non-const L-Value Referance' türünden bir referansa bağlanamaz. const int& cr = 1000; // Legaldir. Çünkü 'R-Value Expression' olan bir ifade, 'const L-Value Referance' türünden bir referansa bağlanabilir. // Çünkü, arka planda derleyici geçici bir nesne oluşturur ve o nesneyi de ilgili 'R-Value Expression' nın değeri ile hayata // getirir. // const int gn = 1000; // Sonrasında da bizim 'cr' isimli referansımızı da oluşturulan bu geçici nesneye bağlar. // const int& cr = gn; // Bahsi geçen referansın isminin skopunun sona ermesi ile geçici nesnenin de hayatı sonra erer. } >> C++ dilinde referansın türü ile bağlanacak nesnenin türü aynı olmak zorundadır. Fakat 'const' bir referanslar için geçerli değildir. Farklı türden nesneleri, 'const' referanslara bağlayabiliriz. * Örnek 1, // Some code here... int main() { int ival = 100; double& dRef = ival; // Sentaks hatasıdır. Çünkü 'int' türünden bir nesne, 'double' türünden bir referansa bağlanamaz. const double& cDoubleRef = ival; // Legaldir. Farklı türden nesneler, farklı türden 'const' referanslara bağlanabilir. Yukarıdaki 'geçici nesne' oluşturma // mekanizması burada da işler. // const double gn = ival; // Burada 'int' türünden 'double' türüne otomatik dönüşüm yapılmaktadır. Veri kaybı meydana gelebilir. // const double& cDoubleRef = gn; } >> C++ dilinde 'reference' semantiği ile 'pointer' semantiği arasındaki farklılıklar: >>> 'pointer' bir değişkene ilk değer vermek zorunlu değil fakat 'reference' a ilk değer vermek zorunludur. >>> Bir 'const-pointer' to 'int' şeklinde olan, yani kendisinin 'const' olduğu 'pointer' lar aynı nesneyi göstermek zorundadırlar. Benzer şekilde de 'reference' lar da aynı nesneye bağlı olmak zorundalar. 'pointer' to 'int' şeklinde olan, yani kendisinin 'const' olmadığı 'pointer' lar farklı nesneleri gösterebilirler. >>> Kendisinin 'const' olmadığı 'pointer' lara 'nullptr' değeri ile ilk değer verebiliriz veya 'nullptr' değerini atayabiliriz. Fakat aynı durum 'reference' lar için geçerli değildir. >>> C dilinde 'pointer' lar dilin kuralından bağımsız olarak iki durumdadırlar; 'valid pointer' veya 'invalid pointer'. >>>> 'valid pointer' durumda olanlar ya bir nesnenin adresini tutarlar ya bir dizinin bittiği yerin adresini(dizinin son elemanından sonraki yer) tutarlar ya da 'nullptr' tutarlar. >>>>> NOT: DİZİNİN BİTTİĞİ YERİN ADRESİNİ TUTAN 'pointer' LARI '*' OPERATÖRÜ İLE DEREFERENCE YAPMAK 'Tanımsız Davranış' A NEDEN OLUR. >>>> 'invalid pointer' durumda olanlar ya 'Garbage Value' tutan 'pointer' lar ya da 'Dangling Pointer'(gösterdiği değişkenin ömrü bitti ama kendisinin ömrü daha bitmedi) olanlardır. >>> 'pointer' gösteren 'pointer' legal iken, yani bir göstericinin adresini tutan bir başka gösterici, 'reference' to 'reference' diye bir kavram yoktur. >>> Elemanları 'pointer' olan bir 'array' mevcut, fakat elemanları 'reference' olan bir dizi mevcut değildir. >> C++ dilindeki 'R-Value Referance' lar 'move semantics' ve 'perfect forwarding' mekanizmalarında kullanılırlar. İki adet '&' işareti deklaratör olarak kullanılır. Nasılki 'L-Value Referance' lara 'L-Value Expression' ile ilk değer vermek mecburi, ki referansın 'const' olmadığı varsayılıyor, 'R-Value Referance' lara da 'R-Value Referance' ile ilk değer vermek mecburidir. * Örnek 1, // Some code here... int main() { int x{}; int& r = 10; // Sentaks hatasıdır. 'non-const L-Value Referance' ları 'R-Value Expression' lara bağlayamayız. int&& rr = x; // Sentaks hatasıdır. 'R-Value Referance' ları 'L-Value Expression' lara bağlayamayız. } > C++ dilinde 'Type Deduction' mekanizması; >> Derleme zamanına ilişkin bir mekanizmadır. Programın çalışma zamanı ile bir ilgisi yoktur. >> Derleyici, koda bakarak bir tür çıkarımında bulunuyor. Değişkenlerin türünü çıkartırken, fonksiyonların geri dönüş değerlerinin türünü çıkartırken, fonksiyonların aldığı parametlerin türlerini çıkartırken, fonksiyon şablonlarında ve sınıf şablonlarında bu mekanizma kullanılmaktadır. >> 'auto' ve 'decltype' anahtar sözcükleri, yani 'specifiers' ları, kullanılır. >>> 'auto' anahtar sözcüğü C dilindeki 'auto' anahtar sözcüğü ile bir alakası yoktur. C++11 ile bu anahtar sözcük bambaşka bir anlam kazanmıştır ve içerisinde birbirinden farklı üç adet kural seti barındırır. Bu kural setleri, >>>> Sadece 'auto' anahtar sözcüğü kullanılması, yani "auto x = init_expression;": İfadenin türü neyse, o türden bir çıkarım yapılır. * Örnek 1, // Some code here... int foo(int, int); int main() { auto x = 100; // 'x' is a 'int'. double dval = 3.4; auto d = dval; // 'd' is a 'double' // İlk değer veren ifade olarak bir dizi ismi kullanıldığında, 'array-decay' mekanizması devreye girer // (dizinin 'const' bir dizi olması kuralı değiştirmez): int a[4]{1, 2, 3, 4}; // Burada 'a' dizisinin türü int[4]. Çünkü daha bu mekanizma devreye girmedi. auto xx = a; // 'array-decay' mekanizması ile dizi ismi, ilk elemanının adresine döner. (int[4] => &a[0]) // Sonrasında da bu adrese bakılarak tür çıkarımı yapılır. 'xx' is a 'int*'. // İlk değer veren ifade olarak 'const' bir dizi ismi kullanıldığında, 'array-decay' mekanizması devreye girer: const int aa[]{1, 2, 3, 4}; // Burada 'aa' dizisinin türü const int[4]. Çünkü daha bu mekanizma devreye girmedi. auto typeAA = aa; // 'array-decay' mekanizması ile dizi ismi ilk elemanının adresine döner. (const int[4] => &a[0]). // Ki bu durumda bu adresin türü 'const int*'. Sonrasında da bu adrese bakılarak tür çıkarımı yapılır. // 'x' is a 'const int*'. Çünkü göstericinin kendisi 'const' değil. cout << "Value : " << std::is_same() << "\n"; // Value: 1 cout << "Value : " << std::is_same() << "\n"; // Value: 0 // İlk değer veren ifade olarak 'const' bir nesne kullanıldığında, 'const' özelliği düşüyor. const int xxx = 1000; auto y = xxx; // 'y' is a 'int'. // İlk değer veren ifade olarak 'reference' bir nesne kullanıldığında, 'reference' özelliği düşüyor. int ival = 10; int& rIval = ival; auto xxxx = rIVal; // 'xxxx' is a 'int'. // İlk değer veren ifade olarak 'const-reference' bir nesne kullanıldığında, 'const-reference' özelliği düşüyor. int ivalTwo = 10; int& rIvalTwo = ivalTwo; auto xxxxx = rIValTwo; // 'xxxxx' is a 'int'. // İlk değer veren ifade olarak bir fonksiyon ismi kullanıldığında, 'function-to-pointer' mekanizması devreye girer. // Bu durumda çıkarılan tür 'function-pointer' olacak. auto x = foo; // 'function-to-pointer conversion' happens. 'x' is a 'int (*)(int, int)'. } >>>> 'auto' anahtar sözcüğü ile birlikte '&' deklaratörünün kullanılması, yani "auto& xx = init_expression": İfade olan türe referanstır. 'L-Value Reference' olma zorunluluğu vardır. * Örnek 1, // Some code here... int foo(int); int main() { auto& r = 'init_expression'; // Burada 'r' nin 'L-Value Referance' olması zorunlu. // Yani ya bir sentaks hatası meydana gelecek ya da 'auto' yerine bir tür çıkarımı yapılacak. auto& r = 20; // İlk değer veren ifade olarak 'R-Value Expression' kullanılması. // Burada 'auto' yerine 'int' gelecektir. Çünkü '20' rakamının türü 'int'. 'r' ise 'int&' olacaktı. // Bu durumda, 'L-Value Referance' türlere 'R-Value Expression' bağlanamayacağı için, sentaks hatası olacaktır. int x = 10; auto& rx = x; // İlk değer veren ifade olarak 'L-Value Expression' kullanılması. // 'init_expression' yerine 'L-Value Expression' gelmiştir. 'x' in türü 'int' olduğu için 'rx' de 'int&' türünden // olacaktır. Bu durumda, 'L-Value Reference' türlere 'L-Value Expression' bağlanabileceği için sentaks hatası // olmayacaktır. Aslında şuna eşit olacak => "int& rx = x;". const int y = 100; auto &ry = y; // İlk değer veren ifade olarak 'const' bir 'L-Value Expression' kullanılması. // Eğer 'const' özelliği kaybolsaydı, tür çıkarımı 'int' e göre yapılacaktı. Yani 'auto' yerine 'int' gelecek, // 'ry' nin türü ise 'int&' olacaktı. Fakat 'non-const' bir referans, 'const' bir nesneye bağlanamayağı için bu // durum da sentaks hatasına yol açacaktı. Dolayısıyla böyle bir senaryoda yukarıdaki nesnenin 'const' özelliği // düşmüyor. Tür çıkarımı 'const int' şeklinde yapılıyor. 'ry' nin türü de 'const int&' oluyor. Aslında şuna eşit // olacak => "const int& ry = y". int a[] = {10, 20, 30, 40}; auto& ra = a; // Bizim referansımız artık diziye referans. // İlk değer veren ifade olarak bir dizi ismi kullanıldığında, 'array-decay' mekanizması devreye girmez. Dolayısıyla, // bizim referansımız bir diziye referans olmuş olur. // Eğer 'array-decay' mekanizması devreye girseydi, şu şekilde olacaktı => "auto& ra = &a[0];". // Fakat bu durumda sentaks hatası oluşacaktı. Çünkü 'address' operatörü ile elde edilen deyimler 'R-Value Expression' // kategorisindedir. 'L-Value Referance' lara da 'R-Value Expression' bağlanması sentaks hatasıdır. // Uzun lafın kısası, aslında, şuna eşit olacak => "int (&ra)[4] = a". Yani 'ra' demek 'a' demektir. auto& rsl = "emre"; // İlk değer veren ifade olarak 'string literal' kullanılsaydı eğer, yine diziye referans olacaktı. // Burada "emre" 'string literal' inin türü 'const char[5]' şeklinde. 'auto' kelimesinin karşılığı da 'const char[5]'. // Çıkarım yapılan bu türe referans ise 'const char(&rsl)[5]' şeklinde. // Dolayısıyla yukarıdaki 'expression' şuna eşit olacaktı => " const char(&rsl)[5] = "emre"; ". auto& rFunc = foo; // İlk değer veren ifade olarak fonksiyon ismi kullanılırsa, referansımız bir 'function reference' olur. // Dolayısıyla şuna eşit olacaktır => "int (&rFunc)(int) = foo"; } >>>> 'auto' anahtar sözcüğü ile birlikte '&&' deklaratörünün kullanılması, yani "auto&& xx = init_expression": Bu durumda referansımız 'R-Value Referance' değil, ileride işlenecek olan 'forwarding Referance' şeklinde. >>> 'decltype' : Genellikle 'generic programming' tarafında kullanılır. 'decltype' kelimesinden hemen sonra '()' gelmek zorunda. Bu parantez çiftinin içerisine yazılacak 'expression' lara göre derleyici tür çıkarımında bulunuyor. Çıkarımda bulunan bu türü, tür gereken her yerde kullanabiliriz. Örneğin, değişken tanımlamalarında, fonksiyon tanımlamalarında vs. * Örnek 1, // Some code here... int g = 100; decltype(g) funcDecltype(decltype(g) funcParam); decltype(g) anotherVar; int main() { decltype(g) xx; } Burada da iki ayrı kural seti bulunmaktadır. >>>> 'decltype' parantezi içerisinde bir isim kullanılmasına("decltype(x)", "decltype(x.y)", "decltype(ptr->y)" vs.) dair kural seti: >>>>> Parantez içerisindeki ismin türü ne ise, o türden bir çıkarım yapılıyor. Örnek 1, // Some code here... int g = 100; const int cg = 200; int main() { decltype(g) dx; // int xx; decltype(cg) dcx; // Parantez içerisindeki isim 'const' türden ise 'const' özelliği düşmüyor. // "const int dcx;" ile eş değer. Fakat C++ dilinde 'const' nesnelere ilk değer vermek zorunlu olduğundan, sentaks hatası // oluşacak. // decltype(cg) dcx = 101; int& rg = g; decltype(rg) drg; // Parantez içerisindeki isim 'reference' türden ise 'reference' özelliği düşmüyor. // "int& drg;" ile eş değer. Fakat C++ dilinde 'reference' nesnelere ilk değer vermek zorunlu olduğundan, sentaks hatası // oluşacak. // decltype(rg) drg = g; // Parantez içerisindeki isim 'const' 'reference' türden ise 'const' 'reference' özelliği düşmüyor. // Parantez içerisinde dizi ismi kullanıldığında 'array-decay' mekanizması devreye girmiyor. int a[4] = {1, 2, 3, 4}; // 'a' dizisinin türü 'int[4]'. decltype(a) b; // 'b' dizisinin türü de 'int[4]' olacak. const char str[] = "alican"; // 'str' dizisinin türü 'const char[7]'; decltype(str) var; // 'var' dizisinün türü de 'const char[7]' olacaktır. Fakat ilk değer vermediğimiz için bu çağrı sentaks // hatası olacak. Çünkü 'const' türden değişkenlere ilk değer vermeliyiz. // decltype(str) ss = "emre"; // Aslında şuna eşit olacaktı => " const char[7] ss = "emre"; ". } >>>> 'decltype' parantezi içerisinde isim formunda olmayan("decltype(10)", "decltype(*ptr)", "decltype(x + 5)", "decltype((x))" vs.) bir ifade kullanılmasına dair kural seti: İlgili ifadenin 'value category' sine göre bir tür çıkarımında bulunulur. Aşağıdaki tabloyu inceleyiniz: 'decltype' parantezi içerisindeki ifade --- çıkarım yapılan tür 'L-Value Expression' => 'T&' // O ifadenin türünden 'L-Value Referance'. 'PR-Value Expression' => 'T' // O ifadenin türünün kendisi. 'X-Value Expression' => 'T&&' // O ifade türünden 'R-Value Referance'. * Örnek 1, // Some codes here... int&& foo(); int main() { decltype(102) tempVar; // 'decltype' parantezi içerisindeki deyimin değer kategorisi 'PR-Value', türü de 'int' olduğundan dolayı, tür çıkarımı 'int' // şeklinde. // int tempVar; int x = 100; int* ptr = &x; decltype(*ptr) tempVarTwo; // 'decltype' parantezi içerisindeki deyimin kategorisi 'L-Value', türü de 'int' olduğundan dolayı, tür çıkarımı 'int&' // şeklinde. // int& tempVarTwo; // Fakat referanslara ilk değer vermek zorunlu olduğundan bu durum sentaks hatası oluşturur. decltype(x) tempVarThree; // İlgili parantez içerisinde bir isim olduğundan, çıkarım yapılan tür 'int' türünden. // int tempVarThree; decltype((x)) tempVarFour; // İlgili parantez içerisindeki bir ifade olduğundan ve 'L-Value Expression' kategorisinde olduğundan, çıkarım yapılan tür // 'int&'. // int& tempVarFour; Fakat ilk değer vermediğimiz için sentaks hatası. decltype(foo()) tempVarSix; // İlgili parantez içerisindeki ifade 'X-Value Expression' ve türü de 'int' olduğundan dolayı, çıkarım yapılan tür 'int&&' // şeklinde. // int&& tempVarSix; } >>>>> PROFESYONEL DÜZEYDE BIR IFADENIN 'Value Category' BULMAK IÇIN KULLANABILECEĞIMIZ KOD: * Örnek 1, // '#s' converts the argument into double quotation mark, aka 'Stringizing operator (#)' #define pvc(x) (std::cout << "value category of '" #x << "' is : " << Valcat::pvcat << "\n") template struct Valcat { constexpr static const char *pvcat = "R value"; }; template struct Valcat { constexpr static const char *pvcat = "L value"; }; template struct Valcat { constexpr static const char *pvcat = "X value"; }; #include int main() { int x = 10; int y = 20; int *ptr = &x; pvc(x); pvc(++x); pvc(x++); pvc((x,y)); pvc(*ptr); } * Örnek 2, // Some codes here... // #x ifadesi, argüman olan ifadeyi bir yazıya çevirmiştir. // (x) ifadesi, argüman olan ifadenin hesaplanmış halidir. #define pvc(x) (std::cout << "The Result of (" << #x << ") = " << (x) << ", " << Valcat::pvcat << "\n") template struct Valcat { constexpr static const char *pvcat = "R value"; }; template struct Valcat { constexpr static const char *pvcat = "L value"; }; template struct Valcat { constexpr static const char *pvcat = "X value"; }; #include int main() { /* # OUTPUT # The Result of (x) = 10, L value The Result of (++x) = 11, L value The Result of (x++) = 11, R value The Result of ((x,y)) = 20, L value The Result of (*ptr) = 12, L value */ int x = 10; int y = 20; int *ptr = &x; pvc(x); pvc(++x); pvc(x++); pvc((x,y)); pvc(*ptr); } > C++ dilinde kullanılan akronimler: >> AAA : Almost Always 'auto' >> ADL : Argument Dependent Look-Up >> ODR : One Definition Rule >> RAII: Resource Acquisition Is Initialization >> EBO : Empty Base Optimization >> STL : Standart Template Library >> SFINAE : Subtitution Failure Is Not An Error /*============================================================================================================*/ (04_20_09_2020) > 'constexpr' anahtar sözcüğü : >> 'Constant Expressions' : Derleme zamanında derleyicilerin, ilgili ifadelerin değerini net olarak hesaplayabildiği ifadelerdir. Bir diğer deyişle bir sabit ifadesi kullanmakla bu ifadeyi kullanmak arasında bir farklılık yoktur. >>> Örneğin C ve C++ dillerinde 'sizeof()' operatörü bir 'constant expression' dur. >>> C dilinde şu şekilde yapılan bir 'initialize' aslında 'constant expression' değildir fakat C++ dilinde 'constant expression' => "const int x = 100;". >>> Buradaki kilit nokta, eşitliğin sağ tarafında bir 'constant expression' olmasıdır. Fonksiyonların geri dönüş değeri kullanıldığında, 'x' değişkeni 'constant expression' olmaktan çıkar. * Örnek 1, // Some code here... int foo(); int main() { const int x = foo(); // 'x' is a 'const' variable. const int y = 100; // 'y' is a 'const' variable, but the expression is a 'constant expression'. } >> C++ dilinde de bu anahtar sözcük ile tanımlanan nesnelere ilk değer vermek zorunludur. Ayrıca ilk değeri veren ifade de 'constant expression' olmak zorundadır. 'const' anahtar sözcüğünde ilk değer veren ifade olarak böyle bir zorunluluk yoktur. Artık bu anahtar sözcük ile tanımlanan değişkenleri, 'constant expression' gereken her yerde kullanabilirim. * Örnek 1, // Some code here... int foo(); int main() { constexpr int x = 100; // Bir 'constant expression' gereken her yerde kullabilirim. const int y = foo(); // Bir 'constant expression' gereken her yerde kullanamam. } >> Bu anahtar sözcük aslında değişkenin türünü nitelemez. Sadece ilgili ifadenin, 'Constant Expression' yerine kullanılabileceğini söylemektedir. * Örnek 1, // Some code here... int main() { constexpr int x = 100; // 'x' değişkeninin türü 'const int'. decltype(x) y; // İlk değer verilmediğinden dolayı sentaks hatasıdır. } * Örnek 2, // Some code here... int foo(); int main() { const int x = foo(); const int y = 1000; // Bir 'constant expression'. constexpr int a = 10; // Bir 'constant expression'. constexpr int b = 20; // Bir 'constant expression'. constexpr int c = a + b; // Bir 'constant expression'. constexpr int d = a + y; // Bir 'constant expression'. constexpr int e = x + a; // Bir 'constant expression' değil. Dolayısıyla bu çağrı bir sentaks hatasıdır. Çünkü derleme zamanında değeri hesaplanamıyor. } >> 'constexpr functions' : Fonksiyonların imzalarını yazarken, geri dönüş değerinin türünü yazmadan evvel, 'constexpr' anahtar sözcüğünü kullanmamız bu fonksiyonu 'constexpr function' yapar. Eğer bu tip fonksiyonlara da argüman olarak 'constant expression' değişkenler geçilirse, bu fonksiyonun geri dönüş değeri DERLEME ZAMANINDA hesaplanır. * Örnek 1, // Some code here... constexpr int sum_square(int a, int b) { return a*a + b*b; } int main() { sum_square(10, 20); // 'sum_square' fonksiyonu bir 'constexpr function' dur. // 'sum_square' fonksiyonuna geçilen her bir parametre bir 'constant expression' dur. Yani derleyici bu ifade yerine bir 'sabit' // (500) kullanacak. constexpr int x = sum_square(10, 20); // 'x' in değeri '500'. int a = 10; int b = 20; ++a; --b; sum_square(a, b); // Sentaks hatası değildir fakat normal bir fonksiyon çağrısıdır. Geri dönüş değeri artık ÇALIŞMA ZAMANINDA elde edilecek. // Dolayısıyla bu ifade bir 'constant expression' DEĞİL. constexpr int c = sum_square(a, b); // Sentaks hatası. // 'constexpr' ANAHTAR SÖZCÜĞÜ İLE NİTELENEN ŞEYLER DERLEME ZAMANINA İLİŞKİN. } * Örnek 2, // Some code here... constexpr int sum_square(int a, int b) { return a*a + b*b; } constexpr int ndigit(int n) { if (n == 0) return 1; int digit_count = 0; while(n) { ++digit_count; val /= 10; } return digit_count; } constexpr int factorial(int n) { return n < 2 ? 1 : n * factorial(n-1); } constexpr bool isPrime(int val) { if(val == 0 || val == 1) return false; if(val % 2 == 0) return val == 2; if(val % 3 == 0) return val == 3; if(val % 5 == 0) return val == 5; for(int k = 7; k * k <= val; k += 2) if(val % k == 0) return false; return true; } int foo() { return 123; } int func() { return 321; } int main() { const int x = 198; // 'x' de bir 'constant expression' çünkü ilk değer aldığı ifade bir 'constant expression'. const int y = 3145; // 'y' de bir 'constant expression' çünkü ilk değer aldığı ifade bir 'constant expression'. constexpr int resultOne = sum_square(x, y); // Dolayısıyla fonksiyona gönderilen değişkenler birer 'constant expression'. İş bu sebepten dolayı yukarıdaki bu ifade, // COMPILE TIME açısından bir sabit. Yani değeri derleme zamanında hesaplanmıştır. // (~9milyon) constexpr int resultTwo = ndigit(sum_square(x, y)); // 'sum_square' fonksiyonu bir 'constant expression' olduğundan dolayı, 'ndigit' fonksiyonu da bir 'constant expression' // olmuştur. Yani bunun da değeri DERLEME ZAMANINDA hesaplanacak. // (7) constexpr int resultThree = factorial(ndigit(sum_square(x, y))); // 'factorial' fonksiyonunun parametresi 'constant expression' olduğundan, işbu fonksiyon da bir 'constant expression' olur. // Değeri DERLEME ZAMANINDA hesaplanır. // (5040) /* // AŞAĞIDAKİ AÇIKLAMAYI DİKKATE ALINIZ: " factorial(ndigit(sum_square(x, y))); " Yukarıdaki ifadedeki 'x' ve 'y' değişkenleri birer 'constant expression' değiller ise hesapalama ÇALIŞMA ZAMANINDA; aksi halde DERLEME ZAMANINDA hesaplanır. Dolayısıyla fonksiyonları 'constexpr' olarak bildirmek/tanımlamak işimize yarayabilir. */ constexpr bool val = isPrime(factorial(ndigit(sum_square(x, y))) + 1); // Compile Time // 'val' değişkeninin değeri DERLEME ZAMANINDA hesaplanmıştır. Çünkü bütün fonksiyonlar 'constexpr' olarak nitelendirilmiş ve geçilen // parametreler de 'Constant Expression' şeklinde. const bool valueTwo = isPrime(factorial(ndigit(sum_square(x, y))) + 1); // 'valueTwo' değişkeninin değeri DERLEME ZAMANINDA hesaplanmıştır. bool valueThree = isPrime(factorial(ndigit(sum_square(foo(), func())))); // 'valueThree' değişkeninin değeri ÇALIŞMA ZAMANINDA hesaplanmıştır. } >>> NEDEN HER BİR FONKSİYON 'constexpr function' OLAMAZ? ÇÜNKÜ, >>>> Bu tip fonksiyon olabilmeleri için almış oldukları parametreler ve geri döndürdükleri değerler birer 'literal type' olmalı. >>>> Bu tip fonksiyon olabilmeleri için bloklarında 'static' yerel değişkenler kullanılmamalı. Çünkü bu tip fonksiyonlar, DERLEME ZAMANINDA işlem yapmaktalar. >>> Normal şartlarda derleyiciler, 'constexpr' olmayan bir fonksiyonun koduna DERLEME ZAMANINDA bakmazlar. Sadece 'linker' isimli yardımcı program, '.exe' dosyasını üretebilmek için, çağrılan fonksiyonun imzası ile gövdesini eşlemek için ilgili fonksiyonun gövdesine bakması gerekmektedir. Aksi durumda kodumuz derlenir fakat çalıştırılamaz. Fakat 'constexpr' fonksiyonlar içim durum tam tersidir. Derleyicilerin, 'constexpr' fonksiyonların gövdelerine bakmaları gerekmektedir ki DERLEME ZAMANINDA ilgili fonksiyon bir çıktı üretsin. Bundan sebeptir ki 'constexpre functions' ların tanımlamarı 'header files' içerisinde olmalı. Bu da implementasyonu ifşa etmektedir. > Trailing Return Type: Jenerik programlama(fonksiyon şablonlarında) tarafında kullanılan bir yöntemdir. Modern Cpp ile gelen bir araçtır. Kullanımı aşağıdaki örnekteki gibidir. Normal fornksiyonların imzalarında kullanılması pek tavsiye edilmez. İstisnai durum olarak ikinci örneği inceleyiniz. * Örnek 1, // Some codes here... // Fonksiyonun geri dönüş değeri yerine 'auto' yazıyoruz. Sonrasında fonksiyon parametre parantezinden sonra bir adet '->' tokeni ve // fonksiyonun geri dönüş değerinin türünü yazıyoruz. auto func()->int { // codes... return 1; } * Örnek 2, // Some codes here... #include #include // 'strcmp()' gibi bir fonksiyon göstericisi döndüren fonksiyonların yazımı: int(*func1())(const char*, const char*) { return &strcmp; } // (1) İlk önce gösterici döndürecek olan fonksiyonun ismi ve varsa alacağı parametreler için parantezleri yazılır => ...func()... // (2) Tıpkı diğer 'function pointer' larda olduğu gibi isimden önce bir adet '*' tokeni konulur => ...*func()... // (3) Tıpkı diğer 'function pointer' larda olduğu gibi deyimimiz dıştan paranteze alınır => ...(*func())... // (3) Sonrasında da en dıştaki parantez çiftinin soluna, 'function pointer' tarafından gösterilen fonksiyonun geri dönüş türü // yazılır => int(*func())... // (4) Son olarak da en dıştaki parantez çiftinin sağına, 'function pointer' tarafından gösterilen fonksiyonun parametreleri // yazılır => int(*func())(const char*, const char*) // AYNI 'func()' İSİMLİ FONKSİYONU 'Trailing Return Type' İLE YAZALIM: auto func2()->int(*)(const char*, const char*) { return &strcmp; } // AYNI 'func()' İSİMLİ FONKSİYONU 'typedef' BİLDİRİMİ İLE YAZALIM: typedef int(*myFuncPtr)(const char*, const char*); myFuncPtr func3() { return &strcmp; } // Function Pointer argüman olarak alan ve geri döndüren fonksiyon: myFuncPtr func4(myFuncPtr other) { return other; } // Function Pointer argüman olarak alan ve geri döndüren fonksiyon: // A || B || C || A int ( *func5 ( int(*myFuncPtr)(const char*, const char*)) ) (const char*, const char*) { return myFuncPtr; } int ( *func6 ( ) (const char*, const char*) { return &strcmp; } /* # AÇIKLAMALAR # A : Bahsi geçen esas fonksiyonumuzun döndürdüğü bir 'function-pointer' a ait bilgiler. Bu iş bu fonksiyonumuz, geri dönüş değeri 'int' olan ve 'const char*' türünden iki adet parametresi olan bir fonksiyon adresi döndürmektedir. B : Bahsi geçen esas fonksiyonumuzun ismi. İş bu fonksiyonu çağırmak için bu ismi kullanacağız. C : Bahsi geçen esas fonksiyonumuzun parametre bilgilerinin geçildiği yer. İş bu fonksiyona geçeceğimiz 'function-pointer', 'int' türden değer döndüren ve 'const char*' türünden iki adet parametre alan bir fonksiyonun adresi olmalıdır. Dolayısıyla bizim esas fonksiyonumuza geçilen argümanın ismi de 'myFuncPtr' olacaktır. */ int main() { const char* x = "Ahmet"; const char* y = "Merve"; std::cout << "strcmp(x,y) : " << strcmp(x,y) << "\n"; std::cout << "func1(x,y) : " << func1()(x,y) << "\n"; std::cout << "func2(x,y) : " << func2()(x,y) << "\n"; std::cout << "func3(x,y) : " << func3()(x,y) << "\n"; std::cout << "func4(strcmp) : " << func4(strcmp)(x,y) << "\n"; // 'typedef' kullanılırsa std::cout << "func5(strcmp) : " << func5(strcmp)(x,y) << "\n"; // 'typedef' kullanılmazsa } * Örnek 3, #include typedef int (*myFuncPTR)(int, int); int foo(int a, int b) { return a*b; } int func(myFuncPTR fp, int x, int y) { return fp(x,y); } myFuncPTR myFoo() { return foo; } int (*myFunc())(int a, int b); int main() { /* # OUTPUT # foo(1,2) : 2 func(foo, 2, 3) : 6 myFoo() : 12 myFunc() : 20 */ std::cout << "foo(1,2) : " << foo(1,2) << "\n"; std::cout << "func(foo, 2, 3) : " << func(foo, 2, 3) << "\n"; std::cout << "myFoo() : " << myFoo()(3, 4) << "\n"; std::cout << "myFunc() : " << myFunc()(4, 5) << "\n"; } int (*myFunc())(int a, int b) { return foo; } > Default Arguments: >> Derleyici, DERLEME ZAMANINDA ilgili fonksiyonun bildirimine bakarak, ÇALIŞMA ZAMANINDA sanki o parametreleri de çağırmış gibi davranıyor. YANİ BU MEKANİZMA DERLEME ZAMANINA İLİŞKİN, ÇALIŞMA ZAMANI İLE HİÇ BİR İLİŞKİSİ YOKTUR. * Örnek 1, // some code here... int foo(int x = 1, int y = 2, int z = 3); int main() { foo(); // Derleyici tarafından DERLEME ZAMANINDA bu çağrı şuna çevrilir => foo(1,2,3); foo(5); // Derleyici tarafından DERLEME ZAMANINDA bu çağrı şuna çevrilir => foo(5,2,3); foo(10, 20); // Derleyici tarafından DERLEME ZAMANINDA bu çağrı şuna çevrilir => foo(10,20,3); foo(200, 300, 400); // Derleyici tarafından DERLEME ZAMANINDA bu çağrı şuna çevrilir => foo(200,300,400); } >> Fonksiyon bildirimlerinde/tanımlamalarında eğer 'default argument' kullanılmış ise (varsa) devamındaki parametrelerin de 'default argument' olmaları zorunlu. Aksi halde sentaks hatası. * Örnek 1, // some code here... int foo(int, double, char x = 'a'); // LEGAL int func(int, double d = 2.3, char x); // SENTAKS HATASIDIR. >> Birden fazla 'default argument' barındıran fonksiyonları çağırırken, 'default argument' alan parametreleri bir boş/bir dolu şekilde parametre geçemeyiz. * Örnek 1, // some code here... int foo(int i = 1, double d = 2.3, char x = 'a'); // LEGAL int main() { foo(, 2, ); // SENTAKS HATASIDIR. } >> 'default argument' YA FONKSİYONLARIN BİLDİRİMİNDE, Kİ TİPİK OLARAK ORALARDA, YA DA FONKSİYON TANIMLAMALARINDA KULLANILMALI. HER İKİSİNDE DE KULLANILAMAZ. >> 'default argument' olarak sadece bir 'constant expression' kullanılması zorunluluğu yoktur. 'global nesneler' de kullanılabilir. Bir sınırlama yoktur. * Örnek 1, // some codes here... int x = 10; int foo(int a, int b = x); // LEGAL int main() { foo(55); // Derleme zamanında derleyici tarafından buna dönüştürülür => foo(55, 10); } * Örnek 2, // some codes here... int foo(int = 100, int = 200); // LEGAL int func(int, int = foo()); // LEGAL int main() { func(77); // Derleme zamanında derleyici tarafından buna dönüştürülür => foo(77, foo(100, 200)); } * Örnek 3, // some codes here... int g1{}; int g2{}; int g3{13}; int foo(int& x = g1, int& y = g2, int* ptr = &g3); int main() { foo(); // Derleme zamanında derleyici tarafından buna dönüştürülür => foo(g1, g2, &g3); } >> 'default argument' olan ifadelerde daha önceki parametrelerin isimleri kullanılamaz. * Örnek 1, // some codes here... int foo(int a, int b = a); // SENTAKS HATASIDIR >> Eklemiş olduğunuz başlık dosyalarındaki fonksiyon bildirimlerinde yer alan fonksiyonların parametreleri her defasında yazmak yerine, onları 'redeclare' edebiliriz. Böylelikle derleyicimiz, bizim deklare ettiğimiz fonksiyonu çağıracaktır. * Örnek 1, // some code here... // include berker.h ile bize gelen fonksiyon void func(int, int, int); //Bizim 'redeclare' ettiğimiz fonksiyon ise void func(int, int, int = 3); int main() { func(1,2); // 6 func(1,2,0); // 0 } void func(int x, int y, int z) { std::cout << "x * y * z : " << x * y * z << "\n"; } >> Bir takım teknikler ile 'default argument' alan fonksiyonların bildirimleri kümülatif etki oluşturmaktadır. * Örnek 1, // some code here... void func(int, int, int = 56); // I void func(int x, int y = 12, int z); // II // Eğer sadece II numaralı fonksiyon bildirimi olsaydı sentaks hatası oluşacaktı. Çünkü 'default argument' alan parametreden // sonraki parametreler de 'default argument' olmak zorunda. Fakat derleyici önce I numaralı fonksiyon bildirimi gördüğü için, // ilgili fonksiyonun son parametresinin 'default argument' olduğunu öğreniyor. Devamında da II numaralı fonksiyon bildirimi // gördüğünden, ilgili fonksiyonun ikinci parametresinin de 'default argument' olduğunu öğreniyor. Sonuç olarak derleyici, // yukarıdaki bildirimleri bir nevi aşağıdaki şekle getiriyor => void func(int x, y = 12, z = 56); int main() { func(1); // func(1, 12, 56); // x * y * z : 672 } void func(int x, int y, int z) { std::cout << "x * y * z : " << x * y * z << "\n"; } >> Bir diğer kullanım alanı da 'default argument' parametrelere göre fonksiyonun gövdesinde farklı işlemler yapılması: * Örnek 1, #include #include #include void processEntryDate(int day = -1, int mon = -1, int year = -1); // Fonksiyon bildirimi. int main() { processEntryDate(); // processEntryDate(-1, -1, -1); // 14-02-2022 processEntryDate(17); // processEntryDate(17, -1, -1); // 17-02-2022 processEntryDate(6,9); // processEntryDate(6, 9, -1); // 06-09-2022 processEntryDate(10, 11, 1993); // processEntryDate(10, 11, 1993); // 10-11-1993 } void processEntryDate(int day, int mon, int year) { if( year == -1) { std::time_t timer; time(&timer); // Artık 'epoch' tan geçen süreyi biliyoruz. // struct tm* ptr = localtime(&timer); // C-style // tm* ptr = localtime(&timer); // Approach I // auto* ptr = localtime(&timer); // Approach II : 'auto' anahtar sözcüğü yerine 'struct tm' türü gelecek. auto ptr = localtime(&timer); // Approach III : 'auto' anahtar sözcüğü yerine 'struct tm*' türü gelecek. year = ptr->tm_year + 1900 ; // Yıl bilgisini çekmiş olduk. // 'year' değişkeni olarak 'default argument' kullanıldığı için, bundan önceki değişken de 'default argument' // olabilir. if( mon == -1) { mon = ptr->tm_mon + 1; // 'mon' değişkeni olarak 'default argument' kullanıldığı için, bundan önceki değişken de 'default argument' // olabilir. if(day == -1) { day = ptr->tm_mday; } } } std::cout << std::setfill('0'); // Yukarıdaki 'std::setfill' isimli manipülatörü, tek kullanımlık değildir. Aksi olanlar standart akıma geçilmediği müddetçe // geçerlidir. std::cout << std::setw(2) << day << "-" << std::setw(2) << mon << "-" << year << "\n"; // Yukarıdaki 'std::setw()' isimli manipülatör ise tek kullanımlıktır. Sadece kendisinden bir sonraki nesne için ayar çeker. // NOT : '\n' yerine 'std::endl' kullanılmamalı. Çünkü 'std::endl' ayrıca çıkış akımının buffer(ını) da flush etmekte. Eğer // bu flush işlemine gerek yokse, bu maliyete girilmemeli. } > 'Funciton Overloading / İşlev Yüklemesi' : Özünde aynı olan fonksiyonların isimlerinin de aynı olmasını sağlamaktır. Fakat gövdelerindeki kodlamalarda farklılıklar olabilir. >> Fonksiyon çağrısı ile hangi fonksiyon yüklemesinin ilişkilendirilmesi/bağlanmasına dair iki adet yöntem vardır. Bunlar 'static binding' ve 'dynamic binding'. >>> 'static binding' veya 'early binding' : Derleyicinin, DERLEME ZAMANINDA koda bakarak, işbu fonksiyon çağrısını hangi fonksiyona bağlayacağına karar vermesi durumudur. >>> 'dynamic binding' veya 'late binding' : İşbu fonksiyon çağrısı ile hangi fonksiyon yüklemesinin çağrılacağının ÇALIŞMA ZAMANINDA belirlenmesi durumudur. >> DERLEME ZAMANINDA derleyiciler koda bakarak hangi fonksiyon yüklemesinin çağrılacağına karar veriyorlar. ÇALIŞMA ZAMANINA dair bir maliyeti yoktur. >> Bu mekanizma da kendi içerisinde farklı kural setleri tanımlar. Bunlar " 'function overloading' var mı yok mu?" sorusu ve " Hangi 'function overload' çağrıldı?" sorularını cevaplar. >>> " 'function overloading' var mı yok mu?" sorusuna cevap veren kural setleri: (İş bu fonksiyonların çağrılıp çağrılmamasına bakılmaz. Sadece var mı yok mu sorusu cevaplanır) >>>> Birden fazla aynı isimli fonksiyonlar, aynı kapsamda(scope) bildirilmiş iseler 'function overloading' vardır. Yani isimlerin ait oldukları scope birbirinden farklı ise 'function overloading' yoktur. Olsa olsa 'name masking' durumu vardır. * Örnek 1, // Some code here... int foo(int); // Global namespace scope. // I int main() { int foo(int, int); // Block scope. // II // Burada 'function overloading' yoktur. 'name masking' vardır. // NOT : İSİM ARAMA, İSİM BULUNDUKTAN SONRA TAMAMLANIR VE ASLA DEVAM ETMEZ. foo(12); // SENTAKS HATASI // Yukarıdaki fonksiyon çağrısından sonra derleyici 'foo' ismini aramaya başlar ve 'II' nolu fonksiyon bildirimini // gördükten sonra aramayı bitirir. İş bu fonksiyon çağrısı ile 'II' nolu fonksiyonu bağlar. Bahsi geçen fonksiyon // iki parametre aldığından ve biz tek parametre geçtiğimiz için de SENTAKS HATASI oluşur. ::foo(15); // LEGAL // Artık derleyici, yukarıdaki fonksiyon ismini 'global namespace' alanında aramaya başlayacaktır. 'I' nolu fonksiyon // bildirimini gördükten sonra aramayı bitirir. İş bu fonksiyon çağrısı ile 'I' nolu fonksiyonu bağlar. } >>>> Bildirilmiş aynı isimli fonksiyonların imzaları da farklı olacaktır. Burada imzadan kastedilen şey fonksiyonun parametrik yapısının, geri dönüş değerinin türü göz ardı edilerek, ele alınmasıdır. Velevki imzaları da aynı olsaydı bu durum 'function redeclaretion' olacaktı ve hem C hem de C++ dillerinde bu LEGAL. Eğer imzaları aynı fakat geri döndüş değerlerinin türü farklı olsaydı bu durum SENTAKS HATASI olacaktır. * Örnek 1, // Some code here... int foo(int); // Normal bir fonksiyon bildirimi. int foo(const int); // Eğer parametre 'pointer' değilse buradaki 'const' anahtar sözcüğü 'function overload' mekanizmasını tetiklemez. // Dolayısıyla bu kod bildirimi 'function redeclaretion' olur. void foo(int); // Sentaks hatasıdır. Çünkü isimleri ve parametrik yapısı aynı olan fakat geri dönüş değerinin türü farklı olduğu // fonksiyon bildirimleri SENTAKS HATASI oluşturur. void func(int* ptr); // Normal bir fonksiyon bildirimi. void func(int* const ptr); // Burada 'ptr' nin kendisi 'const' olduğundan dolayı burada 'function overloading' söz konusu değildir. // 'function redeclaretion' vardır. void func(const int* ptr); // BURADA ARTIK 'function overloading' VARDIR. // NOT : FONKSİYONLARI BİRDEN FAZLA KEZ BİLDİREBİLİRİZ FAKAT SADECE BİR DEFA TANIMLAYABİLİRİZ. void myFunc(int&); // Normal bir fonksiyon bildirimi. void myFunc(const int& ); // BURADA ARTIK 'function overloading' VARDIR. void myOtherFunc(int x, int y = 10); // Normal bir fonksiyon bildirimi. void myOtherFunc(int x); // BURADA ARTIK 'function overloading' VARDIR. // Buradan da anlayabiliriz ki 'default argument' olup olmaması imzayı değiştirmiyor. İlk fonksiyon çağrısı aslında // iki parametre alan, ikinci çağrı ise tek parametre alan bir fonksiyon. Bundan dolayı 'function overloading' mevcut. void theFunc(int x, int y = 10); // Normal bir fonksiyon bildirimi. void theFunc(int x, int y); // Burada artık 'function redeclaretion' söz konusudur. // Tür eş ismi kullanılması, 'function overloading' mekanizmasını tetiklemez. typedef int MyInt; // using MyInt = int; void theFuncTwo(int); // Normal bir fonksiyon bildirimi. void theFuncTwo(MyInt); // Burada artık 'function redeclaretion' söz konusudur. // Gerek C dilinde gerek C++ dilinde üç ayrı 'char' türü olduğundan, aşağıdaki kullanım 'function overloading' // mekanizmasını tetikler. void theFuncThree(char); // Normal bir fonksiyon bildirimi. void theFuncThree(signed char); // BURADA ARTIK 'function overloading' VARDIR. void theFuncThree(unsigned char); // BURADA ARTIK 'function overloading' VARDIR. // Her ne kadar derleyiciler 'enum' türleri arka planda 'int' türler olarak ele alsa da aslında bu iki tür birbirinden // farklıdır. Yani 'function overloading' mekanizmasını tetiklerler. Farklı 'enum' türleri de birbirinden farklıdır. enum Color{ White, Gray }; void theFuncFour(int); // Normal bir fonksiyon bildirimi. void theFuncFour(Color); // BURADA ARTIK 'function overloading' VARDIR. // AŞAĞIDAKİ KULLANIMI DİKKATLİ İNCELEYİNİZ #include void theFuncFive(int32_t); void theFuncFive(int); // Eğer 'int32_t' tür eş ismine karşılık gelen tür 'int' ise yukarıdaki senaryo 'function redeclaretion', başka bir türe // karşılık ise, örneğin 'long', 'function overloading' vardır. Bu durumda, yukarıdaki kullanım DERLEYİCİYE BAĞLIDIR. >>> " Hangi 'function overload' çağrıldı?" sorusuna, ki aynı zamanda bu soru 'function overload resolution' ile ilgilidir, cevap veren kural setleri: >>>> 'function overloading' söz konusudur fakat ilgili fonksiyon çağrısı ile işbu fonksiyonların yüklemeleri birbirlerine bağlanamayabilir. Yani 5 adet fonksiyon yüklemesi vardır fakat çeşitli nedenlerden dolayı çağıran kod bunların hiç birine bağlanamaz. Bu durumda da iki farklı SENTAKS HATASI oluşur. Bunlardan bir tanesine 'no-match' tipi, diğerine 'ambiguity' denir. >>>>> 'no-match' şeklindeki sentaks hatalarında, fonksiyon çağrısında kullanılan parametreler ile fonksiyon bildirimlerindeki parametrelerin eşleşmemesi durumudur. >>>>> 'ambiguity' şeklindeki sentaks hatalarında ise derleyici 'function overload resolution' işlemi sonucunda iki farklı fonksiyon yüklemesi bulur. Hangisini seçeceğini bilemez. * Örnek 1, // Some codes here... void func(long double); void func(char); int main() { float f = 12.4f; func(f); // Bu fonksiyon çağrısı 'ambiguity' tip sentaks hatasına neden olur. AÇIKLAMASI DERS SONUNDA. } >>>> 'function overload resolution' üç aşamalı bir süreçtir. >>>>> İlk aşamada derleyici 'candidate functions' ların ismini listeliyor. Bu listelemeyi yaparken de fonksiyonların parametrelerine ve fonksiyon çağrısında kullanılan parametrelere bakmıyor. Sadece yükleme yapılan fonksiyonların isimlerini listeliyor. (NOT: İlgili işbu fonksiyonların isimleri ve bulundukları isim alanları da aynıdır. Aksi halde 'function overloading' söz konusu olmayacaktır.) * Örnek 1, // Some codes here... struct A {}; void func(int); void func(int, int); void func(int* ); void func(double); void func(char); void func(A); int main() { func(12); // Yukarıdaki fonksiyon bildirimlerindeki isimler aynı olduğundan ve aynı isim alanında bildirildikleri için, // bunların hepsi birer 'candidate function'. } >>>>> İkinci aşamada derleyici 'viable functions'ları, 'candidate functions'lara bakarak seçiyor. Seçim yaparken de her bir yüklemeyi tek tek inceliyor. İncelerken de sanki sadece o fonksiyon varmış gibi davranıyor. Eğer fonksiyon çağrısında kullanılan parametre adedi ile 'candidate functions'lardan seçilen tekil fonksiyonun parametre adedi aynı ve her iki fonksiyonun parametre türleri arasında da uyumlu bir dönüşüm var ise, o 'candidate functions' artık bir 'viable functions' haline geliyor. (NOT: Aslında ikinci aşamada şu soruya cevap aranıyor; "Eğer bu fonksiyon tek başına olsaydı, fonksiyon çağrısı legal olur muydu?". Cevap evet ise o fonksiyon 'viable function'.) * Örnek 1, // Some codes here... struct A {}; void func(int); // Sadece bu fonksiyonu ele alalım ve aşağıdaki dört fonksiyon bildirimlerini de yok sayalım. 'main()' fonksiyon bloğundaki // 'func(12)' çağrısı ile bu fonksiyonu biz çağırabiliriz. Dolayısıyla bu fonksiyon artık bir 'viable function'. void func(int, int); // Sadece bu fonksiyonu ele alalım; 'func(12)' çağrısı ile bu fonksiyonu çağıramayız çünkü parametre adetleri farklı. // Dolayısıyla bu fonksiyon bir 'viable function' DEĞİL. void func(int* ); // Sadece bu fonksiyonu ele alalım; 'func(12)' çağrısı ile bu fonksiyonu da çağıramayız çünkü parametreler arasında uygun bir // 'implicit conversion' yok. Yani 'int' türünden 'int*' türüne otomatik dönüşüm yok. Dolayısıyla bu fonksiyon bir // 'viable function' DEĞİL. void func(double); // Sadece bu fonksiyonu ele alalım; 'func(12)' çağrısı ile bu fonksiyonu ÇAĞIRABİLİRİZ. Çünkü parametleri arasında uygun bir // dönüşüm vardır. Yani 'int' türünden 'double' türüne otomatik dönüşüm VAR. Dolayısıyla bu fonksiyon bir 'viable function'. // BURADA ÖNEMLİ OLAN VERİ KAYBI DEĞİL, İŞİN LEGALİTESİ. void func(char); // Sadece bu fonksiyonu ele alalım; 'func(12)' çağrısı ile bu fonksiyonu ÇAĞIRABİLİRİZ. Çünkü parametleri arasında uygun bir // dönüşüm vardır. Yani 'int' türünden 'char' türüne otomatik dönüşüm VAR. Dolayısıyla bu fonksiyon bir 'viable function'. // BURADA ÖNEMLİ OLAN VERİ KAYBI DEĞİL, İŞİN LEGALİTESİ. void func(A); // Sadece bu fonksiyonu ele alalım; 'func(12)' çağrısı ile bu fonksiyonu da çağıramayız çünkü parametreler arasında uygun bir // 'implicit conversion' yok. Yani 'int' türünden 'A' / 'struct A' türüne otomatik dönüşüm yok. Dolayısıyla bu fonksiyon bir // 'viable function' DEĞİL. int main() { func(12); } // ARTIK BU AŞAMADA ELİMİZDE HİÇ 'viable function' YOKSA, 'no-match' TİPİNDE BİR SENTAKS HATASI ALIRIZ. // VELEVKİ SADECE BİR TANE 'viable function' OLSAYDI, O SEÇİLECEKTİ VE 'function over resolution' TAMAMLANACAKTI. // EĞER İKİ YADA DAHA FAZLA 'viable function' VARSA ELİMİZDE, ÜÇÜNCÜ AŞAMAYA GEÇİYORUZ. >>>>> Üçüncü aşamada 'viable functions' lar arasından bir seçim yapılırsa, o seçim bir 'best-match' olacak ve çözümleme tamamlanacak. Eğer bir seçim yapılamaz ise 'ambiguity' tip SENTAKS HATASI alacağız. Yani bu aşamada fonksiyon çağrısında kullanılan parametrelerin adedi ile fonksiyon imzalarındaki parametrelerin adetleri aynı ve türleri arasında da uyumlu bir dönüşüm söz konusu. Artık derleyici dilin kurallarına dayanarak, parametre türlerini arasındaki dönüşümü öncelik sırasına göre gruplayacak. Bu dönüşümler ise önem sırasına göre şu şekildedir: "variadic conversion < user-defined conversion < standart conversion". Hadi bu grupları da açıklayalım: >>>>>> 'variadic conversion' : Bildirimi yapılan fonksiyonlar 'variadic' parametreye sahip ise, ve daha uygun fonksyion yok ise fonksiyon çağrısı ile o 'variadic' fonksiyon birbirine bağlanır. * Örnek 1, // Some code here... void func(int, ...); // 'variadic function'. İlk parametreye değer geçmek zorundayız fakat devamında ise bir tür/adet sınırı yoktur. int main() { func(12); // Yukarıdaki 'variadic function' çağrılır. func(12, 3.4); // Yukarıdaki 'variadic function' çağrılır. func(12, 3.4, 5); // Yukarıdaki 'variadic function' çağrılır. } // BÖYLE BİR DÖNÜŞÜM TÜRÜNÜN ÖNCELİĞİ EN DÜŞÜK OLANDIR. >>>>>> 'user-defined conversion' : Normalde uygun bir 'implicit conversion' yok. Fakat bizim bildireceğimiz bir fonksiyondan dolayı derleyici 'implicit conversion' gerçekleştiriyor. * Örnek 1, struct A{ // (III) : Eğer aşağıdaki gibi bir fonksiyon bildirirsek, derleyici 'implicit conversion' gerçekleştirecektir. A(int); // (IV) : İşte 'implicit conversion' gerçekleşmesini sağlayan fonksiyonun bildirimi. Bu bildirime dayanarak // derleyici otomatik dönüşümü gerçekleştirir. }; void func(A); int main() { func(12); // (I) : Normal şartlarda bu fonksiyon çağrısı herhangi bir fonksiyona bağlanamaz çünkü uygun bir 'implicit conversion' // yok. // (II) : Yani 'int' türünden 'A' türüne otomatik dönüşüm yapılamıyor. } // BÖYLE BİR DÖNÜŞÜM TÜRÜNÜN ÖNCELİĞİ 'variadic conversion' TÜRÜNDEN DAHA YÜKSEKTİR. >>>>>> 'standart conversion' : Öz ve öz böyle bir dönüşüm vardır. Aklımıza gelen dönüşümler eğer 'variadic conversion' değilse ya da 'user-defined conversion' değilse 'standart conversion' dur. * Örnek 1, // some codes here... "int <==> double" // 'int' türünden 'double' türüne ya da tam tersi. "int* ==> void*" // 'int*' türünden 'void*' türüne. "enum ==> int" // 'enum' türünden 'int' türüne. * Örnek 2, struct A{ A(int); }; void func(A); // (I) void func(double); // (II) int main() { func(12); // Yukarıdaki fonksiyon çağrısı sonrasında, 'function overload resolution' ile (I) ve (II) numaralı fonksiyonların her // ikisi de 'viable functions'. Fakat (II) numaralı fonksiyon sırasında 'standart conversion', (I) numaralı fonksiyon // çağrısında da 'user-defined conversion' kullanıldığından, derleyici (II) numaralı fonksiyonu bağlayacaktır. } // BÖYLE BİR DÖNÜŞÜM TÜRÜNÜN ÖNCELİĞİ EN YÜKSEKTİR. EĞER BİRDEN FAZLA 'standart conversion' VARSA, BU DURUMDA DERLEYİCİ BU 'standart conversion' LARI DA KENDİ İÇİNDE GRUPLARA AYIRIR. ÖNEM SIRASINA GÖRE BU TÜRLER; "exact-match > promotion > normal-conversion" şeklindedir. HADİ BU SEFER DE BUNLARI AÇIKLAYALIM: >>>>>>> 'exact-match' : Aşağıdaki durumlar 'exact-match' olarak sayılır. >>>>>>>> Fonksiyon çağrısındaki parametrenin türü ile seçilen 'viable function' un türü birbirinin aynısı olma durumu; >>>>>>>> 'array-decay' mekanizması. >>>>>>>> 'const conversion' mekanizması. >>>>>>>> 'function-to-pointer' mekanizması. >>>>>>>> 'L-Value to R-Value conversion' mekanizması. * Örnek 1, // Some code here... void func(int* ); void foo(const int*); int thefunc(int); void thefoo(int(*)(int)); // 'thefoo()' fonksiyonunun parametresi, geri dönüş değeri 'int' türden olan ve aldığı tek parametrenin de türünün // 'int' olduğu bir fonksiyonun adresi. void thefuncTwo(int); int main() { int a[] = {2, 5, 9}; // Array-decay senaryosu: // 'a' dizisinin türü 'int[3]' şeklindedir. func(a); // Dizi ismi bir ifade içinde kullanıldığında, dizinin ilk elemanının adresine dönüşür. => int[3] => int* // 'func(&a[0]);' şeklindeki çağrıdan bir farkı yoktur. // Parametrenin de türü 'int*' olmuştur. int x = 100; foo(&x); // 'const-conversion' senaryosu: // Burada 'x' nesnesinin türü 'int'. Parantez içerisindeki ifadenin türü de 'int*'. // İlgili 'foo()' fonksiyonunun aldığı argümanın türü ise 'const int*'. // Bu durumda 'int*' türünden 'const int*' türüne dönüşüm sağlanıyor. thefunc(); // Normalde bu fonksiyonun türü => "int()(int)" şeklinde. &thefunc(); // 'address-of' operatörünün operandı olduğunda ise türü => "int(*)(int)" şekline geliyor. // 'function-to-pointer' senaryosu: // Bir fonksiyonun ismi bir ifade içerisinde kullanılırsa eğer, 'function-to-pointer' mekanizması devreye girer. // Yani fonksiyonun ismini, fonksiyonun adresine dönüştürüyor. Aslında '&func()' şeklinde kullanmışız gibi oluyor. thefoo(thefunc); // Aslında => "thefoo(&thefunc);" // L-Value to R-Value conversion: int y = 100; // 'y' is a L-Value. thefuncTwo(y); // 'y' değişkeninin değerini kullanmak için derleyici, 'L-value' olan değişkeni 'R-value' haline getiriyor. } >>>>>>> # ÖZETLE AŞAĞIDAKİ DURUMLAR 'exact-match' KABUL EDİLİR # >>>>>>>> Argüman ifadenin türü ile parametrenin türünün birebir aynı olması, >>>>>>>> 'array-decay' dönüşümü, >>>>>>>> 'const-conversion' dönüşümü, >>>>>>>> 'function-to-pointer' dönüşümü, >>>>>>>> 'L-Value to R-Value' dönüşümü. >>>>>>> 'promotion' : İki ayrı kategoride ele alınır. Bir tanesi C dilinden gelen 'integral promotion' ve 'float-to-double promotion'. >>>>>>>> 'integral promotion' : Derecesi(rank) 'int' türünden de aşağıda olan türlerin, işleme sokulmadan evvel 'int' türüne dönüştürülmesi olayıdır. Bu alt türler 'bool', 'signed/unsigned short', 'char', 'signed char' ve 'unsigned char' türleridir. >>>>>>>> 'float-to-double promotion' : 'float' türünün işleme sokulmadan evvel 'double' türüne dönüştürülmesi işlemidir. >>>>>>> 'normal-conversion' : 'exact-match' ve 'promotion' olmayan bütün dönüşümler 'normal-conversion' şeklindedir. * Örnek 1, // some codes here... void func(double x); // (I) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'promotion', void func(int x); // (II) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion', void func(unsigned int x); // (III) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion'. int main() { func(12.5f); // (I) numaralı fonksiyon çağrılır. } * Örnek 2, // some codes here... void func(int x); // (I) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'promotion', void func(double x); // (II) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion', void func(long x); // (III) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion'. int main() { func('A'); // (I) numaralı fonksiyon çağrılır. } * Örnek 3, // some codes here... void func(unsigned int x); // (I) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion', void func(long double x); // (II) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion', int main() { func(12); // Her ikisi de 'normal-conversion' olduğundan 'ambiguity' oluşur ve SENTAKS hatasıdır. } * Örnek 4, // Some codes here... void func(long double); // (I) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion', void func(char); // (II) : 'func()' çağrısına bu fonksiyonun bağlanma durumuna 'normal-conversion', int main() { float f = 12.4f; func(f); // Her ikisi de 'normal-conversion' olduğundan 'ambiguity' oluşur ve SENTAKS hatasıdır. } /*============================================================================================================*/ (05_26_09_2020) > 'function overloading' ÖZETLEMESİ, >> 'FO' var mı yok mu sorusuna cevap aranır. Bu durumda FONKSİYON İSİMLERİ VE BİLDİRİLDİKLERİ İSİM ALANLARI AYNI OLACAK FAKAT PARAMETRİK YAPISI FARKLI OLACAK. Bu üç şartı da sağlıyor ise 'FO' VARDIR. Aksi halde 'FO' yoktur. >>> Eğer 'FO' var ise fonksiyon çağrısının hangi fonksiyon yüklemesine bağlanacağı aşağıdaki kural setleri ile belirlenir. >>>> İlk önce 'FO'ya dahil edilen fonksiyon isimleri, parametrik yapılarına ve geri dönüş değerlerine bakılmaksızın, alt alta listelenir. >>>> Daha sonra bu fonksiyon çağrısında kullanılan parametrelerin türleri ve adetleri, listelenmiş olan fonksiyonlar ile tek tek karşılaştırılır. Bir diğer değişle "Eğer bu fonksiyon tek başına olsaydı, fonksiyon çağrısı legal olur muydu?" sorusuna cevap aranır. Eğer cevap EVET ise bir sonraki aşamaya geçilir. >>>>> Artık bu aşamada fonksiyon çağrısında kullanılan parametreler ile fonksiyon imzasında kullanılan argümanlar arasındaki 'uyumlu dönüşüm' irdelenecektir. Bu aşama, kendi arasıda ÜÇ FARKLI kural seti tanımlar; artık derleyici dilin kurallarına dayanarak, parametre türlerini arasındaki dönüşümü öncelik sırasına göre gruplayacak. Bu dönüşümler ise önem sırasına göre şu şekildedir: "variadic conversion < user-defined conversion < standart conversion". >>>>>> 'variadic conversion' : Fonksiyon imzasında 'variadic parametre' olmasından dolayı, parametreler ile argümanlar arasındaki ilişkiye hitap eder. Eğer birden fazla bu dönüşüm var ise 'ambiguity' sentaks hatası alırız. >>>>>> 'user-defined conversion' : Normalde dilin kuralları gereği bahsi geçen bir dönüşüm sentaks hatası. Fakat bizim yazacağımız üçüncü bir fonksiyondan dolayı, kod legal hale geliyor. Eğer birden fazla bu dönüşüm var ise 'ambiguity' sentaks hatası alırız. >>>>>> 'standart conversion' : Yukarıdaki iki dönüşüm haricindeki diğer dönüşümler ise bu dönüşüm kategorisindedir. Eğer birden fazla bu dönüşüm var ise aşağıdaki kurallara göre bir seçim yapılır: "exact-match > promotion > normal-conversion". >>>>>>> 'exact-match' : Aşağıdaki şartlar bu dönüşümü ifade eder: >>>>>>>> Argüman ifadenin türü ile parametrenin türünün birebir aynı olması, >>>>>>>> 'array-decay' dönüşümü, >>>>>>>> 'const-conversion' dönüşümü, >>>>>>>> 'function-to-pointer' dönüşümü, >>>>>>>> 'L-Value to R-Value' dönüşümü. >>>>>>> 'promotion' : Kendi içinde iki farklı 'promotion' barındırır: >>>>>>>> 'integral promotion' : Derecesi(rank) 'int' türünden de aşağıda olan türlerin, işleme sokulmadan evvel 'int' türüne dönüştürülmesi olayıdır. Bu alt türler 'bool', 'signed/unsigned short', 'char', 'signed char' ve 'unsigned char' türleridir. >>>>>>>> 'float-to-double promotion' : 'float' türünün işleme sokulmadan evvel 'double' türüne dönüştürülmesi işlemidir. >>>>>>> 'normal-conversion' : 'exact-match' ve 'promotion' olmayan bütün dönüşümler 'normal-conversion' şeklindedir. >> 'Default argument' kullanılması her ne kadar 'FO' yu tetiklese de zaman zaman 'ambiguity' tip SENTAKS hatasına neden olabilir. * Örnek 1, // some codes here... void func(int x, int y = 100); // I void func(int x); // II int main() { func(12); // Sentaks hatası. } >> Parametrelerin 'call-by-value' veya 'call-by-reference' olması birbirine üstünlük taslamamaktadır. * Örnek 1, // some codes here... void func(int& x); // I void func(int x); // II void foo(const int& x); // III void foo(int x); // IV int main() { int a = 10; func(a); // 'ambiguity' tip sentaks hatası. func(25); // II numaralı fonksiyon çağrılır. foo(50); // 'ambiguity' tip sentaks hatası. } >> Parametrelerin 'L-Value Referance' veya 'R-Value Referance' olmaları durumunda çağrım: * Örnek 1, // some codes here... void func(const int& x); // I void func(int&& x); // II int main() { int a = 10; func(a); // I numaralı çağrılacak. (Copy-Semantics) func(50); // II numaralı çağrılacak. (Move-Semantics) C++ 11 ile dile geldi. } * Örnek 2, // some codes here... void func(int& x); // I void func(const int& x); // II void func(int&& x); // III int main() { int x = 10; const int y = 20; func(x); // I func(y); // II func(50); // III } >> Parametrelerin 'void*' ve 'bool' olma durumu: * Örnek 1, // some codes here... void func(void* x); // I void func(bool x); // II int main() { int* ptr = nullptr; func(ptr); // Yukarıdaki her iki fonksiyon da ayrı ayrı olsaydı sentaks hatası olmayacaktı. Çünkü C++ dilinde 'int*' türünden 'void*' // türüne ve 'int*' türünden 'bool' türüne otomatik tür dönüşümü vardır. // 'nullptr' değerindeki göstericiler, 'bool' türüne dönüşürken 'false' değerini alıyor. Diğer durumlarda 'true' değerini alıyor. // FAKAT DİLİN KURALLARI GEREĞİ, 'void*' TÜRÜNE DÖNÜŞÜMÜN SEÇİLEBİLİRLİĞİ DAHA YÜKSEK OLDUĞUNDAN I NUMARALI FONKSİYON ÇAĞRILIR. } >> Birden çok parametresi olan fonksiyonların yüklemelerinin seçilmesinde aşağıdaki kural seti aranır: >>> Bir parametre diğerlerine üstünlük sağlayacak; diğer parametreler ise dönüşüm üstünlüğü bakımından, diğerlerinden kötü olmayacak. * Örnek 1, // some codes here... void func(int, int, double); // I void func(long, float, int); // II void func(double, double, double); // III int main() { func(12, 56L, 3.4); // i. '12' değerinin türü 'int'. I numaralı bildirim ile 'exact-match'. Dolayısıyla fonksiyon çağrısındaki diğer parametreler, // yukarıdaki bildirimlerde kullanılan parametrelerden daha kötü olmayacak. // ii. '56L' değerinin türü 'long'. Her üç fonksiyonda da 'long' türünden sırasıyla 'int', 'float' ve 'double' türlerine dönüşüm // 'normal-conversion'. Dolayısıyla bu parametre, diğerlerinden kötü değil. // iii. '3.4' değerinin türü 'double'. I ve III 'exact-match' fakat I numaralı fonksiyonun parametreleri daha uygun olduğundan, // GÜNÜN SONUNDA I numaralı seçilir. } * Örnek 2, // some codes here... void func(int, int, float); // I void func(long, float, int); // II void func(double, double, double); // III int main() { func(12, 56L, 3.4); // i. '12' değerinin türü 'int'. I numaralı bildirim ile 'exact-match'. Dolayısıyla fonksiyon çağrısındaki diğer parametreler, // yukarıdaki bildirimlerde kullanılan parametrelerden daha kötü olmayacak. // ii. '56L' değerinin türü 'long'. Her üç fonksiyonda da 'long' türünden sırasıyla 'int', 'float' ve 'double' türlerine dönüşüm // 'normal-conversion'. Dolayısıyla bu parametre, diğerlerinden kötü değil. // iii. '3.4' değerinin türü 'double'. I numaralı 'normal-conversion' fakat III numaralı 'exact-match'. Dolayısıyla üçüncü // parametre için III numaralı daha iyi. // GÜNÜN SONUNDA 'ambiguity' tip SENTAKS hatası alırız. } > 'ternery operator' ün ikinci ve üçüncü operatörleri 'L-Value Expression' ise, sonuç 'L-Value Expression'; en az birisi 'R-Value Expression' ise sonuç 'R-Value Expression'. > ' extern "C" ' decleration : C dilinde derlenmiş kodların, C++ derleyicisinden çağrılmasına olanak veren mekanizma. Kullanımı ise şu şekildedir; >> C dilinde bildirilen fonksiyonun geri dönüş değerini yazmadan ' extern "C" ' anahtar sözcüğünü yazıyoruz. Böylelikle C dilinde derlenmiş kodları, C++ dosyalarında kullabiliyoruz. * Örnek 1, // Ahmo.h int square(int); // Ahmo.c int square(int x) { return x*x; } // main.cpp #include "Ahmo.h" int main() { int x = 4; std::cout << " x : " << square(4) << "\n"; // Yukarıdaki kod derlenecektir fakat 'link' aşamasında hata alacağız. Bu 'bağlama' hatasını gidermek için de 'Ahmo.h' // dosyası içerisinde bildirilen 'square()' fonksiyonunu şu şekilde bildireceğiz => " extern "C" square(int); " // Artık kodumuz başarıyla çalışabilir. Fakat bu durumda, 'Ahmo.c' dosyasına 'Ahmo.h' başlık dosyasını eklediğimizde // sentaks hatasını alacağız. Çünkü C dilinde ' extern "C" ' şeklinde bir anahtar sözcük yok. Ya ayrı başlık dosyaları // olacak, ya bütün fonksiyonları " extern "C" int square(int); " diye bildireceğiz ya da aşağıdaki gibi bir kullanım // yapacağız; /* // Neco.h #ifdef __cplusplus extern "C" { #endif int square(int); #ifdef __cplusplus } #endif // CPP DERLEYİCİSİNİN GÖRDÜĞÜ: extern "C" { int square(int); } // C DERLEYİCİSİNİN GÖRDÜĞÜ: int square(int); // SEBEBİ: '__cplusplus' PREDEFINED MACRO IS ONLY FOR C++ COMPILER. __STDC__ IS COUNTERPART FOR C COMPILER. */ } > C++ Dilindeki Tür Dönüştürme Operatörleri : C dilindeki tür dönüştürme operatörüne ek olarak (C-style casting), C++ dilinde dört adet daha tür dönüştürme operatörü vardır. Bunlar, >> 'static_cast' : * Örnek 1, // Some code here... int main() { int x{823}, y{23}; double z = static_cast(x) / y; } >> 'const_cast' : 'const T*' türünden 'T*' türüne veya 'const int&' türünden 'int&' türüne yapılan dönüşümler için kullanılır. * Örnek 1, // Some code here... int main() { int x{823}; const int* cptr = &x; int* iptr = const_cast(cptr); } >> 'reinterpret_cast' : Farklı türden adreslerin birbirlerine dönüşümlerinde kullanılır. * Örnek 1, // Some code here... int main() { unsigned int x{823}; char* p = reinterpret_cast(&x); } >> 'dynamic_cast' : 'inheritence' ağacında kullanılır. Çalışma zamanı çok biçimliliği, Run Time Polymorphism. >> 'void*' türünden 'int*' türüne dönüşüm yaparken 'static_cast' veya 'reinterpret_cast' kullanabiliriz. > C++ dilinde Numaralandırma Türleri(enum) ve Kapsamlandırılmış Numaralandırma Türleri(enum class): >> Gerek C dilinde, gerek C++ dilinde 'enum' türleri hem 'user-defined types' hem de 'integer types' olarak geçmektedir. >> C dilinde, 'enum' larda kullanılan 'underlying type', 'int' olmak zorunda. Yani kabaca, 'sizeof(int) == sizeof(enum)' diyebiliriz. Fakat C++ dilinde bu durum farklı. 'enum' türünün elemanlarına atanan değere bakarak derleyici, 'underlying type' ı seçiyor. Eğer elemanlara atanan değerlerden çıkarım yapılan türler arasında farklılık var ise 'underlying type' seçimi daha kapsamlı türe göre yapılıyor. Bunun açtığı sorunlardan birisi de 'forward declaration' sırasında. Derleyici, 'enum' türünün tanımını görmediğinden ve eğer bu türden bir nesnenin bildirildiğini görürse, SENTAKS HATASI verecektir. >> Diğer 'aritmetik' türlerden 'enum' türlere dönüşüm SENTAKS HATASI fakat 'enum' türlerden 'aritmetik' türlere, 'underlying type' a bakılmaksızın, otomatik dönüşüm vardır. * Örnek 1, // Some code here... enum Color{ White, Gray }; int main() { Color mycolor = Gray; mycolor = 5; // Bu çağrı bir sentaks hatasıdır. Eşitliğin sağ tarafında ya bir numaralandırma sabiti ('Gray') ya da 'Color' türünden // bir başka nesne gelmeli. long ival = mycolor; // Bu çağrı ise LEGAL bir çağrıdır. } >> 'enum' içerisindeki numaralandırma sabitlerinin ('enumaration constant') ayrı bir kapsamları yoktur. Eğer 'global namespace' de bildirilen bir 'enum' içerisindeki sabitler, kaynak dosyanın her tarafında görülür durumdadırlar. Eğer farklı başlık dosyalarından gelen 'enum' türlerin numaralandırma sabitlerinin ismi aynı ise bu durum SENTAKS hatasına yol açacak. * Örnek 1, #include "screen.h" #include "traffic.h" enum ScreenColor { White, Green, Red }; enum TrafficLight { Red, Yellow, Green }; // Bu noktada, yukarıdaki numaralandırma sabitleri görülür durumdadırlar. Fakat 'Green' iki farklı 'enum' içerisinde olmasına // rağmen, yani birbirinden farklı kaynaklara hitap etmesine rağmen derleyici açısından 'redefinition' durumu olacak. Bu durum // da SENTAKS HATASINA yol açacak. >> C++11 ile yukarıdaki üç soruna çözüm bulmak için 'enum class' türler eklendi. Fakat 'class' isminin geçmesi, bu türü bir 'class-type' yapmaz. Sadece farklı bir 'enum' türü. Artık bu tür ile 'underlying type' ı biz seçebiliyoruz. Bundan dolayıdır ki 'forward declaration' yaptıktan sonra 'underlying type' ı da belirtebileceğimiz için, bu türden bir nesnenin bildirimini yapabiliriz. Ayrıca 'enum class' türünden 'aritmetik' türlere otomatik dönüşüm artık SENTAKS HATASIDIR. 'static_cast<>' operatörü ile harici olarak dönüşüm yaptırabiliriz. Son olarak'enum class' içerisinde bulunan numaralandırma sabitlerinin kendi türleri vardır. Artık bu sabitleri kullanırken, ait olduğu 'enum class' türünü nitelememiz gerekiyor. * Örnek 1, // Some code here... // 'underlying type' is 'unsigned int'. enum class Color : unsigned int { White, Gray }; int main() { Color mycolor = Color::Gray; // 'Gray' numaralandırma sabitinin 'Color' enum türüne ait olduğunu nitelememiz gerekiyor. long ival = mycolor; // Artık SENTAKS HATASI. } > Sınıflar : Artık sınıfların da katkısı ile kapsamlar(scopes) daha da çeşitlendi. >> C dilindeki 'scope' kuralları şunlardan ibarettir : 'file scope', 'block scope', 'function scope' ve 'function prototype scope'. >> C++ dilindeki 'scope' kuralları ise şu şekildedir : 'namespace scope', 'class scope', 'block scope', 'function scope' ve 'function prototype scope'. >>> Bir ismin 'class scope' da aranmasını sağlayan üç ayrı durum vardır. Bunlar, >>>> '.' (notka) operatörünün sağ operandı olarak kullanılması durumunda. >>>> '->' (ok) operatörünün sağ operandı olarak kullanılması durumunda. >>>> '::' (scope resolution) operatörünün sağ operandı olarak kullanılması durumunda. >> Bir sınıf bloğu aşağıdaki şeylerden oluşur: >>> 'data members' : Sınıf bloğu içerisinde bildirilen/tanımlanan değişkenlerdir. Bunlar da kendi içinde iki gruba ayrılır: >>>> 'non-static data members' : >>>> 'static data members' : * Örnek 1, // Some code here... class Data{ int mx; // non-static data member. static int my; // static data member. }; >>> 'member functions' : Sınıf bloğu içerisinde bildirilen/tanımlanan fonksiyonlardır. Bunlar da kendi içinde iki gruba ayrılır: >>>> 'non-static member functions' : >>>> 'static member functions' : * Örnek 1, // Some code here... class Data{ void func(int); // non-static member function. static void func(int); // static member function. }; >>> 'type members' : Sınıf bloğu içerisinde kullanılan 'typedef' tür eş isimleri, başka sınıf bildirimleri/tanımlamaları vs. bu gruptadır. >> 'access specifiers' lardan 'private' ve 'protected' olarak betimlenen 'class member' ların sınıf dışından erişilmeye çalışılması bir sentaks hatası oluşturur. Oluşturulan bu sentaks hatasının 'NAME LOOK-UP' ile hiç bir alakası yoktur. Sadece ve sadece 'access control' bazlı bir sentaks hatasıdır. Çünkü onlar ERİŞİLEMEZ. >> 'incomplete type' olarak kullanım şekli şunlardır: Fonksiyonların bildirimlerinde geri dönüş değeri/alacağı parametre isimlerinde, tür eş ismi yazarken, 'pointer' tanımlarken vs. * Örnek 1, // some code here... class Data; Data& foo(); typedef Data* DataPtr; int main() { Data&r = foo(); } >> C dilinde fonksiyonlara işlem yaptırtabilmek için nesnenin adresini geçmek gerekiyor. Fakat C++ dilinde 'member function' lar için böyle bir durum söz konusu değildir. İlgili fonksiyonun ilk parametresi olarak, o nesnenin adresi otomatik olarak geçilir (gizli parametre gibi). * Örnek 1, // In C, struct Fighter{ int power; }; void kill(struct Fighter* myFighter, struct Fighter* enemyFighter); // In C++ class Fighter{ void kill(Fighter& enemyFighter); int power; }; int main() { struct Fighter my_fighter, enemy_fighter; kill(&my_fighter, &enemyFighter); // In C, // Sezgisel olarak incelendiğinde, yukarıdaki fonksiyon çağrısının kimin için çağrıldığını anlamak mümkün değil. // Ayrıca, problem domaininden de baktığımız zaman, bizler değişkenlerin adresleri ile de uğraşmamız gerekiyor. Fighter my_fighter, enemy_fighter; myFighter.kill(enemy_fighter); // In C++, // Sezgisel olarak bakıldığında, 'my_fighter' nesnesine ait olan 'kill()' fonksiyonunun çağrıldığını görebiliriz. // Ek olarak arka planda fonksiyon çağrısının ilk parametresi olarak da 'my_fighter' nesnenin adresi geçilmekte. // MAKİNE KODU BAZINDA YUKARIDAKİ HER İKİ DURUM DA ASLINDA AYNI ŞEY. SADECE DİL KATMANINDA KULLANICI KOLAYLIĞI İÇİN // BÖYLE BİR KOLAYLIK GETİRİLMİŞ. } >> C++ dilinde 'member function' lar da 'Function Overloading' e tabii olur. 'access specifiers' lar bu durumu değiştirmezler. Onlar sadece 'erişim kontrolü' için varlar. >>> NOT : 'FO' aynı skopta bildirilen/tanımlanan, aynı isimli fonksiyonların, farklı imzalara sahip olması sonucunda mevcuttur. Daha sonrasında da fonksiyonların bağlanması aşaması gelir ki buna da 'Function Overload Resolution' denmektedir. * Örnek 1, // Some code here... class Myclass{ public: void func(int); // 1 void func(int, int); // 2 void func(double); // 3 void func(); // 4 private: void func(char, char); // 5 }; int main() { // 'Myclass' sınıfı içerisinde bildirilen fonksiyonlar; // - Hepsinin de ismi aynı olduğu için, // - Hepsi 'class scope' da olduğu için, // - İmzaları farklı olduğu için // Birbirlerinin 'overload' edilmiş halidir. Yani beş adet 'overload' vardır. Myclass mx; mx.func(); // 4 numara çağrılacak. mx.func(12); // 1 numara çağrılacak. mx.func(12, 45); // 2 numara çağrılacak. mx.func(.14); // 3 numara çağrılacak. mx.func(12u); // 'unsigned int' türünden 'int' türüne dönüşüm 'normal conversion'. Yani 1 numaralı çağrılabilir. // 'unsigned int' türünden 'double' türüne dönüşüm 'normal conversion'. Yani 3 numaralı çağrılabilir. // Derleyici, her iki dönüşüm de birbirine üstün gelemediği için, KARARSIZ KALIR VE 'ambiguity' TİP // SENTAKS HATASI ALIRIZ. } * Örnek 2, // Some code here... class A{ public: void func(double); // 1 private: void func(int); // 2 }; int main() { A ax; ax.func(12); // Yukarıdaki fonksiyon çağrısı SENTAKS HATASINA neden olur. Çünkü derleme sırasında derleyici, // ilk önce 'func' ismini 'class scope' içerisinde arayacak. İki adet isim bulacak. // Sonrasında bunların aldıkları parametreler ve türlerini, fonksiyon çağrısında kullanılanlar ile karşılaştıracak ve // iki adet 'overload' olduğunu görecek. Bunlardan 2 numaralı fonksiyon 'exact-match' olduğundan, 1 numaralı fonksiyonun // ise 'normal-conversion' olmasından dolayı, fonksiyon çağrısı 2 numaralı fonksiyona bağlanacaktır. // Fakat ilgili 2 numaralı fonksiyon 'erişim kontrolüne' takılacağından, SENTAKS HATASI ALACAĞIZ. // ÇÜNKÜ SIRALAMA AŞAĞIDAKİ GİBİDİR: // 1. NAME LOOK-UP. // 2. CONTEXT CONTROL. // 3. ACCESS CONTROL. } >> 'global function' lardan farklı olarak, 'class member function' lar 'redeclare' EDİLEMİYOR. SENTAKS HATASI. /*============================================================================================================*/ (06_27_09_2020) > Sınıflar (devam): >> C++ dilinde, üye fonksiyonların tanımlanması : >>> Sınıf bloğu içerisinde bildirimi yapılmamış bir fonksiyonun tanımını yapamayız, SENTAKS HATASIDIR. >>> Genel olarak fonksiyon bildirimleri başlık dosyası içerisinde, tanımları ise kaynak dosya içerisindedir. Zaman zaman da tanımları başlık dosyası içerisinde de yapılır ki bu durumu ileride inceleyeceğiz. * Örnek 1, // neco.h class Myclass{ public: void foo(int x, int y); void foo(int x); static void func(int x); // DAHA SONRA İŞLENECEKTİR. private: int mx, my; }; // neco.cpp void Myclass:foo(int x, int y) { // codes here... } void Myclass::foo(int z) { // codes here... } // NOT : 'Myclass' sınıfından üretilen tüm objeler için sadece bir tek 'foo' fonksiyonu vardır. Sadece sınıf içerisinde // bildirildiklerinden dolayı, bu tür fonksiyonların birinci parametreleri o sınıf türünden adrestir. /* C dilinde olsaydı eğer, || C++ dilinde olsaydı eğer, || C++ dilinde 'static' olsaydı eğer, void foo(int x) || void foo(Myclass* ptr, int x) || void func(int x) { || { || { || || } || } || } Yani 20 tane de o sınıf türünden nesne de olsa, 30 tane de olsa, aslında bir adet üye sınıf var. Her bir nesne için ayrı ayrı 'foo' fonksiyonu yoktur. Fakat 'data member' için bu geçerli değildir. HER NESNENİN AYRI BİR 'data member' I VARDIR. */ >>> NAME LOOK-UP IN MEMBER FUNCTIONS : Derleyiciler şu sırayla işlem yaparlar : "Name look-up", "Context Control", "Access Control". >>>> İsim arama yaparken de sırasıyla şu aşamaları izler : "Block Scope", "Bütün Kapsayan Block Scopes", "Class Scope" ve son olarak "Namespace Scope" da. * Örnek 1, // Some code here... // Senaryo III int a(); // iiii. Görüldüğü üzere burada isim hiç aranmadı çünkü blok içerisinde bulundu. void Myclass::func(int x) { // NOT : İSİM ARAMA BİR KEZ YAPILIR VE BULUNURSA BİTER. BUNU YAPARKEN DE BELİRLİ BİR SIRAYI İZLER. // Senaryo I int a = 10; // ii. Daha sonra 'a' nın bir değişken ismi olduğu öğrenilir ve İSİM ARAMA SONLANIR. a = 12; // i. İlk önce eşitliğin sol tarafındaki isim aranır. // iii. 'a' değişkenine bu şekil bir atama yapılması sentaks hatası oluşturmayacağı için atama gerçekleşir. // Senaryo II int b(); // ii. Daha sonra 'b' nin bir fonksiyon ismi olduğu öğrenilir ve İSİM ARAMA SONLANIR. b = 14; // i. İlk önce eşitliğin sol tarafındaki isim aranır. // iii. Fakat fonksiyon isimleri DİLİN KURALLARINA GÖRE ATAMA OPERATÖRÜNÜN SOL OPERANDI OLAMAZ. // BU DURUMDAN DOLAYI SENTAKS HATASI OLUŞUR. // Senaryo III int a = 10; // ii. Daha sonra 'a' nın bir değişken ismi olduğu öğrenilir ve İSİM ARAMA SONLANIR. a(); // i. İlk önce eşitliğin sol tarafındaki isim aranır. // iii. Fakat 'a' değişkeninin böyle kullanılması SENTAKS HATASINA neden olur. } * Örnek 2, // neco.h class Myclass{ public: void func(int x); private: int mx; // iii. Eğer 'mx' ismini 'block scope' da BULUNAMASAYDI, 'class scope' da arayacaktı. Bu durumda da // 'mx' isminin 'data member' olan değişkene ait olacağını anlayacaktı. Bulunduğu için de İSİM ARAMA // SONLANACAKTI. } // neco.cpp int mx(); // iiii. Eğer 'mx' ismi 'class scope' da BULUNAMASAYDI, 'namepsace scope' da arayacaktı. Bu durumda da 'mx' // isminin bir fonksiyon ismi olduğunu anlayacaktı. Bulunduğu için de İSİM ARAMA SONLANACAKTI. void Myclass::func(int x) { int mx = 10; // ii. 'mx' isminin bir yerel değişkene ismi olduğunu anlaşılır ve İSİM ARAMA BİTER. mx = 20; // i. İsim arama önce 'block scope' içerisinde yapılır. // NOT : YUKARIDAKİ SENARYODA, // - 'block scope' DAKİ İSİM 'class scope' DAKİ İSMİ GİZLEMEKTE, // - 'class scope' DAKİ İSİM İSİM 'namepsace scope' DAKİ İSMİ GİZLEMEKTEDİR. // NOT : YUKARIDAKİ SENARYODA, // - Eğer ismi direkt 'class scope' içerisinde aratmak isteydik, 'mx' ismini 'this->' veya 'Myclass::' // şeklinde nitelememiz gerekiyordu ("this->mx = 20;") / ("Myclass::mx = 20;"). // - Eğer ismi direkt 'namespace scope' içerisinde aratmak isteseydik, 'mx' ismini '::' ile nitelememiz // gerekiyordu ("::mx = 20;"). } * Örnek 3, // Some code here... // neco.h class Myclass{ public: void func(int x); void foo(); } // neco.cpp void Myclass::func(int x) { } void Myclass::foo() { func(); // iii. Sonrasında sıra bu fonksiyonun bloğunu incelemeye geldi. 'func' ismi önce 'block scope' // da arandı fakat bulunamadı. Sonrasında 'class scope' da arandı ve bulundu. // iiii. Fakat bulunan 'func' isimli fonksiyonun parametreleri ile fonksiyon çağrısı sırasındaki // parametreler uyuşmadığı için 'Context Control' e takıldı => SENTAKS hatası. // BURADAKİ SENTAKS HATASI İSE PARAMETRE UYUŞMAZLIĞINDAN KAYNAKLANMAKTADIR. } void func() { } int main() { Myclass nec; // i. 'Myclass' türünden, 'nec' isimli bir nesne oluşturuldu. nec.foo(); // ii. Daha sonra 'foo' ismi, 'nec' nesnesi içinde arandı, bulundu. Sonrasında da bu // çağrı ona bağlandı. } >>> 'access control' : Aşağıdaki örneği inceleyiniz. * Örnek 1, // some codes here... // neco.h class Myclass{ public: void func(Myclass x); private: int mx; }; // neco.cpp Myclass g; void Myclass::func(Myclass x) { Myclass neco; mx = 20; // i. Buradaki 'mx' ismi isim arama sonucunda 'class scope' içerisinde aranacak ve bulunacak. // Yani 'func()' fonksiyonunu çağıran nesneye ait olan 'mx'. x.mx = 30; // ii. Buradaki 'mx' ismi isim arama sonucunda 'class scope' içerisinde aranacak ve bulunacak. // Yani ilgili 'func()' fonksiyonuna geçilen argümana ait olan 'mx'. neco.mx = 40; // iii. Burada 'mx' ismi ise 'neco' isimli nesneye ait olduğu anlaşılacak. g.mx = 50; // iiii. Buradaki 'mx' ise global değişken olan 'g' isimli nesneye ait olduğu anlaşılacak. } void foo(Myclass x) { mx = 200; // i. Sentaks hatası. Çünkü kimin 'mx' değeri? : Blok scope içerisinde bulunamayacak ve 'global namespace' // içerisinde aranacak. Orada da bulunamayacak. x.mx = 300; // ii. Buradaki 'mx' ismi isim arama sonucunda 'class scope' içerisinde aranacak ve bulunacak. Fakat bu // fonksiyon bir 'member function' olmadığından, ilgili 'mx' nesnesine erişmeye çalışma SENTAKS HATASI olacak. Myclass neco; neco.mx = 400; // iii. Burada 'mx' ismi ise 'neco' isimli nesneye ait olduğu anlaşılacak. Fakat bu fonksiyon bir // 'member function' olmadığından, ilgili 'mx' nesnesine erişmeye çalışma SENTAKS HATASI olacak. g.mx = 500; // iiii. Buradaki 'mx' ise global değişken olan 'g' isimli nesneye ait olduğu anlaşılacak. Fakat bu // fonksiyon bir 'member function' olmadığından, ilgili 'mx' nesnesine erişmeye çalışma SENTAKS HATASI olacak. } // BURADAN DA GÖREBİLECEĞİMİZ GİBİ, 'class scope' İÇERİSİNDE BİLDİRİLEN FONKSİYONLARDAN, 'private' KISMA DA ULAŞIM // SAĞLAYABİLİYOR. >>> 'this' Göstericisi : Bir anahtar sözcüktür. >>>> Sadece ve sadece 'non-static member functions' lar içerisinde kullanabiliriz. >>>> Bir ifade içinde kullanıldığında 'PR-value Expression'. >>>> Bu gösterici, hangi nesne için çağrılmışsa, o nesnenin adresini tutmaktadır. Dolayısıyla '*this' ise o nesnenin kendisidir. >>>> 'fluent api' : Yine nesnenin kendisini döndürerek, o sınıfa ait 'member functions' ların tek satırda çağrılması. * Örnek 1, // some code here... class A{ public: A& f1() { return *this; } A& f2() { return *this; } A& f3() { return *this; } }; int main() { A ma; ma.f1().f2().f3(); // 'fluent api'. } >>>> Yukarıdaki 'member functions' ların gizli parametresi olan 'nesnenin adresini' temsil etmektedir. >>>> 'this' göstericisinin KENDİSİ 'const' TUR. >>>>> ' int* const this ' : 'this' is a 'const' pointer-to 'int'. // Burada 'this' isimli göstericinin kendisi 'const' şeklindedir. >>>>> ' const int* this ' : 'this' is a pointer-to 'const int'. // Burada ise 'this' isimli göstericinin kendisi 'const' DEĞİLDİR. Gösterdiği değer 'const' şeklindedir. >>> 'const member functions' : BU FONKSİYON HANGİ NESNE İÇİN ÇAĞRILIRSA O NESNENİN PROBLEM DOMAİNİNDEKİ TEMSİL ETTİĞİ ANLAMI/DEĞERİ DEĞİŞTİRMEYECEKTİR. PEKİ, OLAYIN VERİ ELEMANLARINI DEĞİŞTİRİP/DEĞİŞTİRMEMEKLE ALAKASI YOKSA, NEYLE ALAKASI VARDIR: PROBLEM DOMAİNİNDEKİ TEMSİL ETTİĞİ ANLAM İLE ALAKASI VARDIR. VERİ ELEMANLARINI DEĞİŞTİRMİŞ/DEĞİŞTİRMEMİŞ BİZİ İLGİLENDİRMEMEKTEDİR. YANİ OLAYIN VERİ ELEMANLARI İLE ALAKASI YOKTUR. * Örnek 1, // Some code here... class Fighter{ public: void getMoreAmmo(); // I double getHealthInfo(); // II }; // Problem domaininde incelediğimizde 'I' numaralı fonksiyon çağrısı sonrasında 'Fighter' nesnesinin cephane miktarı artacak. // Artık daha fazla cephaneye sahip olduğundan, düşmana temkinli yaklaşmak yerine, düşmanın üzerine koşarak gidecektir. Bu fonksiyon çağrısı sonrasında o sınıf nesnesinin daha bir agresif kafa yapısında olduğunu söyleyebiliriz. YANİ EVVELCE DAHA // TEMKİNLİYKEN, ŞİMDİ DAHA SALDIRGAN. YANİ PROBLEM DOMAİNİNDEKİ ANLAMI ARTIK DEĞİŞTİ. // 'II' numaralı fonksiyon çağrısı sadece bilgilendirme yaptığı için nesnenin anlamını değiştirmiyor. Yani bu fonksiyon çağrısı // sonrasında o nesne hala aynı kafa yapısında. İŞ BU SEBEPTEN DOLAYI 'I' NUMARALI 'non-const', 'II' NUMARALI İSE 'const' OLMALI // Kİ YUKARIDA ANLATILAN MOTTO SENTAKS AÇISINDAN DA TEMELİ OLSUN. >>>> PEKİ BU FELSEFİK DÜŞÜNCEYİ SENTAKS AÇISINDAN NASIL ELE ALABİLİRİZ : * Örnek 1, // Some code here... class Account{ public: double get_balance(); // I void draw(double sum); // I }; // Yukarıdaki I ve II numaralı fonksiyonlar birer üye fonksiyonlar olduklarından, aslında bir gizli parametreleri daha vardırdır // ki çağrıldıkları nesnenin adresini de alırlar. Bir nevi aşağıdaki global fonksiyonlar gibi: double get_balance(Account&); // II void draw(Account&, double sum); // II // Buradan da görüldüğü üzere, adres alan ilk parametre 'const' olmadığı için aslında bir nevi 'setter' function gibi diyebiliriz. // Peki bizler yukarıdaki bu global fonksiyonları nasıl birer 'getter' yapardık: double get_balance(const Account&); // III void draw(const Account&, double sum); // III // Artık yukarıdaki iki fonksiyon da birinci parametrelerine geçilen nesneyi değiştirmeme tahattütünde bulunuyorlar. Böyle bir // girişim artık SENTAKS HATASI. Fakat bu yaklaşımı 'member functions' larda nasıl gerçekleştireceğiz: class Account{ public: double get_balance()const; // IIII void draw(double sum)const; // IIII }; // Fonksiyonların bildirimlerinin VE tanımlarının sonuna, yani '()' tokeninden sonra, 'const' anahtar sözcüğünü yazarak artık // bunları birer 'const-member function' haline getirebiliriz. // Günün sonunda 'I' ve 'II' birbirlerinin 'global function' ve 'member function' karşılıklarıyken, 'III' ve 'IIII' ise // birbirlerinin karşılıkları. // ARTIK 'this' göstericisinin türü, // 'non-const member functions' ise : "... T* const..." // 'const member function' ise : "... const T* const..." // Yani bu göstericiyi kullanarak, 'const-member function' gövdesinde, 'data member' ları DEĞİŞTİREMEYİZ. * Örnek 2, // Some code here... class Account{ public: double balance; // İlgili banka hesabındaki 'mevcuat tutarını' saklayan değişken. int debug_counter; // 'member functions' ların kaç defa çağrıldığını saklayan değişken. }; // Felsefi açıdan düşünürsek, // - 'balance' değişkenini değiştirmemiz aslında problem domaininde temsil ettiği ANLAMI DEĞİŞTİRECEKTİR. // - 'debug_counter' değişkenini değiştirmemiz ise problem domainindeki bu anlam İLE ALAKASI OLMADIĞINDAN, DEĞİŞTİRMEYECEKTİR. // Sentaks açısından bakarsak, // - 'const-member function' İÇERİSİNDE 'data members' LARI DEĞİŞTİREMEYİZ. ÇÜNKÜ 'this' GÖSTERİCİSİNİN TÜRÜ => "const T* const". // - 'non-const member function' İÇERİSİNDE 'data members' LARI DEĞİŞTİREBİLİRİZ. ÇÜNKÜ 'this' GÖSTERİCİSİNİN TÜRÜ => "T* const". >>>> 'const-member function' içerisinde 'non-const-member function' çağıramayız, SENTAKS HATASIDIR. * Örnek 1, // some code here... class A{ public: void func()const; void foo(); }; void A::func() const { // Artık iş bu fonksiyonun aldığı gizli parametrenin türü 'const T* const' şeklinde. foo(); // Fakat bu 'foo()' fonksiyonunun aldığı gizli parametrenin türü 'T* const' şeklinde. Ve bu çağrı yapıldığında da, // 'func' fonksiyonuna geçilen parametre 'foo' fonksiyonuna da geçileceğinden dolayı, 'const T* const' türünden // 'T* const' türüne dönüşüme zorluyoruz ki bu sentaks hatasıdır. YANİ 'const T* const' şeklinde olan bir gösterici // ile neyi yapamıyorsak, bu tip 'const-member functions' gövdelerinde de aynı şeyleri YAPAMAYIZ. } >>>> 'non-const' sınıf nesneleri için 'const-member functions' ve 'non-const-member functions' lar çağırabiliriz fakat 'const' sınıf nesneleri için sadece 'const-member functions' ları çağırabiliriz. * Örnek 1, // some code here... class A{ public: void func()const; void foo(); }; int main() { const A ax; // 'ax' isimli nesne 'const' bir nesne olduğundan değeri DEĞİŞTİRİLEMEZ. ax.func(); // Bu fonksiyonun gizli ilk parametresinin türü 'const A*' şeklinde. Bu parametreye geçilen 'ax' isimli nesnenin // adresi de 'const A*' şeklinde. Dolayısıyla bir sorun yok. ax.foo(); // Bu fonksiyonun gizli ilk parametresinin türü 'A*' şeklinde. Bu parametreye geçilen 'ax' isimli nesnenin // adresi de 'const A*' şeklinde. 'const A*' türünden 'A*' türüne dönüşüm SENTAKS HATASI olduğundan, bu çağrı SENTAKS // HATASI VERDİRİR. A bx; // 'bx' isimli nesne 'non-const' bir nesne olduğundan değeri DEĞİŞTİRİLEBİLİR. ax.func(); // Bu fonksiyonun gizli ilk parametresinin türü 'const A*' şeklinde. Bu parametreye geçilen 'ax' isimli nesnenin // adresi de 'A*' şeklinde. 'A*' türünden 'const A*' türüne dönüşüm LEGAL OLDUĞUNDAN, BU ÇAĞRI LEGALDİR. ax.foo(); // Bu fonksiyonun gizli ilk parametresinin türü 'A*' şeklinde. Bu parametreye geçilen 'ax' isimli nesnenin adresi // de 'A*' şeklinde. Dolayısıyla bir sorun yok. // Her ne kadar 'this' göstericisi burada yazılmamış olsa bile, arka plandaki işlemlerde 'this' göstericisi kullanıldı. } >>>> 'non-const T*' türünden değer döndürmek de sentaks hatasıdır. * Örnek 1, // some code here... class A{ public: A* func()const; }; A* A::func() const { // some codes here... return this; // Bu çağrı SENTAKS HATASIDIR. Çünkü 'this' göstericisi bir 'const A* const' türünden. Fakat bu 'return' deyimi ile // aslında biz 'const A* const' türünden 'A* const' türüne ÖRTÜLÜ DÖNÜŞÜM YAPTIRMAK İSTİYORUZ Kİ BU DURUM SENTAKS // HATASINA YOL AÇAR. // Yukarıdaki problemi çözmek için, // - Ya 'func()' fonksiyonu 'const A*' türünden döndürecek, // - Ya da 'func()' fonksiyonu bir 'non-const member function' olacak. } >>>> EĞER BİR 'data member' I DEĞİŞTİRMEK, O NESNENİN PROBLEM DOMAİNİNDE TEMSİL ETTİĞİ ANLAMI DA DEĞİŞTİRECEKSE, BU TÜR DEĞİŞİKLİKLERİ YAPAN FONKSİYONLARI 'non-const member functions' ŞEKLİNDE, EĞER ANLAMINI DEĞİŞTİRMEYECEKSE 'const-member function' ŞEKLİNDE BİLDİRMELİYİZ. ÇÜNKÜ O 'non-const member functions' LAR FELSEFİ OLARAK NESNENİN AMACINI, ANLAMINI DEĞİŞTİRMEKTEDİR. >>>> 'mutable' Anahtar sözcüğü (konuyu felsefi ve sentaks açısından pekiştirmeye yarayan bir örnek) : * Örnek 1, // some code here... // Step I class Fighter{ public: void attack(); int get_age()const; private: std::string name; // Savaşçının ismini tutan, int age; // Savaşçının yaşını tutan, int power; // Savaşçının güç puanını tutan, mutable int debug_counter; // Üye fonksiyonların kaç defa çağrıldığını tutan. }; // Yukarıdaki sınıf tanımın bakarak, // - 'name' isimli, 'age' isimli ve 'power' isimli değişkenlerin değerinin değişmesi, nesnemizin problem domaininde temsil // ettiği anlamı değiştirebilir. Çünkü bu değişkenler, problem domaininde, nesnemize anlam katan değişkenlerdir. // Fakat 'debug_counter' isimli değişkenin değişmesi, NESNENİN PROBLEM DOMAİNİNDEKİ ANLAMI İLE BİR ALAKASI YOKTUR. // İŞTE BU SEBEPTEN DOLAYI BU DEĞİŞKEN 'mutable' OLARAK BİLDİRİLDİ. int Fighter::get_age()const { ++debug_counter; // Felsefi olarak bu değişkenin değişmesi, nesnemizin problem domainde temsil ettiği anlam ile biralakası yoktur. // Fakat,sentaks kuralları gereği bizim bu değişkeni değiştirmemiz lazım ki kaç defa 'member functions' çağrılmış // bilelim. İşte bu durumda 'debug_counter' isimli değişken 'mutable' OLARAK NİTELENMELİ. return m_age; } // NOT : ARTIK NESNEMİZİN PROBLEM DOMAİNİNDEKİ ANLAMINI DEĞİŞTİREN FONKSİYONLARI 'non-const member functions' ŞEKLİNDE, // ANLAMINI DEĞİŞTİRMEYEN FONKSİYONLARI İSE 'const-member functions' ŞEKLİNDE BİLDİRMELİYİZ. FAKAT PROBLEM DOMAİNDEKİ // ANLAM İLE ALAKASI OLMAYAN 'data member' LARI DA 'mutable' OLARAK NİTELERSEK, 'const-member functions' İÇERİSİNDE // ONLARI DEĞİŞTİREBİLİRİZ. DAHA AÇIKLAYICI BİR ÖRNEK VERMEK GEREKİRSE, // SENARYO I: // BİR OYUN PROGRAMINDA 'getName()' İSİMLİ BİR FONKSİYON OLSUN. BU FONKSİYON ÇAĞRILDIĞINDA SAVAŞÇI SADECE İSMİNİ SÖYLESİN. // OYUN SENARYOSU GEREĞİ, SAVAŞÇININ İSMİNİ SÖYLEMESİ NORMAL KARŞILANSIN. DOLAYISIYLA BU FONKSİYONUN ÇAĞRILMASI NESNEMİZİN // ANLAMINI DEĞİŞTİRMEYECEKTİR. BUNDAN SEBEPTİR Kİ BU FONKSYİON 'const-member function' OLABİLİR. // SENARYO II: // BİR OYUN PROGRAMINDA 'getName()' İSİMLİ BİR FONKSİYON OLSUN. BU FONKSİYON ÇAĞRILDIĞINDA SAVAŞÇI SADECE İSMİNİ SÖYLESİN. // OYUN SENARYOSU GEREĞİ, SAVAŞÇININ İSMİNİ ÜÇ DEFA SÖYLEMESİ HAYATINI KAYBETMESİNE NEDEN OLSUN. DOLAYISIYLA İSMİNİ 2 DEFA // SÖYLEYEN İLE 1 DEFA SÖYLEYEN SAVAŞÇI AYNI DEĞİLDİR. İŞTE BU SEBEPTEN DOLAYI, BU FONKSİYONUN ÇAĞRILMASI NESNEMİZİN ANLAMINI // DEĞİŞTİRECEKTİR. İŞTE BU SEBEPTEN DOLAYI DA BU FONKSİYONUMUZUN 'non-const member functions' OLMASI DAHA UYGUN. * Örnek 2, #include class Myclass { public: void func()const; private: mutable int m_x; mutable int m_y; }; void Myclass::func() const { std::cout << "m_x : " << m_x << "\n"; m_x = 100; std::cout << "m_x : " << m_x << "\n"; std::cout << "m_y : " << m_y << "\n"; m_y = 200; std::cout << "m_y : " << m_y << "\n"; int x = 31; std::cout << "x : " << x << "\n"; x = 62; // Bu değişiklik legal çünkü 'x' bir 'data member' değil. Dolayısıyla 'this' göstericisi kullanılmamaktadır. std::cout << "x : " << x << "\n"; } int main() { Myclass ma; ma.func(); /* # OUTPUT # m_x : 1639795408 m_x : 100 m_y : 32767 m_y : 200 x : 31 x : 62 */ } >> Constructor / Destructor (Kurucu İşlev / Sonlandırıcı İşlev) : >>> Consturctor : Bir nesneyi hayata getiren 'member function' fonksiyondur. 'non-static' OLMAK ZORUNDADIR. 'global function' OLAMAZ. İSMİ İSE 'sınıf' İSMİ İLE AYNI OLMAK ZORUNDA. Geri dönüş değeri kavramı yoktur. Sınıfın 'public/protected/private' alanlarında olabilir. Overload edilebilir. Parametre alabilir. >>>> 'Default Ctor' : Parametresi olmayan veya bütün parametreleri 'default argument' olan Ctor. Altı 'special member functions' tan bir tanesidir. >>>>> 'special member functions' : Özel statülü fonksiyonlardır. Bunlar "Default Ctor / Varsayılan Kurucu İşlev", "Dtor / Sonlandırıcı İşlev", "Copy Ctor / Kopyalayan Kurucu İşlev", "Copy Assignment / Kopyalayan Atama Fonksiyonu", "Move Ctor / Taşıyan Kurucu İşlev" ve "Move Assignment / Taşıyan Atama Fonksiyonu" şeklindedir. Bunların kodları bizim talebimiz doğrultusunda derleyici tarafından yazılabilir veya derleyici durumdan vazife çıkartıp kodları kendisi yazabilir. İşte buna da o işlevin 'default' edilmesi denir. 'default Ctor' işlevindeki 'default' kelimesi ile KARIŞTIRMAYIN. DERLEYİCİ İLGİLİ İŞLEVLERİ 'default' ETMEKTE. Örneğin, "Derleyici, bu sınıfın Varsayılan Kurucu İşlevini 'default etti.' ". * Örnek 1, // some code here... class Myclass{ public: Myclass() = default; // Programcı, derleyiciden, 'default ctor' ın kodunu yazmasını talep ediyor. // 'default Ctor' is 'user-declared'. }; >>> ~Destructor : Bir nesnenin hayatını bitiren 'member function' fonksiyondur. 'non-static' OLMAK ZORUNDADIR. 'global function' OLAMAZ. İSMİ İSE 'sınıf' İSMİ İLE AYNI OLMAK ZORUNDA. Geri dönüş değeri kavramı yoktur. Sınıfın 'public/protected/private' alanlarında olabilir. Overload EDİLEMEZ. PARAMETRESİ OLMAYACAK. /*============================================================================================================*/ (07_03_10_2020) > Sınıflar (devam): >> Constructor / Destructor (Devam) : >>> '.' operatörü ile Destructor ÇAĞRILABİLİR Kİ BU DURUM KÖTÜ BİR ALIŞKANLIKLIK, Constructor çağrılamaz. >>> Derleyicinin durumdan vazife çıkartarak Ctor kodunu kendisinin yazması durumuna 'implicitly declared' denir. * Örnek 1, class Myclass { public: }; // Yukarıdaki sınıf bildirimi içerisine bizler Ctor bildirimi yapmadık. Bu durumda derleyici, durumdan vazife // çıkartarak, bir adet 'default Ctor' yazar. >>> Sınıf tanımı içerisinde en az bir tane parametreli Ctor bildirirsek, artık derleyici durumdan vazife çıkartıp da kendisi 'default Ctor' yazmıyor. Bu durumda 'default Ctor' is 'not-declared'. Yani böyle Ctor YOKTUR. * Örnek 1, class Myclass { public: Myclass(int); }; // Yukarıdaki parametreli Ctor bildiriminden dolayı, 'default Ctor' is 'not-declared'. >>> 'special member functions' lar üç farklı statü de olabilirler. Bunlar sırasıyla 'not-declared', 'user-declared' ve 'implicitly-declared' şeklindedir. >>>> 'not-declared' : Olmaması durumudur. Böyle bir Ctor / Dtor yok. >>>> 'user-declared' : Üç farklı biçimde bir Ctor 'user-declared' şeklinde anılır. Bunlar, 'declared', 'user-declared, but defaulted' ve 'user-declared, but deleted' şeklindedir. >>>>> 'declared' : Apaçık bir şekilde Ctor bildiriminin yapılmasıdır. * Örnek 1, // Some code here... class Myclass { public: Myclass(int, double); // 'declared' }; >>>>> 'user-declared, but defaulted' : Programcı, derleyiciden, 'default ctor' ın kodunu yazmasını talep ediyor * Örnek 1, // Some code here... class Myclass { public: Myclass() = default; // 'user-declared, but defaulted' }; >>>>> 'user-declared, but deleted' : Programcı Ctor bildirimini yapar fakat onu 'delete' eder. Bu durumda, bu tip bir Ctor için, YOKTUR diyemeyiz. YOKTUR diyebilmek için onun 'not-declared' olması gerekiyor * Örnek 1, // Some code here... class Myclass { public: Myclass() = delete; // 'user-declared, but defaulted' }; >>>> 'implicitly-declared' : Derleyicinin durumdan vazife çıkartarak Ctor kodunu yazması olayıdır. Programcının bir talebi gözetilmez. İki farklı biçimde anılır. Bunlar 'implicitly-declared, but defaulted' ve 'implicitly-declared, but deleted' şeklindedir. >>>>> 'implicitly-declared, but defaulted' : Derleyici, durumdan vazife çıkartıp, Ctor kodunu yazması olayıdır. >>>>> 'implicitly-declared, but deleted' : Derleyici, durumdan vazife çıkartıp, Ctor kodunu yazması olayıdır. Fakat daha sonra onu 'delete' etmesi olayıdır. Bu durumda, bu tip bir Ctor için, YOKTUR diyemeyiz. YOKTUR diyebilmek için onun 'not-declared' olması gerekiyor > 'static' YEREL DEĞİŞKENLER VE GLOBAL DEĞİŞKENLERİN HAYAT GELİŞLERİ: >> AYNI KAYNAK DOSYADAKİ 'static' ÖMÜRLÜ VE GLOBAL İSİM ALANINDAKİ NESNELER/DEĞİŞKENLER, BİLDİRİM SIRASINA GÖRE, 'main()' FONKSİYONU ÇAĞRISINDAN EVVEL HAYATA GELİRLER. YANİ 'main()' ÇAĞRISINDAN YAPILMADAN ÖNCE, İLK BİLDİRİLEN NESNE İLK HAYATA GELİR. * Örnek 1, //inside main.cpp #include class A{ public: A() { std::cout << "A::A() was called.\n"; } ~A() { std::cout << "A::~A() was called.\n"; } }; class B{ public: B() { std::cout << "B::B() was called.\n"; } ~B() { std::cout << "B::~B() was called.\n"; } }; class C{ public: C() { std::cout << "C::C() was called.\n"; } ~C() { std::cout << "C::~C() was called.\n"; } }; A ax; B bx; int main() { std::cout << "int main() was called.\n"; C cx; return 0; } // A, B ve C isimlerinin bir tür ismi olduğunu ve sırasıyla 'ax', 'bx' ve 'cx' isimlerinin ise o türlerden değişkenler olduğunu // varsayalım. Bu üç değişken de aynı kaynak dosya içerisinde. Bu durumda hayata önce 'ax' isimli değişken, sonrasında 'bx' // isimli değişken gelecek. Sonrasında 'main()' fonksiyonu çağrılacak ve 'cx' isimli değişken hayata gelecek. Buradaki önemli // nokta BU DEĞİŞKENLER ÇAĞRILMASALAR BİLE HAYATA GELİYORLAR. // Hayata veda ederlerken de hayata son gelen, ilk veda edecek. Yani bu sefer de sırasıyla önce 'cx', sonra 'bx', son olarak da // 'ax' hayata veda edecek, 'main()' FONKSİYONUNDAN ÇIKTIKTAN SONRA. >>>> 'main()' fonksiyonunun çağrılmasından evvel bir fonksiyon çağırtmak: // Some code here... int foo(void); int g = foo(); // C diline göre bu çağrı bir sentaks hatasıdır. Çünkü C dilinde 'global' değişkenler 'constant expression' ile hayata // gelmeli. Fakat Cpp dilinde böyle bir zorunluluk yoktur. int main() { std::cout << "int main() was called.\n"; } int foo(void) { std::cout << "int foo(void) was called.\n"; return 1; } // Yukarıdaki durumda 'g' isimli global değişken hayata 'main()' çağrısından önce geleceği için, 'foo()' fonksiyonunu // çağırtmış olacağız. >> 'static' ÖMÜRLÜ YEREL BİR NESNE/DEĞİŞKENLERİN HAYATA GELMELERİ İÇİN, İÇİNDE BULUNMUŞ OLDUKLARI FONKSİYONLARIN EN AZ BİR DEFA ÇAĞRILMALARI GEREKMEKTEDİR. BU NESNELERİN HAYATI İSE 'main()' FONKSİYONUNDAN ÇIKTIKTAN SONRA BİTMEKTEDİR. * Örnek 1, // Some code here... #include class A{ public: A() { std::cout << "A::A() was called.\n"; } ~A() { std::cout << "A::~A() was called.\n"; } }; class B{ public: B() { std::cout << "B::B() was called.\n"; } ~B() { std::cout << "B::~B() was called.\n"; } }; class C{ public: C() { std::cout << "C::C() was called.\n"; } ~C() { std::cout << "C::~C() was called.\n"; } }; void foo() { static C cx; } int main() { std::cout << "int main() was called.\n"; for(int i = 0; i < 3; ++i) foo(); std::cout << "int main() was called.\n"; return 0; } /* # PROGRAM ÇIKTISI # int main() was called. C::C() was called. int main() was called. C::~C() was called. */ >> FARKLI KAYNAK DOSYALARDAKİ 'static' ÖMÜRLÜ VE GLOBAL İSİM ALANINDAKİ DEĞİŞKENLERİN HAYATA GELME SIRASI 'unspecified behaviour'. BİR DİĞER DEYİŞLE SIRALAMA, DERLEYİCİYE BAĞLIDIR (Bkz. Static Init. Fiasco). >> NOT : BU İKİ ÖRNEKTE DE GÖRÜLDÜĞÜ GİBİ 'static' YEREL DEĞİŞKENLER HAYATA GELMELERİ İÇİN İÇİNDE TANIMLANDIKLARI FONKSİYONUN ÇAĞRILMASI GEREKMEKTE. FAKAT GLOBAL DEĞİŞKENLER İÇİN BÖYLE BİR DURUM SÖZ KONUSU DEĞİLDİR. O DEĞİŞKENLER ÇAĞRILMASALAR BİLE HAYATA GELMEKTEDİRLER. > 'dynamic' ÖMÜRLÜ NESNELERİN HAYATA GETİRİLMESİ 'operator' SEVİYESİNDE BİR İŞLEMDİR. BU İŞLEMLERİ GERÇEKLEŞTİREN 'expression' LARA İSE 'new expressions' DENİR. 'garbage-collection' OLMADIĞI İÇİN BU TİP NESNELERİN HAYATINI BİZ PROGRAMCILAR BİTİRMELİ. BUNU YAPAN OPERATÖRLERE DE 'delete operators' DENMEKTEDİR. > DEĞİŞKENLERİN TANIMLANMASI : >> 'default-initialization' : "int x;", "int a[10];" ve "Myclass mx;" şeklinde yapılan hayata getirme biçimidir. >>> Otomatik ömürlü aritmetik türden değişkenleri ve/veya dizileri yukarıdaki şekilde hayata getirirsek, 'garbage-value' veya 'indetermined value' ile hayata gelirler. >>> 'statik' ömürlü değişkenler ise önce 'zero-initialize' edilirler. >>>> 'zero-initialize' : Primitiv türden değişkenler hayata 'sıfır' değeriyle, boolean türden değişkenler 'false' değeriyle ve 'pointer' türden değişkenler ise 'nullptr' ile hayata gelir. Sınıf türünden nesneler ise nesnenin bütün 'data member' ları yukarıdaki şekilde 'zero-initialize' edilirler. * Örnek 1, #include class Myclass { public: void print() { std::cout << "m_b : " << m_b << "\n"; std::cout << "m_i : " << m_i << "\n"; std::cout << "m_f : " << m_f << "\n"; } private: bool m_b; int m_i; float m_f; }; Myclass mb; // Statik ömürlü bir değişken olduğu için 'default Ctor' çağrıldı ve bütün elemanlar 'zero-initialize' edili. int main() { Myclass ma; // Otomatik ömürlü bir değişken olduğu için 'default Ctor' çağrıldı ve bütün elemanlar 'garbage-value' ile hayata geldi. ma.print(); /* # OUTPUT # m_b : 173 m_i : -1167531328 m_f : 4.59163e-41 */ mb.print(); /* # OUTPUT # m_b : 0 m_i : 0 m_f : 0 */ } >>> Bir sınıf türünden otomatik ömürlü nesneler için ise 'default Ctor.' çağrılır. Eğer 'default Ctor' yok ise SENTAKS HATASI alırız. * Örnek 1, #include class Myclass { public: Myclass(int b, int i, int f) { m_b = b; m_i = i; m_f = f; } void print() { std::cout << "m_b : " << m_b << "\n"; std::cout << "m_i : " << m_i << "\n"; std::cout << "m_f : " << m_f << "\n"; } private: bool m_b; int m_i; float m_f; }; int main() { Myclass ma; // error: no matching function for call to ‘Myclass::Myclass()’ ma.print(); } >> 'value-initialize' : "int x{};", "Myclass mx{};", "int a[]{};" şeklinde hayata getirme biçimidir. Küme parantezinin içinin boş olması gerekmektedir. Dolayısıyla değişkenler önce 'zero-initialize' edilirler. * Örnek 1, // some code here... struct Myclass{ int mx, my; }; Myclass gx; // Global bir değişken olduğu için bütün elemanları önce 'zero-initialize' edildi. Ondan sonra 'default Ctor' çağrıldı ama // hiç bir şey yapmadı. int main() { Myclass gy; // 'gy' nesnesi 'default-initialize' edildi. 'default Ctor' çağrıldı ama hiç bir şey yapmadığı için 'mx' ve 'my' isimli // 'data members' lar ÇÖP DEĞER tutmaktalar. Myclass gz{}; // 'gz' nesnesinin bütün elemanları önce 'zero-initialize' edildi. Ondan sonra 'default Ctor' çağrıldı ama hiç bir şey // yapmadı. } >> 'direct-initialize' : "Myclass mx(10);" şeklindeki hayata getiriş biçimidir. Duruma göre uygun 'parametreli Ctor' çağrılacaktır. >> 'copy-initialization' : "Myclass mx = 200;" şeklindeki hayata getiriş biçimidir. Tehlikelidir. Uygun şartlar altında parantezin sağ tarafındaki 'primitive' türden değişkenleri sınıf türüne dönüştürebilir. >> 'direct-list-initialization' : "Myclass mx{500};" şeklindeki hayata getiriş biçimidir. 'brace initialization' da denir. 'Narrowing Conversion' sentaks hatasına neden olur. Yukarıdaki her iki yöntemde ise bu durum LEGAL ama veri kaybına neden olur. >> Yukarıdaki üç yöntem için örnek, * Örnek 1, // some code here... struct Myclass{ Myclass(int x, int y) { mx = x; my = y; } int mx, my; }; int main() { /* # OUTPUT # 12_24 36_48 60_72 */ Myclass m1(12, 24); // direct-initialize Myclass m2{36, 48}; // brace-initialize Myclass m3 = {60, 72}; // copy-initialize std::cout << m1.mx << "_" << m1.my << std::endl; std::cout << m2.mx << "_" << m2.my << std::endl; std::cout << m3.mx << "_" << m3.my << std::endl; } > ODR (One-Definition-Rule) ve 'inline' Fonksiyonlar: >> Cpp dilinde değişkenlerin, fonksiyonların ve sınıfların bildirimleri birden fazla kez yapılabilir fakat tanımlarının birden fazla kez yapılması ya SENTAKS HATASI ya da 'tanımsız davranış'. * Örnek 1, // AYNI KAYNAK DOSYADA int a; // This's a DEFINITION int a; // 'redefinition'. SENTAKS HATASI. extern int b; // This's a DECLERATION extern int b; // Legal extern int b; // Legal int b; // This's a DEFINITION int b; // 'redefinition'. SENTAKS HATASI. class A; // This's a Forward-DECLERATION class A; // Legal class A{}; // This's a DEFINITION class A{}; // 'redefinition'. SENTAKS HATASI. void func(int); // This's a DECLERATION void func(int); // Legal void func(int); // Legal void func(int) // This's a DEFINITION { } void func(int) // 'redefinition'. SENTAKS HATASI. { } // FARKLI KAYNAK DOSYADA // Inside main.cpp class A{ int a,b,c; }; void func(int) // This's a DEFINITION { } int main() { } // Inside neco.cpp class A{ int ma, mb, mc; }; void func(int) // This's a DEFINITION { } // BU DURUMDA HEM FONKSİYON TANIMLARKEN HEM DE SINIFLARI TANIMLARKEN 'ODR' İHLAL EDİLMİŞ OLDU VE 'Tanımsız Davranış'. // DERLEME ZAMANINDA HATA ALMAYIZ AMA BİR İHTİMAL 'link' AŞAMASINDA 'linker' HATA VEREBİLİR. // Eğer yukarıdaki 'func()' isimli fonksiyonları 'static' anahtar sözcüğü ile niteleseydik, ilgili fonksiyonu // 'iç bağlantıya' alacağımız için, ODR ihlal edilmemiş olur. // Eğer yukarıdaki 'A' türünden sınıfların tanımlamalarını 'token-by-token' birbirinin AYNISI yapsaydık, // ODR ihlal edilmemiş olur. // Şablonlarda(templates) da 'token-by-token' birbirinin AYNISI ise ODR ihlal edilmemiş olur. // Başlık dosyası içerisinde tanımlayacağımız değişkenleri ve fonksiyonları 'inline' anahtar sözcüğü ile betimlersek, // ODR ihlal edilmemiş olur. /* KISACA, - BAŞLIK DOSYASI IÇERISINDE YAPACAĞıMıZ DEĞIŞKEN VE FONKSIYON TANIMLAMALARINI 'INLINE' ANAHTAR SÖZCÜĞÜ ILE NITELEMEMIZ GEREKIYOR KI ODR IHLAL EDILMESIN. 'USER-DEFINED' TÜRLER EĞER 'TOKEN-BY-TOKEN' AYNı ISE ZATEN ODR IHLAL ETMIYOR. - FARKLı KAYNAK DOSYALARDAKI AYNı ISIMDEKI FONKSIYONLARDAN BAZILARINI 'IÇ BAĞLANTıYA' AÇARAK ODR IHLAL EDILMESIN. */ >> 'inline' Fonksiyonlar : Fonksiyon tanımlarını 'inline' anahtar sözcüğünün kullanılması ile elde edilir. >>> Yukarıdaki ODR ihlalini engellemek için kullanılır. >>> Derleyici, bir fonksiyon çağrısının yapıldığı yerde fonksiyonun tanımını da görürse, o çağrı yerine fonksiyonun derlenmiş halini yazıyor. 'linker' a işi havale etmiyor.Fakat bu durum %100 geçerli değil. Fonksiyonun tanımını gördüğünden dolayı, bir avantaj sağlayacağını düşündüğü noktada, yer değişikliği yapabilir. Ama fonksiyonun kodları çok karışık ise yer değiştirme işlemi yapmayadabilir. Yani bizler bir nevi derleyiciden 'rica ediyoruz'. Diğer yandan bu değişikliği 'inline' anahtar sözcüğü olmadan da derleyici yapabilir. >>> Bir fonksiyonun tanımını, sınıfın tanımı içerisinde yaparsak, bu fonksiyon direkt 'inline' olur. İlgili 'inline' anahtar sözcüğünü kullanmamız yada kullanmamamız durumu değiştirmez. * Örnek 1, // APPROACH I // Inside neco.hpp file class Myclass{ public: void foo(int) // Artık bu 'foo()' fonksiyonu bir inline. { //... } }; // APPROACH II // Inside neco.hpp file class Myclass{ public: /*inline*/ void foo(int); // 'inline' anahtar sözcüğünü ister bildirime ister tanıma yazın. }; /*inline*/ void Myclass::foo(int) { //... } >>> Derleyici tarafından 'implicitly-declared' olan 'special member functions' birer 'inline' fonksiyonlardır. > Sınıflar (devam) : >> Ctor. Initializer List / Member Initializer List: Yalnızca Kurucu İşlevlere özgü bir sentaks özelliğidir. Sınıfın 'non-static' veri elemanlarına ilk değer verir. * Örnek 1, // Some code here... class Myclass{ int mx, my; public: Myclass() { mx = 10; // Assignment my = 20; // Assignment // KURAL : Çalışma zamanında program, bir Kurucu İşlevin ana bloğuna girmişse, bütün 'data members' HAYATA GELMİŞ // demektir. Dolayısıyla yukarıdaki her iki deyim birer atamadır. Peki ilk değer verme işini nasıl halledeceğiz? // => Örnek 2 <= } }; * Örnek 2, // Some code here... class Myclass{ int mx, my; public: Myclass() : mx(50), my(900) // Parantez içerisine bir ifade de gelebilir. { // 'mx' değişkeninin değeri : 50 // 'my' değişkeninin değeri : 900 mx = 10; // Assignment my = 20; // Assignment } }; >>> Aksi durumda zorlayıcı bir nedenimiz yoksa, bütün 'data member' lara bu yöntem ile ilk değer vermeliyiz. >>> Eğer bu yöntem ile ilk değer vermediğimiz veya Ctor bloğu içerisinde bir değer atamadığımız 'data members' var ise onlar 'default initialize' edilirler. Yani 'garbage-value' değerini tutarlar. Eğer 'static' ömürlü ise o nesnemiz, 'data member' da 'zero-initialize' edilir. >>> 'data members', sınıf tanımındaki sıraya göre hayata gelirler. 'Ctor Initializer List' sırasındaki yazım sıralamasına göre DEĞİL. DOLAYISIYLA BİLDİRİMDEKİ SIRA İLE BURADAKİ SIRAYI AYNI YAPMALIYIZ. * Örnek 1, class Myclass{ public: Myclass(int x, int y) : mb(y), ma(x) { // II I } private: int ma, mb; }; // Bildirimdeki sıra gözetildiği için önce 'I' numaralı kod çalıştırılır, sonrasında da 'II' numaralı. * Örnek 2, class Myclass{ public: Myclass(int x) : mb(y), ma( mb / 3) { // II I } private: int ma, mb; }; // Bildirimdeki sıra gözetildiği için önce 'I' numaralı kod çalıştırılır, sonrasında da 'II' numaralı. Fakat 'I' kod // çalıştırılırken 'mb' garbage-value tuttuğu için bu durum 'Tanımsız Davranış'. >>> C++11 ve sonrası dönemde '()' yerine '{}' kullanabiliriz. >>> Aggregiate Initialization : Bileşik nesnelere ilk değer verme biçimidir. * Örnek 1, #include class Myclass { public: Myclass() : ma{1, 2, 3} { std::cout << "\n[ma] : [" << ma << "] => "; for(auto index : ma) std::cout << index << " "; } private: int ma[3]; }; Myclass g; int main() { /* # OUTPUT # [ma] : [0x55ebb4199158] => 1 2 3 [ma] : [0x7ffe3b4e2a4c] => 1 2 3 */ Myclass gg; } >> Default Member Init. / In-class Init : Modern Cpp ile dile eklenmiştir. Verilerin hayata gelişi ile alakalı. 'data-member' ların 'non-static' OLMASI GEREKİYOR. >>> İster 'Default Ctor' u biz yazalım, ister derleyici yazsın; aşağıdaki kullanım sonucunda, sonuç aynıdır. * Örnek 1, class Myclass{ public: int mx = 10; // Legal int my{20}; // Legal int mz(30); // Sentaks hatası. }; int main() { Myclass mx; // Veri elemanı olan 'mx', '=10' ataması olmasaydı eğer, ÇÖP DEĞER İLE HAYATA GELECEKTİ, ÇÜNKÜ OTOMATİK ÖMÜRLÜ. // Fakat böyle bir atamadan dolayı hayata 10 değeri ile birlikte geliyor. Tıpkı "Ctor Initializer List" kullanmışız gibi. } * Örnek 2, #include class Member{ public: void print() { std::cout << "m_s : " << m_s << "\n"; std::cout << "m_i : " << m_i << "\n"; std::cout << "m_f : " << m_f << "\n"; } private: short m_s; int m_i{200}; float m_f; }; int main() { Member ma; ma.print(); /* # OUTPUT # m_s : 0 m_i : 200 m_f : -3.03244e-05 */ Member mb{}; mb.print(); /* # OUTPUT # m_s : 0 m_i : 200 m_f : 0 */ } >>> Ctor Initializer List ile başka bir ilk değer verme yaparsak, bu sefer 'Ctor Initializer List' teki atama geçerli oluyor. 'In-class Init' hükmü kalmıyor. > Fonksiyonların 'delete' edilmesi: >> Bütün fonksiyonlar 'delete' edilebilir. Global olması, 'static' ve 'non-static' olması durumu değiştirmez. * Örnek 1, int foo() = delete; // Artık bu fonksiyona yapılan çağrı sentaks hatası. >> 'Function Overload Resolution' A katılırlar. * Örnek 1, int foo() = delete; // Artık bu fonksiyona yapılan çağrı sentaks hatası. int foo(char); int foo(int); int foo(double); // Toplam dört adet fonksiyon yüklemesi vardır. int main() { foo(); // SENTAKS HATASININ NEDENİ İLGİLİ FONKSİYONUN 'delete' EDİLMİŞ OLMASI. } >> DERLEYİCİ SÖZ KONUSU ÜYE FONKSİYONUN KODUNU YAZARKEN BİR SENTAKS HATASI OLUŞURSA, YAZMAKTA OLDUĞU ÖZEL ÜYE FONKSİYONU 'delete' EDER. * Örnek 1, class Myclass{ public: const int x; int& r; }; // Yukarıdaki sınıfın 'Default Ctor' u DERLEYİCİ TARAFINDAN YAZILMIŞTIR, 'implicitly-declared' STATÜSÜNDEDİR. Dolayısıyla primitive // türleri de 'garbage-value' ile hayata getirir. Fakat 'const' / 'L-value Reference' türden değişkenlere ilk değer vermek zorundayız. // Bundan dolayı 'Default Ctor' 'delete' edildi. int main() { Myclass mx; // 'delete' edilmiş bir üye fonksiyona çağrı SENTAKS hatası oluşturacaktır. } * Örnek 2, class Member{ public: Member(int); // Parametreli Ctor olduğu için 'Default Ctor' YOKTUR. }; class Neco{ private: Member mx; // Bu sınıfın 'Default Ctor' U DERLEYİCİ TARAFINDAN YAZILDI. Sınıf türleri için de 'Default Ctor' çağrıldı. // Fakat Member sınıfında o türden üye fonksiyon olmadığından, bu sınıfın 'Default Ctor' u 'delete' EDİLDİ. }; int main() { Neco nec; // Nec türü için 'Default Ctor' çağrılacak. Fakat 'delete' edildiğinden, SENTAKS HATASI. } * Örnek 3, class Member{ private: Member(); // 'Default Ctor' is a 'private'. }; class Neco{ private: Member mx; // Bu sınıfın 'Default Ctor' U DERLEYİCİ TARAFINDAN YAZILDI. Sınıf türleri için de 'Default Ctor' çağrıldı. // Fakat Member sınıfının 'Default Ctor' u 'private' olduğundan erişemeyecek ve bu sınıfın 'Default Ctor' u // 'delete' edilecek. }; int main() { Neco nec; // Nec türü için 'Default Ctor' çağrılacak. Fakat 'delete' edildiğinden, SENTAKS HATASI. } * Örnek 4, class Member{ public: Member() = delete; // 'Default Ctor' programcı tarafından 'delete' edilmiş. 'user-declared, but deleted'. }; class Neco{ private: Member mx; // Bu sınıfın 'Default Ctor' U DERLEYİCİ TARAFINDAN YAZILDI. Sınıf türleri için de 'Default Ctor' çağrıldı. // Fakat Member sınıfının 'Default Ctor' u 'delete' edildiğinden dolayı, bu sınıfınki de 'delete' edilecek. }; int main() { Neco nec; // Nec türü için 'Default Ctor' çağrılacak. Fakat 'delete' edildiğinden, SENTAKS HATASI. } /*============================================================================================================*/ (08_04_10_2020) > Sınıflar (devam) : >> Copy Constructor : Şu durumlarda 'Copy Ctor' çağrılır; fonksiyona geçilen 'call-by-value' şeklindeki parametreler, fonksiyonların sınıf türünden değer döndürümesi ve ilk değer verme sonucunda. Fakat buradaki önemli nokta, hayata gelecek nesnenin türü ile onu hayata getirecek nesnenin türü aynı OLMALI. Kopyalama işlemi yapıldığında, bu 'ctor' çağrılır. * Örnek 1, class Myclass{}; void func(Myclass mx); // I Myclass foo(); // II int main() { // i. 'I' numaralı fonksiyon 'call-by-value' ile değer aldığından, bu fonksiyona geçilen parametre için // 'mx' nesnesi için 'Copy Ctor' çağrılır. // ii. 'II' numaralı fonksiyonun geri dönüş değeri de 'call-by-value' şeklinde olduğundan, // 'return' ifadesinden elde edilen 'geçici nesne' için de 'Copy Ctor' çağrılır. Myclass mx; // 'Default Ctor' çağrılır. Myclass my{ mx }; // 'my' nesnesi için 'Copy Ctor' çağrılır. } >>> Derleyicinin yazmış olduğu 'Copy Ctor', sınıfın her bir elemanını bildirimdeki sıra ile karşılıklı olarak birbirine kopyalayarak hayata getirir. Eğer 'data member' olarak bir sınıf nesnesi de varsa, onun 'Copy Ctor' u çağrılır. * Örnek 1, #include class A; class Myclass { public: Myclass() = default; // Default Ctor Myclass(const Myclass& other) : mmon{other.mmon} // Copy Ctor { std::cout << "Myclass::Myclass(const Myclass& other) was called.\n"; } Myclass(const A& other) // Another Ctor. { } void print() { std::cout << "mmon : " << mmon << "\n"; } public: int mmon{1}; }; class Member{ public: Member() = default; Member(const Member& other) : mday{other.mday}, mmon{other.mmon}, myear{other.myear} { std::cout << "Member::Member(const Myclass& other) was called.\n"; } void print() { std::cout << "mday : " << mday << "\n"; std::cout << "mmon : " << mmon.mmon << "\n"; std::cout << "myear : " << myear << "\n"; } private: int mday{1}; Myclass mmon{}; int myear{1970}; }; int main() { Member mx{}; mx.print(); std::cout << "---------------\n"; Member my{mx}; my.print(); /* # OUTPUT # mday : 1 mmon : 1 myear : 1970 --------------- Myclass::Myclass(const Myclass& other) was called. Member::Member(const Myclass& other) was called. mday : 1 mmon : 1 myear : 1970 */ } >> Hangi durumlarda, 'Copy Ctor' u bizler yazmalıyız? >>>> RAII idiomu güden sınıflar için yazabiliriz. Sınıfın elemanlarının 'gösterici' olması durumunda, 'Copy Ctor' çağrısı ile 'göstericiler' birbirine kopyalanacaktır. Bu durumda her iki 'gösterici', aslında aynı nesneyi gösteriyor olacak (Bkz. 'Shallow Copy / Member-wise Copy'). 'gösterici' lerden bir tanesini kullanarak kaynağı geri verdiğimiz zaman, diğer gösterici 'dangling' haline düşecek. Bunun önüne geçmek için de bizlerin 'göstericileri' birbirine kopyalamak yerine, onların gösterdiği adresteki nesneleri birbirine kopyalamamız gerekiyor (Bkz. 'Deep Copy'). * Örnek 1, // Some code here... // Aşağıdaki sınıf RAII idiom'u gütmektedir. Çünkü bu sınıf türünden bir nesne hayata gelirken, dinamik bir bellek // de elde ediyor. class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) : m_id{id} // Parametreli Ctor. { mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } // Copy Ctor. defaulted by compiler itself. ~Person() { if(mpa) std::free(mpa); } void print()const { // Imagine that the data members were being printed to the console. } private: int m_id; char* mpa; }; void process_person(Person px) { px.print(); // iii. Artık 'px' nesnesi ile bu fonksiyon çağrısında kullanılan argüman olan 'p' nesnesi birbirinin // kopyası. Fakat bizim sınıfımız içerisinde 'raw pointer' olduğundan, her iki 'px' ve 'p' nesneleri // içerisindeki bu göstericiler aslında aynı yazıyı gösterir oldular. // iv. 'px' nesnesinin hayatı burada sonlanacağından, onun için 'Dtor' çağrılacaktır ve içerisindeki // 'raw pointer' tarafından gösterilen blok geri verilecektir. } int main() { Person p{1234, "Ahmet"}; // i. Parametreli Ctor'a yapılan çağrı sonucunda, 'p' nesnesi ilgili değerler ile hayata geldi. process_person(p); // ii. 'Copy Ctor' derleyici tarafından yazıldığı için, 'shallow-copy' yöntemi ile bu fonksiyona // ilgili 'p' nesnesi kopyalandı. p.print(); // Vi. Artık 'p' nesnesi içerisinde bulunan 'raw pointer', 'dangling' hale gelmiştir. Çünkü gösterdiği // bellek alanı geri verilmiş durumda. // Yukarıdaki senaryoda iki noktada 'Tanımsız Davranış' gözlenmiştir; // I. 'process_person' fonksiyonu çağrısından sonra, ilgili 'p' nesnesini kullanmak. // II. İlgili 'p' nesnesinin hayatı bittiğinde, zaten 'dangling' durumda olan bir fonksiyonu // 'free()' fonksiyonuna geçmek. } * Örnek 2, yukarıdaki 'shallow-copy' den kaynaklanan 'run-time' hatasını gidermek için yazılacak 'Copy Ctor / Deep Copy' : // Some code here... class Person{ public: // Some code here... Person(const Person& other) : m_id{other.m_id} { mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } // Some code here... private: int m_id; char* mpa; }; > Dipnot : İçeriden dışarıya tasarım (önce 'data members' ları belirtip, ona göre bir 'interface' oluşturulması) modelinden KAÇINMALIYIZ. Bunun yerine önce bir 'interface' belirleyip, ona göre 'implementation' yazmamız gerekiyor ki buna da dışarıdan içeriye tasarım denir. > Sınıflar (devam) : >> 'Copy Assignment' : Aynı türden nesneleri birbirine atadığımız zaman, bu özel işlev çağrılacaktır. Bu bir 'Ctor' DEĞİLDİR. Çünkü atama operatörünün sağındaki ve solundaki her iki nesne de hayatta. Sadece değerleri birbirine kopyalanmakta, YANİ 'shallow-copy' yapmakta. Bu fonksiyonu ya derleyici yazacak ya da biz yazacağız. Peki derleyici nasıl yazıyor?: * Örnek 1, // Some code here... class A; class B; class Neco{ public: // Some codes here... Neco& operator=(const Neco& other) // Derleyicinin yazdığı 'Copy Assignment' { // BİLDİRİMDEKİ SIRA İLE 'data members' LARI BİRBİRİNE ATAMAKTADIR. (shallow-copy ŞEKLİNDE.) m_x = other.m_x; ax = other.ax; // 'ax' için de 'Copy Assignment' çağrılacak. bx = other.bx; // 'bx' için de 'Copy Assignment' çağrılacak. *this; } private: int m_x; A ax; B bx; }; >>> Yukarıdaki örnekte de görüldüğü üzere, eğer derleyici 'Copy Ctor' ve/veya 'Copy Assignment Functions' ları kendisi yazarsa, 'shallow-copy' yapmakta. Dolayısıyla hangi neden veya nedenlerden ötürü bizler 'Copy Ctor' yazmamız gerekiyorsa, işte aynı nedenlerden dolayı da bizlerin 'Copy Assignment Function' yazması gerekmekte. Eğer 'Copy Ctor' u biz yazarsak fakat 'Copy Assignment Function' u yazmazsak, nesneleri birbirine atama yaparken problem yaşamız oluruz. Tam tersi durumda eğer 'Copy Ctor' yazmazsak ama 'Copy Assignment Function' yazarsak, bu durumda da nesneleri hayata getirirken problem yaşarız. * Örnek 1, // Yukarıdaki Person sınıfını ele alalım; class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) : m_id{id} // Parametreli Ctor. { mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } Person(const Person& other) : m_id{other.m_id} // Copy Ctor { mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } ~Person() // Dtor { if(mpa) std::free(mpa); } void print()const { // Imagine that the data members were being printed to the console. } private: int m_id; char* mpa; }; int main() { Person p1{1234, "Ahmet"}; // i. İlgili 'p1' nesnesi hayata geliyor. p1.print(); { Person p2{9876, "Merve"}; // ii. İlgili 'p2' nesnesi hayata geliyor. p2.print(); p2 = p1; // iii. Derleyici tarafından yazılan 'Copy Assignment Function' a bir çağrı yapılıyor ve 'p1' in // elemanları, 'p2' ye karşılıklı kopyalanıyor. Fakat kopyalama işlemi öncesinde 'p2' içerisindeki // göstericinin gösterdiği bellek 'free()' edilmediğinden, 'memory leak' MEYDANA GELİYOR. Kopyalama // işleminden sonra hem 'p1' hem de 'p2' nesnelerinin göstericileri aynı bellek bloğunu göstermekteler. p2.print(); // IV i. Artık 'p2' nesnesinin hayatı sonlanacağı için, içerisindeki göstericinin gösterdiği bellek // alanı da geri verilecek. } // V i. Artık 'p1' nesnesinin içerisindeki gösterici 'dangling' hale geldi. p1.print(); // VI i. 'dangling' göstericilerin bu şekilde kullanılması da 'Tanımsız Davranış'. // Yukarıdaki senaryoda üç noktada HATA gözlenmiştir; // I. 'Copy Assignment Function' çağrısı öncesinde, 'p2' içerisindeki göstericinin gösterdiği bellek alanı // 'free()' edilmediğinden dolayı 'memory leak' oluşacak. // II. İlgili 'p2' nesnesinin hayatı bittiğinde, 'dangling' durumda geçen 'p1' içerisindeki göstericiyi kullanmak. // III. İlgili 'p1' nesnesinin hayatı bittiğinde, 'dangling' durumdaki gösteriyici 'free()' etmek. } * Örnek 2, yukarıdaki 'shallow-copy' den kaynaklanan 'run-time' hatasını gidermek için yazılacak 'Copy Assignment Function / Deep Copy' : // Some code here... class Person{ public: // Some code here... // Copy Assignment Function. Bu şekilde yazmak zorunda değiliz. Başka yöntemler de vardır. Person& operator=(const Person& other) { // Protection against 'Self Assignment'. Eğer bu korumayı yapmasaydık, 'other' nesnesi ile 'this' aynı // olduğu durumda, aşağı satırdaki 'free()' çağrısı ile 'mpa'nın gösterdiği bellek bloğunu geri verdiğimiz // zaman artık 'mpa' göstericisi 'dangling' duruma düşecekler. Dolayısıyla bu durumdaki bir göstericiyi de // 'strlen()' içerisinde kullanmamız 'Tanımsız Davranış' a neden olacaktır. if(this == &other) { return *this; } m_id = other.m_id; std::free(mpa); mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); return *this; } // Some code here... private: int m_id; char* mpa; }; >> 'copy-swap idiom' : Detaylarını ileride göreceğimiz, 'Copy Ctor' ve 'Copy Assignment Function' unun beraber kullanıldığı bir deyim. >> 'Copy Ctor' ve 'Copy Assignment Function' unun ortak kodlarının tek yerde toplanması: * Örnek 1, ilk hali; // some code here... class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) : m_id{id} // Parametreli Ctor. { mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } Person(const Person& other) : m_id{other.m_id} // Copy Ctor { m_id = other.m_id; mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } // Copy Assignment Function. Bu şekilde yazmak zorunda değiliz. Başka yöntemlerde vardır. Person& operator=(const Person& other) { // Protection against 'Self Assignment'. Eğer bu korumayı yapmasaydık, 'other' nesnesi ile 'this' aynı olduğu // durumda, aşağı satırdaki 'free()' çağrısı ile 'mpa'nın gösterdiği bellek bloğunu geri verdiğimiz zaman artık // 'mpa' göstericisi 'dangling' duruma düşecekler. Dolayısıyla bu durumdaki bir göstericiyi de 'strlen()' // içerisinde kullanmamız 'Tanımsız Davranış' a neden olacaktır. if(this == &other) { return *this; } m_id = other.m_id; std::free(mpa); mpa = static_cast(std::malloc(std::strlen(other.m_id) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); return *this; } ~Person() // Dtor { release_resources(); } void print()const { // Imagine that the data members were being printed to the console. } private: int m_id; char* mpa; }; * Örnek 2, class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) // Parametreli Ctor. { deep_copy(id, p); } Person(const Person& other) // Copy Ctor { deep_copy(other); } // Copy Assignment Function. Bu şekilde yazmak zorunda değiliz. Başka yöntemler de vardır. Person& operator=(const Person& other) { // Protection against 'Self Assignment'. Eğer bu korumayı yapmasaydık, 'other' nesnesi ile 'this' aynı olduğu durumda, // aşağı satırdaki 'free()' çağrısı ile 'mpa'nın gösterdiği bellek bloğunu geri verdiğimiz zaman artık 'mpa' göstericisi // 'dangling' duruma düşecekler. Dolayısıyla bu durumdaki bir göstericiyi de 'strlen()' içerisinde kullanmamız // 'Tanımsız Davranış' a neden olacaktır. if(this == &other) { return *this; } release_resources(); return deep_copy(other); } ~Person() // Dtor { if(mpa) std::free(mpa); } void print()const { // Imagine that the data members were being printed to the console. } private: Person& deep_copy(const Person& other) { m_id = other.m_id; mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } void deep_copy(int i, const char* p) { m_id = i; mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } void release_resources() { std::free(mpa); } int m_id; char* mpa; }; >> Big-Three : 'Destructor', 'Copy Ctor' ve 'Copy Assignment' üçlüsünden meydana geliyor. Eğer bunlardan bir tanesini biz yazıyorsak, diğer iki tanesini de yazmalıyız ki çalışma zamanında veya diğer senaryolarda başımız ağrımasın. Örneğin, yukarıdaki örnekler, bu senaryolar için birer örnek niteliğindedir. >> Big-Five: 'Destructor', 'Copy Ctor', 'Copy Assignment', 'Move Ctor' ve 'Move Assignment' beşlisinden meydana gelmektedir. Bunlardan 'Big-Three' kapsamında olanlar için kural hala geçerli. Yani 'Big-Three'den bir tanesini yazıyorsak, diğer iki tanesini de yazmalıyız. Fakat 'Move Ctor' ve 'Move Assignment' için kural biraz daha farklı. Bu ikisinin yazılması, ki buna 'Move Semantics' de denir, genel olarak verim ile alakalıdır. Bir diğer deyişle, "KOPYALAMANIN YAPILDIĞI YERDE, TAŞIMA YAPILMASI DAHA UYGUN İSE", 'Move Ctor' ve 'Move Assignment' bizler yazmalıyız. >> DİKKAT: >>> Copy Ctor bloğu içerisinde ve/veya Ctor Init List içerisinde kopyalanmasını ihmal ettiğimiz 'data members' lar 'default initialize' edilirler. Yani 'primitive' türler 'garbage-value' ile hayata gelirken, sınıf türden nesneler için de 'default ctor' çağrılır. Dolayısıyla bütün 'data member' ları birbirine gerektiği gibi atamalıyız. (İlgili nesnenin 'otomatik' ömürlü olduğu varsayıldı) * Örnek 1, class Myclass{ public: Myclass(const Myclass& other) // 'ptr' isimli 'data member' için bizler 'deep-copy' yapılması için 'Copy Ctor' yazdık. { // Only 'ptr' is copied. // Diğer veri elemanlarına dokunulmadığı için onlar 'garbage-value' ile hayata gelirler. } private: int a; int b; float* ptr; }; >>> Copy Assignment Function içerisinde ataması yapılmamış 'data members' lar ATANMAMIŞ OLARAK kalırlar. >> 'Move Semantics' : Hayatı biteceği garanti olan bir nesnenin kaynağını 'çalarak' yeni bir nesne oluşturulmasına ve/veya atama yapılmasına denir. >> O sınıf türünden oluşturulan geçici nesnelerin türü 'R-Value' kategorisindedir. Aynı zamanda bir sınıf türünden değer döndüren fonksiyonların geri dönüş değerlerinin türü de 'R-Value' kategorisindedir. * Örnek 1, // Some code here... class Myclass{}; Myclass foo(); int main() { Myclass& r = foo(); // SENTAKS HATASI Myclass&& rr = foo(); // LEGAL const Myclass& cr = foo(); // LEGAL Myclass mx; Myclass&& rrMx = mx; // SENTAKS HATASI Myclass&& rrTemp = Myclass{}; // LEGAL } >>> 'Move Ctor' biz yazarsak; * Örnek 1, // Some code here... class Person{ public: // Some code here... Person(Person&& other) : m_id{other.m_id}, mpa{other.mpa} // Move Ctor. { other.mpa = nullptr; } private: int m_id; char* mpa; }; >>> 'Move Assignment Function' biz yazarsak; * Örnek 1, // Some code here... class Person{ public: // Some code here... Person& operator=(Person&& other) // Move Assignment Function. { if(this == &other) return *this; std::free(mpa); m_id = other.m_id; mpa = other.mpa; other.mpa = nullptr; return *this; } private: int m_id; char* mpa; }; >>> 'std::move()' : Argüman olarak aldığı SOL TARAF parametreyi, SAĞ TARAFA ÇEVİRMEKTEDİR. >>> Gerek 'Move Ctor', gerek 'Move Assignment Function' bir sınıf için "VAR DEĞİL" ise, yani 'not-declared' statüsündeyse, bunların yerine 'Copy Assignment' ve 'Copy Assignment Function' çağrılacak eğer onlar da "VAR" ise ve 'delete' EDİLMEMİŞSE. /*============================================================================================================*/ (09_10_10_2020) > Sınıflar (devam) : >> Eğer bir nesne hayata gelirken 'L-Value Expression' kullanılırsa, o nesneye ait 'Copy Constructor' çağrılır. Benzer şekilde eğer bir 'R-Value Expression' kullanılırsa, 'Move Constructor' çağrılır. >> Eğer hayatta olan bir nesneye atama yapılırken 'L-Value Expression' kullanılırsa, o nesneye ait 'Copy Assignment Funciton' çağrılır. Benzer şekilde eğer bir 'R-Value Expression' kullanılırsa, 'Move Assignment Funciton' çağrılır. >> 6 'Special Member Funcitons', şu aşağıdaki statülerden bir tanesinden olmak zorundadır; >>> 'Not Declared' : Bu durumda olan işlevler için, "Böyle bir işlev yoktur." diyebiliriz. >>> 'Implicitly Declared' : Derleyicinin, kullanıcıdan gelen talebe bakmaksızın durumdan vazife çıkartarak, işlev/işlevleri bildirmesidir. Bu grup da kendi içinde ikiye ayrılır. >>>> 'Implicitly Declared, but Defaulted' : Derleyicinin ilgili işlev ve işlevlerin kodunun yazması durumudur. >>>> 'Implicitly Declared, but Deleted' : Derleyicinin ilgili işlev ve işlevlerinin kodunu yazarken SENTAKS HATASI ile karşılması durumunda, o işlev ve işlevleri 'delete' etmesi durumudur. >>> 'User Declared' : Kullanıcının kendisinin bizzat bildirdiği işlev/işlevlerdir. Bu grup da kendi içinde üçe ayrılır. >>>> 'User Declared, but Deleted' : Kullanıcının kendisinin bizzat 'delete' ettiği işlev ve işlevlerdir. >>>> 'User Declared, but Defaulted' : Kullanıcının bildirdiği fakat tanımını DERLEYİCİYE YAPTIRDIĞI işlev ve işlevlerdir. >>>> 'User Declared, but User Defined' : Kullanıcının bildirdiği ve tanımının da bizzat KENDİSİNİN YAPTIĞI işlev ve işlevlerdir. >>> 'Default Ctor' için yukarıdaki statülere ait örnekler, * Örnek 1, // Some code here... class A { }; // 'Default Ctor' burada 'Implicitly Declared, but Defaulted' durumundadır. * Örnek 2, // Some code here... class A { A(int); }; // 'Default Ctor' burada 'Not Declared' durumundadır. Çünkü PARAMETRELİ CTOR bildirildiğinde, 'Default Ctor' bildirilmez. * Örnek 3, // Some code here... class A { const int x; }; // 'Default Ctor' burada 'Implicitly Declared, but Deleted' durumundadır. Çünkü ilgili işlevin tanımını yazarken, 'const' bir // değişkene ilk değer verilmediğinden, SENTAKS HATASI oluşacak. Bundan dolayı da bu işlev 'delete' edilecektir. 'const' BİR // 'data member' ya 'In Class Init.' ile yada 'Ctor Init. List' ile ilk değer verilmelidir. * Örnek 4.1, // Some code here... class A { A(); }; // 'Default Ctor' burada 'User Declared' durumundadır. TANIMINI DERLEYİCİ YAZMAYACAKTIR. BİZİM BİZZAT KENDİMİZ YAZMAMIZ GEREKİYOR. * Örnek 4.2, // Some code here... class A { A() {} }; // 'Default Ctor' burada 'User Declared' durumundadır. TANIMINI KULLANICI KENDİSİ YAZMIŞTIR. * Örnek 5, // Some code here... class A { A() = default; }; // 'Default Ctor' burada 'User Declared, but Defaulted' durumundadır. İlgili işlevin tanımını DERLEYİCİ YAZACAKTIR. * Örnek 6, // Some code here... class A { A() = delete; }; // 'Default Ctor' burada 'User Declared, but Deleted' durumundadır. >>> 6 'Special Member Funcitons' hangi durumda derleyici tarafından 'default' edildiği, hangi durumda 'delete' edildiğini gösteren tablo: >>>> Eğer kullanıcı hiç bir şekilde bu altı özel üye fonksiyonu bildirmez ise, DERLEYİCİ BÜTÜN HEPSİNİ 'default' EDER, HEPSİNİ KENDİSİ YAZAR. >>>> Eğer kullanıcı sadece 'Parametreli Ctor' bildirirse, >>>>> 'Default Ctor' un durumu 'Not Declared' olur. >>>>> Geri kalan beş özel üye fonksiyon ise derleyici tarafından 'default' edilir. >>> Eğer kullanıcı sadece 'Default Ctor' bizzat kendisi bildirirse, >>>> 'Default Ctor' un durumu 'User Declared' olur. >>>> Geri kalan beş özel üye fonksiyon derleyici tarafından 'default' edilir. >>> Eğer kullanıcı sadece 'Destructor' bizzat kendisi bildirirse, >>>> Racon gereği kullanıcı 'Copy Ctor' ve 'Copy Assignment Funciton' ını da bizzat kendisi bildirmelidir. >>>> Dilin kuralları gereği, >>>>> 'Default Ctor', derleyici tarafından 'default' edilir. >>>>> 'Copy Ctor' ve 'Copy Assignment Funciton', derleyici tarafından 'default' edilir ki bu çalışma zamanında sorunlara yol açabilir. Dolayısıyla bu ikisini bizlerin yazması gerekebilir. >>>>> 'Move Ctor' ve 'Move Assignment Funciton' ise 'Not Declared' hükmündedir. >>> Eğer kullanıcı sadece 'Copy Ctor' bizzat kendisi bildirirse, >>>> 'Default Ctor' un durumu 'Not Declared' olur. Çünkü bildirilen bu işlev de bir 'Parametreli Ctor'. >>>> Racon gereği 'Destructor' ve 'Copy Assignment Funciton' ını da bizzat kendisi bildirmelidir. >>>> Dilin kuralları gereği, >>>>> 'Destructor' ve 'Copy Assignment Funciton', derleyici tarafından 'default' edilir ki bu çalışma zamanında sorunlara yol açabilir. >>>>> 'Move Ctor' ve 'Move Assignment Funciton' ise 'Not Declared' hükmündedir. >>> Eğer kullanıcı sadece 'Copy Assignment Function' bildirirse, >>>> 'Default Ctor', derleyici tarafından 'default' edilir. >>>> Racon gereği kullanıcı 'Destructor' ve 'Copy Ctor' ını da bizzat kendisi bildirmelidir. >>>> Dilin kuralları gereği, >>>>> 'Destructor' ve 'Copy Ctor', derleyici tarafından 'default' edilir ki bu çalışma zamanında sorunlara yol açabilir. >>>>> 'Move Ctor' ve 'Move Assignment Funciton' ise 'Not Declared' hükmündedir. >>> Eğer kullanıcı sadece 'Move Ctor' bildirirse, >>>> 'Default Ctor' un durumu 'Not Declared' olur. Çünkü bildirilen bu işlev de bir 'Parametreli Ctor'. >>>> 'Destructor', derleyici tarafından 'default' edilir. >>>> 'Copy Ctor' ve 'Copy Assignment Funciton', derleyici tarafından 'delete' edilir. >>>> 'Move Assignment Funciton' un durumu ise 'Not Declared' olur. >>> Eğer kullanıcı sadece 'Move Assignment Function' bildirirse, >>>> 'Default Ctor' ve 'Destructor', derleyici tarafından 'default' edilir. >>>> 'Copy Ctor' ve 'Copy Assignment Funciton', derleyici tarafından 'delete' edilir. >>>> 'Move Ctor' un durumu ise 'Not Declared' olur. >>> # ÖZETLE # >>>> Eğer parametreli bir 'Ctor' var ise 'Default Ctor' durumu 'Not Declared' statüsündedir. >>>> Eğerki Rule of Three den birisini bildirirsek, >>>>> Geri kalan diğer ikisini derleyici 'default' eder. Fakat bu durum problemlere yol açabilir. DOLAYISIYLA EĞER 'Rule of Three' DEN BİRİSİNİ BİZ BİLDİRİYORSAK DİĞER İKİSİNİZDE BİZ YAZMALIYIZ. >>>>> 'Move Semantics' e ait fonksiyonlar 'Not Declared' statüsünde olur. >>>> Eğer 'Move Semantics' e ait fonksiyonlardan bir tanesini bildirirsek, >>>>> Diğer bildirilmeyenin durumu 'Not-Declared' olur. >>>>> 'Rule of Three' e ait fonksiyonlar 'deleted' olur ('Dtor' hariç. O 'defaulted'). >> Bir sınıf türünden nesnenin 'non-copyable' ve 'non-moveable' olmasını istiyorsak; >>> Yukarıdaki tabloya nazaran, ilgili sınıf türüne ait 'Copy Ctor' ı bildireceğiz fakat 'delete' edeceğiz. Böylelikle 'Copy Ctor' a yapılan çağrı sentaks hatası olacak. 'Copy Ctor' bildirdiğimiz için de 'Move Ctor' un ve 'Move Assignment Funciton' ların durumu 'Not Declared' olacak. Fakat 'Copy Assignment Funciton' derleyici tarafından 'default' edileceğinden, bizlerin onu da BİLDİRMEMİZ FAKAT 'delete' ETMEMİZ gerekiyor. * Örnek 1, // some code here... class A{ public: A(const A&) = delete; A& operator=(const A&) = delete; }; // Artık yukarıdaki sınıfımız taşımaya ve kopyalamaya kapalıdır. >> Bir sınıf türünden nesnenin 'non-copyable' ama 'moveable' olmasını istiyorsak; >>> Yukarıdaki tabloya nazaran, sadece 'Move Ctor' ve 'Move Assignment Funciton' ları bildirirsek, 'Copy Ctor' ve 'Copy Assignment Function' lar 'deleted' durumunda olacaklar. Böylelikle sınıfımız sadece taşınabilir olacak. * Örnek 1, #include class A{ public: A() = default; A(A&& other) { std::cout << "Move Ctor was called.\n"; } A& operator=(A&& other) { std::cout << "Move Assignment Function was called.\n"; return *this; } }; // note: ‘constexpr A::A(const A&)’ is implicitly declared as deleted because ‘A’ declares a move constructor // or move assignment operator int main() { A ax; A bx{ax}; // use of deleted function ‘constexpr A::A(const A&)’ A cx; cx = ax; // use of deleted function ‘A& A::operator=(const A&)’ return 0; } >> BİR MÜLAKAT SORUSU: Aşağıdaki örneği inceleyiniz. * Örnek 1, // Some code here... class A{ /*...*/ }; void func(A&& other); Soru: Yukarıdaki 'func' isimli fonksiyonun amacı sizce nedir? Cevap: Argüman olarak geçilen nesneyi 'TAŞIMAK'. Çünkü, büyük ihtimalle fonksiyonun tanımında bir yerde 'std::move()' a çağrı yapılmıştır. void func(A&& other) { //.. A ax = std::move(other); // NOT: 'func' fonksiyona geçilen ifade bir 'R-Value Expression'. Fakat fonksiyon bloğu içerisinde bu ifadenin // karşılığı olan 'other' bir 'L-Value Expression'. Çünkü isimli değişkenler 'L-Value Expression' hükmündedir. // Dolayısıyla aşağıdaki gibi bir kullanımda, 'Move Ctor' çağrılmayacaktır. /* //.. A bx = other; // Teknik olarak bir engel yok ise 'Copy Ctor' çağrılacaktır. //.. */ // İşte bu sebepten dolayı bizler 'other' nesnesini tekrardan 'R-Value Expression' çeviriyoruz. } * Örnek 2, #include class A{ public: A() = default; A(const A& other) { std::cout << "Copy Ctor was called. The address of the obj: [" << this << "]\n"; } A& operator=(const A& other) { std::cout << "Copy Assignment Function was called.\n"; return *this; } A(A&& other) { std::cout << "Move Ctor was called. The address of the obj: [" << this << "]\n"; } A& operator=(A&& other) { std::cout << "Move Assignment Function was called.\n"; return *this; } }; void foo(A&& other) { A bx{other}; // ii. Copy Ctor was called. The address of the obj: [0x7ffe1fc0ccd6] std::cout << "The address of [bx]: [" << &bx << "]\n"; // iii. The address of [bx]: [0x7ffe1fc0ccd6] A cx{std::move(other)}; // IV i. Move Ctor was called. The address of the obj: [0x7ffe1fc0ccd7] std::cout << "The address of [cx]: [" << &cx << "]\n"; // V i. The address of [cx]: [0x7ffe1fc0ccd7] } int main() { /* # OUTPUT # The address of [ax]: [0x7ffe1fc0ccf7] Copy Ctor was called. The address of the obj: [0x7ffe1fc0ccd6] The address of [bx]: [0x7ffe1fc0ccd6] Move Ctor was called. The address of the obj: [0x7ffe1fc0ccd7] The address of [cx]: [0x7ffe1fc0ccd7] */ A ax; std::cout << "The address of [ax]: [" << &ax << "]\n"; // i. The address of [ax]: [0x7ffe1fc0ccf7] foo(std::move(ax)); return 0; } // Örnekten de anlaşıldığı üzere, isimli bir değişkenler 'L-Value Expression' hükmündedir. Dolayısıyla bizlerin // 'Move Ctor' çağırmamız için, bunları 'R-Value Expression' haline getirmemiz gerekiyor. >> 'Conversion Ctor' : Sınıfın tek parametreli 'Ctor' ları, sınıf türünden olmayan bir değeri, ilgili sınıf türüne dönüştürme özelliğine sahiptir. Bu tip 'Ctor' lara verilen isimdir. Yapılan işlem derleyici tarafından yapılıyor olup, örtülü bir işlemdir. * Örnek 1, // some code here... class A{ public: A() = default; A(int); // I }; int main() { // Eğer 'I' numaralı 'Ctor' bildirilmeseydi, aşağıdaki atama/ilk değer verme işlemleri SENTAKS HATASINA NEDEN OLACAKTI. int ival = 100; A ax(ival); // SENTAKS HATASI. A bx; bx = ival; // SENTAKS HATASI. // Eğer 'I' numaralı 'Ctor' bildirilseydi, aşağıdaki atama/ilk değer verme işlemleri LEGAL olacaktı. int ival = 100; A ax(ival); // LEGAL A bx; bx = ival; // LEGAL // A gn(ival); // bx = gn; } * Örnek 2, #include class A{ public: A() { std::cout << "A::A() was called. address is : [" << this << "]\n"; } A(int x) { std::cout << "A::A(int x) was called. x is : [" << x << "], address is : [" << this << "]\n"; } ~A() { std::cout << "A::~A() was called. address is : [" << this << "]\n"; } }; int main() { /* # OUTPUT # A::A(int x) was called. x is : [100], address is : [0x7ffd7f81757d] address of [ax] : 0x7ffd7f81757d A::A() was called. address is : [0x7ffd7f81757e] address of [bx] : 0x7ffd7f81757e A::A(int x) was called. x is : [200], address is : [0x7ffd7f81757f] A::~A() was called. address is : [0x7ffd7f81757f] A::~A() was called. address is : [0x7ffd7f81757e] A::~A() was called. address is : [0x7ffd7f81757d] */ int ival = 100; A ax = ival; // i. A::A(int x) was called. x is : [100], address is : [0x7ffd7f81757d] std::cout << "address of [ax] : " << &ax << "\n"; // ii. address of [ax] : 0x7ffd7f81757d int ivalTwo = 200; A bx; // iii. A::A() was called. address is : [0x7ffd7f81757e] std::cout << "address of [bx] : " << &bx << "\n"; // IV i. address of [bx] : 0x7ffd7f81757e bx = ivalTwo; // V i. A::A(int x) was called. x is : [200], address is : [0x7ffd7f81757f] // VI i. A::~A() was called. address is : [0x7ffd7f81757f] => 'bx' e atanan geçici nesnenin hayatı sonlandı. // VII i. A::~A() was called. address is : [0x7ffd7f81757e] => 'bx' nesnesinin hayatı sonlandı. // VIII i. A::~A() was called. address is : [0x7ffd7f81757d] => 'ax' nesnesinin hayatı sonlandı. } * Örnek 3, #include class A{ public: A() { std::cout << "A::A() was called. address is : [" << this << "]\n"; } A(const A& other) { std::cout << "A::A(const A& other) was called. address is this : [" << this << "]. address of other : [" << &other << "]\n"; } A& operator=(const A& other) { std::cout << "A::A& operator=(const A& other) was called. address is this : [" << this << "]. address of other : [" << &other << "]\n"; return *this; } A(int x) { std::cout << "A::A(int x) was called. x is : [" << x << "], address is : [" << this << "]\n"; } ~A() { std::cout << "A::~A() was called. address is : [" << this << "]\n"; } }; int main() { /* # OUTPUT # === Initializing an object, using a primitive type === A::A(int x) was called. x is : [100], address is : [0x7ffce5bc389d] address of [ax] : 0x7ffce5bc389d === Assigning a primitive type to an object === A::A() was called. address is : [0x7ffce5bc389e] address of [bx] : 0x7ffce5bc389e A::A(int x) was called. x is : [200], address is : [0x7ffce5bc389f] A::A& operator=(const A& other) was called. address is this : [0x7ffce5bc389e]. address of other : [0x7ffce5bc389f] A::~A() was called. address is : [0x7ffce5bc389f] === End of the 'main()' === A::~A() was called. address is : [0x7ffce5bc389e] A::~A() was called. address is : [0x7ffce5bc389d] */ std::cout << "=== Initializing an object, using a primitive type === \n"; int ival = 100.234; A ax = ival; std::cout << "address of [ax] : " << &ax << "\n"; std::cout << "\n=== Assigning a primitive type to an object === \n"; double dval = 200.002; A bx; std::cout << "address of [bx] : " << &bx << "\n"; bx = dval; std::cout << "\n=== End of th e 'main()' === \n"; // Yukarıdaki örnekte iki adet örtülü dönüşüm vardır; // - 'double' to 'int', standart conversion // - 'int' to 'A', user-defined conversion } >>> Artık bu dönüşüm türüne, 'Function Overloading' sırasında da gördüğümüz gibi, 'user-defined conversion' denmektedir. Çünkü bu dönüşümün sağlanabilmesi için ilgili 'Ctor' un tanımlanması gerekmektedir. Öyle bir işlev olmasaydı, böyle bir dönüşüm de olmayacaktı (NOT: Bir diğer tür dönüştürme fonksiyonuna da 'type-cast operator function' denir.). >>> Fakat bu kurucu işlev de tehlikelere gebe olabilir. * Örnek 1, //.. class Myclass{ Myclass() = default; Myclass(bool); }; int main() { Myclass* p = nullptr; Myclass mx; Myclass mp; //.. mx = p; // Yukarıdaki atama işleminde bizim niyetimiz 'mp' nesnesini atamaktı fakat yanlışlıkla 'p' göstericisini atadık. Bu // durumda derleyici 'gösterici' türünü 'bool' türüne örtülü olarak çevirdi. Sonrasında da bizim bildirdiğimiz 'Ctor' // ile de 'bool' türünden 'Myclass' türüne çevirdi. Bu durumda önce 'implicit conversion', sonrasında da // 'user-defined conversion' yapıldı. // A ‘prvalue’ of integral, floating-point, unscoped enumeration, pointer, and pointer-to-member types can be converted // to a ‘prvalue’ of type ‘bool’. // https://en.cppreference.com/w/cpp/language/implicit_conversion } >>> İş bu kurucu işlevlerin parametresinin türünün 'int' olması önemli değil. 'double' da olabilir, diğer primitive türler de olabilir, 'class-type' türler de olabilir. Önemli olan tek parametreli olması. >> 'explicit' : Yukarıda detayları ile anlatılan 'Conversion Ctor' bünyesindeki ÖRTÜLÜ DÖNÜŞÜMÜ YAPMAYAN, sadece harici olarak 'type-casting' operatörleri ile dönüşüme izin verir eğer bu ismi 'Ctor.' için kullanırsak. * Örnek 1, //.. class Myclass{ public: Myclass() = default; explicit Myclass(int); // Explicit Ctor }; int main() { Myclass mx; mx = 100; // Artık SENTAKS HATASI. mx = static_cast(100); // LEGAL } * Örnek 2, class Myclass{ public: Myclass() = default; explicit Myclass(int); // Explicit Ctor }; void foo(Myclass mx); int main() { int ival = 300; foo(ival); // i. 'int' parametreli 'Ctor' umuz 'explicit' olarak betimlendiği için artık yukarıdaki fonksiyon çağrısı SENTAKS HATASI olacaktır. // ii. Eğer 'explicit' olarak betimlemezsek, derleyici, 'int' türünden 'Myclass' türüne ilgili 'Ctor' u kullanarak dönüşüm yapacaktı. } >>> SINIFLARIMIZDAKİ BÜTÜN 'Tek Parametreli Ctor' LARI 'explicit' OLARAK BETİMLEMELİYİZ Kİ İSTENMEYEN HATALAR OLUŞMASIN. >>> Fakat bu tip 'Ctor' a sahip sınıflara ilk değer verirken 'Copy Initialization' yönteminin kullanılması SENTAKS HATASINA yol açar. * Örnek 1, //.. class Myclass { public: explicit Myclass(int); }; int main() { // Via Copy Init. Myclass m1 = 100; // Sentaks hatası. // Via Direct Init. Myclass m2(200); // Legal. // Via Direct-List Init. since c++11 Myclass m3{300}; // Legal. } >>> Aynı parametrik yapıda fakat 'explicit' olmayan bir 'Ctor' ile beraber kullanıldığında, 'explicit' olan kurucu işlev, 'Function Overload Resolution' a katılmıyor. * Örnek 1, //.. class Myclass{ public: explicit Myclass(int); // I Myclass(double); // II }; int main() { Myclass mx = 20; // Yukarıdaki ilk değer verme işlemi LEGALDİR. Çünkü 'I' numaralı kurucu işlev, 'Function Overload Resolution' a // KATILMAMAKTADIR. 'II' numaralı kurucu işlev çağrılacaktır. } >>> 'Default Ctor' un 'explicit' olma senaryosu: * Örnek 1, //.. class Myclass{ public: explicit Myclass(); }; Myclass func() { return {}; // İş bu 'Default Ctor' 'explicit' olarak nitelendiği için artık bu ilk değer verme GEÇERSİZ. } int main() { Myclass mx = {}; // İş bu 'Default Ctor' 'explicit' olarak nitelendiği için artık bu ilk değer verme GEÇERSİZ. } >>> İki parametreli 'Ctor' un 'explicit' olma senaryosu: * Örnek 1, //.. class Myclass{ public: explicit Myclass(int, int); }; int main() { Myclass mx{12,45}; // HALA GEÇERLİ Myclass my = {12, 45}; // İş bu 'Ctor', 'explicit' olarak nitelendiği için, artık bu ilk değer verme GEÇERSİZ. } >> 'Geçici Nesneler' : İsimlendirilmiş bir nesne, ASLA BİR GEÇİCİ NESNE DEĞİLDİR. Tanım olarak, "Kaynak kodda doğrudan ismi olmayan // ancak çalışan kodda hayata getirilen nesnelerdir", diyebiliriz. >>> Derleyici tarafından otomatik oluşturulan geçici nesneler: * Örnek 1, //.. int main() { int ival = 100; double dval = 45.54; auto n = ival + dval; // İşte burada bir 'geçici nesne' vardır. // # Psuedo Code # // double gn(ival); // n = gn + dval; } * Örnek 2, //.. class A{ public: A() = default; A(int); }; int main() { A ax; ax = 5; // İşte burada bir 'geçici nesne' vardır. // # Psudo Code # // A gn(5); // ax = gn; } >>> Programcının talimatı doğrultusunda oluşturulan geçici nesneler: * Örnek 1, //.. class A{ public: A() = default; A(int); }; int main() { A(12); // i. Sınıf türünün adını yazıyoruz => ...A... // ii. İlgili sınıf isminin yanına // bir adet '()' çifti veya '{}' çifti yazıyoruz => ...A{}... // iii. '()' veya '{}' içerisine de o sınıfa ait // Kurucu İşlevlere geçilecek argümanları yazıyoruz. => ...A{25};... } >>> Peki bu geçici nesnelerin hayatı ne zaman başlayıp, ne zaman bitiyor? El-cevap : İçinde bulundukları ifadenin yürütülmesi sırasında hayata geliyorlar ve iş bu ifadenin yürütülmesinden sonra hayatları sona eriyor. * Örnek 1, //.. class A{ public: A() = default; A(int x) { std::cout << "A::A(int x) was called." << "The 'x' is : " << x << ", The address : " << this << "\n"; } ~A() { std::cout << "A::~A() was called. The address : " << this << "\n"; } }; int main() { /* # OUTPUT # main() started. Address of 'ax' : 0x7ffcee5f2aa6 A::A(int x) was called.The 'x' is : 35, The address : 0x7ffcee5f2aa7 A::~A() was called. The address : 0x7ffcee5f2aa7 main() ended. A::~A() was called. The address : 0x7ffcee5f2aa6 */ std::cout << "main() started.\n"; A ax; std::cout << "Address of 'ax' : " << &ax << "\n"; ax = A{35}; //.. std::cout << "main() ended.\n"; } >>> Peki programcılar neden 'geçici nesne' leri kullanmalılar? El-cevap : Bir defaya mahsus kullanılacak değişkenler için isimlendirilmiş bir değişken kullanmamak için. * Örnek 1, //.. class A{}; void func(A ax) { //.. } void foo(std::vector& aVec) { //.. } int main() { // Senaryo I A ax; func(ax); // Eğer 'ax' ismi sadece 'func()' fonksiyonuna argüman olarak geçmek için oluşturduysa, yani kodun geri kalan // kısmında kullanılmayacaksa, bu durum 'scope leakage' a neden olabilir. Çünkü kodun geri kalan kısmında // yanlışlıkla 'ax' ismini kullanabiliriz. std::vector avec(100); // Senaryo I.I: // 100 elemanlı bir vektör oluşturuldu. Oluşturulma amacı sadece 'foo()' fonksiyonuna argüman olarak geçilmek. foo(avec); // 'avec' ismi kodun geri kalanında kullanılmayacağından, programın sonuna kadar bu vektör boş yere yer // kaplayacaktır. Yine yukarıdaki senaryodaki gibi 'scope leakage' da neden olabilir. func(A{35}); // Senaryo II // Ortada bir isim olmadığından, 'scope leakage' a da neden olacak bir durum yoktur. } >>> Geçici nesne oluşturan ifadelerin değer kategorileri 'PR-Value Expression' şeklindedir. Bundan sebeptir ki bunları ya 'const L-Value Referance' yada 'R-Value Referance' bağlayabiliriz. * Örnek 1, //.. class A{ //.. }; void f1(A); void f2(A&); void f3(const A&); void f4(A&&); int main() { f1(A{}); // Legaldir ve Call-By-Value kullanılmıştır. f2(A{}); // Sentaks hatasıdır. 'non-const L-value Referance' lara 'R-Value Expression' BAĞLANAMAZ. f3(A{}); // Legaldir. 'const L-value Referance' lar, 'R-Value Expression' lara BAĞLANABİLİR. f4(A{}); // Legaldir. Çünkü 'R-Value Referance' lar, 'R-Value Expression' lara BAĞLANABİLİR. } >>> Yukarıda da görüldüğü gibi geçici nesneler uygun referanslara bağlanabiliyor. Peki bu durumda ömürleri ne hale geliyor? Tabii ki refere eden referansın hayatına bağlı oluyor. Artık içinde kullanılan ifadenin bitmesinden sonra değil, refere eden referansın hayatının bitmesi ile bu geçici nesnelerin hayatları sona eriyor. * Örnek 1, //.. class A{ //.. }; int main() { A{}; // Geçici nesne burada hayata geldi. // Geçici nesnenin hayatı artık burada sonlanmış oldu. { A const & ra = A{}; // Geçici nesne burada hayata geldi ve 'ra' isimli referansa bağlandı. // Artık geçici nesne burada hayatta çünkü 'ra' isimli referansın hayatı burada sonlanmış değil. } // 'ra' isimli referansın hayatı burada sonlanmış olduğundan, geçici nesnenin de hayatı burada sonlanmış oldu. } * Örnek 2, #include class A{ public: A() { std::cout << "A::A() was called." << "\n"; } A(int x) { std::cout << "A::A(int x) was called. x is " << x << "\n"; } ~A() { std::cout << "A::~A() was called." << "\n"; } void print(void) const { std::cout << "A::print(void) was called.\n"; } }; A B(void) { std::cout << "A B(void) was called.\n"; return A{}; } const A& refA = B(); int main() { /* # OUTPUT # A B(void) was called. A::A() was called. A::print(void) was called. A::~A() was called. */ refA.print(); } * Örnek 3, #include class A{ public: A() { std::cout << "A::A() was called." << "\n"; } A(int x) { std::cout << "A::A(int x) was called. x is " << x << "\n"; } ~A() { std::cout << "A::~A() was called." << "\n"; } void print(void) const { std::cout << "A::print(void) was called.\n"; } }; void foo(const A&) { std::cout << "foo was called.\n"; } void func(const A& other) { std::cout << "func was called.\n"; foo(other); } int main() { /* # OUTPUT # main started. A::A() was called. func was called. foo was called. A::~A() was called. main ended. */ std::cout << "main started.\n"; func(A{}); std::cout << "main ended.\n"; } * Örnek 4, #include class A{ public: A() { std::cout << "A::A() was called." << "\n"; } A(int x) { std::cout << "A::A(int x) was called. x is " << x << "\n"; } ~A() { std::cout << "A::~A() was called." << "\n"; } void print(void) const { std::cout << "A::print(void) was called.\n"; } }; const A& foo(const A& other) { std::cout << "foo was called.\n"; return other; } int main() { /* # OUTPUT # main started. A::A() was called. foo was called. A::~A() was called. inside if-statement. main ended. */ std::cout << "main started.\n"; if(1) { const A& r = foo(A{}); std::cout << "inside if-statement.\n"; } std::cout << "main ended.\n"; // Buradan da gördüğümüz gibi, fonksiyonun geri dönmnesinden evvel bizim nesnemizin hayatı sonra erdi. Artık 'r' // isimli referansımız hayatı sona ermiş bir nesneyi refere etmektedir. ÇÜNKÜ 'life extension' OLMASI İÇİN İSİMLE // BİR BAŞKA REFERANSA AKTARMAM LAZIM. FONKSİYON ÇAĞRISI İSİMLENDİRİLMİŞ BİR NESNE OLMADIĞINDAN DOLAYI, YUKARIDAKİ // GİBİ 'life extension' SONRA ERDİ. } >> Sınıfların 'static' veri elemanları ve 'static' üye fonksiyonları: Genel olarak tekrarlamak gerekirse sınıflarımız şu üç farklı grubu bünyesinde barındırabilir; >>> 'data members' : Bunlar da kendi içinde iki gruba ayrılırlar. >>>> 'static data members' >>>> 'non-static data members' >>> 'member functions' : Bunlar da kendi içinde iki gruba ayrılırlar. >>>> 'static member functions' >>>> 'non-static member functions' >>> 'nested-types / type-member / member-type' >>> Bunları tek bir çatı altında toplarsak, * Örnek 1, //.. class Myclass{ // data members // static ones : // non-static ones : Bu sınıf türünden her bir nesne ayrı ayrı bu tip 'member' lara sahip. Dolayısıyla 'sizeof()' // çağrısına etki eder. // member functions // static ones // non-static ones : Bu sınıf türünden her bir nesne için sadece bir tane var fakat gizli parametre olarak BU SINIF // TÜRÜNDEN BİR ADRES ALMAKTA ki o adres ise 'this' göstericisine bağlanıyor. Dolayısıyla 'sizeof()' çağrısına etki // etmez. // nested-types / type-member / member-type }; >>> 'static data members' özellikleri: >>>> Sınıf nesneleri ile bir alakaları yoktur, onlardan bağımsızdırlar. >>>> C dilindeki 'global namespace' alanındaki değişkenler gibidirler. >>>> 'static' ömürlüdürler. Dolayısıyla, C/C++ dilindeki 'global namespace' alanındaki değişkenler gibi, 'main()' çağrısı öncesinde hayata geliyorlar ve 'main()' den sonra hayatı sona eriyor. >>>> Sınıf içerisinde olduklarından, 'class scope' içerisindedirler. Bundan dolayı da, 'access control' e tabii olurlar. >>>> ASSEMBLY DÜZEYİNE 'global namespace' LER İLE BİR FARKI YOKTUR. DİL DÜZEYİNDEKİ BİR DEĞİŞİKLİKTİR. >>>> Bildirim şekilleri ve tanımlamaları da şu şekilde, * Örnek 1, //.. // class.hpp class Myclass{ public: static int mval; // BU BİR BİLDİRİMDİR, TANIMLANMASI GEREKMEKTEDİR. AKSİ HALDE 'link' AŞAMASINDA HATA ALIRIZ. }; // class.cpp int Myclass::mval; // ARTIK BURADA TANIMLANMIŞTIR. 'default initialization' edildiğinden dolayı, önce 'zero-initialize' EDİLMİŞTİR. int Myclass::mval{}; // ARTIK BURADA TANIMLANMIŞTIR. 'value initialization' edildiğinden dolayı, '0' değeri ile hayata gelmiştir. int Myclass::mval(10); // ARTIK BURADA TANIMLANMIŞTIR. 'direct-initialization' edildiğinden dolayı, '10' değeri ile hayata gelmiştir. int Myclass::mval{20}; // ARTIK BURADA TANIMLANMIŞTIR. 'direct-list-initialization' edildiğinden dolayı, '20' değeri ile hayata gelmiştir. int Myclass::mval = 30; // ARTIK BURADA TANIMLANMIŞTIR. 'copy initialization' edildiğinden dolayı, '30' değeri ile hayata gelmiştir. >>>> Bir sınıfın kendi türünden sadece 'static' bir 'data member' ı olabilir. Kendi türünden 'non-static' veri elemanı OLAMAZ, 'GÖSTERİCİLER' HARİÇ. Kendi sınıf türünden gösterici olabilir. * Örnek 1, //.. class Myclass{ static Myclass mx; // Legal Myclass* my; // Legal Myclass mz; // İLLEGAL }; >>> Farklı kaynak dosyalardaki bu türden olan data member lardan hangisinin önce hayata geleceği de, tıpkı 'global namespace' isim alanındaki değişkenler gibi, DERLEYİCİYE BAĞLIDIR. >>> Bu türden olan değişkenlere, sınıf tanımları içerisinde ilk değer veremeyiz! ('In-class Init' İLE SADECE 'non-static' VERİ ELEMANLARINA DEĞER VEREBİLİRİZ). * Örnek 1, //.. class Myclass{ static int mx = 100; // Sentaks hatasıdır. }; >>>>> Bunun bir istisnası, iş bu veri elemanının 'const' ve 'integral type' olmasıdır. * Örnek 1, //.. class Myclass{ const static int mx = 200; // LEGAL. }; > 'complete-type' ve 'incomplete-type' : Derleyicinin sadece bildirim ile karşılaştığı, tanımı ile karşılaşmadığı durumlar 'incomplete-type' olarak geçer. Eğer tanımı ile karşılaşırsa 'complete-type'. Peki bizler bu türler ile neler yapabilir, neler yapamayız?: >> 'incomplete-type' ile, >>> Fonksiyon bildirimlerinde geri dönüş değer türü ve/veya parametre değer türü olarak kullanabiliriz. * Örnek 1, //.. class Cenk; // incomplete-type, forward-decleration class Neco; // incomplete-type Cenk f1(Neco other); Cenk* f2(Neco* other); Cenk& f3(Neco& other); >>> Tür eş isim bildirimi yapabiliriz. * Örnek 1, //.. class Cenk; // incomplete-type typedef Cenk* CenkPtr; using CenkRef = Cenk&; >>> 'gösterici' ve 'referans' tanımlamalarında kullanılabilirler. Çünkü 'gösterici' ler, gösterdiği objeden bağımsız olarak, bellekte aynı boyutta yer kaplarlar. Yani 'Neco' sınıfından bir değişkenin adresini tutacak 'gösterici' ile 'char' türden bir değişkenin adresini tutacak 'gösterici' aynı boyutta yer kaplar. Fakat 'function-pointer' ile aynı boyutta yer kaplayacağına dair bir garanti YOKTUR. Ama 'function-pointer' lar da kendi içinde aynı boyutta yer kaplayacağı garanti altında. * Örnek 1, class Cenk; // incomplete-type class Memo foo(class Dedo* other); // Fonksiyon bildirimi. int main() { Cenk* ptr = nullptr; class Ahmo* ptrAhmo = nullptr; // 'Ahmo' sınıf türü için 'forward-decleration' yazmadığımız için, 'class/struct' anahtar sözcüğü ile birlikte yazmamız // gerekiyor. Bu durum fonksiyon bildirimlerinde de geçerli. Fakat bu 'göstericileri' DEREFENCE EDEMEYİZ, SENTAKS HATASIDIR. } * Örnek 2, //.. class Neco; // forward-decleration Neco* foo(); // Legal Neco& func(); // Legal int main() { Neco* p1 = foo(); // Legal Neco& r1 = func(); // Also legal. } >>> 'inheritence' mekanizmasında kullanılamaz. Mutlak suret kalıtımın yapıldığı yerde tanımı olması gerekiyor. * Örnek 1, class Aksu; class Mehmet : public Aksu { }; // Sentaks hatası. 'incomplete type' is not allowed. >>> 'extern' bildirimlerinde kullanılabilir. * Örnek 1, class Furkan; extern Furkan f; // İşte 'extern decleration' kısmı burasıdır. Bunun tanımı başka bir modülde yapılıyor. Legaldir. extern Furkan a[]; // Legaldir. >>> Sınıf tanımları içerisindeki 'static' 'data-member' bildirimlerinde kullanılabilir. * Örnek 1, //.. class A; // forward-decleration class Myclass{ static A ax; // Legal. }; >>> 'incomplete-type' TÜRLER İLE NESNE OLUŞTURAMAYIZ. * Örnek 1, //.. class A; class B; class C; class Myclass{ A ax; // Sentaks hatası. B bx; // Sentaks hatası. }; int main() { C cx; // Sentaks hatası. } // Yukarıdaki sentaks hatasını gidermek için iki yöntemimiz var: // i. İlgili sınıfların tanımlarının olduğu başlık dosyalarını '#include' etmek // ii. 'pimpl' deyimini kullanmak, ki böylelikle sınıfımızın 'private' bölümünü sınıf tanımından çıkartmış oluyoruz. >>> 'sizeof()' OPERATÖRÜNÜN OPERANDI YAPAMAYIZ. SENTAKS HATASIDIR (AYNI ZAMANDA 'void' TÜRÜ DE YAPILAMAZ ÇÜNKÜ O DA BİR 'incomplete-type'). >> 'incomplete-type' kullanarak çözeceğimiz problemleri, BAŞLIK DOSYASI EKLEYEREK ÇÖZMEKTEN KAÇINMALIYIZ. > 'C++Quiz' Sorusu, #include struct X{ X() { std::cout << "1"; } ~X() { std::cout << "2"; } X(const X&) { std::cout << "3"; } void f() { std::cout << "4"; } }object; int main() { // /* # Expected Output: 13242 # 1 'OBJECT' HAYATA GELDİ. 3 'GN' HAYATA GELDİ. 2 'GN' HAYATI BİTTİ. 4 'OBJECT' İLE MEMBER FONK. ÇAĞRILDI. 2 'OBJECT' HAYATI BİTTİ. */ // OUTPUT : 11422 X(object); object.f(); // # Açıklama # // Bir değişkeni tanımlarken, ismini '()' içerisinde yazmak LEGALDIR. // int (x); // Legal bir tanımlamadır. // Peki aşağıdaki gibi bir senaryoda durum nasıl olurdu? // X (object); // Burada karşımıza iki farklı anlam çıkmaktadır. // i. 'object' isimli, 'X' sınıf türünden bir değişken tanımlamak, ki bu durumda 'global namepsace' alanındaki 'object' isimli // değişken maskelenecek. // ii. 'X' sınıf türünden Geçici Nesne oluşturmak ve bunu yaparken 'global namepsace' alanındaki 'object' değişkenini kullanmak, // ki bu durumda da Geçici Nesne için 'Copy Ctor' çağrılacak. // Dilin kurallarına göre yukarıdaki senaryoda 'i.' anlam kullanılır. // YUKARIDAKİ İKİ ANLAMA GELEN SENARYOYU ORTADAN KALDIRMAK İÇİN, '()' YERİNE '{}' KULLANMALIYIZ. BÖYLELİKLE BİZ GEÇİCİ NESNE // OLUŞTURMUŞ OLACAĞIZ, YANİ SADECE 'ii' ANLAM OLACAK. } /*============================================================================================================*/ (10_11_10_2021) > 'inline' Variables : C++17 ile dile eklenmiş bir özelliktir. Başlık dosyasında tanımlanan değişkenlerin 'ODR' kurallarını ihlal etmemesini sağlar. Aksi halde o başlık dosyasını '#include' eden bütün kaynak dosyalarda bahsi geçen değişkenimizin tanımı olacağından, 'ODR' ihlal edilmiş olacaktır. * Örnek 1, //neco.hpp #include class A{ }; inline std::vector aVec; // 'inline' olarak betimlendiği için artık 'ODR' ihlal edilmemiş oluyor. >> Sınıfların 'static data members' larını sınıf içerisinde tanımlayabilmemiz için 'const' ve 'integral type' olma zorunluluğu vardır. Fakat bunları da 'inline' yaparsak bu zorunluluk ortadan kalkar. * Örnek 1, //.. class A{ static double mx{20}; // Normal şartlarda SENTAKS HATASI. Çünkü burada bir 'bildirim' yapılmakta. const static int my{30}; // İlgili 'static' değişkenimiz, 'const' ve 'integral-type' olduğu müddetçe, bu deyim LEGALDİR. inline static double {200.022}; // C++17'den itibaren bu deyim de artık LEGALDİR. Artık 'const' ve 'integral-type' olma // zorunluluğu yoktur. }; // Dipnot : Artık 'static' değişkenlerimizi 'const' yapmak yerine 'constexpr' şeklinde betimlemek daha yaygın hale gelmektedir. > Sınıflar (devam) : >> 'static data-members' (Devam) : >>> 'static data-members' lara 'non-static member functions' lar içerisinden erişebiliriz. İş bu fonksiyonların 'const' olması veya olmaması durumu değiştirmez. Çünkü değişkenimiz 'static' olduğundan, nesne adreslerinden bağımsızdır. * Örnek 1, //.. class A{ static int x; void func()const { x = 20; // LEGAL. } }; >> Sınıf türünden bir obje kullanarak da 'public-static-data-members' lara ERİŞİM SAĞLAYABİLİRİZ. * Örnek 1, //.. class A{ static int sx; void func() { this->sx = 50; // BU ATAMA DA LEGALDİR. 'this' göstericisi sadece İSİM ARAMA için kullanılmıştır. 'this' göstericisi tarafından // gösterilen nesne ile 'sx' nesnesinin uzaktan yakından bir ilişkisi yoktur. } void foo()const { this->sx = 60; // BU ATAMA DA LEGALDİR. 'this' göstericisi sadece İSİM ARAMA için kullanılmıştır. 'this' göstericisi tarafından // gösterilen nesne ile 'sx' nesnesinin uzaktan yakından bir ilişkisi yoktur. } }; int main() { A ax; ax.sx = 20; // Bu atama LEGALDİR. Çünkü isim arama kuralları gereği, 'sx' ismi 'class scope' içerisinde aranıyor ve bulunuyor. // Mevzunun 'ax' nesnesi ile uzaktan yakından bir alakası yoktur. Çünkü 'ax' nesnesi sadece İSİM ARAMA için // kullanılmıştır. A* pax {&ax}; pax->sx = 30; // Bu atama da LEGALDİR ve 'pax' göstericisi burada sadece İSİM ARAMA için kullanılmıştır. 'sx' değişkeninin 'pax' // ile hiç bir bağı yoktur. A& rax {ax}; rax.sx = 40; // Bu atama da LEGALDİR ve 'rax' referansı burada sadece İSİM ARAMA için kullanılmıştır. 'sx' değişkeninin 'pax' // ile hiç bir bağı yoktur. // Yukarıdaki örneklerden de görüldüğü üzere 'static data members' lara '.' ve '->' operatörü ile erişebiliyoruz. // Soru : FAKAT YANLIŞLIKLA BİZLER BU DEĞİŞKENLERE ERİŞİRSEK NE OLUR? // Cevap : Derleyici hiç bir şekilde bize uyarı/sentaks hatası vermeyeceği için yanlış değer ile oynamış oluruz. // Bundan kaçınmak için, 'static data-members' lara erişmek için sadece ve // sadece '::' operatörünü kullanmalıyız ki hem okuyan kişiye de direkt olarak mesajı vermiş oluruz, hem de yanlış // değere erişmekten kaçınmış oluruz. } >> 'static member functions' : >>> Gizli parametre olarak sınıf türünden adres değeri ALMAZLAR. Dolayısıyla sınıfın geneline hitap ederler. Bir nevi, global fonksiyonların sınıf kapsamına alınmış halleri gibidirler. Tıpkı 'static data members' larda olduğu gibi. >>> Sınıf içerisinde olduklarından, 'class scope' içerisindedirler. Bundan dolayı da, 'access control' e tabii olurlar. >>> Sınıf içerisinde olduklarından, sınıfın 'private' kısmına da erişebilirler. >>> ASSEMBLY DÜZEYİNE 'global functions' LER İLE BİR FARKI YOKTUR. DİL DÜZEYİNDEKİ BİR DEĞİŞİKLİKTİR. >>> Tıpkı 'static data members' larda olduğu gibi, bu tip fonksiyonları da '.' ve '->' operatörleri ile de çağrılabiliriz ki bu iki operatör SADECE ve SADECE İSİM ARAMA İÇİN KULLANILIR. Yukarıda detaylarıyla açıklanan senaryo gereği, bu tip fonksiyonları da '::' operatörü ile çağırmalıyız. >>> 'nullptr' tutan bir göstericiyi 'derefence' etmek 'tanımsız davranış'. Velevki bu gösterici üzerinden 'static member functions' ları çağıralım, yine 'tanımsız davranış'. * Örnek 1, //.. class Myclass{ public: static void func(); }; int main() { Myclass* ptr{nullptr}; ptr->func(); // 'tanımsız davranış'. } >>> (In C lang.) Farklı fonksiyon adresi türlerini, birbirine tür dönüştürme operatörü ile 'cast' edersek, 'tanımsız davranış' olur. Fakat bu durum KAĞIT ÜZERİNDEDİR. Gerçekte %99 oranında doğru çalışırlar. * Örnek 1, //.. void qsort(void* vpa, size_t size, size_t sz, int (*fp)(const void*, const void*)); struct Data{}; int cmp_Data(const Data* p1, const Data* p2); typedef int (*Fcmp)(const void*, const void*)); int main() { Data a[100]; qsort(a, 100, sizeof(*a), (Fcmp)&cmp_Data); } >>> Tıpkı üye fonksiyonlar gibi, sınıf içerisinde de tanımlayabiliriz. 'inline' anahtarına gerek kalmadan. >>> Bu tip fonksiyonların gövdelerinde 'non-static data members' lara erişmek sentaks hatasıdır. Çünkü bu tip değişkenlere erişirken biz açık açık yazmasak bile 'this' göstericisi kullanılıyor. Fakat bizim fonksiyonlarımız 'static' olduğundan, gizli parametre olarak bir adres te almıyorlar. * Örnek 1, //.. class A{ public: static void foo() { mx = 200; // SENTAKS HATASIDIR. Çünkü aslında "this->mx = 200;" şeklinde bir kullanım var. Fakat 'foo()' fonksiyonuna gizli // parametre olarak o sınıf türünden adres geçilmediğinden, 'this' göstericisini kullanamıyoruz. // 'non-static data member' kullanabilmemiz için bizim BİR NESNEYE ihtiyacımız var. } static void func(Myclass p) { Myclass r; p.mx = 300; // Bu legal. Çünkü biz 'p' nesnesini kullanıyoruz. Bahsi geçen fonksiyon da bir member fonksiyon olduğundan, // 'private' kısma erişebiliyoruz. r.mx = 400; // Bu legal. Çünkü biz 'r' nesnesini kullanıyoruz. Bahsi geçen fonksiyon da bir member fonksiyon olduğundan, // 'private' kısma erişebiliyoruz. } private: int mx; }; >>> Bu tip fonksiyonları 'const' 'member function' haline getiremeyiz. Çünkü 'const' özelliği, 'this' göstericisini değiştiriyor. Fakat bizim gizli parametremiz olmadığından, SENTAKS HATASIDIR. * Örnek 1, //.. class Myclass{ static void func()const; // SENTAKS HATASIDIR. Çünkü 'const' anahtar sözcüğü, gizli parametre olan address etkilemektedir. Fakat burada böyle // bir adres olmadığından dolayı, problem. }; >>> Bu tip fonksiyonların gövdelerinde, 'non-static member function' çağrısı yapamayız. Çünkü 'non-static member functions' lar gizli parametre olarak bir nesnenin adresini alırlar ki bu da bir nesneye bağlı olduklarını, yani bir nesne ile birlikte çağrılmaları gerektiğini anlamına gelir. Fakat bizim 'static member functions' lar böyle bir gizli parametreye sahip olmadıklarından, sentaks hatasıdır. * Örnek 1, class A{ public: static void func() { foo(); // SENTAKS HATASI. ÇÜNKÜ 'foo()' FONKSİYONU GİZLİ BİR SINIF TÜRÜNDEN ADRES ALMAKTA. Myclass m; m.foo(); // LEGAL. ÇÜNKÜ 'foo()' fonksiyonuna 'm' nesnesinin adresi geçilmekte bir nevi. } void foo() { func(); // LEGAL. ÇÜNKÜ 'func()' FONKSİYONU GİZLİ BİR SINIF TÜRÜNDEN ADRES ALMAMAKTA. } private: int mx; }; >>> "cannot overload static and non-static member funcs. with the same parameter types". Yani aynı parametrik yapıya sahip iki fonksiyondan birini 'static' diğerini 'non-static' yapmak suretiyle 'FO' yapamayız, sentaks hatasıdır. (Her ne kadar imzaları farklı, çünkü birisinin ilk parametresi gizli ve sınıf türünden adres). * Örnek 1, //.. class Myclass{ void func(int); static void func(int); }; // Bu durum sentaks hatası. Her ne kadar imzaları farklı da olsa. >>> Sınıfın 'static data members' a ilk değer veren ifade, yani eşitliğin sağ tarafındaki ifade, isim arama kurallarına göre önce 'class scope' İÇERİSİNDE ARANIR. Eğer orada bulunamazsa, 'global namespace' isim alanında aranıyor. * Örnek 1, MÜLAKAT SORUSU //.. class A{ public: static int foo() { return 1; } static int mx; }; int foo() { return 2; } // A.cpp int A::mx = foo(); int main() { std::cout << A::mx << "\n"; // EKRANA '1' YAZACAKTIR. } >> MÜLAKAT SORULARI : >>> Hayattaki sınıf nesnelerinin sayısını istediğimiz zaman öğrenebileceğimiz bir sınıf yazınız => 'static' bir 'data member' bildirilir. 'Ctor' çağrıları bu sayıyı bir arttırırken, 'Dtor' çağrıları da bu sayıyı bir azaltır. Duruma göre 'Copy Assignment Function' çağrılarına da bu özellik yüklenebilir veya yüklenmeyebilir. * Örnek 1, //.. class Myclass { public: Myclass() { ++ms_live_count; } Myclass(int) { //.. ++ms_live_count; } ~Myclass() { --ms_live_count; } //.. static int get_live_count() { return ms_live_count; } private: inline static int ms_live_count{}; // since c++17 }; >>> Öyle bir sınıf oluşturun ki o sınıf türünden nesneler birbirleri ile iletişim halinde olsun => 'this' göstericileri tutan bir 'static-container'. * Örnek 1, //Fighter.hpp #include #include #include #include #include using namespace std; class Fighter { public: Fighter(std::string name) : m_name{std::move(name)} { m_fVec.push_back(this); } ~Fighter() { auto iter = std::find(m_fVec.begin(), m_fVec.end(), this); assert(iter != m_fVec.end()); m_fVec.erase(iter); } void call_fighters() { std::cout << "I need help!\n"; for(auto index: m_fVec) { // Kendi adını çağırmasın diye, if(index != this) std::cout << "[" << index->m_name << "] "; } std::cout << "is being called by-name!..\n"; } private: std::string m_name; static std::vector m_fVec; }; //Fighter.cpp std::vector Fighter::m_fVec; // Vector sınıfının 'Default Ctor' u kullanıldı. int main() { /* # OUTPUT # I need help! [Merve] [Ali] [Veli] is being called by-name!.. I need help! [Ali] [Veli] is being called by-name!.. */ Fighter a{"Ahmet"},b{"Merve"},c{"Ali"},d{"Veli"}; a.call_fighters(); a.~Fighter(); // Avoid. b.call_fighters(); } >> Bir tasarım kalıbı olan 'Singleton' implementation => ilgili sınıf türünden sadece bir adet nesne oluşturulabilir. İkinci bir nesne oluşturulamaz. * Örnek 1, (geleneksel yaklaşım) //.. class Singleton { public: static Singleton& get_instance() { if(!m_pObject) m_pObject = new Singleton; return *m_pObject; } void f1() { } // 'Dtor' was also omitted. private: Singleton(); // 'Copy Ctor' & 'Copy Assignment Function' was omitted. inline static Singleton* m_pObject = nullptr; }; int main() { // Singleton::get_instance().f1(); auto& rs = Singleton::get_instance(); rs.f1(); } * Örnek 2, (Meyers Singleton) //.. class Singleton { public: static Singleton& get_instance() { static Singleton sx; return sx; } private: Singleton(); }; int main() { // Singleton::get_instance().f1(); auto& rs = Singleton::get_instance(); rs.f1(); } >> 'Named Ctor Idiom' : Doğrudan 'Ctor' çağırmak yerine, kullanıcıyı fabrika fonksiyonları çağırtarak nesne oluşturmasını sağlatmak. Örneğin, sadece 'dinamik' ömürlü nesne oluşturulması. * Örnek 1, // Sadece 'dinamik' ömürlü nesneler oluşturma örneği. //.. class DynamicOnly { public: static DynamicOnly* createObject(); { //.. return new DynamicOnly; } private: DynamicOnly(); // 'Copy Ctor' ve 'Copy Assignment Func' was omitted. }; DynamicOnly g; // Artık sentaks hatası. int main() { DynamicOnly gg; // Artık sentaks hatası. } * Örnek 2, // Kompleks sayıları ilgilendiren bir sınıf. //.. class Complex { public: static Complex createPolar(double a, double d) { return Complex(a,d); } static Complex createCartasian(double r, double i) { return Complex(r, i, 0); } private: Complex(double angel, double distance); Complex(double r, double i, int); // 3rd parameter is a dummy. // 'Copy Ctor' ve 'Copy Assignment Func' was omitted. }; >> 'Copy Ellision' : Kopyalama/taşıma yapılması gereken bazı yerlerde bu işlemin yapılmaması, başka yollar gerçekleştirilmesidir. C++17'den itibaren sentaks kuralları, öncesinde ise derleyici optimizasyonu şeklinde. * Örnek 1, (Geçici nesneler ile çağrılan call-by-value tipindeki fonksiyonların çağrılması, SENTAKS KURALI) //.. class A{ A() // 'Default Ctor' { } A(const A&) // 'Copy Ctor' { } A(A&&) // 'Move Ctor' { } ~A() { } }; void func(A ax) { } int main() { func(A{}); // Beklenti: // i. Geçici nesne için 'Default Ctor' çağrıldı. // ii. Onun fonksiyon parametresine kopyalanması sonucunda, ki fonksiyon parametresindeki sınıf nesnesi hayata geldi, // 'Copy Ctor' çağrıldı. // iii. Geçici nesne için 'Dtor' çağrıldı. // iiii. Fonksiyon parametresi olan nesne için 'Dtor' çağrıldı. // Gerçekleşen: // i. 'Default Ctor' çağrıldı // ii. 'Dtor' çağrıldı. // Çıkarılacak Sonuç: // i. Parametresi sınıf türünden bir nesne olan, ki burada call-by-value olması önemli, bir fonksiyona geçici nesneyi // argüman olarak geçersek sadece fonksiyon parametresi hayata gelir. // Artık 'kopyalama' eliminate edildi. // ii. Bu senaryo artık C++17'den sonra DİLİN KURALI fakat öncesinde DERLEYİCİLERİN OPTİMİZASYON TEKNİKLERİNDEN. } * Örnek 2, (Return Value Optimization : call-by-value ile sınıf türünden geçici nesne döndürmek, SENTAKS KURALI) class A{ A() { } A(const A&) { } A(A&&) { } ~A() { } }; A func() { return A{}; } int main() { A ax = func(); // Beklenti: // i. Geçici nesne için 'Default Ctor' çağrıldı. // ii. Geri dönüş değerinin tutulması için ki bu değerini geçici nesneden alır, 'Copy Ctor'. // iii. 'ax' nesnesini hayata getirmek için ki bu da değerini 'geri dönüş değerini tutan' değişkenden alır, 'Copy Ctor' // iiii. ... // Gerçekleşen: // i. 'Default Ctor' çağrıldı // ii. 'Dtor' çağrıldı. // Çıkarılacak Sonuç: // İlgili 'ax' nesnesi direkt olarak yerinde hayata geldi. } * Örnek 3, Tipik olarak çoğu derleyici bunu yapıyor fakat SENTAKS KURALI DEĞİL. (Return Value Optimization un bir türevi), Named Return Value Optimization: class A{ A() { } A(const A&) { } A(A&&) { } ~A() { } }; A func() { A axx; return axx; } int main() { A ax = func(); // Beklenti: // i. 'axx' için 'Default Ctor' çağrıldı. // ii. 'ax' için 'Copy Ctor' çağrıldı. // ... // Gerçekleşen: // i. 'Default Ctor' çağrıldı // ... // Çıkarılacak Sonuç: // 'axx' nesnesi aslında bizim 'ax' nesnemiz. İkisinin de adresi aynı. FAKAT BU YAKLAŞIM DERLEYİCİNİN AYARLARINA BAĞLI. // YANİ 'Debug' MODUNDA BAŞKA, 'Release' MODUNDA BAŞKA. } >> 'friend' declarations: C++ dilinde bir sınıfın 'public interface' sini, sadece ilgili sınıfın 'public' bölümündekiler değil ama başlık dosyasındaki 'global namespace functions' lar da OLUŞTURUR. Fakat bu 'global namespace functions' sınıfın 'private' kısmına erişemiyorlar. Ama onlar da sınfın bir malı, 'interface' e dahil. İşte bu 'global namespace functions' lara 'private' kısma erişme hakkı veriyoruz. 3 farklı yaklaşım vardır. >>> Global fonksiyonlara verilmesi, * Örnek 1, //.. class Nec{ private: void pfoo(); int x; }; Nec gn; void gf(Nec p) { p.foo(); // 'private' kısma erişmeye çalıştığından SENTAKS HATASI. Nec nx; nx.x = 10; // 'private' kısma erişmeye çalıştığından SENTAKS HATASI. gn.foo(); // 'private' kısma erişmeye çalıştığından SENTAKS HATASI. } * Örnek 2, #include class Nec{ public: Nec(int y) : x(y) {} friend void gf(Nec p); private: void foo() { std::cout << "void foo() was called. x : " << x << "\n"; } int x; }; Nec gn{120}; void gf(Nec p = gn) { std::cout << "void gf(Nec p) was called. p.x : " << p.x << "\n"; } int main() { /* # OUTPUT # void gf(Nec p) was called. p.x : 120 void gf(Nec p) was called. p.x : 240 */ gf(); Nec yn{240}; gf(yn); Nec other = 360; other.gf(); // error: ‘class Nec’ has no member named ‘gf’ } // 'friend' BİLDİRİMİNİ SINIFIN 'public' veya 'private' ALANINDA YAPMAMIZ BİR ANLAM FARKI OLUŞTURMAZ. >>> Sınıfın belirli bir üye fonksiyonlarına 'friend' yetkisi vermek, * Örnek 1, //.. class A{ friend void Myclass::foo(int); // Bu bildirimin geçerli olması için, derleyicinin, Myclass sınıfının bildirimini görmesi gerekiyor. // Artık Myclass sınıfının 'foo()' isimli fonksyionu, 'A' sınıfının 'private' kısmıne ERİŞEBİLİR. } >>> Bir sınıfın başka bir sınıfa 'friend' yetkisi vermesi. * Örnek 1, //.. class Myclass; // forward-dec. is enough class A{ friend class Myclass; // 'Myclass' sınıfının tüm üye fonksiyonları artık 'A' sınıfının 'private' kısmına erişebilir. int mx; }; * Örnek 2, #include class Neco; class Myclass{ public: friend class Neco; private: void f1(){ std::cout << "mx : " << mx << "\n"; } void f2(){ std::cout << "my : " << my << "\n"; } void f3(){ std::cout << "mz : " << mz << "\n"; } int mx{1}; int my{2}; int mz{3}; }; class Neco{ public: void print() { myClass.f1(); myClass.f2(); myClass.f3(); } private: Myclass myClass; }; int main() { /* # OUTPUT # mx : 1 my : 2 mz : 3 */ Neco myNec; myNec.print(); } >>> Sınıf içerisinde 'friend' olarak bildirilen fonksiyonlar, ADL durumunda, isimleri bulunuyor. O ismi direkt olarak kullandığımız zaman, sınıf içerisinde isim aranmadığından, fonksiyon bulunamıyor. * Örnek 1, //.. class Nec{ private: friend void pfoo(); // Senaryo I friend void pfoo(Nec); // Senaryo II void pfoo(); }; int main() { // Senaryo I pfoo(); // HER NE KADAR BU FONKSİYON 'global functions' DA OLSA İSİM SINIF İÇERİSİNDE ARANMADIĞINDAN DOLAYI // BULUNAMIYOR VE SENTAKS HATASI. // Senaryo II Nec ax; pfoo(ax); // ADL KURALLARI GEREĞİ BU ÇAĞRI LEGALDİR. } > 'CppCon::BackToBasics' video serisinin izlenmesi tavsiye ediliyor. Aynı şekilde aynı konferanstaki diğer video seriler de tavsiye edilmekte, özellikle C++20 hakkındakiler. > Alan Key, Quora C/Cpp, takip edilmesi tavsiye edilmekte. > Design Patterns: Elements of Reusable Object-Oriented Software, kitabı da tavsiye edilmektedir. > 'refactoring.guru' sitesi bu tasarım kalıpları hakkında bilgi vermektedir. > dictionary.com /*============================================================================================================*/ (11_17_10_2020) > Sınıflar (devam) : >> 'friend' Bildirimleri (devam) : >>> Bir sınıfın 'Special Member Functions' larına da bu özellik verilebilir. Tıpkı bir sınıfın bir fonksiyonuna bu özelliğin verilmesi gibi. >>> 'Partly Friend' / 'Kısmi Arkadaşlık' özelliğini doğrudan vermek mümkün değildir. Yani bir sınıfa/fonksiyona 'friend' özelliği verildiğinde, normal şartlar altında, iş bu sınıf/fonksiyon sizin bütün 'data members' lara ve 'member functions' lara erişebilir. Kısmi arkadaşlığı sağlayan 'idiomatic' yaklaşımlara ise 'Avukat-Müvekkilik Idiom' denir. >>> ARKADAŞIMIN ARKADAŞI BENİM ARKADAŞIM DEĞİLDİR. >>> BİRİNE ARKADAŞLIK TEKLİF ETTİM DİYE O DA BENİM ARKADAŞIM OLACAK DEĞİL. >>> BABAMIN/DEDEMİN ARKADAŞLARI BENİM ARKADAŞIM DEĞİL. >> '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. >>> BU MEKANİZMANIN GENEL KURALLARI: >>>> 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': => Üye fonksiyon olması durumunda: 'x.operator!()'; // Çünkü sınıf nesnesi için çağrıldığından, gizli parametre olarak ilgili nesnenin // adresini almaktadır. '*this = x'. 'x.operator!(int);' // ARTIK BU ÇAĞRI SENTAKS HATASIDIR. => Global fonksiyon olması durumunda: 'operator!(x);' // Çünkü artık sınıf nesnesi için çağrılmadıklarından, sadece bir adet parametre // alabilirler. 'operator!(x, y);' // ARTIK BU ÇAĞRI SENTAKS HATASIDIR. 'operator!();' // 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 < y': => Üye fonksiyon olması durumunda: 'x.operator<(y);' // Çünkü sınıf nesnesi için çağrıldığından, gizli parametre olarak ilgili nesnenin adresini // almaktadır. '*this = x'. 'x.operator<(y, int);' // ARTIK BU ÇAĞRI SENTAKS HATASIDIR. => Global fonksiyon olması durumunda: 'operator<(x, y);' // Çünkü artık sınıf nesnesi için çağrılmadıklarından, sadece iki adet parametre alabilirler. 'operator<(x);' // ARTIK BU ÇAĞRI SENTAKS HATASIDIR. 'operator<();' // ARTIK BU ÇAĞRI SENTAKS HATASIDIR. >>>> 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. * Ö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. >>> Mülakat Sorusu, "Sadece üye operatör fonksiyonu olsaydı, yani global operatör fonksiyonu hiç olmasaydı, hangi operatörülerin fonksiyonlarını yazamazdık?" * Örnek 1, '+' operatörü için // Assume that 'x' is a 'user-defined' type. // 'x + 4' deyimi aslında 'x.operator(4)' şeklindeki bir fonksiyon çağrısına dönüştürülüyor. Peki '4 + x' deyimi // yazıldığında ne oluyor? => '4' e ait bir fonksiyon olmayacağından SENTAKS HATASI ALIYORUZ. İşte bu durumu örtbas etmek // için BİZİM GLOBAL OPERATÖR FONKSİYONUNA İHTİYACIMIZ VAR Kİ 'x + 4' ve '4 + x' ŞEKİLLERİNDE ÇAĞRIDA BULUNABİLELİM. // Üye Fonksiyon olarak 'x+4' sadece ve sadece 'x.operator+(4)' şeklinde yazabiliriz. // Global Fonksiyon olarak 'x+4' deyimini 'operator+(x,4)' şeklinde, '4+x' deyimini de 'operator+(4,x)' şeklinde yazabiliriz. * Örnek 2, '<<' operatörü için // Konsola yazarken '<<' operatörü için 'iostream' sınıfına 'user-defined' tür alan bir 'Overload' yazılmamış. Bizlerin de // o sınıf için fonksiyon yazmamız mümkün değil. Bundan dolayı, bu operatörü 'std::cout' ile kullanmak için GLOBAL OPERATÖR // FONKSİYONUNA ihtiyacımız vardır. >>> '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. * Örnek 1, '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 operatör 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. >>> Pekiştirici Örnekler, * Örnek 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İ. /*============================================================================================================*/ (12_18_10_2020) > Sınıflar (devam) : > 'operator overloading' devam, * Örnek 1, ö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ı. } } >>> '+' ve '-' operatörlerinin C ve C++ dillerindeki kullanımları, * Örnek 1, ifadenin türünü 'PR-Value' haline getirir. //.. int main() { int x = 10; x...; // 'x' ismi burada 'L-Value' şeklinde. +x...; // 'x' ismi artık 'PR-Value' şeklinde. y...; // 'y' ismi burada 'L-Value' şeklinde. -y...; // 'y' ismi artık 'PR-Value' şeklinde. } * Örnek 2, 'integral-promotion' a neden olur. //.. int main() { char c = 10; c...; // 'c' isminin türü 'char'. +c...; // 'c' ismi bir işleme sokulduğundan bu ifadenin türü artık 'int'. } > Dinamik Ömürlü Nesnelere Giriş: Ömür kategorileri dört şekildir. Bunlar, Otomatik ömürlü değişkenler, 'static' ömürlü değişkenler, dinamik ömürlü değişkenler ve 'thread-local' ömürlü değişkenler şeklindedir. Dinamik ömürlü nesneler ile Dinamik Bellek Yönetimi birbirinin eşleniği DEĞİLDİR. Sadece aralarında bir ilişki vardır. Burada, >> Dinamik Ömürlü Nesneden kastedilen, ilgili nesnenin hayatının sadece ve sadece PROGRAMCI OLARAK BİZLERE BAĞLI olduğu olmasıdır. Velevki böyle bir nesneyi hayata getirdik. Eğer programcı olarak bizler, ilgili nesnenin hayatını sonlandırmayı unutursak, 'memory leak' meydana gelir. Bunu 'resource leak' ile karıştırmamalıyız. >>> 'Memory Leak' : 'malloc()' gibi Dinamik Bellek Yönetiminden sorumlu bir fonksiyon ile aldığımız yerin, işimiz bittikten sonra, tekrar iade edilmemesi durumudur. Bu durumda çalışma zamanında o alan artık 'locked' olacaktır. Eğer bizler bu alma-geri vermeme işlemini sürekli olarak tekrar ettirsek bir zaman sonra 'malloc()' fonksiyonu bize yeni yerler tahsis EDEMEZ HALE GELECEKTİR. >>> 'Resource Leak' : 'malloc()' gibi Dinamik Bellek Yönetiminden sorumlu bir fonksiyon ile bir yer tahsis edelim ve orasını da 'Student' sınıf türünden bir nesne için kullanalım. İş bu sınıf nesnesi de hayata gelirken 'data members' için bir 'database' bağlantısına, ilave bir bellek alanına VEYA bir dosyanın açılmasına ihtiyaç duysun. Bu nesne ile işimiz bittiğinde eğer 'data members' için kullanılan 'database' bağlantılarını sonlandırmazsak veya alınan o ilave bellek alanlarını tekrar geri vermezsek yada açılan o dosyayı kapatmazsak 'Resource Leak' meydana gelir. İlgili sınıf nesnesi için alınan bellek alanının geri verilmemesi de bu konuya dahildir fakat bu daha genel bir sızıntıdır. O sınıf nesnesi hayata gelirken aldığı/kullandığı kaynakların geri verilmemesinden BAHSETMEKTEDİR. >>> C dilinde bu süreç şu şekildedir; >>>> İlk önce sınıf nesnemizin oluşturulacağı bellek alanını 'malloc()' çağrısı ile elde et. Elde edilememesi durumunda duruma müdahale et(Hata Yönetimi). >>>> Hayata getirilecek nesne kaynaklara ihtiyaç duyuyorsa, o kaynakları da edin. Örneğin, bir dosyanın açılması gerekiyorsa dosyayı aç veya bir 'database' bağlantısına ihtiyaç var ise o bağlantıyı sağla veya ekstra bir bellek alanlarına ihtiyaç duyuyorsa o bellek alanlarını tahsis et. >>>> Hayata başarılı bir şekilde gelmiş olan bu nesneyi kullan. >>>> O nesnenin hayatını bitirmeden evvel açmış olduğun dosyaları/sağlamış olduğun 'database' bağlantılarını/almış olduğun ekstra bellek alanlarını geri ver. >>>> Nesnemizin kendisinin kullandığı bellek alanını sisteme geri ver. >> Dinamik ömürlü nesnelerin bellek alanları Dinamik Bellek Yönetimi ile elde edilir. >> C++ dilinde Dinamik Ömürlü Nesne oluşturulması 'operator' seviyesinde işlemlerdir. İsimlerinin içinde 'new' kelimesi geçen operatörler, Dinamik Ömürlü Nesne oluşturan operatörlerdir. Örneğin, 'new', 'new []', 'placement new', 'nothrow new'. Bu nesnelerin hayatını bitiren operatörlere ise 'delete' operatörleri denmektedir. * Örnek 1, //.. new int; // 'int' türden dinamik bir nesne hayata getirmiş olduk. new Student; // 'Student' türden dinamik bir nesne hayata getirmiş olduk. new vector; // 'vector' türden dinamik bir nesne hayata getirmiş olduk. >>> 'new' operatörleri, başarılı olduklarında, o türden birer adres döndürmektedir. Dolayısıyla bizlerin de o türden bir 'gösterici' lere ihtiyacımız vardır. * Örnek 1, //.. Fighter* p1 = new Fighter; Fighter* p2(new Fighter); Fighter* p3{new Fighter}; auto p4 = new Fighter; // Tür çıkarımı 'Fighter*' şeklinde. auto p5(new Fighter); auto p6{new Fighter}; auto* p7{new Fighter}; // Tür çıkarımı 'Fighter' şeklinde. auto& p8 = *new Fighter; // Pek rast gelinmeyen bir yaklaşım. >>> 'new' operatörleri arka planda 'operator new()' isimli bir fonksiyona çağrı yapmaktadır ki bu fonksiyon da C dilindeki 'malloc()' ile aynı parametrik yapıdadır( Bkz. "void* malloc(size_t n)"). Arka plandaki bu operatör fonksiyonu işini yapamadığında 'bad_alloc' sınıf türünden 'exception throw' etmekte. Fakat 'malloc()' ise aynı durumda 'nullptr' döndürmekte. Peki hangi sıraya göre işlemler yapılmakta? // - Açıklama - - Psuedo Code - >>>> sizeof(T) türü kadarlık bellek alanı ayrılıyor. => ...operator new(sizeof(Fighter))... // - Açıklama - - Psuedo Code - >>>> Daha sonrasında da geri dönüş değerini ilgili türe 'cast' etmekte => ...static_cast( operator new(sizeof(Fighter)) )... // - Açıklama - - Psuedo Code - >>>> Son olarak da o sınıf türünden nesne için 'ctor' çağrısı yapmakta. => ( static_cast( operator new(sizeof(Fighter)) ) )->Fighter(); // 'Default Ctor' çağrılacak. => (...)->Fighter(1,2); // 'Paramereli Ctor'. => (...)->Fighter(anotherFighter); // 'Copy Ctor' >>>> Bizler 'new' operatörlerinin davranışını değiştiremeyiz, bu operatörü 'overload' EDEMEYİZ. Sadece arka plandaki 'operator new()' fonksiyonunu 'overload' edebiliriz. Bu durumda 'new' operatörü ile bizim özelleştirdiğimiz fonksiyon çağrılacak. Aksi durumda standart olan 'operator new()' fonksiyonu çağrılacak. >>> 'delete' operatörünün operandı kullanılarak, ilgili sınıf nesnesinin 'Dtor' u çağrılıyor. Daha sonra 'operator new()' fonksiyonu ile elde edilen bellek bloğunu da 'operator delete()' fonksiyonunu çağırarak geri veriyor. Yine çağrılan bu fonksiyona da OPERAND olan gösterici argüman olarak geçiliyor. C dilindeki 'free()' fonksiyonu ile benzer parametrik yapıdadır (Bkz. "void free(void* )"). Eğer 'delete' operatörüne çağrı yapılmazsa yada unutulursa 'Dtor' çağrılmayacağı için 'Resource Leak' meydana gelebilir eğer o sınıf hayata gelirken bir kaynak kullanmış ise. Ayrılan bellek bloğu da geri verilmeyeceği için 'Memory Leak' meydana gelir. >>> # ÖZETLE # >>>> 'new' operatörü arka planda 'operator new()' fonksiyonuna çağrı yapmaktadır. Bu 'new' operatörünü 'overload' edemeyiz, dilin kuralları izin vermiyor. Sadece arka plandaki 'operator new()' fonksiyonunu 'overload' edebiliriz. İş bu arka plandaki operatör fonksiyon, aslındaki C dilindeki 'malloc()' ile aynı parametrik yapıdadır. Bkz; // In C || In C++ // İmzaları => void* malloc(size_t size); void* operator new(size_t size); // Hata Durumları => 'NULL' döndürür. 'bad_alloc' sınıf türünden 'exception throw' eder. >>>> 'delete' operatörü arka planda 'operator delete()' fonksiyonuna çağrı yapmaktadır. Bu 'delete' operatörünü 'overload' edemeyiz, dilin kuralları izin vermiyor. Sadece arka plandaki 'operator delete()' fonksiyonunu 'overload' edebiliriz. İş bu arka plandaki operatör fonksiyon, aslındaki C dilindeki 'free()' ile aynı parametrik yapıdadır. Bkz; // In C || In C++ // İmzaları => void free(void* ptr); void operator delete(void* ptr); * Örnek 1, RAII idiom: 'Ctor' kaynaklar edinirken, 'Dtor' kaynakları geri verir. //.. #include class ResourceUser{ public: ResourceUser() { std::cout << this << " : adresindeki nesne için kaynaklar edinildi.\n"; } ~ResourceUser() { std::cout << this << " : adresindeki nesne için kaynaklar geri verildi.\n"; } }; ResourceUser rss; // 'Ctor' çağrılacak ve bir takım kaynaklar edinilecek. int main() { // 'rss' nesnemiz STATİK ÖMÜRLÜ olduğundan dolayı hala hayatta. { ResourceUser rs; // 'Ctor' çağrılacak ve bir takım kaynaklar edinilecek. // Birazdan da 'Dtor' çağrılacak ve az evvel temin edilen kaynaklar tekrar geri verilecek. // Çünkü 'rs' isimli nesnemiz OTOMATİK ÖMÜRLÜ. } ResourceUser* p; p = new ResourceUser; // 'operator new()' fonksiyonu ile bir bellek bloğu ayrılacak ve 'Ctor' çağrılacak ve bir takım kaynaklar // edinilecek. Eğer biz bu fonksiyonu 'overload' edersek bizimki, etmezsek de standart olanınki çağrılacak. // 'p' adresindeki nesne için 'delete' operatörü henüz kullanılmadığından, kaynaklar ve bellek bloğu geri // verilmedi. ÇÜNKÜ NESNEMİZ DİNAMİK ÖMÜRLÜ. delete p; // Artık 'p' tarafından gösterilen nesnenin 'Dtor' u çağrıldı, temin ettiği kaynaklar geri verildi. Sonrasında // da 'new' operatörü ile elde edilen bellek alanı 'operator delete()' fonksiyonu ile geri verildi. Eğer bu // fonksiyonu 'overload' edersek bizimki, etmezsek standart olan çağrılacak. // 'rss' nesnemiz STATİK ÖMÜRLÜ olduğundan dolayı hala hayatta. } // 'rss' nesnemizin için 'Dtor' çağrılacak ve az evvel temin edilen kaynaklar tekrar geri verilecek. * Örnek 2.1, 'operator new()' ve 'operator delete()' fonksiyonlarının 'overload' edilmeleri //.. #include void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } 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"; } }; int main() { /* # OUTPUT # operator new(1) was called. A memory block has been occupied starting from : 0x55b50c5272c0 An object has been created at the address of : 0x55b50c5272c0 An object has been destroyed at the address of : 0x55b50c5272c0 operator delete() was called. A memory block will be given back, starting from : 0x55b50c5272c0 */ Myclass* p = new Myclass; delete p; // Yukarıdaki 'delete' çağrısı yapılmasaydı eğer // i. 'Dtor' çağrılmayacaktı. Bu da 'Resource Leak' e neden olabilir. Eğer ilgili sınıf içerisinde // dinamik ömürlü bir 'data member' varsa onun da hayatı sona ermeyeceğinden, 'Memory Leak' oluşacaktı. // Buradan hareketle 'Resource Leak' kendi içinde 'Memory Leak' de barındırabilir. // ii. 'p' göstericisi tarafından gösterilen adres alanı tekrar sisteme geri verilmediğinden 'Memory Leak' meydana gelecektir. } // Her 'new' operatörü kullanımından ve 'delete' operatörü kullanımından sonra yukarıdaki fonksiyonlar çağrılacaktır. * Örnek 2.2, //.. #include void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } 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"; } private: char myArray[4096]{}; }; int main() { /* # OUTPUT # operator new(4096) was called. A memory block has been occupied starting from : 0x55ef5a36f2c0 An object has been created at the address of : 0x55ef5a36f2c0 An object has been destroyed at the address of : 0x55ef5a36f2c0 operator delete() was called. A memory block will be given back, starting from : 0x55ef5a36f2c0 */ Myclass* p = new Myclass; delete p; } // Örnek 2.1'de 1 byte'lık alan allocate edildi fakat bu örnekte 4096 byte'lık alan allocate edildi. * Örnek 2.3, //.. #include void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } class Myclass { public: Myclass() : m_ptr{new char} { 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"; delete m_ptr; } private: char* m_ptr; }; int main() { /* # OUTPUT # operator new(8) was called. A memory block has been occupied starting from : 0x561e77cc22c0 operator new(1) was called. A memory block has been occupied starting from : 0x561e77cc22e0 An object has been created at the address of : 0x561e77cc22c0 An object has been destroyed at the address of : 0x561e77cc22c0 operator delete() was called. A memory block will be given back, starting from : 0x561e77cc22e0 operator delete() was called. A memory block will be given back, starting from : 0x561e77cc22c0 */ Myclass* p = new Myclass; delete p; } // Yukarıdaki örnekte hem 'data member' olan için hem de sınıf nesnesinin kendisi için dinamik // ömürlü nesneler hayata getirildi. >>> 'new' operatörleri ile dinamik ömürlü nesne hayata getirmenin tehlikeli yanları; >>>> İş bu operatörler ile ayrılan bellek alanlarını 'delete' operatörleri ile geri verilmesinin unutulması. Örneğin, birden fazla gösterici bizim nesnemizi gösterir olsun. İşlemler yapılırken de nesnemizin adresi fonksiyondan fonksiyona geçer olsun. Bu durumda gerçekten fonksiyonu yazan kişi 'delete' operatörlerini çağırmayı unutur ya da bu işi diğer fonksiyonların yapmasını ister. Diğer fonksiyonlar da başka fonksiyonların 'delete' etmesini ister. Günün sonunda bizim nesnemiz ortada kalır ve 'delete' edilmemiş olur. Kod karmaşık hale geldikçe, kodda çalışan kişi sayısı arttıkça, bu işlemin unutulma ihtimali de bir hayli yüksek ki takip edilmesi de pek mümkün değil. >>>> İş bu operatörler ile ayrılan bellek bloğunu birden fazla gösterici gösteriyor olabilir. Bu göstericilerden bir tanesinin bu nesneyi 'delete' etmesi durumunda, diğer göstericiler 'dangling pointer' statüsüne geçeceklerdir. İş bu göstericileri kullanan programcılar da bunun farkına varamayabilirler. Bu göstericileri tekrar 'delete' etmek istediklerinde hem hayatı bitmiş bir nesne için 'Dtor' çağrılacak hem de 'dangling pointer' için 'operator delete()' fonksiyonu çağrılacak. ALIN SİZE FELAKET, 'Tanımsız Davranış'. >>>> İş bu operatörler ile ayrılan bellek bloğunu birden fazla gösterici gösteriyor olabilir. Bu göstericilerden bir tanesinin bu nesneyi 'delete' etmesi durumunda, diğer göstericiler 'dangling pointer' statüsüne geçeceklerdir. Programcı olarak bizler bunun farkında olmayabiliriz ve ilgili göstericiyi kullanmaya devam edebiliriz ki 'dangling pointer' statüsünde olan göstericilerin kullanılması da 'Tanımsız Davranış'. >>>> İşte yukarıdaki nedenlerden ötürü bizler 'smart pointer' sınıflarını kullanmalıyız. * Örnek 1, 'unique_ptr' //.. #include #include void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } class Myclass { public: Myclass() : m_ptr{new char} { 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"; delete m_ptr; } private: char* m_ptr; }; int main() { /* # OUTPUT # main() was started. operator new(8) was called. A memory block has been occupied starting from : 0x55d68d9792c0 // Sınıf nesnemizin kendisi için. operator new(1) was called. A memory block has been occupied starting from : 0x55d68d9792e0 // Sınıf nesnemizin içerisindeki 'raw-pointer' için. An object has been created at the address of : 0x55d68d9792c0 // Sınıf nesnemiz hayata geldi. An object has been destroyed at the address of : 0x55d68d9792c0 // Sınıf nesnemizin hayatı sona erdi. operator delete() was called. A memory block will be given back, starting from : 0x55d68d9792e0 // Sınıf nesnemizin içerisindeki 'raw-pointer' için. operator delete() was called. A memory block will be given back, starting from : 0x55d68d9792c0 // Sınıf nesnemizin kendi adresi. main() was ended. */ std::cout << "main() was started.\n"; { auto p = std::make_unique(); } std::cout << "main() was ended.\n"; } // 'p' isimli sınıf nesnemizin ömrü otomatik ömürlü. Dolayısıyla içerisdeki bloğun sonunda hayatı sonra erecek. // Diğer taraftan sınıfımız için bir bellek bloğu ayrıldı. // Sonrasında sınıfımızın içerisindeki 'raw-pointer' için dinamik bir bellek bloğu daha ayrıldı. // Daha sonra nesnemiz hayata geldi. // 'p' isimli sınıf nesnesinin hayatı sona erdiğinden, dinamik ömürlü sınıf nesnemizin de hayatı sona erecek. // Bunun için de dinamik ömürlü sınıf nesnesi için 'Dtor' çağrıldı. // Daha sonra dinamik ömürlü sınıf nesnemizin elemanı olan 'raw-pointer' ın gösterdiği bellek alanı geri verildi. // Son olarak dinamik ömürlü sınıf nesnemizin hayatı sona erdi. >>> 'new' operatörleri ile hayata nesne getirme yolları, örnekler ile; * Örnek 1, // class Myclass{ public: Myclass() { } Myclass(int, int) { } }; int main() { Myclass* p1 = new Myclass; // 'Default Ctor' çağrılacaktır, (default initialize). Myclass* p2 = new Myclass{}; // Önce 'zero-initialize' ediliyor, sonra 'Default Ctor' çağrılacaktır, (value initialize since C++11). Myclass* p3 = new Myclass(); // Önce 'zero-initialize' ediliyor, sonra 'Default Ctor' çağrılacaktır, (value initialize). Myclass* p4 = new Myclass(12,56); // 'Parametreli Ctor' çağrılacaktır, (direct initialize). Myclass* p4 = new Myclass{12,56}; // 'Parametreli Ctor' çağrılacaktır, (direct-list initialize). auto pp = new int; // 'pp' değişkeninin türü 'int*' ve Garbage-Value ile hayata geldi çünkü 'default initialize' edildi. // ...*pp...; // 'Tanımsız Davranış' auto ppp = new int(); // 'ppp' değişkeni 'value-initalize' edildi. Fakat kural gereği önce 'zero-initalize' edilecek, // sonra 'default initialize'. // ...*ppp...; // '0' değerinde. auto pppp = new int{}; // 'pppp' değişkeni 'value-initalize' edildi. Fakat kural gereği önce 'zero-initalize' edilecek, // sonra 'default initialize'. // ...*pppp...; // '0' değerinde. // Yukarıdaki '()' ve '{}' içerisinde rakam yazabiliriz. Böylece o değer ile hayata gelirler. auto q = const new int{253}; // 'q' nesnesinin türü 'const int*'. 'const' özelliği düşmedi çünkü // göstericinin kendisi 'const' değil. auto qq = const new Myclass; // Yine 'qq' nesnesi de 'const Myclass*' şeklinde. } >>> Temel İlkelerimiz: >>>> 'new' operatörlerini kullanmaktan kaçının. Bunların yerine 'smart pointer' sınıf türünden nesneler kullanmaya çalışın. Dolayısıyla bu sınıf türünden nesne döndüren 'make_unique' ve 'make_shared' fonksiyonlarını da bu 'smart pointer' sınıf türünden nesneler ile birlikte kullanmalıyız. > Sınıflar (devam) : >> Operatörlerin 'overload' edilmeleri (devam); İ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"; } >>> '*', derefencing operatörünün 'overload' edilmesi: AŞAĞIDA ÖRNEKLENMİŞTİR. >>> '->', arrow operatörünün 'overload' edilmesi: 'unique_ptr' sınıf nesnesinin ilkel hali, >>>> '->' 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 # An object has been created at the address of : 0x55f37379beb0 // 'p' isimli değişken. An object has been created at the address of : 0x7ffdbaffeef0 // 'x' isimli değişken. void func() was called for the object at the address of : 0x55f37379beb0 main started An object has been created at the address of : 0x55f37379d2d0 // Blok içerisindeki 'p' isimli değişken. An object has been created at the address of : 0x7ffdbafffef0 // Blok içerisindeki 'x' isimli değişken. void func() was called for the object at the address of : 0x55f37379d2d0 An object has been destroyed at the address of : 0x7ffdbafffef0 // Blok içerisindeki 'x' isimli değişken. An object has been destroyed at the address of : 0x55f37379d2d0 // Blok içerisindeki 'p' isimli değişken. main ended An object has been destroyed at the address of : 0x7ffdbaffeef0 // 'x' isimli değişken. */ 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: SONRAKİ DERSE KALDI. >>> 'type-cast' operatörünün 'overload' edilmesi: SONRAKİ DERSE KALDI. /*============================================================================================================*/ (13_24_10_2020) > Sınıflar (devam) : >> 'operator overloading' (devam) : >>> '()', 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. >>>> 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 5 adet oge 2 rakamina tam bolunmekte. // İlkel kısma ait. 3 adet oge 5 rakamina bolunmekte. 3 adet oge 7 rakamina bolunmekte. Kaca tam bolunenleri istersiniz? : 3 7 adet oge 3 rakamina tam bolunmekte. // Functor yazılan kısma ait. Kaca tam bolunenleri istersiniz? : 11 3 adet oge 11 rakamina tam bolunmekte. // Lambda Expressions kısmına ait. */ // İ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{}; int m_max{ std::numeric_limits::max() }; // İlgili 'm_max' isimli değişkenimiz, 'signed int' türünün taşıyabileceği maksimum değer ile hayata gelecektir. }; 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', 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. >>>> Daha önceki 'Mint' sınıfını ele alalım; * Örnek 1, // 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 1.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"; // mx = 56; // Tek parametre alan 'Ctor', yani 'Conversion Ctor', 'explicit' olduğundan SENTAKS HATASI. // int temp = mx + my; // 'operator int()' fonksiyonu 'explicit' olduğundan SENTAKS HATASI. int temp = static_cast(mx + my); // 'operator int()' fonksiyonu 'explicit' olduğundan dolayı, O fonksiyonu SADECE BU ŞEKİLDE KULLANABİLİRİZ. std::cout << "temp : " << temp << "\n"; std::cout << "-----------------\n"; int tempTwo = mx + my + 44; std::cout << "tempTwo : " << tempTwo << "\n"; } >>>> 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. * Örnek 1, //.. 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"; mq = static_cast(100); // 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. std::cout << mq << "\n"; int ival = static_cast(mq); // 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. 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"; } >>>> İ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. auto f = mx && my; // Aşağıdaki ifadeler de 'boolean context' olduğundan, yani 'logic-control' istenen yer olduğundan, // geçerlidir: // 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"; } } >> Composition: Nesneler arasındaki ilişkilerden bir tanesidir (diğer ilişki türleri ise 'Aggregation', 'Association' ve 'Dependencies' şeklindedirler). Genel olarak bu ilişkileri 'has-a relationship' ve 'is-a relationship' şeklinde gruplandırılabilir. (https://www.learncpp.com) >>> Bu ilişki türünü kurabilmek için ilgili sınıfların 'complete-type' olması gerekmektedir. Fakat 'static' sınıf nesnelerimiz için 'incomplete-type' yeterli gelecektir çünkü 'static' olarak niteleme yaparken bizler aslında bildirim yaptığımız için. * Örnek 1, //.. class Neco{ }; class Myclass{ Neco myNeco; // Yukarıdaki sınıf bildirimine ihtiyacımız vardır. Neco* myNecPtr; // Yukarıdaki sınıf bildirimine ihtiyacımız yoktur çünkü 'pointer' türler için 'incomplete-type' yeterli gelmektedir. static Neco myNecoTwo; // Yukarodaki sınıf bildirimine ihtiyacımız yoktur, çünkü bu bir bildirimdir. }; >>> Bu ilişki türünde, yukarıdaki örnek baz alınmıştır, 'Myclass' sınıfının içerisinde 'Neco' sınıfı vardır. Dolayısıyla kapsayan sınıf, yani dışarıdaki sınıf, 'Myclass' olurken içerideki sınıf ise 'Neco' olmaktadır. Bunu 'sizeof()' ile de teyit edebiliriz. * Örnek 1, //.. #include class Member{ char str[16]; double x,y; }; class Owner{ int a,b; Member mx; // called 'Member Object' or 'Embedded Object' }; int main() { /* # OUTPUT # The size of Member : 32 byte. // 16 byte for the array, 8 byte for the 'x' and 8 byte for the 'y'. The size of Owner : 40 byte. // 32 byte for the Member, 4 byte for the 'a' and 4 byte for the 'b'. */ std::cout << "The size of Member : " << sizeof(Member) << " byte.\n"; std::cout << "The size of Owner : " << sizeof(Owner) << " byte.\n"; } >>> Bu ilişki türünde, dışarıdaki sınıf içerideki sınıfın 'private' kısmına erişemiyor. Çünkü 'private' kısım DIŞARIYA KAPALI. Bu ilişki türünde 'protected' kısım da kapalıdır. Fakat 'friend' ÖZELLİĞİ SUNMAMIZ TAKTİRDE HER İKİ KISMA DA ERİŞEBİLİRİZ. * Örnek 1, //.. #include class Member{ public: void foo(); // friend class Owner; // Bu bildirim ile 'Owner' sınıfı bizim 'private' ve 'protected' kısımlarımıza artık erişebilir. protected: void myFunc(); private: void pfunc(); }; class Owner{ public: void f() { mx.foo(); // LEGAL mx.pfunc(); // 'Access Control' e takılacağı için SENTAKS HATASI. Eğer yukarıdaki 'friend' bildirimini aktif hale // getirirsek SENTAKS HATASI ortadan kalkacaktır. mx.myFunc(); // 'Access Control' e takılacağı için SENTAKS HATASI. Eğer yukarıdaki 'friend' bildirimini aktif hale // getirirsek SENTAKS HATASI ortadan kalkacaktır. } private: Member mx; // called 'Member Object' or 'Embedded Object' }; int main() { /* # OUTPUT # The size of Member : 32 byte. // 16 byte for the array, 8 byte for the 'x' and 8 byte for the 'y'. The size of Owner : 40 byte. // 32 byte for the Member, 4 byte for the 'a' and 4 byte for the 'b'. */ std::cout << "The size of Member : " << sizeof(Member) << " byte.\n"; std::cout << "The size of Owner : " << sizeof(Owner) << " byte.\n"; } >>> Acaba, yukarıdaki sınıf baz alındığında, 'Member' sınıfı türünden bir elemana sahip olmam 'Member' sınıfının Arayüzünü aldığım anlamına geliyor mu? El-cevap: Hayır. * Örnek 1, //.. class Engine{ public: void start(); void stop(); }; class Car{ public: void maintain() { // 'm_eng' can be used here. } // 'Engine' sınıfının 'interface' sini bu şekilde adapte edebiliriz: void stop() { m_eng.stop(); // Legal. } private: Engine m_eng; }; int main() { Car myCar; myCar.start(); // Sentaks hatası. myCar.stop(); // Legal. } >>> Peki sınıf nesnelerimiz nasıl hayata gelecekler? El-cevap : Eğer 'Ctor' ları derleyici yazıyorsa bütün elemanları 'Default Initialize' yapacaktır. Bu durumda ilgili sınıfın 'data members' ları da 'Default Initialize' edilecektir. 'primitive' türler ise hayata çöp değer ile gelecekler(otomatik ömürlü oldukları varsayıldı), 'user-defined' türler ise 'Default Ctor' çağrılacaktır. * Örnek 1, //.. #include class Member{ public: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ private: Member mx; }; int main() { /* # OUTPUT # Member::Member() was called. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani bütün // elemanları 'Default Initialize' edilmiştir. } * Örnek 2, 'Member::Member()' YOKTUR. //.. #include class Member{ public: Member(int) { std::cout << "Member::Member(int) was called.\n"; } }; class Owner{ private: Member mx; }; int main() { /* # OUTPUT # */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani bütün // elemanları 'Default Initialize' edilmiştir. 'Member' sınıfının 'Default Ctor' u olmadığından derleyici 'Owner' // sınıfının 'Default Ctor' unu 'delete' eder. Dolayısıyla yukarıdaki nesnenin hayata getirilmesi SENTAKS HATASIDIR. // # ÖZETLE # // i. 'Member' sınıfının 'Default Ctor' u YOKTUR. // ii. 'Owner' sınıfının 'Default Ctor' u 'implicitly-declared but deleted' durumundadır. } * Örnek 3, 'Member::Member()' is 'private' //.. #include class Member{ private: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ private: Member mx; }; int main() { /* # OUTPUT # */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani bütün elemanları // 'Default Initialize' edilmiştir. 'Member' sınıfının 'Default Ctor' u 'private' olduğundan, 'Owner' sınıfının buna erişmeye // çalışmasından dolayı, yukarıdaki nesnenin hayata getirilmesi SENTAKS HATASIDIR. // # ÖZETLE # // i. 'Member' sınıfının 'Default Ctor' u vardır ama 'private'. // ii. 'Owner' sınıfının 'Default Ctor' u 'private' kısma erişmeye çalıştığından sentaks hatası meydana geliyor. } * Örnek 4, #include class Member{ public: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ public: Owner() { std::cout << "Owner::Owner() was called.\n"; } private: Member mx; }; int main() { /* # OUTPUT # Member::Member() was called. Owner::Owner() was called. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının 'Default Ctor' u biz yazdığımız için 'data member' ların hayata getirilmesinden biz sorumluyuz. // Hayata getirmediklerimiz 'Default Initialize' edilirler. Dolayısıyla program 'Owner' sınıfının 'Ctor' bloğuna girmeden evvel // 'Member' sınıfının 'Ctor' u çağrılır. } * Örnek 5, 'In-class Initialization' kullanımı: #include class Member{ public: Member(int) { std::cout << "Member::Member(int) was called.\n"; } }; class Owner{ public: private: Member mx{31}; }; int main() { /* # OUTPUT # Member::Member(int) was called. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani bütün elemanları // 'Default Initialize' edilmiştir. Fakat biz 'In-class Initialization' kullandığımız için derleyici 'Member' sınıfının // 'Parametreli Ctor' unu çağıracaktır. Burada sadece '{}' ve '=' kullanılır. '()' kullanılması sentaks hatası. Eğer '=' // kullanacaksak da ilgili parametreli 'Ctor' un 'explicit' OLMAMASI LAZIM. } * Örnek 6, 'Ctor Initialization List' kullanımı: #include class Member{ public: Member(int) { std::cout << "Member::Member(int) was called.\n"; } Member(int, int) { std::cout << "Member::Member(int, int) was called.\n"; } }; class Owner{ public: Owner() : mx{56} { std::cout << "Owner::Owner() was called.\n"; } // An alternative way I // Owner() : mx{56, 31} // { // std::cout << "Owner::Owner() was called.\n"; // } Owner(int x) : mx{56, x} { std::cout << "Owner::Owner(int) was called.\n"; } private: Member mx; // An alternative way II // Member mx{56,32}; }; int main() { /* # OUTPUT # Member::Member(int) was called. // The 'mx' inside of 'objOne'. Owner::Owner() was called. // The 'objOne' itself. Member::Member(int, int) was called. // The 'mx' inside of 'objTwo'. Owner::Owner(int) was called. // The 'objTwo' itself. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Default Ctor' u biz yazdık. Fakat 'Member' sınıfından olan 'mx' i hayata // getirirken de istediğimiz 'Ctor' un çağrılmasını istedik. Bu durumda bizler 'Ctor Initialization List' kullanmalıyız. Owner objTwo{31}; } * Örnek 7, Birden fazla sınıf nesnesi var ise sınıfımızda, o sınıflara ait 'Ctor' lar ise kapsayan sınıfımız içerisindeki bildirimlere göre hayata gelecektir. //.. #include class Member{ public: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ public: Owner() { std::cout << "Owner::Owner() was called.\n"; } private: Member mx; Member my; }; int main() { /* # OUTPUT # Member::Member() was called. // 'mx' Member::Member() was called. // 'my' Owner::Owner() was called. // 'objOne' */ Owner objOne; // ELEMANLARIN BİLDİRİM SIRASINA GÖRE HAYATA GELİRLER. } * Örnek 8, //.. #include class A{ public: A() { std::cout << "A"; } }; class B{ public: B() { std::cout << "B"; } private: A ax; }; class C{ public: C() { std::cout << "C"; } private: B bx; }; int main() { /* # OUTPUT # ABC */ C cx; } * Örnek 9, //.. #include #include class Student{ public: void func() { std::cout << "Grades : " << mgrades.size() << "\n"; } private: std::vector mgrades; }; int main() { /* # OUTPUT # Grades : 0 */ Student s1; // İlgili sınıfın 'Default Ctor' u derleyici tarafından yazılacak ve bütün 'data members' // 'Default Initialize' edilecekler. Bu durumda da 'vector' sınıfının da 'Default Ctor' u çağrılacak. // Çağrılan bu kurucu işlev ise içi boş bir vektör oluşturacak. s1.func(); } >>> Peki sınıf nesneleri nasıl hayata son verecek? El-cevap: 'Owner' sınıfının 'Dtor' u Derleyici tarafından, 'Member' sınıfınınki de bizim tarafımızdan yazılmış olsun. Bu durumda derleyici tarafından yazılan 'Dtor', bizim tarafımızdan yazılanı çağıracaktır. SON HAYATA GELEN İLK ÖLÜR. >>> Composition içerisinde Copy Ctor : Normal şartlarde eğer 'Copy Ctor' derleyici tarafından yazılmışsa, ilgili 'data members' karşılıklı olarak birbirine kopyalanır. Eğer bu 'data members' birer 'user-defined' tür iseler onların 'Copy Ctor' ları da çağrılır. Eğer kapsayan sınıfın 'Copy Ctor' unu biz yazarsak fakat gövdesinde kopyalama işlemi yapmazsak, bu sefer de 'data members' lar 'Default Initialize' edililirler. 'user-defined' türler için de 'Default Ctor' çağrılacaktır. * Örnek 1, Kapsayan sınıfın 'Copy Ctor' unu derleyici yazarsa: //.. #include #include class A{ public: A() { std::cout << "Default Ctor for A was called.\n"; } A(const A& other) { std::cout << "Copy Ctor for A was called.\n"; } }; class Data{ public: Data() { std::cout << "Default Ctor for Data was called.\n"; } private: A ax; }; int main() { /* # OUTPUT # Default Ctor for A was called. // 'ax' inside 'dx'. Default Ctor for Data was called. // 'dx' itself. Copy Ctor for A was called. // 'dy' itself. */ Data dx; Data dy(dx); } * Örnek 2, Kapsayan sınıfın 'Copy Ctor' unu biz yazalım fakat kopyalamayı unutalım: //.. #include #include class A{ public: A() { std::cout << "Default Ctor for A was called.\n"; } A(const A& other) { std::cout << "Copy Ctor for A was called.\n"; } }; class Data{ public: Data() { std::cout << "Default Ctor for Data was called.\n"; } Data(const Data& other) { std::cout << "Copy Ctor for Data was called.\n"; } private: A ax; }; int main() { /* # OUTPUT # Default Ctor for A was called. // 'ax' inside 'dx'. Default Ctor for Data was called. // 'dx' itself. Default Ctor for A was called. // 'ax' inside 'dy' Copy Ctor for Data was called. // 'dy' itself */ Data dx; Data dy(dx); // Copy Ctor içerisinde kopyalanmayanlar 'Default Initialize' edilirler. Bu durumda ya 'Default Ctor' çağrılır; // ya Çöp Değer ile hayata gelirler ya da 'Zero Init.' edilirler. } * Örnek 3, Kapsayan sınıfın 'Copy Ctor' unu biz yazalım ama olması gerektiği gibi kopyalayalım: //.. #include #include class A{ public: A() { std::cout << "Default Ctor for A was called.\n"; } A(const A& other) { std::cout << "Copy Ctor for A was called.\n"; } }; class Data{ public: Data() { std::cout << "Default Ctor for Data was called.\n"; } Data(const Data& other) : ax(other.ax) { std::cout << "Copy Ctor for Data was called.\n"; } private: A ax; }; int main() { /* # OUTPUT # Default Ctor for A was called. // 'ax' inside 'dx'. Default Ctor for Data was called. // 'dx' itself. Copy Ctor for A was called. // 'ax' inside 'dy' Copy Ctor for Data was called. // 'dy' itself */ Data dx; Data dy(dx); } >>> Composition içerisinde Copy Assigningment : Normal şartlarde eğer 'Copy Assigningment Function' derleyici tarafından yazılmışsa, ilgili 'data members' karşılıklı olarak birbirine kopyalanır(BİLDİRİM SIRASINA GÖRE). Eğer bu 'data members' birer 'user-defined' tür iseler onların 'Copy Assigningment Function' ları da çağrılır. Eğer kapsayan sınıfın 'Copy Assigningment Function' unu biz yazarsak fakat gövdesinde atama işlemi yapmazsak, bu sefer de 'data members' lar ATANMAMIŞ OLURLAR. Yani 'user-defined' tür iseler onların 'Copy Assigningment Function' ları ÇAĞRILMAZ. /*============================================================================================================*/ (14_25_10_2020) > Sınıflar (devam) : >> Composition (devam) : >>> Composition içerisinde Move Ctor ve Move Assigningment : Tıpkı 'Copy Ctor' ve 'Copy Assigningment Function' larda olduğu gibi bu ikisini derleyici yazarsa 'data members' lar birbirine karşılıklı olarak taşınmaktadır. Eğer biz yazıyorsak yine bütün 'data member' lara değinmeliyiz. Pas geçtiklerimiz ya 'Default Init.' edilecektir ya da ATANMAMIŞ olacaklardır. Dolayısıyla bizim yazacağımız bu iki fonksiyon da aşağıdaki gibi olmalı: * Örnek 1, //.. class Neco{ public: //.. Nec(Neco&& other) : ax{std::move(other.ax)}, bx{std::move(other.bx)} {} Neco& operator=(Neco&& other) { //.. self assignment protection was ommitted. ax = std::move(other.ax); bx = std::move(other.bx); } private: A ax; B bx; }; >>> 'Abstract Data-Type' konusuna bir örnek : 'Data-Structure' ile 'Abstract Data-Type' konuları arasındaki fark şudur; Veri Yapıları sadece 'interface' değil, 'implementation' da içermektedir. Fakat 'ADT' sadece 'interface' içermektedir. En çok kullanılan 'ADT' tipler için 'Stack / LIFO', 'Queue / FIFO', 'Priority Queue / Önceliği yüksek olan ilk çıkıyor' diyebiliriz. Bunların bir 'interface' var ama implementasyonları bizi ilgilendirmemektedir. * Örnek 1, temsili 'stack' sınıfı: //istack.hpp #ifndef ISTACK_INCLUDED #define ISTACK_INCLUDED #include class Istack{ public: // Rule of Zero applied. Bütün Özel Üye Fonksiyonları derleyici yazmıştır. bool empty()const { return mvec.empty(); } size_t size()const { return mvec.size(); } int& top() { return mvec.back(); // Konteynırda tutulan son öğeye referans ile erişiyoruz. // Eğer konteynır boş ise 'Tanımsız Davranış'. } void push(int val) { mvec.push_back(val); } /* // Fluent-API mümkün kılsaydık Istack& push(int val) { mvec.push_back(val); return *this; } */ void pop() // Eğer konteynır boş ise 'Tanımsız Davranış'. { mvec.pop_back(); } void clear() { mvec.clear(); } private: std::vector mvec; }; #endif // Gördüğünüz gibi 'std::Vector' sınıfının bütün 'interface' fonksiyonlarını almadık. // 'Composition' durumundan kastedilen işte budur. // main.cpp #include "istack.h" #include #include // 'srand' için #include // 'time' için int main() { /* # OUTPUT # yigina 652129470 degeri sokuldu. yigina 1152014854 degeri sokuldu. yigina 1137185282 degeri sokuldu. yigina 726108626 degeri sokuldu. yigina 2055166497 degeri sokuldu. yigina 67970508 degeri sokuldu. yigina 1427659420 degeri sokuldu. yigina 1416891355 degeri sokuldu. yigina 1905501274 degeri sokuldu. yigina 170850764 degeri sokuldu. yiginimizda 10kadar deger var. yiginden cikartilacak deger : 170850764 yiginden cikartilacak deger : 1905501274 yiginden cikartilacak deger : 1416891355 yiginden cikartilacak deger : 1427659420 yiginden cikartilacak deger : 67970508 yiginden cikartilacak deger : 2055166497 yiginden cikartilacak deger : 726108626 yiginden cikartilacak deger : 1137185282 yiginden cikartilacak deger : 1152014854 yiginden cikartilacak deger : 652129470 */ std::srand(static_cast(std::time(nullptr))); Istack mystack; // mystack.push(12).push(17); // Fluent-API mümkün kılınsaydı for(int i = 0; i < 10; ++i) { auto val = rand(); // Rasteleliğin önemli olduğu yerlerde bu fonksiyonu çağırmaktan kaçınmalıyız. // Dağılımın önemli olduğu yerlerde '%' operatörünü kullanmaktan kaçınmalıyız. mystack.push(val); std::cout << "yigina " << val << "degeri sokuldu.\n"; } std::cout << "yiginimizda " << mystack.size() << "kadar deger var.\n"; while(!mystack.empty()) { std::cout << "yiginden cikartilacak deger : " << mystack.top() << "\n"; mystack.pop(); } return 0; } * Örnek 2, Yukarıdaki kullanım sadece 'int' veri tipi için. Şimdi de yukarıdaki kullanımı şablonlar ile gerçekleştirmeye çalışalım: //main.cpp #include #include #include int main() { /* # OUTPUT # size : 4 => [memati] will be removed. size : 3 => [nihal] will be removed. size : 2 => [fatma] will be removed. size : 1 => [alican] will be removed. */ std::stack myStack; myStack.push("alican"); // 'const string& ' parametreli çağrılacak 'overload' çağrılacak. // Bu durumda bir Geçici Nesne oluşturulacak ve referans ona bağlanacak. myStack.push("fatma"); myStack.push("nihal"); myStack.push("memati"); while(!myStack.empty()) { std::cout << "size : " << myStack.size() << " => "; std::cout << "[" << myStack.top() << "] will be removed.\n"; myStack.pop(); } return 0; } >>> 'incomplete-type' ile işimizi halledebileceğimiz senaryolarda gereksiz başlık dosyalarınu 'include' etmekten kaçınmalıyız. Buna bir alternatif olan 'pimpl' deyiminin implementasyonları: AÇIKLAMA AŞAĞIDA. >> 'Nested Types / Member Types' : Bir türün tanımının 'class scope' içerisinde yapılması durumudur. * Örnek 1, //.. class Data{}; class Myclass{}; // Yukarıdaki 'Data' ve 'Myclass' sınıfları aynı isim alanı içerisinde tanımlanmışlardır. int main() { Data mx; // Legal Myclass my; // Legal } * Örnek 2, class Myclass{ // enclosing class class Data{ // nested class }; }; // Yukarıdaki 'Data' sınıfı, 'Myclass' içerisinde tanımlanmıştır. int main() { Data mx; // Sentaks hatası çünkü 'Data' sınıfının tanımı 'Myclass' içerisinde yapıldığından, // ilgili ismi 'class scope' içerisinde aratmamız gerekiyor. Myclass::Data my; // Artık legal. Fakat 'access control' e takılacağından dolayı SENTAKS HATASI. } >>> 'class scope' içerisinde tanımlandığından, 'access control' e de tabiidir. >>> 'Nested Types' olarak illa da 'class-type' olması gerekmiyor. Aşağıdakiler de 'Nested Types' için örnek olarak verilebilir. >>>> 'class', 'union' vs. >>>> 'typedef' bildirimleri / 'using' bildirimleri. * Örnek 1, //.. class Myclass{ typedef int Word; using Word = int; // Artık 'Word', 'class scope' içerisinde tanımlandığından, ilgili sınıf ismi ile nitelemeliyiz. }; >>>> Numaralandırma sınıflarından olabilir('enum class'). >>> İsim arama, sınıfın bildiriminin başladığı yerden başlar ve ilk kullanıldığı yerde sona erer. Eğer bulunamaz ise 'namespace scope' içerisinde aranır. * Örnek 1, //.. // (iii) Eğer sınıf içerisinde belirtilen alanda bulunamaz ise artık isim burada aranmaya başlayacak, tıpkı // C dilinde olduğu gibi. Yani sayfanın en üst noktasından başlayıp, sınıf bildiriminin olduğu yere kadarki // alanda. class Myclass { //(ii) İsim aramanın ilk noktası da burası. Yani sınıf bildiriminin başladığı yer. Word wx; // (i) 'Word' ismi ilk burada görüldüğü için isim aramanın son noktası burası. typedef int Word; }; * Örnek 2, //.. struct Word{}; class Data{ Word wx; // İsim arama kuralları sonucunda ilgili ismin bir sınıf ismi olduğu, 'int' türünün EŞ İSMİ // OLMADIĞI anlaşıldı. typedef int Word; }; >>>> 'inline' olarak tanımlanan 'member function' içerisindeki isim arama kuralları hala geçerli. Yani önce ilgili fonksiyon bloğunda aranır. Bulunamaz ise 'class scope' İÇERİSİNDE HER YERE BAKILIR. Bulunamazsa 'namespace scope' içerisinde aranır. Dolayısıyla bu kural 'nested-types' için de geçerlidir. * Örnek 1, //.. class Myclass{ void func() { Nested nx; // İlgili 'Nested' ismi sınıf bloğu içerisinde bulunamadığından, sınıf içerisindeki her yere BAKILIR. } class Nested{}; }; >>> Bir sınıfın 'Nested-type' ı olduğu gibi, o 'nested-type' türünden 'static' ve/veya 'non-static' VERİ ELEMANLARINA DA SAHİP OLABİLİR. BU İKİSİ FARKLI NOKTALARDIR. * Örnek 1, //.. class Myclass{ class Data{}; Data dx; }; >>> Bir sınıf 'interface' olarak yine bu 'nested-type' türleri kullanabilir. * Örnek 1, //.. class Myclass{ public: class Data{}; Data foo(); void func(Data); }; int main() { Myclass mx; auto x = mx.foo(); // Myclass::Data x = mx.foo(); mx.func(x); } >>> STL içerisindeki 'iteratör' sınıfı, 'nested-types' için güzel bir örnektir. * Örnek 1, Temsili gösterimi //.. template<...> class Vector{ public: class iterator{ }; iterator begin(); iterator end(); //.. }; * Örnek 2, Temsili kullanımı //.. int main() { std::vector ivec; std::vector::iterator iter = ivec.begin(); // auto iter = ivec.begin(); } >>> 'enclosing-type' olan sınıf, 'nested-type' olan sınıfın 'private' bölümüne erişebilir mi? El-cevap: ERİŞME HAKKI YOKTUR. * Örnek 1, //.. class Nec{ class Nested{ void nfoo(); }; void necFoo() { Nested myNested; myNested.nfoo(); // 'private' KISMA ERİŞİM HAKKI OLMADIĞINDAN SENTAKS HATASIDIR. // error: ‘void Nec::Nested::nfoo()’ is private within this context } }; int main() { Nec mx; mx.necFoo(); } >>> Eğer 'nested-type' friend özelliği verirse 'enclosing-type' a, o zaman erişebilir. * Örnek 1, //.. class Nec{ class Nested{ friend class Nec; // 'Nec' sınıfı artık 'Nested' sınıfının 'private' kısmına erişebilir. void nfoo(){} }; void necFoo() { Nested myNested; myNested.nfoo(); } }; int main() { Nec mx; mx.necFoo(); // error: ‘void Nec::necFoo()’ is private within this context } >>> 'nested-type', 'enclosing-type' olan sınıfın 'private' kısmına erişebilir (since C++11). * Örnek 1, //.. class Nec{ static void f(); int mx; class Nested{ void nfoo() { Nec::f(); // LEGAL. auto sz = sizeof(mx); // LEGAL. } }; }; >>> İlginç bir kural: * Örnek 1, //.. class Nec{ private: class nested{ }; public: nested func(); }; int main() { Nec myNec; Nec::nested x = myNec.func(); // Bu ilk değer verme sentaks hatası çünkü 'nested' sınıfı kapsayan sınıfın 'private' bölümünde. auto xx = myNec.func(); // LEGAL since C++11. } >>> 'nested-type' sınıflara ait üye fonksiyonların tanımlanmaları: >>>> 'inline' olarak tanımlayabiliriz. Yani sınıf tanımının içerisinde yaparak. * Örnek 1, //.. class Neco{ class Nested{ void func() { //.. } }; }; >>>> Başka yerde tanımlıyorsak da ilgili fonksiyonun ismini hem kapsayan sınıf ile hem de içerideki sınıf ile nitelemeliyiz. * Örnek 1, //.. class Neco{ class Nested{ void func(); Nested foo(Nested other); }; }; void Neco::Nested::func() { //.. } Neco::Nested Neco::Nested::foo(Nested other) { Nested myNested = other; return myNested; } // Yukarıdaki kullanımda ilgili fonksiyonun tanımındaki ve bildirimindeki parametre parantezinin içerisindeki // isimler 'class-scope' da olduğundan, 'Neco' ile nitelememize gerek yoktur. Aynı şekilde ilgili fonksiyonun // bloğu da 'class-scope' içerisinde olduğundan, 'Neco' ile nitelemeye gerek yoktur. Fakat fonksiyonun geri dönüş // değerinin türünü yazarken 'Neco' nitelemesini YAPMAK ZORUNDAYIZ. Çünkü o kısım 'class-scope' içerisinde DEĞİL, // 'namespace-scope' içerisinde. >>> 'namespace scope' içerisinde yapılan 'incomplete-type' bildirimleri ile neler yapabiliyorsak, 'enclosing-type' sınıf içerisinde yapılan 'nested-type' sınıf bildirimleri ile aynı şeyleri yapabiliriz. * Örnek 1, //.. class Neco{ class Nested; // 'forward-decleration' of class Neco Nested* ptr; // LEGAL Nested foo(); // LEGAL }; >> pimpl idiom : Pointer Implementation Idiom. Sınıfın 'private' kısmını gizlemek için kullanabiliriz. Böylelikle bizler 'incomplete-type' kullanabiliriz, başlık dosyasını eklemekten kaçınabiliriz. * Örnek 1, Sade Kullanım //neco.hpp #pragma once #include "istack.h" #include "mint.h" #include "person.h" #include class Neco{ public: void necoFunc(); private: Istack istack; // 'Istack' sınıfının tanımını içeren başlık dosyasını da 'include' etmeliyiz. Mint mint; // 'Mint' sınıfının tanımını içeren başlık dosyasını da 'include' etmeliyiz. Person person; // 'Person' sınıfının tanımını içeren başlık dosyasını da 'include' etmeliyiz. std::string str; // 'string' sınıfının tanımını içeren başlık dosyasını da 'include' etmeliyiz. }; // Yukarıda hem ilgili başlık dosyalarını 'include' etme mecburiyeti içerisindeyiz hem de sınıfımızın 'private' kısmını da // kullanılara gösterdiğimiz için 'implementasyon' hakkında bilgi veriyor olabiliriz. //neco.cpp #include "neco.h" void Neco::necoFunc() { istack.push(12); str.push_back('a'); person.print(); mint += Mint(109); } * Örnek 2, 'raw pointer' kullanarak 'pimpl' idiom: //neco.hpp #pragma once class Neco{ public: Neco(); ~Neco(); void necoFunc(); private: struct pimpl; // 'forwarding declaration' pimpl* mp; }; //neco.cpp #include "neco.h" #include "istack.h" #include "mint.h" #include "person.h" #include struct Neco::pimpl{ Istack istack; Mint mint; Person person; std::string str; }; Neco::Neco() : mp{new pimpl} { } Neco::~Neco() { delete mp; } void Neco::necoFunc() { mp->istack.push(12); mp->str.push_back('a'); mp->person.print(); mp->mint += Mint(109); } // Artık 'dinamik' ömürlü bir nesne hayata getirdiğimiz için bu yaklaşım daha maliyetli. // 'pimpl' sınıfının elemanlarına erişim artık 'pointer' vasıtasıyla gerçekleştiğinden maliyet biraz daha arttı. >> 'operator overload' mekanizmasını, Numaralandırma Sabitleri için de kullanabiliriz: * Ö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 } >> S.O.L.I.D. : Programlama dilinden bağımsız ilkeler grubudur. >> S : Single Responsibilty Principle, bir sınıfın tek bir sorumluluğu olmalı. Ayrıca bir fonksiyonun da tek bir sorumluluğu olmalı. >> O : Open-closed Principle >> L : Liskov Principle >> I : //.. >> D : //.. >> Kod tekrarı yapmaktan kaçınmalıyız. Değişkenlere ve fonksiyonlara güzel isimler vermeliyiz. Sık sık yorum satırından kaçınmalıyız. > 'namespaces' : Daha çok kütüphanenin kodlarını çağıracağımız için ilgili 'namespace' isimlerini çağırmamız yeterli gelmektedir. Fakat kütüphane yazarken de 'namespace' nasıl oluşturulur sorusunun da cevabını bilmeliyiz ki şöyle; >> 'namespace' anahtar sözcüğünden sonra bir isim yazıyoruz. O isimden sonra da bir adet '{}' çifti ekliyoruz. İşte bu süslü parantez çiftinin arasına yazacağımız şeyler, ismini verdiğimiz isim alanına dahil olmaktalar. * Örnek 1, //.. // Global namespace area namespace Neco{ // 'Neco' namespace area //.. } >> 'namespace' anahtar sözcüğünden sonra isim yazmadan '{}' çiftini yazmamız SENTAKS HATASI değildir. Bu tip isim alanlarına 'İsimlendirilmemiş İsim Alanları' denmektedir ve farklı kural setlerini bünyesinde barındırır. * Örnek 1, //.. namespace{ // Unnamed namespace //.. } >> 'namespace' alanı içerisinde bildirilen değişkenlerin ömür kategorilerinde bir değişiklik olmamaktadır. AMACIMIZ İSİM ÇAKIŞMASINI ENGELLEMEK. Buradan da hareketle diyebiliriz ki 'global namespace' içerisinde yapabildiklerimizi kendi 'custom namespace' içerisinde de yapabiliriz. * Örnek 1, // Neco.hpp included namespace Neco{ int x = 100; // 'x' hala 'static' ömürlü bir değişken. Fakat 'global' isim alanı içerisindeki 'Neco' isim alanı // içerisinde tanımlanmıştır. } // Emre.hpp included namespace Emre{ int x = 100; // Farklı isim alanları içerisinde tanımlandıklarından bir çakışma söz konusu değildir. } >> 'namespace' içerisinde bir başka 'namespace' isim alanları da bildirebiliriz. * Örnek 1, //.. // Global namespace area namespace Human{ //.. namespace Girl{ //.. namespace Baby{ } } } >> 'scope' kurallarının tekrar edelim; >>> C dilindeki : 'file scope', 'block scope', 'function prototype scope' ve 'function scope' >>> C++ dilindeki : 'namespace scope', 'class scope', 'block scope', 'function prototype scope' ve 'function scope' >> 'namespace' ler kümülatif özellikteler. * Örnek 1, //.. namespace Ali{ int a,b,c; } namespace Ali{ int x,y,z; } // Yukarıdaki iki bildirimi gören derleyici aslında aşağıdaki halde ele alıyor: Burada dikkat edilmesi gereken nokta // değişkenlere verilen isimlerin BİRBİRİNDEN FARKLI OLMASI. Eğer aynı olsalar idi bu durum SENTAKS HATASINA yol açacaktı. namespace Ali{ int a,b,c; int x,y,z; } >> 'namespace' kullanmak isim çakışmasını %100 ENGELLEMEZ, %99 ENGELLER. Çünkü, * Örnek 1, //.. int ali = 100; // Değişken ismi 'ali' aslında 'global namespace' içerisinde. namespace ali{ // İsim alanı olarak kullanılan isim 'ali' de aslında 'global namespace' içerisinde. } // Görüldüğü üzere aynı isim alanında, ki bu isim alanı 'global namespace', aynı isimde farklı nesneler olamayağıcandan, // İSİMLERİN ÇAKIŞMA RİSKİ HER ZAMAN VARDIR. >> '::' operatörünün kullanımı: >>> 'Unary Operator' olarak kullanıldığında sadece ve sadece 'global namespace' alanında isim arar. >>> 'Binary Operator' olarak kullanıldığında ise iki ihtimal vardır: >>>> Sol operand bir sınıf ismi ise ilgili sağ operand 'class scope' içerisinde aranır. Bulamaz ise sentaks hatası alırız. >>>> Sol operand bir 'namespace' ismi ise ilgili sağ operand sadece ve sadece o 'namespace' içerisinde aranır. * Örnek 1, #include int x = 300; class Myclass{ public: static int y; }; int Myclass::y = 100; namespace myNameSpace{ int z = 200; } int main() { std::cout << "Myclass::x : " << Myclass::x << "\n"; // error: ‘x’ is not a member of ‘Myclass’ std::cout << "myNameSpace::x : " << myNameSpace::x << "\n"; // error: ‘x’ is not a member of ‘myNameSpace’; did you mean ‘x’? std::cout << "x : " << ::x << "\n"; } >> İlgili 'namespace' içerisinde bildirilen öğeleri ilgili isim alanı dışında tanımlarken, bahsi geçen 'namespace' ile onları nitelemeliyiz: * Örnek 1, //.. namespace Neco{ int x = 10; void func() { //.. } class Myclass{ void func(); }; } void Neco::Myclass::func() { //.. } >> Bir isim alanı içerisindeki ismi, isim alanının ismi ile nitelemeden kullanabilmemizi sağlayan üç ayrı araç vardır. Bunlar, >>> 'using declaration' : >>> 'using namespace declaration' : >>> 'Argument Dependant Lookup' : /*============================================================================================================*/ (15_31_10_2020) > 'namespaces' (devam) : Bir isim alanının ismini kullanmadan, o isim alanında bildirilen/tanımlanan öğeleri kullanma yolları: >> 'using declaration' : "using ...;" >>> Bu bildirimin de bir kapsamı vardır. Bundan dolayıdır ki bir 'global isim' alanında bildirimini yapabiliriz, bir blok içerisinde yapabiliriz veya bir isim alanı içerisinde bildirimini yapabiliriz. * Örnek 1, 'global namespace' içerisinde bildirilme senaryosu, //.. namespace Neco{ int x, y, z; } int b = x; // GEÇERSİZ. using Neco::x; // Bu bildirimin görülür olduğu her yerde geçerlidir. x = 100; // Neco::x int a = x; // Neco::x void f1() { x = 200; // Neco::x } * Örnek 2, Bir blok içerisinde bildirilme senaryosu //.. namespace Neco{ int x, y, z; } x = 100; // GEÇERSİZ. int a = x; // GEÇERSİZ. void f1() { using Neco::x; // Bu bildirimin görülür olduğu her yerde geçerlidir. x = 200; // Neco::x } void f2() { x = 300; // GEÇERSİZ. } >>> 'using declaration' ile sunulan isim, 'using declaration' kapsamına enjekte edilir. Orada aynı isimde başka bir değişken OLAMAZ. * Örnek 1, //.. namespace Neco { int a, b, c; } int main() { using Neco::a; int a = 100; // SENTAKS HATASI. Çünkü 'using declaration' KAPSAMI, sanki bu isim burada tanımlanmış gibi bir // etki yapıyor. Aynı isim birden fazla varlığa VERİLEMEZ. // error: ‘int a’ conflicts with a previous declaration } >>> Birden fazla varlıklar için 'using declaration' için tek tek yazmamız gerekiyor fakat C++17 ile tek satırda virgüller ile ayrılan bildirimler yazabiliriz. >>> 'nested namespace' için de bu bildirimi kullanabiliriz. Kural değişikliği yoktur. >> 'using namespace declaration' : "using namespace ...;" >>> Bu bildirimin olduğu yerde, ilgili isim alanı içerisindeki isimler, sanki o isim alanı içerisinde değilmiş gibi davranmaktadır. * Örnek 1, //.. // Bizim gördüğümüz namespace Neco{ int a, b, c; } using namespace Neco; // Bu bildirimin etkin olduğu her yerde, ilgili isim alanı içinde bildirilenler, sanki isim alanı // yokmuş gibi davranmaktadır. // Bu bildirimden sonra derleyicinin gördüğü: // int a, b, c; int x = a; // Neco::a void f1() { b = 100; // Neco::b } >>> Bu bildirimin de bir kapsamı vardır. Bundan dolayıdır ki bir 'global isim' alanında bildirimini yapabiliriz, bir blok içerisinde yapabiliriz veya bir isim alanı içerisinde bildirimini yapabiliriz. * Örnek 1, //..'global namespace' içerisinde bildirilme senaryosu, namespace Neco{ int x, y, z; } int b = x; // GEÇERSİZ. using namespace Neco; // Bu bildirimin görülür olduğu her yerde geçerlidir. x = 100; // Neco::x int a = x; // Neco::x void f1() { x = 200; // Neco::x } * Örnek 2, Bir blok içerisinde bildirilme senaryosu //.. namespace Neco{ int x, y, z; } x = 100; // GEÇERSİZ. int a = x; // GEÇERSİZ. void f1() { using namespace Neco; // Bu bildirimin görülür olduğu her yerde geçerlidir. x = 200; // Neco::x } void f2() { x = 300; // GEÇERSİZ. } >>> 'using namespace declaration' ile sunulan isim, 'using namespace declaration' kapsamına enjekte EDİLMEZ. Orada aynı isimde başka bir değişken olabilir falat 'ambiguity' tip SENTAKS HATASINA neden olur. * Örnek 1, 'ambiguity' durumu //.. namespace Neco{ int a = 90, b, c; } using namespace Neco; // Kodun bundan sonraki kısmları için artık 'Neco' isim alanı yokmuş gibi davranılır. int a = 45; // LEGAL. int main() { std::cout << "a : " << a << "\n"; // 'ambiguity' tip SENTAKS HATASI. Çünkü birden fazla yerde 'a' ismi bulundu. std::cout << "Neco::a : " << Neco::a << "\n"; // LEGAL std::cout << "::a : " << ::a << "\n"; // LEGAL // Yukarıdaki senaryo da 'a' ismini nitelemeden kullandığımız zaman derleyici 'a' ismini iki yerde de görüyor. // Çünkü 'using namespace declaration' yaptığımız zaman ilgili isim alanı içerisindekiler sanki o isim alanı // yokmuş gibi davranmaktalar. } * Örnek 2, 'name masking' durumu //.. namespace Neco{ int a = 90, b, c; } using namespace Neco; int a = 45; // LEGAL. int main() { int a = 13; std::cout << "a : " << a << "\n"; // LEGAL. Çünkü isim arama ilk olarak 'blok scope' içerisinde yapıldığından, // 'a' ismi bulunuyor ve İSİM ARAMA SONA ERİYOR. } * Örnek 3, 'name masking' durumu v2 //.. namespace Neco{ int a = 90, b, c; } int a = 45; // LEGAL. int main() { using namespace Neco; // Kodun bundan sonraki kısmları için artık 'Neco' isim alanı yokmuş gibi davranılır. int a = 13; std::cout << "a : " << a << "\n"; // LEGAL. Çünkü isim arama ilk olarak 'blok scope' içerisinde yapıldığından, 'a' ismi bulunuyor ve // İSİM ARAMA SONA ERİYOR. } * Örnek 4, 'name masking' durumu v3 //.. namespace Neco{ int a = 90, b, c; } int a = 45; // LEGAL. int main() { int a = 13; // Kodun bu kısmında 'Neco' isim alanının ismini kullanmamız gerekiyor eğer o isim alanındaki // isimlere erişmek istiyorsak. using namespace Neco; // Kodun bundan sonraki kısmları için artık 'Neco' isim alanı yokmuş gibi davranılır. Dolayısıyla o isim // alanındaki isimleri nitelemeden kullanabiliriz. std::cout << "a : " << a << "\n"; // LEGAL. Çünkü isim arama ilk olarak 'blok scope' içerisinde yapıldığından, 'a' ismi bulunuyor ve // İSİM ARAMA SONA ERİYOR. } >>> Farklı başlık dosyalarından gelen 'namespace' ler için 'using namespace' bildirimini kullanmamız durumunda da 'ambiguity' tip SENTAKS HATASI alırız. * Örnek 1, //.. // from Neco.hpp namespace Neco{ int a = 90, b, c; } // from Nec.hpp namespace Nec{ int a = 90, b, c; } using namespace Neco; using namespace Nec; int main() { a = 31; // 'ambiguity' tip SENTAKS HATASIDIR. Çünkü yukarıdaki 'using namespace' bildirimlerinden sonra // ilgili isim alanı içerisindeki öğeler sanki isim alanı yokmuş gibi davranmakta. // Bu da derleyicinin iki adet 'a' ismini bulmasına sebebiyet vermektedir. } >> 'ADL' : İlgili fonksiyonlara geçilen argümanlara bakarak fonksiyonların isimlerinin nerede aranacağına karar verilmesi durumudur, Argümana Bağlı İsim Arama. (Koenig Lookup / Argument Dependant Lookup). Bir diğer deyiş ile "Eğer bir fonksiyon çağrısında fonksiyona gönderilen argümanlardan biri bir isim alanı içinde tanımlanan bir türe ilişkin ise söz konusu fonksiyonun ismi bu isim alanında da aranır." şeklinde açıklama getirebiliriz. * Örnek 1, //.. namespace Nec{ class Data{}; enum class Color{White, Black}; void func(Data); void foo(int); void myFunc(Color); void myFoo(std::vector vec); } int main() { Nec::Data myData; func(myData); // Legal. Çünkü argüman olan ifade 'Nec' isim alanı içerisinde tanımlanan 'Data' sınıf türüne ait. // Dolayısıyla 'func' ismi 'Nec' isim alanı içerisinde de ARANACAK. foo(12); // SENTAKS HATASI. Çünkü argüman olan ifade 'Nec' isim alanı içerisinde tanımlanan bir türe ilişkin değil. // Dolayısıyla klasik isim arama kurallarına tabii. myFunc(Nec::Color::Black); // 'ADL' burada da geçerli. std::vector myVec; myFoo(myVec); // 'ADL' burada da geçerli. Çünkü ilgili fonksiyonun parametresi 'Data' türünden olmamasına rağmen, // argüman olan vektör 'Nec' isim alanı içerisinde tanımlanan 'Data' türü ile ilişkin olmasından dolayı, // 'myFoo' ismi 'Nec' isim alanı içerisinde de aranacaktır. } * Örnek 2, //.. int main() { std::cout << "Hello World\n"; // Burada çağrılan operatör fonksiyonu 'global operator function'. Bu fonksiyona geçilen parametre de // 'std' isim alanı içerisinde tanımlanmıştır. Dolayısıyla bu fonksiyonun ismi 'std' isim alanı içerisinde // de aranmaktadır. std::operator<<(std::cout, "Hello World\n"); // 'ADL' olmasaydı eğer bu şekilde çağırmak durumunda kalabilirdik. operator<<(std::cout, "Hello World\n"); // 'ADL' in kullanıldığının açık bir örneği. } * Örnek 3, Mülakat Sorusu //.. int main() { std::vector ivec; count(ivec.begin(), ivec.end(), 7); // Bu 'count' fonksiyonuna gönderilen argümanlardan 'ivec' in türü 'iterator'. Bu 'iterator' sınıfı ise // 'vector' sınıfı içerisinde tanımlanmıştır. 'vector' sınıfı ise 'std' isim alanı içerisinde tanımlanmıştır. // Bundan dolayı 'count' ismi de 'std' isim alanı içerisinde arandı ve bulundu. auto n = count(begin(ivec), end(ivec), 7); // 'begin' ve 'end' fonksiyonlarına gönderilen argümen olan 'ivec', yukarıda da açıklandığı gibi, // 'std' isim alanı içerisinde tanımlı. Dolayısıyla 'begin' ve 'end' isimleri 'std' içerisinde arandı // ve bulundu çünkü bu ikisi 'global namespace function'. Bu iki fonksiyonun geri döndürdüğü değer de // 'std' isim alanı içerisinde tanımlı bir türe ait. Haliyle 'count' fonksiyonuna geçilen argümanlar da // 'std' isim alanında tanımlı. Buradan hareketle 'count' ismi de 'std' isim alanı içerisinde arandı ve bulundu. } >>> 'ADL' mekanizması ile 'FO' mekanizması birlikte nasıl çalışmaktadır? El cevap: Aşağıdaki örnekleri inceleyelim. * Örnek 1, #include using namespace std; namespace Neco{ class Data{}; void func(Data) // I { cout << "void Neco::func(Data) was called.\n"; } } void func(int) // II { cout << "void func(int) was called.\n"; } int main() { /* # OUTPUT # void func(int) was called. void Neco::func(Data) was called. */ Neco::Data mydata; func(12); // 'func' ismi 'block-scope' da bulunamadığından 'global namespace scope' da aranacak ve bulunacak. // 'Neco' isim alanında aranmayacaktır. func(mydata); // 'func' ismi 'ADL' mekanizmasından dolayı 'Nec' isim alanı içerisinde de aranmaktadır. // Parametreler 'exact-match' olduğundan, isim alanındaki çağrılacaktır. } * Örnek 2, #include using namespace std; namespace Neco{ class Data{}; void func(Data) // I { cout << "void Neco::func(Data) was called.\n"; } } void func(Neco::Data) // II { cout << "void func(Neco::Data) was called.\n"; } int main() { /* # OUTPUT # error: call of overloaded ‘func(Neco::Data&)’ is ambiguous note: candidate: ‘void func(Neco::Data)’ note: candidate: ‘void Neco::func(Neco::Data)’ */ Neco::Data mydata; func(mydata); // 'ambiguity'. Çünkü 'II' numaralı fonksiyon klasik isim arama kuralları sonucunda, // 'I' ise 'ADL' den kaynaklı isim arama sonucunda bulunuyor. } * Örnek 3, #include using namespace std; namespace Neco{ class Data{}; void func(Data) // I { cout << "void Neco::func(Data) was called.\n"; } } void func(Neco::Data) // II - Tanım { cout << "void func(Neco::Data) was called.\n"; } int main() { /* # OUTPUT # void func(Neco::Data) was called. */ void func(Neco::Data); // II - Bildirim Neco::Data mydata; func(mydata); // 'II' numaralı olan çağrılacaktır. } >> 'namespace' ler ya 'global namespace' içerisinde ya da bir 'namaspace' içerisinde tanımlanabilirler. Bunun harici alanlarda tanımlamazlar. >> 'namespace' ler için bir 'access-control' SÖZ KONUSU DEĞİLDİR. >> 'unnamed-namespaces' : Bu isim alanına koyulan her şey 'internal linkage' durumundadır. Çünkü C dilindeki 'internal linkage' haline getirme yolları C++ dilinde 'deprecated' edilmiş, yani o yöntemler istenmemektedir. Fakat sentaks kuralları açısından o yöntemler hala geçerlidir. İşte bu istenmeyen yöntemler yerine 'unnamed namespaces' kullanmalıyız. 'namespace' lerin kümülatif özelliği burada da geçerlidir. * Örnek 1, //.. void foo(); // C ve C++ dillerinde 'external linkage' statüsündeki bir global fonksiyon. static void func(); // C dilinde 'internal linkage' statüsündeki bir global fonksiyon. // C++ dilinde de geçerli fakat kullanılması İSTENMEMEKTEDİR. namespace{ void myCppFoo(); // C++ dilinde 'internal linkage' statüsündeki bir global fonksiyon bildirimi. // Artık gerek çağırırken gerek tanımlarken 'myCppFoo' ismini nitelememe gerek yok. // İlgili modül içerisinde direkt olarak görülür durumdadır. } >> 'nested-namespaces' : İçsel isim alanlarıdır. C++11 ile yeni bir kullanım yaklaşımı getirilmiştir. 'using namespace declaration' içerideki isim alanları için de kullanılabilir. * Örnek 1, //.. namespace A::B::C{ int x = 100; } // Burada 'A' ve 'B' isim alanlarının BİLDİRİLMİŞ/TANIMLANMIŞ OLMASINA GEREK YOKTUR. C++11 ile dile eklenmiştir. namespace A::B{ int y; } namespace A{ int z; } int main() { A::B::C::x = 31; A::B::y = 34; A::z = 56; } * Örnek 2, //.. namespace A { namespace B { int x; } using namespace B; // Bu alan A isim alanına aittir. B isim alanındaki öğeler kullanmak için 'B' ismi ile nitelememiz gerekiyor. // Fakat bu bildirim vesilesi ile bu gereklilik ortadan kalktı. int a = x; // Yukarıdaki 'using namespace B' bildirimden dolayı bu satır LEGALDİR. Eğer o bildirim olmasaydı, burada // SENTAKS HATASI oluşacaktı. // int a = B::x; // Eğer bahsi geçen bildirim olmasaydı. } using namespace A ; int main() { x = 42; // 'using namespace A' bildiriminden dolayı bu atama geçerli. Çünkü o isim alanı içerisinde biz B için de // 'using namespace' bildiriminde bulunduk. } * Örnek 3, 'ADL' her zaman da çalışmıyor. //.. namespace A { class Neco{}; namespace B { void func(A::Neco other); } using namespace B; // I } int main() { A::Neco nec; func(nec); // 'I' numaralı bildirim olmasına rağmen 'ADL' mekanizması burada ÇALIŞMAMAKTADIR. İlgili 'func' ismini 'B' // isim alanında aramamaktadır. Sentaks hatası. A::B::func(nec); // Legal } >> 'inline-namespaces' : Modern C++ ile dile gelen bir isim alanıdır. 'inline' anahtar sözcüğü ile nitelenir ilgili isim alanı. Nitelediği isim alanı içerisindeki öğeler, dışarıdaki isim alanında da görülür hale gelir. Sanki o içerideki isim alanı yokmuş gibi davranır. * Örnek 1, //.. #include namespace A{ /*inline*/ // Alternative II namespace B{ int x = 10; } // using namespace B; // Alternative I } int main() { // Normal Şartlardaki Kullanım. A::B::x = 100; std::cout << "A::B::x : " << A::B::x << "\n"; // OUTPUT => A::B::x : 100 // Alternative I : İlgili bildirimi yaptıktan sonraki kullanım. // A::x = 200; std::cout << "A::x : " << A::x << "\n"; // OUTPUT => A::x : 200 // Alternative II : 'inline' anahtar sözcüğü ile nitelersek. // A::x = 300; std::cout << "A::x : " << A::x << "\n"; // OUTPUT => A::x : 300 } * Örnek 2, //.. inline namespace A{ inline namespace B{ inline namespace C{ int x = 100; } } } int main() { x = 31; // Bütün isim alanları 'inline' olarak nitelendiğiden, bu şekilde kullanım da mümkündür. } >>> Versiyonlamada kullanıılır. * Örnek 1, Koşullu Derleme komutları kullanılmazsa: //.. #include namespace Neco{ /*inline*/ namespace Ver1{ class Myclass{ }; } /*inline*/ namespace Ver2{ class Myclass{ }; } } int main() { // Normal şartlarda kullanım: Neco::Ver1::Myclass mx; Neco::Ver2::Myclass my; // Ver1 isim alanı 'inline' olarak nitelenirse: Neco::Myclass mz; // Ver1 içerisinde tanımlanan sınıf türünden. // Ver2 isim alanı 'inline' olarak nitelenirse: Neco::Myclass ma; // Ver2 içerisinde tanımlanan sınıf türünden. } * Örnek 2, Koşullu Derleme komutları kullanılırsa: // #include // #define OLD_VERSION // Bu satırı yorum satırı olmaktan çıkartırsak, Ver1 isim alanı 'inline' haline gelecektir. // #define NEW_VERSION // Bu satırı yorum satırı olmaktan çıkartırsak, Ver2 isim alanı 'inline' haline gelecektir. namespace Neco{ #ifdef OLD_VERSION inline #endif namespace Ver1{ class Myclass{ }; } #ifdef NEW_VERSION inline #endif namespace Ver2{ class Myclass{ }; } } int main() { Neco::Myclass mx; // 'OLD_VERSION' kelimesi tanımlı hale gelirse bu nesnemiz Ver1::Myclass türünden olacak. // 'NEW_VERSION' kelimesi tanımlı hale gelirse bu nesnemiz Ver2::Myclass türünden olacak. } >>> 'nested namespace' senaryosunda devreye girmeyen 'ADL' mekanizması, 'inline namespace' durumunda DEVREYE GİRİYOR. * Örnek 3, 'ADL' artık devrede. //.. namespace A{ class Neco{}; inline namespace B{ void func(A::Neco other); } } int main() { A::Neco nec; func(nec); // 'B' isim alanı 'inline' olarak nitelendiği için, 'ADL' devreye giriyor ve isim arama // 'B' isim alanında da yapılıyor. A::B::func(nec); // Legal } >> 'friend' bildirimi alan fonksiyonları çağırabilmek için 'ADL' mekanizmasına da ihtiyacımız vardır. * Örnek 1, //.. #include class Myclass{ public: friend void func(int); friend void foo(Myclass); }; void foo(Myclass other) { std::cout << "void func(Myclass other) was called.\n"; } int main() { func(12); // SENTAKS HATASI. Çünkü 'func' ismi BULUNAMIYOR. Myclass mx; foo(mx); // OUTPUT => void foo(Myclass other) was called. } >> 'namespace alias' : Bir isim alanına, o isim alanının yerine geçecek Eş İsim verilmesi durumudur. * Örnek 1, //.. namespace Nec_Erg { int x = 100; } // namespace Nec = Nec_Erg; // I int main() { // Normal Kullanım Nec_Erg::x = 10; // 'I' numaralı bildirimi yaparsak bu şekilde kullanabiliriz. // Nec::x = 20; } * Örnek 2, 'nested namespace' için de bunu kullanabiliriz. //.. namespace Nec_Erg{ namespace op_string{ namespace details{ int x = 100; } } } // namespace myDetails = Nec_Erg::op_string::details; // I int main() { // Normal Kullanım Nec_Erg::op_string::details::x = 10; // 'I' numaralı bildirimi yaparsak bu şekilde kullanabiliriz. // myDetails::x = 10; } > En temel C++ hataları: >> Başlık dosyaları içerisinde 'using namespace declaration' ve 'using declaration' kullanılması. Bundan dolayı bizim başlık dosyalarımızı kullanan kişiler de bu bildirimden etkileneceğinden, bunu yapmaktan kaçınmalıyız. >> Sık sık 'using namespace declaration' bildirimi kullanmaktan kaçınmalıyız. Çünkü bu bildiri yaptığımız zaman İSİM ÇAKIŞMA RİSKİNİ attırmış oluyoruz. Alternatif olarak, * Örnek 1, '.cpp' dosyalarında implementasyon yaparken kullanabiliriz. // ali.hpp namespace Nec{ class Data{ public: Data func(Data other); }; } // ali.cpp Nec::Data Nec::Data::func(Data other) { } // Yukarıda; // i. Fonksiyon parametresinin olduğu parantez 'namespace' içerisinde kabul edildiğinden 'Nec' nitelemesine gerek yok. // ii. Fakat fonksiyon ismi için 'Nec' nitelemesi yapmak ZORUNDAYIZ. // iii. Aynı şekilde fonksiyon geri dönüş değerinin türünü yazarken de aynı NİTELEMEYİ YAPMAK ZORUNDAYIZ. // Görüldüğü üzere kodu okumak zorlaştı. Bunun için '.cpp' dosyası içerisinde 'using namespace Nec' bildirimini yapabiliriz. // Data Data::func(Data other) { } * Örnek 2, 'namespace' lerin kümülatif etkisinden faydalanmak: // ali.hpp class Data{ public: Data func(Data other); }; // Legacy kodlarda yukarıdaki gibi bir sınıf bildirimi olduğunu düşünelim. Zaman içerisinde kodlar revize edilirken, // sınıf tanımının bir isim alanı içerisine alındığını düşünelim. Dolayısıyla, durum aşağıdaki gibi olacaktır; namespace Proj{ class Data{ public: Data func(Data other); }; } // ali.cpp // Artık yukarıdaki sınıfa ait fonksiyonların tanımlarında ilgili 'Proj' isim alanını kullanmamız gerekmektedir. // Bunu da iki şekilde yapabiliriz: // i. İlgili '.cpp' dosyasının içerisinde 'using namespace Proj' bildirimini yapmak. using namespace Proj; void Data::func(Data other) { } // ii. İlgili '.cpp' dosyasındaki kodların hepsini 'namespace Proj' ile çevrelemek. namespace Proj{ void Data::func(Data other) { } } * Örnek 3, ilgili bildirimi lokal olarak kullanabiliriz: //.. void func(std::vector& myvec) { using namespace std; // Bu bildirim makul görülebilir çünkü sadece bu fonksiyon bloğuna hitap etmektedir. } > 'public interface' : 'client' lara açılmış ara yüz demektir. 'ADL' burada fayda sağlamaktadır. * Örnek 1, // date.hpp namespace Neco{ class Date{ public: void set(int, int, int); // I }; int compare(const Date&, const Date&); // II } void showStatus(void); // III // Yukarıdaki 'I', 'II' ve 'III' numaralı fonksiyonların hepsi ilgili 'Date' sınıfının 'public interface' sine DAHİLDİR. // Çünkü bu üç fonksiyon da müşteri kodlar tarafından çağrılabilir. // main.cpp #include "date.hpp" int main() { showStatus(); // Müşteri olarak bu fonksiyonu çağırabiliriz. Neco::Date dx, dy; //.. dx.set(1,2,3); // Müşteri olarak bu fonksiyonu çağırabiliriz. auto result = compare(dx, dy); // Müşteri olarak bu fonksiyonu çağırabiliriz. Ek olarak 'ADL' burada da iş görmektedir. Çünkü 'compare' fonksiyonuna // geçilen argümanlar 'Neco' isim alanı içerisinde tanımlanan 'Date' türüne ilişkin öğeler. Bundan dolayı 'compare' // ismi 'Neco' isim alanında da arancaktır. // Buradan hareketler 'ADL' in felsefi olarak varlık nedeni 'public-interface' bünyesinde olan global fonksiyonları, // ilgili isim alanı ile nitelemeden, çağırabilmektir. // Global fonksiyon olduğu için, onu isim alanı ile niteleme zorunluluğunu ortadan kaldırıyor. } > Standart Template Library (STL) : Başlangıç olarak 'std::string' sınıf şablonundan incelemeye başlayacağız. >> 'std::string' sınıf şablonunun incelenmesi : Bir dinamik dizi sınıf şablonudur. Yani arkada dinamik bir dizi vardır. En çok kullanılan sınıf çeşididir. 'STL' kaplarından bir tanesidir. Temsili olarak şablon şu şekildedir, * Örnek 1, template, typename A = allocator> class basic_string{ }; // C : Yazının karakterinin türü // B : Yazının karakterleriyle ilgili temel işlemlerin nasıl yapılacağını belirleyen şablon parametresi. // Ekseriyet ile bu parametreye argüman geçmeyeceğiz, varsayılan argümanı kullanacağız. // A : Dinamik bellek yönetiminden sorumlu sınıfı belirlemektedir. Ekseriyet ile bu parametreye de argüman // geçmeyeceğiz, varsayılan argümanı kullanacağız. // 'string' ismi de bir 'typedef' tür eş isim bildirimi olduğundan, tanımlanmış şekli şudur, // typedef std::basic_string std::string; // diyebiliriz. int main() { std::basic_string myString; // LEGAL. // std::string myStringTwo; // LEGAL. std::basic_string myStringThree; // LEGAL. // wstring myStringFour; // LEGAL. } >>> 'Fat-Interface' sahiptir. Yani kullanıcıya sunduğu fonksiyonlar bir hayli fazladır. Bu fonksiyonların bazıları 'class-member-functions', bazıları 'global namespace functions' ve bazıları da 'algorithm' başlık dosyasında bildirilen/tanımlanan fonksiyon şablonlarından oluşmaktadır. >>> Başlık dosyamızın ismi 'string' şeklindedir. 'cstring' ismi ise C dilindeki 'string.h' başlık dosyasının C++ dilindeki hali. >>> 'string_view' başlık dosyasındaki fonksiyonlar ile 'regex' başlık dosyasındaki fonksiyonlar da bizim 'string' sınıfımız ile ilişkili fonksiyonlardır. >>> Bünyesinde üç adet değişken tutmaktadır. Bunlardan bir tanesi 'allocate' edilmiş bellek bloğunun başını, diğeri ise aynı bellek bloğunun sonunu tutmaktadır. Üçüncü gösterici ise bu bellek bloğundaki yazının sonunu göstermektedir. Ek olarak, ilgili yazının sonunu tutan ile bellek bloğunun sonunu tutan öğenin gösterici olma zorunluluğu da yok ama bellek bloğunun başını tutanın gösterici olma zorunluluğu vardır. Böylelikle 'pointer-aritmatich' mekanizmasını kullanarak da işlerimizi halledebiliriz. >>> İlgili sınıfımız arka planda dinamik dizi veri yapısında barındırmaktadır ki bu veri yapısında; >>>> Sondan yapılan ekleme ve silme işlemleri 'constant-time / O(1)' karmaşıklığındadır. Ama amorti edilmiş 'constant-time' olduğunu da unutmayalım. >>>> Sondan olmayan ekleme ve silme işlemleri ise 'linear-complexlity / O(n)' şeklinde. >>>> Yine aynı tip veri yapısında indeks ile erişim de 'constant-time' zaman karmaşıklığındadır. Çünkü döngüsel bir yapı kullanmamıza gerek kalmıyor. >>>> Ayrıca ilgili dinamik dizide yer kalmadığında, eğer yeniden bir ekleme işlemi daha yapılırsa, 'reallocation' süreci başlatılacak. Bu süreçte ise önce var olan bellek bloğunun 1.5 katı veya 2 katı büyüklüğünde yeni bir bellek alanı ayrılacak. Eski bellek alanındaki yazılar yeni bellek alanına TAŞINACAK. Sonrasında da eski bellek bloğu 'free' edilecek. Buradaki 'reallocation' senaryosuna dikkat etmemiz gerekiyor. Çünkü; DİKKATLİ KULLANILMADIĞINDA İLAVE BİR MALİYET GETİRMEKTEDİR. Örneğin, 40000 byte lık bir belleğimiz var ve ağzına kadar dolu. Biz ise sadece bir karakter eklemesi yapmamız gerekiyor. Bu durumda bu 40000 byte lık bellek alanımız taşınacaktır. Gereksiz yere 40000 byte lık bir alan taşınmış oldu. Bunun yerine bellek bloğunun kaç byte olacağını ÖN GÖREREK hareket etsek, işe başlamadan yeterli büyüklükte bir alanı bir defa da ayırsak, bu taşıma ve yeni yer israfına da gerek KALMAYABİLİR. Yani baştan 'reserve' yapmamız GÜZEL OLUR. Ek olarak bu 'reallocation' süreci 'string' sınıfımız içerisindeki 'pointer' ve 'reference' lar 'dangling' DURUMUNA DÜŞEBİLİRLER >>> [ 'size' / 'capacity' ] : [ Veri alanında o an tutulan toplam eleman sayısıdır / Veri alanında o an tutulabilecek maksimum eleman sayısıdır]. Örneğin, 'size' değerimiz 100 olsun ve 'capacity' değerimiz de 500 [100/500]. Buradan hareketle 'reallocation' olmadan 400 adet da eleman ekleyebiliriz. Bu durumda [ 500 / 500 ] olacaktır. Bundan sonraki yapılacak ilk ekleme işleminde 'reallocation' işlemi gerçekleşecektir. >> Mülakat Sorusu, SSO / SBO //.. #include #include int main() { /* # OUTPUT # sizeof int* : 8 // Bir göstericinin boyutu. sizeof string : 32 // Görünüşe göre sadece üç adet gösterici içermemektedir, ilgili std::string sınıfı. Bunun nedeni SSO / SBO // implementasyonundan kaynaklanmaktadır. */ std::cout << "sizeof int* : " << sizeof(int*) << "\n"; std::cout << "sizeof string : " << sizeof(std::string) << "\n"; // SSO (Small String Optimization) / SBO (Small Buffer Optimization) : 'static' bir bellek alanı kullanmak ile // 'dynamic' bir bellek alanı kullanmak arasında 40-50 kat kadar bir fark vardır. Bu maliyeti minimize etmek için // 'string' sınıfımız bünyesinde ekstra bir 'dizi' daha tutmaktadır. Yazıların boyutunun küçük olduğu durumlarda, // yazıları tutmak için, Dinamik Bellek Alanı ile uğraşmadan direkt olarak Statik Bellek Alanındaki bu diziyi kullanıyor. // İşte yukarıdaki byte farkı da bu böyle bir dizinin varlığına işaret etmektedir. Eğer yazımız bu dizide tutulamayacak // kadar büyükse ya da sonradan bu dizide tutulamayacak kadar büyük hale gelirse, artık Dinamik Bellek Alanını kullanmaya // başlıyor. } * Örnek 1, yukarıda açıklanan SSO implementasyonunun ispatı: 'operator new' ve 'operator delete' fonksiyonlarının 'overload' edilemeleri. //.. #include #include void* operator new(size_t n) { std::cout << "operator new was called. size : " << n << " byte.\n"; void* vp = std::malloc(n); if(!vp) throw std::bad_alloc{}; std::cout << "address of allocated block : " << vp << "\n"; return vp; } void operator delete(void* vp) { std::cout << "operator delete was called.\naddress of allocated block : " << vp << "\n"; if(vp) std::free(vp); } int main() { std::string str{"necati"}; // Dizimizin boyutu küçük olduğundan Dinamik Bellek Alanı ayrılmadı. /* # OUTPUT # */ std::string str{"necati ergin Cpp anlatiyor."}; // Dizimizin boyutu artık ilgili diziye sığmadığından, Dinamik Bellek Alanı kullanılmaya başlandı. /* # OUTPUT # operator new was called. size : 28 byte. address of allocated block : 0x559527a2f2c0 operator delete was called. address of allocated block : 0x559527a2f2c0 */ } >>> 'std::string::size_type' : Bir tür eş ismidir. Genel olarak 'string' sınıfında 'size_t' türüne tekabül etmektedir, duruma göre başka bir tür de olabilir. Çünkü 'string' sınıfı da bir sınıf şablonu. İşte bu nedenden dolayı bizlerin 'string::size_type' şeklinde kullanması daha mantıklı. İş bu 'std::string:size_type' eş ismi aşağıdaki noktalarda kullanılmaktadır: >>>> 'string' sınıfının 'interface' sindeki fonksiyonların geri dönüş değer türü ve/veya parametre türü olarak kullanılmaktadır. >>>> 'string' sınıfındaki fonksiyonların bir çoğu yazıların karakterinin 'indeks' ini istemektedir. İşte bu indeksin türü 'std::string::size_type' türünden. >>>> 'string' sınıfındaki yazı uzunluğu türü olarak da 'std::string::size_type' kullanılmaktadır. >>>> Tane-adet türü olarak da 'std::string::size_type' kullanılmaktadır. >>> 'std::string' sınıfının 'interface' fonksiyonları: >>>> 'string::size_type string::size()const' fonksiyonu: Bir 'for-loop' içerisinde sürekli olarak bu fonksiyona çağrı yapılması sorun teşkil etmez. Derleyici 'inline' olarak açmaktadır iş bu fonksiyonu. Bir nevi sabit bir sayı kullanılmış gibi oluyor. Yazının uzunluk bilgisini döndürür. * Örnek 1, #include #include int main() { std::string name{"necati"}; for(size_t i = 0; i < name.size() ; ++i) { std::cout << "[" << name[i] << "]\n"; } } >>>> 'string::size_type string::length()const' fonksiyonu ve 'string::size_type string::capacity()const' fonksiyonu: * Örnek 1, #include #include int main() { /* # OUTPUT # --------- size : 9 leng : 9 capa : 15 --------- --------- size : 15 leng : 15 capa : 15 --------- --------- size : 16 leng : 16 capa : 30 --------- */ std::string str{"ali erkan"}; std::cout << "---------\n"; auto strSize = str.size(); // 'constant-time' operation // std::string::size_type n = str.size(); auto strLength = str.length(); // 'constant-time' operation // std::string::size_type n = str.length(); auto strCap = str.capacity(); // 'constant-time' operation // std::string::size_type n = str.capacity(); std::cout << "size : " << strSize << "\n"; std::cout << "leng : " << strLength << "\n"; std::cout << "capa : " << strCap << "\n"; std::cout << "---------\n"; str += "012345"; std::cout << "---------\n"; std::cout << "size : " << str.size() << "\n"; std::cout << "leng : " << str.length() << "\n"; std::cout << "capa : " << str.capacity() << "\n"; std::cout << "---------\n"; str += '6'; std::cout << "---------\n"; std::cout << "size : " << str.size() << "\n"; std::cout << "leng : " << str.length() << "\n"; std::cout << "capa : " << str.capacity() << "\n"; std::cout << "---------\n"; } * Örnek 2, Reserve işlemi gerçekleştirmemenin sonuçları: #include #include int main() { /* Link : https://godbolt.org/z/xnYGEPhfv # OUTPUT # - GCC & Clang 1. reallocation occured. Size / Capacity : [16 / 30] 2. reallocation occured. Size / Capacity : [31 / 60] 3. reallocation occured. Size / Capacity : [61 / 120] 4. reallocation occured. Size / Capacity : [121 / 240] 5. reallocation occured. Size / Capacity : [241 / 480] 6. reallocation occured. Size / Capacity : [481 / 960] 7. reallocation occured. Size / Capacity : [961 / 1920] 8. reallocation occured. Size / Capacity : [1921 / 3840] 9. reallocation occured. Size / Capacity : [3841 / 7680] 10. reallocation occured. Size / Capacity : [7681 / 15360] 11. reallocation occured. Size / Capacity : [15361 / 30720] 12. reallocation occured. Size / Capacity : [30721 / 61440] 13. reallocation occured. Size / Capacity : [61441 / 122880] 14. reallocation occured. Size / Capacity : [122881 / 245760] 15. reallocation occured. Size / Capacity : [245761 / 491520] 16. reallocation occured. Size / Capacity : [491521 / 983040] 17. reallocation occured. Size / Capacity : [983041 / 1966080] # OUTPUT # - MSVC 1. reallocation occured. Size / Capacity : [16 / 31] 2. reallocation occured. Size / Capacity : [32 / 47] 3. reallocation occured. Size / Capacity : [48 / 70] 4. reallocation occured. Size / Capacity : [71 / 105] 5. reallocation occured. Size / Capacity : [106 / 157] 6. reallocation occured. Size / Capacity : [158 / 235] 7. reallocation occured. Size / Capacity : [236 / 352] 8. reallocation occured. Size / Capacity : [353 / 528] 9. reallocation occured. Size / Capacity : [529 / 792] 10. reallocation occured. Size / Capacity : [793 / 1188] 11. reallocation occured. Size / Capacity : [1189 / 1782] 12. reallocation occured. Size / Capacity : [1783 / 2673] 13. reallocation occured. Size / Capacity : [2674 / 4009] 14. reallocation occured. Size / Capacity : [4010 / 6013] 15. reallocation occured. Size / Capacity : [6014 / 9019] 16. reallocation occured. Size / Capacity : [9020 / 13528] 17. reallocation occured. Size / Capacity : [13529 / 20292] 18. reallocation occured. Size / Capacity : [20293 / 30438] 19. reallocation occured. Size / Capacity : [30439 / 45657] 20. reallocation occured. Size / Capacity : [45658 / 68485] 21. reallocation occured. Size / Capacity : [68486 / 102727] 22. reallocation occured. Size / Capacity : [102728 / 154090] 23. reallocation occured. Size / Capacity : [154091 / 231135] 24. reallocation occured. Size / Capacity : [231136 / 346702] 25. reallocation occured. Size / Capacity : [346703 / 520053] 26. reallocation occured. Size / Capacity : [520054 / 780079] 27. reallocation occured. Size / Capacity : [780080 / 1170118] */ std::string str{"necati ergin"}; auto lastCap = str.capacity(); int reallocationCounter = 0; for(int i = 0; i < 1000000; ++i) { str.push_back('x'); if(str.capacity() > lastCap) { std::cout << ++reallocationCounter << ". reallocation occured. Size / Capacity : [" << str.size() << " / " << str.capacity() << "]\n"; lastCap = str.capacity(); } } } * Örnek 3, Reserve işlemi gerçekleştirmenin sonuçları: #include #include int main() { /* Link : https://godbolt.org/z/3x9sevT7q # OUTPUT # - GCC & Clang 1. reallocation occured. Size / Capacity : [500001 / 1000000] 2. reallocation occured. Size / Capacity : [1000001 / 2000000] # OUTPUT # - MSVC 1. reallocation occured. Size / Capacity : [500016 / 750022] 2. reallocation occured. Size / Capacity : [750023 / 1125033] */ std::string str{"necati ergin"}; str.reserve(500000); // Yazının uzunluğunun bir şekilde 500.000 karakter olduğu ön görülmüş olsun. auto lastCap = str.capacity(); int reallocationCounter = 0; for(int i = 0; i < 1000000; ++i) { str.push_back('x'); if(str.capacity() > lastCap) { std::cout << ++reallocationCounter << ". reallocation occured. Size / Capacity : [" << str.size() << " / " << str.capacity() << "]\n"; lastCap = str.capacity(); } } } >>>> Parametrik yapısı 'const std::string&' şeklinde olan fonksiyonlar, bizden bir 'string' sınıf türünden nesne istemektedir. >>>> Parametrik yapısı 'const char*' şeklinde olan fonksiyonlar ise bizden 'Null-terminated-string / C-string' istemektedir. Bu fonksiyona geçilecek yazının sonunda '\0' karakterinin olması kullanıcı olarak bizlerin sorumluluğunda. Eğer sonunda '\0' karakteri olmayan bir adres/yazı geçersek de 'Tanımsız Davranış' meydana gelir. Herhangi bir 'exception throw' ETMEMEKTEDİR. >>>> Parametrik yapısı 'const char*, size_t' şeklinde olan fonksiyonlara kabaca 'data' parametreli fonksiyonlar denmektedir. Bu da şu manaya gelmektedir: BİZDEN BİR ADRES İSTENMEKTEDİR VE BU ADRESTEN SONRA KAÇ KARAKTER ÜZERİNDE İŞLEM YAPILACAKSA O KARAKTERİN ADEDİ İSTENMEKTEDİR. Örneğin, 'str, 10' şeklinde bir argüman geçildiğide 'str' adresinden itibaren ilk 10 karakterlik yazı argüman oluyor. Velevki 'str' adresindeki dizinin uzunluğu 10 karakterden de az ise, 'Tanımsız Davranış' meydana gelecektir. Haliyle geçilecek olan argümanın taşma yapıp yapmayacağı bizim sorumluluğumuzda. Bu konunun 'Null-Character' ile bir alakası YOKTUR. >>>> Parametrik yapısı 'const char*, const char*' şeklinde olan fonksiyonlara 'range' parametreli fonksiyonlar denmektedir. Burada sol taraftaki argüman olan ifadenin 'range' kapsamında olduğu, sağ taraftaki argümanın ise 'range' kapsamında olmadığını belirtmeliyiz. Yani bir tanesi başlangıç noktası, diğeri ise bitiş noktası. Fakat sol taraftaki ifade eğer sağ taraftaki ifadeye eşit olamayacaksa yine 'Tanımsız Davranış' meydana gelir. Örneğin, sol taraftaki adresin çifter çifter arttırılması sonucu, sağ taraftaki adresi es geçmesi. Bu iki konum bilgisi arasındaki yazı argüman olarak geçilmektedir. >>>> Parametrik yapısı 'size_t, char' türden olan fonksiyonlara 'fill' parametreli fonksiyonlar denmektedir. Yani argüman olarak 'n' adedince 'c' karakteri. >>>> Parametrik yapısı 'const std::string&, size_t' türden olan fonksiyonlara 'sub-string' parametreli fonksiyonlar denmektedir ve geçilen argümanlardan sol taraftaki 'string' nesnesinin sağ taraftaki argüman olan indeksten başlayarak işlem yapmaktadır. >>>> Parametrik yapısı 'const std::string&, size_t, size_t' türden olan fonksiyonlara özelleştirilmiş 'sub-string' fonksiyonlar denmektedir. Bunlar sol argüman olan sınıf nesnesinin ortadaki argüman olan indeksinden başlayarak sağ argüman olan karakter adedince ilerleyen işlemler yapmaktadır. >>>> Parametrik yapısı 'char' olanlar ise sadece bir karakter istemektedir. >>>> Parametrik yapısı 'initializer_list' olan fonksiyonlar vardır. Bunlar '{}' içerisine virgüller ile ayrılan karakterler 'initializer_list' oluşturur. Örneğin, "{'a', 'l', 'p'}" şeklindeki argümanlardır. >>> 'string' sınıfımız bir sınıf şablonu olduğundan, sadece çağırmış olduğumuz fonksiyonların kodları yazılacaktır. * Örnek 1, //.. int main() { std::string str(20, '0'); // 'Ctor' kodu yazıldı. 20 elemanlı ve elemanları 'null-string' olan bir yazı oluşturuldu. str.length(); // İş bu fonksiyonun kodu şimdi yazıldı. // 'Dtor' çağrılacağı için onun da kodu yazıldı. } >>> 'string' sınıfının Kurucu İşlevlerinin incelenmesi: >>>> 'string' nesnesinin 'Default Init.' edilmesi : Uzunluğu sıfır olan, herhangi bir yazı da tutmayan bir nesnedir. * Örnek 1, #include #include void print(const std::string& other) { std::cout << "[" << other.length() << "] = \"" << other << "\"\n"; } int main() { std::string s1; print(s1); // OUTPUT => [0] = "" } >>>> 'C-string' Parametreli 'Ctor.' : 'Null-Karakter' görene kadar geçilen karakterlerden bir yazı oluşturulacak. * Örnek 1, #include #include void print(const std::string& other) { std::cout << "[" << other.length() << "] = \"" << other << "\"\n"; } int main() { std::string s2 = "alican"; // std::string s2("alican"); // 'direct init.' // std::string s2{"alican"}; // 'direct-list init.' print(s2); // OUTPUT => [6] = "alican" char buffer[] = "today is saturday."; std::string s3{buffer}; print(s3); // OUTPUT => [18] = "today is saturday." } >>>> 'Range' Parametreli 'Ctor' : İlk argümanın gösterdiği karakter dahil, ikinci argümanın gösterdiği karakter hariç, bu iki argüman arasındaki yazıların tamamı ile oluşturulan yazı. * Örnek 1, #include #include void print(const std::string& other) { std::cout << "[" << other.length() << "] = \"" << other << "\"\n"; } int main() { char buffer[] = "today is saturday."; std::string s3{buffer, buffer + 5}; print(s3); // OUTPUT => [5] = "today" std::string s2{buffer + 6, buffer + 8}; print(s2); // OUTPUT => [2] = "is" } >>>> 'Data' Parametreli 'Ctor' : İlk argüman olarak bir adres, ikinci argüman olarak da bir tam sayı alan ve yazıyı ilgili adresten başlayıp ilgili tam sayı kadar gittikten sonra oluşturulan yazı. 'Range' parametre ile KARIŞTIRMAYALIM. * Örnek 1, //.. #include #include void print(const std::string& other) { std::cout << "[" << other.length() << "] = \"" << other << "\"\n"; } int main() { char buffer[] = "today is saturday."; std::string s3{buffer, 8}; print(s3); // OUTPUT => [8] = "today is" std::string s2{buffer + 8, 9}; print(s2); // OUTPUT => [9] = " saturday" std::string s1{buffer, 60}; // 'Tanımsız Davranış' print(s1); // OUTPUT => [60] = "today is saturday.w[htIZp��bd�o�+eU��;" } >>>> 'Init. List' Parametreli 'Ctor' : Virgüller ile ayrılmış karakterlerden yazı meydana getirilmesi. Bu listenin elemanları 'char' türden yada 'char' türüne dönüştürülebilecek bir tür olmak ZORUNDA. * Örnek 1, #include #include void print(const std::string& other) { std::cout << "[" << other.length() << "] = \"" << other << "\"\n"; } int main() { std::string s1 = {'e', 'n', 'e', 's'}; // std::string s2('e', 'n', 'e', 's'); // 'direct init.' // SENTAKS HATASI std::string s3{'e', 'n', 'e', 's'}; // 'direct-list init.' print(s1); // OUTPUT => [4] = "enes" print(s3); // OUTPUT => [4] = "enes" } >>>> 'fill' Parametreli 'Ctor' : Birinci argümanı 'size_t' türünden, ikinci argüman ise bir karakter sabiti olan ve 'n' adedince 'c' karakteri ile doldurulan bir yazı. * Örnek 1, #include #include void print(const std::string& other) { std::cout << "[" << other.length() << "] = \"" << other << "\"\n"; } int main() { std::string s1(3, 'a'); // 'direct init.' OLDUĞUNA DİKKAT EDİN. print(s1); // OUTPUT => [3] = "aaa" } >> 'Init. List' sınıfının incelenmesi : Başlık dosyası 'initializer_list' isimli başlık dosyasında tanımlanmıştır. Bir sınıf şablonudur. >>> Açıklayıcı örnekler: * Örnek 1, //.. int main() { std::initializer_list myList{2, 3, 4, 5, 6, 5, 4, 3, 2}; auto n = {2, 3, 4, 5, 6, 5}; // 'n' değişkeninin türü : 'initializer_list' in 'int' açılımı şeklinde. std::cout << "type : <" << typeid(n).name() << ">\n"; // OUTPUT => type : auto m = {2.2, 3.3, 4.4, 5.5}; // 'm' değişkeninin türü : 'initializer_list' in 'double' açılımı şeklinde. std::cout << "type : <" << typeid(n).name() << ">\n"; // OUTPUT => type : auto q = {10.10f}; // 'q' değişkeninin türü : 'initializer_list' in 'float' açılımı şeklinde. std::cout << "type : <" << typeid(q).name() << ">\n"; // OUTPUT => type : auto p{10L}; // 'p' değişkeninin türü : 'long' şeklinde. Artık tür çıkarımı 'initializer_list' yönünde değil, 'primitive' // tür yönünde yapıldı. std::cout << "type : <" << typeid(p).name() << ">\n"; // OUTPUT => type : auto l{10,20}; // SENTAKS HATASI. => error: direct-list-initialization of ‘auto’ requires exactly one element [-fpermissive] } >>> Elimizde bu sınıf türünden bir nesne var ise ne yapabiliriz? El-cevap: Bu sınıf türü bünyesinde üç-dört adet fonksiyon barındırır. Bunlar '.size()', '.begin()' ve '.end()' şeklinde. * Örnek 1, #include int main() { std::initializer_list myList{2, 3, 4, 5, 6, 5, 4, 3, 2}; std::cout << "size of list : " << myList.size() << "\n"; // OUTPUT => size of list : 9 // Geleneksel Yaklaşım: for(auto first = myList.begin(); first != myList.end(); ++first) { std::cout << "[" << *first << "] "; // OUTPUT => [2] [3] [4] [5] [6] [5] [4] [3] [2] } std::cout << "\n"; // Modern Yaklaşım: for(auto index : myList) { std::cout << "[" << index << "] "; // OUTPUT => [2] [3] [4] [5] [6] [5] [4] [3] [2] } std::cout << "\n"; // Modern Yaklaşım v2: for(auto index : {2, 3, 4, 5, 6, 5, 4, 3, 2}) { std::cout << "[" << index << "] "; // OUTPUT => [2] [3] [4] [5] [6] [5] [4] [3] [2] } } >>> Elemanlarının birer 'Sabit-İfadesi' olma zorunluluğu YOKTUR. * Örnek 1, #include void func(int a, int b, int c, int d) { std::initializer_list myList{a,b,c,d}; // Legal for(auto index : myList) { std::cout << "[" << index << "] "; } } void func(const std::initializer_list& myList) { for(auto index : myList) { std::cout << "[" << index << "] "; // Legal } } int main() { func(1,2,3,4); // OUTPUT => [1] [2] [3] [4] func({9,8,7,6}); // OUTPUT => [9] [8] [7] [6] } /*============================================================================================================*/ (16_01_11_2020) > Standart Template Library (STL) (devam) : >> 'Init. List' sınıfının incelenmesi (devam) : >>> 'initializer_list' her zaman için 'const'. Dolayısıyla elemanlarına bir atama yapamıyoruz. Elemanları sadece okuma amaçlı kullanabiliriz. * Örnek 1, #include #include int main() { auto myList = {2,3,4,5}; auto iter = myList.begin(); std::cout << "first : " << *iter << "\n"; *iter = 10; // error: assignment of read-only location ‘* iter’ std::cout << "first : " << *iter << "\n"; } >>> İki adet 'initializer_list' i birbirine kopyaladığımız zaman iki adet ayrı arka-plan-dizisi ortada yok. Referans semantiği ile sınıf nesneleri birbirine kopyalanmakta. >>> Herhangi bir sınıfın hem 'initializer_list' parametreli hem de 'primitive' parametreli bir 'Ctor' olsun. '{}' çifti ile nesne hayata getirdiğimiz zaman 'initializer_list' parametreli 'Ctor' un çağrılma önceliği vardır. * Örnek 1, #include #include class Myclass{ public: Myclass(int x) { std::cout << "Myclass(int x) was called.\n"; } Myclass(int x, int y) { std::cout << "Myclass(int x, int y) was called.\n"; } Myclass(std::initializer_list iList) { std::cout << "Myclass(std::initializer_list iList) was called.\n"; } }; int main() { // Myclass m = {1, 2, 3, 4}; // OUTPUT => Myclass(std::initializer_list iList) was called. // Myclass mm{1, 2, 3, 4}; // OUTPUT => Myclass(std::initializer_list iList) was called. // Myclass m(12); // OUTPUT => Myclass(int x) was called. // Myclass mm{12}; // OUTPUT => Myclass(std::initializer_list iList) was called // Myclass m(12,21); // OUTPUT => Myclass(int x, int y) was called. // Myclass mm{12,21}; // OUTPUT => Myclass(std::initializer_list iList) was called Myclass m = 12; // OUTPUT => Myclass(int x) was called. Myclass mm = {12}; // OUTPUT => Myclass(std::initializer_list iList) was called. Myclass mmm = {12,24}; // OUTPUT => Myclass(std::initializer_list iList) was called. } >> 'string' sınıfının incelenmesi(devam) : >>> 'Fill' parametreli 'Ctor' ile 'initializer_list' parametreli 'Ctor' karşılaştırması: '{}' çifti ile nesne hayata getirdiğimiz zaman 'initializer_list' parametreli 'Ctor' çağrılacaktır. * Örnek 1, #include #include int main() { std::string str(49, 'A'); // 12 adet 'A' karakteri ile bir yazı oluşturuyor. std::cout << "[ " << str << " ]\n"; // OUTPUT => [ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ] std::string strstr{49, 'A'}; // ilgili 'Ctor' fonksiyonun parametresi 'initializer_list' şeklinde. Bundan dolayı her // iki argüman da Karakter Sabiti olarak ele alınacaktır. ASCII tablosuna göre // 49 rakamının karakter karşılığı '1' olduğundan '1A' yazısı oluşturuldu. std::cout << "[ " << strstr << " ]\n"; // OUTPUT => [ 1A ] } >>> Bir 'RAII' sınıfı olduğundan, 'string' sınıfının da bir 'Copy Ctor' ve 'Move Ctor' u var: * Örnek 1, #include #include int main() { std::string str{"necati ergin"}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [12] : necati ergin std::string strTwo = std::move(str); // Move Ctor çağrıldı. std::cout << "[" << strTwo.size() << "] : " << strTwo << "\n"; // OUTPUT => [12] : necati ergin std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : // 'strTwo' adresinden itibaren altı karakter ileri git ve o karakterden itibaren bir yazı oluştur. std::string strThree(strTwo, 6); std::cout << "[" << strThree.size() << "] : [" << strThree << "]\n"; // OUTPUT => [6] : [ ergin] // 'strTwo' adresinden itibaren altı karakter ileri git ve o karakterden ile o karakterden üç karakter sonraki // karakter arasındaki karakterler ile bir yazı oluştur. std::string strFour(strTwo, 6, 3); std::cout << "[" << strFour.size() << "] : [" << strFour << "]\n"; // OUTPUT => [3] : [ er] } >>> 'string' sınıfında 'char' parametreli bir 'Ctor' YOKTUR. Amacımız tek bir karakter ile birden fazla yöntem mevcut. * Örnek 1, #include #include int main() { // std::string str('A'); // error: no matching function for call to ‘std::__cxx11::basic_string::basic_string(char)’ // C-String Parametreli Ctor. Çağrısı: std::string str("A"); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [1] : [A] // Fill Parametreli Ctor. Çağrısı: std::string strTwo(1, 'A'); std::cout << "[" << strTwo.size() << "] : [" << strTwo << "]\n"; // OUTPUT => [1] : [A] // Init. List Ctor. Çağrısı: std::string strThree{'A'}; std::cout << "[" << strThree.size() << "] : [" << strThree << "]\n"; // OUTPUT => [1] : [A] } >>> Amacımız bir yazı oluşturmak ve o yazıyı döndürmek ise 'Call-by-value' mekanizmasını kullanmamız tavsiye edilmekte. Çünkü gerek 'Move Semantics' gerek 'Copy Ellision' mekanizmalarından dolayı kopyalama elimine ediliyor. * Örnek 1, #include #include class Myclass{ public: Myclass() { std::cout << "Myclass() was called. Address : " << this << "\n"; } Myclass(Myclass&& other) { std::cout << "Myclass(Myclass&& other) was called. Adress : " << this << "\n"; } }; Myclass func() { Myclass m; // I std::cout << "&m : " << &m << "\n"; // II return m; } int main() { /* # OUTPUT # I => Myclass() was called. Address : 0x7fffe0c5a957 II => &m : 0x7fffe0c5a957 III => &mm : 0x7fffe0c5a957 */ Myclass mm = func(); std::cout << "&mm : " << &mm << "\n"; // III } >>> 'string' sınıfının 'member-functions' incelemesi : >>>> 'string' sınıfının 'operator<<' fonksiyonu ve 'operator>>' fonksiyonları: Boşluk karakterleri yazıya dahil edilmemektedir. Onlar tıpkı 'scanf()' fonksiyonunda olduğu gibi ayraç olarak kullanılmakta. O yazılar buffer da kalıyor. Bu durumda bütün karakterleri buffer dan almak istiyorsak 'getline()' fonksiyonunu kullanmalıyız. C dilinde ise '.gets()/.gets_s()' ve '.fgets()' fonksiyonları yapmaktadır. * Örnek 1, #include #include int main() { std::string str, strTwo, strThree, strFour, strFive, strSix; std::cout << "Bir yazi girin : " ; std::cin >> str; // INPUT => c++ dili ne kadar da guzel degil mi? bence oyle. Hele de ogretmenın de kaliteli ise. std::cin >> strTwo; std::cin >> strThree; std::cin >> strFour; std::cin >> strFive; std::cin >> strSix; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [3] : c++ std::cout << "[" << strTwo.size() << "] : " << strTwo << "\n"; // OUTPUT => [4] : dili std::cout << "[" << strThree.size() << "] : " << strThree << "\n"; // OUTPUT => [2] : ne std::cout << "[" << strFour.size() << "] : " << strFour << "\n"; // OUTPUT => [5] : kadar std::cout << "[" << strFive.size() << "] : " << strFive << "\n"; // OUTPUT => [2] : da std::cout << "[" << strSix.size() << "] : " << strSix << "\n"; // OUTPUT => [5] : guzel std::string strSeven; std::getline(std::cin, strSeven); std::cout << "[" << strSeven.size() << "] : " << strSeven << "\n"; // OUTPUT => [59] : degil mi? bence oyle. Hele de ogretmenın de kaliteli ise. } >>>> Yazının karakterlerine erişim için yazılan fonksiyonların ayrıca 'const-overload' versiyonları da vardır. >>>>> '.operator[]' : 'const' olmayan nesnelerin ilgili indeksteki karakterine okuma ve yazma amacı ile erişebiliriz. Çünkü geri dönüş değeri 'call-by-reference' şeklinde. 'const' olan sınıf nesneler için bu operatorün geri dönüş değeri 'const-call-by-reference' olduğundan, sadece okuma amacı ile çağırabiliriz. Geçersiz indeks bilgisi geçildiğinde 'Tanımsız Davranış' meydana gelir, herhangi bir 'exception throw' ETMEZ. Yine bu operatörü kullansam, adresini bir C API sine gönderebilirim. * Örnek 1, #include #include void func(const char* ptr, size_t len); int main() { std::string name{"mehmet aksu"}; for(size_t i = 0; i < name.size(); ++i) std::cout << "[" << name[i] << "] "; // OUTPUT => [m] [e] [h] [m] [e] [t] [ ] [a] [k] [s] [u] std::cout << "\n"; name[8] = 'L'; std::cout << "[" << name.size() << "] : " << name << "\n"; // OUTPUT => [11] : mehmet aLsu const std::string surname{"cantoprak"}; // surname[2] = 'P'; // İlgili '.operator[]' fonksiyonunun 'const-overload' çağrılacağı için artık YAZMA YAPAMIYORUZ. try{ auto c = name[45]; // 'Tanımsız Davranış'. Ayrıca 'exception-throw' ETMEMEKTEDİR. } catch(std::exception& ex) { std::cout << "exception yakalandi : " << ex.what() << "\n"; // OUTPUT => } func(&name[0], name.size()); // LEGAL } >>>>> '.at()' fonksiyonu : Yukarıdaki '.operator[]' fonksiyonunda yazılanlar bu fonksiyon için de geçerlidir. Ekstra olarak bu fonksiyon hata durumunda 'exception-throw' etmektedir. * Örnek 1, #include #include int main() { std::string name{"mehmet aksu"}; for(size_t i = 0; i < name.size(); ++i) std::cout << "[" << name.at(i) << "] "; // OUTPUT => [m] [e] [h] [m] [e] [t] [ ] [a] [k] [s] [u] std::cout << "\n"; name.at(8) = 'L'; std::cout << "[" << name.size() << "] : " << name << "\n"; // OUTPUT => [11] : mehmet aLsu const std::string surname{"cantoprak"}; // surname.at(5) = 'P'; // İlgili '.at()' fonksiyonunun 'const-overload' çağrılacağı için artık YAZMA YAPAMIYORUZ. try{ auto c = name.at(45); // 'Tanımsız Davranış'. Ayrıca 'exception-throw' ETMEMEKTEDİR. } catch(std::exception& ex) { std::cout << "exception yakalandi : " << ex.what() << "\n"; // OUTPUT => exception yakalandi : basic_string::at: __n (which is 45) >= this->size() (which is 11) } } >>>>> '.front()' ve '.back()' fonksiyonları: Sırasıyla yazının ilk ve son karakterlerine erişim için kullanılır. Yine aynı şekilde 'const' nesneler için bu fonksiyonların da 'const-overload' ı vardır. Ayrıca ilgili 'string' nesnesi boşsa bu iki fonksiyon 'Tanımsız Davranış' a neden olur. * Örnek 1, #include #include int main() { std::string name{"beyhan"}; ++name.front(); std::cout << "[" << name.size() << "] : " << name << "\n"; // OUTPUT => [6] : ceyhan name.back() = '*'; std::cout << "[" << name.size() << "] : " << name << "\n"; // OUTPUT => [6] : ceyha* const std::string surname{"aksu"}; // İlgili '.front()' ve '.back()' fonksiyonlarının 'const-overload' çağrılacağı için artık YAZMA YAPAMIYORUZ. // ++surname.front(); // surname.back() = '*'; } >>>> 'string' sınıfındaki yazıyı dolaşma yolları: 'iterator' mekanizması, '[]' operatörü ile ve '.at()' fonksiyonu ile yapabiliriz. Bu üçü ile OKUMA ve YAZMA yapabiliriz. Bu üç kalemin de ayrıca 'const-overload' versiyonu da vardır ki onlar ile sadece OKUMA yapabiliriz. * Örnek 1, #include #include int main() { std::string str{"Berat Albayrak"}; for(size_t i = 0; i < str.size(); ++i) { std::cout << "[" << str[i] << "] "; // OUTPUT => [B] [e] [r] [a] [t] [ ] [A] [l] [b] [a] [y] [r] [a] [k] // std::cout << "[" << str.at(i) << "] "; } std::cout << "\n"; for(auto iter = str.begin(); iter != str.end(); ++iter) std::cout << "[" << *iter << "] "; // OUTPUT => [B] [e] [r] [a] [t] [ ] [A] [l] [b] [a] [y] [r] [a] [k] std::cout << "\n"; //for(auto c : str) for(char c : str) // 'for' döngüsünü derleyici yazmaktadır. 'call-by-value' mekanizması kullanıldığından, ilgili 'string' // nesnesindeki her bir karakter 'c' değişkenine kopyalanmaktadır. 'c' karakteri üzerindeki değişiklik ilgili // 'str' nesnesindeki yazıyı değiştirmeyecektir. std::cout << "[" << c << "] "; // OUTPUT => [B] [e] [r] [a] [t] [ ] [A] [l] [b] [a] [y] [r] [a] [k] //for(auto& c : str) for(char& c : str) // 'for' döngüsünü derleyici yazmaktadır. 'call-by-reference' mekanizması kullanıldığından, ilgili 'string' // nesnesindeki her bir karakter 'c' değişkenine refere edilmektedir. 'c' karakteri üzerindeki değişiklik ilgili // 'str' nesnesindeki yazıyı değiştirecektir. std::cout << "[" << c << "] "; // OUTPUT => [B] [e] [r] [a] [t] [ ] [A] [l] [b] [a] [y] [r] [a] [k] //for(const auto& c : str) for(const char& c : str) // 'for' döngüsünü derleyici yazmaktadır. 'const-call-by-reference' mekanizması kullanıldığından, ilgili // 'string' nesnesindeki her bir karakter 'c' değişkenine refere edilmektedir. İlgili 'c' karakteri üzerinde // DEĞİŞİKLİK YAPILAMAZ. std::cout << "[" << c << "] "; // OUTPUT => [B] [e] [r] [a] [t] [ ] [A] [l] [b] [a] [y] [r] [a] [k] } >>>> Arka plandaki kahraman fonksiyonların incelenmesi; >>>>> '.reserve()' fonksiyonu : Kapasiteyi rezerve etmektedir. 'size' büyüklüğü değişmiyor. 'capacity' değeri de bu rakama yaklaşık olmaktadır. 'size' büyüklüğü, 'capacity' değerini aşarsa 'reallocation' gerçekleşecektir. Bu fonksiyonu kullanarak 'capacity' değerini küçültemeyiz. Bunu yapmaya çalıştığımız zaman 'non-binding shrink request' mevzusu olur. Sentaks hatası değildir, 'Tanımsız Davranış' değildir. Derleyici 'capacity' değerini KÜÇÜLTMEK ZORUNDA DEĞİLDİR. Varsayılan argüman olarak da '0' değerini alır. * Örnek 1, #include #include int main() { std::string str; str.reserve(100); // GCC & CLANG || MSVC std::cout << "str : " << str << "\n"; // OUTPUT => str : || std::cout << "Size : " << str.size() << "\n"; // OUTPUT => Size : 0 || 0 std::cout << "Capacity : " << str.capacity() << "\n"; // OUTPUT => Capacity : 100 || 111 str.reserve(50); std::cout << "str : " << str << "\n"; // OUTPUT => str : || std::cout << "Size : " << str.size() << "\n"; // OUTPUT => Size : 0 || 0 std::cout << "Capacity : " << str.capacity() << "\n"; // OUTPUT => Capacity : 50 || 111 str.reserve(); // Varsayılan argüman olarak '0' değeri geçildi. std::cout << "str : " << str << "\n"; // OUTPUT => str : || std::cout << "Size : " << str.size() << "\n"; // OUTPUT => Size : 0 || 0 std::cout << "Capacity : " << str.capacity() << "\n"; // OUTPUT => Capacity : 15 || 15 } >>>>> '.shrink_to_fit()' fonksiyonu : 'capacity' değerini, 'size' büyüklüğüne uygun olacak şekilde büzer. Herhangi bir argüman almamaktadır. C++11 ile dile eklenmiştir. * Örnek 1, #include #include int main() { std::string str; str = "ahmet kandemir pehlivanli"; std::cout << "str : " << str << "\n"; // OUTPUT => str : ahmet kandemir pehlivanli std::cout << "Size : " << str.size() << "\n"; // OUTPUT => Size : 25 std::cout << "Capacity : " << str.capacity() << "\n"; // OUTPUT => Capacity : 30 str.shrink_to_fit(); std::cout << "str : " << str << "\n"; // OUTPUT => str : ahmet kandemir pehlivanli std::cout << "Size : " << str.size() << "\n"; // OUTPUT => Size : 25 std::cout << "Capacity : " << str.capacity() << "\n"; // OUTPUT => Capacity : 25 } >>>>> '.c_str()' fonksiyonu : 'string' sınıf türünden 'const char*' türüne otomatik dönüşüm YOKTUR. Ama 'C-string' isteyen bir fonksiyonumuz var elimizde. '.c_str()' fonksiyonunu kullanarak iş bu fonksiyona çağrı yapabiliriz. Bu fonksiyonun geri dönüş değeri 'const char*' türündendir. 'reallocation' sonrasında bu fonksiyonu tekrardan çağırmamız gerekiyor, içerideki göstericinin gösterdiği adres değiştiğinden dolayı. * Örnek 1, #include #include #include int main() { std::string name{"enes aydin"}; auto ptr = name.c_str(); std::cout << "using ptr : " << ptr << "\n"; // OUTPUT => using ptr : enes aydin std::cout << "using C-Api, length : " << std::strlen(name.c_str()) << "\n"; // OUTPUT => using C-Api, length : 10 std::cout << "&name[0], length : " << std::strlen(&name[0]) << "\n"; // OUTPUT => &name[0], length : 10 name += "isimli kisinin ismi orneklerde sikca gecmektedir."; std::cout << "using ptr : " << ptr << "\n"; // OUTPUT => using ptr : ; // 'ptr' is dangling pointer now. Çünkü 'reallocation' gerçekleşti. Eğer gerçekleşmeseydi, // 'ptr' göstericisini hala kullanabiliriz. std::cout << "using C-Api, length : " << std::strlen(name.c_str()) << "\n"; // OUTPUT => using C-Api, length : 59 std::cout << "&name[0], length : " << std::strlen(&name[0]) << "\n"; // &name[0], length : 59 // Görüldüğü üzere '.c_str()' fonksiyonunu her zaman tekrar tekrar çağırmamız gerekmektedir. } >>>>> '.data()' fonksiyonu : C++17 ile bu üye fonksiyonun 'global namespace' versiyonu 'algorithm' başlık dosyasına eklendi. Her iki versiyon da yukarıdaki '.c_str()' fonksiyonu ile aynı işi yapmaktadır. Modern C++ sonrasındaki dönemde ikisi arasında bir fark YOKTUR. O dönem öncesinde '.data()' ile çağrılan fonksiyonun 'Null-terminated-string' olma ZORUNLULUĞU YOKTU. >>>>> '.copy' fonksiyonu : Az çağrılan bir fonksiyondur. İlk argüman, kopyalanacak adres. İkinci argüman kopyalanacak toplam karakter adedi. Üçüncü argüman ise off-set. O off-set değerinden itibaren kopyalama başlayacak. Geri dönüş değeri de kopyalanan karakter sayısıdır. Kopyalanacak karakter sayısı, hedef dizindeki eleman sayısından büyük ise, kopyalama işleminden sonra '\0' karakterini eklemeliyiz çünkü kopyalama sırasında '\0' karakterini KOPYALANMAMAKTADIR. * Örnek 1, #include #include int main() { std::string name{"necati ergin bugun ders anlatti."}; char str[10]; size_t n = name.copy(str, 10, 7); // 'name' nesnesinin içindeki yazının yedinci karakterinden itibaren toplam on karakter kopyalanacaktır. std::cout << "[" << n << "] : [" << str << "]\n"; // OUTPUT => [10] : [ergin bugu] std::string surname{"necati"}; char strTwo[10]; size_t m = surname.copy(strTwo, 10); strTwo[m] = '\0'; // İlgili karakter kopyalanmadığından, bizim eklememiz gerekiyor. std::cout << "[" << m << "] : [" << strTwo << "]\n"; // OUTPUT => [6] : [necati] } >>>> 'string' sınıfındaki arama fonksiyonları : Bütün arama fonksiyonlarının ara yüzü ortaktık. Geri dönüş değerinin türü 'std::string::size_type' şeklindedir. Başarısız olma durumunda 'std::string::npos' değerini döndürmektedir. Birinci parametre HER ZAMAN ARANAN DEĞERDİR. >>>>> 'constexpr std::string::size_type std::string::npos' : ÇOK ÖNEMLİ BİR 'static' VERİ ELEMANIDIR. Türü, 'std::string::size_type' şeklindedir. Değeri bu türün en büyük değeri. 'std::string' sınıfında bu tür 'size_t' şeklindedir. 'string' sınıfındaki arama fonksiyonlarının ki bunlar '.find()', '.rfind()', '.find_first_of()', '.find_last_of()', '.find_first_not_of()' ve '.find_last_not_of()' fonksiyonlarıdır, başarısız olmaları durumunda geri dönüş değeri olarak kullanılır. İş bu fonksiyonlar başarılı olmaları durumunda ise 'std::string::size_type' türünden değer döndürmektedirler. Bu sabitin bir diğer kullanım alanı ise şurasıdır; bazı fonksiyonlar bizden bir indeks ve toplam karakter sayısı istemektedir. Böylelik o indeksten başlayıp, o kadar karakter sayısı kadarlık yazı üzerinde işlem yapabilsinler. Eğer bir şekilde argüman olarak geçeceğimiz rakam, dizinin toplam uzunluğundan büyükse, ne SENTAKS HATASI NE DE 'Tanımsız Davranış' meydana geliyor. Bu durumda yazının o indisten itibaren geri kalan bütün hepsi ele alınıyor. Peki ya yazının toplam uzunluğunu bilmiyorsak? İşte bu tip durumlarda %100 güvenli yöntem olarak 'std::string::npos' değerini argüman olarak geçmektir. Böylelikle o indisten itibaren yazının geri kalanı ele alınmış oluyor. DİPNOT: C dilindeki arama fonksiyonları ise adres döndürmektedir. * Örnek 1, #include #include int main() { std::cout << "std::string::npos : " << std::string::npos << "\n"; // OUTPUT => std::string::npos : 18446744073709551615 std::cout << "(size_t)(-1) : " << (size_t)(-1) << "\n"; // OUTPUT => std::string::npos : 18446744073709551615 std::string str; std::cout << "Bir yazi giriniz: "; std::getline(std::cin, str); // INPUT => Ahmet Kandemir Pehlivanli auto idx = str.find('a'); if(idx != std::string::npos) { std::cout << "bulundu. indeks : " << idx << "\n"; // OUTPUT => bulundu. indeks : 7 str[idx] = '@'; std::cout << "[" << str.size() << "] : " << str << "\n"; / / OUTPUT => [25] : Ahmet K@ndemir Pehlivanli } else { std::cout << "bulunamadi."; } } >>>>> '.find()' fonksiyonu : Birinci parametre aranacak öğe, ki bu öğe bir 'C-String' olabilir, 'string'/'sub-string' olabilir, 'Data' Parametre olabilir. Birden çok 'overload' mevcut. İkinci parametre ise off-set. Yani kaçıncı indeksten aramaya başlanacağını söylüyor. Varsayılan argüman ise sıfırıncı indeks. Bulduğu ilk indekte arama sonlanır. Arama baştan sonra doğru yapılır. * Örnek 1, 'e' karakterinin aranması. #include #include int main() { std::string str{"Bjarne Stroustrup invented C++."}; if(auto idx = str.find('e'); idx != std::string::npos) { std::cout << "Bulundu. Indeks : " << idx << "\n"; // OUTPUT => Bulundu. Indeks : 5 str[idx] = '!'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [31] : Bjarn! Stroustrup invented C++. } else { std::cout << "Bulunamadı...\n"; } } * Örnek 2, 'e' karakterinin belirli bir indeksten itibaren aranması. #include #include int main() { std::string str{"Bjarne Stroustrup invented C++."}; if(auto idx = str.find('e', 6); idx != std::string::npos) { std::cout << "Bulundu. Indeks : " << idx << "\n"; // OUTPUT => Bulundu. Indeks : 21 str[idx] = '!'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [31] : Bjarne Stroustrup inv!nted C++. } else { std::cout << "Bulunamadı...\n"; } } * Örnek 3, Bir 'C-String' aranması. #include #include int main() { std::string str{"Bjarne Stroustrup invented C++."}; // "up" kelimesinin başladığı indeksi bize döndürecektir. Eğer böyle bir kelime olmasaydı, // ifade 'else' bloğuna girecektir. if(auto idx = str.find("up"); idx != std::string::npos) { std::cout << "Bulundu. Indeks : " << idx << "\n"; // OUTPUT => Bulundu. Indeks : 15 str[idx] = '!'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [31] : Bjarne Stroustr!p invented C++. } else { std::cout << "Bulunamadı...\n"; } } * Örnek 4, Bir yazı içerisindeki bütün 'e' karakterinin bulunması. #include #include int main() { std::string str{"Bjarne Stroustrup invented C++."}; for(size_t idx = 0; idx < str.size(); ++idx) { if(size_t lastFoundIndex = str.find('e'); lastFoundIndex != std::string::npos) { lastFoundIndex = str.find('e', lastFoundIndex); // En son bulduğu karakterden aramaya başlıyor. str[lastFoundIndex] = '!'; } } std::cout << "[" << str.size() << "] : " << str << "\n"; } >>>>> '.rfind()' fonksiyonu : Bir yazıda aramaya sondan başlıyor. '.find()' fonksiyonunun 'reverse' edilmiş hali diyebiliriz. Bunun haricinde '.find()' fonksiyonundan bir farkı yoktur. Bulduğu ilk indekste aramayı sonlandırır. >>>>> '.find_first_of()' : İlk parametre yine her zaman olduğu gibi aranan değer. İkinci olarak ise aramaya başlanacak indeks için off-set. 'C-String' bir ifade argüman olarak geçildiğinde, kelimenin harflerini ayrı ayrı arayacaktır. '.find()' fonksiyonu gibi kelimeyi bütün halde aramayacaktır. * Örnek 1, #include #include int main() { std::string str; std::cin >> str; // INPUT => trxmoprnwertqs std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxmoprnwertqs // "aeiou" kelimesindeki harflerden // birisinin ilk bulunduğu indeksi bize döndürecektir. Kelimenin bütününü aramamaktadır. if(auto idx = str.find_first_of("aeiou"); idx != std::string::npos) { std::cout << "Bulundu. idx : " << idx << "\n"; // OUTPUT => Bulundu. idx : 4 str[idx] = '*'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxm*prnwertqs } else { std::cout << "Bulunamadı.\n"; } } >>>>> '.find_last_of()' fonksiyonu : '.find_first_of()' fonksiyonu gibi çalışmaktadır. Tek fark, bu fonksiyon aramaya sondan başlayacaktır. Ayrıca döndürdüğü indeks numarası, baştan başladığımız zamanki indeks numarasıdır. * Örnek 1, #include #include int main() { std::string str{"trxmoprnwertqs"}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxmoprnwertqs if(auto idx = str.find_last_of("aeiou"); idx != std::string::npos) { std::cout << "Bulundu. idx : " << idx << "\n"; // OUTPUT => Bulundu. idx : 9 str[idx] = '*'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxmoprnw*rtqs } else { std::cout << "Bulunamadı.\n"; } } >>>>> '.find_first_not_of()' fonksiyonu : Argüman olarak geçilen ifadedeki karakterlerden olmayan ilk karakteri bulacaktır. * Örnek 1, #include #include int main() { std::string str{"trxmoprnwertqs"}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxmoprnwertqs //Bu karakterlerden biri olmayan ilk karakteri bulacaktır. if(auto idx = str.find_first_not_of("aeiou"); idx != std::string::npos) { std::cout << "Bulundu. idx : " << idx << "\n"; // OUTPUT => Bulundu. idx : 0 str[idx] = '*'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : *rxmoprnwertqs } else { std::cout << "Bulunamadı.\n"; } } >>>>> '.find_last_not_of()' fonksiyonu : Argüman olarak geçilen ifadedeki karakterlerden olmayan ilk karakteri bulacaktır fakat aramaya sondan başlayacaktır. * Örnek 1, #include #include int main() { std::string str{"trxmoprnwertqs"}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxmoprnwertqs //Bu karakterlerden biri olmayan ilk karakteri bulacaktır. if(auto idx = str.find_last_not_of("aeiou"); idx != std::string::npos) { std::cout << "Bulundu. idx : " << idx << "\n"; // OUTPUT => Bulundu. idx : 13 str[idx] = '*'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : trxmoprnwertq* } else { std::cout << "Bulunamadı.\n"; } } >>>> 'string' sınıfındaki atama fonksiyonları: >>>>> '.operator=(const std::string&)' fonksiyonu: * Örnek 1, #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : std::string strTwo{"Taceddin"}; str = strTwo; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [8] : Taceddin } >>>>> '.operator=(std::string&&)' fonksiyonu: * Örnek 1, #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : std::string strTwo{"Taceddin"}; str = std::move(strTwo); std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [8] : Taceddin std::cout << "[" << strTwo.size() << "] : " << strTwo << "\n"; // OUTPUT => [0] : } >>>>> '.operator=(std::initializer_list&)' fonksiyonu: * Örnek 1, #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : str = {'o', 'k', 'a', 'n'}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [4] : okan } >>>>> '.operator=(const char*)' fonksiyonu: * Örnek 1, #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : str = "serafettin"; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [10] : serafettin } >>>>> '.operator=(char)' fonksiyonu : Her ne kadar 'char' parametreli 'Ctor' olmasa bile, iş bu parametreli atama operatörü mevcuttur. * Örnek 1, #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : str = 'X'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [1] : X } >>>>> '.assign()' fonksiyonu : Bünyesinde birden fazla 'overload' barındırır. Bunlar 'fill' parametreli, 'range'parametreli, 'C-String' parametreli, 'Data' parametreli vs. fonksiyonlardır. Daha kompleks atamaları yapmamıza imkan sağlar. * Örnek 1, 'C-String' parametreli versiyonu: #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : char buffer[] = "Muhsin Gozenek"; str.assign(buffer); std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [14] : Muhsin Gozenek } * Örnek 2, 'fill' parametreli versiyonu: #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : str.assign(9, 'E'); std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [9] : EEEEEEEEE } * Örnek 3, 'range' parametreli versiyonu: #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : char buffer[] = "Muhsin Gozenek"; str.assign(buffer, buffer + 6); // Birinci ve ikinci adresler arasındaki karakterler. std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [6] : Muhsin } * Örnek 4, 'Data' parametreli versiyonu: #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : char buffer[] = "Muhsin Gozenek"; str.assign(buffer + 7, 3); // Birinci adresten başlayıp ilk üç karakter std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [3] : Goz } * Örnek 5, İlgili indisten başlayıp, yazının geri kalanı. #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : std::string strTwo = "Muhsin Gozenek"; str.assign(strTwo, 10); // Onuncu indisten başlayarak, yazının geri kalanı std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [4] : enek } * Örnek 6, İlgili indisten başlayıp belirli bir karakter adedince. #include #include int main() { std::string str{}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : std::string strTwo = "Muhsin Gozenek"; str.assign(strTwo, 10, 2); // Onuncu indisten başlayarak, ilk iki karakterlik yazı. std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [2] : en } >>>> 'string' sınıfının '.resize()' fonksiyonu: Kaptaki öğe sayısını değiştirmektedir. Var olan büyüklük değerinden daha büyük bir rakam argüman olarak geçilirse, fazlalık olan karakterler 'null-char.' değerinde olacaklardır. Bir tane 'overload' ı vardır ki o fonksiyon ile fazlalık karakterlerin ne olması gerektiğine karar verebiliyoruz. Bu fonksiyon ile 'string' nesnesinin uzunluğunu da KÜÇÜLTEBİLİR. 'capacity' bilgisi yazı küçülürken DEĞİŞMEME GARANTİSİ VAR fakat yazı büyürken de duruma göre ya DEĞİŞİYOR ya da DEĞİŞMİYOR. * Örnek 1, #include #include int main() { std::string str{"Muhsin Gozenek"}; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [0] : str.resize(20); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [40] : Muhsin Gozenek str.resize(40, 'x'); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [40] : [Muhsin Gozenekxxxxxxxxxxxxxxxxxxxx] str.resize(6); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [6] : [Muhsin] } >>>> 'string' sınıfındaki karşılaştırma fonksiyonları: Bir başka 'string' türünden nesne ile veya bir 'C-String' ile karşılaştırmaya olanak veren fonksiyonlardır. Bir 'karakter' ile karşılaştırma yapan fonksiyon YOKTUR. >>>>> Karşılaştırma operatörleri arka planda 'global operator functions' çağırdıklarından, ilk operatörün 'string' sınıf türünden bir nesne olması da gerekmemektedir. 6 operatör için de 'global operator functions' vardır. Birden fazla 'overload' versiyonları vardır. Geri dönüş değeri 'bool' türündendir. >>>>> Ek olarak 'member function' olan '.compare()' fonksiyonu da vardır ki daha kompleks karşılaştırmalarda işimizi görür. C dilindeki 'strcmp()' fonksiyonu ile benzer arayüze sahiptir. Bu fonksiyonun da 'overload' ı vardır. Geri dönüş değeri de 'int' türdendir. >>>> 'string' sınıfındaki '.swap()' fonksiyonu: İlgili sınıf nesnesi içerisindeki yazılar birbiri ile değiştirilir. * Örnek 1, #include #include int main() { /* # OUTPUT # ------------- &x : 0x7ffc28ca5a60=> ali &y : 0x7ffc28ca5a80=> ahmet ------------- &x : 0x7ffc28ca5a60=> ahmet &y : 0x7ffc28ca5a80=> ali ------------- &x : 0x7ffc28ca5a60=> ali &y : 0x7ffc28ca5a80=> ahmet ------------- */ std::cout << "-------------\n"; std::string x{"ali"},y{"ahmet"}; std::cout << "&x : " << &x << "=> " << x << "\n"; std::cout << "&y : " << &y << "=> " << y << "\n"; std::cout << "-------------\n"; // KAÇINMAMIZ GEREKEN 'swap' YÖNTEMİ. ÇÜNKÜ BURADA KOPYALAMA YAPILMAKTADIR. auto temp = x; x = y; y = temp; std::cout << "&x : " << &x << "=> " << x << "\n"; std::cout << "&y : " << &y << "=> " << y << "\n"; std::cout << "-------------\n"; // KULLANMAMIZ GEREKEN YÖNTEM: x.swap(y); std::cout << "&x : " << &x << "=> " << x << "\n"; std::cout << "&y : " << &y << "=> " << y << "\n"; std::cout << "-------------\n"; } >>>> 'sub-string' elde etme fonksiyonları ; >>>>> '.substr()' fonksiyonu : Birinci parametresi bizden bir indeks istemektedir. İkinci parametre ise o indisten itibaren kaç karakteri kapsayağının bilgisini istemektedir. Eğer ikinci parametre boş kalırsa varsayılan olarak 'std::string::npos' değeri geçilir ki bu da geri kalan bütün karakterleri kapsar. Eğer ilk parametre boş kalırsa, ki bu durumda varsayılan değer olarak '0' değeri geçilecektir, yazının bir kopyasını çıkartmış oluruz. * Örnek 1, #include #include int main() { std::string name{"berker boyaci, alper oner"}; auto str = name.substr(7, 6); // Yedinci indisten baslayıp, ilk alti karakter auto strTwo = name.substr(15); // Onbeşinci indisten başlayıp, yazının geri kalan kısmı. std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [25] : [berker boyaci, alper oner] std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [6] : [boyaci] std::cout << "[" << strTwo.size() << "] : [" << strTwo << "]\n"; // OUTPUT => [10] : [alper oner] } >>>> 'string' sınıfındaki iteratör ile yapılan EKLEME ve SİLME İŞLEMLERİ; >>>>> '.insert()' fonksiyonu, birden fazla 'overload' mevcuttur. * Örnek 1, #include #include int main() { std::string name{"berker boyaci"}; name.insert(name.begin() + 3, '!'); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [14] : [ber!ker boyaci] } * Örnek 2, #include #include int main() { std::string name{"berker boyaci"}; name.insert(name.begin() + 3, {'Q', 'Q', 'Q'}); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [16] : [berQQQker boyaci] } * Örnek 3, #include #include int main() { std::string name{"berker boyaci"}; name.insert(name.begin() + 3, 12, 'Q'); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [25] : [berQQQQQQQQQQQQker boyaci] } >>>>> '.erase()' fonksiyonu, birden fazla 'overload' içermekte olup, verilen konumdaki öğeyi silmektedir. * Örnek 1, #include #include int main() { std::string name{"berker boyaci"}; name.erase(name.begin() + 3); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [12] : [berer boyaci] } * Örnek 2, #include #include int main() { std::string name{"berker boyaci"}; name.erase(name.begin() + 3, name.begin() + 10); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [6] : [beraci] } >>>> 'string' sınıfındaki "indeks ile ekleme ve silme işlemleri" : Bu işlemlerde kullanılan fonksiyonlar da birden fazla 'overload' içermektedir. >>>>> 'insert' işleminde kullanılacak: Birinci parametre 'insert' edilecek yerin konumu. Diğer parametreler ise neyin 'insert' edileceğine dair parametrelerdir. Geri dönüş değeri '*this'. * Örnek 1, #include #include int main() { std::string name{"0123456789"}; name.insert(4, "ali"); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [13] : [0123ali456789] } * Örnek 2, #include #include int main() { std::string name{"0123456789"}; std::string surname{"mustafa"}; name.insert(4, surname); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [17] : [0123mustafa456789] } * Örnek 3, #include #include int main() { std::string name{"0123456789"}; std::string surname{"mustafa"}; name.insert(4, 8, '!'); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [18] : [0123!!!!!!!!456789] } * Örnek 4, #include #include int main() { std::string name{"0123456789"}; std::string surname{"mustafa"}; name.insert(0, surname).insert(8, "ceyda"); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [22] : [mustafa0ceyda123456789] } >>>>> 'erase' işleminde kullanılacak: Birinci parametre konum bilgisidir fakat bu konum bilgisi SİLMEZ. 'overload' versiyonlarına göre bu indisten başlayarak silme işlemini gerçekleştirir. Birinci parametre için varsayılan konum bilgisi 'sıfır' konumudur. Yani bu durumda yazının hepsini silecektir. İkinci karakter ise toplamda kaç adet karakterin silineceğinin bilgisini içermektedir. * Örnek 1, #include #include int main() { std::string name{"0123456789"}; std::string surname{"mustafa"}; name.erase(5); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [5] : [01234] name.erase(); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [0] : [] } * Örnek 2, #include #include int main() { std::string name{"0123456789"}; std::string surname{"mustafa"}; name.erase(5, 3); std::cout << "[" << name.size() << "] : [" << name << "]\n"; // OUTPUT => [7] : [0123489] } * Örnek 3, #include #include int main() { std::string str{"dolar kuru hizli bir sekilde yukselirken, enflasyon da tirmaniyor."}; std::string strToDelete; std::cout << "Silinecek Yazi : " ; std::cin >> strToDelete; // INPUT => hizli std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [66] : [dolar kuru hizli bir sekilde yukselirken, enflasyon da tirmaniyor.] if(auto idx = str.find(strToDelete); idx != std::string::npos) { str.erase(idx, strToDelete.size()); } else { std::cout << "[" << strToDelete << "] yazisi bulunamadi.\n"; } std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [61] : [dolar kuru bir sekilde yukselirken, enflasyon da tirmaniyor.] } * Örnek 4, Mülakat Sorusu : Aşağıdaki diziyi birden fazla yöntem ile silmeyi deneyin. #include #include int main() { std::string str{"mustafa erman"}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [13] : [mustafa erman] // str.clear(); // I // str.erase(str.begin(), str.end()); // II : Iterator interface // str.erase(); // III : Index interface // str = ""; // IV : Boş bir yazı atarsak. // str = {}; // V // str.resize(0); // VI // str = std::string{}; // VII : Geçici nesnenin değerini atadığımız için 'Move Ctor' çağrılacak ve o nesnenin değerini çalacak. std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [0] : [] } >>>> '.push_back()' ve '.pop_back()' fonksiyonları: Sırasıyla sondan ekleme ve çıkarma yapan bir fonksiyonlardır. * Örnek 1, #include #include int main() { std::string str{"mustafa erman"}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [13] : [mustafa erman] str.push_back('c'); str.push_back('c'); str.push_back('c'); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [16] : [mustafa ermanccc] str.pop_back(); str.pop_back(); str.pop_back(); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [13] : [mustafa erman] } * Örnek 2, #include #include int main() { /* # OUTPUT # [26] : [mustafa erman sen cok yasa] [25] : [mustafa erman sen cok yas] [24] : [mustafa erman sen cok ya] [23] : [mustafa erman sen cok y] [22] : [mustafa erman sen cok ] [21] : [mustafa erman sen cok] [20] : [mustafa erman sen co] [19] : [mustafa erman sen c] [18] : [mustafa erman sen ] [17] : [mustafa erman sen] [16] : [mustafa erman se] [15] : [mustafa erman s] [14] : [mustafa erman ] [13] : [mustafa erman] [12] : [mustafa erma] [11] : [mustafa erm] [10] : [mustafa er] [9] : [mustafa e] [8] : [mustafa ] [7] : [mustafa] [6] : [mustaf] [5] : [musta] [4] : [must] [3] : [mus] [2] : [mu] [1] : [m] */ std::string str{"mustafa erman sen cok yasa"}; while(!str.empty()) { std::cout << "[" << str.size() << "] : [" << str << "]\n"; str.pop_back(); // str.erase(0,1); // Alternatif II : Sıfırıncı indisten başla ve bir karakter sil. } } * Örnek 3, #include #include int main() { /* # OUTPUT # [26] : [mustafa erman sen cok yasa] [25] : [ustafa erman sen cok yasa] [24] : [stafa erman sen cok yasa] [23] : [tafa erman sen cok yasa] [22] : [afa erman sen cok yasa] [21] : [fa erman sen cok yasa] [20] : [a erman sen cok yasa] [19] : [ erman sen cok yasa] [18] : [erman sen cok yasa] [17] : [rman sen cok yasa] [16] : [man sen cok yasa] [15] : [an sen cok yasa] [14] : [n sen cok yasa] [13] : [ sen cok yasa] [12] : [sen cok yasa] [11] : [en cok yasa] [10] : [n cok yasa] [9] : [ cok yasa] [8] : [cok yasa] [7] : [ok yasa] [6] : [k yasa] [5] : [ yasa] [4] : [yasa] [3] : [asa] [2] : [sa] [1] : [a] */ std::string str{"mustafa erman sen cok yasa"}; while(!str.empty()) { std::cout << "[" << str.size() << "] : [" << str << "]\n"; str.erase(str.begin()); // str.erase(0,1); // Alternatif I : Sıfırıncı indisten başla ve bir karakter sil. } } >>>> '.replace()' : '.erase()' & '.insert()' fonksiyonlarının işini aynı ayna yapmaktadır. Birden çok 'overload' mevcuttur. Her zaman için ilk parametre "neyin yerine gelecektir 'insert' edeceğimiz karakter?" sorusuna cevap verir. * Örnek 1, #include #include int main() { std::string str{"mustafa erman sen cok yasa"}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [26] : [mustafa erman sen cok yasa] str.replace(0, 7, "Kandemir"); // Sıfırıncı indisten başla, ilk yedi karakterlik yazı yerine "Kandemir" yazısınıekle std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [27] : [Kandemir erman sen cok yasa] } > C++17 ile dile eklenen 'init-statement in selection statements' : Bir ismin sadece ve sadece bir tane 'if-else' veya 'switch' bloğunda bilinir olmasını sağlamaktır. Kapsam sızıntısının önüne geçmeyi hedefler. Evvelce kapsam sızıntılarını engellemek için ilgili bloğu bir '{}' içerisine alırlardı. * Örnek 1, C++17 ile kullanılan yöntem #include #include int foo() { return 31; } int main() { std::string str; std::cout << "Bir yazi giriniz: "; std::getline(std::cin, str); // INPUT => Ahmet Kandemir Pehlivanli if(auto idx = str.find('a'); idx != std::string::npos) // if-with init. since C++17 { std::cout << "bulundu. indeks : " << idx << "\n"; // OUTPUT => bulundu. indeks : 7 str[idx] = '@'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [25] : Ahmet K@ndemir Pehlivanli } else { std::cout << "bulunamadi."; } switch(auto x = foo(); x) { //.. } } * Örnek 2, Geleneksel Yaklaşım #include #include int main() { { std::string str; std::cout << "Bir yazi giriniz: "; std::getline(std::cin, str); // INPUT => Ahmet Kandemir Pehlivanli auto idx = str.find('a'); if(idx != std::string::npos) { std::cout << "bulundu. indeks : " << idx << "\n"; // OUTPUT => bulundu. indeks : 7 str[idx] = '@'; std::cout << "[" << str.size() << "] : " << str << "\n"; // OUTPUT => [25] : Ahmet K@ndemir Pehlivanli } else { std::cout << "bulunamadi."; } } } * Örnek 3, Aşağıdaki kullanımı, yukarıdaki 'if-with-initializor' ile karıştırmayalım. #include #include int foo() { return 31; } int main() { // Bu tip kullanım ezelden beridir dilde mevcut ve 'x' burada bir lojik yorumlamaya e tabii tutuluyor. // " x != 0 " ifadesi gibi düşünebiliriz. 'int' yerine 'auto' da yazabiliriz. if(int x = foo()) { std::cout << "x : " << x << "\n"; // OUTPUT => x : 31 } } > Standart Template Library (STL) (devam) : >> 'STL Containers' incelenmesi. Bunlar, üç ana gruba ayrılırlar. >>> Sequence Containers : Bunlar da kendi içinde birden fazla öğe içerir. Bunlar Vector, Deque, List, Forward_List, String, Array ve C-Array öğelerinden oluşur. Bir takım fonksiyonlar sadece bu kaba özel fonksiyonlardır. Örneğin, >>>> '.back()' fonksiyonu, kaptaki son öğeye erişir. >>>> '.front()' fonksiyonu, kaptaki ilk öğeye erişmektedir. >>>> '.resize()' fonksiyonu, kaptaki öğe sayısını değiştirmektedir. >>> Associative Containers : Bunlar da kendi içinde birden fazla öğe içerir. Bunlar Set, Multiset, Map, Multimap öğelerinden oluşur. >>> Unordered Associative Containers : Bunlar da kendi içinde birden fazla öğe içerir. Bunlar Unoredered_Set, Unoredered_Multiset, Unoredered_Map, Unoredered_Multimap öğelerinden oluşur. >>> İş bu bütün kaplarda ortak olan bazı fonksiyonlar vardır ki bunlar >>>> '.empty()' fonksiyonu, ilgili kabın boş olup olmadığını sınar. 'bool' türünden değer döndürür. >>>> '.size()' fonksiyonu, ilgili kaptaki eleman sayısını döndüren bir fonksiyondur. 'string' sınıfında alternatif olarak '.length()' fonksiyonu da vardır. >>>> '.clear()' fonksiyonu kapta bulunan bütün öğeleri siler, kaptan çıkartır. >>>> '.swap()' fonksiyonu kaptaki öğeleri değil, sınıfın içerisindeki 'member' lar takas edilir. Varsayılan olarak 'lexicographical_compare' algoritması kullanılır. >>>>> 'lexicographical_compare' : Öğeler karşılıklı olarak karşılaştırılırken öğeler birbirine eşit ve öğe sayısı da birbiri ile aynı ise bu durumda bunlar birbirine eşittir. Eğer öğelerin elemanları daha bitmemişken, eşit olmayan bir eleman çifti görülürse, elemanı büyük olan öğe daha büyüktür. Karşılaştırma devam ederken bir öğenin elemanlarının bitmesi durumunda, o öğe küçük öğe seçilir. * Örnek 1, //.. Öğe 1 : 1 2 3 4 Öğe 2 : 1 2 3 4 // Yukarıdaki Öğe 1 ve Öğe 2'nin eleman sayısı ve elemanlarının kendileri de birbirine eşit olduğundan, // Öğe 1 ve Öğe 2 birbirine eşittir diyebiliriz. * Örnek 2, Öğe 1 : 1 2 3 4 Öğe 2 : 1 2 5 // Yukarıdaki Öğe 1 ve Öğe 2'nin ikinci indislerindeki elemanlar birbirine eşit değil. // Öğeler daha eleman da barındırmaktadır. Dolayısıla '5' > '3' olacağından, Öğe 2 daha büyüktür. * Örnek 3, Öğe 1 : 1 2 3 4 Öğe 2 : 1 2 3 // Yukarıdaki karşılaştırma üçüncü indisten sonra bitecektir. Öğe 1'in daha elemanı olduğundan, // Öğe 1 daha büyüktür. * Örnek 4, #include #include int main() { std::vector iVecOne(100000, 1); std::cout << "iVecOne.size() : " << iVecOne.size() << "\n"; // OUTPUT => iVecOne.size() : 100000 std::vector iVecTwo(1, 2); std::cout << "iVecTwo.size() : " << iVecTwo.size() << "\n"; // OUTPUT => iVecTwo.size() : 1 std::cout << std::boolalpha << (iVecOne < iVecTwo) << "\n"; // OUTPUT => true } >>> Konum tutan değişkenlere 'iterator' denmekte olup, 'STL' kaplarında Ekleme ve Silme işlemlerinde kullanılırlar. Birinci parametre her zaman ekleme yapılacak konum bilgisini, ikinci parametre ise eklenecek değeri. Örneğin, '.insert()' ve '.erase()' fonksiyonları ki '.erase()' fonksiyonu 'std::array' sınıf türünde YOKTUR. '.insert()' fonksiyonunun geri dönüş değeri duruma göre ya '*this' ya da bir iteratördür. >>> '.maxsize()' fonksiyonu 'const' bir üye fonksiyon olup, kapta tutulabilecek maksimum öğe sayısını döndürür. > github:: "necatiergin/cpp_kursu_odevleri" adresindeki alıştırma pratiklerini yapmalıyız. > Standart Template Library (STL) (devam) : >> 'algorithm' başlık dosyasındaki bazı fonksiyonların incelemesi: >>> 'reverse()' fonksiyonu : Argüman olarak aldığı ifadeyi tersine çevirmektedir. * Örnek 1, #include #include #include int main() { std::string str{"mustafa erman sen cok yasa"}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [26] : [mustafa erman sen cok yasa] reverse(str.begin(), str.end()); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [26] : [asay koc nes namre afatsum] } >>> 'sort()' fonksiyonu : Argüman olarak aldığı ifadedeki karakterleri ASCII tablosuna göre sıralamaktadır. * Örnek 1, #include #include #include int main() { std::string str{"mustafa erman sen cok yasa"}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [26] : [mustafa erman sen cok yasa] sort(str.begin(), str.end()); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [26] : [ aaaaaceefkmmnnorssstuy] } >>> 'remove()' fonksiyonu : Argüman olarak aldığı ifadede silme işlemi yapmaktadır ama LOJİK SİLME İŞLEMİDİR. * Örnek 1, #include #include #include int main() { std::string str{"mustafa erman sen cok yasa"}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [26] : [mustafa erman sen cok yasa] char c = 'a'; // The character to be deleted. str.erase( remove(str.begin(), str.end(), c), str.end() ); // Remove-Erase idiom. std::cout << "[" << str.size() << "] : [" << str << "]\n"; // OUTPUT => [21] : [mustf ermn sen cok ys] } > Sınıflar (devam) : >> Inheritence : Bir sınıfın 'public-interface' devir alarak yeni bir sınıf oluşturulmasıdır. Yani o sınıfın 'public-interface' sinde hangi fonksiyonlar var ise benimkinde de onlar olsun. Fakat eğer istersem bunlara ilaveler yapabileyim. 'Run-time Polymorphism' ile desteklendiğinde, bir hayli zor problemlerin çözümünü, bakımını kolaylaştırmaktadır. Peki bunun haricinde biz neden böyle bir mekanizmaya ihtiyaç duyuyoruz? El cevap: (İlk verilmemesi gereken cevap) Kod tekrarını azaltmak. (Verilmesi gereken cevap) Eski kodların, yeni kodları kullanmasını sağlamak. Bir diğer değişle nesneleri belirli bir çatıda toplayıp, işlenme kolaylığı. >>> Bu ilişki tipine 'is-a relationship' denmektedir. Fakat 'composition' tipindeki ilişkilere ise 'has-a relationship' denmektedir (Her Tavşan bir Hayvandır). >>> C++ dilindeki 'inheritence', Nesne Yönelimli Programlamadaki 'inheritence' ile birebir aynı değil. Çünkü 'is-a relationship' ilişkisini sağladığı gibi çok daha kapsamlı olarak da kullanılmaktadır. Örneğin, tamamen 'Derleme Zamanına' yönelik kullanılması. >>> C++ dilinde üç ayrı kalıtım yolu vardır. Bunlar >>>> 'public-inheritence' kalıtım şekli ki bu aslında Nesne Yönelimli Programlama dillerinde 'is-a relationship' ilişkisini kurmaktadır, >>>> 'private inheritence', kalıtım şekli ki aslında Nesne Yönelimli Programlama dillerinde 'has-a relationship' ilişkisini kurmaktadır. Dolayısıyla 'composition' tip ilişkiye de bir hayli benzemektedir. >>>> 'protected inheritence'. >>> Nesne Yönelimli Programlama dillerinde yukarıdaki sınıflara genel olarak 'super-class' veya 'parent-class' denmektedir. Daha aşağıdaki sınıflara ise 'child-class' veya 'sub-class' denmektedir. C++ dilinde 'super-class' için 'base-class' kelimesi kullanılmaktadır. Türetilmiş sınıflar, yani 'sub-class' için C++ dilinde 'derived-class' da denmektedir. >>> 'incomplete-type' sınıflar, 'base-class' olarak KULLANILAMAZLAR. > >> Bir 'Base-class' kullanarak birden fazla 'Derived-class' elde edebiliriz. >>> C++ dilinde 'inheritence' mekanizmasının kullanım biçimi: * Örnek 1, //.. class Base{}; // A complete-type base-class. class DerPub : public Base {}; // 'DerPub' is a derived class, derived from 'Base' using 'public-inheritence' techniques. class DerPro : protected Base {}; // 'DerPub' is a derived class, derived from 'Base' using 'protected-inheritence' techniques. class DerPri : private Base {}; // 'DerPri' is a derived class, derived from 'Base' using 'private-inheritence' techniques. // Yukarıdaki kalıtım mekanizması olarak kullanılan 'public', 'protected' ve 'private' anahtar sözcüklerini kullanmak mecburi // değil. Fakat 'Derived-class' tanımlarken 'class' anahtar sözcüğü kullanıldığı için, varsayılan kalıtım biçimi // 'private-inheritence' şeklinde olacaktır eğer anahtar sözcük kullanmaz isek. Eğer 'Derived-class' tanımlarken 'struct' // anahtar sözcüğünü kullansaydık, varsayılan kalıtım biçimi 'public-inheritence' şeklinde olacaktı. // Yukarıdaki işlemler sonucunda, // i. Her bir 'DerPub' nesnesi bir 'Base' nesnesi olarak kullanılabilir. Dolayısıyla 'Base' sınıf türünden bir gösterici/referans, // 'DerPub' sınıf türünden de bir nesne gösterebilir. Fakat unutulmamalıdır ki 'Base' sınıftan bir gösterici/refernas kullanmadan, // yani nesne bazında da dönüşüm sentaks açısından legaldır. İleride öğreneceğimiz nedenlerden dolayı böyle bir DÖNÜŞÜMDEN // KAÇINMALIYIZ(Bkz. Object Slicing). >> 'upcasting' : Kalıtım hiyerarşisinde 'Sub-class' türünden olan bir nesneyi 'Base-class' türden bir nesneye dönüştürlmesi işlemidir. Dil, bu tip yukarı yönlü dönüşümleri OTOMATİK OLARAK, yani 'implicit-conversion' şeklinde gerçekleştirmektedir. * Örnek 1, class Car{}; class Audi : public Car {}; class Volvo : public Car {}; int main() { Audi myAudi; Volvo myVolvo; Car* myCar = &myAudi; // OK myCar = &myVolvo; // OK Car myCarTwo; myCarTwo = myAudi; // OK, Object Slicing: İLERİDE ÖĞRENECEĞİMİZ SEBEPLERDEN ÖTÜRÜ BU TİP KULLANIMDAN KAÇINMALIYIZ. } >> UML, diagram dili. Sınıf diagramını ve sınıflar arasındaki ilişkileri resmeden dil. Görselleştirmek için kullanılır. /*============================================================================================================*/ (17_07_11_2020) > Sınıflar (devam) : >> Inheritence('Kalıtım'), (Devam): >>> Nesne bazında 'Derived-class' türünden nesneleri 'Base-class' türünden nesnelere de atayabiliriz ki bu durum 'Object Slicing' e neden olur. Çünkü böyle bir atama sonrasında, 'Derived-class' içerisindeki 'Base-class', eşitliğin sol tarafındaki 'Base-class' a atanmakta ve 'Derived-class' içerisindeki diğer bilgiler ise silinmektedir. Çünkü bizim 'Derived-class' sınıfımız içerisinde de ayrıca bir 'Base-class' mevcuttur. * Örnek 1, #include class Car { public: void carFoo() { std::cout << "void Car::carFoo() was called. this => " << this << "\n"; } void carFunc() { std::cout << "void Car::carFunc() was called. this => " << this << "\n"; } }; class Volvo : public Car { public: void volvoFoo() { std::cout << "void Volvo::volvoFoo() was called. this => " << this << "\n"; } }; int main() { /* # OUTPUT # The address of myVolvoOne : [0x7ffc426ce3a6]. void Car::carFoo() was called. this => 0x7ffc426ce3a6 void Car::carFunc() was called. this => 0x7ffc426ce3a6 void Volvo::volvoFoo() was called. this => 0x7ffc426ce3a6 The address of myTemplateCar : [0x7ffc426ce3a7]. The address of myVolvoOne : [0x7ffc426ce3a6]. void Volvo::volvoFoo() was called. this => 0x7ffc426ce3a6 */ Volvo myVolvoOne{}; std::cout << "The address of myVolvoOne : [" << &myVolvoOne << "].\n"; myVolvoOne.carFoo(); // the function was derived from the 'Car' class. myVolvoOne.carFunc(); // the function was derived from the 'Car' class. myVolvoOne.volvoFoo(); // the function only belongs to the 'Volvo' class. Car myTemplateCar{}; std::cout << "The address of myTemplateCar : [" << &myTemplateCar << "].\n"; myTemplateCar = myVolvoOne; // 'Object Slicing' happened here. std::cout << "The address of myVolvoOne : [" << &myVolvoOne << "].\n"; myVolvoOne.volvoFoo(); } >>> 'composition' türü ilişki ile 'inheritence' tür ilişkinin ortak özelliği her iki ilişkide de bir sınıf türü içerisinde bir başka sınıf vardır. Yani 'composition' ve 'inheritence' tür ilişkileri diagrama döktüğümüz zaman mutlak suret ile bir kapsayan sınıf bir de kapsanan sınıf vardır. Sadece isimlendirme bazında farklılık vardır; -- COMPOSITION -- || -- INHERITENCE -- Member Object || Derived-class Buna ek olarak, 'Derived-class' sınıf türünden bir nesne ile o nesnenin içindeki 'Base-class' sınıfın adresleri aynı olacak diye bir KAİDE yoktur. Derleyici duruma göre içerideki 'Base-class' ı başka adreslere de yerleştirebilir. (C dilinde ise bir 'struct' türünden nesnenin adresi ile o yapı içerisindeki 'data-member' ın adresi aynı olmak ZORUNDA.). * Örnek 1, #include class Member { int mx, my; }; class Car { int mx, my; }; class Volvo : public Car { public: Member mx; char dy; }; int main() { /* # OUTPUT # sizeof(Member) : 8 sizeof(Car) : 8 sizeof(Volvo) : 20 = 'Car (8 byte)' + 'Member (8 byte)' + 'char (4 byte)' */ std::cout << "sizeof(Member) : " << sizeof(Member) << "\n"; std::cout << "sizeof(Car) : " << sizeof(Car) << "\n"; std::cout << "sizeof(Volvo) : " << sizeof(Volvo) << "\n"; } * Örnek 2, //.. struct LearningC{ int x; double y; }first; int main() { /* # OUTPUT # // Cpp &first => 0x5651f01f6160 &first.x => 0x5651f01f6160 &first.y => 0x5651f01f6168 */ // std::cout << "&first => " << &first << std::endl; // std::cout << "&first.x => " << &first.x << std::endl; // std::cout << "&first.y => " << &first.y << std::endl; /* # OUTPUT # // C &first => 0x55f57abfc020 &first.x => 0x55f57abfc020 &first.y => 0x55f57abfc028 */ printf("&first => %p\n", &first); printf("&first.x => %p\n", &first.x); printf("&first.y => %p\n", &first.y); return 0; } >>> İsim arama: C++ dilinde her şeyin başı 'name look-up'. 'Base-class' ve 'Derived-class' farklı alanlara sahip. İSİM ARAMA, ARANAN İSMİN BULUNMASI İLE BİTER. 'inheritence' mekanizmasında da isim arama şu şekilde meydana geliyor: Önce TÜREMİŞ SINIFIN İÇERİSİNDE, BULUNAMAZ İSE TABAN SINIFTA ARANIR. (Sistem şöyle işlemektedir. Önce 'İsim Arama', sonrasında 'Context Control' ve son olarak 'Access Control'). * Örnek 1, #include class Base { public: void func(); }; class Der : public Base { public: void foo(); }; int main() { Der myDer; myDer.foo(); // İSİM ARAMA ŞU SIRA İLE GERÇEKLEŞECEKTİR: // i. Önce '.' operatörünün solundaki sınıf içerisinde aranır ki bu durumda da 'foo' ismi bulunur ve ona bağlanır. // Velevki 'foo' isimli fonksiyon 'private' kısımda bildirilmiş olsun, yine isim aranır. Fakat bu sefer de // 'access-control' aşamasına takılacağından sentaks hatası alırız. // İsim bulunduğu için de İSİM ARAMA TAMAMLANIR. myDer.func(); // İSİM ARAMA ŞU SIRA İLE GERÇEKLEŞECEKTİR: // i. Önce '.' operatörünün solundaki sınıf içerisinde aranır ki bu durumda da 'func' ismi orada BULUNAMAZ. // ii. Sonrasında da Taban Sınıf içerisinde arama başlar ki bu durumda ismi orada bulur ve ona bağlar. // İsim bulunduğu için de İSİM ARAMA TAMAMLANIR. } * Örnek 2, #include class Base { public: void func(); }; class Der : public Base { // private: // void func(); public: void func(); }; int main() { Der myDer; myDer.func(); // 'Der' sınıfı içerisindeki çağrılacak. // Velevki bu 'func()' sınıfı ilgili 'Der' sınıfının 'private' kısmında bildirilmiş olsaydı İSİM BULUNACAKTI FAKAT // 'access-control' E TAKILACAKTI. } * Örnek 3, #include class Base { public: void func(int); }; class Der : public Base { public: void func(); }; int main() { Der myDer; myDer.func(12); // İlgili 'func' ismi 'Der' sınıfında aranacak ve bulunacak. Sonrasında 'Context Control' yapılacak fakat // parametre uyuşmazlığı olduğundan SENTAKS HATASI alacağız. // YUKARIDAKİ SENARYODA 'Function Overloading' MEKANİZMASI YOKTUR. // Yukarıdaki senaryoya göre 'Base' sınıfındaki 'func(int)' fonksiyonunun çağrılması: myDer.Base::func(12); // Fakat unutulmamalıdır ki yukarıdaki fonksiyon isimlendirmeleri çok nadir karşılaşacağımız isimlendirmelerdir. // Normal şartlarda kimse 'Base' sınıf ile 'Derived' sınıftaki fonksiyonlara aynı ismi VERMEZ. // 'Base' sınıfındaki fonksiyonu çağırdığımız gibi 'Derived' sınıftaki fonksiyonu da aynı şekilde çağırabiliriz // fakat GEREKSİZ: myDer.Der::func(); } * Örnek 4, #include class Base { public: void func(int); }; class Der : public Base { void func(double); public: }; int main() { Der myDer; myDer.func(12); // İlgili 'func' ismi 'Der' sınıfında aranacak ve bulunacak. Sonrasında 'Context Control' aşamasına geçilecek. // 'int' türden 'double' türe otomatik dönüşüm olduğundan bu aşamada da sorun çıkmayacak. En son olarak // 'Access Control' aşamasına gelinecek ki burada SENTAKS HATASI ALACAĞIZ çünkü ilgili isimdeki fonksiyonumuz // 'private' alanda bildirilmiş. } * Örnek 5, #include class Base { public: void func(int); }; void func(); class Der : public Base { public: void func() { func(12); // i. İlgili isim blok içerisinde aranacak fakat bulunamayacak. Sonrasında 'class-scope' içerisinde aranacak // ve 'Der' sınıfı içerisinde bulunacak, ona bağlanacak. Devamında 'Context Control' yapılacak fakat parametre // uyuşmazlığı yüzünden SENTAKS HATASI alacağız. func(); // recursive çağrı. Base::func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. (Base*)this->func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. static_cast(this)->func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. static_cast(*this).func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. ::func(); // 'global namespace-scope' da bulunan 'func()' çağrılacaktır. } }; int main() { //.. } * Örnek 6, #include class Base { public: void func(int); }; class Der : public Base { public: void func() { int func = 5; func(); // 'func' ismini blok içerisinde bulacak. Fakat 'Context Control' aşamasını geçemeyeceği için SENTAKS // HATASI alacağız. } }; int main() { //.. } * Örnek 7, #include // Multi-level inheritence class Base { public: void func(int); }; class Der : public Base { public: void func(double); }; class DerVerTwo : public Der{ public: void func(long); }; int main() { DerVerTwo mx; mx.func(); // Önce 'DerVerTwo' sınıfında isim aranır. Bulunamaz ise 'DerVerTwo::Der' sınıfında aranır. Yine bulunamaz ise // 'DerVerTwo::Der::Base' içerisinde aranır. // Yukarıdaki hiyerarşiye göre, // 'Der' sınıfı, 'DerVerTwo' sınıfının 'Direct-Base-Class' şeklinde. // 'Base' sınıfı da 'DerVerTwo' sınıfının 'Indirect-Base-Class' şeklinde. } >>> Yukarıda da gösterildiği üzere, Kalıtım Hiyerarşisi içerisinde normal şartlarda 'Function Overloading' mekanizması YOKTUR. Peki bizler bu mekanizmayı hiyerarşimize nasıl dahil edebiliriz? El-cevap : Birden fazla yöntem mevcuttur. * Örnek 1, Türetilmiş sınıfa, 'forwarding-function' görevi gören bir 'wrapper' function eklemek: #include class Base { public: void func(int) { std::cout << "void Base::func(int) was called.\n"; } }; class Der : public Base { public: void func() { std::cout << "void Der::func() was called.\n"; } // Alternative way of mimicing 'Function Overloading' Mechanism: // This kind of functions also may be called like 'forwarding function' void func(int n) { Base::func(n); } }; int main() { /* # OUTPUT # void Base::func(int) was called. void Der::func() was called. */ Der myDer; myDer.func(12); // 'Base::func(int)' çağrılacaktır. Fakat dolaylı yoldan çağrılacaktır. myDer.func(); // 'Der::func()' çağrılacaktır. } >>>> Taban sınıfın 'protected' kısmına fonksiyon eklemek: Kalıtım yoluyla elde edilen sınıflar, 'Base-class' ın 'public' ve 'protected' bölümlerini bünyesine almaktadır. Dolayısıyla türetilmiş sınıfın içerisinde Taban sınıfın 'public' ve 'protected' kısımlarını kullanabiliriz. Türetilmiş sınıfın dışarısında da sadece 'public' kısmı kullanabiliriz. * Örnek 1, #include class Base { protected: void func() { std::cout << "void Base::func() was called.\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # void Base::func(int) was called. void Der::func() was called. */ Der myDer; myDer.func(); // İlgili 'func' ismi 'Der' sınıfı içerisinde aranacaktır fakat bulunamayacaktır. Sonrasında 'Der::Base' // içerisinde aranacaktır. Devamında da 'Context Control' mekanizması işletilecektir ki burada da bir // sorun yoktur. En son olarak 'Access Control' aşaması olacak fakat burada SENTAKS HATASI alacağız. // Çünkü 'protected' kısım da 'private' kısım gibi dışarıya kapalıdır. } * Örnek 2, #include class Base { public: void func() { std::cout << "void Base::func() was called.\n"; } protected: void func(int x) { std::cout << "void Base::func(" << x << ") was called.\n"; } private: void func(char x) { std::cout << "void Base::func(" << x << ") was called.\n"; } }; class Der : public Base { public: void func() { Base::func(); } void func(int x) { Base::func(x); } void func(char c) { // Base::func(c); // error: ‘void Base::func(char)’ is private within this context } }; int main() { Der myDer; myDer.func(); // OUTPUT => void Base::func() was called. myDer.func(12); // OUTPUT => void Base::func(12) was called. // myDer.func('A'); // OUTPUT => error: ‘void Base::func(char)’ is private within this context } >>> Kalıtım hiyerarşisinde Özel Üye Fonksiyonların durumu: Bir türemiş nesne hayata gelirken, ilk önce içerisindeki taban sınıf hayata gelmektedir. * Örnek 1, Taban sınıfa ait 'Default Ctor' ve 'Dtor' var ise: #include class Base { public: Base() { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # Base::Base() Base::~Base() */ Der myDer; // Yukarıdaki 'Der' sınıfının 'Default Ctor' ve 'Dtor' fonksiyonları derleyici tarafından yazıldı. Bundan // dolayı da 'Der' içindeki 'Base' sınıfını da 'Default Init.' etti. Haliyle ilgili 'Base' sınıflarının // 'Default Ctor.' çağrıldı. İlgili nesnenin de hayatı biterken 'Der' sınıfının 'Dtor' fonksiyonu, // 'Base' sınıfının 'Dtor' fonksiyonunu çağıracaktır. // İLGİLİ 'myDer' nesnesinin OTOMATİK ÖMÜRLÜ OLDUĞUNU UNUTMAYALIM. } * Örnek 2, Taban sınıfa ait 'Default Ctor' YOK İSE: #include class Base { public: Base(int) { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # error: use of deleted function ‘Der::Der()’ note: ‘Der::Der()’ is implicitly deleted because the default definition would be ill-formed: */ Der myDer; // Yukarıdaki 'Der' sınıfının 'Default Ctor' ve 'Dtor' fonksiyonları derleyici tarafından yazıldı. Bundan // dolayı da 'Der' içindeki 'Base' sınıfını da 'Default Init.' etti. Haliyle ilgili 'Base' sınıflarının // 'Default Ctor.' çağrıldı. Fakat 'Base' sınıfının 'Default Ctor' OLMADIĞINDAN, çünkü 'Parametreli Ctor' var, // 'Der' sınıfının 'Default Ctor' fonksiyonu 'delete' edildi. Bizler de bu 'delete' edilmiş fonksiyona çağrı // yaptığımız için SENTAKS HATASI. } * Örnek 3, Taban sınıfın 'Default Ctor' unun 'private' kısımda olması : #include class Base { Base() { std::cout << "Base::Base()\n"; } public: ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # error: use of deleted function ‘Der::Der()’ note: ‘Der::Der()’ is implicitly deleted because the default definition would be ill-formed: error: ‘Base::Base()’ is private within this context */ Der myDer; // Yukarıdaki 'Der' sınıfının 'Default Ctor' ve 'Dtor' fonksiyonları derleyici tarafından yazıldı. Bundan // dolayı da 'Der' içindeki 'Base' sınıfını da 'Default Init.' etti. Haliyle ilgili 'Base' sınıflarının // 'Default Ctor.' çağrıldı. Fakat 'Base' sınıfının 'Default Ctor' ilgili sınıfın 'private' kısmında olduğudan, // 'Der' sınıfının 'Default Ctor' fonksiyonu 'delete' edildi. Bizler de bu 'delete' edilmiş fonksiyona çağrı // yaptığımız için SENTAKS HATASI. } * Örnek 4, Türemiş sınıfın 'Default Ctor' unun bizim tarafımızdan yazılması: #include class Base { public: Base() { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: Der() { std::cout << "Der::Der()\n"; } ~Der() { std::cout << "Der::~Der()\n"; } }; int main() { /* # OUTPUT # Base::Base() Der::Der() Der::~Der() Base::~Base() */ Der myDer; // Her ne kadar bizler 'Der' sınıfına 'Default Ctor' yazarken 'Base' sınıfını hayata getirmemiş olsak da // derleyici bunu otomatik olarak yapmaktadır ve ilgili sınıfın 'Default Ctor.' unu çağırmaktadır. } * Örnek 5, Türemiş sınıfının 'Default Ctor' fonksiyonunun bizler tarafından yazılması v2. #include class Base { public: Base(int) { std::cout << "Base::Base(int)\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: Der() : /* Base{31} */ // Bu ÇAĞRI ZORUNLU. AKSİ HALDE AŞAĞIDAKİ GİBİ HATA ALIRIZ. { std::cout << "Der::Der()\n"; } ~Der() { std::cout << "Der::~Der()\n"; } }; int main() { /* # OUTPUT # error: no matching function for call to ‘Base::Base()’ note: candidate: Base::Base(int) */ Der myDer; // Burada 'Der' sınıfının 'Default Ctor' fonksiyonunu biz yazıyoruz. 'Base' sınıfında da 'Default Ctor' YOK. // İş bu sebepten, bizler mutlak suret ile 'Base' sınıfın 'Parametreli Ctor' fonksiyonunu 'Ctor Init. List' ile // ÇAĞIRMAK ZORUNDAYIZ. Derleyicinin, 'Default Ctor' YOKTUR, diye bizim yazdığımız 'Default Ctor' fonksiyonunu // 'delete' etme LÜKSÜ YOK. } >>> Bir Kalıtım hiyerarşisinde ilk önce Taban sınıf, sonra var ise 'Data Members', son olarak da sınıfın kendisi hayata gelir. * Örnek 1, #include class Base { public: Base() { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Member { public: Member() { std::cout << "Member::Member()\n"; } ~Member() { std::cout << "Member::~Member()\n"; } }; class Der : public Base { public: Der() { std::cout << "Der::Der()\n"; } ~Der() { std::cout << "Der::~Der()\n"; } private: Member mx; }; int main() { /* # OUTPUT # Base::Base() Member::Member() Der::Der() Der::~Der() Member::~Member() Base::~Base() */ Der myDer; } >>> Kalıyım hiyerarşisinde 'Copy Ctor' ve 'Move Ctor' fonksiyonlarının durumu: Derleyici eğer Türemiş sınıf için bu fonksiyonları yazarsa, Taban sınıf için de bunları yazacaktır. * Örnek 1, //.. #include class Base { public: Base() { std::cout << "Base::Base()\n"; } Base(const Base&) { std::cout << "Base::Base(const Base&)\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # Base::Base() Base::Base(const Base&) */ Der myDer; Der myDerTwo(myDer); } * Örnek 2, Eğer türemiş sınıfın 'Copy Ctor' ve/veya 'Move Ctor' fonksiyonunu biz yazmışsak, Taban sınıfın 'Copy Ctor' / 'Move Ctor' fonksiyonunun çağrılmasından biz sorumluyuz. Eğer bu çağırma işlemini yapmaz isek derleyici Taban sınıfın 'Default Ctor' fonksiyonunu çağıracaktır. //.. #include class Base { public: Base() { std::cout << "Base::Base()\n"; } Base(const Base&) { std::cout << "Base::Base(const Base&)\n"; } }; class Der : public Base { public: Der() = default; // Taban sınıfın 'Copy Ctor' fonksiyonunu çağırmadığımız için derleyici 'Default Ctor' çağıracaktır Taban sınıf için. Der(const Der& other) /* : Base(other) */ { std::cout << "Der::Der(const Der&)\n"; } }; int main() { /* # OUTPUT # Base::Base() Base::Base() Der::Der(const Der&) */ Der myDer; Der myDerTwo(myDer); } >> Kalıtım hiyerarşisinde 'Copy Assigningment' ve 'Move Assigningment' fonksiyonlarının durumları: Derleyici eğer Türemiş sınıf için bu fonksiyonları yazarsa, Taban sınıf için de bunları yazacaktır. Eğer bu iki fonksiyonu biz yazarsak, Taban sınıflar için bu fonksiyonları çağırmak bizim sorumluluğumuzda. Aksi halde Türemiş sınıf içerisindeki Taban sınıf elemanları ATANMAMIŞ DURUMDA OLUR. * Örnek 1, #include class Base { public: Base& operator=(const Base&) { std::cout << "Base::operator=()\n"; return *this; } }; class Der : public Base { public: Der& operator=(const Der& other) { std::cout << "Der::operator=()\n"; // operator=(other); // RECURSIVE CALL. /* Base::operator=(other)*/ // TRUE WAY OF CALLING return *this; } Der& operator=(Der&& other) { std::cout << "Der::operator=()\n"; // operator=(std::move(other)); // RECURSIVE CALL. /* Base::operator=(std::move(other))*/ // TRUE WAY OF CALLING return *this; } }; int main() { /* # OUTPUT # Der::operator=() */ Der myDer, myDerTwo; myDerTwo = myDer; } >> Mülakat Sorusu: >>> 'func' nesnesi içerisindeki 'other' isimli değişkenin bilgileri nedir? * Örnek 1, #include class Myclass{ }; void foo(Myclass&&) { std::cout << "void foo(Myclass &&) was called.\n"; } void foo(const Myclass&) { std::cout << "void foo(const Myclass &) was called.\n"; } void func(Myclass&& other) { // 'other' isimli değişkenin; // Value Category : L-Value // Data Type : Myclass&& // İspatı, foo(other); // Çıktıda da görüldüğü üzere, İSİMLENDİRİLMİŞ HER BİR NESNE 'L-Value' kategorisindedir. Eğer TAŞIMAK İSTİYORSAK // AŞAĞIDAKİ GİBİ BİR ÇAĞRI YAPMALIYIZ: // foo(std::move(other)); // Bu fonksiyon içerisinde 'other' nesnesi ile bir sınıf türünden nesne hayata getirirsek, o sınıfın 'Copy Ctor' // fonksiyonu çağrılacaktır. // Myclass otherNew(other); // 'Copy Ctor' fonksiyonu çağrılacaktır. // Fakat bu fonksiyon sadece HAYATI BİTEN BİR NESNE İÇİN ÇAĞRILMALI. BU DURUMDA AMACIMIZIN DIŞINA ÇIKMIŞ OLUYORUZ. // İşte bu yüzden aşağıdaki gibi bir çağrı yapmalıyız: // Myclass otherNew(std::move(other)); // Bu fonksiyon içerisinde 'other' nesnesini bir vektöre taşımak için de; //... // vec.push_back(std::move(other)); } int main() { /* # OUTPUT # void foo(const Myclass &) was called. */ func(Myclass{}); } >> BİR FONKSİYONUN PARAMETRESİNİ SAĞ TARAF YAPACAKSAK'(Myclass&&)' TEK AMACIMIZ TAŞIMAK OLMALIDIR. * Örnek 1, KOPYALAMA SENARYOSU: #include #include void foo(std::string&& other) { std::cout << "void foo(Myclass &&) was called.\n"; std::string foo(std::move(other)); } void foo(const std::string& other) { std::cout << "void foo(const Myclass &) was called.\n"; std::string foo(other); } int main() { /* # OUTPUT # [33] : [Necati Ergin ders anlatmaktadır.] void foo(const Myclass &) was called. [33] : [Necati Ergin ders anlatmaktadır.] */ std::string str{"Necati Ergin ders anlatmaktadır."}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; foo(str); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // ÇIKTIDAN DA GÖRÜLDÜĞÜ ÜZERE 'str' NESNESİ HALA ESKİSİ GİBİ. YENİ BİR DEĞER ATANMALI YADA ÖMRÜ TAMAMEN BİTENE // KADAR BOŞ KALACAKTIR. } * Örnek 2, TAŞIMA SENARYOSU: #include #include void foo(std::string&& other) { std::cout << "void foo(Myclass &&) was called.\n"; std::string foo(std::move(other)); std::string fooTwo(other); // 'Copy Ctor' ÇAĞRILACAKTIR. } void foo(const std::string& other) { std::cout << "void foo(const Myclass &) was called.\n"; std::string foo(other); } int main() { /* # OUTPUT # [33] : [Necati Ergin ders anlatmaktadır.] void foo(Myclass &&) was called. [0] : [] */ std::string str{"Necati Ergin ders anlatmaktadır."}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; foo(std::move(str)); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // ÇIKTIDAN DA GÖRÜLDÜĞÜ ÜZERE 'str' NESNESİ ARTIK ESKİSİ GİBİ DEĞİL. YENİ BİR DEĞER ATANMALI YADA ÖMRÜ TAMAMEN BİTENE // KADAR BOŞ KALACAKTIR. } >> 'Run-time Polymorphism' (Çalışma Zamanı Çok Biçimliliği) : Kalıtım hiyerarşisinde taban sınıfın vermiş olduğu fonksiyonların/implementasyonların 'override' edilme durumlarıdır. >>>> Aşağıdaki yapılanmaları inceleyelim: Kalıtım hiyerarşisinde taban sınıflar türemiş sınıflara aşağıdaki hizmetleri sunmaktadır, 'public' kalıtım söz konusudur; >>>>> Türemiş sınıflara hem 'interface' hem de 'implementation' vermesi. * Örnek 1, #include class Airplane{ public: void takeOff() { std::cout << "Airplane has taken-off from the ground.\n"; } }; class Chessna : public Airplane { }; int main() { /* # OUTPUT # Airplane has taken-off from the ground. */ Chessna myPlane; myPlane.takeOff(); // Taban sınıfın sunöuş olduğu olanakları direkt olarak kullandı. } >>>>> Türemiş sınıflara hem 'interface' hem de bir 'default-implementation' vermesi, ki bu durumda bu tip taban sınıflar 'polymorphic' sınıf olarak geçer, ve türemiş sınıfların almış oldukları bu özellikleri 'override' edebilme kabiliyetleri. * Örnek 1, #include class Airplane{ public: void takeOff() { std::cout << "Airplane has taken-off from the ground.\n"; } // 'virtual' anahtar sözcüğü ile nitelendiğinden dolayı bu fonksiyon bir 'Sanal Fonksiyondur' / 'Virtual Function'. virtual void fly() { std::cout << "Airplane has started flying above the ground.\n"; } }; class Chessna : public Airplane { }; int main() { /* # OUTPUT # Airplane has taken-off from the ground. */ Chessna myPlane; myPlane.fly(); // Taban sınıfın sunmuş olduğu 'Default-Implementation' özelliğin kullanılması. Bu özelliğin 'override' edilmesi // daha sonra işlenecektir. } >>>>> Türemiş sınıflara sadece ve sadece bir 'interface' vermeleri, ki bu durumda bu tip taban sınıflara 'abstract' sınıf denmektedir, ve türemiş sınıflar iş bu özellikleri 'override' ETME ZORUNLULUĞUNDALAR. 'abstract' olan bir sınıf türden nesne OLUŞTURAMAYIZ. * Örnek 1, #include class Airplane{ public: void takeOff() { std::cout << "Airplane has taken-off from the ground.\n"; } // 'virtual' anahtar sözcüğü ile nitelendiğinden dolayı bu fonksiyon bir 'Sanal Fonksiyondur' / 'Virtual Function'. virtual void fly() { std::cout << "Airplane has started flying about the ground.\n"; } virtual void land() = 0; // Hem 'virtual' anahtar sözcüğü ile hem de '= 0' şeklinde yazıldığından bu fonksiyonumuz // 'Saf sanal Fonksiyondur' / 'Pure Virtual Function'. İş bu fonksiyonun TÜREMİŞ SINIFLARCA İMPLEMENTE EDİLMESİ // BİR ZORUNLULUKTUR. AKSİ HALDE ÇAĞIRAMAYIZ. Artık bu sınıfımız bir 'abstract' sınıf olduğundan, bu sınıf // türünden nesne de oluşturamayız. }; class Chessna : public Airplane { }; int main() { /* # OUTPUT # error: cannot declare variable ‘myPlane’ to be of abstract type ‘Chessna’ note: because the following virtual functions are pure within ‘Chessna’: note: virtual void Airplane::land() */ Chessna myPlane; myPlane.fly(); } >>> 'Late-binding' veya 'dynamic-binding' : Hangi fonksiyonun çağrılacağı ÇALIŞMA ZAMANINDA BELLİ OLAN senaryolardır. * Örnek 1, Taban sınıf türemiş sınıfa hem 'interface' hem de 'Default-Implementation' hizmetlerini sunmaktadır. Fakat türemiş sınıf içerisinde bu özellikler 'override' edilmişlerdir. #include class Airplane{ public: void takeOff() { std::cout << "void Airplane::takeOff() was called...\n"; } virtual void fly() { std::cout << "virtual void Airplane::fly() was called...\n"; } }; class Chessna : public Airplane { public: void takeOff() { std::cout << "void Chessna::takeOff() was called...\n"; } void fly() { std::cout << "void Chessna::fly() was called...\n"; } }; int main() { /* # OUTPUT # void Airplane::takeOff() was called... void Chessna::fly() was called... */ Chessna myPlane; Airplane* myPlanePtr = &myPlane; myPlanePtr->takeOff(); // İsim arama kuralları gereği 'takeOff' ismi 'Airplane' sınıfı içerisinde aranacak ve bulunacak. Derleme zamanında // ona bağlanacaktır. myPlanePtr->fly(); // İsim arama kuralları gereği 'fly' ismi 'Airplane' sınıfı içerisinde aranacak fakat Hangi fonksiyonun çağrılacağı // 'Çalışma Zamanında' belli olacaktır. Buna sebebiyet veren şey ise ilgili 'fly' fonksiyonun Taban Sınıfta 'virtual' // olarak nitelendirilmesi. Burada yapılan çağrının 'gösterici' veya 'referans' ile YAPILMASI ÖNEMLİDİR. } >>> 'virtual dispatch' : Yukarıdaki sürece denmektedir. Türemiş sınıf, taban sınıftan aldığı özellikleri 'override' edebilir. >>>> Türemiş sınıf, Taban sınıftan aldığı fonksiyonları 'override' ederken, İMZALARI VE GERİ DÖNÜŞ DEĞERİ DE AYNI OLMAK ZORUNDADIR. AKSİ HALDE SENTAKS HATASI. * Örnek 1, #include class Airplane{ public: virtual void func(); void foo(); }; class Chessna : public Airplane { public: void func(); // Taban sınıftaki 'func' ile AYNI İMZALARA VE AYNI GERİ DÖNÜŞ DEĞERİNE SAHİP OLDUĞUNDAN EMİN OLMALIYIZ EĞER // 'override' EDİYORSAK ki burada 'override' edilmiştir. int func(); // Sentaks hatası. Çünkü geri dönüş değeri birbiri ile tutmuyor. void func(int); // NE 'override' DURUMU NE DE 'Function Overloading' DURUMU. SADECE VE SADECE 'masking' DURUMU. int foo(); // Legal çünkü 'foo' fonksiyonu 'virtual' olarak NİTELENMEMİŞ. Fakat bu şekildeki kullanımdan kaçınmalıyız. }; int main() { /* # OUTPUT # */ //.. } * Örnek 2, #include class Airplane{ public: virtual void func() { std::cout << "A"; } void foo() { std::cout << "AA"; } }; class Chessna : public Airplane { public: // BU FONKSİYON HER NE KADAR 'virtual' ANAHTAR SÖZCÜĞÜ İLE NİTELENMEMİŞ OLSA DAHİ, ASLINDA BU FONKSİYON DA BİR // 'virtual function' DUR. void func() { std::cout << "B"; } void foo() // Bu şekildeki kullanımdan kaçınmalıyız. { std::cout << "BB"; } }; int main() { /* # OUTPUT # error: overriding ‘virtual void Airplane::func()’ */ //.. Chessna myPlaneChessna; Airplane* myPlane = &myPlaneChessna; myPlane->func(); // OUTPUT => B myPlane->foo(); // OUTPUT => AA } >>>> 'override' anahtar sözcüğünün kullanılması: Taban sınıftaki sanal bir fonksiyonu yukarıdaki örnekler gibi 'override' edebiliriz. Fakat C++11 ile dile eklenen bu anahtar sözcük ile ilgili işlemleri bu anahtar sözcüğü kullanarak yapmamız gerekmektedir. BU ANAHTAR KELİMEYİ KULLANMAMAK HER NE KADAR SENTAKS HATASI OLMASA BİLE YANLIŞ KABUL EDİLMEKTEDİR. Çünkü bu anahtar sözcük kullanıldığında, 'override' için gereken şartların sağlandığından emin olduruyor. DERLEYİCİ 'override' ŞARTLARININ SAĞLANIP SAĞLANMADIĞINA BAKMAKTADIR. 'cosnt' anahtarı da İMZANIN BİR PARÇASI OLDUĞUNDAN, 'override' kelimesi en son yazılmalıdır. * Örnek 1, Yanlış İmza yanlışının önüne geçilmesi #include class Base { public: virtual void func(int) const { std::cout << "Base::func(int) const\n"; } }; class Der : public Base { public: // 'virtual' anahtar sözcüğünü yazmasak bile bu fonksiyon da bir 'virtual-function'. 'const' anahtar sözcüğü // imzaya dahil fakat biz burada yazmadığımız için ne 'override' ettik ne de 'overload' ettik. Sadece 'masking'. void func(int) { std::cout << "Base::func(int)\n"; } }; int main() { /* # OUTPUT # //.. */ Der myDer; Base* myBase = &myDer; myBase->func(31); // OUTPUT => Base::func(int) const // Çünkü isim arama kuralları gereği isim 'Base' sınıfında arandı ve bulundu. Oradaki fonksiyon çağrısına bağlandı. // Fakat bizim niyetimiz 'override' etmekti ama başaramadık. Çünkü Türemiş Sınıf içerisinde imza yanlış yazıldı. } * Örnek 2, Sanal Olmayan fonksiyonların 'override' edilmesinin önüne geçmek #include class Base { public: void func(int) { std::cout << "Base::func(int)\n"; } }; class Der : public Base { public: void func(int) // İş bu 'func' fonksiyonu SANAL OLMADIĞINDAN, 'override' edilemez. { std::cout << "Der::func(int)\n"; } }; int main() { /* # OUTPUT # //.. */ Der myDer; Base* myBase = &myDer; myBase->func(31); // OUTPUT => Base::func(int) // Çünkü isim arama kuralları gereği isim 'Base' sınıfında arandı ve bulundu. Oradaki fonksiyon çağrısına bağlandı. // Fakat bizim niyetimiz 'override' etmekti ama başaramadık. Çünkü ilgili fonksiyon Taban Sınıf içerisinde // 'virtual' olarak nitelenmedi. } * Örnek 3, Taban Sınıfdaki sanal olan bir fonksiyonun imzasının bir zaman sonra değişmesi Türemiş Sınıflardaki aynı isimli fonksiyonların 'override' etme vasfını yitirtecektir. Dolayısıyla türemiş sınıflardaki o fonksiyonlar bir nevi 'name-masking', bir nevi 'redefinition' durumuna düşeceklerdir. Duruma göre SENTAKS HATASI DA alamayabiliriz. İşte bu sorunun da önüne geçmek için 'override' anahtar sözcüğünü kullanmalıyız. * Örnek 4, 'override' anahtar sözcüğünün kullanılması #include class Base { public: void func(int) { std::cout << "Base::func(int)\n"; } virtual void foo(char) { std::cout << "Base::foo(char)\n"; } virtual void myFoo() { std::cout << "Base::myFoo()\n"; } }; class Der : public Base { public: void func(int) override; { std::cout << "Der::func(int)\n"; } float foo(float) override { std::cout << "Der::foo(float)\n"; return 13.31f; } void myFo() override { std::cout << "Der::myFoo()\n"; } }; int main() { /* # OUTPUT # error: ‘void Der::func(int)’ marked ‘override’, but does not override (ÇÜNKÜ SANAL OLMAYAN BİR FONKSİYONU 'override' ETMEYE ÇALIŞTIK) error: ‘float Der::foo(float)’ marked ‘override’, but does not override (ÇÜNKÜ İMZASI VE GERİ DÖNÜŞ DEĞERİ FARKLI BİR FONKSİYON İLE 'override' ETMEYE ÇALIŞTIK) error: ‘void Der::myFo()’ marked ‘override’, but does not override (ÇÜNKÜ TABAN SINIFTA OLMAYAN BİR FONKSİYONU 'override' ETMEYE ÇALIŞTIK) */ Der myDer; Base* myBase = &myDer; myBase->func(31); myBase->foo(31.13f); } >>>> 'Object Slicing' senaryosu en çok burada etkisini göstermektedir. * Örnek 1, #include class Airplane{ public: virtual void func() { std::cout << "A"; } }; class Chessna : public Airplane { public: void func() { std::cout << "B"; } }; int main() { /* # OUTPUT # */ //.. Chessna myPlaneChessna; Airplane myPlane = myPlaneChessna; // Object-slicing occured here. 'Chessna' içerisindeki 'Airplane' sınıfı, // 'myPlane' isimli değişkene ilk değer verirken kullanıldı. myPlane.func(); // OUTPUT => A } >>> PEKİŞTİRİCİ BİR ÖRNEK: * Örnek 1, // Car.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Car case. The Car just started. The Car just started running. The Car just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { car_game(create_random_car()); } // Yukarıdaki 'Audi', 'Mercedes' sınıflarından herhangi birisi 'Car' sınıfı içerisindeki fonksiyonlardan birini // ya da bir kaçını 'override' ETMEDİĞİ DURUMLARDA, 'Car' sınıfındaki İMPLEMENTASYON KULLANILACAKTIR. // Eğer 'Mercedes_S500' sınıfı, taban sınıf olan 'Mercedes' sınıfındaki fonksiyonlardan birini ya da bir kaçını // 'overload' etmemişse, bu durumda 'Mercedes' sınıfındaki İMPLEMENTASYON KULLANILACAKTIR. // ÖZETLE, TABAN SINIFTAN ALINAN VIRTUAL FONKSİYONLAR 'override' EDİLMEZ İSE İMPLEMENTASYONUN SAĞLANMIŞ OLAN // TABAN SINIFTAKİ FONKSİYON ÇAĞRILIR. Yani, // 'Mercedes' etmez ise 'Car' sınıfındaki; // 'Mercedes_S500' etmez ise 'Mercedes' sınıfındaki; // Hem 'Mercedes_S500' hem de 'Mercedes' etmez ise 'Car' sınıfındaki. // 'virtual dispatch' mekanizmasının işlemesi için 'car_game' isimli fonksiyonun ya 'gösterici' ya da 'referans' // yoluyla argüman alması gerekmektedir. Düz nesne olarak argüman aldığında, sadece 'Car' sınıfındaki fonksiyonlar // çağrılacaktır. } >>> Taban sınıfın sanal fonksiyonunu 'override' eden türemiş sınıf fonksiyonlarının bildiriminde 'virtual' anahtar sözcüğü kullanılsa da kullanılmasa da bu fonksiyon da sanaldır. * Örnek 1, class Base { public: virtual void func(); // 'func' is virtual }; class Der : public Base { public: void func(); // 'func' is virtual and overrides. }; class DerTwo : public Der { public: void func(); // 'func' is also virtual and overrides. } > 'Contextual Keyword' : Tek başlarına kullanıldıklarında bir 'keyword' değil fakat özel yerlerde kullanıldıklarında bir 'keyword' olarak işlev gören kelimelerdir. Bunlar, C++11 ile dile eklenen 'override' kelimesi ile 'final' kelimesidir. > C++17 öncesi dönemde 'inline-variable' kullanımına alternatifler: 'inline' anahtar sözcüğünün değişkenleri nitelemesi C++17 ile dile eklendiğinden, bundan önceki dönemlerde aşağıdaki gibi bir kullanım uygulanabilir; * Örnek 1, // theHeader.hpp //.. inline int& get_instance() { static int x = 100; // 'x' her hangi bir türden olabilir. return x; } // main.cpp #include "theHeader.hpp" //.. int main() { auto& x = get_instance(); //.. } > Sınıflar (devam) : >> Sınıf içi 'using declaration' : 'using' anahtar sözcüğü aşağıdaki kullanımlar için 'overload' edilmiş olup her biri kendi içerisinde farklı kural setleri tanımlamaktadır. >>> 'using std::cout;' şeklindeki kullanımlar ki bu tip kullanımlara 'using declaration' diyoruz, >>> 'using namespace std;' şeklindeki kullanımlar ki bu tip kullanımlara da 'using namespace directive' diyoruz, >>> 'using Word = int;' şeklindeki kullanımlar ki bu tip kullanımlar da C dilindeki 'typedef' ile oluşturulan Tür Eş İsimlerin C++ versiyonu. >>> 'using Base::func;' şeklindeki kullanım ki bu da dördüncü 'overload' anlamıdır, Taban sınıfdaki ismi Türemiş sınıfa ENJEKTE ETMEKTEDİR. Bu tip kullanıma ise 'in-class using declaration' denmektedir. Dolayısıyla, eğer türemiş sınıf içerisinde de aynı isimde bir fonksiyon varsa, dolaylı yoldan 'Function Overloading' mekanizması devreye girmektedir. * Örnek 1, #include class Base { public: void func() { std::cout << "void Base::func() was called.\n"; } void func(int, double) { std::cout << "void Base::func(int, double) was called.\n"; } void func(int, int, double) { std::cout << "void Base::func(int, int, double) was called.\n"; } void func(int, int, double, double) { std::cout << "void Base::func(int, int, double, double) was called.\n"; } }; class Der : public Base { public: using Base::func; // I void func(int) { std::cout << "void Der::func(int) was called.\n"; } }; int main() { /* # OUTPUT # void Base::func() was called. void Der::func(int) was called. void Base::func(int, double) was called. void Base::func(int, int, double) was called. void Base::func(int, int, double, double) was called. */ Der myDer; myDer.func(); // 'func' ismi 'Der' sınıfı içerisinde aranacak. 'I' numaralı 'in-class using directive' gördüğü için // 'Base' sınıf içerisine de bakacak. Bu işlem de dolaylı yoldan 'Function Overloading' mekanizmasını // tetikleyecektir. Artık iki tane 'overload' olduğundan, parametreler birbirleri ile karşılaştırılacak. // 'Exact-match' durumundan, 'Base' sınıfındaki seçilecek. myDer.func(12); myDer.func(12,1.2); myDer.func(12,24,1.2); myDer.func(12,24,1.2,2.4); } >>> Taban sınıf ve türemiş sınıf içerisindeki ilgili fonksiyonların imzaları da aynı olursa, 'ambiguity' TİP SENTAKS HATASI OLUŞMAZ. * Örnek 1, #include class Base { public: void func() { std::cout << "void Base::func() was called.\n"; } }; class Der : public Base { public: using Base::func; // I void func() { std::cout << "void Der::func() was called.\n"; } }; int main() { /* # OUTPUT # void Der::func() was called. */ Der myDer; myDer.func(); // 'func' ismi 'Der' sınıfı içerisinde aranacak. 'I' numaralı 'in-class using directive' gördüğü için // 'Base' sınıf içerisine de bakacak. Bu işlem de dolaylı yoldan 'Function Overloading' mekanizmasını // tetikleyecektir. Artık iki tane 'overload' olduğundan, parametreler birbirleri ile karşılaştırılacak. // 'Exact-match' durumundan, 'Der' sınıfındaki seçilecek. Çünkü ilgili fonksiyonun gizli parametresi // 'Der*' iken diğerininki 'Base*' şeklinde. } >>> Bu tip kullanım ile Taban sınıfın 'protected' bölümündeki fonksiyonları da Türemiş sınıf içerisinde görülür kılabiliyoruz. * Örnek 1, #include class Base { protected: void func() { std::cout << "void Base::func() was called.\n"; } }; class Der : public Base { public: using Base::func; // I }; int main() { /* # OUTPUT # void Base::func() was called. */ Der myDer; myDer.func(); // 'I' numaralı bildirimi 'public' alanda yaptığımız için 'Base' sınıfının 'protected' bölümünü bir nevi // dışarıya da açmış olduk. Aksi halde 'protected' bölümü sadece Türemiş Sınıf içerisinde erişilebilir durumdadır. } >>> Bu bildirimi Taban sınıfın 'private' kısmındaki bir fonksiyon için yapsaydık, BU BİLDİRİMİN KENDİSİ BİR SENTAKS HATASI OLACAKTI. >> Standart Template Library (STL) (devam) : >> C++ 17 ile gelen 'invoke()' fonksiyonu: Bir sınıf türünden nesneye ve bir fonksiyon adresini argüman olarak alan, sonrasında da bu fonksiyonu çağıran fonksiyondur. 'functional' başlık dosyasında bildirilmiştir. * Örnek 1, #include #include class Base { public: void func() { std::cout << "void Base::func() was called. this => " << this << "\n"; } }; class Der : public Base { }; int main() { /* # OUTPUT # &[myDer] : 0x7ffc12226cff void Base::func() was called. this => 0x7ffc12226cff */ Der myDer; std::cout << "&[myDer] : " << &myDer << "\n"; std::invoke(&Base::func, myDer); } >> 'string' sınıfındaki 'Tür Dönüştürme' operatörleri: >>> 'to_string()' : Global bir fonksiyondur. Temel türlerin her birisi için çağrılabilir. Geri dönüş değeri 'std::string' şeklindedir. C++11 ile dile eklenmiştir. >>> 'stoi()' : Global bir fonksiyondur. 'std::string' türünden nesneleri, 'int' türe dönüştürür. Yazı içindeki harflare ellemez. İlk parametresi dönüştürülecek yazı, ikinci parametresi bir indeks, üçüncü parametre ise ilgili rakam için kaçlık taban bilgisi. İkinci parametreye geçilen adrese, en son dönüştürülen rakamın adresini yazmaktadır. * Örnek 1, #include #include int main() { std::string str{"123Emre"}; size_t idx; auto ival = std::stoi(str); std::cout << "ival : " << ival << "\n"; // OUTPUT => ival : 123 ival = std::stoi(str, &idx); std::cout << "ival : " << ival << "\n"; // OUTPUT => ival : 123 std::cout << "idx : " << idx << "\n"; // OUTPUT => idx : 3 ival = std::stoi(str, &idx, 16); std::cout << "ival : " << ival << "\n"; // OUTPUT => ival : 4670 std::cout << "idx : " << idx << "\n"; // OUTPUT => idx : 4 // İlgili yazıdaki 'E' karakteri de dahil edildi. } /*============================================================================================================*/ (18_08_11_2020) > Sınıflar (devam) : >> 'Run-time Polymorphism' (devam) : 'Abstract Class' ve 'Concrete Class' olarak iki gruba ayrılır. * Örnek 1, #include class Base {}; // 'Base' is a 'Concrate Class' class BaseTwo { virtual void foo(); // 'BaseTwo' is still a 'Concrate Class' }; class BaseThree { virtual void foo() = 0; // 'BaseThree' is a 'Abstract Class'. Bu sınıfı ya sadece 'incomplete-type' kullanabileceğimiz yerlerde kullanabiliriz // ya da 'foo' fonksiyonunu türemiş sınıflar içerisinde tanımlamalıyız. }; int main() { /* # OUTPUT # //.. */ Base myBase; // Legal BaseTwo myBaseTwo; // Legal BaseThree myBaseThree; // İLLEGAL. } * Örnek 2, #include class Base // 'Base' is a 'Abstract Class' { public: virtual void foo() = 0; }; class Nec : public Base // 'Nec' is a 'Abstract Class' çünkü 'foo' fonksiyonunu TANIMLAMADI. { }; class NecTwo : public Nec // 'NecTwo' is a 'Concrate Class'. Taban sınıftaki BÜTÜN 'pure-virtual-function' LAR TANIMLI OLMALI.. { void foo()override { } }; int main() { /* # OUTPUT # //.. */ Base myBase; // error: cannot declare variable ‘myBase’ to be of abstract type ‘Base’ Nec myNec; // error: cannot declare variable ‘myNec’ to be of abstract type ‘Nec’ NecTwo myNecTwo; } * Örnek 3, // CAR.hpp #include #include class Car { public: virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } virtual void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Tesla case. The Tesla just started. The Tesla just started running. The Tesla just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { car_game(create_random_car()); } } >>> 'virtual-dispatch' mekanizmasının devreye girdiği ve girmediği yerler: >>>> Nesnenin kendisini kullanarak fonksiyon çağrımlarında bu mekanizma devreye GİRMEZ. Referans veya gösterici kullanmamız gerekmektedir. >>>> Taban sınıf içerisindeki SANAL OLMAYAN fonksiyonları çağırırken de DEVREYE GİRER çünkü 'this' göstericisinden dolayı. * Örnek 1, Yukarıdaki 'Car.hpp' dosyasını baz alalım: // Car.hpp //.. class Car { public: //.. void maintain() // Bu fonksiyon bir 'this' göstericisine ihtiyaç duymaktadır. { // Aynı 'this' göstericisi bu fonksiyonlara argüman olarak geçilecektir. start(); stop(); } }; // main.cpp //.. int main() { /* # OUTPUT # The Mercedes case. The Mercedes just started. The Mercedes just stopped. */ auto ptr = create_random_car(); ptr->maintain(); // Çıktıdan da görülebileceği üzere sanallık mekanizması devreye girdi. Çünkü 'ptr' isimli göstericinin türü // 'Car*' şeklinde. Göstermiş olduğu öğenin türü de 'Mercedes*' türünden bir nesne. // Her ne kadar 'maintain' fonksiyonu SANAL OLMASA DA, o fonksiyona geçilen gizli argümanın türü 'Mercedes*' // olduğu için, o fonksiyonun gövdesindeki diğer iki fonksiyona da aynı adres geçildi. // İşte sanal olmayan fonksiyonlar ile sanal fonksiyonların çağrılmasına da 'NVI' denmektedir, // 'non-virtual interface'. Bu yaklaşım biçiminde, sanal fonksiyonlar dışarıya KAPALIDIR. // Bir diğer deyişle SANAL FONKSİYONLARIMIZI 'private' yaparak ve onlara bir 'public' ama sanal olmayan fonksiyon // ile çevrelemiş olduk. } * Örnek 2, #include #include class Car{ private: virtual void start() { std::cout << "Car has just started..." << std::endl; } virtual void run() { std::cout << "Car has just running..." << std::endl; } virtual void stop() { std::cout << "Car has just stopped..." << std::endl; } public: void handleCar() { start(); run(); stop(); } }; class Tesla : public Car{ private: virtual void start() override { std::cout << "Tesla has just started..." << std::endl; } virtual void run() override { std::cout << "Tesla has just running..." << std::endl; } virtual void stop() override { std::cout << "Tesla has just stopped..." << std::endl; } void charge() { std::cout << "Tesla has just charged..." << std::endl; } }; class Tesla_S500 : public Tesla{ private: virtual void start() override { std::cout << "Tesla_S500 has just started..." << std::endl; } virtual void run() override { std::cout << "Tesla_S500 has just running..." << std::endl; } virtual void stop() override { std::cout << "Tesla_S500 has just stopped..." << std::endl; } }; class Ford : public Car{ private: virtual void start() override { std::cout << "Ford has just started..." << std::endl; } virtual void run() override { std::cout << "Ford has just running..." << std::endl; } virtual void stop() override { std::cout << "Ford has just stopped..." << std::endl; } }; class Nissan : public Car{ private: virtual void start() override { std::cout << "Nissan has just started..." << std::endl; } virtual void run() override { std::cout << "Nissan has just running..." << std::endl; } virtual void stop() override { std::cout << "Nissan has just stopped..." << std::endl; } }; class Fiat : public Car{ private: virtual void start() override { std::cout << "Fiat has just started..." << std::endl; } virtual void run() override { std::cout << "Fiat has just running..." << std::endl; } virtual void stop() override { std::cout << "Fiat has just stopped..." << std::endl; } }; class Volvo : public Car{ private: virtual void start() override { std::cout << "Volvo has just started..." << std::endl; } virtual void run() override { std::cout << "Volvo has just running..." << std::endl; } virtual void stop() override { std::cout << "Volvo has just stopped..." << std::endl; } }; Car* carFactory() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{1, 6}; switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Tesla_S500 case.\n"; return new Tesla_S500; case 2: std::cout << "The Ford case.\n"; return new Ford; case 3: std::cout << "The Nissan case.\n"; return new Nissan; case 4: std::cout << "The Fiat case.\n"; return new Fiat; case 5: std::cout << "The Volvo case.\n"; return new Volvo; default: return nullptr; } } void car_game(Car* carPtr) { carPtr->handleCar(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Nissan case. Nissan has just started... Nissan has just running... Nissan has just stopped... //---------------------------------------------------------- The Volvo case. Volvo has just started... Volvo has just running... Volvo has just stopped... //---------------------------------------------------------- The Volvo case. Volvo has just started... Volvo has just running... Volvo has just stopped... //---------------------------------------------------------- The Nissan case. Nissan has just started... Nissan has just running... Nissan has just stopped... //---------------------------------------------------------- The Ford case. Ford has just started... Ford has just running... Ford has just stopped... //---------------------------------------------------------- The Fiat case. Fiat has just started... Fiat has just running... Fiat has just stopped... //---------------------------------------------------------- The Nissan case. Nissan has just started... Nissan has just running... Nissan has just stopped... //---------------------------------------------------------- The Tesla_S500 case. Tesla_S500 has just started... Tesla_S500 has just running... Tesla_S500 has just stopped... //---------------------------------------------------------- The Volvo case. Volvo has just started... Volvo has just running... Volvo has just stopped... //---------------------------------------------------------- */ for(size_t counter{}; counter < 100; ++counter) car_game(carFactory()); return 0; } >>>>> Buradan da hareketle diyebiliriz ki taban sınıfın 'private' alanında bildirilen/tanımlanan sanal fonksiyonlar, türemiş sınıf içerisinde 'override' edilebilirler. >>>> Taban sınıf içerisindeki SANAL OLMAYAN fonksiyonlar, sanal fonksiyonları '::' ile çağırır ise DEVREYE GİRMEZ. Yukarıdaki örneği baz alırsak; * Örnek 1, // Car.hpp //.. class Car { public: //.. virtual void start() { std::cout << "The Car just started.\n"; } void maintain() { Car::start(); // Artık bu fonksiyon çağrısında sanallık mekanizması DEVREYE GİRMEYECEKTİR. stop(); } }; // main.cpp //.. int main() { /* # OUTPUT # The Mercedes case. The Car just started. The Mercedes just stopped. */ auto ptr = create_random_car(); ptr->maintain(); } >>>> Taban sınıfın kurucu işlevi ve yıkıcı işlevi içerisinde yapılan sanal fonksiyon çağrılarında sanal gönderim mekanizması DEVREYE GİRMEZ. * Örnek 1, #include class Base { public: Base() { vfunc(); } ~Base() { vfunc(); } virtual void vfunc() { std::cout << "Base::vfunc()\n"; } void foo() { vfunc(); } }; class Der : public Base { public: void vfunc() override { std::cout << "Der::vfunc()\n"; } }; int main() { /* # OUTPUT # Base::vfunc() // 'Der' içerisindeki 'Base' için 'Ctor.' Der::vfunc() // 'overriden' Base::vfunc() // 'Der' içerisindeki 'Base' için 'Dtor.' */ Der myDer; myDer.foo(); // Burada dönen mevzu şudur; 'Der' nesnesi hayata gelirken önce içerisindeki 'Base' nesnesi hayata gelmektedir. // Velevki sanallık mekanizması devreye girmiş olsaydı, 'Der' nesnesinin hayata gelme süreci tamamlanmadan bir üye // fonksiyonuna çağrı yapılacaktı. Bu da istenmeyen bir durum. Aynı durum 'Der' nesnesinin hayatı sona ererken de // geçerlidir. İlgili nesnenin hayatı bittikten sonra ona ait olan bir fonksiyon çağrılacak eğer mekanizma devreye // girseydi. } >>>> Bu mekanizmanın implementasyonu(MÜLAKAT SORUSU): Derleyiciyi yazanlara göre değişir. Standartlarda bir zorlama yoktur. Bir sınıf içerisinde 'virtual' fonksiyon olmadığı sürece, C dilindeki yapılardaki gibi, elemanlarının sizeof değerlerinin toplamı kadar bir büyüklüğe sahiptir. Eğer bir tane bile 'virtual' fonksiyon eklersek, bu sınıf artık 'polymorphic' bir sınıf haline geldi. Bu haldeki sınıfların içerisinde gömülü olarak bir 'gösterici' daha vardır ki bu göstericiye populer olarak 'virtual-table' yada 'virtual function table pointer' denmektedir. Tabiri caiz ise Sanal Fonksiyonların adreslerinin bulunduğu bir veri yapısı düşünün. İşte gömülü olan bu gösterici, bu veri yapısını göstermektedir. Fakat bu sanal fonksiyonlar, ilgili veri yapısının birinci indisinten itibaren konumlanmaktadırlar. Her sınıf için bu tablo vardır ve her sınıf içerisindeki bu tablonun aynı indisi, aynı fonksiyonun adresini tutmaktadır. Sıfırıncı adreste ne olduğunu daha sonra göreceğiz. Aşağıdaki örneği inceleyelim: * Örnek 1, //.. class Car { public: virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; }; int main() { /* PSUEDU CODE */ // i. 'carptr' isminin bir gösterici ismi olduğunu varsayalım ve bu göstericiyi kullanarak herhangi bir // 'member-function' çağrısı yapalım => carptr->start(); // ii. Derleme zamanında derleyici 'start' ismini 'Car' sınıfında arayacak ve bunun Sanal Fonksiyona ait olduğunu // görecek. // iii. Daha sonra derleyici gömülü vaziyette duran 'Virtual Function Table Pointer' şeklinde işlev gören göstericiye // gidecek. => carptr->vptr... // IV. Aynı zamanda Sanal Fonksiyonlar da bu Sanal Fonksiyon Tablosunda indislenecekler. Varsayalım ki 'start' // fonksiyonu birinci indise, 'run' fonksiyonu ikinci indise ve 'stop' fonksiyonu da üçüncü indise konumlandırılsın. // Daha sonra derleyici 'start' fonksiyonunun karşılığı olan indise erişecek => carptr->vptr[1]... // V. Sonrasında da bu fonksiyona çağrı yapacak. => carptr->vptr[1](); // Buradaki tabloda yerleştirilen fonksiyonlar taban sınıftaki fonksyion da olacaktır eğer ilgili fonksiyon türmiş // sınıflarda 'override' edilmemiş ise. Eğer 'override' edilmişler ise türemiş sınıflardaki fonksiyonlar tabloya // eklenecektir. } >>>> Bu mekanizmanın da bir maliyeti vardır. Buna dikkat etmeliyiz. Bu maliyet SINIF BAŞINA OLUŞTURULAN 'virtual function table', ki bu aynı zamanda bir veri yapısıdır (örneğin, vektör) ve her nesnenin sahip olduğu 'virtual function table pointer' dan oluşmaktadır. Çalışma zamanında da yukarıda anlatılan ekstra 'derefencing' ve ilgili 'virtual function table' oluşturma ve doldurma maliyetleri vardır. * Örnek 1, #include class Base { public: virtual void vfunc(){} // 8 byte virtual void vfoo(){} void foo(){} int mx, my; // 4 byte + 4 byte }; class Der : public Base {}; int main() { /* # OUTPUT # */ std::cout << "sizeof Base : " << sizeof(Base) << "\n"; // OUTPUT => sizeof Base : 16 std::cout << "sizeof Der : " << sizeof(Der) << "\n"; // sizeof Der : 16 Base myBase; std::cout << "sizeof myBase : " << sizeof(myBase) << "\n"; // OUTPUT => sizeof Base : 16 } >>> 'Improper Inheritence' : Taban sınıfın arayüzünde İSTEMEDİĞİMİZ BİR ÖĞE VAR İSE O SINIFTAN KALITIM YOLUYLA YENİ BİR SINIF TÜRETMEMELİYİZ. Türemiş sınıflarda o ARAYÜZÜ DARALTMAMALIYIZ. Aksi halde Mantıkî açıdan sorun teşkil eder. >>> 'virtual constructor idiom' : C++ dilinde kurucu işlevlerin 'virtual' anahtar sözcüğü ile nitelenmesi SENTAKS HATASIDIR. Çalışma zamanında türün belirlenmesinde kullanılır. C++ dilinde bunu sağlayan bir araç normalde YOKTUR. Aşağıdaki örneği inceleyelim; * Örnek 1, // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Car* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Car* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Car* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include "CAR.hpp" void car_game(Car* carPtr) { auto p = carPtr->clone(); p->start(); carPtr->start(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes_S500 case. A brand new Mercedes_S500 has came to life. The Mercedes_S500 just started. The Mercedes_S500 just started. //---------------------------------------------------------- The Audi case. A brand new Audi has came to life. The Audi just started. The Audi just started. //---------------------------------------------------------- The Mercedes case. A brand new Mercedes has came to life. The Mercedes just started. The Mercedes just started. //---------------------------------------------------------- The Tesla case. A brand new Tesla has came to life. The Tesla just started. The Tesla just started. //---------------------------------------------------------- The Audi case. A brand new Audi has came to life. The Audi just started. The Audi just started. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { car_game(create_random_car()); } } >>> 'virtual destructor' : Dinamik ömürlü nesnelerin hayatını sonlandırırken 'delete' operatörü kullanılıyor ve 'operator delete()' fonksiyonuna ilgili adres argüman olarak geçiliyor. Velevki geçilen bu adres bilgisi 'operator new()' ile elde edilen adres değil de başka bir adres ise bu durumda da TANIMSIZ DAVRANIŞ oluşturuyor. C dilindeki karşılığı da 'malloc()'/'free()' fonksiyonlarıdır. Daha detaylar için aşağıdaki örneği inceleyelim; * Örnek 1, #include class Base { public: Base() { std::cout << "Base ctor.\n"; } /*virtual*/ ~Base() { std::cout << "Base dtor.\n"; } }; class Der : public Base { public: Der() { std::cout << "Der ctor.\n"; } ~Der() { std::cout << "Der dtor.\n"; } }; int main() { /* # OUTPUT # Base ctor. Der ctor. Base dtor. */ Base* ptr = new Der; delete ptr; // Görüldüğü üzere 'Der' sınıfı için 'Dtor' çağrılmadı çünkü 'delete' operatörüne geçilen argüman 'Base*' türünden, // bu bilgiye de derleme zamanında ulaşıldı. İsim arama derleme zamanında yapıldığından. // Peki bu durumun yol açtığı sıkıntılar: // i. 'Der' nesnesi hayata gelirken kaynaklar elde edebilir. Dolayısıyla bu kaynaklar geri verilmeyecektir. // Bk. Memory Leak and Resource Leak. // ii. 'delete' operatörüne geçilen argüman 'Base*' türünden bir adres bilgisi. Fakat 'Der' içindeki 'Base' in adresi // ile 'Der' in adresinin aynı olacağı GARANTİ ALTINDA DEĞİL. NOT: C dilindeki yapılar ile yapıların ilk elemanının aynı // adreste olma zorunluluğu var. Bu durumda bu 'delete' işlemi de Tanımsız Davranış olabilir. // İşte bu nedenlerden dolayı 'polymorphic' sınıfların yıkıcı işlevleri de 'virtual' olmalı. } >>>> İş bu sebepten dolayı, 'polymorphic' sınıfların 'Dtor' fonksiyonları; >>>>> Ya 'virtual-public' olmalı. Böylelikle Taban sınıf türünden bir gösterici ile türemiş sınıf türünden nesneleri rahatlıkla kullanabiliriz. * Örnek 1, #include class Base { public: Base() { std::cout << "Base ctor.\n"; } virtual ~Base() { std::cout << "Base dtor.\n"; } }; class Der : public Base { public: Der() { std::cout << "Der ctor.\n"; } ~Der() { std::cout << "Der dtor.\n"; } }; int main() { /* # OUTPUT # Base ctor. Der ctor. Der dtor. Base dtor. */ Base* ptr = new Der; delete ptr; } >>>>> Ya da 'NON-virtual-protected' olmalı, ki bu durumda taban sınıf türünden bir gösteri ile türemiş sınıf kullanmak sentaks hatası olacaktır. * Örnek 1, #include class Base { protected: ~Base() { std::cout << "Base dtor.\n"; } public: Base() { std::cout << "Base ctor.\n"; } }; class Der : public Base { public: Der() { std::cout << "Der ctor.\n"; } ~Der() { std::cout << "Der dtor.\n"; } }; int main() { /* # OUTPUT # //.. */ Base* ptr = new Der; delete ptr; // error: ‘Base::~Base()’ is protected within this context // Artık her sınıfı, sadece kendi sınıf türünden bir gösterici ile çağırabiliriz. // Der* ptr = new Der; // delete ptr; } >>>> Asla ve asla 'NON-virtual-public' OLMAMALI. * Pekiştirici bir örnek: #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Car* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Car* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Car* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- void car_game(Car* carPtr) { auto p = carPtr->clone(); p->start(); carPtr->start(); delete p; std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes case. A brand new Mercedes has came to life. The Mercedes just started. The Mercedes just started. The Mercedes has just died. //---------------------------------------------------------- The Mercedes_S500 case. A brand new Mercedes_S500 has came to life. The Mercedes_S500 just started. The Mercedes_S500 just started. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Tesla case. A brand new Tesla has came to life. The Tesla just started. The Tesla just started. The Tesla has just died. //---------------------------------------------------------- The Mercedes case. A brand new Mercedes has came to life. The Mercedes just started. The Mercedes just started. The Mercedes has just died. //---------------------------------------------------------- The Mercedes_S500 case. A brand new Mercedes_S500 has came to life. The Mercedes_S500 just started. The Mercedes_S500 just started. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); } } >>> Global Fonksiyonların Sanal Olmaları: Normal şartlarda Global Fonksiyonlar 'virtual' olamazlar. Aynı şekilde 'static member function' lar da olamazlar. Fakat bir takım yaklaşımlar ile Global Fonksiyonları 'virtual' hale getirebiliriz. Aşağıdaki örneği inceleyelim: * Örnek 1, // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual std::ostream& print(std::ostream& os) const = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Car* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Tesla.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Car* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Audi.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Car* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes_S500.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" std::ostream& operator<<(std::ostream& os, const Car& myCar) { return myCar.print(os); } int main() { /* # OUTPUT # The Mercedes_S500 case. Hey! I am a Mercedes_S500. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Mercedes case. Hey! I am a Mercedes. The Mercedes has just died. //---------------------------------------------------------- The Mercedes_S500 case. Hey! I am a Mercedes_S500. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { auto p = create_random_car(); std::cout << *p << "\n"; delete p; std::cout << "//----------------------------------------------------------\n"; } } >>> Covariance (Variant Return Type): Taban sınıftaki sanal fonksiyonu öyle bir 'override' EDECEĞİZ ki bu yeni fonksiyon, taban sınıftaki fonksiyonun geri dönüş değerinden farklı bir geri dönüş değerine sahip olacak. Bunu sağlamanın tek yolu da taban sınıftaki iş bu fonksiyonun geri dönüş değerinin türü T* veya T& olacak ve türemiş sınıftaki 'override' edilen bu fonksiyonun geri dönüş değeri de T sınıf türünden türetilmiş bir sınıf göstericisi olacak. Geri dönüş değerinin T sınıf türünden olması bu mekanizmayı tetiklemez. Aşağıdaki örneği inceleyelim: * Örnek 1, #include class A {}; class B : public A {}; class Base{ public: virtual A* func(int); }; class Der : public Base { public: B* func(int) override; // Her 'B' bir 'A' olduğundan sorun yok. }; int main() { //... } * Örnek 2, Yukarıdaki 'CAR.hpp' dosyasındaki sınıfı ele alalım: // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual std::ostream& print(std::ostream& os) const = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Tesla* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Tesla.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Audi* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Audi.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Mercedes* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes_S500.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" void car_game(Car* carPtr) { auto p = carPtr->clone(); p->start(); carPtr->start(); delete p; std::cout << "//----------------------------------------------------------\n"; } std::ostream& operator<<(std::ostream& os, const Car& myCar) { return myCar.print(os); } int main() { /* # OUTPUT # The Audi case. Hey! I am a Audi. The Audi has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Mercedes_S500 case. Hey! I am a Mercedes_S500. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { auto p = create_random_car(); std::cout << *p << "\n"; delete p; std::cout << "//----------------------------------------------------------\n"; } // Ayrıca bu mekanizma şu olanağa da izin vermektedir: // İlgili 'Audi' sınıfının 'clone()' fonksiyonu 'Car' türünden gösterici döndürdüğünde, aşağıdaki gibi bir ilk değer // verme sentaks hatası olacaktır: // Audi* ap = new Audi; // Audi* aptr = ap->clone(); // error: invalid conversion from ‘Car*’ to ‘Audi*’ [-fpermissive] // Hata mesajından da görüleceği üzere 'downcasting' derleyici tarafından OTOMATİK OLARAK YAPILMAMAKTADIR. // 'static_cast' operatörünü kullanmalıydık. } >>> 'final' anahtar sözcüğü: Kalıtım hiyerarşisinde son yaprak olan sınıfı belirlemede kullanılır. Yani bu anahtar sözcük ile nitelenen sınıflardan kalıtım yolu ile başka sınıflar elde edilemezler. * Örnek 1, class Base {}; class Der final : public Base {}; class DerTwo : public Der {}; // SENTAKS HATASI. Artık kalıtım hiyerarşisindeki son dal 'Der' sınıfı. class BaseTwo final {}; // Artık kalıtım hiyerarşisindeki son dal 'BaseTwo' sınıfı. Bu sınıftan başka sınıflar türetilemezler. * Örnek 2, class Base { public: virtual void func(); }; class Der : public Base{ public: void func() override final; // Artık bu 'override' son. Bu sınıftan türetilen başka sınıflar, iş bu fonksiyonu tekrar // 'override' EDEMEZLER. 'override' ve 'final' kelimeleri birbirleri ile yer değiştirebilir. }; class DerTwo : public Der{ public: void func() override; // SENTAKS HATASI. }; >>> Pekiştirici Örnekler, >>>> Taban sınıfın 'private' kısmında bildirilen/tanımlanan sanal fonksiyonların türemiş sınıfta 'override' edilebilirlikleri: * Örnek 1, #include class Base { virtual void foo() { std::cout << "Base::foo()\n"; } }; class Der : public Base { public: void foo() override { std::cout << "Der::foo()\n"; } }; void globalFoo(Base* other) { other->foo(); // Bu çağrı sentaks hatasıdır çünkü isim arama 'statik' türe göre yapılmaktadır. Dolayısıyla ilgili 'foo' ismi // 'Base' sınıfında aranacak, bulunacak, bağlanacak fakat en son 'access control' e takılacaktır. } int main() { /* # OUTPUT # //.. */ Der myDer; globalFoo(&myDer); } >>>> Taban sınıfın 'public' kısmında bildirilen/tanımlanan fonksiyonların, türemiş sınıfın 'private' kısmında 'override' edilmeleri: * Örnek 1, #include class Base { public: virtual void foo() { std::cout << "Base::foo()\n"; } }; class Der : public Base { private: void foo() override { std::cout << "Der::foo()\n"; } }; void globalFoo(Base* other) { other->foo(); } int main() { /* # OUTPUT # Der::foo() */ Der myDer; globalFoo(&myDer); // Burada gerçekleşenler şu şekildedir: // i. İsim arama 'static' türe göre yapıldığından, 'globalFoo' içerisindeki 'foo' fonksiyonunun ismi 'Base' // sınıfında arandı, bulundu ve bağlandı. // ii. Sıra çalışma zamanına geldiğinde ise fonksiyon bağlama işi sonlandığından, 'virtual dispatch' mekanizması // devreye girdi. // iii. Bundan dolayı da türemiş sınıf içerisindeki 'override' edilmiş versiyon çağrıldı. } >>>> Taban sınıfın 'public' kısmında bildirilen ama varsayılan argüman kullanılan fonksiyonların, türemiş sınıfların 'public' kısımlarında varsayılan argüman kullanılarak 'override' edilmeleri: * Örnek 1, #include class Base { public: virtual void foo(int x = 10) { std::cout << "Base x : " << x << "\n"; } }; class Der : public Base { public: void foo(int x = 99) override { std::cout << "Der x : " << x << "\n"; } }; void globalFoo(Base* other) { other->foo(); } int main() { /* # OUTPUT # Der x : 10 Der x : 99 */ Der myDer; globalFoo(&myDer); // OUTPUT => Der x : 10 // Burada gerçekleşenler şu şekildedir: // i. İsim arama 'static' türe göre yapıldığından, 'globalFoo' içerisindeki 'foo' fonksiyonunun ismi 'Base' sınıfında // arandı, bulundu ve bağlandı. // Varsayılan argüman mekanizması da 'static' türe göre yapıldığından, ilgili fonksiyonun varsayılan argüman olarak // '10' aldığı not edildi. // ii. Sıra çalışma zamanına geldiğinde ise, fonksiyon bağlama işi sonlandığından, 'virtual dispatch' mekanizması // devreye girdi. // iii. Bundan dolayı da türemiş sınıf içerisindeki 'override' edilmiş versiyon çağrıldı. Varsayılan argüman olarak // da '10' değeri geçilmiş oldu. myDer.foo(); // OUTPUT => Der x : 99 // Burada gerçekleşenler şu şekildedir: // i. İsim arama 'static' türe göre yapıldığından, 'globalFoo' içerisindeki 'foo' fonksiyonunun ismi 'Der' sınıfında // arandı, bulundu ve bağlandı. // Varsayılan argüman mekanizması da 'static' türe göre yapıldığından, ilgili fonksiyonun varsayılan argüman olarak // '99' aldığı not edildi. // ii. Sıra çalışma zamanına geldiğide ise, tıpkı normal bir 'member function' çağrısında olanlar oldu. } > AŞAĞIDAKİ KONTROLLER DERLEME ZAMANINDA YAPILIR VE 'static' TÜRE İLİŞKİNDİR: >> İsim Arama : '.', '->' ve '::' operatörleri ile yapılan isim aramalar derleme zamanında yapıldığından 'static' türe göre isim aranır. Üye fonksiyonun içinde yapılan aramalarda da 'static' türe bakılır. >> Erişim kontrolü de yine 'static' türe göre yapılır. >> Varsayılan argümanlar da yine 'static' türe göre belirlenir. > Ödev Sorusu: Neco isimli bir sınıfa öyle bir şekilde friend lik verilsin ki ilgili Neco sınıfı Myclass sınıfının sadece 'f2' isimli 'private' fonksiyonuna ve 'my' isimli 'private' veri elemanına erişebilsin. Myclass sınıfındaki diğer öğelere erişim sentaks hatası olsun. * Örnek 1, #include class Neco; class Myclass{ public: friend class Neco; private: class SubMyclass{ void f1(){ std::cout << "mx : " << mx << "\n"; } void f3(){ std::cout << "mz : " << mz << "\n"; } int mx{1}; int mz{3}; }; void f2(){ std::cout << "my : " << my << "\n"; } int my{2}; }; class Neco{ public: void print() { myClass.f2(); } private: Myclass myClass; }; int main() { /* # OUTPUT # my : 2 */ Neco myNec; myNec.print(); } > Sınıflar (devam) : >> Birbiri ile alakasız iki sınıf türünden nesne 'static_cast<>()' ile dönüştürülemez. 'reinterpret_cast<>()' ile bu dönüşüm mümkündür fakat hoş bir şey olmadığından, kaçınmalıyız. Aşağıdaki örneği inceleyelim: * Örnek 1, class Car { //.. Yukarıdaki 'CAR.hpp' sınıfını baz alalım. }; class Audi : public Car { //.. Yukarıdaki 'CAR.hpp' sınıfını baz alalım. }; void car_game(Car* myCar) { Audi* ap = static_cast(myCar); // Bu dönüşüm derleme zamanı açısından problem teşkil etmez. Fakat çalışma zamanında 'myCar' göstericisine başka bir sınıf // türünden nesne gelirse PATLARIZ. Run-time hatası alırız. // Bu yaklaşımı iki sınıfın birbirlerinin taban sınıf olup olmadıklarını sınamak için de kullanabiliriz. Örneğin, // Car* myCar; // Audi* ap = static_cast(myCar); // LEGAL // Car* myCarTwo = static_cast(ap);// LEGAL // Mercedes* myMerso; // Tesla* myTesla = static_cast(myMerso); // error: invalid static_cast from type ‘Mercedes*’ to type ‘Tesla*’ } > Çalışma zamanında fonksiyona gelen Dinamik Türün ne olduğunu anlamak: Her ne kadar aşağıdaki yaklaşım hoş olmasa bile zaman zaman bir zorunluluktan yapılmaktadır. RTTI: Run Time Type Identification * Örnek 1, Meşhur 'CAR.hpp' başlık dosyasındaki sınıfları ele alalım: // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual std::ostream& print(std::ostream& os) const = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Tesla* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Tesla.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Audi* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Audi.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Mercedes* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Mercedes_S500* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes_S500.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp void car_game(Car* carPtr) { carPtr->start(); if(Tesla* tp = dynamic_cast(carPtr)) tp->charge(); if(Mercedes* mp = dynamic_cast(carPtr)) mp->open_sunroof(); if(Mercedes_S500* mps = dynamic_cast(carPtr)) { mps->open_sunroof(); mps->lock_differential(); } carPtr->run(); carPtr->stop(); } int main() { /* # OUTPUT # The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The sunroof of Mercedes just opened. The differential of Mercedes_S500 just locked. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. The Mercedes has just died. //---------------------------------------------------------- The Tesla case. The Tesla just started. The Tesla just started charging. The Tesla just started running. The Tesla just stopped. The Tesla has just died. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { Car* cp = create_random_car(); car_game(cp); delete cp; std::cout << "//----------------------------------------------------------\n"; } } >> Polymorphic List: Hiyerarşideki sınıfların 'gösterici' yolu ile bir kapta saklanma olayıdır. Bunu yapmanın geleneksel yolu ise 'raw-pointer' kullanarak bir kapta saklamaktır. Aşağıdaki örneği inceleyelim: * Örnek 1, Yukarıdaki 'CAR.hpp' sınıfını ele alalım: #include "CAR.hpp" #include int main() { /* # OUTPUT # The Tesla case. The Tesla case. The Audi case. The Audi case. The Mercedes_S500 case. ------------------------------------------ The Tesla just started. The Tesla just started running. The Tesla just stopped. The Tesla has just died. The Tesla just started. The Tesla just started running. The Tesla just stopped. The Tesla has just died. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. The Mercedes_S500 has just died. The Mercedes has just died. */ std::vector myVec; for(size_t i = 0; i < 5; ++i) { myVec.push_back(create_random_car()); } std::cout << "------------------------------------------\n"; for(auto p : myVec) { p->start(); p->run(); p->stop(); delete p; std::cout << "\n"; } } /*============================================================================================================*/ (19_14_11_2020) > Sınıflar (devam) : >> Inherited Ctor. : Aşağıdaki örneği inceleyelim: * Örnek 1, class Base{ public: Base(int); Base(int, int); Base(double); Base(double, double); }; class Der : public Base{ public: Der(int a) : Base{a} {} // I Der(int a, int b) : Base{a, b} {} // II // using Base::Base; // III }; int main() { /* # OUTPUT # */ Der myDer(12); // 'Der' sınıfında 'I' numaralı Ctor olmasaydı eğer Sentaks Hatası alacaktık. Der myDer(1, 4); // 'Der' sınıfında 'II' numaralı Ctor olmasaydı eğer Sentaks Hatası alacaktık. // Gördüğünüz gibi 'Der' sınıfı için ayrı ayrı Ctor yazmak zorundayız. Fakat Modern C++ ile dile 'III' numaralı bildirim // ile bu zorunluluktan kurtulmuş olduk. Artık aşağıdakiler için ayrı ayı Ctor yazmamıza gerek kalmadı: // Der x(13.31); // Der y(13.31, 31.13); // Fakat burada unutulmamalıdır ki taban sınıftaki Ctor fonksiyonlar 'public' kısımda olmalı. Eğer 'protected' kısımda olursa, // yine bizler kullanamayacağız. Sentaks hatası alacağız. Ama 'member function' için böyle bir kural YOK. // Örnek 2'yi inceleyelim. // Son olarak 'III' numaralı bildirim yaparsak ve 'Der' sınıfının da başka üyeleri varsa, onları da bizler 'In Init.' ile hayata // getirmeliyiz. 'III' numaralı bildirim onları etkilemeyecektir. } * Örnek 2, class Base{ protected: Base(int); }; class Der : public Base{ public: using Base::Base; }; int main() { /* # OUTPUT # */ Der myDer(12); // error: ‘Base::Base(int)’ is protected within this context // Görüldüğü üzere taban sınıfın 'protected' kısmındaki Ctor fonksiyonuna erişim sağlayamıyoruz. Fakat üye fonksiyon olsaydı, // erişim sağlayabilecektik. Örnek 3'u inceleyelim. } * Örnek 3, class Base{ protected: void func(int) { } void func(int, double) { } }; class Der : public Base{ public: using Base::func; }; int main() { Der myDer; myDer.func(31); // OK myDer.func(31, 31.13); // OK } >> NVI (Non-Virtual Interface) : Taban sınıfın sanal fonksiyonlarını 'private' yapıp, sonrasında onları sanal olmayan ama 'public' olan fonksiyonlar ile kaplayarak o fonksiyonlar üzerinde daha fazla 'pre-condition' ve 'post-condition' konularında kontrolümüzün olmasını sağlamak. Bir nevi 'customization-point' oluşturuyoruz. Eğer türemiş sınıflarımızda 'override' edilen fonksiyonlar, taban sınıftaki halini çağıracak ise bu durumda taban sınıfımızdaki iş bu sanal fonksiyonumuzu 'protected' alana koymalıyız. Böylelikle dışarıdan çağrım yaparken sanal fonksiyonumuz direkt çağrılamayacak fakat türemiş sınıflardan direkt olarak çağrılabilecek. * Örnek 1, class Base{ protected: virtual void func(int); public: void foo(int a) // NVI uygulandığı nokta. { //.. Check for 'pre-condition' func(a); //.. Check for 'post-condition' } }; class Der : public Base{ public: void func(int x)override { Base::func(x); // Taban sınıftaki versiyonu çağırma şeklimiz. //.. } }; >> 'private' Inheritence : Şu unutulmamalıdır ki kalıtım yoluyla A sınıfından türetilen B sınıfı içerisinde, bir A sınıfı mutlaka vardır. Burada kalıtımın 'public', 'protected' ve 'private' olarak yapılmasının bir önemi yoktur. Yine türemiş sınıftan bir nesne hayata gelirken içerideki taban sınıfın kurucu işlevinin çağrılması, ilgili nesnenin hayatı biterken de içerideki taban sınıfın yıkıcı işlevinin çağrılması gibi fonksiyonlar 'private' kalıtım mekanizmasında da geçerlidir. Bu kalıtım yaklaşımının 'public' kalıtım yaklaşımından iki farkı vardır. >>> 'private' kalıtım ile 'public' kalıtım arasındaki Farklılıklar, >>>> 'Access Control' tabanlı farklılıklar: 'private' kalıtımda taban sınıfın 'public interface' türemiş sınıfın 'private' bölümüne eklenmektedir. * Örnek 1, class Base{ public: void f1(); }; class Der : private Base{ public: void func(); }; int main() { Der myDer; myDer.f1(); // error: ‘void Base::f1()’ is inaccessible within this context // Çünkü ilgili 'f1()' fonksiyonu türemiş sınıfın 'private' alanına eklendi. } * Örnek 2, #include class Base{ public: void f1() { std::cout << "f1()\n"; } protected: void f2() { std::cout << "f2()\n"; } private: void f3() { std::cout << "f3()\n"; } }; class Der : public Base{ public: void func() { f1(); // OK f2(); // OK f3(); // error: ‘void Base::f3()’ is private within this context } }; int main() { Der myDer; // -- private inheritence -- || public inheritence myDer.f1(); // error: ‘void Base::f1()’ is inaccessible within this context || OK myDer.f2(); // error: ‘void Base::f2()’ is protected within this context || Same Error myDer.f3(); // error: ‘void Base::f3()’ is private within this context || Same Error myDer.func(); // SONUÇ: // 'public' inheritence yaptığımız zaman taban sınıfın 'public-interface' de bulunan öğeler türemiş sınıfın 'public' // kısmına eklenmekteler. // Buradaki 'public-interface' ile kastedilen kısım 'global namespace functions' ve taban sınıfın 'public' bölümündeki // öğeler. // 'private' kalıtım yaptığımız zaman ise yukarıdaki 'public-interface' de bulunan öğeler türemiş sınıfın 'private' // bölümüne eklenmekteler. // Diğer yandan taban sınıfın 'protected' kısmı da dışarı kapalı ama türemiş sınıflara ve sıfının kendisine açık // olduğundan, o bölgelere de kısıtlı bir erişim söz konusudur. } >>>> Artık 'private' kalıtım uygulandığında 'Is-a relationship' OLMAMASI. Fakat bu kural 'client' olan müşteriler için geçerlidir. Bundan dolayıdır ki artık müşteriler tarafından yapılan 'upcasting' şeklindeki dönüşümler SENTAKS HATASIDIR. Türemiş sınıfın kendisinin için ve türemiş sınıfın 'friend' leri için hala 'Is-a relationship' ilişkisi SAĞLANMAKTADIR. * Örnek 1, #include class Base{ public: void f1() { std::cout << "f1()\n"; } protected: void f2() { std::cout << "f2()\n"; } private: void f3() { std::cout << "f3()\n"; } }; class Der : private Base{ public: void foo() { // Fakat türemiş sınıf nesnesinin kendisi ele alındığında 'Is-a relationship' korunmaktadır. Dolayısıyla // aşağıdakiler geçerlidir. Der myDer; Base* myBasePtr = &myDer; } friend void myFunc(); }; void myFunc() { // Ayrıca türemiş sınıfın 'friend' leri için de bu ilişki korunmaktadır. Dolayısıyla aşağıdakiler geçerlidir. Der myDer; Base* myBasePtr = &myDer; } int main() { Der myDer; Base* myBasePtr = &myDer; // error: ‘Base’ is an inaccessible base of ‘Der’ // 'client' kodlar açısından; // 'public' kalıtımda her bir türemiş sınıf nesnesi aynı zamanda taban sınıf olarak kabul edildiğinden, 'upcasting' // işlemi 'implicit-conversion' şeklinde yapılmaktayken; // 'private' kalıtımda böyle bir 'Is-a' ilişkisi olmadığından, 'upcasting' işlemi 'implicit-conversion' şeklinde // YAPILMAMAKTADIR. // Fakat türemiş sınıf nesnesinin kendisi ele alındığında 'Is-a relationship' korunmaktadır. Ayrıca türemiş sınıfın // 'friend' leri için de bu ilişki korunmaktadır. } >>> 'private' kalıtım ile 'public' kalıtım arasındaki benzerlikler: >>>> Taban sınıfın sanal fonksiyonlarını 'override' edebiliyoruz. Çünkü bunu yapabilmenin kalıtımın ne şekilde yapıldığı ile bir alakası yoktur. >>> 'private' kalıtım bir iki istisna dışında 'composition' a alternatif olarak kullanılmaktadır. Unutulmamalıdır ki gerek kalıtım gerek 'composition' ile kurulan ilişkilerde türemiş sınıf içerisinde bir taban sınıf vardır. Bu noktada ikisi arasında bir fark yoktur. Peki biz neden 'composition' yerine kalıtım mekanizmasını kullanmalıyız? El-cevap: aşağıdaki örneği inceleyelim: * Örnek 1, #include class A{ public: void fa1() { std::cout << "A::fa1()\n"; } void fa2() { std::cout << "A::fa2()\n"; } protected: void prot() { std::cout << "A::prot()\n"; } }; class B{ // composition: Her 'B' bir 'A' DEĞİLDİR. public: // Normal şartlarda 'A' nın 'interface' 'B' sınıfına otomatik olarak eklenmiyordu fakat bu fonksiyon ile kısmi olarak // eklemiş olduk. void fa1() { ax.fa1(); } // Bir cambazlık yapmaz ise 'A' sınıfının sanal fonksiyonlarını 'B' sınıfında 'override' EDEMEM. // 'A' sınıfının 'protected' kısmı dışarı kapalı olduğundan, artık o kısma ERİŞEMEM. private: A ax; }; class C : private A{ // 'private-inheritence' : İstisna dışında her 'B' bir 'A' değildir. public: using A::fa1; // Normal şartlarda 'A' nın 'interface' 'C' sınıfına otomatik olarak eklenmiyordu fakat bu bildirim ile kısmi olarak // eklemiş olduk. // 'A' sınıfının sanal fonksiyonlarını 'C' sınıfında 'override' EDEBİLİRİM. // 'A' sınıfının 'protected' kısmı türemiş sınıflara açık olduğundan, oradaki fonksiyonlara ERİŞEBİLİRİM. }; int main() { // El-cevap: Taban sınıfın sanal fonksiyonlarını 'override' etme ihtiyacımız var ise veya Taban sınıfın 'protected' // bölümündeki fonksiyonlarına erişme ihtiyacımız var ise veya türemiş sınıf içerisinde 'Is-a relationship' mekanizmasını // kurmak istiyorsak 'private-inheritence' yaklaşımını kullanmalıyız. Fakat bu özelliklere ihtiyacımız yok ise 'composition' // KULLANMALIYIZ. Çünkü kalıtım maliyetli bir işlemdir. } >> Empty Base Optimization: Soru: ne öğesi ne de bir interface ye sahip bir sınıf ne işe yarar? El-cevap: ARAŞTIR. Konuya dönecek olursak; Normal şartlar altında herhangi bir öğesi ve interfacesi olmayan sınıflar bir byte yer kaplarlar. * Örnek 1, #include class Neco{}; class INeco{ void f1(); void f2(); void f3(); }; class Myclass{ INeco mx; }; int main() { /* # OUTPUT # sizeof Neco : 1 sizeof INeco : 1 sizeof Myclass : 1 */ std::cout << "sizeof Neco : " << sizeof(Neco) << "\n"; std::cout << "sizeof INeco : " << sizeof(INeco) << "\n"; std::cout << "sizeof Myclass : " << sizeof(Myclass) << "\n"; } * Örnek 2, velevki yukarıdaki 'Myclass' sınıfı ekstra bir 'int' öğeye sahip olsaydı ne olurdu? #include class Neco{}; class INeco{ void f1(); void f2(); void f3(); }; class Myclass{ int ix; INeco mx; }; int main() { /* # OUTPUT # sizeof Neco : 1 sizeof INeco : 1 sizeof Myclass : 8 */ std::cout << "sizeof Neco : " << sizeof(Neco) << "\n"; std::cout << "sizeof INeco : " << sizeof(INeco) << "\n"; std::cout << "sizeof Myclass : " << sizeof(Myclass) << "\n"; // Görüldüğü üzere ilgili sınıfımızın büyüklüğü ne kadar da arttı. } * Örnek 3, peki bu ekstra büyümeyi nasıl minimize edebiliriz? El-cevap: Tabii ki ilgili 'Myclass' sınıfı 'private' kalıtım yolu ile türeterek. #include class Neco{}; class INeco{ void f1(); void f2(); void f3(); }; class Myclass : private INeco{ int ix; }; int main() { /* # OUTPUT # sizeof Neco : 1 sizeof INeco : 1 sizeof Myclass : 4 */ std::cout << "sizeof Neco : " << sizeof(Neco) << "\n"; std::cout << "sizeof INeco : " << sizeof(INeco) << "\n"; std::cout << "sizeof Myclass : " << sizeof(Myclass) << "\n"; } >> Kontrollü Polymorphism / 'Restricted Polymorphism' : Sınıfımızdaki bazı fonksiyonlar Run Polymorphism mekanizmasından yararlanmasıdır. * Örnek 1, #include class Base{ public: virtual void vFunc(); }; class Der: private Base{ public: void vFunc() override; friend void fooOne(); }; // Polymorphic yapı da burası. Bu fonksiyona gönderilen argümanlara göre 'virtual-dispatch' mekanizması uygulanabilir. Fakat bizler // 'private' kalıtım yaptığımız için 'client' kodlar için her 'Der' bir 'Base' değil. void gFunc(Base& other) { other.vFunc(); } // İşte bu fonksiyona 'friend' özelliği verirsek, bu fonksiyonun gövdesinde her 'Der' bir 'Base' olacağından, kısmi olarak // 'virtual-dispatch' mekanizmasını işletebiliriz. void fooOne() { Der myDer; gFunc(der); } // Burası artık 'client' kod işlevi göreceğinden, her 'Der' bir 'Base' değildir bu fonksiyonun gövdesinde. void fooTwo() { Der myDer; gFunc(der); } int main() { //.. } >> 'protected' inheritence : Neredeyse her şey 'private' kalıtım ile aynı. Tek farkı taban sınıfın ilgili kısımları türemiş sınıfın 'protected' bölümüne eklenmektedir. >> 'multiple inheritence' : C++ dilinin yoğun kullanılan ve biraz da zor bir aracıdır. Mülakatlarda mutlaka bir soru gelmektedir. C# ve Java gibi dillerde doğrudan olmayan bir araçtır. >>> Bir sınıfın birden fazla taban sınıf kullanarak türetilmesi olayıdır. Bu durumda kurulan bu hiyerarşi 'public' kalıtım yapılarak kurulmuş ise her 'Der' bir 'BaseX' ve bir 'BaseY' şeklinde iki taraflı 'Is-a relationship' ilişkisi kurulur. * Örnek 1, #include class A{ public: void fA(); }; class B{ public: void fB(); }; // 'AB' sınıfı, 'A' sınıfından 'public' kalıtım ve 'B' sınıfından 'public' kalıtım yapılarak elde edilmiştir. class AB : public A, public B{ }; int main() { } * Örnek 2, kalıtım nasıl yapılacağını belirtmediğimiz durumlarda varsayılan kalıtım biçimi uygulanır. #include class A{ public: void fA(); }; class B{ public: void fB(); }; // 'AB' sınıfı, 'A' sınıfından 'public' kalıtım ve 'B' sınıfından 'private' kalıtım yapılarak elde edilmiştir. class AB : public A, B{ }; int main() { } * Örnek 3, Gerçek hayattan örnek vermek gerekirse de 'Singing Waiter'. Hem şarkı söylüyor hem de servis yapıyor. * Örnek 4, Türemiş sınıf her iki taban sınıfında 'public-inheritence' özelliklerini bünyesinde barındırır. #include class A{ public: void fA(); }; class B{ public: void fB(); }; class AB : public A, public B{ public: void fAB(); }; int main() { AB myAB; myAB.fA(); // OK myAB.fB(); // OK myAB.fAB(); // OK } * Örnek 5, Türemiş sınıf her iki taban sınıfın 'virtual' fonksiyonlarını 'override' edebilir. #include class A{ public: void fA(); virtual void vfA(); }; class B{ public: void fB(); virtual void vfB(); }; class AB : public A, public B{ public: void fAB(); void vfA() override; void vfB() override; }; int main() { //.. } >>> İsim arama: Aşağıdaki örneği inceleyelim. * Örnek 1, #include class A{ public: void func(int); }; class B{ public: void func(double); }; class AB : public A, public B{ public: }; int main() { AB myAB; myAB.func(1.9); // Sentaks hatası. myAB.func(87); // Sentaks hatası. // Yukarıdaki durumda bir 'Function Overloading' SÖZ KONUSU DEĞİLDİR. Çünkü 'A' ve 'B' sınıflarının 'scope' ları farklı. // Aynı zamanda 'ambiguity' tip sentaks hatası alırız. Çünkü Çoklu Kalıtım hiyerarşisinde isim arama şöyle yapılmaktadır: // i. İsim arama derleme aşamasında 'statik' koda bakarak yapılmaktadır. Dolayısıyla önce 'AB' sınıfı içerisinde aranır. // ii. Bulunamaması durumunda taban sınıflarda aranır fakat kalıtım yapılırkenki sıra gözetilmez. Yani yukarıdaki örneği // baz alırsak; önce 'A' sınıfında sonra 'B' sınıfında ARAMA YAPILMAZ. // Bir nevi bu iki sınıfta aynı anda arama yapar. İki farklı isim geleceği için hangisinin seçileceği karar verilemez, // 'ambiguity' tip sentaks hatası alırız. // 'Function Overloading' olması için İSİM ARAMANIN DA TAMAMLANMIŞ OLMASI GEREKİYOR. Fakat daha isim arama // tamamlanmadığından, yani ilgili ismin bir fonksiyon ismi olduğu anlaşılamadığından, böyle bir mekanizma da henüz // işletilmez. Eğer ilgili fonksiyon ismini niteleyerek çağırırsak, bu sentaks hatasından da kurtulmuş oluruz. myAB.A::func(12); // OK myAB.B::func(1.2); // OK } * Örnek 2, isim arama için güzel bir örnek daha. #include class A{ public: int x{}; }; class B{ public: void x(); }; class AB : public A, public B{ public: }; int main() { AB myAB; myAB.x(); // 'ambiguity' tip sentaks hatası. Çünkü derleyici daha 'x' in neyin ismi olduğuna karar veremedi. } * Örnek 3, isim arama için güzel bir örnek daha. #include class A{ public: void func(int); }; class B{ public: void func(double); }; class AB : public A, public B{ public: void foo() { func(12); // Sentaks hatası. func(1.2); // Sentaks hatası. // Yine 'ambiguity' tip sentaks hatasıdır. Çünkü, // i. 'func' ismini blok içerisinde aradı fakat bulamadı. Eğer bulsaydı, bu çağrı ile o ismi eşleyecekti. // İkinci aşama olarak da 'Context Control' yapılacaktı. // ii. Sonra 'AB' sınıfı içerisinde her yere baktı fakat yine bulamadı. // iii. Sonrasında 'A' ve 'B' sınıfları içerisine aynı anda baktı ve iki adet isim olduğunu gördü. // Bu problemi de aşmak için; B::func(12); // OK A::func(1.2); // OK } }; int main() { //.. } >>> Çoklu kalıtım hiyerarşisi kurarken taban sınıfların isimlerinin yazılma sırası, 'upcasting' konusunda bir önem teşkil etmez. * Örnek 1, #include class A{ public: void func(int); }; class B{ public: void func(double); }; class AB : public A, public B{ public: }; void f(A*); void f(B*); int main() { AB myAB; f(&myAB); // Sentaks hatası oluşacaktır. Burada 'Function Overloading' SÖZ KONUSUDUR. Fakat 'upcasting' konusunda bir öncelik // olmadığından, 'ambiguity' tip sentaks hatası alacağız. Bu hatayı aşmak için de 'static_cast<>()' ile 'A*' veya // 'B*' türlerine dönüşüm yapabiliriz. f(static_cast(&myAB)); // Referansa dönüşüm => f(static_cast(&myAB)); f(static_cast(&myAB)); } >>> Çoklu kalıtım hiyerarşisi kurulduğunda, türemiş sınıf içerisinde taban sınıfların her ikiside mevcut. Dolayısıyla türemiş sınıfın 'sizeof' değeri, taban sınıfların 'sizeof' değerlerinin toplamına eşit veya daha fazla olacaktır. Bundan dolayıdır ki tekli kalıtım hiyerarşisindeki özel üye fonksiyonların durumları ne ise çoklu kalıtım hiyerarşisinde de aynı durumlar OLACAKTIR. Sadece derleyicinin türemiş sınıf için yazmış olduğu özel üye fonksiyonlar, taban sınıfların özel üye fonksiyonlarını çağırırken, çoklu kalıtım hiyerarşisi kurulurken taban sınıfların sırasına göre çağıracaklar. Aşağıdaki örneği inceleyelim. * Örnek 1, #include class A{ public: A() { std::cout << "A::A()\n"; } }; class B{ public: B() { std::cout << "B::B()\n"; } }; class AB : public A, public B{ public: // Bu sınıfın özel üye fonksiyonlarını derleyici yazdı. Eğer biz yazsaydık ve 'Ctor Init. List' kullansaydık, // yine de kalıtım mekanizması kurulurkenki sıra dikkate alınacaktı. Yani önce 'A', sonra 'B'. Bu durum 'data member' // ların hayata gelmesinde de geçerli. Sınıf içerisinde bildirilen sıraya göre hayata geliyorlar onlar. }; int main() { AB myAB; // Çoklu kalıtım hiyerarşisi kurulurken önce 'A' sınıfı yazıldığından derleyici önce 'A' sınıfının 'Default Ctor' // fonksiyonunu, sonra 'B' sınıfınınkini çağıracaktır. } >>> Çoklu kalıtımdaki 'diamond formation' : En yukarıda bir adet taban sınıf olduğunu düşünelim. Sonrasında bunun altında, bu taban sınıfından ayrı ayrı tekli kalıtım yolu ile türetilen iki adet türemiş sınıf olduğunu düşünelim. Sonrasında da bu iki adet türetilmiş sınıftan bir adet çoklu kalıtım ile türetilen bir başka sınıf daha düşünelim. İşte bu formasyon biçimine denmektedir. Tabiri caiz ise; - A Base Class => Base_Class + A Derived Class from Base_Class => BC_One + Another Derived Class from Base_Class => BC_Two - A Base Class => Device + A Derived Class from Device => D_Fax + Another Derived Class from Device => D_Modem + A Multiple Inherited Class from BC_One & BC_Two => BC_One_Two + A Multiple Inherited Class from D_Fax & D_Modem => D_FaxModem şeklinde temsil edilebilir. Şekilden de anlaşılacağı üzere 'MultiDerClass' sınıfı içerisinde hem 'DerOne' üzerinden gelen hem de 'DerTwo' üzerinden gelen iki adet 'Base_Class' sınıfı vardır. İşte bu bize iki farklı probleme neden olabilir, eğer önlem almaz isek: >>>> Bunlardan bir tanesi derleme zamanına ilişkin problem, ki aslında iki adet 'Base_Class' olduğundan, 'ambiguity' tip sentaks hatasıdır. >>>> Bir diğer problem ise bu içerideki 'Base_Class' sınıflarının bir tanesinde değişiklik yaptığımızda, bunun diğerine yansımamasıdır. Bir takım araçlar ile bu birden fazla olan 'Base_Class' sınıfını teke düşürüyoruz. * Örnek 1, #include class Base{ public: int x{}; }; class DerOne : public Base{ public: int y{}; }; class DerTwo : public Base{ public: int z{}; }; class MultiDer : public DerOne, public DerTwo{ }; int main() { /* # OUTPUT # sizeof Base : 4 sizeof DerOne : 8 sizeof DerTwo : 8 sizeof MultiDer : 16 */ std::cout << "sizeof Base : " << sizeof(Base) << "\n"; std::cout << "sizeof DerOne : " << sizeof(DerOne) << "\n"; std::cout << "sizeof DerTwo : " << sizeof(DerTwo) << "\n"; std::cout << "sizeof MultiDer : " << sizeof(MultiDer) << "\n"; } * Örnek 2, 'ambiguity' tip hataları gidermek için ilgili 'Base_Class' sınıfını niteleyerek çağırırmamız gerekmektedir. #include class Base{ public: void foo(); }; class DerOne : public Base{ public: }; class DerTwo : public Base{ public: }; class MultiDer : public DerOne, public DerTwo{ public: void func() { foo(); // Aşağıda açıklanan nedenden ötürü 'ambiguity' tip sentaks hatası alıyoruz. DerOne::foo(); // OK DerTwo::foo(); // OK } }; int main() { MultiDer myMultiDer; myMultiDer.foo(); // error: request for member ‘foo’ is ambiguous // Çünkü 'MultiDer' sınıfı içerisinde hem 'DerOne' tarafından gelen hem de 'DerTwo' tarafından gelen 'Base' sınıf // olduğundan, derleyici iki isim ile karşılaşmaktadır. myMultiDer.DerOne::foo(); // OK myMultiDer.DerTwo::foo(); // OK // Her 'MultiDer' bir 'Base' olduğundan, çünkü 'public-inheritence' kullanıldı, 'virtual-dispatch' mekanizması da // kullanabiliriz. Fakat iki adet 'Base' olduğundan, yine 'ambiguity' tip SENTAKS HATASI alacağız. Gösterici yerine // referans kullansaydık da sonuç aynı kalacaktı. Base* p = &myMultiDer; // Sentaks hatası. Base* pOne = static_cast(&myMultiDer); Base* pTwo = static_cast(&myMultiDer); } >>> İki adet 'Base_Class' durumunu teke düşürmek için de 'virtual inheritence' mekanizmasına başvurmamız gerekmektedir. * Örnek 1, #include class Base{ public: void foo(); int a{}; }; class DerOne : virtual public Base{ // Her iki kalıtıma da yazıldığından emin olmalıyız. public: int b{}; }; class DerTwo : virtual public Base{ // Her iki kalıtıma da yazıldığından emin olmalıyız. public: int c{}; }; class MultiDer : public DerOne, public DerTwo{ public: }; int main() { /* # OUTPUT # sizeof Base : 4 => 'int (4-byte)' sizeof DerOne : 16 => 'Base (4-byte)' + 'int (4-byte)' + 'theVirtualPointer (8-byte)' sizeof DerTwo : 16 => 'Base (4-byte)' + 'int (4-byte)' + 'theVirtualPointer (8-byte)' sizeof MultiDer : 32 => 'DerOne (16-byte)' + 'DerTwo (16-byte)' */ std::cout << "sizeof Base : " << sizeof(Base) << "\n"; std::cout << "sizeof DerOne : " << sizeof(DerOne) << "\n"; std::cout << "sizeof DerTwo : " << sizeof(DerTwo) << "\n"; std::cout << "sizeof MultiDer : " << sizeof(MultiDer) << "\n"; MultiDer myMultiDer; myMultiDer.foo(); // ARTIK LEGAL. Çünkü bir adet 'Base' sınıfı var içeride. Base* p = &myMultiDer; // ARTIK LEGAL. Çünkü bir adet 'Base' sınıfı var içeride. } * Örnek 2, #include class Device{ public: void turnOn() { flag = true; } void turnOff() { flag = false; } bool isOn()const { return flag; } private: bool flag{}; }; class Fax : /* virtual */ public Device{ // I public: void sendFax() { if(isOn()) std::cout << "Fax gonderildi.\n"; else std::cout << "Cihaz kapali oldugundan, fax gonderilemedi.\n"; } void receiveFax() { if(isOn()) std::cout << "Fax alindi.\n"; else std::cout << "Cihaz kapali oldugundan, fax alinamadi.\n"; } }; class Modem : /* virtual */ public Device{ // II public: void sendData() { if(isOn()) std::cout << "Data gonderildi.\n"; else std::cout << "Cihaz kapali oldugundan, data gonderilemedi.\n"; } void receiveData() { if(isOn()) std::cout << "Data alindi.\n"; else std::cout << "Cihaz kapali oldugundan, data alinamadi.\n"; } }; class FaxModem : public Fax, public Modem{ }; int main() { /* # OUTPUT # //.. */ FaxModem myFaxModem; // myFaxModem.turnOn(); // error: request for member ‘turnOn’ is ambiguous myFaxModem.Fax::turnOn(); // OK myFaxModem.sendFax(); // OUTPUT => Fax gonderildi. myFaxModem.sendData(); // OUTPUT => Cihaz kapali oldugundan, data gonderilemedi. // Yukarıdaki senaryoda da görüldüğü üzere bizler cihazı açmamıza rağmen 'data' gönderilemedi. Çünkü içerideki // iki 'Device' sınıfından birinde yaptığımız değişikli diğerini etkilemedi. Her ne kadar bizler 'ambiguous' problemini // ismi niteleyerek aşsak da çalışma zamanındaki bu problemi aşamamış olduk. myFaxModem.Modem::turnOff(); // OK myFaxModem.receiveFax(); // OUTPUT => Fax alindi. // Bu senaryoda da görüldüğü gibi cihazı kapatmamıza rağmen 'fax' almaya devam ediyoruz. Evet bizler 'ambiguous' problemini // aştık fakat çalışma zamanındaki bu problemi aşamadık. // İŞTE BU ÇALIŞMA ZAMANINDAKİ PROBLEMLERİ AŞABİLMEK İÇİN 'I' ve 'II' numaralı yerlerde 'virtual-inheritence' // UYGULAMAMIZ GEREKMEKTEDİR. // Yeni sonuç: /* Fax gonderildi. Data gonderildi. Cihaz kapali oldugundan, fax alinamadi. */ } >>> Bu iki problem haricinde bir üçüncü problem daha vardır çoklu kalıtım mekanizmasında: * Örnek 1, #include class A{ public: A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : /* virtual */ public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : /* virtual */ public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: // M() /* : A(2)*/ {} }; int main() { /* # OUTPUT # A::A(int x) x : 0 X::X() A::A(int x) x : 1 Y::Y() */ M mx; // Çıktıdan da görüldüğü üzere derleyicinin 'M' sınıfı için yazdığı 'Default Ctor' fonksiyonu, her iki taban sınıf olan // 'X' ve 'Y' sınıflarının 'Default Ctor' larına çağrı yapmıştır. Bu iki sınıfın 'Default Ctor' fonksiyonları da // kendilerinin taban sınıfı olan 'A' sınıfının 'Ctor' fonksiyonlarına çağrı yapmıştır. // Peki bizler 'virtual-inheritence' uygulasaydık, sonuç ne olurdu? // " error: use of deleted function ‘M::M()’ " şeklinde bir SENTAKS HATASI alırdık. Derleyici 'M' sınıfına yazmış // olduğu 'Default Ctor' fonksiyonunu herhangi bir nedenden dolayı 'delete' ETMİŞ. // Peki bizler kendimiz 'M' sınıfı için 'Default Ctor' yazsaydık ne olurdu? // "error: no matching function for call to ‘A::A()’" şeklinde bir SENTAKS HATASI alırdık. // Eee sorun nerede öyleyse? // Yukarıdaki hata mesajında da görüldüğü üzere 'A' nesnesinin 'Default Ctor' fonksiyonuna çağrı yapılmış. Fakat öyle // bir fonksyion OLMADIĞINDAN, SENTAKS HATASI ALIYORUZ. İşte bu sebepten ötürü de derleyici kendisinin yazmış olduğu // fonksiyonu 'delete' etmiştir. Ayrıca iki defa 'A' nesnesini HAYATA GETİREMEYİZ. // Peki bunun çözümü nedir? // Kendi yazmış olduğumuz 'M' sınıfına ait 'Ctor' fonksiyonunda 'A' sınıfının 'Ctor' fonksiyonuna manuel çağrı yapmak // yada 'A' sınıfı için 'Default Ctor' yazmak. // Çözümler için sırası ile Örnek 2 ve Örnek 3'ü inceleyelim. } * Örnek 2, ilgili sınıfın 'Ctor' fonksiyonuna çağrı yaparak: #include class A{ public: A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : virtual public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : virtual public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: M() : A(2) {} }; int main() { /* # OUTPUT # A::A(int x) x : 2 X::X() Y::Y() */ M mx; // Gördüğünüz gibi 'A' nesnesi bir tane olduğundan bir defa ona ait 'Ctor' fonksiyonu çağrıldı. // FAKAT ŞUNU UNUTMAMALIYIZ Kİ 'M' SINIFINDAN TÜRETİLEN BÜTÜN SINIFLAR, KENDİ 'Ctor' FONKSİYONLARI GÖVDELERİNDE, // 'A' SINIFI İÇİN YUKARIDAKİ GİBİ BİR 'CTOR' ÇAĞRISI YAPMAK ZORUNDADIR. } * Örnek 2.1, #include class A{ public: A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : virtual public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : virtual public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: M() : A(2) {} }; class N : public M{ }; int main() { /* # OUTPUT # A::A(int x) x : 2 X::X() Y::Y() */ N nx; // error: use of deleted function ‘N::N()’ // Gördüğünüz gibi 'N' sınıfı için yazılan 'Default Ctor.' 'delete' edilmiş. Çünkü 'A' sınıfının 'Default Ctor.' YOK. // Ya biz 'N' sınıfının 'Ctor' fonksiyonunu yazacağız ve gövdesinde 'A' nın 'Ctor' fonksiyonu için çağrıda bulunacağız // ya da 'A' için yeni bir 'Default Ctor' yazacağız. } * Örnek 3, İlgili taban sınıf için 'Default Ctor' yazmak: #include class A{ public: A() { std::cout << "A::A()\n"; } A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : virtual public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : virtual public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: }; int main() { /* # OUTPUT # A::A() X::X() Y::Y() */ M mx; // Gördüğünüz gibi 'A' nesnesi bir tane olduğundan bir defa ona ait 'Ctor' fonksiyonu çağrıldı. } >> Giriş-Çıkış akımlarının hiyerarşisi: (T : Template), (virtual: virtual-inheritence) ios_base | basic_ios(T) / \ basic_istream(T) basic_ostream(T) (virtual) (virtual) / \ / \ / basic_iostream(T) \ / | \ basic_ifstream(T) basic_fstream(T) basic_ofstream(T) >> Multi-level inheritence mekanizmasında dolaylı taban sınıfımızın 'Ctor' fonksiyonuna, türemiş sınıf tarafından çağrı YAPAMAYIZ. * Örnek 1, #include class A{ public: A(int){} }; class B : public A{ public: B() : A(12) {} // 'A' sınıfında 'Default Ctor' OLMADIĞINDAN, bu şekilde çağrı yapmak ZORUNDAYIZ. }; class C : public B{ public: C() : A(31) {} // Bu şekilde dolaylı taban sınıfımızın 'Ctor' fonksiyonuna ÇAĞRI YAPAMAYIZ. }; >> CRTP & 'std::variant' : Run-time Polymorphism in maliyeti yerine alternatif kullanımlar. > İskambil kartlarının İngilizceleri: Club(Sinek), Diamond(Karo), Heart(Kupa), Spade(Maça). Bunların her birine de 'suit' denmektedir. Her bir 'suit' ise 13 adet 'rank' ten oluşmaktadır. /*============================================================================================================*/ (20_15_11_2020) > 'Exception Handling' : Programlama dünyasında programcılar kod yazarlarken iki farklı mentalitede kod yazarlardı. Bir taraftan işi yapacak kodları yazarlarken, diğer taraftan da programlama hatalarına veya programın çalışma zamanında karşılaşma ihtimali olduğu problemler için kodlar yazarlardı. İşte bu tip ikinci grup hatalara karşı yazılan kodlar ise iki grupta ele alınabilir. Bunlar 'static assertion' ve 'dynamic assertion'. Yani sırası ile derleme ve çalışma zamanına yönelik hataların bulunması, ele alınmasına yönelik çalışmalar. >> 'dynamic assertion' : C dilindeki 'assert()' makrosu bu gruptadır. >> 'static assertion' : Aşağıdaki örneği inceleyelim; * Örnek 1, //.. this is a C code. int myArray[0]; // Bu bir sentaks hatasıdır çünkü büyüklüğü sıfır olan bir dizi olamaz. typedef int ar[0]; // Bu da bir sentaks hatasıdır çünkü büyüklüğü sıfır olan bir diziye eş isim verilemez. typedef int ar[sizeof(int) == 4]; // Eğer bizim sistemimizde 'int' veri büyüklüğü 4-byte ise bu ifade bu hale gelecektir => "typedef int ar[1];" // Bu durumda herhangi bir sorun yoktur. Fakat eğer veri büyüklüğü 2-byte ise ilgili ifade şu hale gelecektir; // "typedef int ar[0];". İşte bu durumda bizler sentaks hatası almış olacağız. // Fakat C11 ile dile yeni bir anahtar sözcük eklendir => _Static_assert(); _Static_assert(sizeof(int) == 4, "Yetersiz int veri büyüklüğü"); >> İşte 'Exception Handling' mekanizmasının esas amacı programın çalışma zamanında beklenmeyen durumlar ile karşılaşılması durumunda nasıl davranacağını belirlemektir. Örneğin bir dosyaya yazma işlemi yapacağız fakat dosya üçüncü kişiler tarafından silinmiş. İşte böyle bir senaryoda kendi kodumuzu değiştirmek problemi ÇÖZMEYECEKTİR. Burada devreye bahsedilen bu mekanizma girmektedir eğer oraya yönelik kod yazarsak. >> Bir fonksiyonumuz işini yapamadığında, geri bildirim olarak "Ben işimi yapamadım." manasına gelen veri döndürüyorsa bu tip durumlara genel olarak Geleneksel Hata İşleme yöntemleri denmektedir. Örneğin, fonksiyonun dökümantasyonunda "sıfır" değerinin döndürülmesi "Başarı", "non-zero" değerin döndürülmesi "Başarısızlık" anlamının taşıdığı yazması. C dilindeki standart fonksiyonlarda genel olarak bu konvensiyon izlenir. Bir diğer örnek olarak C dilindeki 'fopen()', 'malloc()', 'setlocale()' fonksiyonları geri dönüş değerini kullanarak hata bilgisini döndürmektedir. Bu yaklaşıma alternatif bir diğer yol ise 'errno/errno.h' başlık dosyasını kullanmak. Çünkü bazı fonksiyonlar hata durumunda, bu başlık dosyasındaki global değişkeni 'set' ediyorlar. Fakat zaman zaman bu yaklaşım işimizi görmemektedir. Aşağıdaki örnekleri inceleyelim: * Örnek 1, 'errno.h' başlık dosyasının kullanılması ve geleneksel hata ayıklama yöntemleri: #include #include #include int main() { printf("errno : %d\n", errno); // OUTPUT => errno : 0 FILE* filePtr = fopen("YokBoyleBirDosya.txt", "r"); // Geleneksel hata işleme yöntemi ile; if(!filePtr) { printf("[YokBoyleBirDosya.txt] isimli boyle bir dosya YOK.\n"); // OUTPUT => [YokBoyleBirDosya.txt] isimli boyle bir dosya YOK. } // 'errno' kullanırsak printf("errno : %d\n", errno); // OUTPUT => errno : 2 // Buradaki '2' rakamı, hata koduna karşılık gelmektedir. İlgili fonksiyonun dökümanından detaylarına bakılabilir. // Hata kodunu yazdirmak istersek; perror("hata "); // OUTPUT => hata : No such file or directory // Çıktıdan da görüldüğü üzere ilgili hata kodunun detaylarını yazdırmaktadır. printf("2 rakamina karsilik gelen hata kodu : %s", strerror(errno)); // OUTPUT => 2 rakamina karsilik gelen hata kodu : No such file or directory puts(strerror(errno)); // OUTPUT => No such file or directory // Buradaki 'strerror' isimli fonksiyon 'string.h' başlık dosyasında bildirilmiş ve ilgili hata kodunu bir yaziya // dönüştürmektedir. } * Örnek 2, Aynı hata kodunun iki farklı durumda döndürülmesi. #include #include int main() { char str[100]; printf("bir yazi girin : "); scanf("%s", str); // INPUT => Ahmet123, 123Ahmo, 0 int ival = atoi(str); printf("ival : %d\n", ival); // OUTPUT => 0, 123, 0 // Gördüğünüz gibi '0' rakamı 'output' olarak iki farklı senaryoda da kullanıldı. } >>> Örneklerden de görüleceği üzere Geleneksel Hata Ayıklama yöntemi aşağıdaki problemleri ve ihmallikleri de beraberinde getirebilir. >>>> İşi yapan kod ile işin yapılıp yapılmadığını test eden kod iç içe olduğundan, ilgili kodun okunması/test edilmesi/değiştirilmesi de bir hayli zorlaşmaktadır. Fakat bizim amacımız işi yapan kod ile hataları elden geçiren kodların birbirlerinden daha ayrık durumda olmaları ki işletme maliyeti azalsın. >>>> Bir hata durumunda fonksiyonu çağıran koda bu hata bilgisi geri döndürüldüğünden, o fonksiyon da tekrardan ilgili hata kodunu geri döndürebilir eğer hatayı çözme yetkisi kendisinde değilse. İç içe 15-20 defa fonksiyon çağrılma durumunda, belki de beşinci fonksiyona kadar bu hata kodu geri döndürülecektir. (her ne kadar C dilinde 'long-jump' mekanizmaları olsa da bunlar kodun okunmasını iyice zorlaştırmaktadır). Bu da bir işletme maliyeti doğurmaktadır. Hem bize 'long-jump' olanağı sunsa hem de okumayı kolaylaştıran bir mekanizma olsa çok daha iyi olurdu. >>>> Bir hatanın elden geçirilmesini zorlama olmadığından, gerek ihmalkarlık gerek başka nedenlerden, program hatalı bir şekilde çalışmaya devam edecektir. >> 'Exception Handling' mekanizması işletilirken beklenmeyen bir durum anında bir 'exception' nesnesi 'throw' edilir. Programın akışı bu aşamadan sonra, yani bir 'to throw exception' senaryosunda, bu hatayı yakalayan kod bloğuna geçiyor. Eğer herhangi bir koda tarafından yakalanmaması durumunda programımız SONLANIYOR. İşte C dilindeki Hata Ayıklama yöntemlerinin oluşturduğu sorunlar, ki bunlar yukarıda açıklanmıştır, C++ dilinde 'Exception Handling' mekanizması ile gideriliyor/hafifletiliyor. >> C dilinde hata durumlarında 'abort()' fonksiyonu çağrılmaktadır eğer duruma uygun ise. Fakat C++ dilinde ise 'std::terminate()' fonksiyonunu çağırmaktadır ki bu fonksiyon da arka planda 'abort()' fonksiyonunu çağırmaktadır. Fakat biz dilersek 'abort()' yerine kendi fonksiyonumuzu da çağırtabiliriz. >>> C dilindeki 'exit()' : "terminates the process normally. Flushes unwritten buffered data. Closes all open files. Removes temporary files. Returns an integer exit status to the operating system." * Örnek 1, /* exit example */ #include #include int main () { FILE * pFile; pFile = fopen ("myfile.txt", "r"); if (pFile == NULL) { printf ("Error opening file"); exit (1); } else { /* file operations here */ } return 0; } >>> C dilindeki 'abort()' : "Unlike exit() function, abort() may not close files that are open. It may also not delete temporary files and may not flush stream buffer. So programs like below might not write “Geeks for Geeks” to “tempfile.txt”." * Örnek 1, #include #include int main() { FILE *fp = fopen("C:\\myfile.txt", "w"); if(fp == NULL) { printf("\n could not open file "); getchar(); exit(1); } fprintf(fp, "%s", "Geeks for Geeks"); /* ....... */ /* ....... */ /* Something went wrong so terminate here */ abort(); getchar(); return 0; // If we want to make sure that data is written to files and/or buffers are flushed then we should either use exit() or // include a signal handler for SIGABRT. } >>> C dilindeki 'assert()' : Aşağıdaki örneğin inceleyelim. * Örnek 1, #include #include #define myTempAssert 35 void open_record(int ID) { assert(ID != myTempAssert); printf("No Assert!\n"); } int main(void) { open_record(25); // OUTPUT => No Assert! open_record(35); // OUTPUT => a.out: main.c:8: open_record: Assertion `ID != myTempAssert' failed. } >> Bir 'to throw an exception' senaryosu olduğunda, programın akışı başka yere kayacağından bahsetmiştik. İşte onun ispatı: * Örnek 1, Herhangi bir hata olmadığında programın akışı: #include void f4() { std::cout << "f4 cagrildi.\n"; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. f4 sona erdi. f3 sona erdi. f2 sona erdi. f1 sona erdi. main sona erdi. */ std::cout << "main cagrildi.\n"; f1(); std::cout << "main sona erdi.\n"; } * Örnek 2, bir hata durumunda(uncaught exception): Bu durumda 'std::terminate()' fonksiyonu çağrılır. Bu fonksiyon varsayılan fonksiyon olarak 'abort()' çağrırır. #include void f4() { std::cout << "f4 cagrildi.\n"; throw 13; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. terminate called after throwing an instance of 'int' */ std::cout << "main cagrildi.\n"; f1(); std::cout << "main sona erdi.\n"; // Çıktıtan da görüldüğü üzere bir hata gönderildikten sonra programın akışı artık başka yere geçmektedir. } >> Herhangi bir hatanın yakalanmaması durumunda 'std::terminate()' fonksiyonunun, onun da arka planda 'abort()' fonksiyonunu çağırdığını göstermiştik. İşte 'std::terminate' fonksiyonunun temsili implementasyonu. * Örnek 1, typedef void (*terminate_handler)(void); // Temsili gösterimi. terminate_handler set_terminate(terminate_handler); // Bizim istediğimiz fonksiyonun çağrılması için o fonksiyonu buraya argüman olarak geçmemiz gerekiyor. // Dolayısıyla eğer bu 'std::terminate' fonksiyonunu tekrar fabrika ayarına geri döndürmek için, bu fonksiyonun // geri dönüş değerini bir başka yerde SAKLAMAMIZ gerekiyore. * Örnek 2, #include #include void myCustomAbort() { std::cerr << "Yakalanamayan hata......\nabort() fonksiyonu cagrilicaktir.\n"; abort(); } void f4() { std::cout << "f4 cagrildi.\n"; throw 13; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. Yakalanamayan hata...... abort() fonksiyonu cagrilicaktir. */ auto defaultAbort = std::set_terminate(myCustomAbort); std::cout << "main cagrildi.\n"; f1(); std::cout << "main sona erdi.\n"; // Çıktıktan da görüldüğü üzere yakalanamayan hata durumunda bizim fonksiyonumuz çağrılmıştır. } >> 'try-catch' Mekanizması: Aşağıdaki örneği inceleyelim. * Örnek 1, Kaba hali ile try-catch bloğu: //.. int main() { //.. try { /* Ama bu blok içerisinde bir şey 'throw' ediliyor olsun Ama bu blok içerisindeki fonksiyon içerisinden bir şey 'throw' ediliyor olsun Bu blokta çalışan kod 'throw' ettiğini varsayalım; */ } catch(/* throw edilen şey ile aynı türü buraya yazıyoruz */) { /* Eğer 'throw' edilen şeyin türü ile 'catch()' fonksiyonunun parametre parantezi içerisindeki tür aynı ise programın akışı buraya giriyor. */ } catch(/* throw edilen şey başka bir ise onun türünü de buraya yazıyoruz */) { /* Eğer 'throw' edilen şeyin türü ile bu 'catch()' fonksiyonunun parametre parantezi içerisindeki tür aynı ise programın akışı buraya giriyor. */ } catch(/*...*/) { /*...*/ } // Burada 'throw' edilen şey 'primitive' tür de olabilir, bu iş için oluşturulan özel bir sınıf da olabilir. // 'primitive' tür olması durumunda, 'catch' fonksiyonlarının parametreleri için bir tür dönüşümü SÖZ KONUSU DEĞİLDİR. // Yani, gönderilen tür 'float' ise onu yakalayacak 'catch()' fonksiyonunun parametre türünün de 'float' OLMASI GEREKİYORE. } * Örnek 2, #include int main() { // OUTPUT => catch(float) try { throw 34.43f; } catch(int) { std::cout << "catch(int)\n"; } catch(double) { std::cout << "catch(double)\n"; } catch(float) { std::cout << "catch(float)\n"; } } * Örnek 3, #include int main() { // OUTPUT => terminate called after throwing an instance of 'float' try { throw 34.43f; } catch(int) { std::cout << "catch(int)\n"; } catch(double) { std::cout << "catch(double)\n"; } } >>> Eğer gönderilen şey yakalanırsa programın akışı ilgili 'catch()' fonksiyonunun bloğuna giriyor. Sonrasında da en sonki 'catch()' bloğunun bittiği yere geçiyor. * Örnek 1, #include #include void f4() { std::cout << "f4 cagrildi.\n"; throw 13; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. Hata yakalandi. => x : 13 main sona erdi. */ std::cout << "main cagrildi.\n"; try{ f1(); } catch(int x) // 'try' bloğu içerisinde 'throw' edilen şeyin türü 'int' olduğundan, buraya da 'int' yazdık. { std::cout << "Hata yakalandi. => x : " << x << "\n"; } std::cout << "main sona erdi.\n"; // Çıktıda da görüldüğü üzere hata yakalandıktan sonra en sonki 'catch' bloğundan sonraki noktaya geçmiştir. } * Örnek 2, yukarıda da anlatıldığı gibi ilgili hatanın yakalanabilmesi için 'throw' edilen şeyin türü ile 'catch()' fonksiyonunun parametresinin türünün aynı olması gerekmektedir. Bunun tek istisnası kalıtım hiyerarşisi. Türemiş sınıf türünden gönderilen bir şeyi taban sınıf ile yakalayabilirim. Onun haricinde 'Funciton Overload' mekanizmasındaki 'Exact-match' durumu. #include #include void f4() { std::cout << "f4 cagrildi.\n"; throw 13.31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. terminate called after throwing an instance of 'double' */ std::cout << "main cagrildi.\n"; try{ f1(); } catch(int x) { std::cout << "Hata yakalandi. => x : " << x << "\n"; } std::cout << "main sona erdi.\n"; // Hata yakalanamadığı için 'abort()' fonksiyonu çağrıldı. } >>> 'try' ve 'catch' blokları da birer blok olduklarından 'block-scope' KURALLARINA TABİİDİR. >>> 'throw' edilen şeyler çok az olasılık ile 'primitive' türler olmasına karşın ekseri çoğunluk standart kütüphanenin sunmuş olduğu hata sınıfı ile bizlerin oluşturduğu hata sınıflarından türlerdir. >>> Standart kütüphanenin öğelerinin göndermiş oldukları şeylerin 'std::exception' sınıfına veya o sınıftan türetilen sınıflara ait olduğu GARANTİ ALTINDADIR. Bu da demektir ki; std::exception / | \ bad_alloc logic_error ... | out_of_range >>>> Standart kütüphanenin göndereceği her bir hata nesnesini, 'std::exception' sınıf türünden parametreye sahip bir 'catch()' fonksiyonu ile yakalayabilirim. >>>> Yine 'out_of_range' türden bir hata nesnesi gönderildiğinde, bu hata nesnesini, gerek 'out_of_range' sınıf türünden parametreye sahip gerek 'logic_error' sınıf türünden parametreye sahip gerek 'std::exception' sınıf türünden parametreye sahip bir 'catch()' fonksiyonu ile yakalayabilirim. İşte kalıtım hiyerarşisinin önemi burada da ortaya çıkmış oldu. Her 'out_of_range' bir 'logic_error' ve her bir 'logic_error' da bir 'std::exception'. * Örnek 1, #include #include #include void f4() { std::cout << "f4 cagrildi.\n"; std::string name{"ali"}; auto c = name.at(123); // İlgili fonksiyon, geçersiz indeks girildiğinde, 'out_of_range' sınıf türünden hatagöndermektedir. std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. Hata yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 3) main devam ediyor. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. Hata yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 3) main sona ermek üzere. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. Hata yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 3) main sona erdi. */ std::cout << "main cagrildi.\n"; try{ f1(); } catch(const std::out_of_range& exception) // { std::cout << "Hata yakalandi. => " << exception.what() << "\n"; } std::cout << "main devam ediyor.\n"; try{ f1(); } catch(const std::logic_error& exception) // { std::cout << "Hata yakalandi. => " << exception.what() << "\n"; } std::cout << "main sona ermek üzere.\n"; try{ f1(); } catch(const std::exception& exception) // { std::cout << "Hata yakalandi. => " << exception.what() << "\n"; } std::cout << "main sona erdi.\n"; // '.what()' fonksiyonu, en tepedeki 'std::exception' sınıfının bir sanal fonksiyonudur. 'virtual-dispatch' // mekanizması burada da işlemektedir. Fakat yazdırdığı yazının ne olacağı garanti altında değildir. // Kalıtım hiyerarşisindeki üç farklı sınıf türü ile de aynı hatayı yakalamış olduk. Fakat unutulmamalıdır ki // 'exception' parametreli 'catch' bloğu standart kütüphanedeki bütün hataları yakalayabilirken, 'out_of_range' // parametreli 'catch' bloğu sadece 'out_of_range' türden hataları yakalamaktadır. Yelpazeyi ne kadar geniş tutarsak, // hatanın detayları konusunda da o kadar az detay bilgiye sahip olabiliriz veya başka hataları yakalayamayabiliriz. // Eğer aynı hata türü için birden fazla 'catch' bloğu yazacaksak, ÖZELDEN GENELE DOĞRU YAZMALIYIZ. // DAHA ÖZEL OLAN DAHA YUKARIDA OLMALI. } * Örnek 2, ÖZELDEN GENELE DOĞRU SIRALAMA: #include #include #include void f4() { std::cout << "f4 cagrildi.\n"; std::string name{"ali"}; auto c = name.at(123); // İlgili fonksiyon, geçersiz indeks girildiğinde, 'out_of_range' sınıf türünden hata göndermektedir. std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # main cagrildi. f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. Hata yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 3) main sona erdi.. */ std::cout << "main cagrildi.\n"; try{ f1(); } catch(const std::out_of_range& exception) // { std::cout << "Hata yakalandi. => " << exception.what() << "\n"; } catch(const std::logic_error& exception) // { std::cout << "Hata yakalandi. => " << exception.what() << "\n"; } catch(const std::exception& exception) // { std::cout << "Hata yakalandi. => " << exception.what() << "\n"; } std::cout << "main sona erdi.\n"; } >>> 'catch()' bloğundaki referanslık, neye referanslık? Derleyici nasıl bir üretmektedir? * Örnek 1, #include int main() { /* # OUTPUT # x : 10 */ int x = 10; try{ throw x; } catch(int& ref) { std::cout << "hata yakalandi.\n"; ref = 100; } std::cout << "x : " << x << "\n"; // Görüldüğü üzere 'x' değişkeninin değeri değişmedi. Demek ki 'x' değişkenine referans değilmiş. // Peki 'catch()' bloğundaki referans neye referans? // 'throw' deyimi ile derleyici ne yapıyor: // throw expression; // Derleyici derleme zamanında 'expression' isimli ifadenin türüne bakıyor. Bu türden bir nesne oluşturuyor, bizim // kod içerisinde görmediğimiz. Oluşturduğu bu nesneye ise 'expression' isimli ifade ile ilk değer veriyor. // Pseudo code : // decltype(expression) myHiddenObject(expression); // Daha sonra derleyici 'myHiddenObject' nesnesini gönderiyor. Dolayısıyla 'catch' bloğundaki referans da buna referans // oluyor. // Peki oluşturulan bu gizli değişkenin ömrü ne zaman bitiyor? // Gönderilen hata 'handle' edildikten sonra da ömrü bitiyor. } * Örnek 2, yukarıdaki örnekteki senaryonun ispatı: #include class A{ public: A(){ std::cout << "A::A() this => " << this << "\n"; } A(const A&) { std::cout << "A::A(const A&) this => " << this << "\n"; } ~A(){ std::cout << "A::~A() this => " << this << "\n"; } }; void func() { std::cout << "func basladi.\n"; A ax; std::cout << "func devam ediyor.\n"; throw ax; } int main() { /* # OUTPUT # main basladi. func basladi. A::A() this => 0x7ffc96f5a567 // 'ax' isimli nesne için. func devam ediyor. A::A(const A&) this => 0x55754b5e0340 // Derleyicinin oluşturduğu gizli nesne için. A::~A() this => 0x7ffc96f5a567 // 'ax' isimli nesne için. hata yakalandi. A::~A() this => 0x55754b5e0340 // Derleyicinin oluşturduğu gizli nesne için. main sona erdi. */ std::cout << "main basladi.\n"; try{ func(); } catch(A& refA) { std::cout << "hata yakalandi.\n"; } std::cout << "main sona erdi.\n"; // Çıktıdan da görüldüğü üzere derleyicinin oluşturduğu gizli nesnenin hayatı, ilgili hata handle edildikten sonra // sona ermiştir. } >>> 'catch()' bloğu tek parametre kabul etmektedir. >>> 'catch()' bloğunda yakalama yaparken sınıf türünün kendisini kullanmamalıyız. O sınıf türüne REFERANS yolu ile yakalamalıyız. Aksi halde, >>>> Kopyalamanın maliyetinden kaçamamış oluruz. >>>> İlgili sınıf türü de kopyalanacağı için ona ait 'Copy Ctor' fonksiyonu da 'exception' gönderebilir. >>>> 'Object-slicing' senaryosu gerçekleşebilir, 'virtual-dispatch' MEKANİZMASI DA ÇALIŞMAYACAKTIR. >>> Kilit mesaj " throw by copy, catch by referance ". Yine 'throw' ederken de Geçici Nesne kullanmaya özen göstermeliyiz. İsimlendirilmiş nesnelerin 'throw' edilmeleri göze HOŞ GELMEMEKTEDİR. Örneğin, "throw std::out_of_range{"Aralik disi deger!!!..."};" şeklinde göndermeye özen göstermeliyiz. >>> 'catch()' bloğunda parantez içerisinde '...' atomunu koymak hata nesnesinin türünden bağımsız bütün hataları yakalamak içindir(%98). Fakat yakalamış olduğumuz hata nesnesinin ne olduğunu anlama şansımız yoktur. Sadece ne OLMADIĞINI anlayabiliriz; girmediği 'catch()' blokları, o türden olmadığı anlamındadır. * Örnek 1, #include #include void f4() { std::cout << "f4 cagrildi.\n"; throw 31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # f1 cagrildi. f2 cagrildi. f3 cagrildi. f4 cagrildi. catch all... main cagrildi. main sona erdi. */ try{f1();} catch(const std::out_of_range& ex) { std::cout << "Hata yakalandi. ex => " << ex.what() << "\n"; } catch(const std::logic_error& ex) { std::cout << "Hata yakalandi. ex => " << ex.what() << "\n"; } catch(const std::exception& ex) { std::cout << "Hata yakalandi. ex => " << ex.what() << "\n"; } catch(...){ std::cout << "catch all...\n"; } // Eğer bu blokta yakalanırsa şundan eminiz ki bizim hatamız standart sınıftaki hata sınıfından veya o sınıftan // türetilen bir sınıfa ait değil. std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; } >> Peki ne türden hatalar göndermeliyiz? El-cevap: Bu sorunun cevabı ekseriyet ile projenin dökümanlarında belirtilmiştir. Aksi belirtilmedikçe o dökümanları takip etmeliyiz. Fakat ekseriyet ile aşağıdaki türlerden hatalar gönderilmektedir. >>> Doğrudan 'std::exception' kalıtım hiyerarşisinde bulunan standart sınıflar türünden hata nesneleri, >>> Kendi oluşturduğumuz hata sınıfımız/sınıflarımız türünden hata nesnesi göndermek. >>>> Sıfırdan kendi sınıfımızı yazarak, >>>> Standart kütüphanenin hata sınıflarının kalıtım yolu ile yeni bir sınıf türeterek, * Örnek 1, #include #include #include // Standart kütüphanedeki bazı hata ayıklama fonksiyonları da burada bildirilmiştir. class A : public std::exception{ // 'std::exception' sınıfındaki 'what()' fonksiyonu 'overload' edilmez ise 'I' numaralı çıktıyı alacağız. public: // Eğer onu 'overload' edersek, 'II' numaralı çıktıyı alacağız. const char* what()const noexcept override { return "Gecersiz data verisi.\n"; } }; int main() { /* # OUTPUT # hata yakalandi => std::exception // I hata yakalandi => Gecersiz data verisi. // II */ try{ throw A{}; } catch(std::exception& ex) { std::cout << "hata yakalandi => " << ex.what() << "\n"; } } >>>> Kullanmış olduğumuz üçüncü parti kütüphanelerinin hata sınıflarını kullanmak veya onlardan kendimize yeni sınıflar türeterek. >> Nerede müdahale etmek istiyorsak, 'exception' LARI ORADA YAKALAMAYA ÇALIŞMALIYIZ. Eğer bizler müdahale etmeyeceksek, herhangi bir hata yakalamaya ÇALIŞMAMALIYIZ. >> Bir şekilde bir hata yakalamış olalım. Önümüzde bizi bekleyen şeyler nelerdir? El-cevap, >>> Hatayı 'handle' eder ve programın çalışmaya devam etmesini veya programın kontrollü bir şekilde sonlanmasını sağlar. >>> Hatayı yakalar fakat başka bir sınıf türünden hata gönderir. (Hatayı 'translate' eder.) >>> Hatayı 'rethrow' eder. >> 'Uncaught-exception' durumunun zararları: RAII deyimi güden bir sınıfımız olsun. İsmi de A. Bu sınıfın kurucu işlevleri ilgili sınıf türünden nesneler hayata gelirken belli bir 'memory' ve 'resource' kullanmaktalar. İlgili nesnenin de hayatı bittiğinde kullanmış oldukları bu 'memory' ve 'resource' ları geri vermekteler. Programımızın çalışması esnasında bir hata fırlatılsa fakat bir nedenden dolayı yakalanamasa, 'abort()' fonksiyonu çağrılacaktır varsayılan ayar olaraktan. İş bu fonksiyon ise kontrolsiz bir şekilde programı sonlandıracaktır. Dolayısıyla sınıf nesnelerimiz elde etmiş oldukları 'memory' ve 'resource' leri kontrollü bir şekilde geri verememiş oldular. Bu da veri kaybına yol açabilir. Fakat eğer bizler bu hatayı yakalarsak dilin bize garanti etmiş olduğu 'stack-unwinding' mekanizması devreye girecektir. Bu mekanizma, hatanın gönderildiği yer ile hatanın ele alınacağı yer arasındaki 'yerel' değişkenlerin 'Dtor' fonksiyonlarının çağrılması mekanizmasıdır. 'catch()' bloğuna girmeden evvel bu 'Dtor' fonksiyonları çağrılmaktadır ve sadece 'Dtor' için de sınırlı değildir. 'Yerel' değişkenlerin ömürleri bitiyor. * Örnek 1, #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugundaalan ayrildi.\n"; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geriverildi\n"; } }; void f4() { std::cout << "f4 cagrildi.\n"; A aaaax; // throw 31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; A aaax; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; A aax; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; A ax; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # (HERHANGİ BİR HATA OLMAMASI DURUMUNDA) f1 cagrildi. 0x7ffd8329db27 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x7ffd8329daf7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x7ffd8329dac7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x7ffd8329da97 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 sona erdi. 0x7ffd8329da97 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. f3 sona erdi. 0x7ffd8329dac7 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. f2 sona erdi. 0x7ffd8329daf7 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. f1 sona erdi. 0x7ffd8329db27 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. main cagrildi. main sona erdi. */ /* # OUTPUT # (f4() FONKSİYONU HATA GÖNDERDİ.) f1 cagrildi. 0x7ffc8adb9697 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x7ffc8adb9667 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x7ffc8adb9637 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x7ffc8adb9607 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. terminate called after throwing an instance of 'int' */ f1(); std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; // Çıktılardan da görüldüğü üzere elde etmiş olduğumuz kaynakları geri veremedik, açmış olduğumuz dosyaları kapatamadık. // Database bağlantılarını sonlandıramadık. } * Örnek 2, Stack Unwinding Mekanizmasının İşletilmesi: #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi.\n"; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi\n"; } }; void f4() { std::cout << "f4 cagrildi.\n"; A aaaax; throw 31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; A aaax; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; A aax; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; A ax; f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # f1 cagrildi. 0x7fff7d99d3c7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x7fff7d99d397 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x7fff7d99d367 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x7fff7d99d337 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. 0x7fff7d99d337 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7fff7d99d367 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7fff7d99d397 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7fff7d99d3c7 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. catch(int)....... main cagrildi. main sona erdi. */ try{ f1(); } catch(int) { std::cout << "catch(int).......\n"; } std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; } * Örnek 3, RAII deyimi ve Stack Unwinding mekanizması: #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi.\n"; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi.\n"; } }; void f4() { std::cout << "f4 cagrildi.\n"; A aaaax; throw 31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; A aaax; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; A aax; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; A ax; f2(); std::cout << "f1 sona erdi.\n"; } void func() { FILE* filePTR = fopen("deneme.txt", "w"); //.. fprintf(filePTR, "asjdflhgfkjhdkflghdlkfjgh"); f1(); fclose(filePTR); } int main() { /* # OUTPUT # (f4() fonksiyonu hata gönderdi ama YAKALANMADI. Ek olarak oluşturulan 'deneme.txt' dosyasına da bir şey yazılamadı.) f1 cagrildi. 0x7ffc89cf0807 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x7ffc89cf07d7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x7ffc89cf07a7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x7ffc89cf0777 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. terminate called after throwing an instance of 'int' */ /* # OUTPUT # (f4() fonksiyonu hata gönderdi ve YAKALANDI. Ek olarak oluşturulan 'deneme.txt' dosyasına da belirtilen yazı yazıldı.) f1 cagrildi. 0x7ffde8a53c07 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x7ffde8a53bd7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x7ffde8a53ba7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x7ffde8a53b77 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. 0x7ffde8a53b77 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7ffde8a53ba7 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7ffde8a53bd7 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7ffde8a53c07 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. catch(int)....... main cagrildi. main sona erdi. */ try{ func(); } catch(int) { std::cout << "catch(int).......\n"; } std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; // Her iki senaryoda da programın akışı "fclose(filePTR);" satırına hiç bir zaman gelmemektedir. Çünkü hata gönderildikten sonra ilgili 'catch()' bloğuna geçmekte, // oradan da bir alt satırdan devam etmektedir. Eğer bizler bu dosya açma-kapama işlemini bu kadar çıplak yapmak yerine RAII deyimi ile sarmalasaydık, // Stack-Unwinding gereği bir sorunumuz kalmayacaktı. } * Örnek 4, RAII deyimi ve Stack Unwinding mekanizması v2: #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi.\n"; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi.\n"; } }; class FileHandler{ public: FileHandler(const char* p) : fp(fopen(p, "w")) { std::cout << "Dosya acildi.\n"; fprintf(fp, "qweroıuwqeroıu"); } ~FileHandler() { fclose(fp); std::cout << "Dosya kapatildi.\n"; } private: FILE* fp; }; void f4() { std::cout << "f4 cagrildi.\n"; A aaaax; throw 31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; A aaax; f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; A aax; f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; A ax; f2(); std::cout << "f1 sona erdi.\n"; } void func() { FileHandler fObj("deneme.txt"); f1(); } int main() { /* # OUTPUT # (f4() hata gönderdi, yakalandi ve dosyaya da qweroıuwqeroıu yazildi.) Dosya acildi. f1 cagrildi. 0x7ffe834d2487 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x7ffe834d2457 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x7ffe834d2427 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x7ffe834d23f7 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. 0x7ffe834d23f7 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7ffe834d2427 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7ffe834d2457 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x7ffe834d2487 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. Dosya kapatildi. catch(int)....... main cagrildi. main sona erdi. */ try{ func(); } catch(int) { std::cout << "catch(int).......\n"; } std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; // RAII deyiminin ne kadar da faydalı olduğunu bir kez daha görmüş olduk. Peki ilgili 'A' sınıfımız dinamik olarak // elde etseydik? İş bu Stack-Unwinding mekanizması yine ÇALIŞMAYACAKTI. Çünkü dinamik ömürlü nesneler 'heap' alanında // saklanmaktadırlar. İşte dinamik ömürlü nesnelere de RAII deyimini eklemek için Akıllı Göstericileri KULLANMALIYIZ. // Çünkü onlar da aslındaki yukarıdaki FileHandler sınıfı gibi birer sınıflar. } * Örnek 5, RAII, Stack-Unwinding ve Akıllı Göstericiler: #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi.\n"; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi.\n"; } }; void f4() { std::cout << "f4 cagrildi.\n"; auto ptr = new A; throw 31; std::cout << "f4 sona erdi.\n"; delete ptr; } void f3() { std::cout << "f3 cagrildi.\n"; auto ptr = new A; f4(); std::cout << "f3 sona erdi.\n"; delete ptr; } void f2() { std::cout << "f2 cagrildi.\n"; auto ptr = new A; f3(); std::cout << "f2 sona erdi.\n"; delete ptr; } void f1() { std::cout << "f1 cagrildi.\n"; auto ptr = new A; f2(); std::cout << "f1 sona erdi.\n"; delete ptr; } int main() { /* # OUTPUT # (f4() hata gönderdi ve yakalandi. Ama dinamik ömürlü nesneler için 'Dtor' çağrılmadı.) f1 cagrildi. 0x562d0002b2c0 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x562d0002b2e0 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x562d0002b300 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x562d0002b320 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. catch(int)....... main cagrildi. */ try{ f1(); } catch(int) { std::cout << "catch(int).......\n"; } std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; // Fonksiyonların içerisindeki 'ptr' isimli şey bir sınıf nesnesidir. } * Örnek 6, RAII, Stack-Unwinding ve Akıllı Göstericiler v2: #include #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi.\n"; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi.\n"; } }; void f4() { std::cout << "f4 cagrildi.\n"; auto ptr = std::make_unique(); throw 31; std::cout << "f4 sona erdi.\n"; } void f3() { std::cout << "f3 cagrildi.\n"; auto ptr = std::make_unique(); f4(); std::cout << "f3 sona erdi.\n"; } void f2() { std::cout << "f2 cagrildi.\n"; auto ptr = std::make_unique(); f3(); std::cout << "f2 sona erdi.\n"; } void f1() { std::cout << "f1 cagrildi.\n"; auto ptr = std::make_unique(); f2(); std::cout << "f1 sona erdi.\n"; } int main() { /* # OUTPUT # (f4() hata gönderdi ve yakalandi. Akıllı Göstericiler kullanıldığından, A sınıfı için de 'Dtor' çağrıldı.) f1 cagrildi. 0x56080fdb92c0 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f2 cagrildi. 0x56080fdb92e0 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f3 cagrildi. 0x56080fdb9300 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. f4 cagrildi. 0x56080fdb9320 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. 0x56080fdb9320 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x56080fdb9300 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x56080fdb92e0 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. 0x56080fdb92c0 : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi. catch(int)....... main cagrildi. main sona erdi. */ try{ f1(); } catch(int) { std::cout << "catch(int).......\n"; } std::cout << "main cagrildi.\n"; std::cout << "main sona erdi.\n"; } >>> Hataların bir çoğunu 'main()' fonksiyonunun bloğunu kapsayıcı bir şekilde 'try' bloğu içine alırsak yakalarız. Fakat 'global namespace' alanındaki değişkenlerin 'Ctor' fonksiyonları hata gönderirse ONLARI YAKALAYAMAYIZ. Çünkü bu nesneler 'main()' fonksiyonundan önce hayata gelmektedirler. Dolayısıyla bu hataları yakalamak için başka yöntemler denemeliyiz. * Örnek 1, #include #include class A{ public: A() { std::cout << this << " : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi.\n"; throw 31; } ~A() { std::cout << this << " : A::~A() cagrildi. X isimli dosya kapatildi. Y database baglantisi kesildi. 250-byte buyuklugunda alan geri verildi.\n"; } }; A ga; // Bu nesnenin kurucu işlevinin gönderdiği hata nesnesi YAKALANAMADI. Dolayısıyla 'abort()' çağrıldı. int main() { try // %99 ihtimal ile yakalamak için. { std::cout << "main cagrildi.\n"; /* # OUTPUT # 0x564155e71151 : A::A() cagrildi. X isimli dosya acildi. Y database baglantisi kuruldu. 250-byte buyuklugunda alan ayrildi. terminate called after throwing an instance of 'int' */ std::cout << "main sona erdi.\n"; } catch(...) { std::cout << "catch(...).......\n"; } } > Kitap Tavsiyesi : Writing SOLID Code by Steve Maguire. C dilinde Bug-free kod yazmak için OKUNMALI. > Fonksiyon yazarken dökümantasyonunda, iş bu fonksiyona 'nullptr' argüman gönderilmesi ve 'nullptr' değer döndürmesi ne manaya gelecektir, sorusunun cevabını mutlaka belirtmeliyiz. Yani o fonksiyonu üçüncü parti kişilere açacaksak mutlaka ÇOK İYİ dökümante ETMELİYİZ. /*============================================================================================================*/ (21_28_11_2020) > Exception Handling mekanizması (devam): >> Exception Kalıtım Hiyerarşisi : std::exception / / / | | \ \ \ logic_error runtime_error bad_alloc bad_cast bad_function_call bad_typeid bad_weak_ptr bad_exception | | domain_error system_error <-ios_base::failure length_error range_error future_error underflow_error out_of_range overflow_error invalid_argument >> NOT: Exception Handling mekanizması, Asenktron Programlamaya yönelik doğrudan bir araç değildir. Tamamen Senkron Programlamaya yönelik bir araçtır. >> Ctor. ve Exceptions : Ctor. bir hata durumunda karşılaştığında EXCEPTION THROW ETMELİ, aksi bildirilmemişse. Eğer bir nesne hayata gelirken EXCEPTION THROW ETMEZ İSE HAYATA GELMİŞ OLACAK. ETMESİ DURUMUNDA HAYATA GELMEMİŞ OLACAK. Dolayısıyla kendisine ait Dtor. fonksiyonu da ÇAĞRILMAYACAK. Eğer iş bu sınıfımız nesne hayata getirirken raw-pointer ile kaynaklar elde ediyorsa, Dtor. fonksiyonu çağrılmayacağı için o KAYNAKLAR DA GERİ VERİLEMEYECEK. İşte bu yüzden Akıllı Pointer kullanmalıyız ki Stack-Unwinding mekanizmasından da yararlanalım. Diğer alternatifler ise Ctor. fonksiyonumuzun EXCEPTION THROW etmeyeceğinden %100 olmalıyız(ama hatayı Ctor. içinde yakalayarak ama hiç hata nesnesi göndermeyen kod yazarak), böylelikle raw-pointer kullanabiliriz. * Örnek 1, #include #include class A{ public: A() { std::cout << this<< " : A::A() cagrildi.\n"; throw 31; } ~A() { std::cout << this << " : A::~A() cagrildi.\n"; } }; int main() { /* # OUTPUT # 0x7fff84fa2433 : A::A() cagrildi. Hata yakalandi. */ try { A ax; } catch(int) { std::cout << "Hata yakalandi.\n"; } } * Örnek 2, Edinilen kaynakların geri verilememesi. #include #include class Nec{ public: Nec() { std::cout << this << " => Nec::Nec() cagrildi.\n"; } ~Nec() { std::cout << this << " => ~Nec::Nec() cagrildi.\n"; } }; class A{ public: A() { std::cout << this<< " : A::A() cagrildi.\n"; mp = new Nec[3]; throw 31; } ~A() { std::cout << this << " : A::~A() cagrildi.\n"; delete[] mp; } private: Nec* mp; }; int main() { /* # OUTPUT # 0x7ffef9134d10 : A::A() cagrildi. 0x564045abd2c8 => Nec::Nec() cagrildi. 0x564045abd2c9 => Nec::Nec() cagrildi. 0x564045abd2ca => Nec::Nec() cagrildi. Hata yakalandi. */ try { A ax; } catch(int) { std::cout << "Hata yakalandi.\n"; } // Her iki örnekteki çıktıdan da görüldüğü üzere A nesnesi hayata gelirken edinmiş olduğu kaynakları geri veremedi. } * Örnek 3, Akıllı Gösterici ile kaynakların geri verilmesi. #include #include #include class Nec{ public: Nec() { std::cout << this << " => Nec::Nec() cagrildi.\n"; } ~Nec() { std::cout << this << " => ~Nec::Nec() cagrildi.\n"; } }; class A{ public: A() { std::cout << this<< " : A::A() cagrildi.\n"; mp = std::make_unique(); throw 31; } ~A() { std::cout << this << " : A::~A() cagrildi.\n"; } private: std::unique_ptr mp; }; int main() { /* # OUTPUT # 0x7fffec8838a0 : A::A() cagrildi. 0x558c4a7462c0 => Nec::Nec() cagrildi. 0x558c4a7462c0 => ~Nec::Nec() cagrildi. Hata yakalandi. */ try { A ax; } catch(int) { std::cout << "Hata yakalandi.\n"; } // Akıllı Gösterici kullanmamız ve Stack-Unwinding mekanizması sayesinde 'ax' nesnesi hayata gelirken elde etmiş olduğu // kaynakları geri verdi. } >>> Dinamik ömürlü bir nesne hayata gelirken ona ait Ctor. fonksiyonu 'exception throw' ederse ve yakalanırsa, elde etmiş olduğu BELLEK BLOĞUNUN GERİ VERİLME GARANTİSİ VARDIR. O nesnenin Dtor. fonksiyonu yine çağrılmayacaktır. * Örnek 1, #include #include #include void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } class A{ public: A() { std::cout << this<< " : A::A() cagrildi.\n"; throw 31; } ~A() { std::cout << this << " : A::~A() cagrildi.\n"; } private: char buffer[1024]; }; int main() { /* # OUTPUT # operator new(1024) was called. A memory block has been occupied starting from : 0x55ad338472c0 0x55ad338472c0 : A::A() cagrildi. operator delete() was called. A memory block will be given back, starting from : 0x55ad338472c0 Hata yakalandi. */ try { A* ptr = new A; } catch(int) { std::cout << "Hata yakalandi.\n"; } } >>> Veri elemanının bir sınıf türünden olması durumunda ise; * Örnek 1, #include #include #include class Member{ public: Member() { std::cout << "Member ctor.\n"; } ~Member() { std::cout << "Member dtor.\n"; } }; class A{ public: A() { std::cout << "A ctor.\n"; throw 31; } ~A() { std::cout << "A dtor.\n"; } private: Member mx; }; int main() { /* # OUTPUT # Member ctor. A ctor. Member dtor. Hata yakalandi. */ try { A ax; } catch(int) { std::cout << "Hata yakalandi.\n"; } // İlk önce A nesnesinin veri elemanları hayata geliyor. Sonrasında programın akışı A sınıfının Ctor. fonksiyonunun // bloğuna giriyor. Dolayısıyla 'mx' nesnesi çoktan hayata gelmiş durumda hata gönderildiğinde. // Stack-Unwinding mekanizması ile hayata gelmemiş 'ax' nesnesi içindeki hayata gelmiş 'mx' nesnesinin Dtor. fonksiyonu // çağrılmaktadır. // Peki hatayı Member sınıfı gönderseydi ne olacaktı? } * Örnek 2, Veri elemanının hata göndermesi #include #include #include class Member{ public: Member() { std::cout << "Member ctor.\n"; throw 31; } ~Member() { std::cout << "Member dtor.\n"; } }; class A{ public: A() { std::cout << "A ctor.\n"; } ~A() { std::cout << "A dtor.\n"; } private: Member mx; }; int main() { /* # OUTPUT # Member ctor. Hata yakalandi. */ try { A ax; } catch(int) { std::cout << "Hata yakalandi.\n"; } // A sınıfı içindeki Member da hayata gelmemiş olacağından, her ikisinin de Dtor. fonksiyonları çağrılmayacaktı. // Peki A sınıfımız birden fazla sınıf türünden veri elemanına sahip olursa? } * Örnek 3, Birden fazla veri elemanına sahip olunması durumunda #include #include #include class Member{ public: Member() { std::cout << "Member ctor.\n"; throw 31; } ~Member() { std::cout << "Member dtor.\n"; } }; class Data{ public: Data() { std::cout << "Data ctor.\n"; } ~Data() { std::cout << "Data dtor.\n"; } }; class A{ public: A() { std::cout << "A ctor.\n"; } ~A() { std::cout << "A dtor.\n"; } private: Data dx; Member mx; }; int main() { /* # OUTPUT # Data ctor. Member ctor. Data dtor. Hata yakalandi. */ try { A ax; } catch(int) { std::cout << "Hata yakalandi.\n"; } // A sınıfının veri elemanları, bildirimdeki sıra ile hayata geleceklerinden dolayı // ilk önce 'dx' nesnesi hayata geliyor. 'mx' nesnesi hayata gelme aşamasındayken // hata gönderdiği için hayata gelemiyor. Dolayısıyla sadece hayata 'dx' geldiğinden, // ona ait Dtor. fonksiyonu çağrılıyor. } >> Exception ve Dtor. Fonksiyonları: Dtor. fonksiyonlarından dışarı bir EXCEPTION GONDERILMEMELI. Bu durumda ya biz Dtor. fonksiyonu içerisinde bu hatayı yakalamalıyız ya da hata göndermeyecek kod yazmalıyız. Çünkü dilin kuralları gereği bir exception 'handle' edilirken bir exception daha fırlatılırsa, 'std::terminate' fonksiyonu çağrılacaktır. Unutulmamalıdır ki Dtor. fonksiyonu iki farklı senaryoda çağrılmaktadır. Bunlardan ilki ömrü biten bir nesne için, ikinci ise Stack-Unwinding mekanizmasından dolayı. Günün sonunda da 'std::terminate' iki farklı senaryoda çağrılmaktadır. İlk senaryo gönderilen hatanın yakalanamaması durumunda(bkz. Uncaught Exception) ve Stack-Unwinding sürecinde, bir exception 'handle' edilirken Dtor. fonksiyonlardan birinin tekrar exception göndermesi durumunda. * Örnek 1, #include #include void myCustomAbort() { std::cerr << "Yakalanamayan hata......\n"; } void foo() { throw 31; } class Member{ public: Member() { std::cout << "Member ctor.\n"; } ~Member() { std::cout << "Member dtor.\n"; foo(); } }; void func() { Member mx; throw 32; } int main() { /* # OUTPUT # Member ctor. Member dtor. Yakalanamayan hata...... */ std::set_terminate(myCustomAbort); try { func(); } catch(int) { std::cout << "Hata yakalandi.\n"; } // Gördüğünüz üzere programın akışı catch bloğuna girmeden 'std::terminate' çağrıldı. Çünkü 'stack-unwinding' sürecinde Dtor. // tekrardan exception gönderdi. BURADAKİ KİLİT NOKTA Dtor. fonksiyonunun, dışarıya exception göndermesi. Velevki biz Dtor. // içerisinde bu hatayı 'handle' etseydik, dışarıya göndermeseydik, bir sorun olmayacaktı. } >> 'rethrow' statement: 'throw' anahtar kelimesini ';' atomunun izlemesi durumudur. Böylelikle yakalanan hata, tekrardan gönderilmektedir. * Örnek 1, #include #include class MyExceptHandler{ public: inline static int mVal{30}; }; void func() { throw MyExceptHandler{}; } void foo() { try{ func(); } catch(MyExceptHandler& x) { std::cout << "Hata foo içinde yakalandi. x : " << x.mVal << "\n"; x.mVal = 15; // throw x; // 're-throw' YAPILMAMAKTADIR. Yeni bir hata nesnesi kopyalama yolu ile oluşturulmuştur // ve o gönderilmiştir. Daha önce yakalananın da hayatı sona ermiş oluyor. // İki numaralı örnek incelenebilir. throw; // re-throw yapıldığı yer. } } int main() { /* # OUTPUT # Hata foo içinde yakalandi. x : 30 Hata main içinde yakalandi. x : 15 */ try { foo(); } catch(MyExceptHandler& x) { std::cout << "Hata main içinde yakalandi. x : " << x.mVal << "\n"; } } * Örnek 2, 'object-slicing' gerçekleşebilir. #include #include #include void func() { try{ std::string name{"Özge"}; auto c = name.at(123); } catch(const std::exception& ex) { std::cout << "Hata func içinde yakalandi. => " << ex.what() << "\n"; throw ex; // I } } int main() { /* # OUTPUT # Hata func içinde yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 5) terminate called after throwing an instance of 'std::exception' what(): std::exception */ try { func(); } catch(const std::out_of_range& ex) { std::cout << "Hata main içinde yakalandi. => " << ex.what() << "\n"; } // Çıktıtan da görüldüğü üzere bizler ikinci defa gönderilen hatayı yakayalamadık. // Çünkü 'I' numaralı deyim yürütüldüğünde 'object-sliding' gerçekleşti, yeni bir nesne // kopyalama yoluyla oluşturuldu ve o tekrardan gönderildi. Bizlerin gönderilen bu ikinci nesneyi yakalaması için // 'main' işlevi içindeki 'catch()' parametresi de 'std::exception' türünden olması gerekmekteydi yada 're-throw' // yapmamız gerekiyordu. } * Örnek 3, 're-throw' yapıldığındaki sonuç: #include #include #include void func() { try{ std::string name{"Özge"}; auto c = name.at(123); } catch(const std::exception& ex) { std::cout << "Hata func içinde yakalandi. => " << ex.what() << "\n"; throw; } } int main() { /* # OUTPUT # Hata func içinde yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 5) Hata main içinde yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 5) */ try { func(); } catch(const std::out_of_range& ex) { std::cout << "Hata main içinde yakalandi. => " << ex.what() << "\n"; } } * Örnek 4, 'main' işlevindeki yakalama parametresinin değiştirilmesi:(object-sliding) #include #include #include void func() { try{ std::string name{"Özge"}; auto c = name.at(123); } catch(const std::exception& ex) { std::cout << "Hata func içinde yakalandi. => " << ex.what() << "\n"; throw ex; } } int main() { /* # OUTPUT # Hata func içinde yakalandi. => basic_string::at: __n (which is 123) >= this->size() (which is 5) Hata main içinde yakalandi. => std::exception */ try { func(); } catch(const std::exception& ex) { std::cout << "Hata main içinde yakalandi. => " << ex.what() << "\n"; } // Çıktıdan da görüldüğü üzere bir 'object-slicing' mevzusu gerçekleşmiş fakat HATA YİNE YAKALANMIŞTIR. } >> 're-throw' deyiminin doğrudan 'catch' bloğu içerisinde olma zorunlulu yoktur. Bu bloktan çağrılan fonksiyonların bloklarında da olabilir. #include #include void myFunc() { std::cout << "myFunc()\n"; throw; } void foo() { std::cout << "foo()\n"; myFunc(); } void func() { try{ throw 31; } catch(int ex) { std::cout << "func::catch(int " << ex << ")\n"; foo(); } } int main() { /* # OUTPUT # func::catch(int 31) foo() myFunc() Hata main içinde yakalandi. => 31 */ try { func(); } catch(int x) { std::cout << "Hata main içinde yakalandi. => " << x << "\n"; } // Peki bizler direkt olarak 'myFunc' fonksiyonunu çağırırsak ne olur? // Derleyici bir throw görmeden 'rethrow' görmesi durumunda 'std::terminate' fonksiyonunu çağıracaktır. // Böylelikle derleyicinin 'std::terminate' fonksiyonunu çağırdığı üç farklı senaryo olmuş oldu. Bunlar; // i. 'Uncaught-exception' durumunda, // ii. 'Stack-Unwinding' durumunda Dtor. fonksiyonunun tekrardan hata göndermesi durumunda, // iii. Derleyici bir hata nesnesinin fırlatıldığını görmeden evvel direkt olarak 're-throw' görmesi durumunda. // Diğer yandan derleyiciler Dtor. fonksiyonunu da iki senaryoda çağrı yapmaktadır. Bunlar, // i. Ömrü biten nesneler için, // ii. 'Stack-Unwinding' durumunda otomatik ömürlü nesneler için. } >> 'Exception Dispatcher' deyiminin bir implementasyonu: * Örnek 1, #include #include class ExceptionTypeI{}; class ExceptionTypeII{}; class ExceptionTypeIII{}; void handle_exceptions() { try{ throw; // 'main' işlevindeki 'catch-all' bloğunda yakalanan hata tekrardan 're-throw' ediliyor. Böylelikle aşağıdaki // üç tane 'catch' bloklarından bir tanesi tarafından yakalanacağı umulmakta. } catch(const ExceptionTypeI& ex) { } catch(const ExceptionTypeII& ex) { } catch(const ExceptionTypeIII& ex) { } } int main() { /* # OUTPUT # func::catch(int 31) foo() myFunc() Hata main içinde yakalandi. => 31 */ try { // Buradan bir hata gönderildiğini varsayalım. } /* Burada standart kütüphanedeki Exception sınıfından parametre alan 'catch' bloklarının oldunu varsayalım.*/ catch(...) { // Dolayısıyla hata burada yakalanırsa, gönderilen hatanın standart kütüphanenin 'exception' sınıfına ait olmadığı // kesin. handle_exceptions(); } } >> 'Exception Guarantees' : Bir kodun 'exception-safe' olabilmesi için bazı koşulları sağlaması gerekmektedir. Unutulmamalıdır ki bir fonksiyon kodunu yazarken 'exception throw' edilmesi durumunda hiç bir şekilde bir kaynak sızıntısına mahal vermemelidir. Aşağıdaki örneği inceleyelim. * Örnek 1, // Aşağıdaki 'func' fonksiyonunu biz yazıyor olalım. void func(int n) { auto p = new int[n]; // some other codes here. // Farz edelim ki burada yazdığımız bir başka kod 'exception' throw etti. // some other codes here... delete p; } // Artık yukarıdaki fonksiyon bloğunda elde etmiş olduğumuz bellek bloğuna erişme ihtimalimiz YOKTUR. İşte bu fonksiyon // 'exception-safe' bir fonksiyon DEĞİLDİR. Kabul edilebilir bir KOD HİÇ DEĞİLDİR. >>> Bir kod, bir takım Garantiler Sunmalıdır. Bunlar, >>>> 'Basic Guarantee' : Bir kodun kabul edilebilir bir kod olması için minimum sağlaması gereken şartlar, sunması gereken minimum garantiye 'Basic Guarantee' denmektedir. İş bu garanti kategorisindeki kodlar kaynak sızıntısına mahal vermeyecek, geride 'geçersiz' durumda hiç bir nesne BIRAKMAYACAKTIR. Fakat programda bir durum değişikliği, bir 'state' değişikliği olabilir. Yine benzer şekilde program TUTARLI, yani devam edebilecek, bir durumda kalmalıdır. Örneğin, bizim fonksiyonumuzun bloğunda bir dosyaya yazı yazılıyor olsun. Yazma işleminden sonra da bir nedenden ötürü hata gönderilsin. Programın akışı bizim fonksiyon bloğundan çıkacağı için, varsayalım ki 'Stack-Unwinding' mekanizması ile yazıyı yazan sınıf dosyayı da kapattı, artık programın durumunda bir değişiklik oldu. Yani programın durumu bizim fonksiyonumuz çağrılmadan evvelki durumu ile çağrıldıktan sonraki durumu arasında BİR FARK VARDIR. * Örnek 1, // some codes here... void f(const char* n) { file outF(n, "w"); // 'file' bir sınıf ismi olduğunu varsayalım. if(outF.is_open()) { outF.put("The value is : "); outF.put(g()); // Farz edelim ki 'g()' fonksiyonu bir hata fırlattı. } } // Yukarıdaki senaryoda hata fırlatıldıktan sonra programın akışı 'catch' bloğuna girdiğini varsayalım. // Dolayısıyla arada 'Stack-Unwinding' mekanizması çalışacak ve 'outF' nesnesi için Dtor. çağrılacak. Bu da // ilgili dosyanın kapatılmasını sağlayacak. Fakat artık dosyaya "The Value is : " yazıldığı için programın // durumunda bir değişiklik OLDU. Öte yandan dosya başarı ile kapatıldığı için de kaynak sızıntısı OLMADI. >>>> 'Strong Guarantee' : Bu garanti türü, 'Basic Guarantee' türünün sağladığı bütün garantileri sunmaktadır. Ek olarak programın durumunda da bir DEĞİŞİKLİK OLMAYACAĞINI GARANTİ ETMEKTEDİR. BİR DİĞER DEYİŞLE BİZİM FONKSİYONUMUZ HİÇ ÇAĞRILMAMIŞ GİBİ OLACAKTIR eğer fonksiyonumuzun bloğundan bir hata gönderilirse. Yani ya işini gör, başarıyla tamamla ya da hiçbir şey yapılmamış durumda bırak. 'Commit or roll-back'. Son olarak program tutarlı, devam edebilecek durumda, OLMALIDIR. * Örnek 1, // some codes here... void f(const char* n) { int temp = g(); // 'g()' fonksiyonunun bir hata fırlatma ihtimaline karşı geri dönüş değerini bir değişkende sakladık. Böylelikle // bir hata göndermesi durumunda programın akışı bu fonksiyondan çıkacağı için dosyaya da bir şey YAZMADIĞIMIZ // İÇİN programımızın durumu da KORUNMUŞ OLDU. file outF(n, "w"); // 'file' bir sınıf ismi olduğunu varsayalım. if(outF.is_open()) { outF.put("The value is : "); outF.put(temp); // Programın akışı buraya geldiyse artık eminiz ki 'g()' fonksiyonumuz bir hata fırlatmadı. } } // İşte yukarıdaki senaryoda ne bir kaynak sızıntısı meydana geldi ne de programın durumunda bir değişiklik oldu. >>>> 'No-throw Guarantee' : Bu garanti türü ise fonksiyonun işini yapma garantisi vermekte. Fonksiyonun bloğundan bir hata gönderilirse bile kendi yakalayıp işini çözecektir. FONKSİYONDAN DIŞARI HATA GÖNDERİLMEYECEĞİ GARANTİ ALTINDADIR. >>> Bir fonksiyonun exception-safe durumlarını belirtme yolları, >>>> Fonksiyonun bildiriminin sonuna 'noexcept' anahtar sözcüğünü eklemek : Artık iş bu fonksiyonumuz 'No-throw Guarantee' tip bir garanti sunmaktadır. İki tip kullanımı vardır. Eğer çalışma zamanında bu garanti çiğnenir ise 'std::terminate' fonksiyonu çağrılmaktadır. Böylelikle 'std::terminate' fonksiyonunun çağrıldığı dördüncü yer de burası olmuş oldu. Bu anahtar sözcük hem bildirimde hem de tanımda yazılacak. 'Function Overloading' tetiklemez. >>>>> 'Specificaer' olarak kullanımı: * Örnek 1, void func(int)noexcept; // Bu fonksiyon excetion göndermeme garantisi vermektedir. * Örnek 2, void func(int)noexcept(constant_expression); // Derleme zamanında 'constant_expression' olan ifadenin değeri hesaplanır. Eğer, // i. 'true' gelmesi durumunda ilgili ifade " void func(int)noexcept(true); " halini alır ki bu da exception // göndermeme GARANTİSİ VERİYOR, demektir. // ii. 'false' gelmesi durumunda ilgili ifade " void func(int)noexcept(false); " halini alır ki bu da exception // göndermeme GARANTİSİ VERMİYOR, demektir. Yani bir exception gönderilebilir. // # Özetle # // i. void func(int)noexcept; / void func(int)noexcept(true); => Exception GÖNDERMEME GARANTİSİ VAR. // ii. void foo(int); / void foo(int)noexcept(false); => Exception GÖNDERMEME GARANTİSİ YOK. >>>>> Operator olarak kullanılması: Derleme zamanı operatörüdür, tıpkı 'sizeof' gibi tıpkı 'decltype' gibi. Argüman olarak bir FONKSİYON ÇAĞRISI ALMAKTADIR. Eğer iş bu fonksiyon hata gönderme garantisi veriyorsa, ilgili operatörümüz 'true' değer döndürmekte. Aksi halde 'false'. * Örnek 1, #include void func()noexcept {} void foo() {} int main() { /* # OUTPUT # Is func guarantees ? : 1 Is foo guarantees ? : 0 */ constexpr auto isFuncGuarantees = noexcept(func()); constexpr auto isFooGuarantees = noexcept(foo()); std::cout << "Is func guarantees ? : " << isFuncGuarantees << "\n"; std::cout << "Is foo guarantees ? : " << isFooGuarantees << "\n"; } * Örnek 1, her iki kullanım biçiminin ortak potada eritilmesi: //.. int func(); // Exception gönderme ihtimali var. // Eğer 'func' fonksiyonunun exception gönderme garantisi var ise benim de var. // Böyle bir garantisi yok ise benim de yok. int foo()noexcept(noexcept(func())); /* | | | |_ Buradaki kullanım şekli de 'operator' olarak. |̲ Buradaki kullanım şekli 'specifier' olarak. */ void f1(); void f2(); void f3()noexcept( noexcept(f1()) && noexcept(f2()) ); * Örnek 2, Verilen garantinin çiğnenmesi senaryosu, #include void foo() { throw 31; } void func()noexcept; int main() { /* # OUTPUT # terminate called after throwing an instance of 'int' */ func(); } void func()noexcept { foo(); } // Böylelikle 'std::terminate' fonksiyonu dört farklı yerde çağrılabilir; // i. 'Uncaught-exception' durumunda. // ii. Bir exception yakalanması fakat ilgili 'catch' bloğune girmeden önce 'Stack-Unwinding' sırasında Dtor. // fonksiyonunun tekrardan bir exception göndermesi durumunda. // iii. Bir exception fırlatıldığı görülmeden önce derleyicinin 'rethrow' deyimini görmesi durumunda. // iiii. Garanti etmiş olduğu 'exception' tipinin çalışma zamanında çiğnenmesi durumunda. >>>>> 'noexcept' garantisi veren bir fonksiyonun adresini tutan bir 'function pointer' a böyle bir garantiyi vermeyen fonksiyon adresini atayamayız. Fakat tam tersi bir atama geçerlidir. * Örnek 1, //.. void func()noexcept {} void foo(){} int main() { void (*pFunc)()noexcept = func; // Legal. Gerek fonksiyon gerek fonksiyon göstericisi aynı garantiyi sunmakta. void (*pFuncTwo)() = func; // Legal. void (*pFoo)()noexcept = foo; // Sentaks hatası. => error: invalid conversion // from ‘void (*)()’ to ‘void (*)() noexcept’ [-fpermissive] } >> 'Exception Specification' (Depreceated) : Eski kodlarda ilgili fonksiyonun hangi türden hata gönderebileceğini söylemek sistemiydi. ASLA VE ASLA Modern C++ ile KULLANMAMALIYIZ. * Örnek 1, void func(int) throw(std::out_of_range, std::runtime_error); // İlgili 'func' fonksiyonu 'out_of_range' ve 'runtime_error' tip hata gönderebilir. void foo(int) throw(); // İlgili 'foo' fonksiyonu bir hata göndermeyeceğini GARANTİ EDİYOR. // Yukarıdaki fonksiyonlar kenarlarında belirtilen kurala uymadıklarında, yani 'func' fonksiyonu başka türden bir hata gönderdiğinde // veya 'foo' fonksiyonu herhangi türden bir hata gönderdiğinde, derleyici 'std::unexpected' fonksiyonunu çağırmaktaydı. Bu da // 'std::terminate' fonksiyonunu, o ise 'abort()' fonksiyonunu çağırmaktaydı. >> Function Try-Block: Bir fonksiyonun gövdesinden gönderilecek hataları yakalamanın en garantili yolu, o fonksiyonun kodlarını 'try' bloğu içerisine almak ve yeteri kadar 'catch()' blokları ile desteklemektir. * Örnek 1, //.. void foo() { try{ // all function code... } catch(...) { //.. catch codes. } } // Böylelikle ilgili fonksiyonumuzun gönderme ihtimali olan her tür hatayı yakalayabiliriz. Peki ilgili fonksiyonumuz bir Ctor. // fonksiyonu olsaydı ne olacaktı? İkinci örneği inceleyelim. * Örnek 2, #include class Member { public: Member() { std::cout << "Member Ctor.\n"; throw 31; } }; class Myclass{ public: Myclass() : mx() { // Eğer programın akışı buraya gelmişse, ilgili sınıfımızın veri elemanları hayata gelmiş demektir velev ki onların // Ctor. fonksiyonları da bir exception fırlatmadıysa. Eğer onlar da fırlatmış ise onları burada yakalamamız MÜMKÜN // DEĞİLDİR. Olsa olsa nesneyi oluşturduğumuz yerde yakalayabiliriz. try { // some other codes. } catch(...) { std::cout << "Myclass Ctor. içinde hata yakalandi.\n"; } } private: Member mx; }; int main() { /* # OUTPUT # Member Ctor. main işlevi içinde hata yakalandi. */ try{ Myclass myClassX; } catch(...) { std::cout << "main işlevi içinde hata yakalandi.\n"; } // Peki veri elemanlarının göndermiş olduğu hataları, sahibi olduğu sınıfın 'Ctor.' fonksiyonu içerisinde yakalamanın bir yolu // var mıdır? El-cevap : Funciton Try-Block mekanizması işte bu soruya cevaptır. } * Örnek 3, Function Try-Block kullanımı. Normal bir fonksiyon için bu mekanizmayı kullanmanın pek de bir esprisi yoktur. Asıl faydası yukarıdaki probleme çözüm olmaktır. // Genel geçer fonksiyonlarda kullanımı: int func(int q) try{ // Bu alan hem 'func' fonksiyonunun bloğu hem de 'try' bloğu. Dolayısıyla bu blok içerisinde tanımlanan isimleri, // aşağıdaki 'catch' bloğu içerisinde kullanamayız. // Sadece fonksiyonun parametresi olan ismi hem burada hem de 'catch' bloğunda kullanabiliriz. // Hem burada hem de 'catch' bloğu içerisinde 'return' deyimini KULLANABİLİRİZ. } catch(int x) { } // Sınıfların Ctor. fonksiyonları için kullanımı: #include class Member { public: Member() { std::cout << "Member Ctor.\n"; throw 31; } }; class Myclass{ public: Myclass() try : mx() { /* // Artık blok içerisindeki bu ikinci kısım lüzumsuz hale geldi. try { // some other codes. } catch(...) { std::cout << "Myclass Ctor. içinde hata yakalandi.\n"; } */ } catch(...) { std::cout << "Myclass Function Try-Block içinde hata yakalandi.\n"; // Artık burada bizler ya programı sonlandırmalıyız ya ya ilgili hatayı 'translate' etmeliyiz ya da hatayı // 're-throw' etmeliyiz. Eğer hiç bir şey yapmadan bırakırsak, derleyici aynı hatayı 're-throw' EDİYOR. } private: Member mx; }; int main() { /* # OUTPUT # Member Ctor. Myclass Function Try-Block içinde hata yakalandi. main işlevi içinde hata yakalandi. */ try{ Myclass myClassX; } catch(...) { std::cout << "main işlevi içinde hata yakalandi.\n"; } // Çıktıdan da görüldüğü üzere ilgili Ctor. fonksiyonuna ait Function-Try Block içerisinde yakalanan hataya // ellemediğimiz için derleyici tarafından 're-throw' edilmektedir. } * Örnek 4, //.. class Myclass{ public: explicit Myclass() try : m_x{31} { std::cout << "Myclass::Myclass(int " << m_x << ") was called." << std::endl; throw 62; } catch(int) { std::cout << "An exception has been caught!!!" << std::endl; } friend std::ostream& operator<<(std::ostream& os, const Myclass& other) { return os << "[" << other.m_x << "]\n"; } private: int m_x{}; }; class Other{ public: inline static Myclass myClass{}; }; int main() { /* # OUTPUT # Myclass::Myclass(int 31) was called. An exception has been caught!!! terminate called after throwing an instance of 'int' */ return 0; } > Sınıflar (devam) : >> RTTI (Run Time Type Identification/Information) / (Çalışma Zamanında Tür Belirlenmesi) : Normal koşullarda, çalışma zamanında bir türün ne olduğunu bilmemize GEREK YOK. Çünkü Nesne Yönelim Programlamaya uygun kod yazarken, aşağıdaki kodların ne olacağını bilmeden kod yazıyoruz. Örneğin, ilgili fonksiyona gelen nesne 'Mercedes' türünden bir 'Car' ise 'open_sunroof()' fonksiyonunu aç, şeklinde bir sorumuz olsun. Bu durumda biz OOP paradigmasından bir nevi uzaklaşmış oluyoruz. Çünkü her bir 'Mercedes' bir 'Car' olduğundan böyle bir sorgulamaya gerek yok. 'Car' sınıfını yazarken zaten 'open_sunroof()' şeklinden bir fonksiyonu belirtmiş olmalıyız ki alttaki kodlar da bu fonksiyonu 'overload' edebilsin. Eğer bizler bu soruyu soruyorsak bir yerde bir sorun var demektir. Velevki böyle bir sorgulamayı YAPMAK ZORUNDAYSAK, BAŞKA BİR ÇAREMİZ DE YOKSA; aşağıdaki örneği inceleyelim. * Örnek 1, Aşağıdaki örnek 'Tanımsız Davranış' örneğidir. // CAR.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); // Eger Mercedes ise cam tavan acilsin. auto myMercedes = static_cast(carPtr); myMercedes->open_sunroof(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Audi case. The Audi just started. The sunroof of Mercedes just opened. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- */ for(int i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); delete p; } // Gördüğünüz gibi 'create_random_car()' fonksiyonundan 'Audi' nesnesi elde edildi fakat // çalışma zamanında 'Mercedes' sınıfına 'cast' edilmeye çalışıldı. 'Tanımsız Davranış' a neden OLDUK. // Tabiri caiz ise tavanı açılamayan bir araç için tavanı açmaya çalıştık. } >> C++ dilinde bir nesnenin Dinamik Türünün ne olduğunu anlamaya yönelik iki adet operatör vardır. Bunlardan ilki 'dynamic_cast', diğeri ise 'typeid' operatörüdür. 'typeid' operatörünü kullanmak için 'type_info' başlık dosyasını da eklememiz gerekiyor. >>> 'dynamic_cast' : Bir 'downcasting' işleminin güvenli olup olmadığını SINAMAKTADIR. Çalışma zamanı ile alakalıdır. Derleme zamanı ile bir ilgisi YOKTUR. Bu operatörün operandı olan sınıfın bir 'Polymorphic' sınıf OLMASI GEREKMEKTEDİR. Aksi halde sentaks hatası alırız. Eğer dönüşüm başarısız olursa 'nullptr' değerini elde ederiz, dönüşümün gösterici ile yapıldığı durumlarda. Referans ile yapılan dönüşümlerin başarısız olmaları durumda ise 'exception' fırlatacaktır. Fırlatılan hatanın türü ise 'bad_cast'. * Örnek 1, #include class Base{ public: virtual ~Base() {} }; class Der : public Base {}; class DerTwo : public Der {}; class Myclass { public: virtual ~Myclass() {} }; void func(Base* basePtr) { /* if( auto myPtr = dynamic_cast(basePtr) ) std::cout << "Evet, Der nesnesi.\n"; else std::cout << "Hayir, Der nesnesi değil.\n"; */ Der* myPtr = dynamic_cast(basePtr); if(myPtr != nullptr) std::cout << "Evet, Der nesnesi.\n"; else std::cout << "Hayir, Der nesnesi değil.\n"; } int main() { /* # OUTPUT # Hayir, Der nesnesi değil. Evet, Der nesnesi. Evet, Der nesnesi. */ Base myBase; Der myDer; DerTwo myDerTwo; Myclass myClass; func(&myBase); func(&myDer); func(&myDerTwo); // func(&myClass); // error: cannot convert ‘Myclass*’ to ‘Base*’ for argument ‘1’ to ‘void func(Base*)’ } * Örnek 2, // CAR.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); // Eger Mercedes ise cam tavan acilsin. if( auto mercedesPtr = dynamic_cast(carPtr) ) mercedesPtr->open_sunroof(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Car case. The Car just started. The Car just started running. The Car just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Car case. The Car just started. The Car just started running. The Car just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- */ for(int i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); delete p; } // Çıktıtan da görüldüğü üzere sadece 'Mercedes' olan, ki 'Mercedes' sınıfından türetilenler de birer // 'Mercedes' kabul edildiklerinden onlarınki de açıldı, araçların cam tavanı açılmış oldu. } * Örnek 3, Yukarıdaki örnekteki aynı başlık dosyasını ele alalım. //.. // main.cpp #include #include "CAR.hpp" void car_game(Car& carRef) { carRef.start(); Mercedes& mRef = dynamic_cast(carRef); mRef.open_sunroof(); carRef.run(); carRef.stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Audi just started. Hata yakalandi. => std::bad_cast */ try{ Mercedes mx; car_game(mx); Mercedes_S500 mx500; car_game(mx500); Audi ax; car_game(ax); } catch(const std::bad_cast& ex) { std::cout << "Hata yakalandi. => " << ex.what() << "\n"; } } >>>> Peki derleyici 'dynamic_cast' operatörü karşısında nasıl bir kod üretiyor? Biz sadece bir gösterici/referams göndermemize rağmen nasıl oluyor da türleri arası 'downcasting' olup olmadığını anlıyor? El-cevap: 'Polymorphic' sınıflarda bir adet Sanal Gösterim Tablosu vardır. Sanal fonksiyonumuzdaki sanal fonksiyonlar, bu tabloya, bildirimlerindeki sıra ile indekslendiğini düşünelim. Ayrıca, yine bu tip sınıf türünden olan her bir nesnenin için de bu tabloyu gösteren ama gömülü vaziyette duran bir sanal fonksiyon göstericisi vardır. Derleyici çalışma zamanının başında 'polymorphic' türler için 'type_info' sınıf türünden nesneler oluşturmakta. Örneğin, bizim 'Car' kalıtım hiyerarşisinde 50 adet türetilmiş sınıf var ise derleyici 50 adet 'type_info' sınıf türünden nesne oluşturmakta. Dolayısıyla 'Mercedes' için ayrı bir 'type_info' nesnesi, 'Audi' için ayrı, 'Mercedes_S500' için ayrı, 'Tesla' için ayrı bir nesnesi mevcut. Sanal fonksiyon tablosunun belirli bir indeksine de, ki genel olarak bu SIFIRINCI İNDİS, ilgili sınıfın 'type_info' nesnesinin adresini koyuyor. * Mülakat sorusu : Büyük bir 'Car' hiyerarşisine sahip olduğumuzu düşünelim. " dynamic_cast(carPtr) " veya " typeid(*carPtr) == typeid(Mercedes) " şeklinde yöntemler ile de sorgulama yapacağız. Hangi sorgulama yöntemi daha az MALİYETLİDİR? >>> 'typeid' operatörü : İş bu operatör operand olarak bir Tür İsmi ve bir deyim almaktadır. Bu operatör 'typeinfo' başlık dosyasında bildirilen 'type_info' sınıfından bir nesneye 'const' bir referans haline dönmektedir. Buradan da hareket ile '.' operatörünü kullanırsam, yine bu ifade ile birlikte, 'type_info' sınıfındaki 'const member-functions' lara erişeceğim. 'Default Ctor.' OLMADIĞI İÇİN ilgili 'type_info' sınıfından bu operatör vasıtası ile nesne üretebiliriz. Yine aynı sınıfın 'Copy Ctor.' fonksiyonu da 'delete' edilmiştir. Her türe karşılık bir 'type_info' nesnesi vardır. Bu 'type_info' sınıfında bizi ilgilendiren en önemli fonksiyonlar; 'operator==' ve 'operator!=' fonksiyonlarıdır. * Örnek 1, #include #include int main() { /* # OUTPUT # x is not a int. */ // std::type_info tx; // error: no matching function for call to ‘std::type_info::type_info()’ // std::type_info tx(typeid(double)); // [GCC] error: ‘std::type_info::type_info(const std::type_info&)’ is private within this context // [MSVC] error C2280: 'type_info::type_info(const type_info &)': attempting to reference a deleted function // [clang] error: calling a private constructor of class 'std::type_info' int x = 100; if(typeid(x) == typeid(unsigned int)) std::cout << "x is a int.\n"; else std::cout << "x is not a int.\n"; } >>>> Bu sınıfın bir diğer 'const' üye fonksiyonu da '.name()' isimli fonksiyonudur ki 'const char*' döndürmekte olup yazdırılan yazı derleyiciye bağlıdır. İlgili türün ismini yazdırmaktadır. #include #include int main() { /* # OUTPUT # clang | Gcc | MSVC x is a [i] : 100 | x is a [i] : 100 | x is a [int] : 100 y is a [d] : 100.001 | y is a [d] : 100.001 | y is a [double] : 100.001 */ int x = 100; double y = 100.001; std::cout << "x is a [" << typeid(x).name() << "] : " << x << "\n"; std::cout << "y is a [" << typeid(y).name() << "] : " << y << "\n"; /* # OUTPUT # bool: 1 char: 1 int: 4 unsigned int: 4 long: 4 __int64: 8 float: 4 double: 8 long double: 8 */ std::cout << typeid(false).name() << ": " << sizeof(false) << "\n"; std::cout << typeid('A').name() << ": " << sizeof('A') << "\n"; std::cout << typeid(12).name() << ": " << sizeof(12) << "\n"; std::cout << typeid(12U).name() << ": " << sizeof(12U) << "\n"; std::cout << typeid(12L).name() << ": " << sizeof(12L) << "\n"; std::cout << typeid(12LL).name() << ": " << sizeof(12LL) << "\n"; std::cout << typeid(12.21f).name() << ": " << sizeof(12.21f) << "\n"; std::cout << typeid(12.21).name() << ": " << sizeof(12.21) << "\n"; std::cout << typeid(12.21L).name() << ": " << sizeof(12.21L) << "\n"; /* # OUTPUT # char: 1 int: 4 */ char c = 18; std::cout << typeid(c).name() << ": " << sizeof(c) << "\n"; std::cout << typeid(+c).name() << ": " << sizeof(+c) << "\n"; } >>>> Bu operatör de 'Unevaluated Context' kategorisindedir. Yani parantezi içerisindeki deyim işlememektedir. >>>>> Unevaluated Context: Bir deyim düşününki sonucunda bir işlem üretilmesin. Örneğin, C dili ile ortak özellikteki 'sizeof' operatörü. Operandı olan ifadede bir işlem gerçekleştirmemektedir. Benzer şekilde 'decltype' da aynı özelliktedir. * Örnek 1, #include int main() { /* # OUTPUT # x : 100 x : 100 */ int x = 100; std::cout << "x : " << x << "\n"; auto y = sizeof(++x); std::cout << "x : " << x << "\n"; } * Örnek 2, #include int main() { /* # OUTPUT # x : 200 x : 200 */ int x = 200; std::cout << "x : " << x << "\n"; decltype(x++) y; // 'x++' deyimi sağ taraf olduğundan ve türü 'int' olduğundan 'decltype' ile çıkarım yapılan tür 'int'. // Eğer '++x' kullanılsaydı sentaks hatası alacaktık. Çünkü bu ifadenin değeri L-value ve türü 'int'. // Dolayısıyla çıkarım yapılan tür de 'int&'. Sol taraf referansın da ilk değer alması zorunlu. // (error C2530: 'y': references must be initialized). std::cout << "x : " << x << "\n"; } * Örnek 1, #include int main() { /* # OUTPUT # x : 200 int x : 200 */ int x = 200; std::cout << "x : " << x << "\n"; std::cout << typeid(++x).name() << "\n"; std::cout << "x : " << x << "\n"; } >>>> Bu operatörün operandının bir 'polymorphic tür' e ilişkin olma ZORUNLULUĞU YOKTUR. Fakat 'dynamic_cast' operatörü için böyle bir zorunluluk vardır. Bu operatör 'Polymorphic' olmayan sınıfları operandı olarak aldığında 'Statik Tür' bilgisini bize döndürürken, 'Polymorphic' sınıfları operandı olarak aldığında ise 'Dinamik Tür' bilgisini bize döndürmektedir. * Örnek 1, #include class Base { public: virtual ~Base() {} }; class Der : public Base {}; class Myclass{}; int main() { /* # OUTPUT # class Der class Base class Myclass */ Der myDer; Base* myBasePtr = &myDer; std::cout << typeid(*myBasePtr).name() << "\n"; // İlgili 'Base' sınıfımız 'Polymorphic' olduğundan 'Dinamik Tür' bilgisi döndürüldü. Base myBase; myBasePtr = &myBase; std::cout << typeid(*myBasePtr).name() << "\n"; // İlgili 'Base' sınıfımız 'Polymorphic' olduğundan 'Dinamik Tür' bilgisi döndürüldü. Myclass myClass; std::cout << typeid(myClass).name() << "\n"; } * Örnek 2, // CAR.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); // Eger Mercedes ise cam tavan acilsin. if( typeid(*carPtr) == typeid(Mercedes) ) { // Eğer programın akışı buraya geldiyse, 'carPtr' bir 'Mercedes' sınıfı demektir. // Fakat unutulmamalıdır ki 'carPtr' ancak ve ancak 'Mercedes' ise bu bloktaki kodlar çalışacaktır. // 'dynamic_cast' operatoründe olduğu gibi alttaki türemiş sınıflar bu eşitliği SAĞLAMAMAKTADIR. // Çünkü her tür için bir 'typeid' var. // Bu dönüşümü yapmak zorundayız aksi halde derleme zamanındaki isim arama gereği 'open_sunroof' // fonksiyonu 'Car' sınıfında aranacaktır. auto* ptr = static_cast(carPtr); ptr->open_sunroof(); } carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- */ for(int i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); delete p; } // Çıktıtan da görüldüğü üzere sadece ve sadece 'Mercedes' olan araçların cam tavanı açılmış oldu. } /*============================================================================================================*/ (22_29_11_2020) >> RTTI (devam) : >>> Bir önceki kursun sonundaki soruya el cevap: typeid() operatörünün kullanımı DAHA AZ MALİYETLİ. Çünkü bu operatör sadece " Bu Mercedes mi? " diye bakıyor. Fakat 'dynamic_cast' operatörü ekstradan 'Mercedes' sınıfından türetilmiş diğer sınıflara da bakıyor. Bir nevi hiyerarşiyi baştan aşağı inceliyor, diyebiliriz. Dolayısla daha çok maliyeti de beraberinde getirmektedir. >>> 'typeid()' operatörünün operandının bir 'nullptr' tutan göstericinin 'derefence' edildiği bir deyim olması durumunda, ilgili operatörümüz 'std::bad_typeid' türünden HATA GÖNDERİR. * Örnek 1, #include #include #include "CAR.hpp" int main() { /* # OUTPUT # Hata yakalandi. => std::bad_typeid */ try{ Car* ptr = nullptr; std::cout << typeid(*ptr).name() << "\n"; } catch(const std::bad_typeid& ex) { std::cout << "Hata yakalandi. => " << ex.what() << "\n"; } } >>> 'polymorphic' tip her bir sınıf için 'RTTI' desteğinin sağlanması hususunda sınıf başına bir adet 'type_info' sınıf türünden nesne oluşturuluyor. Eğer bizler bu mekanizmadan yararlanmayacaksak, derleyicinin ayarlarından bunu kapatabiliriz. > 'Exception Handling' (devam) : Statik ömürlü nesnelerin hayata gelmesi sırasında DIŞARIYA bir hata gönderilirse BU HATAYI HİÇ BİR ŞEKİLDE YAKALAYAMAYIZ. Bir diğer deyişle global isim alanındaki nesnelerin veya sınıfların 'static' veri elemanı olan nesnelerin Ctor. fonksiyonlarından dışarıya fırlatılan hataları YAKALAYAMAYIZ. Çünkü bu tip nesneler 'main' fonksiyonundan da önce çağrılmaktadırlar. Sadece 'function try-block' kullanarak yakalama yapabiliriz. > Şablonlar ve Jenerik Programlama: Tek kodun birden fazla türe hizmet etmesi durumudur. Temel olarak iki farklı grupta incelenir. Bunlar 'function template' ve 'class template' ki bunlar sırası ile fonksiyon şablonları ve sınıf şablonları şeklinde de isimlendirilebilir. Modern C++ ile dile iki grup daha eklenmiş oldu ki bunlar sırasıyla 'variable template' ve 'alias template' şeklinde gruplardır. >> Bir şablon oluşturmak için 'template' anahtar sözcüğü ve bunu takip eden bir çift '<>' atomu yazmamız gerekmektedir. Bu bir çift '<>' atomunun arasına da bizim şablonumuzun parametre bilgileri geçilmektedir. * Örnek 1, sentaksı anlatan temel bir örnek //.. template // Açısal parantez içerisinde yazılanlar birer 'Template Parameter'. Örneğin, buradaki 'T' bir'template parameter'. >>> İş bu şablon parametreleri ise kendi içinde dört gruba ayrılırlar. Bunlar sırasıyla 'Template Type Parameter', 'Template Non-Type Parameter', 'Template Parameter Pack' ve 'Template Template Parameter' şeklindedirler. >>>> 'Template Type Parameters' : Aşağıdakiler bu gruba girmektedir. >>>>> Şablon parametreleri ise bir türe karşılık gelmektedir. Bir diğer deyişle 'T' bir nevi 'placeholder' görevi görmektedir. Derleyici bu şablonları açarken bu 'T' yerine gerçek türler yazmakta. Örneğin, 'int', 'int*', 'double' vs. Benzer şekilde şablon parametrelerinin yazıldığı alandaki 'class' anahtar sözcüğü ise ilgili 'T' nin bir TÜR OLDUĞUNU söylemektedir, onun bir sınıf türü olduğunu DEĞİL. Dolayısla alternatif olarak bu kelime yerine de 'typename' yazılmaktadır. 'T' nin ayrıca bir 'identifier' olduğunu da unutmayalım. İstersek 'T' yerine 'mustafa' yı da kullanabiliriz. Konvensiyonel olarak 'T', 'U' gibi karakterler kullanılmaktadır. >>>>> Şablon parametreleri istediğimiz kadar olabilir, bir tane olacak diye bir zorlama yoktur. Dolayısıyla bu tip şablonlara da birden fazla tür bilgisini geçmeliyiz, duruma göre. * Örnek 1, //.. template // Artık bu şablon ne için kullanılacak ise iki adet tür bilgisi geçmemiz gerekiyor. >>>>> Derleyicinin derleme zamanında 'T', 'U' gibi harflerin yerine koyacağı gerçek türler için 'Template Type Arguments' denmektedir. Yani Şablon Tür Argümanları şeklinde de söyleyebiliriz. Bu durumda bu 'T' ve 'U' harfleri için 'Template Parameter' denirken, bu türlere karşılık gelen gerçek türlere ise 'Templete Type Arguments' denmektedir. >>>> 'Templete Non-Type Parameter' : Derleyici şablona bakarak yazacağı kodlarda sabitler kullanmasını istiyoruz. İşte 'Template Parameter' olarak bu sabitlere karşılık gelen isimler, 'Template Non Type Parameter' olarak anılırlar. Sabitin türünü yazıp bir isim vererek bu işi yapıyoruz. Tam sayı sabiti veya adres kullanabiliyoruz, gerçek sayı sabiti kullanamıyoruz. * Örnek 1, //.. template // Artık 'm' bir 'Template Non Type Parameter'. 'm' isminin olduğu her yerde bir sabit kullanılacak. 'm' yerine geçecek // argümanlar birer 'Constant Expression' olmak zorundadır. * Örnek 2, //.. template // LEGAL DEĞİL. >>>> Derleme zamanında derleyici 'Template Type Parameter' ve 'Template Non-Type Parameter' için neyin karşılık geleceğini üç farklı araç seti ile öğrenir. Bunlar, >>>>> Bizlerin açık açık derleyiciye bu bilgiyi geçmemiz. Kısaca buna 'Explicit Template Argument' de denmektedir. >>>>> Çıkarım mekanizması kullanılabilir. Derleyici 'T' ye ve/veya 'm' neyin ve nelerin karşılık geleceğini koda bakarak çıkarım yapmakta ('deduction'). Fakat C++17 ye kadar çıkarımlar sadece fonksiyon şablonları için çalışırken, C++17 ile birlikte sınıf şablonları için de çalışır oldu. Bu duruma da 'CTAD' denmektedir, yani 'Class Template Argument Deduction'. * Örnek 1, //.. int main() { int x = 12, y = 23; swap(x,y); // 'Explicit Template Argument'. Yani bizler derleyiciye açık açık bildirdik 'T' ye karşılık gelecek ilgili türleri. double xx = 12.21, yy = 23.32; swap(xx,yy); // Derleyici çıkarım mekanizmasını kullanaraktan ilgili 'T' parametleri için çıkarımda bulundu. } >>>>> Varsayılan Şablon Argümanı kullanılarak derleyici neyin karşılık geleceğini anlıyor. * Örnek 1, //.. template // Derleyici varsayılan 'Template Type Argument' olarak 'int' ve 'double' türlerini alacaktır // eğer aksi belirtilmez ise. Yani 'T' yerine 'int', 'U' yerine de 'double' gelecektir. >>>> Hadi gelin bu iki şeyi bir örnek ile pekiştirelim: * Örnek 1, //.. int main() { std::array ar; // i. 'Template Type Parameter' olan 'T' yerine 'Template Type Argument' olarak 'int' bilgisi geçilmiştir. // Şablonda 'T' geçen her yerde 'int' kullanılacaktır. // ii. 'Template Non Type Parameter' olarak da '10' sabiti 'Template Non Type Argument' olarak geçilmiştir. // Şablonda artık 'Template Non Type Parameter' geçen her yerde '10' sabiti kullanılacaktır. Bizler gerek // 'Template Type Parameter' bilgisini gerek 'Template Non-Type Parameter' bilgisini derleyiciye kendi elimiz // ile geçmiş olduk('Explicit Template Argument'). } >>> 'Template Parameter' olarak sadece 'Template Type Parameter' gelebilir, sadece 'Template Non-Type Parameter' gelebilir veya hem 'Template Type Parameter' gelebilir, hem de 'Template Non-Type Parameter' gelebilir. * Örnek 1, //.. template // Burada 'T' yerine bir tür, 'm_size' yerine ise 'size_t' türünden bir SABİT gelecektir. * Örnek 2, template * Örnek 3, template >> Şablon kodlar tipik olarak Başlık Dosyasında bulunur. ODR kuralını da İHLAL ETMEZLER. >> Fonksiyon Şablonlarının detaylı incelenmesi: Fonksiyon şablonları derleyiciye fonksiyon kodları yazdıran şablonlardır. Yukarıdaki gibi bir 'template' başlığından sonra fonksiyonun bildirimi gelmektedir. * Örnek 1 //.. template // T : Template Type Parameter T func(T x, T y) { // İstersek fonksiyonun geri dönüş değeri için kullanılan veya fonksiyonun parametre parantezi içerisinde kullanılan 'T' // için gerçek türleri yazabiliriz. Böylelikle derleyici ona göre fonksiyon şablonunu açacaktır. } // UNUTULMAMALIDIR Kİ BU BİR FONKSİYON ŞABLONUDUR, BİR FONKSİYON KODU VS. DEĞİLDİR. // YİNE UNUTULMAMALIDIR Kİ ÇALIŞMA ZAMANINDA BU KOD ÇAĞRILMAYACAKTIR. DERLEYİCİNİN ŞABLONU AÇARAK YAZMIŞ OLDUĞU GERÇEK FONKSİYON // ÇAĞRILACAKTIR. >>> İlk aşamada derleyici sadece fonksiyon şablonunu gördüğünden, yani 'Template Type Parameter' gibi şablon parametrelerine hangi türün geçileceğini daha bilmediği anlarda, çok temel seviyede bir sentaks hatalarını inceler. Örneğin, açılan parantezler kapatılmış mı veya bloklar birbiri ile uyumlu mu vs. Ek olarak birde isim arama yapacağı için bulunamayan isimler için de sentaks hatası verecektir. * Örnek 1, template // T : Template Type Parameter T func(T x, T y) { x.foo(); y.func(); return x+y; // Derleyici yukarıda 'x' ve 'y' isimlerinin neye karşılık geldiğini kabaca bilmektedir. Fakat 'foo' ve 'func' isimlerinin // gerçekten 'x' içerisinde olup olmadığını bilemez. Bu fonksiyona öyle bir sınıf türünden nesne geçilir ki bünyesinde 'foo' // fonkiyonlarını barındırır, 'func' fonksiyonlarını barındırır, 'operator+' fonksiyonunu barındırır vs. x = a+b; // Derleyici burada bir sentaks hatası verecektir çünkü 'a' ve 'b' isimlerinin neye ait olduğunu bilememiştir. } >>> İkinci aşamada 'Template Type Parameter' olarak hangi gerçek türlerin geldiğini bildiği vakit artık buna yönelik bir kontrol daha yapacaktır. * Örneğin, template // T : Template Type Parameter T func(T x, T y) { // Artık derleyici 'T' yerine hangi gerçek türün geldiğini bildiğinden; x.foo(); // i. Bu satırdaki kod sentaks hatası verdirtecek. y.func(); // ii. Benzer şekilde bu satırdaki kod da sentaks hatası verdirtecek. return x+y; } int main() { func(12,21); // 'Template Type Parameter' olarak 'int' türünün geleceğini bizler 'explicit' olarak söylemiş olduk. } >>> Fonksiyon şablonlarında türlerin sadece ve sadece 'T' türüne bağlı olmaları şeklinde bir zorunlulukta yoktur. * Örneğin, template // T : Template Type Parameter double func(T x, int a) // Gördüğümüz gibi ikinci parametre bir 'int'. { // Burada 'T' türüne karşılık 'int' türü geliyorsa, 'p' nin türü de 'int*' olmaktadır. // Burada 'T' türüne karşılık 'int*' türü geliyorsa, 'p' nin türü de 'int**' olmaktadır. T* p; } >>> 'Template Type Argument' in ne olduğuna dair bir çıkarım söz konusu olduğunda bu süreç iki farklı şekilde sonuçlanabilir. Ya sentaks hatası alacağız ya da bir türe dair çıkarımda bulunacağız. >>>> Tür çıkarımı yapılırken sentaks hatası olma durumu: Bu tip sentaks hatası da iki farklı nedenden dolayı gerçekleşebilir. Bunlardan ilki fonksiyon çağrı ifadesine bakarak yeterli bilgiye sahip olamaması. Bir diğer hata kaynağı ise fonksiyonun birden fazla parametre değişkeninin olması durumunda gerçekleşmektedir. Derleme zamanında derleyici 'T' nin 'int' olduğunu görürse ilgili şablonu da 'int' türüne göre açacaktır. * Örnek 1, Fonksiyon çağrı ifadesine bakarak yeterli bilgiye sahip olamaması: //.. template void func() { T tx; } int main() { func(); // Derleyici bu ifadeye bakarak 'T' yerine hangi türün geleceğini BİLEMEZ ve bize // " could not deduce template argument for T " minvalinde bir hata verir. // Artık bu durumda bizlerin ya şablonu değiştirmemiz ya da 'explicit' olarak // bir tür bilgisini geçmemiz gerekmektedir. func(); // Artık derleyici 'T' yerine 'double' geleceğini biliyor olmaktadır. } * Örnek 2, Fonksiyonun birden fazla parametre değişkenine sahip olması: template // Bu şablonda bir adet 'Template Parameter' vardır ki o da 'T'. void func(T x, T y) // Buradaki fonksiyon şablonunun ise iki adet 'Function Parameter' vardır ki bunlar 'x' ve 'y' dir. { T tx = x+y; } int main() { func(31, 13); // Burada derleyici birinci argümandan hareketle 'T' ye karşılık 'int' geldiğini anlayacaktır. // Yine ikinci argümandan hareketle 'T' ye yine 'int' geldiğini anlayacaktır. Zaten şablon parametresi olarak // da bir adet 'T' kullanıldığından herhangi bir problem de oluşmayacağından, şablon 'int' türüne göre açılacaktır. func(12, 1.2); // Burada derleyici birinci argümandan hareketle 'T' ye karşılık 'int' geldiğine hükmediyor. Yine ikinci argümandan // hareketle 'T' ye yine 'double' geldiğine hükmediyor. Zaten bir adet şablon parametresi olduğundan ve bunun iki // farklı türe karşılık gelmesi İMKANSIZ olduğundan, 'ambiguity' tip sentaks hatası alıyoruz.BURADAKİ PROBLEMİN // KAYNAĞI FONKSİYONUN YANLIŞ ARGÜMANLAR İLE ÇAĞRILMASINDADIR. } >>>>> 'Template Argument Deduction' Süreci : 'auto' anahtar sözcüğüne karşılık gelen tür çıkarımı ile bir istisna hariç birebir AYNIDIR. Derleyici 'auto', ki bu bir 'placeholder specifier', için bir tür elde edecek. 'auto' karşılığında gelen türün ne olması gerektiğini, değişkene ilk değer veren ifadeden yola çıkarak anlayacak. Değişen bir şey yok. Fonksiyona gönderilen argümandan 'T' nin ne olduğunu anlayacak. * Örnek 1, //.. template void func(); int main() { auto x = 10; // Burada '10' sabitine bakarak 'auto' ya karşılık gelen türü anlayacak. func(10); // Burada '10' a bakarak 'T' ye karşılık gelen türü anlayacak. } >>>>>> 'auto' anahtar sözcüğü ile yapılan tür çıkarımda üç farklı kural seti vardır. Bunlar, >>>>>>> 'auto' anahtar sözcüğünün yalın halde kullanılması, referans deklaratörü ile birlikte kullanılmaması: >>>>>>> 'auto' anahtar sözcüğünün bir adet referans deklaratörü ile birlikte kullanılması: >>>>>>> 'auto' anahtar sözcüğünün iki adet referans deklaratörü ile birlikte kullanılması: SAĞ TARAF REFERANS ANLAMINA GELMEMEKTEDİR. 'Forwarding Reference' anlamındadır. >>>>>> Fonksiyon Şablon Parametresinin sadece 'T' kullanılması, yani 'auto' anahtar sözcüğünün yalın halde kullanılmasına karşılık gelen senaryo: Aynı kural setidir. Burada çıkarım 'T' için YAPILMAKTADIR. * Örnek 1, template void func(T x) { } int foo(int); int main() { func(/**/); // Bu fonksiyona gönderilen argümanın türünden derleyici 'T' ye karşılık gelen türü anlıyor. func(10); // Argüman olan ifadenin türü 'int' olduğu için 'T' ye karşılık gelen tür de 'int' olacaktır. double dval = 2.3; func(dval); // Argüman olan ifade türü 'double' olduğundan 'T' ye karşılık gelen tür de 'int' olacaktır. const int cival = 123; func(cival); // Tıpkı 'auto' da olduğu gibi kendisinin 'const' olduğu nesneler tür çıkarımında kullanıldığında, // 'const' olma özelliği düşüyor. İşte bu yüzden argüman olan ifade 'const int' olmasına rağmen // 'T' ye karşılık gelen tür 'int' olacaktır. int ival = 123; int& rival = ival; func(rival); // Tıpkı 'auto' da olduğu gibi kendisinin 'reference' olduğu nesneler tür çıkarımında kullanıldığında, // 'reference' olma özelliği düşüyor. İşte bu yüzden argüman olan ifade 'int&' olmasına rağmen // 'T' ye karşılık gelen tür 'int' olacaktır. const int& crival = ival; func(crival); // Tıpkı 'auto' da olduğu gibi kendisinin 'const-reference' olduğu nesneler tür çıkarımında kullanıldığında, // 'const-reference' olma özelliği düşüyor. İşte bu yüzden argüman olan ifade 'const int&' olmasına rağmen // 'T' ye karşılık gelen tür 'int' olacaktır. int a[]{1, 2, 3, 4}; func(a); // Tıpkı 'auto' da olduğu gibi 'array-decay' gerçekleşmekte ve dizi ismi dizinin ilk elemanının adresine // dönmektedir, ki o da 'int*'. Dolayısıyla argüman olan ifade 'int*' olduğundan, 'T' ye karşılık gelen tür de // 'int*' olmaktadır. const ca[]{1, 2, 3, 4}; func(ca); // Tıpkı 'auto' da olduğu gibi 'array-decay' gerçekleşmekte ve dizi ismi dizinin ilk elemanının adresine // dönmektedir, ki o da 'const int*'. Argüman olan ifadenin kendisi 'const' olmadığından, sadece gösterdiği // değer 'const' olduğundan, 'const' özelliği DÜŞMÜYOR. Dolayısla 'T' ye karşılık gelen tür // 'const int*' olmaktadır. func("Can"); // Tıpkı 'auto' da olduğu gibi, argüman olan ifadenin türü 'const char[4]'. 'array-decay' mekanizmasından // dolayı 'cont char*' olmaktadır. Nesnenin kendisi 'const' olmadığından, tür çıkarımı da, yani 'T' ye // karşılık gelen tür de 'const char*' olacaktır. func(foo); // Tıpkı 'auto' da olduğu gibi 'function-to-pointer' dönüşümü gerçekleşmektedir. Dolayısıyla argüman olan // ifadenin türü 'int(*)(int)' şeklinde. İş bu nedenden ötürü de çıkarım yapılan tür, yani 'T' ye karşılık // gelen tür de 'int(*)(int)' olmaktadır. Burada 'foo' fonksiyonunun türü 'int(int)' şeklinde. Fakat // 'function-to-pointer' mekanizması ile 'int(*)(int)' türüne dönüşmektedir. } * Örnek 2, 'T' ye karşılık gelen türü teyit etme yöntemi: #include template class TypeTeller; // Bir sınıf şablonunun bildirimi, Forward Declaration. template void func(T x) { TypeTeller t; // 'incomplete' tür kullandığımız için derleyici hata verecek. Hata mesajında da 'T' ye karşılık gelen // türü de söylemiş olacak. } int foo(int); int main() { func(10); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] */ int a[]{1, 2, 3, 4}; func(a); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int * ] */ const ca[]{1, 2, 3, 4}; func(ca); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const int * ] */ func("Can"); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const char * ] */ func(foo); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int (__cdecl *)(int) ] */ } * Örnek 3, //.. template void func(T (*fPtr)(U)); // Burada derleyici 'T' ve 'U' için tür çıkarımı yapmaktadır. Bunlar kesinleştikten sonra 'fPtr' isimli fonksiyon // göstericisinin türü belli olmaktadır. Örneğin, 'T' için 'int' şeklinde ve 'U' için de 'double' şeklinde tür // çıkarımı yaparsa derleyicinin yazdığı iş bu fonksiyonun parametre değişkeni öyle bir fonksiyon göstericisi // olmalı ki "Geri Dönüş Değeri 'int' olan ve Parametresi 'double'" olan bir fonksiyonu göstermeli. { T tx; // 'tx' in türü 'int' olacak. U uy; // 'uy' nin de türü 'double' olacak. } >>>>>> Fonksiyon Şablon Parametresinin sadece 'T&' kullanılması, yani 'auto&' anahtar sözcüğünün kullanılmasına karşılık gelen senaryo: Aynı kural setidir. 'T' için çıkarım yapılacaktır ve çıkarım başarılı olursa yazılan fonksiyonun parametresi 'L-Value Referance'. * Örnek 1, template void func(T& r) { } int foo(double); int main() { func(10); // 'T' türünün çıkarımı 'int' olarak yapıldı. Fakat fonksiyon şablon parametresinin türü 'int&' olacağından, // yani bir 'L-Value Reference' olmasından ve bu tip referansların da 'R-Value Expression' lara // bağlanamamasından dolayı, SENTAKS HATASI ALIRIZ. int ival = 123; func(ival); // 'T' türü için 'int' yönünde bir çıkarım yapılacak. Fakat fonksiyon şablon parametresinin türü de 'int&' // olacaktır. 'L-Value Referance' tipler 'L-Value Expression' lara bağlanabileceği için hiç bir sorun yoktur. const int cival = 123; func(cival); // 'T' türü için 'const int' yönünde bir çıkarım yapılacak. Fakat fonksiyon şablon parametresinin türü de // 'const int&' olacaktır. 'L-Value Referance' tipler 'L-Value Expression' lara bağlanabileceği için hiç bir // sorun yoktur. int a[5]{}; func(a); // 'array-decay' mekanizması çalışmayacaktır. İlgili argümanın türü 'int[5]' olduğundan, 'T' için de // 'int[5]' şeklinde tür çıkarımı yapılacaktır. Fakat derleyicinin yazdığı fonksiyonun parametresinin türü // ise beş boyutlu bir diziye referans şeklinde, yani 'int(&rArray)[5]' şeklinde. func("berker"); // Argüman olan ifadenin türü 'const char[7]' şeklinde. 'array-decay' mekanizması çalışmayacaktır. Dolayısıyla // 'T' için çıkarım yapılan tür de 'const char[7]' şeklinde olacaktır. Fakat fonksiyonun parametresinin türü // ise 'const char(&crArray)[7]' şeklinde olacaktır. func(foo); // 'function-to-pointer' mekanizması DEVREYE GİRMEYECEKTİR. İlgili 'foo' isimli fonksiyonun türü 'int(double)' // olduğundan, 'T' için çıkarım da 'int(double)' şeklinde olacaktır. Fakat fonksiyonun parametresinin türü // ise 'int(&rFoo)(double)' şeklinde olacaktır. } * Örnek 2, #include template class TypeTeller; // Bir sınıf şablonunun bildirimi, Forward Declaration. template void func(T& x) { TypeTeller t; } int foo(double); int main() { int ival = 123; func(ival); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] */ const int cival = 123; func(cival); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const int ] */ int a[5]{}; func(a); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int [5] ] */ func("berker"); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const char [7] ] */ func(foo); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int (double) ] */ } * Mülakat Sorusu: //.. template void func(T& x, T& y); int main() { int a[5], b[5], c[6]; func("ali", "nur"); // OK. Birinci argümana bakarak 'T' için 'const char[4]' şeklinde bir çıkarım yapılıyor. İkinci argümana // bakarak da 'T' için 'const char[4]' şeklinde. Aynı türe çıkarım yapıldığından, bir sorun yoktur. func("emre", "muhammed"); // SENTAKS HATASI. Birinci argümana bakarak 'T' için 'const char[5]' şeklinde bir çıkarım yapılıyor. İkinci // argümana bakarak da 'T' için 'const char[9]' şeklinde. Aynı 'T' türüne karşılık iki farklı çıkarım // yapıldığından dolayı. func(a, b); // OK. Birinci argümana bakarak 'T' için 'int[5]' şeklinde bir çıkarım yapılıyor. İkinci argümana bakarak // da 'T' için 'int[5]' şeklinde. Aynı türe çıkarım yapıldığından, bir sorun yoktur. func(b, c); // SENTAKS HATASI. Birinci argümana bakarak 'T' için 'int[5]' şeklinde bir çıkarım yapılıyor. İkinci argümana // bakarak da 'T' için 'int[6]' şeklinde. Aynı 'T' türüne karşılık iki farklı çıkarım yapıldığından dolayı. } >>>>>> Fonksiyon Şablon Parametresinin sadece 'T&&' kullanılması, yani 'auto&&' anahtar sözcüğünün kullanılmasına karşılık gelen senaryo: Aynı kural setidir. 'T' için çıkarım yapılacaktır ve başarılı olursa fonksiyonun parametresi 'Forwarding Referance' olacaktır. 'T' türünün çıkarımının nasıl yapıldığını belirleyen şey, fonksiyona gönderilen argüman olan ifadenin 'Value-Category' sidir. >>>>>>> Eğer gönderdiğimiz argüman olan ifade 'L-value' ise, 'T' türünün çıkarımı o türden bir referans olacaktır. Dolayısıyla derleyicinin yazdığı fonksiyonun parametre türü de bir nevi 'reference-to-reference' oluyor. Normalde bir referansın bir başka referansı refere etmesi söz konusu değil fakat bu nokta bir istisna oluyor ve devreye 'Referance Collapsing' kuralları giriyor. >>>>>>>> 'Reference Collapsing' Kuralları: "It is permitted to form references to references through type manipulations in templates or typedefs, in which case the reference collapsing rules apply: rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference." * Örnek 1, typedef int& lref; typedef int&& rref; int n; lref& r1 = n; // type of r1 is int& lref&& r2 = n; // type of r2 is int& rref& r3 = n; // type of r3 is int& rref&& r4 = 1; // type of r4 is int&& * Örnek 2, C++ Reference Collapsing happen in 4 contexts; template instantiation. auto type generation. The generation and use of typedefs. Alias declarations. * Örnek 1, template void func (T&& param); //Function template with Universal reference int w; func(w); //calling the template with an L-value argument // i. Here, first T will deduce to type of passed argument. i.e., int&. (L-value). // This will create L-value reference to R-value reference in following statement: "func(int& && param);" // ii. As per rules, the final type will become L-value (from, L-value to R-value expression) : func(int & param); >>>>>>> Fakat gönderdiğimiz argüman olan ifade 'R-Value' ise 'T' nin çıkarımı o türden yapılmakta. Dolayısıyla derleyicinin yazdığı fonksiyonun parametre türü de bir nevi 'R-Value Referance' olmaktadır. * Örnek 1, template void func (T&& param); //Function template with Universal reference func(4) // calling the template with R-value // i. First, T will deduce to type of argument. i.e., int&& (R-value). // This will create R-value reference to R-value reference in following statement: "func( int&& && param);" // ii. The final signature will become R-value (from R-value to R-value expression): "func(int&& param)" * Örnek 1, template void func(T&& r) { } int main() { int y = 100; func(y); // Argüman olan ifadenin değer kategorisi 'L-Value' ve türü 'int'. Dolayısıyla 'T' ye karşılık gelecek tür 'int&'. // Fakat bu durumda fonksiyon parametresinin türü de 'int& &&' olmakta. Bir nevi 'reference-to-reference'. // İşte bu noktada 'Reference Collapsing' kuralları devreye giriyor ve fonksiyon parametresinin türü 'int&' oluyor. func(45); // Argüman olan ifadenin türü 'int'. 'T' türüne karşılık gelen ifade ise 'int' olmakta. Fonksiyon parametresinin // türü ise bu durumda 'int&&' olmakta. } * Örnek 2, #include template class TypeTeller; // Bir sınıf şablonunun bildirimi, Forward Declaration. template void func(T&& x) { TypeTeller t; } int foo(double); int main() { int x = 100; func(x); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int & ] note: see reference to function template instantiation 'void func(T)' being compiled with [ T=int & ] */ func(45); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] */ } * Örnek 3, Argüman olan ifade 'L-Value' ise. #include template void func(T&& x) { // X is a 'L-Value Reference'. x = 200; std::cout << "ii. x : " << x << "\n"; } int main() { int x = 100; std::cout << "i. x : " << x << "\n"; func(x); std::cout << "iii. x : " << x << "\n"; /* # OUTPUT # i. x : 100 ii. x : 200 iii. x : 200 */ } * Örnek 4, Argüman olan ifade bir 'R-Value' ise. #include template void func(T&& x) { // X is a 'R-Value Reference'. std::cout << "ii. x : " << x << "\n"; } int main() { int x = 100; std::cout << "i. x : " << x << "\n"; func(45); std::cout << "iii. x : " << x << "\n"; /* # OUTPUT # i. x : 100 ii. x : 45 iii. x : 100 */ } * Örnek 5.1, #include template class TypeTeller; template void func(T x) { TypeTeller t; } template void funcTwo(T& x) { TypeTeller t; } template void funcThree(T&& x) { TypeTeller t; } class Myclass{}; int main() { int ival = 123; func(ival); // auto => 'void func(T)' being compiled funcTwo(ival); // auto& => 'void funcTwo(T &)' being compiled funcThree(ival); // auto&& => 'void funcThree(T)' being compiled int& rival = ival; func(rival); // auto => 'void func(T) [with T = int]': funcTwo(rival); // auto& => 'void funcTwo(T&) [with T = int]': funcThree(rival); // auto&& => 'void funcThree(T&&) [with T = int&]': const int cival = 123; func(cival); // auto => 'void func(T) [with T = int]': funcTwo(cival); // auto& => 'void funcTwo(T&) [with T = const int]': funcThree(cival); // auto&& => 'void funcThree(T&&) [with T = const int&]': Myclass obj; func(obj); // auto => 'void func(T) [with T = Myclass]': funcTwo(obj); // auto& => 'void funcTwo(T&) [with T = Myclass]': funcThree(obj); // auto&& => 'void funcThree(T&&) [with T = Myclass&]': } * Örnek 5.2, #include template void func(T x) { x *= 2; } int main() { int ival = 123; std::cout << "-----------------------\n"; /* # OUTPUT # ival : 123 ival : 123 */ std::cout << "ival : " << ival << "\n"; func(ival); std::cout << "ival : " << ival << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # 45 : 45 45 : 45 */ std::cout << "45 : " << 45 << "\n"; func(45); std::cout << "45 : " << 45 << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refIval : 123 refIval : 123 */ int& refIval = ival; std::cout << "refIval : " << refIval << "\n"; func(refIval); std::cout << "refIval : " << refIval << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refrefIval : 45 refrefIval : 45 */ int&& refrefIval = 45; std::cout << "refrefIval : " << refrefIval << "\n"; func(refrefIval); std::cout << "refrefIval : " << refrefIval << "\n"; std::cout << "-----------------------\n"; } * Örnek 5.3, #include template void funcTwo(T& x) { x *= 2; } int main() { int ival = 123; std::cout << "-----------------------\n"; /* # OUTPUT # ival : 123 ival : 246 */ std::cout << "ival : " << ival << "\n"; funcTwo(ival); std::cout << "ival : " << ival << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’ */ std::cout << "45 : " << 45 << "\n"; // funcTwo(45); // error: std::cout << "45 : " << 45 << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refIval : 246 refIval : 492 */ int& refIval = ival; std::cout << "refIval : " << refIval << "\n"; funcTwo(refIval); std::cout << "refIval : " << refIval << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refrefIval : 45 refrefIval : 90 */ int&& refrefIval = 45; std::cout << "refrefIval : " << refrefIval << "\n"; funcTwo(refrefIval); std::cout << "refrefIval : " << refrefIval << "\n"; std::cout << "-----------------------\n"; } * Örnek 5.4, #include template void funcThree(T&& x) { x *= 2; } int main() { int ival = 123; std::cout << "-----------------------\n"; /* # OUTPUT # ival : 123 ival : 246 */ std::cout << "ival : " << ival << "\n"; funcThree(ival); std::cout << "ival : " << ival << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # 45 : 45 45 : 45 */ std::cout << "45 : " << 45 << "\n"; funcThree(45); std::cout << "45 : " << 45 << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refIval : 246 refIval : 492 */ int& refIval = ival; std::cout << "refIval : " << refIval << "\n"; funcThree(refIval); std::cout << "refIval : " << refIval << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refrefIval : 45 refrefIval : 90 */ int&& refrefIval = 45; std::cout << "refrefIval : " << refrefIval << "\n"; funcThree(refrefIval); std::cout << "refrefIval : " << refrefIval << "\n"; std::cout << "-----------------------\n"; } * Örnek 6, #include template class TypeTeller; template void func(T&& x) { TypeTeller t; } class A{}; int main() { // func(31); // 'void func(T&&) [with T = int]': int x; // func(x); // 'void func(T&&) [with T = int&]': int& rx = x; // func(rx); // 'void func(T&&) [with T = int&]': int&& rrx = 31; // func(rrx); // 'void func(T&&) [with T = int&]': A ax; // func(std::move(ax)); // 'void func(T&&) [with T = A]': } * Örnek 7, #include template class TypeTeller; template void foo(T&& x) { TypeTeller t; } template void func(T&& x) { std::cout << __FUNCSIG__ << " => x : " << x << "\n"; int ival = 13; x = ival; } int main() { // func(31); // void __cdecl func(int &&) => x : 31 // foo(31); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] note: see reference to function template instantiation 'void foo(T &&)' being compiled with [ T=int ] */ int x; func(x); // void __cdecl func(int &) => x : 7 foo(x); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int & ] note: see reference to function template instantiation 'void foo(T)' being compiled with [ T=int & ] */ } >>>>>> 'auto' daki tür çıkarım ile buradaki tür çıkarım kuralları arasındaki tek farlılık 'std::initializer_list' kullanımında. * Örnek 1, //.. template void func(T x); int main() { auto x = {2, 4, 6, 8}; // Tür çıkarımı 'std::initializer_list' in 'int' açılımı yönünde. func({2, 4, 6, 8}); // Sentaks hatası. } * Mülakat Sorusu: Derleme zamanı sabiti olarak aşağıdaki dizinin boyutunu nasıl elde edebiliriz? //.. // Aşağıdaki 'a' dizisini bu fonksiyona geçebilmek için bu şekilde bir fonksiyon parametresi yazdık. // Böylelikle 'rArray' aşağıdaki 'a' dizisine referanstır. Dolayısıyla 'T' için 'int' gelecek, // 'm' için de '8' rakamı gelecek. template constexpr size_t asize(T(&rArray)[m]) { return m; // İşte bu şekilde bir dizinin boyutunu derleme zamanında C++ usülü hesaplamış olduk. } int main() { int a[] = {1, 2, 3, 4, 5, 6, 7, 8}; /* El-cevap : */ auto size = sizeof(a) / sizeof(a[0]); // C-style ve C++ style /* El-cevap : */ constexpr auto n = asize(a); // 'a' dizisinin türü => int[8]. /* El-cevap : */ constexpr auto m = std::size(a); // C++17. } >>>>>> Varsayılan argüman ifadesinden tür çıkarımı YAPILAMAZ. * Örnek 1, //.. template void func(T x = 10){} int main() { func(); // SENTAKS HATASI. ÇIKARIM YAPILAMAZ. } >>> Aşağıdaki genel örneği bir inceleyelim; * Örnek 1, //.. template void func(F f){ //.. some code here. f(); // Burada 'F' ya bir fonksiyon göstericisi olacak ya da 'operator()()' fonksiyonunu 'overload' etmiş bir sınıf, // ki bu tip sınıflara da 'functor' denmektedir. //.. some code here. } * Örnek 2, Fonksiyon göstericisi olması durumu: //.. template void func(F f){ //.. some code here. f(); //.. some code here. } void foo() { std::cout << "foo cagrildi.\n"; } int main() { func(foo); // OUTPUT => foo cagrildi. } * Örnek 3, Functor olma durumu: //.. class MyFunctor { public: void operator()()const { std::cout << "void MyFunctor::operator()()const cagrildi.\n"; } }; template void func(F f){ //.. some code here. f(); //.. some code here. } int main() { MyFunctor mfc; func(mfc); // OUTPUT => void MyFunctor::operator()()const cagrildi. // func(MyFunctor{}); // Aynı sonucu verecektir. } >>> Derleyici her farklı argüman için ayrı bir fonksiyon yazar. Eğer halihazırda yazılmış bir versiyonu farklı argümanlar ile çağırmak istiyorsak, o yazılanı 'explicit' olarak nitelememiz gerekiyor. * Örnek 1, #include template void func(T mx) { std::cout << "T : "; std::puts(typeid(T).name()); std::cout << "mx : "; std::puts(typeid(mx).name()); } int main() { /* # OUTPUT # T : bool mx : bool T : unsigned char mx : unsigned char T : signed char mx : signed char T : char mx : char T : short mx : short T : int mx : int T : __int64 mx : __int64 T : float mx : float T : double mx : double T : long double mx : long double T : int mx : int */ bool b = true; func(b); unsigned char uc = 'A'; func(uc); signed char sc = 'B'; func(sc); char c = 'C'; func(c); short s = 12; func(s); func(12345); func(12345654321L); func(1.2f); func(1.2); func(1.2L); func(12U); // Bu fonksiyon için yeni bir tane yazılmadı. Daha önce 'int' için yazılan çağrıldı. } >>> Derleyici 'explicit' olarak nitelediğimiz türler için şablonları açacaktır. * Örnek 1, //.. template void func(T mx, U nx) { //.. } int main() { func(12, 4.5); // Her iki argüman için Tür Çıkarımı yapılacaktır. func<>(45, 1.2); // Her iki argüman için Tür Çıkarımı yapılacaktır. func('A', 2.3f); // Buradaki açılım 'int' ve 'double' türlerine göre yapılacaktır. funcfunc(3, 4.5); // İlk argüman için bizim belirttiğimiz, ikinci argüman için de Tür Çıkarımı yapılacaktır. } >> Birden fazla 'Template Type Parameter' olması, bunlara karşılık gelen argümanların farklı türler olması zorunluluğunu da beraberinde GETİRMİYOR. * Örnek 1, #include template void func(T x, U y); int main() { func(12, 21); // LEGAL. 'T' ve 'U' ya karşılık 'int' gelmiştir. } template void func(T x, U y) { std::cout << "x + y : " << x + y << "\n"; } >> C++ Templates, by Joutistiss. C++17 için yeniden yazıldı. > 'Trailing Return Type' : Fonksiyon şablonlarında kullanılma zorunluluğu yoktur. Bir fonksiyonu tanımlarken fonksiyonun geri dönüş türünü yazdığımız kısma 'auto' yazıyoruz. Devamında fonksiyonun ismini ve parametrelerini. Devamında '->' adet token ekliyoruz. Devamında da ilgili fonksiyonun geri dönüş türünü yazıyoruz. Esas kullanım alanı şablonlar. * Örnek 1, //.. auto func(int x)->double { //.. } * Örnek 2, Fonksiyonun geri dönüş değerinin bir fonksiyon göstericisinin olması durumunda fayda sağlayabilir. //.. int*(*func())(int); // 'func' öyle bir fonksiyon ki; // i. Herhangi bir parametre almamaktadır. // ii. Öyle bir fonksiyon adresi döndürmektedir ki bu döndürülen fonksiyonun geri dönüş değeri 'int*' ve aldığı // parametre 'int' türden. auto funcTwo()->int*(*)(int); // Yukarıdaki ile aynı. * Örnek 3, Şablonlarda kullanımı: Farz edelim ki derleyicinin yazmış olduğu fonksiyona geçilen argümanların değerlerinin toplamının türü ne ise o türden döndürmek istiyoruz. İlgili türü de 'decltype' ile elde edebiliriz. Fakat fonksiyonun geri dönüş değerinin türünün yazıldığı yer fonksiyon skobu içerisinde olmadığından sentaks hatası alacağız. Hadi inceleyelim. //.. template decltype(a+b) func(T a, U b) { //.. BU DURUM SENTAKS HATASIDIR. } template auto foo(T a, U b) -> decltype(a+b) { //.. BU DURUM LEGALDİR. } /*============================================================================================================*/ (23_05_12_2020) > 'auto' return type : Geri dönüş değerinin türünü yazdığımız yere 'auto' yazıyoruz. > Şablonlar ve Jenerik Programlama (devam): >> Fonksiyon Şablonları (devam) : >>> Fonksiyon şablonlarının 'overload' edilmeleri: Bir fonksiyon şablonu ile aynı isimde bir fonksiyon birbirinin 'overload' durumunda olabilir. Fonksiyon şablonları da birbirlerini 'overload' edebilirler. >>>> Bir şablon ile gerçek bir fonksiyonun birbirini 'overload' etme durumu: * Örnek 1, #include template void func(T x) { std::cout << "Function Template => T is [" << typeid(T).name() << "]\n"; } void func(int x) { std::cout << "func(int)\n"; } int main() { /* # OUTPUT # Function Template => T is [double] Function Template => T is [float] Function Template => T is [char] func(int) */ func(2.3); // i. Derleyici bu çağrı ile karşılaştığında önce şablona bakıyor ve tür bilgisini elde etmeye çalışıyor. // Bu örnek için 'T' için tür çıkarımı 'double' yönünde olacak. // ii. Sonrasında bu 'double' parametreli 'func' fonksiyonu ile 'int' parametreli 'func' fonksiyonunu // 'Function Overload Resolution' a dahil ediyor. // iii. Parametre uyuşması konusunda 'double' parametreli 'exact-match' olduğundan, bu çağrı ona bağlanıyor. func(2.3f); func('A'); func(999); // Buradan da hareketle diyebiliriz ki parametreler arasında 'exact-match' durumu yok ise şablon o yeni tür // için açılmaktadır. } * Örnek 2, Mülakat Sorusu: Öyle bir fonksiyon oluşturalım ki sadece ve sadece 'int' türden parametre kabul etsin. Diğer türlerden gönderilen argümanlar için SENTAKS HATASI versin. #include template void func(T x) = delete; void func(int x) { std::cout << "func(int)\n"; } int main() { /* # OUTPUT # func(int) */ // func(1.2); // error C2280: 'void func(T)': attempting to reference a deleted function func(12); } > >>> Fonksiyon şablonlarının birbirlerini 'overload' etme durumu: Çok sık karşımıza çıkan bir durumdur. Hangi 'overload' versiyon şablonunun seçileceğini belirleyen kural setine de 'Partial Ordering Rules' denmektedir. * Örnek 1, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T*) { std::cout << "template func(T*)\n"; } int main() { /* # OUTPUT # template func(T*) */ int x{}; func(&x); // Eğer 'II' numaralı fonksiyon tanımı olmasaydı; ilgili fonksiyona gönderilen argümanın türü 'int*'. // Dolayısıyla 'T' nin türü 'int*' şeklinde çıkarım yapıldı. Derleyicinin yazdığı fonksiyonun parametresi de // 'int*' olacaktı. Eğer 'I' numaralı fonksiyon tanımı olmasaydı; ilgili fonksiona gönderilen argümanın türü // 'int*'. Fakat 'T' nin türü 'int' şeklinde çıkarım yapılacaktı. Derleyicinin yazdığı fonksiyonun parametresi // de 'int*' olacaktı. Eğer her iki fonksiyon da tanımlı olsaydı, 'II' numaralı versiyon çağrılacaktı. Çünkü // kural gereği DAHA SPESİFİK FONKSİYON PARAMETRESİNE SAHİP VERSİYON SEÇİLİR. } * Örnek 2, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T*) { std::cout << "template func(T*)\n"; } template // III void func(T**) { std::cout << "template func(T**)\n"; } int main() { /* # OUTPUT # template func(T**) */ func((int**)0x1200); // Yukarıdaki her üç fonksiyon şablonu da bu çağrıya bağlanabilir. Fakat 'III' numaralı version daha // fazla deklaratöre sahip olduğundan, yani daha spesifik fonksion parametresine sahip olduğundan, o seçilmiştir. } * Örnek 3, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T(&r)[n]) { std::cout << "template T(&r)[n]\n"; } int main() { /* # OUTPUT # template T(&r)[n] */ int a[12]{}; func(a); // 'II' numaralı version seçilecektir çünkü çok daha spesifik. Dolayısıyla 'T' türü adına 'int' türü, // 'n' için de '12' rakamı gelecektir. Eğer sadece 'I' olsaydı, 'T' yerine de 'int*' şeklinde bir // çıkarım yapılacaktı. } >>>>> 'Partial Ordering Rules' : Aşağıdaki örneği inceleyelim. * Örnek 1, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T*) { std::cout << "template func(T*)\n"; } int main() { // Round - I // T yerine bir tür seçelim. Varsayalım ki 'int' seçildi. // 'int' türden bir argüman ile 'I' numaralı şablona bir çağrı yapalım. // Bu durumda 'I' numaralı şablonda fonksiyon parametresi 'int' türden olacak. // 'I' numaralı şablondaki fonksiyon parametresi ile 'II' numaralı şablon fonksiyonunu çağırırsak // SENTAKS HATASI OLACAK. // Çünkü 'II' numaralı şablon fonksiyonuna 'int' türden bir değer göndermiş oluyoruz. // Özetle; // i. from main() to function-I succeed. => 'T' in the 'I' is 'int'. // ii. from function-I to function-II failed. => FAILED. // Round - II // T yerine bir tür seçelim. Varsayalım ki 'int' seçildi. // 'int*' türden bir nesne adresi ile 'II' numaralı şablona bir çağrı yapalım. // Bu durumda 'II' numaralı şablonda fonksiyon parametresi 'int*' türden olacak. // 'II' numaralı şablondaki fonksiyon parametresi ile 'I' numaralı şablon fonksiyonunu çağırırsak // SENTAKS HATASI OLMAYACAK. // Çünkü 'I' numaralı fonksiyona 'int*' türden bir değer göndermiş oluyoruz ve 'I' numaralı fonksiyon // şablonunu için 'T' için tür çıkarımı 'int*' yönünde yapılmış oluyor. // Özetle, // i. from main() to function-II succeed. => 'T' in the 'II' is 'int'. // ii. from function-II to function-I succeed. => 'T' in the 'II' is 'int*'. // Eğer Round-I ve Round-II SENTAKS HATASI VERMESEYDİ, 'ambiguity' tip SENTAKS HATASI alacaktık. } >>> 'Perfect Forwarding' : Modern C++ ile dile gelen en önemli özelliklerden birisidir. Gerçek bir fonksiyona yapacağım çağrı ile fonksiyon şablonuna, ki bu da arka planda aynı gerçek fonksiyonu çağıracaktır, yapacağım çağrı arasında bir fark OLMASIN İSTİYORUM. * Örnek 1, #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III int main() { // Ben foo işlevine hangi argüman ile çağrı yaparsam, // bununla benim bu argümanı 'func' fonksiyonuna göndermem ve 'func' fonksiyonunun bu argüman ile 'foo' // fonksiyonunu çağırması arasında bir fark OLMAMALI. // Value-Category ve 'const' gibi özellikleri KORUNMUŞ OLACAK. /* # OUTPUT # foo(int&) foo(const int&) foo(int&&) */ int a{}; foo(a); // 'III' numaralı 'viable' DEĞİL. 'I' numaralı 'exact-match' olduğundan çağrılacaktır. const int& cx{5}; foo(cx); // 'II' numaralı çağrılacaktır. foo(34); // 'III' numaralı çağrılacaktır. // Peki bizler öyle bir fonksiyon şablonu yazalım ki o şablona geçeceğimiz parametreler, // yine bu örnekteki çıktıyı versin; // El-cevap: aşağıdaki cevapları inceleyelim. } * Örnek 2, * Örnek 2.0 : Yazacağımız fonksiyon şablonunun parametresi "const T& x" şeklinde olursa: #include void foo(int& x) { std::cout << "foo(int&)\n"; }// I void foo(const int& x) { std::cout << "foo(const int&)\n"; } // II void foo(int&& x) { std::cout << "foo(int&&)\n"; } // III template void func(const T& x) { foo(x); } int main() { /* # OUTPUT # ------- foo(int&) foo(const int&) ------- foo(const int&) foo(const int&) ------- foo(int&&) foo(const int&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; // ÇIKTITAN DA GÖRÜLDÜĞÜ ÜZERE BU ŞEKİLDEKİ BİR ŞABLON İŞE YARAMADI. } * Örnek 2.1 : Yazacağımız fonksiyon şablonunun parametresi "T x" şeklinde olursa: #include void foo(int& x) { std::cout << "foo(int&)\n"; } // I void foo(const int& x) { std::cout << "foo(const int&)\n"; } // II void foo(int&& x) { std::cout << "foo(int&&)\n"; } // III template void func(T x) { foo(x); } int main() { /* # OUTPUT # ------- foo(int&) foo(int&) ------- foo(const int&) foo(int&) ------- foo(int&&) foo(int&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; } * Örnek 2.2 : Yazacağımız fonksiyon şablonunun parametresi "T&& x" şeklinde olursa: #include void foo(int& x) { std::cout << "foo(int&)\n"; } // I void foo(const int& x) { std::cout << "foo(const int&)\n"; } // II void foo(int&& x) { std::cout << "foo(int&&)\n"; } // III template void func(T&& x) { foo(x); } int main() { /* # OUTPUT # ------- foo(int&) foo(int&) ------- foo(const int&) foo(const int&) ------- foo(int&&) foo(int&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; } * Örnek 3, ÇÖZÜM : 'std::forward' isimli fonksiyon şablonunun geri dönüş değerini, ki bu da 'std::move' gibi dönüştürmeye işlemi yapıyor, bizim 'func' isimli fonksiyon şablonumuzda çağrı yapıyoruz. #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III template void func(T&& x) { foo(std::forward(x)); } int main() { /* # OUTPUT # ------- foo(int&) foo(int&) ------- foo(const int&) foo(const int&) ------- foo(int&&) foo(int&&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; // # KISSADAN HİSSE # // BİR ŞABLON FONKSİYONUNUN 'Template Type Argument' İ OLARAK 'T&&' SEÇİYORSAK, // TEK AMACIMIZ 'Perfect Forwarding' OLMALIDIR. Bu şekil bir fonksiyon bildirimi // görmüşsek şundan eminiz ki ilgili fonksiyonun bloğunda 'std::forward' fonksiyonunun // 'T' türünden açılımı kullanılmıştır. } * Örnek 4, #include void func(const int& x) { std::cout << "func(const int&)\n"; } void func(int&& x) { std::cout << "func(int&&)\n"; } template void f1(T&& a) { f2(std::forward(a)); } template void f2(T&& a) { f3(std::forward(a)); } template void f3(T&& a) { func(std::forward(a)); } int main() { f1(12); // OUTPUT => func(int&&) int x{}; f1(x); // OUTPUT => func(const int&) } * Örnek 5, #include class Data{ public: Data() = default; Data(const Data&) { std::cout << "copy ctor\n"; } Data(Data&&) { std::cout << "move ctor\n"; } }; void func(const Data& x) { Data mydata(x); } void func(Data&& x) { Data mydata(std::move(x)); } int main() { Data mydata; func(mydata); // OUTPUT => copy ctor func(Data{}); // OUTPUT => move ctor } >>>> 'std::vector' sınıfındaki '.emplace_back()' fonksiyonu bu yapıda çalışmaktadır. Bu fonksiyonlara geçilecek argümanlar, vektörde tutulan sınıfın 'Ctor.' fonksiyonuna gönderilmekte. Dolayısıyla bizler bir nevi iglili sınıfın 'Ctor.' fonksiyonunun istediği argümanları geçiyoruz. >>>> Mülakat Sorusu: * Örnek 1, Aşağıdaki iki fonksiyonun varlık nedenini ve ikisi arasındaki farkı açıklayınız. class Data{}; void func(Data&& x) {} // Burada bir şablon kullanılmadığından tür çıkarımı söz konusu değil. Dolayısıyla 'x' in türü 'Data&&'. // Yani bir 'R-Value Referance'. Böyle bir fonksiyonun tek varlık nedeni 'Move Semantics'. template void func(T&& x){} // Burada bir şablon kullanılmakta. Dolayısıyla 'x' bir 'R-Value Referance' DEĞİL. 'x' bir 'Forwarding Reference'. // Böyle bir fonksiyonun tek varlık nedeni almış olduğu argümanı başka bir fonksiyona PAS ETMEKTİR. >>> 'Variadic Function Templates' : 'Template Type Parameter' in bulunduğu yeri bu sefer şu şekilde yazıyoruz => template. 'myVariadicTemplate' ismi için de artık 'Template Parameter Pack' denmektedir. Derleyicinin yazdığı fonksiyonun parametresine de 'Function Parameter Pack' denmektedir. İş bu 'Function Parameter Pack' eğer; >>>> Yalın halinde kullanılırsa, bu paket içindeki türler yalın türler olmaktadırlar. >>>> Yanlarında bir referans deklaratörü olmasu durumunda bu paket içindeki türler birer 'L-Value Referance' halini almaktadır. >>>> Eğer 'const' anahtar sözcüğü ve referans deklaratörü ile birlikte kullanılırsa da paket içindeki türler 'const L-Value Referance' halini alırlar. >>>> Eğer yanlarında sadece iki adet referans deklaratörü ile birlikte kullanılırsa da bu paket içindeki türler 'Forwarding Referance' halini almaktadır. * Örnek 1, template void foo(MyVariadicTemplate... myOpenedVariadicTemplate) { } // Burada, // 'Template Parameter Pack' : MyVariadicTemplate // 'Function Parameter Pack' : myOpenedVariadicTemplate // isimlerine karşılık gelmektedir. int main() { foo(2,5); // 'Function Parameter Pack' içindeki türler birer 'primitive' türler. } * Örnek 2, template void foo(MyVariadicTemplate& ... myOpenedVariadicTemplate) { } int main() { foo(2, 5); // SENTAKS HATASI. Çünkü 'Function Parameter Pack' içindeki türler birer 'L-Value Reference' şeklindeler. // Dolayısıyla bunlara 'R-Value Expression' bağlanamazlar. } * Örnek 3, template void foo(cosnt MyVariadicTemplate& ... myOpenedVariadicTemplate) { } int main() { foo(2, 5); // 'Function Parameter Pack' içindeki türler birer 'const-L-Value Reference' şeklindeler. // Dolayısıyla bunlara 'R-Value Expression' bağlanamazlar. } * Örnek 4, template void foo(MyVariadicTemplate&& ... myOpenedVariadicTemplate) { } int main() { foo(2, 5); // 'Function Parameter Pack' içindeki türler birer 'Forwarding Reference'. } >>>> GCC derleyicinde 'PrettyFunction' isminde bir makro var. Fonksiyonun imzasını içeren bir 'string' e açılmaktadır. MSVC derleyicisinde de bu makronun karşılığı '__FUNCSIG__' makrosudur. * Örnek 1, #include template void func(MyVariadicTemplate... myOpenedVariadicTemplate) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { func(2,5); // OUTPUT => [ void __cdecl func(int,int) ] } * Örnek 2, #include template void func(MyVariadicTemplate& ... myOpenedVariadicTemplate) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { int x = 2; double d = 2.2; func(x, d); // OUTPUT => [ void __cdecl func(int &,double &) ] // Çıkarım Yapılan | Fonksiyonun // Tür | Parametreleri } * Örnek 3, #include template void func(MyVariadicTemplate&& ... myOpenedVariadicTemplate) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { int x = 2; func(x, 1.2); // OUTPUT => [ void __cdecl func(int &,double &&) ] // Fonksiyonun birinci parametresi 'int&'. Çünkü fonksiyona argüman olarak bir 'L-Value' geçildiği için. // Fonksiyonun ikinci parametresi 'int&&'. Çünkü fonksiyona argüman olarak bir 'R-Value' geçildiği için. // Birinci 'Template Type Argument' => 'int&' // İkinci 'Template Type Argument' => 'int' } * Örnek 4, #include template void func(T x) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } template void funcRef(T& x) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } template void funcRefRef(T&& x) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { // A : 'T' yerine gelen tür. // B : Fonksiyon parametresi olan 'x' in türü. std::cout << "\n*******************************\n"; // A | B func(31); // OUTPUT => [ void __cdecl func (int) ] int x = 100; func(x); // OUTPUT => [ void __cdecl func (int) ] int& rx = x; func(rx); // OUTPUT => [ void __cdecl func (int) ] int&& rrx = 32; // OUTPUT => [ void __cdecl func (int) ] const int& crx = x; func(crx); // OUTPUT => [ void __cdecl func (int) ] std::cout << "*******************************\n"; // funcRef(100); // SENTAKS HATASI. int xx = 200; funcRef(xx); // OUTPUT => [ void __cdecl funcRef(int &) ] int& rxx = xx; funcRef(rxx); // OUTPUT => [ void __cdecl funcRef(int &) ] int&& rrxx = 400; funcRef(rrxx); // OUTPUT => [ void __cdecl funcRef(int &) ] const int& crxx = xx; funcRef(crxx); // OUTPUT => [ void __cdecl funcRef(const int &) ] std::cout << "*******************************\n"; funcRefRef(300); // OUTPUT => [ void __cdecl funcRefRef(int &&) ] int xxx = 200; funcRefRef(xxx); // OUTPUT => [ void __cdecl funcRefRef(int &) ] int& rxxx = xxx; funcRefRef(rxxx); // OUTPUT => [ void __cdecl funcRefRef(int &) ] int&& rrrxxx = 400; funcRefRef(rrrxxx); // OUTPUT => [ void __cdecl funcRefRef(int &) ] const int& crxxx = xxx; funcRefRef(crxxx); // OUTPUT => [ void __cdecl funcRefRef(const int &) ] std::cout << "*******************************\n"; } >>>> Gerek 'Template Parameter Pack' gerek 'Function Parameter Pack' öğe sayısını bize 'compile time' sabiti olarak verecek bie operatör vardır; C++11 ile dile eklenen yeni bir operatördür. 'sizeof' anahtar kelimesinden sonra '...' atomunu yazıyoruz. Devamında da parantez içerisine bu iki parametre paketinden birini yazıyoruz. * Örnek 1, #include template void func(MyVariadicTemplate ... myOpenedVariadicTemplate) { constexpr auto n1 = sizeof...(MyVariadicTemplate); constexpr auto n2 = sizeof...(myOpenedVariadicTemplate); std::cout << "n1 : " << n1 << "\n"; std::cout << "n2 : " << n2 << "\n"; } int main() { func(1, 5, 6.7, 7.6); } >>>> Parametre paketlerinin açılış şekilleri; * Örnek 1, #include template void func(Args...args) { std::tuple myTuple; // Derleyici fonksiyon çağrısındaki argümanlara bakarak 'Args...' deyimini 'int, double, long' şeklinde açacaktır. // std::tuple myTuple; } template void foo(Args...args) { int a[] = {args...}; // 'args...' ifadesini derleyici virgüller ile ayrılmış ve elemanları paketin bir elemanı olan listeye çevirmekte. } /* // 'main' içerisindeki 'foo' çağrısını gören derleyici aşağıdaki gibi bir kod yazacaktır. void foo(int p1, int p2, int p3) { int a[] = {p1, p2, p3}; } */ template void myFoo(Args...args) { fooAnother(args)... } /* // 'main' içerisindeki 'myFoo' çağrısını gören derleyici aşağıdaki gibi bir kod yazacaktır. void myFoo(int p1, double p2, long p3) { fooAnother(p1), fooAnother(p2), fooAnother(p3) } */ // Yukarıdaki şablonlar ışığında, derleyici, aşağıdaki şablonu neye dönüştürecektir? template void myFunc(Args&& ...args) { fooAnotherTwo(std::forward(args)...); } /* // İŞTE AŞAĞIDAKİ GİBİ BİR KOD YAZACAKTIR. void func(int&& p1, double&& p2, long&& p3) { fooAnotherTwo(std::forward(p1), std::forward(p2), std::forward(p3)); } */ int main() { func(10, 3.4, 5L); foo(1, 2, 3); myFoo(1, 2.2, 5L); myFunc(10, 3.4, 5L); } >>>>> Derleme zamanı Öz-yinelemeli yöntem ile paketin açılması: Derleyiciye derleme zamanında 'n' tane fonksiyon kodu yazdırılmasıdır. * Örnek 1, template void print(const T& r) { std::cout << r << "\n"; } template void print(const T& r, const Args& ... args) { // std::cout << __FUNCSIG__ << "\n\n"; print(r); print(args...); } int main() { int x = 10; double d = 23.5; print(x, d, 9L, 'a'); } // Derleyici yukarıdaki 'print' fonksiyonlarını aşağıdaki şekilde yazmaktadır: /* // Round - I void print(int x, double p1, long p2, char p3) { print(x); print(p1, p2, p3); } */ /* // Round - II void print(double x, long p1, char p2) { print(x); print(p1, p2); } */ /* // Round - III void print(long x, char p1) { print(x); print(p1); } */ // PARAMETRE PAKETİNİN AÇILIMI BURADA ARTIK BİTMİŞTİR. * Örnek 2, #include #include template T summer(T v) { return v; } template T summer(const T& first, const Args&... args) { return first + summer(args...); } int main() { /* # OUTPUT # 157 NecAtiErgingin */ std::cout << summer(10, 20, 30, 80, 17) << "\n"; std::string s1 = "Nec", s2 = "Ati", s3 = "Ergin"; std::cout << summer(s1, s2, s3, "gin") << "\n"; } >>>>> 'std::make_unique' fonksiyonunun temsili bir implementasyonu: * Örnek 1, #include class Data{ public: Data(int, double, long){} }; template std::unique_ptr MakeUnique(Args&& ... args) { return std::unique_ptr{ new T(std::forward(args)...)}; | | | |T türünden dinamik ömürlü bir nesne oluşturduk, | |onun Ctor. fonksiyonuna argümanlargönderdik. | |std::unique_ptr sınıf türünden geçici bir türü nesne oluşturduk. |Bu geçici nesneyi de dönrdürdük. } int main() { auto ptr = MakeUnique(12, 3.4, 34L); } >>> Gerçek sınıflarımızın üye fonksiyonları da birer fonksiyon şablonları olabilir. Bu üye fonksiyonlara özel üye fonksiyonlar da dahildir. * Örnek 1, #include #include class Myclass{ public: template void func(T x){ std::cout << ">>> " << __FUNCSIG__ << " \n"; } template void funcRef(T& x){ std::cout << ">>> " << __FUNCSIG__ << " \n"; } template void funcRefRef(T&& x){ std::cout << ">>> " << __FUNCSIG__ << " \n"; } }; int main() { Myclass mx; std::cout << "------------------------------------------------------------\n"; mx.func(12); // >>> void __cdecl Myclass::func(int) // mx.funcRef(12); // Sentaks hatası. mx.funcRefRef(12); // >>> void __cdecl Myclass::funcRefRef(int &&) std::cout << "------------------------------------------------------------\n"; int ival = 200; mx.func(ival); // >>> void __cdecl Myclass::func(int) mx.funcRef(ival); // >>> void __cdecl Myclass::funcRef(int &) mx.funcRefRef(ival); // >>> void __cdecl Myclass::funcRefRef(int &) std::cout << "------------------------------------------------------------\n"; int& rival = ival; mx.func(rival); // >>> void __cdecl Myclass::func(int) mx.funcRef(rival); // >>> void __cdecl Myclass::funcRef(int &) mx.funcRefRef(rival); // >>> void __cdecl Myclass::funcRefRef(int &) std::cout << "------------------------------------------------------------\n"; const int cival = 300; const int& crival = cival; mx.func(cival); // >>> void __cdecl Myclass::func(int) mx.funcRef(crival); // >>> void __cdecl Myclass::funcRef(const int &) mx.funcRefRef(crival); // >>> void __cdecl Myclass::funcRefRef(const int &) std::cout << "------------------------------------------------------------\n"; int&& rrival = 500; mx.func(rrival); // >>> void __cdecl Myclass::func(int) mx.funcRef(rrival); // >>> void __cdecl Myclass::funcRef(int &) mx.funcRefRef(rrival); // >>> void __cdecl Myclass::funcRefRef(int &) std::cout << "------------------------------------------------------------\n"; } >> Sınıf Şablonları : Fonksiyon şablonu oluştururken fonksiyon ismini, geri dönüş değerini ve parametre parantezini yazdığımız satıra artık sınıfımızın tanımını yazmaktayız. * Örnek 1, //.. template class Myclass{ public: T func(T x){} private: T mx; }; // Yukarıdaki sınıf şablonuna bakarak 'T' ye karşılık gelen her bir tür için sınıf şablonu yazılacaktır. >>> Derleyici, çağırmadığımız üye fonksiyonun kodunu YAZMAMAKTADIR. * Örnek 1, //.. template class Myclass{ public: T func(T x){} void foo(T x, int m) {} private: T mx; }; int main() { Myclass m1; // Derleyici 'int' türüne açılan bir sınıf şablonu yazacak fakat sadece 'Default Ctor.' ve'Dtor.' // fonksiyonlarının kodunu yazacaktır. 'func' fonksiyonunun kodunu yazmayacaktır. m1.func(); // Şimdi sadece 'func' fonksiyonunun kodunu yazmıştır. 'foo' fonksiyonun kodunu yine yazmayacaktır. } >>> Sınıf şablonlarında 'data members' ve 'member functions' lar için de şablon parametresi kullanılabilir. * Örnek 1, template class Neco{ public: void myFunc(T (*myFuncPtr)(U)); private: T my_array[m]{}; }; int main() { Neco mx; } >>> Sınıf şablonlarının kendisi bir SINIF DEĞİLDİR. * Örnek 1, template class Myclass{ }; int main() { Myclass mx; // SENTAKS HATASI. } >>> Aynı sınıf şablonunun türetilen sınıflar arasında OTOMATİK BİR DÖNÜŞÜM YOKTUR. * Örnek 1, template class Myclass{ }; int main() { Myclass ival; Myclass dval; ival = dval; // SENTAKS HATASI. İKİSİ FARKLI SINIF OLDUĞUNDAN ARALARINA OTOMATİK DÖNÜŞÜM YOKTUR. } * Örnek 2, bu kural sınıfların fonksiyonları için de geçerlidir. template class Nec{ public: void func(Nec x); }; int main() { Nec<1> nx; Nec<1> nxx; Nec<2> ny; nx.func(ny); // SENTAKS HATASI. nx.func(nxx); // LEGAL. } >>> Gerçek sınıfları ne şekilde kullanabiliyorsak, sınıf şablonlarından türetilen sınıfları da aynı yerlerde kullanabiliriz. * Örnek 1, //.. #include #include template class Neco{}; template void func(T x) { std::cout << typeid(T).name() << "\n"; } template void foo(Neco x) { std::cout << typeid(T).name() << "\n"; } int main() { Neco nx; func(nx); // OUTPUT => class Neco foo(nx); // OUTPUT => float } >>> Sınıf şablonlarını iç içe de açtırabiliriz. * Örnek 1, //.. template class Neco{}; int main() { Neco> nx; // 'nx' öyle bir sınıf türünden nesne ki elemanları 'Neco' türünden. } * Örnek 2, STL de dinamik dizilerin dizilerini oluşturmada da kullanılır. //.. int main() { std::vector ivecOne; // => 1D dizi. std::vector> ivecTwo; // Öyle bir vektör ki elemanları da 'int' tutan bir vektör. => 2D dizi. std::vector>> ivecThree; // => 3D dizi. } >>> Sınıflara ait üye şablon sınıfların tanımını gerek 'inline' olarak sınıfın tanımının içinde GEREK SINIFIN DIŞINDA YAPABİLİRİZ. * Örnek 1, 'inline' olarak sınıfın içerisinde yapılması: #include template class Counter{ public: Counter() = default; Counter(T x) : cnt{x}{} void set(T val) { cnt = val; } void get()const { return cnt; } void print()const { std::cout << "[" << cnt << "]\n"; } Counter& operator++() { ++cnt; return *this; } Counter operator++(int) { Counter temp{*this}; ++*this; return temp; } friend std::ostream& operator<<(std::ostream& os, const Counter& x) { return os << "[" << x.cnt << "]\n"; } private: T cnt{}; }; int main() { /* # OUTPUT # [34] [35] [87135] */ Counter cx; cx.set(34); std::cout << cx; ++cx; cx.print(); Counter cy{ 87134 }; ++cy; std::cout << cy << "\n"; } * Örnek 2, tanımlamalarının sınıfın dışında yapılması. Buradaki unutulmaması gereken nokta tanımlar yine başlık dosyasında. Gerçek sınıf kodlarındaki gibi '.cpp' uzantılı dosyada değil. #include template class Counter{ public: Counter() = default; Counter(T x); void set(T val); void get()const; void print()const; Counter& operator++(); Counter operator++(int); friend std::ostream& operator<<(std::ostream& os, const Counter& x); private: T cnt{}; }; template Counter::Counter(T x) : cnt{x} {} template void Counter::set(T x) { Counter temp; xnt = x; } // Sınıfın üye fonksiyonunda sınıfın kendisini kullanırken tekrardan '<>' şeklinde açılım yapılmasına gerek yok. Örneğin, // 'temp' nesnesinin kullanımı LEGALDİR. Çünkü fonksiyonun bloğu ve parametre parantezi 'class-scope' içerisindedir. Fakat // fonksiyonun geri dönüş değerinin türünün yazıldığı yerde, '<>' NİTELEMESİNİ YAPMAK ZORUNDAYIZ. template void Counter::get() const { return cnt; } // Diğer fonksiyonları da yukarıdaki şekilde tek tek tanımlamalıyız. >>> 'std::pair' sınıf şablonunun temsili bir implementasyonu: * Örnek 1, #include #include template struct Pair{ Pair() = default; Pair(const T& t, const U& u) : first{t}, second{u} {} T first{}; U second{}; }; // Standart küpüthanedeki 'pair' sınıfının 'operator<<' fonksiyonu 'overload' EDİLMEMİŞ, yani öyle bir sınıfı yok. template std::ostream& operator<<(std::ostream& os, const Pair& p) { return os << "[" << p.first << "|" << p.second << "]\n"; } int main() { /* # OUTPUT # [0|0] [0|0] [987|Ahmet] [31|C] [[0|0] |[|] ] */ Pair p; std::cout << "[" << p.first << "|" << p.second << "]\n"; Pair px{12, 4.5}; std::cout << "[" << p.first << "|" << p.second << "]\n"; Pair pxx{987, "Ahmet"}; std::cout << pxx; Pair pxxx{ 31, 'C'}; std::cout << pxxx; Pair< Pair , Pair> idCard{}; std::cout << idCard; } >> 'template' KODLAR BAŞLIK DOSYALARINDA BULUNUR. Sadece implementasyonu ilgilendiren, sadece o '.cpp' dosyasında kullanılacak şablonlar, başlık dosyasında olmayabilir. Sadece aşağıdaki gibi görsel bir ayrım yapabiliriz: * Örnek 1, // Neco.h template T sum(T x, T y); #include "Neco.hpp" // Neco.hpp template T sum(T x, T y) { } // main.cpp //.. #include "Neco.h" /*============================================================================================================*/ (24_06_12_2020) > Şablonlar ve Jenerik Programlama (devam): >> Sınıf Şablonları(devam) : >>> Standart kütüphanedeki 'std::pair' sınıfının incelenmesi: >>>> Kendileri, 'utility' başlık dosyasında tanımlanmıştır. Kendi oluşturacağımız 'nutility' başlık dosyasına eklenecek ilk fonksiyonumuz örnektedir. * Örnek 1, #include template std::ostream& operator<< (std::ostream& os, const std::pair& px) { return os << "[" << px.first << "," << px.second << "]"; } >>>> C++17 ile tür çıkarımı 'Ctor.' ile Sınıf Şablonlarına da geldi. Fakat öncesinde tür çıkarımı nasıl yapılıyordu bir bakalım; * Örnek 1, #include #include #include using namespace std; template std::ostream& operator<< (std::ostream& os, const std::pair& px) { return os << "[" << px.first << "," << px.second << "]"; } template std::pair MakePair(const T& t, const U& u) { return std::pair{t, u}; } int main() { /* # OUTPUT # [12,5.6] [[A,12],[alican,4.5]] */ auto p = MakePair(12, 5.6); std::cout << p << "\n"; // Orjinal Hali => make_pair(12, 5.6); auto pp = make_pair( make_pair('A', 12), make_pair("alican", 4.5) ); std::cout << pp << "\n"; } >>>> İki 'std::pair' arasındaki karşılaştırma kriteri şu şekildedir: 'first' değeri küçük olan 'pair' küçüktür. Eğer 'first' değerleri eşitse 'second' değeri küçük olan küçüktür. Hadi gelin standarttaki 'pair' sınıfında olan 'operator<' fonksiyonu nasıl implemente edilmiş. * Örnek 1, template bool operator<(const std::pair& px, const std::pair& py) { return px.first < py.first || ( !(py.first < px.first) && px.second < py.second ); // Buradaki, // I: Kısa devre mekanizmasını tetiklemesi için yazılmıştır. 'first' değeri küçük ise o kısım 'true' olacak // ve deyimin geri kalanı HESAPLANMAYACAKTIR. // II ve III : Buradaki amaç '==' operatörünü kullanmamak, sadece '<' operatörünü kullanmak. Belki 'T' veya // 'U' türleri için 'operator==' overload edilmemişse diye. } >>>> Ayrıca yine iş bu 'std::pair' sınıfının 'swap' fonksiyonu vardır. >>>> 'std::pair' sınıfının 'Copy Semantics' fonksiyonları bir 'Member Template' olmasından ötürü, 'std::pair' nesneleri birbirine KOPYALANABİLMEKTEDİR. Fakat unutulmamalıdır ki 'std::pair' sınıfının öğeleri de birbirine atanabilir olması gerekmektedir. * Örnek 1, //.. class Data{}; int main() { std::pair px; std::pair py; px = py; // SENTAKS HATASI. Çünkü 'Data' sınıfı 'std::string' sınıfına BU HALİ İLE ATANAMAZ. } >>>>> 'Member Template' : Sınıf şablonlarının bünyelerinde fonksiyon şablonları barındırmaları. * Örnek 1, template class Neco{ public: void func(Neco); // Gerçek bir fonksion. }; int main() { Neco nx; Neco ny; nx.func(ny); // GEÇERSİZ. Çünkü her iki nesne de birbirinden farkı. } * Örnek 2, #include template class Neco{ public: template void func(Neco x) // Member template { std::cout << "type of *this : " << typeid(*this).name(); std::cout << "\ntype of param : " << typeid(x).name(); } }; int main() { /* # OUTPUT # type of *this : class Neco type of param : class Neco */ Neco nx; Neco ny; nx.func(ny); // Artık legal. } * Örnek 3, Daha önce yazılan 'Counter' sınıfına, bu mekanizmanın dahil edilmesi. #include template class Counter{ public: Counter() = default; Counter(T x) : cnt{x}{} void set(T val) { cnt = val; } T get()const { return cnt; } void print()const { std::cout << "[" << cnt << "]\n"; } Counter& operator++() { ++cnt; return *this; } Counter operator++(int) { Counter temp{*this}; ++*this; return temp; } template Counter operator=(const Counter& other) { cnt = other.get(); return *this; } friend std::ostream& operator<<(std::ostream& os, const Counter& x) { return os << "[" << x.cnt << "]\n"; } private: T cnt{}; }; int main() { Counter cx{21}; Counter cy{}; cx = cy; } >> Fonksiyon şablonlarının ve sınıf şablonlarının varsayılan (default) template argüman almaları: * Örnek 1, template class Myclass {}; int main() { Myclass m; // C++17 ve sonrasıdaki dönem için LEGAL. Evvelki dönemlerde SENTAKS HATASI. Myclass<> mx; // 'int' açılımı yapılacak. Myclass my; } * Örnek 2, #include template class Myclass { public: Myclass() { std::cout << "type T is : " << typeid(T).name() << "\n"; std::cout << "type U is : " << typeid(U).name() << "\n"; } }; int main() { // # OUTPUT # // type T is : long // type U is : char Myclass mx; // # OUTPUT # // type T is : int // type U is : double Myclass<> my; Myclass<, long> mz; // SENTAKS HATASI. } * Örnek 3, Bir önceki şablon parametresi de sonraki şablon parametresine varsayılan olarak kullanılabilir. #include template class Myclass { public: Myclass() { std::cout << "type T is : " << typeid(T).name() << "\n"; std::cout << "type U is : " << typeid(U).name() << "\n"; } }; int main() { // # OUTPUT # // type T is : long // type U is : char Myclass mx; // # OUTPUT # // type T is : int // type U is : int Myclass<> my; Myclass<, long> mz; // SENTAKS HATASI. } * Örnek 4, varsayılan şablon argümanı olarak bir başka sınıf şablonları da kullanılabilir. #include template class Neco{}; template> class Myclass { public: Myclass() { std::cout << "type T is : " << typeid(T).name() << "\n"; std::cout << "type U is : " << typeid(U).name() << "\n"; } }; int main() { // # OUTPUT # // type T is : long // type U is : char Myclass mx; // # OUTPUT # // type T is : int // type U is : class Neco Myclass<> my; // # OUTPUT # // type T is : class Neco // type U is : class Neco > Myclass> mz; } >> Fonksiyon şablonlarının ve sınıf şablonlarının varsayılan 'non-type' şablon parametre almaları: * Örnek 1, //.. template class Neco{}; int main() { Neco<10, 20> nx; Neco<50> ny; Neco<> nz; } * Örnek 2, STD deki 'std::array' sınıfının temsili bir implementasyonu. //.. template struct Array{ //.. Interface deki diğer fonksiyonlar. T a[n]; }; >>> Argüman olan ifadenin bir 'Constant Expression' olması gerekmektedir. * Örnek 1, template class Myclass{}; constexpr int square(int x) { return x*x; } int main() { int m = 10; Myclass mx; // Sentaks Hatası. const int cm = 200; Myclass my; // LEGAL. constexpr int cem = 300; Myclass mz; // LEGAL. Myclass ma; // LEGAL. } >>> Argüman olan ifadenin Gerçek Sayı olması sentaks hatasıdır. * Örnek 1, template // SENTAKS HATASI. class Myclass{}; >>> Argüman olan ifadenin 'enum' türünden olması LEGALDİR. * Örnek 1, enum Color{blue}; template class Myclass{}; >>> Argüman olan ifade adres türü de olabilir. Fakat adresi geçilecek nesnenin 'static' ÖMÜRLÜ OLMASI GEREKMEKTEDİR. Buna fonksiyon adresleri de dahildir. * Örnek 1, template class Myclass{}; void func(int); // template template class Neco{}; int main() { int x = 10; Myclass<&x> mx; // SENTAKS HATASI. Neco m; // LEGAL. } >>> C++ 17 itibariyle 'auto' kelimesini de kullanabiliriz. * Örnek 1, template class Myclass{}; int y = 56; int main() { Myclass<12> mx; // '12' rakamını gördüğü için => 'template' şeklinde açılım yapıyor. Myclass<&y> my; // 'auto' yerine gelen tür 'int*' olacaktır. } >>> STL içerisinde bulunan 'type_traits' başlık dosyasının temsili implementasyonu: * Örnek 1, template struct IntegralConstant{ typedef T value_type; auto constexpr value = n; }; using TrueType = IntegralConstant; using FalseType = IntegralConstant; int main() { } >> Şablonların Özelleştirilmesi; 'Template Instantiation', derleyicinin bir şablondan hareketle gerçek kodu yazması olayıdır. İş bu şablondan üretilen ürün ise bir 'Template Instance'. 'template' karşılığı oluşturulan ürün manasına gelen teknik kelime ise 'Template Specialization'. Bu teknik kelime bünyesinde iki şey barındırmaktadır. Bunlar; >>> Derleyicinin, şablona bakarak, gerçek kodu yazdığında ortaya çıkan ürün. >>> Programcı olarak bizlerin belli başlı türlerin kodunu bizzat kendimizin yazması ve derleyiciye bu türler için gerçek kod yazmaması sonucu ortaya çıkan ürün. Bu yaklaşım da kendi içinde iki adet araç barındırmakta; >>>> 'Explicit Specialization' / 'Template Full Specialization' : Hem fonksiyon hem de sınıf şablonlarında kullanılabilir. Aşağıdaki örneği inceleyelim. Normal şablon oluşturur gibi ama sadece 'template<>' yazarak, bu şekilde bir özelleştirme yapabiliriz. 'Function Overload Resolution' A KATILMAZLAR. * Örnek 1, //.. template T Max(T x, T y) { return x < y ? y : x; } int main() { std::cout << Max(12, 56) << "\n"; // Doğru çalışacaktır. const char* p1{"zeynep"}; const char* p2{"aysegul"}; std::cout << Max(p1, p2) << "\n"; // İlgili yazıların tutulduğu adresleri karşılaştıracaktır. Yazıların kendileri KARŞILAŞTIRILMAYACAKTIR. // Peki bu problemi nasıl çözeriz? El-cevap: aşağıdaki örnekleri inceleyelim. } * Örnek 2, Gerçek bir fonksiyon yazarak: template T Max(T x, T y) { return x < y ? y : x; } const char* Max(const char* p1, const char* p2) { return strcmp(p1, p2) > 0 ? p1 : p2; } int main() { std::cout << Max(12, 56) << "\n"; // Doğru çalışacaktır. const char* p1{"zeynep"}; const char* p2{"aysegul"}; std::cout << Max(p1, p2) << "\n"; // Artık doğru çalışacaktır. // Peki bu gerçek fonksiyonu fonksiyon şablonumuza nasıl gömebiliriz? El-cevap: aşağıdaki örneği inceleyelim. } * Örnek 3, Yukarıdaki örnekteki gerçek kodu sınıf şablonuna gömülmesi: template // Normal 'Function Template' T Max(T x, T y) { return x < y ? y : x; } template<> // 'Explicit Specialization'. Yukarıdaki şablon olmasaydı, bu kısım sentaks hatası olacaktır. const char* Max(const char* p1, const char* p2) { return strcmp(p1, p2) > 0 ? p1 : p2; } int main() { std::cout << Max(12, 56) << "\n"; // Doğru çalışacaktır. const char* p1{"zeynep"}; const char* p2{"aysegul"}; std::cout << Max(p1, p2) << "\n"; // Artık doğru çalışacaktır. } * Örnek 4, Pekiştirme örneği. #include template void func(T x) { std::cout << "primary template, T is type of " << typeid(T).name() << "\n"; } template<> void func(int x) { std::cout << "func explicit specialization.\n"; } template<> void func(char x) { std::cout << "func explicit specialization.\n"; } int main() { /* # OUTPUT # func explicit specialization. primary template, T is type of double primary template, T is type of float func explicit specialization. */ func(5); func(5.); func(5.f); func('A'); } * Örnek 5, Mülakat Sorusu: //.. template void func(T x) { std::cout << "I\n"; } template void func(int* x) { std::cout << "II\n"; } template void func(T* x) { std::cout << "III\n"; } int main() { int* ptr = nullptr; func(ptr); // 'III' numaralı seçilecektir. Çünkü 'II' numaralı bir 'explicit specialization' dolayısıyla // 'function overload resolution' DAHİL EDİLMİYOR. // 'Partial Ordering Rules' kurallarına göre daha çok deklaratör içeren seçilecektir. // İş bu sebepten ötürü 'III'. // Eğer bir şekilde 'I' seçilmiş olsaydı, bu sefer de 'II' numaralı özelleştirilmiş kullanılacaktı. } * Örnek 6, Mülakat Sorusu //.. template void func(T x) { std::cout << "I\n"; } template void func(T* x) { std::cout << "II\n"; } template void func(int* x) { std::cout << "III\n"; } int main() { int* ptr = nullptr; func(ptr); // 'III' numaralı seçilecektir. Çünkü kendisi 'Function Overload Resolution' ve 'Partial Ordering Rule' // kurallarına göre seçilen 'II' numaralının özelleştirilmiş versiyonu olduğu için. } * Örnek 7, Sınıf şablonlarında kullanımı: Özelleştirilmiş versiyon ile 'primary' durumdaki version farklı 'interface' sahip olabilir. #include template class Myclass{ public: Myclass() { std::cout << "Myclass primary template.\n"; } void f1(){} }; template<> class Myclass{ public: Myclass() { std::cout << "Myclass explicit specialization.\n"; } void foo1(){} }; int main() { /* # OUTPUT # Myclass primary template. Myclass primary template. Myclass explicit specialization. */ Myclass f1; Myclass d1; Myclass i1; f1.foo(); // Sentaks hatası. Name-look-up hatası. i1.f1(); // Sentaks hatası. Name-look-up hatası. } * Örnek 8, STL deki 'vector' sınıfının 'bool' türden açılımı bir 'specialization'. Dolayısıyla 'int' türden bir açılımdaki 'interface' fonksiyonlarının hepsine sahip değil. Hatta 'bool' açılımında olan ama diğer tür açılımında olmayan fonksiyonlar da vardır. Fakat bunun yerine 'bitset' sınıfını KULLANMALIYIZ. //.. int main() { std::vector vec; vec.flip(); // Specialization a özel bir fonksiyon. vector ivec; ivec.flip(); // Sentaks hatası. Name look-up. } * Örnek 9, 'primary' şablondaki şablon parametresi kadar 'explicit' versiyonunda da parametre olmalı. Her iki versiyondaki şablon parametre adetleri aynı olmalı. >>>> 'Partial Specialization' : Sadece sınıf şablonlarında kullanılabilir. Aşağıdaki örnekleri inceleyelim. * Örnek 1, 'pointer' türler için özelleştirme yapılması. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T*).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Partial specialization. => Myclass Primary template. => Myclass Partial specialization. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m4; Myclass m2; Myclass m3; Myclass m5; // Çıktıdan da görülebildiği üzere 'pointer' tipler için bizim özelleştirdiğimiz version, diğerleri için asıl // şablon kullanılmaktadır. } * Örnek 2, Diziler için özelleştirme yapılması. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T*).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; // 'm1' ve 'm2' nin FARKLI TÜRLER OLDUĞU unutulmamalıdır. } * Örnek 3, Belirli bir dizi açılımı için de kullanılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T[31]).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; } * Örnek 4, Referans tipler için de özelleştirme yapılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T&).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; // 'm1' ve 'm2' nin FARKLI TÜRLER OLDUĞU unutulmamalıdır. } * Örnek 5, birden fazla şablon parametresi için de özelleştirme yapılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << "," << typeid(U).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T).name() << "," << typeid(T).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; } * Örnek 6, birden fazla şablon parametresi olan durumlarda 'non-type' parametreler için de özelleştirme yapılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << "," << typeid(U).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T).name() << "," << typeid(int).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; Myclass m4; } * Örnek 7, Asıl şablonda kullanılan şablon parametresi ile özelleştirme için yazılan şablona ait şablon parametrelerinin adetlerinin aynı olmaları BİR ZORUNLULUK DEĞİLDİR. Fakat özelleştirmeyi yazdığımız yer de aynı adet olmak zorundadır. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass>{ public: Myclass() { std::cout << "Partial specialization. => Myclass< std::pair<" << typeid(T).name() << "," << typeid(U).name() << ">>\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass< std::pair> */ Myclass m0; Myclass m1; Myclass m2; Myclass m3; Myclass> m4; // Günün sonunda yine özelleştirilmiş şablon için bir adet argüman bilgisi geçiyoruz fakat dolaylı yoldan // birden fazla tür bilgisi geçmiş oluyoruz. } * Örnek 8, Sadece 'non-type' parametreye sahip asıl şablonlar da özelleştirilebilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << m << ">\n"; } }; template<> class Myclass<31>{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << 31 << "\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass<5> Primary template. => Myclass<8> Partial specialization. => Myclass<31 */ Myclass<5> m1; Myclass<8> m2; Myclass<31> m3; } * Örnek 9, döngü kullanmadan 1-100 arasındaki yazıları ekrana yazdırma: #include // Approach - I struct Neco{ public: Neco(){ static int x{1}; std::cout << x++ << " "; } }; // Approach - II template struct Data : public Data { Data() { std::cout << n << " "; } }; template<> struct Data<0> {}; int main() { /* # OUTPUT # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 ------------------------------------------------------------ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 */ // Approach - I Neco list[100]; std::cout << "\n------------------------------------------------------------\n"; // Approach - II Data<100> mydata; } * Örnek 10, Faktöriyel hesaplanması. // Approach - I constexpr int factorial(int n) { return n < 2 ? 1 : n * factorial(n-1); } // Approach - II template struct Factorial{ static const int value = n * Factorial::value; }; template<> struct Factorial<0>{ static const int value = 1; }; int main() { // Approach - I constexpr auto val = factorial(5); // Gerek fonksiyonun bir 'constexpr' fonksiyon olması gerek geçilen argümanın bir 'constant' olmasından // dolayı bu fonksiyonun değeri derleme zamanında hesaplanmıştır. // Approach - II const auto valTwo = Factorial<5>::value; } >> 'Alias Template' : 'typedef' ve 'using' eş isim bildirimlerinin şablonlar için olan versiyonu. * Örnek 1, typedef int Word; //using Word = int; typedef int(*fPtr)(int); // using fptr = int(*)(int); using intArray10 = int[10]; template // 'primary template' class Myclass{}; template // 'Alias Template' using MyclassInt = Myclass; int main() { intArray10 myArray; intArray10 myArrayTwo[20]; //int myArrayTwo[20][10] Myclass m1; // Normal yollar ile nesne oluşturma MyclassInt m2; // => Myclass m2; } * Örnek 2, //.. template using greaterSet = std::set>; int main() { greaterSet mySet; // => std::set> mySet; } * Örnek 3, //.. template using myCustomPtr = T*; int main() { int x = 200; myCustomPtr ptr = &x; // => int* ptr = &x; } * Örnek 4, //.. template using greaterMap = std::map>; int main() { std::map> m1; // => greaterMap m1; } > Standart Template Library (STL) : Bu kütüphane 'Containers', 'Iterators', 'Algorithms', 'Adapters', 'Function Objects' vs. şeklinde bölümlerden oluşmaktadır. >> Bu gruplardan, >>> 'Containers' grubu veri yapılarını temsil etmektedirler. Kendi içinde üç gruba ayrılmışlardır. Bu gruplar 'Sequence Containers', 'Associative Containers' ve 'Unordered Associative Containers' şeklinde gruplardır. Bu üç grup ise şu temel sınıf şablonlarını bünyelerinde barındırmaktadır. Bunlar; >>>> 'Sequence Containers' : 'std::vector', 'std::deque', 'std::list', 'std::forward_list', 'std::array' ve 'std::string' sınıf şablonlarından; >>>>> 'std::vector' : Dinamik dizi veri yapısıdır. >>>>> 'std::deque' : Dinamik dizilerin dizisi şeklinde bir veri yapısıdır. >>>>> 'std::list' : Çifte bağlı liste veri yapısıdır. >>>>> 'std::forward_list' : Tekli bağlı liste veri yapıdır. >>>>> 'std::array' : C dilindeki dinamik olmayan dizilerin bir sınıf ile kaplanmış halidir. >>>>> 'std::string' : Dinamik dizi veri yapısıdır. Karakterler ile uğraşmaktadır. >>>> 'Associative Containers' : 'std::set', 'std::multiset', 'std::map' ve 'std::multimap' sınıf şablonlarından; >>>>> 'std::set' : İkili arama ağacının implementasyonudur. Bünyesinde sadece o türden bir tane değer tutar. >>>>> 'std::multiset' : 'std::set' den farkı, o türden birden fazla değeri bünyesinde tutabilmektedir. >>>>> 'std::map' : Eşlemede kullanılır. Bünyesinde o türden sadece bir çift bulunur('key-value' pair). >>>>> 'std::multimap' : 'std::map' den farkı, o türden birden fazla çift bulundurabilir. >>>> 'Unordered Associative Containers' : 'std::unordered_set', 'std::unordered_multiset', 'std::unordered_map' ve 'std::unordered_multimap' sınıf şablonlarından; >>>>> Diğer programlama dillerindeki 'hash_set', 'hash_map' veri yapılarının C++ dilindeki karşılığıdır. >>> 'Iterators' grubu göstericilerin daha soyutlaştırılmış halleridir. Kaplardaki öğelerin konumlarını tutma amacı taşırlar. Kapların 'nested-type' ları olacak şekilde implementasyonları yapılmıştır. Yine kaplar gibi sınıf şablonlarıdır. Her kapta bulunurlar. >>> 'Algorithms' grubu ise fonksiyon şablonlarıdır. Kaplar üzerinde iş yapacak algoritmaları implemente etmektedirler. Parametreleri iteratör şeklindedirler. >> Bu kütüphanenin amacı programcının rahat kod yazmasından ziyade yazılan kodun hızlı çalışmasını sağlamaktır. Dolayısla kullanıcı dostu değildir. >> Bu kütüphane nesne yönelimli bir kütüphaneden ziyade jenerik bazlı bir kütüphane. >> 'Iterators' grubunun incelenmesi: Aşağıdaki örneği inceleyelim. * Örnek 1, #include #include #include #include template void print_range(Iter beg, Iter end) { while(beg != end) std::cout << *beg++ << " "; std::cout << "\n"; } // Buradaki 'Iter' bir şablon parametresidir. 'Iter' e karşılık gelen şeyler // i. 'operator!=' fonksiyonunu 'overload' etmeli veya operand olabilmeli, // ii. 'operator*' ve 'operator++' fonksiyonlarını 'overload' etmeli veya bunlara operand olabilmeli, // iii. 'operator<<' fonksiyonunu 'overlaod' etmeli veya buna operand olabilmeli. // Aksi halde sentaks hatası alacağız. // Derleyicinin şablondan hareketle 'I' nolu çağrı için yazacağı fonksiyon: /* void print_range(int* beg, int* end) { while(beg != end) std::cout << *beg++ << " "; std::cout << "\n"; } */ // Derleyicinin şablondan hareketle 'II' nolu çağrı için yazacağı fonksiyon: /* void print_range(std::vector::iterator beg, std::vector::iterator end) { while(beg != end) std::cout << *beg++ << " "; std::cout << "\n"; } */ int main() { /* # OUTPUT # 1 4 7 2 31 1 4 7 2 31 1 4 7 2 31 ali veli merve ayse metin */ int a[] = { 1, 4, 7, 2, 31 }; print_range(a, a + 5); // I : Tür çıkarımı yapılacak. 'Iter' ile birlikte '&' deklaratörü kullanılmadığından, // 'array-decay' mekanizması çalışacaktır. std::vector ivec{ 1, 4, 7, 2, 31 }; print_range(ivec.begin(), ivec.end()); // II : 'Iter' için tür çıkarımı bu sefer 'iterator' şeklinde yapılacaktır. std::list ilist{ 1, 4, 7, 2, 31 }; print_range(ilist.begin(), ilist.end()); // III : 'Iter' için tür çıkarımı bu sefer 'iterator' şeklinde yapılacaktır. std::list myNameList{ "ali", "veli", "merve", "ayse", "metin" }; print_range(myNameList.begin(), myNameList.end()); // Çıktıdan da görüldüğü üzere tek bir fonksiyon şablonu birbirinden farklı türler için kullanıldı. // Peki bu nasıl mümkün oldu? El-cevap : Aşağıdaki örneği incelediğim. } * Örnek 2, Kapların ve iteratörlerin temsili gösterimi: template class Vector{ public: class Iterator{ public: T& operator*(); Iterator& operator++(); Iterator operator++(int); T* operator->(); }; //.. Iterator begin(); // Kapta tutulan ilk öğeyi gösteren bir iteratör döndürür. Iterator end(); // Kapta tutulan en sonki öğenin konumundan bir sonraki konumu gösteren bir iteratör döndürür. // Dolayısıyla 'derefence' EDEMEYİZ çünkü geçerli bir konum değildir. Karşılaştırma amacıyla kullanabiliriz. }; //.. int main() { std::vector ivec{ 1, 4, 7, 9, 2 }; auto iter = ivec.begin(); // std::vector::iterator iter = ivec.begin(); std::cout << *iter << "\n"; // Konumu tutulan nesneye eriştik ve onu yazdırdık. ++iter; // Konum bilgisini bir arttırdık. std::cout << *iter << "\n"; // Konumu tutulan nesneye eriştik ve onu yazdırdık. for(auto iter = ivec.begin(); iter != ivec.end(); ++iter) std::cout << *iter << "\n"; } >>> Bir iteratörü 'derefence' ettiğimiz zaman o konumda nesne bulunması koşulunu sağlaması gereken BİZLERİZ. O konumda geçerli bir nesne olmadığında ilgili iteratöre erişmeye çalışmak 'Tanımsız Davranış' olacaktır. * Örnek 1, //.. int main() { std::vector ivec; auto iter = ivec.begin(); std::cout << *iter << "\n"; // Bu noktada tanımsız davranış olacaktır. } >>> 'range' bilgisi iki konum arasındaki mesafe demektir. Fakat bu konum bilgilerinden ilki 'range' içerisindeki ilk öğeyi kastetmekteyken, ikinci konum bilgisi ise 'range' içerisindeki son öğeden sonraki öğenin bulunduğu konumu kastetmektedir. * Örnek 1, template void print_range(Iter beg, Iter end) { while(beg != end) std::cout << *beg++ << " "; std::cout << "\n"; } int main() { int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; print_range( a, a + 10 ); // 'a' birinci konum bilgisi demektir. Kaptaki ilk öğenin konumudur. // 'a + 10' ise ikinci konum bilgisi demektir. Kaptaki son öğeden sonraki öğenin konumudur. // Yukarıdaki iki konum arası ise bizim 'range' dediğimiz bölümdür. // Çıktı => 0 1 2 3 4 5 6 7 8 9 print_range( a + 5, a + 8 ); // 'a + 5' konumu dahil, 'a + 8' konumu hariç, bu iki konum arasındaki nesneleri yazdıracaktır. // Çıktı => 5 6 7 } >>>> 'range' durumunun geçerli olması için ilk konum bilgisini tutan değişkeni '++' operatörünün operandı yaptığım zaman bir müddet sonra ikinci konum bilgisine EŞİT HALE GELMESİ ZORUNLUDUR. Aksi durumda geçerli bir 'range' söz konusu değildir. * Örnek 1, template void print_range(Iter beg, Iter end) { while(beg != end) std::cout << *beg++ << " "; std::cout << "\n"; } int main() { int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; print_range( a + 5, a + 2 ); // 'a + 5' konumundan başlamamıza rağmen hiç bir zaman 'a+2' konumuna ulaşamayacağız. // Dolayısıyla 'Tanımsız Davranış' olacaktır. } /*============================================================================================================*/ (25_12_12_2020) > Standart Template Library (STL) (devam) : >> 'Iterators' (devam) : >>> C-style dizilerin bittiği yerin adresi 'derefence' edilemezler fakat karşılaştırma amacıyla kullanılabilirler. >>> Peki bir iteratör nesnesi ile neler yapabiliriz? El-cevap: İteratörlerin kategorilerine göre değişmektedir. Genel hatlarıyla iteratörler şu kategorilerden birine ait olmak zorundadır; >>>> 'output_iterator' : 'output_iterator_tag' türünün 'typedef' halidir. Bu '_tag' isimleri boş bir 'struct' şeklindedir. 'ostream_iterator' ve 'ostreambuf_iterator' grupları, bu tip iteratörlere sahiptirler. Bu tip iteratörler 'Copy Constructable' ama 'Default Constructable' DEĞİLDİR. 'operator++' ve 'operator++(int)' operatörlerine, 'operator*'(derefence) ve 'operator->' operatörlerine operand olabiliyor. >>>> 'input_iterator' : 'input_iterator_tag' türünün 'typedef' halidir. Bu '_tag' isimleri boş bir 'struct' şeklindedir. 'istream_iterator' ve 'istreambuf_iterator' grupları, bu tip iteratörlere sahiptirler. Bu tip iteratörler, 'output_iterator' grubunun yapabildiklerini yapabilmektedirler. Ek olarak 'operator==' ve 'operator!=' operatörlerine de operand olabiliyorlar. >>>> 'forward_iterator' : 'forward_iterator_tag' türünün 'typedef' halidir. Bu '_tag' isimleri boş bir 'struct' şeklindedir. 'forward_list', 'unordered_map', 'unordered_multimap', 'unordered_set' ve 'unordered_multiset' grupları bu tip iteratörlere sahiptirler. Bu tip iteratörler 'input_iterator' ve 'output_iterator' gruplarının yapabildiklerini yapabilmektedir. Ek olarak bu tip iteratörler 'Default Constructable' haldedirler. >>>> 'bidirectional_iterator' : 'bidirectional_iterator_tag' türünün 'typedef' halidir. Bu '_tag' isimleri boş bir 'struct' şeklindedir. 'list', 'set', 'multiset', 'map' ve 'multimap' grupları bu tip iteratörlere sahiptirler. Bu tip iteratörler 'forward_iterator' grubunun yaptığı her şeyi yapabilmektedir. Ek olarak 'operator--' ve 'operator--(int)' operatörlerine operand olabilmektedirler. >>>> 'random_access_iterator' : 'random_access_iterator_tag' türünün 'typedef' halidir. Bu '_tag' isimleri boş bir 'struct' şeklindedir. 'vector', 'deque', 'array', 'string' ve 'C-arrays' bu tip iteratör gruplarına sahiptirler. Bu tip iteratörler 'bidirectional_iterator' grubunun yaptığı her şeyi yapabilmektedir. Kendileri birebir 'pointer' gibi davranmaktadırlar. Dolayısıyla ek olarak bir 'C-pointer' ile neler yapabiliyorsak, bu tip iteratörler ile aynılarını yapabiliriz. Dolayısıyla bu kategorideki iteratörlerin de 'interface' bilgileri BİRBİRLERİNDEN FARKLIDIR. Bu kategoriler arasındaki ilişkiyi özetlemek gerekirse; "random_access_iterator( bidirectional_iterator( forward_iterator( input_iterator + output_iterator ) ) )" şeklinde özetleyebilir. Buradan hareketle diyebiliriz ki en geniş yetkilere sahip 'random_access_iterator' iteratör grubudur. 'random_access_iterator', C dilindeki 'göstericiler' ile yapabildiğimiz işlerin aynısını yapabilmektedirler. >>> Bir iteratörün hangi kategoriden olduğunu saptayabilmek için 'iterator' sınıfının 'nested-type' olan 'iterator_category' türünü kullanmalıyız. Yukarıdaki '_tag' isimlerini 'typeid' operatörünün operandı yaparak. * Örnek 1, #include #include #include #include int main() { std::cout << typeid(std::vector::iterator::iterator_category).name() << "\n"; // OUTPUT => struct std::random_access_iterator_tag std::cout << typeid(std::list::iterator::iterator_category).name() << "\n"; // OUTPUT => struct std::bidirectional_iterator_tag std::cout << typeid(std::forward_list::iterator::iterator_category).name() << "\n"; // OUTPUT => struct std::forward_iterator_tag std::cout << typeid(std::istream_iterator::iterator_category).name() << "\n"; // OUTPUT => struct std::input_iterator_tag } >>> İteratörlerde 'const' semantiği : Aşağıdaki örnekleri inceleyelim. * Örnek 1, //.. int main() { std::vector iVec{ 1, 2, 3, 4 }; const std::vector::iterator iter = iVec.begin(); // Artık 'iter' isimli iteratörün kendisi 'const' durumdadır. Artık başka bir konum BİLGİSİ TUTAMAZ. // 'high-level pointer' gibidir. *iter = 35; // I: LEGAL ++iter; // II : SENTAKS HATASI. Çünkü 'const' bir nesne ile 'non-const' fonksiyon çağıramayız. // Peki bizler 'I' numaralı durumun sentaks hatası olmasını istiyorsak, bir diğer değiş ile 'low-level pointer' // durumunun temsili olan bir iteratör oluşturmak istiyorsak, ne yapmalıyız? // El-cevap : İkinci örneği inceleyelim. } * Örnek 2, 'const_iterator' türünden nesne oluşturarak, ki bu tip sınıflar 'low-level pointer' mekanizmasını taklit etmektedir. //.. int main() { std::vector iVec{ 1, 2, 3, 4 }; std::vector::const_iterator iter = iVec.begin(); // Artık 'iter' isimli iteratörü sadece salt okuma amaçlı kullanabiliriz. Kendisi 'const' da değildir. std::cout << "*iter : " << *iter << "\n"; // LEGAL. *iter = 35; // I: SENTAKS HATASI. ++iter; // II : LEGAL. // İŞ BU SEBEPETEN ÖTÜRÜ BİR 'range' OKUMA AMACIYLA DOLAŞILIYORSA, 'const_iterator' TÜRÜNDEN İTERATÖRLER İLE // DOLAŞMALIYIZ. Örnek 3'ü inceleyelim. } * Örnek 3, '.cbegin()' gibi üye fonksiyonlar 'const_iterator' sınıf türünden iteratör döndürmektedir. //.. int main() { std::vector iVec{ 1, 2, 3, 4 }; // SALT OKUMA YAPACAKSAK AŞAĞIDAKİ GİBİ KULLANMALIYIZ: for(auto iter = iVec.cbegin(); iter != iVec.cend(); ++iter) std::cout << iter << ", "; } >>> Herhangi bir 'range' söz konusu olsun. Aşağıdaki örnekleri inceleyelim; Mülakat konusu; * Örnek 1, Kaplarda bulunan '.rbegin()', '.end()' fonksiyonlarının kullanılması: //.. template void print_range(InIter beg, InIter end) { while (beg != end) std::cout << *beg++ << ", "; std::cout << "\n"; } int main() { /* # OUTPUT # 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1, */ std::vector iVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; print_range(iVec.begin(), iVec.end()); print_range(iVec.rbegin(), iVec.rend()); // Peki bu nasıl mümkün hale geldi? El-cevap : Nasıl ki kapların 'iterator' isimli 'nested-type' // sınıfları varsa aynı şekilde 'reverse_iterator' isimli 'nested-type' sınıfları da vardır. } * Örnek 2, İş bu fonksiyonların döndürmüş olduğu iteratörler sırasıyla kaplardaki son ve ilk öğelerin konumlarını tutmaktadır. Yani son öğeden başlıyoruz. //.. int main() { /* # OUTPUT # [9], [8], [8] | [7] | [6] | [5] | [4] | [3] | [2] | [1] | */ std::vector iVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; std::vector::reverse_iterator iter = iVec.rbegin(); // 'reverse_iterator' sınıfı kalıtım yoluyla 'iterator' sınıfından elde edilmektedir. std::cout << "[" << *iter << "], "; // OUTPUT => 9 ++iter; std::cout << "[" << *iter << "], "; // OUTPUT => 8 std::cout << "\n"; while(iter != iVec.rend()) { std::cout << "[" << *(iter++) << "] | "; } // Peki arka plandaki işleyen mekanizma nasıl bir mekanizma? Her ne kadar dizinin son öğesinden sonraki öğenin // konumu geçerli bir konum olsa da dizinin ilk öğesinden bir önceki öğenin konumu geçerli bir konum bilgisi değildir. // O konum bilgisini kullanmak 'Tanımsız Davranış' oluşturur. // El-cevap : '.rbegin()' fonksiyonunun geri dönüş değeri dizinin bittiği yerin adresi. Tıpkı '.end()' fonksiyonu gibi. // Ama '.operator*' fonksiyonu öyle bir 'overload' edilmiş ki tuttuğu konum bilgisinden bir önceki konum bilgisindeki // nesneye eriştirmektedir. } * Örnek 3, '.operator*()', '.operator++()' fonksiyonlarının incelenmesi: //.. int main() { std::vector iVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; auto reverseIter = iVec.rbegin(); // 'reverseIter' aslında konum bilgisi olarak en son öğeden sonraki öğenin konum bilgisini tutmaktadır. std::cout << *reverseIter << "\n"; // Fakat içerik operatörünün operandı olduğunda konumunu tuttuğu öğeye değil, bir önceki konumda bulunan // öğeye eriştirmektedir. // 'reverse_iterator' sınıfındaki '.base()' isimli fonksiyon ise bir 'iterator' döndürmekte olup '.end()' // fonksiyonunun döndürdüğü konum bilgisini tutmaktadır. auto normalIter = reverseIter.base(); if(normalIter == iVec.end()) { std::cout << "Son öğeden sonraki öğenin konumundayız. =>" << *normalIter << "\n"; // Fakat ilgili konumu 'derefence' ETMEMELİYİZ. 'Tanımsız Davranış' oluşturur. } // '++' operatörünün operandı olduğunda içerideki reverse_iteratör bir azalmaktadır. ++reverseIter; // Artık 'reverseIter' direkt olarak son öğenin konum bilgisini tutmaktadır. std::cout << "Sondan bir önceki öğe : " << *reverseIter << "\n"; // 'derefence' edildiğinde, bir önceki öğeyi döndürmektedir. auto normalIterTwo = reverseIter.base(); // Konum bilgisi olarak en son öğeden bir önceki öğenin konum bilgisini tutmaktadır. std::cout << "Son öğe : " << *normalIterTwo << "\n"; /* # OUTPUT # 9 Son öğeden sonraki öğenin konumundayız. => 0 Sondan bir önceki öğe : 8 Son öğe : 9 */ // YANLIŞ KULLANILM std::cout << "reverseIter | normalIter\n"; while(reverseIter != iVec.rend()) std::cout << *reverseIter++ << " | " << *normalIterTwo++ << "\n"; /* # OUTPUT # reverseIter | normalIter 8 | 9 7 | 0 6 | 1041 5 | 0 4 | 545005620 3 | 808793141 2 | 875639609 1 | 1869482553 */ // Çıktıdan da görüldüğü üzere 'Tanımsız Davranış' meydana gelmiştir. // Çünkü '.base()' sınıfı 'iterator' döndürmektedir. // DOĞRU KULLANILM std::cout << "reverseIter | normalIter\n"; while(reverseIter != iVec.rend()) std::cout << *reverseIter++ << " | " << *normalIterTwo-- << "\n"; /* # OUTPUT # reverseIter | normalIter 8 | 9 7 | 8 6 | 7 5 | 6 4 | 5 3 | 4 2 | 3 1 | 2 */ // Dolayısla o iteratörü '--' operatörü ile birlikte kullanmamız gerekmektedir. } * Örnek 4, '.base()' fonksiyonunun kullanımına bir örnek: //.. template void print_range(InIter beg, InIter end) { while (beg != end) std::cout << *beg++ << ", "; std::cout << "\n"; } int main() { std::vector iVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; auto reverseIter_begin = iVec.rbegin(); auto reverseIter_end = iVec.rend(); print_range(reverseIter_begin, reverseIter_end); // OUTPUT => 9, 8, 7, 6, 5, 4, 3, 2, 1, print_range(reverseIter_end.base(), reverseIter_begin.base()); // OUTPUT => 1, 2, 3, 4, 5, 6, 7, 8, 9, } * Örnek 5, Peki bu tip iteratör ile algoritmaları kombine edersek ne olur? : //.. int main() { /* # OUTPUT # I: 77 96 46 13 79 93 20 44 4 72 60 39 94 83 25 93 4 65 58 88 71 89 16 70 99 6 15 ----------------------------------------------------------------------------- II: 4 4 6 13 15 16 20 25 39 44 46 58 60 65 70 71 72 77 79 83 88 89 93 93 94 96 99 ----------------------------------------------------------------------------- III: 99 96 94 93 93 89 88 83 79 77 72 71 70 65 60 58 46 44 39 25 20 16 15 13 6 4 4 ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 27, Irand{1, 100}); pc(iVec); // I sort(iVec.begin(), iVec.end()); pc(iVec); // II sort(iVec.rbegin(), iVec.rend()); pc(iVec); // III } >> STL kütüphanesinde en sık kullanılan algoritmalar; >>> 'Copy' algoritması : En sık kullanılan algoritmalardan bir tanesidir. Bir 'range' içerisindeki öğeleri başka bir 'range' içerisine kopyalamaktadır. Gelin temsili bir implementasyonuna bakalım. * Örnek 1, //.. template OutIter MyCopy(InIter beg, InIter end, OutIter destbeg) { while(beg != end) *destbeg++ = *beg++; return destbeg; } // 'InIter' ismine karşılık gelecek tür her neyse aşağıdaki işlemleri desteklemesi gerekmektedir; // 'operator!=' fonksiyonuna operand olabilme, // 'operator*' fonksiyonuna operand olabilme, ki burada 'derefence' için kullanılmaktadır, // 'operator++(int)' fonksiyonuna operand olabilme. // Yukarıdaki tablodan baktığımız zaman bu operatörlere operand olabilen minimum iteratör grubu 'input_iterator' // grubudur. // İşte bu neden dolayı, kullanıcıya da mesaj verebilmek adına, 'input_iterator' manasına gelen 'InIter' ismi // KULLANILMIŞTIR. // Benzer neden dolayı 'OutIter' ismine karşılık gelen minimum iteratör grubu 'output_iterator' olduğundan, bu // şekilde bir isim verilmiştir. // İSİMLENDİRME KONVENSİYONU BU ŞEKİLDEDİR. // Yine yukarıdaki fonksiyonun geri dönüş değer konum bilgisi olduğu için, bu fonksiyona geçilen üçüncü argüman // ile birlikte kullanılması durumunda, bir 'range' oluşturulabilir. * Örnek 2, //.. int main() { std::vector iVec{ 1, 2, 3, 4, 5, 6 }; std::vector vx; copy(iVec.begin(), iVec.end(), vx.begin()); // Bu fonksiyon çağrısı 'Tanımsız Davranış' a neden olacaktır. Çünkü 'vx' boş bir kap. Usüle uygun çalışması için en // az 6 elemana sahip olması gerekiyordu. Velev ki 'vx' üç elemanlı olsaydı bile yine çalışma zamanında hata alacaktık. // Peki bu problemleme alternatif çözümler nelerdir? // El-cevap: aşağıdaki örneği inceleyelim. // UNUTULMAMALIDIR Kİ BOŞ BİR KAPTA '.end()' ve '.begin()' fonksiyonları aynı konum bilgisi döndürmektedir. // İş bu konum bilgileri, ilgili kap boş olduğundan, 'derefence' EDİLMEMELİDİR. } * Örnek 3, //.. // Bu algoritmadan derleyiciye öyle bir fonksiyon kodu yazdıracağım ki kaynak aralıktaki öğeler, bir kaba, kabın // '.push_back()' fonksiyonu ile 'insert' edileceklerdir. template OutIter MyCopy(InIter beg, InIter end, OutIter destBeg) { while (beg != end) *destBeg++ = *beg++; return destBeg; } // Öyle bir sınıf şablonu oluşturacağız ki 'OutIter' yerine bizimki kullanılacak. template class BackInsertIterator{ public: BackInsertIterator(C& other) : m_r{other} {} // Artık bu sınıf türünden bir nesne hayata geldiğide veri elemanı olan 'm_r', 'Ctor.' fonksiyonuna // geçilen argümana referans olacaktır. BackInsertIterator& operator*() { return *this; } // Bu fonksiyona yapılan çağrı, bu fonksiyonu çağıran nesnenin kendisini döndürmektedir. // Örneğin, '*destBeg' demek aslında 'destBeg' nesnesinin kendisi demektir. BackInsertIterator& operator++() { return *this; } // Bu fonksiyona yapılan çağrı, bu fonksiyonu çağıran nesnenin kendisini döndürmektedir. // Örneğin, '++destBeg' demek aslında 'destBeg' nesnesinin kendisi demektir. BackInsertIterator& operator++(int) { return *this; } // Bu fonksiyona yapılan çağrı, bu fonksiyonu çağıran nesnenin kendisini döndürmektedir. // Örneğin, 'destBeg++' demek aslında 'destBeg' nesnesinin kendisi demektir. // Bu fonksiyonun parametresi olan '::value_type', kapta tutulan öğelerin tür bilgisini döndürmektedir. // Aşağıdaki kullanımı örnek alırsak 'C::value_type' yerine 'int' gelecektir. Buradan da hareketle 'value' // isimli değişkenin türü 'const int&' olacaktır. Kullanım şeklinin de parantez içerisindeki gibi olduğunu // UNUTMAYALIM. Önce 'const' niteleyicisi, sonrasında da 'typename' anahtar sözcüğü ve son olarak da // 'C::value_type' niteleyicisi. En son olarak da '&' deklaratörü. BackInsertIterator& operator=( const typename C::value_type& value ) { m_r.push_back(value); // 'm_r' isimli veri elemanımız, elemanları 'int' türden olan bir kaba referans olduğundan, // bu çağrı da legal hale gelmiştir. return *this; // Bu fonksiyona yapılan çağrı, bu fonksiyonu çağıran nesnenin kendisini döndürmektedir. } private: C& m_r; // Artık 'C' her ne tür ise 'm_r' ise o türden bir referans. }; // Öyle bir fonksiyon şablonu yazacağız ki yukarıdaki 'BackInsertIterator' sınıf türünü döndüreceğiz. // Bu fonksiyon şablonundan yazılan fonksiyona geçilen argümanın türü neyse, 'other' isimli değişken de o türden bir // referans olacaktır. Aşağıdaki örneği baz alırsak; // 'other' değişkeninin türü 'std::vector' türüne referanstır. Yani 'T' yerine 'std::vector' gelmektedir. template BackInsertIterator BackInserter(T& other) { return BackInsertIterator{ other }; // 'BackInsertIterator' sınıfının veri elemanı olan 'm_r' ise bu çağrı sonucunda 'std::vector' türüne referans hale gelmiştir. } int main() { /* # OUTPUT # 2 4 6 8 10 ----------------------------------------------------------------------------- 2 4 6 8 10 ----------------------------------------------------------------------------- 2 4 6 8 10 ----------------------------------------------------------------------------- */ std::vector iVec{ 2, 4, 6, 8, 10 }; std::vector vx; BackInsertIterator> myCustomIter(vx); // Tür çıkarımı sonucunda sınıf şablonu parametresi olan 'C' yerine 'std::vector>' gelecektir ve // derleyicinin yazacağı sınıfının veri elemanı olan 'm_r' artık 'vx' öğesine referans haline gelecektir. // Yani 'm_r' demek 'vx' demektir. MyCopy(iVec.begin(), iVec.end(), myCustomIter); // Üçüncü argüman olan sınıf '.operator*()', '.operator++()' ve 'operator==()' fonksiyonlarını 'overload' // etmelidir ki sentaks hatası almayalım. pc(vx); // Artık hedefteki kaba sondan ekleme yaparak bir kopyalama işlemi gerçekleştirildi. // İsimlendirilmiş bir sınıf nesnesi yerine geçici nesne de oluşturabilirdik; std::vector vxx; MyCopy(iVec.begin(), iVec.end(), BackInsertIterator>{ vxx }); pc(vxx); // Her ne kadar geçici nesne kullansak da yazım uzunluğundan dolayı, bunu bir fonksiyon çağrısı ile // de yaptırabiliriz: std::vector vxxx; MyCopy(iVec.begin(), iVec.end(), BackInserter(vxxx)); pc(vxxx); // PEKİ 'STL' İÇERİSİNDE YUKARIDAKİ MEKANİZMAYI KULLANAN MEKANİZMA VAR MIDIR? // El-cevap : Aşağıdaki örneği inceleyelim. } * Örnek 4, 'back_inserter' iteratör sınıfının kullanılması: //.. int main() { /* # OUTPUT # ----------------------------------------------------------------------------- 2 4 6 8 10 ----------------------------------------------------------------------------- */ std::vector iVec{ 2, 4, 6, 8, 10 }; std::vector vx; pc(vx); copy(iVec.begin(), iVec.end(), std::back_inserter(vx)); pc(vx); // Artık 'back_inserter' fonksiyonunun arka planda yaptıklarını da görmüş olduk. Yukarıdaki kullanım ile boş // bir kaba da yazma işlemi yapabiliyoruz. Bütün yazma algoritmaları için bunu kullanabiliriz. UNUTULMAMALIDIR // Kİ 'vx' KABINA SONDAN EKLEME YAPILMAKTADIR. } * Örnek 5, 'front_inserter' iteratör sınıfının kullanılması: //.. int main() { /* # OUTPUT # I: ----------------------------------------------------------------------------- II: 2 4 6 8 10 ----------------------------------------------------------------------------- III: 10 8 6 4 2 2 4 6 8 10 ----------------------------------------------------------------------------- */ std::vector iVec{ 2, 4, 6, 8, 10 }; std::list iList; pc(iList); // I copy(iVec.begin(), iVec.end(), back_inserter(iList)); // İlgili listeye sondan ekleme yapıldı. pc(iList); // II copy(iVec.begin(), iVec.end(), front_inserter(iList)); // İlgili listeye baştan ekleme yapıldı. pc(iList); // III } * Örnek 6, 'back_inserter' ve 'front_inserter' sınıflarının birlikte kullanılması ve 'overwrite' durumu: //.. int main() { /* # OUTPUT # I: ----------------------------------------------------------------------------- II: 2 4 6 8 10 ----------------------------------------------------------------------------- III: 10 8 6 4 2 2 4 6 8 10 ----------------------------------------------------------------------------- IIII: 20 40 60 80 100 2 4 6 8 10 ----------------------------------------------------------------------------- */ std::vector iVecTwo{ 20, 40, 60, 80, 100 }; std::vector iVec{ 2, 4, 6, 8, 10 }; std::list iList; pc(iList); // I copy(iVec.begin(), iVec.end(), back_inserter(iList)); // İlgili listeye sondan ekleme yapıldı. pc(iList); // II copy(iVec.begin(), iVec.end(), front_inserter(iList)); // İlgili listeye baştan ekleme yapıldı. pc(iList); // III copy(iVecTwo.begin(), iVecTwo.end(), iList.begin()); // İlgili listenin başındaki öğeler 'overwrite' edildi. pc(iList); // IIII } >>> 'Count' algoritmasıdır: Bir 'range' içerisindeki öğeleri saymaktadır. Gelin temsili bir implementasyonuna bakalım. * Örnek 1, //.. template int MyCount(InIter beg, InIter end, const T& t) { int cnt{}; while(beg != end) { if( *beg == t) ++cnt; ++beg; } return cnt; } * Örnek 2, //.. int main() { std::vector svec; fc(svec, 100000, rname); // İlgili 'svec' isimli kap, 'rname' isimli callable vasıtasıyla 100'000 adet öğeye sahip olmuştur. std::string nameToLookUp = "ayse"; std::cout << count(begin(svec), end(svec), nameToLookUp) << " adet " << nameToLookUp << "bulundu.\n"; } * Örnek 3, //.. int main() { std::list myList; fc(myList, 1000000, Date::random); // İlgili 'myList' isimli kap, 'Date::random' isimli callable vasıtasıyla 1'000'000 adet öğeye sahip olmuştur. Date dateToLookUp{10, 12, 1999}; std::cout << count(myList.begin(), myList.end(), dateToLookUp) << " adet " << dateToLookUp << "bulundu.\n"; } >>> 'find' algoritması (Mülakat sorusu) : Bir 'linear-search' algoritmasıdır. Arama için kullanılır. * Örnek 1, Bir kap içerisindeki belirli bir değere sahip son öğeyi sil. //.. int main() { /* # OUTPUT # I: 1 2 5 9 6 5 7 8 2 3 4 82 2 3 ----------------------------------------------------------------------------- II: 2 5 9 6 5 7 8 2 3 4 82 2 3 ----------------------------------------------------------------------------- III: 2 5 9 6 5 7 8 2 3 4 82 2 ----------------------------------------------------------------------------- IIII: 2 5 9 6 5 7 8 2 3 4 82 ----------------------------------------------------------------------------- */ std::vector iVec{ 1, 2, 5, 9, 6, 5, 7, 8, 2, 3, 4, 82, 2, 3 }; pc(iVec); // I iVec.erase(iVec.begin()); // '.erase()' fonksiyonu bir konum bilgisi almakta ve o konumdaki öğeyi silmektedir. Dolayısıyla kaptaki ilk öğe // silinmiş olacaktır. pc(iVec); // II iVec.erase(iVec.end() - 1); // Kaptaki son öğe silinecektir. pc(iVec); // III // Sorunun çözümü; int ival = 2; // Aranacak öğe. auto iter = find(iVec.rbegin(), iVec.rend(), ival); // Kaptaki ilgili değere eşit son öğenin konum bilgisi döndürülmüş oldu. iVec.erase(iter.base() - 1); // Bir değerini çıkartıyoruz. Çünkü 'iter' konum olarak aslında '3' rakamının konum // bilgisini tutmakta çünkü 'reverse_iterator' kullandık. Bir çıkartarak ondan da bir önceki konum bilgisine // erişmiş oluyoruz. pc(iVec); // IIII } >>> Sonu '_if' ile biten algoritmalar argüman olarak bir 'predicate' alıyorlar. Kaptaki öğeler üzerinde işlem yapıyorlar eğer 'predicate' 'true' döndürürse. Gelin temsili bir implementasyonuna bakalım. * Örnek 1, 'Count_if' : //.. template int Count_if(InIter beg, InIter end, Pred myPredicate) { int cnt{}; while( beg != end ){ if( myPredicate(*beg) ) ++cnt; ++beg; } return cnt; } * Örnek 2, //.. bool is_ok(const Date& date) { return date.month_day() > 25; } int main() { std::vector myVec; fc(myVec, 100000, Date::random); // İlgili 'myVec' isimli kap, 'Date::random' isimli callable vasıtasıyla 100'000 adet öğeye sahiptir. std::cout << count_if(myVec.begin(), myVec.end(), is_ok) << "\n"; // İş bu kaptaki öğelerden 'gün' bilgisinin 25'ten büyük olanları sayılmıştır. } * Örnek 3, bool has_length_five(const std::string& s) { return s.length() == 5; } int main() { std::vector myVec; fc(myVec, 10000, rname); // İlgili 'myVec' isimli kap, 'rname' isimli callable vesilesiyle 10000 adet öğeye sahip olmuştur. std::cout << count_if(myVec.begin(), myVec.end(), has_length_five) << " adet öğenin uzunluğu beşe eşittir.\n"; // OUTPUT => 3910 adet öğenin uzunluğu beşe eşittir. } * Örnek 4, 'Copy_if' : 'UnPred' denmesinin sebebi 'UnaryPredicate' manasında. Yani tek bir argüman alan 'predicate' demek için. //.. class DivPred{ public: DivPred(int value) : m_val{value}{} bool operator()(int value)const { return value % m_val == 0; } private: int m_val; }; int main() { std::vector iVec; std::vector iList(100); fc(iVec, 100, Irand(0, 10000)); auto iter_end = std::copy_if(iVec.begin(), iVec.end(), iList.begin(), DivPred(5)); for(auto iter = iList.begin(); iter != iter_end; ++iter) std::cout << *iter << ", "; // OUTPUT => 9945, 3975, 4940, 7880, 2630, 325, 8295, 6245, 3110, 8525, 1010, 3590, 7820, 2690, 9300, // 100, 755, 2410, 2195, 4530, 9930, pr(iList.begin(), iter_end); // OUTPUT => 6585 7300 8765 4465 8400 9370 8215 8040 6085 9055 4520 245 3410 3025 5810 9995 6265 9705 // ----------------------------------------------------------------------------- } * Örnek 5, 'find_if' : Bir 'range' içerisinde bir koşulu sağlayan ilk öğeyi ARAMAKTADIR. //.. template InIter Find_If(InIter beg, InIter end, UnPred myPredicate) { while (beg != end) if(myPredicate(*beg)) { ++beg; return beg; } return beg; } class CharPred{ public: CharPred(char c) : m_c{c} {} bool operator()(const std::string& name) { return name.find(m_c) != std::string::npos; } private: char m_c; }; int main() { /* # OUTPUT # emrecan pakize sumeyye emine mahir sezer tekin feramuz nazli melike mukerrem nefes zerrin sumeyye yavuz nalan kasim hulki devlet garo bekir beste yavuz abdullah selenay ----------------------------------------------------------------------------- Aranan {e} karakteri, {emrecan} isminde bulundu. Aranan [q] karakteri bulunamadı. Aranan {k} karakteri, {pakize} isminde bulundu. */ std::vector sVec; fc(sVec, 25, rname); pc(sVec); char charToLookUp = 'e'; if(auto iter = Find_If(sVec.begin(), sVec.end(), CharPred{charToLookUp}); iter == sVec.end()) std::cout << "Aranan [" << charToLookUp << "] karakteri bulunamadı.\n"; else std::cout << "Aranan {" << charToLookUp << "} karakteri, {" << *iter << "} isminde bulundu.\n"; charToLookUp = 'q'; if(auto iter = std::find_if(sVec.begin(), sVec.end(), CharPred{charToLookUp}); iter == sVec.end()) std::cout << "Aranan [" << charToLookUp << "] karakteri bulunamadı.\n"; else std::cout << "Aranan {" << charToLookUp << "} karakteri, {" << *iter << "} isminde bulundu.\n"; charToLookUp = 'k'; if(auto iter = std::find_if( sVec.begin(), sVec.end(), [charToLookUp] (const std::string& name){ return name.find(charToLookUp) != std::string::npos; } ) ;iter == sVec.end() ) std::cout << "Aranan [" << charToLookUp << "] karakteri bulunamadı.\n"; else std::cout << "Aranan {" << charToLookUp << "} karakteri, {" << *iter << "} isminde bulundu.\n"; } >>> 'reverse' algoritması: Bir 'range' içerisindeki öğeleri tersine çevirmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # 298407 73548 217062 633124 513747 379080 435106 340988 245473 549216 643147 363300 302239 66470 987288 873964 768419 226376 111238 376691 325296 935402 153142 731074 286564 ----------------------------------------------------------------------------- 286564 731074 153142 935402 325296 376691 111238 226376 768419 873964 987288 66470 302239 363300 643147 549216 245473 340988 435106 379080 513747 633124 217062 73548 298407 ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 25, Irand(0, 1000000)); pc(iVec); reverse(iVec.begin(), iVec.end()); pc(iVec); } * Örnek 2, //.. int main() { /* # OUTPUT # bilge aslican suleyman samet tunc ----------------------------------------------------------------------------- cnut temas namyelus nacilsa eglib ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 5, rname); pc(sVec); reverse(begin(sVec), end(sVec)); for(auto& name : sVec) reverse(begin(name), end(name)); pc(sVec); } >>>> İş bu fonksiyonlara geçilen üçüncü argüman bir fonksiyon olabilir, 'operator()()' fonksiyonunu 'overload' eden bir sınıf olabilir ve bir 'lambda-expression' olabilir. * Örnek 1, Function Object kullanılması. //.. class LengthPredicate{ public: LengthPredicate(size_t length) : m_length{length} {} bool operator()(const std::string& s)const { return s.length() == m_length; } private: size_t m_length; }; int main() { std::vector myVec; fc(myVec, 100000, rname); // İlgili 'myVec' isimli kap 100000 öğeye sahiptir. size_t lengthToCount = 7; // Uzunluğu 7 olanların sayılması için kullanılacak. std::cout << "Uzunluğu " << lengthToCount << " olan " << std::count_if(myVec.begin(), myVec.end(), LengthPredicate{lengthToCount}) << " adet isim vardır.\n"; // 'LengthPredicate' sınıf türünden geçici bir nesne oluşturuldu. O nesne üzerinden 'operator()()' // fonksiyonuna çağrıda bulunuldu. } * Örnek 2, Lambda Expression kullanılması. 'Lambda Expressions' ile yukarıdaki örnekteki sınıf kodunu da derleyiciye yazdırabiliriz. //.. int main() { std::vector myVec; fc(myVec, 100000, rname); // İlgili 'myVec' isimli kap 100000 öğeye sahiptir. size_t lengthToCount = 7; // Uzunluğu 7 olanların sayılması için kullanılacak. std::cout << "Uzunluğu " << lengthToCount << " olan " << std::count_if(myVec.begin(), myVec.end(), [lengthToCount] (const std::string& s){ return s.length() == lengthToCount; } ) << " adet isim vardır.\n"; } >> STL kütüphanesindeki genel konvensiyonlar; >>> Yukarıdaki örnekteki isimlendirme konvensiyonuna çoğu yerde rastlanmaktadır. >>> Bir yere yazım işlemi yapan algoritmalar, örneğin yukarıda incelediğimiz 'Copy', geri dönüş değeri olarak HER ZAMAN en son yazdıkları konumdan bir sonraki konumu DÖNDÜRMEKTEDİR. >>> Parametre sırasında okuma amacı ile kullanılacak, yani 'source' olarak kullanılacak, 'range' her zaman hedef 'range' ten, yani 'destination' olarak kullanılan, DAHA ÖNCEDİR. Tıpkı yukarıdaki örnekte olduğu gibi. >>> Yukarıdaki örnekte kullanılan kopyalama fonksiyonu gibi STL kütüphanesindeki çoğu fonksiyonun son parametresine hedef 'range' için başlangıç konumunun bilgisini vermekteyiz. Dolayısıyla hedef 'range' içerisinde, en az 'source' olarak kullanılan 'range' kadar öğe olmasından, BİZ SORUMLUYUZ. 30 elemanlı bir 'range' de bulunan elemanları 15 elemanlı bir 'range' içerisine KOPYALAMA YAPILMADIĞINDAN BİZ SORUMLUYUZ. >>> Algoritmalar iteratör parametreli olduğu için farklı kaplar arasında da işlem yapma olanağı vermektedir. Örneğin 'vector' kabından 'list' kabına kopyalama için. * Örnek 1, #include #include #include /* // Derleyicinin yazacağı fonksiyonun temsili gösterimi: std::list::iterator Copy(std::vector::iterator beg, std::vector::iterator end, std::list::iterator destbeg) { while(beg != end) *destbeg++ = *beg++; return destbeg; } */ int main() { /* # OUTPUT # Vector => 12 67 90 23 77 List => 0 0 0 0 0 List => 12 67 90 23 77 */ std::vector ivec{ 12, 67, 90, 23, 77 }; std::cout << "Vector => "; for(auto n : ivec) std::cout << n << " "; std::cout << "\n"; std::cout << "List => "; std::list ilist(5); for(auto n : ilist) std::cout << n << " "; std::cout << "\n"; std::copy(ivec.begin(), ivec.end(), ilist.begin()); std::cout << "List => "; for(auto n : ilist) std::cout << n << " "; } >>> Bütün arama algoritmaları, aranan değerin bulunamaması durumunda kendisine geçilen 'range' in '.end()' konumunu döndürmektedir. * Örnek 1, //.. int main() { std::vector> sVec; fc(sVec, 20, rname); pc(sVec); std::string nameToLookUp{"efe"}; if(auto iter = find(sVec.begin(), sVec.end(), nameToLookUp); iter == sVec.end()) std::cout << "Aranılan [" << nameToLookUp << "] ismi BULUNAMADI.\n"; else std::cout << "Aranılan isim bulundu => {" << *iter << "}.\n"; /* # OUTPUT # turgut askin nihal sumeyye akin sezai bora izzet kayahan nurdan nihal necmettin naci taner naz sadi polat seyhan azmi izzet ----------------------------------------------------------------------------- Aranılan [efe] ismi BULUNAMADI. */ nameToLookUp = "haluk"; /* # OUTPUT # turgut haluk nefes handesu polat birhan aydan anil atil yilmaz naz sadi polat seyhan azmi izzet ----------------------------------------------------------------------------- Aranılan isim bulundu => {haluk}. */ } >> STL kütüphanesine Modern C++ ile global 'begin()' ve 'end()' fonksiyonları eklendi. Dolayısıla kaplardaki '.begin()' ve '.end()' fonksiyonlarına alternatif olarak kullanabiliriz. Yine bu global fonksiyonlara 'C-Array' de argüman olarak geçebiliriz. * Örnek 1, //.. int main() { std::deque myDeq; fc(myDeq, 20, Date::random); // 'Date' sınıfının '.random()' fonksiyonu kullanılarak ilgili kap dolduruldu. std::sort(myDeq.begin(), myDeq.end()); // Version-I sort(begin(myDeq), end(myDeq)); // Version-II : ADL mekanizması devreye girmiştir. Argümanlar bir isim alanında tanımlanmış iseler, // isim arama o isim alanında da yapılır. } > 'nutility.h' isimli kütüphanemizdeki, >> 'fc' isimli fonksiyon bir kabı doldurmaktadır. İlk parametre olarak bir kap nesnesi, ikinci parametre olarak öğe sayısı ve üçüncü parametre olarak kabı doldurmada kullanılacak bir 'callable'. * Örnek 1, //.. int main() { std::vector ivec; fc(ivec, 20, rand); // 'ivec' isimli kaba, 20 adet öğe ekleyecektir. Bu öğeleri seçerken de 'rand' fonksiyonuna çağrı yapacaktır. fc(ivec, 100, Irand{0, 1000}); // Aynı kabı 'Irand' isimli 'callable' kullanarak doldurulması. } >> 'pc' isimli fonksiyon ise bir kaptaki öğeleri yazdırmaktadır. İlk parametre olarak bir kap nesnesi, ikinci ve üçüncü parametre olarak varsayılan argüman almaktadır. Eğer ikinci argüman için bir ayraç verirsek, yazdırma işleminde o ayraç kullanılmakta. Eğer üçüncü parametre olarak bir 'ofstream' nesnesi gönderirsek, artık dosyaya yazdırmaktadır. > Derleyiciye iteratör döngüsü yazdıran döngü deyimine 'range-based for-loop' denmektedir. * Örnek 1, //.. int main() { std::vector iVec{ 10, 20, 30, 40 ,50 }; for(auto x : iVec) std::cout << x << ", "; // for(auto iter = iVec.begin(); iter != iVec.end(); ++iter) { auto x = *iter; std::cout << x << ", "; } for(auto& x : iVec) std::cout << x << ", "; // for(auto iter = iVec.begin(); iter != iVec.end(); ++iter) { auto& x = *iter; std::cout << x << ", "; } for(const auto& x : iVec) std::cout << x << ", "; // for(auto iter = iVec.begin(); iter != iVec.end(); ++iter) { const auto& x = *iter; std::cout << x << ", "; } // GÖRÜLDÜĞÜ ÜZERE BİR KABIN TAMAMI ÜZERİNDE İŞLEM YAPMAK ZORUNDAYIZ. } /*============================================================================================================*/ (26_13_12_2020) > Standart Template Library (STL) (devam) : >> 'Iterators' (devam) : >>> 'Iterator' şu aşağıdakilerden meydana gelmektedir. Bunlar, >>>> 'iterator' : Bu tip iteratörler kapların '.begin()' ve '.end()' fonksiyonları ile elde edilirler. Ek olarak C++17 ile bu üye fonksiyonların global fonksiyon versiyonları da dile eklenmiştir. Sırasıyla 'begin()' ve 'end()' fonksiyonları, üye fonksiyonların eşlenikleridir. >>>> 'const_iterator' : Bu tip iteratörler kapların '.cbegin()' ve '.cend()' fonksiyonları ile elde edilirler. Ek olarak C++17 ile bu üye fonksiyonların global fonksiyon versiyonları da dile eklenmiştir. Sırasıyla 'cbegin()' ve 'cend()' fonksiyonları, üye fonksiyonların eşlenikleridir. >>>> 'reverse_iterator' : Konteynır sıfının 'nested-type' olan iteratör sınıfı en az 'bidirectional_iterator' grubuna dahil olmalı ki bu tip iteratör gruplarına sahip olsunlar. Bu tip iteratörler kapların '.rbegin()' ve '.rend()' fonksiyonları ile elde edilirler. Ek olarak C++17 ile bu üye fonksiyonların global fonksiyon versiyonları da dile eklenmiştir. Sırasıyla 'rbegin()' ve 'rend()' fonksiyonları, üye fonksiyonların eşlenikleridir. >>>> 'const_reverse_iterator' : Konteynır sıfının 'nested-type' olan iteratör sınıfı en az 'bidirectional_iterator' grubuna dahil olmalı ki bu tip iteratör gruplarına sahip olsunlar. Bu tip iteratörler kapların '.crbegin()' ve '.crend()' fonksiyonları ile elde edilirler. Ek olarak C++17 ile bu üye fonksiyonların global fonksiyon versiyonları da dile eklenmiştir. Sırasıyla 'crbegin()' ve 'crend()' fonksiyonları, üye fonksiyonların eşlenikleridir. >>> İteratörleri manipüle eden yardımcı fonksiyon şablonları: Bunlar, >>>> 'advance()' : Bir iteratörü referans yoluyla alıyor ve bu iteratörü 'n' pozisyon ilerletmektedir. Argüman olarak geçilen iteratör 'bidirectional_iterator' ise negatif rakam da geçilebilir. Hiç bir şekilde bir 'exception' 'throw' ETMEMEKTEDİR. Dolayısıyla kaydırma yaparken 'range' dışına çıkılmadığından BİZ SORUMLUYUZ. Artık bu fonksiyona geçilen iteratörün tuttuğu konum bilgisi değişmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # [773] => 773 810 747 998 999 193 461 993 91 292 579 76 972 404 582 643 623 64 ----------------------------------------------------------------------------- [193] => 773 810 747 998 999 193 461 993 91 292 579 76 972 404 582 643 623 64 ----------------------------------------------------------------------------- [747] => 773 810 747 998 999 193 461 993 91 292 579 76 972 404 582 643 623 64 ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 18, Irand{0, 1000}); auto iter = iVec.begin(); std::cout << "[" << *iter << "] => "; pc(iVec); // iter += 5; // İlgili iteratörün en az 'random_access_iterator' olması gerekmektedir. std::advance(iter, 5); std::cout << "[" << *iter << "] => "; pc(iVec); // iter -= 3; // İlgili iteratörün en az 'random_access_iterator' olması gerekmektedir. std::advance(iter, -3); // İlgili iteratörün en az 'bidirectional_iterator' olması gerekmektedir. std::cout << "[" << *iter << "] => "; pc(iVec); } * Örnek 2, //.. int main() { /* # OUTPUT # [701] => 701 716 600 69 24 477 902 71 211 998 168 915 411 768 241 135 679 771 ----------------------------------------------------------------------------- [477] => 701 716 600 69 24 477 902 71 211 998 168 915 411 768 241 135 679 771 ----------------------------------------------------------------------------- [600] => 701 716 600 69 24 477 902 71 211 998 168 915 411 768 241 135 679 771 ----------------------------------------------------------------------------- */ std::list iList; fc(iList, 18, Irand{0, 1000}); auto iter = iList.begin(); std::cout << "[" << *iter << "] => "; pc(iList); // iter += 5; // İlgili iteratörün en az 'random_access_iterator' olması gerekmektedir. std::advance(iter, 5); // Fakat bu fonksiyon çağrısı ile ilgili iteratörü kaydırabiliyoruz. std::cout << "[" << *iter << "] => "; pc(iList); // iter -= 3; // İlgili iteratörün en az 'random_access_iterator' olması gerekmektedir. std::advance(iter, -3); // Fakat bu fonksiyon çağrısı ile ilgili iteratörü kaydırabiliyoruz. std::cout << "[" << *iter << "] => "; pc(iList); } >>>>> Yukarıdaki her iki örnekte de görüldüğü üzere bir kod seçimi yapılmaktadır. İlgili fonksiyiona geçilen argüman bir 'vektor' sınıf türünden ise ilgili sınıfın '.operator+=()' fonksiyonuna çağrı yapılıyor, fakat ilgili argüman bir 'list' sınıf türünden ise bir döngü içerisinde 'n' kez ilgili iteratörün arttırılması/azaltılması gerekmektedir. Peki arka planda bunu mümkün kılan yapılanma nasıldır? * Örnek 1, Meta programlamada kullanılan 'tag-dispatch' tekniği: (diğer teknikler ise 'constexpr if' ve 'spayn-en') //.. // 'std::random_access_iterator_tag' için 'overload' : template void MyAdvanceImplementation(Iter& iter, int n, std::random_access_iterator_tag) { iter += n; } // 'std::bidirectional_iterator_tag' için 'overload' : template void MyAdvanceImplementation(Iter& iter, int n, std::bidirectional_iterator_tag) { if( n > 0) { while(n--) ++iter; } else { while(n++) --iter; } } //.. Diğer 'tag' için 'overload' lar. template void MyAdvance(Iter& iter, int n) { MyAdvanceImplementation(iter, n, typename Iter::iterator_category{}); // Eğer 'Iter' yerine 'std::vector::iterator' gelirse, // 'Iter::iterator_category' yerine 'random_access_iterator_tag' gelecektir. // Eğer 'Iter' yerine 'std::list::iterator' gelirse, // 'Iter::iterator_category' yerine 'bidirectional_iterator_tag' gelecektir. // Bunu sağlamak için de 'typename' anahtar SÖZCÜĞÜNÜ KULLANMAK ZORUNLU. // Tıpkı yukarda daha önce 'BackInsertIterator' sınıf şablonunu yazarken ilgili kabın tuttuğu // elemanların tür bilgisini elde ederkenki gibi. // Biz, karşılık gelen tür ne ise o türden bir geçici nesne oluşturuyoruz. Böylelikle, // bu üçüncü argümana bakarak, yukarıdaki 'MyAdvanceImplementation' fonksiyonlarından hangi // 'overload' versiyon uygun ise o seçilecektir. } int main() { /* # OUTPUT # [9] => 9 90 94 24 57 44 24 41 31 46 83 52 11 96 15 17 51 83 94 70 88 76 80 22 47 ----------------------------------------------------------------------------- [31] => 9 90 94 24 57 44 24 41 31 46 83 52 11 96 15 17 51 83 94 70 88 76 80 22 47 ----------------------------------------------------------------------------- [94] => 9 90 94 24 57 44 24 41 31 46 83 52 11 96 15 17 51 83 94 70 88 76 80 22 47 ----------------------------------------------------------------------------- */ std::list iList; fc(iList, 25, Irand{9, 100}); auto iter = iList.begin(); std::cout << "[" << *iter << "] => "; pc(iList); MyAdvance(iter, 8); std::cout << "[" << *iter << "] => "; pc(iList); MyAdvance(iter, -6); std::cout << "[" << *iter << "] => "; pc(iList); // BU MEKANİZMANIN ÇALIŞMA ZAMANI İLE BİR ALAKASI YOKTUR. // TAMAMİYLE DERLEME ZAMANINA İLİŞKİNDİR. } >>>> 'distance()' : İki iteratör arasındaki mesafeyi elde edebiliyoruz. * Örnek 1, //.. int main() { /* # OUTPUT # 1528726442 463433013 208427435 482200417 1950432305 444597464 514460685 1709010121 1008062565 1921019392 ----------------------------------------------------------------------------- size : 10 size : 10 Aranacak deger : 1008062565 Aranan [1008062565] rakamı [8] indisinde bulundu. Aranan [1008062565] rakamı [8] indisinde bulundu. */ randomize(); std::vector iVec; fc(iVec, 10, random); pc(iVec); std::cout << "size : " << std::distance(iVec.begin(), iVec.end()) << "\n"; std::cout << "size : " << iVec.end() - iVec.begin() << "\n"; // En az 'random_access_iterator' olmalı. std::cout << "Aranacak deger : "; int numberToLookUp; std::cin >> numberToLookUp; if( auto iter = std::find(iVec.begin(), iVec.end(), numberToLookUp); iter != iVec.end() ) { std::cout << "Aranan [" << numberToLookUp << "] rakamı [" << std::distance(iVec.begin(), iter) << "] indisinde bulundu.\n"; std::cout << "Aranan [" << numberToLookUp << "] rakamı [" << iter - iVec.begin() << "] indisinde bulundu.\n"; // En az 'random_access_iterator' olmalı. } else { std::cout << "Aranan [" << numberToLookUp << "] rakamı BULUNAMADI.\n"; } } * Örnek 2, //.. int main() { /* # OUTPUT # 1694615831 1506296853 50781890 579410133 727108356 1768694123 945782193 903807668 1221500803 1885210938 ----------------------------------------------------------------------------- size : 10 Aranacak deger : 1694615831 Aranan [1694615831] rakamı [0] indisinde bulundu. */ randomize(); std::list iList; fc(iList, 10, random); pc(iList); std::cout << "size : " << std::distance(iList.begin(), iList.end()) << "\n"; // std::cout << "size : " << iList.end() - iList.begin() << "\n"; // En az 'random_access_iterator' olmalı. std::cout << "Aranacak deger : "; int numberToLookUp; std::cin >> numberToLookUp; if( auto iter = std::find(iList.begin(), iList.end(), numberToLookUp); iter != iList.end() ) { std::cout << "Aranan [" << numberToLookUp << "] rakamı [" << std::distance(iList.begin(), iter) << "] indisinde bulundu.\n"; // std::cout << "Aranan [" // << numberToLookUp // << "] rakamı [" // << iter - iList.begin() // << "] indisinde bulundu.\n"; // En az 'random_access_iterator' olmalı. } else { std::cout << "Aranan [" << numberToLookUp << "] rakamı BULUNAMADI.\n"; } } >>>> C++11 ile dile eklenen 'next()' ve 'prev()' fonksiyonları : İteratörün tuttuğu konum bilgisini değiştirmeden sadece o konumdan 'n' konum sonrası veya öncesindeki konumlara erişmek için kullanılır. Her iki fonksion da ikinci argümanı varsayılan olarak bir değerini almaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # 1586070685 1978505654 1266866527 300525574 1979937931 1320568275 966617558 2132587798 1755296543 739439990 ----------------------------------------------------------------------------- Birinci öğe : 1586070685 [3] konum sonrasındaki öğe : 300525574 Birinci öğe : 1586070685 İkinci öğe : 1978505654 */ randomize(); std::vector iVec; fc(iVec, 10, random); pc(iVec); auto iter = iVec.begin(); std::cout << "Birinci öğe : " << *iter << "\n"; std::cout << "[" << 3 << "] konum sonrasındaki öğe : " << *next(iter, 3) << "\n"; std::cout << "Birinci öğe : " << *iter << "\n"; std::cout << "İkinci öğe : " << *next(iter) << "\n"; } * Örnek 2, //.. int main() { /* # OUTPUT # 863294851 1766641905 430546786 197891584 504445422 1476846807 905168151 580938961 643899547 1618256969 ----------------------------------------------------------------------------- En sonki oge sonraki oge : 0 En sonki oge : 1618256969 Birinci oge : 863294851 */ randomize(); std::vector iVec; fc(iVec, 10, random); pc(iVec); auto iter = iVec.end(); // 'iter' konumunda bir öğe yok. Çünkü son öğenin konumundan bir sonraki konumu göstermektedir. Dolayısla // 'derefence' etmem durumunda 'Tanımsız Davranış' oluşacaktı. std::cout << "En sonki oge sonraki oge : " << *iter << "\n"; // Tanımsız Davranış. std::cout << "En sonki oge : " << *prev(iter) << "\n"; std::cout << "Birinci oge : " << *prev(iter, 10) << "\n"; } * Örnek 3, //.. int main() { /* # OUTPUT # kirikkale diyarbakir eskisehir osmaniye kastamonu cankiri siirt malatya karaman kirikkale ----------------------------------------------------------------------------- kirikkale diyarbakir eskisehir osmaniye kastamonu cankiri siirt malatya karaman kirikkale ----------------------------------------------------------------------------- diyarbakir eskisehir osmaniye kastamonu cankiri siirt malatya karaman ----------------------------------------------------------------------------- */ std::list iList; fc(iList, 10, rtown); pc(iList); pr(iList.begin(), iList.end()); // Belli bir aralıktaki öğeleri yazdıran fonksiyonumuz. pr ( next(iList.begin()), prev(iList.end()) ); // İlk öğe hariç, son öğe haric, aradaki bütün öğeleri yazdırdık. } >>>> 'iter_swap' : İki iteratör konumundaki öğeleri takas etmektedir. Dolayısıyla bu öğelerin birbirine ATANABİLİR olması gerekiyor. İstemeden de olsak VERİ KAYBINA neden olabiliriz. Nesneleri birbirine atadığımız için bu fonksiyona geçilen iteratörlerin de kategorileri aynı olması GEREKMİYOR. * Örnek 1, //.. // 'iter_swap' fonksiyonunun temsili implementasyonu: template void MyIterSwap(IterOne first, IterTwo second) { auto temp = *first; *first = *second; *second = temp; } int main() { /* # OUTPUT # batman kirikkale bingol amasya antalya amasya duzce kastamonu duzce nigde ----------------------------------------------------------------------------- eskisehir bitlis aksaray hakkari bolu denizli canakkale kilis sinop canakkale ----------------------------------------------------------------------------- ***************************************************************************** eskisehir kirikkale bingol amasya antalya amasya duzce kastamonu duzce nigde ----------------------------------------------------------------------------- batman bitlis aksaray hakkari bolu denizli canakkale kilis sinop canakkale ----------------------------------------------------------------------------- ***************************************************************************** eskisehir canakkale bingol amasya antalya amasya duzce kastamonu duzce nigde ----------------------------------------------------------------------------- batman bitlis aksaray hakkari bolu denizli canakkale kilis sinop kirikkale ----------------------------------------------------------------------------- */ std::list iList; fc(iList, 10, rtown); std::vector iVec; fc(iVec, 10, rtown); pc(iList); pc(iVec); std::cout << "\n*****************************************************************************\n\n"; MyIterSwap(iList.begin(), iVec.begin()); pc(iList); pc(iVec); std::cout << "\n*****************************************************************************\n\n"; iter_swap(next(iList.begin()), prev(iVec.end())); pc(iList); pc(iVec); } * Örnek 2, //.. int main() { /* # OUTPUT # */ randomize(); std::vector iVec; fc(iVec, 10, random); pc(iVec); std::list iList; fc(iList, 10, random); pc(iList); std::iter_swap ( next(iVec.begin(), 5), prev(iList.end(), 5) ); // ERROR: => The dereferenced values *a and *b must be "swappable", which implies that swap(*a, *b) // must be valid, and thus the dereferenced types must be identical, // although the iterator types do not have to be. pc(iVec); pc(iList); } > 'Lambda Expressions' : Bir 'lambda expression' derleyiciye sınıf kodu yazdırır ve o ifadeyi de yazmış olduğu sınıf türünden geçici nesneye dönüştürür. C++11 ile dile eklenmiştir. Bu durumda derleyicinin yazmış olduğu sınıfa 'closure type', bu sınıftan oluşturulan geçici nesneye de 'closure object' denmektedir. * Örnek 1, aşağıdaki örnekleri inceyelelim: //.. int main() { [](){}; // 'Lambda-Expression' [](){}(); // Geçerli bir ifadedir. Derleyicinin yazdığı sınıf türünden geçici nesne oluşturup, '.operator()()' fonksiyonu çağrılmıştır. [](){{{{}}}}(); // Geçerli bir ifadedir. ((((([](){{{{}}}})))))(); // Geçerli bir ifadedir. [](){}(); // En basit ve geçerli bir ifadedir, oluşturulan geçici nesne ile '.operator()()' çağrılmıştır. // Peki yukarıdaki ifadeyi bileşenlerine ayırırsak; // i. '[]' : Bir 'lambda-expression' için olmazsa olmazdır. 'Lambda Introducer' denmektedir. Bu parantez içerisine, // duruma göre, bir şeyler yazabiliyoruz. // ii. '()' : Derleyiciye yazdırdığımız sınıfın '.operator()()' fonksiyonunun PARAMETRE PARANTEZİ. // iii. '{}' : Derleyicinin yazmış olduğu iş bu sınıfın '.operator()()' fonksiyonunun ANA BLOĞU. // '()' ve '{}' parantez çiftlerinin arasına, duruma göre, 'Trailing Return Type' mekanizması ile 'mutable', 'noexcept' ve // 'constexpr' anahtar sözcüklerini yazabiliyoruz. // iiii. '()' : Derleyicinin oluşturduğu geçici nesne ile '.operator()()' fonksiyonunu çağırma yöntemi. Parantez içerisine // argüman olan ifadeleri yazabiliyoruz. [](){ std::cout << "Hello, it my first lambda-expression.\n"; }(); // Aşağıdaki şekilde bir sınıf, derleyici tarafından yazılacaktır. class com_gen // Bir fonksiyon içerisinde sınıf tanımlayabiliyoruz. { public: void operator()()const{ std::cout << "Hello, it my first class written by the compiler.\n"; }; // İş bu fonksiyonun geri dönüş değeri, yukarıdaki 'lambda-expression' içerisindeki '{}' çiftinin içine yazılan // koda göre çıkarım yapılmakta. O noktada kod olmadığından, 'void' seçildi. }; com_gen{}(); // Daha sonra da bu ifadeye dönüştürüldü ve ilgili fonksiyon çağrıldı. /* # OUTPUT # Hello, it my first lambda-expression. Hello, it my first class written by the compiler. */ std::cout << "***************************************************\n"; std::cout << "Using Lambda-Expression => " << [](int x){ return x*x; }(30) << "\n"; class com_gen_two{ public: int operator()(int x)const { return x*x; } }; std::cout << "Using com_gen_two => " << com_gen_two{}(30) << "\n"; /* # OUTPUT # Using Lambda-Expression => 900 Using com_gen_two => 900 */ } >> Eğer fonksiyonumuzun parametre değişkeni yok ise ve yukarıdaki örneklerde açıklanan anahtar kelimeleri kullanmıyorsak, 'lambda-expression' içerisindeki '()' çiftini kullanmayabiliyoruz. * Örnek 1, //.. int main() { []{ std::cout << "Hello, again. I am from function block-scope area.\n"; }(); // '.operator()()' fonksiyonu için parametre almayacağından, ilk '()' çiftini yazmadık. /* # OUTPUT # Hello, again. I am from function block-scope area. */ } >> 'auto' anahtar kelimesinin en sık kullanıldığı noktalardan birisi de bir fonksiyonun 'lambda-expression' döndürmesidir. * Örnek 1, //.. auto f_multiply(int x) { return [x](int a){ return a*x; }; } int main() { /* # OUTPUT # 12 x 20 = 240 12 x 30 = 360 */ auto f = f_multiply(12); std::cout << "12 x 20 = " << f(20) << "\n"; std::cout << "12 x 30 = " << f(30) << "\n"; } >> Derleyiciye yazdıracağımız 'closure-type' sınıflar için 'Data Members' eklemesi yapmasını da sağlayabiliriz. Bunu da 'Lambda Introducer' diye adlandırdığımı '[]' içerisine yazacağımız bir takım ifadeler ile yapmak mümkündür. İş bu ifadelere de 'Capture Close' denmektedir. Diğer yandan 'lambda-expression' içerisindeki '{}' içerisinde 'global namespace scope' içerisindeki değişkenleri veya 'static' ömürlü yerel değişkenleri direkt olarak kullanabilmekteyim. Fakat 'automatic' ömürlü değişkenleri direkt olarak kullanmamız mümkün değildir. * Örnek 1, //.. int g = 10; int main() { static int s_Ival = 20; auto f = [](int x){ return x * g + s_Ival; }; // Legal. int ival = 30; auto g = [](int x){ return x * ival; }; // Sentaks Hatası // Peki otomatik ömürlü değişkenlerimi nasıl kullanacağız? // El-cevap : Aşağıdaki örneği inceleyelim. } * Örnek 2, Derleyici bana öyle bir sınıf kodu yaz ki sınıfın veri elemanı 'int' türden olsun ve değerini aşağıdaki 'x' değişkeninden alsın: //.. // Derleyicinin yazacağı temsili sınıf: class com_gen{ public: com_gen(int other) : x_(other){} int operator()(int a) const { return a * x_; } private: int x_; }; int main() { int x = 20; auto f = [x](int a){ return a * x; }; // Yukarıdaki 'com_gen' şeklinde bir sınıf yazılacaktır. std::cout << "f(20) : " << f(20) << "\n"; // Görüldüğü gibi 'automatic' ömürlü değişkenimiz 'call-by-value' semantiği ile 'lambda-expression' içerisinde // kullanılmıştır. Peki 'call-by-reference' semantiğini nasıl kullanabilirim? // El-cevap: Aşağıdaki örneği inceleyelim. } * Örnek 3, //.. // Derleyicinin yazdığı temsili sınıf: class abc_gen { public: abc_gen(int& other) : m_r{other}{} void operator()(int value)const{ m_r += value; } // 'const' olmasına rağmen içeride işlem yaptık çünkü nesne değişmedi. private: int& m_r; }; int main() { int x = 90; abc_gen myObj(x); // Yukarıdaki sınıf içerisindeki 'm_r' isimli referans, 'x' değişkenini refere etmektedir. myObj(10); // Artık 'x' değişkeninin değeri '100' oldu. // Peki yukarıdaki temsili sınıfın 'lambda-expression' karşılığı nasıl olmalı? // 'capture closure' kısmına '&' deklaratörü ekleyerek. auto f = [x](int value){ x += value; } // 'capture by copy' auto g = [&x](int value){ x += value; } // 'capture by reference' } >>> 'Lambda Introducer' içerisine yazılabilecek ifadeler: >>>> İlgili 'Lambda Introducer' in boş bırakılması, hiç bir 'Capture Closure' yazılmaması: Bu tip 'lambda-expression' lar için 'stateless lambda-expression' denmektedir. >>>> İlgili 'Lamda Introducer' içine sadece değişken isimlerinin yazılması: İlgili değişken/değişkenleri 'capture by copy' etmek demektir. Örneğin, '[x](){}' şeklindeki bir ifade otomatik ömürlü 'x' değişkenini kopyalama yoluyla yakalamaktadır. Benzer şekilde '[x, y, z](){}' şeklindeki ifade ise otomatik ömürlü 'x', 'y' ve 'z' değişkenlerini kopyalama yoluyla yakalamaktadır. >>>> İlgili 'Lambda Introducer' içine bir değişken ismi ve bir '&' deklaratörü yazılması: İlgili değişken/değişkenleri 'capture by reference' etmek demektir. Örneğin, '[&x](){}' şeklindeki bir ifade otomatik ömürlü 'x' değişkenini referans yoluyla yakalamaktadır. Benzer şekilde '[&x, &y, &z](){}' şeklindeki ifade ise otomatik ömürlü 'x', 'y' ve 'z' değişkenlerini referans yoluyla yakalamaktadır. Yakalam işinde bu iki yaklaşımı kombine de edebiliriz. Örneğin, '[&a, b, &c](){}' şeklindeki bir ifade otomatik ömürlü 'a', 'b' ve 'c' değişkenlerinden 'a' ve 'c' değişkenlerini 'capture by reference' ile 'b' değişkenini ise 'capture by copy' ile yakalamaktadır. >>>> İlgili 'Lambda Introducer' içine sadece '=' deklaratörü yazmamız 'capture all by copy' demektir. Yani görülür durumdaki otomatik ömürlü bütün değişkenleri kopyalama yoluyla yakalıyoruz. >>>> İlgili 'Lambda Introducer' içine sadece '&' deklaratörü yazmamız 'capture all by reference' demektir. Yani görülür durumdaki otomatik ömürlü bütün değişkenleri referans yoluyla yakalıyoruz. >>>> İlgili 'Lambda Introducer' içine '=', '&' ve isim yazılması : '&' deklaratörü hangi ismi niteliyor ise o 'capture by referance' ile fakat diğer hepsi 'capture by copy' ile. >>>> İlgili 'Lambda Introducer' içine '&' deklaratörünün ve ismin ayrı ayrı yazılması: Sadece ilgili isim 'capture by copy', geri kalan bütün hepsi 'capture by referance'. * Örnek 1, //.. int main() { int a = 1, b = 2, c = 3; auto f = [](int x){ return x * (a + b + c); }; // Şu an bu satır sentaks hatasıdır. auto ff = [a,b,c](int x){ return x * (a + b + c); }; // Artık legal hale geldi. // 'capture by copy' auto fff = [=](int x){ return x * (a + b + c); }; // Artık legal hale geldi. // 'capture all by copy' auto g = [](){ ++a; ++b; ++c; }; // Şu an bu satır sentaks hatasıdır. auto gg = [&a, &b, &c](){ ++a; ++b; ++c; }; // Artık legal hale geldi. // 'capture by reference' auto ggg = [&](){ ++a; ++b; ++c; }; // Artık legal hale geldi. // 'capture all by reference' auto h = [=, &a](){ return ++a * b * c; }; // 'a' değişkeni 'capture by reference' ile fakat diğerleri 'capture all by copy' ile elde edilmiştir. auto hh = [&, b](){ return a * ++b * c; }; // 'b' değişkeni 'capture by copy' ile fakat diğerleri 'capture all by reference' ile elde edilmiştir. // UNUTULMAMALIDIR Kİ YUKARIDAKİ 'f', 'ff', 'fff', 'g', 'gg', 'ggg', 'h' ve 'hh' sınıf türünden nesneler bir fonksiyon // çağrı operatörünün operandı olmadıklarından, ilgili 'a', 'b' ve 'c' isimli değişkenler // üzerinde bir etkiye sahip değillerdir. } * Örnek 2, //.. int main() { std::string str{ "gokhan" }; auto f = [str](const char* p){ str.append(p); }; // Sentaks hatası. İlgili 'lambda-expression' 'mutable' olmadığından, yazılacak sınıfın '.operator()()' // fonksiyonu da 'const' olacaktır. 'const' fonksiyon içerisinden 'non-const' fonksiyonlara çağrı yapılamaz. } * Örnek 3, 'Dangling Referance' konusunda hakkında bir mülakat sorusu: //.. auto f_multiply(int x) { std::cout << "f_multiply started...\n"; std::cout << "12 x [" << x << "] = " << 12 * x << "\n"; std::cout << "f_multiply ended...\n"; return [&x](int a){ return a*x; }; } int main() { auto f = f_multiply(12); std::cout << "12 x 20 = " << f(20) << "\n"; std::cout << "12 x 30 = " << f(30) << "\n"; /* # OUTPUT # f_multiply started... 12 x [12] = 144 f_multiply ended... 12 x 20 = 655340 12 x 30 = 983010 */ // 'f_multiply' fonksiyonumuz içerisinde 'capture by reference' ile yerel bir değişken olan 'x' değişkeni // 'call-by-reference' ile yakalanmıştır. Fakat 'x' değişkeninin ömrü fonksiyonun bloğundan sonra // bittiğinden, referansımız 'Dangling Reference' haline gelmiştir. auto ff = f_multiply(31); std::cout << "31 x 20 = " << f(20) << "\n"; std::cout << "31 x 30 = " << f(30) << "\n"; /* # OUTPUT # f_multiply started... 12 x [31] = 372 f_multiply ended... 31 x 20 = 655340 31 x 30 = 983010 */ } >>>> 'Lambda Init. Capture' : Derleyicinin yazacağı sınıfın veri elemanını yakaladığımız değişkenin değeri ile değil ama o değerin değiştirilmiş versiyonu ile hayata getirmemize olanak verir. * Örnek 1, //.. int main() { int x = 10; auto f = [x](int a){ return a*x; }; // Derleyicinin yazacağı sınıfın veri elemanı, 'x' değişkeninin değeri ile hayata geldi. Peki bizler 'x+5' // ifadesinin değeri ile hayata getirmek isteseydik? El-cevab: 'Lambda Init. Capture' kullanmalıydık. auto ff = [y = x + 5](int a){ return a*x; }; // Artık derleyicinin yazacağı sınıfın veri elemanı 'y' değişkeninin değeri ile hayata gelecektir. // Yine 'y' yerine de 'x' ismini kullanabilirdik. auto fff = [x = x + 15](int a){ return a*x; }; // Legal. } * Örnek 2, Kopyalamaya karşı kapalı sınıfları taşımak için de bu yaklaşım kullanılabilir. //.. class MoveOnly{ public: MoveOnly() = default; MoveOnly(MoveOnly&&) {/*...*/} MoveOnly& operator=(MoveOnly&&) {/*...*/} MoveOnly(const MoveOnly&) = delete; MoveOnly& operator=(const MoveOnly&) = delete; }; int main() { MoveOnly mx; auto f = [mx](){}; // Sentaks hatası. Çünkü bizler 'capture by copy' yaptığımız için ilgili sınıfın 'Copy Ctor.' fonksiyonu // çağrılacak ki o da 'delete' edildiğinden sentaks hatası alacağız. auto ff = [&mx](){}; // Alternatif-I : 'capture by reference' yaparak ilgili sentaks hatasından kurtulabiliriz. auto fff = [mx = std::move(mx)](){}; // Alternatif-II : İlgili sınıf nesnesini taşıyarak ve 'Lambda Init. Capture' yaparak yukarıdaki sentaks // hatasından kurtulabiliriz. Artık 'mx' isimli nesne, derleyicinin 'lambda-expression' karşılığında yazacağı // sınıfın veri elemanına TAŞINMIŞTIR. // Yukarıdaki alternatiflerin hangilerinin seçileceği tamamiyle o anki duruma göre değişkenlik göstermektedir. } >> Derleyicinin yazdığı sınıftaki '.operator()()' fonksiyonu: >>> Bir 'const' üye fonksiyondur. * Örnek 1, //.. class xyz_gen // I { public: xyz_gen(int a) : x(a) {} void operator()()const{ ++x; /*Sentaks hatası.*/ } private: int x; }; class abc_gen // II { public: xyz_gen(int a) : x(a) {} void operator()(){ ++x; /*LEGAL.*/ } private: int x; }; int main() { int x = 23; auto f = [x](){ ++x; }; // Sentaks hatası. Çünkü ilgili fonksiyon bir 'const' üye fonksiyondur. Temsili yazılan 'I' nolu sınıf yukarıdadır. // Peki ne yapmalıyız da buradaki 'x' değişkeninin değerini değiştirelim? El-cevap : 'mutable' anahtar // sözcüğünü kullanmak. auto g = [x]()mutable{ ++x ;}; // Peki bu durumda derleyicinin yazacağı sınıf nasıl bir sınıf oluyor? // El-cevap : Temsili yazılan 'II' nolu sınıf yukarıdadır. // İlgili sınıftan da görüldüğü üzere derleyicinin yazacağı // sınıftaki '.operator()()' fonksiyonu artık 'const' DEĞİL. // Buradan hareketle 'call-by-value' ile kullanacağımız ve değiştireceğimiz değişkenler için // 'mutable' anahtar sözcüğünü KULLANMAK ZORUNDAYIZ. } >>> İş bu fonksiyon birden fazla türden değer döndürmesi durumunda sentaks hatası meydana gelir. * Örnek 1, //.. int main() { auto f = [](int x){ if(x > 0) return 2.3; return 2; }; // Normal şartlarda böyle bir kullanım sentaks hatasıdır. auto ff = [](int x)->float{ return 2.3; return 2; }(); // Artık derleyici yazacağı '.operator()()' fonksiyonunun geri dönüş değerini 'float' olarak seçecek; // '{}' çifti içerisindeki ifadenin türüne göre çıkarım yapmayacaktır. } >>> İş bu fonksiyonun parametre parantezinin içine 'auto' anahtar kelimesinin yazılması sonucunda 'generalized lambda-expression' denmektedir. Derleyici ilgili 'lambda-expression' için yazacağı sınıfın '.operator()()' fonksiyonu bir fonksiyon şablonu haline gelmiştir. İş bu '.operator()()' fonksiyonuna geçilen argümanın türünden çıkarım yapılarak, fonksiyon şablonunun şablon parametresinin türü belirlenmiş olmaktadır. * Örnek 1, //.. // 'f' için derleyicinin yazdığı temsili sınıf: class com_gen{ public: template auto operator()(T x) { return x*x; } }; // 'ff' için derleyicinin yazdığı temsili sınıf: class com_gen{ public: template auto operator()(T& x) { return x*x; } }; // 'fff' için derleyicinin yazdığı temsili sınıf: class com_gen{ public: template auto operator()(const T& x) { return x*x; } }; // 'ffff' için derleyicinin yazdığı temsili sınıf: class com_gen{ public: template auto operator()(T&& x) { return x*x; } }; int main() { auto f = [](auto x){ return x*x; }; // Generalized lambda-expression f(12); // '12' ifadesinin türü 'int' olduğundan, yazılacak sınıfın içerisindeki fonksiyon şablonunun şablon // parametresi olan 'T' yerine de 'int' gelecektir. f(1.2); // '1.2' ifadesinin türü 'double' olduğundan, ilgili 'T' yerine 'double' gelecektir. auto ff = [](auto& x){ return x*x; }; // Artık tür çıkarımı 'L-value Reference' şeklinde yapılacaktır. auto fff = [](const auto& x){ return x*x; }; // Artık tür çıkarımı 'const L-value Reference' şeklinde yapılacaktır. auto ffff = [](auto&& x){ return x*x; }; // Artık tür çıkarımı 'Forwarding Referance' şeklinde yapılacaktır. } >>> İş bu fonksiyonlar varsayılan argümanlar da alabilmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # x : 100 x : 31 */ auto f = [](int x = 100){ std::cout << "x : " << x << "\n"; }; f(); f(31); } >> Bir 'lambda-expression' ile neler yapabiliriz? >>> Derleyicinin yazmış olduğu 'closure object', fonksiyon çağrı operatörünün operandı yapılabilir. Yukarıdaki örneklerde kullanılmıştır. >>> İsimlendirilmiş bir nesne oluşturabiliriz. * Örnek 1, //.. int main() { auto fSquare = [](int a){ return a*a; }; // 'fSquare', derleyicinin yazdığı sınıf türünden bir nesne. std::cout << fSquare(12) << "\n"; std::cout << fSquare(25) << "\n"; } >>> Fonksiyon şablonları için 'template argument' olarak kullanılırlar. * Örnek 1, //.. template void func(T x) { std::cout << "Type of T : [" << typeid(T).name() << "]\n"; } int main() { /* # OUTPUT # Type of T : [int] Type of T : [int * __ptr64] Type of T : [class ] Type of T : [int] Type of T : [class ] */ int ival{10}; func(ival); // 'T' türü için 'int' gelecek. func(&ival); // 'T' türü için 'int*' gelecek. func([](int a){ return a*a; }); // 'T' türü derleyicinin yazdığı sınıf türünden. İlgili 'lambda-expression' ine ait 'closure type' türünden. func([](int a){ return a*a; }(ival)); // 'T' türü için 'int' gelecek. Çünkü burada artık bir fonksiyon çağrılmakta. auto f = [](int a){ return a*a; }; func(f); // 'T' türü derleyicinin yazdığı sınıf türünden. İlgili 'lambda-expression' ine ait 'closure type' türünden. } >>> Algoritmalara argüman olarak geçilebilirler. Aşağıdaki örnekleri inceleyelim: * Örnek 1, //.. int main() { /* # OUTPUT # mukerrem ercument sadullah abdullah sadettin yurdagul muzaffer sadettin muzaffer yurdakul yurdagul mukerrem sadettin sadettin abdullah muzaffer mukerrem sadettin muzaffer ercument ferhunde sadettin ----------------------------------------------------------------------------- */ std::vector srcVec; fc(srcVec, 500, rname); size_t lengthToCopy = 8; std::vector desVec; std::copy_if(srcVec.begin(), srcVec.end(), std::back_inserter(desVec), [lengthToCopy](const std::string& name){ return name.length() == lengthToCopy; } ); pc(desVec); // Hedef kap boş olduğu için kopyalama sırasında 'back_inserter' kullanıldığını unutmayalım. } * Örnek 2, Şubat 29 olan ilk öğeyi indisi ile birlikte yazdıralım. //.. int main() { /* # OUTPUT # Aranılan [29 Subat 1988 Pazartesi] tarihli öğe [1058] nolu indiste bulundu. */ std::vector dVec; fc(dVec, 100'000, Date::random); if( auto iter = std::find_if( dVec.begin(), dVec.end(), [](const Date& date){ return date.month_day() == 29 && date.month() == 2; } ); iter != dVec.end() ) { std::cout << "Aranılan [" << *iter << "] tarihli öğe [" << std::distance(dVec.begin(), iter) << "] nolu indiste bulundu.\n"; std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyası oluşturulamadı.\n"; exit(EXIT_FAILURE); } pc(dVec, "\n", ofs); } else { std::cout << "Aranılan [" << *iter << "] tarihli öğe bulunamadı.\n"; } } * Örnek 3, Bir sınıf şablonunu 'lambda-expression' olan ifadeye göre de açabiliriz. //.. template class Myclass{}; int main() { auto f = [](int x){ return x*x; }; Myclass mx; // Derleyicinin yazacağı sınıf her ne tür ise sınıf şablonu da o türe göre açılacak. // Bir nevi 'T' yerine o tür gelecektir. } * Örnek 4, 'std::sort()' fonksiyonu ile birlikte kullanımı: //.. int main() { /* # OUTPUT # cihan yaygara | atalay yarma | melike kabasakal | emre elebasi | demir sivri | candan kesman | haluk cangoz | turgut jilet | agah bayraktar | berivan dunyalik | // I: ----------------------------------------------------------------------------- agah bayraktar < atalay yarma < berivan dunyalik < candan kesman < cihan yaygara < demir sivri < emre elebasi < haluk cangoz < melike kabasakal < turgut jilet < // II: ----------------------------------------------------------------------------- turgut jilet > melike kabasakal > haluk cangoz > emre elebasi > demir sivri > cihan yaygara > candan kesman > berivan dunyalik > atalay yarma > agah bayraktar > // III: ----------------------------------------------------------------------------- demir sivri , atalay yarma , emre elebasi , haluk cangoz , turgut jilet , candan kesman , cihan yaygara , agah bayraktar , berivan dunyalik , melike kabasakal , // IV ----------------------------------------------------------------------------- */ std::vector sVec; // Kendi oluşturduğumuz 'fc' fonksiyon şablonuna bir 'lambda-expression' da argüman olarak geçebiliriz. fc ( sVec, 10, [](){ return rname() + " " + rfname(); } ); pc(sVec, " | "); // I: İsimlerin arasına '|' karakteri koyacaktır. std::sort(sVec.begin(), sVec.end()); // Sıralama varsayılan argüman olan '.operator<()' fonksiyonu ile yapıldı. pc(sVec, " < "); // II: std::sort(sVec.begin(), sVec.end(), [](const std::string& s1, const std::string& s2){ return s2 < s1; }); // Sıralama, üçüncü argüman olan 'lambda-expression' a bakılarak büyükten küçüğe doğru yapılmıştır. pc(sVec, " > "); // III: std::sort ( sVec.begin(), sVec.end(), [](const std::string& s1, const std::string& s2) { return s1.length() < s2.length() || ( s1.length() == s2.length() && s1 < s2 ); } ); // Uzunluğu kısa olanlar başa, aynı uzunlukta olan isimleri, kendi içinde alfabetik olarak sıraladık. pc(sVec, " , "); // IIII: } >> C++20 öncesinde 'lambda-expressions' karşılığı yazılan sınıfın 'Default Ctor.' fonksiyonu ve 'Copy Assigningment' fonksiyonları 'delete' edilmiştir. C++20 ile kullanılabilir durumdadırlar. * Örnek 1, //.. int main() { /* # OUTPUT # error: use of deleted function ‘main()::::()’ note: a lambda closure type has a deleted default constructor */ auto f = [](int x){ return x + 5; }; decltype(f) g; // C++20 öncesinde sentaks hatası çünkü 'Default Ctor.' fonksiyonu 'delete' edildi. auto ff = f; // 'Copy Ctor.' fonksiyonu hala geçerlidir. ff = f; // C++20 öncesinde sentaks hatası çünkü 'Copy Assigningment' fonksiyonu 'delete' edildi. } >> Bünyesindeki kodlar birebir aynı olsa da iki defa kullanım sonucunda farklı sınıflar yazılır. Aşağıdaki örneği inceleyelim. * Örnek 1, //.. int main() { /* # OUTPUT # Farklı sınıflardır. => Z4mainEUlvE_ / Z4mainEUlvE0_ */ auto f = [](){ return 1; }; auto g = [](){ return 1; }; if( typeid(f) == typeid(g) ) { std::cout << "Aynı sınıflardır.\n"; } else { std::cout << "Farklı sınıflardır. => " << typeid(f).name() << " / " << typeid(g).name() << " \n"; } } > Immediately Invoked Function Expression : 'const' nesnelere ilk değer vermek mecburidir. İlk değer verecek ifade bir hesaplama yoluyla elde edilmiş olsun. Bu fonksiyonun geri dönüş değeri ile de nesnemize ilk değer vereceğiz. Artık bunun yerine 'lambda-expression' kullanıyoruz. * Örnek 1, //.., void func(int a, int b) { return a*b; } int main() { // Geleneksel yöntem: const int myValue = func(31, 31); // I.I.F.E deyimi: const int myValueTwo = [](int a, int b) { return a*b; }(31, 32); } > 'ostream_iterator' sınıfı: Aşağıdaki örneği inceleyelim: * Örnek 1, Aşağıdaki 'MyCopy' fonksiyonunu çıkış akımına yazma işlemi yapacak şekilde çalıştırabilir miyiz? //.. template OutIter MyCopy(InIter beg, InIter end, OutIter destBeg) { while( beg != end ) *destBeg++ = *beg++; return destBeg; } template class OstreamIterator{ public: OstreamIterator(std::ostream& other_os, const char* p = ", ") : m_p{p}, m_os{other_os} {} OstreamIterator& operator*() { return *this; } OstreamIterator& operator++() { return *this; } OstreamIterator& operator++(int) { return *this; } OstreamIterator& operator=(const T& value) { m_os << value << m_p; return *this; } private: const char* m_p; std::ostream& m_os; }; int main() { /* # OUTPUT # 383 886 777 915 793 335 386 492 649 421 362 27 690 59 763 926 540 426 172 736 211 368 567 429 782 ----------------------------------------------------------------------------- 383, 886, 777, 915, 793, 335, 386, 492, 649, 421, 362, 27, 690, 59, 763, 926, 540, 426, 172, 736, 211, 368, 567, 429, 782, ----------------------------------------------------------------------------- 383 | 886 | 777 | 915 | 793 | 335 | 386 | 492 | 649 | 421 | 362 | 27 | 690 | 59 | 763 | 926 | 540 | 426 | 172 | 736 | 211 | 368 | 567 | 429 | 782 | ----------------------------------------------------------------------------- nurdan - ziya - fahri - tufan - cemal - feraye - lale - halime - nuriye - enes - derin - yurdanur - zahide - kezban - cihat - kayhan - tugay - saniye - zubeyde - fikret - abdullah - erdem - fahri - aycan - melih - ----------------------------------------------------------------------------- 8.61022e+08 ` 2.78723e+08 ` 2.33665e+08 ` 2.14517e+09 ` 4.68703e+08 ` 1.10151e+09 ` 1.80198e+09 ` 1.31563e+09 ` 6.35723e+08 ` 1.36913e+09 ` 1.1259e+09 ` 1.05996e+09 ` 2.08902e+09 ` 6.28175e+08 ` 1.65648e+09 ` 1.13118e+09 ` 1.65338e+09 ` 8.59484e+08 ` 1.91454e+09 ` 6.08414e+08 ` 7.56899e+08 ` 1.73458e+09 ` 1.97359e+09 ` 1.49798e+08 ` 2.03866e+09 ` */ std::vector iVec; fc(iVec, 25, []{ return rand() % 1000; }); pc(iVec); // Çıkış akımının kendisini kullanırsak, MyCopy(iVec.begin(), iVec.end(), OstreamIterator{std::cout}); // Yukarıdaki 'MyCopy' fonksiyon şablonunu kullanarak, std::cout << "\n-----------------------------------------------------------------------------\n"; MyCopy(iVec.begin(), iVec.end(), OstreamIterator{std::cout, " | "}); // Yukarıdaki 'MyCopy' fonksiyon şablonunu kullanarak, std::cout << "\n-----------------------------------------------------------------------------\n"; std::list sList; fc(sList, 25, rname); MyCopy(sList.begin(), sList.end(), OstreamIterator{std::cout, " - "}); std::cout << "\n-----------------------------------------------------------------------------\n"; std::ofstream ofs{"ofs.txt"}; if(!ofs) { std::cerr << "out.txt dosyası oluşturulamadı.\n"; exit(EXIT_FAILURE); } std::deque dDeque; fc(dDeque, 25, rand); MyCopy(dDeque.begin(), dDeque.end(), OstreamIterator{ofs, " ` "}); } * Örnek 2, 'STL' içerisinde bulunan 'ostream_iterator' sınıfının kullanılması: 'iterator' başlık dosyasında bildirimi/tanımı yapılmıştır. //.. int main() { /* # OUTPUT # 383 -> 886 -> 777 -> 915 -> 793 -> 335 -> 386 -> 492 -> 649 -> 421 -> 362 -> 27 -> 690 -> 59 -> 763 -> 926 -> 540 -> 426 -> 172 -> 736 -> 211 -> 368 -> 567 -> 429 -> 782 -> */ std::vector iVec; fc(iVec, 25, []{ return rand() % 1000; }); std::copy(iVec.begin(), iVec.end(), std::ostream_iterator{std::cout, " -> "}); } * Örnek 3, Bir kapta bulunan isimlerden 'c' karakterinden 'n' taneye sahip olanları yazdırma işlemi: //.. int main() { /* # OUTPUT # [murat karaelmas] , [zeliha kabasakal] , [semsit uzunadam] , [hakki belgeli] , [aytac canbay] , [sevim temizkalp] , [yasar samanci] , [olcay komurcu] , [fikret samanci] , [fazilet kelepce] , ----------------------------------------------------------------------------- [hakki belgeli]_[sevim temizkalp]_[fikret samanci]_ */ std::vector sVec; fc(sVec, 10, []{ return "[" + rname() + ' ' + rfname() + "]"; }); std::ofstream ofs{"ofs.txt"}; if(!ofs) { std::cerr << "out.txt dosyası oluşturulamadı.\n"; exit(EXIT_FAILURE); } copy(sVec.begin(), sVec.end(), std::ostream_iterator{ofs, " , "}); ofs << "\n-----------------------------------------------------------------------------\n"; char c = 'i'; // İş bu karakterden int n = 2; // adet olanları arayacağız. auto f = [c, n](const std::string& name){ return std::count(name.begin(), name.end(), c) == n; }; std::copy_if(sVec.begin(), sVec.end(), std::ostream_iterator{ofs, "_"}, f); } > 'STL' içerisindeki silme algoritmaları. 'STL' bünyesindeki bir algoritma SİLMA İŞLEMİ ve EKLEME İŞLEMLERİNİ YAPAMAZ. Çünkü bu işlemleri yapan kapların kendi üye fonksiyonlarıdır. İlgili algoritmalar argüman olarak ilgili kapları almadıklarından, nasıl o kapların üye fonksiyonlarına erişebilirler? Peki buradaki silme işleminden kastedilen nedir? El-cevap: Bahsedilen algoritmalar LOJİK SİLME işlemi yapmaktadır, gerçek silme işlemi yapmamaktadır. >> 'remove()' fonksiyonu: Aşağıdaki örneği inceleyelim. * Örnek 1, //.. int main() { /* # OUTPUT # ivec : [18] => 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 ivec : [18] => 1 2 3 4 5 6 7 8 8 7 6 5 4 3 2 1 2 1 */ std::vector iVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; std::cout << "ivec : [" << iVec.size() << "] => "; for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; auto logic_end = std::remove(iVec.begin(), iVec.end(), 9); std::cout << "ivec : [" << iVec.size() << "] => "; for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; // Çıktıtan da görüldüğü üzere ilgili kabın büyüklüğü değişmedi. Derleyici burada '9' rakamını kaptan içerisinden // ayıklar ve bu rakamın sağındaki rakamları da sola doğru kaydırdı. Yukarıdaki kullanım için iki defa öteleme işlemi // gerçekleştirildi. Bu işlemler yapılırken sağ taraftan da iki rakam daha eklendi fakat bu iki rakamın ne olduğu bizim // için ÖNEMLİ DEĞİL. Bu rakamlar '9' rakamı da olabilir başka rakamlar da. // 'remove()' fonksiyonu ise kaydırılan en sonki öğeden bir sonraki öğenin konumunu tutan bir iteratör döndürmekte. // Bir diğer değişle sağdan giren iki rakamdan soldaki rakamın konumu geri döndürüldü. Bu konum bilgisine ise 'logic_end' // konum bilgisi denmektedir. // Eğer bizler bu 'logic_end' ve '.end()' fonksiyonunun geri döndürdüğü iteratörleri, vektör sınıfının gerçekten de silme // işlemi yapan üye fonksiyonuna geçersek, gerçekten de silme işlemi gerçekleşecektir. /* # OUTPUT # ----------------------------------------------------------------------------- ivec : [16] => 1 2 3 4 5 6 7 8 8 7 6 5 4 3 2 1 ----------------------------------------------------------------------------- ivec : [2] => 2 1 ----------------------------------------------------------------------------- */ std::cout << "-----------------------------------------------------------------------------\n"; std::cout << "ivec : [" << std::distance(iVec.begin(), logic_end) << "] => "; pr(iVec.begin(), logic_end); // 'remove' fonksiyonu sonrasında gerçek rakamlar. std::cout << "ivec : [" << std::distance(logic_end, iVec.end()) << "] => "; pr(logic_end, iVec.end()); // 'remove' fonksiyonu sonrasında yeni eklenen önemsiz rakamlar. // Çıktıtan da görüldüğü üzere 'logic_end' konumu silinmemiş son öğeden sonraki öğe veya silinmiş ilk öğenin konumu. // Buradaki silme kelimesinden de kastedilen ilgili değişkenin 'range' içerisinden çıkartılmasıdır. /* # OUTPUT # ivec : [16] => 1 2 3 4 5 6 7 8 8 7 6 5 4 3 2 1 ----------------------------------------------------------------------------- */ iVec.erase(logic_end, iVec.end()); std::cout << "ivec : [" << iVec.size() << "] => "; pr(iVec.begin(), iVec.end()); // Gerçek silme işlemi sonucunda kaptaki rakamlar. // İşte şimdi gerçekten de silme işlemi gerçekleştirildi. } /*============================================================================================================*/ (27_19_12_2020) > 'STL' içerisindeki silme algoritmaları (devam) : >> 'remove-erase' idiom: Aşağıdaki örneği inceleyelim. * Örnek 1, //.. Varsayalım ki bu şekilde bir eleman kümemiz olsun => { 2, 3, 5, 2, 6, 7, 2, 8, 2, 9, 1, 2, 3 } '.begin()' => ^ '.end()' => ^ 'remove()' fonksiyonu ile '2' rakamlarını silelim => { 3, 5, 6, 7, 8, 9, 1, 3, *, *, *, *, * } '.begin()' => ^ 'logic_end' => ^ '.end()' => ^ '.erase()' fonksiyonu ile gerçek silme yapalım => { 3, 5, 6, 7, 8, 9, 1, 3 } '.begin()' => ^ '.end()' => ^ * Örnek 2, Yukarıdaki gösterimi C++ kodu ile de gösterelim. //.. int main() { /* # OUTPUT # iVec : [50] => 10 18 17 15 17 12 12 10 20 14 4 5 7 3 14 9 20 5 17 6 4 6 3 19 4 13 0 10 7 2 8 14 8 10 14 11 7 11 7 10 13 9 10 5 14 16 14 0 18 14 iVec : [49] => 10 18 17 17 12 12 10 20 14 4 5 7 3 14 9 20 5 17 6 4 6 3 19 4 13 0 10 7 2 8 14 8 10 14 11 7 11 7 10 13 9 10 5 14 16 14 0 18 14 */ std::vector iVec; fc(iVec, 50, Irand{0, 20}); std::cout << "iVec : [" << iVec.size() << "] => "; for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; int numberToDelete = 15; iVec.erase ( std::remove( iVec.begin(), iVec.end(), numberToDelete ), iVec.end() ); std::cout << "iVec : [" << iVec.size() << "] => "; for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; } >> 'remove_if()' fonksiyonu : Belirli bir koşulu sağlayanları kaptan çıkartmaya yarıyor. * Örnek 1, //.. int main() { /* # OUTPUT # iVec : [50] => 16 17 13 17 5 4 5 18 8 3 2 8 16 8 9 13 14 8 0 19 8 18 15 19 6 6 14 9 15 8 12 18 9 12 0 2 8 15 17 1 16 6 20 18 15 12 0 4 15 11 iVec : [42] => 16 17 13 17 5 4 5 18 8 3 2 8 16 8 9 13 14 8 19 8 18 19 6 6 14 9 8 12 18 9 12 2 8 17 1 16 6 20 18 12 4 11 */ std::vector iVec; fc(iVec, 50, Irand{0, 20}); std::cout << "iVec : [" << iVec.size() << "] => "; for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; int numberToDelete = 15; iVec.erase ( std::remove_if( iVec.begin(), iVec.end(), [numberToDelete](int a) {return a % numberToDelete == 0;} ), iVec.end() ); // '15' rakamına tam bölünenleri kaptan gerçekten de siliyoruz. std::cout << "iVec : [" << iVec.size() << "] => "; for(auto index : iVec) std::cout << index << " "; std::cout << "\n"; } * Örnek 2, //.. int main() { /* # OUTPUT # cumhur_yurdakul_emine_zerrin_yeliz_hasan_adnan_muslum_celal_yurdanur_akin_murathan_emirhan_agah_hilmi_ mert_emrecan_ugur_bora_muslum_kazim_ceyhun_utku_suleyman_mahir_ ----------------------------------------------------------------------------- cumhur_yurdakul_emine_zerrin_yeliz_hasan_muslum_celal_yurdanur_murathan_emirhan_hilmi_mert_emrecan_ugur_ bora_muslum_kazim_ceyhun_utku_suleyman_mahir_ ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 25, rname); pc(sVec, "_"); char characterToLookUp = 'a'; sVec.erase ( std::remove_if( sVec.begin(), sVec.end(), [characterToLookUp](const auto& name){ return !name.empty() && name.at(0) == characterToLookUp; } ), sVec.end() ); pc(sVec, "_"); } >> 'unique()' fonksiyonu : Ardışık ve eş değer öğelerin sayısını bire indirmektedir. 'remove()' gibi 'logic_end' konumunu döndürmektedir. '.operator==()' fonksiyonunu varsayılan fonksiyon olarak kullanmaktadır fakat bir 'overload' versiyonu da bizden 'predicate' istemektedir. Böylelikle 'custom' bir karşılaştırma kriteri oluşturabiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # 3 | 3 | 2 | 1 | 0 | 1 | 3 | 0 | 1 | 1 | 1 | 1 | 3 | 0 | 2 | 1 | 3 | 2 | 3 | 2 | 1 | 2 | 1 | 0 | 2 | ----------------------------------------------------------------------------- 3 | 2 | 1 | 0 | 1 | 3 | 0 | 1 | 3 | 0 | 2 | 1 | 3 | 2 | 3 | 2 | 1 | 2 | 1 | 0 | 2 | ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 25, Irand{0, 3}); pc(iVec, " | "); iVec.erase ( std::unique(iVec.begin(), iVec.end()), iVec.end() ); pc(iVec, " | "); } * Örnek 2, //.. int main() { /* # OUTPUT # 39 | 80 | 5 | 63 | 2 | 66 | 63 | 87 | 36 | 12 | 38 | 26 | 41 | 37 | 69 | 69 | 13 | 26 | 2 | 62 | 74 | 46 | 85 | 28 | 50 | 36 | 19 | 66 | 37 | 16 | 92 | 23 | 52 | 78 | 71 | 40 | 37 | 60 | 42 | 63 | 24 | 64 | 100 | 85 | 64 | 43 | 49 | 46 | 34 | 27 | ----------------------------------------------------------------------------- 2 | 5 | 12 | 13 | 16 | 19 | 23 | 24 | 26 | 27 | 28 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 49 | 50 | 52 | 60 | 62 | 63 | 64 | 66 | 69 | 71 | 74 | 78 | 80 | 85 | 87 | 92 | 100 | ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 50, Irand{0, 100}); pc(iVec, " | "); std::sort(iVec.begin(), iVec.end()); iVec.erase ( std::unique(iVec.begin(), iVec.end()), iVec.end() ); pc(iVec, " | "); } * Örnek 3, //.. int main() { /* # OUTPUT # 89 | 28 | 41 | 42 | 7 | 0 | 43 | 55 | 18 | 81 | 89 | 4 | 74 | 96 | 100 | 89 | 25 | 22 | 1 | 33 | 50 | 70 | 48 | 88 | 93 | 10 | 2 | 33 | 59 | 27 | 79 | 85 | 51 | 19 | 54 |91 | 94 | 7 | 36 | 44 | 77 | 29 | 11 | 78 | 61 | 83 | 78 | 50 | 36 | 34 | ----------------------------------------------------------------------------- 89 | 28 | 41 | 42 | 7 | 0 | 43 | 18 | 81 | 4 | 89 | 22 | 1 | 50 | 93 | 10 | 33 | 54 | 91 | 94 | 7 | 36 | 77 | 78 | 61 | 78 | ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 50, Irand{0, 100}); pc(iVec, " | "); iVec.erase ( std::unique(iVec.begin(), iVec.end(), [](int a, int b){ return a % 2 == b % 2; }), iVec.end() ); pc(iVec, " | "); } * Örnek 4, //.. int main() { /* # OUTPUT # kunter_esra_fahri_sadullah_tijen_suleyman_atakan_askin_naciye_nalan_eda_teslime_ ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 250, rname); sVec.erase ( std::unique(sVec.begin(), sVec.end(), [](const auto& s1, const auto& s2){ return s1.size() == s2.size(); }), sVec.end() ); pc(sVec, "_"); } * Örnek 5(Mülakat Sorusu), Bir yazıdaki boşluk karakterlerinin sayısını teke indir: //.. int main() { /* # OUTPUT # Girilen yazi => [Ahmet Kandemir Pehlivanli Sultangazi Istanbul Turkey] Yazi => [Ahmet Kandemir Pehlivanli Sultangazi Istanbul Turkey] */ std::string letter; std::cout << "Yaziyi giriniz => "; std::getline(std::cin, letter); std::cout << "Girilen yazi => [" << letter << "]\n"; letter.erase ( std::unique(letter.begin(), letter.end(), [](char c1, char c2){ return isspace(c1) && isspace(c2); }), letter.end() ); std::cout << "Yazi => [" << letter << "]\n"; } > 'STL' içerisinde sonu '_copy' ile biten algoritmalar: Bir işlem yapılmış gibi bir 'range' i başka bir yere kopyalayan algoritmalardır. Örneğin, 'reverse' algoritması argüman aldığı 'range' içerisindeki öğeleri tersine çevirmektedir. 'reverse_copy' ise tersine çevirip bir başka 'range' e kopyalamaktadır. Benzer şekilde 'replace', belirli değerdeki öğeyi başka bir değer ile değiştiriyor. 'replace_copy' ise bu işlem yapılmış gibi başka bir yere kopyalamaktadır. Yine aynı şekilde 'remove' algoritması bir 'range' üzerinde lojik silme işlemi yapmakta. Dolayısla 'remove_copy' ise bu işlem yapılmış gibi başka bir yere kopyalamaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # sVec : [25] => sabriye celal necmiye fugen yilmaz celal kaan ahmet murathan sevilay zekai kazim hulusi malik bennur derin demet melike bekir cezmi efe yusuf malik sarp edip sList : [0] => sVec : [25] => sabriye celal necmiye fugen yilmaz celal kaan ahmet murathan sevilay zekai kazim hulusi malik bennur derin demet melike bekir cezmi efe yusuf malik sarp edip sList : [24] => sabriye celal necmiye fugen yilmaz celal kaan murathan sevilay zekai kazim hulusi malik bennur derin demet melike bekir cezmi efe yusuf malik sarp edip */ std::vector sVec; fc(sVec, 25, rname); std::cout << "sVec : [" << sVec.size() << "] => "; for(auto index : sVec) std::cout << index << " "; std::cout << "\n"; std::list sList; std::cout << "sList : [" << sList.size() << "] => "; for(auto index : sList) std::cout << index << " "; std::cout << "\n"; std::string nameToDelete = "ahmet"; std::remove_copy(sVec.begin(), sVec.end(), std::back_inserter(sList), nameToDelete); // Hedef kap boş olduğundan 'back_inserter' kullandık. std::cout << "sVec : [" << sVec.size() << "] => "; for(auto index : sVec) std::cout << index << " "; std::cout << "\n"; std::cout << "sList : [" << sList.size() << "] => "; for(auto index : sList) std::cout << index << " "; std::cout << "\n"; } * Örnek 2, //.. int main() { /* # OUTPUT # 660_967_205_733_606_547_529_547_488_349_190_659_102_821_306_149_330_61_795_29_674_863_387_557_438_619_762_491_ */ std::vector iVec; fc(iVec, 100, Irand{0, 1000}); std::unique_copy( iVec.begin(), iVec.end(), std::ostream_iterator{std::cout, "_"}, [](int a, int b) { return isprime(a) == isprime(b); } ); } * Örnek 3, 'reverse_copy()' fonksiyonu: //.. // Temsili bir implementasyonu. template OutIter ReverseCopy(BidIter beg, BidIter end, OutIter destBeg) { while( beg != end ) *destgBeg++ = *--end; return destBeg; } >> Yine bu tip algoritmaların sonuna '_if' gelirse de belirli şartları sağlayanları ele alıyor. >>> 'remove_copy_if' : Belirli bir şartı sağlayanları başka bir 'range' aralığına kopyalamaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # Vector : [10] => 02 Subat 1963 Cumartesi,07 Eylul 2007 Cuma,16 Kasim 1984 Cuma,06 Ocak 1969 Pazartesi, 11 Subat 1967 Cumartesi,20 Temmuz 1955 Carsamba,27 Eylul 1992 Pazar,02 Subat 1952 Cumartesi, 05 Ocak 1954 Sali,11 Ocak 1980 Cuma, ----------------------------------------------------------------------------- List : [0] => ----------------------------------------------------------------------------- Vector : [10] => 02 Subat 1963 Cumartesi,07 Eylul 2007 Cuma,16 Kasim 1984 Cuma,06 Ocak 1969 Pazartesi, 11 Subat 1967 Cumartesi,20 Temmuz 1955 Carsamba,27 Eylul 1992 Pazar,02 Subat 1952 Cumartesi, 05 Ocak 1954 Sali,11 Ocak 1980 Cuma, ----------------------------------------------------------------------------- List : [7] => 07 Eylul 2007 Cuma,16 Kasim 1984 Cuma,06 Ocak 1969 Pazartesi,20 Temmuz 1955 Carsamba, 27 Eylul 1992 Pazar,05 Ocak 1954 Sali,11 Ocak 1980 Cuma, ----------------------------------------------------------------------------- */ std::vector dVec; fc(dVec, 10, Date::random); std::list dList; std::cout << "Vector : [" << dVec.size() << "] => "; pc(dVec, ","); std::cout << "List : [" << dList.size() << "] => "; pc(dList, ","); int monthToDelete = 2; remove_copy_if ( dVec.begin(), dVec.end(), std::back_inserter(dList), [monthToDelete](const Date& date){ return date.month() == monthToDelete; } ); std::cout << "Vector : [" << dVec.size() << "] => "; pc(dVec, ","); std::cout << "List : [" << dList.size() << "] => "; pc(dList, ","); } > 'for_each' : Bir 'range' içerisindeki öğeleri bir fonksiyona argüman olarak göndermektedir. * Örnek 1, Temsili implementasyonu: //.. template F ForEach(Iter beg, Iter end, F callable) { while( beg != end ) callable(*beg++); return callable; } int main() { /* # OUTPUT # 1 | -1 | 2 | -2 | 3 | -3 | 4 | -4 | 5 | -5 | ----------------------------------------------------------------------------- 11 | -11 | 22 | -22 | 33 | -33 | 44 | -44 | 55 | -55 | ----------------------------------------------------------------------------- 1 | -1 | 2 | -2 | 3 | -3 | 4 | -4 | 5 | -5 | */ std::vector iVec{ 1, -1, 2, -2, 3, -3, 4, -4, 5, -5 }; pc(iVec, " | "); int multiplyBy = 11; ForEach(iVec.begin(), iVec.end(), [multiplyBy](int& a){ a *= multiplyBy; }); pc(iVec, " | "); int divideBy = multiplyBy; std::for_each(iVec.begin(), iVec.end(), [divideBy](int& a){ a /= divideBy; }); std::for_each(iVec.begin(), iVec.end(), [](int a){ std::cout << a << " | "; }); } * Örnek 2, //.. int main() { /* # OUTPUT # nisan | bilal | derya | tugay | mert | zubeyde | atalay | fazilet | gulsah | sami | ----------------------------------------------------------------------------- nisan_can | bilal_can | derya_can | tugay_can | mert_can | zubeyde_can | atalay_can | fazilet_can | gulsah_can | sami_can | ----------------------------------------------------------------------------- */ std::list sList; fc(sList, 10, rname); pc(sList, " | "); std::string wordToAppend = "_can"; for_each(sList.begin(), sList.end(), [wordToAppend](std::string& name){ name += wordToAppend; }); pc(sList, " | "); } > 'transform()' fonksiyonu : İki adet 'overload' versiyonu vardır. Bunlardan ilki 'range' içerisindeki öğeleri bir fonksiyona argüman olarak gönderiyor, o fonksiyondan elde edilen geri dönüş değerini bir 'range' içerisine yazmaktadır. İkinci 'overload' versiyonunda ise argüman olarak alınan fonksiyon iki parametreli. Bir 'range' den ilk argümanı, bir diğer 'range' den ikinci argümanı alacak ve iş bu fonksiyona gönderecek. Geri dönüş değerini de bir 'range' içerisine yazacaktır. * Örnek 1, Birinci 'overload' //.. // Temsili implementasyonu: template OutIter MyTransform(InIter beg, InIter end, OutIter destBeg, Func callable) { while( beg != end ) *destBeg++ = callable(*beg++); return destBeg; } int main() { /* # OUTPUT # 12 34 56 7 9 2 1 3 ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- 12 34 56 7 9 2 1 3 ----------------------------------------------------------------------------- 17 39 61 12 14 7 6 8 ----------------------------------------------------------------------------- 12 34 56 7 9 2 1 3 ----------------------------------------------------------------------------- 12 34 56 7 9 2 1 3 ----------------------------------------------------------------------------- */ std::vector iVec{ 12, 34, 56, 7, 9, 2, 1, 3 }; std::list iList; pc(iVec); pc(iList); MyTransform(iVec.begin(), iVec.end(), std::back_inserter(iList), [](int x){ return x + 5; }); pc(iVec); pc(iList); std::transform(iList.begin(), iList.end(), iList.begin(), [](int x){ return x - 5; }); pc(iVec); pc(iList); } * Örnek 2, İkinci 'overload' //.. // Temsili implementasyonu: template OutIter MyTransform(InIterOne beg, InIterOne end, InIterTwo begTwo, OutIter destBeg, Func callable) { while( beg != end ) *destBeg++ = callable(*beg++, *begTwo++); return destBeg; } int main() { /* # OUTPUT # 1 | 2 | 3 | 4 | 5 | 6 | ----------------------------------------------------------------------------- -1 | -2 | -3 | -4 | -5 | -6 | ----------------------------------------------------------------------------- 0 | 0 | 0 | 0 | 0 | 0 | ----------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ----------------------------------------------------------------------------- -1 | -2 | -3 | -4 | -5 | -6 | ----------------------------------------------------------------------------- 2 | 4 | 6 | 8 | 10 | 12 | ----------------------------------------------------------------------------- */ std::vector sourceOne{ 1, 2, 3, 4, 5, 6 }; std::vector sourceTwo{ -1, -2, -3, -4, -5, -6 }; std::vector destOne(sourceOne.size()); std::transform( sourceOne.begin(), sourceOne.end(), sourceTwo.begin(), destOne.begin(), [](int a, int b){ return a + b; } ); pc(sourceOne, " | "); pc(sourceTwo, " | "); pc(destOne, " | "); MyTransform( sourceOne.begin(), sourceOne.end(), sourceTwo.begin(), destOne.begin(), [](int a, int b){ return a - b; } ); pc(sourceOne, " | "); pc(sourceTwo, " | "); pc(destOne, " | "); } > 'STL' içerisindeki kaplar: >> 'std::vector' : Eğer sondan ekleme yapıyorsak 'constant-time', 'std::list' de ise hangi noktadan yapıyorsam yapayım 'constant-time'. Yine karmaşıklık açısından sırasıyla 'O(n)' ve 'O(1)' karmaşıklığında. Fakat zaman içerisinde bu sınınfa o kadar güzel implementasyonlar yapılmış ki belli bir büyüklüğe kadar 'std::vector' sınıfı, 'std::list' sınıfından daha verimli çalışmaktadır. >>> 'std::vector' de bir sınıf şablonudur. * Örnek 1, //.. // Temsili implementasyonu: template class CustomAllocator; // Temsili implementasyon: template> class Vector{}; // Temsili implementasyon: template using CustomVec = std::Vector>; int main() { std::Vector iVec; // İlgili fonksiyon şablonunun; // i. Birinci parametresi, ki bu durumda 'T' ye karşılık gelecek tür, kapta tutulacak öğelerin tür bilgisidir. // ii. İkinci parametresi, ki bu durumda 'A' ye karşılık gelecek tür, dinamik bellek alanı ihtiyacını karşılayan // bir sınıftır. Bu sınıf ayrıca o bellek alanında nesnelerin oluşturulması ve yok edilmesinden de sorumludurlar. // Varsayılan değer olarak 'std::allocator' sınıf şablonu kullanılır. std::Vector> myCustomVec; // Görüldüğü üzere 'allocator' olarak bizim 'custom' allocator sınıfımız kullanıldı. // Uzun uzun yazmak yerine aşağıdaki gibi bir 'template alias' da kullanabilirdik; CustomVec myCustomVecTwo; } >>> Dinamik dizi veri yapısını implemente etmektedir. Kapasite dolduğunda, yeni bir ekleme yapılırsa, ilgili bellek alanı başka bir yere taşınıyor eğer halihazırdaki konumun devamında yeteri kadar yer yok ise. >>> Modern C++ ile dile 'initializer_list' parametre türünden bir 'Ctor.' eklenmiştir. >>>> 'initializer_list' hatırlatması: Üye fonksiyon olarak sadece '.begin()', '.end()' ve '.size()' fonksiyonu vardır. Fakat bu fonksiyonların 'global function' eşleniklerine, bu sınıf türünü argüman olarak geçebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # [4] => ali can veli ahmet */ std::initializer_list nameList{ "ali", "can", "veli", "ahmet" }; std::cout << "[" << nameList.size() << "] => "; std::copy(nameList.begin(), nameList.end(), std::ostream_iterator{std::cout, " "}); // Bu sınıf türünden iteratörleri salt okuma amaçlı kullanabiliriz. auto iter = nameList.begin(); *(++iter) = "_"; // error: no match for ‘operator=’ // (operand types are ‘const std::__cxx11::basic_string’ and ‘const char [2]’) } * Örnek 2, Tür çıkarımını bu sınıf türünden de yaptırabiliriz. Tek istisna, fonksiyon şablonlarında. //.. template void func(T x) {} int main() { auto x = { 1 }; // Tür çıkarımı 'std::initializer_list' auto y{ 10 }; // Artık Modern C++ ile burada tür çıkarımı 'int' şeklinde. auto xx = { 1.1, 2.2 }; // Tür çıkarımı 'std::initializer_list' auto yy{ 10, 20 }; // Sentaks hatası oluşuyor. func({ 1, 2, 3, 4 }); // İstisna olan kısım burası. Argüman olarak virgüller ile ayrılan bir liste gönderildiğinde tür çıkarımı // yapılamıyor ve sentaks hatası oluşuyor. } * Örnek 3, En çok 'Ctor.' fonksiyonlarının parametre olarak kullanılırlar. //.. class Myclass{ public: Myclass(int) { std::cout << "1\n"; } Myclass(std::initializer_list) { std::cout << "2\n"; } }; int main() { Myclass m(10); // '1' numaralı çağrılacaktır. Myclass mm{10}; // '2' numaralı çağrılacaktır. Çünkü 'Function Overload Resolution' aşamasında ilgili 'overload' versiyonun // seçilebilirliği daha yüksektir. } * Örnek 4, Bazı sınıf şablonlarında 'size_t' parametreli 'Ctor.' ile birlikte kullanılır. BU NOKTAYA DİKKAT EDİLMESİ GEREKİYOR. //.. int main() { std::string s(10, 'A'); // 10 adet öğe içermektedir ve her birisinin değeri 'A' karakteridir. std::string s{48, 'A'}; // 2 adet öğe içermektedir. İlk öğe 48 kodlu karakter, ki '0' karakterine karşılık gelmektedir. // İkinci öğe ise 'A' karakteridir. std::vector x(10); // 10 tane öğe var, hepsi de '0' ile hayata gelik. Çünkü burada 'size_t' parametreli 'Ctor.' çağrıldı. std::vector y{10}; // 1 tane öğe var, '10' ile hayata gelik. Çünkü burada 'std::initializer_list' parametreli 'Ctor.' çağrıldı. std::vector x(10, 20); // 10 tane öğe var, hepsi de '20' ile hayata gelikler. Çünkü burada 'size_t' parametreli 'Ctor.' çağrıldı. std::vector y{10, 20}; // 2 tane öğe var, '10' ve '20' ile hayata gelikler. Çünkü burada 'std::initializer_list' parametreli 'Ctor.' çağrıldı. } * Örnek 5, //.. class Summer{ public: Summer(std::initializer_list list) { int sum = 0; for(auto index : list) sum += index; m_average = static_cast(sum) / list.size(); } double getAverate()const { return m_average; } private: double m_average; }; int main() { Summer mx{ 2, 7, 9, 5, 3 }; std::cout << "Average : " << mx.getAverate(); // OUTPUT => Average : 5.2 } * Örnek 6, //.. void func(std::initializer_list list) { /*...*/ } int main() { auto myList = { 1, 3, 7, 12 }; func(myList); // BURADA BİR KOPYALAMA YOKTUR. ARKA PLANDA BİR 'const' BİR DİZİ OLUŞTURULMAKTADIR VE // ADRES BİLGİSİ GEÇİLMEKTEDİR. Fakat yine de ilgili fonksiyonun imzasında '&' deklaratörü // kullanabiliriz. } * Örnek 7, //.. int main() { /* # OUTPUT # 2 | 4 | 6 | 8 | 10 | ----------------------------------------------------------------------------- 2 | -1 | -2 | -3 | -4 | 4 | 6 | 8 | 10 | ----------------------------------------------------------------------------- */ std::vector iVec{ 2, 4, 6, 8, 10 }; pc(iVec, " | "); iVec.insert ( std::next(iVec.begin()), { -1, -2, -3, -4 } ); pc(iVec, " | "); } >>>> Kapta tutulan öğelerin sınıf türlerinden olması durumunda '{}' kullanılması bir anlam farklılığı OLUŞTURMAYACAKTIR. //.. int main() { Date dx{ 12, 5, 1999 }; std::vector dVec{ 10, dx }; // Tutulan öğeler sınıf türünden oldukları için; kapta 10 tane öğe olacak ve her birinin değeri 'dx' nesnesi // ile aynı olacak. } >>> Pekiştirici bir örnek: * Örnek 1, //.. int main() { std::list myList{ 12, 53, 78, 95, 65 }; std::vector x; // Default Ctor. std::vector y{x}; // Copy Ctor. std::vector z(10); // 'size_t' Parametreli Ctor. std::vector z(10, 45); // 'size_t' ve 'T' Parametreli Ctor. std::vector a{10, 45}; // 'std::initializer_list' Parametreli Ctor. std::vector b{ myList.begin(), myList.end() }; // 'range' Parametreli Ctor. std::list myVec{ 12, 53, 78, 95, 65 }; std::list myVecTwo{ std::move(myVec) }; // Move Ctor. } * Örnek 2, Bir fonksiyonun geri dönüş değeri 'container' ise tipik olarak otomatik ömürlü bir nesne döndürecektir; bu durumda ya 'Move Semantics' devreye giriyor ya da derleyicinin yaptığı optimizasyonlar sonucu kopyalama maliyetinden kaçınılmış olmaktadır. AMACIMIZ ÇAĞIRAN KODA BİR 'container' İLETMEK İSE AŞAĞIDAKİ ŞEKİLDE RAHATLIKLA YAPABİLİRİZ, MODERN CPP KULLANIYORSAK. //.. std::vector myFunc(void) { std::vector sVec; //.. some code here return sVec; } int main() { auto vec = func(); // Burada kopyalamanın maliyetinden kaçınılmış oluyor, Modern Cpp dönemiyle birlikte. } >>> 'std::vector' sınıfının arayüzündeki fonksiyonlar: >>>> İsminde 'begin' ve 'end' içereren bütün fonksiyonlar iteratör veren fonksiyonlardır. >>>> '.capacity()' fonksiyonu, 'reallocation' gerçekleşmeden evvel, kabın tutabileceği maksimum öğe sayısını döndürmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # [0x5599d3768f70], size / capacity => 20 / 32 12 adedince yeni oge eklenebilir. [0x5599d3768f70], size / capacity => 32 / 32 [0x5599d3769410], size / capacity => 33 / 64 [0x7ffebcfc4ce0], size / capacity => 1033 / 2048 */ std::vector iVec; fc(iVec, 20, rand); std::cout << "[" << &iVec.at(0) << "], size / capacity => " << iVec.size() << " / " << iVec.capacity() << "\n"; auto remainingSize = (iVec.capacity() - iVec.size()); std::cout << remainingSize << " adedince yeni oge eklenebilir.\n"; for (size_t i = 0; i < remainingSize; i++) { iVec.push_back(i); } std::cout << "[" << &iVec.at(0) << "], size / capacity => " << iVec.size() << " / " << iVec.capacity() << "\n"; iVec.push_back(0); std::cout << "[" << &iVec.at(0) << "], size / capacity => " << iVec.size() << " / " << iVec.capacity() << "\n"; for (int i = 0; i < 1'000; i++) { iVec.push_back(i); } std::cout << "[" << &iVec << "], size / capacity => " << iVec.size() << " / " << iVec.capacity() << "\n"; // Çıktıtan da görülebileceği üzere 'reallocation' yapıldıktan sonra öğelerin adresleri değişmektedir. // Bu da beraberinde şöyle bir problemi getirebilir; velevki bizim öğelerimi gösteren göstericilerimiz, // iteratörlerimiz olsun. 'reallocation' sonucunda öğelerin yerleri değiştiği taktirde bu göstericiler/iteratörler // eski adresleri/konumları göstereceklerinden dolayı artık 'invalid' hale gelecekler. Bu duruma da // 'iterator_invalidation' denmektedir. } * Örnek 2, //.. int main() { /* # OUTPUT # 1804289383_846930886_1681692777_1714636915_1957747793_ ----------------------------------------------------------------------------- size / capacity 5 / 8 *iter = 1804289383 3 kadar oge daha eklenebilir... size / capacity 8 / 8 1804289383_846930886_1681692777_1714636915_1957747793_1_1_1_2_ ----------------------------------------------------------------------------- size / capacity 9 / 16 *iter = 0 3 kadar oge daha eklenebilir... */ std::vector iVec; fc(iVec, 5, rand); pc(iVec, "_"); std::cout << "size / capacity\n"; std::cout << std::setw(4) << std::left << iVec.size() << " / " << std::setw(5) << iVec.capacity() << "\n\n"; auto iter = iVec.begin(); std::cout << "*iter = " << *iter << "\n"; auto remainingSize = iVec.capacity() - iVec.size(); std::cout << remainingSize << " kadar oge daha eklenebilir...\n"; for (int i = 0; i < remainingSize; i++) { iVec.push_back(1); } std::cout << "\nsize / capacity\n"; std::cout << std::setw(4) << std::left << iVec.size() << " / " << std::setw(5) << iVec.capacity() << "\n\n"; iVec.push_back(2); // Bu çağrı sonucunda 'reallocation' gerçekleşeceğinden, yukarıdaki 'iter' isimli iteratör artık // 'dangling' hale gelmiştir. for (auto i : iVec) { std::cout << i << "_"; } std::cout << "\n-----------------------------------------------------------------------------\n"; std::cout << "\nsize / capacity\n"; std::cout << std::setw(4) << std::left << iVec.size() << " / " << std::setw(5) << iVec.capacity() << "\n\n"; std::cout << "*iter = " << *iter << "\n"; std::cout << remainingSize << " kadar oge daha eklenebilir...\n"; } >>>> '.at()' ve '.operator[]' fonksiyonları ile belirli bir indisteki öğelere erişebiliriz. Tıpkı 'std::string' sınıfında olduğu gibi. Bu fonksiyonların geri dönüş değeri 'reference' olduğundan, ilgili indisteki öğeleri de değiştirebiliriz. Geçersiz indis bilgisi geçildiğide sadece '.at()' fonksiyonu bir hata gönderecektir. Diğer fonksiyon ise 'run-time' hatasına neden olmakta. Ek olarak bu iki fonksiyonun da 'const' 'overload' edilmiş versiyonu vardır. 'const' nesneleri için kullanılmaktadır. Dolayısla artık bu fonksiyonların geri dönüş değeri de 'const-reference' şeklindedir. Bu iki fonksiyon haricinde, 'sequence-container' gruplarda ortak olan '.back()' ve '.front()' fonksiyonları da vardır ki onlar hakkında bilgi aşağıda paylaşılmıştır. Son olarak unutmamalıyız ki 'iterator' kullanarak da bizler kap içerisindeki nesnelere erişim sağlayabiliriz. >>>>> Bir 'std::vector' ü dolaşmak için üç farklı seçeneğimiz vardır. Bunlar; >>>>>> '.at()' ve/veya '.operator[]' fonksiyonları ile birlikte C dilinde de olan 'for-loop' kullanmak: * Örnek 1, //.. int main() { std::vector sVec; fc(iVec, 10, rtown); for(size_t i = 0; i < sVec.size(); ++i) { std::cout << sVec.at(i) << " "; // std::cout << sVec[i] << " "; } } >>>>>> '.at()' ve/veya '.operator[]' fonksiyonları ile birlikte C dilinde de olan 'for-loop' döngüsünü 'iterator' ile birlikte kullanmak: * Örnek 1, //.. int main() { std::vector sVec; fc(iVec, 10, rtown); for(auto iter = sVec.cbegin(); iter != sVec.cend(); ++iter) { std::cout << *iter << " "; } } >>>>>> 'range-based for-loop' kullanmak: * Örnek 1, //.. int main() { std::vector sVec; fc(iVec, 10, rtown); for(auto name : sVec) std::cout << name << " "; } * Örnek 2, Okuma amaçlı dolaşmak için aşağıdaki yöntemi kullanmalıyız. //.. int main() { std::vector sVec; fc(iVec, 10, rtown); for(const auto& name : sVec) std::cout << name << " "; } * Örnek 3, Değiştirme amacıyla aşağıdaki yöntemi kullanabiliriz. //.. int main() { std::vector sVec; fc(iVec, 10, rtown); for(auto& name : sVec) name += "_can"; } >>>> '.reserve()' : Bu fonksiyon kapasiteyi rezerve etmektedir. 'size' büyüklüğüne bir etkisi yoktur. Sadece ekstra 'reallocation' yapılmasının önüne geçmektedir. Kapasiteyi bu fonksiyon ile düşürmek ise derleyiciye bağlı bir durumdur. Negatif değer argüman olarak geçilirse bir hata gönderecektir. * Örnek 1, //.. int main() { std::vector iVec; ivec.reserve(100); // Kapasite büyüklüğünü bir şekilde ön gördüğümüzü varsayar isek // 'reallocation' adedini azaltmış oluruz. std::cout << "size = " << iVec.size() << "\n"; std::cout << "capacity = " << iVec.capacity() << "\n"; } * Örnek 2, //.. int main() { /* # OUTPUT # 1. allocation happened. size = 11, capacity = 20 2. allocation happened. size = 21, capacity = 40 3. allocation happened. size = 41, capacity = 80 4. allocation happened. size = 81, capacity = 160 5. allocation happened. size = 161, capacity = 320 6. allocation happened. size = 321, capacity = 640 7. allocation happened. size = 641, capacity = 1280 8. allocation happened. size = 1281, capacity = 2560 9. allocation happened. size = 2561, capacity = 5120 10. allocation happened. size = 5121, capacity = 10240 ... */ std::vector iVec(10); auto lastCapacity = iVec.capacity(); int allocationCounter{}; for(;;) { iVec.push_back(0); auto currentCapacity = iVec.capacity(); if( currentCapacity > lastCapacity ) { std::cout << ++allocationCounter << ". allocation happened. size = " << iVec.size() << ", capacity = " << iVec.capacity(); lastCapacity = currentCapacity; (void)getchar(); } } } * Örnek 3, int main() { /* # OUTPUT # 1. allocation happened. size = 10001, capacity = 20000 ... */ std::vector iVec(10); iVec.reserve(10000); auto lastCapacity = iVec.capacity(); int allocationCounter{}; for(;;) { iVec.push_back(0); auto currentCapacity = iVec.capacity(); if( currentCapacity > lastCapacity ) { std::cout << ++allocationCounter << ". allocation happened. size = " << iVec.size() << ", capacity = " << iVec.capacity(); lastCapacity = currentCapacity; (void)getchar(); } } } * Örnek 4, //.. int main() { /* # OUTPUT # size = 100, capacity = 1000 size = 100, capacity = 1000 */ std::vector iVec(100); iVec.reserve(1000); std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; iVec.reserve(200); std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity(); } >>>> '.shrink_to_fit()' : Modern Cpp ile dile eklenen bir fonksiyondur. Fazla kapasiteyi büzmek için kullanılır. * Örnek 1, //.. int main() { /* # OUTPUT # size = 10000, capacity = 16384 size = 1, capacity = 16384 size = 1, capacity = 1 */ std::vector iVec; for(int i = 0; i < 10'000; ++i) iVec.push_back(i); std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; iVec.erase(std::next(iVec.begin()), iVec.end()); // Kapta bir öğe kalıncaya kadar bütün öğeler silindi. std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; iVec.shrink_to_fit(); // Fazlalık kapasite miktarı küçültüldü. std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; } * Örnek 2, 'swap-trick' hilesi: Artık bu hileyi kullanmaya gerek kalmadı. //.. int main() { /* # OUTPUT # size = 10000, capacity = 16384 size = 1, capacity = 16384 size = 1, capacity = 1 */ std::vector iVec; for(int i = 0; i < 10'000; ++i) iVec.push_back(i); std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; iVec.erase(std::next(iVec.begin()), iVec.end()); // Kapta bir öğe kalıncaya kadar bütün öğeler silindi. std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; std::vector{iVec}.swap(iVec); // swap-trick : 'iVec' nesnesi ile bir Geçici Nesne oluşturuyoruz. Oluşturulan bu geçici nesnenin // '.size()' büyüklüğü aynı kalmakta fakat '.capacity()' büyüklüğü, '.size()' büyüklüğüne uygun olmakta. // Oluşturulan bu geçici nesne üzerinden '.swap()' fonksiyonu çağrılıyor ve argüman olarak da bizim orjinal // 'iVec' isimli sınıf nesnemiz kullanılıyor. Kaptaki öğeler değişmezken, '.capacity()' bilgisi 'swap' // ediliyor. Böylelikle geçici nesnenin '.capacity()' bilgisi bizim has sınıf nesnemize geçmiş oluyor. std::cout << " size = " << iVec.size() << ", capacity = " << iVec.capacity() << "\n"; } >>>> 'std::vector' sınıfında 'push_front' fonksiyonu YOKTUR. >>>> Bir 'std::vector' içerisindeki bütün öğeleri silmek için neler yapabiliriz? El-cevab : Konteynırın '.clear()' fonksiyonunu kullanmalıyız. * Örnek 1, //.. int main() { std::vector sVec; fc(sVec, 50, rname); pc(sVec); // sVec.clear(); // I // sVec.resize(0); // II // sVec.erase(sVec.begin(), sVec.end()); // III : Kapasite büzülmeyecektir. // sVec = std::vector{}; // IIII : Kapasite büzülecektir. } >>> 'std::string' sınıfındaki 'Small Buffer Optimization' bu sınıfta MEVCUT DEĞİLDİR. >> Bütün 'container' sınıf şablonlarının 'Default Ctor.' fonksiyonları, içinde öğe tutmayan bir kap nesnesi hayata getirmektedir. * Örnek 1, //.. int main() { std::vector iVecOne; // Öğesi olmayan bir kap nesnesi hayata geldi. std::vector iVecTwo{}; // Öğesi olmayan bir kap nesnesi hayata geldi. std::vector iVecThree(); // 'Most Vexing Parse' gereği bu bir fonksiyon BİLDİRİMİDİR. std::cout << "[" << iVecOne.size() << "]\n"; // OUTPUT => [0] std::cout << "[" << iVecTwo.size() << "]\n"; // OUTPUT => [0] // std::cout << "[" << iVecThree.size() << "]\n"; // error: request for member ‘size’ in ‘iVecThree’, which is of non-class type ‘std::vector()’ } >> Bütün 'container' sınıf şablonlarının '.size()' fonksiyonun geri dönüş değeri, ilgili kabın 'nested-type' ı olan 'size_type' sınıf türündendir. Fakat bu 'size_t' isminin eş ismi. Kaptaki öğe sayısını döndürmektedir. Sadece 'forward_list' sınıfının bu fonksiyonu yoktur. >> Bütün 'container' sınıf şablonlarının '.empty()' fonksiyonu, "Kap boş mu?" sorusuna cevap vermektedir. 'constant-time' garantisi vermektedir. >> Bütün 'container' sınıf şablonlarının 'Copy Ctor.' ve 'Copy Assigning' fonksiyonları mevcuttur. Aynı türden öğeler ve aynı eleman sayısına sahip olmalası gerekiyor. * Örnek 1, //.. int main() { std::vector x{ 1, 3, 5, 23, 78 }; std::vector xx{x}; std::vector xxx(xx); std::vector xxxx = xxx; std::vector y(x); // SENTAKS HATASI. std::list z{ 1, 3, 5, 23, 78 }; std::vector xz{ z }; // SENTAKS HATASI. } >> Bütün 'container' sınıf şablonlarının '.push_back()' fonksiyonu, kaba sondan ekleme yapan bir fonksiyondur. İki adet 'overload' versiyonu vardır. Birisi 'L-value expression' geçtiğimiz zaman çağrılacak ki bu durumda bizim ifademiz kopyalanacaktır, diğeri ise 'R-Value expression' geçtiğimiz zaman çağrılacaktır. Bu durumda da bizim ifademiz ÇALINACAKTIR. * Örnek 1, //.. int main() { std::vector nameList; for(int i = 0; i < 5; ++i) nameList.push_back(rname()); // İlgili 'rname()' fonksiyonu sağ taraf ifadesi döndürdüğünden, // 'Move Semantics' mekanizması devreye girecektir. std::string name{ "ali" }; for(int i = 0; i < 5; ++i) nameList.push_back(name); // İlgili 'name' isimli değişken sol taraf ifadesi olduğundan, // 'Copy Semantics' mekanizması devreye girecektir. } >> Bütün 'container' sınıf şablonlarının '.insert()' fonksiyonu belirli bir konuma ekleme yapmak için kullanılır. İlk argüman hedef konum bilgisidir. Geri döndürdüğü değer, ekleme yapılmış konum bilgisidir. * Örnek 1, //.. int main() { /* # OUTPUT # ali | nur | eda | ata | ----------------------------------------------------------------------------- XXX | ali | nur | eda | ata | ----------------------------------------------------------------------------- XXX | YYY | ali | nur | eda | ata | ----------------------------------------------------------------------------- XXX | YYY | ali | nur | eda | ata | ZZZ | ----------------------------------------------------------------------------- XXX | YYY | ali | nur | eda | ata | QQQ | ZZZ | ----------------------------------------------------------------------------- */ std::vector nameList{ "ali", "nur", "eda", "ata" }; pc(nameList, " | "); nameList.insert(nameList.begin(), "XXX"); pc(nameList, " | "); nameList.insert(nameList.begin() + 1, "YYY"); pc(nameList, " | "); nameList.insert(nameList.end(), "ZZZ"); pc(nameList, " | "); nameList.insert(nameList.end() - 1, "QQQ"); pc(nameList, " | "); } * Örnek 2, //.. int main() { /* # OUTPUT # ali | nur | eda | ata | ----------------------------------------------------------------------------- AAA | BBB | CCC | DDD | ----------------------------------------------------------------------------- [2] => ali | nur | AAA | BBB | CCC | DDD | eda | ata | ----------------------------------------------------------------------------- [0] => EEE | FFF | GGG | HHH | ali | nur | AAA | BBB | CCC | DDD | eda | ata | ----------------------------------------------------------------------------- [12] => EEE | FFF | GGG | HHH | ali | nur | AAA | BBB | CCC | DDD | eda | ata | IIII | IIII | IIII | IIII | ----------------------------------------------------------------------------- */ std::vector nameList { "ali", "nur", "eda", "ata" }; pc(nameList, " | "); std::list nameListTwo{ "AAA", "BBB", "CCC", "DDD" }; pc(nameListTwo, " | "); std::cout << "[" << std::distance(nameList.begin(), nameList.insert(nameList.begin() + 2, nameListTwo.begin(), nameListTwo.end())) << "] => "; pc(nameList, " | "); std::cout << "[" << std::distance(nameList.begin(), nameList.insert(nameList.begin(), { "EEE" , "FFF", "GGG", "HHH" })) << "] => "; pc(nameList, " | "); std::cout << "[" << std::distance(nameList.begin(), nameList.insert(nameList.end(), 4, "IIII")) << "] => "; pc(nameList, " | "); } >> '.emplace()' fonksiyonları: Eklemenin yapılacağı bellek alanını 'this' göstericisi olarak kullanıp, yerinde bir nesne hayata getirmektedir. Yani ne kopyalama semantiği ile ne de taşıma semantiği ile bir nesne hayata getirilmemektedir. Direkt olarak nokta atışı, o noktada nesne hayata getiriliyor. * Örnek 1, //.. int main() { /* # OUTPUT # ali | nur | eda | ata | ----------------------------------------------------------------------------- ali | nur | eda | ata | muhittin | ----------------------------------------------------------------------------- ali | nur | eda | ata | muhittin | muhittin | ----------------------------------------------------------------------------- ali | nur | eda | ata | muhittin | muhittin | AAAAAAAAAAAAAAAAAAAA | ----------------------------------------------------------------------------- */ std::vector nameList{ "ali", "nur", "eda", "ata" }; pc(nameList, " | "); std::string name{"muhittin"}; nameList.push_back(name); // Copy Ctor. pc(nameList, " | "); nameList.push_back(std::move(name)); // Move Ctor. pc(nameList, " | "); nameList.emplace_back(20, 'A'); // Doğrudan hedef adreste nesne oluşturulacak. Dolayısıyla 'std::string' sınıfının 'Ctor.' fonksiyonlarından // birisine çağrı yapmamız gerekiyor. 'forwarding reference' mekanizması ile 'emplace_back' fonksiyonuna // geçilen argümanlar direkt olarak 'Ctor.' çağrısında kullanılacaktır. // Bu satırda ne 'Move Ctor.' ne de 'Copy Ctor.' çağrıldı. pc(nameList, " | "); } * Örnek 2, //.. int main() { /* # OUTPUT # 01 Ocak 1900 Pazartesi | 01 Ocak 1900 Pazartesi | 01 Ocak 1900 Pazartesi | ----------------------------------------------------------------------------- 01 Ocak 1900 Pazartesi | 01 Ocak 1900 Pazartesi | 01 Ocak 1900 Pazartesi | 11 Nisan 2022 Pazartesi | ----------------------------------------------------------------------------- 17 Eylul 1993 Cuma | 01 Ocak 1900 Pazartesi | 01 Ocak 1900 Pazartesi | 01 Ocak 1900 Pazartesi | 11 Nisan 2022 Pazartesi | ----------------------------------------------------------------------------- */ std::vector dVec(3); pc(dVec, " | "); dVec.emplace_back(11, 4, 2022); pc(dVec, " | "); dVec.emplace(dVec.begin(), 17, 9, 1993); pc(dVec, " | "); } >> Silme işlemleri : Bir tanesi bir konum bilgisi alır ve o konumdakini siler. Diğeri ise bir 'range' alır ve o 'range' içerisindeki öğeleri siler. Geri dönüş değeri silme işleminden sonra kalan ilk öğenin konum bilgisidir. * Örnek 1, Direkt olarak konum bilgisi verirsek: //.. int main() { /* # OUTPUT # amasya | mugla | mersin | trabzon | tokat | eskisehir | nevsehir | nigde | artvin | burdur | ----------------------------------------------------------------------------- [9] => amasya | mugla | mersin | trabzon | tokat | eskisehir | nevsehir | nigde | artvin | ----------------------------------------------------------------------------- [0] => mugla | mersin | trabzon | tokat | eskisehir | nevsehir | nigde | artvin | ----------------------------------------------------------------------------- mugla | mersin | trabzon | tokat | eskisehir | nevsehir | nigde | ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 10, rtown); pc(sVec, " | "); std::cout << "[" << std::distance(sVec.begin(), sVec.erase(sVec.end() - 1)) << "] => "; // Son konumdaki gitti. pc(sVec, " | "); std::cout << "[" << std::distance(sVec.begin(), sVec.erase(sVec.begin())) << "] => "; // İlk konumdaki gitti. pc(sVec, " | "); sVec.pop_back(); // Son konumdaki gitti. pc(sVec, " | "); } * Örnek 2, Bir aralık verilmesi durumunda //.. int main() { /* # OUTPUT # bitlis | mersin | tunceli | bartin | antalya | sinop | karaman | tekirdag | kirsehir | van | ----------------------------------------------------------------------------- [1] => bitlis | van | ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 10, rtown); pc(sVec, " | "); std::cout << "[" << std::distance(sVec.begin(), sVec.erase(sVec.begin() + 1, sVec.end() - 1)) << "] => "; // İlk konumdaki ve son konumdakiler hariç, aradaki hepsi gitti. pc(sVec, " | "); } >> İki farklı sınıf şablonundan nesneyi, 'iterator' parametreli 'Ctor.' fonksiyonlarına çağrı yaparak, birlikte kullanabiliriz. * Örnek 1, //.. int main() { std::list myList{ 1, 3, 5, 23, 78 }; // std::vector myVec{ myList }; // SENTAKS HATASI. std::vector myVec{ myList.begin(), myList.end() }; // İş bu vektör nesnesini, 'myList' sınıfının öğeleri ile hayata getirdik. // İteratör konumundaki nesneler birbirine atanabilir olduğu sürece, // ilgili kaplarda tutulan öğelerin türleri de önemsizdir. // İş bu vektör nesnesini, 'myList' sınıfının öğeleri ile hayata getirdik. std::vector myVecTwo{ myList.begin(), myList.end() }; // std::vector myVecTwo{ myList }; // SENTAKS HATASI. // C-array de kullanabiliriz. const char* nameList[] = { "enes", "gokhan", "canibek", "necati" }; std::vector myVecThree{ std::begin(nameList), std::end(nameList) }; } >> 'Sequence Container' grubundaki sınıf şablonlarınım 'size_t' parametreli 'Ctor.' fonksiyonları, iş bu kap nesnesini, bu 'm' kadar 'default init.' edilmiş öğe ile hayata başlatmaktadır. * Örnek 1, //.. int main() { std::vector iVec(10); // İlgili 'iVec' nesnesi 'default init.' edilmiş 10 öğe tutmaktadır. } * Örnek 2, Kapta tutulan öğeler bir sınıf türünden olması durumunda, bu öğelerin 'Default Constructable' olmaları gerekiyor. //.. class Data { public: Data(int); // 'Parametreli Ctor.' olduğundan 'Default Ctor.' YOKTUR. }; int main() { std::vector dVec(20); // Sentaks hatası. } >> 'Sequence Container' grubundaki ortak fonksiyonlar '.back()' ve '.front' isimli fonksiyonlar, kaplardaki son ve ilk öğeye erişim sağlamaktadır. Yine bu fonksiyonların 'const' 'overload' edilmiş versiyonları da vardır. İlgili kapların boş olması durumunda bu iki fonksiyonun çağrılması bir HATA GÖNDERİLMESİNE neden olmaz. >> 'Sequence Container' grubundaki sınıf şablonlarının '.resize()' isimli bir üye fonksiyonu vardır. '.size()' büyüklüğünü değiştirmektedir. Dolayısıyla sondan ekleme yoluyla yeni öğeler eklendi. * Örnek 1, //.. int main() { /* # OUTPUT # size = 6, capacity = 6 => 10 20 30 40 56 90 ----------------------------------------------------------------------------- size = 10, capacity = 12 => 10 20 30 40 56 90 0 0 0 0 ----------------------------------------------------------------------------- */ std::vector iVec{ 10, 20, 30, 40, 56, 90 }; std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); iVec.resize(10); // Yeni eklenen öğeler '0' değerinde. std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); } * Örnek 2, ilgili fonksiyonun bir diğer 'overload' versiyonu ise ikinci bir argüman almaktadır. Yeni eklenecek öğeleri bu argüman ile hayata getirmektedir. //.. int main() { /* # OUTPUT # size = 6, capacity = 6 => 10 20 30 40 56 90 ----------------------------------------------------------------------------- size = 15, capacity = 15 => 10 20 30 40 56 90 31 31 31 31 31 31 31 31 31 ----------------------------------------------------------------------------- */ std::vector iVec{ 10, 20, 30, 40, 56, 90 }; std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); iVec.resize(15, 31); // Yeni eklenen öğeler '31' değerinde. std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); } * Örnek 3, ilgili fonksiyona '.size()' boyutundan küçük bir değer argüman olarak geçilirse kabın sonundan başlayarak bir GERÇEK SİLME işlemi gerçekleştirecektir. //.. int main() { /* # OUTPUT # size = 6, capacity = 6 => 10 20 30 40 56 90 ----------------------------------------------------------------------------- size = 15, capacity = 15 => 10 20 30 40 56 90 31 31 31 31 31 31 31 31 31 ----------------------------------------------------------------------------- size = 6, capacity = 15 => 10 20 30 40 56 90 ----------------------------------------------------------------------------- */ std::vector iVec{ 10, 20, 30, 40, 56, 90 }; std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); iVec.resize(15, 31); // Yeni eklenen öğeler '31' değerinde. std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); iVec.resize(6); std::cout << "size = " << iVec.size() << ", capacity = " << iVec.capacity() << " => "; pc(iVec); } > Mülakat Sorusu: * Örnek 1, //.. int main() { std::vector sVec; fc(sVec, 50, rname); pc(sVec); // Bir iteratör döngüsü ile şu işlemi yapmaya çalışalım: // Eğer 'range' içerisindeki ismin uzunluğu 4 ise bir tane daha 'insert' edilecek. // Eğer 'range' içerisindeki ismin uzunluğu 5 ise silinecek. // Diğer uzunluktaki isimlere dokunulmayacak. // INPUT => mert ayhan nur kaya sinem abidin can tunc melek selim kayhan // OUTPUT => mert mert nur kaya kaya abidin can tunc tunc kayhan } /*============================================================================================================*/ (28_20_12_2020) > 'STL' içerisindeki kaplar (devam): >> 'std::vector' sınıf şablonunun incelenmesi (devam) : >>> '.max_size()' fonksiyonu : Tutabileceği maksimum öğe sayısıdır. 'static' üye fonksiyon DEĞİLDİR. Çünkü kullanılan 'std::allocator' sınıfından ötürü maksimum kapasite değişebilir. >>> Atama işlemini gerçekleştiren fonksiyonlar : >>>> '.operator=()' fonksiyonları hem 'Copy Semantics' hem de 'Move Semantics' için destek vermektedir. 'initializer-list' için de destek vermektedir. * Örnek 1, //.. int main() { std::vector a{ 1, 2, 3, 4 }; std::vector b; b = a; // b.operator(a); // Copy Ctor. b = std::move(a); // b.operator(std::move(a)); // Move Ctor. a = { 4, 3, 2, 1 }; // 'Init. List' } >>>> '.assign()' fonksiyonu üç adet 'overload' barındırmaktadır. Bunlar 'fill-assign', 'range-assign' ve 'initializer-list-assign'. Geri dönüş değeri yoktur. * Örnek 1, //.. int main() { std::vector a{ 1, 2, 3, 4 }; std::vector b; b.assign(10, 56); // İlgili kap 10 adet '56' değerinde öğeye sahiptir. b.assign( { 4, 3, 2, 1 } ); // İlgili kap '{}' arasındaki değerlere sahip öğelerden oluşmaktadır. std::deque c{ 10, 11, 12, 13 }; b.assign( c.begin(), c.end() ); // İlgili kap, 'c' kabının öğeleri ile hayata gelmiştir. } >>>> '.swap()' fonksiyonu kapların içerisinde bulunan 'göstericileri' birbirleri ile değiştirmektedir. Kaplardaki öğeler birbiri ile yer değiştirmemektedir. * Örnek 1, //.. int main() { std::vector a{ 1, 2, 3, 4 }; std::vector b{ 11, 22, 33, 44 }; a.swap(b); // swap(a, b); } >>> '.clear()' : Kaptaki bütün öğeleri silmektedir. >>> '.data()' : Arkadaki dinamik bellek bloğunun adresini döndürmektedir. C API leri ile birlikte kullanıldığında çok işe yaramaktadır. Geri dönüş değeri 'T*' veya 'const T*' türünden bir gösterici. * Örnek 1, //.. void print_array(const int* p, size_t size) { while(size--) printf("%d", *p++); printf("\n-----------------------\n"); } int main() { std::vector myVec{ 1, 3, 5, 7, 9 }; printf_array(myVec.data(), myVec.size()); // &myVec[0]; // data(myVec); // C++17 // &*myVec.begin(); // UNUTULMAMALIDIR Kİ GERİ DÖNÜŞ DEĞERİNİ BİR GÖSTERİCİDE SAKLARSAK, 'reallocation' SONRASINDA İLGİLİ GÖSTERİCİ // 'Dangling Pointer' HALİNE GELECEKTİR. } >>> '.get_allocator()' : İlgili kapta kullanılan 'allocator' sınıfını 'get' etmektedir. >> Çoğu kap bünyesinde karşılaştırma operatörleri barındırır ki bu operatörler 'lexicographical_compare' şeklinde karşılaştırma yapmaktadır. Kapların birbirine eşit olabilmeleri için hem öğe sayıları hem de karşılıklı bütün öğeleri birbirine eşit olacak. Eşitlik olmadığında kaplardaki öğeler karşılıklı olarak birbirleri ile karşılaştırılırlar. İki öğenin birbirinden farklı olduğu ilk noktada, hangi öğe büyükse onun bulunduğu kap büyüktür kabul edilir. Son olarak bütün öğeler eşitken bir kaptaki öğe sayısı bittiyse, öğe sayısı bitmeyen kap daha büyüktür. * Örnek 1, //.. int main() { // Eşit olma durumu std::vector a{ 1, 2, 3, 4 }; std::vector b{ 1, 2, 3, 4 }; std::cout << ( a == b ) << "\n"; // OUTPUT => 1 // Büyük olma durumu std::vector c(1'000'000); // Bir milyon elemanı var ama bütün elemanları '0'. std::vector d{4}; // Bir elemanı var ama '4'. std::cout << ( d > c ) << "\n"; // OUTPUT => 1 // Büyük olma durumu v2 std::vector e{ 1, 2, 3, 4, 5}; std::vector f{ 1, 2, 3, 4 }; std::cout << ( e > f ) << "\n"; // OUTPUT => 1 } > 'iterator invalidation' : Aşağıdaki açıklamaları inceleyelim. >> '.insert()', '.emplace_back()', '.emplace()' ve '.push_back()' fonksiyonları 'reallocation' a sebebiyet verebilir. Bu durumda bütün 'references', 'pointers' ve 'iterators' geçersiz hale gelirler. Çünkü yeni bir bellek alanı elde edildiğinden dolayı önceki bellek alanı geri verilmektedir. Eğer iş bu fonksiyonlar 'reallocation' a sebebiyet vermez ise ekleme yapılan noktadan önceki öğeleri gösteren 'references', 'pointers' ve 'iterators' GEÇERLİ OLMAYA DEVAM EDERLER. İş bu noktadan sonraki öğeleri gösterenler ise GEÇERSİZ OLACAKLARDIR. >> '.reserve()' fonksiyonu velev ki 'reallocation' a neden olursa yine bütün 'references', 'pointers' ve 'iterators' geçersiz hale gelecektir. Fakat 'reserve' işleminden sonra yapılan eklemeler, tekrardan 'reallocation' tetiklenmediği sürece, ilgili referansları, iteratörleri ve göstericileri GEÇERSİZ HALE GETİRMEYECEKTİR. Çünkü bunlar zaten '.reserve()' fonksiyonundan önce konum gösteriyorlar. 'reallocation' da gerçekleşmedi. Dolayısıyla adres bilgileri değişmedi. >> '.erase()' ve '.pop_back()' fonksiyonları silme işleminin yapıldığı konumu ve sonrasındaki konumu gösteren 'references', 'pointers' ve 'iterators' GEÇERSİZ HALE GETİRECEKTİR. > Önceki derste verilen ödev sorusunun cevabı: //.. int main() { std::vector sVec; fc(sVec, 50, rname); pc(sVec); // 'Iterator Invalidation' a sebebiyet veren senaryo. for(auto iter = sVec.begin(); iter != sVec.end(); ++iter) { if(iter->length() == 4) sVec.insert(iter, *iter); // Eğer bu noktada ekleme yapılırsa, 'reallocation' gerçekleşebilir. Dolayısıyla bizim 'iter' isimli değişkenimizi // güncellememiz gerekiyor. else if(iter->length() == 5) sVec.erase(iter) // Benzer sebepten dolayı: } pc(sVec); // Artık 'Tanımsız Davranış' meydana gelmiştir. // 'Iterator Invalidation' a sebebiyet VERMEYEN senaryo. auto iter = sVec.begin(); while(iter != sVec.end()) { if(iter->size() == 4) { iter = sVec.insert(iter, *iter); iter += 2; } else if(iter->size() == 5) { iter = sVec.erase(iter); } else { ++iter; } } pc(sVec); // Artık doğru çalışmaktadır. // Yukarıdaki 'while' döngüsünü açıklamak gerekirse: ilgili vektörümüzde aşağıdaki isimler vardır, // mert tunc abidin ayhan murat necati // 'while' öncesi iter => ^ // 'if' bloğu 'true' ise => mert mert tunc abidin ayhan murat necati // 'insert' geri dönüşü => ^ | // 'iter +=2' sonrasında => ^ // ... // 'else-if' 'true' ise => mert mert tunc abidin murat necati // 'erase' geri dönüşü => ^ // ... // 'else' 'true' ise => ^ } > Mülakat Sorusu : Belirli değere sahip son öğeyi silelim => 'find' algoritmasını 'reverse_iterator' ile çalıştırmalıyız. #include #include int main() { /* # OUTPUT # 2_5_6_7_2_8_9_2_1_ riter : 2 riter.base() : 1 2_5_6_7_2_8_9_1_ */ std::vector iVec{ 2, 5, 6, 7, 2, 8, 9, 2, 1 }; for(auto index : iVec) std::cout << index << "_"; if(auto riter = std::find(iVec.rbegin(), iVec.rend(), 2); riter != iVec.rend()) { std::cout << "\nriter : " << *riter << "\n"; std::cout << "riter.base() : " << *(riter.base()) << "\n"; iVec.erase(std::prev(riter.base())); // Reverse Iterator kullandığımız için, '.erase()' fonksiyonu da bu parametreli 'overload' olmadığından. } for(auto index : iVec) std::cout << index << "_"; } > 'swap_ranger()' fonksiyonu : İki 'range' içerisindeki öğeleri 'swap' eder. //.. int main() { std::vector sVec; fc(sVec, 10, rname); std::vector sList; fc(sList, 10, rtown); std::swap_ranges(sVec.begin(), sVec.end(), sList.begin()); // Hedef 'range' içerisinde yeterli öğe bulunmasından biz sorumluyuz. Büyüklüğü ise ilk 'range' içerisindeki öğe sayısı // belirlemektedir. Geri dönüş değeri ise hedef 'range' içerisine yazılan son öğeden sonraki konum. BURADA 'container' // ALMADIĞI İÇİN ÖĞELER FİİLEN TAKAS EDİLMEKTE. DOLAYISIYLA MALİYETLİ BİR İŞLEMDİR. } > Standart Fonksiyon Nesneleri : Operatörler aşağıdaki gibi fonksiyon nesneleri haline getirilmiştir. 'functional' başlık dosyasında tanımlanmışlardır. Bu tip sınıf nesnelerinin yerine de 'lambda-expression' kullanabiliriz. * Örnek 1, //.. template struct Less{ constexpr bool operator()(const T& t1, const T& t2)const { return t1 < t2; } }; template struct Greater{ constexpr bool operator()(const T& t1, const T& t2)const { return t1 > t2; } }; template struct Multiplies{ constexpr T operator()(const T& t1, const T& t2)const { return t1 * t2; } }; int main() { /* # OUTPUT # 0 1 2 3 4 6 7 8 9 10 ----------------------------------------------------------------------------- */ std::vector iVec; fc(iVec, 10, Irand{ 0, 10 }); std::sort(iVec.begin(), iVec.end(), Less{}); // std::sort(iVec.begin(), iVec.end(), std::less{}); print(iVec); /* # OUTPUT # 10 9 8 7 6 4 3 2 1 0 ----------------------------------------------------------------------------- */ std::sort(iVec.begin(), iVec.end(), Greater{}); // std::sort(iVec.begin(), iVec.end(), std::greater{}); print(iVec); /* # OUTPUT # 10 11 12 14 15 16 17 18 19 20 ----------------------------------------------------------------------------- */ std::vector iVecTwo; fc(iVecTwo, 10, Irand{ 10, 20 }); print(iVecTwo); /* # OUTPUT # 100 99 96 98 90 64 51 36 19 0 ----------------------------------------------------------------------------- */ std::vector iVecThree(iVecTwo.size()); std::transform(iVec.begin(), iVec.end(), iVecTwo.begin(), iVecThree.begin(), Multiplies{}); // std::transform(iVec.begin(), iVec.end(), iVecTwo.begin(), iVecThree.begin(), std::multiplies{}); print(iVecThree); } > 'STL' bünyesindeki sıralamaya ilişkin algoritmalar: 'sort()', 'partial_sort()', 'stable_sort()', 'nth_element()', 'partition()', 'stable_partition()' vs. şeklinde örneklendirebiliriz. >> 'sort()' : 'random_access_iterator' istemektedir. Varsayılan fonksiyon objesi ise 'std::less'. Dolayısla küçükten büyüğe doğru sıralama yapacaktır. Sınıf türünden nesneleri sıralayacaksak, '.operator<()' fonksiyonu 'overload' edilmiş olması gerekiyor. Velevki büyükten küçüğe doğru sıralamak isteseydik ya 'reverse_iterator' kullanacaktık ya da 'std::greater' fonksiyon objesini. Ek olarak kendi kriterimize göre de sıralama yapabiliriz. * Örnek 1, //.. int main() { std::vector sVec; fc(sVec, 100'000, []{ return rname() + ' ' + rfname(); }); std::sort(sVec.begin(), sVec.end()); std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyasi olusturulamadi.\n"; exit(EXIT_FAILURE); } pc(sVec, "\n", ofs); } * Örnek 2, //.. int main() { std::vector sVec; fc(sVec, 100'000, []{ return rname() + ' ' + rfname(); }); std::sort( sVec.begin(), sVec.end(), [](const std::string& s1, const std::string& s2){ return s1.length() < s2.length() || ( s1.length() == s2.length() && s1 < s2 ) } ); std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyasi olusturulamadi.\n"; exit(EXIT_FAILURE); } pc(sVec, "\n", ofs); } >> 'stable_sort()' : Aynı anahtara sahip değerlerin izafi konumları sıralamadan sonra da korunma garantisini veren sıralamadır. '.sort()' bu GARANTİYİ VERMEMEKTEDİR. "https://en.cppreference.com/w/cpp/algorithm/stable_sort" Yani sıralama öncesinde aynı 'key' değerine sahip 'value' lerin kendi içindeki sıralaması, sıralamadan sonra da aynı kalmaktadır. * Örnek 1, //.. int main() { std::vector> myVec; fc(myVec, 100, []{ return std::make_pair( Irand{ 0, 100 }(), rname() ); }); /* 0 hande 3 cetin 3 gulden 4 burhan 4 pinat 8 nalan 9 engin 11 celal 13 yurdakul 14 hakki 14 kayhan 14 kelami 18 refik 19 candan 21 busra 21 kaan 21 sidre 22 nurdan 23 nahit 24 busra 24 ismail 25 polathan 26 aydan 27 gulsah 28 mukerrem 30 metin 34 aslican 35 nasrullah 36 nisan 36 seyhan 37 arda 37 tarkan 38 keriman 40 mukerrem 41 bilgin 42 bennur 43 caner 43 temel 43 yusuf 44 cumhur 44 devlet 45 hasan 46 yavuz 48 melisa 48 papatya 51 halime 52 kurthan 56 beste 57 gulsen 58 gurbuz 58 sezen 58 tarcan 63 cezmi 63 ciler 64 haluk 65 aykut 65 cemal 67 gurbuz 69 burhan 69 derya 70 lamia 71 atil 71 esra 71 tevfik 73 ceyhan 75 naci 75 samet 76 cahit 77 belgin 78 muruvvet 78 selin 79 tayyip 80 diana 80 hilal 80 okan 81 cengiz 81 kaan 82 ercument 82 kaan 82 tansu 83 edip 84 recep 85 cahit 85 hasan 85 zekai 87 beste 87 zekai 88 atil 90 pakize 91 ercument 92 atif 95 askin 95 beste 96 garo 96 nazif 98 necmettin 98 sadiye 100 burhan 100 sidre 100 tufan */ std::sort(myVec.begin(), myVec.end()); // İlgili kaptaki öğeler küçükten büyüğe doğru sıralandılar. Buradaki sıralama kriteri ise 'first' değeri küçük olan küçük, // 'first' değeri eşit ise 'second' değeri küçük olan küçüktür. auto myLambda = [](const auto& p1, const auto& p2){ return p1.second < p2.second; }; /* 58 abdi 66 abdulmuttalip 98 afacan 67 alev 55 aslican 67 aslihan 19 atil 90 aydan 59 aydan 86 ayse 99 aytac 50 aziz 75 azmi 64 baran 32 beril 53 beste 3 binnur 52 birhan 81 can 44 candan 13 candan 40 cansu 34 celal 55 cemile 28 cengiz 38 cihat 83 ciler 65 cumhur 54 cuneyt 28 dilber 55 dogan 60 ece 26 egemen 89 enes 16 engin 18 ercument 97 esen 78 esen 95 esin 59 esra 67 fadime 20 fadime 36 fahri 30 feramuz 89 galip 23 garo 55 gazi 47 gursel 97 hakan 71 hakan 85 hakki 82 hande 44 hilal 32 hilal 89 hulusi 2 ismail 82 kamil 25 kelami 33 korhan 71 mehmet 38 melih 35 melike 40 metin 68 muzaffer 55 nazife 24 nihal 24 nurdan 28 nurullah 78 nusret 84 perihan 84 petek 16 polat 37 polathan 7 sade 37 sadi 38 sadiye 62 sadullah 97 samet 11 sami 98 sefa 56 sevilay 99 sezer 20 sidre 46 soner 18 suleyman 98 taci 69 tarkan 86 tarkan 61 tarkan 59 teoman 58 teoman 55 tugra 98 tugra 5 ufuk 95 umit 85 utku 65 yasar 63 yavuz 32 zahide 33 zubeyde */ std::sort(myVec.begin(), myVec.end(), myLambda); std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyasi olusturulamadi.\n"; exit(EXIT_FAILURE); } ofs << std::left; // Sola yaslandı yazılar. for(const auto& [ID, name] : myVec) // Structural Binding kullanıldı { ofs << std::setw(3) << ID << " " << name << "\n"; // Vektördeki öğeler bir dosyaya yazıldı. } } * Örnek 2, //.. int main() { std::vector> myVec; fc(myVec, 100, []{ return std::make_pair( Irand{ 0, 100 }(), rname() ); }); /* 2 nalan 2 soner 6 sadettin 7 pakize 8 birhan 10 adnan 10 ece 11 dilek 12 utku 13 gulden 13 irmak 16 sevda 16 tuncer 17 nuri 19 durriye 19 taner 22 cengiz 23 nusret 24 rumeysa 25 hasan 27 adem 27 cahide 28 caner 28 celik 28 soner 29 azmi 29 ferhunde 31 aylin 32 deniz 32 helin 32 pinat 34 yurdagul 36 aycan 36 azmi 37 ayla 37 sefer 38 malik 39 hikmet 39 selenay 40 halime 40 julide 42 tansu 45 galip 45 tevfik 48 haldun 49 zahit 50 tarik 51 soner 53 kaan 53 nevsin 53 sami 54 birhan 54 garo 54 nazli 54 sami 56 olcay 57 selenay 58 devlet 60 canan 61 efecan 62 naz 63 sidre 65 korhan 66 sevilay 67 zeliha 68 cetin 69 bekir 69 kamile 69 nagehan 70 hakki 71 gursel 72 kerem 75 handesu 76 durmus 76 murat 77 huseyin 78 izzet 78 malik 78 suheyla 79 alican 79 emrecan 79 yurdakul 80 abdullah 80 sevilay 83 melike 84 lamia 85 halime 88 cemal 88 korhan 88 pinat 91 poyraz 93 yusuf 94 busra 94 tekin 94 umit 95 korhan 96 ceylan 99 fugen 99 nefes 100 naciye */ std::sort(myVec.begin(), myVec.end()); // İlgili kaptaki öğeler küçükten büyüğe doğru sıralandılar. Buradaki sıralama kriteri ise 'first' değeri küçük olan küçük, // 'first' değeri eşit ise 'second' değeri küçük olan küçüktür. auto myLambda = [](const auto& p1, const auto& p2){ return p1.second < p2.second; }; /* 63 abdullah 53 adnan 43 atalay 19 atil 23 atil 6 aykut 75 ayla 31 aylin 35 aziz 0 baran 45 bekir 44 belgin 78 beril 78 berk 23 bilal 86 bilgin 82 bulent 2 canan 51 canan 13 candan 24 cebrail 44 celik 29 ciler 43 cumhur 63 deniz 20 derin 23 derya 52 diana 31 emine 32 emine 94 emine 64 fadime 47 ferhat 30 fuat 99 fuat 11 gulsen 94 gulsen 37 gursel 93 haluk 39 handan 30 helin 11 hikmet 14 hilmi 72 hulki 1 hulya 87 hulya 1 iffet 35 irmak 31 izzet 32 izzet 34 izzet 71 izzet 49 kasim 89 kayahan 17 kerem 68 menekse 74 metin 40 murat 39 mustafa 33 muzaffer 66 muzaffer 41 naci 47 nazife 79 necmettin 69 nefes 0 nihal 87 niyazi 74 nusret 61 okan 23 onat 7 pakize 11 papatya 69 pelinsu 97 pelinsu 60 polathan 79 recep 83 recep 22 sefer 50 sefer 80 selin 27 su 83 su 99 suheyla 96 tarkan 80 tayfun 34 teoman 57 teoman 56 tijen 57 tijen 53 tuncer 78 tuncer 89 tuncer 91 turhan 35 yalcin 23 yasin 33 yelda 82 yesim 9 zahide 22 zarife 61 ziya */ std::stable_sort(myVec.begin(), myVec.end(), myLambda); std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyasi olusturulamadi.\n"; exit(EXIT_FAILURE); } ofs << std::left; // Sola yaslandı yazılar. for(const auto& [ID, name] : myVec) // Structural Binding kullanıldı { ofs << std::setw(3) << ID << " " << name << "\n"; // Vektördeki öğeler bir dosyaya yazıldı. } } * Örnek 3, //.. #include #include #include #include struct Employee { int age; std::string name; // Does not participate in comparisons }; bool operator<(const Employee & lhs, const Employee & rhs) { return lhs.age < rhs.age; } int main() { /* # OUTPUT # 32, Arthur 108, Zaphod 108, Ford */ std::vector v = { {108, "Zaphod"}, {32, "Arthur"}, {108, "Ford"}, }; std::stable_sort(v.begin(), v.end()); for (const Employee & e : v) std::cout << e.age << ", " << e.name << '\n'; } >> 'partial_sort()' : İlk 'n' tane öğeyi sıralı hale getiriyor. Örneğin, 50'000 tane öğrenci içerisinden en iyi 10 tanesini seçeceksek bu algoritma karlı olacaktır. En iyi on adet öğrenci bulmak için 50'000 tane öğeyi sıralamaya gerek yoktur. Birinci ve üçüncü parametre üzerinde gezilecek 'range' için başlangıç ve bitiş konumlarıyken, ikinci parametre ise sıralanmış 'range' in 'n' adedi. * Örnek 1, //.. int main() { /* # OUTPUT # ahmet sivri ali dokmeci gunay daglarca haluk cilingir handan engerek tayyar adiguzel taner yersiz sezen semiz soner serce sadi haselici mukerrem elkizi seyhan kosnuk poyraz yolyapan nuriye edepli necati sessiz yesim tamirci korhan cangoz julide korukcu melisa kelleci iffet canlikaya ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 20, []{ return rname() + ' ' + rfname(); }); int n = 5; // İlk beş sıralı olacaktır. std::partial_sort(sVec.begin(), sVec.begin() + n, sVec.end()); std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyasi olusturulamadi.\n"; exit(EXIT_FAILURE); } print(sVec, "\n", ofs); } >> 'nth_element()' : Kaptaki bütün öğeler sıralanmış olması durumunda o öğe hangi konumda olacaksa, sıralama yapılmaksızın ilgili öğe yine aynı konumda olacaktır. * Örnek 1, //.. int main() { /* # OUTPUT # akin sarikafa asim uslu aycan yagizeli aynur degirmenci beyhan boztas beyhan edepli bora reis bora unkapani cemile malkaciran ceyhun gedik devrim unalmis ercument koralp kezban agaoglu necmi karakuzu nusret resimci selin sener sinem karasaban tonguc uluocak yurdagul gilgamis zerrin temizel ----------------------------------------------------------------------------- */ std::vector sVec; fc(sVec, 20, []{ return rname() + ' ' + rfname(); }); int n = 5; // Beşinci konumdaki öğe. std::nth_element(sVec.begin(), sVec.begin() + n, sVec.end()); std::cout << n << "th element : " << *(sVec.begin() + n) << "\n"; // OUTPUT => 5th element : beyhan edepli std::sort(sVec.begin(), sVec.end()); std::ofstream ofs{ "out.txt" }; if(!ofs) { std::cerr << "out.txt dosyasi olusturulamadi.\n"; exit(EXIT_FAILURE); } print(sVec, "\n", ofs); } * Örnek 2, Medyan Hesaplaması (Mülakat Sorusu) : Sıralı olması durumunda ortadaki değer. //.. template auto get_median(Iter beg, Iter end, UnPred f) { auto midPoint = std::distance(beg, end) / 2; std::nth_element(beg, midPoint, end, f); return *std::next(beg, midPoint); } int main() { //... } >> 'partition()' : En önemli algoritmalardan birisidir. Bir kritere göre partisyon yapmakta. Kriteri sağlayanlar başta, sağlamayanlar sonda. 'Partition Point' ise koşulu sağlamayanlardan ilkinin konumu demektir. * Örnek 1, //.. int main() { /* # OUTPUT # erdem ugursuz | kerim komurcu | sevilay takes | kamile elebasi | selin yurekli | cetin azmak | berk kaplan | seyhan kotek | suphi karaorman | can koralp | ----------------------------------------------------------------------------- Object until the Partition Point => 8, suphi karaorman From the begining until the point => erdem ugursuz < kerim komurcu < sevilay takes < kamile elebasi < selin yurekli < cetin azmak < berk kaplan < seyhan kotek < From the point until the end => suphi karaorman | can koralp | */ std::vector sVec; fc(sVec, 10, []{ return rname() + ' ' + rfname(); }); char c = 'e'; // Bu karaktere sahip olanları yazının başına alacağız. if( auto partitionPoint = std::partition( sVec.begin(), sVec.end(), [c](const std::string& s1){ return s1.find(c) != std::string::npos; } ); partitionPoint != sVec.end() ) { print(sVec, " | "); std::cout << "Object until the Partition Point => " << std::distance(sVec.begin(), partitionPoint) << ", " << *partitionPoint << "\n"; std::cout << "From the begining until the point => "; std::copy(sVec.begin(), partitionPoint, std::ostream_iterator{std::cout, " < "}); std::cout << "\n"; std::cout << "From the point until the end => "; std::copy(partitionPoint, sVec.end(), std::ostream_iterator{std::cout, " | "}); } } >> 'stable_partition()' : İzafi konumlarını koruyarak yukarıdaki sıralamayı yapmaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # tarik oztoklu | cahit zengin | kaan eloglu | yasar simsek | melek onaran | celal yavasakan | turgut koylu | zekai baturalp | agah hamsikoylu | bennur sonuzun | ----------------------------------------------------------------------------- cahit zengin | kaan eloglu | yasar simsek | melek onaran | celal yavasakan | zekai baturalp | bennur sonuzun | tarik oztoklu | turgut koylu | agah hamsikoylu | ----------------------------------------------------------------------------- Object until the Partition Point => 7, tarik oztoklu From the begining until the point => cahit zengin < kaan eloglu < yasar simsek < melek onaran < celal yavasakan < zekai baturalp < bennur sonuzun < From the point until the end => tarik oztoklu | turgut koylu | agah hamsikoylu | */ std::vector sVec; fc(sVec, 10, []{ return rname() + ' ' + rfname(); }); print(sVec, " | "); char c = 'e'; // Bu karaktere sahip olanları yazının başına alacağız. auto partitionPoint = std::stable_partition( sVec.begin(), sVec.end(), [c](const std::string& s1){ return s1.find(c) != std::string::npos; } ); print(sVec, " | "); std::cout << "Object until the Partition Point => " << std::distance(sVec.begin(), partitionPoint) << ", " << *partitionPoint << "\n"; std::cout << "From the begining until the point => "; std::copy(sVec.begin(), partitionPoint, std::ostream_iterator{std::cout, " < "}); std::cout << "\n"; std::cout << "From the point until the end => "; std::copy(partitionPoint, sVec.end(), std::ostream_iterator{std::cout, " | "}); } >> Sonu '_copy' ile biten sıralama algoritmaları, tıpkı sonu diğer sonu '_copy' ile bitenler gibi, sıralanmış halini başka bir 'range' e yazmaktadır. >> 'is_sorted()' : Bir 'range' sıralı mı değil mi kontrol eder. 'bool' veri tipinde geri dönüş değerine sahiptir. >> 'is_sorted_until()' : Kaçıncı indisli öğeye kadar sıralı olduğunu kontrol ediyor. Bir 'iterator' döndürmektedir, bu da sıralamayı bozan ilk öğenin konum bilgisidir. >> 'is_partitioned()' : Benzer şekilde bir 'range' öğelerinin 'partition' şeklinde sıralanıp sıralanmadığını kontrol etmektedir. >> 'heap' algoritmaları: 'make_heap', 'push_heap', 'pop_heap' ve 'sort_heap' fonksiyonlarından meydana gelir. Detaylarına daha sonra değinilecektir. > 'STL' içerisindeki kaplar (devam): >> 'std::deque' sınıf şablonunun incelenmesi : Dinamik dizilerin dizisi manasındadır. Amaç indeks ile sabit zamanda erişmek, hem baştan hem sondan eklemeyi 'constant-time' yapmak. Vektörün vektör açılımı şeklinde de örneklendirebiliriz. 'reallocation' problemi yoktur. Kap dolduğunda yeni bir dizi daha eklenecektir. Fakat bu yeni eklenen dizi ile önceki dizilerin peşpeşe olma garantisi yoktur. Vektör sınıfı ile benzer parametre yapısına ve 'interface' fonksiyonlarına sahiptirler. * Örnek 1, //.. int main() { /* # OUTPUT # 994 ----------------------------------------------------------------------------- 114 994 ----------------------------------------------------------------------------- 518 114 994 ----------------------------------------------------------------------------- 38 518 114 994 ----------------------------------------------------------------------------- 38 518 114 994 843 ----------------------------------------------------------------------------- 38 518 114 994 843 415 ----------------------------------------------------------------------------- 38 518 114 994 843 415 235 ----------------------------------------------------------------------------- 112 38 518 114 994 843 415 235 ----------------------------------------------------------------------------- 62 112 38 518 114 994 843 415 235 ----------------------------------------------------------------------------- 62 112 38 518 114 994 843 415 235 687 ----------------------------------------------------------------------------- */ std::deque myDeque; Irand rand{ 0, 1000 }; for(int i = 0; i < 10; ++i) { auto value = rand(); if(value % 2 == 0) myDeque.push_front(value); else myDeque.push_back(value); print(myDeque); } } >>> 'std::vector' sınıfında olmayan ama 'std::deque' sınıfında olan fonksiyonlar: '.emplace_front()', '.pop_front()', '.push_front()' fonksiyonlarından oluşmaktadır. >>> 'reallocation' meydana gelmemektedir. >>> 'iterator invalidation' kuralları, 'std::vector' sınıfındakinden tamamiyle farklıdır. İş bu kurallar; Baştan ve sondan değil ama aradaki konumlardan yapılan ekleme işlemleri, ilgili kaptaki öğeleri gösteren bütün iteratörleri, referansları ve göstericileri 'invalid' durumuna getirir. Eğer her iki uçtan birine yapılan ekleme yapılırsa, kaptaki öğeleri gösteren bütün iteratörleri 'invalid' durumuna durumuna gelirken referans ve göstericileri ETKİLEMEMEKTEDİR. Eğer silme işlemi yaparsak ve bu işlem sondan gerçeleşir ise sadece son konumu gösteren iteratör ile silinen öğeyi gösteren referans ve göstericiler 'invalid' durumuna geçerler. Eğer silme işlemini ilk öğe için yaparsak, ki bu ilk öğenin son öğe olmaması lazım, silinen öğeyi gösteren iteratörler, referanslar ve göstericiler 'invalid'olurlar. >>> 'std::vector' sınıfının 'bool' açılımı 'bool' türden değişkenler tutmamaktadır fakat 'std::deque' sınıfının 'bool' açılımı gerçekten de 'bool' tutmaktadır. >> Bağlı liste veri yapıları: 'std::list' ve 'std::forward_list' sınıf şablonlarından meydana gelmektedir. Konumu bilinen her yerden ekleme ve silme işlemleri 'constant-time'. 'swap' işleminin yoğun yapıldığı ve öğelerin de bir hayli büyük olduğu bir 'range' için liste sınıf şablonu kullanılabilir. Çünkü bu tip veri yapılarında verinin kendisi değil, düğümleri gösteren göstericiler 'swap' edilmektedir. Ayrıca düğümler hangi listeye ait olduklarını bilmezler. Yani bir düğümü halihazırda bulunduğu listeden çıkartıp bir başka listeye ekleyebiliyoruz. Örneğin, iki vektör arasında bir nesne transferi gerçekleştirmek isteyelim. İlk önce kaynak vektördeki ilgili nesnenin hayatı sonlandırılıyor. Sonrasında onun bulunduğu vektör tekrardan 'allocate' ediliyor, boşalan yeri doldurmak için. Devamında ise hedef vektörde bir yer açılıyor ve nesnemiz o konumda hayata geliyor. Fakat liste kullansaydık sadece göstericileri 'swap' yaparak öğeleri takas yapabilirdik. ÜStelik ilgili nesne için 'Ctor.' ve 'Dtor.' fonksiyonlarının çağrılmasına, ilgili liste için tekrardan yer açılmasına da gerek kalmayacaktı. Bu sınıflara ait iteratörler 'bi-directional iterator' grubundandır. >>> 'std::list' sınıfının incelenmesi : Asla 'std::unique()', 'std::reverse()', 'std::merge()' gibi algoritmaları asla kullanmamalıyız. Bunların yerine üye fonksiyon versiyonlarını kullanmalıyız. İş bu üye fonksiyonlar ise 'std::vector' sınıfında mevcut değillerdir. >>>> 'std::vector' ve 'std::deque' sınıf şablonlarında olmayan ama algoritma fonksiyonlarının üye fonksiyon halleri olan üye fonksiyonların incelenmesi: >>>>> '.sort()' : 'sort()' algoritma fonksiyonunun üye fonksiyon hali. * Örnek 1, //.. int main() { /* # OUTPUT # tijen enes dogan cengiz ferhat aslihan ciler bilgin melih necmiye nazif ----------------------------------------------------------------------------- aslihan bilgin cengiz ciler dogan enes ferhat melih nazif necmiye tijen ----------------------------------------------------------------------------- tijen necmiye nazif melih ferhat enes dogan ciler cengiz bilgin aslihan ----------------------------------------------------------------------------- enes ciler dogan melih nazif tijen bilgin cengiz ferhat aslihan necmiye ----------------------------------------------------------------------------- */ std::list lString; fc(lString, 11, rname); print(lString); lString.sort(); print(lString); lString.sort(std::greater{}); print(lString); lString.sort( [](const std::string& s1, const std::string& s2){ return s1.size() < s2.size() || ( s1.size() == s2.size() && s1 < s2 ); } ); print(lString); } >>>>> '.reverse()' : 'reverse()' algoritma fonksiyonunun üye fonksiyon hali. >>>>> '.remove()' : 'remove()' algoritma fonksiyonunun üye fonksiyon hali. Üye fonksiyon olduğundan GERÇEK SİLME YAPAR. >>>>> '.merge()' : Sıralı birleştirme işlemidir. Kaynak olarak kullanılan kap boşalacaktır. Aynı sıralama kriteri kullanılmalıdır. * Örnek 1, //.. int main() { /* # OUTPUT # agah cihan kamil melek soner yasin alican cumhur furkan yalcin kurthan ----------------------------------------------------------------------------- ediz askin hulki nedim sevim yeliz dilber muhsin niyazi yilmaz emirhan ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- agah ediz askin cihan hulki kamil melek nedim sevim soner yasin yeliz alican cumhur dilber furkan muhsin niyazi yalcin yilmaz emirhan kurthan ----------------------------------------------------------------------------- */ auto f = [](const std::string& s1, const std::string& s2){ return s1.size() < s2.size() || ( s1.size() == s2.size() && s1 < s2 ); }; std::list lString; fc(lString, 11, rname); lString.sort(f); print(lString); std::list lStringTwo; fc(lStringTwo, 11, rname); lStringTwo.sort(f); print(lStringTwo); lStringTwo.merge(lString, f); print(lString); print(lStringTwo); } >>>>> '.splice()' : İstediğimiz kadar öğeyi bir zincirden kopartıp başka zincire eklemektedir. Sıralı olması gibi bir koşul söz konusu değil. * Örnek 1, //.. int main() { /* # OUTPUT # nurdan aslican hakan sevda kelami poyraz tugra ercument gazi celik dogan ----------------------------------------------------------------------------- julide askin cemile kunter pelin dogan nurdan hikmet fazilet nazife atif ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- nurdan aslican hakan sevda kelami poyraz tugra ercument gazi celik dogan julide askin cemile kunter pelin dogan nurdan hikmet fazilet nazife atif ----------------------------------------------------------------------------- */ /* auto f = [](const std::string& s1, const std::string& s2){ return s1.size() < s2.size() || ( s1.size() == s2.size() && s1 < s2 ); }; */ std::list lString; fc(lString, 11, rname); print(lString); std::list lStringTwo; fc(lStringTwo, 11, rname); print(lStringTwo); lStringTwo.splice(lStringTwo.begin(), lString); print(lString); print(lStringTwo); } * Örnek 2, //.. int main() { /* # OUTPUT # nazli abdullah temel muruvvet ufuk zarife yurdanur hakan olcay poyraz pinat ----------------------------------------------------------------------------- tijen saadet pinat aynur onat nazli rupen berivan pelinsu necmiye melike ----------------------------------------------------------------------------- abdullah temel muruvvet ufuk zarife yurdanur hakan olcay poyraz pinat ----------------------------------------------------------------------------- tijen saadet pinat aynur onat nazli rupen berivan pelinsu necmiye nazli melike ----------------------------------------------------------------------------- */ /* auto f = [](const std::string& s1, const std::string& s2){ return s1.size() < s2.size() || ( s1.size() == s2.size() && s1 < s2 ); }; */ std::list lString; fc(lString, 11, rname); print(lString); std::list lStringTwo; fc(lStringTwo, 11, rname); print(lStringTwo); lStringTwo.splice(prev(lStringTwo.end()), lString, lString.begin()); print(lString); print(lStringTwo); } > Mülakat Sorusu: Bir işi hem üye fonksiyon yapsın hem de 'algoritm' başlık dosyasındaki bir fonksiyon. Bu durumda bizlerin ÜYE FONKSİYONU SEÇMELİYİZ. Çünkü üye fonksiyon 'private' kısma erişebilmekte, oradan bir fayda sağlayabilir. * Örnek 1, //.. class BigData{}; int main() { std::list myList; // Öğeleri 'reverse' etmek isteyelim. // Approach - I myList.reverse(); // Approach - II reverse(myList.begin(), myList.end()); // Bu durumda bizlerin ilk yolu tercih etmemiz gerekiyor. } * Örnek 2, //.. // 'find()' fonksiyonunun temsili implementasyonu: Linear Karmaşıklıkta. Bütün kabı tek tek dolaşıyoruz. template InIter Find(InIter beg, InIter end, const Key& key) { while (beg != end) if(*beg == key) return beg; ++beg; return beg; } int main() { // 'std::set' sınıfının üye fonksiyonu olan '.find()' ise log(n) karmaşıklığında. İkiye böle böle tarama yapmakta. // Dolayısıyla ben bir 'set' içerisinde veri ararken bütün kabı dolaşmaktan ise üye fonksiyonu kullanarak çok daha // hızlı sonuca varabilirim. } /*============================================================================================================*/ (29_26_12_2020) > 'STL' içerisindeki kaplar (devam): >> Bağlı liste veri yapıları (devam): >>> 'std::forward_list' sınıfının incelenmesi : Ender kullanım alanı vardır. Daha çok çifte bağlı liste kullanılır. C++11 ile dile eklendi. C dilindeki bağlı liste ile benzer performansı sağlaması amaçlanmıştır. Dolayısıyla '.size()' üye fonksiyonu mevcut değildir. Çifte bağlı listede her iki yöne de gidebiliyorken, tekli bağlı listede sadece tek yöne gidebiliyoruz. Bu da şöyle bir problemi beraberinde getirmektedir; çifte bağlı listede ilgili 'n' konumuna ekleme yapabiliyoruz fakat tekli bağlı listede ilgili 'n' konumuna ekleme yapabilmek için bizim 'n-1' konumu gerekiyor. Dolayısıyla tekli bağlı listedeki ekleme yapan üye fonksiyonların isimleri de 'insert_after' şeklindedir. Bu durumu şu şekilde açıklayabiliriz: 'n-1' konumundaki düğümün içerisindeki göstericiyi kullanarak 'n' konumundaki düğümün içerisindeki 'veri' teyit ediliyor. Eğer doğru ise ekleme yapılıyor. Benzer işlemler silme işlemleri için de geçerlidir. ÖZETLE EKLEME VE SİLME İŞLEMLERİ YAPAN FONKSİYONLARA GEÇİLEN KONUM BİLGİSİNDEN SONRAKİ KONUMA VEYA ÖNCEKİ KONUMA EKLEME/SİLME İŞLEMİ YAPILIYOR. O zaman aklımıza şu soru geliyor; ilk öğenin konumuna nasıl ekleme ve silme işlemi yapılacak? Bunun için de '.before_begin()' isimli bir fonksiyon mevcuttur. Kendisi bir iteratör döndürmekte olup, ilk düğümü gösteren 'anchor' düğümünün konumunu döndürmekte. ASLA VE ASLA 'derefence' ETMEMELİYİZ. Bu fonksiyonun geri döndürdüğü iteratörü ekleme ve silme işlemlerine argüman olarak geçeceğiz. Bu sıfın şablonunun iteratör grubu ise 'forward_iterator' grubundandır. Aşağıdaki örnekleri inceleyelim: * Örnek 1, //.. int main() { /* # OUTPUT # I: tansel nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- II: tansel AAAAA nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- III: BBBBB tansel AAAAA nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- IV: CCCCC BBBBB tansel AAAAA nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- V: CCCCC tansel AAAAA nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- VI: tansel AAAAA nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- VII: AAAAA nasrullah binnur suleyman olcay ----------------------------------------------------------------------------- */ std::forward_list myList; for(int i = 0; i < 5; ++i) myList.push_front(rname()); print(myList); // I myList.insert_after(myList.begin(), "AAAAA"); // İmplementason gereği aldığı konumdan sonraki konuma ekleme yapmakta. print(myList); // II myList.insert_after(myList.before_begin(), "BBBBB"); // 'anchor' düğümünün konumunu geçtiğimiz için ilk öğenin konumuna eklendi. print(myList); // III myList.push_front("CCCCC"); // 'insert_after' ile benzer mottoda. Fakat 'insert_after' gereken yerler de vardır. print(myList); // IV myList.erase_after(myList.begin()); // Silme işleminden sonra silinen öğeden sonraki öğenin konumunu döndürmektedir. print(myList); // V myList.erase_after(myList.before_begin()); print(myList); // VI myList.pop_front(); print(myList); // VII return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # tarik ercument dogan mukerrem celal beril yurdagul zeliha tonguc abdulmuttalip ----------------------------------------------------------------------------- Silinecek isim : zeliha zeliha tonguc abdulmuttalip ----------------------------------------------------------------------------- */ std::forward_list myList; for(int i = 0; i < 10; ++i) myList.push_front(rname()); print(myList); std::string nameToDelete; std::cout << "Silinecek isim : "; std::cin >> nameToDelete; if( auto iter = std::find(myList.begin(), myList.end(), nameToDelete); iter != myList.end()) { myList.erase_after(myList.before_begin(), iter); } print(myList); return 0; } * Örnek 3.1, mülakat sorusu: //.. int main() { /* # OUTPUT # sidre burak binnur busra tonguc ceylan alev papatya aycan kayahan ----------------------------------------------------------------------------- Silinecek isim : busra silindi... sidre burak binnur tonguc ceylan alev papatya aycan kayahan ----------------------------------------------------------------------------- */ std::forward_list myList; for(int i = 0; i < 10; ++i) myList.push_front(rname()); print(myList); std::string nameToDelete; std::cout << "Silinecek isim : "; std::cin >> nameToDelete; auto iter_erase = myList.before_begin(); auto iter = myList.begin(); for(; iter != myList.end(); ++iter, ++iter_erase) if(*iter == nameToDelete) break; if(iter != myList.end()) { std::cout << "<" << nameToDelete << "> silindi...\n"; myList.erase_after(iter_erase); } print(myList); return 0; } * Örnek 3.2, //.. int main() { /* # OUTPUT # orkun nahit gul naciye recep sezen mert sadullah tarcan yalcin ----------------------------------------------------------------------------- Silinecek isim : sadullah silindi... orkun nahit gul naciye recep sezen mert tarcan yalcin ----------------------------------------------------------------------------- */ std::forward_list myList; for(int i = 0; i < 10; ++i) myList.push_front(rname()); print(myList); std::string nameToDelete; std::cout << "Silinecek isim : "; std::cin >> nameToDelete; for(auto iter = myList.before_begin(); iter != myList.end(); ++iter) { if( *next(iter) == nameToDelete ) { std::cout << "<" << nameToDelete << "> silindi...\n"; myList.erase_after(iter); break; } } print(myList); return 0; } >> 'Associative Containers' : 'std::set', 'std::map', 'std::multiset' ve 'std::multimap' sınıf şablonlarıdır. İkili arama ağacı veri yapısını kullanır. Bir karşılaştırma ilişkisine göre öğeler kap içerisinde konumlanmakta. Dolayısıyla istediğimiz konuma değer ekleyemiyoruz. Bu dört sınıf şablonlarındaki üye fonksiyonlar neredeyse birbirinin aynısı. Arada küçük nüanslar bulunmaktadır. 'std::set' kabında bir anahtardan sadece bir tane olabilir. O değerden ikinci bir tanesi EKLENMEYECEKTİR. Fakat 'std::multiset' kabında o değerden birden fazla değer olabilir. İkili arama ağacında o türden bir veri tutulmakta. 'std::map' kabında ise anahtar-değer çiftleri tutulur. Her anahtarı tutan bir değer vardır yani. Yani ikili arama ağacında 'std::pair' sınıf şablonu tutulur. Yine bu sınıf kabında da sadece bir tane anahtar mevcut ama farklı anahtarlar aynı değeri tutabilirler. 'std::multimap' ise bünyesinde aynı anahtardan birden fazla çift tutabilir. Son olarak 'std::set' ve 'std::multiset' sınıfları 'set' isimli başlık dosyasında, 'std::map' ve 'std::multimap' sınıfları ise 'map' isimli başlık dosyasındadır. >>> 'std::set' sınıf şablonu : Aşağıdaki temsili şekli inceleyelim: /* 78 28 195 15 31 152 213 */ Görüldüğü üzere bu ikili ağacı denge durumundadır. Bu durumdayken ekleme/silme/arama işlemleri logaritmik karmaşıklık düzeyindedir. İş bu sınıf şablonu varsayılan karşılaştırma ilişkini 'std::less' isimli fonksiyon nesnesi ile kurmaktadır. * Örnek 1, //.. int main() { std::set mySet; // std::set> mySet; } >>>> Eğer karşılaştırma kritleri olarak 'std::less()' kullanılacaksa, sınıf türden elemanları saklayabilmek için ilgili sınıfımız '.operator<()' fonksiyonunu 'overload' etmelidir. Buradan hareketle diyebiliriz ki karşılaştırma kriterini neyle yapıyorsak sınıfımızda ilgili operatör de 'overload' edilmiş olmalı. Karşılaştırma 'equivalance' kurallarına göre yapılmaktadır. Fakat karşılaştırma operatörlerini bizler de yazabiliriz. Bir functor, 'lambda-expression' veya bir fonksiyon gelebilir; bir 'callable' geçebiliriz. Eğer bizler yazıyorsak yazdığımız kriter 'strict weak ordering' kurallarına tabii olması gerekiyor, 'Tanımsız Davranış' neden olmaması gerekiyor. * Örnek 1, //.. class Data{}; int main() { Data myData; std::set mySet; mySet.insert(myData); // Sentaks Hatası. std::set> mySetTwo; mySetTwo.insert(myData); // Sentaks Hatası. } * Örnek 2, //.. template using greaterSet = std::set>; int main() { greaterSet mySet; // Yazım kolaylığı sağlanmış oldu. } * Örnek 3, //.. class myComparator{ public: bool operator()(int x, int y)const{ return y < x; } // Daha karmaşık karşılaştırma kriteri de belirleyebiliriz. }; int main() { std::set mySet; } >>>>> 'strict weak ordering' : 'operator' kelimesi, bizim karşılaştırma kriterimiz olsun. Yazmış olduğumuz karşılaştırma kriteri aşağıdaki garantileri vermek ZORUNDADIR. >>>>>> 'antisymmetric' : "x operator y" işleminin sonucu 'true' ise "y operator x" işleminin sonucu 'false' OLMALI. >>>>>> 'irreflexive' : "x operator x" işleminin sonucu her zaman 'false' OLMALI. >>>>>> 'transitive' : "x operator y" işleminin sonucu 'true', "y operator z" işleminin sonucu da 'true' ise "x operator z" işleminin sonucu da 'true' OLMALI. >>>>>> 'transitivity of equivalance' : "!(x operator y) && !(y operator x)" işleminin sonucu 'true', "!(y operator z) && !(z operator y)" işleminin sonucu da 'true' ise "!(x operator z) && !(z operator x)" işlemi de 'true' OLMALI. >>>> 'std::set' sınıf şablonu bünyesindeki üye fonksiyonların incelenmesi: >>>>> '.insert()' fonksiyonu: Argüman olarak aldığı değeri, kap içerisinde uygun bir yere ekler. * Örnek 1, //.. int main() { /* # OUTPUT # 1563 eklendi... 1589 eklendi... 1527 eklendi... 172 eklendi... 753 eklendi... 965 eklendi... 1373 eklendi... 1807 eklendi... 467 eklendi... 812 eklendi... 172 467 753 812 965 1373 1527 1563 1589 1807 */ std::set mySet; Irand rand{ 0, 2000 }; for(int i = 0; i < 10; ++i) { int ival = rand(); std::cout << ival << " eklendi...\n"; mySet.insert(ival); } for(auto iter = mySet.cbegin(); iter != mySet.cend(); ++iter) std::cout << *iter << " "; // Çıktıda da görüleceği üzere küçük büyüğe doğru ekleme yapıldı. return 0; } * Örnek 2.1, karşılaştırma kriteri olarak bir 'functor' kullanmak: //.. class myComparatorClass{ public: bool operator()(int a, int b)const { return a>b; } }; int main() { /* # OUTPUT # 9 7 6 5 3 2 1 */ std::set mySet; for(int i = 0; i < 10; ++i) { mySet.insert(rand() % 10); } for(auto iter = mySet.cbegin(); iter != mySet.cend(); ++iter) std::cout << *iter << " "; std::cout << "\n"; return 0; } * Örnek 2.2, karşılaştırma kriteri olarak bir fonksiyon kullanmak: //.. bool myComparatorFunc(int a, int b) { return a>b; } int main() { /* # OUTPUT # 19 17 16 15 13 12 11 */ std::set mySetTwo(myComparatorFunc); // std::set mySetTwo(myComparatorFunc); for(int i = 0; i < 10; ++i) { mySetTwo.insert(rand() % 10 + 10); } for(auto iter = mySetTwo.cbegin(); iter != mySetTwo.cend(); ++iter) std::cout << *iter << " "; std::cout << "\n"; return 0; } * Örnek 2.3, karşılaştırma kriteri olarak bir 'lambda-expression' kullanmak: //.. auto f = [](int a, int b){ return a>b; }; int main() { /* # OUTPUT # 29 27 26 25 23 22 21 */ std::set mySetThree; // C++20'ye kadar bu bildirim sentaks hatasıdır çünkü 'lambda-expression' ile yazılan sınıfların // 'Default Ctor.' fonksiyonları 'delete' edildi. // std::set mySetThree(f); // C++20 öncesi dönem. for(int i = 0; i < 10; ++i) { mySetThree.insert(rand() % 10 + 20); } for(auto iter = mySetThree.cbegin(); iter != mySetThree.cend(); ++iter) std::cout << *iter << " "; std::cout << "\n"; return 0; } >>>>> 'Default Ctor.', 'Initializer-list Ctor.' ya da 'range' alan bir 'Ctor.' fonksiyonu vardır. Diğer kaplarda olduğu gibi 'size_t' alan ya da 'size_t' ve 'char' alan 'Ctor.' YOKTUR. Yine bu fonksiyonlara bir karşılaştırma kriter bilgisi de geçebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # 30 47 168 200 205 253 345 347 394 400 401 433 472 703 758 767 786 795 857 901 ----------------------------------------------------------------------------- 168 400 30 758 205 857 347 433 200 394 786 472 703 795 253 345 401 901 767 47 ----------------------------------------------------------------------------- */ std::set mySet; Irand myRand{ 0, 1000 }; while( mySet.size() < 20 ) mySet.insert(myRand()); print(mySet); std::mt19937 myRandomEngine; std::vector myVec{ mySet.begin(), mySet.end() }; std::shuffle( myVec.begin(), myVec.end(), myRandomEngine ); print(myVec); return 0; } >>>>> Ekleme için '.insert()' ve '.emplace()' fonksiyonlarını kullanabiliriz. Burada '.insert()' fonksiyonunun geri dönüş değeri 'std::pair::iterator, bool>' şeklindedir. '.insert_hint()' fonksiyonu 'iterator' alan bir 'overload' a sahiptir. İş bu 'overload' versiyonu ise ilgili değerin olup olmadığının aranması işleminin geçilen bu konum bilgisinden itibaren gerçekleşmesini beyan etmektedir. Ek olarak 'std::initializer_list' ve 'range' parametreli versiyonları da mevcuttur iş bu '.insert()' için. '.insert()' fonksiyonunun özellikleri '.emplace()' fonksiyonu için de geçerli. Tek farkı 'perfect-forward' ile direkt olarak 'Ctor.' fonksiyonuna iletiyor. * Örnek 1, //.. int main() { std::set mySet{ 1, 4, 7, 9, 6, 3, 2, 5 }; // İş bu rakamlar küçükten büyüğe doğru kaba eklenecektir. std::pair::iterator, bool> x = mySet.insert(69); // auto x = mySet.insert(71); // Eğer '69' rakamı 'std::set' içerisinde halihazırda VARSA, '.insert()' geri dönüş değeri olan // 'std::pair<>' sınıfının; // i. 'second' isimli veri elemanı 'false' olacak. // ii. 'first' isimli veri elemanı ise o var olan halihazırdaki öğenin konum bilgisi olacak. // Eğer '69' rakamı 'std::set' içerisinde halihazırda YOKSA, '.insert()' geri dönüş değeri olan // 'std::pair<>' sınıfının; // i. 'second' isimli veri elemanı 'true' olacak. // ii. 'first' isimli veri elemanı ise o yeni eklenen öğenin konum bilgisi olacak. /* # OUTPUT # 69 degeri 8 konumuna eklenmistir. */ if(x.second) std::cout << *(x.first) << " degeri " << std::distance(mySet.begin(), x.first) << " konumuna eklenmistir.\n"; else std::cout << *(x.first) << " degeri " << std::distance(mySet.begin(), x.first) << " konumunda mevcuttur.\n"; /* # OUTPUT # 3 degeri 2 konumunda mevcuttur. */ x = mySet.insert(3); if(x.second) std::cout << *(x.first) << " degeri " << std::distance(mySet.begin(), x.first) << " konumuna eklenmistir.\n"; else std::cout << *(x.first) << " degeri " << std::distance(mySet.begin(), x.first) << " konumunda mevcuttur.\n"; return 0; } >>>>> Arama için; eğer nihai amacımız sadece ve sadece var/yok sorgulaması yapmaksa, yani bulunduğunda o öğeye erişme amacımız yoksa, '.count()' fonksiyonu bu iş için verimli olabilir. 'std::multiset' için bu çağrı ilgili öğeden toplam kaç adet olduğunu bildirmektedir. Bunun haricinde '.find()' fonksiyonunu gerçekten de ilgili öğeye erişmek için kullanabiliriz. Geri döndürdüğü şey bir iteratör ve bulduğu nesnenin konumunu döndürmektedir. 'std::multiset' için iş bu fonksiyon ilk öğenin konumunu döndürmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # afyonkarahisar antalya artvin aydin batman cankiri diyarbakir elazig erzurum giresun karabuk kastamonu kirikkale kirsehir kocaeli osmaniye samsun siirt sivas tokat ----------------------------------------------------------------------------- siirt Evet var. */ std::set mySet; fcs(mySet, 20, rtown); print(mySet); std::string nameToCount; std::cin >> nameToCount; if(mySet.count(nameToCount)) std::cout << "Evet var.\n"; else std::cout << "Hayir, yok.\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # adana afyonkarahisar amasya ankara antalya ardahan bartin diyarbakir erzincan gaziantep kars konya mardin ordu osmaniye rize samsun sinop trabzon yozgat ----------------------------------------------------------------------------- yozgat Aranan yozgat isimli sehir 19. konumda bulundu. */ std::set mySet; fcs(mySet, 20, rtown); print(mySet); std::string nameToCount; std::cin >> nameToCount; if( auto iter = mySet.find(nameToCount); iter != mySet.end()) { std::cout << "Aranan " << *iter << " isimli sehir " << std::distance(mySet.begin(), iter) << ". konumda bulundu.\n"; } else { std::cout << "Aranan " << nameToCount << " isimli sehir BULUNAMADI.\n"; } return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # abdulmuttalip ali deniz ihsan turgut ----------------------------------------------------------------------------- ihsan => true ulya => false */ std::set maleNames{ "ali", "turgut", "ihsan", "deniz", "abdulmuttalip" }; print(maleNames); std::cout << "ihsan => " << ( maleNames.contains("ihsan") ? "true" : "false" ) << "\n"; std::cout << "ulya => " << ( maleNames.contains("ulya") ? "true" : "false" ) << "\n"; return 0; } >>>>> Silme işlemleri için '.erase()' fonksiyonunu kullanabiliriz ki bu fonksiyon 'iterator' alan , 'range' alan vs. 'overload' lara sahip. 'std::multiset' için '.erase()' fonksiyonu silinen toplam adedi döndürmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # I: aksaray bartin canakkale igdir malatya sirnak ----------------------------------------------------------------------------- II: aksaray canakkale igdir malatya sirnak ----------------------------------------------------------------------------- III: aksaray canakkale malatya sirnak ----------------------------------------------------------------------------- Silinecel sehir : malatya [malatya] isimli sehir silindi... IV: aksaray canakkale sirnak ----------------------------------------------------------------------------- */ std::set mySet; fcs(mySet, 6, rtown); print(mySet); // I mySet.erase(std::next(mySet.begin())); print(mySet); // II mySet.erase( std::next(mySet.begin(), 2), std::prev(mySet.end(), 2)); print(mySet); // III std::string townToDelete; std::cout << "Silinecel sehir : "; std::cin >> townToDelete; if(auto iter = mySet.find(townToDelete); iter != mySet.end()) { mySet.erase(iter); std::cout << "[" << townToDelete << "] isimli sehirler silindi...\n"; } else { std::cout << "[" << townToDelete << "] isimli sehir silinemedi...\n"; } print(mySet); // IV return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # adiyaman amasya denizli hakkari karaman kayseri mus usak yalova yozgat ----------------------------------------------------------------------------- Sirasiyla eski ve yeni isimleri girin => yalova kirikkale Sirasiyla eski ve yeni isimleri girin => usak ankara adiyaman amasya ankara denizli hakkari karaman kayseri kirikkale mus yozgat ----------------------------------------------------------------------------- */ std::set mySet; fcs(mySet, 10, rtown); print(mySet); // I std::string old_name, new_name; std::cout << "Sirasiyla eski ve yeni isimleri girin => "; std::cin >> old_name >> new_name; // C++17 oncesi auto iter = mySet.find(old_name); if(iter != mySet.end()) { mySet.erase(iter); // Anahtar ağaçtan çıkartıldı ve içindeki nesne de yok edildi.. mySet.insert(new_name); // Yeni bir nesne hayata geldi ve ağaca eklendi. } std::cout << "Sirasiyla eski ve yeni isimleri girin => "; std::cin >> old_name >> new_name; // C++17 ve sonrası if(auto iter = mySet.find(old_name); iter != mySet.end()) { // std::set::node_type handler = mySet.extract(old_name); auto handler = mySet.extract(old_name); // Artık anahtarımız fiilen ağaçtan çıkartıldı fakat yok edilmedi. handler.value() = new_name; // Anahtarımızın değerini değiştirdik. mySet.insert(std::move(handler)); // Yeni anahtarımızı ağaca ekledik fakat anahtarımızı'R-Value' haline getirmemiz zorunlu. } print(mySet); return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # abdulmuttalip ali deniz ihsan turgut ----------------------------------------------------------------------------- ayse belgin emine naciye zarife ----------------------------------------------------------------------------- abdulmuttalip ali ihsan turgut ----------------------------------------------------------------------------- ayse belgin deniz emine naciye zarife ----------------------------------------------------------------------------- */ std::set maleNames{ "ali", "turgut", "ihsan", "deniz", "abdulmuttalip" }; std::set femaleNames{ "ayse", "belgin", "zarife", "emine", "naciye" }; print(maleNames); print(femaleNames); if(auto handler = maleNames.extract("deniz"); handler) { femaleNames.insert(std::move(handler)); } print(maleNames); print(femaleNames); return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # abdulmuttalip ali deniz ihsan turgut ----------------------------------------------------------------------------- ayse belgin emine naciye zarife ----------------------------------------------------------------------------- abdulmuttalip ali ayse belgin deniz emine ihsan naciye turgut zarife ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ std::set maleNames{ "ali", "turgut", "ihsan", "deniz", "abdulmuttalip" }; std::set femaleNames{ "ayse", "belgin", "zarife", "emine", "naciye" }; print(maleNames); print(femaleNames); maleNames.merge(femaleNames); print(maleNames); print(femaleNames); return 0; } >>>>> '.key_comp()' : Karşılaştırma kriterini döndüren bir fonksiyondur. * Örnek 1, //.. int main() { /* # OUTPUT # abdulmuttalip ali deniz ihsan turgut ----------------------------------------------------------------------------- key_comp : St4lessINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE result : 1 */ std::set maleNames{ "ali", "turgut", "ihsan", "deniz", "abdulmuttalip" }; print(maleNames); std::cout << "key_comp : " << typeid(maleNames.key_comp()).name() << "\n"; std::cout << "result : " << maleNames.key_comp()("ahmet", "ulya") << "\n"; // Aynı karşılaştırma kriterini kullanarak iki ismi karşılaştırdık. return 0; } >>>> 'std::set' sınıf kabındaki bir öğeyi değiştirmeye çalışmak SENTAKS HATASINA neden olur çünkü iteratör konumundaki nesne 'const' kabul edilmektedir. Sentaks hatasını ortadan kaldıracak bir kod yazmamız durumunda 'Tanımsız Davranış' meydana gelecektir. Peki değiştirmemiz gerekiyor ise ne yapmalıyız? Var olanı silip, yeni değeri tekrardan bağlı listeye eklemeliyiz. >>>> Tıpkı bağlı listede olduğu gibi ikili arama ağacındaki düğümler, hangi ağaca ait olduklarını bilmemektedirler. >>> 'std::set' ve 'std::multiset' farklılığa için bir örnek: * Örnek 1, //.. int main() { /* # OUTPUT # 01 Ekim 1961 Pazar 02 Mayis 2006 Sali 03 Mart 1983 Persembe 04 Agustos 1992 Sali 05 Aralik 1990 Carsamba 06 Aralik 2003 Cumartesi 07 Nisan 1968 Pazar 08 Haziran 1963 Cumartesi 09 Temmuz 1967 Pazar 10 Aralik 1977 Cumartesi 11 Ocak 2014 Cumartesi 12 Subat 2017 Pazar 14 Aralik 2019 Cumartesi 15 Ekim 2016 Cumartesi 16 Mart 1955 Carsamba 17 Agustos 1992 Pazartesi 18 Agustos 1993 Carsamba 19 Nisan 2011 Sali 20 Subat 2018 Sali 21 Haziran 1997 Cumartesi 22 Ekim 1953 Persembe 23 Kasim 1997 Pazar 24 Eylul 2007 Pazartesi 25 Eylul 1992 Cuma 26 Subat 1990 Pazartesi 27 Aralik 1978 Carsamba 28 Agustos 1972 Pazartesi 29 Kasim 1978 Carsamba 30 Ekim 2007 Sali 31 Ekim 1960 Pazartesi ----------------------------------------------------------------------------- 01 Nisan 1998 Carsamba 01 Agustos 1966 Pazartesi 01 Haziran 1957 Cumartesi 02 Eylul 1991 Pazartesi 02 Temmuz 1984 Pazartesi 02 Nisan 1952 Carsamba 02 Eylul 2016 Cuma 02 Ekim 1961 Pazartesi 03 Eylul 1984 Pazartesi 03 Temmuz 1962 Sali 03 Kasim 2003 Pazartesi 03 Ocak 1967 Sali 03 Ocak 1951 Carsamba 04 Mart 2016 Cuma 04 Aralik 1995 Pazartesi 04 Ocak 1982 Pazartesi 05 Aralik 1955 Pazartesi 05 Kasim 1973 Pazartesi 05 Ekim 1992 Pazartesi 06 Subat 1974 Carsamba 07 Eylul 2000 Persembe 07 Subat 1976 Cumartesi 07 Kasim 1993 Pazar 07 Mayis 1978 Pazar 08 Agustos 1961 Sali 08 Haziran 1992 Pazartesi 08 Ekim 1967 Pazar 08 Agustos 1969 Cuma 09 Aralik 2005 Cuma 10 Haziran 1990 Pazar 10 Haziran 1986 Sali 10 Aralik 1980 Carsamba 11 Subat 2001 Pazar 11 Temmuz 1952 Cuma 11 Aralik 1994 Pazar 11 Agustos 1986 Pazartesi 12 Eylul 1983 Pazartesi 12 Ocak 1985 Cumartesi 12 Mayis 1974 Pazar 12 Subat 1986 Carsamba 13 Aralik 1989 Carsamba 13 Ocak 2006 Cuma 13 Haziran 1986 Cuma 13 Temmuz 1958 Pazar 13 Haziran 1971 Pazar 15 Eylul 2001 Cumartesi 15 Mart 1991 Cuma 15 Mayis 1995 Pazartesi 16 Aralik 1970 Carsamba 16 Nisan 1998 Persembe 16 Haziran 1950 Cuma 16 Aralik 1986 Sali 17 Subat 1959 Sali 17 Kasim 1950 Cuma 17 Ekim 2010 Pazar 17 Ekim 1975 Cuma 18 Temmuz 2000 Sali 19 Nisan 2006 Carsamba 19 Ocak 2010 Sali 19 Temmuz 2015 Pazar 19 Kasim 1980 Carsamba 19 Ekim 1965 Sali 19 Haziran 2017 Pazartesi 19 Haziran 1998 Cuma 20 Mayis 1957 Pazartesi 20 Subat 1986 Persembe 20 Mayis 1981 Carsamba 20 Aralik 1966 Sali 20 Temmuz 2002 Cumartesi 21 Eylul 1954 Sali 21 Ekim 2018 Pazar 21 Subat 1961 Sali 22 Ekim 2019 Sali 22 Temmuz 2010 Persembe 22 Subat 2019 Cuma 22 Mart 2006 Carsamba 23 Eylul 1985 Pazartesi 23 Mart 1996 Cumartesi 23 Mart 1974 Cumartesi 23 Temmuz 2006 Pazar 24 Aralik 1959 Persembe 24 Eylul 2016 Cumartesi 25 Kasim 2017 Cumartesi 25 Mayis 1990 Cuma 25 Kasim 2002 Pazartesi 26 Kasim 1960 Cumartesi 26 Haziran 2002 Carsamba 26 Kasim 1951 Pazartesi 26 Kasim 1962 Pazartesi 26 Temmuz 1986 Cumartesi 26 Mayis 1992 Sali 26 Haziran 1963 Carsamba 27 Ocak 1984 Cuma 27 Nisan 2010 Sali 29 Mayis 1989 Pazartesi 29 Eylul 1971 Carsamba 29 Ekim 2013 Sali 29 Subat 2004 Pazar 29 Ocak 1957 Sali 30 Aralik 1962 Pazar ----------------------------------------------------------------------------- */ auto f = [](const Date& dx, const Date& dy){ return dx.month_day() < dy.month_day(); }; std::set mySet; for(int i = 0; i < 100; ++i) mySet.insert(Date::random()); print(mySet, "\n"); std::multiset myMultiSet; for(int i = 0; i < 100; ++i) myMultiSet.insert(Date::random()); print(myMultiSet, "\n"); // Çıktıda da görüldüğü üzere 'std::multiset' aynı değere sahip birden fazla öğe içermektedir. // BURADAKİ ANAHTAR AYIN GÜNÜ. return 0; } >>>> 'std::multiset' sınıf şablonundaki '.insert()' üye fonksiyonunun geri dönüş değeri doğrudan bir 'iterator' şeklindedir. >>> 'std::map' sınıf şablonunun incelenmesi: Logaritmik karmaşıklıkta "Anahtar-Değer" çiftlerinin tutulmasıdır. Yine ikili arama ağacı veri yapısı kullanılır. Yine karşılaştırma usülüne göre kapta doldurma yapar. Tıpkı 'std::set' sınıf şablonunda olduğu gibi ilgili karşılaşırma kriterini biz belirleyebiliriz fakat varsayılan olarak anahtara bakarak küçükten büyüğe doğru yapmaktadır. 'std::multimap' den farkı tıpkı 'std::set' de olduğu gibi 'std::map' sadece bir adet 'Key' tutmakta fakat 'std::multimap' birden fazla 'Key' tutabilir. Aynı değerden birden fazla da olabilir. Artık kapta tutulan öğelerimiz birer 'std::pair<>' şeklinde. * Örnek 1, //.. int main() { /* # OUTPUT # */ std::map myMap; // Anahtara karşılık gelen tür 'int'. Bu durumda varsayılan karşılaştırma kriter 'std::less' olacaktır. // Değere karşılık gelen tür 'std::string' return 0; } >>>> İlgili sınıfın özel üye fonksiyonlarının incelenmesi: >>>>> 'Ctor.' fonksiyonları artık 'std::pair' almaktadır. Onun haricinde 'std::set' ile aynıdır. * Örnek 1, //.. int main() { /* # OUTPUT # <6,sinem> <9,hakan> <12,ayse> <15,korhan> */ std::map myMap { {12, "ayse"}, {9, "hakan"}, {6, "sinem"}, {15, "korhan"} }; // Anahtara karşılık gelen tür 'int'. Bu durumda varsayılan karşılaştırma kriter // 'std::less' olacaktır. Değere karşılık gelen tür 'std::string' for(const auto& index : myMap) std::cout << "<" << index.first << "," << index.second << ">\n"; return 0; } >>>>> Ekleme işlemleri: '.insert()' fonksiyonuna bir 'std::pair<>' geçebiliriz. 'CTAD' gelmesi ile birlikte kullanım kolaylığı gelmiştir. 'std::set' ile aynı fonksiyonlara sahiptir. * Örnek 1, //.. int main() { /* # OUTPUT # <6,sinem> <9,hakan> <12,ayse> <15,korhan> --------------------- <3,nergis> <6,sinem> <9,hakan> <12,ayse> <15,korhan> <18,hulusi> --------------------- <3,nergis> <6,sinem> <9,hakan> <12,ayse> <15,korhan> <18,hulusi> <21,murda> */ std::map myMap { {12, "ayse"}, {9, "hakan"}, {6, "sinem"}, {15, "korhan"} }; // Anahtara karşılık gelen tür 'int'. Bu durumda varsayılan karşılaştırma kriter 'std::less' olacaktır. // Değere karşılık gelen tür 'std::string' for(const auto& index : myMap) std::cout << "<" << index.first << "," << index.second << ">\n"; std::cout << "---------------------\n"; myMap.insert( std::pair{18, "hulusi"} ); // C++17 öncesi myMap.insert({ 3, "nergis" }); // C++17 ve sonrası for(const auto& index : myMap) std::cout << "<" << index.first << "," << index.second << ">\n"; std::cout << "---------------------\n"; myMap.insert( std::make_pair(21, "murda") ); for(const auto& index : myMap) std::cout << "<" << index.first << "," << index.second << ">\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # cemil => 30 Mart 2010 Sali eylul => 05 Subat 2013 Sali gulden => 07 Mart 1990 Carsamba halime => 09 Mayis 1997 Cuma malik => 02 Aralik 1973 Pazar mukerrem => 10 Subat 1967 Cuma petek => 14 Ocak 1963 Pazartesi tayfun => 10 Kasim 1959 Sali tonguc => 12 Kasim 1997 Carsamba */ std::map myMap; for(int i = 0; i < 10; ++i) { myMap.insert( {rname(), Date::random()} ); } std::cout << std::left; for(const auto& [name, birth_date] : myMap) { std::cout << std::setw(12) << name << " => " << birth_date << "\n"; } return 0; } >>>>> Arama işlemleri de yine 'std::set' ile aynıdır. * Örnek 1, //.. int main() { /* # OUTPUT # Aranacak ismi girin : ahmet ahmet, 70298129 ahmet, 172669440 ahmet, 377108003 ahmet, 385158732 ahmet, 450395738 ahmet, 572561811 ahmet, 575705360 ahmet, 603123090 ahmet, 628257755 ahmet, 660420424 ahmet, 796664164 ahmet, 838139053 ahmet, 869921280 ahmet, 886578186 ahmet, 899334107 ahmet, 919191847 ahmet, 1030273655 ahmet, 1104028380 ahmet, 1108199257 ahmet, 1311572935 ahmet, 1347584264 ahmet, 1362757102 ahmet, 1449189833 ahmet, 1506997827 ahmet, 1530548506 ahmet, 1632381616 ahmet, 1667250732 ahmet, 1685622011 ahmet, 1849557795 ahmet, 1943003493 ahmet, 2139842053 */ std::multimap myMap; fcs(myMap, 10'000, []{ return std::make_pair(rname(), rand()); }); std::cout << "Aranacak ismi girin : "; std::string nameToLookUp; std::cin >> nameToLookUp; auto [iterLowerBound, iterUpperBound] = myMap.equal_range(nameToLookUp); while( iterLowerBound != iterUpperBound ) { std::cout << iterLowerBound->first << ", " << iterLowerBound->second << "\n"; ++iterLowerBound; } return 0; } >> Sıralı haldeki veri yapılarında kullanılan 'lower_bound', 'upper_bound' ve 'equal_range' kelimelerin anlamları: >>> 'lower_bound' : Anahtar olan öğeden Büyük-Eşit olan ilk öğenin konumu. Bir diğer değişle ilgili anahtar değerini 'insert' edebileceğim İLK konum bilgisidir. * Örnek 1, Sıralı Veri Yapısı => 2 3 3 3 5 5 7 7 7 8 12 16 19 Key: 7, lower_bound => ^ Key: 4, lower_bound => ^ >>> 'upper_bound' : Anahtar olan öğeden Büyük olan ilk öğenin konumudur. Bir diğer değişle ilgili anahtar değerini 'insert' edebileceğim SON konum bilgisidir. * Örnek 1, Sıralı Veri Yapısı => 2 3 3 3 5 5 7 7 7 8 12 16 19 Key: 7, lower_bound => ^ , upper_bound => ^ Key: 4, lower_bound => ^ upper_bound => ^ Key: 13, lower_bound => ^ , upper_bound => ^ >>> 'equal_range' : Öyle bir 'range' düşününki 'lower_bound' ve 'upper_bound' konum bilgilerinden meydana gelsin. Bir 'pair of iterator' döndürmektedir. Bu 'pair' sınıfının 'first' isimli veri elemanı 'lower_bound' konumunu, 'second' isimli veri elemanı da 'upper_bound' konumunu tutmaktadır. * Örnek 1, Sıralı Veri Yapısı => 2 3 3 3 5 5 7 7 7 8 12 16 19 Key: 7, lower_bound => ^ , upper_bound => ^ , equal_range => ^ ^ Key: 13, lower_bound => ^ , upper_bound => ^ , equal_range => // 'upper_bound' ve 'lower_bound' konumları aynı olduğundan 'range' BOŞTUR. * Örnek 1, //.. int main() { /* # OUTPUT # 0 1 2 3 6 7 8 9 11 12 13 16 18 19 20 22 23 25 27 29 ----------------------------------------------------------------------------- 6 rakami için lower_bound konumu : 4 6 rakami için upper_bound konumu : 5 [1] => 6 ----------------------------------------------------------------------------- */ std::multiset mySet; fcs(mySet, 20, []{ return rand() % 30; }); print(mySet); auto iter_lower = mySet.lower_bound(6); // '6' rakamı için 'lower_bound' konumu. std::cout << 6 << " rakami için lower_bound konumu : " << std::distance(mySet.begin(), iter_lower) << "\n"; auto iter_upper = mySet.upper_bound(6); // '6' rakamı için 'upper_bound' konumu. std::cout << 6 << " rakami için upper_bound konumu : " << std::distance(mySet.begin(), iter_upper) << "\n"; // Approach - I std::cout << "[" << std::distance(iter_lower, iter_upper) << "] => "; print(iter_lower, iter_upper); // Approach - II // auto p = mySet.equal_range(6); // print(p.first, p.second); return 0; } > 'equality' ve 'equivalance' : C++ dilinde farklı anlamlarda kullanılmaktadır. >> 'equality' : Karşılaştırma '==' operatörü ile yapıldığındaki durumdur. Bu işlemi yapan fonksiyon nesnesi('functor') ise 'std::equal_to()' şeklindedir. >> 'equivalance' : Karşılaştırma '==' operatörü ile yapılmamaktadır. Velevki varsayılan karşılaştırma operatörü olarak 'std::less' kullanılsın; karşılaştırma "!(x < y) && !(y < x)" kriterine göre yapılacaktır. /*============================================================================================================*/ (30_27_12_2020) > 'Structural Binding' : Aşağıdaki örnekleri inceleyelim. * Örnek 1, //.. struct Data{ int m1, m2, m3; }; Data foo(void){ return { 10, 56, 67 }; } int main() { int a[3] = { 1, 4, 7 }; auto [ x, y, z ] = a; // 'x' has the value of a[0] // 'y' has the value of a[1] // 'z' has the value of a[2] Data myData{ 2, 7, 9 }; auto [ xx, yy, zz ] = myData; // 'xx' has the value of myData.m1; // 'yy' has the value of myData.m2; // 'zz' has the value of myData.m3; auto& [ rxx, ryy, rzz ] = myData; // 'rxx' is a reference to myData.m1; // 'ryy' is a reference to myData.m2; // 'rzz' is a reference to myData.m3; auto [ fxx, fyy, fzz ] = foo(); // 'fxx' has the value of '10'; // 'fyy' has the value of '56'; // 'fzz' has the value of '67'; // '[]' içerisindeki değişken sayısındaki uyumsuzluk sentaks hatasıdır. } * Örnek 2, //.. std::tuple foo(); int main() { auto [ x, y, z ] = foo(); // 'x' has the value of the first element in the 'tuple'. // 'y' has the value of the second element in the 'tuple'. // 'z' has the value of the third element in the 'tuple'. } * Örnek 3, //.. int main() { /* # OUTPUT # alev 10 Agustos 1956 Cuma cahide 30 Aralik 1973 Pazar can 15 Kasim 1970 Pazar diana 23 Subat 1997 Pazar ferhat 06 Mayis 1996 Pazartesi gursel 06 Kasim 1996 Carsamba hilmi 17 Aralik 1966 Cumartesi irmak 03 Subat 1957 Pazar nahit 07 Temmuz 2001 Cumartesi naz 10 Haziran 1965 Persembe sefer 16 Temmuz 2000 Pazar tarkan 01 Mayis 1992 Cuma utku 30 Temmuz 2008 Carsamba */ std::map myMap; fcs(myMap, 15, []{ return std::make_pair(rname(), Date::random()); }); std::cout << std::left; for(const auto& [name, birthDate] : myMap) { std::cout << std::setw(12) << name << birthDate << "\n"; } return 0; } * Örnek 4, Mülakat sorusu: Aşağıdaki 'p' değişkeninin türü nedir? //.. struct Data{ int x; int a[10]; }; Data foo() { return Data{}; } int main() { /* # OUTPUT # i is i p is A10_i */ auto [ i, p ] = foo(); // Arka planda olanların temsili gösterimi: // i. 'auto' anahtarına karşılık gelen tür 'foo()' fonksiyonun geri dönüş değeri, // yani 'Data' türü. // ii. 'i' ve 'p' ise bu durumda 'Data' türünün elemanlarına hitap ediyor. Dolayısıyla // 'p' nin türü 10 elemanlı bir dizi. std::cout << "i is " << typeid(i).name() << "\n"; std::cout << "p is " << typeid(p).name() << "\n"; return 0; } * Örnek 5, //.. struct Data{ int x; int y; double d; int a[10]; }; Data foo() { return Data{}; } int main() { auto [ x, y, z, _] = foo(); // İlgili 'Data' sınıfındaki 'a' değişkenini kullanmak istemediğimiz için onun adını '_' olarak verdik. // Fakat '_' ismi görülür olduğu her yerde tekrar bildirilemez. } > 'STL' içerisindeki kaplar (devam) : >> 'Associative Containers' (devam ): >>> 'std::map' sınıf şablonunun incelenmesi (devam) : >>>> '.operator[]' fonksiyonu: Aşağıdaki örneği inceleyelim: * Örnek 1, //.. int main() { /* # OUTPUT # [bilgin, 58] [ediz, 48] [hilal, 51] [naci, 62] [nuri, 60] ----------------------------------------------------------------------------- İsim ve yaş bilgilerini girin: ahmet 31 [ahmet, 31] [bilgin, 58] [ediz, 48] [hilal, 51] [naci, 62] [nuri, 60] ----------------------------------------------------------------------------- */ /* # OUTPUT # [caner, 51] [nurullah, 60] [pelinsu, 62] [ugur, 48] [zekai, 58] ----------------------------------------------------------------------------- İsim ve yaş bilgilerini girin: caner 15 [caner, 15] [nurullah, 60] [pelinsu, 62] [ugur, 48] [zekai, 58] ----------------------------------------------------------------------------- */ map myMap; fcs(myMap, 5, []{ return std::make_pair(rname(), rand() % 60 + 5 ); }); print(myMap, "\n"); string name; int age; std::cout << "İsim ve yaş bilgilerini girin: "; std::cin >> name >> age; myMap[name] = age; std::cout << "\n\n"; print(myMap, "\n"); // Çıktıda görüldüğü üzere eğer 'Key' mevcut değil ise ikili arama ağacına ekleniyor; // eğer mevcut ise o 'Key' e karşılık gelen 'Value' değiştiriliyor. // Bu durumda şunu diyebiliriz ki; // i. İlgili anahtar bulunması durumunda, '.operator[]' fonksiyonunun geri dönüş değeri ilgili // 'Key-Value' çiftinin 'second' isimli veri elemanına referanstır. // ii. İlgili anahtar yok ise yeni bir 'Key-Value' çifti oluşturuluyor, ki 'first' ve 'second' // isimli veri elemanları değerlerini sırasıyla 'name' ve 'age' isimli değişkenlerden almaktadır, // ve '.operator[]' fonksiyonunun geri dönüş değeri yeni oluşturulan 'Key-Value' çiftinin 'second' // isimli veri elemanına referanstır. Fakat 'second' isimli veri elemanı ilk olarak 'Value Init.' // edilmektedir. BURADA GÖZ ARDI EDİLMEMESİ GEREKEN NOKTA İLGİLİ 'Key' YOK İSE YENİ BİR EKLEME // YAPILACAKTIR. } * Örnek 2, bir konteynırda bulunan isimlerden kaçar tane olduğunun hesaplanması: //.. int main() { /* # OUTPUT # [abdulmuttalip, 2] [alev, 1] [ali, 1] [alparslan, 1] [arda, 1] [askin, 1] [aslican, 1] [ata, 1] [atakan, 1] [atalay, 1] [atif, 1] [aynur, 1] [ayse, 1] [baran, 1] [beril, 1] [bilgin, 1] [binnur, 1] [burhan, 1] [candan, 1] [cebrail, 1] [ceyhan, 1] [ceyhun, 1] [cihat, 4] [devrim, 1] [diana, 1] [dogan, 1] [dost, 1] [ece, 1] [eda, 2] [edip, 1] [emine, 1] [engin, 1] [esen, 1] [esra, 1] [fadime, 2] [fahri, 1] [ferhat, 1] [fuat, 1] [gizem, 1] [gursel, 2] [hakan, 1] [haldun, 2] [haluk, 1] [hulki, 1] [hulya, 1] [iffet, 1] [izzet, 1] [kaan, 1] [kasim, 1] [kazim, 1] [kezban, 1] [korhan, 1] [mahir, 2] [melih, 1] [melike, 1] [metin, 1] [murathan, 1] [nagehan, 1] [nalan, 2] [nihat, 1] [nuri, 1] [nuriye, 1] [olcay, 3] [onat, 1] [papatya, 1] [petek, 2] [sabriye, 1] [sadegul, 1] [sami, 1] [saniye, 1] [selenay, 1] [sevda, 2] [sezen, 1] [su, 1] [taner, 1] [tansu, 1] [tarcan, 1] [tugra, 1] [turgut, 1] [ufuk, 1] [yasemin, 1] [yasin, 1] [yelda, 1] [zahide, 1] [zeliha, 1] [zerrin, 1] ----------------------------------------------------------------------------- */ std::vector sVec; fcs(sVec, 100, rname); std::map cMap; for(const auto& name: sVec) ++cMap[name]; // i. 'for' döngüsünün ilk turunda 'name' için 'Enes' isminin geldiğini varsayalım; // ii. Bu durumda kap boş olduğundan bir adet 'std::pair<>' oluşturulacak. Bunun 'first' // isimli veri elemanı 'Enes' ismini alırken, 'second' isimli veri elemanı ise '0' değeri // ile hayata gelecek çünkü 'Value Init.' edildi. Sonrasında da bu ifade '.operator++()' // fonksiyonuna operand olacak ki 'second' isimli veri elemanının değeri bir artacak. // iii. Döngünün ikinci turunda tekrardan 'Enes' ismi gelirse bizim 'second' bir artacak. // Başka bir isim gelirse yukarudaki işlemler gerçekleşecek. print(cMap, "\n"); } * Örnek 3, yukarıdaki örnekteki isimleri, 'Değer' bilgisine göre, Büyükten Küçüpe doğru sıralayalım: //.. int main() { /* # OUTPUT # [feramuz, 4] [nazif, 3] [ciler, 3] [rupen, 2] [murathan, 2] [kaya, 2] [kaan, 2] [nefes, 2] [nuriye, 2] [fugen, 2] [fahri, 2] [erdem, 2] [sami, 2] [soner, 2] [sumeyye, 2] [beyhan, 2] [tugra, 2] [anil, 2] [zarife, 1] [nuri, 1] [niyazi, 1] [nisan, 1] [nevsin, 1] [yurdagul, 1] [yurdanur, 1] [yelda, 1] [naz, 1] [nasrullah, 1] [mustafa, 1] [muslum, 1] [zubeyde, 1] [yilmaz, 1] [turhan, 1] [orkun, 1] [tunc, 1] [pakize, 1] [recep, 1] [kayahan, 1] [sadegul, 1] [sadullah, 1] [sarp, 1] [teoman, 1] [sidre, 1] [tarcan, 1] [binnur, 1] [durriye, 1] [dilek, 1] [ceyhun, 1] [ceyhan, 1] [cemile, 1] [cemal, 1] [can, 1] [bulent, 1] [ege, 1] [bilge, 1] [baran, 1] [azize, 1] [aslican, 1] [askin, 1] [asim, 1] [adem, 1] [hasan, 1] [melisa, 1] [melek, 1] [lale, 1] [abdullah, 1] [jade, 1] [irmak, 1] [huseyin, 1] [hulki, 1] [murat, 1] [gulsen, 1] [furkan, 1] [eylul, 1] [emrecan, 1] [emre, 1] [emine, 1] [egemen, 1] ----------------------------------------------------------------------------- */ std::vector sVec; fcs(sVec, 100, rname); std::map cMap; for(const auto& name: sVec) ++cMap[name]; std::vector> myVec{ cMap.begin(), cMap.end() }; // std::vector myVec{ cMap.begin(), cMap.end() }; // CTAD : Sınıf şablonlarında tür çıkarımı. std::sort( myVec.begin(), myVec.end(), [](const std::pair& p1, const std::pair& p2){ return p1.second > p2.second; } ); // Generalized Lambda Expression // std::sort(myVec.begin(), myVec.end(), [](const auto& p1, const auto& p2){ return p1.second > p2.second; }; print(myVec, "\n"); } >>>> '.at()' fonksiyonu: C++11 ile dile eklenmiştir. Aranan 'Key' değeri ikili arama ağacında var ise değerini değiştiriyor. Fakat yok ise 'exception' fırlatmaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # [cahit, 62] [hilal, 60] [sumeyye, 58] [tayyar, 48] [tugra, 51] ----------------------------------------------------------------------------- İsim ve yaş bilgilerini girin: ahmet 21 Hata yakalandi => map::at [cahit, 62] [hilal, 60] [sumeyye, 58] [tayyar, 48] [tugra, 51] ----------------------------------------------------------------------------- */ /* # OUTPUT # [caner, 51] [nurullah, 60] [pelinsu, 62] [ugur, 48] [zekai, 58] ----------------------------------------------------------------------------- İsim ve yaş bilgilerini girin: caner 15 [caner, 15] [nurullah, 60] [pelinsu, 62] [ugur, 48] [zekai, 58] ----------------------------------------------------------------------------- */ map myMap; fcs(myMap, 5, []{ return std::make_pair(rname(), rand() % 60 + 5 ); }); print(myMap, "\n"); string name; int age; try { std::cout << "İsim ve yaş bilgilerini girin: "; std::cin >> name >> age; myMap.at(name) = age; } catch(const std::exception& ex) { std::cout << "Hata yakalandi => " << ex.what() << "\n"; } std::cout << "\n\n"; print(myMap, "\n"); } >>>> '.try_emplace()' fonksiyonu : '.emplace()' fonksiyonu, kendisine geçilen argümanlar ile bir 'std::pair<>' oluşturmakta sonrasında bunu ikili arama ağacına eklemeye çalışmaktadır. Başarılı da olabilir başarısız da. '.try_emplace()' fonksiyonu ise önce ilgili ağaçta 'Key' var mı yok mu diye bakınıyor. Var ise 'std::pair<>' OLUŞTURMUYOR. >> 'Sorted Range Algorithm' : Öyle algoritmalardır ki üzerinde koştuğu 'range' in bir kritere göre sıralanmış olduğunu kabul etmektedir. Sıralanmamış 'range' üzerinde bu algoritmaları çalıştırmak ise 'Tanımsız Davranış' a neden olacaktır. >>> '.binary_search()' : O n(log)n karmaşıklığında, sıralanmış bir 'range' üzerinde 'binary_search' işlemi gerçekleştirmektedir. Varsayılan sıralama kriteri 'std::less' fakat sıralama başka bir şekilde yapıldıysa o kriteri de yine bu fonksiyona argüman olarak geçmemiz gerekmektedir. Var mı yok mu sorgulaması yapar. * Örnek 1, //.. int main() { /* # OUTPUT # antalya artvin bilecik bilecik corum hatay kutahya manisa sivas yozgat ----------------------------------------------------------------------------- Aranacak sehir : yozgat Aranan [yozgat] isimli sehir bulundu... */ std::vector sVec; fcs(sVec, 10, rtown); std::sort(sVec.begin(), sVec.end()); print(sVec); std::string town; std::cout << "Aranacak sehir : "; std::cin >> town; if(std::binary_search(sVec.begin(), sVec.end(), town)) std::cout << "Aranan [" << town << "] isimli sehir bulundu...\n"; else std::cout << "Aranan [" << town << "] isimli sehir bulunamadı...\n"; } * Örnek 2, //.. auto f = [](const std::string& s1, const std::string& s2){ return s1.length() < s2.length() || ( s1.length() == s2.length() && s1 < s2 ) }; int main() { /* # OUTPUT # antalya artvin bilecik bilecik corum hatay kutahya manisa sivas yozgat ----------------------------------------------------------------------------- Aranacak sehir : yozgat Aranan [yozgat] isimli sehir bulundu... */ std::vector sVec; fcs(sVec, 10, rtown); std::sort(sVec.begin(), sVec.end(), f); print(sVec); std::string town; std::cout << "Aranacak sehir : "; std::cin >> town; if(std::binary_search(sVec.begin(), sVec.end(), town, f)) std::cout << "Aranan [" << town << "] isimli sehir bulundu...\n"; else std::cout << "Aranan [" << town << "] isimli sehir bulunamadı...\n"; } >>> 'lower_bound()', 'upper_bound()' ve 'equal_range()' fonksiyonları ki bunlar sırasıyla ilgili değerden büyük-eşit olan ilk değerin konumu, ilgili değerden büyük olan ilk değerin konumu ve bu iki konumun oluşturduğu 'range' şeklindedir. * Örnek 1, //.. int main() { /* # OUTPUT # bartin bolu bolu erzincan igdir kirikkale kirsehir kutahya kutahya van ----------------------------------------------------------------------------- Aranacak sehir : bolu [2] => bolu bolu ----------------------------------------------------------------------------- */ std::vector sVec; fcs(sVec, 10, rtown); std::sort(sVec.begin(), sVec.end()); print(sVec); std::string town; std::cout << "Aranacak sehir : "; std::cin >> town; std::cout << "[" << std::distance(std::lower_bound(sVec.begin(), sVec.end(), town), std::upper_bound(sVec.begin(), sVec.end(), town)) << "] => "; auto bounds = std::equal_range(sVec.begin(), sVec.end(), town); print(bounds.first, bounds.second); } * Örnek 2, sırayı bozmadan bir kaba ekleme yapmak: //.. int main() { /* # OUTPUT # Eklenecek isim: murat => ayhan murat salim turgut ----------------------------------------------------------------------------- Eklenecek isim: kezban => ayhan kezban murat salim turgut ----------------------------------------------------------------------------- Eklenecek isim: melike => ayhan kezban melike murat salim turgut ----------------------------------------------------------------------------- Eklenecek isim: naz => ayhan kezban melike murat naz salim turgut ----------------------------------------------------------------------------- Eklenecek isim: tayfun => ayhan kezban melike murat naz salim tayfun turgut ----------------------------------------------------------------------------- */ std::vector sVec{ "ayhan", "salim", "turgut" }; for(int i = 0; i < 5; ++i) { std::string name = rname(); std::cout << "Eklenecek isim: " << name << " => "; sVec.insert( std::lower_bound( sVec.begin(), sVec.end(),name ), name ); print(sVec); } return 0; } * Örnek 3, sırayı bozmadan bir kaba ekleme yapmak: //.. auto f = [](const std::string& s1, const std::string& s2){ return s1.length() < s2.length() || (s1.length() == s2.length() && s1 < s2); }; int main() { /* # OUTPUT # Eklenecek isim: nalan => ayhan nalan salim turgut ----------------------------------------------------------------------------- Eklenecek isim: adem => adem ayhan nalan salim turgut ----------------------------------------------------------------------------- Eklenecek isim: ceyda => adem ayhan ceyda nalan salim turgut ----------------------------------------------------------------------------- Eklenecek isim: korhan => adem ayhan ceyda nalan salim korhan turgut ----------------------------------------------------------------------------- Eklenecek isim: muzaffer => adem ayhan ceyda nalan salim korhan turgut muzaffer ----------------------------------------------------------------------------- */ std::vector sVec{ "ayhan", "salim", "turgut" }; for(int i = 0; i < 5; ++i) { std::string name = rname(); std::cout << "Eklenecek isim: " << name << " => "; sVec.insert( std::lower_bound( sVec.begin(), sVec.end(),name, f ), name); print(sVec); } return 0; } >>> 'set_union()', 'set_intersection()', 'set_difference()' ve 'set_symmetric_difference()' fonksiyonları sırasıyla Birleşim, Kesişim, Fark ve Simetrik Fark anlamlarında kullanılırlar. Buradaki; >>>> Birleşim, iki kümenin birleşimi manasında. * Örnek 1, //.. int main() { /* # OUTPUT # berk cahide gizem gulden hakan jade kerim lamia murathan nuriye perihan perihan sevilay sinem tanju tugay tuncer tuncer yurdanur zeliha ----------------------------------------------------------------------------- aslican beril binnaz birhan eda edip ediz hulki kasim melisa muslum nagehan necati refika sumeyye taci teoman teslime zarife zekai ----------------------------------------------------------------------------- aslican beril berk binnaz birhan cahide eda edip ediz gizem gulden hakan hulki jade kasim kerim lamia melisa murathan muslum nagehan necati nuriye perihan perihan refika sevilay sinem sumeyye taci tanju teoman teslime tugay tuncer tuncer yurdanur zarife zekai zeliha */ std::vector sVecOne; fcs(sVecOne, 20, rname); std::sort(sVecOne.begin(), sVecOne.end()); print(sVecOne); std::vector sVecTwo; fcs(sVecTwo, 20, rname); std::sort(sVecTwo.begin(), sVecTwo.end()); print(sVecTwo); std::set_union( sVecOne.begin(), sVecOne.end(), sVecTwo.begin(), sVecTwo.end(), std::ostream_iterator{std::cout, " "} ); return 0; } >>>> Kesişim, iki kümenin kesişimi manasında. * Örnek 1, //.. int main() { /* # OUTPUT # anil asim baran fadime fazilet ferhat galip hulya kenan olcay orkun sevda su tansu tarkan tayfun yasar yasar zahide zeliha ----------------------------------------------------------------------------- asim ata beril berk celik esra fahri furkan haluk hilmi kurthan melisa mustafa nagehan nihat sadri sinem temel yalcin zarife ----------------------------------------------------------------------------- asim */ std::vector sVecOne; fcs(sVecOne, 20, rname); std::sort(sVecOne.begin(), sVecOne.end()); print(sVecOne); std::vector sVecTwo; fcs(sVecTwo, 20, rname); std::sort(sVecTwo.begin(), sVecTwo.end()); print(sVecTwo); std::set_intersection( sVecOne.begin(), sVecOne.end(), sVecTwo.begin(), sVecTwo.end(), std::ostream_iterator{std::cout, " "} ); return 0; } >>>> Fark, iki kümenin farkı manasında. * Örnek 1, //.. int main() { /* # OUTPUT # afacan dost durmus durriye gul gunay necmi necmi olcay saadet sadiye sadri sevda seyhan su sumeyye taner taner yilmaz yusuf ----------------------------------------------------------------------------- abdulmuttalip anil burhan celik cemile diana ediz efe enes ferhat gulden hulusi kayahan kazim nazife sevda sezen tansu yasemin yurdanur ----------------------------------------------------------------------------- afacan dost durmus durriye gul gunay necmi necmi olcay saadet sadiye sadri seyhan su sumeyye taner taner yilmaz yusuf */ std::vector sVecOne; fcs(sVecOne, 20, rname); std::sort(sVecOne.begin(), sVecOne.end()); print(sVecOne); std::vector sVecTwo; fcs(sVecTwo, 20, rname); std::sort(sVecTwo.begin(), sVecTwo.end()); print(sVecTwo); std::set_difference( sVecOne.begin(), sVecOne.end(), sVecTwo.begin(), sVecTwo.end(), std::ostream_iterator{std::cout, " "} ); return 0; } >>>> Simetrif Fark, iki kümenin birleşiminden kesişiminin çıkartılması manasında kullanılır. * Örnek 1, //.. int main() { /* # OUTPUT # aslican binnur burak burhan ceylan cezmi dilber dost ece kamil kaya kenan nagehan necati refika sadiye selenay sidre ugur yurdakul ----------------------------------------------------------------------------- atil berivan cemal cemre cengiz erdem esen esra feramuz jade kamile kamile melek nahit nurdan tunc turgut yasemin yeliz zubeyde ----------------------------------------------------------------------------- aslican atil berivan binnur burak burhan cemal cemre cengiz ceylan cezmi dilber dost ece erdem esen esra feramuz jade kamil kamile kamile kaya kenan melek nagehan nahit necati nurdan refika sadiye selenay sidre tunc turgut ugur yasemin yeliz yurdakul zubeyde */ std::vector sVecOne; fcs(sVecOne, 20, rname); std::sort(sVecOne.begin(), sVecOne.end()); print(sVecOne); std::vector sVecTwo; fcs(sVecTwo, 20, rname); std::sort(sVecTwo.begin(), sVecTwo.end()); print(sVecTwo); std::set_symmetric_difference( sVecOne.begin(), sVecOne.end(), sVecTwo.begin(), sVecTwo.end(), std::ostream_iterator{std::cout, " "} ); return 0; } >>> 'next_permutation()' : Bir 'range' içerisindeki bütün kombinasyonları ele alan fonksiyondur. Test fonksiyonu olarak işe yarayabilir. * Örnek 1, //.. int main() { /* # OUTPUT # arda aylin cahit ----------------------------------------------------------------------------- sabriye tevfik yunus ----------------------------------------------------------------------------- arda aylin cahit ----------------------------------------------------------------------------- arda cahit aylin ----------------------------------------------------------------------------- aylin arda cahit ----------------------------------------------------------------------------- aylin cahit arda ----------------------------------------------------------------------------- cahit arda aylin ----------------------------------------------------------------------------- cahit aylin arda ----------------------------------------------------------------------------- Total number of combination : 6 */ std::vector sVecOne; fcs(sVecOne, 3, rname); std::sort(sVecOne.begin(), sVecOne.end()); print(sVecOne); int counter = 0; do { print(sVecOne); ++counter; } while(std::next_permutation(sVecOne.begin(), sVecOne.end())); std::cout << "Total number of combination : " << counter << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # Total number of combination : 24 */ std::vector sVecOne{ "A", "B", "C", "D" }; int counter = 0; do { ++counter; } while(std::next_permutation(sVecOne.begin(), sVecOne.end())); std::cout << "Total number of combination : " << counter << "\n"; return 0; } * Örnek 3, Bizim belirlediğimiz bir sıralama kriteri olsaydı: //.. int main() { /* # OUTPUT # canan sevda fazilet ----------------------------------------------------------------------------- gurbuz rumeysa sadettin ----------------------------------------------------------------------------- canan sevda fazilet ----------------------------------------------------------------------------- canan fazilet sevda ----------------------------------------------------------------------------- sevda canan fazilet ----------------------------------------------------------------------------- sevda fazilet canan ----------------------------------------------------------------------------- fazilet canan sevda ----------------------------------------------------------------------------- fazilet sevda canan ----------------------------------------------------------------------------- Total number of combination : 6 */ std::vector sVecOne; fcs(sVecOne, 3, rname); std::sort(sVecOne.begin(), sVecOne.end(), f); print(sVecOne); int counter = 0; do { print(sVecOne); ++counter; } while(std::next_permutation(sVecOne.begin(), sVecOne.end(), f)); std::cout << "Total number of combination : " << counter << "\n"; return 0; } >> 'Unordered Associative Containers' : Diğer programlama dillerinde 'hash-set', 'hash-map' diye isimlendirilen veri yapılarının C++ dilindeki karşılığıdır. 'Hash-table' veri yapılarıdır. >>> 'Hash-table' veri yapısı: Bir diziye elemanlar yerleştirsek ve bu dizide bir arama yapsak bu 'O(n)' karmaşıklığında olacaktı. Sıralı bir dizide 'binary_search' yaparsak da 'O(log(n))' karmaşıklığında olacaktır. İşte 'hash-table' ise bu ikisi arasında bir karmaşıklık sunmaktadır. Burada aramaya konu olan değer bir indise dönüştürülür, ki buna 'hashing' denir, ve ilgili indis kapta aranır. Bu 'hashing' işlemini yerine getiren fonksiyonlara da 'hash function' denmektedir. Bu fonksiyon ne kadar iyiyse farklı değerleri farklı indislere dönüştürmektedir. Velevki farklı değerleri aynı indise dönüştürürse, bu duruma da 'Collision' denmektedir. Fakat unutulmamalıdır ki ilgili 'hash-function' ne kadar iyi olursa olsun 'collision' kaçınılmazdır. Peki bizler bu 'collision' ihtimalini nasıl minimize edebiliriz? El-cevap: Veri Yapıları ve Algoritmalar biliminin konusu. * Örnek 1, Aramaya Konu Olan Değer => HashFunction => İndis Ahmet =================> 4 Merve =================> 2 Aylin =================> 6 Ulya =================> 4 // 'Collision' gerçekleşti. >>>> Arka planda ise elemanları bağlı liste olan bir vektör bulunmaktadır. İlgili 'hash-function' sonucu elde edilen indis vektörde aranmakta ve erişilmekte. Eğer 'collision' olmuş ise bu sefer de o indisteki bağlı listede birden fazla düğümler meydana gelmekte. Bu da bizim tekrardan o düğümlerde arama yapmamızı gerektirmekte. * Örnek 1, temsili 'unordered-set' Vektör Bağlı Liste [0] => [AAA] Girdi => HashFunction [1] => [BBB] -> [bbb] [2] => [CCC] -> [ccc] -> [CcC] * Örnek 2, temsili 'unordered-map' Vektör Bağlı Liste [0] => [AAA - 111] Girdi => HashFunction [1] => [BBB - 222] -> [bbb - 333] [2] => [CCC - 444] -> [ccc - 555] -> [CcC - 666] >>>> Standart kütüphanedeki 'primitive' türler ve standart kütüphanedeki sınıf şablonları için standart bir 'hash-function' mevcuttur. Kendi sınıf türlerimizi eğer bu veri yapısında tutmak istiyorsak, ilgili 'hash-function' ları bizler yazmalıyız. >>> 'unoredered_set' ve 'unoredered_map': >>>> İş bu sınıf şablonları sırasıyla 'std::set' ve 'std::map' sınıf şablonlarına çok benzemektedir. Fakat arka plandaki veri yapısının BİRBİRİNDEN FARKLI OLDUĞUNU UNUTMAYALIM. BİRİSİNDE 'BINARY TREE' KULLANILIRKEN, DİĞERİNDE 'HASH TABLE' KULLANILMAKTADIR. >>>> Yine 'hash table' veri yapısındaki karşılaştırma kriteri '==' operatörü ile yapılırken, 'binary tree' veri yapısında '<' operatörü kullanılmakta. Buradan hareketle diyebiliriz ki iş bu kapta tutacağımız sınıf türleri için '.operator<()' fonksiyonunu yazmak lüzumsuz veya yeterli bilgiye sahip değilsek veya lojik açıdan da mümkün değilse ama '.operator==()' fonksiyonunu yazmak mantıklı ise 'Binary Tree' yerine 'Hash Table' kullanmalıyız. Buradan da diyebiliriz ki 'std::set' / 'std::map' sınıf şablonlarında karşılaştırma 'equivalence' ile fakat 'std::unordered_set' / 'std::unordered_map' sınıf şablonlarında ise 'equity' ile yapılmakta. >>>> Geçmişte yazılan kodlar ile çakışmaması adına bu garip isim seçildi. Aslında bu kaplar birer 'hash_set' ve 'hash_map' kaplarıdır. Fakat bildirimleri sırasıyla 'unordered_set' ve 'unordered_map' başlık dosyasındadır. >>>> İlgili sınıf şablonunun ikinci parametresi olan 'hasher' incelenmesi: * Örnek 1, standart kütüphanedeki 'std::hash' ve bizim yazdığımız 'hash' sınıfları: //.. template struct myHash { size_t operator()(const T& other){ return other.size() * 2 / 4; } }; int main() { /* # OUTPUT # [ahmet] => 6192890985721302765 [ahmet] => 2 */ std::string keyToHash = "ahmet"; std::cout << "[" << keyToHash << "] => " << std::hash{}(keyToHash) << "\n"; std::cout << "[" << keyToHash << "] => " << myHash{}(keyToHash) << "\n"; return 0; // Çıktıda görülen bu değerler, ilgili sınıf şablonu tarafından tekrardan indis bilgisine dönüştürülecektir. } * Örnek 2, standart kütüphanedeki 'std::hash' fonksiyonunun bizim sınıfımız için özelleştirilmesi: //.. // İlgili 'hash' fonksiyonumuz 'std' isim alanında olduğundan, yazacağımız özelleştirilmiş şablon da o isim alanında olmalı. // Fakat bu tip 'std' için şablon türetmek haricinde 'std' için şeyler yazmak 'Tanımsız Davranış' olur. namespace std { template<> // 'explicit specialization' struct hash{ size_t operator()(const Date& date) { /* Boost kütüphanesinin 'hash' fonksiyonu bu tip 'custom' türler için ideal. */ // Alternatif - I return std::hash{}(date.month_day()) + std::hash{}(date.month()) + std::hash{}(date.year()); } }; } int main() { /* # OUTPUT # [17 Eylul 1993 Cuma] => 2019 */ Date keyToHash(17, 9, 1993); std::cout << "[" << keyToHash << "] => " << std::hash{}(keyToHash) << "\n"; // 'std::hash' fonksiyonu için 'Date' açılımı standart kütüphanede olmadığından // bizler 'explicit specialization' yazmalıyız. // BİZLER BURADA STANDART OLAN 'std::hash' FONKSİYONUNU KULLANMAK İSTEDİĞİMİZ İÇİN ÖZELLEŞTİRME YAPTIK. // BU FONKSİYON YERİNE KENDİMİZ BİR 'functor' DA OLUŞTURABİLİRDİK. std::unordered_set mySet; // Yukarıdaki özelleştirme yapıldığı için artık burası SENTAKS HATASI DEĞİL. return 0; } * Örnek 3, kendi sınıfımız için 'custom-hasher' kullanılması. //.. struct DateHasher{ size_t operator()(const Date& date) { /* Boost kütüphanesinin 'hash' fonksiyonu bu tip 'custom' türler için ideal. */ // Alternatif - I return std::hash{}(date.month_day()) + std::hash{}(date.month()) + std::hash{}(date.year()); } }; int main() { std::unordered_set mySet; // Yukarıdaki 'functor' yazıldığı için artık burası SENTAKS HATASI DEĞİL. return 0; } * Örnek 4, kendi sınıfımız için 'custom-hasher' kullanılması. //.. auto dateHasher = [](const Date& date){ return std::hash{}(date.month_day()) + std::hash{}(date.month()) + std::hash{}(date.year()); }; int main() { std::unordered_set mySet; // Yukarıdaki 'lambda-expression' yazıldığı için artık burası SENTAKS HATASI DEĞİL. // Ayrıca bu kodun C++20 ile derlenmesi gerekiyor çünkü 'lambda-expressions' için 'default ctor.' // 'delete' EDİLMİŞ DEĞİL. std::unordered_set mySetTwo(100, dateHasher); // C++20 öncesi return 0; } >>>> İlgili sınıf şablonunun üçüncü parametresi olan karşılaştırma kriterinin incelenmesi: * Örnek 1, //.. class myClass{}; namespace std{ template<> // 'explicit specialization' struct hash{ size_t operator()(const myClass& other){ return 31; } }; } int main() { /* # OUTPUT # error: no match for ‘operator==’ (operand types are ‘const myClass’ and ‘const myClass’) */ std::unordered_set> mySet; mySet.insert(myClass{}); // Sentaks hatası çünkü üçüncü şablon parametresi varsayılan argüman olarak 'std::equal_to' kullanmakta fakat // ilgili 'functor' bizim sınıfımız için tanımlı DEĞİL. return 0; } * Örnek 2, //.. class myClass{}; namespace std{ template<> // 'explicit specialization' struct hash{ size_t operator()(const myClass& other) const { return 31; } }; } const bool operator==(const myClass& m1, const myClass& m2) { return m1==m2; } int main() { /* # OUTPUT # */ std::unordered_set > mySet; mySet.insert(myClass{}); return 0; } * Örnek 3, //.. // Custom Hasher struct DateHasher{ size_t operator()(const Date& date)const{ return std::hash{}(date.month_day()) + std::hash{}(date.month()) + std::hash{}(date.year()); } }; // Custom Comparator struct DateEqual{ bool operator()(const Date& dateOne, const Date& dateTwo)const{ return dateOne == dateTwo; } // İlgili 'Date' sınıfı '.operator==()' fonksiyonunu 'overload' etmiştir. }; int main() { /* # OUTPUT # */ Date myDate(17, 9, 1993); std::unordered_set mySet; mySet.insert(myDate); return 0; } * Örnek 4, //.. // Custom class class myClass{}; // Custom Hasher struct DateHasher{ size_t operator()(const myClass& date)const{ return 31; } }; // Custom Comparator struct DateEqual{ bool operator()(const myClass& dateOne, const myClass& dateTwo)const{ return true; } // İlgili 'myClass' sınıfı '.operator==()' fonksiyonunu 'overload' etmediğinden, bu şekilde yazıldı. }; int main() { /* # OUTPUT # */ myClass myDate; std::unordered_set mySet; mySet.insert(myDate); return 0; } >>>> Unutulmamalıdır ki bu veri yapısında herhangi bir sıralama da söz konusu değildir. Yani öğeler eklenirken bir sıra gözetilmezler. >>>> 'Hash-table' bünyesinde bulunan ilgili vektörün her bir indise aslında 'bucket' denmektedir. Dolayısıyla bu 'bucket' adedi performansı etkileyen en önemli etkenlerden birisidir. >>>> 'std::set' / 'std::map' sınıf şablonlarında olmayan fonksiyonlar: >>>>> '.hash_function()' : İlgili fonksiyonu döndüren üye fonksiyondur. * Örnek 1, //.. int main() { /* # OUTPUT # ahmet emine handan galip ercument cebrail garo alparslan berivan zubeyde ----------------------------------------------------------------------------- The Hasher : St4hashINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE */ std::unordered_set mySet; fcs(mySet, 10, rname); print(mySet); auto hashFunc = mySet.hash_function(); std::cout << "The Hasher : " << typeid(hashFunc).name() << "\n"; return 0; } >>>>> '.bucket_count()' : İlgili kaptaki 'bucket' sayısını döndürmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # Size : 46 Bucket Size : 103 */ std::unordered_set mySet(100); fcs(mySet, 50, rname); std::cout << "Size : " << mySet.size() << "\n"; std::cout << "Bucket Size : " << mySet.bucket_count() << "\n"; return 0; } >>>>> '.load_factor()' : Kapta tutulan öğe sayısının 'bucket' sayısına oranını döndürmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # Size : 47 Bucket Size : 103 Load Factor : 0.456311 Load Factor : 0.456311 */ std::unordered_set mySet(100); fcs(mySet, 50, rname); std::cout << "Size : " << mySet.size() << "\n"; std::cout << "Bucket Size : " << mySet.bucket_count() << "\n"; std::cout << "Load Factor : " << mySet.load_factor() << "\n"; std::cout << "Load Factor : " << static_cast(mySet.size()) / mySet.bucket_count() << "\n"; return 0; } >>>>> '.max_load_factor' : Öyle bir 'load_factor' oranı ki o orana geldiğinde 'rehash' yapılacak. Yani yeni 'bucket' eklenecek ve 'hash table' yeniden düzenlenecek. Bu oranı kendimiz de belirleyebiliyoruz. * Örnek 1, //.. int main() { /* # OUTPUT # Size : 47 Bucket Size : 103 Load Factor : 0.456311 Load Factor : 0.456311 Max Load Factor : 1 Max Load Factor : 2.31 */ std::unordered_set mySet(100); fcs(mySet, 50, rname); std::cout << "Size : " << mySet.size() << "\n"; std::cout << "Bucket Size : " << mySet.bucket_count() << "\n"; std::cout << "Load Factor : " << mySet.load_factor() << "\n"; std::cout << "Load Factor : " << static_cast(mySet.size()) / mySet.bucket_count() << "\n"; std::cout << "Max Load Factor : " << mySet.max_load_factor() << "\n"; mySet.max_load_factor(2.31); std::cout << "Max Load Factor : " << mySet.max_load_factor() << "\n"; return 0; } >>>>> '.bucket_size()' : İlgili 'bucket' içerisindeki eleman sayısını döndürmektedir. >>>>> '.rehash()' : 'bucket' sayısını arttırmak için kullanılır. Tekrardan 'rehash' işlemi yapmaktadır. >>>>> Pekiştirici örnek, //.. int main() { /* # OUTPUT # Size : 41 Bucket Size : 103 Load Factor : 0.398058 Load Factor : 0.398058 Max Load Factor : 1 [ 0], (0) => [ 1], (0) => [ 2], (0) => [ 3], (1) => yeliz [ 4], (0) => [ 5], (2) => pinat fahri [ 6], (1) => muruvvet [ 7], (1) => hakki [ 8], (0) => [ 9], (0) => [ 10], (2) => azize muslum [ 11], (1) => tarik [ 12], (0) => [ 13], (0) => [ 14], (2) => petek burak [ 15], (0) => [ 16], (1) => bulent [ 17], (1) => cemile [ 18], (0) => [ 19], (0) => [ 20], (1) => orkun [ 21], (1) => sadiye [ 22], (0) => [ 23], (0) => [ 24], (1) => izzet [ 25], (1) => emirhan [ 26], (0) => [ 27], (0) => [ 28], (0) => [ 29], (0) => [ 30], (0) => [ 31], (0) => [ 32], (0) => [ 33], (1) => nuriye [ 34], (0) => [ 35], (0) => [ 36], (0) => [ 37], (1) => kasim [ 38], (0) => [ 39], (0) => [ 40], (2) => tarkan hulusi [ 41], (0) => [ 42], (0) => [ 43], (0) => [ 44], (2) => lale leyla [ 45], (0) => [ 46], (0) => [ 47], (0) => [ 48], (0) => [ 49], (0) => [ 50], (0) => [ 51], (0) => [ 52], (0) => [ 53], (0) => [ 54], (0) => [ 55], (1) => murathan [ 56], (1) => turhan [ 57], (0) => [ 58], (0) => [ 59], (0) => [ 60], (0) => [ 61], (0) => [ 62], (0) => [ 63], (1) => recep [ 64], (1) => sadi [ 65], (1) => bilal [ 66], (0) => [ 67], (1) => abdullah [ 68], (1) => semsit [ 69], (1) => ercument [ 70], (1) => tijen [ 71], (0) => [ 72], (1) => yurdanur [ 73], (0) => [ 74], (0) => [ 75], (2) => nusret yelda [ 76], (1) => selenay [ 77], (0) => [ 78], (0) => [ 79], (0) => [ 80], (0) => [ 81], (0) => [ 82], (0) => [ 83], (0) => [ 84], (1) => adnan [ 85], (0) => [ 86], (0) => [ 87], (0) => [ 88], (0) => [ 89], (0) => [ 90], (0) => [ 91], (0) => [ 92], (0) => [ 93], (1) => sinem [ 94], (0) => [ 95], (0) => [ 96], (0) => [ 97], (1) => turgut [ 98], (2) => egemen perihan [ 99], (0) => [100], (1) => feraye [101], (0) => [102], (0) => */ std::unordered_set mySet(100); fcs(mySet, 50, rname); std::cout << "Size : " << mySet.size() << "\n"; std::cout << "Bucket Size : " << mySet.bucket_count() << "\n"; std::cout << "Load Factor : " << mySet.load_factor() << "\n"; std::cout << "Load Factor : " << static_cast(mySet.size()) / mySet.bucket_count() << "\n"; std::cout << "Max Load Factor : " << mySet.max_load_factor() << "\n"; for(size_t i{}; i < mySet.bucket_count(); ++i) { std::cout << "[" << std::setw(3) << i << "], "; std::cout << "(" << mySet.bucket_size(i) << ") => "; for(auto iter = mySet.cbegin(i); iter != mySet.cend(i); ++iter) { std::cout << *iter << " "; } std::cout << "\n"; } return 0; } >>>> Diğer 'Associative' kaplardaki sınıf şablonlarına ek olarak bu sınıf şablonlarının 'Ctor.' fonksiyonları, argüman olarak, 'bucket' sayısı almaktadır. >> 'all_of()', 'any_of()', 'none_of()' fonksiyonları bir 'range' içerisindeki öğeleri bir kriteri karşılayıp karşılamadığını sınamaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # */ int a[]{ 2, 6, 8, 10, 20, 60, 70, 90, 23, 50 }; // Bütün elemanlar kriteri karşılıyor mu? std::cout << std::boolalpha << ( std::all_of(std::begin(a), std::end(a), [](int a){ return a % 2 == 0; }) ) << "\n"; // OUTPUT => false // Bütün elemanlar ktiteri karşılamıyor mu? std::cout << std::boolalpha << ( std::none_of(std::begin(a), std::end(a), [](int a){ return a % 2 == 0; }) ) << "\n"; // OUTPUT => false // Elemanlardan kriteri karşılayan var mı? std::cout << std::boolalpha << ( std::any_of(std::begin(a), std::end(a), [](int a){ return a % 2 == 0; }) ) << "\n"; // OUTPUT => true return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # */ std::vector a; // Bütün elemanlar kriteri karşılıyor mu? : Olmayan öğe için çift desek yanlış bir önerme olmaz. std::cout << std::boolalpha << ( std::all_of(std::begin(a), std::end(a), [](int a){ return a % 2 == 0; }) ) << "\n"; // OUTPUT => true // Bütün elemanlar ktiteri karşılamıyor mu? : Olmayan öğe için çift desek yanlış bir önerme olmaz. std::cout << std::boolalpha << ( std::none_of(std::begin(a), std::end(a), [](int a){ return a % 2 == 0; }) ) << "\n"; // OUTPUT => true // Elemanlardan kriteri karşılayan var mı? : Hiç eleman yok ki hangisi çift olsun. std::cout << std::boolalpha << ( std::any_of(std::begin(a), std::end(a), [](int a){ return a % 2 == 0; }) ) << "\n"; // OUTPUT => false return 0; } > Mülakat Sorusu : Bir vektördeki sonuncu olmayan bir öğenin 'constant time' zaman karmaşıklığında silinmesi: //.. int main() { /* # OUTPUT # emine galip busra korhan bulent bilgin gul tayfun izzet izzet ----------------------------------------------------------------------------- Silinecek indis : 2 emine galip izzet korhan bulent bilgin gul tayfun izzet busra ----------------------------------------------------------------------------- */ std::vector sVec; fcs(sVec, 10, rname); print(sVec); int n; std::cout << "Silinecek indis : "; std::cin >> n; std::swap(sVec[n], sVec.back()); // İlgili indis ile son öğenin yerini değiştirdik. print(sVec); } /*============================================================================================================*/ (31_02_01_2021) > 'STL' içerisindeki kaplar (devam) : >> 'Unordered Associative Containers' (devam) : >>> 'unoredered_set' ve 'unoredered_map' (devam): >>>> Kendi 'custom' türler için 'hash-function' yazılması: * Örnek 1, //.. template inline void hash_combine(std::size_t& seed, const T& val) { seed ^= std::hash()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } template inline void hash_val(std::size_t& seed, const T& val) { hash_combine(seed, val); } template inline void hash_val(std::size_t& seed, const T& val, const Types&... args) { hash_combine(seed, val); hash_val(seed, args...); } // Boost kütüphanesindeki 'hash' fonksiyonu: template inline std::size_t hash_val(const Types&... args) { std::size_t seed = 0; hash_val(seed, args...); return seed; } namespace std { template<> // 'explicit specialization' struct hash{ size_t operator()(const Date& date) { /* Boost kütüphanesinin 'hash' fonksiyonu bu tip 'custom' türler için ideal. */ // Alternatif - I return std::hash{}(date.month_day()) + std::hash{}(date.month()) + std::hash{}(date.year()); } }; } int main() { /* # OUTPUT # [17 Eylul 1993 Cuma] => 2019 [17 Eylul 1993 Cuma] => 11093822750367 */ Date keyToHash(17, 9, 1993); std::cout << "[" << keyToHash << "] => " << std::hash{}(keyToHash) << "\n"; // Standart 'std::hash' fonksiyonunun 'Date' sınıfı için özelleştirilmiş versiyonu çağrıldı. std::cout << "[" << keyToHash << "] => " << hash_val(17, 9, 1993) << "\n"; // 'Boost' kütüphanesindeki versiyonu çağrıldı. std::unordered_set mySet; return 0; } >> 'std::array' : C dilindeki dizileri sarmalayan sınıf şablonlarıdır. Böylelikle 'array-decay' mekanizmasının yol açacağı problemler önlenmiş olacaktır. Bunun haricinde iki dizi birbirine atanamamaktadır fakat bu sınıf türünden iki obje birbirine atanabilmektedir. >>> 'array-decay' : Bir dizi isminin bir ifade içerisinde kullanılması sonucu, dizinin ilk elemanının adresine dönüşmesidir. Bir kaç istisnai senaryo haricinde bu mekanizma çalışmamaktadır. Bunlar 'sizeof()' ve '&' operatörlerine operand olmaları, 'decltype' ve 'auto&' ile tür çıkarımında bu mekanizma çalışmaz. * Örnek 1, //.. int main() { int a[10]; // ifade ifadenin türü // &a => int(*)[10] // İlgili mekanizma burada çalışmamıştır. Dizinin ilk elemanının adresine dönüşmemiştir. Çünkü dönüşüm // gerçekleşseydi, 'R-Value Expression' haline gelecekti fakat '&' operatörü 'L-Value Expression' kabul // ettiğinden dolayı sentaks hatası olacaktı. // &a[0] => (int*) // İlgili mekanizma burada çalışmamıştır. Dizinin ilk elemanının adres bilgisini elde ediyoruz. // Array-Decay // int(*)[10] => (int*) // Yukarıdaki ifadelerden: // i. '&a + 1' : Gösterici aritmetiğine göre 'sizeof(int) * 10' kadar artacaktır. // Yani ikinci bir on elemanlı dizi gösterir olacak. // ii. '&a[0] + 1' : Gösterici aritmetiğine göre 'sizeof(int)' kadar artacaktır. // Yani bir sonraki indisteki öğeyi gösterecektir. } >>> 'Default Init.' gerçekleştirdiğimiz zaman bütün öğeleri çöp değer ile hayata gelirken 'Value Init.' ettiğimiz zaman elemanları 'Zero Init.' ile hayata gelmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # 4 0 703554653 21852 -566558776 0 0 0 0 0 */ std::array arx; // 'Default Init.' std::array ary{}; // 'Value Init.' for(size_t i{}; i < arx.size(); ++i) std::cout << arx[i] << " "; std::cout << "\n"; for(size_t i{}; i < ary.size(); ++i) std::cout << ary[i] << " "; return 0; } >>> Bu sınıf şablonu bir 'aggregiate type' olduğu için 'aggregiate init.' uygulayabilirim. * Örnek 1, //.. int main() { /* # OUTPUT # 1 3 5 7 9 */ std::array arx{ 1, 3, 5, 7, 9 }; // 'Aggregiate Init.' for(size_t i{}; i < arx.size(); ++i) std::cout << arx[i] << " "; std::cout << "\n"; return 0; } >>> Standart çıkış akımına yazmak için sınıf şablonunda standart bir üye fonksiyon yoktur. Temsili olarak bizler yazabiliriz: * Örnek 1, //.. template std::ostream& operator<<(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } int main() { /* # OUTPUT # [ 1, 3, 5, 7, 9 ] */ std::array arx{ 1, 3, 5, 7, 9 }; // 'Aggregiate Init.' std::cout << arx << "\n"; return 0; } >>> Sınıfın üye fonksiyonu olan '.data()' ile veya global fonksiyon olan 'data()' fonksiyon ile bu sınıf şablonunu C fonksiyonlarında da kullanabilirim. * Örnek 1, //.. template std::ostream& operator<<(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } void printArray(const int* p, size_t size) { while(size--) printf("%d ", *p++); printf("\n"); } int main() { /* # OUTPUT # I: [ 1, 3, 5, 7, 9 ] II: 1 3 5 7 9 III: 1 3 5 7 9 IV: 1 3 5 7 9 */ std::array arx{ 1, 3, 5, 7, 9 }; // 'Aggregiate Init.' std::cout << arx << "\n"; // I printArray(arx.data(), arx.size()); // II printArray(&arx[0], arx.size()); // III printArray(&*arx.begin(), arx.size()); // IV return 0; } >>> Türleri aynı olmak şartıyla bu türden nesneler de birbirine atanabilmektedir/taşınabilmektedir. * Örnek 1, //.. template std::ostream& operator<<(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } int main() { /* # OUTPUT # [ 1, 3, 5, 7, 9 ] [ 0, 0, -74250752, 22029, -1976765216 ] [ 1, 3, 5, 7, 9 ] */ std::array arx{ 1, 3, 5, 7, 9 }; std::cout << arx << "\n"; std::array ary; std::cout << ary << "\n"; ary = arx; // ary = std::move(arx); std::cout << ary << "\n"; return 0; } >>> Boyutu '0' olan bu türden bir sınıf nesnesi tanımlanabilir. * Örnek 1, //.. template std::ostream& operator<<(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } void printArray(const int* p, size_t size) { while(size--) printf("%d ", *p++); printf("\n"); } int main() { /* # OUTPUT # */ int a[0]; printArray(a, 0); std::array ary; std::cout << ary << "\n"; return 0; } >>> C dizilerini barındırdığı için ekleme ve silme fonksiyonları mevcut değildir. >>> Sırf bu kaba özel bir üye fonksiyon yoktur. Fakat 'STL' uyumlu bir sınıf şablonudur. * Örnek 1, //.. template void printArray(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; } void printArray(const int* p, size_t size) { printf("[ %d, ", *p); --size; while(--size) printf("%d, ", *++p); printf("%d ]\n", *++p); } int main() { /* # OUTPUT # [ 5, 4, 3, 2, 1 ] [ 10, 9, 8, 7, 6 ] 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < */ int a[5]{ 5, 4, 3, 2, 1 }; printArray(a, 5); std::sort(std::begin(a), std::end(a)); std::array ary{ 10, 9, 8, 7, 6 }; printArray(std::cout, ary); std::sort(ary.begin(), ary.end()); std::copy(std::begin(a), std::end(a), std::ostream_iterator{std::cout, " < "}); std::copy(ary.begin(), ary.end(), std::ostream_iterator{std::cout, " < "}); return 0; } >>> 'Structural Binding' mekanizması da kullanılabilir. * Örnek 1, //.. int main() { /* # OUTPUT # a : 10 ... e : 6 */ std::array arx{ 10, 9, 8, 7, 6 }; const auto& [ a, b, c, d, e] = arx; std::cout << "a : " << a << "\n...\n"; std::cout << "e : " << e << "\n"; return 0; } * Örnek 2, //.. std::array foo(){ return { 10, 11, 12, 13, 14 }; } int main() { /* # OUTPUT # a : 10 ... e : 14 */ const auto& [ a, b, c, d, e] = foo(); std::cout << "a : " << a << "\n...\n"; std::cout << "e : " << e << "\n"; return 0; } >>> 'std::tuple' ye ait 'interface' ye de destek vermektedir. * Örnek 1, //.. template std::ostream& operator<<(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } int main() { /* # OUTPUT # [ 10, 9, 8, 7, 6 ] [ 40, 10, 7, 14, 2 ] */ std::array arx{ 10, 9, 8, 7, 6 }; std::cout << arx; // tuple-interface std::get<0>(arx) = 40; ++std::get<1>(arx); --std::get<2>(arx); std::get<3>(arx) *= 2; std::get<4>(arx) /= 3; std::cout << arx; return 0; } >>> İki boyutlu dizi oluşturmak için de kullanılabilir. * Örnek 1, //.. template std::ostream& operator<<(std::ostream& os, std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } int main() { /* # OUTPUT # [ [ 1, 2, 3 ] , [ 4, 5, 6 ] , [ 7, 8, 9 ] , [ 10, 11, 12 ] ] */ std::array, 4> array2D{ { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10, 11, 12 } } }; std::cout << array2D; return 0; } >>> Bu kapta da 'lexicographical_compare' kullanılmaktadır. >> 'Reference Wrapper' sınıf şablonunun incelenmesi: Normalde referanslar 're-bind' edilemiyorlar, bir kapta eleman olarak tutulamıyorlar. İşte bu gibi sıkıntıları bertaraf etmek için oluşturulan ve arka planda bir gösterici tutan sınıf şablonudur. >>> Temsili implementasyonu: * Örnek 1, //.. template class ReferenceWrapper{ public: ReferenceWrapper(T& other) : _mp{&other} {} ReferenceWrapper& operator=(T& other) { _mp = &other; return *this; } operator T& () { return *_mp; } // 'T' hangi türden ise bu fonksiyon ise o türden bir referansa dönüştürecektir, iş bu sınıf nesnesini. T& get() { return *_mp; } // A getter. private: T* _mp; }; template ReferenceWrapper Ref(T& other) { return ReferenceWrapper{other}; } int main() { int x = 10; ReferenceWrapper r = x; // '_mp' isimli gösterici, 'x' değişkeninin adresini tutmaktadır. // ReferenceWrapper r = x; // CTAD ile tür çıkarımı yapıldı. int y = 20; r = y; // '_mp' isimli gösterici, 'y' değişkeninin adresini tutmaktadır. int z = r; // '_mp' isimli göstericinin gösterdiği değeri, 'z' değişkenine atadık. // int z = r.operator int& (); auto y = Ref(x); } >>> Bünyesindeki fonksiyonların incelenmesi: * Örnek 1, //.. int main() { /* # OUTPUT # r : 133 r : 778 */ int x{ 132 }, y{ 777 }; std::reference_wrapper r{x}; // 'r' demek 'x' demek. ++r; // r.operator int&(); std::cout << "r : " << r.get() << "\n"; r = y; // 'r' demek 'y' demek. ++r; // r.operator int&(); std::cout << "r : " << r.get() << "\n"; return 0; } * Örnek 2, //.. int main() { int x{ 132 }; auto y = x; // 'y' is 'int'. auto z = std::ref(x); // 'z' is 'std::reference_wrapper' return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # a : 10 a : 11 */ int a = 10; func(a); // 'T' is 'int' std::cout << "a : " << a << "\n"; func(std::ref(a)); // 'T' is 'std::reference_wrapper' std::cout << "a : " << a << "\n"; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # a: 10 d: 2.3 a: 11 d: 1.3 */ int a = 10; double d = 2.3; auto p = std::make_pair( a, d ); ++p.first; --p.second; std::cout << "a: " << a << "\n"; std::cout << "d: " << d << "\n"; auto pRef = std::make_pair( std::ref(a), std::ref(d) ); ++pRef.first; --pRef.second; std::cout << "a: " << a << "\n"; std::cout << "d: " << d << "\n"; return 0; } >>>> Okuma amaçlı olarak, 'std::ref()' yerine 'std::cref()' sarmalamasını kullanabiliriz. * Örnek 5, //.. class Biggie{ public: //.. bool operator()(int i)const { return true; } //.. }; const Biggie gBig; std::vector iVecOne; int main() { /* # OUTPUT # */ std::vector iVecTwo; std::copy_if(iVecOne.begin(), iVecOne.end(), std::back_inserter(iVecTwo), ref(gBig)); // Burada son argüman olan 'gBig' referans yolu ile gönderilmeseydi, ilgili 'copy_if' fonksiyonuna kopyalanacaktı. return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # a : 11 b : 12 c : 13 d : 14 */ int a = 10, b = 11, c = 12, d = 13; // std::vector iVecOne{ a, b, c, d }; // Sentaks hatası. std::vector> iVecOne{ a, b, c, d }; // İlgili kaptaki her bir öğe sırasıyla 'a', 'b', 'c' ve 'd' değişkenlerine referanstır. ++iVecOne.at(0); std::cout << "a : " << a << "\n"; ++iVecOne.at(1); std::cout << "b : " << b << "\n"; ++iVecOne.at(2); std::cout << "c : " << c << "\n"; ++iVecOne.at(3); std::cout << "d : " << d << "\n"; return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # derin derya onat ufuk fadime sezai kamile beril ediz cihan cebrail tarik necmi emine hulusi muslum melisa sefa alican handesu ----------------------------------------------------------------------------- kamilecan alicancan handesucan muslumcan edizcan fadimecan melisacan tarikcan ufukcan eminecan onatcan necmican berilcan derincan sezaican hulusican cebrailcan deryacan cihancan sefacan ----------------------------------------------------------------------------- derincan deryacan onatcan ufukcan fadimecan sezaican kamilecan berilcan edizcan cihancan cebrailcan tarikcan necmican eminecan hulusican muslumcan melisacan sefacan alicancan handesucan ----------------------------------------------------------------------------- */ std::list sList; fcs(sList, 20, rname); print(sList); // Herhangi bir sebepten ötürü bizlerin 'random_access_iterator' kullanma ihtiyacımız olsun: std::vector> sVec{ sList.begin(), sList.end() }; // Artık 'sVec' kabındaki her öğe dolaylı yoldan 'sList' kabındaki öğelere referans. std::shuffle(sVec.begin(), sVec.end(), std::mt19937{ std::random_device{}() }); for(const auto& index : sVec) { std::cout << (index.get() += "can") << " "; } std::cout << "\n------------------------------------------\n"; print(sList); return 0; } >> 'std::tuple' sınıf şablonunun incelenmesi: 'std::tuple' başlık dosyasında bildirilmiştir. 'std::pair' sınıfının çoklusu şeklinde düşünülebilir. Variyadik bir sınıf şablonudur. Yine 'reference_wrapper' gibi bir 'get' arayüzüne sahiptir. İş bu arayüz hem yazma hem okuma amaçlı kullanılabilir. * Örnek 1, //.. int main() { /* # OUTPUT # 0 0 0 10 10.01 10.01 */ std::tuple myTuple; std::cout << get<0>(myTuple) << std::endl; std::cout << get<1>(myTuple) << std::endl; std::cout << get<2>(myTuple) << std::endl; get<0>(myTuple) = 10; get<1>(myTuple) = 10.01f; get<2>(myTuple) = 10.01; std::cout << get<0>(myTuple) << std::endl; std::cout << get<1>(myTuple) << std::endl; std::cout << get<2>(myTuple) << std::endl; int ival = 0; get(myTuple) = 20; std::cout << get<0>(myTuple) << std::endl; // error: the value of ‘ival’ is not usable in a constant expression // Hata kodundan da anlaşılacağı üzere bizim bir 'constant expression' a ihtiyacımız vardır. } * Örnek 2, //.. int main() { /* # OUTPUT # 10 10.01 10.01 */ std::tuple myTuple{ 10, 10.01f, 10.01 }; // std::tuple myTuple{ 10, 10.01f, 10.01 }; // CTAD : 'int', 'float' ve 'double' açılımı gerçekleşecektir. // std::tuple myTuple = { 10, 10.01f, 10.01 }; // CTAD : 'int', 'float' ve 'double' açılımı gerçekleşecektir. std::cout << get<0>(myTuple) << std::endl; std::cout << get<1>(myTuple) << std::endl; std::cout << get<2>(myTuple) << std::endl; } * Örnek 3, //.. constexpr int indexer(int x) { return x; } int main() { /* # OUTPUT # 30 30.03 30.03 */ std::tuple myTuple{ 30, 30.03f, 30.03 }; std::cout << get(myTuple) << std::endl; std::cout << get(myTuple) << std::endl; std::cout << get(myTuple) << std::endl; } * Örnek 4, //.. int main() { /* # OUTPUT # 40 40.04 40.04 */ std::tuple myTuple{ 40, 40.04f, 40.04 }; std::cout << get(myTuple) << std::endl; std::cout << get(myTuple) << std::endl; std::cout << get(myTuple) << std::endl; std::tuple myTupleTwo{ 1, 2, 3 }; std::cout << get(myTupleTwo) << std::endl; // ARTIK SENTAKS HATASI. std::cout << get(myTupleTwo) << std::endl; // ARTIK SENTAKS HATASI. std::cout << get(myTupleTwo) << std::endl; // ARTIK SENTAKS HATASI. } * Örnek 5, //.. using age = int; using wage = double; using name = std::string; int main() { /* # OUTPUT # Age : 31 Wage : 700 Name : Ahmo */ std::tuple workerOne{ 31, 700, "Ahmo" }; std::cout << "Age : " << std::get(workerOne) << "\n"; std::cout << "Wage : " << std::get(workerOne) << "\n"; std::cout << "Name : " << std::get(workerOne) << "\n"; } * Örnek 6, //.. int main() { /* # OUTPUT # Age / Wage / Name => 31 / 700 / 31 */ int age = 31; int wage = 700; std::string name = "Ahmo"; auto workerOne = std::make_tuple( age, wage, name ); std::cout << " Age / Wage / Name => " << std::get<0>(workerOne) << " / " << std::get<1>(workerOne) << " / " << std::get<0>(workerOne) << "\n"; } * Örnek 7, //.. int main() { /* # OUTPUT # Age / Wage / Name => 31 / 700 / Ahmo Age / Wage / Name => 31 / 700 / Ahmo Age / Wage / Name => 31 / 700 / Ahmo Age / Wage / Name => 31 / 700 / Ahmo Age / Wage / Name => 62 / 350 / Ahmo_ */ int age = 31; int wage = 700; std::string name = "Ahmo"; std::cout << " Age / Wage / Name => " << age << " / " << wage << " / " << name << "\n"; auto workerOne = std::make_tuple( age, wage, name ); std::cout << " Age / Wage / Name => " << std::get<0>(workerOne) << " / " << std::get<1>(workerOne) << " / " << std::get<2>(workerOne) << "\n"; std::get<0>(workerOne) *= 2; std::get<1>(workerOne) /= 2; std::get<2>(workerOne) += "_"; std::cout << " Age / Wage / Name => " << age << " / " << wage << " / " << name << "\n"; auto workerTwo = std::make_tuple( std::ref(age), std::ref(wage), std::ref(name) ); std::cout << " Age / Wage / Name => " << age << " / " << wage << " / " << name << "\n"; std::get<0>(workerTwo) *= 2; std::get<1>(workerTwo) /= 2; std::get<2>(workerTwo) += "_"; std::cout << " Age / Wage / Name => " << age << " / " << wage << " / " << name << "\n"; } * Örnek 8, //.. std::tuple workerMaker(void) { return { "Ahmo", 28, 4500 }; } int main() { /* # OUTPUT # */ // Approach - I : Hem yazım zorluğu hem de ilgili 'myWorkerOne' ismi 'scope-leakage' neden olabilir. auto myWorkerOne = workerMaker(); std::string workerName = std::get<0>(myWorkerOne); int workerAge = std::get<1>(myWorkerOne); int workerWage = std::get<2>(myWorkerOne); // Approach - II : 'std::tie()' fonksiyonunu kullanmak: Önce değişkenlerimizi hayata getirdik sonrasında atama yaptık. std::tie(workerName, workerAge, workerWage) = myWorkerOne; // std::tuple(workerName, workerAge, workerWage) = myWorkerOne; // Approach - III : Tek bir satırda hem değişkenlerimize ilk değer verdik. auto [workerNameTwo, workerAgeTwo, workerWageTwo] = workerMaker(); } * Örnek 9, //.. int main() { /* # OUTPUT # x : 24 y : 4.6 z : name_surname */ int x = 23; double y = 2.3; std::string z = "name"; auto myTuple = std::tie( x, y, z ); ++std::get<0>(myTuple); std::get<1>(myTuple) *= 2; std::get<2>(myTuple) += "_surname"; std::cout << "x : " << x << "\n"; std::cout << "y : " << y << "\n"; std::cout << "z : " << z << "\n"; } * Örnek 10, //.. std::tuple workerMaker(void) { return { "Ahmo", 28, 4500 }; } int main() { /* # OUTPUT # Name : Ahmo Age : 32537 Wage : 4500 Name : Ahmo Age : 28 */ std::string workerName; int workerAge; std::tie(workerName, ignore, ignore) = workerMaker(); // 'ignore', 'ignore_t' türünden bir sınıf nesnesi. Bir 'placeholder' auto [ workerNameTwo, workerAgeTwo, ignore ] = workerMaker(); // 'ignore', 'ignore_t' türünden bir sınıf nesnesi. std::cout << "Name : " << workerName << "\n"; std::cout << "Age : " << workerAge << "\n"; // Çöp değer ile hayata geldi. std::cout << "Wage : " << ignore << "\n"; // 'ignore' nesnesini kullanmamalıyız. std::cout << "\n"; std::cout << "Name : " << workerNameTwo << "\n"; std::cout << "Age : " << workerAgeTwo << "\n"; } * Örnek 11, class MyClass{ public: bool operator<(const MyClass& other) { // Approach - I : Bütün veri elemanlarını tek tek karşılaştırmak return ( age < other.age && wage < other.wage && name < other.name && surname < other.surname ); // Approach - II : Bütün veri elemanlarını bir demet halinde karşılaştırmak return std::tie(age, wage, name, surname) < std::tie(other.age, other.wage, other.name, other.surname); // Approach - III : C++20 ile dile eklenen 'spaceship' operatörü: } private: int age; int wage; std::string name; std::string surname; }; int main() { //.. } * Örnek 12, //.. using age = int; using wage = float; using name = std::string; using info = std::tuple; int main() { /* # OUTPUT # Age : 58 Wage : 99.99 Name : tempNameSurname Age : 61 Wage : 99.99 Name : tempNameSurname Age : 27 Wage : 99.99 Name : tempNameSurname Age : 40 Wage : 99.99 Name : tempNameSurname Age : 68 Wage : 99.99 Name : tempNameSurname */ std::vector workerVec; for(size_t index{}; index < 5; ++index) { workerVec.emplace_back( rand() % 75, 99.99f, "tempNameSurname" ); } for(const auto& [ _age, _wage, _name ] : workerVec) { std::cout << "Age : " << _age << "\n"; std::cout << "Wage : " << _wage << "\n"; std::cout << "Name : " << _name << "\n"; std::cout << "\n"; } } >> 'bitset' sınıf şablonunun incelenmesi: Aşağıdaki örnekleri inceleyelim: * Örnek 1, //.. int main() { /* # OUTPUT # 0000000000000000 0001110111100011 0110001100000111 */ std::bitset<16> myBitSet; std::cout << myBitSet << "\n"; // 'Default Ctor.' std::bitset<16> myBitSetTwo{ 7651u }; std::cout << myBitSetTwo << "\n"; // İşaretsiz tam sayı parametreli 'Ctor.' std::string name{"0110001100000111000"}; std::bitset<16> myBitSetThree{ name }; std::cout << myBitSetThree << "\n"; // 'std::string' parametreli 'Ctor.' return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 2 tabanında gösterilecek sayı: -1 2 tabanındaki hali : 11111111111111111111111111111111 */ std::cout << "2 tabanında gösterilecek sayı: "; size_t number; std::cin >> number; std::bitset<32> myBitSet(number); std::cout << "\n2 tabanındaki hali : " << myBitSet << "\n"; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # 0000000000000000 False... */ std::bitset<16> myBitSet; std::cout << myBitSet << "\n"; // 'Default Ctor.' // Herhangi bir 'bit', 'set' edilmiş ise 'true' döndürecektir. Bütün 'bit' ler '0' ise 'false' döndürecektir. if ( myBitSet.any() ) { std::cout << "True...\n"; } else { std::cout << "False...\n"; } return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # 0000000000000000 False... True... */ std::bitset<16> myBitSet; std::cout << myBitSet << "\n"; // 'Default Ctor.' // Herhangi bir 'bit', 'set' edilmiş ise 'true' döndürecektir. Bütün 'bit' ler '0' ise 'false' döndürecektir. if ( myBitSet.any() ) { std::cout << "True...\n"; } else { std::cout << "False...\n"; } // Herhangi bir 'bit', 'set' edilmemiş ise 'true' döndürecektir. Aksi halde 'false' döndürecektir. if ( myBitSet.none() ) { std::cout << "True...\n"; } else { std::cout << "False...\n"; } return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # 0000000000000000 False... True... False... */ std::bitset<16> myBitSet; std::cout << myBitSet << "\n"; // 'Default Ctor.' // Herhangi bir 'bit', 'set' edilmiş ise 'true' döndürecektir. Bütün 'bit' ler '0' ise 'false' döndürecektir. if ( myBitSet.any() ) { std::cout << "True...\n"; } else { std::cout << "False...\n"; } // Herhangi bir 'bit', 'set' edilmemiş ise 'true' döndürecektir. Aksi halde 'false' döndürecektir. if ( myBitSet.none() ) { std::cout << "True...\n"; } else { std::cout << "False...\n"; } // Bütün 'bit' ler, 'set' edilmiş ise 'true' döndürecektir. Aksi halde 'false' döndürecektir. if ( myBitSet.all() ) { std::cout << "True...\n"; } else { std::cout << "False...\n"; } return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # 11111111 bitSize / Set Counter : 8 / 8 */ constexpr int bitSize = 8; std::bitset myBitSet(-1); std::cout << myBitSet << "\n"; // 'Default Ctor.' std::cout << "bitSize / Set Counter : " << bitSize << " / " << myBitSet.count() << "\n"; return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # 00000000 00000001 00000111 11111111 */ constexpr int bitSize = 8; std::bitset myBitSet; std::cout << myBitSet << "\n"; // 'Default Ctor.' myBitSet.set(0); std::cout << myBitSet << "\n"; myBitSet.set(1).set(2); std::cout << myBitSet << "\n"; myBitSet.set(); std::cout << myBitSet << "\n"; return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # 00000000 00000001 00000111 11111111 11111110 11111000 00000000 */ constexpr int bitSize = 8; std::bitset myBitSet; std::cout << myBitSet << "\n"; // 'Default Ctor.' myBitSet.set(0); std::cout << myBitSet << "\n"; myBitSet.set(1).set(2); std::cout << myBitSet << "\n"; myBitSet.set(); std::cout << myBitSet << "\n"; myBitSet.reset(0); std::cout << myBitSet << "\n"; myBitSet.reset(1).reset(2); std::cout << myBitSet << "\n"; myBitSet.reset(); std::cout << myBitSet << "\n"; return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # 00011111 11100000 01100000 00000000 */ constexpr int bitSize = 8; std::bitset myBitSet(31); std::cout << myBitSet << "\n"; // 'Default Ctor.' myBitSet.flip(); std::cout << myBitSet << "\n"; myBitSet.flip(7); std::cout << myBitSet << "\n"; myBitSet.flip(6).flip(5); std::cout << myBitSet << "\n"; return 0; } * Örnek 10, //.. int main() { /* # OUTPUT # 1000 type name : St6bitsetILm4EE type name : NSt6bitsetILm4EE9referenceE 3 is set. */ constexpr int bitSize = 4; std::bitset myBitSet(8); std::cout << myBitSet << "\n"; std::cout << "type name : " << typeid(myBitSet).name() << "\n"; std::cout << "type name : " << typeid(myBitSet[5]).name() << "\n"; if( myBitSet.test(0) ) { std::cout << "0 is set.\n"; } else if( myBitSet.operator[](1) ) { std::cout << "1 is set.\n"; } else if( myBitSet.operator[](2).operator bool() ) { std::cout << "2 is set.\n"; } else { std::cout << "3 is set.\n"; } return 0; } * Örnek 11, //.. int main() { /* # OUTPUT # type name : St6bitsetILm4EE type name : NSt6bitsetILm4EE9referenceE 1000 3 is set. 1101 0 is set. */ constexpr int bitSize = 4; std::bitset myBitSet(8); std::cout << "type name : " << typeid(myBitSet).name() << "\n"; std::cout << "type name : " << typeid(myBitSet[5]).name() << "\n"; std::cout << myBitSet << "\n"; if( myBitSet.test(0) ) { std::cout << "0 is set.\n"; } else if( myBitSet.operator[](1) ) { std::cout << "1 is set.\n"; } else if( myBitSet.operator[](2).operator bool() ) { std::cout << "2 is set.\n"; } else { std::cout << "3 is set.\n"; } myBitSet[2] = myBitSet[3]; myBitSet.set(0); std::cout << myBitSet << "\n"; if( myBitSet.test(0) ) { std::cout << "0 is set.\n"; } else if( myBitSet.operator[](1) ) { std::cout << "1 is set.\n"; } else if( myBitSet.operator[](2).operator bool() ) { std::cout << "2 is set.\n"; } else { std::cout << "3 is set.\n"; } return 0; } * Örnek 12, //.. int main() { /* # OUTPUT # 000000000000000100000110100011013 00000000000010000011010001101000 */ constexpr int bitSize = 32; std::bitset myBitSet(67213u); std::cout << myBitSet << 3 << "\n"; std::cout << ( myBitSet << 3 ) << "\n"; return 0; } * Örnek 13, //.. int main() { /* # OUTPUT # a : 00000000000000010000011010001101 b : 00000000000010111111000000101111 a & b : 00000000000000010000000000001101 a | b : 00000000000010111111011010101111 a ^ b : 00000000000010101111011010100010 */ constexpr int bitSize = 32; std::bitset a(67213u); std::bitset b(782383u); std::cout << "a : " << a << "\n"; std::cout << "b : " << b << "\n"; std::cout << "a & b : " << ( a & b ) << "\n"; std::cout << "a | b : " << ( a | b ) << "\n"; std::cout << "a ^ b : " << ( a ^ b ) << "\n"; return 0; } * Örnek 14, //.. int main() { /* # OUTPUT # 67213 : 00000000000000010000011010001101 268852 : 00000000000001000001101000110100 */ constexpr int bitSize = 32; std::bitset a(67213u); std::cout << a.to_ulong() << " : " << a << "\n"; a <<= 2; std::cout << a.to_ulong() << " : " << a << "\n"; return 0; } * Örnek 15, //.. enum Color { Red, Yellow, Green, NumberOfColors }; int main() { /* # OUTPUT # 0 : 000 1 : 001 1 : 001 1 : 001 */ std::bitset a; std::cout << a.to_ulong() << " : " << a << "\n"; a[Red] = true; std::cout << a.to_ulong() << " : " << a << "\n"; a[Yellow] = ~a[Red]; std::cout << a.to_ulong() << " : " << a << "\n"; a[Green] = a[Yellow]; std::cout << a.to_ulong() << " : " << a << "\n"; return 0; } * Örnek 16, //.. int main() { /* # OUTPUT # 0 : 000 1 : 001 1 : 001 1 : 001 */ std::set> mySet; mySet.insert(16); // error: no match for ‘operator<’ (operand types are ‘const std::bitset<16>’ and ‘const std::bitset<16>’) return 0; } * Örnek 17, //.. int main() { /* # OUTPUT # 00000000 00000001 00000010 00000011 00000100 00000101 00000110 00000111 00001000 00001001 */ std::vector> mySet; for(size_t i{}; i < 10; ++i) mySet.emplace_back(i); for(const auto& index : mySet) std::cout << index << "\n"; return 0; } >> 'Standart Containers Adaptors' : 'stack', 'queue', 'priority_queue' vs. sınıf şablonlarından oluşmaktadırlar. BUNLAR BİR VERİ YAPISI DEĞİLDİR. VERİ YAPISINDA İMPLEMENTASYON DA BİZİ İLGİLENDİRMEKTEDİR. Fakat bu adaptörlerin arka planında vektör mü kullanılmış, bağlı liste mi kullanılmış bizi ilgilendirmemektedir. Yine bunların bir 'container' OLMADIĞINI, sadece ve sadece 'container adaptor' olduğunu da unutmayalım. Çünkü bunlar bir kabı eleman olarak alıyorlar ve onun arayüzünü kendilerine adapte ediyorlar. >>> 'stack' : Arka planda varsayılan kap olarak 'std::deque' kabını kullanmaktadır. Temsili olarak gösterimi aşağıdaki gibidir: * Örnek 1, //.. template> class Stack{ public: typename C::size_type size() const { return _mCon.size(); } T& top() const { return _mCon.back(); } bool empty() const { return _mCon.empty(); } void push(const T& value) { _mCon.push_back(value); } void pop(const T& value) { _mCon.pop_back(value); } template void emplace(Args&& ...args) { _mCon.emplace(args...); } private: C _mCon; }; template using vecStack = std::stack>; int main() { Stack myStack; Stack> myStackTwo; // vecStack myStackTwo; } >>>> Üye Fonksiyonlarının İncelenmesi: * Örnek 1, //.. int main() { /* # OUTPUT # Yığının en üstündeki çıkartılacak öğe: 2 Yığının en üstündeki çıkartılacak öğe: 1 Yığının en üstündeki çıkartılacak öğe: 0 */ std::stack myStack; for(size_t i{}; i < 3; ++i) myStack.push(i); while(!myStack.empty()) { std::cout << "Yığının en üstündeki çıkartılacak öğe: " << myStack.top(); myStack.pop(); std::cout << "\n"; // Yığın boşken bu iki fonksiyonun çağrılması 'Tanımsız Davranış'. } return 0; } * Örnek 2, //.. int main() { std::deque myDeque; //.. std::stack myStack(myDeque); // Arka plandaki varsayılan veri yapısı 'deque' olduğundan LEGALDİR. //.. std::vector myVector; //.. std::stack myStackTwo(myVector); // Arka plandaki varsayılan veri yapısı 'deque' olduğundan SENTAKS HATASIDIR. // error: no matching function for call to ‘std::stack::stack(std::vector&)’ std::stack> myStackThree(myVector); // Arka plandaki varsayılan veri yapısı 'vector' olduğundan LEGALDİR. return 0; } /*============================================================================================================*/ (32_03_01_2021) > 'STL' içerisindeki kaplar (devam) : >> 'Standart Containers Adaptors' (devam) : >>> 'stack' (devam) : Bu adaptör 'LIFO' şeklinde, yani son girenin ilk çıktığı, yapılandırılmıştır. >>>> 'stack' bünyesinde kullanılan veri tabanı ilgili sınıf şablonunun 'protected' kısmındadır. Ayrıca 'stack' sınıf şablonundan kalıtım da yapabiliriz. * Örnek 1, //.. class MyStack : public std::stack{ public: void clear() { c.clear; } // Normal şartlarda 'stack' public arayüzünde böyle bir fonksiyon yoktur. // Fakat kalıtım yoluyle yeni bir sınıf elde ettiğimiz zaman, taban sınıfın 'protected' kısmına ulaşarak // böyle bir özellik kazandırabiliriz. Unutmamalıyız ki 'stack' sınıfında sanal bir fonksiyon olmadığından, // bu kalıtım ile 'Run Time Polymorphism' hedeflenmemiştir. Sadece ve sadece arayüzünün genişletilmesi // hedeflenmiştir. }; int main() { std::vector myVec; fcs(myVec, 20, Date::random); print(myVec); return 0; } >>> 'queue' ve 'priority_queue' sınıf şablonları: Her iki sınıf şablonu da 'queue' başlık dosyasında bildirilmiştir. 'FIFO' şeklinde, yani ilk girenin ilk çıktığı, yapılandırılmıştır. Arka planda kullanılan veri yapısının '.pop_front()' şeklinde bir fonksiyonu olmalıdır. İşte bu yüzden bizler arka planda 'std::vector' kullanamayız eğer öğe çıkartmak istiyorsak. Arayüzünde bulunan üye fonksiyonlar, 'stack' sınıf şablonundakiler ile benzerdir. 'queue' sınıfı ekstra bir üye fonksiyonu bulunmamaktadır. >>>> 'priority_queue' sınıf şablonu: >>>>> 'binary_heap' hatırlatması: Yine bir ikili ağaçtır. Her 'parent' iki adet 'child' barındırmaktadır. Fakat, son seviye hariç, her seviyenin tamam olması gerekmektedir. Sadece son seviyenin en sağındaki öğe eksik olabilir. Bu şartı sağlamaz ise 'binary_heap' OLMAYACAKTIR. Vektörel veri yapısını da 'std::make_heap' fonksiyonu ile yukarıdaki hale getirebiliriz. 75 37 42 24 26 30 25 17 21 18 12 28 22 [75] [37 42] [24 26] [30 25] [17 21] [18 12] [28 22] Bu görülen ağacımız, son seviye hariç her seviyesi 'complete' olduğundan dolayı, bir 'binary_heap' şeklindedir. Velevki '21' rakamı olmasaydı, seviyelerde bir boşlık olacağından, 'binary_heap' özelliği kaybolacaktır. Bu özelliğe ek olarak her düğüm, altındaki düğümlerden daha yüksek bir değere sahip olmalıdır. 'Binary Search Tree' den farklıdır. (bkz. : https://www.geeksforgeeks.org/difference-between-binary-search-tree-and-binary-heap/ #:~:text=The%20fundamental%20distinction%20is%20that,is%20the%20way%20to%20go.) >>>>>> Örnekler, * Örnek 1, //.. int main() { /* # OUTPUT # 96 199 871 103 133 157 664 901 746 778 814 261 753 244 406 352 12 524 682 15 ----------------------------------------------------------------------------- 901 814 871 746 778 753 664 352 682 96 133 261 157 244 406 103 12 524 199 15 ----------------------------------------------------------------------------- */ /* 901 814 871 746 778 753 664 352 682 96 133 261 157 244 406 103 12 524 199 15 ----------------------------------------------------------------------------- | V [901] [814 871] [746 778] [753 664] [352 682] [96 133] [261 157] [244 406] [103 12] [524 199] [15] */ std::vector myVec; fcs(myVec, 20, Irand{ 0, 1000 }); print(myVec); std::make_heap(myVec.begin(), myVec.end()); print(myVec); // Vektörel veri yapısı artık mevcut değil. return 0; } * Örnek 2, int main() { /* # OUTPUT # 798 852 281 477 894 565 322 383 67 281 304 983 144 826 700 290 762 195 610 898 ----------------------------------------------------------------------------- 983 898 826 762 894 565 798 477 610 852 304 281 144 322 700 290 383 195 67 281 ----------------------------------------------------------------------------- 898 894 826 762 852 565 798 477 610 281 304 281 144 322 700 290 383 195 67 983 ----------------------------------------------------------------------------- 898 894 826 762 852 565 798 477 610 281 304 281 144 322 700 290 383 195 67 ----------------------------------------------------------------------------- */ std::vector myVec; fcs(myVec, 20, Irand{ 0, 1000 }); print(myVec); std::make_heap(myVec.begin(), myVec.end()); print(myVec); std::pop_heap(myVec.begin(), myVec.end()); print(myVec); // Ağacın en yukarısındaki ağacın en sonuna gönderildi. myVec.pop_back(); print(myVec); // En sondaki öğe çıkartıldı. return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # I: 618 283 236 863 265 599 694 475 610 453 84 436 593 235 206 521 753 92 743 166 ----------------------------------------------------------------------------- II: 863 753 694 743 453 599 236 521 618 265 84 436 593 235 206 283 475 92 610 166 ----------------------------------------------------------------------------- [863] => 753 743 694 618 453 599 236 521 610 265 84 436 593 235 206 283 475 92 166 863 ----------------------------------------------------------------------------- [753] => 743 618 694 610 453 599 236 521 166 265 84 436 593 235 206 283 475 92 753 ----------------------------------------------------------------------------- [743] => 694 618 599 610 453 593 236 521 166 265 84 436 92 235 206 283 475 743 ----------------------------------------------------------------------------- [694] => 618 610 599 521 453 593 236 475 166 265 84 436 92 235 206 283 694 ----------------------------------------------------------------------------- [618] => 610 521 599 475 453 593 236 283 166 265 84 436 92 235 206 618 ----------------------------------------------------------------------------- [610] => 599 521 593 475 453 436 236 283 166 265 84 206 92 235 610 ----------------------------------------------------------------------------- [599] => 593 521 436 475 453 235 236 283 166 265 84 206 92 599 ----------------------------------------------------------------------------- [593] => 521 475 436 283 453 235 236 92 166 265 84 206 593 ----------------------------------------------------------------------------- [521] => 475 453 436 283 265 235 236 92 166 206 84 521 ----------------------------------------------------------------------------- [475] => 453 283 436 166 265 235 236 92 84 206 475 ----------------------------------------------------------------------------- [453] => 436 283 236 166 265 235 206 92 84 453 ----------------------------------------------------------------------------- [436] => 283 265 236 166 84 235 206 92 436 ----------------------------------------------------------------------------- [283] => 265 166 236 92 84 235 206 283 ----------------------------------------------------------------------------- [265] => 236 166 235 92 84 206 265 ----------------------------------------------------------------------------- [236] => 235 166 206 92 84 236 ----------------------------------------------------------------------------- [235] => 206 166 84 92 235 ----------------------------------------------------------------------------- [206] => 166 92 84 206 ----------------------------------------------------------------------------- [166] => 92 84 166 ----------------------------------------------------------------------------- [92] => 84 92 ----------------------------------------------------------------------------- [84] => 84 ----------------------------------------------------------------------------- */ std::vector myVec; fcs(myVec, 20, Irand{ 0, 1000 }); print(myVec); // I std::make_heap(myVec.begin(), myVec.end()); print(myVec); // II while(!myVec.empty()) { std::pop_heap(myVec.begin(), myVec.end()); std::cout << "[" << myVec.back() << "] => "; print(myVec); myVec.pop_back(); } return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # ata refika efe durmus malik ali caner esen sabriye yasin ----------------------------------------------------------------------------- sabriye refika caner durmus yasin ali efe esen ata malik ----------------------------------------------------------------------------- [sabriye] => refika durmus caner malik yasin ali efe esen ata sabriye ----------------------------------------------------------------------------- [refika] => durmus yasin caner malik ata ali efe esen refika ----------------------------------------------------------------------------- [durmus] => yasin malik caner esen ata ali efe durmus ----------------------------------------------------------------------------- [yasin] => malik esen caner efe ata ali yasin ----------------------------------------------------------------------------- [malik] => caner esen ali efe ata malik ----------------------------------------------------------------------------- [caner] => esen efe ali ata caner ----------------------------------------------------------------------------- [esen] => efe ata ali esen ----------------------------------------------------------------------------- [efe] => ata ali efe ----------------------------------------------------------------------------- [ata] => ali ata ----------------------------------------------------------------------------- [ali] => ali ----------------------------------------------------------------------------- */ std::vector myVec; fcs(myVec, 10, rname); print(myVec); // I std::make_heap(myVec.begin(), myVec.end(), myStringPredicate); print(myVec); // Artık vektörel bir veri yapısı yoktur. Elemanlar bizim kriterimize göre sıralanmışlardır. while(!myVec.empty()) { std::pop_heap(myVec.begin(), myVec.end(), myStringPredicate); std::cout << "[" << myVec.back() << "] => "; print(myVec); myVec.pop_back(); } return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # kaan emine tugra ----------------------------------------------------------------------------- tugra emine kaan ----------------------------------------------------------------------------- [tugra] => emine kaan tugra ----------------------------------------------------------------------------- [XXX] => emine kaan XXX ----------------------------------------------------------------------------- */ std::vector myVec; fcs(myVec, 3, rname); print(myVec); // I std::make_heap(myVec.begin(), myVec.end(), myStringPredicate); print(myVec); // Artık vektörel bir veri yapısı yoktur. Elemanlar bizim kriterimize göre sıralanmışlardır. std::pop_heap(myVec.begin(), myVec.end(), myStringPredicate); std::cout << "[" << myVec.back() << "] => "; print(myVec); myVec.pop_back(); std::cout << "[" << "XXX" << "] => "; myVec.push_back("XXX"); std::push_heap(myVec.begin(), myVec.end(), myStringPredicate); print(myVec); return 0; } >>>>> İşte yukarıdaki örneklerde yapılanları gerçekleştiren sınıf şablonumuz da 'priority_queue' sınıf şablonudur. Bu sınıf şablonumuz ilk parametre olarak tutulacak öğenin cinsi, ikinci parametre olarak arka tarafta kullanılacak veri yapısını ki bu bir 'std::vector' sınıfıdır, üçüncü parametre olarak da karşılaştırma kriterini almaktadır ki bu da 'std::less' sınıf şablonudur. * Örnek 1, //.. int main() { /* # OUTPUT # [13 Ocak 2020 Pazartesi] [27 Nisan 2017 Persembe] [15 Mayis 2013 Carsamba] [25 Mart 2013 Pazartesi] [09 Kasim 2008 Pazar] [05 Eylul 1995 Sali] [24 Nisan 1992 Cuma] [04 Kasim 1977 Cuma] [26 Subat 1970 Persembe] [29 Kasim 1956 Persembe] */ std::priority_queue myQ; for(size_t i{}; i < 10; ++i) myQ.push(Date::random()); while(!myQ.empty()) { std::cout << "[" << myQ.top() << "]\n"; myQ.pop(); } return 0; } * Örnek 2, //.. template using min_queue = std::priority_queue, std::greater>; int main() { /* # OUTPUT # [14 Subat 1953 Cumartesi] [17 Kasim 1953 Sali] [06 Ocak 1975 Pazartesi] [02 Subat 1977 Carsamba] [22 Ocak 1986 Carsamba] [17 Ocak 1992 Cuma] [16 Aralik 2005 Cuma] [28 Haziran 2013 Cuma] [12 Subat 2016 Cuma] [24 Ekim 2019 Persembe] */ min_queue myQ; for(size_t i{}; i < 10; ++i) myQ.push(Date::random()); while(!myQ.empty()) { std::cout << "[" << myQ.top() << "]\n"; myQ.pop(); } return 0; } >> 'std::invoke' fonksiyon şablonunun incelenmesi: Her türlü 'callable' bu şablon ile çağrılabilmektedir. * Örnek 1, //.. void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } int f4(void) { std::cout << "Myclass::f4(void) was called. " << 4 << " will be returned.\n"; return 4; } void f2(int value) { std::cout << "Myclass::f2(int " << value << " ) was called.\n"; } int f5(int value) { std::cout << "Myclass::f5(int " << value << " ) was called. " << value * 5 << " will be returned.\n"; return value * 5; } void f3(int value, int valueTwo) { std::cout << "Myclass::f3(int " << value << ", int " << valueTwo << " ) was called.\n"; } int f6(int value, int valueTwo) { std::cout << "Myclass::f6(int " << value << ", int " << valueTwo << " ) was called. " << value * valueTwo << " will be returned.\n"; return value * valueTwo; } int main() { /* # OUTPUT # Myclass::f1(void) was called. Myclass::f4(void) was called. 4 will be returned. Myclass::f2(int 2 ) was called. Myclass::f5(int 5 ) was called. 25 will be returned. Myclass::f3(int 3, int 3 ) was called. Myclass::f6(int 6, int 6 ) was called. 36 will be returned. */ std::invoke(f1); std::invoke(f4); std::invoke(f2, 2); std::invoke(f5, 5); std::invoke(f3, 3, 3); std::invoke(f6, 6, 6); return 0; } * Örnek 2, //.. class Functor{ public: void f1(int value) { std::cout << "Functor::f1(int " << value << " ) was called.\n"; } }; int main() { /* # OUTPUT # Functor::f1(int 31 ) was called. */ std::invoke(&Functor::f1, Functor{}, 31); return 0; } * Örnek 3, //.. class Functor{ public: void f1(int value) { std::cout << "Functor::f1(int " << value << " ) was called.\n"; } }; int main() { /* # OUTPUT # Functor::f1(int 32 ) was called. Functor::f1(int 33 ) was called. Functor::f1(int 34 ) was called. Functor::f1(int 35 ) was called. */ auto funcPtr = &Functor::f1; Functor fx; (fx.*funcPtr)(32); auto fy = new Functor; (fy->*funcPtr)(33); std::invoke(funcPtr, fx, 34); std::invoke(funcPtr, fy, 35); return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # Result : 1296 */ auto f = [](int a){ return a*a; }; std::cout << "Result : " << std::invoke(f, 36) << "\n"; return 0; } >> 'std::function' sınıf şablonunun incelenmesi: Herhangi bir 'callable' sarmalanmaktadır. * Örnek 1, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } int main() { /* # OUTPUT # int foo( 10 ) was called. 100 will be returned. int foo( 11 ) was called. 121 will be returned. */ std::function fp; // Geri dönüş değeri 'int' ve 'int' türden bir parametre alan herhangi bir 'callable' // ilk değer olarak verilebilir veya atanabilir. foo(10); fp = foo; fp(11); return 0; } * Örnek 2, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } int main() { /* # OUTPUT # int foo( 10 ) was called. 100 will be returned. int foo( 11 ) was called. 121 will be returned. int foo( 12 ) was called. 144 will be returned. */ std::function fp; // Geri dönüş değeri 'int' ve 'int' türden bir parametre alan herhangi bir 'callable' // ilk değer olarak verilebilir veya atanabilir. foo(10); fp = foo; fp(11); auto funcPtr = &foo; fp = funcPtr; fp(12); return 0; } * Örnek 3, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } class Functor{ public: int operator()(int value)const { std::cout << "int Functor::operator()( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } }; int main() { /* # OUTPUT # int foo( 10 ) was called. 100 will be returned. int foo( 11 ) was called. 121 will be returned. int foo( 12 ) was called. 144 will be returned. int Functor::operator()( 13 ) was called. 169 will be returned. */ std::function fp; // Geri dönüş değeri 'int' ve 'int' türden bir parametre alan herhangi bir 'callable' // ilk değer olarak verilebilir veya atanabilir. foo(10); fp = foo; fp(11); auto funcPtr = &foo; fp = funcPtr; fp(12); fp = Functor{}; fp(13); return 0; } * Örnek 4, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } class Functor{ public: int operator()(int value)const { std::cout << "int Functor::operator()( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } }; auto f = [](int value){ std::cout << "Lambda-Expression ( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; }; int main() { /* # OUTPUT # int foo( 10 ) was called. 100 will be returned. int foo( 11 ) was called. 121 will be returned. int foo( 12 ) was called. 144 will be returned. int Functor::operator()( 13 ) was called. 169 will be returned. Lambda-Expression ( 14 ) was called. 196 will be returned. */ std::function fp; // Geri dönüş değeri 'int' ve 'int' türden bir parametre alan herhangi bir 'callable' // ilk değer olarak verilebilir veya atanabilir. foo(10); fp = foo; fp(11); auto funcPtr = &foo; fp = funcPtr; fp(12); fp = Functor{}; fp(13); fp = f; fp(14); return 0; } * Örnek 5, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } class Functor{ public: int operator()(int value)const { std::cout << "int Functor::operator()( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } }; auto f = [](int value){ std::cout << "Lambda-Expression ( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; }; void theCaller(int value, std::function fp) { fp(value); } int main() { /* # OUTPUT # int foo( 100 ) was called. 10000 will be returned. int Functor::operator()( 101 ) was called. 10201 will be returned. Lambda-Expression ( 102 ) was called. 10404 will be returned. */ theCaller(100, foo); theCaller(101, Functor{}); theCaller(102, f); return 0; } * Örnek 6, //.. auto f = [](int value){ std::cout << "Lambda-Expression ( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; }; class Myclass{ public: int func(int value) { return m_fp(value); } private: std::function m_fp = f; }; int main() { /* # OUTPUT # Lambda-Expression ( 10 ) was called. 100 will be returned. */ Myclass mx; mx.func(10); return 0; } * Örnek 7, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } class Functor{ public: int operator()(int value)const { std::cout << "int Functor::operator()( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } }; auto f = [](int value){ std::cout << "Lambda-Expression ( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; }; int main() { /* # OUTPUT # int foo( 10 ) was called. 100 will be returned. int Functor::operator()( 11 ) was called. 121 will be returned. Lambda-Expression ( 12 ) was called. 144 will be returned. */ std::vector> funcVec; funcVec.emplace_back(&foo); funcVec.emplace_back(Functor{}); funcVec.emplace_back(f); for(size_t i{}; i < funcVec.size(); ++i) { static int counter{ 10 }; funcVec.at(i)(counter++); } return 0; } * Örnek 8, //.. int foo(int value) { std::cout << "int foo( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } class Functor{ public: int operator()(int value)const { std::cout << "int Functor::operator()( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; } }; auto f = [](int value){ std::cout << "Lambda-Expression ( " << value << " ) was called. " << value * value << " will be returned.\n"; return value*value; }; std::function theCalled(int value) { switch(value) { case 10: return foo; break; case 11: return Functor{}; break; case 12: return f; break; default: return foo; break; } } int main() { /* # OUTPUT # int foo( 10 ) was called. 100 will be returned. int Functor::operator()( 11 ) was called. 121 will be returned. Lambda-Expression ( 12 ) was called. 144 will be returned. */ auto theCalee = theCalled(10); theCalee(10); theCalee = theCalled(11); theCalee(11); theCalee = theCalled(12); theCalee(12); return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # Ben boşum. */ std::function fp; // Herhangi bir 'callable' sarmalanmamıştır. //if(!fp) if(!fp.operator bool()) std::cout << "Ben boşum.\n"; else std::cout << "Ben boş değilim.\n"; return 0; } * Örnek 10, //.. int main() { /* # OUTPUT # Ben boşum. bad_function_call */ std::function fp; // Herhangi bir 'callable' sarmalanmamıştır. // if(!fp) if(!fp.operator bool()) std::cout << "Ben boşum.\n"; else std::cout << "Ben boş değilim.\n"; try { fp(31); } catch(const std::exception& ex) { std::cout << ex.what() << "\n"; } return 0; } >> 'std::bind' fonksiyon şablonunun incelenmesi: Argüman olarak bir 'callable' ve gerekli argümanları geçiyoruz. Geri dönüş değeri olarak da bir 'function object' alıyoruz. Yani 'operator()()' fonksiyonunu 'overlaod' etmiş bir sınıf türünden nesne döndürülmekte. * Örnek 1, //.. int foo(int valueOne, int valueTwo, int valueThree) { std::cout << "int foo (" << valueOne << ", " << valueTwo << ", " << valueThree << ") was called. " << valueOne*valueTwo*valueThree << " will be returned.\n"; return valueOne*valueTwo*valueThree; } class Functor{ public: int operator()(int valueOne, int valueTwo, int valueThree)const { std::cout << "int Functor::operator()(" << valueOne << ", " << valueTwo << ", " << valueThree << ") was called. " << valueOne*valueTwo*valueThree << " will be returned.\n"; return valueOne*valueTwo*valueThree; } }; auto f = [](int valueOne, int valueTwo, int valueThree){ std::cout << "Lambda-Expression (" << valueOne << ", " << valueTwo << ", " << valueThree << ") was called. " << valueOne*valueTwo*valueThree << " will be returned.\n"; return valueOne*valueTwo*valueThree; }; int main() { /* # OUTPUT # int foo (10, 11, 12) was called. 1320 will be returned. int Functor::operator()(10, 11, 12) was called. 1320 will be returned. Lambda-Expression (10, 11, 12) was called. 1320 will be returned. */ auto fp = std::bind(foo, 10, 11, 12); fp(); auto fpp = std::bind(Functor{}, 10, 11, 12); fpp(); auto fppp = std::bind(f, 10, 11, 12); fppp(); return 0; } * Örnek 2, //.. int foo(int valueOne, int valueTwo, int valueThree) { std::cout << "int foo (" << valueOne << ", " << valueTwo << ", " << valueThree << ") was called. " << valueOne*valueTwo*valueThree << " will be returned.\n"; return valueOne*valueTwo*valueThree; } class Functor{ public: int operator()(int valueOne, int valueTwo, int valueThree)const { std::cout << "int Functor::operator()(" << valueOne << ", " << valueTwo << ", " << valueThree << ") was called. " << valueOne*valueTwo*valueThree << " will be returned.\n"; return valueOne*valueTwo*valueThree; } }; auto f = [](int valueOne, int valueTwo, int valueThree){ std::cout << "Lambda-Expression (" << valueOne << ", " << valueTwo << ", " << valueThree << ") was called. " << valueOne*valueTwo*valueThree << " will be returned.\n"; return valueOne*valueTwo*valueThree; }; int main() { /* # OUTPUT # int foo (10, 11, 12) was called. 1320 will be returned. int Functor::operator()(10, 11, 12) was called. 1320 will be returned. Lambda-Expression (10, 11, 11) was called. 1210 will be returned. */ auto fp = std::bind(foo, 10, 11, 12); fp(); auto fpp = std::bind(Functor{}, std::placeholders::_1, 11, 12); fpp(10); auto fppp = std::bind(f, std::placeholders::_1, 11, std::placeholders::_2); fppp(10, 11); return 0; } * Örnek 3, //.. void func(int& x, int& y) { ++x; ++y; } int main() { /* # OUTPUT # a / b => 11 / 21 */ int a = 10, b = 20; auto fp = std::bind(func, std::placeholders::_1, std::placeholders::_2); fp(a, b); std::cout << " a / b => " << a << " / " << b << "\n"; return 0; } * Örnek 4, //.. void func(int& x, int& y) { ++x; ++y; } int main() { /* # OUTPUT # a / b => 11 / 21 a / b => 11 / 21 */ int a = 10, b = 20; auto fp = std::bind(func, std::placeholders::_1, std::placeholders::_2); fp(a, b); std::cout << " a / b => " << a << " / " << b << "\n"; auto fpp = std::bind(func, a, b); fpp(); // Bu çağrı 'a' ve 'b' değişkenleri üzerinde bir etki oluşturmamıştır. Çünkü bu şekildeki kullanımda // 'a' ve 'b' değişkenlerinin değerleri, 'std::bind' fonksiyonunun geri döndürdüğü sınıf nesnesinin veri // elemanlarına kopyalanmaktadır. Yani bir 'call-by-value' durumu söz konusudur. std::cout << " a / b => " << a << " / " << b << "\n"; return 0; } * Örnek 5, //.. void func(int& x, int& y) { ++x; ++y; } int main() { /* # OUTPUT # a / b => 11 / 21 a / b => 11 / 21 a / b => 12 / 21 */ int a = 10, b = 20; auto fp = std::bind(func, std::placeholders::_1, std::placeholders::_2); fp(a, b); std::cout << " a / b => " << a << " / " << b << "\n"; auto fpp = std::bind(func, a, b); fpp(); std::cout << " a / b => " << a << " / " << b << "\n"; auto fppp = std::bind(func, std::ref(a), b); fppp(); // Bir 'reference_wrapper' ile sarmaladığımız için 'call-by-reference' durumu söz konusudur // ama 'a' için. 'b' ise hala 'call-by-value'. std::cout << " a / b => " << a << " / " << b << "\n"; return 0; } * Örnek 6, (bkz. https://godbolt.org/z/9M5Kq33nh) //.. void print(std::ostream& os, int value) { os << " value : " << value << "\n"; } int main() { /* # OUTPUT # value : 10 value : 11 */ print(std::cout, 10); auto fp = std::bind(print, std::ref(std::cout), std::placeholders::_1); fp(11); // Burada 'std::cout' kopyalamaya kapalı olduğundan 'std::ref()' ile sarmaladık. return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # void foo(void)const was called. void func(10)const was called. */ Myclass mx; auto fp = std::bind(&Myclass::foo, std::placeholders::_1); fp(mx); auto fpp = std::bind(&Myclass::func, std::placeholders::_1, std::placeholders::_2); fpp(mx, 10); return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # void foo(void)const was called. void func(10)const was called. void foo(void)const was called. void func(11)const was called. */ Myclass mx; auto fp = std::bind(&Myclass::foo, std::placeholders::_1); fp(mx); auto fpp = std::bind(&Myclass::func, std::placeholders::_1, std::placeholders::_2); fpp(mx, 10); auto mxPtr = new Myclass; fp(mxPtr); fpp(mxPtr, 11); return 0; } >> 'std::generate()' fonksiyonunun incelenmesi: Bir adet 'callable' ile bir adet 'range' argüman olarak alıyor ve ilgili 'range' içerisindeki öğeleri 'callable' vasıtasıyla dolduruyor. * Örnek 1, //.. int main() { /* # OUTPUT # 74 55 57 62 0 98 65 78 17 26 ----------------------------------------------------------------------------- 65_33_20_52_41_ */ std::vector iVec(10); std::generate(iVec.begin(), iVec.end(), []{ return Irand{ 0, 100 }(); }); print(iVec); std::generate_n( std::ostream_iterator{std::cout, "_"}, 5, []{ return Irand{ 0, 100 }(); } ); return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 74 55 57 62 0 98 65 78 17 26 ----------------------------------------------------------------------------- 65_33_20_52_41_ */ std::vector iVec(1000); std::generate(iVec.begin(), iVec.end(), []{ return Irand{ 0, 1000 }(); }); int numberGreaterThan = 500; std::cout << "[" << std::count_if(iVec.begin(), iVec.end(), [numberGreaterThan](int value){ return value > numberGreaterThan; }) << "] adet rakam " << numberGreaterThan << " rakamından büyüktür.\n"; std::cout << "[" << std::count_if(iVec.begin(), iVec.end(), std::bind(std::greater{}, std::placeholders::_1, numberGreaterThan)) << "] adet rakam " << numberGreaterThan << " rakamından büyüktür.\n"; return 0; } >> 'mem_fn' ve 'not_fn' fonksiyon şablonlarının incelenmesi: Bir sınıfın üye fonksiyonunu argüman olarak alıyor ve o üye fonksiyonun çağrılmasını sağlamaktadır, 'mem_fn()' olan. Bir 'function-object' döndürmektedir. 'not_fn()' ise yine bir 'callable' alıyor. Geri dönüş değeri ise, aldığı 'callable' ın geri dönüş değerinin, 'Lojik-Değil' i. * Örnek 1, //.. int main() { /* # OUTPUT # Month : 17 Month : 17 */ Date myDate{ 17, 9, 1993 }; auto fp = std::mem_fn(&Date::month_day); std::cout << "Month : " << myDate.month_day() << "\n"; std::cout << "Month : " << fp(myDate); return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # bennur | candan | seyhan | ferhunde | haluk | ----------------------------------------------------------------------------- 6 | 6 | 6 | 8 | 5 | */ std::vector nameList; fcs(nameList, 5, rname); print(nameList, " | "); std::transform( nameList.begin(), nameList.end(), std::ostream_iterator{std::cout, " | "}, std::mem_fn(&std::string::size) ); return 0; } * Örnek 3, //.. int main() { auto fp = std::not_fn(&prime); std::prime(13); // 'true' değer döndürecektir. fp(13); // 'false' değer döndürecektir. } * Örnek 4, //.. int main() { /* # OUTPUT # 9592 adet asal sayı vardır. 90408 adet asal olmayan sayı vardır. */ std::vector iVec(100'000); std::iota(iVec.begin(), iVec.end(), 0); std::cout << std::count_if(iVec.begin(), iVec.end(), isprime) << " adet asal sayı vardır.\n"; std::cout << std::count_if(iVec.begin(), iVec.end(), std::not_fn(isprime)) << " adet asal olmayan sayı vardır.\n"; return 0; } * Örnek 5, HATANIN SEBEBİNİ BUL. //.. int main() { /* # OUTPUT # error: no match for call to ‘(std::_Not_fn >) (std::__cxx11::basic_string&)’ // EVDE İNCELE */ size_t length = 5; auto myLambda = [length](const std::string& s){ s.size() == length; }; std::vector sVec(1000); std::generate(sVec.begin(), sVec.end(), []{ return rname() + " " + rfname(); }); std::vector destVec; std::copy_if(sVec.begin(), sVec.end(), std::back_inserter(destVec), std::not_fn(myLambda)); print(destVec, " | "); return 0; } >> 'iota()' : Bir adet 'range' ve bir adet değer almaktadır. İlgili 'range' içerisindeki öğeleri, almış olduğu değer ile başlatıyor ve birer arttırıyor. * Örnek 1, //.. int main() { /* # OUTPUT # 0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_ ----------------------------------------------------------------------------- */ std::vector iVec(15); std::iota(iVec.begin(), iVec.end(), 0); print(iVec, "_"); return 0; } >> 'min_element()', 'max_element()' ve 'minmax_element()' fonksiyonları: Aşağıdaki örneği inceleyelim. Unutmamalıyız ki karşılaştırma kriterini kendimiz de belirleyebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # [97] => 74 97 75 26 67 0 48 27 80 7 ----------------------------------------------------------------------------- */ std::vector iVec; fcs(iVec, 10, Irand{ 0, 100 }); std::cout << "[" << *std::max_element(iVec.begin(), iVec.end()) << "] => "; // Varsayılan olarak 'std::less()' kullanıldı. print(iVec); return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # [6] => 54 6 19 31 61 78 89 11 30 13 ----------------------------------------------------------------------------- */ std::vector iVec; fcs(iVec, 10, Irand{ 0, 100 }); std::cout << "[" << *std::min_element(iVec.begin(), iVec.end()) << "] => "; // Varsayılan olarak 'std::less()' kullanıldı. print(iVec); return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # [20 / 100] => 83 29 32 20 100 25 39 92 88 33 ----------------------------------------------------------------------------- */ std::vector iVec; fcs(iVec, 10, Irand{ 0, 100 }); std::cout << "[" << *(std::minmax_element(iVec.begin(), iVec.end()).first) << " / " << *(std::minmax_element(iVec.begin(), iVec.end()).second) << "] => "; print(iVec); return 0; } > Sınıfların üye elemanlarının gösterici tarafından gösterilmesi: Aşağıdaki örneği inceleyelim: * Örnek 1, //.. struct Data{ int x = 10; }; int main() { /* # OUTPUT # x : 10 x : 20 x : 30 */ Data myData; std::cout << "x : " << myData.x << "\n"; auto strPtr = &myData.x; // 'strPtr' is 'int*' *strPtr = 20; std::cout << "x : " << myData.x << "\n"; auto strMemPtr = &Data::x; // 'strMemPtr' is 'int Data::*' (myData.*strMemPtr) = 30; std::cout << "x : " << myData.x << "\n"; return 0; } * Örnek 2, //.. struct Data{ int x = 10; }; int main() { /* # OUTPUT # x : 10 x : 20 x : 30 x : 40 */ Data myData; std::cout << "x : " << myData.x << "\n"; auto strPtr = &myData.x; // 'strPtr' is 'int*' *strPtr = 20; std::cout << "x : " << myData.x << "\n"; auto strMemPtr = &Data::x; // 'strMemPtr' is 'int Data::*' (myData.*strMemPtr) = 30; std::cout << "x : " << myData.x << "\n"; auto myDataPtr = &myData; myDataPtr->*strMemPtr = 40; std::cout << "x : " << myData.x << "\n"; return 0; } > Sınıfların üye fonksiyonlarının gösterici tarafından gösterilmesi: Sınıfların 'static' üye fonksiyonları, 'this' göstericisine sahip olmadığından dolayı, onları 'global function' gibi düşünebiliriz. Eğer iş bu fonksiyonumuz 'static' DEĞİLSE, bizim ilgili sınıfı nitelememiz gerekmektedir. * Örnek 1, //.. class Myclass { public: static int func(int value) { std::cout << "value : " << value << "\n"; return value*value; } }; int main() { /* # OUTPUT # value : 12 value : 13 val / valTwo => 144 / 169 */ int (*myFuncPtr)(int) = &Myclass::func; // auto myFuncPtr = &Myclass::func; int val = myFuncPtr(12); // auto val = myFuncPtr(12); int valTwo = (*myFuncPtr)(13); // auto valTwo = (*myFuncPtr)(12); std::cout << "val / valTwo => " << val << " / " << valTwo << "\n"; return 0; } * Örnek 2, //.. class Myclass { public: static int func(int value) { std::cout << "value : " << value << "\n"; return value*value; } void foo(int value)const { std::cout << "value : " << value << "\n"; } }; int main() { /* # OUTPUT # value : 12 value : 13 val / valTwo => 144 / 169 value : 14 value : 15 value : 16 */ int (*myFuncPtr)(int) = Myclass::func; // '&' kullanmak mecburi değildir. // auto myFuncPtr = &Myclass::func; int val = myFuncPtr(12); // auto val = myFuncPtr(12); int valTwo = (*myFuncPtr)(13); // auto valTwo = (*myFuncPtr)(13); std::cout << "val / valTwo => " << val << " / " << valTwo << "\n"; void (Myclass::*myFuncPtrTwo)(int)const = &Myclass::foo; // '&' kullanmak mecburidir. // auto myFuncPtrTwo = &Myclass::foo; Myclass objOne; (objOne.*myFuncPtrTwo)(14); // 'static' olmadığından dolayı bir sınıf nesnesine ihtiyacımız vardır. Myclass* objPtr = new Myclass; ((*objPtr).*myFuncPtrTwo)(15); (objPtr->*myFuncPtrTwo)(16); return 0; } * Örnek 3, //.. class Myclass { public: void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } void f2(void) { std::cout << "Myclass::f2(void) was called.\n"; } void f3(void) { std::cout << "Myclass::f3(void) was called.\n"; } void print(void) { (this->*m_funcPtr)(); } // İlgili çağrının bu şekilde yapılması zorunlu. private: void(Myclass::*m_funcPtr)(void) = &Myclass::f1; // Bu noktada '&' zorunludur. }; int main() { /* # OUTPUT # Myclass::f1(void) was called. */ Myclass mx; mx.print(); return 0; } * Örnek 4, //.. class Myclass { public: void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } void f2(void) { std::cout << "Myclass::f2(void) was called.\n"; } void f3(void) { std::cout << "Myclass::f3(void) was called.\n"; } void print(void) { (this->*m_funcPtr)(); } // İlgili çağrının bu şekilde yapılması zorunlu. void set( void(Myclass::*otherFuncPtr)(void) ) { m_funcPtr = otherFuncPtr; } private: void(Myclass::*m_funcPtr)(void) = &Myclass::f1; // Bu noktada '&' zorunludur. }; int main() { /* # OUTPUT # Myclass::f1(void) was called. Myclass::f2(void) was called. Myclass::f3(void) was called. */ Myclass mx; mx.print(); mx.set(&Myclass::f2); mx.print(); mx.set(&Myclass::f3); mx.print(); return 0; } * Örnek 5, //.. class Myclass { public: void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } void f2(void) { std::cout << "Myclass::f2(void) was called.\n"; } void f3(void) { std::cout << "Myclass::f3(void) was called.\n"; } void f4(void) { std::cout << "Myclass::f4(void) was called.\n"; } void f5(void) { std::cout << "Myclass::f5(void) was called.\n"; } }; using MyclassFuncPtr = void(Myclass::*)(void); int main() { /* # OUTPUT # Myclass::f1(void) was called. Myclass::f2(void) was called. Myclass::f3(void) was called. Myclass::f4(void) was called. Myclass::f5(void) was called. */ MyclassFuncPtr arrayPtr[] = { &Myclass::f1, &Myclass::f2, &Myclass::f3, &Myclass::f4, &Myclass::f5 }; Myclass mx; for(auto index : arrayPtr) (mx.*index)(); return 0; } > Ödev Sorusu: Bir metin içerisinde açılan parantezlerin kapatıldığının teyit edilmesi; uyumsuzluk durumunun tespit edilmesi. (İpucu: her açılan parantezi 'stack' içerisinde gönderiyoruz. Kapanan parantez gördüğümüz zaman 'stack'in en üstündeki ile karşılaştırıyoruz. Aynısı ise onu kaptan çıkartıyoruz. Değilse uyumsuzluk vardır.) /*============================================================================================================*/ (33_09_01_2020) > 'STL' içerisindeki kaplar (devam) : >> Dinamik Ömürlü Nesneler ve Akıllı Göstericiler: >>> Dinamik Ömürlü Nesneler: 'new', 'new[]', 'nothrow new' ve 'placement new' operatörleri kullanılarak Dinamik Ömürlü Nesne elde edebiliyoruz. Bunlardan 'new' operatörü ile bir adet nesne, 'new[]' ile ardışık birden fazla öğeye sahip olabiliyoruz. Yine bu gruptaki 'nothrow new' ise 'new' operatörünün 'exception throw' ETMEYEN HALİYKEN, 'placement new' ise BİZİM İSTEDİĞİMİZ ALANDA yeni bir öğe meydana getiren operatördür. İlgili dinamik ömürlü değişkenimizi/nesnemizin hayatını sonlandırmak için de 'delete', 'delete[]' gibi operatörler kullanılmaktadır. >>>> 'new' operatörü: Velevki bizler bir sınıf nesnesi hayata getirmek isteyelim yada 'primitive' türden bir değişken. İlk önce 'operator new()' fonksiyonu çağrılmaktadır. Eğer iş bu operatör istenilen büyüklükte bellek alanı elde edemezse 'exception throw' etmektedir. İlgili bellek alanının elde edilmesi durumunda ise, sınıf türleri için, 'Ctor.' fonksiyonuna çağrıda bulunulmaktadır. Bizler kullanıcı olarak bu 'operator new()' fonksiyonunu 'overlaod' edebiliriz. İlk yöntem global bir 'operator new()' fonksiyonu tanımlamak ki bu durumda bizimki çağrılacaktır. Burada 'operator delete' yazmazsak, standart olan çağrılacaktır. Eğer yazarsak bizimkisi çağrılacaktır. İkinci yöntem ise sınıfımıza 'static' bir 'operator new()' fonksiyonu yazmaktır. Böylelikle o sınıfımız türünden dinamik bir nesne hayata getirdiğimiz zaman, bizim yazdığımız çağrılacaktır. Fakat burada bizler 'operator delete' DE YAZMAK ZORUNDAYIZ. >>>> 'delete' operatörü: Bu ise ilk önce sınıf türünden bir nesne için onun 'Dtor.' fonksiyonunu çağırmaktadır. Sonrasında da 'operator delete()' fonksiyonu çağrılmaktadır. * Örnek 1, Global 'operator new()' fonksiyonu: //.. void* operator new(size_t size) { std::cout << "The specializated operator new(" << size << ") was called.\n"; if(void * vp{ std::malloc(size) }; vp) { std::cout << "Address of allocated block of memory : " << vp << "\n"; return vp; } else { std::cout << "Not enough block of memory exists.\n"; throw std::bad_alloc{}; } } void operator delete(void* vp) { std::cout << "Address of allocated block of memory : " << vp << "\n"; if(vp) std::free(vp); } * Örnek 2, Aşağıdaki örnekleri sırasıyla inceleyelim. //.. struct Data{ char buffer[1024*1024]{}; }; int main() { /* # OUTPUT # hata => bad allocation size : 1898 */ std::vector vec; try{ for(;;) { vec.push_back(new Data); // std::cout << "."; } } catch(const std::exception& ex) { std::cout << "hata => " << ex.what() << "\n"; } std::cout << "size : " << vec.size() << "\n"; // Çıktıda da görüldüğü üzere belli bir noktada yeterli alan ayrılamadığı için hata gönderildi. // Peki arka planda esas dönen şey nedir? return 0; } * Örnek 3, //.. struct Data { char buffer[1024 * 1024]{}; }; void myHiddenFunc() { static int counter{}; std::cout << "myHiddenFunc was called " << ++counter << " times.\n"; } int main() { /* # OUTPUT # myHiddenFunc was called 1 times. myHiddenFunc was called 2 times. myHiddenFunc was called 3 times. //... */ std::vector vec; std::set_new_handler(&myHiddenFunc); // Bu fonksiyon çağrısı ile yeterli boyut olmadığında, benim fonksiyonum çağrılacaktır. try { for (;;) { vec.push_back(new Data); // std::cout << "."; } } catch (const std::exception& ex) { std::cout << "hata => " << ex.what() << "\n"; } std::cout << "size : " << vec.size() << "\n"; // Yeterli alan tahsis edilemeyeği için artık benim atamış olduğum fonksiyon sonsuz kere çağrılacaktır. return 0; } * Örnek 4, //.. char* myBigBuffer = new char[100'000'000]; struct Data { char buffer[1024 * 1024]{}; }; void myHiddenFunc() { static int counter{}; std::cout << "myHiddenFunc was called " << ++counter << " times.\n"; (void)getchar(); if (counter == 5) delete[] myBigBuffer; } int main() { /* # OUTPUT # ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................................................................................................................ ................... myHiddenFunc was called 1 times. myHiddenFunc was called 2 times. myHiddenFunc was called 3 times. myHiddenFunc was called 4 times. myHiddenFunc was called 5 times. .........................................................................................myHiddenFunc was called 6 times. myHiddenFunc was called 7 times. //... */ std::vector vec; std::set_new_handler(&myHiddenFunc); try { for (;;) { vec.push_back(new Data); std::cout << "."; } } catch (const std::exception& ex) { std::cout << "hata => " << ex.what() << "\n"; } std::cout << "size : " << vec.size() << "\n"; // İşte benim atamış olduğum bu fonksiyon, başka bir göstericinin kullandığı bellek alanını geri verdiği için, // 'main()' içerisindeki '.push_back()' için yer açmış oldu. // Benim atamış olduğum bu fonksiyon işini yapamazsa başka bir fonksiyonu da bu iş için göreve çağırabilir. return 0; } * Örnek 5, //.. struct Data { char buffer[1024 * 1024]{}; }; void myHelperHiddenFunc() { static int counter{}; std::cout << "myHelperHiddenFunc was called " << ++counter << " times.\n"; (void)getchar(); } void myHiddenFunc() { static int counter{}; std::cout << "myHiddenFunc was called " << ++counter << " times.\n"; (void)getchar(); if (counter == 5) std::set_new_handler(&myHelperHiddenFunc); } int main() { /* # OUTPUT # .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... .......................................................................................................... ................................................................................................. myHiddenFunc was called 1 times. myHiddenFunc was called 2 times. myHiddenFunc was called 3 times. myHiddenFunc was called 4 times. myHiddenFunc was called 5 times. myHelperHiddenFunc was called 1 times. myHelperHiddenFunc was called 2 times. myHelperHiddenFunc was called 3 times. myHelperHiddenFunc was called 4 times. myHelperHiddenFunc was called 5 times. myHelperHiddenFunc was called 6 times. */ std::vector vec; std::set_new_handler(&myHiddenFunc); try { for (;;) { vec.push_back(new Data); std::cout << "."; } } catch (const std::exception& ex) { std::cout << "hata => " << ex.what() << "\n"; } std::cout << "size : " << vec.size() << "\n"; return 0; } * Örnek 6, //.. struct Data { char buffer[1024 * 1024]{}; }; void myHiddenFunc() { static int counter{}; std::cout << "myHiddenFunc was called " << ++counter << " times.\n"; (void)getchar(); if (counter == 5) { // throw std::bad_alloc{}; std::set_new_handler(nullptr); } } int main() { /* # OUTPUT # ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......................................................................................................... ......... myHiddenFunc was called 1 times. myHiddenFunc was called 2 times. myHiddenFunc was called 3 times. myHiddenFunc was called 4 times. myHiddenFunc was called 5 times. hata => bad allocation size : 1899 */ std::vector vec; std::set_new_handler(&myHiddenFunc); try { for (;;) { vec.push_back(new Data); std::cout << "."; } } catch (const std::exception& ex) { std::cout << "hata => " << ex.what() << "\n"; } std::cout << "size : " << vec.size() << "\n"; // Bir diğer yöntemi ise ya 'exception throw' etmek ya da 'std::set_new_handler()' fonksiyonuna // 'nullptr' geçmek ki bu da yine 'std::bad_alloc' türünden bir hata nesnesi fırlatacaktır. return 0; } >>>> Bu altı örnekten de anlaşılacağı üzere yeterli bellek alanı yoksa ve 'std::set_new_handler()' fonksiyonunu çağırarak arka plandaki 'handler' vazifeli gösterici 'set' EDİLMEMİŞSE 'std::bad_alloc' türünden bir hata nesnesi gönderilecektir. Eğer bizler arka plandaki bu 'handler' vazifeli göstericiye kendi şahsımıza ait bir fonksiyon atarsak, devreye bu fonksiyon girecektir. Artık yeterli bellek alanının tahsisi ile artık bizim fonksiyonumuz ilgilenecektir. Bu durumda bizim fonksiyonumuz 'std::bad_alloc' sınıf türünden bir hata nesnesi fırlatabilir, bir başka kendi fonksiyonumuzu arka plandaki 'handler' göstericiye atayarak bellek tahsisi görevini ona devredebilir veya 'std::set_new_handler()' fonksiyonuna 'nullptr' değeri geçerek 'std::bad_alloc' sınıf türünden bir hata gönderilmesini sağlatabilir. Bütün bunlara ek olarak arka plandaki 'handler' vazifeli göstericinin gösterdiği fonksiyonu da 'std::get_new_handler()' isimli fonksiyon ile elde edebiliriz. * Örnek 7, Sınıfın üye fonksiyonu olacak şekilde 'operator new()' fonksiyonunun yazılması: 'operator new()' ve 'operator delete()' fonksiyonu 'static' bir ÜYE FONKSİYONDUR HER NE KADAR İMZASINDA 'static' YAZMASAK BİLE. //.. class Myclass{ public: Myclass()=default; /* static */ void* operator new(size_t size) { if(auto* voidPtr = std::malloc(size); voidPtr != nullptr) { std::cout << voidPtr << " operator new(" << size << ") was called.\n"; return voidPtr; } else { throw std::bad_alloc{}; } } /* static */ void operator delete(void* voidPtr) { std::cout << "void operator delete(" << voidPtr << ") was called.\n"; if(voidPtr) { std::free(voidPtr); voidPtr = nullptr; } } private: char buffer[1024]; }; int main() { /* # OUTPUT # Name : Ahmet 0x559ea9f3f2f0 operator new(1024) was called. void operator delete(0x559ea9f3f2f0) was called. */ { auto newName = new std::string("Ahmet"); std::cout << "Name : " << *newName << "\n"; } { auto newName = new Myclass; delete newName; } // Çıktıda da görüldüğü üzere sadece 'Myclass' sınıfı için bizim yazdığımız 'operator new()' ve 'operator delete()' // fonksiyonları çağrıldı. return 0; } >>>> Dinamik ömürlü nesneler, dilin araçları kullanıldığında standart olarak 'heap' alanında hayata gelirler. Fakat bizler 'operator new()' ve 'operator delete()' fonksiyonlarını özelleştirirsek 'heap' yerine başka alanın kullanılmasını sağlayabiliriz. * Örnek 1, //.. // Myclass.h class Myclass { public: void* operator new(size_t size); void operator delete(void* voidPtr); static constexpr size_t maxObj = 100; // Maksimum 100 eleman tutulacak şekilde aşağıdaki 'buffer' ve 'flags' dizileri ayarlanacaktır. private: static unsigned char buffer[]; // 'Myclass' sınıf türünden nesneler bu bellek alanında hayata gelecekler. Bildirim yaptığımız için dizinin // boyutunu belirtmedik. static bool flags[]; // 'buffer' bellek alanındaki boş alanların bilgisini tutulacaktır. char message[256]{}; // Her bir 'Myclass' sınıf türünden nesnenin bünyesinde barındıracağı mesaj. }; // Myclass.cpp //.. unsigned char Myclass::buffer[Myclass::maxObj * sizeof(Myclass)]{}; bool Myclass::flags[Myclass::maxObj]{}; void* Myclass::operator new(size_t size) { if(auto iter = std::find(std::begin(flags), std::end(flags), false); iter == std::end(flags)) { std::cerr << "Yeterli bellek alanı mevcut değildir. Hata nesnesi gönderiliyor..."; throw std::bad_alloc{}; } else { auto location = std::distance(std::begin(flags), iter); flags[location] = true; return buffer + location * sizeof(Myclass); // 'flags' dizisindeki boş indisi elde ettikten sonra, gösterici aritmetiği ile 'buffer' dizisinde // aynı lokasyonu döndürüyoruz. } } void Myclass::operator delete(void* voidPtr) { if(!voidPtr) return; auto location = (static_cast(voidPtr) - buffer) / sizeof(Myclass); // 'static_cast' yerine 'reinterpret_cast' de kullanabiliriz. Çünkü 'void*' için iki dönüşüm de legal. flags[location] = false; } // main.cpp //.. int main() { /* # OUTPUT # Yeterli bellek alanı mevcut değildir. Hata nesnesi gönderiliyor...Hata yakalandi... => std::bad_alloc */ std::vector myVec; for(size_t i{}; i < Myclass::maxObj; ++i) { myVec.push_back(new Myclass); } try { auto newObj = new Myclass; // myVec.push_back(newObj); auto newObjTwo = ::new Myclass; // Artık standart kütüphanedeki 'operator new()' çağrılacaktır. } catch(const std::bad_alloc& ex) { std::cout << "Hata yakalandi... => " << ex.what() << "\n"; } return 0; } >>>> 'new[]' operatörü : Ardışık şekilde bellekte yer ayırmak için kullanılır. * Örnek 1, //.. class Data{ public: Data() { std::cout << "Address : " << this << "\n"; } ~Data() { std::cout << "Address : " << this << "\n"; } }; int main() { /* # OUTPUT # Size : 5 Address : 0x56270b3046d8 Address : 0x56270b3046d9 Address : 0x56270b3046da Address : 0x56270b3046db Address : 0x56270b3046dc */ size_t size{}; std::cout << "Size : "; std::cin >> size; auto objOne = new Data[size]; // Görüldüğü gibi sabit bir sayı gerekmemektedir. return 0; } * Örnek 2, //.. class Data{ public: Data() { std::cout << "Address : " << this << "\n"; } ~Data() { std::cout << "Address : " << this << "\n"; } private: char buffer[16]{}; }; int main() { /* # OUTPUT # Size : 5 Address : 0x55bd4f4ca6d8 Address : 0x55bd4f4ca6e8 Address : 0x55bd4f4ca6f8 Address : 0x55bd4f4ca708 Address : 0x55bd4f4ca718 */ size_t size{}; std::cout << "Size : "; std::cin >> size; auto objOne = new Data[size]; return 0; } * Örnek 3, //.. class Data{ public: Data() { std::cout << "Address : " << this << "\n"; } ~Data() { std::cout << "Address : " << this << "\n"; } private: char buffer[16]{}; }; int main() { /* # OUTPUT # Size : 3 Address : 0x55fd410326d8 Address : 0x55fd410326e8 Address : 0x55fd410326f8 Address : 0x55fd410326f8 Address : 0x55fd410326e8 Address : 0x55fd410326d8 */ size_t size{}; std::cout << "Size : "; std::cin >> size; auto objOne = new Data[size]; delete[] objOne; // delete ObjOne; // 'Tanımsız Davranış' return 0; } >>>> 'placement new' : Nesnemizi bizim belirlediğimiz alanda hayata getirmek için kullanacağız. * Örnek 1, //.. class Data{ public: Data() { std::cout << "Address : " << this << "\n"; } ~Data() { std::cout << "Address : " << this << "\n"; } private: char buffer[16]{}; }; int main() { /* # OUTPUT # Address : 0x7fff2be19290 Address : 0x7fff2be19290 Address : 0x7fff2be19290 */ char buffer[sizeof(Data)]; std::cout << "Address : " << static_cast(buffer) << "\n"; Data* objPtr = new(buffer)Data; // Artık yukarıdaki 'buffer' isimli bellek alanında nesne oluşturulacaktır. // delete objPtr; // 'run-time error : double free or corruption (out)' // delete[] objPtr // 'run-time error' // İlgili bellek alanını geri vermek için gereken tek yol, 'Dtor.' fonksiyonunu çağırmaktır. objPtr->~Data(); // 'Dtor.' fonksiyonunun doğrudan çağrılmasını gerektiren senaryodur. return 0; } >>>> 'nothrow new' : Hata göndermeyen 'operator new' fonksiyonudur. * Örnek 1, //.. class Data{ public: Data() { std::cout << "Address : " << this << "\n"; } ~Data() { std::cout << "Address : " << this << "\n"; } private: char buffer[1024*1024]{}; }; int main() { /* # OUTPUT # //... Bellek yetersiz. main devam ediyor. */ int counter{}; for(;;) { auto objPtr = new(std::nothrow)Data; // 'nothrow', 'nothrow_t' türünden bir 'constexpr' nesnenin ismidir. std::cout << ++counter << "\n"; if(objPtr == nullptr) { std::cout << "Bellek yetersiz.\n"; break; } } std::cout << "main devam ediyor.\n"; return 0; } >> Akıllı Göstericiler: Gerçekte 'pointer' olmayan fakat 'pointer-like' gibi davranan sınıflar için kullanılmaktadır. Aslında temel seviye göstericilerin kullanımları şu problemleri de beraberinde getirmektedir; ilgili gösterici otomatik ömürlü bir nesneyi mi göstermekte yoksa 'static' ömürlü bir nesneyi mi veya daha öncesinde 'delete' edilmiş mi edilmemiş mi ve edilmiş ise başka kodların aynı nesneyi kullanıp kullanmadığından ne kadar eminiz vs. İşte bu sorunların çözümü Akıllı Gösterici sınıf şablonudur. Her ne kadar 'iterators' da 'pointer-like' gibi davransalar da onlar 'smart-pointer' değildirler. Akıllı Göstericiler bünyesinde iki adet temel gösterici, bir tane de yardımcı gösterici barındırmaktadır. Bunlar 'std::unique_ptr', 'std::shared_ptr' ve 'std::weak_ptr' sınıf şablonlarıdır. >>> 'std::unique_ptr<>()' : Aşağıdaki temsili implementasyonu inceleyelim. Özünde 'raw-pointer' sarmalayan bir sınıftır. * Örnek 1, //.. template> // İkinci şablon argümanı aslında bir 'deleter'. Standart olankinde 'std::default_delete' sınıf şablonu // kullanılmakta. // Eğer amacımız sadece 'delete' etmek ise bu standar sınıf şablonu işimizi görecektir. Aksi halde kendi 'deleter' // sınıfımızı argüman olarak geçmeliyiz. class UniquePtr{ public: //.. UniquePtr(T* ptr) : mp{ptr} {} UniquePtr(const UniquePtr& other) = delete; UniquePtr& operator=(const UniquePtr& other) = delete; UniquePtr(UniquePtr&& other) { //.. } UniquePtr& operator=(UniquePtr&& other) { //.. } ~UniquePtr() { if(mp) D{}(mp); } private: T* mp; }; template class DefaultDelete{ public: void operator()(T* ptr) { delete ptr; } }; int main() { UniquePtr uptr; //... } * Örnek 2, //.. int main() { /* # OUTPUT # sizeof(int*) / sizeof(std::unique_ptr) => 8 / 8 */ std::cout << "sizeof(int*) / sizeof(std::unique_ptr) => " << sizeof(int*) << " / " << sizeof(std::unique_ptr) << "\n"; return 0; } * Örnek 3, //.. class Triple{ public: Triple(int a = 0, int b = 0, int c = 0) : m_a{a}, m_b{b}, m_c{c} { std::cout << this << " adresinde yeni bir nesne hayata geldi.\n"; } ~Triple() { std::cout << this << " adresindeki nesnenin hayatı sona erdi.\n"; } void set(int a = 1, int b = 1, int c = 1) { m_a = a; m_b = b; m_c = c; } friend std::ostream& operator<<(std::ostream& os, const Triple& other) { return os << "(" << other.m_a << ", " << other.m_b << ", " << other.m_c << ")\n"; } private: int m_a, m_b, m_c; }; int main() { /* # OUTPUT # main basladi... 0x55b410ee52c0 adresinde yeni bir nesne hayata geldi. 0x55b410ee52c0 adresindeki nesnenin hayatı sona erdi. main devam ediyor... */ std::cout << "main basladi...\n"; if(1) { // std::unique_ptr uPtr = new Triple(17, 9, 1993); // 'unique_ptr' sınıfının bu parametreli 'Ctor.' fonksiyonu 'explicit' // olduğundan SENTAKS HATASI. std::unique_ptr uPtr{new Triple(17, 9, 1993)}; } std::cout << "main devam ediyor...\n"; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # Ben bosum... 0x55fe8f0c72c0 adresinde yeni bir nesne hayata geldi. Ben bos değilim... 0x55fe8f0c72c0 adresindeki nesnenin hayatı sona erdi. */ std::unique_ptr uPtr; // if(!uPtr.operator bool()) // if(uPtr == nullptr) if(!uPtr) std::cout << "Ben bosum...\n"; else std::cout << "Ben bos değilim...\n"; std::unique_ptr uPtrTwo{ new Triple }; if(!uPtrTwo) std::cout << "Ben bosum...\n"; else std::cout << "Ben bos değilim...\n"; return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # */ std::unique_ptr uPtr; try { std::cout << *uPtr << "\n"; // İlgili 'uPtr' nesnesi boş olduğunda 'dereference' edilmesi 'Tanımsız Davranış' meydana getirir. } catch(const std::exception& ex) { std::cout << ex.what() << "\n"; // Çıktıdan da görüldüğü üzere herhangi bir hata fırlatmaz. } return 0; } * Örnek 6, //.. template std::unique_ptr MakeUnique(Args&&...args) { return std::unique_ptr{ new T(std::forward(args)...) }; } int main() { /* # OUTPUT # 0x55ac10bd2eb0 adresinde yeni bir nesne hayata geldi. (1, 2, 3) 0x55ac10bd2eb0 adresindeki nesnenin hayatı sona erdi. */ auto uPtr = MakeUnique(1, 2, 3); std::cout << *uPtr << "\n"; return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # 0x55a34eac7eb0 adresinde yeni bir nesne hayata geldi. */ auto uPtr = std::make_unique(1, 2, 3); auto rawPtr = uPtr.release(); // Artık 'uPtr' isimli gösterici BOŞ DURUMDADIR. Mülkiyet ilgili 'rawPtr' isimli göstericiye geçmiştir. // delete rawPtr; // Bu çağrıyı yapmak zorundayız. Aksi halde 'delete' işlemi gerçekleşmeyecektir. return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # 0x557007708eb0 adresinde yeni bir nesne hayata geldi. 0x557007708eb0 adresindeki nesnenin hayatı sona erdi. 0x557007708eb0 adresindeki nesnenin hayatı sona erdi. free(): double free detected in tcache 2 */ auto uPtr = std::make_unique(1, 2, 3); auto rawPtr = uPtr.get(); delete rawPtr; // 'uPtr' isimli nesne boşaltılmadığı için ilgili kaynak 'uPtr' üzerinden geri verildi. // İşte bu sebepten dolayı => free(): double free detected in tcache 2 return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # 0x556c928fdeb0 adresinde yeni bir nesne hayata geldi. (1, 2, 3) 0x556c928fe2e0 adresinde yeni bir nesne hayata geldi. 0x556c928fdeb0 adresindeki nesnenin hayatı sona erdi. (10, 20, 30) 0x556c928fe2e0 adresindeki nesnenin hayatı sona erdi. */ auto uPtr = std::make_unique(1, 2, 3); std::cout << *uPtr; uPtr.reset(new Triple(10, 20, 30)); std::cout << *uPtr; uPtr.reset(); // Mülkiyet hakkı olduğu ilgili nesnenin hayatı sona erdi fakat 'uPtr' nesnesinin hayatı devam etmekte. // uPtr.reset(nullptr); // uPtr = nullptr; return 0; } /*============================================================================================================*/ (34_10_01_2021) > 'STL' içerisindeki kaplar (devam) : >> Akıllı Göstericiler (devam) : >>> Aynı kaynağın iki farklı akıllı gösterici tarafından gösterilmediğinden emin olmalıyız. * Örnek 1, //.. int main() { /* # OUTPUT # 0x55eb84757eb0 adresinde yeni bir nesne hayata geldi. (9, 8, 7) 0x55eb84757eb0 adresindeki nesnenin hayatı sona erdi. 0x55eb84757eb0 adresindeki nesnenin hayatı sona erdi. free(): double free detected in tcache 2 */ auto ptr{ new Triple{1, 2, 3} }; std::unique_ptr upx{ ptr }; std::unique_ptr upy{ ptr }; upx->set(9, 8, 7); std::cout << *upy << "\n"; // Çıktıda da görüldüğü üzere iki defa 'Dtor.' fonksiyonu çağrıldı. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 0x564f09901eb0 adresinde yeni bir nesne hayata geldi. 0x564f09901eb0 adresindeki nesnenin hayatı sona erdi. 0x564f09901eb0 adresindeki nesnenin hayatı sona erdi. free(): double free detected in tcache 2 */ Triple* ptr = new Triple{ 1, 2, 3 }; { std::unique_ptr upx{ ptr }; } std::unique_ptr upy{ ptr }; // 'ptr' is a dangling-pointer now. return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # 0x55c1e26b3eb0 adresinde yeni bir nesne hayata geldi. 0x55c1e26b3eb0 adresindeki nesnenin hayatı sona erdi. 0x55c1e26b3eb0 adresindeki nesnenin hayatı sona erdi. free(): double free detected in tcache 2 */ auto upx { std::make_unique(1, 2, 3) }; Triple* ptr{ upx.get() }; std::unique_ptr upy{ ptr }; // Aynı kaynak iki farklı gösterici tarafından paylaşılmıştır. 'Tanımsız Davranış'. return 0; } >>> Dinamik ömürlü olmayan bir nesne adresini akıllı göstericilerde kullanmayınız. * Örnek 1, //.. int main() { /* # OUTPUT # 0x7ffd906e2c8c adresinde yeni bir nesne hayata geldi. 0x7ffd906e2c8c adresindeki nesnenin hayatı sona erdi. free(): invalid pointer */ Triple t{ 1, 2, 3 }; std::unique_ptr upx{ &t }; // Çıktıda görüldüğü üzere 'Run Time' hatası alıyoruz. return 0; } >>> '.release()' fonksiyonu ile ilgili adresi başka bir 'raw-pointer' a aktardığımız zaman 'delete' işlemini yapmalıyız. * Örnek 1, //.. int main() { /* # OUTPUT # 0x557d50b50eb0 adresinde yeni bir nesne hayata geldi. upx is occupied upx is empty ptr => (1, 2, 3) */ auto upx{ std::make_unique(1, 2, 3) }; std::cout << "upx is " << (upx ? "occupied" : "empty") << "\n"; auto ptr = upx.release(); std::cout << "upx is " << (upx ? "occupied" : "empty") << "\n"; std::cout << "ptr => " << *ptr << "\n"; std::cout << "upx => " << *upx << "\n"; // 'Tanımsız Davranış' // Çıktıdan da görüldüğü üzere ilgili adresler geri verilmed. // delete ptr; // Bunu yazmak zorundayız. return 0; } >>> Kendi 'custom-deleter' fonksiyonu yazarak, nesnenin hayatını o fonksiyon ile bitirtebiliriz. * Örnek 1, //.. struct CustomDeleter{ public: void operator()(Triple* other) { std::cout << "My custom-deleter function was called. " << *other << " will be deleted.\n"; delete other; } }; int main() { /* # OUTPUT # 0x55c677477eb0 adresinde yeni bir nesne hayata geldi. My custom-deleter function was called. (1, 2, 3) will be deleted. 0x55c677477eb0 adresindeki nesnenin hayatı sona erdi. */ std::unique_ptr upx{ new Triple{1, 2, 3} }; return 0; } * Örnek 2, //.. void CustomDeleterFunc(Triple* other) { std::cout << "My custom-deleter function was called. " << *other << " will be deleted.\n"; delete other; } int main() { /* # OUTPUT # 0x5567dee02eb0 adresinde yeni bir nesne hayata geldi. My custom-deleter function was called. (1, 2, 3) will be deleted. 0x5567dee02eb0 adresindeki nesnenin hayatı sona erdi. */ std::unique_ptr upx{ new Triple{1, 2, 3}, CustomDeleterFunc }; return 0; } * Örnek 3, //.. auto CustomDeleterLambda = [](Triple* other) { std::cout << "My custom-deleter function was called. " << *other << " will be deleted.\n"; delete other; }; int main() { /* # OUTPUT # 0x555cf0bfceb0 adresinde yeni bir nesne hayata geldi. My custom-deleter function was called. (1, 2, 3) will be deleted. 0x555cf0bfceb0 adresindeki nesnenin hayatı sona erdi. */ std::unique_ptr upx{ new Triple{1, 2, 3}, CustomDeleterLambda }; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # // Yeni bir dosya oluşturuldu... */ auto f = [](FILE* other){ std::cout << "Dosya kapatiliyor...\n"; fclose(other); }; std::unique_ptr upx{ fopen("deneme.txt", "w"), f}; // Eğer yukarıdaki gibi bir 'lambda-expression' kullanmasaydık, dosya kapatılmadan evvel 'delete' operatörü // çağrılacaktı. return 0; } * Örnek 5, //.. struct Deck{ //.. }; Deck* createDeck(){ std::cout << "A deck has been created.\n"; return new Deck; } void closeDeck(Deck* other) { std::cout << "A deck has been destroyed.\n"; delete other; } int main() { /* # OUTPUT # A deck has been created. A deck has been destroyed. */ // Senaryo - I auto f = [](Deck* other){ closeDeck(other); }; std::unique_ptr upx{ createDeck(), f }; //... // Yukarıdaki kod bölümünde herhangi bir hata fırlatıldığında, 'stack-unwinding' gereği, 'Dtor.' fonksiyonları // çağrılacaktır. Bu fonksiyon da bizim 'custom-deleter' fonksiyonumuzu çağıracaktır. // Senaryo - II auto p = createDeck(); //... closeDeck(p); // Yukarıdaki kod bölümünde herhangi bir hata fırlatıldığında programın akışı 'closeDeck()' satırına gelmeyeceği // için duruma göre 'resource leak', duruma göre 'memory leak' oluşacaktı. return 0; } * Örnek 6, //.. struct Deck{ //.. }; Deck* createDeck(){ std::cout << "A deck has been created.\n"; return new Deck; } void closeDeck(Deck* other) { std::cout << "A deck has been destroyed.\n"; delete other; } struct DeckCleaner{ void operator()(Deck* other) { closeDeck(other); } }; int main() { /* # OUTPUT # A deck has been created. A deck has been destroyed. */ std::unique_ptr upx{ createDeck() }; return 0; } >>> 'std::make_unique<>()' fonksiyonunu da sarmalayabiliriz. * Örnek 1, //.. std::unique_ptr createDeck(int a, int b, int c) { std::cout << "A deck has been created.\n"; return std::make_unique(a, b, c); } int main() { /* # OUTPUT # A deck has been created. 0x5587c64ae2c0 adresinde yeni bir nesne hayata geldi. A deck has been created. 0x5587c64ae2e0 adresinde yeni bir nesne hayata geldi. (1, 2, 3) (3, 2, 1) 0x5587c64ae2e0 adresindeki nesnenin hayatı sona erdi. 0x5587c64ae2c0 adresindeki nesnenin hayatı sona erdi. */ auto deckOne = createDeck(1, 2, 3); auto deckTwo = createDeck(3, 2, 1); std::cout << *deckOne << "\n" << *deckTwo << "\n"; return 0; } >>> 'Run-time Polymorphism' için de kullanılabilir. * Örnek 1, //.. std::unique_ptr CreateRandomCar() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution dist{ 0, 3 }; switch(dist(eng)) { case 0: return std::make_unique(); case 1: return std::make_unique(); case 2: return std::make_unique(); case 3: return std::make_unique(); } return nullptr; } int main() { /* # OUTPUT # The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. The Tesla just started. The Tesla just started running. The Tesla just stopped. The Audi just started. The Audi just started running. The Audi just stopped. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. The Audi just started. The Audi just started running. The Audi just stopped. */ for(size_t i{}; i < 5 ; ++i) { auto myCar = CreateRandomCar(); myCar->start(); myCar->run(); myCar->stop(); } return 0; } >>> Bir kap içerisinde de akıllı göstericilerimizi tutabiliriz. * Örnek 1, //.. template struct CustomDeleter{ void operator()(T* other) { std::cout << "Deleting...\n"; delete other; } }; template using MyCustomUPtr = std::unique_ptr>; template using MyCustomUPtrVector = std::vector>; int main() { /* # OUTPUT # 0x55fe4c4e5eb0 adresinde yeni bir nesne hayata geldi. 0x55fe4c4e6300 adresinde yeni bir nesne hayata geldi. 0x55fe4c4e62e0 adresinde yeni bir nesne hayata geldi. (1, 2, 3) (4, 5, 6) (7, 8, 9) Deleting... 0x55fe4c4e5eb0 adresindeki nesnenin hayatı sona erdi. Deleting... 0x55fe4c4e6300 adresindeki nesnenin hayatı sona erdi. Deleting... 0x55fe4c4e62e0 adresindeki nesnenin hayatı sona erdi. */ MyCustomUPtr myPtr{ new Triple{ 1, 2, 3 } }; MyCustomUPtrVector myPtrVector; myPtrVector.push_back(std::move(myPtr)); myPtrVector.push_back(MyCustomUPtr{ new Triple{ 4, 5, 6 } }); myPtrVector.emplace_back( new Triple{ 7, 8, 9 } ); for(const auto& index : myPtrVector ) std::cout << *index << "\n"; return 0; } * Örnek 2, //.. template struct CustomDeleter{ void operator()(T* other) { std::cout << "Deleting...\n"; delete other; } }; template using MyCustomUPtr = std::unique_ptr>; template using MyCustomUPtrVector = std::vector>; int main() { /* # OUTPUT # 0x55612eb18eb0 adresinde yeni bir nesne hayata geldi. 0x55612eb19300 adresinde yeni bir nesne hayata geldi. 0x55612eb192e0 adresinde yeni bir nesne hayata geldi. size : 3; (1, 2, 3) (4, 5, 6) (7, 8, 9) Deleting... 0x55612eb18eb0 adresindeki nesnenin hayatı sona erdi. size : 2; (4, 5, 6) (7, 8, 9) Deleting... 0x55612eb19300 adresindeki nesnenin hayatı sona erdi. Deleting... 0x55612eb192e0 adresindeki nesnenin hayatı sona erdi. */ MyCustomUPtr myPtr{ new Triple{ 1, 2, 3 } }; MyCustomUPtrVector myPtrVector; myPtrVector.push_back(std::move(myPtr)); myPtrVector.push_back(MyCustomUPtr{ new Triple{ 4, 5, 6 } }); myPtrVector.emplace_back( new Triple{ 7, 8, 9 } ); std::cout << "size : " << myPtrVector.size() << ";\n"; for(const auto& index : myPtrVector ) std::cout << *index << " "; myPtrVector.erase(myPtrVector.begin()); std::cout << "size : " << myPtrVector.size() << ";\n"; for(const auto& index : myPtrVector ) std::cout << *index << " "; return 0; } * Örnek 3, //.. template struct CustomDeleter{ void operator()(T* other) { std::cout << "Deleting...\n"; delete other; } }; template using MyCustomUPtr = std::unique_ptr>; template using MyCustomUPtrVector = std::vector>; int main() { /* # OUTPUT # 0x559c50d84eb0 adresinde yeni bir nesne hayata geldi. 0x559c50d85300 adresinde yeni bir nesne hayata geldi. 0x559c50d852e0 adresinde yeni bir nesne hayata geldi. size : 3; (1, 2, 3) (4, 5, 6) (7, 8, 9) Deleting... 0x559c50d84eb0 adresindeki nesnenin hayatı sona erdi. Deleting... 0x559c50d85300 adresindeki nesnenin hayatı sona erdi. Deleting... 0x559c50d852e0 adresindeki nesnenin hayatı sona erdi. size : 0; */ MyCustomUPtr myPtr{ new Triple{ 1, 2, 3 } }; MyCustomUPtrVector myPtrVector; myPtrVector.push_back(std::move(myPtr)); myPtrVector.push_back(MyCustomUPtr{ new Triple{ 4, 5, 6 } }); myPtrVector.emplace_back( new Triple{ 7, 8, 9 } ); std::cout << "size : " << myPtrVector.size() << ";\n"; for(const auto& index : myPtrVector ) std::cout << *index << " "; myPtrVector.clear(); std::cout << "size : " << myPtrVector.size() << ";\n"; for(const auto& index : myPtrVector ) std::cout << *index << " "; return 0; } * Örnek 4, //.. template struct CustomDeleter{ void operator()(T* other) { std::cout << "Deleting...\n"; delete other; } }; template using MyCustomUPtr = std::unique_ptr>; template using MyCustomUPtrVector = std::vector>; int main() { /* # OUTPUT # 0x555ef035ceb0 adresinde yeni bir nesne hayata geldi. 0x555ef035d300 adresinde yeni bir nesne hayata geldi. 0x555ef035d2e0 adresinde yeni bir nesne hayata geldi. size : 3; (1, 2, 3) (4, 5, 6) (7, 8, 9) Deleting... 0x555ef035d2e0 adresindeki nesnenin hayatı sona erdi. size : 3; (1, 2, 3) (4, 5, 6) */ MyCustomUPtr myPtr{ new Triple{ 1, 2, 3 } }; MyCustomUPtrVector myPtrVector; myPtrVector.push_back(std::move(myPtr)); myPtrVector.push_back(MyCustomUPtr{ new Triple{ 4, 5, 6 } }); myPtrVector.emplace_back( new Triple{ 7, 8, 9 } ); std::cout << "size : " << myPtrVector.size() << ";\n"; for(const auto& index : myPtrVector ) std::cout << *index << " "; myPtrVector.at(2).reset(); std::cout << "size : " << myPtrVector.size() << ";\n"; for(const auto& index : myPtrVector ) { //if(index) std::cout << *index << " "; } // Kapta tutulan akıllı göstericilerin boş olup olmadığına bakılmaksızın 'dereference' edildiğinden, // yukarıdaki çıktı elde edildi. return 0; } >>> Üç farklı yapıda fabrika fonksiyonu olarak kullanılabilir. >>>> Geri dönüş değeri olmayan ve parametre olarak 'std::unique_ptr' alan bir fonksiyon, ki bu fonksiyon bir nevi 'sink' usülü çalışmaktadır. Böylelikle kendisine argüman olarak geçilen akıllı göstericilerin hayatını sonlandırmaktadır. >>>> Geri dönüş değeri akıllı gösterici olan ve parametre almayan bir fonksiyon, ki bu fonksiyonlar vesilesi ile kullanıcı dinamik ömürlü nesneler oluşturmaktadır. >>>> Hem geri dönüş değeri hem de parametresi akıllı gösterici olan fonksiyon, ki bu fonksiyonlar ise argüman olarak aldıkları göstericiyi işlemekte ve geri döndürmektedirler. * Örnek 1, //.. // Referans yolu ile argüman almadık çünkü argümanımızın hayatını sonlandıracağız. void sink(std::unique_ptr other) { std::cout << "The " << *other << " will be destroyed.\n"; } // Referans yolu ile dönmedik çünkü 'copy-ellision' oluştu. // Geri dönüş değerini 'shared_ptr' da saklayabilirdik. std::unique_ptr create(int a, int b, int c) { return std::make_unique(a, b, c); } std::unique_ptr handle(std::unique_ptr other) { other->set(4, 5, 6); // return other; return std::move(other); } int main() { /* # OUTPUT # 0x5649b027aeb0 adresinde yeni bir nesne hayata geldi. The (4, 5, 6) will be destroyed. 0x5649b027aeb0 adresindeki nesnenin hayatı sona erdi. */ std::unique_ptr uPtr{ create(1, 2, 3) }; std::unique_ptr uPtrTwo{ handle(std::move(uPtr)) }; sink(std::move(uPtrTwo)); return 0; } >>> Akıllı göstericinin sarmaladığı nesnenin adresini ekrana yazdırmak için direkt olarak sınıf nesnesini kullanabiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # 0x5637f28ddeb0 adresinde yeni bir nesne hayata geldi. &uPtr => 0x7ffc2ec7b100 uPtr.get() => 0x5637f28ddeb0 0x5637f28ddeb0 adresindeki nesnenin hayatı sona erdi. */ auto uPtr = std::make_unique( 1, 2, 3 ); std::cout << "&uPtr => " << &uPtr << "\n"; std::cout << "uPtr.get() => " << uPtr.get() << "\n"; return 0; } >>> Akıllı göstericilerin 'array' formu: Normal versiyonundan farklı bir arayüze sahiptir. Normal versiyonları 'T' şeklinde açılımı yaparken, 'array' formu için 'T[]' için 'partial specialization' açılımı kullanılmaktadır. * Örnek 1, //.. template struct CustomDeleter{ void operator()(T* other){ std::cout << "CustomDeleter was called...\n"; delete other; } }; template struct CustomDeleter{ void operator()(T* other){ std::cout << "CustomDeleter was called...\n"; delete[] other; }; }; int main() { /* # OUTPUT # main basladi... 0x5623af0262c8 adresinde yeni bir nesne hayata geldi. 0x5623af0262d4 adresinde yeni bir nesne hayata geldi. 0x5623af0262e0 adresinde yeni bir nesne hayata geldi. CustomDeleter was called... 0x5623af0262e0 adresindeki nesnenin hayatı sona erdi. 0x5623af0262d4 adresindeki nesnenin hayatı sona erdi. 0x5623af0262c8 adresindeki nesnenin hayatı sona erdi. main devam ediyor... */ std::cout << "main basladi...\n"; { auto p = new Triple[3]{ {1,1,1}, {2,2,2}, {3,3,3} }; std::unique_ptr> uPtr{p}; } std::cout << "main devam ediyor...\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # main basladi... 0x559477cc92c8 adresinde yeni bir nesne hayata geldi. 0x559477cc92d4 adresinde yeni bir nesne hayata geldi. 0x559477cc92e0 adresinde yeni bir nesne hayata geldi. MyCustomDeleter was called... 0x559477cc92e0 adresindeki nesnenin hayatı sona erdi. 0x559477cc92d4 adresindeki nesnenin hayatı sona erdi. 0x559477cc92c8 adresindeki nesnenin hayatı sona erdi. main devam ediyor... */ std::cout << "main basladi...\n"; { auto p = new Triple[3]{ {1,1,1}, {2,2,2}, {3,3,3} }; auto f = [](Triple* other){ std::cout << "MyCustomDeleter was called...\n"; delete[] other; }; std::unique_ptr uPtr{ p, f}; } std::cout << "main devam ediyor...\n"; return 0; } >>> 'shared_ptr' sınıf şablonu da tıpkı 'unique_ptr' sınıf şablonu gibi '.operator bool()' fonksiyonu içermektedir. >>> 'shared_ptr' sınıf şablonu kopyalamaya karşı açık bir sınıf şablonudur. Bünyesinde 'reference-counter' işlevi gören bir fonksiyon barındırmaktadır. Bu fonksiyonun geri dönüş değeri bir olduğunda, sarmalanan dinamik ömürlü nesnenin de hayatı sona erecektir. * Örnek 1, //.. int main() { /* # OUTPUT # 0x55b43fb7aeb0 adresinde yeni bir nesne hayata geldi. Reference Counter for spx => 1 Reference Counter for spx => 2 Reference Counter for spy => 2 Reference Counter for spx => 3 Reference Counter for spx => 3 Reference Counter for spz => 3 Reference Counter for spx => 2 Reference Counter for spy => 2 Reference Counter for spx => 1 0x55b43fb7aeb0 adresindeki nesnenin hayatı sona erdi. */ std::shared_ptr spx{ new Triple{1,1,1} }; std::cout << "Reference Counter for spx => " << spx.use_count() << "\n"; { auto spy{spx}; std::cout << "Reference Counter for spx => " << spx.use_count() << "\n"; std::cout << "Reference Counter for spy => " << spy.use_count() << "\n"; { auto spz{spy}; std::cout << "Reference Counter for spx => " << spx.use_count() << "\n"; std::cout << "Reference Counter for spx => " << spx.use_count() << "\n"; std::cout << "Reference Counter for spz => " << spz.use_count() << "\n"; } std::cout << "Reference Counter for spx => " << spx.use_count() << "\n"; std::cout << "Reference Counter for spy => " << spy.use_count() << "\n"; } std::cout << "Reference Counter for spx => " << spx.use_count() << "\n"; return 0; } >>> 'shared_ptr' sınıf şablonu, 'unique_ptr' sınıf şablonuna nazaran bünyesinde iki adet gösterici barındırmaktadır. Bunlardan bir ilki dinamik ömürlü nesnemizin adresini gösterirken, diğer ikincisi ise 'control-block' diye adlandırılan bloğun adresini tutmaktadır. Buradan hareketle ilgili 'shared_ptr' sınıf nesnemizi '->' operatörünün operandı yaptığımız zaman ilk göstericinin gösterdiği dinamik ömürlü nesneye erişirken, '.' operatörünün operandı yaptığımız zaman ise ikinci göstericinin gösterdiği 'control-block' ğuna erişmekteyiz. Son olarak unutmamalıyız ki ilk gösterici tarafından gösterilen dinamik ömürlü nesnemizin adresi aynı zamanda ikinci gösterici tarafından gösterilen 'control-block' içerisinde de tutulmaktadır. Yine bizler ilk olarak dinamik ömürlü bir nesne hayata getirsek, sonrasında da bu nesneyi kullanarak 'shared_ptr' sınıf türünden bir nesne hayata getirsek/atama yapsak, elimizde iki farklı adres bilgisi olacaktır. İlk adres bilgisi dinamik ömürlü nesnemizinki, ikinci adres bilgisi ise yukarıda bahsi geçen 'control-block' una ait adres bilgisidir. Fakat 'make_shared' fonksiyon şablonunu kullanarak 'shared_ptr' sınıf türünden bir nesne hayata getirirsek/atama yaparsak elimizde sadece ve sadece tek bir adet adres bilgisi olacaktır. Bu adres bilgisini kullanarak hem ilgili dinamik ömürlü nesneye hem de ilgili 'control-block' una ulaşabileceğiz. Buradan hareketle diyebiliriz ki 'shared_ptr' sınıf türünden nesne hayata getirirken 'make_shared' fonksiyon şablonunu kullanmamız bir takım maliyetlerden kurtulmamıza olanak sağlamaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # size of Triple* => 8 size of std::unique_ptr() => 8 size of std::shared_ptr() => 16 */ std::cout << "size of Triple* => " << sizeof(Triple*) << "\n"; std::cout << "size of std::unique_ptr() => " << sizeof(std::unique_ptr) << "\n"; std::cout << "size of std::shared_ptr() => " << sizeof(std::shared_ptr) << "\n"; return 0; } * Örnek 2, //.. void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } int main() { /* # OUTPUT # operator new(12) was called. A memory block has been occupied starting from : 0x564a906222c0 0x564a906222c0 adresinde yeni bir nesne hayata geldi. operator new(24) was called. A memory block has been occupied starting from : 0x564a906222e0 operator new(32) was called. A memory block has been occupied starting from : 0x564a90622300 0x564a90622310 adresinde yeni bir nesne hayata geldi. 0x564a90622310 adresindeki nesnenin hayatı sona erdi. operator delete() was called. A memory block will be given back, starting from : 0x564a90622300 0x564a906222c0 adresindeki nesnenin hayatı sona erdi. operator delete() was called. A memory block will be given back, starting from : 0x564a906222c0 operator delete() was called. A memory block will be given back, starting from : 0x564a906222e0 */ auto ptrOne{ new Triple{1,1,1} }; std::shared_ptr ptrTwo{ ptrOne }; std::shared_ptr ptrThree{ std::make_shared(2,2,2) }; // 'ptrThree' için tek bir allocation yapıldı. return 0; } * Örnek 3, //.. void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } int main() { /* # OUTPUT # operator new(12) was called. A memory block has been occupied starting from : 0x56262e8772c0 0x56262e8772c0 adresinde yeni bir nesne hayata geldi. operator new(24) was called. A memory block has been occupied starting from : 0x56262e8772e0 0x56262e8772c0 adresindeki nesnenin hayatı sona erdi. operator delete() was called. A memory block will be given back, starting from : 0x56262e8772c0 operator delete() was called. A memory block will be given back, starting from : 0x56262e8772e0 */ std::shared_ptr spx{ new Triple }; // İki kez allocation yapıldı. Bir tanesi kontrol bloğu için diğeri de dinamik ömürlü nesne için. return 0; } * Örnek 4, //.. void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } int main() { /* # OUTPUT # operator new(32) was called. A memory block has been occupied starting from : 0x5556bd48f2c0 0x5556bd48f2d0 adresinde yeni bir nesne hayata geldi. 0x5556bd48f2d0 adresindeki nesnenin hayatı sona erdi. operator delete() was called. A memory block will be given back, starting from : 0x5556bd48f2c0 */ std::shared_ptr spx{ std::make_shared() }; // Sadece bir kez allocation yapıldı. Gerek dinamik ömürlü nesne gerek kontrol bloğu aynı adreste. return 0; } * Örnek 5, //.. void* operator new(size_t n) { std::cout << "operator new(" << n << ") was called.\n"; void* vp = std::malloc(n); std::cout << "A memory block has been occupied starting from : " << vp << "\n"; if(!vp) throw std::bad_alloc{}; return vp; } void operator delete(void* vp) { std::cout << "operator delete() was called.\n"; std::cout << "A memory block will be given back, starting from : " << vp << "\n"; if(vp) free(vp); } int main() { /* # OUTPUT # operator new(12) was called. A memory block has been occupied starting from : 0x55fa924512c0 0x55fa924512c0 adresinde yeni bir nesne hayata geldi. operator new(24) was called. A memory block has been occupied starting from : 0x55fa924512e0 0x55fa924512c0 adresindeki nesnenin hayatı sona erdi. operator delete() was called. A memory block will be given back, starting from : 0x55fa924512c0 operator delete() was called. A memory block will be given back, starting from : 0x55fa924512e0 */ auto upx{ std::make_unique(0,0,0) }; std::shared_ptr spx{ std::move(upx) }; return 0; } >>> 'shared_ptr' sınıf şablonu için 'custom-deleter' kullanmak istediğimiz zaman bunu şablon parametresi olarak DEĞİL, 'Ctor.' fonksiyonuna bir argüman olarak geçmeliyiz. * Örnek 1, //.. struct CustomDeleter{ public: void operator()(Triple* other) { std::cout << "My custom-deleter function was called. " << *other << " will be deleted.\n"; delete other; } }; int main() { /* # OUTPUT # 0x55cff590beb0 adresinde yeni bir nesne hayata geldi. My custom-deleter function was called. (1, 1, 1) will be deleted. 0x55cff590beb0 adresindeki nesnenin hayatı sona erdi. */ std::shared_ptr spx{ new Triple{1,1,1}, CustomDeleter{} }; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # main basladi... 0x5634ede612c0 adresinde yeni bir nesne hayata geldi. (0, 0, 0) will be deleted... 0x5634ede612c0 adresindeki nesnenin hayatı sona erdi. main devam ediyor... */ std::cout << "main basladi...\n"; { std::shared_ptr spx{ new Triple{0,0,0}, [](Triple* other){ std::cout << *other << " will be deleted...\n"; delete other; } }; } std::cout << "main devam ediyor...\n"; return 0; } >>> Bir 'unique_ptr' sınıfını 'shared_ptr' sınıfına taşıyabiliyoruz. Böylelikle 'unique_ptr' döndüren fabrika fonksiyonlarının geri dönüş değerini de 'shared_ptr' sınıfından bir nesnede tutabiliriz de. * Örnek 1, //.. int main() { /* # OUTPUT # 15 Mayis 2022 Pazar 15 Mayis 2022 Pazar */ auto upx{ std::make_unique(15,5,2022) }; std::cout << *upx << "\n"; std::shared_ptr spx{ std::move(upx) }; std::cout << *spx << "\n"; return 0; } * Örnek 2, //.. std::unique_ptr createDate(int day, int month, int year) { return std::make_unique(day, month, year); } int main() { /* # OUTPUT # 15 Mayis 2022 Pazar 17 Eylul 1993 Cuma */ std::unique_ptr upx{ createDate(15, 5, 2022) }; std::cout << *upx << "\n"; std::shared_ptr spx{ createDate(17, 9, 1993) }; std::cout << *spx << "\n"; return 0; } >>> İki adet 'shared_ptr' sınıf türünden nesneyi birbirine atadığımız zaman bir tanesinin referans sayacı bir azalırken, diğerininki bir artacaktır. * Örnek 1, //.. int main() { /* # OUTPUT # 0x5648a6e07ec0 adresinde yeni bir nesne hayata geldi. Ref. Counter : 1 0x5648a6e08300 adresinde yeni bir nesne hayata geldi. Ref. Counter : 1 0x5648a6e07ec0 adresindeki nesnenin hayatı sona erdi. Ref. Counter : 2 Ref. Counter : 2 0x5648a6e08300 adresindeki nesnenin hayatı sona erdi. */ std::shared_ptr ptrOne{ std::make_shared(1,1,1) }; std::cout << "Ref. Counter : " << ptrOne.use_count() << "\n"; std::shared_ptr ptrTwo{ std::make_shared(2,2,2) }; std::cout << "Ref. Counter : " << ptrTwo.use_count() << "\n"; ptrOne = ptrTwo; std::cout << "Ref. Counter : " << ptrOne.use_count() << "\n"; std::cout << "Ref. Counter : " << ptrTwo.use_count() << "\n"; return 0; } >>> 'shared_ptr' sınıf şablonu her ne kadar paylaşım ilkesini bünyesinde barındırsa da aynı adres bloğu için iki farklı kontrol bloğu oluşturulduğunda 'Tanımsız Davranış' meydana gelecektir. * Örnek 1, //.. int main() { /* # OUTPUT # 0x56512eba9eb0 adresinde yeni bir nesne hayata geldi. 0x56512eba9eb0 adresindeki nesnenin hayatı sona erdi. 0x56512eba9eb0 adresindeki nesnenin hayatı sona erdi. free(): double free detected in tcache 2 */ auto ptr = new Triple{0,0,0}; std::shared_ptr spx{ ptr }; std::shared_ptr spy{ ptr }; // Yukarıda her ne kadar aynı adres kullanılsada farklı kontrol blokları oluşturulmuştur. // Bu nedenden ötürü 'Tanımsız Davranış' meydana gelecektir. // PAYLAŞIMLILIK İLKESİNDEN KASTEDİLEN BU DEĞİLDİR. return 0; } >>> 'shared_ptr' sınıfından nesneler bir kap içerisinde de tutulabilir. * Örnek 1, //.. int main() { /* # OUTPUT # 0x557fd9301eb0 adresinde yeni bir nesne hayata geldi. 0x557fd9302330 adresinde yeni bir nesne hayata geldi. 0x557fd93023a0 adresinde yeni bir nesne hayata geldi. 0x557fd9302410 adresinde yeni bir nesne hayata geldi. 0x557fd9302480 adresinde yeni bir nesne hayata geldi. 0x557fd93024f0 adresinde yeni bir nesne hayata geldi. 0x557fd9302560 adresinde yeni bir nesne hayata geldi. 0x557fd93025e0 adresinde yeni bir nesne hayata geldi. [8] => (0, 0, 0) | (0, 0, 1) | (0, 1, 0) | (1, 0, 0) | (1, 0, 1) | (1, 1, 0) | (1, 1, 1) | (-1, -1, -1) | [8] => (1, 1, 1) | (2, 1, 1) | (3, 1, 1) | (4, 1, 1) | (5, 1, 1) | (6, 1, 1) | (7, 1, 1) | (8, 1, 1) | 0x557fd9301eb0 adresindeki nesnenin hayatı sona erdi. 0x557fd9302330 adresindeki nesnenin hayatı sona erdi. 0x557fd93023a0 adresindeki nesnenin hayatı sona erdi. 0x557fd9302410 adresindeki nesnenin hayatı sona erdi. 0x557fd9302480 adresindeki nesnenin hayatı sona erdi. 0x557fd93024f0 adresindeki nesnenin hayatı sona erdi. 0x557fd9302560 adresindeki nesnenin hayatı sona erdi. 0x557fd93025e0 adresindeki nesnenin hayatı sona erdi. */ std::list> myList; myList.emplace_back( new Triple{0,0,0} ); myList.emplace_back( new Triple{0,0,1} ); myList.emplace_back( new Triple{0,1,0} ); myList.emplace_back( new Triple{1,0,0} ); myList.emplace_back( new Triple{1,0,1} ); myList.emplace_back( new Triple{1,1,0} ); myList.emplace_back( new Triple{1,1,1} ); myList.push_back( std::make_shared(-1, -1, -1) ); std::cout << "[" << myList.size() << "] => "; for(const auto& index : myList) std::cout << *index << " | "; { std::vector> myVec( myList.begin(), myList.end() ); for(auto& index : myVec) { static int counter{}; index->set(++counter); } } std::cout << "\n\n"; std::cout << "[" << myList.size() << "] => "; for(const auto& index : myList) std::cout << *index << " | "; // Aynı nesne hem 'list' içerisinde hem de 'vektör' içerisinde saklanmıştır. return 0; } >>> 'Owner' isimli bir sınıfımız ve bu sınıfımız da 'Member' sınıf türünden bir veri elemanımıza sahip olsa. 'Owner' sınıf türünden sınıf nesnesi tutan bir 'shared_ptr' ve 'Owner' içerisindeki 'Member' türündeki veri elemanını tutan başka bir 'shared_ptr' akıllı göstericisi olsa. Bu durumda 'Owner' tutan 'shared_ptr' sınıfının ömrü bittiğinde ilgili 'Owner' sınıfı da yok edilecektir. Bu durumda 'Owner' içerisindeki 'Member' ı gösteren akıllı gösterici 'dangling-pointer' haline gelecektir. Bu duruma önlem amaçlı 'shared_ptr' sınıfı 'Alias Ctor.' fonksiyonu vardır. * Örnek 1, //.. class Member{ public: Member() { std::cout << "Member::Member() was called...\n"; } ~Member() { std::cout << "Member::~Member() was called...\n"; } }; class Owner{ public: Owner() { std::cout << "Owner::Owner() was called...\n"; } ~Owner() { std::cout << "Owner::~Owner() was called...\n"; } public: Member mx; }; int main() { /* # OUTPUT # Member::Member() was called... Owner::Owner() was called... Ref. Counter : 2 Ref. Counter : 2 Owner::~Owner() was called... Member::~Member() was called... */ auto ownerPtr{ std::make_shared() }; auto memberOfOwnerPtr{ std::shared_ptr(ownerPtr, &ownerPtr->mx) }; std::cout << "Ref. Counter : " << ownerPtr.use_count() << "\n"; std::cout << "Ref. Counter : " << memberOfOwnerPtr.use_count() << "\n"; /* # OUTPUT # Member::Member() was called... Owner::Owner() was called... Ref. Counter : 1 Ref. Counter : 1 Member::~Member() was called... double free or corruption (out) */ auto ownerPtr{ std::make_shared() }; auto memberOfOwnerPtr{ std::shared_ptr(&ownerPtr->mx) }; // 'dangling-pointer' std::cout << "Ref. Counter : " << ownerPtr.use_count() << "\n"; std::cout << "Ref. Counter : " << memberOfOwnerPtr.use_count() << "\n"; // Her iki çıktıda da görüldüğü üzere; // İkinci senaryoda 'dangling-pointer' vardır. return 0; } * Örnek 2, //.. class Member{ public: Member() { std::cout << "Member::Member() was called...\n"; } ~Member() { std::cout << "Member::~Member() was called...\n"; } }; class Owner{ public: Owner() { std::cout << "Owner::Owner() was called...\n"; } ~Owner() { std::cout << "Owner::~Owner() was called...\n"; } public: Member mx; }; using Ovec = std::vector; int main() { /* # OUTPUT # Ref. Count : 1 Member::Member() was called... Owner::Owner() was called... Owner::~Owner() was called... Member::~Member() was called... Ref. Count : 1 Member::Member() was called... Owner::Owner() was called... Owner::~Owner() was called... Member::~Member() was called... Owner::~Owner() was called... Member::~Member() was called... Ref. Count : 1 Ref. Count : 2 Ref. Count : 2 Ref. Count : 3 Ref. Count : 3 Ref. Count : 3 Ref. Count : 4 Ref. Count : 4 Ref. Count : 4 Ref. Count : 4 Ref. Count : 0 Ref. Count : 3 Ref. Count : 3 Ref. Count : 3 Owner::~Owner() was called... Member::~Member() was called... Owner::~Owner() was called... Member::~Member() was called... */ // Vektörü gösteren akıllı gösterici. std::shared_ptr os{ new Ovec }; std::cout << "Ref. Count : " << os.use_count() << "\n"; // İlgili vektöre yeni bir akıllı gösterici eklendi. os->push_back(Owner{}); std::cout << "Ref. Count : " << os.use_count() << "\n"; // İlgili vektöre yeni bir akıllı gösterici eklendi. os->push_back(Owner{}); std::cout << "Ref. Count : " << os.use_count() << "\n"; // Yeni bir akıllı gösterici oluşturuldu ki bu ilgili vektörün ilk elemanındaki // sınıf nesnesini göstermektedir. std::shared_ptr ownerOne{ os, &os->at(0) }; std::cout << "Ref. Count : " << os.use_count() << "\n"; std::cout << "Ref. Count : " << ownerOne.use_count() << "\n"; // Yeni bir akıllı gösterici oluşturuldu ki bu da ilgili vektörün ikinci // elemanındaki sınıf nesnesini göstermektedir. std::shared_ptr ownerTwo{ os, &os->at(1) }; std::cout << "Ref. Count : " << os.use_count() << "\n"; std::cout << "Ref. Count : " << ownerOne.use_count() << "\n"; std::cout << "Ref. Count : " << ownerTwo.use_count() << "\n"; // Yeni bir akıllı gösterici oluşturuldu ki bu ilgili vektörün ikinci // elemanının içerisindeki 'Member' sınıf türünden veri elemanını // göstermektedir. std::shared_ptr memberOne{ os, &os->at(1).mx }; std::cout << "Ref. Count : " << os.use_count() << "\n"; std::cout << "Ref. Count : " << ownerOne.use_count() << "\n"; std::cout << "Ref. Count : " << ownerTwo.use_count() << "\n"; std::cout << "Ref. Count : " << memberOne.use_count() << "\n"; // İlgili vektörümüzü gösteren akıllı gösterici boşa alındı. os.reset(); std::cout << "Ref. Count : " << os.use_count() << "\n"; std::cout << "Ref. Count : " << ownerOne.use_count() << "\n"; std::cout << "Ref. Count : " << ownerTwo.use_count() << "\n"; std::cout << "Ref. Count : " << memberOne.use_count() << "\n"; return 0; } >>> 'weak_ptr' sınıf şablonu ayrı bir akıllı gösterici DEĞİL, yardımcı bir akıllı göstericidir. Bir 'shared_ptr' nesnesinden 'weak_ptr' nesnesi oluşturabiliyoruz. 'weak_ptr' sınıfının '.operator*()' ve '.operator->()' fonksiyonları mevcut değildir. >>> 'shared_ptr' ile bir 'weak_ptr' hayata getirmem, 'shared_ptr' içerisindeki 'reference-counter' değişkenini DEĞİŞTİRMEMEKTEDİR. Dolayısla bu sayaç bire düştüğünde ilgili nesnemizin hayatı da sona erecektir. >>> 'weak_ptr' sınıf şablonunun üye fonksiyonu olan '.expired()' kullanarak, 'weak_ptr' nesnesini hayata getiren 'shared_ptr' sınıf nesnesinin hayat bilgisini sorgulayabiliyoruz. * Örnek 1, //.. int main() { /* # OUTPUT # 0x5557cc2a2ec0 adresinde yeni bir nesne hayata geldi. sharedPtr.use_count() : 1 weakPtr.use_count() : 1 it is alive... 0x5557cc2a2ec0 adresindeki nesnenin hayatı sona erdi. sharedPtr.use_count() : 0 weakPtr.use_count() : 0 it has been destroyed... */ auto sharedPtr{ std::make_shared(1, 2, 3) }; std::weak_ptr weakPtr{ sharedPtr }; std::cout << "sharedPtr.use_count() : " << sharedPtr.use_count() << "\n"; std::cout << "weakPtr.use_count() : " << weakPtr.use_count() << "\n"; if( !weakPtr.expired() ) std::cout << "it is alive...\n"; else std::cout << "it has been destroyed...\n"; sharedPtr.reset(); std::cout << "sharedPtr.use_count() : " << sharedPtr.use_count() << "\n"; std::cout << "weakPtr.use_count() : " << weakPtr.use_count() << "\n"; if( !weakPtr.expired() ) std::cout << "it is alive...\n"; else std::cout << "it has been destroyed...\n"; return 0; } >>> 'weak_ptr' sınıfının bir başka üye fonksiyonu olan '.lock()' fonksiyonu bir 'shared_ptr' döndürmektedir göstermiş olduğu kaynağın boş veya dolu olmasına göre. Yani bu fonksiyonu kullanarak aynı kaynağı paylaşan bir başka 'shared_ptr' elde edebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # 0x55913594dec0 adresinde yeni bir nesne hayata geldi. sharedPtr.use_count() : 1 weakPtr.use_count() : 1 it is alive... sharedPtr.use_count() : 2 weakPtr.use_count() : 2 tempPtr.use_count() : 2 sharedPtr.use_count() : 1 weakPtr.use_count() : 1 0x55913594dec0 adresindeki nesnenin hayatı sona erdi. it has been destroyed... */ auto sharedPtr{ std::make_shared(1, 2, 3) }; std::weak_ptr weakPtr{ sharedPtr }; std::cout << "sharedPtr.use_count() : " << sharedPtr.use_count() << "\n"; std::cout << "weakPtr.use_count() : " << weakPtr.use_count() << "\n"; if( auto tempPtr = weakPtr.lock() ) // Hayatta olan 'sharedPtr' nin bir kopyası çıkartıldı. { std::cout << "it is alive...\n"; std::cout << "sharedPtr.use_count() : " << sharedPtr.use_count() << "\n"; std::cout << "weakPtr.use_count() : " << weakPtr.use_count() << "\n"; std::cout << "tempPtr.use_count() : " << tempPtr.use_count() << "\n"; } else std::cout << "it has already been destroyed...\n"; std::cout << "sharedPtr.use_count() : " << sharedPtr.use_count() << "\n"; std::cout << "weakPtr.use_count() : " << weakPtr.use_count() << "\n"; sharedPtr.reset(); if( auto tempPtr = weakPtr.lock() ) std::cout << "it is alive...\n"; else std::cout << "it has been destroyed...\n"; return 0; } * Örnek 2, Aynı işlemi 'shared_ptr' sınıfının 'Ctor.' fonksiyonunu kullanarak da yapabiliriz. Fakat kaynak boş ise bir hata fırlatılacaktır. //.. int main() { /* # OUTPUT # 0x55e3cbf3cec0 adresinde yeni bir nesne hayata geldi. sptr.use_count() : 1 wptr.use_count() : 1 sptrTwo.use_count() : 2 sptrTwo.use_count() : 0 0x55e3cbf3cec0 adresindeki nesnenin hayatı sona erdi. sptr.use_count() : 0 Hata yakalandi... bad_weak_ptr */ auto sptr{ std::make_shared(1, 2, 3) }; std::cout << "sptr.use_count() : " << sptr.use_count() << "\n"; std::weak_ptr wptr{ sptr }; std::cout << "wptr.use_count() : " << wptr.use_count() << "\n"; std::shared_ptr sptrTwo{ wptr }; std::cout << "sptrTwo.use_count() : " << sptrTwo.use_count() << "\n"; sptrTwo.reset(); std::cout << "sptrTwo.use_count() : " << sptrTwo.use_count() << "\n"; sptr.reset(); std::cout << "sptr.use_count() : " << sptr.use_count() << "\n"; try{ std::shared_ptr sptrThree{ wptr }; } catch(const std::exception& ex) { std::cout << "Hata yakalandi... " << ex.what() << "\n"; } return 0; } >>> 'weak_ptr' sınıf şablonunun en çok işlevli olduğu nokta iki 'shared_ptr' sınıfının birbirine bağlı olması durumunda ortaya çıkan 'cycling-reference' problemini ortadan kaldırmaktır. * Örnek 1, //.. int main() { /* # OUTPUT # 0x55a7e38d2ec0 adresinde yeni bir nesne hayata geldi. sptr.use_count() : 1 wp.use_count() : 0 it has been destroyed... 0x55a7e38d2ec0 adresindeki nesnenin hayatı sona erdi. */ auto sptr{ std::make_shared(12, 5, 1987) }; std::weak_ptr wptr{ sptr }; std::cout << "sptr.use_count() : " << sptr.use_count() << "\n"; wptr.reset(); std::cout << "wp.use_count() : " << wptr.use_count() << "\n"; if(!wptr.expired()) { std::cout << "it is alive...\n"; std::shared_ptr sptrTwo{ wptr }; std::cout << *sptrTwo << "\n"; } else { std::cout << "it has been destroyed...\n"; } return 0; } * Örnek 2, //.. struct B; struct A{ std::shared_ptr bptr; //std::weak_ptr bptr; ~A(){ std::cout << "A Dtor.\n"; } }; struct B{ std::shared_ptr aptr; ~B(){ std::cout << "B Dtor.\n"; } }; int main() { /* # OUTPUT # spa.use_count() : 1 spb.use_count() : 1 spb.use_count() : 2 spa.use_count() : 2 */ std::shared_ptr spa{ new A }; std::cout << "spa.use_count() : " << spa.use_count() << "\n"; std::shared_ptr spb{ new B }; std::cout << "spb.use_count() : " << spb.use_count() << "\n"; spa->bptr = spb; std::cout << "spb.use_count() : " << spb.use_count() << "\n"; spb->aptr = spa; std::cout << "spa.use_count() : " << spa.use_count() << "\n"; // Çıktıda da görüldüğü gibi sınıf nesnelerimiz 'destroy' edilmediler. return 0; } * Örnek 3, //.. struct B; struct A{ //std::shared_ptr bptr; std::weak_ptr bptr; // 'derefence' edilemezler. Bunu kullanarak yeni bir 'shared_ptr' hayata getirip, onun vesilesi ile 'dereference' // yapabiliriz. ~A(){ std::cout << "A Dtor.\n"; } }; struct B{ std::shared_ptr aptr; ~B(){ std::cout << "B Dtor.\n"; } }; int main() { /* # OUTPUT # spa.use_count() : 1 spb.use_count() : 1 spb.use_count() : 2 spa.use_count() : 2 B Dtor. A Dtor. */ std::shared_ptr spa{ new A }; std::cout << "spa.use_count() : " << spa.use_count() << "\n"; std::shared_ptr spb{ new B }; std::cout << "spb.use_count() : " << spb.use_count() << "\n"; spa->bptr = spb; std::cout << "spb.use_count() : " << spb.use_count() << "\n"; spb->aptr = spa; std::cout << "spa.use_count() : " << spa.use_count() << "\n"; return 0; } * Örnek 4, //Dog.h //.. class Dog{ public: Dog(const std::string& name) : m_name{name} { std::cout << "dog-" << m_name << " oluşturuldu...\n"; } ~Dog() { std::cout << "dog-" << m_name << " oyundan çıkıyor...\n"; } void bark(){ std::cout << "dog-" << m_name << " havliyor...\n"; } void make_friend(std::shared_ptr otherDog) { mp_friend = otherDog; std::cout << "dog-" << m_name << ", dog-" << otherDog->m_name << " ile arkadaş oldu...\n"; } void show_friend()const { if(!mp_friend.expired()) std::cout << "Benim arkadaşım : dog-" << mp_friend.lock()->m_name << "...\n"; else std::cout << "Bir arkadaşım yok...\n"; } private: std::weak_ptr mp_friend; std::string m_name; }; // main.cpp //.. int main() { /* # OUTPUT # dog-shiva oluşturuldu... dog-peggy oluşturuldu... dog-shiva, dog-peggy ile arkadaş oldu... dog-peggy, dog-shiva ile arkadaş oldu... Benim arkadaşım : dog-peggy... dog-peggy oyundan çıkıyor... Bir arkadaşım yok... dog-shiva oyundan çıkıyor... */ std::shared_ptr sp1{ std::make_shared("shiva") }; std::shared_ptr sp2{ std::make_shared("peggy") }; sp1->make_friend(sp2); sp2->make_friend(sp1); sp1->show_friend(); sp2 = nullptr; sp1->show_friend(); return 0; } >>> Bir 'shared_ptr' tarafından gösterilen dinamik ömürlü sınıf nesnesinin üye fonksiyonu içerisinde, kendisini gösteren ilgili 'shared_ptr' nin bir kopyasını nasıl çıkartabiliriz? * Örnek 1, //.. class MyclassTwo{ public: void func() { // 'main' içerisindeki 'sp' isimli akıllı göstericinin bir kopyasını burada oluşturmak // istiyorum. Referans sayacı da artacak şekilde. std::shared_ptr spx{this}; // Bu şekilde ilgili akıllı göstericinin bir kopyasını oluşturmamış, // sadece aynı kaynağı paylaşan iki adet gösterici oluşturmuş oluruz. std::cout << "spx.use_count() : " << spx.use_count() << "\n"; } }; int main() { /* # OUTPUT # sp.use_count() : 1 spx.use_count() : 1 double free or corruption (out) */ auto sp{ std::make_shared() }; std::cout << "sp.use_count() : " << sp.use_count() << "\n"; sp->func(); std::cout << "sp.use_count() : " << sp.use_count() << "\n"; return 0; } * Örnek 2, yukarıdaki problemin çözümü 'CRTP' deyimi ile mümkündür. Gelecek ders işlenecektir. /*============================================================================================================*/ (35_16_01_2021) > 'STL' içerisindeki kaplar (devam) : >> Akıllı Göstericiler (devam) : >>> 'CRTP' ve 'enabled_shared_from_this' : Aşağıdaki örneği inceleyelim. * Örnek 1, //.. template class Base{ public: void func() { (static_cast(this))->foo(); //(I) this->foo(); } void foo() { std::cout << "Base::foo()\n"; } }; class Der : public Base{ // (II) public: void foo() { std::cout << "Der::foo()\n"; } }; int main() { /* # OUTPUT # Der::foo() Base::foo() */ Der myDer; myDer.func(); // 'Der' sınıfı, 'Base' sınıfının 'Der' açılımından türetilmiştir. Yani 'II' numaralı türetiliş. // Buna güvenilerek 'I' numaralı kod parçacığı yazılmıştır. // Dolayısıyla 'Der' sınıfının içerisinde 'Base' sınıfının 'Der' açılımı bulunmaktadır. // Peki biz bu özelliği nerede kullanabiliriz? El-cevap : İkinci ve üçüncü örneği inceleyelim. return 0; } * Örnek 2, Zaman zaman 'Run-time Polymorphism' mekanizmasının maliyetinden kurtulmak isteriz. İşte o tip durumlarda bu mekanizma kullanılabilir. Böylelikle derleme zamanında hangi fonksiyonun çağrılacağı belirlenmiş olmaktadır. Bu örnekte 'Run-time Polymorphism' kullanılmıştır. //.. class Pet{ public: void makeSound() { std::cout << get_sound() << "\n"; } private: virtual std::string get_sound() const = 0; }; class Bat : public Pet{ public: virtual std::string get_sound() const override { return {"....."}; } }; class Cat : public Pet{ public: virtual std::string get_sound() const override { return { "miyav" }; } }; void petGame(Pet& pet) { pet.makeSound(); } int main() { /* # OUTPUT # miyav ..... */ Cat myCat; petGame(myCat); Bat myBat; petGame(myBat); return 0; } * Örnek 3, 'CRTP' ile yukarudaki 'Run-time Polymorphism' mekanizmasını 'Static Polymorphism' mekanizmasına dönüştürüyoruz. //.. template class Pet{ public: void makeSound() { std::cout << thisObject().get_sound() << "\n"; } private: const T& thisObject() { return *static_cast(this); } // i. Sanal bir fonksiyon bildirilmemiştir. // ii. Bu sınıf şablonu 'T' türüne ilişkin 'std::string T::get_sound() const' imzalı // bir fonksiyon bulunacağına göre yazılmaktadır. }; class Bat : public Pet{ // Şablon argüman türüne dikkat ediniz! public: std::string get_sound() const { return {"....."}; } // 'Pet' sınıfının 'private' kısmındaki 'thisObject' isimli fonksiyon içerisindeki kod, // aslında yukarıdaki 'get_sound()' fonksiyonunun yazılmış olacağı düşünülerek yazıldı. }; class Cat : public Pet{ // Şablon argüman türüne dikkat ediniz! public: std::string get_sound() const { return {"miyav"}; } // 'Pet' sınıfının 'private' kısmındaki 'thisObject' isimli fonksiyon içerisindeki kod, // aslında yukarıdaki 'get_sound()' fonksiyonunun yazılmış olacağı düşünülerek yazıldı. }; template void petGame(Pet& pet) { pet.makeSound(); } int main() { /* # OUTPUT # miyav ..... */ Cat myCat; petGame(myCat); Bat myBat; petGame(myBat); return 0; } * Örnek 4, 'CRTP' örüntüsünü de gördüğümüze göre en sonki derste yarım kalan; bir sınıfın üye fonksiyonu içerisinde 'shared_ptr' ile hayatı kontrol edilen '*this' nesnesini gösteren 'shared_ptr' nin kopyasını çıkartmak isterseniz, sınıfımızı 'CRTP' örüntüsü ile kalıtım yoluyla 'std::enable_shared_from_this' sınıfından elde etmeliyiz. //.. class Neco : public std::enable_shared_from_this{ // 'CRTP' noktası public: Neco() { std::cout << "Neco::Neco() : " << this << "\n"; } void func() { std::cout << "Neco::func() işlevi : " << this << "\n"; // Ben, 'func' işlevinin bir 'shared_ptr' ile kontrol edilen dinamik Neco nesnesi için // çağrıldığına eminim. Aksi halde 'exception' gönderilecektir. auto sptr{ shared_from_this() }; // Artık 'func' işlevi hangi nesne için çağrılmış ise o nesnenin bir kopyası oluşturuldu. std::cout << "sptr.use_count() : " << sptr.use_count() << "\n"; std::cout << "this : " << this << "\n"; std::cout << "sptr : " << sptr << "\n"; } ~Neco() { std::cout << "Neco::~Neco()\n"; } }; int main() { /* # OUTPUT # Neco::Neco() : 0x562878916ec0 Neco::func() işlevi : 0x562878916ec0 sptr.use_count() : 2 this : 0x562878916ec0 sptr : 0x562878916ec0 Neco::~Neco() Neco::Neco() : 0x5628789172f0 Neco::func() işlevi : 0x5628789172f0 hata yakalandi : bad_weak_ptr */ { auto sp{ std::make_shared() }; sp->func(); } { try{ auto srp{ new Neco }; srp->func(); } catch(const std::exception& ex) { std::cout << "hata yakalandi : " << ex.what() << "\n"; } } return 0; } >> Input-Output Operations : Aşağıdaki kalıtım hiyerarşisini inceleyelim. ios_base | basic_ios<> / \ basic_istream<> basic_ostream<> / | \ / \ basic_ifstream<> | basic_iostream<> | basic_ofstream<> | | | | | | | | basic_istringstream<> | | basic_ostringstream<> | | | basic_fstream<> basic_stringstream<> Tabii bütün kalıtım hiyerarşisi yukarıdakilerden oluşmamaktadır. Detayları için ilgili kaynak incelenmelidir. >>> 'std::cout' : Bizim hep kullandığımız 'cout' nesnemiz, 'basic_ostream<>' sınıfının 'char' açılımı türündendir. Yani 'ostream' sınıf türünden. Aslında bu 'ostream' ise 'basic_ostream' sınıfının 'char' açılımının tür eş ismi şeklindedir. Eğer açılım 'whar_t' türünden olsaydı, tür eş ismi olarak 'wostream' ismini kullanacaktık. >>>> 'void*' parametreli üye fonksiyon olan '.operator<<()', argüman olarak aldığı ifadenin adresini yazdırırken 'const char*' parametreli global fonksiyon olan 'operator<<()' ise yazının kendisini ekrana yazdırmaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # Ahmet 0x556a02baa054 */ const char* name{"Ahmet"}; std::operator<<(std::cout, name); // Global versiyon yazının kendisini yazdırırken. // Geri dönüş değeri birinci parametreye geçilen argümana ait bir referans. std::cout << "\n"; std::cout.operator<<(name); // Üye version ise adresini yazdırmaktadır. // Geri dönüş değeri '*this'. return 0; } >>>> Yine 'std::string', 'std::bitset', 'std::complex' vs. sınıf türlerinden parametreli fonksiyonları da vardır. * Örnek 1, //.. int main() { /* # OUTPUT # [Ahmet], [00000000000000011110001001000000], [(1.4,6.7)] */ std::string name{"Ahmet"}; std::bitset<32> bs{123456ul}; std::complex c{1.4, 6.7}; std::cout << "[" << name << "], [" << bs << "], [" << c << "]\n"; return 0; } >>>> 'custom-type' dediğimiz türler için ise global bir 'operator<<()' fonksiyonu yazmalıyız. Örneğin, 'Date' sınıfı. * Örnek 1, //.. class MyCustom{ public: inline static int counter{}; // C++17 void showCounter()const{ std::cout << "Counter: " << counter << "\n"; } public: MyCustom(){ ++counter; } ~MyCustom(){ --counter; } }; // İlgili sınıfımızı 'private' bölümüne erişmek isteseydik, ilgili sınıf içerisinde iş bu fonksiyonun // 'friend' bildirimini/tanımını yapmak zorundaydık. std::ostream& operator<<(std::ostream& os, const MyCustom& other) { other.showCounter(); return os << "\n"; } int main() { /* # OUTPUT # Counter: 1 Counter: 2 Counter: 4 Counter: 4 */ MyCustom m0; std::cout << m0; MyCustom m1; std::cout << m1; MyCustom m2, m3; operator<<(operator<<(std::cout, m2), m3); return 0; } >>> 'std::cin' : Bizim hep kullandığımız 'cin' nesnemiz, 'basic_ostream<>' sınıfının 'char' açılımı türündendir. Yani 'istream' sınıf türünden. Aslında bu 'istream' ise 'basic_ostream' sınıfının 'char' açılımının tür eş ismi şeklindedir. Eğer açılım 'whar_t' türünden olsaydı, tür eş ismi olarak 'wistream' ismini kullanacaktık. >>> Formatlama ayarları aslında kalıtım hiyerarşisinin en tepesinde bulunan 'ios_base' sınıfının 'public' üye fonksiyonları vasıtasıyla yapılmaktadır. Dolayısla iş bu fonksiyonlara, ilgili sınıftan kalıtım yoluyle elde edilen, alt seviye sınıflar vesilesiyle de ulaşabiliriz. Aşağıda detayları açıklanacak olan formatlama bayrakları, 'fmtflags' türünden 'constexpr static' veri elemanlarıdır. 'fmtflags' ise bir tür eş ismi olup, hangi türe ait olduğu derleyicye göre değişmektedir. Bu formatlama ayarlarını kategorize edersek; * Örnek 1, 'fmtflags' türünün temsili bir gösterimi: //.. class iosbase{ public: typedef int fmtflags; constexpr static fmtflags basefield; //... }; >>>> 'boolean' veri tipinde tutulan formatlama ayarları: Pozitif sayılarda '+' işaretinin kullanılıp kullanılmayacağı, sırasyıla sekiz tabanlı ve on altı tabanlı rakamları yazdırırken '0' ve '0x' işaretlerinin kullanılıp kullanılmayacağı, 'bool' türden bir değişkenin '0' / '1' şeklinde mi yoksa 'false' / 'true' şeklinde mi yazılacağı bilgisi için kullanılır. Buna ek olarak küsüratlı rakamları yazdırırken '.' işaretinden sonra sıfır rakamının gelmesi durumunda bu rakamın gösterilip gösterilmeyeceği ayarını da yine bu tip formatlama ayarları ile sağlayabiliriz. Özetle bu tip formatlama ayarları için kısaca 'ON/OFF Flag' diyebiliriz. * Örnek 1, bitsel seviyedeki işlemleri gerçekleştiren 'fmtflags' tür eş isminin kullandığı veri tipi: //.. int main() { /* # OUTPUT # St13_Ios_Fmtflags */ std::cout << typeid(std::ios_base::fmtflags).name() << "\n"; return 0; } * Örnek 2, 'On/Off' bayraklarının yazdırılması: //.. int main() { /* # OUTPUT # boolalpha => [00000000000000000000000000000001] showbase => [00000000000000000000001000000000] showpos => [00000000000000000000100000000000] skipws => [00000000000000000001000000000000] uppercase => [00000000000000000100000000000000] */ std::cout << "boolalpha => [" << std::bitset<32>{ std::ios::boolalpha } << "]\n"; // 'bool' veri tipini 'true/false' biçimde yazdırmaya yarıyor. std::cout << "showbase => [" << std::bitset<32>{ std::ios::showbase } << "]\n"; // Kullanılan sayı sistemini de yazdırmak için kullanılıyor. std::cout << "showpos => [" << std::bitset<32>{ std::ios::showpos } << "]\n"; // Pozitif rakamlar için '+' işaretinin koyulmasını sağlar. std::cout << "skipws => [" << std::bitset<32>{ std::ios::skipws } << "]\n"; std::cout << "uppercase => [" << std::bitset<32>{ std::ios::uppercase } << "]\n"; // On altılık sayı sistemindeki harflerin büyük harf olmasını sağlar. return 0; } * Örnek 3, 'bool' türden değişkenleri yazdırırken: //.. int main() { /* # OUTPUT # Flags => [4098] Flags => [4099] Flags => [4099] */ // Arka plandaki 'fmtflags' değişkenini geri döndürmektedir eğer bir parametre geçmezsek. std::cout << "Flags => [" << std::cout.flags() << "]\n"; // Herhangi bir parametre geçmemiz durumunda sadece ilgili bayrağı 'set' etmektedir. std::cout.flags(std::cout.flags() | std::ios_base::boolalpha); std::cout << "Flags => [" << std::cout.flags() << "]\n"; // Yukarıdaki uzun kullanım yerine aynı işi yapan '.setf()' fonksiyonunu da kullanabiliriz. std::cout.setf(std::ios_base::boolalpha); std::cout << "Flags => [" << std::cout.flags() << "]\n"; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # 1 true true true 1 1 */ // İlgili 'boolalpha' bayrağı henüz ayarlanmamışken: std::cout << (10 > 5) << "\n"; // İlgili 'boolalpha' bayrağı artık ayarlandı: std::cout.setf(std::ios_base::boolalpha); std::cout << (10 > 5) << "\n"; // 'std::ios_base', en tepedeki taban sınıfın ismi. 'basic_ios' sınıfının // 'char' türden açılımının tür eş ismi 'ios' olduğundan, 'std::ios_base::boolalpha' // yerine 'std::ios::boolalpha' da yazabiliriz. std::cout.setf(std::ios::boolalpha); std::cout << (10 > 5) << "\n"; // Kalıtım olduğu için 'std::ios' yerine, 'std::ostream' de yazabiliriz. std::cout.setf(std::ios::boolalpha); std::cout << (10 > 5) << "\n"; // ... // Peki ilgili bayrağı tekrar eski haline getirmek için: std::cout.flags(std::cout.flags() & ~std::ios::boolalpha); std::cout << (10 > 5) << "\n"; // Yine yukarıdaki işi yapan '.unsetf()' isimli üye fonksiyonu da çağırabiliriz: std::cout.unsetf(~std::ios::boolalpha); std::cout << (10 > 5) << "\n"; return 0; } * Örnek 5, Arka planda kullanılan sayı sistemini de göstermektedir. //.. int main() { /* # OUTPUT # x : 54703 x : d5af x : 0xd5af */ int x = 54703; std::cout << "x : " << x << "\n"; std::cout << std::hex; // Artık on altılık sayı sistemi kullanılacaktır. std::cout << "x : " << x << "\n"; std::cout.setf(std::ios::showbase); // Artık kullanılan sayı sistemi de yazdırılacaktır. std::cout << "x : " << x << "\n"; return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # x : 54703 x : +54703 */ int x = 54703; std::cout << "x : " << x << "\n"; std::cout.setf(std::ios::showpos); // Pozitif rakamlar için '+' işaretini de ekleyecektir. std::cout << "x : " << x << "\n"; return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # x : 54703 x : d5af x : 0xd5af x : 0XD5AF */ int x = 54703; std::cout << "x : " << x << "\n"; std::cout << std::hex; // Artık on altılık sayı sistemi kullanılacaktır. std::cout << "x : " << x << "\n"; std::cout.setf(std::ios::showbase); // Artık kullanılan sayı sistemi de yazdırılacaktır. std::cout << "x : " << x << "\n"; std::cout.setf(std::ios::uppercase); // Artık harfler büyük harf haline geldiler. std::cout << "x : " << x << "\n"; return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # d : 1 d : 1.00000 */ double d{1}; std::cout << "d : " << d << "\n"; std::cout.setf(std::ios::showpoint); std::cout << "d : " << d << "\n"; return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # x : 54703 x : d5af x : 0XD5AF */ int x = 54703; std::cout << "x : " << x << "\n"; std::cout << std::hex; // Artık on altılık sayı sistemi kullanılacaktır. std::cout << "x : " << x << "\n"; // Artık kullanılan sayı sistemi de yazdırılacaktır. // Artık harfler büyük harf haline geldiler. std::cout.setf(std::ios::showbase | std::ios::uppercase); std::cout << "x : " << x << "\n"; return 0; } >>>> Hangi sayı sisteminin kullanılacağını belirleyen bayraklar: * Örnek 1, //.. int main() { /* # OUTPUT # x : 100 x : 64 x : 144 std::ios::basefield => [00000000000000000000000001001010] std::ios::dec => [00000000000000000000000000000010] std::ios::hex => [00000000000000000000000000001000] std::ios::oct => [00000000000000000000000001000000] */ int x = 100; // Varsayılan ayar olarak onluk sayı sisteminde yazdırmaktadır. std::cout << "x : " << x << "\n"; // Herhangi başka bir sayı sisteminde yazdırmak için öncelikle ilgili bayrakları // sıfırlamamız gerekmektedir. Sonrasında da ilgili sayı sistemine hitap eden // bayrağı 'set' etmemiz gerekmektedir. // On altılık sayı sisteminde yazdırmak için: std::cout.setf(std::ios::hex, std::ios::basefield); std::cout << "x : " << x << "\n"; // Sekizlik sayı sisteminde yazdırmak için: std::cout.setf(std::ios::oct, std::ios::basefield); std::cout << "x : " << x << "\n"; // Yukarıda kullanılan 'std::ios::basefield' aslında ilgili bütün bitleri '1' şeklindedir. std::cout << "std::ios::basefield => [" << std::bitset<32>{std::ios::basefield} << "]\n"; std::cout << "std::ios::dec => [" << std::bitset<32>{std::ios::dec} << "]\n"; std::cout << "std::ios::hex => [" << std::bitset<32>{std::ios::hex} << "]\n"; std::cout << "std::ios::oct => [" << std::bitset<32>{std::ios::oct} << "]\n"; return 0; } * Örnek 2, //.. void func(std::ostream& os) { if(os.flags() & std::ios::boolalpha) std::cout << "true false şeklinde yazmaktadır.\n"; else std::cout << "0 1 şeklinde yazmaktadır.\n"; // 'if' parantezi içerisinde; // i. İlgili '.flags()' fonksiyon çağrısı ile o anki bayrakların durum bilgisi elde ediliyor. // ii. Bu bayrakları 'std::ios::boolalpha' bayrağı ile 'bitsel-ve' işlemine sokuyoruz. // iii. Her iki bayrak da '1' ise programın akışı yukarıdaki 'std::cout' çağrısına, // eğer en az bir tanesi '0' ise programın akışı aşağıdaki 'std::cout' çağrısına geçmektedir. } int main() { /* # OUTPUT # 0 1 şeklinde yazmaktadır. true false şeklinde yazmaktadır. */ func(std::cout); std::cout.setf(std::ios::boolalpha); func(std::cout); return 0; } * Örnek 3, //.. void func(std::ostream& os, int x) { auto fm = os.flags(); os.setf(std::ios::hex, std::ios::basefield); std::cout << "x : " << x << "\n"; os.flags(fm); } int main() { /* # OUTPUT # x : 100 x : 64 x : 100 */ int x = 100; std::cout << "x : " << x << "\n"; func(std::cout, x); std::cout << "x : " << x << "\n"; return 0; } >>>> Gerçek sayıların yazdırılmasında kullanılan bayraklar: * Örnek 1, //.. void printFloatFormat(std::ostream& os) { if(os.flags() & std::ios::fixed) std::cout << "The flag of fixed has been set...\n"; if(os.flags() & std::ios::scientific) std::cout << "The flag of scientific has been set...\n"; if(os.flags() & std::ios::scientific & std::ios::fixed) std::cout << "The flag of both has been fixed...\n"; } int main() { /* # OUTPUT # 3.14165 7.43534e+07 The flag of fixed has been set... 3.141653 74353425.872346 The flag of scientific has been set... 3.141653e+00 7.435343e+07 The flag of fixed has been set... The flag of scientific has been set... 0x1.9221b39347992p+1 0x1.1ba2c477d483p+26 */ // Gerçek sayının büyüklüğüne göre format bayrağı otomatik olarak 'set' // edilmektedir. printFloatFormat(std::cout); std::cout << 3.1416534871234 << "\n"; std::cout << 74353425.872345692345 << "\n"; // Eğer bizler 'fixed' bayrağını 'set' etmek istiyorsak: std::cout.setf(std::ios::fixed, std::ios::floatfield); printFloatFormat(std::cout); std::cout << 3.1416534871234 << "\n"; std::cout << 74353425.872345692345 << "\n"; // Eğer bizler 'scientific' bayrağını 'set' etmek istiyorsak: std::cout.setf(std::ios::scientific, std::ios::floatfield); printFloatFormat(std::cout); std::cout << 3.1416534871234 << "\n"; std::cout << 74353425.872345692345 << "\n"; // Eğer bizler hem 'scientific' hem de 'fixed' bayrağını 'set' etmek istiyorsak: std::cout.flags(std::ios::scientific | std::ios::fixed); // Hexadecimal sayı sisteminde yazdıracaktır. printFloatFormat(std::cout); std::cout << 3.1416534871234 << "\n"; std::cout << 74353425.872345692345 << "\n"; return 0; } >>>> Yazma alanı genişliğini ayarlayan üye fonksiyonlar: Bu tip üye fonksiyonlar budamaya neden olmazlar. '.width()' fonksiyonuna argüman geçmeden geri dönüş değerini kullanırsak, o anki yazma alanı genişlik bilgisini 'get' etmiş oluruz. Eğer iş bu fonksiyona argüman geçersek de yazma alanı genişliğini 'set' etmiş oluruz. Yine '.fill()' fonksiyonu ile de yazma alanı daha büyük olduğu durumlarda doldurma karakterini 'get' ve 'set' edebiliriz. Yazma alanı genişlik bilgisini 'set' eden bayraklar yukarıdaki bayraklar gibi kalıcı değiller. Sadece ve sadece kendilerinden sonraki ilk argüman için geçerli olurlar. * Örnek 1, //.. int main() { /* # OUTPUT # [0] => 1234567 1234567 */ int x{1'234'567}; // Varsayılan yazım alanı genişliği sıfırdır: auto n = std::cout.width(); std::cout << "[" << n << "] => " << x << "\n"; // Yazım alanı genişliği budamaya neden olmaz: std::cout.width(3); std::cout << x << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 1234567 Karaman 1234567Karaman 1234567Karaman */ int x{1'234'567}; std::string name{"Karaman"}; std::cout.width(12); std::cout.setf(std::ios::left, std::ios::adjustfield); std::cout << x << name; std::cout << "\n"; std::cout.width(12); std::cout.setf(std::ios::right, std::ios::adjustfield); std::cout << x << name; std::cout << "\n"; std::cout.width(12); std::cout.setf(std::ios::internal, std::ios::adjustfield); std::cout << x << name; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # n : 32 1234567.....Karaman ?????1234567Karaman *****1234567Karaman */ int x{1'234'567}; std::string name{"Karaman"}; int n = std::cout.fill(); // ASCII tablosu karşılığıdır. std::cout << "n : " << n << "\n"; std::cout.fill('.'); std::cout.width(12); std::cout.setf(std::ios::left, std::ios::adjustfield); std::cout << x << name << "\n"; std::cout << "\n"; std::cout.fill('?'); std::cout.width(12); std::cout.setf(std::ios::right, std::ios::adjustfield); std::cout << x << name << "\n"; std::cout << "\n"; std::cout.fill('*'); std::cout.width(12); std::cout.setf(std::ios::internal, std::ios::adjustfield); std::cout << x << name << "\n"; std::cout << "\n"; return 0; } >>> Giriş çıkış akımında kullanılan manipülatörler ve manipülatör kavramı: Aşağı örnekleri inceleyelim: * Örnek 1, 'std::ostream' sınıfının temsili implementasyonu: //.. class Ostream{ public: Ostream& operator<<(const char* c) { std::cout << c; return *this; } // 'const char*' Ostream& operator<<(int x) { std::cout << x << "\n"; return *this; } // I : 'int' veri tipini yazdırmak için Ostream& operator<<(double x) { std::cout << x << "\n"; return *this; } // II : 'double' veri tipini yazdırmak için Ostream& operator<<(Ostream&(*funcPtr)(Ostream&)) // III : 'std::endl' fonksiyon şablonunun temsilisi. { return funcPtr(*this); } void flush(){ std::cout.flush(); } }; // 'std::endl' fonksiyon şablonunun temsilisi. Ostream& myEndl(Ostream& os) { os << "\n"; os.flush(); return os; } int main() { /* # OUTPUT # 100 100.001 */ int ival = 100; Ostream myCout; myCout << ival; myCout << myEndl; double dval = 100.001; myCout << dval; return 0; // Çıktıtan da görüldüğü üzere bizler 'custom' manipülatörler yazabiliriz. // Bunun sebebi 'std::cout' nesnesinin üye fonksiyonu olan '.operator<<()', 'function-pointer' argümanlı // bir 'overload' a sahip olmasından dolayıdır. Bu fonksiyon göstericisi ise öyle bir fonksiyonu göstermektedir ki // onun geri dönüş değeri ve aldığı argüman 'std::ostream&' türünden. } * Örnek 2, //.. std::ostream& mySeparator(std::ostream& os) { return os << "\n-------------------------------\n"; } int main() { /* # OUTPUT # 100 ------------------------------- 100.001 */ int ival = 100; double dval = 100.001; std::cout << ival << mySeparator << dval << std::endl; // Peki bizler bu yaklaşımı yukarıdaki bayraklar için de yazabilir miyiz? // El-cevap: Aşağıdaki cevabı inceleyelim. return 0; } * Örnek 3, //.. std::ostream& mySeparator(std::ostream& os) { return os << "\n-------------------------------\n"; } std::ostream& myBoolAlphaFlag(std::ostream& os) { os.setf(std::ios::boolalpha); return os; } int main() { /* # OUTPUT # true ------------------------------- */ std::cout << myBoolAlphaFlag << (10 > 5) << mySeparator; // Peki bizler nasıl tekrar eski haline alacağız? // El-cevap: Aşağıdaki cevabı inceleyelim. return 0; } * Örnek 4, //.. std::ostream& mySeparator(std::ostream& os) { return os << "\n-------------------------------\n"; } std::ostream& myBoolAlphaFlagToggleOn(std::ostream& os) { os.setf(std::ios::boolalpha); return os; } std::ostream& myBoolAlphaFlagToggleOff(std::ostream& os) { os.unsetf(std::ios::boolalpha); return os; } int main() { /* # OUTPUT # true ------------------------------- 1 ------------------------------- */ std::cout << myBoolAlphaFlagToggleOn << (10 > 5) << mySeparator; std::cout << myBoolAlphaFlagToggleOff << (10 > 5) << mySeparator; // Buradaki örneklerde kullanılan 'On/Off' bayraklarına ek olarak yukarıdaki sayı sistemini değiştiren, // gerçek sayıların gösteriminde kullanılan ve yazma alan genişliğini ayarlayan diğer bayrakları da // benzer şekilde kullanabiliriz. return 0; } * Örnek 5, //.. std::ostream& mySeparator(std::ostream& os) { return os << "\n-------------------------------\n"; } std::ostream& myHexxer(std::ostream& os) { os.setf(std::ios::hex, std::ios::basefield); return os; } int main() { /* # OUTPUT # x : 100 x : 64 ------------------------------- */ int x = 100; std::cout << "x : " << x << "\n"; std::cout << myHexxer << "x : " << x << mySeparator; return 0; } >>>> Yukarıdaki örneklerde temsili implementasyonları gösterilenler için aslında standart kütüphane içerisinde manipülatörler bulunmaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # 0x3039 */ std::cout << std::left << std::showbase << std::hex; int x{12345}; std::cout << x << "\n"; return 0; } >>>> Parametreli manipülatörlerin kullanımı: Bunları kullanabilmek için 'iomanip' başlık dosyasını çağırmamız gerekmektedir. Yine kalıcı değil sadece bir sonraki yazma işlemi için geçerlidir. * Örnek 1, //.. int main() { /* # OUTPUT # 17 => su 35 => nusret 45 => nedim 56 => fugen 60 => derya 61 => papatya 82 => nefes 70 => asim 25 => aziz 83 => tansu ------------------- 17 => su 35 => nusret 45 => nedim 56 => fugen 60 => derya 61 => papatya 82 => nefes 70 => asim 25 => aziz 83 => tansu */ std::vector> myVec; fcs(myVec, 10, []{ return std::make_pair(Irand{0, 100}(), rname()); }); for(const auto&[ID, name] : myVec) std::cout << ID << " => " << name << "\n"; std::cout << "-------------------\n"; std::cout << std::left; for(const auto&[ID, name] : myVec) std::cout << std::setw(3) << ID << " => " << std::setw(10) << name << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 3.14159 ------------------- 3.1416 ------------------- 3.1416 ------------------- */ double PI{3.14159265359}; std::cout << PI << "\n"; std::cout << "-------------------\n"; //Senaryo - I std::cout.setf(std::ios::left, std::ios::adjustfield); // Sola dayayarak yazması için. std::cout.setf(std::ios::fixed, std::ios::floatfield); // 'fixed' şeklinde yazması için. std::cout.precision(4); // Ondalık kısımdan dört rakam yazması için. std::cout.width(8); // Yazma alanı genişliği. std::cout << PI << "\n"; std::cout << "-------------------\n"; // Senaryo - II std::cout << std::left << std::fixed << std::setprecision(4) << std::setw(8) << PI << "\n"; std::cout << "-------------------\n"; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # 3.14159 ------------------- 3.142*********** ------------------- */ double PI{3.14159265359}; std::cout << PI << "\n"; std::cout << "-------------------\n"; std::cout << std::left << std::setfill('*') << std::setw(16) << std::setprecision(4) << PI << "\n"; std::cout << "-------------------\n"; return 0; } * Örnek 4, //.. class lineSeparator{ public: lineSeparator() = default; lineSeparator(int totalLine) : m_lineCounter{totalLine}{} friend std::ostream& operator<<(std::ostream& os, const lineSeparator& other) { for(size_t i{}; i < other.m_lineCounter; ++i) os << "\n"; return os; } private: int m_lineCounter{1}; }; int main() { /* # OUTPUT # 3.14159 3.1***** 3.142*********** */ double PI{3.14159265359}; std::cout << PI; std::cout << lineSeparator{}; std::cout << std::left << std::setfill('*') << std::setw(8) << std::setprecision(2) << PI << "\n"; std::cout << lineSeparator{3}; std::cout << std::left << std::setfill('*') << std::setw(16) << std::setprecision(4) << PI << "\n"; return 0; } * Örnek 5, //.. class lineSeparator{ public: lineSeparator() = default; lineSeparator(int totalLine) : m_lineCounter{totalLine}{} friend std::ostream& operator<<(std::ostream& os, const lineSeparator& other) { for(size_t i{}; i < other.m_lineCounter; ++i) os.put(' '); return os; } private: int m_lineCounter{1}; }; int main() { /* # OUTPUT # Ali Veli Mahmut Ulya */ std::cout << "Ali" << lineSeparator{} << "Veli" << lineSeparator{2} << "Mahmut" << lineSeparator{3} << "Ulya" << "\n"; return 0; } >>> Giriş çıkış akımlarının 'conditional-state' bilgisi: Yukarıdaki bayraklar 'fmtflags' türünden değişkenlerken, burada ilgili durum bilgisini tutan değişkenler ise 'iostate' türünden değişkenlerdir. 'iostate' ise tipik olarak 'int' türündendir. İlgili değişkenlerin yine 'static constexpr' olduklarını da unutmayalım. Bu değişkenler ise 'std::ios::goodbit', 'std::ios::failbit', 'std::ios::eofbit' ve 'std::ios::badbit' şeklinde isimlendirilmiştir. * Örnek 1, //.. int main() { /* # OUTPUT # iostate : St12_Ios_Iostate */ std::cout << "iostate : " << typeid(std::ios::iostate).name() << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # std::ios::goodbit : 00000000000000000000000000000000 std::ios::failbit : 00000000000000000000000000000100 std::ios::eofbit : 00000000000000000000000000000010 std::ios::badbit : 00000000000000000000000000000001 */ std::cout << "std::ios::goodbit : " << std::bitset<32>{std::ios::goodbit} << "\n"; // Okuma işlemi yaparken bir başarısızlık söz konusu olduğunda bu bitler 'set' edilir. // Örneğin, bir formatlama hatası var ise 'failbit' 'set' edilmektedir veya akımın // 'buffer' ında karakter yokken okuma yapıldığında 'eofbit' ve 'failbit' 'set' edilir. std::cout << "std::ios::failbit : " << std::bitset<32>{std::ios::failbit} << "\n"; std::cout << "std::ios::eofbit : " << std::bitset<32>{std::ios::eofbit} << "\n"; // Kendini tekrardan 'recover' edemediği durumlarda bu bit 'set' edilmektedir. Örneğin, // giriş-çıkış işlemleri için yeterli bellek alanı elde edilemez ise. std::cout << "std::ios::badbit : " << std::bitset<32>{std::ios::badbit} << "\n"; return 0; } * Örnek 3, //.. void print_stream_state(std::istream& os) { if(os.rdstate() == 0) { std::cout << "Akim, iyi durumda.\n"; return; } std::cout << "failbit => " << (os.rdstate() & std::ios::failbit ? "set" : "unset") << "\n"; std::cout << "eofbit => " << (os.rdstate() & std::ios::eofbit ? "set" : "unset") << "\n"; std::cout << "badbit => " << (os.rdstate() & std::ios::badbit ? "set" : "unset") << "\n"; } int main() { /* # OUTPUT # Akim, iyi durumda. Bir tam sayi giriniz: 15 Girilen tam sayi : 15 Akim, iyi durumda. */ print_stream_state(std::cin); int x{}; std::cout << "Bir tam sayi giriniz: "; std::cin >> x; std::cout << "Girilen tam sayi : " << x << "\n"; print_stream_state(std::cin); return 0; } * Örnek 4, //.. void print_stream_state(std::istream& os) { if(os.rdstate() == 0) { std::cout << "Akim, iyi durumda.\n"; return; } std::cout << "failbit => " << (os.rdstate() & std::ios::failbit ? "set" : "unset") << "\n"; std::cout << "eofbit => " << (os.rdstate() & std::ios::eofbit ? "set" : "unset") << "\n"; std::cout << "badbit => " << (os.rdstate() & std::ios::badbit ? "set" : "unset") << "\n"; } int main() { /* # OUTPUT # Akim, iyi durumda. Bir tam sayi giriniz: ali Girilen tam sayi : 0 failbit => set eofbit => unset badbit => unset */ print_stream_state(std::cin); int x{}; std::cout << "Bir tam sayi giriniz: "; std::cin >> x; std::cout << "Girilen tam sayi : " << x << "\n"; print_stream_state(std::cin); return 0; } * Örnek 5, //.. void print_stream_state(std::istream& os) { if(os.rdstate() == 0) { std::cout << "Akim, iyi durumda.\n"; return; } std::cout << "failbit => " << (os.rdstate() & std::ios::failbit ? "set" : "unset") << "\n"; std::cout << "eofbit => " << (os.rdstate() & std::ios::eofbit ? "set" : "unset") << "\n"; std::cout << "badbit => " << (os.rdstate() & std::ios::badbit ? "set" : "unset") << "\n"; } int main() { /* # OUTPUT # Akim, iyi durumda. Bir tam sayi giriniz: ^a Girilen tam sayi : 0 failbit => set eofbit => unset badbit => unset */ print_stream_state(std::cin); int x{}; std::cout << "Bir tam sayi giriniz: "; std::cin >> x; std::cout << "Girilen tam sayi : " << x << "\n"; print_stream_state(std::cin); return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # Bir tam sayi giriniz: ali HATA!!! HATA!!! */ int x{}; std::cout << "Bir tam sayi giriniz: "; std::cin >> x; // Arka planda 'std::cin.operator bool()' fonksiyonu çağrılmıştır. if(std::cin) { std::cout << "Girilen tam sayi : " << x << "\n"; } else { std::cout << "HATA!!!\n"; } std::cin >> x; // İlgili bayrakları direkt olarak 'get' etmek yerine, bu fonksiyonu da çağırabiliriz. if(std::cin.good()) { std::cout << "Girilen tam sayi : " << x << "\n"; } else { std::cout << "HATA!!!\n"; } return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # Bir tam sayi giriniz: ali HATA!!! HATA!!! */ int x{}; std::cout << "Bir tam sayi giriniz: "; std::cin >> x; // Arka planda 'std::cin.operator!()' fonksiyonu çağrılmıştır. if(!std::cin) { std::cout << "HATA!!!\n"; } else { std::cout << "Girilen tam sayi : " << x << "\n"; } std::cin >> x; // İlgili bayrakları direkt olarak 'get' etmek yerine, bu fonksiyonu da çağırabiliriz. if(std::cin.fail()) { std::cout << "HATA!!!\n"; } else { std::cout << "Girilen tam sayi : " << x << "\n"; } return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # Bir tam sayi giriniz: ayşe HATA!!! HATA!!! */ int x{}; std::cout << "Bir tam sayi giriniz: "; std::cin >> x; if(std::cin.eof()) { std::cout << "Girilen tam sayi : " << x << "\n"; } else { std::cout << "HATA!!!\n"; } std::cin >> x; if(std::cin.bad()) { std::cout << "Girilen tam sayi : " << x << "\n"; } else { std::cout << "HATA!!!\n"; } return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # Bir tam sayi giriniz: ahmet Hatali giriş yaptınız. Tekrar deneyiniz... Bir tam sayi giriniz: ^X Hatali giriş yaptınız. Tekrar deneyiniz... Bir tam sayi giriniz: ^Z 12ahmet Girilen tam sayi : 12 */ int x{}; for(;;) { std::cout << "Bir tam sayi giriniz: "; if(std::cin >> x; std::cin.good()) { std::cout << "Girilen tam sayi : " << x << "\n"; break; } else if(std::cin.eof()) { std::cout << "Bir giriş yapmadınız...\n"; std::cin.clear(); // Programın akışı buraya girmişse demektir ki bir hata bayrağı 'set' // edilmiş. Tekrardan okumanın yapılabilmesi için bütün hata bayraklarının // tekrardan eski haline getirilmesi gerekiyor. // Eğer bu fonksiyona argüman olarak bir 'flag' geçersek, sadece onu tekrardan // eski haline getirmektedir. } else if(std::cin.fail() || std::cin.bad()) { std::cout << "Hatali giriş yaptınız. Tekrar deneyiniz...\n"; std::cin.clear(); // Yukarıdaki aynı sebepten dolayı burada da bayrakları eski haline getirmemiz gerekiyor. std::cin.ignore(std::numeric_limits::max(), '\n'); // Programın akışı buraya girmişse 'buffer' a karakter aktarılmış demektir. // Sağlıklı giriş için 'buffer' içerisindeki bütün karakterleri, 'buffer' dan // çıkartmalıyız. Boşluk karakterini görene kadar bütün karakterler çıkartılacaktır. } else { std::cout << "Something unexpected occured...\n."; std::exit(EXIT_FAILURE); } } return 0; } * Örnek 10, //.. int main() { /* # OUTPUT # Bir rakam girin : 12AhmetKandemirPehlivanli12 Girilen rakam : 12 Geride kalan karakterler : [AhmetKandemirPehlivanli12] */ int x{}; std::cout << "Bir rakam girin : "; std::cin >> x; if(std::cin) std::cout << "Girilen rakam : " << x << "\n"; std::string tempBufferChars{}; std::cin >> tempBufferChars; std::cout << "Geride kalan karakterler : [" << tempBufferChars << "]\n"; return 0; } * Örnek 12, //.. int main() { /* # OUTPUT # Bir rakam girin : 12AhmetKandemirPehlivanliQ21 Girilen rakam : 12 Geride kalan rakamlar : [21] */ int x{}; std::cout << "Bir rakam girin : "; std::cin >> x; if(std::cin) std::cout << "Girilen rakam : " << x << "\n"; std::cin.ignore(std::numeric_limits::max(), 'Q'); // 'Q' karakteri gelene kadar bütün karakterler 'buffer' dan boşaltıldı. int tempBufferInt{}; std::cin >> tempBufferInt; std::cout << "Geride kalan rakamlar : [" << tempBufferInt << "]\n"; return 0; } >>> Bellekte üzerinde okuma ve yazma işlemlerini gerçekleştiren sınıflar: 'sstream' başlık dosyasını çağırmamız gerekiyor. Bünyesinde 'std::ostringstream', 'std::istringstream' ve 'std::stringstream' sınıflarını içermektedir. * Örnek 1, //.. int main() { /* # OUTPUT # -------------------------- ID : [6144] Name : [Ahmet] Age : [17] -------------------------- */ std::ostringstream oss; // Belleğe yazmak için kullanılacak. int ival{17}; int ID{6144}; std::string name{"Ahmet"}; // Değişkenler belleğe yazıldılar. oss << "ID : [" << ID << "]\n" << "Name : [" << name << "]\n" << "Age : [" << ival << "]\n"; // Bellekteki değişkenleri bir 'std::string' olarak tekrar geri alabiliriz: std::cout << "--------------------------\n" << oss.str() << "--------------------------\n"; return 0; } * Örnek 2, //.. std::string get_log_file_name() { std::ostringstream oss; time_t timer; time(&timer); tm* p{ localtime(&timer) }; oss.fill('0'); oss << p->tm_year + 1900 << '_' << std::setw(2) << (p->tm_mon + 1) << '_' << std::setw(2) << (p->tm_mday) << '_' << std::setw(2) << (p->tm_hour) << '_' << std::setw(2) << (p->tm_min) << '_' << std::setw(2) << (p->tm_sec) << ".log"; return oss.str(); } int main() { /* # OUTPUT # [2022_05_22_23_51_18.log] */ std::cout << "[" << get_log_file_name() << "]\n"; return 0; } * Örnek 3, //.. class Triple{ public: //... friend std::ostream& operator<<(std::ostream& os, const Triple& other) { return os << "(" << other.m_a << ", " << other.m_b << ", " << other.m_c << ")\n"; } private: //... }; int main() { /* # OUTPUT # 0x7ffd2976a66c adresinde yeni bir nesne hayata geldi. (17, 9, 1993) => Ahmet Kandemir Pehlivanli 0x7ffd2976a66c adresindeki nesnenin hayatı sona erdi. */ Triple mx{17, 9, 1993}; std::cout << std::setw(10) << mx << " => Ahmet Kandemir Pehlivanli\n"; // Çıktıdan da görüldüğü üzere yazma alanı genişliği kendinden sonraki ilk // karakteri etkilediğinden, ilgili sınıf nesnesi ile ismim arasında 10 karakterlik // boşluk meydana gelmedi. return 0; } * Örnek 4, //.. class Triple{ public: //.. friend std::ostream& operator<<(std::ostream& os, const Triple& other) { std::ostringstream oss; oss << "(" << other.m_a << ", " << other.m_b << ", " << other.m_c << ")\n"; return os << oss.str(); } private: //.. }; int main() { /* # OUTPUT # 0x7fff05f7bacc adresinde yeni bir nesne hayata geldi. (17, 9, 1993) => Ahmet Kandemir Pehlivanli 0x7fff05f7bacc adresindeki nesnenin hayatı sona erdi. */ Triple mx{17, 9, 1993}; std::cout << std::setw(10) << mx << " => Ahmet Kandemir Pehlivanli\n"; // Evet işte şimdi tam da istediğimiz gibi oldu. return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # Day : 17 Month : 9 Year : 1993 Name : Ahmet */ std::istringstream iss{"17 9 1993 Ahmet"}; size_t day, month, year; std::string name; iss >> day >> month >> year >> name; std::cout << "Day : " << day << "\n" << "Month : " << month << "\n" << "Year : " << year << "\n" << "Name : " << name << "\n"; return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # Bir yazi girin : Bugün ilk defa İstanbul'a kar yağdığını gördüm. Gerçekten de uzun zamandır İstanbul'a kar yağmıyordu. [Bugün] [ilk] [defa] [İstanbul'a] [kar] [yağdığını] [gördüm.] [Gerçekten] [de] [uzun] [zamandır] [İstanbul'a] [kar] [yağmıyordu.] */ std::cout << "Bir yazi girin : "; std::string sLine; std::getline(std::cin, sLine); std::istringstream iss{sLine}; std::string word{}; while(iss >> word) std::cout << "[" << word << "]\n"; return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # [dilber01.txt][dilber01.txtsu02.txt][dilber01.txtsu02.txtemine03.txt][dilber01.txtsu02.txtemine03.txtsoner04.txt] */ std::ostringstream oss; oss << std::setfill('0'); for(size_t i{1}; i < 5; ++i) { oss << rname() << std::setw(2) << i << ".txt"; std::cout << "[" << oss.str() << "]"; } // Çıktıdan da görüldüğü üzere 'oss' 'buffer' boşaltılmadığı için. return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # [celal01.txt][suphi02.txt][menekse03.txt][egemen04.txt] */ for(size_t i{1}; i < 5; ++i) { std::ostringstream oss; oss << std::setfill('0'); oss << rname() << std::setw(2) << i << ".txt"; std::cout << "[" << oss.str() << "]"; } // Çıktıdan da görüldüğü üzere 'oss' 'buffer' boşaltılmadığı için. return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # [feramuz01.txt][sami02.txt][rumeysa03.txt][gursel04.txt] */ std::ostringstream oss; oss << std::setfill('0'); for(size_t i{1}; i < 5; ++i) { oss << rname() << std::setw(2) << i << ".txt"; std::cout << "[" << oss.str() << "]"; oss.str(""); // İlgili fonksiyona boş bir yazı geçildiğinde, 'buffer' sıfırlanmaktadır. } return 0; } /*============================================================================================================*/ (36_17_01_2021) > 'STL' içerisindeki kaplar (devam) : >> Input-Output Operations (devam) : >>> 'ostream_iterator' hatırlatması: Bir 'range' içerisindeki öğeleri bir akıma yazdırmak için kullanılır. * Örnek 1, //.. int main() { /* # OUTPUT # bekir, adem, pinat, rumeysa, papatya, */ std::vector sVec; fcs(sVec, 5, rname); std::copy(sVec.begin(), sVec.end(), std::ostream_iterator(std::cout, ", ")); return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # sidre gazi ece aykut emrecan ----------------------------------------------------------------------------- n a c e r m e _ t u k y a _ e c e _ i z a g _ e r d i s ----------------------------------------------------------------------------- */ std::vector sVec; fcs(sVec, 5, rname); print(sVec); std::ostringstream oss; std::copy(sVec.begin(), sVec.end(), std::ostream_iterator(oss, "_")); auto myStr{ oss.str() }; myStr.pop_back(); std::reverse(myStr.begin(), myStr.end()); print(myStr); return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # gunay | derin | aylin | alican | sinem | sumeyye | ceyhun | pakize | ercument | murathan | */ // Hedef 'range' i aşağıdaki bilgiler ışığında doldurmaca std::generate_n( std::ostream_iterator{std::cout, " | "}, // Hedef 'range' için başlangıç konumu 10, // Kaç defa aşağıdaki 'callable' çağrılacağı bilgisi rname // Çağrılacak 'callable' ); return 0; } >>> 'std::istream_iterator' : 'std::ostream_iterator' a nazaran herhangi bir akımdan okuma yapmamızı sağlamaktadır. * Örnek 1, //.. int main() { /* # OUTPUT # 1 11 111 222 22 2 a [6] => 1-11-111-222-22-2- ----------------------------------------------------------------------------- */ std::vector iVec{ std::istream_iterator(std::cin), std::istream_iterator{} }; // Yukarıdaki parametreler ile bir 'range' oluşturmuş olduk. Giriş akımındaki bütün rakamları, // 'iVec' isimli kaba aktaracaktır. // Girişi sonlandırmak için herhangi bir karakter girişi yapabiliriz. std::cout << "[" << iVec.size() << "] => "; print(iVec, "-"); return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # Max Element : 12 24 36 48 60 72 a 72 */ std::cout << "Max Element : " << *std::max_element( std::istream_iterator(std::cin), std::istream_iterator{} ) << "\n"; // Bir önceki örnekteki gibi bir 'range' oluşturduğumuz için, bu 'range' i ise 'std::max_element()' fonksiyonuna // argüman olarak geçebildik. Böylelikle girdiğimiz sayılar arasındaki en büyük sayıyı bulmuş olduk. return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # Toplam: 1 11 111 -111 -11 -1 a 0 */ std::cout << "Toplam: " << std::accumulate( std::istream_iterator(std::cin), std::istream_iterator{}, 0 ) << "\n"; // İş bu fonksiyon, 'numeric' başlık dosyasında bildirilmiş olup bir 'range' içerisindeki // rakamları, bir baz değer üzerine toplamaktadır. Bahsi geçen baz değer ise ikinci parametreye // geçilen argüman şeklindedir. // std::cout << "Toplam: " << std::accumulate( std::istream_iterator(std::cin), {}, 0 ) << "\n"; // C++17 ile birlikte yukarıdaki 'std::accumulate()' fonksiyonunun ilk iki parametresine geçilen 'range' bilgilerinden // ikinci argüman yerine sadece '{}' yazarak tür çıkarımını derleyiciye de yaptırtabiliriz. return 0; } * Örnek 4, Aşağıdaki örnekte üç farklı kod bloğu aynı blok içerisinde yazılmıştır. Bloğu üçe bölerek ayrı ayrı çalıştırmalısınız. //.. int main() { // From 'vector' to a file. /* # source.txt # askin_muglali korhan_poturgeli fugen_kayabasi caner_dunyalik yunus_ordulu ceyhun_yarma efe_comakci emirhan_kaplan rumeysa_yavas teslime_eloglu */ std::vector sVec; fcs(sVec, 10, []{ return rname() + "_" + rfname(); }); std::ofstream ofs{ "source.txt" }; // Bir dosyaya yazılacaktır. if(!ofs) std::exit(EXIT_FAILURE); std::copy(std::begin(sVec), std::end(sVec), std::ostream_iterator(ofs, "\n")); // Bir dosyaya yazılacaktır. // From the file to 'std::set' /* # OUTPUT # cengiz_kahraman durmus_fedai hilal_kecisakal hulki_topatan menekse_soysalan metin_cengaver nagehan_keskin poyraz_sonuzun seyhan_akgunes tayfun_topatan ----------------------------------------------------------------------------- */ std::ifstream ifs{ "source.txt" }; if(!ifs) std::exit(EXIT_FAILURE); std::set mySet{ std::istream_iterator{ifs}, {} }; // C++17 ve sonrasındaki dönemde ikinci argüman için tür çıkarımı yapılacaktır. print(mySet, "\n"); // From the 'std::set' to the same file. /* # source.txt # cengiz_kahraman durmus_fedai hilal_kecisakal hulki_topatan menekse_soysalan metin_cengaver nagehan_keskin poyraz_sonuzun seyhan_akgunes tayfun_topatan */ std::ofstream ofs{ "source.txt" }; // Bir dosyaya yazılacaktır. if(!ofs) std::exit(EXIT_FAILURE); std::copy(std::begin(mySet), std::end(mySet), std::ostream_iterator(ofs, "\n")); // Bir dosyaya yazılacaktır. return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # */ // Most Vexing Parse std::vector sVec( std::istream_iterator(std::cin), std::istream_iterator() ); std::cout << "[" << sVec.size() << "]\n"; return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # 1 3 5 7 9 9 7 5 3 1 a 1_1_3_3_5_5_7_7_9_9_ */ // Standart giriş akımından alınan yazıları alfabetik sıraya göre ekrana yazma: std::multiset myMultiSet{ std::istream_iterator{std::cin}, {} }; std::copy(std::begin(myMultiSet), std::end(myMultiSet), std::ostream_iterator{std::cout, "_"}); return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # Bir yazi girin => Bugün hava kapalı olduğundan, bütün gün başım ağrıdı. Bugün ağrıdı. başım bütün gün hava kapalı olduğundan, */ std::string str{}; std::cout << "Bir yazi girin => "; std::getline(std::cin, str); std::istringstream iss{str}; std::string singleWord{}; std::vector theMessage{}; while(iss >> singleWord) theMessage.push_back(singleWord); std::sort(theMessage.begin(), theMessage.end()); std::copy(theMessage.begin(), theMessage.end(), std::ostream_iterator{std::cout, "\n"}); return 0; } >>> '.rdbuf()' fonksiyonu: 'std::streambuf' türünden bir gösterici döndürmekte olup ilgili çıkış akımının 'buffer' adresini döndürmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # cengiz_kahraman durmus_fedai hilal_kecisakal hulki_topatan menekse_soysalan metin_cengaver nagehan_keskin poyraz_sonuzun seyhan_akgunes tayfun_topatan */ std::ifstream ifs{ "source.txt" }; // Bahsi geçen dosya açıldığında içerisindeki yazılar bu dosyanın 'buffer' bölgesine aktarılmakta. std::cout << ifs.rdbuf(); // İş bu '.rdbuf()' fonksiyonu ise yukarıda açıklanan 'buffer' bölgesinin adresini döndürmekte. // 'std::ostream' sınıfının da ilgili 'buffer' parametre türünden bir 'inserter' operatörü var. return 0; } * Örnek 2, //.. // fcopy source.txt dest.txt int main(int argc, char** argv) { /* # OUTPUT # */ if(argc != 3) { std::cerr << "usage : \n"; return 1; } std::ifstream ifs{ argv[1] }; //... std::ofstream ofs{ argv[2] }; //... ofs << ifs.rdbuf(); return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # using ofs... 47802 1 using std::cout... 47802 1 using ofs after manipulating its format settings... 0XBABA true using std::cout w/o formating its format settings... 47802 1 */ std::ostream ofs{ std::cout.rdbuf() }; // Artık 'ofs' ile 'std::cout' aynı 'buffer' bölgesini kullanmaktalar. ofs << "using ofs...\n"; int ival{ 47802 }; bool flag{ true }; ofs << ival << " " << flag << "\n"; std::cout << "using std::cout...\n"; std::cout << ival << " " << flag << "\n"; ofs << std::uppercase << std::hex << std::showbase << std::boolalpha; ofs << "using ofs after manipulating its format settings...\n"; ofs << ival << " " << flag << "\n"; std::cout << "using std::cout w/o formating its format settings...\n"; std::cout << ival << " " << flag << "\n"; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # 0XBABA_0XDEDE_4.19E+03_false 47802_57054_4187.72_0 */ std::ostream ofs{ std::cout.rdbuf() }; // Artık 'ofs' ile 'std::cout' aynı 'buffer' bölgesini kullanmaktalar. ofs.setf(std::ios::boolalpha | std::ios::uppercase | std::ios::showbase); ofs.setf(std::ios::hex, std::ios::basefield); ofs.setf(std::ios::scientific, std::ios::floatfield); ofs.precision(2); int x{ 47'802 }, y{ 57'054 }; double z{ 4187.7233 }; ofs << x << "_" << y << "_" << z << "_" << (x > y) << "\n"; std::cout << x << "_" << y << "_" << z << "_" << (x > y) << "\n"; return 0; } * Örnek 5, //.. void print_file_n_times(const std::string& fileName, int counter) { std::ifstream ifs{ fileName }; if(!ifs) { std::cerr << "dosya acilamadi...\n"; std::exit(EXIT_FAILURE); } while(--counter) { std::cout << ifs.rdbuf(); std::cout << "\n-----------------------------\n"; ifs.seekg(0); // Dosya konum göstericisi tekrardan en başa alındı. } } int main() { /* # OUTPUT # cengiz_kahraman durmus_fedai hilal_kecisakal hulki_topatan menekse_soysalan metin_cengaver nagehan_keskin poyraz_sonuzun seyhan_akgunes tayfun_topatan ----------------------------- cengiz_kahraman durmus_fedai hilal_kecisakal hulki_topatan menekse_soysalan metin_cengaver nagehan_keskin poyraz_sonuzun seyhan_akgunes tayfun_topatan ----------------------------- */ print_file_n_times("source.txt", 3); return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # Ahmet Kandemir Pehlivanli Local Content => Ulya Yürük */ std::streambuf* defaultCoutBuffer{ std::cout.rdbuf() }; std::ostringstream oss; std::cout.rdbuf( oss.rdbuf() ); // Artık 'std::cout', 'oss' isimli değişkenin 'buffer' alanını kullanmakta. // Dolayısıyla ekran yerine belleğe yazacaktır. std::cout << "Ulya Yürük\n"; std::cout.rdbuf(defaultCoutBuffer); // 'std::cout' artık kendi 'buffer' alanını kullanmaktadır. // Dolayısıyla bellek yerine ekrana yazacaktır. std::cout << "Ahmet Kandemir Pehlivanli\n"; std::cout << "Local Content => " << oss.str() << "\n"; return 0; } >>> Giriş çıkış işlemlerinde 'Exception Handling' : Giriş-çıkış akımları üzerinde yapılan işlemler herhangi bir hata fırlatmamaktadır. * Örnek 1, //.. void print_exception_state(const std::istream& ib) { auto currentState{ ib.exceptions() }; // İş bu fonksiyonu bu haliyle kullanırsak 'getter' görevi görür. std::cout << "\n----------------------------------------------------\n"; if(currentState == 0) std::cout << "hata durumunda exception throw etmez.\n"; if(currentState & std::ios::eofbit) std::cout << "eofbit set edildiginde, exception throw eder.\n"; if(currentState & std::ios::failbit) std::cout << "failbit set edildiginde, exception throw eder.\n"; if(currentState & std::ios::badbit) std::cout << "badbit set edildiginde, exception throw eder.\n"; std::cout << "\n----------------------------------------------------\n"; } int main() { /* # OUTPUT # ---------------------------------------------------- hata durumunda exception throw etmez. ---------------------------------------------------- ---------------------------------------------------- badbit set edildiginde, exception throw eder. ---------------------------------------------------- ---------------------------------------------------- eofbit set edildiginde, exception throw eder. failbit set edildiginde, exception throw eder. ---------------------------------------------------- */ print_exception_state(std::cin); std::cin.exceptions(std::ios::badbit); // Artık iş bu fonksiyon 'setter' olarak işlev görmekte. print_exception_state(std::cin); std::cin.exceptions(std::ios::failbit | std::ios::eofbit); print_exception_state(std::cin); return 0; } * Örnek 2, //.. void print_exception_state(const std::istream& ib) { auto currentState{ ib.exceptions() }; // İş bu fonksiyonu bu haliyle kullanırsak 'getter' görevi görür. std::cout << "\n----------------------------------------------------\n"; if(currentState == 0) std::cout << "hata durumunda exception throw etmez.\n"; if(currentState & std::ios::eofbit) std::cout << "eofbit set edildiginde, exception throw eder.\n"; if(currentState & std::ios::failbit) std::cout << "failbit set edildiginde, exception throw eder.\n"; if(currentState & std::ios::badbit) std::cout << "badbit set edildiginde, exception throw eder.\n"; std::cout << "\n----------------------------------------------------\n"; } int main() { /* # OUTPUT # ---------------------------------------------------- failbit set edildiginde, exception throw eder. ---------------------------------------------------- Bir rakam girin : ahmet Hata yakalandi... => basic_ios::clear: iostream error */ auto defaultCinStateFlags{ std::cin.exceptions() }; std::cin.exceptions(std::ios::failbit); // 'failbit' isimli bayrak 'set' edildi. print_exception_state(std::cin); try{ std::cout << "Bir rakam girin : "; int x; std::cin >> x; std::cout << "Girilen rakam : " << x << "\n"; } catch(const std::exception& ex) { std::cout << "Hata yakalandi... => " << ex.what() << "\n"; } return 0; } * Örnek 3, //.. void print_exception_state(const std::istream& ib) { auto currentState{ ib.exceptions() }; // İş bu fonksiyonu bu haliyle kullanırsak 'getter' görevi görür. std::cout << "\n----------------------------------------------------\n"; if(currentState == 0) std::cout << "hata durumunda exception throw etmez.\n"; if(currentState & std::ios::eofbit) std::cout << "eofbit set edildiginde, exception throw eder.\n"; if(currentState & std::ios::failbit) std::cout << "failbit set edildiginde, exception throw eder.\n"; if(currentState & std::ios::badbit) std::cout << "badbit set edildiginde, exception throw eder.\n"; std::cout << "\n----------------------------------------------------\n"; } int main() { /* # OUTPUT # ---------------------------------------------------- failbit set edildiginde, exception throw eder. ---------------------------------------------------- Bir rakam girin : ahmet std::terminate cagrildi.... myterminate cagrildi.... std::abort() cagrildi */ auto defaultCinStateFlags{ std::cin.exceptions() }; std::cin.exceptions(std::ios::failbit); // 'failbit' isimli bayrak 'set' edildi. print_exception_state(std::cin); std::set_terminate(my_terminate); // 'std::abort' yerine bizim sonlandırıcı fonksiyonumuz çağrılacaktır. std::cout << "Bir rakam girin : "; int x; std::cin >> x; std::cout << "Girilen rakam : " << x << "\n"; return 0; } * Örnek 4, //.. double readSum(std::istream& is) { auto oldException{ is.exceptions() }; is.exceptions( std::ios::failbit | std::ios::badbit ); // 'failbit' veya 'badbit' ler 'set' edildiğinde,'exception throw' edecek hale geldi. double dval, sum{}; try{ while(is >> dval) sum += dval; } catch(...) { if(!is.eof()) // Bu bloğa girmesi halinde okunacak 'byte' kalmamış demektir. { is.exceptions( oldException ); // Bu akımı kullanan başka kodlar olabileceği için bayraklar tekrar eski haline getirildi. throw; // 'exception re-throw' edilmiştir. } } is.exceptions( oldException ); return sum; } int main() { // std::istringstream iss{ "2.6 3.4 5.6 7.8 9.1" }; // OUTPUT => Sum : 28.5 std::istringstream iss{ "2.6 3.4 ali 7.8 9.1" }; // OUTPUT => giris-cıkıs hatasi... => basic_ios::clear: iostream error double sum; try{ sum = readSum(iss); } catch(const std::ios::failure& ex) { std::cerr << "giris-cıkıs hatasi... => " << ex.what() << "\n"; return 1; } catch(const std::exception& ex) { std::cerr << "standart kutuphane hatasi... => " << ex.what() << "\n"; return 2; } catch(...) { std::cerr << "bilinmeyen bir hata...\n"; return 3; } std::cout << "Sum : " << sum << "\n"; return 0; } >> Dosya İşlemleri : Üç adet sınıf şablonumuz vardır ve bunlar 'fstream' başlık dosyasında tanımlanmışlardır. >>> Sırasıyla bu sınıflar 'std::ifstream', 'std::ofstream' ve 'fstream' şeklindedirler. Bu sınıflardan 'std::ifstream' sınıfı 'std::istream' sınıfından, 'std::ofstream' sınıfı 'std::ostream' sınıfından ve 'std::fstream' sınıfı ise 'std::iostream' sınıfından kalıtım yoluyla elde edilmiş olduklarından sırasıyla okuma, yazma ve hem yazma hem okuma arayüzüne sahiptirler. >>> Bu üç sınıfımız 'default constructable' olduklarından, böyle bir senaryoda herhangi bir dosya ile ilişkilendirilmemiş sınıf nesnesi hayata getirmiş oluyoruz. Bu ilişki bilgisini de sınıfın '.is_open()' fonksiyonu vesilesi ile öğrenebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # Acik bir dosya yoktur... Acil bir dosya VARDIR... Acik bir dosya yoktur... Acik bir dosya yoktur... */ std::ifstream ifs; if(ifs.is_open()) { std::cout << "Acil bir dosya VARDIR...\n"; } else { std::cout << "Acik bir dosya yoktur...\n"; } std::ifstream ifsTwo{ "source.txt" }; if(ifsTwo.is_open()) { std::cout << "Acil bir dosya VARDIR...\n"; } else { std::cout << "Acik bir dosya yoktur...\n"; } ifsTwo.close(); if(ifsTwo.is_open()) { std::cout << "Acil bir dosya VARDIR...\n"; } else { std::cout << "Acik bir dosya yoktur...\n"; } std::ifstream ifsThree{ "sourceTwo.txt" }; if(ifsThree.is_open()) { std::cout << "Acil bir dosya VARDIR...\n"; } else { std::cout << "Acik bir dosya yoktur...\n"; } // İş bu '.is_open()' fonksiyonu dosya açma işleminin başarılı bir şekilde yapılıp yapılmadığı değil, // ilgili sınıf nesnesinin bir dosya ile ilişkilendirilip ilişkilendirilmediğini SINAMAKTADIR. return 0; } >>> Sınıflarımınız 'Dtor.' fonksiyonları ise 'RAII' deyimini gütmektedir. >>> Yazma amacıyla dosya oluştururken; >>>> Dosya yok ise yeni bir dosya oluşturulur. >>>> Dosya var ise içerisindeki bilgiler silinir, dosya sıfırlanır. >>> İş bu sınıflarımızı kullanarak bir dosya açacaksak ya sınıfın '.open()' isimli fonksiyonunu ya da parametreli 'Ctor.' fonksiyonlarına çağrı yapmamız gerekmektedir. Fakat unutmamalıyız ki parametreli 'Ctor.' fonksiyonuna çağrı yaparsak, dosya açış mod bilgisini de geçmeliyiz. >>>> Dosya açış mod bilgisi taban sınıftan gelen 'std::ios_base::openmode' türden maskelerdir. Bu maskeler 'in', 'out', 'trunc', 'binary' ve 'ate' şeklindedir. Bu bayraklardan; >>>>> 'trunc' : Dosyanın sıfırlanması içindir. İçindeki verileri kaybedeceğiz. >>>>> 'ate' : Dosya konum göstericisini dosyanın sonuna ötelemektedir. >>>>> 'binary' : Dosyayı 'text' yerine 'binary' modda açmak içindir. >>>> Dosya açım sırasında bu maskeleri kullanmazsak varsayılan bayraklar; >>>>> 'std::ifstream' sınıfı için 'std::ios::in', >>>>> 'std::ofstream' sınıfı için 'std::ios::out' ve 'std::ios::trunc' bayrakları, >>>>> 'std::fstream' sınıfı için 'std::ios::in' ve 'std::ios::out' şeklindedir. >>> C dilindeki dosya işlemleri ile karşılaştırılması: * Örnek 1, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "r") }; // In Cpp lang. std::ifstream ifs{ "source.txt" }; return 0; } * Örnek 2, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "rb") }; // In Cpp lang. std::ifstream ifs{ "source.txt", std::ios::binary }; return 0; } * Örnek 3, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "r+") }; // In Cpp lang. std::fstream fs{ "source.txt" }; return 0; } * Örnek 4, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "rb+") }; // In Cpp lang. std::fstream fs{ "source.txt", std::ios::binary }; return 0; } * Örnek 5, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "w") }; // In Cpp lang. std::ofstream ofs{ "source.txt" }; return 0; } * Örnek 6, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "wb") }; // In Cpp lang. std::ofstream ofs{ "source.txt", std::ios::binary}; return 0; } * Örnek 7, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "w+") }; // In Cpp lang. std::fstream fs{ "source.txt" }; return 0; } * Örnek 8, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "wb+") }; // In Cpp lang. std::fstream fs{ "source.txt", std::ios::binary }; return 0; } * Örnek 9, //.. int main() { // In C lang. FILE* f{ fopen("source.txt", "a") }; // In Cpp lang. std::fstream fs{ "source.txt", std::ios::app }; return 0; } >>> Dosyanın açılamaması durumunda ilgili akım nesnesi 'fail' duruma geçecektir ve '.is_open()' fonksiyonu 'true' değer döndürecektir. * Örnek 1, //.. int main() { /* # OUTPUT # Dosya acilamadi... */ std::ifstream ifs{ "sourceTwo.txt" }; // if(ifs.fail()) if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } else { std::cout << "Dosya acildi...\n"; } if(ifs.is_open()) { std::cout << "Dosya acildi...\n"; } return 0; } >>> Sınıf nesnesinin kendisi hayattayken dosyayı kapatmak için '.close()' fonksiyonunu çağırabiliriz. Böylelikle dosya kapatılacaktır. Sonrasında aynı nesne ile başka bir dosya açmak için '.open()' fonksiyonuna çağrı yapmamız gerekmektedir. >>> Dosyadan okuma ve yazma işlemleri: C dilindeki formatlı giriş çıkış işlemlerinin('fscanf()', 'fprintf()') Cpp dilindeki karşılığı '.operator<<()' ve '.operator>>()' fonksiyonlarıdır. Yine C dilindeki formatsız giriş çıkış işlemlerinin ('fread()', 'fwrite()') Cpp dilindeki karşılığı ise '.write()' ve '.read()' isimlerindeki üye fonksiyonlardır. * Örnek 1, //.. int main() { /* # prime_005.txt # 2 | 3 | 5 | 7 | 11 | */ size_t totalPrime{ 5 }; // İlk beş asal sayı. // Dosyamızın ismini ilk önce belleğe yazacağız. std::ostringstream oss; oss << std::setfill('0'); oss << "prime_" << std::setw(3) << totalPrime << ".txt"; // Dosyamızı 'yazma' modunda açıyoruz. std::ofstream ofs{ oss.str() }; if(!ofs) { std::cerr << "dosya olusturulamadi...\n"; return 1; } // Dosyamıza yazma işlemini gerçekleştiriyoruz. size_t primeNumber{ 2 }; size_t primeCounter{ 0 }; while(primeCounter < totalPrime) { if(isprime(primeNumber)) { ofs << primeNumber << " | "; ++primeCounter; } ++primeNumber; } return 0; } * Örnek 2, //.. int main() { /* # prime_005.txt # 2 | 3 | 5 | 7 | 11 | */ size_t totalPrime{ 5 }; // İlk beş asal sayı. // Dosyamızın ismini ilk önce belleğe yazacağız. std::ostringstream oss; oss << std::setfill('0'); oss << "prime_" << std::setw(3) << totalPrime << ".txt"; // Dosyamızı 'yazma' modunda açıyoruz. std::ofstream ofs{ oss.str() }; if(!ofs) { std::cerr << "dosya olusturulamadi...\n"; return 1; } // Dosyamıza yazma işlemini gerçekleştiriyoruz. size_t primeNumber{ 2 }; size_t primeCounter{ 0 }; while(primeCounter < totalPrime) { if(isprime(primeNumber)) { if(primeCounter && primeCounter % 3 == 0) ofs << "\n"; ofs << primeNumber << " | "; ++primeCounter; } ++primeNumber; } return 0; } * Örnek 3, //.. int main() { /* # prime_005.txt # 0X2 | 0X3 | 0X5 | 0X7 | 0XB | */ size_t totalPrime{ 5 }; // İlk beş asal sayı. // Dosyamızın ismini ilk önce belleğe yazacağız. std::ostringstream oss; oss << std::setfill('0'); oss << "prime_" << std::setw(3) << totalPrime << ".txt"; // Dosyamızı 'yazma' modunda açıyoruz. std::ofstream ofs{ oss.str() }; if(!ofs) { std::cerr << "dosya olusturulamadi...\n"; return 1; } // Dosyamıza yazma işlemini gerçekleştiriyoruz. size_t primeNumber{ 2 }; size_t primeCounter{ 0 }; ofs << std::left << std::hex << std::showbase << std::uppercase; while(primeCounter < totalPrime) { if(isprime(primeNumber)) { if(primeCounter && primeCounter % 3 == 0) ofs << "\n"; ofs << std::setw(3) << primeNumber << " | "; ++primeCounter; } ++primeNumber; } return 0; } * Örnek 4, //.. int main() { /* # source.txt # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi devrim ergin - kirklareli, 30 Aralik 1991 Pazartesi nihat olmez - batman, 24 Nisan 1976 Cumartesi polat supuren - bolu, 23 Aralik 1987 Carsamba abdullah elebasi - siirt, 17 Temmuz 1972 Pazartesi semsit cansever - tokat, 17 Agustos 2016 Carsamba melih kapici - batman, 28 Ekim 1968 Pazartesi durmus temiz - adana, 10 Subat 2011 Persembe nihat uluocak - duzce, 07 Haziran 1955 Sali */ std::ofstream ofs{ "source.txt" }; if(!ofs) { std::cerr << "Dosya olusturulamadi...\n"; return 1; } ofs << std::left; for(size_t i{}; i < 10; ++i) { ofs << std::setw(20) << (rname() + " " + rfname()) << " - " << rtown() << ", " << Date::random() << "\n"; } return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi devrim ergin - kirklareli, 30 Aralik 1991 Pazartesi nihat olmez - batman, 24 Nisan 1976 Cumartesi polat supuren - bolu, 23 Aralik 1987 Carsamba abdullah elebasi - siirt, 17 Temmuz 1972 Pazartesi semsit cansever - tokat, 17 Agustos 2016 Carsamba melih kapici - batman, 28 Ekim 1968 Pazartesi durmus temiz - adana, 10 Subat 2011 Persembe nihat uluocak - duzce, 07 Haziran 1955 Sali */ // Dosyamızı 'okuma' modunda açıyoruz. std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } std::string words{}; while( ifs >> words ) { std::cout << words << "\n"; } return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi devrim ergin - kirklareli, 30 Aralik 1991 Pazartesi nihat olmez - batman, 24 Nisan 1976 Cumartesi polat supuren - bolu, 23 Aralik 1987 Carsamba abdullah elebasi - siirt, 17 Temmuz 1972 Pazartesi semsit cansever - tokat, 17 Agustos 2016 Carsamba melih kapici - batman, 28 Ekim 1968 Pazartesi durmus temiz - adana, 10 Subat 2011 Persembe nihat uluocak - duzce, 07 Haziran 1955 Sali */ // Dosyamızı 'okuma' modunda açıyoruz. std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } std::string words{}; size_t wordCounter{}; while( ifs >> words ) { if( wordCounter && wordCounter % 8 == 0) { std::cout << "\n"; } std::cout << words << " "; ++wordCounter; } return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 5 Ocak 1974 Cumartesi devrim ergin - kirklareli, 30 Aralik 1991 Pazartesi nihat olmez - batman, 24 Nisan 1976 Cumartesi polat supuren - bolu, 23 Aralik 1987 Carsamba abdullah elebasi - siirt, 17 Temmuz 1972 Pazartesi semsit cansever - tokat, 17 Agustos 2016 Carsamba melih kapici - batman, 28 Ekim 1968 Pazartesi durmus temiz - adana, 10 Subat 2011 Persembe nihat uluocak - duzce, 7 Haziran 1955 Sali */ // Dosyamızı 'okuma' modunda açıyoruz. std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } // Dosyamız içerisindekileri ayrı ayrı değişkenlerde saklayacağız. std::string name, surname, town, bMonth, bWeekDay; size_t bDay, bYear; char wordSeparator; // Dosyadan okuduklarımızı da ekrana yazacağız. std::cout << std::left; while( ifs >> name >> surname >> wordSeparator >> town >> bDay >> bMonth >> bYear >> bWeekDay ) { std::cout << std::setw(20) << (name + " " + surname) << " " << wordSeparator << " " << town << " " << bDay << " " << bMonth << " " << bYear << " " << bWeekDay << "\n"; } return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # [80] => sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi devrim ergin - kirklareli, 30 Aralik 1991 Pazartesi nihat olmez - batman, 24 Nisan 1976 Cumartesi polat supuren - bolu, 23 Aralik 1987 Carsamba abdullah elebasi - siirt, 17 Temmuz 1972 Pazartesi semsit cansever - tokat, 17 Agustos 2016 Carsamba melih kapici - batman, 28 Ekim 1968 Pazartesi durmus temiz - adana, 10 Subat 2011 Persembe nihat uluocak - duzce, 07 Haziran 1955 Sali */ std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } // Dosyadaki kelimeleri kaba aktarmış olduk. std::vector sVec{ std::istream_iterator{ifs}, {} }; std::cout << "[" << sVec.size() << "] => "; std::copy(sVec.begin(), sVec.end(), std::ostream_iterator{std::cout, "\n"}); return 0; } * Örnek 9, 'source.txt' dosyasındaki satır sayısı ikiye indirilmiştir. //.. int main() { /* # OUTPUT # [s] | [a] | [d] | [e] | [t] | [t] | [i] | [n] | [ ] | [o] | [t] | [a] | [c] | [i] | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] | [-] | [ ] | [b] | [u] | [r] | [s] | [a] | [,] | [ ] | [2] | [7] | [ ] | [M] | [a] | [y] | [i] | [s] | [ ] | [1] | [9] | [5] | [0] | [ ] | [C] | [u] | [m] | [a] | [r] | [t] | [e] | [s] | [i] | [] | [u] | [f] | [u] | [k] | [ ] | [a] | [l] | [t] | [i] | [n] | [i] | [s] | [i] | [k] | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] | [-] | [ ] | [b] | [a] | [t] | [m] | [a] | [n] | [,] | [ ] | [0] | [5] | [ ] | [O] | [c] | [a] | [k] | [ ] | [1] | [9] | [7] | [4] | [ ] | [C] | [u] | [m] | [a] | [r] | [t] | [e] | [s] | [i] | [] | */ std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } // Dosyadaki yazıları 'byte' nezdinde okuyacağız. char c{}; while(ifs.get(c)) { std::cout << "[" << c << "] | "; } return 0; } * Örnek 10, 'source.txt' dosyasındaki satır sayısı ikiye indirilmiştir. //.. int main() { /* # OUTPUT # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi */ std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } // Dosyadaki yazıları 'byte' nezdinde okuyacağız. char c{}; while(ifs.get(c)) { std::cout.put(c); } return 0; } * Örnek 11, 'source.txt' dosyasındaki satır sayısı ikiye indirilmiştir. //.. int main() { /* # OUTPUT # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi */ std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } // Dosyadaki yazıları 'byte' nezdinde okuyacağız. int c{}; while( (c = ifs.get()) != EOF) { std::cout.put(c); } return 0; } * Örnek 12, //.. int main(int argc, char** argv) { /* # OUTPUT # */ // Komut satırından girilen kelimelerin adedi 3 olmalı. if(argc != 3) { std::cerr << "usage : \n"; return 1; } std::ifstream ifs{ "source.txt", std::ios::binary }; if(!ifs) { std::cerr << argv[1] << " dosyasi acilamadi.\n"; return 2; } std::ofstream ofs{ "dest.txt", std::ios::binary }; if(ofs.fail()) { std::cerr << argv[3] << " dosyasi olusturulamadi.\n"; return 3; } size_t byteCounter{}; char c{}; while(ifs.get(c)) { ofs.put(c); ++byteCounter; } std::cout << "Toplamda " << byteCounter << "byte kopyalanmisitr.\n"; return 0; } * Örnek 13, //.. int main(int argc, char** argv) { /* # OUTPUT # C:\Users\ahmet>cd C:\Users\ahmet\source\repos\OutputFiles C:\Users\ahmet\source\repos\OutputFiles>rename ConsoleApplication2.exe kopyala.exe C:\Users\ahmet\source\repos\OutputFiles>kopyala source.exe dest.exe Toplamda 55296byte kopyalanmisitr. */ if (argc != 3) { std::cerr << "usage : \n"; return 1; } std::ifstream ifs{ "source.exe", std::ios::binary }; if (!ifs) { std::cerr << argv[1] << " dosyasi acilamadi.\n"; return 2; } std::ofstream ofs{ "dest.exe", std::ios::binary }; if (ofs.fail()) { std::cerr << argv[3] << " dosyasi olusturulamadi.\n"; return 3; } size_t byteCounter{}; char c{}; while (ifs.get(c)) { ofs.put(c); ++byteCounter; } std::cout << "Toplamda " << byteCounter << "byte kopyalanmisitr.\n"; return 0; } * Örnek 14, //.. int main(int argc, char** argv) { /* # OUTPUT # C:\Users\ahmet\Desktop\Separator>main copy.exe 1000 88026 byte buyuklugunda copy.exe dosyasi 1000 byte buyuklugunda 89 adet dosyaya bolunmustur. */ // Komut satirina yazılacak argümanların adedi üç olmalı. if(argc != 3) { std::cerr << "usage : .exe \n"; return 1; } std::ifstream ifs{ argv[1], std::ios::binary }; // Dosyayi 'binary' modda ve okuma amacıyla açıyoruz. if(ifs.fail())// Dosya açilamaz ise program sonlanacaktır. { std::cerr << argv[1] << " isimli dosya acilamadi.\n"; return 2; } char c{}; // 'byte' kısım haline okuma yaparken kullanılacak değişken. int byteCount{}, fileCount{}, chunkAmount{ std::atoi(argv[2]) }; // Sırasıyla okunan 'byte' adedi, oluşturulan dosyaların adedi ve // her bir dosyanın olması gereken büyüklük bilgisi. std::ofstream ofs; // Yazma amacı için kullanılacak sınıf türünden değişken. while(ifs.get(c)) { // İlgili sınıfımız herhangi bir dosya ile ilişkilendirilmemişse, // programın akışı içeriye girecektir. if(!ofs.is_open()) { // Dosya ismi oluşturmak için bellek kullanıyoruz. std::ostringstream oss; oss << std::setfill('0'); // Dosya ismini oluşturduk. oss << "part_" << std::setw(3) << fileCount << ".par"; // İlgili sınıf nesnemiz ile dosyayı ilişkilendirmiş olduk. ofs.open(oss.str(), std::ios::binary); // Dosya açılmaz ise program sonlanacaktır. if(!ofs) { std::cerr << oss.str() << " isimli dosya olusturulamadi.\n"; return 3; } // Programın akışı buraya geldiğinde dosya açılmış demektir ve sayacı bir arttırıyoruz. ++fileCount; } // Okumuş olduğumuz ilk 'byte' değerini 'c' değişkeni üzerinden hedef dosyaya yazıyoruz. ofs.put(c); ++byteCount; // Kaç adet 'byte' yazıldığı bilgisi, hedef adede ulaşırsa ilgili dosya kapatılacaktır. if(byteCount % chunkAmount == 0) { ofs.close(); } } std::cout << byteCount << " byte buyuklugunda " << argv[1] << " dosyasi " << chunkAmount << " byte buyuklugunda " << fileCount << " adet dosyaya bolunmustur.\n"; return 0; } * Örnek 15, //.. int main(int argc, char** argv) { /* # OUTPUT # C:\Users\ahmet\Desktop\Separator>main.exe combinedFiles.exe 89 adet dosya, 88026 byte buyuklugunda combinedFiles.exe dosyasi olarak birlestirildi. */ // Komut satirina yazılacak argümanların adedi üç olmalı. if(argc != 2) { std::cerr << "usage : .exe .exe\n"; return 1; } // Birleşim sonucu meydana gelecek dosya için kullanılacaktır. std::ofstream ofs{ argv[1], std::ios::binary }; if(!ofs) { std::cerr << argv[1] << " dosyasi olusturulamadi...\n"; return 2; } int fileCount{}, byteCount{}; char c{}; for(;;) { std::ostringstream oss; oss << "part_" << std::setfill('0') << std::setw(3) << fileCount << ".par"; // Tekil her bir dosyanın okunması için kullanılacaktır. std::ifstream ifs{oss.str(), std::ios::binary}; if(!ifs) { break; // Tekil dosyalardan biri açılamadığında döngüden çıkacaktır. } ++fileCount; // Programın akışı buraya gelmişse tekil dosya açılmış demektir. while(ifs.get(c)) { ofs.put(c); ++byteCount; } // Programın akışı buraya gelmişse dosyada okunacak 'byte' kalmadığı içindir. // Dolayısıyla 'EOF' bayrağı 'set' edilmiştir. Fakat her seferinde sınıf nesnemiz tekrar hayata // geldiğinden, bayrakları sıfırlamamıza gerek yoktur. Fakat devamında aynı // sınıf nesnemiz ile okuma yapacaksak '.clear()' üye fonksiyonunu çağırmamız gerekiyor. // Tekli dosyaları silebilmek için öncelikle o dosyayı kapatmamız gerekiyor. ifs.close(); // 'remove' fonksiyonu C dilinden gelmekte olup, 'cstdlib' başlık dosyasında bildirilmiştir. // Dosya silmek için de kullanılır. if(std::remove(oss.str().c_str())) { std::cerr << oss.str() << " isimli dosya silinemedi.\n"; return 3; } } std::cout << fileCount << " adet dosya, " << byteCount << " byte buyuklugunda " << argv[1] << " dosyasi olarak birlestirildi.\n"; return 0; } * Örnek 16, //.. int main(int argc, char** argv) { /* # primeNumber.dat #          % ) + / 5 ; = C G I O S Y a e g k m q  ƒ ‰ ‹ • —  £ § ­ ³ µ ¿ Á Å Ç Ó ß ã å é ï ñ û ā ć č ď ĕ ę ě ĥ ij ķ Ĺ Ľ ŋ ő ś ŝ š ŧ ů ŵ Ż ſ ƅ ƍ Ƒ ƙ ƣ ƥ Ư Ʊ Ʒ ƻ ǁ lj Ǎ Ǐ Ǔ ǟ ǧ ǫ dz Ƿ ǽ ȉ ȋ ȝ */ // Formatsız yazma yapacağımız için 'binary' modda açmamız önemlidir. std::ofstream ofs{ "primeNumber.dat", std::ios::binary }; if(!ofs) { std::cerr << "dosya olusturulamadi...\n"; return 1; } int primeCounter{}, primeNumber{2}; while (primeCounter < 100) { if(isprime(primeNumber)) { ofs.write(reinterpret_cast(&primeNumber), sizeof(int)); ++primeCounter; } ++primeNumber; } return 0; } * Örnek 17, //.. int main(int argc, char** argv) { /* # OUTPUT # 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19 | 23 | 29 | 31 | 37 | 41 | 43 | 47 | 53 | 59 | 61 | 67 | 71 | 73 | 79 | 83 | 89 | 97 | 101 | 103 | 107 | 109 | 113 | 127 | 131 | 137 | 139 | 149 | 151 | 157 | 163 | 167 | 173 | 179 | 181 | 191 | 193 | 197 | 199 | 211 | 223 | 227 | 229 | 233 | 239 | 241 | 251 | 257 | 263 | 269 | 271 | 277 | 281 | 283 | 293 | 307 | 311 | 313 | 317 | 331 | 337 | 347 | 349 | 353 | 359 | 367 | 373 | 379 | 383 | 389 | 397 | 401 | 409 | 419 | 421 | 431 | 433 | 439 | 443 | 449 | 457 | 461 | 463 | 467 | 479 | 487 | 491 | 499 | 503 | 509 | 521 | 523 | 541 | */ // Formatsız yazma yapacağımız için 'binary' modda açmamız önemlidir. std::ifstream ifs{ "primeNumber.dat", std::ios::binary }; if(!ifs) { std::cerr << "dosya olusturulamadi...\n"; return 1; } int number{}; while(ifs.read(reinterpret_cast(&number), sizeof(int))) { std::cout << number << " | "; } return 0; } * Örnek 18, formatsız okuma yapıp, formatlı yazma işlemi. //.. int main(int argc, char** argv) { /* # OUTPUT # 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 */ // Formatsız yazma yapacağımız için 'binary' modda açmamız önemlidir. std::ifstream ifs{ "primeNumber.dat", std::ios::binary }; if(!ifs) { std::cerr << "dosya acilamadi...\n"; return 1; } // Dosyaya yazma işlemi formatlı yapılacağından, 'binary' modda açmadık. std::ofstream ofs{ "primeNumber.txt" }; if(!ofs) { std::cerr << "dosya olusturulamadi...\n"; return 2; } int primeCounter{}; int number{}; ofs << std::left; while(ifs.read(reinterpret_cast(&number), sizeof(int))) { if(primeCounter && primeCounter % 5 == 0) ofs << "\n"; ofs << std::setw(16) << number; ++primeCounter; } return 0; } * Örnek 19, //.. int main(int argc, char** argv) { /* # OUTPUT # [100] 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 */ // Formatsız yazma yapacağımız için 'binary' modda açmamız önemlidir. std::ifstream ifs{ "primeNumber.txt"}; if(!ifs) { std::cerr << "dosya acilamadi...\n"; return 1; } std::vector iVec{ std::istream_iterator{ifs}, std::istream_iterator{} }; std::cout << "[" << iVec.size() << "]\n"; std::copy(iVec.begin(), iVec.end(), std::ostream_iterator{std::cout, "\n"}); return 0; } * Örnek 20, //.. int main(int argc, char** argv) { /* # OUTPUT # [25] => 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 --------------------------------------------------- [25] => 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 --------------------------------------------------- [25] => 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 --------------------------------------------------- [25] => 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 --------------------------------------------------- */ // Formatsız yazma yapacağımız için 'binary' modda açmamız önemlidir. std::ifstream ifs{ "primeNumber.dat"}; if(!ifs) { std::cerr << "dosya acilamadi...\n"; return 1; } constexpr int SIZE{25}; int a[SIZE]; while (ifs.read(reinterpret_cast(a), SIZE * sizeof(int))) { // '.gcount()' fonksiyonu, okunan 'byte' büyüklüğünü yazdırmaktadır. auto n{ ifs.gcount() / sizeof(int) }; std::cout << "[" << n << "] => "; std::copy(a, a + n, std::ostream_iterator{std::cout, " "}); std::cout << "\n---------------------------------------------------\n"; } return 0; } * Örnek 21, //.. std::string get_text_from_file(const std::string& fileName) { std::ifstream ifs{ fileName }; if(!ifs) { throw std::runtime_error{ fileName + " dosyasi acilamadi."}; } std::ostringstream oss; oss << ifs.rdbuf(); // Dosyanın 'buffer' bölgesindekileri belleğe aktarıyoruz. return oss.str(); } int main(int argc, char** argv) { /* # OUTPUT # #include #include #include #include #include #include #include #include std::string get_text_from_file(const std::string& fileName) { std::ifstream ifs{ fileName }; if(!ifs) { throw std::runtime_error{ fileName + " dosyasi acilamadi."}; } std::ostringstream oss; oss << ifs.rdbuf(); // Dosyanın 'buffer' bölgesindekileri belleğe aktarıyoruz. return oss.str(); } int main(int argc, char** argv) { // // # OUTPUT # // auto myText{get_text_from_file("main.cpp")}; std::cout << myText; return 0; } */ auto myText{get_text_from_file("main.cpp")}; std::cout << myText; return 0; } /*============================================================================================================*/ (37_23_01_2021) > 'Move Semantics' ve 'Perfect Forwarding' Hatırlatması: Aşağıdaki kodu inceleyelim: * Örnek 1, //.. class Myclass{}; // ii. Artık 'other' demek argüman olarak geçilen geçici nesne demektir. void func(Myclass&& other) { Myclass otherTwo{ other }; // iii. BURADA DA HERHANGİ BİR TAŞIMA SÖZ KONUSU DEĞİLDİR. Çünkü 'other' isimli nesnenin // 'value-type' bilgisi 'Myclass&&' şeklinde fakat 'value-category' bilgisi 'L-Value'. // ÇÜNKÜ İSİMLENDİRİLMİŞ DEĞİŞKENLER 'L-Value' değer kategorisindedir. İşbu sebepten dolayı // 'Copy Ctor.' fonksiyonu ÇAĞRILACAKTIR. Myclass otherThree{ std::move(other) }; // iv. Artık burada 'value-category' bilgisi 'R-Value' haline dönüştürüldüğü için // 'Move Ctor.' fonksiyonu çağrılacaktır. TAŞIMA İŞLEMİ, kaynakların çalınması // işte bu 'Move Ctor.' fonksiyon bloğundaki kodlar vesilesiyle gerçekleşecektir. } int main() { func(Myclass{}); // i. BURADA HERHANGİ BİR TAŞIMA SÖZ KONUSU DEĞİLDİR. Sadece geçici nesne fonksiyona argüman // olarak geçildiğinden, 'life-extension' SÖZ KONUSUDUR. } * Örnek 2, //.. class Myclass{}; void foo(const Myclass& other) {} void foo(Myclass&& other) {} // i. Buradaki 'other' isimli değişken 'R-Value Reference' değil, // 'Universal Reference' / 'Forwarding Reference' şeklinde. template void func(T&& other) { // ii. Bu işlevin amacı almış olduğu argümanların 'const' bilgisini, 'value-category' bilgisini koruyarak // başka fonksiyon/fonksiyonları çağırmaktır. Dolayısıyla o başka fonksiyonları 'main()' içerisinden bizlerin // direkt olarak çağırması ile işbu 'func()' üzerinden çağırtması arasında bir fark olmaması gerekmektedir. // iii. Eğer bu işlev 'L-Value Expression' ile çağrılırsa, // 'T' türü 'Myclass&' olacaktır. // 't' türü ise 'Myclass&' olacaktır. // iv. Eğer bu işlev 'R-Value Expression' ile çağrılırsa, // 'T' türü 'Myclass' olacaktır. // 't' türü ise 'Myclass&&' olacaktır. foo(std::forward(t)); // v. 'std::forward' fonksiyonuna çağrı yapıp, geri dönüş değerini de çağıracağımız esas // fonksiyona argüman yapmamız gerekmektedir. } * Örnek 3.1, //.. class Myclass{}; void foo(Myclass& other) { std::cout << "Myclass&\n"; } // I void foo(Myclass&& other) { std::cout << "Myclass&&\n"; } // II void foo(const Myclass& other) { std::cout << "const Myclass&\n"; } // III int main() { /* # OUTPUT # Myclass& const Myclass& Myclass&& */ Myclass mx{}; foo(mx); // Function Overload Resolution kuralları gereği; // 'II' numaralı fonksiyon 'viable' DEĞİL. Dolayısıyla // 'I' ve 'III' numaralı fonksiyonlar 'viable'. 'const-overload' // kuralları gereği 'I' numaralı çağrılacaktır. const Myclass c_mx{}; foo(c_mx); // Function Overload Resolution kuralları gereği; // 'II' numaralı fonksiyon 'viable' DEĞİL. Dolayısıyla // 'I' ve 'III' numaralı fonksiyonlar 'viable'. 'const-overload' // kuralları gereği 'III' numaralı çağrılacaktır. foo(Myclass{}); // Function Overload Resolution kuralları gereği; // 'I' numaralı fonksiyon 'viable' DEĞİL. Dolayısıyla // 'II' ve 'III' numaralı fonksiyonlar 'viable'. Dilin kuralları gereği // 'II' numaralı çağrılacaktır. return 0; } * Örnek 3.2, //.. class Myclass{}; void foo(Myclass& other) { std::cout << "Myclass&\n"; } // I void foo(Myclass&& other) { std::cout << "Myclass&&\n"; } // II void foo(const Myclass& other) { std::cout << "const Myclass&\n"; } // III template void func(T&& other) { foo( std::forward(other) ); } int main() { /* # OUTPUT # Myclass& const Myclass& Myclass&& */ Myclass mx{}; func(mx); const Myclass c_mx{}; func(c_mx); func(Myclass{}); return 0; } * Örnek 4, //.. class Myclass{ public: Myclass(int a, int b, int c) : m_a{a}, m_b{b}, m_c{c} {} friend std::ostream& operator<<(std::ostream& os, const Myclass& other) { os << "[" << other.m_a << " " << other.m_b << " " << other.m_c << "]"; return os; } private: int m_a{}, m_b{}, m_c{}; }; template void foo(char c = 's', Args&&...args) { if(c == 's') { T x{ std::forward(args)... }; // 'T' sınıfının 'Ctor.' fonksiyonuna parametre paketindeki değerler // geçilmiş oldu. std::cout << "Statically Allocated Units => " << x << "\n"; } else if(c == 'd') { T* ptr = new T{ std::forward(args)... }; // 'T' sınıfının 'Ctor.' fonksiyonuna parametre paketindeki değerler // geçilmiş oldu. std::cout << "Dynamically Allocated Units => " << *ptr << "\n"; } else { std::cerr << "Please enter 's' or 'd' to create an unit Statically or Dynamically, respectively...\n"; } } int main() { /* # OUTPUT # Statically Allocated Units => [1 2 3] Dynamically Allocated Units => [11 22 33] */ foo('s', 1, 2, 3); foo('d', 11, 22, 33); return 0; } >> 'STL' içerisinde de 'Move Semantics' / 'Copy Semantics' mekanizmalarını kullanan fonksiyonlardan bir tanesi vektör sınıfının '.push_back()' fonksiyonuyken, 'Perfect Forwarding' mekanizmasını kullanan fonksiyon ise '.emplace_back()' fonksiyonudur. > 'STL' içerisindeki kaplar (devam) : >> Dosya İşlemleri (devam) : Dosya konum göstericisinin konumunu ayarlayan fonksiyonlar sırasıyla '.seekg()' ki bunu okuma amacıyla yaptığımız işlemlerde kullanabiliriz, '.seekp()' ki bunu da yazma amacıyla yaptığımız işlemlerde kullanabiliriz, fonksiyonlarıdır. Dosya konum göstericisinin konum bilgisini döndüren fonksiyonlar ise '.tellg()' ve '.tellp()' fonksiyonlarıdır. Yine sırasıyla okuma ve yazma işlemleri için kullanılırlar bu fonksiyonlar. Bu dört fonksiyonun sadece dosya işlemleri için değil, belleğe yazarken de kullanılabilir olduğunu unutmayalım. >>> C tekrarı: C dilinde dosya konum göstericisini 'set' eden 'fseek()' fonksiyonu şu üç parametre yapısına sahiptir. İlk parametre 'FILE*' türünden bir değişken, ikinci parametre yazılacak byte-bloğu adedi ve üçüncü parametre ise dosya konum göstericisinin hangi konumdan başlatılacağı bilgisi. * Örnek 1, //.. int main() { //.. // 'f' isimli değişkenin 'FILE*' türden bir değişken olduğunu varsayarsak; fseek(f, 5, SEEK_SET); // Dosya konum göstericisi dosyanın başına konumlandırıldı ve bu konumdan itibaren // beş byte blokluk ötelendi. İkinci parametre 'long' türden ve sıfır ile pozitif bir değer // aralığında olmalıdır. fseek(f, -5, SEEK_CUR); // Dosya konum göstericisinin aktüel konumu orjin olarak alınacaktır ve bu konumdan itibaren // beş byte blokluk geriye doğru ötelendi. İkinci parametre 'long' türden ve negatif ile pozitif bir değer // aralığında olabilir. fseek(f, -5, SEEK_END); // Dosya konum göstericisi dosyanın sonuna konumlandırıldı ve bu konumdan itibaren // beş byte blokluk geriye doğru ötelendi. İkinci parametre 'long' türden ve sıfır ile negatif bir değer // aralığında olmalıdır. } >>> C dilindeki 'fseek()' fonksiyonuna geçilen maskeler gibi C++ dilindeki '.seekg()' fonksiyonunun bir diğer 'overlaod' versiyonuna da benzer amaçlı maskeler geçiyoruz. Bu maskeler 'std::ios::beg', 'std::ios::cur' ve 'std::ios::end' maskeleridir. * Örnek 1, //.. int main() { //.. std::istringstream iss; iss.seekg(0, std::ios::beg); // Dosya konum göstericisi dosyanın başına alındı. } * Örnek 2, //.. int main() { //.. std::istringstream iss; iss.seekg(0, std::ios::end); // Dosya konum göstericisi dosyanın sonuna alındı. } * Örnek 3, //.. int main() { //.. std::istringstream iss; iss.seekg(100, std::ios::cur); // Dosya konum göstericisi 100 birim ileriye doğru ötelendi. iss.seekg(-75, std::ios::cur); // Dosya konum göstericisi 75 birim geriye doğru ötelendi. } * Örnek 4, Dosyanın boyutunu hesaplama: //.. int main() { /* # OUTPUT # 108 108 */ // Cpp-Style std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } ifs.seekg(0, std::ios::end); // Dosya konum göstericisi, dosyanın sonuna alındı. std::cout << ifs.tellg() << "\n"; // C-Style FILE* filePtr = fopen("source.txt", "r"); if(!filePtr) { std::cerr << "Dosya acilamadi...\n"; return 2; } fseek(filePtr, 0, SEEK_END); // Dosya konum göstericisi, dosyanın sonuna alındı. std::cout << ftell(filePtr) << "\n"; return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # 0 27 54 81 108 */ // Cpp-Style std::ifstream ifs{ "source.txt" }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } // Dosya konum göstericisi, dosyanın başına alındı. ifs.seekg(0, std::ios::beg); std::cout << ifs.tellg() << "\n"; // Dosya konum göstericisi, en sonki konumdan itibaren 27-byte blok ileriye doğru ötelendi. ifs.seekg(27, std::ios::cur); std::cout << ifs.tellg() << "\n"; // Dosya konum göstericisi, en sonki konumdan itibaren 27-byte blok ileriye doğru ötelendi. ifs.seekg(27, std::ios::cur); std::cout << ifs.tellg() << "\n"; // Dosya konum göstericisi, en sonki konumdan itibaren 27-byte blok ileriye doğru ötelendi. ifs.seekg(27, std::ios::cur); std::cout << ifs.tellg() << "\n"; // Dosya konum göstericisi, en sonki konumdan itibaren 27-byte blok ileriye doğru ötelendi. ifs.seekg(27, std::ios::cur); std::cout << ifs.tellg() << "\n"; return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # The character => [s] The character => [a] The character => [d] The character => [e] The character => [t] The character => [t] The character => [i] The character => [n] The character => [ ] The character => [o] The character => [t] The character => [a] The character => [c] The character => [i] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [-] The character => [ ] The character => [b] The character => [u] The character => [r] The character => [s] The character => [a] The character => [,] The character => [ ] The character => [2] The character => [7] The character => [ ] The character => [M] The character => [a] The character => [y] The character => [i] The character => [s] The character => [ ] The character => [1] The character => [9] The character => [5] The character => [0] The character => [ ] The character => [C] The character => [u] The character => [m] The character => [a] The character => [r] The character => [t] The character => [e] The character => [s] The character => [i] The character => [ ] The character => [u] The character => [f] The character => [u] The character => [k] The character => [ ] The character => [a] The character => [l] The character => [t] The character => [i] The character => [n] The character => [i] The character => [s] The character => [i] The character => [k] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [ ] The character => [-] The character => [ ] The character => [b] The character => [a] The character => [t] The character => [m] The character => [a] The character => [n] The character => [,] The character => [ ] The character => [0] The character => [5] The character => [ ] The character => [O] The character => [c] The character => [a] The character => [k] The character => [ ] The character => [1] The character => [9] The character => [7] The character => [4] The character => [ ] The character => [C] The character => [u] The character => [m] The character => [a] The character => [r] The character => [t] The character => [e] The character => [s] The character => [i] The character => [i] */ // Cpp-Style std::ifstream ifs{ "source.txt", std::ios::binary }; if(!ifs) { std::cerr << "Dosya acilamadi...\n"; return 1; } char index{}; char byte{}; // Reading the first character: ifs.seekg( index * sizeof(char), std::ios::beg ); // Konum göstericisi dosyanın başına alındı. ifs.read((&byte), sizeof(char)); // O konumdaki 'byte' bilgisi 'byte' isimli değişkene atandı. std::cout << "The character => [" << byte << "]\n"; // Reading the second character: ifs.seekg( (index + 1) * sizeof(char), std::ios::beg); // Konum göstericisi dosyanın başına alındı ve ileriye doğru bir birim ötelendi. ifs.read(&byte, sizeof(char)); // O konumdaki 'byte' bilgisi 'byte' isimli değişkene atandı. std::cout << "The character => [" << byte << "]\n"; // Reading the third character: ifs.seekg( (index) * sizeof(char), std::ios::cur); // Konum göstericisi aktuel konumu orjin olarak alındı. ifs.read(&byte, sizeof(char)); // O konumdaki 'byte' bilgisi 'byte' isimli değişkene atandı. std::cout << "The character => [" << byte << "]\n"; // Reading the rest of the characters: for(;;) { ifs.seekg( (index) * sizeof(char), std::ios::cur); // Konum göstericisi aktuel konumu orjin olarak alındı. if(ifs) { ifs.read(&byte, sizeof(char)); // O konumdaki 'byte' bilgisi 'byte' isimli değişkene atandı. std::cout << "The character => [" << byte << "]\n"; } else { return 1; } } return 0; } * Örnek 7, //.. int main() { std::istringstream iss{ "Ahmet Kandemir Pehlivanli" }; std::string str{}; iss >> str; // İlk boşluk karakterine kadarki karakterler 'str' değişkenine kopyalandı. // Dosya konum göstericisi artık 'Ahmet' ile 'Kandemir' kelimeleri arasındaki boşluk karakterini göstermektedir. std::cout << "[" << str << "]\n"; // OUTPUT => Ahmet iss >> str; // İkinci boşluk karakterine kadarki karakterler 'str' değişkenine kopyalandı. // Dosya konum göstericisi artık 'Kandemir' ile 'Pehlivanli' kelimeleri arasındaki boşluk karakterini göstermektedir. std::cout << "[" << str << "]\n"; // OUTPUT => Kandemir iss >> str; // Üçüncü boşluk karakterine kadarki karakterler 'str' değişkenine kopyalandı. // Dosya konum göstericisi artık dosyanın sonunu göstermektedir. std::cout << "[" << str << "]\n"; // OUTPUT => Pehlivanli iss.seekg(0, std::ios_base::beg); // std::ios::base // Dosya konum göstericisi tekrardan dosyanın başına alındı. iss >> str; std::cout << "[" << str << "]\n"; // OUTPUT => Ahmet iss.seekg(1, std::ios_base::cur); // Dosya konum göstericisinin aktuel konumu orjin olarak ele alındı. iss >> str; std::cout << "[" << str << "]\n"; // OUTPUT => Kandemir iss.seekg(1, std::ios_base::cur); // Dosya konum göstericisinin aktuel konumu orjin olarak ele alındı. iss >> str; std::cout << "[" << str << "]\n"; // OUTPUT => Pehlivanli iss.seekg(-19, std::ios_base::cur); // Dosya konum göstericisinin aktuel konumu orjin olarak ele alındı. iss >> str; std::cout << "[" << str << "]\n"; // OUTPUT => Kandemir iss.seekg(-14, std::ios_base::cur); // Dosya konum göstericisinin aktuel konumu orjin olarak ele alındı ve // ilgili rakam adedince geriye doğru ötelendi. iss >> str; std::cout << "[" << str << "]\n"; // OUTPUT => Ahmet iss.seekg(-25, std::ios_base::end); // Dosya konum göstericisi dosyanın sonundan itibaren geriye doğru ötelendi.. iss >> str; std::cout << "[" << str << "]\n"; // OUTPUT => Kandemir return 0; } * Örnek 8, //.. void print_file_n_times(const std::string& fileName, int counter) { std::ifstream ifs{ fileName }; if(!ifs) { std::cerr << "dosya acilamadi...\n"; std::exit(EXIT_FAILURE); } while(--counter) { std::cout << ifs.rdbuf(); std::cout << "\n-----------------------------\n"; ifs.seekg(0); // Dosya konum göstericisi tekrardan en başa alındı. } } int main() { /* # OUTPUT # sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi ----------------------------- sadettin otaci - bursa, 27 Mayis 1950 Cumartesi ufuk altinisik - batman, 05 Ocak 1974 Cumartesi ----------------------------- */ print_file_n_times("source.txt", 3); return 0; } > 'TypeTraits' : Türler hakkında derleme zamanında bilgilendirme amaçlı kullanılan araçlar olup jenerik programlama tarafında kod yazılırken kullanılır. Bu işin temelindeyse 'integral_constant' türü yatmaktadır. >> 'STL' içerisinde bulunan 'integral_constant' sınıfının temsili implementasyonu: * Örnek 1, //.. template< typename T, T v> // 'T' hangi türe karşılık geliyorsa, 'v' de o türden bir değişken. struct IntegralConstant{ // 'struct' kullanıldığına dikkat çekelim. static constexpr T value = v; // 'value' isimli değişkenimizin türü 'T', değeri ise 'v'. using value_type = T; // 'T' hangi türe karşılık geliyorsa, 'value_type' ise o türün eş ismi. //... }; int main() { constexpr auto val{ IntegralConstant::value }; std::cout << "val : " << val << "\n"; // OUTPUT => val : 5 IntegralConstant::value_type valTwo{ 'a' }; std::cout << "valTwo : " << valTwo << "\n"; // OUTPUT => valTwo : a IntegralConstant::value_type valThree{ false }; std::cout << "valThree : " << valThree << "\n"; // OUTPUT => valThree : 0 return 0; } * Örnek 2, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; // 'type', sınıfımız neyin açılımı ise, onun tür eş ismi. //... }; int main() { IntegralConstant::type::value_type val{ 31 }; // 'type', 'IntegralConstant' açılımının tür eş ismidir. std::cout << "val : " << val << "\n"; // OUTPUT => val : 31 return 0; } * Örnek 3, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; constexpr operator value_type() const noexcept { return value; } // Tür dönüştürme operatör fonksiyonudur. Sınıf türünden 'value' isimli nesnemiz, //'T' türden değişkene dönüştürülmektedir. //... }; int main() { constexpr auto val{ IntegralConstant{} }; int a[val]{ 1, 2, 3, 4, 5 }; std::copy(a, a + 5, std::ostream_iterator{std::cout, " "}); // OUTPUT => 1 2 3 4 5 return 0; } * Örnek 4, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; constexpr operator value_type() const noexcept { return value; } //... }; using TrueType = IntegralConstant; using FalseType = IntegralConstant; struct Ahmo : TrueType{ // 'Ahmo' sınıfı 'IntegralConstant' sınıfının 'bool, true' açılımından 'public' kalıtım yoluyla türetilmiştir. //.. Taban sınıfın bütün 'public-interface' üyeleri artık bizim sınıfa katılmış oldu. }; int main() { bool val{ Ahmo::value }; // 'Ahmo::value', taban sınıftan gelmekte. Taban sınıfımız ise 'IntegralConstant' sınıfının 'true' açılımı. // Dolayısıyla 'val' değişkenimizin türü 'bool', değeri de 'true'. std::cout << "val : " << std::boolalpha << val << "\n"; // OUTPUT => val : true return 0; } >> Derleme zamanında bir türün 'pointer' türü olup olmadığının test edilmesi: * Örnek 1, //.. template struct IsPointer : std::false_type{}; // I : 'primary' tür, 'false_type' dan türetildi. template struct IsPointer : std::true_type{}; // II : Özelleştirme kullanarak ('explicit specialization' kullanıldı), 'true_type' lar için türetildi. // 'Template Specialization' kullanıldığı için, // 'T' türü gösterici ise 'II' numaralı şablon açılacaktır. // 'T' türü gösterici değil ise 'I' numaralı şablon açılacaktır. // Dolayısıyla taban sınıftan gelen 'value' isimli değişkeni kullanarak, // şablon parametresinin 'pointer' olup olmadığını sınayabiliriz. int main() { /* # OUTPUT # Evet, bir gösterici.. */ constexpr auto isPtr{ IsPointer::value }; // 'value' değişkeninin türü 'bool' ve değeri 'true' olacaktır, // yukarıdaki özelleştirmeden dolayı. if(isPtr) std::cout << "Evet, bir gösterici...\n"; else std::cerr << "Hayır, bir gösterici değil...\n"; return 0; } * Örnek 2, //.. // Sadece 'pointer' bir değişken gönderildiğinde işlev gören bir fonksiyon yazalım. template void func(T x) { // Parantez içerisindeki ifade 'false' olduğunda ilgili yazı ekrana basılacaktır. static_assert(std::is_pointer::value, "T turu bir pointer olmalidir."); std::cout << "The value at the address of [" << x << "] => " << *x << "\n"; } int main() { /* # OUTPUT # The value at the address of [0x7ffefb698cf4] => 31 */ int a{31}; func(&a); // int b; func(b); // main.cpp:8:39: error: static assertion failed: T turu bir pointer olmalidir. return 0; } * Örnek 3, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; constexpr operator value_type() const noexcept { return value; } //... }; using TrueType = IntegralConstant; using FalseType = IntegralConstant; // Eğer aşağıdaki şablonu özelleştirmezsek, bütün türler için 'false' değer üretecek, // taban sınıftan gelen 'value' değişkeni. template struct IsPointer : FalseType{}; // İş bu özelleştirme ile 'pointer' tipler için 'value' değişkenimiz 'true' değer üretecektir. template struct IsPointer : TrueType{}; // Sadece 'pointer' bir değişken gönderildiğinde işlev gören bir fonksiyon yazalım. template void func(T x) { // Parantez içerisindeki ifade 'false' olduğunda ilgili yazı ekrana basılacaktır. static_assert(IsPointer::value, "T turu bir pointer olmalidir."); std::cout << "The value at the address of [" << x << "] => " << *x << "\n"; } int main() { /* # OUTPUT # The value at the address of [0x7ffefb698cf4] => 31 */ int a{31}; func(&a); // int b; func(b); // main.cpp:8:39: error: static assertion failed: T turu bir pointer olmalidir. return 0; } >> Derleme zamanında bir türün 'integral' tür olup olmadığının test edilmesi: * Örnek 1, //.. // Sadece 'integral-type' bir değişken gönderildiğinde işlev gören bir fonksiyon yazalım. template void func(T x) { // Parantez içerisindeki ifade 'false' olduğunda ilgili yazı ekrana basılacaktır. static_assert(std::is_integral::value, "T turu bir integal-type olmalidir."); std::cout << "The value at the address of [" << &x << "] => " << x << "\n"; } int main() { /* # OUTPUT # The value at the address of [0x7ffded53244c] => 31 */ int a{31}; func(a); // double b{31.13}; func(b); // error: static assertion failed: T turu bir integal-type olmalidir return 0; } >> C++17 ile birlikte taban sınıftan gelen 'value' değişkenini yazmak zahmetli olduğundan, bir kısaltma dile eklenmiştir. İşte onun temsili implementasyonu: * Örnek 1, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; constexpr operator value_type() const noexcept { return value; } //... }; using TrueType = IntegralConstant; using FalseType = IntegralConstant; template struct IsPointer : FalseType{}; template struct IsPointer : TrueType{}; template inline constexpr bool IsPointer_v = IsPointer::value; // C++17 int main() { /* # OUTPUT # Hayir, bir gösterici değil... */ bool isPtr {IsPointer_v}; // if(isPtr) std::cout << "Evet, bir gösterici...\n"; else std::cerr << "Hayir, bir gösterici değil...\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # true / false */ auto val{ std::is_pointer::value }; auto valTwo{ std::is_pointer_v }; std::cout << std::boolalpha << val << " / " << std::boolalpha << valTwo << "\n"; return 0; } >> Pekiştirici örnekler, * Örnek 1, //.. int main() { /* # OUTPUT # false / true / false */ auto val{ std::is_array::value }; auto valTwo{ std::is_array::value }; auto valThree{ std::is_array::value }; std::cout << std::boolalpha << val << " / " << std::boolalpha << valTwo << " / " << std::boolalpha << valThree << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # false / true */ auto val{ std::is_const::value }; auto valTwo{ std::is_const::value }; std::cout << std::boolalpha << val << " / " << std::boolalpha << valTwo << "\n"; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # false / true */ auto val{ std::is_reference::value }; auto valTwo{ std::is_reference::value }; std::cout << std::boolalpha << val << " / " << std::boolalpha << valTwo << "\n"; return 0; } * Örnek 4, //.. template void foo(T x, U y) { if( std::is_same_v ) { std::cout << "They're same...\n"; } else { std::cerr << "They're not same...\n"; } } int main() { /* # OUTPUT # They're not same... */ foo(2, 2.2); return 0; } * Örnek 5, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; constexpr operator value_type() const noexcept { return value; } //... }; using TrueType = IntegralConstant; using FalseType = IntegralConstant; // Eğer aşağıdaki şablonu özelleştirmezsek, bütün türler için 'false' değer üretecek, taban sınıftan gelen 'value' değişkeni. template struct IsFloatingPoint : FalseType{}; // 'explicit specialization' kullanarak belli türleri kalıtım yoluyla elde ediyoruz; template<> struct IsFloatingPoint : TrueType{}; // 'float' için özelleştirme yaptığımız için, artık 'float' türler için 'true' değer elde edilecek. template<> struct IsFloatingPoint : TrueType{}; // 'double' için de özelleştirme yaptığımız için, artık 'double' türler için de 'true' değer elde edilecek. template<> struct IsFloatingPoint : TrueType{}; // 'long double' için özelleştirme yaptığımız için, artık 'long double' türler için 'true' değer elde edilecek. // Yukarıdaki türler, 'const' versiyonlarından farklı olduklarından, onlar için de özelleştirme yapmamız gerekebilir. template<> struct IsFloatingPoint : TrueType{}; template<> struct IsFloatingPoint : TrueType{}; template<> struct IsFloatingPoint : TrueType{}; // Yukarıdaki 'non-const' türler, 'volatile' versiyonlarından farklı olduklarından, // onlar için de özelleştirme yapmamız gerekebilir. template<> struct IsFloatingPoint : TrueType{}; template<> struct IsFloatingPoint : TrueType{}; template<> struct IsFloatingPoint : TrueType{}; // Yukarıdaki 'non-const' türler, 'const-volatile' versiyonlarından farklı olduklarından, // onlar için de özelleştirme yapmamız gerekebilir. template<> struct IsFloatingPoint : TrueType{}; template<> struct IsFloatingPoint : TrueType{}; template<> struct IsFloatingPoint : TrueType{}; int main() { /* # OUTPUT # false true true true true true true true true true true true true */ std::cout << std::boolalpha; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; std::cout << IsFloatingPoint::value << "\n"; return 0; } * Örnek 6, //.. template struct RemoveCV { using type = T; }; // 'T' her ne türden ise 'type' onun eş anlamlısı. template struct RemoveCV { using type = T; }; // 'T' her ne türden ise 'type' onun eş anlamlısı. template struct RemoveCV { using type = T; }; // 'T' her ne türden ise 'type' onun eş anlamlısı. template struct RemoveCV { using type = T; }; // 'T' her ne türden ise 'type' onun eş anlamlısı. template using RemoveCV_T = typename RemoveCV::type; // 'RemoveCV' nin 'T' açılımındaki 'T' hangi tür ise, 'RemoveCV_T' de o türün eş anlamlısı. int main() { /* # OUTPUT # true true true false false false true true true */ std::cout << std::boolalpha; std::cout << std::is_same_v, int> << "\n"; std::cout << std::is_same_v , int> << "\n"; std::cout << std::is_same_v , int> << "\n"; std::cout << std::is_same_v, int*> << "\n"; std::cout << std::is_same_v , int*> << "\n"; std::cout << std::is_same_v , int*> << "\n"; std::cout << std::is_same_v, int*> << "\n"; std::cout << std::is_same_v , int*> << "\n"; std::cout << std::is_same_v , int*> << "\n"; return 0; } * Örnek 7, //.. template< typename T, T v> struct IntegralConstant{ static constexpr T value = v; using value_type = T; using type = IntegralConstant; constexpr operator value_type() const noexcept { return value; } //... }; template struct RemoveCV { using type = T; }; template struct RemoveCV { using type = T; }; template struct RemoveCV { using type = T; }; template struct RemoveCV { using type = T; }; template struct IsFloatingPoint : IntegralConstant< bool, std::is_same_v::type> || std::is_same_v::type> || std::is_same_v::type> > {}; template inline constexpr bool IsFloatingPoint_V = IsFloatingPoint::value; int main() { /* # OUTPUT # false true true true true true */ std::cout << std::boolalpha; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; return 0; } * Örnek 8, //.. template struct IsFloatingPoint : std::integral_constant< bool, // std::is_same_v> || std::is_same::type>::value || // std::is_same_v> || std::is_same::type>::value || // std::is_same_v> std::is_same::type>::value > {}; template inline constexpr bool IsFloatingPoint_V = IsFloatingPoint::value; int main() { /* # OUTPUT # false true true true true true .*/ std::cout << std::boolalpha; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; std::cout << IsFloatingPoint_V << "\n"; return 0; } * Örnek 9.1, //.. // Eğer aşağıdaki özelleştirmeler olmasaydı, bütün türler için 'false' değer döndürecekti. template struct IsIntegral : std::false_type{}; // Artık aşağıdaki türler için özelleştirme yapıldığından, sadece aşağıdaki türler için 'true' değer döndürecektir. template<> struct IsIntegral : std::true_type{}; template<> struct IsIntegral : std::true_type{}; template<> struct IsIntegral : std::true_type{}; template<> struct IsIntegral : std::true_type{}; template<> struct IsIntegral : std::true_type{}; template<> struct IsIntegral : std::true_type{}; template<> struct IsIntegral : std::true_type{}; template inline constexpr bool IsIntegral_V = IsIntegral::value; int main() { /* # OUTPUT # 1 / 0 */ std::cout << (IsIntegral::value) << " / " << (IsIntegral_V) << "\n"; return 0; } * Örnek 9.2, //.. template struct IsIntegralBase : std::false_type {}; template<> struct IsIntegralBase : std::true_type {}; template<> struct IsIntegralBase : std::true_type {}; template<> struct IsIntegralBase : std::true_type {}; template struct IsIntegral : IsIntegralBase> {}; int main() { /* # OUTPUT # 0 / 0 / 1 */ std::cout << (IsIntegral::value) << " / " << (IsIntegral::value) << " / " << (IsIntegral::value) << "\n"; return 0; } * Örnek 10, //.. template struct IsIntegral : std::integral_constant< bool, std::is_same_v> || std::is_same_v> || std::is_same_v> || std::is_same_v> > {}; template inline constexpr bool IsIntegral_V = IsIntegral::value; int main() { /* # OUTPUT # 0 / 0 / 1 */ std::cout << (IsIntegral::value) << " / " << (IsIntegral_V) << " / " << (IsIntegral_V) << "\n"; return 0; } * Örnek 11, //.. // 'volatile' özelliği eklemek için. template struct add_Volatile { using type = volatile T; }; template using add_V_T = typename add_Volatile::type; // 'const' özelliği eklemek için. template struct add_Const { using type = const T; }; template using add_C_T = typename add_Const::type; // 'const' ve 'volatile' özelliği eklemek için. template struct add_CV { using type = const volatile T; }; template using add_CV_T = typename add_CV::type; int main() { /* # OUTPUT # true true true */ std::cout << std::boolalpha; std::cout << std::is_same_v< add_CV_T, const volatile int > << "\n"; std::cout << std::is_same_v< add_C_T, const int > << "\n"; std::cout << std::is_same_v< add_V_T, volatile int > << "\n"; return 0; } * Örnek 12, //.. template void makeUnsigned(T x) { //typename std::make_unsigned::type other{ x }; std::make_unsigned_t other{ x }; std::cout << other << "\n"; } int main() { /* # OUTPUT # Value : -31 => 4294967265 Value : 31 => 31 */ // Signed -> Unsigned int a = -31; std::cout << "Value : " << a << " => "; makeUnsigned(a); // Unsigned -> Unsigned unsigned int b = 31; std::cout << "Value : " << b << " => "; makeUnsigned(b); return 0; } * Örnek 13, //.. enum Neco : unsigned char{ x, y, z }; int main() { std::underlying_type x; // 'x' is 'unsigned char' return 0; } * Örnek 14, //.. template struct IsSame : std::false_type{}; template struct IsSame : std::true_type{}; template inline constexpr bool IsSame_V{ IsSame::value }; int main() { /* # OUTPUT # Same!!! */ if( !IsSame_V ) { std::cerr << "Not same!!!\n"; // OUTPUT in 'std::err' => Not same!!! } if( IsSame_V ) { std::cout << "Same!!!\n"; // OUTPUT in 'std::out' => Same!!! } return 0; } * Örnek 15.1, Öyle bir 'func' işlevi olsun ki tam sayı türleri için bir kod bloğu, tam sayı olmayan türler için ayrı bir kod bloğu çalışsın. //.. 'template' tür parametresine bağlı türü kullanmak için 'typename' anahtar sözcüğünü yazmamız gerekmektedir. template void func_impl(T x, std::true_type) { std::cout << "Tam sayilar için kullanilacak kod bloğu...\n"; } template void func_impl(T x, std::false_type) { std::cout << "Diğer sayilar için kullanilacak kod bloğu...\n"; } template void func(T x) { func_impl(x, typename std::is_integral::type{}); // 'type' burada bir tür bilgisine denk gelmekte olup ya 'std::true_type' ya da 'std::false_type' türlerinden birisidir. // 'Function Overload Resolution' için geçici nesne oluşturduk. // 'typename' anahtar sözcüğünü kullanmak zorundayız çünkü bir tür bilgisi geçiyoruz. } int main() { /* # OUTPUT # Tam sayilar için kullanilacak kod bloğu... Diğer sayilar için kullanilacak kod bloğu... */ func(12); func(1.2f); return 0; } * Örnek 15.2, //.. template void func(T x) { if( std::is_integral_v ) { std::cout << "Tam sayilar için kullanilacak kod bloğu...\n"; } else { std::cout << "Diğer sayilar için kullanilacak kod bloğu...\n"; } } int main() { /* # OUTPUT # Tam sayilar için kullanilacak kod bloğu... Diğer sayilar için kullanilacak kod bloğu... */ func(12); func(1.2f); return 0; } > 'Compile-Time-IF' deyimi: C++17 ile dile eklenmiştir. 'if' anahtar sözcüğünden hemen sonra 'constexpr' anahtar sözcüğünü yazıyoruz. Dolayısıyla argüman olan ifadenin bir 'sabit-ifadesi' olması gerekmektedir. * Örnek 1, //.. template void func(T x) { if constexpr ( std::is_integral_v ) { std::cout << "Tam sayilar için kullanilacak kod bloğu...\n"; } else { std::cout << "Diğer sayilar için kullanilacak kod bloğu...\n"; } } int main() { /* # OUTPUT # Tam sayilar için kullanilacak kod bloğu... Diğer sayilar için kullanilacak kod bloğu... */ func(12); func(1.2f); return 0; } * Örnek 2, //.. template auto getValue(T t) { if constexpr ( std::is_pointer_v ) return *t; // Parantez içerisindeki ifade 'true' döndürürse bu kısım derleme aşamasına katılacak alınacak. else return t; // Parantez içerisindeki ifade 'false' döndürürse bu kısım derleme aşamasına KATILMAYACAK. } int main() { /* # OUTPUT # 87 87 4.5 4.5 */ int ival{ 87 }; int* iPtr{ &ival }; double dval{ 4.5 }; double* dPtr{ &dval }; std::cout << getValue(ival) << "\n"; std::cout << getValue(iPtr) << "\n"; std::cout << getValue(dval) << "\n"; std::cout << getValue(dPtr) << "\n"; return 0; } * Örnek 3, //.. template std::string as_string(T x) { // 'x' eğer 'std::string' türünden ise aşağıdaki deyimler derlemeye katılmayacak. if constexpr ( std::is_same_v ) return x; // 'x' eğer aritmatik türden ise aşağıdaki ve yukarıdaki deyimler derlemeye katılmayacak. else if constexpr ( std::is_arithmetic_v ) return std::to_string(x); // Eğer 'x' yukarıdaki şartlara haiz değil, başka bir türden ise sadece aşağıdaki deyim derlemeye katılacaktır. else return std::string{x}; } int main() { /* # OUTPUT # 42 4.200000 Hello World */ std::cout << as_string(42) << "\n"; std::cout << as_string(4.2) << "\n"; std::cout << as_string(std::string{"Hello"}) << "\n"; std::cout << as_string("World") << "\n"; return 0; } * Örnek 4, //.. template void func(T& tx) { // Bu 'if' bloğu her halükarda derlemeye katılacaktır. if( tx > 0 ) { // Eğer 'true' değer döndürülürse, bu 'if' bloğu derlemeye katılacaktır. if constexpr ( std::is_integral_v ) ++tx; // Eğer 'false' değer döndürülse bu 'else' bloğu derlemeye katılacaktır. else --tx; } } int main() { /* # OUTPUT # ival : 6 dval : 4.5 */ int ival{ 5 }; func(ival); double dval{ 5.5 }; func(dval); std::cout << "ival : " << ival << "\n"; std::cout << "dval : " << dval << "\n"; return 0; } * Örnek 5, HER ŞEYİN BAŞI İSİM ARAMADIR. //.. template void func(T x) { if constexpr ( true ) { ++x; } else { foo(); // there are no arguments to ‘foo’ that depend on a template parameter, // so a declaration of ‘foo’ must be available [-fpermissive] } } int main() { //.. return 0; } * Örnek 6, //.. constexpr auto func() { if constexpr ( sizeof(int) > 4u ) return 1; else return 0.5; } int main() { /* # OUTPUT # Value : 0.5 */ constexpr auto value { func() }; std::cout << "Value : " << value << "\n"; return 0; } * Örnek 7, GÜNÜN EN GÜZEL ÖRNEĞİ //.. // Using 'if-constexpr' template void advanceTwo(Iter& pos, Dist n) { using Cat = typename std::iterator_traits::iterator_category; if constexpr ( std::is_same_v ) { pos += n; } else if constexpr ( std::is_same_v ) { if(n > 0) { while(n--) ++pos; } else { while(n++) --pos; } } else if constexpr ( std::is_same_v ) { while(n--) ++pos; } else { std::cerr << "UNKNOWN IF-CLAUSE BLOCK\n"; } } // Tag-dispatch yaklaşımı: template void advanceOne(Iter& pos, Dist n) { using Cat = typename std::iterator_traits::iterator_category; advance_impl(pos, n, Cat{}); } // Implementation for random access iterators: template void advance_impl(Iter& pos, Dist n, std::random_access_iterator_tag) { pos += n; } // Implementation for bidirectional access iterators: template void advance_impl(Iter& pos, Dist n, std::bidirectional_access_iterator_tag) { if(n > 0) { while(n--) ++pos; } else { while(n++) --pos; } } // Implementation for input iterators: template void advance_impl(Iter& pos, Dist n, std::input_iterator_tag) { while(n--) ++pos; } int main() { /* # OUTPUT # */ //.. return 0; } * Örnek 8, Şablon haricinde kullanması durumunda bütün bloklar derlemeye alınır. //.. struct Neco{ }; int main() { /* # OUTPUT # */ Neco nec; if constexpr ( sizeof(int) > 1u ) ++nec; // error: no match for ‘operator++’ (operand type is ‘Neco’) else --nec; // error: no match for ‘operator--’ (operand type is ‘Neco’) return 0; } * Örnek 9, //.. struct Nec{ Nec( int i, double d, std::string s ) : ival{i}, dval{d}, str{s} {} int ival{}; double dval{}; std::string str{}; }; // 'forward-declaration' for 'primary-template' template auto& get(Nec& nec); // 'specialization' for the 'primary-template' template<> auto& get<0>(Nec& nec){ return nec.ival; } template<> auto& get<1>(Nec& nec){ return nec.dval; } template<> auto& get<2>(Nec& nec){ return nec.str; } // 'constexpr-if' kullanırsak template auto& Get(Nec& nec) { if constexpr ( n == 0 ) return nec.ival; else if constexpr ( n == 1 ) return nec.dval; else if constexpr (n == 2) return nec.str; else return "SOME UNKNOWN ERROR OCCURED!"; } Nec foo() { return { 12, 4.5, "HelloWorld" }; } int main() { /* # OUTPUT # 12 / 4.5 / HelloWorld 12 / 4.5 / HelloWorld 12 / 4.5 / HelloWorld */ auto [ i, d, name] = foo(); std::cout << i << " / " << d << " / " << name << "\n"; Nec nec{ foo() }; std::cout << get<0>(nec) << " / " << get<1>(nec) << " / " << get<2>(nec) << "\n"; std::cout << Get<0>(nec) << " / " << Get<1>(nec) << " / " << Get<2>(nec) << "\n"; return 0; } * Örnek 10, //.. // 'primary-template' template constexpr int fibonacci() { return fibonacci() + fibonacci(); } // '1' için özelleştirme template<> constexpr int fibonacci<1>() { return 1; } // '0' için özelleştirme template<> constexpr int fibonacci<0>() { return 0; } // 'constexpr-if' kullanarak template constexpr int fibo() { if constexpr ( N >= 2 ) return fibo() + fibo(); else return N; } int main() { /* # OUTPUT # 610 / 5 */ // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610 // x // y constexpr auto x{ fibonacci<15>() }; constexpr auto y{ fibo<5>() }; std::cout << x << " / " << y << "\n"; return 0; } * Örnek 11, //.. template void func(T x) { // BURADA ARTIK KISA DEVRE DAVRANIŞI SÖZ KONUSU DEĞİLDİR. EĞER 'Run-Time-If' kullansaydık, KISA DEVRE oluşacaktı. if constexpr ( std::is_integral::value && std::numeric_limits::min() < 10) { // error: no match for ‘operator<’ (operand types are ‘std::__cxx11::basic_string’ and ‘int’) } } int main() { /* # OUTPUT # */ func(12); func(std::string{}); return 0; } /*============================================================================================================*/ (38_24_01_2021) > 'User-Defined Literals' : 'UDL' şeklinde kısaltılır. Kendi özel sabitlerimizi oluşturmamıza olanak verir. >> Arka planda yine bir fonksiyon çağrısı yapılmaktadır. Geri dönüş değerini kullanıyoruz. 'Literal Operator Functions' denmektedir iş bu fonksiyonlara. * Örnek 1, //.. int main() { auto name{ "Mehmet" }; // 'name' is 'const char*' auto surname{ "Aksu"s }; // 'surname' is 'std::string' return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # */ using namespace std::chrono; auto duration{ 3h }; // Bir fonksiyon çağrısının geri dönüş değerini kullanıyoruz. return 0; } >> Kendi sabitlerimiz için '_' atomunu eklemek zorundayız ve bu '_' atomundan öncekileri bir yazı biçiminde gönderiyorsak 'uncooked', bir sayı bütünü olarak gönderiyorsak da 'cooked' versiyonu kullanıyoruz demektir. >> Bahsi geçen operatör fonksiyonunun geri dönüş değeri bizim insifiyetimizde. Fakat ismi 'operator""_' ile başlamak zorunda. Devamında da parametre parantezi ve alacağı argümanlar yazılmakta ki bunlar da bizim İNSİFİYATİMİZDE DEĞİLDİR. Tam sayılar için 'long double', gerçek sayılar için 'long long' olmalı vs. Son olarak iş bu fonksiyonları 'constexpr' olarak nitelemek avantajımıza olabilir. >> Pekiştirici örnekler, * Örnek 1, //.. // Ana birim 'm' olarak seçildi. constexpr double operator""_m(long double val) { return static_cast(val); } // Dolayısıyla aşağıdakiler de 'm' cinsindendir. // 'km' olarak geldi, 'm' olarak çıktı. 'cooked' versiyon. // Çünkü alacağı argümanları bir rakam olarak gönderiyoruz. constexpr double operator""_km(long double val) { return static_cast(val * 1000); } // 'dm' olarak geldi, 'm' olarak çıktı. constexpr double operator""_dm(long double val) { return static_cast(val / 10); } // 'cm' olarak geldi, 'm' olarak çıktı. constexpr double operator""_cm(long double val) { return static_cast(val / 100); } // 'mm' olarak geldi, 'm' olarak çıktı. constexpr double operator""_mm(long double val) { return static_cast(val / 1000); } int main() { /* # OUTPUT # Length : 7891.52 */ constexpr double totalLength{ 2.3_km + 34.98_m + 54321.0_dm + 12345.6_cm + 987.654_mm }; /* constexpr double totalLength{ operator""_km(2.3) + operator""_m(34.98) + operator""_dm(54321.) + operator""_cm(12345.6) + operator""_mm(987.654) }; */ std::cout << "Length : " << totalLength << "\n"; return 0; } * Örnek 2, //.. constexpr double operator""_FAG(long double degree) { return static_cast( (degree - 32) / 1.8 ); } int main() { /* # OUTPUT # 1.59444 */ constexpr double santiGrad{ 34.87_FAG }; // 'cooked' std::cout << santiGrad << "\n"; return 0; } * Örnek 3, //.. // 'uncooked' unsigned int operator""_Bnry(const char* other) { unsigned uval{ 0 }; while( *other ) { std::cout << "\n--------------------------\n"; char digit{ *other }; std::cout << "_digit_ => " << digit << "\n"; if( digit != '1' && digit != '0' ) throw std::runtime_error("0 yada 1 olmayan bir karakter!!!"); uval = uval * 2 + ( digit - '0'); // " 'digit' - '0' " yaparak karakterimizin tam sayı değerini elde etmiş oluyoruz. // '0' karakterinin tam sayı karşılığı 65 varsayarsak, rakamların da ardışık sıralandığını düşünürsek, // '1' karakterinin tam sayı karşılığı 66 olmuş olacaktır. // " '1' - '0' " yaparak da aslında karakterimizin tam sayı değerini elde ediyoruz. std::cout << "_uval_ => " << uval << "\n"; ++other; std::cout << "\n--------------------------\n"; } return uval; } // 'Function-try' block int main() try { /* # STDOUT # -------------------------- _digit_ => 1 _uval_ => 1 -------------------------- -------------------------- _digit_ => 1 _uval_ => 3 -------------------------- -------------------------- _digit_ => 0 _uval_ => 6 -------------------------- uvalOne : 6 -------------------------- _digit_ => 1 _uval_ => 1 -------------------------- -------------------------- _digit_ => 2 */ unsigned uvalOne{ 110_Bnry }; std::cout << "uvalOne : " << uvalOne << "\n"; /* # STDERR # Hata yakalandi... 0 yada 1 olmayan bir karakter!!! */ unsigned uvalTwo{ 123_Bnry }; std::cout << "uvalTwo : " << uvalTwo << "\n"; return 0; } catch(const std::exception& ex) { std::cerr << "Hata yakalandi... " << ex.what() << "\n"; } * Örnek 4, //.. // 'uncooked' unsigned int operator""_Octl(const char* other) { unsigned uval{ 0 }; while( *other ) { std::cout << "\n--------------------------\n"; char digit{ *other }; std::cout << "_digit_ => " << digit << "\n"; if( !( digit >= '0' && digit < '8' ) ) throw std::runtime_error("Octal olmayan bir karakter!!!"); uval = uval * 8 + ( digit - '0'); std::cout << "_uval_ => " << uval << "\n"; ++other; std::cout << "\n--------------------------\n"; } return uval; } // 'Function-try' block int main() try { /* # STDOUT # -------------------------- _digit_ => 1 _uval_ => 1 -------------------------- -------------------------- _digit_ => 2 _uval_ => 10 -------------------------- -------------------------- _digit_ => 1 _uval_ => 81 -------------------------- uvalOne : 81 -------------------------- _digit_ => 7 _uval_ => 7 -------------------------- -------------------------- _digit_ => 8 */ unsigned uvalOne{ 121_Octl }; std::cout << "uvalOne : " << uvalOne << "\n"; /* # STDERR # Hata yakalandi... Octal olmayan bir karakter!!! */ unsigned uvalTwo{ 789_Octl }; std::cout << "uvalTwo : " << uvalTwo << "\n"; return 0; } catch(const std::exception& ex) { std::cerr << "Hata yakalandi... " << ex.what() << "\n"; } * Örnek 5, Yazı olarak girilen rakamları tam sayıya çevirme: //.. void strToDecimal(const std::string& other) { int ival{}; for(size_t index{}; index < other.size(); ++index) { ival = ival * 10 + other.at(index) - '0'; } std::cout << "Result : " << ival << "\n"; } void strToBinary(const std::string& other) { int ival{}; for(size_t index{}; index < other.size(); ++index) { ival = ival * 2 + other.at(index) - '0'; } std::cout << "Result : " << ival << "\n"; } void strToOctal(const std::string& other) { int ival{}; for(size_t index{}; index < other.size(); ++index) { ival = ival * 8 + other.at(index) - '0'; } std::cout << std::showbase << "Result : " << ival << "\n"; } void strToHexa(const std::string& other) { int ival{}; for(size_t index{}; index < other.size(); ++index) { ival = ival * 16 + other.at(index) - '0'; } std::cout << std::showbase << "Result : " << ival << "\n"; } int main() { /* # OUTPUT # Enter numbers to be converted to Decimal : 123 Result : 123 Enter numbers to be converted to Binary : 101 Result : 5 Enter numbers to be converted to Octal : 123 Result : 83 Enter numbers to be converted to Hexa : 123 Result : 291 */ std::string sval{}; std::cout << "Enter numbers to be converted to Decimal : "; std::cin >> sval; strToDecimal(sval); std::cout << "Enter numbers to be converted to Binary : "; std::cin >> sval; strToBinary(sval); std::cout << "Enter numbers to be converted to Octal : "; std::cin >> sval; strToOctal(sval); std::cout << "Enter numbers to be converted to Hexa : "; std::cin >> sval; strToHexa(sval); return 0; } * Örnek 6, //.. // 'cooked' / 'uncooked' ayrımını sağlamak için argümanların bu şekilde olması zorunlu. Date operator""_Date(const char* p, size_t) { return Date{ p }; } int main() { /* # OUTPUT # 05 Haziran 2022 Pazar */ auto myDate{ "05-06-2022"_Date }; std::cout << myDate << "\n"; return 0; } * Örnek 7, //.. // 'User Defined Literals' genellikle bir isim alanı içerisine konulurlar. namespace Storage{ // Parametrenin 'unsigned long long' olması mecburidir. constexpr size_t operator""_KB(unsigned long long size) { return static_cast( size * 1024 ); } constexpr size_t operator""_MB(unsigned long long size) { return static_cast( size * 1024 * 1024 ); } } int main() { /* # OUTPUT # size : 8 size : 16384 size : 1048576 */ using namespace Storage; auto bufferZero{ 1_KB }; std::cout << "size : " << sizeof(bufferZero) << "\n"; using byte = unsigned char; auto bufferOne{ std::array{} }; std::cout << "size : " << sizeof(bufferOne) << "\n"; // 16 * 1024 auto bufferTwo{ std::array{} }; std::cout << "size : " << sizeof(bufferTwo) << "\n"; // 1 * 1024 * 1024 return 0; } > 'Ratio' kütüphanesi : 'Run-time' ile hiç bir alakası yoktur. Bünyesindeki her şey bir şablondur. >> Pekiştirici örnekler: * Örnek 1, //.. int main() { /* # OUTPUT # */ // 'num' isimli değişken, sadeleştirme sonucunda paydaki kısım. Sadeleştirme otomati olarak yapılmakta. constexpr auto x{ std::ratio<1, 5>::num }; // 'x' is 1 constexpr auto y{ std::ratio<8, 24>::num }; // 'y' is 1 // 'den' isimli değişken, sadeleştirme sonucunda paydadaki kısım. Sadeleştirme otomati olarak yapılmakta. constexpr auto z{ std::ratio<1, 5>::den }; // 'z' is 5 constexpr auto t{ std::ratio<8, 24>::den }; // 't' is 3 return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # St5ratioILl1ELl5EE */ // 'type', kendisinin türünün karşılığıdır. std::cout << typeid( std::ratio<1, 5>::type ).name() << "\n"; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # St5ratioILl1ELl100EE => 1 / 100 */ std::cout << typeid( std::centi::type ).name() << " => " << std::centi::num << " / " << std::centi::den << "\n"; // 'std::centi', 'std::ratio<100,1>' açılımının tür eş ismidir. return 0; } * Örnek 4, //.. class de{ public: constexpr de(long long x) : mx{x} {} friend std::ostream& operator<< (std::ostream& os, de x) { auto s{ std::to_string(x.mx) }; if( s[0] == '-' ) { os << "-"; s.erase(0, 1); // Sadece ilk karakteri silecektir. } auto len{ s.length() }; for(size_t i{}; i < len; ++i) { os << s[i]; if( auto n = len - 1 - i; n && n % 3 == 0 ) { os << "."; } } return os; } private: long long mx; }; template std::ostream& operator<<(std::ostream& os, std::ratio r) { return os << de{r.num} << " / " << de{r.den}; } int main() { /* # OUTPUT # deci : 1 / 10 centi : 1 / 100 milli : 1 / 1.000 micro : 1 / 1.000.000 nano : 1 / 1.000.000.000 pico : 1 / 1.000.000.000.000 femto : 1 / 1.000.000.000.000.000 atto : 1 / 1.000.000.000.000.000.000 exa : 1.000.000.000.000.000.000 / 1 peta : 1.000.000.000.000.000 / 1 tera : 1.000.000.000.000 / 1 giga : 1.000.000.000 / 1 mega : 1.000.000 / 1 kilo : 1.000 / 1 hecto : 100 / 1 deca : 10 / 1 */ std::cout << "deci : " << std::deci{} << "\n"; std::cout << "centi : " << std::centi{} << "\n"; std::cout << "milli : " << std::milli{} << "\n"; std::cout << "micro : " << std::micro{} << "\n"; std::cout << "nano : " << std::nano{} << "\n"; std::cout << "pico : " << std::pico{} << "\n"; std::cout << "femto : " << std::femto{} << "\n"; std::cout << "atto : " << std::atto{} << "\n"; std::cout << "exa : " << std::exa{} << "\n"; std::cout << "peta : " << std::peta{} << "\n"; std::cout << "tera : " << std::tera{} << "\n"; std::cout << "giga : " << std::giga{} << "\n"; std::cout << "mega : " << std::mega{} << "\n"; std::cout << "kilo : " << std::kilo{} << "\n"; std::cout << "hecto : " << std::hecto{} << "\n"; std::cout << "deca : " << std::deca{} << "\n"; return 0; } * Örnek 5, //.. constexpr std::intmax_t a{17}; constexpr std::intmax_t b{23}; int main() { /* # OUTPUT # Result : 818 / 391 */ auto num{ std::ratio_add< std::ratio, std::ratio >::num }; auto den{ std::ratio_add< std::ratio, std::ratio >::den }; std::cout << "Result : " << num << " / " << den << "\n"; return 0; } * Örnek 6, //.. constexpr std::intmax_t a{17}; constexpr std::intmax_t b{23}; int main() { /* # OUTPUT # Result : 1 */ // auto result{ std::ratio_less_v< std::ratio, std::ratio > }; auto result{ std::ratio_less< std::ratio, std::ratio >::value }; std::cout << "Result : " << result << "\n"; return 0; } > 'std::chrono' kütüphanesi : C++11 ile dile eklenmiştir. Tarih ve zaman işlemleri için de kullanılır. Kütüphanedekileri kullanabilmek için 'chrono' isim alanını nitelememiz gerekmektedir. >> Pekiştirici örnekler, * Örnek 1, //.. int main() { /* # OUTPUT # nx is 76324721 nanosecond long. mx is 76 second long. */ using namespace std::chrono; auto nx{ 76324721ns }; // Buradaki 'nx' gerçekten de 76324721 nanosaniye süresindedir. std::cout << "nx is " << nx.count() << " nanosecond long.\n"; milliseconds mx{ duration_cast(nx) }; // Basamağın altındaki süreden yukarıya otomatik dönüşüm olmadığından, // bu şekilde bir dönüşüm gerçekleştirdik. std::cout << "mx is " << mx.count() << " millisecond long.\n"; return 0; } * Örnek 2, //.. // C++20 öncesine kadar aşağıdaki 'insert' işlevi mevcut değildi. template std::ostream& operator<<(std::ostream& os, const std::chrono::duration& dur) { return os << de(dur.count()) << " * (" << de(P::num) << " / " << de(P::den) << ")"; } int main() { /* # OUTPUT # Second : 1 * (1 / 1) Millisecond : 11 * (1 / 1.000) Nanosecond : 111 * (1 / 1.000.000.000) */ using namespace std::chrono; // 'operator<<()' fonksiyonunun ikinci parametresi olan 'duration' aslında iki parametre // almaktadır. Bu parametrelerden ilki içeride kullanılacak 'tick' sayacının türü. // Diğeri de içeride kullanılan 'ratio' ya ait oran bilgisi. std::cout << "Second : " << 1s << "\n"; std::cout << "Millisecond : " << 11ms << "\n"; std::cout << "Nanosecond : " << 111ns << "\n"; return 0; } * Örnek 3, //.. template std::ostream& operator<<(std::ostream& os, const std::chrono::duration& dur) { return os << de(dur.count()) << " * (" << de(P::num) << " / " << de(P::den) << ")"; } using halfSecond = std::chrono::duration>; constexpr std::chrono::duration operator""_QS (long double value) { return std::chrono::duration>{value}; } int main() { /* # OUTPUT # Second : 123 * (1 / 1) Half of Second : 654 * (1 / 2) Quarter of Second : 246.853.091 * (1 / 1) Nanosecond : 65.123 * (1 / 1.000.000.000) Total Duration : 246.853.541 * (1 / 1) */ using namespace std::chrono; // 123 saniye std::cout << "Second : " << 123s << "\n"; // 327 saniye std::cout << "Half of Second : " << halfSecond{ 654 } << "\n"; // 246853091.2 saniye std::cout << "Quarter of Second : " << 987412365._QS << "\n"; // 0.000065123 saniye std::cout << "Nanosecond : " << 65123ns << "\n"; // 246853541.2 saniye std::cout << "Total Duration : " << duration_cast((123s + halfSecond{ 654 } + 987412365._QS + 65123ns)) << "\n"; return 0; } * Örnek 4, //.. template std::ostream& operator<<(std::ostream& os, const std::chrono::duration& dur) { return os << de(dur.count()) << " * (" << de(P::num) << " / " << de(P::den) << ")"; } using halfSecond = std::chrono::duration>; // Saniyenin yarısı using frame = std::chrono::duration>; // Saniyenin onda biri. using Day = std::chrono::duration>; // Bir gündeki saniye sayısı. int main() { /* # OUTPUT # Half Second : 131 Millisecond : 65500 Millisecond : 65.500 * (1 / 1.000) Frame : 655 Day : 0 */ using namespace std::chrono; halfSecond x{ 128 }; // 64 saniye / 128 yarım saniye. ++x; ++x; ++x; // 65.5 saniye / 131 yarım saniye std::cout << "Half Second : " << x.count() << "\n"; std::cout << "Millisecond : " << milliseconds{ x }.count() << "\n"; std::cout << "Millisecond : " << milliseconds{ x } << "\n"; std::cout << "Frame : " << frame{ x }.count() << "\n"; std::cout << "Day : " << duration_cast(x).count() << "\n"; return 0; } * Örnek 5, //.. using dsec = std::chrono::duration; // Saniye türü. int main() { using namespace std::chrono; microseconds us{ 7654777 }; std::cout << us.count() << "\n"; // OUTPUT => 7654777 // Daha ince(fine) türden, daha kalın(coarse) türe otomatik dönüşüm vardır. nanoseconds ns = us; std::cout << us.count() << "\n"; // OUTPUT => 7654777 // milliseconds msx = us; // GEÇERSİZ. Daha ince türe otomatik dönüşüm YOKTUR. // Dolayısıyla bizler dönüştürmeliyiz. Fakat veri kaybı meydana gelebilir. milliseconds msx = duration_cast(us); std::cout << msx.count() << "\n"; // OUTPUT => 7654 // Yukarıya tamamlayacaktır. msx = ceil(us); std::cout << msx.count() << "\n"; // OUTPUT => 7655 // Aşağıya tamamlayacaktır. msx = floor(us); std::cout << msx.count() << "\n"; // OUTPUT => 7654 // '.5' yukarısını yukarıya, '.5' aşağısını da aşağı tamamlıyor. msx = round(us); std::cout << msx.count() << "\n"; // OUTPUT => 7655 dsec ds = us; std::cout << ds.count() << "\n"; // OUTPUT => 7.65478 return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # milisaniye olarak sureyi girin : 123456789 34 saat, 17 dakika, 36 saniye, 789 salise. */ using namespace std; using namespace chrono; long long mSec; std::cout << "milisaniye olarak sureyi girin : "; std::cin >> mSec; // 'mSec' is 'long long' // 'ms' is 'milliseconds' milliseconds ms{ mSec }; // 'hrs' is 'hours' hours hrs{ duration_cast(ms) }; // 'ms' bir milisaniye değerinden. Bir saate göre modunu alırsak, geriye kalan bilgi dakikadır. minutes mns{ duration_cast( ms % 1h ) }; // 'ms' bir milisaniye değerinden. Bir dakikaya göre modunu alırsak, geriye kalan saniyedir. seconds sc{ duration_cast( ms % 1min ) }; // 'ms' bir milisaniye değerinden. Bir saniyeye göre modunu alırsak, geriye kalan milisaniyedir. milliseconds msc{ ms % 1s }; if( hrs.count() ) std::cout << hrs.count() << " saat, "; if( mns.count() ) std::cout << mns.count() << " dakika, "; if( sc.count() ) std::cout << sc.count() << " saniye, "; if( msc.count() ) std::cout << msc.count() << " salise.\n"; return 0; } * Örnek 7, //.. // 'pulseSecond' demek '100' saniye demektir. using pulseSecond = std::chrono::duration>; int main() { /* # OUTPUT # 12300 12400 */ using namespace std; using namespace chrono; pulseSecond pss{ 123 }; // 123000 saniye demektir. std::cout << seconds{ pss }.count() << "\n"; ++pss; // '100' saniye daha eklendi. std::cout << seconds{ pss }.count() << "\n"; return 0; } >> Tarih-zaman çizgisinde herhangi bir noktayı gösteren 'time-point' ler vardır. Bu 'time-point' leri yorumlayabilmemiz için bizlerin bir 'epoch' noktasına, yani başlangıç noktasına ihtiyacımız vardır. Buradan hareketle diyebiliriz ki iki 'time-point' arasındaki fark aslında bir 'duration' şeklindedir. İşte bize bunu veren de 'clock' sınıfı. Aşağıdaki örnekleri inceleyelim. * Örnek 1, //.. ---------------------------------------------------------------- Tarih-Zaman Çizgisi ^ ^ ^ | | | | | | | | | | Time-point Time-point | | | | | | | |<------- Duration ------->| | | Epoch >>> Bize verilen 'clock' sınıfları ise şunlardır: 'system_clock', 'steady_clock' ve 'high_resolution_clock'. Bir 'event' in süresi ölçülürken 'steady_clock' kullanılması tavsiye olunur çünkü 'system_clock' bizim sistemimize bağlıdır. Hesap sırasında alınan 'time-point' ler arasında sistemimizin saati değiştiğinde dengeler bozulabilir. İşte 'steady_clock' bunun olmayacağının garantisini vermektedir. Bunu da sınıfın 'is_steady' değişkeni üzerinden öğrenebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # false true */ using namespace std; using namespace chrono; std::cout << std::boolalpha << system_clock::is_steady << "\n"; std::cout << std::boolalpha << steady_clock::is_steady << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # Duration of the event : 1.06e-07 It lasted 0 seconds. It lasted 0 milliseconds. It lasted 0 microseconds. It lasted 106 nanoseconds. */ using namespace std; using namespace chrono; // İlk 'time-point' bilgisini 'timePointOne' isimli değişkende sakladık. auto timePointOne{ steady_clock::now() }; // Some events occured here... // İkinci 'time-point' bilgisi de 'timePointTwo' isimli değişkende sakladık. auto timePointTwo{ steady_clock::now() }; std::cout << "Duration of the event : " << duration(timePointTwo - timePointOne).count() << "\n"; std::cout << "It lasted " << seconds{ duration_cast(timePointTwo-timePointOne) }.count() << " seconds.\n"; std::cout << "It lasted " << milliseconds{ duration_cast(timePointTwo-timePointOne) }.count() << " milliseconds.\n"; std::cout << "It lasted " << microseconds{ duration_cast(timePointTwo-timePointOne) }.count() << " microseconds.\n"; std::cout << "It lasted " << nanoseconds{ duration_cast(timePointTwo-timePointOne) }.count() << " nanoseconds.\n"; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # islem basliyor... islem sone erdi... Duration : 4877 milliseconds Duration : 4.8775 seconds */ using namespace std; using namespace chrono; std::vector iVec; mt19937 eng; uniform_int_distribution<> dist{ 0, 1'000'000 }; std::cout << "islem basliyor...\n"; auto tp_start{ steady_clock::now() }; fcs(iVec, 10'000'000, [&]{ return dist(eng); }); sort(iVec.begin(), iVec.end()); auto tp_end{ steady_clock::now() }; std::cout << "islem sone erdi...\n"; std::cout << "Duration : " << duration_cast(tp_end - tp_start).count() << " milliseconds\n"; std::cout << "Duration : " << duration{tp_end - tp_start}.count() << " seconds\n"; return 0; } * Örnek 4, //.. long long runTimeFibonacci(unsigned n) { return n < 2 ? n : runTimeFibonacci(n-1) + runTimeFibonacci(n-2); } int main() { /* # OUTPUT # Bir sayi girin : 50 Fibonacci of 50 is 12586269025 Duration : 153.926 second. */ using namespace std; using namespace chrono; unsigned n; std::cout << "Bir sayi girin : "; std::cin >> n; auto start{ steady_clock::now() }; auto val{ runTimeFibonacci(n) }; auto end{ steady_clock::now() }; std::cout << "Fibonacci of " << n << " is " << val << "\n"; std::cout << "Duration : " << duration{end-start}.count() << " second.\n"; return 0; } * Örnek 5, //.. using namespace std; using namespace chrono; void printTime() { static const char* const pmons[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static const char* const pdays[] = { "Mon", "Tue", "Wed", "Thru", "Fri", "Sat", "Sun" }; // C++ dilindeki 'time-point', C dilindeki versiyonuna dönüştürüldü. time_t timer = system_clock::to_time_t( system_clock::now() ); // C dilindeki kullanım şekli. auto p = localtime(&timer); std::cout << std::setfill('0'); std::cout << std::setw(2) << p->tm_mday << " " << pmons[p->tm_mon] << " " << p->tm_year + 1900 << " " << pdays[p->tm_wday] << " " << std::setw(2) << p->tm_hour << " " << std::setw(2) << p->tm_min << " " << std::setw(2) << p->tm_sec << " "; } void printTime(const time_t& timer) { static const char* const pmons[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static const char* const pdays[] = { "Mon", "Tue", "Wed", "Thru", "Fri", "Sat", "Sun" }; auto p = localtime(&timer); // C dilindeki kullanım şekli. std::cout << std::setfill('0'); std::cout << std::setw(2) << p->tm_mday << " " << pmons[p->tm_mon] << " " << p->tm_year + 1900 << " " << pdays[p->tm_wday] << " " << std::setw(2) << p->tm_hour << " " << std::setw(2) << p->tm_min << " " << std::setw(2) << p->tm_sec << " "; } int main() { /* # OUTPUT # ----------------------------------- 06 Jun 2022 Tue 20 23 08 ----------------------------------- 06 Jun 2022 Tue 20 23 08 ----------------------------------- Kac dakika : 35 ----------------------------------- 35 dakika oncesi : 06 Jun 2022 Tue 19 48 08 ----------------------------------- 35 dakika sonrasi : 06 Jun 2022 Tue 20 58 08 ----------------------------------- */ std::cout << "\n-----------------------------------\n"; mt19937 eng( static_cast( duration_cast( system_clock::now().time_since_epoch() ).count() ) ); // i. 'system_clock::now()' fonksiyonu bize bir 'time-point' döndürmektedir. // ii. Bu 'time-point' bünyesindeki '.time_since_epoch()' fonksiyonu ile orjinden geçen süreyi alıyoruz. // iii. Bu süreyi 'millisecond' haline getiriyoruz. // iv. Bu 'millisecond' şeklindeki süreyi 'unsigned' veri tipine dönüştürüyoruz. // v. 'unsigned' veri tipini de TOHUM DEĞERİ olarak kullanıyoruz. printTime(); std::cout << "\n-----------------------------------\n"; // Bir 'time-point' temin ettik. auto tp_now{ system_clock::now() }; // Bunu C dilindeki 'time_t' versiyonuna dönüştürüp, kullandık. printTime(system_clock::to_time_t(tp_now)); std::cout << "\n-----------------------------------\n"; int min; std::cout << "Kac dakika : "; std::cin >> min; std::cout << "\n-----------------------------------\n"; // Temin ettiğimiz 'time-point' den önceki 'min' dakika öncesine gittik // ve bunun C dilindeki 'time_t' versiyonuna dönüştürdük. time_t timer = system_clock::to_time_t(tp_now - minutes{min}); // Bu değer ile 'min' kadar önceki 'time-point' kullanıldı. std::cout << min << " dakika oncesi : "; printTime(timer); std::cout << "\n-----------------------------------\n"; timer = system_clock::to_time_t(tp_now + minutes{min}); std::cout << min << " dakika sonrasi : "; printTime(timer); std::cout << "\n-----------------------------------\n"; return 0; } * Örnek 6, //.. using namespace std; using namespace chrono; int main() { /* # OUTPUT # 1654546364861952106 19149 */ // İlk 'time-point' elde edildi. auto timePoint{ system_clock::now() }; // Orijinden geçen süre. std::cout << timePoint.time_since_epoch().count() << "\n"; // Bir gün içindeki saniye miktarı. using Days = duration>; // Orijindne geçen süre 'Gün' haline getirildi. std::cout << duration_cast(timePoint.time_since_epoch()).count() << "\n"; return 0; } * Örnek 7, //.. using namespace std; using namespace chrono; std::vector spendTime(size_t n) { vector ivec(n); generate(begin(ivec), end(ivec), rand); sort(begin(ivec), end(ivec)); return ivec; } int main() { /* # OUTPUT # Duration : 5690335439 nanoseconds. Duration : 5690 milliseconds. Duration : 5690335 microseconds. Duration : 5 seconds. */ auto tp_start = steady_clock::now(); auto x = spendTime(10'000'000); auto tp_end = steady_clock::now(); std::cout << "Duration : " << nanoseconds{ tp_end - tp_start }.count() << " nanoseconds.\n"; std::cout << "Duration : " << duration_cast( tp_end - tp_start).count() << " milliseconds.\n"; std::cout << "Duration : " << duration_cast( tp_end - tp_start).count() << " microseconds.\n"; std::cout << "Duration : " << duration_cast( tp_end - tp_start).count() << " seconds.\n"; return 0; } * Örnek 8, //.. using namespace std; using namespace chrono; std::vector spendTime(size_t n) { vector ivec; generate_n(back_inserter(ivec), n, rand); sort(begin(ivec), end(ivec)); return ivec; } int main() { /* # OUTPUT # Duration : 4857310217 nanoseconds. Duration : 4857 milliseconds. Duration : 4857310 microseconds. Duration : 4 seconds. */ auto tp_start = steady_clock::now(); auto x = spendTime(10'000'000); auto tp_end = steady_clock::now(); std::cout << "Duration : " << nanoseconds{ tp_end - tp_start }.count() << " nanoseconds.\n"; std::cout << "Duration : " << duration_cast( tp_end - tp_start).count() << " milliseconds.\n"; std::cout << "Duration : " << duration_cast( tp_end - tp_start).count() << " microseconds.\n"; std::cout << "Duration : " << duration_cast( tp_end - tp_start).count() << " seconds.\n"; return 0; } > 'random' kütüphanesi: Sayıyı üreten kısım başka, o sayıları dağıtan kısım başkadır. Sayıyı üreten kısmın kodları bütün derleyicilerde aynı olmak zorunludur. Fakat dağıtan kısım için böyle bir zorunluk söz konusu değildir. * Örnek 1, //.. int main() { /* # OUTPUT # 3.499.211.612 581.869.302 3.890.346.734 3.586.334.585 545.404.204 4.161.255.391 3.922.919.429 949.333.985 2.715.962.298 1.323.567.403 */ std::mt19937 eng; for(int i = 0; i < 10; ++i) std::cout << de(eng()) << std::endl; // '.operator()()' fonksiyonuna çağrı yapılmıştır. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 1101000010010001101110110101110000100010101011101001111011110110111001111110000111111 0101110111011010101110000110001111101111001001000001000001000110101001011001111100000 0001111011011111011111111010011101001100000000000001010011100010010101101011111110000 11010000111100010010010111011101001001110111001000000100100101011 */ std::mt19937 eng; for(int i = 0; i < 10; ++i) std::cout << std::bitset<32>(eng()); // Aslında dağıtım için kullanılacak sınıf, yukarıdaki birler ve sıfırları kullanarak bir // dağılım gerçekleştirmektedir. return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # 0 / 4294967295 */ std::mt19937 eng; std::cout << eng.min() << " / " << eng.max() << std::endl; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # 3.241.986.011 788.524.768 550.517.220 195.242.712 2.503.779.386 1.202.091.195 330.249.489 884.407.208 2.578.693.249 3.611.958.086 */ std::mt19937 eng( std::chrono::steady_clock::now().time_since_epoch().count() ); // Tohum değeri sistem saatine bağlandığı için her defasında başka farklı sayılar // üretilecektir. for(int i = 0; i < 10; ++i) std::cout << de(eng()) << std::endl; return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # 1.903.269.765 1.922.826.769 1.789.128.003 324.336.966 3.913.977.513 1.935.086.952 539.655.785 4.241.226.083 1.583.303.667 3.847.192.695 */ std::mt19937 eng( std::random_device{}() ); // 'random_device' her defasında farklı bir sayı üreteceğinden, tohum değerini ona bağlarsak // her defasında farklı sayı ürettirebiliriz. for(int i = 0; i < 10; ++i) std::cout << de(eng()) << std::endl; return 0; } * Örnek 6, //.. int main() { std::mt19937 eng; eng.discard(9999); // İlk '9999' adet rakam atlandı. // Bütün derleyicilerde bu rakam aynı olmak zorunda. std::cout << eng() << std::endl; // OUTPUT => 4123659995 return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # true 3.499.211.612 581.869.302 3.890.346.734 3.586.334.585 545.404.204 4.161.255.391 3.922.919.429 949.333.985 2.715.962.298 1.323.567.403 false 3.499.211.612 581.869.302 3.890.346.734 3.586.334.585 545.404.204 4.161.255.391 3.922.919.429 949.333.985 2.715.962.298 1.323.567.403 true */ std::mt19937 engOne, engTwo; // Her iki sayı üreticisi eğer aynı miktarda sayı üretmişse ve tohum değerleri de birbirine eşitse // durumları da birbirine eşittir. std::cout << std::boolalpha << (engOne == engTwo) << "\n"; for(int i = 0; i < 10; ++i) std::cout << de(engOne()) << std::endl; std::cout << std::boolalpha << (engOne == engTwo) << "\n"; for(int i = 0; i < 10; ++i) std::cout << de(engTwo()) << std::endl; std::cout << std::boolalpha << (engOne == engTwo) << "\n"; return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # 725.333.953 251.387.296 3.200.466.189 725.333.953 251.387.296 3.200.466.189 */ std::mt19937 engOne; engOne.discard(10000); std::stringstream ss; ss << engOne; // 'state' bilgisi belleğe yazıldı. for(int i = 0; i < 3; ++i) std::cout << de(engOne()) << " "; std::cout << "\n"; ss >> engOne; // 'state' bilgisi bellekten okundu. for(int i = 0; i < 3; ++i) std::cout << de(engOne()) << " "; std::cout << "\n"; // 'state' bilgisi aynı olduğundan aynı değerleri üretti. return 0; } /*============================================================================================================*/ (39_01_31_2021) > 'random' kütüphanesi (devam): Önceki derste bahsedilen 'engine' ler ile üretilen bitleri, direkt olarak kullanmıyoruz. Belirli dağıtım kriterlerine göre belli aralıklara dağıtıyoruz. ŞU UNUTULMAMALIDIR Kİ PROGRAMI HER ÇALIŞTIRDIĞIMIZ ZAMAN FARKLI SAYI ZİNCİRLERİNİN ÜRETİLMESİ BİR ZORUNLULUK DEĞİLDİR. AYNI SAYI ZİNCİRİ TEST KODU OLARAK KULLANILMASI GEREKİYORSA, TOHUM DEĞERİNİ DEĞİŞTİRMEMELİYİZ. YİNE ŞUNU DA UNUTMAMALIYIZ Kİ RASTGELE SAYI ÜRETEN MOTORLARIN KODLARI BÜTÜN DERLEYİCİLERDE AYNI OLDUĞUNDAN, BÜTÜN DERLEYİCİLER AYNI TOHUM DEĞERİ İLE AYNI SAYI ZİNCİRİNİ OLUŞTURMASI GARANTİ ALTINDADIR. >> Pekiştirici örnekler, * Örnek 1, //.. int main() { /* # OUTPUT # 3867483570 20356776 1379478486 1464935884 1455388117 */ std::mt19937 eng{ 76324u }; // Eğer 4 'byte' ise o kadar büyüklük kadar farklı tohum değerine sahip olabilir, // sahip olduğumuz farklı tohum değerleri ile de farlı sayı zincirleri üretebiliriz. for (size_t i = 0; i < 5; ++i) std::cout << eng() << std::endl; // Üretilen rastgele sayıları bizler 'bit' kaynağı olarak kullanacağız. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 1406028867 679029235 2532478700 1169003239 990393276 */ using namespace std::chrono; // Daraltıcı dönüşüm göz ardı edilmiştir. std::mt19937 eng{ steady_clock::now().time_since_epoch().count() }; // ^ ^ ^ // | | | // | | Bu 'duration' bilgisini tohum değeri olarak kullandık. // | Bu 'time-point' i kullanarak orijinden geçen 'duration' elde edildi. // Bir 'time-point elde ettik' for (size_t i = 0; i < 5; ++i) std::cout << eng() << std::endl; // Artık programı her çalıştırdığımız zaman farklı bir zincir elde edeceğiz. return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # 2398323425 1661854891 2158870406 3119861730 18490202 */ using namespace std::chrono; std::mt19937 eng{ std::random_device{}() }; // 'random_device' gerçek rastgele sayı üretmek için kullanılan bir sınıf türü. // Bu sınıfı kullanarak rastgele oluşturulan sayıları tohum değeri olarak kullanırsak, // bizim 'eng' isimli motorumuz da rastgele sayıları üretecektir her çalıştırdığımızda. for (size_t i = 0; i < 5; ++i) std::cout << eng() << std::endl; // Artık programı her çalıştırdığımız zaman farklı bir zincir elde edeceğiz. return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # 4123659995 */ using namespace std::chrono; std::mt19937 eng; eng.discard(9999); // İlk '9999' adet sayıyı üretmişsin gibi 'state' bilgini güncelle fakat gerçekte hiç üretme. std::cout << eng() << std::endl; // Üretilecek 10000. sayı bütün derleyicilerde aynıdır. return 0; } * Örnek 5.1, //.. static const size_t SIZE = 10'000'000; void fillVector(size_t size) { std::mt19937 eng; std::vector ivec; std::generate_n(std::back_inserter(ivec), SIZE, eng); } int main() { /* # OUTPUT # Duration : 0.696304 */ using namespace std::chrono; auto tp_start = steady_clock::now(); fillVector(SIZE); auto tp_end = steady_clock::now(); std::cout << "Duration : " << duration(tp_end - tp_start).count() << std::endl; return 0; } * Örnek 5.2, //.. static const size_t SIZE = 10'000'000; void fillVector(size_t size) { std::mt19937 eng; std::vector ivec(size); std::generate_n(ivec.begin(), size, eng); } int main() { /* # OUTPUT # Duration : 0.221361 */ using namespace std::chrono; auto tp_start = steady_clock::now(); fillVector(SIZE); auto tp_end = steady_clock::now(); std::cout << "Duration : " << duration(tp_end - tp_start).count() << std::endl; return 0; } * Örnek 5.3, //.. static const size_t SIZE = 10'000'000; void fillVector(size_t size) { std::mt19937 eng; std::vector ivec; ivec.reserve(size); std::generate_n(std::back_inserter(ivec), size, eng); } int main() { /* # OUTPUT # Duration : 0.65144 */ using namespace std::chrono; auto tp_start = steady_clock::now(); fillVector(SIZE); auto tp_end = steady_clock::now(); std::cout << "Duration : " << duration(tp_end - tp_start).count() << std::endl; return 0; } * Örnek 5.4, //.. static const size_t SIZE = 10'000'000; void fillVector(size_t size) { std::mt19937 eng; std::vector ivec(size); std::generate_n(ivec.begin(), size, std::ref(eng)); } int main() { /* # OUTPUT # Duration : 0.429909 */ using namespace std::chrono; auto tp_start = steady_clock::now(); fillVector(SIZE); auto tp_end = steady_clock::now(); std::cout << "Duration : " << duration(tp_end - tp_start).count() << std::endl; return 0; } * Örnek 6, //.. template void showID(T x) { std::cout << typeid(T).name() << "\n"; } int main() { /* # OUTPUT # St23mersenne_twister_engineImLm32ELm624ELm397ELm31ELm2567483615ELm11ELm4294967295ELm7ELm2636928 640ELm15ELm4022730752ELm18ELm1812433253EE ---------------------------------- St17reference_wrapperISt23mersenne_twister_engineImLm32ELm624ELm397ELm31ELm2567483615ELm11ELm42 94967295ELm7ELm2636928640ELm15ELm4022730752ELm18ELm1812433253EEE */ using namespace std::chrono; std::mt19937 eng; showID(eng); std::cout << "----------------------------------\n"; showID(ref(eng)); return 0; } * Örnek 7, //.. std::mt19937& myengine(/*Parametreler duruma göre değiştirilebilir.*/) { static std::mt19937 eng{ std::random_device{}() }; return eng; } int main() { /* # OUTPUT # */ using namespace std::chrono; std::cout << myengine( )( ) << std::endl; // ^ ^ // | | // | Fonksiyonun referans döndürdüğü 'mt19937' nesnesinin '.operator()()' fonksiyonuna yapılan çağrı. // Fonksiyon çağrı parantezi return 0; } >> Üretilen sayıların dağılımında kullanılan dağılım sınıfları: Rastgele sayı üreten sınıfların derleyiciler nezdinde aynı sayıları üretme garantisi vardır fakat dağılımda kullanılan sınıflar için böyle bir garanti söz konusu değildir. Bu dağıtım sınıflarından en çok kullanılanları 'std::uniform_int_distribution', 'std::uniform_real_distribution', 'std::normal_distribution' ve 'discrete_distribution' sınıflarıdır. >>> Pekiştirici Örnekler, * Örnek 1, //.. int main() { /* # OUTPUT # 581869302 545404204 949333985 1323567403 418932835 1196140740 809094426 676943009 471852626 2084672536 */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. std::uniform_int_distribution dist; // 'int' türünün tutabileceği minimum ve maksimum değer aralığında // dağılım yapılacaktır. Aralığı daraltmak istiyorsak 'Ctor.' fonksiyonuna // argüman olarak geçmemiz gerekiyor. for(int i = 0; i < 10; ++i) std::cout << dist(eng) << std::endl; // 'eng' ile üretilen 'bit' leri, 'dist' ile yukarıdaki aralık // değerleri arasında dağıtıyoruz ve ekrana yazdırıyoruz. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # 1 166077 2 166840 3 167194 4 166974 5 166220 6 166695 */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. std::uniform_int_distribution dist{1, 6}; // '1' ve '6' dahil bu iki rakam aralığında sayılar üretilecek. std::map cmap; for(int i = 0; i < 1'000'000; ++i) { ++cmap[dist(eng)]; } for(auto [dice, count] : cmap) { std::cout << dice << " " << count << std::endl; } // Üretilen rakamlar birbirlerine ne kadar da yakınlar. return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # 1 ****************************************************************************************** **************************************************************************** 2 ****************************************************************************************** **************************************************************************** 3 ****************************************************************************************** ***************************************************************************** 4 ****************************************************************************************** **************************************************************************** 5 ****************************************************************************************** **************************************************************************** 6 ****************************************************************************************** **************************************************************************** */ // 'default_seed' değeri kullanıldı. std::mt19937 eng; // '1' ve '6' dahil bu iki rakam aralığında sayılar üretilecek. std::uniform_int_distribution dist{1, 6}; std::map cmap; for(int i = 0; i < 1'000'000; ++i) { ++cmap[dist(eng)]; } for(auto [dice, count] : cmap) { // Her 1000 rakam için bir '*' basacaktır. std::cout << dice << " " << std::string(count / 1000, '*') << "\n"; } // Üretilen rakamlar birbirlerine ne kadar da yakınlar. return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. // Aşağıda şablon parametresi olarak sadece 'int' değil 'integral' tiplerin hepsi kullanılabilir. std::uniform_int_distribution dist{1, 6}; // Normal kullanım. std::uniform_int_distribution<> dist2{1, 6}; // Şablon parametresi varsayılan argüman olarak 'int' almakta. std::uniform_int_distribution dist3{1, 6}; // C++17, CTAD // Gerçek sayılar için 'std::uniform_real_distribution' sınıfını kullanmamız gerekiyor. //... return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. std::uniform_int_distribution dist{1, 6}; // Normal kullanım. std::uniform_int_distribution<> dist2{1, 6}; // Şablon parametresi varsayılan argüman olarak 'int' almakta. std::uniform_int_distribution dist3{1, 6}; // C++17, CTAD //... return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. std::uniform_real_distribution dist{.0f, 1.0f}; // Normal kullanım. std::uniform_real_distribution<> dist2{.0, 1.0}; // Şablon parametresi varsayılan argüman olarak 'double' almakta. std::uniform_real_distribution dist3{.0f, 1.0f}; // C++17, CTAD //... return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # 0.814724_0.135477_0.905792 0.835009_0.126987_0.968868 0.913376_0.221034_0.632359 0.308167_0.0975404_0.547221 0.278498_0.188382_0.546881 0.992881_0.957507_0.996461 0.964889_0.967695_0.157613 0.725839_0.970593_0.98111 0.957167_0.109862_0.485376 0.798106_0.80028_0.297029 */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. std::uniform_real_distribution dist3{.0f, 1.0f}; // C++17, CTAD for(int i = 0; i < 10; ++i) { auto x = dist3(eng); auto y = dist3(eng); auto z = dist3(eng); std::cout << x << "_" << y << "_" << z << "\n"; } return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # ------------------------------------------- Ortalama : 50 Standart Sapma : 5 26 27 28 29 30 31 32 33 34 35 36* 37** 38**** 39******* 40********** 41*************** 42********************** 43****************************** 44************************************** 45************************************************ 46********************************************************** 47****************************************************************** 48************************************************************************* 49****************************************************************************** 50******************************************************************************* 51****************************************************************************** 52************************************************************************* 53****************************************************************** 54********************************************************* 55************************************************ 56************************************** 57***************************** 58********************** 59*************** 60********** 61******* 62**** 63** 64* 65 66 67 68 69 70 71 72 73 */ std::mt19937 eng; // 'default_seed' değeri kullanıldı. std::cout << "Ortalama : "; double mean; std::cin >> mean; std::cout << "Standart Sapma : "; double sDev; std::cin >> sDev; std::normal_distribution<> dist{mean, sDev}; // Varsayılan argüman 'double' std::map cmap; for(int i = 0; i < 1'000'000; ++i) { ++cmap[std::round(dist(eng))]; // .5 değerine göre aşağı veya yukarıya yuvarlıyoruz. } std::cout << "\n-------------------------------------------\n" << std::endl; std::cout << "Ortalama : " << mean << "\n"; std::cout << "Standart Sapma : " << sDev << "\n"; std::cout << std::setfill('0'); for(auto [value, counter] : cmap) { std::cout << std::setw(2) << value << std::string(counter / 1000, '*') << "\n"; } return 0; } * Örnek 9, Her bir değerin gelme yüzdesini biz belirlemek istiyorsak: //.. int main() { /* # OUTPUT # Zar Degeri : 0 : 1559 Zar Degeri : 1 : 1630 Zar Degeri : 2 : 1654 Zar Degeri : 3 : 1601 Zar Degeri : 4 : 1655 Zar Degeri : 5 : 1901 */ std::default_random_engine eng{ std::random_device{}() }; // 'default_random_engine' aslında arka planda 'mt19937' kullanıyor ekseriyetle. std::array weights{ 10., 10., 10., 10., 10., 12. }; // Bir zarı ele alırsak; // '1' gelme ihtimali 10 / 62 // '6' gelme ihtimali 12 / 62 // Böylelikle bir nevi hileli bir zar oluşturduk. // Herhangi bir veri yapısı da kullanabiliriz. std::discrete_distribution dist{ std::begin(weights), std::end(weights) }; // Elimizdeki veri yapısını kullanarak, sınıfın 'range' parametreli // kurucu işlevine çağrı yapıyoruz. std::map results; for(size_t i{}; i < 10'000; ++i) { ++results[dist(eng)]; } for(const auto& [die, count] : results) { std::cout << "Zar Degeri : " << die << " : " << count << std::endl; } return 0; } * Örnek 10, //.. int main() { /* # OUTPUT # Zar Degeri : 0 : 613 Zar Degeri : 1 : 683 Zar Degeri : 2 : 761 Zar Degeri : 3 : 588 Zar Degeri : 4 : 859 Zar Degeri : 5 : 1037 Zar Degeri : 6 : 443 Zar Degeri : 7 : 315 Zar Degeri : 8 : 1330 Zar Degeri : 9 : 3371 */ std::default_random_engine eng{ std::random_device{}() }; // 'default_random_engine' aslında arka planda 'mt19937' kullanıyor ekseriyetle. std::discrete_distribution dist{ 5, 6, 7, 5, 8, 9, 4, 3, 12, 31 }; // İlgili sınıfın 'std::initializer_list' parametreli // kurucu işlevine çağrı yapıyoruz. std::map results; for(size_t i{}; i < 10'000; ++i) { ++results[dist(eng)]; } for(const auto& [die, count] : results) { std::cout << "Zar Degeri : " << die << " : " << count << std::endl; } return 0; } > 'std::regex' kütüphanesi : İş bu kütüphane metin üzerinde 'validation', 'search', 'replacement', 'tokenizing' gibi işlemlerde kullanılır. Bir hayli büyük bir kütüphane olduğundan temkinli yaklaşmamız, bu kütüphaneye muhtaç isek kullanmamız gerekmektedir. 'regex' notasyonu pratiği için "regex101.com" sitesini kullanabiliriz. 'regex' notasyonu ile bir kural betimlenmektedir ve bu kural yukarıdaki işlemlerde kullanılmaktadır. Güvenilir kaynaklardan, başkalarının yazdığı notasyonları da kullanabiliriz. >> Notasyonda kullanılan 'meta' karakterler, yani kendi öz anlamı haricinde notasyon içerisinde kullanıldığında kazandığı anlamlar: # Karakter | Kattığı Anlam 1. . | "Any character except 'newline' " 2. [...] | "One of the characters... (may contain ranges)" 3. [^...] | "None of the characters... (may contain ranges)" 4. [[:charclass:]] | "A character of the specified character class 'charclass' " 5. \n,\t,\f,\r,\v | "A newline, tabulator, form feed, carriage return, or vertical tab" 6. \xhh,\uhhh | "A hexadecimal or Unicode character" 7. \d,\D,\s,\S,\w,\W | "A shortcut for a character of a character class" 8. * | "The previous character or group any times" 9. ? | "The previous character or group optional (none or one times)" 10. + | "The previous character or group at least one time" 11. {n} | "The previous character or group n times" 12. {n,} | "The previous character or group at least n times" 13. {n,m} | "The previous character or group at least n times at most m times" 14. ...|... | "The pattern before or the pattern |" 15. (...) | "Grouping" 16. \1,\2,\3 | "The 'nth' group (first group has index 1)" 17. \b | "A positive word boundary (beginning or end of a word)" 18. \B | "A negative word boundary (no beginning or end of a word)" 19. ^ | "The beginning of a line (includes beginning of all characters)" 20. $ | "The end of a line (includes end of all characters)" * Örnek 1, iş bu 'meta' karakterleri kendi öz anlamları olarak kullanmak için kendilerini '\' atomu ile birlikte kullanmalıyız. Örneğin, ".zge" bir notasyon olsun. Burada sistem dört harfli fakat son üç harfi 'zge' olan bütün kelimeleri bulacaktır. Eğer bizler '.zge' kelimesinin bulunmasını istiyorsak "\.zge" şeklinde bir notasyon kullanmalıyız. Benzer şekilde "[zge" şeklindeki notasyon '[zge' kelimesini ARAMAYACAKTIR. Onun için "\[zge" şeklinde bir notasyon kullanmalıyız. * Örnek 2, '.' karakteri yerine bütün karakterler gelebilir ama '\n' hariç. ".zge" şeklindeki bir notasyon 'özge' ismini, 'izge' ismini, 'ezge' ismini vs. isimleri doğrulayacaktır. * Örnek 3, '[]' arasına yazılan karakterleri içeren kelimeler doğrulanacaktır. "k[aei]r" şeklindeki bir notasyon 'kar', 'ker' 'kir' vs. şeklindeki kelimeleri doğrulayacaktır. * Örnek 4, '[^]' arasına yazılan karakterleri içermeyen kelimeler doğrulanacaktır. "k[^aei]r" şeklindeki notasyon 'kor' kelimesini doğrulayacaktır. 'kar', 'ker' ve 'kir' kelimeleri doğrulanmayacaktır. * Örnek 5, '[-]' şeklindeki notasyon ise şu şekildedir. '-' atomunun önündeki ve arkasındaki harfler bir 'range' oluşturuyor. Bu 'range' içerisindeki harflerden birini barındıranlar doğrulanmış oluyor. "k[d-m]r" şeklindeki notasyon, 'd' ve 'm' karakteri arasındaki karakterleri içeren kelimeleri doğrulamaktadır. "k[defgğhıijklm]r" şeklinin kısa yazımı. * Örnek 6, '[^-]' şeklindeki notasyon ise yukarıdaki kullanımın tersidir. Aralıktaki harfleri içermeyen kelimeler doğrulanır. * Örnek 7, "k[yqa-f0-9]r" şeklindeki notasyon öyle kelimeleri doğrulayacaktır ki bu kelimeler 'y' ve 'q' harflerini, 'a' ve 'f' arasındaki harflerden herhangi biri ve '0' ve '9' arasındaki rakamlardan herhangi birini. * Örnek 8, yukarıdaki tablonun beşinci satırındaki karakterler C dilindeki aynı anlamlara sahiptirler. * Örnek 9, yukarıdaki tablonun yedinci satırındaki karakterler özel anlam taşımaktadırlar. '\d' karakterinin notasyon içindeki anlamı 'digit' manasındadır. Yani "ar[0-9]xy" notasyonu aslında "ar\dxy" notasyonu şeklinde de yazılabilir. 'r' ve 'x' kelimelerinin arasına bir rakam karakteri olması gerekmektedir. '\D' ise '\d' karakterinin 'NOT' versiyonu gibidir. Geldiği yerde rakam olmaması gerekmektedir. "ar[^0-9]xy" demek aslında "ar\Dxy" demektir. * Örnek 10, yukarıdaki tablonun yedinci satırındaki '\s' karakteri ise boşluk karakteri manasındadır. "\d\s\d" notasyonu '9 9' şeklindeki yazıyı doğrulayacaktır. '\S' karakteri ise 'non-white-character' manasındadır. Boşluk karakteri olmayan karakter demektir. * Örnek 11, yukarıdaki tablonun yedinci satırındaki '\w' ise alfanumerik ya da '_' karakteri manasındadır. "[A-Za-z0-9_]" şeklindeki notasyon ile aynı manadadır. Herhangi bir büyük harf karakteri olabilir, herhangi bir küçük harf karakter olabilir, herhangi bir rakam karakteri olabilir ve '_' karakterini içermelidir. '\W' ise bunun 'NOT' versiyonudur. * Örnek 12, yukarıdaki tablonun sekizinci satırındaki '*' atomu, kendisinden önce gelen karakterden herhangi bir miktarda bulunabileceğini söylemektedir. "ba*k" şeklinde bir notasyonumuz olsun. Bununla 'bk', 'bak', 'baaaaaaaaak' vs. şeklindeki kelimeleri doğrulayabiliriz. ".*" şeklindeki karakterimiz ise herhangi bir karakterden herhangi miktarda olan kelimeleri doğrulamaktadır. * Örnek 13, yukarıdaki tablonun dokuzuncu satırındaki '?' atomu, kendisinden önce gelen karakter ya bir tane olacak ya da hiç olmadığı zaman doğrulamayı sağlatacaktır. "ba?k" şeklindeki notasyon sadece 'bak' ve 'bk' kelimelerini doğrulayacaktır. * Örnek 14, yukarıdaki tablonun onuncu satırındaki '+' atomu, kendisinden önce gelen öğeden bir yada birden fazla olması anlamına gelmektedir. "ba+k" şeklindeki notasyon 'bak', 'baaaaaaaaak' 'baaaaaaaaaaaaaaaaaaaak' şeklindeki kelimeleri doğrulamaktadır. * Örnek 15, yukarıdaki tablonun on üçüncü satırındaki '{n,m}' kullanım biçimi bir nevi 'range' işlevi görmektedir. Kendisinden önce gelen öğeden ilgili 'range' içerisindeki adet kadar olması manasına gelir. "ba{3, 6}k" şeklindeki notasyon sadece 'baaak', 'baaaak', 'baaaaak' vs 'baaaaaak' kelimelerini doğrulamaktadır. * Örnek 16, yukarıdaki tablonun onbirinci satırındaki '{n}' kullanım biçimi kendisinden önce gelen karakterin olması gerektiği adedi belirtir. "ba{3}k" şeklindeki notasyon sadece ve sadece 'baaak' kelimesini doğrulamaktadır. * Örnek 17, yukarıdaki tablonun onikinci satırındaki '{n,}' kullanım biçimi ise kendisinden önce gelen karakterin olması gereken minimum adedini belirtir. "ba{3,}k" şeklindeki notasyon 'baaak', 'baaaaaaak' vs. şeklindeki kelimeleri doğrulamaktadır. * Örnek 18, yukarıdaki tablonun ondördüncü satırındaki '...|...', ya sol tarafındaki varlık ya da sağ tarafındaki varlık kelime içerisinde olacaktır. "a|e" şeklindeki notasyon, bünyesinde 'a' ve 'e' karakterlerini barındıran kelimeleri doğrulayacaktır. "([a-e]{3,4})|([s-z]{2,})" şeklindeki notasyon 'a' ve 'e' arasındaki karakterlerden en az üç, en fazla dört adet içeren veya 's' ve 'z' arasındaki karakterlerden en az iki tane içeren kelimeleri doğrulayacaktır. * Örnek 19, yukarıdaki tablonun onyedinci satırındaki '\b' kelime sınırı demektir. "\bpro" şeklindeki notasyon 'pro', 'proje' kelimesini doğrular fakat 'apron' kelimesini doğrulamaz. İlgili atomu sonda kullandığımız zaman ise bittiği yeri göstermektedir. Yani, "bin\b" şeklindeki notasyon 'binali' kelimesini doğrulamaz ama 'kabin' kelimesini doğrulayacaktır. * Örnek 20, yukarıdaki tablonun onsekizinci satırındaki '\B', kelime başı veya kelime sonu olmayan demektir. Yukarıdakinin tersi manasındadır. * Örnek 21, yukarıdaki tablonun ondokuzuncu satırındaki '^' satırın başında olursa eğer kendisinden sonrakilerin satırın başında olması durumunda doğrulamaktadır. "^pr[aoei]" şeklindeki notasyon, "proje profosyonel programcılar tarafından ele alınıyor apronda bekliyoruz" cümlesindeki sadece cümlenin başındaki 'proje' kelimesi içindeki 'pro' yu bulacaktır. * Örnek 21, yukarıdaki tablonun ondokuzuncu satırındaki '$' ise yukarıdaki senaryonun satırın sonunda olması durumudur. * Örnek 22, yukarıdaki tablonun onbeşinci satırındaki '()' atomu öncelik etkisi yapmaktadır. "(ar){2,4}" şeklindeki notasyon bünyesinde en az iki en çok dört tane 'ar' kelimesini içeren kelimeleri onaylayacaktır. Bir diğer anlamı da parantez içerisine yazdıklarımız 'regex' motoru tarafından ayrıca değerlendirilmektedir. "[1-5]{3}ar[d-h]{2}" şeklindeki notasyon şu anlamı taşımaktadır; Bir ve beş arasındaki rakamlardan üç adet, 'a' ve 'r' karakterleri, 'd' ve 'h' arasındaki karakterlerden iki adet içerenlerin doğrulanması. Velevki notasyonumuz "([1-5]{3})ar([d-h]{2})" şeklinde olsaydı anlam olarak bir değişiklik oluşmayacaktı fakat uygun kelimeleri bulduğu zaman sol taraftaki parantez içerisindekileri ayrıca, sağ parantez içerisindekileri ayrıca işleyebiliyorum. Bir nevi alt birim haline getiriyoruz. Eğer bizler bu ikinci anlamı kullanmak istemiyorsak, sadece ve sadece öncelik parantezi olarak kullanmak istiyorsak, parantezin açıldığı yere '?:' atomunu ekliyoruz. "?:[1-5]{3}". * Örnek 23, yukarıdaki tablonun altıncı satırındakiler ise 'back_inserter' işlevi görmekteler. Kendilerinden önce ne varsa, bulundukları yerde de o kelimeler olacaktır. >> Pekiştirici örnekler '(regex101.com)' isimli internet sitesi kullanılmıştır. * Örnek 1, "a[etms]b" şeklindeki notasyon 'atb' ve 'aeb' kelimelerini doğrulayacaktır fakat 'alb' kelimesini doğrulamayacaktır. * Örnek 2, "pr[aoei]" notasyonu 'proje', 'apron', 'alpro' kelimelerini doğrulamaktadır. * Örnek 3, "\bpr[aoei]" notasyonu ise yukarıdaki 'proje', 'apron' ve 'alpro' kelimelerinden SADECE 'proje' kelimesini doğrulayacaktır. * Örnek 4, "pr[aoei]\b" notasyonu ise yukarıdaki 'proje', 'apron' ve 'alpro' kelimelerinden SADECE 'alpro' kelimesini doğrulayacaktır. * Örnek 5, "\bpr[aoei]\b" notasyonu ise yukarıdaki 'proje', 'apron' ve 'alpro' kelimelerinden HİÇ BİRİNİ doğrulamayacaktır fakat 'pro' kelimesini doğrulayacaktır. * Örnek 6, C dilinde 'if' deyiminden hemen sonra ';' atomunun yazılıp yazılmadığının kontrolü; Notasyon : "if\s*\(.*\)\s*;" Anlamı : "if...", 'i' ve 'f' karakterlerini içerecek, "if\s*...", 'i' ve 'f' karakterlerinden sonra herhangi miktarda boşluk karakteri içerebilir, "if\s*\(...", yukarı deyimin devamında '(' atomu gelmeli, "if\s*\(.*...", yukarıdaki deyimdeki '(' atomundan sonra herhangi bir karakterden sonra herhangi miktarda karakter gelebilir. "if\s*\(.*\)...", yukarıdaki deyimdeki bahsi geçen karakterlerden hemen sonra ')' atomu gelmeli. "if\s*\(.*\)\s*", yukarıdaki deyimde bahsi geçen ')' atomundan hemen sonra herhangi miktarda boşluk karakteri gelebilir. "if\s*\(.*\)\s*;", yukarıdaki deyimde bahsi geçen boşluk karakterinden sonra ';' atomu gelmeli. if (x > 5) ; // Bunu doğrulayacaktır. ifs(a>b) ; if (x > 10) ++y; if (x > 5); // Bunu doğrulayacaktır. a++ if(1); // Bunu doğrulayacaktır. if (x > y) ; { // Bunu doğrulayacaktır. * Örnek 7, 0-255 aralığındaki sayıları buldurma; Notasyon : "\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b" Anlamı : "\b(...)\b", parantez içerisindekilerin hem başlangıç hem de bitiş olarak kullanılması. "\b([0-9]|...)\b", '0' ve '9' arasındaki rakamlar ile başlayacak, '0' ve '9' rakamları arasındakiler DOĞRULANACAKTIR. "\b([0-9]|[1-9][0-9]|...)\b", veya '1' ve '9' arasındaki rakamlar ve '0' ve '9' arasındaki rakamlar ile başlayacak, '10' ve '99' arasındaki sayılar DOĞRULANACAKTIR. "\b([0-9]|[1-9][0-9]|1[0-9][0-9]|...)\b", veya '1' rakamı ile başlayacak ve devamında bir adet '0' ve '9' arasındaki ve bir adet '0' ve '9' arasındaki rakamlar olacak, '100' ile '199' arasındaki sayılar DOĞRULANACAKTIR. "\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|...", veya '2' rakamı ile başlayacak ve devamında bir adet '0' ve '4' arasındaki rakam olacak ve bir adet '0' ve '9' arasındaki rakamlar olacak, '200' ile '249' arasındaki sayılar DOĞRULANACAKTIR. "\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b", veya '25' rakamı olacak ve devamında da bir adet '0' ve '5' arasındaki rakamlar olacak, '250' ile '255' arasındaki sayılar DOĞRULANACAKTIR. 0 // Bunu doğrulayacaktır. 1 // Bunu doğrulayacaktır. 123 // Bunu doğrulayacaktır. 255 // Bunu doğrulayacaktır. 256 012 87 // Bunu doğrulayacaktır. 99 // Bunu doğrulayacaktır. 111 // Bunu doğrulayacaktır. 1112 777 783487943 * Örnek 8, Geçerli Tarih sorgulaması: Notasyon : "(0[1-9]|[12][0-9]|3[01])([-.\/])(0[1-9]|1[012])\2(19|20)\d\d" Anlamı : "(...)(...)(...)", şeklinde büyük resme bakarsak; "(0[1-9]|[12][0-9]|3[01])...", İlk başa '0' karakteri, devamında '1' ve '9' rakamları arasındaki rakamlardan birisi, '01' ve '09' rakamları arasındakiler veya '1' ve '2' rakamları ve '0' ile '9' rakamları arasındaki bir rakam, '10' ile '29' arasındakiler veya '3' rakamı, '0' ve '1' rakamları, '30' ve '31' rakamlarından birisi "(0[1-9]|[12][0-9]|3[01])([-./])...", yukarıdaki notasyonun devamında '-', '.' veya '/' karakterinden birisi gelebilir.(BİRİNCİ 'capture-point') "(0[1-9]|[12][0-9]|3[01])([-./])(0[1-9]|1[012])...", yukarıdaki notasyona ek olarak '0' karakteri ve devamında '1' ile '9' arasındaki karakterlerden birisi, '01' ile '09' arasındaki rakamlardan birisi veya '1' karakteri ile '0', '1' ve '2' karakterlerinden birisi, '10', '11' veya '12' rakamlarından birisi. "(0[1-9]|[12][0-9]|3[01])([-./])(0[1-9]|1[012])\2...", yukarıdaki notasyona ek olarak 'back_reference' işlevi görmektedir. Eğer bu işlev yerine "([-./])" notasyonunu kullansaydık gün-ay arasındaki ayraç ile ay-yıl arasındaki ayraç farklı olması durumunda da doğrulama yapılacaktı. 'back_reference' ile bunun önüne geçmiş oluyoruz. Birinci 'capture-point' de ne olduysa bu noktada da aynısı olacaktır. "(0[1-9]|[12][0-9]|3[01])([-./])(0[1-9]|1[012])\2(19|20)...", yukarıdaki notasyona ek olarak ya '19' sayısı ya da '20' sayısı olacak. "(0[1-9]|[12][0-9]|3[01])([-./])(0[1-9]|1[012])\2(19|20)\d\d", yukarıdaki notasyona ek olarak herhangi iki rakam gelebilir. 05.12.1998 // Bunu doğrulayacaktır. 05-1987 01.11.1998 // Bunu doğrulayacaktır. 12.05.1000 * Örnek 9, Geçerli gerçek sayı olup olmadığının testi: Notasyon : "[-+]?[0-9]*\.?[0-9]+" Anlamı : "[-+]...", '-' veya '+' karakterinin birisinin olması, "[-+]?...", yukarıdaki karakterlerden bir tane veya hiç olmaması durumu, "[-+]?[0-9]...", yukarıdaki notasyonun devamında '0' ve '9' rakamlarının arasındaki rakamlardan birisinin olması durumu, "[-+]?[0-9]*...", yukarıdaki notasyonun devamında, kendinden önce gelen rakamdan herhangi bir adet kadar olması, "[-+]?[0-9]*\....", yukarıdaki notasyonun devamında, sadece '.' atomunun gelmesi. "[-+]?[0-9]*\.?...", yukarıdaki notasyonun devamında, '.' atomundan ya bir tane ya da hiç olmaması durumu, "[-+]?[0-9]*\.?[0-9]", yukarıdaki notasyonun devamında, '0' ve '9' rakamlarının arasındaki rakamlardan birisinin olması durumu, "[-+]?[0-9]*\.?[0-9]+", yukarıdaki notasyonun devamında, ilgili karakterlerden en az bir tane olması durumudur. 2.4 // Bunu doğrulayacaktır. 0.7832487 // Bunu doğrulayacaktır. -78234.87234 // Bunu doğrulayacaktır. 87934.2 // Bunu doğrulayacaktır. 78345 // Bunu doğrulayacaktır. -83.234 // Bunu doğrulayacaktır. +73.87234 // Bunu doğrulayacaktır. 0 // Bunu doğrulayacaktır. 0.0 // Bunu doğrulayacaktır. * Örnek 10, C dilinde hexadecimal sabit belirtiyor mu? Notasyon : "\b0[xX][0-9a-fA-F]+\b" Anlamı : "\b0...", kelime başının '0' karakteri olması zorunlu. "\b0[xX]...", devamında 'x' veya 'X' karakterlerinden birisinin gelmesi zorunlu. "\b0[xX][0-9a-fA-F]...", devamında ise '0' ve '9' rakamları arasındaki rakamlardan birisi, 'a' ve 'f' harfleri arasındaki harflerden bir tanesi ve 'A' ve 'F' harfleri arasındaki harflerden bir tanesi olmak zorunda. "\b0[xX][0-9a-fA-F]+...", yukarıdaki notasyondan en az bir tane olmak zorunda. "\b0[xX][0-9a-fA-F]+\b", kelime bitişinin de yukarıdaki notasyon olmak zorunda. * Örnek 11, C dilindeki anahtar sözcükleri bulan notasyon Notasyon : "(\bauto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register |restrict|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while|_Bool|_Complex|_Imaginary\b)" Anlamı : https://regex101.com/r/gQcHz1/1 * Örnek 12, Gerçek sayı olup olmadığı sınamaktadır. Notasyon : "[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?" Anlamı : https://regex101.com/r/1QMAbw/1/ >> C++ dilindeki kullanımı: Gerek Cpp dilinde gerek 'regex' notasyonunda '(\)' karakteri özel anlam taşıdığı için karakterin, kendisini kullanmamız için, '\\' şeklinde bir kullanım sergilememiz gerekmektedir. Örneğin, elimizde '\\\s' şeklinde bir 'regex' notasyonu olsun. Bunun anlamı şudur, '(\)' karakterinin kendisi ve '\s' karakteri. Eğer bunu Cpp dilinde bir 'string' olarak işlemek istersek "\\\\\\s" olacaktır. Çünkü soldak ilk dört '(\)' karakteri aslında 'regex' notasyonunda '\\' karşılığı. Devamında gelen '\\s' ise '\s' nin karşılığı. Böylelikle Cpp dilindeki "\\\\\\s" nin 'regex' karşılığı '\\s' şeklinde. Bunun anlamı da '\ ' karakterinin yazı içerisinde aranması demektir. İşte bu karmaşayı önlemek için Cpp dilinde 'raw-string literals' şeklinde bir aracı vardır. Bu aracın kullanımı da şu şekildedir; 'R"(...)"', buradaki '...' yerine direkt olarak 'regex' notasyonunu yaza biliriz. * Örnek 1, //.. int main() { /* # OUTPUT # "ali" \nigde\ */ const char* regexNotaion{ R"("ali" \nigde\)" }; std::cout << regexNotaion << std::endl; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # Korkma, sönmez bu şafaklarda yüzen al sancak; Sönmeden yurdumun "üstünde" tüten en son ocak. O benim milletimin yıldızıdır, parlayacak; O benimdir, o benim milletimindir ancak. */ const char* regexNotaion{R"( Korkma, sönmez bu şafaklarda yüzen al sancak; Sönmeden yurdumun "üstünde" tüten en son ocak. O benim milletimin yıldızıdır, parlayacak; O benimdir, o benim milletimindir ancak. )" }; std::cout << regexNotaion << std::endl; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # another brother example measure meeting prepare present president pressure property protect question statement station whatever whether */ auto wordList{ get_dictionary("wordList.txt") }; std::cout << "Our word list contains " << wordList.size() << " words." << std::endl; std::regex myRegexNotaion{ ".{2}[aeio][tmps].{3,5}" }; // Argüman olarak direkt olarak bir 'regex' notasyonu geçilmektedir. // ".", herhangi bir karakterden bir adet // ".{2}", yukarıdaki notasyondan iki adet. Yani iki adet herhangi bir karakter // ".{2}[aeio]...", yukarıdaki notasyona ek olarak 'a', 'e', 'i' ve 'o' karakterlerinden birini içermesi // ".{2}[aeio][tmps]...", yukarıdaki notasyona ek olarak 't', 'm', 'p' ve 's' karakterlerinden birini içermesi // ".{2}[aeio][tmps]. ...", herhangi bir karakterden bir adet" // ".{2}[aeio][tmps].{3,5}", yukarıdaki notasyonu sağlayan en az üç, en fazla beş adet. Yani herhangi bir // karakterden en az üç, en fazla beş adet. for(const auto& word : wordList) { // 'std::regex_match', yazılan 'regex' notasyonuna göre kapta arama yapmaktadır. if( std::regex_match(word, myRegexNotaion) ) { std::cout << word << std::endl; } } return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # Our word list contains 1005 words. aaabbbcccardddeeeffftion farmation */ auto wordList{ get_dictionary("wordList.txt") }; std::cout << "Our word list contains " << wordList.size() << " words." << std::endl; std::regex myRegexNotaion{ ".*ar.*tion.*" }; // Argüman olarak direkt olarak bir 'regex' notasyonu geçilmektedir. // ".*...", herhangi bir karakterden 'n' kadar // ".*ar...", devamında 'ar' kelimesi // ".*ar.*...", devamında ise herhangi bir karakterden 'n' kadar // ".*ar.*tion...", devamında 'tion' kelimesi // ".*ar.*tion.*", devamında ise herhangi bir karakterden 'n' kadar for(const auto& word : wordList) { // 'std::regex_match', yazılan 'regex' notasyonuna göre kapta arama yapmaktadır. if( std::regex_match(word, myRegexNotaion) ) { std::cout << word << std::endl; } } return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # New regex notation : .*A.*h.*m.*e.*t.* Our word list contains 1005 words. ssssAeeehfffmgggehhht */ // Bu nesne bir 'regex' notasyonu olarak kullanılacak. std::string regexNotationStr{".*"}; const std::string name{"Ahmet"}; for(auto c : name) { regexNotationStr += c; regexNotationStr += ".*"; } std::cout << "New regex notation : " << regexNotationStr << "\n"; std::regex myRegexNotaion{ regexNotationStr }; auto wordList{ get_dictionary("wordList.txt") }; std::cout << "Our word list contains " << wordList.size() << " words." << std::endl; for(const auto& word : wordList) { // 'std::regex_match', yazılan 'regex' notasyonuna göre kapta arama yapmaktadır. if( std::regex_match(word, myRegexNotaion) ) { std::cout << word << std::endl; } } return 0; } /*============================================================================================================*/ (40_07_02_2021) > 'std::regex' kütüphanesi (devam) : >> C++ dilindeki kullanımı (devam): Eğer geçersiz bir 'regex' notasyonunu sınıfın kurucu işlevine argüman olarak geçersek, bir hata nesnesi gönderilecektir. * Örnek 1, //.. int main() { /* # OUTPUT # main.cpp: In function ‘int main()’: main.cpp:11:22: warning: unknown escape sequence: '\.' // 'raw-string literal' olarak kullanmadık. Dolayısıyla '\.' hata oluşturacak. 11 | std::regex rgx{ "[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?" }; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ try{ std::regex rgx{ "[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?" }; // 'raw-string literal' olarak kullanmadık. Dolayısıyla '\.' hata oluşturacak. // Bunun için ya 'raw-string literal' yada '\\.' yazmamız gerekiyor. // Bu hali ile sentaks hatası. }catch(const std::exception& ex) { std::cout << "Hata yakalandi... " << ex.what() << std::endl; } return 0; } >>> 'sub-group' veya 'capture-point' bilgilerinin elde edilmesi: Sınıfın '.mark_count()' isimli üye fonksiyonu ile bunu gerçekleştirebiliriz. * Örnek 1, //.. int main() { /* # OUTPUT # capture-point : 2 */ std::regex rgx{ "(Ahmet)(Kandemir)(?:Pehlivanli)" }; // En sağdaki parantez çifti 'capture-point' işlevi değil, sadece öncelik parantezidir. std::cout << "capture-point : " << rgx.mark_count() << "\n"; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # capture-point : 4 */ std::regex rgx{ "(Ah(m)et)(Kandemir)(Pehlivanli)" }; std::cout << "capture-point : " << rgx.mark_count() << "\n"; return 0; } >>> 'std::regex' sınıfın kurucu işlevine geçilen 'std::regex_constant' isim alanında tanımlı bazı bayraklar vardır. * Örnek 1, //.. int main() { /* # OUTPUT # capture-point : 0 */ std::regex rgx{ "(Ah(m)et)(Kandemir)(Pehlivanli)", std::regex_constants::nosubs}; // 'nosubs' isimli bayrak, 'capture-point' leri görmezden geldirmektedir. std::cout << "capture-point : " << rgx.mark_count() << "\n"; return 0; } * Örnek 2, //.. int main() { std::regex rgx{ "(Ah(m)et)(Kandemir)(Pehlivanli)", std::regex_constants::icase}; // 'icase' isimli bayrak, notasyonun 'incase sensitive' olmasını sağlar. Yani büyük/küçük harf ayrımı yapmaz. return 0; } >>> Pekiştirici örnekler, * Örnek 1, //.. int main() { /* # OUTPUT # Regex Notation : [-+]?[\d]*\.?[\d]+ ------------------------------------------------------- 2.86356 => VALID +123.78965 => VALID -7862.8273 => VALID .7 => VALID *5 => INVALID .7123 => VALID 18763.8263 => VALID -0.734 => VALID 254.abc => INVALID enes => INVALID 12.87e-5 => INVALID */ std::ifstream ifs{ "float.txt" }; if(!ifs) return 1; std::string regexString{ "[-+]?[\\d]*\\.?[\\d]+" }; // 'raw-string literal' kullanmadığımız için elimizdeki yazıyı uygun bir 'regex' notasyonu haline getirdik. std::cout << "Regex Notation : " << regexString << "\n-------------------------------------------------------\n"; std::regex regexNotation{ regexString }; std::string lines; while( ifs >> lines ) { if( std::regex_match(lines, regexNotation) ) { std::cout << lines << " => VALID" << std::endl; } else { std::cout << lines << " => INVALID" << std::endl; } } return 0; } * Örnek 2, https://regex101.com //.. constexpr const char* eRegex() { return R"((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]))"; } int main() { /* # OUTPUT # Regex Notation : /(?:[a-z0-9!#$%&'*+\=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/gm E-mail address : ali.ali.@gmail.co [Aali.ali.@gmail.co] is INVALID... */ /* # OUTPUT # Regex Notation : (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]) E-mail address : aali.ali.@gmail.com [aali.ali.@gmail.com] is INVALID... */ /* # OUTPUT # Regex Notation : (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]) E-mail address : aali.ali@gmail.com [aali.ali@gmail.com] is VALID... */ /* # OUTPUT # Regex Notation : (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]) E-mail address : ahmet.pehlivanli@yahoo.com [ahmet.pehlivanli@yahoo.com] is VALID... */ auto regexNotation{ eRegex() }; std::cout << "Regex Notation : " << regexNotation << std::endl; std::regex rgx{ regexNotation }; std::string eMail; std::cout << "E-mail address : "; std::cin >> eMail; if( std::regex_match(eMail, rgx) ) { std::cout << "[" << eMail << "] is VALID..." << std::endl; } else { std::cout << "[" << eMail << "] is INVALID..." << std::endl; } return 0; } >>> 'regex' notasyonu ile ARAMA işlemleri: Artık fonksiyonumuzun ismi 'regex_search()' fakat argümanlar 'regex_match()' ile aynıdır. İş bu fonksiyonun da geri dönüş değeri 'bool' veri tipidir. Fakat ikinci argüman olarak artık 'smatch' veya 'cmatch' sınıflarından değişkenler kullanıyoruz ki sonuçlar bu değişkenlere kaydedilsin. Bu sınıflar sırasıyla 'std::string' ve 'const char*' tip yazılar için kullanılır. * Örnek 1, //.. İçinde arama yapacağımız 'std::string' : "alican7642.fdce.9245enes" Kullanacağımız 'regex' notasyonu : "(\d{4})\.([a-f]{4})\.(\d{4})" ^ ^ ^ ^ ^ | | | | | | | | | Bir rakam karakteri fakat dört adet. | | | '.' karakterinin kendisi. | | 'a' ve 'f' karakterlerinin arasındaki karakterlerden dört adet. | '.' karakterinin kendisi. Bir rakam karakteri fakat dört adet. Bulunan yazımız : "7642.fdce.9245" | ^ | ^ | ^ | | b | c | d | | | | | | | | | | | | | |--------------| | a | a: Bulunan yazının tamamıdır. 'smatch' isimli sınıf bir kap olduğundan, '0' indisli öğesi iş bu yazıdır. b: İlgili bütün yazının birinci 'sub-match' grubudur. '1' indisli öğesi iş bu yazıdır. b: İlgili bütün yazının ikinci 'sub-match' grubudur. '2' indisli öğesi iş bu yazıdır. b: İlgili bütün yazının üçüncü 'sub-match' grubudur. '3' indisli öğesi iş bu yazıdır. * Örnek 2, //.. int main() { /* # OUTPUT # Icinde arama yapilacak yazi : alican7642.fdce.9245enes Regex Notation : (\d{4})\.([a-f]{4})\.(\d{4}) Capture Points : 3 Bulunan yazidan onceki metin : alican 0 indisli oge, [14] => 7642.fdce.9245 1 indisli oge, [4] => 7642 2 indisli oge, [4] => fdce 3 indisli oge, [4] => 9245 Bulunan yazidan sonraki metin : enes */ std::string textToSearchIn{ "alican7642.fdce.9245enes" }; // Bir 'regex' notasyonu kullanarak, bu yazı içerisinde arama yapacağız. std::cout << "Icinde arama yapilacak yazi : " << textToSearchIn << std::endl; std::string regexNotationString{ R"((\d{4})\.([a-f]{4})\.(\d{4}))" }; // Kullanacağımız 'regex' notasyonu, 'raw-string literal' şeklinde. std::regex regexNotation{ regexNotationString }; // 'regex' nesnemizi hayata getirdik. std::cout << "Regex Notation : " << regexNotationString << std::endl; std::cout << "Capture Points : " << regexNotation.mark_count() << std::endl; std::smatch mathchResults; // Bulunan hakkındaki değerlerin saklanacağı değişkenimiz. 'smatch' kullandığımız için 'std::string' ile işlem yapmalıyız. // Bu sınıfımız aslında bir 'container' olup, 'sub-match' isimli sınıfların öğelerinin birleşmesinden meydana gelir. // Bir 'container' olduğundan, '.operator[]()' fonksiyonunu çağırabiliriz. '0' indisini argüman olarak geçtiğimiz zaman // bulunan yazının tamamını, '1' geçildiğinde birinci 'sub-match' bölümünü, '2' indisini argüman olarak geçtiğimiz zaman // ikinci 'sub-match' bölümünü vs. temin edebiliriz. if( std::regex_search(textToSearchIn, mathchResults, regexNotation) ) { // Aranan ilk kez bulunduğunda programın akışı buraya girecektir. Bütün hepsini bulmak için başka yöntemler denemeliyiz. std::cout << "Bulunan yazidan onceki metin : " << mathchResults.prefix() << std::endl; for(size_t i{}; i < mathchResults.size(); ++i) { // std::cout << i << " indis konumundaki oge : " << mathchResults[i] << std::endl; std::cout << i << " indisli oge, [" << mathchResults.length(i) << "] => " << mathchResults.str(i) << std::endl; } /* for(const auto& subMatch : mathchResults) { // Döngünün her turunda 'subMatch' isimli değişken aslında 'sub-match' sınıf türünden bir nesne. std::cout << sub-match.str() << "\n"; } */ /* for(auto iter{mathchResults.cbegin()}; i != mathchResults.cend(); ++iter) { std::cout << iter->str() << "\n"; } */ std::cout << "Bulunan yazidan sonraki metin : " << mathchResults.suffix() << std::endl; } return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # Icinde arama yapilacak yazi : alican7642.fdce.9245enes Regex Notation : (\d{4})\.([a-f]{4})\.(\d{4}) Capture Points : 3 Aranan yazi 6 konumunda bulundu. Bulunan yazinin ilk kismi : 6 konumunda bulundu. Bulunan yazinin ikinci kismi : 11 konumunda bulundu. Bulunan yazinin ucuncu kismi : 16 konumunda bulundu. */ std::string textToSearchIn{ "alican7642.fdce.9245enes" }; // Bir 'regex' notasyonu kullanarak, bu yazı içerisinde arama yapacağız. std::cout << "Icinde arama yapilacak yazi : " << textToSearchIn << std::endl; std::string regexNotationString{ R"((\d{4})\.([a-f]{4})\.(\d{4}))" }; // Kullanacağımız 'regex' notasyonu, 'raw-string literal' şeklinde. std::regex regexNotation{ regexNotationString }; // 'regex' nesnemizi hayata getirdik. std::cout << "Regex Notation : " << regexNotationString << std::endl; std::cout << "Capture Points : " << regexNotation.mark_count() << std::endl; std::smatch mathchResults; // Bulunan hakkındaki değerlerin saklanacağı değişkenimiz. 'smatch' kullandığımız için 'std::string' ile işlem yapmalıyız. // Bu sınıfımız aslında bir 'container' olup, 'sub-match' isimli sınıfların öğelerinin birleşmesinden meydana gelir. // Bir 'container' olduğundan, '.operator[]()' fonksiyonunu çağırabiliriz. '0' indisini argüman olarak geçtiğimiz zaman // bulunan yazının tamamını, '1' geçildiğinde birinci 'sub-match' bölümünü, '2' indisini argüman olarak geçtiğimiz zaman // ikinci 'sub-match' bölümünü vs. temin edebiliriz. if( std::regex_search(textToSearchIn, mathchResults, regexNotation) ) { std::cout << "Aranan yazi " << mathchResults.position() << " konumunda bulundu." << std::endl; std::cout << "Bulunan yazinin ilk kismi : " << mathchResults.position(1) << " konumunda bulundu." << std::endl; std::cout << "Bulunan yazinin ikinci kismi : " << mathchResults.position(2) << " konumunda bulundu." << std::endl; std::cout << "Bulunan yazinin ucuncu kismi : " << mathchResults.position(3) << " konumunda bulundu." << std::endl; } return 0; } * Örnek 4, //.. int main() { /* # out.txt # 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 7642.fdce.9245 */ std::string regexNotationString{ R"((\d{4})\.([a-f]{4})\.(\d{4}))" }; // Kullanacağımız 'regex' notasyonu, 'raw-string literal' şeklinde. std::regex regexNotation{ regexNotationString }; // 'regex' nesnemizi hayata getirdik. auto textsToSearchIn{ get_str_from_file("notation.txt") }; std::ofstream ofs{"out.txt"}; if(ofs) { for(std::sregex_iterator iter{textsToSearchIn.cbegin(), textsToSearchIn.cend(), regexNotation}, end; iter != end; ++iter) { ofs << iter->str() << "\n"; } } else { std::cerr << "Dosya acilamadi...\n"; return 1; } return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # @6, [48] => disa.columbus.ns.mbx.hostmaster-dod-nic@mail.mil @55, [48] => disa.columbus.ns.mbx.arin-registrations@mail.mil @113, [22] => sergiosouza@ipw.com.br @136, [12] => cert@cert.br @149, [18] => mail-abuse@cert.br @176, [21] => support@caribsurf.com @198, [20] => kjones@caribsurf.com @227, [23] => ipadmin@erfwireless.com @251, [19] => jsealy@isp-tech.net @279, [28] => itsupport@tonysfinefoods.com @316, [25] => it.networking@basspro.com @351, [21] => ricardopi@hotmail.com @373, [12] => cert@cert.br @386, [18] => mail-abuse@cert.br @424, [29] => tanveer.rahman@novocom-bd.com @461, [48] => disa.columbus.ns.mbx.arin-registrations@mail.mil @537, [26] => jodhingo@agra-alliance.org @564, [26] => skisonzo@agra-alliance.org @591, [15] => amaina@agra.org @607, [17] => skisonzo@agra.org @633, [19] => support@esedona.net @653, [17] => abuse@esedona.net @722, [15] => hiou@viasat.com @738, [24] => wildblueabuse@viasat.com @774, [14] => odd@viasat.com @789, [22] => wildbluenoc@viasat.com @821, [21] => mcouto@nowtech.com.br @843, [12] => cert@cert.br @856, [18] => mail-abuse@cert.br @883, [19] => lgurley@flocorp.com @919, [16] => abuse@rogers.com @936, [24] => tecpro@rogerstelecom.net @961, [21] => noc@rogerstelecom.net @983, [23] => abuse@rogerstelecom.net @1007, [24] => dsl.ip@rogerstelecom.net @1039, [18] => dpieper@exwire.com @1058, [16] => abuse@exwire.com @1075, [18] => support@exwire.com @1102, [32] => bob.gelety@clarkconstruction.com @1135, [17] => pfeul@clarkus.com @1177, [17] => ip-req@sprint.net @1195, [19] => ipsa.noc@sprint.com @1223, [14] => abuse@i4hk.com @1238, [13] => info@i4hk.com @1306, [15] => daz@emswifi.com @1330, [17] => jnguyen@crpud.org @1355, [18] => jlynnsh@igctel.com @1391, [26] => netadmin@tractorsupply.com @1434, [20] => khatfield@socllc.net @1455, [16] => abuse@socllc.net @1507, [26] => ark.theobald@harlandfs.com @1551, [24] => ayman.louis@ca-egypt.com @1577, [24] => aged.tammam@ca-egypt.com @1620, [19] => abuse@infoquest.com @1640, [21] => support@infoquest.com @1662, [22] => lmelhorn@infoquest.com @1702, [15] => info@wit.net.my @1726, [27] => support@flatplanetphone.com @1754, [25] => abuse@flatplanetphone.com @1780, [23] => noc@flatplanetphone.com @1830, [28] => robertshutt@quinnemanuel.com @1859, [19] => support@perivue.com @1913, [14] => julius@mnl.seq @1936, [19] => ops@mypublisher.com @1992, [24] => aitne@clarisnetworks.com @2017, [28] => dstilwill@clarisnetworks.com @2053, [20] => mmueller@attmail.com @2082, [15] => abuse@onesc.net @2098, [16] => netops@onesc.net @2115, [22] => hostmaster@onestep.net @2154, [24] => arin.noc@radialpoint.com @2179, [25] => arin.tech@radialpoint.com @2205, [26] => arin.abuse@radialpoint.com @2249, [19] => sanjay@nec-labs.com @2277, [22] => abuse-mail@townisp.com @2300, [21] => arin-tech@townisp.com @2322, [14] => sdorsey@ci.shr @2345, [31] => network_management@homedics.com @2385, [20] => lhamilton@teainc.org @2414, [18] => meira@fonar.com.br @2433, [12] => cert@cert.br @2446, [18] => mail-abuse@cert.br @2473, [22] => arin-admin@skybeam.com @2496, [17] => abuse@skybeam.com @2514, [26] => bplimpton@jabbroadband.com @2576, [20] => italerts@liaison.com @2614, [23] => lorenzo.piccioli@ctv.ca @2646, [13] => noc@mhsfl.net @2722, [17] => mahmood@cytel.com @2748, [22] => arin.tech@labspace.com @2779, [20] => z-netops@zscaler.com @2800, [17] => abuse@zscaler.com @2818, [16] => bill@zscaler.com @2843, [25] => lservello@valuedrugco.com @2904, [19] => domadmin@hormel.com @2924, [22] => kloldenkamp@hormel.com @2947, [20] => sjholtorf@hormel.com @2985, [29] => sami.elloumi@orangetunisie.tn @3015, [28] => fethi.miled@orangetunisie.tn @3044, [30] => marouen.hamza@orangetunisie.tn @3083, [20] => eki@orangetunisie.tn @3122, [20] => noc@pranasystems.com @3143, [25] => bgoldman@pranasystems.com @3185, [24] => security@americanbus.com @3210, [24] => brichter@americanbus.com @3235, [25] => pbarsness@americanbus.com @3295, [15] => dgreen@dart.org */ std::regex regexNotation{ eRegex() }; // 'regex' nesnemizi hayata getirdik. auto textsToSearchIn{ get_str_from_file("mails.txt") }; for(std::sregex_iterator iter{textsToSearchIn.cbegin(), textsToSearchIn.cend(), regexNotation}, end; iter != end; ++iter) { std::cout << "@" << iter->position() << ", [" << iter->length() << "] => " << iter->str() << std::endl; } return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # administration => administ(ra){tion} attention => at(te)n{tion} collection => (co)llec{tion} condition => (co)ndi{tion} direction => di(re)c{tion} education => edu(ca){tion} generation => gene(ra){tion} institution => ins(ti)tu{tion} international => in(te)rna{tion}al operation => ope(ra){tion} population => (po)pula{tion} position => po(si){tion} production => p(ro)duc{tion} relationship => (re)la{tion}ship section => (se)c{tion} situation => (si)tua{tion} station => s(ta){tion} traditional => t(ra)di{tion}al */ auto sVec{ get_dictionary("wordList.txt") }; std::regex rgx{ ".*([ckprts]+[aeio]+).*([tcln]ion).*" }; std::smatch matchResults; for(auto& word : sVec) { if( std::regex_match(word, matchResults, rgx) ) { std::cout << word << " => "; word.insert( matchResults.position(1), 1, '(' ); word.insert( matchResults.position(1) + matchResults.length(1) + 1, 1, ')' ); word.insert( word.begin() + matchResults.position(2) + 2, '{' ); word.insert( word.begin() + matchResults.position(2) + 3 + matchResults.length(2), '}' ); std::cout << word << std::endl; } } return 0; } * Örnek 8, //.. int main() { /* # OUTPUT # administration attention collection condition direction education generation institution international operation population position production relationship section situation station traditional */ auto sVec{ get_str_from_file("wordList.txt") }; std::regex regexNotation{ ".*([ckprts]+[aeio]+).*([tcln]ion).*" }; for_each( std::sregex_iterator{sVec.cbegin(), sVec.cend(), regexNotation}, std::sregex_iterator{}, [](const auto& matchResults){ std::cout << matchResults.str() << std::endl; } ); return 0; } >>> 'regex' notasyonu ile 'tokenizing' işlemleri: * Örnek 1, //.. int main() { /* # OUTPUT # Uyanlarin tamami: [projede,] [projeyi] [profesyonel] [programcilar] Birinci sub-match grubu: [pro] [pro] [pro] [pro] Ikinci sub-match grubu: [jede,] [jeyi] [fesyonel] [gramcilar] Birinci ve ikinci sub-match grubu: [pro] [jede,] [pro] [jeyi] [pro] [fesyonel] [pro] [gramcilar] Butun gruplar: [projede,] [pro] [jede,] [projeyi] [pro] [jeyi] [profesyonel] [pro] [fesyonel] [programcilar] [pro] [gramcilar] Ayiraclar: [] [ ] [ basariya ulastıracak, ] [ ] [ gerekiyor] */ std::string textToSearchIn{ "projede, projeyi basariya ulastıracak, profesyonel programcilar gerekiyor" }; std::regex regexNotation{ "\\b(pro)([^ ]*)" }; // 'pro' kelimesi, kelimenin başında olacak. // Devamında ise, boşluk karakteri olmayan karakterlerden 'n' kadar olacak. std::sregex_token_iterator regexTokenizer; std::cout << "Uyanlarin tamami: \n"; // '0' geçmemiz gerekiyor, uyanlar için. for(std::sregex_token_iterator iter(textToSearchIn.begin(), textToSearchIn.end(), regexNotation, 0); iter != regexTokenizer; ++iter) { std::cout << "[" << *iter << "]" << std::endl; } std::cout << "\n\n" << std::endl; std::cout << "Birinci sub-match grubu: " << std::endl; // '1' geçmemiz gerekiyor. Bir numaralı 'sub-match', 'pro' kelimesidir. for(std::sregex_token_iterator iter(textToSearchIn.begin(), textToSearchIn.end(), regexNotation, 1); iter != regexTokenizer; ++iter) { std::cout << "[" << *iter << "]" << std::endl; } std::cout << "\n\n" << std::endl; std::cout << "Ikinci sub-match grubu: " << std::endl; // '2' geçmemiz gerekiyor. İki numaralı 'sub-match', boşluk karakteri olmayan bütün karakterlerdir. for(std::sregex_token_iterator iter(textToSearchIn.begin(), textToSearchIn.end(), regexNotation, 2); iter != regexTokenizer; ++iter) { std::cout << "[" << *iter << "]" << std::endl; } std::cout << "\n\n" << std::endl; std::cout << "Birinci ve ikinci sub-match grubu: " << std::endl; // 'std::initializer_list' kullanarak '1' ve '2' geçildi. Her iki 'sub-match' grubu. for(std::sregex_token_iterator iter(textToSearchIn.begin(), textToSearchIn.end(), regexNotation, {1,2}); iter != regexTokenizer; ++iter) { std::cout << "[" << *iter << "]" << std::endl; } std::cout << "\n\n" << std::endl; std::cout << "Butun gruplar: " << std::endl; // 'std::initializer_list' kullandık. for(std::sregex_token_iterator iter(textToSearchIn.begin(), textToSearchIn.end(), regexNotation, {0,1,2}); iter != regexTokenizer; ++iter) { std::cout << "[" << *iter << "]" << std::endl; } std::cout << "\n\n" << std::endl; std::cout << "Ayiraclar: " << std::endl; for(std::sregex_token_iterator iter(textToSearchIn.begin(), textToSearchIn.end(), regexNotation, -1); iter != regexTokenizer; ++iter) { std::cout << "[" << *iter << "]" << std::endl; } std::cout << "\n\n" << std::endl; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # murat kadir kemal ve aysenur Cpp ogrendiler iyi yaptilar */ std::string textsToSearchIn{ "murat, kadir, kemal ve aysenur Cpp ogrendiler, iyi yaptilar..." }; std::regex regexNotation{ "[\\s,.]+" }; // Boşluk karakteri, virgül karakteri ve nokta karakterinden bir yada daha fazla olabilir. std::sregex_token_iterator iter{ textsToSearchIn.cbegin(), textsToSearchIn.cend(), regexNotation, -1 }; // '-1' geçerek bir nevi kurala uymayanları temin ettik. std::sregex_token_iterator end; for(; iter != end; ++iter) std::cout << iter->str() << std::endl; return 0; } >>> 'regex' notasyonu ile 'replace' işlemleri: * Örnek 1, //.. int main() { std::string str{ "profesyonel butun programcilar projelerdeki tum yazilimsal problemleri cozebilmeli." }; std::regex rgx{ "\\b(pro)([^ ]*)" }; auto s{ std::regex_replace(str, rgx, "($1)($2)") }; // '$1' yerine birinci 'sub-match', '$2' yerine ise ikinci 'sub-match' grubu gelecektir. std::cout << s << std::endl; // OUTPUT => (pro)(fesyonel) butun (pro)(gramcilar) (pro)(jelerdeki) tum yazilimsal (pro)(blemleri) cozebilmeli. auto ss{ std::regex_replace(str, rgx, "$1_$2 ") }; std::cout << ss << std::endl; // OUTPUT => pro_fesyonel butun pro_gramcilar pro_jelerdeki tum yazilimsal pro_blemleri cozebilmeli. auto sss{ std::regex_replace(str, rgx, "$&") }; std::cout << sss << std::endl; // OUTPUT => profesyonel butun programcilar projelerdeki tum yazilimsal problemleri cozebilmeli. auto ssss{ std::regex_replace(str, rgx, "($&) [$&]") }; std::cout << ssss << std::endl; // OUTPUT => (profesyonel) [profesyonel] butun (programcilar) [programcilar] (projelerdeki) // [projelerdeki] tum yazilimsal (problemleri) [problemleri] cozebilmeli. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # [corona virus yayiliyor, olu sayisinin artmasindan endise edilmekte. Allah ulkemizi korusun Amin!] [corona virus yayiliyor, olu sayisinin artmasindan endise edilmekte. Allah ulkemizi korusun Amin!] */ std::string str{ "corona virus yayiliyor,\n" "olu sayisinin artmasindan\r\n" "endise edilmekte.\n" "Allah ulkemizi korusun\r\n" "Amin!" }; std::cout << "[" << str << "]" << std::endl; std::regex rgx{ "\\r?\\n" }; auto line{ std::regex_replace(str, rgx, " ") }; std::cout << "[" << line << "]" << std::endl; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # */ std::string str{ "profesyonel butun programcilar projelerdeki tum yazilimsal problemleri cozebilmeli." }; std::regex rgx{ "\\b(pro)([^ ]*)" }; auto s{ std::regex_replace(str, rgx, "($1)($2)", std::regex_constants::format_first_only) }; // OUTPUT => (pro)(fesyonel) butun programcilar projelerdeki tum yazilimsal problemleri cozebilmeli. std::cout << s << std::endl; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # Bir yazi giriniz: damla damla damlıyordu akan yağmur damlaları damla damlıyordu akan yağmur damlaları */ std::string str; std::cout << "Bir yazi giriniz: " << std::endl; std::getline(std::cin, str); std::regex rgx{ R"(\b(\w+)\s+\1)" }; std::cout << std::regex_replace(str, rgx, "$1") << std::endl; return 0; } > C++ dilindeki 'vocabulary' types: 'std::optional', 'std::variant' ve 'std::any' sınıflarının bulunduğu gruba verilen isimdir. >> 'std::optional' : Bu sınıf türünden değişkenler bir değer tutabilir veya tutmayabilirler. C dilindeki 'NULL' döndürme mekanizmasına iyi bir ALTERNATİFTİR. Cpp17 ile dile eklenmiştir. * Örnek 1, //.. template class A{ unsigned char buffer[n]; }; template using optype = std::optional>; int main() { /* # OUTPUT # std::cout << sizeof(optype<128>) << '\n'; std::cout << sizeof(optype<256>) << '\n'; std::cout << sizeof(optype<512>) << '\n'; std::cout << sizeof(optype<1024>) << '\n'; std::cout << sizeof(optype<2048>) << '\n'; */ std::cout << sizeof(optype<128>) << '\n'; std::cout << sizeof(optype<256>) << '\n'; std::cout << sizeof(optype<512>) << '\n'; std::cout << sizeof(optype<1024>) << '\n'; std::cout << sizeof(optype<2048>) << '\n'; // Aradaki bu fark, 'std::optional' sınıfı bünyesindeki 'bool' türden değişkenden kaynaklıdır. // Bir değer tutup tutmamasına bağlı olarak, bu değer değişmektedir. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # ben su an bos degilim => 31 */ std::optional x{ 31 }; // if(x.has_value()) // if(x) if(x.operator bool()) std::cout << "ben su an bos degilim => "; std::cout << *x << std::endl; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # ben su an bosum... 0 */ std::optional x{}; if(x.operator bool()) std::cout << "ben su an bos degilim => "; else std::cout << "ben su an bosum...\n"; try{ std::cout << *x << std::endl; // İş bu fonksiyon referans döndürmektedir. } catch(const std::exception& ex) { std::cout << "hata bulundu... " << ex.what() << std::endl; } // Herhangi bir hata fırlatmadığına dikkat edin. // Dolayısıyla boş olanları 'dereference' edersek 'run-time' hatası alırız, yani 'Tanımsız Davranış' oluşur. return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # en su an bosum... hata bulundu... bad optional access */ std::optional x{ }; if(x.operator bool()) std::cout << "ben su an bos degilim => "; else std::cout << "ben su an bosum...\n"; try{ std::cout << x.value() << std::endl; // İş bu fonksiyon referans döndürmektedir. } catch(const std::exception& ex) { std::cout << "hata bulundu... " << ex.what() << std::endl; } return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # Oguz Karan AAAAA Hata yakalandi... */ std::optional op{"Oguz Karan"}; std::cout << op.value() << std::endl; op.value().assign(5, 'A'); // İlgili sınıf nesnemiz içerisindeki yazı değiştirilmiştir. std::cout << op.value() << std::endl; op = std::nullopt; // İlgili sınıf nesnemiz boşaltılmıştır. try{ std::cout << op.value() << std::endl; } catch(...) { std::cout << "Hata yakalandi..." << std::endl; } return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # Oguz Karan Bilinmeyen kisi... */ std::optional op{"Oguz Karan"}; std::cout << op.value_or("Bilinmeyen kisi...") << std::endl; // İŞ bu fonksiyonumuz referans DÖNDÜRMEMEKTEDİR. op = std::nullopt; std::cout << op.value_or("Bilinmeyen kisi...") << std::endl; return 0; } * Örnek 7, //.. template void print_op(const std::optional& op) { std::cout << "type is : " << typeid(T).name() << std::endl; if(op) std::cout << "value is : " << *op << std::endl; else std::cout << "HAS NO VALUE!" << std::endl; std::cout << "----------------------------\n" << std::endl; } int main() { /* # OUTPUT # type is : i HAS NO VALUE! ---------------------------- type is : d HAS NO VALUE! ---------------------------- type is : c HAS NO VALUE! ---------------------------- type is : f value is : 12.5 ---------------------------- type is : PKc value is : necati ---------------------------- type is : NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE value is : ergin ---------------------------- type is : NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE value is : Cpp anlatiyor. ---------------------------- */ using namespace std::string_literals; std::optional op1; print_op(op1); std::optional op2{}; print_op(op2); // std::optional op3(); print_op(op3); // Most Vexing Parse std::optional op4 = std::nullopt; print_op(op4); std::optional op5{ 12.5f }; print_op(op5); // CTAT std::optional op6{ "necati" }; print_op(op6); std::optional op7{ "ergin"s }; print_op(op7); // UDL : User-defined literal std::optional op8{ "Cpp anlatiyor." }; print_op(op8); return 0; } * Örnek 8, //.. class UserName{ public: explicit UserName(const std::string& str) : m_name(str) { std::cout << "Ctor. : " << m_name << "\n"; } ~UserName(){ std::cout << "Dtor. : " << m_name << "\n"; } private: std::string m_name; }; int main() { /* # OUTPUT # Ctor. : Necati Ergin Dtor. : Necati Ergin Ctor. : Ali Serce Dtor. : Ali Serce Ctor. : Kaan Arslan Dtor. : Kaan Arslan Ctor. : Oguz Karan Ctor. : Nuri Yilmaz Dtor. : Nuri Yilmaz Dtor. : Nuri Yilmaz */ std::optional op_name; op_name.emplace("Necati Ergin"); op_name.emplace("Ali Serce"); // 'Dtor.' çağrılmasına vesile olacak. op_name.reset(); // 'Dtor.' çağrılmasına vesile olacak. op_name.emplace("Kaan Arslan"); // 'Dtor.' çağrılmasına vesile olacak. op_name = std::nullopt; op_name.emplace("Oguz Karan"); op_name = UserName("Nuri Yilmaz"); return 0; } * Örnek 9, //.. int main() { /* # OUTPUT # op1 : (1.2,5.6) op2 : necati op3 : ergin op4 : (3,4) <2> <3> <5> <7> <9> */ using namespace std::string_literals; // 'std::in_place' geçilmesi zorunludur. Tür çıkarımında işe yaramaktalar. std::optional> op1{ std::in_place, 1.2, 5.6 }; std::cout << "op1 : " << *op1 << std::endl; auto op2{ std::make_optional("necati") }; std::cout << "op2 : " << *op2 << std::endl; auto op3{ std::make_optional("ergin"s) }; std::cout << "op3 : " << *op3 << std::endl; auto op4{ std::make_optional>(3.0, 4.0) }; std::cout << "op4 : " << *op4 << std::endl; auto fcomp = [](int x, int y){ return std::abs(x) < std::abs(y); }; // 'std::in_place' geçilmesi zorunludur. Tür çıkarımında işe yaramaktalar. std::optional> op5{ std::in_place, { 2, 3, 5, 7, 9 }, fcomp }; for (auto index : op5.value()) { std::cout << "<" << index << ">" << std::endl; } return 0; } * Örnek 10, //.. int main() { std::optional oe; std::optional ox{10}; std::optional oy{20}; std::cout.setf(std::ios::boolalpha); std::cout << (oe == ox) << std::endl; // false std::cout << (oe == std::nullopt) << std::endl; // true std::cout << (10 == ox) << std::endl; // true std::cout << (oe < ox) << std::endl; // true std::cout << (oe > ox) << std::endl; // false std::optional oz; std::optional omin{10}; std::cout << (oz < omin) << std::endl; // true : Boş olan, dolu olandan her zaman daha küçük kabul edilmiştir. return 0; } * Örnek 11, //.. int main() { std::optional oe{std::nullopt}; // Boş std::optional ox{false}; // Dolu std::optional oy{true}; // Dolu std::cout.setf(std::ios::boolalpha); std::cout << (oe == ox) << std::endl; // false std::cout << (oe == oy) << std::endl; // false std::cout << (oe < ox) << std::endl; // true std::cout << (oe < oy) << std::endl; // true std::cout << (oe == true) << std::endl; // false std::cout << (oe == false) << std::endl; // false std::cout << (ox == oy) << std::endl; // false std::cout << (ox < oy) << std::endl; // true return 0; } * Örnek 12, //.. int main() { std::optional oe{46}; oe = std::nullopt; oe = {}; oe.reset(); return 0; } * Örnek 13, //.. std::optional to_int(const std::string& s) { try{ return std::stoi(s); }catch(...) { // return {}; return std::nullopt; } } std::optional to_int2(const std::string& s) { std::optional ret; // Boş try{ ret = std::stoi(s); }catch(...) { } return ret; } int main() { /* # OUTPUT # 42 yazisi, 42 degerindeki int tipine donusturuldu... 077 yazisi, 77 degerindeki int tipine donusturuldu... necati yazisi, int tipine donusturulemiyor... 0x33 yazisi, 0 degerindeki int tipine donusturuldu... */ for(auto s : { "42", "077", "necati", "0x33" }) { std::optional op = to_int(s); if(op) std::cout << s << " yazisi, " << *op << " degerindeki int tipine donusturuldu..." << std::endl; else std::cout << s << " yazisi, int tipine donusturulemiyor..." << std::endl; } return 0; } * Örnek 14, //.. class UserRecord{ public: UserRecord(const std::string& name, std::optional nick, std::optional age) : m_name(name), m_nick(nick), m_age(age){} friend std::ostream& operator<<(std::ostream& os, const UserRecord& ur) { os << ur.m_name; if(ur.m_nick) os << " \"" << *ur.m_nick << "\" "; if(ur.m_age) os << *ur.m_age << " yasinda."; return os; } private: std::string m_name; std::optional m_nick; std::optional m_age; }; int main() { /* # OUTPUT # Necati Ergin "Neco" 70 yasinda. Kagan Arslan */ UserRecord ur1 = {"Necati Ergin", "Neco", 70}; UserRecord ur2{ "Kagan Arslan", std::nullopt, std::nullopt }; std::cout << ur1 << std::endl; std::cout << ur2 << std::endl; return 0; } * Örnek 15, //.. std::optional get_middle_name(const std::string& s) { std::string s1, s2, s3; std::istringstream iss(s); iss >> s1 >> s2 >> s3; // 's3' eğer 'set' edilememiş ise isim-soyisim kombini var demektir. if(iss.fail()) return {}; return s2; } int main() { /* # OUTPUT # Middle name : Kandemir No middle name!!! */ std::string name{ "Ahmet Kandemir Pehlivanli" }; auto ox = get_middle_name(name); ox ? (std::cout << "Middle name : " << *ox << std::endl) : (std::cout << "No middle name!!!" << std::endl); name = "Necati Ergin"; ox = get_middle_name(name); ox ? (std::cout << "Middle name : " << *ox << std::endl) : (std::cout << "No middle name!!!" << std::endl); return 0; } >> 'std::variant' sınıfı: C dilindeki ve Cpp dilindeki 'union' türlerinin nesne yönelimli programlamaya uygun hale getirilmiş versiyonlarıdır. Bu sınıf türünden bir nesne, önceden belirlenmiş değerlerden birini tutmak zorundadır. * Örnek 1, //.. int main() { // 'std::variant' türden nesnemiz ya 'int' türden, ya 'double' türden ya da 'std::string' türden değişken tutacaktır. std::variant vx; vx = 12; // Artık 'int' türden bir değişken tutmaktadır. vx = 1.2; // Artık 'double' türden bir değişken tutmaktadır. vx = std::string{"Ahmo"}; // Artık "std::string" türden bir değişken tutmaktadır. // Pek tabii ilgili 'std::variant' sınıfın 'Ctor.' fonksiyonuna iş bu türleri argüman olarak geçerek // de yukarıdaki aynı şeyleri yapmasını sağlayabiliriz. return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # true false false false true false false false true */ std::variant vx; // 'std::variant' türden nesnemiz ya 'int' türden, ya 'double' türden ya da 'std::string' türden değişken tutacaktır. vx = 12; // Artık 'int' türden bir değişken tutmaktadır. std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << "\n\n" << std::endl; vx = 1.2; // Artık 'double' türden bir değişken tutmaktadır. std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << "\n\n" << std::endl; vx = std::string{"Ahmo"}; // Artık "std::string" türden bir değişken tutmaktadır. std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; std::cout << std::boolalpha << std::holds_alternative(vx) << std::endl; // Pek tabii ilgili 'std::variant' sınıfın 'Ctor.' fonksiyonuna iş bu türleri argüman // olarak geçerek de yukarıdaki aynı şeyleri yapmasını sağlayabiliriz. return 0; } * Örnek 3, //.. class A{ public: A() {std::cout << "A()" << std::endl;} A(int val) : mx{val} {std::cout << "A(" << val << ")" << std::endl;} ~A() {std::cout << "~A()" << std::endl;} friend std::ostream& operator<<(std::ostream& os, const A& other) { return os << "(A : " << other.mx << ")"; } private: int mx = 0; }; class B{ public: B() {std::cout << "B()" << std::endl;} B(int val) : mx{val} {std::cout << "B(" << val << ")" << std::endl;} ~B() {std::cout << "~B()" << std::endl;} friend std::ostream& operator<<(std::ostream& os, const B& other) { return os << "(B : " << other.mx << ")"; } private: int mx = 0; }; A ax(10); B bx(10); int main() { /* # OUTPUT # A(10) // global 'ax' nesnesi B(10) // global 'bx' nesnesi main basliyor... A turunden nesne tutuluyor: true B turunden nesne tutuluyor: false Tutulan degerin indeksi : 0, degeri : (A : 10) ~A() // global 'ax' nesnesi A turunden nesne tutuluyor: false B turunden nesne tutuluyor: true Tutulan degerin indeksi : 1, degeri : (B : 10) main bitiyor... ~B() // Oluşturulan geçici nesne, 'var_ab' başka türden değer tuttuğunda. ~B() // Oluşturulan geçici nesne, 'var_ab' ilk hayata geldiğinde. ~A() // global 'bx' nesnesi */ std::cout << "main basliyor..." << std::endl; std::cout << std::boolalpha << std::endl; std::variant var_ab(ax); std::cout << "A turunden nesne tutuluyor: " << std::holds_alternative(var_ab) << std::endl; std::cout << "B turunden nesne tutuluyor: " << std::holds_alternative(var_ab) << std::endl; std::cout << "Tutulan degerin indeksi : " << var_ab.index() << ", degeri : " << std::get<0>(var_ab) << std::endl; var_ab = bx; std::cout << "A turunden nesne tutuluyor: " << std::holds_alternative(var_ab) << std::endl; std::cout << "B turunden nesne tutuluyor: " << std::holds_alternative(var_ab) << std::endl; std::cout << "Tutulan degerin indeksi : " << var_ab.index() << ", degeri : " << std::get<1>(var_ab) << std::endl; std::cout << "main bitiyor..." << std::endl; return 0; } * Örnek 4, //.. int main() { /* # OUTPUT # main basliyor... Value : Hata yakalandi, Unexpected index Value : Hata yakalandi, Unexpected index Value : 23.75 degeri mevcuttur. main bitiyor... */ std::cout << "main basliyor..." << std::endl; std::variant vx{ 23.75 }; // Şu anda 'double' tutmaktadır. try{ // std::cout << "Value : " << std::get(vx) << "degeri mevcuttur." << std::endl; std::cout << "Value : " << std::get<0>(vx) << "degeri mevcuttur." << std::endl; } catch(const std::exception& ex) { std::cout << "Hata yakalandi, " << ex.what() << std::endl; } try{ // std::cout << "Value : " << std::get(vx) << "degeri mevcuttur." << std::endl; std::cout << "Value : " << std::get<1>(vx) << "degeri mevcuttur." << std::endl; } catch(const std::exception& ex) { std::cout << "Hata yakalandi, " << ex.what() << std::endl; } try{ // std::cout << "Value : " << std::get(vx) << "degeri mevcuttur." << std::endl; std::cout << "Value : " << std::get<2>(vx) << " degeri mevcuttur." << std::endl; } catch(const std::exception& ex) { std::cout << "Hata yakalandi, " << ex.what() << std::endl; } std::cout << "main bitiyor..." << std::endl; // Gördüğünüz gibi değer tutmayan diğer indekslere erişim hata fırlatılmasına neden oldu. return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # error: invalid use of incomplete type ‘struct std::variant_alternative<1, std::variant<> >’ */ std::cout << "main basliyor..." << std::endl; std::variant vx{ 23.75 }; // Şu anda 'double' tutmaktadır. // Geçersiz bir indeks geçilmesi durumunda sentaks hatası alıyoruz. std::cout << "Value : " << std::get<4>(vx) << std::endl; std::cout << "main bitiyor..." << std::endl; return 0; } * Örnek 6, //.. int main() { /* # OUTPUT # main basliyor... Age : 19 Age : 19 Age : 19 ----------------- Weight : 3.14 Weight : 3.14 Weight : 3.14 ----------------- Name : Ahmet Kahraman Name : Ahmet Kahraman Name : Ahmet Kahraman main bitiyor... */ std::cout << "main basliyor..." << std::endl; try{ using age_t = int; using weight_t = double; using name_t = std::string; std::variant va; enum : size_t { idx_age, idx_weight, idx_name }; // Arka planda tutulan veri tipi 'size_t' va = 19; std::cout << "Age : " << std::get<0>(va) << std::endl; std::cout << "Age : " << std::get(va) << std::endl; std::cout << "Age : " << std::get(va) << std::endl; std::cout << "-----------------" << std::endl; // va = 7u; // ambiguity tip sentaks hatası çünkü 'unsigned' türden 'int' ve 'double' // türlerine dönüşüm 'standar conversion'. va = 3.14; std::cout << "Weight : " << std::get<1>(va) << std::endl; std::cout << "Weight : " << std::get(va) << std::endl; std::cout << "Weight : " << std::get(va) << std::endl; std::cout << "-----------------" << std::endl; va = "Ahmet Kahraman"; std::cout << "Name : " << std::get<2>(va) << std::endl; std::cout << "Name : " << std::get(va) << std::endl; std::cout << "Name : " << std::get(va) << std::endl; }catch(const std::bad_variant_access& ex) { std::cout << "Hata yakalandi: " << ex.what() << std::endl; } std::cout << "main bitiyor..." << std::endl; return 0; } * Örnek 7, //.. int main() { /* # OUTPUT # main basliyor... double tür, degeri : 23.98 Hayir, int türden değil... Hayir, char türden değil... main bitiyor... */ std::cout << "main basliyor..." << std::endl; std::variant va(23.98); if( auto ptr = std::get_if(&va) /* auto ptr = std::get_if<2>(&va) */ ) { std::cout << "double tür, degeri : " << *ptr << std::endl; } else { std::cout << "Hayir, double türden degil..." << std::endl; } if( auto ptr = std::get_if(&va) /* auto ptr = std::get_if<1>(&va) */ ) { std::cout << "int tür, degeri : " << *ptr << std::endl; } else { std::cout << "Hayir, int türden değil..." << std::endl; } if( auto ptr = std::get_if(&va) /* auto ptr = std::get_if<3>(&va) */ ) { std::cout << "int char, degeri : " << *ptr << std::endl; } else { std::cout << "Hayir, char türden değil..." << std::endl; } std::cout << "main bitiyor..." << std::endl; return 0; } /*============================================================================================================*/ (41_14_02_2021) > Genel Hatırlatma: * Örnek 1, //.. class MYclass{ public: MYclass() = default; MYclass(MYclass&&) = delete; }; MYclass FOO1() { return MYclass{}; } // 'MYclass' türünden geçici nesne döndürülmekte. // İş bu sebepten dolayı burada 'copy-ellision' bir zorunluluktur, 'Return-Value Optimization' // Bu yüzden SENTAKS HATASI DEĞİLDİR. MYclass FOO11() { MYclass mx; return mx; } // 'MYclass' türünden yerel bir nesne döndürmekte. 'copy-ellision', DERLEYİCİYE BAĞLI. 'Named-Return-Value Optimization' // BURADA YİNE TAŞIMA SEMANTİĞİ DEVREYE GİRECEKTİK. DOLAYISIYLA 'mx' NESNESİNİ 'std::move()' İLE DÖNÜŞTÜRMEMİZE GEREK KALMIYOR. // HATTA BAZI YERLERDE 'std::move()' İLE DÖNÜŞTÜRMEK BİR TAKIM SAKINCALARA DA YOL AÇABİLMEKTE. MYclass FOO111() { MYclass mx; return std::move(mx); } // error: use of deleted function ‘constexpr MYclass::MYclass(const MYclass&)’ int main() { auto a = FOO1(); auto aa = FOO11(); auto aaa = FOO111(); return 0; } * Örnek 2, //.. std::unique_ptr func() { auto uptr = std::make_unique("necati"); return uptr; // BU ŞEKİLDE DÖNMEDE HİÇ BİR SAKINCA YOKTUR. 'moveable-only' değişkenleri böyle döndürmeye özen göstermeliyiz. // 'L-Value' to 'R-Value' DÖNÜŞÜMÜ MECBURİDİR. Fakat 'copy-ellision' ZORUNLU DEĞİL, derleyicilere bağlıdır. } int main() { /* # OUTPUT # */ auto a = func(); return 0; } * Örnek 3, //.. struct Base { void f(int) { std::cout << "1\n"; } }; struct Derived : Base { void f(double) { std::cout << "2\n"; } }; int main() { /* # OUTPUT # 2 */ Derived d; int i = 0; d.f(i); // i. Taban sınıf içerisindeki 'f' isimli fonksiyon 'virtual' değil. // ii. Her şeyin başı isim arama olduğundan, 'f' ismi ilk önce 'Derived' sınıfı içerisinde aranır ve bulunur. // iii. Kalıtım ise 'public' kalıtım çünkü 'struct' otomatik olarak 'public' kalıtım sağlamakta. // iv. Bütün bunların birleşmesi sonucunda da cevap '2'. return 0; } * Örnek 4, //.. int foo() { return 10; } struct foobar{ static int x; static int foo() { return 11; } }; int foobar::x = foo(); // 'static' veri elemanlarına ilk değer veren ifadelerde // ismi geçenler, eğer betimlenmemiş ise, ilk önce 'class-scope' içerisinde aranmaktadır. int main() { /* # OUTPUT # 11 */ std::cout << foobar::x << std::endl; return 0; } > C++ dilindeki 'vocabulary' types (devam): >> 'std::variant' sınıfı (devam): * Örnek 1, //.. class Data{ public: Data(int x) : mx{x}{} private: int mx; }; int main() { /* # OUTPUT # index : 0 variant is in monostate variant is in monostate index : 1 index : 2 index : 3 index : 0 */ // std::variant v1; // 'v1' nesnemiz için 'Default Ctor.' fonksiyonu çağrılacak ve birinci türden bir nesne tutmaya başlayacaktır. // Birinci türümüz 'Data' türünden olduğu için o sınıfın 'Default Ctor.' fonksiyonu çağrılacaktır. // Fakat ona dair bir 'Default Ctor.' OLMADIĞI İÇİN SENTAKS HATASI ALACAĞIZ. // error: use of deleted function ‘std::variant<_Types>::variant() [with _Types = {Data, int, double}]’ std::variant v2; // Yukarıdaki problemi çözmek için ilk tür olarak 'std::monostate' sınıf nesnesini kullanıyoruz. std::cout << "index : " << v2.index() << std::endl; if(std::holds_alternative(v2)) std::cout << "variant is in monostate" << std::endl; else std::cout << "variant is NOT in monostate" << std::endl; if(std::get_if(&v2)) std::cout << "variant is in monostate" << std::endl; else std::cout << "variant is NOT in monostate" << std::endl; v2 = Data{13}; std::cout << "index : " << v2.index() << std::endl; v2 = 23; std::cout << "index : " << v2.index() << std::endl; v2 = 4.5; std::cout << "index : " << v2.index() << std::endl; v2 = std::monostate{}; std::cout << "index : " << v2.index() << std::endl; return 0; } * Örnek 2, //.. int main() { /* # OUTPUT # [0] => 23 [1] => 2.3 [2] => AAAAA [3] => 12 Mayis 1987 Sali */ std::variant va; // va = 23; va.emplace<0>(23); std::cout << "[" << va.index() << "] => " << std::get(va) << std::endl; // va = 2.3; va.emplace<1>(2.3); std::cout << "[" << va.index() << "] => " << std::get<1>(va) << std::endl; // va = std::string(5, 'A'); // '()' çiftinin kullanılması gerekiyor. Aksi durumda 'std::initializer_list' argümanı çağrılıyor. va.emplace(5, 'A'); std::cout << "[" << va.index() << "] => " << std::get(va) << std::endl; // va = Date{12, 5, 1987}; va.emplace(12, 5, 1987); std::cout << "[" << va.index() << "] => " << std::get<3>(va) << std::endl; return 0; } * Örnek 3, //.. // Approach - I struct S{ void operator()(int x)const { std::cout << x << std::endl; } void operator()(char x)const { std::cout << x << std::endl; } void operator()(double x)const { std::cout << x << std::endl; } }; // Approach - II struct TS{ template void operator()(T x)const { std::cout << x << std::endl; } }; int main() { /* # OUTPUT # 12 12 12 12 */ std::variant var = 12; // An itibari ile 'int' türden bir değişken tutmakta. const auto f = [](auto x){ std::cout << x << std::endl; }; // 'generalized lambda-expression'. Derleyicinin yazacağı sınıfın // '.operator()()' fonksiyonu artık bir fonksiyon şablonu. std::visit(S{}, var); // İş bu 'std::visit' fonksiyonunun ilk parametresi bir 'callable' OLMAK ZORUNDA. // İkinci parametresi ise o 'callable' a gönderilecek 'std::variant' türden bir nesne. // Dolayısıyla bu çağrıda 'S' türünden geçici nesne kullanarak, 'var' içinde hangi tür varsa, // 'S' sınıfının o tür parametreli '.operator()()' fonksiyonuna çağrı yapılmıştır. // Dolayısıyla 'std::variant' şablon parametreleri kadarlık 'overload', '.operator()()' // fonksiyonu için YAZILMAK ZORUNDA OLABİLİR. std::visit(TS{}, var); // Artık 'TS' sınıfından oluşturulan geçici nesne kullanarak, o sınıfın '.operator()()' // fonksiyonuna çağrı yapılmıştır. Artık hangi tür 'std::variant' içerisindeyse, o türe // dair '.operator()()' açılacaktır. std::visit([](auto x){ std::cout << x << std::endl; }, var); // İlk parametre olarak bir 'lambda-expression' kullanıldı. std::visit(f, var); // İlk parametre olarak bir 'lambda-expression' kullanıldı. return 0; } * Örnek 4, Kapalı Hiyerarşi kalıtıma bir alternatiftir. Kapalı Hiyerarşi, kalıtımdaki sınıfların önceden belli olması ve sonrasında ise değişmeyeceğinin kesin olması durumudur. //.. class Cat{ public: Cat(std::string name) : m_name{ std::move(name) } {} // Bu şekilde veri elemanlarımıza ilk değer verirken geçici nesne kullanmamız durumunda // 'copy-ellision' gerçekleşmektedir. Eğer 'R-value expression' kullanırsak da // taşıma semantiği devreye girecektir. Artık Modern Cpp ile 'std::string' sınıfı // için bu şekil populer hale gelmeye başlamıştır. // Cat(const std::string& name) : m_name{name} {} // 'old-school' ilk değer verme yöntemi olup hala geçerliliğini korumaktadır. void meow(){ std::cout << m_name << " is meowing..." << std::endl; } friend std::ostream& operator<<(std::ostream& os, Cat other) { other.meow(); return os; } private: std::string m_name; }; class Dog{ public: Dog(std::string name) : m_name{ std::move(name) } {} void woof(){ std::cout << m_name << " is barking..." << std::endl; } friend std::ostream& operator<<(std::ostream& os, Dog other) { other.woof(); return os; } private: std::string m_name; }; class Lamb{ public: Lamb(std::string name) : m_name{ std::move(name) } {} void bleat(){ std::cout << m_name << " is bleating..." << std::endl; } friend std::ostream& operator<<(std::ostream& os, Lamb other) { other.bleat(); return os; } private: std::string m_name; }; using Animal = std::variant; struct AnimalSpeaker{ void operator()(Cat& _animal) { _animal.meow(); } void operator()(Dog& _animal) { _animal.woof(); } void operator()(Lamb& _animal) { _animal.bleat(); } }; template bool is_type(const Animal& animal) { return std::holds_alternative(animal); } int main() { /* # OUTPUT # 2 adet kedi, 3 adet kopek, 1 adet kuzu bulunmaktadir. ------------------------------- Minnos is meowing... Karabas is barking... Kont is barking... Luna is meowing... Whisky is bleating... Pumy is barking... ------------------------------- Minnos is meowing... Karabas is barking... Kont is barking... Luna is meowing... Whisky is bleating... Pumy is barking... ------------------------------- Minnos is meowing... Karabas is barking... Kont is barking... Luna is meowing... Whisky is bleating... Pumy is barking... ------------------------------- Minnos is meowing... Karabas is barking... Kont is barking... Luna is meowing... Whisky is bleating... Pumy is barking... */ std::vector animalFarm{ Cat{"Minnos"}, Dog{"Karabas"}, Dog{"Kont"}, Cat{"Luna"}, Lamb{"Whisky"}, Dog{"Pumy"} }; std::cout << std::count_if(animalFarm.begin(), animalFarm.end(), is_type) << " adet kedi, " << std::count_if(animalFarm.begin(), animalFarm.end(), is_type) << " adet kopek, " << std::count_if(animalFarm.begin(), animalFarm.end(), is_type) << " adet kuzu bulunmaktadir.\n"; std::cout << "-------------------------------\n"; // Approach - I for(auto& animal : animalFarm) // Her bir 'animal' aslında 'std::variant<>' sınıfıdır. { if( animal.index() == 0 ) std::get<0>(animal).meow(); else if( animal.index() == 1 ) std::get<1>(animal).woof(); else if( animal.index() == 2 ) std::get<2>(animal).bleat(); } std::cout << "-------------------------------\n"; // Approach - II for(auto& animal : animalFarm) { if( auto p = std::get_if<0>(&animal) ) p->meow(); if( auto p = std::get_if<1>(&animal) ) p->woof(); if( auto p = std::get_if<2>(&animal) ) p->bleat(); } std::cout << "-------------------------------\n"; // Approach - III for(auto& animal : animalFarm) std::visit(AnimalSpeaker{}, animal); std::cout << "-------------------------------\n"; // Approach - IV for(auto& animal : animalFarm) std::visit([](auto& _animal){ std::cout << _animal; }, animal); return 0; } > 'std::string_view' sınıfı : Normal şartlarda fonksiyonlarımızın parametresi 'const' türden birer referanslardır eğer ilgili yazıyız sadece okuma amacıyla alıyorsak. Fakat bu durum zaman zaman ekstra maliyetli olmaktadır. Aşağıdaki örneği inceleyelim; * Örnek 1, //.. void func(const std::string& other) {/*...*/} int main() { func("Ahmet"); // İlgili fonksiyonumuzun aldığı argüman 'const char[6]' türünden. Fakat fonksiyonumuz 'std::string' // türden argüman beklemekte. Dolayısıyla 'std::string' türünden geçici bir nesne oluşturulacak ki // değeri "Ahmet" yazısı, ve fonksiyonumuzun bloğu içerisinde 'other' isimli referans aslında bu geçici // nesneye referans olacaktır. 'std::string' türünden geçici nesne oluşturulduğu için maliyeti arttırıyor. /* Buradan hareketle diyebiliriz ki; i. OKUMA AMAÇLI BİR FONKSİYON KULLANACAKSAK, PARAMETRE TÜRÜ 'std::string_view' OLMASI, YUKARIDAKİ GEREKSİZ MALİYETTEN KAÇINMAMIZA OLANAK SAĞLAYACAKTIR. Çünkü 'std::string_view' sınıfımız içerisinde ya iki tane 'const char*' türünden gösterici ya da bir adet 'const char*' türünden göstericiyle bir adet 'size_t' türünden değişken vardor. Böylelikle 'pointer-aritmatich' kullanarak okuma amacıyla yazımızı üzerinde gezinebiliyoruz. ii. 'std::string_view' içerisindeki göstericiler 'dangling-pointer' olabilir veya 'null-terminated-string' GÖSTERMİYOR OLABİLİR. Bunun sorumluluğu 'std::string_view' sınıfını kullanan bizlerde. DOLAYISIYLA DİKKATLİ OLMALIYIZ. iii. 'std::string_view' sınıfı içerisinde sadece ve sadece 'std::string' sınıfının OKUMA AMACI GÜDEN ÜYE FONKSİYONLARI VARDIR. */ } * Örnek 2, //.. int main() { /* # OUTPUT # Ahmet -------------------------- Ahmet -------------------------- Ahmet -------------------------- */ std::array nameArray{ 'A', 'h', 'm', 'e', 't' }; for (auto i : nameArray) std::cout << i; std::cout << "\n--------------------------" << std::endl; std::vector nameVector{ 'A', 'h', 'm', 'e', 't' }; for (auto i : nameVector) std::cout << i; std::cout << "\n--------------------------" << std::endl; char nameCarray[6] = { 'A', 'h', 'm', 'e', 't', '\0' }; for(size_t i = 0; i < sizeof(nameCarray) / sizeof(nameCarray[0]); ++i) std::cout << nameCarray[i]; std::cout << "\n--------------------------" << std::endl; return 0; } * Örnek 3, //.. int main() { /* # OUTPUT # std::string => 32 std::string_view => 16 */ std::cout << "std::string => " << sizeof(std::string) << std::endl; std::cout << "std::string_view => " << sizeof(std::string_view) << std::endl; return 0; } * Örnek 4, //.. int main() { char str[] = "Ahmet Kandemir Pehlivanli"; std::string s { "Cpp ogrenmek istiyor." }; std::array ar{ 'a', 'm', 'a', ' ', 'n', 'a', 's', 'i', 'l', '?' }; std::string_view sw1; std::cout << "[" << sw1 << "]" << std::endl; // OUTPUT => [] std::string_view sw2 = "Necati Ergin"; std::cout << "[" << sw2 << "]" << std::endl; // OUTPUT => [Necati Ergin] std::string_view sw3{ str, 6 }; std::cout << "[" << sw3 << "]" << std::endl; // OUTPUT => [Ahmet ] std::string_view sw4{ s }; std::cout << "[" << sw4 << "]" << std::endl; // OUTPUT => [Cpp ogrenmek istiyor.] std::string_view sw5{ s.data() + 13, 8 }; std::cout << "[" << sw5 << "]" << std::endl; // OUTPUT => [istiyor.] std::string_view sw6{ ar.data(), std::size(ar) }; // C++17 ile birlikte 'size' büyüklüğü olan her şeyi, 'std::size()' fonksiyonuna geçebiliriz. std::cout << "[" << sw6 << "]" << std::endl; // OUTPUT => [ama nasil?] return 0; } * Örnek 5, //.. int main() { /* # OUTPUT # true true true false false false */ std::cout << std::boolalpha; std::string_view sw; std::cout << ( sw.data() == nullptr ) << std::endl; std::cout << sw.empty() << std::endl; std::cout << ( sw.size() == 0 ) << std::endl; std::cout << "\n" << std::endl; sw = "Ahmet"; std::cout << ( sw.data() == nullptr ) << std::endl; std::cout << sw.empty() << std::endl; std::cout << ( sw.size() == 0 ) << std::endl; return 0; } * Örnek 6, //.. class Person{ public: Person(std::string name) : m_name(std::move(name)){} std::string_view getName() const { return m_name; // BUNU YAPMAMALIYIZ. } private: std::string m_name; }; Person createPerson() { return Person{"Ahmet Kandemir Pehlivanli"}; } int main() { /* # OUTPUT # �z�*Vehlivanli Ahmet Kandemir Pehlivanli */ // Approach - I auto sw = createPerson().getName(); // ^ ^ // | Oluiturulan geçici nesnenin '*this' i kullanılmıştır. // Burada geçici nesne oluşturulmuştur. // Artık geçici nesnenin hayatı burada biteceği için, 'sw' içerisindeki göstericiler 'dangling-pointer' haline gelmiştir. std::cout << sw << std::endl; // Approach - II Person personOne{ createPerson() }; // Artık geçici nesne değil, otomatik ömürlü bir nesne oluşturduk. auto swTwo = personOne.getName(); // İş bu otomatik ömürlü nesnemiz hayatta olduğu süre boyunca 'swTwo' içerisindeki göstericiler 'valid'. std::cout << swTwo << std::endl; // ÇIKTIDA DA GÖRÜLDÜĞÜ ÜZERE GERİ DÖNÜŞ DEĞERİNİ 'std::string_view' YAPMAMIZ BİR TAKIM SORUNLARA YOL AÇMAKTADIR. return 0; } * Örnek 7, //.. std::string getString(int ival) { return std::to_string(ival); } int main() { /* # OUTPUT # Tam sayi : 31133113 s2 : 31133113 s3 : 31133113 s4 : 31133113 *p1 : 3 *p2 : 3 */ int ival = 31133113; std::cout << "Tam sayi : " << ival << std::endl; // auto& s1 = getString(ival); // error: cannot bind non-const lvalue reference of type ‘std::__cxx11::basic_string&’ to an // rvalue of type ‘std::string’ {aka ‘std::__cxx11::basic_string’} const auto& s2 = getString(ival); std::cout << "s2 : " << s2 << std::endl; // Burada ilgili fonksiyonumuzun döndürdüğü geçici nesnenin hayatı uzatılmıştır. 'life-extension' auto&& s3 = getString(ival); std::cout << "s3 : " << s3 << std::endl; // Burada ilgili fonksiyonumuzun döndürdüğü geçici nesnenin hayatı uzatılmıştır. 'life-extension' std::string_view s4 = getString(ival); std::cout << "s4 : " << s4 << std::endl; // 'Tanımsız Davranış'. 's4' içerisindeki göstericiler 'dangling-pointer' haline gelmekteler. // Çünkü ilgili fonksiyonumuzun döndürdüğü geçici nesnenin ömrü bitmiştir. const char* p1 = getString(ival).c_str(); std::cout << "*p1 : " << *p1 << std::endl; // 'Tanımsız Davranış'. 'p1' artık 'dangling-pointer' haline gelmekte. // Çünkü ilgili fonksiyonumuzun döndürdüğü geçici nesnenin ömrü bitmiştir. auto p2 = getString(ival).c_str(); std::cout << "*p2 : " << *p2 << std::endl; // 'Tanımsız Davranış'. 'p2' artık 'dangling-pointer' haline gelmekte. // Çünkü ilgili fonksiyonumuzun döndürdüğü geçici nesnenin ömrü bitmiştir. return 0; } * Örnek 8, //.. class BigData{ public: char* getBuffer() { return buffer; } private: char buffer[5'000'000]; }; int main() { /* # OUTPUT # Char : 1 Char* : 8 BigData : 5000000 std::string : 32 std::string_view : 16 */ std::cout << "Char : " << sizeof(char) << std::endl; std::cout << "Char* : " << sizeof(char*) << std::endl; BigData dataOne; std::cout << "BigData : " << sizeof(dataOne) << std::endl; std::string stringOne{ dataOne.getBuffer() }; std::cout << "std::string : " << sizeof(stringOne) << std::endl; std::string_view stringViewOne{ dataOne.getBuffer() }; std::cout << "std::string_view : " << sizeof(stringViewOne) << std::endl; return 0; } * Örnek 9, //.. void func(std::string_view sw) { // i. 'std::string_view' sınıfının gösterdiği yazının 'null-terminated string' OLMA ZORUNLULUĞU YOKTUR. const char* p = sw.data(); // ii. Fakat 'std::strlen()' fonksiyonuna 'null-terminated string' geçilmediği durumlarda // 'Tanımsız Davranış' meydana gelecektir. auto n = std::strlen(p); std::cout << "[" << n << "] => " << p << std::endl; } int main() { /* # OUTPUT # [25] => Ahmet Kandemir Pehlivanli */ func("Ahmet Kandemir Pehlivanli"); // LEGAL. Çünkü yazının sonunda 'null' karakteri vardır. func(std::string_view{}); // Burada herhangi bir 'null' karakter mevcut değil. std::array nameArray{ 'A', 'h', 'm', 'e', 't' }; // Burada herhangi bir 'null' karakter mevcut değil. func( std::string_view( nameArray.data(), 5 )); return 0; } * Örnek 10, //.. int main() { /* # OUTPUT # ------------------------------- [ , basimda bir bosluk hissi var...] [ , basimda bir bosluk hissi var...] ------------------------------- [ , basimda bir bosluk hissi var...] [bosluk hissi var...] ------------------------------- [ , basimda bir bosluk hissi var...] [ , basimda bir bosluk hissi var...] ------------------------------- [ , basimda bir bosluk hissi var...] [, basimda bir bosluk hissi var...] */ std::cout << "-------------------------------" << std::endl; std::string str{ " , basimda bir bosluk hissi var..." }; std::string_view strView{ str }; std::cout << "[" << str << "]" << std::endl; std::cout << "[" << strView << "]" << std::endl; std::cout << "-------------------------------" << std::endl; strView.remove_prefix(15); std::cout << "[" << str << "]" << std::endl; std::cout << "[" << strView << "]" << std::endl; std::cout << "-------------------------------" << std::endl; strView.remove_prefix(-15); std::cout << "[" << str << "]" << std::endl; std::cout << "[" << strView << "]" << std::endl; std::cout << "-------------------------------" << std::endl; strView.remove_prefix( std::min( strView.find_first_not_of(" "), strView.size() ) ); // i. İlgili '.find_first_not_of()' fonksiyonu boşluk karakteri olmayan ilk karakterin // indeksini döndürecektir. Eğer bütün karakterler boşluk karakteriyse, 'std::string::npos' // değerini döndürecektir. // ii. 'std::min()' ise argüman olarak aldığı ifadelerden en küçüğünü döndürecektir. std::cout << "[" << str << "]" << std::endl; std::cout << "[" << strView << "]" << std::endl; // '.remove_prefix()' işlevi görüntülemenin aralığını daraltmak içindir. Yani arka plandaki // göstericilerden, yazının başını göstereni ötelemektedir. return 0; } * Örnek 11, //.. int main() { /* # OUTPUT # ar : [abcd] sw : [abcd] @trim_index : 4 Our string: [abcd], uzunlugu : (7) Our string: [abcd], uzunlugu : (4) */ char ar[] = { 'a', 'b', 'c', 'd', '\0', '\0', '\0' }; std::cout << "ar : " << "[" << ar << "]" << std::endl; std::string_view sw(ar, sizeof ar); // 'sizeof()' işlevinin operandı bir ifade ise '()' zorunlu DEĞİL. std::cout << "sw : " << "[" << sw << "]" << std::endl; auto trim_index = sw.find('\0'); // '\0' karakterini bulduğunda, indeksini döndürmektedir. std::cout << "@trim_index : " << trim_index << std::endl; if( trim_index != std::string_view::npos ) // Eğer '\0' karakteri bulunamasaydı 'std::string::npos' değeri döndürülecekti. // sw.remove_suffix(3); sw.remove_suffix( sw.size() - trim_index ); // Yazının sonunu gösteren gösterici ötelenmiş oldu. std::cout << "Our string: [" << ar << "], uzunlugu : (" << sizeof(ar) << ")" << std::endl; std::cout << "Our string: [" << sw << "], uzunlugu : (" << sw.size() << ")" << std::endl; // '.remove_suffix()' işlevi görüntülemenin aralığını daraltmak içindir. // Yani arka plandaki göstericilerden, yazının sonunu göstereni ötelemektedir. return 0; } * Örnek 12, //.. int main() { /* # OUTPUT # [] [necati] [necati] [necati] */ std::string_view sw; std::string str{"necati"}; std::cout << "[" << sw << "]" << std::endl; std::cout << "[" << str << "]" << std::endl; std::cout << "\n" << std::endl; // sw = str; sw = str.operator std::string_view(); // sw = str.operator std::basic_string_view>(); std::cout << "[" << sw << "]" << std::endl; std::cout << "[" << str << "]" << std::endl; std::cout << "\n" << std::endl; // 'std::string' sınıfının 'std::string_view' sınıfına otomatik dönüşümü gerçekleştirecek ki // ilgili 'Ctor.' fonksiyonunun 'explicit' olmadığı anlaşılmaktadır, '.operator string_view()' // tür dönüştürme operatör fonksiyonu vardır. // Yukarıdaki senaryonun tam tersinde ise '.operator=()' fonksiyonu çağrılmaktadır. /* # ÖZETLE # (aşağıdaki denklem tam tersi de olabilir, teyit edilmelidir.) std::string => std::string_view : '.operator std::string_view()' std::string_view => std::string : '.operator=' */ return 0; } * Örnek 13, //.. int main() { /* # OUTPUT # A7_c NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE St17basic_string_viewIcSt11char_traitsIcEE */ using namespace std::string_literals; using namespace std::string_view_literals; std::cout << typeid("alican").name() << std::endl; std::cout << typeid("alican"s).name() << std::endl; std::cout << typeid("alican"sv).name() << std::endl; return 0; } /*============================================================================================================*/ (42_21_02_2020) > "CppQuiz.org" sitesindeki örnekler, (prularsight) isimli internet sitesindeki cpp soruları da güzeldir. * Örnek 1, Kolay seviye zorluk soruları: //.. void f(int& a, const int& b) { std::cout << b; // 'b' demek 'x' demektir. a = 1; // 'a' demek de 'x' demektir. std::cout << b; } int main() { /* # OUTPUT # 01 */ int x = 0; f(x, x); } * Örnek 2, //.. int main() { /* # OUTPUT # 0 */ static int a; // 'static' ömürlü değişkenler 'default init.' edilmeleri durumunda ilk önce 'zero-init.' edilirler. std::cout << a: } * Örnek 3, //.. class A { int foo = 0; public: int& getFoo() { return foo; } void printFoo() const { std::cout << foo; } }; int main() { A a; auto bar = a.getFoo(); // '&' deklaratörü kullanılmadığından, referanslık düşmektedir. ++bar; a.printFoo(); // OUTPUT => 0 } * Örnek 4, //.. class A { public: A() { std::cout << 'a'; } // ii. Dolayısıyla bu fonksiyon çağrılacak. ~A() { std::cout << 'A'; } // v. Son olarak bu nesnenin hayatı bitecek. }; class B { public: B() { std::cout << 'b'; } // iii. Sonrasında bu nesne hayata gelecek. ~B() { std::cout << 'B'; } // iv. En son bu nesne hayata geldiği için ilk önce bu nesnenin hayatı bitecek. A a; // i. İlk önce bu nesne hayata gelecek. }; int main() { B b; // OUTPUT => abBA } * Örnek 5, //.. void f(int) { std::cout << 1; } void f(unsigned) { std::cout << 2; } int main() { f(-2.5); // 'double' to 'int' : 'standart conversion' // 'double' to 'unsigned' : 'standart conversion' // Syntax Error : error: call of overloaded ‘f(double)’ is ambiguous } * Örnek 6, //.. int main() { int a = 10; int b = 20; int x; x = a, b; // '=' operatörünün önceliği ',' operatörünün önceliğinden yüksek olduğu için, std::cout << x; // OUTPUT => 10 std::cout << std::endl; x = (a,b); std::cout << x; // OUTPUT => 20 } * Örnek 7, //.. int main() { std::cout << sizeof(""); // Argüman olan ifadenin türü 'const char[1]'. Fakat değeri hesaplanmayacağı için // 'array-decay' gerçekleşmeyecektir. // OUTPUT => 1 } * Örnek 8, //.. class A { public: virtual void f() { std::cout << "A"; } }; class B : public A { public: void f() { std::cout << "B"; } }; void g(A a) { a.f(); } // 'g' fonksiyonunun parametresi 'pointer' DEĞİLDİR. void gg(A& a) { a.f(); } int main() { B b; g(b); // OUTPUT => A // İsim arama kuralları gereği, 'g()' içindeki 'f()' fonksiyonu, 'A' sınıfında aranmıştır. // 'g()' fonksiyonu 'pointer'/'reference' ile argüman almadığı için 'virtual-dispatch' söz konusu da değildir. // 'object-slicing' devreye girmiştir. 'b' içerisindeki 'a', fonksiyona argüman olmuştur. gg(b); // OUTPUT => B // 'pointer' kullandığımız için 'virtual-dispatch' devreye girmiştir. } * Örnek 9, //.. void f(char* &&) { std::cout << 1; } // 'R-value reference' to 'char*' void f(char* &) { std::cout << 2; } // 'L-value reference' to 'char*' int main() { char c = 'a'; f(&c); // 'PR-value' expression. OUTPUT => 1 } * Örnek 10, //.. using namespace std; bool default_constructed = false; bool constructed = false; bool assigned = false; class C { public: C() { default_constructed = true; } C(int) { constructed = true; } C& operator=(const C&) { assigned = true; return *this; } }; int main() { map m; // i. Boş bir 'std::map' nesnesi hayata gelmekte. m[7] = C(1); // ii. Dolayısıyla bir 'std::pair<>' hayata getirilmekte ki bunun 'first' isimli değişkeni '7' ile // 'second' isimli değişkeni de 'C' nesnesi. Fakat 'default init.' edilmiştir. // iii. Sonrasında geçici 'C' nesnesi hayata gelmekte. // iv. Son olarak da geçici bu nesne 'default init.' edilen 'C' nesnesine atanmıştır. cout << default_constructed << constructed << assigned; // 111 }; * Örnek 11, //.. struct A { A() { std::cout << "A"; } // v. Dolayısıyla bu fonksiyon çağrıldı. }; struct B { B() { std::cout << "B"; } // iii. Bundan dolayı da fu fonksiyon çağrıldı. }; class C { public: C() : a(), b() {} private: B b; // ii. Bildirimdeki sıra ile hayata ilk bu geldi. A a; // iv. Sonrasında sıra bunun hayata gelmesinde geldi. }; int main() { C(); // i. Geçici 'C' türünden bir nesne oluşturuldu. // OUTPUT => BA } * Örnek 12, //.. struct GeneralException{ virtual void print() { std::cout << "G"; } }; struct SpecialException : public GeneralException { void print() override { std::cout << "S"; } }; void f() { throw SpecialException(); } int main() { try { f(); } catch (GeneralException e) { e.print(); // 'reference' semantiği kullanılmadığı için 'virtual dispatch' devreye girmeyecektir. // 'object-slicing' meydana gelmiştir. // OUTPUT => G } } * Örnek 13, //.. int main() { int a = 0; decltype((a)) b = a; // 'b' demek 'a' demek. Demek çünkü '(a)' bir 'L-Value Expression'. // int& b = a; b++; std::cout << a << b; // OUTPUT => 11 // decltype(isim) => 'isim' hangi tür ise 'decltype(isim)' de o tür. Bir nevi 'auto' şeklindeki tür çıkarımı gibi. // decltype(ifade) => 'ifade' eğer 'L-Value Expression' ise 'decltype(ifade)' o türe referans. // eğer 'PR-Value Expression' ise 'decltype(ifade)' o türün kendisi. // eğer 'X-Value Expression' ise 'decltype(ifade)' o türün sağ taraf referansı. } * Örnek 14, //.. class A { public: void f() { std::cout << "A"; } }; class B : public A { public: void f() { std::cout << "B"; } }; void g(A& a) { a.f(); } int main() { B b; g(b); // OUTPUT => A // Dolayısıyla isim arama kuralları gereği, 'g()' içindeki 'f' fonksiyon ismi, 'A' sınıfında aranmıştır. // Taban sınıfımız içerisindeki 'f()' fonksiyonu, 'virtual' olmadığı için, 'virtual-dispatch' mekanizmasından söz edilemez. // 'object-slicing' devreye girmiştir. 'b' içerisindeki 'a', fonksiyona argüman olmuştur. } * Örnek 15, //.. int f(int& a, int& b) { a = 3; // i. Buradaki 'a', 'main' içerisindeki 'a' demektir. 'main' içerisindeki 'a' artık '3' değerinde. b = 4; // ii. Buradaki 'b', 'main' içerisindeki 'a' demektir. 'main' içerisindeki 'a' artık '4' değerinde. return a + b; // iii. '8' değeri geri döndürülecektir. } int main() { int a = 1; int b = 2; int c = f(a, a); // iv. 'c' artık '8' değerindedir. std::cout << a << b << c; // OUTPUT => 428 } * Örnek 16, //.. void f(int& a, const int& b) { std::cout << b; // i. 'b' demek 'x' demektir. a = 1; // ii. 'a' demek de 'x' demektir. std::cout << b; // iii. 'a' vesilesiyle 'x' in değeri değişmiştir. } int main() { int x = 0; f(x, x); // OUTPUT => 01 } * Örnek 17, //.. class A { public: virtual void f() { std::cout << "A"; } // iii. Fakat ilgili 'f()' fonksiyonumuz 'virtual' olduğundan ve 'g()' fonksiyonu da // referans/gösterici yoluyla argüman aldığından 'virtual-dispatch' devreye girecek. }; class B : public A { private: void f() { std::cout << "B"; } // iv. Günün sonunda da bu fonksiyon çağrılacaktır. }; void g(A& a) { a.f(); } // ii. Dolayısıyla 'f()' için isim arama 'A' sınıfı içerisinde yapılacak ve bulunacak. int main() { B b; g(b); // i. İlgili 'g()' fonksiyonumuz referans yoluyla argüman almaktadır. // # SIRALAMA # // 1. 'name-lookup' => Derleme zamanı ile ilgili. // 2. 'context-control' => Derleme zamanı ile ilgili. // 3. 'access-control' => Derleme zamanı ile ilgili. // OUTPUT => B // NOT: İsim arama derleme aşamasıyla ilgili olup, 'virtual-dispatch' çalışma zamanıyla ilgilidir. // Dolayısıyla isim arama sırasında ilgili 'f()' fonksiyonumuz 'A' sınıfında arandığı ve bulunduğu için // sonlanmıştır. Artık bu aşamadan sonra isim arama ile ilgili kurallar geride kalmıştır. Bu yaklaşım ile // bir sınıfın 'private' bölümündeki fonksiyonları çağırabildiğimizi görmüş olduk. // NOT-2: Taban sınıfın sanal veya sap-sanal fonksiyonları, ilgili sınıfın 'private', 'protected' ve 'public' // kısımlarında olabilir. Bu, iş bu fonksiyonları 'override' EDEMEYECEĞİM anlamına gelmemektedir. } * Örnek 18, //.. class A { public: A() { std::cout << "a"; } ~A() { std::cout << "A"; } }; class B { public: B() { std::cout << "b"; } ~B() { std::cout << "B"; } }; class C { public: C() { std::cout << "c"; } ~C() { std::cout << "C"; } }; A a; // i. İlk önce bu nesne hayata gelecektir. int main() { C c; // ii. Sonrasında bu nesne hayata gelecektir. B b; // iii. Devamında da bu nesne hayata gelecektir. // iv. En son 'b' nesnesi hayata geldiğinden, ilk onun hayatı bitecektir. // v. Ondan sonra 'c' nesnesinin hayatı bitecektir. // vi. En son olarak da 'a' nesnesinin hayatı bitecektir. // OUTPUT => acbBCA } * Örnek 19, //.. struct C { C() : i(1) {} int i; }; struct D { D() : i(2) {} int i; }; int main() { const std::variant v; // i. 'default init.' edildiğinden şu an 'C' sınıfından bir veri tutmaktadır. 'i' değişkeninin değeri '1'. std::visit([](const auto& value) { std::cout << value.i; }, v); // OUTPUT => 1 } * Örnek 20, //.. int main() { int i = 42; int j = 1; std::cout << i / --j; // 'Tanımsız Davranış'. Çünkü ilk olarak 'j' değişkeninin değeri bir azaltılacak. Bu sefer de sıfıra bölme // ile karşılaşacağız. İşaretli sayılarda sıfıra bölme 'Tanımsız Davranış' } * Örnek 21, //.. int j = 1; int main() { // i. Virgüller ile ayrılan bildirimlerde gösterici veya referans niteleyicisi sadece ilk değişken için geçerlidir. int& i = j, j; // int& i = j; // int j; // ii. Artık buradaki 'j', 'main' içerisindeki 'j'. Global isim alanındaki 'j' maskelenmiştir. j=2; std::cout << i << j; // OUTPUT => 12 } * Örnek 22, Orta seviye zorluk soruları: //.. namespace x { class C {}; void f(const C& i) { std::cout << "1"; } } namespace y { void f(const x::C& i) { std::cout << "2"; } } int main() { f(x::C()); // OUTPUT => 1 // Fonksiyonun argümanı 'x' isim alanında bildirildiğinden, onu nitelememiz gerekiyor ki 'C' sınıf // türünden geçici bir nesne oluşturabilelim. 'ADL' mekanizmasından dolayı da fonksiyonun argümanlarının // bulunduğu isim alanında, fonksiyonun ismi aranmaktadır. Dolayısıyla 'x' isim alanında 'f' ismi de aranacaktır. // İş bu sebepten dolayı ekrana '1' yazılmıştır. } * Örnek 23, //.. namespace x { class C {}; void f(const C& i) { std::cout << "1"; } } namespace { void f(const x::C& i) { std::cout << "2"; } } int main() { f(x::C()); // OUTPUT => error: call of overloaded ‘f(x::C)’ is ambiguous // İsimsiz isim alanı içerisinde tanımlanan 'f' fonksiyonu aslında C dilindeki 'static' global fonksiyonlardır. // Yani 'internal-linkage' durumundakiler. 'x' isim alanı içerisinde tanımlanan 'f' fonksiyonu ise yine aynı isim // alanında tanımlanan 'C' sınıf türünden bir değişken almaktadır. Bu durumda hem 'ADL' mekanizması vesilesiyle 'x' // isim alanında hem de global isim alanında 'f' ismi bulunacak. Başka bir seçim kriter olmadığı için, bundan dolayı // da 'ambiguous' tip sentaks hatası alacağız. Burada hem normal olarak hem de ADL ile birlikte isim aranmaktadır. // Birbirlerine karşı öncelikleri yoktur, arama için. } * Örnek 24, //.. int i; // i. 'i' değişkeninin değeri şu an '0'. void f(int x) { std::cout << x << i; // iv. Burada 'x' değişkeni '3', 'i' ise '4' değerine sahiptir. } int main() { i = 3; // ii. 'i' değişkeninin değeri artık '3' oldu. f(i++); // iii. Fonksiyona argüman olarak '3' değeri gönderildi. Fonksiyon çağrısı olduğundan yan etki gerçekleşecek ve // 'i' değişkeni '4' değerini alacak. // OUTPUT => 34 } * Örnek 25, //.. struct S { int one; int two; int three; }; int main() { S s{ 1,2 }; std::cout << s.one; std::cout << s.two; std::cout << s.three; // OUTPUT => 120 // İlgili 'S' türümüz 'aggregate' tür olduğundan; // 'one' isimli değişkeni '1' değeri ile, // 'two' isimli değişken '2' değeri ile, // 'three' ise '0' değeri ile hayata gelmiştir. // Aggregate: An aggregate is an array or a class (clause 9) with // no user-declared constructors (12.1), // no private or protected non-static data members (clause 11), // no base classes (clause 10), and no virtual functions (10.3). } * Örnek 26, //.. int main() { std::cout << std::boolalpha << std::is_signed::value; // OUTPUT => true // 'unsigned char', 'signed char' ve 'char' türleri birbirinden farklı türler olup; // 'unsigned char' türü işaretsiz, // 'signed char' türü işaretli, // 'char' türününü işaret bilgisi ise 'implementation defined' şeklindedir. Yani bir nevi derleyiciye bağlıdır. } * Örnek 27, //.. struct X { X() { std::cout << "1"; } X(X&) { std::cout << "2"; } X(const X&) { std::cout << "3"; } X(X&&) { std::cout << "4"; } ~X() { std::cout << "5"; } }; struct Y { mutable X x; Y() = default; Y(const Y&) = default; }; int main() { Y yOne; Y yTwo = std::move(yOne); // OUTPU => 1255 // i. 'yOne' için 'Ctor.' fonksiyonu çağrılacaktır. Bu da öncesinde veri elemanı olan 'x' değişkenine ait // 'Ctor.' fonksiyonunu çağıracaktır. // Bu işlem sonucunda ekrana '1' yazılacaktır. // ii. Daha sonra 'std::move()' çağrısı ile 'yOne' değişkeninin türü 'R-value' türüne dönüştürülecektir. // iii. Dolayısıyla 'yTwo' için 'Move Ctor.' fonksiyonu çağrılacaktır fakat OLMADIĞI İÇİN ONUN YERİNE sınıfın // 'Copy Ctor.' fonksiyonu çağrılacaktır. Fakat ilgili 'Copy Ctor.' fonksiyonunu derleyicinin yazdığını da unutmayalım. // Derleyici bu çağrıdan hareketle, 'X' sınıfı için de 'Copy Ctor.' fonksiyonunu çağıracaktır. İki adet 'Copy Ctor.' // adayı olduğu görülmektedir. İlgili 'x' isimli veri elemanımız 'mutable' olarak nitelendiği için derleyici üstteki versiyonu // seçecektir ve bu seçim sonucunda ekrana '2' yazılacaktır. 'mutable' olarak NİTELEMESEYDİK EĞER aşağıdaki versiyonu seçecekti. // iv. En son hayata gelen 'yTwo' içerisindeki 'x' olduğu için ilk onun hayatı sona ereceğinden, ekrana '5' yazılacaktır. // v. Sonrasında da 'yOne' içerisindeki 'x' hayata veda edeceğinden, ekrana yine '5' yazılacaktır. } * Örnek 28, //.. struct X { X() { std::cout << "1"; } X(const X&) { std::cout << "2"; } X(X&&) { std::cout << "3"; } }; struct Y { X m_x; Y() { std::cout << "4"; } Y(const Y& x) : m_x{x.m_x} { std::cout << "5"; } Y(Y&& x) : m_x{x.m_x} { std::cout << "6"; } }; int main() { Y yOne; // i. İlk önce 'Y' içerisindeki 'm_x' hayata geleceğinden ekrana önce '1', sonrasında da '4' yazılacaktır. Y yTwo = std::move(yOne); // ii. 'yOne' sağ taraf haline dönüştürülecek ve 'yTwo' için 'Move Ctor.' çağrılacaktır. Bu da ekrana önce '2', // sonrasında da '6' yazılmasına neden olacaktır. Çünkü 'Y' sınıfının 'Move Ctor.' fonksiyonu bloğunda 'X' // sınıfının 'Move Ctor.' fonksiyonuna çağrı olmadığından, 'Copy Ctor.' fonksiyonu çağrılacaktır. // OUTPUT => 1426 } * Örnek 29, //.. class A { public: A() { cout << "a"; } ~A() { cout << "A"; } }; int i = 1; int main() { label: A a; if (i--) goto label; // OUTPUT => aAaA // i. Programın akışı ilk olarak 'a' değişkeninin tanımına geldiğinde sınıf türünden değişkenimiz // hayata gelecek. Bundan sebep ekrana 'a' yazılacak. // ii Sonrasında programın akışı 'if' bloğuna girdiğinde, oradan da 'goto' üzerinden tekrardan 'a' // değişkeninin tanımın geldiğinde, az evvel hayata getirilen değişkenin hayatı bitecek. Ömrü bitmiş // muamalesi görecek. Bundan sebep ekrana 'A' yazılacak. // iii. Fakat programın akışı yine bir değişken bildirimine geldiğinden dolayı tekrardan bir 'a' // değişkeni hayata gelecek. Bundan dolayı da ekrana 'a' yazacak. // iv. Son olarak programın akışı 'if' bloğuna girmeyecek çünkü yan etkiden dolayı ve değişkenlerimizin // hayatı bitecek. Bu da ekrana 'A' yazılmasına neden olacak. } * Örnek 30, //.. void takes_pointer(int* pointer) { if (typeid(pointer) == typeid(int[])) std::cout << 'a'; if (typeid(pointer) == typeid(int*)) std::cout << 'p'; // ii. Ekrana bu yazı yazılacaktır. } void takes_array(int array[]) { if (typeid(array) == typeid(int[])) std::cout << 'a'; if (typeid(array) == typeid(int*)) std::cout << 'p'; // iii. Ekrana bu yazı yazdırılacaktır. } int main() { int* pointer = nullptr; int array[1]; takes_pointer(array); // i.'array-decay' gerçekleşecektir. 'int[1]' => 'int*' takes_array(pointer); std::cout << (typeid(int*) == typeid(int[])); // iv. Ekrana '0' yazdırılacaktır. // OUTPUT => pp0 } * Örnek 31, //.. int main() { std::cout << std::is_pointer_v; // i. 'nullptr', 'nullptr_t' türünden bir değişken ismi. // ii. 'decltype()' içerisinde bir isim kullanıldığından, çıkan tür bilgisi o türün kendisi. Yani 'nullptr_t'. // 'nullptr_t' türü ise bir 'class-type' fakat bir gösteriyice dönüşebilmektedir. // OUTPUT => 0 } * Örnek 32, //.. struct A { A() { foo(); } virtual ~A() { foo(); } virtual void foo() { std::cout << "1"; } void bar() { foo(); } }; struct B : public A { virtual void foo() { std::cout << "2"; } }; int main() { B b; b.bar(); // i. İlk önce 'B' sınıfının içerisindeki 'A' sınıfı hayata gelecektir. Bu da ekrana '1' yazdırtacaktır. // ii. Daha sonra taban sınıf içerisindeki '.bar()' isimli üye fonksiyona çağrı yapılacaktır ki bu da ekrana // '2' yazdırtacaktır. Çünkü 'virtual-dispatch' mekanizması DEVREYE GİRECEKTİR. // iii. Son olarak da hayatı biten 'B' sınıfı için 'Dtor.' fonksiyonu çağrılacaktır ki bu da ekrana '1' yazdırtacaktır. // OUTPUT => 121 } * Örnek 32, //.. class Base{ public: virtual void myBase() { std::cout << "1" << std::endl; } void myBaseTwo() { std::cout << "2" << std::endl; } }; class Der : public Base{ public: virtual void myBase() override { std::cout << "3" << std::endl; } void myDer() { std::cout << "4" << std::endl; } }; void foo(Base other) { other.myBase(); } void foo(Base* other) { other->myBase(); } int main() { std::cout << "--" << std::endl; Base one; one.myBase(); // OUTPUT => 1 one.myBaseTwo(); // OUTPUT => 2 // one.myDer(); // error: ‘class Base’ has no member named ‘myDer’ std::cout << "--" << std::endl; Der two; two.myBase(); // OUTPUT => 3 two.myBaseTwo(); // OUTPUT => 2 two.myDer(); // OUTPUT => 4 std::cout << "--" << std::endl; foo(one); // OUTPUT => 1 std::cout << "--" << std::endl; foo(two); // OUTPUT => 1 std::cout << "--" << std::endl; foo(&one); // OUTPUT => 1 std::cout << "--" << std::endl; foo(&two); // OUTPUT => 3 } * Örnek 33, //.. struct A { virtual void foo(int a = 1) { std::cout << "A" << a; } }; struct B : A { virtual void foo(int a = 2) { std::cout << "B" << a; } }; int main() { A* b = new B; b->foo(); // OUTPUT => B1 // i. 'foo()' ismi derleme aşamasında 'A' sınıfı içerisinde aranır ve varsayılan argüman olarak 'a' // değişkeninin '1' değeri alacağını görür. // ii. Çalışma zamanında 'virtual-dispatch' mekanizması devreye gireceğinden, fonksiyon çağrısı 'B' // sınıfındakine bağlanır. Fakat varsayılan argüman olarak '1' değerini alacağını daha önce öğrendiğinden // 'a' yerine '1' gelir. } * Örnek 34, //.. int main() { if (std::is_signed::value) std::cout << std::is_same::value; else std::cout << std::is_same::value; // 'char' türünün işaret durumu derleyiciye bağlıdır. Dolayısıyla programın akışı // 'if' bloğuna da girebilir ama 'else' bloğuna da. Fakat her iki blok içerisinde de // 'std::is_same<>' sınaması yapılmış. 'char', 'unsigned char' ve 'signed char' türleri // birbirinden farklı tür olduklarından, her halükarda '0' değeri döndürülecektir. } * Örnek 36, // int main() { std::cout << std::is_same_v< void(int), void(const int)>; // C ve Cpp dilinde gösterici olmayan türler için türün kendisine ait 'const' nitelenmesi, // imzada bir farklılık oluşturmamaktadır. std::cout << std::is_same_v< void(int*), void(const int*)>; // Burada gösterici kullanıldığından, imzalar da farklı olacaktır. // OUTPUT => 10 } * Örnek 37, // int main(int argc, char* argv[]) { /* # OUTPUT # 132765 &a[0] : 0x7ffd3ce545d0 &a[1] : 0x7ffd3ce545d4 &a[2] : 0x7ffd3ce545d8 &a[3] : 0x7ffd3ce545dc &a[4] : 0x7ffd3ce545e0 &a[5] : 0x7ffd3ce545e4 */ std::cout << (argv[argc] == nullptr); // Standartlara göre, 'argv' isimli dizinin 'argc' numaralı öğesi 'nullptr' olmak zorunda. // Bu kural sadece komut satırı argümanlarında geçerlidir. int a[5] = { 0 }; std::cout << a[5] << std::endl; // Bu tip 'C-array' ler için, ilgili indeksin sadece adresini alabiliriz. // Geri kalan işlemler 'Tanımsız Davranış'. std::cout << "&a[0] : " << &a[0] << std::endl; std::cout << "&a[1] : " << &a[1] << std::endl; std::cout << "&a[2] : " << &a[2] << std::endl; std::cout << "&a[3] : " << &a[3] << std::endl; std::cout << "&a[4] : " << &a[4] << std::endl; std::cout << "&a[5] : " << &a[5] << std::endl; } * Örnek 38, // int main() { int x = 10; int y = 10; // Standartlara göre 'std::max()' ve 'std::min()' fonksiyonlarına geçilen argümanlar eşit // ise birinci argümanı döndürmelidir. const int& max = std::max(x, y); // 'max' demek 'x' demek. const int& min = std::min(x, y); // 'min' demek 'x' demek. x = 11; y = 9; std::cout << max << min; // OUTPUT => 1111 } * Örnek 39, // class C { public: C(int i) : i(i) { std::cout << i; } ~C() { std::cout << i + 5; } private: int i; }; int main() { const C& c = C(1); C(2); C(3); // OUTPUT => 127386 // ^^^^^^ // |||||'C(1)' nesnesinin hayatı bittiğinde. // ||||'C(3)' nesnesinin hayatı bittiğinde. // |||'C(3)' nesnesi hayata geldiğinde. // ||'C(2)' nesnesinin hayatı bittiğinde. // |'C(2)' nesnesi hayata geldiğinde. // 'C(1)' nesnesi hayata geldiğinde. 'life-extension' gerçekleşmiştir. } * Örnek 40, // int main() { std::variant v; // 'default-init.' edildiğinden, 'int' türden tutacaktır. std::cout << v.index(); // OUTPUT => 0 } * Örnek 41, //.. int main() { std::cout << 1["ABC"]; // std::cout << "ABC"[1]; // OUTPUT => B // Pointer arithmatic. } * Örnek 42, //.. int main() { std::cout << 2["elma"] - 1["elma"] << std::endl; // std::cout << 'm' - 'l' << std::endl; // ASCII karakter tablosundaki karakterlere karşılık gelen rakamlar çıkartılmıştır. // OUTPUT => 1 } * Örnek 43, //.. void f( int a = []() { static int b = 1; return b++; }() ) { std::cout << a; } int main() { f(); // i. Varsayılan argüman olan 'lambda-expression' a göre derleyici bir sınıf kodu yazacaktır. // Bu sınıfın '.operator()()' fonksiyon bloğunda ise 'b' değişkeni '1' değeri ile hayata gelecektir. // Ekrana da '1' rakamını yazdıracak, sonrasında da değeri '2' olacaktır. f(); // ii. Yine aynı 'lambda-expression' kullanılacaktır. Bu sefer 'b' değişkeninin değeri '2' olduğundan // ekrana '2' yazdırılacaktır. Sonrasında da değeri '3'. // OUTPUT => 12 } * Örnek 44, //.. int main() { char* a = const_cast("Hello"); a[4] = '\0'; std::cout << a; // 'const' nesneleri DEĞİŞTİRMEYE ÇALIŞMAK HER ZAMAN 'Tanımsız Davranış' OLUR. } * Örnek 45, //.. int foo() { return 10; } struct foobar { static int x; static int foo() { return 11; } }; int foobar::x = foo(); // i. 'x' değişkeni 'static' olması hasebiyle, 'foo()' fonksiyonu da 'class-scope' içinde aranacaktır. // Bu yüzden 'x' değişkeninin değeri '11'. int main() { std::cout << foobar::x; } * Örnek 46, //.. struct X { X() { std::cout << "a"; } X(const X& x) { std::cout << "b"; } const X& operator=(const X& x) { std::cout << "c"; return *this; } }; int main() { X x; // OUTPUT => a X y(x); // 'direct-init.' // OUTPUT => b X z = y; // 'copy-init.' // OUTPUT => b z = x; // OUTPUT => c } * Örnek 47, //.. size_t get_size_1(int* arr) { return sizeof arr; } size_t get_size_2(int arr[]) { return sizeof arr; } size_t get_size_3(int(&arr)[10]) { return sizeof arr; } int main() { int array[10]; std::cout << (sizeof(array) == get_size_1(array)); // 'array-decay' gerçekleşecektir ve 'array' artık 'int*' türündendir. İkisinin büyüklüğü farklıdır. std::cout << (sizeof(array) == get_size_2(array)); // 'array-decay' gerçekleşecektir ve 'array' artık 'int*' türündendir. İkisinin büyüklüğü farklıdır. std::cout << (sizeof(array) == get_size_3(array)); // 'array-decay' GERÇEKLEŞMEYECEKTİR. Fonksiyon parametresi 'arr', 10 boyutlu bir 'int' diziye referanstır // ki bu da aslında bizim 'array' isimli dizimiz. 'arr' demek 'array' demektir. // OUTPUT => 001 } * Örnek 48, //.. struct A { A(int i) : m_i(i) {} operator bool() const { return m_i > 0; } int m_i; }; int main() { A a1(1), a2(2); std::cout << a1 + a2 << (a1 == a2); // Sınıfımızın '.operator+()' ve '.operator==()' fonksiyonları olmadığı için, // yukarıdaki '+' ve '==' işlemleri için sınıfımızın '.operator bool()' fonksiyonu // çağrılacaktır. Çünkü '.operator bool()' fonksiyonu 'explicit' değildir. // Dolayısıyla; // i. '+' işlemi sırasında her iki nesne için '.operator bool()' fonksiyonu 'true' değerini döndürecektir. // Bu da 'int' türüne 'cast' edilecektir. Dolayısıyla ilk olarak ekrana '2' yazacaktır. // ii. Devamında ise '==' işlemi sırasında iki 'bool' tür birbiri ile karşılaştırılacak ve 'true' değer // elde edilecektir. Bu da 'int' türüne 'cast' edilecektir. Bundan sebeptir ki ekrana '1' yazacaktır. // OUTPUT => 21 // Buradaki kilit nokta '.operator bool()' fonksiyonunun 'explicit' olmamasıdır. } * Örnek 49, //.. template void f(T) { static int i = 0; std::cout << ++i; } int main() { f(1); // 'int' türü için şablon açılacaktır. Dolayısıyla 'f' içerisindeki 'i' değişkeninin değeri '0' fakat // ekrana '1' yazdıracaktır. Çünkü artık değeri '1'. f(1.0); // 'double' türü için şablon açılacaktır. Dolayısıyla 'f' içerisindeki 'i' değişkeninin değeri '0' olacak // ve ekrana '1' yazdıracaktır. f(1); // 'int' türü için ŞABLON AÇILMAYACAKTIR. Daha önceki versiyon çağrılacaktır. O versiyonda ise 'i' değişkeni // '1' idi ama '2' oldu. Ekrana da '2' yazdıracaktır. // OUTPUT => 112 } * Örnek 50, //.. int f() { std::cout << "f"; return 0; } int g() { std::cout << "g"; return 0; } void h(std::vector v) {} int main() { h( { f(), g() } ); // Derleyiciye bağlıdır DEĞİLDİR. Çünkü bir 'std::initializer_list' kullanılmış, // 'h' fonksiyonunun 'std::initializer_list' parametreli 'Ctor.' fonksiyonu // çağrılmıştır. 'std::initializer_list' kullanıldığı için de yazımdaki sıraya göre // fonksiyonlar çağrılacaktır. // OUTPUT => fg // Velev ki 'std::initializer_list' kullanılmasaydı; // İşte bu durumda hangi fonksiyonun önce çağrılacağı derleyiciye bağlı olacaktır. } * Örnek 51, //.. int y(int&) { return 1; } int y(int&&) { return 2; } template int f(T&& x) { return y(x); } template int g(T&& x) { return y(std::move(x)); } template int h(T&& x) { return y(std::forward(x)); } int main() { int i = 10; std::cout << f(i) << f(20); std::cout << g(i) << g(20); std::cout << h(i) << h(20); // i. 'f(i)' çağrısı sonrasında, şablon parametresi 'T' yerin 'int' gelecektir. 'x' ise 'int&&' türünden olacaktır. // Fakat fonksiyon bünyesinde bir İSİM kullanıldığından ki isim kullanılması her zaman 'L-Value Expression' manasındadır, // üstteki 'y' fonksiyonu çağrılacaktır. Dolayısıyla ekrana '1' yazacaktır. // ii. 'f(20)' ise yukarıdaki aynı sebepten dolayı ekrana yine '1' yazacaktır. // iii. 'g(i)' çağrısı sonrasında, şablon parametresi 'T' yerin 'int' gelecektir. 'x' ise 'int&&' türünden olacaktır. // Fakat fonksiyonun bünyesinde 'std::move()' çağrısı yapıldığından ifademiz artık 'R-Value Expression' manasındadır. // Bu nedenden dolayı alttaki 'y' fonksiyonu çağrılacak, ekranda '2' yazacaktır. // iv. 'g(20)' ise yukarıdaki aynı sebepten dolayı ekrana yine '2' yazacaktır. // v. 'h(i)' çağrısı sonrasında, şablon parametresi 'T' yerin 'int' gelecektir. 'x' ise 'int&&' türünden olacaktır. // Fakat fonksiyonun bünyesinde 'std::forward<>()' çağrısı yapılmıştır ki bu çağrı aslında ifadenin 'const' bilgisini // ve 'Value Category' bilgisini korumasını sağlamaktadır. Dolayısıyla 'h(i)' çağrısı bir 'L-Value Expression' ile // yapıldığından üstteki 'y' fonksiyonu çağrılacaktır. Dolayısıyla ekrana '1' yazacaktır. // vi. Yukarıdaki açıklanan 'std::forward<>()' çağrısından dolayı, 'h(20)' çağrısı da bir 'R-Value Expression' ile // yapıldığından alttaki 'y' fonksiyonu çağrılacaktır. Dolayısıyla ekrana '2' yazacaktır. } * Örnek 52, //.. int main() { using namespace std::string_literals; std::string s1("hello world", 5); // 'std::string' sınıfının 'data' parametreli 'Ctor.' fonksiyonuna çağrı yapılmıştır. Bu fonksiyon bir adres ve adet // bilgisi istemektedir. Yazının başından başlayarak ilk beş karakter, aslında 's1' içerisinde tutulacaktır. std::string s2("hello world"s, 5); // 'std::string' sınıfının 'sub-str' parametreli 'Ctor.' fonksiyonuna çağrı yapılmıştır. Bu fonksiyon bir // 'const std::string&' ve indis bilgisi istemektedir. Argüman olarak aldığı 'std::string' nesnesinin ilgili // indisinden başlayarak, yazının geri kalanını 's2' içerisine almaktadır. std::cout << "[" << s1 << "][" << s2 << "]"; // OUTPUT => [hello][ world] } * Örnek 53, //.. struct A { A() { std::cout << "A"; } ~A() { std::cout << "a"; } }; int main() { std::cout << "main"; return sizeof new A; // OUTPUT => main // 'sizeof' operatörünün operandı olan ifadeler HESAPLANMAZLAR. } * Örnek 54, //.. class C { public: C() = default; C(const C&) { std::cout << 1; } }; void f(std::initializer_list i) {} int main() { C c; std::initializer_list i{ c }; // Bu noktada 'Copy Ctor.' fonksiyonu çağrıldığı için ekrana '1' yazılacaktır. f(i); // Her ne kadar 'call-by-value' gibi gözükse de 'std::initializer_list' parametre türü olduğunda arka planda // 'call-by-reference' dönecektir. Dolayısıyla herhangi bir kopyalama söz konusu değildir. f(i); // Yukarıdaki aynı nedenden dolayı bu çağrıda da kopyalama YOKTUR. } * Örnek 55, //.. void f() { std::cout << "1"; } template struct B { void f() { std::cout << "2"; } }; template struct D : B { void g() { f(); } }; int main() { D d; // i. 'D' sınıfı, 'B' sınıfının 'int' açılımından kalıtım yoluyla elde edildi. d.g(); // ii. İsim arama kuralları gereği 'g' ismi 'D' sınıfı içerisinde aranacak, bulunacak. // Fakat 'g' içerisinde de 'f' fonksiyonu var. Şimdi; // Taban sınıfta da 'f' fonksiyonu var, // Global isim alanında da 'f' fonksiyonu var. // Eğer ortada şablon olmasaydı, çağrılacak fonksiyon kesinlikle TABAN SINIFTAKİ olacaktı. // Fakat şablon olduğu zaman şöyle bir kural devreye giriyor; İSİM HERHANGİ BİR ŞEKİLDE NİTELENMEMİŞSE, // TABAN SINIF İÇERİSİNDE İSİM ARANMIYOR. İşte bu nedenden dolayı global isim alanındaki bulunuyor ve çağrılıyor. // OUTPUT => 1 } * Örnek 56, //.. int main() { int i = std::numeric_limits::max(); std::cout << ++i; // İşaretli tam sayılarda taşma olması 'Tanımsız Davranış' oluşturur. } * Örnek 57, //.. void f(const std::string&) { std::cout << 1; } void f(const void*) { std::cout << 2; } int main() { f("foo"); // i. 'const char*' türünden 'void*' türüne dönüşüm 'Standart Conversion' fakat 'std::string' türüne dönüşüm // 'User-defined Conversion'. const char* bar = "bar"; f(bar); // ii. Yukarıdaki aynı neden ötürü. // OUTPUT => 22 } * Örnek 58, //.. struct Base { void f(int) { std::cout << "i"; } }; struct Derived : Base { void f(double) { std::cout << "d"; } }; int main() { Derived d; int i = 0; d.f(i); // 'f' ismi 'Derived' içerisinde aranacak ve bulunacak. // OUTPUT => d } * Örnek 59, //.. struct Base { void f(int) { std::cout << "i"; } }; struct Derived : Base { void f(double) { std::cout << "d"; } }; class A { public: virtual void f() { std::cout << "A"; } }; class B : public A { public: void f() { std::cout << "B"; } }; void g(A a) { a.f(); } int main() { Derived d; int i = 0; d.f(i); // d.Base::f(i); // Taban sınıftaki versiyonunu çağırmak için. std::cout << "\n----------------" << std::endl; B b; g(b); std::cout << "\n----------------" << std::endl; b.f(); /* # OUTPUT # d ---------------- A ---------------- B */ } * Örnek 60, //.. int main() { void* p = &p; // 'pointer' değişkenimizin kendi adresini tutması sentaks hatasına yol açmaz. // Kural gereği '=' operatörünün sol tarafındaki isim, sağ tarafında GÖRÜLÜR DURUMDADIR. std::cout << bool(p); // Tür dönüştürme operatörü kullanıldı. Bir 'pointer', 'bool' türüne dönüştürüldüğünde değerinin 'nullptr' // OLMADIĞI DİĞER DURUMLARDA 'true' DEĞERİNE DÖNÜŞÜR. Aksi durumda 'false'. // OUTPUT => 1 } * Örnek 61, //.. class show_id { public: ~show_id() { std::cout << id; } int id; }; int main() { delete[] new show_id[3]{ {0}, {1}, {2} }; // OUTPUT => 210 } * Örnek 62, //.. int main() { int i = 1; int const& a = i > 0 ? i : 1; // Bu ifadenin ikinci ('i') ve üçüncü ('1') operandları 'L-Value Expression' olsaydı, // ifadenin kendisi 'L-Value Expression' olacaktı. Fakat üçüncü operant 'R-Value Expression' olduğu için // ifadenin kendisi 'R-Value Expression' şeklindedir. // int const& a = 1; // Dolayısıyla ifade aslında bu şekildedir. i = 2; // 'i' artık '2' değerinde. std::cout << i << a; // OUTPUT => 21 } * Örnek 63, //.. using namespace std; int main() { int a = '0'; // 'a' değişkeni, '0' karakterinin ASCII tablodaki karşılığını tutmaktadır. Yani değeri '0'. char const& b = a; // Burada 'b' aslında geçici nesneye referanstır. // char temp = a; // const char b = temp; cout << b; // Ekrana '0' yazacaktır. a++; cout << b; // Ekrana '0' yazacaktır. // OUTPUT => 00 } * Örnek 64, //.. int x = 0; // i. 'x' değişkeni '0' değeri ile hayata geldi. class A { public: A() { std::cout << 'a'; // v. Ekrana 'a' yazısını yazacaktır. // xi. Ekrana tekrardan 'a' yazısını yazdıracaktır. // xii. Artık programın akışı bu 'if' bloğuna girmeyecektir çünkü 'x' değişkenimizin değeri '1'. // vi. 'x' değişkeni '0' değerinde olduğundan programın akışı buraya girecektir. if (x++ == 0) { throw std::exception(); // vii. Bu türden bir hata nesnesi fırlatılacaktır. Artık programın akışı buradan çıkacaktır. } } ~A() { std::cout << 'A'; } // xv. Son olarak da bunnu hayatı bitecektir. }; class B { public: B() { std::cout << 'b'; } // xiii. 'B' içindeki 'A' başarılı bir şekilde hayata geldiğinden, programın akışı buraya gelecektir // ve ekrana 'b' yazdıracaktır. ~B() { std::cout << 'B'; } // xiv. En son hayata gelen 'B' oldğundan, ilkin onun hayatı bitecektir. Ekrana 'B' yazdıracaktır. A a; // iv. Fakat önce veri elemanı olan 'a' hayata gelecektir. }; void foo() { static B b; } // iii. 'static' ömürlü 'B' sınıf türünden nesne hayata geldi. // x. Fakat 'b' değişkeninin hayata gelme süreci tamamlanmadığından, derleyici tekrardan 'B' türünden nesne // hayata getirmeye çalışacaktır. int main() { try { foo(); // ii. İş bu çağrı gerçekleşti. } catch (std::exception&) { std::cout << 'c'; // viii. Fırlatılan hata nesnesi yakalanacağı için programın akışı buraya gelecektir ve ekrana 'c' yazacaktır. foo(); // ix. iş bu fonksiyon çağrısı yapılacaktır. } // OUTPUT => acabBA // HAYATA GELMEMİŞ NESNE İÇİN 'DTOR.' FONKSİYONU ÇAĞRILMAZ. } * Örnek 65, //.. struct X { X() { std::cout << "X"; } }; struct Y { Y(const X& x) { std::cout << "Y"; } void f() { std::cout << "f"; } }; int main() { //Y y( X() ); // MOST-VEXING PARSE: Function Declaration // i. Geri dönüş değerinin türü 'Y' sınıf türü. // ii. Fonksiyonumuzun ismi 'y'. // iii. Parametresi 'function-pointer' // Y y((X())); // Y y( X{} ); Y y{ X{} }; // i. 'X' sınıf türünden geçici nesne oluşturulduğundan ekrana 'X' yazacaktır. // ii. 'Y' sınıfı için 'Copy Ctor.' fonksiyonu çağrılacak ve ekrana 'Y' yazacaktır. // iii. İş bu fonksiyon ile de ekrana 'f' yazılacaktır. y.f(); // OUTPUT => XYf } * Örnek 66, //.. using namespace std; template void adl(T) { cout << "T"; } struct S {}; template void call_adl(T t) { adl(S()); adl(t); } void ad1(S) { cout << "S"; } int main() { call_adl(S()); // OUTPUT => TT } * Örnek 67, //.. int main() { int n = sizeof(0)["abcdefghij"]; // i. Operatör önceliğine göre 'sizeof' operatörü daha düşük öncelikte. // int n = sizeof( (0)["abcdefghij"] ); // ii. Dolayısıyla ifade aslında bu şekilde. // int n = sizeof( 'a' ); // iii. ' (0)["abcdefghij"] ' ifadesi aslında ilk karakter olan 'a' karakterine karşılık gelmektedir. // 'char' türünün 'sizeof' değeri de '1' byte olduğundan, EKRANA '1' YAZMIŞTIR. std::cout << n; // OUTPUT => 1 } /*============================================================================================================*/ (42_attributes) > Modern Cpp ile dile eklenen 'attributes' incelemesi: Derleyicilerin belirli durumlarda uyarı mesajı vermesini sağlarlar. Böylece kodlama hatalarına karşı önlem almış oluruz. Bazı durumlarda da bunun tam tersi amaç için kullanılırlar. Yani derleyicinin vereceği uyarı mesajını by-pass etmiş, ilgili uyarının bir 'false-positive' olmasını sağlarız. Bazı durumlarda da derleyicinin daha etkin kod üretmesine olanak vermektedir iş bu 'attributes'. Genel kullanım sentaks biçimi '[[...]]' şeklinde olup, '...' yerine ilgili 'attribute' özelliğini yazıyoruz. 'attribute' ler anahtar sözcük DEĞİLLERDİR. Cpp17 ile birlikte standart olmayan fakat derleyiciye bağlı 'attribute' lar için, diğer derleyicilerin uyarı verme zorunlulu YOKTUR. >> Yıllara göre standartlara eklenen 'attributes': >>> Cpp11 ile dile 'noreturn' ve 'carries_dependency' şeklinde iki adet 'attribute' eklenmiştir. >>>> 'noreturn' : Bir fonksiyonu nitelemekte olup, nitelediği fonksiyonun GERİ DÖNÜŞ YAPMAYACAĞINI ANLATMAKTADIR. Yani kendisini çağıran koda geri dönüş yapılmayacaktır. Böylelikle fonksiyonu çağıran koda geri dönülmesi sırasında üretilen bir takım kodların üretilmemesi sağlanabilir. Bu da kodun daha hızlı çalışmasına olanak verebilir. Geri dönüş değerinin 'void' olması ile karıştırılmasın. Çünkü bir fonksiyon, bir hata nesnesi fırlatarak, 'std::exit()' çağrısıyla, 'std::abort()' çağrısı, C dilindeki standart 'longjmp()' vb. çağrılar ile fonksiyondan çıkış yapabilir. İşte bu 'attribute', aslında sadece bu yöntemler ile fonksiyondan çıkılabileceğini belirtmektedir. Bu 'attribute' ile nitelenmiş bir fonksiyonun 'return' etmesi de bir 'Tanımsız Davranış' oluşturur. * Örnek 1, //.. [[noreturn]] void foo() {} int main() { /* # OUTPUT # */ foo(); // warning: ‘noreturn’ function does return std::cout << "necati..." << std::endl; // İlgili fonksiyon, kendisini çağıran koda geri dönmüştür. Fakat dönmemesi gerekiyor. // Bu satırın aslında 'unreacable code' olması gerekmektedir. } * Örnek 2, //.. [[noreturn]] void foo() { throw std::runtime_error{"ERROR!!!"}; } int main() { /* # OUTPUT # hata yakalandi: ERROR!!! This line should be reachable line of code... */ try { foo(); std::cout << "This line should be unreachable line of code..." << std::endl; // This point is 'unreachable' } catch( const std::exception& ex ) { std::cout << "hata yakalandi: " << ex.what() << std::endl; std::cout << "This line should be reachable line of code..." << std::endl; // This point is 'reachable' } } >>>> 'carries_dependency' : 'multi-thread' uygulamalarda kullanılan, alt seviye kodlarda görülen ama ender kullanılan bir niteleyicidir. >>> Cpp14 ile dile 'deprecated' ve 'deprecated("TheReason")' şeklinde iki adet 'attribute' eklenmiştir. >>>> 'deprecated' : İki farklı kullanım biçimi vardır. İlk kullanım biçimi yalın kullanım biçimi olup, ikincisi ise 'string-literal' ile birlikte kullanım biçimidir. Bu niteleyici ile nitelenen şeylerin üçüncü kişiler tarafından KULLANILMAMASI GEREKTİĞİNİ SÖYLEMEKTEDİR. ER YADA GEÇ İLGİLİ KOD PARÇACIĞI KALDIRILACAKTIR VEYA STANDART OLMAYACAKTIR. Bu niteleyici diğerlerine nazaran daha geniş kodları nitelemektedir. Örneğin, sınıfları, sınıfların üye fonksiyonlarını, global fonksiyonları, 'enum' sabitlerini ve isim alanlarını niteleyebilir. Fakat derleyiciler, bu niteleyici ile nitelenen kodun kullanıldığını gördüğünde bir uyarı mesajı vermesi konusunda TEŞVİK EDİLMEKTEDİR. * Örnek 1, //.. [[deprecated]] void foo() { throw std::runtime_error{"ERROR!!!"}; } int main() { /* # OUTPUT # warning: ‘void foo()’ is deprecated [-Wdeprecated-declarations] hata yakalandi: ERROR!!! This line should be reachable line of code... */ try { foo(); std::cout << "This line should be unreachable line of code..." << std::endl; // This point is 'unreachable' } catch( const std::exception& ex ) { std::cout << "hata yakalandi: " << ex.what() << std::endl; std::cout << "This line should be reachable line of code..." << std::endl; // This point is 'reachable' } } * Örnek 2, //.. struct [[deprecated]] Neco{ int a, b, c; }; int main() { /* # OUTPUT # warning: ‘Neco’ is deprecated [-Wdeprecated-declarations] 32766, 0, 0 */ Neco x; std::cout << x.a << ", " << x.b << ", " << x.c << std::endl; } * Örnek 3, //.. [[deprecated]] int x = 0; [[deprecated]] typedef int Int32; using BYTE [[deprecated]] = unsigned char; struct [[deprecated]] Data { int x, y, z; }; enum Color{ White, Gray, Blue, [[deprecated]] Brown, Black, }; class Nec{ int mx; [[deprecated]] int y; }; int main() { /* # OUTPUT # */ //... } >>> Cpp17 ile dile 'fallthrough', 'maybe_unused' ve 'nodiscard' şeklinde üç adet 'attribute' eklenmiştir. >>>> 'fallthrough' : 'switch-case' merdiveninde 'break' komutu kullanılmadığında uyarı mesajını 'by-pass' etmek için kullanılır. Bu niteleyicinin devamında ';' da gerekmektedir. * Örnek 1, //.. void f1() { std::cout << 1 << std::endl; } void f2() { std::cout << 2 << std::endl; } void f3() { std::cout << 3 << std::endl; } void f4() { std::cout << 4 << std::endl; } int main() { /* # OUTPUT # 1 2 --- 1 2 */ int x = 1; switch(x) { case 1: f1(); case 2: f2(); break; case 3: f3(); break; case 4: f4(); break; } // Yukarıdaki senaryoda 'x' değişkeni '1' değerine sahip olması durumunda hem 'f1()' hem de 'f2()' fonksiyonu // çağrılacaktır. Fakat programcı " 'break;' deyimini mi unuttu yoksa algoritma gereği orada 'break;' komutu // olmaması mı lazım? " sorusunun cevabı kesin olarak bilinemeyeceğinden, akılda bir şüphe kalmaktadır. std::cout << "---" << std::endl; switch(x) { case 1: f1(); [[fallthrough]]; case 2: f2(); break; case 3: f3(); break; case 4: f4(); break; } // Artık 'x' değişkeninin değeri '1' olması durumunda her iki fonksiyonun çağrılması algoritma gereği olduğu // bilgisini kodu okuyana vermektedir. } >>>> 'nodiscard' : Bir fonksiyonun geri dönüş değerinin 'discard' EDİLMEMESİ GEREKTİĞİNİ, ISKARTAYA ÇIKARTILMAMASI GEREKTİĞİNİ VE MUTLAKA KULLANILMASI GEREKTİĞİNİ ANLATAN bir niteleyicidir. Dolayısla aslen fonksiyonların geri dönüş değerini nitelemektedir. Fakat referans veya gösterici döndüren fonksiyonlarda kullanıldığında bir uyarı mesajı alamayabiliriz. * Örnek 1, //.. [[nodiscard]] bool isPrime(int x) { return true; } int main() { /* # OUTPUT # warning: ignoring return value of ‘bool isPrime(int)’, declared with attribute nodiscard [-Wunused-result] 31 */ int x = 31; isPrime(x); std::cout << x << std::endl; } * Örnek 2, //.. class [[nodiscard]] Neco{}; // note: ‘Neco’ defined here Neco f1() { return Neco{}; } // i. note: in call to ‘Neco f1()’, defined here Neco f2() { return Neco{}; } // ii. note: in call to ‘Neco f1()’, defined here Neco f3() { return Neco{}; } // iii. note: in call to ‘Neco f1()’, defined here int main() { f1(); // i. warning: ignoring returned value of type ‘Neco’, declared with attribute nodiscard [-Wunused-result] f2(); // ii. warning: ignoring returned value of type ‘Neco’, declared with attribute nodiscard [-Wunused-result] f3(); // iii. warning: ignoring returned value of type ‘Neco’, declared with attribute nodiscard [-Wunused-result] } >>>> 'maybe_unused' : Kullanılmayan bir öğe için derleyicinin uyarı mesajı vermesini engeller. * Örnek 1, //.. static void func() { /*...*/ } // Bu fonksiyon çağrılmadığı için derleyici uyarı mesajı verebilir. [[maybe_unused]] static void foo() { /*...*/ } // Bu fonksiyon çağrılmaz ise DERLEYİCİ UYARI MESAJI VERMEYECEKTİR. int main() { int x = 42; // Bu değişken kullanılmadığı için derleyici uyarı mesajı verebilir. [[maybe_unused]] int y = 43; // Bu değişken kullanılmaz ise DERLEYİCİ UYARI MESAJI VERMEYECEKTİR. } * Örnek 2, //.. // #define DebugMode // Bu satırı yorum satırı yaptığımız zaman, // derleyici uyarı mesajı verebilir. #ifdef DebugMode #include #endif static int save_and_project(int, int) { return 0; } std::pair plot_to_curve(int x, int y) { int z = save_and_project(x, y); #ifdef DebugMode assert( z == 0); // 'z' değişkeni sadece 'assert' için kullanıldı. Projemiz 'debug' modundayken bu 'assert' çağrıları işimize // yarayacaktır fakat 'release' moduna geçildiğinde bunları kaldırmamız gerekmektedir. Tek tek bütün hepsini // kaldırmak yerine Koşullu Derleme komutlarını da kullanabiliriz. Dolayısıyla 'z' değişkeni 'release' modunda // artık kullanılmayacaktır. İşte bu durumda derleyici uyarı mesajı verebilir. Bunu engellemek için de // ' [[maybe_unused]] ' isimli niteleyiciyi kullanabiliriz. #endif return { x, y }; } int main() { /* # OUTPUT # 31 / 13 */ auto myPair = plot_to_curve(31, 13); std::cout << myPair.first << " / " << myPair.second << std::endl; } >>> Cpp20 ile dile 'likely', 'unlikely', 'nodiscard("TheReason")' ve 'no_unique_address' şeklinde dört adet 'attribute' eklenmiştir. >>>> 'no_unique_address' : Derleyici, bu nesne için ayrı bir yer ayırmana gerek yok. Sen 'allignment' şeklini ona göre ayarla. * Örnek 1, //.. // EBO : Empty Base Object öncesi. class Empty{ }; class MyclassTwo{ int mx; Empty mex; }; int main() { /* # OUTPUT # sizeof Empty : 1 sizeof int : 4 sizeof Empty : 8 */ Empty ex; std::cout << "sizeof Empty : " << sizeof(ex) << std::endl; int x; std::cout << "sizeof int : " << sizeof(x) << std::endl; MyclassTwo mx; std::cout << "sizeof Empty : " << sizeof(mx) << std::endl; // Çıktıda da görüldüğü üzere boş bir sınıf nesnesi için '1' byte yer kaplamakta. // Derleyici adresleme işlemi yapabilmek için bu ayırmayı yapmak zorunda. // '4' byte için de 'int' türüne ayrıldı. // 'allignment' sağlanması için, 'int' için dört ve 'Empty' için dört byte yer ayırdı. } * Örnek 2, //.. // EBO : Empty Base Object sonrası. class Empty{ }; class MyclassTwo : private Empty{ int mx; }; int main() { /* # OUTPUT # sizeof Empty : 1 sizeof int : 4 sizeof Empty : 4 */ Empty ex; std::cout << "sizeof Empty : " << sizeof(ex) << std::endl; int x; std::cout << "sizeof int : " << sizeof(x) << std::endl; MyclassTwo mx; std::cout << "sizeof Empty : " << sizeof(mx) << std::endl; // Çıktıda da görüldüğü üzere boş bir sınıf nesnesi için '1' byte yer kaplamakta. // Derleyici adresleme işlemi yapabilmek için bu ayırmayı yapmak zorunda. // '4' byte için de 'int' türüne ayrıldı. // 'private' kalıtım kullanıldığı için derleyici 'allignment' yapmadı. } * Örnek 3, //.. // EBO : Empty Base Object, niteleyici kullanarak. class Empty{ }; class MyclassTwo{ int mx; [[no_unique_address]] Empty mex; }; int main() { /* # OUTPUT # sizeof Empty : 1 sizeof int : 4 sizeof Empty : 4 */ Empty ex; std::cout << "sizeof Empty : " << sizeof(ex) << std::endl; int x; std::cout << "sizeof int : " << sizeof(x) << std::endl; MyclassTwo mx; std::cout << "sizeof Empty : " << sizeof(mx) << std::endl; // Çıktıda da görüldüğü üzere boş bir sınıf nesnesi için '1' byte yer kaplamakta. // Derleyici adresleme işlemi yapabilmek için bu ayırmayı yapmak zorunda. // '4' byte için de 'int' türüne ayrıldı. // ' [[no_unique_address]] ' niteleyicisi kullanıldığı için ona göre 'allignment' yapıldı. } >>>> 'likely' ve 'unlikely' : Dallanma deyimlerinde bir kodun yürütülme ihtimalinin daha yüksek veya daha düşük olduğunu belirten niteleyicilerdir. Derleyici bu niteleyici kullandığımız için daha verimli kod üretme zorunluluğu YOKTUR. Dikkatli ve özenli kullanılması gerekmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # */ //... for(size_t i = 0; i < v.size(); ++i) { if( v[i] < 0 ) [[likely]] sum -= sqrt(-v[i]); else sum += sqrt(v[i]); } // Burada biz programın büyük olasılıkla 'if' deyiminin bloğuna gireceğini biliyoruz/tahmin ediyoruz. // Bu bilgiyi de derleyiciye ip ucu olarak geçiyoruz ki daha iyi optimizasyon yapabilsin. } * Örnek 2, //.. int f(int i) { switch(i) { case 1: [[fallthrough]]; [[likely]] case 2: return 1; // 'i' değişkeninin '2' olma ihtimalinin daha yüksek olduğu belirtilmiş. } return 2; } int main() { /* # OUTPUT # 1 */ std::cout << f(2) << std::endl; } * Örnek 3, https://en.cppreference.com/w/cpp/language/attributes/likely //.. #include #include #include #include #include // namespace with_attributes namespace with_attributes { constexpr double pow(double x, long long n) noexcept { if (n > 0) [[likely]] return x * pow(x, n - 1); else [[unlikely]] return 1; } constexpr long long fact(long long n) noexcept { if (n > 1) [[likely]] return n * fact(n - 1); else [[unlikely]] return 1; } constexpr double cos(double x) noexcept { constexpr long long precision{16LL}; double y{}; for (auto n{0LL}; n < precision; n += 2LL) [[likely]] y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n)); return y; } } // namespace no_attributes namespace no_attributes { constexpr double pow(double x, long long n) noexcept { if (n > 0) return x * pow(x, n - 1); else return 1; } constexpr long long fact(long long n) noexcept { if (n > 1) return n * fact(n - 1); else return 1; } constexpr double cos(double x) noexcept { constexpr long long precision{16LL}; double y{}; for (auto n{0LL}; n < precision; n += 2LL) y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n)); return y; } } double gen_random() noexcept { static std::random_device rd; static std::mt19937 gen(rd()); static std::uniform_real_distribution dis(-1.0, 1.0); return dis(gen); } volatile double sink{}; // ensures a side effect int main() { /* # OUTPUT # x = 0.125 0.99219766722932900560039115589461289346218109130859375 0.99219766722932900560039115589461289346218109130859375 equal x = 0.25 0.96891242171064473343022882545483298599720001220703125 0.96891242171064473343022882545483298599720001220703125 equal x = 0.5 0.8775825618903727587394314468838274478912353515625 0.8775825618903727587394314468838274478912353515625 equal x = 1.490116119384765625e-08 0.99999999999999988897769753748434595763683319091796875 0.99999999999999988897769753748434595763683319091796875 equal ----------------------------------------------------------------------------- Time: 4.499906 sec (with attributes) Time: 4.510733 sec (without attributes) Time: 0.733663 sec (std::cos) ----------------------------------------------------------------------------- */ for (const auto x : {0.125, 0.25, 0.5, 1. / (1 << 26)}) { std::cout << std::setprecision(53) << "x = " << x << '\n' << std::cos(x) << '\n' << with_attributes::cos(x) << '\n' << (std::cos(x) == with_attributes::cos(x) ? "equal" : "differ") << '\n'; } std::cout << "-----------------------------------------------------------------------------" << std::endl; auto benchmark = [](auto fun, auto rem) { const auto start = std::chrono::high_resolution_clock::now(); for (auto size{1ULL}; size != 10'000'000ULL; ++size) { sink = fun(gen_random()); } const std::chrono::duration diff = std::chrono::high_resolution_clock::now() - start; std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() << " sec " << rem << std::endl; }; benchmark(with_attributes::cos, "(with attributes)"); benchmark(no_attributes::cos, "(without attributes)"); benchmark([](double t) { return std::cos(t); }, "(std::cos)"); std::cout << "\n-----------------------------------------------------------------------------" << std::endl; } > 'wandbox.org' internet sitesini de kullanabiliriz. /*============================================================================================================*/ (43_concurrency_1) > 'concurrency' : Eğer birden fazla eylem belirli bir zaman dilimi içinde birlikte yürütülüyorsa burada bir 'concurrency' söz konusudur. Örneğin, İsmail isimli birisinin bir saat boyunca hem dans ettiğini hem de şarkı söylediğini düşünelim. Bu bir saatin sonunda bu kişi hem dans etmiş hem de şarkı söylemiştir, bu kesindir. Fakat detaylarına indiğimiz zaman karşımıza iki farklı dal çıkmaktadır. Bunlar; >> Bu kişi bir saat boyunca dans ederken şarkı söylemiştir ki İngilizce dilinde bunun karşılığı 'simultaneously'. Buna 'paralelism' de diyebiliriz. Birden fazla işlemci olduğunu veya tek bir işlemcinin birden fazla çekirdeğe sahip olduğunu düşünelim. Modern işletim sistemleri, yürütülmeyi bekleyen işlemleri bu çekirdeklere pay edebilir. Böylelikle gerçekten de 'simultaneously' bir şekilde bu işlemler yürütülmektedir. * Örnek 1, Dancing:DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD Singing:SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS ------------------------------> Time Line >> Bu kişi önce bir müddet dans etmiştir. Dans bittikten sonra da bir müddet şarkı söylemiştir. Şarkı bittikten sonra da tekrar dans etmiştir. Bu şekilde bir saati doldurmuştur. Bu durumun da İngilizce dilindeki karşılığı 'at the same time'. Tek bir işlemci olduğunu ve bu işlemcinin de tek bir çekirdeğe sahip olduğunu düşünelim. Bu durumda bu işlemci aynı anda bir adet kod bloğunu yürütecektir. Fakat modern işletim sistemleri sayesinde bu tip işlemciler farklı farklı işlemleri kısa süreler için çalıştırabiliyorlar. İngilizce dilinde bu süreye 'time quanta' denmektedir. Aslında iş şu şekilde yürümekte; beş adet işlem yürütülmeyi bekliyor olsun. Bu tip işlemci önce birinci işlemi 'time quanta' süresince yürütüyor. Örneğin, 20 veya 60 milisaniye. Sonrasında ikinci işleme geçiyor ve onu da 20 veya 60 milisaniye boyunca yürütüyor. Derken sırayla üçüncü, dördüncü ve beşinci işlemleri 20 veya 60 milisaniye boyunca yürütüyor. Gözlemci olarak dışarıdan baktığımız zaman bizler sanki 'simultaneously' bir şekilde bu beş işlemin aynı anda yürütüldüğünü görüyoruz fakat hakikatte bu işlemler 'at the same time' bir şekilde yürütülmekteler. * Örnek 1, Dancing: D D D D D D D D D D D D D D D Singing:S S S S S S S S S S S S S S S S ------------------------------> Time Line Bazı işlemler için, örneğin bir 'thread' için öncelik belirlenmesi konusunda, işletim sisteminin sistem fonksiyonlarını çağırmak zorunda kalabiliriz. Yani her şeyi Cpp dilinin standart fonksiyonları ile yapamıyoruz. > İleri C ve Sistem Programlama ve UNIX Sistem Programlama, by Kaan Arslan. /*============================================================================================================*/ (44_concurrency_2) > 'program', kaynak kodlar için ya da çalıştırılabilir dosyalar için kullanılan bir terimdir. Bir 'program' çalıştırıldığında ise artık ona 'process' denmektedir. Prosesler ise işletim sisteminin kontrolündedir. Bu duruma, yani proseslerin işletim sistemi tarafından yönetilmesi olayına, da 'process management' denmektedir. İşletim sistemleri proseslerin kullanımı açısından kategorize edilebilir; aynı anda tek bir programı çalıştıran işletim sistemlerine "tek prosesli (single-processing)" sistemler, aynı anda birden fazla programı çalıştıran işletim sistemlerine "çok prosesli (multi-processing)" işletim sistemleri denir. Örneğin, 'DOS' işletim sistemi tek prosesli bir sistemdi. Ancak bir program çalıştırıldıktan sonra baska bir program çalıştırılabilirdi. 'Windows', 'UNIX/Linux' ve 'MacOS X' çok prosesli işletim sistemleridir. 'process' terimi ile 'task' terimi pek çok bağlamda aynı anlamda kullanılır, istisnai durumlar da söz konusudur. Yani 'multi-processing' yerine 'multi-tasking' terimini de kullanabiliriz. Peki tek bir CPU olduğunda, 'multi-processing' nasıl gerçekleştirilir? Her ne kadar yukarıda ana hatları ile açıklasak da aslında süreç şu şekildedir; bu tip senaryolarda programlar aynı anda çalışmamaktadırlar. Çalışma, 'time-sharing' denilen zaman paylaşımlı bir biçimde yapılmaktadır. Yani bir 'process' CPU ya atanır. Bir süre bu proses çalıştırılır. Sonra çalışmasına ara verilir. Bu sefer bir başka 'process' CPU ya atanır. Yine bir süre çalıştırılır ve sonra çalışmasına ara verilir. Çalışma genel olarak bu şekilde devam ettirilir. Her bir 'process', CPU ya atandıktan sonra kaldığı yerden işlemeye devam eder. Dışarıdan bakıldığında ise sanki bu bütün prosesler aynı anda çalışıyormuş algısı oluşur. Bir nevi yukarıdaki 'at the same time' senaryosu. İş bu çalışma stilinde, her bir prosesin çalışma süresine de 'time-quanta' denmekte olup Windows sistemlerde bu süre 20 milisaniyeyken, UNIX/Linux sistemlerde 60 milisaniyedir. Tabii işletim sisteminin sürümleri arasında da bu süre farklılık gösterebilir. Çünkü burada etken nokta seçilen süre ile çekirdeğin çalışma biçiminin uyumlu olmasıdır. Peki iki 'process' arasındaki geçiş sürecine ne ad verilir? 'process-switch' veya 'task-switch' ya da daha genel bağlamda 'context-switch' adı verilir. Dolayısıyla prosesler arasındaki geçiş de doğal olarak bir zaman kaybına neden olur. Proseslerin işletilme süresi olan 'time-quanta' uzun seçildiğinde birim zamanda yapılan İŞ MİKTARI ARTAR ve interaktivite(latency) AZALIR. 'time-quanta' süresinin kısa seçilmesi halinde ise birim zamanda yapılan İŞ MİKTARI AZALACAK, interaktivite(latency) ARTACAKTIR. Bunun da yegane sebebi sık sık 'context-switch' boyunca zaman kaybedilmesidir. Windows ve UNIX/Linux kökenli işletim sistemleri 'preemptive' sistemlerdir. Yani bir prosesin 'time-quanta' dolduğunda, zoraki durdurularak başka bir prosesin devreye alınmasıdır. O anda prosesin neresinde olunduğu ÖNEMSİZDİR. Quanta süresi dolduğunda bir başka proses işlemciye atanmaktadır. Fakat bu şekilde 'preemptive' OLMAYAN, yani 'non-preemptive' veya 'cooperative multi-tasking' olan sistemler de vardır ki Windows 3.1 ve PalmOS bu şekildedir. Bu tip sistemlerde proses kendi isteğiyle akışı bırakır ve prosesler arası geçiş gerçekleşir. Fakat bunun dezavantajı ise ilgili prosesin, işletim sistemini tek eli altına almasıdır. Akışı bırakmak istemediği durumlarda işletim sisteminin yapacağı bir şey kalmamaktadır. İşletim sistemlerinin hangi prosesin işlemciye ne zaman atanacağını belirleyen ve bu işlemi gerçekleştiren alt sistemlerine de 'scheduler' denmektedir. Hangi prosesin ne zaman çalıştırılması gerektiğini belirlemede kullanılan algoritmalara da 'scheduler algorithms' denmektedir. Bu algoritmalardan en basit ve adil olanı 'round-robin scheduling' isimli olanıdır. Bu algoritmada her proses sırasıyla ve belli bir süre çalıştırılır. En sonki prosesten sonra tekrardan ilk proses çalıştırılır. Proseslerin çizelgelenmesi yani işletim sistemi tarafından proseslerin işlemcilere atanması, birden fazla işlemci ya da birden fazla çekirdek olması durumunda da benzer bir şekilde yapılmaktadır. Bu tip senaryoda ise işletim sistemi şöyle bir şey yapmaktadır; işlemciler ya da çekirdekler için ayrı ayrı kuyruklar oluşturulmakta. Yine her işlemci veya çekirdek, tıpkı tek çekirdek gibi, zaman paylaşımlı çalışmaktadır. Bir diğer deyişle her çekirdek veya işlemci 'at the same time' yaklaşımı ile çalışırken, bütün çekirdekler incelendiğinde 'simultaneously' şeklinde bir çalışma görülmektedir. Peki çekirdeklerin birisi işini bitirdiğinde ne oluyor? Diğer çekirdeklerdeki prosesler bu boşalan çekirdeğe aktarılmaktadır. Fakat bazı işletim sistemleri ise çok işlemcili ya da çok çekirdekli sistemler için global tek bir kuyruk oluşturmaktadır. Her çekirdeğin Quanta süresi dolduğunda, kuyruktaki sırada bekleyen proses ona atanmaktadır. Fakat bu yöntem pek de iyi bir yöntem olmayabilir çünkü çekirdeklerin içlerindeki ön belleklerden ('cache') maksimum fayda sağlayabilmek için bir prosesin bir önceki Quanta için atanan CPU ya atanması daha uygun olabilmektedir. Bir prosesin bir işlem bitene kadar ya da bir işlem gerçekleşene kadar çizelge('scheduling') dışına çıkartılarak bekletilmesine, yani aslında o proses hiç yokmuş gibi davranmasına, o prosesin bloke olması('blocking') denir. Çünkü zaman zaman bir proses çalışırken dışsal bir olay başlattığında (örneğin disk işlemi, klavyeden okuması, soket okuması vs.) işletim sistemi, atanan CPU boş yere beklemesin diye geçici olarak bu prosesi çizelge dışına alır ve o işlemciye başka bir proses atar. Dışarı aldığı bu prosesi de ayrıca gözlemler. Dışarı çıkartılan bu prosesin işi bittiğinde tekrar çizelgelemeye dahil edilir. Böylelikle CPU boşu boşuna beklememiş olur. Bir çok durumda prosesler arasında haberleşmenin sağlanması gerekmektedir. Bu haberleşmeyi sağlayan tipik teknikler Borular('pipes'), Paylaşılan Bellek Alanları('Shared Memory'), Mesaj Kuyrukları('Message Queue') şeklindeki tekniklerdir. Boru Haberleşmeleri hem Windows hem de UNIX/Linux sistemlerinde kullanılan yaygın bir tekniktir. Senkronize bir haberleşme tekniğidir. UNIX/Linux sistemlerinde borular tek kanallıdır. Yani bir taraf yazarken diğer taraf okumaktadır. Fakat Windows sistemlerinde ise borular çift kanallıdır. Bir tarafın yazdığını diğer taraf okurken, diğer tarafın yazdığını da karşı taraf okuyabilmektedir. Bu mekanizmayı UNIX/Linux sistemlerinde uygulayabilmek için iki adet boru yaratmak gerekmektedir. Paylaşılen Bellek Alanları şeklindeki haberleşme tekniğinde bir bellek alanı iki prosesin tarafından da kullanılabilmektedir. Normal şartlar altında her prosesin kendine ait ayrı bir bellek alanı mevcuttur. Bu haberleşme tekniği senkronize olmadığından akla şu soruları getirmektedir; proseslerden birisi paylaşılan bellek alanına yazdığında diğer proses onu ne zaman okuyacaktır veya diğeri bir şey yazdığında eskisi silinecek midir, şeklindeki sorular. Bu sorulara cevap olarak Üretici-Tüketici Problemi('Producer-Consumer Problem') diye isimlendirilen bir yöntem kullanılmalıdır ki senkronizasyon sağlansın. Bu haberleşme yöntemi hem Windows hem de UNIX/Linux sistemlerinde var olan bir haberleşme yöntemidir. Mesaj Kuyrukları haberleşme tekniğinde ise bir mesaj kuyruğu yaratılır ki kuyruklar aslında 'FIFO' biçimindedir. Yani ilk girenin ilk çıktığı biçimler. Bir proses kuyruğa yazma yaparken diğeri de okuma yapmaktadır. Bu neden ile Boru Haberleşme tekniğine benzemektedir. Fakat Mesaj Kuyruklarında paket tarzı bir aktarım söz konusudur. Bir taraf "mesaj" adı altında bir paket gönderir, diğer bunu alır. Fakat alıcı taraf, gönderilen paketin TAMAMINI TEK HAMLEDE ALMAK zorundadır. Mesaj kuyrukları Windows işletim sisteminde GUI alt sistemindeki mesajlar yoluyla kullanılabilmektedir. /*============================================================================================================*/ (45_concurrency_3) > 'thread' kavramı: Türkçe karşılığı 'iplik' olan, proseslerin bir alt kümesidir. Aradaki küçük farklılıkları görmezsek, 'thread' aslında 'process' demektir. Çok 'thread' li işletim sistemlerinde çizelgelenen varlıklar artık prosesler değil 'thread' lerdir. Bir proses bir ya da birden fazla 'thread' e sahip olabilir. Bu durumda 'thread' siz sistemler, tek 'thread' li sistemler olarak düşünülebilir. Çok 'thread' li sistemlerde proses çalışmaya bir 'thread' ile başlar. Buna prosesin ana 'thread' i denir. Diğer 'thread' ler ise işletim sisteminin sistem fonksiyonlarıyla oluşturulurlar. Her Quanta süresi dolduğunda bir 'thread' in çalışmasına ara verilir, diğer bir 'thread' çalıştırılır. 'thread' ler arka plan olaylarını izlemek için iyi bir araç oluşturmaktadırlar. Örneğin, bir yandan klavyeden giriş okurken diğer yandan ekranın sağ üst köşesine saatin basılması olayında iki farklı 'thread' ile bu sorunu çözebiliriz. UNUTMAMALIYIZ Kİ BİR PROSES SONLANDIĞINDA BÜNYESİNDEKİ BÜTÜN 'thread' LER DE SONLANACAKTIR. Sık yapılan hata şudur; Bir grup 'thread' oluşturulmuştur fakat 'main-thread' bekletilmemiştir. 'main-thread' işini önce bitirdiğinden proses sonlanmıştır. Dolayısıyla bizim grup 'thread' de sonlanmış olacaktır. 'thread' lerin 'stack' bölgeleri de farklıdır. Örneğin, dört adet 'thread' oluşturulım ve bunların hepsi de aynı fonksiyonu çalıştırsın. İlgili fonksiyonun gövdesindeki otomatik ömürlü değişkenler her bir 'thread' için ayrı ayrı olacaktır. Fakat global ve 'static' yerel değişkenler prosese özgü olduklarından, her 'thread' ilgili değişkenler için kendi kopyasını OLUŞTURMUYORLAR. Benze kural 'heap' alanındaki değişkenler için de geçerlidir. Statik ve dinamik ömürlü değişkenler 'thread' tarafından ortak kullanılır, kendilerine ait kopyası oluşturulmaz. 'thread' arası haberleşme için özel bir yönteme gerek yoktur, prosesler arası haberleşmeye nazaran. Zaten dinamik ve statik ömürlü nesneler 'thread' ler arasında ortak adresi kullandıklarından, bir 'thread' üzerinden yapılan değişikliği diğer 'thread' üzerinden görebiliyoruz. Burada önemli olan nokta iki farklı 'thread' in aynı prosese ait olmasıdır. 'thread' oluşturma, 'thread' arası haberleşme ve 'thread' arası geçişler proses oluşturma, prosesler arası haberleşme ve prosesler arası geçişe göre daha az maliyetlidirler. Fakat 'thread' arası haberleşme/geçiş için senkronizasyon gerektiğinden, 'data-racing' şeklindeki problemler ile karşılaşabiliriz. /*============================================================================================================*/ (46_concurrency_4) > Aşağıdaki örnekleri inceleyelim: * Örnek 1, //.. int main() { /* # OUTPUT # It is waiting for a task... */ std::thread t; // İlgili 'thread' parçacığına herhangi bir görev ataması yapılmamıştır. std::cout << ( t.joinable() ? " It is occupied... " : "It is waiting for a task..." ) << std::endl; // İlgili 'thread' boş ise 'false' değer döndürecektir. } * Örnek 2, //.. void foo(void) { std::cout << "void foo(void) was called..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds{ 2 }); // İlgili 'thread' iki saniye boyunca bloke edilecektir. // Parametre olarak herhangi bir 'duration' geçebiliriz. } int main() { /* # OUTPUT # It is occupied... void foo(void) was called... // Aslında bu satırda iki saniyelik bir bekleme gerçekleşmiştir. */ std::thread t(foo); // İlgili 'thread' parçacığına herhangi bir görev ataması yapılmıştır. Herhangi bir 'callable' atanabilir. Eğer fonksiyonumuz // geri değer döndürüyorsa, o değeri kullanmak için başka araçlar kullanmalıyız. Aksi halde o değer 'discard' edilecektir. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; // İlgili 'thread' boş ise 'false' değer döndürecektir. t.join(); // İşlevi ilerleyen örneklerde açıklanacaktır. } * Örnek 3, //.. void foo(void) { std::this_thread::sleep_for(std::chrono::seconds{ 2 }); // İlgili 'thread' iki saniye boyunca bloke edilecektir. std::cout << "void foo(void) was called..." << std::endl; // Parametre olarak herhangi bir 'duration' geçebiliriz. } int main() { /* # OUTPUT # It is occupied... void foo(void) was called... // Aslında bu satırda iki saniyelik bir bekleme gerçekleşmiştir. */ void (*fooPtr)(void) = foo; std::thread t(fooPtr); // İlgili 'thread' parçacığına herhangi bir görev ataması yapılmıştır. // Herhangi bir 'callable' atanabilir. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; // İlgili 'thread' boş ise 'false' değer döndürecektir. t.join(); // İşlevi ilerleyen örneklerde açıklanacaktır. } * Örnek 4, //.. struct MyFunctor{ void operator()(void) { std::this_thread::sleep_for(std::chrono::seconds{ 2 }); // İlgili 'thread' iki saniye boyunca bloke edilecektir. // Parametre olarak herhangi bir 'duration' geçebiliriz. std::cout << "void foo(void) was called..." << std::endl; } }; int main() { /* # OUTPUT # It is occupied... void foo(void) was called... // Aslında bu satırda iki saniyelik bir bekleme gerçekleşmiştir. */ std::thread t( MyFunctor{} ); // İlgili 'thread' parçacığına herhangi bir görev ataması yapılmıştır. Herhangi bir 'callable' atanabilir. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; // İlgili 'thread' boş ise 'false' değer döndürecektir. t.join(); // İşlevi ilerleyen örneklerde açıklanacaktır. } * Örnek 5, //.. int main() { /* # OUTPUT # It is occupied... void foo(void) was called... // Aslında bu satırda iki saniyelik bir bekleme gerçekleşmiştir. */ auto myLambdaExpression = [](void) { std::this_thread::sleep_for(std::chrono::seconds{ 2 }); // İlgili 'thread' iki saniye boyunca bloke edilecektir. // Parametre olarak herhangi bir 'duration' geçebiliriz. std::cout << "void foo(void) was called..." << std::endl; }; std::thread t( myLambdaExpression ); // İlgili 'thread' parçacığına herhangi bir görev ataması yapılmıştır. Herhangi bir 'callable' atanabilir. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; // İlgili 'thread' boş ise 'false' değer döndürecektir. t.join(); // İşlevi ilerleyen örneklerde açıklanacaktır. } * Örnek 6, Eğer koşturacağımız fonksiyon parametre alıyorsa bu parametreleri 'std::thread' sınıfının kurucu işlevine geçiyoruz. 'Perfect Forwarding' ile bu parametreler iletilmektedirler. //.. void foo(int x) { std::cout << "void foo(" << x << ") was called..." << std::endl; for(int i = 0; i < x; ++i) { std::cout << "*" << std::endl; } } int main() { /* # OUTPUT # It is occupied... void foo(3) was called... * * * */ std::thread t{ foo, 3 }; // İlgili 'thread' parçacığına herhangi bir görev ataması yapılmıştır. Herhangi bir 'callable' atanabilir. // Tam olarak bu noktada iki adet 'thread' çalışmaktadır. Bunlardan ilki 'main-thread', ikincisi ise 't' // isimli bizim oluşturduğumuz. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; // İlgili 'thread' boş ise 'false' değer döndürecektir. t.join(); // 't' nesnesinin 'Dtor.' fonksiyonu çağrılmadan evvel ilgili 'thread' nesnesini ya 'join' etmem ya // da 'detach' etmem gerekmektedir. } * Örnek 7, //.. void foo(int x) { std::cout << "void foo(" << x << ") was called..." << std::endl; for(int i = 0; i < x; ++i) { std::cout << "*" << std::endl; } } int main() { /* # OUTPUT # It is occupied... void foo(3) was called... * * * It is waiting for a task... */ std::thread t{ foo, 3 }; // Tam olarak bu noktada iki adet 'thread' çalışmaktadır. Bunlardan ilki 'main-thread', // ikincisi ise 't' isimli bizim oluşturduğumuz. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; t.join(); // 't' isimli 'thread' nesnesinin '.join()' isimli fonksiyonu çağrıldığında 'main-thread', // bizim 'thread' nesnesi işini bitirene kadar bloke edilecektir. Yani bizim 'thread' hangi görevi aldıysa // ONU TAMAMLA demektir. // Dolayısıyla programın akışının buraya gelmesi için, 't' isimli 'thread' nesnesinin işini bitirmiş olması // gerekmektedir. Bir diğer deyişle programın akışı buraya geldiyse sadece 'main-thread' çalışıyordur. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; } * Örnek 8, //.. void foo(int x) { std::cout << "void foo(" << x << ") was called..." << std::endl; for(int i = 0; i < x; ++i) { std::cout << "*" << std::endl; } } int main() { /* # OUTPUT # It is occupied... void foo( It is waiting for a task... 30) was called... ) was called... * * * */ std::thread t{ foo, 30 }; // Tam olarak bu noktada iki adet 'thread' çalışmaktadır. Bunlardan ilki 'main-thread', // ikincisi ise 't' isimli bizim oluşturduğumuz. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; t.detach(); // İş bu 'thread' nesnesinin 'main-thread' den bağımsız olarak çalışması demektir. // Dolayısıyla bu örnek nezdinde konuşacak olursak, 'main-thread' işini daha önce bitirdiğinden, // bizim 'thread' nesnesi işini tam olarak bitiremedi. Çünkü proses sonlandı. std::cout << ( t.joinable() ? "It is occupied... " : "It is waiting for a task..." ) << std::endl; } * Örnek 9, //.. void foo(int x) { std::cout << "void foo(" << x << ") was called..." << std::endl; for(int i = 0; i < x; ++i) { std::cout << "*" << std::endl; } } int main() { /* # OUTPUT # main basladi... std::terminate cagrildi.... myterminate cagrildi.... std::abort() cagrildi */ std::set_terminate(my_terminate); // Artık bizim 'std::terminate()' versiyonumuz çağrılacaktır. std::cout << "main basladi..." << std::endl; { std::thread t{ foo, 30 }; } // Bu noktada 't' nesnesinin hayatı bitecek fakat 'joinable' durumda. Dolayısıyla 'std::terminate()' // fonksiyonu çağrılacaktır. Çünkü ne '.join()' ne de '.detach()' fonksiyonlarına çağrı yaptık. // İşte bu nedenden dolayı ya 'join' etmeli ya da 'detach' etmeliyiz. Ayrıca koşan fonksiyonun bloğundan // bir hata nesnesi fırlatılsa ve fonksiyon bloğu içerisinde yakalanamaz ise 'std::terminate()' çağrılacaktır. std::cout << "main devam ediyor..." << std::endl; // BURADAN HAREKETLE İLGİLİ 'thread' SINIFINI SARMALAYAN VE RAII deyimini güden bir sınıf yazmamız halinde, // '.join()' ve ya '.detach()' çağrılarının unutulmasının önüne geçebiliriz. Cpp20 ile bu işi yapan bir sınıf da dile // eklenmiştir. } * Örnek 10, //.. void foo(int x) { std::cout << "void foo(" << x << ") was called..." << std::endl; for(int i = 0; i < x; ++i) { std::cout << "*" << std::endl; } throw std::runtime_error{"HATA!!!!!!!!!!!!!!!"}; } int main() { /* # OUTPUT # main basladi... std::terminate cagrildi.... myterminate cagrildi.... std::abort() cagrildi void foo(30) was called... */ std::set_terminate(my_terminate); std::cout << "main basladi..." << std::endl; { std::thread t{ foo, 30 }; } std::cout << "main devam ediyor..." << std::endl; } * Örnek 11, //.. void foo(int x) { try { std::cout << "void foo(" << x << ") was called..." << std::endl; for(int i = 0; i < x; ++i) { std::cout << "*" << std::endl; } throw std::runtime_error{"HATA!!!!!!!!!!!!!!!"}; }catch(...) { std::cout << "BİR HATA YAKALANDI..." << std::endl; } } int main() { /* # OUTPUT # main basladi... void foo(30) was called... * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BİR HATA YAKALANDI... main devam ediyor... */ std::set_terminate(my_terminate); std::cout << "main basladi..." << std::endl; { std::thread t{ foo, 30 }; t.join(); } std::cout << "main devam ediyor..." << std::endl; } * Örnek 12, //.. int main() { /* # OUTPUT # main basladi... I am FREE now... Hata yakalandi... : Invalid argument main devam ediyor... */ std::cout << "main basladi..." << std::endl; try{ std::thread t; std::cout << ( t.joinable() ? "I am busy now..." : "I FREE now..." ) << std::endl; t.join(); // 'joinable' olmayan bir nesne için '.join()' veya '.detach()' çağrısı yapmak hata nesnesi // gönderilmesine sebep olur. }catch(const std::exception& ex) { std::cout << "Hata yakalandi... : " << ex.what() << std::endl; // Hata nesnesinin türü 'std::system_error'. } std::cout << "main devam ediyor..." << std::endl; } * Örnek 13, //.. void foo(){} int main() { /* # OUTPUT # main basladi... I am FREE now... Hata yakalandi... : Invalid argument main devam ediyor... */ std::cout << "main basladi..." << std::endl; std::thread t(foo); std::cout << ( t.joinable() ? "I am busy now..." : "I FREE now..." ) << std::endl; t.join(); try{ t.join(); // '.join()' veya '.detach()' çağrısı yapılmış bir nesne için '.join()' veya // '.detach()' çağrısı yapmak hata nesnesi gönderilmesine sebep olur. }catch(const std::exception& ex) { std::cout << "Hata yakalandi... : " << ex.what() << std::endl; } std::cout << "main devam ediyor..." << std::endl; } * Örnek 14, //.. void foo(int a, int b) { std::cout << "void foo(" << a << ", " << b << ") was called." << std::endl; } struct MyFunctor{ void operator()(int a, int b) { std::cout << "void MyFunctor::operator()(" << a << ", " << b << ") was called." << std::endl; } }; auto MyLambda = [](int a, int b){ std::cout << "MyLambda(" << a << ", " << b << ") was called." << std::endl; }; int main() { /* # OUTPUT # main basladi... main devam ediyor... */ void (*fooPtr)(int, int) = foo; std::cout << "main basladi..." << std::endl; std::thread tOne{ foo, 1, 2 }; tOne.detach(); std::thread tTwo{ fooPtr, 10, 20 }; tTwo.detach(); std::thread tThree{ MyFunctor{}, 100, 200 }; tThree.detach(); std::thread tFour{ MyLambda, 1000, 2000 }; tFour.detach(); std::cout << "main devam ediyor..." << std::endl; // 'main-thread' ilk önce bittiğinden, diğerlerini de durdurdu. } * Örnek 15, //.. void foo(int a, int b) { std::cout << "void foo(" << a << ", " << b << ") was called." << std::endl; } struct MyFunctor{ void operator()(int a, int b)const { std::cout << "void MyFunctor::operator()(" << a << ", " << b << ") was called." << std::endl; } }; auto MyLambda = [](int a, int b){ std::cout << "MyLambda(" << a << ", " << b << ") was called." << std::endl; }; int main() { /* # OUTPUT # main basladi... void foo(1, 2) was called. void foo(10, 20) was called. void MyFunctor::operator()(100, 200) was called. MyLambda(1000, 2000) was called. main devam ediyor... */ void (*fooPtr)(int, int) = foo; std::cout << "main basladi..." << std::endl; std::thread tOne{ foo, 1, 2 }; tOne.join(); std::thread tTwo{ fooPtr, 10, 20 }; tTwo.join(); std::thread tThree{ MyFunctor{}, 100, 200 }; tThree.join(); std::thread tFour{ MyLambda, 1000, 2000 }; tFour.join(); std::cout << "main devam ediyor..." << std::endl; } /*============================================================================================================*/ (47_concurrency_5) > Aşağıdaki örnekleri inceleyelim: * Örnek 1, //.. class Functor { public: void operator()(int n)const { for (int i = 10; i < n; ++i) std::cout << i << " "; std::cout << std::endl; } }; class MyClass { public: static void sprintn(int n) { for (int i = 30; i < n; ++i) std::cout << i << " "; std::cout << std::endl; } void printn(int n)const { for (int i = 40; i < n; ++i) std::cout << i << " "; std::cout << std::endl; } }; void printn(int n) { for (int i = 0; i < n; ++i) std::cout << i << " "; std::cout << std::endl; } int main() { /* # OUTPUT # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 */ std::thread t1{ printn, 10 }; t1.join(); std::thread t2{ Functor{}, 20 }; t2.join(); std::thread t3{ [](int n) { for (int i = 20; i < n; ++i) std::cout << i << " "; std::cout << std::endl; }, 30 }; t3.join(); std::thread t4{ MyClass::sprintn, 40 }; t4.join(); MyClass m; std::thread t5{ &MyClass::printn, m, 50 }; // Burada '&' atomunun kullanılması bir zorunluluktur. t5.join(); } * Örnek 2, 'thread' tarafından koşulacak fonksiyonun parametresinin sol taraf referans olması durumunda işler değişiyor. //.. using namespace std; void func(int& r) { /// r += 29; } void foo(string &str) { str += "can"; } int main() { string name{ "necati" }; thread t{ foo, name}; t.join(); cout << "name = " << name << "\n"; int x = 31; thread tt{ func, x }; tt.join(); cout << "x = " << x << "\n"; // Aslında burada gerçekleşen şey şudur: 'thread' nesnesinin kurucu işlevine geçilen argümanlar direkt // olarak koşulacak fonksiyona argüman olarak geçilmiyorlar. Birer kopyası çıkartıldıktan sonra fonksiyonlara // argüman olarak geçiliyorlar. Birer kopyası çıkartıldıkları için ki bu kopyalar da 'R-Value Expression', // 'non-const L-Value Reference' tiplerine BAĞLANAMIYORLAR. Bu durumda 'ReferenceWrapper' sınıfından destek // almamız gerekmektedir eğer değişkenin değerini değiştireceksek. Okuma amacıyla değişkeni kullanacaksak // 'const L-Value Referance' veya 'R-Value Referance' kullanabiliriz. } * Örnek 3, //.. using namespace std; void func(int& r) { /// r += 29; } void foo(string &str) { str += "can"; } int main() { /* # OUTPUT # name = necatican x = 60 */ string name{ "necati" }; thread t{ foo, ref(name)}; t.join(); cout << "name = " << name << "\n"; int x = 31; thread tt{ func, ref(x) }; tt.join(); cout << "x = " << x << "\n"; } * Örnek 4, //.. using namespace std; void func(int&& r) { std::cout << "r : " << r << std::endl; } void foo(const string &str) { std::cout << "str : " << str << std::endl; } int main() { /* # OUTPUT # t_id : str : necati140528876435200 tt_id : 140528876435200 r : 31 ------------------ t_id : thread::id of a non-executing thread tt_id : thread::id of a non-executing thread */ string name{ "necati" }; thread t{ foo, name }; std::cout << "t_id : " << t.get_id() << std::endl; t.join(); int x = 31; thread tt{ func, x }; std::cout << "tt_id : " << tt.get_id() << std::endl; tt.join(); std::cout << "\n------------------" << std::endl; std::cout << "t_id : " << t.get_id() << std::endl; std::cout << "tt_id : " << tt.get_id() << std::endl; // '.get_id()' fonksiyonunun geri dönüş değerinin türü 'std::thread::id' sınıf türündendir. } * Örnek 5, //.. void func(void) { std::cout << std::this_thread::get_id() << "\n"; // İş bu fonksiyon, hangi 'thread' tarafından koşulur ise onun 'ID' bilgisini ekrana yazdıracaktır. } int main() { /* # OUTPUT # 139931070924608 139931070920448 */ func(); // Şu an 'main-thread' e ait olan ID ekrana yazdıracaktır. std::thread t{ func }; // Şu an 't' isimli 'thread' e ait olan ID ekrana yazdıracaktır. t.join(); } * Örnek 6, //.. std::thread::id main_thread_id; // 'id' sınıf türünden, 'main_thread_id' isimli global değişken. void func() { //func'ın hangi thread'den cagrildigina bagli olarak farkli isler yapiliyor if (std::this_thread::get_id() == main_thread_id) { std::cout << "cagri main thread'den yapildi\n"; } else { std::cout << "cagri ikincil threadlerden yapildi\n"; } } int main() { /* # OUTPUT # cagri main thread'den yapildi cagri ikincil threadlerden yapildi */ main_thread_id = std::this_thread::get_id(); // 'main-thread' e ait olan ID bilgisi elde edildi. func(); std::thread t{ func }; // İkincil bir 'thread' daha oluşturmaktayız. t.join(); } * Örnek 7, //.. std::mutex cout_mtx; // Ne işe yaradığı sonraki örneklerde açıklanacaktır. void func() { std::lock_guard myguard(cout_mtx); // Senkronizasyon sağlamak için fakat daha detaylı açıklaması sonraki örneklerdedir. std::cout << std::this_thread::get_id() << "\n"; // Bizim odaklanmamız gereken nokta burası. Bu fonksiyonu hangi 'thread' çalıştırırsa, onun ID bilgisi // ekrana yazılacaktır. } int main() { /* # OUTPUT # 140290892769024 140290892769024 */ std::thread tx{ func }; std::cout << tx.get_id() << std::endl; tx.join(); // Çıktıta görülen ID numarası, 'tx' isimli 'thread' e aittir. } * Örnek 8, //.. void func() { // 'sleep_for()' için bir 'duration' geçmemiz gerekiyor. // std::this_thread::sleep_for( std::chrono::duration{ 2.5 } ); // 2.5 saniye boyunca bloke edilecektir. std::this_thread::sleep_for(std::chrono::seconds{ 5 }); // Bu fonksiyonu koşturan 'thread', beş saniye boyunca bloke edilecektir. std::cout << "void func() was called..." << std::endl; } int main() { /* # OUTPUT # // Aslında burada beş saniyelik bir bekleme söz konusu. void func() was called... */ func(); // Bu fonksiyonu koşturan 'thread', 'main-thread'. Beş saniye boyunca bloke edildi. } * Örnek 9, //.. // std::chrono::time_point now() { return std::chrono::steady_clock::now(); } auto now() { return std::chrono::steady_clock::now(); } // 2999 milisaniye sonrasına dair bir 'time_point' döndürmektedir. auto awake_time() { using std::chrono::operator""ms; return now() + 2999ms; } int main() { /* # OUTPUT # Hello, waiter... Waited 2999.08 ms */ std::cout << "Hello, waiter...\n" << std::flush; const auto start{ now() }; // 'start' aslında bir 'time-point'. Sürenin başlangıcını tutmaktadır. std::this_thread::sleep_until(awake_time()); // '.sleep_until()' bir 'time-point' istemektedir. std::chrono::duration elapsed{ now() - start }; // 'start' noktası ile şu an arasındaki geçen süre. std::cout << "Waited " << elapsed.count() << " ms\n"; } * Örnek 10, //.. int main() { /* # OUTPUT # 8 */ std::cout << std::thread::hardware_concurrency() << std::endl; // Bizim sistemimizdeki 'concurrent' olarak çalıştırılabilecek maksimum 'thread' sayısını vermektedir. } * Örnek 11, //.. void func(size_t size, char c) { while(size--) std::cout.put(c); } int main() { /* # OUTPUT # bdddbbcccaaaeeefffhhhggg */ std::vector tVec; for(size_t i = 0; i < std::thread::hardware_concurrency(); ++i) tVec.emplace_back(func, 3, 'a' + i); //.. // 'thread' leri tekrardan 'join' etmeliyiz. for(auto& t : tVec) t.join(); } * Örnek 12, //.. void printer(const std::vector& iVec) { for(auto i : iVec) std::cout << i << std::endl; } void filler(std::vector& myVec, size_t amount) { while(amount--) myVec.push_back(Irand{ 10, 99 }()); } void stealer(std::vector&& myVec, size_t amount) { while(amount--) myVec.push_back(Irand{ 10, 99 }()); printer(myVec); } int main() { /* # OUTPUT # 1 2 3 4 5 ---------- 1 2 3 4 5 48 81 42 ---------------- 1 2 3 4 5 48 81 42 61 23 73 ---------------- [0] */ std::vector myVec{ 1, 2, 3, 4, 5 }; printer(myVec); std::cout << "----------" << std::endl; std::thread tx{ filler, std::ref(myVec), 3 }; tx.join(); printer(myVec); std::cout << "----------------" << std::endl; std::thread ttx{ stealer, std::move(myVec), 3 }; ttx.join(); printer(myVec); std::cout << "----------------" << std::endl; std::cout << "[" << myVec.size() << "]" << std::endl; } * Örnek 13, //.. void fworker(const std::vector* vp) { std::cout << std::accumulate(vp->begin(), vp->end(), 0) << "\n"; // 'vp' gösterdiği 'vector' nesnesinin elemanlarının toplamı hesaplanacaktır. // Eğer üçüncü parametreye '100' geçilseydi, çıkan sonuç yüz fazla olacaktı. } void thread_starter() { std::vector ivec(1'00'000, 5); // OTOMATİK ÖMÜRLÜ YEREL NESNE. std::thread t{ fworker, &ivec}; // dikkat yerel nesne adresi t.detach(); // İş bu 'thread', 'main-thread' den bağımsız bir şekilde çalışacaktır. // 't' isimli 'thread' arka planda bağımsız bir şekilde çalışmaktadır ve 'fworker' // isimli fonksiyonu koşturmaktadır. Dolayısıyla 'vp' isimli gösterici 'dangling-pointer' olabilir // çünkü 'ivec' nesnesinin hayatı, toplama işleminin hesaplanmasından önce sona erebilir. } int main() { /* # OUTPUT # // Herhangi bir çıktı üretilemedi. */ thread_starter(); std::this_thread::sleep_for(std::chrono::seconds{ 5 }); } /*============================================================================================================*/ (48_concurrency_6) > 'std::thread' sınıfı KOPYALAMAYA KARŞI KAPATILMIŞ FAKAT TAŞIMAYA KARŞI AÇILMIŞ BİR SINIFTIR. Taşıma yapılması durumunda da kaynak olarak kullanılan 'thread' objesi 'joinable' OLMAKTAN ÇIKAR. Bir iş yükü olduğu durumlarda 'joinable' olmaktan çıkmaktadır. * Örnek 1, //.. void func() {} int main() { /* # OUTPUT # true false */ std::thread tx{ func }; std::cout << std::boolalpha << tx.joinable() << "\n"; std::thread ty{ move(tx) }; std::cout << std::boolalpha << tx.joinable() << "\n"; ty.join(); } * Örnek 2, //.. void func() {} int main() { /* # OUTPUT # 1 1 terminate called without an active exception */ std::thread tx{ func }; std::thread ty{ func }; std::cout << tx.joinable() << "\n"; std::cout << ty.joinable() << "\n"; ty = move(tx); // Halihazırda 'joinable' olana atama yapıldığında 'std::terminate()' fonksiyonu çağrılır. std::cout << "main devam ediyor\n"; } * Örnek 3, //.. std::thread make_thread() { return std::thread( [](){std::cout << "Necati Ergin!\n";} ); } std::thread func(std::thread t) { return t; } int main() { std::thread t0{ std::thread{ []{} } }; // Geçici nesne kullandığımız için 'copy-ellision' devreye girmektedir. std::thread t1(make_thread()); std::thread t2(std::move(t1)); t1 = std::move(t2); t1 = func(std::move(t1)); t1.join(); } > 'std::thread' nesnelerinin otomatik olarak 'join' edilmeleri: * Örnek 1, //.. int main() { /* # OUTPUT # true terminate called without an active exception */ std::thread tx{ []{} }; // 'tx' nesnesi için ne '.join()' ne de '.detach()' fonksiyonları çağrıldı. // Bu noktada 'tx' hala 'joinable'. Yani bir iş yükü var kendisinde. std::cout << std::boolalpha << tx.joinable() << std::endl; // Fakat bu noktada 'tx' için 'Dtor.' fonksiyonu çağrılacak ama 'tx' // hala 'joinable' olduğundan, 'std::terminate()' çağrılacaktır. } * Örnek 2.1, //.. void foo(){} void func(){} void bar() { std::thread tx{ foo }; // 'f' fonksiyonunun bir hata nesnesi gönderme ihtimali olduğunu varsayalım: // Senaryo - I try{ f(); } catch(...) { // Hata nesnesi yakalanmıştır... //... } tx.join(); // Programın akışı buraya geldiğinde ya hata nesnesi gönderilmemiştir ya da gönderilen hata nesnesi yakalanmış // ve ele alınmıştır. } int main() { /* # OUTPUT # //... */ bar(); // Yukarıdaki senaryo hem karışık hem de okuma konusunda zor. Çok daha iyi bir yöntem olan // RAII deyimini kullanabiliriz. Fakat bu deyimi güden ve Cpp20 ile dile eklenen 'std::jthread' // sınıfını da kullanabiliriz. } * Örnek 2.2, //.. class Jthread { public: Jthread(std::thread &t) : mt{t} {} ~Jthread() { if (mt.joinable()) mt.join(); } Jthread(const Jthread&) = delete; Jthread& operator=(const Jthread&) = delete; private: std::thread& mt; }; int main() { /* # OUTPUT # I am a lambda! */ // Yukarıdaki 'Jthread' sınıfını sadece ve sadece sol taraf değeri ile kullanabiliriz. std::thread tx{ []{ std::cout << "I am a lambda!" << std::endl; } }; Jthread jtx{ tx }; } * Örnek 2.3, //.. class scoped_thread { private: std::thread t; public: // Tek parametreli 'Ctor.' için varsayılan olarak 'explicit' yapmalıyız. explicit scoped_thread(std::thread t_) : t(std::move(t_)) { if (!t.joinable()) throw std::logic_error("No thread"); } ~scoped_thread() { t.join(); } scoped_thread(scoped_thread const&) = delete; scoped_thread& operator=(scoped_thread const&) = delete; }; void foo(int x) { std::cout << "x : " << x << std::endl; } void f() { int ival{31}; scoped_thread t{ std::thread(foo, ival)}; } int main() { f(); // OUTPUT => x : 31 } * Örnek 2.4, //.. class joining_thread { private: std::thread t; public: // Default Ctor. joining_thread() noexcept = default; // A Ctor. 'Variadic Template' kullanılmış. 'Callable' ile kastedilen, 'thread' nesnesinin koşturacağı 'callable'. // Parametre paketi ise koşturulacak şeye geçilecek argümanlar. // Parametre paketi açıldığında aslında 'Ctor.' şöyle olacak; // " ... t( std::forward(func), std::forward(arg1), std::forward(arg2), std::forward(arg3) ){} ..." template explicit joining_thread(Callable&& func, Args&& ... args) : t(std::forward(func), std::forward(args)...) {} bool joinable() const noexcept { return t.joinable(); } void join() { t.join(); } // 'Move Ctor.' fonksiyonumuz. Bu bildiirmden dolayı 'Copy Ctor.' fonksiyonu 'delete' EDİLDİ. joining_thread(joining_thread&& other) noexcept : t(std::move(other.t)) {} // 'Move Assignment' fonksiyonumuz. Bu bildiirmden dolayı 'Copy Assignment' fonksiyonu 'delete' EDİLDİ. joining_thread& operator=(joining_thread&& other) noexcept { if (joinable()) join(); t = std::move(other.t); return *this; } // Argüman olarak geçilen 'thread' nesnesi, veri elemanına taşınacaktır. explicit joining_thread(std::thread t_) noexcept : t(std::move(t_)) {} // Argüman olarak geçilen 'thread' nesnesi, veri elemanına taşınacaktır. joining_thread& operator=(std::thread other) noexcept { if (joinable()) join(); t = std::move(other); return *this; } // 'Dtor.' fonksiyonumuz. ~joining_thread() noexcept { if (joinable()) join(); } void swap(joining_thread& other) noexcept { t.swap(other.t); } std::thread::id get_id() const noexcept { return t.get_id(); } void detach() { t.detach(); } // A 'getter' for 'non-const joining_thread' objects. std::thread& as_thread() noexcept { return t; } // A 'getter' for 'const joining_thread' objects. const std::thread& as_thread() const noexcept { return t; } }; int main() { joining_thread tx{ [](char c, int i, double d){ std::cout << c << i << d << std::endl; }, 'a', 29, 13.31 }; // OUTPUT => a2913.31 } > 'concurrency in action' by Anthony Williams, 2019 yılında yayınlanan sürümü. /*============================================================================================================*/ (49_concurrency_7) > Aşağıdaki Örnekleri İnceleyelim: * Örnek 1, //.. uint64_t sum_even = 0; uint64_t sum_odd = 0; constexpr uint64_t n = 10'000'000'000; void get_sum_even() { for(uint64_t i = 0; i < n; i +=2) sum_even += i; } void get_sum_odd() { for(uint64_t i = 1; i < n; i +=2) sum_odd += i; } int main() { /* # OUTPUT # Time Passed: 19.8453 Sum of Even Numbers : 6553255921290448384 Sum of Odd Numbers : 6553255926290448384 */ auto start = std::chrono::steady_clock::now(); get_sum_even(); get_sum_odd(); auto end = std::chrono::steady_clock::now(); std::cout << "Time Passed: " << std::chrono::duration{ end - start }.count() << std::endl; std::cout << "Sum of Even Numbers : " << sum_even << std::endl; std::cout << "Sum of Odd Numbers : " << sum_odd << std::endl; } * Örnek 2, //.. uint64_t sum_even = 0; uint64_t sum_odd = 0; constexpr uint64_t n = 10'000'000'000; void get_sum_even() { for(uint64_t i = 0; i < n; i +=2) sum_even += i; } void get_sum_odd() { for(uint64_t i = 1; i < n; i +=2) sum_odd += i; } int main() { /* # OUTPUT # Time Passed: 16.3162 Sum of Even Numbers : 6553255921290448384 Sum of Odd Numbers : 6553255926290448384 */ auto start = std::chrono::steady_clock::now(); std::thread tx{ get_sum_even }; std::thread ty{ get_sum_odd }; tx.join(); ty.join(); auto end = std::chrono::steady_clock::now(); std::cout << "Time Passed: " << std::chrono::duration{ end - start }.count() << std::endl; std::cout << "Sum of Even Numbers : " << sum_even << std::endl; std::cout << "Sum of Odd Numbers : " << sum_odd << std::endl; } > Nesnelerin Ömürleri: Şu ana kadar nesnelerimiz "automatic storage", "static storage" ve "dynamic storage" şeklindeydi. Cpp11 ile dile artık "Thread-Local Storage" şeklinde yeni bir ömür kategorisi daha eklendi. Bu ömür kategorisinde değişkenler hayata getirmek için 'thread_local' anahtar sözcüğünü kullanıyoruz. Statik ömürlü ama bildiğimiz şekliyle değil; 'thread' nesnesi hayata geldiğinde hayata gelmektedir. Dolayısıyla 'thread' e özgüdür. * Örnek 1, //.. std::mutex mtx; // Detayları ilerleyen derslerde açıklanacaktır. thread_local int tg = 0; // Bir nevi statik ömürlü ama her 'thrad' kendi kopyasını oluşturmakta. void func() { ++tg; std::lock_guard guard(mtx); // Detayları ilerleyen derslerde açıklanacaktır. std::cout << "tg : " << tg << std::endl; } int main() { /* # OUTPUT # tg : 1 tg : 1 tg : 1 tg : 1 */ ++tg; std::cout << "tg : " << tg << std::endl; std::thread tx{ func }; std::thread ty{ func }; std::thread tz{ func }; tx.join(); ty.join(); tz.join(); // Çıktıdan da görüldüğü üzere 'th' değişkeni her 'thrad' için ayrıdır. } * Örnek 2, //.. std::mutex mtx; void func(int id) { int x{}; // Otomatik ömürlü. static int y{}; // Statik ömürlü. thread_local int z{}; // Ömrü, 'thread' e bağlı. // Otomatik ömürlü ve 'thread_local' ömür kategorisinde olan değişkenler // her 'thrad' için ayrı olacağından, 'data-racing' söz konusu değildir. // Her 'thread' için bir kopya oluşturulur. ++x; ++z; // Fakat statik ömürlü değişkenler için böyle bir şey söz konusu olmadığından // 'data-racing' e karşı bir koruma yapmamız gerekmektedir. Bütün 'thread' // nesneleri bu değişkeni paylaşmaktadır. std::lock_guard guard{ mtx }; // Bir 'thread' bu kodu yürüttünde, diğerleri bloke olmak zorundadır. ++y; std::cout << "[" << id << "]" << std::endl; std::cout << "(auto storage), x : " << x << std::endl; std::cout << "(static storage), y : " << y << std::endl; std::cout << "(thread_local storage), z : " << z << std::endl << std::endl; } int main() { /* # OUTPUT # [0] (auto storage), x : 1 (static storage), y : 1 (thread_local storage), z : 1 [2] (auto storage), x : 1 (static storage), y : 2 (thread_local storage), z : 1 [1] (auto storage), x : 1 (static storage), y : 3 (thread_local storage), z : 1 [3] (auto storage), x : 1 (static storage), y : 4 (thread_local storage), z : 1 */ std::thread a{ func, 0 }; std::thread b{ func, 1 }; std::thread c{ func, 2 }; std::thread d{ func, 3 }; a.join(); b.join(); c.join(); d.join(); } * Örnek 3, //.. std::mutex mtx; void func(int id) { int x{}; // Otomatik ömürlü. static int y{}; // Statik ömürlü. thread_local int z{}; // Ömrü, 'thread' e bağlı. // Otomatik ömürlü ve 'thread_local' ömür kategorisinde olan değişkenler // her 'thrad' için ayrı olacağından, 'data-racing' söz konusu değildir. // Her 'thread' için bir kopya oluşturulur. ++x; ++z; // Fakat statik ömürlü değişkenler için böyle bir şey söz konusu olmadığından // 'data-racing' e karşı bir koruma yapmamız gerekmektedir. Bütün 'thread' // nesneleri bu değişkeni paylaşmaktadır. std::lock_guard guard{ mtx }; // Bir 'thread' bu kodu yürüttünde, diğerleri bloke olmak zorundadır. ++y; std::cout << "[" << id << "]" << std::endl; std::cout << "(auto storage), x : " << x << std::endl; std::cout << "(static storage), y : " << y << std::endl; std::cout << "(thread_local storage), z : " << z << std::endl << std::endl; } void foo(int id) { func(id); func(id); func(id); func(id); } int main() { /* # OUTPUT # [1] (auto storage), x : 1 (static storage), y : 1 (thread_local storage), z : 1 [1] (auto storage), x : 1 (static storage), y : 2 (thread_local storage), z : 2 [1] (auto storage), x : 1 (static storage), y : 3 (thread_local storage), z : 3 [1] (auto storage), x : 1 (static storage), y : 4 (thread_local storage), z : 4 [2] (auto storage), x : 1 (static storage), y : 5 (thread_local storage), z : 1 [2] (auto storage), x : 1 (static storage), y : 6 (thread_local storage), z : 2 [2] (auto storage), x : 1 (static storage), y : 7 (thread_local storage), z : 3 [2] (auto storage), x : 1 (static storage), y : 8 (thread_local storage), z : 4 [0] (auto storage), x : 1 (static storage), y : 9 (thread_local storage), z : 1 [0] (auto storage), x : 1 (static storage), y : 10 (thread_local storage), z : 2 [0] (auto storage), x : 1 (static storage), y : 11 (thread_local storage), z : 3 [0] (auto storage), x : 1 (static storage), y : 12 (thread_local storage), z : 4 [3] (auto storage), x : 1 (static storage), y : 13 (thread_local storage), z : 1 [3] (auto storage), x : 1 (static storage), y : 14 (thread_local storage), z : 2 [3] (auto storage), x : 1 (static storage), y : 15 (thread_local storage), z : 3 [3] (auto storage), x : 1 (static storage), y : 16 (thread_local storage), z : 4 */ std::thread a{ foo, 0 }; std::thread b{ foo, 1 }; std::thread c{ foo, 2 }; std::thread d{ foo, 3 }; a.join(); b.join(); c.join(); d.join(); } * Örnek 4, //.. class Functor{ public: void operator()() { std::cout << "void Functor::operator()()" << std::endl; } }; int main() { /* # OUTPUT # */ // Most Vexing Parse std::thread t(Functor()); // std::thread t(Functor(*)()); t.join(); // Sentaks Hatası } /*============================================================================================================*/ > Genel Alıştırma Örnekleri, * Örnek 1, #include #include #define myFuncMacro(void) std::cout << "void myFunc(void) was called.\n"; void myFunc(void) { std::cout << "void myFunc(void) was called.\n"; } class myFuncClass{ public: void operator()(void) { std::cout << "void myFunc(void) was called.\n"; } }; int main() { /* # OUTPUT # void myFunc(void) was called. void myFunc(void) was called. void myFunc(void) was called. void myFunc(void) was called. void myFunc(void) was called. void myFunc(void) was called. void myFunc(void) was called. */ // Normal Fonksiyon Çağrısı myFunc(); // Fonksiyon Göstericisi Kullanarak auto myFuncPtr{ myFunc }; myFuncPtr(); // Fonksiyonel Makro Kullanarak myFuncMacro(); // Lambda Deyimi Kullanarak auto myFuncLambda = []{ std::cout << "void myFunc(void) was called.\n"; }; myFuncLambda(); // 'Functor' Kullanarak myFuncClass{}(); // 'std::function' Kullanarak std::function myFunction = myFunc; myFunction(); // 'std::bind' Kullanarak: std::bind(myFunc)(); return 0; } * Örnek 2, //.. class MoveOnly{ public: MoveOnly() = default; MoveOnly(MoveOnly&&) {} MoveOnly& operator=(MoveOnly&&) { return *this; } }; int main() { MoveOnly a; // MoveOnly b{a}; // error: use of deleted function 'constexpr MoveOnly::MoveOnly(const MoveOnly&)' MoveOnly b; // b = a; // error: use of deleted function 'constexpr MoveOnly::MoveOnly(const MoveOnly&)' MoveOnly c{ std::move(b) }; } * Örnek 3, //.. class CopyOnly{ public: CopyOnly() = default; CopyOnly(CopyOnly&& ) = delete; CopyOnly& operator=(CopyOnly&&) = delete; CopyOnly(const CopyOnly& ) {} CopyOnly& operator=(const CopyOnly&) { return *this; } }; int main() { CopyOnly a; CopyOnly b{a}; b = a; // CopyOnly c{ std::move(b) }; // error: use of deleted function 'CopyOnly::CopyOnly(CopyOnly&&)' } * Örnek 4, //.. template class Myclass{}; int main() { auto f = [](int x){ return x*x; }; // Myclass mx; // note: expected a type, got ‘f’ Myclass mx; // OK using g = int; Myclass my; // OK } * Örnek 5, //.. #include int& foo(int x) { return x; // MSVC : warning C4172: returning address of local variable or temporary: x } int main() { /* # OUTPUT # (5) : warning C4172: returning address of local variable or temporary: x cl : Command line warning D9002 : ignoring unknown option '-std=c++2b' Execution build compiler returned: 0 Program returned: 0 i. x : 31 ii. x : 62 iii. x : 62 */ int x = 31; auto& y = foo(x); std::cout << "i. x : " << x << std::endl; x += 31; std::cout << "ii. x : " << x << std::endl; y += 62; std::cout << "iii. x : " << x << std::endl; return 0; } * Örnek 6, //.. #include #include #include template std::ostream& operator<<(std::ostream& os, const std::array& array) { os << "[ " << array.front() << ", "; for(std::size_t i{1}; i < size - 1; ++i) os << array[i] << ", "; os << array.back() << " ]\n"; return os; } template std::ostream& operator<<(std::ostream& os, const std::vector& array) { os << "[ " << array.front() << " | "; for(std::size_t i{1}; i < array.size() - 1; ++i) os << array[i] << " | "; os << array.back() << " ]\n"; return os; } int main() { std::array arx{ 1, 2, 3, 4, 5 }; std::cout << arx; std::vector ary{ 5, 6, 7, 8, 9 }; std::cout << ary << std::endl; return 0; } * Örnek 7, //.. #include #include struct Myclass{ Myclass(int a, int b, int c) : _a(a), _b(b), _c(c) {} int _a, _b, _c; }; Myclass funcOne(int a, int b, int c) { return Myclass{a, b, c}; } Myclass funcTwo(int a, int b, int c) { return {a, b, c}; } template Myclass funcThree(Args&& ... args) { return Myclass{ std::forward(args)... }; } std::ostream& operator<<(std::ostream& os, const Myclass& other) { return os << "[ " << other._a << ", " << other._b << ", " << other._c << " ]"; } int main() { /* # OUTPUT # [ 1, 2, 3 ] [ 10, 20, 30 ] [ 100, 200, 300 ] */ auto objOne{ funcOne(1, 2, 3) }; std::cout << objOne << std::endl; auto objTwo{ funcTwo(10, 20, 30) }; std::cout << objTwo << std::endl; auto objThree{ funcThree(100, 200, 300) }; std::cout << objThree << std::endl; return 0; } * Örnek 8, //.. /* * Creating a temporary variable, * Integral-promotion, * Move semantics */ #include #include #include uint16_t doit(uint16_t&& value) { return value * 160; } uint32_t doit(uint32_t&& value) { return value * 320; } int main() { uint16_t value16 = 1; uint32_t value32 = 1; printf("(%d)\n", doit(value16)); // OUTOUT => (320) printf("(%d)\n", doit(value32)); // OUTOUT => (160) return 0; } * Örnek 9, //.. /* * Find the max_of_four */ #include #include int max_of_four(int a, int b, int c, int d) { return std::max(a, std::max(b, std::max(c, d))); } int main() { int a, b, c, d; scanf("%d %d %d %d", &a, &b, &c, &d); int ans = max_of_four(a, b, c, d); printf("%d", ans); return 0; } * Örnek 10, //.. /* * https://www.hackerrank.com/challenges/variable-sized-arrays/problem */ #include #include #include #include #include using namespace std; int main() { unsigned int rows, questions; cin >> rows >> questions; vector> vectorsOfData; for (unsigned int indexOuter{}; indexOuter < rows; ++indexOuter) { unsigned int iVecSize; cin >> iVecSize; vector iVec; for (unsigned int indexInner{}; indexInner < iVecSize; ++indexInner) { unsigned int iVecValue; cin >> iVecValue; iVec.push_back(iVecValue); } vectorsOfData.push_back(iVec); } unsigned int whichArray, whichColomn; for (unsigned int index = 0; index < questions; ++index) { cin >> whichArray >> whichColomn; cout << vectorsOfData[whichArray][whichColomn] << "\n"; } return 0; } * Örnek 11, //.. #include struct A{ A() { f(); } virtual void f() && { std::cout << "void f::A() &&" << std::endl; } virtual void f() & { std::cout << "void f::A() &" << std::endl; } }; struct B : public A{ B() { f(); } virtual void f() && { std::cout << "void f::B() &&" << std::endl; } virtual void f() & { std::cout << "void f::B() &" << std::endl; } }; int main() { /* # OUTPUT # void f::A() & void f::B() & void f::B() && */ B{}.f(); } * Örnek 12, //.. /* * Rule of Four : A Dtor, A Copy Ctor, A Move Ctor & Assignment Operator by Value */ class NaiveVector{ public: // Copy Ctor. NaiveVector(const NaiveVector& rhs) { size_ = rhs.size_; ptr_ = new int[rhs.size_]; std::copy(rhs.ptr_, rhs.ptr_ + size_, ptr_); } // Move Ctor. NaiveVector(NaiveVector&& rhs) noexcept { size_ = std::exhange(rhs.size_, 0); ptr_ = std::exhange(rhs.ptr_, nullptr); } // An Assignment Operator by Value NaiveVector& operator=(NaiveVector rhs) noexcept { rhs.swap(*this); return *this; } // Dtor. ~NaiveVector() noexcept { delete[] ptr_; } // An ADL overload "swap" function. This function is not a MEMBER FUNCTION. friend void swap(NaiveVector& a, NaiveVector& b) noexcept { a.swap(b); } // A member "swap" function. void swap(NaiveVector& rhs) noexcept { using std::swap; swap(ptr_, rhs.ptr_); swap(size_, rhs.size_); } private: size_t size_; int* ptr_; }; * Örnek 13, //.. /* * Closer-to-Rule-of-Zero */ class NaiveVector{ public: // Copy Ctor. NaiveVector(const NaiveVector& rhs) { size_ = rhs.size_; uptr_ = std::make_unique(rhs.size_); std::copy(rhs.uptr_, rhs.uptr_ + size_, uptr_); } // Move Ctor. NaiveVector(NaiveVector&& rhs) noexcept = default; // An Assignment Operator by Value NaiveVector& operator=(NaiveVector rhs) noexcept { rhs.swap(*this); return *this; } // Dtor. ~NaiveVector() noexcept = default; // An ADL overload "swap" function. This function is not a MEMBER FUNCTION. friend void swap(NaiveVector& a, NaiveVector& b) noexcept { a.swap(b); } // A member "swap" function. void swap(NaiveVector& rhs) noexcept { using std::swap; swap(ptr_, rhs.ptr_); swap(size_, rhs.size_); } private: size_t size_; std::unique_ptr uptr_; }; * Örnek 14, //.. /* * True-Rule-of-Zero => "Rule of Zero" is about resource management, not about * how do I create an object. For that reason, we can have multiple ctor. functions * with different parameters. */ class NaiveVector{ public: // Copy Ctor. NaiveVector(const NaiveVector& rhs) = default; // Move Ctor. NaiveVector(NaiveVector&& rhs) noexcept = default; // An Assignment Operator by Value NaiveVector& operator=(NaiveVector rhs) noexcept { rhs.swap(*this); return *this; } /* * However, we can also write "NaiveVector& operator=(NaiveVector&& rhs) = default;" * and "NaiveVector& operator=(const NaiveVector& rhs) = default;" as an alternative. * Because, if we have multiple data members, the defaulted assignment operators will * assign them member-by-member (a.k.a member-wise assignment). This may cause some * problems. */ // Dtor. ~NaiveVector() noexcept = default; // An ADL overload "swap" function. This function is not a MEMBER FUNCTION. friend void swap(NaiveVector& a, NaiveVector& b) noexcept { a.swap(b); } // A member "swap" function. void swap(NaiveVector& rhs) noexcept { vec_.swap(rhs.vec_); } private: std::vector vec_; }; * Örnek 15, //.. /* * Passing lambdas to template functions. * Capture-all-by-value in lambdas. */ #include template auto myFoo(T t) { return t(); } int main() { double divided = 100.; int divisor = 3; auto myLambda = [=](){ return divided / divisor; }; std::cout << "[" << myLambda() << "]\n"; // [33.3333] std::cout << "[" << myFoo(myLambda) << "]\n"; // [33.3333] return 0; } * Örnek 16, /* * Run Time Type Polymorphism : using 'virtual' keyword in Base class and * Base class Dtor. */ #include class Base{ public: Base() { std::cout << "Base::Base()\n"; } virtual void Activate() { std::cout << "Base::Activate()\n"; } virtual ~Base() { std::cout << "Base::~Base()\n"; } }; class Derived : public Base{ public: Derived() { std::cout << "Derived::Derived()\n"; } void Activate() override { std::cout << "Derived::Activate()\n"; } ~Derived() { std::cout << "Derived::~Derived()\n"; } }; int main() { /* # OUTPUT # Base::Base() Derived::Derived() Derived::Activate() Derived::~Derived() Base::~Base() */ Base* myBasePtr = new Derived; myBasePtr->Activate(); delete myBasePtr; } * Örnek 17, //.. /* * An Alternative for Run Time Type Polymorphism : Forbiding the function * calls using Base class type pointers. */ #include class Base{ public: Base() { std::cout << "Base::Base()\n"; } void Activate() { std::cout << "Base::Activate()\n"; } protected: ~Base() { std::cout << "Base::~Base()\n"; } }; class Derived : public Base{ public: Derived() { std::cout << "Derived::Derived()\n"; } void Activate() { std::cout << "Derived::Activate()\n"; } ~Derived() { std::cout << "Derived::~Derived()\n"; } }; int main() { /* # OUTPUT # : In function 'int main()': :37:12: error: 'Base::~Base()' is protected within this context 37 | delete myBasePtr; | ^~~~~~~~~ :11:9: note: declared protected here 11 | ~Base() { std::cout << "Base::~Base()\n"; } | ^ */ Base* myBasePtr = new Derived; myBasePtr->Activate(); delete myBasePtr; // err line. /* # OUTPUT # Base::Base() Derived::Derived() Derived::Activate() Derived::~Derived() Base::~Base() */ Derived* myDerivedPtr = new Derived; myDerivedPtr->Activate(); delete myDerivedPtr; } * Örnek 18, //.. /* * An Alternative for Run Time Type Polymorphism : CRTP */ #include template class Base{ public: void Activate() { T& derived = static_cast(*this); derived.DerivedActivate(); } }; class Derived : public Base { public: void DerivedActivate() { std::cout << "Derived, from Base\n"; } }; class DerivedDerived : public Base { public: void DerivedActivate() { std::cout << "DerivedDerived, from Base\n"; } }; int main() { Base typeOne; typeOne.Activate(); // OUTPUT => Derived, from Base Base typeTwo; typeTwo.Activate(); // OUTPUT => DerivedDerived, from Base } * Örnek 19, //.. /* * Template examples */ #include #include #include #include // Allias Template template using RemoveReference_t = typename std::remove_reference::type; // Allias Template template using gset = std::set>; // Class Template template struct X { constexpr static int value = 500; }; // Template template constexpr auto X_v = X::value; int main() { /* # OUTPUT # ---------- 5 4 3 2 1 ---------- 31 ---------- 500 ---------- */ std::cout << "----------" << std::endl; // Allias Template gset x_Allias{ 1, 2, 3, 4, 5, 5, 4, 3, 2, 1}; for (auto index : x_Allias) { std::cout << index << std::endl; } std::cout << "----------" << std::endl; // Allias Template RemoveReference_t x_Allias_Two{31}; std::cout << x_Allias_Two << std::endl; std::cout << "----------" << std::endl; // Class Template constexpr auto ival = X_v; std::cout << ival << std::endl; std::cout << "----------" << std::endl; } * Örnek 20, /* A pointer to private data member of a class. */ #include class MyClass { int i{21}; public: void print(void) { std::cout << "[" << i << "]" << std::endl; } }; int main() { /* # OUTPUT # [21] [21] [32] */ MyClass obj; obj.print(); int* ptr; ptr = (int*)&obj; // ptr = static_cast(&obj); // error: invalid static_cast from type ‘MyClass*’ to type ‘int*’ std::cout << "[" << *ptr << "]" << std::endl; *ptr = 32; obj.print(); return 0; } /*============================================================================================================*/