Menambahkan C ++ - Kapan menggunakan x ++ atau ++ x?

93

Saat ini saya mempelajari C ++ dan saya telah mempelajari tentang inkrementasi beberapa waktu yang lalu. Saya tahu bahwa Anda dapat menggunakan "++ x" untuk membuat penambahan sebelum dan "x ++" untuk melakukannya setelahnya.

Namun, saya benar-benar tidak tahu kapan harus menggunakan salah satu dari keduanya ... Saya tidak pernah benar-benar menggunakan "++ x" dan sejauh ini semuanya selalu berfungsi dengan baik - jadi, kapan saya harus menggunakannya?

Contoh: Dalam perulangan for, kapan lebih baik menggunakan "++ x"?

Selain itu, dapatkah seseorang menjelaskan dengan tepat cara kerja incrementation (atau decrementation) yang berbeda? Aku akan sangat menghargainya.

Jesse Emond
sumber

Jawaban:

118

Ini bukan soal preferensi, tapi soal logika.

x++menambah nilai variabel x setelah memproses pernyataan saat ini.

++xmenambah nilai variabel x sebelum memproses pernyataan saat ini.

Jadi putuskan saja logika yang Anda tulis.

x += ++iakan menambah i dan menambahkan i + 1 ke x. x += i++akan menambahkan i ke x, lalu menambah i.

Oliver Friedrich
sumber
27
dan perhatikan bahwa dalam loop for, pada primatif, sama sekali tidak ada perbedaan. Banyak gaya pengkodean tidak akan merekomendasikan penggunaan operator increment karena dapat disalahpahami; yaitu, x ++ atau ++ x seharusnya hanya ada di barisnya sendiri, tidak pernah sebagai y = x ++. Secara pribadi, saya tidak suka ini, tapi ini tidak biasa
Mikeage
2
Dan jika digunakan pada barisnya sendiri, kode yang dihasilkan hampir pasti sama.
Nosredna
14
Ini mungkin tampak seperti kesederhanaan (terutama karena :)) tetapi di C ++, x++adalah nilai r dengan nilai xsebelum kenaikan, x++adalah nilai l dengan nilai xsetelah kenaikan. Tidak ada ekspresi yang menjamin saat nilai inkrementasi aktual disimpan kembali ke x, hanya dijamin bahwa itu terjadi sebelum titik urutan berikutnya. 'setelah memproses pernyataan saat ini' tidak sepenuhnya akurat karena beberapa ekspresi memiliki titik urutan dan beberapa pernyataan adalah pernyataan gabungan.
CB Bailey
10
Sebenarnya jawabannya menyesatkan. Titik waktu di mana variabel x dimodifikasi mungkin tidak berbeda dalam praktiknya. Perbedaannya adalah bahwa x ++ didefinisikan untuk mengembalikan nilai r dari nilai sebelumnya dari x sedangkan ++ x masih mengacu pada variabel x.
sellibitze
5
@ BeowulfOF: Jawabannya menyiratkan urutan yang tidak ada. Tidak ada dalam standar untuk mengatakan kapan kenaikan terjadi. Kompilator berhak mengimplementasikan "x + = i ++" sebagai: int j = i; i = i + 1; x + = j; "(yaitu. 'i' bertambah sebelum" memproses pernyataan saat ini "). Inilah sebabnya mengapa" i = i ++ "memiliki perilaku tidak terdefinisi dan itulah mengapa menurut saya jawabannya perlu" penyesuaian ". Deskripsi" x + = ++ i "benar karena tidak ada saran urutan:" akan menambah i dan menambahkan i + 1 ke x ".
Richard Corden
53

Scott Meyers memberitahu Anda untuk memilih prefiks kecuali pada saat-saat di mana logika akan menentukan bahwa postfix tepat.

"C ++ Lebih Efektif" item # 6 - itu otoritas yang cukup bagi saya.

Bagi mereka yang tidak memiliki buku tersebut, berikut adalah kutipan terkait. Dari halaman 32:

Dari hari-hari Anda sebagai pemrogram C, Anda mungkin ingat bahwa bentuk awalan dari operator kenaikan kadang-kadang disebut "kenaikan dan pengambilan", sedangkan formulir postfix sering disebut sebagai "pengambilan dan kenaikan." Kedua frasa itu penting untuk diingat, karena semuanya bertindak sebagai spesifikasi formal ...

Dan di halaman 34:

Jika Anda adalah tipe orang yang mengkhawatirkan efisiensi, Anda mungkin berkeringat saat pertama kali melihat fungsi kenaikan postfix. Fungsi itu harus membuat objek sementara untuk nilai kembaliannya dan implementasi di atas juga membuat objek sementara eksplisit yang harus dibangun dan dihancurkan. Fungsi kenaikan prefiks tidak memiliki temporer seperti itu ...

duffymo
sumber
4
Jika compiler tidak menyadari bahwa nilai sebelum increment adalah unnessescary, kompilator mungkin mengimplementasikan increment postfix dalam beberapa instruksi - salin nilai lama, lalu increment. Penambahan awalan harus selalu menjadi satu instruksi.
gnud
8
Saya kebetulan mengujinya kemarin dengan gcc: dalam perulangan for di mana nilainya dibuang setelah menjalankan i++atau ++i, kode yang dihasilkan sama.
Giorgio
Cobalah di luar for loop. Perilaku dalam penugasan harus berbeda.
duffymo
Saya secara eksplisit tidak setuju dengan Scott Meyers pada poin keduanya - biasanya tidak relevan karena 90% atau lebih kasus "x ++" atau "++ x" biasanya diisolasi dari tugas apa pun, dan pengoptimal cukup pintar untuk mengenali bahwa tidak ada variabel sementara yang perlu dibuat dalam kasus seperti itu. Dalam hal ini, kedua bentuk tersebut sepenuhnya dapat dipertukarkan. Implikasi dari hal ini adalah bahwa basis kode lama yang diisi dengan "x ++" harus dibiarkan sendiri - Anda lebih cenderung memunculkan kesalahan halus dengan mengubahnya menjadi "++ x" daripada meningkatkan kinerja di mana pun. Mungkin lebih baik menggunakan "x ++" dan membuat orang berpikir.
omatai
2
Anda dapat mempercayai Scott Meyers semua yang Anda inginkan, tetapi jika kode Anda sehingga kinerja tergantung bahwa setiap perbedaan kinerja antara ++xdan x++benar-benar penting, itu jauh lebih penting bahwa Anda benar-benar menggunakan compiler yang benar-benar dan benar dapat mengoptimalkan baik versi tidak peduli apa konteks. "Karena saya menggunakan palu tua yang jelek ini, saya hanya dapat memasukkan paku pada sudut 43,7 derajat" adalah argumen yang buruk untuk membangun rumah dengan memasukkan paku hanya pada 43,7 derajat. Gunakan alat yang lebih baik.
Andrew Henle
28

Dari cppreference saat menambahkan iterator:

Anda sebaiknya memilih operator pra-penambahan (++ iter) daripada operator pasca-kenaikan (iter ++) jika Anda tidak akan menggunakan nilai lama. Post-increment umumnya diterapkan sebagai berikut:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

Jelas, ini kurang efisien dibandingkan kenaikan sebelumnya.

Pre-increment tidak menghasilkan objek sementara. Ini dapat membuat perbedaan yang signifikan jika objek Anda mahal untuk dibuat.

Phillip Ngan
sumber
8

Saya hanya ingin memperhatikan bahwa kode genetika salah sama jika Anda menggunakan inkrementasi pra / posting di mana semantik (dari pra / posting) tidak masalah.

contoh:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"
chub
sumber
5
Untuk tipe primitif seperti integer, ya. Sudahkah Anda memeriksa untuk melihat apa perbedaan yang ternyata untuk sesuatu seperti a std::map::iterator? Tentu saja ada dua operator yang berbeda, tetapi saya penasaran apakah kompiler akan mengoptimalkan postfix ke prefix jika hasilnya tidak digunakan. Saya rasa itu tidak diperbolehkan - mengingat versi postfix dapat mengandung efek samping.
seh
Juga, ' kompilator mungkin akan menyadari bahwa Anda tidak memerlukan efek samping dan mengoptimalkannya ' seharusnya tidak menjadi alasan untuk menulis kode ceroboh yang menggunakan operator postfix yang lebih kompleks tanpa alasan apapun, selain dari mungkin fakta bahwa begitu banyak Seharusnya bahan ajar menggunakan postfix tanpa alasan yang jelas dan disalin secara grosir.
underscore_d
6

Hal terpenting yang perlu diingat, imo, adalah bahwa x ++ perlu mengembalikan nilai sebelum kenaikan benar-benar terjadi - oleh karena itu, ia harus membuat salinan sementara dari objek tersebut (kenaikan awal). Ini kurang efisien daripada ++ x, yang ditambahkan di tempat dan dikembalikan.

Hal lain yang perlu disebutkan, adalah bahwa sebagian besar kompiler akan dapat mengoptimalkan hal-hal yang tidak perlu jika memungkinkan, misalnya kedua opsi akan mengarah ke kode yang sama di sini:

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)
rmn
sumber
5

Saya setuju dengan @BeowulfOF, meskipun untuk kejelasan, saya akan selalu menganjurkan pemisahan pernyataan sehingga logikanya benar-benar jelas, yaitu:

i++;
x += i;

atau

x += i;
i++;

Jadi jawaban saya adalah jika Anda menulis kode yang jelas maka ini jarang menjadi masalah (dan jika itu penting maka kode Anda mungkin tidak cukup jelas).

Tawaran
sumber
2

Hanya ingin menekankan kembali bahwa ++ x diharapkan lebih cepat dari x ++, (terutama jika x adalah objek dari beberapa tipe arbitrer), jadi kecuali diperlukan untuk alasan logis, ++ x harus digunakan.

Shailesh Kumar
sumber
2
Saya hanya ingin menekankan bahwa penekanan ini kemungkinan besar menyesatkan. Jika Anda melihat beberapa loop yang diakhiri dengan "x ++" yang terisolasi dan berpikir "Aha! - itulah alasan mengapa ini berjalan sangat lambat!" dan Anda mengubahnya menjadi "++ x", maka tidak ada perbedaan yang diharapkan. Pengoptimal cukup pintar untuk mengenali bahwa tidak ada variabel sementara yang perlu dibuat ketika tidak ada yang akan menggunakan hasil mereka. Implikasinya adalah bahwa basis kode lama yang diisi dengan "x ++" harus dibiarkan sendiri - Anda lebih mungkin menyebabkan kesalahan saat mengubahnya daripada meningkatkan kinerja di mana saja.
omatai
1

Anda menjelaskan perbedaannya dengan benar. Itu hanya tergantung pada apakah Anda ingin x meningkat sebelum setiap proses melalui loop, atau setelah itu. Itu tergantung pada logika program Anda, apa yang sesuai.

Perbedaan penting saat berurusan dengan STL-Iterator (yang juga mengimplementasikan operator ini) adalah, bahwa ++ membuat salinan objek yang ditunjuk iterator, lalu menambah, dan kemudian mengembalikan salinannya. ++ itu di sisi lain melakukan penambahan terlebih dahulu dan kemudian mengembalikan referensi ke objek yang sekarang ditunjuk oleh iterator. Ini sebagian besar hanya relevan ketika setiap bit kinerja diperhitungkan atau saat Anda menerapkan iterator STL Anda sendiri.

Edit: memperbaiki campuran awalan dan notasi sufiks

Björn Pollex
sumber
Pembicaraan tentang "sebelum / sesudah" iterasi loop hanya bermakna jika pre / post inc / decrement terjadi dalam kondisi tersebut. Lebih sering, ini akan berada di klausa lanjutan, di mana ia tidak dapat mengubah logika apa pun, meskipun mungkin lebih lambat untuk tipe kelas menggunakan postfix dan orang tidak boleh menggunakannya tanpa alasan.
underscore_d
1

Bentuk postfix dari ++, - operator mengikuti aturan use-then-change ,

Bentuk awalan (++ x, - x) mengikuti aturan ubah-lalu-gunakan .

Contoh 1:

Ketika beberapa nilai di-cascade dengan << menggunakan cout maka perhitungan (jika ada) dilakukan dari kanan-ke-kiri tetapi pencetakan berlangsung dari kiri-ke-kanan misalnya, (jika val jika awalnya 10)

 cout<< ++val<<" "<< val++<<" "<< val;

akan menghasilkan

12    10    10 

Contoh 2:

Dalam Turbo C ++, jika beberapa kemunculan ++ atau (dalam bentuk apapun) ditemukan dalam sebuah ekspresi, maka pertama-tama semua bentuk prefiks dihitung kemudian ekspresi dievaluasi dan terakhir bentuk postfix dihitung misalnya,

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Outputnya di Turbo C ++ akan

48 13

Sedangkan keluarannya dalam kompiler modern adalah (karena mereka mengikuti aturan dengan ketat)

45 13
  • Catatan: Penggunaan lebih dari satu operator increment / decrement pada variabel yang sama dalam satu ekspresi tidak disarankan. Penanganan / hasil
    ekspresi tersebut bervariasi dari kompilator ke kompilator.
Sunil Dhillon
sumber
Ini bukan karena ekspresi yang berisi beberapa operasi inc / decrement "bervariasi dari kompiler ke kompiler", tetapi lebih buruk: beberapa modifikasi antara titik urutan memiliki perilaku yang tidak terdefinisi dan meracuni program.
underscore_d
0

Memahami sintaks bahasa penting saat mempertimbangkan kejelasan kode. Pertimbangkan untuk menyalin string karakter, misalnya dengan kenaikan pasca:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

Kami ingin loop dieksekusi melalui pertemuan dengan karakter nol (yang menguji false) di akhir string. Itu membutuhkan pengujian nilai pra-kenaikan dan juga menaikkan indeks. Tetapi tidak harus dalam urutan itu - cara untuk mengkodekan ini dengan kenaikan awal adalah:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

Ini masalah selera yang lebih jelas dan jika mesin memiliki beberapa register, keduanya harus memiliki waktu eksekusi yang sama, bahkan jika [i] adalah fungsi yang mahal atau memiliki efek samping. Perbedaan yang signifikan mungkin adalah nilai keluar dari indeks.

shkeyser
sumber