ผมเคยเห็นพวกเขาทั้งสองถูกนำมาใช้ในหลายชิ้นของรหัส C # และฉันต้องการที่จะทราบเมื่อมีการใช้i++
หรือ++i
( i
เป็นตัวแปรจำนวนเช่นint
, float
, double
ฯลฯ ) ใครที่รู้เรื่องนี้
ผมเคยเห็นพวกเขาทั้งสองถูกนำมาใช้ในหลายชิ้นของรหัส C # และฉันต้องการที่จะทราบเมื่อมีการใช้i++
หรือ++i
( i
เป็นตัวแปรจำนวนเช่นint
, float
, double
ฯลฯ ) ใครที่รู้เรื่องนี้
คำตอบ:
ดูเหมือนว่าคำตอบอีกสองคำไม่ได้สะกดออกมาและมันก็คุ้มที่จะพูดว่า:
i++
หมายถึง 'บอกฉันถึงคุณค่าของการi
เพิ่มขึ้นแล้ว'
++i
หมายถึง 'การเพิ่มขึ้นi
แล้วบอกค่า'
ตัวดำเนินการเป็นตัวดำเนินการล่วงหน้า ในทั้งสองกรณีตัวแปรจะเพิ่มขึ้นแต่ถ้าคุณต้องรับค่าของทั้งสองนิพจน์ในกรณีเดียวกันผลลัพธ์จะแตกต่างกันไป
คำตอบทั่วไปสำหรับคำถามนี้ซึ่งน่าเสียดายที่โพสต์ที่นี่แล้วคือการเพิ่ม "ก่อน" การดำเนินการที่เหลืออยู่และอีกการเพิ่ม "หลังจาก" การดำเนินการที่เหลืออยู่ แม้ว่าจะได้รับความคิดอย่างหยั่งรู้ แต่แถลงการณ์ดังกล่าวก็ต้องเผชิญกับความผิดทั้งหมด ลำดับของเหตุการณ์ในเวลาเป็นอย่างมากที่ดีที่กำหนดใน C # และมันก็เป็นอย่างเด่นชัดไม่ได้เป็นกรณีที่คำนำหน้า (var ++) และ postfix (var ++) รุ่น ++ ทำสิ่งที่อยู่ในลำดับที่แตกต่างเกี่ยวกับการดำเนินการอื่น ๆ
ไม่น่าแปลกใจเลยที่คุณจะเห็นคำตอบที่ผิดมากสำหรับคำถามนี้ หนังสือ "สอนตัวเอง C #" ที่ยอดเยี่ยมมากมายก็เข้าใจผิดเช่นกัน นอกจากนี้วิธีที่ C # ทำนั้นแตกต่างจากวิธีที่ C ทำ หลายคนคิดว่า C # และ C เป็นภาษาเดียวกัน พวกเขาจะไม่. การออกแบบตัวเพิ่มและลดตัวดำเนินการใน C # ในความคิดของฉันหลีกเลี่ยงข้อบกพร่องในการออกแบบของตัวดำเนินการเหล่านี้ใน C
มีคำถามสองข้อที่ต้องตอบเพื่อพิจารณาว่าการทำงานของคำนำหน้าและ postfix ++ เป็นอย่างไรใน C # คำถามแรกคือผลลัพธ์คืออะไร และคำถามที่สองคือเมื่อใดที่มีผลข้างเคียงของการเพิ่มขึ้นเกิดขึ้น?
ไม่ชัดเจนว่าคำตอบของทั้งสองคำถามคืออะไร แต่จริงๆแล้วมันค่อนข้างง่ายเมื่อคุณเห็น ให้ฉันสะกดให้คุณอย่างแม่นยำว่า x ++ และ ++ x ทำอะไรกับตัวแปร x
สำหรับแบบฟอร์มคำนำหน้า (++ x):
สำหรับแบบฟอร์ม postfix (x ++):
สิ่งที่ควรสังเกต:
ครั้งแรกลำดับของเหตุการณ์ที่เกิดขึ้นในเวลาที่จะเหมือนกันในทั้งสองกรณี อีกครั้งไม่ใช่กรณีที่ลำดับเหตุการณ์ในเวลาเปลี่ยนแปลงระหว่างคำนำหน้าและ postfix มันผิดทั้งหมดที่จะบอกว่าการประเมินเกิดขึ้นก่อนการประเมินอื่น ๆ หรือหลังการประเมินอื่น ๆ การประเมินเกิดขึ้นในลำดับเดียวกันในทั้งสองกรณีอย่างที่คุณเห็นในขั้นตอนที่ 1 ถึง 4 เหมือนกัน ความแตกต่างเพียงอย่างเดียวคือขั้นตอนสุดท้าย - ไม่ว่าผลลัพธ์จะเป็นค่าของชั่วคราวหรือค่าใหม่ที่เพิ่มขึ้น
คุณสามารถสาธิตสิ่งนี้ได้อย่างง่ายดายด้วยแอพคอนโซล C #:
public class Application
{
public static int currentValue = 0;
public static void Main()
{
Console.WriteLine("Test 1: ++x");
(++currentValue).TestMethod();
Console.WriteLine("\nTest 2: x++");
(currentValue++).TestMethod();
Console.WriteLine("\nTest 3: ++x");
(++currentValue).TestMethod();
Console.ReadKey();
}
}
public static class ExtensionMethods
{
public static void TestMethod(this int passedInValue)
{
Console.WriteLine("Current:{0} Passed-in:{1}",
Application.currentValue,
passedInValue);
}
}
นี่คือผลลัพธ์ ...
Test 1: ++x
Current:1 Passed-in:1
Test 2: x++
Current:2 Passed-in:1
Test 3: ++x
Current:3 Passed-in:3
ในการทดสอบครั้งแรกคุณจะเห็นว่าทั้งสองcurrentValue
และสิ่งที่ผ่านไปTestMethod()
ส่วนขยายแสดงค่าเดียวกันตามที่คาดไว้
อย่างไรก็ตามในกรณีที่สองผู้คนจะพยายามบอกคุณว่าการเพิ่มcurrentValue
ขึ้นเกิดขึ้นหลังจากการโทรTestMethod()
แต่อย่างที่คุณเห็นจากผลลัพธ์มันเกิดขึ้นก่อนการโทรดังที่ระบุไว้โดยผลลัพธ์ 'ปัจจุบัน: 2'
ในกรณีนี้ค่าแรกของcurrentValue
จะถูกเก็บไว้ในชั่วคราว ถัดไปเวอร์ชันที่เพิ่มขึ้นของค่านั้นจะถูกเก็บไว้ในcurrentValue
แต่ไม่ต้องสัมผัสชั่วคราวซึ่งยังคงเก็บค่าดั้งเดิมไว้ TestMethod()
สุดท้ายว่าชั่วคราวจะถูกส่งไป หากการเพิ่มขึ้นเกิดขึ้นหลังจากการเรียกไปTestMethod()
แล้วมันจะเขียนออกมาเหมือนกันไม่ใช่ค่าที่เพิ่มขึ้นสองครั้ง แต่มันไม่ได้
สิ่งสำคัญคือโปรดทราบว่าค่าที่ส่งคืนจากทั้งสอง
currentValue++
และ++currentValue
การดำเนินงานจะขึ้นอยู่กับชั่วคราวและไม่ใช่ค่าจริงที่เก็บไว้ในตัวแปรในเวลาที่การดำเนินการอย่างใดอย่างหนึ่งออกเรียกคืนตามลำดับการดำเนินการด้านบนสองขั้นตอนแรกคัดลอกค่าปัจจุบันของตัวแปรลงในชั่วคราว นั่นคือสิ่งที่ใช้ในการคำนวณค่าส่งคืน ในกรณีของคำนำหน้ารุ่นมันเป็นค่าชั่วคราวที่เพิ่มขึ้นในขณะที่ในกรณีของรุ่นต่อท้ายมันเป็นค่าที่โดยตรง / ไม่เพิ่มขึ้น ตัวแปรจะไม่ถูกอ่านอีกครั้งหลังจากหน่วยเก็บข้อมูลเริ่มต้นเป็นแบบชั่วคราว
กล่าวง่ายๆคือเวอร์ชัน postfix จะส่งคืนค่าที่อ่านจากตัวแปร (เช่นค่าของชั่วคราว) ในขณะที่รุ่นคำนำหน้าจะส่งคืนค่าที่ถูกเขียนกลับไปที่ตัวแปร (เช่นค่าที่เพิ่มขึ้นของชั่วคราว) ไม่ส่งคืนค่าของตัวแปร
สิ่งนี้เป็นสิ่งสำคัญที่ต้องเข้าใจเนื่องจากตัวแปรนั้นอาจเปลี่ยนแปลงได้และมีการเปลี่ยนแปลงในเธรดอื่นซึ่งหมายความว่าค่าส่งคืนของการดำเนินการเหล่านั้นอาจแตกต่างจากค่าปัจจุบันที่เก็บไว้ในตัวแปร
เป็นเรื่องปกติที่คนจะสับสนเกี่ยวกับความเป็นผู้นำการเชื่อมโยงและลำดับผลข้างเคียงที่ถูกประหารผมสงสัยว่าส่วนใหญ่เป็นเพราะมันสับสนใน C. C # ได้รับการออกแบบอย่างระมัดระวังเพื่อให้เกิดความสับสนน้อยลงในเรื่องทั้งหมดนี้ สำหรับการวิเคราะห์เพิ่มเติมของปัญหาเหล่านี้รวมถึงฉันแสดงให้เห็นถึงความผิดพลาดของแนวคิดที่การดำเนินการส่วนหน้าและส่วนท้ายของ "การย้ายสิ่งต่าง ๆ ในเวลา" ดูเพิ่มเติม:
https://ericlippert.com/2009/08/10/precedence-vs-order-redux/
ซึ่งนำไปสู่คำถาม SO นี้:
int [] arr = {0}; int value = arr [arr [0] ++]; ค่า = 1?
คุณอาจสนใจในบทความก่อนหน้าของฉันในเรื่อง:
https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/
และ
https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/
และกรณีที่น่าสนใจที่ C ทำให้มันยากที่จะให้เหตุผลเกี่ยวกับความถูกต้อง:
https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited
นอกจากนี้เราพบปัญหาที่ละเอียดอ่อนเช่นเดียวกันเมื่อพิจารณาการดำเนินการอื่น ๆ ที่มีผลข้างเคียงเช่นการมอบหมายอย่างง่าย:
https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple
และนี่คือโพสต์ที่น่าสนใจว่าทำไมตัวดำเนินการที่เพิ่มขึ้นส่งผลให้เกิดค่าใน C # แทนที่จะเป็นตัวแปร :
i++
หรือ++i
ถูกใช้ในโค้ดสิ่งต่าง ๆ ที่เกิดขึ้นในพื้นหลังก็เป็นเช่นนั้น ในพื้นหลัง ฉันเขียน C # เพื่อปีนขึ้นไปสู่ระดับที่เป็นนามธรรมเหนือสิ่งที่เกิดขึ้นในระดับนี้ดังนั้นหากสิ่งนี้สำคัญสำหรับรหัส C # ของคุณคุณอาจเป็นภาษาผิด
i++;
นั้น) อยู่ในfor (int i = 0; i < x; i++)
... และฉันก็เป็นอย่างมาก มีความสุขกับสิ่งนี้! (และฉันไม่เคยใช้โอเปอเรเตอร์คำนำหน้า) ถ้าฉันต้องเขียนสิ่งที่ต้องใช้โปรแกรมเมอร์ระดับสูง 2 นาทีในการถอดรหัส ... ดี ... ดีกว่าที่จะเขียนโค้ดอีกหนึ่งบรรทัดหรือแนะนำตัวแปรชั่วคราว :-) ฉันคิดว่า "บทความ" ของคุณ (ฉันชนะ ไม่เรียกว่า "คำตอบ") พิสูจน์ตัวเลือกของฉัน :-)
ถ้าคุณมี:
int i = 10;
int x = ++i;
แล้วจะเป็นx
11
แต่ถ้าคุณมี:
int i = 10;
int x = i++;
แล้วจะเป็นx
10
โปรดสังเกตว่า Eric ชี้ให้เห็นว่าการเพิ่มขึ้นนั้นเกิดขึ้นพร้อมกันในทั้งสองกรณี แต่เป็นค่าที่ได้รับเนื่องจากผลลัพธ์ที่แตกต่าง (ขอบคุณ Eric!)
โดยทั่วไปแล้วฉันชอบที่จะใช้++i
เว้นแต่จะมีเหตุผลที่ดีที่จะไม่ ตัวอย่างเช่นเมื่อเขียนลูปฉันชอบใช้:
for (int i = 0; i < 10; ++i) {
}
หรือถ้าฉันต้องการเพิ่มตัวแปรฉันชอบที่จะใช้:
++x;
โดยปกติแล้วไม่ทางใดก็ทางหนึ่งไม่ได้มีความสำคัญมากและมีสไตล์การเขียนโค้ด แต่ถ้าคุณใช้โอเปอเรเตอร์ในการมอบหมายอื่น ๆ (เช่นในตัวอย่างดั้งเดิมของฉัน) สิ่งสำคัญคือการตระหนักถึงผลข้างเคียงที่อาจเกิดขึ้น
i
สำหรับชื่อตัวแปรแทนที่จะvar
เป็นคำหลัก C #
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.
สิ่งนี้ตอบคำถามของคุณหรือไม่
วิธีการทำงานของโอเปอเรเตอร์คือมันได้รับการเพิ่มขึ้นในเวลาเดียวกัน แต่ถ้ามันอยู่ก่อนตัวแปรนิพจน์จะประเมินด้วยตัวแปรที่เพิ่ม / ลดลง:
int x = 0; //x is 0
int y = ++x; //x is 1 and y is 1
หากเป็นหลังตัวแปรคำสั่งปัจจุบันจะถูกเรียกใช้งานด้วยตัวแปรดั้งเดิมราวกับว่ายังไม่ได้ถูกเพิ่ม / ลดระดับ:
int x = 0; //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0
ฉันเห็นด้วยกับ dcp ในการใช้ pre-increment / decrement (++ x) เว้นแต่จำเป็น จริงๆครั้งเดียวที่ฉันใช้ post-increment / decrement อยู่ในขณะที่ลูปหรือลูปของการเรียงลำดับนั้น ลูปเหล่านี้เหมือนกัน:
while (x < 5) //evaluates conditional statement
{
//some code
++x; //increments x
}
หรือ
while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
//some code
}
คุณยังสามารถทำสิ่งนี้ได้ขณะทำดัชนีอาร์เรย์และเช่น:
int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678; //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);
ฯลฯ
สำหรับระเบียนใน C ++ หากคุณสามารถใช้ (เช่น) คุณไม่สนใจเกี่ยวกับการสั่งซื้อการดำเนินการ (คุณเพียงต้องการเพิ่มหรือลดและใช้ในภายหลัง) ตัวดำเนินการคำนำหน้าจะมีประสิทธิภาพมากกว่าเนื่องจากไม่ ต้องสร้างสำเนาชั่วคราวของวัตถุ น่าเสียดายที่คนส่วนใหญ่ใช้ posfix (var ++) แทนคำนำหน้า (++ var) เพียงเพราะนั่นคือสิ่งที่เราเรียนรู้ในตอนแรก (ฉันถูกถามเกี่ยวกับเรื่องนี้ในการสัมภาษณ์) ไม่แน่ใจว่านี่จะเป็นจริงใน C # แต่ฉันคิดว่ามันจะเป็น