2011-01-08 7 views
18

Es ist bekannt, dass der Benutzer Strom Manipulatoren wie folgt definieren:Wie funktionieren die Stream-Manipulatoren?

ostream& tab(ostream & output) 
{ 
    return output<< '\t'; 
} 

Und das kann in main() wie folgt verwendet werden:

diese
cout<<'a'<<tab<<'b'<<'c'<<endl; 

Bitte erklären Sie mir, wie tut Alle Arbeit? Wenn Operator < < als zweiten Parameter einen Zeiger auf die Funktion übernimmt, die ostream & übernimmt und zurückgibt, dann erläutern Sie mir bitte, warum es notwendig ist? Was wäre falsch, wenn die Funktion nicht und zurück nimmt Ostream & aber es war Leere statt Ostream &?

Auch ist es interessant, warum "dec", "hex" Manipulatoren wirksam werden, bis ich nicht zwischen ihnen ändere, aber benutzerdefinierte Manipulatoren sollten immer verwendet werden, um für jedes Streaming wirksam zu werden?

+0

Können Sie die Frage in Ihrem letzten Absatz klären? Ich verstehe nicht, was du meinst. –

+0

Ich meine, dass, wenn Sie Cout schreiben << hex << a << b << endl << c; dann werden alle Zahlen in Hex-Basis angezeigt, aber wenn ich cut << 'a' << tab << 'b' << 'c' << endl; schreibe, dann wird der "Tab" nur während des Druckens von 'b wirksam ', aber nicht für' c '. – Narek

+0

@Narek: Ihre Definition von 'Tab' druckt nur einen Tab, ändert nicht die Einstellungen des Streams wie' hex'. –

Antwort

20

Der Standard definiert die folgende operator<< Überlastung in der Klasse basic_ostream Vorlage:

basic_ostream<charT,traits>& operator<<(
    basic_ostream<charT,traits>& (*pf) (basic_ostream<charT,traits>&)); 

Wirkungen: Keine. Funktioniert nicht wie eine formatierte Ausgabefunktion (wie in 27.6.2.5.1 beschrieben).

Rückgabe: pf(*this).

Der Parameter ist ein Zeiger auf eine Funktion, die einen Verweis auf eine std::ostream nimmt und zurückgibt.

Dies bedeutet, dass Sie eine Funktion mit dieser Signatur an ein ostream-Objekt "streamen" können, und dies hat den Effekt, dass diese Funktion im Stream aufgerufen wird. Wenn Sie den Namen einer Funktion in einem Ausdruck verwenden, wird dieser (normalerweise) in einen Zeiger auf diese Funktion konvertiert.

std::hex ist ein std::ios_base Manipulator wie folgt definiert.

ios_base& hex(ios_base& str); 

Effekte: Ruft str.setf(ios_base::hex, ios_base::basefield).

Rückgabe: str.

Das bedeutet, dass zu einem ostreamhex Streaming wird die Ausgangsbasis Formatierung Flags Ausgangs Zahlen in Hexadezimal eingestellt. Der Manipulator gibt selbst nichts aus.

+1

Ich dachte, die Frage war, "wie kommt es, dass Manipulatoren selbst nicht definiert sind, um' void' zurückzugeben, und den 'Operator <<' overload für Manipulatoren, die 'pf (* this)' definieren und dann '* this' zurückgeben. ? –

+0

@Steve Jessop: Ich dachte, die Frage war: "Bitte erklären Sie mir, wie funktioniert das alles?". Es funktioniert, weil dieser 'basic_ostream' diese spezielle Überladung definiert und deshalb die angegebene Signatur benötigt wird.Ich bin der Frage im letzten Absatz jedoch nicht gefolgt. –

+0

Oh, OK. Normalerweise lese ich diese Fragen als "Was ist die Motivation für den Standard?" anstatt "was sagt der Standard?", aber vielleicht liegt es nur daran, dass ich eine Kopie des Standards habe, die ich für mich selbst lesen kann, und mich nicht mit denen einfühlen kann, die das nicht tun ;-) –

4

Normalerweise legt der Stream-Manipulator einige Flags (oder andere Einstellungen) für das Stream-Objekt fest, damit es beim nächsten Mal entsprechend den Flags verwendet wird. Der Manipulator gibt daher dasselbe Objekt zurück, das übergeben wurde. Die operator<< Überladung, die den Manipulator aufgerufen hat, hat dieses Objekt natürlich bereits, so wie Sie bemerkt haben, der Rückgabewert wird für diesen Fall nicht unbedingt benötigt. Ich denke, das deckt alle Standardmanipulatoren ab - sie geben alle ihren Input zurück.

Allerdings ist das Framework mit dem Rückgabewert flexibel genug, dass ein benutzerdefinierter Stream-Manipulator ein anderes Objekt zurückgeben kann, vermutlich ein Wrapper für das angegebene Objekt. Dieses andere Objekt würde dann von cout << 'a' << tab zurückgegeben werden und könnte etwas tun, das die integrierten ostream Formatierungseinstellungen nicht unterstützen.

Ich bin mir nicht sicher, wie Sie dieses andere Objekt für die Freigabe freigeben würden, also weiß ich nicht, wie praktisch das ist. Es muss möglicherweise etwas Eigenartiges sein, wie ein Proxy-Objekt, das von der ostream selbst verwaltet wird. Dann würde der Manipulator nur für benutzerdefinierte Stream-Klassen funktionieren, die ihn aktiv unterstützen, was normalerweise nicht der Zweck von Manipulatoren ist.

8

Es ist nichts falsch daran, außer es gibt keinen überladenen < < Operator dafür definiert. Die vorhandenen Überlasten für < < erwarten einen Manipulator mit der Signatur ostream & (* fp) (ostream &).

Wenn Sie ihm einen Manipulator mit dem Typ Ostream & (* fp)() geben würden Sie einen Compiler-Fehler erhalten, da es nicht hat eine Definition für Operator < < (ostream &, Ostream & (* fp)()). Wenn Sie diese Funktionalität wünschen, müssen Sie den Operator < < überladen, um Manipulatoren dieses Typs zu akzeptieren.

Sie würden eine Definition für diese schreiben:
Ostream & ostream :: operator < < (ostream & (* m)())

Beachten Sie hier, dass nichts Magisches geschieht hier . Die Stream-Bibliotheken basieren stark auf Standard C++ - Funktionen: Überladen von Operatoren, Klassen und Referenzen.

Nun, da Sie wissen, wie Sie die Funktionen erstellen können, die Sie beschrieben, ist hier, warum wir es nicht tun:

Ohne einen Verweis auf den Strom geben wir zu manipulieren versuchen, können wir nicht auf den Strom, Änderungen verbunden mit dem letzten Gerät (cin, out, err, fstream, etc). Die Funktion (Modifier sind alle nur Funktionen mit Phantasie Namen) würde entweder eine neue Ostream, die nichts mit der links vom < Operator oder durch einen sehr hässlichen Mechanismus zu tun hatte, um herauszufinden, welche Ostream es sollte connect with else alles auf der rechten Seite des Modifiers wird es nicht zum endgültigen Gerät machen, sondern würde lieber an den ostream geschickt werden, den die Funktion/Modifier zurückgegeben hat.

Denken Sie an Ströme wie dieser

cout << "something here" << tab << "something else"<< endl; 

wirklich bedeutet

(((cout << "something here") << tab) << "something else") << endl); 

wo jeder Satz von Klammern ist etwas cout (schreiben, ändern usw.) und kehrt dann cout so den nächsten Satz von Klammern kann daran arbeiten.

Wenn Ihr Tab Modifikator/Funktion keinen Bezug zu einem Ostream hatte, müsste er irgendwie erraten, welche Ostream der < < Operator zur Ausführung seiner Aufgabe übrig hatte. Arbeitest du mit cour, cerr, einem Dateistrom ...? Die Interna der Funktion werden niemals erfahren, es sei denn, sie erhalten diese Information wie und warum nicht so einfach wie eine Referenz darauf.

nun wirklich den Punkt nach Hause zu fahren, lassen Sie uns sehen, was Endl wirklich ist und die überladene Version des < < Operator wir verwenden:

Dieser Operator wie folgt aussieht:

ostream& ostream::operator<<(ostream& (*m)(ostream&)) 
    { 
     return (*m)(*this); 
    } 

endl sieht wie folgt aus:

ostream& endl(ostream& os)  
    { 
     os << '\n'; 
     os.flush();  
     return os; 
    } 

der Zweck endl ist ein newli hinzufügen ne und spülen Sie den Stream und stellen Sie sicher, dass der gesamte Inhalt des internen Puffers des Streams auf das Gerät geschrieben wurde. Um dies zu tun, muss zuerst ein '\ n' in diesen Stream geschrieben werden. Es muss dann dem Stream mitteilen, dass er gespült werden soll. Die einzige Möglichkeit für endl, zu wissen, in welchen Stream geschrieben und gespült werden soll, ist, dass der Operator diese Informationen beim Aufruf an die Funktion endl weiterleitet. Es wäre so, als würde ich dir sagen, dass du mein Auto waschen sollst, aber dir auf dem vollen Parkplatz nie sagen darf, welches Auto mir gehört. Du würdest niemals deine Arbeit erledigen können. Du brauchst entweder mein Auto oder ich kann es selbst waschen.

Ich hoffe, dass es löscht

PS up - Wenn Sie mein Auto aus Versehen finden passieren Sie zu, fügen Sie es waschen.