I l @ ve RuBoard |
C uses operators to represent arithmetic operations. For example, the + operator causes the two values flanking it to be added together. If the term "operator" seems odd to you, please keep in mind that those things had to be called something. "Operator" does seem to be a better choice than, say, "those things" or "arithmetical transactors." Now take a look at the operators used for basic arithmetic: =, +, -, *, and /. (C does not have an exponentiating operator. The standard C math library, however, provides a pow() function for that purpose. For example, pow(3.5, 2.2) returns 3.5 raised to the 2.2 power.)
In C, the equal sign does not mean "equals." Rather, it is a value-assigning operator. The statement
bmw = 2002;
assigns the value 2002 to the variable named bmw . That is, the item to the left of the = sign is the name of a variable, and the item on the right is the value assigned to the variable. The = symbol is called the assignment operator . Again, don't think of the line as saying, " bmw equals 2002 ." Instead, read it as "assign the value 2002 to the variable bmw ." The action goes from right to left for his operator.
Perhaps this distinction between the name of a variable and the value of a variable seems like hair-splitting, but consider the following common type of computer statement:
i = i + 1;
As mathematics, this statement makes no sense. If you add 1 to a finite number, the result isn't "equal to" the number you started with, but as a computer assignment statement, it is perfectly reasonable. It means "find the value of the variable named i ; to that value, add 1 , and then assign this new value to the variable named i " (see Figure 5.1).
A statement such as
2002 = bmw;
makes no sense in C (and, indeed, is invalid) because 2002 is just a constant. You can't assign a value to a constant; it already is its value. When you sit down at the keyboard, therefore, remember that the item to the left of the = sign must be the name of a variable. Actually, the left side must refer to a storage location. The simplest way is to use the name of a variable, but, as you will see later, a "pointer" can be used to point to a location. More generally , ANSI C uses the term modifiable lvalue to label those entities to which you can assign values. "Modifiable lvalue" is not, perhaps, the most intuitive phrase you've encountered , so let's look at some definitions.
A data object is a general term for a region of data storage that can be used to hold values. The data storage used to hold a variable or an array is a data object, for instance. ANSI C uses the term lvalue to mean a name or expression that identifies a particular object. The name of a variable, for instance, is an lvalue, so object refers to the actual data storage, but lvalue is a label used to identify, or locate, that storage.
Not all objects can have their values changed, so ANSI C uses the term modifiable lvalue to identify objects whose values can be changed. Therefore, the left side of an assignment operator should be a modifiable lvalue. Indeed, the l in lvalue comes from left because modifiable lvalues can be used on the left side of assignment operators.
The term rvalue refers to quantities that can be assigned to modifiable lvalues. For instance, consider this statement:
bmw = 2002;
Here, bmw is a modifiable lvalue, and 2002 is an rvalue. As you probably guessed, the r in rvalue comes from right . Rvalues can be constants, variables , or any other expression that yields a value.
As long as you are learning the names of things, the proper term for what we have called an "item" (as in "the item to the left of the =") is operand . Operands are what operators operate on. For example, you can describe eating a hamburger as applying the "eat" operator to the "hamburger" operand, or you can say that the left operand of the = operator shall be a modifiable lvalue.
The basic C assignment operator is a little flashier than most. Try the short program in Listing 5.3.
/* golf.c -- golf tournament scorecard */ #include <stdio.h> int main(void) { int jane, tarzan, cheeta; cheeta = tarzan = jane = 68; printf(" cheeta tarzan jane\n"); printf(First round score %4d %8d %8d\n",cheeta,tarzan,jane); return 0; }
Many languages would balk at the triple assignment made in this program, but C accepts it routinely. The assignments are made right to left: First jane gets the value 68, then tarzan does, and finally cheeta does. Therefore, the output is as follows :
cheeta tarzan jane First round score 68 68 68
The addition operator causes the two values on either side of it to be added together. For example, the statement
printf("%d", 4 + 20);
causes the number 24 to be printed, not the expression
4 + 20
The values (operands) to be added can be variables as well as constants. Therefore, the statement
income salary + bribes;
causes the computer to look up the values of the two variables on the right, add them, and assign this total to the variable income.
The subtraction operator causes the number after the - sign to be subtracted from the number before the sign. The statement
takehome = 224.00 - 24.00;
assigns the value 200.0 to takehome .
The + and - operators are termed binary , or dyadic , operators, meaning that they require two operands.
The minus sign can also be used to indicate or to change the algebraic sign of a value. For instance, the sequence
rocky = -12; smokey = -rocky;
gives smokey the value 12 .
When the minus sign is used in this way, it is called a unary operator , meaning that it takes just one operand (see Figure 5.2).
The ANSI standard adds a unary + operator to C. It doesn't alter the value or sign of its operand; it just enables you to use statements like
dozen = +12;without getting a compiler complaint. Formerly, this construction was not allowed.
Multiplication is indicated by the * symbol. The statement
cm = 2.54 * in;
multiplies the variable in by 2.54 and assigns the answer to cm .
By any chance, do you want a table of squares? C doesn't have a squaring function, but, as shown in Listing 5.4, you can use multiplication to calculate squares.
/* squares.c -- produces a table of first 20 squares */ #include <stdio.h> int main(void) { int num = 1; while (num < 21) { printf("%10d %10d\n", num, num * num); num = num + 1; } return 0; }
This program prints the first 20 integers and their squares, as you can verify for yourself. Let's look at a more interesting example.
You have probably heard the story of the powerful ruler who seeks to reward a scholar who has done him a great service. When the scholar is asked what he would like, he points to a chessboard and says, just one grain of wheat on the first square, two on the second, four on the third, eight on the next , and so on. The ruler, lacking mathematical erudition, is astounded at the modesty of this request, for he had been prepared to offer great riches. The joke, of course, is on the ruler, as the program in Listing 5.5 shows. It calculates how many grains go on each square and keeps a running total. Because you might not be up to date on wheat crops, the program also compares the running total to a rough estimate of the annual wheat crop in the United States.
/* wheat.c -- exponential growth */ #include <stdio.h> #define SQUARES 64 /* squares on a checkerboard */ #define CROP 9E14 /* US wheat crop in grains */ int main(void) { double current, total; int count = 1; printf("square grains added total grains "); printf("fraction of \n"); printf(" "); printf("US total\n"); total = current = 1.0; /* start with one grain */ printf("%4d %15.2e %13.2e %13.2e\n", count, current, total, total/CROP); while (count < SQUARES) { count = count + 1; current = 2.0 * current; /* double grains on next square */ total = total + current; /* update total */ printf("%4d %15.2e %13.2e %13.2e\n", count, current, total, total/CROP); } return 0; }
The output begins innocuously enough:
square grains added total grains fraction of US total 1 1.00e+000 1.00e+000 1.11e-015 2 2.00e+000 3.00e+000 3.33e-015 3 4.00e+000 7.00e+000 7.78e-015 4 8.00e+000 1.50e+001 1.67e-014 5 1.60e+001 3.10e+001 3.44e-014 6 3.20e+001 6.30e+001 7.00e-014 7 6.40e+001 1.27e+002 1.41e-013 8 1.28e+002 2.55e+002 2.83e-013 9 2.56e+002 5.11e+002 5.68e-013 10 5.12e+002 1.02e+003 1.14e-012
After ten squares, the scholar has acquired just a little over a thousand grains of wheat, but look what has happened by square 50!
50 5.63e+014 1.13e+015 1.25e+000
The haul has exceeded the total U.S. annual output! If you want to see what happens by the 64th square, you will have to run the program yourself.
This example illustrates the phenomenon of exponential growth. The world population growth and our use of energy resources have followed the same pattern.
C uses the / symbol to represent division. The value to the left of the / is divided by the value to the right. For example, the following gives four the value of 4.0 :
four = 12.0/3.0;
Division works differently for integer types than it does for floating types. Floating-type division gives a floating-point answer, but integer division yields an integer answer. An integer has to be a whole number, which makes dividing 5 by 3 awkward , because the answer isn't a whole number. In C, any fraction resulting from integer division is discarded. This process is called truncation .
Try the program in Listing 5.6 to see how truncation works and how integer division differs from floating-point division.
/* divide.c -- divisions we have known */ #include <stdio.h> int main(void) { printf("integer division: 5/4 is %d \n", 5/4); printf("integer division: 6/3 is %d \n", 6/3); printf("integer division: 7/4 is %d \n", 7/4); printf("floating division: 7./4. is %1.2f \n", 7./4.); printf("mixed division: 7./4 is %1.2f \n", 7./4); return 0; }
Listing 5.6 includes a case of "mixed types" by having a floating-point value divided by an integer. C is a more forgiving language than some and will let you get away with this, but normally you should avoid mixing types. Now for the results:
integer division: 5/4 is 1 integer division: 6/3 is 2 integer division: 7/4 is 1 floating division: 7./4. is 1.75 mixed division: 7./4 is 1.75
Notice how integer division does not round to the nearest integer, but always truncates, that is, discards the entire fractional part. When you mixed integers with floating point, the answer came out the same as floating point. When a calculation uses both types, the integer is converted to floating point before division.
The properties of integer division turn out to be handy for some problems, and we will give an example fairly soon. First, there is another important matter: What happens when you combine more than one operation into one statement? That is the next topic.
Consider the following line of code:
butter = 25.0 + 60.0 * n / SCALE;
This statement has an addition, a multiplication, and a division. Which operation takes place first? Is 25.0 added to 60.0 , the result of 85.0 then multiplied by n , and that result then divided by SCALE ? Is 60.0 multiplied by n , the result added to 25.0 , and that answer then divided by SCALE ? Is it some other order? Let's take n to be 6.0 and SCALE to be 2.0. If you work through the statement using these values, you will find that the first approach yields a value of 255. The second approach yields 192.5. A C program must have some other order in mind, for it would give a value of 205.0 for butter .
Clearly, the order of executing the various operations can make a difference, so C needs unambiguous rules for choosing what to do first. C does this by setting up an operator pecking order. Each operator is assigned a precedence level. As in ordinary arithmetic, multiplication and division have a higher precedence than addition and subtraction, so they are performed first. What if two operators have the same precedence? If they share an operand, they are executed according to the order in which they occur in the statement. For most operators, the order is from left to right. (The = operator was an exception to this.) Therefore, in the statement
butter = 25.0 + 60.0 * n / SCALE;
the order of operations is as follows:
60.0 * n The first * or / in the expression (assuming n is 6 so that 60.0 * n is 360.0) 360.0 / SCALE Then the second * or / in the expression 25.0 + 180 Finally (because SCALE is 2.0), the first + or - in the expression to yield 205.0
Many people like to represent the order of evaluation with a type of diagram called an expression tree . Figure 5.3 is an example of such a diagram. The diagram shows how the original expression is reduced by steps to a single value.
What if you want, say, an addition to take place before a division? Then you can do as we have done in this line:
flour = (25.0 + 60.0 * n) / SCALE;
Whatever is enclosed in parentheses is executed first. Within the parentheses, the usual rules hold. For this example, first the multiplication takes place and then the addition. That completes the expression in the parentheses. Now the result can be divided by SCALE .
Table 5.1 summarizes the rules for the operators used so far. (Appendix B, "C Operators," contains a table covering all operators.)
Operators | Associativity |
---|---|
( ) | Left to right |
+ - (unary) | Right to left |
* / | Left to right |
+ - (binary) | Left to right |
= | Right to left |
Notice that the two uses of the minus sign have different precedences, as do the two uses of the plus sign. The associativity column tells you how an operator associates with its operands. For example, the unary minus sign associates with the quantity to its right, and in division the left operand is divided by the right.
Operator precedence provides vital rules for determining the order of evaluation in an expression, but it doesn't necessarily determine the complete order. C leaves some choices up to the implementor. Consider this statement:
y = 6 * 12 + 5 * 20;
Precedence dictates the order of evaluation when two operators share an operand. For example, the 12 is an operand for both the * and the + operators, and precedence says that multiplication comes first. Similarly, precedence says that the 5 is to be multiplied, not added. In short, the multiplications 6 * 12 and 5 * 20 take place before any addition. What precedence does not establish is which of these two multiplications occurs first. C leaves that choice to the implementor because one choice might be more efficient for one kind of hardware, but the other choice might work better on another kind of hardware. In either case, the expression reduces to 72 + 100 , so the choice doesn't affect the final value for this particular example. "But," you say, "multiplication associates from left to right. Doesn't that mean the leftmost multiplication is performed first?" (Well, maybe you don't say that, but somewhere someone does.) The association rule applies for operators that share an operand. For instance, in the expression 12 / 3 * 2 , the / and * operators, which have the same precedence, share the operand 3 . Therefore, the left-to-right rule applies in this case, and the expression reduces to 4 * 2 , or 8 . (Going from right to left would give 12 / 6 , or 2 . Here the choice does matter.) In the previous example, the two * operators did not share a common operand, so the left-to-right rule did not apply.
Let's try these rules on a more complex example (see Listing 5.7).
/* rules.c -- precedence test */ #include <stdio.h> int main(void) { int top, score; top = score = -(2 + 5) * 6 + (4 + 3 * (2 + 3)); printf("top = %d \n", top); return 0; }
What value will this program print? Figure it out; then run the program or read the following description to check your answer.
First, parentheses have the highest precedence. Whether the parentheses in -(2 + 5) * 6 or in (4 + 3 * (2 + 3)) are evaluated first depends on the implementation, as we just discussed. Either choice will lead to the same result for this example, so let's take the left one first. The high precedence of parentheses means that in the subexpression -(2 + 5) * 6 , you evaluate (2 + 5) first, getting 7. Next, you apply the unary minus operator to 7 to get -7 . Now the expression is this:
top = score = -7 * 6 + (4 + 3 * (2 + 3))
The next step is to evaluate 2 + 3 . The expression becomes
top = score = -7 * 6 + (4 + 3 * 5)
Next, because the * in the parentheses has priority over + , the expression becomes
top = score = -7 * 6 + (4 + 15)
and then
top = score = -7 * 6 + 19
Multiply -7 by 6 and get this expression:
top = score = -42 + 19
Then addition makes it
top = score = -23
Now score is assigned the value -23 , and, finally, top gets the value -23 . Remember that the = operator associates from right to left.
I l @ ve RuBoard |