Objects First


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

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:

SymbolOperation
name
ExampleResult Notes
+addition3+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 and3&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:

  1. You do not need to remember a lot of trivial detail.
  2. 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!
  3. 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!
  4. 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: 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.

Key terms

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.

Continue on to Functions Back to the Table of Contents
©
John Morris, 1998