Neden dizi indeksleri yerine yineleyiciler kullanılır?


239

Aşağıdaki iki kod satırını alın:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Ve bu:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Bana ikinci yolun tercih edildiği söylendi. Neden tam olarak bu?


72
Tercih edilir İkinci yol değiştirmek olduğunu some_iterator++için ++some_iterator. Artım sonrası gereksiz bir geçici yineleyici oluşturur.
jason

6
Ayrıca end()beyan maddesini de getirmelisiniz .
Yörüngedeki Hafiflik Yarışları

5
@Tomalak: verimsiz bir C ++ uygulaması kullanan herkes vector::endmuhtemelen döngülerden çekilip çekilmediğinden endişelenecek daha kötü sorunlara sahiptir. Şahsen netliği tercih ederim - findfesih durumunda bir çağrı olsaydı endişelenirim.
Steve Jessop

13
@Tomalak: Bu kod özensiz değildir (iyi, belki de artım sonrası), C ++ yineleyicilerinin özlüğe izin verdiği ölçüde özlü ve açıktır. Daha fazla değişken eklemek, erken optimizasyon uğruna bilişsel çaba ekler. Bu özensiz.
Steve Jessop

7
@Tomalak: Bir darboğaz değilse erken. Sizin ikinci nokta doğru karşılaştırma arasındaki olmadığından, bana saçma görünüyor it != vec.end()ve it != endbunun arasında olduğunu, (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)ve (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Karakterleri saymam gerekmiyor. Elbette birini diğerine tercih edin, ancak diğer insanların tercihinize olan uyuşmazlığı "özensizlik" değildir, daha az değişkenli daha basit bir kod ve bu yüzden okurken düşünmek daha az tercih edilir.
Steve Jessop

Yanıtlar:


210

İlk form yalnızca vector.size () hızlı bir işlemse etkilidir. Bu, vektörler için geçerlidir, ancak listeler için geçerli değildir. Ayrıca, döngü içinde ne yapmayı planlıyorsunuz? Öğelere aşağıdaki gibi erişmeyi planlıyorsanız

T elem = some_vector[i];

o zaman kabın operator[](std::size_t)tanımladığı varsayımını yaparsınız . Yine, bu vektör için geçerlidir, ancak diğer kaplar için geçerli değildir.

Yineleyicilerin kullanımı sizi konteyner bağımsızlığına yaklaştırır . Rasgele erişim yeteneği veya hızlı size()işlem hakkında varsayımlar yapmıyorsunuz , yalnızca kapsayıcıda yineleyici özellikleri var.

Standart algoritmalar kullanarak kodunuzu daha da geliştirebilirsiniz. Neyi başarmaya çalıştığınıza bağlı olarak, kullanmayı tercih edebilirsiniz std::for_each(), std::transform()vb. Açık bir döngü yerine standart bir algoritma kullanarak tekerleği yeniden icat etmekten kaçınırsınız. Kodunuzun daha verimli (doğru algoritmanın seçildiği göz önüne alındığında), doğru ve yeniden kullanılabilir olması muhtemeldir.


8
Ayrıca yineleyicilerin başarısızlık gibi şeyler yapabileceğini unuttunuz, böylece erişmekte olduğunuz yapıda eşzamanlı bir değişiklik varsa, bunu bileceksiniz. Bunu sadece bir tamsayı ile yapamazsınız.
Marcin

4
Bu beni karıştırıyor: "Bu vektörler için geçerli, örneğin listeler için geçerli değil." Neden? Beyni olan herkes size_tüye değişkenini takip eder size().
GManNickG

19
@ GMan - neredeyse tüm uygulamalarda, size () vektörler için olduğu kadar listeler için de hızlıdır. Standardın bir sonraki sürümü bunun doğru olmasını gerektirecektir. Asıl sorun, pozisyona göre geri çekilmenin yavaşlığıdır.
Daniel Earwicker

8
@ GMan: Liste boyutunu saklamak için liste dilimleme ve birleştirme O (1) yerine O (n) olmalıdır.

5
C ++ 0x, size()üye işlevi de dahil olmak üzere tüm kapsayıcılar için sabit zaman karmaşıklığı olması gerekir std::list.
James McNellis

54

Bu modern C ++ aşılama sürecinin bir parçası. Yineleyiciler çoğu kapsayıcıyı yinelemenin tek yoludur, bu yüzden vektörlerle bile kendinizi uygun zihniyete sokmak için kullanırsınız. Cidden, bunu yapmamın tek nedeni - Bir vektörü farklı bir kapla değiştirdiğimi sanmıyorum.


Vay canına, bu üç hafta sonra hâlâ reddediliyor. Sanırım biraz yanak olmanın parası yok.

Dizi dizini daha okunabilir olduğunu düşünüyorum. Diğer dillerde kullanılan sözdizimi ve eski moda C dizileri için kullanılan sözdizimi ile eşleşir. Ayrıca daha az ayrıntılı. Derleyiciniz iyi ise verimlilik bir yıkama olmalı ve zaten önemli olduğu durumlar neredeyse yok.

Yine de, kendimi hala yineleyicileri vektörlerle sık sık kullanıyorum. Yineleyicinin önemli bir kavram olduğuna inanıyorum, bu yüzden yapabildiğim zaman onu tanıtıyorum.


1
C ++ yineleyicileri de kavramsal olarak korkunç bir şekilde kırılmıştır. Vektörler için, yakalandım çünkü son ibre acutally end + 1 (!). Akışlar için yineleyici modeli sadece gerçeküstü - mevcut olmayan hayali bir jeton. Benzer şekilde bağlantılı listeler için. Paradigma sadece diziler için anlamlıdır ve o zaman çok fazla değildir. Neden sadece bir değil iki yineleyici nesneye ihtiyacım var ...
Ayarlanabilir

5
@aberglas hiç kırılmamışlar, sadece onlara alışkın değilsiniz, bu yüzden gerekmediğinde bile onları kullanmayı savunuyorum! Yarı açık aralıklar ortak bir kavramdır ve asla doğrudan erişilmesi amaçlanmayan nöbetçiler programlamanın kendisi kadar eskidir.
Mark Ransom

4
akış yineleyicilerine bir göz atın ve kalıba uymak için ne == saptırıldığını düşünün ve sonra yineleyicilerin bozuk olmadığını söyleyin! Veya bağlantılı listeler için. Diziler için bile, sonundan bir tane belirtmek zorunda kalmak, kırık bir C tarzı fikirdir - asla asla işaret etmiyor. Bir yineleyici (iki nesne yerine) ve basit bir son testle Java veya C # veya başka bir dilin yineleyicileri gibi olmalıdırlar.
Ayarlanabilir

53

çünkü kodunuzu some_vector listesinin belirli bir uygulamasına bağlamıyorsunuz. dizi indeksleri kullanırsanız, bu bir dizi dizi olmalıdır; yineleyiciler kullanırsanız, bu kodu herhangi bir liste uygulamasında kullanabilirsiniz.


23
Std :: list interface intentionnaly, O [n) olacağından [] (size_t n) operatörünü sunmaz.
MSalters

33

Some_vector öğesinin bağlantılı listeyle uygulandığını düşünün. Daha sonra i-inci yerde bir öğe istemek, düğümler listesinde geçiş yapmak için i işlemlerinin yapılmasını gerektirir. Şimdi, yineleyici kullanırsanız, genel olarak konuşursak, mümkün olduğunca verimli olmak için elinden geleni yapacaktır (bağlantılı bir liste söz konusu olduğunda, geçerli düğüme bir işaretçi tutacak ve her yinelemede bir tek işlem).

Yani iki şey sağlar:

  • Kullanım soyutlama: sadece bazı unsurları yinelemek istiyorsunuz, nasıl yapılacağını umursamıyorsunuz
  • Verim

1
"mevcut düğüme bir işaretçi korumak ve [verimlilik hakkında iyi şeyler] ilerletecek" - evet, insanların neden yineleyiciler kavramını anlamakta sorun var anlamıyorum. kavramsal olarak sadece bir işaretçi kümesidir. bir öğeyi yalnızca bir işaretçiyi önbelleğe alabildiğinizde neden tekrar tekrar hesaplıyorsunuz? yineleyiciler de bunu yapar.
underscore_d

27

Ben burada şeytanlar savunucusu olacağım ve yineleyicileri tavsiye etmeyeceğim. Bunun ana nedeni, Masaüstü uygulama geliştirmeden oyun geliştirmeye kadar çalıştığım tüm kaynak kodlarım var ve yineleyicileri kullanmak zorunda değilim. İhtiyaç duyulmadıkları her zaman ve ikincisi, yineleyicilerle aldığınız gizli varsayımlar ve kod karmaşası ve hata ayıklama kabusları, hız gerektiren herhangi bir uygulamada kullanmamalarının en iyi örneğidir.

Sessizlik açısından bile bir karmaşa. Bu onlar yüzünden değil, sahnenin arkasındaki tüm takma adlar yüzünden. Standartlardan tamamen farklı bir şey yapan kendi sanal vektör veya dizi listenizi uygulamadığınızı nasıl bilebilirim? Çalışma zamanında şu anda ne tür olduğunu biliyor muyum? Bir operatörü aşırı yüklediniz mi, tüm kaynak kodunuzu kontrol etmek için zamanım yoktu. Cehennem STL'nin hangi sürümünü kullandığınızı bile biliyor muyum?

Yineleyicilerle karşılaştığınız bir sonraki sorun sızdıran soyutlamadır, ancak bunları ayrıntılı olarak tartışan çok sayıda web sitesi vardır.

Üzgünüm, yineleyicilerde herhangi bir nokta görmedim ve görmedim. Listeyi veya vektörü sizden uzaklaştırırlarsa, aslında ne zaman vektörü bilmelisiniz veya başa çıkmadığınız zaman listelemelisiniz, o zaman sadece gelecekte büyük hata ayıklama oturumları için kendinizi kuracaksınız.


23

Üzerinde yineleme yaparken vektöre öğe ekleyecek / kaldıracaksanız bir yineleyici kullanmak isteyebilirsiniz.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Dizinler kullanıyorsanız, ekleme ve silme işlemlerini gerçekleştirmek için dizideki öğeleri yukarı / aşağı karıştırmanız gerekir.


3
kabın ortasına eleman eklemek istiyorsanız, belki de bir vektör başlamak için iyi bir kap seçimi değildir. elbette, yineleyicilerin neden havalı olduğuna geri döndük; bir listeye geçmek önemsizdir.
wilhelmtell

Tüm öğelerin üzerinde yineleme std::listyapmak std::vector, a yerine bağlantılı bir liste kullanmanızı önerirseniz, a'ya kıyasla oldukça pahalıdır std::vector. Bkz. Sayfa 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Deneyimlerime göre, hepsini araştırıyor ve keyfi konumlardaki öğeleri kaldırıyor olsam bile , a'danstd::vector daha hızlı buldum std::list.
David Stone

Endeksler sabittir, bu yüzden ekleme ve silme işlemleri için hangi ek karıştırma işleminin gerekli olduğunu görmüyorum.
musiphil

... Ve bağlantılı bir liste ile - ki burada kullanılması gereken şey - döngü ifadeniz, bir araya getirilen for (node = list->head; node != NULL; node = node->next)ilk iki kod satırınızdan (açıklama ve döngü başı) daha kısa olacaktır . Tekrar söylüyorum - yineleyicileri kullanma ve kullanmama arasında çok temel bir fark yok - forkullansanız bile , bir ifadenin üç bölümünü de tatmin edersiniz while: beyan, yineleme, kontrol sonlandırma.
Mühendis

16

Endişelerin Ayrılması

Yineleme kodunu döngünün 'temel' kaygısından ayırmak çok güzel. Neredeyse bir tasarım kararı.

Gerçekten de, indekse göre tekrarlamak sizi konteynerin uygulanmasına bağlar. Kaptan bir başlangıç ​​ve bitiş yineleyicisi istemek, döngü kodunun diğer kap tipleri ile kullanılmasını sağlar.

Ayrıca, bu std::for_eachşekilde, koleksiyona içleri hakkında bir şeyler sormak yerine ne yapmasını söylersiniz

0x standardı, bu yaklaşımı daha kolay hale getirecek kapanışları tanıtacak - örneğin Ruby'nin etkileyici gücüne bir göz atın [1..6].each { |i| print i; }...

Verim

Ancak belki de çok fazla denetlenen bir sorun, for_eachyaklaşımı kullanmanın yinelemeyi paralel hale getirme fırsatı vermesidir - intel iş parçacığı blokları kod bloğunu sistemdeki işlemci sayısı üzerinden dağıtabilir!

Not: algorithmsKütüphaneyi keşfettikten sonra , özellikle foreach, geliştiricilerinizi çıldırtacak gülünç küçük 'yardımcı' operatör yapıları yazarak iki veya üç ay geçirdim. Bu zamandan sonra, pragmatik bir yaklaşıma geri döndüm - küçük döngü gövdeleri artık hak etmiyor foreach:)

Bir yineleyiciler referans okumak gerekir kitap "Genişletilmiş STL" dir .

GoF, Yineleme modelinin sonunda, bu yineleme markası hakkında konuşan küçük bir paragrafa sahiptir; buna 'dahili yineleyici' denir. Buraya da bir göz atın .


15

Çünkü daha nesne yönelimlidir. eğer bir indeks ile tekrarlıyorsanız:

a) bu nesnelere talimat
verildiği b) bu ​​nesnelere bir indeks ile elde edilebileceği
c) indeks artışının her öğeye çarpacağı
d) bu indeks sıfırdan başlar

Bir yineleyici ile, temel uygulamanın ne olduğunu bilmeden "bana bununla çalışabilmem için her şeyi ver" diyorsunuz. (Java'da, bir dizin aracılığıyla erişilemeyen koleksiyonlar vardır)

Ayrıca, bir yineleyici ile, dizinin sınırlarından çıkma konusunda endişelenmenize gerek yok.


2
"Nesne yönelimli" nin doğru terim olduğunu düşünmüyorum. Yineleyiciler tasarımda "nesne yönelimli" değildir. Fonksiyonel programlamayı nesne yönelimli programlamaya göre daha fazla teşvik ederler çünkü algoritmaları sınıflardan ayırmayı teşvik ederler.
wilhelmtell

Ayrıca, yineleyiciler sınırların dışına çıkmaktan kaçınmazlar. Standart algoritmalar yapar, ancak yineleyiciler tek başına kullanmaz.
wilhelmtell

Yeterince adil @wilhelmtell, bunu Java merkezli bir bakış açısıyla düşünüyorum.
cynicalman

1
Bence OO'yu teşvik ediyor, çünkü koleksiyonlar üzerindeki işlemleri bu koleksiyonun uygulanmasından ayırıyor. Bir nesne koleksiyonu, onlarla çalışmak için hangi algoritmaların kullanılması gerektiğini bilmemelidir.
cynicalman

Aslında, tekrarlayıcıları kontrol eden STL'nin sürümleri var, yani bu yineleyiciyle bir şey yapmaya çalıştığınızda bir çeşit sınır dışı istisna atacak.
Daemin

15

Yineleyiciler hakkında bir başka güzel şey, const tercihinizi ifade etmenize (ve uygulamanıza) daha iyi izin vermeleridir. Bu örnek, döngünüzün ortasında vektörü değiştirmemenizi sağlar:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

Bu makul görünüyor, ama yine de sahip olmanın nedeni buysa const_iterator. Döngüdeki vektörü değiştirirsem, bir nedenden dolayı yaparım ve değiştirmenin bir kaza olmadığı zamanın% 99,9'u ve geri kalanı için, yazarın kodundaki herhangi bir hata gibi bir hatadır düzeltilmesi gerekiyor. Çünkü Java ve diğer birçok dilde, hiçbir const nesnesi yoktur, ancak bu dillerin kullanıcılarının bu dillerde const desteği olmadan hiçbir zaman bir sorunu yoktur.
neevek

2
@neevek Sahip olmanızın nedeni bu değilse, neden const_iteratorDünya'da ne olabilir?
underscore_d

@underscore_d, ben de merak ediyorum. Bu konuda uzman değilim, sadece cevap benim için ikna edici değil.
neevek

15

Tüm diğer mükemmel cevapların yanı sıra ... intvektörünüz için yeterince büyük olmayabilir. Bunun yerine, dizin oluşturmayı kullanmak istiyorsanız size_type, kapsayıcısı için şunu kullanın :

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz, bu çok iyi bir nokta. STL tabanlı bir Windows uygulamasını x64'e aktarırken, muhtemelen kesmeye neden olan bir int'e size_t atama konusunda yüzlerce uyarı ile uğraşmak zorunda kaldım.
bk1e

1
Boyut türlerinin imzasız ve int imzalı olduğu gerçeğinden bahsetmiyoruz, bu yüzden sadece karşılaştırmak int iiçin devam eden sezgisel olmayan, hata gizleme dönüşümleriniz var myvector.size().
Adrian McCarthy

12

Muhtemelen şunu da göstermeliyim ki

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

STL yineleyicileri çoğunlukla oradadır, böylece sort gibi STL algoritmaları kaptan bağımsız olabilir.

Bir vektördeki tüm girdilerin üzerinde döngü yapmak istiyorsanız, dizin döngü stilini kullanın.

Çoğu insan için daha az yazı yazmak ve ayrıştırmak daha kolaydır. C ++ şablon sihirli ile denize girmeden basit bir foreach döngüsüne sahip olsaydı iyi olurdu.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

Bir vektör için çok fazla fark yarattığını düşünmüyorum. Daha okunabilir olduğunu düşündüğüm için bir indeks kullanmayı tercih ederim ve 6 öğeyi ileriye atlamak veya gerekirse geriye doğru atlamak gibi rastgele erişim yapabilirsiniz.

Ayrıca, döngü içindeki öğeye böyle bir referans yapmak istiyorum, böylece yerin etrafında çok fazla köşeli ayraç yok:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

İleride bir noktada vektörü bir liste ile değiştirmeniz gerekebileceğini ve ayrıca STL düşkünlerine daha şık görüneceğini düşünüyorsanız, ancak başka bir neden düşünemiyorum.


algoritmaların çoğu , bir kabın her bir elemanı üzerinde sırayla bir kez çalışır. Elbette, bir koleksiyonu belirli bir sırayla veya şekilde geçirmek istediğiniz istisnalar vardır, ancak bu durumda çok çalışmayı ve STL ile entegre olan ve yineleyicilerle çalışan bir algoritma yazmayı deneyeceğim.
wilhelmtell

Bu, daha sonra yeniden kullanılmasını teşvik eder ve tek tek hataları önler. Daha sonra bu algoritmayı diğer standart algoritmalar gibi yineleyiciler olarak adlandırırım.
wilhelmtell

1
İlerlemeye bile gerek yok (). Yineleyici, bir dizinle aynı + = ve - = işleçlerine sahiptir (vektör ve vektör benzeri kaplar için).
MSalters

I prefer to use an index myself as I consider it to be more readablesadece bazı durumlarda; diğerlerinde ise, endeksler hızla dağınık hale gelir. and you can do random accessendekslerin benzersiz bir özelliği değildir: bkz. en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

İkinci form daha doğru yaptığınızı gösterir. Örneğinizde, i'nin değerini umursamıyorsunuz, gerçekten - istediğiniz tek şey yineleyicideki bir sonraki öğedir.


3

Bu cevabın konusu hakkında biraz daha bilgi edindikten sonra, bunun biraz fazla basitleştirildiğinin farkındayım. Bu döngü arasındaki fark:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Ve bu döngü:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Oldukça azdır. Aslında, bu şekilde döngü yapmanın sözdizimi beni büyütüyor gibi görünüyor:

while (it != end){
    //do stuff
    ++it;
}

Yineleyiciler oldukça güçlü bazı bildirici özelliklerin kilidini açar ve STL algoritmaları kitaplığıyla birleştirildiğinde dizi dizini yönetiminin kapsamı dışında oldukça güzel şeyler yapabilirsiniz.


Gerçek şu ki, tüm yineleyiciler son örneğiniz kadar kompakt olsaydı, kutudan çıkar çıkmaz, onlarla çok az sorunum olurdu. Tabii ki, bu aslında for (Iter it = {0}; it != end; ++it) {...}- beyanı dışarıda bıraktınız - yani kısalık ikinci örneğinizden çok farklı değil. Yine de +1.
Mühendis

3

Dizin oluşturma işlemi ek bir mulişlem gerektirir . Örneğin, için vector<int> vderleyici dönüştürür, v[i]içine &v + sizeof(int) * i.


Muhtemelen çoğu durumda yineleyicilere göre önemli bir dezavantaj değildir, ancak farkında olmak iyi bir şeydir.
nobar

3
İzole tek eleman erişimleri için, muhtemelen. Ancak döngülerden bahsediyorsak - OP gibi - o zaman bu cevabın hayali optimize edici olmayan bir derleyiciye dayandığından eminim. Herhangi bir yarım terbiyeli olan, sizeofher seferinde tüm ofset hesaplamasını yapmak yerine, önbelleğe alma ve sadece yineleme başına bir kez ekleme fırsatı ve şansına sahip olacaktır .
underscore_d

2

Yineleme sırasında işlenecek öğenin sayısını bilmenize gerek yoktur. Sadece öğeye ihtiyacınız var ve yineleyiciler böyle şeyleri çok iyi yapıyorlar.


2

Henüz hiç kimse endekslerin bir avantajı gibi bitişik bir kapsayıcıya eklediğinizde geçersiz hale gelmemeleri std::vector, böylece yineleme sırasında kapsayıcıya öğeler ekleyebilirsiniz.

Bu, yineleyicilerle de mümkündür, ancak aramalısınız reserve()ve bu nedenle kaç öğe ekleyeceğinizi bilmeniz gerekir.


1

Birkaç iyi nokta zaten. Birkaç ek yorumum daha var:

  1. C ++ standart kitaplığından bahsettiğimizi varsayarsak, "vektör", C-dizisi (rastgele erişim, contiguos bellek düzeni vb.) 'Some_container' demiş olsaydınız, yukarıdaki cevapların çoğu daha doğru olurdu (konteyner bağımsızlığı vb.).

  2. Derleyici optimizasyonuna olan bağımlılıkları ortadan kaldırmak için, dizinindeki koddaki some_vector.size () yöntemini aşağıdaki gibi taşıyabilirsiniz:

    const size_t numElems = some_vector.size ();
    için (size_t i = 0; i 
  3. Yineleyicileri her zaman önceden artırın ve artımları istisnai durumlar olarak ele alın.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// şeyler yap}

std::vector<>Konteyner gibi kabul edilebilir ve endekslenebilir , sırayla konteynerden geçerek birini diğerine tercih etmek için iyi bir neden yoktur. Daha eski veya daha yeni görünen dizinlere sık sık başvurmanız gerekiyorsa, dizinlenmiş sürüm daha uygundur.

Genel olarak, yineleyicilerin kullanılması tercih edilir çünkü algoritmalar bunlardan yararlanır ve davranış yineleyicinin tipi değiştirilerek kontrol edilebilir (ve dolaylı olarak belgelenebilir). Dizi konumları yineleyiciler yerine kullanılabilir, ancak sözdizimsel fark ortaya çıkacaktır.


1

Her ifadeyi sevmediğim için yineleyicileri kullanmıyorum. Birden fazla iç döngüye sahipken, tüm yerel değerleri ve yineleyici adlarını da hatırlamak zorunda kalmadan küresel / üye değişkenleri takip etmek yeterince zordur. Yararlı bulduğum şey, farklı durumlar için iki dizi indeks kullanmaktır:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Hatta "animation_matrices [i]" gibi bazı rastgele "anim_matrix" -named-iterator gibi şeyleri kısaltmak istemiyorum, çünkü o zaman bu değerin hangi diziden kaynaklandığını açıkça göremiyorsunuz.


Endekslerin bu anlamda nasıl daha iyi olduğunu görmüyorum. Kolayca iteratörler ve sadece kendi adları için bir kongre almak olabilir: it, jt, ktvb hatta sadece kullanmaya devam i, j, kvb Ve benimle böyle ardından, yineleyici temsil tam olarak ne bir bilmek şey gerekiyorsa for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()daha açıklayıcı olacaktır sürekli indekslemekten daha fazla animation_matrices[animIndex][boneIndex].
underscore_d

vay, bu düşünceyi yazdığım zamanlar gibi hissettiriyor. günümüzde hem foreach hem de c ++ yineleyicilerini fazla cırcırlamadan kullanıyor. Yıllarca buggy kodu ile çalışmak sanırım hoşgörü oluşturur, bu yüzden tüm sözdizimlerini ve konvansiyonları kabul etmek daha kolaydır ... çalıştığı sürece ve biri eve gidebildiği sürece bilirsiniz;)
AareP

Haha, gerçekten, bunun daha önce kaç yaşında olduğuna gerçekten bakmadım! Bir şekilde son kez düşünmediğim başka bir şey, bugünlerde aralık tabanlı fordöngüye sahip olmamızdı , bu da bunu yineleyici tabanlı bir şekilde yapmanın daha özlü olmasını sağlıyor.
underscore_d

1
  • Metale yakın olmak / uygulama detaylarına güvenmemek isterseniz yineleyicileri kullanmayın .
  • Geliştirme sırasında düzenli olarak bir koleksiyon türünü diğeri için değiştirirseniz, yineleyicileri kullanın .
  • Eğer bulursanız zor, (belki birkaç farklı dış kullanımda kaynaklardan çeşitli var) koleksiyonların farklı çeşit yineleme nasıl hatırlamak kullanım elemanları üzerinde yürümek hangi araçları birleştirmeye Yineleyicilerin. Bu, bağlantılı listenin dizi listesiyle değiştirilmesinde geçerlidir.

Gerçekten, hepsi bu kadar. Ortalama olarak her iki şekilde de daha fazla kısalık kazanacaksınız ve kısalık gerçekten hedefinizse, her zaman makrolara geri dönebilirsiniz.


1

C ++ 11 özelliklerine erişiminiz varsa , vektörünüz (veya başka bir kapsayıcı) üzerinde aşağıdaki gibi yineleme yapmak için aralık tabanlı bir fordöngü de kullanabilirsiniz :

for (auto &item : some_vector)
{
     //do stuff
}

Bu döngünün yararı, itembir indikatörü bozma veya yineleyiciyi devrederken hata yapma riski olmadan , vektör öğelerine doğrudan değişken aracılığıyla erişebilmenizdir . Ek olarak, yer tutucu auto, konteyner elemanlarının türünü tekrarlamanıza engel olur, bu da sizi konteynırdan bağımsız bir çözüme daha da yaklaştırır.

Notlar:

  • Döngünüzdeki öğe dizinine ve operator[]kapınız için var olana (ve sizin için yeterince hızlı) ihtiyacınız varsa, ilk yol için daha iyi gidin.
  • forKapsayıcıya / kapsayıcıya öğe eklemek / kapsayıcıdan öğe eklemek / aralık tabanlı döngü kullanılamaz. Bunu yapmak istiyorsanız, Brian Matthews tarafından verilen çözüme sadık kalın.
  • Eğer kap içinde elemanlarını değiştirmek istemiyorsanız, o zaman anahtar kelime kullanmanız gerekir constaşağıdaki gibi: for (auto const &item : some_vector) { ... }.

0

Hatta "CPU'ya ne yapması gerektiğini söylemek" (zorunlu) "kütüphanelere ne istediğinizi söylemek" (işlevsel).

Bu yüzden döngüler kullanmak yerine stl'de bulunan algoritmaları öğrenmelisiniz.



0

Ben her zaman dizi dizin kullanın çünkü benim birçok uygulama "görüntü küçük resim" gibi bir şey gerektirir. Ben de şöyle bir şey yazdım:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

Her iki uygulama da doğrudur, ancak 'for' döngüsünü tercih ederim. Başka bir kapsayıcı değil, bir Vector kullanmaya karar verdiğimiz için, dizinleri kullanmak en iyi seçenek olacaktır. Yineleyicilerin Vektörlerle birlikte kullanılması, nesnelerin erişimlerini kolaylaştırmaya yardımcı olan sürekli bellek bloklarında bulunma avantajını kaybedecektir.


2
"Yineleyicilerin Vektörlerle kullanılması, nesnelerin erişimlerini kolaylaştıran sürekli bellek bloklarında bulundurma avantajını kaybeder." [kaynak belirtilmeli]. Neden? Bir yineleyicinin bitişik bir kaba artırılmasının basit bir ek olarak uygulanamayacağını düşünüyor musunuz?
underscore_d

0

Buradaki cevapların hiçbirinin, yineleyicileri neden kapsayıcılara endeksleme konusunda genel bir kavram olarak sevdiğimi açıklamamıştım. Yineleyicileri kullanma deneyimimin çoğunun aslında C ++ değil Python gibi daha üst düzey programlama dillerinden geldiğini unutmayın.

Yineleyici arabirimi, işlevinizin tüketicilerine daha az gereksinim getirir ve bu da tüketicilerin onunla daha fazlasını yapmasını sağlar.

İhtiyacınız olan tüm iletmek isteriz ki muktedir ise, geliştirici indeksli kapları kullanarak sınırlı değildir - bunlar uygulayan herhangi bir sınıf kullanabilirsiniz operator++(T&), operator*(T)ve operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

Algoritmanız ihtiyaç duyduğunuz durumda çalışır - bir vektör üzerinden yineleme - ancak mutlaka beklemediğiniz uygulamalar için de yararlı olabilir:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

Yineleyici uygulaması nispeten basitken, bu yineleyiciye benzer bir şey yapan bir köşeli parantez operatörünün uygulanmaya çalışılması uygun olacaktır. Köşeli parantez operatörü ayrıca, uygulamanız zor veya verimsiz olabilecek sınıfınızın yetenekleri hakkında - herhangi bir keyfi noktaya indeksleyebileceğiniz - imalar yapar.

Yineleyiciler de dekorasyona borç veriyorlar . İnsanlar yapıcılarında yineleyici alan ve işlevlerini genişleten yineleyiciler yazabilir:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

Bu oyuncaklar sıradan görünse de, basit bir arayüzle güçlü şeyler yapmak için yineleyiciler ve yineleyici dekoratörleri kullandığınızı hayal etmek zor değildir - örneğin, tek bir sonuçtan bir model nesnesi oluşturan bir yineleyici ile veritabanı sonuçlarının ileriye dönük bir yineleyicisini süslemek . Bu kalıplar, sonsuz kümelerin bellek tasarruflu yinelemesini sağlar ve yukarıda yazdığım gibi bir filtreyle, sonuçların potansiyel olarak tembel olarak değerlendirilmesini sağlar.

C ++ şablonlarının gücünün bir kısmı, sabit uzunluklu C dizilerinin beğenilerine uygulandığında yineleyici arayüzünüzdür, basit ve verimli işaretçi aritmetiğine bozulur ve gerçekten sıfır maliyetli bir soyutlama yapar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.