แบบอิงตามช่วงสำหรับการทำงานสำหรับอาร์เรย์ธรรมดาอย่างไร


88

ใน C ++ 11 คุณสามารถใช้การอ้างอิงตามช่วงforซึ่งทำหน้าที่เป็นforeachภาษาอื่น ๆ ใช้งานได้แม้กับอาร์เรย์ C ธรรมดา:

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

จะรู้ได้อย่างไรว่าเมื่อไรควรหยุด? ใช้ได้เฉพาะกับอาร์เรย์แบบคงที่ที่ได้รับการประกาศในขอบเขตเดียวกันกับforใช้หรือไม่? คุณจะใช้สิ่งนี้forกับอาร์เรย์แบบไดนามิกอย่างไร


10
ไม่มีอาร์เรย์ "ไดนามิก" ใน C หรือ C ++ ต่อ se - มีประเภทอาร์เรย์แล้วมีพอยน์เตอร์ที่อาจชี้ไปที่อาร์เรย์หรือบล็อกหน่วยความจำที่จัดสรรแบบไดนามิกซึ่งส่วนใหญ่ทำงานเหมือนอาร์เรย์ สำหรับอาร์เรย์ของประเภท 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;

สำหรับคำอธิบายโดยละเอียดยิ่งขึ้นหากประเภทของนิพจน์ที่ส่งไปทางขวาของ:เป็นประเภทอาร์เรย์ลูปจะวนซ้ำจากptrถึงptr + size( ptrชี้ไปที่องค์ประกอบแรกของอาร์เรย์ซึ่งsizeเป็นจำนวนองค์ประกอบของอาร์เรย์)

สิ่งนี้ตรงกันข้ามกับชนิดที่ผู้ใช้กำหนดซึ่งทำงานโดยการค้นหาbeginและendในฐานะสมาชิกถ้าคุณส่งผ่านคลาสอ็อบเจ็กต์หรือ (ถ้าไม่มีสมาชิกเรียกแบบนั้น) ฟังก์ชันที่ไม่ใช่สมาชิก ฟังก์ชันเหล่านั้นจะให้ตัวเริ่มต้นและสิ้นสุดตัววนซ้ำ (ชี้ไปที่หลังองค์ประกอบสุดท้ายและจุดเริ่มต้นของลำดับตามลำดับ)

คำถามนี้ทำให้กระจ่างขึ้นว่าเหตุใดจึงมีความแตกต่าง


8
ผมคิดว่าคำถามคือวิธีการที่ไม่ได้ทำงานไม่เมื่อไม่ได้ทำงาน
sehe

1
@ คำถามนี้มี "?" หลายข้อ หนึ่งคือ "มันทำงานร่วมกับ ... ?" ฉันอธิบายทั้งสองอย่างว่ามันทำงานอย่างไรและเมื่อไหร่
Johannes Schaub - litb

8
@JohannesSchaub: ฉันคิดว่าปัญหา "อย่างไร" ที่นี่คือวิธีที่คุณได้ขนาดของวัตถุประเภทอาร์เรย์ในตอนแรก (เนื่องจากตัวชี้เทียบกับความสับสนของอาร์เรย์เกือบทุกคนไม่ทราบว่าขนาดของอาร์เรย์คือมีให้สำหรับโปรแกรมเมอร์)
JohannesD

ผมเชื่อว่ามันเพียงมองหาที่ไม่ใช่สมาชิกbegin`ปลาย. It just happens that มาตรฐาน :: เริ่มต้นการ`std::endใช้ฟังก์ชั่นสมาชิกและจะใช้ในกรณีที่การแข่งขันที่ดีกว่าคือไม่สามารถใช้ได้
Dennis Zickefoose

3
@ เดนนิสไม่มีในมาดริดได้ตัดสินใจที่จะเปลี่ยนแปลงและเพื่อสนับสนุนสมาชิกเริ่มต้นและสิ้นสุด การไม่เข้าข้างสมาชิกเริ่มต้นและสิ้นสุดทำให้เกิดความคลุมเครือที่ยากจะหลีกเลี่ยง
Johannes Schaub - litb

45

ฉันคิดว่าส่วนที่สำคัญที่สุดของคำถามนี้คือ 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;
};

จากนั้นเทมเพลตคลาสนี้สามารถใช้เพื่อสร้างช่วงซึ่งคุณสามารถทำซ้ำได้โดยใช้ช่วงสำหรับไวยากรณ์ใหม่ ฉันใช้สิ่งนี้เพื่อเรียกใช้วัตถุแอนิเมชั่นทั้งหมดในฉากที่นำเข้าโดยใช้ไลบรารีที่ส่งคืนตัวชี้ไปยังอาร์เรย์และขนาดเป็นค่าแยกกันเท่านั้น

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

ในความคิดของฉันไวยากรณ์นี้ชัดเจนกว่าสิ่งที่คุณจะใช้std::for_eachหรือการforวนซ้ำธรรมดา


3

มันรู้ว่าเมื่อใดควรหยุดเพราะมันรู้ขอบเขตของอาร์เรย์คงที่

ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดย "อาร์เรย์แบบไดนามิก" ไม่ว่าในกรณีใดก็ตามหากไม่ได้ทำซ้ำบนอาร์เรย์แบบคงที่คอมไพเลอร์จะค้นหาชื่อbeginและendในขอบเขตของคลาสของออบเจ็กต์ที่คุณทำซ้ำหรือดู ขึ้นสำหรับbegin(range)และend(range)ใช้การค้นหาขึ้นอยู่กับอาร์กิวเมนต์และใช้เป็นตัวทำซ้ำ

สำหรับข้อมูลเพิ่มเติมในมาตรฐาน C ++ 11 (หรือแบบร่างสาธารณะ) "6.5.4 The range-based forstatement" หน้า 145


4
A "อาร์เรย์แบบไดนามิก" new[]จะเป็นหนึ่งที่สร้างขึ้นด้วย ในกรณีนี้คุณมีเพียงตัวชี้ที่ไม่มีการระบุขนาดดังนั้นจึงไม่มีวิธีใดที่จะใช้งานตามช่วงforได้
Mike Seymour

คำตอบของฉันมีอาร์เรย์แบบไดนามิกซึ่งทราบขนาด (4) ในเวลาคอมไพล์ แต่ฉันไม่รู้ว่าการตีความ "อาร์เรย์แบบไดนามิก" นั้นเป็นสิ่งที่ผู้ถามตั้งใจไว้หรือไม่
Johannes Schaub - litb

3

แบบอิงตามช่วงสำหรับการทำงานสำหรับอาร์เรย์ธรรมดาอย่างไร

ที่จะอ่านว่า " บอกฉันทีว่า ranged-for ทำ (พร้อมอาร์เรย์) คืออะไร? "

ฉันจะตอบโดยสมมติว่า - ใช้ตัวอย่างต่อไปนี้โดยใช้อาร์เรย์ที่ซ้อนกัน:

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

for (auto &pl : ia)

เวอร์ชันข้อความ:

iaคืออาร์เรย์ของอาร์เรย์ ("อาร์เรย์ที่ซ้อนกัน") ซึ่งมี[3]อาร์เรย์โดยแต่ละอาร์เรย์มี[4]ค่า ตัวอย่างข้างต้นวนiaซ้ำโดยเป็น 'range' ( [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](เราได้รับที่อยู่ของรายการนั้นในอาร์เรย์)

นี่คือสิ่งที่เกิดขึ้น:

  • ในแต่ละวงplจะถูกตั้งค่าเป็นอ้างอิงจะia[n]มีnเท่ากับจำนวนวงในปัจจุบันที่เริ่มต้นจาก 0. ดังนั้นplคือia[0]ในรอบแรกที่สองมันเป็นia[1]และอื่น ๆ มันดึงค่าผ่านการวนซ้ำ
  • ห่วงไปในตราบใดที่มีค่าน้อยกว่าia+nend(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 9 จาก 10 ครั้งชื่อคือสิ่งที่ตรงกับใน Google / การค้นหาใด ๆ - ชื่อถามว่าสิ่งเหล่านี้ทำงานอย่างไร ไม่เมื่อมันไม่ทราบเมื่อมีการหยุด? . ถึงกระนั้นก็ตามคำถามที่แฝงอยู่โดยนัยก็ครอบคลุมอยู่ในคำตอบนี้ในระดับหนึ่งและยังคงเป็นคำตอบสำหรับใครก็ตามที่กำลังมองหาคำตอบอื่น ๆ คำถามเกี่ยวกับไวยากรณ์เช่นคำถามเหล่านี้ควรมีชื่อวลีเพื่อให้สามารถเขียนคำตอบโดยใช้คำตอบนั้นเพียงอย่างเดียวเนื่องจากเป็นข้อมูลทั้งหมดที่ผู้ค้นหาต้องการเพื่อค้นหาคำถาม คุณไม่ผิดอย่างแน่นอน - คำถามไม่มีชื่ออย่างที่ควรจะเป็น
Super Cat

0

โค้ดตัวอย่างบางส่วนเพื่อแสดงความแตกต่างระหว่างอาร์เรย์บน Stack เทียบกับอาร์เรย์บน 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
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.