일반 배열에 대한 범위 기반은 어떻게 작동합니까?


87

C ++ 11에서는 다른 언어의 for역할을 하는 범위 기반을 사용할 수 foreach있습니다. 일반 C 배열에서도 작동합니다.

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

언제 중지해야하는지 어떻게 알 수 있습니까? for사용되는 것과 동일한 범위에서 선언 된 정적 배열에서만 작동합니까 ? 이것을 for동적 배열과 함께 어떻게 사용 하시겠습니까?


10
C 또는 C ++에는 그 자체로 "동적"배열이 없습니다. 배열 유형이 있으며 대부분 배열처럼 동작하는 동적 할당 메모리 블록이나 배열을 가리킬 수도 있고 가리 키지 않을 수도있는 포인터가 있습니다. T [n] 유형의 모든 배열에 대해 해당 크기는 유형으로 인코딩되며에서 액세스 할 수 있습니다 for. 그러나 배열이 포인터로 붕괴되는 순간 크기 정보가 손실됩니다.
JohannesD

1
당신의 예에서, 요소의 수는있는 numbers것입니다 sizeof(numbers)/sizeof(int)예를 들어.
JohannesD

답변:


57

유형이 배열 인 모든 표현식에서 작동합니다. 예를 들면 :

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

보다 자세한 설명을 위해 오른쪽으로 전달 된 표현식의 유형이 :배열 유형 인 경우 루프는에서 ptrto ptr + size( ptr배열의 첫 번째 요소를 가리키며 배열 size의 요소 수)를 반복 합니다.

이것은 클래스 객체를 전달하거나 (그런 식으로 호출되는 멤버가없는 경우) 비 멤버 함수를 전달하는 경우 조회 beginend멤버로 작동하는 사용자 정의 유형과 대조됩니다 . 이러한 함수는 시작 및 종료 반복자를 생성합니다 (각각 마지막 요소 바로 뒤와 시퀀스의 시작을 가리킴).

이 질문은 그 차이가 존재하는 이유를 명확히합니다.


8
나는 질문이 생각 하는 방법 이 작동하지 않습니다 그것을 작동합니까
sehe

1
@sehe 질문에 여러 개의 '?'가 포함되어 있습니다. 하나는 "...와 작동합니까?"였습니다. 나는 그것이 작동 하는 방법시기를 모두 설명했다 .
Johannes Schaub-litb

8
@JohannesSchaub : 여기서 "어떻게"문제는 처음에 배열 유형의 객체 크기를 정확히 얻는 방법이라고 생각합니다 (포인터 대 배열 혼동으로 인해 거의 모든 사람이 배열의 크기 프로그래머 가능).
JohannesD

내가 믿는 유일한 비회원을 찾습니다 begin`최종 . It just happens that 표준 : 시작 `std::end멤버 함수를 사용하여 더 나은 경기를 사용할 수없는 경우 사용됩니다.
Dennis Zickefoose 2011 년

3
@Dennis no in Madrid는이를 변경하고 시작 및 종료 멤버를 선호하기로 결정했습니다. 시작 및 종료 멤버를 선호하지 않기 위해 피하기 어려운 모호함이 발생했습니다.
Johannes Schaub-litb 2010 년

44

이 질문의 가장 중요한 부분은 C ++가 배열의 크기를 어떻게 아는가입니다 (적어도이 질문을 발견했을 때 알고 싶었습니다).

C ++는 배열 정의의 일부이기 때문에 배열의 크기를 알고 있습니다. 이는 변수의 유형입니다. 컴파일러는 유형을 알아야합니다.

C ++ 11 std::extent을 사용하여 배열의 크기를 얻을 수 있기 때문에 :

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

물론 이것은별로 의미가 없습니다. 첫 번째 줄에 명시 적으로 크기를 제공 한 다음 두 번째 줄에서 가져와야하기 때문입니다. 그러나 사용할 수도 decltype있고 더 흥미로워집니다.

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

6
이것이 제가 원래 묻고 있었던 것입니다. :)
Paul Manta

19

최신 C ++ Working Draft (n3376)에 따르면 ranged for 문은 다음과 같습니다.

{
    auto && __range = range-init;
    for (auto __begin = begin-expr,
              __end = end-expr;
            __begin != __end;
            ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

따라서 for반복자를 사용 하는 일반 루프 와 동일한 방식으로 중지하는 방법을 알고 있습니다.

포인터와 크기 (동적 배열)로만 구성된 배열에 위의 구문을 사용하는 방법을 제공하기 위해 다음과 같은 것을 찾고있을 수 있습니다.

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

그런 다음이 클래스 템플릿을 사용하여 범위를 만들 수 있으며이 범위 에서 새로운 ranged for 구문을 사용하여 반복 할 수 있습니다 . 나는 이것을 사용하여 배열에 대한 포인터와 크기를 별도의 값으로 반환하는 라이브러리를 사용하여 가져온 장면의 모든 애니메이션 개체를 실행하고 있습니다.

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

이 구문은 제 생각에 사용하는 것 std::for_each또는 일반 for루프 보다 훨씬 명확 합니다.


3

정적 배열의 경계를 알고 있기 때문에 중지 할시기를 알고 있습니다.

"동적 배열"이 무엇을 의미하는지 잘 모르겠습니다. 어떤 경우에도 정적 배열을 반복하지 않으면 비공식적으로 컴파일러가 이름 beginend개체의 클래스 범위에서 반복하거나 찾습니다. 최대 begin(range)end(range)인수 종속적 조회 및 반복자로 사용을 사용.

자세한 내용은 C ++ 11 표준 (또는 공개 초안), "6.5.4 범위 기반 for진술", pg.145


4
"동적 배열"은 new[]. 이 경우 크기 표시가없는 포인터 만 있으므로 범위 기반 for이 작업 할 방법이 없습니다 .
Mike Seymour

내 대답에는 컴파일 타임에 크기 (4)가 알려진 동적 배열이 포함되어 있지만 "동적 배열"에 대한 해석이 질문자가 의도 한 것인지 여부는 알 수 없습니다.
Johannes Schaub-litb

3

일반 배열에 대한 범위 기반은 어떻게 작동합니까?

" 원거리가 (배열과 함께) 무엇을하는지 말해줘 "라고 읽어야 할까요? "

나는 다음과 같은 가정하에 대답 할 것이다-중첩 배열을 사용하는 다음 예제를 보자 :

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

텍스트 버전 :

ia[3]각각 [4]값을 포함하는 배열을 포함하는 배열 ( "중첩 배열")의 배열 입니다. 위의 예제 ia는 기본 '범위'( [3])를 통해 반복되므로 [3]시간을 반복합니다. 각 루프는 첫 번째에서 시작하여 마지막으로 끝나는 ia[3]기본 값 중 하나를 생성합니다 .- [4]값을 포함하는 배열입니다 .

  • 첫 번째 루프 : pl같음 {1,2,3,4}-배열
  • 두 번째 루프 : pl같음 {5,6,7,8}-배열
  • 세 번째 루프 : pl같음 {9,10,11,12}-배열

프로세스를 설명하기 전에 다음은 어레이에 대한 몇 가지 유용한 알림입니다.

  • 배열은 첫 번째 값에 대한 포인터로 해석됩니다. 반복없이 배열을 사용하면 첫 번째 값의 주소가 반환됩니다.
  • pl 배열을 복사 할 수 없으므로 참조 여야 합니다.
  • 만약이 - 어레이 객체 자체에 번호를 추가 할 때 배열로, 그것은 앞으로 해당 항목에 많은 시간과 '점'고 진보 n, 다음 문제의 숫자가 ia[n]동일하다 *(ia+n)(우리의 주소를 역 참조하는 n항목을 앞으로), (배열에서 해당 항목의 주소를 얻음 )과 ia+n동일 &ia[n]합니다.

무슨 일이 일어나고 있는지 :

  • 각 루프에서 plA와 설정된 기준 으로 ia[n]하여, n0 지금부터 전류 루프 횟수를 같게 pl이고 ia[0]그것의 두번째, 제 1 라운드 ia[1]등. 반복을 통해 값을 검색합니다.
  • ia+n이보다 작은 한 루프가 계속됩니다 end(ia).

... 그게 다입니다.

이것은 실제로 이것을 작성 하는 간단한 방법입니다 .

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

배열 중첩 되지 않은 경우 반복 된 값이 배열이 아니라 '정상'값이기 때문에 참조가 필요 하지 않다는 점에서이 프로세스가 조금 더 간단 해 집니다.

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

추가 정보

auto만들 때 키워드 를 사용하지 않으려면 pl어떻게합니까? 어떤 모습일까요?

다음 예에서, pl을 말한다 array of four integers. 각 루프 pl에는 다음 값이 제공됩니다 ia[n].

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

그리고 ... 그것이 작동 방식이며, 혼란을 없애기위한 추가 정보가 있습니다. for자동으로 계산 되는 '속기' 루프 일 뿐이지 만 수동으로 수행하지 않고 현재 루프를 검색하는 방법이 부족합니다.


@Andy 10 번 중 9 번 제목 이 Google에서 일치하는 항목 / 검색어가 무엇이든-제목이 어떻게 작동하는지 묻습니다 . , 언제 멈춰야할지 알지합니까? . 그럼에도 불구하고 내포 된 근본적인 질문 이 답변에서 어느 정도 다루어지며 다른 답변을 찾는 다른 사람을 위해 계속 됩니다. 이와 같은 구문 질문에는 검색자가 질문을 찾는 데 필요한 모든 정보가 있기 때문에 답변을 작성할 수 있도록 제목이 있어야합니다. 당신은 틀림없이 틀린 것이 아닙니다. 질문의 제목은 당연한 것이 아닙니다.
Super Cat

0

스택의 배열과 힙의 배열 간의 차이점을 보여주는 샘플 코드


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.