Apa perbedaan antara i ++ dan ++ i?


204

Aku pernah melihat mereka berdua sedang digunakan dalam berbagai potongan kode C #, dan saya ingin tahu kapan harus menggunakan i++atau ++i( imenjadi variabel nomor seperti int, float, double, dll). Adakah yang tahu ini?


13
Kecuali jika tidak ada bedanya, Anda tidak boleh menggunakan keduanya, karena itu hanya akan membuat orang mengajukan pertanyaan yang sama dengan yang Anda tanyakan sekarang. "Don't make me think" berlaku untuk kode dan juga desain.
Instance Hunter


2
@Dor: Apakah Anda bahkan membaca tautan dalam komentar saya? Yang pertama adalah tentang C #, dan yang kedua adalah bahasa-agnostik dengan jawaban yang diterima yang berfokus pada C #.
gnovice

7
@ gadget, yang pertama bertanya tentang perbedaan kinerja sementara saya bertanya tentang perbedaan kode-bijaksana yang sebenarnya, yang kedua adalah tentang perbedaan dalam satu lingkaran sementara saya bertanya perbedaan secara umum, dan yang ketiga adalah tentang C ++.
Dlaor

1
Saya percaya ini bukan duplikat Perbedaan antara i ++ dan ++ i dalam satu lingkaran? - seperti yang dikatakan Dloar dalam komentarnya di atas , pertanyaan lain menanyakan secara khusus tentang penggunaan dalam satu loop.
chue x

Jawaban:


201

Anehnya, sepertinya dua jawaban lainnya tidak menjelaskannya, dan itu layak untuk dikatakan:


i++berarti 'beri tahu saya nilai i, lalu kenaikan'

++iberarti 'kenaikan i, lalu beri tahu saya nilai'


Mereka adalah operator pra-kenaikan, pasca kenaikan. Dalam kedua kasus, variabel bertambah , tetapi jika Anda mengambil nilai dari kedua ekspresi dalam kasus yang persis sama, hasilnya akan berbeda.


11
Ini sepertinya bertentangan dengan apa yang dikatakan Eric
Evan Carroll

65
Tidak ada pernyataan yang benar. Pertimbangkan pernyataan pertama Anda. i ++ sebenarnya berarti "simpan nilainya, tambahkan, simpan di i, lalu beri tahu saya nilai yang disimpan asli". Yaitu, penceritaan terjadi setelah penambahan, bukan sebelumnya seperti yang Anda nyatakan. Pertimbangkan pernyataan kedua. i ++ sebenarnya berarti "menyimpan nilai, menambahkannya, menyimpannya di saya, dan memberi tahu saya nilai yang bertambah". Cara Anda mengatakannya membuat tidak jelas apakah nilainya adalah nilai i, atau nilai yang ditugaskan untuk i; mereka bisa berbeda .
Eric Lippert

8
@ Eric, menurut saya bahkan dengan jawaban Anda, pernyataan kedua sudah pasti benar. Padahal dia mengecualikan beberapa langkah.
Evan Carroll

20
Tentu, sebagian besar waktu ceroboh, cara yang salah menggambarkan semantik operasional memberikan hasil yang sama dengan deskripsi yang tepat dan benar. Pertama, saya tidak melihat nilai yang meyakinkan dalam mendapatkan jawaban yang benar melalui penalaran yang salah, dan kedua, ya, saya telah melihat kode produksi yang membuat hal semacam ini salah. Saya mungkin mendapatkan setengah lusin pertanyaan setahun dari programmer nyata tentang mengapa ekspresi tertentu penuh dengan kenaikan dan penurunan dan susunan dereferensi tidak bekerja seperti yang mereka duga.
Eric Lippert

12
@supercat: Diskusi tentang C #, bukan C.
Thanatos

428

Jawaban khas untuk pertanyaan ini, sayangnya sudah diposting di sini, adalah bahwa seseorang melakukan kenaikan "sebelum" operasi yang tersisa dan yang lainnya melakukan kenaikan "setelah" operasi yang tersisa. Meskipun itu secara intuisi menyampaikan gagasan, pernyataan itu benar-benar salah . The urutan kejadian dalam waktu sangat baik didefinisikan dalam C #, dan itu adalah tegas tidak terjadi bahwa awalan (var ++) dan postfix (var ++) versi ++ melakukan hal-hal dalam urutan yang berbeda sehubungan dengan operasi lainnya.

Tidak mengherankan bahwa Anda akan melihat banyak jawaban yang salah untuk pertanyaan ini. Banyak sekali buku "ajarkan diri Anda C #" yang salah. Juga, cara C # melakukannya berbeda dari cara C melakukannya. Banyak orang beralasan seolah-olah C # dan C adalah bahasa yang sama; mereka tidak. Desain operator kenaikan dan penurunan dalam C # menurut pendapat saya menghindari cacat desain operator ini di C.

Ada dua pertanyaan yang harus dijawab untuk menentukan apa sebenarnya operasi awalan dan postfix ++ di C #. Pertanyaan pertama adalah apa hasilnya? dan pertanyaan kedua adalah kapan efek samping kenaikan itu terjadi?

Tidak jelas apa jawaban dari kedua pertanyaan itu, tetapi sebenarnya cukup sederhana begitu Anda melihatnya. Biarkan saya menguraikan untuk Anda apa yang dilakukan x ++ dan ++ x untuk variabel x.

Untuk formulir awalan (++ x):

  1. x dievaluasi untuk menghasilkan variabel
  2. Nilai variabel disalin ke lokasi sementara
  3. Nilai sementara ditingkatkan untuk menghasilkan nilai baru (bukan menimpa sementara!)
  4. Nilai baru disimpan dalam variabel
  5. Hasil operasi adalah nilai baru (yaitu nilai sementara yang meningkat)

Untuk bentuk postfix (x ++):

  1. x dievaluasi untuk menghasilkan variabel
  2. Nilai variabel disalin ke lokasi sementara
  3. Nilai sementara ditingkatkan untuk menghasilkan nilai baru (bukan menimpa sementara!)
  4. Nilai baru disimpan dalam variabel
  5. Hasil operasi adalah nilai sementara

Beberapa hal yang perlu diperhatikan:

Pertama, urutan peristiwa dalam waktu persis sama dalam kedua kasus . Sekali lagi, itu sama sekali tidak terjadi bahwa urutan peristiwa dalam waktu berubah antara awalan dan postfix. Adalah sepenuhnya salah untuk mengatakan bahwa evaluasi terjadi sebelum evaluasi lain atau setelah evaluasi lainnya. Evaluasi terjadi dalam urutan yang persis sama dalam kedua kasus seperti yang Anda lihat dengan langkah 1 hingga 4 identik. Satu- satunya perbedaan adalah langkah terakhir - apakah hasilnya adalah nilai sementara, atau baru, nilai tambah.

Anda dapat dengan mudah menunjukkan ini dengan aplikasi konsol C # sederhana:

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);
    }
}

Inilah hasilnya ...

Test 1: ++x
Current:1 Passed-in:1

Test 2: x++
Current:2 Passed-in:1

Test 3: ++x
Current:3 Passed-in:3

Di tes pertama, Anda dapat melihat bahwa keduanya currentValuedan apa yang diteruskan keTestMethod() ekstensi menunjukkan nilai yang sama, seperti yang diharapkan.

Namun, dalam kasus kedua, orang akan mencoba memberi tahu Anda bahwa kenaikan currentValueterjadi setelah panggilan TestMethod(), tetapi seperti yang Anda lihat dari hasilnya, itu terjadi sebelum panggilan seperti ditunjukkan oleh hasil 'Saat ini: 2'.

Dalam hal ini, pertama nilai currentValuedisimpan sementara. Selanjutnya, versi yang meningkat dari nilai tersebut disimpan kembali currentValuetetapi tanpa menyentuh sementara yang masih menyimpan nilai aslinya. Akhirnya sementara itu diteruskan ke TestMethod(). Jika kenaikan terjadi setelah panggilan ke TestMethod()maka akan menuliskan nilai yang sama, yang tidak bertambah dua kali, tetapi tidak.

Penting untuk dicatat bahwa nilai yang dikembalikan dari kedua currentValue++dan ++currentValueoperasi didasarkan pada nilai sementara dan bukan yang sebenarnya disimpan dalam variabel pada saat salah satu operasi keluar.

Ingat dalam urutan operasi di atas, dua langkah pertama menyalin nilai variabel saat itu ke variabel sementara. Itulah yang digunakan untuk menghitung nilai pengembalian; dalam kasus versi awalan, itu adalah nilai sementara yang meningkat sementara dalam kasus versi akhiran, itu nilai yang langsung / tidak bertambah. Variabel itu sendiri tidak dibaca lagi setelah penyimpanan awal menjadi sementara.

Sederhananya, versi postfix mengembalikan nilai yang telah dibaca dari variabel (yaitu nilai temporer) sedangkan versi awalan mengembalikan nilai yang ditulis kembali ke variabel (yaitu nilai temporer yang meningkat). Tidak mengembalikan nilai variabel.

Ini penting untuk dipahami karena variabel itu sendiri dapat berubah-ubah dan telah berubah pada utas lainnya yang berarti nilai pengembalian operasi tersebut dapat berbeda dari nilai saat ini yang disimpan dalam variabel.

Sangat mengejutkan bagi orang untuk menjadi sangat bingung tentang presedensi, asosiatif, dan urutan di mana efek samping dieksekusi, saya menduga sebagian besar karena itu sangat membingungkan dalam C. C # telah dirancang dengan hati-hati untuk menjadi kurang membingungkan dalam semua hal ini. Untuk beberapa analisis tambahan tentang masalah-masalah ini, termasuk saya lebih lanjut menunjukkan kepalsuan gagasan bahwa operasi awalan dan postfix "memindahkan barang dalam waktu" lihat:

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

yang mengarah pada pertanyaan SO ini:

int [] arr = {0}; int value = arr [arr [0] ++]; Nilai = 1?

Anda mungkin juga tertarik dengan artikel saya sebelumnya tentang masalah ini:

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

dan

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

dan kasus yang menarik di mana C mempersulit pemikiran tentang kebenaran:

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

Juga, kami mengalami masalah serupa yang serupa ketika mempertimbangkan operasi lain yang memiliki efek samping, seperti tugas sederhana dirantai:

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

Dan inilah pos yang menarik tentang mengapa operator kenaikan menghasilkan nilai dalam C # daripada dalam variabel :

Mengapa saya tidak bisa melakukan ++ i ++ dalam bahasa C-like?


4
+1: Untuk yang benar-benar penasaran, dapatkah Anda memberikan referensi tentang apa yang Anda maksud dengan "kekurangan desain operasi ini di C"?
Justin Ardini

21
@ Justin: Saya telah menambahkan beberapa tautan. Tetapi pada dasarnya: di C, Anda tidak memiliki jaminan apa pun tentang urutan apa yang terjadi pada waktunya. Compiler yang menyesuaikan dapat melakukan hal apa pun yang diinginkan ketika ada dua mutasi pada titik urutan yang sama, dan tidak perlu memberi tahu Anda bahwa Anda sedang melakukan sesuatu yang merupakan perilaku yang ditentukan implementasi. Ini mengarahkan orang untuk menulis kode berbahaya yang tidak dapat ditonton yang berfungsi pada beberapa kompiler dan melakukan sesuatu yang sama sekali berbeda pada yang lain.
Eric Lippert

14
Saya harus mengatakan bahwa untuk yang benar-benar penasaran, ini adalah pengetahuan yang baik, tetapi untuk aplikasi C # rata-rata, perbedaan antara kata-kata dalam jawaban lain dan hal-hal aktual yang terjadi jauh di bawah tingkat abstraksi bahasa yang benar-benar dibuatnya. tidak ada perbedaan. C # bukan assembler, dan dari 99,9% dari waktu i++atau ++idigunakan dalam kode, hal-hal yang terjadi di latar belakang hanya itu; di latar belakang . Saya menulis C # untuk naik ke level abstraksi di atas apa yang terjadi pada level ini, jadi jika ini benar-benar penting untuk kode C # Anda, Anda mungkin sudah menggunakan bahasa yang salah.
Tomas Aschan

24
@ Thomas: Pertama, saya khawatir tentang semua aplikasi C #, bukan hanya massa aplikasi rata-rata. Jumlah aplikasi non-rata-rata besar. Kedua, tidak ada bedanya kecuali dalam kasus di mana ia membuat perbedaan . Saya mendapat pertanyaan tentang hal ini berdasarkan bug nyata dalam kode nyata mungkin setengah lusin kali setahun. Ketiga, saya setuju dengan poin Anda yang lebih besar; seluruh gagasan ++ adalah ide tingkat rendah yang terlihat sangat aneh dalam kode modern. Ini benar-benar hanya ada di sana karena itu idiomatis untuk bahasa seperti C untuk memiliki operator seperti itu.
Eric Lippert

11
+1 tetapi saya harus mengatakan yang sebenarnya ... Sudah bertahun-tahun penggunaan satu-satunya operator postfix yang saya lakukan tidak sendirian di baris (jadi tidak i++;) di for (int i = 0; i < x; i++)... Dan saya sangat sangat senang dengan ini! (dan saya tidak pernah menggunakan operator awalan). Jika saya harus menulis sesuatu yang akan membutuhkan programmer senior 2 menit untuk menguraikan ... Yah ... Lebih baik menulis satu baris kode lagi atau memperkenalkan variabel sementara :-) Saya rasa "artikel" Anda (saya menang sebut saja "jawab") membuktikan pilihan saya :-)
xanatos

23

Jika Anda memiliki:

int i = 10;
int x = ++i;

maka xakan 11.

Tetapi jika Anda memiliki:

int i = 10;
int x = i++;

maka xakan 10.

Perhatikan sebagaimana Eric tunjukkan, kenaikan terjadi pada waktu yang sama dalam kedua kasus, tetapi nilai itulah yang diberikan sebagai hasil yang berbeda (terima kasih Eric!).

Secara umum, saya suka menggunakan ++ikecuali ada alasan bagus untuk tidak melakukannya. Misalnya, saat menulis loop, saya suka menggunakan:

for (int i = 0; i < 10; ++i) {
}

Atau, jika saya hanya perlu menambahkan variabel, saya suka menggunakan:

++x;

Biasanya, satu cara atau yang lain tidak memiliki banyak arti dan turun ke gaya pengkodean, tetapi jika Anda menggunakan operator di dalam tugas lain (seperti dalam contoh asli saya), penting untuk mengetahui potensi efek samping.


2
Anda mungkin dapat mengklarifikasi contoh Anda dengan meletakkan baris kode secara berurutan - yaitu, katakanlah "var x = 10; var y = ++ x;" dan "var x = 10; var y = x ++;" sebagai gantinya.
Tomas Aschan

21
Jawaban ini sangat salah. Ketika kenaikan terjadi tidak berubah sehubungan dengan operasi yang tersisa sehingga mengatakan bahwa dalam satu kasus itu dilakukan sebelum operasi yang tersisa dan dalam kasus lain itu dilakukan setelah operasi yang tersisa sangat menyesatkan. Peningkatan tersebut dilakukan pada saat yang sama dalam kedua kasus . Yang berbeda adalah nilai apa yang diberikan sebagai hasilnya , bukan ketika kenaikan dilakukan .
Eric Lippert

3
Saya telah mengedit ini untuk digunakan iuntuk nama variabel daripada varkarena kata kunci C #.
Jeff Yates

2
Jadi tanpa menetapkan variabel dan hanya menyatakan "i ++;" atau "++ i;" akan memberikan hasil yang sama persis atau saya salah?
Dlaor

1
@ Eric: Bisakah Anda mengklarifikasi mengapa kata-kata aslinya "berbahaya"? Itu memang mengarah ke penggunaan yang benar, bahkan jika rinciannya salah.
Justin Ardini

7
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.

Apakah ini menjawab pertanyaan Anda?


4
Orang dapat membayangkan bahwa kenaikan postfix dan pengurangan prefix tidak berpengaruh pada variabel: P
Andreas

5

Cara kerja operator adalah bahwa ia bertambah pada saat yang sama, tetapi jika sebelum variabel, ekspresi akan dievaluasi dengan variabel yang bertambah / dikurangi:

int x = 0;   //x is 0
int y = ++x; //x is 1 and y is 1

Jika setelah variabel, pernyataan saat ini akan dieksekusi dengan variabel asli, seolah-olah belum ditambahkan / dikurangi:

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

Saya setuju dengan dcp dalam menggunakan pre-increment / decrement (++ x) kecuali perlu. Sungguh satu-satunya waktu saya menggunakan post-increment / decrement adalah saat loop atau loop semacam itu. Loop-loop ini sama:

while (x < 5)  //evaluates conditional statement
{
    //some code
    ++x;       //increments x
}

atau

while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
    //some code
}

Anda juga dapat melakukan ini saat mengindeks array dan semacamnya:

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);

Dll, dll ...


4

Sebagai catatan saja, di C ++, jika Anda dapat menggunakan salah satu (yaitu) Anda tidak peduli tentang urutan operasi (Anda hanya ingin menambah atau mengurangi dan menggunakannya nanti) operator awalan lebih efisien karena tidak harus membuat salinan sementara objek. Sayangnya, kebanyakan orang menggunakan posfix (var ++) alih-alih prefix (++ var), hanya karena itulah yang kami pelajari pada awalnya. (Saya ditanya tentang hal ini dalam sebuah wawancara). Tidak yakin apakah ini benar dalam C #, tapi saya anggap itu benar.


2
Itu sebabnya saya menggunakan ++ i di c # ketika digunakan sendiri (mis. Tidak dalam ekspresi yang lebih besar). Setiap kali saya melihat ac # loop dengan i ++, saya menangis sedikit, tetapi sekarang saya tahu bahwa di c # ini sebenarnya cukup banyak kode yang sama saya merasa lebih baik. Secara pribadi saya masih akan menggunakan ++ i karena kebiasaan sulit.
Mikle
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.