Iteration over std :: vector: unsigned vs variable index yang ditandatangani


470

Apa cara yang benar untuk mengulangi vektor dalam C ++?

Pertimbangkan dua fragmen kode ini, ini berfungsi dengan baik:

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

dan yang satu ini:

for (int i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

yang menghasilkan warning: comparison between signed and unsigned integer expressions.

Saya baru di dunia C ++, jadi unsignedvariabelnya terlihat sedikit menakutkan bagi saya dan saya tahu unsignedvariabel bisa berbahaya jika tidak digunakan dengan benar, jadi - apakah ini benar?


10
Yang tidak ditandatangani benar karena polygon.size () bertipe unsigned. Unsigned berarti selalu positif atau 0. Itu saja artinya. Jadi jika penggunaan variabel selalu hanya untuk penghitungan maka unsigned adalah pilihan yang tepat.
Adam Bruss

3
@AdamBruss .size()bukan tipe unsignedalias unsigned int. Itu tipe std::size_t.
underscore_d

1
@underscore_d size_t adalah alias untuk unsigned.
Adam Bruss

2
@AdamBruss No. std::size_tadalah typedef yang didefinisikan oleh _implementation. Lihat Standar. std::size_tmungkin setara dengan unsigneddalam implementasi Anda saat ini, tetapi itu tidak relevan. Berpura-pura dapat menghasilkan kode non-portabel dan perilaku yang tidak terdefinisi.
underscore_d

2
@ LF ... tentu, yang mungkin std::size_tdalam praktek. Apakah Anda pikir kami sudah membahas semuanya dalam arus komentar yang mengoceh selama 6 tahun?
underscore_d

Jawaban:


817

Untuk iterasi mundur lihat jawaban ini .

Iterasi ke depan hampir identik. Cukup ganti iterators / swap decrement demi increment. Anda harus memilih iterators. Beberapa orang memberi tahu Anda untuk menggunakan std::size_tsebagai tipe variabel indeks. Namun, itu tidak portabel. Selalu gunakan size_typetypedef wadah (Meskipun Anda bisa pergi hanya dengan konversi dalam kasus iterasi maju, itu bisa benar-benar salah sepanjang jalan dalam kasus iterasi berulang ketika menggunakan std::size_t, dalam kasus std::size_tlebih luas dari apa yang diketikkan dari size_type) :


Menggunakan std :: vector

Menggunakan iterators

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

Yang penting adalah, selalu gunakan formulir kenaikan awalan untuk iterator yang definisinya tidak Anda ketahui. Itu akan memastikan kode Anda berjalan generik mungkin.

Menggunakan Range C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Menggunakan indeks

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << v[i]; ... */
}

Menggunakan array

Menggunakan iterators

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

Menggunakan Range C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Menggunakan indeks

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

Baca di jawaban iterasi mundur apa masalahnya sizeof bisa dihasilkan pendekatan ini.


size type of pointer: using difference_type mungkin lebih portabel. coba iterator_traits <element_type *> :: difference_type. ini adalah salah satu seteguk dari deklarasi, tetapi lebih portabel ...
wilhelmtell

wilhelmtell, untuk apa saya harus menggunakan difference_type? sizeof didefinisikan untuk mengembalikan size_t :) saya tidak mengerti Anda. jika saya harus mengurangi pointer satu sama lain, perbedaan_type akan menjadi pilihan yang tepat.
Johannes Schaub - litb

iterasi atas array menggunakan teknik yang telah Anda sebutkan di posting ini tidak akan berfungsi jika iterasi sedang dilakukan dalam suatu fungsi pada array yang diteruskan ke fungsi itu. Karena sizeof array hanya akan mengembalikan pointer sizeof.
systemsfault

1
@Nils saya setuju bahwa menggunakan penghitung loop yang tidak ditandai adalah ide yang buruk. tetapi karena pustaka standar menggunakan tipe integer yang tidak ditandai untuk indeks dan ukuran, saya lebih suka tipe indeks yang tidak ditandatangani untuk pustaka standar. perpustakaan lain akibatnya hanya menggunakan tipe yang ditandatangani, seperti Qt lib.
Johannes Schaub - litb

32
Pembaruan untuk C ++ 11: rentang berbasis untuk loop. for (auto p : polygon){sum += p;}
Siyuan Ren

170

Empat tahun berlalu, Google memberi saya jawaban ini. Dengan standar C ++ 11 (alias C ++ 0x ) sebenarnya ada cara baru yang menyenangkan untuk melakukan ini (dengan harga melanggar kompatibilitas mundur): autokata kunci baru . Ini menghemat rasa sakit karena harus secara eksplisit menentukan jenis iterator untuk digunakan (mengulangi jenis vektor lagi), ketika jelas (ke kompiler), jenis mana yang digunakan. Dengan vmenjadi milik Anda vector, Anda dapat melakukan sesuatu seperti ini:

for ( auto i = v.begin(); i != v.end(); i++ ) {
    std::cout << *i << std::endl;
}

C ++ 11 melangkah lebih jauh dan memberi Anda sintaks khusus untuk mengulangi koleksi seperti vektor. Ini menghilangkan keharusan menulis hal-hal yang selalu sama:

for ( auto &i : v ) {
    std::cout << i << std::endl;
}

Untuk melihatnya di program kerja, buat file auto.cpp:

#include <vector>
#include <iostream>

int main(void) {
    std::vector<int> v = std::vector<int>();
    v.push_back(17);
    v.push_back(12);
    v.push_back(23);
    v.push_back(42);
    for ( auto &i : v ) {
        std::cout << i << std::endl;
    }
    return 0;
}

Saat menulis ini, saat Anda mengompilasi ini dengan g ++ , Anda biasanya perlu mengaturnya agar bekerja dengan standar baru dengan memberikan tanda tambahan:

g++ -std=c++0x -o auto auto.cpp

Sekarang Anda dapat menjalankan contoh:

$ ./auto
17
12
23
42

Harap dicatat bahwa instruksi tentang kompilasi dan menjalankan adalah khusus untuk kompiler gnu c ++ di Linux , program harus platform (dan kompiler) independen.


7
C ++ 11 memberi Andafor (auto& val: vec)
Flexo

@flexo Terima kasih, saya tidak tahu bagaimana saya bisa melupakan itu. Kurasa tidak cukup melakukan C ++. Tidak percaya ada sesuatu yang praktis (berpikir itu sebenarnya sintaksis JavaScript). Saya mengubah jawaban untuk memasukkan itu.
kratenko

Jawaban Anda sangat bagus. Tidak menyenangkan bahwa versi standar g ++ di berbagai OS devkits berada di bawah 4.3 yang membuatnya tidak berfungsi.
Ratata Tata

Apakah Anda perlu menginisialisasi vektor std::vector<int> v = std::vector<int>();, atau dapatkah Anda menggunakan std::vector<int> v;saja?
Bill Cheatham

@ BillCheatham Yah - Saya baru mencobanya tanpa menginisialisasi, dan ternyata berhasil, jadi sepertinya tidak berfungsi.
kratenko

44

Dalam kasus khusus dalam contoh Anda, saya akan menggunakan algoritma STL untuk mencapai ini.

#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

Untuk kasus yang lebih umum, tetapi masih cukup sederhana, saya akan membahas:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );

38

Mengenai jawaban Johannes Schaub:

for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) { 
...
}

Itu mungkin bekerja dengan beberapa kompiler tetapi tidak dengan gcc. Masalahnya di sini adalah pertanyaan apakah std :: vector :: iterator adalah tipe, variabel (anggota) atau fungsi (metode). Kami mendapatkan kesalahan berikut dengan gcc:

In member function void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant

Solusinya menggunakan kata kunci 'typename' seperti yang diceritakan:

typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...

2
Anda harus menjelaskan bahwa ini hanya berlaku ketika Targumen templat, dan dengan demikian ekspresi std::vector<T*>::iteratoradalah nama dependen. Agar nama dependen diuraikan sebagai tipe, itu harus diawali dengan typenamekata kunci, seperti yang ditunjukkan oleh diagnostik.
Pasang kembali Monica

17

Panggilan untuk vector<T>::size()mengembalikan nilai tipe std::vector<T>::size_type, bukan int, int tidak bertanda tangan atau sebaliknya.

Juga umumnya iterasi di atas wadah dalam C ++ dilakukan menggunakan iterator , seperti ini.

std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();

for(; i != end; i++){
    sum += *i;
}

Di mana T adalah tipe data yang Anda simpan dalam vektor.

Atau menggunakan algoritma iterasi yang berbeda ( std::transform, std::copy, std::fill, std::for_eachdan sebagainya).


Iterator pada umumnya adalah ide yang baik, meskipun saya ragu ada kebutuhan untuk menyimpan "end" dalam variabel yang terpisah dan semuanya dapat dilakukan di dalam pernyataan for (;;).
Saulius Žemaitaitis

1
Saya tahu mulai () dan akhir () adalah waktu konstan diamortisasi, tetapi saya biasanya menemukan ini lebih mudah dibaca daripada menjejalkan semuanya menjadi satu baris.
Jasper Bekkers

3
Anda dapat membagi untuk menjadi baris terpisah untuk meningkatkan keterbacaan. Mendeklarasikan iterator di luar loop berarti Anda membutuhkan nama iterator yang berbeda untuk setiap loop di atas kontainer dari jenis yang berbeda.
Jay Conrod

Saya menyadari semua perbedaan, dan pada dasarnya turun adalah preferensi pribadi; ini umumnya bagaimana saya akhirnya melakukan sesuatu.
Jasper Bekkers

2
@pihentagy Saya kira itu akan mengaturnya di bagian pertama for-loop. misalnya. for (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
Jasper Bekkers

11

Gunakan size_t:

for (size_t i=0; i < polygon.size(); i++)

Mengutip Wikipedia :

File header stdlib.h dan stddef.h menentukan tipe data yang disebut size_tyang digunakan untuk mewakili ukuran objek. Fungsi pustaka yang mengambil ukuran mengharapkannya bertipe size_t, dan ukurannya dievaluasi oleh operator size_t.

Jenis aktualnya size_tadalah platform-dependent; kesalahan umum adalah menganggap size_tsama dengan unsigned int, yang dapat menyebabkan kesalahan pemrograman, terutama karena arsitektur 64-bit menjadi lebih umum.


size_t OK untuk vektor, karena ia harus menyimpan semua objek dalam array (itu sendiri objek juga) tetapi std :: list mungkin mengandung lebih dari elemen size_t!
MSalters

1
size_t biasanya cukup untuk menghitung semua byte dalam ruang alamat suatu proses. Meskipun saya dapat melihat bagaimana hal ini tidak terjadi pada beberapa arsitektur eksotis, saya lebih suka tidak khawatir tentang hal itu.

AFAIK disarankan untuk #include <cstddef>daripada <stddef.h>atau, lebih buruk, keseluruhan [c]stdlib, dan gunakan std::size_tdaripada versi yang tidak memenuhi syarat - dan sama untuk situasi lain di mana Anda memiliki pilihan antara <cheader>dan <header.h>.
underscore_d

7

Sedikit sejarah:

Untuk menyatakan apakah suatu angka negatif atau tidak, komputer menggunakan bit 'tanda'. intadalah tipe data yang ditandatangani artinya dapat memiliki nilai positif dan negatif (sekitar -2billion hingga 2billion). Unsignedhanya dapat menyimpan angka positif (dan karena tidak membuang sedikit pun pada metadata, ia dapat menyimpan lebih banyak: 0 hingga sekitar 4 miliar).

std::vector::size()mengembalikan sebuah unsigned, karena bagaimana mungkin vektor memiliki panjang negatif?

Peringatan itu memberi tahu Anda bahwa operan kanan pernyataan ketidaksetaraan Anda dapat menyimpan lebih banyak data daripada yang kiri.

Pada dasarnya jika Anda memiliki vektor dengan lebih dari 2 miliar entri dan Anda menggunakan integer untuk mengindeks ke dalam Anda akan mendapatkan masalah overflow (int akan membungkus kembali sekitar negatif 2 miliar).


6

Saya biasanya menggunakan BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

Ini bekerja pada wadah STL, array, string gaya-C, dll.


2
Jawaban yang baik untuk beberapa pertanyaan lain (bagaimana saya harus iterate vektor?), Tapi benar-benar tidak sama sekali apa yang OP bertanya (apa arti dari peringatan tentang variabel unsigned?)
abelenky

3
Yah, dia bertanya apa cara yang benar untuk mengulangi vektor. Jadi sepertinya cukup relevan. Peringatan itu hanya mengapa dia tidak senang dengan solusi saat ini.
jalf

5

Agar lengkap, sintaksis C ++ 11 hanya memungkinkan satu versi lain untuk iterator ( ref ):

for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
  // do something with *it
}

Yang juga nyaman untuk iterasi terbalik

for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
  // do something with *it
}

5

Dalam C ++ 11

Saya akan menggunakan algoritma umum for_eachuntuk menghindari mencari jenis iterator dan ekspresi lambda yang tepat untuk menghindari fungsi / objek bernama ekstra.

Contoh "cantik" pendek untuk kasus khusus Anda (dengan asumsi poligon adalah vektor bilangan bulat):

for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });

diuji pada: http://ideone.com/i6Ethd

Jangan lupa sertakan: algoritma dan, tentu saja, vektor :)

Microsoft sebenarnya juga merupakan contoh yang bagus untuk ini:
sumber: http://msdn.microsoft.com/en-us/library/dd293608.aspx

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // Create a vector object that contains 10 elements.
   vector<int> v;
   for (int i = 1; i < 10; ++i) {
      v.push_back(i);
   }

   // Count the number of even numbers in the vector by 
   // using the for_each function and a lambda.
   int evenCount = 0;
   for_each(v.begin(), v.end(), [&evenCount] (int n) {
      cout << n;
      if (n % 2 == 0) {
         cout << " is even " << endl;
         ++evenCount;
      } else {
         cout << " is odd " << endl;
      }
   });

   // Print the count of even numbers to the console.
   cout << "There are " << evenCount 
        << " even numbers in the vector." << endl;
}

4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

2
Untuk vektor ini baik-baik saja, tetapi secara umum lebih baik menggunakan ++ daripada ++, jika iterator itu sendiri non-sepele.
Steve Jessop

Secara pribadi, saya terbiasa menggunakan ++ i, tapi saya pikir kebanyakan orang lebih suka gaya i ++ (potongan kode VS default untuk "for" adalah i ++). Hanya sebuah pemikiran
Mehrdad Afshari

@MehrdadAfshari Siapa yang peduli dengan apa yang dilakukan "kebanyakan orang"? "kebanyakan orang" salah tentang banyak hal. Pasca-penambahan / penurunan di mana nilai pra tidak pernah digunakan adalah salah dan tidak efisien, setidaknya dalam teori - terlepas dari seberapa sering itu digunakan secara membabi buta dalam kode contoh sub-par di mana-mana. Anda seharusnya tidak mendorong praktik buruk hanya untuk membuat hal-hal terlihat lebih akrab bagi orang yang belum tahu lebih baik.
underscore_d

2

Yang pertama adalah jenis yang benar, dan benar dalam beberapa hal. (Jika Anda berpikir tentang itu, ukuran tidak pernah bisa kurang dari nol.) Namun, peringatan itu mengejutkan saya sebagai salah satu kandidat yang baik untuk diabaikan.


2
Saya pikir itu adalah kandidat yang mengerikan untuk diabaikan - mudah untuk diperbaiki, dan sesekali bug asli terjadi karena kesalahan membandingkan nilai yang ditandatangani / tidak ditandatangani secara tidak tepat. Misalnya dalam kasus ini, jika ukurannya lebih besar dari INT_MAX loop tidak pernah berakhir.
Steve Jessop

... atau mungkin itu segera berakhir. Salah satu dari keduanya. Tergantung apakah nilai yang ditandatangani dikonversi menjadi unsigned untuk perbandingan, atau unsigned dikonversi menjadi ditandatangani. Pada platform 64bit dengan int 32bit, meskipun, seperti win64, int akan dipromosikan ke size_t, dan loop tidak pernah berakhir.
Steve Jessop

@SteveJessop: Anda tidak bisa mengatakan dengan pasti bahwa loop tidak pernah berakhir. Pada iterasi kapan i == INT_MAX, kemudian i++menyebabkan perilaku yang tidak terdefinisi. Pada titik ini apa pun bisa terjadi.
Ben Voigt

@ BenVoigt: benar, dan masih tidak memberikan alasan untuk mengabaikan peringatan :-)
Steve Jessop

2

Pertimbangkan apakah Anda perlu mengulangi sama sekali

The <algorithm>standar header memberikan kita dengan fasilitas untuk ini:

using std::begin;  // allows argument-dependent lookup even
using std::end;    // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);

Fungsi lain di pustaka algoritma melakukan tugas-tugas umum - pastikan Anda tahu apa yang tersedia jika Anda ingin menghemat usaha.


1

Detail yang tidak jelas tetapi penting: jika Anda mengatakan "untuk (otomatis)" sebagai berikut, Anda mendapatkan salinan objek, bukan elemen sebenarnya:

struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
    it.i = 1;         // doesn't change the element v[0]

Untuk memodifikasi elemen-elemen vektor, Anda perlu mendefinisikan iterator sebagai referensi:

for(auto &it : v)

1

Jika kompiler Anda mendukungnya, Anda bisa menggunakan rentang berbasis untuk untuk mengakses elemen vektor:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

Cetakan: 1 2 3. Catatan, Anda tidak dapat menggunakan teknik ini untuk mengubah elemen-elemen vektor.


0

Dua segmen kode bekerja sama. Namun, rute unsigned int "benar. Menggunakan tipe int unsigned akan bekerja lebih baik dengan vektor pada saat Anda menggunakannya. Memanggil fungsi size () anggota pada vektor mengembalikan nilai integer yang tidak ditandai, jadi Anda ingin membandingkan variabel "i" dengan nilai tipe sendiri.

Juga, jika Anda masih sedikit gelisah tentang bagaimana "unsigned int" terlihat dalam kode Anda, coba "uint". Ini pada dasarnya adalah versi singkat dari "unsigned int" dan berfungsi persis sama. Anda juga tidak perlu menyertakan header lain untuk menggunakannya.


Integer unsigned untuk ukuran () tidak harus sama dengan "unsigned int" dalam istilah C ++, sering 'integer unsigned' dalam hal ini adalah integer 64bit yang tidak ditandatangani sedangkan 'unsigned int' biasanya 32 bit.
Medran

0

Menambahkan ini karena saya tidak dapat menemukannya disebutkan dalam jawaban apa pun: untuk iterasi berbasis indeks, kita dapat menggunakan decltype(vec_name.size())yang akan dievaluasistd::vector<T>::size_type

Contoh

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.