std :: vectorの反復:符号なしvs符号付きインデックス変数


470

C ++でベクトルを反復処理する正しい方法は何ですか?

次の2つのコードフラグメントを検討してください。これは正常に機能します。

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

そしてこれ:

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

を生成しwarning: comparison between signed and unsigned integer expressionsます。

私はC ++の世界で新しいので、unsigned変数は少し恐ろしいように見え、unsigned正しく使用しないと変数が危険である可能性があることを知っています。これは正しいですか?


10
polygon.size()はunsigned型であるため、unsignedは正しいです。符号なしは常に正または0を意味します。それが意味するすべてです。したがって、変数の使用が常にカウントのみである場合は、符号なしが正しい選択です。
Adam Bruss 2013年

3
@AdamBruss .size()はタイプunsignedakaではありませんunsigned int。タイプstd::size_tです。
underscore_d 2016年

1
@underscore_d size_tはunsignedのエイリアスです。
Adam Bruss

2
@AdamBruss No. std::size_tは、_implementation-defined typedefです。標準を参照してください。現在の実装std::size_tと同等かもしれませんがunsigned、それは関係ありません。ふりをすると、移植性のないコードや未定義の動作が発生する可能性があります。
underscore_d 2016年

2
@LF ...確かに、これはおそらくstd::size_t実際に行われています。6年以上にわたるこのとりとめのないコメントの流れの中で、すべてをカバーしたと思いますか
underscore_d

回答:


817

逆方向の反復については、この回答を参照してください。

順方向の反復はほとんど同じです。イテレータを変更するか、デクリメントをスワップインクリメントで変更します。イテレータを優先する必要があります。一部の人々はstd::size_t、インデックス変数の型として使用するようにあなたに言います。ただし、これは移植可能ではありません。常に使用しsize_typeた容器のtypedefのを(あなたが前方反復処理の場合にのみ変換して逃げることができますが使用している場合、それは実際に後方反復ケースにすべての道を間違って行くことができstd::size_t、場合std::size_tのtypedefであるものよりも広くなっていますsize_type) :


std :: vectorの使用

イテレータの使用

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

重要なのは、定義がわからないイテレータには、常にプレフィックス増分形式を使用することです。これにより、コードが可能な限り汎用的に実行されるようになります。

Range C ++ 11の使用

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

インデックスの使用

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

配列の使用

イテレータの使用

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

Range C ++ 11の使用

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

インデックスの使用

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

sizeofただし、アプローチがどのような問題を引き起こす可能性があるかについて、後方反復の回答を読んでください。


ポインタのサイズタイプ:iterator_traits <element_type *> :: difference_typeを試してください。これは宣言の1口ですが、より移植性があります...
ウィルヘルムテル2009年

wilhelmtell、difference_typeは何に使うべきですか?sizeofはsize_tを返すように定義されています:)わかりません。ポインターを互いに減算する場合は、difference_typeが適切です。
Johannes Schaub-litb 2009年

この投稿で述べた手法を使用した配列の反復は、その関数に渡された配列の関数で反復が実行されている場合は機能しません。sizeof配列はsizeofポインタのみを返すためです。
systemsfault

1
@Nils署名のないループカウンターを使用することは悪い考えであることに同意します。ただし、標準ライブラリはインデックスとサイズに符号なし整数型を使用するため、標準ライブラリには符号なしインデックス型を使用します。その結果、Qt libのような他のライブラリは署名された型のみを使用します。
Johannes Schaub-litb

32
C ++ 11の更新:forループに基づく範囲。for (auto p : polygon){sum += p;}
Siyuan Ren 2013

170

4年が経過し、Googleがこの答えをくれました。では、標準C ++ 11(別名、C ++ 0xの新しい:)(後方互換性を壊すの価格で)これを行うための新しい快適な方法実際にそこにあるautoキーワードが。使用するタイプが(コンパイラーに)明らかである場合、使用するイテレーターのタイプを明示的に指定する必要がある(ベクタータイプを繰り返す)必要がなくなります。ではv、あなたのものvector、あなたはこのような何かを行うことができます。

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

C ++ 11はさらに進んで、ベクトルのようなコレクションを反復するための特別な構文を提供します。常に同じものを書く必要がなくなります。

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

動作しているプログラムでそれを表示するには、ファイルを作成します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;
}

これを書いている時点で、これをg ++でコンパイルするときは、通常、追加のフラグを指定して新しい標準で動作するように設定する必要があります。

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

これで、例を実行できます。

$ ./auto
17
12
23
42

コンパイルと実行の手順はLinux上のgnu c ++コンパイラに固有であり、プログラムはプラットフォーム(およびコンパイラ)に依存しない必要があることに注意してください


7
C ++ 11がもたらすfor (auto& val: vec)
Flexo

@flexoありがとう、どうすればそれを忘れることができるかわかりません。十分なC ++を行っていないと思います。実用的なものがあるとは信じられませんでした(実際、それはJavaScript構文でした)。それを含めるように答えを変更しました。
kratenko

あなたの答えはとてもいいです。さまざまなOS開発キットのg ++​​のデフォルトバージョンが4.3未満であるために機能しないのは不愉快です。
Ratata Tata 2013

でベクトルを初期化する必要std::vector<int> v = std::vector<int>();がありますか、それとも単にstd::vector<int> v;代わりに使用できましたか?
Bill Cheatham 2016年

@BillCheathamまあ-私は初期化せずに試してみましたが、うまくいきました。
kratenko 2016年

44

あなたの例の特定のケースでは、これを達成するためにSTLアルゴリズムを使用します。

#include <numeric> 

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

より一般的ですが、それでもかなり単純なケースでは、次のようにします。

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

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

38

ヨハネスシャウブの回答について:

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

一部のコンパイラでは機能するかもしれませんが、gccでは機能しません。ここでの問題は、std :: vector :: iteratorが型、変数(メンバー)、または関数(メソッド)の場合の問題です。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

解決策は、「typename」というキーワードを使用することです。

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

2
これが適用されるのTはテンプレート引数であり、式std::vector<T*>::iteratorが依存名である場合のみであることを説明する必要があります。依存名をタイプとして解析するtypenameには、診断が示すように、キーワードの前に依存名を付ける必要があります。
モニカを14

17

への呼び出しvector<T>::size()は、std::vector<T>::size_typeint、unsigned intなどではなく、タイプの値を返します。

また、一般に、C ++でのコンテナーの反復は、次のようにイテレーターを使用して行われます

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

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

ここで、Tはベクターに格納するデータのタイプです。

または異なる反復アルゴリズムを使用して(std::transformstd::copystd::fillstd::for_eachエトセトラ)。


イテレータは一般的には良い考えですが、 "end"を別の変数に格納する必要はなく、for(;;)ステートメント内ですべて実行できます。
SauliusŽemaitaitis2009年

1
begin()とend()は一定の時間で償却されることはわかっていますが、通常、すべてを1行に詰め込むよりも読みやすいと思います。
Jasper Bekkers、2009年

3
forを別々の行に分割して、読みやすくすることができます。ループの外側でイテレーターを宣言するということは、異なるタイプのコンテナーに対するループごとに異なるイテレーター名が必要であることを意味します。
ジェイコンロッド

私はすべての違いを認識しています。基本的には個人の好みです。これが一般的に私が物事を成し遂げる方法です。
ジャスパーベッカーズ2009年

2
@pihentagyこれは、forループの最初のセクションで設定することになると思います。例えば。for(auto i = polygon.begin()、end = polygon.end(); i!= end; i ++)
Jasper Bekkers

11

使用size_t

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

ウィキペディアの引用:

stdlib.hおよびstddef.hヘッダーファイルsize_tは、オブジェクトのサイズを表すために使用されると呼ばれるデータ型を定義します。サイズをとるライブラリ関数は、それらがタイプsize_tであることを期待し、sizeof演算子はに評価されsize_tます。

の実際のタイプsize_tはプラットフォームに依存します。よくある間違いはsize_t、特に64ビットアーキテクチャが普及するにつれて、プログラミングエラーにつながる可能性があるunsigned intと同じであると想定することです。


すべてのオブジェクトを配列(それ自体もオブジェクト)に格納する必要がありますが、std :: listにはsize_tより多くの要素が含まれている可能性があるため、ベクターはsize_tで問題ありません。
MSalters 2009年

1
size_tは通常、プロセスのアドレス空間のすべてのバイトを列挙するのに十分です。一部のエキゾチックなアーキテクチャではこれが当てはまらない場合があることは理解できますが、心配する必要はありません。

私の知る限り、それはすることをお勧めします#include <cstddef>のではなく<stddef.h>、悪化し、全体または[c]stdlib、使用std::size_tあなたが間の選択肢を持っている他のどのような状況のためと同じ-というよりも修飾されていないバージョン<cheader>とを<header.h>
underscore_d 2016年

7

少し歴史:

数値が負であるかどうかを表すには、コンピュータで「符号」ビットを使用します。 int符号付きデータ型で、正と負の値(約-20億から20億)を保持できます。 Unsigned正の数値のみを保存できます(メタデータに少しも無駄をかけないため、0から約40億まで保存できます)。

std::vector::size()返すunsignedベクトルは負の長さを持っている可能性がどのようにするために、?

警告は、不等式ステートメントの右側のオペランドが左側より多くのデータを保持できることを示しています。

基本的に、20億を超えるエントリを持つベクトルがあり、整数を使用してインデックスを作成すると、オーバーフローの問題が発生します(intは負の20億に戻ります)。


6

私は通常BOOST_FOREACHを使用します:

#include <boost/foreach.hpp>

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

STLコンテナー、配列、Cスタイルの文字列などで動作します。


2
他のいくつかの質問への良い答え(ベクトルをどのように反復する必要がありますか?)ですが、OPが求めていることはまったくありません(符号なし変数に関する警告の意味は何ですか?)
abelenky

3
さて、彼はベクトルを反復する正しい方法は何であるかを尋ねました。十分に関連があるようです。警告は、彼が現在の解決策に満足していない理由です。
jalf

5

完全にするために、C ++ 11構文はイテレータ(ref)の1つのバージョンのみを有効にします。

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

これは逆反復にも快適です

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

5

C ++ 11の場合

for_each正しいタイプのイテレータとラムダ式の検索を回避するなどの一般的なアルゴリズムを使用して、余分な名前付き関数/オブジェクトを回避します。

あなたの特定のケースのための短い「きれいな」例(ポリゴンが整数のベクトルであると仮定):

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

テスト済み:http : //ideone.com/i6Ethd

含めることを忘れないでくださいアルゴリズム、そしてもちろん、ベクトル:)

マイクロソフトはこれについても良い例を持っています:
ソース: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
ベクトルの場合はこれで問題ありませんが、イテレータ自体が重要な場合は、一般的にit ++ではなく++ itを使用することをお勧めします。
スティーブジェソップ

個人的には++ iを使用することに慣れていますが、ほとんどの人はi ++スタイルを好むと思います(「for」のデフォルトのVSコードスニペットはi ++です)。ただの考え
Mehrdad Afshari、

@MehrdadAfshari「ほとんどの人」が何をするのか誰が気にするのか?「ほとんどの人」は多くのことについて間違っています。前の値が決して使用されない事後インクリメント/デクリメントは、少なくとも理論的には、どこでもサブパーのサンプルコードで盲目的に使用される頻度に関係なく、間違って非効率的です。まだよく知らない人に物事をより身近に見せるためだけに、悪い習慣を奨励すべきではありません。
underscore_d 2016年

2

1つ目はタイプが正しく、厳密な意味で正しいタイプです。(そうだとすれば、サイズが0未満になることは決してありません。)しかし、この警告は、無視されるのに適した候補の1つとして私を襲います。


2
無視するのはひどい候補だと思います-簡単に修正でき、署名された値と署名されていない値を不適切に比較するエラーが原因で本物のバグが時々発生します。たとえばこの場合、サイズがINT_MAXより大きい場合、ループは終了しません。
スティーブジェソップ

...または多分それはすぐに終了します。2つのうちの1つ。符号付きの値を比較のために符号なしに変換するか、符号なしを符号付きに変換するかによって異なります。ただし、32ビットintの64ビットプラットフォームでは、win64のように、intはsize_tに昇格され、ループは終了しません。
スティーブジェソップ

@SteveJessop:ループは決して終わらないとは言えません。ときの繰り返しでi == INT_MAX、その後、i++未定義の動作が発生します。この時点で、何かが起こる可能性があります。
Ben Voigt 2014年

@BenVoigt:true、それでも警告を無視する根拠は提供されません:-)
Steve Jessop

2

反復する必要があるかどうかを検討する

<algorithm>標準ヘッダは、このための設備を提供してくれます。

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

アルゴリズムライブラリの他の関数は一般的なタスクを実行します。自分の労力を節約したい場合は、何が利用できるかを確認してください。


1

あいまいですが重要な詳細:次のように「for(auto it)」と言うと、実際の要素ではなく、オブジェクトのコピーが表示されます。

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]

ベクトルの要素を変更するには、イテレータを参照として定義する必要があります。

for(auto &it : v)

1

コンパイラがサポートしている場合は、に基づく範囲を使用してベクトル要素にアクセスできます。

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

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

版画:1 2 3。ベクトルの要素を変更するためにこのテクニックを使用することはできません。


0

2つのコードセグメントは同じように機能します。ただし、unsigned int "ルートは正しいです。unsignedint型を使用すると、それを使用したインスタンスのベクターでより効果的に機能します。ベクターでsize()メンバー関数を呼び出すと、符号なし整数値が返されるため、変数を比較します。 「i」を独自のタイプの値に。

また、「unsigned int」がコードでどのように表示されるかについて少し不安がある場合は、「uint」を試してください。これは基本的に「unsigned int」の短縮版であり、まったく同じように機能します。また、それを使用するために他のヘッダーを含める必要はありません。


size()の符号なし整数は、C ++用語では必ずしも「unsigned int」に等しいとは限りません。多くの場合、この場合の「unsigned integer」は64ビットの符号なし整数であり、「unsigned int」は通常32ビットです。
メドラン

0

答えで言及されていないため、これを追加します。インデックスベースの反復の場合はdecltype(vec_name.size())std::vector<T>::size_type

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.