Binary operators act on two operands in an expression. The binary operators are:
Multiplication (*)
Division (/)
Modulus (%)
Addition (+)
Subtraction (–)
Right shift (>>)
Left shift (<<)
Less than (<)
Greater than (>)
Less than or equal to (<=)
Greater than or equal to (>=)
Equal to (==)
Not equal to (!=)
Bitwise AND (&)
Bitwise exclusive OR (^)
Bitwise inclusive OR (|)
Logical AND (&&)
Logical OR (||)
The multiplicative operators are:
Multiplication (*)
Division (/)
Modulus or “remainder from division” (%)
These binary operators have left-to-right associativity.
multiplicative-expression:
pm-expression
multiplicative-expression*pm-expression
multiplicative-expression/pm-expression
multiplicative-expression%pm-expression
The multiplicative operators take operands of arithmetic types. The modulus operator (%) has a stricter requirement in that its operands must be of integral type. (To get the remainder of a floating-point division, use the run-time function, fmod.) The conversions covered in “Arithmetic Conversions” in Chapter 3, on topic are applied to the operands, and the result is of the converted type.
The multiplication operator yields the result of multiplying the first operand by the second.
The division operator yields the result of dividing the first operand by the second.
The modulus operator yields the remainder given by the following expression, where e1 is the first operand and e2 is the second: e1 – (e1 / e2) * e2, where both operands are of integral types.
Division by 0 in either a division or modulus expression is undefined and causes a run-time error. Therefore the following expressions generate undefined, erroneous results:
i % 0
f / 0.0
If both operands to a multiplication, division, or modulus expression have the same sign, the result is positive. Otherwise, the result is negative. The result of a modulus operation's sign is implementation-defined.
In Microsoft C++, the result of a modulus expression is always the same as the sign of the first operand. ¨
If the computed division of two integers is inexact and only one of the operands is negative, the result is the largest integer (in magnitude, disregarding the sign) that is less than the exact value the division operation would yield. For example, the exact value of –11 / 3 is –3.666666666 (not a rational number). The result of that integral division is –3.
The relationship between the multiplicative operators is given by the identity (e1 / e2) * e2 + e1 % e2 == e1.
Addition (+)
Subtraction (–)
These binary operators have left-to-right associativity.
additive-expression:
multiplicative-expression
additive-expression+multiplicative-expression
additive-expression–multiplicative-expression
The additive operators take operands of arithmetic or pointer types. The result of the addition (+) operator is the sum of the operands. The result of the subtraction (–) operator is the difference between the operands. If one or both of the operands are pointers, they must be pointers to objects, not to functions.
Additive operators take operands of arithmetic, integral, and scalar types. These are defined in Table 4.2:
Table 4.2 Types Used with Additive Operators
Type | Meaning |
arithmetic | Integral and floating types are collectively called “arithmetic” types. |
integral | Types char and int of all sizes (long, short) and enumerations are “integral” types. |
scalar | Scalar operands are operands of either arithmetic or pointer type. |
The legal combinations for these operators are:
arithmetic + arithmetic
scalar + integral
integral + scalar
arithmetic – arithmetic
scalar – scalar
Note that addition and subtraction are not equivalent operations.
If both operands are of arithmetic type, then the conversions covered in “Arithmetic Conversions” in Chapter 3, on topic are applied to the operands, and the result is of the converted type.
If one of the operands in an addition operation is a pointer to an array of objects, the other must be of integral type. The result is a pointer that is of the same type as the original pointer and that points to another array element. The following code fragment illustrates this concept:
short IntArray[10]; // Objects of type short occupy 2 bytes
short *pIntArray = IntArray;
for( int i = 0; i < 10; ++i )
{
*pIntArray = i;
cout << *pIntArray << “n”;
pIntArray = pIntArray + 1;
}
Although the integral value 1 is added to pIntArray, it does not mean “add 1 to the address”; rather it means “adjust the pointer to point to the next object in the array” (which happens to be 2 bytes away).
Note:
Code of the form pIntArray = pIntArray + 1 is rarely found in C++ programs; to perform an increment, these forms are preferable: pIntArray++ or pIntArray += 1.
If both operands are pointers, then the result of subtraction is the difference (in array elements) between the operands. The subtraction expression yields a signed integral result of type ptrdiff_t (defined in the standard include file STDDEF.H).
One of the operands can be of integral type, as long as it is the second operand. The result of the subtraction is of the same type as the original pointer. The value of the subtraction is a pointer to the (n – i)th array element, where n is the element pointed to by the original pointer and i is the integral value of the second operand.
The bitwise shift operators are:
Right shift (>>)
Left shift (<<)
These binary operators have left-to-right associativity.
shift-expression:
additive-expression
shift-expression<<additive-expression
shift-expression>>additive-expression
Both operands of the shift operators must be of integral types. Integral promotions are performed according to the rules described in “Integral Promotions” in Chapter 3, on topic . The type of the result is the same as the type of the left operand. The value of a right shift expression e1 >> e2 is e1 / 2e2, and the value of a left shift expression e1 << e2 is e1 * 2e2.
The results are undefined if the right operand of a shift expression is negative, or if the right operand is greater than or equal to the number of bits in the (promoted) left operand.
The left-shift operator causes the bit pattern in the first operand to be shifted left the number of bits specified by the second operand. Bits vacated by the shift operation are zero-filled. The shift is a logical shift as opposed to a shift-and-rotate operation.
The right-shift operator causes the bit pattern in the first operand to be shifted right the number of bits specified by the second operand. Bits vacated by the shift operation are zero-filled for unsigned quantities. For signed quantities, the sign bit is propagated into the vacated bit positions. The shift is a logical shift if the left operand is an unsigned quantity; otherwise, it is an arithmetic shift.
The result of a right shift of a signed negative quantity is implementation dependent. Although Microsoft C++ propagates the most-significant bit to fill vacated bit positions, there is no guarantee other implementations will do likewise.¨
Relational and Equality Operators
The relational and equality operators determine equality, inequality, or relative values of their operands. The relational operators are shown in Table 4.3.
Table 4.3 Relational and Equality Operators
Operator | Meaning |
== | Equal to |
!= | Not equal to |
< | Less than |
> | Greater than |
<= | Less than or equal to |
>= | Greater than or equal to |
The binary relational operators determine the following relationships:
Less than
Greater than
Less than or equal to
Greater than or equal to
relational-expression:
shift-expression
relational-expression<shift-expression
relational-expression>shift-expression
relational-expression<=shift-expression
relational-expression>=shift-expression
The relational operators have left-to-right associativity. Both operands of relational operators must be of arithmetic or pointer type. They yield values of type int. The value returned is 0 if the relationship in the expression is false; otherwise, it is 1. Consider the following code that demonstrates several relational expressions:
#include <iostream.h>
int main()
{
cout << “The true expression 3 > 2 yields: ”
<< (3 > 2) << “\n”;
cout << “The false expression 20 < 10 yields: ”
<< (20 < 10) << “\n”;
cout << “The expression 10 < 20 < 5 yields: ”
<< (10 < 20 < 5) << “\n”;
return 0;
}
The output from this program is:
The true expression 3 < 2 yields 1
The false expression 20 < 10 yields 0
The expression 10 < 20 < 5 yields 1
The expressions in the example above must be parenthesized because the insertion operator (<<) has higher precedence than the relational operators. Therefore, the first expression without the parentheses would be evaluated as follows:
(cout << “The true expression 3 > 2 yields: ” << 3) < (2 << “\n”);
Note that the third expression evaluates to 1—because of the left-to-right associativity of relational operators, the explicit grouping of the expression 10 < 20 < 5 is:
(10 < 20) < 5
Therefore, the test performed is:
1 < 5
and the result is 1 (or true).
The usual arithmetic conversions covered in “Arithmetic Conversions” in Chapter 3, on topic are applied to operands of arithmetic types.
Comparing Pointers Using Relational Operators
When two pointers to objects of the same type are compared, the result is determined by the location of the objects pointed to in the program's address space. Pointers can also be compared to a constant expression that evaluates to 0 or to a pointer of type void *. If a pointer comparison is made against a pointer of type void *, the other pointer is implicitly converted to type void *. Then the comparison is made.
Two pointers of different types cannot be compared unless:
One type is a class type derived from the other type.
At least one of the pointers is explicitly converted (cast) to type void *. (The other pointer is implicitly converted to type void * for the conversion.)
Two pointers of the same type that point to the same object are guaranteed to compare equal. If two pointers to nonstatic members of an object are compared, the following rules apply:
If the class type is not a union, and if the two members are not separated by an access-specifier, such as public, protected, or private, the pointer to the member declared last will compare greater than the pointer to the member declared earlier. (For information on access-specifier, see the “Syntax” section on topic in Chapter 10.)
If the two members are separated by an access-specifier, the results are undefined.
If the class type is a union, pointers to different data members in that union compare equal.
If two pointers point to elements of the same array or to the element one beyond the end of the array, the pointer to the object with the higher subscript compares higher. Comparison of pointers is guaranteed valid only when the pointers refer to objects in the same array or to the location one past the end of the array.
Microsoft Specific
In Microsoft C++, far pointers that are compared for magnitude (using any operator other than ==) are compared using their offsets only. The segments are not considered in the comparison. Far pointers compared for equality, however, are compared using a segment and offset comparison. ¨Equality Operators
The binary equality operators compare their operands for strict equality or inequality.
equality-expression:
relational-expression
equality-expression==relational-expression
equality-expression!=relational-expression
The equality operators, equal to (==) and not equal to (!=), have lower precedence than the relational operators, but they behave similarly in all other respects.
The equal-to operator (==) returns true if both operands have the same value; otherwise it returns false. The not-equal-to operator (!=) returns true if the operands do not have the same value; otherwise it returns false.
Equality operators can compare pointers to members of the same type. In such a comparison, pointer-to-member conversions, as discussed in “Pointer-to-Member Conversions” in Chapter 3, on topic , are performed. Pointers to members can also be compared to a constant expression that evaluates to 0.
Bitwise AND (&)
Bitwise exclusive OR (^)
Bitwise inclusive OR (|)
These operators return bitwise combinations of their operands.
The bitwise AND operator (&) returns the bitwise AND of the two operands. All bits that are on (1) in both the left and right operand are on in the result; bits that are off (0) in either the left or the right operand are off in the result.
and-expression:
relational-expression
and-expression&equality-expression
Both operands to the bitwise AND operator must be of integral types. The usual arithmetic conversions covered in “Arithmetic Conversions” in Chapter 3, on topic are applied to the operands.
The bitwise exclusive OR operator (^) returns the bitwise exclusive OR of the two operands. All bits that are on (1) in either the left or right operand, but not both, are on in the result. Bits that are the same (either on or off) in both operands are off in the result.
exclusive-or-expression:
and-expression
exclusive-or-expression^and-expression
Both operands to the bitwise exclusive OR operator must be of integral types. The usual arithmetic conversions covered in “Arithmetic Conversions” in Chapter 3, on topic are applied to the operands.
The bitwise inclusive OR operator (|) returns the bitwise inclusive OR of the two operands. All bits that are on (1) in either the left or right operand are on in the result. Bits that are off (0) in both operands are off in the result.
inclusive-or-expression:
exclusive-or-expression
inclusive-or-expression|exclusive-or-expression
Both operands to the bitwise inclusive OR operator must be of integral types. The usual arithmetic conversions covered in “Arithmetic Conversions” in Chapter 3, on topic are applied to the operands.
The logical operators, logical AND (&&) and logical OR (||), are used to combine multiple conditions formed using relational or equality expressions.
The logical AND operator (&&) returns the integral value 1 if both operands are nonzero; otherwise it returns 0. Logical AND has left-to-right associativity.
logical-and-expression:
inclusive-or-expression
logical-and-expression&&inclusive-or-expression
The operands to the logical AND operator need not be of the same type, but they must be of integral or pointer type. The operands are commonly relational or equality expressions.
The first operand is completely evaluated and all side effects are completed before continuing evaluation of the logical AND expression.
The second operand is evaluated only if the first operand evaluates to true (nonzero). This “short-circuit” evaluation eliminates needless evaluation of the second operand when the logical AND expression has already been determined false. Short-circuit evaluation can be used to prevent null-pointer dereferencing as shown in the following example:
char *pch = 0;
...
(pch) && (*pch = 'a');
If pch is null (0), the right side of the expression is never evaluated. Therefore, the assignment through a null pointer is impossible.
The logical OR operator (||) returns the integral value 1 if either operand is nonzero; otherwise it returns 0. Logical OR has left-to-right associativity.
logical-or-expression:
logical-and-expression
logical-or-expression||logical-and-expression
The operands to the logical OR operator need not be of the same type, but they must be of integral or pointer type. The operands are commonly relational or equality expressions.
The first operand is completely evaluated and all side effects are completed before continuing evaluation of the logical OR expression.
The second operand is evaluated only if the first operand evaluates to false (0). This “short-circuit” evaluation eliminates needless evaluation of the second operand when the logical OR expression has already been determined true.
Assignment operators store a value in the object designated by the left operand. There are two kinds of assignment operations: “simple assignment,” in which the value of the second operand is stored in the object specified by the first operand, and “compound assignment,” in which an arithmetic, shift, or bitwise operation is performed prior to storing the result. All of the assignment operators in Table 4.4 except the = operator are compound assignment operators.
Table 4.4 Assignment Operators
Operator | Meaning |
= | Store the value of the second operand in the object specified by the first operand (“simple assignment”). |
*= | Multiply the value of the first operand by the value of the second operand; store the result in the object specified by the first operand. |
/= | Divide the value of the first operand by the value of the second operand; store the result in the object specified by the first operand. |
%= | Take modulus of the first operand specified by the value of the second operand; store the result in the object specified by the first operand. |
+= | Add the value of the second operand to the value of the first operand; store the result in the object specified by the first operand. |
–= | Subtract the value of the second operand from the value of the first operand; store the result in the object specified by the first operand. |
<<= | Shift the value of the first operand left the number of bits specified by the value of the second operand; store the result in the object specified by the first operand. |
>>= | Shift the value of the first operand right the number of bits specified by the value of the second operand; store the result in the object specified by the first operand. |
&= | Obtain the bitwise AND of the first and second operands; store the result in the object specified by the first operand. |
^= | Obtain the bitwise exclusive OR of the first and second operands; store the result in the object specified by the first operand. |
|= | Obtain the bitwise inclusive OR of the first and second operands; store the result in the object specified by the first operand. |
assignment-expression:
conditional-expression
unary-expression assignment-operator assignment-expression
assignment-operator: one of
=
*=
/=
%=
+=
–=
<<=
>>=
&=
^=
|=
Result of Assignment Operators
The assignment operators return the value of the object specified by the left operand after the assignment. The resultant type is the type of the left operand. The result of an assignment expression is always an l-value. These operators have right-to-left associativity. The left operand must be an l-value not of type const.
Note:
In ANSI C, the result of an assignment expression is not an l-value. Therefore, the legal C++ expression (a += b) += c is illegal in C.
The simple assignment operator (=) causes the value of the second operand to be stored in the object specified by the first operand. If both objects are of arithmetic types, the right operand is converted to the type of the left, prior to storing the value.
A const pointer of a given type can be assigned to a pointer of the same type. However, a pointer that is not const cannot be assigned to a const pointer. The following code shows correct and incorrect assignments:
int *const cpObject = 0;
int *pObject;
int main()
{
pObject = cpObject; // OK
cpObject = pObject; // Error
return 0;
}
Objects of const and volatile types can be assigned to l-values of types that are just volatile or that are neither const nor volatile.
When the left operand is a pointer to member, the right operand must be of pointer-to-member type or be a constant expression that evaluates to 0. This assignment is valid only in the following cases:
The right operand is a pointer to a member of the same class as the left operand.
The left operand is a pointer to a member of a class derived publicly and unambiguously from the class of the right operand.
Just as with other assignments, when a pointer to member assignment is evaluated, the right operand is converted to the type of the left operand before carrying out the assignment.
Assignment to objects of class type (struct, union, and class types) is performed by a function named operator=. The default behavior of this operator function is to perform a bitwise copy; however, this behavior can be modified using overloaded operators. (See “Overloaded Operators” in Chapter 12, on topic for more information about operator overloading.)
An object of any unambiguously derived class from a given base class can be assigned to an object of the base class; the reverse is not true. For example:
#include <iostream.h>
class ABase
{
public:
ABase() { cout << “constructing ABase\n”; }
};
class ADerived : public ABase
{
public:
ADerived() { cout << “constructing ADerived\n”; }
};
int main()
{
ABase aBase;
ADerived aDerived;
aBase = aDerived; // OK
aDerived = aBase; // Error
return 0;
}
Assignments to reference types behave as if the assignment were being made to the object to which the reference points.
For class-type objects, assignment is different from initialization. To illustrate how different assignment and initialization can be, consider the code:
UserType1 A;
UserType2 B = A;
The above code shows an initializer; it calls the constructor for UserType1 that takes an argument of type UserType1. Given the code
UserType1 A;
UserType2 B;
B = A;
the assignment statement
B = A;
can have one of the effects listed below:
Call the function operator= for UserType2, provided operator= is provided with a UserType1 argument.
Call the explicit conversion function UserType1::operator UserType2, if such a function exists.
Call a constructor UserType2::UserType2, provided such a constructor is provided, that takes a UserType1 argument, then copy the result.
The compound assignment operators, shown in Table 4.4, are specified in the form e1 op= e2, where e1 is an l-value not of const type, and e2 is one of the following:
An arithmetic type
A pointer, if op is + or –
Compound assignment to an enumerated type generates an error message. If the left operand is of a pointer type, the right operand must be of pointer type, or it must be a constant expression that evaluates to 0. If the left operand is of an integral type, the right operand must not be of a pointer type.
The comma operator allows grouping two statements where one is expected.
expression:
assignment-expression
expression,assignment-expression
The comma operator has left-to-right associativity. Two expressions separated by a comma are evaluated left to right. The left operand is guaranteed evaluated, and all side effects are completed before evaluation of the right operand.
Consider the expression
e1 , e2
The type and value of the expression are the type and value of e2; the result of evaluating e1 is discarded. The result is an l-value if the right operand is an l-value.
Where the comma has special meaning (for example in actual arguments to functions or aggregate initializers), the comma operator and its operands must be enclosed in parentheses. Therefore, the following function calls are not equivalent:
// Declare functions:
void Func( int, int );
void Func( int );
Func( arg1, arg2 ); // Call Func( int, int )
Func( (arg1, arg2) ); // Call Func( int )