Expressions
Since most computer programs are used for performing calculations
of one type or another,
let's look at C expressions.
These are similar to algebraic expressions and consist of
operands (variables and constants) and operators
(for example, typical arithmetic operations such as addition and subtraction
as well as function invocations).
Some simple expressions are seen in the methods from the
Rectangle class for calculating the area and perimeter.
Click here to load the Rectangle
class' implementation code in a separate window
Note that at the top of the file, there is a statement beginning
struct rectangle.
This is where the attributes are defined.
Note that this implementation has (as expected) height
and width attributes.
The notation double means
that both these attributes are double precision floating point values.
Scroll down until you find the Height method.
Note that this method simply returns
(via the return statement)
the value of the height attribute
(obtained from the name of the object, r,
with the
r->height pattern).
Like algebraic functions, C functions use the
values of their arguments to evaluate functions and return values.
The return directive is followed
by an expression which is evaluated and returned to the
calling or invoking function.
Note that return is a
reserved word.
You can't use return as an
ordinary name anywhere.
Scroll down further until you find the area and perimeter methods at the bottom.Note that the expressions that are used to calculate
the area and perimeter are similar to normal algebraic expressions
except that C programs require the
multiplication to be explicitly included and use the asterisk
symbol * for it.
Circle class
A class for circles would be defined in a very similar way to
the Rectangle one.
There might only be one attribute, the radius, and a pair of
methods to obtain and set the radius.
In order to be consistent, we would name these
methods Radius and SetRadius, using the
same naming pattern as we used for Rectangle.
The area method would look like:
double Area( Circle c ) {
return 3.1415926*c->radius*c->radius;
}
Note that
- 3.1415926 is a constant or
numeric literal.
It has the normal form for decimal numbers -
an optional sign, string of digits, optional decimal point
and further string of digits.
- C has no way of expressing r2 and we must
use r*r instead.
For rn, where n is large, there
is a
pow function: for xy,
write
pow(x,y).
Thus C allows us to write algebraic expressions
using the usual arithmetic operators (+,-,*,/),
just as we might expect.
As in algebraic expressions, parentheses may be used
to remove possible ambiguities, eg
(x + y)*z
and
x + (y*z)
are both legal C expressions that have the
expected meanings.
Functions
Functions return values and can be part of expressions also:
I could write:
x + y*Area( r )
in which the Area
function is invoked to return a value
which is used to evaluate the expression.
Expressions occur in many places in C programs:
the key elements of many statements are expressions.
(In fact, in the formal syntax, even assignment statements
are expressions!)
Function arguments are expressions also.
This allows us to make quite complex expressions.
x + y*f( x, x+7, g(p, Area(r)) )
In this example, Area(r) is evaluated first,
its value (along with p) is passed to the function, g,
which is evaluated to produce a value.
7 is added to x. Then the original x,
x+7 and the value of the function,
g,
are used to evaluate f.
The result of evaluating f is multiplied by
y,
added to x and we're done!
Before we can look at expressions fully,
we need to study some classes (or data types in the language
of the C standard) which are so commonly used that they have
special characteristics.
Primitive data types
Some classes are so commonly used that they are built into the language.
Class of integers
We are so used to writing 3+4 to add two integers together that it would be
rather clumsy to have to invoke a function add_integer(3,4)
everytime we
wanted to add two numbers in our program!
Thus the class of integers is
predefined in C and we can always write:
int x, y, z;
to declare three integer variables named x, y and z.
We are also allowed to invoke certain operations on the class of integers
using infix operators (operators placed between their operands), thus we can
write:
x = 3+4;
y = 2*x;
z = x + y * 2; |
There are quite a large number of infix operators defined for integers:
| Symbol | Operation name | Example | Result |
Notes |
| + | addition | 3+4 | 7 | . |
| - | subtraction | 5-2 | 3 | . |
| * | multiplication | 8*3 | 24 | . |
| / | division | 5/2 | 2 | . |
| % | modulus | 10%3 | 1 | . |
| << | left shift | 3<<5 | 96 | equivalent to 3 * 2^5 |
| >> | right shift | 4>>1 | 2 | equivalent to 4 / 2^1 |
| & | arithmetic and | 3&7 | 3 | bit-wise and of 3 and 7 |
| | | arithmetic or | 7 | 5 | 7 | bit-wise or of 7 and 5 |
| ^ | arithmetic xor | 1 ^ 3 | 2 | bit-wise xor of 1 and 3 |
The last five operators have been included for completeness at this stage: we
will conside them in more detail later when we examine the machine
representations of objects.
These are all the operators that, when applied to a pair of integers, produce
another integer. There are additional operators (eg <) which compare integers
and produce TRUE or FALSE results: we will introduce them later when we
discuss boolean expressions.
Operator precedence
In the last example above, we had
z = x + y * 2;
As you should already know, the value of this expression depends on the
order in which we perform the operations.
With x equal to 7 and
y equal to
14, we get 42 if we perform the addition first and 35 if we perform the
multiplication first. You are probably familiar with the convention that
multiplication takes precedence over addition and would expect that the
correct value is 35.
Since C obeys the multiplication takes precedence over
addition rule, you would be right.
However, in order to save remembering the
precedence rules, it is strongly suggested that you always use parentheses to
indicate the correct order in which to perform operations: thus you should
write:
z = x + (y * 2);
or
z = (x + y) * 2;
depending on which way you intended z
to be computed.
You can find a table of operator parentheses in any C textbook - even this one!
However, because it is not necessary for you to remember this table to write
your own code, it has been relegated in this text to an appendix!
Using parentheses rather than knowledge of the precedence rules to write
your own code has a number of major advantages:
- You do not need to remember a lot of trivial detail.
- The meaning of your code is obvious to anyone who reads it - thus
readers of your code don't need to know the precedence rules either!
- Not all computer languages have exactly the same operator precedence.
This means that a competent Ada or Pascal programmer can easily make a
mistake when writing C and vice versa. Using parentheses works in all
languages that I know!
- There is a small possibility that the compiler writer has been careless and
not implemented the precedence table precisely as it is in the ANSI standard,
so that an expression may evaluate incorrectly when using this compiler.
It's far more likely that an expression with parentheses will evaluate as you
intended.
Of course, if your compiler is so broken that it doesn't compile
expressions correctly, you probably should be using another one anyway, but
as experience will teach you, it is very easy to overlook minor details in large
software systems and there is a definite possibility that an otherwise excellent
B
compiler has a small bug of this type!
As an example of the dangers of remembering the precedence rules, try to
guess what this expression will evaluate to:
3 + 4 << 1
(treat 4<<1 as 4 * 21)
and then look up the precedence table to find out how a correct ANSI C
compiler will evaluate it.
The inbuilt primitive classes are usually referred to as types.
However, you
can take type as a synonym for class
for the purposes of this course.
As you
will see when you examine the object orientation concept fully, classes have
additional properties not usually associated with types, but we won't consider
these additional properties in this course. Thus, although most texts will refer
to the integer type, I will continue to refer to the integers and other inbuilt
types, such as characters, which we consider next, as classes.
A boolean class?
C, unlike Pascal, Ada and many other languages, has no separate class of
booleans - data items which take only the values TRUE or FALSE. Instead it
uses two values of integers:
- an integer with value 0 can also be considered as a boolean with value FALSE
- an integer with any non-zero value can be considered as a boolean with value
TRUE.
Thus we also have a set of relational and logical operators which operate on
integers and produce TRUE or FALSE values which are integers with non-
zero or zero values.
Thus we have:
Note that when a relational operator produces a FALSE value, it must
produce an integer 0, but when it produces a TRUE value, the compiler writer
may choose any non-zero value to represent TRUE. Many compilers use 1,
but they are not required to do so.
Any code which makes any assumption
about the value of TRUE may not work when ported to a different machine:
it may not even work when compiled with a different compiler on the same
machine! Yet all the compilers may comply with the ANSI standard!
This is a particular trap:
x = some relational expression
if( x == TRUE ) some statement |
in which x, computed by some relational expression,
is compared to some (arbitrary) non-zero value chosen by the programmer
for TRUE on this machine.
Since the TRUE value is arbitrary, such code may not run on other
machines, even though the new machine's compiler also conforms to the
ANSI C standard! What the programmer should have written is
simply:
x = some relational expression
if( x ) some statement |
which will run in the same way on all systems.
- reserved word
- a name which cannot be used in any other context in a programming
language: examples in C are return, if, else and
while.
- numeric literal
- a numeric constant used in a programming language
- inbuilt types
- data types which are pre-defined in a language, eg int,
float, double, and char in C
- infix operators
- operators which are placed between their operands, eg x+y,a*b
- prefix operators
- operators which are placed before their operands,
C function calls could be considered prefix operators because the name of
the function (operation) precedes the arguments (operands)
- operator precedence
- An order in which operators will be used in expressions.
All computer languages have operator precedence rules:
the rules are not always the same!
Parentheses can always be used to override operator precedence rules
and ensure that an expression is evaluated as intended.
© John Morris, 1998