C and C++ are languages that are rich in operators. The languages provide
arithmetic, relational, logical, bitwise, conditional and many others.
Both C and C++ have a great many operators. In fact one
of the criticisms of the C language is that it has too many operators
which makes the language difficult to read. The operators fall into several
categories: arithmetic, logical, relational, bitwise, assignment and miscellaneous.
Within a category the operators can be classified into types: unary, binary,
or ternary. The type indicates the number of operands required with the
operator. For instance, a unary operator only requires a single operand,
whereas a binary type operator requires two operands, and of course ternary
type operators require three operands. The operators have an order of precedence
among themselves. This order of precedence dictates in what order the operators
are evaluated when several operators are together in a statement or expression.
Also, with each operator is an associativity factor that tells in what
order the operands associated with the operator are to be evaluated. The
following is a chart of the operators in the C and C++ language.
| Operators | Description | Associativity | |
|---|---|---|---|
| () | function call | left to right | |
| [] | array element | ||
| . | structure/union member | ||
| -> | structure/union member using pointer | ||
| ! | logical not | right to left | |
| ~ | one's complement | ||
| - | unary minus | ||
| ++ -- | increment/decrement | ||
| & | address of | ||
| * | indirection | ||
| (type) | type cast | ||
| sizeof | size in bytes | ||
| * | multiply | left to right | |
| / | divide | ||
| % | modulus | ||
| + | add | left to right | |
| - | subtract | ||
| << | left shift | left to right | |
| >> | right shift | ||
| < | less than | left to right | |
| <= | less than or equal | ||
| > | greater than | ||
| >= | greater than or equal | ||
| == | equal to | left to right | |
| != | not equal to | ||
| & | bitwise AND | left to right | |
| ^ | bitwise XOR | 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 | |
| *= | multiply and assign | ||
| /= | divide and assign | ||
| %= | modulo and assign | ||
| += | add and assign | /TR> | |
| -= | subtract and assign | ||
| <<= | left shift and assign | ||
| >>= | right shift and assign | ||
| &= | bitwise AND and assign | ||
| ^= | bitwise XOR and assign | ||
| |= | bitwise OR and assign | ||
| , | comma | left to right |
| Symbol | Operator | Example | * | multiplication | a*b |
|---|---|---|
| / | division | a/b |
| % | modulo | a%b |
| + | addition | a+b |
| - | subtraction | a-b |
All arithmetic operators evaluate from left to right. When
several arithmetic operators, in fact operators of any type, appear within an expression several passes of the expression may be necessary to completely evaluate the expression.
Problems can occur if you mix unsigned variables with variables of other data types. Due to differences in computer architecture, unsigned variables do not always convert to the larger data type. This can result in loss of accuracy, and even incorrect results. There are other times when you might want to fully control the type conversions instead of letting C and C++ make
its best bet.
You can override C and C++'s default conversions by specifying your own temporary type change. This process is called typecasting. When you typecast, you temporarily change a variable's data type from its declared data type to a new one. The two formats of the
typecast are:
(data type) expression /* ANSI C method */ and data type(expression) // C++ method
where data type can be any valid C and C++ data type, such as int or float, and the expression can be a variable, literal, or an expression that combines both. The following code temporarily typecast the integer variable age into a double floating-point variable, so that it can be multiplied by the double floating-point factor. Both formats of the typecast are illustrated.
age_factor = (double)age * factor;
The second way of typecasting puts the parentheses around the variable rather than the data type:
age_factor = double(age) * factor;
Instead of having C and C++ perform the conversion, you might want to
typecast all mixed expression to ensure that they convert the way you want
them to.
The sizeof operator returns the physical size, in bytes, of the data item for which it is applied. It can be used with any type of data item except bit fields. The general form is:
size_t sizeof( item );
When sizeof is used on a character field the result returned is 1 (if a character is stored in one byte). When used on an integer the result returned is the size in bytes of that integer. When used on an array the result is the number of bytes in the array, not the number of characters which appear before a NULL. In the ANSI standard the sizeof operator returns a data type of size_t which is usually an unsigned int value.
int nums[10];
printf("There are %d types in the array and %d elements" , sizeof( nums ), sizeof( nums ) / sizeof( int ) );
| Symbol | Operator | Example |
|---|---|---|
| < | less than | a < B |
| > | greater than | a > b |
| >= | greater than or equal | a >= b |
| == | equal to | a==b |
| != | not equal | a!=b |
| Sample | Operator | Example |
|---|---|---|
| ! | NOT | !(a < b) | /TR>
| && | AND | a < b && c > d |
| || | OR | a || d |
Relational and logical operators evaluate to only a true (1) or false
(0) value. Relational operators have a higher order of precedence that
logical operators and therefore are evaluated first in any expression that
includes both types of operators.
The conditional operator is C and C++'s only ternary operator. It works on three values as opposed to the binary operators you have seen that operate on only two valurs. The conditional operator is used to replace if-else logic in some situations. It is a two-symbol operator, ?:, with the following format:
result = conditional_expression ? expression1 :expression2;
The conditional_expression is any expression in C and C++ that results
in a True (nonzero) or False (zero) answer. If the result of conditional_expression
is true, expression1 executes. Otherwise, if the result of conditional_expression
is false, expression2 executes. Only one of the expressions following the
question mark ever executes. Only a single semicolon appears at the end
of expression2. The internal expressions, such as expression1, do not have
a semicolon. The resultant value generated by the expression that is executed
is returned and can be captured into the result identifier.
If you require simple if-else logic, the conditional operator usually
provides a more direct and succinct method, although you should always
prefer readability over compact code.
To glimpse the conditional operator at work, consider the section of code that follows:
if( a > b ) ans = 10; else ans = 25;
You can easily rewrite this kind of if-else code by using a single conditional operator.
ans = a > b ? 10 : 25;
| Symbol | Operator | Example |
|---|---|---|
| ++ | increment | a++ or ++a |
| -- | decrement | a-- or --a |
The decrement and increment operators function the same whether the
operator is pre (meaning before the operand) or post (meaning after the
operand). When the operators are used in conjunction with a conditional
test, such as in an if statement or a loop control conditional test, then
the placement of the operator in relationship to the operand is taken into
account as to when to decrement or increment the value of the operand.
| Symbol | Operator | Example |
|---|---|---|
| = | assignment | a = b |
| += | addition and assignment | a += b same as a = a + b |
| -= | subtraction and assignment | a -= b same as a = a - b |
| *= | multiplication and assignment | a *= b same as a = a * b |
| /= | division and assignment | a /= b same as a = a / b |
| %= | modulo and assignment | a %= b same as a = a % b |
| &= | bitwise AND assignment | a &= b same as a = a & b |
| |= | bitwise OR and assignment | a |= b same as a = a | b |
| ^= | bitwise XOR and assignment | a ^= b same as a = a ^ b |
| <<= | shift left and and assignment | a <<= 2 same as a = a << 2 |
| >>= | shift right and assignment | a >>= 4 same as a = a >> 4 |
The various assignment operators are intended as a shorthand method.
Depending upon the machine architecture, the shorthand notation could be
faster than the traditional method.
Each of the bitwise operators affects individual bits in a value.
Some of these operators are binary, taking two bits and returning a third
bit. Bitwise operators only work on values that reside in a word or less
of storage. Bitwise operators cannot be used with floating point, double,
or long values. The size of a word will vary from one machine architecture
to another.
The order of precedence for the bitwise operators is as follows:
| Operator | Description | Associativity |
|---|---|---|
| ~ | Ones complement | right to left |
| << | Left shift | left to right |
| >> | Right shift | left to right |
| & | Bitwise AND | left to right |
| ^ | Bitwise XOR | left to right |
| | | Bitwise OR | left to right |
This operator is a unary operator, that flips the value of each
bit. The operator takes a bit and converts it to 0 if the bit
was 1 and to 1 if the bit was 0.
| value | ~value | 0 | 1 |
|---|---|
| 1 | 0 |
Assuming 16 bit integers and that unsigned int Z is 0xA, the
expression ~Z has the value 0xFFF5 as shown below:
| Expression | Binary Representation | Value |
|---|---|---|
| Z | 0000 0000 0000 1010 | 0xA |
| ~Z | 1111 1111 1111 0101 | 0xFFF5 |
The bitwise complement operator should not be confused with the
arithmetic unary minus (-) or the logical negation (!). For
example, if Z is defined to be an int and set equal to 0, then -Z
results in 0 and !Z is 1, but ~Z yields -1 on a 2's complement
machine.
The bitwise complement operator is useful in writing portable
code as it avoids inclusion of machine-dependent information in
the program. For example, the statement
Z &= ~0xFF;
sets the last 8 bits of Z to 0, independent of word length.
C provides two bitwise shift operators, bitwise left shift (<<)
and bitwise right shift (>>), for shifting bits left or right by
an integral number of positions in integral data. Both of these
operators are binary, and the left operand is the integral data
whose bits are to be shifted, and the right operand, called the
shift count, specifies the number of positions by which bits need
shifting. The shift count must be nonnegative and less than the
number of bits required to represent data of the type of the left
operand.
Automatic unary conversions are performed on both operands.
However, the type of the result is that of the promoted left
operand; the right operand does not promte the result.
The result of applying these operators to signed operands is
implementation-dependent. For portability, therefore, these
operators should only be used on unsigned operands.
These operators can also be used, like other binary operators, to
form compound assignment operators >>= and <<=.
The left shift operator shifts bits to the left, and has the
formation
intvalue << intvalue
As bits are shifted toward high-order positions, 0 bits enter the
low-order positions. Bits shifted out through the high-order
position are lost. For example, given
unsigned int Z = 5;
and 16-bit integers, that is,
Z is 00000000 00000101
then
Z << 1 is 00000000 00001010 or 10 decimal
and
Z << 15 is 10000000 00000000 or 32768 decimal
The right shift operator shifts bits to the right, and has the
formation
intvalue >> intvalue
As bits are shifted toward low-order position, 0 bits enter the
high-order positions, if the data is unsigned. If the data is
signed and the sign bit is 0, then 0 bits also enter the high-
ordr positions. However, if the sign bit is 1, the bits entering
high-order positions are implementation-dependent. On some
machines 1s, and on others 0s, are shifted in. The former type
of operation is known as the arithmetic right shift, and the
latter type the logical right shift. For example, given
unsigned int Z = 40960;
and 16-bit integers, that is
Z is 10100000 00000000
then
Z >> 1 is 01010000 00000000 or 20480 decimal
and
Z >> 15 is 00000000 00000001 or 1 decimal
In the second example, the 1 originally in the fourteenth bit
position has dropped off. Another right shift will drop off the
1 in the first bit position, and Z will become zero.
The left shift of a value by one position has the effect of
multiplying the value by two, unless an overflow occurs due to a
1 falling off from the high-order position. Similarly, the right
shift of a value by one position has the effect of dividing the
value by two, provided the value is nonnegative. Here are some
examples, assuming 16-bit integers:
| unsigned int Z | Binary Representation | Decimal Value |
|---|---|---|
| Z=3 | 00000000 00000011 | 3 |
| Z << 1 | 00000000 00000110 | 6 |
| Z << 4 | 00000000 01100000 | 96 |
| Z << 9 | 11000000 00000000 | 49152 |
| Z << 1 | 10000000 00000000 | 32768 |
| Z >> 1 | 01000000 00000000 | 16384 |
| Z >> 9 | 00000000 00100000 | 32 |
| Z >> 4 | 00000000 00000010 | 2 |
| Z >> 1 | 00000000 00000001 | 1 |
| Z >> 1 | 00000000 00000000 | 0 |
The operation Z << 1, when the value of Z is 49152 results in an
overflow and does not have the effect of multiplication by 2.
However, the operation Z >> 1, when the value of Z is 1, has the
effect of integer division. Remember, shifting left is a
multiply by 2 at each bit position, where shifting right is
dividing by 2 at each bit position shifted.
Left and right shift operators have equal precedence and they
associate from left to right. Thus, the expression
1 << 1 >> 2
is interpreted as
(1 << 1) >> 2
The precedence of the shift operators is lower than that of any
arithmetic operator, but higher than that of any bitwise logical
operator except the unary bitwise complement operator. Thus, the
express
1 << 2 - 1
is interpreted as
1 << (2 -1)
and the expression
01 | ~01 << 1
is interpreted as
01 | ( ( ~01 ) << 1 )
This is not the address of operator, but the bitwise AND
operator. The address of operator is a unary operator that has
only one operand, whereas the bitwise AND operator is a binary
operator and thus requires two operands.
The bitwise AND operator & has the formation
intvalue & intvalue
When it is applied to two integral operands, the binary
representations of the converted values of the operands are
compared bit by bit. If Z1 and Z2 represent corresponding bits
of the two operands, then the result of Z1 & Z2 is shown in the
following truth table:
| Z1 | Z2 | Z1 & Z2 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 0 |
For example, given that
unsigned int Z1 = 0xA, Z2 = 0x7;
and that an integer is represented in 16 bits in the machine
being used, the expression Z1 & Z2 has the value 0x2 as show
below:
| Expression | Binary Representation | Value |
|---|---|---|
| Z1 | 0000 1010 | 0xA |
| Z2 | 0000 0111 | 0x7 |
| Z1 & Z2 | 0000 0010 | 0x2 |
The logical AND operator && and the bitwise AND operator & are quite different. The result of applying && is always 0 or 1, but that of & depends upon the values of the operands.
For example,
whereas the value of the expresseion 0xA && 0x7 is 1, the value
of 0xA & 0x7 is 0x2. Only in the special case when the values of
the operands are restricted to be 0 or 1 is the result of
applying & and && the same. Moreover, in the case of &&, the
second operand is not evaluated if the first operand is 0, but
both operands are evaluated in the case of &.
The bitwise AND operator is often used to turn some specified
bits off, that is, to set them to 0. For example, the statement
Z1 &= Z2;
as shown in the preceding example, turns off all but the low-
order three bits of Z1. Those three bits remain unchanged.
The bitwise inclusive OR operator |, frequently referred to
simply as the bitwise OR operator, has the formation
intvalue | intvalue
As in the case of the bitwise AND operator, when the bitwise OR
operator is applied to two integral operands, the binary
representations of the converted values of the operands are
compared bit by bit. If Z1 and Z2 represent corresponding bits
of the two operands, then the result of Z1 | Z2 is as shown in
the following truth table:
| Z1 | Z2 | Z1 | Z2 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
For example, given that
unsigned int Z1 = 0xA, Z2 = 0x7;
and that an integer is represented in 16 bits in the machine
being used, the expression Z1 | Z2 has the value 0xF as show
below:
| Expression | Binary Representation | Value |
|---|---|---|
| Z1 | 0000 1010 | 0xA |
| Z2 | 0000 0111 | 0x7 |
| Z1 | Z2 | 0000 1111 | 0xF |
The logical OR operator || and the bitwise OR operator | are also
quite different. The result of applying | depends upon the
values of the operands, but that of || is always 0 or 1. Only in
the special case when the values of the operands are restricted
to be 0 or 1 is the result of | and || the same. Further, in the
case of ||, the second operand is not evaluated if the first
operand is 1, but both operands are evaluated in the case of |.
The bitwise OR operator is frequently used to turn some specified
bits on, that is, to set them to 1. For example, the statment
Z1 |= Z2;
as shown in the preceding example, ensures that the three
rightmost bits of Z1 are turned on.
The bitwise exclusive OR operator ^, frequently referred to
as the bitwise XOR operator, has the formation
intvalue ^ intvalue
In the case of the bitwise exclusive OR also, the binary
representations of the converted values of the operands are
compared bit by bit. If Z1 and Z2 represent corresponding bits
of the two operands, then the result of Z1 ^ Z2 is as shown in
the following truth table:
| Z1 | Z2 | Z1 ^ Z2 |
|---|---|---|
| 1 | 1 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
For example, given that
unsigned int Z1 = 0xA, Z2 = 0x7;
and that an integer is represented in 16 bits in the machine
being used, the expression Z1 ^ Z2 has the value 0x9 as show
below:
| Expression | Binary Representation | Value |
|---|---|---|
| Z1 | 0000 1010 | 0xA |
| Z2 | 0000 0111 | 0x7 |
| Z1 ^ Z2 | 0000 1001 | 0x9 |
The exclusive OR operator has the property that any value XORed
with itself produces 0. as the result. Thus, we have
| Expression | Binary Representation | Value |
|---|---|---|
| Z1 | 0000 1010 | 0xA |
| Z1 | 0000 1010 | 0xA |
| Z1 ^ Z1 | 0000 0000 | 0x0 |
This property is often used by assembly language programmers to
set a value to 0 or to compare two values to determine if they
are equal.
Another useful property of this operator is that if the result of
XORing a value with another value is again XORed with the second
value, the result is the first value. Thus we have
| Expression | Binary Representation | Value |
|---|---|---|
| Z1 | 0000 1010 | 0xA |
| Z2 | 0000 0111 | 0x7 |
| Z1 ^ Z1 | 0000 0000 | 0x0 |
| Z1 ^ Z2 | 0000 1101 | 0xD |
| (Z1 ^ Z2) ^ Z2 | 0000 1010 | 0xA |
That is, (Z1 ^ Z2) ^ Z2 is equal to Z1. This property is
frequently used in designing bit-manipulation ciphers in
cryptography.
The bitwise exclusive OR operator can also be used to interchange
two values without using a temporary variable. Thus, the
statement
Z1 ^= Z2, Z2 ^= Z1, Z1 ^= Z2;
swaps the values of Z1 and Z2, as shown below:
| Expression | Binary Representation | Value |
|---|---|---|
| Z1 | 0000 1010 | 0xA |
| Z2 | 0000 0111 | 0x7 |
| Z1 ^= Z2 | 0000 1010 | 0xD |
| Z2 ^= Z1 | 0000 1101 | 0xA |
| Z1 ^= Z2 | 0000 0111 | 0x7 |
The order of precedence of the bitwise logical operators is
bitwise complement, bitwise AND, bitwise exclusive OR, and then
bitwise inclusive OR. Except for the bitwise complement that
associates from right to left, all others associate from left to
right. Thus, the expression
01 | ~01 ^ 01 & 01
is interpreted as
01 | ( ( ~01 ) ^ ( 01 & 01 ) )
Note that the precedence of the binary bitwise logical operators
is lower than the equality operator == and the inequality operator
!=. Thus, parentheses are necessary in expressions such as
( i & 01 ) == 0
or
( i ^ 01 ) != 0
The comma operator is used to insure that parts of an expression
are performed in a left to right sequence. The comma allows for
the use of multiple expressions to be used where normally only
one would be allowed. It is used most often in the for loop
statement where one statement is called for, but several actually
need to be coded.
The comma operator forces all operations that appear to the left
to be fully completed before proceeding to the right of comma.
This helps eliminate side effects of the expression evaluation.
num1 = num2 + 1, num2 = 2
The comma insures that num2 will not be changed to a 2 before
num2 has been added to 1 and the result placed into num1.
Other operators are also considered to be sequence points. They
are:
&&
||
?:
When any of these operators are encountered all activity
associated with any operator to the left is completed before the
new operator begins executing. Both the semicolon and the comma
also perform this service, insuring that there is a way to
control the order of when things happen in a program.
The commas that separate the actual arguments in a function call are punctuation symbols, not sequence points. A punctuation symbol does not guarantee that the arguments are either evaluated or passed to the function in any particular order.