Table 4-1 The Simple Operators-unary Take the negative of Most of these operators are called binary operators because they operate on two values: one on the left side of the operator and
Trang 1Chapter 4 Smooth Operators
In This Chapter
Performing a little arithmetic
Doing some logical arithmetic
Complicating matters with compound logical operators
Mathematicians create variables and manipulate them in various ways, adding them, multiplying them, and — here’s a toughie — even integrat-ing them Chapter 3 describes how to declare and define variables However, it says nothing about how to use variables to get anything done after you’ve declared them This chapter looks at the operations you can perform on
vari-ables to actually get something done Operations require operators, such as +,
-, =, <, and & I cover arithmetic, logical, and other operators in this chapter Writing programs that get things done is good You’ll never make it as a C# programmer if your programs don’t actually do something — unless, of course, you’re a consultant
Performing Arithmetic
The set of arithmetic operators breaks down into several groups: the simple arithmetic operators, the assignment operators, and a set of special opera-tors unique to programming After you’ve digested these, you also need to
digest a separate set of logical operators Bon appétit!
Simple operators You learned most of the simple operators in elementary school Table 4-1
lists them Note: Computers use an asterisk (*) for multiplication, not the
multiplication sign (×)
Trang 2Table 4-1 The Simple Operators
-(unary) Take the negative of
Most of these operators are called binary operators because they operate on
two values: one on the left side of the operator and one on the right side The one exception is the unary negative However, this one is just as straightfor-ward as the others, as I show in the following example:
int n1 = 5;
int n2 = -n1; // n2 now has the value -5
The value of -nis the negative of the value of n The modulo operator may not be quite as familiar to you Modulo is similar
to the remainder after division Thus, 5 % 3is 2 (5 / 3 = 1, remainder 2), and
25 % 3is 1 (25 / 3 = 8, remainder 1) Read it “five modulo three” or simply
“five mod three.”
The strict definition of % is “the operator such that: x = (x / y) + x % y.” Divide
xby y Add xmodulo y(which equals the remainder after x / y) The result
is x The arithmetic operators other than modulo are defined for all the numeric types The modulo operator is not defined for floating point numbers because you have no remainder after division of floating point values
Operating orders The value of some expressions may not be clear Consider, for example, the following expression:
int n = 5 * 3 + 2;
Does the programmer mean “multiply 5 times 3 and then add 2,” which is 17, or does this line mean “multiply 5 times the sum of 3 and 2,” which gives you 25?
Trang 3C# generally executes common operators from left to right So, the preceding example assigns the value 17 to the variable n
C# determines the value of nin the following example by first dividing 24 by 6 and then dividing the result of that operation by 2 (as opposed to dividing 24
by the ratio 6 over 2):
int n = 24 / 6 / 2
However, the various operators have a hierarchy, or order of precedence C#
scans an expression and performs the operations of higher precedence before those of lower precedence For example, multiplication has higher precedence than addition Many books take great pains to explain the order
of precedence, but frankly that’s a complete waste of time (and brain cells)
Don’t rely on yourself or someone else knowing the precedence order Make your meaning (to human readers of the code as well as to the compiler) explicit with parentheses
The value of the following expression is clear, regardless of the operators’
order of precedence:
int n = (7 % 3) * (4 + (6 / 3));
Parentheses can override the order of precedence by stating exactly how the compiler is to interpret the expression C# looks for the innermost parentheses for the first expression to evaluate, dividing 6 by 3 to yield 2 The result follows:
int n = (7 % 3) * (4 + 2); // 2 = 6 / 3
Then C# works its way outward, evaluating each set of parentheses in turn, innermost to outermost, as follows:
int n = 1 * 6; // 6 = (4 + 2)
And here’s the final result:
int n = 6
The “always use parentheses” rule has perhaps one exception I don’t con-done this behavior, but many programmers omit parentheses in examples like the following, because multiplication has higher precedence than addi-tion Consider the following example:
int n = 7 + 2 * 3; // same as 7 + (2 * 3)
In this case, the value of the variable nis 13 (not 27)
Trang 4The assignment operator C# has inherited an interesting concept from C and C++: Assignment is itself a binary operator The assignment operator has the value of the argument to the right The assignment has the same type as both arguments, which must match
This new view of the assignment operator has no effect on the expressions you’ve seen so far:
n = 5 * 3;
In this example, 5 * 3 is 15 and an int The assignment operator stores the
inton the right into the inton the left and returns the value 15 However, this new view of the assignment operator allows the following:
m = n = 5 * 3;
Assignments are evaluated in series from right to left The right-hand assign-ment stores the value 15 into nand returns 15 The left-hand assignment stores 15 into mand returns 15, which is then dropped on the floor, leaving the value of each variable as 15
This strange definition for assignment makes the following rather bizarre expressions legal (but I would avoid this):
int n;
int m;
n = m = 2;
I avoid chaining assignments that way because it’s less clear to human readers Anything that can confuse people reading your code (including you) is worth avoiding because confusion breeds errors A huge proportion of computer pro-gramming, from language rules and constructs to naming conventions and rec-ommended programmer practices, is devoted to fighting error Join the fight C# extends the simple operators with a set of operators constructed from other binary operators For example:
n += 1;
This expression is equivalent to the following:
n = n + 1;
An assignment operator exists for just about every binary operator I’m really not sure how these various assignment operators came to be, but there they are Table 4-2 shows the most common compound assignment operators
Trang 5Table 4-2 Common Compound Assignment Operators
a += b Assign a + b to a
a -= b Assign a - b to a
a *= b Assign a * b to a
a /= b Assign a / b to a
a %= b Assign a % b to a
a &= b Assign a & b to a (& is a logical operator, discussed later)
a |= b Assign a | b to a (| is a logical operator)
a ^= b Assign a ^ b to a (^ is a logical operator)
Table 4-2 omits a couple of advanced compound assignment operators, <<=
and >>= I mention the “bit-shifting” operators later in the chapter
The increment operator
Of all the additions that you may perform in programming, adding 1 to a vari-able is the most common, as follows:
n = n + 1; // increment n by 1
Why have an increment operator?
The reason for the increment operator lies in the obscure fact that the PDP-8 computer of the 1970s had an increment instruction This would
be of little interest today were it not for the fact that the C language, the original precursor to C#, was originally written for the PDP-8 Because that machine had an increment instruction, n++
generated fewer machine instructions than n =
n + 1 As slow as those machines were, saving
a few machine instructions was a big deal
Today, compilers are smarter and no difference exists in the execution time for n++and n = n + 1, so the increment operator is no longer
needed However, programmers are creatures
of habit, and the operator remains to this day
You almost never see a C++ programmer incre-ment a value using the longer but more intuitive
n = n + 1 Instead, you see the increment operator
Further, when standing by itself (that is, not part
of a larger expression), the postincrement oper-ator almost always appears instead of the preincrement operator There’s no reason other than habit and the fact that it looks cooler, espe-cially to C++ programmers: n++
Trang 6C# defines the assignment operator shorthand as follows:
n += 1; // increment n by 1
Even that’s not good enough C# provides this even shorter version:
++n; // increment n by 1
All three of the preceding statements are equivalent — they all increment n
by 1
The increment operator is strange enough, but believe it or not, C# has two increment operators: ++nand n++ The first one, ++n, is called the
preincre-ment operator, while n++is the postincrement operator The difference is
subtle but important
Remember that every expression has a type and a value In the following code, both ++nand n++are of type int:
int n;
n = 1;
int p = ++n;
n = 1;
int m = n++;
But what are the resulting values of mand p? (Hint: The choices are 1 or 2.) The value of pis 2, and the value of mis 1 That is, the value of the expression
++nis the value of nafter being incremented, while the value of the
expres-sion n++is the value of nbefore it is incremented Either way, the resulting
value of nis 2
Equivalent decrement operators — that is, n and n— exist to replace n
= n – 1 These work in exactly the same way as the increment operators
Performing Logical Comparisons —
Is That Logical?
C# also provides a set of logical comparison operators, as shown in Table 4-3
These operators are called logical comparisons because they return either a
trueor a falseof type bool
Trang 7Table 4-3 The Logical Comparison Operators
Operator Operator Is True If
a == b a has the same value as b
a > b a is greater than b
a >= b a is greater than or equal to b
a < b a is less than b
a <= b a is less than or equal to b
a != b a is not equal to b
Here’s an example that involves a logical comparison:
int m = 5;
int n = 6;
bool b = m > n;
This example assigns the value falseto the variable bbecause 5 is not greater than 6
The logical comparisons are defined for all numeric types, including float,
double, decimal, and char All the following statements are legal:
bool b;
b = 3 > 2; // true
b = 3.0 > 2.0; // true
b = ‘a’ > ‘b’; // false - alphabetically later = greater
b = ‘A’ < ‘a’; // true - upper A is less than lower a
b = ‘A’ < ‘b’; // true - all upper are less than all lower
b = 10M > 12M; // false
The comparison operators always produce results of type bool The compar-ison operators other than ==are not valid for variables of type string (Not
to worry; C# offers other ways to compare strings.)
Comparing floating point numbers:
Is your float bigger than mine?
Comparing two floating values can get dicey, and you need to be careful with these comparisons Consider the following comparison:
Trang 8float f1;
float f2;
f1 = 10;
f2 = f1 / 3;
bool b1 = (3 * f2) == f1;
f1 = 9;
f2 = f1 / 3;
bool b2 = (3 * f2) == f1;
Notice that the fifth and eighth lines in the preceding example each contain first an assignment operator (=) and then a logical comparison (==) These are different animals C# does the logical comparison and then assigns the result to the variable on the left
The only difference between the calculations of b1and b2is the original value of f1 So, what are the values of b1and b2? The value of b2is clearly
true: 9 / 3 is 3; 3 * 3 is 9; and 9 equals 9 Voilà!
The value of b1is not so obvious: 10 / 3 is 3.333 3.333 * 3 is 9.999 Is 9.999 equal to 10? That depends on how clever your processor and compiler are On a Pentium or later processor, C# is not smart enough to realize that b1
should be trueif the calculations are moved away from the comparison You can use the system absolute value function to compare f1and f2as follows:
Math.Abs(f1 - 3.0 * f2) < 00001; // use whatever level of accuracy
This function returns truefor both cases You can use the constant
Double.Epsiloninstead of 00001 to get the maximum level of accuracy
Epsilonis the smallest possible difference between two nonequal double
variables
For a self-guided tour of the System.Mathclass, where Absand many other
useful mathematical functions live, choose Help➪Index and type Math in the
Look For box
Compounding the confusion with compound logical operations
The boolvariables have another set of operators defined just for them, as shown in Table 4-4
Trang 9Table 4-4 The Compound Logical Operators
Operator Operator Is True If
!a a is false
a & b a and b are true
a | b Either a or b or else both are true (also known as a and/or b)
a ^ b a is true or b is true but not both (also known as a xor b)
a && b a is true and b is true with short-circuit evaluation
a || b a is true or b is true with short-circuit evaluation (I discuss
short-circuit evaluation in the nearby text.)
The !operator is the logical equivalent of the minus sign For example, !a
(read “not a”) is true if ais false and false if ais true Can that be true?
The next two operators are straightforward enough First, a & bis only true
if both aand bare true And a | bis true if either aor bis true (or both)
The ^(also known as exclusive or — xor) operator is sort of an odd beast An
exclusive or is true if either aor bis true but not if both aand bare true
All three operators produce a logical boolvalue as their result
The &, |, and ^operators also have a bitwise operator version When applied
to intvariables, these operators perform their magic on a bit-by-bit basis
Thus, 6 & 3 is 2 (01102& 00112is 00102), 6 | 3 is 7 (01102| 00112is 01112), and
6 ^ 3 is 5 (01102^ 00112is 01012) Binary arithmetic is really cool but beyond the scope of this book
The remaining two logical operators are similar to, but subtly different from, the first three Consider the following example:
bool b = (boolExpression1) & (boolExpression2);
In this case, C# evaluates boolExpression1and boolExpression2 It then looks to see whether they are both true before deciding the value of b However, this may be a wasted effort If one expression is false, there’s no reason to perform the other Regardless of the value of the second expres-sion, the result will be false
The &&operator enables you to avoid evaluating both expressions unneces-sarily, as shown in the following example:
bool b = (boolExpression1) && (boolExpression2);
Trang 10In this case, C# evaluates boolExpression1 If it’s false, bis set to false and the program continues on its merry way On the other hand, if boolExpression1is true, C# evaluates boolExpression2and stores the result in b
The &&operator uses short-circuit evaluation because it short-circuits around
the second boolean expression, if necessary
The ||operator works the same way, as shown in the following expression:
bool b = (boolExpression1) || (boolExpression2);
If boolExpression1is true, there’s no point in evaluating boolExpression2
because the result is always true
You can read these operators as “short-circuit and” and “short-circuit or.”
Finding the Perfect Date — Matching Expression Types
In calculations, an expression’s type is just as important as its value
Consider the following expression:
int n;
n = 5 * 5 + 7;
My calculator says the resulting value of nis 32 However, that expression also has a type
Written in “type language,” the preceding expression becomes the following:
int [=] int * int + int;
To evaluate the type of an expression, follow the same pattern you use to evaluate the expression’s value Multiplication takes precedence over addi-tion An inttimes an intis an int Addition comes next An intplus an int
is an int In this way, you can reduce the preceding expression as follows:
int * int + int int + int int