Skocz do zawartości
  • 👋 Witaj na MPCForum!

    Przeglądasz forum jako gość, co oznacza, że wiele świetnych funkcji jest jeszcze przed Tobą! 😎

    • Pełny dostęp do działów i ukrytych treści
    • Możliwość pisania i odpowiadania w tematach
    • System prywatnych wiadomości
    • Zbieranie reputacji i rozwijanie swojego profilu
    • Członkostwo w jednej z największych społeczności graczy

    👉 Dołączenie zajmie Ci mniej niż minutę – a zyskasz znacznie więcej!

    Zarejestruj się teraz

[ TUT / SOURCE ] Kalkulator w C++


Rekomendowane odpowiedzi

Opublikowano

Witam!

Często pojawiały się na forum tematy, których autorzy próbowali napisać swój pierwszy kalkulator w C++.

Aby ukrócić te często zadawane pytania, postanowiłem podzielić się dobrze ukomentowanym, przykładowym kodem na kalkulator w C++.

 

Stworzyłem aplikację w dwóch ujęciach - obiektowym (typowym dla C++) oraz strukturalnym (typowym dla C).

 

Gdyby cokolwiek było w kodzie niejasne - nie wzmagajcie się przed napisaniem postu w tym wątku!

 

 

 

UJĘCIE OBIETKOWE:

 

 

#include <iostream> // biblioteka odpowiedzialna za komunikacę wejścia - wyjścia.

#include <stdexcept> // biblioteka odpowiedzialna za obsługę wyjątków

#include <typeinfo> // biblioteka odpowiedzialna za RTTI ( używane podczas dynamic_cast'ów )






/*

		Klasy reprezentujące wykonywanie poszczególnych operacji arytmetycznych.

*/


/*
Klasa bazowa, reprezentująca operację matematyczną.
*/
class COperationBase { // Abstrakcyjny funktor bazowy. Abstrakcyjna klasa bazowa naśladująca funkcję. Łatwo poznać po przeciążonym operatorze (), i co najmniej jednej metodzie czysto wirtualnej.
protected:
	int primo; // pierwsza liczba
	int secundo; // druga liczba

public:
	virtual double operator()(void)  = 0 ; // czysto wirtualny, przeciążony, bezargumentowy operator wywołania funkcji zwracający wartość zmiennoprzecinkową podwójnej precyzji
	COperationBase(int $1, int $2) {
		primo = $1;
		secundo = $2;
	}
};






/*
Trzy klasy pochodne - odpowiedziane odpowiednio za dodawanie, mnożenie i dzielenie.
*/
struct CSum : public COperationBase { // Klasa pochodna odpowiedzialna za dodawanie.

virtual double operator()(void) { // operator wykonujący faktyczne działanie
	return primo + secundo;
}

CSum(int $1, int $2) : COperationBase($1, $2) {}; // konstruktor, wywołujący konstruktor klasy bazowej z podanymi argumentami
};

struct CMultiply : public COperationBase { // Klasa pochodna odpowiedzialna za mnożenie.

virtual double operator()(void) { // operator wykonujący faktyczne działanie
               return primo * secundo;
}

CMultiply(int $1, int $2) : COperationBase($1, $2) {}; // konstruktor, wywołujący konstruktor klasy bazowej z podanymi argumentami
};

struct CDivide : public COperationBase { // Klasa pochodna odpowiedzialna za dzielenie.

virtual double operator()(void) { // operator wykonujący faktyczne działanie

	if ( secundo == 0 ) {
		throw std::runtime_error("Nie dziel przez zero!") ; // wyrzucamy wyjątek, ponieważ nie można dzielić przez zero
	}

	return (double)primo / secundo; // konwersja pierwsze liczby z int na double... arch, po prostu musi tak być ;]
}

CDivide(int $1, int $2) : COperationBase($1, $2) {}; // konstruktor, wywołujący konstruktor klasy bazowej z podanymi argumentami
};













/*

		Dwie klasy reprezentujące faktyczny mechanizm kalkulatora.
		CCalcEngine -> implementująca funkcję runEngine, która wykonuje operację arytmetyczną,
		oraz LEAF CKalkulator, dziedziączy z CCalcEngine.

		Klasa będąca LEAF ( ang. liść ) jest w założeniu programisty klasą "końcową", z której nie należy już dziedziczyć.
		Jest to finalna, gotowa implementacja, reprezentująca gotowy interface

*/


/*
Klasa CCalcEngine - wywoływanie operacji arytmetycznych
*/
class CCalcEngine { // Klasa odpowiedzialna za obliczenia naszego kalkulatora
private:
	COperationBase* operation; // dzialanie wykonane przez silnik

	// funkcja odpowiedzialna za czyszczenie zasobów
	void clear() {
		if ( operation )
			delete operation;
	}

public:
	double runEngine(int, int, int); // funkcja zwracająca wynik operacji

	CCalcEngine() : operation(NULL) // konstruktor domyślny, inicjalizujemy tylko wartość wskaźnika
	{
	}

	~CCalcEngine()
	{
		clear();
	}

};

double CCalcEngine::runEngine( int operation_symbol, int $1, int $2 ) { // funkcja uruchamiająca silnik

clear(); // czyścimy zasoby

// sprawdzamy symbol operacji ( działania ). W zależności od wartości, przypisujemy do wskaźnika inny funktor (wskaźnik jest typu COperationBase* )
if ( operation_symbol == 1 )
	operation = new CSum($1, $2);
else if ( operation_symbol == 2 )
	operation = new CMultiply($1, $2);
else if ( operation_symbol == 3 )
	operation = new CDivide($1, $2);
else
	throw std::exception();

return (*operation)(); // wywołanie operatora funkcji klasy wirtualnej będącej pochodną klasy COperationBase.
}







/*
Klasa CKalkulator - LEAF
*/
class CKalkulator : public CCalcEngine { // główna klasa programu
private:
	int exit_status; // return code programu -> więcej w przeciążonym operatorze konwersji na int'a

	int MainLoop(); // główna pętla

public:

CKalkulator() { // kontruktor domyślny
	// Inicjacja zmiennych
	exit_status = 0;

	// Wejście w główną pętlę
	exit_status = MainLoop();
}

operator int() { // jeśli caller ( funkcja lub metoda operująca na naszym obiekcie ) będzie oczekiwał konwersji na int'a -> zapewnimy mu ją, zwracając exit_status - atrybut zwracany przez metodę MainLoop
			// naszym caller'em jest tutaj funkcja int main(), czyli entry point programu
               return exit_status;
}
} ;

int CKalkulator::MainLoop() {

int primo, secundo, operacja; // zmienne lokalne


/* 	Poniżej tworzę funktor pomocniczy.
	Stanowczo zwiększa on czytelność kodu.
*/
struct {	// anonimowy, zagnieżdżony funktor [ niewidoczny na zewnątrz TEJ funkcji -> niszczony zaraz po zakończeniu wykonywania TEJ funkcji ]
	bool operator() ( std::istream& stream ) { // przeciążony operator wywołania funkcji przyjmujący za argument referencję do strumienia wejścia
		if ( ! stream.fail() ) // jeśli zły bit NIE został ustawiony
			return true;
		else { // w innym wypadku
			stream.clear(); // wyczyść...
			stream.sync(); // ... i zsynchronizuj strumień
			return false;
		}
	}
} check_stream ;

while( true ) {

	do{

		std::cout <<			/* Wyświetlamy tekst na standardowym wyjściu ( konsoli ) */
			"\r\n"
			"Wybierz dzialanie: \r\n"
			"1. Dodawanie.  \r\n"
			"2. Mnozenie. \r\n"
			"3. Dzielenie. \r\n"
			"4. Wyjscie z programu. \r\n";

		std::cin >> operacja;

	} while ( not check_stream(std::cin) ) ;


	do {

		std::cout <<			/* Wyświetlamy tekst na standardowym wyjściu ( konsoli ) */
			"\r\n"
			"Podaj pierwsza liczbe: \r\n";

		std::cin >> primo;

	} while ( not check_stream(std::cin) ) ;


	do {

		std::cout <<			/* Wyświetlamy tekst na standardowym wyjściu ( konsoli ) */
			"\r\n"
			"Podaj druga liczbe: \r\n";

		std::cin >> secundo;

	} while ( not check_stream(std::cin) ) ;

	try {

		std::cout << // wypisujemy na ekran wynik operacji
			"\r\n"
			"Wynik: \r\n\t" <<
			runEngine( operacja, primo, secundo ) <<
			"\r\n==============================================================\r\n"; // ot, taka tam linia, żeby było ładnie...

	} catch ( std::exception& e ) { 	// łapiemy wyjątki. Tutaj zacznie się zabawa.
							// runEngine może wyrzucić std::exception ( dla złej operacji - wtedy zamykamy program ) lub std::runtime_error przy błędnym inpucie użytkownika ( w tym przykładzie podczas próby dzielenia przez zero )
							// Można to zrobić oczywiście prościej (najpierw łapać runtime_error a dopiero później exception)... jednak wtedy nie byłoby zabawy.

		// musimy sprawdzić, czy nasz wyjątek typu exception nie jest typem runtime_error. W tym celu korzystamy z dynamic_cast'a, gdyż exception jest klasą bazową runtime_error. Nie możemy bezpiecznie castować w górę hierarchii. ( castowanie z klasy dziedziczącej do bazowej jest zawsze bezpieczne )
		// Jako, że korzystamy z referencji ( zalecane przy wyrzucania wyjątków ), dynamic_cast może rzucić wyjątek bad_cast jeśli cast jest niemożliwy do przeprowadzenia ( exception nie jest runtime_error'em )
		try
		{
			std::runtime_error& err ( dynamic_cast<std::runtime_error&>(e) ); // próba konwersji. W tym miejscu będzie dynamic_cast może rzucić wyjątek
			std::cout << err.what() << std::endl; // wyjątek nie został rzucony - wypisujemy na ekran informację o wyjątku. Tutaj będzie to "Nie dziel przez zero!". Po tej instrukcji pętla while(true) będzie dalej się wykonywać
		}
		catch ( std::bad_cast const& ) // dynamic_cast rzucił bad_cast. W tym wypadku kończymy pętlę i zwracamy wartość 0 ( wszystko OK, w końcu już sami zajęliśmy się wyjątkiem )
		{
			return 0; // kończymy funkcję
		} // nie lapiemy nic innego, gdyż nic innego nie wyrzuci wyjątku

	} catch ( ... ) { // wyjątek innego typu. Nie powinno się zdarzyć prawie nigdy, ale na wszelki wypadek...
		return (size_t)-1; // zwracamy wynik, który jasno sugeruje użytkownikowi, że coś źle się spieprzyło. Jest to rodzaj błędu, który powinien wyświetlić " Skontaktuj się z developerem w celu naprawienia wadliwego oprogramowania "
	}

}
}












/*
Entry point programu, funkcja main()
*/
int main() {
try {
	return CKalkulator();
} catch ( ... ) {
	return (size_t)-1; // fatal error!
}
}

 

 

 

 

UJĘCIE STRUKTURALNE:

 

 

#include <iostream> // biblioteka odpowiedzialna za komunikacę wejścia - wyjścia.


// deklaracja funkcji pomocniczych
bool ___czy_strumien_std_cin_jest_dobry(); // funkcja ta będzie sprawdzała ( i ewentualnie czyscila ) strumień wejscia ( std::cin, stąd nazwa )
void ___wyswietl_wynik(int wynik); // funkcja wypisze nam wynik na wyjście
void ___wyswietl_ostrzezenie_o_dzieleniu_przez_zero(); // mam nadzieje, ze nazwa jest klarowana ( ang. self-explanatory )



// nasza główna funkcja w tym programie
int MojPierwszyKalkulator() {

// informujemy kompilator o korzystaniu z odpowiednich symboli z przestrzeni nazw std::. Częstym ( błędnym ) sposobem jest "using namespace std;". Nie polecam tego rozwiązania.
using std::cin;
using std::cout;

int wynik, primo, secundo, operacja; // zmienne lokalne, odpowiedzialne kolejno za wynik, pierwszą liczbę, drugą liczbę, oraz operację jaką chcemy wykonać

while( true ) {

	do{

		cout <<			/* Wyświetlamy tekst na standardowym wyjściu ( konsoli ) */
			"\r\n"
			"Wybierz dzialanie: \r\n"
			"1. Dodawanie.  \r\n"
			"2. Mnozenie. \r\n"
			"3. Dzielenie. \r\n"
			"4. Wyjscie z programu. \r\n";

		cin >> operacja;

	} while ( not ___czy_strumien_std_cin_jest_dobry() ) ;	/* 	Tutaj sprawdzamy, czy strumień jest dobry; to jest ( w tym wypadku ) czy została wprowadzona liczba.
													Jeśli nie - prosimy użytkownika o input jeszcze raz. */


	do {

		cout <<			/* Wyświetlamy tekst na standardowym wyjściu ( konsoli ) */
			"\r\n"
			"Podaj pierwsza liczbe: \r\n";

		cin >> primo;

	} while ( not ___czy_strumien_std_cin_jest_dobry() ) ;


	do {

		cout <<			/* Wyświetlamy tekst na standardowym wyjściu ( konsoli ) */
			"\r\n"
			"Podaj druga liczbe: \r\n";

		cin >> secundo;

	} while ( not ___czy_strumien_std_cin_jest_dobry() ) ;


	/*
		W tym punkcie użytkownik podał już jaką chce wykonać operację oraz liczby, na których chce wykonać tę operację.
		Zaczniemy od sprawdzenia operacji.
	*/

	switch ( operacja ) {

		case 1:
			wynik = primo + secundo;
			___wyswietl_wynik(wynik);
			break;

		case 2:
			wynik = primo * secundo;
			___wyswietl_wynik(wynik);
			break;

		case 3:
			if ( secundo == 0 ) { // nie możemy dzielić przez zero!!!
				___wyswietl_ostrzezenie_o_dzieleniu_przez_zero();
				break;
			}

			wynik = primo / secundo;
			___wyswietl_wynik(wynik);
			break;

		default:
			return 0; // Konczymy wykonywanie funkcji MojPierwszyKalkulator

	}

}
}

bool ___czy_strumien_std_cin_jest_dobry() { // definicja funkcji pomocniczej
using std::cin; // deklarujemy używanie std::cin, dla przejrzystości
if ( ! cin.fail() ) // jeśli zły bit NIE został ustawiony
	return true;
else { // w innym wypadku
	cin.clear(); // wyczyść...
	cin.sync(); // ... i zsynchronizuj strumień
	return false;
}
}

void ___wyswietl_ostrzezenie_o_dzieleniu_przez_zero() {
std::cout << "Nie dziel przez zero!\n";
}

void ___wyswietl_wynik(int wynik) {
std::cout << // wypisujemy na ekran wynik operacji
	"\r\n"
	"Wynik: \r\n\t" <<
	wynik <<
	"\r\n==============================================================\r\n"; // ot, taka tam linia, żeby było ładnie...
}





/*
Entry point programu, funkcja main()
*/
int main() {
MojPierwszyKalkulator(); // Odpalamy naszą funkcję.
return 0; // wychodzimy z programu.
}

 

Ta sygnatura jest pusta.

Opublikowano

Bardzo dziwna maniera z tym podwójnym podkreśleniem.Jest kilka rzeczy które można napisać krócej np.



bool ___czy_strumien_std_cin_jest_dobry() { // definicja funkcji pomocniczej
	using std::cin; // deklarujemy używanie std::cin, dla przejrzystości
	if ( ! cin.fail() ) // jeśli zły bit NIE został ustawiony
			return true;
	else { // w innym wypadku
			cin.clear(); // wyczyść...
			cin.sync(); // ... i zsynchronizuj strumień
			return false;
	}
}

Wystarczy:

bool ___czy_strumien_std_cin_jest_dobry() {
     if(cin.fail()) {
          cin.clear(); // wyczyść..[/font]
          cin.sync(); // ... i zsynchronizuj strumie[/font]
          return false;[/font]
     }
     return true;
}

Cytat

 

Orientacja seksualna polega na tym, że jeden mężczyzna lubi blondynki, drugi szatynki, że jedna pani woli umięśnionych mężczyzn a inna chudych intelektualistów. Homoseksualizm nie jest orientacją jest chorobą za którą homoseksualiści nie ponoszą odpowiedzialności. Pewna grupa ludzi rodzi się upośledzona fizycznie, na przykład bez ręki ale czy to znaczy, że wszystkim zdrowym powinniśmy też obcinać ręce? Nie, powinniśmy zapewnić im protezy. Obowiązkiem państwa jest zapewnienie homoseksualistom, jak wszystkim upośledzonym fizycznie czy psychicznie profesjonalnej opieki medycznej.

Weteran
Opublikowano

Nie rozumiem czemu ma służyć ten tutorial, po co tyle udziwnień w głupim kalkulatorze?

 

Kod - x/10 (tyle tego, że nawet nie chcę mi się na to patrzeć)

funkcjonalność programu - 4/10 (po skompilowaniu program to nic ciekawego, nawet nie można pisać działań w jednej linijce)

 

@Down

Okey, powiedzmy, że jeżeli to ma być do nauki w jakimś tam stopniu teorii c++, to może się przydać, bo jest w nim ujętych wiele ważnych aspektów oop.

Ale i tak nigdy mi do głowy by nie przyszło żeby napisać tak kalkulator :)

Opublikowano

@Assarelliuss:

Podwójnym podkreśleniem? Masz na myśli *potrójny* podkreślnik przed nazwami małych funkcji? :)

 

Ominięcie using std::cin nie wystarczy, nie mam globalnie zadeklarowanego using namespace std; ( bo to brzydkie ). Poza tym zmieniasz wyłącznie kolejność, jak kto woli, jak komu czytelniej...

 

@XAULIN:

Tutorial ten ma służyć zaprezentowaniu początkującym kodu kalkulatora w dwóch ujęciach.

Udziwnienia w ujęciu obiektowym mają służyć pokazaniu największych dobrodziejstw C++, czyli poliformizmu, przeciążaniu operatorów oraz RTTI. Brakuje szablonów, ale już nie miałem pojęcia jak by je tam wcisnąć.

To jest zbędne, to jest tylko przykład... Częstsze rozwiązanie problemu prostego kalkulatora przedstawiłem w kodzie strukturalnym, w którym nie korzystam z OOP w ogóle, kod dzielę na funkcje.

Ta sygnatura jest pusta.

  • 2 miesiące temu...
Opublikowano

@autor

po wybraniu 4 opcji (Wyjście z programu) musisz podać 1 i 2 liczbę i dopiero potem zamyka, nie wiem czy zauważyłeś :)

ale tak to mi się podoba ^_^ Like

  • 1 miesiąc temu...

Zarchiwizowany

Ten temat przebywa obecnie w archiwum. Dodawanie nowych odpowiedzi zostało zablokowane.

×
×
  • Dodaj nową pozycję...