The course dictionary (which is the source of all the rollover definitions in the website) defines an expression as follows:
A combination of variables, constants, operators and functions which is progressively evaluated an operation at a time until it is reduced to a final value.
You're familiar with expressions from mathematics and we have built on that familiarity already by using expressions in a limited fashion. Its time to introduce them more formally.
The crucial thing about expressions is the last part of that definition. When an expression is run as part of a program, it is always reduced to a single value. That expression reduction process is called evaluation.
Here is a list of statements that include expressions, most lifted from the previous topics (possibly in modified form):
statement | expression included |
---|---|
e; |
e |
pi = 3.14159; |
pi = 3.14159 |
return (base * height)/ 2; | (base * height)/ 2 |
speed(distance, time); | speed(distance, time) |
Even just the occurrence of a variable or constant by itself is an expression because it can be evaluated. If e represents the charge on an electron it is 1.60217646e-19. Similarly, a function call by itself is an expression, because it can be evaluated.
Arithmetic expressions are based on the use of arithmetic operators. The first thing you have to understand is that computers have two sets of special hardware for doing arithmetic—one set for integer arithmetic and one set for real (in the form of doubles) arithmetic. Whenever you create an arithmetic operation, the machine has to decide which set of hardware to use. The integer hardware is much faster. The double hardware can handle real numbers.
Every operation involves an operator and either one or two operands (the values being operated on by the operator). The decision as to which piece of hardware to invoke is based on the type of the operands.
We characterize C++ operators as being
+ -
+ - * /
where *
and /
signify respectively multiplication and division. C++ has no exponentiation operator.
Here's an example of the unary + and - at work.
double
a = -4.3; double b = -5.73; double x = 2.0; y = 2*x *(-b) + 3.4*x*(+a);
The minus signs in -4.3 and -5,73 are part of the
literal
values. However, the unary minus applied to b
is clearly an
operator
. It means take whatever b
's value is (positive or negative) and negate it (reverse the sign). The value of the unary +
as applied to a might be dubious but it is legal. Unary + is part of the language for the sake of consistency.
The four multiplies (*) and one addition in the example above are examples of binary operations. They each have two operands, one to the left of the operator and one to the right. Evaluation of the expression is carried out one operation at a time.
In double arithmetic, all operands are
double
and the result is always double
. Consider the following function to compute the quadratic x2+2x+1.5 for
any x (without an exponentiation operator, x*x is used in place of x2). Focus on the step by step evaluation of the quadratic expression within the Expression Engine.
The
int
operators are
+ -
+ - * / %
where *
and /
signify respectively multiplication
and division. Notice that integer arithmetic has an extra operator. The % operator, known as the modulo operator, computes the remainder of an integer division. In integer arithmetic, all operands are int
and the result is always int
The results are what you would expect until you get to division.
In normal arithmetic 11/7
would be 1.571
. However, the result
of an integer operation is always an integer.
So why not 2
?
C++ int
arithmetic doesn't round. Instead it gives us two int
division operators.
/
gives us the integer part. %
gives us the remainderThis is exactly arithmetic as you first learned it in grade school, before you learned "decimals". Seven goes into eleven one time with four left over.
int
s and double
s are different types. Computers
can
double
arithmeticint
arithmeticThey can't do mixed arithmetic. Instead, they convert from one type to the other.
In the example evaluation of the term 2 * x
requires an implicit
conversion.
The 2
is automatically converted to a double
yielding 2.0
* x
and then a double
multiply is called.
A programmer can also force a conversion explicitly by doing a type cast.
int y = 2 * (int) x;
Here the operator (int)
is an int
type cast applied
to the double
variable
x
coercing it to an int
.
This is known as a downcast because precision is lost. Downcasting also occurs in the following example because, although the expressionis evaluated using doubles, the final result is returned as an int. There's no video for this one. At this point in the course you should be able to run it yourself.
When a double
is converted to an int
, it is not rounded, it is truncated.
The fractional part is discarded
This is consistent with the integer /
operator's behaviour.
Here's how you round positive nos.
The technique has to be ammended for negative nos. We'll show you how later.
Many compilers, including Eclipse, will warn you about downcasts. Such warnings are not syntax errors. Rather, they are telling the programmer that a logical error is likely to occur. Crudely, a logical error is one that occurs once you start the program running.
There are some special shorthand assignment operators in C++, created because the original designer of C didn't like to type more than necessary. We just show them by example.
x += 2;
means x
= x+2;
x -= 2;
means x
= x-2;
x *= 2;
means x
= x*2;
x /= 2;
means x
= x/2;
Again, remember, these aren't equations. They're assignments. We evaluate the right side that write the value into the left side. The Teaching Machine will automatically convert compound assignment expression to the right hand form in the Expression Engine.
The order of evaluation in compound expressions is determined by
( )
unary - , + |
Highest (evaluated first) |
*, /, % |
|
-, + |
Lowest (evaluated last) |
For example, given the expression 4+3*2, the multiply operation is carried out before the addition because multiply has a higher precedence than addition. Thus the value of the expression is 10 instead of 14. Here are a few simple examples.
There are thousands of prebuilt functions available in dozens of libraries
#include
the appropriate library header file for
each.
The header files mostly contain the declarations needed to use the libraries
For example, the math library has declarations for
double sin(double x), double tan(double
x)
double atan(double x)
double pow(double x, double exp)
double atan2(double y, double x)
which
returns the arc tangent of y/x
, a function which is defined
for x = 0
. To use any of these functions you would add to the top of your file the line
#include <cmath>
This is a very special line of code. The #
which
starts it means it is actually a command to the precompiler.
The precompiler is a kind of administrative assistant to
the compiler. What this line really means is "I want to use the cmath
library so go
and get it and add it to our program." The compiler never sees this
line of code. Instead it sees special lines of code inserted by the precompiler
that it needs in order to be able to use the cmath
library.
Here is a package of functons to provide services for conversion between rectangular and polar co-ordinates.
Note that the functions we build are calling library functions. Up to now, we have built or implemented functions, thus providing a service for someone else.
When call a function, it means that we are the client making use of a service provided by some other developer.
Some special lines of note:
In
polarMag
we have the following:return sqrt(pow(x,2) + pow(y,2))
sqrt
is the square root function as provided by thecmath
library and as you would expect, it takes a singledouble
argument.For that argument, we are actully giving it an expression that has two other function calls in it.
Thus we can have function calls embedded in function calls—now we're getting some power!
Also note
As the comment says, neither C nor C++ have a built-in value for
π
so we calculate it on the fly. This is much better than typing something like 3.14159 because our calculation gives us the full precision of the floating point processor built in to the computer.Still, creating a function to do it is inefficient, since we have to recompute pi every time we need it. What we need is a constant available throughout our entire program.
The solution, as it appeared in one of your assignments, is to create a computed constant
const double PI = 4.0 * atan(1.0);
and then put it into an h file which can be shared throughout the program.
Exercises
Your textbooks are full of formulae. Pick some out and write functions for them. For example, from circuits: