เรามีคำถามคือมีความแตกต่างระหว่างประสิทธิภาพi++
และ++i
ใน C ?
คำตอบสำหรับ C ++ คืออะไร
เรามีคำถามคือมีความแตกต่างระหว่างประสิทธิภาพi++
และ++i
ใน C ?
คำตอบสำหรับ C ++ คืออะไร
คำตอบ:
[บทสรุปผู้บริหาร: ใช้++i
หากคุณไม่มีเหตุผลเฉพาะที่จะใช้i++
]
สำหรับ C ++ คำตอบนั้นซับซ้อนกว่าเล็กน้อย
ถ้าi
เป็นประเภทที่เรียบง่าย (ไม่ใช่อินสแตนซ์ของคลาส C ++) ดังนั้นคำตอบที่ได้รับสำหรับ C ("ไม่มีความแตกต่างของประสิทธิภาพ")ถือไว้เนื่องจากคอมไพเลอร์กำลังสร้างรหัส
อย่างไรก็ตามถ้าi
เป็นอินสแตนซ์ของคลาส C ++ ดังนั้นi++
และ++i
กำลังเรียกไปยังหนึ่งในoperator++
ฟังก์ชัน นี่คือฟังก์ชันมาตรฐานคู่เหล่านี้:
Foo& Foo::operator++() // called for ++i
{
this->data += 1;
return *this;
}
Foo Foo::operator++(int ignored_dummy_value) // called for i++
{
Foo tmp(*this); // variable "tmp" cannot be optimized away by the compiler
++(*this);
return tmp;
}
เนื่องจากคอมไพเลอร์ไม่ได้สร้างรหัส แต่เพียงเรียกใช้operator++
ฟังก์ชันจึงไม่มีวิธีที่จะปรับtmp
ตัวแปรให้เหมาะสมและตัวสร้างสำเนาที่เกี่ยวข้อง หากตัวสร้างสำเนามีราคาแพงแสดงว่ามีผลกระทบต่อประสิทธิภาพอย่างมาก
ใช่. นั่นคือ
ตัวดำเนินการ ++ อาจกำหนดหรือไม่ก็ได้ว่าเป็นฟังก์ชั่น สำหรับประเภทดึกดำบรรพ์ (int, double, ... ) ตัวดำเนินการถูกสร้างขึ้นดังนั้นคอมไพเลอร์อาจจะสามารถปรับรหัสของคุณให้เหมาะสม แต่ในกรณีของวัตถุที่กำหนดสิ่งที่ผู้ประกอบการ ++ จะแตกต่างกัน
ฟังก์ชันโอเปอเรเตอร์ ++ (int) จะต้องสร้างสำเนา นั่นเป็นเพราะ postfix ++ คาดว่าจะส่งคืนค่าที่แตกต่างจากที่เก็บไว้: จะต้องเก็บค่าไว้ในตัวแปร temp เพิ่มค่าและส่งคืน temp ในกรณีของโอเปอเรเตอร์ ++ ++ (), คำนำหน้า ++ ไม่จำเป็นต้องสร้างสำเนา: วัตถุสามารถเพิ่มตัวมันเองแล้วกลับมาเอง
นี่คือภาพประกอบของประเด็น:
struct C
{
C& operator++(); // prefix
C operator++(int); // postfix
private:
int i_;
};
C& C::operator++()
{
++i_;
return *this; // self, no copy created
}
C C::operator++(int ignored_dummy_value)
{
C t(*this);
++(*this);
return t; // return a copy
}
ทุกครั้งที่คุณเรียกโอเปอเรเตอร์ ++ (int) คุณต้องสร้างสำเนาและคอมไพเลอร์ไม่สามารถทำอะไรกับมันได้ เมื่อได้รับตัวเลือกให้ใช้โอเปอเรเตอร์ ++ (); วิธีนี้คุณจะไม่บันทึกสำเนา มันอาจมีความสำคัญในกรณีของการเพิ่มขึ้นจำนวนมาก (ลูปขนาดใหญ่?) และ / หรือวัตถุขนาดใหญ่
C t(*this); ++(*this); return t;
ในบรรทัดที่สองคุณกำลังเพิ่มตัวชี้นี้ให้ถูกต้องดังนั้นจะt
ได้รับการอัปเดตอย่างไรถ้าคุณเพิ่มส่วนนี้ ค่าของสิ่งนี้ถูกคัดลอกไปแล้วt
หรือไม่?
The operator++(int) function must create a copy.
ไม่มันไม่ใช่. ไม่มากไปกว่าสำเนาoperator++()
นี่คือมาตรฐานสำหรับกรณีที่เมื่อผู้ประกอบการเพิ่มขึ้นอยู่ในหน่วยการแปลที่แตกต่างกัน คอมไพเลอร์กับ g ++ 4.5
ไม่สนใจปัญหาสไตล์ในตอนนี้
// a.cc
#include <ctime>
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
int main () {
Something s;
for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
std::clock_t a = clock();
for (int i=0; i<1024*1024*30; ++i) ++s;
a = clock() - a;
for (int i=0; i<1024*1024*30; ++i) s++; // warm up
std::clock_t b = clock();
for (int i=0; i<1024*1024*30; ++i) s++;
b = clock() - b;
std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
<< ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
return 0;
}
// b.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
for (auto it=data.begin(), end=data.end(); it!=end; ++it)
++*it;
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
ผลลัพธ์ (การกำหนดเวลาเป็นวินาที) ด้วย g ++ 4.5 บนเครื่องเสมือน:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 1.70 2.39
-DPACKET_SIZE=50 -O3 0.59 1.00
-DPACKET_SIZE=500 -O1 10.51 13.28
-DPACKET_SIZE=500 -O3 4.28 6.82
ให้เราใช้ไฟล์ต่อไปนี้:
// c.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
มันไม่ทำอะไรเลยในการเพิ่ม สิ่งนี้จะจำลองกรณีเมื่อการเพิ่มมีความซับซ้อนคงที่
ผลลัพธ์ตอนนี้แตกต่างกันมาก:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 0.05 0.74
-DPACKET_SIZE=50 -O3 0.08 0.97
-DPACKET_SIZE=500 -O1 0.05 2.79
-DPACKET_SIZE=500 -O3 0.08 2.18
-DPACKET_SIZE=5000 -O3 0.07 21.90
หากคุณไม่ต้องการค่าก่อนหน้าให้ทำให้เป็นนิสัยที่จะใช้การเพิ่มล่วงหน้า ให้สอดคล้องกับประเภทบิวด์อินคุณจะคุ้นเคยและไม่เสี่ยงต่อการสูญเสียประสิทธิภาพที่ไม่จำเป็นหากคุณเปลี่ยนบิวอินชนิดที่มีชนิดกำหนดเอง
i++
increment i, I am interested in the previous value, though
กล่าวว่า++i
กล่าวหรือincrement i, I am interested in the current value
increment i, no interest in the previous value
อีกครั้งคุณจะคุ้นเคยกับมันแม้ว่าคุณจะไม่ได้ตอนนี้การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด เช่นเดียวกับการมองในแง่ร้ายก่อนวัยอันควร
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
ไม่ต้องคำนึงถึงโครงสร้างของต้นไม้ที่แท้จริง (BSP, kd, Quadtree, Octree Grid เป็นต้น) การดังกล่าวจะต้อง iterator การรักษาสถานะบางอย่างเช่นparent node
, child node
, index
และสิ่งที่ชอบ โดยรวมท่าทางของฉันคือแม้ว่าจะมีเพียงไม่กี่ตัวอย่างเท่านั้น ...
มันไม่ถูกต้องทั้งหมดที่จะกล่าวว่าคอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการคัดลอกตัวแปรชั่วคราวในกรณี postfix การทดสอบอย่างรวดเร็วกับ VC แสดงว่าอย่างน้อยสามารถทำได้ในบางกรณี
ในตัวอย่างต่อไปนี้โค้ดที่สร้างขึ้นจะเหมือนกันสำหรับคำนำหน้าและ postfix ตัวอย่างเช่น:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
ไม่ว่าคุณจะทำ ++ testFoo หรือ testFoo ++ คุณจะยังได้รับรหัสผลลัพธ์เดียวกัน ในความเป็นจริงโดยไม่ต้องอ่านจำนวนในจากผู้ใช้เพิ่มประสิทธิภาพได้ทุกสิ่งลงไปที่ค่าคงที่ ดังนั้นนี่คือ:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
ส่งผลให้ในต่อไปนี้:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
ดังนั้นแม้ว่าจะเป็นกรณีที่เวอร์ชัน postfix อาจช้าลง แต่ก็อาจเป็นไปได้ว่าเครื่องมือเพิ่มประสิทธิภาพจะดีพอที่จะกำจัดสำเนาชั่วคราวหากคุณไม่ได้ใช้งาน
Google c ++ คู่มือสไตล์พูดว่า:
รูปแบบและการคาดการณ์ล่วงหน้า
ใช้แบบฟอร์มคำนำหน้า (++ i) ของตัวดำเนินการเพิ่มและลดที่มีตัววนซ้ำและวัตถุแม่แบบอื่น ๆ
คำจำกัดความ:เมื่อตัวแปรถูกเพิ่มค่า (++ i หรือ i ++) หรือลดค่า (--i หรือ i--) และไม่ได้ใช้ค่าของนิพจน์ค่าหนึ่งจะต้องตัดสินใจว่าจะเติมเงินล่วงหน้า (ลดค่า) หรือลดจำนวนลง (ลดลง)
ข้อดี:เมื่อค่าที่ส่งคืนถูกละเว้นรูปแบบ "pre" (++ i) จะไม่มีประสิทธิภาพน้อยกว่าฟอร์ม "post" (i ++) และมักจะมีประสิทธิภาพมากกว่า นี่เป็นเพราะโพสต์เพิ่มขึ้น (หรือลดลง) ต้องการสำเนาของฉันที่จะทำซึ่งเป็นค่าของการแสดงออก ถ้าฉันเป็นตัววนซ้ำหรือไม่ใช่แบบสเกลาร์อื่น ๆ การคัดลอกฉันอาจมีราคาแพง เนื่องจากการเพิ่มขึ้นสองประเภทจะทำงานเหมือนกันเมื่อค่าถูกละเว้นทำไมไม่เพิ่มค่าล่วงหน้าล่วงหน้าเสมอไป
ข้อด้อย:ประเพณีที่พัฒนาขึ้นใน C ใช้การเพิ่มภายหลังเมื่อไม่ใช้ค่านิพจน์โดยเฉพาะในลูป บางคนพบว่าโพสต์เพิ่มขึ้นง่ายต่อการอ่านเนื่องจาก "หัวเรื่อง" (i) นำหน้า "verb" (++) เหมือนภาษาอังกฤษ
การตัดสินใจ:สำหรับค่าสเกลาร์แบบง่าย (ไม่ใช่วัตถุ) ไม่มีเหตุผลที่จะชอบรูปแบบเดียวและเราอนุญาตเช่นกัน สำหรับตัววนซ้ำและชนิดเทมเพลตอื่นให้ใช้การเพิ่มล่วงหน้า
ฉันต้องการจะชี้ให้เห็นโพสต์ที่ยอดเยี่ยมโดย Andrew Koenig ใน Code Talk เมื่อเร็ว ๆ นี้
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
ที่ บริษัท ของเราเรายังใช้การประชุมของ ++ iter สำหรับความสอดคล้องและประสิทธิภาพการทำงานที่เกี่ยวข้อง แต่แอนดรูว์ให้รายละเอียดที่เกินจริงเกี่ยวกับเจตนาและประสิทธิภาพ มีบางครั้งที่เราต้องการใช้ iter ++ แทน ++ iter
ดังนั้นก่อนตัดสินใจความตั้งใจของคุณและถ้าก่อนหรือโพสต์ไม่สำคัญไปด้วย pre เพราะมันจะมีประโยชน์ประสิทธิภาพการทำงานบางอย่างโดยหลีกเลี่ยงการสร้างวัตถุพิเศษและโยนมัน
@Ketan
... เพิ่มรายละเอียดที่เกินจริงเกี่ยวกับเจตนาเทียบกับประสิทธิภาพ มีบางครั้งที่เราต้องการใช้ iter ++ แทน ++ iter
เห็นได้ชัดว่าการโพสต์และการเพิ่มล่วงหน้ามีความหมายที่แตกต่างกันและฉันแน่ใจว่าทุกคนยอมรับว่าเมื่อใช้ผลลัพธ์คุณควรใช้โอเปอเรเตอร์ที่เหมาะสม ฉันคิดว่าคำถามคือสิ่งที่เราควรทำเมื่อผลลัพธ์ถูกยกเลิก (เช่นเดียวกับfor
ลูป) คำตอบสำหรับคำถามนี้ (IMHO) คือเนื่องจากข้อควรพิจารณาด้านประสิทธิภาพมีน้อยมากคุณควรทำในสิ่งที่เป็นธรรมชาติมากกว่า สำหรับตัวเอง++i
เป็นธรรมชาติมากขึ้น แต่ประสบการณ์ของฉันบอกฉันว่าฉันอยู่ในชนกลุ่มน้อยและการใช้i++
จะทำให้ค่าใช้จ่ายโลหะน้อยลงสำหรับคนส่วนใหญ่อ่านรหัสของคุณ
เพราะนั่นคือเหตุผลที่ภาษาไม่ได้ถูกเรียกว่า " ++C
" [*]
[*] แทรกการอภิปรายแบบบังคับเกี่ยวกับ++C
การเป็นชื่อที่มีเหตุผลมากกว่า
เมื่อไม่ได้ใช้คืนค่าคอมไพเลอร์มีการประกันจะไม่ใช้ชั่วคราวในกรณีของผม++ ไม่รับประกันว่าจะเร็วกว่า แต่รับประกันว่าจะไม่ช้า
เมื่อใช้ค่าส่งคืนi ++อนุญาตให้ตัวประมวลผลผลักทั้งส่วนเพิ่มและด้านซ้ายเข้าสู่ไปป์ไลน์เนื่องจากไม่ได้พึ่งพาซึ่งกันและกัน ++ ฉันอาจถ่วงไปป์ไลน์เนื่องจากตัวประมวลผลไม่สามารถเริ่มทางด้านซ้ายได้จนกว่าการดำเนินการที่เพิ่มขึ้นล่วงหน้าจะวนเวียนไปมาตลอด อีกครั้งไม่รับประกันแผงลอยไปป์ไลน์เนื่องจากโปรเซสเซอร์อาจพบสิ่งที่มีประโยชน์อื่น ๆ
Mark: แค่ต้องการชี้ให้เห็นว่าโอเปอเรเตอร์ ++ นั้นเป็นตัวเลือกที่ดีที่จะต้องถูกแทรกและถ้าคอมไพเลอร์เลือกที่จะทำเช่นนั้นสำเนาที่ซ้ำซ้อนจะถูกกำจัดในกรณีส่วนใหญ่ (เช่นประเภท POD ซึ่งโดยทั่วไปจะเป็นตัววนซ้ำ)
ที่กล่าวว่ายังคงมีสไตล์ที่ดีกว่าในการใช้ ++ iter ในกรณีส่วนใหญ่ :-)
ความแตกต่างด้านประสิทธิภาพระหว่าง++i
และi++
จะชัดเจนขึ้นเมื่อคุณคิดว่าตัวดำเนินการเป็นฟังก์ชั่นคืนค่าและวิธีการใช้งาน เพื่อให้ง่ายต่อการเข้าใจสิ่งที่เกิดขึ้นตัวอย่างรหัสต่อไปนี้จะใช้เป็นว่ามันเป็นint
struct
++i
เพิ่มตัวแปรแล้วส่งคืนผลลัพธ์ สิ่งนี้สามารถทำได้ในสถานที่และมีเวลา CPU น้อยที่สุดซึ่งต้องการโค้ดเพียงบรรทัดเดียวในหลายกรณี:
int& int::operator++() {
return *this += 1;
}
แต่ไม่สามารถพูดi++
ได้เหมือนกัน
โพสต์เพิ่มขึ้นi++
มักจะถูกมองว่าเป็นการคืนค่าเดิมก่อนที่จะเพิ่มขึ้น อย่างไรก็ตามฟังก์ชั่นสามารถส่งคืนผลลัพธ์เมื่อเสร็จสิ้นเท่านั้น ด้วยเหตุนี้จึงจำเป็นต้องสร้างสำเนาของตัวแปรที่มีค่าดั้งเดิมเพิ่มค่าตัวแปรจากนั้นส่งคืนสำเนาที่เก็บค่าดั้งเดิม:
int int::operator++(int& _Val) {
int _Original = _Val;
_Val += 1;
return _Original;
}
เมื่อไม่มีการทำงานที่แตกต่างระหว่าง pre-increment และ post-increment คอมไพเลอร์สามารถดำเนินการปรับให้เหมาะสมซึ่งจะไม่มีความแตกต่างระหว่างประสิทธิภาพทั้งสอง อย่างไรก็ตามหากชนิดข้อมูลคอมโพสิตเช่น a struct
หรือclass
มีส่วนเกี่ยวข้องตัวสร้างการคัดลอกจะถูกเรียกใช้ในการเพิ่มภายหลังและจะไม่สามารถทำการปรับให้เหมาะสมนี้หากจำเป็นต้องมีการทำสำเนาลึก โดยทั่วไปการเพิ่มล่วงหน้าจะเร็วกว่าและต้องการหน่วยความจำน้อยกว่าการเพิ่มภายหลัง
@ Mark: ฉันลบคำตอบก่อนหน้าของฉันเพราะมันค่อนข้างพลิกและสมควรได้รับ downvote สำหรับคนเดียว จริง ๆ แล้วฉันคิดว่ามันเป็นคำถามที่ดีในแง่ที่ว่ามันถามสิ่งที่อยู่ในใจของผู้คนจำนวนมาก
คำตอบปกติคือ ++ ฉันเร็วกว่า i ++ และไม่ต้องสงสัยเลยว่ามี แต่คำถามที่ใหญ่กว่าคือ "เมื่อใดที่คุณควรสนใจ"
หากเศษส่วนของเวลา CPU ที่ใช้ในการเพิ่มตัววนซ้ำน้อยกว่า 10% แสดงว่าคุณไม่สนใจ
หากเศษส่วนของเวลา CPU ที่ใช้ในการเพิ่มตัววนซ้ำมากกว่า 10% คุณสามารถดูได้ว่าประโยคใดที่ทำซ้ำ ดูว่าคุณสามารถเพิ่มจำนวนเต็มแทนที่จะใช้ตัววนซ้ำได้ไหม โอกาสที่คุณจะทำได้และในขณะที่มันอาจเป็นที่น่าพอใจน้อยกว่าในบางโอกาสที่ค่อนข้างดีคุณจะประหยัดเป็นหลักตลอดเวลาที่ใช้ในการทำซ้ำเหล่านั้น
ฉันเคยเห็นตัวอย่างที่การเพิ่มตัววนซ้ำใช้เวลานานกว่า 90% ในกรณีดังกล่าวการเพิ่มจำนวนเต็มจะช่วยลดเวลาในการดำเนินการลงโดยลดจำนวนลง (เช่นดีกว่าเร่งความเร็ว 10x)
@wilhelmtell
คอมไพเลอร์สามารถขจัดชั่วคราว ใช้คำต่อคำจากเธรดอื่น:
คอมไพเลอร์ C ++ ได้รับอนุญาตให้กำจัดชั่วคราวตามสแต็คแม้ว่าจะทำเช่นนั้นการเปลี่ยนแปลงพฤติกรรมของโปรแกรม ลิงก์ MSDN สำหรับ VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
เหตุผลที่คุณควรใช้ ++ i ถึงแม้จะเป็นแบบในตัวซึ่งไม่มีข้อได้เปรียบด้านประสิทธิภาพคือการสร้างนิสัยที่ดีให้กับตัวเอง
ทั้งสองอย่างรวดเร็ว;) หากคุณต้องการมันเป็นการคำนวณแบบเดียวกันสำหรับโปรเซสเซอร์มันเป็นเพียงลำดับที่แตกต่างกัน
ตัวอย่างเช่นรหัสต่อไปนี้:
#include <stdio.h>
int main()
{
int a = 0;
a++;
int b = 0;
++b;
return 0;
}
ผลิตชุดประกอบต่อไปนี้:
0x0000000100000f24 <main+0>: push %rbp 0x0000000100000f25 <main+1>: mov %rsp,%rbp 0x0000000100000f28 <main+4>: movl $0x0,-0x4(%rbp) 0x0000000100000f2f <main+11>: incl -0x4(%rbp) 0x0000000100000f32 <main+14>: movl $0x0,-0x8(%rbp) 0x0000000100000f39 <main+21>: incl -0x8(%rbp) 0x0000000100000f3c <main+24>: mov $0x0,%eax 0x0000000100000f41 <main+29>: leaveq 0x0000000100000f42 <main+30>: retq
คุณเห็นว่าสำหรับ a ++ และ b ++ มันเป็น mnemonic รวมดังนั้นจึงเป็นการดำเนินการเดียวกัน;)
คำถามที่ต้องการเกิดขึ้นเมื่อไม่ได้ใช้ผลลัพธ์ (ชัดเจนสำหรับคำถาม C) ใครสามารถแก้ไขได้เนื่องจากคำถามคือ "community wiki"
เกี่ยวกับการปรับให้เหมาะสมก่อนวัยอันควร Knuth มักจะอ้างถึง ถูกตัอง. แต่โดนัลด์นัทท์จะไม่ป้องกันด้วยรหัสที่น่ากลัวซึ่งคุณสามารถดูได้ในวันนี้ เคยเห็น a = b + c ท่ามกลาง Java Integers (ไม่ใช่ int) หรือไม่ นั่นคือจำนวนการแปลง 3 มวย / unboxing หลีกเลี่ยงสิ่งที่เป็นสิ่งสำคัญ และการเขียน i ++ อย่างไร้ประโยชน์แทนที่จะเป็น ++ ฉันก็เป็นความผิดพลาดเดียวกัน แก้ไข: ตามที่ phresnel ใส่ไว้อย่างดีในความคิดเห็นสิ่งนี้สามารถสรุปได้ว่า "การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นสิ่งที่ชั่วร้าย
แม้ความจริงที่ว่าผู้คนคุ้นเคยกับ i ++ มากขึ้นก็เป็นมรดก C ที่โชคร้ายที่เกิดจากความผิดพลาดทางแนวคิดโดย K&R (ถ้าคุณทำตามการโต้แย้งเจตนานั่นเป็นข้อสรุปเชิงตรรกะและปกป้อง K&R เพราะพวกเขาเป็น K&R ไม่มีความหมาย ยอดเยี่ยม แต่ไม่ดีนักในการออกแบบภาษามีข้อผิดพลาดนับไม่ถ้วนในการออกแบบ C อยู่ตั้งแต่ get () ถึง strcpy () ถึง strncpy () API (ควรมี strlcpy () API ตั้งแต่วันที่ 1) )
Btw ฉันเป็นหนึ่งในผู้ที่ไม่คุ้นเคยกับ C ++ ในการค้นหา ++ ฉันน่ารำคาญที่จะอ่าน ถึงกระนั้นฉันก็ใช้มันตั้งแต่ฉันยอมรับว่ามันถูกต้อง
++i
น่ารำคาญไปกว่าi++
(อันที่จริงแล้วฉันพบว่ามันเจ๋งกว่านี้) แต่ส่วนที่เหลือของโพสต์ของคุณจะได้รับการยอมรับอย่างสมบูรณ์ อาจเพิ่มจุด "การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นสิ่งชั่วร้ายเช่นเดียวกับการมองในแง่ร้ายก่อนวัยอันควร"
strncpy
ทำหน้าที่ในระบบไฟล์ที่ใช้อยู่ในขณะนั้น ชื่อไฟล์เป็นบัฟเฟอร์ 8 ตัวอักษรและไม่จำเป็นต้องยกเลิกด้วย null คุณไม่สามารถตำหนิพวกเขาหากไม่เห็น 40 ปีในอนาคตของวิวัฒนาการภาษา
strlcpy()
ได้รับการพิสูจน์โดยข้อเท็จจริงว่ามันยังไม่ได้ถูกคิดค้น
เวลาให้คนที่มีอัญมณีแห่งปัญญา;) - มีเคล็ดลับง่ายๆที่จะทำให้การเพิ่ม postfix ของ C ++ มีพฤติกรรมเหมือนกับการเพิ่มคำนำหน้า (ประดิษฐ์สิ่งนี้ด้วยตัวเอง แต่เห็นด้วยเช่นกันในรหัสคนอื่นดังนั้นฉันจึงไม่ อยู่คนเดียว)
โดยพื้นฐานแล้วกลอุบายคือการใช้คลาสผู้ช่วยเพื่อเลื่อนการเพิ่มขึ้นหลังจากการกลับมาและ RAII มาช่วย
#include <iostream>
class Data {
private: class DataIncrementer {
private: Data& _dref;
public: DataIncrementer(Data& d) : _dref(d) {}
public: ~DataIncrementer() {
++_dref;
}
};
private: int _data;
public: Data() : _data{0} {}
public: Data(int d) : _data{d} {}
public: Data(const Data& d) : _data{ d._data } {}
public: Data& operator=(const Data& d) {
_data = d._data;
return *this;
}
public: ~Data() {}
public: Data& operator++() { // prefix
++_data;
return *this;
}
public: Data operator++(int) { // postfix
DataIncrementer t(*this);
return *this;
}
public: operator int() {
return _data;
}
};
int
main() {
Data d(1);
std::cout << d << '\n';
std::cout << ++d << '\n';
std::cout << d++ << '\n';
std::cout << d << '\n';
return 0;
}
สิ่งประดิษฐ์สำหรับรหัสตัววนซ้ำที่กำหนดเองจำนวนมากและลดเวลาทำงาน ค่าใช้จ่ายของคำนำหน้ากับ postfix เป็นการอ้างอิงหนึ่งตอนนี้และถ้านี่คือโอเปอเรเตอร์แบบกำหนดเองที่มีการเคลื่อนไหวที่หนักหน่วงคำนำหน้าและ postfix ก็ให้ผลในขณะเดียวกัน
++i
เร็วกว่าi++
เพราะไม่ได้ส่งคืนค่าเดิม
นอกจากนี้ยังใช้งานง่ายยิ่งขึ้น:
x = i++; // x contains the old value of i
y = ++i; // y contains the new value of i
ตัวอย่าง C นี้พิมพ์ "02" แทน "12" ที่คุณอาจคาดหวัง:
#include <stdio.h>
int main(){
int a = 0;
printf("%d", a++);
printf("%d", ++a);
return 0;
}
#include <iostream>
using namespace std;
int main(){
int a = 0;
cout << a++;
cout << ++a;
return 0;
}