Menu

C++ Yineleyiciler Açıklaması: begin, end ve Yineleyici Türleri

C++ yineleyicilerinin konteynerlere yönelik genelleştirilmiş işaretçiler olarak nasıl çalıştığı: begin() ve end(), referans çözme, ilerletme, const/reverse türevleri ve tanımsız davranışa yol açan geçersizleşme ile end()'in referansını çözme tuzakları.

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

Bir Yineleyici Aslında Nedir

Her standart konteyner - vector, string, map, set, list - elemanlarını içeride farklı şekilde saklar. Bir vector bitişik bir bloktur, bir map dengeli bir ağaçtır, bir list bağlı düğümlerdir. Yine de hepsinin üzerinde aynı şekilde dolaşabilirsin. Bunu mümkün kılan şey yineleyicidir: bir elemana "işaret eden" ve bir sonrakine nasıl adım atacağını bilen küçük bir nesne.

Bir yineleyiciyi genelleştirilmiş bir işaretçi olarak düşün. Birini begin()'den alır, işaret ettiği elemanı * ile okur ve ++ ile ileri taşırsın. Parçalar şöyle bir araya gelir:

v.begin() ilk elemana bir yineleyici döndürür; *it sana o elemanı verir; ++it bir sonrakine geçer. Bu üçlü - referans çöz, ilerle, karşılaştır - tüm zihinsel modeldir.

begin(), end() ve Yarı Açık Aralık

Tablonun diğer yarısı end()'dir. Çok önemlidir: end() son elemana işaret etmez - son elemanın bir ötesindeki yuvaya işaret eder. Bu, bilinçli bir "yarı açık" aralıktır [begin, end): begin dahildir, end ise durma işaretidir.

Bu tasarım standart döngüyü temiz hale getirir - yineleyici end()'e eşit olana kadar dolaşırsın:

it < v.end() değil, it != v.end() olduğuna dikkat et. Çoğu konteyner yineleyicisi (map veya list gibi) < operatörünü desteklemez, yalnızca == ve != operatörlerini destekler, dolayısıyla != taşınabilir seçimdir. Ve auto, vector<int>::iterator'ı elle yazmaktan seni kurtarır - derleyici onu çıkarsar.

Boş konteyner durumu kendiliğinden ortaya çıkar: bir konteyner boş olduğunda begin() == end() olur, dolayısıyla döngü gövdesi hiç çalışmaz. Özel bir duruma gerek yoktur.

end()'in Referansını Asla Çözme

En yaygın yineleyici hatası end()'in referansını çözmektir. Son elemanın bir ötesine işaret ettiği için *v.end() sana ait olmayan belleği okur - tanımsız davranış, yani dostça bir hata değil; bir çökme ya da sessiz çöp:

vector<int> v = {1, 2, 3};
cout << *v.end();   // TANIMSIZ DAVRANIŞ - end() bir eleman değildir

Aynı tuzak arama fonksiyonlarını da vurur. std::find, değeri bulamadığında end() döndürür, dolayısıyla referansını çözmeden önce kontrol etmelisin:

Döndürülen yineleyiciyi referansını çözmeden önce her zaman end() ile karşılaştır. Bu if'i unutmak, yeni başlayanların STL kodundaki en sık çökme kaynaklarından biridir.

const, cbegin ve Reverse Yineleyiciler

Konteynerler, neye ihtiyacın olduğuna bağlı olarak farklı yineleyici çeşitleri sunar:

  • begin() / end() - normal okuma/yazma yineleyicileri (*it = ... çalışır).
  • cbegin() / cend() - const_iterator'lar; onlar üzerinden okuyabilirsin ama elemanı değiştiremezsin.
  • rbegin() / rend() - sondan başa doğru dolaşan reverse yineleyiciler; ++ aslında geriye doğru hareket eder.

Reverse yineleyiciler, zahmetli indis matematiği olmadan tersten dolaşmanın temiz yoludur:

Reverse yineleyicilerde ilerlemek için yine ++it yazarsın - yineleyici "geriye doğru" yönü dahili olarak halleder. Bir döngü yalnızca okuma yapmalıysa, derleyici yanlışlıkla yazmanı engellesin diye cbegin()/cend() (veya konteynere bir const referans) kullan.

Map Yineleyicileri Pair Verir

Her yineleyici bir işaretçi üzerine ince bir sarmalayıcı değildir. Bir std::map yineleyicisi bir ağaçta dolaşır ve referansını çözmek sana anahtar ile değerin bir std::pair'ini verir; bunlara ->first ve ->second ile erişilir (tıpkı bir işaretçi gibi, bir yineleyici -> operatörünü destekler):

Aralık tabanlı for döngüsü doğrudan begin()/end() üzerine kuruludur, dolayısıyla düz ileri yönlü yineleme için genellikle ona başvurursun. Açık (explicit) yineleyiciler ise ters yönlü bir gezinmeye, bir elemanın konumuna veya bir aralığı bir algoritmaya geçirmeye ihtiyacın olduğunda işe yarar.

Büyük Tuzak: Yineleyici Geçersizleşmesi

Bu, eninde sonunda herkesi ısıran tuzaktır. Bir konteynerin yapısını değiştirdiğinde, mevcut yineleyiciler geçersizleşebilir - serbest bırakılmış veya taşınmış belleğe işaret ederler. Birini kullanmak tanımsız davranıştır.

Bir vector için push_back, büyütmek amacıyla tüm tamponu yeniden ayırabilir ve her bekleyen yineleyiciyi geçersiz kılar. Dolaşırken silmek daha da meşhurdur - bu klasik bir çökmedir:

vector<int> v = {1, 2, 3, 4};
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0)
        v.erase(it);   // HATA - erase it'i geçersiz kılar, sonra ++it tanımsız davranıştır
}

Çözüm şudur: erase, silinen elemanın sonrasındaki elemana geçerli bir yineleyici döndürür. Yalnızca silmediğin zaman ilerle:

for başlığında ++it olmadığına dikkat et - ilerleyip ilerlemeyeceğine gövde karar verir. (Gerçek kodda, erase-remove deyimi veya C++20'nin std::erase_if'i bunu tek satırda yapar.) Akılda tutulacak kural: eleman ekleyen veya kaldıran herhangi bir işlem yineleyicileri geçersiz kılabilir, dolayısıyla böyle bir değişiklik boyunca eski bir yineleyiciyi elinde tutma.

Sırada: Algoritmalar

Artık bir aralığı bir begin/end çifti olarak tanımlayabildiğine göre, tüm STL algoritmaları kütüphanesinin kilidini açtın. sort, find, count ve accumulate gibi fonksiyonlar hangi konteynere sahip olduğunu umursamaz - yineleyici aralıkları üzerinde çalışırlar, dolayısıyla aynı çağrı bir vector'da, bir dizide veya birinin bir diliminde çalışır. Sırada bu yineleyicileri işe koşacak ve döngüyü senin yerine standart kütüphanenin yapmasına izin vereceğiz.

Sıkça Sorulan Sorular

C++'ta yineleyici (iterator) nedir?

Yineleyici, bir konteyner içindeki bir elemana işaret eden ve bir sonrakine nasıl geçeceğini bilen bir nesnedir. İlkini container.begin() ile, sonun bir ötesindeki işaretçiyi de container.end() ile alırsın. Elemanı okumak veya yazmak için *it ile referansını çöz, ilerletmek için ++it kullan. Yineleyiciler, STL algoritmalarının herhangi bir konteynerle çalışmasını sağlayan ortak arayüzdür.

C++'ta yineleyici ile işaretçi arasındaki fark nedir?

Bir vector veya dizi için yineleyici neredeyse tam olarak bir işaretçi gibi davranır: * ile referansını çözer, ++ ile ilerletir ve ==/!= ile karşılaştırırsın. Ancak yineleyici bir kavramdır, mutlaka ham bir işaretçi değildir: bir map veya list yineleyicisi bir ağaçta veya bağlı düğümlerde dolaşır, dolayısıyla * ve ++ operatörlerini aşırı yükleyen bir sınıf türüdür. İşaretçiler yineleyicinin bir türüdür; yineleyiciler bu fikri her konteynere genelleştirir.

C++'ta yineleyici geçersizleşmesine ne sebep olur?

Bir konteynerin yapısını değiştirmek, mevcut yineleyicilerin serbest bırakılmış veya taşınmış belleğe işaret etmesine neden olabilir. Bir vector için push_back yeniden bellek ayırıp tüm yineleyicileri geçersiz kılabilir; erase ise silinen eleman ve sonrasındaki yineleyicileri geçersiz kılar. Geçersizleşmiş bir yineleyiciyi kullanmak tanımsız davranıştır. Güvende kalmak için erase'in döndürdüğü yineleyiciyi kullan ya da kapasiteyi önceden ayır.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA