Chapter 7 - More operators
C has an almost embarrassingly large number of operators (42 altogether!), and we have only begun to scratch the surface. So far, we have encountered the following operators:
Assignment: = Arithmetic: + - * / % Relational: == != < > <= >= ! Pointer: & * Array subscript: []
In this chapter, we will introduce another handful of operators but, before we do, we need an effective way to deal with the issue of precedence.
Parentheses
We have seen that some operators take precedence over others. For example, we know that multiplication takes precedence over addition. Here's an analogy (just a picture, not a technical explanation): we can think of precedence as being an attractive force exerted by an operator, like a magnet. Multiplication is a stronger magnet than addition, so in an expression like n = 5 + 3 * 9, the 3 is pulled in the direction of the *, giving us (graphically speaking):
n = 5 + 3*9 ;
This picture also shows that the = assignment operator is even weaker than the + addition operator.
So, in:
n = 5 + 3 * 9; /* n becomes 32, not 72 */
That's fine if that's what we want. But what if we actually wanted to add 5 + 3, and then multiply the result by 9?
We could do this in two steps:
n = 5 + 3; n = n * 9;
and in fact that's not at all a bad idea. It's simple and clear. If that's the way you'd rather do it, then go ahead. It's perfectly good C.
It works because there is a sequence point in the code between the addition and the multiplication, so the compiler has to make sure the addition is complete before it starts worrying about the multiplication, and the only way it can do that is to use 5 and 3 as the operands for the addition.
But if you'd rather write the code in a single line, you can. You can override the precedence order of * and + by using parentheses, as follows:
n = (5 + 3) * 9; /* n becomes 72, not 32 */
This use of parentheses to gather operands and operators together has a higher precedence than anything else at all in the whole C language, so you can be assured that you can always be precise about which operands you want to supply for each operator.
More assignment operators
On a couple of occasions, we have had occasion to write code such as:
n = n + 1;
In other words, we need to increase the value of n by 1. More generally:
n = n + val;
(Increase the value of an object by some other value.)
This is rather wordy code, which is unfortunate because we need to do this sort of thing quite often. C therefore provides a shortcut which means precisely the same thing:
n += val;
This operator adds val to n, storing the result in n.
There are equivalent operators for the other mathematical operations:
n += val; /* increase n by val */ n -= val; /* decrease n by val */ n *= val; /* multiply n by val (storing the result in n) */ n /= val; /* divide n by val (storing the result in n): val must not be 0 */ n %= val; /* divide n by val (storing the remainder in n): val must not be 0 */
More arithmetic operators
C provides an even shorter shortcut for adding one to the value of an object. It comes in two flavours:
n++; /* add 1 to n */ ++n; /* add 1 to n */
You might wonder why there are two versions, when they both appear to do the same thing. Well, they do seem to, but there is a difference that becomes clear when we use the operator as part of a wider expression.
Like all operators, ++ yields a value. In the case of n++, it yields the value of n before the 1 is added. In the case of ++n, it yields the value of n after the 1 is added. So, in the following code:
n = 0; m = n++; /* m is now 0, and n is 1 */ n = 0; /* reset n so that we can start again */ m = ++n; /* m is now 1, and n is 1 */
Which one you want depends on what you're doing, so both are available to you.
A similar pair of operators exists for subtracting 1 from the value of an object.
n--; /* subtract 1 from n, yielding n's original value */ --n; /* subtract 1 from n, yielding n's new value */
Logical operators
The logical AND operator
We have seen that the equality operators == != and the relational operators < > <= >= provide us with a truth value (0 meaning false, and 1 meaning true) that we normally put to such uses as selection (if/else) or loop control. Sometimes, though, we need more complicated conditions. For example, if we wish to execute a block of code only if a is less than b and c is less than d using only what we already know, we'd do it something like this:
if(a < b) { if(c < d) { stuff we want to do goes here
To make our lives easier, C provides the logical AND operator, which allows us to combine the two conditions as follows:
if(a < b && c < d) { stuff we want to do goes here
So we give this operator two conditions, and it will yield a truth value (1) only if both conditions are true.
In fact, the && operator has an interesting quirk. If the first condition is false, it cannot possibly be the case that both conditions are true, and so && stops immediately and yields 0. To illustrate this, let's put a function call into the second test, as follows:
#include <stdio.h> int test_and(void); int main(void) { if(6 > 42 && test_and()) { puts("True"); } else { puts("False"); } return 0; } int test_and(void) { puts("In test_and"); return 1; }
Because the && operator is initially faced with the test 6 > 42, which is patently false, it knows immediately that the whole test (are both conditions true?) must fail. So it gives us 0 immediately, and the test_and function is never called. So the program just prints out "False".
If we change the first test from 6 > 42 to 6 < 42, and run the program again, the && function will see that 6 < 42 is a true expression, so it will then proceed onto the second test, which involves calling the test_and function, and so the output will be:
In test_and True
which demonstrates that test_and is called. Since test_and returns 1 (which && interprets as 'true'), the whole test is true, and so True is printed.
The logical OR operator
The logical OR operator is similar in nature to logical AND, except that it tests to see whether either condition is true. If both are true, that's fine too. The symbol used for logical-OR is the vertical pipe, written twice. On a normal UK or US keyboard layout, you'll find the vertical pipe to the left of your Z key, but you'll need to use SHIFT to get to it.
Here's how you might use logical-OR:
if(a < b || c < d) { stuff we want to do
Under what circumstances, then, can we do the stuff we want to do? There are four possibilities: both conditions could be true (in which case || gives us 1, meaning 'true'). The first condition might be false, but the second one true (in which case || gives us 1, meaning 'true'). The first condition might be true, but the second one false (in which case || gives us 1, meaning 'true'). Or both conditions might be false. In this circumstance, || will yield 0, meaning 'false'.
In other words, || only gives us 0 (false) if both conditions are false.
Like &&, || will stop as soon as it knows the answer. If the first condition is true, there's no need to worry about the second condition, and so it is not evaluated at all. (Modifying the above program to demonstrate this is a useful exercise.)
Other operators
C is very generous in its provision of operators. There are still plenty more to discover, in fact. But that generosity has resulted in a few operators that defy classification: for example, the conditional operator and the comma operator. These are both a little odd, and you don't actually need either of them. Some people love them, and use them a lot. Others ignore them completely. And then there is sizeof, which many C programmers don't even realise is an operator.
The conditional operator
The conditional operator ?: allows you to choose which of two values it will yield, based on a condition. If the condition is true (non-zero), it will yield the first value. Otherwise, it will yield the second.
int minimum(int a, int b) { int result = a < b ? a : b; return result; }
In this example function, a and b are passed by the calling function. The conditional operator ?: has a condition part, in this case a < b. This condition is tested. If it turns out to be true (i.e. if a is indeed less than b), then the value after the ? is the result of the operation. Otherwise, the value after the : is the result of the operation.
We could have written this without the conditional operator, of course, but then we'd have needed more code:
int minimum(int a, int b) { int result; if(a < b) { result = a; } else { result = b; } return result; }
This code does exactly the same thing, but it takes longer to type.
It should be stressed that only one of the two possible results of the conditional operator is actually evaluated. For a < b ? a : b this hardly matters. But in a situation such as this:
int result = a < b ? funcA() : funcB();
only one of those two functions will actually be called. If a < b, then funcA will be called (and the return value will be assigned to the result object), but funcB won't be called at all. Otherwise, funcA will not be called, but funcB will be called.
The comma operator
The comma operator allows us to chain expressions together. Like the semicolon, a comma operator introduces a sequence point into the code. Note: the comma operator is not the same thing as the comma separator that is used in function parameter lists, array initialiser lists, and the like, which does not introduce a sequence point.
The comma operator evaluates the expression on its left. Then there is a sequence point. Breathe! Then it evaluates the expression on its right. The result of the comma operator is the value of the expression on the right.
int a; int b; int c; int d; d = (a = 6, b = a / 2, c = minimum(a, b));
An artificial example, perhaps, but simple to understand. (I've pinched the minimum() function from earlier in the chapter; it isn't a standard library function.)
That code is equivalent to:
int a; int b; int c; int d; a = 6; b = a / 2; c = minimum(a, b); d = c;
Note that the result of the comma operator is the rightmost value, not the leftmost.
Comma operators are perhaps most often used in for loops, which are the subject of the next chapter.
sizeof
The sizeof operator yields the size, in bytes, of its operand. Unusually for an operator, sizeof has no symbol assigned to it; it is spelled out in letters instead, which may explain why some people don't realise it's an operator. It has another quirk, too, which is that (with one single exception introduced in a fairly recent revision of C) the sizeof operator does not evaluate its operand. This fact is of extraordinary usefulness and importance, as we shall see later. It is especially important for arrays, because the whole business of an unindexed array name being converted into a pointer to the first element is the result of evaluating the array name expression. Since sizeof doesn't evaluate its operand, this conversion doesn't take place. Consequently, we can tell how much memory an array takes:
sizeof arrayname
We can even tell how many elements it has, if we simply divide by the size (in bytes) of one element:
sizeof arrayname / sizeof arrayname[0]
The value yielded by sizeof is of a rather peculiar type, which we'll address in due course.
Summary
In this chapter, you learned about how to override precedence, and you were introduced to some more assignment operators and arithmetic operators. You learned about the && and || logical operators, the conditional operator, the comma operator, and the sizeof operator.
In the next chapter, we will investigate another control structure, the for loop.
Progress
Terminology
- precedence
- array
- index
- pointer
- sentinel value
- null character
- null terminator
- string
- sequence, selection, and iteration
- control structure
Syntax
- comments
- types
- char
- operators
- increment and decrement operators
- ++n n++ --n n--
- assignment operators
- = += -= *= /= %=
- additive operators
- the + operator
- the - operator
- multiplicative operators
- the * operator
- the / operator
- the % operator
- equality and relational operators
- == != < > <= >= !
- logical operators
- the && operator
- the || operator
- address and indirection operators
- the unary * operator
- the unary & operator
- the array subscripting operator []
- miscellaneous operators
- the conditional operator ? :
- the comma operator ,
- the sizeof operator
- increment and decrement operators
- control structures
- if/else
- while
- do/while