Menu

C++ STL Algoritmaları: Örneklerle Pratik Rehber

C++ standart algoritmalarını - find, count_if, transform, accumulate, remove - kullanarak elle döngü yazmadan aralıklar üzerinde gerçek iş yapın; ayrıca iterator-çifti ve erase-remove deyimine dair tuzaklar.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Yazmak Zorunda Olmadığın Döngüler

Önceki sayfada her konteynerin iterator verdiğini gördün - begin() ve end() ile gelen hafif imleçler. Bu soyutlama, standart algoritmaların var olmasının tek nedenidir. Veriyi her arama, sayma ya da dönüştürme isteğinde ham bir for döngüsü yazmak yerine, <algorithm> içinden adı olan bir fonksiyonu çağırır ve ona bir aralık verirsin.

Bir aralık yalnızca iki iteratordur: nereden başlanacağı ve nerede duracağının bir sonrası. Her algoritma bu aynı iterator dilini konuştuğu için, aynı find bir vector, bir string veya düz bir dizi üzerinde çalışır.

İçselleştirilmesi gereken kalıp: arama yapan bir algoritma "hiçbir şey bulunamadı" demek için end() iteratorunu döndürür. Sonucu dereferans etmeden önce daima end() ile karşılaştır - end() dereferans etmek tanımsız davranıştır.

Yüklemlerle Sayma ve Test Etme

Birçok algoritma bir yüklem alır - her eleman için bool döndüren bir fonksiyon (genellikle bir lambda). count_if eşleşmeleri sayar; all_of, any_of ve none_of ise tüm aralık hakkında evet/hayır sorularını yanıtlar.

std::count (_if olmadan), bir koşul yerine tam bir değeri sayan daha basit kuzenidir. Testin "belirli bir değer" yerine "bir kurala uyan herhangi bir şey" olduğu anda yüklemli sürümlere yönel.

Bir Aralığı Dönüştürme ve İndirgeme

İki iş atı veri işlemenin çoğunu karşılar: std::transform her elemanı bir fonksiyondan geçirerek eşler ve std::accumulate (<numeric>'ten, <algorithm>'dan değil) bir aralığı tek bir değere katlar.

transform sonuçlarını bir çıkış iteratoru üzerinden yazar. Yaygın ve tehlikeli bir hata, çıkışı boş bir vector'a yönlendirmektir - algoritma zaten yer olduğunu varsayar ve sonun ötesine yazar. Ya hedefi önce boyutlandır ya da her sonucun push_back edilmesi için bir back_inserter kullan.

accumulate bir başlangıç değerinden başlar ve elemanları soldan sağa birleştirir. Başlangıç değerinin türü önemlidir: 0 (bir int) geç ve toplam int içinde hesaplanır, bu da taşma ya da kesilmeye yol açabilir.

Eğer accumulate(prices.begin(), prices.end(), 0) şeklinde int bir tohum ile yazsaydın, her toplama int içinde gerçekleşir ve kuruşlar yok olurdu. Tohum türü, sonuç türünü sessizce belirler.

Erase-Remove Deyimi

İşte herkesi şaşırtan tuzak. std::remove, konteynerden hiçbir şeyi kaldırmaz. Algoritmaların yalnızca iteratorları vardır, bu yüzden bir konteynerin boyutunu değiştiremezler - bir konteynerin var olduğundan bile haberleri yoktur. remove'un gerçekte yaptığı şey, tüm tutulan elemanları başa taşımak, kuyruğu belirsiz bir durumda bırakmak ve yeni mantıksal sona bir iterator döndürmektir.

// remove tek başına boyutu değiştirmeden bırakır - işte hata bu:
remove(v.begin(), v.end(), 0);  // göz ardı ettiğin bir iterator döndürür
// v hâlâ orijinal boyutunda; kuyruk çöp

Elemanları gerçekten silmek için remove'u konteynerin erase'i ile eşleştirirsin; bu yüzden adı erase-remove deyimidir:

Tam bir değer yerine bir yüklem için remove_if kullan. C++20'de bu dansı tamamen atlamak için her iki adımı senin yerine yapan serbest fonksiyonlar std::erase / std::erase_if kullanabilirsin: erase(v, 0);.

İteratorlar Geçerliliklerini Senin Riskinle Aşar

Algoritmalar iterator döndürdüğü için, bu iteratorlar önceki sayfada karşılaştığın aynı geçersizleşme kurallarına tabidir. Bir iteratoru saklayıp ardından konteyneri değiştirmek - yeniden tahsis tetikleyen bir push_back ya da bir erase - saklanan iteratoru boşta bırakabilir ve onu kullanmak tanımsız davranıştır.

auto it = find(v.begin(), v.end(), 16);
v.push_back(99);   // v'nin belleğini yeniden tahsis edebilir
cout << *it;       // BUG: `it` artık serbest bırakılmış belleği gösteriyor olabilir

Güvenli alışkanlık: bir algoritmanın döndürdüğü iteratoru, konteyneri yeniden boyutlandırabilecek ya da yeniden tahsis edebilecek herhangi bir işlemden önce hemen kullan. Bir sonuca dayanarak konteyneri değiştirmen gerekiyorsa, bunun yerine bir indeks (it - v.begin()) yakala, çünkü indeksler yeniden tahsisten sağ çıkar.

Sıradaki: Sıralama

Artık aramayı, saymayı, dönüştürmeyi ve indirgemeyi gördün - ama bir algoritma kendi sayfasını hak edecek kadar önemli. std::sort bir aralığı yerinde yeniden düzenler ve veri bir kez sıralandığında daha hızlı, yalnızca sıralı veriye özgü bir algoritma ailesi (binary_search, lower_bound, equal_range) açılır. Sırada sıralamayı derinlemesine inceleyeceğiz: özel bir karşılaştırıcının nasıl sağlanacağı, sort ile stable_sort arasındaki fark ve karşılaştırma fonksiyonunun tanımsız davranıştan kaçınmak için uyması gereken kurallar.

Sıkça Sorulan Sorular

C++'ta <algorithm> başlığı nedir?

<algorithm>, std::find, std::sort, std::count_if ve std::transform gibi genel amaçlı fonksiyonları barındıran standart kütüphane başlığıdır. Bunlar bir iterator çifti (genellikle begin() ve end()) ile tanımlanan aralıklar üzerinde çalışır; bu yüzden aynı algoritma bir vector, array, string veya iterator sunan herhangi bir konteyner üzerinde çalışır.

C++'ta bir değerin vector içinde olup olmadığını nasıl kontrol ederim?

std::find kullanın: auto it = find(v.begin(), v.end(), target);. Eğer it == v.end() ise değer mevcut değildir; aksi halde it ilk eşleşmeyi gösterir. Tam bir değer yerine bir koşulu test etmek için bir yüklem ile std::any_of kullanın.

std::remove neden konteynerimden elemanları gerçekten silmiyor?

Algoritmalar konteyneri değil yalnızca iteratorları görür, bu yüzden std::remove onu küçültemez - tutulan elemanları başa kaydırır ve yeni mantıksal sona bir iterator döndürür. Kalanları fiziksel olarak atmak için bunu v.erase(...) ile takip etmelisiniz (erase-remove deyimi).

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA