Previous: 5.4.2 The Data Type of the Result
Up: 5.4 Operators and Expression Evaluation
Previous Page: 5.4.2 The Data Type of the Result

5.4.3 Some New Operators

In Table 5.1 there are several operators which we have not yet discussed. Some of these are described below; the remainder will be delayed until later chapters when we discuss the appropriate data types.

Increment and Decrements Operators

A common operation in many programs is to increase or decrease a variable value by one; for example, this is how we keep a count of how many times a loop is executed. C provides a ``shorthand'' way of performing this operation with special increment and decrement operators, ++ and -- respectively. These are unary operators requiring one operand and may be used either as prefix or postfix operators meaning they either precede or follow their operands. In postfix form, x++ increases the value of x by one, and y-- decreases the value of y by one. Likewise, in prefix form, ++x increases the value of x by one, and --y decreases the value of y by one. However, there is a difference between the prefix and postfix operators. In the case of prefix operators, the operation is performed first and then the expression evaluates to the new value of its operand. For postfix operators, the expression first evaluates to the current value of the operand and then the operators are applied. For example, if x is 1, the expression ++x first increments x to 2 and then evaluates to the value 2. On the other hand, again if x is 1, the expression x++ first evaluates to the value of x, namely 1, and then increments x to 2. Here is a code fragment showing the use of the increment and decrement operators:

int x, y, z1, z2, z3;

x = 4; y = 4;

The value of

++x - x++
is implementation dependent. A compiler may either evaluate the first term first or the second term first. It is therefore not possible to say what the expression will evaluate to. For example, assume that x is initially 1. If the first expression is evaluated first, then the expression is:
2 - 2
i.e. 0, and x is 3. On the other hand, if the second term is evaluated first, then the expression is:
3 - 1
i.e. 2, and x is 3.

Increment and decrement operations can just as well be written as assignment expressions:

x = x + 1;
     y = y - 1;
The use of increment and decrement operators does not accomplish anything that cannot be done by appropriately placed assignments. These operators were designed to be used with machines that have increment and decrement registers; in which case the compiler can take advantage of these registers and improve the performance of the program. However, many machines today do not have these registers, so most compilers translate expressions with increment and decrement operators in exactly the same manner as they do assignment expressions, but these operators remain as a ``shorthand'' syntax for compact programs.

The syntax of the increment and decrement operators is:

The operand must be and <Lvalue>, i.e. a location into which a value can be placed. (So far, we have seen that only a variable name may be used as an <Lvalue>. We will see other possibilities in Chapter ). The precedence and associativity of increment and decrement operators is given in Table 5.1. Here are some examples of their use in program code:

Composite Assignment Statements

The above operators provide a ``shorthand'' way of increasing or decreasing a variable by one; but sometimes we would like to increase or decrease (or multiply, divide or mod) by some other value. C provides ``short hand'' operators for these as well, called the composite assignment operators. These operators and their equivalent are shown in Table 5.2.

The general syntax of a composite assignment operator is:

where <op> may be one of the binary arithmetic operators, +, -, *, /, or %. The left operand of these operators must be an <Lvalue>, but the right operand may be an arbitrary <expression>.

Again, there is no particular advantage in using the composite assignment operators over the simple assignment operator except that they produce a somewhat more compact program. The precedence and grouping for composite assignment operators given in Table 5.1 shows they are the same as the assignment operator. Figure 5.10 shows the factorial function (see Figure 5.2) using these new operators.

Conditional Expression

Sometimes in a program we would like to determine the value of an expression based on some condition. For example, if we had two variables, x and y, and we wanted to assign the larger value to the variable, z. We could write and if statement to perform this task as follows:

if (x < y)  z = y;
     else z = x;
Another way of stating this in words is that z should be assigned the value of y if x < y or x, otherwise. The (operator) symbols ? and : may be used to form such a conditional expression as follows:
z = x < y ? y : x;
The expression to the right of the assignment operator is evaluated first as follows. If x < y, the expression evaluates to the value of the expression after ?, i.e. y. Otherwise, it evaluates to the value of the expression after :, i.e. x. In other words, the expression evaluates to the larger of x and y which is then assigned to the variable, z.

As another example, we can write an expression that evaluates to the absolute value of x:

x < 0 ? -x : x
If x is negative, the expression evaluates to -x (a positive value); otherwise to x.

The syntax for writing a conditional expression is:

The first operand, <expr1>, is evaluated; if true, the result of the entire expression is the value of < expr2>. Otherwise, the result is the value of <expr3>. The conditional operator is a ternary operator since it requires three operands.

An if statement can always perform the task that a conditional expression does. Whether to use one or the other is a matter of choice and convenience. Figure 5.11 shows a function which returns the value of the larger of two double arguments.

The Comma Operator

The comma operator, ,, provides a way to combine several expressions into a single expression. The syntax is:

The semantics are that <expression1> is evaluated first, followed by <expression2> with the value of the entire expression being that of <expression2>. These expressions may be arbitrary expressions, including another comma expression.

The comma expression is useful where the syntax of a statement requires a single expression, but we have several expressions to be evaluated, such as a for statement where several variables are used to control the loop. Here, the comma operator may be used to write multiple initialization and update expressions. As an example, we will use comma operators to write a function that computes and prints Fibonacci numbers. Fibonacci numbers are natural numbers in the sequence:

1, 1, 2, 3, 5, 8, 13, ...
Each number of the sequence is computed by adding the previous two numbers of the sequence. Thus, we must start with the first two numbers, which are both 1, then the next number is , the next one is , the next one is , and so on.

We will write a driver, main(), which calls a function, fib(), to print the Fibonacci numbers. The function starts with two variables, which are initialized to the values of the first two numbers 1 and 1. Each new number is computed as a sum of the previous two until the limit is reached. Figure 5.12 shows the code.

The function, fib(), prints Fibonacci numbers less than its argument, lim. It uses a for loop with comma expressions for the first and last expressions. The first expression initializes two variables, i and j to 1, with i assumed to be the first and j assumed to be the second number in the sequence. The variable, n, the next number in the sequence, is initialized to zero so that the loop condition may be tested the first time with some value of n less than lim. The sum of i and j is the next number in the sequence, n, which is computed and printed in the loop body. The variables are then updated to the new values, j assigned the value of n and i assigned the value of j. Thus, i and j always have the values of the last two Fibonacci numbers in the sequence. The process is repeated until n exceeds lim.

The output of the program is shown below:

The sizeof Operator

The exact amount of space reserved in memory for different data types depends on the implementation. Typically, a character is assigned 8 bits or one byte of space; integers are generally assigned 2 or 4 bytes of storage; float numbers usually require at least four bytes, and double at least eight bytes. Table 5.3 shows some typical examples for the HP9000, an HP_UX Unix system, and the IBM PC, a DOS environment.

It is sometimes necessary to use the sizes of objects in expressions, and since the sizes are implementation dependent, to make our programs portable, we should not build the values into our programs as constants. For any implementation, size of an object can be easily determined by the use of the sizeof operator with syntax:

The unary operator, sizeof, yields the size, in bytes, of the type of its operand. The operand may be an arbitrary expression, however, the expression is NOT evaluated; the sizeof expression simply evaluates to the number of bytes used for the type of the result. For example, the expression, sizeof x, evaluates to the size of x in bytes. Here is a code fragment using the sizeof operator:
int x;
     double y;

printf("Size of x is %d bytes\n", sizeof x); printf("Size of x+y is %d bytes\n", sizeof (x+y));

The first printf() statement will print the size (in bytes) of the int type object, x. The second will print the size of the value of the expression, x+y. As we saw earlier, this addition would be done in double precision and the result would be a double. Remember, the expression, x+y is not evaluated; only its size is used by the sizeof operator. Also remember that sizeof is an operator, like +; not a function call. It has a precedence and associativity like any other operator (shown in Table 5.1). That is why the parentheses are required in that second printf(), the precedence of sizeof is higher than +. Without the parentheses, the expression would be evaluated as:
(sizeof x) + y
It is also possible for the operand of sizeof to be a parenthesized type name, like a cast operator, rather than a variable name, for example:
sizeof (int)
     sizeof (float)
     sizeof (long int)
     sizeof (unsigned long int)

We can easily write a program to determine the sizes of different types for the host implementation. The code is shown in Figure 5.13.

A sample output for the HP9000 is:

Whenever the size of a type is required in a program, the sizeof operator should be used rather than the actual size, since the actual value is implementation dependent. Such a use of the sizeof operator in a program ensures that the program will be portable from one type of computer to another.



Previous: 5.4.2 The Data Type of the Result
Up: 5.4 Operators and Expression Evaluation
Previous Page: 5.4.2 The Data Type of the Result

tep@wiliki.eng.hawaii.edu
Wed Aug 17 08:40:40 HST 1994