Tại sao chúng ta có gia tăng postfix?


55

Tuyên bố miễn trừ trách nhiệm : Tôi biết rất rõ ngữ nghĩa của việc tăng tiền tố và hậu tố. Vì vậy xin đừng giải thích cho tôi cách họ làm việc.

Đọc các câu hỏi về stack stack, tôi không thể không nhận thấy rằng các lập trình viên bị nhầm lẫn bởi toán tử gia tăng postfix nhiều lần. Từ đây, câu hỏi sau đây được đặt ra: có trường hợp sử dụng nào trong đó việc tăng tiền tố cung cấp một lợi ích thực sự về chất lượng mã không?

Hãy để tôi làm rõ câu hỏi của tôi với một ví dụ. Đây là một triển khai siêu ngắn gọn của strcpy:

while (*dst++ = *src++);

Nhưng đó không chính xác là mã tự viết tài liệu nhất trong cuốn sách của tôi (và nó tạo ra hai cảnh báo khó chịu trên trình biên dịch lành mạnh). Vì vậy, những gì sai với sự thay thế sau đây?

while (*dst = *src)
{
    ++src;
    ++dst;
}

Sau đó chúng ta có thể thoát khỏi nhiệm vụ khó hiểu trong điều kiện và nhận được mã hoàn toàn không có cảnh báo:

while (*src != '\0')
{
    *dst = *src;
    ++src;
    ++dst;
}
*dst = '\0';

(Có, tôi biết srcdstsẽ có các giá trị kết thúc khác nhau trong các giải pháp thay thế này, nhưng vì strcpyngay lập tức trả về sau vòng lặp, nên trong trường hợp này không thành vấn đề.)

Có vẻ như mục đích của việc tăng tiền tố là làm cho mã càng ngắn càng tốt. Tôi chỉ đơn giản là không thấy làm thế nào đây là một cái gì đó chúng ta nên cố gắng. Nếu điều này ban đầu là về hiệu suất, nó vẫn còn liên quan đến ngày hôm nay?


7
Biến thể postfix là biến thể được sử dụng phổ biến nhất, vì vậy nếu bạn định tạo ra một trường hợp chống lại nó, tốt hơn là một trường hợp khá mạnh. Và ví dụ đầu tiên của bạn là một người đàn ông rơm, vì tôi nghi ngờ sẽ có ít người thực sự viết mã strcpyphương pháp theo cách này (vì những lý do bạn đã đề cập).
Robert Harvey

4
Tại sao có một trong hai?
James McNellis

31
Nếu chúng tôi loại bỏ mọi thứ khỏi ngôn ngữ có khả năng gây nhầm lẫn cho các lập trình viên, chúng tôi sẽ không có nhiều tính năng. Thực tế một cái gì đó không hữu ích cho bạn, hoặc chỉ rất hiếm khi hữu ích, không có nghĩa là nó nên bị bắn tỉa. Nếu nó không còn phù hợp nữa, đừng sử dụng nó; kết thúc.
Cody Grey

4
Vâng nhưng sau đó bạn không bao giờ có thể làmint c = 0; c++;
Biff MaGriff

4
@Cody: Bởi vì một số thứ vừa khó hiểu vừa hữu ích. Anh ấy không bối rối bởi sự gia tăng sau đó, anh ấy bối rối về tính hữu dụng của nó . Chúng ta không nên có những thứ vô dụng, khó hiểu hay không.
GManNickG

Câu trả lời:


23

Mặc dù nó đã từng có một số ý nghĩa về hiệu suất, tôi nghĩ lý do thực sự là để thể hiện ý định của bạn một cách sạch sẽ. Câu hỏi thực sự là liệu một cái gì đó while (*d++=*s++);thể hiện ý định rõ ràng hay không. IMO, nó có, và tôi thấy các lựa chọn thay thế mà bạn cung cấp ít rõ ràng hơn - nhưng đó có thể (dễ dàng) là kết quả của việc dành hàng thập kỷ để làm quen với cách mọi thứ được thực hiện. Sau khi học C từ K & R (vì có hầu như không có cuốn sách khác trên C vào thời điểm đó) có thể giúp quá.

Ở một mức độ nào đó, sự thật là sự căng thẳng được đánh giá ở mức độ lớn hơn nhiều trong mã cũ. Cá nhân, tôi nghĩ rằng đây phần lớn là một điều tốt - hiểu một vài dòng mã thường khá tầm thường; Điều khó khăn là hiểu được khối mã lớn. Các thử nghiệm và nghiên cứu đã cho thấy nhiều lần, rằng việc phù hợp với tất cả các mã trên màn hình cùng một lúc là một yếu tố chính để hiểu mã. Khi màn hình mở rộng, điều này dường như vẫn đúng, vì vậy việc giữ mã (hợp lý) vẫn còn có giá trị.

Tất nhiên là có thể quá nhiệt tình, nhưng tôi không nghĩ là vậy. Cụ thể, tôi nghĩ rằng nó quá mức khi việc hiểu một dòng mã trở nên cực kỳ khó khăn hoặc tốn thời gian - cụ thể, khi hiểu ít dòng mã hơn sẽ tiêu tốn nhiều công sức hơn là hiểu nhiều dòng hơn. Điều đó thường xuyên xảy ra ở Lisp và APL, nhưng dường như (ít nhất là với tôi) là trường hợp ở đây.

Tôi ít quan tâm đến các cảnh báo của trình biên dịch - đó là kinh nghiệm của tôi khi nhiều trình biên dịch phát ra các cảnh báo hoàn toàn vô lý trên cơ sở khá thường xuyên. Mặc dù tôi chắc chắn nghĩ rằng mọi người nên hiểu mã của họ (và bất kỳ cảnh báo nào nó có thể tạo ra), mã tốt xảy ra để kích hoạt cảnh báo trong một số trình biên dịch không nhất thiết là sai. Phải thừa nhận rằng, người mới bắt đầu không phải lúc nào cũng biết những gì họ có thể bỏ qua một cách an toàn, nhưng chúng tôi không ở lại người mới bắt đầu và cũng không cần phải viết mã như chúng tôi.


12
+1 để chỉ ra rằng phiên bản ngắn gọn dễ đọc hơn đối với một số người trong chúng ta. :-)

1
Nếu bạn không thích một số cảnh báo về trình biên dịch của mình (và có nhiều hơn một vài cảnh báo tôi có thể làm mà không cần - nghiêm túc, tôi muốnif(x = y), tôi thề!) Bạn nên điều chỉnh các tùy chọn cảnh báo của trình biên dịch để bạn không vô tình bỏ lỡ cảnh báo bạn làm như thế.

4
@Brooks Moses: Tôi là một người rất nhiều ít tán về điều đó. Tôi không thích làm ô nhiễm mã của mình để bù đắp cho những thiếu sót của nhà soạn nhạc.
Jerry Coffin

1
@Jerry, @Chris: Chú thích này dành cho trường hợp bạn nghĩ cảnh báo thường là một điều tốt , nhưng bạn không muốn nó áp dụng cho một dòng cụ thể đang làm điều gì đó bất thường. Nếu bạn không bao giờ muốn cảnh báo và coi đó là một thiếu sót, hãy sử dụng -Wno-X, nhưng nếu bạn chỉ muốn tạo một ngoại lệ cho nó và muốn cảnh báo được áp dụng ở mọi nơi khác, thì điều này dễ hiểu hơn nhiều so với sử dụng nhảy vòng cung như Chris đề cập đến .

1
@Brooks: dấu ngoặc kép và các (void)xcảnh báo không sử dụng là những thành ngữ khá nổi tiếng để làm im lặng những cảnh báo hữu ích khác. Chú thích trông hơi giống pragmas, không thể mang theo tốt nhất: /
Matthieu M.

32

Đó là, lỗi, là, một điều phần cứng


Thú vị mà bạn sẽ nhận thấy. Sự gia tăng của hậu tố có lẽ là có một số lý do hoàn toàn chính đáng, nhưng giống như nhiều điều trong C, sự phổ biến của nó có thể được truy nguyên từ nguồn gốc của ngôn ngữ.

Mặc dù C được phát triển trên nhiều loại máy đầu tiên và không đủ mạnh, C và Unix lần đầu tiên đạt được thời gian lớn tương đối với các mô hình được quản lý bộ nhớ của PDP-11. Đây là những máy tính tương đối quan trọng trong ngày của họ và Unix là đến nay tốt hơn - theo cấp số nhân tốt hơn - hơn 7 hệ điều hành dể thương khác có sẵn cho -11.

Và, điều đó xảy ra, trên PDP-11,

*--p

*p++

... đã được thực hiện trong phần cứng như các chế độ địa chỉ. (Ngoài ra *p, nhưng không có sự kết hợp khác.) Trên những máy sớm, tất cả ít hơn 0,001 GHz, tiết kiệm một hai hướng dẫn hoặc trong một vòng lặp phải gần như đã chờ đợi-một-thứ hai hoặc chờ đợi-một phút hoặc đi-ra-cho -lunch khác biệt. Điều này không nói chính xác với postincrement, nhưng một vòng lặp với postincrement có thể tốt hơn rất nhiều so với lập chỉ mục trước đó.

Kết quả là, các mẫu thiết kế vì các thành ngữ C đã trở thành các macro tinh thần.

Nó giống như khai báo các biến ngay sau khi {... không phải vì C89 là hiện tại nên đây là một yêu cầu, nhưng giờ đây nó là một mẫu mã.

Cập nhật: Rõ ràng, lý do chính *p++là trong ngôn ngữ là bởi vì đó là chính xác những gì một người thường muốn làm. Sự phổ biến của mẫu mã được củng cố bởi phần cứng phổ biến đi kèm và phù hợp với mẫu đã có của ngôn ngữ được thiết kế một chút trước khi PDP-11 xuất hiện.

Ngày nay, không có sự khác biệt nào khi bạn sử dụng mẫu hoặc nếu bạn sử dụng lập chỉ mục và chúng tôi thường lập trình ở mức cao hơn, nhưng nó phải có vấn đề rất lớn trên các máy 0,001GHz đó và sử dụng bất cứ thứ gì khác ngoài *--xhoặc *x++có nghĩa là bạn đã không "lấy" PDP-11, và bạn có thể sẽ có người đến gặp bạn và nói "bạn có biết rằng ..." :-) :-)


25
Wikipedia nói: "Một huyền thoại dân gian (sai) là kiến ​​trúc tập lệnh của PDP-11 đã ảnh hưởng đến việc sử dụng thành ngữ của ngôn ngữ lập trình C. Các chế độ địa chỉ tăng và giảm của PDP-11 tương ứng với −−ii++cấu trúc trong C. If ijlà cả hai biến đăng ký, một biểu thức *(−−i) = *(j++)có thể được biên dịch thành một lệnh máy duy nhất. [...] Dennis Ritchie hoàn toàn mâu thuẫn với huyền thoại dân gian này. [Sự phát triển của ngôn ngữ C ] "
fredoverflow

3
Huh (Từ liên kết được cung cấp trong nhận xét của FredOverflow): "Mọi người thường đoán rằng chúng được tạo ra để sử dụng các chế độ địa chỉ tự động tăng và giảm tự động do DEC PDP-11 cung cấp, trong đó C và Unix lần đầu tiên trở nên phổ biến. không thể, vì không có PDP-11 khi B được phát triển. Tuy nhiên, PDP-7 đã có một vài ô nhớ 'tự động tăng', với đặc tính là tham chiếu bộ nhớ gián tiếp thông qua chúng làm tăng tế bào. đề nghị các nhà khai thác như vậy cho Thompson, việc khái quát hóa để biến cả hai tiền tố và tiền tố thành của riêng họ. "

3
DMR chỉ nói rằng PDP-11 không phải là lý do nó được thêm vào C. Tôi cũng nói vậy. Những gì tôi nói là nó đã được sử dụng để tận dụng PDP-11 và trở thành một mẫu thiết kế phổ biến vì lý do chính xác đó. Nếu Wikipedia mâu thuẫn với điều đó thì đơn giản là họ sai, nhưng tôi không đọc nó theo cách đó. Đó là cụm từ kém trên trang W đó.
DigitalRoss

3
@FredOverflow. Tôi nghĩ bạn đã hiểu nhầm chính xác "huyền thoại dân gian" là gì. Huyền thoại là C được thiết kế để khai thác 11 op đó, không phải chúng không tồn tại và không phải là mẫu mã không trở nên phổ biến vì mọi người đều biết rằng chúng đã ánh xạ tới 11 giếng. Trong trường hợp không rõ ràng: PDP-11 thực sự thực sự thực hiện hai chế độ đó trong phần cứng cho hầu hết mọi lệnh như một chế độ địa chỉ, và nó thực sự là máy C quan trọng đầu tiên chạy trên đó.
DigitalRoss

2
"nó hẳn đã có vấn đề rất lớn trên những cỗ máy 0,001GHz đó". Ừm, tôi ghét phải nói điều đó vì nó hẹn hò với tôi, nhưng tôi đã có hướng dẫn sử dụng Motorola và Intel cho CPU của tôi để tìm kích thước của hướng dẫn và số lần chúng thực hiện. Tìm các mã nhỏ nhất được thêm vào để có thể thêm một tính năng nữa vào bộ đệm in và lưu một vài chu kỳ có nghĩa là một cổng nối tiếp có thể theo kịp một số giao thức như MIDI mới được phát minh. C đã giảm bớt một số lựa chọn nit, đặc biệt là khi trình biên dịch được cải thiện, nhưng điều quan trọng là phải biết cách nào để viết mã hiệu quả nhất.
Tin Man

14

Tiền tố và hậu tố --++các nhà khai thác đã được giới thiệu trong các ngôn ngữ B (người tiền nhiệm C) bởi Ken Thompson - và không có, họ không được lấy cảm hứng từ PDP-11, trong đó không tồn tại vào thời điểm đó.

Trích dẫn từ "Sự phát triển của ngôn ngữ C" của Dennis Ritchie :

Thompson đã tiến một bước xa hơn bằng cách phát minh ra ++--toán tử, tăng hoặc giảm; vị trí tiền tố hoặc hậu tố của chúng xác định xem sự thay đổi xảy ra trước hay sau khi lưu ý giá trị của toán hạng. Chúng không phải là phiên bản đầu tiên của B, nhưng xuất hiện trên đường đi. Mọi người thường đoán rằng chúng được tạo ra để sử dụng các chế độ địa chỉ tự động tăng và giảm tự động được cung cấp bởi DEC PDP-11, trong đó C và Unix lần đầu tiên trở nên phổ biến. Điều này là không thể trong lịch sử, vì không có PDP-11 khi B được phát triển. Tuy nhiên, PDP-7 đã có một vài ô nhớ 'tự động tăng', với thuộc tính tham chiếu bộ nhớ gián tiếp thông qua chúng làm tăng ô. Tính năng này có thể gợi ý các nhà khai thác như vậy cho Thompson; khái quát hóa để làm cho cả hai tiền tố và hậu tố là của riêng mình. Thật,++xlà nhỏ hơn so với x=x+1.


8
Không, tôi không liên quan đến Ken Thompson.
Keith Thompson

Cảm ơn bạn đã tham khảo rất thú vị! Điều này xác nhận trích dẫn của tôi từ K & R ban đầu đề xuất mục tiêu của sự nhỏ gọn hơn là tối ưu hóa kỹ thuật. Tôi không biết rằng tuy nhiên các nhà khai thác đã tồn tại B.
Christophe

@BenPen Theo tôi biết không có chính sách đóng câu hỏi nếu có một bản sao trên một trang SE khác , miễn là cả hai câu hỏi đều phù hợp với các trang tương ứng của họ.
Ordous

Thú vị ... Lập trình viên không phải là một câu hỏi gần như chắc chắn.
BenPen

@BenPen: Câu trả lời được chấp nhận cho câu hỏi Stack Overflow đã trích dẫn cùng một nguồn mà tôi đã làm (mặc dù không nhiều như vậy).
Keith Thompson

6

Lý do rõ ràng để toán tử hậu sinh tồn tại là do bạn không phải viết các biểu thức như (++x,x-1)hoặc ở (x+=1,x-1)khắp mọi nơi hoặc tách các câu lệnh tầm thường vô dụng với các tác dụng phụ dễ hiểu thành nhiều câu lệnh. Dưới đây là một số ví dụ:

while (*s) x+=*s++-'0';

if (x) *s++='.';

Bất cứ khi nào đọc hoặc viết một chuỗi theo hướng chuyển tiếp, postincrement, và không preincrement, hầu như luôn luôn là hoạt động tự nhiên. Tôi gần như chưa bao giờ gặp phải việc sử dụng trong thế giới thực cho tiền sử dụng và việc sử dụng chính duy nhất tôi tìm thấy để dự đoán là chuyển đổi số thành chuỗi (vì hệ thống chữ viết của con người viết số ngược).

Chỉnh sửa: Trên thực tế, nó sẽ không quá xấu xí; thay vì (++x,x-1)bạn tất nhiên có thể sử dụng ++x-1hoặc (x+=1)-1. Vẫn x++dễ đọc hơn.


Bạn phải cẩn thận về những biểu thức đó vì bạn đang sửa đổi và đọc một biến giữa hai điểm chuỗi. Thứ tự mà các biểu thức được đánh giá không được đảm bảo.

@Snowman: Cái nào? Tôi thấy không có vấn đề như vậy trong câu trả lời của tôi.
R ..

nếu bạn có một cái gì đó như (++x,x-1)(gọi hàm, có thể?)

@Snowman: Đó không phải là một cuộc gọi chức năng. Đó là toán tử dấu phẩy C, là một điểm thứ tự, được ngoặc đơn để kiểm soát mức độ ưu tiên. Kết quả giống như x++trong hầu hết các trường hợp (một ngoại lệ: xlà loại không dấu có thứ hạng thấp hơn intvà giá trị ban đầu xlà giá trị tối đa của loại đó).
R ..

Ah, toán tử dấu phẩy phức tạp ... khi dấu phẩy không phải là dấu phẩy. Dấu ngoặc đơn và hiếm của toán tử dấu phẩy đã ném tôi đi. Đừng bận tâm!

4

PDP-11 cung cấp các hoạt động tăng sau và giảm trước trong tập lệnh. Bây giờ đây không phải là hướng dẫn. Chúng là các công cụ sửa đổi hướng dẫn cho phép bạn chỉ định rằng bạn muốn giá trị của một thanh ghi trước khi nó được tăng lên hoặc sau khi nó bị giảm.

Đây là một bước sao chép từ trong ngôn ngữ máy:

movw (r1)++,(r2)++

đã di chuyển một từ từ vị trí này sang vị trí khác, được chỉ ra bởi các thanh ghi và các thanh ghi sẽ sẵn sàng để làm lại từ đó.

Vì C được xây dựng trên và cho PDP-11, rất nhiều khái niệm hữu ích được tìm thấy trong C. C được dự định là một sự thay thế hữu ích cho ngôn ngữ trình biên dịch. Tăng trước và giảm sau được thêm vào cho đối xứng.


Đó là công cụ sửa đổi tuyệt vời ... Nó khiến tôi muốn học lắp ráp PDP-11. BTW, bạn có một tài liệu tham khảo về "cho đối xứng?" Nó có ý nghĩa, nhưng đây là lịch sử, đôi khi có ý nghĩa không phải là chìa khóa. LOL
BenPen

2
Còn được gọi là chế độ địa chỉ . PDP-11 cung cấp tăng sau và giảm trước kết hợp độc đáo với nhau cho các hoạt động ngăn xếp.
Erik Eidt

1
PDP-8 cung cấp một chỉ định bộ nhớ tăng sau, nhưng không có hoạt động giảm trước tương ứng. Với bộ nhớ lõi từ, việc đọc một từ bộ nhớ đã xóa sạch nó (!), Vì vậy bộ xử lý đã sử dụng chức năng ghi lại để khôi phục từ vừa đọc. Áp dụng một mức tăng trước khi viết lại xuất hiện khá tự nhiên và việc di chuyển khối hiệu quả hơn đã trở nên khả thi.
Erik Eidt

3
Xin lỗi, PDP-11 đã không truyền cảm hứng cho các nhà khai thác này. Nó chưa tồn tại khi Ken Thompson phát minh ra chúng. Xem câu trả lời của tôi .
Keith Thompson

3

Tôi nghi ngờ nó đã thực sự cần thiết. Theo như tôi biết, nó không biên dịch thành bất kỳ thứ gì nhỏ gọn hơn trên hầu hết các nền tảng hơn là sử dụng mức tăng trước như bạn đã làm, trong vòng lặp. Chỉ là tại thời điểm nó được tạo ra, độ chặt của mã quan trọng hơn sự rõ ràng của mã.

Điều đó không có nghĩa là sự rõ ràng của mã không (hoặc không) quan trọng, nhưng khi bạn nhập vào một modem baud thấp (bất cứ điều gì bạn đo trong baud đều chậm) trong đó mọi thao tác gõ phím phải làm cho nó máy tính lớn và sau đó được lặp lại một byte mỗi lần với một bit được sử dụng làm kiểm tra chẵn lẻ, bạn không muốn phải nhập nhiều.

Đây là loại giống như & và | toán tử có độ ưu tiên thấp hơn ==

Nó được thiết kế với mục đích tốt nhất (phiên bản ngắn mạch của && và ||), nhưng bây giờ nó gây nhầm lẫn cho các lập trình viên hàng ngày và có lẽ nó sẽ không bao giờ thay đổi.

Dù sao, đây chỉ là một câu trả lời mà tôi nghĩ rằng không có câu trả lời hay cho câu hỏi của bạn, nhưng có lẽ tôi sẽ được chứng minh là sai bởi một lập trình viên guru hơn tôi.

--BIÊN TẬP--

Tôi nên lưu ý rằng tôi thấy có cả hai cực kỳ hữu ích, tôi chỉ chỉ ra rằng nó sẽ không thay đổi cho dù có ai thích hay không.


Điều này thậm chí không đúng sự thật. Không có gì là "độ căng của mã" quan trọng hơn sự rõ ràng.
Cody Grey

Chà, tôi sẽ tranh luận rằng đó là một điểm tập trung nhiều hơn. Bạn đang hoàn toàn đúng, mặc dù nó chắc chắn không bao giờ được nhiều quan trọng hơn rõ ràng của mã ... để hầu hết mọi người.

6
Chà, định nghĩa của "terse" là "mượt mà tao nhã: bóng bẩy" hoặc "sử dụng một vài từ: không có sự thừa thãi", cả hai đều nói chung là một điều tốt khi viết mã. "Terse" không có nghĩa là "tối nghĩa, khó đọc và khó hiểu" như nhiều người nghĩ.
James McNellis

@James: Trong khi bạn đúng, tôi nghĩ hầu hết mọi người có nghĩa là định nghĩa thứ hai của "terse" khi sử dụng nó trong ngữ cảnh lập trình: "sử dụng một vài từ; không có sự thừa thãi". Theo định nghĩa đó, chắc chắn sẽ dễ dàng hơn để tưởng tượng các tình huống có sự đánh đổi giữa tính ngắn gọn và tính biểu cảm của một đoạn mã cụ thể. Rõ ràng điều đúng đắn cần làm khi viết mã cũng giống như khi nói: làm cho mọi thứ ngắn gọn như chúng cần, nhưng không ngắn hơn. Đánh giá sự căng thẳng hơn biểu cảm có thể dẫn đến mã trông khó hiểu, nhưng chắc chắn là không nên.
Cody Grey

1
tua lại 40 năm và tưởng tượng bạn phải điều chỉnh chương trình của mình trên chiếc trống đó chỉ chứa khoảng 1000 ký tự hoặc viết một trình biên dịch chỉ có thể hoạt động với 4 nghìn byte ram. Có những lúc căng thẳng và rõ ràng thậm chí không phải là một vấn đề. Bạn phải thật ngắn gọn, đã không tồn tại một máy tính trên hành tinh này có thể lưu trữ, chạy hoặc biên dịch chương trình không ngắn gọn của bạn.
số

3

Tôi thích công cụ khai vị postfix khi làm việc với con trỏ (cho dù tôi có bỏ qua chúng hay không) bởi vì

p++

đọc tự nhiên hơn là "di chuyển đến vị trí tiếp theo" so với tương đương

p += 1

mà chắc chắn phải nhầm lẫn người mới bắt đầu lần đầu tiên họ nhìn thấy nó được sử dụng với một con trỏ trong đó sizeof (* p)! = 1.

Bạn có chắc là bạn nêu chính xác vấn đề nhầm lẫn? Không phải điều gây nhầm lẫn cho người mới bắt đầu thực tế là toán tử postfix ++ có quyền ưu tiên cao hơn toán tử dereference * sao cho

*p++

phân tích như

*(p++)

và không

(*p)++

như một số người có thể mong đợi?

(Bạn nghĩ điều đó có tệ không? Xem phần Đọc Tuyên bố C. )


2

Trong số các yếu tố tinh tế của lập trình tốt là nội địa hóatối giản hóa :

  • đặt các biến trong một phạm vi sử dụng tối thiểu
  • sử dụng const khi không cần truy cập ghi
  • Vân vân.

Theo cùng một tinh thần, x++có thể được coi là một cách bản địa hóa một tham chiếu đến giá trị hiện tại xtrong khi ngay lập tức chỉ ra rằng bạn - ít nhất là bây giờ - đã hoàn thành với giá trị đó và muốn chuyển sang giá trị tiếp theo (hợp lệ cho dù xinthay một con trỏ hoặc lặp). Với một chút trí tưởng tượng, bạn có thể so sánh điều này với việc để giá trị / vị trí cũ xbị "vượt khỏi phạm vi" ngay lập tức sau khi không còn cần thiết và chuyển sang giá trị mới. Điểm chính xác nơi chuyển đổi có thể được đánh dấu bằng hậu tố++ . Hàm ý rằng đây có lẽ là lần sử dụng cuối cùng của "cái cũ" xcó thể là cái nhìn sâu sắc có giá trị về thuật toán, hỗ trợ người lập trình khi họ quét qua mã xung quanh. Tất nhiên, hậu tố++ có thể đặt lập trình viên tìm kiếm việc sử dụng giá trị mới, có thể tốt hay không tùy thuộc vào thời điểm giá trị mới đó thực sự cần thiết, vì vậy đó là khía cạnh của "nghệ thuật" hoặc "thủ công" của lập trình để xác định thêm gì nữa hữu ích trong hoàn cảnh.

Mặc dù nhiều người mới bắt đầu có thể bị nhầm lẫn bởi một tính năng, nhưng nó đáng để cân bằng với lợi ích lâu dài: người mới bắt đầu không ở lại người mới bắt đầu lâu.


2

Ồ, rất nhiều câu trả lời không hoàn toàn đúng (nếu tôi có thể quá táo bạo), và xin vui lòng tha thứ cho tôi nếu tôi chỉ ra điều hiển nhiên - đặc biệt là trong nhận xét của bạn để không chỉ ra ngữ nghĩa, nhưng rõ ràng (theo quan điểm của Stroustrup, tôi cho rằng) dường như chưa được đăng! :)

Postfix x++tạo ra một tạm thời được chuyển 'lên trên' trong biểu thức, trong khi biến xsau đó được tăng lên.

Tiền tố ++xkhông tạo ra một đối tượng tạm thời, nhưng tăng 'x' và chuyển kết quả cho biểu thức.

Giá trị là sự tiện lợi, cho những người biết điều hành.

Ví dụ:

int x = 1;
foo(x++); // == foo(1)
// x == 2: true

x = 1;
foo(++x); // == foo(2)
// x == 2: true

Tất nhiên, kết quả của ví dụ này có thể được tạo ra từ mã tương đương khác (và có lẽ ít xiên hơn).

Vậy tại sao chúng ta có toán tử postfix? Tôi đoán bởi vì đó là một thành ngữ vẫn tồn tại, bất chấp sự nhầm lẫn mà nó rõ ràng tạo ra. Đó là một cấu trúc di sản mà trước đây từng được coi là có giá trị, mặc dù tôi không chắc rằng giá trị cảm nhận đó là rất nhiều cho hiệu suất cũng như sự thuận tiện. Sự tiện lợi đó đã không bị mất, nhưng tôi nghĩ rằng sự đánh giá cao về khả năng đọc đã tăng lên, dẫn đến việc đặt câu hỏi về mục đích của nhà điều hành.

Chúng ta có cần toán tử postfix nữa không? Có khả năng là không. Kết quả của nó là khó hiểu và tạo ra một rào cản cho sự dễ hiểu. Một số lập trình viên giỏi chắc chắn sẽ biết ngay nơi tìm thấy nó hữu ích, thường là ở những nơi có vẻ đẹp "PERL-esque" đặc biệt. Chi phí của vẻ đẹp đó là khả năng đọc.

Tôi đồng ý rằng mã rõ ràng có lợi ích hơn độ căng, dễ đọc, dễ hiểu và khả năng duy trì. Nhà thiết kế và quản lý giỏi muốn điều đó.

Tuy nhiên, có một vẻ đẹp nhất định mà một số lập trình viên nhận ra rằng khuyến khích họ tạo mã bằng những thứ hoàn toàn đơn giản - chẳng hạn như toán tử postfix - mã mà họ chưa từng nghĩ đến - hoặc thấy kích thích trí tuệ. Có một cái gì đó cho nó chỉ làm cho nó đẹp hơn, nếu không muốn nói là hơn, mặc dù có lòng tốt.

Nói cách khác, một số người while (*dst++ = *src++);chỉ đơn giản là một giải pháp đẹp hơn, một cái gì đó lấy hơi thở của bạn bằng sự đơn giản của nó, giống như nó là một bàn chải để vẽ. Rằng bạn buộc phải hiểu ngôn ngữ để đánh giá cao vẻ đẹp chỉ làm tăng thêm sự tráng lệ của nó.


3
+1 "một số người tìm thấy trong khi (* dst ++ = * src ++); đơn giản là một giải pháp đẹp hơn". Chắc chắn rồi. Những người không hiểu ngôn ngữ lắp ráp không thực sự hiểu C thanh lịch như thế nào, nhưng tuyên bố mã cụ thể đó là một ngôi sao sáng.
Người đàn ông Tin

"Chúng ta có cần toán tử postfix nữa không? Có thể không." - Tôi không thể không nghĩ về máy Turing ở đây. Đã bao giờ chúng ta cần các biến tự động, forcâu lệnh, heck, thậm chí while( gotosau tất cả chúng ta có )?

@Bernd: Hà! Hãy tưởng tượng đóng cửa! Đóng cửa! Bê bối! lol
Brian M. Hunt

Đóng cửa! Thật nực cười làm sao. Chỉ cần cho tôi một cuộn băng! Nghiêm túc mà nói, điều tôi muốn nói là tôi không nghĩ / cần / một tính năng nên là tiêu chí quyết định chính (trừ khi bạn muốn thiết kế, nói, lisp). IME tôi sử dụng (nay, cần ) các toán tử postfix gần như độc quyền và chỉ hiếm khi cần tiền tố thay thế.

2

Hãy xem xét biện minh ban đầu của Kernighan & Ritchie (bản gốc của K & R trang 42 và 43):

Các khía cạnh khác thường là ++ và - có thể được sử dụng làm tiền tố hoặc tiền tố. (...) Trong bối cảnh không muốn có giá trị nào (..), hãy chọn tiền tố hoặc hậu tố theo sở thích. Nhưng htere là những tình huống mà người này hay người kia được đặc biệt kêu gọi.

Văn bản tiếp tục với một số ví dụ sử dụng gia số trong chỉ mục, với mục tiêu rõ ràng là viết mã " gọn hơn ". Vì vậy, lý do đằng sau các nhà khai thác này là sự tiện lợi của mã nhỏ gọn hơn.

Ba ví dụ được đưa ( squeeze(), getline()strcat()) chỉ sử dụng postfix trong biểu thức sử dụng lập chỉ mục. Các tác giả so sánh mã với một phiên bản dài hơn không sử dụng gia số nhúng. Điều này xác nhận rằng tập trung vào sự nhỏ gọn.

Điểm nổi bật của K & R trên trang 102, việc sử dụng các toán tử này kết hợp với hội thảo con trỏ (ví dụ *--p*p--). Không có ví dụ nào được đưa ra, nhưng một lần nữa, họ nói rõ rằng lợi ích là sự nhỏ gọn.

Ví dụ, tiền tố được sử dụng rất phổ biến khi giảm chỉ mục hoặc con trỏ nhìn chằm chằm từ cuối.

 int i=0; 
 while (i<10) 
     doit (i++);  // calls function for 0 to 9  
                  // i is 10 at this stage
 while (--i >=0) 
     doit (i);    // calls function from 9 to 0 

1

Tôi sẽ không đồng ý với tiền đề rằng ++p, p++bằng cách nào đó khó đọc hoặc không rõ ràng. Một nghĩa là "tăng p và sau đó đọc p", còn lại có nghĩa là "đọc p và sau đó tăng p". Trong cả hai trường hợp, toán tử trong mã chính xác là vị trí của nó trong phần giải thích mã, vì vậy nếu bạn biết ++nghĩa là gì , bạn sẽ biết mã kết quả có nghĩa là gì.

Bạn có thể tạo mã obfuscated sử dụng bất kỳ cú pháp, nhưng tôi không thấy một trường hợp ở đây là p++/ ++pvốn obfuscatory.


1

đây là từ POV của một kỹ sư điện:

có nhiều bộ xử lý có các toán tử tăng sau và giảm trước tích hợp cho mục đích duy trì ngăn xếp cuối cùng trước xuất trước (LIFO).

nó có thể giống như:

 float stack[4096];
 int stack_pointer = 0;

 ... 

 #define push_stack(arg) stack[stack_pointer++] = arg;
 #define pop_stack(arg) arg = stack[--stack_pointer];

 ...

i dunno, nhưng đó là lý do tôi mong đợi để thấy cả toán tử gia tăng tiền tố và hậu tố.


1

Để nhấn mạnh quan điểm của Barshe về sự nhỏ gọn, tôi muốn hiển thị cho bạn một số mã từ động cơ V6. Đây là một phần của trình biên dịch C gốc, từ http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/c/c00.c :

/*
 * Look up the identifier in symbuf in the symbol table.
 * If it hashes to the same spot as a keyword, try the keyword table
 * first.  An initial "." is ignored in the hash.
 * Return is a ptr to the symbol table entry.
 */
lookup()
{
    int ihash;
    register struct hshtab *rp;
    register char *sp, *np;

    ihash = 0;
    sp = symbuf;
    if (*sp=='.')
        sp++;
    while (sp<symbuf+ncps)
        ihash =+ *sp++;
    rp = &hshtab[ihash%hshsiz];
    if (rp->hflag&FKEYW)
        if (findkw())
            return(KEYW);
    while (*(np = rp->name)) {
        for (sp=symbuf; sp<symbuf+ncps;)
            if (*np++ != *sp++)
                goto no;
        csym = rp;
        return(NAME);
    no:
        if (++rp >= &hshtab[hshsiz])
            rp = hshtab;
    }
    if(++hshused >= hshsiz) {
        error("Symbol table overflow");
        exit(1);
    }
    rp->hclass = 0;
    rp->htype = 0;
    rp->hoffset = 0;
    rp->dimp = 0;
    rp->hflag =| xdflg;
    sp = symbuf;
    for (np=rp->name; sp<symbuf+ncps;)
        *np++ = *sp++;
    csym = rp;
    return(NAME);
}

Đây là mã được thông qua trong samizdat như một nền giáo dục theo phong cách tốt, nhưng chúng tôi sẽ không bao giờ viết nó như thế này ngày hôm nay. Hãy xem có bao nhiêu tác dụng phụ đã được đóng gói vào ifvà các whileđiều kiện, và ngược lại, làm thế nào cả hai forvòng lặp không có biểu thức gia tăng bởi vì điều đó được thực hiện trong thân vòng lặp. Nhìn vào cách các khối chỉ được bọc trong niềng răng khi thực sự cần thiết.

Một phần của điều này chắc chắn là tối ưu hóa vi mô thủ công mà chúng tôi mong đợi trình biên dịch sẽ thực hiện cho chúng tôi hôm nay, nhưng tôi cũng sẽ đưa ra giả thuyết rằng khi toàn bộ giao diện của bạn với máy tính là một tty kính 80x25, bạn muốn mã của mình càng dày đặc càng tốt để bạn có thể nhìn thấy nhiều hơn cùng một lúc.

(Sử dụng bộ đệm toàn cầu cho mọi thứ có lẽ là một sự nôn nao từ việc cắt răng của một người bằng ngôn ngữ lắp ráp.)


Xử lý cẩn thận: "ihash = + * sp ++;" Tại một số điểm C không chỉ có toán tử + = mà còn = +. Ngày nay, = + là một toán tử gán, theo sau là một dấu cộng đơn không làm gì cả, vì vậy nó tương đương với "ihash = * sp; sp + = 1;"
gnasher729

@ gnasher729 Có, và sau này rp->hflag =| xdflg, tôi tin rằng đó sẽ là lỗi cú pháp trên trình biên dịch hiện đại. "Tại một số điểm" chính xác là động cơ V6, như nó xảy ra; sự thay đổi để +=hình thành xảy ra trong V7. (Trình biên dịch V6 chỉ được chấp nhận =+, nếu tôi đọc mã này một cách chính xác - xem ký hiệu () trong cùng một tệp.)
zwol

-1

Có lẽ bạn cũng nên nghĩ về mỹ phẩm.

while( i++ )

được cho là "có vẻ tốt hơn"

while( i+=1 )

Vì một số lý do, toán tử tăng sau trông rất hấp dẫn. Nó ngắn, ngọt ngào và tăng mọi thứ lên 1. So sánh nó với tiền tố:

while( ++i )

"Nhìn ngược" phải không? Bạn không bao giờ thấy một dấu cộng ĐẦU TIÊN trong toán học, ngoại trừ khi ai đó ngớ ngẩn với việc chỉ định một cái gì đó tích cực ( +0hoặc một cái gì đó tương tự). Chúng ta đã từng thấy ĐỐI TƯỢNG + SOMETHING

Nó có phải là một cái gì đó để làm với phân loại? A, A +, A ++? Tôi có thể nói với bạn rằng ban đầu tôi rất vui khi người Python xóa operator++ngôn ngữ của họ!


1
Ví dụ của bạn không làm điều tương tự. i++trả về giá trị ban đầu của ii+=1++itrả về giá trị ban đầu của icộng 1. Vì bạn đang sử dụng các giá trị trả về cho luồng điều khiển, điều này rất quan trọng.
David Thornley

Vâng bạn đã đúng. Nhưng tôi chỉ nhìn vào khả năng đọc ở đây.
bobobobo

-2

Đếm ngược 9 đến 0, bao gồm:

for (unsigned int i=10; i-- > 0;)  {
    cout << i << " ";
}

Hoặc vòng lặp while tương đương.


1
Thế còn for (unsigned i = 9; i <= 9; --i)?
fredoverflow
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.