Cout neden bu kod parçacığında "2 + 3 = 15" yazdırıyor?


126

Aşağıdaki programın çıktısı neden nedir?

#include <iostream>
using namespace std;

int main(){

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

üretir

2+3 = 15

beklenen yerine

2+3 = 5

Bu soru zaten birden çok kapatma / yeniden açma döngüsüne gitti.

Kapanış için oy vermeden önce, lütfen bu konu hakkındaki bu meta tartışmayı düşünün .


96
;İlk çıktı satırının sonunda noktalı virgül olmasını istiyorsunuz , değil <<. Yazdırdığınızı düşündüğünüz şeyi yazdırmıyorsunuz. Yapıyorsun cout << cout, hangi baskıları 1kullanıyor ( cout.operator bool()sanırım). Sonra 5(kimden 2+3) hemen onu takip ederek on beş rakamı gibi görünür.
Igor Tandetnik

5
@StephanLechner Bu muhtemelen gcc4 kullanıyor. Özellikle gcc5'e kadar tam uyumlu akışlara sahip değillerdi, özellikle o zamana kadar dönüştürmeyi örtük olarak sürdürüyorlardı.
Baum mit Augen

4
@IgorTandetnik bu bir cevabın başlangıcı gibi geliyor. Görünüşe göre bu soruya ilk okunduğunda belli olmayan birçok incelik var.
Mark Ransom

14
İnsanlar neden bu soruyu kapatmak için oy vermeye devam ediyor? "Lütfen bana bu kodda neyin yanlış olduğunu söyleyin" değil, "Bu kod neden bu çıktıyı üretiyor?" Birincisinin cevabı "bir yazım hatası yaptınız", evet, ancak ikincisi derleyicinin kodu nasıl yorumladığına, bunun neden bir derleyici hatası olmadığına ve bir işaretçi adresi yerine nasıl "1" aldığına dair bir açıklama gerektirir.
jaggedSpire

6
@jaggedSpire Eğer bu bir yazım hatası değilse, o zaman çok kötü bir sorudur, çünkü kasıtlı olduğuna işaret etmeden, kasıtlı olarak tipografik bir hataya benzeyen alışılmadık bir yapı kullanır. Her iki durumda da yakın bir oylamayı hak ediyor. (Yazım hatası veya kötü / kötü niyetli olduğu için. Bu, başkalarını kandırmaya çalışan insanlar değil, yardım arayanlar için bir site.)
David Schwartz

Yanıtlar:


229

İster kasıtlı olarak ister kazayla, <<muhtemelen kastettiğiniz yerde ilk çıktı satırının sonuna sahipsiniz ;. Yani esasen sahipsin

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

Öyleyse soru şuna indirgeniyor: neden cout << cout;yazdırılıyor "1"?

Bu, belki de şaşırtıcı bir şekilde ince görünüyor. std::cout, Taban sınıfı ile std::basic_ios, içerir , belirli bir tür dönüşüm operatörü olduğu gibi, boolean bağlamında kullanılmak üzere tasarlanmıştır

while (cout) { PrintSomething(cout); }

Çıktının başarısız olması zor olduğu için bu oldukça zayıf bir örnektir - ancak std::basic_iosaslında hem giriş hem de çıkış akışları için temel bir sınıftır ve girdi için çok daha mantıklıdır:

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

(akışın sonunda veya akım karakterleri geçerli bir tam sayı oluşturmadığında döngüden çıkar).

Şimdi, bu dönüştürme operatörünün kesin tanımı, standardın C ++ 03 ve C ++ 11 sürümleri arasında değişti. Eski sürümlerde, operator void*() const;(genellikle olarak uygulanır return fail() ? NULL : this;), daha yeni sürümlerde explicit operator bool() const;(genellikle basitçe uygulanır return !fail();). Her iki bildirim de boole bağlamında iyi çalışır, ancak bu tür bağlamın dışında (yanlış) kullanıldığında farklı davranır.

Özellikle, C ++ 03 kuralları altında, bir adres cout << coutolarak yorumlanacak cout << cout.operator void*()ve bir adres yazdırılacaktır. C ++ 11 kurallarına göre, cout << coutoperatör bildirildiği explicitve dolayısıyla örtük dönüştürmelere katılamayacağı için hiç derlenmemelidir . Aslında değişimin birincil motivasyonu buydu - anlamsız kodların derlenmesini engellemek. Her iki standarda da uyan bir derleyici, yazdıran bir program üretmez "1".

Görünüşe göre, bazı C ++ uygulamaları, derleyici ve kitaplığı uygun olmayan sonuç üretecek şekilde karıştırmaya ve eşleştirmeye izin veriyor (@StephanLechner'dan alıntı: "xcode'da 1 üreten bir ayar ve bir adres veren başka bir ayar buldum: Dil lehçesi "Standart kitaplık libc ++ (c ++ 11 destekli LLVM standart kitaplığı)" ile birleştirilen c ++ 98 1 verirken, libstdc (gnu c ++ standart kitaplığı) ile birleştirilen c ++ 98 bir adres verir; "). explicitDönüşüm operatörlerini (C ++ 11'de yenidir) anlamayan ve dönüşümü operator bool(). Böyle bir karışımla, basitçe ve baskı cout << coutolarak yorumlanmak mümkün hale gelir .cout << cout.operator bool()cout << true"1"


1
@TC Bu alanda C ++ 03 ve C ++ 98 arasında bir fark olmadığından oldukça eminim. Sorunları açıklığa kavuşturmaya yardımcı olacaksa, C ++ 03'ün tüm sözlerini "pre-C ++ 11" ile değiştirebilirim. Linux ve diğerlerinde derleyici ve kitaplık sürümlemesinin karmaşıklıklarına hiç aşina değilim; Windows / MSVC adamıyım.
Igor Tandetnik

4
C ++ 03 ve C ++ 98 arasında nitelemeye çalışmıyordum; asıl mesele, libc ++ 'nın C ++ 11 ve yalnızca daha yeni olmasıdır; C ++ 98 / 03'e uymaya çalışmaz.
TC

45

Igor'un dediği gibi, bunu bir C ++ 11 kitaplığı ile elde edersiniz, burada std::basic_iosthe operator boolyerine vardır operator void*, ancak bir şekilde bildirilmez (veya olarak değerlendirilmez) explicit. Doğru beyan için buraya bakın .

Örneğin, uyumlu bir C ++ 11 derleyicisi ile aynı sonucu verecektir.

#include <iostream>
using namespace std;

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

ama sizin durumunuzda, static_cast<bool>örtük bir dönüştürme olarak (yanlış bir şekilde) izin verilmektedir.


Düzenleme: Bu olağan veya beklenen bir davranış olmadığından, platformunuzu, derleyici sürümünü vb. Bilmek yararlı olabilir.


Düzenleme 2: Referans için kod genellikle şu şekilde yazılır

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

veya olarak

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

ve iki stili karıştırarak hatayı açığa çıkarıyor.


1
Önerilen ilk çözüm kodunuzda bir yazım hatası var. Çok fazla operatör.
eerorika

3
Şimdi ben de yapıyorum, bulaşıcı olmalı. Teşekkürler!
Yararsız

1
Ha! :) Cevabımın ilk düzenlemesinde noktalı virgül eklemeyi önerdim, ancak satırın sonundaki operatörü fark etmedim. Bence OP ile birlikte, bunun sahip olabileceği en önemli yazım hatası permütasyonlarını oluşturduk.
eerorika

21

Beklenmeyen çıktının nedeni bir yazım hatasıdır. Muhtemelen demek istedin

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

Beklenen çıktıya sahip dizeleri göz ardı edersek, kalırız:

cout << cout;

C ++ 11'den beri, bu biçimsizdir. std::coutörtülü bir şekilde std::basic_ostream<char>::operator<<(veya üye olmayan aşırı yüklemenin) kabul edeceği herhangi bir şeye dönüştürülemez . Bu nedenle, standartlara uygun bir derleyici en azından bunu yaptığınız için sizi uyarmalıdır. Derleyicim programınızı derlemeyi reddetti.

std::coutdönüştürülebilir olacaktır boolve akım girdi operatörünün bool aşırı yüklemesi 1'lik gözlemlenen çıktıya sahip olacaktır. Ancak, bu aşırı yük açıktır, bu nedenle örtük bir dönüşüme izin vermemelidir. Derleyici / standart kitaplık uygulamanızın kesinlikle standarda uymadığı görülüyor.

Ön-C ++ 11 standardında, bu iyi oluşturulmuştur. O zamanlar std::cout, void*akış girişi operatörü aşırı yüklemesine sahip örtük bir dönüştürme operatörü vardı . Ancak bunun çıktısı farklı olacaktır. std::coutnesnenin bellek adresini yazdırır .


11

Gönderilen kod, herhangi bir C ++ 11 (veya daha sonraki uyumlu derleyici) için derlenmemelidir, ancak C ++ 11 öncesi uygulamalarda bir uyarı bile olmadan derlenmelidir.

Aradaki fark, C ++ 11'in bir akışın dönüşümünü açık bir bool'a dönüştürmesidir:

C.2.15 Madde 27: Giriş / çıkış kitaplığı [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

Değişiklik: Mevcut boole dönüştürme işleçlerinde açık kullanımının belirtilmesi
Gerekçe: Amaçları netleştirin, geçici çözümlerden kaçının.
Orijinal özellik üzerindeki etki: Örtük boole dönüşümlerine dayanan geçerli C ++ 2003 kodu bu Uluslararası Standart ile derlenemez. Bu tür dönüşümler aşağıdaki koşullarda gerçekleşir:

  • bool türünde bir argüman alan bir işleve bir değer iletmek;
    ...

ostream operatörü << bir bool parametresiyle tanımlanır. Bool'a bir dönüşüm var olduğundan (ve açık olmadığından), C ++ 11 öncesi cout << coutçevrildi ve cout << true1 sonucunu verdi.

Ve C.2.15'e göre, bu artık C ++ 11'den başlayarak derlenmemelidir.


3
boolC ++ 03'te hiçbir dönüşüm yoktur , ancak std::basic_ios::operator void*()bir koşulun veya döngünün denetleyici ifadesi olarak anlamlı olan vardır .
Ben Voigt

7

Bu şekilde kodunuzun hatalarını kolayca ayıklayabilirsiniz. coutÇıktınızı kullandığınızda arabelleğe alınır, böylece şu şekilde analiz edebilirsiniz:

İlk oluşumunun couttamponu temsil ettiğini ve operatörün tamponun <<sonuna eklemeyi temsil ettiğini düşünün . <<Sizin durumunuzda, operatörün sonucu çıktı akışıdır cout. Şunlardan başlıyorsunuz:

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

Yukarıda belirtilen kuralları uyguladıktan sonra, aşağıdaki gibi bir dizi eylem alırsınız:

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

Daha önce de söylediğim gibi sonucu buffer.append()tampon. Başlangıçta tamponunuz boştur ve işlemek için aşağıdaki ifadeye sahipsiniz:

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

tampon: empty

Önce buffer.append("2+3 = "), verilen dizgeyi doğrudan tampona koyan ve haline gelen var buffer. Şimdi durumunuz şöyle görünüyor:

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

tampon: 2+3 = 

Bundan sonra ifadenizi analiz etmeye devam edersiniz coutve tamponun sonuna eklemek için bir argüman olarak karşılaşırsınız. coutOlarak kabul edilir 1Eğer ekler böylece 1sizin tampon sonuna. Şimdi bu durumdasın:

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

tampon: 2+3 = 1

Tamponda sahip olduğunuz sonraki şey 2 + 3ve toplama, çıktı operatöründen daha yüksek önceliğe sahip olduğundan, önce bu iki sayıyı ekleyeceksiniz ve sonra sonucu tampona koyacaksınız. Bundan sonra alırsınız:

Beyan: buffer.append(endl);

tampon: 2+3 = 15

Sonunda endltamponun sonuna değer eklersiniz ve şunlara sahip olursunuz:

Beyan:

tampon: 2+3 = 15\n

Bu işlemden sonra tampondaki karakterler tek tek tampondan standart çıktıya yazdırılır. Yani kodunuzun sonucu 2+3 = 15. Eğer bakarsak ek olsun 1dan coutyazdırmaya çalıştı. Ekstrenizden çıkararak << coutistediğiniz çıktıyı alacaksınız.


6
Bunların hepsi doğru (ve güzel bir şekilde biçimlendirilmiş) olmasına rağmen, sanırım soruyu soruyor. Sanırım soru "Neden en başta cout << coutüretim yapıyor 1?" ve ekleme operatörü zincirleme ile ilgili bir tartışmanın ortasında olduğunu iddia ettiniz.
Yararsız

1
Yine de güzel biçimlendirme için +1. Bunun ilk cevabınız olduğunu düşünürsek, yardım etmeye çalışmanız çok güzel :)
gldraphael
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.