Algorithm | std::iota


std::iota C++11 ile gelen bir özelliktir. <numeric> başlık dosyasının içinde tanımlı olarak gelmektedir. Kısaca ardışıl ifadeler üretmek ve bunları container benzeri veri yapılarına atama yapmak için kullanılır. 

Temel bir std::iota implementasyonu
Yukarıda şablon türünde implementasyonu yapılan örnekte görüleceği üzere en az forward türünde bir Iterator 'ü parametre olarak almaktadır. Algoritmamız aldığı Iterator aralığındaki öğeleri value değerinden başlayarak ardışık değerlerle doldurmaktadır.

std:iota örneği - 1
Yukarıdaki örnekte iota'ya int dizisinin başlangıç ve bitiş adresleri verilmiş ve value olarak 10 değeri girilmiştir. Buradan şu çıkarımı yapabiliriz ki
  • numbers[0] = 10, 
  • numbers[1] = 11, 
  • numbers[2] = 12, 
  • numbers[3] = 13, 
  • numbers[4] = 14 olarak atama yapılacaktır. Ardından ekrana yazdırılarak işlemin doğru bir şekilde gerçekleştiği görülebilir.

std:iota örneği - 2 

Yukarıdaki örnekte iota'ya list'in başlangıç ve bitiş adresleri verilmiş ve value olarak 1 değeri girilmiştir. Buradan da benzer çıkarımı yapabiliriz ki ilist[0] = 1, ilist[1] = 2, ilist[2] = 3, ilist[3] = 4, ... , ilist[99] = 100 olarak atama yapılacaktır. Ardından burada da ekrana yazılarak işlemin doğru bir şekilde gerçekleştiği görülebilir.

std:iota örneği - 3   
Yukarıdaki örnekte ise diğerlerinden farklı olan nokta ise std::copy ile ekrana yazdırma işlemini gerçekleştirilmiş olmasıdır.

std:iota örneği - 4

std::iota'nın kullanım senaryolarının kısıtlı olmasından dolayı (Örneğin: 3 er 3 şer ardışıllık sağlamak gibi) yukarıda kendi yazacağımız sınıfın operator++ fonksiyonu sayesinde iota'nın bizim istediğimiz gibi çalışmasını sağlamış oluruz.

std:iota örneği - 4 (devam)

Burada kritik işleme sahip nokta yazdığımız sınıfın operator++'nın dışarıdan alınan lambda'yla ayarlanmasıdır. Yukarıda main kısmında görülebileceği üzere vector'ün içine şablon sınıfımızı kullanarak 3'li artan elemanlar eklenmiş olacaktır.


STL Containers | Vector - 1

Containerlar içinde en çok kullanılan veri yapısı olan std::vector, ardışıl bir veri yapısıdır. Yani dizinin elemanları hafızada birbirini takip ederek konumlanır. Aynı zamanda dinamik bir veri yapısıdır. Bu sayede doğrudan dizinin boyutunu büyütmemize gerek yoktur. Kendisi arka planda algoritmik olarak  dizinin boyutunu ihtiyaç oldukça arttırmaktadır.

begin(), rbegin(), end(), rend() fonksiyonlarının iteratör konumları
std::vector<T>::iterator  RandomAccessIterator türündendir. Ve RandomAccessIterator bir BidirectionalIterator olduğundan dolayı
sabit zamanda elemana erişim,
iki yönlü hareket edebilme
iteratör'ü sabit bir değerle işleme sokabilme olanağı vardır.

std::vector | İlklendirme (Initializing)

vector veri yapısı hayata getirilirken birden fazla biçimde gerçekleştirilebilir. Aşağıda 6 farklı biçimde hayata getirilmesi gösterilmiştir.

std::vector | Initializing örneği
  • 6. numaralı satır : varsayılan hayata getirme yöntemidir. ivec1 içinde eleman yoktur ama belli bir kapasitede yer ayrılmıştır.
  • 8. satır : initializer list aracılığıyla hayata geçirme yöntemidir. ivec2 içinde 5 eleman ve belli bir kapasitede yer ayrılmıştır.
  • 11. satır : uniform initialization şeklinde hayata geçirme yöntemidir. C++11 ile gelmiştir. ivec3 içinde 5 eleman ve belli bir kapasitede yer ayrılmıştır.
  • 14. satır : Başka bir vector aracığılıyla hayata geçirme biçimidir. Verilen iki iteratör'dön birincisi başlangıç, ikincisi bitiş iteratör değeri olarak [X,Y) elemanları vektöre yerleştirir.  
  • 17. satır : 3 elemanlı bir vektör oluşturur. Ve T değerinin varsayılan değeri neyse vektör onunla hayata başlar. Yukarıdaki örnekte int olduğu için 3 elemanlı ve sıfırlardan oluşan bir vektör hayata getirilir.
  • 20. satır : 17. satırdakine benzer şekilde 3 elemanlı bir vektör oluşturur. Fakat elemanlar bu sefer sıfır değil 3'ü de -1 olarak vektör hayata getirilir.

std::vector | Elemanlara Erişim (Element access)

vector veri yapısı içindeki elemanlara erişmemizi sağlayan birden fazla yöntem mevcuttur. Aşağıda tüm erişim sağlayan yöntemler görülebilir.

std::vector | element access
Yukarıdaki tüm fonksiyonları içine alan bir erişim örneği aşağıdaki gibidir:

std::vector | elemanlara erişim örneği
Burada at ile [] erişimleri arasındaki temel fark at'in std::out_of_range exception'ı fırlatmasıdır. Bu sebeple operator[] yerine at'in kullanılması daha güvenli kod yazılmasını sağlayacaktır.
Program çıktısı

std::vector | Kapasite (Capacity)

vector veri yapısının eleman sayısı/kapasite bilgilerine erişmenin ve kapasite ile ilgili direktif vermenin birden fazla yöntemi mevcuttur. Aşağıda tüm yöntemler görülebilir.

std::vector | capacity
Yukarıdaki tüm fonksiyonları içine alan bir kapasite örneği aşağıdaki gibidir:

std::vector | kapasite örneği
Yukarıdaki örnekte eleman sayısı iki katına çıkınca kapasiteninde 2 katına çıktığı görülmektedir.

Program çıktısı

std::vector | Elemanları Düzenleme (Modifiers)


vector veri yapısında eleman eklenmesi/silinmesini ya da değiştirilmesini sağlayan birden fazla yöntem mevcuttur. Aşağıda tüm yöntemler görülebilir.

std::vector | modifiers
Yukarıdaki fonksiyonları içine alan iki kapasite örneği aşağıdaki gibidir:

std::vector | elemanları düzenleme örneği - 1
Yukarıda push_back ile sondan ekleme, pop_back ile sondan silme, insert ile konum bazlı ekleme, erase ile konum bazlı silme ve aralık silme yapılmıştır.

Program çıktısı
Aşağıdaki örnekte ise yukardakine ek olarak resize ile silme, swap ile container'ları yer değiştirme, clear ile container'ı boşaltma  işlemleri yapılmıştır.

std::vector | elemanları düzenleme örneği - 2
14, 20, 24. satırlardaki container'ı ekrana yazmak için kullanılan kod parçaları tek satıra indirgemek için farklı bir formata çekilmiştir. Kodun anlaşılabilirliği açısından sıkıntılı olan bu biçimde kod yazmamaya dikkat edin.
Program çıktısı

Note:

Aşağıdaki örnek ise reserve, assign, front, back ve empty fonksiyonlarına ilişkindir.

std::vector | örneği
reserve ile istenilen kapasitede bir alan tahsis edilecek ama yer açtığı alanın içine herhangi bir ilk değer verme işlemi gerçekleştirilmeyecektir, assign ile vector içindeki değerler yenileri ile yer değiştirecek ve kapasite ona göre ayarlanacaktır, front ile ön taraftaki değeri back ile arka taraftaki değeri geri döndürür, ve empty ile container'ın boş olup olmadığı kontrolünü yapar.
Program çıktısı
Yukarıdaki örnekte ivec2 clear ile silindiği için empty çağrısı true değer döndürmüştür.


Algorithm | search, search_n

C++'da bir veri yapısı içinde arama yapmak için kullanılabilecek std::search, std::search_n algoritmalarını sırasıyla anlatmaya çalışalım.

Algorithm | search

search algoritmasının template bildirimi
std::search algoritmasının çalışma prensibi bir veri yapısı içinde başka bir veri yapısının elemanlarını ardışıl olarak arama ve bulduğunda ilk eşitliği sağlayan değerin iteratör değeri ile geri dönmesi üzerinedir. search çağrısı sırasında verilen ilk iki parametre aranılan veri yapısına ilişkin aralık belirten iteratörler, sonraki iki parametre ise aranacak veri yapısına ilişkin başlangıç - bitiş iteratör değerleridir. Ve 5. parametre olarak Binary Predicate bir callable ifade de karşılaştırma için yazılabilmektedir.

Algorithm | search örneği - 1

Yukarıdaki örnekte ivecB içinde ardışıl olarak 2, 3, 5, 7 değerleri aranır ve bulunursa indeks değeri ile geri dönmektedir. Aşağıda ise programın ekran çıktısını görebiliriz.

Örnek - 1 : Çıktı

Aşağıdaki örnekte ivecB içinde ardışıl olarak 2, 3, 5, 7 değerleri aranır ve bulunursa indeks değeri ile geri dönmektedir. Bu örnekte yukarıdakinden faklı olarak binary predicate bir lambda ifadesi yazılmıştır.

Algorithm | search örneği - 2
Aşağıda ise programın ekran çıktısını görebiliriz.

Örnek - 2 : Çıktı

Algorithm | search_n

search_n algoritmasının template bildirimi
std::search_n algoritmasının çalışma prensibi bir veri yapısı içindeki elemanların tekrar edip etmediğini arama üzerinedir. Burada minimum kaç tekrarı ve hangi elemanı tekrar ettiğini parametre olarak almaktadır. search_n çağrısı sırasında verilen ilk iki parametre aranılan veri yapısına ilişkin aralık belirten iteratörler, sonraki iki parametre ise aranacak verinin kaç kez tekrar ettiği ve hangi elemanın tekrar edip etmediği bilgisinin girildiği değerlerdir. Ve 5. parametre olarak Binary Predicate bir callable ifade de karşılaştırma için yazılabilmektedir.

Algorithm | search_n örneği - 1
Yukarıdaki örnekte 30 değerinin 2 kez tekrar ettiği noktayı ivec vektörü içinde bulmaya çalışan programı yazdık. Aşağıda ise programın ekran çıktısını görebiliriz.

Örnek - 1 : Çıktı

Aşağıdaki örnekte bir string ifade içinde . değerinin 2 kez tekrar ettiği noktayı bulmaya çalışan programı yazdık.

Algorithm | search_n örneği - 2
 Aşağıda ise programın ekran çıktısını görebiliriz.

Örnek - 2 : Çıktı


Çalışma Zamanı Çokbiçimliliği ve Sanal Fonksiyonlar (Runtime polymorphism and virtual functions) - 1

C++ ve aslında nesneye dayalı programlamada karşımıza çıkan çalışma zamanı çokbiçimliliğini sağlayabilmek için kullanacağımız virtual anahtar kelimesi hangi nedenlerle kullanılmaktadır? Kullanım biçimleri nelerdir? sorularını cevaplamadan önce basitçe şunu söylemek gerekirse:

Polymorphism türleri
Polymorphism temel olarak ikiye ayrılmaktadır.
  1. Derleme zamanı çokbiçimliliği (Fonksiyon ve operatör aşırı yüklenmesi ile)
  2. Çalışma zamanı çokbiçimliliği (Sanal ve saf sanal fonksiyon ile)

Aşağıda anlatacağımız yöntemlerin tamamı çalışma zamanı çokbiçimliliğine ilişkindir. Aşağıdaki örnekte taban ve türeyen sınıf içinde foo isimli fonksiyonlar bulunmakta ve türeyen sınıfın taban sınıfın referansı ile adreslenebildiği görülebilmektedir. Buna literatürde Liskov’un Yerine Geçme Prensibi (Liskov Substitution Principle) denilmekte ve bu prensibe göre Alt sınıflardan oluşturulan nesneler üst sınıfların nesneleriyle yer değiştirdiklerinde / işaretçileriyle adreslendiğinde nesnenin kendi davranışını göstermek zorunda olduklarını belirtir. Peki aşağıdaki örnek bu prensibe uygun mudur?


Çalıştırılıp çıktıya bakılırsa iki durumda da taban sınıfın foo() fonksiyonu çağrılmaktadır. Bu durumda çok biçimlilik ilkesi gerçekleşmemiş her durumda taban sınıfın fonksiyonu çağrılmıştır. Bu durum nasıl düzeltebilir? derseniz karşımıza virtual anahtar kelimesi çıkar. Taban sınıfın ilgili fonksiyonu türeyen sınıfta override edileceğini belirtmek için virtual anahtar kelimesi kullanılmış türeyen sınıfta ise C++11 ile gelen override anahtar kelimesi ile (Zorunlu değil yazmazsak benzer davranışı gösterir ama yazılması tavsiye edilmektedir.) fonksiyon override edilmiştir.


Şimdi programı çalıştırıp çıktıya bakılırsa bu durumda istenen çıktının sağlandığı ve 24. kod satırı için Base::Foo, 25. satır için Derived::Foo çıktısı alınacaktır. Yukarıdaki örnekte türeyen sınıfın overide edilip edilmeyeceğine ilişkin bir zorunluluk bulunmamaktadır. Ama türeyen sınıfa böyle bir zorunluluk getirmek istenirse saf sanal (pure virtual) 'lığın kullanılması gerekmektedir. Bunun için yapılması gereken fonksiyon bildirimi ve =0; biçiminde aşağıdaki gibi kullanılması gerekmektedir.


Bu durumda türeyen sınıfta override edilmezse aşağıdaki gibi derleme hatası ile karşılaşılmaktadır. Bu noktada C++’ta saf sanal fonksiyonların bulunduğu sınıflara Java/C# 'taki gibi abstract sınıflar diyebiliriz ve benzer şekilde abstract sınıflar üzerinden nesne yaratılamamaktadır.


Aşağıda abstract class ve türeyen sınıfı içeren kod yazıldığında sorunsuz çalıştığı ve beklenen çıktının alındığı görülmektedir.


Son olarak çok biçimliliğe ve çok biçimliliğin faydalarına ilişkin bir örnek vermek gerekirse aşağıdaki örnekte olduğu gibi Caller fonksiyonuna yapılan çağrının, farklı nesnelerde nesnenin tipine bağlı olarak farklı fonksiyonların çağrılmasını sağlamıştır. Ve bu sayede çok biçimlilik sağlanmıştır. Ve uzun vadede türeyen sınıfın üstüne bir tane daha türetme yapılsa bile Caller fonksiyonunda bir değişiklik yapmaya gerek olmamaktadır ve geleceğe uyumlu kod yazabilme olanağı sağlamıştır.


Bitirirken şu noktaya dikkat çekmek gerekirse çalışma zamanı çok biçimliliği doğru zamanlarda kullanılmalıdır ve performans maliyeti olduğu akıldan çıkarılmamalıdır. Maliyetin sebebi; çalışma zamanı çok biçimliliği sağlayan late binding ya da diğer adıyla dynamic binding işleminin çalışma zamanında virtual table (C++ standartları polymorphism'in nasıl gerçekleşeceğini tanımlamaz. Ancak tüm derleyiciler virtual table yöntemini kullanmaktadır.) mekanizması yoluyla gerçekleştirilmesi ve bunun da maliyetli bir işlem olduğu akıllardan çıkartılmamalıdır.


Algorithm | find_first_of, find_end, adjacent_find

C++'da bir veri yapısı içinde arama yapmak için kullanılabilecek std::find_first_of, std::find_end, std::adjacent_find algoritmalarını sırasıyla anlatmaya çalışalım.

Algorithm | find_first_of

find_first_of algoritmasının template bildirimi
std::find_first_of algoritması C++11 ile gelen bir algoritma ve çalışma prensibi bir veri yapısı içinde başka bir veri yapısının elemanlarını arama ve bulduğunda iteratör değeri ile geri dönme üzerinedir. find_first_of çağrısı sırasında verilen ilk iki parametre aranılan veri kümesine ilişkin aralık belirten iteratörler, sonraki iki parametre ise aranacak veri yapısına ilişkin başlangıç - bitiş iteratör değerleridir. Ve 5. parametre olarak Binary Predicate bir callable ifade de karşılaştırma için yazılabilmektedir.

Algorithm | find_first_of örneği - 1
Yukarıdaki örnek find_first_of algoritmasının temel kullanımına ilişkindir. v vektör container'ı içinde t container 'ında olan değerlerden herhangi biri bulunmaya çalışılır. Yukarıdaki örnekte hiçbir değer bulunamayacaktır. Aşağıdaki örnek ise string sınıfı içindeki find_first_of algoritmasının kullanımına ilişkindir. string sınıfı içindeki find_first_of çağrısı sırasında verilen string parametre içindeki değerlerden herhangi birinin bulunmasına ilişkin arama yapılmasını ve bulduğunda ilgili değere ilişkin iteratör değeri ile geri dönülmesini sağlamaktadır.

Algorithm | find_first_of örneği - 2
Yukarıdaki örnekte str string'i içerisinde O, U, T değerlerinden herhangi birinin str içinde geçtiği yerleri bulan ve bunları küçük harflerle değiştirmektedir.

Algorithm | find_first_of örneği - 3
Yukarıdaki örnekte 5. parametre olarak Binary Predicate bir callable olarak lambda ifade tanımlanmış ve 2, 3, 4 'den herhangi birine tam olarak bölünebilen ilk sayı bulunmuştur.

Algorithm | find_end

find_end algoritmasının template bildirimi
std::find_end algoritmasının çalışma prensibi bir veri yapısı içinde başka bir veri yapısının elemanlarını bütünsel ve ardışıl olarak arama ve bulduğunda ilk elemanın iteratör değeri ile geri dönmesi üzerinedir. find_end çağrısı sırasında verilen ilk iki parametre aranılan veri kümesine ilişkin aralık belirten iteratörler, sonraki iki parametre ise aranacak veri yapısına ilişkin başlangıç - bitiş iteratör değerleridir.

Algorithm | find_end örneği - 1
Yukarıdaki örnekte 2, 3, 4 'ün ardışıl olarak sıralandığı son yer alan 8 indeks değerini ekrana basacaktır. Aşağıdaki örnek ise . ifadesinin string içinde son geçtiği yerin bulunmasını sağlamaktadır.

Algorithm | find_end örneği - 2

Algorithm | adjacent_find

adjacent_find algoritmasının template bildirimi
adjacent 'in kelime anlamı bitişik'tir. Ve std::adjacent_find algoritmasının çalışma prensibi de tam olarak buna karşılık gelmektedir. Yani bir veri yapısı içinde elemanlarını ardışıl olarak tekrar edip etmediğine bakmakta ve tekrar eden ilk değeri bulduğunda elemanın iteratör değeri ile geri dönmektedir. adjacent_find çağrısı sırasında verilen ilk iki parametre kontrolü yapılan veri yapısına ilişkin aralık belirten iteratör değerleridir. 3. parametre ise yazılmazsa varsayılan karşılaştırma; binary predicate callable bir ifade yazılmışsa ona havale edilmektedir.

Algorithm | adjacent_find örneği - 1
Yukarıdaki örnekte string içerisinde tekrar eden harfler listelenmektedir. Aşağıdaki örnekte ise benzer işlem 3. parametrede Binary Predicate bir lambda ifade üzerinden sağlanmaktadır.

Algorithm | adjacent_find örneği - 2



std::string | Karakter silme işlemleri

C++ ile string ifadeler içinde silme/ekleme yapabilmek için kullanabileceğimiz erase fonksiyonunun temel olarak 3 farklı çağırma biçimi vardır. Şimdi çağırma biçimlerini sırasıyla inceleyelim.

erase fonksiyonu template bildirimi

Karakter dizisi içinde bir bölümü silme

Aşağıdaki örnek yukarıda üç gruba ayrılmış olan erase'in ilk template bildirimine ilişkin olup erase çağrısı sırasında verilen iki parametreden birincisi string veri yapısı içerisinde hangi indeks değerinden itibaren sileceğini, ikinci parametre ise silinecek parçanın uzunluğunu ifade eder.  2. parametre varsayılan arguman olarak std::string::npos olarak bildirilmiştir.  Bu sayede 2. parametreyi geçirmezsek 1. parametrede verilen değerden başlayarak ifadenin sonuna kadar silme işlemini gerçekleştirir. 1. parametreye varsayılan arguman geçildiğinde ise silme işlemi  0 indeks değerinden başlamaktadır.  Son olarak bu grupta yer alan erase 'in geri dönüş değeri ise *this nesnesidir. 

erase örneği - 1
Yukarıdaki kullanım en temel kullanım biçimidir ve sadece 1. parametre geçilmiştir. Bu da yukarıda anlattığımız gibi ifadenin sonuna kadar silecektir. Aşağıda ise programın çıktısı görülmekte ve beklenen şekilde 16.  indeks değerinden itibaren silme işlemi gerçekleşmiştir. 

örnek - 1 : çıktı
Aşağıdaki örnek C++11 ile gelen 3. template bildirimi örneğine ilişkin olup erase çağrısı sırasında verilen iki parametreden birincisi başlangıç iteratör değeri, 2. parametre ise  silinecek son elemanın iteratör değeri şeklindedir. Bir aralık silmek için iteratör değeri kullanmak gerekmektedir.

erase örneği - 2
Yukarıdaki örnekte başlangıçtan 4 ilerideki iteratör değerinden başlayarak, son değerden bir sonrakini gösteren end iteratör değerinden 9 gerisine kadar bölgenin silinmesi istenmektedir. Aşağıda ise beklenen çıktı görülebilmektedir.

örnek - 2 : çıktı

Karakter dizisi içinde bir noktayı silme

Aşağıdaki örnek ise C++11 ile gelen 2. grup template bildirimi örneğine ilişkin erase çağrı türü olup parametre olarak iteratör değeri geçirilerek sadece 1 değerin silinmesi sağlanmıştır.

erase örneği - 3
Aşağıda ise programın çıktısı görülmekte ve beklenen şekilde 1.  indeks değerinin silinme işlemi gerçekleştiği görülmektedir.

örnek - 3  : çıktı

erase ile birlikte çeşitli kullanım idiyomları

Aşağıdaki örnek find - erase ikilisi ile belirli bir karakterin tümünün string ifade içinden silinmesine ilişkindir. find fonksiyonu ile silinmek istenen karakterin indeks değerinin bulunması ve indeks değeri ile erase çağrısının gerçekleştirilmesi üzerinedir. Bu sırada erase çağrısına 2. parametre olarak 1 değerinin geçilmesi sadece bir karakterin silinmek istenmesinden dolayıdır. Aksi halde string'i sonuna kadar sileceğini yukarıda açıklamıştık.

erase örneği - 4
Aşağıda ise programın çalışmasını ve çıktısının beklenen şekilde olduğu rahatlıkla görülmektedir.

örnek - 4 : çıktı
Aşağıdaki örnekte ise yukarıdaki gibi döngü kullanmadan remove - erase idiyomu ile yukarıdaki gibi istenen belirli karakterin tamamı silinebilmektedir.

erase örneği - 5
Aşağıda ise örnek - 4 'e benzer bir çıktı sağlandığı görülebilmektedir. 

örnek - 5 : çıktı
Aşağıdaki örnekte ise remove_if - erase beraber kullanılarak string içindeki rakamların tamamı silinmiştir.

erase örneği - 6
Programın çıktısı ise aşağıdaki gibidir.

örnek - 6 : çıktı
Aşağıdaki örnekte ise insert - erase beraber kullanılarak string içindeki belli bir blok silinmiş ve yerine karakter ekleme yapılmıştır.

erase örneği - 7
Aşağıda program çıktısı olarak 2017 'nin silindiği yerine X ifadesinin eklendiği görülmektedir.

örnek - 7 : çıktı