"Her Öğe İçin Şunu Yap"
Klasik sayaçlı for döngüsü, sürmek için bir indeksiniz olduğunda harikadır. Ama çoğu zaman indeks aslında umurunuzda değildir; sadece bir konteynerdeki her öğeye dokunmak istersiniz. Bunun için for (int i = 0; i < v.size(); i++) yazmak gürültülüdür ve i, sınır dışını okumaya bir off-by-one hatası kadar yakındır.
C++11 tam da bunun için aralık tabanlı for döngüsünü ekledi. Bir değişken adlandırır, bir konteyneri işaret eder ve döngü her öğeyi sizin için gezer:
İndeks yok, .size() yok, yanlış yapılacak sınır yok. Bunu "scores içindeki her s için" diye okuyun. Ham dizilerde, std::vector, std::string, std::map ve begin() ile end() sunan her şeyde çalışır.
Türü auto Seçsin
Öğe türünü açıkça yazmak işe yarar ama kırılgandır; konteynerin türünü değiştirin, her döngünün de değişmesi gerekir. Aralık tabanlı for'u auto ile birleştirin; derleyici öğe türünü sizin için çıkarsın:
Yine de burada gizli bir maliyet var. Sade auto name, string çıkarımı yapar ve her geçişte her öğeyi name içine kopyalar. Bir int için bu bedavadır; bir string veya büyük bir struct için her yinelemede boşa giden bir tahsistir. Çözüm referanslardır ki anlaşılması gereken bir sonraki konu da budur.
auto& ile Yerinde Değiştirme
auto x yazarsanız bir kopya alırsınız; dolayısıyla x'e atama yapmak kopyayı değiştirir, konteyneri değil. Şu tuzağa dikkat:
Çarpma işlemi sessizce hiçbir şey yapmaz, çünkü n tek kullanımlık bir kopyadır. Öğeleri gerçekten düzenlemek için onları auto& ile referansla alın:
Tek bir &, "bak ama dokunma" ile "yerinde düzenle" arasındaki tüm farktır. Değişikliklerinizin neden kaybolduğunu merak ederseniz, neredeyse her zaman sebebi budur.
Kopyalamadan Okuma: const auto&
Öğeleri yalnızca okumanız gerektiğinde ama kopyalamaları pahalı olduğunda const auto& kullanın. Referans kopyayı önler ve const, hiçbir şeyi değiştirmeyeceğinizi belgeler (ve zorunlu kılar):
İyi bir pratik kural:
for (auto x : c) // kopya - ucuz türler (int, char, işaretçiler)
for (auto& x : c) // düzenle - öğeleri değiştirmek istersin
for (const auto& x : c) // oku - yalnızca incelediğin ağır türler
Okurken varsayılan olarak const auto&, yazarken auto& kullanın. Sade auto'ya yalnızca gerçekten küçük ve kopyalaması ucuz türler için başvurun.
Map'ler ve Pair'ler Üzerinde Döngü
std::map üzerinde aralık tabanlı bir for, her girdi için size .first (anahtar) ve .second (değer) içeren bir std::pair verir. C++17'den itibaren structured bindings, bu pair'i tam döngü başlığında iki adlandırılmış değişkene ayırmanıza olanak tanır:
[name, age], her yerde entry.first ve entry.second'ı tekrarlamaktan çok daha açıktır. Burada da const auto&'yi koruyun; bir map girdisinin anahtarı bir string'tir, dolayısıyla her pair'i kopyalamak israf olurdu.
Tuzak: Döngü Sırasında Yeniden Boyutlandırma
En büyük tuzak, aralık tabanlı bir for konteyneri gezerken konteynerin boyutunu değiştirmektir. push_back, erase, insert veya clear çağırmak alttaki depolamayı yeniden tahsis edebilir ve döngünün dahili iterator'larını geçersiz kılabilir; sonuç tanımsız davranıştır; yani çökme veya çöp veri, dostça bir hata değil:
vector<int> v = {1, 2, 3};
for (int x : v) {
v.push_back(x); // TANIMSIZ DAVRANIŞ - yeniden tahsis aralığı geçersiz kılar
}
İşlem sırasında öğe eklemeniz veya çıkarmanız gerekiyorsa indeks veya iterator tabanlı bir for döngüsüne geçin ve sınırları kendiniz yönetin ya da ayrı bir sonuç konteyneri oluşturup sonradan onunla değiştirin. Aynı aileden iki küçük tuzak daha: aralık tabanlı bir for'u hemen yok olan bir geçici nesneye asla bağlamayın (for (auto x : makeVector()) sorun değil, ama for (auto& x : someObj.getTempVector()) sarkık kalabilir) ve for (auto& c : myString)'in tek tek karakterleri yerinde değiştirmenize izin verdiğini unutmayın.
Sıradaki: Fonksiyonlar
Aralık tabanlı for döngüsü yinelemeyi toparlar ve az önce öğrendiğiniz auto / auto& / const auto& seçimleri doğrudan C++'taki en önemli araçlardan birine taşınır. Sırada, mantığı yeniden kullanılabilir fonksiyonlara paketleyeceğiz: koda bir ad, parametreler ve bir dönüş değeri vererek, kendinizi tekrarlamak yerine onu her yerden çağırabilmenizi sağlayacağız.
Sıkça Sorulan Sorular
C++'ta aralık tabanlı for döngüsü nedir?
Aralık tabanlı for döngüsü, bir indeks veya iterator yönetmenize gerek kalmadan bir konteynerdeki (dizi, vector, string, map vb.) her öğeyi gezer. Söz dizimi for (auto x : container) { ... } şeklindedir. C++11'de eklendi ve "her öğe için şunu yap" demenin en temiz yoludur.
Aralık tabanlı for döngüsünde auto yerine auto& ne zaman kullanmalıyım?
Öğeleri yerinde değiştirmek istediğinizde auto& x, yalnızca okuyup kopyalamadan kaçınmak istediğinizde (string, vector veya büyük nesneler için önemlidir) const auto& x kullanın. Sade auto x her yinelemede bir kopya oluşturur; int gibi ucuz türler için sorun değil, aksi halde israftır.
C++'ta aralık tabanlı for döngüsünün içinde bir vector'ün boyutunu değiştirebilir misiniz?
Hayır. Yinelediğiniz konteyner üzerinde push_back, erase, insert veya clear çağırmak döngünün dahili iterator'larını geçersiz kılar ve tanımsız davranıştır; çökmeye yol açabilir ya da veriyi sessizce bozabilir. Döngü sırasında öğe eklemeniz veya çıkarmanız gerekiyorsa bunun yerine indeks veya iterator tabanlı bir for döngüsü kullanın.