เหตุใดมาตรฐานจึงกำหนดend()
เป็นจุดสิ้นสุดที่ผ่านมาแทนที่จะเป็นที่จุดสิ้นสุดจริง
เหตุใดมาตรฐานจึงกำหนดend()
เป็นจุดสิ้นสุดที่ผ่านมาแทนที่จะเป็นที่จุดสิ้นสุดจริง
คำตอบ:
ข้อโต้แย้งที่ดีที่สุดคือสิ่งที่Dijkstraทำเอง :
คุณต้องการให้ขนาดของช่วงเป็นจุดสิ้นสุดที่แตกต่างง่าย ๆ - เริ่มต้น ;
รวมถึงขอบเขตที่ต่ำกว่านั้นเป็น "ธรรมชาติ" เมื่อลำดับลดลงเป็นค่าว่างและเนื่องจากทางเลือก ( ไม่รวมขอบเขตล่าง) จะต้องมีค่า Sentinel ของ "หนึ่ง - ก่อน - ต้น - เริ่ม"
คุณยังต้องพิสูจน์ว่าทำไมคุณถึงเริ่มนับที่ศูนย์มากกว่าหนึ่ง แต่นั่นไม่ใช่คำถามของคุณ
ภูมิปัญญาที่อยู่เบื้องหลังการประชุม [เริ่มต้นสิ้นสุด) จะเป็นการสละเวลาและอีกครั้งเมื่อคุณมีอัลกอริทึมใด ๆ ที่เกี่ยวข้องกับการเรียกซ้อนหรือการวนซ้ำหลายครั้งไปยังสิ่งก่อสร้างที่อิงตามช่วง ในทางตรงกันข้ามการใช้ช่วงปิดสองเท่าจะทำให้เกิดโค้ดผิดพลาดและรหัสที่ไม่พึงประสงค์และมีเสียงดังมาก ตัวอย่างเช่นพิจารณาพาร์ติชัน [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 ) อีกตัวอย่างคือลูปการวนซ้ำมาตรฐานfor (it = begin; it != end; ++it)
ซึ่งใช้end - begin
เวลา รหัสที่เกี่ยวข้องจะอ่านได้น้อยลงมากหากทั้งสองรวมอยู่ด้วยและลองจินตนาการว่าคุณจะจัดการกับช่วงที่ว่างได้อย่างไร
ในที่สุดเรายังสามารถโต้แย้งได้ดีว่าทำไมการนับควรเริ่มต้นที่ศูนย์: ด้วยการประชุมครึ่งเปิดสำหรับช่วงที่เราเพิ่งจัดตั้งขึ้นถ้าคุณได้รับช่วงขององค์ประกอบN (พูดเพื่อแจกแจงสมาชิกของอาร์เรย์) จากนั้น 0 คือ "การเริ่มต้น" ตามธรรมชาติเพื่อให้คุณสามารถเขียนช่วงเป็น [0, N ) โดยไม่มีการชดเชยหรือการแก้ไขที่น่าอึดอัดใจ
โดยสรุป: ความจริงที่ว่าเราไม่เห็นจำนวน1
ทุกที่ในอัลกอริทึมตามช่วงนั้นเป็นผลโดยตรงของและแรงจูงใจสำหรับการประชุม [เริ่มต้นสิ้น]
begin
และมีค่าและตามลำดับมันเหมาะอย่างสมบูรณ์ มันเป็นเงื่อนไขที่เป็นธรรมชาติมากกว่าแบบดั้งเดิมแต่เราไม่เคยค้นพบสิ่งนั้นจนกว่าเราจะเริ่มคิดถึงคอลเล็กชั่นทั่วไปมากขึ้น end
int
0
N
!=
<
++
เทมเพลตตัววนซ้ำที่step_by<3>
ไม่สามารถแก้ไขได้ซึ่งจะมีซีแมนติกที่โฆษณาไว้ในตอนแรก
!=
เมื่อเขาควรจะใช้<
แล้วมันเป็นข้อผิดพลาด โดยวิธีการที่กษัตริย์ของข้อผิดพลาดที่หาง่ายด้วยการทดสอบหน่วยหรือยืนยัน
ที่จริงแล้วสิ่งที่เกี่ยวข้องกับตัววนซ้ำจำนวนมากทำให้เข้าใจได้ง่ายขึ้นถ้าคุณพิจารณาตัววนซ้ำไม่ได้ชี้ไปที่องค์ประกอบของลำดับ แต่ในระหว่างนั้นด้วยการยกเลิกการเข้าถึงองค์ประกอบถัดไปที่ถูกต้อง จากนั้นตัววนซ้ำ "one past end" ก็ทำให้รู้สึกทันที:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
เห็นได้ชัดว่าbegin
ชี้ไปที่จุดเริ่มต้นของลำดับและend
ชี้ไปที่จุดสิ้นสุดของลำดับเดียวกัน การยกเลิกการลงทะเบียนbegin
เข้าใช้งานองค์ประกอบA
และการยกเลิกการลงทะเบียนend
ไม่มีเหตุผลเพราะไม่มีองค์ประกอบที่ถูกต้อง นอกจากนี้การเพิ่มตัววนซ้ำที่i
อยู่ตรงกลางจะช่วยให้
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
และคุณทันทีที่เห็นว่าช่วงขององค์ประกอบจากbegin
การi
มีองค์ประกอบA
และB
ขณะที่ช่วงขององค์ประกอบจากi
การend
มีองค์ประกอบและC
D
การยกเลิกการลงทะเบียนi
จะให้องค์ประกอบที่ถูกต้องนั่นคือองค์ประกอบแรกของลำดับที่สอง
แม้แต่ "off-by-one" สำหรับตัววนซ้ำแบบย้อนกลับก็เห็นได้ชัดว่า: การย้อนลำดับนั้นให้:
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
ฉันได้เขียนตัววนซ้ำ (ฐาน) แบบ non-reverse ที่สอดคล้องกันในวงเล็บด้านล่าง คุณเห็น iterator ย้อนกลับที่อยู่i
(ซึ่งผมได้ตั้งชื่อri
) ยังคงชี้ในระหว่างองค์ประกอบและB
C
อย่างไรก็ตามเนื่องจากการย้อนกลับของลำดับตอนนี้องค์ประกอบB
จะอยู่ทางขวาของมัน
foo[i]
) เป็นชวเลขสำหรับรายการทันทีหลังจากตำแหน่งi
) เมื่อนึกถึงมันฉันสงสัยว่ามันอาจจะมีประโยชน์สำหรับภาษาที่จะต้องแยกโอเปอเรเตอร์สำหรับ "รายการทันทีหลังจากตำแหน่ง i" และ "รายการทันทีก่อนที่ตำแหน่งฉัน" เนื่องจากอัลกอริทึมจำนวนมากทำงานกับคู่ของรายการที่อยู่ติดกัน รายการที่ตำแหน่งทั้งสองด้านของ i "อาจสะอาดกว่า" รายการที่ตำแหน่ง i และ i + 1 "
begin[0]
(สมมติว่าเป็นตัววนซ้ำการเข้าถึงแบบสุ่ม) จะเข้าถึงองค์ประกอบ1
เนื่องจากไม่มีองค์ประกอบ0
ในลำดับตัวอย่างของฉัน
start()
ในชั้นเรียนของคุณสำหรับการเริ่มต้นกระบวนการที่เฉพาะเจาะจงหรืออะไรก็ตามมันจะน่ารำคาญถ้าขัดแย้งกับสิ่งที่มีอยู่แล้ว)
เหตุใดมาตรฐานจึงกำหนดend()
เป็นจุดสิ้นสุดที่ผ่านมาแทนที่จะเป็นที่จุดสิ้นสุดจริง
เพราะ:
begin()
เท่ากับ
end()
& end()
ยังไม่ถึงเพราะว่าตอนนั้น
size() == end() - begin() // For iterators for whom subtraction is valid
และคุณจะไม่ต้องทำสิ่งที่น่าอึดอัดใจเช่นนั้น
// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }
และคุณจะไม่ได้ตั้งใจเขียนรหัสผิดพลาดเหมือน
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
นอกจากนี้: จะเกิดอะไรขึ้นfind()
หากend()
ชี้ไปที่องค์ประกอบที่ถูกต้อง
คุณจริงๆต้องการอีกสมาชิกเรียกว่าinvalid()
ซึ่งผลตอบแทนที่ไม่ถูกต้อง iterator ?!
ตัววนซ้ำสองตัวนั้นเจ็บปวดพออยู่แล้ว ...
Oh, และเห็นนี้โพสต์ที่เกี่ยวข้อง
ถ้าend
ก่อนหน้านี้เป็นองค์ประกอบสุดท้ายคุณจะinsert()
จบชีวิตจริงอย่างไร!
สำนวน iterator ของช่วงปิดครึ่ง[begin(), end())
แรกเริ่มนั้นใช้เลขคณิตของตัวชี้สำหรับอาร์เรย์ธรรมดา ในโหมดการทำงานนั้นคุณจะมีฟังก์ชั่นที่ผ่านอาร์เรย์และขนาด
void func(int* array, size_t size)
การแปลงเป็นช่วงปิดครึ่ง[begin, end)
นั้นง่ายมากเมื่อคุณมีข้อมูลดังกล่าว:
int* begin;
int* end = array + size;
for (int* it = begin; it < end; ++it) { ... }
หากต้องการทำงานกับช่วงปิดอย่างสมบูรณ์มันยากกว่า:
int* begin;
int* end = array + size - 1;
for (int* it = begin; it <= end; ++it) { ... }
ตั้งแต่ตัวชี้ไปยังอาร์เรย์มี iterators ใน C ++ (และไวยากรณ์ที่ถูกออกแบบมาเพื่อให้นี้) มันง่ายมากที่จะเรียกกว่าก็คือการโทรstd::find(array, array + size, some_value)
std::find(array, array + size - 1, some_value)
Plus ถ้าคุณทำงานกับช่วงครึ่งปิดคุณสามารถใช้!=
ประกอบการเพื่อตรวจสอบสภาพท้ายที่สุดเพราะ (หากผู้ประกอบการของคุณมีการกำหนดไว้อย่างถูกต้อง) หมายถึง<
!=
for (int* it = begin; it != end; ++ it) { ... }
อย่างไรก็ตามไม่มีวิธีง่ายๆในการทำเช่นนี้กับช่วงปิดเต็ม <=
คุณกำลังติดอยู่กับ
ตัววนซ้ำชนิดเดียวที่รองรับ<
และ>
การทำงานใน C ++ เป็นตัววนซ้ำแบบเข้าถึงได้ ถ้าคุณต้องเขียน<=
ผู้ประกอบการสำหรับการเรียน iterator ทุกใน C ++ คุณจะต้องทำให้ทุก iterators ของคุณเปรียบได้อย่างเต็มที่และคุณควรที่จะเลือกน้อยลงสำหรับการสร้าง iterators สามารถน้อย (เช่น iterators สองทิศทางบนstd::list
หรือ iterators การป้อนข้อมูล ที่ทำงานบนiostreams
) ถ้า C ++ ใช้ช่วงปิดเต็ม
ด้วยการend()
ชี้ไปหนึ่งครั้งสุดท้ายมันเป็นเรื่องง่ายที่จะย้ำคอลเล็กชันด้วยการวนซ้ำ:
for (iterator it = collection.begin(); it != collection.end(); it++)
{
DoStuff(*it);
}
ด้วยการend()
ชี้ไปที่องค์ประกอบสุดท้ายลูปจะซับซ้อนกว่า:
iterator it = collection.begin();
while (!collection.empty())
{
DoStuff(*it);
if (it == collection.end())
break;
it++;
}
begin() == end()
ถ้าภาชนะที่ว่างเปล่า!=
แทน<
(น้อยกว่า) ในend()
สภาพลูปดังนั้นการชี้ไปยังตำแหน่งที่หนึ่งนอกสิ้นจึงสะดวก