왜 표준 반복자 범위가 [begin, end] 대신 [begin, end]입니까?


204

왜 표준 end()이 실제 끝이 아닌 마지막 끝을 정의 합니까?


19
나는 "표준이 말한 것이기 때문에"그것을 자르지 않을 것이라고 추측하고 있습니다. :)
Luchian Grigore

39
@LuchianGrigore : 물론 아닙니다. 그것은 표준에 대한 우리의 존중을 약화시킬 것입니다. 표준에 따라 선택 해야 할 이유 가있을 것으로 기대합니다 .
Kerrek SB

4
요컨대, 컴퓨터는 사람들처럼 계산하지 않습니다. 그러나 왜 사람들이 컴퓨터를 좋아하지 않는지 궁금하다면, Nothing is Is : Natural History of Zero 를 추천합니다. 하나보다.
John McFarlane

8
"마지막 방법"을 생성 할 수있는 방법은 하나뿐이기 때문에 실제로는 저렴하기 때문에 종종 저렴하지 않습니다. "벼랑 끝에서 떨어졌다"는 것은 항상 싸기 때문에 가능한 많은 표현이 될 것입니다. (void *) "ahhhhhhh"는 잘 할 것입니다.
Hans Passant

6
나는 질문의 날짜를 보았고 거기에서 잠시 당신이 농담이라고 생각했습니다.
Asaf

답변:


286

가장 좋은 주장은 Dijkstra 자신이 만든 주장입니다 .

  • 범위의 크기가 단순한 차이가 되길 원합니다. end  -  begin ;

  • 하한을 포함하는 것은 서열이 빈 것으로 변질 될 때 더욱 "자연적"이며, 또한 하한을 제외한 대안 은 "시작 전"센티넬 값의 존재를 필요로하기 때문이다.

왜 1이 아닌 0으로 계산을 시작하는지 이유를 정당화해야하지만 그것은 귀하의 질문의 일부가 아닙니다.

[시작, 끝] 규칙 뒤에 숨은 지혜는 자연스럽게 연결되는 범위 기반 구성에 대한 여러 중첩 또는 반복 호출을 처리하는 알고리즘이있을 때마다 시간을 낭비합니다. 반대로, 이중 폐쇄 범위를 사용하면 개별적으로 발생하고 매우 불쾌하고 시끄러운 코드가 발생할 수 있습니다. 예를 들어, 파티션 [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 )을 고려하십시오. 또 다른 예로는 표준 반복 루프 for (it = begin; it != end; ++it)가 있는데, 이는 end - begin시간 을 실행 합니다. 양쪽 끝이 모두 포함 된 경우 해당 코드는 읽기가 쉽지 않으며 빈 범위를 처리하는 방법을 상상해보십시오.

마지막으로, 우리는 왜 카운트가 0에서 시작해야하는지에 대한 좋은 논쟁을 할 수 있습니다. 방금 설정 한 범위에 대한 반 개방 규칙을 통해 N 개의 요소 범위 (배열의 멤버를 열거하는) 가 주어진 다면 0은 자연스러운 "시작"이므로 어색한 오프셋이나 수정없이 범위를 [0, N ) 으로 쓸 수 있습니다 .

요컨대 1, 범위 기반 알고리즘의 모든 곳에서 숫자를 볼 수 없다는 사실 은 [시작, 끝] 규칙의 직접적인 결과이며 동기 부여입니다.


2
크기가 N 인 배열을 반복하는 루프의 일반적인 C는 "for (i = 0; i <N; i ++) a [i] = 0;"입니다. 이제는 반복자를 사용하여 직접 표현할 수 없습니다. 많은 사람들이 <의미있는 것으로 만들기 위해 시간을 낭비했습니다. 그러나 "for (i = 0; i! = N; i ++) ..."라고 말하는 것은 거의 똑같습니다. 0을 시작하고 N을 종료하는 것이 편리합니다.
Krazy Glew

3
@ KrazyGlew : 의도적으로 루프 예제에 유형을 넣지 않았습니다. 당신이 생각하는 경우 beginend같은 int값의 0N각각, 완벽하게 맞습니다. 논란의 여지가 있지만, 그것은 !=전통적인 것보다 더 자연스러운 조건 <이지만, 더 일반적인 컬렉션에 대해 생각하기 시작할 때까지는 결코 발견하지 못했습니다.
Kerrek SB

4
@KerrekSB : "더 일반적인 컬렉션에 대해 생각하기 시작할 때까지 [! =가 더 좋다]는 것을 결코 알지 못했습니다." Stepanov는 STL 이전에 이러한 템플릿 라이브러리를 작성하려고 시도한 사람으로서 말하기에 크레딧을받을 자격이있는 것 중 하나 인 IMHO입니다. 그러나, 나는 "! ="가 더 자연 스럽다는 것에 대해 논쟁 할 것이다. 오히려! =가 아마도 버그를 도입했을 것이라고 주장 할 것이다. 생각해보십시오 (i = 0; i! = 100; i + = 3) ...
Krazy Glew

@KrazyGlew : 시퀀스 {0, 3, 6, ..., 99}가 OP가 요청한 형식이 아니기 때문에 마지막 요점은 다소 논외입니다. 만약 당신이 그렇게하기를 원한다면, ++-incrementable iterator template step_by<3>을 작성해야합니다.
Kerrek SB

@KrazyGlew <가 언젠가 버그를 숨기더라도 어쨌든 버그입니다 . 누군가 사용하면 !=자신이 사용해야하는 경우 <, 다음 그 것이다 버그. 그건 그렇고, 그 오류의 왕은 단위 테스트 또는 어설 션으로 쉽게 찾을 수 있습니다.
Phil1970

80

당신이 반복자가 가리키는하지 고려한다면 사실, 반복자 관련 물건을 많이 갑자기 훨씬 더 의미 에서 시퀀스의 요소 만 사이에 그것을 다음 요소를 오른쪽으로 접근 역 참조와 함께. 그런 다음 "한 과거 끝"반복자가 갑자기 즉시 의미가 있습니다.

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^               ^
   |               |
 begin            end

분명히 begin시퀀스의 시작을 end가리키고 동일한 시퀀스의 끝을 가리 킵니다. 역 참조는 begin요소를 액세스 A하고, 역 참조는 end그것에 어떤 요소 권리가 없기 때문에 이해되지 않는다. 또한 i중간에 반복자 를 추가 하면

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
 begin     i      end

당신은 즉시에서 요소의 범위 볼 begin에이 i요소를 포함 A하고 B의 요소의 범위 반면 i에이 end요소가 들어 CD. 역 참조 i는 오른쪽 요소, 즉 두 번째 시퀀스의 첫 번째 요소를 제공합니다.

리버스 이터레이터에 대한 "off-by-one"조차도 갑자기 다음과 같이 분명해집니다.

   +---+---+---+---+
   | D | C | B | A |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
rbegin     ri     rend
 (end)    (i)   (begin)

아래에 괄호 안에 해당 비역 (기본) 반복자를 작성했습니다. 당신은 참조 역 반복자에 속하는 i(I의 이름 적이있는 ri) 여전히 요소들 사이에서 점 BC. 그러나 순서를 반전 시키면 요소 B가 오른쪽에 있습니다.


2
반복자가 숫자를 가리키고 요소가 숫자 사이에있는 경우 (구문 foo[i])이 위치 바로 뒤에 있는 항목의 약어 인 경우 더 잘 설명 될 수 있다고 생각하지만 이것은 IMHO의 가장 좋은 대답 i입니다. 그것에 대해 생각하면, 많은 알고리즘이 인접한 항목 쌍과 함께 작동하고 " 위치 i의 어느 한쪽에있는 항목은 "i 및 i + 1에있는 항목"보다 깨끗할 수 있습니다.
supercat

@ supercat : 숫자는 반복자 위치 / 표시를 나타내지 않고 요소 자체를 나타냅니다. 더 명확하게하기 위해 숫자를 글자로 바꿉니다. 실제로, 주어진 숫자로 begin[0](임의 액세스 반복자를 가정) 내 예제 시퀀스에 1요소가 없으므로 element 0에 액세스 합니다.
celtschk

"시작"이 아닌 "시작"이라는 단어가 사용되는 이유는 무엇입니까? 결국 "시작"은 동사입니다.
user1741137

@ user1741137 "begin"은 "beginning"(이제 의미가 있음)의 약어로 생각됩니다. "시작"이 너무 길면 "시작"이 잘 맞습니다. "start"는 동사 "start"와 충돌합니다 (예 start(): 특정 프로세스를 시작하기 위해 클래스에서 함수를 정의해야하는 경우 또는 기존 프로세스와 충돌하면 성가 시게됩니다).
Fareanor

74

왜 표준 end()이 실제 끝이 아닌 마지막 끝을 정의 합니까?

때문에:

  1. 빈 범위에 대한 특별한 처리를 피합니다. 빈 범위의 경우 &와 begin()같습니다 end()
  2. 이는 요소를 반복하는 루프에 대해 최종 기준을 단순하게 만듭니다. 루프 end()는 도달하지 않는 한 계속 지속됩니다 .

64

그때부터

size() == end() - begin()   // For iterators for whom subtraction is valid

그리고 당신은 같은 어색한 일 을 할 필요가 없습니다

// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }

당신은 실수로 쓰지 않습니다 잘못된 코드 등이

bool empty() { return begin() == end() - 1; }    // a typo from the first version
                                                 // of this post
                                                 // (see, it really is confusing)

bool empty() { return end() - begin() == -1; }   // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators

또한 : 유효한 요소를 가리키는 경우 무엇이 find()반환 end()됩니까? 잘못된 반복자를 반환하는 다른 멤버
정말로 원 하십니까 ?! 두 개의 이터레이터는 이미 충분히 고통 스럽습니다 ...invalid()

아, 관련 게시물을 참조하십시오 .


또한:

는 경우 end마지막 요소 전과 어떻게 것 insert()진정한 끝!


2
이것은 과소 평가 된 답변입니다. 예는 간결하고 요점까지 정확하며, "Also"는 다른 사람에 의해 언급되지 않았으며, 회고에서 매우 명백해 보이지만 계시와 같은 종류의 것들입니다.
underscore_d

@underscore_d : 감사합니다 !! :)
user541686

btw, 내가지지하지 않는 위선자처럼 보일 경우, 그것은 이미 2016 년 7 월에 돌아 왔기 때문입니다!
underscore_d

@underscore_d : 하하하 난 눈치 채지 못했지만 감사합니다! :)
user541686

22

반 폐쇄 범위의 반복자 관용구 [begin(), end())는 원래 일반 배열에 대한 포인터 산술을 기반으로합니다. 해당 작동 모드에서는 배열과 크기가 전달 된 함수가 있습니다.

void func(int* array, size_t size)

[begin, end)해당 정보가있을 때 반 폐쇄 범위로 변환하는 것은 매우 간단합니다.

int* begin;
int* end = array + size;

for (int* it = begin; it < end; ++it) { ... }

완전히 닫힌 범위로 작업하기가 더 어렵습니다.

int* begin;
int* end = array + size - 1;

for (int* it = begin; it <= end; ++it) { ... }

배열에 대한 포인터는 C ++의 반복자이며 구문은이를 허용하도록 설계되었으므로 호출하는 std::find(array, array + size, some_value)것보다 호출 하는 것이 훨씬 쉽습니다 std::find(array, array + size - 1, some_value).


당신이 반 폐쇄 범위에서 작동하는 경우 또한, 당신은 사용할 수 있습니다 !=(귀하의 사업자가 올바르게 정의 된 경우) becuase, 최종 상태를 확인하기 위해 연산자를 <의미한다 !=.

for (int* it = begin; it != end; ++ it) { ... }

그러나 완전히 닫힌 범위 에서이 작업을 수행하는 쉬운 방법은 없습니다. 당신은 붙어 있습니다 <=.

C ++에서 지원 <하고 >조작 하는 유일한 반복자 는 랜덤 액세스 반복자입니다. <=C ++에서 모든 반복자 클래스에 대해 연산자 를 작성해야한다면 모든 반복자를 완전히 비교할 수 있어야하고 덜 반복적 인 반복자 (예 : 양방향 반복자 std::list또는 입력 반복자) 를 작성하기위한 선택 사항이 적습니다. iostreamsC ++에서 완전히 닫힌 범위를 사용하는 경우) 에서 작동 합니다.


8

으로 end()끝을지나 가리키는 하나, 그것은 for 루프와 컬렉션 반복 간단합니다 :

for (iterator it = collection.begin(); it != collection.end(); it++)
{
    DoStuff(*it);
}

함께 end()마지막 요소를 가리키는 루프는 복잡 할 것입니다 :

iterator it = collection.begin();
while (!collection.empty())
{
    DoStuff(*it);

    if (it == collection.end())
        break;

    it++;
}

0
  1. 컨테이너가 비어 있으면 begin() == end().
  2. C ++ 프로그래머는 루프 조건에서 (보다 작음) !=대신 사용 하는 경향이 <있으므로 end()하나의 비표준 위치 를 가리키는 것이 편리합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.