LABORATORY 04

Operators and Expressions

Duration: 2 hours Language: C / C++ Previous: Laboratory 3 PDF handout RO versiunea română

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

a + b * c > d>+da*bcevaluated lastevaluated first
Fig. - The evaluation tree of the expression. Precedence decides the shape of the tree: multiplication binds tighter than addition, and comparison more loosely than both - so it applies last.
  • 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:

CategorySyntaxExample
Unaryoperator operand-x, !ok, ~mask, ++i
Binaryop1 operator op2a + b, x < y, m & n
Ternaryop1 ? op2 : op3(a > b) ? a : b
Operators with a double meaningThe symbols +, -, *, 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

OperatorOperationExampleResult
+addition7 + 29
-subtraction7 - 25
*multiplication7 * 214
/division7 / 23 (integer!)
/real division7.0 / 23.5
%remainder (modulo)7 % 21
++incrementi++i increases by 1
--decrementi--i decreases by 1
Integer divisionIf both operands are integers, the result is an integer, and the fractional part is lost. int average = sum / n; truncates. For a real result, at least one operand must be real: (float)sum / n.
The % operatorApplies only to integer operands. 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

OperatorMeaningExampleResult
==equal to3 == 31 (true)
!=not equal to3 != 30 (false)
< > <= >=comparisons2 <= 51
&&logical AND(a>0) && (b>0)1 if both
||logical OR(a>0) || (b>0)1 if at least one
!logical NOT!01
Confusing = with ==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.
Short-circuit evaluationWith &&, 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

OperatorNameEffect on each bitTypical use
&bitwise AND1 only if both bits are 1testing / masking bits
|bitwise OR1 if at least one bit is 1setting bits
^exclusive OR1 if the bits differtoggling bits
~negationinverts every bitbuilding masks
<<left shiftmoves bits to the leftfast multiplication by 2ⁿ
>>right shiftmoves bits to the rightfast division by 2ⁿ
& is not &&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.

Bitwise operators - interactive binary representation

7Precedence and associativity

LevelOperatorsAssociativity
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
Practical consequenceBitwise operators have lower precedence than relational ones. The expression 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.

How ++i and i++ are evaluated
Undefined behaviorExpressions of the form 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:

the implicit conversion hierarchy
char / short  ->  int  ->  unsigned int  ->  long  ->  float  ->  double
ExpressionResult typeValue
7 / 2int3
7 / 2.0double3.5
(float)7 / 2float3.5
(float)(7 / 2)float3.0 - the conversion happens too late
'A' + 1int66
Comparing signed and unsigned 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

division.c - integer vs. real
#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;
}
bits.c - classic mask techniques
#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;
}
ternary.c - the conditional operator
#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.

Prefix and postfix increment
#include <stdio.h>

int main(void)
{
    int a = 5, b;

    b = a++;                  /* assigns first, then increments */
    printf("after b = a++ :  a = %d, b = %d\n", a, b);

    a = 5;
    b = ++a;                  /* increments first, then assigns */
    printf("after b = ++a :  a = %d, b = %d\n", a, b);

    int x = 10;
    printf("\nx / 3   = %d   (integer division)\n", x / 3);
    printf("x %% 3   = %d   (the remainder)\n", x % 3);
    printf("x / 3.0 = %.4f (one real operand -> real result)\n", x / 3.0);
    return 0;
}
Bitwise operators - exercise
#include <stdio.h>

/* Prints the 8 bits of a value, from bit 7 to bit 0 */
void printBits(unsigned char v)
{
    int i;
    for (i = 7; i >= 0; i--)
        printf("%d", (v >> i) & 1);
    printf("\n");
}

int main(void)
{
    unsigned char a = 0x2C;   /* 0010 1100 */

    printf("a       = "); printBits(a);
    printf("a | 0x03= "); printBits(a | 0x03);
    printf("a & 0x0F= "); printBits(a & 0x0F);

    /* Complete this: set bit 7 of a, then clear bit 2.
       Hints: to set, use   |= (1 << 7)
              to clear, use &= ~(1 << 2)   */

    printf("result  = "); printBits(a);
    return 0;
}

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 == 0 gives a different result from (x & 1) == 0.
  • Test the difference between ++i and i++ inside a printf.
  • Compare a negative int with a positive unsigned and explain the result.
  • Rewrite a simple if-else structure using the ternary operator.

13Extended application

ExtensionWrite a program that, using only bitwise operators (no 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.

14Review questions

15Resources