2012-10-17 15 views
5

Powiel możliwe:
Operator overloadingW jaki sposób operatory "<<" and ">>" obsługują I/O?

Robię długo oczekiwany powrót do C++ i jest jakiś podstawowy zapis, że tak naprawdę nie wydaje się być tak, że widoczne w innych językach.

Jeśli spojrzeć na tej linii kodu

cout << "firstvalue is " << firstvalue << endl; 

Zdaję sobie sprawę co to robi. Na konsoli jest napisane "firstvalue is x". x jest wartością pierwszej wartości. Jednak nie wiem nic o nawiasach "kątowych" < < "lub" >> ". Nie byłem w stanie ich zbadać ani tego, co robią, ponieważ nie znam ich oficjalnej nazwy.

Moje pytanie brzmi, co tak naprawdę stanie się (krok po kroku) w powyższym stwierdzeniu? A do czego służą te "< <"? Myślę, że rozumiem, że cout jest standardową funkcją biblioteki do pisania na konsolę. Jednak jestem przyzwyczajony do zapisu notacji obiektywnej-c lub kropkowej. Nie widzę, do którego obiektu jest ta funkcja "cout".

Potrafię zrozumieć nieco łatwiej, ponieważ przynajmniej zapewnia aparaty dla argumentów. na przykład printf ("twój ciąg tutaj").

+2

Obowiązkowe czytanie: [The Definitive C++ Book Guide and List] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) –

+1

W tym kontekście '<< 'to [operator] (http://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt), a wyrażenie łączy wyniki połączeń z tym operatorem. – juanchopanza

+2

'cout' nie jest funkcją. Jest to obiekt, dla którego operator '<<' jest przeciążony. – Grizzly

Odpowiedz

13

C++ pozwala operatorowi przeciążać. Oznacza to, że typ zdefiniowany przez użytkownika może definiować własne zachowanie na wbudowanych operatorach. W tym przypadku operatory są nazywane operatorami: left shift lub right shift. Operatory te były tradycyjnie używane do przenoszenia bitów, ale standardowa biblioteka ponownie je symbolizuje operacje przesyłania strumieniowego.

Listę dostępnych operatorów można znaleźć pod numerem C and C++ here.

W przypadku przesyłania strumieniowego literał łańcuchowy i wartość jakiegoś typu do obiektu std::cout, który jest obiektem typu std::basic_ostream.

Step-by-Step

Po zasady pierwszeństwa zostały zastosowane kod wygląda następująco:

((cout << "foobar") << x) << endl; 

Kompilator zasadzie przekształcenia object << object wyrażeń w wywołań funkcji.

operator<<(operator<<(operator<<(cout, "foobar"), x), endl); 

Następnie wykryje, które przeciążenie wywołuje. (To jest naprawdę trudne, ponieważ powinno być wystarczające przekonanie, że po prostu szuka przeciążenia operator<< z pasującymi argumentami).

Większość wbudowanych przeciążeń dla basic_ostream to here i here.

+0

Przeciążenie, które przyjmuje literał ciągu, w rzeczywistości nie jest członkiem, jest to jedna z wolnych funkcji, które połączyłem. –

+0

Dzięki, nie zrozumiałem tego na początku, potem poświęciłem trochę czasu na przeczytanie Accelerated C++. I po przeczytaniu tego ponownie wszystko miało więcej sensu. –

1

Jest to cukier syntaktyczny za:

// Let the function 'print' be a renaming of 'operator<<' 
// with T being the type of the object you want to print. 
std::ostream& print(std::ostream&, const T&); 

// 1) Print "first value is" and then return the stream you 
// to which to just printed (ie. cout). 2) Use the returned 
// stream to chain function calls and print 'firstValue'. 
print(print(std::cout, "first value is"), firstValue); 
2

Oni dalej wkładania strumień (lub ekstrakcji, w przypadku istream >>) i są rzeczywiście semantyczny przeciążenia lewej przesunięcia i prawej - operatory przesunięcia.

Więc tak:

int x = 1 << 1; 

jest przesunięcie bitowe, ale w ten sposób:

std::cout << x; 

jest wstawienie strumień. Możesz napisać to jednoznacznie jako:

operator <<(std::cout, x); 

i uzyskać dokładnie taki sam wynik. Konwencjonalny format operatorów strumień wstawiania (mogą być przeciążone dla typów zdefiniowanych przez użytkownika, więc nie jest niczym niezwykłym, aby napisać własne) jest

std::ostream& operator <<(std::ostream&, T value); 

strumienia wyjściowego jest zwracana (odnośnik), dzięki czemu można Połączenia łańcuchowe: Twój przykład tłumaczy się jako:

operator<< (
    operator<< (
    operator<<(std::cout, "firstvalue"), 
    firstvalue 
), 
    std::endl 
); 

Aha, i ... std::cout (i std::cerr etc.) nie są funkcje: są globalne obiekty. Ta funkcja jest przeciążonym operatorem <<. Pomyśl o nich jako o odpowiednikach FILE *stdout, *stderr.

Istnieje kilka zalet C++ iostreams w stosunku do printf et. AL:

  • typu bezpieczeństwo: nie można błędnie wydrukować liczbę całkowitą z "%f" i inne śmieci, ponieważ rozdzielczość przeciążenie automatycznie wybiera funkcję std::ostream& operator<<(std::ostream&, double) w czasie kompilacji
  • wsparcie dla typów zdefiniowanych przez użytkownika: można napisać operator wstawiania strumienia dla twojej nowej, cudownej klasy, i będzie działał wszędzie, gdzie abstrakcja strumienia: możesz używać tych samych przeciążeń (więc zapisujesz je tylko raz), aby sformatować na stdout i stderr (cout/cerr), oraz pliki (std::ofstream) i ciągi znaków (std::ostringstream). Nie ma potrzeby oddzielnego obsługiwania printf/fprintf/snprintf.

Istnieją również pewne wady:

  • wydajność: jest jakaś kara do całej tej abstrakcji, a powszechność systemu locale który jest skonfigurowany w czasie wykonywania
  • oznajmiania: przynajmniej prymitywny typy już wspierane przez printf, ciągi formatu są znacznie terser i bardziej wyraziste
4

operator << jest „arytmetyka lewy shift” w C + +.Na przykład:

3 << 2 

ocenia do 12. Powodem jest to, że reprezentacja binarna 3 jest

00000011 

przesuwając go dwa razy w lewo można dostać

00001100 

a wartością numeryczną wyniku jest 12.

Co to ma zrobić z wyjściem? Nic w rzeczywistości. Jednak w C++ możesz na nowo zdefiniować znaczenie operatorów dzięki przeciążeniu. Biblioteka standardowa C++ postanowiła przedefiniować znaczenie operatora lewostronnej jako "wysyłanie do strumienia".

Więc co się dzieje, że

std::cout << "whatever" 

powraca jako wartości std::cout, ale jako efekt uboczny wyprowadza łańcuch „cokolwiek”.

Operator został wybrany, ponieważ miał uzasadniony pierwszeństwo (przeciążenie nie może zmienić pierwszeństwa i nie można zdefiniować nowych operatorów), a kształt sprawia, że ​​wygląda nieco "naturalnie". Należy jednak pamiętać, że operator lewy shift jest po prostu normalny operator i na przykład nie ma gwarancji, o kolejności ocena:

std::cout << f() << g() << h(); 

tutaj wyjście będzie wynikiem nazywając f(), a następnie w wyniku wywoływania g() a następnie wynik wywołania h() ... ale same funkcje mogą być wywoływane w innej kolejności i na przykład h() może być wywołany jako pierwszy!

W pewnym sensie "wygląd sekwencji" operatora jest mylący, ponieważ chodzi o sekwencję wyjściową, ale nie o sekwencję oceny.

2

Operator << jest operatorem w ten sam sposób, co operator +, a operator to *. W sposób następujący: wyrażenia są równoważne

5 + 3 + 2 
((5 + 3) + 2) 

tak są dwa następne:

std::cout << "Hello" << std::endl 
((std::cout << "Hello") << std::endl) 

To tylko operator z dwoma argumentami. W przypadku typów podstawowych operatory << i >> są w rzeczywistości znane jako operatory przesunięcia w lewo i w prawo. Wykonują przesunięcie bitowe. Na przykład przesunie wszystkie bity w 5 (0101) w lewo o jedno miejsce, aby uzyskać 10 (1010).

Jednak, podobnie jak w przypadku większości innych operatorów, można przeciążać operatorów zmian. W przypadku biblioteki wejścia/wyjścia operatory zmian są przeciążone, aby zapewnić naturalną składnię dla wejścia i wyjścia dla strumienia. To dlatego, że kierunkowość żetonów wygląda jak coś płynącego w jedną lub w drugą stronę. W przypadku tych klas I/O, te przeciążenia operatora zwracają odwołanie do strumienia, w którym wykonujesz operację, aby mogły być połączone razem.

Przeciążanie operatorów zmian dla określonej klasy polega na udostępnieniu funkcji składowej operator<< lub operator>>, która przyjmuje jeden argument (operand po prawej stronie operatora). Alternatywnie, możesz podać funkcję nie będącą członkiem z tymi samymi nazwami, które przyjmują dwa argumenty, odpowiednio dwa operandy operatora.

Powiązane problemy