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.
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.
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}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:
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:
gcc hello.c -o hello # compile hello.c into a program called hello
./hello # run itThe structure of a C program
Bigger programs follow the same skeleton. Your exam may ask you to label these parts:
/* 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;
}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:
- Every statement ends with a semicolon
;. - C is case-sensitive —
printfworks,Printfdoes not. - Blocks use curly braces
{ }. - Whitespace is ignored — indent for humans, not for the compiler.
- 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:
#include <stdio.h>
int main(void) {
printf("Learning ");
printf("C is fun.");
printf("\n");
printf("This is on a new line.\n");
return 0;
}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:
#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;
}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) |
#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;
}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.
#include <stdio.h>
int main(void) {
puts("Hello from puts");
putchar('O');
putchar('K');
putchar('\n');
return 0;
}Hello from puts OK
Comments
Comments are notes for humans. The compiler ignores them completely.
// 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 tooChapter 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.
#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;
}Age: 19 CGPA: 8.75 Grade: A Next year: 20
Declaring and naming variables
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 onceNaming rules (these are called identifiers):
- Use letters, digits and underscores only:
total_marks,num1. - Must not start with a digit:
1numis invalid. - Cannot be a keyword such as
int,return,while. - Case matters:
Sumandsumare 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:
#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;
}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 |
#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;
}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:
#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;
}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:
#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;
}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 &:
#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;
}Enter your age: 21
In 5 years you will be 26.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:
#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;
}Enter two numbers: 12 7
Sum = 19, Difference = 5, Product = 84
Quotient = 1.71Reading decimals: %f versus %lf
printf prints both float and double with %f, but scanf is pickier:
| Variable type | scanf specifier |
|---|---|
float |
%f |
double |
%lf |
#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;
}Weight in kg: 72.5 Height in m: 1.68 Your BMI is 25.7
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:
#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;
}Quantity: 3 Confirm order? (Y/N): Y You ordered 3 and answered Y
Reading strings and whole lines
%s reads one word (it stops at a space). For a full line with spaces, use fgets:
#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;
}First name: Asha City and state: Bengaluru, Karnataka Asha lives in Bengaluru, Karnataka.
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:
#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;
}Enter a number: abc
That was not a number!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 |
#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;
}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.
#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;
}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 |
#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;
}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 |
#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;
}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:
#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;
}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 |
#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;
}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.
#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;
}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
#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;
}Age: 17
Thanks for checking!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
#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;
}Enter a number: -7
-7 is oddelse if ladders
Check conditions top to bottom; the first true one wins and the rest are skipped.
#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;
}Marks out of 100: 83
Grade A+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.
#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;
}Year: 1900
1900 is not a leap yearThe 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:
#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;
}Enter two numbers: 12 4 Operator (+ - * /): / 3.00
Rules to remember:
- The
switchexpression must be an integer type (int,char,enum) — notfloator a string. - Each
caselabel must be a constant, and labels must be unique. breakexits the switch. Without it, execution falls through into the next case.defaultruns when no case matches. It is optional and can appear anywhere.
Fall-through is occasionally useful — for grouping cases:
#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;
}Letter: e
e is a vowelChapter 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.
#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;
}Enter a number: 40213
Digits: 5, Sum of digits: 10do...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:
#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;
}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.
for
A for loop packs the three parts of counting into one line: initialise; condition; update.
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 5; i++) {
printf("5 x %d = %d\n", i, 5 * i);
}
return 0;
}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.
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
#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;
}n = 10
10! = 3628800Prime check
A number is prime if no number from 2 to √n divides it. Checking only up to √n makes the loop much faster:
#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;
}n = 97
97 is primeFibonacci series
#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;
}How many terms? 10
0 1 1 2 3 5 8 13 21 34Reverse a number and check palindrome
#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;
}n = 12321
Reversed: 12321
12321 is a palindromeChapter 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:
#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;
}1 2 3 4 5 Loop ended early.
continue
continue skips the rest of the current pass and jumps to the next one:
#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;
}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.
#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;
}(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.
#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;
}* * * * * * * * * * * * * * *
Pattern 2: number triangle and Floyd's triangle
#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;
}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.
#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;
}* *** ***** ******* *********
Pattern 4: diamond
A diamond is a pyramid followed by an upside-down pyramid:
#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;
}* *** ***** ******* ***** *** *
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:
#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;
} 1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1Chapter 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
#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;
}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.
#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;
}==================== 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:
#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;
}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.
#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;
}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:
#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;
}Two numbers: 12 18
GCD = 6, LCM = 36gcd 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 |
#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;
}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:
- Base case — the smallest input, answered directly without recursion.
- 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:
#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;
}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:
factorial(4), five frames are on the stack. factorial(1) returns 1, then each frame multiplies and returns.factorial(4) = 4 * factorial(3)
= 4 * (3 * factorial(2))
= 4 * (3 * (2 * factorial(1)))
= 4 * (3 * (2 * 1))
= 24Fibonacci
fib(0) = 0, fib(1) = 1, fib(n) = fib(n−1) + fib(n−2):
#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;
}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).
fib(5). Highlighted calls are repeated work — fib(3) is computed twice and fib(2) three times.More recursive classics
#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;
}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.
#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;
}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
#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;
}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:
#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;
}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:
// 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].
#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;
}First mark: 78 Last mark: 71 marks[0] = 78 marks[1] = 92 marks[2] = 70 marks[3] = 88 marks[4] = 71
int marks[5] — five ints side by side in memory. Indexes run from 0 to size − 1.Declaring and initialising
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: 5The 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
#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;
}How many numbers? 6 Enter 6 numbers: 45 12 89 33 89 7 Sum = 275, Average = 45.83 Max = 89, Min = 7
Linear search
Check each element until you find the target. In the worst case you look at all n elements: O(n).
#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;
}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:
#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;
}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:
#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;
}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.
#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;
}Row 1, column 2: 7 1 2 3 4 5 6 7 8 9 10 11 12
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
#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;
}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.
#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;
}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.
#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;
}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
The three nested loops make this O(n³) for square matrices.
Diagonal sums
#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;
}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.
#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;
}ASHA has 5 bytes of storage name[0] = 'A' (65) name[1] = 'S' (83) name[2] = 'H' (72) name[3] = 'A' (65)
char name[] = "ASHA" takes 5 bytes: four letters plus the terminating '\0'.Declaring strings
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 |
#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;
}Length of "Data": 4
Joined: Data Structures
Copy: Data Structures
strcmp("apple", "banana") = -1
strcmp("cat", "cat") = 0
Found "Struct" at position 5Write them yourself
Exams frequently say "without using library functions". These four are the most common:
#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;
}Length: 11 Vowels: 3 Reversed: gnimmargorP "madam" palindrome? yes "kalvium" palindrome? no
Changing case with ctype.h
#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;
}Type a line: Hello, CSE 101!
Upper case: HELLO, CSE 101!
Letters 8, digits 3, spaces 2Arrays of strings
A list of words is a 2D char array — one row per word:
#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;
}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 doesxlive?"*p— the dereference operator: "the value stored at the address inp".
#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;
}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:
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:
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.
#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;
}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.
#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;
}*p = 10 *(p + 1) = 20 *(p + 4) = 50 after p++, *p = 20 byte distance between &a[3] and &a[0] = 12 element distance = 3
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:
#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;
}x = 7, *p = 7, **pp = 7 x after **pp = 99: 99
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.
#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;
}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:
#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;
}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:
#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;
}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 |
#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;
}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:
#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;
}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:
#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;
}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:
#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;
}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.
#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;
}Number of students: 5 Enter 5 marks: 80 92 67 74 88 Average: 80.2
calloc
calloc(count, size) allocates count × size bytes and sets them all to zero:
#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;
}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:
#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;
}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:
#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;
}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.
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.
#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;
}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:
#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;
}Area = 6.0
Arrays of structures
#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;
}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
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:
#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;
}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
#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;
}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:
#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;
}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.
#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;
}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:
#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;
}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
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
#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;
}Saved 3 records. Asha 91 Ravi 78 Meera 85 Class average: 84.67
Line by line with fgets, and appending
#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;
}1 | Monday: started C file handling 2 | Tuesday: wrote my first log file
Character by character: copy a file and count stats
#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;
}chars 52, words 11, lines 2
Binary files: saving structures
fwrite and fread copy raw bytes, which makes saving whole structures easy and fast:
#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;
}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
#include <stdio.h> // angle brackets: search the system include folders
#include "mylib.h" // quotes: search your project folder first#define: constants and macros
#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;
}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:
#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;
}BAD_SQUARE(3 + 1) = 7 GOOD_SQUARE(3 + 1) = 16 100 / BAD_SQUARE(5) = 100 100 / GOOD_SQUARE(5) = 4
Predefined macros
#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;
}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:
#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;
}[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:
// mathutil.h
#ifndef MATHUTIL_H
#define MATHUTIL_H
int gcd(int a, int b);
int lcm(int a, int b);
#endifCommand-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.
#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;
}gcc add.c -o add
./add 12 30
# 12 + 30 = 42Chapter 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
#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
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 iterationPointers 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³.
#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;
}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
- Features of C and the structure of a C program.
- Compilation stages: preprocessor, compiler, assembler, linker.
- Difference between
whileanddo-while; betweenbreakandcontinue. - Call by value vs call by reference with an example.
- Recursion vs iteration.
- Storage classes with scope, lifetime and default values.
- Structure vs union.
mallocvscalloc; what a memory leak is.- File opening modes.
- Macro vs function.