C operators combine values into expressions, but the visible symbol is only part of the rule. Operand types control conversions, precedence controls grouping, short-circuit operators control whether the right operand runs, and side effects can make an expression undefined when the same scalar is modified and reused without sequencing.
After this lesson, you can predict the type and value of common expressions, add parentheses where intent matters, distinguish logical from bitwise operations, and rewrite compact expressions whose evaluation order would be unsafe or difficult to review.
The arithmetic operators are +, -, *, /, and %. Integer division discards the fractional part toward zero, so 7 / 2 produces 3 while 7.0 / 2 produces 3.5. The remainder operator requires integer operands. Division or remainder by zero is invalid and must be prevented before evaluating the expression.
Before many binary operations, integer promotions and the usual arithmetic conversions bring operands to a common type. Mixing signed and unsigned values can therefore produce a surprising unsigned comparison or result. Use types that represent the domain, validate ranges, and convert deliberately at a boundary instead of scattering casts that hide warnings.
Relational operators compare ordering, and equality operators compare equality. Their result is an int with value 1 for true or 0 for false. Do not confuse assignment = with equality ==. A compiler warning level that diagnoses assignment used as a condition catches many accidental cases.
Floating-point values often contain rounding error, so exact equality is appropriate only when the program logic guarantees identical representations. For measured or calculated values, compare according to an error policy that matches the scale and domain rather than using one universal epsilon.
The logical operators !, &&, and || treat zero as false and nonzero as true, and produce 0 or 1. && evaluates its right operand only when the left operand is true. || evaluates its right operand only when the left operand is false. This sequencing supports guarded access such as pointer != NULL && pointer->ready.
Use short-circuiting to protect a required precondition, not to hide a large state-changing operation inside a condition. Pull complex side effects into named statements so debugging and error handling remain visible.
Bitwise &, |, ^, ~, <<, and >> operate on integer representations. They are useful for masks, flags, packed protocols, device registers, and powers-of-two transformations. They do not perform boolean short-circuiting: both operands of & and | are evaluated.
Prefer unsigned integer types for bit manipulation so shifts and complements are easier to reason about. Validate the shift count; shifting by a negative count or by a count greater than or equal to the width of the promoted left operand is invalid. Do not assume a packed bit layout is portable across protocols without defining widths and byte order.
Simple assignment stores a converted right-hand value in a modifiable left operand. Compound assignment such as total += amount reads, combines, converts, and stores while evaluating the left operand once. Prefix ++i updates then yields the new value; postfix i++ yields the old value and still performs the update.
Keep one modification of a scalar in a full expression. Code such as i = i++ or a function call that both modifies and reads i through separate arguments has undefined behavior or unspecified ordering concerns depending on the exact expression. Split the work into statements with an obvious sequence.
#include <stdio.h>
int main() {
int a = 10, b = 3;
// Arithmetic
printf("a + b = %d\n", a + b); // 13
printf("a - b = %d\n", a - b); // 7
printf("a * b = %d\n", a * b); // 30
printf("a / b = %d\n", a / b); // 3 (integer division)
printf("a %% b = %d\n", a % b); // 1
// Increment / Decrement
int x = 5;
printf("x++: %d\n", x++); // 5 (post-increment: use then increment)
printf("x: %d\n", x); // 6
printf("++x: %d\n", ++x); // 7 (pre-increment: increment then use)
// Relational
printf("\n5 == 5: %d\n", 5 == 5); // 1
printf("5 != 3: %d\n", 5 != 3); // 1
printf("5 > 8: %d\n", 5 > 8); // 0
// Logical
int age = 20;
printf("\nage >= 18 && age <= 60: %d\n", age >= 18 && age <= 60); // 1
printf("age < 18 || age > 60: %d\n", age < 18 || age > 60); // 0
printf("!(age == 20): %d\n", !(age == 20)); // 0
return 0;
}
#include <stdio.h>
int main() {
int a = 5, b = 3; // a = 0101, b = 0011 in binary
// Bitwise operators
printf("a & b = %d\n", a & b); // 1 (0001)
printf("a | b = %d\n", a | b); // 7 (0111)
printf("a ^ b = %d\n", a ^ b); // 6 (0110)
printf("~a = %d\n", ~a); // -6
printf("a << 1 = %d\n", a << 1); // 10 (multiply by 2)
printf("a >> 1 = %d\n", a >> 1); // 2 (divide by 2)
// Ternary operator: condition ? value_if_true : value_if_false
int num = 7;
char *result = (num % 2 == 0) ? "even" : "odd";
printf("\n%d is %s\n", num, result); // 7 is odd
int max = (a > b) ? a : b;
printf("Max of %d and %d is %d\n", a, b, max); // 5
// sizeof operator
printf("\nsizeof(int): %zu\n", sizeof(int));
printf("sizeof(double): %zu\n", sizeof(double));
printf("sizeof(a): %zu\n", sizeof(a)); // same as sizeof(int)
return 0;
}
Precedence determines how an unparenthesized expression groups; associativity resolves operators at the same precedence level. Neither rule generally promises which operand runs first. Parentheses document grouping but do not invent an evaluation order between function arguments or ordinary arithmetic operands.
The conditional operator condition ? yes : no evaluates only one of its second or third operands. It works well for a small value choice. Use if and else when branches need multiple statements, separate error handling, or comments.
#include <stdio.h>
int main(void) {
int flags = 0x05;
int required = 0x01;
int enabled = (flags & required) != 0;
int score = 72;
const char *result = score >= 60 ? "pass" : "retry";
printf("enabled=%d, result=%s\n", enabled, result);
return 0;
}
enabled=1, result=pass
Parentheses make the mask operation happen before comparison. The conditional operator selects one string and evaluates only the selected branch.
Try this next
0 of 3 completed
They control grouping, but most operators still do not specify whether the left or right operand is evaluated first.
Unsigned arithmetic has defined modulo behavior and avoids many signed-shift and sign-extension surprises.
No, but it is easy to confuse with equality. Use it only when intentional, parenthesized, and clear to reviewers.
Explore 500+ free tutorials across 20+ languages and frameworks.