Standart yineleyici aralıkları neden [başlangıç, bitiş] yerine [başlangıç, bitiş)?


205

Standart neden end()asıl sondan ziyade bir son olarak tanımlanıyor ?


19
Tahmin ediyorum "çünkü standardın söylediği şey" kesmeyecek, değil mi? :)
Luchian Grigore

39
@LuchianGrigore: Tabii ki hayır. Bu, (arkasındaki insanlar) standarda olan saygımızı aşındırır. Standart tarafından yapılan seçimler için bir neden olmasını beklemeliyiz .
Kerrek SB

4
Kısacası, bilgisayarlar insanlar gibi sayılmaz. Ancak insanların neden bilgisayar gibi sayılmadığını merak ediyorsanız, İnsanların Hiç Bir Şey: Ben Doğal Değil: Doğal Bir Tarihin, insanların daha az bir sayı olduğunu keşfettikleri soruna derinlemesine bakmak için tavsiye ederim . birden.
John McFarlane

8
"Sonuncusu" oluşturmanın tek bir yolu olduğundan, genellikle ucuz değildir, çünkü gerçek olmak zorundadır. "Uçurumun ucundan düştünüz" üretmek her zaman ucuzdur, pek çok olası temsil olacaktır. (geçersiz *) "ahhhhhhh" iyi olur.
Hans Passant

6
Sorunun tarihine baktım ve bir saniyeliğine şaka yaptığını düşündüm.
Asaf

Yanıtlar:


286

En iyi argüman Dijkstra'nın kendisi tarafından yapılan tartışmadır :

  • Sen aralığın büyüklüğü basit bir fark olmasını istediğiniz  -  başlayacak ;

  • sekanslar boş olanlara dejenere olduğunda alt sınır dahil olmak daha "doğal" dır ve ayrıca alternatif ( alt sınır hariç ) bir "başlangıçtan önce" sentinel değerinin varlığını gerektireceğinden.

Hala neden bir sayı yerine sıfırdan saymaya başladığınızı gerekçelendirmeniz gerekiyor, ancak bu sorunuzun bir parçası değildi.

[Başlangıç, bitiş) kuralının ardındaki bilgelik, doğal olarak zincirlenen aralık tabanlı yapılara birden çok iç içe veya yinelenen çağrı ile ilgilenen herhangi bir algoritmaya sahip olduğunuz zaman tekrar tekrar öder. Buna karşılık, iki kat kapalı bir aralık kullanılması, tek tek ve son derece hoş olmayan ve gürültülü bir kodla sonuçlanabilir. Örneğin, bir bölümü [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 ) ele alalım . Başka bir örnek, süreleri for (it = begin; it != end; ++it)çalıştıran standart yineleme döngüsüdür end - begin. Her iki uç da kapsayıcıysa, ilgili kod daha az okunabilir olacaktır ve boş aralıkları nasıl ele alacağınızı hayal edin.

Son olarak, saymanın neden sıfırdan başlaması gerektiği konusunda güzel bir argüman yapabiliriz: Yeni oluşturduğumuz aralıklar için yarı açık kuralla, bir dizi N öğesi verilirse (bir dizinin üyelerini saymayı söyleyin), 0 doğal "başlangıç" tır, böylece aralığı herhangi bir garip ofset veya düzeltme olmadan [0, N ) olarak yazabilirsiniz .

Özetle: 1menzil tabanlı algoritmalarda her yerde sayıyı görmüyor olmamız , [başlangıç, bitiş) kuralının doğrudan bir sonucu ve motivasyonudur.


2
N boyutlu bir dizi üzerinden döngü yinelemesi için tipik C "(i = 0; i <N; i ++) a [i] = 0;" dir. Şimdi, bunu doğrudan yineleyicilerle ifade edemezsiniz - birçok kişi <anlamlı hale getirmeye zaman harcamaktadır. Ancak, "(i = 0; i! = N; i ++) ..." için 0 ve son için N eşlemesi demek neredeyse eşit derecede açıktır.
Krazy Glew

3
@KrazyGlew: Döngü örneğime türleri kasten koymadım. Eğer değerleri ve s olarak düşünürseniz beginve sırasıyla, mükemmel bir uyum sağlar. Tartışmalı olarak, geleneksel olandan daha doğal olan durumdur , ancak daha genel koleksiyonları düşünmeye başlayana kadar bunu asla keşfetmedik. endint0N!=<
Kerrek SB

4
@KerrekSB: "Daha genel koleksiyonları düşünmeye başlayana kadar [! = Daha iyi] olduğunu asla keşfetmedik." Stepanov'un STL'den önce böyle şablon kütüphaneleri yazmaya çalışan biri olarak konuşmayı hak ettiği şeylerden biri olan IMHO. Ancak, "! =" Daha doğal olduğunu tartışacağım - ya da daha doğrusu! = Muhtemelen hatalar getirdiğini, <yakalayacağını. Düşünün (i = 0; i! = 100; i + = 3) ...
Krazy Glew

@KrazyGlew: {0, 3, 6, ..., 99} dizisi OP'nin istediği formda olmadığından son noktanız biraz konu dışı. Eğer böyle olmasını istediyseniz , başlangıçta reklamı yapılan anlambilime sahip olacak bir ++eklenebilir yineleyici şablonu yazmalısınız step_by<3>.
Kerrek SB

@KrazyGlew <bazen bir hatayı gizlese bile, yine de bir hatadır . Birisi kullanması !=gerektiğinde kullanırsa <, bu bir hatadır. Bu arada, bu hata kralı birim testi veya iddialarla bulmak kolaydır.
Phil1970

80

Eğer yineleyiciler göstermiyorsa düşünün Aslında, eğer yineleyici ilgili bir sürü şey aniden çok daha mantıklı en dizisinin elemanları fakat aradaki yanında eleman hakkı erişen çözümleyecek ile. Sonra "bir geçmiş son" yineleyici aniden hemen mantıklı:

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

Açıkçası begindizinin başlangıcını endve aynı dizinin sonunu işaret eder. Dereferencing beginöğeye erişir Ave dereferencing endhiçbir anlam ifade etmez, çünkü öğesinde doğru öğe yoktur. Ayrıca, iortada bir yineleyici eklemek

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

ve hemen gelen unsurların aralığı görüyoruz beginiçin iöğeleri içerir Ave Bgelen unsurların aralığı ise ihiç endöğeleri içerir Cve D. Dereferencing i, öğeye doğru, yani ikinci dizinin ilk öğesi olan öğeyi verir.

Ters yineleyiciler için "birebir" bile aniden bu şekilde belirginleşir: Bu diziyi ters çevirmek:

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

Karşılık gelen (ters) yineleyicileri aşağıdaki parantez içinde yazdım. Görüyorsunuz, ters yineleyici ait i(I adlandırdığınız hangi ri) hala elemanları arasında yer noktaları Bve C. Ancak diziyi tersine çevirme nedeniyle, şimdi öğe Bonun sağındadır.


2
Bu en iyi cevap IMHO'dur, ancak yineleyicilerin sayılara işaret etmesi ve elemanlar arasındaki sayının (sözdizimi foo[i]) konumdan hemen sonra öğe için bir kısayol olması daha iyi gösterilebileceğini düşünüyorum i. Bunu düşünerek, bir dilin "konum i'den hemen sonra öğe" ve "konum i'den hemen önce öğe" için ayrı işleçlere sahip olmasının yararlı olup olmayacağını merak ediyorum, çünkü birçok algoritma bitişik öğe çiftleriyle çalışıyor ve " İ "konumunun her iki yanındaki öğeler" i ve i + 1 konumlarındaki öğeler "den daha temiz olabilir.
supercat

@supercat: Sayıların yineleyici konumlarını / indekslerini değil, öğelerin kendilerini göstermeleri gerekiyordu. Bunu daha net hale getirmek için rakamları harflerle değiştireceğim. Aslında, verilen sayılarla, begin[0](rasgele bir erişim yineleyicisi varsayarak) öğeye erişebilir 1, çünkü 0örnek dizilimde hiçbir öğe yoktur .
celtschk

Neden "start" yerine "start" kelimesi kullanılıyor? Sonuçta, "başlangıç" bir fiildir.
user1741137

@ user1741137 Bence "başlangıç" kısaltması anlamına geliyor "başlangıç" (şimdi mantıklı) kısaltmasıdır. "başlamak" çok uzun olmak, "başlamak" kulağa hoş geliyor. "start", "start" fiili ile çelişir (örneğin, start()belirli bir işlemi başlatmak için sınıfınızda bir işlev tanımlamanız gerektiğinde veya her ne olursa olsun, zaten varolan bir işlemle çakışması rahatsız edici olacaktır).
Fareanor

74

Standart neden end()asıl sondan ziyade bir son olarak tanımlanıyor ?

Çünkü:

  1. Boş aralıklar için özel kullanımdan kaçınır. Boş aralıklar begin()için end()&
  2. Öğeler üzerinde yinelenen döngüler için son kriteri basitleştirir: Döngüler end()ulaşılmadığı sürece devam eder .

64

Çünkü o zaman

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

ve böyle garip şeyler yapmak zorunda kalmayacaksın

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

ve yanlışlıkla hatalı kod yazmazsınız.

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

Ayrıca: Geçerli bir öğeye işaret edilirse ne find()dönecektir end()? Gerçekten geçersiz bir yineleyici döndüren adlı başka bir üye ister
misiniz ?! İki yineleyici zaten yeterince acı verici ...invalid()

Oh, ve bakın bu ilişkili bir yazıya .


Ayrıca:

Eğer endson öğe önceydi, nasıl olur insert()gerçek sonunda ?!


2
Bu oldukça önemsiz bir cevaptır. Örnekler kısa ve öz bir konudur ve “Ayrıca” ler başkası tarafından söylenmemiştir ve geçmişe bakıldığında çok açık görünen ama vahiyler gibi bana çarpan şeylerdir.
underscore_d

@underscore_d: Teşekkürler !! :)
user541686

btw, oy vermemek için bir ikiyüzlü gibi görünmeme rağmen, Temmuz 2016'da zaten geri döndüm!
underscore_d

@underscore_d: hahaha Fark etmedim, ama teşekkürler! :)
user541686

22

Yarı kapalı aralıkların yineleyici deyimi [begin(), end())başlangıçta düz diziler için işaretçi aritmetiğine dayanır. Bu çalışma modunda, bir diziye ve bir boyuta geçirilen işlevleriniz olur.

void func(int* array, size_t size)

[begin, end)Bu bilgilere sahip olduğunuzda yarı kapalı aralıklara dönüştürmek çok basittir:

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

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

Tamamen kapalı aralıklarla çalışmak daha zordur:

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

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

Dizilere işaretçiler C ++ 'da yineleyiciler olduğundan (ve sözdizimi buna izin vermek için tasarlandığından), aramaktan std::find(array, array + size, some_value)daha kolay std::find(array, array + size - 1, some_value).


Ayrıca, yarı kapalı aralıklarla çalışıyorsanız, !=son koşulu kontrol etmek için operatörü kullanabilirsiniz , çünkü (operatörleriniz doğru tanımlanmışsa) <ima eder !=.

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

Ancak bunu tamamen kapalı aralıklarla yapmanın kolay bir yolu yoktur. İle sıkışıp kaldın <=.

C ++ 'da destekleyen <ve çalışan tek yineleyici >rasgele erişimli yineleyicilerdir. <=C ++ 'da her yineleyici sınıfı için bir operatör yazmak zorunda olsaydınız, tüm yineleyicilerinizi tamamen karşılaştırılabilir hale getirmeniz gerekir ve daha az yetenekli yineleyiciler (üzerinde çift yönlü yineleyiciler std::listveya giriş yineleyiciler gibi) oluşturmak için daha az seçenek olurdu faaliyet gösterdiklerini iostreamsC ++ tamamen kapalı aralıkları kullanılıyorsa).


8

İle end()sonuna geçmiş işaret biri, bu döngü için bir koleksiyon yineleme kolaydır:

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

İle end()son öğeye işaret ederek, bir döngü daha karmaşık olacaktır:

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

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

    it++;
}

0
  1. Bir kap boşsa begin() == end(),.
  2. C ++ Programcıları döngü koşullarında (daha az) !=yerine kullanma eğilimindedir <, bu nedenle end()uç noktadan bir pozisyona işaret etmek uygundur.
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.