Arithmetic operators and precedence in C++ decide what your expressions actually mean. Addition, subtraction, multiplication, division and modulo do the maths, while precedence decides which operation happens first when they are chained in a single expression. Get the precedence wrong and your program quietly computes a different quantity from the one you intended.
The five arithmetic operators
C++ gives you the five familiar operators plus a remainder operator.
+addition-subtraction and unary negation*multiplication/division%remainder, also called modulo, available for integers only
There is no exponentiation operator in the language. Multiplying by itself works fine for small powers, and std::pow from <cmath> handles the general case.
#include
#include
int main() {
int flour = 500; // grams
int water = 300;
int batches = 4;
std::cout << "total grams: " << flour + water << "\n";
std::cout << "per batch: " << (flour + water) / batches << "\n";
std::cout << "leftover: " << (flour + water) % batches << "\n";
std::cout << "doubled: " << flour * 2 << "\n";
std::cout << "as a square: " << std::pow(flour, 2) << "\n";
return 0;
}Notice the parentheses in the second line. They are not optional decoration: without them the division happens first, and you would divide the flour by four and then add the water.
Precedence decides the order of evaluation
The precedence table, from highest to lowest, is short enough to memorise for the arithmetic operators.
- Parentheses — you always win.
- Unary
+and-. *,/,%.+,-.
Multiplication, division and modulo bind tighter than addition and subtraction. Among the three at the same level, evaluation is left to right, which is what associativity means here.
#include
int main() {
std::cout << "2 + 3 * 4 = " << 2 + 3 * 4 << "\n";
std::cout << "(2 + 3) * 4 = " << (2 + 3) * 4 << "\n";
std::cout << "10 - 4 - 3 = " << 10 - 4 - 3 << "\n";
std::cout << "10 - (4 - 3) = " << 10 - (4 - 3) << "\n";
std::cout << "20 / 5 / 2 = " << 20 / 5 / 2 << "\n";
std::cout << "20 / (5 / 2) = " << 20 / (5 / 2) << "\n";
return 0;
}The output makes the two effects obvious:
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
10 - 4 - 3 = 3
10 - (4 - 3) = 9
20 / 5 / 2 = 2
20 / (5 / 2) = 10Left-to-right evaluation explains 20 / 5 / 2 being 2 rather than 8, and it is also why floating point division is not associative: (0.1 + 0.2) + 0.3 can differ from 0.1 + (0.2 + 0.3) in the last bits.
The last line is a good illustration of why parentheses matter so much. The inner 5 / 2 is integer division and produces 2, so the outer division gives 10. Had you expected 8 you were mentally doing real division, which is a different calculation.
Integer and floating point division
The division operator does two completely different things depending on the types of its operands.
#include
int main() {
int batches = 7;
int guests = 2;
std::cout << "int division: " << batches / guests << "\n";
std::cout << "double division: " << static_cast(batches) / guests << "\n";
std::cout << "mixed division: " << batches / 2.0 << "\n";
std::cout << "modulo: " << batches % guests << "\n";
return 0;
}The integer result and the true result differ, which is the whole point:
int division: 3
double division: 3.5
mixed division: 3.5
modulo: 1Integer division truncates toward zero, so 7 divided by 2 is 3, and the modulo is 1. One floating-point operand anywhere in the expression turns the whole thing into floating-point arithmetic.
The remainder operator only accepts integers. Applying % to a double is a compile error, which is one way the compiler helps you keep counts and measurements apart.
Compound assignment operators
Writing x = x + 1 works, but C++ provides a shorter form for every arithmetic operator. They behave identically, including the promotion rules.
+=,-=,*=/,/=,%=
#include
int main() {
int servings = 4;
int batches = 1;
servings *= 2; // 8
servings += 2; // 10
servings -= 3; // 7
servings /= 2; // 3
servings %= 2; // 1
batches <<= 2; // 4, but this is a shift, see below
std::cout << "servings: " << servings << "\n";
std::cout << "batches: " << batches << "\n";
return 0;
}One subtlety worth knowing: x /= 2 on an int divides as an integer, exactly like x = x / 2 would. If you meant a real division you have to write x /= 2.0.
Increment and decrement
++ and -- add or subtract one. As prefix they return the new value, as postfix they return the old value. In a statement of their own the difference is invisible, which is why i++ and ++i are interchangeable there.
#include
int main() {
int i = 5;
int after_postfix = i++;
std::cout << "postfix: " << after_postfix << " then i=" << i << "\n";
int after_prefix = ++i;
std::cout << "prefix: " << after_prefix << " then i=" << i << "\n";
return 0;
}Because the difference is subtle and never useful in the cases where it is visible, the modern advice is to use the prefix form in expressions and prefer i += 1 when the result is not used at all.
Precedence bugs and how to avoid them
Precedence errors are common because the compiler cannot tell you that you meant something different. The defences are simple and mechanical.
- Parenthesise every mixed expression. It costs one keystroke and removes the entire class of bug.
- Use one concept per statement. Do not compute a ratio and a count in one line.
- Give intermediate results names.
double per_batch = total / batches;is clearer than inlining the division twice. - Compile with warnings on.
-Wall -Wextrawill flag suspicious mixes of signed and unsigned and other likely mistakes.
Why order of evaluation inside one expression is a trap
Precedence fixes the order in which operators combine, but it says nothing about the order in which two independent sub-expressions are evaluated. When you write two operations in one statement and they touch the same variable, the order is not fixed by the standard, and modifying a variable twice between sequence points is .undefined behaviour
#include
int main() {
int total = 100;
int tax = 10;
total = total + tax / 100; // well defined: tax is read once
std::cout << "total: " << total << "\n";
// Writing the increment inside a chain of insertions is undefined:
// std::cout << total++ << " " << total;
int value = total;
int incremented = value++;
std::cout << "incremented: " << incremented << " value now " << value << "\n";
return 0;
}The safe pattern is to compute each result into its own named variable, then print. It is longer by one line and it removes every question about what happens when.
Quick reference
*,/and%bind tighter than+and-.- Parentheses override everything; use them whenever a reader might hesitate.
- Same-level operators evaluate left to right.
- Two integers divide as integers; one floating-point operand makes the expression floating point.
%is integer-only and gives the remainder after truncation.- Compound forms
+=,/=and the rest behave exactly like the expanded version. - Prefer prefix
++iin expressions andi += 1when the value is unused.
Try it yourself: write down the value of each of these expressions before running them, then check: [[2 + 3 * 4]], [[(2 + 3) * 4]], [[10 / 5 / 2]], [[7 % 3 + 1]] and [[1 << 2 + 3]]. Score yourself on how many you got right, then add parentheses everywhere and confirm nothing changed.