Bytebook Compiler

Bytebook · CSE 101

Programming in C

Start from zero and learn to think like a programmer in C: variables, decisions, loops, functions, arrays, strings, pointers, memory, structures and files.

Back to lessons

Chapter 1 · Getting Started

C Introduction

What C is, why it still runs the world, and your very first program — explained line by line.

C is a general-purpose programming language created by Dennis Ritchie at Bell Labs in 1972. It was built to write the Unix operating system, and today it still runs underneath almost everything: Linux and Windows kernels, Python's interpreter, databases like MySQL, microcontrollers in washing machines and cars.

C is small, fast and close to the hardware. You decide where every byte of memory goes. That makes it the perfect first language for a computer science student: once you understand C, every other language makes more sense.

Your first C program

Every C journey starts with the same program. Read it, then press Try it Yourself to run and edit it.

C
#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}
Output
Hello, World!

Here is what each line does:

Line Meaning
#include <stdio.h> Brings in the standard input/output library, which contains printf.
int main(void) Every C program starts running at main. It returns an int to the operating system.
{ ... } Curly braces group statements into a block — here, the body of main.
printf("Hello, World!\n"); Prints text. \n moves to a new line. Every statement ends with ;.
return 0; Ends main and tells the OS the program finished successfully.

From source code to a running program

Your computer's processor cannot run C directly — it only understands machine code. A compiler such as gcc translates your file in four stages:

hello.c — the code you writePreprocessor: expands #include and #defineCompiler: turns C into assemblyAssembler: turns assembly into machine code (hello.o)Linker: joins hello.o with the C libraryhello — a program you can run
The four stages that gcc hello.c -o hello runs for you.

On your own machine (Linux, macOS, or Windows with MinGW/WSL) you compile and run like this:

Terminal
gcc hello.c -o hello     # compile hello.c into a program called hello
./hello                  # run it

The structure of a C program

Bigger programs follow the same skeleton. Your exam may ask you to label these parts:

C
/* 1. Documentation: what this program does */
// Prints the area of a circle

/* 2. Preprocessor directives */
#include <stdio.h>
#define PI 3.14159

/* 3. Global declarations */
double area(double r);

/* 4. The main function */
int main(void) {
    double r = 2.0;
    printf("Area = %.2f\n", area(r));
    return 0;
}

/* 5. User-defined functions */
double area(double r) {
    return PI * r * r;
}
Output
Area = 12.57

Where C is used today

  • Operating systems — the Linux kernel is about 30 million lines of C.
  • Embedded systems — microcontrollers in cars, routers, smart TVs and medical devices.
  • Language runtimes — CPython, the Ruby interpreter and parts of Node.js are written in C.
  • Databases and networking — SQLite, MySQL, Redis, curl and most network stacks.

Chapter 2 · Getting Started

Syntax, Output & Comments

The grammar rules of C, how printf prints text and numbers, escape sequences, and how to comment code.

C syntax rules

C is strict about grammar. Five rules cover almost every beginner error:

  1. Every statement ends with a semicolon ;.
  2. C is case-sensitive — printf works, Printf does not.
  3. Blocks use curly braces { }.
  4. Whitespace is ignored — indent for humans, not for the compiler.
  5. Text goes in double quotes "..."; a single character goes in single quotes 'A'.

Printing with printf

printf (print formatted) writes text to the screen. Text without \n stays on the same line:

C
#include <stdio.h>

int main(void) {
    printf("Learning ");
    printf("C is fun.");
    printf("\n");
    printf("This is on a new line.\n");
    return 0;
}
Output
Learning C is fun.
This is on a new line.

To print numbers, put a format specifier in the text and pass the value after it. %d prints an integer:

C
#include <stdio.h>

int main(void) {
    int marks = 92;
    printf("Marks: %d\n", marks);
    printf("%d + %d = %d\n", 7, 5, 7 + 5);
    return 0;
}
Output
Marks: 92
7 + 5 = 12

You will meet all the format specifiers in the next lesson.

Escape sequences

A backslash \ inside a string starts an escape sequence — a special character you cannot type directly.

Sequence Meaning
\n New line
\t Horizontal tab
\\ A backslash
\" A double quote inside a string
\' A single quote
\0 The null character (ends every string)
%% A percent sign (inside printf only)
C
#include <stdio.h>

int main(void) {
    printf("Name\tMarks\n");
    printf("Asha\t95\n");
    printf("Ravi\t88\n");
    printf("She said \"C is easy\" and scored 100%%.\n");
    printf("Path: C:\\Users\\asha\n");
    return 0;
}
Output
Name	Marks
Asha	95
Ravi	88
She said "C is easy" and scored 100%.
Path: C:\Users\asha

puts and putchar

Two simpler output functions are also in stdio.h:

  • puts("text") prints a string and adds a newline automatically.
  • putchar('A') prints exactly one character.
C
#include <stdio.h>

int main(void) {
    puts("Hello from puts");
    putchar('O');
    putchar('K');
    putchar('\n');
    return 0;
}
Output
Hello from puts
OK

Comments

Comments are notes for humans. The compiler ignores them completely.

C
// A single-line comment runs to the end of the line.

/* A multi-line comment
   can span several lines. */

int total = 0; // comments can follow code too

Chapter 3 · Getting Started

Variables, Data Types & Constants

Store values in variables, choose the right data type, print them with the right format specifier, and lock values with constants.

A variable is a named box in memory that holds a value. Every variable in C has a type, which decides how many bytes the box uses and what kind of value it can hold.

C
#include <stdio.h>

int main(void) {
    int age = 19;          // whole number
    float cgpa = 8.75f;    // decimal number
    char grade = 'A';      // single character

    printf("Age: %d\n", age);
    printf("CGPA: %.2f\n", cgpa);
    printf("Grade: %c\n", grade);

    age = age + 1;         // variables can change
    printf("Next year: %d\n", age);
    return 0;
}
Output
Age: 19
CGPA: 8.75
Grade: A
Next year: 20

Declaring and naming variables

C
int count;            // declaration (value is garbage until you assign one)
count = 10;           // assignment
int total = 0;        // declaration + initialisation
int a = 1, b = 2;     // several at once

Naming rules (these are called identifiers):

  • Use letters, digits and underscores only: total_marks, num1.
  • Must not start with a digit: 1num is invalid.
  • Cannot be a keyword such as int, return, while.
  • Case matters: Sum and sum are different variables.

Data types

Type Typical size Holds Example
char 1 byte one character or small integer 'A'
short 2 bytes small integers 300
int 4 bytes integers, about ±2.1 billion -42
long long 8 bytes very large integers 9000000000LL
float 4 bytes decimals, ~7 digits of precision 3.14f
double 8 bytes decimals, ~15 digits of precision 3.14159265

Add unsigned to an integer type to store only non-negative values with double the positive range. Sizes can vary between machines, so C gives you the sizeof operator to check:

C
#include <stdio.h>

int main(void) {
    printf("char      : %zu byte\n", sizeof(char));
    printf("short     : %zu bytes\n", sizeof(short));
    printf("int       : %zu bytes\n", sizeof(int));
    printf("long long : %zu bytes\n", sizeof(long long));
    printf("float     : %zu bytes\n", sizeof(float));
    printf("double    : %zu bytes\n", sizeof(double));
    return 0;
}
Output
char      : 1 byte
short     : 2 bytes
int       : 4 bytes
long long : 8 bytes
float     : 4 bytes
double    : 8 bytes

Format specifiers

The specifier must match the variable's type, or printf prints nonsense.

Specifier Type Specifier Type
%d or %i int %c char
%u unsigned int %s string
%ld long %x / %o hex / octal
%lld long long %e scientific notation
%f float / double %p pointer address
%.2f 2 decimal places %zu sizeof result
C
#include <stdio.h>

int main(void) {
    int n = 255;
    double pi = 3.14159265;
    printf("%d in hex is %x and in octal is %o\n", n, n, n);
    printf("pi = %f\n", pi);
    printf("pi = %.3f\n", pi);
    printf("pi = %e\n", pi);
    printf("[%5d] [%-5d]\n", 42, 42);   // width 5, right and left aligned
    return 0;
}
Output
255 in hex is ff and in octal is 377
pi = 3.141593
pi = 3.142
pi = 3.141593e+00
[   42] [42   ]

Characters are numbers

A char stores a small integer — its ASCII code. 'A' is 65, 'a' is 97 and '0' is 48. That is why you can do arithmetic on characters:

C
#include <stdio.h>

int main(void) {
    char c = 'A';
    printf("%c has ASCII code %d\n", c, c);
    printf("Next letter: %c\n", c + 1);
    printf("Lowercase: %c\n", c + 32);
    printf("Digit '7' as a number: %d\n", '7' - '0');
    return 0;
}
Output
A has ASCII code 65
Next letter: B
Lowercase: a
Digit '7' as a number: 7

Constants

Some values should never change. C gives you two ways to make them:

C
#include <stdio.h>
#define GRAVITY 9.8          // preprocessor constant: text replaced before compiling

int main(void) {
    const int DAYS_IN_WEEK = 7;   // typed constant: the compiler refuses changes
    printf("Weeks in 28 days: %d\n", 28 / DAYS_IN_WEEK);
    printf("Weight of 50 kg: %.1f N\n", 50 * GRAVITY);
    return 0;
}
Output
Weeks in 28 days: 4
Weight of 50 kg: 490.0 N

Trying DAYS_IN_WEEK = 8; would be a compile error: assignment of read-only variable.

Chapter 4 · Getting Started

User Input with scanf

Read numbers, characters and whole lines from the keyboard — and avoid the three classic scanf traps.

scanf (scan formatted) reads input from the keyboard. It uses the same format specifiers as printf, but it needs the address of each variable, written with &:

C
#include <stdio.h>

int main(void) {
    int age;
    printf("Enter your age: ");
    scanf("%d", &age);
    printf("In 5 years you will be %d.\n", age + 5);
    return 0;
}
Output
Enter your age: 21
In 5 years you will be 26.
Sample input is includedTry it Yourself

The green underlined text in the output is what the user typed. Click Try it Yourself and change the input box to test other values.

Why the & ?

scanf has to put a value into your variable, so it needs to know where the variable lives in memory. &age means "the address of age". You will understand this fully in the Pointers lesson — for now, remember: numbers and characters need &, strings do not.

Reading several values

One scanf can read many values. The user can separate them with spaces or new lines:

C
#include <stdio.h>

int main(void) {
    int a, b;
    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);
    printf("Sum = %d, Difference = %d, Product = %d\n", a + b, a - b, a * b);
    printf("Quotient = %.2f\n", (float)a / b);
    return 0;
}
Output
Enter two numbers: 12 7
Sum = 19, Difference = 5, Product = 84
Quotient = 1.71
Sample input is includedTry it Yourself

Reading decimals: %f versus %lf

printf prints both float and double with %f, but scanf is pickier:

Variable type scanf specifier
float %f
double %lf
C
#include <stdio.h>

int main(void) {
    double weight, height;
    printf("Weight in kg: ");
    scanf("%lf", &weight);
    printf("Height in m: ");
    scanf("%lf", &height);
    printf("Your BMI is %.1f\n", weight / (height * height));
    return 0;
}
Output
Weight in kg: 72.5
Height in m: 1.68
Your BMI is 25.7
Sample input is includedTry it Yourself

Reading a character

%c reads the very next character — including a leftover newline from the previous input. Put a space before it (" %c") to skip whitespace first:

C
#include <stdio.h>

int main(void) {
    int qty;
    char confirm;
    printf("Quantity: ");
    scanf("%d", &qty);
    printf("Confirm order? (Y/N): ");
    scanf(" %c", &confirm);      // the space skips the newline left by the Enter key
    printf("You ordered %d and answered %c\n", qty, confirm);
    return 0;
}
Output
Quantity: 3
Confirm order? (Y/N): Y
You ordered 3 and answered Y
Sample input is includedTry it Yourself

Reading strings and whole lines

%s reads one word (it stops at a space). For a full line with spaces, use fgets:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    char first[20], city[50];
    printf("First name: ");
    scanf("%19s", first);                 // no & for arrays; 19 leaves room for '\0'
    getchar();                            // eat the newline left behind
    printf("City and state: ");
    fgets(city, sizeof city, stdin);      // reads spaces too
    city[strcspn(city, "\n")] = '\0';     // remove the trailing newline
    printf("%s lives in %s.\n", first, city);
    return 0;
}
Output
First name: Asha
City and state: Bengaluru, Karnataka
Asha lives in Bengaluru, Karnataka.
Sample input is includedTry it Yourself

Checking that input worked

scanf returns how many values it successfully read. If the user types letters where a number is expected, it returns 0:

C
#include <stdio.h>

int main(void) {
    int n;
    printf("Enter a number: ");
    if (scanf("%d", &n) == 1)
        printf("You typed %d\n", n);
    else
        printf("That was not a number!\n");
    return 0;
}
Output
Enter a number: abc
That was not a number!
Sample input is includedTry it Yourself

Chapter 5 · Getting Started

Operators & Expressions

Arithmetic, increment, relational, logical, bitwise and ternary operators — plus precedence and type casting, which examiners love.

Operators are symbols that perform operations on values (called operands). a + b uses the + operator on operands a and b.

Arithmetic operators

Operator Meaning Example Result
+ Addition 17 + 5 22
- Subtraction 17 - 5 12
* Multiplication 17 * 5 85
/ Division 17 / 5 3 (integer division!)
% Remainder (modulus) 17 % 5 2
C
#include <stdio.h>

int main(void) {
    int a = 17, b = 5;
    printf("%d / %d = %d\n", a, b, a / b);         // integers: the fraction is dropped
    printf("%d %% %d = %d\n", a, b, a % b);
    printf("17.0 / 5 = %.1f\n", 17.0 / 5);         // one double operand: real division
    printf("-17 / 5 = %d, -17 %% 5 = %d\n", -17 / 5, -17 % 5);
    return 0;
}
Output
17 / 5 = 3
17 % 5 = 2
17.0 / 5 = 3.4
-17 / 5 = -3, -17 % 5 = -2

Increment and decrement

++ adds 1 and -- subtracts 1. The position matters when the result is used in the same expression:

  • Pre-increment ++x: increase first, then use the new value.
  • Post-increment x++: use the old value, then increase.
C
#include <stdio.h>

int main(void) {
    int x = 5, y;
    y = x++;            // y gets 5, then x becomes 6
    printf("x = %d, y = %d\n", x, y);
    y = ++x;            // x becomes 7, then y gets 7
    printf("x = %d, y = %d\n", x, y);
    return 0;
}
Output
x = 6, y = 5
x = 7, y = 7

Assignment operators

x += 3 is shorthand for x = x + 3. The same works for -=, *=, /=, %=, &=, |=, ^=, <<= and >>=.

Relational and logical operators

Relational operators compare values and produce 1 (true) or 0 (false): ==, !=, <, >, <=, >=.

Logical operators combine conditions:

Operator Name True when
&& AND both sides are true
|| OR at least one side is true
! NOT the operand is false
C
#include <stdio.h>

int main(void) {
    int marks = 78, attendance = 82;
    printf("marks >= 40: %d\n", marks >= 40);
    printf("eligible: %d\n", marks >= 40 && attendance >= 75);
    printf("needs help: %d\n", marks < 40 || attendance < 75);
    printf("!eligible: %d\n", !(marks >= 40 && attendance >= 75));
    return 0;
}
Output
marks >= 40: 1
eligible: 1
needs help: 0
!eligible: 0

C uses short-circuit evaluation: in a && b, if a is false, b is never evaluated. In a || b, if a is true, b is skipped. This lets you write safe checks like if (d != 0 && n / d > 2).

Bitwise operators

Bitwise operators work on the individual bits of integers. They are fast and appear in systems code and interviews.

Operator Name Example (12 = 1100, 10 = 1010) Result
& AND 12 & 10 8 (1000)
| OR 12 | 10 14 (1110)
^ XOR 12 ^ 10 6 (0110)
~ NOT (one operand) ~12 -13
<< Left shift 12 << 1 24
>> Right shift 12 >> 2 3
C
#include <stdio.h>

int main(void) {
    int a = 12, b = 10;
    printf("a & b = %d\n", a & b);
    printf("a | b = %d\n", a | b);
    printf("a ^ b = %d\n", a ^ b);
    printf("a << 1 = %d (same as a * 2)\n", a << 1);
    printf("a >> 2 = %d (same as a / 4)\n", a >> 2);
    printf("13 is %s\n", (13 & 1) ? "odd" : "even");   // lowest bit tells odd/even
    return 0;
}
Output
a & b = 8
a | b = 14
a ^ b = 6
a << 1 = 24 (same as a * 2)
a >> 2 = 3 (same as a / 4)
13 is odd

The ternary operator

condition ? value_if_true : value_if_false is a compact if-else that produces a value:

C
#include <stdio.h>

int main(void) {
    int a = 14, b = 29;
    int max = (a > b) ? a : b;
    printf("Larger number: %d\n", max);
    printf("%d is %s\n", a, a % 2 == 0 ? "even" : "odd");
    return 0;
}
Output
Larger number: 29
14 is even

Precedence and associativity

When an expression has several operators, precedence decides which runs first, and associativity breaks ties. Highest first:

Level Operators Associativity
1 () [] -> . x++ x-- left to right
2 ++x --x ! ~ -x (type) *p &x sizeof right to left
3 * / % left to right
4 + - left to right
5 << >> left to right
6 < <= > >= left to right
7 == != left to right
8–10 & then ^ then | left to right
11–12 && then || left to right
13 ?: right to left
14 = += -= … right to left
C
#include <stdio.h>

int main(void) {
    printf("%d\n", 2 + 3 * 4);       // * before +  -> 14
    printf("%d\n", (2 + 3) * 4);     // brackets first -> 20
    printf("%d\n", 20 / 4 * 2);      // same level, left to right -> 10
    printf("%d\n", 10 - 4 - 3);      // left to right -> 3
    return 0;
}
Output
14
20
10
3

Type conversion and casting

When you mix types, C implicitly converts the smaller type to the larger one (int → double in 5 / 2.0). To force a conversion yourself, use a cast: (type) value.

C
#include <stdio.h>

int main(void) {
    int total = 437, count = 5;
    float wrong = total / count;            // int division happens first: 87
    float right = (float)total / count;     // cast first: 87.4
    printf("wrong = %.2f, right = %.2f\n", wrong, right);

    double price = 99.99;
    int rupees = (int)price;                // casting to int truncates
    printf("rupees = %d\n", rupees);
    return 0;
}
Output
wrong = 87.00, right = 87.40
rupees = 99

Chapter 6 · Control Flow

If, Else & Switch

Make decisions with if, else if, nested conditions and switch — with the classic grade, leap-year and calculator programs.

Programs become useful when they can decide. C runs a block of code only when a condition is true (any non-zero value) and skips it when the condition is false (zero).

if

C
#include <stdio.h>

int main(void) {
    int age;
    printf("Age: ");
    scanf("%d", &age);
    if (age >= 18) {
        printf("You can vote.\n");
    }
    printf("Thanks for checking!\n");
    return 0;
}
Output
Age: 17
Thanks for checking!
Sample input is includedTry it Yourself

The braces can be dropped when the block has exactly one statement, but keeping them avoids bugs when you later add a second line.

if...else

C
#include <stdio.h>

int main(void) {
    int n;
    printf("Enter a number: ");
    scanf("%d", &n);
    if (n % 2 == 0)
        printf("%d is even\n", n);
    else
        printf("%d is odd\n", n);
    return 0;
}
Output
Enter a number: -7
-7 is odd
Sample input is includedTry it Yourself

else if ladders

Check conditions top to bottom; the first true one wins and the rest are skipped.

C
#include <stdio.h>

int main(void) {
    int marks;
    printf("Marks out of 100: ");
    scanf("%d", &marks);

    if (marks < 0 || marks > 100)
        printf("Invalid marks\n");
    else if (marks >= 90)
        printf("Grade O (Outstanding)\n");
    else if (marks >= 80)
        printf("Grade A+\n");
    else if (marks >= 70)
        printf("Grade A\n");
    else if (marks >= 60)
        printf("Grade B+\n");
    else if (marks >= 40)
        printf("Grade P (Pass)\n");
    else
        printf("Grade F (Fail)\n");
    return 0;
}
Output
Marks out of 100: 83
Grade A+
Sample input is includedTry it Yourself

Nested if: the leap-year program

A year is a leap year if it is divisible by 4, except century years, which must be divisible by 400. This is one of the most-asked programs in CSE 101 exams.

C
#include <stdio.h>

int main(void) {
    int year;
    printf("Year: ");
    scanf("%d", &year);
    if (year % 4 == 0) {
        if (year % 100 == 0) {
            if (year % 400 == 0)
                printf("%d is a leap year\n", year);
            else
                printf("%d is not a leap year\n", year);
        } else {
            printf("%d is a leap year\n", year);
        }
    } else {
        printf("%d is not a leap year\n", year);
    }
    return 0;
}
Output
Year: 1900
1900 is not a leap year
Sample input is includedTry it Yourself

The same logic fits in one condition: (year % 4 == 0 && year % 100 != 0) || year % 400 == 0.

switch

When you compare one integer or character against many fixed values, switch is cleaner than a long else if ladder:

C
#include <stdio.h>

int main(void) {
    double a, b;
    char op;
    printf("Enter two numbers: ");
    scanf("%lf %lf", &a, &b);
    printf("Operator (+ - * /): ");
    scanf(" %c", &op);

    switch (op) {
        case '+': printf("%.2f\n", a + b); break;
        case '-': printf("%.2f\n", a - b); break;
        case '*': printf("%.2f\n", a * b); break;
        case '/':
            if (b != 0) printf("%.2f\n", a / b);
            else        printf("Cannot divide by zero\n");
            break;
        default:  printf("Unknown operator %c\n", op);
    }
    return 0;
}
Output
Enter two numbers: 12 4
Operator (+ - * /): /
3.00
Sample input is includedTry it Yourself

Rules to remember:

  • The switch expression must be an integer type (int, char, enum) — not float or a string.
  • Each case label must be a constant, and labels must be unique.
  • break exits the switch. Without it, execution falls through into the next case.
  • default runs when no case matches. It is optional and can appear anywhere.

Fall-through is occasionally useful — for grouping cases:

C
#include <stdio.h>

int main(void) {
    char ch;
    printf("Letter: ");
    scanf(" %c", &ch);
    switch (ch) {
        case 'a': case 'e': case 'i': case 'o': case 'u':
        case 'A': case 'E': case 'I': case 'O': case 'U':
            printf("%c is a vowel\n", ch);
            break;
        default:
            printf("%c is a consonant\n", ch);
    }
    return 0;
}
Output
Letter: e
e is a vowel
Sample input is includedTry it Yourself

Chapter 7 · Control Flow

Loops: while, do-while and for

Repeat work with the three C loops, choose the right one, and write the classic digit, factorial, prime and Fibonacci programs.

A loop repeats a block of code while a condition stays true. C has three loops. They can all do the same job; each one simply fits a different situation.

while

A while loop checks its condition before each pass. Use it when you do not know in advance how many times to repeat.

C
#include <stdio.h>

int main(void) {
    int n, sum = 0, count = 0;
    printf("Enter a number: ");
    scanf("%d", &n);
    while (n > 0) {
        sum += n % 10;      // take the last digit
        n /= 10;            // drop the last digit
        count++;
    }
    printf("Digits: %d, Sum of digits: %d\n", count, sum);
    return 0;
}
Output
Enter a number: 40213
Digits: 5, Sum of digits: 10
Sample input is includedTry it Yourself

do...while

A do-while loop checks its condition after each pass, so the body always runs at least once. That makes it perfect for menus:

C
#include <stdio.h>

int main(void) {
    int choice;
    do {
        printf("1) Say hi  2) Say bye  3) Quit : ");
        scanf("%d", &choice);
        if (choice == 1) printf("Hi!\n");
        else if (choice == 2) printf("Bye!\n");
    } while (choice != 3);
    printf("Menu closed.\n");
    return 0;
}
Output
1) Say hi  2) Say bye  3) Quit : 1
Hi!
1) Say hi  2) Say bye  3) Quit : 2
Bye!
1) Say hi  2) Say bye  3) Quit : 3
Menu closed.
Sample input is includedTry it Yourself

for

A for loop packs the three parts of counting into one line: initialise; condition; update.

C
#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 5; i++) {
        printf("5 x %d = %d\n", i, 5 * i);
    }
    return 0;
}
Output
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
5 x 4 = 20
5 x 5 = 25

The order is: initialise once → check condition → run body → update → check condition → … until the condition is false.

StartInitialise: i = 1i <= 5 ?YesRun the body, then i++NoContinue after the loop
How a for loop runs. After the body and i++, control jumps back to the condition.

Choosing a loop

Loop Checks condition Runs at least once? Best for
for before each pass no counting a known number of times
while before each pass no repeating until something happens
do-while after each pass yes menus, input validation

Classic loop programs

Factorial

C
#include <stdio.h>

int main(void) {
    int n;
    long long fact = 1;     // factorials grow fast, so use a big type
    printf("n = ");
    scanf("%d", &n);
    for (int i = 2; i <= n; i++)
        fact *= i;
    printf("%d! = %lld\n", n, fact);
    return 0;
}
Output
n = 10
10! = 3628800
Sample input is includedTry it Yourself

Prime check

A number is prime if no number from 2 to √n divides it. Checking only up to √n makes the loop much faster:

C
#include <stdio.h>

int main(void) {
    int n, isPrime = 1;
    printf("n = ");
    scanf("%d", &n);
    if (n < 2) isPrime = 0;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) { isPrime = 0; break; }
    }
    printf("%d is %s\n", n, isPrime ? "prime" : "not prime");
    return 0;
}
Output
n = 97
97 is prime
Sample input is includedTry it Yourself

Fibonacci series

C
#include <stdio.h>

int main(void) {
    int n, a = 0, b = 1;
    printf("How many terms? ");
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        printf("%d ", a);
        int next = a + b;
        a = b;
        b = next;
    }
    printf("\n");
    return 0;
}
Output
How many terms? 10
0 1 1 2 3 5 8 13 21 34
Sample input is includedTry it Yourself

Reverse a number and check palindrome

C
#include <stdio.h>

int main(void) {
    int n, original, reversed = 0;
    printf("n = ");
    scanf("%d", &n);
    original = n;
    while (n > 0) {
        reversed = reversed * 10 + n % 10;
        n /= 10;
    }
    printf("Reversed: %d\n", reversed);
    printf("%d %s a palindrome\n", original, original == reversed ? "is" : "is not");
    return 0;
}
Output
n = 12321
Reversed: 12321
12321 is a palindrome
Sample input is includedTry it Yourself

Chapter 8 · Control Flow

Break, Continue & Pattern Programs

Control loops with break, continue and goto, then master nested loops through the star and number patterns every exam asks for.

break

break ends the nearest loop (or switch) immediately:

C
#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 10; i++) {
        if (i == 6) break;
        printf("%d ", i);
    }
    printf("\nLoop ended early.\n");
    return 0;
}
Output
1 2 3 4 5 
Loop ended early.

continue

continue skips the rest of the current pass and jumps to the next one:

C
#include <stdio.h>

int main(void) {
    printf("Odd numbers below 12: ");
    for (int i = 1; i < 12; i++) {
        if (i % 2 == 0) continue;
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}
Output
Odd numbers below 12: 1 3 5 7 9 11

Nested loops

A loop inside another loop runs its inner body rows × columns times. The outer loop usually picks the row; the inner loop prints the columns of that row. That is the whole secret of pattern programs.

C
#include <stdio.h>

int main(void) {
    for (int row = 1; row <= 3; row++) {
        for (int col = 1; col <= 4; col++)
            printf("(%d,%d) ", row, col);
        printf("\n");
    }
    return 0;
}
Output
(1,1) (1,2) (1,3) (1,4) 
(2,1) (2,2) (2,3) (2,4) 
(3,1) (3,2) (3,3) (3,4)

Pattern 1: right triangle

Row i has i stars.

C
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++)
            printf("* ");
        printf("\n");
    }
    return 0;
}
Output
* 
* * 
* * * 
* * * * 
* * * * *

Pattern 2: number triangle and Floyd's triangle

C
#include <stdio.h>

int main(void) {
    int n = 4, k = 1;
    for (int i = 1; i <= n; i++) {          // 1 / 1 2 / 1 2 3 ...
        for (int j = 1; j <= i; j++) printf("%d ", j);
        printf("\n");
    }
    printf("\n");
    for (int i = 1; i <= n; i++) {          // Floyd: keep counting across rows
        for (int j = 1; j <= i; j++) printf("%-3d", k++);
        printf("\n");
    }
    return 0;
}
Output
1 
1 2 
1 2 3 
1 2 3 4 

1  
2  3  
4  5  6  
7  8  9  10

Pattern 3: pyramid

Each row needs spaces first, then stars. For n rows, row i has n - i spaces and 2i - 1 stars.

C
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = 1; i <= n; i++) {
        for (int s = 1; s <= n - i; s++) printf(" ");
        for (int j = 1; j <= 2 * i - 1; j++) printf("*");
        printf("\n");
    }
    return 0;
}
Output
    *
   ***
  *****
 *******
*********

Pattern 4: diamond

A diamond is a pyramid followed by an upside-down pyramid:

C
#include <stdio.h>

int main(void) {
    int n = 4;
    for (int i = 1; i <= n; i++) {
        for (int s = 0; s < n - i; s++) printf(" ");
        for (int j = 0; j < 2 * i - 1; j++) printf("*");
        printf("\n");
    }
    for (int i = n - 1; i >= 1; i--) {
        for (int s = 0; s < n - i; s++) printf(" ");
        for (int j = 0; j < 2 * i - 1; j++) printf("*");
        printf("\n");
    }
    return 0;
}
Output
   *
  ***
 *****
*******
 *****
  ***
   *

Pattern 5: Pascal's triangle

Each number is the sum of the two above it. Using the formula C(i, j+1) = C(i, j) × (i − j) / (j + 1) we can print each row without storing earlier rows:

C
#include <stdio.h>

int main(void) {
    int n = 6;
    for (int i = 0; i < n; i++) {
        for (int s = 0; s < n - i - 1; s++) printf("  ");
        int c = 1;
        for (int j = 0; j <= i; j++) {
            printf("%4d", c);
            c = c * (i - j) / (j + 1);
        }
        printf("\n");
    }
    return 0;
}
Output
             1
           1   1
         1   2   1
       1   3   3   1
     1   4   6   4   1
   1   5  10  10   5   1

Chapter 9 · Functions

Functions

Split programs into named, reusable pieces: declarations, definitions, parameters, return values and call by value.

A function is a named block of code that does one job. You have been calling functions since lesson one — printf and scanf are functions written by someone else. Now you will write your own.

Why bother?

  • Reuse — write the logic once, call it many times.
  • Readability — isPrime(n) explains itself; forty lines of loop do not.
  • Testing — a small function is easy to check on its own.

Defining and calling a function

C
#include <stdio.h>

// definition: return type, name, parameters, body
int square(int x) {
    return x * x;
}

int main(void) {
    int a = square(7);           // call: 7 is the argument
    printf("7 squared is %d\n", a);
    printf("12 squared is %d\n", square(12));
    return 0;
}
Output
7 squared is 49
12 squared is 144
Part In the example
Return type int — the type of value sent back
Name square
Parameter list (int x) — inputs the function receives
Body { return x * x; }
return sends a value back and ends the function

A function that returns nothing has return type void. A function that takes nothing has (void) as its parameter list.

C
#include <stdio.h>

void printLine(int n, char ch) {
    for (int i = 0; i < n; i++) putchar(ch);
    putchar('\n');
}

int main(void) {
    printLine(20, '=');
    printf("  REPORT CARD\n");
    printLine(20, '-');
    return 0;
}
Output
====================
  REPORT CARD
--------------------

Declarations (prototypes)

C reads your file top to bottom. If main calls a function defined below it, you must declare the function first with a prototype — the header line followed by a semicolon:

C
#include <stdio.h>

int isPrime(int n);          // prototype: tells the compiler the function exists

int main(void) {
    printf("Primes up to 30: ");
    for (int i = 1; i <= 30; i++)
        if (isPrime(i)) printf("%d ", i);
    printf("\n");
    return 0;
}

int isPrime(int n) {         // definition comes later
    if (n < 2) return 0;
    for (int d = 2; d * d <= n; d++)
        if (n % d == 0) return 0;
    return 1;
}
Output
Primes up to 30: 2 3 5 7 11 13 17 19 23 29

Call by value

When you pass a variable to a function, C copies its value into the parameter. The function works on the copy; the original is untouched.

C
#include <stdio.h>

void tryToChange(int x) {
    x = 100;
    printf("Inside the function: x = %d\n", x);
}

int main(void) {
    int x = 5;
    tryToChange(x);
    printf("Back in main:        x = %d\n", x);
    return 0;
}
Output
Inside the function: x = 100
Back in main:        x = 5

So how can a function swap two variables in main? It needs their addresses — that is call by reference, which you will learn in Pointers, Arrays & Functions.

Functions calling functions

Good programs are built from small functions that call each other:

C
#include <stdio.h>

int gcd(int a, int b) {
    while (b != 0) {
        int r = a % b;
        a = b;
        b = r;
    }
    return a;
}

int lcm(int a, int b) {
    return a / gcd(a, b) * b;
}

int main(void) {
    int a, b;
    printf("Two numbers: ");
    scanf("%d %d", &a, &b);
    printf("GCD = %d, LCM = %d\n", gcd(a, b), lcm(a, b));
    return 0;
}
Output
Two numbers: 12 18
GCD = 6, LCM = 36
Sample input is includedTry it Yourself

gcd uses Euclid's algorithm: gcd(a, b) = gcd(b, a mod b), stopping when b is 0.

Library functions

C ships with many ready-made functions. Include the right header to use them:

Header Useful functions
stdio.h printf, scanf, fgets, puts, fopen
math.h sqrt, pow, fabs, ceil, floor, sin, log
string.h strlen, strcpy, strcmp, strcat
stdlib.h malloc, free, abs, rand, atoi, exit
ctype.h isdigit, isalpha, toupper, tolower
C
#include <stdio.h>
#include <math.h>

int main(void) {
    printf("sqrt(144) = %.0f\n", sqrt(144));
    printf("pow(2, 10) = %.0f\n", pow(2, 10));
    printf("ceil(4.1) = %.0f, floor(4.9) = %.0f\n", ceil(4.1), floor(4.9));
    printf("hypotenuse of 3,4 = %.1f\n", sqrt(3 * 3 + 4 * 4));
    return 0;
}
Output
sqrt(144) = 12
pow(2, 10) = 1024
ceil(4.1) = 5, floor(4.9) = 4
hypotenuse of 3,4 = 5.0

Chapter 10 · Functions

Recursion

Functions that call themselves: base cases, the call stack, and the factorial, Fibonacci, power, digit-sum and Tower of Hanoi programs.

A function is recursive when it calls itself. Recursion solves a problem by solving a smaller version of the same problem, until it reaches a case so small that the answer is obvious.

Every recursive function needs two parts:

  1. Base case — the smallest input, answered directly without recursion.
  2. Recursive case — express the answer using the same function on a smaller input.

Factorial

5! = 5 × 4! and 0! = 1. That definition is the code:

C
#include <stdio.h>

long long factorial(int n) {
    if (n <= 1) return 1;               // base case
    return n * factorial(n - 1);        // recursive case
}

int main(void) {
    for (int i = 0; i <= 6; i++)
        printf("%d! = %lld\n", i, factorial(i));
    return 0;
}
Output
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720

What happens on the call stack

Each call gets its own stack frame holding its own copy of n. Calls pile up until the base case, then return values flow back down:

main()factorial(4)factorial(3)factorial(2)factorial(1)topcall stack
While computing factorial(4), five frames are on the stack. factorial(1) returns 1, then each frame multiplies and returns.
Text
factorial(4) = 4 * factorial(3)
             = 4 * (3 * factorial(2))
             = 4 * (3 * (2 * factorial(1)))
             = 4 * (3 * (2 * 1))
             = 24

Fibonacci

fib(0) = 0, fib(1) = 1, fib(n) = fib(n−1) + fib(n−2):

C
#include <stdio.h>

int fib(int n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

int main(void) {
    for (int i = 0; i < 12; i++) printf("%d ", fib(i));
    printf("\n");
    return 0;
}
Output
0 1 1 2 3 5 8 13 21 34 55 89

This is elegant but slow: fib(n) recomputes the same values again and again, making about 2ⁿ calls. You will fix this with memoisation in the Algorithms subject (Dynamic Programming).

f2f1f3f2f4f2f1f3f5
The call tree of fib(5). Highlighted calls are repeated work — fib(3) is computed twice and fib(2) three times.

More recursive classics

C
#include <stdio.h>

int sumDigits(int n)        { return n == 0 ? 0 : n % 10 + sumDigits(n / 10); }
long long power(int b, int e) {
    if (e == 0) return 1;
    long long half = power(b, e / 2);       // fast power: b^e = (b^(e/2))^2
    return e % 2 ? half * half * b : half * half;
}
void printBinary(int n)     { if (n > 1) printBinary(n / 2); printf("%d", n % 2); }
int gcd(int a, int b)       { return b == 0 ? a : gcd(b, a % b); }

int main(void) {
    printf("sumDigits(9045) = %d\n", sumDigits(9045));
    printf("power(3, 13) = %lld\n", power(3, 13));
    printf("13 in binary = "); printBinary(13); printf("\n");
    printf("gcd(84, 36) = %d\n", gcd(84, 36));
    return 0;
}
Output
sumDigits(9045) = 18
power(3, 13) = 1594323
13 in binary = 1101
gcd(84, 36) = 12

Tower of Hanoi

Move n disks from peg A to peg C using peg B, never placing a larger disk on a smaller one. The recursive insight: move n−1 disks out of the way, move the biggest disk, then move the n−1 disks back on top.

C
#include <stdio.h>

int moves = 0;

void hanoi(int n, char from, char via, char to) {
    if (n == 0) return;
    hanoi(n - 1, from, to, via);
    printf("Move disk %d from %c to %c\n", n, from, to);
    moves++;
    hanoi(n - 1, via, from, to);
}

int main(void) {
    hanoi(3, 'A', 'B', 'C');
    printf("Total moves: %d\n", moves);   // always 2^n - 1
    return 0;
}
Output
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C
Total moves: 7

Recursion vs iteration

Recursion Iteration (loops)
Code short, mirrors the maths sometimes longer
Memory one stack frame per call constant
Speed function-call overhead usually faster
Best for trees, divide and conquer, backtracking simple repetition

Chapter 11 · Functions

Scope & Storage Classes

Where variables live and how long they last: local, global and block scope, plus the auto, static, extern and register storage classes.

Every variable has a scope (where in the code its name can be used) and a lifetime (how long its memory exists). A storage class keyword controls both.

Local, global and block scope

C
#include <stdio.h>

int counter = 0;                 // global: visible in every function below

void visit(void) {
    counter++;                   // uses the global
    int local = 10;              // local: exists only during this call
    printf("visit: counter = %d, local = %d\n", counter, local);
}

int main(void) {
    visit();
    visit();
    int x = 1;
    {
        int x = 2;               // block scope: this x hides the outer x
        printf("inner x = %d\n", x);
    }
    printf("outer x = %d\n", x);
    return 0;
}
Output
visit: counter = 1, local = 10
visit: counter = 2, local = 10
inner x = 2
outer x = 1

When an inner variable has the same name as an outer one, the inner one shadows it inside its block.

The four storage classes

Storage class Where stored Default value Scope Lifetime
auto stack garbage block until the block ends
register CPU register (a hint) garbage block until the block ends
static data segment 0 block (or file) the whole program
extern data segment 0 global the whole program

auto

Every local variable is auto by default, so you never need to write the keyword.

static

A static local variable is created once and keeps its value between calls:

C
#include <stdio.h>

void countCalls(void) {
    static int calls = 0;   // initialised only once
    int normal = 0;         // re-created on every call
    calls++;
    normal++;
    printf("calls = %d, normal = %d\n", calls, normal);
}

int main(void) {
    countCalls();
    countCalls();
    countCalls();
    return 0;
}
Output
calls = 1, normal = 1
calls = 2, normal = 1
calls = 3, normal = 1

A static global variable or function is visible only inside its own .c file — a simple way to hide helpers from other files.

extern

extern declares a global variable that is defined in another file (or later in this one). It does not create new memory:

C
// file1.c
int total = 50;            // definition: memory is created here

// file2.c
extern int total;          // declaration: "total exists somewhere else"
void show(void) { printf("%d\n", total); }

register

register int i; asks the compiler to keep i in a CPU register for speed. Modern compilers ignore the hint and decide for themselves. You cannot take the address (&i) of a register variable.

Chapter 12 · Arrays & Strings

Arrays

Store many values of one type under one name: declaring, indexing, looping, and the sum, max, search and reverse programs.

An array stores many values of the same type in one contiguous block of memory, under one name. Instead of mark1, mark2, mark3… you write marks[0], marks[1], marks[2].

C
#include <stdio.h>

int main(void) {
    int marks[5] = {78, 92, 65, 88, 71};
    printf("First mark: %d\n", marks[0]);
    printf("Last mark:  %d\n", marks[4]);
    marks[2] = 70;                    // change one element
    for (int i = 0; i < 5; i++)
        printf("marks[%d] = %d\n", i, marks[i]);
    return 0;
}
Output
First mark: 78
Last mark:  71
marks[0] = 78
marks[1] = 92
marks[2] = 70
marks[3] = 88
marks[4] = 71
marks780921702883714
int marks[5] — five ints side by side in memory. Indexes run from 0 to size − 1.

Declaring and initialising

C
int a[5];                       // 5 ints, values are garbage (if local)
int b[5] = {1, 2, 3, 4, 5};     // fully initialised
int c[5] = {1, 2};              // rest become 0  -> {1, 2, 0, 0, 0}
int d[5] = {0};                 // all zeros
int e[] = {4, 8, 15, 16, 23};   // size worked out from the list: 5

The number of elements is sizeof(arr) / sizeof(arr[0]) — handy when you initialise without writing the size.

Reading an array and finding sum, average, max and min

C
#include <stdio.h>

int main(void) {
    int n, a[100];
    printf("How many numbers? ");
    scanf("%d", &n);
    printf("Enter %d numbers: ", n);
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);

    int sum = 0, max = a[0], min = a[0];
    for (int i = 0; i < n; i++) {
        sum += a[i];
        if (a[i] > max) max = a[i];
        if (a[i] < min) min = a[i];
    }
    printf("Sum = %d, Average = %.2f\n", sum, (float)sum / n);
    printf("Max = %d, Min = %d\n", max, min);
    return 0;
}
Output
How many numbers? 6
Enter 6 numbers: 45 12 89 33 89 7
Sum = 275, Average = 45.83
Max = 89, Min = 7
Sample input is includedTry it Yourself

Check each element until you find the target. In the worst case you look at all n elements: O(n).

C
#include <stdio.h>

int linearSearch(int a[], int n, int key) {
    for (int i = 0; i < n; i++)
        if (a[i] == key) return i;     // found: return its index
    return -1;                         // not found
}

int main(void) {
    int a[] = {14, 3, 27, 9, 41, 6};
    int n = sizeof(a) / sizeof(a[0]);
    printf("27 found at index %d\n", linearSearch(a, n, 27));
    printf("50 found at index %d\n", linearSearch(a, n, 50));
    return 0;
}
Output
27 found at index 2
50 found at index -1

Notice how arrays are passed to functions: int a[] plus the size n. Inside the function, sizeof(a) would not give the array size — the function only receives the address of the first element. That is why the size is passed separately.

Reverse an array in place

Swap the first and last, then the second and second-last, meeting in the middle:

C
#include <stdio.h>

int main(void) {
    int a[] = {1, 2, 3, 4, 5, 6, 7};
    int n = 7;
    for (int i = 0, j = n - 1; i < j; i++, j--) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    for (int i = 0; i < n; i++) printf("%d ", a[i]);
    printf("\n");
    return 0;
}
Output
7 6 5 4 3 2 1

Counting frequencies

Use a second array as a tally. Here count[d] counts how often digit d appears:

C
#include <stdio.h>

int main(void) {
    int votes[] = {2, 3, 2, 1, 2, 3, 3, 3, 0, 2};
    int count[4] = {0};
    for (int i = 0; i < 10; i++) count[votes[i]]++;
    for (int c = 0; c < 4; c++) printf("Candidate %d: %d votes\n", c, count[c]);
    return 0;
}
Output
Candidate 0: 1 votes
Candidate 1: 1 votes
Candidate 2: 4 votes
Candidate 3: 4 votes

Chapter 13 · Arrays & Strings

2D Arrays & Matrices

Rows and columns: declaring 2D arrays, reading matrices, and the addition, transpose, multiplication and diagonal-sum programs.

A 2D array is an array of arrays — a grid with rows and columns. It is how C represents matrices, game boards, seating charts and images.

C
#include <stdio.h>

int main(void) {
    int m[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    printf("Row 1, column 2: %d\n", m[1][2]);
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++)
            printf("%4d", m[i][j]);
        printf("\n");
    }
    return 0;
}
Output
Row 1, column 2: 7
   1   2   3   4
   5   6   7   8
   9  10  11  12
m0123012341567829101112
m[1][2] is row 1, column 2 — the value 7. The first index is the row, the second is the column.

How it sits in memory

Memory is one long line, so C stores 2D arrays in row-major order: all of row 0, then all of row 1, and so on. Element m[i][j] lives at position i * COLS + j from the start. That is why, when you pass a 2D array to a function, you must give the column count: void print(int m[][4], int rows).

Matrix addition

C
#include <stdio.h>
#define R 2
#define C 3

int main(void) {
    int a[R][C] = {{1, 2, 3}, {4, 5, 6}};
    int b[R][C] = {{6, 5, 4}, {3, 2, 1}};
    int sum[R][C];
    for (int i = 0; i < R; i++)
        for (int j = 0; j < C; j++)
            sum[i][j] = a[i][j] + b[i][j];
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) printf("%3d", sum[i][j]);
        printf("\n");
    }
    return 0;
}
Output
  7  7  7
  7  7  7

Transpose

The transpose swaps rows and columns: t[j][i] = a[i][j]. A 2×3 matrix becomes 3×2.

C
#include <stdio.h>

int main(void) {
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int t[3][2];
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 3; j++)
            t[j][i] = a[i][j];
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) printf("%3d", t[i][j]);
        printf("\n");
    }
    return 0;
}
Output
  1  4
  2  5
  3  6

Matrix multiplication

To multiply an r1 × c1 matrix by a c1 × c2 matrix, each result cell is a row × column dot product: c[i][j] = Σ a[i][k] × b[k][j]. Multiplication is only possible when the first matrix's columns equal the second's rows.

C
#include <stdio.h>

int main(void) {
    int r1, c1, r2, c2, a[10][10], b[10][10], c[10][10];
    printf("Rows and columns of A: ");
    scanf("%d %d", &r1, &c1);
    printf("Enter A:\n");
    for (int i = 0; i < r1; i++) for (int j = 0; j < c1; j++) scanf("%d", &a[i][j]);
    printf("Rows and columns of B: ");
    scanf("%d %d", &r2, &c2);
    if (c1 != r2) { printf("Cannot multiply: %d != %d\n", c1, r2); return 0; }
    printf("Enter B:\n");
    for (int i = 0; i < r2; i++) for (int j = 0; j < c2; j++) scanf("%d", &b[i][j]);

    for (int i = 0; i < r1; i++)
        for (int j = 0; j < c2; j++) {
            c[i][j] = 0;
            for (int k = 0; k < c1; k++)
                c[i][j] += a[i][k] * b[k][j];
        }

    printf("A x B =\n");
    for (int i = 0; i < r1; i++) {
        for (int j = 0; j < c2; j++) printf("%5d", c[i][j]);
        printf("\n");
    }
    return 0;
}
Output
Rows and columns of A: 2 3
Enter A:
1 2 3
4 5 6
Rows and columns of B: 3 2
Enter B:
7 8
9 10
11 12
A x B =
   58   64
  139  154
Sample input is includedTry it Yourself

The three nested loops make this O(n³) for square matrices.

Diagonal sums

C
#include <stdio.h>

int main(void) {
    int m[3][3] = {{2, 7, 6}, {9, 5, 1}, {4, 3, 8}};
    int main_d = 0, anti_d = 0;
    for (int i = 0; i < 3; i++) {
        main_d += m[i][i];
        anti_d += m[i][3 - 1 - i];
    }
    printf("Main diagonal = %d, anti-diagonal = %d\n", main_d, anti_d);
    return 0;
}
Output
Main diagonal = 15, anti-diagonal = 15

(That matrix is a magic square — every row, column and diagonal adds up to 15.)

Chapter 14 · Arrays & Strings

Strings

Character arrays ending in '\0': reading strings, the string.h functions, and writing length, reverse, palindrome and vowel-count programs yourself.

C has no special string type. A string is a char array that ends with the null character '\0'. Every string function relies on that terminator to know where the text stops.

C
#include <stdio.h>

int main(void) {
    char name[] = "ASHA";            // the compiler adds '\0' for you
    printf("%s has %zu bytes of storage\n", name, sizeof(name));
    for (int i = 0; name[i] != '\0'; i++)
        printf("name[%d] = '%c' (%d)\n", i, name[i], name[i]);
    return 0;
}
Output
ASHA has 5 bytes of storage
name[0] = 'A' (65)
name[1] = 'S' (83)
name[2] = 'H' (72)
name[3] = 'A' (65)
nameA0S1H2A3\04
char name[] = "ASHA" takes 5 bytes: four letters plus the terminating '\0'.

Declaring strings

C
char a[20] = "Hello";        // 20 bytes, "Hello" + '\0' + unused space
char b[] = "Hello";          // exactly 6 bytes
char c[] = {'H', 'i', '\0'}; // character by character — don't forget '\0'

string.h functions

Function What it does
strlen(s) length, not counting '\0'
strcpy(dst, src) copy src into dst
strncpy(dst, src, n) copy at most n characters
strcat(dst, src) append src to the end of dst
strcmp(a, b) 0 if equal, negative if a < b, positive if a > b (dictionary order)
strchr(s, c) pointer to first c in s, or NULL
strstr(s, t) pointer to first occurrence of t in s, or NULL
C
#include <stdio.h>
#include <string.h>

int main(void) {
    char first[30] = "Data";
    char second[] = "Structures";
    printf("Length of \"%s\": %zu\n", first, strlen(first));
    strcat(first, " ");
    strcat(first, second);
    printf("Joined: %s\n", first);

    char copy[30];
    strcpy(copy, first);
    printf("Copy: %s\n", copy);

    printf("strcmp(\"apple\", \"banana\") = %d\n", strcmp("apple", "banana") < 0 ? -1 : 1);
    printf("strcmp(\"cat\", \"cat\") = %d\n", strcmp("cat", "cat"));
    printf("Found \"Struct\" at position %td\n", strstr(first, "Struct") - first);
    return 0;
}
Output
Length of "Data": 4
Joined: Data Structures
Copy: Data Structures
strcmp("apple", "banana") = -1
strcmp("cat", "cat") = 0
Found "Struct" at position 5

Write them yourself

Exams frequently say "without using library functions". These four are the most common:

C
#include <stdio.h>

int myStrlen(const char s[]) {
    int n = 0;
    while (s[n] != '\0') n++;
    return n;
}

void myReverse(char s[]) {
    for (int i = 0, j = myStrlen(s) - 1; i < j; i++, j--) {
        char t = s[i]; s[i] = s[j]; s[j] = t;
    }
}

int isPalindrome(const char s[]) {
    for (int i = 0, j = myStrlen(s) - 1; i < j; i++, j--)
        if (s[i] != s[j]) return 0;
    return 1;
}

int countVowels(const char s[]) {
    int v = 0;
    for (int i = 0; s[i]; i++) {
        char c = s[i] | 32;           // quick lowercase for letters
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') v++;
    }
    return v;
}

int main(void) {
    char word[] = "Programming";
    printf("Length: %d\n", myStrlen(word));
    printf("Vowels: %d\n", countVowels(word));
    myReverse(word);
    printf("Reversed: %s\n", word);
    printf("\"madam\" palindrome? %s\n", isPalindrome("madam") ? "yes" : "no");
    printf("\"kalvium\" palindrome? %s\n", isPalindrome("kalvium") ? "yes" : "no");
    return 0;
}
Output
Length: 11
Vowels: 3
Reversed: gnimmargorP
"madam" palindrome? yes
"kalvium" palindrome? no

Changing case with ctype.h

C
#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main(void) {
    char line[100];
    int letters = 0, digits = 0, spaces = 0;
    printf("Type a line: ");
    fgets(line, sizeof line, stdin);
    line[strcspn(line, "\n")] = '\0';
    for (int i = 0; line[i]; i++) {
        if (isalpha((unsigned char)line[i])) letters++;
        else if (isdigit((unsigned char)line[i])) digits++;
        else if (line[i] == ' ') spaces++;
        line[i] = toupper((unsigned char)line[i]);
    }
    printf("Upper case: %s\n", line);
    printf("Letters %d, digits %d, spaces %d\n", letters, digits, spaces);
    return 0;
}
Output
Type a line: Hello, CSE 101!
Upper case: HELLO, CSE 101!
Letters 8, digits 3, spaces 2
Sample input is includedTry it Yourself

Arrays of strings

A list of words is a 2D char array — one row per word:

C
#include <stdio.h>

int main(void) {
    char days[7][10] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
    for (int i = 0; i < 7; i++)
        printf("%d: %s\n", i + 1, days[i]);
    return 0;
}
Output
1: Mon
2: Tue
3: Wed
4: Thu
5: Fri
6: Sat
7: Sun

Chapter 15 · Pointers & Memory

Pointers

Addresses, the & and * operators, pointer types, NULL, and pointer arithmetic — the idea that makes C powerful.

Every variable lives at some address in memory, like a house number. A pointer is a variable that stores an address. That one idea lets C build linked lists, pass big data without copying it, and talk directly to hardware.

& and *

  • &x — the address-of operator: "where does x live?"
  • *p — the dereference operator: "the value stored at the address in p".
C
#include <stdio.h>

int main(void) {
    int x = 10;
    int *p = &x;            // p is a pointer to int, holding x's address

    printf("x  = %d\n", x);
    printf("&x = %p\n", (void *)&x);
    printf("p  = %p (the same address)\n", (void *)p);
    printf("*p = %d (the value at that address)\n", *p);

    *p = 25;                // change x through the pointer
    printf("x is now %d\n", x);
    return 0;
}
Output · yours may differ
x  = 10
&x = 0x16f206768
p  = 0x16f206768 (the same address)
*p = 10 (the value at that address)
x is now 25

Addresses change every run, so your output will show different hex numbers. The picture below uses simple made-up addresses:

25xint0x10000x1000pint *0x2000
p stores the address of x, so *p and x are the same box of memory.

Declaring pointers

The type before * says what the pointer points to:

C
int    *ip;     // points to an int
double *dp;     // points to a double
char   *cp;     // points to a char
int *a, b;      // careful: a is a pointer, b is a plain int!

All pointers are the same size (8 bytes on a 64-bit machine) because they all hold an address. The type matters for dereferencing and arithmetic.

NULL and wild pointers

A pointer that has not been given an address yet should be set to NULL (address 0). Dereferencing NULL crashes the program immediately — which is much better than a wild pointer (uninitialised) that silently corrupts random memory.

C
#include <stdio.h>

int main(void) {
    int *p = NULL;
    if (p == NULL)
        printf("p points nowhere yet; do not dereference it.\n");
    int value = 42;
    p = &value;
    if (p != NULL)
        printf("Now *p = %d\n", *p);
    return 0;
}
Output
p points nowhere yet; do not dereference it.
Now *p = 42

Pointer arithmetic

Adding 1 to a pointer moves it to the next element, not the next byte. For an int * (4-byte ints), p + 1 is 4 bytes further on.

C
#include <stdio.h>

int main(void) {
    int a[5] = {10, 20, 30, 40, 50};
    int *p = a;                     // same as &a[0]

    printf("*p       = %d\n", *p);
    printf("*(p + 1) = %d\n", *(p + 1));
    printf("*(p + 4) = %d\n", *(p + 4));
    p++;                            // move to the next int
    printf("after p++, *p = %d\n", *p);
    printf("byte distance between &a[3] and &a[0] = %td\n", (char *)&a[3] - (char *)&a[0]);
    printf("element distance = %td\n", &a[3] - &a[0]);
    return 0;
}
Output
*p       = 10
*(p + 1) = 20
*(p + 4) = 50
after p++, *p = 20
byte distance between &a[3] and &a[0] = 12
element distance = 3
100201302403504pp+1p+4
p + k points k elements past p, whatever the element size.

Allowed pointer operations: add or subtract an integer, subtract two pointers into the same array, and compare pointers. You cannot add, multiply or divide two pointers.

Pointer to pointer

A pointer also lives at an address, so you can point to it:

C
#include <stdio.h>

int main(void) {
    int x = 7;
    int *p = &x;
    int **pp = &p;
    printf("x = %d, *p = %d, **pp = %d\n", x, *p, **pp);
    **pp = 99;
    printf("x after **pp = 99: %d\n", x);
    return 0;
}
Output
x = 7, *p = 7, **pp = 7
x after **pp = 99: 99
99xint0x10000x1000pint *0x20000x2000ppint **0x3000
pp points to p, which points to x.

void pointers

A void * can hold the address of any type, but you must cast it before dereferencing. malloc returns void *, which is why it can allocate memory for anything.

C
#include <stdio.h>

int main(void) {
    int n = 5;
    double d = 2.5;
    void *vp = &n;
    printf("int via void*: %d\n", *(int *)vp);
    vp = &d;
    printf("double via void*: %.1f\n", *(double *)vp);
    return 0;
}
Output
int via void*: 5
double via void*: 2.5

Chapter 16 · Pointers & Memory

Pointers, Arrays & Functions

Call by reference, the swap function, returning several results, how arrays decay to pointers, and pointers to strings.

Call by reference

Functions receive copies of their arguments. To let a function change the caller's variable, pass its address, and have the function write through the pointer:

C
#include <stdio.h>

void swapWrong(int a, int b) { int t = a; a = b; b = t; }
void swap(int *a, int *b)    { int t = *a; *a = *b; *b = t; }

int main(void) {
    int x = 3, y = 8;
    swapWrong(x, y);
    printf("after swapWrong: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("after swap:      x = %d, y = %d\n", x, y);
    return 0;
}
Output
after swapWrong: x = 3, y = 8
after swap:      x = 8, y = 3

Returning several results

A function can return only one value, but it can fill as many variables as you give it addresses of:

C
#include <stdio.h>

void stats(const int a[], int n, int *min, int *max, double *avg) {
    *min = *max = a[0];
    int sum = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] < *min) *min = a[i];
        if (a[i] > *max) *max = a[i];
        sum += a[i];
    }
    *avg = (double)sum / n;
}

int main(void) {
    int marks[] = {67, 91, 54, 78, 85};
    int lo, hi;
    double mean;
    stats(marks, 5, &lo, &hi, &mean);
    printf("min %d, max %d, average %.1f\n", lo, hi, mean);
    return 0;
}
Output
min 54, max 91, average 75.0

scanf("%d", &n) works exactly this way — now you know why it needs &.

Arrays and pointers

An array's name, used in an expression, decays to a pointer to its first element. These are all equivalent:

Array notation Pointer notation
a[i] *(a + i)
&a[i] a + i
a[0] *a
C
#include <stdio.h>

int sum(int *p, int n) {          // same as int p[]
    int s = 0;
    for (int i = 0; i < n; i++) s += *(p + i);
    return s;
}

int main(void) {
    int a[] = {5, 10, 15, 20};
    printf("a[2] = %d, *(a + 2) = %d, 2[a] = %d\n", a[2], *(a + 2), 2[a]);
    printf("sum = %d\n", sum(a, 4));
    printf("sum of last two = %d\n", sum(a + 2, 2));   // pass part of an array
    return 0;
}
Output
a[2] = 15, *(a + 2) = 15, 2[a] = 15
sum = 50
sum of last two = 35

(2[a] works because a[i] is defined as *(a + i), and addition does not care about order. Fun fact, never write it in real code.)

Pointers and strings

A string literal can be referenced by a char *. Walking a string with a pointer is a very common C idiom:

C
#include <stdio.h>

int length(const char *s) {
    const char *start = s;
    while (*s) s++;               // stop at '\0'
    return (int)(s - start);
}

void toUpper(char *s) {
    for (; *s; s++)
        if (*s >= 'a' && *s <= 'z') *s -= 32;
}

int main(void) {
    char word[] = "pointer";
    printf("length = %d\n", length(word));
    toUpper(word);
    printf("upper  = %s\n", word);

    const char *greeting = "hello";   // points to read-only memory
    printf("%c%c\n", *greeting, *(greeting + 4));
    return 0;
}
Output
length = 7
upper  = POINTER
ho

Arrays of pointers

An array of char * is the usual way to store a list of strings of different lengths:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    const char *subjects[] = {"C", "Data Structures", "Discrete Mathematics", "Python"};
    int n = sizeof(subjects) / sizeof(subjects[0]);
    for (int i = 0; i < n; i++)
        printf("%d. %s (%zu chars)\n", i + 1, subjects[i], strlen(subjects[i]));
    return 0;
}
Output
1. C (1 chars)
2. Data Structures (15 chars)
3. Discrete Mathematics (20 chars)
4. Python (6 chars)

Function pointers

Functions have addresses too. A function pointer lets you pass behaviour as an argument — this is how qsort lets you choose the sort order:

C
#include <stdio.h>
#include <stdlib.h>

int ascending(const void *a, const void *b)  { return *(const int *)a - *(const int *)b; }
int descending(const void *a, const void *b) { return *(const int *)b - *(const int *)a; }

void show(const int *a, int n) { for (int i = 0; i < n; i++) printf("%d ", a[i]); printf("\n"); }

int main(void) {
    int a[] = {42, 7, 19, 3, 88, 25};
    int n = 6;
    qsort(a, n, sizeof(int), ascending);
    show(a, n);
    qsort(a, n, sizeof(int), descending);
    show(a, n);

    int (*op)(const void *, const void *) = ascending;   // a variable holding a function
    printf("op(&a[0], &a[1]) = %d\n", op(&a[0], &a[1]) > 0);
    return 0;
}
Output
3 7 19 25 42 88 
88 42 25 19 7 3 
op(&a[0], &a[1]) = 1

Chapter 17 · Pointers & Memory

Dynamic Memory Allocation

Ask for memory at run time with malloc, calloc and realloc, give it back with free, and avoid leaks and dangling pointers.

So far every array had a size fixed when you wrote the program. But often you only learn the size while the program runs — how many students in this class? How many lines in this file? Dynamic memory allocation asks the operating system for memory at run time, from a region called the heap.

Stack vs heap

Stack Heap
Used for local variables, function calls memory you request with malloc
Size small (often 1–8 MB) large (most of RAM)
Freed automatically when the function returns only when you call free
Speed very fast slower to allocate

malloc

malloc(bytes) returns a pointer to a block of uninitialised memory, or NULL if it fails. All four functions live in stdlib.h.

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("Number of students: ");
    scanf("%d", &n);

    int *marks = malloc(n * sizeof(int));     // room for n ints
    if (marks == NULL) {
        printf("Out of memory!\n");
        return 1;
    }

    printf("Enter %d marks: ", n);
    for (int i = 0; i < n; i++) scanf("%d", &marks[i]);   // use it like an array

    int total = 0;
    for (int i = 0; i < n; i++) total += marks[i];
    printf("Average: %.1f\n", (double)total / n);

    free(marks);          // give the memory back
    marks = NULL;         // avoid a dangling pointer
    return 0;
}
Output
Number of students: 5
Enter 5 marks: 80 92 67 74 88
Average: 80.2
Sample input is includedTry it Yourself

calloc

calloc(count, size) allocates count × size bytes and sets them all to zero:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *freq = calloc(10, sizeof(int));   // ten zeros
    int digits[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    for (int i = 0; i < 11; i++) freq[digits[i]]++;
    for (int d = 0; d < 10; d++)
        if (freq[d]) printf("digit %d appears %d time(s)\n", d, freq[d]);
    free(freq);
    return 0;
}
Output
digit 1 appears 2 time(s)
digit 2 appears 1 time(s)
digit 3 appears 2 time(s)
digit 4 appears 1 time(s)
digit 5 appears 3 time(s)
digit 6 appears 1 time(s)
digit 9 appears 1 time(s)

realloc

realloc(ptr, newBytes) resizes a block. It may move the data to a new address, so always use the pointer it returns. This is how growable arrays (like C++'s vector or Python's list) work underneath:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int capacity = 2, size = 0;
    int *a = malloc(capacity * sizeof(int));
    for (int value = 1; value <= 9; value++) {
        if (size == capacity) {
            capacity *= 2;                                   // double when full
            int *bigger = realloc(a, capacity * sizeof(int));
            if (!bigger) { free(a); return 1; }
            a = bigger;
            printf("grew to capacity %d\n", capacity);
        }
        a[size++] = value * value;
    }
    for (int i = 0; i < size; i++) printf("%d ", a[i]);
    printf("\n");
    free(a);
    return 0;
}
Output
grew to capacity 4
grew to capacity 8
grew to capacity 16
1 4 9 16 25 36 49 64 81

malloc vs calloc

malloc(n * size) calloc(n, size)
one argument: total bytes two arguments: count and element size
memory contains garbage memory is zeroed
slightly faster safe default when you need zeros

Dynamic 2D arrays

Allocate an array of row pointers, then each row:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;
    int **grid = malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        grid[i] = malloc(cols * sizeof(int));
        for (int j = 0; j < cols; j++) grid[i][j] = i * cols + j;
    }
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) printf("%3d", grid[i][j]);
        printf("\n");
    }
    for (int i = 0; i < rows; i++) free(grid[i]);   // free rows first
    free(grid);                                     // then the row array
    return 0;
}
Output
  0  1  2  3
  4  5  6  7
  8  9 10 11

Memory leaks

A memory leak happens when you lose the last pointer to a block without freeing it. The memory stays reserved until the program ends. In long-running programs (servers, games) leaks slowly eat all RAM.

C
void leaky(void) {
    int *p = malloc(100 * sizeof(int));
    // ... use p ...
    // forgot free(p): when the function returns, p disappears but the block stays
}

Chapter 18 · Structures, Files & More

Structures, Unions & Enums

Group related data with struct, name types with typedef, use arrays of structures and pointers to them, and compare structures with unions and enums.

Real things have several properties. A student has a roll number, a name and a CGPA. A structure (struct) bundles variables of different types into one new type.

C
#include <stdio.h>
#include <string.h>

struct Student {
    int roll;
    char name[30];
    float cgpa;
};

int main(void) {
    struct Student s1 = {101, "Asha Rao", 9.1f};
    struct Student s2;
    s2.roll = 102;                        // the dot operator accesses members
    strcpy(s2.name, "Ravi Kumar");
    s2.cgpa = 8.4f;

    printf("%d  %-12s %.1f\n", s1.roll, s1.name, s1.cgpa);
    printf("%d  %-12s %.1f\n", s2.roll, s2.name, s2.cgpa);
    return 0;
}
Output
101  Asha Rao     9.1
102  Ravi Kumar   8.4

typedef

typedef gives a type a shorter name, so you can write Student instead of struct Student:

C
#include <stdio.h>

typedef struct {
    double x, y;
} Point;

double area(Point a, Point b, Point c) {   // triangle area from three corners
    double v = (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2;
    return v < 0 ? -v : v;
}

int main(void) {
    Point a = {0, 0}, b = {4, 0}, c = {0, 3};
    printf("Area = %.1f\n", area(a, b, c));
    return 0;
}
Output
Area = 6.0

Arrays of structures

C
#include <stdio.h>

typedef struct {
    int roll;
    char name[20];
    int marks;
} Student;

int main(void) {
    Student cls[50];
    int n, top = 0;
    printf("How many students? ");
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        printf("Roll, name, marks: ");
        scanf("%d %19s %d", &cls[i].roll, cls[i].name, &cls[i].marks);
        if (cls[i].marks > cls[top].marks) top = i;
    }
    printf("Topper: %s (roll %d) with %d marks\n", cls[top].name, cls[top].roll, cls[top].marks);
    return 0;
}
Output
How many students? 3
Roll, name, marks: 1 Asha 88
Roll, name, marks: 2 Ravi 95
Roll, name, marks: 3 Meera 79
Topper: Ravi (roll 2) with 95 marks
Sample input is includedTry it Yourself

Pointers to structures and the arrow operator

With a pointer to a struct, use -> to reach a member. p->marks is shorthand for (*p).marks:

C
#include <stdio.h>

typedef struct {
    char name[20];
    int balance;
} Account;

void deposit(Account *acc, int amount) {     // pass by pointer: no copy, changes stick
    acc->balance += amount;
}

int main(void) {
    Account a = {"Priya", 500};
    Account *p = &a;
    deposit(p, 250);
    printf("%s has Rs %d\n", p->name, p->balance);
    printf("Same thing: %d\n", (*p).balance);
    return 0;
}
Output
Priya has Rs 750
Same thing: 750

Passing a pointer to a large struct is faster than passing the struct itself, because nothing is copied.

Nested structures

C
#include <stdio.h>

typedef struct { int day, month, year; } Date;
typedef struct { char title[40]; Date due; } Assignment;

int main(void) {
    Assignment a = {"Linked list lab", {14, 11, 2026}};
    printf("%s is due on %02d/%02d/%d\n", a.title, a.due.day, a.due.month, a.due.year);
    return 0;
}
Output
Linked list lab is due on 14/11/2026

Structure size and padding

The compiler may insert unused padding bytes so each member starts at an address the CPU can read quickly. So a struct can be bigger than the sum of its members:

C
#include <stdio.h>

struct A { char c; int i; char d; };     // 1 + (3 pad) + 4 + 1 + (3 pad) = 12
struct B { int i; char c; char d; };     // 4 + 1 + 1 + (2 pad) = 8

int main(void) {
    printf("sizeof(struct A) = %zu\n", sizeof(struct A));
    printf("sizeof(struct B) = %zu\n", sizeof(struct B));
    return 0;
}
Output
sizeof(struct A) = 12
sizeof(struct B) = 8

Unions

A union looks like a struct, but all its members share the same memory. Its size is the size of its largest member, and only one member holds a meaningful value at a time.

C
#include <stdio.h>

union Value {
    int i;
    float f;
    char c;
};

int main(void) {
    union Value v;
    printf("sizeof(union Value) = %zu\n", sizeof(v));
    v.i = 65;
    printf("as int: %d, as char: %c\n", v.i, v.c);   // same bytes, read as a char
    v.f = 3.5f;                                       // overwrites the int
    printf("as float: %.1f\n", v.f);
    return 0;
}
Output
sizeof(union Value) = 4
as int: 65, as char: A
as float: 3.5
Structure Union
Each member has its own memory All members share one memory location
Size ≥ sum of member sizes Size = largest member (plus padding)
All members usable at once Only the last written member is valid

Enumerations

An enum gives names to integer constants, which makes code self-explanatory:

C
#include <stdio.h>

enum Day { MON = 1, TUE, WED, THU, FRI, SAT, SUN };   // TUE = 2, WED = 3 ...

int main(void) {
    enum Day today = FRI;
    printf("Friday is day %d\n", today);
    if (today == SAT || today == SUN) printf("Weekend!\n");
    else printf("%d day(s) to the weekend\n", SAT - today);
    return 0;
}
Output
Friday is day 5
1 day(s) to the weekend

Chapter 19 · Structures, Files & More

File Handling

Save data that outlives your program: open, write, read, append and close text files with fopen, fprintf, fscanf, fgets and friends.

Variables vanish when a program ends. Files keep data on disk. C works with files through a FILE * pointer from stdio.h.

The pattern is always the same: open → read or write → close.

Opening a file

C
FILE *fp = fopen("marks.txt", "w");
if (fp == NULL) { perror("fopen"); return 1; }   // always check!
Mode Meaning If the file exists If it does not
"r" read read from the start fails (returns NULL)
"w" write erases it creates it
"a" append adds at the end creates it
"r+" read and write opens it fails
"w+" read and write erases it creates it
"a+" read and append opens it creates it

Add b for binary files: "rb", "wb".

Writing and reading text

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("marks.txt", "w");
    if (!fp) { perror("marks.txt"); return 1; }
    fprintf(fp, "%s %d\n", "Asha", 91);
    fprintf(fp, "%s %d\n", "Ravi", 78);
    fprintf(fp, "%s %d\n", "Meera", 85);
    fclose(fp);
    printf("Saved 3 records.\n");

    fp = fopen("marks.txt", "r");
    if (!fp) { perror("marks.txt"); return 1; }
    char name[20];
    int marks, total = 0, count = 0;
    while (fscanf(fp, "%19s %d", name, &marks) == 2) {   // stop when a read fails
        printf("%-6s %d\n", name, marks);
        total += marks;
        count++;
    }
    fclose(fp);
    printf("Class average: %.2f\n", (double)total / count);
    return 0;
}
Output
Saved 3 records.
Asha   91
Ravi   78
Meera  85
Class average: 84.67

Line by line with fgets, and appending

C
#include <stdio.h>
#include <string.h>

int main(void) {
    FILE *fp = fopen("diary.txt", "w");
    fputs("Monday: started C file handling\n", fp);
    fclose(fp);

    fp = fopen("diary.txt", "a");                 // append keeps the old content
    fputs("Tuesday: wrote my first log file\n", fp);
    fclose(fp);

    char line[100];
    int n = 0;
    fp = fopen("diary.txt", "r");
    while (fgets(line, sizeof line, fp) != NULL) {
        line[strcspn(line, "\n")] = '\0';
        printf("%d | %s\n", ++n, line);
    }
    fclose(fp);
    return 0;
}
Output
1 | Monday: started C file handling
2 | Tuesday: wrote my first log file

Character by character: copy a file and count stats

C
#include <stdio.h>

int main(void) {
    FILE *out = fopen("source.txt", "w");
    fputs("Files are just bytes.\nRead them one char at a time!\n", out);
    fclose(out);

    FILE *in = fopen("source.txt", "r");
    FILE *copy = fopen("copy.txt", "w");
    int ch, chars = 0, lines = 0, words = 0, inWord = 0;
    while ((ch = fgetc(in)) != EOF) {          // ch must be int to hold EOF
        fputc(ch, copy);
        chars++;
        if (ch == '\n') lines++;
        if (ch == ' ' || ch == '\n' || ch == '\t') inWord = 0;
        else if (!inWord) { inWord = 1; words++; }
    }
    fclose(in);
    fclose(copy);
    printf("chars %d, words %d, lines %d\n", chars, words, lines);
    return 0;
}
Output
chars 52, words 11, lines 2

Binary files: saving structures

fwrite and fread copy raw bytes, which makes saving whole structures easy and fast:

C
#include <stdio.h>

typedef struct { int id; char item[16]; double price; } Product;

int main(void) {
    Product stock[3] = {{1, "Notebook", 45.0}, {2, "Pen", 10.5}, {3, "Calculator", 799.0}};
    FILE *fp = fopen("stock.dat", "wb");
    fwrite(stock, sizeof(Product), 3, fp);
    fclose(fp);

    Product p;
    fp = fopen("stock.dat", "rb");
    fseek(fp, 2 * sizeof(Product), SEEK_SET);     // jump straight to record #3
    fread(&p, sizeof(Product), 1, fp);
    printf("Record 3: %s costs Rs %.2f\n", p.item, p.price);
    rewind(fp);                                   // back to the start
    fread(&p, sizeof(Product), 1, fp);
    printf("Record 1: %s\n", p.item);
    fclose(fp);
    return 0;
}
Output
Record 3: Calculator costs Rs 799.00
Record 1: Notebook
Function Purpose
fopen, fclose open and close
fprintf, fscanf formatted write / read
fputs, fgets write / read a line
fputc, fgetc write / read one character
fwrite, fread write / read raw blocks (binary)
fseek, ftell, rewind move around in a file
feof, ferror check for end of file or an error

Chapter 20 · Structures, Files & More

Preprocessor, Macros & Command-Line Arguments

What happens before compilation: #include, #define macros and their traps, conditional compilation, header guards, and reading arguments with argc/argv.

Before the compiler sees your code, the preprocessor runs. It handles every line starting with #. It does pure text substitution — it does not understand C types or syntax.

#include

C
#include <stdio.h>      // angle brackets: search the system include folders
#include "mylib.h"      // quotes: search your project folder first

#define: constants and macros

C
#include <stdio.h>
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main(void) {
    printf("Area of r=2: %.2f\n", PI * SQUARE(2));
    printf("MAX(7, 12) = %d\n", MAX(7, 12));
    printf("SQUARE(3 + 1) = %d\n", SQUARE(3 + 1));
    return 0;
}
Output
Area of r=2: 12.57
MAX(7, 12) = 12
SQUARE(3 + 1) = 16

The bracket trap

Because macros are text replacement, missing brackets give wrong answers:

C
#include <stdio.h>
#define BAD_SQUARE(x) x * x
#define GOOD_SQUARE(x) ((x) * (x))

int main(void) {
    printf("BAD_SQUARE(3 + 1)  = %d\n", BAD_SQUARE(3 + 1));    // 3 + 1 * 3 + 1 = 7
    printf("GOOD_SQUARE(3 + 1) = %d\n", GOOD_SQUARE(3 + 1));   // (3 + 1) * (3 + 1) = 16
    printf("100 / BAD_SQUARE(5)  = %d\n", 100 / BAD_SQUARE(5)); // 100 / 5 * 5 = 100
    printf("100 / GOOD_SQUARE(5) = %d\n", 100 / GOOD_SQUARE(5));
    return 0;
}
Output
BAD_SQUARE(3 + 1)  = 7
GOOD_SQUARE(3 + 1) = 16
100 / BAD_SQUARE(5)  = 100
100 / GOOD_SQUARE(5) = 4

Predefined macros

C
#include <stdio.h>

int main(void) {
    printf("File: %s, line: %d\n", __FILE__, __LINE__);
    printf("Compiled on %s at %s\n", __DATE__, __TIME__);
    printf("C standard version: %ld\n", __STDC_VERSION__);
    return 0;
}
Output · yours may differ
File: main.c, line: 4
Compiled on Oct 31 2026 at 13:03:21
C standard version: 201710

Conditional compilation

Include or exclude code before compiling — useful for debug output and for code that differs between Windows and Linux:

C
#include <stdio.h>
#define DEBUG 1

int main(void) {
    int total = 0;
    for (int i = 1; i <= 4; i++) {
        total += i * i;
#if DEBUG
        printf("[debug] i = %d, total = %d\n", i, total);
#endif
    }
    printf("Sum of squares: %d\n", total);
#ifdef _WIN32
    printf("Built for Windows\n");
#else
    printf("Built for Linux or macOS\n");
#endif
    return 0;
}
Output
[debug] i = 1, total = 1
[debug] i = 2, total = 5
[debug] i = 3, total = 14
[debug] i = 4, total = 30
Sum of squares: 30
Built for Linux or macOS

Header guards

When you split code into files, put declarations in a header. Wrap the header in a guard so including it twice does no harm:

C
// mathutil.h
#ifndef MATHUTIL_H
#define MATHUTIL_H

int gcd(int a, int b);
int lcm(int a, int b);

#endif

Command-line arguments

main can receive the words typed after the program name:

  • argc — argument count, including the program name.
  • argv — argument vector: an array of strings. argv[0] is the program name.
C
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc < 3) {
        printf("Usage: %s num1 num2\n", argv[0]);
        return 1;
    }
    int a = atoi(argv[1]), b = atoi(argv[2]);   // convert text to int
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}
Terminal
gcc add.c -o add
./add 12 30
# 12 + 30 = 42

Chapter 21 · Structures, Files & More

C Cheat Sheet & Exam Prep

Everything from CSE 101 on one page: syntax, format specifiers, operators, must-know programs and the mistakes that cost marks.

Use this page for last-minute revision. Every row links back to the full lesson.

Program skeleton

C
#include <stdio.h>

int main(void) {
    // declarations
    // input
    // processing
    // output
    return 0;
}

Data types and format specifiers

Type Size* printf scanf
char 1 %c %c (use " %c")
int 4 %d %d
unsigned int 4 %u %u
long long 8 %lld %lld
float 4 %f %f
double 8 %f / %lf %lf
string — %s %s (no &)
pointer 8 %p —
size_t 8 %zu %zu

* typical 64-bit sizes; check with sizeof. See Variables & Data Types.

Control flow at a glance

C
if (cond) { } else if (cond2) { } else { }
switch (n) { case 1: ...; break; default: ...; }
for (int i = 0; i < n; i++) { }
while (cond) { }
do { } while (cond);     // runs at least once
break;     // leave loop/switch
continue;  // skip to next iteration

Pointers in one table

Expression Meaning
int *p = &x; p stores the address of x
*p the value at that address
p + 1 address of the next int
a[i] ≡ *(a + i) array indexing is pointer arithmetic
p->m ≡ (*p).m member through a struct pointer
int **pp = &p; pointer to pointer
void f(int *x) call by reference

Must-practise programs

Topic Programs
Basics swap two numbers (with and without a temp), simple interest, area, temperature conversion
Decisions largest of three, leap year, grade calculator, vowel or consonant, calculator with switch
Loops factorial, Fibonacci, prime check, primes in a range, Armstrong number, palindrome number, sum of digits, GCD/LCM
Patterns right triangle, pyramid, diamond, Floyd's triangle, Pascal's triangle
Arrays max/min, second largest, reverse, linear search, frequency count, remove duplicates
2D arrays matrix add, multiply, transpose, diagonal sums
Strings length, reverse, palindrome, count vowels, without library functions
Recursion factorial, Fibonacci, power, sum of digits, Tower of Hanoi
Pointers swap with pointers, array via pointer, dynamic array with malloc
Structs & files student records, topper, save and read records from a file

Armstrong number (asked very often)

A number is an Armstrong number if the sum of its digits, each raised to the number of digits, equals the number: 153 = 1³ + 5³ + 3³.

C
#include <stdio.h>

int isArmstrong(int n) {
    int digits = 0, sum = 0;
    for (int t = n; t > 0; t /= 10) digits++;
    for (int t = n; t > 0; t /= 10) {
        int d = t % 10, p = 1;
        for (int i = 0; i < digits; i++) p *= d;
        sum += p;
    }
    return sum == n;
}

int main(void) {
    printf("Armstrong numbers below 10000: ");
    for (int i = 1; i < 10000; i++)
        if (isArmstrong(i)) printf("%d ", i);
    printf("\n");
    return 0;
}
Output
Armstrong numbers below 10000: 1 2 3 4 5 6 7 8 9 153 370 371 407 1634 8208 9474

Mistakes that cost marks

Mistake Fix
if (x = 5) if (x == 5)
scanf("%d", x) scanf("%d", &x)
scanf("%f", &d) for a double %lf
for (i = 0; i <= n; i++) on an array of n i < n
5 / 2 expecting 2.5 5.0 / 2 or a cast
str1 == str2 strcmp(str1, str2) == 0
missing break in switch add break; after each case
char s[5] = "Hello"; needs 6 bytes for '\0'
using gets fgets(buf, size, stdin)
malloc without free free every block exactly once

Theory questions to prepare

  1. Features of C and the structure of a C program.
  2. Compilation stages: preprocessor, compiler, assembler, linker.
  3. Difference between while and do-while; between break and continue.
  4. Call by value vs call by reference with an example.
  5. Recursion vs iteration.
  6. Storage classes with scope, lifetime and default values.
  7. Structure vs union.
  8. malloc vs calloc; what a memory leak is.
  9. File opening modes.
  10. Macro vs function.