Tôi đã nhìn thấy cả hai đang được sử dụng ở nhiều mẩu mã C #, và tôi muốn biết khi nào sử dụng i++
hay ++i
( i
là một biến số như int
, float
, double
, vv). Bất cứ ai biết điều này?
Tôi đã nhìn thấy cả hai đang được sử dụng ở nhiều mẩu mã C #, và tôi muốn biết khi nào sử dụng i++
hay ++i
( i
là một biến số như int
, float
, double
, vv). Bất cứ ai biết điều này?
Câu trả lời:
Điều kỳ lạ là có vẻ như hai câu trả lời khác không đánh vần ra và điều đó chắc chắn đáng nói:
i++
có nghĩa là 'cho tôi biết giá trị của i
, sau đó tăng lên'
++i
có nghĩa là 'tăng i
, sau đó cho tôi biết giá trị'
Họ là các toán tử gia tăng trước, tăng sau. Trong cả hai trường hợp, biến được tăng lên , nhưng nếu bạn lấy giá trị của cả hai biểu thức trong cùng một trường hợp, kết quả sẽ khác nhau.
Câu trả lời điển hình cho câu hỏi này, không may được đăng ở đây, là một trong những hoạt động còn lại "trước" và hoạt động còn lại thực hiện gia tăng "sau" các hoạt động còn lại. Mặc dù điều đó bằng trực giác có ý tưởng xuyên suốt, nhưng tuyên bố đó là trên mặt của nó hoàn toàn sai . Các chuỗi các sự kiện trong thời gian là vô cùng rõ ràng trong C #, và nó là một cách dứt khoát không là trường hợp mà các tiền tố (++ var) và postfix (var ++) phiên bản của ++ làm những điều theo một thứ tự khác nhau liên quan đến các hoạt động khác với.
Thật không ngạc nhiên khi bạn sẽ thấy rất nhiều câu trả lời sai cho câu hỏi này. Rất nhiều cuốn sách "dạy cho mình C #" cũng hiểu sai. Ngoài ra, cách C # làm nó khác với cách C làm điều đó. Nhiều người cho rằng C # và C là cùng một ngôn ngữ; họ không phải. Theo tôi, việc thiết kế các toán tử tăng và giảm trong C # giúp tránh các lỗi thiết kế của các toán tử này trong C.
Có hai câu hỏi phải được trả lời để xác định chính xác hoạt động của tiền tố và hậu tố ++ là gì trong C #. Câu hỏi đầu tiên là kết quả là gì? và câu hỏi thứ hai là khi nào tác dụng phụ của sự gia tăng diễn ra?
Không rõ câu trả lời cho một trong hai câu hỏi là gì, nhưng nó thực sự khá đơn giản một khi bạn nhìn thấy nó. Hãy để tôi đánh vần cho bạn chính xác những gì x ++ và ++ x làm cho một biến x.
Đối với dạng tiền tố (++ x):
Đối với dạng postfix (x ++):
Một số điều cần chú ý:
Đầu tiên, thứ tự của các sự kiện trong thời gian là hoàn toàn giống nhau trong cả hai trường hợp . Một lần nữa, hoàn toàn không phải là trường hợp thứ tự các sự kiện trong thời gian thay đổi giữa tiền tố và hậu tố. Hoàn toàn sai khi nói rằng việc đánh giá xảy ra trước các đánh giá khác hoặc sau các đánh giá khác. Các đánh giá xảy ra theo cùng một thứ tự trong cả hai trường hợp như bạn có thể thấy trong các bước từ 1 đến 4 giống hệt nhau. Sự khác biệt duy nhất là bước cuối cùng - cho dù kết quả là giá trị tạm thời hay giá trị gia tăng mới.
Bạn có thể dễ dàng chứng minh điều này với một ứng dụng bảng điều khiển C # đơn giản:
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);
}
}
Đây là kết quả ...
Test 1: ++x
Current:1 Passed-in:1
Test 2: x++
Current:2 Passed-in:1
Test 3: ++x
Current:3 Passed-in:3
Trong thử nghiệm đầu tiên, bạn có thể thấy rằng cả hai currentValue
và những gì được truyền vào TestMethod()
phần mở rộng đều hiển thị cùng một giá trị, như mong đợi.
Tuy nhiên, trong trường hợp thứ hai, mọi người sẽ cố gắng nói với bạn rằng sự gia tăng currentValue
xảy ra sau cuộc gọi đến TestMethod()
, nhưng như bạn có thể thấy từ kết quả, nó xảy ra trước cuộc gọi như được chỉ ra bởi kết quả 'Hiện tại: 2'.
Trong trường hợp này, đầu tiên giá trị của currentValue
được lưu trữ tạm thời. Tiếp theo, một phiên bản gia tăng của giá trị đó được lưu trữ trở lại currentValue
nhưng không chạm vào tạm thời vẫn lưu trữ giá trị ban đầu. Cuối cùng, tạm thời được chuyển đến TestMethod()
. Nếu gia số xảy ra sau cuộc gọi đến TestMethod()
thì nó sẽ viết ra cùng một giá trị không tăng hai lần, nhưng không.
Điều quan trọng cần lưu ý là giá trị được trả về từ cả hoạt động
currentValue++
và++currentValue
hoạt động dựa trên giá trị tạm thời và không phải là giá trị thực được lưu trữ trong biến tại thời điểm cả hai hoạt động thoát ra.Nhớ lại theo thứ tự các thao tác ở trên, hai bước đầu tiên sao chép giá trị hiện tại của biến vào tạm thời. Đó là những gì được sử dụng để tính giá trị trả lại; trong trường hợp của phiên bản tiền tố, giá trị tạm thời tăng lên trong khi trong trường hợp của phiên bản hậu tố, đó là giá trị trực tiếp / không tăng. Biến chính nó không được đọc lại sau khi lưu trữ ban đầu vào tạm thời.
Nói một cách đơn giản hơn, phiên bản postfix trả về giá trị được đọc từ biến (tức là giá trị tạm thời) trong khi phiên bản tiền tố trả về giá trị được ghi lại cho biến (tức là giá trị tăng của tạm thời). Không trả về giá trị của biến.
Điều này rất quan trọng để hiểu bởi vì chính biến đó có thể biến động và đã thay đổi trên một luồng khác, điều đó có nghĩa là giá trị trả về của các hoạt động đó có thể khác với giá trị hiện tại được lưu trữ trong biến.
Thật đáng ngạc nhiên khi mọi người rất bối rối về sự ưu tiên, sự kết hợp và thứ tự thực hiện các tác dụng phụ, tôi nghi ngờ chủ yếu bởi vì nó rất khó hiểu trong C. C # đã được thiết kế cẩn thận để ít gây nhầm lẫn trong tất cả các vấn đề này. Đối với một số phân tích bổ sung về các vấn đề này, bao gồm cả tôi chứng minh thêm về sự giả dối của ý tưởng rằng các hoạt động tiền tố và hậu tố "di chuyển công cụ theo thời gian" xem:
https://ericlippert.com/2009/08/10/precedence-vs-order-redux/
dẫn đến câu hỏi SO này:
int [] Array = {0}; int value = Array [Array [0] ++]; Giá trị = 1?
Bạn cũng có thể quan tâm đến các bài viết trước của tôi về chủ đề này:
https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/
và
https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/
và một trường hợp thú vị trong đó C làm cho khó lý luận về tính đúng đắn:
https://docs.microsoft.com/archive/bloss/ericlippert/bad-recursion-revisited
Ngoài ra, chúng tôi gặp phải các vấn đề tinh tế tương tự khi xem xét các hoạt động khác có tác dụng phụ, chẳng hạn như các bài tập đơn giản được xâu chuỗi:
https://docs.microsoft.com/archive/bloss/ericlippert/chained-simple-assignments-is-not-so-simple
Và đây là một bài viết thú vị về lý do tại sao các toán tử gia tăng dẫn đến các giá trị trong C # thay vì các biến :
Tại sao tôi không thể làm ++ i ++ bằng các ngôn ngữ giống như C?
i++
hoặc ++i
được sử dụng trong mã, những điều đang diễn ra trong nền chỉ là như vậy; trong nền . Tôi viết C # để leo lên các mức trừu tượng trên mức đang diễn ra ở cấp độ này, vì vậy nếu điều này thực sự quan trọng đối với mã C # của bạn, bạn có thể đã sử dụng sai ngôn ngữ.
i++;
) là trong for (int i = 0; i < x; i++)
... Và tôi rất rất hạnh phúc vì điều này (và tôi chưa từng sử dụng toán tử tiền tố). Nếu tôi phải viết một cái gì đó sẽ yêu cầu một lập trình viên cao cấp 2 phút để giải mã ... Chà ... tốt hơn là viết thêm một dòng mã hoặc giới thiệu một biến tạm thời :-) Tôi nghĩ rằng "bài báo" của bạn (Tôi đã thắng 'gọi đó là "câu trả lời") minh chứng cho sự lựa chọn của tôi :-)
Nếu bạn có:
int i = 10;
int x = ++i;
sau đó x
sẽ được 11
.
Nhưng nếu bạn có:
int i = 10;
int x = i++;
sau đó x
sẽ được 10
.
Lưu ý như Eric chỉ ra, sự gia tăng xảy ra cùng một lúc trong cả hai trường hợp, nhưng đó là giá trị được đưa ra do kết quả khác nhau (cảm ơn Eric!).
Nói chung, tôi thích sử dụng ++i
trừ khi có lý do chính đáng để không. Ví dụ, khi viết một vòng lặp, tôi thích sử dụng:
for (int i = 0; i < 10; ++i) {
}
Hoặc, nếu tôi chỉ cần tăng một biến, tôi muốn sử dụng:
++x;
Thông thường, cách này hay cách khác không có nhiều ý nghĩa và đi theo phong cách mã hóa, nhưng nếu bạn đang sử dụng các toán tử bên trong các bài tập khác (như trong ví dụ ban đầu của tôi), điều quan trọng là phải nhận thức được các tác dụng phụ tiềm ẩn.
i
cho tên biến chứ không phải var
là từ khóa 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.
Điều này có trả lời câu hỏi của bạn không?
Cách thức hoạt động của toán tử là nó được tăng lên cùng một lúc, nhưng nếu nó nằm trước một biến, biểu thức sẽ đánh giá với biến tăng / giảm:
int x = 0; //x is 0
int y = ++x; //x is 1 and y is 1
Nếu là sau biến, câu lệnh hiện tại sẽ được thực thi với biến ban đầu, như thể nó chưa được tăng / giảm:
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
Tôi đồng ý với dcp trong việc sử dụng tăng / giảm trước (++ x) trừ khi cần thiết. Thực sự lần duy nhất tôi sử dụng tăng / giảm sau là trong khi các vòng lặp hoặc vòng lặp đó. Các vòng lặp này giống nhau:
while (x < 5) //evaluates conditional statement
{
//some code
++x; //increments x
}
hoặc là
while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
//some code
}
Bạn cũng có thể làm điều này trong khi lập chỉ mục các mảng và như vậy:
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);
Vân vân...
Chỉ dành cho bản ghi, trong C ++, nếu bạn có thể sử dụng (nghĩa là) bạn không quan tâm đến thứ tự các thao tác (bạn chỉ muốn tăng hoặc giảm và sử dụng nó sau) thì toán tử tiền tố hiệu quả hơn vì nó không phải tạo một bản sao tạm thời của đối tượng. Thật không may, hầu hết mọi người sử dụng posfix (var ++) thay vì prefix (++ var), chỉ vì đó là những gì chúng ta đã học ban đầu. (Tôi đã được hỏi về điều này trong một cuộc phỏng vấn). Không chắc điều này có đúng trong C # không, nhưng tôi cho rằng nó sẽ như vậy.