Lecture 1 / 12
Lecture 01 · Foundations

Introduction to C

Beginner ~30 min No prerequisites

What is C?

C is a general-purpose, procedural programming language developed in 1972 by Dennis Ritchie at Bell Labs. It is one of the most influential languages ever created — the Linux kernel, Python interpreter, and countless embedded systems are written in C.

✅ Why learn C?
C gives you direct control over memory, teaches you how computers really work, and underpins virtually every operating system, embedded device, and high-performance application in the world.

Setting Up Your Environment

You need a C compiler. The most popular choice is GCC (GNU Compiler Collection).

Your First C Program

hello.c
#include <stdio.h>

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

Compiling and running

terminal
# Compile
gcc hello.c -o hello

# Run
./hello
▶ Output
Hello, World!

Anatomy of the Program

ℹ️ Header Files
Header files (like stdio.h) declare functions from the C Standard Library. You include them so the compiler knows the function signatures before you call them.
🎯 Exercise 1.1

Create hello.c, compile it with GCC, and run it. Then modify the program to print your name and the current year on separate lines using two printf() calls.

Lecture 02 · Foundations

Variables & Data Types

Beginner ~40 min Requires: Lecture 01

Fundamental Data Types

Type Size Range (typical) Format specifier
char 1 byte -128 to 127 %c
int 4 bytes -2,147,483,648 to 2,147,483,647 %d
float 4 bytes ~6-7 decimal digits precision %f
double 8 bytes ~15-16 decimal digits precision %lf
long 8 bytes very large integers %ld
unsigned int 4 bytes 0 to 4,294,967,295 %u

Declaring and Initialising Variables

variables.c
#include <stdio.h>

int main() {
    /* Integer */
    int age = 25;

    /* Floating point */
    float pi = 3.14159f;
    double precise_pi = 3.14159265358979;

    /* Character */
    char grade = 'A';

    /* Print them */
    printf("Age: %d\n", age);
    printf("Pi (float): %.2f\n", pi);
    printf("Pi (double): %.10lf\n", precise_pi);
    printf("Grade: %c\n", grade);

    return 0;
}
▶ Output
Age: 25
Pi (float): 3.14
Pi (double): 3.1415926536
Grade: A

Constants

Use const to declare a variable whose value cannot change after assignment.

constants.c
const double GRAVITY = 9.81;
const int    MAX_SIZE = 100;

/* GRAVITY = 10.0;  ← compile error! */

Reading User Input with scanf()

input.c
#include <stdio.h>

int main() {
    int num;
    printf("Enter a number: ");
    scanf("%d", &num);   /* & gives scanf the address of num */
    printf("You entered: %d\n", num);
    return 0;
}
⚠️ Always use & with scanf
The & operator gives scanf the memory address of the variable so it can write the value there. Forgetting it causes undefined behaviour and crashes.
🎯 Exercise 2.1

Write a program that asks the user for their name initial (a single char), their age (int), and their GPA (float). Print a formatted summary like:
Student: A | Age: 20 | GPA: 3.75

Lecture 03 · Foundations

Operators & Expressions

Beginner ~35 min Requires: Lecture 02

Arithmetic Operators

Operator Meaning Example Result
+ Addition 5 + 3 8
- Subtraction 5 - 3 2
* Multiplication 5 * 3 15
/ Division 7 / 2 3 (integer division!)
% Modulus (remainder) 7 % 2 1

Relational & Logical Operators

Type Operators Example
Relational == != < > <= >= a > b
Logical AND && a > 0 && b > 0
Logical OR || a == 0 || b == 0
Logical NOT ! !(a == b)

Increment, Decrement & Assignment

operators.c
int x = 5;
x++;          // x = 6 (post-increment)
++x;          // x = 7 (pre-increment)
x--;          // x = 6 (post-decrement)

x += 3;       // x = 9  (same as x = x + 3)
x *= 2;       // x = 18
x %= 5;       // x = 3

Type Casting

casting.c
int    a = 7, b = 2;
double result;

result = a / b;                  // 3.0  ← integer division
result = (double)a / b;         // 3.5  ← cast to double first
printf("%.1lf\n", result);       // 3.5
⚠️ Integer Division Gotcha
In C, dividing two integers always gives an integer result — 7/2 is 3, not 3.5. Cast one operand to double or float when you need decimal precision.
🎯 Exercise 3.1

Write a program that reads two integers from the user and prints: their sum, difference, product, integer quotient, remainder, and their exact division result as a double.

Lecture 04 · Foundations

Control Flow

Beginner ~50 min Requires: Lecture 03

if / else if / else

ifelse.c
#include <stdio.h>

int main() {
    int score;
    printf("Enter score: ");
    scanf("%d", &score);

    if (score >= 90) {
        printf("Grade: A\n");
    } else if (score >= 70) {
        printf("Grade: B\n");
    } else if (score >= 50) {
        printf("Grade: C\n");
    } else {
        printf("Grade: F\n");
    }

    return 0;
}

switch Statement

switch.c
char day = 'M';

switch (day) {
    case 'M': printf("Monday\n");   break;
    case 'T': printf("Tuesday\n");  break;
    case 'W': printf("Wednesday\n"); break;
    default:  printf("Other day\n"); break;
}

Loops

for loop

for_loop.c
for (int i = 1; i <= 5; i++) {
    printf("%d\n", i);
}

while loop

while_loop.c
int n = 1;
while (n <= 100) {
    n *= 2;
}
printf("First power of 2 above 100: %d\n", n);

do-while loop

dowhile.c
int choice;
do {
    printf("Enter a positive number: ");
    scanf("%d", &choice);
} while (choice <= 0);

break & continue

🎯 Exercise 4.1

Write a program that prints a multiplication table for a number entered by the user (1 through 12). Then write a second program that uses a do-while loop to keep asking for a password until the user types 1234.

Lecture 05 · Foundations

Functions

Beginner ~50 min Requires: Lecture 04

Defining and Calling Functions

Functions let you organise code into reusable blocks. In C you must declare a function before calling it (either via a prototype or by defining it first).

functions.c
#include <stdio.h>

/* Function prototype */
int add(int a, int b);
void greet(const char *name);

int main() {
    int result = add(3, 7);
    printf("3 + 7 = %d\n", result);
    greet("Alice");
    return 0;
}

/* Function definitions */
int add(int a, int b) {
    return a + b;
}

void greet(const char *name) {
    printf("Hello, %s!\n", name);
}

Recursion

A function that calls itself. The classic example: factorial.

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

// factorial(5) = 5 * 4 * 3 * 2 * 1 = 120

Pass by Value vs Pass by Reference

C is pass-by-value: functions receive a copy. To modify the original, pass a pointer.

passbyref.c
/* Does NOT modify x — works on a copy */
void tryDouble(int n)    { n *= 2; }

/* DOES modify x — passes address */
void realDouble(int *n) { *n *= 2; }

int main() {
    int x = 5;
    tryDouble(x);           // x is still 5
    realDouble(&x);         // x is now 10
    return 0;
}
✅ Scope Rule
Variables declared inside a function are local to it and disappear when the function returns. Variables declared outside all functions are global — visible everywhere in the file.
🎯 Exercise 5.1

Write three functions: isPrime(int n) that returns 1 if n is prime, fibonacci(int n) that returns the nth Fibonacci number recursively, and swap(int *a, int *b) that swaps two integers. Test each in main().

Lecture 06 · Core Concepts

Arrays & Strings

Intermediate ~55 min Requires: Lecture 05

Arrays

An array stores multiple values of the same type in contiguous memory locations, accessed by index starting at 0.

arrays.c
#include <stdio.h>

int main() {
    /* Declaration and initialisation */
    int scores[5] = {85, 92, 78, 95, 88};

    /* Access by index */
    printf("First: %d, Last: %d\n", scores[0], scores[4]);

    /* Iterate with a loop */
    int sum = 0;
    for (int i = 0; i < 5; i++) {
        sum += scores[i];
    }
    printf("Average: %.1f\n", (float)sum / 5);
    return 0;
}

2D Arrays

matrix.c
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

/* Print the matrix */
for (int r = 0; r < 3; r++) {
    for (int c = 0; c < 3; c++) {
        printf("%3d", matrix[r][c]);
    }
    printf("\n");
}

Strings in C

Strings are arrays of char ending with a null terminator '\0'. The <string.h> library provides useful functions.

strings.c
#include <stdio.h>
#include <string.h>

int main() {
    char name[50] = "Alice";
    char greeting[80];

    printf("Length: %zu\n", strlen(name));    // 5
    strcpy(greeting, "Hello, ");
    strcat(greeting, name);                   // "Hello, Alice"
    printf("%s\n", greeting);

    /* Compare strings (0 = equal) */
    if (strcmp(name, "Alice") == 0) {
        printf("Found Alice!\n");
    }
    return 0;
}
🚨 Buffer Overflow Risk
Never write more characters into a char array than its allocated size. Use strncpy(dest, src, size) and strncat() for safe copies with bounds checking.
🎯 Exercise 6.1

Write a program that reads an array of 5 integers, then: sorts them in ascending order using bubble sort, prints the sorted array, and prints the minimum, maximum, and average values.

Lecture 07 · Core Concepts

Pointers

Intermediate ~60 min Requires: Lecture 06

What is a Pointer?

A pointer is a variable that stores the memory address of another variable. This is C's most powerful (and most tricky) feature.

pointers.c
#include <stdio.h>

int main() {
    int  x   = 42;
    int *ptr = &x;   /* ptr holds the address of x */

    printf("Value of x    : %d\n",  x);
    printf("Address of x  : %p\n",  (void*)ptr);
    printf("Value via ptr : %d\n", *ptr); /* dereference */

    *ptr = 100;   /* modify x through the pointer */
    printf("x is now: %d\n", x);  // 100
    return 0;
}

Pointer Arithmetic

ptr_arith.c
int arr[] = {10, 20, 30, 40};
int *p = arr;          /* points to arr[0] */

printf("%d\n", *p);     // 10
p++;
printf("%d\n", *p);     // 20
printf("%d\n", *(p+2)); // 40

Pointers and Arrays

In C, the name of an array is a pointer to its first element. So arr[i] and *(arr + i) are equivalent.

Pointer to Pointer

ptr_to_ptr.c
int   val  = 5;
int  *p    = &val;
int **pp   = &p;

printf("%d\n", **pp);   // 5
🚨 Null & Dangling Pointers
Always initialise pointers. An uninitialised pointer is undefined behaviour. Set to NULL when no address is available yet, and never dereference a NULL pointer. A dangling pointer points to memory that has been freed — set the pointer to NULL after free().
🎯 Exercise 7.1

Write a function reverseArray(int *arr, int size) that reverses an array in-place using pointer arithmetic (no index notation like arr[i]). Verify it works in main().

Lecture 08 · Core Concepts

Structures & Unions

Intermediate ~50 min Requires: Lecture 07

Structures (struct)

A struct groups variables of different types under a single name — C's way of creating custom data types.

structs.c
#include <stdio.h>
#include <string.h>

/* Define a struct */
struct Student {
    char name[50];
    int  age;
    float gpa;
};

void printStudent(struct Student s) {
    printf("%s | Age: %d | GPA: %.2f\n", s.name, s.age, s.gpa);
}

int main() {
    struct Student s1;
    strcpy(s1.name, "Bob");
    s1.age = 20;
    s1.gpa = 3.8f;

    printStudent(s1);
    return 0;
}

typedef for Cleaner Code

typedef.c
typedef struct {
    float x, y;
} Point;

Point p1 = {3.0f, 4.0f};
printf("(%.1f, %.1f)\n", p1.x, p1.y);

Pointer to Struct (the -> operator)

struct_ptr.c
Point *pp = &p1;
printf("x = %.1f\n", pp->x); /* same as (*pp).x */

Unions

A union is like a struct, but all members share the same memory. Only one member can hold a value at a time. Useful for memory-efficient code and interpreting bits.

union.c
union Data {
    int   i;
    float f;
    char  c;
};

union Data d;
d.i = 65;
printf("%d  %c\n", d.i, d.c);  // 65  A (same bytes!)
🎯 Exercise 8.1

Create a struct Book with fields for title, author, year, and price. Write functions to create a book, print it, and find the most expensive book from an array of 5 books. Use pointers to pass structs efficiently.

Lecture 09 · Core Concepts

File I/O

Intermediate ~50 min Requires: Lecture 08

Opening and Closing Files

Use fopen() to open a file and fclose() when done. Always check that fopen() didn't return NULL.

Mode Meaning
"r" Read only. File must exist.
"w" Write. Creates or truncates file.
"a" Append. Creates if not exists.
"r+" Read and write. File must exist.
"rb" / "wb" Binary read / write.

Writing to a File

write_file.c
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        perror("Error opening file");
        return 1;
    }

    fprintf(fp, "Line 1: Hello, file!\n");
    fprintf(fp, "Line 2: Score = %d\n", 95);
    fclose(fp);
    printf("File written.\n");
    return 0;
}

Reading from a File

read_file.c
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "r");
    if (!fp) { perror("open"); return 1; }

    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        printf("%s", line);   /* fgets keeps the \n */
    }

    fclose(fp);
    return 0;
}

Binary Files

binary_io.c
typedef struct { int id; float score; } Record;

/* Write */
Record r = {1, 98.5f};
FILE *f = fopen("rec.bin", "wb");
fwrite(&r, sizeof(Record), 1, f);
fclose(f);

/* Read back */
Record r2;
f = fopen("rec.bin", "rb");
fread(&r2, sizeof(Record), 1, f);
fclose(f);
printf("ID: %d, Score: %.1f\n", r2.id, r2.score);
🎯 Exercise 9.1

Write a program that reads 5 student names and scores from the user, saves them to a text file students.txt, then reads the file back and prints the student with the highest score.

Lecture 10 · Advanced

Dynamic Memory Allocation

Advanced ~60 min Requires: Lecture 09

The Heap vs The Stack

Local variables live on the stack and are freed automatically. When you need memory whose size is unknown at compile time, allocate it on the heap using the functions in <stdlib.h>.

Function Purpose
malloc(size) Allocates size bytes. Contents uninitialised.
calloc(n, size) Allocates n × size bytes, zero-initialised.
realloc(ptr, new_size) Resizes a previous allocation.
free(ptr) Returns memory to the OS.
dynamic.c
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("How many numbers? ");
    scanf("%d", &n);

    /* Allocate an array at runtime */
    int *arr = (int*)malloc(n * sizeof(int));
    if (!arr) {
        fprintf(stderr, "Out of memory!\n");
        return 1;
    }

    for (int i = 0; i < n; i++) arr[i] = i * i;

    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    free(arr);     /* always free! */
    arr = NULL;    /* prevent dangling pointer */
    return 0;
}

Dynamic Linked List

linked_list.c
typedef struct Node {
    int data;
    struct Node *next;
} Node;

Node* newNode(int val) {
    Node *n = (Node*)malloc(sizeof(Node));
    n->data = val;
    n->next = NULL;
    return n;
}

/* Prepend a node to the list */
Node* prepend(Node *head, int val) {
    Node *n = newNode(val);
    n->next = head;
    return n;
}
🚨 Memory Leaks
Every malloc()/calloc() must be matched with exactly one free(). Use Valgrind (valgrind --leak-check=full ./program) to detect memory leaks.
🎯 Exercise 10.1

Implement a dynamic stack using a linked list with push(int val), pop(), and peek() operations. Allocate each node with malloc and free all nodes when done.

Lecture 11 · Advanced

Preprocessor & Macros

Advanced ~45 min Requires: Lecture 10

What is the Preprocessor?

Before compilation, GCC runs the C preprocessor (cpp) which processes all lines starting with #. This happens before the compiler sees your code.

#define — Object-like Macros

macros.c
#define PI          3.14159265
#define MAX_SIZE    100
#define SQUARE(x)  ((x) * (x))
#define MAX(a, b)  ((a) > (b) ? (a) : (b))

double area = PI * SQUARE(5.0);   // 78.539...
printf("%d\n", MAX(3, 7));         // 7
⚠️ Always Parenthesise Macro Arguments
Without parentheses, SQUARE(1+2) expands to 1+2 * 1+2 = 5 instead of 9. With parentheses: ((1+2)*(1+2)) = 9. ✅

Conditional Compilation

conditional.c
#define DEBUG 1

#ifdef DEBUG
    #define LOG(msg)  printf("[DEBUG] %s\n", msg)
#else
    #define LOG(msg)  /* nothing */
#endif

int main() {
    LOG("Starting program");
    /* ... */
    return 0;
}

Header Guards

Prevent a header file from being included more than once:

mylib.h
#ifndef MYLIB_H
#define MYLIB_H

void myFunction(void);
typedef struct { int x, y; } Point;

#endif  /* MYLIB_H */

Useful Predefined Macros

Macro Expands to
__FILE__ Current filename as a string
__LINE__ Current line number as an int
__DATE__ Compilation date string
__func__ Current function name (C99+)
🎯 Exercise 11.1

Create a header file mathutils.h with header guards and macros for ABS(x), MIN(a,b), MAX(a,b), and CLAMP(val,lo,hi). Include a DEBUG-controlled logging macro. Test all macros in a main program.

Lecture 12 · Capstone

Capstone Project — Student Record System

Advanced ~90 min Requires: All Lectures

Build a console-based Student Record Management System that exercises everything you have learned:

Project File Structure

project layout
student_system/
├── main.c          /* entry point, menu loop */
├── student.h       /* struct definition, prototypes */
├── student.c       /* add, delete, search, sort */
├── fileio.c        /* save / load binary file */
└── utils.h         /* DEBUG macro, MIN/MAX macros */

Starter Code

student.h
#ifndef STUDENT_H
#define STUDENT_H

#define MAX_NAME   60
#define NUM_SUBJ    5

typedef struct {
    int   roll;
    char  name[MAX_NAME];
    float marks[NUM_SUBJ];
    float gpa;
} Student;

/* Dynamic collection */
typedef struct {
    Student *data;
    int     size;
    int     capacity;
} StudentDB;

void    db_init    (StudentDB *db);
void    db_add    (StudentDB *db, Student s);
void    db_remove (StudentDB *db, int roll);
Student *db_find  (StudentDB *db, int roll);
void    db_sort   (StudentDB *db);
void    db_print  (StudentDB *db);
void    db_free   (StudentDB *db);

void    db_save   (StudentDB *db, const char *fname);
void    db_load   (StudentDB *db, const char *fname);

float   calcGPA   (float marks[], int n);

#endif
main.c (skeleton)
#include <stdio.h>
#include "student.h"
#include "utils.h"

#define DB_FILE "students.bin"

int main() {
    StudentDB db;
    db_init(&db);
    db_load(&db, DB_FILE);   /* load from disk */

    int choice;
    do {
        printf("\n=== STUDENT SYSTEM ===\n"
               "1. Add Student\n"
               "2. Delete Student\n"
               "3. Search by Roll\n"
               "4. Display All\n"
               "5. Sort by GPA\n"
               "0. Exit\n"
               "Choice: ");
        scanf("%d", &choice);

        switch (choice) {
            case 1: /* ... */ break;
            case 2: /* ... */ break;
            case 3: /* ... */ break;
            case 4: db_print(&db); break;
            case 5: db_sort(&db);  break;
        }
    } while (choice != 0);

    db_save(&db, DB_FILE);   /* persist to disk */
    db_free(&db);
    return 0;
}

What You Will Prove

🚀 Next Steps After the Capstone
  • Explore data structures in C: linked lists, binary trees, hash tables.
  • Learn POSIX threads (pthreads) for concurrent programming.
  • Study network programming with the BSD socket API.
  • Dive into C++ or embedded C (Arduino, STM32).
  • Read The C Programming Language by Kernighan & Ritchie — the definitive reference.
🎯 Final Challenge

Extend the system with a grade statistics report: for each subject compute the class average, highest, and lowest mark, and print a bar chart to the terminal using only printf. Run Valgrind — you must have zero leaks and zero errors.