| < Day Day Up > |
|
Most of the expressions listed in table 5-3 involve the use of multiplicative, additive, relational, conditional, and assignment operators. You may already have a fundamental understanding of how these operators work, especially the multiplicative, additive, and assignment. I will discuss these and a few others in this section. Any operator I fail to discuss here will be introduced to you later in the book when you are ready to learn its use.
Before I talk about each operator I want to talk briefly about operator precedence.
Operators in C++ have a precedence associated with their use. Table 5-4 lists C++ operators in order of their precedence, from highest to lowest, along with their associativity. The use of parentheses is covered in the next section. Use them and you will have fewer bugs in your code and fewer headaches.
Operator | Description | Associates |
++ | Post-increment | Left to right |
-- | Post-decrement | Left to right |
( ) | Function call | Left to right |
[ ] | Array element | Left to right |
-> | Pointer to structure member | Left to right |
. | Structure or union member | Left to right |
++ | Pre-increment | Right to left |
-- | Pre-decrement | Right to left |
! | Logical NOT | Right to left |
~ | Bitwise NOT | Right to left |
- | Unary minus | Right to left |
+ | Unary plus | Right to left |
& | Address | Right to left |
* | Indirection | Right to left |
sizeof | Size in bytes | Right to left |
new | Allocate program memory | Left to right |
delete | Deallocate program memory | Left to right |
(type) | Type cast (includes all C++ cast operators) | Left to right |
.* | Pointer to member (objects) | Left to right |
->* | Pointer to member (pointers) | Left to right |
* | Multiply | Left to right |
/ | Divide | Left to right |
% | Modulo or Remainder | Left to right |
+ | Add | Left to right |
- | Subtract | Left to right |
<< | Left shift | Left to right |
>> | Right shift | Left to right |
< | Less than | Left to right |
<= | Less than or equal to | Left to right |
> | Greater than | Left to right |
>= | Greater than or equal to | Left to right |
== | Equal to | Left to right |
!= | Not equal to | Left to right |
& | Bitwise AND | Left to right |
^ | Bitwise exclusive OR | Left to right |
| | Bitwise OR | Left to right |
&& | Logical AND | Left to right |
|| | Logical OR | Left to right |
? : | Conditional | Right to left |
= | Assignment | Right to left |
*=, /=, %=, +=, -= <<=, >>=, &=, ^=, |= | Compound assignment | Right to Left |
, | Comma | Left to right |
Using table 5-4 as a guide, can you determine what value will be printed to the screen when this statement executes?
cout<<7 * 3 + 1 - 201 % 20<<endl; //version 1
How about the next statement?
cout<<(7 * (3 + 1)) - (201 % 20)<<endl; //version 2
And this one?
cout<<((((7 * 3) + 1) - 201) % 20)<<endl; //version 3
As you might guess, each version of the expression results in a different value. Version 1 results in a value derived from performing the computations using each operator’s native precedence. Versions 2 and 3 result in different values because the parentheses force a different order of computation.
From a human perspective, versions 2 and 3 are easier to understand. Version 1 takes a little effort unless you are already familiar with the precedence of the operators used. The next version produces the same result as version 1:
cout<<(((7 * 3) + 1) - (201 % 20))<<endl; //version 4
The parentheses make the expression easier to read and understand. Now you are faced with a dilemma; try and memorize the precedence of every operator, or, use parentheses and simplify your life! Choose wisely grasshopper!
Table 5-5 lists the three multiplicative operators.
Operator | Description |
---|---|
* | multiplication |
/ | division |
% | modulus |
The multiplication and division operators are overloaded to work on all the arithmetic types such as float, double, and integer, and enumerations. The modulus operator works on integral type and enumerations only.
The asterisk is used as the multiplication operator. The following code gives an example of its use:
int i=0, j=10, k=10000; i = j*k; cout<<i<<endl;
Be careful when using the multiplication operator. You can easily calculate a value that is too big to fit into a small integer variable. The following code looks like the previous example with one exception. Can you spot the difference?
int j=10, k=10000; short i = j*k; cout<<i<<endl;
The variable named i is now declared a short, which holds half as much as a regular integer. This causes a truncation of the larger value to a size that will fit into the smaller data type. These types of errors are easy to make and hard to detect because the compiler offers no warning.
The asterisk, like other symbols in C++, is overloaded to perform more than just multiplication in C++. It is also used to declare and dereference pointers. (See chapter 8) As you gain experience reading and writing C++ code you will become comfortable recognizing the context in which operators are used, but, until that happens, you will be a little confused to see what you think is the multiplication operator being used for something other than multiplication.
The division operator works as you would expect although there are a few issues to keep in mind when you use it. The following statement shows the division operator in use:
float f = 3.5f / 1.5f;
In this example, a float variable named f is declared and initialized to the value of 2.33333. In this case, each of the numeric literals are of type float and the size of the result fits into the variable f. You will run into trouble when you attempt to store the results of floating point division into an integer variable. The following statement shows an example:
int result = 3.5f/1.5f;
Integer types are not designed to represent the decimal portion of floating point values so you will lose the .33333 portion of the result. The variable result will be initialized with the value 2 and you will not be warned about the loss of numeric precision.
You can perform division on complex expressions by using parentheses. The following statement gives an example:
float f = (3.5 + 1.5) / 2.5;
You should also be aware that an attempt to divide a number by zero will result in a compiler warning.
The modulus or remainder operator works like the division operator but returns the remainder and discards the quotient. The following statement shows the modulus operator in use:
int remainder = 215 % 20;
This statement declares an integer variable named remainder and initializes its value to 15.
The primary thing to remember with the modulus operator is that it is to be used on integral types only. Using it on floating point literals or variables results in a compiler error.
Table 5.6 lists the additive operators.
Operator | Description |
---|---|
+ | Addition |
- | Subtraction |
The addition operator performs arithmetic addition on arithmetic, enumeration, or pointer types. The following code shows an example of the addition operator being used with an enumeration type:
enum set_one {up, down, left, right}; ... int where = 1 + down;
In this example, an enumerated type named set_one is declared with the four enumerations up, down, left, right. The value of up is 0, the value of down 1, the value of left 2, and the value of right 3. (Enums are covered in more detail in chapter 10) The integer variable where is declared and initialized to the value of the integer literal 1 plus the enumeration value of down.
The following code gives an example of the addition operator being used with a pointer operand:
int int_array[] = {1,2,3,4,5}; int int_val = *(int_array + 3);
In this example an array of integers named int_array is declared and initialized with five integer values. On the next line an integer variable named int_val is declared and initialized to the value that resides at the 4th element of the array. In this case the value stored in the 4th element of the array is 4. Arrays are covered in excruciating detail in chapter 8 but here’s a quick explanation of what’s going on here. The array name int_array points to the start of the integer array. This means the name of the array is a pointer, which means it contains a memory address. Since the addition operator is being applied to a memory address that points to an array of integer elements, the value 3 means 3 integer storage units. The addition will be the memory address of the array + (3 times the size of an integer in bytes) or array address + (3 x 4) or array address + 12.
The value resulting from the addition is a memory address or pointer. The overloaded asterisk symbol, in this case used as the pointer dereferencing operator, must be applied to the result to obtain the actual integer object residing in the 4th element of the array. It is this value that is ultimately assigned to the integer variable named int_val.
Don’t worry if you don’t fully understand all this pointer stuff right now. After you read chapters 7 and 8 you will be an expert!
Besides being used for traditional arithmetic subtraction operations on arithmetic data types, the subtraction operator can be used on pointers as well. The following code demonstrates the use of the subtraction operator on pointer types:
int_val = *((&int_array[4]) - 3);
Using the integer array from the previous example, the subtraction operator is being used to subtract 3 integer storage units from the address of the 5th element of int_array. This will result in the value 12 being subtracted from the address of the 5th array element which will yield the address of the 2nd element. The result of the subtraction is a memory address and must be dereferenced to access the integer object stored at that address. When this statement is executed the integer value 2 will be assigned to the variable named int_val.
Shift operators let you perform bit shifting operations on integral objects. Table 5-7 lists the shift operators.
Operator | Description |
---|---|
<< | Left Shift |
>> | Right Shift |
The following code shows the left shift operator in use.
unsigned shift_val = 1; shift_val = shift_val << 1;
The first statement declares an unsigned integer variable named shift_val and initializes its value to 1. In a computer with 32 bit registers the value 1 looks like this in binary:
00000000000000000000000000000001
The second statement shifts the bits of shift_val to the left by one bit and assigns the result of the shift operation back to the shift_val variable. As the bits are shifted to the left, the right-hand replacement bits are set to zero. Figure 5-8 shows what happens to shift_val when its bits are shifted to the left four times.
Figure 5-8: Left Shifting shift_val
The effect of left shifting a bit value by one bit is the same as multiplying the value by two. But you need to be careful and not left shift it too far. You also have to pay attention to the type of bit pattern you are shifting. For instance, left shifting a signed type will result in values going from positive to negative depending on the value of the bit that moves into the sign bit position.
The right shift operator works similar to the left shift operator. If the value being shifted is an unsigned type or a signed type with a positive value then the effect of shifting the bits to the right by one bit will be the same as dividing the value by 2. If the value being shifted is a negative number then the result of shifting right is implementation dependent, meaning the operator’s behavior in this regard is left to the discretion of the compiler manufacturer. The following code shows the right shift operator in use on a negative number:
int shift_val = 0xFFFFFFFF; shift_val = shift_val >> 1;
The first statement declares the integer variable shift_val and initializes it to the hexadecimal value FFFFFFFF. This is the bit pattern for -1. When statement two is executed the bits are shifted to the right by one bit. On Metrowerks CodeWarrior the value remains -1. This is due to the bits coming in from the left being set to 1, which keeps the sign bit set.
It will be helpful to see another example. The following code initializes shift_val to the maximum negative number an integer can hold then shifts it to the right by one bit.
int shift_val = 0x80000000; shift_val = shift_val >> 1;
In the first statement, shift_val is initialized to the value -2,147,483,648. After the execution of the second statement its value will be -1,073,741,824. Shifting the negative number the right by one bit has the effect of dividing the number by 2. However, this will only work until you have shifted all the way to the right, at which time the value will be -1. Further right shifting will have no effect. Figure 5-9 shows what happens to the variable shift_val when the right shift operator is applied 4 times.
Figure 5-9: Right Shifting shift_val
You can see in figure 5-9, with a negative number, the bits shifted into the bit pattern in the most significant bit position are set to 1. This keeps the value negative.
If you start with a positive number in a signed integer type the number will remain positive as you shift bits to the right. This means the bit being shifted into the most significant bit position is set to 0.
Table 5-8 lists the relational operators.
Operator | Description |
---|---|
< | Less Than |
> | Greater Than |
<= | Less Than or Equal To |
>= | Greater Than or Equal To |
Relational operators are used to compare the value of arithmetic, enumeration, or pointer objects. A relational operator returns a boolean value which is either true or false.
The less than operator takes two operands and returns true if the left operand is less than the right operand. The following statement illustrates the use of the less than operator:
bool result = 3 < 5;
When this statement is executed the boolean variable named result will be initialized to the value true or 1.
The greater than operator takes two operands and returns true if the left operand is greater than the right operand. The following statement illustrates the use of the greater than operator:
bool result = 3 > 5;
In this case, the variable result will be initialized to false or 0.
The less than or equal to operator takes two operands and returns true if the left operand is of lesser value or equal to the right operand. The following statement illustrates the use of the less than or equal to operator:
bool result = 3 <= 5;
When this statement is executed the boolean variable result will be initialized to true or 1.
The greater than or equal to operator takes two operands and returns true if the left operand is of greater value or equal to the right operand. The following statement illustrates the use of the greater than or equal to operator:
bool result = 3 >= 5;
When this statement executes result will be initialized to the value false or 0.
You will use relational operators heavily to make decisions in your source code in order to figure out what to do next. You will see more about relational operators covered in chapter 6 when I discuss how to control the flow of program execution.
Table 5-9 lists the equality operators. Equality operators can compare arithmetic, enumeration, and pointer values just like the relational operators but have a lower precedence.
Operator | Description |
---|---|
== | Equal To |
!= | Not Equal To |
The equal to operator compares two operands for equality and returns a boolean value of true or false based on the result of the comparison. The following statement shows the equality operator in action:
bool result = 3 == 5;
This statement will initialize the boolean variable result to false.
The not equal to operator compares two operands and returns true if they are not equal. The following code illustrates the use of the not equal to operator:
bool result = 3 != 5
This statement will initialize the variable result to true or 1.
The bitwise AND operator takes two operands, ANDs them, and returns the result. What the heck is an AND operation you ask?
Figure 5-10: AND Truth Table
An AND operation compares two bits. The result of the bit comparison is either 1 or 0 depending on the state of the two bits compared. An AND on two bits will result in a true or 1 output only when both bits being compared are true or 1. Figure 5-10 gives the truth table for an AND operation. The following statement shows the bitwise AND operator in use:
int and_result = 0x00000001 & 0xFFFFFFFF;
In this example, the variable and_result will be initialized to the result of the AND operation which, in this case, is 1. Take a look at this operation again in slow motion:
00000000000000000000000000000001 11111111111111111111111111111111 00000000000000000000000000000001
When the hexadecimal is converted to binary you can easily see the only bit comparison that results in a true is the two least significant bits. The rest of the bits are set to false or 0 because only one bit in each comparison is on or true. I use the terms on, true, and 1 interchangeably here as well as off, false, or 0.
Bitwise exclusive OR operations take place according to the truth table shown in figure 5-11. The following statement illustrates the use of the exclusive OR operator in action:
Figure 5-11: Exclusive OR Truth Table
int ex_or_result = 0x00000001 ^ 0xFFFFFFFF;
When this statement is executed the variable ex_or_result will be initialized to 0xFFFFFFFE. Take a look at the binary version:
00000000000000000000000000000001 11111111111111111111111111111111 11111111111111111111111111111110
Figure 5-12: Inclusive OR Truth Table
The bitwise inclusive OR takes two operands and returns the result of an inclusive OR comparison between each bit. Figure 5-12 gives the truth table for an inclusive OR operation. The following statement show the bitwise inclusive OR operator in action:
int in_or_result = 0x00000001 | 0xFFFFFFFF;
The variable in_or_result will be set to the value 0xFFFFFFFF. Examine the binary version:
00000000000000000000000000000001 11111111111111111111111111111111 11111111111111111111111111111111
In this case all bits are set to 1 as the truth table for the inclusive OR operation would suggest.
The logical AND operator takes two boolean expressions as operands and returns true or false based on the truth table given in figure 5-10. This is different from its bitwise counterpart in that it is not comparing bits, rather, it is comparing the result of one expression to that of another, and returning a result base on the result of the comparison. The following code gives an example of the logical AND operator in use:
int a = 0; boolean and_result = (a<5) && (true);
On the first line an integer variable named a is declared and initialized to 0. On the second line the boolean variable and_result is declared and initialized to the result of the logical AND expression. The logical AND operator will compare the results of (a<5), which is true, to the (true), which is always true.
The logical OR operator makes comparisons of two boolean expressions according to the truth table shown in Figure 5-12. The following code gives an example of the logical OR in use:
int a = 0; boolean or_result = (a<5) || (true);
In this example, the boolean variable or_result is initialized to the result of the OR comparison between (3<5) which is true, and (true), which is still true!
This is a cool operator but until you get used to it you will look a little cross-eyed at it when you see it in source code. To discuss the conditional operator I have to get ahead of my story a little bit. The conditional operator is a shorthand way of writing an if statement. If statements are covered in detail in chapter 6.
The conditional operator will evaluate a boolean expression and offer two possible alternatives, depending on the result of the evaluation. The expression to be evaluated comes before the question mark. The first alternative, or statement you want to execute if the expression is true is placed to the right of the question mark and to the left of the colon. The statement you want to execute if the expression evaluates to false is placed to the right of the colon. Use this handy map to the conditional operator if you get lost while trying to impress your friends by using it:
Figure 5-13: Conditional Operator Map
The following statement shows the conditional operator in use:
(3>5) ? cout<<“True statement”<<endl : cout<<“False statement”<<endl;
When this statement is executed the expression (3>5) will be evaluated and result in a boolean value of false. This will cause the false statement to be executed. In this case the text False statement will be printed to the screen. The following if statement will do the same thing:
if(3>5) cout<<“True statement”<<endl; else cout<<“False statement”<<end;
Table 5-10 lists the assignment operators.
Operator | Description |
---|---|
= | Assignment |
*= | Compound Multiplication Assignment |
/= | Compound Division Assignment |
%= | Compound Modulus Assignment |
+= | Compound Addition Assignment |
-= | Compound Subtraction Assignment |
>>= | Compound Right Shift Assignment |
<<= | Compound Left Shift Assignment |
&= | Compound Bitwise AND Assignment |
^= | Compound Bitwise Exclusive OR Assignment |
|= | Compound Inclusive OR Assignment |
You have seen the assignment operator, =, in action many times in this chapter. The important thing to remember when using the assignment operator is that it is not the Equal To operator, ==, which is an equality operator vice an assignment operator. To better understand the use of the assignment operator it is helpful to know the difference between an “lvalue” and an “rvalue”.
If you are new to C++ programming you have probably seen the compiler error, “Not an lvalue...”, and wondered what it meant.
When making an assignment using an assignment operator, what you are trying to do is assign the result, or value, of some expression to a memory location. The assignment expression, when viewed in this context, looks like this:
Figure 5-14: Assignment Operator Operands
An assignment is an expression and therefore returns a result. The type of the result is the type that was stored in the memory location pointed to by the left operand. The following example might help clarify this concept:
int a = 0, b = 0; a = (b = 5);
In this example, two integer variables named a and b were declared and initialized to 0 using the assignment operator. The second statement assigns to the variable b the value 5. The result of the assignment is 5 and is assigned to the variable a.
The rest of the assignment operators are known as compound assignment operators and are used as a shorthand way of getting things done in C++. The following code shows the compound multiplication assignment operator in action:
int a = 3; a *= 3;
The first statement declares the integer variable named a and assigns it the value 3. The next statement multiplies a by 3 and assigns the result back to the variable a. The second statement is equivalent to the following longer version that does the same thing:
a = a * 3;
The rest of the compound operators work the same way.
The comma operator can be used to separate expressions. The following code shows the comma operator in use:
int a = 0, b = 0, c = 0; a = (c = 10), (b = 8); (3 < 5), (a = 0);
The first statement shows the comma operator being used to separate variable declarations and assignments on the same line. The second statement shows two assignment expressions being separated by the comma operator. The third statement is something not often seen but reiterates the use of the comma operator’s ability to separate any expression, not just assignment expressions.
There are many times during the course of programming that you need to increment or decrement a variable by 1. You could increment the old fashioned way...
i = i + 1; or i += 1;
...in this example the variable i is being set to the value it contains plus 1, or use the ++ operator:
i++; or ++i;
There are two versions of the increment and decrement operators: prefix and postfix. Consider the following example:
int a = 0, i = 1; a = i++;
This is an example of the postfix version of the increment operator in use. When the second statement executes a will be assigned the value of 1, which is the value of i before the increment operator expression is evaluated. The following example demonstrates the use of the prefix version of the increment operator and will result in the variable a being assigned the value 2:
int a = 0, i = 1; a = ++i;
The decrement operator works the same way. You will most likely see the increment and decrement operators used in for statements to do array processing. The following code gives an example:
for(int i=0; i<ARRAY_SIZE, i++){ //array processing statements here }
Chapter 6 discusses program control flow statements like the for statement, above, in detail.
| < Day Day Up > |
|