Lý do cho toán tử gia tăng (bài / trước) trong Java hoặc C #


8

Gần đây, tôi đã vấp phải câu hỏi về việc dự đoán đầu ra của mã sử dụng nhiều toán tử gia tăng sau / trước trên số nguyên. Tôi là một lập trình viên C có kinh nghiệm, vì vậy tôi cảm thấy như ở nhà, nhưng mọi người đã tuyên bố rằng Java chỉ sao chép một cách mù quáng từ C (hoặc C ++) và đây là một tính năng vô dụng trong Java.

Điều đó đang được nói, tôi không thể tìm thấy lý do chính đáng cho điều đó (đặc biệt là ghi nhớ sự khác biệt giữa các dạng bài đăng / mẫu trước) trong Java (hoặc C #), vì không giống như bạn sẽ thao tác các mảng và sử dụng chúng làm chuỗi trong Java . Ngoài ra, cách đây đã lâu khi tôi nhìn vào mã byte, vì vậy tôi không biết có INChoạt động không, nhưng tôi không thấy bất kỳ lý do nào tại sao điều này

for(int i = 0; i < n; i += 1)

có thể kém hiệu quả hơn

for(int i = 0; i < n; i++)

Có phần đặc biệt nào của ngôn ngữ mà điều này thực sự hữu ích không, hay đây chỉ là một tính năng để đưa các lập trình viên C vào thị trấn?


2
"Gosling muốn mã được viết bởi ai đó có kinh nghiệm trong C (không phải bằng Java) sẽ được thực thi bởi ai đó đã từng chạy PostScript trên NeWS ..."
gnat

7
@TimothyGroote Tôi không nghĩ đó là một đánh giá công bằng vì 1) mảng có thể thay đổi, String không; 2) bạn không thể gán Chuỗi cho char [] hoặc ngược lại. Chúng chắc chắn giống như mảng, chúng có khả năng được triển khai với các mảng và đủ dễ dàng để chuyển đổi sang và từ một mảng, nhưng chúng chắc chắn không giống nhau.
Doval

4
Có rất nhiều câu trả lời hay ở đây vì vậy tôi sẽ không nêu lại những gì người khác đã nói, ngoại trừ nhấn mạnh rằng các toán tử này là các tính năng kinh khủng. Chúng rất khó hiểu; sau hơn 25 năm tôi vẫn bị lẫn lộn trước và sau ngữ nghĩa. Họ khuyến khích những thói quen xấu như kết hợp đánh giá kết quả với sản xuất các tác dụng phụ. Nếu các tính năng này không có trong C / C ++ / Java / JavaScript / etc, thì chúng sẽ không được phát minh cho C #.
Eric Lippert

5
Để trả lời trực tiếp các khiếu nại mà người của bạn đưa ra: C # đã không sao chép một cách mù quáng bất cứ điều gì từ bất cứ ai. Mọi tính năng của C # đều được thiết kế vô cùng cẩn thận. Và đây không phải là những tính năng vô dụng ; họ có một công dụng
Eric Lippert

2
@EricLippert: Tôi nghĩ (hy vọng) hầu hết chúng ta có thể đồng ý rằng sự gia tăng trước / sau là khủng khiếp, nhưng trong bối cảnh đó ... tại sao chúng lại được đưa vào C #? Vì sự quen thuộc?
Phoshi

Câu trả lời:


19

đây chỉ là một tính năng để đưa các lập trình viên C vào thị trấn

Đây chắc chắn là một tính năng "mang các lập trình viên C (hoặc C ++) trong thị trấn", nhưng sử dụng từ "chỉ" ở đây đánh giá thấp giá trị của sự tương đồng như vậy.

  • nó làm cho mã (hoặc ít nhất là đoạn mã) dễ dàng chuyển từ C (hoặc C ++) sang Java hoặc C #

  • i++ít để gõ hơn i += 1x[i++]=0;thành ngữ hơn nhiềux[i]=0;i+=1;

Và đúng vậy, mã sử dụng nhiều toán tử tăng / giảm trước có thể khó đọc và duy trì, nhưng đối với bất kỳ mã nào mà các toán tử này bị lạm dụng, tôi sẽ không mong đợi chất lượng mã tăng mạnh ngay cả khi ngôn ngữ không cung cấp cho các toán tử này. Đó là bởi vì nếu bạn có các nhà phát triển không quan tâm đến khả năng bảo trì, họ sẽ luôn tìm cách viết mã khó hiểu.

Liên quan: Có sự khác biệt nào giữa các toán tử Java và C ++ không? Từ câu trả lời này, người ta có thể suy luận rằng bất kỳ hành vi toán tử nào được xác định rõ trong C đều giống nhau trong Java và Java chỉ thêm một số định nghĩa cho quyền ưu tiên của toán tử trong đó C có hành vi không xác định. Điều này không giống như sự trùng hợp, nó có vẻ khá cố ý.


10
"Nếu bạn có các nhà phát triển không quan tâm đến khả năng bảo trì, họ sẽ luôn tìm cách viết mã khó hiểu" - nói rất hay
Lovis

Bạn có đăng ký với niềm tin chung của các lập trình viên C rằng bạn chỉ có quá nhiều tổ hợp phím C trong bạn trước khi chết? Tiết kiệm rằng một tổ hợp phím có vẻ như là một sự đánh đổi kém với tôi.
Eric Lippert

@EricLippert: xin chào Eric, là một trong những người tạo ra C # tại sao bạn không khai sáng cho chúng tôi và cho chúng tôi biết một chút về động lực của bạn để giữ các toán tử trong C # tương tự như Java và C?
Doc Brown

@DocBrown: Câu trả lời của bạn áp dụng khá tốt cho C #. Tôi đã để lại một bình luận ở trên.
Eric Lippert

2
@DocBrown: x[i++]=0là thành ngữ trong C, nhưng tôi không đồng ý rằng nó là thành ngữ trong C # và Java. Mức độ thường xuyên bạn gặp phải các cụm từ như vậy, trừ khi bạn làm việc trong lĩnh vực kỹ thuật / toán học, trong đó, tôi cho rằng, không có quá 1% mã Java / C # trong tự nhiên?
Mladen Jablanović

6

Tôi sử dụng đôi khi sự gia tăng postfix trong câu lệnh return. Ví dụ, hãy xem xét điều này:

//Java
public class ArrayIterator implements Iterator<T> {
   private final T[] array;
   private int index = 0; 
   public ArrayIterator(T ... array) {
      this.array = array;
   }

   public T next {
      if (! hasNext()) throw new NoSuchElementException();
      return array[index++];   
   }
   ...
}

Bạn không thể làm điều này với index += 1. Vì vậy, ít nhất phiên bản postfix có thể giúp bạn tiết kiệm ngay bây giờ và sau đó một số dòng.

Điều đó nói rằng, các nhà khai thác này thực sự không cần thiết, và có thể gây nhầm lẫn cho mọi người. Tôi biết Scala đã quyết định chống lại các nhà khai thác này (bạn chỉ có +=-=, như bạn đề xuất), và do cách tiếp cận "cấp cao" hơn nên tôi không thực sự nhớ họ ở đó. Tôi hy vọng Java phát triển (chậm hơn) theo cùng một hướng.

Tuy nhiên, Java có một loạt các toán tử khác mà bạn không thường thấy "trong tự nhiên" (bạn đã từng sử dụng ^=hay >>>=chưa?) Và không ai quan tâm.


1
^=là một lý do phổ biến hợp lý. Bạn có thể sử dụng nó để lật cờ bitfield, hoán đổi hai số nguyên mà không có biến tạm thời và trong một số thuật toán băm / mật mã. Tôi thừa nhận rằng tôi chưa bao giờ sử dụng >>>=. (Sau đó, một lần nữa, tôi chưa bao giờ sử dụng >>>hoặc <<<trong bất kỳ chương trình sản xuất nào ...)
Brian S

4

Tôi đã xây dựng nhiều ngôn ngữ trong những năm qua, chủ yếu là mục đích đặc biệt. Điều gì xảy ra là bạn có một lượng lớn khán giả, và tất cả họ đều có kỳ vọng. Khi xem xét từng tính năng, bạn phải cân nhắc lợi ích của tính năng này so với chi phí có thể vi phạm các kỳ vọng.

Một thứ mà cá nhân tôi đã phải ủng hộ là phân chia số nguyên, thường cắt ngắn xuống, phải không? Giống như 3 / 2 = 1.5 = 1, phải không? Chà, quay trở lại vào những ngày đầu của Fortran, ai đó đã quyết định -3 / 2 = -1.5 = -1(không phải -2). Nói cách khác, nếu cổ tức là tiêu cực và số chia là tích cực, nó nên cắt ngắn lên . Lưu ý điều này ngụ ý - trong trường hợp đó phần còn lại là âm . Điều đó vi phạm giả định thông thường rằng phần còn lại = mô đun. Nếu bạn đang làm đồ họa, với tọa độ nguyên, điều này có thể dẫn đến tất cả các loại rắc rối. SO, bằng ngôn ngữ tôi chỉ cần có số nguyên truncate phân chia xuống tất cả các thời gian, và nói như vậy trong doc.

Và SO, đoán xem chuyện gì đã xảy ra? Tất cả h *** vỡ ra. Ngôn ngữ có một lỗi trong phân chia số nguyên!

Thật không tốt khi nói cách cũ đã bị phá vỡ. Thay đổi nó dễ hơn là tranh luận.

Vì vậy, để trả lời câu hỏi của bạn, trong các ngôn ngữ như C # và Java, việc bao gồm các toán tử ++ (mà cá nhân tôi thích) đơn giản hơn là giải thích tại sao không.


4
Một ví dụ khác, ngôn ngữ lập trình D (theo nhiều cách là bước tiến hóa tiếp theo từ C ++) có câu lệnh chuyển đổi và chúng giữ các breaks bắt buộc trong mỗi mệnh đề mặc dù ngôn ngữ không cho phép đi qua. Trong khi D đã loại bỏ khả năng tương thích ngược của C ++ với C, chính sách của họ là bất kỳ cấu trúc C nào mà D giữ sẽ gây ngạc nhiên cho lập trình viên C ít nhất có thể.
Doval

@Doval: Tôi thậm chí đã xây dựng một ngôn ngữ mà tôi gọi là D, như C nhưng với các tính năng OO và song song. Nó không đi đến đâu (thương xót) và không liên quan đến D mà bạn đang nói đến. Tôi làm mọi người phát điên trong C ?? bằng cách luôn luôn viết break; case ...;-)
Mike Dunlavey

1
@MikeDunlavey: Sở thích của tôi là nói rằng /sản lượng doublehoặc float(sử dụng doubletrong trường hợp có thể hoạt động được) và có các toán tử riêng biệt cho phân chia thả nổi, cắt ngắn và "làm bất cứ điều gì", cũng như mô đun, phần còn lại và "chia đều cho "[Cái sau mang lại Boolean]. Tôi sẽ khẳng định rằng bất kỳ kết quả nào int x=y/z;cũng có khả năng khác biệt đáng kể so với dự định, do đó, biểu thức không nên mang lại bất kỳ kết quả nào .
supercat

Các ngôn ngữ không có gì lạ khi có các toán tử riêng biệt cho int-split-by-int mang lại int hoặc dấu phẩy động. Ngoài ra, dấu hiệu của phần còn lại (ảnh hưởng đến thương số) thay đổi dữ dội. Xem en.wikipedia.org/wiki/Modulo_operation để biết danh sách các ngôn ngữ. Một phần của quyết định cho FORTRAN chắc chắn dựa trên phần cứng họ đã sử dụng (IBM pre-360?) Và cách nó hoạt động với cổ tức đã ký và / hoặc số chia.
Phil Perry

Tôi biết cả Pascal và Python tách biệt các "toán tử int" từ các toán tử "mang lại dấu phẩy động", mặc dù tôi chưa thấy bất kỳ cái nào tách ra các chế độ làm tròn. Tôi thấy thật sự khó hiểu khi mã muốn tính trung bình của ba số và có thể được viết bằng Python (a+b+c)//3trong hầu hết các ngôn ngữ được viết dưới dạng một mớ hỗn độn đáng ghét để xử lý ngữ nghĩa phân chia bị cắt cụt (đặc biệt là trên nhiều bộ xử lý , một phân chia nổi có thể được thực hiện nhanh hơn so với cắt ngắn!)
supercat

2

Yup, bản thân nó không có gì quan trọng, lợi ích duy nhất của nó là đường cú pháp giúp cho việc gõ dễ dàng hơn.

Lý do nó ở C ngay từ đầu chỉ là vì PDP-11 là cơ sở cho C, trở lại thời xưa, có một hướng dẫn đặc biệt để tăng thêm 1. Trong những ngày đó, sử dụng hướng dẫn này rất quan trọng đối với nhận được nhiều tốc độ ra khỏi hệ thống, do đó có tính năng ngôn ngữ.

Tuy nhiên, tôi nghĩ mọi người thích lệnh này đến mức họ sẽ phàn nàn nếu nó không có ở đó.


Để đảm bảo tính chính xác, PDP-11 không có hướng dẫn tăng trước hoặc tăng sau, nhưng có các chế độ xử lý địa chỉ cho chúng (và giảm dần), có nghĩa là bất kỳ lệnh nào truy cập bộ nhớ thông qua thanh ghi cũng có thể được đặt thành trước hoặc sau tăng hoặc giảm đăng ký cùng một lúc. Công nghệ trình biên dịch tại thời điểm đó thực sự không thể thực hiện điều đó một cách tự động thông qua tối ưu hóa, vì vậy hỗ trợ rõ ràng là cách duy nhất để làm cho nó hoạt động.
Jules

2

Đây là cú pháp thành ngữ trong C trong nhiều tình huống (chẳng hạn như forvòng lặp bạn đã trích dẫn), vì vậy như những người khác đã nói, nó có thể giúp những người đó chuyển sang Java như một ngôn ngữ mới và giúp việc chuyển mã kiểu C hiện có dễ dàng hơn. Vì những lý do đó, bao gồm cả quyết định tự nhiên được đưa ra trong những ngày đầu của Java để tăng tốc độ áp dụng ngôn ngữ mới. Bây giờ nó ít có ý nghĩa hơn, bởi vì mã nhỏ gọn hơn có chi phí yêu cầu các lập trình viên học cú pháp không thực sự cần thiết.

Những người thích nó tìm thấy nó thuận tiện và tự nhiên. Làm mà không có vẻ khó xử. Sử dụng nó không đạt được hiệu quả, đó là vấn đề dễ đọc. Nếu bạn nghĩ rằng các chương trình của bạn dễ đọc và duy trì hơn bằng cách sử dụng các toán tử này, hãy sử dụng chúng.

Ý nghĩa ban đầu trong C không chỉ là "thêm 1" hay "trừ 1". Đó không chỉ là đường cú pháp hay hiệu suất nhỏ. Toán tử trong C có nghĩa là "tăng (hoặc giảm) cho mục tiếp theo". Nếu bạn có một con trỏ tới một cái gì đó lớn hơn một byte và bạn bước đến địa chỉ tiếp theo như trong *pointer++, thì mức tăng thực tế có thể là 4, hoặc 8 hoặc bất cứ điều gì được yêu cầu. Trình biên dịch thực hiện công việc cho bạn, theo dõi kích thước của các mục dữ liệu và bỏ qua đúng số byte:

int k;
int *kpointer = &k;
*kpointer++ = 7;

Trên máy của tôi, cái này thêm 4 vào kpulum, nó không thêm 1. Đây là một trợ giúp thực sự trong C, ngăn ngừa lỗi và lưu các lệnh giả mạo của sizeof (). Không có số học con trỏ tương đương trong Java, vì vậy đây không phải là một điều cần cân nhắc. Ở đó, đó là về sức mạnh của biểu hiện hơn bất cứ điều gì khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.