Std üzerinden yineleme :: vektör: imzasız vs imzalı dizin değişkeni


470

C ++ 'da bir vektör üzerinde yineleme yapmanın doğru yolu nedir?

Bu iki kod parçasını düşünün, bu iyi çalışıyor:

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

ve bu:

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

hangi üretir warning: comparison between signed and unsigned integer expressions.

C ++ dünyasında yeniyim, bu yüzden unsigneddeğişken benim için biraz korkutucu görünüyor ve unsigneddeğişkenler doğru kullanılmazsa tehlikeli olabileceğini biliyorum , bu yüzden - bu doğru mu?


10
İmzasız olan doğrudur, çünkü polygon.size () imzasız tiptedir. İmzasız her zaman pozitif veya 0 anlamına gelir. Bu nedenle, değişkenin kullanımı her zaman sadece sayımlar içinse, imzasız doğru seçimdir.
Adam Bruss

3
@AdamBruss aka .size()tipinde değil . Bu tür . unsignedunsigned intstd::size_t
underscore_d

1
@underscore_d size_t, imzasız olanların takma adıdır.
Adam Bruss

2
AdamBruss No. std::size_t_ uygulama tarafından tanımlanan bir typedef. Standarda bakınız. mevcut uygulamanızda std::size_teşdeğer olabilir unsigned, ancak bu alakalı değildir. Olduğunu iddia etmek taşınabilir olmayan kodlara ve tanımlanmamış davranışlara neden olabilir.
underscore_d

2
@LF ... elbette, muhtemelen std::size_tuygulamada. Sizce 6 yıldan fazla süren bu karışık yorum akışında her şeyi ele aldık mı?
underscore_d

Yanıtlar:


817

Geriye doğru yineleme için bu cevaba bakınız .

İleri doğru yineleme neredeyse aynıdır. Sadece yineleyicileri / takas azalmasını arttırarak değiştirin. Yineleyicileri tercih etmelisiniz. Bazı insanlar std::size_tdizin değişkeni türü olarak kullanmanızı söyler . Ancak, bu taşınabilir değildir. Her zaman size_typekabın typedef'ini kullanın (İleri yineleme durumunda yalnızca bir dönüşümle kurtulabilirsiniz, ancak kullanırken std::size_t, geriye doğru yineleme durumunda tamamen yanlış gidebilir std::size_t, eğer typedef'in ne olduğundan daha genişse size_type) :


Std :: vector kullanma

Yineleyicileri kullanma

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

Önemli olan, tanımlarını bilmediğiniz yineleyiciler için her zaman önek artış formunu kullanın. Bu, kodunuzun mümkün olduğunca genel çalışmasını sağlayacaktır.

Aralık C ++ 11'i kullanma

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

Endeksleri kullanma

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

Dizileri kullanma

Yineleyicileri kullanma

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

Aralık C ++ 11'i kullanma

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

Endeksleri kullanma

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

sizeofYine de , yaklaşımın hangi soruna yol açabileceğini yineleyen cevapları okuyun .


boyut türü işaretçiler: fark_tipi kullanmak daha taşınabilir olabilir. iterator_traits <element_type *> :: fark_türünü deneyin. Bu bir bildirinin bir ağız dolusu, ama daha taşınabilir ...
wilhelmtell

wilhelmtell, ne için fark_tipi kullanmalıyım? sizeof size_t dönmek için tanımlandı :) seni anlamıyorum. Eğer işaretçiler birbirinden çıkarmak olsaydı, fark_türü doğru seçim olurdu.
Johannes Schaub - litb

Yineleme, bu işleve iletilen bir dizideki bir işlevde yineleniyorsa, bu gönderide bahsettiğiniz tekniği kullanan diziler üzerinde yineleme çalışmaz. Çünkü sizeof dizisi yalnızca sizeof işaretçisini döndürür.
systemsfault

1
@Nils imzasız döngü sayaçları kullanmanın kötü bir fikir olduğunu kabul ediyorum. ancak standart kütüphane dizin ve boyut için imzasız tamsayı türleri kullandığından, standart kütüphane için imzasız dizin türlerini tercih ederim. sonuç olarak diğer kütüphaneler sadece Qt lib gibi imzalı türleri kullanır.
Johannes Schaub - litb

32
C ++ 11 için güncelleme: döngü tabanlı aralık. for (auto p : polygon){sum += p;}
Siyuan Ren

170

Dört yıl geçti, Google bana bu cevabı verdi. İle standart C ++ 11 (aka C ++ 0x yeni:) aslında (geriye dönük uyumluluk kırma pahasına) Bunu yapmanın yeni bir hoş yol var autoanahtar sözcüğü. Kullanılacağı açıksa (derleyiciye), hangi türün kullanılacağını açık bir şekilde kullanmak için yineleyicinin türünü (vektör türünü tekrar tekrar) belirtmek zorunda kalmanın acısını kurtarır. İle vvarlığınızın vector, böyle bir şey yapabilirsiniz:

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

C ++ 11 daha da ileri gider ve vektörler gibi koleksiyonları tekrarlamak için size özel bir sözdizimi sağlar. Her zaman aynı olan şeyleri yazma zorunluluğunu ortadan kaldırır:

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

Çalışan bir programda görmek için bir dosya oluşturun 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;
}

Bunu yazarken, bunu g ++ ile derlediğinizde , normalde ekstra bir bayrak vererek yeni standartla çalışacak şekilde ayarlamanız gerekir:

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

Şimdi örneği çalıştırabilirsiniz:

$ ./auto
17
12
23
42

Derleme ve çalıştırma hakkındaki talimatların Linux'ta gnu c ++ derleyicisine özel olduğunu , programın platformdan (ve derleyiciden) bağımsız olması gerektiğini lütfen unutmayın .


7
C ++ 11 size verirfor (auto& val: vec)
Flekso

@flexo Teşekkürler, bunu nasıl unutabileceğimi bilmiyorum. Yeterince C ++ yapmıyorum, sanırım. Pratik bir şey olduğuna inanamadım (aslında JavaScript sözdizimi olduğunu düşündüm). Cevabı buna dahil etmek için değiştirdim.
kratenko

Cevabınız çok güzel. Çeşitli OS devititlerinde g ++ 'ın varsayılan sürümünün 4.3'ün altında olması hoşnut değildir, bu da çalışmamasını sağlar.
Ratata Tata

Vektörü ile başlatmanız mı gerekiyor std::vector<int> v = std::vector<int>();, yoksa std::vector<int> v;bunun yerine basitçe kullanabilirsiniz mi?
Bill Cheatham

@BillCheatham Şey - Ben sadece başlatma olmadan denedim ve işe yaradı, bu yüzden olmadan çalışıyor gibi görünüyor.
kratenko

44

Örneğinizdeki özel durumda, bunu gerçekleştirmek için STL algoritmalarını kullanırdım.

#include <numeric> 

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

Daha genel, ama yine de oldukça basit bir durum için:

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

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

38

Johannes Schaub'un cevabı hakkında:

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

Bazı derleyicilerle çalışabilir, ancak gcc ile çalışmayabilir. Buradaki sorun, std :: vector :: iterator öğesinin bir tür, değişken (üye) veya işlev (yöntem) olup olmadığıdır. Gcc ile aşağıdaki hatayı alıyoruz:

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

Çözüm, 'typename' anahtar kelimesini söylendiği gibi kullanıyor:

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

2
Bunun yalnızca Tbir şablon bağımsız değişkeni olduğunda geçerli olduğunu ve bu nedenle ifadenin std::vector<T*>::iteratorbağımlı bir ad olduğunu açıklamalısınız. Bağımlı bir adın tür olarak ayrıştırılması için typename, tanılamada belirtildiği gibi anahtar sözcüğün önüne eklenmesi gerekir .
Monica

17

İnt, unsigned int veya else türünde vector<T>::size()bir değer döndürmek için yapılan çağrı std::vector<T>::size_type.

Ayrıca genellikle C ++ 'da bir kap üzerinde yineleme bu gibi yineleyiciler kullanılarak yapılır .

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

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

Burada T, vektörde sakladığınız veri türüdür.

Ya da farklı yineleme algoritmaları kullanarak ( std::transform, std::copy, std::fill, std::for_eachve saire).


Yineleyiciler genellikle iyi bir fikir olsa da, "son" ayrı bir değişken içinde saklamak için bir ihtiyaç olduğunu ve tüm bir for (;;) deyimi içinde yapılabilir şüpheliyim.
Saulius Žemaitaitis

1
Ben start () ve end () sabit zaman amortisman biliyorum, ama genellikle bu her şeyi tek bir satır tıkmak daha okunabilir olduğunu düşünüyorum.
Jasper Bekkers

3
Okunabilirliği artırmak için for öğesini ayrı satırlara bölebilirsiniz. Yineleyicilerin döngü dışında bildirilmesi, farklı türdeki kaplar üzerindeki her döngü için farklı bir yineleyici adına ihtiyacınız olduğu anlamına gelir.
Jay Conrod

Tüm farklılıkların farkındayım ve temelde kişisel tercih; genellikle bu şekilde bir şeyler yapıyorum.
Jasper Bekkers

2
@pihentagy Sanırım for-loop'un ilk bölümünde bunu ayarlamak olurdu. Örneğin. (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
Jasper Bekkers

11

Kullanım size_t:

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

Wikipedia alıntısı :

Stdlib.h ve stddef.h başlık dosyaları size_t, bir nesnenin boyutunu temsil etmek için kullanılan bir veri türü tanımlar . Boyut alan kütüphane işlevleri bunların türünü bekler size_tve sizeof operatörü olarak değerlendirilir size_t.

Gerçek türü size_tplatforma bağlıdır; yaygın bir hata, size_timzasız int ile aynı olduğunu varsaymaktır , bu da özellikle 64 bit mimariler daha yaygın hale geldikçe programlama hatalarına yol açabilir.


size_t Tamam vektör için, bir dizideki tüm nesneleri (kendisi de bir nesne) depolamak gerekir, ancak std :: list size_t öğelerinden daha fazlasını içerebilir!
MSalters

1
size_t normalde bir işlemin adres alanındaki tüm baytları numaralandırmak için yeterlidir. Bazı egzotik mimarilerde durum böyle olmayabilirken, bunun için endişelenmemeyi tercih ederim.

AFAIK bu önerilir #include <cstddef>ziyade <stddef.h>, daha da kötüsü, tamamını veya [c]stdlibve kullanımını std::size_tsize arasında bir seçim var başka durum için ve aynı - ziyade vasıfsız versiyonunu <cheader>ve <header.h>.
underscore_d

7

Biraz tarih:

Sayının negatif olup olmadığını göstermek için bir 'işaret' biti kullanın. intpozitif ve negatif değerleri (yaklaşık -2 milyar ila 2 milyar) tutabileceği anlamına gelen imzalı bir veri türüdür. Unsignedyalnızca pozitif sayıları depolayabilir (ve meta verilerde biraz israf etmediğinden daha fazlasını depolayabilir: 0 ila yaklaşık 4 milyar).

std::vector::size()Bir döndüren unsignedbir vektör negatif uzunluğa sahip olabilir nasıl?

Uyarı, eşitsizlik ifadenizin sağ işleneninin soldan daha fazla veri tutabileceğini söylüyor.

Esasen 2 milyardan fazla girişe sahip bir vektörünüz varsa ve taşma sorunlarına çarpacağınız endekslemek için bir tamsayı kullanırsanız (int negatif 2 milyara geri döner).


6

Genellikle BOOST_FOREACH kullanıyorum:

#include <boost/foreach.hpp>

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

STL kapları, diziler, C tarzı dizgiler vb.


2
Başka bir soruya iyi bir cevap (nasıl bir vektörü tekrarlamalıyım?), Ama tamamen OP'nin ne istediğini değil (imzasız bir değişken hakkındaki uyarının anlamı nedir?)
abelenky

3
Bir vektör üzerinde doğru yineleme yolunun ne olduğunu sordu. Yani yeterince alakalı görünüyor. Uyarı, mevcut çözümünden neden memnun olmadığıdır.
jalf

5

Tam olarak, C ++ 11 sözdizimi yineleyiciler için yalnızca bir başka sürümü etkinleştirir ( ref ):

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

Tersine yineleme için de rahat olan

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

5

C ++ 11'de

for_eachEk adlandırılmış işlevleri / nesneleri önlemek için doğru yineleyici ve lambda ifade türü arama önlemek için gibi genel algoritmalar kullanırsınız .

Özel durumunuz için kısa "güzel" örnek (çokgenin bir tamsayı vektörü olduğu varsayılarak):

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

üzerinde test edildi: http://ideone.com/i6Ethd

Dahil etmeyi unutmayın : algoritma ve elbette vektör :)

Microsoft'un bu konuda da güzel bir örneği var:
kaynak: 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
Vektör için bu iyidir, ancak genel olarak yineleyicinin kendisinin önemsiz olmadığı durumlarda ++ yerine ++ kullanmak daha iyidir.
Steve Jessop

Şahsen, ben ++ i kullanmaya alışkınım, ama çoğu insanın i ++ stilini tercih ettiğini düşünüyorum ("için" için varsayılan VS kod snippet'i i ++ 'dır). Just a
think

@MehrdadAfshari "Çoğu insanın" ne yaptığına kim önem veriyor? "çoğu insan" birçok şey hakkında yanlıştır. Ön değerin hiçbir zaman kullanılmadığı post-inc / decrement, en azından teoride, her yerde par. Kötü şeyleri, sadece işleri daha iyi bilmeyen insanlara daha tanıdık göstermek için teşvik etmemelisiniz.
underscore_d

2

Birincisi, doğru tip ve kesin anlamda doğrudur. (Eğer olduğunu düşünüyorsanız, boyut asla sıfırdan küçük olamaz.) Bu uyarı beni görmezden gelinmek için iyi adaylardan biri olarak görüyor.


2
Göz ardı edilmesi korkunç bir aday olduğunu düşünüyorum - düzeltilmesi kolay ve arada sırada imzalı / imzasız değerleri uygun olmayan şekilde karşılaştıran hatalar nedeniyle gerçek hatalar oluşuyor. Örneğin, bu durumda, boyut INT_MAX değerinden büyükse döngü hiçbir zaman sona ermez.
Steve Jessop

... ya da belki derhal sona erer. İkinin biri. İmzalanan değerin karşılaştırma için imzasız veya imzasız olanın imzalı hale dönüştürülüp dönüştürülmediğine bağlıdır. 32bit int'e sahip 64bit bir platformda, win64 gibi, int de size_t değerine yükseltilecek ve döngü asla bitmeyecek.
Steve Jessop

@SteveJessop: Döngünün asla bitmediğini kesinlikle söyleyemezsin. Yinelemede i == INT_MAX, daha sonra i++tanımlanmamış davranışa neden olur. Bu noktada her şey olabilir.
Ben Voigt

@BenVoigt: doğru ve hala uyarıyı görmezden gelmek için zemin sağlamıyor :-)
Steve Jessop

2

Hiç yinelemeniz gerekip gerekmediğini düşünün

<algorithm>Standart başlık bunun için imkanları ile bize sunar:

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

Algoritma kitaplığındaki diğer işlevler ortak görevler yerine getirir - çaba harcamak istiyorsanız nelerin kullanılabilir olduğunu bildiğinizden emin olun.


1

Belirsiz ama önemli ayrıntı: aşağıdaki gibi "için (otomatik)" derseniz, nesnenin bir kopyasını alırsınız, asıl öğenin değil:

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]

Vektörün elemanlarını değiştirmek için yineleyiciyi referans olarak tanımlamanız gerekir:

for(auto &it : v)

1

Derleyiciniz destekliyorsa, vektör öğelerine erişmek için tabanlı bir aralık kullanabilirsiniz:

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

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

Baskılar: 1 2 3. Not, bu tekniği vektörün elemanlarını değiştirmek için kullanamazsınız.


0

İki kod parçası aynı şekilde çalışır. Ancak, imzasız int "yolu doğrudur. İmzasız int türlerinin kullanılması, kullandığınız örnekte vektörle daha iyi çalışacaktır. Bir vektörde size () üye işlevinin çağrılması imzasız bir tamsayı değeri döndürür, bu nedenle değişkeni karşılaştırmak istersiniz "i" kendi türünde bir değere çevirir.

Ayrıca, "unsigned int" kodunuzda nasıl göründüğü konusunda hala biraz tedirgin iseniz, "uint" i deneyin. Bu temelde "unsigned int" in kısaltılmış bir versiyonudur ve tamamen aynı şekilde çalışır. Kullanmak için başka başlıklar da eklemeniz gerekmez.


Size () için işaretsiz tamsayı mutlaka C ++ terimleriyle "işaretsiz int" e eşit değildir, bu durumda genellikle "işaretsiz tam sayı" 64 bit işaretsiz bir tamsayı iken, "işaretsiz int" genellikle 32 bittir.
Medran

0

I gibi bu ekleme herhangi cevapta belirtildiği bulamadık: endeks bazlı yineleme için kullanabileceğimiz decltype(vec_name.size())için değerlendirmek hangistd::vector<T>::size_type

Misal

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
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.