ทำไมต้องใช้ตัววนซ้ำแทนดัชนีอาร์เรย์


239

รับโค้ดสองบรรทัดต่อไปนี้:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

และนี่:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

ฉันบอกว่าวิธีที่สองเป็นที่ต้องการ ทำไมถึงเป็นอย่างนี้


72
วิธีที่สองเป็นที่ต้องการคือคุณเปลี่ยนไปsome_iterator++ ++some_iteratorการเพิ่มภายหลังสร้างตัววนซ้ำชั่วคราวที่ไม่จำเป็น
jason

6
คุณควรนำend()ข้อประกาศไปด้วย
การแข่งขัน Lightness ใน Orbit

5
@ Tomalak: ใครก็ตามที่ใช้งาน C ++ ที่ไม่มีประสิทธิภาพvector::endอาจมีปัญหาที่แย่กว่าที่คุณกังวลมากกว่าว่ามันจะออกจากลูปหรือไม่ โดยส่วนตัวแล้วฉันชอบความชัดเจน - ถ้าเป็นการโทรถึงfindในเงื่อนไขการเลิกจ้างที่ฉันกังวล
Steve Jessop

13
@ Tomalak: รหัสนั้นไม่เลอะเทอะ (อาจจะเป็นการเพิ่มภายหลัง) มันกระชับและชัดเจนเท่าที่ตัววนซ้ำ C ++ อนุญาตให้มีความรัดกุม การเพิ่มตัวแปรเพิ่มเติมเพิ่มความพยายามทางปัญญาเพื่อประโยชน์ของการปรับให้เหมาะสมก่อนวัยอันควร นั่นเลอะเทอะ
Steve Jessop

7
@ Tomalak: มันเร็วเกินไปถ้าไม่ใช่คอขวด จุดที่สองของคุณดูเหมือนจะไร้สาระกับผมตั้งแต่การเปรียบเทียบที่ถูกต้องคือไม่ได้อยู่ระหว่างit != vec.end()และit != endก็ระหว่างและ(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)ฉันไม่จำเป็นต้องนับตัวละคร โดยรวมแล้วชอบมากกว่าสิ่งอื่น แต่ความขัดแย้งของคนอื่นกับความชอบของคุณไม่ใช่ "ความเลอะเทอะ" มันเป็นความชอบสำหรับโค้ดที่ง่ายกว่าโดยมีตัวแปรน้อยลงและคิดน้อยลงเมื่ออ่านมัน
Steve Jessop

คำตอบ:


210

แบบฟอร์มแรกจะมีประสิทธิภาพก็ต่อเมื่อ vector.size () เป็นการดำเนินการที่รวดเร็ว สิ่งนี้เป็นจริงสำหรับเวกเตอร์ แต่ไม่ใช่สำหรับรายการ นอกจากนี้คุณวางแผนจะทำอะไรในร่างกายของลูป? หากคุณวางแผนที่จะเข้าถึงองค์ประกอบดังเช่น

T elem = some_vector[i];

ถ้าอย่างนั้นคุณก็สมมุติว่าภาชนะนั้นได้operator[](std::size_t)กำหนดไว้ นี่เป็นความจริงสำหรับเวกเตอร์ แต่ไม่ใช่สำหรับคอนเทนเนอร์อื่น

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

คุณสามารถปรับปรุงโค้ดของคุณเพิ่มเติมโดยใช้อัลกอริธึมมาตรฐาน ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณกำลังพยายามที่จะบรรลุคุณอาจเลือกที่จะใช้งานstd::for_each(), std::transform()และอื่น ๆ ด้วยการใช้อัลกอริธึมมาตรฐานแทนที่จะวนลูปอย่างชัดเจนคุณจะหลีกเลี่ยงการสร้างวงล้อใหม่ รหัสของคุณน่าจะมีประสิทธิภาพมากกว่า (เลือกอัลกอริธึมที่ถูกต้อง) แก้ไขและนำมาใช้ซ้ำได้


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

4
สิ่งนี้ทำให้ฉันสับสน: "นี่เป็นจริงสำหรับเวกเตอร์ แต่ไม่ใช่สำหรับรายการ" ทำไม? ทุกคนที่มีสมองจะทำให้การติดตามตัวแปรสมาชิกเก็บรักษาsize_t size()
GManNickG

19
@GMan - ในการนำไปใช้งานเกือบทั้งหมดขนาด () นั้นเร็วสำหรับรายการมากเท่ากับเวกเตอร์ มาตรฐานรุ่นถัดไปจะต้องให้สิ่งนี้เป็นจริง ปัญหาที่แท้จริงคือความช้าของการตอบกลับตามตำแหน่ง
Daniel Earwicker

8
@GMan: การจัดเก็บขนาดรายการต้องใช้การแบ่งส่วนรายการและการต่อเป็น O (n) แทน O (1)

5
ใน C ++ 0x ที่ฟังก์ชันสมาชิกจะต้องมีความซับซ้อนเวลาคงที่สำหรับภาชนะทั้งหมดที่สนับสนุนรวมทั้งsize() std::list
James McNellis

54

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


ว้าวนี่ยังคงลดลงหลังจากผ่านไปสามสัปดาห์ ฉันเดาว่ามันจะไม่จ่ายเงินให้กับแก้มลิ้น

ฉันคิดว่าดัชนีอาร์เรย์อ่านได้ง่ายขึ้น มันตรงกับไวยากรณ์ที่ใช้ในภาษาอื่น ๆ และไวยากรณ์ที่ใช้สำหรับอาร์เรย์ C แบบเก่า มันยังน้อย verbose ประสิทธิภาพควรล้างหากคอมไพเลอร์ของคุณดีและมีกรณีใด ๆ ที่มันสำคัญอยู่แล้ว

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


1
ตัววนซ้ำของ C ++ ยังมีแนวคิดที่แตกสลายอย่างน่ากลัว สำหรับเวกเตอร์ฉันเพิ่งถูกจับได้เนื่องจากตัวชี้ปลายเป็นจุดสิ้นสุด + 1 (!) สำหรับสตรีมโมเดล iterator เป็นเพียงเซอร์เรียล - โทเค็นจินตภาพที่ไม่มีอยู่จริง เช่นเดียวกันสำหรับรายการที่เชื่อมโยง กระบวนทัศน์เพียงทำให้รู้สึกถึงอาร์เรย์และจากนั้นไม่มาก ทำไมฉันต้องสองวัตถุ iterator ไม่เพียง ...
Tuntable

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

4
ดูสตรีมตัววนซ้ำและคิดเกี่ยวกับสิ่งที่ == ถูกบิดเบือนในการทำเพื่อให้พอดีกับแพทเทิร์นแล้วบอกตัววนซ้ำไม่ให้แตก! หรือสำหรับรายการที่เชื่อมโยง แม้แต่อาร์เรย์ต้องมีการระบุหนึ่งที่ผ่านมาจุดสิ้นสุดเป็นแนวคิดสไตล์ C ที่ขาด - ตัวชี้เข้าไปในไม่เคย ควรเป็นเช่น Java หรือ C # หรือตัววนซ้ำของภาษาอื่นโดยต้องมีตัววนซ้ำหนึ่งตัว (แทนที่จะเป็นสองวัตถุ) และการทดสอบขั้นสุดท้าย
Tuntable

53

เพราะคุณไม่ได้ผูกรหัสของคุณกับการใช้งานบางอย่างของรายการ some_vector ถ้าคุณใช้ดัชนีอาเรย์มันจะต้องเป็นรูปแบบของอาเรย์ หากคุณใช้ตัววนซ้ำคุณสามารถใช้รหัสนั้นในการใช้งานรายการใด ๆ


23
ความตั้งใจอินเตอร์เฟส std :: list ไม่ได้เสนอโอเปอเรเตอร์ [] (size_t n) เพราะมันจะเป็น O (n)
MSalters

33

ลองนึกภาพ some_vector ถูกนำไปใช้กับรายการที่ลิงก์ จากนั้นการร้องขอไอเท็มใน i-th ต้องการการดำเนินการ i ที่ต้องทำเพื่อสำรวจรายการโหนด ตอนนี้ถ้าคุณใช้ตัววนซ้ำพูดโดยทั่วไปมันจะพยายามอย่างเต็มที่เพื่อให้มีประสิทธิภาพมากที่สุดเท่าที่จะทำได้ (ในกรณีของรายการที่เชื่อมโยงมันจะรักษาตัวชี้ไปยังโหนดปัจจุบันและเลื่อนไปข้างหน้าในแต่ละรอบซ้ำ การดำเนินการเดียว)

ดังนั้นจึงมีสองสิ่ง:

  • การใช้งานที่เป็นนามธรรม: คุณเพียงแค่ต้องการย้ำองค์ประกอบบางอย่างคุณไม่สนใจว่าจะทำอย่างไร
  • ประสิทธิภาพ

1
"มันจะรักษาตัวชี้ไปยังโหนดปัจจุบันและเลื่อนไปข้างหน้า [สิ่งที่ดีเกี่ยวกับประสิทธิภาพ]" - ใช่ฉันไม่เข้าใจว่าทำไมคนถึงมีปัญหาในการทำความเข้าใจแนวคิดของตัววนซ้ำ พวกมันเป็นแค่แนวคิดพอยน์เตอร์ เหตุใดจึงคำนวณออฟเซ็ตขององค์ประกอบบางส่วนซ้ำแล้วซ้ำอีกเมื่อคุณสามารถแคชตัวชี้ไปยังมันได้? นั่นคือสิ่งที่ผู้วนซ้ำทำเช่นกัน
underscore_d

27

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

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

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

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


23

คุณอาจต้องการใช้ตัววนซ้ำถ้าคุณจะเพิ่ม / ลบรายการไปยังเวกเตอร์ในขณะที่คุณวนซ้ำ

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

หากคุณใช้ดัชนีคุณจะต้องสลับรายการขึ้น / ลงในอาร์เรย์เพื่อจัดการการแทรกและการลบ


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

iterating กว่าทุกองค์ประกอบสวยราคาแพงในstd::listเมื่อเทียบกับแต่ถ้าคุณกำลังแนะนำให้ใช้การเชื่อมโยงรายชื่อแทนstd::vector std::vectorดูหน้า 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf จากประสบการณ์ของฉันฉันพบว่าstd::vectorเร็วกว่าstd::listแม้ว่าฉันจะค้นหาทั้งหมดและลบองค์ประกอบออกจากตำแหน่งโดยพลการ
David Stone

ดัชนีมีความเสถียรดังนั้นฉันจึงไม่เห็นว่าการสับแบบเพิ่มเติมจำเป็นสำหรับการแทรกและการลบ
musiphil

... และด้วยรายการที่เชื่อมโยง - ซึ่งเป็นสิ่งที่ควรใช้งานที่นี่ - คำสั่งวนรอบของคุณจะfor (node = list->head; node != NULL; node = node->next)สั้นกว่าโค้ดสองบรรทัดแรกของคุณ (การประกาศและวนหัว) ดังนั้นฉันจึงพูดอีกครั้ง - ไม่มีความแตกต่างพื้นฐานมากในช่วงสั้น ๆ ระหว่างการใช้ตัววนซ้ำและไม่ใช้พวกมัน - คุณยังคงตอบสนองสามส่วนของforคำสั่งแม้ว่าคุณจะใช้while: ประกาศย้ำย้ำตรวจสอบการเลิกจ้าง
วิศวกร

16

การแยกความกังวล

มันดีมากที่จะแยกรหัสการวนซ้ำออกจากข้อกังวล 'หลัก' ของลูป มันเกือบจะเป็นการตัดสินใจออกแบบ

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

นอกจากนี้ในstd::for_eachทางที่คุณบอกคอลเลกชันจะทำอย่างไรแทนที่จะถามบางสิ่งเกี่ยวกับภายในของมัน

มาตรฐาน 0x จะนำเสนอการปิดซึ่งจะทำให้วิธีการนี้ง่ายต่อการใช้งานมากขึ้น - ดูที่พลังการแสดงออกเช่น Ruby's [1..6].each { |i| print i; }...

ประสิทธิภาพ

แต่อาจมีปัญหาเรื่องการดูแลมากคือการใช้for_eachวิธีการนี้ทำให้มีโอกาสที่จะมีการวนซ้ำแบบขนาน - บล็อกเธรดของ Intelสามารถแจกจ่ายบล็อกโค้ดในจำนวนโปรเซสเซอร์ในระบบ!

หมายเหตุ: หลังจากค้นพบalgorithmsห้องสมุดและโดยเฉพาะอย่างยิ่งforeachฉันใช้เวลาสองหรือสามเดือนในการเขียนโอเปอเรเตอร์ตัวช่วยเล็ก ๆ 'helper' ซึ่งจะทำให้นักพัฒนาเพื่อนของคุณบ้าคลั่ง หลังจากเวลานี้ฉันกลับไปที่วิธีปฏิบัติ - ร่างเล็กวนไม่สมควรforeachอีกต่อไป :)

ต้องอ่านอ้างอิง iterators เป็นหนังสือ"STL ขยาย"

GoF มีย่อหน้าเล็ก ๆ เล็กน้อยในตอนท้ายของรูปแบบ Iterator ซึ่งพูดถึงการทำซ้ำของแบรนด์นี้ มันเรียกว่า 'ตัววนซ้ำภายใน' ลองดูที่นี่ด้วย


15

เพราะมันเป็นเชิงวัตถุมากขึ้น หากคุณกำลังทำซ้ำกับดัชนีคุณจะถือว่า:

a) วัตถุเหล่านั้นถูกจัดเรียง
b) วัตถุเหล่านั้นสามารถรับได้โดยดัชนี
c) ที่การเพิ่มขึ้นของดัชนีจะกระทบทุกรายการ
d) ดัชนีนั้นเริ่มต้นที่ศูนย์

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

นอกจากนี้ยังมีตัววนซ้ำไม่จำเป็นต้องกังวลเกี่ยวกับการออกนอกขอบเขตของอาร์เรย์


2
ฉันไม่คิดว่า "object oriented" เป็นคำที่ถูกต้อง Iterators ไม่ใช่ "เชิงวัตถุ" ในการออกแบบ พวกเขาส่งเสริมการเขียนโปรแกรมเชิงฟังก์ชันมากกว่าการเขียนโปรแกรมเชิงวัตถุเนื่องจากพวกเขาสนับสนุนการแยกอัลกอริทึมจากคลาส
wilhelmtell

นอกจากนี้ตัววนซ้ำไม่ได้ช่วยหลีกเลี่ยงการออกนอกขอบเขต อัลกอริธึมมาตรฐานทำ แต่ตัววนซ้ำไม่ทำอย่างเดียว
wilhelmtell

ยุติธรรมพอ @ วิลเฮล์มเทลฉันเห็นได้ชัดว่าฉันเห็นสิ่งนี้จากมุมมองที่เป็นศูนย์กลางของจาวา
cynicalman

1
และฉันคิดว่ามันเป็นการโปรโมต OO เพราะมันเป็นการแยกปฏิบัติการบนคอลเลกชันออกจากการใช้คอลเลกชันนั้น คอลเลกชันของวัตถุไม่ควรรู้ว่าอัลกอริทึมที่ควรใช้ในการทำงานกับพวกเขา
cynicalman

จริงๆแล้วมีรุ่นของ STL ออกที่นั่นที่ได้ตรวจสอบตัววนซ้ำหมายความว่ามันจะโยนข้อยกเว้นนอกขอบเขตเมื่อคุณพยายามทำบางอย่างกับตัววนซ้ำนั้น
Daemin

15

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


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

นี้มีลักษณะที่เหมาะสม const_iteratorแต่ฉันยังคงมีข้อสงสัยว่าถ้าเป็นเหตุผลสำหรับการมี ถ้าฉันเปลี่ยนเวคเตอร์ในลูปฉันจะทำด้วยเหตุผลและ 99.9% ของเวลาที่เปลี่ยนไม่ใช่อุบัติเหตุและที่เหลือก็เป็นข้อผิดพลาดเหมือนกับข้อบกพร่องทุกชนิดในรหัสที่ผู้เขียนเขียน ต้องแก้ไข เนื่องจากใน Java และภาษาอื่น ๆ อีกมากมายไม่มีวัตถุ const เลย แต่ผู้ใช้ภาษาเหล่านั้นไม่เคยมีปัญหาโดยไม่มีการสนับสนุน const ในภาษาเหล่านั้น
neevek

2
@neevek หากนั่นไม่ใช่เหตุผลของการมีเหตุผลconst_iteratorบนโลกนี้จะเป็นเช่นไร?
underscore_d

@underscore_d ฉันก็สงสัยเหมือนกัน ฉันไม่ใช่ผู้เชี่ยวชาญในเรื่องนี้ แต่เป็นเพียงคำตอบที่ไม่น่าเชื่อถือสำหรับฉัน
neevek

15

นอกเหนือจากคำตอบที่ยอดเยี่ยมอื่น ๆ ทั้งหมด ... intอาจไม่ใหญ่พอสำหรับเวคเตอร์ของคุณ หากคุณต้องการใช้การจัดทำดัชนีให้ใช้size_typeสำหรับคอนเทนเนอร์ของคุณแทน:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@ Pat Notz นั่นเป็นจุดที่ดีมาก ในระหว่างการย้ายแอปพลิเคชัน Windows ที่ใช้ STL ไปยัง x64 ฉันต้องจัดการกับคำเตือนหลายร้อยคำเกี่ยวกับการกำหนด size_t ให้กับ int ซึ่งอาจทำให้เกิดการตัดทอน
bk1e

1
ไม่ต้องพูดถึงความจริงที่ว่าประเภทขนาดจะไม่ได้ลงนามและ int มีการลงนามเพื่อให้คุณมีที่ไม่ได้ใช้งานง่ายแปลงข้อผิดพลาดที่หลบซ่อนตัวที่เกิดขึ้นเพียงเพื่อเปรียบเทียบการint i myvector.size()
Adrian McCarthy

12

ฉันควรจะชี้ให้เห็นว่าคุณสามารถโทรหา

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

ตัววนซ้ำ STL ส่วนใหญ่อยู่ที่นั่นเพื่อให้อัลกอริทึม STL เช่นการเรียงลำดับสามารถเป็นคอนเทนเนอร์แบบอิสระ

หากคุณต้องการวนซ้ำรายการทั้งหมดในเวกเตอร์เพียงใช้สไตล์ลูปดัชนี

มันพิมพ์น้อยลงและง่ายต่อการแยกสำหรับมนุษย์ส่วนใหญ่ มันจะดีถ้า C ++ มีลูป foreach แบบง่ายโดยไม่ต้องลงน้ำด้วยเวทย์มนตร์เทมเพลต

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

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

ฉันยังต้องการอ้างอิงถึงรายการในวงเช่นนี้จึงมีวงเล็บเหลี่ยมไม่มากรอบสถานที่:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

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


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

สิ่งนี้จะสนับสนุนการใช้ซ้ำและหลีกเลี่ยงข้อผิดพลาดแบบทีละรายการในภายหลัง ฉันจะเรียกอัลกอริทึมนั้นเหมือนกับอัลกอริธึมมาตรฐานอื่น ๆ พร้อมตัววนซ้ำ
wilhelmtell

1
ไม่จำเป็นต้องมีแม้แต่ล่วงหน้า () ตัววนซ้ำมีตัวดำเนินการ + = และ - = เหมือนกับดัชนี (สำหรับคอนเทนเนอร์แบบเวกเตอร์และเวกเตอร์)
MSalters

I prefer to use an index myself as I consider it to be more readableในบางสถานการณ์เท่านั้น ดัชนีอื่น ๆ จะยุ่งมากอย่างรวดเร็ว and you can do random accessซึ่งไม่ใช่คุณลักษณะเฉพาะของดัชนีเลย: ดูen.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

แบบฟอร์มที่สองแสดงถึงสิ่งที่คุณกำลังทำอย่างแม่นยำมากขึ้น ในตัวอย่างของคุณคุณไม่สนใจคุณค่าของ i จริงๆ - ทั้งหมดที่คุณต้องการคือองค์ประกอบถัดไปในตัววนซ้ำ


3

หลังจากเรียนรู้เพิ่มเติมเล็กน้อยเกี่ยวกับเรื่องของคำตอบนี้ฉันรู้ว่ามันเป็นเรื่องเล็กน้อย ความแตกต่างระหว่างวงนี้:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

และวงนี้:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

ค่อนข้างน้อย อันที่จริงไวยากรณ์ของการทำลูปด้วยวิธีนี้ดูเหมือนจะเติบโตกับฉัน:

while (it != end){
    //do stuff
    ++it;
}

Iterators ทำการปลดล็อกคุณสมบัติการประกาศที่ทรงพลังพอสมควรและเมื่อรวมเข้ากับไลบรารีอัลกอริธึม STL คุณสามารถทำสิ่งที่ยอดเยี่ยมที่อยู่นอกขอบเขตของการจัดทำดัชนีอาร์เรย์


ความจริงก็คือว่าหากตัววนซ้ำทั้งหมดมีขนาดกะทัดรัดเท่ากับตัวอย่างสุดท้ายของคุณทันทีที่ออกนอกกรอบฉันจะมีปัญหาเล็กน้อยกับพวกเขา แน่นอนว่าเท่ากับfor (Iter it = {0}; it != end; ++it) {...}- คุณเพิ่งประกาศออกไปดังนั้นความกะทัดรัดไม่แตกต่างจากตัวอย่างที่สองของคุณมากนัก ถึงกระนั้น +1
วิศวกร

3

การจัดทำดัชนีต้องการการmulดำเนินการพิเศษ ตัวอย่างเช่นสำหรับvector<int> vการแปลงคอมไพเลอร์เข้าไปv[i]&v + sizeof(int) * i


อาจไม่ใช่ข้อเสียอย่างมีนัยสำคัญเมื่อเทียบกับตัววนซ้ำในกรณีส่วนใหญ่ แต่เป็นสิ่งที่ดีที่ควรระวัง
nobar

3
สำหรับการเข้าถึงองค์ประกอบแยกเดี่ยวอาจเป็นไปได้ แต่ถ้าเรากำลังพูดถึงลูป - เช่นเดียวกับ OP คือ - ฉันค่อนข้างแน่ใจว่าคำตอบนี้อยู่บนพื้นฐานของคอมไพเลอร์ไม่เพิ่มประสิทธิภาพจินตนาการ ผู้ที่มีคุณสมบัติเหมาะสมครึ่งหนึ่งจะมีโอกาสและโอกาสที่จะแคชsizeofและเพียงเพิ่มหนึ่งครั้งต่อการวนซ้ำมากกว่าที่จะทำการคำนวณออฟเซ็ตทั้งหมดอีกครั้งทุกครั้ง
underscore_d

2

ในระหว่างการทำซ้ำคุณไม่จำเป็นต้องทราบจำนวนรายการที่จะดำเนินการ คุณแค่ต้องการไอเท็มและผู้วนซ้ำทำสิ่งที่ดีมาก


2

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

สามารถทำได้ด้วยตัววนซ้ำ แต่คุณต้องโทรreserve()จึงจำเป็นต้องรู้ว่าคุณจะต่อท้ายรายการกี่รายการ


1

จุดดีหลายอย่างแล้ว ฉันมีความคิดเห็นเพิ่มเติมเล็กน้อย:

  1. สมมติว่าเรากำลังพูดถึงไลบรารีมาตรฐาน C ++ "vector" หมายถึงคอนเทนเนอร์การเข้าถึงแบบสุ่มที่มีการรับประกันของ C-array (การเข้าถึงแบบสุ่มรูปแบบหน่วยความจำ contiguos เป็นต้น) หากคุณพูดว่า 'some_container' คำตอบข้างต้นหลายคำจะแม่นยำกว่า (ความเป็นอิสระของคอนเทนเนอร์ ฯลฯ )

  2. หากต้องการกำจัดการพึ่งพาการเพิ่มประสิทธิภาพคอมไพเลอร์คุณสามารถย้าย some_vector.size () ออกจากลูปในโค้ดที่จัดทำดัชนีดังนี้:

    const size_t numElems = some_vector.size ();
    สำหรับ (size_t i = 0; i 
  3. วนซ้ำที่เพิ่มขึ้นล่วงหน้าเสมอและถือว่าการเพิ่มภายหลังเป็นกรณีพิเศษ

สำหรับ (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// ทำสิ่ง}

ดังนั้นการสมมติและทำดัชนีstd::vector<>เช่นคอนเทนเนอร์ไม่มีเหตุผลที่ดีที่จะชอบสิ่งใดสิ่งหนึ่งเหนือสิ่งอื่นใดโดยเรียงตามลำดับจะผ่านภาชนะ หากคุณต้องอ้างถึงดัชนี elemnent ที่เก่ากว่าหรือใหม่กว่าบ่อยครั้งเวอร์ชันที่จัดทำดัชนีจะมีความเหมาะสมมากกว่า

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


1

ฉันไม่ได้ใช้ตัววนซ้ำด้วยเหตุผลเดียวกันกับที่ฉันไม่ชอบคำสั่ง foreach เมื่อมีวงในหลายวงมันยากพอที่จะติดตามตัวแปรโกลบอล / สมาชิกโดยไม่ต้องจำค่าโลคัลและชื่อตัววนซ้ำทั้งหมดเช่นกัน สิ่งที่ฉันคิดว่ามีประโยชน์คือการใช้ดัชนีสองชุดสำหรับโอกาสต่าง ๆ :

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

ฉันไม่ต้องการที่จะย่อสิ่งต่าง ๆ เช่น "animation_matrices [i]" ไปยังบางตัวอย่าง "anim_matrix" - ชื่อ-iterator แบบสุ่มบางอย่างเพราะคุณจะไม่สามารถเห็นได้อย่างชัดเจนว่าอาร์เรย์นี้มีต้นกำเนิดค่านี้มาจากอะไร


ฉันไม่เห็นว่าดัชนีมีความดีขึ้นในแง่นี้อย่างไร คุณสามารถจะใช้ iterators และเพียงแค่รับการประชุมสำหรับชื่อของพวกเขาit, jt, ktฯลฯ หรือแม้เพียงแค่ดำเนินการโดยใช้i, j, kฯลฯ และถ้าคุณต้องการที่จะรู้ว่าสิ่งที่แสดงให้เห็นถึง iterator แล้วให้ฉันสิ่งที่ต้องการfor (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()จะสื่อความหมายมากขึ้น กว่าต้องทำดัชนีต่อเนื่องเหมือนanimation_matrices[animIndex][boneIndex]กัน
underscore_d

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

ฮ่าฮ่าจริง ๆ ฉันไม่ได้ดูว่าอายุเท่านี้มาก่อน! อย่างอื่นที่ฉันไม่คิดว่าครั้งล่าสุดคือว่าทุกวันนี้เรายังมีforวงวนตามช่วงซึ่งทำให้วิธีการทำซ้ำโดยใช้การรัดกุมยิ่งขึ้น
underscore_d

1
  • ถ้าคุณชอบอยู่ใกล้กับโลหะ / ไม่ไว้วางใจรายละเอียดการใช้งานอย่าใช้ตัววนซ้ำ
  • หากคุณเปลี่ยนประเภทคอลเลกชันเป็นประจำสำหรับประเภทอื่นในระหว่างการพัฒนาให้ใช้ตัววนซ้ำ
  • หากคุณพบว่ามันยากที่จะจำได้ว่าจะย้ำคอลเลกชันประเภทต่าง ๆ ได้อย่างไร (บางทีคุณอาจมีหลายประเภทจากแหล่งข้อมูลภายนอกที่แตกต่างกันในการใช้งาน) ให้ใช้ตัววนซ้ำเพื่อรวมวิธีการต่างๆ สิ่งนี้ใช้เพื่อบอกว่าสลับรายการที่เชื่อมโยงกับรายการอาร์เรย์

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


1

หากคุณมีการเข้าถึงC ++ 11คุณสมบัติแล้วคุณยังสามารถใช้ช่วงตามforห่วงสำหรับวนมากกว่าเวกเตอร์ของคุณ (หรือภาชนะอื่น ๆ ) ดังต่อไปนี้:

for (auto &item : some_vector)
{
     //do stuff
}

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

หมายเหตุ:

  • หากคุณต้องการดัชนีองค์ประกอบในลูปและoperator[]มีอยู่สำหรับคอนเทนเนอร์ของคุณ (และเร็วพอสำหรับคุณ) จากนั้นไปที่วิธีแรกได้ดีกว่า
  • forไม่สามารถใช้การวนซ้ำแบบอิงช่วงได้เพื่อเพิ่ม / ลบองค์ประกอบลงใน / จากคอนเทนเนอร์ ถ้าคุณต้องการที่จะทำอย่างนั้นดีกว่ายึดติดกับโซลูชั่นที่กำหนดโดย Brian Matthews
  • หากคุณไม่ต้องการที่จะเปลี่ยนองค์ประกอบในภาชนะของคุณแล้วคุณควรใช้คำหลักดังต่อไปนี้:constfor (auto const &item : some_vector) { ... }

0

ดียิ่งขึ้นกว่า "บอก CPU ว่าจะทำอย่างไร" (จำเป็น) คือ "บอกห้องสมุดสิ่งที่คุณต้องการ" (การทำงาน)

ดังนั้นแทนที่จะใช้ลูปคุณควรเรียนรู้อัลกอริทึมที่มีอยู่ใน stl



0

ฉันมักจะใช้ดัชนีอาร์เรย์เพราะแอปพลิเคชันของฉันจำนวนมากต้องการบางสิ่งเช่น "ภาพขนาดย่อที่แสดง" ดังนั้นฉันจึงเขียนสิ่งนี้:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

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


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

0

ฉันรู้สึกว่าไม่มีคำตอบที่นี่อธิบายว่าทำไมฉันชอบ iterator เป็นแนวคิดทั่วไปมากกว่าการจัดทำดัชนีลงในภาชนะ โปรดทราบว่าประสบการณ์ส่วนใหญ่ของฉันในการใช้ตัววนซ้ำไม่ได้มาจากภาษา C ++ แต่มาจากภาษาการเขียนโปรแกรมระดับสูงเช่น Python

อินเทอร์เฟซตัววนซ้ำกำหนดข้อกำหนดที่น้อยลงสำหรับผู้บริโภคในฟังก์ชั่นของคุณซึ่งช่วยให้ผู้บริโภคสามารถทำอะไรได้มากขึ้น

หากสิ่งที่คุณต้องมีเพื่อให้สามารถส่งต่อย้ำนักพัฒนาที่ไม่ได้ จำกัด อยู่กับการใช้ภาชนะที่จัดทำดัชนี - พวกเขาสามารถใช้ชั้นใดการดำเนินการoperator++(T&), และoperator*(T)operator!=(const &T, const &T)

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

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

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

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

Iterators ยังยืมตัวให้ตกแต่ง ผู้ใช้สามารถเขียนตัววนซ้ำซึ่งใช้ตัววนซ้ำในตัวสร้างและขยายการทำงาน:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

แม้ว่าของเล่นเหล่านี้อาจดูธรรมดา แต่ก็ไม่ยากที่จะจินตนาการว่าการใช้ iterators และ iterator decorator ทำสิ่งที่ทรงพลังด้วยอินเตอร์เฟสที่เรียบง่าย - การตกแต่ง iterator ไปข้างหน้าอย่างเดียวของผลลัพธ์ฐานข้อมูลด้วย iterator ซึ่งสร้างวัตถุจำลองจากผลลัพธ์เดียวเช่น . รูปแบบเหล่านี้เปิดใช้งานการวนซ้ำที่ไม่มีประสิทธิภาพของหน่วยความจำและด้วยตัวกรองเช่นเดียวกับที่ฉันเขียนไว้ด้านบน

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

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