Mengapa cout mencetak “2 + 3 = 15” dalam potongan kode ini?


126

Mengapa output dari program di bawah ini apa itu?

#include <iostream>
using namespace std;

int main(){

    cout << "2+3 = " <<
    cout << 2 + 3 << endl;
}

menghasilkan

2+3 = 15

bukannya yang diharapkan

2+3 = 5

Pertanyaan ini sudah berulang kali tutup / buka kembali siklus.

Sebelum pemungutan suara ditutup, pertimbangkan diskusi meta ini tentang masalah ini.


96
Anda ingin titik koma ;di akhir garis output pertama, bukan <<. Anda tidak mencetak apa yang menurut Anda dicetak. Anda lakukan cout << cout, yang mencetak 1(menggunakan cout.operator bool(), saya pikir). Kemudian 5(dari 2+3) langsung mengikuti, membuatnya tampak seperti angka lima belas.
Igor Tandetnik

5
@StephanLechner Itu mungkin menggunakan gcc4 lalu. Mereka tidak memiliki aliran yang sepenuhnya sesuai sampai gcc5, khususnya, mereka masih memiliki konversi yang tersirat sampai saat itu.
Baum mit Augen

4
@IgorTandetnik yang terdengar seperti awal dari sebuah jawaban. Tampaknya ada banyak seluk-beluk untuk pertanyaan ini yang tidak terbukti saat pertama kali dibaca.
Mark Ransom

14
Mengapa orang terus memberikan suara untuk menutup pertanyaan ini? Itu bukan "Tolong beri tahu saya apa yang salah dengan kode ini," tetapi "Mengapa kode ini menghasilkan output ini?" Jawaban untuk yang pertama adalah "Anda membuat kesalahan ketik", ya, tapi yang kedua membutuhkan penjelasan tentang bagaimana kompiler menafsirkan kode, mengapa itu bukan kesalahan kompiler, dan bagaimana ia mendapatkan "1" bukannya alamat pointer.
jaggedSpire

6
@jaggedSpire Jika itu bukan kesalahan ketik, maka itu adalah pertanyaan yang sangat buruk karena kemudian dengan sengaja menggunakan konstruksi yang tidak biasa yang terlihat seperti kesalahan ketik tanpa menunjukkan bahwa itu disengaja. Either way, untuk mendapatkan suara dekat. (Baik karena kesalahan ketik atau buruk / jahat. Ini adalah situs untuk orang yang mencari bantuan, bukan orang yang mencoba menipu orang lain.)
David Schwartz

Jawaban:


229

Baik disengaja atau tidak, Anda memiliki <<di akhir garis output pertama, di mana Anda mungkin dimaksudkan ;. Jadi pada dasarnya Anda memilikinya

cout << "2+3 = ";  // this, of course, prints "2+3 = "
cout << cout;      // this prints "1"
cout << 2 + 3;     // this prints "5"
cout << endl;      // this finishes the line

Jadi pertanyaannya bermuara pada ini: mengapa cout << cout;cetak "1"?

Ini ternyata, mungkin mengejutkan, halus. std::cout, melalui kelas dasarnya std::basic_ios, menyediakan operator konversi tipe tertentu yang dimaksudkan untuk digunakan dalam konteks boolean, seperti pada

while (cout) { PrintSomething(cout); }

Ini adalah contoh yang sangat buruk, karena sulit untuk mendapatkan output gagal - tetapi std::basic_iossebenarnya adalah kelas dasar untuk input dan output stream, dan untuk input itu jauh lebih masuk akal:

int value;
while (cin >> value) { DoSomethingWith(value); }

(keluar dari loop di ujung aliran, atau ketika karakter aliran tidak membentuk bilangan bulat yang valid).

Sekarang, definisi pasti dari operator konversi ini telah berubah antara versi standar C ++ 03 dan C ++ 11. Di versi yang lebih lama, itu operator void*() const;(biasanya diimplementasikan sebagai return fail() ? NULL : this;), sedangkan di versi yang lebih baru explicit operator bool() const;(biasanya diimplementasikan sebagai return !fail();) Kedua deklarasi berfungsi dengan baik dalam konteks boolean, tetapi berperilaku berbeda ketika (mis) digunakan di luar konteks tersebut.

Secara khusus, di bawah aturan C ++ 03, cout << coutakan ditafsirkan sebagai cout << cout.operator void*()dan mencetak beberapa alamat. Di bawah aturan C ++ 11, cout << couttidak boleh dikompilasi sama sekali, karena operator dinyatakan explicitdan karenanya tidak dapat berpartisipasi dalam konversi tersirat. Itu sebenarnya adalah motivasi utama untuk perubahan - mencegah penyusunan kode yang tidak masuk akal. Kompiler yang sesuai dengan standar mana pun tidak akan menghasilkan program yang mencetak "1".

Rupanya, implementasi C ++ tertentu memungkinkan pencampuran dan pencocokan kompiler dan perpustakaan sedemikian rupa sehingga menghasilkan hasil yang tidak sesuai (mengutip @StephanLechner: "Saya menemukan pengaturan dalam xcode yang menghasilkan 1, dan pengaturan lain yang menghasilkan alamat: Dialek bahasa c ++ 98 dikombinasikan dengan "Perpustakaan standar libc ++ (perpustakaan standar LLVM dengan dukungan c ++ 11)" menghasilkan 1, sedangkan c ++ 98 dikombinasikan dengan libstdc (gnu c ++ perpustakaan standar) menghasilkan alamat; "). Anda dapat memiliki kompiler gaya C ++ 03 yang tidak memahami explicitoperator konversi (yang baru dalam C ++ 11) dikombinasikan dengan pustaka gaya C ++ 11 yang mendefinisikan konversi sebagai operator bool(). Dengan campuran seperti itu, menjadi mungkin untuk cout << coutdiartikan sebagai cout << cout.operator bool(), yang pada gilirannya sederhana cout << truedan dicetak "1".


1
@TC Saya cukup yakin tidak ada perbedaan antara C ++ 03 dan C ++ 98 di area khusus ini. Saya kira saya bisa mengganti semua menyebutkan C ++ 03 dengan "pre-C ++ 11", jika ini akan membantu memperjelas masalah. Saya sama sekali tidak akrab dengan seluk-beluk versi kompiler dan perpustakaan di Linux et al; Saya seorang pria Windows / MSVC.
Igor Tandetnik

4
Saya tidak mencoba melakukan nitpick antara C ++ 03 dan C ++ 98; intinya adalah bahwa libc ++ adalah C ++ 11 dan lebih baru saja; itu tidak mencoba untuk menyesuaikan diri dengan C ++ 98/03.
TC

45

Seperti kata Igor, Anda mendapatkan ini dengan pustaka C ++ 11, di mana std::basic_iosmemiliki operator boolbukan operator void*, tapi entah bagaimana tidak dideklarasikan (atau diperlakukan sebagai) explicit. Lihat di sini untuk deklarasi yang benar.

Sebagai contoh, kompiler C ++ 11 yang sesuai akan memberikan hasil yang sama dengan

#include <iostream>
using namespace std;

int main() {
    cout << "2+3 = " << 
    static_cast<bool>(cout) << 2 + 3 << endl;
}

tetapi dalam kasus Anda, static_cast<bool>sedang (salah) diizinkan sebagai konversi implisit.


Sunting: Karena ini tidak biasa atau perilaku yang diharapkan, mungkin berguna untuk mengetahui platform Anda, versi kompiler, dll.


Sunting 2: Untuk referensi, kode biasanya ditulis sebagai

    cout << "2+3 = "
         << 2 + 3 << endl;

atau sebagai

    cout << "2+3 = ";
    cout << 2 + 3 << endl;

dan itu mencampur dua gaya bersama yang mengekspos bug.


1
Ada kesalahan ketik dalam kode solusi yang disarankan pertama Anda. Satu operator terlalu banyak.
eerorika

3
Sekarang saya melakukannya juga, itu harus menular. Terima kasih!
berguna

1
Ha! :) Pada pengeditan awal jawaban saya, saya menyarankan untuk menambahkan titik koma, tetapi tidak menyadari operator di akhir baris. Saya pikir bersama dengan OP, kami telah menghasilkan permutasi yang paling signifikan dari kesalahan ketik ini.
eerorika

21

Alasan untuk output yang tidak terduga adalah kesalahan ketik. Anda mungkin bermaksud

cout << "2+3 = "
     << 2 + 3 << endl;

Jika kita mengabaikan string yang memiliki output yang diharapkan, kita dibiarkan dengan:

cout << cout;

Sejak C ++ 11, ini salah bentuk. std::couttidak secara implisit dikonversi ke apa pun yang std::basic_ostream<char>::operator<<(atau kelebihan anggota) akan menerima. Oleh karena itu, sebuah kompiler yang memenuhi standar setidaknya harus memperingatkan Anda untuk melakukan ini. Kompiler saya menolak untuk mengkompilasi program Anda.

std::coutakan dapat dikonversi menjadi bool, dan bool overload dari operator input stream akan memiliki output yang diamati dari 1. Namun, overload itu eksplisit, jadi seharusnya tidak memungkinkan konversi implisit. Tampaknya implementasi kompiler / perpustakaan standar Anda tidak sepenuhnya sesuai dengan standar.

Dalam standar pra-C ++ 11, ini terbentuk dengan baik. Saat itu std::coutmemiliki operator konversi implisit void*yang memiliki kelebihan input operator aliran. Namun output untuk itu akan berbeda. itu akan mencetak alamat memori std::coutobjek.


11

Kode yang dipasang tidak boleh mengkompilasi untuk C ++ 11 (atau kompiler konforman yang lebih baru), tetapi harus dikompilasi tanpa peringatan pada implementasi pra C ++ 11.

Perbedaannya adalah bahwa C ++ 11 membuat konversi aliran ke bool eksplisit:

C.2.15 Klausa 27: Pustaka input / output [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

Ubah: Tentukan penggunaan eksplisit di operator konversi boolean yang ada
Dasar Pemikiran: Jelaskan maksud, hindari solusi.
Efek pada fitur asli: Kode C ++ 2003 yang valid yang bergantung pada konversi boolean implisit akan gagal dikompilasi dengan Standar Internasional ini. Konversi semacam itu terjadi dalam kondisi berikut:

  • meneruskan nilai ke fungsi yang mengambil argumen tipe bool;
    ...

operator ostream << didefinisikan dengan parameter bool. Karena konversi ke bool ada (dan tidak eksplisit) adalah pra-C ++ 11, cout << coutditerjemahkan cout << trueyang menghasilkan 1.

Dan menurut C.2.15, ini seharusnya tidak lagi dikompilasi dimulai dengan C ++ 11.


3
Tidak ada konversi yang boolada di C ++ 03, namun ada std::basic_ios::operator void*()yang bermakna sebagai ekspresi pengontrol kondisional atau loop.
Ben Voigt

7

Anda dapat dengan mudah men-debug kode Anda dengan cara ini. Ketika Anda menggunakan coutoutput Anda buffered sehingga Anda dapat menganalisisnya seperti ini:

Bayangkan kemunculan pertama coutmewakili buffer dan operator <<mewakili menambahkan ke akhir buffer. Hasil operator <<adalah aliran output, dalam kasus Anda cout. Anda mulai dari:

cout << "2+3 = " << cout << 2 + 3 << endl;

Setelah menerapkan aturan yang disebutkan di atas Anda mendapatkan serangkaian tindakan seperti ini:

buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

Seperti yang saya katakan sebelumnya hasil buffer.append()buffer. Pada awalnya buffer Anda kosong dan Anda memiliki pernyataan berikut untuk diproses:

pernyataan: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

penyangga: empty

Pertama Anda memiliki buffer.append("2+3 = ")yang menempatkan string yang diberikan langsung ke buffer dan menjadi buffer. Sekarang negara Anda terlihat seperti ini:

pernyataan: buffer.append(cout).append(2 + 3).append(endl);

penyangga: 2+3 = 

Setelah itu Anda terus menganalisis pernyataan Anda dan muncul coutsebagai argumen untuk ditambahkan ke akhir buffer. The coutdiperlakukan sebagai 1sehingga Anda akan menambahkan 1ke akhir buffer Anda. Sekarang Anda berada di negara ini:

pernyataan: buffer.append(2 + 3).append(endl);

penyangga: 2+3 = 1

Hal berikutnya yang Anda miliki dalam buffer adalah 2 + 3dan karena penjumlahan memiliki prioritas lebih tinggi daripada operator keluaran, Anda akan menambahkan dua angka ini terlebih dahulu dan kemudian Anda akan memasukkan hasilnya dalam buffer. Setelah itu Anda dapatkan:

pernyataan: buffer.append(endl);

penyangga: 2+3 = 15

Akhirnya Anda menambahkan nilai endlke ujung buffer dan Anda memiliki:

pernyataan:

penyangga: 2+3 = 15\n

Setelah proses ini karakter dari buffer dicetak dari buffer ke output standar satu per satu. Jadi hasil dari kode Anda adalah 2+3 = 15. Jika Anda melihat ini, Anda mendapatkan tambahan 1dari coutAnda mencoba mencetak. Dengan menghapus << coutdari pernyataan Anda, Anda akan mendapatkan hasil yang diinginkan.


6
Meskipun ini semua benar (dan diformat dengan indah), saya pikir ini menimbulkan pertanyaan. Saya percaya pertanyaannya adalah "Mengapa harus cout << coutberproduksi 1?" , dan Anda baru saja menegaskan bahwa hal itu terjadi di tengah diskusi tentang rangkaian operator penyisipan.
berguna

1
+1 untuk pemformatan yang indah. Mempertimbangkan bahwa ini adalah jawaban pertama Anda, senang Anda mencoba membantu :)
gldraphael
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.