Bagaimana cara kerja berbasis rentang untuk array biasa?

88

Di C ++ 11 Anda dapat menggunakan berbasis jangkauan for, yang bertindak sebagai foreachbahasa lain. Ia bekerja bahkan dengan array C biasa:

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

Bagaimana cara mengetahui kapan harus berhenti? Apakah ini hanya bekerja dengan array statis yang telah dideklarasikan dalam lingkup yang sama dengan foryang digunakan? Bagaimana Anda akan menggunakan ini fordengan array dinamis?

Paul Manta
sumber
10
Tidak ada larik "dinamis" dalam C atau C ++ per se - ada jenis larik dan kemudian ada penunjuk yang mungkin atau mungkin tidak menunjuk ke larik atau blok memori yang dialokasikan secara dinamis yang sebagian besar berperilaku seperti larik. Untuk semua larik tipe T [n], ukurannya dikodekan dalam tipe dan dapat diakses oleh for. Tapi saat array itu meluruh menjadi pointer, informasi ukuran hilang.
JohannesD
1
Dalam contoh Anda, jumlah elemen di numbersis sizeof(numbers)/sizeof(int), misalnya.
JohannesD

Jawaban:

57

Ini berfungsi untuk ekspresi apa pun yang tipenya adalah array. Sebagai contoh:

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

Untuk penjelasan yang lebih mendetail, jika jenis ekspresi yang diteruskan di sebelah kanan :adalah jenis array, maka loop akan beriterasi dari ptrke ptr + size( ptrmenunjuk ke elemen pertama dari array, sizemenjadi jumlah elemen array).

Ini berbeda dengan tipe yang ditentukan pengguna, yang bekerja dengan mencari begindan endsebagai anggota jika Anda meneruskan objek kelas atau (jika tidak ada anggota yang dipanggil dengan cara itu) fungsi non-anggota. Fungsi-fungsi tersebut akan menghasilkan iterator awal dan akhir (masing-masing menunjuk langsung setelah elemen terakhir dan awal urutan).

Pertanyaan ini menjelaskan mengapa perbedaan itu ada.

Johannes Schaub - litb
sumber
8
Saya pikir pertanyaannya adalah bagaimana cara kerjanya, bukan kapan itu berhasil
lihat
1
@sehe pertanyaan berisi beberapa '?' es. Salah satunya adalah "Apakah itu bekerja dengan ...?". Saya menjelaskan bagaimana dan kapan itu bekerja.
Johannes Schaub - litb
8
@JohannesSchaub: Saya pikir masalah "bagaimana" di sini adalah bagaimana tepatnya Anda mendapatkan ukuran objek dari tipe array di tempat pertama (karena kebingungan pointer vs array, tidak hampir semua orang tahu bahwa ukuran array adalah tersedia untuk programmer.)
JohannesD
Saya percaya ini hanya mencari non-anggota begin`end . It just happens that std :: begin `std::endgunakan fungsi anggota, dan akan digunakan jika kecocokan yang lebih baik tidak tersedia.
Dennis Zickefoose
3
@Dennis no di Madrid diputuskan untuk mengubahnya dan mendukung anggota awal dan akhir. Tidak menyukai anggota awal dan akhir menyebabkan ambiguitas yang sulit dihindari.
Johannes Schaub - litb
45

Saya pikir bagian terpenting dari pertanyaan ini adalah, bagaimana C ++ tahu apa ukuran array (setidaknya saya ingin mengetahuinya ketika saya menemukan pertanyaan ini).

C ++ mengetahui ukuran sebuah larik, karena itu adalah bagian dari definisi larik - ini adalah tipe dari variabelnya. Seorang compiler harus mengetahui tipenya.

Karena C ++ 11 std::extentdapat digunakan untuk mendapatkan ukuran array:

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

Tentu saja, ini tidak masuk akal, karena Anda harus secara eksplisit memberikan ukuran di baris pertama, yang kemudian Anda dapatkan di baris kedua. Tapi Anda juga bisa menggunakan decltypedan kemudian menjadi lebih menarik:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;
psur
sumber
6
Ini memang yang awalnya saya tanyakan. :)
Paul Manta
19

Menurut Draf Kerja C ++ terbaru (n3376), pernyataan ranged for setara dengan berikut ini:

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

Jadi ia tahu bagaimana menghentikan dengan cara yang sama seperti forloop biasa yang menggunakan iterator.

Saya pikir Anda mungkin mencari sesuatu seperti berikut ini untuk menyediakan cara menggunakan sintaks di atas dengan array yang hanya terdiri dari pointer dan ukuran (array dinamis):

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

Template kelas ini kemudian dapat digunakan untuk membuat rentang, di mana Anda dapat mengulang menggunakan sintaks ranged untuk yang baru . Saya menggunakan ini untuk menjalankan semua objek animasi dalam sebuah adegan yang diimpor menggunakan pustaka yang hanya mengembalikan pointer ke array dan ukuran sebagai nilai terpisah.

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

Sintaks ini, menurut pendapat saya, jauh lebih jelas daripada apa yang akan Anda gunakan std::for_eachatau forloop biasa .

Hibah
sumber
3

Ia tahu kapan harus berhenti karena ia tahu batas-batas array statis.

Saya tidak yakin apa yang Anda maksud dengan "array dinamis", dalam hal apa pun, jika tidak melakukan iterasi pada array statis, secara informal, kompilator mencari nama begindan enddalam lingkup kelas objek yang Anda iterasi, atau lihat up untuk begin(range)dan end(range)menggunakan pencarian yang bergantung pada argumen dan menggunakannya sebagai iterator.

Untuk informasi lebih lanjut, dalam standar C ++ 11 (atau draf publiknya), "6.5.4 forPernyataan berbasis rentang ", hal.145

dingin
sumber
4
Sebuah "array dinamis" akan menjadi salah satu yang dibuat dengan new[]. Dalam hal ini, Anda hanya memiliki pointer tanpa indikasi ukurannya, jadi tidak ada cara untuk range-based foruntuk bekerja dengannya.
Mike Seymour
Jawaban saya mencakup larik dinamis yang ukurannya (4) diketahui pada waktu kompilasi, tetapi saya tidak tahu apakah penafsiran "larik dinamis" itu yang dimaksudkan oleh penanya.
Johannes Schaub - litb
3

Bagaimana cara kerja berbasis rentang untuk array biasa?

Apakah itu dibaca sebagai, " Katakan padaku apa yang dilakukan ranged-untuk (dengan array)? "

Saya akan menjawab dengan asumsi bahwa - Ambil contoh berikut menggunakan array bersarang:

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

for (auto &pl : ia)

Versi teks:

iaadalah larik larik ("larik bersarang"), berisi [3]larik, dengan masing-masing berisi [4]nilai. Contoh di atas mengulang melalui ia'range' ( [3]) primernya , dan oleh karena itu mengulang [3]kali. Setiap loop menghasilkan salah satu ia's [3]nilai utama mulai dari yang pertama dan berakhir dengan yang terakhir - Sebuah array yang mengandung [4]nilai-nilai.

  • Loop pertama: plsama dengan {1,2,3,4}- Array
  • Loop kedua: plsama dengan {5,6,7,8}- Array
  • Loop ketiga: plsama dengan {9,10,11,12}- Sebuah array

Sebelum kami menjelaskan prosesnya, berikut adalah beberapa pengingat ramah tentang array:

  • Array ditafsirkan sebagai penunjuk ke nilai pertamanya - Menggunakan larik tanpa iterasi apa pun mengembalikan alamat dari nilai pertama
  • pl harus menjadi referensi karena kita tidak dapat menyalin array
  • Dengan larik, saat Anda menambahkan angka ke objek larik itu sendiri, ia maju berkali-kali dan 'menunjuk' ke entri yang setara - Jika nbilangan yang dimaksud, maka ia[n]sama dengan *(ia+n)(Kami mendereferensi alamat yang merupakan nentri maju), dan ia+nsama dengan &ia[n](Kami mendapatkan alamat dari entri itu dalam larik).

Inilah yang terjadi:

  • Pada setiap loop, pldiatur sebagai referensi untuk ia[n], dengan nmenyamai jumlah loop arus mulai dari 0. Jadi, pladalah ia[0]pada putaran pertama, pada kedua itu ia[1], dan sebagainya. Ini mengambil nilai melalui iterasi.
  • Pengulangan berlangsung selama ia+nkurang dari end(ia).

... Dan itu saja.

Ini benar-benar hanya cara yang disederhanakan untuk menulis ini :

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

Jika array Anda tidak bersarang, maka proses ini menjadi sedikit lebih sederhana karena referensi tidak diperlukan, karena nilai yang diiterasi bukanlah array, melainkan nilai 'normal':

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

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

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

Beberapa informasi tambahan

Bagaimana jika kami tidak ingin menggunakan autokata kunci saat membuat pl? Seperti apa kelihatannya?

Dalam contoh berikut, plmengacu pada file array of four integers. Pada setiap loop pldiberi nilai ia[n]:

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

Dan ... Begitulah cara kerjanya, dengan informasi tambahan untuk menghilangkan kebingungan. Ini hanya forloop 'singkat' yang secara otomatis dihitung untuk Anda, tetapi tidak memiliki cara untuk mengambil loop saat ini tanpa melakukannya secara manual.

Kucing Super
sumber
@Andy 9 dari 10 kali judulnya adalah apa yang cocok di Google / pencarian apa pun - Judul menanyakan bagaimana cara kerjanya? , bukan kapan dia tahu kapan harus berhenti? . Meski begitu, pertanyaan mendasar tersirat adalah tercakup dalam jawaban ini sampai batas tertentu, dan melanjutkan dengan jawaban untuk orang lain mencari lain jawaban. Pertanyaan sintaksis seperti ini harus memiliki judul yang diutarakan sehingga jawaban dapat ditulis menggunakan itu saja karena itulah semua informasi yang dibutuhkan pencari untuk menemukan pertanyaan tersebut. Anda pasti tidak salah - Pertanyaannya tidak berjudul sebagaimana mestinya.
Super Cat
0

Beberapa kode contoh untuk menunjukkan perbedaan antara array di Stack vs array di Heap


/**
 * 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
}
Yip Cubed
sumber