I'd recommend you read Programming: Principles and Practice Using C++ by Stroustrup. There are 2 chapters (Ch 6 - 7) dedicated to creating a calculator as you mentioned.
Here's my summary(may not be perfect):
Basically, you would have to define a grammar(a set of rules) which tells C++ how to parse expressions. For example, this is the grammar Stroustrup defined for a calculator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
/*
Expression:
Term
Expression '+' Term
Expression '-'
Term:
Primary
Term '*' Primary
Term '/' Primary
Primary:
Number
'(' Expression ')'
Number:
floating point literal
*/
|
An expression can consist of:
-> a term
-> itself + a term
-> itself - a term
For example, is 45 + 27 an expression?
We read the above sequentially and break each section into tokens(a unit that represents something). In this case, the tokens would be 45, +, and 27. Then we read each token and apply the above grammar to it.
45 is a number, which is a primary, which is a term, which is an expression.
The next character we see is a '+', so we must look for a term after that, so that it satisfies the "Expression + Term" grammar.
27 is a number, which is a primary, which is a term.
Thus, 45 + 27 satisfies the "Expression + Term" grammar, so it is an expression.
Now to implement this grammar into code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
#include <cctype>
#include <iostream>
using namespace std;
const char number_t{ 'n' }, print_ch{ '\n' }, quit_ch{ 'q' };
/*
type: Is it a number, operator or parentheses?
value: If it's a number, what is its value?
*/
class Token
{
public:
Token( ) : type{}, value{}
{
}
// we'll only use the type member variable
// to store parentheses and operators
Token( char t ) : type{ t }, value{}
{
}
// for numbers
Token( double v ) : type{ number_t }, value{ v }
{
}
char type;
double value;
};
/*
a wrapper for cin
to read Tokens from input
*/
class Token_stream
{
public:
Token_stream( ) : token{}, is_full{}
{
}
Token get( );
// for when we don't use the Token
// easier to demonstrate with an example
void putback( const Token& t );
private:
Token token;
bool is_full;
};
Token Token_stream::get( )
{
if( is_full ) {
is_full = false;
return token;
}
char c{};
// '\n' returns true for isspace
while( cin.get( c ) && isspace( c ) )
if( c == print_ch ) return Token{ print_ch };
switch( c )
{
case '+': case '-': case '*': case '/':
case '(': case ')':
case print_ch: case quit_ch:
return Token{ c };
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.':
{
cin.putback( c );
double d{};
cin >> d;
return Token{ d };
}
default:
throw "bad token";
}
}
void Token_stream::putback( const Token& t )
{
if( is_full ) throw "putback when full";
token = t; is_full = true;
}
double expression( Token_stream& ts );
// deals with numbers and parentheses
double primary( Token_stream& ts )
{
Token t{ ts.get( ) };
switch( t.type )
{
case number_t:
return t.value;
case '(':
{
t = expression( ts );
Token t2{ ts.get( ) };
if( t2.type != ')' ) throw "expected ')'";
return t.value;
}
default:
throw "primary expected";
}
}
// deals with * and /
double term( Token_stream& ts )
{
double left{ primary( ts ) };
while( true ) {
Token t{ ts.get( ) };
switch( t.type )
{
case '*':
left *= primary( ts );
break;
case '/':
{
double right{ primary( ts ) };
if( right == 0.0 ) throw "divide by 0";
left /= right;
break;
}
default:
ts.putback( t );
return left;
}
}
}
// deals with + and -
double expression( Token_stream& ts )
{
double left{ term( ts ) };
while( true ) {
Token t{ ts.get( ) };
switch( t.type )
{
case '+':
left += term( ts );
break;
case '-':
left -= term( ts );
break;
default:
ts.putback( t );
return left;
}
}
}
void keep_window_open( )
{
cout << "Enter a character to quit: ";
char c{};
cin >> c;
}
int main( )
{
try {
Token_stream ts{};
while( true ) {
cout << "> ";
Token t{ ts.get( ) };
// after entering our expression, there is a '\n'
// in cin, causing primary( ) to throw
// this will eat all the '\n', so we can enter
// another expression
while( t.type == print_ch )
t = ts.get( );
if( t.type == quit_ch ) break;
else {
ts.putback( t );
cout << " = " << expression( ts ) << "\n";
}
}
}
catch( const char* exc ) {
cout << "exception caught: " << exc << "\n";
keep_window_open( );
return 1;
}
catch( ... ) {
cout << "unhandled exception caught\n";
keep_window_open( );
return 2;
}
keep_window_open( );
}
|
As you can see expression() calls term() which calls primary(). Through this, we can see that the order of operations is correct as each function deals with their own operators. This may be a lot to get your head around (it was confusing for me at first, but after explaining it to you I have a more thorough understanding).
Hope this helps :)