std :: vector에 대한 반복 : 부호없는 vs 부호있는 인덱스 변수


470

C ++에서 벡터를 반복하는 올바른 방법은 무엇입니까?

이 두 코드 조각을 고려하십시오.이 코드는 잘 작동합니다.

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이므로 서명되지 않은 것이 맞습니다. 부호 없음은 항상 양수 또는 0을 의미합니다. 그게 전부입니다. 따라서 변수의 사용이 항상 카운트에만 사용된다면 unsigned가 올바른 선택입니다.
Adam Bruss

3
@AdamBruss .size()unsigned일명 유형이 아닙니다 unsigned int. 유형 std::size_t입니다.
underscore_d

1
@underscore_d size_t는 부호없는 별칭입니다.
Adam Bruss

2
@AdamBruss No. std::size_t는 _implementation-defined typedef입니다. 표준을 참조하십시오. 현재 구현에서 std::size_t와 같을 수도 unsigned있지만 관련이 없습니다. 척하는 것은 이식 불가능한 코드와 정의되지 않은 행동을 초래할 수 있습니다.
underscore_d

2
@LF ... 확실히, 아마 std::size_t실제로입니다. 우리가 6 년 동안이 끔찍한 논평에서 아직 모든 것을 다루었다고 생각하십니까?
underscore_d

답변:


817

거꾸로 반복하려면 이 답변을 참조하십시오 .

앞으로 반복하는 것은 거의 동일합니다. 반복자 / 스왑 감소를 증분 단위로 변경하십시오. 반복자를 선호해야합니다. 어떤 사람들 std::size_t은 색인 변수 유형 으로 사용하라고 말합니다 . 그러나 그것은 휴대용이 아닙니다. 항상 사용 size_type(당신이 앞으로 반복하는 경우에만 변환 넘어갈 수 있지만 사용하는 경우, 실제로 후진을 반복하는 경우에 모든 방법을 잘못 될 수있는 컨테이너의 타입 정의를 std::size_t경우 std::size_t의 형식 정의 무엇보다 넓은 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그러나 접근 방식이 어떤 문제를 일으킬 수 있는지 거꾸로 반복되는 답변을 읽으십시오 .


포인터의 크기 유형 : difference_type을 사용하는 것이 더 이식 가능할 수 있습니다. iterator_traits <element_type *> :: difference_type을 시도하십시오. 이것은 선언의 한 입이지만, 더 휴대하기
쉽다

wilhelmtell, difference_type을 어떻게 사용해야합니까? sizeof는 size_t를 반환하도록 정의되어 있습니다 :) 당신을 이해하지 못합니다. 서로 포인터를 빼면 difference_type이 올바른 선택입니다.
Johannes Schaub-litb

이 게시물에서 언급 한 기술을 사용하여 배열을 반복하면 해당 함수에 전달 된 배열의 함수에서 반복이 수행되는 경우 작동하지 않습니다. sizeof 배열은 sizeof 포인터 만 반환하기 때문입니다.
systemsfault

1
@Nils 서명되지 않은 루프 카운터를 사용하는 것은 나쁜 생각이라는 데 동의합니다. 그러나 표준 라이브러리는 색인 및 크기에 부호없는 정수 유형을 사용하기 때문에 표준 라이브러리에 부호없는 색인 유형을 선호합니다. 다른 라이브러리는 결과적으로 Qt lib와 같은 서명 된 유형 만 사용합니다.
Johannes Schaub-litb

32
C ++ 11 업데이트 : 범위 기반 for 루프. for (auto p : polygon){sum += p;}
Siyuan Ren

170

4 년이 지났는데 구글 이이 답을 주었다. 와 표준 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

참고 컴파일 및 실행에 대한 지침은 특정 것을 GNU C ++ 의 컴파일러 리눅스 , 프로그램은 플랫폼 (및 컴파일러) 독립적이어야한다.


7
C ++ 11은 다음과 같은 기능을 제공합니다for (auto& val: vec)
Flexo

@flexo 감사합니다. 어떻게 잊을 수 있을지 모르겠습니다. 충분한 C ++을 수행하지 않는 것 같습니다. 실용적인 것이 있다고 생각할 수 없었습니다 (실제로는 JavaScript 구문이라고 생각했습니다). 나는 그것을 포함하도록 대답을 바꿨다.
kratenko

당신의 대답은 매우 좋습니다. 다양한 OS devkit에서 g ++의 기본 버전이 4.3 미만이므로 작동하지 않습니다.
Ratata Tata

을 사용하여 벡터를 초기화해야합니까 std::vector<int> v = std::vector<int>();, 아니면 간단히 사용할 수 std::vector<int> v;있습니까?
Bill Cheatham

@ BillCheatham Well-방금 초기화하지 않고 시도했지만 작동했지만 작동하지 않는 것 같습니다.
kratenko

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

Johannes Schaub의 답변에 관하여 :

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하려면 진단에서 알 수 있듯이 키워드 앞에 추가해야합니다 .
Monica Monica 복원

17

를 호출하면 int, unsigned int 등이 아닌 vector<T>::size()type 값 을 반환합니다 std::vector<T>::size_type.

또한 일반적으로 C ++에서 컨테이너에 대한 반복은 이와 같은 반복자를 사용하여 수행됩니다 .

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

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

여기서 T는 벡터에 저장하는 데이터 유형입니다.

또는 다른 반복 알고리즘을 사용하여 ( std::transform, std::copy, std::fill, std::for_each등등).


반복자는 일반적으로 좋은 생각이지만 "end"를 별도의 변수에 저장할 필요가 있는지 의심하고 for (;;) 문 내에서 모두 끝낼 수 있습니다.
Saulius

1
begin ()과 end ()는 일정한 시간으로 상각된다는 것을 알고 있지만 일반적으로 모든 것을 한 줄에 넣는 것보다 더 읽기 쉽습니다.
재스퍼 베커

3
가독성을 높이기 위해 for를 별도의 줄로 나눌 수 있습니다. 루프 외부에서 반복자를 선언하면 유형이 다른 컨테이너의 모든 루프마다 다른 반복자 이름이 필요합니다.
Jay Conrod

나는 모든 차이점을 알고 있으며 기본적으로 개인 취향은 무엇인지 알고 있습니다. 이것은 일반적으로 내가 일을하는 방법입니다.
Jasper Bekkers

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_tsizeof 연산자는 다음과 같이 평가됩니다 size_t.

실제 유형 size_t은 플랫폼에 따라 다릅니다. 일반적인 실수는 size_t부호없는 int와 같다고 가정 하는 것인데, 이는 특히 64 비트 아키텍처가 널리 보급됨에 따라 프로그래밍 오류로 이어질 수 있습니다.


size_t 모든 객체를 배열 (자체 자체)에 저장해야하지만 std :: list는 size_t보다 많은 요소를 포함 할 수 있기 때문에 벡터에 좋습니다.
MSalters

1
size_t는 일반적으로 프로세스의 주소 공간에있는 모든 바이트를 열거하기에 충분합니다. 일부 이국적인 아키텍처에서는 그렇지 않을 수도 있지만 걱정하지 않아도됩니다.

AFAIK이 (가) 전체 버전 #include <cstddef>보다 적 <stddef.h>거나 나쁘거나 전체 버전을 [c]stdlib사용하는 std::size_t것이 좋습니다 . <cheader>그리고 (와) 중에서 선택할 수있는 다른 상황에서도 동일합니다 <header.h>.
underscore_d

7

약간의 역사 :

숫자가 음수인지 아닌지를 나타내려면 'sign'비트를 사용하십시오. int부호있는 데이터 유형으로 양수 및 음수 값 (약 -2 십억-2 십억)을 보유 할 수 있습니다. 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 )에 대해 다른 버전 만 사용할 수 있습니다 .

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

다음포함하는 것을 잊지 마십시오 : 알고리즘과 물론 :)

Microsoft는 실제로 이것에 대한 좋은 예를 가지고 있습니다 :
source : 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를 사용하는 것이 좋습니다.
Steve Jessop

개인적으로 저는 ++ i를 사용하는 데 익숙하지만 대부분의 사람들은 i ++ 스타일을 선호한다고 생각합니다 ( "for"의 기본 VS 코드 스 니펫은 i ++입니다). 그냥 생각
Mehrdad Afshari

@MehrdadAfshari 누가 "대부분의 사람들"이 무엇을 신경 쓰는가? "대부분의 사람들"은 많은 것들에 대해 잘못입니다. 사전 값이 사용되지 않는 사후 발생 / 감소는 적어도 이론 상으로는 하위 파 예제 코드에서 얼마나 자주 맹목적으로 사용되는지에 관계없이 잘못되고 비효율적입니다. 아직 잘 모르는 사람들에게 더 친숙하게 보이도록 나쁜 습관을 장려해서는 안됩니다.
underscore_d

2

첫 번째는 형식이 정확하고 엄격한 의미에서 올바른 것입니다. (당신이 생각한다면, 크기는 0보다 작을 수 없습니다.) 그 경고는 나를 무시할 좋은 후보 중 하나라고 생각합니다.


2
무시하기가 끔찍한 후보라고 생각합니다. 수정이 쉽고, 때때로 부호있는 / 부호없는 값을 부적절하게 비교하는 오류로 인해 진정한 버그가 발생합니다. 예를 들어이 경우 크기가 INT_MAX보다 크면 루프가 종료되지 않습니다.
Steve Jessop

... 또는 즉시 종료됩니다. 둘 중 하나. 부호있는 값이 비교를 위해 부호없는 것으로 변환되는지 또는 부호없는 것이 부호있는 것으로 변환되는지에 따라 다릅니다. 그러나 win64와 같이 32 비트 int가있는 64 비트 플랫폼에서 int는 size_t로 승격되며 루프는 끝나지 않습니다.
Steve Jessop

@SteveJessop : 루프가 끝나지 않는다는 것을 확실하게 말할 수는 없습니다. 반복 일 때 i == INT_MAX, 다음 i++정의되지 않은 동작이 발생합니다. 이 시점에서 어떤 일이든 일어날 수 있습니다.
Ben Voigt

@BenVoigt : 사실, 그리고 여전히 경고를 무시할 근거를 제공하지 않습니다 :-)
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

두 코드 세그먼트는 동일하게 작동합니다. 그러나 부호없는 int "라우트는 정확합니다. 부호없는 int 유형을 사용하면 사용한 인스턴스의 벡터에서 더 잘 작동합니다. 벡터에서 size () 멤버 함수를 호출하면 부호없는 정수 값이 반환되므로 변수를 비교하려고합니다. "i"는 고유 한 유형의 값입니다.

또한 "unsigned int"가 코드에서 어떻게 보이는지에 대해 조금 불안하다면 "uint"를 사용해보십시오. 이것은 기본적으로 "unsigned int"의 단축 버전이며 정확히 동일하게 작동합니다. 또한 사용하기 위해 다른 헤더를 포함하지 않아도됩니다.


size ()에 대한 부호없는 정수는 C ++ 용어에서 "부호없는 정수"와 반드시 같을 필요는 없으며,이 경우 종종 '부호없는 정수'는 64 비트 부호없는 정수이고 '부호없는 정수'는 보통 32 비트입니다.
Medran

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.