Mengapa menggunakan iterators dan bukan indeks array?

239

Ambil dua baris kode berikut:

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

Dan ini:

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

Saya diberitahu bahwa cara kedua lebih disukai. Kenapa ini?

Jason Baker
sumber
72
Cara kedua lebih disukai adalah Anda mengubah some_iterator++ke ++some_iterator. Pasca-kenaikan menciptakan iterator sementara yang tidak perlu.
jason
6
Anda juga harus end()memasukkan klausa deklarasi.
Lightness Races dalam Orbit
5
@ Tomalak: siapa pun yang menggunakan implementasi C ++ dengan tidak efisien vector::endmungkin memiliki masalah yang lebih buruk untuk dikhawatirkan daripada apakah itu diangkat dari loop atau tidak. Secara pribadi saya lebih suka kejelasan - jika itu adalah panggilan untuk finddalam kondisi terminasi saya khawatir.
Steve Jessop
13
@ Tomalak: Kode itu tidak ceroboh (well, mungkin setelah kenaikan), itu singkat dan jelas, sejauh C ++ iterators memungkinkan keringkasan. Menambahkan lebih banyak variabel menambah upaya kognitif demi optimasi prematur. Ceroboh.
Steve Jessop
7
@ Tomalak: ini terlalu dini jika itu bukan hambatan. Poin kedua Anda tampaknya tidak masuk akal bagi saya, karena perbandingan yang benar bukan antara it != vec.end()dan it != end, itu antara (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)dan (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Saya tidak perlu menghitung karakter. Dengan segala cara lebih suka satu daripada yang lain, tetapi ketidaksepakatan orang lain dengan preferensi Anda bukanlah "kecerobohan", itu adalah preferensi untuk kode yang lebih sederhana dengan variabel yang lebih sedikit dan dengan demikian lebih sedikit dipikirkan saat membacanya.
Steve Jessop

Jawaban:

210

Bentuk pertama efisien hanya jika vector.size () adalah operasi cepat. Ini berlaku untuk vektor, tetapi tidak untuk daftar, misalnya. Juga, apa yang Anda rencanakan untuk dilakukan di dalam tubuh loop? Jika Anda berencana mengakses elemen seperti pada

T elem = some_vector[i];

maka Anda membuat asumsi bahwa wadah telah operator[](std::size_t)ditentukan. Sekali lagi, ini berlaku untuk vektor tetapi tidak untuk wadah lain.

Penggunaan iterator membawa Anda lebih dekat ke kemandirian wadah . Anda tidak membuat asumsi tentang kemampuan akses acak atau size()operasi cepat , hanya saja wadah tersebut memiliki kemampuan iterator.

Anda dapat meningkatkan kode Anda lebih lanjut dengan menggunakan algoritma standar. Bergantung pada apa yang ingin Anda capai, Anda dapat memilih untuk menggunakannya std::for_each(), std::transform()dan seterusnya. Dengan menggunakan algoritme standar dan bukannya perulangan eksplisit, Anda menghindari menciptakan kembali roda. Kode Anda cenderung lebih efisien (mengingat algoritma yang tepat dipilih), benar dan dapat digunakan kembali.

wilhelmtell
sumber
8
Anda juga lupa bahwa iterator dapat melakukan hal-hal seperti gagal-cepat, sehingga jika ada modifikasi bersamaan pada struktur yang Anda akses, Anda akan mengetahuinya. Anda tidak dapat melakukannya hanya dengan integer.
Marcin
4
Ini membingungkan saya: "Ini berlaku untuk vektor, tetapi tidak untuk daftar, misalnya." Mengapa? Siapa pun yang memiliki otak akan menyimpan size_tvariabel anggota size().
GManNickG
19
@ GM - di hampir semua implementasi, ukuran () cepat untuk daftar sama banyaknya untuk vektor. Versi standar berikutnya akan mengharuskan ini benar. Masalah sebenarnya adalah lambatnya retreval berdasarkan posisi.
Daniel Earwicker
8
@ GM: Menyimpan ukuran daftar membutuhkan pengiris daftar dan penyambungan menjadi O (n), bukan O (1).
5
Dalam C ++ 0x, size()fungsi anggota akan diharuskan memiliki kompleksitas waktu yang konstan untuk semua kontainer yang mendukungnya, termasuk std::list.
James McNellis 6-10
54

Ini adalah bagian dari proses indoktrinasi C ++ modern. Iterator adalah satu-satunya cara untuk mengulang sebagian besar wadah, sehingga Anda menggunakannya bahkan dengan vektor hanya untuk membuat Anda masuk ke pola pikir yang tepat. Serius, itulah satu-satunya alasan saya melakukannya - saya tidak berpikir saya pernah mengganti vektor dengan jenis wadah yang berbeda.


Wow, ini masih diturunkan setelah tiga minggu. Kurasa tidak ada gunanya sedikit bicara.

Saya pikir indeks array lebih mudah dibaca. Ini cocok dengan sintaks yang digunakan dalam bahasa lain, dan sintaksis yang digunakan untuk array C kuno. Itu juga kurang verbose. Efisiensi harus menjadi pencuci jika kompiler Anda bagus, dan hampir tidak ada kasus di mana itu penting.

Meski begitu, saya masih menemukan diri saya sering menggunakan iterator dengan vektor. Saya percaya iterator adalah konsep penting, jadi saya mempromosikannya kapan saja saya bisa.

Mark tebusan
sumber
1
C ++ iterator juga rusak secara konseptual. Untuk vektor, saya baru saja ketahuan karena penunjuk akhir secara akut berakhir +1 (!). Untuk stream, model iterator hanya nyata - token imajiner yang tidak ada. Demikian juga untuk daftar tertaut. Paradigma hanya masuk akal untuk array, dan kemudian tidak banyak. Mengapa saya perlu dua objek iterator, bukan hanya satu ...
Tuntable
5
@ aberglas mereka sama sekali tidak rusak, Anda hanya tidak terbiasa dengan mereka, itulah sebabnya saya menganjurkan menggunakannya bahkan ketika Anda tidak harus! Rentang setengah terbuka adalah konsep umum, dan penjaga yang tidak pernah dimaksudkan untuk diakses secara langsung hampir setua pemrograman itu sendiri.
Mark Ransom
4
lihat aliran iterators dan pikirkan tentang apa yang == telah dilakukan sesat agar sesuai dengan pola, dan kemudian katakan padaku iterator tidak rusak! Atau untuk daftar tertaut. Bahkan untuk array, harus menentukan satu melewati akhir adalah ide gaya C rusak - pointer ke tidak pernah. Mereka harus seperti Java atau C # atau iterator bahasa lain, dengan satu iterator diperlukan (bukan dua objek) dan tes akhir yang sederhana.
Tuntable
53

karena Anda tidak mengikat kode Anda dengan implementasi tertentu dari daftar some_vector. jika Anda menggunakan indeks array, harus berupa array; jika Anda menggunakan iterator Anda dapat menggunakan kode itu pada implementasi daftar apa pun.

kejam
sumber
23
Intnaly antarmuka daftar std :: tidak menawarkan operator [] (size_t n) karena itu akan menjadi O (n).
MSalters
33

Bayangkan some_vector diimplementasikan dengan daftar tertaut. Kemudian meminta item di tempat ke-i mengharuskan operasi saya harus dilakukan untuk melintasi daftar node. Sekarang, jika Anda menggunakan iterator, secara umum, itu akan melakukan upaya terbaik untuk menjadi seefisien mungkin (dalam kasus daftar tertaut, itu akan mempertahankan pointer ke node saat ini dan memajukannya di setiap iterasi, hanya membutuhkan operasi tunggal).

Jadi itu menyediakan dua hal:

  • Abstraksi penggunaan: Anda hanya ingin mengulang beberapa elemen, Anda tidak peduli bagaimana melakukannya
  • Performa
asterit
sumber
1
"Ini akan mempertahankan pointer ke node saat ini dan memajukannya [hal-hal baik tentang efisiensi]" - ya, saya tidak mengerti mengapa orang kesulitan memahami konsep iterators. mereka secara konseptual hanya superset dari pointer. mengapa menghitung offset beberapa elemen berulang kali ketika Anda bisa men-cache pointer ke sana? baik, itulah yang dilakukan iterator juga.
underscore_d
27

Aku akan menjadi pendukung setan di sini, dan tidak merekomendasikan iterator. Alasan utama mengapa, adalah semua kode sumber yang telah saya kerjakan mulai dari pengembangan aplikasi Desktop hingga pengembangan game apakah saya juga tidak perlu menggunakan iterators. Setiap saat mereka tidak diperlukan dan kedua asumsi tersembunyi dan kekacauan kode dan debugging mimpi buruk yang Anda dapatkan dengan iterator menjadikannya contoh utama untuk tidak menggunakannya dalam aplikasi apa pun yang membutuhkan kecepatan.

Bahkan dari sudut pandang maintence mereka berantakan. Itu bukan karena mereka tetapi karena semua alias yang terjadi di belakang layar. Bagaimana saya tahu bahwa Anda belum mengimplementasikan daftar vektor atau array virtual Anda sendiri yang melakukan sesuatu yang sama sekali berbeda dengan standar. Apakah saya tahu jenis apa yang sekarang saat runtime? Apakah Anda membebani operator saya tidak punya waktu untuk memeriksa semua kode sumber Anda. Apa saya tahu versi STL yang Anda gunakan?

Masalah berikutnya yang Anda dapatkan dengan iterator adalah abstraksi yang bocor, meskipun ada banyak situs web yang membahas hal ini secara terperinci dengan mereka.

Maaf, saya belum dan masih belum melihat titik di iterator. Jika mereka abstrak daftar atau vektor dari Anda, padahal sebenarnya Anda harus sudah tahu apa vektor atau daftar berurusan dengan Anda jika Anda tidak maka Anda hanya akan mengatur diri sendiri untuk beberapa sesi debug hebat di masa depan.

Chad
sumber
23

Anda mungkin ingin menggunakan iterator jika Anda ingin menambah / menghapus item ke vektor saat Anda mengulanginya.

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;
    }
}

Jika Anda menggunakan indeks, Anda harus mengacak item naik / turun di array untuk menangani penyisipan dan penghapusan.

Brian Matthews
sumber
3
jika Anda ingin memasukkan elemen di tengah wadah maka mungkin vektor bukan pilihan wadah yang baik untuk memulai. tentu saja, kita kembali ke mengapa iterator itu keren; itu sepele untuk beralih ke daftar.
wilhelmtell
Mengulangi semua elemen cukup mahal std::listdibandingkan dengan std::vector, jika Anda merekomendasikan menggunakan daftar-tertaut alih-alih a std::vector. Lihat Halaman 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Dalam pengalaman saya, saya menemukan std::vectorlebih cepat daripada std::listbahkan jika saya mencari semua itu dan menghapus elemen di posisi sewenang-wenang.
David Stone
Indeks stabil, jadi saya tidak melihat pengocokan tambahan apa yang diperlukan untuk memasukkan dan menghapus.
musiphil
... Dan dengan daftar tertaut - yang seharusnya digunakan di sini - pernyataan loop Anda akan lebih for (node = list->head; node != NULL; node = node->next)pendek dari dua baris kode Anda yang disatukan (deklarasi dan loop head). Jadi saya katakan lagi - tidak ada perbedaan mendasar dalam singkatnya antara menggunakan iterator dan tidak menggunakannya - Anda masih harus memenuhi tiga bagian dari forpernyataan, bahkan jika Anda menggunakan while: menyatakan, beralih, periksa penghentian.
Insinyur
16

Pemisahan Kekhawatiran

Sangat bagus untuk memisahkan kode iterasi dari perhatian 'inti' dari loop. Ini hampir merupakan keputusan desain.

Memang, pengulangan dengan indeks mengikat Anda ke implementasi wadah. Meminta wadah untuk memulai dan mengakhiri iterator, memungkinkan kode loop untuk digunakan dengan jenis wadah lainnya.

Juga, di std::for_eachjalan, Anda MEMBERITAHU koleksi apa yang harus dilakukan, bukannya MEMINTA sesuatu tentang internal

Standar 0x akan memperkenalkan penutupan, yang akan membuat pendekatan ini jauh lebih mudah digunakan - lihatlah kekuatan ekspresif eg Ruby [1..6].each { |i| print i; }...

Performa

Tapi mungkin masalah yang paling diawasi adalah bahwa, dengan menggunakan for_eachpendekatan ini menghasilkan peluang untuk membuat iterasi diparalelkan - blok threading intel dapat mendistribusikan blok kode pada jumlah prosesor dalam sistem!

Catatan: setelah menemukan algorithmsperpustakaan, dan terutama foreach, saya melewati dua atau tiga bulan menulis struct operator 'pembantu' yang sangat kecil yang akan membuat rekan pengembang Anda menjadi gila. Setelah waktu ini, saya kembali ke pendekatan pragmatis - badan lingkaran kecil tidak layak foreachlagi :)

Referensi yang harus dibaca tentang iterator adalah buku "Extended STL" .

GoF memiliki paragraf kecil kecil di akhir pola Iterator, yang berbicara tentang merek iterasi ini; itu disebut 'iterator internal'. Lihat di sini juga.

xtofl
sumber
15

Karena lebih berorientasi objek. jika Anda mengulangi dengan indeks Anda mengasumsikan:

a) bahwa benda-benda itu dipesan
b) bahwa benda-benda itu dapat diperoleh dengan indeks
c) bahwa kenaikan indeks akan mengenai setiap item
d) bahwa indeks itu dimulai dari nol

Dengan iterator, Anda mengatakan "berikan saya segalanya sehingga saya bisa bekerja dengannya" tanpa mengetahui apa yang menjadi implementasi yang mendasarinya. (Di Jawa, ada koleksi yang tidak dapat diakses melalui indeks)

Juga, dengan iterator, tidak perlu khawatir keluar dari batas array.

sinis
sumber
2
Saya tidak berpikir "berorientasi objek" adalah istilah yang benar. Iterator tidak "berorientasi objek" dalam desain. Mereka mempromosikan pemrograman fungsional lebih dari pemrograman berorientasi objek, karena mereka mendorong pemisahan algoritma dari kelas.
wilhelmtell
Juga, iterator tidak membantu menghindari keluar dari batas. Algoritma standar memang, tetapi iterator sendiri tidak.
wilhelmtell
Cukup adil @wilhelmtell, saya jelas memikirkan ini dari sudut pandang Jawa-sentris.
cynicalman
1
Dan saya pikir itu memang mempromosikan OO, karena memisahkan operasi pada koleksi dari implementasi koleksi itu. Kumpulan objek tidak harus selalu tahu algoritma apa yang harus digunakan untuk bekerja dengannya.
cynicalman
Sebenarnya ada versi STL di luar sana yang telah memeriksa iterator, artinya akan mengeluarkan semacam pengecualian di luar batas ketika Anda mencoba melakukan sesuatu dengan iterator itu.
Daemin
15

Satu hal yang menyenangkan tentang iterator adalah mereka lebih memungkinkan Anda untuk mengekspresikan (dan menegakkan) preferensi konstanta Anda. Contoh ini memastikan bahwa Anda tidak akan mengubah vektor di tengah-tengah loop Anda:


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
}
Pat Notz
sumber
Ini terlihat masuk akal, tetapi saya masih ragu apakah itu alasan untuk memilikinya const_iterator. Jika saya mengubah vektor dalam loop, saya melakukannya karena suatu alasan, dan untuk 99,9% dari waktu itu mengubah bukanlah kecelakaan, dan untuk sisanya, itu hanya bug seperti segala jenis bug dalam kode pembuatnya perlu diperbaiki. Karena di Jawa, dan banyak bahasa lainnya, tidak ada objek const sama sekali, tetapi pengguna bahasa tersebut tidak pernah memiliki masalah tanpa dukungan const dalam bahasa tersebut.
neevek
2
@neevek Jika itu bukan alasan untuk memiliki const_iterator, lalu apa alasannya?
underscore_d
@underscore_d, saya juga ingin tahu. Saya tidak ahli dalam hal ini, hanya saja jawabannya tidak meyakinkan bagi saya.
neevek
15

Selain semua jawaban luar biasa lainnya ... intmungkin tidak cukup besar untuk vektor Anda. Sebaliknya, jika Anda ingin menggunakan pengindeksan, gunakan size_typeuntuk wadah Anda:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}
Pat Notz
sumber
1
@Pat Notz, itu poin yang sangat bagus. Dalam proses porting aplikasi Windows berbasis STL ke x64, saya harus berurusan dengan ratusan peringatan tentang menetapkan size_t ke int yang mungkin menyebabkan pemotongan.
bk1e
1
Belum lagi fakta bahwa jenis ukuran yang ditandatangani dan int ditandatangani, sehingga Anda memiliki non-intuitif, konversi bug-bersembunyi terjadi hanya untuk membandingkan int ike myvector.size().
Adrian McCarthy
12

Saya mungkin harus menunjukkan Anda juga bisa menelepon

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

MSalters
sumber
7

Iterator STL sebagian besar ada sehingga semacam algoritma STL bisa menjadi wadah independen.

Jika Anda hanya ingin mengulang semua entri dalam vektor, cukup gunakan gaya loop indeks.

Ini kurang mengetik dan lebih mudah diurai untuk kebanyakan manusia. Akan lebih baik jika C ++ memiliki loop foreach sederhana tanpa berlebihan dengan templat magic.

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

Saya tidak berpikir itu membuat banyak perbedaan untuk vektor. Saya lebih suka menggunakan indeks sendiri karena saya menganggapnya lebih mudah dibaca dan Anda dapat melakukan akses acak seperti melompat ke depan 6 item atau melompat mundur jika perlu.

Saya juga suka membuat referensi ke item di dalam loop seperti ini sehingga tidak ada banyak tanda kurung di sekitar tempat:

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

    // Do stuff to "item".
}

Menggunakan iterator bisa bagus jika Anda berpikir Anda mungkin perlu mengganti vektor dengan daftar di beberapa titik di masa depan dan juga terlihat lebih bergaya untuk STL aneh tapi saya tidak bisa memikirkan alasan lain.

Adam Pierce
sumber
sebagian besar algoritma beroperasi sekali pada setiap elemen wadah, secara berurutan. Tentu saja ada pengecualian di mana Anda ingin melintasi koleksi dalam urutan atau cara tertentu, tetapi dalam hal ini saya akan berusaha keras dan menulis algoritma yang terintegrasi dengan STL dan yang bekerja dengan iterator.
wilhelmtell
Ini akan mendorong penggunaan kembali dan menghindari kesalahan satu demi satu di kemudian hari. Saya kemudian akan memanggil algoritma itu sama seperti algoritma standar lainnya, dengan iterator.
wilhelmtell
1
Bahkan tidak perlu uang muka (). Iterator memiliki operator + = dan - = yang sama dengan indeks (untuk vektor dan wadah mirip vektor).
MSalters
I prefer to use an index myself as I consider it to be more readablehanya dalam beberapa situasi; di negara lain, indeks dengan cepat menjadi sangat berantakan. and you can do random accessyang bukan fitur unik dari indeks sama sekali: lihat en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d
3

Bentuk kedua mewakili apa yang Anda lakukan dengan lebih akurat. Dalam contoh Anda, Anda tidak peduli tentang nilai i, sungguh - semua yang Anda inginkan adalah elemen berikutnya di iterator.

Colen
sumber
3

Setelah mempelajari sedikit lebih banyak tentang masalah jawaban ini, saya menyadari itu agak terlalu menyederhanakan. Perbedaan antara loop ini:

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

Dan loop ini:

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

Cukup minim. Bahkan, sintaks melakukan loop dengan cara ini tampaknya tumbuh pada saya:

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

Iterator membuka beberapa fitur deklaratif yang cukup kuat, dan ketika dikombinasikan dengan perpustakaan algoritma STL Anda dapat melakukan beberapa hal keren yang berada di luar lingkup indeks array administrivia.

Jason Baker
sumber
Yang benar adalah bahwa jika semua iterator sama kompaknya dengan contoh terakhir Anda, langsung dari kotak, saya akan punya sedikit masalah dengan mereka. Tentu saja, itu sama dengan for (Iter it = {0}; it != end; ++it) {...}- Anda baru saja meninggalkan deklarasi - jadi singkatnya tidak jauh berbeda dari contoh kedua Anda. Tetap, +1.
Insinyur
3

Pengindeksan membutuhkan muloperasi ekstra . Misalnya, untuk vector<int> v, kompiler dikonversi v[i]menjadi &v + sizeof(int) * i.

Marc Eaddy
sumber
Mungkin bukan kerugian yang signifikan dibandingkan dengan iterator dalam banyak kasus, tetapi itu adalah hal yang baik untuk diperhatikan.
nobar
3
Untuk akses elemen tunggal yang terisolasi, mungkin. Tetapi jika kita berbicara tentang loop - seperti OP - maka saya cukup yakin jawaban ini didasarkan pada kompiler non-optimisasi imajiner. Setiap orang yang setengah layak akan memiliki banyak peluang dan kemungkinan untuk men-cache sizeofdan hanya menambahkannya sekali per iterasi, daripada melakukan seluruh perhitungan offset lagi setiap waktu.
underscore_d
2

Selama iterasi Anda tidak perlu tahu jumlah item yang akan diproses. Anda hanya perlu item dan iterator melakukan hal-hal seperti itu dengan sangat baik.

Sergey Stolyarov
sumber
2

Belum ada yang menyebutkan bahwa satu keuntungan dari indeks adalah bahwa mereka tidak menjadi tidak valid ketika Anda menambahkan ke wadah yang berdekatan seperti std::vector, sehingga Anda dapat menambahkan item ke wadah selama iterasi.

Ini juga dimungkinkan dengan iterator, tetapi Anda harus menelepon reserve(), dan karena itu perlu tahu berapa banyak item yang akan Anda tambahkan.

danpla
sumber
1

Beberapa poin bagus sudah. Saya punya beberapa komentar tambahan:

  1. Dengan asumsi kita berbicara tentang pustaka standar C ++, "vektor" menyiratkan sebuah wadah akses acak yang memiliki jaminan C-array (akses acak, tata letak memori contiguos dll). Jika Anda mengatakan 'some_container', banyak dari jawaban di atas akan lebih akurat (kemandirian wadah dll).

  2. Untuk menghilangkan ketergantungan pada optimisasi kompiler, Anda dapat memindahkan some_vector.size () dari loop dalam kode yang diindeks, seperti:

    const size_t numElems = some_vector.size ();
    untuk (size_t i = 0; i 
  3. Selalu lakukan iterator pra-kenaikan dan perlakukan post-increment sebagai kasus luar biasa.

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

Jadi dengan asumsi dan diindekskan std::vector<>seperti wadah, tidak ada alasan yang baik untuk memilih satu dari yang lain, secara berurutan melalui wadah. Jika Anda harus sering merujuk ke indeks elemnent yang lebih lama atau lebih baru, maka versi yang diindeks lebih sesuai.

Secara umum, menggunakan iterator lebih disukai karena algoritma memanfaatkannya dan perilaku dapat dikontrol (dan secara implisit didokumentasikan) dengan mengubah jenis iterator. Lokasi array dapat digunakan sebagai ganti iterator, tetapi perbedaan sintaksis akan menonjol.

pengguna22044
sumber
1

Saya tidak menggunakan iterator untuk alasan yang sama saya tidak suka untuk setiap pernyataan. Ketika memiliki banyak loop dalam, cukup sulit untuk melacak variabel global / anggota tanpa harus mengingat semua nilai lokal dan nama iterator juga. Apa yang menurut saya berguna adalah menggunakan dua set indeks untuk berbagai kesempatan:

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] ...


  }

Saya bahkan tidak ingin menyingkat hal-hal seperti "animation_matrices [i]" ke beberapa acak "anim_matrix" -named-iterator misalnya, karena Anda tidak dapat melihat dengan jelas dari array mana nilai ini berasal.

AareP
sumber
Saya tidak melihat bagaimana indeks lebih baik dalam hal ini. Anda bisa dengan mudah menggunakan iterator dan hanya memilih konvensi untuk nama-nama mereka: it, jt, kt, dll atau bahkan hanya terus menggunakan i, j, k, dll Dan jika Anda perlu tahu persis apa yang iterator mewakili, maka saya sesuatu seperti for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()akan lebih deskriptif daripada harus terus-menerus mengindeks seperti animation_matrices[animIndex][boneIndex].
underscore_d
wow, rasanya seperti berabad-abad yang lalu ketika saya menulis pendapat itu. saat ini menggunakan foreach dan c ++ iterators tanpa merasa cringing. Saya kira bekerja dengan kode buggy selama bertahun-tahun membangun toleransi seseorang, jadi lebih mudah untuk menerima semua sintaksis dan konvensi ... selama itu berhasil, dan selama orang bisa pulang, Anda tahu;)
AareP
Haha, memang, aku tidak benar-benar melihat berapa umur ini sebelumnya! Sesuatu yang entah bagaimana saya tidak pikirkan terakhir kali adalah bahwa saat ini kami juga memiliki forloop berbasis rentang , yang membuat cara berbasis iterator melakukan ini bahkan lebih ringkas.
underscore_d
1
  • Jika Anda suka dekat dengan logam / tidak percaya pada detail implementasi, jangan gunakan iterator.
  • Jika Anda secara teratur mengganti satu jenis koleksi dengan yang lain selama pengembangan, gunakan iterator.
  • Jika Anda merasa sulit untuk mengingat bagaimana cara mengulang berbagai jenis koleksi (mungkin Anda memiliki beberapa jenis dari beberapa sumber eksternal yang digunakan), gunakan iterator untuk menyatukan cara yang Anda gunakan untuk menelusuri elemen. Ini berlaku untuk mengatakan beralih daftar tertaut dengan daftar array.

Sungguh, hanya itu yang ada untuk itu. Ini bukan seolah-olah Anda akan mendapatkan lebih banyak brevity dengan cara rata-rata, dan jika brevity benar-benar tujuan Anda, Anda selalu dapat kembali ke makro.

Insinyur
sumber
1

Jika Anda memiliki akses ke fitur C ++ 11 , maka Anda juga dapat menggunakan loop berbasis rentangfor untuk mengulangi vektor Anda (atau wadah lain) sebagai berikut:

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

Manfaat dari loop ini adalah Anda dapat mengakses elemen-elemen vektor secara langsung melalui itemvariabel, tanpa menjalankan risiko mengacaukan indeks atau membuat kesalahan ketika mendereferensikan iterator. Selain itu, placeholder automencegah Anda dari keharusan mengulangi jenis elemen wadah, yang membawa Anda lebih dekat ke solusi independen wadah.

Catatan:

  • Jika Anda membutuhkan indeks elemen di loop Anda dan operator[]ada untuk wadah Anda (dan cukup cepat untuk Anda), maka lebih baik pergi untuk cara pertama Anda.
  • forLingkaran berbasis rentang tidak dapat digunakan untuk menambah / menghapus elemen ke / dari suatu wadah. Jika Anda ingin melakukan itu, maka lebih baik tetap pada solusi yang diberikan oleh Brian Matthews.
  • Jika Anda tidak ingin mengubah elemen dalam wadah Anda, maka Anda harus menggunakan kata kunci constsebagai berikut: for (auto const &item : some_vector) { ... }.
membunyikan
sumber
0

Bahkan lebih baik daripada "memberi tahu CPU apa yang harus dilakukan" (keharusan) adalah "memberi tahu perpustakaan apa yang Anda inginkan" (fungsional).

Jadi alih-alih menggunakan loop Anda harus mempelajari algoritma yang ada di stl.

Cyber ​​Oliveira
sumber
0

Untuk kemandirian wadah

semuanya2
sumber
0

Saya selalu menggunakan indeks array karena banyak aplikasi saya memerlukan sesuatu seperti "tampilan gambar kecil gambar". Jadi saya menulis sesuatu seperti ini:

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;
    }

}
Krirk
sumber
0

Kedua implementasinya benar, tetapi saya lebih suka loop 'for'. Karena kami telah memutuskan untuk menggunakan Vector dan bukan wadah lain, menggunakan indeks akan menjadi pilihan terbaik. Menggunakan iterator dengan Vektor akan kehilangan manfaat memiliki objek dalam blok memori kontinu yang membantu memudahkan akses mereka.

Mesias
sumber
2
"Menggunakan iterator dengan Vektor akan kehilangan manfaat dari memiliki objek dalam blok memori berkelanjutan yang membantu memudahkan akses mereka." [rujukan?]. Mengapa? Apakah Anda berpikir bahwa peningkatan iterator ke wadah yang berdekatan tidak dapat diimplementasikan sebagai tambahan sederhana?
underscore_d
0

Saya merasa bahwa tidak ada jawaban di sini yang menjelaskan mengapa saya suka iterator sebagai konsep umum alih-alih pengindeksan ke dalam wadah. Perhatikan bahwa sebagian besar pengalaman saya menggunakan iterator sebenarnya tidak berasal dari C ++ tetapi dari bahasa pemrograman tingkat tinggi seperti Python.

Antarmuka iterator membebankan lebih sedikit persyaratan pada konsumen atas fungsi Anda, yang memungkinkan konsumen untuk berbuat lebih banyak dengannya.

Jika semua yang Anda butuhkan adalah dapat meneruskan-iterate, pengembang tidak terbatas menggunakan wadah yang dapat diindeks - mereka dapat menggunakan implementasi kelas apa pun operator++(T&), operator*(T)dan 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());

Algoritme Anda berfungsi untuk case yang Anda butuhkan - iterasi melalui vektor - tetapi juga dapat berguna untuk aplikasi yang tidak perlu Anda antisipasi:

#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;
}

Mencoba menerapkan operator kurung siku yang melakukan sesuatu yang mirip dengan iterator ini akan dibuat-buat, sedangkan implementasi iterator relatif sederhana. Operator kurung kotak juga membuat implikasi tentang kemampuan kelas Anda - yang dapat Anda indeks ke titik sembarang - yang mungkin sulit atau tidak efisien untuk diterapkan.

Iterator juga cocok untuk dekorasi . Orang-orang dapat menulis iterator yang mengambil iterator di konstruktor mereka dan memperluas fungsinya:

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;
}

Walaupun mainan ini mungkin tampak biasa, tidak sulit untuk membayangkan menggunakan iterator dan dekorator iterator untuk melakukan hal-hal yang kuat dengan antarmuka yang sederhana - menghias iterator hasil-hasil basis data hanya-maju dengan iterator yang membangun objek model dari hasil tunggal, misalnya . Pola-pola ini memungkinkan iterasi yang efisien-memori dari set yang tak terbatas dan, dengan filter seperti yang saya tulis di atas, berpotensi hasil evaluasi malas.

Bagian dari kekuatan template C ++ adalah antarmuka iterator Anda, ketika diterapkan pada orang-orang seperti array C panjang-tetap, meluruh ke aritmatika pointer sederhana dan efisien , menjadikannya abstraksi yang benar-benar nol biaya.

Marcus Harrison
sumber