Operators

Intro

To truly understand operators, we first need to understand what an expression is. Initially, I tried to rely on dictionary definitions, but they felt too abstract. Therefore, before diving into the formal definition, let's look at some examples of expressions:

int a;
a++;
int b = 10;
a += b;
a = a*2 + b;

With these examples in mind, let's introduce the formal definition of an expression:

An expression is a sequence of operators and their operands that specifies a computation.

Operators are sequences like =, +, +=, or <<. We can imagine that they are functions embedded in the language itself. They can take one argument (unary operators) or two arguments (binary operators). Unary comes from the Latin prefix "uni-" meaning "one," and binary means "two."

If operators are like functions, then their arguments are called operands. An operand can be a number like 1, a variable like foo, or another expression like 1 + 1.

With this analogy to functions and their arguments, it's easier to understand how operations and operands compose expressions. We can see 1 + 2 as add(1, 2). The key difference is that operators don't require function call overhead, making them much faster.

Besides unary and binary operators, C also has a conditional operator that takes three operands. It looks like this: condition ? expression_true : expression_false.

In this article, we will go over all the operators in C and provide examples for each.

Unary operators

Increment and decrement

There are two increment operators: ++a and a++. When the ++ appears before the variable, the variable is incremented before evaluating the expression. When it appears after, the variable is incremented afterward.

#include <stdio.h>

int main() {
  int a = 0;
  if (++a == 0) {
    puts("This is never printed");
  }

  int b = 0;
  if (b++ == 0) {
    puts("This is printed");
  }

  printf("a=%d, b=%d\n", a, b);

  return 0;
}

Similiary with decrement --a and a--.

#include <stdio.h>

int main() {
  int a = 0;
  if (--a == 0) {
    puts("This is never printed");
  }

  int b = 0;
  if (b-- == 0) {
    puts("This is printed");
  }

  printf("a=%d, b=%d\n", a, b);

  return 0;
}

Arithmetic operators

C has three unary arithmetic operators: +a, -a, and ~a.

  • +a returns a unchanged.
  • -a returns the negated value of a.
#include <stdio.h>

int main() {
  int a = 1;
  if (-a == 1) {
    puts("This is never printed");
  }

  int b = 1;
  if (+b == 1) {
    puts("This is printed");
  }

  int c = 1;
  printf("a=%d, b=%d, c=%d\n", a, b, c);
  printf("%2s=%08x\n", "c", c);

  return 0;
}

Logical operators

The only unary logical operator is negation !a. It converts a nonzero value to 0, and 0 to 1.

#include <stdio.h>

int main() {
  int a = 1;
  int b = 2;
  int c = 3;
  printf("a=%d, b=%d, c=%d\n", !a, !b, !c);

  return 0;
}

Member access operators

The unary member access operators are *a (dereference) and &a (address-of).

#include <stdio.h>

int main() {
  int a = 1;
  int *b = &a;
  printf("a=%d, b=%p, *b=%d\n", a, b, *b);

  return 0;
}

sizeof operator

The sizeof operator returns the size (in bytes) of a variable or data type.

#include <stdio.h>

int main() {
  int a = 1;
  int *b = &a;
  printf("sizeof(a)=%lu, sizeof(b)=%lu\n", sizeof(a), sizeof(b));

  return 0;
}

Bitwise operators

Bitwise not

Flips all bits of an integer using the ~ operator.

#include <stdio.h>

int main() {
  int a = 0xf0000000;

  printf("a=%08x\n", a);
  printf("a=%08x\n\n", ~a);

  a = 0x0f0f0f0f;
  printf("a=%08x\n", a);
  printf("a=%08x\n\n", ~a);

  a = 0x00fff00;
  printf("a=%08x\n", a);
  printf("a=%08x\n\n", ~a);

  return 0;
}

Binary operators

Arithmetic operators

Addition operator

Adds two operands together.

#include <stdio.h>

int main() {
  int a = 0;

  // basic assignment
  printf("a=%d\n", a);
  printf("a+3=%d\n", a + 3);

  return 0;
}

Substraction operator

Subtracts the right-hand operand from the left-hand operand.

#include <stdio.h>

int main() {
  int a = 0;

  // basic assignment
  printf("a=%d\n", a);
  printf("a-3=%d\n", a - 3);

  return 0;
}

Multiplication operator

Multiplies two operands together.

#include <stdio.h>

int main() {
  int a = 2;

  // basic assignment
  printf("a=%d\n", a);
  printf("a*3=%d\n", a*3);

  return 0;
}

Division operator

Divides the left-hand operand by the right-hand operand.

#include <stdio.h>

int main() {
  int a = 3;

  // basic assignment
  printf("a=%d\n", a);
  printf("a/3=%d\n", a/3);

  return 0;
}

Modulo operator

Returns the remainder of division between two operands.

#include <stdio.h>

int main() {
  int a = 3;

  // basic assignment
  printf("a=%d\n", a);
  printf("a%%3=%d\n", a % 3);

  return 0;
}

Bitwise operators

Bitwise and

Performs bitwise AND between two integers.

#include <stdio.h>

int main() {
  int a = 0x0f;

  printf("a=%02x\n", a);
  printf("a=%02x\n", a & 0x0f);
  printf("a=%02x\n", a & 0xf0);
  printf("a=%02x\n", a & 0x01);
  printf("a=%02x\n", a & 0x02);

  return 0;
}

Bitwise or

Performs bitwise OR between two integers.

#include <stdio.h>

int main() {
  int a = 0x0f;

  printf("a=%02x\n", a);
  printf("a=%02x\n", a | 0x0f);
  printf("a=%02x\n", a | 0xf0);
  printf("a=%02x\n", a | 0x01);
  printf("a=%02x\n", a | 0x10);

  return 0;
}

Bitwise left shift operator

Shifts bits to the left, effectively multiplying the number by powers of two.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

uint32_t _pow(uint32_t a, uint32_t b) {
  uint32_t result = 1;
  while (b--) {
    result *= a;
  }

  return result;
}

void binary_print(uint32_t a) {
  for (int32_t bit_i = (sizeof(uint32_t) * 8) - 1; bit_i >= 0; bit_i--) {
    uint32_t mask = _pow(2, bit_i);
    int is_one = a & mask;
    printf("%d", is_one ? 1 : 0);
  }
  puts("");
}

int main() {
  uint32_t a = 7;

  binary_print(a);
  // left shit by 1 place
  binary_print(a << 1);
  // left shit by 2 places
  binary_print(a << 2);
  // left shit by 3 places
  binary_print(a << 3);
  // left shit by 4 places
  binary_print(a << 4);
  // left shit by 5 places
  binary_print(a << 5);
  // left shit by 6 places
  binary_print(a << 6);

  return 0;
}

Bitwise rigth shift operator

Shifts bits to the right, effectively dividing the number by powers of two.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

uint32_t _pow(uint32_t a, uint32_t b) {
  uint32_t result = 1;
  while (b--) {
    result *= a;
  }

  return result;
}

void binary_print(uint32_t a) {
  for (int32_t bit_i = (sizeof(uint32_t) * 8) - 1; bit_i >= 0; bit_i--) {
    uint32_t mask = _pow(2, bit_i);
    int is_one = a & mask;
    printf("%d", is_one ? 1 : 0);
  }
  puts("");
}

int main() {
  uint32_t a = 0x80000000;

  binary_print(a);
  // rigth shit by 1 place
  binary_print(a >> 1);
  // rigth shit by 2 places
  binary_print(a >> 2);
  // rigth shit by 3 places
  binary_print(a >> 3);
  // rigth shit by 4 places
  binary_print(a >> 4);
  // rigth shit by 5 places
  binary_print(a >> 5);
  // rigth shit by 6 places
  binary_print(a >> 6);

  return 0;
}

Assignment operators

Assignment operators always include the = sign. The most common form is var = val, but it's important to know that var = val also evaluates as an expression to val. For example, (a = 2) evaluates to 2.

#include <stdio.h>

int main() {
  int a = 1;

  printf("%7s=%d\n", "a", a);
  printf("%7s=%d\n", "(a = 2)", (a = 2));
  printf("%7s=%d\n", "a", a);
  printf("%7s=%d\n", "(a = 3)", (a = 3));
  printf("%7s=%d\n", "a", a);

  if ((a = 0) == 0) {
    printf("%22s a=%d\n", "This is always printed", a);
  }

  a = 2;
  if (a > 0 && (a += 1) > 2) {
    printf("%22s a=%d\n", "This is also printed", a);
  }

  printf("%6s: a=%d\n", "Before", a);
  printf("%6s: a=%d\n", "During", a *= 5);
  printf("%6s: a=%d\n", "After", a);
  return 0;
}

There are multiple assignment operators.

#include <stdio.h>

int main() {
  int a;

  // basic assignment
  a = 0;
  printf("1. a=%d\n", a);

  // addition assignment
  a += 5;
  printf("2. a=%d\n", a);

  // substraction assignment
  a -= 3;
  printf("3. a=%d\n", a);

  // multiplication assignment
  a *= 8;
  printf("4. a=%d\n", a);

  // division assignment
  a /= 4;
  printf("5. a=%d\n", a);

  // modulo assignment
  a %= 3;
  printf("6. a=%d\n", a);

  // bitwise left shift assignment
  a <<= 1;
  printf("7.1 a=%d\n", a);

  a <<= 1;
  printf("7.2 a=%d\n", a);

  a <<= 1;
  printf("7.3 a=%d\n", a);

  // bitwise right shift assignment
  a >>= 1;
  printf("8.1 a=%d\n", a);

  a >>= 1;
  printf("8.2 a=%d\n", a);

  a >>= 1;
  printf("8.3 a=%d\n", a);

  // bitwise AND
  a = 0x01;
  a &= 0x0f; // a = 0x01 & 0x0f
  printf("9. a=%x\n", a);

  a &= 0xf0; // a = 0x01 & 0xf0
  printf("9. a=%x\n", a);

  // bitwise OR
  a = 0x01;
  a |= 0x0f;
  printf("10. a=%x\n", a);

  a |= 0xf0;
  printf("10. a=%x\n", a);

  // bitwise XOR
  a = 0x01;
  a ^= 0x0f;
  printf("11. a=%x\n", a);

  a ^= 0xf0;
  printf("11. a=%x\n", a);

  return 0;
}

Logical operators

Logical operators && and || are used to combine multiple conditions.

#include <stdio.h>

int main() {
  int a = 1;
  int b = 0;
  printf("a=%d, b=%d\n", a, b);
  printf("%4s is %d\n", "a&&b", a && b);
  printf("%4s is %d\n", "a||b", a || b);

  a = 1;
  b = 1;
  printf("a=%d, b=%d\n", a, b);
  printf("%4s is %d\n", "a&&b", a && b);
  printf("%4s is %d\n", "a||b", a || b);

  return 0;
}

Comparison operators

Comparison operators ==, !=, <, >, <=, and >= are used to compare two values.

#include <stdio.h>

int main() {
  int a = 7;
  int b = 5;
  printf("a=%d, b=%d\n\n", a, b);
  printf("%5s is %d\n", "a==b", a == b);
  printf("%5s is %d\n", "a!=b", a != b);
  printf("%5s is %d\n", "a>b", a > b);
  printf("%5s is %d\n", "a<b", a < b);
  printf("%5s is %d\n", "a<=b", a <= b);
  printf("%5s is %d\n", "a>=b", a >= b);

  return 0;
}

Member access operators

The array indexing operator [] and the structure member access operators . and -> allow access to elements and structure fields.

#include <stdio.h>

struct example {
  int attribute;
};

int main() {
  int arr[] = {1, 2, 3};

  printf("arr[0]=%d\n", arr[0]);

  struct example ex, *ex_ptr;
  ex.attribute = 7;
  ex_ptr = &ex;
  printf("ex.attribute=%d\n", ex.attribute);
  printf("ex_ptr->attribute=%d\n", ex_ptr->attribute);

  ex.attribute = 2;
  printf("ex.attribute=%d\n", ex.attribute);
  printf("ex_ptr->attribute=%d\n", ex_ptr->attribute);

  return 0;
}

Conclusions

That was a massive amount of information, wasn't it? All these operators—arithmetic, logical, bitwise, and more—each serving a distinct purpose. Initially, they may seem overwhelming, but as you spend more time with C, you'll see that mastering these operators is crucial for writing clear and efficient code.

Published:

Last updated:

Category: C

Comments

Leave a comment