Musisz się przyzwyczaić, że w trakcie nauki nie zawsze będziesz rozumiał "dlaczego tak? dlaczego to działa?". Zastanawiałeś się skąd się biorą << przy cout? (to nie jest część języka C++ ;-)
Ale nie o tym. Zastanawiasz się nad faktem dlaczego działa:
while (cin.get(ch))
while (!cin.get(ch))
Ale nie działa
while (cin.get(ch) == true )
Aby zrozumieć to w 100% musiałbyś znać obiektowość i wiedzieć czym jest funkcja konwertująca. Bo cin, to właśnie obiekt klasy istream. No ale łopatologicznie spróbuję.
Dlaczego cin.get( ) w ogóle może stać wewnątrz warunku pętli?
Spójrz na dokumentację klasy istream: http://www.cplusplus.com/reference/istream/istream/
Tutaj są napisane wszystkie funkcje, z których korzysta cin. W gąszczu zapewne niezrozumiałych dla Ciebie (jeszcze) nazw i liter znajdują się 2 szczególnie Cię interesujące w tym momencie. W sekcji Public member functions inherited from ios. Mianowicie:
operator!
operator bool (C++11)
Te funkcje opisują jak cin będzie się zachowywał, jeśli użyjesz go w wyrażeniu warunkowym (if, while itp.).
No to wejdźmy do opisu funkcji operator bool: http://www.cplusplus.com/reference/ios/ios/operator_bool/
Co widzimy? Zrobię screeny: Tutaj jestem przełączony na standard C++98
Co my tu mamy? Widzimy, że funkcja operator bool jest zdefiniowana w tym przypadku jako operator void*() const. Tym const się nie przejmuj. Funkcja ta według deklaracji zwraca void*. Czyli wskaźnik typu void. (poczytaj o nich, bo nie jest to treścią tego pytania). Niżej widzimy opis zwracanej wartości. Po przetłumaczeniu mamy mniej więcej coś takiego: Jeśli jest ustawiona jakaś flaga błędu, np. EOF (koniec pliku, wczytywania), to zwraca null pointer. Czyli wskaźnik na NULL. Czyli po konwersji na liczbę będzie to 0 (interpretowane wewnątrz warunku pętli, ifa jako fałsz).
ALE: czytamy dalej, w przeciwnym razie (gdy nie ma flagi błędu) zwracana jest jakaś inna wartość. Czyli może to być 5, ale może to być też -22135324. No i porównanie takiej liczby z wartością true w C++ nie ma sensu, bo przy takim porównaniu:
int n = 5;
if( n == true )
cout << "prawda" << endl;
To będzie fałsz i instrukcja cout się nie wykona. Mój kompilator (Visual) wyrzuca mi nawet ostrzeżenie, bo porównuję int z bool. Dlaczego nie działa? Bo porównujesz int z bool, więc kompilator zamieni bool na int i ze stałej true zrobi wartość 1. Więc w rzeczywistości porównujesz 5 == 1. Dlatego nie działa. Natomiast takie coś już zadziała:
int n = 5;
if( n )
cout << "prawda" << endl;
if( !n )
cout << "falsz" << endl;
Widzisz podobieństwo do Twojego problemu teraz?
Użycie operatora wykrzyknik (!) w kontekście cin jest opisane w dokumentacji funkcji operator!. Ale pozwól, że daruję już sobie omawianie jej. Jest ciekawsza rzecz. Problem wystąpił u Ciebie, bo zapewne nie piszesz w standardzie C++11. (dziwię się, bo mamy 2018 rok, a kompilatory nadal domyślnie nie mają ustawionej zgodności z C++11). Teraz ten sam screenshot z dokumentacji funkcji operator bool tylko jestem przełączony na zakładki C++11.
Widzimy inne zapisy. Po pierwsze, deklaracja teraz wygląda tak: explicit operator bool() const. Teraz dopiero jest to prawdziwa funkcja operator bool, bo (jeśli nie umiesz obiektowości i funkcji konwertujących, to uwierz mi na słowo), funkcja zwraca wartość bool. No ale kiedy ta funkcja jest wywoływana? Ano... dzieje się to niejawnie, za Twoimi plecami, gdy stawiasz cin do warunku pętli, if jako wyrażenie warunkowe.
Przeczytajmy opis zwracanej wartości. Po przetłumaczeniu: funkcja zwraca (rozum jako: cin przyjmuje wartość) false, gdy ustawiona jest flaga failbit lub badbit oraz true gdy żadna z nich.
Gdyby nie było (pewnie tajemniczego dla Ciebie "jeszcze") słowa explicit, to porównanie:
(cin.get(ch) == true)
Miałoby sens. Niestety dodanie powyższego słówka kluczowego nie zezwala na użycie tego w ten sposób. (bo zaszłaby niejawna konwersja, której nie chcemy). Spowodujemy w takim przypadku błąd kompilacji.
(uwaga, trochę zaawansowane)
Zauważ jednak, że napotkanie końca pliku spowoduje ustawienie flagi eofbit, ale jeśli ta flaga jest ustawiona, to nasz cin wcale nie przyjmie wartości false (przyjmie tylko dla failbit i badbit). Jednakże przy takim wczytywaniu, które Ty realizujesz, czyli w warunku pętli, po przesłaniu znaku Ctrl+Z funkcja ustawi dodatkowo flagę failbit. Pojawi się failbit, cin przyjmie wartość false i pętla się zakończy. Czyli jednocześnie zepsujesz strumień w pewnym sensie i będziesz musiał czyścić flagi błędów, aby dalej móc się nim posługiwać :-)
Podsumowując. Jeśli jest jakiś problem, to zawsze należy sięgać do dokumentacji. Niestety jeśli uczysz się dopiero, to musisz wykazać się pokorą, że nie wszystko do tej pory jesteś fizycznie w stanie zrozumieć. Wiele rzeczy przyjmij jako dogmat, idź dalej z nauką. Ucz się ogólnie o języku, i wchodź w szczegóły stopniami. Dowiedz się czym jest obiektowość (tak ogólnie), a potem ucz się o konwersjach, które tutaj opisałem.
Jeśli chcesz, to mogę Ci podać przykład programiku, gdzie używam tego opretator bool() i jak to działa, ale jeśli masz dość, to zrozumiem :-)
Pytaj dalej jak coś. Chętnie odpowiem :-)