การเพิ่ม C ++ - เมื่อใดควรใช้ x ++ หรือ ++ x


93

ฉันกำลังเรียนรู้ C ++ และฉันได้เรียนรู้เกี่ยวกับการเพิ่มขึ้นเมื่อสักครู่ที่ผ่านมา ฉันรู้ว่าคุณสามารถใช้ "++ x" เพื่อเพิ่มส่วนก่อนและ "x ++" เพื่อทำหลังจากนั้น

ถึงกระนั้นฉันก็ไม่รู้จริงๆว่าจะใช้ทั้งสองอย่างเมื่อใด ... ฉันไม่เคยใช้ "++ x" จริงๆและจนถึงตอนนี้สิ่งต่างๆก็ใช้ได้ดี - ดังนั้นฉันควรใช้เมื่อใด

ตัวอย่าง: ในสำหรับลูปควรใช้ "++ x" เมื่อใด

นอกจากนี้ใครบางคนสามารถอธิบายได้อย่างชัดเจนว่าการเพิ่มขึ้น (หรือการลดระดับ) ทำงานอย่างไร ฉันจะขอบคุณมันจริงๆ

คำตอบ:


118

ไม่ใช่คำถามเกี่ยวกับความชอบ แต่เป็นเหตุผล

x++เพิ่มค่าของตัวแปร x หลังจากประมวลผลคำสั่งปัจจุบัน

++xเพิ่มค่าของตัวแปร x ก่อนประมวลผลคำสั่งปัจจุบัน

ดังนั้นเพียงแค่ตัดสินใจตามตรรกะที่คุณเขียน

x += ++iจะเพิ่ม i และเพิ่ม i + 1 เป็น x x += i++จะเพิ่ม i เป็น x แล้วเพิ่ม i


27
และโปรดทราบว่าสำหรับการวนซ้ำบนไพรเมอร์ไม่มีความแตกต่างอย่างแน่นอน รูปแบบการเข้ารหัสจำนวนมากจะแนะนำว่าอย่าใช้ตัวดำเนินการเพิ่มในกรณีที่อาจเข้าใจผิด กล่าวคือ x ++ หรือ ++ x ควรมีอยู่ในบรรทัดของตัวเองเท่านั้นห้ามเป็น y = x ++ โดยส่วนตัวแล้วฉันไม่ชอบสิ่งนี้ แต่มันผิดปกติ
Mikeage

2
และหากใช้ในบรรทัดของตัวเองรหัสที่สร้างขึ้นก็เกือบจะแน่ใจได้ว่าจะเหมือนกัน
Nosredna

14
สิ่งนี้อาจดูเหมือนอวดดี (ส่วนใหญ่เป็นเพราะ :)) แต่ใน C ++ x++เป็นค่า rvalue ที่มีค่าxก่อนการเพิ่มขึ้นx++เป็นค่า lvalue ที่มีค่าxหลังจากการเพิ่ม นิพจน์ทั้งสองไม่รับประกันเมื่อค่าที่เพิ่มขึ้นจริงถูกจัดเก็บกลับเป็น x แต่จะรับประกันได้ว่าจะเกิดขึ้นก่อนจุดลำดับถัดไป 'หลังการประมวลผลคำสั่งปัจจุบัน' ไม่ถูกต้องอย่างเคร่งครัดเนื่องจากบางนิพจน์มีลำดับจุดและบางคำสั่งเป็นคำสั่งประกอบ
CB Bailey

10
จริงๆแล้วคำตอบนั้นทำให้เข้าใจผิด จุดเวลาที่มีการแก้ไขตัวแปร x อาจไม่แตกต่างกันในทางปฏิบัติ ความแตกต่างคือ x ++ ถูกกำหนดให้ส่งกลับค่า rvalue ของค่าก่อนหน้าของ x ในขณะที่ ++ x ยังคงอ้างถึงตัวแปร x
sellibitze

5
@BeowulfOF: คำตอบหมายถึงคำสั่งซื้อที่ไม่มีอยู่จริง ไม่มีอะไรในมาตรฐานที่จะบอกว่าเมื่อเพิ่มขึ้นเกิดขึ้น คอมไพเลอร์มีสิทธิ์ที่จะใช้ "x + = i ++" เป็น: int j = i; ฉัน = ฉัน + 1; x + = j; "(เช่น" i "เพิ่มขึ้นก่อน" ประมวลผลคำสั่งปัจจุบัน ") นี่คือสาเหตุที่" i = i ++ "มีพฤติกรรมที่ไม่ได้กำหนดและเป็นสาเหตุที่ฉันคิดว่าคำตอบต้องการ" ปรับแต่ง "คำอธิบายของ" x + = ++ i "ถูกต้องเนื่องจากไม่มีคำแนะนำของคำสั่ง:" จะเพิ่ม i และเพิ่ม i + 1 เป็น x "
Richard Corden

53

สก็อตต์เมเยอร์สบอกให้คุณชอบคำนำหน้ายกเว้นในโอกาสที่ตรรกะกำหนดว่า postfix นั้นเหมาะสม

"C ++ ที่มีประสิทธิภาพมากขึ้น" รายการ # 6 - นั่นเป็นอำนาจที่เพียงพอสำหรับฉัน

สำหรับผู้ที่ไม่ได้เป็นเจ้าของหนังสือนี่คือคำพูดที่เกี่ยวข้อง จากหน้า 32:

ตั้งแต่สมัยคุณเป็นโปรแกรมเมอร์ C คุณอาจจำได้ว่ารูปแบบคำนำหน้าของตัวดำเนินการส่วนเพิ่มบางครั้งเรียกว่า "Increment and fetch" ในขณะที่รูปแบบ postfix มักเรียกว่า "การดึงข้อมูลและการเพิ่ม" วลีทั้งสองเป็นสิ่งสำคัญที่ต้องจำเพราะทั้งหมดนี้ทำหน้าที่เป็นข้อกำหนดที่เป็นทางการ ...

และในหน้า 34:

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


4
หากคอมไพลเลอร์ไม่ทราบว่าค่าก่อนการเพิ่มนั้นไม่น่ากลัวก็อาจใช้การเพิ่มส่วนท้ายของการแก้ไขในหลาย ๆ คำสั่ง - คัดลอกค่าเก่าแล้วเพิ่มขึ้น การเพิ่มคำนำหน้าควรเป็นเพียงคำสั่งเดียวเสมอ
gnud

8
ฉันบังเอิญทดสอบสิ่งนี้เมื่อวานนี้ด้วย gcc: ใน for loop ที่ค่าถูกทิ้งไปหลังจากเรียกใช้งานi++หรือ++iรหัสที่สร้างขึ้นจะเหมือนกัน
Giorgio

ลองใช้ภายนอกสำหรับลูป พฤติกรรมในการมอบหมายงานต้องแตกต่างกัน
duffymo

ฉันไม่เห็นด้วยอย่างชัดเจนกับ Scott Meyers ในประเด็นที่สองของเขาซึ่งโดยปกติแล้วจะไม่เกี่ยวข้องเนื่องจาก 90% หรือมากกว่านั้นโดยทั่วไปแล้วกรณีของ "x ++" หรือ "++ x" จะแยกออกจากงานใด ๆ และเครื่องมือเพิ่มประสิทธิภาพจะฉลาดพอที่จะรับรู้ว่าไม่จำเป็นต้องมีตัวแปรชั่วคราว ถูกสร้างขึ้นในกรณีเช่นนี้ ในกรณีนี้ทั้งสองรูปแบบสามารถใช้แทนกันได้อย่างสมบูรณ์ ความหมายของสิ่งนี้คือฐานรหัสเก่าที่เต็มไปด้วย "x ++" ควรถูกปล่อยให้อยู่ตามลำพังคุณมีแนวโน้มที่จะแนะนำข้อผิดพลาดเล็กน้อยที่เปลี่ยนเป็น "++ x" มากกว่าที่จะปรับปรุงประสิทธิภาพที่ใดก็ได้ เป็นเรื่องที่ดีกว่าถ้าใช้ "x ++" และทำให้ผู้คนคิด
omatai

2
คุณสามารถไว้วางใจ Scott Meyers ได้ทุกอย่างที่คุณต้องการ แต่ถ้าโค้ดของคุณขึ้นอยู่กับประสิทธิภาพซึ่งความแตกต่างระหว่างประสิทธิภาพ++xและx++ความสำคัญจริงๆแล้วสิ่งที่สำคัญกว่านั้นคือคุณต้องใช้คอมไพเลอร์ที่สามารถปรับแต่งเวอร์ชันใดเวอร์ชันหนึ่งได้อย่างสมบูรณ์และเหมาะสมไม่ว่าจะเป็น บริบท. "เนื่องจากฉันใช้ค้อนเก่าเส็งเคร็งนี้ฉันสามารถตอกตะปูได้ในมุม 43.7 องศาเท่านั้น" จึงเป็นข้อโต้แย้งที่ไม่ดีสำหรับการสร้างบ้านด้วยการตอกตะปูในระดับ 43.7 องศาเท่านั้น ใช้เครื่องมือที่ดีกว่า
Andrew Henle

28

จากcppreferenceเมื่อเพิ่มตัววนซ้ำ:

คุณควรเลือกตัวดำเนินการเพิ่มล่วงหน้า (++ iter) มากกว่าตัวดำเนินการหลังการเพิ่ม (iter ++) หากคุณจะไม่ใช้ค่าเก่า โดยทั่วไปแล้วการเพิ่มขึ้นภายหลังจะดำเนินการดังนี้:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

เห็นได้ชัดว่ามีประสิทธิภาพน้อยกว่าการเพิ่มขึ้นล่วงหน้า

การเพิ่มล่วงหน้าไม่ได้สร้างวัตถุชั่วคราว สิ่งนี้สามารถสร้างความแตกต่างอย่างมากหากวัตถุของคุณมีราคาแพงในการสร้าง


8

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

ตัวอย่าง:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"

5
สำหรับประเภทดั้งเดิมเช่นจำนวนเต็มใช่ คุณได้ตรวจสอบเพื่อดูว่าความแตกต่างเป็นอย่างไรสำหรับ a std::map::iterator? แน่นอนว่าตัวดำเนินการทั้งสองแตกต่างกัน แต่ฉันอยากรู้ว่าคอมไพเลอร์จะเพิ่มประสิทธิภาพ postfix เป็นคำนำหน้าหรือไม่หากไม่ได้ใช้ผลลัพธ์ ฉันไม่คิดว่าจะได้รับอนุญาต - เนื่องจากเวอร์ชัน postfix อาจมีผลข้างเคียง
ก.ย.

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

6

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

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

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)

5

ฉันเห็นด้วยกับ @BeowulfOF แม้ว่าเพื่อความชัดเจนฉันจะสนับสนุนการแยกข้อความเพื่อให้ตรรกะนั้นชัดเจนอย่างแน่นอนเช่น:

i++;
x += i;

หรือ

x += i;
i++;

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


2

เพียงแค่ต้องการเน้นย้ำอีกครั้งว่า ++ x คาดว่าจะเร็วกว่า x ++ (โดยเฉพาะอย่างยิ่งถ้า x เป็นอ็อบเจ็กต์บางประเภทโดยพลการ) ดังนั้นเว้นแต่ว่าจำเป็นด้วยเหตุผลเชิงตรรกะควรใช้ ++ x


2
ฉันแค่อยากจะเน้นว่าการเน้นนี้มีแนวโน้มที่จะทำให้เข้าใจผิด หากคุณกำลังดูลูปบางอันที่ลงท้ายด้วย "x ++" ที่แยกออกมาและคิดว่า "อ๊า! - นั่นคือเหตุผลที่มันทำงานช้ามาก!" และคุณเปลี่ยนเป็น "++ x" จากนั้นคาดว่าจะไม่มีความแตกต่างอย่างแน่นอน นักเพิ่มประสิทธิภาพฉลาดพอที่จะรับรู้ว่าไม่จำเป็นต้องสร้างตัวแปรชั่วคราวเมื่อไม่มีใครใช้ผลลัพธ์ของมัน ความหมายคือฐานรหัสเก่าที่เต็มไปด้วย "x ++" ควรถูกปล่อยให้อยู่ตามลำพังคุณมีแนวโน้มที่จะแนะนำข้อผิดพลาดในการเปลี่ยนแปลงข้อผิดพลาดมากกว่าการปรับปรุงประสิทธิภาพที่ใดก็ได้
omatai

1

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

ความแตกต่างที่สำคัญเมื่อจัดการกับ STL-Iterators (ซึ่งใช้ตัวดำเนินการเหล่านี้ด้วย) คือ ++ จะสร้างสำเนาของวัตถุที่ตัววนซ้ำชี้ไปแล้วเพิ่มขึ้นแล้วส่งคืนสำเนา ++ ในทางกลับกันจะทำการเพิ่มขึ้นก่อนจากนั้นส่งกลับการอ้างอิงไปยังวัตถุที่ตัววนซ้ำชี้ไป ส่วนใหญ่จะเกี่ยวข้องเมื่อประสิทธิภาพทุกบิตมีค่าหรือเมื่อคุณใช้ STL-iterator ของคุณเอง

แก้ไข: แก้ไขการผสมของคำนำหน้าและสัญกรณ์ต่อท้าย


การพูดถึง "before / after" การวนซ้ำจะมีความหมายก็ต่อเมื่อเกิดขึ้นในเงื่อนไขก่อน / หลัง บ่อยครั้งที่มันจะอยู่ในประโยคความต่อเนื่องซึ่งไม่สามารถเปลี่ยนตรรกะใด ๆ ได้แม้ว่าประเภทคลาสจะใช้ postfix ได้ช้าลงและผู้คนไม่ควรใช้สิ่งนั้นโดยไม่มีเหตุผล
underscore_d

1

รูปแบบของ Postfix ++ - ผู้ประกอบการเป็นไปตามกฎการใช้งานแล้วเปลี่ยน ,

รูปแบบคำนำหน้า (++ x - x) เป็นไปตามกฎการเปลี่ยนแปลงการใช้งานแล้ว

ตัวอย่างที่ 1:

เมื่อค่าหลายค่าเรียงกันด้วย << โดยใช้cout การคำนวณ (ถ้ามี) จะเกิดขึ้นจากขวาไปซ้าย แต่การพิมพ์จะเกิดจากซ้ายไปขวาเช่น (ถ้าvalถ้าเริ่มต้น 10)

 cout<< ++val<<" "<< val++<<" "<< val;

จะส่งผลให้

12    10    10 

ตัวอย่างที่ 2:

ใน Turbo C ++ หากพบการเกิดขึ้นหลายครั้งของ ++ หรือ (ในรูปแบบใด ๆ ) ในนิพจน์ประการแรกรูปแบบคำนำหน้าทั้งหมดจะถูกคำนวณจากนั้นนิพจน์จะได้รับการประเมินและสุดท้ายจะคำนวณรูปแบบ postfix เช่น

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

เอาต์พุตใน Turbo C ++ จะเป็น

48 13

ในขณะที่เอาต์พุตในคอมไพเลอร์สมัยใหม่จะเป็น (เพราะปฏิบัติตามกฎอย่างเคร่งครัด)

45 13
  • หมายเหตุ: ไม่แนะนำให้ใช้ตัวดำเนินการเพิ่ม / ลดหลายตัวกับตัวแปรเดียวกันในนิพจน์เดียว การจัดการ / ผลลัพธ์ของ
    นิพจน์ดังกล่าวแตกต่างกันไปในแต่ละคอมไพเลอร์ถึงคอมไพเลอร์

ไม่ใช่ว่านิพจน์ที่มีการดำเนินการ inc / ลดหลายรายการ "แตกต่างกันไปในแต่ละคอมไพเลอร์เป็นคอมไพเลอร์" แต่ที่แย่กว่านั้นคือการปรับเปลี่ยนหลาย ๆ ครั้งระหว่างจุดลำดับมีพฤติกรรมที่ไม่ได้กำหนดและทำให้โปรแกรมเป็นพิษ
underscore_d

0

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

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

เราต้องการให้ลูปทำงานผ่านการเผชิญหน้ากับอักขระศูนย์ (ซึ่งทดสอบเป็นเท็จ) ที่ส่วนท้ายของสตริง ที่ต้องทดสอบค่าก่อนการเพิ่มและการเพิ่มดัชนี แต่ไม่จำเป็นต้องอยู่ในลำดับนั้นวิธีการเขียนโค้ดด้วยส่วนเพิ่มล่วงหน้าจะเป็นดังนี้:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

เป็นเรื่องของรสชาติที่ชัดเจนกว่าและหากเครื่องมีรีจิสเตอร์ครบมือทั้งสองเครื่องควรมีเวลาดำเนินการเหมือนกันแม้ว่า [i] จะเป็นฟังก์ชันที่มีราคาแพงหรือมีผลข้างเคียงก็ตาม ความแตกต่างที่สำคัญอาจเป็นค่าทางออกของดัชนี

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