Operators applied to operands form expressions. C/C++ has one of the richest collections of operators among programming languages - which gives expressive power, but makes precedence rules and implicit conversions matter enormously.
1Lab objectives
- Classifying operators by number of operands and by kind of operation
- Correctly using integer division and the modulo operator
- Telling logical operators apart from bitwise ones
- Applying precedence and associativity rules
- Understanding the difference between pre-increment and post-increment
- Controlling implicit and explicit type conversions
2Classifying operators
Operators can be grouped by number of operands:
| Category | Syntax | Example |
|---|---|---|
| Unary | operator operand | -x, !ok, ~mask, ++i |
| Binary | op1 operator op2 | a + b, x < y, m & n |
| Ternary | op1 ? op2 : op3 | (a > b) ? a : b |
+,
-, *, and & are defined both as unary and as binary
operators. The interpretation is decided implicitly by the number of operands: *p
is dereferencing, while a * b is multiplication.Operators specific to C++, which do not exist in C: new, delete,
the scope operator ::, the pointer-to-member .*, and its equivalent
form ->*.
3Arithmetic operators
| Operator | Operation | Example | Result |
|---|---|---|---|
+ | addition | 7 + 2 | 9 |
- | subtraction | 7 - 2 | 5 |
* | multiplication | 7 * 2 | 14 |
/ | division | 7 / 2 | 3 (integer!) |
/ | real division | 7.0 / 2 | 3.5 |
% | remainder (modulo) | 7 % 2 | 1 |
++ | increment | i++ | i increases by 1 |
-- | decrement | i-- | i decreases by 1 |
int average = sum / n; truncates. For a
real result, at least one operand must be real: (float)sum / n.7.5 % 2 is a compile error. It is useful for: testing parity
(n % 2 == 0), extracting digits (n % 10), and cyclically limiting an
index (i % size).4Relational and logical operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
== | equal to | 3 == 3 | 1 (true) |
!= | not equal to | 3 != 3 | 0 (false) |
< > <= >= | comparisons | 2 <= 5 | 1 |
&& | logical AND | (a>0) && (b>0) | 1 if both |
|| | logical OR | (a>0) || (b>0) | 1 if at least one |
! | logical NOT | !0 | 1 |
if (x = 5) assigns 5 to x, and
the result is 5, which is true - the condition is always satisfied. The correct form is
if (x == 5). The compiler accepts both, so the mistake is not flagged.&&, if the first
operand is false, the second is not evaluated. With ||, if the first is
true, the second is skipped. This is used for protection:
if (p != NULL && *p > 0) - the dereference only happens if the pointer
is valid.5Bitwise operators
| Operator | Name | Effect on each bit | Typical use |
|---|---|---|---|
& | bitwise AND | 1 only if both bits are 1 | testing / masking bits |
| | bitwise OR | 1 if at least one bit is 1 | setting bits |
^ | exclusive OR | 1 if the bits differ | toggling bits |
~ | negation | inverts every bit | building masks |
<< | left shift | moves bits to the left | fast multiplication by 2ⁿ |
>> | right shift | moves bits to the right | fast division by 2ⁿ |
1 & 2 gives 0 (the bits do
not overlap), but 1 && 2 gives 1 (both values are nonzero, so both
are true). Confusing the two produces logic errors that are very hard to find.6Bit playground
Toggle the bits directly on the binary representation and watch how the
result forms, position by position. Try << and >> in
particular, to see the equivalence with multiplying and dividing by powers of 2.
7Precedence and associativity
| Level | Operators | Associativity |
|---|---|---|
| 1 (highest) | () [] -> . | left → right |
| 2 | ! ~ ++ -- unary +/-, * & sizeof (cast) | right → left |
| 3 | * / % | left → right |
| 4 | + - | left → right |
| 5 | << >> | left → right |
| 6 | < <= > >= | left → right |
| 7 | == != | left → right |
| 8 | & | left → right |
| 9 | ^ | left → right |
| 10 | | | left → right |
| 11 | && | left → right |
| 12 | || | left → right |
| 13 | ?: | right → left |
| 14 (lowest) | = += -= *= /= %= | right → left |
x & 1 == 0 evaluates as
x & (1 == 0), i.e. x & 0, which is always 0. Correct:
(x & 1) == 0. When in doubt, use parentheses - they cost nothing at run time.8Simulator: pre-increment vs. post-increment
The difference between ++i and i++ only shows up
when the expression's result is used. Watch step by step what value the variable has and what
value the expression has.
i = i++ + ++i; have no result guaranteed by the standard - different compilers
produce different values. Never modify the same variable twice within the same expression.9Type conversions
When the operands have different types, the compiler automatically converts toward the "larger" type:
char / short -> int -> unsigned int -> long -> float -> double
| Expression | Result type | Value |
|---|---|---|
7 / 2 | int | 3 |
7 / 2.0 | double | 3.5 |
(float)7 / 2 | float | 3.5 |
(float)(7 / 2) | float | 3.0 - the conversion happens too late |
'A' + 1 | int | 66 |
int i = -1; unsigned u = 1; if (i < u) is false! The value −1 is converted
to unsigned and becomes 4,294,967,295, which is greater than 1. Avoid mixing the
two types in comparisons.10Source code
#include <stdio.h>
int main(void)
{
int a = 7, b = 2;
printf("a / b = %d\n", a / b); // 3 - truncated
printf("a %% b = %d\n", a % b); // 1 - the remainder
printf("(float)a/b = %.3f\n", (float)a / b); // 3.500 - correct
printf("(float)(a/b)= %.3f\n", (float)(a / b)); // 3.000 - too late!
// extracting the digits of a number
int n = 4739, digit;
printf("\nDigits of %d, from the right: ", n);
while (n > 0) {
digit = n % 10; // the last digit
n = n / 10; // drop the last digit
printf("%d ", digit);
}
printf("\n");
return 0;
}
#include <stdio.h>
void printBinary(unsigned char v)
{
for (int i = 7; i >= 0; i--)
printf("%d", (v >> i) & 1);
printf("\n");
}
int main(void)
{
unsigned char reg = 0b00001010; // 10 decimal
printf("initial: "); printBinary(reg);
reg |= (1 << 4); // SET bit 4
printf("after set b4: "); printBinary(reg);
reg &= ~(1 << 1); // CLEAR bit 1
printf("after clear b1: "); printBinary(reg);
reg ^= (1 << 0); // TOGGLE bit 0
printf("after toggle b0:"); printBinary(reg);
// TEST a bit
if (reg & (1 << 4))
printf("bit 4 is set\n");
// fast multiplication and division
int x = 12;
printf("\n%d << 2 = %d (that is %d * 4)\n", x, x << 2, x);
printf("%d >> 2 = %d (that is %d / 4)\n", x, x >> 2, x);
return 0;
}
#include <stdio.h>
int main(void)
{
int a = 17, b = 42;
int maximum = (a > b) ? a : b; // equivalent to an if-else
printf("The maximum is %d\n", maximum);
// these can be chained, but readability drops quickly
int n = 0;
printf("The number is %s\n",
(n > 0) ? "positive" : (n < 0) ? "negative" : "zero");
// watch the precedence: the parentheses are NOT optional here
printf("Sum: %d\n", a + ((a > b) ? a : b));
return 0;
}
11Code workshop
Operators have precedence rules and side effects that are hard to remember from text alone. Run these and, especially, use Step by step to see exactly when each variable changes.
12Work tasks
- Compute the arithmetic mean of three integers, obtaining a correct real result.
- Write a program that extracts and prints the digits of a number, using
%and/. - Implement the function that prints a byte in binary and test it for the values 0, 1, 128, and 255.
- Apply the four classic bit operations (set, clear, toggle, test) to a variable.
- Verify experimentally that
x & 1 == 0gives a different result from(x & 1) == 0. - Test the difference between
++iandi++inside aprintf. - Compare a negative
intwith a positiveunsignedand explain the result. - Rewrite a simple
if-elsestructure using the ternary operator.
13Extended application
if, no multiplication or division), determines: whether a number is even, whether
it is a power of 2, how many 1 bits it contains, and the position of its highest set bit. Hint
for the power of 2: n & (n - 1) is 0 only in that case.