Aralık tabanlı düz diziler için nasıl çalışır?


88

C ++ 11'de , diğer diller forgibi davranan bir aralık tabanlı kullanabilirsiniz foreach. Düz C dizileriyle bile çalışır:

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

Ne zaman duracağını nasıl biliyor? Yalnızca forkullanıldığı ile aynı kapsamda bildirilmiş statik dizilerle mi çalışır? Bunu fordinamik dizilerle nasıl kullanırsınız ?


10
C veya C ++ 'da "dinamik" dizi yoktur - dizi türleri vardır ve daha sonra bir diziye veya çoğunlukla bir dizi gibi davranan dinamik olarak tahsis edilmiş bir bellek bloğuna işaret eden veya göstermeyen işaretçiler vardır. T [n] tipindeki herhangi bir dizi için, boyutu tipte kodlanmıştır ve buradan erişilebilir for. Ancak dizi bir işaretçiye dönüştüğü anda boyut bilgisi kaybolur.
JohannesD

1
Senin örnekte, öğelerin sayısı içinde numbersolduğu sizeof(numbers)/sizeof(int)örneğin.
JohannesD

Yanıtlar:


57

Türü bir dizi olan herhangi bir ifade için çalışır. Örneğin:

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

Daha detaylı bir açıklama için, sağ geçirilen ifade tipi ise :, bir dizi türü, den sonra ilmek dolaşır olduğu ptriçin ptr + size( ptrdizinin ilk elemana işaret sizedizisinin eleman sayısı olmak üzere).

Bu, bir sınıf nesnesi veya (bu şekilde çağrılan üye yoksa) üye olmayan işlevler ilettiğinizde arama yaparak beginve endüye olarak çalışan kullanıcı tanımlı türlerin tersidir . Bu işlevler, başlangıç ​​ve bitiş yineleyicileri verir (sırasıyla son öğeden sonra ve sıranın başlangıcını gösterir).

Bu soru, bu farkın neden var olduğunu açıklıyor.


8
Ben soru olduğunu düşünüyorum nasıl bu işe etmez zaman o iş yapar
sehe

1
@soru birden fazla "?" içeriyordu. Biri "... ile çalışır mı?" Hem nasıl ve ne zaman çalıştığını anlattım .
Johannes Schaub -

8
@JohannesSchaub: Burada "nasıl" Sorun, çünkü diziler karışıklık vs işaretçileri (ilk etapta bir dizi türünde bir nesnenin boyutunu almak tam olarak nasıl olduğunu düşünüyorum, değil neredeyse herkes bir dizinin boyutu bilir olduğunu programcı tarafından kullanılabilir.)
JohannesD

İnanması sadece üye olmayan arar begin`uç . It just happens that std :: başlamak `std::endüye işlevlerini kullanın ve daha iyi bir eşleşme yoksa kullanılacaktır.
Dennis Zickefoose

3
@Dennis no Madrid'de bunu değiştirmeye ve başlangıç ​​ve bitiş üyelerini tercih etmeye karar verildi. Başlangıç ​​ve bitiş üyelerini tercih etmemek, kaçınılması zor belirsizliklere neden oldu.
Johannes Schaub -

45

Sanırım bu sorunun en önemli kısmı, C ++ 'nın bir dizinin boyutunun ne olduğunu nasıl bildiğidir (en azından bu soruyu bulduğumda bilmek istedim).

C ++ bir dizinin boyutunu bilir, çünkü dizinin tanımının bir parçasıdır - değişkenin türüdür. Bir derleyicinin türü bilmesi gerekir.

C ++ 11 std::extentbir dizinin boyutunu elde etmek için kullanılabileceğinden:

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

Elbette bu pek bir anlam ifade etmiyor, çünkü boyutu ilk satırda açıkça belirtmeniz gerekiyor, daha sonra bunu ikinci satırda elde ediyorsunuz. Ancak kullanabilirsiniz decltypeve sonra daha ilginç hale gelir:

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

6
Aslında asıl sorduğum şey buydu. :)
Paul Manta

19

En son C ++ Çalışma Taslağına (n3376) göre ranged for ifadesi aşağıdakine eşdeğerdir:

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

Böylece, foryineleyicileri kullanan normal bir döngünün yaptığı gibi nasıl durdurulacağını bilir .

Yukarıdaki sözdizimini yalnızca bir işaretçi ve boyuttan (dinamik diziler) oluşan dizilerle kullanmanın bir yolunu sağlamak için aşağıdakine benzer bir şey arıyor olabilirsiniz:

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

Bu sınıf şablonu, daha sonra, sözdizimi için yeni aralığı kullanarak üzerinde yineleyebileceğiniz bir aralık oluşturmak için kullanılabilir . Bunu, yalnızca bir diziye bir işaretçi ve ayrı değerler olarak bir boyut döndüren bir kitaplık kullanılarak içe aktarılan bir sahnedeki tüm animasyon nesnelerini çalıştırmak için kullanıyorum.

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

Bu sözdizimi, bence, ne kullanacağınızdan std::for_eachveya düz bir fordöngüden çok daha nettir .


3

Ne zaman duracağını bilir çünkü durağan dizilerin sınırlarını bilir.

"Dinamik diziler" ile ne demek istediğinizden emin değilim, her durumda, statik diziler üzerinde yineleme yapmıyorsa, gayri resmi olarak, derleyici adları beginve endüzerinde yinelediğiniz nesnenin sınıfının kapsamına bakar veya bakar. argümana bağlı arama için begin(range)ve end(range)kullanma ve bunları yineleyici olarak kullanır.

Daha fazla bilgi için, C ++ 11 standardında (veya genel taslağında), "6.5.4 Aralık tabanlı forifade", s. 145


4
Bir "dinamik dizi" ile oluşturulacaktır new[]. Bu durumda, yalnızca boyutu belirtmeyen bir işaretçiniz vardır, bu nedenle aralık temelli forbununla çalışmasının bir yolu yoktur .
Mike Seymour

Cevabım, boyutu (4) derleme zamanında bilinen dinamik bir dizi içeriyor, ancak "dinamik dizi" yorumunun sorgulayan kişinin amaçladığı şey olup olmadığını bilmiyorum.
Johannes Schaub -

3

Aralık tabanlı düz diziler için nasıl çalışır?

Bu, " Söyle bana bir menzilli (dizilerle) ne yapar? "

Bunu varsayarak cevaplayacağım - İç içe dizileri kullanarak aşağıdaki örneği alın:

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

for (auto &pl : ia)

Metin versiyonu:

ia[3]her biri [4]değerler içeren dizileri içeren bir dizi dizisidir ("iç içe dizi") . Yukarıdaki örnek ia, birincil 'aralık' ( [3]) ile döngü oluşturur ve bu nedenle [3]zamanları döngüye sokar. Her bir halka bir üreten iabireyin [3]içeren bir dizi - primer değerler birinci başlayan ve son ile biten [4]değerleri.

  • İlk döngü: pleşittir {1,2,3,4}- Bir dizi
  • İkinci döngü: pleşittir {5,6,7,8}- Bir dizi
  • Üçüncü döngü: pleşittir {9,10,11,12}- Bir dizi

Süreci açıklamadan önce, diziler hakkında bazı dostça hatırlatmalar:

  • Diziler ilk değerlerine işaretçiler olarak yorumlanır - Yineleme olmadan bir dizi kullanmak ilk değerin adresini döndürür
  • pl bir referans olmalı çünkü dizileri kopyalayamıyoruz
  • Dizilerle, dizi nesnesinin kendisine bir sayı eklediğinizde, o kadar ileri doğru ilerler ve eşdeğer girdiyi 'işaret eder' - Söz nkonusu sayı ise, o zaman ia[n]şu şekilde aynıdır *(ia+n)( nGirişler olan adresi referans alıyoruz forward) ve ia+naynıdır &ia[n](Dizideki bu girişin adresini alıyoruz).

İşte neler oluyor:

  • Her döngü, plbir şekilde ayarlanır referans ile ia[n]birlikte, n0. So başlayarak akım döngüsü sayısını eşitlemek plolan ia[0]bu kadar saniye, ilk turda ia[1], vb. Değeri yineleme yoluyla alır.
  • Döngü ia+n, daha az olduğu sürece devam eder end(ia).

... Ve bununla ilgili.

Gerçekten bunu yazmanın basitleştirilmiş bir yolu :

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

Diziniz varsa değil iç içe, o zaman bu işlem referans olması biraz daha basit hale gelir değil tekrarlanan değer bir dizi değil, 'normal' değeri olmadığı için, gerekli:

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

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

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

Bazı ek bilgiler

Ya autoanahtar kelimeyi yaratırken kullanmak istemeseydik pl? Bu neye benziyor?

Aşağıdaki örnekte, plbir array of four integers. Her döngüde plşu değer verilir ia[n]:

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

Ve ... Her türlü karışıklığı ortadan kaldırmak için ek bilgilerle birlikte bu şekilde çalışır. Bu sadece forsizin için otomatik olarak sayılan bir 'kısaltma' döngüdür, ancak mevcut döngüyü manuel olarak yapmadan geri getirmenin bir yolu yoktur.


@Andy 10 kez başlığın 9'u Google'da ne eşleşiyor / hangi arama olursa olsun - Başlık, bunların nasıl çalıştığını soruyor ? , ne zaman duracağını ne zaman bilmiyor? . Öyle olsa bile, işaret ettiği ana soru olduğu ölçüde bu cevap kaplıydı ve arayan başkası için cevaba devam diğer cevap. Bunun gibi sözdizimi sorularının başlıkları olmalıdır, böylece bir cevap tek başına bu kullanılarak yazılabilir çünkü arayanın soruyu bulmak için ihtiyaç duyduğu tüm bilgiler budur. Kesinlikle yanılmıyorsun - Soru olması gerektiği gibi başlıklandırılmamış.
Süper Kedi

0

Yığın üzerindeki diziler ile Öbek üzerindeki diziler arasındaki farkı göstermek için bazı örnek kodlar


/**
 * 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
}
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.