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.
+areturnsaunchanged.-areturns the negated value ofa.
#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.