2.8. Unusual Operators

Time: 00:04:04 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)

This section introduces operators that are unusual in the sense that we have no everyday analogs to them. Nevertheless, most are very useful, frequently used in programs, and worth taking the time to understand. The last two operators covered here, sizeof and the comma operator, are less common and less useful, but we cover them briefly for completeness.

Pre-/Post-Increment and Pre-/Post-Decrement

There are two auto increment operators, x++ and ++x, and two auto decrement operators, x-- and --x. In all four versions, x must be a variable. (That requirement is an oversimplification: it can be an l-value, but requiring a variable is sufficient for now). When the operators precede (i.e., come before) the variable, the operations are called pre-increment and pre-decrement; when the operators succeed (i.e., come after) the variable, the operations are called post-increment and post-decrement. In the pre- and post-operations, the value stored in x is either incremented (++) or decremented (--) by 1. We only see the difference between the pre- and post-operations when the program embeds them in a larger expression where the evaluation order is significant.

When we embed the increment and decrement operations in larger expressions, thinking about "using the value" can help us understand their behavior. The increment and decrement operators accomplish two distinct sub-operations. Like all operators, they calculate an expression value, but they also change the value stored in a variable. The key to understanding their behavior is the relative order in which they "use" or access the value stored in the variable and when they change it.

Operator Meaning Result Description
a = x++; a = x;
x = x + 1;
a == 10
x == 11
Store 10 (use x) in a, then increment x to 11
a = ++x; x = x + 1;
a = x;
a == 11
x == 11
Increment x to 11, then store 11 (use x) in a
a = x--; a = x;
x = x - 1;
a == 10
x == 9
Store 10 (use x) in a, then decrement x to 9
a = --x; x = x - 1;
a = x;
a == 9
x == 9
Decrement x to 9, then store 9 (use x) to a
Understanding the auto-increment and auto-decrement operators. Each example begins assuming that x = 10. Focus on the order of operations in the "Meaning" column, reading them from top to bottom. The "Result" column shows the final values saved in both variables after the statement execution completes.

Operation With Assignment

Operation with assignment is a shorthand notation, most often used with the arithmetic operators, but we may also use it with the relational and binary bitwise operators. The left-hand operand must always be a variable, but the right-hand operand can be an arbitrarily complex expression.

x += y
Means: x = x + y
x -= y
Means: x = x - y
x *= y
Means: x = x * y
x /= y
Means: x = x / y
x %= y
Means: x = x % y
int	dollars = pennies / 100;
pennies %= 100;

int	quarters = pennies / 25;
pennies %= 25;

int	dimes = pennies / 10;
pennies %= 10;

int	nickels = pennies / 5;
pennies %= 5;
(a)(b)
Arithmetic operators with assignment. C++ allows operation with assignment with all binary arithmetic operators, but not with the unary operators such as the negation operator (e.g., -x).
  1. Arithmetic operation with assignment and their equivalent statements
  2. Rewriting the coin calculations of the money.cpp program using the mod operator with assignment: %=
If we use x, 🙂, and expression as placeholders for an arbitrary variable, operator, and expression, then x 🙂= expression is a shortcut for x = x 🙂 expression.

Conditional Operator

The conditional operator is the only tertiary operator (i.e., an operator that takes three operands) in C++ (or in Java) and consists of two symbols, ? and :, that separate the three operands:

(exp1) ? (exp2) : (exp3)
The conditional operator. Although the behavior of this operator is similar to an if-else statement, it is an operator forming an expression and producing a value. If exp1 evaluates to true (i.e., to a non-zero), then the value of the overall expression is the value of exp2, else the value of the overall expression is exp3. The parentheses are not a required part of the syntax. However, programmers typically use them whenever any sub-expression is more complex than a constant or a single variable to help avoid precedence conflicts and improve readability.

The conditional operator is a convenient and compact way of performing a simple conditional calculation. Assuming that x and y are appropriately defined and initialized integer variables, the following code fragments produce the same results:

int min = (x < y) ? x : y;
int min;
if (x < y)
	min = x;
else
	min = z;
(a)(b)
Calculating a minimum with the conditional operator. Both examples find the minimum of two values:
  1. The conditional operator permits the variable definition, test, and assignment to occur in a single statement.
    • expr1 = (x < y)
    • expr2 = x
    • expr3 = y
  2. Programmers typically write if-else statements in a multi-line format, but even if they rewrite it on a single line, it must contain multiple sub-statements. Note that the parentheses used here are necessary for the if-statement syntax.

sizeof

sizeof is an "unusual" operator in at least two ways. First, we tend to think of operators as consisting of non-alphabetic symbols like + or *, whereas sizeof is a word (well, two words joined together). Second, sizeof is unique because the compiler evaluates it rather than the program when it executes. The compiler replaces the sizeof expression with a constant value rather than generating executable code. So, sizeof produces the size as the number of bytes of memory needed to store some data. The data can be (a) a data type name, (b) a specific variable, or (c) a constant. For example:

int	x;

cout << sizeof(int) << endl;
cout << sizeof(x) << endl;
cout << sizeof x << endl;
cout << sizeof(5) << endl;
cout << sizeof 5 << endl;
Examples of the sizeof operator. When sizeof operates on a named data type (e.g., "int"), the operation must use parentheses as illustrated, but not when it operates on a variable or a constant. The parentheses always work, so it's easier for me to remember the forms demonstrated by sizeof(int), sizeof(x), and sizeof(5).

We rarely need the sizeof operator in practice, but we occasionally use it to write platform-independent code when the code itself depends on the data size. For example, recall that the size of an int can vary between 2, 4, or 8 bytes from one computer to another. The following example uses the sizeof operator to determine the size of (i.e., how many bytes are in) an integer. Then, it uses that size to convert a number to binary and display the result.

#include <iostream>
using namespace std;

int main()
{
	cout << "Please enter a number: ";
	int number;
	cin >> number;

	int	bin[128];

	for (int i = sizeof(int) * 8 - 1; i >= 0; i--)
	{
		bin[i] = number % 2;
		number /= 2;
	}

	for (int i = 0; i < sizeof(int) * 8; i++)
		cout << bin[i];
	cout << endl;

	return 0;
}
Binary conversion and output: a sizeof example. The example demonstrates how a program uses sizeof to determine the size of an integer, measured in bytes. There are 8 bits in each byte, and the for-loops process the integer one bit at a time. The first loop converts the integer into binary, and the second loop prints the binary value, one bit for each loop iteration.

The Comma Operator

The comma operator is used even less often than is the sizeof operator; nevertheless, it's important to know about it - as much to avoid misusing it as to use it correctly. The comma operator allows chaining two or more expressions together: E1, E2, E3, ..., En. The comma operator evaluates the expressions left to right, discards the values of the first En-1 expressions, and the overall value of the joined expressions is the value of the last expression or En. It might seem pointless to evaluate an expression only to discard its value, but in addition to creating a value, expressions can also have useful side effects.

In this context, a side effect is a secondary change to a program (e.g., changing the value stored in a variable) beyond returning a value. It's much easier to understand side effects and the comma operator with a set of examples. First, recall that the assignment operator returns a value, which forms the basis of our examples.

y = 5; x = y = 5; z = (x = 5, y = 2);
(a)(b)(c)
Illustrating side effects with the assignment operator. Each statement consists of at least one expression; examples (b) and (c) form the expressions from smaller sub-expressions.
  1. The expression y = 5 produces the value 5 (i.e., the statement cout << y = 5 << endl; prints "5" to the console). The example ignores the assignment side effect of storing 5 in y.
  2. The program evaluates the assignment operator from right to left, so it evaluates the sub-expression y = 5 first. This sub-expression has the useful side effect of storing 5 in y. However, the assignment operator forms an expression, producing the value 5. So, the second expression, x = y = 5, then stores the value 5, returned by the first sub-expression, into variable x.
  3. The first sub-expression, x = 5, stores 5 in x and returns 5; the second sub-expression, y = 2, stores 2 in y and returns 2. The comma operator evaluates x = 5,  y = 2, discarding the value of the first sub-expression. So, the overall value of the top-level expression is 2 (the value of the last sub-expression), which the assignment operator saves in the variable z.

The comma operator is most often used in for-loops (Chapter 3). Underscoring this conclusion is the observation that for-loops are the only place Java allows the comma operator. The comma operator allows programmers to form a compound expression that evaluates two or more sub-expressions where the syntax only allows a single expression:

for (int i = 0, j = 0; i < 10 && j < 10; i++, j++)
	cout << i << " " << j << endl;
Using the comma operator in a for-loop. For-loops have three expressions, separated by semicolons, inside of the parentheses: for (exp1; exp2; exp3). The first expression initializes the loop control variable, the second tests the loop control (the loop continues as long as the second expression evaluates to true - i.e., to non-zero), and the third expression updates the loop control. Using the comma operator allows the programmer to use multiple loop control variables within a single for-loop.